Initial port of FBX to 2.8.

Default cube scene exports and imports ok (besides missing features like
nodal material handling). Anything else is either known broken, or yet
to be tested. :P

Note that I raised main number of addon version, so that we can keep
track of smaller fixes that can be done in both 2.7x and 2.8 versions of
the addon.
This commit is contained in:
Bastien Montagne 2018-09-21 15:12:27 +02:00
parent b5328b13c5
commit 2bfab056d2
Notes: blender-bot 2023-09-11 17:29:21 +02:00
Referenced by pull request #104876, Fix #104875: Animated dupli instances export frozen in place
Referenced by commit 6b576d5282, Fix #104875: Animated dupli instances export frozen in place
4 changed files with 214 additions and 205 deletions

View File

@ -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'},

View File

@ -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)

View File

@ -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 thats 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",

View File

@ -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)