From e1ba5a715619c967745dfc0bc668d831754987f3 Mon Sep 17 00:00:00 2001 From: Andres Stephens Date: Mon, 27 May 2024 14:06:33 -0500 Subject: [PATCH] #4272 Updated legacy addons to extensions equivalents with BFA changes Also removed the legacy addon from the addon, since they are now core. Added a note to the title of the addon too, into the manifest for now. --- .../autodesk_3ds_format/__init__.py | 4 +- .../autodesk_3ds_format/blender_manifest.toml | 0 .../autodesk_3ds_format/export_3ds.py | 0 .../autodesk_3ds_format/import_3ds.py | 0 .../Bforartists Modifications.txt | 13 - .../Default_Addons/io_scene_3ds/__init__.py | 353 --- .../Default_Addons/io_scene_3ds/export_3ds.py | 2068 ----------------- .../Default_Addons/io_scene_3ds/import_3ds.py | 1940 ---------------- .../import_autocad_dxf_format_dxf/__init__.py | 20 +- .../blender_manifest.toml | 11 +- .../Default_Extensions/navigation/__init__.py | 12 +- .../Errors/render_freestyle_svg.py | 775 ++++++ .../Errors/vdm_brush_baker/__init__.py | 349 +++ .../Errors/vdm_brush_baker/bakematerial.py | 73 + .../bfa_default_addons/Read Me.txt | 14 + .../bfa_default_addons/__init__.py | 13 +- .../addons_core/bl_pkg/bl_extension_ops.py | 4 +- .../__init__.py} | 0 .../blender_manifest.toml | 12 + scripts/addons_core/io_mesh_stl/__init__.py | 442 ---- .../addons_core/io_mesh_stl/blender_utils.py | 96 - scripts/addons_core/io_mesh_stl/stl_utils.py | 279 --- .../stl_format_legacy/__init__.py | 4 +- .../stl_format_legacy/blender_manifest.toml | 2 +- .../stl_format_legacy/blender_utils.py | 0 .../stl_format_legacy/stl_utils.py | 0 .../__init__.py | 0 .../viewport_pie_menus/blender_manifest.toml | 12 + .../pie_align_menu.py | 0 .../pie_animation_menu.py | 0 .../pie_apply_transform_menu.py | 0 .../pie_defaults_menu.py | 0 .../pie_delete_menu.py | 0 .../pie_editor_switch_menu.py | 0 .../pie_manipulator_menu.py | 0 .../pie_modes_menu.py | 0 .../pie_origin.py | 0 .../pie_proportional_menu.py | 0 .../pie_save_open_menu.py | 0 .../pie_sculpt_menu.py | 0 .../pie_select_menu.py | 0 .../pie_shading_menu.py | 0 .../pie_views_numpad_menu.py | 0 43 files changed, 1265 insertions(+), 5231 deletions(-) rename scripts/addons_core/{bfa_default_addons/Default_Extensions => }/autodesk_3ds_format/__init__.py (98%) rename scripts/addons_core/{bfa_default_addons/Default_Extensions => }/autodesk_3ds_format/blender_manifest.toml (100%) rename scripts/addons_core/{bfa_default_addons/Default_Extensions => }/autodesk_3ds_format/export_3ds.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Extensions => }/autodesk_3ds_format/import_3ds.py (100%) delete mode 100644 scripts/addons_core/bfa_default_addons/Default_Addons/Bforartists Modifications.txt delete mode 100644 scripts/addons_core/bfa_default_addons/Default_Addons/io_scene_3ds/__init__.py delete mode 100644 scripts/addons_core/bfa_default_addons/Default_Addons/io_scene_3ds/export_3ds.py delete mode 100644 scripts/addons_core/bfa_default_addons/Default_Addons/io_scene_3ds/import_3ds.py create mode 100644 scripts/addons_core/bfa_default_addons/Errors/render_freestyle_svg.py create mode 100644 scripts/addons_core/bfa_default_addons/Errors/vdm_brush_baker/__init__.py create mode 100644 scripts/addons_core/bfa_default_addons/Errors/vdm_brush_baker/bakematerial.py create mode 100644 scripts/addons_core/bfa_default_addons/Read Me.txt rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_copy_attributes.py => copy_attributes_menu/__init__.py} (100%) create mode 100644 scripts/addons_core/copy_attributes_menu/blender_manifest.toml delete mode 100644 scripts/addons_core/io_mesh_stl/__init__.py delete mode 100644 scripts/addons_core/io_mesh_stl/blender_utils.py delete mode 100644 scripts/addons_core/io_mesh_stl/stl_utils.py rename scripts/addons_core/{bfa_default_addons/Default_Extensions => }/stl_format_legacy/__init__.py (99%) rename scripts/addons_core/{bfa_default_addons/Default_Extensions => }/stl_format_legacy/blender_manifest.toml (85%) rename scripts/addons_core/{bfa_default_addons/Default_Extensions => }/stl_format_legacy/blender_utils.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Extensions => }/stl_format_legacy/stl_utils.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_pie_menus => viewport_pie_menus}/__init__.py (100%) create mode 100644 scripts/addons_core/viewport_pie_menus/blender_manifest.toml rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_pie_menus => viewport_pie_menus}/pie_align_menu.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_pie_menus => viewport_pie_menus}/pie_animation_menu.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_pie_menus => viewport_pie_menus}/pie_apply_transform_menu.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_pie_menus => viewport_pie_menus}/pie_defaults_menu.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_pie_menus => viewport_pie_menus}/pie_delete_menu.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_pie_menus => viewport_pie_menus}/pie_editor_switch_menu.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_pie_menus => viewport_pie_menus}/pie_manipulator_menu.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_pie_menus => viewport_pie_menus}/pie_modes_menu.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_pie_menus => viewport_pie_menus}/pie_origin.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_pie_menus => viewport_pie_menus}/pie_proportional_menu.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_pie_menus => viewport_pie_menus}/pie_save_open_menu.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_pie_menus => viewport_pie_menus}/pie_sculpt_menu.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_pie_menus => viewport_pie_menus}/pie_select_menu.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_pie_menus => viewport_pie_menus}/pie_shading_menu.py (100%) rename scripts/addons_core/{bfa_default_addons/Default_Addons/space_view3d_pie_menus => viewport_pie_menus}/pie_views_numpad_menu.py (100%) diff --git a/scripts/addons_core/bfa_default_addons/Default_Extensions/autodesk_3ds_format/__init__.py b/scripts/addons_core/autodesk_3ds_format/__init__.py similarity index 98% rename from scripts/addons_core/bfa_default_addons/Default_Extensions/autodesk_3ds_format/__init__.py rename to scripts/addons_core/autodesk_3ds_format/__init__.py index a507f479838b..346a521590ae 100644 --- a/scripts/addons_core/bfa_default_addons/Default_Extensions/autodesk_3ds_format/__init__.py +++ b/scripts/addons_core/autodesk_3ds_format/__init__.py @@ -304,11 +304,11 @@ def poll_drop(cls, context): # Add to a menu def menu_func_export(self, context): - self.layout.operator(Export3DS.bl_idname, text="Autodesk 3DS (.3ds)") + self.layout.operator(Export3DS.bl_idname, text="3D Studio (.3ds)", icon='SAVE_3DS') #bfa - added icon def menu_func_import(self, context): - self.layout.operator(Import3DS.bl_idname, text="Autodesk 3DS (.3ds)") + self.layout.operator(Import3DS.bl_idname, text="3D Studio (.3ds)", icon='LOAD_3DS') #bfa - added icon def register(): diff --git a/scripts/addons_core/bfa_default_addons/Default_Extensions/autodesk_3ds_format/blender_manifest.toml b/scripts/addons_core/autodesk_3ds_format/blender_manifest.toml similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Extensions/autodesk_3ds_format/blender_manifest.toml rename to scripts/addons_core/autodesk_3ds_format/blender_manifest.toml diff --git a/scripts/addons_core/bfa_default_addons/Default_Extensions/autodesk_3ds_format/export_3ds.py b/scripts/addons_core/autodesk_3ds_format/export_3ds.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Extensions/autodesk_3ds_format/export_3ds.py rename to scripts/addons_core/autodesk_3ds_format/export_3ds.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Extensions/autodesk_3ds_format/import_3ds.py b/scripts/addons_core/autodesk_3ds_format/import_3ds.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Extensions/autodesk_3ds_format/import_3ds.py rename to scripts/addons_core/autodesk_3ds_format/import_3ds.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/Bforartists Modifications.txt b/scripts/addons_core/bfa_default_addons/Default_Addons/Bforartists Modifications.txt deleted file mode 100644 index 1111c5be8390..000000000000 --- a/scripts/addons_core/bfa_default_addons/Default_Addons/Bforartists Modifications.txt +++ /dev/null @@ -1,13 +0,0 @@ -achm_room_maker.py -edit_dimensions.py -io_mesh_dxf\ -io_mesh_stl\ -io_mesh_uv_layout\ -io_scene_3d\ -meshinset\ -meshedgetools.py -minilightlib\ -space_view3d_3d_navigation.py -space_view3d_copy_attributes.py -space_view3d_pie_menus\ - diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/io_scene_3ds/__init__.py b/scripts/addons_core/bfa_default_addons/Default_Addons/io_scene_3ds/__init__.py deleted file mode 100644 index 18f62979ea0d..000000000000 --- a/scripts/addons_core/bfa_default_addons/Default_Addons/io_scene_3ds/__init__.py +++ /dev/null @@ -1,353 +0,0 @@ -# SPDX-FileCopyrightText: 2011-2023 Blender Foundation -# -# SPDX-License-Identifier: GPL-2.0-or-later - -from bpy_extras.io_utils import ( - ImportHelper, - ExportHelper, - orientation_helper, - axis_conversion, - poll_file_object_drop, -) -from bpy.props import ( - BoolProperty, - EnumProperty, - FloatProperty, - StringProperty, - CollectionProperty, -) -import bpy -bl_info = { - "name": "Autodesk 3DS format", - "author": "Bob Holcomb, Campbell Barton, Sebastian Schrand", - "version": (2, 5, 1), - "blender": (4, 2, 0), - "location": "File > Import-Export", - "description": "3DS Import/Export meshes, UVs, materials, textures, " - "cameras, lamps & animation", - "warning": "Images must be in file folder, " - "filenames are limited to DOS 8.3 format", - "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/scene_3ds.html", - "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.max3ds" - bl_label = 'Import 3DS' - bl_options = {'PRESET', 'UNDO'} - - filename_ext = ".3ds" - filter_glob: StringProperty(default="*.3ds", options={'HIDDEN'}) - files: CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'}) - directory: StringProperty(subtype='DIR_PATH') - - constrain_size: FloatProperty( - name="Constrain Size", - 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_scene_unit: BoolProperty( - name="Scene Units", - description="Convert to scene unit length settings", - default=False, - ) - use_center_pivot: BoolProperty( - name="Pivot Origin", - description="Move all geometry to pivot origin", - default=False, - ) - use_image_search: BoolProperty( - name="Image Search", - description="Search subdirectories for any associated images " - "(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_AXIS', 0x10), - ), - description="Object types to import", - default={'WORLD', 'MESH', 'LIGHT', 'CAMERA', 'EMPTY'}, - ) - use_apply_transform: BoolProperty( - name="Apply Transform", - description="Workaround for object transformations " - "importing incorrectly", - default=True, - ) - use_keyframes: BoolProperty( - name="Animation", - description="Read the keyframe data", - default=True, - ) - use_world_matrix: BoolProperty( - name="World Space", - description="Transform to matrix world", - default=False, - ) - use_collection: BoolProperty( - name="Collection", - description="Create a new collection", - default=False, - ) - use_cursor: BoolProperty( - name="Cursor Origin", - description="Read the 3D cursor location", - default=False, - ) - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False - - import_include(layout, self) - import_transform(layout, self) - - 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) - - def invoke(self, context, event): - return self.invoke_popup(context) - - -def import_include(layout, operator): - header, body = layout.panel("MAX3DS_import_include", default_closed=False) - header.label(text="Include") - if body: - line = body.row(align=True) - line.prop(operator, "use_image_search") - line.label(text="", icon='OUTLINER_OB_IMAGE' if operator.use_image_search else 'IMAGE_DATA') - body.column().prop(operator, "object_filter") - line = body.row(align=True) - line.prop(operator, "use_keyframes") - line.label(text="", icon='ANIM' if operator.use_keyframes else 'DECORATE_DRIVER') - line = body.row(align=True) - line.prop(operator, "use_collection") - line.label(text="", icon='OUTLINER_COLLECTION' if operator.use_collection else 'GROUP') - line = body.row(align=True) - line.prop(operator, "use_cursor") - line.label(text="", icon='PIVOT_CURSOR' if operator.use_cursor else 'CURSOR') - - -def import_transform(layout, operator): - header, body = layout.panel("MAX3DS_import_transform", default_closed=False) - header.label(text="Transform") - if body: - body.prop(operator, "constrain_size") - line = body.row(align=True) - line.prop(operator, "use_scene_unit") - line.label(text="", icon='EMPTY_ARROWS' if operator.use_scene_unit else 'EMPTY_DATA') - line = body.row(align=True) - line.prop(operator, "use_center_pivot") - line.label(text="", icon='OVERLAY' if operator.use_center_pivot else 'PIVOT_ACTIVE') - line = body.row(align=True) - line.prop(operator, "use_apply_transform") - line.label(text="", icon='MESH_CUBE' if operator.use_apply_transform else 'MOD_SOLIDIFY') - line = body.row(align=True) - line.prop(operator, "use_world_matrix") - line.label(text="", icon='WORLD' if operator.use_world_matrix else 'META_BALL') - body.prop(operator, "axis_forward") - body.prop(operator, "axis_up") - - -@orientation_helper(axis_forward='Y', axis_up='Z') -class Export3DS(bpy.types.Operator, ExportHelper): - """Export to 3DS file format (.3ds)""" - bl_idname = "export_scene.max3ds" - bl_label = 'Export 3DS' - bl_options = {'PRESET', 'UNDO'} - - filename_ext = ".3ds" - filter_glob: StringProperty(default="*.3ds", options={'HIDDEN'}) - - collection: StringProperty( - name="Source Collection", - description="Export objects from this collection", - default="", - ) - scale_factor: FloatProperty( - name="Scale Factor", - description="Master scale factor for all objects", - min=0.0, max=100000.0, - soft_min=0.0, soft_max=100000.0, - default=1.0, - ) - use_scene_unit: BoolProperty( - name="Scene Units", - description="Take the scene unit length settings into account", - default=False, - ) - use_selection: BoolProperty( - name="Selection", - description="Export selected objects only", - default=False, - ) - 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_AXIS',0x10), - ('OTHER', "Other".rjust(12), "", 'MATSHADERBALL', 0x20), - ), - description="Object types to export", - default={'WORLD', 'MESH', 'LIGHT', 'CAMERA', 'EMPTY', 'OTHER'}, - ) - use_apply_transform: bpy.props.BoolProperty( - name="Apply Transform", - description="Apply matrix transform before export", - default=True, - ) - use_keyframes: BoolProperty( - name="Animation", - description="Write the keyframe data", - default=True, - ) - use_hierarchy: BoolProperty( - name="Hierarchy", - description="Export hierarchy chunks", - default=False, - ) - use_collection: BoolProperty( - name="Collection", - description="Export active collection only", - default=False, - ) - use_cursor: BoolProperty( - name="Cursor Origin", - description="Save the 3D cursor location", - default=False, - ) - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False - - browser = context.space_data.type == 'FILE_BROWSER' - - export_include(layout, self, browser) - export_transform(layout, self) - - 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) - - -def export_include(layout, operator, browser): - header, body = layout.panel("MAX3DS_export_include", default_closed=False) - header.label(text="Include") - if body: - if browser: - line = body.row(align=True) - line.prop(operator, "use_selection") - line.label(text="", icon='RESTRICT_SELECT_OFF' if operator.use_selection else 'RESTRICT_SELECT_ON') - body.column().prop(operator, "object_filter") - line = body.row(align=True) - line.prop(operator, "use_keyframes") - line.label(text="", icon='ANIM' if operator.use_keyframes else 'DECORATE_DRIVER') - line = body.row(align=True) - line.prop(operator, "use_hierarchy") - line.label(text="", icon='OUTLINER' if operator.use_hierarchy else 'CON_CHILDOF') - if browser: - line = body.row(align=True) - line.prop(operator, "use_collection") - line.label(text="", icon='OUTLINER_COLLECTION' if operator.use_collection else 'GROUP') - line = body.row(align=True) - line.prop(operator, "use_cursor") - line.label(text="", icon='PIVOT_CURSOR' if operator.use_cursor else 'CURSOR') - - -def export_transform(layout, operator): - header, body = layout.panel("MAX3DS_export_transform", default_closed=False) - header.label(text="Transform") - if body: - body.prop(operator, "scale_factor") - line = body.row(align=True) - line.prop(operator, "use_scene_unit") - line.label(text="", icon='EMPTY_ARROWS' if operator.use_scene_unit else 'EMPTY_DATA') - line = body.row(align=True) - line.prop(operator, "use_apply_transform") - line.label(text="", icon='MESH_CUBE' if operator.use_apply_transform else 'MOD_SOLIDIFY') - body.prop(operator, "axis_forward") - body.prop(operator, "axis_up") - - -class IO_FH_3ds(bpy.types.FileHandler): - bl_idname = "IO_FH_3ds" - bl_label = "Autodesk 3DS" - bl_import_operator = "import_scene.max3ds" - bl_export_operator = "export_scene.max3ds" - bl_file_extensions = ".3ds;.3DS" - - @classmethod - def poll_drop(cls, context): - return poll_file_object_drop(context) - - -# Add to a menu -def menu_func_export(self, context): - self.layout.operator(Export3DS.bl_idname, text="3D Studio (.3ds)", icon='SAVE_3DS') #bfa - added icon - - -def menu_func_import(self, context): - self.layout.operator(Import3DS.bl_idname, text="3D Studio (.3ds)", icon='LOAD_3DS') #bfa - added icon - - -def register(): - bpy.utils.register_class(Import3DS) - bpy.utils.register_class(Export3DS) - bpy.utils.register_class(IO_FH_3ds) - 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.utils.unregister_class(IO_FH_3ds) - bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) - bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) - - -if __name__ == "__main__": - register() diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/io_scene_3ds/export_3ds.py b/scripts/addons_core/bfa_default_addons/Default_Addons/io_scene_3ds/export_3ds.py deleted file mode 100644 index 644c848c96c5..000000000000 --- a/scripts/addons_core/bfa_default_addons/Default_Addons/io_scene_3ds/export_3ds.py +++ /dev/null @@ -1,2068 +0,0 @@ -# SPDX-FileCopyrightText: 2005 Bob Holcomb -# -# SPDX-License-Identifier: GPL-2.0-or-later - -""" -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 time -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 -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 -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 -O_CONSTS = 0x1500 # The origin of the 3D cursor -AMBIENTLIGHT = 0x2100 # The color of the ambient light -FOG = 0x2200 # The fog atmosphere settings -USE_FOG = 0x2201 # The fog atmosphere flag -DISTANCE_CUE = 0x2300 # The distance cue atmosphere settings -USE_DISTANCE_CUE = 0x2301 # The distance cue atmosphere flag -LAYER_FOG = 0x2302 # The fog layer atmosphere settings -USE_LAYER_FOG = 0x2303 # The fog layer atmosphere flag -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) -MATXPFALL = 0xA052 # Transparency falloff ratio (percent) -MATREFBLUR = 0xA053 # Reflection blurring ratio (percent) -MATSELFILLUM = 0xA080 # # Material self illumination flag -MATSELFILPCT = 0xA084 # Self illumination strength (percent) -MATWIRE = 0xA085 # Material wireframe rendered flag -MATFACEMAP = 0xA088 # Face mapped textures flag -MATPHONGSOFT = 0xA08C # Phong soften material flag -MATWIREABS = 0xA08E # Wire size in units flag -MATWIRESIZE = 0xA087 # Rendered wire size in pixels -MATSHADING = 0xA100 # Material shading method - -# >------ sub defines of MAT_MAP -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 -MAT_MAP_FILE = 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 Color1 -RGB1 = 0x0011 # RGB int Color1 -RGBI = 0x0012 # RGB int Color2 -RGBF = 0x0013 # RGB float Color2 -PCT = 0x0030 # Percent chunk -PCTF = 0x0031 # Percent float -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 -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 -LIGHT_INNER_RANGE = 0x4659 # Light inner range value -LIGHT_OUTER_RANGE = 0x465A # Light outer range value -LIGHT_ATTENUATE = 0x4625 # Light attenuation flag -LIGHT_SPOTLIGHT = 0x4610 # The target of a spotlight -LIGHT_SPOT_ROLL = 0x4656 # Light spot roll angle -LIGHT_SPOT_SHADOWED = 0x4630 # Light spot shadow flag -LIGHT_SPOT_LSHADOW = 0x4641 # Light spot shadow parameters -LIGHT_SPOT_SEE_CONE = 0x4650 # Light spot show cone flag -LIGHT_SPOT_RECTANGLE = 0x4651 # Light spot rectangle flag -LIGHT_SPOT_OVERSHOOT = 0x4652 # Light spot overshoot flag -LIGHT_SPOT_PROJECTOR = 0x4653 # Light spot projection bitmap -LIGHT_SPOT_ASPECT = 0x4657 # Light spot aspect ratio - -# >------ 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 -AMBIENT_NODE_TAG = 0xB001 # Ambient node tag -OBJECT_NODE_TAG = 0xB002 # Object tree tag -CAMERA_NODE_TAG = 0xB003 # Camera object tag -TARGET_NODE_TAG = 0xB004 # Camera target tag -LIGHT_NODE_TAG = 0xB005 # Light object tag -LTARGET_NODE_TAG = 0xB006 # Light target tag -SPOT_NODE_TAG = 0xB007 # Spotlight tag -KFDATA_KFSEG = 0xB008 # Frame start & end -KFDATA_KFCURTIME = 0xB009 # Frame current -KFDATA_KFHDR = 0xB00A # Keyframe header - -# >------ sub defines of OBJECT_NODE_TAG -OBJECT_NODE_ID = 0xB030 # Object hierachy ID -OBJECT_NODE_HDR = 0xB010 # Hierachy tree header -OBJECT_INSTANCE_NAME = 0xB011 # Object instance name -OBJECT_PARENT_NAME = 0x80F0 # Object parent name -OBJECT_PIVOT = 0xB013 # Object pivot position -OBJECT_BOUNDBOX = 0xB014 # Object boundbox -OBJECT_MORPH_SMOOTH = 0xB015 # Object smooth angle -POS_TRACK_TAG = 0xB020 # Position transform tag -ROT_TRACK_TAG = 0xB021 # Rotation transform tag -SCL_TRACK_TAG = 0xB022 # Scale transform tag -FOV_TRACK_TAG = 0xB023 # Field of view tag -ROLL_TRACK_TAG = 0xB024 # Roll transform tag -COL_TRACK_TAG = 0xB025 # Color transform tag -HOTSPOT_TRACK_TAG = 0xB027 # Hotspot transform tag -FALLOFF_TRACK_TAG = 0xB028 # Falloff transform tag -ROOT_OBJECT = 0xFFFF # Root object - -EMPTYS = {'EMPTY'} -DUMMYS = {'ARMATURE', 'LATTICE', 'SPEAKER', 'VOLUME'} -OTHERS = {'CURVE', 'SURFACE', 'FONT', 'META'} - -# 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")[:16] - 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.""" - __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(' adjacent else -0.0 - diagonal = math.sqrt(pow(posi.x ,2) + pow(posi.y ,2)) - target_x = math.copysign(posi.x + (posi.y * math.tan(pan)), pan) - target_y = math.copysign(posi.y + (posi.x * math.tan(adjacent - pan)), turn) - target_z = math.copysign(posi.z + diagonal * math.tan(adjacent - tilt), lean) - - return target_x, target_y, target_z - - -################# -# 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(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)) - kfseg.add_variable("stop", _3ds_uint(stop)) - - kfcurtime = _3ds_chunk(KFDATA_KFCURTIME) - kfcurtime.add_variable("curtime", _3ds_uint(curtime)) - - kfdata.add_subchunk(kfhdr) - kfdata.add_subchunk(kfseg) - 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, fov, hotspot or falloff 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 - fcurves.update() - 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 += 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): - pos_track = [fc for fc in fcurves if fc is not None and fc.data_path == 'location'] - pos_x = next((tc.evaluate(frame) for tc in pos_track if tc.array_index == 0), ob_pos.x) - pos_y = next((tc.evaluate(frame) for tc in pos_track if tc.array_index == 1), ob_pos.y) - pos_z = next((tc.evaluate(frame) for tc in pos_track if tc.array_index == 2), ob_pos.z) - pos = ob_size @ mathutils.Vector((pos_x, pos_y, pos_z)) - 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((pos.x, pos.y, pos.z))) - - elif ID == ROT_TRACK_TAG: # Rotation - for i, frame in enumerate(kframes): - rot_track = [fc for fc in fcurves if fc is not None and fc.data_path == 'rotation_euler'] - rot_x = next((tc.evaluate(frame) for tc in rot_track if tc.array_index == 0), ob_rot.x) - rot_y = next((tc.evaluate(frame) for tc in rot_track if tc.array_index == 1), ob_rot.y) - rot_z = next((tc.evaluate(frame) for tc in rot_track if tc.array_index == 2), ob_rot.z) - quat = mathutils.Euler((rot_x, rot_y, rot_z)).to_quaternion().inverted() - 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((quat.angle, quat.axis.x, quat.axis.y, quat.axis.z))) - - elif ID == SCL_TRACK_TAG: # Scale - for i, frame in enumerate(kframes): - scale_track = [fc for fc in fcurves if fc is not None and fc.data_path == 'scale'] - size_x = next((tc.evaluate(frame) for tc in scale_track if tc.array_index == 0), ob_size.x) - size_y = next((tc.evaluate(frame) for tc in scale_track if tc.array_index == 1), ob_size.y) - size_z = next((tc.evaluate(frame) for tc in scale_track if tc.array_index == 2), ob_size.z) - 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_x, size_y, size_z))) - - elif ID == ROLL_TRACK_TAG: # Roll - for i, frame in enumerate(kframes): - roll_track = [fc for fc in fcurves if fc is not None and fc.data_path == 'rotation_euler'] - roll = next((tc.evaluate(frame) for tc in roll_track if tc.array_index == 1), ob_rot.y) - 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), 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 - fcurves.update() - 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 += 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 = 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 = next((fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'lens'), ob.data.lens) - fov = 2 * math.atan(ob.data.sensor_width / (2 * lens)) - 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 - for i, frame in enumerate(kframes): - beamsize = next((fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'spot_size'), ob.data.spot_size) - blend = next((fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'spot_blend'), ob.data.spot_blend) - hot_spot = math.degrees(beamsize) - (blend * math.floor(math.degrees(beamsize))) - 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 = next((fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == 'spot_size'), 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), 4))) - - 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) - quat = ob_rot.to_quaternion().inverted() - track_chunk.add_variable("rotation", _3ds_point_4d((quat.angle, quat.axis.x, quat.axis.y, quat.axis.z))) - - 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_rot.y), 4))) - - elif ID == COL_TRACK_TAG: # Color values - track_chunk.add_variable("color", _3ds_float_color(ob.data.color[:3])) - - 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_object_node(ob, translation, rotation, scale, name_id): - """Make a node chunk for a Blender object. Takes Blender object as parameter. - Blender Empty objects are converted to dummy nodes.""" - - 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) - - # Chunk for the object ID from name_id dictionary: - obj_id_chunk = _3ds_chunk(OBJECT_NODE_ID) - obj_id_chunk.add_variable("node_id", _3ds_ushort(name_id[name])) - obj_node.add_subchunk(obj_id_chunk) - - # Object node header with object name - obj_node_header_chunk = _3ds_chunk(OBJECT_NODE_HDR) - parent = ob.parent - - if ob.type in EMPTYS: # 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))) - 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 'Smooth by Angle' in ob.modifiers: - 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_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_id dictionary: - obj_node_header_chunk.add_variable("parent", _3ds_ushort(name_id[parent.name])) - ''' - - # Add subchunk for node header - obj_node.add_subchunk(obj_node_header_chunk) - - # Alternatively use PARENT_NAME chunk for hierachy - if parent is not None and (parent.name in name_id): - obj_parent_name_chunk = _3ds_chunk(OBJECT_PARENT_NAME) - obj_parent_name_chunk.add_variable("parent", _3ds_string(sane_name(parent.name))) - obj_node.add_subchunk(obj_parent_name_chunk) - - # Empty objects need to have an extra chunk for the instance name - if ob.type in EMPTYS: # 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))) - obj_node.add_subchunk(obj_instance_name_chunk) - - if ob.type in {'MESH'} or EMPTYS: # 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) - - # 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 smooth modifier is used - if ob.type == 'MESH' and 'Smooth by Angle' in ob.modifiers: - obj_morph_smooth = _3ds_chunk(OBJECT_MORPH_SMOOTH) - obj_morph_smooth.add_variable("angle", _3ds_float(round(ob.modifiers['Smooth by Angle']['Input_1'], 6))) - obj_node.add_subchunk(obj_morph_smooth) - - # Add track chunks for position, rotation, size - ob_scale = scale[name] # and collect masterscale - if parent is None or (parent.name not in name_id): - ob_pos = translation[name] - ob_rot = rotation[name] - ob_size = ob.scale - - else: # Calculate child position and rotation of the object center, no scale applied - ob_pos = translation[name] - translation[parent.name] - ob_rot = rotation[name].to_quaternion().cross(rotation[parent.name].to_quaternion().copy().inverted()).to_euler() - ob_size = mathutils.Vector((1.0, 1.0, 1.0)) - - obj_node.add_subchunk(make_track_chunk(POS_TRACK_TAG, ob, ob_pos, ob_rot, ob_scale)) - - if ob.type in {'MESH'} or EMPTYS: - 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, name_id): - """Make a target chunk for light and camera objects.""" - - name = ob.name - name_id["ø " + name] = len(name_id) - 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) - - # Chunk for the object ID from name_id dictionary: - tar_id_chunk = _3ds_chunk(OBJECT_NODE_ID) - tar_id_chunk.add_variable("node_id", _3ds_ushort(name_id[name])) - tar_node.add_subchunk(tar_id_chunk) - - # 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] - ob_scale = scale[name] - target_pos = calc_target(ob_pos, ob_rot.x, ob_rot.z) - - # 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 - fcurves.update() - 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 += 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): - loc_target = [fc for fc in fcurves if fc is not None and fc.data_path == 'location'] - loc_x = next((tc.evaluate(frame) for tc in loc_target if tc.array_index == 0), ob_pos.x) - loc_y = next((tc.evaluate(frame) for tc in loc_target if tc.array_index == 1), ob_pos.y) - loc_z = next((tc.evaluate(frame) for tc in loc_target if tc.array_index == 2), ob_pos.z) - rot_target = [fc for fc in fcurves if fc is not None and fc.data_path == 'rotation_euler'] - rot_x = next((tc.evaluate(frame) for tc in rot_target if tc.array_index == 0), ob_rot.x) - rot_z = next((tc.evaluate(frame) for tc in rot_target if tc.array_index == 2), ob_rot.z) - target_distance = ob_scale @ mathutils.Vector((loc_x, loc_y, loc_z)) - target_pos = calc_target(target_distance, rot_x, rot_z) - 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_pos)) - - 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_pos)) - - tar_node.add_subchunk(track_chunk) - - return tar_node - - -def make_ambient_node(world): - """Make an ambient node for the world color, if the color is animated.""" - - amb_color = world.color[:3] - amb_node = _3ds_chunk(AMBIENT_NODE_TAG) - track_chunk = _3ds_chunk(COL_TRACK_TAG) - - # Chunk for the ambient ID is ROOT_OBJECT - amb_id_chunk = _3ds_chunk(OBJECT_NODE_ID) - amb_id_chunk.add_variable("node_id", _3ds_ushort(ROOT_OBJECT)) - amb_node.add_subchunk(amb_id_chunk) - - # 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.use_nodes and world.node_tree.animation_data.action: - ambioutput = 'EMISSION' ,'MIX_SHADER', 'WORLD_OUTPUT' - action = world.node_tree.animation_data.action - links = world.node_tree.links - ambilinks = [lk for lk in links if lk.from_node.type in {'EMISSION', 'RGB'} and lk.to_node.type in ambioutput] - if ambilinks and action.fcurves: - fcurves = action.fcurves - fcurves.update() - emission = next((lk.from_socket.node for lk in ambilinks if lk.to_node.type in ambioutput), False) - ambinode = next((lk.from_socket.node for lk in ambilinks if lk.to_node.type == 'EMISSION'), emission) - kframes = [kf.co[0] for kf in [fc for fc in fcurves if fc is not None][0].keyframe_points] - ambipath = ('nodes[\"RGB\"].outputs[0].default_value' if ambinode and ambinode.type == 'RGB' else - 'nodes[\"Emission\"].inputs[0].default_value') - 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 == ambipath] - if not ambient: - ambient = amb_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[:3])) - - elif world.animation_data.action: - action = world.animation_data.action - if action.fcurves: - fcurves = action.fcurves - fcurves.update() - 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 += 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 = amb_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="", collection="", scale_factor=1.0, use_scene_unit=False, - use_selection=False, object_filter=None, use_apply_transform=True, use_keyframes=True, - use_hierarchy=False, use_collection=False, global_matrix=None, use_cursor=False): - """Save the Blender scene to a 3ds file.""" - - # Time the export - duration = time.time() - context.window.cursor_set('WAIT') - - scene = context.scene - layer = context.view_layer - depsgraph = context.evaluated_depsgraph_get() - items = scene.objects - world = scene.world - - unit_measure = 1.0 - if use_scene_unit: - unit_length = scene.unit_settings.length_unit - if unit_length == 'MILES': - unit_measure = 0.000621371 - elif unit_length == 'KILOMETERS': - unit_measure = 0.001 - elif unit_length == 'FEET': - unit_measure = 3.280839895 - elif unit_length == 'INCHES': - unit_measure = 39.37007874 - elif unit_length == 'CENTIMETERS': - unit_measure = 100 - elif unit_length == 'MILLIMETERS': - unit_measure = 1000 - elif unit_length == 'THOU': - unit_measure = 39370.07874 - elif unit_length == 'MICROMETERS': - unit_measure = 1000000 - - mtx_scale = mathutils.Matrix.Scale((scale_factor * unit_measure),4) - - if use_collection: - items = layer.active_layer_collection.collection.all_objects - elif collection: - item_collection = bpy.data.collections.get(collection) - if item_collection: - items = item_collection.all_objects - - if global_matrix is None: - global_matrix = mathutils.Matrix() - - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='OBJECT') - - # Initialize the main chunk (primary) - primary = _3ds_chunk(PRIMARY) - - # 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 - object_info = _3ds_chunk(OBJECTINFO) - mesh_version = _3ds_chunk(MESHVERSION) - mesh_version.add_variable("mesh", _3ds_uint(3)) - object_info.add_subchunk(mesh_version) - - # Init main keyframe data chunk - if use_keyframes: - revision = 0x0005 - stop = scene.frame_end - start = scene.frame_start - curtime = scene.frame_current - kfdata = make_kfdata(revision, start, stop, curtime) - - # Make a list of all materials used in the selected meshes (use dictionary, each material is added once) - materialDict = {} - mesh_objects = [] - free_objects = [] - - if object_filter is None: - object_filter = {'WORLD', 'MESH', 'LIGHT', 'CAMERA', 'EMPTY', 'OTHER'} - - if 'OTHER' in object_filter: - object_filter.remove('OTHER') - object_filter.update(DUMMYS) - object_filter.update(OTHERS) - EMPTYS.update(DUMMYS) - - if use_selection: - objects = [ob for ob in items 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 items if ob.type in object_filter and ob.visible_get(view_layer=layer)] - - empty_objects = [ob for ob in objects if ob.type in EMPTYS] - light_objects = [ob for ob in objects if ob.type == 'LIGHT'] - camera_objects = [ob for ob in objects if ob.type == 'CAMERA'] - - for ob in objects: - # Get derived objects - derived_dict = bpy_extras.io_utils.create_derived_objects(depsgraph, [ob]) - derived = derived_dict.get(ob) - if derived is None: - continue - - for ob_derived, mtx in derived: - if ob.type not in {'MESH', 'CURVE', 'SURFACE', 'FONT', 'META'}: - continue - - if ob_derived.type in OTHERS: - item = ob_derived.evaluated_get(depsgraph) - data = bpy.data.meshes.new_from_object(item, preserve_all_data_layers=True, depsgraph=depsgraph) - free_objects.append(data) - else: - try: - data = ob_derived.to_mesh() - except: - data = None - - if data: - matrix = mtx @ global_matrix if use_apply_transform else global_matrix - data.transform(matrix) - data.transform(mtx_scale) - mesh_objects.append((ob_derived, data, matrix)) - ma_ls = data.materials - ma_ls_len = len(ma_ls) - - # Get material/image tuples - if data.uv_layers: - if not ma_ls: - ma = ma_name = None - - for f, uf in zip(data.polygons, data.uv_layers.active.data): - if ma_ls: - ma_index = f.material_index - if ma_index >= 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 - - - # 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])) - - # Add MASTERSCALE element - mscale = _3ds_chunk(MASTERSCALE) - mscale.add_variable("scale", _3ds_float(1.0)) - object_info.add_subchunk(mscale) - - # Add 3D cursor location - if use_cursor: - cursor_chunk = _3ds_chunk(O_CONSTS) - cursor_chunk.add_variable("cursor", _3ds_point_3d(scene.cursor.location)) - object_info.add_subchunk(cursor_chunk) - - # Add AMBIENT 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) - - # Add BACKGROUND and BITMAP - if world.use_nodes: - bgtype = 'BACKGROUND' - ntree = world.node_tree.links - background_color_chunk = _3ds_chunk(RGB) - background_chunk = _3ds_chunk(SOLIDBACKGND) - background_flag = _3ds_chunk(USE_SOLIDBGND) - bgmixer = 'BACKGROUND', 'MIX', 'MIX_RGB' - bgshade = 'ADD_SHADER', 'MIX_SHADER', 'OUTPUT_WORLD' - bg_tex = 'TEX_IMAGE', 'TEX_ENVIRONMENT' - bg_cue = 'BACKGROUND', 'EMISSION', 'MIX', 'MIX_RGB', 'MIX_SHADER' - bg_color = next((lk.from_node.inputs[0].default_value[:3] for lk in ntree if lk.from_node.type == bgtype and lk.to_node.type in bgshade), world.color) - bg_mixer = next((lk.from_node.type for lk in ntree if lk.from_node.type in bgmixer and lk.to_node.type == bgtype), bgtype) - bg_image = next((lk.from_node.image for lk in ntree if lk.from_node.type in bg_tex and lk.to_node.type == bg_mixer), False) - gradient = next((lk.from_node.color_ramp.elements for lk in ntree if lk.from_node.type == 'VALTORGB' and lk.to_node.type in bgmixer), False) - background_color_chunk.add_variable("color", _3ds_float_color(bg_color)) - background_chunk.add_subchunk(background_color_chunk) - if bg_image and bg_image is not None: - background_image = _3ds_chunk(BITMAP) - background_flag = _3ds_chunk(USE_BITMAP) - background_image.add_variable("image", _3ds_string(sane_name(bg_image.name))) - object_info.add_subchunk(background_image) - object_info.add_subchunk(background_chunk) - - # Add VGRADIENT chunk - if gradient and len(gradient) >= 3: - gradient_chunk = _3ds_chunk(VGRADIENT) - background_flag = _3ds_chunk(USE_VGRADIENT) - gradient_chunk.add_variable("midpoint", _3ds_float(gradient[1].position)) - gradient_topcolor_chunk = _3ds_chunk(RGB) - gradient_topcolor_chunk.add_variable("color", _3ds_float_color(gradient[0].color[:3])) - gradient_chunk.add_subchunk(gradient_topcolor_chunk) - gradient_midcolor_chunk = _3ds_chunk(RGB) - gradient_midcolor_chunk.add_variable("color", _3ds_float_color(gradient[1].color[:3])) - gradient_chunk.add_subchunk(gradient_midcolor_chunk) - gradient_lowcolor_chunk = _3ds_chunk(RGB) - gradient_lowcolor_chunk.add_variable("color", _3ds_float_color(gradient[2].color[:3])) - gradient_chunk.add_subchunk(gradient_lowcolor_chunk) - object_info.add_subchunk(gradient_chunk) - object_info.add_subchunk(background_flag) - - # Add FOG - fognode = next((lk.from_socket.node for lk in ntree if lk.from_socket.node.type == 'VOLUME_ABSORPTION' and lk.to_socket.node.type in bgshade), False) - if fognode: - fog_chunk = _3ds_chunk(FOG) - fog_color_chunk = _3ds_chunk(RGB) - use_atmo_flag = _3ds_chunk(USE_FOG) - fog_density = fognode.inputs['Density'].default_value * 100 - fog_color_chunk.add_variable("color", _3ds_float_color(fognode.inputs[0].default_value[:3])) - fog_chunk.add_variable("nearplane", _3ds_float(world.mist_settings.start)) - fog_chunk.add_variable("nearfog", _3ds_float(fog_density * 0.5)) - fog_chunk.add_variable("farplane", _3ds_float(world.mist_settings.depth)) - fog_chunk.add_variable("farfog", _3ds_float(fog_density + fog_density * 0.5)) - fog_chunk.add_subchunk(fog_color_chunk) - object_info.add_subchunk(fog_chunk) - - # Add LAYER FOG - foglayer = next((lk.from_socket.node for lk in ntree if lk.from_socket.node.type == 'VOLUME_SCATTER' and lk.to_socket.node.type in bgshade), False) - if foglayer: - layerfog_flag = 0 - if world.mist_settings.falloff == 'QUADRATIC': - layerfog_flag |= 0x1 - if world.mist_settings.falloff == 'INVERSE_QUADRATIC': - layerfog_flag |= 0x2 - layerfog_chunk = _3ds_chunk(LAYER_FOG) - layerfog_color_chunk = _3ds_chunk(RGB) - use_atmo_flag = _3ds_chunk(USE_LAYER_FOG) - layerfog_color_chunk.add_variable("color", _3ds_float_color(foglayer.inputs[0].default_value[:3])) - layerfog_chunk.add_variable("lowZ", _3ds_float(world.mist_settings.start)) - layerfog_chunk.add_variable("highZ", _3ds_float(world.mist_settings.height)) - layerfog_chunk.add_variable("density", _3ds_float(foglayer.inputs[1].default_value)) - layerfog_chunk.add_variable("flags", _3ds_uint(layerfog_flag)) - layerfog_chunk.add_subchunk(layerfog_color_chunk) - object_info.add_subchunk(layerfog_chunk) - - # Add DISTANCE CUE - distcue = next((lk.from_socket.node for lk in ntree if lk.from_socket.node.type == 'MAP_RANGE' and lk.to_socket.node.type in bg_cue), False) - if distcue: - distance_cue_chunk = _3ds_chunk(DISTANCE_CUE) - use_atmo_flag = _3ds_chunk(USE_DISTANCE_CUE) - distance_cue_chunk.add_variable("nearcue", _3ds_float(distcue.inputs[1].default_value)) - distance_cue_chunk.add_variable("neardim", _3ds_float(distcue.inputs[2].default_value)) - distance_cue_chunk.add_variable("farcue", _3ds_float(distcue.inputs[4].default_value)) - distance_cue_chunk.add_variable("fardim", _3ds_float(distcue.inputs[3].default_value)) - object_info.add_subchunk(distance_cue_chunk) - if fognode or foglayer or distcue and layer.use_pass_mist: - object_info.add_subchunk(use_atmo_flag) - if use_keyframes and world.animation_data or (world.node_tree and world.node_tree.animation_data): - kfdata.add_subchunk(make_ambient_node(world)) - - # Collect translation for transformation matrix - translation = {} - rotation = {} - 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: - translation[ob.name] = mtx_scale @ ob.location - rotation[ob.name] = ob.rotation_euler - scale[ob.name] = mtx_scale.copy() - name_id[ob.name] = len(name_id) - object_id[ob.name] = len(object_id) - - for ob in empty_objects: - translation[ob.name] = mtx_scale @ ob.location - rotation[ob.name] = ob.rotation_euler - scale[ob.name] = mtx_scale.copy() - name_id[ob.name] = len(name_id) - - for ob in light_objects: - translation[ob.name] = mtx_scale @ ob.location - rotation[ob.name] = ob.rotation_euler - scale[ob.name] = mtx_scale.copy() - name_id[ob.name] = len(name_id) - object_id[ob.name] = len(object_id) - - for ob in camera_objects: - translation[ob.name] = mtx_scale @ ob.location - rotation[ob.name] = ob.rotation_euler - scale[ob.name] = mtx_scale.copy() - name_id[ob.name] = len(name_id) - object_id[ob.name] = len(object_id) - - # Create object chunks for all meshes - i = 0 - for ob, mesh, matrix in mesh_objects: - 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)) - - # 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 object node - if use_keyframes: - kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale, name_id)) - - i += i - - # Create chunks for all empties - only requires a object node - if use_keyframes: - for ob in empty_objects: - kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale, name_id)) - - # Create light object chunks - for ob in light_objects: - object_chunk = _3ds_chunk(OBJECT) - obj_light_chunk = _3ds_chunk(OBJECT_LIGHT) - color_float_chunk = _3ds_chunk(RGB) - light_distance = translation[ob.name] - light_attenuate = _3ds_chunk(LIGHT_ATTENUATE) - light_inner_range = _3ds_chunk(LIGHT_INNER_RANGE) - light_outer_range = _3ds_chunk(LIGHT_OUTER_RANGE) - light_energy_factor = _3ds_chunk(LIGHT_MULTIPLIER) - light_ratio = ob.data.energy if ob.data.type == 'SUN' else ob.data.energy * 0.001 - object_chunk.add_variable("light", _3ds_string(sane_name(ob.name))) - obj_light_chunk.add_variable("location", _3ds_point_3d(light_distance)) - color_float_chunk.add_variable("color", _3ds_float_color(ob.data.color)) - light_outer_range.add_variable("distance", _3ds_float(ob.data.cutoff_distance)) - light_inner_range.add_variable("radius", _3ds_float(ob.data.shadow_soft_size * 100)) - light_energy_factor.add_variable("energy", _3ds_float(light_ratio)) - obj_light_chunk.add_subchunk(color_float_chunk) - obj_light_chunk.add_subchunk(light_outer_range) - obj_light_chunk.add_subchunk(light_inner_range) - obj_light_chunk.add_subchunk(light_energy_factor) - if ob.data.use_custom_distance: - obj_light_chunk.add_subchunk(light_attenuate) - - if ob.data.type == 'SPOT': - cone_angle = math.degrees(ob.data.spot_size) - hot_spot = cone_angle - (ob.data.spot_blend * math.floor(cone_angle)) - spot_pos = calc_target(light_distance, rotation[ob.name].x, rotation[ob.name].z) - spotlight_chunk = _3ds_chunk(LIGHT_SPOTLIGHT) - spot_roll_chunk = _3ds_chunk(LIGHT_SPOT_ROLL) - spotlight_chunk.add_variable("target", _3ds_point_3d(spot_pos)) - spotlight_chunk.add_variable("hotspot", _3ds_float(round(hot_spot, 4))) - spotlight_chunk.add_variable("angle", _3ds_float(round(cone_angle, 4))) - spot_roll_chunk.add_variable("roll", _3ds_float(round(rotation[ob.name].y, 6))) - spotlight_chunk.add_subchunk(spot_roll_chunk) - if ob.data.use_shadow: - spot_shadow_flag = _3ds_chunk(LIGHT_SPOT_SHADOWED) - spot_shadow_chunk = _3ds_chunk(LIGHT_SPOT_LSHADOW) - spot_shadow_chunk.add_variable("bias", _3ds_float(round(ob.data.shadow_buffer_bias,4))) - spot_shadow_chunk.add_variable("filter", _3ds_float(round((ob.data.shadow_buffer_clip_start * 10),4))) - spot_shadow_chunk.add_variable("buffer", _3ds_ushort(0x200)) - spotlight_chunk.add_subchunk(spot_shadow_flag) - spotlight_chunk.add_subchunk(spot_shadow_chunk) - if ob.data.show_cone: - spot_cone_chunk = _3ds_chunk(LIGHT_SPOT_SEE_CONE) - spotlight_chunk.add_subchunk(spot_cone_chunk) - if ob.data.use_square: - spot_square_chunk = _3ds_chunk(LIGHT_SPOT_RECTANGLE) - spotlight_chunk.add_subchunk(spot_square_chunk) - if ob.scale.x and ob.scale.y != 0.0: - spot_aspect_chunk = _3ds_chunk(LIGHT_SPOT_ASPECT) - spot_aspect_chunk.add_variable("aspect", _3ds_float(round((ob.scale.x / ob.scale.y),4))) - spotlight_chunk.add_subchunk(spot_aspect_chunk) - if ob.data.use_nodes: - links = ob.data.node_tree.links - bptype = 'EMISSION' - bpmix = 'MIX', 'MIX_RGB', 'EMISSION' - bptex = 'TEX_IMAGE', 'TEX_ENVIRONMENT' - bpout = 'ADD_SHADER', 'MIX_SHADER', 'OUTPUT_LIGHT' - bshade = next((lk.from_node.type for lk in links if lk.from_node.type == bptype and lk.to_node.type in bpout), None) - bpnode = next((lk.from_node.type for lk in links if lk.from_node.type in bpmix and lk.to_node.type == bshade), bshade) - bitmap = next((lk.from_node.image for lk in links if lk.from_node.type in bptex and lk.to_node.type == bpnode), False) - if bitmap and bitmap is not None: - spot_projector_chunk = _3ds_chunk(LIGHT_SPOT_PROJECTOR) - spot_projector_chunk.add_variable("image", _3ds_string(sane_name(bitmap.name))) - spotlight_chunk.add_subchunk(spot_projector_chunk) - obj_light_chunk.add_subchunk(spotlight_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 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) - - # Add light object and hierarchy chunks to object info - object_info.add_subchunk(object_chunk) - - # Export light and spotlight target node - if use_keyframes: - kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale, name_id)) - if ob.data.type == 'SPOT': - kfdata.add_subchunk(make_target_node(ob, translation, rotation, scale, name_id)) - - # Create camera object chunks - for ob in camera_objects: - object_chunk = _3ds_chunk(OBJECT) - camera_chunk = _3ds_chunk(OBJECT_CAMERA) - crange_chunk = _3ds_chunk(OBJECT_CAM_RANGES) - camera_distance = translation[ob.name] - camera_target = calc_target(camera_distance, rotation[ob.name].x, rotation[ob.name].z) - object_chunk.add_variable("camera", _3ds_string(sane_name(ob.name))) - camera_chunk.add_variable("location", _3ds_point_3d(camera_distance)) - camera_chunk.add_variable("target", _3ds_point_3d(camera_target)) - camera_chunk.add_variable("roll", _3ds_float(round(rotation[ob.name].y, 6))) - camera_chunk.add_variable("lens", _3ds_float(ob.data.lens)) - crange_chunk.add_variable("clipstart", _3ds_float(ob.data.clip_start * 0.1)) - crange_chunk.add_variable("clipend", _3ds_float(ob.data.clip_end * 0.1)) - camera_chunk.add_subchunk(crange_chunk) - 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 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) - - # Add light object and hierarchy chunks to object info - object_info.add_subchunk(object_chunk) - - # Export camera and target node - if use_keyframes: - kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale, name_id)) - kfdata.add_subchunk(make_target_node(ob, translation, rotation, scale, name_id)) - - # Add main object info chunk to primary chunk - primary.add_subchunk(object_info) - - # Add main keyframe data chunk to primary chunk - if use_keyframes: - primary.add_subchunk(kfdata) - - # The chunk hierarchy is completely built, now 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() - - # Remove free objects - for free in free_objects: - bpy.data.meshes.remove(free) - - # Clear name mapping vars, could make locals too - del name_unique[:] - name_mapping.clear() - free_objects.clear() - - # Debugging only: report the exporting time - context.window.cursor_set('DEFAULT') - print("3ds export time: %.2f" % (time.time() - duration)) - - # Debugging only: dump the chunk hierarchy - # primary.dump() - - return {'FINISHED'} diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/io_scene_3ds/import_3ds.py b/scripts/addons_core/bfa_default_addons/Default_Addons/io_scene_3ds/import_3ds.py deleted file mode 100644 index ffd5860d2751..000000000000 --- a/scripts/addons_core/bfa_default_addons/Default_Addons/io_scene_3ds/import_3ds.py +++ /dev/null @@ -1,1940 +0,0 @@ -# SPDX-FileCopyrightText: 2005 Bob Holcomb -# -# SPDX-License-Identifier: GPL-2.0-or-later - -import os -import bpy -import time -import math -import struct -import mathutils -from bpy_extras.image_utils import load_image -from bpy_extras.node_shader_utils import PrincipledBSDFWrapper -from pathlib import Path - -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 -COLOR_F = 0x0010 # color defined as 3 floats -COLOR_24 = 0x0011 # color defined as 3 bytes -LIN_COLOR_24 = 0x0012 # linear byte color -LIN_COLOR_F = 0x0013 # linear float color -PCT_SHORT = 0x0030 # percentage short -PCT_FLOAT = 0x0031 # percentage float -MASTERSCALE = 0x0100 # Master scale factor - -# >----- sub defines of OBJECTINFO -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 -O_CONSTS = 0x1500 # The origin of the 3D cursor -AMBIENTLIGHT = 0x2100 # The color of the ambient light -FOG = 0x2200 # The fog atmosphere settings -USE_FOG = 0x2201 # The fog atmosphere flag -FOG_BGND = 0x2210 # The fog atmosphere background flag -LAYER_FOG = 0x2302 # The fog layer atmosphere settings -USE_LAYER_FOG = 0x2303 # The fog layer atmosphere flag -DCUE_BGND = 0x2310 # The distance cue background flag -MATERIAL = 0xAFFF # This stored the texture info -OBJECT = 0x4000 # This stores the faces, vertices, etc... - -# >------ sub defines of MATERIAL -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_XPFALL = 0xA052 # Transparency falloff value -MAT_REFBLUR = 0xA053 # Reflection blurring value -MAT_SELF_ILLUM = 0xA080 # # Material self illumination flag -MAT_TWO_SIDE = 0xA081 # Material is two sided flag -MAT_DECAL = 0xA082 # Material mapping is decaled flag -MAT_ADDITIVE = 0xA083 # Material has additive transparency flag -MAT_SELF_ILPCT = 0xA084 # Self illumination strength (percent) -MAT_WIRE = 0xA085 # Material wireframe rendered flag -MAT_FACEMAP = 0xA088 # Face mapped textures flag -MAT_PHONGSOFT = 0xA08C # Phong soften material flag -MAT_WIREABS = 0xA08E # Wire size in units flag -MAT_WIRESIZE = 0xA087 # Rendered wire size in pixels -MAT_SHADING = 0xA100 # Material shading method -MAT_USE_XPFALL = 0xA240 # Transparency falloff flag -MAT_USE_REFBLUR = 0xA250 # Reflection blurring flag - -# >------ sub defines of MATERIAL_MAP -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_TEXBLUR = 0xA353 # Texture blurring factor (float 0-1) -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 - -# >------ 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 -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 -LIGHT_OFF = 0x4620 # The light is off -LIGHT_ATTENUATE = 0x4625 # Light attenuate flag -LIGHT_RAYSHADE = 0x4627 # Light rayshading flag -LIGHT_SPOT_SHADOWED = 0x4630 # Light spot shadow flag -LIGHT_LOCAL_SHADOW = 0x4640 # Light shadow values 1 -LIGHT_LOCAL_SHADOW2 = 0x4641 # Light shadow values 2 -LIGHT_SPOT_SEE_CONE = 0x4650 # Light spot cone flag -LIGHT_SPOT_RECTANGLE = 0x4651 # Light spot rectangle flag -LIGHT_SPOT_OVERSHOOT = 0x4652 # Light spot overshoot flag -LIGHT_SPOT_PROJECTOR = 0x4653 # Light spot bitmap name -LIGHT_EXCLUDE = 0x4654 # Light excluded objects -LIGHT_RANGE = 0x4655 # Light range -LIGHT_SPOT_ROLL = 0x4656 # The roll angle of the spot -LIGHT_SPOT_ASPECT = 0x4657 # Light spot aspect flag -LIGHT_RAY_BIAS = 0x4658 # Light ray bias value -LIGHT_INNER_RANGE = 0x4659 # The light inner range -LIGHT_OUTER_RANGE = 0x465A # The light outer range -LIGHT_MULTIPLIER = 0x465B # The light energy factor -LIGHT_ATTENUATE = 0x4625 # Light attenuation flag -LIGHT_AMBIENT_LIGHT = 0x4680 # Light ambient flag - -# >------ 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 # The objects face material -OBJECT_UV = 0x4140 # The vertex UV texture coordinates -OBJECT_SMOOTH = 0x4150 # The objects face smooth groups -OBJECT_TRANS_MATRIX = 0x4160 # The objects Matrix - -# >------ sub defines of EDITKEYFRAME -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 - -# >------ sub defines of KEYFRAME_NODE -OBJECT_NODE_HDR = 0xB010 # Keyframe object node header -OBJECT_INSTANCE_NAME = 0xB011 # Keyframe object name for dummy objects -OBJECT_PRESCALE = 0xB012 # Keyframe object prescale -OBJECT_PIVOT = 0xB013 # Keyframe object pivot position -OBJECT_BOUNDBOX = 0xB014 # Keyframe object boundbox -MORPH_SMOOTH = 0xB015 # Auto smooth angle for keyframe mesh objects -POS_TRACK_TAG = 0xB020 # Keyframe object position track -ROT_TRACK_TAG = 0xB021 # Keyframe object rotation track -SCL_TRACK_TAG = 0xB022 # Keyframe object scale track -FOV_TRACK_TAG = 0xB023 # Keyframe camera field of view track -ROLL_TRACK_TAG = 0xB024 # Keyframe camera roll track -COL_TRACK_TAG = 0xB025 # Keyframe light color track -MORPH_TRACK_TAG = 0xB026 # Keyframe object morph smooth track -HOTSPOT_TRACK_TAG = 0xB027 # Keyframe spotlight hotspot track -FALLOFF_TRACK_TAG = 0xB028 # Keyframe spotlight falloff track -HIDE_TRACK_TAG = 0xB029 # Keyframe object hide track -OBJECT_NODE_ID = 0xB030 # Keyframe object node id -PARENT_NAME = 0x80F0 # Object parent name tree (dot seperated) -ROOT_OBJECT = 0xFFFF - -global scn -scn = None - -object_dictionary = {} -parent_dictionary = {} -matrix_transform = {} -object_matrix = {} - - -class Chunk: - __slots__ = ( - "ID", - "length", - "bytes_read", - ) - # we don't read in the bytes_read, we compute that - binary_format = ' 0 else False - else: - bmesh.polygons.foreach_set("use_smooth", [False] * len(bmesh.polygons)) - - if contextMatrix: - if WORLD_MATRIX: - ob.matrix_world = contextMatrix - else: - ob.matrix_local = contextMatrix - object_matrix[ob] = contextMatrix.copy() - - # a spare chunk - new_chunk = Chunk() - temp_chunk = Chunk() - - CreateBlenderObject = False - CreateCameraObject = False - 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 - return 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] - - def calc_target(loca, target): - pan = tilt = 0.0 - plane = loca + target - angle = math.radians(90) # Target triangulation - check_sign = abs(loca.y) < abs(target.y) - check_axes = abs(loca.x - target.x) > abs(loca.y - target.y) - plane_y = plane.y if check_sign else -1 * plane.y - sign_xy = plane.x if check_axes else plane.y - axis_xy = plane_y if check_axes else plane.x - hyp = math.sqrt(pow(plane.x,2) + pow(plane.y,2)) - dia = math.sqrt(pow(hyp,2) + pow(plane.z,2)) - yaw = math.atan2(math.copysign(hyp, sign_xy), axis_xy) - bow = math.acos(hyp / dia) if dia != 0 else 0 - turn = angle - yaw if check_sign else angle + yaw - tilt = angle - bow if loca.z > target.z else angle + bow - pan = yaw if check_axes else turn - return tilt, pan - - def read_track_data(track_chunk): - """Trackflags 0x1, 0x2 and 0x3 are for looping. 0x8, 0x10 and 0x20 - locks the XYZ axes. 0x100, 0x200 and 0x400 unlinks the XYZ axes.""" - tflags = read_short(track_chunk) - contextTrack_flag = tflags - temp_data = file.read(SZ_U_INT * 2) - track_chunk.bytes_read += SZ_U_INT * 2 - nkeys = read_long(track_chunk) - for i in range(nkeys): - nframe = read_long(track_chunk) - nflags = read_short(track_chunk) - for f in range(bin(nflags)[-5:].count('1')): - temp_data = file.read(SZ_FLOAT) # Check for spline terms - track_chunk.bytes_read += SZ_FLOAT - trackdata = read_float_array(track_chunk) - keyframe_data[nframe] = trackdata - return keyframe_data - - def read_track_angle(track_chunk): - temp_data = file.read(SZ_U_SHORT * 5) - track_chunk.bytes_read += SZ_U_SHORT * 5 - nkeys = read_long(track_chunk) - for i in range(nkeys): - nframe = read_long(track_chunk) - nflags = read_short(track_chunk) - for f in range(bin(nflags)[-5:].count('1')): - temp_data = file.read(SZ_FLOAT) # Check for spline terms - track_chunk.bytes_read += SZ_FLOAT - angle = read_float(track_chunk) - keyframe_angle[nframe] = math.radians(angle) - return keyframe_angle - - dirname = os.path.dirname(file.name) - - # loop through all the data for this chunk (previous chunk) and see what it is - while (previous_chunk.bytes_read < previous_chunk.length): - read_chunk(file, new_chunk) - - # Check the Version chunk - if new_chunk.ID == VERSION: - # read in the version of the file - temp_data = file.read(SZ_U_INT) - version = struct.unpack(' 3: - print("\tNon-Fatal Error: Version greater than 3, may not load correctly: ", version) - - # The main object info chunk - elif new_chunk.ID == OBJECTINFO: - process_next_chunk(context, file, new_chunk, imported_objects, - CONSTRAIN, FILTER, IMAGE_SEARCH, WORLD_MATRIX, - KEYFRAME, APPLY_MATRIX, CONVERSE, MEASURE, CURSOR) - - # keep track of how much we read in the main chunk - new_chunk.bytes_read += temp_chunk.bytes_read - - # If material chunk - elif new_chunk.ID == MATERIAL: - contextAlpha = True - contextReflection = False - contextTransmission = False - contextColor = mathutils.Color((0.8, 0.8, 0.8)) - 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) - # to not loose this data, ambient color is stored in line color - if temp_chunk.ID == COLOR_F: - contextMaterial.line_color[:3] = read_float_array(temp_chunk) - elif temp_chunk.ID == COLOR_24: - 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 == COLOR_F: - contextColor = mathutils.Color(read_float_array(temp_chunk)) - contextMaterial.diffuse_color[:3] = contextColor - elif temp_chunk.ID == COLOR_24: - contextColor = mathutils.Color(read_byte_color(temp_chunk)) - contextMaterial.diffuse_color[:3] = contextColor - 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) - if temp_chunk.ID == COLOR_F: - contextMaterial.specular_color = read_float_array(temp_chunk) - elif temp_chunk.ID == COLOR_24: - 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 == PCT_SHORT: - contextMaterial.roughness = 1 - (float(read_short(temp_chunk) / 100)) - elif temp_chunk.ID == PCT_FLOAT: - contextMaterial.roughness = 1.0 - float(read_float(temp_chunk)) - else: - skip_to_end(file, temp_chunk) - new_chunk.bytes_read += temp_chunk.bytes_read - - elif new_chunk.ID == MAT_SHIN2: - read_chunk(file, temp_chunk) - if temp_chunk.ID == PCT_SHORT: - contextMaterial.specular_intensity = float(read_short(temp_chunk) / 100) - elif temp_chunk.ID == PCT_FLOAT: - contextMaterial.specular_intensity = float(read_float(temp_chunk)) - else: - skip_to_end(file, temp_chunk) - new_chunk.bytes_read += temp_chunk.bytes_read - - elif new_chunk.ID == MAT_SHIN3: - read_chunk(file, temp_chunk) - if temp_chunk.ID == PCT_SHORT: - contextMaterial.metallic = float(read_short(temp_chunk) / 100) - elif temp_chunk.ID == PCT_FLOAT: - contextMaterial.metallic = float(read_float(temp_chunk)) - else: - skip_to_end(file, temp_chunk) - new_chunk.bytes_read += temp_chunk.bytes_read - - elif new_chunk.ID == MAT_TRANSPARENCY: - read_chunk(file, temp_chunk) - if temp_chunk.ID == PCT_SHORT: - contextAlpha = 1 - (float(read_short(temp_chunk) / 100)) - contextMaterial.diffuse_color[3] = contextAlpha - elif temp_chunk.ID == PCT_FLOAT: - contextAlpha = 1.0 - float(read_float(temp_chunk)) - contextMaterial.diffuse_color[3] = contextAlpha - else: - skip_to_end(file, temp_chunk) - if contextAlpha < 1: - contextMaterial.blend_method = 'BLEND' - new_chunk.bytes_read += temp_chunk.bytes_read - - elif new_chunk.ID == MAT_XPFALL: - read_chunk(file, temp_chunk) - if temp_chunk.ID == PCT_SHORT: - contextTransmission = float(abs(read_short(temp_chunk) / 100)) - else: - skip_to_end(file, temp_chunk) - new_chunk.bytes_read += temp_chunk.bytes_read - - elif new_chunk.ID == MAT_REFBLUR: - read_chunk(file, temp_chunk) - if temp_chunk.ID == PCT_SHORT: - contextReflection = float(read_short(temp_chunk) / 100) - elif temp_chunk.ID == PCT_FLOAT: - contextReflection = float(read_float(temp_chunk)) - else: - skip_to_end(file, temp_chunk) - new_chunk.bytes_read += temp_chunk.bytes_read - - elif new_chunk.ID == MAT_SELF_ILPCT: - read_chunk(file, temp_chunk) - if temp_chunk.ID == PCT_SHORT: - contextMaterial.line_priority = int(read_short(temp_chunk)) - elif temp_chunk.ID == PCT_FLOAT: - contextMaterial.line_priority = (float(read_float(temp_chunk)) * 100) - else: - skip_to_end(file, temp_chunk) - new_chunk.bytes_read += temp_chunk.bytes_read - - elif new_chunk.ID == MAT_SHADING: - shading = read_short(new_chunk) - if shading >= 2: - contextWrapper.use_nodes = True - contextWrapper.base_color = contextColor[:] - contextWrapper.metallic = contextMaterial.metallic - contextWrapper.roughness = contextMaterial.roughness - contextWrapper.transmission = contextTransmission - contextWrapper.specular = contextMaterial.specular_intensity - contextWrapper.specular_tint = contextMaterial.specular_color[:] - contextWrapper.emission_color = contextMaterial.line_color[:3] - contextWrapper.emission_strength = contextMaterial.line_priority / 100 - contextWrapper.alpha = contextMaterial.diffuse_color[3] = contextAlpha - contextWrapper.node_principled_bsdf.inputs['Coat Weight'].default_value = contextReflection - 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: - 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 == PCT_SHORT: - contextWrapper.normalmap_strength = (float(read_short(temp_chunk) / 100)) - elif temp_chunk.ID == PCT_FLOAT: - contextWrapper.normalmap_strength = float(read_float(temp_chunk)) - else: - skip_to_end(file, temp_chunk) - new_chunk.bytes_read += temp_chunk.bytes_read - - elif new_chunk.ID == MAT_SHIN_MAP: - read_texture(new_chunk, temp_chunk, "Shininess", 'ROUGHNESS') - - elif new_chunk.ID == MAT_SELFI_MAP: - read_texture(new_chunk, temp_chunk, "Emit", 'EMISSION') - - elif new_chunk.ID == MAT_TEX2_MAP: - read_texture(new_chunk, temp_chunk, "Tex", 'TEXTURE') - - # If cursor location - elif CURSOR and new_chunk.ID == O_CONSTS: - context.scene.cursor.location = read_float_array(new_chunk) - - # If ambient light chunk - elif CreateWorld and new_chunk.ID == AMBIENTLIGHT: - path, filename = os.path.split(file.name) - realname, ext = os.path.splitext(filename) - contextWorld = bpy.data.worlds.new("Ambient: " + realname) - context.scene.world = contextWorld - read_chunk(file, temp_chunk) - if temp_chunk.ID == COLOR_F: - contextWorld.color[:] = read_float_array(temp_chunk) - elif temp_chunk.ID == LIN_COLOR_F: - contextWorld.color[:] = read_float_array(temp_chunk) - else: - skip_to_end(file, temp_chunk) - new_chunk.bytes_read += temp_chunk.bytes_read - - # If background chunk - elif CreateWorld and new_chunk.ID == SOLIDBACKGND: - backgroundcolor = mathutils.Color((0.1, 0.1, 0.1)) - if contextWorld is None: - path, filename = os.path.split(file.name) - realname, ext = os.path.splitext(filename) - contextWorld = bpy.data.worlds.new("Background: " + realname) - context.scene.world = contextWorld - contextWorld.use_nodes = True - worldnodes = contextWorld.node_tree.nodes - backgroundnode = worldnodes['Background'] - read_chunk(file, temp_chunk) - if temp_chunk.ID == COLOR_F: - backgroundcolor = read_float_array(temp_chunk) - elif temp_chunk.ID == LIN_COLOR_F: - backgroundcolor = read_float_array(temp_chunk) - else: - skip_to_end(file, temp_chunk) - backgroundmix = next((wn for wn in worldnodes if wn.type in {'MIX', 'MIX_RGB'}), False) - backgroundnode.inputs[0].default_value[:3] = backgroundcolor - if backgroundmix: - backgroundmix.inputs[2].default_value[:3] = backgroundcolor - new_chunk.bytes_read += temp_chunk.bytes_read - - # If bitmap chunk - elif CreateWorld and new_chunk.ID == BITMAP: - bitmap_name, read_str_len = read_string(file) - if contextWorld is None: - path, filename = os.path.split(file.name) - realname, ext = os.path.splitext(filename) - contextWorld = bpy.data.worlds.new("Bitmap: " + realname) - context.scene.world = contextWorld - contextWorld.use_nodes = True - links = contextWorld.node_tree.links - nodes = contextWorld.node_tree.nodes - bitmap_mix = nodes.new(type='ShaderNodeMixRGB') - bitmapnode = nodes.new(type='ShaderNodeTexEnvironment') - bitmapping = nodes.new(type='ShaderNodeMapping') - bitmap_mix.label = "Background Mix" - bitmapnode.label = "Bitmap: " + bitmap_name - bitmap_mix.inputs[2].default_value = nodes['Background'].inputs[0].default_value - bitmapnode.image = load_image(bitmap_name, dirname, place_holder=False, recursive=IMAGE_SEARCH, check_existing=True) - bitmap_mix.inputs[0].default_value = 0.5 if bitmapnode.image is not None else 1.0 - bitmapnode.location = (-520, 400) - bitmap_mix.location = (-200, 360) - bitmapping.location = (-740, 400) - coordinates = next((wn for wn in nodes if wn.type == 'TEX_COORD'), False) - links.new(bitmap_mix.outputs[0], nodes['Background'].inputs[0]) - links.new(bitmapnode.outputs[0], bitmap_mix.inputs[1]) - links.new(bitmapping.outputs[0], bitmapnode.inputs[0]) - if not coordinates: - coordinates = nodes.new(type='ShaderNodeTexCoord') - coordinates.location = (-1340, 400) - if not bitmapping.inputs['Vector'].is_linked: - links.new(coordinates.outputs[0], bitmapping.inputs[0]) - new_chunk.bytes_read += read_str_len - - # If gradient chunk: - elif CreateWorld and new_chunk.ID == VGRADIENT: - if contextWorld is None: - path, filename = os.path.split(file.name) - realname, ext = os.path.splitext(filename) - contextWorld = bpy.data.worlds.new("Gradient: " + realname) - context.scene.world = contextWorld - contextWorld.use_nodes = True - links = contextWorld.node_tree.links - nodes = contextWorld.node_tree.nodes - gradientnode = nodes.new(type='ShaderNodeValToRGB') - layerweight = nodes.new(type='ShaderNodeLayerWeight') - conversion = nodes.new(type='ShaderNodeMath') - normalnode = nodes.new(type='ShaderNodeNormal') - coordinate = next((wn for wn in nodes if wn.type == 'TEX_COORD'), False) - backgroundmix = next((wn for wn in nodes if wn.type in {'MIX', 'MIX_RGB'}), False) - mappingnode = next((wn for wn in nodes if wn.type == 'MAPPING'), False) - conversion.location = (-740, -60) - layerweight.location = (-940, 170) - normalnode.location = (-1140, 300) - gradientnode.location = (-520, -20) - gradientnode.label = "Gradient" - conversion.operation = 'MULTIPLY_ADD' - conversion.name = conversion.label = "Multiply" - links.new(conversion.outputs[0], gradientnode.inputs[0]) - links.new(layerweight.outputs[1], conversion.inputs[0]) - links.new(layerweight.outputs[0], conversion.inputs[1]) - links.new(normalnode.outputs[1], conversion.inputs[2]) - links.new(normalnode.outputs[0], layerweight.inputs[1]) - links.new(normalnode.outputs[1], layerweight.inputs[0]) - if not coordinate: - coordinate = nodes.new(type='ShaderNodeTexCoord') - coordinate.location = (-1340, 400) - links.new(coordinate.outputs[6], normalnode.inputs[0]) - if backgroundmix: - links.new(gradientnode.outputs[0], backgroundmix.inputs[2]) - else: - links.new(gradientnode.outputs[0], nodes['Background'].inputs[0]) - if mappingnode and not mappingnode.inputs['Vector'].is_linked: - links.new(coordinate.outputs[0], mappingnode.inputs[0]) - gradientnode.color_ramp.elements.new(read_float(new_chunk)) - read_chunk(file, temp_chunk) - if temp_chunk.ID == COLOR_F: - gradientnode.color_ramp.elements[0].color[:3] = read_float_array(temp_chunk) - elif temp_chunk.ID == LIN_COLOR_F: - gradientnode.color_ramp.elements[0].color[:3] = read_float_array(temp_chunk) - else: - skip_to_end(file, temp_chunk) - new_chunk.bytes_read += temp_chunk.bytes_read - read_chunk(file, temp_chunk) - if temp_chunk.ID == COLOR_F: - gradientnode.color_ramp.elements[1].color[:3] = read_float_array(temp_chunk) - elif temp_chunk.ID == LIN_COLOR_F: - gradientnode.color_ramp.elements[1].color[:3] = read_float_array(temp_chunk) - else: - skip_to_end(file, temp_chunk) - new_chunk.bytes_read += temp_chunk.bytes_read - read_chunk(file, temp_chunk) - if temp_chunk.ID == COLOR_F: - gradientnode.color_ramp.elements[2].color[:3] = read_float_array(temp_chunk) - elif temp_chunk.ID == LIN_COLOR_F: - gradientnode.color_ramp.elements[2].color[:3] = read_float_array(temp_chunk) - else: - skip_to_end(file, temp_chunk) - new_chunk.bytes_read += temp_chunk.bytes_read - - # If fog chunk: - elif CreateWorld and new_chunk.ID == FOG: - if contextWorld is None: - path, filename = os.path.split(file.name) - realname, ext = os.path.splitext(filename) - contextWorld = bpy.data.worlds.new("Fog: " + realname) - context.scene.world = contextWorld - contextWorld.use_nodes = True - links = contextWorld.node_tree.links - nodes = contextWorld.node_tree.nodes - fognode = nodes.new(type='ShaderNodeVolumeAbsorption') - fognode.label = "Fog" - fognode.location = (10, 20) - volumemix = next((wn for wn in worldnodes if wn.name == "Volume" and wn.type in {'ADD_SHADER', 'MIX_SHADER'}), False) - if volumemix: - links.new(fognode.outputs[0], volumemix.inputs[1]) - else: - links.new(fognode.outputs[0], nodes['World Output'].inputs[1]) - contextWorld.mist_settings.use_mist = True - contextWorld.mist_settings.start = read_float(new_chunk) - nearfog = read_float(new_chunk) * 0.01 - contextWorld.mist_settings.depth = read_float(new_chunk) - farfog = read_float(new_chunk) * 0.01 - fognode.inputs[1].default_value = (nearfog + farfog) * 0.5 - read_chunk(file, temp_chunk) - if temp_chunk.ID == COLOR_F: - fognode.inputs[0].default_value[:3] = read_float_array(temp_chunk) - elif temp_chunk.ID == LIN_COLOR_F: - fognode.inputs[0].default_value[:3] = read_float_array(temp_chunk) - else: - skip_to_end(file, temp_chunk) - new_chunk.bytes_read += temp_chunk.bytes_read - elif CreateWorld and new_chunk.ID == FOG_BGND: - pass - - # If layer fog chunk: - elif CreateWorld and new_chunk.ID == LAYER_FOG: - """Fog options flags are bit 20 (0x100000) for background fogging, - bit 0 (0x1) for bottom falloff, and bit 1 (0x2) for top falloff.""" - if contextWorld is None: - path, filename = os.path.split(file.name) - realname, ext = os.path.splitext(filename) - contextWorld = bpy.data.worlds.new("LayerFog: " + realname) - context.scene.world = contextWorld - contextWorld.use_nodes = True - links = contextWorld.node_tree.links - nodes = contextWorld.node_tree.nodes - worldout = nodes.get("World Output") - worldfog = worldout.inputs[1] - litepath = nodes.new(type='ShaderNodeLightPath') - layerfog = nodes.new(type='ShaderNodeVolumeScatter') - fognode = next((wn for wn in worldnodes if wn.type == 'VOLUME_ABSORPTION'), False) - if fognode: - cuenode = next((wn for wn in worldnodes if wn.type == 'MAP_RANGE'), False) - mxvolume = nodes.new(type='ShaderNodeMixShader') - mxvolume.label = mxvolume.name = "Volume" - mxvolume.location = (220, 0) - cuesource = cuenode.outputs[0] if cuenode else litepath.outputs[7] - cuetarget = cuenode.inputs[3] if cuenode else mxvolume.inputs[0] - links.new(fognode.outputs[0], mxvolume.inputs[1]) - links.new(litepath.outputs[7], cuetarget) - links.new(cuesource, mxvolume.inputs[0]) - links.new(mxvolume.outputs[0], worldfog) - worldfog = mxvolume.inputs[2] - layerfog.label = "Layer Fog" - layerfog.location = (10, -120) - worldout.location = (440, 160) - litepath.location = (-200, 70) - links.new(layerfog.outputs[0], worldfog) - links.new(litepath.outputs[8], layerfog.inputs[2]) - links.new(litepath.outputs[2], nodes['Background'].inputs[1]) - contextWorld.mist_settings.use_mist = True - contextWorld.mist_settings.start = read_float(new_chunk) - contextWorld.mist_settings.height = read_float(new_chunk) - density = read_float(new_chunk) # Density - layerfog.inputs[1].default_value = density if density < 1 else density * 0.01 - layerfog_flag = read_long(new_chunk) - if layerfog_flag == 0: - contextWorld.mist_settings.falloff = 'LINEAR' - if layerfog_flag & 0x1: - contextWorld.mist_settings.falloff = 'QUADRATIC' - if layerfog_flag & 0x2: - contextWorld.mist_settings.falloff = 'INVERSE_QUADRATIC' - read_chunk(file, temp_chunk) - if temp_chunk.ID == COLOR_F: - layerfog.inputs[0].default_value[:3] = read_float_array(temp_chunk) - elif temp_chunk.ID == LIN_COLOR_F: - layerfog.inputs[0].default_value[:3] = read_float_array(temp_chunk) - else: - skip_to_end(file, temp_chunk) - new_chunk.bytes_read += temp_chunk.bytes_read - - # If distance cue chunk: - elif CreateWorld and new_chunk.ID == DISTANCE_CUE: - if contextWorld is None: - path, filename = os.path.split(file.name) - realname, ext = os.path.splitext(filename) - contextWorld = bpy.data.worlds.new("DistanceCue: " + realname) - context.scene.world = contextWorld - contextWorld.use_nodes = True - links = contextWorld.node_tree.links - nodes = contextWorld.node_tree.nodes - distcue_node = nodes.new(type='ShaderNodeMapRange') - camera_data = nodes.new(type='ShaderNodeCameraData') - distcue_node.label = "Distance Cue" - distcue_node.clamp = False - distcue_mix = next((wn for wn in worldnodes if wn.name == "Volume" and wn.type == 'MIX_SHADER'), False) - distcuepath = next((wn for wn in worldnodes if wn.type == 'LIGHT_PATH'), False) - if not distcuepath: - distcuepath = nodes.new(type='ShaderNodeLightPath') - distcue_node.location = (-940, 10) - distcuepath.location = (-1140, 70) - camera_data.location = (-1340, 170) - raysource = distcuepath.outputs[7] if distcue_mix else distcuepath.outputs[0] - raytarget = distcue_mix.inputs[0] if distcue_mix else nodes['Background'].inputs[1] - links.new(camera_data.outputs[1], distcue_node.inputs[1]) - links.new(camera_data.outputs[2], distcue_node.inputs[0]) - links.new(raysource, distcue_node.inputs[4]) - links.new(distcue_node.outputs[0], raytarget) - distcue_node.inputs[0].name = "Distance" - distcue_node.inputs[2].name = "Near" - distcue_node.inputs[3].name = "Far" - distcue_node.inputs[1].default_value = read_float(new_chunk) # Near Cue - distcue_node.inputs[2].default_value = read_float(new_chunk) # Near Dim - distcue_node.inputs[4].default_value = contextWorld.light_settings.distance = read_float(new_chunk) # Far Cue - distcue_node.inputs[3].default_value = read_float(new_chunk) # Far Dim - elif CreateWorld and new_chunk.ID == DCUE_BGND: - pass - - elif CreateWorld and new_chunk.ID in {USE_FOG, USE_LAYER_FOG}: - context.view_layer.use_pass_mist = True - - # If object chunk - can be mesh, light and spot or camera - elif new_chunk.ID == OBJECT: - if CreateBlenderObject: - putContextMesh(context, contextMesh_vertls, contextMesh_facels, contextMesh_flag, - contextMeshMaterials, contextMesh_smooth, WORLD_MATRIX) - - contextMesh_vertls = [] - contextMesh_facels = [] - contextMeshMaterials = [] - contextMesh_flag = None - contextMesh_smooth = None - contextMeshUV = None - contextMatrix = None - - CreateBlenderObject = True if CreateMesh else False - CreateLightObject = CreateCameraObject = False - contextObName, read_str_len = read_string(file) - new_chunk.bytes_read += read_str_len - - # If mesh chunk - elif new_chunk.ID == OBJECT_MESH: - pass - - 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 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 - contextMesh_facels = struct.unpack('<%dH' % (num_faces * 4), temp_data) - 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 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) - temp_data = file.read(SZ_U_SHORT * num_faces_using_mat) - new_chunk.bytes_read += SZ_U_SHORT * num_faces_using_mat - temp_data = struct.unpack('<%dH' % (num_faces_using_mat), temp_data) - contextMeshMaterials.append((material_name, temp_data)) - # look up the material in all the materials - - 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 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 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('= 0.01 else 0.1 - contextCamera.data.clip_start = startrange * CONSTRAIN - contextCamera.data.clip_end = read_float(new_chunk) * CONSTRAIN - elif CreateCameraObject and new_chunk.ID == OBJECT_HIERARCHY: # Hierarchy - child_id = get_hierarchy(new_chunk) - elif CreateCameraObject and new_chunk.ID == OBJECT_PARENT: - get_parent(new_chunk, child_id) - - # start keyframe section - elif new_chunk.ID == EDITKEYFRAME: - pass - - elif KEYFRAME and new_chunk.ID == KFDATA_KFSEG: - start = read_long(new_chunk) - context.scene.frame_start = start - stop = read_long(new_chunk) - context.scene.frame_end = stop - - elif KEYFRAME and new_chunk.ID == KFDATA_CURTIME: - current = read_long(new_chunk) - context.scene.frame_current = current - - # including these here means their OB_NODE_HDR are scanned - elif new_chunk.ID in {KF_AMBIENT, KF_OBJECT, KF_OBJECT_CAMERA, KF_OBJECT_LIGHT, KF_OBJECT_SPOT_LIGHT}: - tracktype = str([kf for kf,ck in globals().items() if ck == new_chunk.ID][0]).split("_")[1] - tracking = str([kf for kf,ck in globals().items() if ck == new_chunk.ID][0]).split("_")[-1] - spotting = str([kf for kf,ck in globals().items() if ck == new_chunk.ID][0]).split("_")[-2] - object_id = hierarchy = ROOT_OBJECT - child = None - if not CreateWorld and tracking == 'AMBIENT': - skip_to_end(file, new_chunk) - if not CreateLight and tracking == 'LIGHT': - skip_to_end(file, new_chunk) - if not CreateCamera and tracking == 'CAMERA': - skip_to_end(file, new_chunk) - - elif CreateTrackData and new_chunk.ID in {KF_TARGET_CAMERA, KF_TARGET_LIGHT}: - tracktype = str([kf for kf,ck in globals().items() if ck == new_chunk.ID][0]).split("_")[1] - tracking = str([kf for kf,ck in globals().items() if ck == new_chunk.ID][0]).split("_")[-1] - child = None - if not CreateLight and tracking == 'LIGHT': - skip_to_end(file, new_chunk) - if not CreateCamera and tracking == 'CAMERA': - skip_to_end(file, new_chunk) - - elif new_chunk.ID == OBJECT_NODE_ID: - object_id = read_short(new_chunk) - - elif new_chunk.ID == OBJECT_NODE_HDR: - object_name, read_str_len = read_string(file) - new_chunk.bytes_read += read_str_len - temp_data = file.read(SZ_U_INT) - new_chunk.bytes_read += SZ_U_INT - hierarchy = read_short(new_chunk) - child = object_dictionary.get(object_name) - if child is None: - if CreateWorld and tracking == 'AMBIENT': - child = context.scene.world - child.use_nodes = True - nodetree = child.node_tree - links = nodetree.links - nodes = nodetree.nodes - backlite = nodes.get("Background") - worldout = nodes.get("World Output") - ambilite = nodes.new(type='ShaderNodeRGB') - raymixer = nodes.new(type='ShaderNodeMix') - mathnode = nodes.new(type='ShaderNodeMath') - ambinode = nodes.new(type='ShaderNodeEmission') - mixshade = nodes.new(type='ShaderNodeMixShader') - litefall = nodes.new(type='ShaderNodeLightFalloff') - raymixer.label = "Ambient Mix" - ambilite.label = "Ambient Color" - raymixer.inputs[3].name = "Ambient" - raymixer.inputs[2].name = "Background" - mixshade.label = mixshade.name = "Surface" - litepath = next((n for n in nodes if n.type == 'LIGHT_PATH'), False) - ambinode.inputs[0].default_value[:3] = child.color - if not litepath: - litepath = nodes.new('ShaderNodeLightPath') - ambinode.location = (10, 160) - worldout.location = (440, 160) - mixshade.location = (220, 280) - ambilite.location = (-200, -30) - raymixer.location = (-200, 170) - litepath.location = (-1340, 70) - mathnode.location = (-1140, 100) - litefall.location = (-1140, -80) - links.new(litepath.outputs[0], mathnode.inputs[0]) - links.new(litepath.outputs[3], mathnode.inputs[1]) - links.new(litepath.outputs[5], litefall.inputs[0]) - links.new(litepath.outputs[2], litefall.inputs[1]) - links.new(litefall.outputs[0], raymixer.inputs[2]) - links.new(mathnode.outputs[0], backlite.inputs[1]) - links.new(mathnode.outputs[0], ambinode.inputs[1]) - links.new(mathnode.outputs[0], raymixer.inputs[3]) - links.new(raymixer.outputs[0], mixshade.inputs[0]) - links.new(ambinode.outputs[0], mixshade.inputs[2]) - links.new(ambilite.outputs[0], ambinode.inputs[0]) - links.new(mixshade.outputs[0], worldout.inputs[0]) - links.new(backlite.outputs[0], mixshade.inputs[1]) - ambinode.label = object_name if object_name != '$AMBIENT$' else "Ambient" - elif CreateEmpty and tracking == 'OBJECT' and object_name == '$$$DUMMY': - child = bpy.data.objects.new(object_name, None) # Create an empty object - context.view_layer.active_layer_collection.collection.objects.link(child) - imported_objects.append(child) - else: - tracking = tracktype = None - if tracktype != 'TARGET' and tracking != 'AMBIENT': - object_dict[object_id] = child - object_list.append(child) - object_parent.append(hierarchy) - pivot_list.append(mathutils.Vector((0.0, 0.0, 0.0))) - - elif new_chunk.ID == PARENT_NAME: - parent_name, read_str_len = read_string(file) - parent_dictionary.setdefault(parent_name, []).append(child) - new_chunk.bytes_read += read_str_len - - elif new_chunk.ID == OBJECT_INSTANCE_NAME and tracking == 'OBJECT': - instance_name, read_str_len = read_string(file) - if child.name == '$$$DUMMY': - child.name = instance_name - else: # Child is an instance - child = child.copy() - child.name = object_name + "." + instance_name - context.view_layer.active_layer_collection.collection.objects.link(child) - object_dict[object_id] = child - object_list[-1] = child - object_dictionary[child.name] = child - new_chunk.bytes_read += read_str_len - - elif new_chunk.ID == OBJECT_PIVOT and tracking == 'OBJECT': # Pivot - pivot = read_float_array(new_chunk) - pivot_list[len(pivot_list) - 1] = mathutils.Vector(pivot) - - elif new_chunk.ID == MORPH_SMOOTH and tracking == 'OBJECT': # Smooth angle - smooth_angle = read_float(new_chunk) - if child.data is not None: # Check if child is a dummy - child.data.set_sharp_from_angle(angle=smooth_angle) - - elif KEYFRAME and new_chunk.ID == COL_TRACK_TAG and tracking == 'AMBIENT': # Ambient - keyframe_data = {} - keyframe_data[0] = ambinode.inputs[0].default_value[:3] = child.color[:] - child.color = read_track_data(new_chunk)[0] - ambilite.outputs[0].default_value[:3] = child.color - for keydata in keyframe_data.items(): - child.color = ambilite.outputs[0].default_value[:3] = keydata[1] - child.keyframe_insert(data_path="color", frame=keydata[0]) - nodetree.keyframe_insert(data_path="nodes[\"RGB\"].outputs[0].default_value", frame=keydata[0]) - contextTrack_flag = False - - elif KEYFRAME and new_chunk.ID == COL_TRACK_TAG and tracking == 'LIGHT': # Color - keyframe_data = {} - keyframe_data[0] = child.data.color[:] - child.data.color = read_track_data(new_chunk)[0] - child.data.use_nodes = True - tree = child.data.node_tree - emitnode = tree.nodes.get("Emission") - emitnode.inputs[0].default_value[:3] = child.data.color - colornode = next((nd for nd in tree.nodes if nd.type == 'RGB'), False) - lightfall = next((nd for nd in tree.nodes if nd.type == 'LIGHT_FALLOFF'), False) - if not colornode: - colornode = tree.nodes.new('ShaderNodeRGB') - colornode.location = (-380, 60) - tree.links.new(colornode.outputs[0], emitnode.inputs[0]) - if not lightfall: - lightfall = tree.nodes.new('ShaderNodeLightFalloff') - lightpath = tree.nodes.new('ShaderNodeLightPath') - lightfall.location = (-720, 20) - lightpath.location = (-940, 180) - tree.links.new(lightpath.outputs[8], lightfall.inputs[0]) - tree.links.new(lightpath.outputs[7], lightfall.inputs[1]) - tree.links.new(lightfall.outputs[1], emitnode.inputs[1]) - colornode.outputs[0].default_value[:3] = child.data.color - for keydata in keyframe_data.items(): - child.data.color = colornode.outputs[0].default_value[:3] = keydata[1] - child.data.keyframe_insert(data_path="color", frame=keydata[0]) - tree.keyframe_insert(data_path="nodes[\"RGB\"].outputs[0].default_value", frame=keydata[0]) - contextTrack_flag = False - - elif KEYFRAME and new_chunk.ID == POS_TRACK_TAG and tracktype == 'OBJECT': # Translation - keyframe_data = {} - keyframe_data[0] = child.location[:] - trackpos = mathutils.Vector(read_track_data(new_chunk)[0]) - loca_mtx = mathutils.Matrix.Translation(-1*trackpos) - matrix_transform[child.name] = loca_mtx - child.location = trackpos - if child.type in {'LIGHT', 'CAMERA'}: - trackposition[0] = child.location - CreateTrackData = True - if contextTrack_flag & 0x8: # Flag 0x8 locks X axis - child.lock_location[0] = True - if contextTrack_flag & 0x10: # Flag 0x10 locks Y axis - child.lock_location[1] = True - if contextTrack_flag & 0x20: # Flag 0x20 locks Z axis - child.lock_location[2] = True - for keydata in keyframe_data.items(): - trackposition[keydata[0]] = keydata[1] # Keep track to position for target calculation - child.location = apply_constrain(keydata[1]) if hierarchy == ROOT_OBJECT else mathutils.Vector(keydata[1]) - if MEASURE != 1.0: - child.location = child.location * MEASURE - if hierarchy == ROOT_OBJECT: - child.location.rotate(CONVERSE) - if not contextTrack_flag & 0x100: # Flag 0x100 unlinks X axis - child.keyframe_insert(data_path="location", index=0, frame=keydata[0]) - if not contextTrack_flag & 0x200: # Flag 0x200 unlinks Y axis - child.keyframe_insert(data_path="location", index=1, frame=keydata[0]) - if not contextTrack_flag & 0x400: # Flag 0x400 unlinks Z axis - child.keyframe_insert(data_path="location", index=2, frame=keydata[0]) - contextTrack_flag = False - - elif KEYFRAME and new_chunk.ID == POS_TRACK_TAG and tracktype == 'TARGET': # Target position - keyframe_data = {} - location = child.location - keyframe_data[0] = trackposition[0] - target = mathutils.Vector(read_track_data(new_chunk)[0]) - direction = calc_target(location, target) - child.rotation_euler.x = direction[0] - child.rotation_euler.z = direction[1] - for keydata in keyframe_data.items(): - track = trackposition.get(keydata[0], child.location) - locate = mathutils.Vector(track) - target = mathutils.Vector(keydata[1]) - direction = calc_target(locate, target) - rotate = mathutils.Euler((direction[0], 0.0, direction[1]), 'XYZ').to_matrix() - scale = mathutils.Vector.Fill(3, (CONSTRAIN * 0.1)) if CONSTRAIN != 0.0 else child.scale - transformation = mathutils.Matrix.LocRotScale(locate, rotate, scale) - child.matrix_world = transformation - if MEASURE != 1.0: - child.matrix_world = mathutils.Matrix.Scale(MEASURE,4) @ child.matrix_world - if hierarchy == ROOT_OBJECT: - child.matrix_world = CONVERSE @ child.matrix_world - child.keyframe_insert(data_path="rotation_euler", index=0, frame=keydata[0]) - child.keyframe_insert(data_path="rotation_euler", index=2, frame=keydata[0]) - contextTrack_flag = False - - elif KEYFRAME and new_chunk.ID == ROT_TRACK_TAG and tracktype == 'OBJECT': # Rotation - keyframe_rotation = {} - keyframe_rotation[0] = child.rotation_axis_angle[:] - tflags = read_short(new_chunk) - temp_data = file.read(SZ_U_INT * 2) - new_chunk.bytes_read += SZ_U_INT * 2 - nkeys = read_long(new_chunk) - if tflags & 0x8: # Flag 0x8 locks X axis - child.lock_rotation[0] = True - if tflags & 0x10: # Flag 0x10 locks Y axis - child.lock_rotation[1] = True - if tflags & 0x20: # Flag 0x20 locks Z axis - child.lock_rotation[2] = True - for i in range(nkeys): - nframe = read_long(new_chunk) - nflags = read_short(new_chunk) - for f in range(bin(nflags)[-5:].count('1')): - temp_data = file.read(SZ_FLOAT) # Check for spline term values - new_chunk.bytes_read += SZ_FLOAT - temp_data = file.read(SZ_4FLOAT) - rotation = struct.unpack('<4f', temp_data) - new_chunk.bytes_read += SZ_4FLOAT - keyframe_rotation[nframe] = rotation - rad, axis_x, axis_y, axis_z = keyframe_rotation[0] - trackrot = mathutils.Quaternion((axis_x, axis_y, axis_z), -rad) # Why negative? - rota_mtx = mathutils.Matrix.Rotation(trackrot.angle, 4, trackrot.axis) - transrot = matrix_transform.get(child.name) - if transrot is not None: - matrix_transform[child.name] = rota_mtx.inverted() @ transrot - child.rotation_euler = trackrot.to_euler() - for keydata in keyframe_rotation.items(): - rad, axis_x, axis_y, axis_z = keydata[1] - child.rotation_euler = mathutils.Quaternion((axis_x, axis_y, axis_z), -rad).to_euler() - if hierarchy == ROOT_OBJECT: - child.rotation_euler.rotate(CONVERSE) - if not tflags & 0x100: # Flag 0x100 unlinks X axis - child.keyframe_insert(data_path="rotation_euler", index=0, frame=keydata[0]) - if not tflags & 0x200: # Flag 0x200 unlinks Y axis - child.keyframe_insert(data_path="rotation_euler", index=1, frame=keydata[0]) - if not tflags & 0x400: # Flag 0x400 unlinks Z axis - child.keyframe_insert(data_path="rotation_euler", index=2, frame=keydata[0]) - - elif KEYFRAME and new_chunk.ID == SCL_TRACK_TAG and tracktype == 'OBJECT': # Scale - keyframe_data = {} - keyframe_data[0] = child.scale[:] - trackscale = mathutils.Vector(read_track_data(new_chunk)[0]) - scale_mtx = mathutils.Matrix.Diagonal(trackscale) - transscale = matrix_transform.get(child.name) - if transscale is not None: - matrix_transform[child.name] = scale_mtx.to_4x4() @ transscale - child.scale = trackscale - if contextTrack_flag & 0x8: # Flag 0x8 locks X axis - child.lock_scale[0] = True - if contextTrack_flag & 0x10: # Flag 0x10 locks Y axis - child.lock_scale[1] = True - if contextTrack_flag & 0x20: # Flag 0x20 locks Z axis - child.lock_scale[2] = True - for keydata in keyframe_data.items(): - child.scale = apply_constrain(keydata[1]) if hierarchy == ROOT_OBJECT else mathutils.Vector(keydata[1]) - if not contextTrack_flag & 0x100: # Flag 0x100 unlinks X axis - child.keyframe_insert(data_path="scale", index=0, frame=keydata[0]) - if not contextTrack_flag & 0x200: # Flag 0x200 unlinks Y axis - child.keyframe_insert(data_path="scale", index=1, frame=keydata[0]) - if not contextTrack_flag & 0x400: # Flag 0x400 unlinks Z axis - child.keyframe_insert(data_path="scale", index=2, frame=keydata[0]) - contextTrack_flag = False - - elif KEYFRAME and new_chunk.ID == ROLL_TRACK_TAG and tracktype == 'OBJECT': # Roll angle - keyframe_angle = {} - keyframe_angle[0] = child.rotation_euler.y - child.rotation_euler.y = read_track_angle(new_chunk)[0] - for keydata in keyframe_angle.items(): - child.rotation_euler.y = keydata[1] - if hierarchy == ROOT_OBJECT: - child.rotation_euler.rotate(CONVERSE) - child.keyframe_insert(data_path="rotation_euler", index=1, frame=keydata[0]) - - elif KEYFRAME and new_chunk.ID == FOV_TRACK_TAG and tracking == 'CAMERA': # Field of view - keyframe_angle = {} - keyframe_angle[0] = child.data.angle - child.data.angle = read_track_angle(new_chunk)[0] - for keydata in keyframe_angle.items(): - child.data.lens = (child.data.sensor_width / 2) / math.tan(keydata[1] / 2) - child.data.keyframe_insert(data_path="lens", frame=keydata[0]) - - elif KEYFRAME and new_chunk.ID == HOTSPOT_TRACK_TAG and tracking == 'LIGHT' and spotting == 'SPOT': # Hotspot - keyframe_angle = {} - cone_angle = math.degrees(child.data.spot_size) - keyframe_angle[0] = cone_angle-(child.data.spot_blend * math.floor(cone_angle)) - hot_spot = math.degrees(read_track_angle(new_chunk)[0]) - child.data.spot_blend = 1.0 - (hot_spot / cone_angle) - for keydata in keyframe_angle.items(): - child.data.spot_blend = 1.0 - (math.degrees(keydata[1]) / cone_angle) - child.data.keyframe_insert(data_path="spot_blend", frame=keydata[0]) - - elif KEYFRAME and new_chunk.ID == FALLOFF_TRACK_TAG and tracking == 'LIGHT' and spotting == 'SPOT': # Falloff - keyframe_angle = {} - keyframe_angle[0] = math.degrees(child.data.spot_size) - child.data.spot_size = read_track_angle(new_chunk)[0] - for keydata in keyframe_angle.items(): - child.data.spot_size = keydata[1] - child.data.keyframe_insert(data_path="spot_size", frame=keydata[0]) - - else: - buffer_size = new_chunk.length - new_chunk.bytes_read - binary_format = '%ic' % buffer_size - temp_data = file.read(struct.calcsize(binary_format)) - new_chunk.bytes_read += buffer_size - - # update the previous chunk bytes read - previous_chunk.bytes_read += new_chunk.bytes_read - - # FINISHED LOOP - # There will be a number of objects still not added - if CreateBlenderObject: - putContextMesh(context, contextMesh_vertls, contextMesh_facels, contextMesh_flag, - contextMeshMaterials, contextMesh_smooth, WORLD_MATRIX) - - # Fix transform - if APPLY_MATRIX: - for obj, mtx in matrix_transform.items(): - cld = object_dictionary.get(obj) - if (cld and cld.data) and cld.type == 'MESH': - cld.data.transform(mtx) - - # Assign parents to objects - # Check if we need to assign first because doing so recalcs the depsgraph - for ind, ob in enumerate(object_list): - if ob is None: - continue - parent = object_parent[ind] - if parent == ROOT_OBJECT: - ob.parent = None - elif parent not in object_dict: - try: - ob.parent = object_list[parent] - except Exception as exc: - print("\tError: ", exc) - else: # get parent from node_id number - try: - ob.parent = object_dict.get(parent) - except: # self to parent exception - pass - - #pivot_list[ind] += pivot_list[parent] # Not sure this is correct, should parent space matrix be applied before combining? - - # if parent name - parent_dictionary.pop(None, ...) - for par, objs in parent_dictionary.items(): - parent = object_dictionary.get(par) - for ob in objs: - if parent is not None: - ob.parent = parent - parent_dictionary.clear() - - # 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 is None: - continue - elif ob.type == 'MESH': - pivot = pivot_list[ind] - pivot_matrix = object_matrix.get(ob, mathutils.Matrix()) # unlikely to fail - pivot_matrix = mathutils.Matrix.Translation(-1 * pivot) - # pivot_matrix = mathutils.Matrix.Translation(pivot_matrix.to_3x3() @ -pivot) - ob.data.transform(pivot_matrix) - if APPLY_MATRIX: - cld = ob - mat = mathutils.Matrix() - while cld.parent: - trans = matrix_transform.get(cld.parent.name) - if trans is not None: - mat = trans @ mat - cld = cld.parent - if ob.type == 'MESH' and ob.data and ob.parent: - ob.data.transform(mat) - - -########## -# IMPORT # -########## - -def load_3ds(filepath, context, CONSTRAIN=10.0, UNITS=False, IMAGE_SEARCH=True, - FILTER=None, WORLD_MATRIX=False, KEYFRAME=True, APPLY_MATRIX=True, - CONVERSE=None, CURSOR=False, PIVOT=False): - - print("importing 3DS: %r..." % (filepath), end="") - - if bpy.ops.object.select_all.poll(): - bpy.ops.object.select_all(action='DESELECT') - - MEASURE = 1.0 - duration = time.time() - current_chunk = Chunk() - file = open(filepath, 'rb') - - # here we go! - read_chunk(file, current_chunk) - if current_chunk.ID != PRIMARY: - print("\tFatal Error: Not a valid 3ds file: %r" % filepath) - file.close() - return - - if CONSTRAIN: - BOUNDS_3DS[:] = [1 << 30, 1 << 30, 1 << 30, -1 << 30, -1 << 30, -1 << 30] - else: - del BOUNDS_3DS[:] - - # fixme, make unglobal, clear in case - object_dictionary.clear() - matrix_transform.clear() - object_matrix.clear() - scn = context.scene - - if UNITS: - unit_length = scn.unit_settings.length_unit - if unit_length == 'MILES': - MEASURE = 1609.344 - elif unit_length == 'KILOMETERS': - MEASURE = 1000.0 - elif unit_length == 'FEET': - MEASURE = 0.3048 - elif unit_length == 'INCHES': - MEASURE = 0.0254 - elif unit_length == 'CENTIMETERS': - MEASURE = 0.01 - elif unit_length == 'MILLIMETERS': - MEASURE = 0.001 - elif unit_length == 'THOU': - MEASURE = 0.0000254 - elif unit_length == 'MICROMETERS': - MEASURE = 0.000001 - - context.window.cursor_set('WAIT') - imported_objects = [] # Fill this list with objects - process_next_chunk(context, file, current_chunk, imported_objects, CONSTRAIN, FILTER, - IMAGE_SEARCH, WORLD_MATRIX, KEYFRAME, APPLY_MATRIX, CONVERSE, MEASURE, CURSOR) - - # fixme, make unglobal - matrix_transform.clear() - object_dictionary.clear() - object_matrix.clear() - - """ - if APPLY_MATRIX: - for ob in imported_objects: - if ob.type == 'MESH': - ob.data.transform(ob.matrix_local.inverted()) - """ - - if UNITS: - unit_mtx = mathutils.Matrix.Scale(MEASURE,4) - for ob in imported_objects: - if ob.type == 'MESH': - ob.data.transform(unit_mtx) - - if CONVERSE and not KEYFRAME: - for ob in imported_objects: - ob.location.rotate(CONVERSE) - ob.rotation_euler.rotate(CONVERSE) - - # Select all new objects - for ob in imported_objects: - if ob.type == 'LIGHT' and ob.data.type == 'SPOT': - square = math.sqrt(pow(1.0,2) + pow(1.0,2)) - aspect = ob.empty_display_size - ob.scale.x = (aspect * square / (math.sqrt(pow(aspect,2) + 1.0))) - ob.scale.y = (square / (math.sqrt(pow(aspect,2) + 1.0))) - ob.scale.z = 1.0 - ob.select_set(True) - if ob.type == 'MESH': - if PIVOT: - bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN') - if not APPLY_MATRIX: # Reset transform - bpy.ops.object.rotation_clear() - bpy.ops.object.location_clear() - bpy.ops.object.scale_clear() - - context.view_layer.update() - - axis_min = [1000000000] * 3 - axis_max = [-1000000000] * 3 - global_clamp_size = CONSTRAIN * 10000 - if global_clamp_size != 0.0: - # Get all object bounds - for ob in imported_objects: - for v in ob.bound_box: - for axis, value in enumerate(v): - if axis_min[axis] > 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 - - mtx_scale = mathutils.Matrix.Scale(scale, 4) - for obj in imported_objects: - if obj.parent is None: - obj.matrix_world = mtx_scale @ obj.matrix_world - - for screen in bpy.data.screens: - for area in screen.areas: - if area.type == 'VIEW_3D': - area.spaces[0].clip_start = scale * 0.1 - area.spaces[0].clip_end = scale * 10000 - - context.window.cursor_set('DEFAULT') - print(" done in %.4f sec." % (time.time() - duration)) - file.close() - - -def load(operator, context, files=None, directory="", filepath="", constrain_size=0.0, use_scene_unit=False, - use_image_search=True, object_filter=None, use_world_matrix=False, use_keyframes=True, - use_apply_transform=True, global_matrix=None, use_cursor=False, use_center_pivot=False, use_collection=False): - - # Get the active collection - collection_init = context.view_layer.active_layer_collection.collection - - # Load each selected file - for file in files: - # Create new collections if activated (collection name = 3ds file name) - if use_collection: - collection = bpy.data.collections.new(Path(file.name).stem) - context.scene.collection.children.link(collection) - context.view_layer.active_layer_collection = context.view_layer.layer_collection.children[collection.name] - load_3ds(Path(directory, file.name), context, CONSTRAIN=constrain_size, UNITS=use_scene_unit, - IMAGE_SEARCH=use_image_search, FILTER=object_filter, WORLD_MATRIX=use_world_matrix, KEYFRAME=use_keyframes, - APPLY_MATRIX=use_apply_transform, CONVERSE=global_matrix, CURSOR=use_cursor, PIVOT=use_center_pivot,) - - # Retrive the initial collection as active - active = context.view_layer.layer_collection.children.get(collection_init.name) - if active is not None: - context.view_layer.active_layer_collection = active - - return {'FINISHED'} diff --git a/scripts/addons_core/bfa_default_addons/Default_Extensions/import_autocad_dxf_format_dxf/__init__.py b/scripts/addons_core/bfa_default_addons/Default_Extensions/import_autocad_dxf_format_dxf/__init__.py index 30c7081b2ba6..447a2ba61c0b 100644 --- a/scripts/addons_core/bfa_default_addons/Default_Extensions/import_autocad_dxf_format_dxf/__init__.py +++ b/scripts/addons_core/bfa_default_addons/Default_Extensions/import_autocad_dxf_format_dxf/__init__.py @@ -15,18 +15,6 @@ except: PYPROJ = False -bl_info = { - "name": "Import AutoCAD DXF Format (.dxf)", - "author": "Lukas Treyer, Manfred Moitzi (support + dxfgrabber library), Vladimir Elistratov, Bastien Montagne, Remigiusz Fiedler (AKA migius)", - "version": (0, 9, 8), - "blender": (2, 80, 0), - "location": "File > Import > AutoCAD DXF", - "description": "Import files in the Autocad DXF format (.dxf)", - "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/scene_dxf.html", - "category": "Import-Export", -} - - proj_none_items = ( ('NONE', "None", "No Coordinate System is available / will be set"), ) @@ -59,8 +47,6 @@ proj_epsg_dict = {e[0]: e[1] for e in proj_epsg_items} -__version__ = '.'.join([str(s) for s in bl_info['version']]) - BY_LAYER = 0 BY_DXFTYPE = 1 BY_CLOSED_NO_BULGE_POLY = 2 @@ -181,7 +167,7 @@ class IMPORT_OT_dxf(bpy.types.Operator): """Import from DXF file format (.dxf)""" bl_idname = "import_scene.dxf" bl_description = 'Import from DXF file format (.dxf)' - bl_label = "Import DXf v." + __version__ + bl_label = "Import DXF" bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_options = {'UNDO'} @@ -194,7 +180,7 @@ class IMPORT_OT_dxf(bpy.types.Operator): filename_ext = ".dxf" files: CollectionProperty( - type=bpy.types.OperatorFileListElement, + type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'} ) @@ -561,7 +547,7 @@ def execute(self, context): else: scene = bpy.data.scenes.new(scene_name) case _: scene = bpy.context.scene - + match self.collection_options: case 'NEW_COLLECTION': collection = bpy.data.collections.new(Path(file.name).stem) diff --git a/scripts/addons_core/bfa_default_addons/Default_Extensions/import_autocad_dxf_format_dxf/blender_manifest.toml b/scripts/addons_core/bfa_default_addons/Default_Extensions/import_autocad_dxf_format_dxf/blender_manifest.toml index 4fabc4324ae6..88ece11fadc1 100644 --- a/scripts/addons_core/bfa_default_addons/Default_Extensions/import_autocad_dxf_format_dxf/blender_manifest.toml +++ b/scripts/addons_core/bfa_default_addons/Default_Extensions/import_autocad_dxf_format_dxf/blender_manifest.toml @@ -1,12 +1,19 @@ schema_version = "1.0.0" id = "import_autocad_dxf_format_dxf" name = "Import AutoCAD DXF Format (.dxf)" -version = "0.9.8" +version = "0.9.9" tagline = "Import files in the Autocad DXF format (.dxf)" maintainer = "Community" type = "add-on" +permissions = ["files"] tags = ["Import-Export"] blender_version_min = "4.2.0" license = ["SPDX:GPL-2.0-or-later"] website = "https://projects.blender.org/extensions/io_import_dxf" -copyright = ["2024 Lukas Treyer", "2024 Manfred Moitzi (support + dxfgrabber library)", "2024 Vladimir Elistratov", "2024 Bastien Montagne", "2024 Remigiusz Fiedler (AKA migius)"] +copyright = [ + "2024 Lukas Treyer", + "2024 Manfred Moitzi (support + dxfgrabber library)", + "2024 Vladimir Elistratov", + "2024 Bastien Montagne", + "2024 Remigiusz Fiedler (AKA migius)", +] diff --git a/scripts/addons_core/bfa_default_addons/Default_Extensions/navigation/__init__.py b/scripts/addons_core/bfa_default_addons/Default_Extensions/navigation/__init__.py index 39492e1cf54e..559abebce855 100644 --- a/scripts/addons_core/bfa_default_addons/Default_Extensions/navigation/__init__.py +++ b/scripts/addons_core/bfa_default_addons/Default_Extensions/navigation/__init__.py @@ -207,14 +207,14 @@ def draw(self, context): col = layout.column(align=True) col.label(text="Align view from:", icon="VIEW3D") row = col.row() - row.operator("view3d.view_axis", text="Front").type = 'FRONT' - row.operator("view3d.view_axis", text="Back").type = 'BACK' + row.operator("view3d.view_axis", text="Front", icon="VIEW_FRONT").type = 'FRONT' #BFA - icon added + row.operator("view3d.view_axis", text="Back", icon="VIEW_BACK").type = 'BACK' #BFA - icon added row = col.row() - row.operator("view3d.view_axis", text="Left").type = 'LEFT' - row.operator("view3d.view_axis", text="Right").type = 'RIGHT' + row.operator("view3d.view_axis", text="Left", icon="VIEW_LEFT").type = 'LEFT' #BFA - icon added + row.operator("view3d.view_axis", text="Right", icon="VIEW_RIGHT").type = 'RIGHT' #BFA - icon added row = col.row() - row.operator("view3d.view_axis", text="Top").type = 'TOP' - row.operator("view3d.view_axis", text="Bottom").type = 'BOTTOM' + row.operator("view3d.view_axis", text="Top", icon="VIEW_TOP").type = 'TOP' #BFA - icon added + row.operator("view3d.view_axis", text="Bottom", icon="VIEW_BOTTOM").type = 'BOTTOM' #BFA - icon added # group of 2 buttons col = layout.column(align=True) diff --git a/scripts/addons_core/bfa_default_addons/Errors/render_freestyle_svg.py b/scripts/addons_core/bfa_default_addons/Errors/render_freestyle_svg.py new file mode 100644 index 000000000000..07b8fea4aa3f --- /dev/null +++ b/scripts/addons_core/bfa_default_addons/Errors/render_freestyle_svg.py @@ -0,0 +1,775 @@ +# SPDX-FileCopyrightText: 2014-2022 Blender Foundation +# +# SPDX-License-Identifier: GPL-2.0-or-later + +bl_info = { + "name": "Freestyle SVG Exporter", + "author": "Folkert de Vries", + "version": (1, 0), + "blender": (2, 80, 0), + "location": "Properties > Render > Freestyle SVG Export", + "description": "Exports Freestyle's stylized edges in SVG format", + "warning": "", + "doc_url": "{BLENDER_MANUAL_URL}/addons/render/render_freestyle_svg.html", + "support": 'OFFICIAL', + "category": "Render", +} + +import bpy +import parameter_editor +import itertools +import os + +import xml.etree.cElementTree as et + +from bpy.app.handlers import persistent +from collections import OrderedDict +from functools import partial +from mathutils import Vector + +from freestyle.types import ( + StrokeShader, + Interface0DIterator, + Operators, + Nature, + StrokeVertex, + ) +from freestyle.utils import ( + getCurrentScene, + BoundingBox, + is_poly_clockwise, + StrokeCollector, + material_from_fedge, + get_object_name, + ) +from freestyle.functions import ( + GetShapeF1D, + CurveMaterialF0D, + ) +from freestyle.predicates import ( + AndBP1D, + AndUP1D, + ContourUP1D, + ExternalContourUP1D, + MaterialBP1D, + NotBP1D, + NotUP1D, + OrBP1D, + OrUP1D, + pyNatureUP1D, + pyZBP1D, + pyZDiscontinuityBP1D, + QuantitativeInvisibilityUP1D, + SameShapeIdBP1D, + TrueBP1D, + TrueUP1D, + ) +from freestyle.chainingiterators import ChainPredicateIterator +from parameter_editor import get_dashed_pattern + +from bpy.props import ( + BoolProperty, + EnumProperty, + PointerProperty, + ) + + +# use utf-8 here to keep ElementTree happy, end result is utf-16 +svg_primitive = """ + + +""" + + +# xml namespaces +namespaces = { + "inkscape": "http://www.inkscape.org/namespaces/inkscape", + "svg": "http://www.w3.org/2000/svg", + "sodipodi": "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd", + "": "http://www.w3.org/2000/svg", + } + + +# wrap XMLElem.find, so the namespaces don't need to be given as an argument +def find_xml_elem(obj, search, namespaces, *, all=False): + if all: + return obj.findall(search, namespaces=namespaces) + return obj.find(search, namespaces=namespaces) + +find_svg_elem = partial(find_xml_elem, namespaces=namespaces) + + +def render_height(scene): + return int(scene.render.resolution_y * scene.render.resolution_percentage / 100) + + +def render_width(scene): + return int(scene.render.resolution_x * scene.render.resolution_percentage / 100) + + +def format_rgb(color): + return 'rgb({}, {}, {})'.format(*(int(v * 255) for v in color)) + + +# stores the state of the render, used to differ between animation and single frame renders. +class RenderState: + + # Note that this flag is set to False only after the first frame + # has been written to file. + is_preview = True + + +@persistent +def render_init(scene): + RenderState.is_preview = True + + +@persistent +def render_write(scene): + RenderState.is_preview = False + + +def is_preview_render(scene): + return RenderState.is_preview or scene.svg_export.mode == 'FRAME' + + +def create_path(scene): + """Creates the output path for the svg file""" + path = os.path.dirname(scene.render.frame_path()) + file_dir_path = os.path.dirname(bpy.data.filepath) + + # try to use the given path if it is absolute + if os.path.isabs(path): + dirname = path + + # otherwise, use current file's location as a start for the relative path + elif bpy.data.is_saved and file_dir_path: + dirname = os.path.normpath(os.path.join(file_dir_path, path)) + + # otherwise, use the folder from which blender was called as the start + else: + dirname = os.path.abspath(bpy.path.abspath(path)) + + + basename = bpy.path.basename(scene.render.filepath) + if scene.svg_export.mode == 'FRAME': + frame = "{:04d}".format(scene.frame_current) + else: + frame = "{:04d}-{:04d}".format(scene.frame_start, scene.frame_end) + + os.makedirs(dirname, exist_ok=True) + + return os.path.join(dirname, basename + frame + ".svg") + + +class SVGExporterLinesetPanel(bpy.types.Panel): + """Creates a panel in the View Layer context of the properties editor""" + bl_idname = "RENDER_PT_SVGExporterLinesetPanel" + bl_space_type = 'PROPERTIES' + bl_label = "Freestyle Line Style SVG Export" + bl_region_type = 'WINDOW' + bl_context = "view_layer" + + def draw(self, context): + layout = self.layout + + scene = context.scene + svg = scene.svg_export + freestyle = context.window.view_layer.freestyle_settings + + try: + linestyle = freestyle.linesets.active.linestyle + + except AttributeError: + # Linestyles can be removed, so 0 linestyles is possible. + # there is nothing to draw in those cases. + # see https://developer.blender.org/T49855 + return + + else: + layout.active = (svg.use_svg_export and freestyle.mode != 'SCRIPT') + row = layout.row() + column = row.column() + column.prop(linestyle, 'use_export_strokes') + + column = row.column() + column.active = svg.object_fill + column.prop(linestyle, 'use_export_fills') + + row = layout.row() + row.prop(linestyle, "stroke_color_mode", expand=True) + + +class SVGExport(bpy.types.PropertyGroup): + """Implements the properties for the SVG exporter""" + bl_idname = "RENDER_PT_svg_export" + + use_svg_export: BoolProperty( + name="SVG Export", + description="Export Freestyle edges to an .svg format", + ) + split_at_invisible: BoolProperty( + name="Split at Invisible", + description="Split the stroke at an invisible vertex", + ) + object_fill: BoolProperty( + name="Fill Contours", + description="Fill the contour with the object's material color", + ) + mode: EnumProperty( + name="Mode", + items=( + ('FRAME', "Frame", "Export a single frame", 0), + ('ANIMATION', "Animation", "Export an animation", 1), + ), + default='FRAME', + ) + line_join_type: EnumProperty( + name="Line Join", + items=( + ('MITER', "Miter", "Corners are sharp", 0), + ('ROUND', "Round", "Corners are smoothed", 1), + ('BEVEL', "Bevel", "Corners are beveled", 2), + ), + default='ROUND', + ) + + +class SVGExporterPanel(bpy.types.Panel): + """Creates a Panel in the render context of the properties editor""" + bl_idname = "RENDER_PT_SVGExporterPanel" + bl_space_type = 'PROPERTIES' + bl_label = "Freestyle SVG Export" + bl_region_type = 'WINDOW' + bl_context = "render" + + def draw_header(self, context): + self.layout.prop(context.scene.svg_export, "use_svg_export", text="") + + def draw(self, context): + layout = self.layout + + scene = context.scene + svg = scene.svg_export + freestyle = context.window.view_layer.freestyle_settings + + layout.active = (svg.use_svg_export and freestyle.mode != 'SCRIPT') + + row = layout.row() + row.prop(svg, "mode", expand=True) + + row = layout.row() + row.prop(svg, "split_at_invisible") + row.prop(svg, "object_fill") + + row = layout.row() + row.prop(svg, "line_join_type", expand=True) + + +@persistent +def svg_export_header(scene): + if not (scene.render.use_freestyle and scene.svg_export.use_svg_export): + return + + # write the header only for the first frame when animation is being rendered + if not is_preview_render(scene) and scene.frame_current != scene.frame_start: + return + + # this may fail still. The error is printed to the console. + with open(create_path(scene), "w") as f: + f.write(svg_primitive.format(render_width(scene), render_height(scene))) + + +@persistent +def svg_export_animation(scene): + """makes an animation of the exported SVG file """ + render = scene.render + svg = scene.svg_export + + if render.use_freestyle and svg.use_svg_export and not is_preview_render(scene): + write_animation(create_path(scene), scene.frame_start, render.fps) + + +def write_animation(filepath, frame_begin, fps): + """Adds animate tags to the specified file.""" + tree = et.parse(filepath) + root = tree.getroot() + + linesets = find_svg_elem(tree, ".//svg:g[@inkscape:groupmode='lineset']", all=True) + for i, lineset in enumerate(linesets): + name = lineset.get('id') + frames = find_svg_elem(lineset, ".//svg:g[@inkscape:groupmode='frame']", all=True) + n_of_frames = len(frames) + keyTimes = ";".join(str(round(x / n_of_frames, 3)) for x in range(n_of_frames)) + ";1" + + style = { + 'attributeName': 'display', + 'values': "none;" * (n_of_frames - 1) + "inline;none", + 'repeatCount': 'indefinite', + 'keyTimes': keyTimes, + 'dur': "{:.3f}s".format(n_of_frames / fps), + } + + for j, frame in enumerate(frames): + id = 'anim_{}_{:06n}'.format(name, j + frame_begin) + # create animate tag + frame_anim = et.XML(''.format(id, (j - n_of_frames) / fps)) + # add per-lineset style attributes + frame_anim.attrib.update(style) + # add to the current frame + frame.append(frame_anim) + + # write SVG to file + indent_xml(root) + tree.write(filepath, encoding='ascii', xml_declaration=True) + + +# - StrokeShaders - # +class SVGPathShader(StrokeShader): + """Stroke Shader for writing stroke data to a .svg file.""" + def __init__(self, name, style, filepath, res_y, split_at_invisible, stroke_color_mode, frame_current): + StrokeShader.__init__(self) + # attribute 'name' of 'StrokeShader' objects is not writable, so _name is used + self._name = name + self.filepath = filepath + self.h = res_y + self.frame_current = frame_current + self.elements = [] + self.split_at_invisible = split_at_invisible + self.stroke_color_mode = stroke_color_mode # BASE | FIRST | LAST + self.style = style + + + @classmethod + def from_lineset(cls, lineset, filepath, res_y, split_at_invisible, use_stroke_color, frame_current, *, name=""): + """Builds a SVGPathShader using data from the given lineset""" + name = name or lineset.name + linestyle = lineset.linestyle + # extract style attributes from the linestyle and scene + svg = getCurrentScene().svg_export + style = { + 'fill': 'none', + 'stroke-width': linestyle.thickness, + 'stroke-linecap': linestyle.caps.lower(), + 'stroke-opacity': linestyle.alpha, + 'stroke': format_rgb(linestyle.color), + 'stroke-linejoin': svg.line_join_type.lower(), + } + # get dashed line pattern (if specified) + if linestyle.use_dashed_line: + style['stroke-dasharray'] = ",".join(str(elem) for elem in get_dashed_pattern(linestyle)) + # return instance + return cls(name, style, filepath, res_y, split_at_invisible, use_stroke_color, frame_current) + + + @staticmethod + def pathgen(stroke, style, height, split_at_invisible, stroke_color_mode, f=lambda v: not v.attribute.visible): + """Generator that creates SVG paths (as strings) from the current stroke """ + if len(stroke) <= 1: + return "" + + if stroke_color_mode != 'BASE': + # try to use the color of the first or last vertex + try: + index = 0 if stroke_color_mode == 'FIRST' else -1 + color = format_rgb(stroke[index].attribute.color) + style["stroke"] = color + except (ValueError, IndexError): + # default is linestyle base color + pass + + # put style attributes into a single svg path definition + path = '\n' + path + # fast-forward till the next visible vertex + it = itertools.dropwhile(f, it) + # yield next visible vertex + svert = next(it, None) + if svert is None: + break + x, y = svert.point + yield '{:.3f}, {:.3f} '.format(x, height - y) + # close current path + yield '" />' + + def shade(self, stroke): + stroke_to_paths = "".join(self.pathgen(stroke, self.style, self.h, self.split_at_invisible, self.stroke_color_mode)).split("\n") + # convert to actual XML. Empty strokes are empty strings; they are ignored. + self.elements.extend(et.XML(elem) for elem in stroke_to_paths if elem) # if len(elem.strip()) > len(self.path)) + + def write(self): + """Write SVG data tree to file """ + tree = et.parse(self.filepath) + root = tree.getroot() + name = self._name + scene = bpy.context.scene + + # create for lineset as a whole (don't overwrite) + # when rendering an animation, frames will be nested in here, otherwise a group of strokes and optionally fills. + lineset_group = find_svg_elem(tree, ".//svg:g[@id='{}']".format(name)) + if lineset_group is None: + lineset_group = et.XML('') + lineset_group.attrib = { + 'id': name, + 'xmlns:inkscape': namespaces["inkscape"], + 'inkscape:groupmode': 'lineset', + 'inkscape:label': name, + } + root.append(lineset_group) + + # create for the current frame + id = "frame_{:04n}".format(self.frame_current) + + stroke_group = et.XML("") + stroke_group.attrib = { + 'xmlns:inkscape': namespaces["inkscape"], + 'inkscape:groupmode': 'layer', + 'id': 'strokes', + 'inkscape:label': 'strokes' + } + # nest the structure + stroke_group.extend(self.elements) + if scene.svg_export.mode == 'ANIMATION': + frame_group = et.XML("") + frame_group.attrib = {'id': id, 'inkscape:groupmode': 'frame', 'inkscape:label': id} + frame_group.append(stroke_group) + lineset_group.append(frame_group) + else: + lineset_group.append(stroke_group) + + # write SVG to file + print("SVG Export: writing to", self.filepath) + indent_xml(root) + tree.write(self.filepath, encoding='ascii', xml_declaration=True) + + +class SVGFillBuilder: + def __init__(self, filepath, height, name): + self.filepath = filepath + self._name = name + self.stroke_to_fill = partial(self.stroke_to_svg, height=height) + + @staticmethod + def pathgen(vertices, path, height): + yield path + for point in vertices: + x, y = point + yield '{:.3f}, {:.3f} '.format(x, height - y) + yield ' z" />' # closes the path; connects the current to the first point + + + @staticmethod + def get_merged_strokes(strokes): + def extend_stroke(stroke, vertices): + for vert in map(StrokeVertex, vertices): + stroke.insert_vertex(vert, stroke.stroke_vertices_end()) + return stroke + + base_strokes = tuple(stroke for stroke in strokes if not is_poly_clockwise(stroke)) + merged_strokes = OrderedDict((s, list()) for s in base_strokes) + + for stroke in filter(is_poly_clockwise, strokes): + for base in base_strokes: + # don't merge when diffuse colors don't match + if diffuse_from_stroke(stroke) != diffuse_from_stroke(stroke): + continue + # only merge when the 'hole' is inside the base + elif stroke_inside_stroke(stroke, base): + merged_strokes[base].append(stroke) + break + # if it isn't a hole, it is likely that there are two strokes belonging + # to the same object separated by another object. let's try to join them + elif (get_object_name(base) == get_object_name(stroke) and + diffuse_from_stroke(stroke) == diffuse_from_stroke(stroke)): + base = extend_stroke(base, (sv for sv in stroke)) + break + else: + # if all else fails, treat this stroke as a base stroke + merged_strokes.update({stroke: []}) + return merged_strokes + + + def stroke_to_svg(self, stroke, height, parameters=None): + if parameters is None: + *color, alpha = diffuse_from_stroke(stroke) + color = tuple(int(255 * c) for c in color) + parameters = { + 'fill_rule': 'evenodd', + 'stroke': 'none', + 'fill-opacity': alpha, + 'fill': 'rgb' + repr(color), + } + param_str = " ".join('{}="{}"'.format(k, v) for k, v in parameters.items()) + path = '') + lineset_group.attrib = { + 'id': name, + 'xmlns:inkscape': namespaces["inkscape"], + 'inkscape:groupmode': 'lineset', + 'inkscape:label': name, + } + root.append(lineset_group) + print('added new lineset group ', name) + + + # for the fills of the current frame + fill_group = et.XML('') + fill_group.attrib = { + 'xmlns:inkscape': namespaces["inkscape"], + 'inkscape:groupmode': 'layer', + 'inkscape:label': 'fills', + 'id': 'fills' + } + + fill_elements = self.create_fill_elements(strokes) + fill_group.extend(reversed(tuple(fill_elements))) + if scene.svg_export.mode == 'ANIMATION': + # add the fills to the of the current frame + frame_group = find_svg_elem(lineset_group, ".//svg:g[@id='frame_{:04n}']".format(scene.frame_current)) + frame_group.insert(0, fill_group) + else: + lineset_group.insert(0, fill_group) + + # write SVG to file + indent_xml(root) + tree.write(self.filepath, encoding='ascii', xml_declaration=True) + + +def stroke_inside_stroke(a, b): + box_a = BoundingBox.from_sequence(svert.point for svert in a) + box_b = BoundingBox.from_sequence(svert.point for svert in b) + return box_a.inside(box_b) + + +def diffuse_from_stroke(stroke, curvemat=CurveMaterialF0D()): + material = curvemat(Interface0DIterator(stroke)) + return material.diffuse + +# - Callbacks - # +class ParameterEditorCallback(object): + """Object to store callbacks for the Parameter Editor in""" + def lineset_pre(self, scene, layer, lineset): + raise NotImplementedError() + + def modifier_post(self, scene, layer, lineset): + raise NotImplementedError() + + def lineset_post(self, scene, layer, lineset): + raise NotImplementedError() + + + +class SVGPathShaderCallback(ParameterEditorCallback): + @classmethod + def poll(cls, scene, linestyle): + return scene.render.use_freestyle and scene.svg_export.use_svg_export and linestyle.use_export_strokes + + @classmethod + def modifier_post(cls, scene, layer, lineset): + if not cls.poll(scene, lineset.linestyle): + return [] + + split = scene.svg_export.split_at_invisible + stroke_color_mode = lineset.linestyle.stroke_color_mode + cls.shader = SVGPathShader.from_lineset( + lineset, create_path(scene), + render_height(scene), split, stroke_color_mode, scene.frame_current, name=layer.name + '_' + lineset.name) + return [cls.shader] + + @classmethod + def lineset_post(cls, scene, layer, lineset): + if not cls.poll(scene, lineset.linestyle): + return [] + cls.shader.write() + + +class SVGFillShaderCallback(ParameterEditorCallback): + @classmethod + def poll(cls, scene, linestyle): + return scene.render.use_freestyle and scene.svg_export.use_svg_export and scene.svg_export.object_fill and linestyle.use_export_fills + + @classmethod + def lineset_post(cls, scene, layer, lineset): + if not cls.poll(scene, lineset.linestyle): + return + + # reset the stroke selection (but don't delete the already generated strokes) + Operators.reset(delete_strokes=False) + # Unary Predicates: visible and correct edge nature + upred = AndUP1D( + QuantitativeInvisibilityUP1D(0), + OrUP1D(ExternalContourUP1D(), + pyNatureUP1D(Nature.BORDER)), + ) + # select the new edges + Operators.select(upred) + # Binary Predicates + bpred = AndBP1D( + MaterialBP1D(), + NotBP1D(pyZDiscontinuityBP1D()), + ) + bpred = OrBP1D(bpred, AndBP1D(NotBP1D(bpred), AndBP1D(SameShapeIdBP1D(), MaterialBP1D()))) + # chain the edges + Operators.bidirectional_chain(ChainPredicateIterator(upred, bpred)) + # export SVG + collector = StrokeCollector() + Operators.create(TrueUP1D(), [collector]) + + builder = SVGFillBuilder(create_path(scene), render_height(scene), layer.name + '_' + lineset.name) + builder.write(collector.strokes) + # make strokes used for filling invisible + for stroke in collector.strokes: + for svert in stroke: + svert.attribute.visible = False + + + +def indent_xml(elem, level=0, indentsize=4): + """Prettifies XML code (used in SVG exporter) """ + i = "\n" + level * " " * indentsize + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " * indentsize + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + indent_xml(elem, level + 1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + elif level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + +def register_namespaces(namespaces=namespaces): + for name, url in namespaces.items(): + if name != 'svg': # creates invalid xml + et.register_namespace(name, url) + +@persistent +def handle_versions(self): + # We don't modify startup file because it assumes to + # have all the default values only. + if not bpy.data.is_saved: + return + + # Revision https://developer.blender.org/rBA861519e44adc5674545fa18202dc43c4c20f2d1d + # changed the default for fills. + # fix by Sergey https://developer.blender.org/T46150 + if bpy.data.version <= (2, 76, 0): + for linestyle in bpy.data.linestyles: + linestyle.use_export_fills = True + + + +classes = ( + SVGExporterPanel, + SVGExporterLinesetPanel, + SVGExport, + ) + + +def register(): + linestyle = bpy.types.FreestyleLineStyle + linestyle.use_export_strokes = BoolProperty( + name="Export Strokes", + description="Export strokes for this Line Style", + default=True, + ) + linestyle.stroke_color_mode = EnumProperty( + name="Stroke Color Mode", + items=( + ('BASE', "Base Color", "Use the linestyle's base color", 0), + ('FIRST', "First Vertex", "Use the color of a stroke's first vertex", 1), + ('FINAL', "Final Vertex", "Use the color of a stroke's final vertex", 2), + ), + default='BASE', + ) + linestyle.use_export_fills = BoolProperty( + name="Export Fills", + description="Export fills for this Line Style", + default=False, + ) + + for cls in classes: + bpy.utils.register_class(cls) + bpy.types.Scene.svg_export = PointerProperty(type=SVGExport) + + + # add callbacks + bpy.app.handlers.render_init.append(render_init) + bpy.app.handlers.render_write.append(render_write) + bpy.app.handlers.render_pre.append(svg_export_header) + bpy.app.handlers.render_complete.append(svg_export_animation) + + # manipulate shaders list + parameter_editor.callbacks_modifiers_post.append(SVGPathShaderCallback.modifier_post) + parameter_editor.callbacks_lineset_post.append(SVGPathShaderCallback.lineset_post) + parameter_editor.callbacks_lineset_post.append(SVGFillShaderCallback.lineset_post) + + # register namespaces + register_namespaces() + + # handle regressions + bpy.app.handlers.version_update.append(handle_versions) + + +def unregister(): + + for cls in classes: + bpy.utils.unregister_class(cls) + del bpy.types.Scene.svg_export + linestyle = bpy.types.FreestyleLineStyle + del linestyle.use_export_strokes + del linestyle.use_export_fills + + # remove callbacks + bpy.app.handlers.render_init.remove(render_init) + bpy.app.handlers.render_write.remove(render_write) + bpy.app.handlers.render_pre.remove(svg_export_header) + bpy.app.handlers.render_complete.remove(svg_export_animation) + + # manipulate shaders list + parameter_editor.callbacks_modifiers_post.remove(SVGPathShaderCallback.modifier_post) + parameter_editor.callbacks_lineset_post.remove(SVGPathShaderCallback.lineset_post) + parameter_editor.callbacks_lineset_post.remove(SVGFillShaderCallback.lineset_post) + + bpy.app.handlers.version_update.remove(handle_versions) + + +if __name__ == "__main__": + register() diff --git a/scripts/addons_core/bfa_default_addons/Errors/vdm_brush_baker/__init__.py b/scripts/addons_core/bfa_default_addons/Errors/vdm_brush_baker/__init__.py new file mode 100644 index 000000000000..fce1a9d4188b --- /dev/null +++ b/scripts/addons_core/bfa_default_addons/Errors/vdm_brush_baker/__init__.py @@ -0,0 +1,349 @@ +# SPDX-FileCopyrightText: 2023 Robin Hohnsbeen +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from mathutils import Vector +from datetime import datetime +from pathlib import Path +import os +import bpy +from . import bakematerial +import importlib +importlib.reload(bakematerial) + +bl_info = { + 'name': 'VDM Brush Baker', + 'author': 'Robin Hohnsbeen', + 'description': 'Bake vector displacement brushes easily from a plane', + 'blender': (3, 5, 0), + 'version': (1, 0, 3), + 'location': 'Sculpt Mode: View3D > Sidebar > Tool Tab', + 'warning': '', + 'category': 'Baking', + 'doc_url': '{BLENDER_MANUAL_URL}/addons/baking/vdm_brush_baker.html' +} + + +class vdm_brush_baker_addon_data(bpy.types.PropertyGroup): + draft_brush_name: bpy.props.StringProperty( + name='Name', + default='NewVDMBrush', + description='The name that will be used for the brush and texture') + render_resolution: bpy.props.EnumProperty(items={ + ('128', '128 px', 'Render with 128 x 128 pixels', 1), + ('256', '256 px', 'Render with 256 x 256 pixels', 2), + ('512', '512 px', 'Render with 512 x 512 pixels', 3), + ('1024', '1024 px', 'Render with 1024 x 1024 pixels', 4), + ('2048', '2048 px', 'Render with 2048 x 2048 pixels', 5), + }, + default='512', name='Map Resolution') + compression: bpy.props.EnumProperty(items={ + ('none', 'None', '', 1), + ('zip', 'ZIP (lossless)', '', 2), + }, + default='zip', name='Compression') + color_depth: bpy.props.EnumProperty(items={ + ('16', '16', '', 1), + ('32', '32', '', 2), + }, + default='16', + name='Color Depth', + description='A color depth of 32 can give better results but leads to far bigger file sizes. 16 should be good if the sculpt doesn\'t extend "too far" from the original plane') + render_samples: bpy.props.IntProperty(name='Render Samples', + default=64, + min=2, + max=4096) + + +def get_addon_data() -> vdm_brush_baker_addon_data: + return bpy.context.scene.VDMBrushBakerAddonData + + +def get_output_path(filename): + save_path = bpy.path.abspath('/tmp') + if bpy.data.is_saved: + save_path = os.path.dirname(bpy.data.filepath) + save_path = os.path.join(save_path, 'output_vdm', filename) + + if bpy.data.is_saved: + return bpy.path.relpath(save_path) + else: + return save_path + + +class PT_VDMBaker(bpy.types.Panel): + """ + The main panel of the add-on which contains a button to create a sculpting plane and a button to bake the vector displacement map. + It also has settings for name (image, texture and brush at once), resolution, compression and color depth. + """ + bl_label = 'VDM Brush Baker' + bl_idname = 'VDM_PT_bake_tools' + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Tool' + + def draw(self, context): + layout = self.layout + addon_data = get_addon_data() + + layout.use_property_split = True + layout.use_property_decorate = False + layout.operator(create_sculpt_plane.bl_idname, icon='ADD') + + layout.separator() + + is_occupied, brush_name = get_new_brush_name() + button_text = 'Overwrite VDM Brush' if is_occupied else 'Render and Create VDM Brush' + + createvdmlayout = layout.row() + createvdmlayout.enabled = context.active_object is not None and context.active_object.type == 'MESH' + createvdmlayout.operator( + create_vdm_brush.bl_idname, text=button_text, icon='BRUSH_DATA') + + if is_occupied: + layout.label( + text='Name Taken: Brush will be overwritten.', icon='INFO') + + col = layout.column() + col.alert = is_occupied + col.prop(addon_data, 'draft_brush_name') + + settings_layout = layout.column(align=True) + settings_layout.label(text='Settings') + layout_box = settings_layout.box() + + col = layout_box.column() + col.prop(addon_data, 'render_resolution') + + col = layout_box.column() + col.prop(addon_data, 'compression') + + col = layout_box.column() + col.prop(addon_data, 'color_depth') + + col = layout_box.column() + col.prop(addon_data, 'render_samples') + + layout.separator() + + +def get_new_brush_name(): + addon_data = get_addon_data() + + is_name_occupied = False + for custom_brush in bpy.data.brushes: + if custom_brush.name == addon_data.draft_brush_name: + is_name_occupied = True + break + + if addon_data.draft_brush_name != '': + return is_name_occupied, addon_data.draft_brush_name + else: + date = datetime.now() + dateformat = date.strftime('%b-%d-%Y-%H-%M-%S') + return False, f'vdm-{dateformat}' + + +class create_sculpt_plane(bpy.types.Operator): + """ + Creates a grid with 128 vertices per side plus two multires subdivisions. + It uses 'Preserve corners' so further subdivisions can be made while the corners of the grid stay pointy. + """ + bl_idname = 'sculptplane.create' + bl_label = 'Create Sculpting Plane' + bl_description = 'Creates a plane with a multires modifier to sculpt on' + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + bpy.ops.mesh.primitive_grid_add(x_subdivisions=128, y_subdivisions=128, size=2, + enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + new_grid = bpy.context.active_object + multires = new_grid.modifiers.new('MultiresVDM', type='MULTIRES') + multires.boundary_smooth = 'PRESERVE_CORNERS' + bpy.ops.object.multires_subdivide( + modifier='MultiresVDM', mode='CATMULL_CLARK') + bpy.ops.object.multires_subdivide( + modifier='MultiresVDM', mode='CATMULL_CLARK') # 512 vertices per one side + + return {'FINISHED'} + + +class create_vdm_brush(bpy.types.Operator): + """ + This operator will bake a vector displacement map from the active object and create a texture and brush datablock. + """ + bl_idname = 'vdmbrush.create' + bl_label = 'Create vdm brush from plane' + bl_description = 'Creates a vector displacement map from your sculpture and creates a brush with it' + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return context.active_object is not None and context.active_object.type == 'MESH' + + def execute(self, context): + if context.active_object is None or context.active_object.type != 'MESH': + return {'CANCELLED'} + + vdm_plane = context.active_object + addon_data = get_addon_data() + new_brush_name = addon_data.draft_brush_name + reference_brush_name = addon_data.draft_brush_name + + is_occupied, brush_name = get_new_brush_name() + if len(addon_data.draft_brush_name) == 0 or is_occupied: + addon_data.draft_brush_name = brush_name + + # Saving user settings + scene = context.scene + default_render_engine = scene.render.engine + default_view_transform = scene.view_settings.view_transform + default_display_device = scene.display_settings.display_device + default_file_format = scene.render.image_settings.file_format + default_color_mode = scene.render.image_settings.color_mode + default_codec = scene.render.image_settings.exr_codec + default_denoise = scene.cycles.use_denoising + default_compute_device = scene.cycles.device + default_scene_samples = scene.cycles.samples + default_plane_location = vdm_plane.location.copy() + default_plane_rotation = vdm_plane.rotation_euler.copy() + default_mode = bpy.context.object.mode + + bpy.ops.object.mode_set(mode='OBJECT') + + vdm_bake_material = bakematerial.get_vdm_bake_material() + vdm_texture_image = None + try: + # Prepare baking + scene.render.engine = 'CYCLES' + scene.cycles.samples = addon_data.render_samples + scene.cycles.use_denoising = False + scene.cycles.device = 'GPU' + + old_image_name = f'{reference_brush_name}' + if old_image_name in bpy.data.images: + bpy.data.images[old_image_name].name = 'Old VDM texture' + + # Removing the image right away can cause a crash when sculpt mode is exited. + # bpy.data.images.remove(bpy.data.images[old_image_name]) + + vdm_plane.data.materials.clear() + vdm_plane.data.materials.append(vdm_bake_material) + vdm_plane.location = Vector([0, 0, 0]) + vdm_plane.rotation_euler = (0, 0, 0) + + vdm_texture_node = vdm_bake_material.node_tree.nodes['VDMTexture'] + render_resolution = int(addon_data.render_resolution) + + bpy.ops.object.select_all(action='DESELECT') + vdm_plane.select_set(True) + output_path = get_output_path(f'{new_brush_name}.exr') + vdm_texture_image = bpy.data.images.new( + name=new_brush_name, width=render_resolution, height=render_resolution, alpha=False, float_buffer=True) + vdm_bake_material.node_tree.nodes.active = vdm_texture_node + + vdm_texture_image.filepath_raw = output_path + + scene.render.image_settings.file_format = 'OPEN_EXR' + scene.render.image_settings.color_mode = 'RGB' + scene.render.image_settings.exr_codec = 'NONE' + if addon_data.compression == 'zip': + scene.render.image_settings.exr_codec = 'ZIP' + + scene.render.image_settings.color_depth = addon_data.color_depth + vdm_texture_image.use_half_precision = addon_data.color_depth == '16' + + vdm_texture_image.colorspace_settings.is_data = True + vdm_texture_image.colorspace_settings.name = 'Non-Color' + + vdm_texture_node.image = vdm_texture_image + vdm_texture_node.select = True + + # Bake + bpy.ops.object.bake(type='EMIT') + # save as render so we have more control over compression settings + vdm_texture_image.save_render( + filepath=bpy.path.abspath(output_path), scene=scene, quality=0) + # Removes the dirty flag, so the image doesn't have to be saved again by the user. + vdm_texture_image.pack() + vdm_texture_image.unpack(method='REMOVE') + + except BaseException as Err: + self.report({"ERROR"}, f"{Err}") + + finally: + scene.render.image_settings.file_format = default_file_format + scene.render.image_settings.color_mode = default_color_mode + scene.render.image_settings.exr_codec = default_codec + scene.cycles.samples = default_scene_samples + scene.display_settings.display_device = default_display_device + scene.view_settings.view_transform = default_view_transform + scene.cycles.use_denoising = default_denoise + scene.cycles.device = default_compute_device + scene.render.engine = default_render_engine + vdm_plane.data.materials.clear() + vdm_plane.location = default_plane_location + vdm_plane.rotation_euler = default_plane_rotation + + # Needs to be in sculpt mode to set 'AREA_PLANE' mapping on new brush. + bpy.ops.object.mode_set(mode='SCULPT') + + # Texture + vdm_texture: bpy.types.Texture = None + if bpy.data.textures.find(reference_brush_name) != -1: + vdm_texture = bpy.data.textures[reference_brush_name] + else: + vdm_texture = bpy.data.textures.new( + name=new_brush_name, type='IMAGE') + vdm_texture.extension = 'EXTEND' + vdm_texture.image = vdm_texture_image + vdm_texture.name = new_brush_name + + # Brush + new_brush: bpy.types.Brush = None + if bpy.data.brushes.find(reference_brush_name) != -1: + new_brush = bpy.data.brushes[reference_brush_name] + self.report({'INFO'}, f'Changed draw brush \'{new_brush.name}\'') + else: + new_brush = bpy.data.brushes.new( + name=new_brush_name, mode='SCULPT') + self.report( + {'INFO'}, f'Created new draw brush \'{new_brush.name}\'') + + new_brush.texture = vdm_texture + new_brush.texture_slot.map_mode = 'AREA_PLANE' + new_brush.stroke_method = 'ANCHORED' + new_brush.name = new_brush_name + new_brush.use_color_as_displacement = True + new_brush.strength = 1.0 + new_brush.hardness = 0.9 + + bpy.ops.object.mode_set(mode = default_mode) + + if bpy.context.object.mode == 'SCULPT': + context.tool_settings.sculpt.brush = new_brush + + return {'FINISHED'} + + +registered_classes = [ + PT_VDMBaker, + vdm_brush_baker_addon_data, + create_vdm_brush, + create_sculpt_plane +] + + +def register(): + for registered_class in registered_classes: + bpy.utils.register_class(registered_class) + + bpy.types.Scene.VDMBrushBakerAddonData = bpy.props.PointerProperty( + type=vdm_brush_baker_addon_data) + + +def unregister(): + for registered_class in registered_classes: + bpy.utils.unregister_class(registered_class) + + del bpy.types.Scene.VDMBrushBakerAddonData diff --git a/scripts/addons_core/bfa_default_addons/Errors/vdm_brush_baker/bakematerial.py b/scripts/addons_core/bfa_default_addons/Errors/vdm_brush_baker/bakematerial.py new file mode 100644 index 000000000000..85f9913692ec --- /dev/null +++ b/scripts/addons_core/bfa_default_addons/Errors/vdm_brush_baker/bakematerial.py @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: 2023 Robin Hohnsbeen +# +# SPDX-License-Identifier: GPL-3.0-or-later + +import bpy + + +def get_vdm_bake_material(): + """Creates a material that is used to bake the displacement from a plane against its UVs. + + Returns: + material: Baking material + """ + material_name = 'VDM_baking_material' + if material_name in bpy.data.materials: + # Recreate material every time to ensure it is unchanged by the user which could lead to issues. + # I would like to keep it directly after bake though so people could look + # at how it is made, if they are interested. + bpy.data.materials.remove(bpy.data.materials[material_name]) + + new_material = bpy.data.materials.new(name=material_name) + + new_material.use_nodes = True + nodes = new_material.node_tree.nodes + principled_node = next(n for n in new_material.node_tree.nodes if n.type == "BSDF_PRINCIPLED") + nodes.remove(principled_node) + material_output = next(n for n in new_material.node_tree.nodes if n.type == "OUTPUT_MATERIAL") + + # Create relevant nodes + combine_node = nodes.new('ShaderNodeCombineXYZ') + + separate_node1 = nodes.new('ShaderNodeSeparateXYZ') + separate_node2 = nodes.new('ShaderNodeSeparateXYZ') + + vector_subtract_node = nodes.new('ShaderNodeVectorMath') + vector_subtract_node.operation = 'SUBTRACT' + + vector_multiply_node = nodes.new('ShaderNodeVectorMath') + vector_multiply_node.operation = 'MULTIPLY' + vector_multiply_node.inputs[1].default_value = [2.0, 2.0, 2.0] + + vector_add_node = nodes.new('ShaderNodeVectorMath') + vector_add_node.operation = 'ADD' + vector_add_node.inputs[1].default_value = [-0.5, -0.5, -0.5] + + tex_coord_node = nodes.new('ShaderNodeTexCoord') + + image_node = nodes.new('ShaderNodeTexImage') + image_node.name = "VDMTexture" + + # Connect nodes + tree = new_material.node_tree + tree.links.new(combine_node.outputs[0], material_output.inputs[0]) + + tree.links.new(separate_node1.outputs[0], combine_node.inputs[0]) + tree.links.new(separate_node1.outputs[1], combine_node.inputs[1]) + + tree.links.new( + vector_subtract_node.outputs[0], separate_node1.inputs[0]) + + tree.links.new( + vector_multiply_node.outputs[0], vector_subtract_node.inputs[1]) + + tree.links.new( + vector_add_node.outputs[0], vector_multiply_node.inputs[0]) + + tree.links.new(tex_coord_node.outputs[2], vector_add_node.inputs[0]) + tree.links.new( + tex_coord_node.outputs[3], vector_subtract_node.inputs[0]) + tree.links.new(tex_coord_node.outputs[3], separate_node2.inputs[0]) + tree.links.new(separate_node2.outputs[2], combine_node.inputs[2]) + + return bpy.data.materials[material_name] diff --git a/scripts/addons_core/bfa_default_addons/Read Me.txt b/scripts/addons_core/bfa_default_addons/Read Me.txt new file mode 100644 index 000000000000..45fbe631f3af --- /dev/null +++ b/scripts/addons_core/bfa_default_addons/Read Me.txt @@ -0,0 +1,14 @@ +[CHANGES FROM BFORARTISTS] +io_mesh_stl\ = stl_format_legacy\ +io_scene_3ds\ = autodesk_3ds_format\ +space_view3d_copy_attributes.py = copy_attributes_menu\ +space_view3d_pie_menus\ = viewport_pie_menus\ + +[REMOVED - unmaintained bugs] +render_freestyle_svg.py + +[REMOVED - no extensions equivalent] +io_mesh_uv_layout\ +mesh_inset\ +meshedgetools.py +minilightlib\ \ No newline at end of file diff --git a/scripts/addons_core/bfa_default_addons/__init__.py b/scripts/addons_core/bfa_default_addons/__init__.py index 7e7380275dce..9308431e8dcd 100644 --- a/scripts/addons_core/bfa_default_addons/__init__.py +++ b/scripts/addons_core/bfa_default_addons/__init__.py @@ -52,17 +52,18 @@ "category": "Bforartists", } +# --------- +# Variables + # Configure the display name and sub-folder of your Library here: source_ext = "Default_Extensions" source_addons = "Default_Addons" -### Variables prefs = bpy.context.preferences # Get the addon path path = p.dirname(__file__) - # Get the extensions source files source_addon_folder = os.path.join(path, source_addons) @@ -81,12 +82,8 @@ # Define the addons sub-folder path destination_addon_folder = version_path / 'scripts' / 'addons' -### - -# Running code, don't change if not necessary! -# ----------------------------------------------------------------------------- +# --------- -# # safe_bl_idname = re.sub("\s", "_", re.sub("[^\w\s]", "", source_ext)).lower() class LIBADDON_APT_preferences(AddonPreferences): @@ -95,7 +92,7 @@ class LIBADDON_APT_preferences(AddonPreferences): def draw(self, context: Context): layout: UILayout = self.layout - # Display addon inormation: Library name and Version. + # Display addon inormation addon_version = bl_info['version'] layout.label( diff --git a/scripts/addons_core/bl_pkg/bl_extension_ops.py b/scripts/addons_core/bl_pkg/bl_extension_ops.py index b500bf5a41da..790b6388339d 100644 --- a/scripts/addons_core/bl_pkg/bl_extension_ops.py +++ b/scripts/addons_core/bl_pkg/bl_extension_ops.py @@ -2314,7 +2314,7 @@ def execute(self, context): # class BlkPkgReplaceLegacyAddons(Operator): - """Remove the legacy addons as soon as the addon is disabled.""" + """Remove the legacy addons when Extensions are enabled.""" bl_idname = "bl_pkg.replace_legacy_addons" bl_label = "Replace Legacy with Extension" @@ -2324,7 +2324,7 @@ def execute(self, context): source_ext = "Default_Extensions" source_addons = "Default_Addons" - # -------------------------- + # ---------- # Variables current_script_path = p.dirname(__file__) diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_copy_attributes.py b/scripts/addons_core/copy_attributes_menu/__init__.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_copy_attributes.py rename to scripts/addons_core/copy_attributes_menu/__init__.py diff --git a/scripts/addons_core/copy_attributes_menu/blender_manifest.toml b/scripts/addons_core/copy_attributes_menu/blender_manifest.toml new file mode 100644 index 000000000000..af901992b223 --- /dev/null +++ b/scripts/addons_core/copy_attributes_menu/blender_manifest.toml @@ -0,0 +1,12 @@ +schema_version = "1.0.0" +id = "copy_attributes_menu" +name = "Copy Attributes Menu - Bforartists Version" +version = "0.6.0" +tagline = "Copy Attributes Menu" +maintainer = "Community" +type = "add-on" +tags = ["User Interface"] +blender_version_min = "4.2.0" +license = ["SPDX:GPL-2.0-or-later"] +website = "https://projects.blender.org/extensions/space_view3d_copy_attributes" +copyright = ["2024 Bassam Kurdali", "2024 Fabian Fricke", "2024 Adam Wiseman", "2024 Demeter Dzadik"] diff --git a/scripts/addons_core/io_mesh_stl/__init__.py b/scripts/addons_core/io_mesh_stl/__init__.py deleted file mode 100644 index 5e5e7a08023e..000000000000 --- a/scripts/addons_core/io_mesh_stl/__init__.py +++ /dev/null @@ -1,442 +0,0 @@ -# SPDX-FileCopyrightText: 2010-2022 Blender Foundation -# -# SPDX-License-Identifier: GPL-2.0-or-later - -bl_info = { - "name": "STL format (legacy)", - "author": "Guillaume Bouchard (Guillaum)", - "version": (1, 1, 3), - "blender": (2, 81, 6), - "location": "File > Import-Export", - "description": "Import-Export STL files", - "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/mesh_stl.html", - "support": 'OFFICIAL', - "category": "Import-Export", -} - - -# @todo write the wiki page - -""" -Import-Export STL files (binary or ascii) - -- Import automatically remove the doubles. -- Export can export with/without modifiers applied - -Issues: - -Import: - - Does not handle endian -""" - -if "bpy" in locals(): - import importlib - if "stl_utils" in locals(): - importlib.reload(stl_utils) - if "blender_utils" in locals(): - importlib.reload(blender_utils) - -import bpy -from bpy.props import ( - StringProperty, - BoolProperty, - CollectionProperty, - EnumProperty, - FloatProperty, - FloatVectorProperty, -) -from bpy_extras.io_utils import ( - ImportHelper, - ExportHelper, - orientation_helper, - axis_conversion, -) -from bpy.types import ( - Operator, - OperatorFileListElement, -) - - -@orientation_helper(axis_forward='Y', axis_up='Z') -class ImportSTL(Operator, ImportHelper): - bl_idname = "import_mesh.stl" - bl_label = "Import STL (legacy)" - bl_description = "Load STL triangle mesh data" - bl_options = {'UNDO'} - - filename_ext = ".stl" - - filter_glob: StringProperty( - default="*.stl", - options={'HIDDEN'}, - ) - files: CollectionProperty( - name="File Path", - type=OperatorFileListElement, - ) - directory: StringProperty( - subtype='DIR_PATH', - ) - global_scale: FloatProperty( - name="Scale", - soft_min=0.001, soft_max=1000.0, - min=1e-6, max=1e6, - default=1.0, - ) - use_scene_unit: BoolProperty( - name="Scene Unit", - description="Apply current scene's unit (as defined by unit scale) to imported data", - default=False, - ) - use_facet_normal: BoolProperty( - name="Facet Normals", - description="Use (import) facet normals (note that this will still give flat shading)", - default=False, - ) - - def execute(self, context): - import os - from mathutils import Matrix - from . import stl_utils - from . import blender_utils - - paths = [os.path.join(self.directory, name.name) for name in self.files] - - scene = context.scene - - # Take into account scene's unit scale, so that 1 inch in Blender gives 1 inch elsewhere! See T42000. - global_scale = self.global_scale - if scene.unit_settings.system != 'NONE' and self.use_scene_unit: - global_scale /= scene.unit_settings.scale_length - - global_matrix = axis_conversion( - from_forward=self.axis_forward, - from_up=self.axis_up, - ).to_4x4() @ Matrix.Scale(global_scale, 4) - - if not paths: - paths.append(self.filepath) - - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='OBJECT') - - if bpy.ops.object.select_all.poll(): - bpy.ops.object.select_all(action='DESELECT') - - for path in paths: - objName = bpy.path.display_name_from_filepath(path) - tris, tri_nors, pts = stl_utils.read_stl(path) - tri_nors = tri_nors if self.use_facet_normal else None - blender_utils.create_and_link_mesh(objName, tris, tri_nors, pts, global_matrix) - - return {'FINISHED'} - - def draw(self, context): - pass - - -class STL_PT_import_transform(bpy.types.Panel): - bl_space_type = 'FILE_BROWSER' - bl_region_type = 'TOOL_PROPS' - bl_label = "Transform" - bl_parent_id = "FILE_PT_operator" - - @classmethod - def poll(cls, context): - sfile = context.space_data - operator = sfile.active_operator - - return operator.bl_idname == "IMPORT_MESH_OT_stl" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False # No animation. - - sfile = context.space_data - operator = sfile.active_operator - - layout.prop(operator, "global_scale") - layout.prop(operator, "use_scene_unit") - - layout.prop(operator, "axis_forward") - layout.prop(operator, "axis_up") - - -class STL_PT_import_geometry(bpy.types.Panel): - bl_space_type = 'FILE_BROWSER' - bl_region_type = 'TOOL_PROPS' - bl_label = "Geometry" - bl_parent_id = "FILE_PT_operator" - - @classmethod - def poll(cls, context): - sfile = context.space_data - operator = sfile.active_operator - - return operator.bl_idname == "IMPORT_MESH_OT_stl" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False # No animation. - - sfile = context.space_data - operator = sfile.active_operator - - layout.prop(operator, "use_facet_normal") - - -@orientation_helper(axis_forward='Y', axis_up='Z') -class ExportSTL(Operator, ExportHelper): - bl_idname = "export_mesh.stl" - bl_label = "Export STL (legacy)" - bl_description = """Save STL triangle mesh data""" - - filename_ext = ".stl" - filter_glob: StringProperty(default="*.stl", options={'HIDDEN'}) - - use_selection: BoolProperty( - name="Selection Only", - description="Export selected objects only", - default=False, - ) - global_scale: FloatProperty( - name="Scale", - min=0.01, max=1000.0, - default=1.0, - ) - use_scene_unit: BoolProperty( - name="Scene Unit", - description="Apply current scene's unit (as defined by unit scale) to exported data", - default=False, - ) - ascii: BoolProperty( - name="Ascii", - description="Save the file in ASCII file format", - default=False, - ) - use_mesh_modifiers: BoolProperty( - name="Apply Modifiers", - description="Apply the modifiers before saving", - default=True, - ) - batch_mode: EnumProperty( - name="Batch Mode", - items=( - ('OFF', "Off", "All data in one file"), - ('OBJECT', "Object", "Each object as a file"), - ), - ) - global_space: FloatVectorProperty( - name="Global Space", - description="Export in this reference space", - subtype='MATRIX', - size=(4, 4), - ) - - @property - def check_extension(self): - return self.batch_mode == 'OFF' - - def execute(self, context): - import os - import itertools - from mathutils import Matrix - from . import stl_utils - from . import blender_utils - - keywords = self.as_keywords( - ignore=( - "axis_forward", - "axis_up", - "use_selection", - "global_scale", - "check_existing", - "filter_glob", - "use_scene_unit", - "use_mesh_modifiers", - "batch_mode", - "global_space", - ), - ) - - scene = context.scene - if self.use_selection: - data_seq = context.selected_objects - else: - data_seq = scene.objects - - # Take into account scene's unit scale, so that 1 inch in Blender gives 1 inch elsewhere! See T42000. - global_scale = self.global_scale - if scene.unit_settings.system != 'NONE' and self.use_scene_unit: - global_scale *= scene.unit_settings.scale_length - - global_matrix = axis_conversion( - to_forward=self.axis_forward, - to_up=self.axis_up, - ).to_4x4() @ Matrix.Scale(global_scale, 4) - - if self.properties.is_property_set("global_space"): - global_matrix = global_matrix @ self.global_space.inverted() - - if self.batch_mode == 'OFF': - faces = itertools.chain.from_iterable( - blender_utils.faces_from_mesh(ob, global_matrix, self.use_mesh_modifiers) - for ob in data_seq) - - stl_utils.write_stl(faces=faces, **keywords) - elif self.batch_mode == 'OBJECT': - prefix = os.path.splitext(self.filepath)[0] - keywords_temp = keywords.copy() - for ob in data_seq: - faces = blender_utils.faces_from_mesh(ob, global_matrix, self.use_mesh_modifiers) - keywords_temp["filepath"] = prefix + bpy.path.clean_name(ob.name) + ".stl" - stl_utils.write_stl(faces=faces, **keywords_temp) - - return {'FINISHED'} - - def draw(self, context): - pass - - -class STL_PT_export_main(bpy.types.Panel): - bl_space_type = 'FILE_BROWSER' - bl_region_type = 'TOOL_PROPS' - bl_label = "" - bl_parent_id = "FILE_PT_operator" - bl_options = {'HIDE_HEADER'} - - @classmethod - def poll(cls, context): - sfile = context.space_data - operator = sfile.active_operator - - return operator.bl_idname == "EXPORT_MESH_OT_stl" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False # No animation. - - sfile = context.space_data - operator = sfile.active_operator - - layout.prop(operator, "ascii") - layout.prop(operator, "batch_mode") - - -class STL_PT_export_include(bpy.types.Panel): - bl_space_type = 'FILE_BROWSER' - bl_region_type = 'TOOL_PROPS' - bl_label = "Include" - bl_parent_id = "FILE_PT_operator" - - @classmethod - def poll(cls, context): - sfile = context.space_data - operator = sfile.active_operator - - return operator.bl_idname == "EXPORT_MESH_OT_stl" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False # No animation. - - sfile = context.space_data - operator = sfile.active_operator - - layout.prop(operator, "use_selection") - - -class STL_PT_export_transform(bpy.types.Panel): - bl_space_type = 'FILE_BROWSER' - bl_region_type = 'TOOL_PROPS' - bl_label = "Transform" - bl_parent_id = "FILE_PT_operator" - - @classmethod - def poll(cls, context): - sfile = context.space_data - operator = sfile.active_operator - - return operator.bl_idname == "EXPORT_MESH_OT_stl" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False # No animation. - - sfile = context.space_data - operator = sfile.active_operator - - layout.prop(operator, "global_scale") - layout.prop(operator, "use_scene_unit") - - layout.prop(operator, "axis_forward") - layout.prop(operator, "axis_up") - - -class STL_PT_export_geometry(bpy.types.Panel): - bl_space_type = 'FILE_BROWSER' - bl_region_type = 'TOOL_PROPS' - bl_label = "Geometry" - bl_parent_id = "FILE_PT_operator" - - @classmethod - def poll(cls, context): - sfile = context.space_data - operator = sfile.active_operator - - return operator.bl_idname == "EXPORT_MESH_OT_stl" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False # No animation. - - sfile = context.space_data - operator = sfile.active_operator - - layout.prop(operator, "use_mesh_modifiers") - - -def menu_import(self, context): - self.layout.operator(ImportSTL.bl_idname, text="Stl (.stl) (legacy)", icon="LOAD_STL") #BFA - icon added - - -def menu_export(self, context): - self.layout.operator(ExportSTL.bl_idname, text="Stl (.stl) (legacy)", icon="SAVE_STL") #BFA - icon added - - -classes = ( - ImportSTL, - STL_PT_import_transform, - STL_PT_import_geometry, - ExportSTL, - STL_PT_export_main, - STL_PT_export_include, - STL_PT_export_transform, - STL_PT_export_geometry, -) - - -def register(): - for cls in classes: - bpy.utils.register_class(cls) - - bpy.types.TOPBAR_MT_file_import.append(menu_import) - bpy.types.TOPBAR_MT_file_export.append(menu_export) - - -def unregister(): - for cls in classes: - bpy.utils.unregister_class(cls) - - bpy.types.TOPBAR_MT_file_import.remove(menu_import) - bpy.types.TOPBAR_MT_file_export.remove(menu_export) - - -if __name__ == "__main__": - register() diff --git a/scripts/addons_core/io_mesh_stl/blender_utils.py b/scripts/addons_core/io_mesh_stl/blender_utils.py deleted file mode 100644 index 1b1fe55b0b3b..000000000000 --- a/scripts/addons_core/io_mesh_stl/blender_utils.py +++ /dev/null @@ -1,96 +0,0 @@ -# SPDX-FileCopyrightText: 2010-2022 Blender Foundation -# -# SPDX-License-Identifier: GPL-2.0-or-later - - -def create_and_link_mesh(name, faces, face_nors, points, global_matrix): - """ - Create a blender mesh and object called name from a list of - *points* and *faces* and link it in the current scene. - """ - - import array - from itertools import chain - import bpy - - mesh = bpy.data.meshes.new(name) - mesh.from_pydata(points, [], faces) - - if face_nors: - # Write imported normals to a temporary attribute so they are interpolated by #mesh.validate(). - # It's important to validate before calling #mesh.normals_split_custom_set() which expects a - # valid mesh. - lnors = tuple(chain(*chain(*zip(face_nors, face_nors, face_nors)))) - mesh.attributes.new("temp_custom_normals", 'FLOAT_VECTOR', 'CORNER') - mesh.attributes["temp_custom_normals"].data.foreach_set("vector", lnors) - - mesh.transform(global_matrix) - - # update mesh to allow proper display - mesh.validate(clean_customdata=False) # *Very* important to not remove lnors here! - - if face_nors: - clnors = array.array('f', [0.0] * (len(mesh.loops) * 3)) - mesh.attributes["temp_custom_normals"].data.foreach_get("vector", clnors) - - mesh.polygons.foreach_set("use_smooth", [True] * len(mesh.polygons)) - - mesh.normals_split_custom_set(tuple(zip(*(iter(clnors),) * 3))) - mesh.attributes.remove(mesh.attributes["temp_custom_normals"]) - - mesh.update() - - obj = bpy.data.objects.new(name, mesh) - bpy.context.collection.objects.link(obj) - bpy.context.view_layer.objects.active = obj - obj.select_set(True) - - -def faces_from_mesh(ob, global_matrix, use_mesh_modifiers=False): - """ - From an object, return a generator over a list of faces. - - Each faces is a list of his vertices. Each vertex is a tuple of - his coordinate. - - use_mesh_modifiers - Apply the preview modifier to the returned liste - - triangulate - Split the quad into two triangles - """ - - import bpy - - # get the editmode data - if ob.mode == "EDIT": - ob.update_from_editmode() - - # get the modifiers - if use_mesh_modifiers: - depsgraph = bpy.context.evaluated_depsgraph_get() - mesh_owner = ob.evaluated_get(depsgraph) - else: - mesh_owner = ob - - # Object.to_mesh() is not guaranteed to return a mesh. - try: - mesh = mesh_owner.to_mesh() - except RuntimeError: - return - - if mesh is None: - return - - mat = global_matrix @ ob.matrix_world - mesh.transform(mat) - if mat.is_negative: - mesh.flip_normals() - mesh.calc_loop_triangles() - - vertices = mesh.vertices - - for tri in mesh.loop_triangles: - yield [vertices[index].co.copy() for index in tri.vertices] - - mesh_owner.to_mesh_clear() diff --git a/scripts/addons_core/io_mesh_stl/stl_utils.py b/scripts/addons_core/io_mesh_stl/stl_utils.py deleted file mode 100644 index f0e5cbbdde18..000000000000 --- a/scripts/addons_core/io_mesh_stl/stl_utils.py +++ /dev/null @@ -1,279 +0,0 @@ -# SPDX-FileCopyrightText: 2010-2022 Blender Foundation -# -# SPDX-License-Identifier: GPL-2.0-or-later - -""" -Import and export STL files - -Used as a blender script, it load all the stl files in the scene: - -blender --python stl_utils.py -- file1.stl file2.stl file3.stl ... -""" - -# TODO: endian - - -class ListDict(dict): - """ - Set struct with order. - - You can: - - insert data into without doubles - - get the list of data in insertion order with self.list - - Like collections.OrderedDict, but quicker, can be replaced if - ODict is optimised. - """ - - def __init__(self): - dict.__init__(self) - self.list = [] - self._len = 0 - - def add(self, item): - """ - Add a value to the Set, return its position in it. - """ - value = self.setdefault(item, self._len) - if value == self._len: - self.list.append(item) - self._len += 1 - - return value - - -# an stl binary file is -# - 80 bytes of description -# - 4 bytes of size (unsigned int) -# - size triangles : -# -# - 12 bytes of normal -# - 9 * 4 bytes of coordinate (3*3 floats) -# - 2 bytes of garbage (usually 0) -BINARY_HEADER = 80 -BINARY_STRIDE = 12 * 4 + 2 - - -def _header_version(): - import bpy - return "Exported from Blender-" + bpy.app.version_string - - -def _is_ascii_file(data): - """ - This function returns True if the data represents an ASCII file. - - Please note that a False value does not necessary means that the data - represents a binary file. It can be a (very *RARE* in real life, but - can easily be forged) ascii file. - """ - - import os - import struct - - # Skip header... - data.seek(BINARY_HEADER) - size = struct.unpack('>> tris, tri_nors, pts = read_stl(filepath) - >>> pts = list(pts) - >>> - >>> # print the coordinate of the triangle n - >>> print(pts[i] for i in tris[n]) - """ - import time - start_time = time.process_time() - - tris, tri_nors, pts = [], [], ListDict() - - with open(filepath, 'rb') as data: - # check for ascii or binary - gen = _ascii_read if _is_ascii_file(data) else _binary_read - - for nor, pt in gen(data): - # Add the triangle and the point. - # If the point is already in the list of points, the - # index returned by pts.add() will be the one from the - # first equal point inserted. - tris.append([pts.add(p) for p in pt]) - tri_nors.append(nor) - - print('Import finished in %.4f sec.' % (time.process_time() - start_time)) - - return tris, tri_nors, pts.list - - -if __name__ == '__main__': - import sys - import bpy - from io_mesh_stl import blender_utils - - filepaths = sys.argv[sys.argv.index('--') + 1:] - - for filepath in filepaths: - objName = bpy.path.display_name(filepath) - tris, pts = read_stl(filepath) - - blender_utils.create_and_link_mesh(objName, tris, pts) diff --git a/scripts/addons_core/bfa_default_addons/Default_Extensions/stl_format_legacy/__init__.py b/scripts/addons_core/stl_format_legacy/__init__.py similarity index 99% rename from scripts/addons_core/bfa_default_addons/Default_Extensions/stl_format_legacy/__init__.py rename to scripts/addons_core/stl_format_legacy/__init__.py index 8d711300eca0..6395b81b7735 100644 --- a/scripts/addons_core/bfa_default_addons/Default_Extensions/stl_format_legacy/__init__.py +++ b/scripts/addons_core/stl_format_legacy/__init__.py @@ -403,11 +403,11 @@ def draw(self, context): def menu_import(self, context): - self.layout.operator(ImportSTL.bl_idname, text="Stl (.stl) (legacy)") + self.layout.operator(ImportSTL.bl_idname, text="Stl (.stl) (legacy)", icon = "LOAD_STL") #BFA - icon added def menu_export(self, context): - self.layout.operator(ExportSTL.bl_idname, text="Stl (.stl) (legacy)") + self.layout.operator(ExportSTL.bl_idname, text="Stl (.stl) (legacy)", icon = "SAVE_STL") #BFA - icon added classes = ( diff --git a/scripts/addons_core/bfa_default_addons/Default_Extensions/stl_format_legacy/blender_manifest.toml b/scripts/addons_core/stl_format_legacy/blender_manifest.toml similarity index 85% rename from scripts/addons_core/bfa_default_addons/Default_Extensions/stl_format_legacy/blender_manifest.toml rename to scripts/addons_core/stl_format_legacy/blender_manifest.toml index f22897e2272b..343a8f3b0514 100644 --- a/scripts/addons_core/bfa_default_addons/Default_Extensions/stl_format_legacy/blender_manifest.toml +++ b/scripts/addons_core/stl_format_legacy/blender_manifest.toml @@ -2,7 +2,7 @@ schema_version = "1.0.0" id = "stl_format_legacy" name = "STL format (legacy)" version = "1.1.3" -tagline = "Import-Export STL files" +tagline = "Import-Export STL files - Bforartists Version" maintainer = "Community" type = "add-on" tags = ["Import-Export"] diff --git a/scripts/addons_core/bfa_default_addons/Default_Extensions/stl_format_legacy/blender_utils.py b/scripts/addons_core/stl_format_legacy/blender_utils.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Extensions/stl_format_legacy/blender_utils.py rename to scripts/addons_core/stl_format_legacy/blender_utils.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Extensions/stl_format_legacy/stl_utils.py b/scripts/addons_core/stl_format_legacy/stl_utils.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Extensions/stl_format_legacy/stl_utils.py rename to scripts/addons_core/stl_format_legacy/stl_utils.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/__init__.py b/scripts/addons_core/viewport_pie_menus/__init__.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/__init__.py rename to scripts/addons_core/viewport_pie_menus/__init__.py diff --git a/scripts/addons_core/viewport_pie_menus/blender_manifest.toml b/scripts/addons_core/viewport_pie_menus/blender_manifest.toml new file mode 100644 index 000000000000..a652c69c5c00 --- /dev/null +++ b/scripts/addons_core/viewport_pie_menus/blender_manifest.toml @@ -0,0 +1,12 @@ +schema_version = "1.0.0" +id = "viewport_pie_menus" +name = "3D Viewport Pie Menus - Bforartists Version" +version = "1.3.0" +tagline = "Pie Menu Activation" +maintainer = "Community" +type = "add-on" +tags = ["User Interface"] +blender_version_min = "4.2.0" +license = ["SPDX:GPL-2.0-or-later"] +website = "https://projects.blender.org/extensions/space_view3d_pie_menus" +copyright = ["2024 meta-androcto"] diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_align_menu.py b/scripts/addons_core/viewport_pie_menus/pie_align_menu.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_align_menu.py rename to scripts/addons_core/viewport_pie_menus/pie_align_menu.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_animation_menu.py b/scripts/addons_core/viewport_pie_menus/pie_animation_menu.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_animation_menu.py rename to scripts/addons_core/viewport_pie_menus/pie_animation_menu.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_apply_transform_menu.py b/scripts/addons_core/viewport_pie_menus/pie_apply_transform_menu.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_apply_transform_menu.py rename to scripts/addons_core/viewport_pie_menus/pie_apply_transform_menu.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_defaults_menu.py b/scripts/addons_core/viewport_pie_menus/pie_defaults_menu.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_defaults_menu.py rename to scripts/addons_core/viewport_pie_menus/pie_defaults_menu.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_delete_menu.py b/scripts/addons_core/viewport_pie_menus/pie_delete_menu.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_delete_menu.py rename to scripts/addons_core/viewport_pie_menus/pie_delete_menu.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_editor_switch_menu.py b/scripts/addons_core/viewport_pie_menus/pie_editor_switch_menu.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_editor_switch_menu.py rename to scripts/addons_core/viewport_pie_menus/pie_editor_switch_menu.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_manipulator_menu.py b/scripts/addons_core/viewport_pie_menus/pie_manipulator_menu.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_manipulator_menu.py rename to scripts/addons_core/viewport_pie_menus/pie_manipulator_menu.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_modes_menu.py b/scripts/addons_core/viewport_pie_menus/pie_modes_menu.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_modes_menu.py rename to scripts/addons_core/viewport_pie_menus/pie_modes_menu.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_origin.py b/scripts/addons_core/viewport_pie_menus/pie_origin.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_origin.py rename to scripts/addons_core/viewport_pie_menus/pie_origin.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_proportional_menu.py b/scripts/addons_core/viewport_pie_menus/pie_proportional_menu.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_proportional_menu.py rename to scripts/addons_core/viewport_pie_menus/pie_proportional_menu.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_save_open_menu.py b/scripts/addons_core/viewport_pie_menus/pie_save_open_menu.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_save_open_menu.py rename to scripts/addons_core/viewport_pie_menus/pie_save_open_menu.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_sculpt_menu.py b/scripts/addons_core/viewport_pie_menus/pie_sculpt_menu.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_sculpt_menu.py rename to scripts/addons_core/viewport_pie_menus/pie_sculpt_menu.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_select_menu.py b/scripts/addons_core/viewport_pie_menus/pie_select_menu.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_select_menu.py rename to scripts/addons_core/viewport_pie_menus/pie_select_menu.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_shading_menu.py b/scripts/addons_core/viewport_pie_menus/pie_shading_menu.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_shading_menu.py rename to scripts/addons_core/viewport_pie_menus/pie_shading_menu.py diff --git a/scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_views_numpad_menu.py b/scripts/addons_core/viewport_pie_menus/pie_views_numpad_menu.py similarity index 100% rename from scripts/addons_core/bfa_default_addons/Default_Addons/space_view3d_pie_menus/pie_views_numpad_menu.py rename to scripts/addons_core/viewport_pie_menus/pie_views_numpad_menu.py