diff options
Diffstat (limited to 'io_scene_fbx/fbx_utils.py')
-rw-r--r-- | io_scene_fbx/fbx_utils.py | 151 |
1 files changed, 85 insertions, 66 deletions
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", )) |