From f07ff3ac2bfd9cf9540bebab8da1546fde6c3bd6 Mon Sep 17 00:00:00 2001 From: Sebastian Sille Date: Wed, 29 Mar 2023 12:18:37 +0200 Subject: [PATCH] io_scene_3ds: Update for Blender 3.x [#104493](https://projects.blender.org/blender/blender-addons/issues/104493) Move back from blender-addons-contrib Signed-off-by: Sebastian Sille --- io_scene_3ds/__init__.py | 176 +++++ io_scene_3ds/export_3ds.py | 1453 ++++++++++++++++++++++++++++++++++++ io_scene_3ds/import_3ds.py | 1271 +++++++++++++++++++++++++++++++ 3 files changed, 2900 insertions(+) create mode 100644 io_scene_3ds/__init__.py create mode 100644 io_scene_3ds/export_3ds.py create mode 100644 io_scene_3ds/import_3ds.py diff --git a/io_scene_3ds/__init__.py b/io_scene_3ds/__init__.py new file mode 100644 index 000000000..b207673b1 --- /dev/null +++ b/io_scene_3ds/__init__.py @@ -0,0 +1,176 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +from bpy_extras.io_utils import ( + ImportHelper, + ExportHelper, + orientation_helper, + axis_conversion, +) +from bpy.props import ( + BoolProperty, + EnumProperty, + FloatProperty, + StringProperty, +) +import bpy +bl_info = { + "name": "Autodesk 3DS format", + "author": "Bob Holcomb, Campbell Barton, Andreas Atteneder, Sebastian Schrand", + "version": (2, 3, 1), + "blender": (3, 0, 0), + "location": "File > Import", + "description": "Import 3DS, meshes, uvs, materials, textures, " + "cameras & lamps", + "warning": "Images must be in file folder, " + "filenames are limited to DOS 8.3 format", + "doc_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" + "Scripts/Import-Export/Autodesk_3DS", + "category": "Import-Export", +} + +if "bpy" in locals(): + import importlib + if "import_3ds" in locals(): + importlib.reload(import_3ds) + if "export_3ds" in locals(): + importlib.reload(export_3ds) + + +@orientation_helper(axis_forward='Y', axis_up='Z') +class Import3DS(bpy.types.Operator, ImportHelper): + """Import from 3DS file format (.3ds)""" + bl_idname = "import_scene.autodesk_3ds" + bl_label = 'Import 3DS' + bl_options = {'UNDO'} + + filename_ext = ".3ds" + filter_glob: StringProperty(default="*.3ds", options={'HIDDEN'}) + + constrain_size: FloatProperty( + name="Size Constraint", + description="Scale the model by 10 until it reaches the " + "size constraint (0 to disable)", + min=0.0, max=1000.0, + soft_min=0.0, soft_max=1000.0, + default=10.0, + ) + use_image_search: BoolProperty( + name="Image Search", + description="Search subdirectories for any associated images " + "(Warning, may be slow)", + default=True, + ) + use_apply_transform: BoolProperty( + name="Apply Transform", + description="Workaround for object transformations " + "importing incorrectly", + default=True, + ) + + read_keyframe: bpy.props.BoolProperty( + name="Read Keyframe", + description="Read the keyframe data", + default=True, + ) + + def execute(self, context): + from . import import_3ds + + keywords = self.as_keywords(ignore=("axis_forward", + "axis_up", + "filter_glob", + )) + + global_matrix = axis_conversion(from_forward=self.axis_forward, + from_up=self.axis_up, + ).to_4x4() + keywords["global_matrix"] = global_matrix + + return import_3ds.load(self, context, **keywords) + + +@orientation_helper(axis_forward='Y', axis_up='Z') +class Export3DS(bpy.types.Operator, ExportHelper): + """Export to 3DS file format (.3ds)""" + bl_idname = "export_scene.autodesk_3ds" + bl_label = 'Export 3DS' + + filename_ext = ".3ds" + filter_glob: StringProperty( + default="*.3ds", + options={'HIDDEN'}, + ) + + use_selection: BoolProperty( + name="Selection Only", + description="Export selected objects only", + default=False, + ) + + def execute(self, context): + from . import export_3ds + + keywords = self.as_keywords(ignore=("axis_forward", + "axis_up", + "filter_glob", + "check_existing", + )) + global_matrix = axis_conversion(to_forward=self.axis_forward, + to_up=self.axis_up, + ).to_4x4() + keywords["global_matrix"] = global_matrix + + return export_3ds.save(self, context, **keywords) + + +# Add to a menu +def menu_func_export(self, context): + self.layout.operator(Export3DS.bl_idname, text="3D Studio (.3ds)") + + +def menu_func_import(self, context): + self.layout.operator(Import3DS.bl_idname, text="3D Studio (.3ds)") + + +def register(): + bpy.utils.register_class(Import3DS) + bpy.utils.register_class(Export3DS) + + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) + bpy.types.TOPBAR_MT_file_export.append(menu_func_export) + + +def unregister(): + bpy.utils.unregister_class(Import3DS) + bpy.utils.unregister_class(Export3DS) + + bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) + +# NOTES: +# why add 1 extra vertex? and remove it when done? - +# "Answer - eekadoodle - would need to re-order UV's without this since face +# order isnt always what we give blender, BMesh will solve :D" +# +# disabled scaling to size, this requires exposing bb (easy) and understanding +# how it works (needs some time) + + +if __name__ == "__main__": + register() diff --git a/io_scene_3ds/export_3ds.py b/io_scene_3ds/export_3ds.py new file mode 100644 index 000000000..470a2d60c --- /dev/null +++ b/io_scene_3ds/export_3ds.py @@ -0,0 +1,1453 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Script copyright (C) Bob Holcomb +# Contributors: Campbell Barton, Bob Holcomb, Richard Lärkäng, Damien McGinnes, Mark Stijnman, Sebastian Sille + +""" +Exporting is based on 3ds loader from www.gametutorials.com(Thanks DigiBen) and using information +from the lib3ds project (http://lib3ds.sourceforge.net/) sourcecode. +""" + +import bpy +import math +import struct +import mathutils +import bpy_extras +from bpy_extras import node_shader_utils + +###################################################### +# Data Structures +###################################################### + +# Some of the chunks that we will export +# ----- Primary Chunk, at the beginning of each file +PRIMARY = 0x4D4D + +# ------ Main Chunks +VERSION = 0x0002 # This gives the version of the .3ds file +KFDATA = 0xB000 # This is the header for all of the key frame info + +# ------ sub defines of OBJECTINFO +OBJECTINFO = 0x3D3D # Main mesh object chunk before the material and object information +MESHVERSION = 0x3D3E # This gives the version of the mesh +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... + +# >------ sub defines of MATERIAL +MATNAME = 0xA000 # This holds the material name +MATAMBIENT = 0xA010 # Ambient color of the object/material +MATDIFFUSE = 0xA020 # This holds the color of the object/material +MATSPECULAR = 0xA030 # Specular color of the object/material +MATSHINESS = 0xA040 # Specular intensity of the object/material (percent) +MATSHIN2 = 0xA041 # Reflection of the object/material (percent) +MATSHIN3 = 0xA042 # metallic/mirror of the object/material (percent) +MATTRANS = 0xA050 # Transparency value (100-OpacityValue) (percent) +MATSELFILPCT = 0xA084 # Self illumination strength (percent) +MATSHADING = 0xA100 # Material shading method + +MAT_DIFFUSEMAP = 0xA200 # This is a header for a new diffuse texture +MAT_SPECMAP = 0xA204 # head for specularity map +MAT_OPACMAP = 0xA210 # head for opacity map +MAT_REFLMAP = 0xA220 # head for reflect map +MAT_BUMPMAP = 0xA230 # head for normal map +MAT_BUMP_PERCENT = 0xA252 # Normalmap strength (percent) +MAT_TEX2MAP = 0xA33A # head for secondary texture +MAT_SHINMAP = 0xA33C # head for roughness map +MAT_SELFIMAP = 0xA33D # head for emission map + +# >------ sub defines of MAT_MAP +MATMAPFILE = 0xA300 # This holds the file name of a texture +MAT_MAP_TILING = 0xa351 # 2nd bit (from LSB) is mirror UV flag +MAT_MAP_TEXBLUR = 0xA353 # Texture blurring factor +MAT_MAP_USCALE = 0xA354 # U axis scaling +MAT_MAP_VSCALE = 0xA356 # V axis scaling +MAT_MAP_UOFFSET = 0xA358 # U axis offset +MAT_MAP_VOFFSET = 0xA35A # V axis offset +MAT_MAP_ANG = 0xA35C # UV rotation around the z-axis in rad +MAP_COL1 = 0xA360 # Tint Color1 +MAP_COL2 = 0xA362 # Tint Color2 +MAP_RCOL = 0xA364 # Red tint +MAP_GCOL = 0xA366 # Green tint +MAP_BCOL = 0xA368 # Blue tint + +RGB = 0x0010 # RGB float +RGB1 = 0x0011 # RGB Color1 +RGB2 = 0x0012 # RGB Color2 +PCT = 0x0030 # Percent chunk +MASTERSCALE = 0x0100 # Master scale factor + +# >------ sub defines of OBJECT +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 + +# >------ Sub defines of LIGHT +LIGHT_MULTIPLIER = 0x465B # The light energy factor +LIGHT_SPOTLIGHT = 0x4610 # The target of a spotlight +LIGHT_SPOTROLL = 0x4656 # The roll angle of the spot + +# >------ sub defines of CAMERA +OBJECT_CAM_RANGES = 0x4720 # The camera range values + +# >------ sub defines of OBJECT_MESH +OBJECT_VERTICES = 0x4110 # The objects vertices +OBJECT_VERTFLAGS = 0x4111 # The objects vertex flags +OBJECT_FACES = 0x4120 # The objects faces +OBJECT_MATERIAL = 0x4130 # This is found if the object has a material, either texture map or color +OBJECT_UV = 0x4140 # The UV texture coordinates +OBJECT_SMOOTH = 0x4150 # The objects smooth groups +OBJECT_TRANS_MATRIX = 0x4160 # The Object Matrix + +# >------ sub defines of KFDATA +KFDATA_KFHDR = 0xB00A +KFDATA_KFSEG = 0xB008 +KFDATA_KFCURTIME = 0xB009 +KFDATA_OBJECT_NODE_TAG = 0xB002 + +# >------ sub defines of OBJECT_NODE_TAG +OBJECT_NODE_ID = 0xB030 +OBJECT_NODE_HDR = 0xB010 +OBJECT_PIVOT = 0xB013 +OBJECT_INSTANCE_NAME = 0xB011 +POS_TRACK_TAG = 0xB020 +ROT_TRACK_TAG = 0xB021 +SCL_TRACK_TAG = 0xB022 + + +# So 3ds max can open files, limit names to 12 in length +# this is very annoying for filenames! +name_unique = [] # stores str, ascii only +name_mapping = {} # stores {orig: byte} mapping + + +def sane_name(name): + name_fixed = name_mapping.get(name) + if name_fixed is not None: + return name_fixed + + # strip non ascii chars + new_name_clean = new_name = name.encode("ASCII", "replace").decode("ASCII")[:12] + i = 0 + + while new_name in name_unique: + new_name = new_name_clean + ".%.3d" % i + i += 1 + + # note, appending the 'str' version. + name_unique.append(new_name) + name_mapping[name] = new_name = new_name.encode("ASCII", "replace") + return new_name + + +def uv_key(uv): + return round(uv[0], 6), round(uv[1], 6) + + +# 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***""" + __slots__ = ("value", ) + + def __init__(self, val=0): + self.value = val + + def get_size(self): + return SZ_SHORT + + def write(self, file): + file.write(struct.pack("= ma_ls_len: + 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 + + img = get_uv_image(ma) + img_name = None if img is None else img.name + + materialDict.setdefault((ma_name, img_name), (ma, img)) + + else: + for ma in ma_ls: + if ma: # material may be None so check its not. + materialDict.setdefault((ma.name, None), (ma, None)) + + # Why 0 Why! + for f in data.polygons: + if f.material_index >= ma_ls_len: + f.material_index = 0 + + # ob_derived_eval.to_mesh_clear() + + #if free: + # free_derived_objects(ob) + + # 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])) + + # Give all objects a unique ID and build a dictionary from object name to object id: + translation = {} # collect translation for transformation matrix + #name_to_id = {} + for ob, data, matrix in mesh_objects: + translation[ob.name] = ob.location + #name_to_id[ob.name]= len(name_to_id) + """ + #for ob in empty_objects: + # name_to_id[ob.name]= len(name_to_id) + """ + + # Create object chunks for all meshes: + i = 0 + for ob, mesh, matrix in mesh_objects: + # create a new object chunk + object_chunk = _3ds_chunk(OBJECT) + + # set the object name + object_chunk.add_variable("name", _3ds_string(sane_name(ob.name))) + + # 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. + 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)) + ''' + + # if not blender_mesh.users: + # bpy.data.meshes.remove(blender_mesh) + #blender_mesh.vertices = None + + 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 light object chunks + for ob in light_objects: + object_chunk = _3ds_chunk(OBJECT) + light_chunk = _3ds_chunk(OBJECT_LIGHT) + color_float_chunk = _3ds_chunk(RGB) + 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)) + color_float_chunk.add_variable("color", _3ds_float_color(ob.data.color)) + energy_factor.add_variable("energy", _3ds_float(ob.data.energy * .001)) + light_chunk.add_subchunk(color_float_chunk) + light_chunk.add_subchunk(energy_factor) + + if ob.data.type == 'SPOT': + cone_angle = math.degrees(ob.data.spot_size) + hotspot = cone_angle - (ob.data.spot_blend * math.floor(cone_angle)) + hypo = math.copysign(math.sqrt(pow(ob.location[0], 2) + pow(ob.location[1], 2)), ob.location[1]) + pos_x = ob.location[0] + (ob.location[1] * math.tan(ob.rotation_euler[2])) + pos_y = ob.location[1] + (ob.location[0] * math.tan(math.radians(90) - ob.rotation_euler[2])) + pos_z = hypo * math.tan(math.radians(90) - ob.rotation_euler[0]) + spotlight_chunk = _3ds_chunk(LIGHT_SPOTLIGHT) + spot_roll_chunk = _3ds_chunk(LIGHT_SPOTROLL) + spotlight_chunk.add_variable("target", _3ds_point_3d((pos_x, pos_y, pos_z))) + spotlight_chunk.add_variable("hotspot", _3ds_float(round(hotspot, 4))) + spotlight_chunk.add_variable("angle", _3ds_float(round(cone_angle, 4))) + spot_roll_chunk.add_variable("roll", _3ds_float(round(ob.rotation_euler[1], 6))) + spotlight_chunk.add_subchunk(spot_roll_chunk) + light_chunk.add_subchunk(spotlight_chunk) + + # Add light to object info + object_chunk.add_subchunk(light_chunk) + object_info.add_subchunk(object_chunk) + + # Create camera object chunks + for ob in camera_objects: + 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])) + focus_y = ob.location[1] + (ob.location[0] * math.tan(math.radians(90) - ob.rotation_euler[2])) + focus_z = diagonal * math.tan(math.radians(90) - ob.rotation_euler[0]) + object_chunk.add_variable("camera", _3ds_string(sane_name(ob.name))) + camera_chunk.add_variable("location", _3ds_point_3d(ob.location)) + camera_chunk.add_variable("target", _3ds_point_3d((focus_x, focus_y, focus_z))) + 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) + object_info.add_subchunk(object_chunk) + + # 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) + ''' + + # At this point, the chunk hierarchy is completely built. + + # Check the size: + primary.get_size() + # Open the file for writing: + file = open(filepath, 'wb') + + # Recursively write the chunks to file: + primary.write(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: + # Blender.Window.WaitCursor(0) + print("3ds export time: %.2f" % (time.time() - duration)) + + # Debugging only: dump the chunk hierarchy: + # primary.dump() + + return {'FINISHED'} diff --git a/io_scene_3ds/import_3ds.py b/io_scene_3ds/import_3ds.py new file mode 100644 index 000000000..9b385881f --- /dev/null +++ b/io_scene_3ds/import_3ds.py @@ -0,0 +1,1271 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Script copyright (C) Bob Holcomb +# Contributors: Bob Holcomb, Richard L?rk?ng, Damien McGinnes, Sebastian Sille +# Campbell Barton, Mario Lapin, Dominique Lorre, Andreas Atteneder + +import os +import time +import struct +import bpy +import math +import mathutils +from bpy_extras.node_shader_utils import PrincipledBSDFWrapper + +BOUNDS_3DS = [] + + +###################################################### +# Data Structures +###################################################### + +# Some of the chunks that we will see +# ----- Primary Chunk, at the beginning of each file +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 +EDITKEYFRAME = 0xB000 # This is the header for all of the key frame info + +# ------ Data Chunks, used for various attributes +PERCENTAGE_SHORT = 0x30 +PERCENTAGE_FLOAT = 0x31 + +# ------ sub defines of OBJECTINFO +MATERIAL = 0xAFFF # This stored the texture info +OBJECT = 0x4000 # This stores the faces, vertices, etc... + +# >------ sub defines of MATERIAL +# ------ sub defines of MATERIAL_BLOCK +MAT_NAME = 0xA000 # This holds the material name +MAT_AMBIENT = 0xA010 # Ambient color of the object/material +MAT_DIFFUSE = 0xA020 # This holds the color of the object/material +MAT_SPECULAR = 0xA030 # Specular color of the object/material +MAT_SHINESS = 0xA040 # Roughness of the object/material (percent) +MAT_SHIN2 = 0xA041 # Shininess of the object/material (percent) +MAT_SHIN3 = 0xA042 # Reflection of the object/material (percent) +MAT_TRANSPARENCY = 0xA050 # Transparency value of material (percent) +MAT_SELF_ILLUM = 0xA080 # Self Illumination value of material +MAT_SELF_ILPCT = 0xA084 # Self illumination strength (percent) +MAT_WIRE = 0xA085 # Only render's wireframe +MAT_SHADING = 0xA100 # Material shading method + +MAT_TEXTURE_MAP = 0xA200 # This is a header for a new texture map +MAT_SPECULAR_MAP = 0xA204 # This is a header for a new specular map +MAT_OPACITY_MAP = 0xA210 # This is a header for a new opacity map +MAT_REFLECTION_MAP = 0xA220 # This is a header for a new reflection map +MAT_BUMP_MAP = 0xA230 # This is a header for a new bump map +MAT_BUMP_PERCENT = 0xA252 # Normalmap strength (percent) +MAT_TEX2_MAP = 0xA33A # This is a header for a secondary texture +MAT_SHIN_MAP = 0xA33C # This is a header for a new roughness map +MAT_SELFI_MAP = 0xA33D # This is a header for a new emission map +MAT_MAP_FILEPATH = 0xA300 # This holds the file name of the texture + +MAT_MAP_TILING = 0xa351 # 2nd bit (from LSB) is mirror UV flag +MAT_MAP_USCALE = 0xA354 # U axis scaling +MAT_MAP_VSCALE = 0xA356 # V axis scaling +MAT_MAP_UOFFSET = 0xA358 # U axis offset +MAT_MAP_VOFFSET = 0xA35A # V axis offset +MAT_MAP_ANG = 0xA35C # UV rotation around the z-axis in rad +MAT_MAP_COL1 = 0xA360 # Map Color1 +MAT_MAP_COL2 = 0xA362 # Map Color2 +MAT_MAP_RCOL = 0xA364 # Red mapping +MAT_MAP_GCOL = 0xA366 # Green mapping +MAT_MAP_BCOL = 0xA368 # Blue mapping +MAT_FLOAT_COLOR = 0x0010 # color defined as 3 floats +MAT_24BIT_COLOR = 0x0011 # color defined as 3 bytes + +# >------ sub defines of OBJECT +OBJECT_MESH = 0x4100 # This lets us know that we are reading a new object +OBJECT_LIGHT = 0x4600 # This lets un know we are reading a light object +OBJECT_LIGHT_SPOT = 0x4610 # The light is a spotloght. +OBJECT_LIGHT_OFF = 0x4620 # The light off. +OBJECT_LIGHT_ATTENUATE = 0x4625 +OBJECT_LIGHT_RAYSHADE = 0x4627 +OBJECT_LIGHT_SHADOWED = 0x4630 +OBJECT_LIGHT_LOCAL_SHADOW = 0x4640 +OBJECT_LIGHT_LOCAL_SHADOW2 = 0x4641 +OBJECT_LIGHT_SEE_CONE = 0x4650 +OBJECT_LIGHT_SPOT_RECTANGULAR = 0x4651 +OBJECT_LIGHT_SPOT_OVERSHOOT = 0x4652 +OBJECT_LIGHT_SPOT_PROJECTOR = 0x4653 +OBJECT_LIGHT_EXCLUDE = 0x4654 +OBJECT_LIGHT_RANGE = 0x4655 +OBJECT_LIGHT_ROLL = 0x4656 +OBJECT_LIGHT_SPOT_ASPECT = 0x4657 +OBJECT_LIGHT_RAY_BIAS = 0x4658 +OBJECT_LIGHT_INNER_RANGE = 0x4659 +OBJECT_LIGHT_OUTER_RANGE = 0x465A +OBJECT_LIGHT_MULTIPLIER = 0x465B +OBJECT_LIGHT_AMBIENT_LIGHT = 0x4680 + +OBJECT_CAMERA = 0x4700 # This lets un know we are reading a camera object + +# >------ sub defines of CAMERA +OBJECT_CAM_RANGES = 0x4720 # The camera range values + +# >------ sub defines of OBJECT_MESH +OBJECT_VERTICES = 0x4110 # The objects vertices +OBJECT_VERTFLAGS = 0x4111 # The objects vertex flags +OBJECT_FACES = 0x4120 # The objects faces +OBJECT_MATERIAL = 0x4130 # This is found if the object has a material, either texture map or color +OBJECT_UV = 0x4140 # The UV texture coordinates +OBJECT_SMOOTH = 0x4150 # The Object smooth groups +OBJECT_TRANS_MATRIX = 0x4160 # The Object Matrix + +# >------ sub defines of EDITKEYFRAME +KFDATA_AMBIENT = 0xB001 +KFDATA_OBJECT = 0xB002 +KFDATA_CAMERA = 0xB003 +KFDATA_TARGET = 0xB004 +KFDATA_LIGHT = 0xB005 +KFDATA_L_TARGET = 0xB006 +KFDATA_SPOTLIGHT = 0xB007 +KFDATA_KFSEG = 0xB008 +# KFDATA_CURTIME = 0xB009 +# KFDATA_KFHDR = 0xB00A +# >------ sub defines of KEYFRAME_NODE +OBJECT_NODE_HDR = 0xB010 +OBJECT_INSTANCE_NAME = 0xB011 +# OBJECT_PRESCALE = 0xB012 +OBJECT_PIVOT = 0xB013 +# OBJECT_BOUNDBOX = 0xB014 +# MORPH_SMOOTH = 0xB015 +POS_TRACK_TAG = 0xB020 +ROT_TRACK_TAG = 0xB021 +SCL_TRACK_TAG = 0xB022 +FOV_TRACK_TAG = 0xB023 +ROLL_TRACK_TAG = 0xB024 +COL_TRACK_TAG = 0xB025 +# MORPH_TRACK_TAG = 0xB026 +# HOTSPOT_TRACK_TAG = 0xB027 +# FALLOFF_TRACK_TAG = 0xB028 +# HIDE_TRACK_TAG = 0xB029 +# OBJECT_NODE_ID = 0xB030 + +ROOT_OBJECT = 0xFFFF + +global scn +scn = None + +object_dictionary = {} +object_matrix = {} + + +class Chunk: + __slots__ = ( + "ID", + "length", + "bytes_read", + ) + # we don't read in the bytes_read, we compute that + binary_format = " 0: + bmesh.polygons[f].use_smooth = True + bmesh.use_auto_smooth = True + + if contextMatrix: + ob.matrix_local = contextMatrix + object_matrix[ob] = contextMatrix.copy() + + # a spare chunk + new_chunk = Chunk() + temp_chunk = Chunk() + + CreateBlenderObject = False + CreateLightObject = False + CreateCameraObject = False + + def read_float_color(temp_chunk): + temp_data = file.read(SZ_3FLOAT) + temp_chunk.bytes_read += SZ_3FLOAT + return [float(col) for col in struct.unpack('<3f', temp_data)] + + def read_float(temp_chunk): + temp_data = file.read(SZ_FLOAT) + temp_chunk.bytes_read += SZ_FLOAT + return struct.unpack(' 3: + print('\tNon-Fatal Error: Version greater than 3, may not load correctly: ', version) + + # is it an object info chunk? + elif new_chunk.ID == OBJECTINFO: + process_next_chunk(context, file, new_chunk, imported_objects, IMAGE_SEARCH, KEYFRAME) + + # 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, + ) + contextMesh_vertls = [] + contextMesh_facels = [] + contextMeshMaterials = [] + contextMesh_flag = None + contextMesh_smooth = None + contextMeshUV = None + # Reset matrix + contextMatrix = None + + CreateBlenderObject = True + contextObName, read_str_len = read_string(file) + new_chunk.bytes_read += read_str_len + + # is it a material chunk? + elif new_chunk.ID == MATERIAL: + contextMaterial = bpy.data.materials.new('Material') + contextWrapper = PrincipledBSDFWrapper(contextMaterial, is_readonly=False, use_nodes=False) + + elif new_chunk.ID == MAT_NAME: + material_name, read_str_len = read_string(file) + + # plus one for the null character that ended the string + new_chunk.bytes_read += read_str_len + contextMaterial.name = material_name.rstrip() # remove trailing whitespace + MATDICT[material_name] = contextMaterial + + elif new_chunk.ID == MAT_AMBIENT: + read_chunk(file, temp_chunk) + # only available color is emission color + if temp_chunk.ID == MAT_FLOAT_COLOR: + contextMaterial.line_color[:3] = read_float_color(temp_chunk) + elif temp_chunk.ID == MAT_24BIT_COLOR: + contextMaterial.line_color[:3] = read_byte_color(temp_chunk) + else: + skip_to_end(file, temp_chunk) + new_chunk.bytes_read += temp_chunk.bytes_read + + elif new_chunk.ID == MAT_DIFFUSE: + read_chunk(file, temp_chunk) + if temp_chunk.ID == MAT_FLOAT_COLOR: + contextMaterial.diffuse_color[:3] = read_float_color(temp_chunk) + elif temp_chunk.ID == MAT_24BIT_COLOR: + contextMaterial.diffuse_color[:3] = read_byte_color(temp_chunk) + else: + skip_to_end(file, temp_chunk) + new_chunk.bytes_read += temp_chunk.bytes_read + + elif new_chunk.ID == MAT_SPECULAR: + read_chunk(file, temp_chunk) + # Specular color is available + if temp_chunk.ID == MAT_FLOAT_COLOR: + contextMaterial.specular_color = read_float_color(temp_chunk) + elif temp_chunk.ID == MAT_24BIT_COLOR: + contextMaterial.specular_color = read_byte_color(temp_chunk) + else: + skip_to_end(file, temp_chunk) + new_chunk.bytes_read += temp_chunk.bytes_read + + elif new_chunk.ID == MAT_SHINESS: + read_chunk(file, temp_chunk) + if temp_chunk.ID == PERCENTAGE_SHORT: + temp_data = file.read(SZ_U_SHORT) + temp_chunk.bytes_read += SZ_U_SHORT + contextMaterial.roughness = 1 - (float(struct.unpack('= 2: + contextWrapper.use_nodes = True + contextWrapper.emission_color = contextMaterial.line_color[:3] + contextWrapper.emission_strength = contextMaterial.line_priority / 100 + contextWrapper.base_color = contextMaterial.diffuse_color[:3] + contextWrapper.specular = contextMaterial.specular_intensity + contextWrapper.roughness = contextMaterial.roughness + contextWrapper.metallic = contextMaterial.metallic + contextWrapper.alpha = contextMaterial.diffuse_color[3] + contextWrapper.use_nodes = False + if shading >= 3: + contextWrapper.use_nodes = True + + elif new_chunk.ID == MAT_TEXTURE_MAP: + read_texture(new_chunk, temp_chunk, "Diffuse", "COLOR") + + elif new_chunk.ID == MAT_SPECULAR_MAP: + read_texture(new_chunk, temp_chunk, "Specular", "SPECULARITY") + + elif new_chunk.ID == MAT_OPACITY_MAP: + contextMaterial.blend_method = 'BLEND' + read_texture(new_chunk, temp_chunk, "Opacity", "ALPHA") + + elif new_chunk.ID == MAT_REFLECTION_MAP: + read_texture(new_chunk, temp_chunk, "Reflect", "METALLIC") + + elif new_chunk.ID == MAT_BUMP_MAP: + read_texture(new_chunk, temp_chunk, "Bump", "NORMAL") + + elif new_chunk.ID == MAT_BUMP_PERCENT: + read_chunk(file, temp_chunk) + if temp_chunk.ID == PERCENTAGE_SHORT: + temp_data = file.read(SZ_U_SHORT) + temp_chunk.bytes_read += SZ_U_SHORT + contextWrapper.normalmap_strength = (float(struct.unpack(' value: + axis_min[axis] = value + if axis_max[axis] < value: + axis_max[axis] = value + + # Scale objects + max_axis = max(axis_max[0] - axis_min[0], + axis_max[1] - axis_min[1], + axis_max[2] - axis_min[2]) + scale = 1.0 + + while global_clamp_size < max_axis * scale: + scale = scale / 10.0 + + scale_mat = mathutils.Matrix.Scale(scale, 4) + + for obj in imported_objects: + if obj.parent is None: + obj.matrix_world = scale_mat @ obj.matrix_world + + # Select all new objects. + print(" done in %.4f sec." % (time.time() - time1)) + file.close() + + +def load(operator, + context, + filepath="", + constrain_size=0.0, + use_image_search=True, + read_keyframe=True, + use_apply_transform=True, + global_matrix=None, + ): + + load_3ds(filepath, + context, + IMPORT_CONSTRAIN_BOUNDS=constrain_size, + IMAGE_SEARCH=use_image_search, + KEYFRAME=read_keyframe, + APPLY_MATRIX=use_apply_transform, + global_matrix=global_matrix, + ) + + return {'FINISHED'} -- 2.30.2