diff --git a/io_scene_3ds/__init__.py b/io_scene_3ds/__init__.py index ddfff583e..176722ab4 100644 --- a/io_scene_3ds/__init__.py +++ b/io_scene_3ds/__init__.py @@ -18,7 +18,7 @@ import bpy bl_info = { "name": "Autodesk 3DS format", "author": "Bob Holcomb, Campbell Barton, Andreas Atteneder, Sebastian Schrand", - "version": (2, 4, 5), + "version": (2, 4, 6), "blender": (3, 6, 0), "location": "File > Import-Export", "description": "3DS Import/Export meshes, UVs, materials, textures, " @@ -66,6 +66,17 @@ class Import3DS(bpy.types.Operator, ImportHelper): "(Warning, may be slow)", default=True, ) + object_filter: EnumProperty( + name="Object Filter", options={'ENUM_FLAG'}, + items=(('WORLD',"World".rjust(11),"",'WORLD_DATA',0x1), + ('MESH',"Mesh".rjust(11),"",'MESH_DATA',0x2), + ('LIGHT',"Light".rjust(12),"",'LIGHT_DATA',0x4), + ('CAMERA',"Camera".rjust(11),"",'CAMERA_DATA',0x8), + ('EMPTY',"Empty".rjust(11),"",'EMPTY_DATA',0x10), + ), + description="Object types to export", + default={'WORLD', 'MESH', 'LIGHT', 'CAMERA', 'EMPTY'}, + ) use_apply_transform: BoolProperty( name="Apply Transform", description="Workaround for object transformations " @@ -124,6 +135,7 @@ class MAX3DS_PT_import_include(bpy.types.Panel): operator = sfile.active_operator layout.prop(operator, "use_image_search") + layout.column().prop(operator, "object_filter") layout.prop(operator, "read_keyframe") @@ -186,15 +198,16 @@ class Export3DS(bpy.types.Operator, ExportHelper): description="Export selected objects only", default=False, ) - object_filter: bpy.props.EnumProperty( + object_filter: EnumProperty( name="Object Filter", options={'ENUM_FLAG'}, - items=(('MESH',"Mesh".rjust(11),"",'MESH_DATA',0x1), - ('LIGHT',"Light".rjust(12),"",'LIGHT_DATA',0x2), - ('CAMERA',"Camera".rjust(11),"",'CAMERA_DATA',0x4), - ('EMPTY',"Empty".rjust(11),"",'EMPTY_DATA',0x8), - ), + items=(('WORLD', "World".rjust(11), "", 'WORLD_DATA',0x1), + ('MESH', "Mesh".rjust(11), "", 'MESH_DATA', 0x2), + ('LIGHT', "Light".rjust(12), "", 'LIGHT_DATA',0x4), + ('CAMERA', "Camera".rjust(11), "", 'CAMERA_DATA',0x8), + ('EMPTY', "Empty".rjust(11), "", 'EMPTY_DATA',0x10), + ), description="Object types to export", - default={'MESH', 'LIGHT', 'CAMERA', 'EMPTY'}, + default={'WORLD', 'MESH', 'LIGHT', 'CAMERA', 'EMPTY'}, ) use_hierarchy: BoolProperty( name="Export Hierarchy", @@ -248,10 +261,8 @@ class MAX3DS_PT_export_include(bpy.types.Panel): operator = sfile.active_operator layout.prop(operator, "use_selection") - laysub = layout.column(align=True) - laysub.enabled = (not operator.use_selection) - laysub.prop(operator, "object_filter") - layout.column().prop(operator, "use_hierarchy") + layout.column().prop(operator, "object_filter") + layout.prop(operator, "use_hierarchy") layout.prop(operator, "write_keyframe") diff --git a/io_scene_3ds/export_3ds.py b/io_scene_3ds/export_3ds.py index 21f6dea20..f40659046 100644 --- a/io_scene_3ds/export_3ds.py +++ b/io_scene_3ds/export_3ds.py @@ -24,12 +24,18 @@ from bpy_extras import node_shader_utils PRIMARY = 0x4D4D # >----- Main Chunks +OBJECTINFO = 0x3D3D # Main mesh object chunk before material and object information +MESHVERSION = 0x3D3E # This gives the version of the mesh VERSION = 0x0002 # This gives the version of the .3ds file KFDATA = 0xB000 # This is the header for all of the keyframe info # >----- sub defines of OBJECTINFO -OBJECTINFO = 0x3D3D # Main mesh object chunk before material and object information -MESHVERSION = 0x3D3E # This gives the version of the mesh +BITMAP = 0x1100 # The background image name +USE_BITMAP = 0x1101 # The background image flag +SOLIDBACKGND = 0x1200 # The background color (RGB) +USE_SOLIDBGND = 0x1201 # The background color flag +VGRADIENT = 0x1300 # The background gradient colors +USE_VGRADIENT = 0x1301 # The background gradient flag AMBIENTLIGHT = 0x2100 # The color of the ambient light MATERIAL = 45055 # 0xAFFF // This stored the texture info OBJECT = 16384 # 0x4000 // This stores the faces, vertices, etc... @@ -1549,13 +1555,30 @@ def save(operator, context, filepath="", scale_factor=1.0, apply_unit=False, use curtime = scene.frame_current kfdata = make_kfdata(revision, start, stop, curtime) - # Add AMBIENT color - if world is not None: + # Add AMBIENT and BACKGROUND color + if world is not None and 'WORLD' in object_filter: ambient_chunk = _3ds_chunk(AMBIENTLIGHT) ambient_light = _3ds_chunk(RGB) ambient_light.add_variable("ambient", _3ds_float_color(world.color)) ambient_chunk.add_subchunk(ambient_light) object_info.add_subchunk(ambient_chunk) + if world.use_nodes: + ntree = world.node_tree.links + background_color = _3ds_chunk(RGB) + background_chunk = _3ds_chunk(SOLIDBACKGND) + background_flag = _3ds_chunk(USE_SOLIDBGND) + bgcol, bgtex, nworld = 'BACKGROUND', 'TEX_IMAGE', 'OUTPUT_WORLD' + bg_color = next((lk.from_node.inputs[0].default_value[:3] for lk in ntree if lk.to_node.type == nworld), world.color) + bg_image = next((lk.from_node.image.name for lk in ntree if lk.from_node.type == bgtex and lk.to_node.type in {bgcol, nworld}), False) + background_color.add_variable("color", _3ds_float_color(bg_color)) + background_chunk.add_subchunk(background_color) + if bg_image: + background_image = _3ds_chunk(BITMAP) + background_flag = _3ds_chunk(USE_BITMAP) + background_image.add_variable("image", _3ds_string(sane_name(bg_image))) + object_info.add_subchunk(background_image) + object_info.add_subchunk(background_chunk) + object_info.add_subchunk(background_flag) if write_keyframe and world.animation_data: kfdata.add_subchunk(make_ambient_node(world)) @@ -1564,7 +1587,7 @@ def save(operator, context, filepath="", scale_factor=1.0, apply_unit=False, use mesh_objects = [] if use_selection: - objects = [ob for ob in scene.objects if ob.visible_get(view_layer=layer) and ob.select_get(view_layer=layer)] + objects = [ob for ob in scene.objects if ob.type in object_filter and ob.visible_get(view_layer=layer) and ob.select_get(view_layer=layer)] else: objects = [ob for ob in scene.objects if ob.type in object_filter and ob.visible_get(view_layer=layer)] diff --git a/io_scene_3ds/import_3ds.py b/io_scene_3ds/import_3ds.py index c7dc5d1d6..cd6c10f6a 100644 --- a/io_scene_3ds/import_3ds.py +++ b/io_scene_3ds/import_3ds.py @@ -25,7 +25,6 @@ PRIMARY = 0x4D4D # >----- Main Chunks OBJECTINFO = 0x3D3D # This gives the version of the mesh and is found right before the material and object information VERSION = 0x0002 # This gives the version of the .3ds file -AMBIENTLIGHT = 0x2100 # The color of the ambient light EDITKEYFRAME = 0xB000 # This is the header for all of the key frame info # >----- Data Chunks, used for various attributes @@ -38,6 +37,7 @@ PCT_FLOAT = 0x0031 # percentage float MASTERSCALE = 0x0100 # Master scale factor # >----- sub defines of OBJECTINFO +AMBIENTLIGHT = 0x2100 # The color of the ambient light MATERIAL = 0xAFFF # This stored the texture info OBJECT = 0x4000 # This stores the faces, vertices, etc... @@ -131,13 +131,13 @@ OBJECT_SMOOTH = 0x4150 # The objects face smooth groups OBJECT_TRANS_MATRIX = 0x4160 # The objects Matrix # >------ sub defines of EDITKEYFRAME -KFDATA_AMBIENT = 0xB001 # Keyframe ambient node -KFDATA_OBJECT = 0xB002 # Keyframe object node -KFDATA_CAMERA = 0xB003 # Keyframe camera node -KFDATA_TARGET = 0xB004 # Keyframe target node -KFDATA_LIGHT = 0xB005 # Keyframe light node -KFDATA_LTARGET = 0xB006 # Keyframe light target node -KFDATA_SPOTLIGHT = 0xB007 # Keyframe spotlight node +KF_AMBIENT = 0xB001 # Keyframe ambient node +KF_OBJECT = 0xB002 # Keyframe object node +KF_OBJECT_CAMERA = 0xB003 # Keyframe camera node +KF_TARGET_CAMERA = 0xB004 # Keyframe target node +KF_OBJECT_LIGHT = 0xB005 # Keyframe light node +KF_TARGET_LIGHT = 0xB006 # Keyframe light target node +KF_OBJECT_SPOT_LIGHT = 0xB007 # Keyframe spotlight node KFDATA_KFSEG = 0xB008 # Keyframe start and stop KFDATA_CURTIME = 0xB009 # Keyframe current frame KFDATA_KFHDR = 0xB00A # Keyframe node header @@ -326,7 +326,7 @@ childs_list = [] parent_list = [] def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAIN, - IMAGE_SEARCH, WORLD_MATRIX, KEYFRAME, CONVERSE, MEASURE): + FILTER, IMAGE_SEARCH, WORLD_MATRIX, KEYFRAME, CONVERSE, MEASURE): contextObName = None contextLamp = None @@ -470,6 +470,12 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI CreateLightObject = False CreateTrackData = False + CreateWorld = 'WORLD' in FILTER + CreateMesh = 'MESH' in FILTER + CreateLight = 'LIGHT' in FILTER + CreateCamera = 'CAMERA' in FILTER + CreateEmpty = 'EMPTY' in FILTER + def read_short(temp_chunk): temp_data = file.read(SZ_U_SHORT) temp_chunk.bytes_read += SZ_U_SHORT @@ -666,7 +672,7 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI print("\tNon-Fatal Error: Version greater than 3, may not load correctly: ", version) # is it an ambient light chunk? - elif new_chunk.ID == AMBIENTLIGHT: + elif CreateWorld and new_chunk.ID == AMBIENTLIGHT: path, filename = os.path.split(file.name) realname, ext = os.path.splitext(filename) world = bpy.data.worlds.new("Ambient: " + realname) @@ -683,24 +689,17 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI # is it an object info chunk? elif new_chunk.ID == OBJECTINFO: process_next_chunk(context, file, new_chunk, imported_objects, CONSTRAIN, - IMAGE_SEARCH, WORLD_MATRIX, KEYFRAME, CONVERSE, MEASURE) + FILTER, IMAGE_SEARCH, WORLD_MATRIX, KEYFRAME, CONVERSE, MEASURE) # keep track of how much we read in the main chunk new_chunk.bytes_read += temp_chunk.bytes_read # is it an object chunk? elif new_chunk.ID == OBJECT: - if CreateBlenderObject: - putContextMesh( - context, - contextMesh_vertls, - contextMesh_facels, - contextMesh_flag, - contextMeshMaterials, - contextMesh_smooth, - WORLD_MATRIX - ) + putContextMesh(context, contextMesh_vertls, contextMesh_facels, contextMesh_flag, + contextMeshMaterials, contextMesh_smooth, WORLD_MATRIX) + contextMesh_vertls = [] contextMesh_facels = [] contextMeshMaterials = [] @@ -709,7 +708,7 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI contextMeshUV = None contextMatrix = None - CreateBlenderObject = True + CreateBlenderObject = True if CreateMesh else False contextObName, read_str_len = read_string(file) new_chunk.bytes_read += read_str_len @@ -867,13 +866,13 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI elif new_chunk.ID == OBJECT_MESH: pass - elif new_chunk.ID == OBJECT_VERTICES: + elif CreateMesh and new_chunk.ID == OBJECT_VERTICES: """Worldspace vertex locations""" num_verts = read_short(new_chunk) contextMesh_vertls = struct.unpack('<%df' % (num_verts * 3), file.read(SZ_3FLOAT * num_verts)) new_chunk.bytes_read += SZ_3FLOAT * num_verts - elif new_chunk.ID == OBJECT_FACES: + elif CreateMesh and new_chunk.ID == OBJECT_FACES: num_faces = read_short(new_chunk) temp_data = file.read(SZ_4U_SHORT * num_faces) new_chunk.bytes_read += SZ_4U_SHORT * num_faces # 4 short ints x 2 bytes each @@ -881,7 +880,7 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI contextMesh_flag = [contextMesh_facels[i] for i in range(3, (num_faces * 4) + 3, 4)] contextMesh_facels = [contextMesh_facels[i - 3:i] for i in range(3, (num_faces * 4) + 3, 4)] - elif new_chunk.ID == OBJECT_MATERIAL: + elif CreateMesh and new_chunk.ID == OBJECT_MATERIAL: material_name, read_str_len = read_string(file) new_chunk.bytes_read += read_str_len # remove 1 null character. num_faces_using_mat = read_short(new_chunk) @@ -891,19 +890,19 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI contextMeshMaterials.append((material_name, temp_data)) # look up the material in all the materials - elif new_chunk.ID == OBJECT_SMOOTH: + elif CreateMesh and new_chunk.ID == OBJECT_SMOOTH: temp_data = file.read(SZ_U_INT * num_faces) smoothgroup = struct.unpack('<%dI' % (num_faces), temp_data) new_chunk.bytes_read += SZ_U_INT * num_faces contextMesh_smooth = smoothgroup - elif new_chunk.ID == OBJECT_UV: + elif CreateMesh and new_chunk.ID == OBJECT_UV: num_uv = read_short(new_chunk) temp_data = file.read(SZ_2FLOAT * num_uv) new_chunk.bytes_read += SZ_2FLOAT * num_uv contextMeshUV = struct.unpack('<%df' % (num_uv * 2), temp_data) - elif new_chunk.ID == OBJECT_TRANS_MATRIX: + elif CreateMesh and new_chunk.ID == OBJECT_TRANS_MATRIX: # How do we know the matrix size? 54 == 4x4 48 == 4x3 temp_data = file.read(SZ_4x3MAT) mtx = list(struct.unpack('