FBX IO: Edge/face sharp access with attributes #104649

Merged
Bastien Montagne merged 1 commits from Mysteryem/blender-addons:attribute_access_sharp_smooth_pr into main 2023-06-26 15:46:41 +02:00
2 changed files with 58 additions and 46 deletions

View File

@ -1030,12 +1030,18 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
# Smoothing.
if smooth_type in {'FACE', 'EDGE'}:
ps_fbx_dtype = np.int32
poly_use_smooth_dtype = bool
edge_use_sharp_dtype = bool
_map = b""
if smooth_type == 'FACE':
t_ps = np.empty(len(me.polygons), dtype=poly_use_smooth_dtype)
me.polygons.foreach_get("use_smooth", t_ps)
# The FBX integer values are usually interpreted as boolean where 0 is False (sharp) and 1 is True
# (smooth).
# The values may also be used to represent smoothing group bitflags, but this does not seem well-supported.
t_ps = MESH_ATTRIBUTE_SHARP_FACE.get_ndarray(attributes)
if t_ps is not None:
# FBX sharp is False, but Blender sharp is True, so invert.
t_ps = np.logical_not(t_ps)
else:
# The mesh has no "sharp_face" attribute, so every face is smooth.
t_ps = np.ones(len(me.polygons), dtype=ps_fbx_dtype)
_map = b"ByPolygon"
else: # EDGE
_map = b"ByEdge"
@ -1050,37 +1056,40 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
mesh_t_ls_view = t_ls[:mesh_poly_nbr]
mesh_t_lei_view = t_lei[:mesh_loop_nbr]
# - Get sharp edges from flat shaded faces
# Get the 'use_smooth' attribute of all polygons.
p_use_smooth_mask = np.empty(mesh_poly_nbr, dtype=poly_use_smooth_dtype)
me.polygons.foreach_get('use_smooth', p_use_smooth_mask)
# Invert to get all flat shaded polygons.
p_flat_mask = np.invert(p_use_smooth_mask, out=p_use_smooth_mask)
# Convert flat shaded polygons to flat shaded loops by repeating each element by the number of sides of
# that polygon.
# Polygon sides can be calculated from the element-wise difference of loop starts appended by the number
# of loops. Alternatively, polygon sides can be retrieved directly from the 'loop_total' attribute of
# polygons, but since we already have t_ls, it tends to be quicker to calculate from t_ls when above
# around 10_000 polygons.
polygon_sides = np.diff(mesh_t_ls_view, append=mesh_loop_nbr)
p_flat_loop_mask = np.repeat(p_flat_mask, polygon_sides)
# Convert flat shaded loops to flat shaded (sharp) edge indices.
# Note that if an edge is in multiple loops that are part of flat shaded faces, its edge index will end
# up in sharp_edge_indices_from_polygons multiple times.
sharp_edge_indices_from_polygons = mesh_t_lei_view[p_flat_loop_mask]
# - Get sharp edges from edges marked as sharp
e_use_sharp_mask = np.empty(mesh_edge_nbr, dtype=edge_use_sharp_dtype)
me.edges.foreach_get('use_edge_sharp', e_use_sharp_mask)
# - Get sharp edges from edges used by more than two loops (and therefore more than two faces)
e_more_than_two_faces_mask = np.bincount(mesh_t_lei_view, minlength=mesh_edge_nbr) > 2
# - Combine with edges that are sharp because they're in more than two faces
e_use_sharp_mask = np.logical_or(e_use_sharp_mask, e_more_than_two_faces_mask, out=e_use_sharp_mask)
# - Get sharp edges from the "sharp_edge" attribute. The attribute may not exist, in which case, there
# are no edges marked as sharp.
e_use_sharp_mask = MESH_ATTRIBUTE_SHARP_EDGE.get_ndarray(attributes)
if e_use_sharp_mask is not None:
# - Combine with edges that are sharp because they're in more than two faces
e_use_sharp_mask = np.logical_or(e_use_sharp_mask, e_more_than_two_faces_mask, out=e_use_sharp_mask)
else:
e_use_sharp_mask = e_more_than_two_faces_mask
# - Combine with edges that are sharp because a polygon they're in has flat shading
e_use_sharp_mask[sharp_edge_indices_from_polygons] = True
# - Get sharp edges from flat shaded faces
p_flat_mask = MESH_ATTRIBUTE_SHARP_FACE.get_ndarray(attributes)
if p_flat_mask is not None:
# Convert flat shaded polygons to flat shaded loops by repeating each element by the number of sides
# of that polygon.
# Polygon sides can be calculated from the element-wise difference of loop starts appended by the
# number of loops. Alternatively, polygon sides can be retrieved directly from the 'loop_total'
# attribute of polygons, but since we already have t_ls, it tends to be quicker to calculate from
# t_ls.
polygon_sides = np.diff(mesh_t_ls_view, append=mesh_loop_nbr)
p_flat_loop_mask = np.repeat(p_flat_mask, polygon_sides)
# Convert flat shaded loops to flat shaded (sharp) edge indices.
# Note that if an edge is in multiple loops that are part of flat shaded faces, its edge index will
# end up in sharp_edge_indices_from_polygons multiple times.
sharp_edge_indices_from_polygons = mesh_t_lei_view[p_flat_loop_mask]
# - Combine with edges that are sharp because a polygon they're in has flat shading
e_use_sharp_mask[sharp_edge_indices_from_polygons] = True
del sharp_edge_indices_from_polygons
del p_flat_loop_mask
del polygon_sides
del p_flat_mask
# - Convert sharp edges to sharp edge keys (t_pvi)
ek_use_sharp_mask = e_use_sharp_mask[t_pvi_edge_indices]
@ -1089,11 +1098,6 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
t_ps = np.invert(ek_use_sharp_mask, out=ek_use_sharp_mask)
del ek_use_sharp_mask
del e_use_sharp_mask
del sharp_edge_indices_from_polygons
del p_flat_loop_mask
del polygon_sides
del p_flat_mask
del p_use_smooth_mask
del mesh_t_lei_view
del mesh_t_ls_view
else:

