Beispiel #1
0
def __gather_skin(blender_object, export_settings):
    modifiers = {m.type: m for m in blender_object.modifiers}
    if "ARMATURE" not in modifiers or modifiers["ARMATURE"].object is None:
        return None

    # no skin needed when the modifier is linked without having a vertex group
    vertex_groups = blender_object.vertex_groups
    if len(vertex_groups) == 0:
        return None

    # check if any vertices in the mesh are part of a vertex group
    depsgraph = bpy.context.evaluated_depsgraph_get()
    blender_mesh_owner = blender_object.evaluated_get(depsgraph)
    blender_mesh = blender_mesh_owner.to_mesh(preserve_all_data_layers=True, depsgraph=depsgraph)
    if not any(vertex.groups is not None and len(vertex.groups) > 0 for vertex in blender_mesh.vertices):
        return None

    # Prevent infinite recursive error. A mesh can't have an Armature modifier
    # and be bone parented to a bone of this armature
    # In that case, ignore the armature modifier, keep only the bone parenting
    if blender_object.parent is not None \
    and blender_object.parent_type == 'BONE' \
    and blender_object.parent.name == modifiers["ARMATURE"].object.name:

        return None

    # Skins and meshes must be in the same glTF node, which is different from how blender handles armatures
    return gltf2_blender_gather_skins.gather_skin(modifiers["ARMATURE"].object, export_settings)
def __gather_skin(blender_object, export_settings):
    modifiers = {m.type: m for m in blender_object.modifiers}

    if "ARMATURE" in modifiers:
        # Skins and meshes must be in the same glTF node, which is different from how blender handles armatures
        return gltf2_blender_gather_skins.gather_skin(
            modifiers["ARMATURE"].object, export_settings)
def __gather_skin(blender_object, export_settings):
    modifiers = {m.type: m for m in blender_object.modifiers}
    if "ARMATURE" not in modifiers or modifiers["ARMATURE"].object is None:
        return None

    # no skin needed when the modifier is linked without having a vertex group
    vertex_groups = blender_object.vertex_groups
    if len(vertex_groups) == 0:
        return None

    # check if any vertices in the mesh are part of a vertex group
    if bpy.app.version < (2, 80, 0):
        blender_mesh = blender_object.to_mesh(bpy.context.scene, True,
                                              'PREVIEW')
    else:
        depsgraph = bpy.context.evaluated_depsgraph_get()
        blender_mesh_owner = blender_object.evaluated_get(depsgraph)
        blender_mesh = blender_mesh_owner.to_mesh(
            preserve_all_data_layers=True, depsgraph=depsgraph)
    if not any(vertex.groups is not None and len(vertex.groups) > 0
               for vertex in blender_mesh.vertices):
        return None

    # Skins and meshes must be in the same glTF node, which is different from how blender handles armatures
    return gltf2_blender_gather_skins.gather_skin(modifiers["ARMATURE"].object,
                                                  blender_object,
                                                  export_settings)
Beispiel #4
0
def __gather_skin(blender_object, export_settings):
    modifiers = {m.type: m for m in blender_object.modifiers}
    if "ARMATURE" not in modifiers or modifiers["ARMATURE"].object is None:
        return None

    # no skin needed when the modifier is linked without having a vertex group
    vertex_groups = blender_object.vertex_groups
    if len(vertex_groups) == 0:
        return None

    # check if any vertices in the mesh are part of a vertex group
    blender_mesh = blender_object.to_mesh(bpy.context.depsgraph, True)
    if not any(vertex.groups is not None and len(vertex.groups) > 0 for vertex in blender_mesh.vertices):
        return None

    # Skins and meshes must be in the same glTF node, which is different from how blender handles armatures
    return gltf2_blender_gather_skins.gather_skin(modifiers["ARMATURE"].object, blender_object, export_settings)
