Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulien Duroure <julien.duroure@gmail.com>2020-09-05 16:19:55 +0300
committerJulien Duroure <julien.duroure@gmail.com>2020-09-05 16:19:55 +0300
commit566a68e2083a2a5d7ad66157dc9b79e410bd038c (patch)
tree8c50d53ef5cf051d9df6ce6e8742752264b5bd4d /io_scene_gltf2
parent0dca80fdc4e81ee7163d46366ab0905c6d2b8ef9 (diff)
glTF exporter: perf: use numpy to speedup primitive extract
Diffstat (limited to 'io_scene_gltf2')
-rwxr-xr-xio_scene_gltf2/__init__.py2
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_extract.py710
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py43
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py8
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py15
5 files changed, 448 insertions, 330 deletions
diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py
index d3a247de..daa6c7e4 100755
--- a/io_scene_gltf2/__init__.py
+++ b/io_scene_gltf2/__init__.py
@@ -15,7 +15,7 @@
bl_info = {
'name': 'glTF 2.0 format',
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
- "version": (1, 4, 8),
+ "version": (1, 4, 9),
'blender': (2, 90, 0),
'location': 'File > Import-Export',
'description': 'Import-Export as glTF 2.0',
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
index eef05044..ca38aa72 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
@@ -12,128 +12,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-# Imports
-#
-
+import numpy as np
from mathutils import Vector, Quaternion, Matrix
from . import gltf2_blender_export_keys
from ...io.com.gltf2_io_debug import print_console
-from ...io.com.gltf2_io_color_management import color_srgb_to_scene_linear
from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins
-#
-# Classes
-#
-
-class Prim:
- def __init__(self):
- self.verts = {}
- self.indices = []
-
-class ShapeKey:
- def __init__(self, shape_key, split_normals):
- self.shape_key = shape_key
- self.split_normals = split_normals
-
-
-#
-# Functions
-#
-
-def convert_swizzle_normal(loc, armature, blender_object, export_settings):
- """Convert a normal data from Blender coordinate system to glTF coordinate system."""
- if (not armature) or (not blender_object):
- # Classic case. Mesh is not skined, no need to apply armature transfoms on vertices / normals / tangents
- if export_settings[gltf2_blender_export_keys.YUP]:
- return Vector((loc[0], loc[2], -loc[1]))
- else:
- return Vector((loc[0], loc[1], loc[2]))
- else:
- # Mesh is skined, we have to apply armature transforms on data
- apply_matrix = (armature.matrix_world.inverted() @ blender_object.matrix_world).to_3x3().inverted()
- apply_matrix.transpose()
- new_loc = ((armature.matrix_world.to_3x3() @ apply_matrix).to_4x4() @ Matrix.Translation(Vector((loc[0], loc[1], loc[2])))).to_translation()
- new_loc.normalize()
-
- if export_settings[gltf2_blender_export_keys.YUP]:
- return Vector((new_loc[0], new_loc[2], -new_loc[1]))
- else:
- return Vector((new_loc[0], new_loc[1], new_loc[2]))
-
-def convert_swizzle_location(loc, armature, blender_object, export_settings):
- """Convert a location from Blender coordinate system to glTF coordinate system."""
- if (not armature) or (not blender_object):
- # Classic case. Mesh is not skined, no need to apply armature transfoms on vertices / normals / tangents
- if export_settings[gltf2_blender_export_keys.YUP]:
- return Vector((loc[0], loc[2], -loc[1]))
- else:
- return Vector((loc[0], loc[1], loc[2]))
- else:
- # Mesh is skined, we have to apply armature transforms on data
- apply_matrix = armature.matrix_world.inverted() @ blender_object.matrix_world
- new_loc = (armature.matrix_world @ apply_matrix @ Matrix.Translation(Vector((loc[0], loc[1], loc[2])))).to_translation()
-
- if export_settings[gltf2_blender_export_keys.YUP]:
- return Vector((new_loc[0], new_loc[2], -new_loc[1]))
- else:
- return Vector((new_loc[0], new_loc[1], new_loc[2]))
-
-
-def convert_swizzle_tangent(tan, armature, blender_object, export_settings):
- """Convert a tangent from Blender coordinate system to glTF coordinate system."""
- if tan[0] == 0.0 and tan[1] == 0.0 and tan[2] == 0.0:
- print_console('WARNING', 'Tangent has zero length.')
-
- if (not armature) or (not blender_object):
- # Classic case. Mesh is not skined, no need to apply armature transfoms on vertices / normals / tangents
- if export_settings[gltf2_blender_export_keys.YUP]:
- return Vector((tan[0], tan[2], -tan[1]))
- else:
- return Vector((tan[0], tan[1], tan[2]))
- else:
- # Mesh is skined, we have to apply armature transforms on data
- apply_matrix = armature.matrix_world.inverted() @ blender_object.matrix_world
- new_tan = apply_matrix.to_quaternion() @ Vector((tan[0], tan[1], tan[2]))
- if export_settings[gltf2_blender_export_keys.YUP]:
- return Vector((new_tan[0], new_tan[2], -new_tan[1]))
- else:
- return Vector((new_tan[0], new_tan[1], new_tan[2]))
-
-def convert_swizzle_rotation(rot, export_settings):
- """
- Convert a quaternion rotation from Blender coordinate system to glTF coordinate system.
-
- 'w' is still at first position.
- """
- if export_settings[gltf2_blender_export_keys.YUP]:
- return Quaternion((rot[0], rot[1], rot[3], -rot[2]))
- else:
- return Quaternion((rot[0], rot[1], rot[2], rot[3]))
-
-
-def convert_swizzle_scale(scale, export_settings):
- """Convert a scale from Blender coordinate system to glTF coordinate system."""
- if export_settings[gltf2_blender_export_keys.YUP]:
- return Vector((scale[0], scale[2], scale[1]))
- else:
- return Vector((scale[0], scale[1], scale[2]))
-
-
def extract_primitives(glTF, blender_mesh, library, blender_object, blender_vertex_groups, modifiers, export_settings):
- """
- Extract primitives from a mesh. Polygons are triangulated and sorted by material.
- Vertices in multiple faces get split up as necessary.
- """
+ """Extract primitives from a mesh."""
print_console('INFO', 'Extracting primitive: ' + blender_mesh.name)
- #
- # First, decide what attributes to gather (eg. how many COLOR_n, etc.)
- # Also calculate normals/tangents now if necessary.
- #
-
use_normals = export_settings[gltf2_blender_export_keys.NORMALS]
if use_normals:
blender_mesh.calc_normals_split()
@@ -156,8 +46,8 @@ def extract_primitives(glTF, blender_mesh, library, blender_object, blender_vert
if export_settings[gltf2_blender_export_keys.COLORS]:
color_max = len(blender_mesh.vertex_colors)
- bone_max = 0 # number of JOINTS_n sets needed (1 set = 4 influences)
armature = None
+ skin = None
if blender_vertex_groups and export_settings[gltf2_blender_export_keys.SKINS]:
if modifiers is not None:
modifiers_dict = {m.type: m for m in modifiers}
@@ -181,191 +71,201 @@ def extract_primitives(glTF, blender_mesh, library, blender_object, blender_vert
skin = gltf2_blender_gather_skins.gather_skin(armature, export_settings)
if not skin:
armature = None
- else:
- joint_name_to_index = {joint.name: index for index, joint in enumerate(skin.joints)}
- group_to_joint = [joint_name_to_index.get(g.name) for g in blender_vertex_groups]
-
- # Find out max number of bone influences
- for blender_polygon in blender_mesh.polygons:
- for loop_index in blender_polygon.loop_indices:
- vertex_index = blender_mesh.loops[loop_index].vertex_index
- groups_count = len(blender_mesh.vertices[vertex_index].groups)
- bones_count = (groups_count + 3) // 4
- bone_max = max(bone_max, bones_count)
use_morph_normals = use_normals and export_settings[gltf2_blender_export_keys.MORPH_NORMAL]
use_morph_tangents = use_morph_normals and use_tangents and export_settings[gltf2_blender_export_keys.MORPH_TANGENT]
- shape_keys = []
+ key_blocks = []
if blender_mesh.shape_keys and export_settings[gltf2_blender_export_keys.MORPH]:
- for blender_shape_key in blender_mesh.shape_keys.key_blocks:
- if blender_shape_key == blender_shape_key.relative_key or blender_shape_key.mute:
- continue
+ key_blocks = [
+ key_block
+ for key_block in blender_mesh.shape_keys.key_blocks
+ if not (key_block == key_block.relative_key or key_block.mute)
+ ]
- split_normals = None
- if use_morph_normals:
- split_normals = blender_shape_key.normals_split_get()
-
- shape_keys.append(ShapeKey(
- blender_shape_key,
- split_normals,
- ))
+ use_materials = export_settings[gltf2_blender_export_keys.MATERIALS]
+ # Fetch vert positions and bone data (joint,weights)
- use_materials = export_settings[gltf2_blender_export_keys.MATERIALS]
+ locs, morph_locs = __get_positions(blender_mesh, key_blocks, armature, blender_object, export_settings)
+ if skin:
+ vert_bones, num_joint_sets = __get_bone_data(blender_mesh, skin, blender_vertex_groups)
+ # In Blender there is both per-vert data, like position, and also per-loop
+ # (loop=corner-of-poly) data, like normals or UVs. glTF only has per-vert
+ # data, so we need to split Blender verts up into potentially-multiple glTF
+ # verts.
#
- # Gather the verts and indices for each primitive.
+ # First, we'll collect a "dot" for every loop: a struct that stores all the
+ # attributes at that loop, namely the vertex index (which determines all
+ # per-vert data), and all the per-loop data like UVs, etc.
#
+ # Each unique dot will become one unique glTF vert.
- prims = {}
+ # List all fields the dot struct needs.
+ dot_fields = [('vertex_index', np.uint32)]
+ if use_normals:
+ dot_fields += [('nx', np.float32), ('ny', np.float32), ('nz', np.float32)]
+ if use_tangents:
+ dot_fields += [('tx', np.float32), ('ty', np.float32), ('tz', np.float32), ('tw', np.float32)]
+ for uv_i in range(tex_coord_max):
+ dot_fields += [('uv%dx' % uv_i, np.float32), ('uv%dy' % uv_i, np.float32)]
+ for col_i in range(color_max):
+ dot_fields += [
+ ('color%dr' % col_i, np.float32),
+ ('color%dg' % col_i, np.float32),
+ ('color%db' % col_i, np.float32),
+ ('color%da' % col_i, np.float32),
+ ]
+ if use_morph_normals:
+ for morph_i, _ in enumerate(key_blocks):
+ dot_fields += [
+ ('morph%dnx' % morph_i, np.float32),
+ ('morph%dny' % morph_i, np.float32),
+ ('morph%dnz' % morph_i, np.float32),
+ ]
+
+ dots = np.empty(len(blender_mesh.loops), dtype=np.dtype(dot_fields))
+
+ vidxs = np.empty(len(blender_mesh.loops))
+ blender_mesh.loops.foreach_get('vertex_index', vidxs)
+ dots['vertex_index'] = vidxs
+ del vidxs
+
+ if use_normals:
+ kbs = key_blocks if use_morph_normals else []
+ normals, morph_normals = __get_normals(
+ blender_mesh, kbs, armature, blender_object, export_settings
+ )
+ dots['nx'] = normals[:, 0]
+ dots['ny'] = normals[:, 1]
+ dots['nz'] = normals[:, 2]
+ del normals
+ for morph_i, ns in enumerate(morph_normals):
+ dots['morph%dnx' % morph_i] = ns[:, 0]
+ dots['morph%dny' % morph_i] = ns[:, 1]
+ dots['morph%dnz' % morph_i] = ns[:, 2]
+ del morph_normals
+
+ if use_tangents:
+ tangents = __get_tangents(blender_mesh, armature, blender_object, export_settings)
+ dots['tx'] = tangents[:, 0]
+ dots['ty'] = tangents[:, 1]
+ dots['tz'] = tangents[:, 2]
+ del tangents
+ signs = __get_bitangent_signs(blender_mesh, armature, blender_object, export_settings)
+ dots['tw'] = signs
+ del signs
+
+ for uv_i in range(tex_coord_max):
+ uvs = __get_uvs(blender_mesh, uv_i)
+ dots['uv%dx' % uv_i] = uvs[:, 0]
+ dots['uv%dy' % uv_i] = uvs[:, 1]
+ del uvs
+
+ for col_i in range(color_max):
+ colors = __get_colors(blender_mesh, col_i)
+ dots['color%dr' % col_i] = colors[:, 0]
+ dots['color%dg' % col_i] = colors[:, 1]
+ dots['color%db' % col_i] = colors[:, 2]
+ dots['color%da' % col_i] = colors[:, 3]
+ del colors
+
+ # Calculate triangles and sort them into primitives.
blender_mesh.calc_loop_triangles()
+ loop_indices = np.empty(len(blender_mesh.loop_triangles) * 3, dtype=np.uint32)
+ blender_mesh.loop_triangles.foreach_get('loops', loop_indices)
- for loop_tri in blender_mesh.loop_triangles:
- blender_polygon = blender_mesh.polygons[loop_tri.polygon_index]
-
- material_idx = -1
- if use_materials:
- material_idx = blender_polygon.material_index
-
- prim = prims.get(material_idx)
- if not prim:
- prim = Prim()
- prims[material_idx] = prim
-
- for loop_index in loop_tri.loops:
- vertex_index = blender_mesh.loops[loop_index].vertex_index
- vertex = blender_mesh.vertices[vertex_index]
-
- # vert will be a tuple of all the vertex attributes.
- # Used as cache key in prim.verts.
- vert = (vertex_index,)
-
- v = vertex.co
- vert += ((v[0], v[1], v[2]),)
-
- if use_normals:
- n = blender_mesh.loops[loop_index].normal
- vert += ((n[0], n[1], n[2]),)
- if use_tangents:
- t = blender_mesh.loops[loop_index].tangent
- b = blender_mesh.loops[loop_index].bitangent
- vert += ((t[0], t[1], t[2]),)
- vert += ((b[0], b[1], b[2]),)
- # TODO: store just bitangent_sign in vert, not whole bitangent?
-
- for tex_coord_index in range(0, tex_coord_max):
- uv = blender_mesh.uv_layers[tex_coord_index].data[loop_index].uv
- uv = (uv.x, 1.0 - uv.y)
- vert += (uv,)
-
- for color_index in range(0, color_max):
- color = blender_mesh.vertex_colors[color_index].data[loop_index].color
- col = (
- color_srgb_to_scene_linear(color[0]),
- color_srgb_to_scene_linear(color[1]),
- color_srgb_to_scene_linear(color[2]),
- color[3],
- )
- vert += (col,)
-
- if bone_max:
- bones = []
- if vertex.groups:
- for group_element in vertex.groups:
- weight = group_element.weight
- if weight <= 0.0:
- continue
- try:
- joint = group_to_joint[group_element.group]
- except Exception:
- continue
- if joint is None:
- continue
- bones.append((joint, weight))
- bones.sort(key=lambda x: x[1], reverse=True)
- bones = tuple(bones)
- if not bones: bones = ((0, 1.0),) # HACK for verts with zero weight (#308)
- vert += (bones,)
-
- for shape_key in shape_keys:
- v_morph = shape_key.shape_key.data[vertex_index].co
- v_morph = v_morph - v # store delta
- vert += ((v_morph[0], v_morph[1], v_morph[2]),)
-
- if use_morph_normals:
- normals = shape_key.split_normals
- n_morph = Vector(normals[loop_index * 3 : loop_index * 3 + 3])
- n_morph = n_morph - n # store delta
- vert += ((n_morph[0], n_morph[1], n_morph[2]),)
-
- vert_idx = prim.verts.setdefault(vert, len(prim.verts))
- prim.indices.append(vert_idx)
+ prim_indices = {} # maps material index to TRIANGLES-style indices into dots
- #
- # Put the verts into attribute arrays.
- #
+ if not use_materials:
+ # Put all vertices into one primitive
+ prim_indices[-1] = loop_indices
+
+ else:
+ # Bucket by material index.
+
+ tri_material_idxs = np.empty(len(blender_mesh.loop_triangles), dtype=np.uint32)
+ blender_mesh.loop_triangles.foreach_get('material_index', tri_material_idxs)
+ loop_material_idxs = np.repeat(tri_material_idxs, 3) # material index for every loop
+ unique_material_idxs = np.unique(tri_material_idxs)
+ del tri_material_idxs
+
+ for material_idx in unique_material_idxs:
+ prim_indices[material_idx] = loop_indices[loop_material_idxs == material_idx]
- result_primitives = []
+ # Create all the primitives.
- for material_idx, prim in prims.items():
- if not prim.indices:
+ primitives = []
+
+ for material_idx, dot_indices in prim_indices.items():
+ # Extract just dots used by this primitive, deduplicate them, and
+ # calculate indices into this deduplicated list.
+ prim_dots = dots[dot_indices]
+ prim_dots, indices = np.unique(prim_dots, return_inverse=True)
+
+ if len(prim_dots) == 0:
continue
- vs = []
- ns = []
- ts = []
- uvs = [[] for _ in range(tex_coord_max)]
- cols = [[] for _ in range(color_max)]
- joints = [[] for _ in range(bone_max)]
- weights = [[] for _ in range(bone_max)]
- vs_morph = [[] for _ in shape_keys]
- ns_morph = [[] for _ in shape_keys]
- ts_morph = [[] for _ in shape_keys]
-
- for vert in prim.verts.keys():
- i = 0
-
- i += 1 # skip over Blender mesh index
-
- v = vert[i]
- i += 1
- v = convert_swizzle_location(v, armature, blender_object, export_settings)
- vs.extend(v)
-
- if use_normals:
- n = vert[i]
- i += 1
- n = convert_swizzle_normal(n, armature, blender_object, export_settings)
- ns.extend(n)
-
- if use_tangents:
- t = vert[i]
- i += 1
- t = convert_swizzle_tangent(t, armature, blender_object, export_settings)
- ts.extend(t)
-
- b = vert[i]
- i += 1
- b = convert_swizzle_tangent(b, armature, blender_object, export_settings)
- b_sign = -1.0 if (Vector(n).cross(Vector(t))).dot(Vector(b)) < 0.0 else 1.0
- ts.append(b_sign)
-
- for tex_coord_index in range(0, tex_coord_max):
- uv = vert[i]
- i += 1
- uvs[tex_coord_index].extend(uv)
-
- for color_index in range(0, color_max):
- col = vert[i]
- i += 1
- cols[color_index].extend(col)
-
- if bone_max:
- bones = vert[i]
- i += 1
- for j in range(0, 4 * bone_max):
+ # Now just move all the data for prim_dots into attribute arrays
+
+ attributes = {}
+
+ blender_idxs = prim_dots['vertex_index']
+
+ attributes['POSITION'] = locs[blender_idxs]
+
+ for morph_i, vs in enumerate(morph_locs):
+ attributes['MORPH_POSITION_%d' % morph_i] = vs[blender_idxs]
+
+ if use_normals:
+ normals = np.empty((len(prim_dots), 3), dtype=np.float32)
+ normals[:, 0] = prim_dots['nx']
+ normals[:, 1] = prim_dots['ny']
+ normals[:, 2] = prim_dots['nz']
+ attributes['NORMAL'] = normals
+
+ if use_tangents:
+ tangents = np.empty((len(prim_dots), 4), dtype=np.float32)
+ tangents[:, 0] = prim_dots['tx']
+ tangents[:, 1] = prim_dots['ty']
+ tangents[:, 2] = prim_dots['tz']
+ tangents[:, 3] = prim_dots['tw']
+ attributes['TANGENT'] = tangents
+
+ if use_morph_normals:
+ for morph_i, _ in enumerate(key_blocks):
+ ns = np.empty((len(prim_dots), 3), dtype=np.float32)
+ ns[:, 0] = prim_dots['morph%dnx' % morph_i]
+ ns[:, 1] = prim_dots['morph%dny' % morph_i]
+ ns[:, 2] = prim_dots['morph%dnz' % morph_i]
+ attributes['MORPH_NORMAL_%d' % morph_i] = ns
+
+ if use_morph_tangents:
+ attributes['MORPH_TANGENT_%d' % morph_i] = __calc_morph_tangents(normals, ns, tangents)
+
+ for tex_coord_i in range(tex_coord_max):
+ uvs = np.empty((len(prim_dots), 2), dtype=np.float32)
+ uvs[:, 0] = prim_dots['uv%dx' % tex_coord_i]
+ uvs[:, 1] = prim_dots['uv%dy' % tex_coord_i]
+ attributes['TEXCOORD_%d' % tex_coord_i] = uvs
+
+ for color_i in range(color_max):
+ colors = np.empty((len(prim_dots), 4), dtype=np.float32)
+ colors[:, 0] = prim_dots['color%dr' % color_i]
+ colors[:, 1] = prim_dots['color%dg' % color_i]
+ colors[:, 2] = prim_dots['color%db' % color_i]
+ colors[:, 3] = prim_dots['color%da' % color_i]
+ attributes['COLOR_%d' % color_i] = colors
+
+ if skin:
+ joints = [[] for _ in range(num_joint_sets)]
+ weights = [[] for _ in range(num_joint_sets)]
+
+ for vi in blender_idxs:
+ bones = vert_bones[vi]
+ for j in range(0, 4 * num_joint_sets):
if j < len(bones):
joint, weight = bones[j]
else:
@@ -373,42 +273,230 @@ def extract_primitives(glTF, blender_mesh, library, blender_object, blender_vert
joints[j//4].append(joint)
weights[j//4].append(weight)
- for shape_key_index in range(0, len(shape_keys)):
- v_morph = vert[i]
- i += 1
- v_morph = convert_swizzle_location(v_morph, armature, blender_object, export_settings)
- vs_morph[shape_key_index].extend(v_morph)
-
- if use_morph_normals:
- n_morph = vert[i]
- i += 1
- n_morph = convert_swizzle_normal(n_morph, armature, blender_object, export_settings)
- ns_morph[shape_key_index].extend(n_morph)
-
- if use_morph_tangents:
- rotation = n_morph.rotation_difference(n)
- t_morph = Vector(t)
- t_morph.rotate(rotation)
- ts_morph[shape_key_index].extend(t_morph)
+ for i, (js, ws) in enumerate(zip(joints, weights)):
+ attributes['JOINTS_%d' % i] = js
+ attributes['WEIGHTS_%d' % i] = ws
- attributes = {}
- attributes['POSITION'] = vs
- if ns: attributes['NORMAL'] = ns
- if ts: attributes['TANGENT'] = ts
- for i, uv in enumerate(uvs): attributes['TEXCOORD_%d' % i] = uv
- for i, col in enumerate(cols): attributes['COLOR_%d' % i] = col
- for i, js in enumerate(joints): attributes['JOINTS_%d' % i] = js
- for i, ws in enumerate(weights): attributes['WEIGHTS_%d' % i] = ws
- for i, vm in enumerate(vs_morph): attributes['MORPH_POSITION_%d' % i] = vm
- for i, nm in enumerate(ns_morph): attributes['MORPH_NORMAL_%d' % i] = nm
- for i, tm in enumerate(ts_morph): attributes['MORPH_TANGENT_%d' % i] = tm
-
- result_primitives.append({
+ primitives.append({
'attributes': attributes,
- 'indices': prim.indices,
+ 'indices': indices,
'material': material_idx,
})
- print_console('INFO', 'Primitives created: %d' % len(result_primitives))
+ print_console('INFO', 'Primitives created: %d' % len(primitives))
+
+ return primitives
+
+
+def __get_positions(blender_mesh, key_blocks, armature, blender_object, export_settings):
+ locs = np.empty(len(blender_mesh.vertices) * 3, dtype=np.float32)
+ blender_mesh.vertices.foreach_get('co', locs)
+ locs = locs.reshape(len(blender_mesh.vertices), 3)
+
+ morph_locs = []
+ for key_block in key_blocks:
+ vs = np.empty(len(blender_mesh.vertices) * 3, dtype=np.float32)
+ key_block.data.foreach_get('co', vs)
+ vs = vs.reshape(len(blender_mesh.vertices), 3)
+ morph_locs.append(vs)
+
+ # Transform for skinning
+ if armature and blender_object:
+ apply_matrix = armature.matrix_world.inverted() @ blender_object.matrix_world
+ loc_transform = armature.matrix_world @ apply_matrix
+
+ loc_transform = blender_object.matrix_world
+ locs[:] = __apply_mat_to_all(loc_transform, locs)
+ for vs in morph_locs:
+ vs[:] = __apply_mat_to_all(loc_transform, vs)
+
+ # glTF stores deltas in morph targets
+ for vs in morph_locs:
+ vs -= locs
+
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ __zup2yup(locs)
+ for vs in morph_locs:
+ __zup2yup(vs)
+
+ return locs, morph_locs
+
+
+def __get_normals(blender_mesh, key_blocks, armature, blender_object, export_settings):
+ """Get normal for each loop."""
+ blender_mesh.calc_normals_split()
+
+ normals = np.empty(len(blender_mesh.loops) * 3, dtype=np.float32)
+ blender_mesh.loops.foreach_get('normal', normals)
+ normals = normals.reshape(len(blender_mesh.loops), 3)
+
+ morph_normals = []
+ for key_block in key_blocks:
+ ns = np.array(key_block.normals_split_get(), dtype=np.float32)
+ ns = ns.reshape(len(blender_mesh.loops), 3)
+ morph_normals.append(ns)
+
+ # Transform for skinning
+ if armature and blender_object:
+ apply_matrix = (armature.matrix_world.inverted() @ blender_object.matrix_world)
+ apply_matrix = apply_matrix.to_3x3().inverted().transposed()
+ normal_transform = armature.matrix_world.to_3x3() @ apply_matrix
+
+ normals[:] = __apply_mat_to_all(normal_transform, normals)
+ __normalize_vecs(normals)
+ for ns in morph_normals:
+ ns[:] = __apply_mat_to_all(normal_transform, ns)
+ __normalize_vecs(ns)
+
+ # glTF stores deltas in morph targets
+ for ns in morph_normals:
+ ns -= normals
+
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ __zup2yup(normals)
+ for ns in morph_normals:
+ __zup2yup(ns)
+
+ return normals, morph_normals
+
+
+def __get_tangents(blender_mesh, armature, blender_object, export_settings):
+ """Get an array of the tangent for each loop."""
+ tangents = np.empty(len(blender_mesh.loops) * 3, dtype=np.float32)
+ blender_mesh.loops.foreach_get('tangent', tangents)
+ tangents = tangents.reshape(len(blender_mesh.loops), 3)
+
+ # Transform for skinning
+ if armature and blender_object:
+ apply_matrix = armature.matrix_world.inverted() @ blender_object.matrix_world
+ tangent_transform = apply_matrix.to_quaternion().to_matrix()
+ tangents = __apply_mat_to_all(tangent_transform, tangents)
+ __normalize_vecs(tangents)
+
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ __zup2yup(tangents)
+
+ return tangents
+
+
+def __get_bitangent_signs(blender_mesh, armature, blender_object, export_settings):
+ signs = np.empty(len(blender_mesh.loops), dtype=np.float32)
+ blender_mesh.loops.foreach_get('bitangent_sign', signs)
+
+ # Transform for skinning
+ if armature and blender_object:
+ # Bitangent signs should flip when handedness changes
+ # TODO: confirm
+ apply_matrix = armature.matrix_world.inverted() @ blender_object.matrix_world
+ tangent_transform = apply_matrix.to_quaternion().to_matrix()
+ flipped = tangent_transform.determinant() < 0
+ if flipped:
+ signs *= -1
+
+ # No change for Zup -> Yup
+
+ return signs
+
+
+def __calc_morph_tangents(normals, morph_normal_deltas, tangents):
+ # TODO: check if this works
+ morph_tangent_deltas = np.empty((len(normals), 3), dtype=np.float32)
+
+ for i in range(len(normals)):
+ n = Vector(normals[i])
+ morph_n = n + Vector(morph_normal_deltas[i]) # convert back to non-delta
+ t = Vector(tangents[i, :3])
+
+ rotation = morph_n.rotation_difference(n)
+
+ t_morph = Vector(t)
+ t_morph.rotate(rotation)
+ morph_tangent_deltas[i] = t_morph - t # back to delta
+
+ return morph_tangent_deltas
+
+
+def __get_uvs(blender_mesh, uv_i):
+ layer = blender_mesh.uv_layers[uv_i]
+ uvs = np.empty(len(blender_mesh.loops) * 2, dtype=np.float32)
+ layer.data.foreach_get('uv', uvs)
+ uvs = uvs.reshape(len(blender_mesh.loops), 2)
+
+ # Blender UV space -> glTF UV space
+ # u,v -> u,1-v
+ uvs[:, 1] *= -1
+ uvs[:, 1] += 1
+
+ return uvs
+
+
+def __get_colors(blender_mesh, color_i):
+ layer = blender_mesh.vertex_colors[color_i]
+ colors = np.empty(len(blender_mesh.loops) * 4, dtype=np.float32)
+ layer.data.foreach_get('color', colors)
+ colors = colors.reshape(len(blender_mesh.loops), 4)
+
+ # sRGB -> Linear
+ rgb = colors[:, :-1]
+ not_small = rgb >= 0.04045
+ small_result = np.where(rgb < 0.0, 0.0, rgb * (1.0 / 12.92))
+ large_result = np.power((rgb + 0.055) * (1.0 / 1.055), 2.4, where=not_small)
+ rgb[:] = np.where(not_small, large_result, small_result)
+
+ return colors
+
+
+def __get_bone_data(blender_mesh, skin, blender_vertex_groups):
+ joint_name_to_index = {joint.name: index for index, joint in enumerate(skin.joints)}
+ group_to_joint = [joint_name_to_index.get(g.name) for g in blender_vertex_groups]
+
+ # List of (joint, weight) pairs for each vert
+ vert_bones = []
+ max_num_influences = 0
+
+ for vertex in blender_mesh.vertices:
+ bones = []
+ if vertex.groups:
+ for group_element in vertex.groups:
+ weight = group_element.weight
+ if weight <= 0.0:
+ continue
+ try:
+ joint = group_to_joint[group_element.group]
+ except Exception:
+ continue
+ if joint is None:
+ continue
+ bones.append((joint, weight))
+ bones.sort(key=lambda x: x[1], reverse=True)
+ if not bones: bones = ((0, 1.0),) # HACK for verts with zero weight (#308)
+ vert_bones.append(bones)
+ if len(bones) > max_num_influences:
+ max_num_influences = len(bones)
+
+ # How many joint sets do we need? 1 set = 4 influences
+ num_joint_sets = (max_num_influences + 3) // 4
+
+ return vert_bones, num_joint_sets
+
+
+def __zup2yup(array):
+ # x,y,z -> x,z,-y
+ array[:, [1,2]] = array[:, [2,1]] # x,z,y
+ array[:, 2] *= -1 # x,z,-y
+
+
+def __apply_mat_to_all(matrix, vectors):
+ """Given matrix m and vectors [v1,v2,...], computes [m@v1,m@v2,...]"""
+ # Linear part
+ m = matrix.to_3x3() if len(matrix) == 4 else matrix
+ res = np.matmul(vectors, np.array(m.transposed()))
+ # Translation part
+ if len(matrix) == 4:
+ res += np.array(matrix.translation)
+ return res
+
- return result_primitives
+def __normalize_vecs(vectors):
+ norms = np.linalg.norm(vectors, axis=1, keepdims=True)
+ np.divide(vectors, norms, out=vectors, where=norms != 0)
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py
index b09e7aa1..1a1abcd0 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py
@@ -14,7 +14,7 @@
import math
import bpy
-from mathutils import Matrix, Quaternion
+from mathutils import Matrix, Quaternion, Vector
from . import gltf2_blender_export_keys
from io_scene_gltf2.blender.com import gltf2_blender_math
@@ -23,7 +23,6 @@ from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins
from io_scene_gltf2.blender.exp import gltf2_blender_gather_cameras
from io_scene_gltf2.blender.exp import gltf2_blender_gather_mesh
from io_scene_gltf2.blender.exp import gltf2_blender_gather_joints
-from io_scene_gltf2.blender.exp import gltf2_blender_extract
from io_scene_gltf2.blender.exp import gltf2_blender_gather_lights
from ..com.gltf2_blender_extras import generate_extras
from io_scene_gltf2.io.com import gltf2_io
@@ -418,13 +417,13 @@ def __gather_trans_rot_scale(blender_object, export_settings):
# make sure the rotation is normalized
rot.normalize()
- trans = gltf2_blender_extract.convert_swizzle_location(trans, None, None, export_settings)
- rot = gltf2_blender_extract.convert_swizzle_rotation(rot, export_settings)
- sca = gltf2_blender_extract.convert_swizzle_scale(sca, export_settings)
+ trans = __convert_swizzle_location(trans, export_settings)
+ rot = __convert_swizzle_rotation(rot, export_settings)
+ sca = __convert_swizzle_scale(sca, export_settings)
if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection:
- trans -= gltf2_blender_extract.convert_swizzle_location(
- blender_object.instance_collection.instance_offset, None, None, export_settings)
+ trans -= __convert_swizzle_location(
+ blender_object.instance_collection.instance_offset, export_settings)
translation, rotation, scale = (None, None, None)
trans[0], trans[1], trans[2] = gltf2_blender_math.round_if_near(trans[0], 0.0), gltf2_blender_math.round_if_near(trans[1], 0.0), \
gltf2_blender_math.round_if_near(trans[2], 0.0)
@@ -476,7 +475,7 @@ def __gather_weights(blender_object, export_settings):
def __get_correction_node(blender_object, export_settings):
- correction_quaternion = gltf2_blender_extract.convert_swizzle_rotation(
+ correction_quaternion = __convert_swizzle_rotation(
Quaternion((1.0, 0.0, 0.0), math.radians(-90.0)), export_settings)
correction_quaternion = [correction_quaternion[1], correction_quaternion[2],
correction_quaternion[3], correction_quaternion[0]]
@@ -494,3 +493,31 @@ def __get_correction_node(blender_object, export_settings):
translation=None,
weights=None
)
+
+
+def __convert_swizzle_location(loc, export_settings):
+ """Convert a location from Blender coordinate system to glTF coordinate system."""
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ return Vector((loc[0], loc[2], -loc[1]))
+ else:
+ return Vector((loc[0], loc[1], loc[2]))
+
+
+def __convert_swizzle_rotation(rot, export_settings):
+ """
+ Convert a quaternion rotation from Blender coordinate system to glTF coordinate system.
+
+ 'w' is still at first position.
+ """
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ return Quaternion((rot[0], rot[1], rot[3], -rot[2]))
+ else:
+ return Quaternion((rot[0], rot[1], rot[2], rot[3]))
+
+
+def __convert_swizzle_scale(scale, export_settings):
+ """Convert a scale from Blender coordinate system to glTF coordinate system."""
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ return Vector((scale[0], scale[2], scale[1]))
+ else:
+ return Vector((scale[0], scale[1], scale[2]))
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py
index 61adea89..e6a5881f 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py
@@ -85,9 +85,9 @@ def __gather_position(blender_primitive, export_settings):
def __gather_normal(blender_primitive, export_settings):
if not export_settings[gltf2_blender_export_keys.NORMALS]:
return {}
- normal = blender_primitive["attributes"].get('NORMAL')
- if not normal:
+ if 'NORMAL' not in blender_primitive["attributes"]:
return {}
+ normal = blender_primitive["attributes"]['NORMAL']
return {
"NORMAL": array_to_accessor(
normal,
@@ -100,9 +100,9 @@ def __gather_normal(blender_primitive, export_settings):
def __gather_tangent(blender_primitive, export_settings):
if not export_settings[gltf2_blender_export_keys.TANGENTS]:
return {}
- tangent = blender_primitive["attributes"].get('TANGENT')
- if not tangent:
+ if 'TANGENT' not in blender_primitive["attributes"]:
return {}
+ tangent = blender_primitive["attributes"]['TANGENT']
return {
"TANGENT": array_to_accessor(
tangent,
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
index 1a2ae00d..4fe498e1 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
@@ -14,6 +14,7 @@
import bpy
from typing import List, Optional, Tuple
+import numpy as np
from .gltf2_blender_export_keys import NORMALS, MORPH_NORMAL, TANGENTS, MORPH_TANGENT, MORPH
@@ -114,21 +115,23 @@ def __gather_indices(blender_primitive, blender_mesh, modifiers, export_settings
# https://github.com/KhronosGroup/glTF/pull/1476/files
# Also, UINT8 mode is not supported:
# https://github.com/KhronosGroup/glTF/issues/1471
- max_index = max(indices)
+ max_index = indices.max()
if max_index < 65535:
component_type = gltf2_io_constants.ComponentType.UnsignedShort
+ indices = indices.astype(np.uint16, copy=False)
elif max_index < 4294967295:
component_type = gltf2_io_constants.ComponentType.UnsignedInt
+ indices = indices.astype(np.uint32, copy=False)
else:
print_console('ERROR', 'A mesh contains too many vertices (' + str(max_index) + ') and needs to be split before export.')
return None
element_type = gltf2_io_constants.DataType.Scalar
- binary_data = gltf2_io_binary_data.BinaryData.from_list(indices, component_type)
+ binary_data = gltf2_io_binary_data.BinaryData(indices.tobytes())
return gltf2_blender_gather_accessors.gather_accessor(
binary_data,
component_type,
- len(indices) // gltf2_io_constants.DataType.num_elements(element_type),
+ len(indices),
None,
None,
element_type,
@@ -156,7 +159,7 @@ def __gather_targets(blender_primitive, blender_mesh, modifiers, export_settings
target_normal_id = 'MORPH_NORMAL_' + str(morph_index)
target_tangent_id = 'MORPH_TANGENT_' + str(morph_index)
- if blender_primitive["attributes"].get(target_position_id):
+ if blender_primitive["attributes"].get(target_position_id) is not None:
target = {}
internal_target_position = blender_primitive["attributes"][target_position_id]
target["POSITION"] = gltf2_blender_gather_primitive_attributes.array_to_accessor(
@@ -168,7 +171,7 @@ def __gather_targets(blender_primitive, blender_mesh, modifiers, export_settings
if export_settings[NORMALS] \
and export_settings[MORPH_NORMAL] \
- and blender_primitive["attributes"].get(target_normal_id):
+ and blender_primitive["attributes"].get(target_normal_id) is not None:
internal_target_normal = blender_primitive["attributes"][target_normal_id]
target['NORMAL'] = gltf2_blender_gather_primitive_attributes.array_to_accessor(
@@ -179,7 +182,7 @@ def __gather_targets(blender_primitive, blender_mesh, modifiers, export_settings
if export_settings[TANGENTS] \
and export_settings[MORPH_TANGENT] \
- and blender_primitive["attributes"].get(target_tangent_id):
+ and blender_primitive["attributes"].get(target_tangent_id) is not None:
internal_target_tangent = blender_primitive["attributes"][target_tangent_id]
target['TANGENT'] = gltf2_blender_gather_primitive_attributes.array_to_accessor(
internal_target_tangent,