Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'io_scene_fbx')
-rw-r--r--io_scene_fbx/__init__.py147
-rw-r--r--io_scene_fbx/export_fbx_bin.py1245
-rw-r--r--io_scene_fbx/fbx_utils.py151
-rw-r--r--io_scene_fbx/import_fbx.py482
4 files changed, 951 insertions, 1074 deletions
diff --git a/io_scene_fbx/__init__.py b/io_scene_fbx/__init__.py
index fddeda6c..d415305d 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, 13, 1),
+ "blender": (2, 80, 0),
"location": "File > Import-Export",
"description": "FBX IO meshes, UV's, vertex colors, materials, textures, cameras, lamps and actions",
"warning": "",
@@ -52,27 +52,25 @@ from bpy.props import (
from bpy_extras.io_utils import (
ImportHelper,
ExportHelper,
- orientation_helper_factory,
+ orientation_helper,
path_reference_mode,
axis_conversion,
)
-IOFBXOrientationHelper = orientation_helper_factory("IOFBXOrientationHelper", axis_forward='-Z', axis_up='Y')
-
-
-class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper):
+@orientation_helper(axis_forward='-Z', axis_up='Y')
+class ImportFBX(bpy.types.Operator, ImportHelper):
"""Load a FBX file"""
bl_idname = "import_scene.fbx"
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 +78,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 +96,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 +169,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 +181,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,25 +226,25 @@ 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')
from . import import_fbx
return import_fbx.load(self, context, **keywords)
-class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper):
+@orientation_helper(axis_forward='-Z', axis_up='Y')
+class ExportFBX(bpy.types.Operator, ExportHelper):
"""Write a FBX file"""
bl_idname = "export_scene.fbx"
bl_label = "Export FBX"
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 +254,29 @@ 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",
+ description="Export selected and visible objects only",
+ default=False,
+ )
+ use_active_collection: BoolProperty(
+ name="Active Collection",
+ description="Export only objects from the active collection (and its children)",
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 +291,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,32 +299,32 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper):
default=False,
)
- object_types = EnumProperty(
+ object_types: EnumProperty(
name="Object Types",
options={'ENUM_FLAG'},
items=(('EMPTY', "Empty", ""),
('CAMERA', "Camera", ""),
- ('LAMP', "Lamp", ""),
+ ('LIGHT', "Lamp", ""),
('ARMATURE', "Armature", "WARNING: not supported in dupli/group instances"),
('MESH', "Mesh", ""),
('OTHER', "Other", "Other geometry types, like curve, metaball, etc. (converted to meshes)"),
),
description="Which kind of object to export",
- default={'EMPTY', 'CAMERA', 'LAMP', 'ARMATURE', 'MESH', 'OTHER'},
+ 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 +334,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 +367,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 +378,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,68 +394,75 @@ 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.
soft_min=0.0, soft_max=10.0,
default=1.0, # default: min slope: 0.005, max frame step: 10.
)
- path_mode = path_reference_mode
- embed_textures = BoolProperty(
+ path_mode: path_reference_mode
+ 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"),
+ ('COLLECTION', "Collection",
+ "Each collection (data-block ones) as a file, does not include content of children collections"),
+ ('SCENE_COLLECTION', "Scene Collections",
+ "Each collection (including master, non-data-block ones) of each scene as a file, "
+ "including content from children collections"),
+ ('ACTIVE_SCENE_COLLECTION', "Active Scene Collections",
+ "Each collection (including master, non-data-block one) of the active scene as a file, "
+ "including content from children collections"),
),
)
- 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'},
@@ -464,6 +474,7 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper):
layout.prop(self, "ui_tab", expand=True)
if self.ui_tab == 'MAIN':
layout.prop(self, "use_selection")
+ layout.prop(self, "use_active_collection")
col = layout.column(align=True)
row = col.row(align=True)
@@ -559,13 +570,13 @@ def register():
for cls in classes:
bpy.utils.register_class(cls)
- bpy.types.INFO_MT_file_import.append(menu_func_import)
- bpy.types.INFO_MT_file_export.append(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
def unregister():
- bpy.types.INFO_MT_file_import.remove(menu_func_import)
- bpy.types.INFO_MT_file_export.remove(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
for cls in classes:
bpy.utils.unregister_class(cls)
diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py
index ed1109e7..6f6005cb 100644
--- a/io_scene_fbx/export_fbx_bin.py
+++ b/io_scene_fbx/export_fbx_bin.py
@@ -27,7 +27,6 @@ import math
import os
import time
-from collections import OrderedDict
from itertools import zip_longest, chain
if "bpy" in locals():
@@ -41,6 +40,7 @@ if "bpy" in locals():
import bpy
import bpy_extras
+from bpy_extras import node_shader_utils
from mathutils import Vector, Matrix
from . import encode_bin, data_types, fbx_utils
@@ -75,6 +75,7 @@ from .fbx_utils import (
get_blender_bindpose_key, get_blender_armature_skin_key, get_blender_bone_cluster_key,
get_blender_anim_id_base, get_blender_anim_stack_key, get_blender_anim_layer_key,
get_blender_anim_curve_node_key, get_blender_anim_curve_key,
+ get_blender_nodetexture_key,
# FBX element data.
elem_empty,
elem_data_single_bool, elem_data_single_int16, elem_data_single_int32, elem_data_single_int64,
@@ -110,7 +111,7 @@ convert_rad_to_deg_iter = units_convertor_iter("radian", "degree")
# TODO: check all those "default" values, they should match Blender's default as much as possible, I guess?
def fbx_template_def_globalsettings(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict()
+ props = {}
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"GlobalSettings", b"", props, nbr_users, [False])
@@ -118,91 +119,91 @@ def fbx_template_def_globalsettings(scene, settings, override_defaults=None, nbr
def fbx_template_def_model(scene, settings, override_defaults=None, nbr_users=0):
gscale = settings.global_scale
- props = OrderedDict((
- # Name, Value, Type, Animatable
- (b"QuaternionInterpolate", (0, "p_enum", False)), # 0 = no quat interpolation.
- (b"RotationOffset", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"RotationPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"ScalingOffset", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"ScalingPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"TranslationActive", (False, "p_bool", False)),
- (b"TranslationMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"TranslationMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"TranslationMinX", (False, "p_bool", False)),
- (b"TranslationMinY", (False, "p_bool", False)),
- (b"TranslationMinZ", (False, "p_bool", False)),
- (b"TranslationMaxX", (False, "p_bool", False)),
- (b"TranslationMaxY", (False, "p_bool", False)),
- (b"TranslationMaxZ", (False, "p_bool", False)),
- (b"RotationOrder", (0, "p_enum", False)), # we always use 'XYZ' order.
- (b"RotationSpaceForLimitOnly", (False, "p_bool", False)),
- (b"RotationStiffnessX", (0.0, "p_double", False)),
- (b"RotationStiffnessY", (0.0, "p_double", False)),
- (b"RotationStiffnessZ", (0.0, "p_double", False)),
- (b"AxisLen", (10.0, "p_double", False)),
- (b"PreRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"PostRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"RotationActive", (False, "p_bool", False)),
- (b"RotationMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"RotationMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"RotationMinX", (False, "p_bool", False)),
- (b"RotationMinY", (False, "p_bool", False)),
- (b"RotationMinZ", (False, "p_bool", False)),
- (b"RotationMaxX", (False, "p_bool", False)),
- (b"RotationMaxY", (False, "p_bool", False)),
- (b"RotationMaxZ", (False, "p_bool", False)),
- (b"InheritType", (0, "p_enum", False)), # RrSs
- (b"ScalingActive", (False, "p_bool", False)),
- (b"ScalingMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"ScalingMax", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
- (b"ScalingMinX", (False, "p_bool", False)),
- (b"ScalingMinY", (False, "p_bool", False)),
- (b"ScalingMinZ", (False, "p_bool", False)),
- (b"ScalingMaxX", (False, "p_bool", False)),
- (b"ScalingMaxY", (False, "p_bool", False)),
- (b"ScalingMaxZ", (False, "p_bool", False)),
- (b"GeometricTranslation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"GeometricRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"GeometricScaling", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
- (b"MinDampRangeX", (0.0, "p_double", False)),
- (b"MinDampRangeY", (0.0, "p_double", False)),
- (b"MinDampRangeZ", (0.0, "p_double", False)),
- (b"MaxDampRangeX", (0.0, "p_double", False)),
- (b"MaxDampRangeY", (0.0, "p_double", False)),
- (b"MaxDampRangeZ", (0.0, "p_double", False)),
- (b"MinDampStrengthX", (0.0, "p_double", False)),
- (b"MinDampStrengthY", (0.0, "p_double", False)),
- (b"MinDampStrengthZ", (0.0, "p_double", False)),
- (b"MaxDampStrengthX", (0.0, "p_double", False)),
- (b"MaxDampStrengthY", (0.0, "p_double", False)),
- (b"MaxDampStrengthZ", (0.0, "p_double", False)),
- (b"PreferedAngleX", (0.0, "p_double", False)),
- (b"PreferedAngleY", (0.0, "p_double", False)),
- (b"PreferedAngleZ", (0.0, "p_double", False)),
- (b"LookAtProperty", (None, "p_object", False)),
- (b"UpVectorProperty", (None, "p_object", False)),
- (b"Show", (True, "p_bool", False)),
- (b"NegativePercentShapeSupport", (True, "p_bool", False)),
- (b"DefaultAttributeIndex", (-1, "p_integer", False)),
- (b"Freeze", (False, "p_bool", False)),
- (b"LODBox", (False, "p_bool", False)),
- (b"Lcl Translation", ((0.0, 0.0, 0.0), "p_lcl_translation", True)),
- (b"Lcl Rotation", ((0.0, 0.0, 0.0), "p_lcl_rotation", True)),
- (b"Lcl Scaling", ((1.0, 1.0, 1.0), "p_lcl_scaling", True)),
- (b"Visibility", (1.0, "p_visibility", True)),
- (b"Visibility Inheritance", (1, "p_visibility_inheritance", False)),
- ))
+ props = {
+ # Name, Value, Type, Animatable
+ b"QuaternionInterpolate": (0, "p_enum", False), # 0 = no quat interpolation.
+ b"RotationOffset": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"RotationPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"ScalingOffset": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"ScalingPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"TranslationActive": (False, "p_bool", False),
+ b"TranslationMin": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"TranslationMax": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"TranslationMinX": (False, "p_bool", False),
+ b"TranslationMinY": (False, "p_bool", False),
+ b"TranslationMinZ": (False, "p_bool", False),
+ b"TranslationMaxX": (False, "p_bool", False),
+ b"TranslationMaxY": (False, "p_bool", False),
+ b"TranslationMaxZ": (False, "p_bool", False),
+ b"RotationOrder": (0, "p_enum", False), # we always use 'XYZ' order.
+ b"RotationSpaceForLimitOnly": (False, "p_bool", False),
+ b"RotationStiffnessX": (0.0, "p_double", False),
+ b"RotationStiffnessY": (0.0, "p_double", False),
+ b"RotationStiffnessZ": (0.0, "p_double", False),
+ b"AxisLen": (10.0, "p_double", False),
+ b"PreRotation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"PostRotation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"RotationActive": (False, "p_bool", False),
+ b"RotationMin": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"RotationMax": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"RotationMinX": (False, "p_bool", False),
+ b"RotationMinY": (False, "p_bool", False),
+ b"RotationMinZ": (False, "p_bool", False),
+ b"RotationMaxX": (False, "p_bool", False),
+ b"RotationMaxY": (False, "p_bool", False),
+ b"RotationMaxZ": (False, "p_bool", False),
+ b"InheritType": (0, "p_enum", False), # RrSs
+ b"ScalingActive": (False, "p_bool", False),
+ b"ScalingMin": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"ScalingMax": ((1.0, 1.0, 1.0), "p_vector_3d", False),
+ b"ScalingMinX": (False, "p_bool", False),
+ b"ScalingMinY": (False, "p_bool", False),
+ b"ScalingMinZ": (False, "p_bool", False),
+ b"ScalingMaxX": (False, "p_bool", False),
+ b"ScalingMaxY": (False, "p_bool", False),
+ b"ScalingMaxZ": (False, "p_bool", False),
+ b"GeometricTranslation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"GeometricRotation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"GeometricScaling": ((1.0, 1.0, 1.0), "p_vector_3d", False),
+ b"MinDampRangeX": (0.0, "p_double", False),
+ b"MinDampRangeY": (0.0, "p_double", False),
+ b"MinDampRangeZ": (0.0, "p_double", False),
+ b"MaxDampRangeX": (0.0, "p_double", False),
+ b"MaxDampRangeY": (0.0, "p_double", False),
+ b"MaxDampRangeZ": (0.0, "p_double", False),
+ b"MinDampStrengthX": (0.0, "p_double", False),
+ b"MinDampStrengthY": (0.0, "p_double", False),
+ b"MinDampStrengthZ": (0.0, "p_double", False),
+ b"MaxDampStrengthX": (0.0, "p_double", False),
+ b"MaxDampStrengthY": (0.0, "p_double", False),
+ b"MaxDampStrengthZ": (0.0, "p_double", False),
+ b"PreferedAngleX": (0.0, "p_double", False),
+ b"PreferedAngleY": (0.0, "p_double", False),
+ b"PreferedAngleZ": (0.0, "p_double", False),
+ b"LookAtProperty": (None, "p_object", False),
+ b"UpVectorProperty": (None, "p_object", False),
+ b"Show": (True, "p_bool", False),
+ b"NegativePercentShapeSupport": (True, "p_bool", False),
+ b"DefaultAttributeIndex": (-1, "p_integer", False),
+ b"Freeze": (False, "p_bool", False),
+ b"LODBox": (False, "p_bool", False),
+ b"Lcl Translation": ((0.0, 0.0, 0.0), "p_lcl_translation", True),
+ b"Lcl Rotation": ((0.0, 0.0, 0.0), "p_lcl_rotation", True),
+ b"Lcl Scaling": ((1.0, 1.0, 1.0), "p_lcl_scaling", True),
+ b"Visibility": (1.0, "p_visibility", True),
+ b"Visibility Inheritance": (1, "p_visibility_inheritance", False),
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"Model", b"FbxNode", props, nbr_users, [False])
def fbx_template_def_null(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict((
- (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
- (b"Size", (100.0, "p_double", False)),
- (b"Look", (1, "p_enum", False)), # Cross (0 is None, i.e. invisible?).
- ))
+ props = {
+ b"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False),
+ b"Size": (100.0, "p_double", False),
+ b"Look": (1, "p_enum", False), # Cross (0 is None, i.e. invisible?).
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"NodeAttribute", b"FbxNull", props, nbr_users, [False])
@@ -210,17 +211,17 @@ def fbx_template_def_null(scene, settings, override_defaults=None, nbr_users=0):
def fbx_template_def_light(scene, settings, override_defaults=None, nbr_users=0):
gscale = settings.global_scale
- props = OrderedDict((
- (b"LightType", (0, "p_enum", False)), # Point light.
- (b"CastLight", (True, "p_bool", False)),
- (b"Color", ((1.0, 1.0, 1.0), "p_color", True)),
- (b"Intensity", (100.0, "p_number", True)), # Times 100 compared to Blender values...
- (b"DecayType", (2, "p_enum", False)), # Quadratic.
- (b"DecayStart", (30.0 * gscale, "p_double", False)),
- (b"CastShadows", (True, "p_bool", False)),
- (b"ShadowColor", ((0.0, 0.0, 0.0), "p_color", True)),
- (b"AreaLightShape", (0, "p_enum", False)), # Rectangle.
- ))
+ props = {
+ b"LightType": (0, "p_enum", False), # Point light.
+ b"CastLight": (True, "p_bool", False),
+ b"Color": ((1.0, 1.0, 1.0), "p_color", True),
+ b"Intensity": (100.0, "p_number", True), # Times 100 compared to Blender values...
+ b"DecayType": (2, "p_enum", False), # Quadratic.
+ b"DecayStart": (30.0 * gscale, "p_double", False),
+ b"CastShadows": (True, "p_bool", False),
+ b"ShadowColor": ((0.0, 0.0, 0.0), "p_color", True),
+ b"AreaLightShape": (0, "p_enum", False), # Rectangle.
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"NodeAttribute", b"FbxLight", props, nbr_users, [False])
@@ -228,137 +229,137 @@ def fbx_template_def_light(scene, settings, override_defaults=None, nbr_users=0)
def fbx_template_def_camera(scene, settings, override_defaults=None, nbr_users=0):
r = scene.render
- props = OrderedDict((
- (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
- (b"Position", ((0.0, 0.0, -50.0), "p_vector", True)),
- (b"UpVector", ((0.0, 1.0, 0.0), "p_vector", True)),
- (b"InterestPosition", ((0.0, 0.0, 0.0), "p_vector", True)),
- (b"Roll", (0.0, "p_roll", True)),
- (b"OpticalCenterX", (0.0, "p_opticalcenterx", True)),
- (b"OpticalCenterY", (0.0, "p_opticalcentery", True)),
- (b"BackgroundColor", ((0.63, 0.63, 0.63), "p_color", True)),
- (b"TurnTable", (0.0, "p_number", True)),
- (b"DisplayTurnTableIcon", (False, "p_bool", False)),
- (b"UseMotionBlur", (False, "p_bool", False)),
- (b"UseRealTimeMotionBlur", (True, "p_bool", False)),
- (b"Motion Blur Intensity", (1.0, "p_number", True)),
- (b"AspectRatioMode", (0, "p_enum", False)), # WindowSize.
- (b"AspectWidth", (320.0, "p_double", False)),
- (b"AspectHeight", (200.0, "p_double", False)),
- (b"PixelAspectRatio", (1.0, "p_double", False)),
- (b"FilmOffsetX", (0.0, "p_number", True)),
- (b"FilmOffsetY", (0.0, "p_number", True)),
- (b"FilmWidth", (0.816, "p_double", False)),
- (b"FilmHeight", (0.612, "p_double", False)),
- (b"FilmAspectRatio", (1.3333333333333333, "p_double", False)),
- (b"FilmSqueezeRatio", (1.0, "p_double", False)),
- (b"FilmFormatIndex", (0, "p_enum", False)), # Assuming this is ApertureFormat, 0 = custom.
- (b"PreScale", (1.0, "p_number", True)),
- (b"FilmTranslateX", (0.0, "p_number", True)),
- (b"FilmTranslateY", (0.0, "p_number", True)),
- (b"FilmRollPivotX", (0.0, "p_number", True)),
- (b"FilmRollPivotY", (0.0, "p_number", True)),
- (b"FilmRollValue", (0.0, "p_number", True)),
- (b"FilmRollOrder", (0, "p_enum", False)), # 0 = rotate first (default).
- (b"ApertureMode", (2, "p_enum", False)), # 2 = Vertical.
- (b"GateFit", (0, "p_enum", False)), # 0 = no resolution gate fit.
- (b"FieldOfView", (25.114999771118164, "p_fov", True)),
- (b"FieldOfViewX", (40.0, "p_fov_x", True)),
- (b"FieldOfViewY", (40.0, "p_fov_y", True)),
- (b"FocalLength", (34.89327621672628, "p_number", True)),
- (b"CameraFormat", (0, "p_enum", False)), # Custom camera format.
- (b"UseFrameColor", (False, "p_bool", False)),
- (b"FrameColor", ((0.3, 0.3, 0.3), "p_color_rgb", False)),
- (b"ShowName", (True, "p_bool", False)),
- (b"ShowInfoOnMoving", (True, "p_bool", False)),
- (b"ShowGrid", (True, "p_bool", False)),
- (b"ShowOpticalCenter", (False, "p_bool", False)),
- (b"ShowAzimut", (True, "p_bool", False)),
- (b"ShowTimeCode", (False, "p_bool", False)),
- (b"ShowAudio", (False, "p_bool", False)),
- (b"AudioColor", ((0.0, 1.0, 0.0), "p_vector_3d", False)), # Yep, vector3d, not corlorgb… :cry:
- (b"NearPlane", (10.0, "p_double", False)),
- (b"FarPlane", (4000.0, "p_double", False)),
- (b"AutoComputeClipPanes", (False, "p_bool", False)),
- (b"ViewCameraToLookAt", (True, "p_bool", False)),
- (b"ViewFrustumNearFarPlane", (False, "p_bool", False)),
- (b"ViewFrustumBackPlaneMode", (2, "p_enum", False)), # 2 = show back plane if texture added.
- (b"BackPlaneDistance", (4000.0, "p_number", True)),
- (b"BackPlaneDistanceMode", (1, "p_enum", False)), # 1 = relative to camera.
- (b"ViewFrustumFrontPlaneMode", (2, "p_enum", False)), # 2 = show front plane if texture added.
- (b"FrontPlaneDistance", (10.0, "p_number", True)),
- (b"FrontPlaneDistanceMode", (1, "p_enum", False)), # 1 = relative to camera.
- (b"LockMode", (False, "p_bool", False)),
- (b"LockInterestNavigation", (False, "p_bool", False)),
+ props = {
+ b"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False),
+ b"Position": ((0.0, 0.0, -50.0), "p_vector", True),
+ b"UpVector": ((0.0, 1.0, 0.0), "p_vector", True),
+ b"InterestPosition": ((0.0, 0.0, 0.0), "p_vector", True),
+ b"Roll": (0.0, "p_roll", True),
+ b"OpticalCenterX": (0.0, "p_opticalcenterx", True),
+ b"OpticalCenterY": (0.0, "p_opticalcentery", True),
+ b"BackgroundColor": ((0.63, 0.63, 0.63), "p_color", True),
+ b"TurnTable": (0.0, "p_number", True),
+ b"DisplayTurnTableIcon": (False, "p_bool", False),
+ b"UseMotionBlur": (False, "p_bool", False),
+ b"UseRealTimeMotionBlur": (True, "p_bool", False),
+ b"Motion Blur Intensity": (1.0, "p_number", True),
+ b"AspectRatioMode": (0, "p_enum", False), # WindowSize.
+ b"AspectWidth": (320.0, "p_double", False),
+ b"AspectHeight": (200.0, "p_double", False),
+ b"PixelAspectRatio": (1.0, "p_double", False),
+ b"FilmOffsetX": (0.0, "p_number", True),
+ b"FilmOffsetY": (0.0, "p_number", True),
+ b"FilmWidth": (0.816, "p_double", False),
+ b"FilmHeight": (0.612, "p_double", False),
+ b"FilmAspectRatio": (1.3333333333333333, "p_double", False),
+ b"FilmSqueezeRatio": (1.0, "p_double", False),
+ b"FilmFormatIndex": (0, "p_enum", False), # Assuming this is ApertureFormat, 0 = custom.
+ b"PreScale": (1.0, "p_number", True),
+ b"FilmTranslateX": (0.0, "p_number", True),
+ b"FilmTranslateY": (0.0, "p_number", True),
+ b"FilmRollPivotX": (0.0, "p_number", True),
+ b"FilmRollPivotY": (0.0, "p_number", True),
+ b"FilmRollValue": (0.0, "p_number", True),
+ b"FilmRollOrder": (0, "p_enum", False), # 0 = rotate first (default).
+ b"ApertureMode": (2, "p_enum", False), # 2 = Vertical.
+ b"GateFit": (0, "p_enum", False), # 0 = no resolution gate fit.
+ b"FieldOfView": (25.114999771118164, "p_fov", True),
+ b"FieldOfViewX": (40.0, "p_fov_x", True),
+ b"FieldOfViewY": (40.0, "p_fov_y", True),
+ b"FocalLength": (34.89327621672628, "p_number", True),
+ b"CameraFormat": (0, "p_enum", False), # Custom camera format.
+ b"UseFrameColor": (False, "p_bool", False),
+ b"FrameColor": ((0.3, 0.3, 0.3), "p_color_rgb", False),
+ b"ShowName": (True, "p_bool", False),
+ b"ShowInfoOnMoving": (True, "p_bool", False),
+ b"ShowGrid": (True, "p_bool", False),
+ b"ShowOpticalCenter": (False, "p_bool", False),
+ b"ShowAzimut": (True, "p_bool", False),
+ b"ShowTimeCode": (False, "p_bool", False),
+ b"ShowAudio": (False, "p_bool", False),
+ b"AudioColor": ((0.0, 1.0, 0.0), "p_vector_3d", False), # Yep, vector3d, not corlorgb… :cry:
+ b"NearPlane": (10.0, "p_double", False),
+ b"FarPlane": (4000.0, "p_double", False),
+ b"AutoComputeClipPanes": (False, "p_bool", False),
+ b"ViewCameraToLookAt": (True, "p_bool", False),
+ b"ViewFrustumNearFarPlane": (False, "p_bool", False),
+ b"ViewFrustumBackPlaneMode": (2, "p_enum", False), # 2 = show back plane if texture added.
+ b"BackPlaneDistance": (4000.0, "p_number", True),
+ b"BackPlaneDistanceMode": (1, "p_enum", False), # 1 = relative to camera.
+ b"ViewFrustumFrontPlaneMode": (2, "p_enum", False), # 2 = show front plane if texture added.
+ b"FrontPlaneDistance": (10.0, "p_number", True),
+ b"FrontPlaneDistanceMode": (1, "p_enum", False), # 1 = relative to camera.
+ b"LockMode": (False, "p_bool", False),
+ b"LockInterestNavigation": (False, "p_bool", False),
# BackPlate... properties **arggggg!**
- (b"FitImage", (False, "p_bool", False)),
- (b"Crop", (False, "p_bool", False)),
- (b"Center", (True, "p_bool", False)),
- (b"KeepRatio", (True, "p_bool", False)),
+ b"FitImage": (False, "p_bool", False),
+ b"Crop": (False, "p_bool", False),
+ b"Center": (True, "p_bool", False),
+ b"KeepRatio": (True, "p_bool", False),
# End of BackPlate...
- (b"BackgroundAlphaTreshold", (0.5, "p_double", False)),
- (b"ShowBackplate", (True, "p_bool", False)),
- (b"BackPlaneOffsetX", (0.0, "p_number", True)),
- (b"BackPlaneOffsetY", (0.0, "p_number", True)),
- (b"BackPlaneRotation", (0.0, "p_number", True)),
- (b"BackPlaneScaleX", (1.0, "p_number", True)),
- (b"BackPlaneScaleY", (1.0, "p_number", True)),
- (b"Background Texture", (None, "p_object", False)),
- (b"FrontPlateFitImage", (True, "p_bool", False)),
- (b"FrontPlateCrop", (False, "p_bool", False)),
- (b"FrontPlateCenter", (True, "p_bool", False)),
- (b"FrontPlateKeepRatio", (True, "p_bool", False)),
- (b"Foreground Opacity", (1.0, "p_double", False)),
- (b"ShowFrontplate", (True, "p_bool", False)),
- (b"FrontPlaneOffsetX", (0.0, "p_number", True)),
- (b"FrontPlaneOffsetY", (0.0, "p_number", True)),
- (b"FrontPlaneRotation", (0.0, "p_number", True)),
- (b"FrontPlaneScaleX", (1.0, "p_number", True)),
- (b"FrontPlaneScaleY", (1.0, "p_number", True)),
- (b"Foreground Texture", (None, "p_object", False)),
- (b"DisplaySafeArea", (False, "p_bool", False)),
- (b"DisplaySafeAreaOnRender", (False, "p_bool", False)),
- (b"SafeAreaDisplayStyle", (1, "p_enum", False)), # 1 = rounded corners.
- (b"SafeAreaAspectRatio", (1.3333333333333333, "p_double", False)),
- (b"Use2DMagnifierZoom", (False, "p_bool", False)),
- (b"2D Magnifier Zoom", (100.0, "p_number", True)),
- (b"2D Magnifier X", (50.0, "p_number", True)),
- (b"2D Magnifier Y", (50.0, "p_number", True)),
- (b"CameraProjectionType", (0, "p_enum", False)), # 0 = perspective, 1 = orthogonal.
- (b"OrthoZoom", (1.0, "p_double", False)),
- (b"UseRealTimeDOFAndAA", (False, "p_bool", False)),
- (b"UseDepthOfField", (False, "p_bool", False)),
- (b"FocusSource", (0, "p_enum", False)), # 0 = camera interest, 1 = distance from camera interest.
- (b"FocusAngle", (3.5, "p_double", False)), # ???
- (b"FocusDistance", (200.0, "p_double", False)),
- (b"UseAntialiasing", (False, "p_bool", False)),
- (b"AntialiasingIntensity", (0.77777, "p_double", False)),
- (b"AntialiasingMethod", (0, "p_enum", False)), # 0 = oversampling, 1 = hardware.
- (b"UseAccumulationBuffer", (False, "p_bool", False)),
- (b"FrameSamplingCount", (7, "p_integer", False)),
- (b"FrameSamplingType", (1, "p_enum", False)), # 0 = uniform, 1 = stochastic.
- ))
+ b"BackgroundAlphaTreshold": (0.5, "p_double", False),
+ b"ShowBackplate": (True, "p_bool", False),
+ b"BackPlaneOffsetX": (0.0, "p_number", True),
+ b"BackPlaneOffsetY": (0.0, "p_number", True),
+ b"BackPlaneRotation": (0.0, "p_number", True),
+ b"BackPlaneScaleX": (1.0, "p_number", True),
+ b"BackPlaneScaleY": (1.0, "p_number", True),
+ b"Background Texture": (None, "p_object", False),
+ b"FrontPlateFitImage": (True, "p_bool", False),
+ b"FrontPlateCrop": (False, "p_bool", False),
+ b"FrontPlateCenter": (True, "p_bool", False),
+ b"FrontPlateKeepRatio": (True, "p_bool", False),
+ b"Foreground Opacity": (1.0, "p_double", False),
+ b"ShowFrontplate": (True, "p_bool", False),
+ b"FrontPlaneOffsetX": (0.0, "p_number", True),
+ b"FrontPlaneOffsetY": (0.0, "p_number", True),
+ b"FrontPlaneRotation": (0.0, "p_number", True),
+ b"FrontPlaneScaleX": (1.0, "p_number", True),
+ b"FrontPlaneScaleY": (1.0, "p_number", True),
+ b"Foreground Texture": (None, "p_object", False),
+ b"DisplaySafeArea": (False, "p_bool", False),
+ b"DisplaySafeAreaOnRender": (False, "p_bool", False),
+ b"SafeAreaDisplayStyle": (1, "p_enum", False), # 1 = rounded corners.
+ b"SafeAreaAspectRatio": (1.3333333333333333, "p_double", False),
+ b"Use2DMagnifierZoom": (False, "p_bool", False),
+ b"2D Magnifier Zoom": (100.0, "p_number", True),
+ b"2D Magnifier X": (50.0, "p_number", True),
+ b"2D Magnifier Y": (50.0, "p_number", True),
+ b"CameraProjectionType": (0, "p_enum", False), # 0 = perspective, 1 = orthogonal.
+ b"OrthoZoom": (1.0, "p_double", False),
+ b"UseRealTimeDOFAndAA": (False, "p_bool", False),
+ b"UseDepthOfField": (False, "p_bool", False),
+ b"FocusSource": (0, "p_enum", False), # 0 = camera interest, 1 = distance from camera interest.
+ b"FocusAngle": (3.5, "p_double", False), # ???
+ b"FocusDistance": (200.0, "p_double", False),
+ b"UseAntialiasing": (False, "p_bool", False),
+ b"AntialiasingIntensity": (0.77777, "p_double", False),
+ b"AntialiasingMethod": (0, "p_enum", False), # 0 = oversampling, 1 = hardware.
+ b"UseAccumulationBuffer": (False, "p_bool", False),
+ b"FrameSamplingCount": (7, "p_integer", False),
+ b"FrameSamplingType": (1, "p_enum", False), # 0 = uniform, 1 = stochastic.
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"NodeAttribute", b"FbxCamera", props, nbr_users, [False])
def fbx_template_def_bone(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict()
+ props = {}
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"NodeAttribute", b"LimbNode", props, nbr_users, [False])
def fbx_template_def_geometry(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict((
- (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
- (b"BBoxMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"BBoxMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"Primary Visibility", (True, "p_bool", False)),
- (b"Casts Shadows", (True, "p_bool", False)),
- (b"Receive Shadows", (True, "p_bool", False)),
- ))
+ props = {
+ b"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False),
+ b"BBoxMin": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"BBoxMax": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"Primary Visibility": (True, "p_bool", False),
+ b"Casts Shadows": (True, "p_bool", False),
+ b"Receive Shadows": (True, "p_bool", False),
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"Geometry", b"FbxMesh", props, nbr_users, [False])
@@ -366,37 +367,37 @@ def fbx_template_def_geometry(scene, settings, override_defaults=None, nbr_users
def fbx_template_def_material(scene, settings, override_defaults=None, nbr_users=0):
# WIP...
- props = OrderedDict((
- (b"ShadingModel", ("Phong", "p_string", False)),
- (b"MultiLayer", (False, "p_bool", False)),
+ props = {
+ b"ShadingModel": ("Phong", "p_string", False),
+ b"MultiLayer": (False, "p_bool", False),
# Lambert-specific.
- (b"EmissiveColor", ((0.0, 0.0, 0.0), "p_color", True)),
- (b"EmissiveFactor", (1.0, "p_number", True)),
- (b"AmbientColor", ((0.2, 0.2, 0.2), "p_color", True)),
- (b"AmbientFactor", (1.0, "p_number", True)),
- (b"DiffuseColor", ((0.8, 0.8, 0.8), "p_color", True)),
- (b"DiffuseFactor", (1.0, "p_number", True)),
- (b"TransparentColor", ((0.0, 0.0, 0.0), "p_color", True)),
- (b"TransparencyFactor", (0.0, "p_number", True)),
- (b"Opacity", (1.0, "p_number", True)),
- (b"NormalMap", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"Bump", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"BumpFactor", (1.0, "p_double", False)),
- (b"DisplacementColor", ((0.0, 0.0, 0.0), "p_color_rgb", False)),
- (b"DisplacementFactor", (1.0, "p_double", False)),
- (b"VectorDisplacementColor", ((0.0, 0.0, 0.0), "p_color_rgb", False)),
- (b"VectorDisplacementFactor", (1.0, "p_double", False)),
+ b"EmissiveColor": ((0.0, 0.0, 0.0), "p_color", True),
+ b"EmissiveFactor": (1.0, "p_number", True),
+ b"AmbientColor": ((0.2, 0.2, 0.2), "p_color", True),
+ b"AmbientFactor": (1.0, "p_number", True),
+ b"DiffuseColor": ((0.8, 0.8, 0.8), "p_color", True),
+ b"DiffuseFactor": (1.0, "p_number", True),
+ b"TransparentColor": ((0.0, 0.0, 0.0), "p_color", True),
+ b"TransparencyFactor": (0.0, "p_number", True),
+ b"Opacity": (1.0, "p_number", True),
+ b"NormalMap": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"Bump": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"BumpFactor": (1.0, "p_double", False),
+ b"DisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb", False),
+ b"DisplacementFactor": (1.0, "p_double", False),
+ b"VectorDisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb", False),
+ b"VectorDisplacementFactor": (1.0, "p_double", False),
# Phong-specific.
- (b"SpecularColor", ((0.2, 0.2, 0.2), "p_color", True)),
- (b"SpecularFactor", (1.0, "p_number", True)),
+ b"SpecularColor": ((0.2, 0.2, 0.2), "p_color", True),
+ b"SpecularFactor": (1.0, "p_number", True),
# Not sure about the name, importer uses this (but ShininessExponent for tex prop name!)
# And in fbx exported by sdk, you have one in template, the other in actual material!!! :/
# For now, using both.
- (b"Shininess", (20.0, "p_number", True)),
- (b"ShininessExponent", (20.0, "p_number", True)),
- (b"ReflectionColor", ((0.0, 0.0, 0.0), "p_color", True)),
- (b"ReflectionFactor", (1.0, "p_number", True)),
- ))
+ b"Shininess": (20.0, "p_number", True),
+ b"ShininessExponent": (20.0, "p_number", True),
+ b"ReflectionColor": ((0.0, 0.0, 0.0), "p_color", True),
+ b"ReflectionFactor": (1.0, "p_number", True),
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"Material", b"FbxSurfacePhong", props, nbr_users, [False])
@@ -405,26 +406,26 @@ def fbx_template_def_material(scene, settings, override_defaults=None, nbr_users
def fbx_template_def_texture_file(scene, settings, override_defaults=None, nbr_users=0):
# WIP...
# XXX Not sure about all names!
- props = OrderedDict((
- (b"TextureTypeUse", (0, "p_enum", False)), # Standard.
- (b"AlphaSource", (2, "p_enum", False)), # Black (i.e. texture's alpha), XXX name guessed!.
- (b"Texture alpha", (1.0, "p_double", False)),
- (b"PremultiplyAlpha", (True, "p_bool", False)),
- (b"CurrentTextureBlendMode", (1, "p_enum", False)), # Additive...
- (b"CurrentMappingType", (0, "p_enum", False)), # UV.
- (b"UVSet", ("default", "p_string", False)), # UVMap name.
- (b"WrapModeU", (0, "p_enum", False)), # Repeat.
- (b"WrapModeV", (0, "p_enum", False)), # Repeat.
- (b"UVSwap", (False, "p_bool", False)),
- (b"Translation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"Rotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"Scaling", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
- (b"TextureRotationPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"TextureScalingPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
+ props = {
+ b"TextureTypeUse": (0, "p_enum", False), # Standard.
+ b"AlphaSource": (2, "p_enum", False), # Black (i.e. texture's alpha), XXX name guessed!.
+ b"Texture alpha": (1.0, "p_double", False),
+ b"PremultiplyAlpha": (True, "p_bool", False),
+ b"CurrentTextureBlendMode": (1, "p_enum", False), # Additive...
+ b"CurrentMappingType": (0, "p_enum", False), # UV.
+ b"UVSet": ("default", "p_string", False), # UVMap name.
+ b"WrapModeU": (0, "p_enum", False), # Repeat.
+ b"WrapModeV": (0, "p_enum", False), # Repeat.
+ b"UVSwap": (False, "p_bool", False),
+ b"Translation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"Rotation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"Scaling": ((1.0, 1.0, 1.0), "p_vector_3d", False),
+ b"TextureRotationPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"TextureScalingPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False),
# Not sure about those two...
- (b"UseMaterial", (False, "p_bool", False)),
- (b"UseMipMap", (False, "p_bool", False)),
- ))
+ b"UseMaterial": (False, "p_bool", False),
+ b"UseMipMap": (False, "p_bool", False),
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"Texture", b"FbxFileTexture", props, nbr_users, [False])
@@ -432,86 +433,86 @@ def fbx_template_def_texture_file(scene, settings, override_defaults=None, nbr_u
def fbx_template_def_video(scene, settings, override_defaults=None, nbr_users=0):
# WIP...
- props = OrderedDict((
+ props = {
# All pictures.
- (b"Width", (0, "p_integer", False)),
- (b"Height", (0, "p_integer", False)),
- (b"Path", ("", "p_string_url", False)),
- (b"AccessMode", (0, "p_enum", False)), # Disk (0=Disk, 1=Mem, 2=DiskAsync).
+ b"Width": (0, "p_integer", False),
+ b"Height": (0, "p_integer", False),
+ b"Path": ("", "p_string_url", False),
+ b"AccessMode": (0, "p_enum", False), # Disk (0=Disk, 1=Mem, 2=DiskAsync).
# All videos.
- (b"StartFrame", (0, "p_integer", False)),
- (b"StopFrame", (0, "p_integer", False)),
- (b"Offset", (0, "p_timestamp", False)),
- (b"PlaySpeed", (0.0, "p_double", False)),
- (b"FreeRunning", (False, "p_bool", False)),
- (b"Loop", (False, "p_bool", False)),
- (b"InterlaceMode", (0, "p_enum", False)), # None, i.e. progressive.
+ b"StartFrame": (0, "p_integer", False),
+ b"StopFrame": (0, "p_integer", False),
+ b"Offset": (0, "p_timestamp", False),
+ b"PlaySpeed": (0.0, "p_double", False),
+ b"FreeRunning": (False, "p_bool", False),
+ b"Loop": (False, "p_bool", False),
+ b"InterlaceMode": (0, "p_enum", False), # None, i.e. progressive.
# Image sequences.
- (b"ImageSequence", (False, "p_bool", False)),
- (b"ImageSequenceOffset", (0, "p_integer", False)),
- (b"FrameRate", (0.0, "p_double", False)),
- (b"LastFrame", (0, "p_integer", False)),
- ))
+ b"ImageSequence": (False, "p_bool", False),
+ b"ImageSequenceOffset": (0, "p_integer", False),
+ b"FrameRate": (0.0, "p_double", False),
+ b"LastFrame": (0, "p_integer", False),
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"Video", b"FbxVideo", props, nbr_users, [False])
def fbx_template_def_pose(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict()
+ props = {}
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"Pose", b"", props, nbr_users, [False])
def fbx_template_def_deformer(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict()
+ props = {}
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"Deformer", b"", props, nbr_users, [False])
def fbx_template_def_animstack(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict((
- (b"Description", ("", "p_string", False)),
- (b"LocalStart", (0, "p_timestamp", False)),
- (b"LocalStop", (0, "p_timestamp", False)),
- (b"ReferenceStart", (0, "p_timestamp", False)),
- (b"ReferenceStop", (0, "p_timestamp", False)),
- ))
+ props = {
+ b"Description": ("", "p_string", False),
+ b"LocalStart": (0, "p_timestamp", False),
+ b"LocalStop": (0, "p_timestamp", False),
+ b"ReferenceStart": (0, "p_timestamp", False),
+ b"ReferenceStop": (0, "p_timestamp", False),
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"AnimationStack", b"FbxAnimStack", props, nbr_users, [False])
def fbx_template_def_animlayer(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict((
- (b"Weight", (100.0, "p_number", True)),
- (b"Mute", (False, "p_bool", False)),
- (b"Solo", (False, "p_bool", False)),
- (b"Lock", (False, "p_bool", False)),
- (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
- (b"BlendMode", (0, "p_enum", False)),
- (b"RotationAccumulationMode", (0, "p_enum", False)),
- (b"ScaleAccumulationMode", (0, "p_enum", False)),
- (b"BlendModeBypass", (0, "p_ulonglong", False)),
- ))
+ props = {
+ b"Weight": (100.0, "p_number", True),
+ b"Mute": (False, "p_bool", False),
+ b"Solo": (False, "p_bool", False),
+ b"Lock": (False, "p_bool", False),
+ b"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False),
+ b"BlendMode": (0, "p_enum", False),
+ b"RotationAccumulationMode": (0, "p_enum", False),
+ b"ScaleAccumulationMode": (0, "p_enum", False),
+ b"BlendModeBypass": (0, "p_ulonglong", False),
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"AnimationLayer", b"FbxAnimLayer", props, nbr_users, [False])
def fbx_template_def_animcurvenode(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict((
- (FBX_ANIM_PROPSGROUP_NAME.encode(), (None, "p_compound", False)),
- ))
+ props = {
+ FBX_ANIM_PROPSGROUP_NAME.encode(): (None, "p_compound", False),
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"AnimationCurveNode", b"FbxAnimCurveNode", props, nbr_users, [False])
def fbx_template_def_animcurve(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict()
+ props = {}
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"AnimationCurve", b"", props, nbr_users, [False])
@@ -581,13 +582,13 @@ def fbx_data_empty_elements(root, empty, scene_data):
# No custom properties, already saved with object (Model).
-def fbx_data_lamp_elements(root, lamp, scene_data):
+def fbx_data_light_elements(root, lamp, scene_data):
"""
Write the Lamp data block.
"""
gscale = scene_data.settings.global_scale
- lamp_key = scene_data.data_lamps[lamp]
+ light_key = scene_data.data_lights[lamp]
do_light = True
decay_type = FBX_LIGHT_DECAY_TYPES['CONSTANT']
do_shadow = False
@@ -595,11 +596,11 @@ def fbx_data_lamp_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(lamp_key))
+ light = elem_data_single_int64(root, b"NodeAttribute", get_fbx_uuid_from_key(light_key))
light.add_string(fbx_name_class(lamp.name.encode(), b"NodeAttribute"))
light.add_string(b"Light")
@@ -639,8 +640,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
@@ -1035,7 +1036,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
# t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
uv_names = [uvlayer.name for uvlayer in me.uv_layers]
for name in uv_names:
- me.calc_tangents(name)
+ me.calc_tangents(uvmap=name)
for idx, uvlayer in enumerate(me.uv_layers):
name = uvlayer.name
# Loop bitangents (aka binormals).
@@ -1121,38 +1122,39 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
del _uvtuples_gen
# Face's materials.
- me_fbxmats_idx = scene_data.mesh_mat_indices.get(me)
- if me_fbxmats_idx is not None:
- me_blmats = me.materials
- if me_fbxmats_idx and me_blmats:
- lay_mat = elem_data_single_int32(geom, b"LayerElementMaterial", 0)
- elem_data_single_int32(lay_mat, b"Version", FBX_GEOMETRY_MATERIAL_VERSION)
- elem_data_single_string(lay_mat, b"Name", b"")
- nbr_mats = len(me_fbxmats_idx)
+ me_fbxmaterials_idx = scene_data.mesh_material_indices.get(me)
+ if me_fbxmaterials_idx is not None:
+ me_blmaterials = me.materials
+ if me_fbxmaterials_idx and me_blmaterials:
+ lay_ma = elem_data_single_int32(geom, b"LayerElementMaterial", 0)
+ elem_data_single_int32(lay_ma, b"Version", FBX_GEOMETRY_MATERIAL_VERSION)
+ elem_data_single_string(lay_ma, b"Name", b"")
+ nbr_mats = len(me_fbxmaterials_idx)
if nbr_mats > 1:
t_pm = array.array(data_types.ARRAY_INT32, (0,)) * len(me.polygons)
me.polygons.foreach_get("material_index", t_pm)
# We have to validate mat indices, and map them to FBX indices.
# Note a mat might not be in me_fbxmats_idx (e.g. node mats are ignored).
- blmats_to_fbxmats_idxs = [me_fbxmats_idx[m] for m in me_blmats if m in me_fbxmats_idx]
- mat_idx_limit = len(blmats_to_fbxmats_idxs)
- def_mat = blmats_to_fbxmats_idxs[0]
- _gen = (blmats_to_fbxmats_idxs[m] if m < mat_idx_limit else def_mat for m in t_pm)
+ blmaterials_to_fbxmaterials_idxs = [me_fbxmaterials_idx[m]
+ for m in me_blmaterials if m in me_fbxmaterials_idx]
+ ma_idx_limit = len(blmaterials_to_fbxmaterials_idxs)
+ def_ma = blmaterials_to_fbxmaterials_idxs[0]
+ _gen = (blmaterials_to_fbxmaterials_idxs[m] if m < ma_idx_limit else def_ma for m in t_pm)
t_pm = array.array(data_types.ARRAY_INT32, _gen)
- elem_data_single_string(lay_mat, b"MappingInformationType", b"ByPolygon")
+ elem_data_single_string(lay_ma, b"MappingInformationType", b"ByPolygon")
# XXX Logically, should be "Direct" reference type, since we do not have any index array, and have one
# value per polygon...
# But looks like FBX expects it to be IndexToDirect here (maybe because materials are already
# indices??? *sigh*).
- elem_data_single_string(lay_mat, b"ReferenceInformationType", b"IndexToDirect")
- elem_data_single_int32_array(lay_mat, b"Materials", t_pm)
+ elem_data_single_string(lay_ma, b"ReferenceInformationType", b"IndexToDirect")
+ elem_data_single_int32_array(lay_ma, b"Materials", t_pm)
del t_pm
else:
- elem_data_single_string(lay_mat, b"MappingInformationType", b"AllSame")
- elem_data_single_string(lay_mat, b"ReferenceInformationType", b"IndexToDirect")
- elem_data_single_int32_array(lay_mat, b"Materials", [0])
+ elem_data_single_string(lay_ma, b"MappingInformationType", b"AllSame")
+ elem_data_single_string(lay_ma, b"ReferenceInformationType", b"IndexToDirect")
+ elem_data_single_int32_array(lay_ma, b"Materials", [0])
# And the "layer TOC"...
@@ -1181,10 +1183,10 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
lay_uv = elem_empty(layer, b"LayerElement")
elem_data_single_string(lay_uv, b"Type", b"LayerElementUV")
elem_data_single_int32(lay_uv, b"TypedIndex", 0)
- if me_fbxmats_idx is not None:
- lay_mat = elem_empty(layer, b"LayerElement")
- elem_data_single_string(lay_mat, b"Type", b"LayerElementMaterial")
- elem_data_single_int32(lay_mat, b"TypedIndex", 0)
+ if me_fbxmaterials_idx is not None:
+ lay_ma = elem_empty(layer, b"LayerElement")
+ elem_data_single_string(lay_ma, b"Type", b"LayerElementMaterial")
+ elem_data_single_int32(lay_ma, b"TypedIndex", 0)
# Add other uv and/or vcol layers...
for vcolidx, uvidx, tspaceidx in zip_longest(range(1, vcolnumber), range(1, uvnumber), range(1, tspacenumber),
@@ -1214,76 +1216,70 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
done_meshes.add(me_key)
-def check_skip_material(mat):
- """Simple helper to check whether we actually support exporting that material or not"""
- return mat.type not in {'SURFACE'}
-
-
-def fbx_data_material_elements(root, mat, scene_data):
+def fbx_data_material_elements(root, ma, scene_data):
"""
Write the Material data block.
"""
+
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)
- node_mat = mat.use_nodes
- mat_type = b"Phong"
- # Approximation...
- if not skip_mat and not node_mat and mat.specular_shader not in {'COOKTORR', 'PHONG', 'BLINN'}:
- mat_type = b"Lambert"
+ ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=True)
+ ma_key, _objs = scene_data.data_materials[ma]
+ ma_type = b"Phong"
- fbx_mat = elem_data_single_int64(root, b"Material", get_fbx_uuid_from_key(mat_key))
- fbx_mat.add_string(fbx_name_class(mat.name.encode(), b"Material"))
- fbx_mat.add_string(b"")
+ fbx_ma = elem_data_single_int64(root, b"Material", get_fbx_uuid_from_key(ma_key))
+ fbx_ma.add_string(fbx_name_class(ma.name.encode(), b"Material"))
+ fbx_ma.add_string(b"")
- elem_data_single_int32(fbx_mat, b"Version", FBX_MATERIAL_VERSION)
+ elem_data_single_int32(fbx_ma, b"Version", FBX_MATERIAL_VERSION)
# those are not yet properties, it seems...
- elem_data_single_string(fbx_mat, b"ShadingModel", mat_type)
- elem_data_single_int32(fbx_mat, b"MultiLayer", 0) # Should be bool...
+ elem_data_single_string(fbx_ma, b"ShadingModel", ma_type)
+ elem_data_single_int32(fbx_ma, b"MultiLayer", 0) # Should be bool...
tmpl = elem_props_template_init(scene_data.templates, b"Material")
- props = elem_properties(fbx_mat)
-
- if not skip_mat:
- elem_props_template_set(tmpl, props, "p_string", b"ShadingModel", mat_type.decode())
- elem_props_template_set(tmpl, props, "p_color", b"DiffuseColor", mat.diffuse_color)
- elem_props_template_set(tmpl, props, "p_number", b"DiffuseFactor", mat.diffuse_intensity)
- if not node_mat:
- elem_props_template_set(tmpl, props, "p_color", b"EmissiveColor", mat.diffuse_color)
- elem_props_template_set(tmpl, props, "p_number", b"EmissiveFactor", mat.emit)
- elem_props_template_set(tmpl, props, "p_color", b"AmbientColor", ambient_color)
- elem_props_template_set(tmpl, props, "p_number", b"AmbientFactor", mat.ambient)
- elem_props_template_set(tmpl, props, "p_color", b"TransparentColor",
- mat.diffuse_color if mat.use_transparency else (1.0, 1.0, 1.0))
- elem_props_template_set(tmpl, props, "p_number", b"TransparencyFactor",
- 1.0 - mat.alpha if mat.use_transparency else 0.0)
- elem_props_template_set(tmpl, props, "p_number", b"Opacity", mat.alpha if mat.use_transparency else 1.0)
- elem_props_template_set(tmpl, props, "p_vector_3d", b"NormalMap", (0.0, 0.0, 0.0))
- # Not sure about those...
- """
- b"Bump": ((0.0, 0.0, 0.0), "p_vector_3d"),
- b"BumpFactor": (1.0, "p_double"),
- b"DisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb"),
- b"DisplacementFactor": (0.0, "p_double"),
- """
- if mat_type == b"Phong":
- elem_props_template_set(tmpl, props, "p_color", b"SpecularColor", mat.specular_color)
- elem_props_template_set(tmpl, props, "p_number", b"SpecularFactor", mat.specular_intensity / 2.0)
- # See Material template about those two!
- elem_props_template_set(tmpl, props, "p_number", b"Shininess", (mat.specular_hardness - 1.0) / 5.10)
- elem_props_template_set(tmpl, props, "p_number", b"ShininessExponent", (mat.specular_hardness - 1.0) / 5.10)
- elem_props_template_set(tmpl, props, "p_color", b"ReflectionColor", mat.mirror_color)
- elem_props_template_set(tmpl, props, "p_number", b"ReflectionFactor",
- mat.raytrace_mirror.reflect_factor if mat.raytrace_mirror.use else 0.0)
+ props = elem_properties(fbx_ma)
+
+ elem_props_template_set(tmpl, props, "p_string", b"ShadingModel", ma_type.decode())
+ elem_props_template_set(tmpl, props, "p_color", b"DiffuseColor", ma_wrap.base_color)
+ # Not in Principled BSDF, so assuming always 1
+ elem_props_template_set(tmpl, props, "p_number", b"DiffuseFactor", 1.0)
+ # Not in Principled BSDF, so assuming always 0
+ elem_props_template_set(tmpl, props, "p_color", b"EmissiveColor", ma_wrap.base_color)
+ elem_props_template_set(tmpl, props, "p_number", b"EmissiveFactor", 0.0)
+ # Not in Principled BSDF, so assuming always 0
+ elem_props_template_set(tmpl, props, "p_color", b"AmbientColor", ambient_color)
+ elem_props_template_set(tmpl, props, "p_number", b"AmbientFactor", 0.0)
+ elem_props_template_set(tmpl, props, "p_color", b"TransparentColor", ma_wrap.base_color)
+ elem_props_template_set(tmpl, props, "p_number", b"TransparencyFactor", ma_wrap.transmission)
+ elem_props_template_set(tmpl, props, "p_number", b"Opacity", 1.0 - ma_wrap.transmission)
+ elem_props_template_set(tmpl, props, "p_vector_3d", b"NormalMap", (0.0, 0.0, 0.0))
+ # Not sure about those...
+ """
+ b"Bump": ((0.0, 0.0, 0.0), "p_vector_3d"),
+ b"BumpFactor": (1.0, "p_double"),
+ b"DisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb"),
+ b"DisplacementFactor": (0.0, "p_double"),
+ """
+ # TODO: use specular tint?
+ elem_props_template_set(tmpl, props, "p_color", b"SpecularColor", ma_wrap.base_color)
+ elem_props_template_set(tmpl, props, "p_number", b"SpecularFactor", ma_wrap.specular / 2.0)
+ # See Material template about those two!
+ # XXX Totally empirical conversion, trying to adapt it
+ # (from 0.0 - 100.0 FBX shininess range to 1.0 - 0.0 Principled BSDF range)...
+ shininess = (1.0 - ma_wrap.roughness) * 10
+ shininess *= shininess
+ elem_props_template_set(tmpl, props, "p_number", b"Shininess", shininess)
+ elem_props_template_set(tmpl, props, "p_number", b"ShininessExponent", shininess)
+ elem_props_template_set(tmpl, props, "p_color", b"ReflectionColor", ma_wrap.base_color)
+ elem_props_template_set(tmpl, props, "p_number", b"ReflectionFactor", ma_wrap.metallic)
elem_props_template_finalize(tmpl, props)
# Custom properties.
if scene_data.settings.use_custom_props:
- fbx_data_element_custom_properties(props, mat)
+ fbx_data_element_custom_properties(props, ma)
def _gen_vid_path(img, scene_data):
@@ -1294,7 +1290,7 @@ def _gen_vid_path(img, scene_data):
return fname_abs, fname_rel
-def fbx_data_texture_file_elements(root, tex, scene_data):
+def fbx_data_texture_file_elements(root, blender_tex_key, scene_data):
"""
Write the (file) Texture data block.
"""
@@ -1302,45 +1298,50 @@ def fbx_data_texture_file_elements(root, tex, scene_data):
# Textures do not seem to use properties as much as they could.
# For now assuming most logical and simple stuff.
- tex_key, _mats = scene_data.data_textures[tex]
- img = tex.texture.image
+ ma, sock_name = blender_tex_key
+ ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=True)
+ tex_key, _fbx_prop = scene_data.data_textures[blender_tex_key]
+ tex = getattr(ma_wrap, sock_name)
+ img = tex.image
fname_abs, fname_rel = _gen_vid_path(img, scene_data)
fbx_tex = elem_data_single_int64(root, b"Texture", get_fbx_uuid_from_key(tex_key))
- fbx_tex.add_string(fbx_name_class(tex.name.encode(), b"Texture"))
+ fbx_tex.add_string(fbx_name_class(sock_name.encode(), b"Texture"))
fbx_tex.add_string(b"")
elem_data_single_string(fbx_tex, b"Type", b"TextureVideoClip")
elem_data_single_int32(fbx_tex, b"Version", FBX_TEXTURE_VERSION)
- elem_data_single_string(fbx_tex, b"TextureName", fbx_name_class(tex.name.encode(), b"Texture"))
+ elem_data_single_string(fbx_tex, b"TextureName", fbx_name_class(sock_name.encode(), b"Texture"))
elem_data_single_string(fbx_tex, b"Media", fbx_name_class(img.name.encode(), b"Video"))
elem_data_single_string_unicode(fbx_tex, b"FileName", fname_abs)
elem_data_single_string_unicode(fbx_tex, b"RelativeFilename", fname_rel)
alpha_source = 0 # None
if img.use_alpha:
- if tex.texture.use_calculate_alpha:
- alpha_source = 1 # RGBIntensity as alpha.
- else:
- alpha_source = 2 # Black, i.e. alpha channel.
+ # ~ if tex.texture.use_calculate_alpha:
+ # ~ alpha_source = 1 # RGBIntensity as alpha.
+ # ~ else:
+ # ~ alpha_source = 2 # Black, i.e. alpha channel.
+ alpha_source = 2 # Black, i.e. alpha channel.
# BlendMode not useful for now, only affects layered textures afaics.
mapping = 0 # UV.
uvset = None
- if tex.texture_coords in {'ORCO'}: # XXX Others?
- if tex.mapping in {'FLAT'}:
+ if tex.texcoords == 'ORCO': # XXX Others?
+ if tex.projection == 'FLAT':
mapping = 1 # Planar
- elif tex.mapping in {'CUBE'}:
+ elif tex.projection == 'CUBE':
mapping = 4 # Box
- elif tex.mapping in {'TUBE'}:
+ elif tex.projection == 'TUBE':
mapping = 3 # Cylindrical
- elif tex.mapping in {'SPHERE'}:
+ elif tex.projection == 'SPHERE':
mapping = 2 # Spherical
- elif tex.texture_coords in {'UV'}:
+ elif tex.texcoords == 'UV':
mapping = 0 # UV
# Yuck, UVs are linked by mere names it seems... :/
- uvset = tex.uv_layer
+ # XXX TODO how to get that now???
+ # uvset = tex.uv_layer
wrap_mode = 1 # Clamp
- if tex.texture.extension in {'REPEAT'}:
+ if tex.extension == 'REPEAT':
wrap_mode = 0 # Repeat
tmpl = elem_props_template_init(scene_data.templates, b"TextureFile")
@@ -1353,16 +1354,15 @@ def fbx_data_texture_file_elements(root, tex, scene_data):
elem_props_template_set(tmpl, props, "p_string", b"UVSet", uvset)
elem_props_template_set(tmpl, props, "p_enum", b"WrapModeU", wrap_mode)
elem_props_template_set(tmpl, props, "p_enum", b"WrapModeV", wrap_mode)
- elem_props_template_set(tmpl, props, "p_vector_3d", b"Translation", tex.offset)
- elem_props_template_set(tmpl, props, "p_vector_3d", b"Scaling", tex.scale)
+ elem_props_template_set(tmpl, props, "p_vector_3d", b"Translation", tex.translation)
+ elem_props_template_set(tmpl, props, "p_vector_3d", b"Rotation", (-r for r in tex.rotation))
+ elem_props_template_set(tmpl, props, "p_vector_3d", b"Scaling", (((1.0 / s) if s != 0.0 else 1.0) for s in tex.scale))
# UseMaterial should always be ON imho.
elem_props_template_set(tmpl, props, "p_bool", b"UseMaterial", True)
- elem_props_template_set(tmpl, props, "p_bool", b"UseMipMap", tex.texture.use_mipmap)
+ elem_props_template_set(tmpl, props, "p_bool", b"UseMipMap", False)
elem_props_template_finalize(tmpl, props)
- # Custom properties.
- if scene_data.settings.use_custom_props:
- fbx_data_element_custom_properties(props, tex.texture)
+ # No custom properties, since that's not a data-block anymore.
def fbx_data_video_elements(root, vid, scene_data):
@@ -1472,7 +1472,7 @@ def fbx_data_armature_elements(root, arm_obj, scene_data):
bo_vg_idx = {bo_obj.bdata.name: ob.vertex_groups[bo_obj.bdata.name].index
for bo_obj in clusters.keys() if bo_obj.bdata.name in ob.vertex_groups}
valid_idxs = set(bo_vg_idx.values())
- vgroups = {vg.index: OrderedDict() for vg in ob.vertex_groups}
+ vgroups = {vg.index: {} for vg in ob.vertex_groups}
verts_vgroups = (sorted(((vg.group, vg.weight) for vg in v.groups if vg.weight and vg.group in valid_idxs),
key=lambda e: e[1], reverse=True)
for v in me.vertices)
@@ -1507,7 +1507,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))
@@ -1580,7 +1580,7 @@ def fbx_data_object_elements(root, ob_obj, scene_data):
obj_type = b"Null"
elif (ob_obj.type in BLENDER_OBJECT_TYPES_MESHLIKE):
obj_type = b"Mesh"
- elif (ob_obj.type == 'LAMP'):
+ elif (ob_obj.type == 'LIGHT'):
obj_type = b"Light"
elif (ob_obj.type == 'CAMERA'):
obj_type = b"Camera"
@@ -1727,46 +1727,30 @@ def fbx_data_animation_elements(root, scene_data):
# ##### Top-level FBX data container. #####
-def fbx_mat_properties_from_texture(tex):
- """
- Returns a set of FBX metarial properties that are affected by the given texture.
- Quite obviously, this is a fuzzy and far-from-perfect mapping! Amounts of influence are completely lost, e.g.
- Note tex is actually expected to be a texture slot.
- """
- # Mapping Blender -> FBX (blend_use_name, blend_fact_name, fbx_name).
- blend_to_fbx = (
- # Lambert & Phong...
- ("diffuse", "diffuse", b"DiffuseFactor"),
- ("color_diffuse", "diffuse_color", b"DiffuseColor"),
- ("alpha", "alpha", b"TransparencyFactor"),
- ("diffuse", "diffuse", b"TransparentColor"), # Uses diffuse color in Blender!
- ("emit", "emit", b"EmissiveFactor"),
- ("diffuse", "diffuse", b"EmissiveColor"), # Uses diffuse color in Blender!
- ("ambient", "ambient", b"AmbientFactor"),
- # ("", "", b"AmbientColor"), # World stuff in Blender, for now ignore...
- ("normal", "normal", b"NormalMap"),
- # Note: unsure about those... :/
- # ("", "", b"Bump"),
- # ("", "", b"BumpFactor"),
- # ("", "", b"DisplacementColor"),
- # ("", "", b"DisplacementFactor"),
- # Phong only.
- ("specular", "specular", b"SpecularFactor"),
- ("color_spec", "specular_color", b"SpecularColor"),
- # See Material template about those two!
- ("hardness", "hardness", b"Shininess"),
- ("hardness", "hardness", b"ShininessExponent"),
- ("mirror", "mirror", b"ReflectionColor"),
- ("raymir", "raymir", b"ReflectionFactor"),
- )
-
- tex_fbx_props = set()
- for use_map_name, name_factor, fbx_prop_name in blend_to_fbx:
- # Always export enabled textures, even if they have a null influence...
- if getattr(tex, "use_map_" + use_map_name):
- tex_fbx_props.add(fbx_prop_name)
-
- return tex_fbx_props
+# Mapping Blender -> FBX (principled_socket_name, fbx_name).
+PRINCIPLED_TEXTURE_SOCKETS_TO_FBX = (
+ # ("diffuse", "diffuse", b"DiffuseFactor"),
+ ("base_color_texture", b"DiffuseColor"),
+ ("transmission_texture", b"TransparencyFactor"),
+ # ("base_color_texture", b"TransparentColor"), # Uses diffuse color in Blender!
+ # ("emit", "emit", b"EmissiveFactor"),
+ # ("diffuse", "diffuse", b"EmissiveColor"), # Uses diffuse color in Blender!
+ # ("ambient", "ambient", b"AmbientFactor"),
+ # ("", "", b"AmbientColor"), # World stuff in Blender, for now ignore...
+ ("normalmap_texture", b"NormalMap"),
+ # Note: unsure about those... :/
+ # ("", "", b"Bump"),
+ # ("", "", b"BumpFactor"),
+ # ("", "", b"DisplacementColor"),
+ # ("", "", b"DisplacementFactor"),
+ ("specular_texture", b"SpecularFactor"),
+ # ("base_color", b"SpecularColor"), # TODO: use tint?
+ # See Material template about those two!
+ ("roughness_texture", b"Shininess"),
+ ("roughness_texture", b"ShininessExponent"),
+ # ("mirror", "mirror", b"ReflectionColor"),
+ ("metallic_texture", b"ReflectionFactor"),
+)
def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes,
@@ -1781,7 +1765,7 @@ def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes,
data_empties[arm_obj] = get_blender_empty_key(arm_obj.bdata)
arm_data = arm_obj.bdata.data
- bones = OrderedDict()
+ bones = {}
for bo in arm_obj.bones:
if settings.use_armature_deform_only:
if bo.bdata.use_deform:
@@ -1795,7 +1779,7 @@ def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes,
else:
bones[bo] = True
- bones = OrderedDict((bo, None) for bo, use in bones.items() if use)
+ bones = {bo: None for bo, use in bones.items() if use}
if not bones:
return
@@ -1823,8 +1807,8 @@ def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes,
# Note: bindpose have no relations at all (no connections), so no need for any preprocess for them.
# Create skin & clusters relations (note skins are connected to geometry, *not* model!).
_key, me, _free = data_meshes[ob_obj]
- clusters = OrderedDict((bo, get_blender_bone_cluster_key(arm_obj.bdata, me, bo.bdata)) for bo in bones)
- data_deformers_skin.setdefault(arm_obj, OrderedDict())[me] = (get_blender_armature_skin_key(arm_obj.bdata, me),
+ clusters = {bo: get_blender_bone_cluster_key(arm_obj.bdata, me, bo.bdata) for bo in bones}
+ data_deformers_skin.setdefault(arm_obj, {})[me] = (get_blender_armature_skin_key(arm_obj.bdata, me),
ob_obj, clusters)
# We don't want a regular parent relationship for those in FBX...
@@ -1859,9 +1843,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
@@ -1874,6 +1858,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
@@ -1884,16 +1869,14 @@ 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
back_currframe = scene.frame_current
- animdata_ob = OrderedDict()
+ animdata_ob = {}
p_rots = {}
for ob_obj in objects:
@@ -1909,8 +1892,8 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
p_rots[ob_obj] = rot
force_key = (simplify_fac == 0.0)
+ animdata_shapes = {}
- animdata_shapes = OrderedDict()
for me, (me_key, _shapes_key, shapes) in scene_data.data_deformers_shape.items():
# Ignore absolute shape keys for now!
if not me.shape_keys.use_relative:
@@ -1921,7 +1904,7 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
acnode.add_group(me_key, shape.name, shape.name, (shape.name,))
animdata_shapes[channel_key] = (acnode, me, shape)
- animdata_cameras = OrderedDict()
+ animdata_cameras = {}
for cam_obj, cam_key in scene_data.data_cameras.items():
cam = cam_obj.bdata.data
acnode = AnimationCurveNodeWrapper(cam_key, 'CAMERA_FOCAL', force_key, force_sek, (cam.lens,))
@@ -1930,10 +1913,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)
@@ -1942,17 +1925,15 @@ 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()
+ animations = {}
# And now, produce final data (usable by FBX export code)
# Objects-like loc/rot/scale...
@@ -1962,34 +1943,28 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
if not anim:
continue
for obj_key, group_key, group, fbx_group, fbx_gname in anim.get_final_data(scene, ref_id, force_keep):
- anim_data = animations.get(obj_key)
- if anim_data is None:
- anim_data = animations[obj_key] = ("dummy_unused_key", OrderedDict())
+ anim_data = animations.setdefault(obj_key, ("dummy_unused_key", {}))
anim_data[1][fbx_group] = (group_key, group, fbx_gname)
# And meshes' shape keys.
for channel_key, (anim_shape, me, shape) in animdata_shapes.items():
- final_keys = OrderedDict()
+ final_keys = {}
anim_shape.simplify(simplify_fac, bake_step, force_keep)
if not anim_shape:
continue
for elem_key, group_key, group, fbx_group, fbx_gname in anim_shape.get_final_data(scene, ref_id, force_keep):
- anim_data = animations.get(elem_key)
- if anim_data is None:
- anim_data = animations[elem_key] = ("dummy_unused_key", OrderedDict())
- anim_data[1][fbx_group] = (group_key, group, fbx_gname)
+ anim_data = animations.setdefault(elem_key, ("dummy_unused_key", {}))
+ anim_data[1][fbx_group] = (group_key, group, fbx_gname)
# And cameras' lens keys.
for cam_key, (anim_camera, camera) in animdata_cameras.items():
- final_keys = OrderedDict()
+ final_keys = {}
anim_camera.simplify(simplify_fac, bake_step, force_keep)
if not anim_camera:
continue
for elem_key, group_key, group, fbx_group, fbx_gname in anim_camera.get_final_data(scene, ref_id, force_keep):
- anim_data = animations.get(elem_key)
- if anim_data is None:
- anim_data = animations[elem_key] = ("dummy_unused_key", OrderedDict())
- anim_data[1][fbx_group] = (group_key, group, fbx_gname)
+ anim_data = animations.setdefault(elem_key, ("dummy_unused_key", {}))
+ anim_data[1][fbx_group] = (group_key, group, fbx_gname)
astack_key = get_blender_anim_stack_key(scene, ref_id)
alayer_key = get_blender_anim_layer_key(scene, ref_id)
@@ -2055,7 +2030,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
@@ -2082,14 +2057,14 @@ def fbx_animations(scene_data):
'location', 'rotation_quaternion', 'rotation_axis_angle', 'rotation_euler', 'rotation_mode', 'scale',
'delta_location', 'delta_rotation_euler', 'delta_rotation_quaternion', 'delta_scale',
'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_draw_type', 'empty_draw_size', 'empty_image_offset', 'pass_index',
- 'color', 'hide', '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',
- 'draw_type', 'show_bounds', 'draw_bounds_type', 'show_name', 'show_axis', 'show_texture_space',
- 'show_wire', 'show_all_edges', 'show_transparent', 'show_x_ray',
+ 'tag', '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_viewport', 'hide_select', 'hide_render', 'use_slow_parent', 'slow_parent_offset',
+ 'instance_type', 'use_instance_frames_speed',
+ 'use_instance_vertices_rotation', 'use_instance_faces_scale', 'instance_faces_scale',
+ 'instance_frames_start', 'instance_frames_end', 'instance_frames_on', 'instance_frames_off',
+ 'display_type', 'show_bounds', 'display_bounds_type', 'show_name', 'show_axis', 'show_texture_space',
+ 'show_wire', 'show_all_edges', 'show_transparent', 'show_in_front',
'show_only_shape_key', 'use_shape_key_edit_mode', 'active_shape_key_index',
)
for p in props:
@@ -2134,7 +2109,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):
@@ -2142,19 +2117,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...
"""
@@ -2169,34 +2144,32 @@ def fbx_data_from_scene(scene, settings):
# This is rather simple for now, maybe we could end generating templates with most-used values
# instead of default ones?
- objects = OrderedDict() # Because we do not have any ordered set...
+ objects = {} # Because we do not have any ordered set...
for ob in settings.context_objects:
if ob.type not in objtypes:
continue
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)...")
- data_lamps = OrderedDict((ob_obj.bdata.data, get_blenderID_key(ob_obj.bdata.data))
- for ob_obj in objects if ob_obj.type == 'LAMP')
+ data_lights = {ob_obj.bdata.data: get_blenderID_key(ob_obj.bdata.data)
+ for ob_obj in objects if ob_obj.type == 'LIGHT'}
# Unfortunately, FBX camera data contains object-level data (like position, orientation, etc.)...
- data_cameras = OrderedDict((ob_obj, get_blenderID_key(ob_obj.bdata.data))
- for ob_obj in objects if ob_obj.type == 'CAMERA')
+ data_cameras = {ob_obj: get_blenderID_key(ob_obj.bdata.data)
+ for ob_obj in objects if ob_obj.type == 'CAMERA'}
# Yep! Contains nothing, but needed!
- data_empties = OrderedDict((ob_obj, get_blender_empty_key(ob_obj.bdata))
- for ob_obj in objects if ob_obj.type == 'EMPTY')
+ data_empties = {ob_obj: get_blender_empty_key(ob_obj.bdata)
+ for ob_obj in objects if ob_obj.type == 'EMPTY'}
perfmon.step("FBX export prepare: Wrapping Meshes...")
- data_meshes = OrderedDict()
+ data_meshes = {}
for ob_obj in objects:
if ob_obj.type not in BLENDER_OBJECT_TYPES_MESHLIKE:
continue
@@ -2221,21 +2194,22 @@ def fbx_data_from_scene(scene, settings):
# No need to create a new mesh in this case, if no modifier is active!
for mod in ob.modifiers:
# For meshes, when armature export is enabled, disable Armature modifiers here!
+ # XXX Temp hacks here since currently we only have access to a viewport depsgraph...
if mod.type == 'ARMATURE' and 'ARMATURE' in settings.object_types:
- tmp_mods.append((mod, mod.show_render))
+ tmp_mods.append((mod, mod.show_render, mod.show_viewport))
mod.show_render = False
- if mod.show_render:
+ mod.show_viewport = False
+ if mod.show_render or mod.show_viewport:
use_org_data = False
if not use_org_data:
tmp_me = ob.to_mesh(
- scene,
- apply_modifiers=settings.use_mesh_modifiers,
- settings='RENDER' if settings.use_mesh_modifiers_render else 'PREVIEW',
- )
+ depsgraph,
+ apply_modifiers=settings.use_mesh_modifiers)
data_meshes[ob_obj] = (get_blenderID_key(tmp_me), tmp_me, True)
# Re-enable temporary disabled modifiers.
- for mod, show_render in tmp_mods:
+ for mod, show_render, show_viewport in tmp_mods:
mod.show_render = show_render
+ mod.show_viewport = show_viewport
if use_org_data:
data_meshes[ob_obj] = (get_blenderID_key(ob.data), ob.data, False)
@@ -2246,7 +2220,7 @@ def fbx_data_from_scene(scene, settings):
perfmon.step("FBX export prepare: Wrapping ShapeKeys...")
# ShapeKeys.
- data_deformers_shape = OrderedDict()
+ data_deformers_shape = {}
geom_mat_co = settings.global_matrix if settings.bake_space_transform else None
for me_key, me, _free in data_meshes.values():
if not (me.shape_keys and len(me.shape_keys.key_blocks) > 1): # We do not want basis-only relative skeys...
@@ -2284,13 +2258,13 @@ def fbx_data_from_scene(scene, settings):
continue
channel_key, geom_key = get_blender_mesh_shape_channel_key(me, shape)
data = (channel_key, geom_key, shape_verts_co, shape_verts_idx)
- data_deformers_shape.setdefault(me, (me_key, shapes_key, OrderedDict()))[2][shape] = data
+ data_deformers_shape.setdefault(me, (me_key, shapes_key, {}))[2][shape] = data
perfmon.step("FBX export prepare: Wrapping Armatures...")
# Armatures!
- data_deformers_skin = OrderedDict()
- data_bones = OrderedDict()
+ data_deformers_skin = {}
+ data_bones = {}
arm_parents = set()
for ob_obj in tuple(objects):
if not (ob_obj.is_object and ob_obj.type in {'ARMATURE'}):
@@ -2307,71 +2281,51 @@ def fbx_data_from_scene(scene, settings):
# Some world settings are embedded in FBX materials...
if scene.world:
- data_world = OrderedDict(((scene.world, get_blenderID_key(scene.world)),))
+ data_world = {scene.world: get_blenderID_key(scene.world)}
else:
- data_world = OrderedDict()
+ data_world = {}
perfmon.step("FBX export prepare: Wrapping Materials...")
- # TODO: Check all the mat stuff works even when mats are linked to Objects
+ # TODO: Check all the material stuff works even when they are linked to Objects
# (we can then have the same mesh used with different materials...).
# *Should* work, as FBX always links its materials to Models (i.e. objects).
# XXX However, material indices would probably break...
- data_materials = OrderedDict()
+ data_materials = {}
for ob_obj in objects:
# If obj is not a valid object for materials, wrapper will just return an empty tuple...
- for mat_s in ob_obj.material_slots:
- mat = mat_s.material
- if mat is None:
+ for ma_s in ob_obj.material_slots:
+ ma = ma_s.material
+ if ma is None:
continue # Empty slots!
# Note theoretically, FBX supports any kind of materials, even GLSL shaders etc.
# However, I doubt anything else than Lambert/Phong is really portable!
- # We support any kind of 'surface' shader though, better to have some kind of default Lambert than nothing.
- # Note we want to keep a 'dummy' empty mat even when we can't really support it, see T41396.
- mat_data = data_materials.get(mat)
- if mat_data is not None:
- mat_data[1].append(ob_obj)
- else:
- data_materials[mat] = (get_blenderID_key(mat), [ob_obj])
+ # Note we want to keep a 'dummy' empty material even when we can't really support it, see T41396.
+ ma_data = data_materials.setdefault(ma, (get_blenderID_key(ma), []))
+ ma_data[1].append(ob_obj)
perfmon.step("FBX export prepare: Wrapping Textures...")
# Note FBX textures also hold their mapping info.
# TODO: Support layers?
- data_textures = OrderedDict()
+ data_textures = {}
# FbxVideo also used to store static images...
- data_videos = OrderedDict()
+ data_videos = {}
# For now, do not use world textures, don't think they can be linked to anything FBX wise...
- for mat in data_materials.keys():
- if check_skip_material(mat):
- continue
- for tex, use_tex in zip(mat.texture_slots, mat.use_textures):
- if tex is None or tex.texture is None or not use_tex:
- continue
- # For now, only consider image textures.
- # Note FBX does has support for procedural, but this is not portable at all (opaque blob),
- # so not useful for us.
- # TODO I think ENVIRONMENT_MAP should be usable in FBX as well, but for now let it aside.
- # if tex.texture.type not in {'IMAGE', 'ENVIRONMENT_MAP'}:
- if tex.texture.type not in {'IMAGE'}:
- continue
- img = tex.texture.image
- if img is None:
+ for ma in data_materials.keys():
+ # Note: with nodal shaders, we'll could be generating much more textures, but that's kind of unavoidable,
+ # given that textures actually do not exist anymore in material context in Blender...
+ ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=True)
+ for sock_name, fbx_name in PRINCIPLED_TEXTURE_SOCKETS_TO_FBX:
+ tex = getattr(ma_wrap, sock_name)
+ if tex is None or tex.image is None:
continue
- # Find out whether we can actually use this texture for this material, in FBX context.
- tex_fbx_props = fbx_mat_properties_from_texture(tex)
- if not tex_fbx_props:
- continue
- tex_data = data_textures.get(tex)
- if tex_data is not None:
- tex_data[1][mat] = tex_fbx_props
- else:
- data_textures[tex] = (get_blenderID_key(tex), OrderedDict(((mat, tex_fbx_props),)))
- vid_data = data_videos.get(img)
- if vid_data is not None:
- vid_data[1].append(tex)
- else:
- data_videos[img] = (get_blenderID_key(img), [tex])
+ blender_tex_key = (ma, sock_name)
+ data_textures[blender_tex_key] = (get_blender_nodetexture_key(*blender_tex_key), fbx_name)
+
+ img = tex.image
+ vid_data = data_videos.setdefault(img, (get_blenderID_key(img), []))
+ vid_data[1].append(blender_tex_key)
perfmon.step("FBX export prepare: Wrapping Animations...")
@@ -2385,8 +2339,8 @@ 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,
- data_empties, data_lamps, data_cameras, data_meshes, None,
+ 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,
)
@@ -2396,14 +2350,14 @@ def fbx_data_from_scene(scene, settings):
perfmon.step("FBX export prepare: Generating templates...")
- templates = OrderedDict()
+ templates = {}
templates[b"GlobalSettings"] = fbx_template_def_globalsettings(scene, settings, nbr_users=1)
if data_empties:
templates[b"Null"] = fbx_template_def_null(scene, settings, nbr_users=len(data_empties))
- if data_lamps:
- templates[b"Light"] = fbx_template_def_light(scene, settings, nbr_users=len(data_lamps))
+ if data_lights:
+ templates[b"Light"] = fbx_template_def_light(scene, settings, nbr_users=len(data_lights))
if data_cameras:
templates[b"Camera"] = fbx_template_def_camera(scene, settings, nbr_users=len(data_cameras))
@@ -2501,9 +2455,9 @@ def fbx_data_from_scene(scene, settings):
bo_data_key = data_bones[ob_obj]
connections.append((b"OO", get_fbx_uuid_from_key(bo_data_key), ob_obj.fbx_uuid, None))
else:
- if ob_obj.type == 'LAMP':
- lamp_key = data_lamps[ob_obj.bdata.data]
- connections.append((b"OO", get_fbx_uuid_from_key(lamp_key), ob_obj.fbx_uuid, None))
+ if ob_obj.type == 'LIGHT':
+ light_key = data_lights[ob_obj.bdata.data]
+ connections.append((b"OO", get_fbx_uuid_from_key(light_key), ob_obj.fbx_uuid, None))
elif ob_obj.type == 'CAMERA':
cam_key = data_cameras[ob_obj]
connections.append((b"OO", get_fbx_uuid_from_key(cam_key), ob_obj.fbx_uuid, None))
@@ -2543,35 +2497,33 @@ def fbx_data_from_scene(scene, settings):
connections.append((b"OO", bo_obj.fbx_uuid, get_fbx_uuid_from_key(clstr_key), None))
# Materials
- mesh_mat_indices = OrderedDict()
+ mesh_material_indices = {}
_objs_indices = {}
- for mat, (mat_key, ob_objs) in data_materials.items():
+ for ma, (ma_key, ob_objs) in data_materials.items():
for ob_obj in ob_objs:
- connections.append((b"OO", get_fbx_uuid_from_key(mat_key), ob_obj.fbx_uuid, None))
- # Get index of this mat for this object (or dupliobject).
- # Mat indices for mesh faces are determined by their order in 'mat to ob' connections.
- # Only mats for meshes currently...
- # Note in case of dupliobjects a same me/mat idx will be generated several times...
+ connections.append((b"OO", get_fbx_uuid_from_key(ma_key), ob_obj.fbx_uuid, None))
+ # Get index of this material for this object (or dupliobject).
+ # Material indices for mesh faces are determined by their order in 'ma to ob' connections.
+ # Only materials for meshes currently...
+ # Note in case of dupliobjects a same me/ma idx will be generated several times...
# Should not be an issue in practice, and it's needed in case we export duplis but not the original!
if ob_obj.type not in BLENDER_OBJECT_TYPES_MESHLIKE:
continue
_mesh_key, me, _free = data_meshes[ob_obj]
idx = _objs_indices[ob_obj] = _objs_indices.get(ob_obj, -1) + 1
- mesh_mat_indices.setdefault(me, OrderedDict())[mat] = idx
+ mesh_material_indices.setdefault(me, {})[ma] = idx
del _objs_indices
# Textures
- for tex, (tex_key, mats) in data_textures.items():
- for mat, fbx_mat_props in mats.items():
- mat_key, _ob_objs = data_materials[mat]
- for fbx_prop in fbx_mat_props:
- # texture -> material properties
- connections.append((b"OP", get_fbx_uuid_from_key(tex_key), get_fbx_uuid_from_key(mat_key), fbx_prop))
+ for (ma, sock_name), (tex_key, fbx_prop) in data_textures.items():
+ ma_key, _ob_objs = data_materials[ma]
+ # texture -> material properties
+ connections.append((b"OP", get_fbx_uuid_from_key(tex_key), get_fbx_uuid_from_key(ma_key), fbx_prop))
# Images
- for vid, (vid_key, texs) in data_videos.items():
- for tex in texs:
- tex_key, _texs = data_textures[tex]
+ for vid, (vid_key, blender_tex_keys) in data_videos.items():
+ for blender_tex_key in blender_tex_keys:
+ tex_key, _fbx_prop = data_textures[blender_tex_key]
connections.append((b"OO", get_fbx_uuid_from_key(vid_key), get_fbx_uuid_from_key(tex_key), None))
# Animations
@@ -2603,8 +2555,8 @@ def fbx_data_from_scene(scene, settings):
return FBXExportData(
templates, templates_users, connections,
- settings, scene, objects, animations, animated, frame_start, frame_end,
- data_empties, data_lamps, data_cameras, data_meshes, mesh_mat_indices,
+ settings, scene, depsgraph, objects, animations, animated, frame_start, frame_end,
+ data_empties, data_lights, data_cameras, data_meshes, mesh_material_indices,
data_bones, data_leaf_bones, data_deformers_skin, data_deformers_shape,
data_world, data_materials, data_textures, data_videos,
)
@@ -2813,10 +2765,10 @@ def fbx_objects_elements(root, scene_data):
for empty in scene_data.data_empties:
fbx_data_empty_elements(objects, empty, scene_data)
- perfmon.step("FBX export fetch lamps (%d)..." % len(scene_data.data_lamps))
+ perfmon.step("FBX export fetch lamps (%d)..." % len(scene_data.data_lights))
- for lamp in scene_data.data_lamps:
- fbx_data_lamp_elements(objects, lamp, scene_data)
+ for lamp in scene_data.data_lights:
+ fbx_data_light_elements(objects, lamp, scene_data)
perfmon.step("FBX export fetch cameras (%d)..." % len(scene_data.data_cameras))
@@ -2837,12 +2789,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...")
@@ -2854,11 +2804,11 @@ def fbx_objects_elements(root, scene_data):
if scene_data.data_leaf_bones:
fbx_data_leaf_bone_elements(objects, scene_data)
- for mat in scene_data.data_materials:
- fbx_data_material_elements(objects, mat, scene_data)
+ for ma in scene_data.data_materials:
+ fbx_data_material_elements(objects, ma, scene_data)
- for tex in scene_data.data_textures:
- fbx_data_texture_file_elements(objects, tex, scene_data)
+ for blender_tex_key in scene_data.data_textures:
+ fbx_data_texture_file_elements(objects, blender_tex_key, scene_data)
for vid in scene_data.data_videos:
fbx_data_video_elements(objects, vid, scene_data)
@@ -2907,7 +2857,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,
@@ -2945,7 +2895,7 @@ def save_single(operator, scene, filepath="",
ObjectWrapper.cache_clear()
if object_types is None:
- object_types = {'EMPTY', 'CAMERA', 'LAMP', 'ARMATURE', 'MESH', 'OTHER'}
+ object_types = {'EMPTY', 'CAMERA', 'LIGHT', 'ARMATURE', 'MESH', 'OTHER'}
if 'OTHER' in object_types:
object_types |= BLENDER_OTHER_OBJECT_TYPES
@@ -2953,12 +2903,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
@@ -3014,7 +2964,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!
@@ -3058,7 +3008,6 @@ def save_single(operator, scene, filepath="",
def defaults_unity3d():
return {
# These options seem to produce the same result as the old Ascii exporter in Unity3D:
- "version": 'BIN7400',
"axis_up": 'Y',
"axis_forward": '-Z',
"global_matrix": Matrix.Rotation(-math.pi / 2.0, 4, 'X'),
@@ -3097,18 +3046,19 @@ def defaults_unity3d():
def save(operator, context,
filepath="",
use_selection=False,
+ use_active_collection=False,
batch_mode='OFF',
use_batch_own_dir=False,
**kwargs
):
"""
- This is a wrapper around save_single, which handles multi-scenes (or groups) cases, when batch-exporting a whole
- .blend file.
+ This is a wrapper around save_single, which handles multi-scenes (or collections) cases, when batch-exporting
+ a whole .blend file.
"""
- ret = None
+ ret = {'FINISHED'}
- 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():
@@ -3117,37 +3067,59 @@ def save(operator, context,
if batch_mode == 'OFF':
kwargs_mod = kwargs.copy()
- if use_selection:
- kwargs_mod["context_objects"] = context.selected_objects
+ if use_active_collection:
+ if use_selection:
+ ctx_objects = tuple(obj
+ for obj in context.view_layer.active_layer_collection.collection.all_objects
+ if obj.select_get())
+ else:
+ ctx_objects = context.view_layer.active_layer_collection.collection.all_objects
else:
- kwargs_mod["context_objects"] = context.scene.objects
+ if use_selection:
+ ctx_objects = context.selected_objects
+ else:
+ ctx_objects = context.view_layer.objects
+ kwargs_mod["context_objects"] = ctx_objects
- ret = save_single(operator, context.scene, filepath, **kwargs_mod)
+ ret = save_single(operator, context.scene, context.depsgraph, filepath, **kwargs_mod)
else:
+ # XXX We need a way to generate a depsgraph for inactive view_layers first...
+ # XXX Also, what to do in case of batch-exporting scenes, when there is more than one view layer?
+ # Scenes have no concept of 'active' view layer, that's on window level...
fbxpath = filepath
prefix = os.path.basename(fbxpath)
if prefix:
fbxpath = os.path.dirname(fbxpath)
- if batch_mode == 'GROUP':
- data_seq = tuple(grp for grp in bpy.data.groups if grp.objects)
+ if batch_mode == 'COLLECTION':
+ data_seq = tuple((coll, coll.name, 'objects') for coll in bpy.data.collections if coll.objects)
+ elif batch_mode in {'SCENE_COLLECTION', 'ACTIVE_SCENE_COLLECTION'}:
+ scenes = [context.scene] if batch_mode == 'ACTIVE_SCENE_COLLECTION' else bpy.data.scenes
+ data_seq = []
+ for scene in scenes:
+ if not scene.objects:
+ continue
+ # Needed to avoid having tens of 'Master Collection' entries.
+ todo_collections = [(scene.collection, "_".join((scene.name, scene.collection.name)))]
+ while todo_collections:
+ coll, coll_name = todo_collections.pop()
+ todo_collections.extend(((c, c.name) for c in coll.children if c.all_objects))
+ data_seq.append((coll, coll_name, 'all_objects'))
else:
- data_seq = bpy.data.scenes
+ data_seq = tuple((scene, scene.name, 'objects') for scene in bpy.data.scenes if scene.objects)
# call this function within a loop with BATCH_ENABLE == False
- # no scene switching done at the moment.
- # orig_sce = context.scene
new_fbxpath = fbxpath # own dir option modifies, we need to keep an original
- for data in data_seq: # scene or group
- newname = "_".join((prefix, bpy.path.clean_name(data.name))) if prefix else bpy.path.clean_name(data.name)
+ for data, data_name, data_obj_propname in data_seq: # scene or collection
+ newname = "_".join((prefix, bpy.path.clean_name(data_name))) if prefix else bpy.path.clean_name(data_name)
if use_batch_own_dir:
new_fbxpath = os.path.join(fbxpath, newname)
- # path may already exist
- # TODO - might exist but be a file. unlikely but should probably account for it.
-
+ # path may already exist... and be a file.
+ while os.path.isfile(new_fbxpath):
+ new_fbxpath = "_".join((new_fbxpath, "dir"))
if not os.path.exists(new_fbxpath):
os.makedirs(new_fbxpath)
@@ -3155,18 +3127,14 @@ def save(operator, context,
print('\nBatch exporting %s as...\n\t%r' % (data, filepath))
- if batch_mode == 'GROUP': # group
- # group, so objects update properly, add a dummy scene.
+ if batch_mode in {'COLLECTION', 'SCENE_COLLECTION', 'ACTIVE_SCENE_COLLECTION'}:
+ # Collection, so that objects update properly, add a dummy scene.
scene = bpy.data.scenes.new(name="FBX_Temp")
- scene.layers = [True] * 20
- # bpy.data.scenes.active = scene # XXX, cant switch
src_scenes = {} # Count how much each 'source' scenes are used.
- for ob_base in data.objects:
- for src_sce in ob_base.users_scene:
- if src_sce not in src_scenes:
- src_scenes[src_sce] = 0
- src_scenes[src_sce] += 1
- scene.objects.link(ob_base)
+ for obj in getattr(data, data_obj_propname):
+ for src_sce in obj.users_scene:
+ src_scenes[src_sce] = src_scenes.setdefault(src_sce, 0) + 1
+ scene.collection.objects.link(obj)
# Find the 'most used' source scene, and use its unit settings. This is somewhat weak, but should work
# fine in most cases, and avoids stupid issues like T41931.
@@ -3186,20 +3154,17 @@ def save(operator, context,
scene = data
kwargs_batch = kwargs.copy()
- kwargs_batch["context_objects"] = data.objects
+ kwargs_batch["context_objects"] = getattr(data, data_obj_propname)
- save_single(operator, scene, filepath, **kwargs_batch)
+ save_single(operator, scene, scene.view_layers[0].depsgraph, filepath, **kwargs_batch)
- if batch_mode == 'GROUP':
- # remove temp group scene
+ if batch_mode in {'COLLECTION', 'SCENE_COLLECTION', 'ACTIVE_SCENE_COLLECTION'}:
+ # Remove temp collection scene.
bpy.data.scenes.remove(scene)
- # no active scene changing!
- # bpy.data.scenes.active = orig_sce
-
- ret = {'FINISHED'} # so the script wont run after we have batch exported.
-
- if active_object and org_mode and bpy.ops.object.mode_set.poll():
- bpy.ops.object.mode_set(mode=org_mode)
+ if active_object and org_mode:
+ context.view_layer.objects.active = active_object
+ if bpy.ops.object.mode_set.poll():
+ bpy.ops.object.mode_set(mode=org_mode)
return ret
diff --git a/io_scene_fbx/fbx_utils.py b/io_scene_fbx/fbx_utils.py
index 0ed26b6a..19f32800 100644
--- a/io_scene_fbx/fbx_utils.py
+++ b/io_scene_fbx/fbx_utils.py
@@ -24,13 +24,13 @@
import math
import time
-from collections import namedtuple, OrderedDict
+from collections import namedtuple
from collections.abc import Iterable
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
@@ -71,7 +71,7 @@ FBX_ANIM_PROPSGROUP_NAME = "d"
FBX_KTIME = 46186158000 # This is the number of "ktimes" in one second (yep, precision over the nanosecond...)
-MAT_CONVERT_LAMP = Matrix.Rotation(math.pi / 2.0, 4, 'X') # Blender is -Z, FBX is -Y.
+MAT_CONVERT_LIGHT = Matrix.Rotation(math.pi / 2.0, 4, 'X') # Blender is -Z, FBX is -Y.
MAT_CONVERT_CAMERA = Matrix.Rotation(math.pi / 2.0, 4, 'Y') # Blender is -Z, FBX is +X.
# XXX I can't get this working :(
# MAT_CONVERT_BONE = Matrix.Rotation(math.pi / 2.0, 4, 'Z') # Blender is +Y, FBX is -X.
@@ -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. #####
@@ -324,7 +324,7 @@ def _key_to_uuid(uuids, key):
def get_fbx_uuid_from_key(key):
"""
- Return an UUID for given key, which is assumed hasable.
+ Return an UUID for given key, which is assumed to be hashable.
"""
uuid = _keys_to_uuids.get(key, None)
if uuid is None:
@@ -431,6 +431,10 @@ def get_blender_anim_curve_key(scene, ref_id, obj_key, fbx_prop_name, fbx_prop_i
fbx_prop_item_name, "AnimCurve"))
+def get_blender_nodetexture_key(ma, socket_names):
+ return "|".join((get_blenderID_key(ma), *socket_names))
+
+
# ##### Element generators. #####
# Note: elem may be None, in this case the element is not added to any parent.
@@ -614,12 +618,12 @@ def elem_props_template_init(templates, template_type):
"""
Init a writing template of given type, for *one* element's properties.
"""
- ret = OrderedDict()
+ ret = {}
tmpl = templates.get(template_type)
if tmpl is not None:
written = tmpl.written[0]
props = tmpl.properties
- ret = OrderedDict((name, [val, ptype, anim, written]) for name, (val, ptype, anim) in props.items())
+ ret = {name: [val, ptype, anim, written] for name, (val, ptype, anim) in props.items()}
return ret
@@ -671,14 +675,11 @@ def fbx_templates_generate(root, fbx_templates):
# for Lights, Cameras, LibNodes, etc.).
ref_templates = {(tmpl.type_name, tmpl.prop_type_name): tmpl for tmpl in fbx_templates.values()}
- templates = OrderedDict()
+ templates = {}
for type_name, prop_type_name, properties, nbr_users, _written in fbx_templates.values():
- tmpl = templates.get(type_name)
- if tmpl is None:
- templates[type_name] = [OrderedDict(((prop_type_name, (properties, nbr_users)),)), nbr_users]
- else:
- tmpl[0][prop_type_name] = (properties, nbr_users)
- tmpl[1] += nbr_users
+ tmpl = templates.setdefault(type_name, [{}, 0])
+ tmpl[0][prop_type_name] = (properties, nbr_users)
+ tmpl[1] += nbr_users
for type_name, (subprops, nbr_users) in templates.items():
template = elem_data_single_string(root, b"ObjectType", type_name)
@@ -844,7 +845,7 @@ class AnimationCurveNodeWrapper:
for elem_key, fbx_group, fbx_gname, fbx_props in \
zip(self.elem_keys, self.fbx_group, self.fbx_gname, self.fbx_props):
group_key = get_blender_anim_curve_node_key(scene, ref_id, elem_key, fbx_group)
- group = OrderedDict()
+ group = {}
for c, def_val, fbx_item in zip(curves, self.default_values, fbx_props):
fbx_item = FBX_ANIM_PROPSGROUP_NAME + "|" + fbx_item
curve_key = get_blender_anim_curve_key(scene, ref_id, elem_key, fbx_group, fbx_item)
@@ -856,7 +857,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 +871,13 @@ 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.original, bdata.instance_object.original)),
+ cls._get_dup_num_id(bdata)))
+ dup_mat = bdata.matrix_world.copy()
+ else:
+ key = get_blenderID_key(bdata.object.original)
else: # isinstance(bdata, (Bone, PoseBone)):
if isinstance(bdata, PoseBone):
bdata = armature.data.bones[bdata.name]
@@ -883,9 +888,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 +907,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 +923,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.original, bdata.instance_object.original)),
+ "Dupli", self._get_dup_num_id(bdata)))
+ self.bdata = bdata.instance_object.original
+ self._ref = bdata.parent.original
+ else:
+ self._tag = 'OB'
+ self.name = get_blenderID_name(bdata)
+ self.bdata = bdata.object.original
+ self._ref = None
else: # isinstance(bdata, (Bone, PoseBone)):
if isinstance(bdata, PoseBone):
bdata = armature.data.bones[bdata.name]
@@ -951,13 +974,17 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
def __hash__(self):
return hash(self.key)
+ def __repr__(self):
+ return self.key
+
# #### Common to all _tag values.
def get_fbx_uuid(self):
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 +1001,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 +1010,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 +1024,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 +1032,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 +1092,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
- elif self.bdata.type == 'LAMP':
- matrix = matrix * MAT_CONVERT_LAMP
+ matrix = matrix @ scene_data.settings.bone_correction_matrix
+ elif self.bdata.type == '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 +1191,11 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
return True
# #### Duplis...
- def dupli_list_create(self, scene, settings='PREVIEW'):
- 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)
+ def dupli_list_gen(self, depsgraph):
+ if self._tag == 'OB' and self.bdata.is_instancer:
+ return (ObjectWrapper(dup) for dup in depsgraph.object_instances
+ if dup.parent and ObjectWrapper(dup.parent.original) == self)
return ()
- dupli_list = property(get_dupli_list)
def fbx_name_class(name, cls):
@@ -1213,8 +1232,8 @@ FBXExportSettings = namedtuple("FBXExportSettings", (
# * animations.
FBXExportData = namedtuple("FBXExportData", (
"templates", "templates_users", "connections",
- "settings", "scene", "objects", "animations", "animated", "frame_start", "frame_end",
- "data_empties", "data_lamps", "data_cameras", "data_meshes", "mesh_mat_indices",
+ "settings", "scene", "depsgraph", "objects", "animations", "animated", "frame_start", "frame_end",
+ "data_empties", "data_lights", "data_cameras", "data_meshes", "mesh_material_indices",
"data_bones", "data_leaf_bones", "data_deformers_skin", "data_deformers_shape",
"data_world", "data_materials", "data_textures", "data_videos",
))
@@ -1223,11 +1242,11 @@ FBXExportData = namedtuple("FBXExportData", (
FBXImportSettings = namedtuple("FBXImportSettings", (
"report", "to_axes", "global_matrix", "global_scale",
"bake_space_transform", "global_matrix_inv", "global_matrix_inv_transposed",
- "use_custom_normals", "use_cycles", "use_image_search",
+ "use_custom_normals", "use_image_search",
"use_alpha_decals", "decal_offset",
"use_anim", "anim_offset",
"use_custom_props", "use_custom_props_enum_as_string",
- "cycles_material_wrap_map", "image_cache",
+ "nodal_material_wrap_map", "image_cache",
"ignore_leaf_bones", "force_connect_children", "automatic_bone_orientation", "bone_correction_matrix",
"use_prepost_rot",
))
diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py
index addb8ae6..f35a6387 100644
--- a/io_scene_fbx/import_fbx.py
+++ b/io_scene_fbx/import_fbx.py
@@ -60,7 +60,7 @@ fbx_elem_nil = None
convert_deg_to_rad_iter = units_convertor_iter("degree", "radian")
MAT_CONVERT_BONE = fbx_utils.MAT_CONVERT_BONE.inverted()
-MAT_CONVERT_LAMP = fbx_utils.MAT_CONVERT_LAMP.inverted()
+MAT_CONVERT_LIGHT = fbx_utils.MAT_CONVERT_LIGHT.inverted()
MAT_CONVERT_CAMERA = fbx_utils.MAT_CONVERT_CAMERA.inverted()
@@ -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
@@ -457,7 +457,7 @@ def add_vgroup_to_objects(vg_indices, vg_weights, vg_name, objects):
# We replace/override here...
vg = obj.vertex_groups.get(vg_name)
if vg is None:
- vg = obj.vertex_groups.new(vg_name)
+ vg = obj.vertex_groups.new(name=vg_name)
for i, w in zip(vg_indices, vg_weights):
vg.add((i,), w, 'REPLACE')
@@ -601,7 +601,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset):
else: # Euler
props[1] = (bl_obj.path_from_id("rotation_euler"), 3, grpname or "Euler Rotation")
- blen_curves = [action.fcurves.new(prop, channel, grpname)
+ blen_curves = [action.fcurves.new(prop, index=channel, action_group=grpname)
for prop, nbr_channels, grpname in props for channel in range(nbr_channels)]
if isinstance(item, Material):
@@ -613,7 +613,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset):
value[channel] = v
for fc, v in zip(blen_curves, value):
- fc.keyframe_points.insert(frame, v, {'NEEDED', 'FAST'}).interpolation = 'LINEAR'
+ fc.keyframe_points.insert(frame, v, options={'NEEDED', 'FAST'}).interpolation = 'LINEAR'
elif isinstance(item, ShapeKey):
for frame, values in blen_read_animations_curves_iter(fbx_curves, anim_offset, 0, fps):
@@ -624,7 +624,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset):
value = v / 100.0
for fc, v in zip(blen_curves, (value,)):
- fc.keyframe_points.insert(frame, v, {'NEEDED', 'FAST'}).interpolation = 'LINEAR'
+ fc.keyframe_points.insert(frame, v, options={'NEEDED', 'FAST'}).interpolation = 'LINEAR'
elif isinstance(item, Camera):
for frame, values in blen_read_animations_curves_iter(fbx_curves, anim_offset, 0, fps):
@@ -635,7 +635,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset):
value = v
for fc, v in zip(blen_curves, (value,)):
- fc.keyframe_points.insert(frame, v, {'NEEDED', 'FAST'}).interpolation = 'LINEAR'
+ fc.keyframe_points.insert(frame, v, options={'NEEDED', 'FAST'}).interpolation = 'LINEAR'
else: # Object or PoseBone:
if item.is_bone:
@@ -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()
@@ -686,7 +686,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset):
rot = rot.to_euler(rot_mode, rot_prev)
rot_prev = rot
for fc, value in zip(blen_curves, chain(loc, rot, sca)):
- fc.keyframe_points.insert(frame, value, {'NEEDED', 'FAST'}).interpolation = 'LINEAR'
+ fc.keyframe_points.insert(frame, value, options={'NEEDED', 'FAST'}).interpolation = 'LINEAR'
# Since we inserted our keyframes in 'FAST' mode, we have to update the fcurves now.
for fc in blen_curves:
@@ -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
@@ -1087,7 +1086,6 @@ def blen_read_geom_layer_smooth(fbx_obj, mesh):
)
# We only set sharp edges here, not face smoothing itself...
mesh.use_auto_smooth = True
- mesh.show_edge_sharp = True
return False
elif fbx_layer_mapping == b'ByPolygon':
blen_data = mesh.polygons
@@ -1165,7 +1163,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 +1240,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!
@@ -1257,7 +1255,6 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings):
mesh.normals_split_custom_set(tuple(zip(*(iter(clnors),) * 3)))
mesh.use_auto_smooth = True
- mesh.show_edge_sharp = True
else:
mesh.calc_normals()
@@ -1319,9 +1316,12 @@ def blen_read_shape(fbx_tmpl, fbx_sdata, fbx_bcdata, meshes, scene):
# Material
def blen_read_material(fbx_tmpl, fbx_obj, settings):
+ from bpy_extras import node_shader_utils
+ from math import sqrt
+
elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'Material')
- cycles_material_wrap_map = settings.cycles_material_wrap_map
+ nodal_material_wrap_map = settings.nodal_material_wrap_map
ma = bpy.data.materials.new(name=elem_name_utf8)
const_color_white = 1.0, 1.0, 1.0
@@ -1329,43 +1329,23 @@ def blen_read_material(fbx_tmpl, fbx_obj, settings):
fbx_props = (elem_find_first(fbx_obj, b'Properties70'),
elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil))
- ma_diff = elem_props_get_color_rgb(fbx_props, b'DiffuseColor', const_color_white)
- ma_spec = elem_props_get_color_rgb(fbx_props, b'SpecularColor', const_color_white)
- ma_alpha = elem_props_get_number(fbx_props, b'Opacity', 1.0)
- ma_spec_intensity = ma.specular_intensity = elem_props_get_number(fbx_props, b'SpecularFactor', 0.25) * 2.0
- ma_spec_hardness = elem_props_get_number(fbx_props, b'Shininess', 9.6)
- ma_refl_factor = elem_props_get_number(fbx_props, b'ReflectionFactor', 0.0)
- ma_refl_color = elem_props_get_color_rgb(fbx_props, b'ReflectionColor', const_color_white)
-
- if settings.use_cycles:
- from modules import cycles_shader_compat
- # viewport color
- ma.diffuse_color = ma_diff
-
- ma_wrap = cycles_shader_compat.CyclesShaderWrapper(ma)
- ma_wrap.diffuse_color_set(ma_diff)
- ma_wrap.specular_color_set([c * ma_spec_intensity for c in ma_spec])
- ma_wrap.hardness_value_set(((ma_spec_hardness + 3.0) / 5.0) - 0.65)
- ma_wrap.alpha_value_set(ma_alpha)
- ma_wrap.reflect_factor_set(ma_refl_factor)
- ma_wrap.reflect_color_set(ma_refl_color)
-
- cycles_material_wrap_map[ma] = ma_wrap
- else:
- # TODO, number BumpFactor isnt used yet
- ma.diffuse_color = ma_diff
- ma.specular_color = ma_spec
- ma.alpha = ma_alpha
- if ma_alpha < 1.0:
- ma.use_transparency = True
- ma.transparency_method = 'RAYTRACE'
- ma.specular_intensity = ma_spec_intensity
- ma.specular_hardness = ma_spec_hardness * 5.10 + 1.0
-
- if ma_refl_factor != 0.0:
- ma.raytrace_mirror.use = True
- ma.raytrace_mirror.reflect_factor = ma_refl_factor
- ma.mirror_color = ma_refl_color
+ ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=False, use_nodes=True)
+ ma_wrap.base_color = elem_props_get_color_rgb(fbx_props, b'DiffuseColor', const_color_white)
+ # No specular color in Principled BSDF shader, assumed to be either white or take some tint from diffuse one...
+ # TODO: add way to handle tint option (guesstimate from spec color + intensity...)?
+ ma_wrap.specular = elem_props_get_number(fbx_props, b'SpecularFactor', 0.25) * 2.0
+ # XXX Totally empirical conversion, trying to adapt it
+ # (from 1.0 - 0.0 Principled BSDF range to 0.0 - 100.0 FBX shininess range)...
+ fbx_shininess = elem_props_get_number(fbx_props, b'Shininess', 20.0)
+ ma_wrap.roughness = 1.0 - (sqrt(fbx_shininess) / 10.0)
+ ma_wrap.transmission = 1.0 - elem_props_get_number(fbx_props, b'Opacity', 1.0)
+ ma_wrap.metallic = elem_props_get_number(fbx_props, b'ReflectionFactor', 0.0)
+ # We have no metallic (a.k.a. reflection) color...
+ # elem_props_get_color_rgb(fbx_props, b'ReflectionColor', const_color_white)
+ # (x / 7.142) is only a guess, cycles usable range is (0.0 -> 0.5)
+ ma_wrap.normalmap_strength = elem_props_get_number(fbx_props, b'BumpFactor', 2.5) / 7.142
+
+ nodal_material_wrap_map[ma] = ma_wrap
if settings.use_custom_props:
blen_read_custom_properties(fbx_obj, ma, settings)
@@ -1479,7 +1459,7 @@ def blen_read_light(fbx_tmpl, fbx_obj, global_scale):
1: 'SUN',
2: 'SPOT'}.get(elem_props_get_enum(fbx_props, b'LightType', 0), 'POINT')
- lamp = bpy.data.lamps.new(name=elem_name_utf8, type=light_type)
+ lamp = bpy.data.lights.new(name=elem_name_utf8, type=light_type)
if light_type == 'SPOT':
spot_size = elem_props_get_number(fbx_props, b'OuterAngle', None)
@@ -1494,11 +1474,14 @@ def blen_read_light(fbx_tmpl, fbx_obj, global_scale):
spot_blend = elem_props_get_number(fbx_props, b'HotSpot', 45.0)
lamp.spot_blend = 1.0 - (spot_blend / spot_size)
- # TODO, cycles
+ # TODO, cycles nodes???
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)
+ if hasattr(lamp, "cycles"):
+ lamp.cycles.cast_shadow = lamp.use_shadow
+ # Keeping this for now, but this is not used nor exposed anymore afaik...
lamp.shadow_color = elem_props_get_color_rgb(fbx_props, b'ShadowColor', (0.0, 0.0, 0.0))
return lamp
@@ -1612,7 +1595,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
@@ -1700,12 +1683,12 @@ class FbxImportHelperNode:
if self.fbx_type == b'Camera':
correction_matrix = MAT_CONVERT_CAMERA
elif self.fbx_type == b'Light':
- correction_matrix = MAT_CONVERT_LAMP
+ correction_matrix = MAT_CONVERT_LIGHT
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 +1765,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 +1795,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 +1821,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 +1897,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 +1910,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 +1941,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 +1950,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 +1967,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.active_layer_collection.collection.objects.link(obj)
+ obj.select_set(True)
return obj
@@ -2007,7 +1990,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 +2010,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 +2077,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 +2097,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.active_layer_collection.collection.objects.link(arm)
+ arm.select_set(True)
# 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...
+ view_layer.objects.active = arm
+ 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 +2116,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 +2129,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 +2137,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.active_layer_collection.collection.objects.link(obj)
+ obj.select_set(True)
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 +2175,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
@@ -2249,7 +2232,6 @@ def load(operator, context, filepath="",
global_scale=1.0,
bake_space_transform=False,
use_custom_normals=True,
- use_cycles=True,
use_image_search=False,
use_alpha_decals=False,
decal_offset=0.0,
@@ -2311,10 +2293,8 @@ def load(operator, context, filepath="",
basedir = os.path.dirname(filepath)
- cycles_material_wrap_map = {}
+ nodal_material_wrap_map = {}
image_cache = {}
- if not use_cycles:
- texture_cache = {}
# Tables: (FBX_byte_id -> [FBX_data, None or Blender_datablock])
fbx_table_nodes = {}
@@ -2325,6 +2305,7 @@ def load(operator, context, filepath="",
material_decals = None
scene = context.scene
+ view_layer = context.view_layer
# #### Get some info from GlobalSettings.
@@ -2350,7 +2331,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.
@@ -2381,11 +2362,11 @@ def load(operator, context, filepath="",
settings = FBXImportSettings(
operator.report, (axis_up, axis_forward), global_matrix, global_scale,
bake_space_transform, global_matrix_inv, global_matrix_inv_transposed,
- use_custom_normals, use_cycles, use_image_search,
+ use_custom_normals, use_image_search,
use_alpha_decals, decal_offset,
use_anim, anim_offset,
use_custom_props, use_custom_props_enum_as_string,
- cycles_material_wrap_map, image_cache,
+ nodal_material_wrap_map, image_cache,
ignore_leaf_bones, force_connect_children, automatic_bone_orientation, bone_correction_matrix,
use_prepost_rot,
)
@@ -2682,7 +2663,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 +2706,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)
@@ -2857,8 +2838,7 @@ def load(operator, context, filepath="",
continue
mat = fbx_item[1]
items.append((mat, lnk_prop))
- if settings.use_cycles:
- print("WARNING! Importing material's animation is not supported for Cycles materials...")
+ print("WARNING! Importing material's animation is not supported for Nodal materials...")
for al_uuid, al_ctype in fbx_connection_map.get(acn_uuid, ()):
if al_ctype.props[0] != b'OO':
continue
@@ -2919,17 +2899,17 @@ def load(operator, context, filepath="",
# So we have to be careful not to re-add endlessly the same material to a mesh!
# This can easily happen with 'baked' dupliobjects, see T44386.
# TODO: add an option to link materials to objects in Blender instead?
- done_mats = set()
+ done_materials = set()
for (fbx_lnk, fbx_lnk_item, fbx_lnk_type) in connection_filter_forward(fbx_uuid, b'Model'):
# link materials
fbx_lnk_uuid = elem_uuid(fbx_lnk)
for (fbx_lnk_material, material, fbx_lnk_material_type) in connection_filter_reverse(fbx_lnk_uuid, b'Material'):
- if material not in done_mats:
+ if material not in done_materials:
mesh.materials.append(material)
- done_mats.add(material)
+ done_materials.add(material)
- # We have to validate mesh polygons' mat_idx, see T41015!
+ # We have to validate mesh polygons' ma_idx, see T41015!
# Some FBX seem to have an extra 'default' material which is not defined in FBX file.
if mesh.validate_material_indices():
print("WARNING: mesh '%s' had invalid material indices, those were reset to first material" % mesh.name)
@@ -2943,51 +2923,36 @@ def load(operator, context, filepath="",
fbx_tmpl = fbx_template_get((b'Material', b'KFbxSurfacePhong'))
# b'KFbxSurfaceLambert'
- # textures that use this material
- def texture_bumpfac_get(fbx_obj):
- assert(fbx_obj.id == b'Material')
- fbx_props = (elem_find_first(fbx_obj, b'Properties70'),
- elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil))
- # (x / 7.142) is only a guess, cycles usable range is (0.0 -> 0.5)
- return elem_props_get_number(fbx_props, b'BumpFactor', 2.5) / 7.142
-
- def texture_mapping_get(fbx_obj):
+ def texture_mapping_set(fbx_obj, node_texture):
assert(fbx_obj.id == b'Texture')
fbx_props = (elem_find_first(fbx_obj, b'Properties70'),
elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil))
- return (elem_props_get_vector_3d(fbx_props, b'Translation', (0.0, 0.0, 0.0)),
- elem_props_get_vector_3d(fbx_props, b'Rotation', (0.0, 0.0, 0.0)),
- elem_props_get_vector_3d(fbx_props, b'Scaling', (1.0, 1.0, 1.0)),
- (bool(elem_props_get_enum(fbx_props, b'WrapModeU', 0)),
- bool(elem_props_get_enum(fbx_props, b'WrapModeV', 0))))
-
- if not use_cycles:
- # Simple function to make a new mtex and set defaults
- def material_mtex_new(material, image, tex_map):
- tex = texture_cache.get(image)
- if tex is None:
- tex = bpy.data.textures.new(name=image.name, type='IMAGE')
- tex.image = image
- texture_cache[image] = tex
-
- # copy custom properties from image object to texture
- for key, value in image.items():
- tex[key] = value
-
- # delete custom properties on the image object
- for key in image.keys():
- del image[key]
-
- mtex = material.texture_slots.add()
- mtex.texture = tex
- mtex.texture_coords = 'UV'
- mtex.use_map_color_diffuse = False
-
- # No rotation here...
- mtex.offset[:] = tex_map[0]
- mtex.scale[:] = tex_map[2]
- return mtex
+ loc = elem_props_get_vector_3d(fbx_props, b'Translation', (0.0, 0.0, 0.0))
+ rot = tuple(-r for r in elem_props_get_vector_3d(fbx_props, b'Rotation', (0.0, 0.0, 0.0)))
+ scale = tuple(((1.0 / s) if s != 0.0 else 1.0)
+ for s in elem_props_get_vector_3d(fbx_props, b'Scaling', (1.0, 1.0, 1.0)))
+ clamp_uv = (bool(elem_props_get_enum(fbx_props, b'WrapModeU', 0)),
+ bool(elem_props_get_enum(fbx_props, b'WrapModeV', 0)))
+
+ if (loc == (0.0, 0.0, 0.0) and
+ rot == (0.0, 0.0, 0.0) and
+ scale == (1.0, 1.0, 1.0) and
+ clamp_uv == (False, False)):
+ return
+
+ node_texture.translation = loc
+ node_texture.rotation = rot
+ node_texture.scale = scale
+
+ # awkward conversion UV clamping to min/max
+ node_texture.min = (0.0, 0.0, 0.0)
+ node_texture.max = (1.0, 1.0, 1.0)
+ node_texture.use_min = node_texture.use_max = clamp_uv[0] or clamp_uv[1]
+ if clamp_uv[0] != clamp_uv[1]:
+ # use bool as index
+ node_texture.min[not clamp[0]] = -1e9
+ node_texture.max[not clamp[0]] = 1e9
for fbx_uuid, fbx_item in fbx_table_nodes.items():
fbx_obj, blen_data = fbx_item
@@ -2999,112 +2964,44 @@ def load(operator, context, filepath="",
image,
fbx_lnk_type) in connection_filter_reverse(fbx_uuid, b'Texture'):
- if use_cycles:
- if fbx_lnk_type.props[0] == b'OP':
- lnk_type = fbx_lnk_type.props[3]
-
- ma_wrap = cycles_material_wrap_map[material]
-
- # tx/rot/scale
- tex_map = texture_mapping_get(fbx_lnk)
- if (tex_map[0] == (0.0, 0.0, 0.0) and
- tex_map[1] == (0.0, 0.0, 0.0) and
- tex_map[2] == (1.0, 1.0, 1.0) and
- tex_map[3] == (False, False)):
- use_mapping = False
- else:
- use_mapping = True
- tex_map_kw = {
- "translation": tex_map[0],
- "rotation": [-i for i in tex_map[1]],
- "scale": [((1.0 / i) if i != 0.0 else 1.0) for i in tex_map[2]],
- "clamp": tex_map[3],
- }
-
- if lnk_type in {b'DiffuseColor', b'3dsMax|maps|texmap_diffuse'}:
- ma_wrap.diffuse_image_set(image)
- if use_mapping:
- ma_wrap.diffuse_mapping_set(**tex_map_kw)
- elif lnk_type == b'SpecularColor':
- ma_wrap.specular_image_set(image)
- if use_mapping:
- ma_wrap.specular_mapping_set(**tex_map_kw)
- elif lnk_type in {b'ReflectionColor', b'3dsMax|maps|texmap_reflection'}:
- ma_wrap.reflect_image_set(image)
- if use_mapping:
- ma_wrap.reflect_mapping_set(**tex_map_kw)
- elif lnk_type == b'TransparentColor': # alpha
- ma_wrap.alpha_image_set(image)
- if use_mapping:
- ma_wrap.alpha_mapping_set(**tex_map_kw)
- if use_alpha_decals:
- material_decals.add(material)
- elif lnk_type == b'DiffuseFactor':
- pass # TODO
- elif lnk_type == b'ShininessExponent':
- ma_wrap.hardness_image_set(image)
- if use_mapping:
- ma_wrap.hardness_mapping_set(**tex_map_kw)
- # XXX, applications abuse bump!
- elif lnk_type in {b'NormalMap', b'Bump', b'3dsMax|maps|texmap_bump'}:
- ma_wrap.normal_image_set(image)
- ma_wrap.normal_factor_set(texture_bumpfac_get(fbx_obj))
- if use_mapping:
- ma_wrap.normal_mapping_set(**tex_map_kw)
- """
- elif lnk_type == b'Bump':
- ma_wrap.bump_image_set(image)
- ma_wrap.bump_factor_set(texture_bumpfac_get(fbx_obj))
- if use_mapping:
- ma_wrap.bump_mapping_set(**tex_map_kw)
- """
- else:
- print("WARNING: material link %r ignored" % lnk_type)
-
- material_images.setdefault(material, {})[lnk_type] = (image, tex_map)
- else:
- if fbx_lnk_type.props[0] == b'OP':
- lnk_type = fbx_lnk_type.props[3]
-
- # tx/rot/scale (rot is ignored here!).
- tex_map = texture_mapping_get(fbx_lnk)
-
- mtex = material_mtex_new(material, image, tex_map)
-
- if lnk_type in {b'DiffuseColor', b'3dsMax|maps|texmap_diffuse'}:
- mtex.use_map_color_diffuse = True
- mtex.blend_type = 'MULTIPLY'
- elif lnk_type == b'SpecularColor':
- mtex.use_map_color_spec = True
- mtex.blend_type = 'MULTIPLY'
- elif lnk_type in {b'ReflectionColor', b'3dsMax|maps|texmap_reflection'}:
- mtex.use_map_raymir = True
- elif lnk_type == b'TransparentColor': # alpha
- material.use_transparency = True
- material.transparency_method = 'RAYTRACE'
- material.alpha = 0.0
- mtex.use_map_alpha = True
- mtex.alpha_factor = 1.0
- if use_alpha_decals:
- material_decals.add(material)
- elif lnk_type == b'DiffuseFactor':
- mtex.use_map_diffuse = True
- elif lnk_type == b'ShininessExponent':
- mtex.use_map_hardness = True
- # XXX, applications abuse bump!
- elif lnk_type in {b'NormalMap', b'Bump', b'3dsMax|maps|texmap_bump'}:
- mtex.texture.use_normal_map = True # not ideal!
- mtex.use_map_normal = True
- mtex.normal_factor = texture_bumpfac_get(fbx_obj)
- """
- elif lnk_type == b'Bump':
- mtex.use_map_normal = True
- mtex.normal_factor = texture_bumpfac_get(fbx_obj)
- """
- else:
- print("WARNING: material link %r ignored" % lnk_type)
+ if fbx_lnk_type.props[0] == b'OP':
+ lnk_type = fbx_lnk_type.props[3]
+
+ ma_wrap = nodal_material_wrap_map[material]
+
+ if lnk_type in {b'DiffuseColor', b'3dsMax|maps|texmap_diffuse'}:
+ ma_wrap.base_color_texture.image = image
+ texture_mapping_set(fbx_lnk, ma_wrap.base_color_texture)
+ elif lnk_type in {b'SpecularColor', b'SpecularFactor'}:
+ # Intensity actually, not color...
+ ma_wrap.specular_texture.image = image
+ texture_mapping_set(fbx_lnk, ma_wrap.specular_texture)
+ elif lnk_type in {b'ReflectionColor', b'ReflectionFactor', b'3dsMax|maps|texmap_reflection'}:
+ # Intensity actually, not color...
+ ma_wrap.metallic_texture.image = image
+ texture_mapping_set(fbx_lnk, ma_wrap.metallic_texture)
+ elif lnk_type in {b'TransparentColor', b'TransparentFactor'}:
+ # Transparency... sort of...
+ ma_wrap.transmission_texture.image = image
+ texture_mapping_set(fbx_lnk, ma_wrap.transmission_texture)
+ if use_alpha_decals:
+ material_decals.add(material)
+ elif lnk_type == b'ShininessExponent':
+ # That is probably reversed compared to expected results? TODO...
+ ma_wrap.roughness_texture.image = image
+ texture_mapping_set(fbx_lnk, ma_wrap.roughness_texture)
+ # XXX, applications abuse bump!
+ elif lnk_type in {b'NormalMap', b'Bump', b'3dsMax|maps|texmap_bump'}:
+ ma_wrap.normalmap_texture.image = image
+ texture_mapping_set(fbx_lnk, ma_wrap.normalmap_texture)
+ """
+ elif lnk_type == b'Bump':
+ # TODO displacement...
+ """
+ else:
+ print("WARNING: material link %r ignored" % lnk_type)
- material_images.setdefault(material, {})[lnk_type] = (image, tex_map)
+ material_images.setdefault(material, {})[lnk_type] = image
# Check if the diffuse image has an alpha channel,
# if so, use the alpha channel.
@@ -3115,30 +3012,21 @@ def load(operator, context, filepath="",
if fbx_obj.id != b'Material':
continue
material = fbx_table_nodes.get(fbx_uuid, (None, None))[1]
- image, tex_map = material_images.get(material, {}).get(b'DiffuseColor', (None, None))
+ image = material_images.get(material, {}).get(b'DiffuseColor', None)
# do we have alpha?
if image and image.depth == 32:
if use_alpha_decals:
material_decals.add(material)
- if use_cycles:
- ma_wrap = cycles_material_wrap_map[material]
- if ma_wrap.node_bsdf_alpha.mute:
- ma_wrap.alpha_image_set_from_diffuse()
- else:
- if not any((True for mtex in material.texture_slots if mtex and mtex.use_map_alpha)):
- mtex = material_mtex_new(material, image, tex_map)
+ ma_wrap = nodal_material_wrap_map[material]
+ ma_wrap.transmission_texture.use_alpha = True
+ ma_wrap.transmission_texture.copy_from(ma_wrap.base_color_texture)
- material.use_transparency = True
- material.transparency_method = 'RAYTRACE'
- material.alpha = 0.0
- mtex.use_map_alpha = True
- mtex.alpha_factor = 1.0
-
- # propagate mapping from diffuse to all other channels which have none defined.
- if use_cycles:
- ma_wrap = cycles_material_wrap_map[material]
- ma_wrap.mapping_set_from_diffuse()
+ # Propagate mapping from diffuse to all other channels which have none defined.
+ # XXX Commenting for now, I do not really understand the logic here, why should diffuse mapping
+ # be applied to all others if not defined for them???
+ # ~ ma_wrap = nodal_material_wrap_map[material]
+ # ~ ma_wrap.mapping_set_from_diffuse()
_(); del _
@@ -3161,14 +3049,8 @@ def load(operator, context, filepath="",
v.co += v.normal * decal_offset
break
- if use_cycles:
- for obj in (obj for obj in bpy.data.objects if obj.data == mesh):
- obj.cycles_visibility.shadow = False
- else:
- for material in mesh.materials:
- if material in material_decals:
- # receive but dont cast shadows
- material.use_raytrace = False
+ for obj in (obj for obj in bpy.data.objects if obj.data == mesh):
+ obj.cycles_visibility.shadow = False
_(); del _
perfmon.level_down()