diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index 12b4aa66aa4cddb93f0ba55a1c1c28e8b202c9a9..17b76b32d7961575a6c7290a73e03fe7619e1812 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, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (1, 3, 10), + "version": (1, 3, 11), 'blender': (2, 83, 9), '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 ae79f883564b5db7c0e8c9fa19e167dd14462849..e546b0631dee7c1ba7f177c0d8fb9f44041520cf 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py @@ -1,702 +1,702 @@ -# Copyright 2018-2019 The glTF-Blender-IO authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# -# Imports -# - -from mathutils import Vector, Quaternion, Matrix -from mathutils.geometry import tessellate_polygon -from operator import attrgetter - -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 -import bpy - -# -# Globals -# - -INDICES_ID = 'indices' -MATERIAL_ID = 'material' -ATTRIBUTES_ID = 'attributes' - -COLOR_PREFIX = 'COLOR_' -MORPH_TANGENT_PREFIX = 'MORPH_TANGENT_' -MORPH_NORMAL_PREFIX = 'MORPH_NORMAL_' -MORPH_POSITION_PREFIX = 'MORPH_POSITION_' -TEXCOORD_PREFIX = 'TEXCOORD_' -WEIGHTS_PREFIX = 'WEIGHTS_' -JOINTS_PREFIX = 'JOINTS_' - -TANGENT_ATTRIBUTE = 'TANGENT' -NORMAL_ATTRIBUTE = 'NORMAL' -POSITION_ATTRIBUTE = 'POSITION' - -GLTF_MAX_COLORS = 2 - - -# -# Classes -# - -class ShapeKey: - def __init__(self, shape_key, vertex_normals, polygon_normals): - self.shape_key = shape_key - self.vertex_normals = vertex_normals - self.polygon_normals = polygon_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], 1.0)) - else: - return Vector((tan[0], tan[1], tan[2], 1.0)) - 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() @ tan - if export_settings[gltf2_blender_export_keys.YUP]: - return Vector((new_tan[0], new_tan[2], -new_tan[1], 1.0)) - else: - return Vector((new_tan[0], new_tan[1], new_tan[2], 1.0)) - -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 decompose_transition(matrix, export_settings): - translation, rotation, scale = matrix.decompose() - - return translation, rotation, scale - - -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. - - Furthermore, primitives are split up, if the indices range is exceeded. - Finally, triangles are also split up/duplicated, if face normals are used instead of vertex normals. - """ - print_console('INFO', 'Extracting primitive: ' + blender_mesh.name) - - if blender_mesh.has_custom_normals: - # Custom normals are all (0, 0, 0) until calling calc_normals_split() or calc_tangents(). - blender_mesh.calc_normals_split() - - use_tangents = False - 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.') - - # - - material_map = {} - - # - # Gathering position, normal and tex_coords. - # - no_material_attributes = { - POSITION_ATTRIBUTE: [], - NORMAL_ATTRIBUTE: [] - } - - if use_tangents: - no_material_attributes[TANGENT_ATTRIBUTE] = [] - - # - # Directory of materials with its primitive. - # - no_material_primitives = { - MATERIAL_ID: 0, - INDICES_ID: [], - ATTRIBUTES_ID: no_material_attributes - } - - material_idx_to_primitives = {0: no_material_primitives} - - # - - vertex_index_to_new_indices = {} - - material_map[0] = vertex_index_to_new_indices - - # - # Create primitive for each material. - # - for (mat_idx, _) in enumerate(blender_mesh.materials): - attributes = { - POSITION_ATTRIBUTE: [], - NORMAL_ATTRIBUTE: [] - } - - if use_tangents: - attributes[TANGENT_ATTRIBUTE] = [] - - primitive = { - MATERIAL_ID: mat_idx, - INDICES_ID: [], - ATTRIBUTES_ID: attributes - } - - material_idx_to_primitives[mat_idx] = primitive - - # - - vertex_index_to_new_indices = {} - - material_map[mat_idx] = vertex_index_to_new_indices - - tex_coord_max = 0 - if blender_mesh.uv_layers.active: - tex_coord_max = len(blender_mesh.uv_layers) - - # - - vertex_colors = {} - - color_index = 0 - for vertex_color in blender_mesh.vertex_colors: - vertex_color_name = COLOR_PREFIX + str(color_index) - vertex_colors[vertex_color_name] = vertex_color - - color_index += 1 - if color_index >= GLTF_MAX_COLORS: - break - color_max = color_index - - # - - bone_max = 0 - for blender_polygon in blender_mesh.polygons: - for loop_index in blender_polygon.loop_indices: - vertex_index = blender_mesh.loops[loop_index].vertex_index - bones_count = len(blender_mesh.vertices[vertex_index].groups) - if bones_count > 0: - if bones_count % 4 == 0: - bones_count -= 1 - bone_max = max(bone_max, bones_count // 4 + 1) - - # - - morph_max = 0 - - blender_shape_keys = [] - - if blender_mesh.shape_keys is not None: - for blender_shape_key in blender_mesh.shape_keys.key_blocks: - if blender_shape_key != blender_shape_key.relative_key: - if blender_shape_key.mute is False: - morph_max += 1 - blender_shape_keys.append(ShapeKey( - blender_shape_key, - blender_shape_key.normals_vertex_get(), # calculate vertex normals for this shape key - blender_shape_key.normals_polygon_get())) # calculate polygon normals for this shape key - - - armature = None - 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 - - - # - # Convert polygon to primitive indices and eliminate invalid ones. Assign to material. - # - for blender_polygon in blender_mesh.polygons: - export_color = True - - # - - if export_settings['gltf_materials'] is False: - primitive = material_idx_to_primitives[0] - vertex_index_to_new_indices = material_map[0] - elif not blender_polygon.material_index in material_idx_to_primitives: - primitive = material_idx_to_primitives[0] - vertex_index_to_new_indices = material_map[0] - else: - primitive = material_idx_to_primitives[blender_polygon.material_index] - vertex_index_to_new_indices = material_map[blender_polygon.material_index] - # - - attributes = primitive[ATTRIBUTES_ID] - - face_normal = blender_polygon.normal - face_tangent = Vector((0.0, 0.0, 0.0)) - face_bitangent = Vector((0.0, 0.0, 0.0)) - if use_tangents: - for loop_index in blender_polygon.loop_indices: - temp_vertex = blender_mesh.loops[loop_index] - face_tangent += temp_vertex.tangent - face_bitangent += temp_vertex.bitangent - - face_tangent.normalize() - face_bitangent.normalize() - - # - - indices = primitive[INDICES_ID] - - loop_index_list = [] - - if len(blender_polygon.loop_indices) == 3: - loop_index_list.extend(blender_polygon.loop_indices) - elif len(blender_polygon.loop_indices) > 3: - # Triangulation of polygon. Using internal function, as non-convex polygons could exist. - polyline = [] - - for loop_index in blender_polygon.loop_indices: - vertex_index = blender_mesh.loops[loop_index].vertex_index - v = blender_mesh.vertices[vertex_index].co - polyline.append(Vector((v[0], v[1], v[2]))) - - triangles = tessellate_polygon((polyline,)) - - for triangle in triangles: - - for triangle_index in triangle: - loop_index_list.append(blender_polygon.loop_indices[triangle_index]) - else: - continue - - for loop_index in loop_index_list: - vertex_index = blender_mesh.loops[loop_index].vertex_index - - if vertex_index_to_new_indices.get(vertex_index) is None: - vertex_index_to_new_indices[vertex_index] = [] - - # - - v = None - n = None - t = None - b = None - uvs = [] - colors = [] - joints = [] - weights = [] - - target_positions = [] - target_normals = [] - target_tangents = [] - - vertex = blender_mesh.vertices[vertex_index] - - v = convert_swizzle_location(vertex.co, armature, blender_object, export_settings) - if blender_polygon.use_smooth or blender_mesh.use_auto_smooth: - if blender_mesh.has_custom_normals: - n = convert_swizzle_normal(blender_mesh.loops[loop_index].normal, armature, blender_object, export_settings) - else: - n = convert_swizzle_normal(vertex.normal, armature, blender_object, export_settings) - if use_tangents: - t = convert_swizzle_tangent(blender_mesh.loops[loop_index].tangent, armature, blender_object, export_settings) - b = convert_swizzle_location(blender_mesh.loops[loop_index].bitangent, armature, blender_object, export_settings) - else: - n = convert_swizzle_normal(face_normal, armature, blender_object, export_settings) - if use_tangents: - t = convert_swizzle_tangent(face_tangent, armature, blender_object, export_settings) - b = convert_swizzle_location(face_bitangent, armature, blender_object, export_settings) - - if use_tangents: - tv = Vector((t[0], t[1], t[2])) - bv = Vector((b[0], b[1], b[2])) - nv = Vector((n[0], n[1], n[2])) - - if (nv.cross(tv)).dot(bv) < 0.0: - t[3] = -1.0 - - if blender_mesh.uv_layers.active: - for tex_coord_index in range(0, tex_coord_max): - uv = blender_mesh.uv_layers[tex_coord_index].data[loop_index].uv - uvs.append([uv.x, 1.0 - uv.y]) - - # - - if color_max > 0 and export_color: - for color_index in range(0, color_max): - color_name = COLOR_PREFIX + str(color_index) - color = vertex_colors[color_name].data[loop_index].color - colors.append([ - color_srgb_to_scene_linear(color[0]), - color_srgb_to_scene_linear(color[1]), - color_srgb_to_scene_linear(color[2]), - color[3] - ]) - - # - - bone_count = 0 - - if blender_vertex_groups is not None and vertex.groups is not None and len(vertex.groups) > 0 and export_settings[gltf2_blender_export_keys.SKINS]: - joint = [] - weight = [] - vertex_groups = vertex.groups - if not export_settings['gltf_all_vertex_influences']: - # sort groups by weight descending - vertex_groups = sorted(vertex.groups, key=attrgetter('weight'), reverse=True) - for group_element in vertex_groups: - - if len(joint) == 4: - bone_count += 1 - joints.append(joint) - weights.append(weight) - joint = [] - weight = [] - - # - - joint_weight = group_element.weight - if joint_weight <= 0.0: - continue - - # - - vertex_group_index = group_element.group - - if vertex_group_index < 0 or vertex_group_index >= len(blender_vertex_groups): - continue - vertex_group_name = blender_vertex_groups[vertex_group_index].name - - joint_index = None - - if armature: - skin = gltf2_blender_gather_skins.gather_skin(armature, export_settings) - for index, j in enumerate(skin.joints): - if j.name == vertex_group_name: - joint_index = index - break - - # - if joint_index is not None: - joint.append(joint_index) - weight.append(joint_weight) - - if len(joint) > 0: - bone_count += 1 - - for fill in range(0, 4 - len(joint)): - joint.append(0) - weight.append(0.0) - - joints.append(joint) - weights.append(weight) - - for fill in range(0, bone_max - bone_count): - joints.append([0, 0, 0, 0]) - weights.append([0.0, 0.0, 0.0, 0.0]) - - # - - if morph_max > 0 and export_settings[gltf2_blender_export_keys.MORPH]: - for morph_index in range(0, morph_max): - blender_shape_key = blender_shape_keys[morph_index] - - v_morph = convert_swizzle_location(blender_shape_key.shape_key.data[vertex_index].co, - armature, blender_object, - export_settings) - - # Store delta. - v_morph -= v - - target_positions.append(v_morph) - - # - - n_morph = None - - if blender_polygon.use_smooth: - temp_normals = blender_shape_key.vertex_normals - n_morph = (temp_normals[vertex_index * 3 + 0], temp_normals[vertex_index * 3 + 1], - temp_normals[vertex_index * 3 + 2]) - else: - temp_normals = blender_shape_key.polygon_normals - n_morph = ( - temp_normals[blender_polygon.index * 3 + 0], temp_normals[blender_polygon.index * 3 + 1], - temp_normals[blender_polygon.index * 3 + 2]) - - n_morph = convert_swizzle_normal(Vector(n_morph), armature, blender_object, export_settings) - - # Store delta. - n_morph -= n - - target_normals.append(n_morph) - - # - - if use_tangents: - rotation = n_morph.rotation_difference(n) - - t_morph = Vector((t[0], t[1], t[2])) - - t_morph.rotate(rotation) - - target_tangents.append(t_morph) - - # - # - - create = True - - for current_new_index in vertex_index_to_new_indices[vertex_index]: - found = True - - for i in range(0, 3): - if attributes[POSITION_ATTRIBUTE][current_new_index * 3 + i] != v[i]: - found = False - break - - if attributes[NORMAL_ATTRIBUTE][current_new_index * 3 + i] != n[i]: - found = False - break - - if use_tangents: - for i in range(0, 4): - if attributes[TANGENT_ATTRIBUTE][current_new_index * 4 + i] != t[i]: - found = False - break - - if not found: - continue - - for tex_coord_index in range(0, tex_coord_max): - uv = uvs[tex_coord_index] - - tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index) - for i in range(0, 2): - if attributes[tex_coord_id][current_new_index * 2 + i] != uv[i]: - found = False - break - - if export_color: - for color_index in range(0, color_max): - color = colors[color_index] - - color_id = COLOR_PREFIX + str(color_index) - for i in range(0, 3): - # Alpha is always 1.0 - see above. - current_color = attributes[color_id][current_new_index * 4 + i] - if color_srgb_to_scene_linear(current_color) != color[i]: - found = False - break - - if export_settings[gltf2_blender_export_keys.SKINS]: - for bone_index in range(0, bone_max): - joint = joints[bone_index] - weight = weights[bone_index] - - joint_id = JOINTS_PREFIX + str(bone_index) - weight_id = WEIGHTS_PREFIX + str(bone_index) - for i in range(0, 4): - if attributes[joint_id][current_new_index * 4 + i] != joint[i]: - found = False - break - if attributes[weight_id][current_new_index * 4 + i] != weight[i]: - found = False - break - - if export_settings[gltf2_blender_export_keys.MORPH]: - for morph_index in range(0, morph_max): - target_position = target_positions[morph_index] - target_normal = target_normals[morph_index] - if use_tangents: - target_tangent = target_tangents[morph_index] - - target_position_id = MORPH_POSITION_PREFIX + str(morph_index) - target_normal_id = MORPH_NORMAL_PREFIX + str(morph_index) - target_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index) - for i in range(0, 3): - if attributes[target_position_id][current_new_index * 3 + i] != target_position[i]: - found = False - break - if attributes[target_normal_id][current_new_index * 3 + i] != target_normal[i]: - found = False - break - if use_tangents: - if attributes[target_tangent_id][current_new_index * 3 + i] != target_tangent[i]: - found = False - break - - if found: - indices.append(current_new_index) - - create = False - break - - if not create: - continue - - new_index = 0 - - if primitive.get('max_index') is not None: - new_index = primitive['max_index'] + 1 - - primitive['max_index'] = new_index - - vertex_index_to_new_indices[vertex_index].append(new_index) - - # - # - - indices.append(new_index) - - # - - attributes[POSITION_ATTRIBUTE].extend(v) - attributes[NORMAL_ATTRIBUTE].extend(n) - if use_tangents: - attributes[TANGENT_ATTRIBUTE].extend(t) - - if blender_mesh.uv_layers.active: - for tex_coord_index in range(0, tex_coord_max): - tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index) - - if attributes.get(tex_coord_id) is None: - attributes[tex_coord_id] = [] - - attributes[tex_coord_id].extend(uvs[tex_coord_index]) - - if export_color: - for color_index in range(0, color_max): - color_id = COLOR_PREFIX + str(color_index) - - if attributes.get(color_id) is None: - attributes[color_id] = [] - - attributes[color_id].extend(colors[color_index]) - - if export_settings[gltf2_blender_export_keys.SKINS]: - for bone_index in range(0, bone_max): - joint_id = JOINTS_PREFIX + str(bone_index) - - if attributes.get(joint_id) is None: - attributes[joint_id] = [] - - attributes[joint_id].extend(joints[bone_index]) - - weight_id = WEIGHTS_PREFIX + str(bone_index) - - if attributes.get(weight_id) is None: - attributes[weight_id] = [] - - attributes[weight_id].extend(weights[bone_index]) - - if export_settings[gltf2_blender_export_keys.MORPH]: - for morph_index in range(0, morph_max): - target_position_id = MORPH_POSITION_PREFIX + str(morph_index) - - if attributes.get(target_position_id) is None: - attributes[target_position_id] = [] - - attributes[target_position_id].extend(target_positions[morph_index]) - - target_normal_id = MORPH_NORMAL_PREFIX + str(morph_index) - - if attributes.get(target_normal_id) is None: - attributes[target_normal_id] = [] - - attributes[target_normal_id].extend(target_normals[morph_index]) - - if use_tangents: - target_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index) - - if attributes.get(target_tangent_id) is None: - attributes[target_tangent_id] = [] - - attributes[target_tangent_id].extend(target_tangents[morph_index]) - - # - # Add non-empty primitives - # - - result_primitives = [ - primitive - for primitive in material_idx_to_primitives.values() - if len(primitive[INDICES_ID]) != 0 - ] - - print_console('INFO', 'Primitives created: ' + str(len(result_primitives))) - - return result_primitives +# Copyright 2018-2019 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Imports +# + +from mathutils import Vector, Quaternion, Matrix +from mathutils.geometry import tessellate_polygon +from operator import attrgetter + +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 +import bpy + +# +# Globals +# + +INDICES_ID = 'indices' +MATERIAL_ID = 'material' +ATTRIBUTES_ID = 'attributes' + +COLOR_PREFIX = 'COLOR_' +MORPH_TANGENT_PREFIX = 'MORPH_TANGENT_' +MORPH_NORMAL_PREFIX = 'MORPH_NORMAL_' +MORPH_POSITION_PREFIX = 'MORPH_POSITION_' +TEXCOORD_PREFIX = 'TEXCOORD_' +WEIGHTS_PREFIX = 'WEIGHTS_' +JOINTS_PREFIX = 'JOINTS_' + +TANGENT_ATTRIBUTE = 'TANGENT' +NORMAL_ATTRIBUTE = 'NORMAL' +POSITION_ATTRIBUTE = 'POSITION' + +GLTF_MAX_COLORS = 2 + + +# +# Classes +# + +class ShapeKey: + def __init__(self, shape_key, vertex_normals, polygon_normals): + self.shape_key = shape_key + self.vertex_normals = vertex_normals + self.polygon_normals = polygon_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], 1.0)) + else: + return Vector((tan[0], tan[1], tan[2], 1.0)) + 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() @ tan + if export_settings[gltf2_blender_export_keys.YUP]: + return Vector((new_tan[0], new_tan[2], -new_tan[1], 1.0)) + else: + return Vector((new_tan[0], new_tan[1], new_tan[2], 1.0)) + +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 decompose_transition(matrix, export_settings): + translation, rotation, scale = matrix.decompose() + + return translation, rotation, scale + + +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. + + Furthermore, primitives are split up, if the indices range is exceeded. + Finally, triangles are also split up/duplicated, if face normals are used instead of vertex normals. + """ + print_console('INFO', 'Extracting primitive: ' + blender_mesh.name) + + if blender_mesh.has_custom_normals: + # Custom normals are all (0, 0, 0) until calling calc_normals_split() or calc_tangents(). + blender_mesh.calc_normals_split() + + use_tangents = False + 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.') + + # + + material_map = {} + + # + # Gathering position, normal and tex_coords. + # + no_material_attributes = { + POSITION_ATTRIBUTE: [], + NORMAL_ATTRIBUTE: [] + } + + if use_tangents: + no_material_attributes[TANGENT_ATTRIBUTE] = [] + + # + # Directory of materials with its primitive. + # + no_material_primitives = { + MATERIAL_ID: 0, + INDICES_ID: [], + ATTRIBUTES_ID: no_material_attributes + } + + material_idx_to_primitives = {0: no_material_primitives} + + # + + vertex_index_to_new_indices = {} + + material_map[0] = vertex_index_to_new_indices + + # + # Create primitive for each material. + # + for (mat_idx, _) in enumerate(blender_mesh.materials): + attributes = { + POSITION_ATTRIBUTE: [], + NORMAL_ATTRIBUTE: [] + } + + if use_tangents: + attributes[TANGENT_ATTRIBUTE] = [] + + primitive = { + MATERIAL_ID: mat_idx, + INDICES_ID: [], + ATTRIBUTES_ID: attributes + } + + material_idx_to_primitives[mat_idx] = primitive + + # + + vertex_index_to_new_indices = {} + + material_map[mat_idx] = vertex_index_to_new_indices + + tex_coord_max = 0 + if blender_mesh.uv_layers.active: + tex_coord_max = len(blender_mesh.uv_layers) + + # + + vertex_colors = {} + + color_index = 0 + for vertex_color in blender_mesh.vertex_colors: + vertex_color_name = COLOR_PREFIX + str(color_index) + vertex_colors[vertex_color_name] = vertex_color + + color_index += 1 + if color_index >= GLTF_MAX_COLORS: + break + color_max = color_index + + # + + bone_max = 0 + for blender_polygon in blender_mesh.polygons: + for loop_index in blender_polygon.loop_indices: + vertex_index = blender_mesh.loops[loop_index].vertex_index + bones_count = len(blender_mesh.vertices[vertex_index].groups) + if bones_count > 0: + if bones_count % 4 == 0: + bones_count -= 1 + bone_max = max(bone_max, bones_count // 4 + 1) + + # + + morph_max = 0 + + blender_shape_keys = [] + + if blender_mesh.shape_keys is not None: + for blender_shape_key in blender_mesh.shape_keys.key_blocks: + if blender_shape_key != blender_shape_key.relative_key: + if blender_shape_key.mute is False: + morph_max += 1 + blender_shape_keys.append(ShapeKey( + blender_shape_key, + blender_shape_key.normals_vertex_get(), # calculate vertex normals for this shape key + blender_shape_key.normals_polygon_get())) # calculate polygon normals for this shape key + + + armature = None + 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 + + + # + # Convert polygon to primitive indices and eliminate invalid ones. Assign to material. + # + for blender_polygon in blender_mesh.polygons: + export_color = True + + # + + if export_settings['gltf_materials'] is False: + primitive = material_idx_to_primitives[0] + vertex_index_to_new_indices = material_map[0] + elif not blender_polygon.material_index in material_idx_to_primitives: + primitive = material_idx_to_primitives[0] + vertex_index_to_new_indices = material_map[0] + else: + primitive = material_idx_to_primitives[blender_polygon.material_index] + vertex_index_to_new_indices = material_map[blender_polygon.material_index] + # + + attributes = primitive[ATTRIBUTES_ID] + + face_normal = blender_polygon.normal + face_tangent = Vector((0.0, 0.0, 0.0)) + face_bitangent = Vector((0.0, 0.0, 0.0)) + if use_tangents: + for loop_index in blender_polygon.loop_indices: + temp_vertex = blender_mesh.loops[loop_index] + face_tangent += temp_vertex.tangent + face_bitangent += temp_vertex.bitangent + + face_tangent.normalize() + face_bitangent.normalize() + + # + + indices = primitive[INDICES_ID] + + loop_index_list = [] + + if len(blender_polygon.loop_indices) == 3: + loop_index_list.extend(blender_polygon.loop_indices) + elif len(blender_polygon.loop_indices) > 3: + # Triangulation of polygon. Using internal function, as non-convex polygons could exist. + polyline = [] + + for loop_index in blender_polygon.loop_indices: + vertex_index = blender_mesh.loops[loop_index].vertex_index + v = blender_mesh.vertices[vertex_index].co + polyline.append(Vector((v[0], v[1], v[2]))) + + triangles = tessellate_polygon((polyline,)) + + for triangle in triangles: + + for triangle_index in triangle: + loop_index_list.append(blender_polygon.loop_indices[triangle_index]) + else: + continue + + for loop_index in loop_index_list: + vertex_index = blender_mesh.loops[loop_index].vertex_index + + if vertex_index_to_new_indices.get(vertex_index) is None: + vertex_index_to_new_indices[vertex_index] = [] + + # + + v = None + n = None + t = None + b = None + uvs = [] + colors = [] + joints = [] + weights = [] + + target_positions = [] + target_normals = [] + target_tangents = [] + + vertex = blender_mesh.vertices[vertex_index] + + v = convert_swizzle_location(vertex.co, armature, blender_object, export_settings) + if blender_polygon.use_smooth or blender_mesh.use_auto_smooth: + if blender_mesh.has_custom_normals: + n = convert_swizzle_normal(blender_mesh.loops[loop_index].normal, armature, blender_object, export_settings) + else: + n = convert_swizzle_normal(vertex.normal, armature, blender_object, export_settings) + if use_tangents: + t = convert_swizzle_tangent(blender_mesh.loops[loop_index].tangent, armature, blender_object, export_settings) + b = convert_swizzle_location(blender_mesh.loops[loop_index].bitangent, armature, blender_object, export_settings) + else: + n = convert_swizzle_normal(face_normal, armature, blender_object, export_settings) + if use_tangents: + t = convert_swizzle_tangent(face_tangent, armature, blender_object, export_settings) + b = convert_swizzle_location(face_bitangent, armature, blender_object, export_settings) + + if use_tangents: + tv = Vector((t[0], t[1], t[2])) + bv = Vector((b[0], b[1], b[2])) + nv = Vector((n[0], n[1], n[2])) + + if (nv.cross(tv)).dot(bv) < 0.0: + t[3] = -1.0 + + if blender_mesh.uv_layers.active: + for tex_coord_index in range(0, tex_coord_max): + uv = blender_mesh.uv_layers[tex_coord_index].data[loop_index].uv + uvs.append([uv.x, 1.0 - uv.y]) + + # + + if color_max > 0 and export_color: + for color_index in range(0, color_max): + color_name = COLOR_PREFIX + str(color_index) + color = vertex_colors[color_name].data[loop_index].color + colors.append([ + color_srgb_to_scene_linear(color[0]), + color_srgb_to_scene_linear(color[1]), + color_srgb_to_scene_linear(color[2]), + color[3] + ]) + + # + + bone_count = 0 + + if blender_vertex_groups is not None and vertex.groups is not None and len(vertex.groups) > 0 and export_settings[gltf2_blender_export_keys.SKINS]: + joint = [] + weight = [] + vertex_groups = vertex.groups + if not export_settings['gltf_all_vertex_influences']: + # sort groups by weight descending + vertex_groups = sorted(vertex.groups, key=attrgetter('weight'), reverse=True) + for group_element in vertex_groups: + + if len(joint) == 4: + bone_count += 1 + joints.append(joint) + weights.append(weight) + joint = [] + weight = [] + + # + + joint_weight = group_element.weight + if joint_weight <= 0.0: + continue + + # + + vertex_group_index = group_element.group + + if vertex_group_index < 0 or vertex_group_index >= len(blender_vertex_groups): + continue + vertex_group_name = blender_vertex_groups[vertex_group_index].name + + joint_index = None + + if armature: + skin = gltf2_blender_gather_skins.gather_skin(armature, export_settings) + for index, j in enumerate(skin.joints): + if j.name == vertex_group_name: + joint_index = index + break + + # + if joint_index is not None: + joint.append(joint_index) + weight.append(joint_weight) + + if len(joint) > 0: + bone_count += 1 + + for fill in range(0, 4 - len(joint)): + joint.append(0) + weight.append(0.0) + + joints.append(joint) + weights.append(weight) + + for fill in range(0, bone_max - bone_count): + joints.append([0, 0, 0, 0]) + weights.append([0.0, 0.0, 0.0, 0.0]) + + # + + if morph_max > 0 and export_settings[gltf2_blender_export_keys.MORPH]: + for morph_index in range(0, morph_max): + blender_shape_key = blender_shape_keys[morph_index] + + v_morph = convert_swizzle_location(blender_shape_key.shape_key.data[vertex_index].co, + armature, blender_object, + export_settings) + + # Store delta. + v_morph -= v + + target_positions.append(v_morph) + + # + + n_morph = None + + if blender_polygon.use_smooth: + temp_normals = blender_shape_key.vertex_normals + n_morph = (temp_normals[vertex_index * 3 + 0], temp_normals[vertex_index * 3 + 1], + temp_normals[vertex_index * 3 + 2]) + else: + temp_normals = blender_shape_key.polygon_normals + n_morph = ( + temp_normals[blender_polygon.index * 3 + 0], temp_normals[blender_polygon.index * 3 + 1], + temp_normals[blender_polygon.index * 3 + 2]) + + n_morph = convert_swizzle_normal(Vector(n_morph), armature, blender_object, export_settings) + + # Store delta. + n_morph -= n + + target_normals.append(n_morph) + + # + + if use_tangents: + rotation = n_morph.rotation_difference(n) + + t_morph = Vector((t[0], t[1], t[2])) + + t_morph.rotate(rotation) + + target_tangents.append(t_morph) + + # + # + + create = True + + for current_new_index in vertex_index_to_new_indices[vertex_index]: + found = True + + for i in range(0, 3): + if attributes[POSITION_ATTRIBUTE][current_new_index * 3 + i] != v[i]: + found = False + break + + if attributes[NORMAL_ATTRIBUTE][current_new_index * 3 + i] != n[i]: + found = False + break + + if use_tangents: + for i in range(0, 4): + if attributes[TANGENT_ATTRIBUTE][current_new_index * 4 + i] != t[i]: + found = False + break + + if not found: + continue + + for tex_coord_index in range(0, tex_coord_max): + uv = uvs[tex_coord_index] + + tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index) + for i in range(0, 2): + if attributes[tex_coord_id][current_new_index * 2 + i] != uv[i]: + found = False + break + + if export_color: + for color_index in range(0, color_max): + color = colors[color_index] + + color_id = COLOR_PREFIX + str(color_index) + for i in range(0, 3): + # Alpha is always 1.0 - see above. + current_color = attributes[color_id][current_new_index * 4 + i] + if color_srgb_to_scene_linear(current_color) != color[i]: + found = False + break + + if export_settings[gltf2_blender_export_keys.SKINS]: + for bone_index in range(0, bone_max): + joint = joints[bone_index] + weight = weights[bone_index] + + joint_id = JOINTS_PREFIX + str(bone_index) + weight_id = WEIGHTS_PREFIX + str(bone_index) + for i in range(0, 4): + if attributes[joint_id][current_new_index * 4 + i] != joint[i]: + found = False + break + if attributes[weight_id][current_new_index * 4 + i] != weight[i]: + found = False + break + + if export_settings[gltf2_blender_export_keys.MORPH]: + for morph_index in range(0, morph_max): + target_position = target_positions[morph_index] + target_normal = target_normals[morph_index] + if use_tangents: + target_tangent = target_tangents[morph_index] + + target_position_id = MORPH_POSITION_PREFIX + str(morph_index) + target_normal_id = MORPH_NORMAL_PREFIX + str(morph_index) + target_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index) + for i in range(0, 3): + if attributes[target_position_id][current_new_index * 3 + i] != target_position[i]: + found = False + break + if attributes[target_normal_id][current_new_index * 3 + i] != target_normal[i]: + found = False + break + if use_tangents: + if attributes[target_tangent_id][current_new_index * 3 + i] != target_tangent[i]: + found = False + break + + if found: + indices.append(current_new_index) + + create = False + break + + if not create: + continue + + new_index = 0 + + if primitive.get('max_index') is not None: + new_index = primitive['max_index'] + 1 + + primitive['max_index'] = new_index + + vertex_index_to_new_indices[vertex_index].append(new_index) + + # + # + + indices.append(new_index) + + # + + attributes[POSITION_ATTRIBUTE].extend(v) + attributes[NORMAL_ATTRIBUTE].extend(n) + if use_tangents: + attributes[TANGENT_ATTRIBUTE].extend(t) + + if blender_mesh.uv_layers.active: + for tex_coord_index in range(0, tex_coord_max): + tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index) + + if attributes.get(tex_coord_id) is None: + attributes[tex_coord_id] = [] + + attributes[tex_coord_id].extend(uvs[tex_coord_index]) + + if export_color: + for color_index in range(0, color_max): + color_id = COLOR_PREFIX + str(color_index) + + if attributes.get(color_id) is None: + attributes[color_id] = [] + + attributes[color_id].extend(colors[color_index]) + + if export_settings[gltf2_blender_export_keys.SKINS]: + for bone_index in range(0, bone_max): + joint_id = JOINTS_PREFIX + str(bone_index) + + if attributes.get(joint_id) is None: + attributes[joint_id] = [] + + attributes[joint_id].extend(joints[bone_index]) + + weight_id = WEIGHTS_PREFIX + str(bone_index) + + if attributes.get(weight_id) is None: + attributes[weight_id] = [] + + attributes[weight_id].extend(weights[bone_index]) + + if export_settings[gltf2_blender_export_keys.MORPH]: + for morph_index in range(0, morph_max): + target_position_id = MORPH_POSITION_PREFIX + str(morph_index) + + if attributes.get(target_position_id) is None: + attributes[target_position_id] = [] + + attributes[target_position_id].extend(target_positions[morph_index]) + + target_normal_id = MORPH_NORMAL_PREFIX + str(morph_index) + + if attributes.get(target_normal_id) is None: + attributes[target_normal_id] = [] + + attributes[target_normal_id].extend(target_normals[morph_index]) + + if use_tangents: + target_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index) + + if attributes.get(target_tangent_id) is None: + attributes[target_tangent_id] = [] + + attributes[target_tangent_id].extend(target_tangents[morph_index]) + + # + # Add non-empty primitives + # + + result_primitives = [ + primitive + for primitive in material_idx_to_primitives.values() + if len(primitive[INDICES_ID]) != 0 + ] + + print_console('INFO', 'Primitives created: ' + str(len(result_primitives))) + + return result_primitives diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py index 488f09eaff13bd781dc12dfb3944ef5836ebeb2d..e641eb1b6d14e04fd15b0f95f4d33491f8168785 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py @@ -92,7 +92,7 @@ def __gather_node(channels: typing.Tuple[bpy.types.FCurve], bones, _, _ = gltf2_blender_gather_skins.get_bone_tree(None, blender_object) if blender_bone.name in [b.name for b in bones]: obj = blender_object.proxy if blender_object.proxy else blender_object - return gltf2_blender_gather_joints.gather_jointb(obj, blender_bone, export_settings) + return gltf2_blender_gather_joints.gather_joint(obj, blender_bone, export_settings) return gltf2_blender_gather_nodes.gather_node(blender_object, blender_object.library.name if blender_object.library else None,