Beispiel #5
0
def extract_primitives(glTF, blender_mesh, 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')

    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: '',
        INDICES_ID: [],
        ATTRIBUTES_ID: no_material_attributes
    }

    material_name_to_primitives = {'': no_material_primitives}

    #

    vertex_index_to_new_indices = {}

    material_map[''] = vertex_index_to_new_indices

    #
    # Create primitive for each material.
    #
    for blender_material in blender_mesh.materials:
        if blender_material is None:
            continue

        attributes = {POSITION_ATTRIBUTE: [], NORMAL_ATTRIBUTE: []}

        if use_tangents:
            attributes[TANGENT_ATTRIBUTE] = []

        primitive = {
            MATERIAL_ID: blender_material.name,
            INDICES_ID: [],
            ATTRIBUTES_ID: attributes
        }

        material_name_to_primitives[blender_material.name] = primitive

        #

        vertex_index_to_new_indices = {}

        material_map[blender_material.name] = 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:
        morph_max = len(blender_mesh.shape_keys.key_blocks) - 1

        for blender_shape_key in blender_mesh.shape_keys.key_blocks:
            if blender_shape_key != blender_shape_key.relative_key:
                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

    #
    # Convert polygon to primitive indices and eliminate invalid ones. Assign to material.
    #
    for blender_polygon in blender_mesh.polygons:
        export_color = True

        #

        if blender_polygon.material_index < 0 or blender_polygon.material_index >= len(blender_mesh.materials) or \
                blender_mesh.materials[blender_polygon.material_index] is None:
            primitive = material_name_to_primitives['']
            vertex_index_to_new_indices = material_map['']
        else:
            primitive = material_name_to_primitives[blender_mesh.materials[
                blender_polygon.material_index].name]
            vertex_index_to_new_indices = material_map[blender_mesh.materials[
                blender_polygon.material_index].name]
        #

        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:
                loop_index_list.append(
                    blender_polygon.loop_indices[triangle[0]])
                loop_index_list.append(
                    blender_polygon.loop_indices[triangle[2]])
                loop_index_list.append(
                    blender_polygon.loop_indices[triangle[1]])
        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, export_settings)
            if blender_polygon.use_smooth:
                n = convert_swizzle_location(vertex.normal, export_settings)
                if use_tangents:
                    t = convert_swizzle_tangent(
                        blender_mesh.loops[loop_index].tangent,
                        export_settings)
                    b = convert_swizzle_location(
                        blender_mesh.loops[loop_index].bitangent,
                        export_settings)
            else:
                n = convert_swizzle_location(face_normal, export_settings)
                if use_tangents:
                    t = convert_swizzle_tangent(face_tangent, export_settings)
                    b = convert_swizzle_location(face_bitangent,
                                                 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]), 1.0
                    ])

            #

            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 = []
                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
                    vertex_group_name = blender_vertex_groups[
                        vertex_group_index].name

                    joint_index = None

                    if modifiers is not None:
                        modifiers_dict = {m.type: m for m in modifiers}
                        if "ARMATURE" in modifiers_dict:
                            armature = modifiers_dict["ARMATURE"].object
                            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,
                        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_location(n_morph,
                                                       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 primitive plus split them if needed.
    #

    result_primitives = []

    for material_name, primitive in material_name_to_primitives.items():
        export_color = True

        #

        indices = primitive[INDICES_ID]

        if len(indices) == 0:
            continue

        position = primitive[ATTRIBUTES_ID][POSITION_ATTRIBUTE]
        normal = primitive[ATTRIBUTES_ID][NORMAL_ATTRIBUTE]
        if use_tangents:
            tangent = primitive[ATTRIBUTES_ID][TANGENT_ATTRIBUTE]
        tex_coords = []
        for tex_coord_index in range(0, tex_coord_max):
            tex_coords.append(primitive[ATTRIBUTES_ID][TEXCOORD_PREFIX +
                                                       str(tex_coord_index)])
        colors = []
        if export_color:
            for color_index in range(0, color_max):
                tex_coords.append(primitive[ATTRIBUTES_ID][COLOR_PREFIX +
                                                           str(color_index)])
        joints = []
        weights = []
        if export_settings[gltf2_blender_export_keys.SKINS]:
            for bone_index in range(0, bone_max):
                joints.append(primitive[ATTRIBUTES_ID][JOINTS_PREFIX +
                                                       str(bone_index)])
                weights.append(primitive[ATTRIBUTES_ID][WEIGHTS_PREFIX +
                                                        str(bone_index)])

        target_positions = []
        target_normals = []
        target_tangents = []
        if export_settings[gltf2_blender_export_keys.MORPH]:
            for morph_index in range(0, morph_max):
                target_positions.append(
                    primitive[ATTRIBUTES_ID][MORPH_POSITION_PREFIX +
                                             str(morph_index)])
                target_normals.append(
                    primitive[ATTRIBUTES_ID][MORPH_NORMAL_PREFIX +
                                             str(morph_index)])
                if use_tangents:
                    target_tangents.append(
                        primitive[ATTRIBUTES_ID][MORPH_TANGENT_PREFIX +
                                                 str(morph_index)])

        #

        count = len(indices)

        if count == 0:
            continue

        max_index = max(indices)

        #

        # NOTE: Values used by some graphics APIs as "primitive restart" values are disallowed.
        # Specifically, the value 65535 (in UINT16) cannot be used as a vertex index.
        # https://github.com/KhronosGroup/glTF/issues/1142
        # https://github.com/KhronosGroup/glTF/pull/1476/files

        range_indices = 65535

        #

        if max_index >= range_indices:
            #
            # Splitting result_primitives.
            #

            # At start, all indices are pending.
            pending_attributes = {POSITION_ATTRIBUTE: [], NORMAL_ATTRIBUTE: []}

            if use_tangents:
                pending_attributes[TANGENT_ATTRIBUTE] = []

            pending_primitive = {
                MATERIAL_ID: material_name,
                INDICES_ID: [],
                ATTRIBUTES_ID: pending_attributes
            }

            pending_primitive[INDICES_ID].extend(indices)

            pending_attributes[POSITION_ATTRIBUTE].extend(position)
            pending_attributes[NORMAL_ATTRIBUTE].extend(normal)
            if use_tangents:
                pending_attributes[TANGENT_ATTRIBUTE].extend(tangent)
            tex_coord_index = 0
            for tex_coord in tex_coords:
                pending_attributes[TEXCOORD_PREFIX +
                                   str(tex_coord_index)] = tex_coord
                tex_coord_index += 1
            if export_color:
                color_index = 0
                for color in colors:
                    pending_attributes[COLOR_PREFIX + str(color_index)] = color
                    color_index += 1
            if export_settings[gltf2_blender_export_keys.SKINS]:
                joint_index = 0
                for joint in joints:
                    pending_attributes[JOINTS_PREFIX +
                                       str(joint_index)] = joint
                    joint_index += 1
                weight_index = 0
                for weight in weights:
                    pending_attributes[WEIGHTS_PREFIX +
                                       str(weight_index)] = weight
                    weight_index += 1
            if export_settings[gltf2_blender_export_keys.MORPH]:
                morph_index = 0
                for target_position in target_positions:
                    pending_attributes[MORPH_POSITION_PREFIX +
                                       str(morph_index)] = target_position
                    morph_index += 1
                morph_index = 0
                for target_normal in target_normals:
                    pending_attributes[MORPH_NORMAL_PREFIX +
                                       str(morph_index)] = target_normal
                    morph_index += 1
                if use_tangents:
                    morph_index = 0
                    for target_tangent in target_tangents:
                        pending_attributes[MORPH_TANGENT_PREFIX +
                                           str(morph_index)] = target_tangent
                        morph_index += 1

            pending_indices = pending_primitive[INDICES_ID]

            # Continue until all are processed.
            while len(pending_indices) > 0:

                process_indices = pending_primitive[INDICES_ID]
                max_index = max(process_indices)

                pending_indices = []

                #
                #

                all_local_indices = []

                for i in range(0, (max_index // range_indices) + 1):
                    all_local_indices.append([])

                #
                #

                # For all faces ...
                for face_index in range(0, len(process_indices), 3):

                    written = False

                    face_min_index = min(process_indices[face_index + 0],
                                         process_indices[face_index + 1],
                                         process_indices[face_index + 2])
                    face_max_index = max(process_indices[face_index + 0],
                                         process_indices[face_index + 1],
                                         process_indices[face_index + 2])

                    # ... check if it can be but in a range of maximum indices.
                    for i in range(0, (max_index // range_indices) + 1):
                        offset = i * range_indices

                        # Yes, so store the primitive with its indices.
                        if face_min_index >= offset and face_max_index < offset + range_indices:
                            all_local_indices[i].extend([
                                process_indices[face_index + 0],
                                process_indices[face_index + 1],
                                process_indices[face_index + 2]
                            ])

                            written = True
                            break

                    # If not written, the triangle face has indices from different ranges.
                    if not written:
                        pending_indices.extend([
                            process_indices[face_index + 0],
                            process_indices[face_index + 1],
                            process_indices[face_index + 2]
                        ])

                # Only add result_primitives, which do have indices in it.
                for local_indices in all_local_indices:
                    if len(local_indices) > 0:
                        current_primitive = extract_primitive_floor(
                            pending_primitive, local_indices, use_tangents)

                        result_primitives.append(current_primitive)

                        print_console(
                            'DEBUG',
                            'Adding primitive with splitting. Indices: ' +
                            str(len(current_primitive[INDICES_ID])) +
                            ' Vertices: ' + str(
                                len(current_primitive[ATTRIBUTES_ID]
                                    [POSITION_ATTRIBUTE]) // 3))

                # Process primitive faces having indices in several ranges.
                if len(pending_indices) > 0:
                    pending_primitive = extract_primitive_pack(
                        pending_primitive, pending_indices, use_tangents)

                    print_console(
                        'DEBUG', 'Creating temporary primitive for splitting')

        else:
            #
            # No splitting needed.
            #
            result_primitives.append(primitive)

            print_console(
                'DEBUG', 'Adding primitive without splitting. Indices: ' +
                str(len(primitive[INDICES_ID])) + ' Vertices: ' +
                str(len(primitive[ATTRIBUTES_ID][POSITION_ATTRIBUTE]) // 3))

    print_console('INFO', 'Primitives created: ' + str(len(result_primitives)))

    return result_primitives
def extract_primitives(glTF, blender_mesh, library, blender_object,
                       blender_vertex_groups, modifiers, export_settings):
    """Extract primitives from a mesh."""
    print_console('INFO', 'Extracting primitive: ' + blender_mesh.name)

    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)

    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_skins.gather_skin(
                armature, 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 = []
    if 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 = __get_bone_data(blender_mesh, skin,
                                                     blender_vertex_groups)

    # In Blender there is both per-vert data, like position, and also per-loop
    # (loop=corner-of-poly) data, like normals or UVs. glTF only has per-vert
    # data, so we need to split Blender verts up into potentially-multiple glTF
    # verts.
    #
    # 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 range(color_max):
        dot_fields += [
            ('color%dr' % col_i, np.float32),
            ('color%dg' % col_i, np.float32),
            ('color%db' % col_i, np.float32),
            ('color%da' % col_i, np.float32),
        ]
    if use_morph_normals:
        for morph_i, _ in enumerate(key_blocks):
            dot_fields += [
                ('morph%dnx' % morph_i, np.float32),
                ('morph%dny' % morph_i, np.float32),
                ('morph%dnz' % morph_i, np.float32),
            ]

    dots = np.empty(len(blender_mesh.loops), dtype=np.dtype(dot_fields))

    vidxs = np.empty(len(blender_mesh.loops))
    blender_mesh.loops.foreach_get('vertex_index', vidxs)
    dots['vertex_index'] = vidxs
    del vidxs

    if use_normals:
        kbs = key_blocks if use_morph_normals else []
        normals, morph_normals = __get_normals(blender_mesh, kbs, armature,
                                               blender_object, export_settings)
        dots['nx'] = normals[:, 0]
        dots['ny'] = normals[:, 1]
        dots['nz'] = normals[:, 2]
        del normals
        for morph_i, ns in enumerate(morph_normals):
            dots['morph%dnx' % morph_i] = ns[:, 0]
            dots['morph%dny' % morph_i] = ns[:, 1]
            dots['morph%dnz' % morph_i] = ns[:, 2]
        del morph_normals

    if use_tangents:
        tangents = __get_tangents(blender_mesh, armature, blender_object,
                                  export_settings)
        dots['tx'] = tangents[:, 0]
        dots['ty'] = tangents[:, 1]
        dots['tz'] = tangents[:, 2]
        del tangents
        signs = __get_bitangent_signs(blender_mesh, armature, blender_object,
                                      export_settings)
        dots['tw'] = signs
        del signs

    for uv_i in range(tex_coord_max):
        uvs = __get_uvs(blender_mesh, uv_i)
        dots['uv%dx' % uv_i] = uvs[:, 0]
        dots['uv%dy' % uv_i] = uvs[:, 1]
        del uvs

    for col_i in range(color_max):
        colors = __get_colors(blender_mesh, col_i)
        dots['color%dr' % col_i] = colors[:, 0]
        dots['color%dg' % col_i] = colors[:, 1]
        dots['color%db' % col_i] = colors[:, 2]
        dots['color%da' % col_i] = colors[:, 3]
        del colors

    # Calculate triangles and sort them into primitives.

    blender_mesh.calc_loop_triangles()
    loop_indices = np.empty(len(blender_mesh.loop_triangles) * 3,
                            dtype=np.uint32)
    blender_mesh.loop_triangles.foreach_get('loops', loop_indices)

    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 range(color_max):
            colors = np.empty((len(prim_dots), 4), dtype=np.float32)
            colors[:, 0] = prim_dots['color%dr' % color_i]
            colors[:, 1] = prim_dots['color%dg' % color_i]
            colors[:, 2] = prim_dots['color%db' % color_i]
            colors[:, 3] = prim_dots['color%da' % color_i]
            attributes['COLOR_%d' % color_i] = colors

        if skin:
            joints = [[] for _ in range(num_joint_sets)]
            weights = [[] for _ in range(num_joint_sets)]

            for vi in blender_idxs:
                bones = vert_bones[vi]
                for j in range(0, 4 * num_joint_sets):
                    if j < len(bones):
                        joint, weight = bones[j]
                    else:
                        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 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
Beispiel #8
0
def __gather_skin(blender_object, export_settings):
    if "Armature" in blender_object.modifiers:
        # Skins and meshes must be in the same glTF node, which is different from how blender handles armatures
        return gltf2_blender_gather_skins.gather_skin(
            blender_object.modifiers["Armature"].object, export_settings)
def extract_primitives(glTF, blender_mesh, 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)

    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: '',
        INDICES_ID: [],
        ATTRIBUTES_ID: no_material_attributes
    }

    material_name_to_primitives = {'': no_material_primitives}

    #

    vertex_index_to_new_indices = {}

    material_map[''] = vertex_index_to_new_indices

    #
    # Create primitive for each material.
    #
    for blender_material in blender_mesh.materials:
        if blender_material is None:
            continue

        attributes = {
            POSITION_ATTRIBUTE: [],
            NORMAL_ATTRIBUTE: []
        }

        if use_tangents:
            attributes[TANGENT_ATTRIBUTE] = []

        primitive = {
            MATERIAL_ID: blender_material.name,
            INDICES_ID: [],
            ATTRIBUTES_ID: attributes
        }

        material_name_to_primitives[blender_material.name] = primitive

        #

        vertex_index_to_new_indices = {}

        material_map[blender_material.name] = 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:
        morph_max = len(blender_mesh.shape_keys.key_blocks) - 1

        for blender_shape_key in blender_mesh.shape_keys.key_blocks:
            if blender_shape_key != blender_shape_key.relative_key:
                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

    #
    # Convert polygon to primitive indices and eliminate invalid ones. Assign to material.
    #
    for blender_polygon in blender_mesh.polygons:
        export_color = True

        #

        if blender_polygon.material_index < 0 or blender_polygon.material_index >= len(blender_mesh.materials) or \
                blender_mesh.materials[blender_polygon.material_index] is None:
            primitive = material_name_to_primitives['']
            vertex_index_to_new_indices = material_map['']
        else:
            primitive = material_name_to_primitives[blender_mesh.materials[blender_polygon.material_index].name]
            vertex_index_to_new_indices = material_map[blender_mesh.materials[blender_polygon.material_index].name]
        #

        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:
                loop_index_list.append(blender_polygon.loop_indices[triangle[0]])
                loop_index_list.append(blender_polygon.loop_indices[triangle[2]])
                loop_index_list.append(blender_polygon.loop_indices[triangle[1]])
        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, export_settings)
            if blender_polygon.use_smooth:
                if blender_mesh.has_custom_normals:
                    n = convert_swizzle_location(blender_mesh.loops[loop_index].normal, export_settings)
                else:
                    n = convert_swizzle_location(vertex.normal, export_settings)
                if use_tangents:
                    t = convert_swizzle_tangent(blender_mesh.loops[loop_index].tangent, export_settings)
                    b = convert_swizzle_location(blender_mesh.loops[loop_index].bitangent, export_settings)
            else:
                n = convert_swizzle_location(face_normal, export_settings)
                if use_tangents:
                    t = convert_swizzle_tangent(face_tangent, export_settings)
                    b = convert_swizzle_location(face_bitangent, 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]),
                        1.0
                    ])

            #

            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
                    vertex_group_name = blender_vertex_groups[vertex_group_index].name

                    joint_index = 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
                            if armature:
                                skin = gltf2_blender_gather_skins.gather_skin(armature, modifier.id_data, 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,
                                                       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_location(n_morph, 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 primitive plus split them if needed.
    #

    result_primitives = []

    for material_name, primitive in material_name_to_primitives.items():
        export_color = True

        #

        indices = primitive[INDICES_ID]

        if len(indices) == 0:
            continue

        position = primitive[ATTRIBUTES_ID][POSITION_ATTRIBUTE]
        normal = primitive[ATTRIBUTES_ID][NORMAL_ATTRIBUTE]
        if use_tangents:
            tangent = primitive[ATTRIBUTES_ID][TANGENT_ATTRIBUTE]
        tex_coords = []
        for tex_coord_index in range(0, tex_coord_max):
            tex_coords.append(primitive[ATTRIBUTES_ID][TEXCOORD_PREFIX + str(tex_coord_index)])
        colors = []
        if export_color:
            for color_index in range(0, color_max):
                tex_coords.append(primitive[ATTRIBUTES_ID][COLOR_PREFIX + str(color_index)])
        joints = []
        weights = []
        if export_settings[gltf2_blender_export_keys.SKINS]:
            for bone_index in range(0, bone_max):
                joints.append(primitive[ATTRIBUTES_ID][JOINTS_PREFIX + str(bone_index)])
                weights.append(primitive[ATTRIBUTES_ID][WEIGHTS_PREFIX + str(bone_index)])

        target_positions = []
        target_normals = []
        target_tangents = []
        if export_settings[gltf2_blender_export_keys.MORPH]:
            for morph_index in range(0, morph_max):
                target_positions.append(primitive[ATTRIBUTES_ID][MORPH_POSITION_PREFIX + str(morph_index)])
                target_normals.append(primitive[ATTRIBUTES_ID][MORPH_NORMAL_PREFIX + str(morph_index)])
                if use_tangents:
                    target_tangents.append(primitive[ATTRIBUTES_ID][MORPH_TANGENT_PREFIX + str(morph_index)])

        #

        count = len(indices)

        if count == 0:
            continue

        max_index = max(indices)

        #

        # NOTE: Values used by some graphics APIs as "primitive restart" values are disallowed.
        # Specifically, the value 65535 (in UINT16) cannot be used as a vertex index.
        # https://github.com/KhronosGroup/glTF/issues/1142
        # https://github.com/KhronosGroup/glTF/pull/1476/files

        range_indices = 65535

        #

        if max_index >= range_indices:
            #
            # Splitting result_primitives.
            #

            # At start, all indices are pending.
            pending_attributes = {
                POSITION_ATTRIBUTE: [],
                NORMAL_ATTRIBUTE: []
            }

            if use_tangents:
                pending_attributes[TANGENT_ATTRIBUTE] = []

            pending_primitive = {
                MATERIAL_ID: material_name,
                INDICES_ID: [],
                ATTRIBUTES_ID: pending_attributes
            }

            pending_primitive[INDICES_ID].extend(indices)

            pending_attributes[POSITION_ATTRIBUTE].extend(position)
            pending_attributes[NORMAL_ATTRIBUTE].extend(normal)
            if use_tangents:
                pending_attributes[TANGENT_ATTRIBUTE].extend(tangent)
            tex_coord_index = 0
            for tex_coord in tex_coords:
                pending_attributes[TEXCOORD_PREFIX + str(tex_coord_index)] = tex_coord
                tex_coord_index += 1
            if export_color:
                color_index = 0
                for color in colors:
                    pending_attributes[COLOR_PREFIX + str(color_index)] = color
                    color_index += 1
            if export_settings[gltf2_blender_export_keys.SKINS]:
                joint_index = 0
                for joint in joints:
                    pending_attributes[JOINTS_PREFIX + str(joint_index)] = joint
                    joint_index += 1
                weight_index = 0
                for weight in weights:
                    pending_attributes[WEIGHTS_PREFIX + str(weight_index)] = weight
                    weight_index += 1
            if export_settings[gltf2_blender_export_keys.MORPH]:
                morph_index = 0
                for target_position in target_positions:
                    pending_attributes[MORPH_POSITION_PREFIX + str(morph_index)] = target_position
                    morph_index += 1
                morph_index = 0
                for target_normal in target_normals:
                    pending_attributes[MORPH_NORMAL_PREFIX + str(morph_index)] = target_normal
                    morph_index += 1
                if use_tangents:
                    morph_index = 0
                    for target_tangent in target_tangents:
                        pending_attributes[MORPH_TANGENT_PREFIX + str(morph_index)] = target_tangent
                        morph_index += 1

            pending_indices = pending_primitive[INDICES_ID]

            # Continue until all are processed.
            while len(pending_indices) > 0:

                process_indices = pending_primitive[INDICES_ID]
                max_index = max(process_indices)

                pending_indices = []

                #
                #

                all_local_indices = []

                for i in range(0, (max_index // range_indices) + 1):
                    all_local_indices.append([])

                #
                #

                # For all faces ...
                for face_index in range(0, len(process_indices), 3):

                    written = False

                    face_min_index = min(process_indices[face_index + 0], process_indices[face_index + 1],
                                         process_indices[face_index + 2])
                    face_max_index = max(process_indices[face_index + 0], process_indices[face_index + 1],
                                         process_indices[face_index + 2])

                    # ... check if it can be but in a range of maximum indices.
                    for i in range(0, (max_index // range_indices) + 1):
                        offset = i * range_indices

                        # Yes, so store the primitive with its indices.
                        if face_min_index >= offset and face_max_index < offset + range_indices:
                            all_local_indices[i].extend(
                                [process_indices[face_index + 0], process_indices[face_index + 1],
                                 process_indices[face_index + 2]])

                            written = True
                            break

                    # If not written, the triangle face has indices from different ranges.
                    if not written:
                        pending_indices.extend([process_indices[face_index + 0], process_indices[face_index + 1],
                                                process_indices[face_index + 2]])

                # Only add result_primitives, which do have indices in it.
                for local_indices in all_local_indices:
                    if len(local_indices) > 0:
                        current_primitive = extract_primitive_floor(pending_primitive, local_indices, use_tangents)

                        result_primitives.append(current_primitive)

                        print_console('DEBUG', 'Adding primitive with splitting. Indices: ' + str(
                            len(current_primitive[INDICES_ID])) + ' Vertices: ' + str(
                            len(current_primitive[ATTRIBUTES_ID][POSITION_ATTRIBUTE]) // 3))

                # Process primitive faces having indices in several ranges.
                if len(pending_indices) > 0:
                    pending_primitive = extract_primitive_pack(pending_primitive, pending_indices, use_tangents)

                    print_console('DEBUG', 'Creating temporary primitive for splitting')

        else:
            #
            # No splitting needed.
            #
            result_primitives.append(primitive)

            print_console('DEBUG', 'Adding primitive without splitting. Indices: ' + str(
                len(primitive[INDICES_ID])) + ' Vertices: ' + str(
                len(primitive[ATTRIBUTES_ID][POSITION_ATTRIBUTE]) // 3))

    print_console('INFO', 'Primitives created: ' + str(len(result_primitives)))

    return result_primitives
def extract_primitives(glTF, blender_mesh, library, blender_object,
                       blender_vertex_groups, modifiers, export_settings):
    """
    Extract primitives from a mesh. Polygons are triangulated and sorted by material.
    Vertices in multiple faces get split up as necessary.
    """
    print_console('INFO', 'Extracting primitive: ' + blender_mesh.name)

    #
    # First, decide what attributes to gather (eg. how many COLOR_n, etc.)
    # Also calculate normals/tangents now if necessary.
    #

    use_normals = export_settings[gltf2_blender_export_keys.NORMALS]
    if use_normals:
        blender_mesh.calc_normals_split()

    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)

    bone_max = 0  # number of JOINTS_n sets needed (1 set = 4 influences)
    armature = 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_skins.gather_skin(
                armature, export_settings)
            if not skin:
                armature = None
            else:
                joint_name_to_index = {
                    joint.name: index
                    for index, joint in enumerate(skin.joints)
                }
                group_to_joint = [
                    joint_name_to_index.get(g.name)
                    for g in blender_vertex_groups
                ]

                # Find out max number of bone influences
                for blender_polygon in blender_mesh.polygons:
                    for loop_index in blender_polygon.loop_indices:
                        vertex_index = blender_mesh.loops[
                            loop_index].vertex_index
                        groups_count = len(
                            blender_mesh.vertices[vertex_index].groups)
                        bones_count = (groups_count + 3) // 4
                        bone_max = max(bone_max, bones_count)

    use_morph_normals = use_normals and export_settings[
        gltf2_blender_export_keys.MORPH_NORMAL]
    use_morph_tangents = use_morph_normals and use_tangents and export_settings[
        gltf2_blender_export_keys.MORPH_TANGENT]

    shape_keys = []
    if blender_mesh.shape_keys and export_settings[
            gltf2_blender_export_keys.MORPH]:
        for blender_shape_key in blender_mesh.shape_keys.key_blocks:
            if blender_shape_key == blender_shape_key.relative_key or blender_shape_key.mute:
                continue

            split_normals = None
            if use_morph_normals:
                split_normals = blender_shape_key.normals_split_get()

            shape_keys.append(ShapeKey(
                blender_shape_key,
                split_normals,
            ))

    use_materials = export_settings[gltf2_blender_export_keys.MATERIALS]

    #
    # Gather the verts and indices for each primitive.
    #

    prims = {}

    blender_mesh.calc_loop_triangles()

    for loop_tri in blender_mesh.loop_triangles:
        blender_polygon = blender_mesh.polygons[loop_tri.polygon_index]

        material_idx = -1
        if use_materials:
            material_idx = blender_polygon.material_index

        prim = prims.get(material_idx)
        if not prim:
            prim = Prim()
            prims[material_idx] = prim

        for loop_index in loop_tri.loops:
            vertex_index = blender_mesh.loops[loop_index].vertex_index
            vertex = blender_mesh.vertices[vertex_index]

            # vert will be a tuple of all the vertex attributes.
            # Used as cache key in prim.verts.
            vert = (vertex_index, )

            v = vertex.co
            vert += ((v[0], v[1], v[2]), )

            if use_normals:
                n = blender_mesh.loops[loop_index].normal
                vert += ((n[0], n[1], n[2]), )
                if use_tangents:
                    t = blender_mesh.loops[loop_index].tangent
                    b = blender_mesh.loops[loop_index].bitangent
                    vert += ((t[0], t[1], t[2]), )
                    vert += ((b[0], b[1], b[2]), )
                    # TODO: store just bitangent_sign in vert, not whole bitangent?

            for tex_coord_index in range(0, tex_coord_max):
                uv = blender_mesh.uv_layers[tex_coord_index].data[
                    loop_index].uv
                uv = (uv.x, 1.0 - uv.y)
                vert += (uv, )

            for color_index in range(0, color_max):
                color = blender_mesh.vertex_colors[color_index].data[
                    loop_index].color
                col = (
                    color_srgb_to_scene_linear(color[0]),
                    color_srgb_to_scene_linear(color[1]),
                    color_srgb_to_scene_linear(color[2]),
                    color[3],
                )
                vert += (col, )

            if bone_max:
                bones = []
                if vertex.groups:
                    for group_element in vertex.groups:
                        weight = group_element.weight
                        if weight <= 0.0:
                            continue
                        try:
                            joint = group_to_joint[group_element.group]
                        except Exception:
                            continue
                        if joint is None:
                            continue
                        bones.append((joint, weight))
                bones.sort(key=lambda x: x[1], reverse=True)
                bones = tuple(bones)
                vert += (bones, )

            for shape_key in shape_keys:
                v_morph = shape_key.shape_key.data[vertex_index].co
                v_morph = v_morph - v  # store delta
                vert += ((v_morph[0], v_morph[1], v_morph[2]), )

                if use_morph_normals:
                    normals = shape_key.split_normals
                    n_morph = Vector(normals[loop_index * 3:loop_index * 3 +
                                             3])
                    n_morph = n_morph - n  # store delta
                    vert += ((n_morph[0], n_morph[1], n_morph[2]), )

            vert_idx = prim.verts.setdefault(vert, len(prim.verts))
            prim.indices.append(vert_idx)

    #
    # Put the verts into attribute arrays.
    #

    result_primitives = []

    for material_idx, prim in prims.items():
        if not prim.indices:
            continue

        vs = []
        ns = []
        ts = []
        uvs = [[] for _ in range(tex_coord_max)]
        cols = [[] for _ in range(color_max)]
        joints = [[] for _ in range(bone_max)]
        weights = [[] for _ in range(bone_max)]
        vs_morph = [[] for _ in shape_keys]
        ns_morph = [[] for _ in shape_keys]
        ts_morph = [[] for _ in shape_keys]

        for vert in prim.verts.keys():
            i = 0

            i += 1  # skip over Blender mesh index

            v = vert[i]
            i += 1
            v = convert_swizzle_location(v, armature, blender_object,
                                         export_settings)
            vs.extend(v)

            if use_normals:
                n = vert[i]
                i += 1
                n = convert_swizzle_normal(n, armature, blender_object,
                                           export_settings)
                ns.extend(n)

                if use_tangents:
                    t = vert[i]
                    i += 1
                    t = convert_swizzle_tangent(t, armature, blender_object,
                                                export_settings)
                    ts.extend(t)

                    b = vert[i]
                    i += 1
                    b = convert_swizzle_tangent(b, armature, blender_object,
                                                export_settings)
                    b_sign = -1.0 if (Vector(n).cross(Vector(t))).dot(
                        Vector(b)) < 0.0 else 1.0
                    ts.append(b_sign)

            for tex_coord_index in range(0, tex_coord_max):
                uv = vert[i]
                i += 1
                uvs[tex_coord_index].extend(uv)

            for color_index in range(0, color_max):
                col = vert[i]
                i += 1
                cols[color_index].extend(col)

            if bone_max:
                bones = vert[i]
                i += 1
                for j in range(0, 4 * bone_max):
                    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 shape_key_index in range(0, len(shape_keys)):
                v_morph = vert[i]
                i += 1
                v_morph = convert_swizzle_location(v_morph, armature,
                                                   blender_object,
                                                   export_settings)
                vs_morph[shape_key_index].extend(v_morph)

                if use_morph_normals:
                    n_morph = vert[i]
                    i += 1
                    n_morph = convert_swizzle_normal(n_morph, armature,
                                                     blender_object,
                                                     export_settings)
                    ns_morph[shape_key_index].extend(n_morph)

                if use_morph_tangents:
                    rotation = n_morph.rotation_difference(n)
                    t_morph = Vector(t)
                    t_morph.rotate(rotation)
                    ts_morph[shape_key_index].extend(t_morph)

        attributes = {}
        attributes['POSITION'] = vs
        if ns: attributes['NORMAL'] = ns
        if ts: attributes['TANGENT'] = ts
        for i, uv in enumerate(uvs):
            attributes['TEXCOORD_%d' % i] = uv
        for i, col in enumerate(cols):
            attributes['COLOR_%d' % i] = col
        for i, js in enumerate(joints):
            attributes['JOINTS_%d' % i] = js
        for i, ws in enumerate(weights):
            attributes['WEIGHTS_%d' % i] = ws
        for i, vm in enumerate(vs_morph):
            attributes['MORPH_POSITION_%d' % i] = vm
        for i, nm in enumerate(ns_morph):
            attributes['MORPH_NORMAL_%d' % i] = nm
        for i, tm in enumerate(ts_morph):
            attributes['MORPH_TANGENT_%d' % i] = tm

        result_primitives.append({
            'attributes': attributes,
            'indices': prim.indices,
            'material': material_idx,
        })

    print_console('INFO', 'Primitives created: %d' % len(result_primitives))

    return result_primitives