|
|
@ -11,6 +11,7 @@ import os
|
|
|
|
import time
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
|
|
from itertools import zip_longest, chain
|
|
|
|
from itertools import zip_longest, chain
|
|
|
|
|
|
|
|
from functools import cache
|
|
|
|
|
|
|
|
|
|
|
|
if "bpy" in locals():
|
|
|
|
if "bpy" in locals():
|
|
|
|
import importlib
|
|
|
|
import importlib
|
|
|
@ -47,9 +48,10 @@ from .fbx_utils import (
|
|
|
|
# Miscellaneous utils.
|
|
|
|
# Miscellaneous utils.
|
|
|
|
PerfMon,
|
|
|
|
PerfMon,
|
|
|
|
units_blender_to_fbx_factor, units_convertor, units_convertor_iter,
|
|
|
|
units_blender_to_fbx_factor, units_convertor, units_convertor_iter,
|
|
|
|
matrix4_to_array, similar_values, similar_values_iter, astype_view_signedness, fast_first_axis_unique,
|
|
|
|
matrix4_to_array, similar_values, shape_difference_exclude_similar, astype_view_signedness, fast_first_axis_unique,
|
|
|
|
|
|
|
|
fast_first_axis_flat,
|
|
|
|
# Mesh transform helpers.
|
|
|
|
# Mesh transform helpers.
|
|
|
|
vcos_transformed_gen, nors_transformed_gen, vcos_transformed, nors_transformed,
|
|
|
|
vcos_transformed_gen, vcos_transformed, nors_transformed,
|
|
|
|
# UUID from key.
|
|
|
|
# UUID from key.
|
|
|
|
get_fbx_uuid_from_key,
|
|
|
|
get_fbx_uuid_from_key,
|
|
|
|
# Key generators.
|
|
|
|
# Key generators.
|
|
|
@ -760,15 +762,19 @@ def fbx_data_mesh_shapes_elements(root, me_obj, me, scene_data, fbx_me_tmpl, fbx
|
|
|
|
for shape, (channel_key, geom_key, shape_verts_co, shape_verts_idx) in shapes.items():
|
|
|
|
for shape, (channel_key, geom_key, shape_verts_co, shape_verts_idx) in shapes.items():
|
|
|
|
# Use vgroups as weights, if defined.
|
|
|
|
# Use vgroups as weights, if defined.
|
|
|
|
if shape.vertex_group and shape.vertex_group in me_obj.bdata.vertex_groups:
|
|
|
|
if shape.vertex_group and shape.vertex_group in me_obj.bdata.vertex_groups:
|
|
|
|
shape_verts_weights = array.array(data_types.ARRAY_FLOAT64, [0.0]) * (len(shape_verts_co) // 3)
|
|
|
|
shape_verts_weights = np.zeros(len(shape_verts_idx), dtype=np.float64)
|
|
|
|
|
|
|
|
# It's slightly faster to iterate and index the underlying memoryview objects
|
|
|
|
|
|
|
|
mv_shape_verts_weights = shape_verts_weights.data
|
|
|
|
|
|
|
|
mv_shape_verts_idx = shape_verts_idx.data
|
|
|
|
vg_idx = me_obj.bdata.vertex_groups[shape.vertex_group].index
|
|
|
|
vg_idx = me_obj.bdata.vertex_groups[shape.vertex_group].index
|
|
|
|
for sk_idx, v_idx in enumerate(shape_verts_idx):
|
|
|
|
for sk_idx, v_idx in enumerate(mv_shape_verts_idx):
|
|
|
|
for vg in vertices[v_idx].groups:
|
|
|
|
for vg in vertices[v_idx].groups:
|
|
|
|
if vg.group == vg_idx:
|
|
|
|
if vg.group == vg_idx:
|
|
|
|
shape_verts_weights[sk_idx] = vg.weight * 100.0
|
|
|
|
mv_shape_verts_weights[sk_idx] = vg.weight
|
|
|
|
break
|
|
|
|
break
|
|
|
|
|
|
|
|
shape_verts_weights *= 100.0
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
shape_verts_weights = array.array(data_types.ARRAY_FLOAT64, [100.0]) * (len(shape_verts_co) // 3)
|
|
|
|
shape_verts_weights = np.full(len(shape_verts_idx), 100.0, dtype=np.float64)
|
|
|
|
channels.append((channel_key, shape, shape_verts_weights))
|
|
|
|
channels.append((channel_key, shape, shape_verts_weights))
|
|
|
|
|
|
|
|
|
|
|
|
geom = elem_data_single_int64(root, b"Geometry", get_fbx_uuid_from_key(geom_key))
|
|
|
|
geom = elem_data_single_int64(root, b"Geometry", get_fbx_uuid_from_key(geom_key))
|
|
|
@ -784,8 +790,7 @@ def fbx_data_mesh_shapes_elements(root, me_obj, me, scene_data, fbx_me_tmpl, fbx
|
|
|
|
elem_data_single_int32_array(geom, b"Indexes", shape_verts_idx)
|
|
|
|
elem_data_single_int32_array(geom, b"Indexes", shape_verts_idx)
|
|
|
|
elem_data_single_float64_array(geom, b"Vertices", shape_verts_co)
|
|
|
|
elem_data_single_float64_array(geom, b"Vertices", shape_verts_co)
|
|
|
|
if write_normals:
|
|
|
|
if write_normals:
|
|
|
|
elem_data_single_float64_array(geom, b"Normals",
|
|
|
|
elem_data_single_float64_array(geom, b"Normals", np.zeros(len(shape_verts_idx) * 3, dtype=np.float64))
|
|
|
|
array.array(data_types.ARRAY_FLOAT64, [0.0]) * len(shape_verts_co))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Yiha! BindPose for shapekeys too! Dodecasigh...
|
|
|
|
# Yiha! BindPose for shapekeys too! Dodecasigh...
|
|
|
|
# XXX Not sure yet whether several bindposes on same mesh are allowed, or not... :/
|
|
|
|
# XXX Not sure yet whether several bindposes on same mesh are allowed, or not... :/
|
|
|
@ -897,19 +902,60 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|
|
|
# We do loose edges as two-vertices faces, if enabled...
|
|
|
|
# We do loose edges as two-vertices faces, if enabled...
|
|
|
|
#
|
|
|
|
#
|
|
|
|
# Note we have to process Edges in the same time, as they are based on poly's loops...
|
|
|
|
# Note we have to process Edges in the same time, as they are based on poly's loops...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Total number of loops, including any extra added for loose edges.
|
|
|
|
loop_nbr = len(me.loops)
|
|
|
|
loop_nbr = len(me.loops)
|
|
|
|
t_pvi = array.array(data_types.ARRAY_INT32, (0,)) * loop_nbr
|
|
|
|
|
|
|
|
t_ls = [None] * len(me.polygons)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
me.loops.foreach_get("vertex_index", t_pvi)
|
|
|
|
# dtypes matching the C data. Matching the C datatype avoids iteration and casting of every element in foreach_get's
|
|
|
|
|
|
|
|
# C code.
|
|
|
|
|
|
|
|
bl_vertex_index_dtype = bl_edge_index_dtype = bl_loop_index_dtype = np.uintc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Start vertex indices of loops. May contain elements for loops added for the export of loose edges.
|
|
|
|
|
|
|
|
t_lvi = np.empty(len(me.loops), dtype=bl_vertex_index_dtype)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Loop start indices of polygons. May contain elements for the polygons added for the export of loose edges.
|
|
|
|
|
|
|
|
t_ls = np.empty(len(me.polygons), dtype=bl_loop_index_dtype)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Vertex indices of edges (unsorted, unlike Mesh.edge_keys), flattened into an array twice the length of the number
|
|
|
|
|
|
|
|
# of edges.
|
|
|
|
|
|
|
|
t_ev = np.empty(len(me.edges) * 2, dtype=bl_vertex_index_dtype)
|
|
|
|
|
|
|
|
# Each edge has two vertex indices, so it's useful to view the array as 2d where each element on the first axis is a
|
|
|
|
|
|
|
|
# pair of vertex indices
|
|
|
|
|
|
|
|
t_ev_pair_view = t_ev.view()
|
|
|
|
|
|
|
|
t_ev_pair_view.shape = (-1, 2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Edge indices of loops. May contain elements for loops added for the export of loose edges.
|
|
|
|
|
|
|
|
t_lei = np.empty(len(me.loops), dtype=bl_edge_index_dtype)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
me.loops.foreach_get("vertex_index", t_lvi)
|
|
|
|
me.polygons.foreach_get("loop_start", t_ls)
|
|
|
|
me.polygons.foreach_get("loop_start", t_ls)
|
|
|
|
|
|
|
|
me.edges.foreach_get("vertices", t_ev)
|
|
|
|
|
|
|
|
me.loops.foreach_get("edge_index", t_lei)
|
|
|
|
|
|
|
|
|
|
|
|
# Add "fake" faces for loose edges.
|
|
|
|
# Add "fake" faces for loose edges. Each "fake" face consists of two loops creating a new 2-sided polygon.
|
|
|
|
if scene_data.settings.use_mesh_edges:
|
|
|
|
if scene_data.settings.use_mesh_edges:
|
|
|
|
t_le = tuple(e.vertices for e in me.edges if e.is_loose)
|
|
|
|
bl_edge_is_loose_dtype = bool
|
|
|
|
t_pvi.extend(chain(*t_le))
|
|
|
|
# Get the mask of edges that are loose
|
|
|
|
t_ls.extend(range(loop_nbr, loop_nbr + len(t_le) * 2, 2))
|
|
|
|
loose_mask = np.empty(len(me.edges), dtype=bl_edge_is_loose_dtype)
|
|
|
|
|
|
|
|
me.edges.foreach_get('is_loose', loose_mask)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
indices_of_loose_edges = np.flatnonzero(loose_mask)
|
|
|
|
|
|
|
|
# Since we add two loops per loose edge, repeat the indices so that there's one for each new loop
|
|
|
|
|
|
|
|
new_loop_edge_indices = np.repeat(indices_of_loose_edges, 2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Get the loose edge vertex index pairs
|
|
|
|
|
|
|
|
t_le = t_ev_pair_view[loose_mask]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# append will automatically flatten the pairs in t_le
|
|
|
|
|
|
|
|
t_lvi = np.append(t_lvi, t_le)
|
|
|
|
|
|
|
|
t_lei = np.append(t_lei, new_loop_edge_indices)
|
|
|
|
|
|
|
|
# Two loops are added per loose edge
|
|
|
|
|
|
|
|
loop_nbr += 2 * len(t_le)
|
|
|
|
|
|
|
|
t_ls = np.append(t_ls, np.arange(len(me.loops), loop_nbr, 2, dtype=t_ls.dtype))
|
|
|
|
del t_le
|
|
|
|
del t_le
|
|
|
|
|
|
|
|
del loose_mask
|
|
|
|
|
|
|
|
del indices_of_loose_edges
|
|
|
|
|
|
|
|
del new_loop_edge_indices
|
|
|
|
|
|
|
|
|
|
|
|
# Edges...
|
|
|
|
# Edges...
|
|
|
|
# Note: Edges are represented as a loop here: each edge uses a single index, which refers to the polygon array.
|
|
|
|
# Note: Edges are represented as a loop here: each edge uses a single index, which refers to the polygon array.
|
|
|
@ -919,83 +965,136 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|
|
|
# for loose edges).
|
|
|
|
# for loose edges).
|
|
|
|
# We also have to store a mapping from real edges to their indices in this array, for edge-mapped data
|
|
|
|
# We also have to store a mapping from real edges to their indices in this array, for edge-mapped data
|
|
|
|
# (like e.g. crease).
|
|
|
|
# (like e.g. crease).
|
|
|
|
t_eli = array.array(data_types.ARRAY_INT32)
|
|
|
|
eli_fbx_dtype = np.int32
|
|
|
|
edges_map = {}
|
|
|
|
|
|
|
|
edges_nbr = 0
|
|
|
|
|
|
|
|
if t_ls and t_pvi:
|
|
|
|
|
|
|
|
# t_ls is loop start indices of polygons, but we want to use it to indicate the end loop of each polygon.
|
|
|
|
|
|
|
|
# The loop end index of a polygon is the loop start index of the next polygon minus one, so the first element of
|
|
|
|
|
|
|
|
# t_ls will be ignored, and we need to add an extra element at the end to signify the end of the last polygon.
|
|
|
|
|
|
|
|
# If we were to add another polygon to the mesh, its loop start index would be the next loop index.
|
|
|
|
|
|
|
|
t_ls = set(t_ls[1:])
|
|
|
|
|
|
|
|
t_ls.add(loop_nbr)
|
|
|
|
|
|
|
|
todo_edges = [None] * len(me.edges) * 2
|
|
|
|
|
|
|
|
# Sigh, cannot access edge.key through foreach_get... :/
|
|
|
|
|
|
|
|
me.edges.foreach_get("vertices", todo_edges)
|
|
|
|
|
|
|
|
todo_edges = set((v1, v2) if v1 < v2 else (v2, v1) for v1, v2 in zip(*(iter(todo_edges),) * 2))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
li = 0
|
|
|
|
# Edge index of each unique edge-key, used to map per-edge data to unique edge-keys (t_pvi).
|
|
|
|
vi = vi_start = t_pvi[0]
|
|
|
|
t_pvi_edge_indices = np.empty(0, dtype=t_lei.dtype)
|
|
|
|
for li_next, vi_next in enumerate(t_pvi[1:] + t_pvi[:1], start=1):
|
|
|
|
|
|
|
|
if li_next in t_ls: # End of a poly's loop.
|
|
|
|
|
|
|
|
vi2 = vi_start
|
|
|
|
|
|
|
|
vi_start = vi_next
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
vi2 = vi_next
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
e_key = (vi, vi2) if vi < vi2 else (vi2, vi)
|
|
|
|
pvi_fbx_dtype = np.int32
|
|
|
|
if e_key in todo_edges:
|
|
|
|
if t_ls.size and t_lvi.size:
|
|
|
|
t_eli.append(li)
|
|
|
|
# Get unsorted edge keys by indexing the edge->vertex-indices array by the loop->edge-index array.
|
|
|
|
todo_edges.remove(e_key)
|
|
|
|
t_pvi_edge_keys = t_ev_pair_view[t_lei]
|
|
|
|
edges_map[e_key] = edges_nbr
|
|
|
|
|
|
|
|
edges_nbr += 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vi = vi_next
|
|
|
|
# Sort each [edge_start_n, edge_end_n] pair to get edge keys. Heapsort seems to be the fastest for this specific
|
|
|
|
li = li_next
|
|
|
|
# use case.
|
|
|
|
# End of edges!
|
|
|
|
t_pvi_edge_keys.sort(axis=1, kind='heapsort')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Note that finding unique edge keys means that if there are multiple edges that share the same vertices (which
|
|
|
|
|
|
|
|
# shouldn't normally happen), only the first edge found in loops will be exported along with its per-edge data.
|
|
|
|
|
|
|
|
# To export separate edges that share the same vertices, fast_first_axis_unique can be replaced with np.unique
|
|
|
|
|
|
|
|
# with t_lei as the first argument, finding unique edges rather than unique edge keys.
|
|
|
|
|
|
|
|
#
|
|
|
|
|
|
|
|
# Since we want the unique values in their original order, the only part we care about is the indices of the
|
|
|
|
|
|
|
|
# first occurrence of the unique elements in t_pvi_edge_keys, so we can use our fast uniqueness helper function.
|
|
|
|
|
|
|
|
t_eli = fast_first_axis_unique(t_pvi_edge_keys, return_unique=False, return_index=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# To get the indices of the elements in t_pvi_edge_keys that produce unique values, but in the original order of
|
|
|
|
|
|
|
|
# t_pvi_edge_keys, t_eli must be sorted.
|
|
|
|
|
|
|
|
# Due to loops and their edge keys tending to have a partial ordering within meshes, sorting with kind='stable'
|
|
|
|
|
|
|
|
# with radix sort tends to be faster than the default of kind='quicksort' with introsort.
|
|
|
|
|
|
|
|
t_eli.sort(kind='stable')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Edge index of each element in unique t_pvi_edge_keys, used to map per-edge data such as sharp and creases.
|
|
|
|
|
|
|
|
t_pvi_edge_indices = t_lei[t_eli]
|
|
|
|
|
|
|
|
|
|
|
|
# We have to ^-1 last index of each loop.
|
|
|
|
# We have to ^-1 last index of each loop.
|
|
|
|
for ls in t_ls:
|
|
|
|
# Ensure t_pvi is the correct number of bits before inverting.
|
|
|
|
t_pvi[ls - 1] ^= -1
|
|
|
|
t_pvi = astype_view_signedness(t_lvi, pvi_fbx_dtype)
|
|
|
|
|
|
|
|
# The index of the end of each loop is one before the index of the start of the next loop.
|
|
|
|
|
|
|
|
t_pvi[t_ls[1:] - 1] ^= -1
|
|
|
|
|
|
|
|
# The index of the end of the last loop will be the very last index.
|
|
|
|
|
|
|
|
t_pvi[-1] ^= -1
|
|
|
|
|
|
|
|
del t_pvi_edge_keys
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
# Should be empty, but make sure it's the correct type.
|
|
|
|
|
|
|
|
t_pvi = np.empty(0, dtype=pvi_fbx_dtype)
|
|
|
|
|
|
|
|
t_eli = np.empty(0, dtype=eli_fbx_dtype)
|
|
|
|
|
|
|
|
|
|
|
|
# And finally we can write data!
|
|
|
|
# And finally we can write data!
|
|
|
|
|
|
|
|
t_pvi = astype_view_signedness(t_pvi, pvi_fbx_dtype)
|
|
|
|
|
|
|
|
t_eli = astype_view_signedness(t_eli, eli_fbx_dtype)
|
|
|
|
elem_data_single_int32_array(geom, b"PolygonVertexIndex", t_pvi)
|
|
|
|
elem_data_single_int32_array(geom, b"PolygonVertexIndex", t_pvi)
|
|
|
|
elem_data_single_int32_array(geom, b"Edges", t_eli)
|
|
|
|
elem_data_single_int32_array(geom, b"Edges", t_eli)
|
|
|
|
|
|
|
|
del t_lvi
|
|
|
|
del t_pvi
|
|
|
|
del t_pvi
|
|
|
|
del t_ls
|
|
|
|
|
|
|
|
del t_eli
|
|
|
|
del t_eli
|
|
|
|
|
|
|
|
del t_ev
|
|
|
|
|
|
|
|
del t_ev_pair_view
|
|
|
|
|
|
|
|
|
|
|
|
# And now, layers!
|
|
|
|
# And now, layers!
|
|
|
|
|
|
|
|
|
|
|
|
# Smoothing.
|
|
|
|
# Smoothing.
|
|
|
|
if smooth_type in {'FACE', 'EDGE'}:
|
|
|
|
if smooth_type in {'FACE', 'EDGE'}:
|
|
|
|
t_ps = None
|
|
|
|
ps_fbx_dtype = np.int32
|
|
|
|
|
|
|
|
poly_use_smooth_dtype = bool
|
|
|
|
|
|
|
|
edge_use_sharp_dtype = bool
|
|
|
|
_map = b""
|
|
|
|
_map = b""
|
|
|
|
if smooth_type == 'FACE':
|
|
|
|
if smooth_type == 'FACE':
|
|
|
|
t_ps = array.array(data_types.ARRAY_INT32, (0,)) * len(me.polygons)
|
|
|
|
t_ps = np.empty(len(me.polygons), dtype=poly_use_smooth_dtype)
|
|
|
|
me.polygons.foreach_get("use_smooth", t_ps)
|
|
|
|
me.polygons.foreach_get("use_smooth", t_ps)
|
|
|
|
_map = b"ByPolygon"
|
|
|
|
_map = b"ByPolygon"
|
|
|
|
else: # EDGE
|
|
|
|
else: # EDGE
|
|
|
|
|
|
|
|
_map = b"ByEdge"
|
|
|
|
|
|
|
|
if t_pvi_edge_indices.size:
|
|
|
|
# Write Edge Smoothing.
|
|
|
|
# Write Edge Smoothing.
|
|
|
|
# Note edge is sharp also if it's used by more than two faces, or one of its faces is flat.
|
|
|
|
# Note edge is sharp also if it's used by more than two faces, or one of its faces is flat.
|
|
|
|
t_ps = array.array(data_types.ARRAY_INT32, (0,)) * edges_nbr
|
|
|
|
mesh_poly_nbr = len(me.polygons)
|
|
|
|
sharp_edges = set()
|
|
|
|
mesh_edge_nbr = len(me.edges)
|
|
|
|
temp_sharp_edges = {}
|
|
|
|
mesh_loop_nbr = len(me.loops)
|
|
|
|
for p in me.polygons:
|
|
|
|
# t_ls and t_lei may contain extra polygons or loops added for loose edges that are not present in the
|
|
|
|
if not p.use_smooth:
|
|
|
|
# mesh data, so create views that exclude the extra data added for loose edges.
|
|
|
|
sharp_edges.update(p.edge_keys)
|
|
|
|
mesh_t_ls_view = t_ls[:mesh_poly_nbr]
|
|
|
|
continue
|
|
|
|
mesh_t_lei_view = t_lei[:mesh_loop_nbr]
|
|
|
|
for k in p.edge_keys:
|
|
|
|
|
|
|
|
if temp_sharp_edges.setdefault(k, 0) > 1:
|
|
|
|
# - Get sharp edges from flat shaded faces
|
|
|
|
sharp_edges.add(k)
|
|
|
|
# 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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# - 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# - Convert sharp edges to sharp edge keys (t_pvi)
|
|
|
|
|
|
|
|
ek_use_sharp_mask = e_use_sharp_mask[t_pvi_edge_indices]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# - Sharp edges are indicated in FBX as zero (False), so invert
|
|
|
|
|
|
|
|
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:
|
|
|
|
else:
|
|
|
|
temp_sharp_edges[k] += 1
|
|
|
|
t_ps = np.empty(0, dtype=ps_fbx_dtype)
|
|
|
|
del temp_sharp_edges
|
|
|
|
t_ps = t_ps.astype(ps_fbx_dtype, copy=False)
|
|
|
|
for e in me.edges:
|
|
|
|
|
|
|
|
if e.key not in edges_map:
|
|
|
|
|
|
|
|
continue # Only loose edges, in theory!
|
|
|
|
|
|
|
|
t_ps[edges_map[e.key]] = not (e.use_edge_sharp or (e.key in sharp_edges))
|
|
|
|
|
|
|
|
_map = b"ByEdge"
|
|
|
|
|
|
|
|
lay_smooth = elem_data_single_int32(geom, b"LayerElementSmoothing", 0)
|
|
|
|
lay_smooth = elem_data_single_int32(geom, b"LayerElementSmoothing", 0)
|
|
|
|
elem_data_single_int32(lay_smooth, b"Version", FBX_GEOMETRY_SMOOTHING_VERSION)
|
|
|
|
elem_data_single_int32(lay_smooth, b"Version", FBX_GEOMETRY_SMOOTHING_VERSION)
|
|
|
|
elem_data_single_string(lay_smooth, b"Name", b"")
|
|
|
|
elem_data_single_string(lay_smooth, b"Name", b"")
|
|
|
@ -1003,16 +1102,29 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|
|
|
elem_data_single_string(lay_smooth, b"ReferenceInformationType", b"Direct")
|
|
|
|
elem_data_single_string(lay_smooth, b"ReferenceInformationType", b"Direct")
|
|
|
|
elem_data_single_int32_array(lay_smooth, b"Smoothing", t_ps) # Sight, int32 for bool...
|
|
|
|
elem_data_single_int32_array(lay_smooth, b"Smoothing", t_ps) # Sight, int32 for bool...
|
|
|
|
del t_ps
|
|
|
|
del t_ps
|
|
|
|
|
|
|
|
del t_ls
|
|
|
|
|
|
|
|
del t_lei
|
|
|
|
|
|
|
|
|
|
|
|
# Edge crease for subdivision
|
|
|
|
# Edge crease for subdivision
|
|
|
|
if write_crease:
|
|
|
|
if write_crease:
|
|
|
|
t_ec = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * edges_nbr
|
|
|
|
ec_fbx_dtype = np.float64
|
|
|
|
for e in me.edges:
|
|
|
|
if t_pvi_edge_indices.size:
|
|
|
|
if e.key not in edges_map:
|
|
|
|
ec_bl_dtype = np.single
|
|
|
|
continue # Only loose edges, in theory!
|
|
|
|
t_ec_raw = np.empty(len(me.edges), dtype=ec_bl_dtype)
|
|
|
|
|
|
|
|
me.edges.foreach_get('crease', t_ec_raw)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Convert to t_pvi edge-keys.
|
|
|
|
|
|
|
|
t_ec_ek_raw = t_ec_raw[t_pvi_edge_indices]
|
|
|
|
|
|
|
|
|
|
|
|
# Blender squares those values before sending them to OpenSubdiv, when other software don't,
|
|
|
|
# Blender squares those values before sending them to OpenSubdiv, when other software don't,
|
|
|
|
# so we need to compensate that to get similar results through FBX...
|
|
|
|
# so we need to compensate that to get similar results through FBX...
|
|
|
|
t_ec[edges_map[e.key]] = e.crease * e.crease
|
|
|
|
# Use the precision of the fbx dtype for the calculation since it's usually higher precision.
|
|
|
|
|
|
|
|
t_ec_ek_raw = t_ec_ek_raw.astype(ec_fbx_dtype, copy=False)
|
|
|
|
|
|
|
|
t_ec = np.square(t_ec_ek_raw, out=t_ec_ek_raw)
|
|
|
|
|
|
|
|
del t_ec_ek_raw
|
|
|
|
|
|
|
|
del t_ec_raw
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
t_ec = np.empty(0, dtype=ec_fbx_dtype)
|
|
|
|
|
|
|
|
|
|
|
|
lay_crease = elem_data_single_int32(geom, b"LayerElementEdgeCrease", 0)
|
|
|
|
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_int32(lay_crease, b"Version", FBX_GEOMETRY_CREASE_VERSION)
|
|
|
@ -1023,7 +1135,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|
|
|
del t_ec
|
|
|
|
del t_ec
|
|
|
|
|
|
|
|
|
|
|
|
# And we are done with edges!
|
|
|
|
# And we are done with edges!
|
|
|
|
del edges_map
|
|
|
|
del t_pvi_edge_indices
|
|
|
|
|
|
|
|
|
|
|
|
# Loop normals.
|
|
|
|
# Loop normals.
|
|
|
|
tspacenumber = 0
|
|
|
|
tspacenumber = 0
|
|
|
@ -1033,28 +1145,35 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|
|
|
# but this does not seem well supported by apps currently...
|
|
|
|
# but this does not seem well supported by apps currently...
|
|
|
|
me.calc_normals_split()
|
|
|
|
me.calc_normals_split()
|
|
|
|
|
|
|
|
|
|
|
|
t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops) * 3
|
|
|
|
ln_bl_dtype = np.single
|
|
|
|
|
|
|
|
ln_fbx_dtype = np.float64
|
|
|
|
|
|
|
|
t_ln = np.empty(len(me.loops) * 3, dtype=ln_bl_dtype)
|
|
|
|
me.loops.foreach_get("normal", t_ln)
|
|
|
|
me.loops.foreach_get("normal", t_ln)
|
|
|
|
t_ln = nors_transformed_gen(t_ln, geom_mat_no)
|
|
|
|
t_ln = nors_transformed(t_ln, geom_mat_no, ln_fbx_dtype)
|
|
|
|
if 0:
|
|
|
|
if 0:
|
|
|
|
t_ln = tuple(t_ln) # No choice... :/
|
|
|
|
lnidx_fbx_dtype = np.int32
|
|
|
|
|
|
|
|
|
|
|
|
lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0)
|
|
|
|
lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0)
|
|
|
|
elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_NORMAL_VERSION)
|
|
|
|
elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_NORMAL_VERSION)
|
|
|
|
elem_data_single_string(lay_nor, b"Name", b"")
|
|
|
|
elem_data_single_string(lay_nor, b"Name", b"")
|
|
|
|
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
|
|
|
|
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
|
|
|
|
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"IndexToDirect")
|
|
|
|
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"IndexToDirect")
|
|
|
|
|
|
|
|
|
|
|
|
ln2idx = tuple(set(t_ln))
|
|
|
|
# Tuple of unique sorted normals and then the index in the unique sorted normals of each normal in t_ln.
|
|
|
|
elem_data_single_float64_array(lay_nor, b"Normals", chain(*ln2idx))
|
|
|
|
# Since we don't care about how the normals are sorted, only that they're unique, we can use the fast unique
|
|
|
|
|
|
|
|
# helper function.
|
|
|
|
|
|
|
|
t_ln, t_lnidx = fast_first_axis_unique(t_ln.reshape(-1, 3), return_inverse=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Convert to the type for fbx
|
|
|
|
|
|
|
|
t_lnidx = astype_view_signedness(t_lnidx, lnidx_fbx_dtype)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
elem_data_single_float64_array(lay_nor, b"Normals", t_ln)
|
|
|
|
# Normal weights, no idea what it is.
|
|
|
|
# Normal weights, no idea what it is.
|
|
|
|
# t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(ln2idx)
|
|
|
|
# t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(t_ln)
|
|
|
|
# elem_data_single_float64_array(lay_nor, b"NormalsW", t_lnw)
|
|
|
|
# elem_data_single_float64_array(lay_nor, b"NormalsW", t_lnw)
|
|
|
|
|
|
|
|
|
|
|
|
ln2idx = {nor: idx for idx, nor in enumerate(ln2idx)}
|
|
|
|
elem_data_single_int32_array(lay_nor, b"NormalsIndex", t_lnidx)
|
|
|
|
elem_data_single_int32_array(lay_nor, b"NormalsIndex", (ln2idx[n] for n in t_ln))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
del ln2idx
|
|
|
|
del t_lnidx
|
|
|
|
# del t_lnw
|
|
|
|
# del t_lnw
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0)
|
|
|
|
lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0)
|
|
|
@ -1062,7 +1181,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|
|
|
elem_data_single_string(lay_nor, b"Name", b"")
|
|
|
|
elem_data_single_string(lay_nor, b"Name", b"")
|
|
|
|
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
|
|
|
|
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
|
|
|
|
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
|
|
|
|
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
|
|
|
|
elem_data_single_float64_array(lay_nor, b"Normals", chain(*t_ln))
|
|
|
|
elem_data_single_float64_array(lay_nor, b"Normals", t_ln)
|
|
|
|
# Normal weights, no idea what it is.
|
|
|
|
# Normal weights, no idea what it is.
|
|
|
|
# t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
|
|
|
|
# t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
|
|
|
|
# elem_data_single_float64_array(lay_nor, b"NormalsW", t_ln)
|
|
|
|
# elem_data_single_float64_array(lay_nor, b"NormalsW", t_ln)
|
|
|
@ -1073,9 +1192,10 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|
|
|
tspacenumber = len(me.uv_layers)
|
|
|
|
tspacenumber = len(me.uv_layers)
|
|
|
|
if tspacenumber:
|
|
|
|
if tspacenumber:
|
|
|
|
# We can only compute tspace on tessellated meshes, need to check that here...
|
|
|
|
# We can only compute tspace on tessellated meshes, need to check that here...
|
|
|
|
t_lt = [None] * len(me.polygons)
|
|
|
|
lt_bl_dtype = np.uintc
|
|
|
|
|
|
|
|
t_lt = np.empty(len(me.polygons), dtype=lt_bl_dtype)
|
|
|
|
me.polygons.foreach_get("loop_total", t_lt)
|
|
|
|
me.polygons.foreach_get("loop_total", t_lt)
|
|
|
|
if any((lt > 4 for lt in t_lt)):
|
|
|
|
if (t_lt > 4).any():
|
|
|
|
del t_lt
|
|
|
|
del t_lt
|
|
|
|
scene_data.settings.report(
|
|
|
|
scene_data.settings.report(
|
|
|
|
{'WARNING'},
|
|
|
|
{'WARNING'},
|
|
|
@ -1084,7 +1204,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
del t_lt
|
|
|
|
del t_lt
|
|
|
|
num_loops = len(me.loops)
|
|
|
|
num_loops = len(me.loops)
|
|
|
|
t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * num_loops * 3
|
|
|
|
t_ln = np.empty(num_loops * 3, dtype=ln_bl_dtype)
|
|
|
|
# t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
|
|
|
|
# t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
|
|
|
|
uv_names = [uvlayer.name for uvlayer in me.uv_layers]
|
|
|
|
uv_names = [uvlayer.name for uvlayer in me.uv_layers]
|
|
|
|
# Annoying, `me.calc_tangent` errors in case there is no geometry...
|
|
|
|
# Annoying, `me.calc_tangent` errors in case there is no geometry...
|
|
|
@ -1102,7 +1222,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|
|
|
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
|
|
|
|
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
|
|
|
|
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
|
|
|
|
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
|
|
|
|
elem_data_single_float64_array(lay_nor, b"Binormals",
|
|
|
|
elem_data_single_float64_array(lay_nor, b"Binormals",
|
|
|
|
chain(*nors_transformed_gen(t_ln, geom_mat_no)))
|
|
|
|
nors_transformed(t_ln, geom_mat_no, ln_fbx_dtype))
|
|
|
|
# Binormal weights, no idea what it is.
|
|
|
|
# Binormal weights, no idea what it is.
|
|
|
|
# elem_data_single_float64_array(lay_nor, b"BinormalsW", t_lnw)
|
|
|
|
# elem_data_single_float64_array(lay_nor, b"BinormalsW", t_lnw)
|
|
|
|
|
|
|
|
|
|
|
@ -1115,7 +1235,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|
|
|
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
|
|
|
|
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
|
|
|
|
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
|
|
|
|
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
|
|
|
|
elem_data_single_float64_array(lay_nor, b"Tangents",
|
|
|
|
elem_data_single_float64_array(lay_nor, b"Tangents",
|
|
|
|
chain(*nors_transformed_gen(t_ln, geom_mat_no)))
|
|
|
|
nors_transformed(t_ln, geom_mat_no, ln_fbx_dtype))
|
|
|
|
# Tangent weights, no idea what it is.
|
|
|
|
# Tangent weights, no idea what it is.
|
|
|
|
# elem_data_single_float64_array(lay_nor, b"TangentsW", t_lnw)
|
|
|
|
# elem_data_single_float64_array(lay_nor, b"TangentsW", t_lnw)
|
|
|
|
|
|
|
|
|
|
|
@ -1129,70 +1249,164 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|
|
|
colors_type = scene_data.settings.colors_type
|
|
|
|
colors_type = scene_data.settings.colors_type
|
|
|
|
vcolnumber = 0 if colors_type == 'NONE' else len(me.color_attributes)
|
|
|
|
vcolnumber = 0 if colors_type == 'NONE' else len(me.color_attributes)
|
|
|
|
if vcolnumber:
|
|
|
|
if vcolnumber:
|
|
|
|
def _coltuples_gen(raw_cols):
|
|
|
|
|
|
|
|
return zip(*(iter(raw_cols),) * 4)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
color_prop_name = "color_srgb" if colors_type == 'SRGB' else "color"
|
|
|
|
color_prop_name = "color_srgb" if colors_type == 'SRGB' else "color"
|
|
|
|
|
|
|
|
# ByteColorAttribute color also gets returned by the API as single precision float
|
|
|
|
|
|
|
|
bl_lc_dtype = np.single
|
|
|
|
|
|
|
|
bl_lvi_dtype = np.uintc
|
|
|
|
|
|
|
|
fbx_lc_dtype = np.float64
|
|
|
|
|
|
|
|
fbx_lcidx_dtype = np.int32
|
|
|
|
|
|
|
|
t_lvi = None
|
|
|
|
|
|
|
|
|
|
|
|
for colindex, collayer in enumerate(me.color_attributes):
|
|
|
|
color_attributes = me.color_attributes
|
|
|
|
|
|
|
|
if scene_data.settings.prioritize_active_color:
|
|
|
|
|
|
|
|
active_color = me.color_attributes.active_color
|
|
|
|
|
|
|
|
color_attributes = sorted(color_attributes, key=lambda x: x == active_color, reverse=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for colindex, collayer in enumerate(color_attributes):
|
|
|
|
is_point = collayer.domain == "POINT"
|
|
|
|
is_point = collayer.domain == "POINT"
|
|
|
|
vcollen = len(me.vertices if is_point else me.loops)
|
|
|
|
vcollen = len(me.vertices if is_point else me.loops)
|
|
|
|
t_lc = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * vcollen * 4
|
|
|
|
# Each rgba component is flattened in the array
|
|
|
|
|
|
|
|
t_lc = np.empty(vcollen * 4, dtype=bl_lc_dtype)
|
|
|
|
collayer.data.foreach_get(color_prop_name, t_lc)
|
|
|
|
collayer.data.foreach_get(color_prop_name, t_lc)
|
|
|
|
|
|
|
|
|
|
|
|
lay_vcol = elem_data_single_int32(geom, b"LayerElementColor", colindex)
|
|
|
|
lay_vcol = elem_data_single_int32(geom, b"LayerElementColor", colindex)
|
|
|
|
elem_data_single_int32(lay_vcol, b"Version", FBX_GEOMETRY_VCOLOR_VERSION)
|
|
|
|
elem_data_single_int32(lay_vcol, b"Version", FBX_GEOMETRY_VCOLOR_VERSION)
|
|
|
|
elem_data_single_string_unicode(lay_vcol, b"Name", collayer.name)
|
|
|
|
elem_data_single_string_unicode(lay_vcol, b"Name", collayer.name)
|
|
|
|
elem_data_single_string(lay_vcol, b"MappingInformationType", b"ByPolygonVertex")
|
|
|
|
elem_data_single_string(lay_vcol, b"MappingInformationType", b"ByPolygonVertex")
|
|
|
|
elem_data_single_string(lay_vcol, b"ReferenceInformationType", b"IndexToDirect")
|
|
|
|
elem_data_single_string(lay_vcol, b"ReferenceInformationType", b"IndexToDirect")
|
|
|
|
|
|
|
|
|
|
|
|
col2idx = tuple(set(_coltuples_gen(t_lc)))
|
|
|
|
# Use the fast uniqueness helper function since we don't care about sorting.
|
|
|
|
elem_data_single_float64_array(lay_vcol, b"Colors", chain(*col2idx)) # Flatten again...
|
|
|
|
t_lc, col_indices = fast_first_axis_unique(t_lc.reshape(-1, 4), return_inverse=True)
|
|
|
|
|
|
|
|
|
|
|
|
col2idx = {col: idx for idx, col in enumerate(col2idx)}
|
|
|
|
|
|
|
|
col_indices = list(col2idx[c] for c in _coltuples_gen(t_lc))
|
|
|
|
|
|
|
|
if is_point:
|
|
|
|
if is_point:
|
|
|
|
# for "point" domain colors, we could directly emit them
|
|
|
|
# for "point" domain colors, we could directly emit them
|
|
|
|
# with a "ByVertex" mapping type, but some software does not
|
|
|
|
# with a "ByVertex" mapping type, but some software does not
|
|
|
|
# properly understand that. So expand to full "ByPolygonVertex"
|
|
|
|
# properly understand that. So expand to full "ByPolygonVertex"
|
|
|
|
# index map.
|
|
|
|
# index map.
|
|
|
|
col_indices = list((col_indices[c.vertex_index] for c in me.loops))
|
|
|
|
if t_lvi is None:
|
|
|
|
|
|
|
|
t_lvi = np.empty(len(me.loops), dtype=bl_lvi_dtype)
|
|
|
|
|
|
|
|
me.loops.foreach_get("vertex_index", t_lvi)
|
|
|
|
|
|
|
|
col_indices = col_indices[t_lvi]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
t_lc = t_lc.astype(fbx_lc_dtype, copy=False)
|
|
|
|
|
|
|
|
col_indices = astype_view_signedness(col_indices, fbx_lcidx_dtype)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
elem_data_single_float64_array(lay_vcol, b"Colors", t_lc)
|
|
|
|
elem_data_single_int32_array(lay_vcol, b"ColorIndex", col_indices)
|
|
|
|
elem_data_single_int32_array(lay_vcol, b"ColorIndex", col_indices)
|
|
|
|
del col2idx
|
|
|
|
|
|
|
|
del t_lc
|
|
|
|
del t_lc
|
|
|
|
del _coltuples_gen
|
|
|
|
del col_indices
|
|
|
|
|
|
|
|
del t_lvi
|
|
|
|
|
|
|
|
|
|
|
|
# Write UV layers.
|
|
|
|
# Write UV layers.
|
|
|
|
# Note: LayerElementTexture is deprecated since FBX 2011 - luckily!
|
|
|
|
# Note: LayerElementTexture is deprecated since FBX 2011 - luckily!
|
|
|
|
# Textures are now only related to materials, in FBX!
|
|
|
|
# Textures are now only related to materials, in FBX!
|
|
|
|
uvnumber = len(me.uv_layers)
|
|
|
|
uvnumber = len(me.uv_layers)
|
|
|
|
if uvnumber:
|
|
|
|
if uvnumber:
|
|
|
|
|
|
|
|
luv_bl_dtype = np.single
|
|
|
|
|
|
|
|
luv_fbx_dtype = np.float64
|
|
|
|
|
|
|
|
lv_idx_bl_dtype = np.uintc
|
|
|
|
|
|
|
|
lv_idx_fbx_dtype = np.int32
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
t_luv = np.empty(len(me.loops) * 2, dtype=luv_bl_dtype)
|
|
|
|
|
|
|
|
# Fast view for sort-based uniqueness of pairs.
|
|
|
|
|
|
|
|
t_luv_fast_pair_view = fast_first_axis_flat(t_luv.reshape(-1, 2))
|
|
|
|
|
|
|
|
# It must be a view of t_luv otherwise it won't update when t_luv is updated.
|
|
|
|
|
|
|
|
assert(t_luv_fast_pair_view.base is t_luv)
|
|
|
|
|
|
|
|
|
|
|
|
# Looks like this mapping is also expected to convey UV islands (arg..... :((((( ).
|
|
|
|
# Looks like this mapping is also expected to convey UV islands (arg..... :((((( ).
|
|
|
|
# So we need to generate unique triplets (uv, vertex_idx) here, not only just based on UV values.
|
|
|
|
# So we need to generate unique triplets (uv, vertex_idx) here, not only just based on UV values.
|
|
|
|
def _uvtuples_gen(raw_uvs, raw_lvidxs):
|
|
|
|
t_lvidx = np.empty(len(me.loops), dtype=lv_idx_bl_dtype)
|
|
|
|
return zip(zip(*(iter(raw_uvs),) * 2), raw_lvidxs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
t_luv = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops) * 2
|
|
|
|
|
|
|
|
t_lvidx = array.array(data_types.ARRAY_INT32, (0,)) * len(me.loops)
|
|
|
|
|
|
|
|
me.loops.foreach_get("vertex_index", t_lvidx)
|
|
|
|
me.loops.foreach_get("vertex_index", t_lvidx)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# If we were to create a combined array of (uv, vertex_idx) elements, we could find unique triplets by sorting
|
|
|
|
|
|
|
|
# that array by first sorting by the vertex_idx column and then sorting by the uv column using a stable sorting
|
|
|
|
|
|
|
|
# algorithm.
|
|
|
|
|
|
|
|
# This is exactly what we'll do, but without creating the combined array, because only the uv elements are
|
|
|
|
|
|
|
|
# included in the export and the vertex_idx column is the same for every uv layer.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Because the vertex_idx column is the same for every uv layer, the vertex_idx column can be sorted in advance.
|
|
|
|
|
|
|
|
# argsort gets the indices that sort the array, which are needed to be able to sort the array of uv pairs in the
|
|
|
|
|
|
|
|
# same way to create the indices that recreate the full uvs from the unique uvs.
|
|
|
|
|
|
|
|
# Loops and vertices tend to naturally have a partial ordering, which makes sorting with kind='stable' (radix
|
|
|
|
|
|
|
|
# sort) faster than the default of kind='quicksort' (introsort) in most cases.
|
|
|
|
|
|
|
|
perm_vidx = t_lvidx.argsort(kind='stable')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Mask and uv indices arrays will be modified and re-used by each uv layer.
|
|
|
|
|
|
|
|
unique_mask = np.empty(len(me.loops), dtype=np.bool_)
|
|
|
|
|
|
|
|
unique_mask[:1] = True
|
|
|
|
|
|
|
|
uv_indices = np.empty(len(me.loops), dtype=lv_idx_fbx_dtype)
|
|
|
|
|
|
|
|
|
|
|
|
for uvindex, uvlayer in enumerate(me.uv_layers):
|
|
|
|
for uvindex, uvlayer in enumerate(me.uv_layers):
|
|
|
|
uvlayer.data.foreach_get("uv", t_luv)
|
|
|
|
|
|
|
|
lay_uv = elem_data_single_int32(geom, b"LayerElementUV", uvindex)
|
|
|
|
lay_uv = elem_data_single_int32(geom, b"LayerElementUV", uvindex)
|
|
|
|
elem_data_single_int32(lay_uv, b"Version", FBX_GEOMETRY_UV_VERSION)
|
|
|
|
elem_data_single_int32(lay_uv, b"Version", FBX_GEOMETRY_UV_VERSION)
|
|
|
|
elem_data_single_string_unicode(lay_uv, b"Name", uvlayer.name)
|
|
|
|
elem_data_single_string_unicode(lay_uv, b"Name", uvlayer.name)
|
|
|
|
elem_data_single_string(lay_uv, b"MappingInformationType", b"ByPolygonVertex")
|
|
|
|
elem_data_single_string(lay_uv, b"MappingInformationType", b"ByPolygonVertex")
|
|
|
|
elem_data_single_string(lay_uv, b"ReferenceInformationType", b"IndexToDirect")
|
|
|
|
elem_data_single_string(lay_uv, b"ReferenceInformationType", b"IndexToDirect")
|
|
|
|
|
|
|
|
|
|
|
|
uv_ids = tuple(set(_uvtuples_gen(t_luv, t_lvidx)))
|
|
|
|
uvlayer.data.foreach_get("uv", t_luv)
|
|
|
|
elem_data_single_float64_array(lay_uv, b"UV", chain(*(uv for uv, vidx in uv_ids))) # Flatten again...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uv2idx = {uv_id: idx for idx, uv_id in enumerate(uv_ids)}
|
|
|
|
# t_luv_fast_pair_view is a view in a dtype that compares elements by individual bytes, but float types have
|
|
|
|
elem_data_single_int32_array(lay_uv, b"UVIndex", (uv2idx[uv_id] for uv_id in _uvtuples_gen(t_luv, t_lvidx)))
|
|
|
|
# separate byte representations of positive and negative zero. For uniqueness, these should be considered
|
|
|
|
del uv2idx
|
|
|
|
# the same, so replace all -0.0 with 0.0 in advance.
|
|
|
|
del uv_ids
|
|
|
|
t_luv[t_luv == -0.0] = 0.0
|
|
|
|
del t_luv
|
|
|
|
|
|
|
|
|
|
|
|
# These steps to create unique_uv_pairs are the same as how np.unique would find unique values by sorting a
|
|
|
|
|
|
|
|
# structured array where each element is a triplet of (uv, vertex_idx), except uv and vertex_idx are
|
|
|
|
|
|
|
|
# separate arrays here and vertex_idx has already been sorted in advance.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Sort according to the vertex_idx column, using the precalculated indices that sort it.
|
|
|
|
|
|
|
|
sorted_t_luv_fast = t_luv_fast_pair_view[perm_vidx]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Get the indices that would sort the sorted uv pairs. Stable sorting must be used to maintain the sorting
|
|
|
|
|
|
|
|
# of the vertex indices.
|
|
|
|
|
|
|
|
perm_uv_pairs = sorted_t_luv_fast.argsort(kind='stable')
|
|
|
|
|
|
|
|
# Use the indices to sort both the uv pairs and the vertex_idx columns.
|
|
|
|
|
|
|
|
perm_combined = perm_vidx[perm_uv_pairs]
|
|
|
|
|
|
|
|
sorted_vidx = t_lvidx[perm_combined]
|
|
|
|
|
|
|
|
sorted_t_luv_fast = sorted_t_luv_fast[perm_uv_pairs]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Create a mask where either the uv pair doesn't equal the previous value in the array, or the vertex index
|
|
|
|
|
|
|
|
# doesn't equal the previous value, these will be the unique uv-vidx triplets.
|
|
|
|
|
|
|
|
# For an imaginary triplet array:
|
|
|
|
|
|
|
|
# ...
|
|
|
|
|
|
|
|
# [(0.4, 0.2), 0]
|
|
|
|
|
|
|
|
# [(0.4, 0.2), 1] -> Unique because vertex index different from previous
|
|
|
|
|
|
|
|
# [(0.4, 0.2), 2] -> Unique because vertex index different from previous
|
|
|
|
|
|
|
|
# [(0.7, 0.6), 2] -> Unique because uv different from previous
|
|
|
|
|
|
|
|
# [(0.7, 0.6), 2]
|
|
|
|
|
|
|
|
# ...
|
|
|
|
|
|
|
|
# Output the result into unique_mask.
|
|
|
|
|
|
|
|
np.logical_or(sorted_t_luv_fast[1:] != sorted_t_luv_fast[:-1], sorted_vidx[1:] != sorted_vidx[:-1],
|
|
|
|
|
|
|
|
out=unique_mask[1:])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Get each uv pair marked as unique by the unique_mask and then view as the original dtype.
|
|
|
|
|
|
|
|
unique_uvs = sorted_t_luv_fast[unique_mask].view(luv_bl_dtype)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# NaN values are considered invalid and indicate a bug somewhere else in Blender or in an addon, we want
|
|
|
|
|
|
|
|
# these bugs to be reported instead of hiding them by allowing the export to continue.
|
|
|
|
|
|
|
|
if np.isnan(unique_uvs).any():
|
|
|
|
|
|
|
|
raise RuntimeError("UV layer %s on %r has invalid UVs containing NaN values" % (uvlayer.name, me))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Convert to the type needed for fbx
|
|
|
|
|
|
|
|
unique_uvs = unique_uvs.astype(luv_fbx_dtype, copy=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Set the indices of pairs in unique_uvs that reconstruct the pairs in t_luv into uv_indices.
|
|
|
|
|
|
|
|
# uv_indices will then be the same as an inverse array returned by np.unique with return_inverse=True.
|
|
|
|
|
|
|
|
uv_indices[perm_combined] = np.cumsum(unique_mask, dtype=uv_indices.dtype) - 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
elem_data_single_float64_array(lay_uv, b"UV", unique_uvs)
|
|
|
|
|
|
|
|
elem_data_single_int32_array(lay_uv, b"UVIndex", uv_indices)
|
|
|
|
|
|
|
|
del unique_uvs
|
|
|
|
|
|
|
|
del sorted_t_luv_fast
|
|
|
|
|
|
|
|
del sorted_vidx
|
|
|
|
|
|
|
|
del perm_uv_pairs
|
|
|
|
|
|
|
|
del perm_combined
|
|
|
|
|
|
|
|
del uv_indices
|
|
|
|
|
|
|
|
del unique_mask
|
|
|
|
|
|
|
|
del perm_vidx
|
|
|
|
del t_lvidx
|
|
|
|
del t_lvidx
|
|
|
|
del _uvtuples_gen
|
|
|
|
del t_luv
|
|
|
|
|
|
|
|
del t_luv_fast_pair_view
|
|
|
|
|
|
|
|
|
|
|
|
# Face's materials.
|
|
|
|
# Face's materials.
|
|
|
|
me_fbxmaterials_idx = scene_data.mesh_material_indices.get(me)
|
|
|
|
me_fbxmaterials_idx = scene_data.mesh_material_indices.get(me)
|
|
|
@ -1205,16 +1419,27 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|
|
|
elem_data_single_string(lay_ma, b"Name", b"")
|
|
|
|
elem_data_single_string(lay_ma, b"Name", b"")
|
|
|
|
nbr_mats = len(me_fbxmaterials_idx)
|
|
|
|
nbr_mats = len(me_fbxmaterials_idx)
|
|
|
|
if nbr_mats > 1:
|
|
|
|
if nbr_mats > 1:
|
|
|
|
t_pm = array.array(data_types.ARRAY_INT32, (0,)) * len(me.polygons)
|
|
|
|
bl_pm_dtype = np.uintc
|
|
|
|
|
|
|
|
fbx_pm_dtype = np.int32
|
|
|
|
|
|
|
|
t_pm = np.empty(len(me.polygons), dtype=bl_pm_dtype)
|
|
|
|
me.polygons.foreach_get("material_index", t_pm)
|
|
|
|
me.polygons.foreach_get("material_index", t_pm)
|
|
|
|
|
|
|
|
|
|
|
|
# We have to validate mat indices, and map them to FBX indices.
|
|
|
|
# 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).
|
|
|
|
# Note a mat might not be in me_fbxmaterials_idx (e.g. node mats are ignored).
|
|
|
|
def_ma = next(me_fbxmaterials_idx[m] for m in me_blmaterials if m in me_fbxmaterials_idx)
|
|
|
|
|
|
|
|
blmaterials_to_fbxmaterials_idxs = [me_fbxmaterials_idx.get(m, def_ma) for m in me_blmaterials]
|
|
|
|
# The first valid material will be used for materials out of bounds of me_blmaterials or materials not
|
|
|
|
ma_idx_limit = len(blmaterials_to_fbxmaterials_idxs)
|
|
|
|
# in me_fbxmaterials_idx.
|
|
|
|
_gen = (blmaterials_to_fbxmaterials_idxs[m] if m < ma_idx_limit else def_ma for m in t_pm)
|
|
|
|
def_me_blmaterial_idx, def_ma = next(
|
|
|
|
t_pm = array.array(data_types.ARRAY_INT32, _gen)
|
|
|
|
(i, me_fbxmaterials_idx[m]) for i, m in enumerate(me_blmaterials) if m in me_fbxmaterials_idx)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Set material indices that are out of bounds to the default material index
|
|
|
|
|
|
|
|
mat_idx_limit = len(me_blmaterials)
|
|
|
|
|
|
|
|
t_pm[t_pm >= mat_idx_limit] = def_me_blmaterial_idx
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Map to FBX indices. Materials not in me_fbxmaterials_idx will be set to the default material index.
|
|
|
|
|
|
|
|
blmat_fbx_idx = np.fromiter((me_fbxmaterials_idx.get(m, def_ma) for m in me_blmaterials),
|
|
|
|
|
|
|
|
dtype=fbx_pm_dtype)
|
|
|
|
|
|
|
|
t_pm = blmat_fbx_idx[t_pm]
|
|
|
|
|
|
|
|
|
|
|
|
elem_data_single_string(lay_ma, 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
|
|
|
|
# XXX Logically, should be "Direct" reference type, since we do not have any index array, and have one
|
|
|
@ -2375,6 +2600,18 @@ def fbx_data_from_scene(scene, depsgraph, settings):
|
|
|
|
# ShapeKeys.
|
|
|
|
# ShapeKeys.
|
|
|
|
data_deformers_shape = {}
|
|
|
|
data_deformers_shape = {}
|
|
|
|
geom_mat_co = settings.global_matrix if settings.bake_space_transform else None
|
|
|
|
geom_mat_co = settings.global_matrix if settings.bake_space_transform else None
|
|
|
|
|
|
|
|
co_bl_dtype = np.single
|
|
|
|
|
|
|
|
co_fbx_dtype = np.float64
|
|
|
|
|
|
|
|
idx_fbx_dtype = np.int32
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def empty_verts_fallbacks():
|
|
|
|
|
|
|
|
"""Create fallback arrays for when there are no verts"""
|
|
|
|
|
|
|
|
# FBX does not like empty shapes (makes Unity crash e.g.).
|
|
|
|
|
|
|
|
# To prevent this, we add a vertex that does nothing, but it keeps the shape key intact
|
|
|
|
|
|
|
|
single_vert_co = np.zeros((1, 3), dtype=co_fbx_dtype)
|
|
|
|
|
|
|
|
single_vert_idx = np.zeros(1, dtype=idx_fbx_dtype)
|
|
|
|
|
|
|
|
return single_vert_co, single_vert_idx
|
|
|
|
|
|
|
|
|
|
|
|
for me_key, me, _free in data_meshes.values():
|
|
|
|
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...
|
|
|
|
if not (me.shape_keys and len(me.shape_keys.key_blocks) > 1): # We do not want basis-only relative skeys...
|
|
|
|
continue
|
|
|
|
continue
|
|
|
@ -2382,41 +2619,44 @@ def fbx_data_from_scene(scene, depsgraph, settings):
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
shapes_key = get_blender_mesh_shape_key(me)
|
|
|
|
shapes_key = get_blender_mesh_shape_key(me)
|
|
|
|
# We gather all vcos first, since some skeys may be based on others...
|
|
|
|
|
|
|
|
_cos = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.vertices) * 3
|
|
|
|
|
|
|
|
me.vertices.foreach_get("co", _cos)
|
|
|
|
|
|
|
|
v_cos = tuple(vcos_transformed_gen(_cos, geom_mat_co))
|
|
|
|
|
|
|
|
sk_cos = {}
|
|
|
|
|
|
|
|
for shape in me.shape_keys.key_blocks[1:]:
|
|
|
|
|
|
|
|
shape.data.foreach_get("co", _cos)
|
|
|
|
|
|
|
|
sk_cos[shape] = tuple(vcos_transformed_gen(_cos, geom_mat_co))
|
|
|
|
|
|
|
|
sk_base = me.shape_keys.key_blocks[0]
|
|
|
|
sk_base = me.shape_keys.key_blocks[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Get and cache only the cos that we need
|
|
|
|
|
|
|
|
@cache
|
|
|
|
|
|
|
|
def sk_cos(shape_key):
|
|
|
|
|
|
|
|
_cos = np.empty(len(me.vertices) * 3, dtype=co_bl_dtype)
|
|
|
|
|
|
|
|
if shape_key == sk_base:
|
|
|
|
|
|
|
|
me.vertices.foreach_get("co", _cos)
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
shape_key.data.foreach_get("co", _cos)
|
|
|
|
|
|
|
|
return vcos_transformed(_cos, geom_mat_co, co_fbx_dtype)
|
|
|
|
|
|
|
|
|
|
|
|
for shape in me.shape_keys.key_blocks[1:]:
|
|
|
|
for shape in me.shape_keys.key_blocks[1:]:
|
|
|
|
# Only write vertices really different from org coordinates!
|
|
|
|
# Only write vertices really different from base coordinates!
|
|
|
|
shape_verts_co = []
|
|
|
|
relative_key = shape.relative_key
|
|
|
|
shape_verts_idx = []
|
|
|
|
if shape == relative_key:
|
|
|
|
|
|
|
|
# Shape is its own relative key, so it does nothing
|
|
|
|
|
|
|
|
shape_verts_co, shape_verts_idx = empty_verts_fallbacks()
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
sv_cos = sk_cos(shape)
|
|
|
|
|
|
|
|
ref_cos = sk_cos(shape.relative_key)
|
|
|
|
|
|
|
|
|
|
|
|
sv_cos = sk_cos[shape]
|
|
|
|
# Exclude cos similar to ref_cos and get the indices of the cos that remain
|
|
|
|
ref_cos = v_cos if shape.relative_key == sk_base else sk_cos[shape.relative_key]
|
|
|
|
shape_verts_co, shape_verts_idx = shape_difference_exclude_similar(sv_cos, ref_cos)
|
|
|
|
for idx, (sv_co, ref_co) in enumerate(zip(sv_cos, ref_cos)):
|
|
|
|
|
|
|
|
if similar_values_iter(sv_co, ref_co):
|
|
|
|
|
|
|
|
# Note: Maybe this is a bit too simplistic, should we use real shape base here? Though FBX does not
|
|
|
|
|
|
|
|
# have this at all... Anyway, this should cover most common cases imho.
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
shape_verts_co.extend(Vector(sv_co) - Vector(ref_co))
|
|
|
|
|
|
|
|
shape_verts_idx.append(idx)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# FBX does not like empty shapes (makes Unity crash e.g.).
|
|
|
|
if not shape_verts_co.size:
|
|
|
|
# To prevent this, we add a vertex that does nothing, but it keeps the shape key intact
|
|
|
|
shape_verts_co, shape_verts_idx = empty_verts_fallbacks()
|
|
|
|
if not shape_verts_co:
|
|
|
|
else:
|
|
|
|
shape_verts_co.extend((0, 0, 0))
|
|
|
|
# Ensure the indices are of the correct type
|
|
|
|
shape_verts_idx.append(0)
|
|
|
|
shape_verts_idx = astype_view_signedness(shape_verts_idx, idx_fbx_dtype)
|
|
|
|
|
|
|
|
|
|
|
|
channel_key, geom_key = get_blender_mesh_shape_channel_key(me, shape)
|
|
|
|
channel_key, geom_key = get_blender_mesh_shape_channel_key(me, shape)
|
|
|
|
data = (channel_key, geom_key, shape_verts_co, shape_verts_idx)
|
|
|
|
data = (channel_key, geom_key, shape_verts_co, shape_verts_idx)
|
|
|
|
data_deformers_shape.setdefault(me, (me_key, shapes_key, {}))[2][shape] = data
|
|
|
|
data_deformers_shape.setdefault(me, (me_key, shapes_key, {}))[2][shape] = data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
del sk_cos
|
|
|
|
|
|
|
|
|
|
|
|
perfmon.step("FBX export prepare: Wrapping Armatures...")
|
|
|
|
perfmon.step("FBX export prepare: Wrapping Armatures...")
|
|
|
|
|
|
|
|
|
|
|
|
# Armatures!
|
|
|
|
# Armatures!
|
|
|
@ -3055,6 +3295,7 @@ def save_single(operator, scene, depsgraph, filepath="",
|
|
|
|
bake_space_transform=False,
|
|
|
|
bake_space_transform=False,
|
|
|
|
armature_nodetype='NULL',
|
|
|
|
armature_nodetype='NULL',
|
|
|
|
colors_type='SRGB',
|
|
|
|
colors_type='SRGB',
|
|
|
|
|
|
|
|
prioritize_active_color=False,
|
|
|
|
**kwargs
|
|
|
|
**kwargs
|
|
|
|
):
|
|
|
|
):
|
|
|
|
|
|
|
|
|
|
|
@ -3122,7 +3363,7 @@ def save_single(operator, scene, depsgraph, filepath="",
|
|
|
|
add_leaf_bones, bone_correction_matrix, bone_correction_matrix_inv,
|
|
|
|
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,
|
|
|
|
bake_anim, bake_anim_use_all_bones, bake_anim_use_nla_strips, bake_anim_use_all_actions,
|
|
|
|
bake_anim_step, bake_anim_simplify_factor, bake_anim_force_startend_keying,
|
|
|
|
bake_anim_step, bake_anim_simplify_factor, bake_anim_force_startend_keying,
|
|
|
|
False, media_settings, use_custom_props, colors_type,
|
|
|
|
False, media_settings, use_custom_props, colors_type, prioritize_active_color
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
import bpy_extras.io_utils
|
|
|
|
import bpy_extras.io_utils
|
|
|
|