Add optional subdivision surface support to the FBX exporter
Add option 'Export Subdivision Surface' to the FBX exporter (disabled by default). When enabled the exporter will write the **last active Catmull-Clark subdivision surface modifier** as FBX properties instead of applying it. Edge crease data is also written to the FBX file if 'Use Creases' is enabled in the subsurf modifier. Reviewers: mont29 Tags: #add-ons Differential Revision: https://developer.blender.org/D4982
This commit is contained in:
parent
a215a3c85a
commit
f1dd37b8ac
|
@ -21,7 +21,7 @@
|
|||
bl_info = {
|
||||
"name": "FBX format",
|
||||
"author": "Campbell Barton, Bastien Montagne, Jens Restemeier",
|
||||
"version": (4, 14, 15),
|
||||
"version": (4, 15, 0),
|
||||
"blender": (2, 80, 0),
|
||||
"location": "File > Import-Export",
|
||||
"description": "FBX IO meshes, UV's, vertex colors, materials, textures, cameras, lamps and actions",
|
||||
|
@ -131,6 +131,12 @@ class ImportFBX(bpy.types.Operator, ImportHelper):
|
|||
default=1.0,
|
||||
)
|
||||
|
||||
use_subsurf: BoolProperty(
|
||||
name="Import Subdivision Surface",
|
||||
description="Import FBX subdivision information as subdivision surface modifiers",
|
||||
default=False,
|
||||
)
|
||||
|
||||
use_custom_props: BoolProperty(
|
||||
name="Import User Properties",
|
||||
description="Import user properties as custom properties",
|
||||
|
@ -205,6 +211,8 @@ class ImportFBX(bpy.types.Operator, ImportHelper):
|
|||
layout.prop(self, "use_anim")
|
||||
layout.prop(self, "anim_offset")
|
||||
|
||||
layout.prop(self, "use_subsurf")
|
||||
|
||||
layout.prop(self, "use_custom_props")
|
||||
sub = layout.row()
|
||||
sub.enabled = self.use_custom_props
|
||||
|
@ -334,6 +342,12 @@ class ExportFBX(bpy.types.Operator, ExportHelper):
|
|||
"(prefer 'Normals Only' option if your target importer understand split normals)",
|
||||
default='OFF',
|
||||
)
|
||||
use_subsurf: BoolProperty(
|
||||
name="Export Subdivision Surface",
|
||||
description="Export the last Catmull-Rom subidivion modifier as FBX subdivision "
|
||||
"(Does not apply the modifier even if 'Apply Modifiers' is enabled)",
|
||||
default=False,
|
||||
)
|
||||
use_mesh_edges: BoolProperty(
|
||||
name="Loose Edges",
|
||||
description="Export loose edges (as two-vertices polygons)",
|
||||
|
@ -507,6 +521,7 @@ class ExportFBX(bpy.types.Operator, ExportHelper):
|
|||
sub.enabled = self.use_mesh_modifiers and False # disabled in 2.8...
|
||||
sub.prop(self, "use_mesh_modifiers_render")
|
||||
layout.prop(self, "mesh_smooth_type")
|
||||
layout.prop(self, "use_subsurf")
|
||||
layout.prop(self, "use_mesh_edges")
|
||||
sub = layout.row()
|
||||
#~ sub.enabled = self.mesh_smooth_type in {'OFF'}
|
||||
|
|
|
@ -49,7 +49,7 @@ from .fbx_utils import (
|
|||
FBX_VERSION, FBX_HEADER_VERSION, FBX_SCENEINFO_VERSION, FBX_TEMPLATES_VERSION,
|
||||
FBX_MODELS_VERSION,
|
||||
FBX_GEOMETRY_VERSION, FBX_GEOMETRY_NORMAL_VERSION, FBX_GEOMETRY_BINORMAL_VERSION, FBX_GEOMETRY_TANGENT_VERSION,
|
||||
FBX_GEOMETRY_SMOOTHING_VERSION, FBX_GEOMETRY_VCOLOR_VERSION, FBX_GEOMETRY_UV_VERSION,
|
||||
FBX_GEOMETRY_SMOOTHING_VERSION, FBX_GEOMETRY_CREASE_VERSION, FBX_GEOMETRY_VCOLOR_VERSION, FBX_GEOMETRY_UV_VERSION,
|
||||
FBX_GEOMETRY_MATERIAL_VERSION, FBX_GEOMETRY_LAYER_VERSION,
|
||||
FBX_GEOMETRY_SHAPE_VERSION, FBX_DEFORMER_SHAPE_VERSION, FBX_DEFORMER_SHAPECHANNEL_VERSION,
|
||||
FBX_POSE_BIND_VERSION, FBX_DEFORMER_SKIN_VERSION, FBX_DEFORMER_CLUSTER_VERSION,
|
||||
|
@ -865,6 +865,30 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|||
if scene_data.settings.use_custom_props:
|
||||
fbx_data_element_custom_properties(props, me)
|
||||
|
||||
# Subdivision levels. Take them from the first found subsurf modifier from the
|
||||
# first object that has the mesh. Write crease information if the object has
|
||||
# and subsurf modifier.
|
||||
write_crease = False
|
||||
if scene_data.settings.use_subsurf:
|
||||
last_subsurf = None
|
||||
for mod in me_obj.bdata.modifiers:
|
||||
if not (mod.show_render or mod.show_viewport):
|
||||
continue
|
||||
if mod.type == 'SUBSURF' and mod.subdivision_type == 'CATMULL_CLARK':
|
||||
last_subsurf = mod
|
||||
|
||||
if last_subsurf:
|
||||
elem_data_single_int32(geom, b"Smoothness", 2) # Display control mesh and smoothed
|
||||
elem_data_single_int32(geom, b"BoundaryRule", 2) # Round edges like Blender
|
||||
elem_data_single_int32(geom, b"PreviewDivisionLevels", last_subsurf.levels)
|
||||
elem_data_single_int32(geom, b"RenderDivisionLevels", last_subsurf.render_levels)
|
||||
|
||||
elem_data_single_int32(geom, b"PreserveBorders", 0)
|
||||
elem_data_single_int32(geom, b"PreserveHardEdges", 0)
|
||||
elem_data_single_int32(geom, b"PropagateEdgeHardness", 0)
|
||||
|
||||
write_crease = mod.use_creases
|
||||
|
||||
elem_data_single_int32(geom, b"GeometryVersion", FBX_GEOMETRY_VERSION)
|
||||
|
||||
# Vertex cos.
|
||||
|
@ -980,7 +1004,21 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|||
elem_data_single_int32_array(lay_smooth, b"Smoothing", t_ps) # Sight, int32 for bool...
|
||||
del t_ps
|
||||
|
||||
# TODO: Edge crease (LayerElementCrease).
|
||||
# Edge crease for subdivision
|
||||
if write_crease:
|
||||
t_ec = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * edges_nbr
|
||||
for e in me.edges:
|
||||
if e.key not in edges_map:
|
||||
continue # Only loose edges, in theory!
|
||||
t_ec[edges_map[e.key]] = e.crease
|
||||
|
||||
lay_crease = elem_data_single_int32(geom, b"LayerElementEdgeCrease", 0)
|
||||
elem_data_single_int32(lay_crease, b"Version", FBX_GEOMETRY_CREASE_VERSION)
|
||||
elem_data_single_string(lay_crease, b"Name", b"")
|
||||
elem_data_single_string(lay_crease, b"MappingInformationType", b"ByEdge")
|
||||
elem_data_single_string(lay_crease, b"ReferenceInformationType", b"Direct")
|
||||
elem_data_single_float64_array(lay_crease, b"EdgeCrease", t_ec)
|
||||
del t_ec
|
||||
|
||||
# And we are done with edges!
|
||||
del edges_map
|
||||
|
@ -1193,6 +1231,10 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|||
lay_smooth = elem_empty(layer, b"LayerElement")
|
||||
elem_data_single_string(lay_smooth, b"Type", b"LayerElementSmoothing")
|
||||
elem_data_single_int32(lay_smooth, b"TypedIndex", 0)
|
||||
if write_crease:
|
||||
lay_smooth = elem_empty(layer, b"LayerElement")
|
||||
elem_data_single_string(lay_smooth, b"Type", b"LayerElementEdgeCrease")
|
||||
elem_data_single_int32(lay_smooth, b"TypedIndex", 0)
|
||||
if vcolnumber:
|
||||
lay_vcol = elem_empty(layer, b"LayerElement")
|
||||
elem_data_single_string(lay_vcol, b"Type", b"LayerElementColor")
|
||||
|
@ -2217,8 +2259,10 @@ def fbx_data_from_scene(scene, depsgraph, settings):
|
|||
# We cannot use default mesh in that case, or material would not be the right ones...
|
||||
use_org_data = not (is_ob_material or ob.type in BLENDER_OTHER_OBJECT_TYPES)
|
||||
backup_pose_positions = []
|
||||
tmp_mods = []
|
||||
if use_org_data and ob.type == 'MESH':
|
||||
# No need to create a new mesh in this case, if no modifier is active!
|
||||
last_subsurf = None
|
||||
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...
|
||||
|
@ -2232,10 +2276,24 @@ def fbx_data_from_scene(scene, depsgraph, settings):
|
|||
backup_pose_positions.append((armature, armature.pose_position))
|
||||
armature.pose_position = 'REST'
|
||||
elif mod.show_render or mod.show_viewport:
|
||||
use_org_data = False
|
||||
# If exporting with subsurf collect the last Catmull-Clark subsurf modifier
|
||||
# and disable it. We can use the original data as long as this is the first
|
||||
# found applicable subsurf modifier.
|
||||
if settings.use_subsurf and mod.type == 'SUBSURF' and mod.subdivision_type == 'CATMULL_CLARK':
|
||||
if last_subsurf:
|
||||
use_org_data = False
|
||||
last_subsurf = mod
|
||||
else:
|
||||
use_org_data = False
|
||||
if settings.use_subsurf and last_subsurf:
|
||||
# XXX: When exporting with subsurf information temporarily disable
|
||||
# the last subsurf modifier.
|
||||
tmp_mods.append((last_subsurf, last_subsurf.show_render, last_subsurf.show_viewport))
|
||||
last_subsurf.show_render = False
|
||||
last_subsurf.show_viewport = False
|
||||
if not use_org_data:
|
||||
# If modifiers has been altered need to update dependency graph.
|
||||
if backup_pose_positions:
|
||||
if backup_pose_positions or tmp_mods:
|
||||
depsgraph.update()
|
||||
ob_to_convert = ob.evaluated_get(depsgraph) if settings.use_mesh_modifiers else ob
|
||||
# NOTE: The dependency graph might be re-evaluating multiple times, which could
|
||||
|
@ -2249,6 +2307,11 @@ def fbx_data_from_scene(scene, depsgraph, settings):
|
|||
print((armature, pose_position))
|
||||
armature.pose_position = pose_position
|
||||
# Update now, so we don't leave modified state after last object was exported.
|
||||
# Re-enable temporary disabled modifiers.
|
||||
for mod, show_render, show_viewport in tmp_mods:
|
||||
mod.show_render = show_render
|
||||
mod.show_viewport = show_viewport
|
||||
if backup_pose_positions or tmp_mods:
|
||||
depsgraph.update()
|
||||
if use_org_data:
|
||||
data_meshes[ob_obj] = (get_blenderID_key(ob.data), ob.data, False)
|
||||
|
@ -2913,6 +2976,7 @@ def save_single(operator, scene, depsgraph, filepath="",
|
|||
use_mesh_modifiers=True,
|
||||
use_mesh_modifiers_render=True,
|
||||
mesh_smooth_type='FACE',
|
||||
use_subsurf=False,
|
||||
use_armature_deform_only=False,
|
||||
bake_anim=True,
|
||||
bake_anim_use_all_bones=True,
|
||||
|
@ -2994,7 +3058,7 @@ def save_single(operator, scene, depsgraph, filepath="",
|
|||
operator.report, (axis_up, axis_forward), global_matrix, global_scale, apply_unit_scale, unit_scale,
|
||||
bake_space_transform, global_matrix_inv, global_matrix_inv_transposed,
|
||||
context_objects, object_types, use_mesh_modifiers, use_mesh_modifiers_render,
|
||||
mesh_smooth_type, use_mesh_edges, use_tspace,
|
||||
mesh_smooth_type, use_subsurf, use_mesh_edges, use_tspace,
|
||||
armature_nodetype, use_armature_deform_only,
|
||||
add_leaf_bones, bone_correction_matrix, bone_correction_matrix_inv,
|
||||
bake_anim, bake_anim_use_all_bones, bake_anim_use_nla_strips, bake_anim_use_all_actions,
|
||||
|
@ -3066,6 +3130,7 @@ def defaults_unity3d():
|
|||
"use_mesh_modifiers_render": True,
|
||||
"use_mesh_edges": False,
|
||||
"mesh_smooth_type": 'FACE',
|
||||
"use_subsurf": False,
|
||||
"use_tspace": False, # XXX Why? Unity is expected to support tspace import...
|
||||
|
||||
"use_armature_deform_only": True,
|
||||
|
|
|
@ -51,6 +51,7 @@ FBX_GEOMETRY_NORMAL_VERSION = 101
|
|||
FBX_GEOMETRY_BINORMAL_VERSION = 101
|
||||
FBX_GEOMETRY_TANGENT_VERSION = 101
|
||||
FBX_GEOMETRY_SMOOTHING_VERSION = 102
|
||||
FBX_GEOMETRY_CREASE_VERSION = 101
|
||||
FBX_GEOMETRY_VCOLOR_VERSION = 101
|
||||
FBX_GEOMETRY_UV_VERSION = 101
|
||||
FBX_GEOMETRY_MATERIAL_VERSION = 101
|
||||
|
@ -1215,7 +1216,7 @@ FBXExportSettings = namedtuple("FBXExportSettings", (
|
|||
"report", "to_axes", "global_matrix", "global_scale", "apply_unit_scale", "unit_scale",
|
||||
"bake_space_transform", "global_matrix_inv", "global_matrix_inv_transposed",
|
||||
"context_objects", "object_types", "use_mesh_modifiers", "use_mesh_modifiers_render",
|
||||
"mesh_smooth_type", "use_mesh_edges", "use_tspace",
|
||||
"mesh_smooth_type", "use_subsurf", "use_mesh_edges", "use_tspace",
|
||||
"armature_nodetype", "use_armature_deform_only", "add_leaf_bones",
|
||||
"bone_correction_matrix", "bone_correction_matrix_inv",
|
||||
"bake_anim", "bake_anim_use_all_bones", "bake_anim_use_nla_strips", "bake_anim_use_all_actions",
|
||||
|
@ -1245,6 +1246,7 @@ FBXImportSettings = namedtuple("FBXImportSettings", (
|
|||
"use_custom_normals", "use_image_search",
|
||||
"use_alpha_decals", "decal_offset",
|
||||
"use_anim", "anim_offset",
|
||||
"use_subsurf",
|
||||
"use_custom_props", "use_custom_props_enum_as_string",
|
||||
"nodal_material_wrap_map", "image_cache",
|
||||
"ignore_leaf_bones", "force_connect_children", "automatic_bone_orientation", "bone_correction_matrix",
|
||||
|
|
|
@ -1112,6 +1112,45 @@ def blen_read_geom_layer_smooth(fbx_obj, mesh):
|
|||
print("warning layer %r mapping type unsupported: %r" % (fbx_layer.id, fbx_layer_mapping))
|
||||
return False
|
||||
|
||||
def blen_read_geom_layer_edge_crease(fbx_obj, mesh):
|
||||
fbx_layer = elem_find_first(fbx_obj, b'LayerElementEdgeCrease')
|
||||
|
||||
if fbx_layer is None:
|
||||
return False
|
||||
|
||||
# all should be valid
|
||||
(fbx_layer_name,
|
||||
fbx_layer_mapping,
|
||||
fbx_layer_ref,
|
||||
) = blen_read_geom_layerinfo(fbx_layer)
|
||||
|
||||
if fbx_layer_mapping != b'ByEdge':
|
||||
return False
|
||||
|
||||
layer_id = b'EdgeCrease'
|
||||
fbx_layer_data = elem_prop_first(elem_find_first(fbx_layer, layer_id))
|
||||
|
||||
# some models have bad edge data, we cant use this info...
|
||||
if not mesh.edges:
|
||||
print("warning skipping edge crease data, no valid edges...")
|
||||
return False
|
||||
|
||||
if fbx_layer_mapping == b'ByEdge':
|
||||
# some models have bad edge data, we cant use this info...
|
||||
if not mesh.edges:
|
||||
print("warning skipping edge crease data, no valid edges...")
|
||||
return False
|
||||
|
||||
blen_data = mesh.edges
|
||||
return blen_read_geom_array_mapped_edge(
|
||||
mesh, blen_data, "crease",
|
||||
fbx_layer_data, None,
|
||||
fbx_layer_mapping, fbx_layer_ref,
|
||||
1, 1, layer_id,
|
||||
)
|
||||
else:
|
||||
print("warning layer %r mapping type unsupported: %r" % (fbx_layer.id, fbx_layer_mapping))
|
||||
return False
|
||||
|
||||
def blen_read_geom_layer_normal(fbx_obj, mesh, xform=None):
|
||||
fbx_layer = elem_find_first(fbx_obj, b'LayerElementNormal')
|
||||
|
@ -1243,6 +1282,8 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings):
|
|||
# must be after edge, face loading.
|
||||
ok_smooth = blen_read_geom_layer_smooth(fbx_obj, mesh)
|
||||
|
||||
ok_crease = blen_read_geom_layer_edge_crease(fbx_obj, mesh)
|
||||
|
||||
ok_normals = False
|
||||
if settings.use_custom_normals:
|
||||
# Note: we store 'temp' normals in loops, since validate() may alter final mesh,
|
||||
|
@ -1276,6 +1317,9 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings):
|
|||
if not ok_smooth:
|
||||
mesh.polygons.foreach_set("use_smooth", [True] * len(mesh.polygons))
|
||||
|
||||
if ok_crease:
|
||||
mesh.use_customdata_edge_crease = True
|
||||
|
||||
if settings.use_custom_props:
|
||||
blen_read_custom_properties(fbx_obj, mesh, settings)
|
||||
|
||||
|
@ -2270,6 +2314,7 @@ def load(operator, context, filepath="",
|
|||
decal_offset=0.0,
|
||||
use_anim=True,
|
||||
anim_offset=1.0,
|
||||
use_subsurf=False,
|
||||
use_custom_props=True,
|
||||
use_custom_props_enum_as_string=True,
|
||||
ignore_leaf_bones=False,
|
||||
|
@ -2398,6 +2443,7 @@ def load(operator, context, filepath="",
|
|||
use_custom_normals, use_image_search,
|
||||
use_alpha_decals, decal_offset,
|
||||
use_anim, anim_offset,
|
||||
use_subsurf,
|
||||
use_custom_props, use_custom_props_enum_as_string,
|
||||
nodal_material_wrap_map, image_cache,
|
||||
ignore_leaf_bones, force_connect_children, automatic_bone_orientation, bone_correction_matrix,
|
||||
|
@ -2799,6 +2845,35 @@ def load(operator, context, filepath="",
|
|||
blend_shape_channels[bc_uuid] = keyblocks
|
||||
_(); del _
|
||||
|
||||
if settings.use_subsurf:
|
||||
perfmon.step("FBX import: Subdivision surfaces")
|
||||
|
||||
# Look through connections for subsurf in meshes and add it to the parent object
|
||||
def _():
|
||||
for fbx_link in fbx_connections.elems:
|
||||
if fbx_link.props[0] != b'OO':
|
||||
continue
|
||||
if fbx_link.props_type[1:3] == b'LL':
|
||||
c_src, c_dst = fbx_link.props[1:3]
|
||||
parent = fbx_helper_nodes.get(c_dst)
|
||||
if parent is None:
|
||||
continue
|
||||
|
||||
child = fbx_helper_nodes.get(c_src)
|
||||
if child is None:
|
||||
fbx_sdata, bl_data = fbx_table_nodes.get(c_src, (None, None))
|
||||
if fbx_sdata.id != b'Geometry':
|
||||
continue
|
||||
|
||||
preview_levels = elem_prop_first(elem_find_first(fbx_sdata, b'PreviewDivisionLevels'))
|
||||
render_levels = elem_prop_first(elem_find_first(fbx_sdata, b'RenderDivisionLevels'))
|
||||
if isinstance(preview_levels, int) and isinstance(render_levels, int):
|
||||
mod = parent.bl_obj.modifiers.new('subsurf', 'SUBSURF')
|
||||
mod.levels = preview_levels
|
||||
mod.render_levels = render_levels
|
||||
|
||||
_(); del _
|
||||
|
||||
if use_anim:
|
||||
perfmon.step("FBX import: Animations...")
|
||||
|
||||
|
|
Loading…
Reference in New Issue