From c552e88485c5824efb74d6070aa9ae3c096bf598 Mon Sep 17 00:00:00 2001 From: Bastien Montagne <montagne29@wanadoo.fr> Date: Tue, 1 Apr 2014 21:43:03 +0200 Subject: [PATCH] FBX: Fix template issue when several sub-types use the same 'super type' element. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit E.g. camera, lamp and empty (Null) data all use the same "NodeAttribute" element type, while having different "sub-types" (FbxNull, FbxCamera, etc.) with different properties. It would have been perfectly possible to have multiple property templates, but from "reading" official FBX files, it’s obvious this is not supported, so in this case we have to pick one to write a real template, and for the other subtypes, we have to explicitely write the whole templates' content in each and every element... At this point, you might wonder why introducing templates, if you have to handle such hairy cases? Well, I’m wondering too... --- io_scene_fbx/export_fbx_bin.py | 155 +++++++++++++++++++++++---------- 1 file changed, 109 insertions(+), 46 deletions(-) diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py index be5776a58..dad89f115 100644 --- a/io_scene_fbx/export_fbx_bin.py +++ b/io_scene_fbx/export_fbx_bin.py @@ -442,23 +442,57 @@ def elem_props_compound(elem, cmpd_name, custom=False): return _setter +def elem_props_template_init(templates, template_type): + """ + Init a writing template of given type, for *one* element's properties. + """ + ret = None + if template_type in templates: + tmpl = templates[template_type] + written = tmpl.written[0] + print(template_type, written) + props = tmpl.properties + ret = OrderedDict((name, [val, ptype, anim, written]) for name, (val, ptype, anim) in props.items()) + return ret or OrderedDict() + + def elem_props_template_set(template, elem, ptype_name, name, value, animatable=False): """ Only add a prop if the same value is not already defined in given template. Note it is important to not give iterators as value, here! """ ptype = FBX_PROPERTIES_DEFINITIONS[ptype_name] - tmpl_val, tmpl_ptype, tmpl_animatable = template.properties.get(name, (None, None, False)) + if len(ptype) > 3: + value = tuple(value) + tmpl_val, tmpl_ptype, tmpl_animatable, tmpl_written = template.get(name, (None, None, False, False)) # Note animatable flag from template takes precedence over given one, if applicable. if tmpl_ptype is not None: - if ((len(ptype) == 3 and (tmpl_val, tmpl_ptype) == (value, ptype_name)) or - (len(ptype) > 3 and (tuple(tmpl_val), tmpl_ptype) == (tuple(value), ptype_name))): + if (tmpl_written and + ((len(ptype) == 3 and (tmpl_val, tmpl_ptype) == (value, ptype_name)) or + (len(ptype) > 3 and (tuple(tmpl_val), tmpl_ptype) == (value, ptype_name)))): return # Already in template and same value. _elem_props_set(elem, ptype, name, value, _elem_props_flags(tmpl_animatable, False)) + template[name][3] = True else: _elem_props_set(elem, ptype, name, value, _elem_props_flags(animatable, False)) +def elem_props_template_finalize(template, elem): + """ + Finalize one element's template/props. + Issue is, some templates might be "needed" by different types (e.g. NodeAttribute is for lights, cameras, etc.), + but values for only *one* subtype can be written as template. So we have to be sure we write those for ths other + subtypes in each and every elements, if they are not overriden by that element. + Yes, hairy, FBX that is to say. When they could easily support several subtypes per template... :( + """ + for name, (value, ptype_name, animatable, written) in template.items(): + if written: + continue + ptype = FBX_PROPERTIES_DEFINITIONS[ptype_name] + print(elem, ptype, name, value, _elem_props_flags(animatable, False)) + _elem_props_set(elem, ptype, name, value, _elem_props_flags(animatable, False)) + + ##### Generators for connection elements. ##### def elem_connection(elem, c_type, uid_src, uid_dst, prop_dst=None): @@ -472,37 +506,55 @@ def elem_connection(elem, c_type, uid_src, uid_dst, prop_dst=None): ##### Templates ##### # TODO: check all those "default" values, they should match Blender's default as much as possible, I guess? -FBXTemplate = namedtuple("FBXTemplate", ("type_name", "prop_type_name", "properties", "nbr_users")) +FBXTemplate = namedtuple("FBXTemplate", ("type_name", "prop_type_name", "properties", "nbr_users", "written")) def fbx_templates_generate(root, fbx_templates): # We may have to gather different templates in the same node (e.g. NodeAttribute template gathers properties # for Lights, Cameras, LibNodes, etc.). + ref_templates = {(tmpl.type_name, tmpl.prop_type_name) : tmpl for tmpl in fbx_templates.values()} + templates = OrderedDict() - for type_name, prop_type_name, properties, nbr_users in fbx_templates.values(): + for type_name, prop_type_name, properties, nbr_users, _written in fbx_templates.values(): if type_name not in templates: - templates[type_name] = [OrderedDict(((prop_type_name, properties),)), nbr_users] + templates[type_name] = [OrderedDict(((prop_type_name, (properties, nbr_users)),)), nbr_users] else: - templates[type_name][0][prop_type_name] = properties + templates[type_name][0][prop_type_name] = (properties, nbr_users) templates[type_name][1] += nbr_users for type_name, (subprops, nbr_users) in templates.items(): template = elem_data_single_string(root, b"ObjectType", type_name) elem_data_single_int32(template, b"Count", nbr_users) - for prop_type_name, properties in subprops.items(): - if prop_type_name and properties: - elem = elem_data_single_string(template, b"PropertyTemplate", prop_type_name) - props = elem_properties(elem) - for name, (value, ptype, animatable) in properties.items(): - elem_props_set(props, ptype, name, value, animatable=animatable) + if len(subprops) == 1: + prop_type_name, (properties, _nbr_sub_type_users) = next(iter(subprops.items())) + subprops = (prop_type_name, properties) + ref_templates[(type_name, prop_type_name)].written[0] = True + else: + # Ack! Even though this could/should work, looks like it is not supported. So we have to chose one. :| + max_users = max_props = -1 + written_prop_type_name = None + for prop_type_name, (properties, nbr_sub_type_users) in subprops.items(): + if nbr_sub_type_users > max_users or (nbr_sub_type_users == max_users and len(properties) > max_props): + max_users = nbr_sub_type_users + max_props = len(properties) + written_prop_type_name = prop_type_name + subprops = (written_prop_type_name, properties) + ref_templates[(type_name, written_prop_type_name)].written[0] = True + + prop_type_name, properties = subprops + if prop_type_name and properties: + elem = elem_data_single_string(template, b"PropertyTemplate", prop_type_name) + props = elem_properties(elem) + for name, (value, ptype, animatable) in properties.items(): + elem_props_set(props, ptype, name, value, animatable=animatable) def fbx_template_def_globalsettings(scene, settings, override_defaults=None, nbr_users=0): props = OrderedDict() if override_defaults is not None: props.update(override_defaults) - return FBXTemplate(b"GlobalSettings", b"", props, nbr_users) + return FBXTemplate(b"GlobalSettings", b"", props, nbr_users, [False]) def fbx_template_def_model(scene, settings, override_defaults=None, nbr_users=0): @@ -583,7 +635,7 @@ def fbx_template_def_model(scene, settings, override_defaults=None, nbr_users=0) )) if override_defaults is not None: props.update(override_defaults) - return FBXTemplate(b"Model", b"FbxNode", props, nbr_users) + return FBXTemplate(b"Model", b"FbxNode", props, nbr_users, [False]) def fbx_template_def_null(scene, settings, override_defaults=None, nbr_users=0): @@ -594,7 +646,7 @@ def fbx_template_def_null(scene, settings, override_defaults=None, nbr_users=0): )) if override_defaults is not None: props.update(override_defaults) - return FBXTemplate(b"NodeAttribute", b"FbxNull", props, nbr_users) + return FBXTemplate(b"NodeAttribute", b"FbxNull", props, nbr_users, [False]) def fbx_template_def_light(scene, settings, override_defaults=None, nbr_users=0): @@ -612,7 +664,7 @@ def fbx_template_def_light(scene, settings, override_defaults=None, nbr_users=0) )) if override_defaults is not None: props.update(override_defaults) - return FBXTemplate(b"NodeAttribute", b"FbxLight", props, nbr_users) + return FBXTemplate(b"NodeAttribute", b"FbxLight", props, nbr_users, [False]) def fbx_template_def_camera(scene, settings, override_defaults=None, nbr_users=0): @@ -727,14 +779,14 @@ def fbx_template_def_camera(scene, settings, override_defaults=None, nbr_users=0 )) if override_defaults is not None: props.update(override_defaults) - return FBXTemplate(b"NodeAttribute", b"FbxCamera", props, nbr_users) + 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() if override_defaults is not None: props.update(override_defaults) - return FBXTemplate(b"NodeAttribute", b"LimbNode", props, nbr_users) + return FBXTemplate(b"NodeAttribute", b"LimbNode", props, nbr_users, [False]) def fbx_template_def_geometry(scene, settings, override_defaults=None, nbr_users=0): @@ -748,7 +800,7 @@ def fbx_template_def_geometry(scene, settings, override_defaults=None, nbr_users )) if override_defaults is not None: props.update(override_defaults) - return FBXTemplate(b"Geometry", b"FbxMesh", props, nbr_users) + return FBXTemplate(b"Geometry", b"FbxMesh", props, nbr_users, [False]) def fbx_template_def_material(scene, settings, override_defaults=None, nbr_users=0): @@ -786,7 +838,7 @@ def fbx_template_def_material(scene, settings, override_defaults=None, nbr_users )) if override_defaults is not None: props.update(override_defaults) - return FBXTemplate(b"Material", b"FbxSurfacePhong", props, nbr_users) + return FBXTemplate(b"Material", b"FbxSurfacePhong", props, nbr_users, [False]) def fbx_template_def_texture_file(scene, settings, override_defaults=None, nbr_users=0): @@ -813,7 +865,7 @@ def fbx_template_def_texture_file(scene, settings, override_defaults=None, nbr_u )) if override_defaults is not None: props.update(override_defaults) - return FBXTemplate(b"Texture", b"FbxFileTexture", props, nbr_users) + return FBXTemplate(b"Texture", b"FbxFileTexture", props, nbr_users, [False]) def fbx_template_def_video(scene, settings, override_defaults=None, nbr_users=0): @@ -840,21 +892,21 @@ def fbx_template_def_video(scene, settings, override_defaults=None, nbr_users=0) )) if override_defaults is not None: props.update(override_defaults) - return FBXTemplate(b"Video", b"FbxVideo", props, nbr_users) + 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() if override_defaults is not None: props.update(override_defaults) - return FBXTemplate(b"Pose", b"", props, nbr_users) + return FBXTemplate(b"Pose", b"", props, nbr_users, [False]) def fbx_template_def_deformer(scene, settings, override_defaults=None, nbr_users=0): props = OrderedDict() if override_defaults is not None: props.update(override_defaults) - return FBXTemplate(b"Deformer", b"", props, nbr_users) + return FBXTemplate(b"Deformer", b"", props, nbr_users, [False]) def fbx_template_def_animstack(scene, settings, override_defaults=None, nbr_users=0): @@ -867,7 +919,7 @@ def fbx_template_def_animstack(scene, settings, override_defaults=None, nbr_user )) if override_defaults is not None: props.update(override_defaults) - return FBXTemplate(b"AnimationStack", b"FbxAnimStack", props, nbr_users) + return FBXTemplate(b"AnimationStack", b"FbxAnimStack", props, nbr_users, [False]) def fbx_template_def_animlayer(scene, settings, override_defaults=None, nbr_users=0): @@ -884,7 +936,7 @@ def fbx_template_def_animlayer(scene, settings, override_defaults=None, nbr_user )) if override_defaults is not None: props.update(override_defaults) - return FBXTemplate(b"AnimationLayer", b"FbxAnimLayer", props, nbr_users) + return FBXTemplate(b"AnimationLayer", b"FbxAnimLayer", props, nbr_users, [False]) def fbx_template_def_animcurvenode(scene, settings, override_defaults=None, nbr_users=0): @@ -893,14 +945,14 @@ def fbx_template_def_animcurvenode(scene, settings, override_defaults=None, nbr_ )) if override_defaults is not None: props.update(override_defaults) - return FBXTemplate(b"AnimationCurveNode", b"FbxAnimCurveNode", props, nbr_users) + 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() if override_defaults is not None: props.update(override_defaults) - return FBXTemplate(b"AnimationCurve", b"", props, nbr_users) + return FBXTemplate(b"AnimationCurve", b"", props, nbr_users, [False]) ##### FBX objects generators. ##### @@ -1017,8 +1069,9 @@ def fbx_data_empty_elements(root, empty, scene_data): elem_data_single_string(null, b"TypeFlags", b"Null") - tmpl = scene_data.templates[b"Null"] + tmpl = elem_props_template_init(scene_data.templates, b"Null") props = elem_properties(null) + elem_props_template_finalize(tmpl, props) # No custom properties, already saved with object (Model). @@ -1047,7 +1100,7 @@ def fbx_data_lamp_elements(root, lamp, scene_data): elem_data_single_int32(light, b"GeometryVersion", FBX_GEOMETRY_VERSION) # Sic... - tmpl = scene_data.templates[b"Light"] + tmpl = elem_props_template_init(scene_data.templates, b"Light") props = elem_properties(light) elem_props_template_set(tmpl, props, "p_enum", b"LightType", FBX_LIGHT_TYPES[lamp.type]) elem_props_template_set(tmpl, props, "p_bool", b"CastLight", do_light) @@ -1061,6 +1114,7 @@ def fbx_data_lamp_elements(root, lamp, scene_data): elem_props_template_set(tmpl, props, "p_double", b"OuterAngle", math.degrees(lamp.spot_size)) elem_props_template_set(tmpl, props, "p_double", b"InnerAngle", math.degrees(lamp.spot_size * (1.0 - lamp.spot_blend))) + elem_props_template_finalize(tmpl, props) # Custom properties. if scene_data.settings.use_custom_properties: @@ -1099,8 +1153,9 @@ def fbx_data_camera_elements(root, cam_obj, scene_data): cam.add_string(fbx_name_class(cam_data.name.encode(), b"NodeAttribute")) cam.add_string(b"Camera") - tmpl = scene_data.templates[b"Camera"] + tmpl = elem_props_template_init(scene_data.templates, b"Camera") props = elem_properties(cam) + elem_props_template_set(tmpl, props, "p_vector", b"Position", loc) elem_props_template_set(tmpl, props, "p_vector", b"UpVector", up) elem_props_template_set(tmpl, props, "p_vector", b"InterestPosition", loc + to) # Point, not vector! @@ -1128,6 +1183,8 @@ def fbx_data_camera_elements(root, cam_obj, scene_data): elem_props_template_set(tmpl, props, "p_enum", b"BackPlaneDistanceMode", 1) # RelativeToCamera. elem_props_template_set(tmpl, props, "p_double", b"BackPlaneDistance", cam_data.clip_end * gscale) + elem_props_template_finalize(tmpl, props) + # Custom properties. if scene_data.settings.use_custom_properties: fbx_data_element_custom_properties(props, cam_data) @@ -1529,8 +1586,9 @@ def fbx_data_material_elements(root, mat, scene_data): elem_data_single_string(fbx_mat, b"ShadingModel", mat_type) elem_data_single_int32(fbx_mat, b"MultiLayer", 0) # Should be bool... - tmpl = scene_data.templates[b"Material"] + tmpl = elem_props_template_init(scene_data.templates, b"Material") props = elem_properties(fbx_mat) + elem_props_template_set(tmpl, props, "p_string", b"ShadingModel", mat_type.decode()) 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) @@ -1561,6 +1619,8 @@ def fbx_data_material_elements(root, mat, scene_data): elem_props_template_set(tmpl, props, "p_number", b"ReflectionFactor", mat.raytrace_mirror.reflect_factor if mat.raytrace_mirror.use else 0.0) + elem_props_template_finalize(tmpl, props) + # Custom properties. if scene_data.settings.use_custom_properties: fbx_data_element_custom_properties(props, mat) @@ -1621,7 +1681,7 @@ def fbx_data_texture_file_elements(root, tex, scene_data): if tex.texture.extension in {'REPEAT'}: wrap_mode = 0 # Repeat - tmpl = scene_data.templates[b"TextureFile"] + tmpl = elem_props_template_init(scene_data.templates, b"TextureFile") props = elem_properties(fbx_tex) elem_props_template_set(tmpl, props, "p_enum", b"AlphaSource", alpha_source) elem_props_template_set(tmpl, props, "p_bool", b"PremultiplyAlpha", @@ -1632,6 +1692,7 @@ def fbx_data_texture_file_elements(root, tex, scene_data): 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_bool", b"UseMipMap", tex.texture.use_mipmap) + elem_props_template_finalize(tmpl, props) # Custom properties. if scene_data.settings.use_custom_properties: @@ -1676,7 +1737,6 @@ def fbx_data_armature_elements(root, armature, scene_data): """ # Bones "data". - tmpl = scene_data.templates[b"Bone"] for bo in armature.data.bones: _bo_key, bo_data_key, _arm = scene_data.data_bones[bo] fbx_bo = elem_data_single_int64(root, b"NodeAttribute", get_fbxuid_from_key(bo_data_key)) @@ -1684,8 +1744,10 @@ def fbx_data_armature_elements(root, armature, scene_data): fbx_bo.add_string(b"LimbNode") elem_data_single_string(fbx_bo, b"TypeFlags", b"Skeleton") + tmpl = elem_props_template_init(scene_data.templates, b"Bone") props = elem_properties(fbx_bo) elem_props_template_set(tmpl, props, "p_double", b"Size", (bo.tail_local - bo.head_local).length) + elem_props_template_finalize(tmpl, props) # Custom properties. if scene_data.settings.use_custom_properties: @@ -1790,15 +1852,13 @@ def fbx_data_object_elements(root, obj, scene_data): loc, rot, scale, matrix, matrix_rot = fbx_object_tx(scene_data, obj) rot = tuple(units_convert_iter(rot, "radian", "degree")) - tmpl = scene_data.templates[b"Model"] + tmpl = elem_props_template_init(scene_data.templates, b"Model") # For now add only loc/rot/scale... props = elem_properties(model) elem_props_template_set(tmpl, props, "p_lcl_translation", b"Lcl Translation", loc) elem_props_template_set(tmpl, props, "p_lcl_rotation", b"Lcl Rotation", rot) elem_props_template_set(tmpl, props, "p_lcl_scaling", b"Lcl Scaling", scale) - # TODO: "constraints" (limit loc/rot/scale, and target-to-object). - # Custom properties. if scene_data.settings.use_custom_properties: fbx_data_element_custom_properties(props, obj) @@ -1823,6 +1883,8 @@ def fbx_data_object_elements(root, obj, scene_data): elem_props_template_set(tmpl, props, "p_enum", b"BackgroundMode", 0) # Don't know what it means elem_props_template_set(tmpl, props, "p_bool", b"ForegroundTransparent", True) + elem_props_template_finalize(tmpl, props) + def fbx_data_animation_elements(root, scene_data): """ @@ -1838,23 +1900,23 @@ def fbx_data_animation_elements(root, scene_data): return (int(v) for v in units_convert_iter((f / fps for f, _v in keys), "second", "ktime")) astack_key, alayers = animations - astack_tmpl = scene_data.templates[b"AnimationStack"] - acn_tmpl = scene_data.templates[b"AnimationCurveNode"] # Animation stack. astack = elem_data_single_int64(root, b"AnimationStack", get_fbxuid_from_key(astack_key)) astack.add_string(fbx_name_class(scene.name.encode(), b"AnimStack")) astack.add_string(b"") + astack_tmpl = elem_props_template_init(scene_data.templates, b"AnimationStack") astack_props = elem_properties(astack) r = scene_data.scene.render fps = r.fps / r.fps_base f_start = int(units_convert(scene_data.scene.frame_start / fps, "second", "ktime")) f_end = int(units_convert(scene_data.scene.frame_end / fps, "second", "ktime")) - elem_props_set(astack_props, "p_timestamp", b"LocalStart", f_start) - elem_props_set(astack_props, "p_timestamp", b"LocalStop", f_end) - elem_props_set(astack_props, "p_timestamp", b"ReferenceStart", f_start) - elem_props_set(astack_props, "p_timestamp", b"ReferenceStop", f_end) + elem_props_template_set(astack_tmpl, astack_props, "p_timestamp", b"LocalStart", f_start) + elem_props_template_set(astack_tmpl, astack_props, "p_timestamp", b"LocalStop", f_end) + elem_props_template_set(astack_tmpl, astack_props, "p_timestamp", b"ReferenceStart", f_start) + elem_props_template_set(astack_tmpl, astack_props, "p_timestamp", b"ReferenceStop", f_end) + elem_props_template_finalize(astack_tmpl, astack_props) for obj, (alayer_key, acurvenodes) in alayers.items(): # Animation layer. @@ -1868,6 +1930,7 @@ def fbx_data_animation_elements(root, scene_data): acurvenode.add_string(fbx_name_class(acurvenode_name.encode(), b"AnimCurveNode")) acurvenode.add_string(b"") + acn_tmpl = elem_props_template_init(scene_data.templates, b"AnimationCurveNode") acn_props = elem_properties(acurvenode) for fbx_item, (acurve_key, def_value, keys, _acurve_valid) in acurves.items(): @@ -1900,6 +1963,8 @@ def fbx_data_animation_elements(root, scene_data): elem_data_single_float32_array(acurve, b"KeyAttrDataFloat", keyattr_datafloat) elem_data_single_int32_array(acurve, b"KeyAttrRefCount", (nbr_keys,)) + elem_props_template_finalize(acn_tmpl, acn_props) + ##### Top-level FBX data container. ##### @@ -2228,8 +2293,6 @@ def fbx_data_from_scene(scene, settings): templates = OrderedDict() templates[b"GlobalSettings"] = fbx_template_def_globalsettings(scene, settings, nbr_users=1) - # XXX Looks like there can only be one NodeAttribute template? At lest, never found more than one - # (null/light/camera) template in one file... :/ if data_empties: templates[b"Null"] = fbx_template_def_null(scene, settings, nbr_users=len(data_empties)) -- GitLab