View File

@ -1315,25 +1315,29 @@ def blen_read_geom_layer_smooth(fbx_obj, mesh):
print("warning skipping sharp edges data, no valid edges...")
return False
blen_data = mesh.edges
blen_data = MESH_ATTRIBUTE_SHARP_EDGE.ensure(mesh.attributes).data
fbx_item_size = 1
assert(fbx_item_size == MESH_ATTRIBUTE_SHARP_EDGE.item_size)
blen_read_geom_array_mapped_edge(
mesh, blen_data, "use_edge_sharp", bool,
mesh, blen_data, MESH_ATTRIBUTE_SHARP_EDGE.foreach_attribute, MESH_ATTRIBUTE_SHARP_EDGE.dtype,
fbx_layer_data, None,
fbx_layer_mapping, fbx_layer_ref,
1, 1, layer_id,
xform=np.logical_not,
1, fbx_item_size, layer_id,
xform=np.logical_not, # in FBX, 0 (False) is sharp, but in Blender True is sharp.
)
# We only set sharp edges here, not face smoothing itself...
mesh.use_auto_smooth = True
return False
elif fbx_layer_mapping == b'ByPolygon':
blen_data = mesh.polygons
blen_data = MESH_ATTRIBUTE_SHARP_FACE.ensure(mesh.attributes).data
fbx_item_size = 1
assert(fbx_item_size == MESH_ATTRIBUTE_SHARP_FACE.item_size)
return blen_read_geom_array_mapped_polygon(
mesh, blen_data, "use_smooth", bool,
mesh, blen_data, MESH_ATTRIBUTE_SHARP_FACE.foreach_attribute, MESH_ATTRIBUTE_SHARP_FACE.dtype,
fbx_layer_data, None,
fbx_layer_mapping, fbx_layer_ref,
1, 1, layer_id,
xform=lambda s: (s != 0), # smoothgroup bitflags, treat as booleans for now
1, fbx_item_size, layer_id,
xform=lambda s: (s == 0), # smoothgroup bitflags, treat as booleans for now
)
else:
print("warning layer %r mapping type unsupported: %r" % (fbx_layer.id, fbx_layer_mapping))
@ -1563,7 +1567,9 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings):
mesh.loops.foreach_get("normal", clnors)
if not ok_smooth:
mesh.polygons.foreach_set("use_smooth", np.full(len(mesh.polygons), True, dtype=bool))
sharp_face = MESH_ATTRIBUTE_SHARP_FACE.get(attributes)
if sharp_face:
attributes.remove(sharp_face)
ok_smooth = True
# Iterating clnors into a nested tuple first is faster than passing clnors.reshape(-1, 3) directly into
@ -1575,7 +1581,9 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings):
mesh.free_normals_split()
if not ok_smooth:
mesh.polygons.foreach_set("use_smooth", np.full(len(mesh.polygons), True, dtype=bool))
sharp_face = MESH_ATTRIBUTE_SHARP_FACE.get(attributes)
if sharp_face:
attributes.remove(sharp_face)
if settings.use_custom_props:
blen_read_custom_properties(fbx_obj, mesh, settings)