From 0d26a2a017b1de4bb3688c670103565f0931a520 Mon Sep 17 00:00:00 2001 From: NRGSille Date: Fri, 9 Jun 2023 11:20:42 +0200 Subject: [PATCH] io_scene_3ds: Added hierarchy chunks Added hierarchy chunks to 3ds import and export. Prevents object hierarchy if no keyframer exported #104614 Signed-off-by: NRGSille --- io_scene_3ds/__init__.py | 7 +++- io_scene_3ds/export_3ds.py | 78 +++++++++++++++++++++++++++++++------- io_scene_3ds/import_3ds.py | 77 ++++++++++++++++++++++++++++++++----- 3 files changed, 137 insertions(+), 25 deletions(-) diff --git a/io_scene_3ds/__init__.py b/io_scene_3ds/__init__.py index 35db8c4b8..9e3438536 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, 4, 3), "blender": (3, 6, 0), "location": "File > Import-Export", "description": "3DS Import/Export meshes, UVs, materials, textures, " @@ -109,6 +109,11 @@ class Export3DS(bpy.types.Operator, ExportHelper): description="Export selected objects only", default=False, ) + use_hierarchy: bpy.props.BoolProperty( + name="Export Hierarchy", + description="Export hierarchy chunks", + default=False, + ) write_keyframe: BoolProperty( name="Write Keyframe", description="Write the keyframe data", diff --git a/io_scene_3ds/export_3ds.py b/io_scene_3ds/export_3ds.py index c9bff967f..4d781f75c 100644 --- a/io_scene_3ds/export_3ds.py +++ b/io_scene_3ds/export_3ds.py @@ -87,6 +87,8 @@ MASTERSCALE = 0x0100 # Master scale factor OBJECT_MESH = 0x4100 # This lets us know that we are reading a new object OBJECT_LIGHT = 0x4600 # This lets us know we are reading a light object OBJECT_CAMERA = 0x4700 # This lets us know we are reading a camera object +OBJECT_HIERARCHY = 0x4F00 # Hierarchy id of the object +OBJECT_PARENT = 0x4F10 # Parent id of the object # >------ Sub defines of LIGHT LIGHT_MULTIPLIER = 0x465B # The light energy factor @@ -1477,7 +1479,7 @@ def make_ambient_node(world): # EXPORT # ########## -def save(operator, context, filepath="", use_selection=False, write_keyframe=False, global_matrix=None): +def save(operator, context, filepath="", use_selection=False, use_hierarchy=False, write_keyframe=False, global_matrix=None): """Save the Blender scene to a 3ds file.""" # Time the export @@ -1608,6 +1610,7 @@ def save(operator, context, filepath="", use_selection=False, write_keyframe=Fal scale = {} # Give all objects a unique ID and build a dictionary from object name to object id + object_id = {} name_id = {} for ob, data, matrix in mesh_objects: @@ -1615,6 +1618,7 @@ def save(operator, context, filepath="", use_selection=False, write_keyframe=Fal rotation[ob.name] = ob.rotation_euler.to_quaternion().inverted() scale[ob.name] = ob.scale name_id[ob.name] = len(name_id) + object_id[ob.name] = len(object_id) for ob in empty_objects: translation[ob.name] = ob.location @@ -1622,6 +1626,12 @@ def save(operator, context, filepath="", use_selection=False, write_keyframe=Fal scale[ob.name] = ob.scale name_id[ob.name] = len(name_id) + for ob in light_objects: + object_id[ob.name] = len(object_id) + + for ob in camera_objects: + object_id[ob.name] = len(object_id) + # Create object chunks for all meshes i = 0 for ob, mesh, matrix in mesh_objects: @@ -1633,20 +1643,32 @@ def save(operator, context, filepath="", use_selection=False, write_keyframe=Fal # 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! + # Add hierachy chunk with ID from object_id dictionary + if use_hierarchy: + obj_hierarchy_chunk = _3ds_chunk(OBJECT_HIERARCHY) + obj_hierarchy_chunk.add_variable("hierarchy", _3ds_ushort(object_id[ob.name])) + + # Add parent chunk if object has a parent + if ob.parent is not None and (ob.parent.name in object_id): + obj_parent_chunk = _3ds_chunk(OBJECT_PARENT) + obj_parent_chunk.add_variable("parent", _3ds_ushort(object_id[ob.parent.name])) + obj_hierarchy_chunk.add_subchunk(obj_parent_chunk) + object_chunk.add_subchunk(obj_hierarchy_chunk) + + # 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") - # Export kf object node + # Export object node if write_keyframe: kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale, name_id)) i += i - # Create chunks for all empties, only requires a kf object node + # Create chunks for all empties - only requires a object node if write_keyframe: for ob in empty_objects: kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale, name_id)) @@ -1654,15 +1676,15 @@ def save(operator, context, filepath="", use_selection=False, write_keyframe=Fal # Create light object chunks for ob in light_objects: object_chunk = _3ds_chunk(OBJECT) - light_chunk = _3ds_chunk(OBJECT_LIGHT) + obj_light_chunk = _3ds_chunk(OBJECT_LIGHT) color_float_chunk = _3ds_chunk(RGB) - energy_factor = _3ds_chunk(LIGHT_MULTIPLIER) + light_energy_factor = _3ds_chunk(LIGHT_MULTIPLIER) object_chunk.add_variable("light", _3ds_string(sane_name(ob.name))) - light_chunk.add_variable("location", _3ds_point_3d(ob.location)) + obj_light_chunk.add_variable("location", _3ds_point_3d(ob.location)) color_float_chunk.add_variable("color", _3ds_float_color(ob.data.color)) - energy_factor.add_variable("energy", _3ds_float(ob.data.energy * 0.001)) - light_chunk.add_subchunk(color_float_chunk) - light_chunk.add_subchunk(energy_factor) + light_energy_factor.add_variable("energy", _3ds_float(ob.data.energy * 0.001)) + obj_light_chunk.add_subchunk(color_float_chunk) + obj_light_chunk.add_subchunk(light_energy_factor) if ob.data.type == 'SPOT': cone_angle = math.degrees(ob.data.spot_size) @@ -1684,10 +1706,24 @@ def save(operator, context, filepath="", use_selection=False, write_keyframe=Fal if ob.data.use_square: spot_square_chunk = _3ds_chunk(LIGHT_SPOT_RECTANGLE) spotlight_chunk.add_subchunk(spot_square_chunk) - light_chunk.add_subchunk(spotlight_chunk) + obj_light_chunk.add_subchunk(spotlight_chunk) - # Add light to object info - object_chunk.add_subchunk(light_chunk) + # Add light to object chunk + object_chunk.add_subchunk(obj_light_chunk) + + # Add hierachy chunks with ID from object_id dictionary + if use_hierarchy: + obj_hierarchy_chunk = _3ds_chunk(OBJECT_HIERARCHY) + obj_parent_chunk = _3ds_chunk(OBJECT_PARENT) + obj_hierarchy_chunk.add_variable("hierarchy", _3ds_ushort(object_id[ob.name])) + if ob.parent is None or (ob.parent.name not in object_id): + obj_parent_chunk.add_variable("parent", _3ds_ushort(ROOT_OBJECT)) + else: # Get the parent ID from the object_id dict + obj_parent_chunk.add_variable("parent", _3ds_ushort(object_id[ob.parent.name])) + obj_hierarchy_chunk.add_subchunk(obj_parent_chunk) + object_chunk.add_subchunk(obj_hierarchy_chunk) + + # Add light object and hierarchy chunks to object info object_info.add_subchunk(object_chunk) # Export light and spotlight target node @@ -1714,6 +1750,20 @@ def save(operator, context, filepath="", use_selection=False, write_keyframe=Fal camera_chunk.add_variable("roll", _3ds_float(round(ob.rotation_euler[1], 6))) camera_chunk.add_variable("lens", _3ds_float(ob.data.lens)) object_chunk.add_subchunk(camera_chunk) + + # Add hierachy chunks with ID from object_id dictionary + if use_hierarchy: + obj_hierarchy_chunk = _3ds_chunk(OBJECT_HIERARCHY) + obj_parent_chunk = _3ds_chunk(OBJECT_PARENT) + obj_hierarchy_chunk.add_variable("hierarchy", _3ds_ushort(object_id[ob.name])) + if ob.parent is None or (ob.parent.name not in object_id): + obj_parent_chunk.add_variable("parent", _3ds_ushort(ROOT_OBJECT)) + else: # Get the parent ID from the object_id dict + obj_parent_chunk.add_variable("parent", _3ds_ushort(object_id[ob.parent.name])) + obj_hierarchy_chunk.add_subchunk(obj_parent_chunk) + object_chunk.add_subchunk(obj_hierarchy_chunk) + + # Add light object and hierarchy chunks to object info object_info.add_subchunk(object_chunk) # Export camera and target node @@ -1755,4 +1805,4 @@ def save(operator, context, filepath="", use_selection=False, write_keyframe=Fal # Debugging only: dump the chunk hierarchy # primary.dump() - return {'FINISHED'} \ No newline at end of file + return {'FINISHED'} diff --git a/io_scene_3ds/import_3ds.py b/io_scene_3ds/import_3ds.py index fe6c26549..49ac602ea 100644 --- a/io_scene_3ds/import_3ds.py +++ b/io_scene_3ds/import_3ds.py @@ -92,6 +92,8 @@ MAT_MAP_BCOL = 0xA368 # Blue mapping OBJECT_MESH = 0x4100 # This lets us know that we are reading a new object OBJECT_LIGHT = 0x4600 # This lets us know we are reading a light object OBJECT_CAMERA = 0x4700 # This lets us know we are reading a camera object +OBJECT_HIERARCHY = 0x4F00 # This lets us know the hierachy id of the object +OBJECT_PARENT = 0x4F10 # This lets us know the parent id of the object # >------ Sub defines of LIGHT LIGHT_SPOTLIGHT = 0x4610 # The target of a spotlight @@ -322,6 +324,9 @@ def add_texture_to_material(image, contextWrapper, pct, extend, alpha, scale, of contextWrapper._grid_to_location(1, 0, dst_node=contextWrapper.node_out, ref_node=shader) +childs_list = [] +parent_list = [] + def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAIN, IMAGE_SEARCH, WORLD_MATRIX, KEYFRAME, CONVERSE): contextObName = None @@ -461,6 +466,7 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI temp_chunk = Chunk() CreateBlenderObject = False + CreateCameraObject = False CreateLightObject = False CreateTrackData = False @@ -924,6 +930,23 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI contextMatrix = mathutils.Matrix( (data[:3] + [0], data[3:6] + [0], data[6:9] + [0], data[9:] + [1])).transposed() + # If hierarchy chunk + elif new_chunk.ID == OBJECT_HIERARCHY: + child_id = read_short(new_chunk) + childs_list.insert(child_id, contextObName) + parent_list.insert(child_id, None) + if child_id in parent_list: + idp = parent_list.index(child_id) + parent_list[idp] = contextObName + elif new_chunk.ID == OBJECT_PARENT: + parent_id = read_short(new_chunk) + if parent_id > len(childs_list): + parent_list[child_id] = parent_id + parent_list.extend([None]*(parent_id-len(parent_list))) + parent_list.insert(parent_id, contextObName) + elif parent_id < len(childs_list): + parent_list[child_id] = childs_list[parent_id] + # If light chunk elif contextObName and new_chunk.ID == OBJECT_LIGHT: # Basic lamp support newLamp = bpy.data.lights.new("Lamp", 'POINT') @@ -934,9 +957,9 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI temp_data = file.read(SZ_3FLOAT) contextLamp.location = struct.unpack('<3f', temp_data) new_chunk.bytes_read += SZ_3FLOAT - contextMatrix = None # Reset matrix CreateBlenderObject = False CreateLightObject = True + contextMatrix = None # Reset matrix elif CreateLightObject and new_chunk.ID == COLOR_F: # Light color temp_data = file.read(SZ_3FLOAT) contextLamp.data.color = struct.unpack('<3f', temp_data) @@ -973,6 +996,21 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI contextLamp.data.show_cone = True elif CreateLightObject and new_chunk.ID == LIGHT_SPOT_RECTANGLE: # Square contextLamp.data.use_square = True + elif CreateLightObject and new_chunk.ID == OBJECT_HIERARCHY: + child_id = read_short(new_chunk) + childs_list.insert(child_id, contextObName) + parent_list.insert(child_id, None) + if child_id in parent_list: + idp = parent_list.index(child_id) + parent_list[idp] = contextObName + elif CreateLightObject and new_chunk.ID == OBJECT_PARENT: + parent_id = read_short(new_chunk) + if parent_id > len(childs_list): + parent_list[child_id] = parent_id + parent_list.extend([None]*(parent_id-len(parent_list))) + parent_list.insert(parent_id, contextObName) + elif parent_id < len(childs_list): + parent_list[child_id] = childs_list[parent_id] # If camera chunk elif contextObName and new_chunk.ID == OBJECT_CAMERA: # Basic camera support @@ -996,8 +1034,24 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI temp_data = file.read(SZ_FLOAT) contextCamera.data.lens = float(struct.unpack(' len(childs_list): + parent_list[child_id] = parent_id + parent_list.extend([None]*(parent_id-len(parent_list))) + parent_list.insert(parent_id, contextObName) + elif parent_id < len(childs_list): + parent_list[child_id] = childs_list[parent_id] # start keyframe section elif new_chunk.ID == EDITKEYFRAME: @@ -1296,12 +1350,22 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI #pivot_list[ind] += pivot_list[parent] # Not sure this is correct, should parent space matrix be applied before combining? + # if parent name for par, objs in parent_dictionary.items(): parent = object_dictionary.get(par) for ob in objs: if parent is not None: ob.parent = parent + # If hierarchy + hierarchy = dict(zip(childs_list, parent_list)) + hierarchy.pop(None, ...) + for idt, (child, parent) in enumerate(hierarchy.items()): + child_obj = object_dictionary.get(child) + parent_obj = object_dictionary.get(parent) + if child_obj and parent_obj is not None: + child_obj.parent = parent_obj + # fix pivots for ind, ob in enumerate(object_list): if ob.type == 'MESH': @@ -1312,14 +1376,7 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI ob.data.transform(pivot_matrix) -def load_3ds(filepath, - context, - CONSTRAIN=10.0, - IMAGE_SEARCH=True, - WORLD_MATRIX=False, - KEYFRAME=True, - APPLY_MATRIX=True, - CONVERSE=None): +def load_3ds(filepath, context, CONSTRAIN=10.0, IMAGE_SEARCH=True, WORLD_MATRIX=False, KEYFRAME=True, APPLY_MATRIX=True, CONVERSE=None): print("importing 3DS: %r..." % (filepath), end="") -- 2.30.2