From 51e15a9db4ce463e01c291f6ec9152efe629bab5 Mon Sep 17 00:00:00 2001 From: Julien Duroure Date: Sun, 25 Sep 2022 17:08:07 +0200 Subject: glTF exporter: Big gltf primitive extraction refactoring + Blender attributes export --- io_scene_gltf2/__init__.py | 10 +- .../blender/com/gltf2_blender_conversion.py | 54 ++ .../blender/exp/gltf2_blender_extract.py | 619 --------------- .../gltf2_blender_gather_primitive_attributes.py | 186 ++--- .../blender/exp/gltf2_blender_gather_primitives.py | 10 +- .../exp/gltf2_blender_gather_primitives_extract.py | 863 +++++++++++++++++++++ .../blender/exp/gltf2_blender_gather_tree.py | 1 - 7 files changed, 997 insertions(+), 746 deletions(-) delete mode 100755 io_scene_gltf2/blender/exp/gltf2_blender_extract.py create mode 100644 io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives_extract.py diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index 4d8acd68..610c061a 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -4,7 +4,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": (3, 4, 22), + "version": (3, 4, 23), 'blender': (3, 3, 0), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', @@ -273,6 +273,12 @@ class ExportGLTF2_Base: default=True ) + export_attributes: BoolProperty( + name='Attributes', + description='Export Attributes', + default=False + ) + use_mesh_edges: BoolProperty( name='Loose Edges', description=( @@ -579,6 +585,7 @@ class ExportGLTF2_Base: export_settings['gltf_materials'] = self.export_materials export_settings['gltf_colors'] = self.export_colors + export_settings['gltf_attributes'] = self.export_attributes export_settings['gltf_cameras'] = self.export_cameras export_settings['gltf_original_specular'] = self.export_original_specular @@ -808,6 +815,7 @@ class GLTF_PT_export_geometry_mesh(bpy.types.Panel): col.active = operator.export_normals col.prop(operator, 'export_tangents') layout.prop(operator, 'export_colors') + layout.prop(operator, 'export_attributes') col = layout.column() col.prop(operator, 'use_mesh_edges') diff --git a/io_scene_gltf2/blender/com/gltf2_blender_conversion.py b/io_scene_gltf2/blender/com/gltf2_blender_conversion.py index ecb91c8f..85ab654a 100755 --- a/io_scene_gltf2/blender/com/gltf2_blender_conversion.py +++ b/io_scene_gltf2/blender/com/gltf2_blender_conversion.py @@ -2,6 +2,8 @@ # Copyright 2018-2021 The glTF-Blender-IO authors. from math import sin, cos +import numpy as np +from io_scene_gltf2.io.com import gltf2_io_constants def texture_transform_blender_to_gltf(mapping_transform): """ @@ -48,3 +50,55 @@ def get_target(property): "scale": "scale", "value": "weights" }.get(property) + +def get_component_type(attribute_component_type): + return { + "INT8": gltf2_io_constants.ComponentType.Float, + "BYTE_COLOR": gltf2_io_constants.ComponentType.UnsignedShort, + "FLOAT2": gltf2_io_constants.ComponentType.Float, + "FLOAT_COLOR": gltf2_io_constants.ComponentType.Float, + "FLOAT_VECTOR": gltf2_io_constants.ComponentType.Float, + "FLOAT_VECTOR_4": gltf2_io_constants.ComponentType.Float, + "INT": gltf2_io_constants.ComponentType.Float, # No signed Int in glTF accessor + "FLOAT": gltf2_io_constants.ComponentType.Float, + "BOOLEAN": gltf2_io_constants.ComponentType.Float + }.get(attribute_component_type) + +def get_data_type(attribute_component_type): + return { + "INT8": gltf2_io_constants.DataType.Scalar, + "BYTE_COLOR": gltf2_io_constants.DataType.Vec4, + "FLOAT2": gltf2_io_constants.DataType.Vec2, + "FLOAT_COLOR": gltf2_io_constants.DataType.Vec4, + "FLOAT_VECTOR": gltf2_io_constants.DataType.Vec3, + "FLOAT_VECTOR_4": gltf2_io_constants.DataType.Vec4, + "INT": gltf2_io_constants.DataType.Scalar, + "FLOAT": gltf2_io_constants.DataType.Scalar, + "BOOLEAN": gltf2_io_constants.DataType.Scalar, + }.get(attribute_component_type) + +def get_data_length(attribute_component_type): + return { + "INT8": 1, + "BYTE_COLOR": 4, + "FLOAT2": 2, + "FLOAT_COLOR": 4, + "FLOAT_VECTOR": 3, + "FLOAT_VECTOR_4": 4, + "INT": 1, + "FLOAT": 1, + "BOOLEAN": 1 + }.get(attribute_component_type) + +def get_numpy_type(attribute_component_type): + return { + "INT8": np.float32, + "BYTE_COLOR": np.float32, + "FLOAT2": np.float32, + "FLOAT_COLOR": np.float32, + "FLOAT_VECTOR": np.float32, + "FLOAT_VECTOR_4": np.float32, + "INT": np.float32, #signed integer are not supported by glTF + "FLOAT": np.float32, + "BOOLEAN": np.float32 + }.get(attribute_component_type) diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py deleted file mode 100755 index bdea2c6f..00000000 --- a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py +++ /dev/null @@ -1,619 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright 2018-2021 The glTF-Blender-IO authors. - -import numpy as np -from mathutils import Vector - -from . import gltf2_blender_export_keys -from ...io.com.gltf2_io_debug import print_console -from io_scene_gltf2.blender.exp import gltf2_blender_gather_nodes - - -def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups, modifiers, export_settings): - """Extract primitives from a mesh.""" - print_console('INFO', 'Extracting primitive: ' + blender_mesh.name) - - blender_object = None - if uuid_for_skined_data: - blender_object = export_settings['vtree'].nodes[uuid_for_skined_data].blender_object - - use_normals = export_settings[gltf2_blender_export_keys.NORMALS] - if use_normals: - blender_mesh.calc_normals_split() - - use_tangents = False - if use_normals and export_settings[gltf2_blender_export_keys.TANGENTS]: - if blender_mesh.uv_layers.active and len(blender_mesh.uv_layers) > 0: - try: - blender_mesh.calc_tangents() - use_tangents = True - except Exception: - print_console('WARNING', 'Could not calculate tangents. Please try to triangulate the mesh first.') - - tex_coord_max = 0 - if export_settings[gltf2_blender_export_keys.TEX_COORDS]: - if blender_mesh.uv_layers.active: - tex_coord_max = len(blender_mesh.uv_layers) - - color_max = 0 - if export_settings[gltf2_blender_export_keys.COLORS]: - color_max = len(blender_mesh.vertex_colors) - - colors_attributes = [] - rendered_color_idx = blender_mesh.attributes.render_color_index - - if color_max > 0: - colors_attributes.append(rendered_color_idx) - # Then find other ones - colors_attributes.extend([ - i for i in range(len(blender_mesh.color_attributes)) if i != rendered_color_idx \ - and blender_mesh.vertex_colors.find(blender_mesh.color_attributes[i].name) != -1 - ]) - - - 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} - if "ARMATURE" in modifiers_dict: - modifier = modifiers_dict["ARMATURE"] - armature = modifier.object - - # Skin must be ignored if the object is parented to a bone of the armature - # (This creates an infinite recursive error) - # So ignoring skin in that case - is_child_of_arma = ( - armature and - blender_object and - blender_object.parent_type == "BONE" and - blender_object.parent.name == armature.name - ) - if is_child_of_arma: - armature = None - - if armature: - skin = gltf2_blender_gather_nodes.gather_skin(uuid_for_skined_data, export_settings) - if not skin: - armature = None - - 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] - - key_blocks = [] - # Shape Keys can't be retrieve when using Apply Modifiers (Blender/bpy limitation) - if export_settings[gltf2_blender_export_keys.APPLY] is False and blender_mesh.shape_keys and export_settings[gltf2_blender_export_keys.MORPH]: - 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) - ] - - use_materials = export_settings[gltf2_blender_export_keys.MATERIALS] - - # Fetch vert positions and bone data (joint,weights) - - locs, morph_locs = __get_positions(blender_mesh, key_blocks, armature, blender_object, export_settings) - if skin: - vert_bones, num_joint_sets, need_neutral_bone = __get_bone_data(blender_mesh, skin, blender_vertex_groups) - if need_neutral_bone is True: - # Need to create a fake joint at root of armature - # In order to assign not assigned vertices to it - # But for now, this is not yet possible, we need to wait the armature node is created - # Just store this, to be used later - armature_uuid = export_settings['vtree'].nodes[uuid_for_skined_data].armature - export_settings['vtree'].nodes[armature_uuid].need_neutral_bone = True - - # 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. - # - # 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. - - # 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 enumerate(colors_attributes): - 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 - - colors_types = [] - for col_i, blender_col_i in enumerate(colors_attributes): - colors, colors_type, domain = __get_colors(blender_mesh, col_i, blender_col_i) - if domain == "POINT": - colors = colors[dots['vertex_index']] - colors_types.append(colors_type) - 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) - - prim_indices = {} # maps material index to TRIANGLES-style indices into dots - - if use_materials == "NONE": # Only for None. For placeholder and export, keep primitives - # 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] - - # Create all the primitives. - - 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 - - # 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 enumerate(colors_attributes): - 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] = {} - attributes['COLOR_%d' % color_i]["data"] = colors - - attributes['COLOR_%d' % color_i]["norm"] = colors_types[color_i] == "BYTE_COLOR" - - 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: - joint, weight = 0, 0.0 - joints[j//4].append(joint) - weights[j//4].append(weight) - - for i, (js, ws) in enumerate(zip(joints, weights)): - attributes['JOINTS_%d' % i] = js - attributes['WEIGHTS_%d' % i] = ws - - primitives.append({ - 'attributes': attributes, - 'indices': indices, - 'material': material_idx, - }) - - if export_settings['gltf_loose_edges']: - # Find loose edges - loose_edges = [e for e in blender_mesh.edges if e.is_loose] - blender_idxs = [vi for e in loose_edges for vi in e.vertices] - - if blender_idxs: - # Export one glTF vert per unique Blender vert in a loose edge - blender_idxs = np.array(blender_idxs, dtype=np.uint32) - blender_idxs, indices = np.unique(blender_idxs, return_inverse=True) - - attributes = {} - - attributes['POSITION'] = locs[blender_idxs] - - for morph_i, vs in enumerate(morph_locs): - attributes['MORPH_POSITION_%d' % morph_i] = vs[blender_idxs] - - 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: - joint, weight = 0, 0.0 - joints[j//4].append(joint) - weights[j//4].append(weight) - - for i, (js, ws) in enumerate(zip(joints, weights)): - attributes['JOINTS_%d' % i] = js - attributes['WEIGHTS_%d' % i] = ws - - primitives.append({ - 'attributes': attributes, - 'indices': indices, - 'mode': 1, # LINES - 'material': 0, - }) - - if export_settings['gltf_loose_points']: - # Find loose points - verts_in_edge = set(vi for e in blender_mesh.edges for vi in e.vertices) - blender_idxs = [ - vi for vi, _ in enumerate(blender_mesh.vertices) - if vi not in verts_in_edge - ] - - if blender_idxs: - blender_idxs = np.array(blender_idxs, dtype=np.uint32) - - attributes = {} - - attributes['POSITION'] = locs[blender_idxs] - - for morph_i, vs in enumerate(morph_locs): - attributes['MORPH_POSITION_%d' % morph_i] = vs[blender_idxs] - - 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: - joint, weight = 0, 0.0 - joints[j//4].append(joint) - weights[j//4].append(weight) - - for i, (js, ws) in enumerate(zip(joints, weights)): - attributes['JOINTS_%d' % i] = js - attributes['WEIGHTS_%d' % i] = ws - - primitives.append({ - 'attributes': attributes, - 'mode': 0, # POINTS - 'material': 0, - }) - - 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) - source = key_blocks[0].relative_key.data if key_blocks else blender_mesh.vertices - source.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_safe() @ 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.""" - if key_blocks: - normals = key_blocks[0].relative_key.normals_split_get() - normals = np.array(normals, dtype=np.float32) - else: - normals = np.empty(len(blender_mesh.loops) * 3, dtype=np.float32) - blender_mesh.calc_normals_split() - 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_safe() @ blender_object.matrix_world) - apply_matrix = apply_matrix.to_3x3().inverted_safe().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) - - for ns in [normals, *morph_normals]: - # Replace zero normals with the unit UP vector. - # Seems to happen sometimes with degenerate tris? - is_zero = ~ns.any(axis=1) - ns[is_zero, 2] = 1 - - # 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_safe() @ 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_safe() @ 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, blender_color_i): - if blender_mesh.color_attributes[blender_color_i].domain == "POINT": - colors = np.empty(len(blender_mesh.vertices) * 4, dtype=np.float32) #POINT - else: - colors = np.empty(len(blender_mesh.loops) * 4, dtype=np.float32) #CORNER - blender_mesh.color_attributes[blender_color_i].data.foreach_get('color', colors) - colors = colors.reshape(-1, 4) - # colors are already linear, no need to switch color space - return colors, blender_mesh.color_attributes[blender_color_i].data_type, blender_mesh.color_attributes[blender_color_i].domain - - -def __get_bone_data(blender_mesh, skin, blender_vertex_groups): - - need_neutral_bone = False - min_influence = 0.0001 - - 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 <= min_influence: - 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: - # Is not assign to any bone - bones = ((len(skin.joints), 1.0),) # Assign to a joint that will be created later - need_neutral_bone = True - 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, need_neutral_bone - - -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 - - -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_primitive_attributes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py index 1af588b9..ce2f9a59 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 @@ -10,32 +10,34 @@ from io_scene_gltf2.io.com import gltf2_io_debug from io_scene_gltf2.io.exp import gltf2_io_binary_data + def gather_primitive_attributes(blender_primitive, export_settings): """ - Gathers the attributes, such as POSITION, NORMAL, TANGENT from a blender primitive. + Gathers the attributes, such as POSITION, NORMAL, TANGENT, and all custom attributes from a blender primitive :return: a dictionary of attributes """ attributes = {} - attributes.update(__gather_position(blender_primitive, export_settings)) - attributes.update(__gather_normal(blender_primitive, export_settings)) - attributes.update(__gather_tangent(blender_primitive, export_settings)) - attributes.update(__gather_texcoord(blender_primitive, export_settings)) - attributes.update(__gather_colors(blender_primitive, export_settings)) - attributes.update(__gather_skins(blender_primitive, export_settings)) - return attributes + # loop on each attribute extracted + # for skinning, all linked attributes (WEIGHTS_ and JOINTS_) need to be calculated + # in one shot (because of normalization), so we need to check that it is called only once. -def array_to_accessor(array, component_type, data_type, include_max_and_min=False): - dtype = gltf2_io_constants.ComponentType.to_numpy_dtype(component_type) - num_elems = gltf2_io_constants.DataType.num_elements(data_type) + skin_done = False + + for attribute in blender_primitive["attributes"]: + if (attribute.startswith("JOINTS_") or attribute.startswith("WEIGHTS_")) and skin_done is True: + continue + if attribute.startswith("MORPH_"): + continue # Target for morphs will be managed later + attributes.update(__gather_attribute(blender_primitive, attribute, export_settings)) + if (attribute.startswith("JOINTS_") or attribute.startswith("WEIGHTS_")): + skin_done = True + + return attributes - if type(array) is not np.ndarray: - array = np.array(array, dtype=dtype) - array = array.reshape(len(array) // num_elems, num_elems) - assert array.dtype == dtype - assert array.shape[1] == num_elems +def array_to_accessor(array, component_type, data_type, include_max_and_min=False): amax = None amin = None @@ -58,109 +60,6 @@ def array_to_accessor(array, component_type, data_type, include_max_and_min=Fals type=data_type, ) - -def __gather_position(blender_primitive, export_settings): - position = blender_primitive["attributes"]["POSITION"] - return { - "POSITION": array_to_accessor( - position, - component_type=gltf2_io_constants.ComponentType.Float, - data_type=gltf2_io_constants.DataType.Vec3, - include_max_and_min=True - ) - } - - -def __gather_normal(blender_primitive, export_settings): - if not export_settings[gltf2_blender_export_keys.NORMALS]: - return {} - if 'NORMAL' not in blender_primitive["attributes"]: - return {} - normal = blender_primitive["attributes"]['NORMAL'] - return { - "NORMAL": array_to_accessor( - normal, - component_type=gltf2_io_constants.ComponentType.Float, - data_type=gltf2_io_constants.DataType.Vec3, - ) - } - - -def __gather_tangent(blender_primitive, export_settings): - if not export_settings[gltf2_blender_export_keys.TANGENTS]: - return {} - if 'TANGENT' not in blender_primitive["attributes"]: - return {} - tangent = blender_primitive["attributes"]['TANGENT'] - return { - "TANGENT": array_to_accessor( - tangent, - component_type=gltf2_io_constants.ComponentType.Float, - data_type=gltf2_io_constants.DataType.Vec4, - ) - } - - -def __gather_texcoord(blender_primitive, export_settings): - attributes = {} - if export_settings[gltf2_blender_export_keys.TEX_COORDS]: - tex_coord_index = 0 - tex_coord_id = 'TEXCOORD_' + str(tex_coord_index) - while blender_primitive["attributes"].get(tex_coord_id) is not None: - tex_coord = blender_primitive["attributes"][tex_coord_id] - attributes[tex_coord_id] = array_to_accessor( - tex_coord, - component_type=gltf2_io_constants.ComponentType.Float, - data_type=gltf2_io_constants.DataType.Vec2, - ) - tex_coord_index += 1 - tex_coord_id = 'TEXCOORD_' + str(tex_coord_index) - return attributes - - -def __gather_colors(blender_primitive, export_settings): - attributes = {} - if export_settings[gltf2_blender_export_keys.COLORS]: - color_index = 0 - color_id = 'COLOR_' + str(color_index) - while blender_primitive["attributes"].get(color_id) is not None: - colors = blender_primitive["attributes"][color_id]["data"] - - if type(colors) is not np.ndarray: - colors = np.array(colors, dtype=np.float32) - colors = colors.reshape(len(colors) // 4, 4) - - if blender_primitive["attributes"][color_id]["norm"] is True: - comp_type = gltf2_io_constants.ComponentType.UnsignedShort - - # Convert to normalized ushorts - colors *= 65535 - colors += 0.5 # bias for rounding - colors = colors.astype(np.uint16) - - else: - comp_type = gltf2_io_constants.ComponentType.Float - - attributes[color_id] = gltf2_io.Accessor( - buffer_view=gltf2_io_binary_data.BinaryData(colors.tobytes(), gltf2_io_constants.BufferViewTarget.ARRAY_BUFFER), - byte_offset=None, - component_type=comp_type, - count=len(colors), - extensions=None, - extras=None, - max=None, - min=None, - name=None, - normalized=blender_primitive["attributes"][color_id]["norm"], - sparse=None, - type=gltf2_io_constants.DataType.Vec4, - ) - - color_index += 1 - color_id = 'COLOR_' + str(color_index) - return attributes - - def __gather_skins(blender_primitive, export_settings): attributes = {} @@ -208,8 +107,10 @@ def __gather_skins(blender_primitive, export_settings): component_type = gltf2_io_constants.ComponentType.UnsignedShort if max(internal_joint) < 256: component_type = gltf2_io_constants.ComponentType.UnsignedByte + joints = np.array(internal_joint, dtype= gltf2_io_constants.ComponentType.to_numpy_dtype(component_type)) + joints = joints.reshape(-1, 4) joint = array_to_accessor( - internal_joint, + joints, component_type, data_type=gltf2_io_constants.DataType.Vec4, ) @@ -236,3 +137,48 @@ def __gather_skins(blender_primitive, export_settings): attributes[weight_id] = weight return attributes + + +def __gather_attribute(blender_primitive, attribute, export_settings): + data = blender_primitive["attributes"][attribute] + + + include_max_and_mins = { + "POSITION": True + } + + if (attribute.startswith("_COLOR") or attribute.startswith("COLOR_")) and blender_primitive["attributes"][attribute]['component_type'] == gltf2_io_constants.ComponentType.UnsignedShort: + # Byte Color vertex color, need to normalize + + data['data'] *= 65535 + data['data'] += 0.5 # bias for rounding + data['data'] = data['data'].astype(np.uint16) + + return { attribute : gltf2_io.Accessor( + buffer_view=gltf2_io_binary_data.BinaryData(data['data'].tobytes(), gltf2_io_constants.BufferViewTarget.ARRAY_BUFFER), + byte_offset=None, + component_type=data['component_type'], + count=len(data['data']), + extensions=None, + extras=None, + max=None, + min=None, + name=None, + normalized=True, + sparse=None, + type=data['data_type'], + ) + } + + elif attribute.startswith("JOINTS_") or attribute.startswith("WEIGHTS_"): + return __gather_skins(blender_primitive, export_settings) + + else: + return { + attribute: array_to_accessor( + data['data'], + component_type=data['component_type'], + data_type=data['data_type'], + include_max_and_min=include_max_and_mins.get(attribute, False) + ) + } 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 576a1418..d7784c3e 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py @@ -8,7 +8,7 @@ import numpy as np from .gltf2_blender_export_keys import NORMALS, MORPH_NORMAL, TANGENTS, MORPH_TANGENT, MORPH from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached, cached_by_key -from io_scene_gltf2.blender.exp import gltf2_blender_extract +from io_scene_gltf2.blender.exp import gltf2_blender_gather_primitives_extract from io_scene_gltf2.blender.exp import gltf2_blender_gather_accessors from io_scene_gltf2.blender.exp import gltf2_blender_gather_primitive_attributes from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials @@ -112,7 +112,7 @@ def __gather_cache_primitives( """ primitives = [] - blender_primitives = gltf2_blender_extract.extract_primitives( + blender_primitives = gltf2_blender_gather_primitives_extract.extract_primitives( blender_mesh, uuid_for_skined_data, vertex_groups, modifiers, export_settings) for internal_primitive in blender_primitives: @@ -184,7 +184,7 @@ def __gather_targets(blender_primitive, blender_mesh, modifiers, export_settings if blender_primitive["attributes"].get(target_position_id) is not None: target = {} - internal_target_position = blender_primitive["attributes"][target_position_id] + internal_target_position = blender_primitive["attributes"][target_position_id]["data"] target["POSITION"] = gltf2_blender_gather_primitive_attributes.array_to_accessor( internal_target_position, component_type=gltf2_io_constants.ComponentType.Float, @@ -196,7 +196,7 @@ def __gather_targets(blender_primitive, blender_mesh, modifiers, export_settings and export_settings[MORPH_NORMAL] \ and blender_primitive["attributes"].get(target_normal_id) is not None: - internal_target_normal = blender_primitive["attributes"][target_normal_id] + internal_target_normal = blender_primitive["attributes"][target_normal_id]["data"] target['NORMAL'] = gltf2_blender_gather_primitive_attributes.array_to_accessor( internal_target_normal, component_type=gltf2_io_constants.ComponentType.Float, @@ -206,7 +206,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) is not None: - internal_target_tangent = blender_primitive["attributes"][target_tangent_id] + internal_target_tangent = blender_primitive["attributes"][target_tangent_id]["data"] target['TANGENT'] = gltf2_blender_gather_primitive_attributes.array_to_accessor( internal_target_tangent, component_type=gltf2_io_constants.ComponentType.Float, diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives_extract.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives_extract.py new file mode 100644 index 00000000..74682ce7 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives_extract.py @@ -0,0 +1,863 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2018-2021 The glTF-Blender-IO authors. + +import numpy as np +from mathutils import Vector + +from . import gltf2_blender_export_keys +from ...io.com.gltf2_io_debug import print_console +from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins +from io_scene_gltf2.io.com import gltf2_io_constants +from io_scene_gltf2.blender.com import gltf2_blender_conversion + + +def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups, modifiers, export_settings): + """Extract primitives from a mesh.""" + print_console('INFO', 'Extracting primitive: ' + blender_mesh.name) + + primitive_creator = PrimitiveCreator(blender_mesh, uuid_for_skined_data, blender_vertex_groups, modifiers, export_settings) + primitive_creator.prepare_data() + primitive_creator.define_attributes() + primitive_creator.create_dots_data_structure() + primitive_creator.populate_dots_data() + primitive_creator.primitive_split() + return primitive_creator.primitive_creation() + +class PrimitiveCreator: + def __init__(self, blender_mesh, uuid_for_skined_data, blender_vertex_groups, modifiers, export_settings): + self.blender_mesh = blender_mesh + self.uuid_for_skined_data = uuid_for_skined_data + self.blender_vertex_groups = blender_vertex_groups + self.modifiers = modifiers + self.export_settings = export_settings + + @classmethod + def apply_mat_to_all(cls, 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 + + @classmethod + def normalize_vecs(cls, vectors): + norms = np.linalg.norm(vectors, axis=1, keepdims=True) + np.divide(vectors, norms, out=vectors, where=norms != 0) + + @classmethod + def zup2yup(cls, array): + # x,y,z -> x,z,-y + array[:, [1,2]] = array[:, [2,1]] # x,z,y + array[:, 2] *= -1 # x,z,-y + + def prepare_data(self): + self.blender_object = None + if self.uuid_for_skined_data: + self.blender_object = self.export_settings['vtree'].nodes[self.uuid_for_skined_data].blender_object + + self.use_normals = self.export_settings[gltf2_blender_export_keys.NORMALS] + if self.use_normals: + self.blender_mesh.calc_normals_split() + + self.use_tangents = False + if self.use_normals and self.export_settings[gltf2_blender_export_keys.TANGENTS]: + if self.blender_mesh.uv_layers.active and len(self.blender_mesh.uv_layers) > 0: + try: + self.blender_mesh.calc_tangents() + self.use_tangents = True + except Exception: + print_console('WARNING', 'Could not calculate tangents. Please try to triangulate the mesh first.') + + self.tex_coord_max = 0 + if self.export_settings[gltf2_blender_export_keys.TEX_COORDS]: + if self.blender_mesh.uv_layers.active: + self.tex_coord_max = len(self.blender_mesh.uv_layers) + + self.use_morph_normals = self.use_normals and self.export_settings[gltf2_blender_export_keys.MORPH_NORMAL] + self.use_morph_tangents = self.use_morph_normals and self.use_tangents and self.export_settings[gltf2_blender_export_keys.MORPH_TANGENT] + + self.use_materials = self.export_settings[gltf2_blender_export_keys.MATERIALS] + + self.blender_attributes = [] + + # Check if we have to export skin + self.armature = None + self.skin = None + if self.blender_vertex_groups and self.export_settings[gltf2_blender_export_keys.SKINS]: + if self.modifiers is not None: + modifiers_dict = {m.type: m for m in self.modifiers} + if "ARMATURE" in modifiers_dict: + modifier = modifiers_dict["ARMATURE"] + self.armature = modifier.object + + # Skin must be ignored if the object is parented to a bone of the armature + # (This creates an infinite recursive error) + # So ignoring skin in that case + is_child_of_arma = ( + self.armature and + self.blender_object and + self.blender_object.parent_type == "BONE" and + self.blender_object.parent.name == self.armature.name + ) + if is_child_of_arma: + self.armature = None + + if self.armature: + self.skin = gltf2_blender_gather_skins.gather_skin(self.export_settings['vtree'].nodes[self.uuid_for_skined_data].armature, self.export_settings) + if not self.skin: + self.armature = None + + self.key_blocks = [] + if self.export_settings[gltf2_blender_export_keys.APPLY] is False and self.blender_mesh.shape_keys and self.export_settings[gltf2_blender_export_keys.MORPH]: + self.key_blocks = [ + key_block + for key_block in self.blender_mesh.shape_keys.key_blocks + if not (key_block == key_block.relative_key or key_block.mute) + ] + + # Fetch vert positions and bone data (joint,weights) + + self.locs = None + self.morph_locs = None + self.__get_positions() + + if self.skin: + self.__get_bone_data() + if self.need_neutral_bone is True: + # Need to create a fake joint at root of armature + # In order to assign not assigned vertices to it + # But for now, this is not yet possible, we need to wait the armature node is created + # Just store this, to be used later + armature_uuid = self.export_settings['vtree'].nodes[self.uuid_for_skined_data].armature + self.export_settings['vtree'].nodes[armature_uuid].need_neutral_bone = True + + def define_attributes(self): + # Manage attributes + COLOR_0 + for blender_attribute_index, blender_attribute in enumerate(self.blender_mesh.attributes): + attr = {} + attr['blender_attribute_index'] = blender_attribute_index + attr['blender_name'] = blender_attribute.name + attr['blender_domain'] = blender_attribute.domain + attr['blender_data_type'] = blender_attribute.data_type + + # For now, we don't export edge data, because I need to find how to + # get from edge data to dots data + if attr['blender_domain'] == "EDGE": + continue + + # Some type are not exportable (example : String) + if gltf2_blender_conversion.get_component_type(blender_attribute.data_type) is None or \ + gltf2_blender_conversion.get_data_type(blender_attribute.data_type) is None: + + continue + + if self.blender_mesh.color_attributes.find(blender_attribute.name) == self.blender_mesh.color_attributes.render_color_index \ + and self.blender_mesh.color_attributes.render_color_index != -1: + + if self.export_settings[gltf2_blender_export_keys.COLORS] is False: + continue + attr['gltf_attribute_name'] = 'COLOR_0' + attr['get'] = self.get_function() + + else: + attr['gltf_attribute_name'] = '_' + blender_attribute.name.upper() + attr['get'] = self.get_function() + if self.export_settings['gltf_attributes'] is False: + continue + + self.blender_attributes.append(attr) + + # Manage POSITION + attr = {} + attr['blender_data_type'] = 'FLOAT_VECTOR' + attr['blender_domain'] = 'POINT' + attr['gltf_attribute_name'] = 'POSITION' + attr['set'] = self.set_function() + attr['skip_getting_to_dots'] = True + self.blender_attributes.append(attr) + + # Manage uvs TEX_COORD_x + for tex_coord_i in range(self.tex_coord_max): + attr = {} + attr['blender_data_type'] = 'FLOAT2' + attr['blender_domain'] = 'CORNER' + attr['gltf_attribute_name'] = 'TEXCOORD_' + str(tex_coord_i) + attr['get'] = self.get_function() + self.blender_attributes.append(attr) + + # Manage NORMALS + if self.use_normals: + attr = {} + attr['blender_data_type'] = 'FLOAT_VECTOR' + attr['blender_domain'] = 'CORNER' + attr['gltf_attribute_name'] = 'NORMAL' + attr['gltf_attribute_name_morph'] = 'MORPH_NORMAL_' + attr['get'] = self.get_function() + self.blender_attributes.append(attr) + + # Manage TANGENT + if self.use_tangents: + attr = {} + attr['blender_data_type'] = 'FLOAT_VECTOR_4' + attr['blender_domain'] = 'CORNER' + attr['gltf_attribute_name'] = 'TANGENT' + attr['get'] = self.get_function() + self.blender_attributes.append(attr) + + # Manage MORPH_POSITION_x + for morph_i, vs in enumerate(self.morph_locs): + attr = {} + attr['blender_attribute_index'] = morph_i + attr['blender_data_type'] = 'FLOAT_VECTOR' + attr['blender_domain'] = 'POINT' + attr['gltf_attribute_name'] = 'MORPH_POSITION_' + str(morph_i) + attr['skip_getting_to_dots'] = True + attr['set'] = self.set_function() + self.blender_attributes.append(attr) + + # Manage MORPH_NORMAL_x + if self.use_morph_normals: + attr = {} + attr['blender_attribute_index'] = morph_i + attr['blender_data_type'] = 'FLOAT_VECTOR' + attr['blender_domain'] = 'CORNER' + attr['gltf_attribute_name'] = 'MORPH_NORMAL_' + str(morph_i) + # No get function is set here, because data are set from NORMALS + self.blender_attributes.append(attr) + + # Manage MORPH_TANGENT_x + # This is a particular case, where we need to have the following data already calculated + # - NORMAL + # - MORPH_NORMAL + # - TANGENT + # So, the following needs to be AFTER the 3 others. + if self.use_morph_tangents: + attr = {} + attr['blender_attribute_index'] = morph_i + attr['blender_data_type'] = 'FLOAT_VECTOR' + attr['blender_domain'] = 'CORNER' + attr['gltf_attribute_name'] = 'MORPH_TANGENT_' + str(morph_i) + attr['gltf_attribute_name_normal'] = "NORMAL" + attr['gltf_attribute_name_morph_normal'] = "MORPH_NORMAL_" + str(morph_i) + attr['gltf_attribute_name_tangent'] = "TANGENT" + attr['skip_getting_to_dots'] = True + attr['set'] = self.set_function() + self.blender_attributes.append(attr) + + for attr in self.blender_attributes: + attr['len'] = gltf2_blender_conversion.get_data_length(attr['blender_data_type']) + attr['type'] = gltf2_blender_conversion.get_numpy_type(attr['blender_data_type']) + + def create_dots_data_structure(self): + # Now that we get all attributes that are going to be exported, create numpy array that will store them + dot_fields = [('vertex_index', np.uint32)] + if self.export_settings['gltf_loose_edges']: + dot_fields_edges = [('vertex_index', np.uint32)] + if self.export_settings['gltf_loose_points']: + dot_fields_points = [('vertex_index', np.uint32)] + for attr in self.blender_attributes: + if 'skip_getting_to_dots' in attr: + continue + for i in range(attr['len']): + dot_fields.append((attr['gltf_attribute_name'] + str(i), attr['type'])) + if attr['blender_domain'] != 'POINT': + continue + if self.export_settings['gltf_loose_edges']: + dot_fields_edges.append((attr['gltf_attribute_name'] + str(i), attr['type'])) + if self.export_settings['gltf_loose_points']: + dot_fields_points.append((attr['gltf_attribute_name'] + str(i), attr['type'])) + + # 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. + # + # 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. + + self.dots = np.empty(len(self.blender_mesh.loops), dtype=np.dtype(dot_fields)) + + # Find loose edges + if self.export_settings['gltf_loose_edges']: + loose_edges = [e for e in self.blender_mesh.edges if e.is_loose] + self.blender_idxs_edges = [vi for e in loose_edges for vi in e.vertices] + self.blender_idxs_edges = np.array(self.blender_idxs_edges, dtype=np.uint32) + + self.dots_edges = np.empty(len(self.blender_idxs_edges), dtype=np.dtype(dot_fields_edges)) + self.dots_edges['vertex_index'] = self.blender_idxs_edges + + # Find loose points + if self.export_settings['gltf_loose_points']: + verts_in_edge = set(vi for e in self.blender_mesh.edges for vi in e.vertices) + self.blender_idxs_points = [ + vi for vi, _ in enumerate(self.blender_mesh.vertices) + if vi not in verts_in_edge + ] + self.blender_idxs_points = np.array(self.blender_idxs_points, dtype=np.uint32) + + self.dots_points = np.empty(len(self.blender_idxs_points), dtype=np.dtype(dot_fields_points)) + self.dots_points['vertex_index'] = self.blender_idxs_points + + + def populate_dots_data(self): + vidxs = np.empty(len(self.blender_mesh.loops)) + self.blender_mesh.loops.foreach_get('vertex_index', vidxs) + self.dots['vertex_index'] = vidxs + del vidxs + + for attr in self.blender_attributes: + if 'skip_getting_to_dots' in attr: + continue + if 'get' not in attr: + continue + attr['get'](attr) + + def primitive_split(self): + # Calculate triangles and sort them into primitives. + + self.blender_mesh.calc_loop_triangles() + loop_indices = np.empty(len(self.blender_mesh.loop_triangles) * 3, dtype=np.uint32) + self.blender_mesh.loop_triangles.foreach_get('loops', loop_indices) + + self.prim_indices = {} # maps material index to TRIANGLES-style indices into dots + + if self.use_materials == "NONE": # Only for None. For placeholder and export, keep primitives + # Put all vertices into one primitive + self.prim_indices[-1] = loop_indices + + else: + # Bucket by material index. + + tri_material_idxs = np.empty(len(self.blender_mesh.loop_triangles), dtype=np.uint32) + self.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: + self.prim_indices[material_idx] = loop_indices[loop_material_idxs == material_idx] + + def primitive_creation(self): + primitives = [] + + for material_idx, dot_indices in self.prim_indices.items(): + # Extract just dots used by this primitive, deduplicate them, and + # calculate indices into this deduplicated list. + self.prim_dots = self.dots[dot_indices] + self.prim_dots, indices = np.unique(self.prim_dots, return_inverse=True) + + if len(self.prim_dots) == 0: + continue + + # Now just move all the data for prim_dots into attribute arrays + + self.attributes = {} + + self.blender_idxs = self.prim_dots['vertex_index'] + + for attr in self.blender_attributes: + if 'set' in attr: + attr['set'](attr) + else: # Regular case + self.__set_regular_attribute(attr) + + if self.skin: + joints = [[] for _ in range(self.num_joint_sets)] + weights = [[] for _ in range(self.num_joint_sets)] + + for vi in self.blender_idxs: + bones = self.vert_bones[vi] + for j in range(0, 4 * self.num_joint_sets): + if j < len(bones): + joint, weight = bones[j] + else: + joint, weight = 0, 0.0 + joints[j//4].append(joint) + weights[j//4].append(weight) + + for i, (js, ws) in enumerate(zip(joints, weights)): + self.attributes['JOINTS_%d' % i] = js + self.attributes['WEIGHTS_%d' % i] = ws + + primitives.append({ + 'attributes': self.attributes, + 'indices': indices, + 'material': material_idx + }) + + if self.export_settings['gltf_loose_edges']: + + if self.blender_idxs_edges.shape[0] > 0: + # Export one glTF vert per unique Blender vert in a loose edge + self.blender_idxs = self.blender_idxs_edges + dots_edges, indices = np.unique(self.dots_edges, return_inverse=True) + self.blender_idxs = np.unique(self.blender_idxs_edges) + + self.attributes = {} + + for attr in self.blender_attributes: + if attr['blender_domain'] != 'POINT': + continue + if 'set' in attr: + attr['set'](attr) + else: + res = np.empty((len(dots_edges), attr['len']), dtype=attr['type']) + for i in range(attr['len']): + res[:, i] = dots_edges[attr['gltf_attribute_name'] + str(i)] + self.attributes[attr['gltf_attribute_name']] = {} + self.attributes[attr['gltf_attribute_name']]["data"] = res + self.attributes[attr['gltf_attribute_name']]["component_type"] = gltf2_blender_conversion.get_component_type(attr['blender_data_type']) + self.attributes[attr['gltf_attribute_name']]["data_type"] = gltf2_blender_conversion.get_data_type(attr['blender_data_type']) + + + if self.skin: + joints = [[] for _ in range(self.num_joint_sets)] + weights = [[] for _ in range(self.num_joint_sets)] + + for vi in self.blender_idxs: + bones = self.vert_bones[vi] + for j in range(0, 4 * self.num_joint_sets): + if j < len(bones): + joint, weight = bones[j] + else: + joint, weight = 0, 0.0 + joints[j//4].append(joint) + weights[j//4].append(weight) + + for i, (js, ws) in enumerate(zip(joints, weights)): + self.attributes['JOINTS_%d' % i] = js + self.attributes['WEIGHTS_%d' % i] = ws + + primitives.append({ + 'attributes': self.attributes, + 'indices': indices, + 'mode': 1, # LINES + 'material': 0 + }) + + if self.export_settings['gltf_loose_points']: + + if self.blender_idxs_points.shape[0] > 0: + self.blender_idxs = self.blender_idxs_points + + self.attributes = {} + + for attr in self.blender_attributes: + if attr['blender_domain'] != 'POINT': + continue + if 'set' in attr: + attr['set'](attr) + else: + res = np.empty((len(self.blender_idxs), attr['len']), dtype=attr['type']) + for i in range(attr['len']): + res[:, i] = self.dots_points[attr['gltf_attribute_name'] + str(i)] + self.attributes[attr['gltf_attribute_name']] = {} + self.attributes[attr['gltf_attribute_name']]["data"] = res + self.attributes[attr['gltf_attribute_name']]["component_type"] = gltf2_blender_conversion.get_component_type(attr['blender_data_type']) + self.attributes[attr['gltf_attribute_name']]["data_type"] = gltf2_blender_conversion.get_data_type(attr['blender_data_type']) + + + if self.skin: + joints = [[] for _ in range(self.num_joint_sets)] + weights = [[] for _ in range(self.num_joint_sets)] + + for vi in self.blender_idxs: + bones = self.vert_bones[vi] + for j in range(0, 4 * self.num_joint_sets): + if j < len(bones): + joint, weight = bones[j] + else: + joint, weight = 0, 0.0 + joints[j//4].append(joint) + weights[j//4].append(weight) + + for i, (js, ws) in enumerate(zip(joints, weights)): + self.attributes['JOINTS_%d' % i] = js + self.attributes['WEIGHTS_%d' % i] = ws + + primitives.append({ + 'attributes': self.attributes, + 'mode': 0, # POINTS + 'material': 0 + }) + + print_console('INFO', 'Primitives created: %d' % len(primitives)) + + return primitives + +################################## Get ################################################## + + def __get_positions(self): + self.locs = np.empty(len(self.blender_mesh.vertices) * 3, dtype=np.float32) + source = self.key_blocks[0].relative_key.data if self.key_blocks else self.blender_mesh.vertices + source.foreach_get('co', self.locs) + self.locs = self.locs.reshape(len(self.blender_mesh.vertices), 3) + + self.morph_locs = [] + for key_block in self.key_blocks: + vs = np.empty(len(self.blender_mesh.vertices) * 3, dtype=np.float32) + key_block.data.foreach_get('co', vs) + vs = vs.reshape(len(self.blender_mesh.vertices), 3) + self.morph_locs.append(vs) + + # Transform for skinning + if self.armature and self.blender_object: + # apply_matrix = armature.matrix_world.inverted_safe() @ blender_object.matrix_world + # loc_transform = armature.matrix_world @ apply_matrix + + loc_transform = self.blender_object.matrix_world + self.locs[:] = PrimitiveCreator.apply_mat_to_all(loc_transform, self.locs) + for vs in self.morph_locs: + vs[:] = PrimitiveCreator.apply_mat_to_all(loc_transform, vs) + + # glTF stores deltas in morph targets + for vs in self.morph_locs: + vs -= self.locs + + if self.export_settings[gltf2_blender_export_keys.YUP]: + PrimitiveCreator.zup2yup(self.locs) + for vs in self.morph_locs: + PrimitiveCreator.zup2yup(vs) + + def get_function(self): + + def getting_function(attr): + if attr['gltf_attribute_name'] == "COLOR_0": + self.__get_color_attribute(attr) + elif attr['gltf_attribute_name'].startswith("_"): + self.__get_layer_attribute(attr) + elif attr['gltf_attribute_name'].startswith("TEXCOORD_"): + self.__get_uvs_attribute(int(attr['gltf_attribute_name'].split("_")[-1]), attr) + elif attr['gltf_attribute_name'] == "NORMAL": + self.__get_normal_attribute(attr) + elif attr['gltf_attribute_name'] == "TANGENT": + self.__get_tangent_attribute(attr) + + return getting_function + + + def __get_color_attribute(self, attr): + blender_color_idx = self.blender_mesh.color_attributes.render_color_index + + if attr['blender_domain'] == "POINT": + colors = np.empty(len(self.blender_mesh.vertices) * 4, dtype=np.float32) + elif attr['blender_domain'] == "CORNER": + colors = np.empty(len(self.blender_mesh.loops) * 4, dtype=np.float32) + self.blender_mesh.color_attributes[blender_color_idx].data.foreach_get('color', colors) + if attr['blender_domain'] == "POINT": + colors = colors.reshape(-1, 4) + colors = colors[self.dots['vertex_index']] + elif attr['blender_domain'] == "CORNER": + colors = colors.reshape(-1, 4) + # colors are already linear, no need to switch color space + self.dots[attr['gltf_attribute_name'] + '0'] = colors[:, 0] + self.dots[attr['gltf_attribute_name'] + '1'] = colors[:, 1] + self.dots[attr['gltf_attribute_name'] + '2'] = colors[:, 2] + self.dots[attr['gltf_attribute_name'] + '3'] = colors[:, 3] + del colors + + + def __get_layer_attribute(self, attr): + if attr['blender_domain'] in ['CORNER']: + data = np.empty(len(self.blender_mesh.loops) * attr['len'], dtype=attr['type']) + elif attr['blender_domain'] in ['POINT']: + data = np.empty(len(self.blender_mesh.vertices) * attr['len'], dtype=attr['type']) + elif attr['blender_domain'] in ['EDGE']: + data = np.empty(len(self.blender_mesh.edges) * attr['len'], dtype=attr['type']) + elif attr['blender_domain'] in ['FACE']: + data = np.empty(len(self.blender_mesh.polygons) * attr['len'], dtype=attr['type']) + else: + print_console("ERROR", "domain not known") + + if attr['blender_data_type'] == "BYTE_COLOR": + self.blender_mesh.attributes[attr['blender_attribute_index']].data.foreach_get('color', data) + data = data.reshape(-1, attr['len']) + elif attr['blender_data_type'] == "INT8": + self.blender_mesh.attributes[attr['blender_attribute_index']].data.foreach_get('value', data) + data = data.reshape(-1, attr['len']) + elif attr['blender_data_type'] == "FLOAT2": + self.blender_mesh.attributes[attr['blender_attribute_index']].data.foreach_get('vector', data) + data = data.reshape(-1, attr['len']) + elif attr['blender_data_type'] == "BOOLEAN": + self.blender_mesh.attributes[attr['blender_attribute_index']].data.foreach_get('value', data) + data = data.reshape(-1, attr['len']) + elif attr['blender_data_type'] == "STRING": + self.blender_mesh.attributes[attr['blender_attribute_index']].data.foreach_get('value', data) + data = data.reshape(-1, attr['len']) + elif attr['blender_data_type'] == "FLOAT_COLOR": + self.blender_mesh.attributes[attr['blender_attribute_index']].data.foreach_get('color', data) + data = data.reshape(-1, attr['len']) + elif attr['blender_data_type'] == "FLOAT_VECTOR": + self.blender_mesh.attributes[attr['blender_attribute_index']].data.foreach_get('vector', data) + data = data.reshape(-1, attr['len']) + elif attr['blender_data_type'] == "FLOAT_VECTOR_4": # Specific case for tangent + pass + elif attr['blender_data_type'] == "INT": + self.blender_mesh.attributes[attr['blender_attribute_index']].data.foreach_get('value', data) + data = data.reshape(-1, attr['len']) + elif attr['blender_data_type'] == "FLOAT": + self.blender_mesh.attributes[attr['blender_attribute_index']].data.foreach_get('value', data) + data = data.reshape(-1, attr['len']) + else: + print_console('ERROR',"blender type not found " + attr['blender_data_type']) + + if attr['blender_domain'] in ['CORNER']: + for i in range(attr['len']): + self.dots[attr['gltf_attribute_name'] + str(i)] = data[:, i] + elif attr['blender_domain'] in ['POINT']: + if attr['len'] > 1: + data = data.reshape(-1, attr['len']) + data_dots = data[self.dots['vertex_index']] + if self.export_settings['gltf_loose_edges']: + data_dots_edges = data[self.dots_edges['vertex_index']] + if self.export_settings['gltf_loose_points']: + data_dots_points = data[self.dots_points['vertex_index']] + for i in range(attr['len']): + self.dots[attr['gltf_attribute_name'] + str(i)] = data_dots[:, i] + if self.export_settings['gltf_loose_edges']: + self.dots_edges[attr['gltf_attribute_name'] + str(i)] = data_dots_edges[:, i] + if self.export_settings['gltf_loose_points']: + self.dots_points[attr['gltf_attribute_name'] + str(i)] = data_dots_points[:, i] + elif attr['blender_domain'] in ['EDGE']: + # No edge attribute exports + pass + elif attr['blender_domain'] in ['FACE']: + if attr['len'] > 1: + data = data.reshape(-1, attr['len']) + data = data.repeat(4, axis=0) + for i in range(attr['len']): + self.dots[attr['gltf_attribute_name'] + str(i)] = data[:, i] + + else: + print_console("ERROR", "domain not known") + + def __get_uvs_attribute(self, blender_uv_idx, attr): + layer = self.blender_mesh.uv_layers[blender_uv_idx] + uvs = np.empty(len(self.blender_mesh.loops) * 2, dtype=np.float32) + layer.data.foreach_get('uv', uvs) + uvs = uvs.reshape(len(self.blender_mesh.loops), 2) + + # Blender UV space -> glTF UV space + # u,v -> u,1-v + uvs[:, 1] *= -1 + uvs[:, 1] += 1 + + self.dots[attr['gltf_attribute_name'] + '0'] = uvs[:, 0] + self.dots[attr['gltf_attribute_name'] + '1'] = uvs[:, 1] + del uvs + + def __get_normals(self): + """Get normal for each loop.""" + key_blocks = self.key_blocks if self.use_morph_normals else [] + if key_blocks: + self.normals = key_blocks[0].relative_key.normals_split_get() + self.normals = np.array(self.normals, dtype=np.float32) + else: + self.normals = np.empty(len(self.blender_mesh.loops) * 3, dtype=np.float32) + self.blender_mesh.calc_normals_split() + self.blender_mesh.loops.foreach_get('normal', self.normals) + + self.normals = self.normals.reshape(len(self.blender_mesh.loops), 3) + + self.morph_normals = [] + for key_block in key_blocks: + ns = np.array(key_block.normals_split_get(), dtype=np.float32) + ns = ns.reshape(len(self.blender_mesh.loops), 3) + self.morph_normals.append(ns) + + # Transform for skinning + if self.armature and self.blender_object: + apply_matrix = (self.armature.matrix_world.inverted_safe() @ self.blender_object.matrix_world) + apply_matrix = apply_matrix.to_3x3().inverted_safe().transposed() + normal_transform = self.armature.matrix_world.to_3x3() @ apply_matrix + + self.normals[:] = PrimitiveCreator.apply_mat_to_all(normal_transform, self.normals) + PrimitiveCreator.normalize_vecs(self.normals) + for ns in self.morph_normals: + ns[:] = PrimitiveCreator.apply_mat_to_all(normal_transform, ns) + PrimitiveCreator.normalize_vecs(ns) + + for ns in [self.normals, *self.morph_normals]: + # Replace zero normals with the unit UP vector. + # Seems to happen sometimes with degenerate tris? + is_zero = ~ns.any(axis=1) + ns[is_zero, 2] = 1 + + # glTF stores deltas in morph targets + for ns in self.morph_normals: + ns -= self.normals + + if self.export_settings[gltf2_blender_export_keys.YUP]: + PrimitiveCreator.zup2yup(self.normals) + for ns in self.morph_normals: + PrimitiveCreator.zup2yup(ns) + + def __get_normal_attribute(self, attr): + self.__get_normals() + self.dots[attr['gltf_attribute_name'] + "0"] = self.normals[:, 0] + self.dots[attr['gltf_attribute_name'] + "1"] = self.normals[:, 1] + self.dots[attr['gltf_attribute_name'] + "2"] = self.normals[:, 2] + + if self.use_morph_normals: + for morph_i, ns in enumerate(self.morph_normals): + self.dots[attr['gltf_attribute_name_morph'] + str(morph_i) + "0"] = ns[:, 0] + self.dots[attr['gltf_attribute_name_morph'] + str(morph_i) + "1"] = ns[:, 1] + self.dots[attr['gltf_attribute_name_morph'] + str(morph_i) + "2"] = ns[:, 2] + del self.normals + del self.morph_normals + + def __get_tangent_attribute(self, attr): + self.__get_tangents() + self.dots[attr['gltf_attribute_name'] + "0"] = self.tangents[:, 0] + self.dots[attr['gltf_attribute_name'] + "1"] = self.tangents[:, 1] + self.dots[attr['gltf_attribute_name'] + "2"] = self.tangents[:, 2] + del self.tangents + self.__get_bitangent_signs() + self.dots[attr['gltf_attribute_name'] + "3"] = self.signs + del self.signs + + def __get_tangents(self): + """Get an array of the tangent for each loop.""" + self.tangents = np.empty(len(self.blender_mesh.loops) * 3, dtype=np.float32) + self.blender_mesh.loops.foreach_get('tangent', self.tangents) + self.tangents = self.tangents.reshape(len(self.blender_mesh.loops), 3) + + # Transform for skinning + if self.armature and self.blender_object: + apply_matrix = self.armature.matrix_world.inverted_safe() @ self.blender_object.matrix_world + tangent_transform = apply_matrix.to_quaternion().to_matrix() + self.tangents = PrimitiveCreator.apply_mat_to_all(tangent_transform, self.tangents) + PrimitiveCreator.normalize_vecs(self.tangents) + + if self.export_settings[gltf2_blender_export_keys.YUP]: + PrimitiveCreator.zup2yup(self.tangents) + + + def __get_bitangent_signs(self): + self.signs = np.empty(len(self.blender_mesh.loops), dtype=np.float32) + self.blender_mesh.loops.foreach_get('bitangent_sign', signs) + + # Transform for skinning + if self.armature and self.blender_object: + # Bitangent signs should flip when handedness changes + # TODO: confirm + apply_matrix = self.armature.matrix_world.inverted_safe() @ self.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 + + + def __get_bone_data(self): + + self.need_neutral_bone = False + min_influence = 0.0001 + + joint_name_to_index = {joint.name: index for index, joint in enumerate(self.skin.joints)} + group_to_joint = [joint_name_to_index.get(g.name) for g in self.blender_vertex_groups] + + # List of (joint, weight) pairs for each vert + self.vert_bones = [] + max_num_influences = 0 + + for vertex in self.blender_mesh.vertices: + bones = [] + if vertex.groups: + for group_element in vertex.groups: + weight = group_element.weight + if weight <= min_influence: + 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: + # Is not assign to any bone + bones = ((len(self.skin.joints), 1.0),) # Assign to a joint that will be created later + self.need_neutral_bone = True + self.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 + self.num_joint_sets = (max_num_influences + 3) // 4 + +##################################### Set ################################### + def set_function(self): + + def setting_function(attr): + if attr['gltf_attribute_name'] == "POSITION": + self.__set_positions_attribute(attr) + elif attr['gltf_attribute_name'].startswith("MORPH_POSITION_"): + self.__set_morph_locs_attribute(attr) + elif attr['gltf_attribute_name'].startswith("MORPH_TANGENT_"): + self.__set_morph_tangent_attribute(attr) + + return setting_function + + def __set_positions_attribute(self, attr): + self.attributes[attr['gltf_attribute_name']] = {} + self.attributes[attr['gltf_attribute_name']]["data"] = self.locs[self.blender_idxs] + self.attributes[attr['gltf_attribute_name']]["data_type"] = gltf2_io_constants.DataType.Vec3 + self.attributes[attr['gltf_attribute_name']]["component_type"] = gltf2_io_constants.ComponentType.Float + + + def __set_morph_locs_attribute(self, attr): + self.attributes[attr['gltf_attribute_name']] = {} + self.attributes[attr['gltf_attribute_name']]["data"] = self.morph_locs[attr['blender_attribute_index']][self.blender_idxs] + + def __set_morph_tangent_attribute(self, attr): + # Morph tangent are after these 3 others, so, they are already calculated + self.normals = self.attributes[attr['gltf_attribute_name_normal']]["data"] + self.morph_normals = self.attributes[attr['gltf_attribute_name_morph_normal']]["data"] + self.tangent = self.attributes[attr['gltf_attribute_name_tangent']]["data"] + + self.__calc_morph_tangents() + self.attributes[attr['gltf_attribute_name']] = {} + self.attributes[attr['gltf_attribute_name']]["data"] = self.morph_tangents + + def __calc_morph_tangents(self): + # TODO: check if this works + self.morph_tangent_deltas = np.empty((len(self.normals), 3), dtype=np.float32) + + for i in range(len(self.normals)): + n = Vector(self.normals[i]) + morph_n = n + Vector(self.morph_normal_deltas[i]) # convert back to non-delta + t = Vector(self.tangents[i, :3]) + + rotation = morph_n.rotation_difference(n) + + t_morph = Vector(t) + t_morph.rotate(rotation) + self.morph_tangent_deltas[i] = t_morph - t # back to delta + + def __set_regular_attribute(self, attr): + res = np.empty((len(self.prim_dots), attr['len']), dtype=attr['type']) + for i in range(attr['len']): + res[:, i] = self.prim_dots[attr['gltf_attribute_name'] + str(i)] + self.attributes[attr['gltf_attribute_name']] = {} + self.attributes[attr['gltf_attribute_name']]["data"] = res + if 'gltf_attribute_name' == "NORMAL": + self.attributes[attr['gltf_attribute_name']]["component_type"] = gltf2_io_constants.ComponentType.Float + self.attributes[attr['gltf_attribute_name']]["data_type"] = gltf2_io_constants.DataType.Vec3 + elif 'gltf_attribute_name' == "TANGENT": + self.attributes[attr['gltf_attribute_name']]["component_type"] = gltf2_io_constants.ComponentType.Float + self.attributes[attr['gltf_attribute_name']]["data_type"] = gltf2_io_constants.DataType.Vec4 + elif attr['gltf_attribute_name'].startswith('TEXCOORD_'): + self.attributes[attr['gltf_attribute_name']]["component_type"] = gltf2_io_constants.ComponentType.Float + self.attributes[attr['gltf_attribute_name']]["data_type"] = gltf2_io_constants.DataType.Vec2 + else: + self.attributes[attr['gltf_attribute_name']]["component_type"] = gltf2_blender_conversion.get_component_type(attr['blender_data_type']) + self.attributes[attr['gltf_attribute_name']]["data_type"] = gltf2_blender_conversion.get_data_type(attr['blender_data_type']) diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py index cfeb70e2..dc25b417 100644 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py @@ -11,7 +11,6 @@ from mathutils import Quaternion, Matrix from io_scene_gltf2.io.com import gltf2_io from io_scene_gltf2.io.imp.gltf2_io_binary import BinaryData from io_scene_gltf2.io.com import gltf2_io_constants -from .gltf2_blender_gather_primitive_attributes import array_to_accessor from io_scene_gltf2.io.exp import gltf2_io_binary_data from io_scene_gltf2.blender.exp import gltf2_blender_gather_accessors -- cgit v1.2.3