diff --git a/io_scene_fbx/__init__.py b/io_scene_fbx/__init__.py index bae3e6cc3823f0a54bd6ed514aefd53a7c5ce3ca..bbdcd9ef36931b7ebaf29b980a146101327235db 100644 --- a/io_scene_fbx/__init__.py +++ b/io_scene_fbx/__init__.py @@ -21,8 +21,8 @@ bl_info = { "name": "FBX format", "author": "Campbell Barton, Bastien Montagne, Jens Restemeier", - "version": (3, 10, 0), - "blender": (2, 79, 1), + "version": (4, 10, 0), + "blender": (2, 80, 0), "location": "File > Import-Export", "description": "FBX IO meshes, UV's, vertex colors, materials, textures, cameras, lamps and actions", "warning": "", @@ -67,12 +67,12 @@ class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper): bl_label = "Import FBX" bl_options = {'UNDO', 'PRESET'} - directory = StringProperty() + directory: StringProperty() filename_ext = ".fbx" - filter_glob = StringProperty(default="*.fbx", options={'HIDDEN'}) + filter_glob: StringProperty(default="*.fbx", options={'HIDDEN'}) - ui_tab = EnumProperty( + ui_tab: EnumProperty( items=(('MAIN', "Main", "Main basic settings"), ('ARMATURE', "Armatures", "Armature-related settings"), ), @@ -80,17 +80,17 @@ class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper): description="Import options categories", ) - use_manual_orientation = BoolProperty( + use_manual_orientation: BoolProperty( name="Manual Orientation", description="Specify orientation and scale, instead of using embedded data in FBX file", default=False, ) - global_scale = FloatProperty( + global_scale: FloatProperty( name="Scale", min=0.001, max=1000.0, default=1.0, ) - bake_space_transform = BoolProperty( + bake_space_transform: BoolProperty( name="!EXPERIMENTAL! Apply Transform", description="Bake space transform into object data, avoids getting unwanted rotations to objects when " "target space is not aligned with Blender's space " @@ -98,69 +98,69 @@ class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper): default=False, ) - use_custom_normals = BoolProperty( + use_custom_normals: BoolProperty( name="Import Normals", description="Import custom normals, if available (otherwise Blender will recompute them)", default=True, ) - use_image_search = BoolProperty( + use_image_search: BoolProperty( name="Image Search", description="Search subdirs for any associated images (WARNING: may be slow)", default=True, ) - use_alpha_decals = BoolProperty( + use_alpha_decals: BoolProperty( name="Alpha Decals", description="Treat materials with alpha as decals (no shadow casting)", default=False, ) - decal_offset = FloatProperty( + decal_offset: FloatProperty( name="Decal Offset", description="Displace geometry of alpha meshes", min=0.0, max=1.0, default=0.0, ) - use_anim = BoolProperty( + use_anim: BoolProperty( name="Import Animation", description="Import FBX animation", default=True, ) - anim_offset = FloatProperty( + anim_offset: FloatProperty( name="Animation Offset", description="Offset to apply to animation during import, in frames", default=1.0, ) - use_custom_props = BoolProperty( + use_custom_props: BoolProperty( name="Import User Properties", description="Import user properties as custom properties", default=True, ) - use_custom_props_enum_as_string = BoolProperty( + use_custom_props_enum_as_string: BoolProperty( name="Import Enums As Strings", description="Store enumeration values as strings", default=True, ) - ignore_leaf_bones = BoolProperty( + ignore_leaf_bones: BoolProperty( name="Ignore Leaf Bones", description="Ignore the last bone at the end of each chain (used to mark the length of the previous bone)", default=False, ) - force_connect_children = BoolProperty( + force_connect_children: BoolProperty( name="Force Connect Children", description="Force connection of children bones to their parent, even if their computed head/tail " "positions do not match (can be useful with pure-joints-type armatures)", default=False, ) - automatic_bone_orientation = BoolProperty( + automatic_bone_orientation: BoolProperty( name="Automatic Bone Orientation", description="Try to align the major bone axis with the bone children", default=False, ) - primary_bone_axis = EnumProperty( + primary_bone_axis: EnumProperty( name="Primary Bone Axis", items=(('X', "X Axis", ""), ('Y', "Y Axis", ""), @@ -171,7 +171,7 @@ class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper): ), default='Y', ) - secondary_bone_axis = EnumProperty( + secondary_bone_axis: EnumProperty( name="Secondary Bone Axis", items=(('X', "X Axis", ""), ('Y', "Y Axis", ""), @@ -183,7 +183,7 @@ class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper): default='X', ) - use_prepost_rot = BoolProperty( + use_prepost_rot: BoolProperty( name="Use Pre/Post Rotation", description="Use pre/post rotation from FBX transform (you may have to disable that in some cases)", default=True, @@ -228,7 +228,8 @@ class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper): def execute(self, context): keywords = self.as_keywords(ignore=("filter_glob", "directory", "ui_tab")) - keywords["use_cycles"] = (context.scene.render.engine == 'CYCLES') + # XXX TODO get rid of this, EEVEE/Cycles use same nodal system... + keywords["use_cycles"] = True #(context.scene.render.engine == 'CYCLES') from . import import_fbx return import_fbx.load(self, context, **keywords) @@ -241,12 +242,12 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): bl_options = {'UNDO', 'PRESET'} filename_ext = ".fbx" - filter_glob = StringProperty(default="*.fbx", options={'HIDDEN'}) + filter_glob: StringProperty(default="*.fbx", options={'HIDDEN'}) # List of operator properties, the attributes will be assigned # to the class instance from the operator settings before calling. - ui_tab = EnumProperty( + ui_tab: EnumProperty( items=(('MAIN', "Main", "Main basic settings"), ('GEOMETRY', "Geometries", "Geometry-related settings"), ('ARMATURE', "Armatures", "Armature-related settings"), @@ -256,24 +257,24 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): description="Export options categories", ) - use_selection = BoolProperty( + use_selection: BoolProperty( name="Selected Objects", description="Export selected objects on visible layers", default=False, ) - global_scale = FloatProperty( + global_scale: FloatProperty( name="Scale", description="Scale all data (Some importers do not support scaled armatures!)", min=0.001, max=1000.0, soft_min=0.01, soft_max=1000.0, default=1.0, ) - apply_unit_scale = BoolProperty( + apply_unit_scale: BoolProperty( name="Apply Unit", description="Take into account current Blender units settings (if unset, raw Blender Units values are used as-is)", default=True, ) - apply_scale_options = EnumProperty( + apply_scale_options: EnumProperty( items=(('FBX_SCALE_NONE', "All Local", "Apply custom scaling and units scaling to each object transformation, FBX scale remains at 1.0"), ('FBX_SCALE_UNITS', "FBX Units Scale", @@ -288,7 +289,7 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): "(Blender uses FBX scale to detect units on import, " "but many other applications do not handle the same way)", ) - bake_space_transform = BoolProperty( + bake_space_transform: BoolProperty( name="!EXPERIMENTAL! Apply Transform", description="Bake space transform into object data, avoids getting unwanted rotations to objects when " "target space is not aligned with Blender's space " @@ -296,7 +297,7 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): default=False, ) - object_types = EnumProperty( + object_types: EnumProperty( name="Object Types", options={'ENUM_FLAG'}, items=(('EMPTY', "Empty", ""), @@ -310,18 +311,18 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): default={'EMPTY', 'CAMERA', 'LIGHT', 'ARMATURE', 'MESH', 'OTHER'}, ) - use_mesh_modifiers = BoolProperty( + use_mesh_modifiers: BoolProperty( name="Apply Modifiers", description="Apply modifiers to mesh objects (except Armature ones) - " "WARNING: prevents exporting shape keys", default=True, ) - use_mesh_modifiers_render = BoolProperty( + use_mesh_modifiers_render: BoolProperty( name="Use Modifiers Render Setting", description="Use render settings when applying modifiers to mesh objects", default=True, ) - mesh_smooth_type = EnumProperty( + mesh_smooth_type: EnumProperty( name="Smoothing", items=(('OFF', "Normals Only", "Export only normals instead of writing edge or face smoothing data"), ('FACE', "Face", "Write face smoothing"), @@ -331,29 +332,29 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): "(prefer 'Normals Only' option if your target importer understand split normals)", default='OFF', ) - use_mesh_edges = BoolProperty( + use_mesh_edges: BoolProperty( name="Loose Edges", description="Export loose edges (as two-vertices polygons)", default=False, ) - use_tspace = BoolProperty( + use_tspace: BoolProperty( name="Tangent Space", description="Add binormal and tangent vectors, together with normal they form the tangent space " "(will only work correctly with tris/quads only meshes!)", default=False, ) - use_custom_props = BoolProperty( + use_custom_props: BoolProperty( name="Custom Properties", description="Export custom properties", default=False, ) - add_leaf_bones = BoolProperty( + add_leaf_bones: BoolProperty( name="Add Leaf Bones", description="Append a final bone to the end of each chain to specify last bone length " "(use this when you intend to edit the armature from exported data)", default=True # False for commit! ) - primary_bone_axis = EnumProperty( + primary_bone_axis: EnumProperty( name="Primary Bone Axis", items=(('X', "X Axis", ""), ('Y', "Y Axis", ""), @@ -364,7 +365,7 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): ), default='Y', ) - secondary_bone_axis = EnumProperty( + secondary_bone_axis: EnumProperty( name="Secondary Bone Axis", items=(('X', "X Axis", ""), ('Y', "Y Axis", ""), @@ -375,12 +376,12 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): ), default='X', ) - use_armature_deform_only = BoolProperty( + use_armature_deform_only: BoolProperty( name="Only Deform Bones", description="Only write deforming bones (and non-deforming ones when they have deforming children)", default=False, ) - armature_nodetype = EnumProperty( + armature_nodetype: EnumProperty( name="Armature FBXNode Type", items=(('NULL', "Null", "'Null' FBX node, similar to Blender's Empty (default)"), ('ROOT', "Root", "'Root' FBX node, supposed to be the root of chains of bones..."), @@ -391,43 +392,43 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): "perfectly in Blender...)", default='NULL', ) - bake_anim = BoolProperty( + bake_anim: BoolProperty( name="Baked Animation", description="Export baked keyframe animation", default=True, ) - bake_anim_use_all_bones = BoolProperty( + bake_anim_use_all_bones: BoolProperty( name="Key All Bones", description="Force exporting at least one key of animation for all bones " "(needed with some target applications, like UE4)", default=True, ) - bake_anim_use_nla_strips = BoolProperty( + bake_anim_use_nla_strips: BoolProperty( name="NLA Strips", description="Export each non-muted NLA strip as a separated FBX's AnimStack, if any, " "instead of global scene animation", default=True, ) - bake_anim_use_all_actions = BoolProperty( + bake_anim_use_all_actions: BoolProperty( name="All Actions", description="Export each action as a separated FBX's AnimStack, instead of global scene animation " "(note that animated objects will get all actions compatible with them, " "others will get no animation at all)", default=True, ) - bake_anim_force_startend_keying = BoolProperty( + bake_anim_force_startend_keying: BoolProperty( name="Force Start/End Keying", description="Always add a keyframe at start and end of actions for animated channels", default=True, ) - bake_anim_step = FloatProperty( + bake_anim_step: FloatProperty( name="Sampling Rate", description="How often to evaluate animated values (in frames)", min=0.01, max=100.0, soft_min=0.1, soft_max=10.0, default=1.0, ) - bake_anim_simplify_factor = FloatProperty( + bake_anim_simplify_factor: FloatProperty( name="Simplify", description="How much to simplify baked values (0.0 to disable, the higher the more simplified)", min=0.0, max=100.0, # No simplification to up to 10% of current magnitude tolerance. @@ -435,24 +436,24 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): default=1.0, # default: min slope: 0.005, max frame step: 10. ) path_mode = path_reference_mode - embed_textures = BoolProperty( + embed_textures: BoolProperty( name="Embed Textures", description="Embed textures in FBX binary file (only for \"Copy\" path mode!)", default=False, ) - batch_mode = EnumProperty( + batch_mode: EnumProperty( name="Batch Mode", items=(('OFF', "Off", "Active scene to file"), ('SCENE', "Scene", "Each scene as a file"), ('GROUP', "Group", "Each group as a file"), ), ) - use_batch_own_dir = BoolProperty( + use_batch_own_dir: BoolProperty( name="Batch Own Dir", description="Create a dir for each exported file", default=True, ) - use_metadata = BoolProperty( + use_metadata: BoolProperty( name="Use Metadata", default=True, options={'HIDDEN'}, diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py index 09638c5d09d8a91fb0c8af0b716e75e66001302b..3ed0d7a51bf02da93e0124df29b8c8f830ec5a72 100644 --- a/io_scene_fbx/export_fbx_bin.py +++ b/io_scene_fbx/export_fbx_bin.py @@ -585,8 +585,8 @@ def fbx_data_light_elements(root, lamp, scene_data): if lamp.type not in {'HEMI'}: if lamp.type not in {'SUN', 'AREA'}: decay_type = FBX_LIGHT_DECAY_TYPES[lamp.falloff_type] - do_light = (not lamp.use_only_shadow) and (lamp.use_specular or lamp.use_diffuse) - do_shadow = lamp.shadow_method not in {'NOSHADOW'} + do_light = True + do_shadow = lamp.use_shadow shadow_color = lamp.shadow_color light = elem_data_single_int64(root, b"NodeAttribute", get_fbx_uuid_from_key(light_key)) @@ -629,8 +629,8 @@ def fbx_data_camera_elements(root, cam_obj, scene_data): # Real data now, good old camera! # Object transform info. loc, rot, scale, matrix, matrix_rot = cam_obj.fbx_object_tx(scene_data) - up = matrix_rot * Vector((0.0, 1.0, 0.0)) - to = matrix_rot * Vector((0.0, 0.0, -1.0)) + up = matrix_rot @ Vector((0.0, 1.0, 0.0)) + to = matrix_rot @ Vector((0.0, 0.0, -1.0)) # Render settings. # TODO We could export much more... render = scene_data.scene.render @@ -1206,7 +1206,8 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes): def check_skip_material(mat): """Simple helper to check whether we actually support exporting that material or not""" - return mat.type not in {'SURFACE'} + ### TODO Fix node-to-simpleshader issue... + return True or mat.type not in {'SURFACE'} def fbx_data_material_elements(root, mat, scene_data): @@ -1215,7 +1216,7 @@ def fbx_data_material_elements(root, mat, scene_data): """ ambient_color = (0.0, 0.0, 0.0) if scene_data.data_world: - ambient_color = next(iter(scene_data.data_world.keys())).ambient_color + ambient_color = next(iter(scene_data.data_world.keys())).color mat_key, _objs = scene_data.data_materials[mat] skip_mat = check_skip_material(mat) @@ -1497,7 +1498,7 @@ def fbx_data_armature_elements(root, arm_obj, scene_data): # http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/why-the-values-return- # by-fbxcluster-gettransformmatrix-x-not-same-with-the-value-in-ascii-fbx-file/ elem_data_single_float64_array(fbx_clstr, b"Transform", - matrix4_to_array(mat_world_bones[bo_obj].inverted_safe() * mat_world_obj)) + matrix4_to_array(mat_world_bones[bo_obj].inverted_safe() @ mat_world_obj)) elem_data_single_float64_array(fbx_clstr, b"TransformLink", matrix4_to_array(mat_world_bones[bo_obj])) elem_data_single_float64_array(fbx_clstr, b"TransformAssociateModel", matrix4_to_array(mat_world_arm)) @@ -1849,9 +1850,9 @@ def fbx_generate_leaf_bones(settings, data_bones): bone_length = (parent.bdata.tail_local - parent.bdata.head_local).length matrix = Matrix.Translation((0, bone_length, 0)) if settings.bone_correction_matrix_inv: - matrix = settings.bone_correction_matrix_inv * matrix + matrix = settings.bone_correction_matrix_inv @ matrix if settings.bone_correction_matrix: - matrix = matrix * settings.bone_correction_matrix + matrix = matrix @ settings.bone_correction_matrix leaf_bones.append((node_name, parent_uuid, node_uuid, attr_uuid, matrix, hide, size)) return leaf_bones @@ -1864,6 +1865,7 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No bake_step = scene_data.settings.bake_anim_step simplify_fac = scene_data.settings.bake_anim_simplify_factor scene = scene_data.scene + depsgraph = scene_data.depsgraph force_keying = scene_data.settings.bake_anim_use_all_bones force_sek = scene_data.settings.bake_anim_force_startend_keying @@ -1874,11 +1876,9 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No continue if ob_obj.type == 'ARMATURE': objects |= {bo_obj for bo_obj in ob_obj.bones if bo_obj in scene_data.objects} - ob_obj.dupli_list_create(scene, 'RENDER') - for dp_obj in ob_obj.dupli_list: + for dp_obj in ob_obj.dupli_list_gen(depsgraph): if dp_obj in scene_data.objects: objects.add(dp_obj) - ob_obj.dupli_list_clear() else: objects = scene_data.objects @@ -1920,10 +1920,10 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No currframe = f_start while currframe <= f_end: real_currframe = currframe - f_start if start_zero else currframe - scene.frame_set(int(currframe), currframe - int(currframe)) + scene.frame_set(int(currframe), subframe=currframe - int(currframe)) - for ob_obj in animdata_ob: - ob_obj.dupli_list_create(scene, 'RENDER') + for dp_obj in ob_obj.dupli_list_gen(depsgraph): + pass # Merely updating dupli matrix of ObjectWrapper... for ob_obj, (anim_loc, anim_rot, anim_scale) in animdata_ob.items(): # We compute baked loc/rot/scale for all objects (rot being euler-compat with previous value!). p_rot = p_rots.get(ob_obj, None) @@ -1932,15 +1932,13 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No anim_loc.add_keyframe(real_currframe, loc) anim_rot.add_keyframe(real_currframe, tuple(convert_rad_to_deg_iter(rot))) anim_scale.add_keyframe(real_currframe, scale) - for ob_obj in objects: - ob_obj.dupli_list_clear() for anim_shape, me, shape in animdata_shapes.values(): anim_shape.add_keyframe(real_currframe, (shape.value * 100.0,)) for anim_camera, camera in animdata_cameras.values(): anim_camera.add_keyframe(real_currframe, (camera.lens,)) currframe += bake_step - scene.frame_set(back_currframe, 0.0) + scene.frame_set(back_currframe, subframe=0.0) animations = OrderedDict() @@ -2045,7 +2043,7 @@ def fbx_animations(scene_data): add_anim(animations, animated, fbx_animations_do(scene_data, strip, strip.frame_start, strip.frame_end, True, force_keep=True)) strip.mute = True - scene.frame_set(scene.frame_current, 0.0) + scene.frame_set(scene.frame_current, subframe=0.0) for strip in strips: strip.mute = False @@ -2074,7 +2072,7 @@ def fbx_animations(scene_data): 'lock_location', 'lock_rotation', 'lock_rotation_w', 'lock_rotations_4d', 'lock_scale', 'tag', 'layers', 'select', 'track_axis', 'up_axis', 'active_material', 'active_material_index', 'matrix_parent_inverse', 'empty_display_type', 'empty_display_size', 'empty_image_offset', 'pass_index', - 'color', 'hide', 'hide_select', 'hide_render', 'use_slow_parent', 'slow_parent_offset', + 'color', 'hide_viewport', 'hide_select', 'hide_render', 'use_slow_parent', 'slow_parent_offset', 'use_extra_recalc_object', 'use_extra_recalc_data', 'dupli_type', 'use_dupli_frames_speed', 'use_dupli_vertices_rotation', 'use_dupli_faces_scale', 'dupli_faces_scale', 'dupli_group', 'dupli_frames_start', 'dupli_frames_end', 'dupli_frames_on', 'dupli_frames_off', @@ -2124,7 +2122,7 @@ def fbx_animations(scene_data): pbo.matrix_basis = mat.copy() ob.animation_data.action = org_act restore_object(ob, ob_copy) - scene.frame_set(scene.frame_current, 0.0) + scene.frame_set(scene.frame_current, subframe=0.0) if pbones_matrices is not ...: for pbo, mat in zip(ob.pose.bones, pbones_matrices): @@ -2132,19 +2130,19 @@ def fbx_animations(scene_data): ob.animation_data.action = org_act bpy.data.objects.remove(ob_copy) - scene.frame_set(scene.frame_current, 0.0) + scene.frame_set(scene.frame_current, subframe=0.0) # Global (containing everything) animstack, only if not exporting NLA strips and/or all actions. if not scene_data.settings.bake_anim_use_nla_strips and not scene_data.settings.bake_anim_use_all_actions: add_anim(animations, animated, fbx_animations_do(scene_data, None, scene.frame_start, scene.frame_end, False)) # Be sure to update all matrices back to org state! - scene.frame_set(scene.frame_current, 0.0) + scene.frame_set(scene.frame_current, subframe=0.0) return animations, animated, frame_start, frame_end -def fbx_data_from_scene(scene, settings): +def fbx_data_from_scene(scene, depsgraph, settings): """ Do some pre-processing over scene's data... """ @@ -2166,12 +2164,10 @@ def fbx_data_from_scene(scene, settings): ob_obj = ObjectWrapper(ob) objects[ob_obj] = None # Duplis... - ob_obj.dupli_list_create(scene, 'RENDER') - for dp_obj in ob_obj.dupli_list: + for dp_obj in ob_obj.dupli_list_gen(depsgraph): if dp_obj.type not in dp_objtypes: continue objects[dp_obj] = None - ob_obj.dupli_list_clear() perfmon.step("FBX export prepare: Wrapping Data (lamps, cameras, empties)...") @@ -2375,7 +2371,7 @@ def fbx_data_from_scene(scene, settings): # Kind of hack, we need a temp scene_data for object's space handling to bake animations... tmp_scdata = FBXExportData( None, None, None, - settings, scene, objects, None, None, 0.0, 0.0, + settings, scene, depsgraph, objects, None, None, 0.0, 0.0, data_empties, data_lights, data_cameras, data_meshes, None, data_bones, data_leaf_bones, data_deformers_skin, data_deformers_shape, data_world, data_materials, data_textures, data_videos, @@ -2593,7 +2589,7 @@ def fbx_data_from_scene(scene, settings): return FBXExportData( templates, templates_users, connections, - settings, scene, objects, animations, animated, frame_start, frame_end, + settings, scene, depsgraph, objects, animations, animated, frame_start, frame_end, data_empties, data_lights, data_cameras, data_meshes, mesh_mat_indices, data_bones, data_leaf_bones, data_deformers_skin, data_deformers_shape, data_world, data_materials, data_textures, data_videos, @@ -2827,12 +2823,10 @@ def fbx_objects_elements(root, scene_data): if ob_obj.is_dupli: continue fbx_data_object_elements(objects, ob_obj, scene_data) - ob_obj.dupli_list_create(scene_data.scene, 'RENDER') - for dp_obj in ob_obj.dupli_list: + for dp_obj in ob_obj.dupli_list_gen(scene_data.depsgraph): if dp_obj not in scene_data.objects: continue fbx_data_object_elements(objects, dp_obj, scene_data) - ob_obj.dupli_list_clear() perfmon.step("FBX export fetch remaining...") @@ -2897,7 +2891,7 @@ def fbx_takes_elements(root, scene_data): # ##### "Main" functions. ##### # This func can be called with just the filepath -def save_single(operator, scene, filepath="", +def save_single(operator, scene, depsgraph, filepath="", global_matrix=Matrix(), apply_unit_scale=False, global_scale=1.0, @@ -2943,12 +2937,12 @@ def save_single(operator, scene, filepath="", # Default Blender unit is equivalent to meter, while FBX one is centimeter... unit_scale = units_blender_to_fbx_factor(scene) if apply_unit_scale else 100.0 if apply_scale_options == 'FBX_SCALE_NONE': - global_matrix = Matrix.Scale(unit_scale * global_scale, 4) * global_matrix + global_matrix = Matrix.Scale(unit_scale * global_scale, 4) @ global_matrix unit_scale = 1.0 elif apply_scale_options == 'FBX_SCALE_UNITS': - global_matrix = Matrix.Scale(global_scale, 4) * global_matrix + global_matrix = Matrix.Scale(global_scale, 4) @ global_matrix elif apply_scale_options == 'FBX_SCALE_CUSTOM': - global_matrix = Matrix.Scale(unit_scale, 4) * global_matrix + global_matrix = Matrix.Scale(unit_scale, 4) @ global_matrix unit_scale = global_scale else: # if apply_scale_options == 'FBX_SCALE_ALL': unit_scale = global_scale * unit_scale @@ -3004,7 +2998,7 @@ def save_single(operator, scene, filepath="", start_time = time.process_time() # Generate some data about exported scene... - scene_data = fbx_data_from_scene(scene, settings) + scene_data = fbx_data_from_scene(scene, depsgraph, settings) root = elem_empty(None, b"") # Root element has no id, as it is not saved per se! @@ -3098,7 +3092,7 @@ def save(operator, context, ret = None - active_object = context.scene.objects.active + active_object = context.view_layer.objects.active org_mode = None if active_object and active_object.mode != 'OBJECT' and bpy.ops.object.mode_set.poll(): @@ -3112,8 +3106,9 @@ def save(operator, context, else: kwargs_mod["context_objects"] = context.scene.objects - ret = save_single(operator, context.scene, filepath, **kwargs_mod) + ret = save_single(operator, context.scene, context.depsgraph, filepath, **kwargs_mod) else: + return # TODO Update for 2.8 fbxpath = filepath prefix = os.path.basename(fbxpath) diff --git a/io_scene_fbx/fbx_utils.py b/io_scene_fbx/fbx_utils.py index 25f5759b816da377e4c4180025c267b68649b865..8de8e8f8daee0d033b025a9cbf0a619c13fb9c52 100644 --- a/io_scene_fbx/fbx_utils.py +++ b/io_scene_fbx/fbx_utils.py @@ -30,7 +30,7 @@ from itertools import zip_longest, chain import bpy import bpy_extras -from bpy.types import Object, Bone, PoseBone, DupliObject +from bpy.types import Object, Bone, PoseBone, DepsgraphObjectInstance from mathutils import Vector, Matrix from . import encode_bin, data_types @@ -271,14 +271,14 @@ def similar_values_iter(v1, v2, e=1e-6): def vcos_transformed_gen(raw_cos, m=None): # Note: we could most likely get much better performances with numpy, but will leave this as TODO for now. gen = zip(*(iter(raw_cos),) * 3) - return gen if m is None else (m * Vector(v) for v in gen) + return gen if m is None else (m @ Vector(v) for v in gen) def nors_transformed_gen(raw_nors, m=None): # Great, now normals are also expected 4D! # XXX Back to 3D normals for now! # gen = zip(*(iter(raw_nors),) * 3 + (_infinite_gen(1.0),)) gen = zip(*(iter(raw_nors),) * 3) - return gen if m is None else (m * Vector(v) for v in gen) + return gen if m is None else (m @ Vector(v) for v in gen) # ##### UIDs code. ##### @@ -856,7 +856,7 @@ class AnimationCurveNodeWrapper: # ##### FBX objects generators. ##### -# FBX Model-like data (i.e. Blender objects, dupliobjects and bones) are wrapped in ObjectWrapper. +# FBX Model-like data (i.e. Blender objects, depsgraph instances and bones) are wrapped in ObjectWrapper. # This allows us to have a (nearly) same code FBX-wise for all those types. # The wrapper tries to stay as small as possible, by mostly using callbacks (property(get...)) # to actual Blender data it contains. @@ -870,9 +870,12 @@ class MetaObjectWrapper(type): dup_mat = None if isinstance(bdata, Object): key = get_blenderID_key(bdata) - elif isinstance(bdata, DupliObject): - key = "|".join((get_blenderID_key((bdata.id_data, bdata.object)), cls._get_dup_num_id(bdata))) - dup_mat = bdata.matrix.copy() + elif isinstance(bdata, DepsgraphObjectInstance): + if bdata.is_instance: + key = "|".join((get_blenderID_key((bdata.parent, bdata.object_instance)), cls._get_dup_num_id(bdata))) + dup_mat = bdata.matrix_world.copy() + else: + key = get_blenderID_key(bdata.object) else: # isinstance(bdata, (Bone, PoseBone)): if isinstance(bdata, PoseBone): bdata = armature.data.bones[bdata.name] @@ -883,9 +886,9 @@ class MetaObjectWrapper(type): cache = cls._cache = {} instance = cache.get(key) if instance is not None: - # Duplis hack: since duplis are not persistent in Blender (we have to re-create them to get updated + # Duplis hack: since dupli instances are not persistent in Blender (we have to re-create them to get updated # info like matrix...), we *always* need to reset that matrix when calling ObjectWrapper() (all - # other data is supposed valid during whole cache live, so we can skip resetting it). + # other data is supposed valid during whole cache live span, so we can skip resetting it). instance._dupli_matrix = dup_mat return instance @@ -902,7 +905,7 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): This class provides a same common interface for all (FBX-wise) object-like elements: * Blender Object * Blender Bone and PoseBone - * Blender DupliObject + * Blender DepsgraphObjectInstance (for dulis). Note since a same Blender object might be 'mapped' to several FBX models (esp. with duplis), we need to use a key to identify each. """ @@ -918,24 +921,42 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): @staticmethod def _get_dup_num_id(bdata): - return ".".join(str(i) for i in bdata.persistent_id if i != 2147483647) + INVALID_IDS = {2147483647, 0} + pids = tuple(bdata.persistent_id) + idx_valid = 0 + prev_i = ... + for idx, i in enumerate(pids[::-1]): + if i not in INVALID_IDS or (idx == len(pids) and i == 0 and prev_i != 0): + idx_valid = len(pids) - idx + break + prev_i = i + return ".".join(str(i) for i in pids[:idx_valid]) def __init__(self, bdata, armature=None): """ - bdata might be an Object, DupliObject, Bone or PoseBone. + bdata might be an Object (deprecated), DepsgraphObjectInstance, Bone or PoseBone. If Bone or PoseBone, armature Object must be provided. """ - if isinstance(bdata, Object): + # Note: DepsgraphObjectInstance are purely runtime data, they become invalid as soon as we step to the next item! + # Hence we have to immediately copy *all* needed data... + if isinstance(bdata, Object): # DEPRECATED self._tag = 'OB' self.name = get_blenderID_name(bdata) self.bdata = bdata self._ref = None - elif isinstance(bdata, DupliObject): - self._tag = 'DP' - self.name = "|".join((get_blenderID_name((bdata.id_data, bdata.object)), - "Dupli", self._get_dup_num_id(bdata))) - self.bdata = bdata.object - self._ref = bdata.id_data + elif isinstance(bdata, DepsgraphObjectInstance): + if bdata.is_instance: + # Note that dupli instance matrix is set by meta-class initialization. + self._tag = 'DP' + self.name = "|".join((get_blenderID_name((bdata.parent, bdata.object)), + "Dupli", self._get_dup_num_id(bdata))) + self.bdata = bdata.object + self._ref = bdata.parent + else: + self._tag = 'OB' + self.name = get_blenderID_name(bdata) + self.bdata = bdata + self._ref = None else: # isinstance(bdata, (Bone, PoseBone)): if isinstance(bdata, PoseBone): bdata = armature.data.bones[bdata.name] @@ -956,8 +977,9 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): return get_fbx_uuid_from_key(self.key) fbx_uuid = property(get_fbx_uuid) + # XXX Not sure how much that’s useful now... :/ def get_hide(self): - return self.bdata.hide + return self.bdata.hide_viewport if self._tag in {'OB', 'DP'} else self.bdata.hide hide = property(get_hide) def get_parent(self): @@ -974,7 +996,7 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): # Mere object parenting. return ObjectWrapper(self.bdata.parent) elif self._tag == 'DP': - return ObjectWrapper(self.bdata.parent or self._ref) + return ObjectWrapper(self._ref) else: # self._tag == 'BO' return ObjectWrapper(self.bdata.parent, self._ref) or ObjectWrapper(self._ref) parent = property(get_parent) @@ -983,12 +1005,12 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): if self._tag == 'OB': return self.bdata.matrix_local.copy() elif self._tag == 'DP': - return self._ref.matrix_world.inverted_safe() * self._dupli_matrix + return self._ref.matrix_world.inverted_safe() @ self._dupli_matrix else: # 'BO', current pose # PoseBone.matrix is in armature space, bring in back in real local one! par = self.bdata.parent par_mat_inv = self._ref.pose.bones[par.name].matrix.inverted_safe() if par else Matrix() - return par_mat_inv * self._ref.pose.bones[self.bdata.name].matrix + return par_mat_inv @ self._ref.pose.bones[self.bdata.name].matrix matrix_local = property(get_matrix_local) def get_matrix_global(self): @@ -997,7 +1019,7 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): elif self._tag == 'DP': return self._dupli_matrix else: # 'BO', current pose - return self._ref.matrix_world * self._ref.pose.bones[self.bdata.name].matrix + return self._ref.matrix_world @ self._ref.pose.bones[self.bdata.name].matrix matrix_global = property(get_matrix_global) def get_matrix_rest_local(self): @@ -1005,14 +1027,14 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): # Bone.matrix_local is in armature space, bring in back in real local one! par = self.bdata.parent par_mat_inv = par.matrix_local.inverted_safe() if par else Matrix() - return par_mat_inv * self.bdata.matrix_local + return par_mat_inv @ self.bdata.matrix_local else: return self.matrix_local.copy() matrix_rest_local = property(get_matrix_rest_local) def get_matrix_rest_global(self): if self._tag == 'BO': - return self._ref.matrix_world * self.bdata.matrix_local + return self._ref.matrix_world @ self.bdata.matrix_local else: return self.matrix_global.copy() matrix_rest_global = property(get_matrix_rest_global) @@ -1065,41 +1087,41 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): if self._tag == 'BO': # If we have a bone parent we need to undo the parent correction. if not is_global and scene_data.settings.bone_correction_matrix_inv and parent and parent.is_bone: - matrix = scene_data.settings.bone_correction_matrix_inv * matrix + matrix = scene_data.settings.bone_correction_matrix_inv @ matrix # Apply the bone correction. if scene_data.settings.bone_correction_matrix: - matrix = matrix * scene_data.settings.bone_correction_matrix + matrix = matrix @ scene_data.settings.bone_correction_matrix elif self.bdata.type == 'LIGHT': - matrix = matrix * MAT_CONVERT_LIGHT + matrix = matrix @ MAT_CONVERT_LIGHT elif self.bdata.type == 'CAMERA': - matrix = matrix * MAT_CONVERT_CAMERA + matrix = matrix @ MAT_CONVERT_CAMERA if self._tag in {'DP', 'OB'} and parent: if parent._tag == 'BO': # In bone parent case, we get transformation in **bone tip** space (sigh). # Have to bring it back into bone root, which is FBX expected value. - matrix = Matrix.Translation((0, (parent.bdata.tail - parent.bdata.head).length, 0)) * matrix + matrix = Matrix.Translation((0, (parent.bdata.tail - parent.bdata.head).length, 0)) @ matrix # Our matrix is in local space, time to bring it in its final desired space. if parent: if is_global: # Move matrix to global Blender space. - matrix = (parent.matrix_rest_global if rest else parent.matrix_global) * matrix + matrix = (parent.matrix_rest_global if rest else parent.matrix_global) @ matrix elif parent.use_bake_space_transform(scene_data): # Blender's and FBX's local space of parent may differ if we use bake_space_transform... # Apply parent's *Blender* local space... - matrix = (parent.matrix_rest_local if rest else parent.matrix_local) * matrix + matrix = (parent.matrix_rest_local if rest else parent.matrix_local) @ matrix # ...and move it back into parent's *FBX* local space. par_mat = parent.fbx_object_matrix(scene_data, rest=rest, local_space=True) - matrix = par_mat.inverted_safe() * matrix + matrix = par_mat.inverted_safe() @ matrix if self.use_bake_space_transform(scene_data): # If we bake the transforms we need to post-multiply inverse global transform. # This means that the global transform will not apply to children of this transform. - matrix = matrix * scene_data.settings.global_matrix_inv + matrix = matrix @ scene_data.settings.global_matrix_inv if is_global: # In any case, pre-multiply the global matrix to get it in FBX global space! - matrix = scene_data.settings.global_matrix * matrix + matrix = scene_data.settings.global_matrix @ matrix return matrix @@ -1164,19 +1186,10 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): return True # #### Duplis... - def dupli_list_create(self, scene, settings='PREVIEW'): + def dupli_list_gen(self, depsgraph): if self._tag == 'OB' and self.bdata.is_duplicator: - self.bdata.dupli_list_create(scene, settings) - - def dupli_list_clear(self): - if self._tag == 'OB'and self.bdata.is_duplicator: - self.bdata.dupli_list_clear() - - def get_dupli_list(self): - if self._tag == 'OB'and self.bdata.is_duplicator: - return (ObjectWrapper(dup) for dup in self.bdata.dupli_list) + return (ObjectWrapper(dup) for dup in depsgraph.object_instances if dup.parent == self.bdata) return () - dupli_list = property(get_dupli_list) def fbx_name_class(name, cls): @@ -1213,7 +1226,7 @@ FBXExportSettings = namedtuple("FBXExportSettings", ( # * animations. FBXExportData = namedtuple("FBXExportData", ( "templates", "templates_users", "connections", - "settings", "scene", "objects", "animations", "animated", "frame_start", "frame_end", + "settings", "scene", "depsgraph", "objects", "animations", "animated", "frame_start", "frame_end", "data_empties", "data_lights", "data_cameras", "data_meshes", "mesh_mat_indices", "data_bones", "data_leaf_bones", "data_deformers_skin", "data_deformers_shape", "data_world", "data_materials", "data_textures", "data_videos", diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py index 8b9a42cd462ddbed4baede76235019e4c2a2ade2..ee569f2825368b407be12657d34e8d856be30073 100644 --- a/io_scene_fbx/import_fbx.py +++ b/io_scene_fbx/import_fbx.py @@ -369,7 +369,7 @@ def blen_read_custom_properties(fbx_obj, blen_obj, settings): def blen_read_object_transform_do(transform_data): # This is a nightmare. FBX SDK uses Maya way to compute the transformation matrix of a node - utterly simple: # - # WorldTransform = ParentWorldTransform * T * Roff * Rp * Rpre * R * Rpost * Rp-1 * Soff * Sp * S * Sp-1 + # WorldTransform = ParentWorldTransform @ T @ Roff @ Rp @ Rpre @ R @ Rpost @ Rp-1 @ Soff @ Sp @ S @ Sp-1 # # Where all those terms are 4 x 4 matrices that contain: # WorldTransform: Transformation matrix of the node in global space. @@ -389,7 +389,7 @@ def blen_read_object_transform_do(transform_data): # But it was still too simple, and FBX notion of compatibility is... quite specific. So we also have to # support 3DSMax way: # - # WorldTransform = ParentWorldTransform * T * R * S * OT * OR * OS + # WorldTransform = ParentWorldTransform @ T @ R @ S @ OT @ OR @ OS # # Where all those terms are 4 x 4 matrices that contain: # WorldTransform: Transformation matrix of the node in global space @@ -414,7 +414,7 @@ def blen_read_object_transform_do(transform_data): # rotation to_rot = lambda rot, rot_ord: Euler(convert_deg_to_rad_iter(rot), rot_ord).to_matrix().to_4x4() - lcl_rot = to_rot(transform_data.rot, transform_data.rot_ord) * transform_data.rot_alt_mat + lcl_rot = to_rot(transform_data.rot, transform_data.rot_ord) @ transform_data.rot_alt_mat pre_rot = to_rot(transform_data.pre_rot, transform_data.rot_ord) pst_rot = to_rot(transform_data.pst_rot, transform_data.rot_ord) geom_rot = to_rot(transform_data.geom_rot, transform_data.rot_ord) @@ -431,21 +431,21 @@ def blen_read_object_transform_do(transform_data): geom_scale[0][0], geom_scale[1][1], geom_scale[2][2] = transform_data.geom_sca base_mat = ( - lcl_translation * - rot_ofs * - rot_piv * - pre_rot * - lcl_rot * - pst_rot * - rot_piv.inverted_safe() * - sca_ofs * - sca_piv * - lcl_scale * + lcl_translation @ + rot_ofs @ + rot_piv @ + pre_rot @ + lcl_rot @ + pst_rot @ + rot_piv.inverted_safe() @ + sca_ofs @ + sca_piv @ + lcl_scale @ sca_piv.inverted_safe() ) - geom_mat = geom_loc * geom_rot * geom_scale + geom_mat = geom_loc @ geom_rot @ geom_scale # We return mat without 'geometric transforms' too, because it is to be used for children, sigh... - return (base_mat * geom_mat, base_mat, geom_mat) + return (base_mat @ geom_mat, base_mat, geom_mat) # XXX This might be weak, now that we can add vgroups from both bones and shapes, name collisions become @@ -661,19 +661,19 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset): # compensate for changes in the local matrix during processing if item.anim_compensation_matrix: - mat = mat * item.anim_compensation_matrix + mat = mat @ item.anim_compensation_matrix # apply pre- and post matrix # post-matrix will contain any correction for lights, camera and bone orientation # pre-matrix will contain any correction for a parent's correction matrix or the global matrix if item.pre_matrix: - mat = item.pre_matrix * mat + mat = item.pre_matrix @ mat if item.post_matrix: - mat = mat * item.post_matrix + mat = mat @ item.post_matrix # And now, remove that rest pose matrix from current mat (also in parent space). if restmat_inv: - mat = restmat_inv * mat + mat = restmat_inv @ mat # Now we have a virtual matrix of transform from AnimCurves, we can insert keyframes! loc, rot, sca = mat.decompose() @@ -1006,8 +1006,7 @@ def blen_read_geom_layer_uv(fbx_obj, mesh): fbx_layer_data = elem_prop_first(elem_find_first(fbx_layer, b'UV')) fbx_layer_index = elem_prop_first(elem_find_first(fbx_layer, b'UVIndex')) - uv_tex = mesh.uv_textures.new(name=fbx_layer_name) - uv_lay = mesh.uv_layers[-1] + uv_lay = mesh.uv_layers.new(name=fbx_layer_name) blen_data = uv_lay.data # some valid files omit this data @@ -1165,7 +1164,7 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings): if geom_mat_co is not None: def _vcos_transformed_gen(raw_cos, m=None): # Note: we could most likely get much better performances with numpy, but will leave this as TODO for now. - return chain(*(m * Vector(v) for v in zip(*(iter(raw_cos),) * 3))) + return chain(*(m @ Vector(v) for v in zip(*(iter(raw_cos),) * 3))) fbx_verts = array.array(fbx_verts.typecode, _vcos_transformed_gen(fbx_verts, geom_mat_co)) if fbx_verts is None: @@ -1242,7 +1241,7 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings): ok_normals = blen_read_geom_layer_normal(fbx_obj, mesh) else: def nortrans(v): - return geom_mat_no * Vector(v) + return geom_mat_no @ Vector(v) ok_normals = blen_read_geom_layer_normal(fbx_obj, mesh, nortrans) mesh.validate(clean_customdata=False) # *Very* important to not remove lnors here! @@ -1498,7 +1497,7 @@ def blen_read_light(fbx_tmpl, fbx_obj, global_scale): lamp.color = elem_props_get_color_rgb(fbx_props, b'Color', (1.0, 1.0, 1.0)) lamp.energy = elem_props_get_number(fbx_props, b'Intensity', 100.0) / 100.0 lamp.distance = elem_props_get_number(fbx_props, b'DecayStart', 25.0) * global_scale - lamp.shadow_method = ('RAY_SHADOW' if elem_props_get_bool(fbx_props, b'CastShadow', True) else 'NOSHADOW') + lamp.use_shadow = elem_props_get_bool(fbx_props, b'CastShadow', True) lamp.shadow_color = elem_props_get_color_rgb(fbx_props, b'ShadowColor', (0.0, 0.0, 0.0)) return lamp @@ -1612,7 +1611,7 @@ class FbxImportHelperNode: self.pre_matrix = settings.global_matrix if parent_correction_inv: - self.pre_matrix = parent_correction_inv * (self.pre_matrix if self.pre_matrix else Matrix()) + self.pre_matrix = parent_correction_inv @ (self.pre_matrix if self.pre_matrix else Matrix()) correction_matrix = None @@ -1705,7 +1704,7 @@ class FbxImportHelperNode: self.post_matrix = correction_matrix if self.do_bake_transform(settings): - self.post_matrix = settings.global_matrix_inv * (self.post_matrix if self.post_matrix else Matrix()) + self.post_matrix = settings.global_matrix_inv @ (self.post_matrix if self.post_matrix else Matrix()) # process children correction_matrix_inv = correction_matrix.inverted_safe() if correction_matrix else None @@ -1782,29 +1781,29 @@ class FbxImportHelperNode: def get_world_matrix_as_parent(self): matrix = self.parent.get_world_matrix_as_parent() if self.parent else Matrix() if self.matrix_as_parent: - matrix = matrix * self.matrix_as_parent + matrix = matrix @ self.matrix_as_parent return matrix def get_world_matrix(self): matrix = self.parent.get_world_matrix_as_parent() if self.parent else Matrix() if self.matrix: - matrix = matrix * self.matrix + matrix = matrix @ self.matrix return matrix def get_matrix(self): matrix = self.matrix if self.matrix else Matrix() if self.pre_matrix: - matrix = self.pre_matrix * matrix + matrix = self.pre_matrix @ matrix if self.post_matrix: - matrix = matrix * self.post_matrix + matrix = matrix @ self.post_matrix return matrix def get_bind_matrix(self): matrix = self.bind_matrix if self.bind_matrix else Matrix() if self.pre_matrix: - matrix = self.pre_matrix * matrix + matrix = self.pre_matrix @ matrix if self.post_matrix: - matrix = matrix * self.post_matrix + matrix = matrix @ self.post_matrix return matrix def make_bind_pose_local(self, parent_matrix=None): @@ -1812,13 +1811,13 @@ class FbxImportHelperNode: parent_matrix = Matrix() if self.bind_matrix: - bind_matrix = parent_matrix.inverted_safe() * self.bind_matrix + bind_matrix = parent_matrix.inverted_safe() @ self.bind_matrix else: bind_matrix = self.matrix.copy() if self.matrix else None self.bind_matrix = bind_matrix if bind_matrix: - parent_matrix = parent_matrix * bind_matrix + parent_matrix = parent_matrix @ bind_matrix for child in self.children: child.make_bind_pose_local(parent_matrix) @@ -1838,8 +1837,8 @@ class FbxImportHelperNode: child.collect_skeleton_meshes(meshes) for m in meshes: old_matrix = m.matrix - m.matrix = armature_matrix_inv * m.get_world_matrix() - m.anim_compensation_matrix = old_matrix.inverted_safe() * m.matrix + m.matrix = armature_matrix_inv @ m.get_world_matrix() + m.anim_compensation_matrix = old_matrix.inverted_safe() @ m.matrix m.is_global_animation = True m.parent = self self.meshes = meshes @@ -1914,7 +1913,7 @@ class FbxImportHelperNode: bone.tail = bone_tail # And rotate/move it to its final "rest pose". - bone_matrix = parent_matrix * self.get_bind_matrix().normalized() + bone_matrix = parent_matrix @ self.get_bind_matrix().normalized() bone.matrix = bone_matrix @@ -1927,7 +1926,7 @@ class FbxImportHelperNode: if child.is_leaf and force_connect_children: # Arggggggggggggggggg! We do not want to create this bone, but we need its 'virtual head' location # to orient current one!!! - child_head = (bone_matrix * child.get_bind_matrix().normalized()).translation + child_head = (bone_matrix @ child.get_bind_matrix().normalized()).translation child_connect(bone, None, child_head, connect_ctx) elif child.is_bone and not child.ignore: child_bone = child.build_skeleton(arm, bone_matrix, bone_size, @@ -1958,7 +1957,7 @@ class FbxImportHelperNode: # Misc Attributes obj.color[0:3] = elem_props_get_color_rgb(fbx_props, b'Color', (0.8, 0.8, 0.8)) - obj.hide = not bool(elem_props_get_visibility(fbx_props, b'Visibility', 1.0)) + obj.hide_viewport = not bool(elem_props_get_visibility(fbx_props, b'Visibility', 1.0)) obj.matrix_basis = self.get_matrix() @@ -1967,12 +1966,12 @@ class FbxImportHelperNode: return obj - def build_skeleton_children(self, fbx_tmpl, settings, scene): + def build_skeleton_children(self, fbx_tmpl, settings, scene, view_layer): if self.is_bone: for child in self.children: if child.ignore: continue - child.build_skeleton_children(fbx_tmpl, settings, scene) + child.build_skeleton_children(fbx_tmpl, settings, scene, view_layer) return None else: # child is not a bone @@ -1984,11 +1983,11 @@ class FbxImportHelperNode: for child in self.children: if child.ignore: continue - child.build_skeleton_children(fbx_tmpl, settings, scene) + child.build_skeleton_children(fbx_tmpl, settings, scene, view_layer) # instance in scene - obj_base = scene.objects.link(obj) - obj_base.select = True + view_layer.collections.active.collection.objects.link(obj) + obj.select_set('Select') return obj @@ -2007,7 +2006,7 @@ class FbxImportHelperNode: # Blender attaches to the end of a bone, while FBX attaches to the start. # bone_child_matrix corrects for that. if child.pre_matrix: - child.pre_matrix = self.bone_child_matrix * child.pre_matrix + child.pre_matrix = self.bone_child_matrix @ child.pre_matrix else: child.pre_matrix = self.bone_child_matrix @@ -2027,7 +2026,7 @@ class FbxImportHelperNode: def set_pose_matrix(self, arm): pose_bone = arm.bl_obj.pose.bones[self.bl_bone] - pose_bone.matrix_basis = self.get_bind_matrix().inverted_safe() * self.get_matrix() + pose_bone.matrix_basis = self.get_bind_matrix().inverted_safe() @ self.get_matrix() for child in self.children: if child.ignore: @@ -2094,7 +2093,7 @@ class FbxImportHelperNode: if child.is_bone and not child.ignore: child.set_bone_weights() - def build_hierarchy(self, fbx_tmpl, settings, scene): + def build_hierarchy(self, fbx_tmpl, settings, scene, view_layer): if self.is_armature: # create when linking since we need object data elem_name_utf8 = self.fbx_name @@ -2114,15 +2113,15 @@ class FbxImportHelperNode: blen_read_custom_properties(self.fbx_elem, arm, settings) # instance in scene - obj_base = scene.objects.link(arm) - obj_base.select = True + view_layer.collections.active.collection.objects.link(arm) + obj.select_set('Select') # Add bones: # Switch to Edit mode. scene.objects.active = arm - is_hidden = arm.hide - arm.hide = False # Can't switch to Edit mode hidden objects... + is_hidden = arm.hide_viewport + arm.hide_viewport = False # Can't switch to Edit mode hidden objects... bpy.ops.object.mode_set(mode='EDIT') for child in self.children: @@ -2133,7 +2132,7 @@ class FbxImportHelperNode: bpy.ops.object.mode_set(mode='OBJECT') - arm.hide = is_hidden + arm.hide_viewport = is_hidden # Set pose matrix for child in self.children: @@ -2146,7 +2145,7 @@ class FbxImportHelperNode: for child in self.children: if child.ignore: continue - child_obj = child.build_skeleton_children(fbx_tmpl, settings, scene) + child_obj = child.build_skeleton_children(fbx_tmpl, settings, scene, view_layer) return arm elif self.fbx_elem and not self.is_bone: @@ -2154,16 +2153,16 @@ class FbxImportHelperNode: # walk through children for child in self.children: - child.build_hierarchy(fbx_tmpl, settings, scene) + child.build_hierarchy(fbx_tmpl, settings, scene, view_layer) # instance in scene - obj_base = scene.objects.link(obj) - obj_base.select = True + view_layer.collections.active.collection.objects.link(obj) + obj.select_set('SELECT') return obj else: for child in self.children: - child.build_hierarchy(fbx_tmpl, settings, scene) + child.build_hierarchy(fbx_tmpl, settings, scene, view_layer) return None @@ -2192,16 +2191,16 @@ class FbxImportHelperNode: # which we obviously cannot do in Blender. :/ if amat is None: amat = self.bind_matrix - amat = settings.global_matrix * (Matrix() if amat is None else amat) + amat = settings.global_matrix @ (Matrix() if amat is None else amat) if self.matrix_geom: - amat = amat * self.matrix_geom - mmat = settings.global_matrix * mmat + amat = amat @ self.matrix_geom + mmat = settings.global_matrix @ mmat if mesh.matrix_geom: - mmat = mmat * mesh.matrix_geom + mmat = mmat @ mesh.matrix_geom # Now that we have armature and mesh in there (global) bind 'state' (matrix), # we can compute inverse parenting matrix of the mesh. - me_obj.matrix_parent_inverse = amat.inverted_safe() * mmat * me_obj.matrix_basis.inverted_safe() + me_obj.matrix_parent_inverse = amat.inverted_safe() @ mmat @ me_obj.matrix_basis.inverted_safe() mod = mesh.bl_obj.modifiers.new(arm.name, 'ARMATURE') mod.object = arm @@ -2325,6 +2324,7 @@ def load(operator, context, filepath="", material_decals = None scene = context.scene + view_layer = context.view_layer # #### Get some info from GlobalSettings. @@ -2350,7 +2350,7 @@ def load(operator, context, filepath="", elem_props_get_integer(fbx_settings_props, b'CoordAxisSign', 1)) axis_key = (axis_up, axis_forward, axis_coord) axis_up, axis_forward = {v: k for k, v in RIGHT_HAND_AXES.items()}.get(axis_key, ('Z', 'Y')) - global_matrix = (Matrix.Scale(global_scale, 4) * + global_matrix = (Matrix.Scale(global_scale, 4) @ axis_conversion(from_forward=axis_forward, from_up=axis_up).to_4x4()) # To cancel out unwanted rotation/scale on nodes. @@ -2682,7 +2682,7 @@ def load(operator, context, filepath="", armature_matrix = tx_arm if tx_bone: - mesh_matrix = tx_bone * mesh_matrix + mesh_matrix = tx_bone @ mesh_matrix helper_node.bind_matrix = tx_bone # overwrite the bind matrix # Get the meshes driven by this cluster: (Shouldn't that be only one?) @@ -2725,7 +2725,7 @@ def load(operator, context, filepath="", root_helper.find_correction_matrix(settings) # build the Object/Armature/Bone hierarchy - root_helper.build_hierarchy(fbx_tmpl, settings, scene) + root_helper.build_hierarchy(fbx_tmpl, settings, scene, view_layer) # Link the Object/Armature/Bone hierarchy root_helper.link_hierarchy(fbx_tmpl, settings, scene)