From cffa138c2f22dbd5f37d4074b44dcf99395e235d Mon Sep 17 00:00:00 2001 From: Julien Duroure Date: Wed, 17 May 2023 13:40:18 +0200 Subject: [PATCH 01/19] glTF: Bump version after 3.6 branch creation --- io_scene_gltf2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index bacd689d0..ef68d657f 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -4,7 +4,7 @@ bl_info = { 'name': 'glTF 2.0 format', 'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (3, 6, 18), + "version": (4, 0, 0), 'blender': (3, 5, 0), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', -- 2.30.2 From fa1da514e4ecba0daf6d66f55f141f40a88da0cd Mon Sep 17 00:00:00 2001 From: Sebastian Sille Date: Wed, 17 May 2023 19:28:35 +0200 Subject: [PATCH 02/19] io_scene_3ds: Raise version number for next Blender release Bump version --- io_scene_3ds/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_scene_3ds/__init__.py b/io_scene_3ds/__init__.py index 32da0f204..3a21498f2 100644 --- a/io_scene_3ds/__init__.py +++ b/io_scene_3ds/__init__.py @@ -16,7 +16,7 @@ import bpy bl_info = { "name": "Autodesk 3DS format", "author": "Bob Holcomb, Campbell Barton, Andreas Atteneder, Sebastian Schrand", - "version": (2, 3, 4), + "version": (2, 4, 0), "blender": (3, 6, 0), "location": "File > Import-Export", "description": "3DS Import/Export meshes, UVs, materials, textures, " -- 2.30.2 From 0c0b00d6dfd501992f5c5edcdbb837eedecaecc1 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 18 May 2023 13:38:14 -0400 Subject: [PATCH 03/19] Node Wrangler: Remove deprecated useless width_hidden property --- node_wrangler/operators.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/node_wrangler/operators.py b/node_wrangler/operators.py index 51f9d14a1..78d2b78cb 100644 --- a/node_wrangler/operators.py +++ b/node_wrangler/operators.py @@ -1349,7 +1349,6 @@ class NWMergeNodes(Operator, NWBase): if tree_type == 'COMPOSITING': first = 1 second = 2 - add.width_hidden = 100.0 elif nodes_list == selected_math: add_type = node_type + 'Math' add = nodes.new(add_type) @@ -1359,7 +1358,6 @@ class NWMergeNodes(Operator, NWBase): loc_y = loc_y - 50 first = 0 second = 1 - add.width_hidden = 100.0 elif nodes_list == selected_shader: if mode == 'MIX': add_type = node_type + 'MixShader' @@ -1369,7 +1367,6 @@ class NWMergeNodes(Operator, NWBase): loc_y = loc_y - 50 first = 1 second = 2 - add.width_hidden = 100.0 elif mode == 'ADD': add_type = node_type + 'AddShader' add = nodes.new(add_type) @@ -1378,7 +1375,6 @@ class NWMergeNodes(Operator, NWBase): loc_y = loc_y - 50 first = 0 second = 1 - add.width_hidden = 100.0 elif nodes_list == selected_geometry: if mode in ('JOIN', 'MIX'): add_type = node_type + 'JoinGeometry' @@ -1401,7 +1397,6 @@ class NWMergeNodes(Operator, NWBase): loc_y = loc_y - 50 first = 0 second = 1 - add.width_hidden = 100.0 elif nodes_list == selected_z: add = nodes.new('CompositorNodeZcombine') add.show_preview = False @@ -1410,7 +1405,6 @@ class NWMergeNodes(Operator, NWBase): loc_y = loc_y - 50 first = 0 second = 2 - add.width_hidden = 100.0 elif nodes_list == selected_alphaover: add = nodes.new('CompositorNodeAlphaOver') add.show_preview = False @@ -1419,7 +1413,6 @@ class NWMergeNodes(Operator, NWBase): loc_y = loc_y - 50 first = 1 second = 2 - add.width_hidden = 100.0 add.location = loc_x, loc_y loc_y += offset_y add.select = True @@ -2184,7 +2177,6 @@ class NWAddReroutes(Operator, NWBase): # unhide 'REROUTE' nodes to avoid issues with location.y if node.type == 'REROUTE': node.hide = False - # When node is hidden - width_hidden not usable. # Hack needed to calculate real width if node.hide: bpy.ops.node.select_all(action='DESELECT') @@ -2726,7 +2718,6 @@ class NWAddMultipleImages(Operator, NWBase, ImportHelper): new_nodes.append(node) node.label = fname node.hide = True - node.width_hidden = 100 node.location.x = xloc node.location.y = yloc yloc -= 40 -- 2.30.2 From ae2f35a6c8b4f3a0c37647d6074b9d2ae39e7280 Mon Sep 17 00:00:00 2001 From: Clemens Barth Date: Thu, 18 May 2023 23:33:13 +0200 Subject: [PATCH 04/19] io_mesh_atomic: mention correct wiki page url --- io_mesh_atomic/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_mesh_atomic/__init__.py b/io_mesh_atomic/__init__.py index 3c2e08483..bbf4b2ba8 100644 --- a/io_mesh_atomic/__init__.py +++ b/io_mesh_atomic/__init__.py @@ -2,7 +2,7 @@ # # # Author : Clemens Barth (Blendphys@root-1.de) -# Homepage(Wiki) : http://development.root-1.de/Atomic_Blender.php +# Homepage(Wiki) : https://docs.blender.org/manual/en/dev/addons/import_export/mesh_atomic.html # # Start of project : 2011-08-31 by CB # First publication in Blender : 2011-11-11 by CB -- 2.30.2 From d0b5970dca10a6f0f6013d7c5c3257a5b6744f73 Mon Sep 17 00:00:00 2001 From: Clemens Barth Date: Fri, 19 May 2023 10:31:56 +0200 Subject: [PATCH 05/19] Fix #104600, io_mesh_atomic: double or tripple bonds are now shown as double and tripple sticks when choosing option 'normal' sticks --- io_mesh_atomic/__init__.py | 2 +- io_mesh_atomic/pdb_import.py | 146 +++++++++++++++++++++++------------ 2 files changed, 97 insertions(+), 51 deletions(-) diff --git a/io_mesh_atomic/__init__.py b/io_mesh_atomic/__init__.py index bbf4b2ba8..e29b1167d 100644 --- a/io_mesh_atomic/__init__.py +++ b/io_mesh_atomic/__init__.py @@ -7,7 +7,7 @@ # Start of project : 2011-08-31 by CB # First publication in Blender : 2011-11-11 by CB # Fusion of the PDB, XYZ and Panel : 2019-03-22 by CB -# Last modified : 2019-05-17 +# Last modified : 2023-05-19 # # Contributing authors # ==================== diff --git a/io_mesh_atomic/pdb_import.py b/io_mesh_atomic/pdb_import.py index 01762a5ac..069adf888 100644 --- a/io_mesh_atomic/pdb_import.py +++ b/io_mesh_atomic/pdb_import.py @@ -1100,6 +1100,7 @@ def draw_sticks_normal(all_atoms, center, Stick_diameter, Stick_sectors, + Stick_dist, use_sticks_smooth, use_sticks_one_object, use_sticks_one_object_nr, @@ -1117,60 +1118,104 @@ def draw_sticks_normal(all_atoms, list_group = [] list_group_sub = [] counter = 0 - for stick in all_sticks: + for i, stick in enumerate(all_sticks): - # The vectors of the two atoms - atom1 = all_atoms[stick.atom1-1].location-center - atom2 = all_atoms[stick.atom2-1].location-center - # Location - location = (atom1 + atom2) * 0.5 - # The difference of both vectors - v = (atom2 - atom1) - # Angle with respect to the z-axis - angle = v.angle(up_axis, 0) - # Cross-product between v and the z-axis vector. It is the - # vector of rotation. - axis = up_axis.cross(v) - # Calculate Euler angles - euler = Matrix.Rotation(angle, 4, axis).to_euler() - # Create stick - stick = bpy.ops.mesh.primitive_cylinder_add(vertices=Stick_sectors, - radius=Stick_diameter, - depth=v.length, - end_fill_type='NGON', - align='WORLD', - enter_editmode=False, - location=location, - rotation=(0, 0, 0)) - # Put the stick into the scene ... - stick = bpy.context.view_layer.objects.active - # ... and rotate the stick. - stick.rotation_euler = euler - # ... and name - stick.name = "Stick_Cylinder" - counter += 1 + # We treat here single, double and tripple bonds: stick.number <= 3 + for repeat in range(stick.number): - # Smooth the cylinder. - if use_sticks_smooth == True: - bpy.ops.object.select_all(action='DESELECT') - stick.select_set(True) - bpy.ops.object.shade_smooth() + # The vectors of the two atoms + atom1 = copy(all_atoms[stick.atom1-1].location)-center + atom2 = copy(all_atoms[stick.atom2-1].location)-center - list_group_sub.append(stick) + dist = Stick_diameter * Stick_dist - if use_sticks_one_object == True: - if counter == use_sticks_one_object_nr: + # The two sticks are on the left and right of the middle connection. + if stick.number == 2: + if repeat == 0: + atom1 += (stick.dist * dist) + atom2 += (stick.dist * dist) + if repeat == 1: + atom1 -= (stick.dist * dist) + atom2 -= (stick.dist * dist) + + if stick.number == 3: + if repeat == 0: + atom1 += (stick.dist * dist) + atom2 += (stick.dist * dist) + if repeat == 2: + atom1 -= (stick.dist * dist) + atom2 -= (stick.dist * dist) + + # Vector pointing along the stick direction + dv = atom1 - atom2 + # The normalized vector of this, with lenght 1 + n = dv / dv.length + # Starting point of the stick + location = (atom1 + atom2) * 0.5 + # Angle with respect to the z-axis + angle = dv.angle(up_axis, 0) + # Cross-product between v and the z-axis vector. It is the + # vector of rotation. + axis = up_axis.cross(dv) + # Calculate Euler angles + euler = Matrix.Rotation(angle, 4, axis).to_euler() + # Create stick + stick_obj = bpy.ops.mesh.primitive_cylinder_add(vertices=Stick_sectors, + radius=Stick_diameter, + depth=dv.length, + end_fill_type='NGON', + align='WORLD', + enter_editmode=False, + location=location, + rotation=(0, 0, 0)) + # Put the stick into the scene ... + stick_obj = bpy.context.view_layer.objects.active + # ... and rotate the stick. + stick_obj.rotation_euler = euler + # ... and name + if stick.number == 1: + stick_obj.name = "Stick_Cylinder_%04d" %(i) + elif stick.number == 2: + if repeat == 0: + stick_obj.name = "Stick_Cylinder_%04d" %(i) + "_left" + elif repeat == 1: + stick_obj.name = "Stick_Cylinder_%04d" %(i) + "_right" + elif stick.number == 3: + if repeat == 0: + stick_obj.name = "Stick_Cylinder_%04d" %(i) + "_left" + elif repeat == 1: + stick_obj.name = "Stick_Cylinder_%04d" %(i) + "_middle" + elif repeat == 2: + stick_obj.name = "Stick_Cylinder_%04d" %(i) + "_right" + # Never occurs: + else: + stick_obj.name = "Stick_Cylinder" + # Never occurs: + else: + stick_obj.name = "Stick_Cylinder" + counter += 1 + + # Smooth the cylinder. + if use_sticks_smooth == True: bpy.ops.object.select_all(action='DESELECT') - for stick in list_group_sub: - stick.select_set(True) - bpy.ops.object.join() - list_group.append(bpy.context.view_layer.objects.active) - bpy.ops.object.select_all(action='DESELECT') - list_group_sub = [] - counter = 0 - else: - # Material ... - stick.active_material = stick_material + stick_obj.select_set(True) + bpy.ops.object.shade_smooth() + + list_group_sub.append(stick_obj) + + if use_sticks_one_object == True: + if counter == use_sticks_one_object_nr: + bpy.ops.object.select_all(action='DESELECT') + for stick_select in list_group_sub: + stick_select.select_set(True) + bpy.ops.object.join() + list_group.append(bpy.context.view_layer.objects.active) + bpy.ops.object.select_all(action='DESELECT') + list_group_sub = [] + counter = 0 + else: + # Material ... + stick_obj.active_material = stick_material if use_sticks_one_object == True: bpy.ops.object.select_all(action='DESELECT') @@ -1184,7 +1229,7 @@ def draw_sticks_normal(all_atoms, group.select_set(True) bpy.ops.object.join() bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', - center='MEDIAN') + center='MEDIAN') sticks = bpy.context.view_layer.objects.active sticks.active_material = stick_material @@ -1531,6 +1576,7 @@ def import_pdb(Ball_type, object_center_vec, Stick_diameter, Stick_sectors, + Stick_dist, use_sticks_smooth, use_sticks_one_object, use_sticks_one_object_nr, -- 2.30.2 From b4ebccb3e7faa44a82d49e937c65472424fec1be Mon Sep 17 00:00:00 2001 From: Clemens Barth Date: Fri, 19 May 2023 10:41:46 +0200 Subject: [PATCH 06/19] Fix #104600, io_mesh_atomic: 1. any distance between double and tripple bonds can be chosen, 2. better initial value, 3. better explanation --- io_mesh_atomic/pdb_gui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/io_mesh_atomic/pdb_gui.py b/io_mesh_atomic/pdb_gui.py index 88f7559ae..2c6435b13 100644 --- a/io_mesh_atomic/pdb_gui.py +++ b/io_mesh_atomic/pdb_gui.py @@ -96,8 +96,8 @@ class IMPORT_OT_pdb(Operator, ImportHelper): name="Bonds", default=False, description="Show double and triple bonds") sticks_dist: FloatProperty( - name="", default = 1.1, min=1.0, max=3.0, - description="Distance between sticks measured in stick diameter") + name="", default = 0.8, min=0.0, max=3.0, + description="Distance between sticks (double or tripple bonds) measured in stick diameter") use_sticks_one_object: BoolProperty( name="One object", default=False, description="All sticks are one object") -- 2.30.2 From 133cc4456b373b36c509e1308662fd2e348193d1 Mon Sep 17 00:00:00 2001 From: Clemens Barth Date: Fri, 19 May 2023 10:52:09 +0200 Subject: [PATCH 07/19] Fix #104600, io_mesh_atomic: because of the new stick feature 'double / tripple bonds' in the 'normal' representation type, show the property field 'distance' (distance between sticks) --- io_mesh_atomic/pdb_gui.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/io_mesh_atomic/pdb_gui.py b/io_mesh_atomic/pdb_gui.py index 2c6435b13..944d7bd25 100644 --- a/io_mesh_atomic/pdb_gui.py +++ b/io_mesh_atomic/pdb_gui.py @@ -184,7 +184,10 @@ class IMPORT_OT_pdb(Operator, ImportHelper): col = row.column() col.active = self.use_sticks_one_object col.prop(self, "use_sticks_one_object_nr") - + row = box.row() + row.active = self.use_sticks and self.use_sticks_bonds + row.label(text="Distance") + row.prop(self, "sticks_dist") def execute(self, context): # Switch to 'OBJECT' mode when in 'EDIT' mode. -- 2.30.2 From b9ad80de611ad471a2a3d0f0e468fbbea95c2b47 Mon Sep 17 00:00:00 2001 From: Clemens Barth Date: Fri, 19 May 2023 11:02:21 +0200 Subject: [PATCH 08/19] io_mesh_atomic: code polishing --- io_mesh_atomic/pdb_import.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/io_mesh_atomic/pdb_import.py b/io_mesh_atomic/pdb_import.py index 069adf888..f1c2abccf 100644 --- a/io_mesh_atomic/pdb_import.py +++ b/io_mesh_atomic/pdb_import.py @@ -556,7 +556,7 @@ def camera_light_source(use_camera, camera_factor = 15.0 - # If chosen a camera is put into the scene. + # If chosen, a camera is put into the scene. if use_camera == True: # Assume that the object is put into the global origin. Then, the @@ -850,7 +850,7 @@ def draw_sticks_dupliverts(all_atoms, i = 0 # What follows is school mathematics! :-) We construct equidistant - # planes, on which the stcik sections (cylinders) are perpendicular on. + # planes, on which the stick sections (cylinders) are perpendicular on. for stick in stick_list: dv = stick[2] -- 2.30.2 From 874d7bf8853e0b0159916d6296216c1a58cc7c36 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 19 May 2023 16:52:12 -0400 Subject: [PATCH 09/19] Carver: Update for change in bevel weights API See blender commit /blender/blender/commit/2a56403cb0dbcbc1dfb19a9bf7e64 --- object_carver/carver_utils.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/object_carver/carver_utils.py b/object_carver/carver_utils.py index c605111fb..b264a0ff3 100644 --- a/object_carver/carver_utils.py +++ b/object_carver/carver_utils.py @@ -308,11 +308,16 @@ def CreateBevel(context, CurrentObject): bpy.ops.object.mode_set(mode='OBJECT') - CurrentObject.data.use_customdata_edge_bevel = True + bevel_weights = CurrentObject.data.attributes["bevel_weight_edge"] + if not bevel_weights: + bevel_weights = CurrentObject.data.attributes.new("bevel_weight_edge", 'FLOAT', 'EDGE') + if bevel_weights.data_type != 'FLOAT' or bevel_weights.domain != 'EDGE': + bevel_weights = None for i in range(len(CurrentObject.data.edges)): if CurrentObject.data.edges[i].select is True: - CurrentObject.data.edges[i].bevel_weight = 1.0 + if bevel_weights: + bevel_weights.data[i] = 1.0 CurrentObject.data.edges[i].use_edge_sharp = True bevel_modifier = False -- 2.30.2 From 585a682ef82688536e2a6df937e7b86c3c3a3247 Mon Sep 17 00:00:00 2001 From: Sebastian Sille Date: Sat, 20 May 2023 03:45:36 +0200 Subject: [PATCH 10/19] io_scene_3ds: Added animation export option Added property to export keyframes --- io_scene_3ds/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/io_scene_3ds/__init__.py b/io_scene_3ds/__init__.py index 3a21498f2..9e52cd126 100644 --- a/io_scene_3ds/__init__.py +++ b/io_scene_3ds/__init__.py @@ -65,12 +65,12 @@ class Import3DS(bpy.types.Operator, ImportHelper): "importing incorrectly", default=True, ) - read_keyframe: bpy.props.BoolProperty( + read_keyframe: BoolProperty( name="Read Keyframe", description="Read the keyframe data", default=True, ) - use_world_matrix: bpy.props.BoolProperty( + use_world_matrix: BoolProperty( name="World Space", description="Transform to matrix world", default=False, @@ -109,6 +109,11 @@ class Export3DS(bpy.types.Operator, ExportHelper): description="Export selected objects only", default=False, ) + write_keyframe: BoolProperty( + name="Write Keyframe", + description="Write the keyframe data", + default=True, + ) def execute(self, context): from . import export_3ds -- 2.30.2 From da9bb97beafa4baac2a336ecbd5ebe2f5f47083f Mon Sep 17 00:00:00 2001 From: Sebastian Sille Date: Sat, 20 May 2023 04:34:25 +0200 Subject: [PATCH 11/19] Export_3ds: Added animation keyframe export Export 3ds now supports optional keyframe exporting. Additional cleanup Pull Request: https://projects.blender.org/blender/blender-addons/pulls/104613 --- io_scene_3ds/export_3ds.py | 620 ++++++++++++++++++++++++++++--------- 1 file changed, 467 insertions(+), 153 deletions(-) diff --git a/io_scene_3ds/export_3ds.py b/io_scene_3ds/export_3ds.py index 5d99ab101..2588eb6d8 100644 --- a/io_scene_3ds/export_3ds.py +++ b/io_scene_3ds/export_3ds.py @@ -121,6 +121,7 @@ KFDATA_KFCURTIME = 0xB009 # Frame current KFDATA_KFHDR = 0xB00A # Keyframe header # >------ sub defines of OBJECT_NODE_TAG +PARENT_NAME = 0x80F0 # Object parent name tree OBJECT_NODE_ID = 0xB030 # Object hierachy ID OBJECT_NODE_HDR = 0xB010 # Hierachy tree header OBJECT_INSTANCE_NAME = 0xB011 # Object instance name @@ -166,7 +167,7 @@ def sane_name(name): def uv_key(uv): return round(uv[0], 6), round(uv[1], 6) -# size defines: +# size defines SZ_SHORT = 2 SZ_INT = 4 SZ_FLOAT = 4 @@ -239,7 +240,7 @@ class _3ds_string(object): file.write(struct.pack(binary_format, self.value)) def __str__(self): - return str(self.value) + return str((self.value).decode("ASCII")) class _3ds_point_3d(object): @@ -260,15 +261,15 @@ class _3ds_point_3d(object): # Used for writing a track -''' class _3ds_point_4d(object): """Class representing a four-dimensional point for a 3ds file, for instance a quaternion.""" - __slots__ = "w","x","y","z" - def __init__(self, point=(0.0,0.0,0.0,0.0)): + __slots__ = "w", "x", "y", "z" + + def __init__(self, point): self.w, self.x, self.y, self.z = point def get_size(self): - return 4*SZ_FLOAT + return 4 * SZ_FLOAT def write(self,file): data=struct.pack('<4f', self.w, self.x, self.y, self.z) @@ -276,7 +277,6 @@ class _3ds_point_4d(object): def __str__(self): return '(%f, %f, %f, %f)' % (self.w, self.x, self.y, self.z) -''' class _3ds_point_uv(object): @@ -342,9 +342,7 @@ class _3ds_face(object): def get_size(self): return 4 * SZ_SHORT - # no need to validate every face vert. the oversized array will - # catch this problem - + # no need to validate every face vert. the oversized array will catch this problem def write(self, file): # The last short is used for face flags file.write(struct.pack('<4H', self.vindex[0], self.vindex[1], self.vindex[2], self.flag)) @@ -363,7 +361,7 @@ class _3ds_array(object): self.values = [] self.size = SZ_SHORT - # add an item: + # add an item def add(self, item): self.values.append(item) self.size += item.get_size() @@ -387,7 +385,6 @@ class _3ds_array(object): class _3ds_named_variable(object): """Convenience class for named variables.""" - __slots__ = "value", "name" def __init__(self, name, val=None): @@ -485,9 +482,9 @@ class _3ds_chunk(object): subchunk.dump(indent + 1) -########## -# EXPORT # -########## +############# +# MATERIALS # +############# def get_material_image(material): """ Get images from paint slots.""" @@ -685,9 +682,9 @@ def make_material_chunk(material, image): primary_tex = False if wrap.base_color_texture: - d_pct = 0.7 + sum(wrap.base_color[:]) * 0.1 color = [wrap.base_color_texture] - matmap = make_material_texture_chunk(MAT_DIFFUSEMAP, color, d_pct) + c_pct = 0.7 + sum(wrap.base_color[:]) * 0.1 + matmap = make_material_texture_chunk(MAT_DIFFUSEMAP, color, c_pct) if matmap: material_chunk.add_subchunk(matmap) primary_tex = True @@ -729,8 +726,8 @@ def make_material_chunk(material, image): material_chunk.add_subchunk(matmap) if wrap.emission_color_texture: - e_pct = wrap.emission_strength emission = [wrap.emission_color_texture] + e_pct = wrap.emission_strength matmap = make_material_texture_chunk(MAT_SELFIMAP, emission, e_pct) if matmap: material_chunk.add_subchunk(matmap) @@ -770,6 +767,10 @@ def make_material_chunk(material, image): return material_chunk +############# +# MESH DATA # +############# + class tri_wrapper(object): """Class representing a triangle. Used when converting faces to triangles""" @@ -849,31 +850,27 @@ def remove_face_uv(verts, tri_list): need to be converted to vertex uv coordinates. That means that vertices need to be duplicated when there are multiple uv coordinates per vertex.""" - # initialize a list of UniqueLists, one per vertex: - # uv_list = [UniqueList() for i in xrange(len(verts))] + # initialize a list of UniqueLists, one per vertex unique_uvs = [{} for i in range(len(verts))] # for each face uv coordinate, add it to the UniqueList of the vertex for tri in tri_list: for i in range(3): - # store the index into the UniqueList for future reference: + # store the index into the UniqueList for future reference # offset.append(uv_list[tri.vertex_index[i]].add(_3ds_point_uv(tri.faceuvs[i]))) context_uv_vert = unique_uvs[tri.vertex_index[i]] uvkey = tri.faceuvs[i] offset_index__uv_3ds = context_uv_vert.get(uvkey) - if not offset_index__uv_3ds: offset_index__uv_3ds = context_uv_vert[uvkey] = len(context_uv_vert), _3ds_point_uv(uvkey) tri.offset[i] = offset_index__uv_3ds[0] - # At this point, each vertex has a UniqueList containing every uv coordinate that is associated with it - # only once. - + # At this point each vertex has a UniqueList containing every uv coord associated with it only once # Now we need to duplicate every vertex as many times as it has uv coordinates and make sure the - # faces refer to the new face indices: + # faces refer to the new face indices vert_index = 0 vert_array = _3ds_array() uv_array = _3ds_array() @@ -894,12 +891,12 @@ def remove_face_uv(verts, tri_list): # Add the uv's in the correct order for uv_3ds in uvmap: - # add the uv coordinate to the uv array: + # add the uv coordinate to the uv array uv_array.add(uv_3ds) vert_index += len(unique_uvs[i]) - # Make sure the triangle vertex indices now refer to the new vertex list: + # Make sure the triangle vertex indices now refer to the new vertex list for tri in tri_list: for i in range(3): tri.offset[i] += index_list[tri.vertex_index[i]] @@ -1003,30 +1000,30 @@ def make_uv_chunk(uv_array): def make_mesh_chunk(ob, mesh, matrix, materialDict, translation): """Make a chunk out of a Blender mesh.""" - # Extract the triangles from the mesh: + # Extract the triangles from the mesh tri_list = extract_triangles(mesh) if mesh.uv_layers: - # Remove the face UVs and convert it to vertex UV: + # Remove the face UVs and convert it to vertex UV vert_array, uv_array, tri_list = remove_face_uv(mesh.vertices, tri_list) else: - # Add the vertices to the vertex array: + # Add the vertices to the vertex array vert_array = _3ds_array() for vert in mesh.vertices: vert_array.add(_3ds_point_3d(vert.co)) - # no UV at all: + # no UV at all uv_array = None - # create the chunk: + # create the chunk mesh_chunk = _3ds_chunk(OBJECT_MESH) - # add vertex chunk: + # add vertex chunk mesh_chunk.add_subchunk(make_vert_chunk(vert_array)) - # add faces chunk: + # add faces chunk mesh_chunk.add_subchunk(make_faces_chunk(tri_list, mesh, materialDict)) - # if available, add uv chunk: + # if available, add uv chunk if uv_array: mesh_chunk.add_subchunk(make_uv_chunk(uv_array)) @@ -1058,17 +1055,18 @@ def make_mesh_chunk(ob, mesh, matrix, materialDict, translation): return mesh_chunk -''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX -def make_kfdata(start=0, stop=0, curtime=0): +################# +# KEYFRAME DATA # +################# + +def make_kfdata(revision, start=0, stop=100, curtime=0): """Make the basic keyframe data chunk""" kfdata = _3ds_chunk(KFDATA) kfhdr = _3ds_chunk(KFDATA_KFHDR) - kfhdr.add_variable("revision", _3ds_ushort(0)) - # Not really sure what filename is used for, but it seems it is usually used - # to identify the program that generated the .3ds: - kfhdr.add_variable("filename", _3ds_string("Blender")) - kfhdr.add_variable("animlen", _3ds_uint(stop-start)) + kfhdr.add_variable("revision", _3ds_ushort(revision)) + kfhdr.add_variable("filename", _3ds_string(b'Blender')) + kfhdr.add_variable("animlen", _3ds_uint(stop - start)) kfseg = _3ds_chunk(KFDATA_KFSEG) kfseg.add_variable("start", _3ds_uint(start)) @@ -1082,107 +1080,396 @@ def make_kfdata(start=0, stop=0, curtime=0): kfdata.add_subchunk(kfcurtime) return kfdata -def make_track_chunk(ID, obj): - """Make a chunk for track data. - Depending on the ID, this will construct a position, rotation or scale track.""" +def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size): + """Make a chunk for track data. Depending on the ID, this will + construct a position, rotation, scale, roll, color or fov track.""" track_chunk = _3ds_chunk(ID) - track_chunk.add_variable("track_flags", _3ds_ushort()) - track_chunk.add_variable("unknown", _3ds_uint()) - track_chunk.add_variable("unknown", _3ds_uint()) - track_chunk.add_variable("nkeys", _3ds_uint(1)) - # Next section should be repeated for every keyframe, but for now, animation is not actually supported. - track_chunk.add_variable("tcb_frame", _3ds_uint(0)) - track_chunk.add_variable("tcb_flags", _3ds_ushort()) - if obj.type=='Empty': - if ID==POS_TRACK_TAG: - # position vector: - track_chunk.add_variable("position", _3ds_point_3d(obj.getLocation())) - elif ID==ROT_TRACK_TAG: - # rotation (quaternion, angle first, followed by axis): - q = obj.getEuler().to_quaternion() # XXX, todo! - track_chunk.add_variable("rotation", _3ds_point_4d((q.angle, q.axis[0], q.axis[1], q.axis[2]))) - elif ID==SCL_TRACK_TAG: - # scale vector: - track_chunk.add_variable("scale", _3ds_point_3d(obj.getSize())) + + if ID in {POS_TRACK_TAG, ROT_TRACK_TAG, SCL_TRACK_TAG} and ob.animation_data and ob.animation_data.action: + action = ob.animation_data.action + if action.fcurves: + fcurves = action.fcurves + kframes = [kf.co[0] for kf in [fc for fc in fcurves if fc is not None][0].keyframe_points] + nkeys = len(kframes) + if not 0 in kframes: + kframes.append(0) + nkeys = nkeys + 1 + kframes = sorted(set(kframes)) + track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) + track_chunk.add_variable("frame_start", _3ds_uint(int(action.frame_start))) + track_chunk.add_variable("frame_total", _3ds_uint(int(action.frame_end))) + track_chunk.add_variable("nkeys", _3ds_uint(nkeys)) + + if ID==POS_TRACK_TAG: # Position + for i, frame in enumerate(kframes): + position = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'location'] + if not position: + position.append(ob_pos) + track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) + track_chunk.add_variable("tcb_flags", _3ds_ushort()) + track_chunk.add_variable("position", _3ds_point_3d(position)) + + elif ID==ROT_TRACK_TAG: # Rotation + for i, frame in enumerate(kframes): + rotation = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'rotation_euler'] + if not rotation: + rotation.append(ob_rot) + quat = mathutils.Euler(rotation).to_quaternion() + axis_angle = quat.angle, quat.axis[0], quat.axis[1], quat.axis[2] + track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) + track_chunk.add_variable("tcb_flags", _3ds_ushort()) + track_chunk.add_variable("rotation", _3ds_point_4d(axis_angle)) + + elif ID==SCL_TRACK_TAG: # Scale + for i, frame in enumerate(kframes): + size = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'scale'] + if not size: + size.append(ob_size) + track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) + track_chunk.add_variable("tcb_flags", _3ds_ushort()) + track_chunk.add_variable("scale", _3ds_point_3d(size)) + + elif ID==ROLL_TRACK_TAG: # Roll + for i, frame in enumerate(kframes): + roll = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'rotation_euler'] + if not roll: + roll.append(ob_rot) + track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) + track_chunk.add_variable("tcb_flags", _3ds_ushort()) + track_chunk.add_variable("roll", _3ds_float(round(math.degrees(roll[1]), 4))) + + elif ID in {COL_TRACK_TAG, FOV_TRACK_TAG, HOTSPOT_TRACK_TAG, FALLOFF_TRACK_TAG} and ob.data.animation_data and ob.data.animation_data.action: + action = ob.data.animation_data.action + if action.fcurves: + fcurves = action.fcurves + kframes = [kf.co[0] for kf in [fc for fc in fcurves if fc is not None][0].keyframe_points] + nkeys = len(kframes) + if not 0 in kframes: + kframes.append(0) + nkeys = nkeys + 1 + kframes = sorted(set(kframes)) + track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) + track_chunk.add_variable("frame_start", _3ds_uint(int(action.frame_start))) + track_chunk.add_variable("frame_total", _3ds_uint(int(action.frame_end))) + track_chunk.add_variable("nkeys", _3ds_uint(nkeys)) + + if ID==COL_TRACK_TAG: # Color + for i, frame in enumerate(kframes): + color = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'color'] + if not color: + color.append(ob.data.color[:3]) + track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) + track_chunk.add_variable("tcb_flags", _3ds_ushort()) + track_chunk.add_variable("color", _3ds_float_color(color)) + + elif ID==FOV_TRACK_TAG: # Field of view + for i, frame in enumerate(kframes): + lens = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'lens'] + if not lens: + lens.append(ob.data.lens) + fov = 2 * math.atan(ob.data.sensor_width/(2*lens[0])) + track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) + track_chunk.add_variable("tcb_flags", _3ds_ushort()) + track_chunk.add_variable("fov", _3ds_float(round(math.degrees(fov), 4))) + + elif ID==HOTSPOT_TRACK_TAG: # Hotspot + beam_angle = math.degrees(ob.data.spot_size) + for i, frame in enumerate(kframes): + blend = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'spot_blend'] + if not blend: + blend.append(ob.data.spot_blend) + hot_spot = beam_angle-(blend[0]*math.floor(beam_angle)) + track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) + track_chunk.add_variable("tcb_flags", _3ds_ushort()) + track_chunk.add_variable("hotspot", _3ds_float(round(hot_spot, 4))) + + elif ID==FALLOFF_TRACK_TAG: # Falloff + for i, frame in enumerate(kframes): + fall_off = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'spot_size'] + if not fall_off: + fall_off.append(ob.data.spot_size) + track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) + track_chunk.add_variable("tcb_flags", _3ds_ushort()) + track_chunk.add_variable("falloff", _3ds_float(round(math.degrees(fall_off[0]), 4))) + else: - # meshes have their transformations applied before - # exporting, so write identity transforms here: - if ID==POS_TRACK_TAG: - # position vector: - track_chunk.add_variable("position", _3ds_point_3d((0.0,0.0,0.0))) - elif ID==ROT_TRACK_TAG: - # rotation (quaternion, angle first, followed by axis): - track_chunk.add_variable("rotation", _3ds_point_4d((0.0, 1.0, 0.0, 0.0))) - elif ID==SCL_TRACK_TAG: - # scale vector: - track_chunk.add_variable("scale", _3ds_point_3d((1.0, 1.0, 1.0))) + track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) # Based on observation default flag is 0x40 + track_chunk.add_variable("frame_start", _3ds_uint(0)) + track_chunk.add_variable("frame_total", _3ds_uint(0)) + track_chunk.add_variable("nkeys", _3ds_uint(1)) + # Next section should be repeated for every keyframe, with no animation only one tag is needed. + track_chunk.add_variable("tcb_frame", _3ds_uint(0)) + track_chunk.add_variable("tcb_flags", _3ds_ushort()) + + # New method simply inserts the parameters + if ID==POS_TRACK_TAG: # Position vector: + track_chunk.add_variable("position", _3ds_point_3d(ob_pos)) + + elif ID==ROT_TRACK_TAG: # Rotation (angle first [radians], followed by axis): + track_chunk.add_variable("rotation", _3ds_point_4d((ob_rot.angle, ob_rot.axis[0], ob_rot.axis[1], ob_rot.axis[2]))) + + elif ID==SCL_TRACK_TAG: # Scale vector: + track_chunk.add_variable("scale", _3ds_point_3d(ob_size)) + + elif ID==ROLL_TRACK_TAG: # Roll angle: + track_chunk.add_variable("roll", _3ds_float(round(math.degrees(ob.rotation_euler[1]), 4))) + + elif ID==COL_TRACK_TAG: # Color values: + track_chunk.add_variable("color", _3ds_float_color(ob.data.color)) + + elif ID==FOV_TRACK_TAG: # Field of view: + track_chunk.add_variable("fov", _3ds_float(round(math.degrees(ob.data.angle), 4))) + + elif ID==HOTSPOT_TRACK_TAG: # Hotspot: + beam_angle = math.degrees(ob.data.spot_size) + track_chunk.add_variable("hotspot", _3ds_float(round(beam_angle-(ob.data.spot_blend*math.floor(beam_angle)), 4))) + + elif ID==FALLOFF_TRACK_TAG: # Falloff: + track_chunk.add_variable("falloff", _3ds_float(round(math.degrees(ob.data.spot_size), 4))) return track_chunk -def make_kf_obj_node(obj, name_to_id): - """Make a node chunk for a Blender object. - Takes the Blender object as a parameter. Object id's are taken from the dictionary name_to_id. - Blender Empty objects are converted to dummy nodes.""" - name = obj.name - # main object node chunk: - kf_obj_node = _3ds_chunk(OBJECT_NODE_TAG) - # chunk for the object id: - obj_id_chunk = _3ds_chunk(OBJECT_NODE_ID) - # object id is from the name_to_id dictionary: - obj_id_chunk.add_variable("node_id", _3ds_ushort(name_to_id[name])) +def make_object_node(ob, translation, rotation, scale): + """Make a node chunk for a Blender object. Takes Blender object as parameter. + Blender Empty objects are converted to dummy nodes.""" - # object node header: + name = ob.name + if ob.type == 'CAMERA': + obj_node = _3ds_chunk(CAMERA_NODE_TAG) + elif ob.type == 'LIGHT': + obj_node = _3ds_chunk(LIGHT_NODE_TAG) + if ob.data.type == 'SPOT': + obj_node = _3ds_chunk(SPOT_NODE_TAG) + else: # Main object node chunk: + obj_node = _3ds_chunk(OBJECT_NODE_TAG) + + # Object node header with object name: obj_node_header_chunk = _3ds_chunk(OBJECT_NODE_HDR) - # object name: - if obj.type == 'Empty': - # Empties are called "$$$DUMMY" and use the OBJECT_INSTANCE_NAME chunk - # for their name (see below): - obj_node_header_chunk.add_variable("name", _3ds_string("$$$DUMMY")) - else: - # Add the name: + parent = ob.parent + + if ob.type == 'EMPTY': # Forcing to use the real name for empties + # Empties called $$$DUMMY and use OBJECT_INSTANCE_NAME chunk as name. + obj_node_header_chunk.add_variable("name", _3ds_string(b"$$$DUMMY")) + obj_node_header_chunk.add_variable("flags1", _3ds_ushort(0x4000)) + obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0)) + + else: # Add flag variables - Based on observation flags1 is usually 0x0040 and 0x4000 for empty objects obj_node_header_chunk.add_variable("name", _3ds_string(sane_name(name))) - # Add Flag variables (not sure what they do): - obj_node_header_chunk.add_variable("flags1", _3ds_ushort(0)) - obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0)) + obj_node_header_chunk.add_variable("flags1", _3ds_ushort(0x0040)) + # Flag 0x01 display path 0x02 use autosmooth 0x04 object frozen 0x10 motion blur 0x20 material morph 0x40 mesh morph + if ob.type == 'MESH' and ob.data.use_auto_smooth: + obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0x02)) + else: + obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0)) + obj_node_header_chunk.add_variable("parent", _3ds_ushort(ROOT_OBJECT)) + + ''' + # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX # Check parent-child relationships: - parent = obj.parent - if (parent is None) or (parent.name not in name_to_id): - # If no parent, or the parents name is not in the name_to_id dictionary, - # parent id becomes -1: + if parent is None or parent.name not in name_to_id: + # If no parent, or parents name is not in dictionary, ID becomes -1: obj_node_header_chunk.add_variable("parent", _3ds_ushort(-1)) - else: - # Get the parent's id from the name_to_id dictionary: + else: # Get the parent's ID from the name_to_id dictionary: obj_node_header_chunk.add_variable("parent", _3ds_ushort(name_to_id[parent.name])) + ''' - # Add pivot chunk: - obj_pivot_chunk = _3ds_chunk(OBJECT_PIVOT) - obj_pivot_chunk.add_variable("pivot", _3ds_point_3d(obj.getLocation())) - kf_obj_node.add_subchunk(obj_pivot_chunk) + # Add subchunk for node header + obj_node.add_subchunk(obj_node_header_chunk) - # add subchunks for object id and node header: - kf_obj_node.add_subchunk(obj_id_chunk) - kf_obj_node.add_subchunk(obj_node_header_chunk) - - # Empty objects need to have an extra chunk for the instance name: - if obj.type == 'Empty': + # Empty objects need to have an extra chunk for the instance name + if ob.type == 'EMPTY': # Will use a real object name for empties for now obj_instance_name_chunk = _3ds_chunk(OBJECT_INSTANCE_NAME) obj_instance_name_chunk.add_variable("name", _3ds_string(sane_name(name))) - kf_obj_node.add_subchunk(obj_instance_name_chunk) + obj_node.add_subchunk(obj_instance_name_chunk) - # Add track chunks for position, rotation and scale: - kf_obj_node.add_subchunk(make_track_chunk(POS_TRACK_TAG, obj)) - kf_obj_node.add_subchunk(make_track_chunk(ROT_TRACK_TAG, obj)) - kf_obj_node.add_subchunk(make_track_chunk(SCL_TRACK_TAG, obj)) + if ob.type in {'MESH', 'EMPTY'}: # Add a pivot point at the object center + pivot_pos = (translation[name]) + obj_pivot_chunk = _3ds_chunk(OBJECT_PIVOT) + obj_pivot_chunk.add_variable("pivot", _3ds_point_3d(pivot_pos)) + obj_node.add_subchunk(obj_pivot_chunk) - return kf_obj_node -''' + # Create a bounding box from quadrant diagonal + obj_boundbox = _3ds_chunk(OBJECT_BOUNDBOX) + obj_boundbox.add_variable("min", _3ds_point_3d(ob.bound_box[0])) + obj_boundbox.add_variable("max", _3ds_point_3d(ob.bound_box[6])) + obj_node.add_subchunk(obj_boundbox) + # Add smooth angle if autosmooth is used + if ob.type == 'MESH' and ob.data.use_auto_smooth: + obj_morph_smooth = _3ds_chunk(OBJECT_MORPH_SMOOTH) + obj_morph_smooth.add_variable("angle", _3ds_float(round(ob.data.auto_smooth_angle, 6))) + obj_node.add_subchunk(obj_morph_smooth) + + # Add track chunks for color, position, rotation and scale: + if parent is None: + ob_pos = translation[name] + ob_rot = rotation[name] + ob_size = scale[name] + + else: # Calculate child position and rotation of the object center, no scale applied + ob_pos = translation[name] - translation[parent.name] + ob_rot = rotation[name].cross(rotation[parent.name].copy().inverted()) + ob_size = (1.0, 1.0, 1.0) + + obj_node.add_subchunk(make_track_chunk(POS_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) + + if ob.type in {'MESH', 'EMPTY'}: + obj_node.add_subchunk(make_track_chunk(ROT_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) + obj_node.add_subchunk(make_track_chunk(SCL_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) + if ob.type =='CAMERA': + obj_node.add_subchunk(make_track_chunk(FOV_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) + obj_node.add_subchunk(make_track_chunk(ROLL_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) + if ob.type =='LIGHT': + obj_node.add_subchunk(make_track_chunk(COL_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) + if ob.type == 'LIGHT' and ob.data.type == 'SPOT': + obj_node.add_subchunk(make_track_chunk(HOTSPOT_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) + obj_node.add_subchunk(make_track_chunk(FALLOFF_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) + obj_node.add_subchunk(make_track_chunk(ROLL_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) + + return obj_node + + +def make_target_node(ob, translation, rotation, scale): + """Make a target chunk for light and camera objects""" + + name = ob.name + if ob.type == 'CAMERA': #Add camera target + tar_node = _3ds_chunk(TARGET_NODE_TAG) + elif ob.type == 'LIGHT': # Add spot target + tar_node = _3ds_chunk(LTARGET_NODE_TAG) + + # Object node header with object name: + tar_node_header_chunk = _3ds_chunk(OBJECT_NODE_HDR) + # Targets get the same name as the object, flags1 is usually 0x0010 and parent ROOT_OBJECT + tar_node_header_chunk.add_variable("name", _3ds_string(sane_name(name))) + tar_node_header_chunk.add_variable("flags1", _3ds_ushort(0x0010)) + tar_node_header_chunk.add_variable("flags2", _3ds_ushort(0)) + tar_node_header_chunk.add_variable("parent", _3ds_ushort(ROOT_OBJECT)) + + # Add subchunk for node header: + tar_node.add_subchunk(tar_node_header_chunk) + + # Calculate target position + ob_pos = translation[name] + ob_rot = rotation[name].to_euler() + ob_size = scale[name] + + diagonal = math.copysign(math.sqrt(pow(ob_pos[0],2)+pow(ob_pos[1],2)), ob_pos[1]) + target_x = ob_pos[0]+(ob_pos[1]*math.tan(ob_rot[2])) + target_y = ob_pos[1]+(ob_pos[0]*math.tan(math.radians(90)-ob_rot[2])) + target_z = -1*diagonal*math.tan(math.radians(90)-ob_rot[0]) + + # Add track chunks for target position: + track_chunk = _3ds_chunk(POS_TRACK_TAG) + + if ob.animation_data and ob.animation_data.action: + action = ob.animation_data.action + if action.fcurves: + fcurves = action.fcurves + kframes = [kf.co[0] for kf in [fc for fc in fcurves if fc is not None][0].keyframe_points] + nkeys = len(kframes) + if not 0 in kframes: + kframes.append(0) + nkeys = nkeys + 1 + kframes = sorted(set(kframes)) + track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) + track_chunk.add_variable("frame_start", _3ds_uint(int(action.frame_start))) + track_chunk.add_variable("frame_total", _3ds_uint(int(action.frame_end))) + track_chunk.add_variable("nkeys", _3ds_uint(nkeys)) + + for i, frame in enumerate(kframes): + target_pos = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'location'] + target_rot = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'rotation_euler'] + if not target_pos: + target_pos.append(ob_pos) + if not target_rot: + target_rot.insert(0, ob_rot.x) + target_rot.insert(1, ob_rot.y) + target_rot.insert(2, ob_rot.z) + diagonal = math.copysign(math.sqrt(pow(target_pos[0],2)+pow(target_pos[1],2)), target_pos[1]) + target_x = target_pos[0]+(target_pos[1]*math.tan(target_rot[2])) + target_y = target_pos[1]+(target_pos[0]*math.tan(math.radians(90)-target_rot[2])) + target_z = -1*diagonal*math.tan(math.radians(90)-target_rot[0]) + track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) + track_chunk.add_variable("tcb_flags", _3ds_ushort()) + track_chunk.add_variable("position", _3ds_point_3d((target_x, target_y, target_z))) + + else: # Track header + track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) # Based on observation default flag is 0x40 + track_chunk.add_variable("frame_start", _3ds_uint(0)) + track_chunk.add_variable("frame_total", _3ds_uint(0)) + track_chunk.add_variable("nkeys", _3ds_uint(1)) + # Keyframe header + track_chunk.add_variable("tcb_frame", _3ds_uint(0)) + track_chunk.add_variable("tcb_flags", _3ds_ushort()) + track_chunk.add_variable("position", _3ds_point_3d((target_x, target_y, target_z))) + + tar_node.add_subchunk(track_chunk) + + return tar_node + + +def make_ambient_node(world): + amb_color = world.color + amb_node = _3ds_chunk(AMBIENT_NODE_TAG) + track_chunk = _3ds_chunk(COL_TRACK_TAG) + + # Object node header, name is "$AMBIENT$" for ambient nodes: + amb_node_header_chunk = _3ds_chunk(OBJECT_NODE_HDR) + amb_node_header_chunk.add_variable("name", _3ds_string(b"$AMBIENT$")) + amb_node_header_chunk.add_variable("flags1", _3ds_ushort(0x4000)) # Flags1 0x4000 for empty objects + amb_node_header_chunk.add_variable("flags2", _3ds_ushort(0)) + amb_node_header_chunk.add_variable("parent", _3ds_ushort(ROOT_OBJECT)) + amb_node.add_subchunk(amb_node_header_chunk) + + if world.animation_data.action: + action = world.animation_data.action + if action.fcurves: + fcurves = action.fcurves + kframes = [kf.co[0] for kf in [fc for fc in fcurves if fc is not None][0].keyframe_points] + nkeys = len(kframes) + if not 0 in kframes: + kframes.append(0) + nkeys = nkeys + 1 + kframes = sorted(set(kframes)) + track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) + track_chunk.add_variable("frame_start", _3ds_uint(int(action.frame_start))) + track_chunk.add_variable("frame_total", _3ds_uint(int(action.frame_end))) + track_chunk.add_variable("nkeys", _3ds_uint(nkeys)) + + for i, frame in enumerate(kframes): + ambient = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'color'] + if not ambient: + ambient.append(world.color) + track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) + track_chunk.add_variable("tcb_flags", _3ds_ushort()) + track_chunk.add_variable("color", _3ds_float_color(ambient)) + + else: # Track header + track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) + track_chunk.add_variable("frame_start", _3ds_uint(0)) + track_chunk.add_variable("frame_total", _3ds_uint(0)) + track_chunk.add_variable("nkeys", _3ds_uint(1)) + # Keyframe header + track_chunk.add_variable("tcb_frame", _3ds_uint(0)) + track_chunk.add_variable("tcb_flags", _3ds_ushort()) + track_chunk.add_variable("color", _3ds_float_color(amb_color)) + + amb_node.add_subchunk(track_chunk) + + return amb_node + + +########## +# EXPORT # +########## def save(operator, context, filepath="", use_selection=True, + write_keyframe=True, global_matrix=None, ): @@ -1193,6 +1480,7 @@ def save(operator, scene = context.scene layer = context.view_layer depsgraph = context.evaluated_depsgraph_get() + world = scene.world if global_matrix is None: global_matrix = mathutils.Matrix() @@ -1200,15 +1488,15 @@ def save(operator, if bpy.ops.object.mode_set.poll(): bpy.ops.object.mode_set(mode='OBJECT') - # Initialize the main chunk (primary): + # Initialize the main chunk (primary) primary = _3ds_chunk(PRIMARY) - # Add version chunk: + # Add version chunk version_chunk = _3ds_chunk(VERSION) version_chunk.add_variable("version", _3ds_uint(3)) primary.add_subchunk(version_chunk) - # Init main object info chunk: + # Init main object info chunk object_info = _3ds_chunk(OBJECTINFO) mesh_version = _3ds_chunk(MESHVERSION) mesh_version.add_variable("mesh", _3ds_uint(3)) @@ -1219,21 +1507,26 @@ def save(operator, mscale.add_variable("scale", _3ds_float(1)) object_info.add_subchunk(mscale) + # Init main keyframe data chunk + if write_keyframe: + revision = 0x0005 + stop = scene.frame_end + start = scene.frame_start + curtime = scene.frame_current + kfdata = make_kfdata(revision, start, stop, curtime) + # Add AMBIENT color - if scene.world is not None: + if world is not None: ambient_chunk = _3ds_chunk(AMBIENTLIGHT) ambient_light = _3ds_chunk(RGB) ambient_light.add_variable("ambient", _3ds_float_color(scene.world.color)) ambient_chunk.add_subchunk(ambient_light) object_info.add_subchunk(ambient_chunk) - - ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX - # init main key frame data chunk: - kfdata = make_kfdata() - ''' + if write_keyframe and world.animation_data: + kfdata.add_subchunk(make_ambient_node(world)) # Make a list of all materials used in the selected meshes (use a dictionary, - # each material is added once): + # each material is added once) materialDict = {} mesh_objects = [] @@ -1270,7 +1563,7 @@ def save(operator, ma_ls = data.materials ma_ls_len = len(ma_ls) - # get material/image tuples. + # get material/image tuples if data.uv_layers: if not ma_ls: ma = ma_name = None @@ -1291,7 +1584,7 @@ def save(operator, else: for ma in ma_ls: - if ma: # material may be None so check its not. + if ma: # material may be None so check its not materialDict.setdefault((ma.name, None), (ma, None)) # Why 0 Why! @@ -1300,25 +1593,31 @@ def save(operator, f.material_index = 0 - # Make material chunks for all materials used in the meshes: + # Make material chunks for all materials used in the meshes for ma_image in materialDict.values(): object_info.add_subchunk(make_material_chunk(ma_image[0], ma_image[1])) # Collect translation for transformation matrix translation = {} + rotation = {} + scale = {} - # Give all objects a unique ID and build a dictionary from object name to object id: + # Give all objects a unique ID and build a dictionary from object name to object id # name_to_id = {} for ob, data, matrix in mesh_objects: translation[ob.name] = ob.location + rotation[ob.name] = ob.rotation_euler.to_quaternion().inverted() + scale[ob.name] = ob.scale # name_to_id[ob.name]= len(name_to_id) for ob in empty_objects: translation[ob.name] = ob.location + rotation[ob.name] = ob.rotation_euler.to_quaternion().inverted() + scale[ob.name] = ob.scale # name_to_id[ob.name]= len(name_to_id) - # Create object chunks for all meshes: + # Create object chunks for all meshes i = 0 for ob, mesh, matrix in mesh_objects: # create a new object chunk @@ -1327,34 +1626,34 @@ def save(operator, # set the object name object_chunk.add_variable("name", _3ds_string(sane_name(ob.name))) - # make a mesh chunk out of the mesh: + # make a mesh chunk out of the mesh object_chunk.add_subchunk(make_mesh_chunk(ob, mesh, matrix, materialDict, translation)) - # ensure the mesh has no over sized arrays - # skip ones that do!, otherwise we cant write since the array size wont - # fit into USHORT. + # ensure the mesh has no over sized arrays, skip ones that do! + # Otherwise we cant write since the array size wont fit into USHORT if object_chunk.validate(): object_info.add_subchunk(object_chunk) else: operator.report({'WARNING'}, "Object %r can't be written into a 3DS file") - ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX - # make a kf object node for the object: - kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id)) - ''' + # Export kf object node + if write_keyframe: + kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale)) i += i - # Create chunks for all empties: - ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX - for ob in empty_objects: - # Empties only require a kf object node: - kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id)) - pass - ''' + # Create chunks for all empties, only requires a kf object node + if write_keyframe: + for ob in empty_objects: + kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale)) # Create light object chunks for ob in light_objects: + translation[ob.name] = ob.location + rotation[ob.name] = ob.rotation_euler.to_quaternion() + scale[ob.name] = ob.scale + + # Add light data subchunks object_chunk = _3ds_chunk(OBJECT) light_chunk = _3ds_chunk(OBJECT_LIGHT) color_float_chunk = _3ds_chunk(RGB) @@ -1392,8 +1691,19 @@ def save(operator, object_chunk.add_subchunk(light_chunk) object_info.add_subchunk(object_chunk) + # Export light and spotlight target node + if write_keyframe: + kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale)) + if ob.data.type == 'SPOT': + kfdata.add_subchunk(make_target_node(ob, translation, rotation, scale)) + # Create camera object chunks for ob in camera_objects: + translation[ob.name] = ob.location + rotation[ob.name] = ob.rotation_euler.to_quaternion() + scale[ob.name] = ob.scale + + # Add camera data subchunks object_chunk = _3ds_chunk(OBJECT) camera_chunk = _3ds_chunk(OBJECT_CAMERA) diagonal = math.copysign(math.sqrt(pow(ob.location[0], 2) + pow(ob.location[1], 2)), ob.location[1]) @@ -1408,13 +1718,17 @@ def save(operator, object_chunk.add_subchunk(camera_chunk) object_info.add_subchunk(object_chunk) + # Export camera and target node + if write_keyframe: + kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale)) + kfdata.add_subchunk(make_target_node(ob, translation, rotation, scale)) + # Add main object info chunk to primary chunk: primary.add_subchunk(object_info) - ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX # Add main keyframe data chunk to primary chunk: - primary.add_subchunk(kfdata) - ''' + if write_keyframe: + primary.add_subchunk(kfdata) # At this point, the chunk hierarchy is completely built. # Check the size: -- 2.30.2 From cd265dfdd40f6b1655eb4cba3918de2899c97706 Mon Sep 17 00:00:00 2001 From: Sebastian Sille Date: Sat, 20 May 2023 11:26:53 +0200 Subject: [PATCH 12/19] io_scene_3ds: Set animation export default to false Set default to false for animation export --- io_scene_3ds/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_scene_3ds/__init__.py b/io_scene_3ds/__init__.py index 9e52cd126..ce3c65588 100644 --- a/io_scene_3ds/__init__.py +++ b/io_scene_3ds/__init__.py @@ -112,7 +112,7 @@ class Export3DS(bpy.types.Operator, ExportHelper): write_keyframe: BoolProperty( name="Write Keyframe", description="Write the keyframe data", - default=True, + default=False, ) def execute(self, context): -- 2.30.2 From c61c7cd104d75febe2556bcbba3ecd56aab19bc4 Mon Sep 17 00:00:00 2001 From: Sebastian Sille Date: Sat, 20 May 2023 11:35:33 +0200 Subject: [PATCH 13/19] Export_3ds: Set animation export default to false Set keyframe export default to false, still experimental and may cause errors Some cleanup --- io_scene_3ds/export_3ds.py | 65 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/io_scene_3ds/export_3ds.py b/io_scene_3ds/export_3ds.py index 2588eb6d8..de729c919 100644 --- a/io_scene_3ds/export_3ds.py +++ b/io_scene_3ds/export_3ds.py @@ -158,7 +158,7 @@ def sane_name(name): new_name = new_name_clean + '.%.3d' % i i += 1 - # note, appending the 'str' version. + # note, appending the 'str' version name_unique.append(new_name) name_mapping[name] = new_name = new_name.encode("ASCII", "replace") return new_name @@ -378,7 +378,7 @@ class _3ds_array(object): value.write(file) # To not overwhelm the output in a dump, a _3ds_array only - # outputs the number of items, not all of the actual items. + # outputs the number of items, not all of the actual items def __str__(self): return '(%d items)' % len(self.values) @@ -580,7 +580,7 @@ def make_material_texture_chunk(chunk_id, texslots, pct): 0x40 activates alpha source, 0x80 activates tinting, 0x100 ignores alpha, 0x200 activates RGB tint. Bits 0x80, 0x100, and 0x200 are only used with TEXMAP, TEX2MAP, and SPECMAP chunks. 0x40, when used with a TEXMAP, TEX2MAP, or SPECMAP chunk must be accompanied with a tint bit, - either 0x100 or 0x200, tintcolor will be processed if colorchunks are present""" + either 0x100 or 0x200, tintcolor will be processed if a tintflag is present""" mapflags = 0 if texslot.extension == 'EXTEND': @@ -629,9 +629,8 @@ def make_material_texture_chunk(chunk_id, texslots, pct): rgb.add_variable("mapcolor", _3ds_rgb_color(spec if texslot.socket_dst.identifier == 'Specular' else base)) mat_sub.add_subchunk(rgb) - # store all textures for this mapto in order. This at least is what - # the 3DS exporter did so far, afaik most readers will just skip - # over 2nd textures. + # store all textures for this mapto in order. This at least is what the + # 3DS exporter did so far, afaik most readers will just skip over 2nd textures for slot in texslots: if slot.image is not None: add_texslot(slot) @@ -1197,34 +1196,34 @@ def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size): track_chunk.add_variable("frame_start", _3ds_uint(0)) track_chunk.add_variable("frame_total", _3ds_uint(0)) track_chunk.add_variable("nkeys", _3ds_uint(1)) - # Next section should be repeated for every keyframe, with no animation only one tag is needed. + # Next section should be repeated for every keyframe, with no animation only one tag is needed track_chunk.add_variable("tcb_frame", _3ds_uint(0)) track_chunk.add_variable("tcb_flags", _3ds_ushort()) # New method simply inserts the parameters - if ID==POS_TRACK_TAG: # Position vector: + if ID==POS_TRACK_TAG: # Position vector track_chunk.add_variable("position", _3ds_point_3d(ob_pos)) - elif ID==ROT_TRACK_TAG: # Rotation (angle first [radians], followed by axis): + elif ID==ROT_TRACK_TAG: # Rotation (angle first [radians], followed by axis) track_chunk.add_variable("rotation", _3ds_point_4d((ob_rot.angle, ob_rot.axis[0], ob_rot.axis[1], ob_rot.axis[2]))) - elif ID==SCL_TRACK_TAG: # Scale vector: + elif ID==SCL_TRACK_TAG: # Scale vector track_chunk.add_variable("scale", _3ds_point_3d(ob_size)) - elif ID==ROLL_TRACK_TAG: # Roll angle: + elif ID==ROLL_TRACK_TAG: # Roll angle track_chunk.add_variable("roll", _3ds_float(round(math.degrees(ob.rotation_euler[1]), 4))) - elif ID==COL_TRACK_TAG: # Color values: + elif ID==COL_TRACK_TAG: # Color values track_chunk.add_variable("color", _3ds_float_color(ob.data.color)) - elif ID==FOV_TRACK_TAG: # Field of view: + elif ID==FOV_TRACK_TAG: # Field of view track_chunk.add_variable("fov", _3ds_float(round(math.degrees(ob.data.angle), 4))) - elif ID==HOTSPOT_TRACK_TAG: # Hotspot: + elif ID==HOTSPOT_TRACK_TAG: # Hotspot beam_angle = math.degrees(ob.data.spot_size) track_chunk.add_variable("hotspot", _3ds_float(round(beam_angle-(ob.data.spot_blend*math.floor(beam_angle)), 4))) - elif ID==FALLOFF_TRACK_TAG: # Falloff: + elif ID==FALLOFF_TRACK_TAG: # Falloff track_chunk.add_variable("falloff", _3ds_float(round(math.degrees(ob.data.spot_size), 4))) return track_chunk @@ -1241,15 +1240,15 @@ def make_object_node(ob, translation, rotation, scale): obj_node = _3ds_chunk(LIGHT_NODE_TAG) if ob.data.type == 'SPOT': obj_node = _3ds_chunk(SPOT_NODE_TAG) - else: # Main object node chunk: + else: # Main object node chunk obj_node = _3ds_chunk(OBJECT_NODE_TAG) - # Object node header with object name: + # Object node header with object name obj_node_header_chunk = _3ds_chunk(OBJECT_NODE_HDR) parent = ob.parent if ob.type == 'EMPTY': # Forcing to use the real name for empties - # Empties called $$$DUMMY and use OBJECT_INSTANCE_NAME chunk as name. + # Empties called $$$DUMMY and use OBJECT_INSTANCE_NAME chunk as name obj_node_header_chunk.add_variable("name", _3ds_string(b"$$$DUMMY")) obj_node_header_chunk.add_variable("flags1", _3ds_ushort(0x4000)) obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0)) @@ -1302,7 +1301,7 @@ def make_object_node(ob, translation, rotation, scale): obj_morph_smooth.add_variable("angle", _3ds_float(round(ob.data.auto_smooth_angle, 6))) obj_node.add_subchunk(obj_morph_smooth) - # Add track chunks for color, position, rotation and scale: + # Add track chunks for color, position, rotation and scale if parent is None: ob_pos = translation[name] ob_rot = rotation[name] @@ -1340,7 +1339,7 @@ def make_target_node(ob, translation, rotation, scale): elif ob.type == 'LIGHT': # Add spot target tar_node = _3ds_chunk(LTARGET_NODE_TAG) - # Object node header with object name: + # Object node header with object name tar_node_header_chunk = _3ds_chunk(OBJECT_NODE_HDR) # Targets get the same name as the object, flags1 is usually 0x0010 and parent ROOT_OBJECT tar_node_header_chunk.add_variable("name", _3ds_string(sane_name(name))) @@ -1348,7 +1347,7 @@ def make_target_node(ob, translation, rotation, scale): tar_node_header_chunk.add_variable("flags2", _3ds_ushort(0)) tar_node_header_chunk.add_variable("parent", _3ds_ushort(ROOT_OBJECT)) - # Add subchunk for node header: + # Add subchunk for node header tar_node.add_subchunk(tar_node_header_chunk) # Calculate target position @@ -1361,7 +1360,7 @@ def make_target_node(ob, translation, rotation, scale): target_y = ob_pos[1]+(ob_pos[0]*math.tan(math.radians(90)-ob_rot[2])) target_z = -1*diagonal*math.tan(math.radians(90)-ob_rot[0]) - # Add track chunks for target position: + # Add track chunks for target position track_chunk = _3ds_chunk(POS_TRACK_TAG) if ob.animation_data and ob.animation_data.action: @@ -1416,7 +1415,7 @@ def make_ambient_node(world): amb_node = _3ds_chunk(AMBIENT_NODE_TAG) track_chunk = _3ds_chunk(COL_TRACK_TAG) - # Object node header, name is "$AMBIENT$" for ambient nodes: + # Object node header, name is "$AMBIENT$" for ambient nodes amb_node_header_chunk = _3ds_chunk(OBJECT_NODE_HDR) amb_node_header_chunk.add_variable("name", _3ds_string(b"$AMBIENT$")) amb_node_header_chunk.add_variable("flags1", _3ds_ushort(0x4000)) # Flags1 0x4000 for empty objects @@ -1468,8 +1467,8 @@ def make_ambient_node(world): def save(operator, context, filepath="", - use_selection=True, - write_keyframe=True, + use_selection=False, + write_keyframe=False, global_matrix=None, ): @@ -1723,34 +1722,34 @@ def save(operator, kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale)) kfdata.add_subchunk(make_target_node(ob, translation, rotation, scale)) - # Add main object info chunk to primary chunk: + # Add main object info chunk to primary chunk primary.add_subchunk(object_info) - # Add main keyframe data chunk to primary chunk: + # Add main keyframe data chunk to primary chunk if write_keyframe: primary.add_subchunk(kfdata) # At this point, the chunk hierarchy is completely built. - # Check the size: + # Check the size primary.get_size() - # Open the file for writing: + # Open the file for writing file = open(filepath, 'wb') - # Recursively write the chunks to file: + # Recursively write the chunks to file primary.write(file) - # Close the file: + # Close the file file.close() # Clear name mapping vars, could make locals too del name_unique[:] name_mapping.clear() - # Debugging only: report the exporting time: + # Debugging only: report the exporting time print("3ds export time: %.2f" % (time.time() - duration)) - # Debugging only: dump the chunk hierarchy: + # Debugging only: dump the chunk hierarchy # primary.dump() return {'FINISHED'} -- 2.30.2 From a899109ac7f9b1a30ea3e84f1db3083a5e11f936 Mon Sep 17 00:00:00 2001 From: Sebastian Sille Date: Sat, 20 May 2023 13:20:01 +0200 Subject: [PATCH 14/19] io_scene_3ds: Bump version for 3ds animation export Raised version number for new animation export property --- io_scene_3ds/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_scene_3ds/__init__.py b/io_scene_3ds/__init__.py index ce3c65588..35db8c4b8 100644 --- a/io_scene_3ds/__init__.py +++ b/io_scene_3ds/__init__.py @@ -16,7 +16,7 @@ import bpy bl_info = { "name": "Autodesk 3DS format", "author": "Bob Holcomb, Campbell Barton, Andreas Atteneder, Sebastian Schrand", - "version": (2, 4, 0), + "version": (2, 4, 1), "blender": (3, 6, 0), "location": "File > Import-Export", "description": "3DS Import/Export meshes, UVs, materials, textures, " -- 2.30.2 From 2563fced2d586c4a8c57c09f32fac972fb7b7d0e Mon Sep 17 00:00:00 2001 From: Sebastian Sille Date: Sun, 21 May 2023 06:25:37 +0200 Subject: [PATCH 15/19] Export_3ds: Added roll angle to keyframe export missed to add the roll angle added some space --- io_scene_3ds/export_3ds.py | 39 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/io_scene_3ds/export_3ds.py b/io_scene_3ds/export_3ds.py index de729c919..df07fc9b2 100644 --- a/io_scene_3ds/export_3ds.py +++ b/io_scene_3ds/export_3ds.py @@ -882,7 +882,7 @@ def remove_face_uv(verts, tri_list): for ii, uv_3ds in unique_uvs[i].values(): # add a vertex duplicate to the vertex_array for every uv associated with this vertex: vert_array.add(pt) - # add the uv coordinate to the uv array: + # add the uv coordinate to the uv array # This for loop does not give uv's ordered by ii, so we create a new map # and add the uv's later # uv_array.add(uv_3ds) @@ -1084,7 +1084,7 @@ def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size): construct a position, rotation, scale, roll, color or fov track.""" track_chunk = _3ds_chunk(ID) - if ID in {POS_TRACK_TAG, ROT_TRACK_TAG, SCL_TRACK_TAG} and ob.animation_data and ob.animation_data.action: + if ID in {POS_TRACK_TAG, ROT_TRACK_TAG, SCL_TRACK_TAG, ROLL_TRACK_TAG} and ob.animation_data and ob.animation_data.action: action = ob.animation_data.action if action.fcurves: fcurves = action.fcurves @@ -1099,7 +1099,7 @@ def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size): track_chunk.add_variable("frame_total", _3ds_uint(int(action.frame_end))) track_chunk.add_variable("nkeys", _3ds_uint(nkeys)) - if ID==POS_TRACK_TAG: # Position + if ID == POS_TRACK_TAG: # Position for i, frame in enumerate(kframes): position = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'location'] if not position: @@ -1108,7 +1108,7 @@ def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size): track_chunk.add_variable("tcb_flags", _3ds_ushort()) track_chunk.add_variable("position", _3ds_point_3d(position)) - elif ID==ROT_TRACK_TAG: # Rotation + elif ID == ROT_TRACK_TAG: # Rotation for i, frame in enumerate(kframes): rotation = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'rotation_euler'] if not rotation: @@ -1119,7 +1119,7 @@ def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size): track_chunk.add_variable("tcb_flags", _3ds_ushort()) track_chunk.add_variable("rotation", _3ds_point_4d(axis_angle)) - elif ID==SCL_TRACK_TAG: # Scale + elif ID == SCL_TRACK_TAG: # Scale for i, frame in enumerate(kframes): size = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'scale'] if not size: @@ -1128,7 +1128,7 @@ def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size): track_chunk.add_variable("tcb_flags", _3ds_ushort()) track_chunk.add_variable("scale", _3ds_point_3d(size)) - elif ID==ROLL_TRACK_TAG: # Roll + elif ID == ROLL_TRACK_TAG: # Roll for i, frame in enumerate(kframes): roll = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'rotation_euler'] if not roll: @@ -1152,7 +1152,7 @@ def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size): track_chunk.add_variable("frame_total", _3ds_uint(int(action.frame_end))) track_chunk.add_variable("nkeys", _3ds_uint(nkeys)) - if ID==COL_TRACK_TAG: # Color + if ID == COL_TRACK_TAG: # Color for i, frame in enumerate(kframes): color = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'color'] if not color: @@ -1161,7 +1161,7 @@ def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size): track_chunk.add_variable("tcb_flags", _3ds_ushort()) track_chunk.add_variable("color", _3ds_float_color(color)) - elif ID==FOV_TRACK_TAG: # Field of view + elif ID == FOV_TRACK_TAG: # Field of view for i, frame in enumerate(kframes): lens = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'lens'] if not lens: @@ -1171,7 +1171,7 @@ def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size): track_chunk.add_variable("tcb_flags", _3ds_ushort()) track_chunk.add_variable("fov", _3ds_float(round(math.degrees(fov), 4))) - elif ID==HOTSPOT_TRACK_TAG: # Hotspot + elif ID == HOTSPOT_TRACK_TAG: # Hotspot beam_angle = math.degrees(ob.data.spot_size) for i, frame in enumerate(kframes): blend = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'spot_blend'] @@ -1182,7 +1182,7 @@ def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size): track_chunk.add_variable("tcb_flags", _3ds_ushort()) track_chunk.add_variable("hotspot", _3ds_float(round(hot_spot, 4))) - elif ID==FALLOFF_TRACK_TAG: # Falloff + elif ID == FALLOFF_TRACK_TAG: # Falloff for i, frame in enumerate(kframes): fall_off = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'spot_size'] if not fall_off: @@ -1201,29 +1201,29 @@ def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size): track_chunk.add_variable("tcb_flags", _3ds_ushort()) # New method simply inserts the parameters - if ID==POS_TRACK_TAG: # Position vector + if ID == POS_TRACK_TAG: # Position vector track_chunk.add_variable("position", _3ds_point_3d(ob_pos)) - elif ID==ROT_TRACK_TAG: # Rotation (angle first [radians], followed by axis) + elif ID == ROT_TRACK_TAG: # Rotation (angle first [radians], followed by axis) track_chunk.add_variable("rotation", _3ds_point_4d((ob_rot.angle, ob_rot.axis[0], ob_rot.axis[1], ob_rot.axis[2]))) - elif ID==SCL_TRACK_TAG: # Scale vector + elif ID == SCL_TRACK_TAG: # Scale vector track_chunk.add_variable("scale", _3ds_point_3d(ob_size)) - elif ID==ROLL_TRACK_TAG: # Roll angle + elif ID == ROLL_TRACK_TAG: # Roll angle track_chunk.add_variable("roll", _3ds_float(round(math.degrees(ob.rotation_euler[1]), 4))) - elif ID==COL_TRACK_TAG: # Color values + elif ID == COL_TRACK_TAG: # Color values track_chunk.add_variable("color", _3ds_float_color(ob.data.color)) - elif ID==FOV_TRACK_TAG: # Field of view + elif ID == FOV_TRACK_TAG: # Field of view track_chunk.add_variable("fov", _3ds_float(round(math.degrees(ob.data.angle), 4))) - elif ID==HOTSPOT_TRACK_TAG: # Hotspot + elif ID == HOTSPOT_TRACK_TAG: # Hotspot beam_angle = math.degrees(ob.data.spot_size) track_chunk.add_variable("hotspot", _3ds_float(round(beam_angle-(ob.data.spot_blend*math.floor(beam_angle)), 4))) - elif ID==FALLOFF_TRACK_TAG: # Falloff + elif ID == FALLOFF_TRACK_TAG: # Falloff track_chunk.add_variable("falloff", _3ds_float(round(math.degrees(ob.data.spot_size), 4))) return track_chunk @@ -1257,7 +1257,8 @@ def make_object_node(ob, translation, rotation, scale): obj_node_header_chunk.add_variable("name", _3ds_string(sane_name(name))) obj_node_header_chunk.add_variable("flags1", _3ds_ushort(0x0040)) - # Flag 0x01 display path 0x02 use autosmooth 0x04 object frozen 0x10 motion blur 0x20 material morph 0x40 mesh morph + # Flags2 defines bit 0x01 for display path, bit 0x02 use autosmooth, bit 0x04 object frozen + # bit 0x10 for motion blur, bit 0x20 for material morph and bit 0x40 for mesh morph if ob.type == 'MESH' and ob.data.use_auto_smooth: obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0x02)) else: -- 2.30.2 From 6d4c667032649fb2844e058d6942139b8d179b0a Mon Sep 17 00:00:00 2001 From: Sebastian Sille Date: Mon, 22 May 2023 12:33:13 +0200 Subject: [PATCH 16/19] Export_3ds: Cleanup docs and comments Cleanup depricated docs and comments --- io_scene_3ds/export_3ds.py | 96 ++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 51 deletions(-) diff --git a/io_scene_3ds/export_3ds.py b/io_scene_3ds/export_3ds.py index df07fc9b2..6c7ae4738 100644 --- a/io_scene_3ds/export_3ds.py +++ b/io_scene_3ds/export_3ds.py @@ -150,7 +150,7 @@ def sane_name(name): if name_fixed is not None: return name_fixed - # strip non ascii chars + # Strip non ascii chars new_name_clean = new_name = name.encode("ASCII", "replace").decode("ASCII")[:12] i = 0 @@ -158,7 +158,7 @@ def sane_name(name): new_name = new_name_clean + '.%.3d' % i i += 1 - # note, appending the 'str' version + # Note, appending the 'str' version name_unique.append(new_name) name_mapping[name] = new_name = new_name.encode("ASCII", "replace") return new_name @@ -167,14 +167,13 @@ def sane_name(name): def uv_key(uv): return round(uv[0], 6), round(uv[1], 6) -# size defines +# Size defines SZ_SHORT = 2 SZ_INT = 4 SZ_FLOAT = 4 class _3ds_ushort(object): - """Class representing a short (2-byte integer) for a 3ds file. - *** This looks like an unsigned short H is unsigned from the struct docs - Cam***""" + """Class representing a short (2-byte integer) for a 3ds file.""" __slots__ = ("value", ) def __init__(self, val=0): @@ -308,7 +307,7 @@ class _3ds_float_color(object): return 3 * SZ_FLOAT def write(self, file): - file.write(struct.pack('3f', self.r, self.g, self.b)) + file.write(struct.pack('<3f', self.r, self.g, self.b)) def __str__(self): return '{%f, %f, %f}' % (self.r, self.g, self.b) @@ -342,7 +341,7 @@ class _3ds_face(object): def get_size(self): return 4 * SZ_SHORT - # no need to validate every face vert. the oversized array will catch this problem + # No need to validate every face vert, the oversized array will catch this problem def write(self, file): # The last short is used for face flags file.write(struct.pack('<4H', self.vindex[0], self.vindex[1], self.vindex[2], self.flag)) @@ -353,15 +352,15 @@ class _3ds_face(object): class _3ds_array(object): """Class representing an array of variables for a 3ds file. - Consists of a _3ds_ushort to indicate the number of items, followed by the items themselves. - """ + Consists of a _3ds_ushort to indicate the number of items, followed by the items themselves.""" + __slots__ = "values", "size" def __init__(self): self.values = [] self.size = SZ_SHORT - # add an item + # Add an item def add(self, item): self.values.append(item) self.size += item.get_size() @@ -409,11 +408,11 @@ class _3ds_named_variable(object): self.value) -# the chunk class +# The chunk class class _3ds_chunk(object): """Class representing a chunk in a 3ds file. - Chunks contain zero or more variables, followed by zero or more subchunks. - """ + Chunks contain zero or more variables, followed by zero or more subchunks.""" + __slots__ = "ID", "size", "variables", "subchunks" def __init__(self, chunk_id=0): @@ -461,7 +460,7 @@ class _3ds_chunk(object): """Write the chunk to a file. Uses the write function of the variables and the subchunks to do the actual work.""" - # write header + # Write header self.ID.write(file) self.size.write(file) for variable in self.variables: @@ -515,7 +514,7 @@ def make_material_subchunk(chunk_id, color): col1 = _3ds_chunk(RGB1) col1.add_variable("color1", _3ds_rgb_color(color)) mat_sub.add_subchunk(col1) - # optional: + # Optional # col2 = _3ds_chunk(RGBI) # col2.add_variable("color2", _3ds_rgb_color(color)) # mat_sub.add_subchunk(col2) @@ -528,7 +527,7 @@ def make_percent_subchunk(chunk_id, percent): pcti = _3ds_chunk(PCT) pcti.add_variable("percent", _3ds_ushort(int(round(percent * 100, 0)))) pct_sub.add_subchunk(pcti) - # optional: + # Optional # pctf = _3ds_chunk(PCTF) # pctf.add_variable("pctfloat", _3ds_float(round(percent, 6))) # pct_sub.add_subchunk(pctf) @@ -629,7 +628,7 @@ def make_material_texture_chunk(chunk_id, texslots, pct): rgb.add_variable("mapcolor", _3ds_rgb_color(spec if texslot.socket_dst.identifier == 'Specular' else base)) mat_sub.add_subchunk(rgb) - # store all textures for this mapto in order. This at least is what the + # Store all textures for this mapto in order. This at least is what the # 3DS exporter did so far, afaik most readers will just skip over 2nd textures for slot in texslots: if slot.image is not None: @@ -731,7 +730,7 @@ def make_material_chunk(material, image): if matmap: material_chunk.add_subchunk(matmap) - # make sure no textures are lost. Everything that doesn't fit + # Make sure no textures are lost. Everything that doesn't fit # into a channel is exported as secondary texture diffuse = [] @@ -758,7 +757,7 @@ def make_material_chunk(material, image): material_chunk.add_subchunk(make_percent_subchunk(MATTRANS, 1 - material.diffuse_color[3])) material_chunk.add_subchunk(shading) - slots = [get_material_image(material)] # can be None + slots = [get_material_image(material)] # Can be None if image: material_chunk.add_subchunk(make_texture_chunk(MAT_DIFFUSEMAP, slots)) @@ -781,7 +780,7 @@ class tri_wrapper(object): self.ma = ma self.image = image self.faceuvs = faceuvs - self.offset = [0, 0, 0] # offset indices + self.offset = [0, 0, 0] # Offset indices self.flag = flag self.group = group @@ -849,19 +848,19 @@ def remove_face_uv(verts, tri_list): need to be converted to vertex uv coordinates. That means that vertices need to be duplicated when there are multiple uv coordinates per vertex.""" - # initialize a list of UniqueLists, one per vertex + # Initialize a list of UniqueLists, one per vertex unique_uvs = [{} for i in range(len(verts))] - # for each face uv coordinate, add it to the UniqueList of the vertex + # For each face uv coordinate, add it to the UniqueList of the vertex for tri in tri_list: for i in range(3): - # store the index into the UniqueList for future reference + # Store the index into the UniqueList for future reference # offset.append(uv_list[tri.vertex_index[i]].add(_3ds_point_uv(tri.faceuvs[i]))) context_uv_vert = unique_uvs[tri.vertex_index[i]] uvkey = tri.faceuvs[i] - offset_index__uv_3ds = context_uv_vert.get(uvkey) + if not offset_index__uv_3ds: offset_index__uv_3ds = context_uv_vert[uvkey] = len(context_uv_vert), _3ds_point_uv(uvkey) @@ -880,17 +879,15 @@ def remove_face_uv(verts, tri_list): pt = _3ds_point_3d(vert.co) # reuse, should be ok uvmap = [None] * len(unique_uvs[i]) for ii, uv_3ds in unique_uvs[i].values(): - # add a vertex duplicate to the vertex_array for every uv associated with this vertex: + # Add a vertex duplicate to the vertex_array for every uv associated with this vertex vert_array.add(pt) - # add the uv coordinate to the uv array - # This for loop does not give uv's ordered by ii, so we create a new map - # and add the uv's later + # Add the uv coordinate to the uv array, this for loop does not give + # uv's ordered by ii, so we create a new map and add the uv's later # uv_array.add(uv_3ds) uvmap[ii] = uv_3ds - # Add the uv's in the correct order + # Add uv's in the correct order and add coordinates to the uv array for uv_3ds in uvmap: - # add the uv coordinate to the uv array uv_array.add(uv_3ds) vert_index += len(unique_uvs[i]) @@ -1010,23 +1007,21 @@ def make_mesh_chunk(ob, mesh, matrix, materialDict, translation): vert_array = _3ds_array() for vert in mesh.vertices: vert_array.add(_3ds_point_3d(vert.co)) - # no UV at all + # No UV at all uv_array = None - # create the chunk + # Create the chunk mesh_chunk = _3ds_chunk(OBJECT_MESH) - # add vertex chunk + # Add vertex and faces chunk mesh_chunk.add_subchunk(make_vert_chunk(vert_array)) - - # add faces chunk mesh_chunk.add_subchunk(make_faces_chunk(tri_list, mesh, materialDict)) - # if available, add uv chunk + # If available, add uv chunk if uv_array: mesh_chunk.add_subchunk(make_uv_chunk(uv_array)) - # create transformation matrix chunk + # Create transformation matrix chunk matrix_chunk = _3ds_chunk(OBJECT_TRANS_MATRIX) obj_matrix = matrix.transposed().to_3x3() @@ -1257,7 +1252,7 @@ def make_object_node(ob, translation, rotation, scale): obj_node_header_chunk.add_variable("name", _3ds_string(sane_name(name))) obj_node_header_chunk.add_variable("flags1", _3ds_ushort(0x0040)) - # Flags2 defines bit 0x01 for display path, bit 0x02 use autosmooth, bit 0x04 object frozen + # Flags2 defines bit 0x01 for display path, bit 0x02 use autosmooth, bit 0x04 object frozen, # bit 0x10 for motion blur, bit 0x20 for material morph and bit 0x40 for mesh morph if ob.type == 'MESH' and ob.data.use_auto_smooth: obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0x02)) @@ -1525,8 +1520,7 @@ def save(operator, if write_keyframe and world.animation_data: kfdata.add_subchunk(make_ambient_node(world)) - # Make a list of all materials used in the selected meshes (use a dictionary, - # each material is added once) + # Make a list of all materials used in the selected meshes (use dictionary, each material is added once) materialDict = {} mesh_objects = [] @@ -1540,7 +1534,7 @@ def save(operator, camera_objects = [ob for ob in objects if ob.type == 'CAMERA'] for ob in objects: - # get derived objects + # Get derived objects derived_dict = bpy_extras.io_utils.create_derived_objects(depsgraph, [ob]) derived = derived_dict.get(ob) @@ -1563,7 +1557,7 @@ def save(operator, ma_ls = data.materials ma_ls_len = len(ma_ls) - # get material/image tuples + # Get material/image tuples if data.uv_layers: if not ma_ls: ma = ma_name = None @@ -1575,7 +1569,7 @@ def save(operator, ma_index = f.material_index = 0 ma = ma_ls[ma_index] ma_name = None if ma is None else ma.name - # else there already set to none + # Else there already set to none img = get_uv_image(ma) img_name = None if img is None else img.name @@ -1584,7 +1578,7 @@ def save(operator, else: for ma in ma_ls: - if ma: # material may be None so check its not + if ma: # Material may be None so check its not materialDict.setdefault((ma.name, None), (ma, None)) # Why 0 Why! @@ -1620,16 +1614,16 @@ def save(operator, # Create object chunks for all meshes i = 0 for ob, mesh, matrix in mesh_objects: - # create a new object chunk + # Create a new object chunk object_chunk = _3ds_chunk(OBJECT) - # set the object name + # Set the object name object_chunk.add_variable("name", _3ds_string(sane_name(ob.name))) - # make a mesh chunk out of the mesh + # Make a mesh chunk out of the mesh object_chunk.add_subchunk(make_mesh_chunk(ob, mesh, matrix, materialDict, translation)) - # ensure the mesh has no over sized arrays, skip ones that do! + # Ensure the mesh has no over sized arrays, skip ones that do! # Otherwise we cant write since the array size wont fit into USHORT if object_chunk.validate(): object_info.add_subchunk(object_chunk) @@ -1649,12 +1643,12 @@ def save(operator, # Create light object chunks for ob in light_objects: + object_chunk = _3ds_chunk(OBJECT) translation[ob.name] = ob.location rotation[ob.name] = ob.rotation_euler.to_quaternion() scale[ob.name] = ob.scale # Add light data subchunks - object_chunk = _3ds_chunk(OBJECT) light_chunk = _3ds_chunk(OBJECT_LIGHT) color_float_chunk = _3ds_chunk(RGB) energy_factor = _3ds_chunk(LIGHT_MULTIPLIER) @@ -1699,12 +1693,12 @@ def save(operator, # Create camera object chunks for ob in camera_objects: + object_chunk = _3ds_chunk(OBJECT) translation[ob.name] = ob.location rotation[ob.name] = ob.rotation_euler.to_quaternion() scale[ob.name] = ob.scale # Add camera data subchunks - object_chunk = _3ds_chunk(OBJECT) camera_chunk = _3ds_chunk(OBJECT_CAMERA) diagonal = math.copysign(math.sqrt(pow(ob.location[0], 2) + pow(ob.location[1], 2)), ob.location[1]) focus_x = ob.location[0] + (ob.location[1] * math.tan(ob.rotation_euler[2])) @@ -1730,7 +1724,7 @@ def save(operator, if write_keyframe: primary.add_subchunk(kfdata) - # At this point, the chunk hierarchy is completely built. + # At this point, the chunk hierarchy is completely built # Check the size primary.get_size() -- 2.30.2 From cb8539b7d82976aefd553a485e28471a58b558ad Mon Sep 17 00:00:00 2001 From: Sebastian Sille Date: Mon, 22 May 2023 16:57:22 +0200 Subject: [PATCH 17/19] Export_3ds: Cleanup for Blender 3.6 release revert revert 084ea4fb7d26390f2ca8fd36fecaf56aa00daf3d revert Export_3ds: Cleanup for Blender 3.6 release cleanup docs and comments --- io_scene_3ds/export_3ds.py | 113 +++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 62 deletions(-) diff --git a/io_scene_3ds/export_3ds.py b/io_scene_3ds/export_3ds.py index 5d99ab101..2b2baeb92 100644 --- a/io_scene_3ds/export_3ds.py +++ b/io_scene_3ds/export_3ds.py @@ -157,7 +157,7 @@ def sane_name(name): new_name = new_name_clean + '.%.3d' % i i += 1 - # note, appending the 'str' version. + # note, appending the 'str' version name_unique.append(new_name) name_mapping[name] = new_name = new_name.encode("ASCII", "replace") return new_name @@ -166,14 +166,13 @@ def sane_name(name): def uv_key(uv): return round(uv[0], 6), round(uv[1], 6) -# size defines: +# size defines SZ_SHORT = 2 SZ_INT = 4 SZ_FLOAT = 4 class _3ds_ushort(object): - """Class representing a short (2-byte integer) for a 3ds file. - *** This looks like an unsigned short H is unsigned from the struct docs - Cam***""" + """Class representing a short (2-byte integer) for a 3ds file.""" __slots__ = ("value", ) def __init__(self, val=0): @@ -308,7 +307,7 @@ class _3ds_float_color(object): return 3 * SZ_FLOAT def write(self, file): - file.write(struct.pack('3f', self.r, self.g, self.b)) + file.write(struct.pack('<3f', self.r, self.g, self.b)) def __str__(self): return '{%f, %f, %f}' % (self.r, self.g, self.b) @@ -342,9 +341,7 @@ class _3ds_face(object): def get_size(self): return 4 * SZ_SHORT - # no need to validate every face vert. the oversized array will - # catch this problem - + # no need to validate every face vert, the oversized array will catch this problem def write(self, file): # The last short is used for face flags file.write(struct.pack('<4H', self.vindex[0], self.vindex[1], self.vindex[2], self.flag)) @@ -355,15 +352,14 @@ class _3ds_face(object): class _3ds_array(object): """Class representing an array of variables for a 3ds file. - Consists of a _3ds_ushort to indicate the number of items, followed by the items themselves. - """ + Consists of a _3ds_ushort to indicate the number of items, followed by the items themselves.""" __slots__ = "values", "size" def __init__(self): self.values = [] self.size = SZ_SHORT - # add an item: + # add an item def add(self, item): self.values.append(item) self.size += item.get_size() @@ -380,7 +376,7 @@ class _3ds_array(object): value.write(file) # To not overwhelm the output in a dump, a _3ds_array only - # outputs the number of items, not all of the actual items. + # outputs the number of items, not all of the actual items def __str__(self): return '(%d items)' % len(self.values) @@ -583,7 +579,7 @@ def make_material_texture_chunk(chunk_id, texslots, pct): 0x40 activates alpha source, 0x80 activates tinting, 0x100 ignores alpha, 0x200 activates RGB tint. Bits 0x80, 0x100, and 0x200 are only used with TEXMAP, TEX2MAP, and SPECMAP chunks. 0x40, when used with a TEXMAP, TEX2MAP, or SPECMAP chunk must be accompanied with a tint bit, - either 0x100 or 0x200, tintcolor will be processed if colorchunks are present""" + either 0x100 or 0x200, tintcolor will be processed if tintflags are present""" mapflags = 0 if texslot.extension == 'EXTEND': @@ -632,9 +628,8 @@ def make_material_texture_chunk(chunk_id, texslots, pct): rgb.add_variable("mapcolor", _3ds_rgb_color(spec if texslot.socket_dst.identifier == 'Specular' else base)) mat_sub.add_subchunk(rgb) - # store all textures for this mapto in order. This at least is what - # the 3DS exporter did so far, afaik most readers will just skip - # over 2nd textures. + # store all textures for this mapto in order. This at least is what the + # 3DS exporter did so far, afaik most readers will just skip over 2nd textures for slot in texslots: if slot.image is not None: add_texslot(slot) @@ -685,9 +680,9 @@ def make_material_chunk(material, image): primary_tex = False if wrap.base_color_texture: - d_pct = 0.7 + sum(wrap.base_color[:]) * 0.1 color = [wrap.base_color_texture] - matmap = make_material_texture_chunk(MAT_DIFFUSEMAP, color, d_pct) + c_pct = 0.7 + sum(wrap.base_color[:]) * 0.1 + matmap = make_material_texture_chunk(MAT_DIFFUSEMAP, color, c_pct) if matmap: material_chunk.add_subchunk(matmap) primary_tex = True @@ -729,8 +724,8 @@ def make_material_chunk(material, image): material_chunk.add_subchunk(matmap) if wrap.emission_color_texture: - e_pct = wrap.emission_strength emission = [wrap.emission_color_texture] + e_pct = wrap.emission_strength matmap = make_material_texture_chunk(MAT_SELFIMAP, emission, e_pct) if matmap: material_chunk.add_subchunk(matmap) @@ -849,8 +844,7 @@ def remove_face_uv(verts, tri_list): need to be converted to vertex uv coordinates. That means that vertices need to be duplicated when there are multiple uv coordinates per vertex.""" - # initialize a list of UniqueLists, one per vertex: - # uv_list = [UniqueList() for i in xrange(len(verts))] + # initialize a list of UniqueLists, one per vertex unique_uvs = [{} for i in range(len(verts))] # for each face uv coordinate, add it to the UniqueList of the vertex @@ -861,7 +855,6 @@ def remove_face_uv(verts, tri_list): context_uv_vert = unique_uvs[tri.vertex_index[i]] uvkey = tri.faceuvs[i] - offset_index__uv_3ds = context_uv_vert.get(uvkey) if not offset_index__uv_3ds: @@ -869,11 +862,9 @@ def remove_face_uv(verts, tri_list): tri.offset[i] = offset_index__uv_3ds[0] - # At this point, each vertex has a UniqueList containing every uv coordinate that is associated with it - # only once. - + # At this point, each vertex has a UniqueList containing every uv coordinate associated with it only once # Now we need to duplicate every vertex as many times as it has uv coordinates and make sure the - # faces refer to the new face indices: + # faces refer to the new face indices vert_index = 0 vert_array = _3ds_array() uv_array = _3ds_array() @@ -884,22 +875,21 @@ def remove_face_uv(verts, tri_list): pt = _3ds_point_3d(vert.co) # reuse, should be ok uvmap = [None] * len(unique_uvs[i]) for ii, uv_3ds in unique_uvs[i].values(): - # add a vertex duplicate to the vertex_array for every uv associated with this vertex: + # add a vertex duplicate to the vertex_array for every uv associated with this vertex vert_array.add(pt) - # add the uv coordinate to the uv array: - # This for loop does not give uv's ordered by ii, so we create a new map - # and add the uv's later + # add the uv coordinate to the uv array, this for loop does not give + # uv's ordered by ii, so we create a new map and add the uv's later # uv_array.add(uv_3ds) uvmap[ii] = uv_3ds # Add the uv's in the correct order for uv_3ds in uvmap: - # add the uv coordinate to the uv array: + # add the uv coordinate to the uv array uv_array.add(uv_3ds) vert_index += len(unique_uvs[i]) - # Make sure the triangle vertex indices now refer to the new vertex list: + # Make sure the triangle vertex indices now refer to the new vertex list for tri in tri_list: for i in range(3): tri.offset[i] += index_list[tri.vertex_index[i]] @@ -1003,30 +993,30 @@ def make_uv_chunk(uv_array): def make_mesh_chunk(ob, mesh, matrix, materialDict, translation): """Make a chunk out of a Blender mesh.""" - # Extract the triangles from the mesh: + # Extract the triangles from the mesh tri_list = extract_triangles(mesh) if mesh.uv_layers: - # Remove the face UVs and convert it to vertex UV: + # Remove the face UVs and convert it to vertex UV vert_array, uv_array, tri_list = remove_face_uv(mesh.vertices, tri_list) else: - # Add the vertices to the vertex array: + # Add the vertices to the vertex array vert_array = _3ds_array() for vert in mesh.vertices: vert_array.add(_3ds_point_3d(vert.co)) - # no UV at all: + # no UV at all uv_array = None - # create the chunk: + # create the chunk mesh_chunk = _3ds_chunk(OBJECT_MESH) - # add vertex chunk: + # add vertex chunk mesh_chunk.add_subchunk(make_vert_chunk(vert_array)) - # add faces chunk: + # add faces chunk mesh_chunk.add_subchunk(make_faces_chunk(tri_list, mesh, materialDict)) - # if available, add uv chunk: + # if available, add uv chunk if uv_array: mesh_chunk.add_subchunk(make_uv_chunk(uv_array)) @@ -1200,15 +1190,15 @@ def save(operator, if bpy.ops.object.mode_set.poll(): bpy.ops.object.mode_set(mode='OBJECT') - # Initialize the main chunk (primary): + # Initialize the main chunk (primary) primary = _3ds_chunk(PRIMARY) - # Add version chunk: + # Add version chunk version_chunk = _3ds_chunk(VERSION) version_chunk.add_variable("version", _3ds_uint(3)) primary.add_subchunk(version_chunk) - # Init main object info chunk: + # Init main object info chunk object_info = _3ds_chunk(OBJECTINFO) mesh_version = _3ds_chunk(MESHVERSION) mesh_version.add_variable("mesh", _3ds_uint(3)) @@ -1233,7 +1223,7 @@ def save(operator, ''' # Make a list of all materials used in the selected meshes (use a dictionary, - # each material is added once): + # each material is added once) materialDict = {} mesh_objects = [] @@ -1270,7 +1260,7 @@ def save(operator, ma_ls = data.materials ma_ls_len = len(ma_ls) - # get material/image tuples. + # get material/image tuples if data.uv_layers: if not ma_ls: ma = ma_name = None @@ -1291,7 +1281,7 @@ def save(operator, else: for ma in ma_ls: - if ma: # material may be None so check its not. + if ma: # material may be None so check its not materialDict.setdefault((ma.name, None), (ma, None)) # Why 0 Why! @@ -1300,14 +1290,14 @@ def save(operator, f.material_index = 0 - # Make material chunks for all materials used in the meshes: + # Make material chunks for all materials used in the meshes for ma_image in materialDict.values(): object_info.add_subchunk(make_material_chunk(ma_image[0], ma_image[1])) # Collect translation for transformation matrix translation = {} - # Give all objects a unique ID and build a dictionary from object name to object id: + # Give all objects a unique ID and build a dictionary from object name to object id # name_to_id = {} for ob, data, matrix in mesh_objects: @@ -1318,7 +1308,7 @@ def save(operator, translation[ob.name] = ob.location # name_to_id[ob.name]= len(name_to_id) - # Create object chunks for all meshes: + # Create object chunks for all meshes i = 0 for ob, mesh, matrix in mesh_objects: # create a new object chunk @@ -1327,12 +1317,11 @@ def save(operator, # set the object name object_chunk.add_variable("name", _3ds_string(sane_name(ob.name))) - # make a mesh chunk out of the mesh: + # make a mesh chunk out of the mesh object_chunk.add_subchunk(make_mesh_chunk(ob, mesh, matrix, materialDict, translation)) - # ensure the mesh has no over sized arrays - # skip ones that do!, otherwise we cant write since the array size wont - # fit into USHORT. + # Ensure the mesh has no over sized arrays, skip ones that do! + # Otherwise we cant write since the array size wont fit into USHORT if object_chunk.validate(): object_info.add_subchunk(object_chunk) else: @@ -1345,7 +1334,7 @@ def save(operator, i += i - # Create chunks for all empties: + # Create chunks for all empties ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX for ob in empty_objects: # Empties only require a kf object node: @@ -1408,7 +1397,7 @@ def save(operator, object_chunk.add_subchunk(camera_chunk) object_info.add_subchunk(object_chunk) - # Add main object info chunk to primary chunk: + # Add main object info chunk to primary chunk primary.add_subchunk(object_info) ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX @@ -1416,27 +1405,27 @@ def save(operator, primary.add_subchunk(kfdata) ''' - # At this point, the chunk hierarchy is completely built. - # Check the size: + # At this point, the chunk hierarchy is completely built + # Check the size primary.get_size() - # Open the file for writing: + # Open the file for writing file = open(filepath, 'wb') - # Recursively write the chunks to file: + # Recursively write the chunks to file primary.write(file) - # Close the file: + # Close the file file.close() # Clear name mapping vars, could make locals too del name_unique[:] name_mapping.clear() - # Debugging only: report the exporting time: + # Debugging only: report the exporting time print("3ds export time: %.2f" % (time.time() - duration)) - # Debugging only: dump the chunk hierarchy: + # Debugging only: dump the chunk hierarchy # primary.dump() return {'FINISHED'} -- 2.30.2 From 8c5684587ab9e36f910cdcbd87a01befbbe5216c Mon Sep 17 00:00:00 2001 From: Sebastian Sille Date: Mon, 22 May 2023 17:22:45 +0200 Subject: [PATCH 18/19] Export_3ds: Finished code for Blender 3.6 release --- io_scene_3ds/export_3ds.py | 531 ++++++++----------------------------- 1 file changed, 111 insertions(+), 420 deletions(-) diff --git a/io_scene_3ds/export_3ds.py b/io_scene_3ds/export_3ds.py index 6c7ae4738..608c76171 100644 --- a/io_scene_3ds/export_3ds.py +++ b/io_scene_3ds/export_3ds.py @@ -260,15 +260,15 @@ class _3ds_point_3d(object): # Used for writing a track +''' class _3ds_point_4d(object): """Class representing a four-dimensional point for a 3ds file, for instance a quaternion.""" - __slots__ = "w", "x", "y", "z" - - def __init__(self, point): + __slots__ = "w","x","y","z" + def __init__(self, point=(0.0,0.0,0.0,0.0)): self.w, self.x, self.y, self.z = point def get_size(self): - return 4 * SZ_FLOAT + return 4*SZ_FLOAT def write(self,file): data=struct.pack('<4f', self.w, self.x, self.y, self.z) @@ -276,6 +276,7 @@ class _3ds_point_4d(object): def __str__(self): return '(%f, %f, %f, %f)' % (self.w, self.x, self.y, self.z) +''' class _3ds_point_uv(object): @@ -1049,18 +1050,17 @@ def make_mesh_chunk(ob, mesh, matrix, materialDict, translation): return mesh_chunk -################# -# KEYFRAME DATA # -################# - -def make_kfdata(revision, start=0, stop=100, curtime=0): +''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX +def make_kfdata(start=0, stop=0, curtime=0): """Make the basic keyframe data chunk""" kfdata = _3ds_chunk(KFDATA) kfhdr = _3ds_chunk(KFDATA_KFHDR) - kfhdr.add_variable("revision", _3ds_ushort(revision)) - kfhdr.add_variable("filename", _3ds_string(b'Blender')) - kfhdr.add_variable("animlen", _3ds_uint(stop - start)) + kfhdr.add_variable("revision", _3ds_ushort(0)) + # Not really sure what filename is used for, but it seems it is usually used + # to identify the program that generated the .3ds: + kfhdr.add_variable("filename", _3ds_string("Blender")) + kfhdr.add_variable("animlen", _3ds_uint(stop-start)) kfseg = _3ds_chunk(KFDATA_KFSEG) kfseg.add_variable("start", _3ds_uint(start)) @@ -1074,387 +1074,102 @@ def make_kfdata(revision, start=0, stop=100, curtime=0): kfdata.add_subchunk(kfcurtime) return kfdata -def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size): - """Make a chunk for track data. Depending on the ID, this will - construct a position, rotation, scale, roll, color or fov track.""" +def make_track_chunk(ID, obj): + """Make a chunk for track data. + Depending on the ID, this will construct a position, rotation or scale track.""" track_chunk = _3ds_chunk(ID) - - if ID in {POS_TRACK_TAG, ROT_TRACK_TAG, SCL_TRACK_TAG, ROLL_TRACK_TAG} and ob.animation_data and ob.animation_data.action: - action = ob.animation_data.action - if action.fcurves: - fcurves = action.fcurves - kframes = [kf.co[0] for kf in [fc for fc in fcurves if fc is not None][0].keyframe_points] - nkeys = len(kframes) - if not 0 in kframes: - kframes.append(0) - nkeys = nkeys + 1 - kframes = sorted(set(kframes)) - track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) - track_chunk.add_variable("frame_start", _3ds_uint(int(action.frame_start))) - track_chunk.add_variable("frame_total", _3ds_uint(int(action.frame_end))) - track_chunk.add_variable("nkeys", _3ds_uint(nkeys)) - - if ID == POS_TRACK_TAG: # Position - for i, frame in enumerate(kframes): - position = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'location'] - if not position: - position.append(ob_pos) - track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) - track_chunk.add_variable("tcb_flags", _3ds_ushort()) - track_chunk.add_variable("position", _3ds_point_3d(position)) - - elif ID == ROT_TRACK_TAG: # Rotation - for i, frame in enumerate(kframes): - rotation = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'rotation_euler'] - if not rotation: - rotation.append(ob_rot) - quat = mathutils.Euler(rotation).to_quaternion() - axis_angle = quat.angle, quat.axis[0], quat.axis[1], quat.axis[2] - track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) - track_chunk.add_variable("tcb_flags", _3ds_ushort()) - track_chunk.add_variable("rotation", _3ds_point_4d(axis_angle)) - - elif ID == SCL_TRACK_TAG: # Scale - for i, frame in enumerate(kframes): - size = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'scale'] - if not size: - size.append(ob_size) - track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) - track_chunk.add_variable("tcb_flags", _3ds_ushort()) - track_chunk.add_variable("scale", _3ds_point_3d(size)) - - elif ID == ROLL_TRACK_TAG: # Roll - for i, frame in enumerate(kframes): - roll = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'rotation_euler'] - if not roll: - roll.append(ob_rot) - track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) - track_chunk.add_variable("tcb_flags", _3ds_ushort()) - track_chunk.add_variable("roll", _3ds_float(round(math.degrees(roll[1]), 4))) - - elif ID in {COL_TRACK_TAG, FOV_TRACK_TAG, HOTSPOT_TRACK_TAG, FALLOFF_TRACK_TAG} and ob.data.animation_data and ob.data.animation_data.action: - action = ob.data.animation_data.action - if action.fcurves: - fcurves = action.fcurves - kframes = [kf.co[0] for kf in [fc for fc in fcurves if fc is not None][0].keyframe_points] - nkeys = len(kframes) - if not 0 in kframes: - kframes.append(0) - nkeys = nkeys + 1 - kframes = sorted(set(kframes)) - track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) - track_chunk.add_variable("frame_start", _3ds_uint(int(action.frame_start))) - track_chunk.add_variable("frame_total", _3ds_uint(int(action.frame_end))) - track_chunk.add_variable("nkeys", _3ds_uint(nkeys)) - - if ID == COL_TRACK_TAG: # Color - for i, frame in enumerate(kframes): - color = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'color'] - if not color: - color.append(ob.data.color[:3]) - track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) - track_chunk.add_variable("tcb_flags", _3ds_ushort()) - track_chunk.add_variable("color", _3ds_float_color(color)) - - elif ID == FOV_TRACK_TAG: # Field of view - for i, frame in enumerate(kframes): - lens = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'lens'] - if not lens: - lens.append(ob.data.lens) - fov = 2 * math.atan(ob.data.sensor_width/(2*lens[0])) - track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) - track_chunk.add_variable("tcb_flags", _3ds_ushort()) - track_chunk.add_variable("fov", _3ds_float(round(math.degrees(fov), 4))) - - elif ID == HOTSPOT_TRACK_TAG: # Hotspot - beam_angle = math.degrees(ob.data.spot_size) - for i, frame in enumerate(kframes): - blend = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'spot_blend'] - if not blend: - blend.append(ob.data.spot_blend) - hot_spot = beam_angle-(blend[0]*math.floor(beam_angle)) - track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) - track_chunk.add_variable("tcb_flags", _3ds_ushort()) - track_chunk.add_variable("hotspot", _3ds_float(round(hot_spot, 4))) - - elif ID == FALLOFF_TRACK_TAG: # Falloff - for i, frame in enumerate(kframes): - fall_off = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'spot_size'] - if not fall_off: - fall_off.append(ob.data.spot_size) - track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) - track_chunk.add_variable("tcb_flags", _3ds_ushort()) - track_chunk.add_variable("falloff", _3ds_float(round(math.degrees(fall_off[0]), 4))) - + track_chunk.add_variable("track_flags", _3ds_ushort()) + track_chunk.add_variable("unknown", _3ds_uint()) + track_chunk.add_variable("unknown", _3ds_uint()) + track_chunk.add_variable("nkeys", _3ds_uint(1)) + # Next section should be repeated for every keyframe, but for now, animation is not actually supported. + track_chunk.add_variable("tcb_frame", _3ds_uint(0)) + track_chunk.add_variable("tcb_flags", _3ds_ushort()) + if obj.type=='Empty': + if ID==POS_TRACK_TAG: + # position vector: + track_chunk.add_variable("position", _3ds_point_3d(obj.getLocation())) + elif ID==ROT_TRACK_TAG: + # rotation (quaternion, angle first, followed by axis): + q = obj.getEuler().to_quaternion() # XXX, todo! + track_chunk.add_variable("rotation", _3ds_point_4d((q.angle, q.axis[0], q.axis[1], q.axis[2]))) + elif ID==SCL_TRACK_TAG: + # scale vector: + track_chunk.add_variable("scale", _3ds_point_3d(obj.getSize())) else: - track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) # Based on observation default flag is 0x40 - track_chunk.add_variable("frame_start", _3ds_uint(0)) - track_chunk.add_variable("frame_total", _3ds_uint(0)) - track_chunk.add_variable("nkeys", _3ds_uint(1)) - # Next section should be repeated for every keyframe, with no animation only one tag is needed - track_chunk.add_variable("tcb_frame", _3ds_uint(0)) - track_chunk.add_variable("tcb_flags", _3ds_ushort()) - - # New method simply inserts the parameters - if ID == POS_TRACK_TAG: # Position vector - track_chunk.add_variable("position", _3ds_point_3d(ob_pos)) - - elif ID == ROT_TRACK_TAG: # Rotation (angle first [radians], followed by axis) - track_chunk.add_variable("rotation", _3ds_point_4d((ob_rot.angle, ob_rot.axis[0], ob_rot.axis[1], ob_rot.axis[2]))) - - elif ID == SCL_TRACK_TAG: # Scale vector - track_chunk.add_variable("scale", _3ds_point_3d(ob_size)) - - elif ID == ROLL_TRACK_TAG: # Roll angle - track_chunk.add_variable("roll", _3ds_float(round(math.degrees(ob.rotation_euler[1]), 4))) - - elif ID == COL_TRACK_TAG: # Color values - track_chunk.add_variable("color", _3ds_float_color(ob.data.color)) - - elif ID == FOV_TRACK_TAG: # Field of view - track_chunk.add_variable("fov", _3ds_float(round(math.degrees(ob.data.angle), 4))) - - elif ID == HOTSPOT_TRACK_TAG: # Hotspot - beam_angle = math.degrees(ob.data.spot_size) - track_chunk.add_variable("hotspot", _3ds_float(round(beam_angle-(ob.data.spot_blend*math.floor(beam_angle)), 4))) - - elif ID == FALLOFF_TRACK_TAG: # Falloff - track_chunk.add_variable("falloff", _3ds_float(round(math.degrees(ob.data.spot_size), 4))) + # meshes have their transformations applied before + # exporting, so write identity transforms here: + if ID==POS_TRACK_TAG: + # position vector: + track_chunk.add_variable("position", _3ds_point_3d((0.0,0.0,0.0))) + elif ID==ROT_TRACK_TAG: + # rotation (quaternion, angle first, followed by axis): + track_chunk.add_variable("rotation", _3ds_point_4d((0.0, 1.0, 0.0, 0.0))) + elif ID==SCL_TRACK_TAG: + # scale vector: + track_chunk.add_variable("scale", _3ds_point_3d((1.0, 1.0, 1.0))) return track_chunk +def make_kf_obj_node(obj, name_to_id): + """Make a node chunk for a Blender object. + Takes the Blender object as a parameter. Object id's are taken from the dictionary name_to_id. + Blender Empty objects are converted to dummy nodes.""" -def make_object_node(ob, translation, rotation, scale): - """Make a node chunk for a Blender object. Takes Blender object as parameter. - Blender Empty objects are converted to dummy nodes.""" + name = obj.name + # main object node chunk: + kf_obj_node = _3ds_chunk(OBJECT_NODE_TAG) + # chunk for the object id: + obj_id_chunk = _3ds_chunk(OBJECT_NODE_ID) + # object id is from the name_to_id dictionary: + obj_id_chunk.add_variable("node_id", _3ds_ushort(name_to_id[name])) - name = ob.name - if ob.type == 'CAMERA': - obj_node = _3ds_chunk(CAMERA_NODE_TAG) - elif ob.type == 'LIGHT': - obj_node = _3ds_chunk(LIGHT_NODE_TAG) - if ob.data.type == 'SPOT': - obj_node = _3ds_chunk(SPOT_NODE_TAG) - else: # Main object node chunk - obj_node = _3ds_chunk(OBJECT_NODE_TAG) - - # Object node header with object name + # object node header: obj_node_header_chunk = _3ds_chunk(OBJECT_NODE_HDR) - parent = ob.parent - - if ob.type == 'EMPTY': # Forcing to use the real name for empties - # Empties called $$$DUMMY and use OBJECT_INSTANCE_NAME chunk as name - obj_node_header_chunk.add_variable("name", _3ds_string(b"$$$DUMMY")) - obj_node_header_chunk.add_variable("flags1", _3ds_ushort(0x4000)) - obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0)) - - else: # Add flag variables - Based on observation flags1 is usually 0x0040 and 0x4000 for empty objects + # object name: + if obj.type == 'Empty': + # Empties are called "$$$DUMMY" and use the OBJECT_INSTANCE_NAME chunk + # for their name (see below): + obj_node_header_chunk.add_variable("name", _3ds_string("$$$DUMMY")) + else: + # Add the name: obj_node_header_chunk.add_variable("name", _3ds_string(sane_name(name))) - obj_node_header_chunk.add_variable("flags1", _3ds_ushort(0x0040)) + # Add Flag variables (not sure what they do): + obj_node_header_chunk.add_variable("flags1", _3ds_ushort(0)) + obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0)) - # Flags2 defines bit 0x01 for display path, bit 0x02 use autosmooth, bit 0x04 object frozen, - # bit 0x10 for motion blur, bit 0x20 for material morph and bit 0x40 for mesh morph - if ob.type == 'MESH' and ob.data.use_auto_smooth: - obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0x02)) - else: - obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0)) - obj_node_header_chunk.add_variable("parent", _3ds_ushort(ROOT_OBJECT)) - - ''' - # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX # Check parent-child relationships: - if parent is None or parent.name not in name_to_id: - # If no parent, or parents name is not in dictionary, ID becomes -1: + parent = obj.parent + if (parent is None) or (parent.name not in name_to_id): + # If no parent, or the parents name is not in the name_to_id dictionary, + # parent id becomes -1: obj_node_header_chunk.add_variable("parent", _3ds_ushort(-1)) - else: # Get the parent's ID from the name_to_id dictionary: + else: + # Get the parent's id from the name_to_id dictionary: obj_node_header_chunk.add_variable("parent", _3ds_ushort(name_to_id[parent.name])) - ''' - # Add subchunk for node header - obj_node.add_subchunk(obj_node_header_chunk) + # Add pivot chunk: + obj_pivot_chunk = _3ds_chunk(OBJECT_PIVOT) + obj_pivot_chunk.add_variable("pivot", _3ds_point_3d(obj.getLocation())) + kf_obj_node.add_subchunk(obj_pivot_chunk) - # Empty objects need to have an extra chunk for the instance name - if ob.type == 'EMPTY': # Will use a real object name for empties for now + # add subchunks for object id and node header: + kf_obj_node.add_subchunk(obj_id_chunk) + kf_obj_node.add_subchunk(obj_node_header_chunk) + + # Empty objects need to have an extra chunk for the instance name: + if obj.type == 'Empty': obj_instance_name_chunk = _3ds_chunk(OBJECT_INSTANCE_NAME) obj_instance_name_chunk.add_variable("name", _3ds_string(sane_name(name))) - obj_node.add_subchunk(obj_instance_name_chunk) + kf_obj_node.add_subchunk(obj_instance_name_chunk) - if ob.type in {'MESH', 'EMPTY'}: # Add a pivot point at the object center - pivot_pos = (translation[name]) - obj_pivot_chunk = _3ds_chunk(OBJECT_PIVOT) - obj_pivot_chunk.add_variable("pivot", _3ds_point_3d(pivot_pos)) - obj_node.add_subchunk(obj_pivot_chunk) + # Add track chunks for position, rotation and scale: + kf_obj_node.add_subchunk(make_track_chunk(POS_TRACK_TAG, obj)) + kf_obj_node.add_subchunk(make_track_chunk(ROT_TRACK_TAG, obj)) + kf_obj_node.add_subchunk(make_track_chunk(SCL_TRACK_TAG, obj)) - # Create a bounding box from quadrant diagonal - obj_boundbox = _3ds_chunk(OBJECT_BOUNDBOX) - obj_boundbox.add_variable("min", _3ds_point_3d(ob.bound_box[0])) - obj_boundbox.add_variable("max", _3ds_point_3d(ob.bound_box[6])) - obj_node.add_subchunk(obj_boundbox) - - # Add smooth angle if autosmooth is used - if ob.type == 'MESH' and ob.data.use_auto_smooth: - obj_morph_smooth = _3ds_chunk(OBJECT_MORPH_SMOOTH) - obj_morph_smooth.add_variable("angle", _3ds_float(round(ob.data.auto_smooth_angle, 6))) - obj_node.add_subchunk(obj_morph_smooth) - - # Add track chunks for color, position, rotation and scale - if parent is None: - ob_pos = translation[name] - ob_rot = rotation[name] - ob_size = scale[name] - - else: # Calculate child position and rotation of the object center, no scale applied - ob_pos = translation[name] - translation[parent.name] - ob_rot = rotation[name].cross(rotation[parent.name].copy().inverted()) - ob_size = (1.0, 1.0, 1.0) - - obj_node.add_subchunk(make_track_chunk(POS_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) - - if ob.type in {'MESH', 'EMPTY'}: - obj_node.add_subchunk(make_track_chunk(ROT_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) - obj_node.add_subchunk(make_track_chunk(SCL_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) - if ob.type =='CAMERA': - obj_node.add_subchunk(make_track_chunk(FOV_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) - obj_node.add_subchunk(make_track_chunk(ROLL_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) - if ob.type =='LIGHT': - obj_node.add_subchunk(make_track_chunk(COL_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) - if ob.type == 'LIGHT' and ob.data.type == 'SPOT': - obj_node.add_subchunk(make_track_chunk(HOTSPOT_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) - obj_node.add_subchunk(make_track_chunk(FALLOFF_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) - obj_node.add_subchunk(make_track_chunk(ROLL_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) - - return obj_node - - -def make_target_node(ob, translation, rotation, scale): - """Make a target chunk for light and camera objects""" - - name = ob.name - if ob.type == 'CAMERA': #Add camera target - tar_node = _3ds_chunk(TARGET_NODE_TAG) - elif ob.type == 'LIGHT': # Add spot target - tar_node = _3ds_chunk(LTARGET_NODE_TAG) - - # Object node header with object name - tar_node_header_chunk = _3ds_chunk(OBJECT_NODE_HDR) - # Targets get the same name as the object, flags1 is usually 0x0010 and parent ROOT_OBJECT - tar_node_header_chunk.add_variable("name", _3ds_string(sane_name(name))) - tar_node_header_chunk.add_variable("flags1", _3ds_ushort(0x0010)) - tar_node_header_chunk.add_variable("flags2", _3ds_ushort(0)) - tar_node_header_chunk.add_variable("parent", _3ds_ushort(ROOT_OBJECT)) - - # Add subchunk for node header - tar_node.add_subchunk(tar_node_header_chunk) - - # Calculate target position - ob_pos = translation[name] - ob_rot = rotation[name].to_euler() - ob_size = scale[name] - - diagonal = math.copysign(math.sqrt(pow(ob_pos[0],2)+pow(ob_pos[1],2)), ob_pos[1]) - target_x = ob_pos[0]+(ob_pos[1]*math.tan(ob_rot[2])) - target_y = ob_pos[1]+(ob_pos[0]*math.tan(math.radians(90)-ob_rot[2])) - target_z = -1*diagonal*math.tan(math.radians(90)-ob_rot[0]) - - # Add track chunks for target position - track_chunk = _3ds_chunk(POS_TRACK_TAG) - - if ob.animation_data and ob.animation_data.action: - action = ob.animation_data.action - if action.fcurves: - fcurves = action.fcurves - kframes = [kf.co[0] for kf in [fc for fc in fcurves if fc is not None][0].keyframe_points] - nkeys = len(kframes) - if not 0 in kframes: - kframes.append(0) - nkeys = nkeys + 1 - kframes = sorted(set(kframes)) - track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) - track_chunk.add_variable("frame_start", _3ds_uint(int(action.frame_start))) - track_chunk.add_variable("frame_total", _3ds_uint(int(action.frame_end))) - track_chunk.add_variable("nkeys", _3ds_uint(nkeys)) - - for i, frame in enumerate(kframes): - target_pos = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'location'] - target_rot = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'rotation_euler'] - if not target_pos: - target_pos.append(ob_pos) - if not target_rot: - target_rot.insert(0, ob_rot.x) - target_rot.insert(1, ob_rot.y) - target_rot.insert(2, ob_rot.z) - diagonal = math.copysign(math.sqrt(pow(target_pos[0],2)+pow(target_pos[1],2)), target_pos[1]) - target_x = target_pos[0]+(target_pos[1]*math.tan(target_rot[2])) - target_y = target_pos[1]+(target_pos[0]*math.tan(math.radians(90)-target_rot[2])) - target_z = -1*diagonal*math.tan(math.radians(90)-target_rot[0]) - track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) - track_chunk.add_variable("tcb_flags", _3ds_ushort()) - track_chunk.add_variable("position", _3ds_point_3d((target_x, target_y, target_z))) - - else: # Track header - track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) # Based on observation default flag is 0x40 - track_chunk.add_variable("frame_start", _3ds_uint(0)) - track_chunk.add_variable("frame_total", _3ds_uint(0)) - track_chunk.add_variable("nkeys", _3ds_uint(1)) - # Keyframe header - track_chunk.add_variable("tcb_frame", _3ds_uint(0)) - track_chunk.add_variable("tcb_flags", _3ds_ushort()) - track_chunk.add_variable("position", _3ds_point_3d((target_x, target_y, target_z))) - - tar_node.add_subchunk(track_chunk) - - return tar_node - - -def make_ambient_node(world): - amb_color = world.color - amb_node = _3ds_chunk(AMBIENT_NODE_TAG) - track_chunk = _3ds_chunk(COL_TRACK_TAG) - - # Object node header, name is "$AMBIENT$" for ambient nodes - amb_node_header_chunk = _3ds_chunk(OBJECT_NODE_HDR) - amb_node_header_chunk.add_variable("name", _3ds_string(b"$AMBIENT$")) - amb_node_header_chunk.add_variable("flags1", _3ds_ushort(0x4000)) # Flags1 0x4000 for empty objects - amb_node_header_chunk.add_variable("flags2", _3ds_ushort(0)) - amb_node_header_chunk.add_variable("parent", _3ds_ushort(ROOT_OBJECT)) - amb_node.add_subchunk(amb_node_header_chunk) - - if world.animation_data.action: - action = world.animation_data.action - if action.fcurves: - fcurves = action.fcurves - kframes = [kf.co[0] for kf in [fc for fc in fcurves if fc is not None][0].keyframe_points] - nkeys = len(kframes) - if not 0 in kframes: - kframes.append(0) - nkeys = nkeys + 1 - kframes = sorted(set(kframes)) - track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) - track_chunk.add_variable("frame_start", _3ds_uint(int(action.frame_start))) - track_chunk.add_variable("frame_total", _3ds_uint(int(action.frame_end))) - track_chunk.add_variable("nkeys", _3ds_uint(nkeys)) - - for i, frame in enumerate(kframes): - ambient = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'color'] - if not ambient: - ambient.append(world.color) - track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) - track_chunk.add_variable("tcb_flags", _3ds_ushort()) - track_chunk.add_variable("color", _3ds_float_color(ambient)) - - else: # Track header - track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) - track_chunk.add_variable("frame_start", _3ds_uint(0)) - track_chunk.add_variable("frame_total", _3ds_uint(0)) - track_chunk.add_variable("nkeys", _3ds_uint(1)) - # Keyframe header - track_chunk.add_variable("tcb_frame", _3ds_uint(0)) - track_chunk.add_variable("tcb_flags", _3ds_ushort()) - track_chunk.add_variable("color", _3ds_float_color(amb_color)) - - amb_node.add_subchunk(track_chunk) - - return amb_node + return kf_obj_node +''' ########## @@ -1475,7 +1190,6 @@ def save(operator, scene = context.scene layer = context.view_layer depsgraph = context.evaluated_depsgraph_get() - world = scene.world if global_matrix is None: global_matrix = mathutils.Matrix() @@ -1502,23 +1216,18 @@ def save(operator, mscale.add_variable("scale", _3ds_float(1)) object_info.add_subchunk(mscale) - # Init main keyframe data chunk - if write_keyframe: - revision = 0x0005 - stop = scene.frame_end - start = scene.frame_start - curtime = scene.frame_current - kfdata = make_kfdata(revision, start, stop, curtime) - # Add AMBIENT color - if world is not None: + if scene.world is not None: ambient_chunk = _3ds_chunk(AMBIENTLIGHT) ambient_light = _3ds_chunk(RGB) ambient_light.add_variable("ambient", _3ds_float_color(scene.world.color)) ambient_chunk.add_subchunk(ambient_light) object_info.add_subchunk(ambient_chunk) - if write_keyframe and world.animation_data: - kfdata.add_subchunk(make_ambient_node(world)) + + ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX + # init main key frame data chunk: + kfdata = make_kfdata() + ''' # Make a list of all materials used in the selected meshes (use dictionary, each material is added once) materialDict = {} @@ -1593,8 +1302,6 @@ def save(operator, # Collect translation for transformation matrix translation = {} - rotation = {} - scale = {} # Give all objects a unique ID and build a dictionary from object name to object id # name_to_id = {} @@ -1630,25 +1337,24 @@ def save(operator, else: operator.report({'WARNING'}, "Object %r can't be written into a 3DS file") - # Export kf object node - if write_keyframe: - kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale)) + ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX + # make a kf object node for the object: + kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id)) + ''' i += i - # Create chunks for all empties, only requires a kf object node - if write_keyframe: - for ob in empty_objects: - kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale)) + # Create chunks for all empties: + ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX + for ob in empty_objects: + # Empties only require a kf object node: + kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id)) + pass + ''' # Create light object chunks for ob in light_objects: object_chunk = _3ds_chunk(OBJECT) - translation[ob.name] = ob.location - rotation[ob.name] = ob.rotation_euler.to_quaternion() - scale[ob.name] = ob.scale - - # Add light data subchunks light_chunk = _3ds_chunk(OBJECT_LIGHT) color_float_chunk = _3ds_chunk(RGB) energy_factor = _3ds_chunk(LIGHT_MULTIPLIER) @@ -1685,20 +1391,9 @@ def save(operator, object_chunk.add_subchunk(light_chunk) object_info.add_subchunk(object_chunk) - # Export light and spotlight target node - if write_keyframe: - kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale)) - if ob.data.type == 'SPOT': - kfdata.add_subchunk(make_target_node(ob, translation, rotation, scale)) - # Create camera object chunks for ob in camera_objects: object_chunk = _3ds_chunk(OBJECT) - translation[ob.name] = ob.location - rotation[ob.name] = ob.rotation_euler.to_quaternion() - scale[ob.name] = ob.scale - - # Add camera data subchunks camera_chunk = _3ds_chunk(OBJECT_CAMERA) diagonal = math.copysign(math.sqrt(pow(ob.location[0], 2) + pow(ob.location[1], 2)), ob.location[1]) focus_x = ob.location[0] + (ob.location[1] * math.tan(ob.rotation_euler[2])) @@ -1712,17 +1407,13 @@ def save(operator, object_chunk.add_subchunk(camera_chunk) object_info.add_subchunk(object_chunk) - # Export camera and target node - if write_keyframe: - kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale)) - kfdata.add_subchunk(make_target_node(ob, translation, rotation, scale)) - - # Add main object info chunk to primary chunk + # Add main object info chunk to primary chunk: primary.add_subchunk(object_info) - # Add main keyframe data chunk to primary chunk - if write_keyframe: - primary.add_subchunk(kfdata) + ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX + # Add main keyframe data chunk to primary chunk: + primary.add_subchunk(kfdata) + ''' # At this point, the chunk hierarchy is completely built # Check the size -- 2.30.2 From bfeb9905bacfe26f6e6ada51120fb8b71ba901a3 Mon Sep 17 00:00:00 2001 From: Sebastian Sille Date: Mon, 22 May 2023 17:29:22 +0200 Subject: [PATCH 19/19] io_scene_3ds: Finished code for Blender 3.6 release --- io_scene_3ds/__init__.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/io_scene_3ds/__init__.py b/io_scene_3ds/__init__.py index 35db8c4b8..32da0f204 100644 --- a/io_scene_3ds/__init__.py +++ b/io_scene_3ds/__init__.py @@ -16,7 +16,7 @@ import bpy bl_info = { "name": "Autodesk 3DS format", "author": "Bob Holcomb, Campbell Barton, Andreas Atteneder, Sebastian Schrand", - "version": (2, 4, 1), + "version": (2, 3, 4), "blender": (3, 6, 0), "location": "File > Import-Export", "description": "3DS Import/Export meshes, UVs, materials, textures, " @@ -65,12 +65,12 @@ class Import3DS(bpy.types.Operator, ImportHelper): "importing incorrectly", default=True, ) - read_keyframe: BoolProperty( + read_keyframe: bpy.props.BoolProperty( name="Read Keyframe", description="Read the keyframe data", default=True, ) - use_world_matrix: BoolProperty( + use_world_matrix: bpy.props.BoolProperty( name="World Space", description="Transform to matrix world", default=False, @@ -109,11 +109,6 @@ class Export3DS(bpy.types.Operator, ExportHelper): description="Export selected objects only", default=False, ) - write_keyframe: BoolProperty( - name="Write Keyframe", - description="Write the keyframe data", - default=False, - ) def execute(self, context): from . import export_3ds -- 2.30.2