def __gather_indices(blender_primitive, blender_mesh, modifiers, export_settings): indices = blender_primitive.get('indices') if indices is None: return None # NOTE: Values used by some graphics APIs as "primitive restart" values are disallowed. # Specifically, the values 65535 (in UINT16) and 4294967295 (in UINT32) cannot be used as indices. # https://github.com/KhronosGroup/glTF/issues/1142 # https://github.com/KhronosGroup/glTF/pull/1476/files # Also, UINT8 mode is not supported: # https://github.com/KhronosGroup/glTF/issues/1471 max_index = indices.max() if max_index < 65535: component_type = gltf2_io_constants.ComponentType.UnsignedShort indices = indices.astype(np.uint16, copy=False) elif max_index < 4294967295: component_type = gltf2_io_constants.ComponentType.UnsignedInt indices = indices.astype(np.uint32, copy=False) else: print_console( 'ERROR', 'A mesh contains too many vertices (' + str(max_index) + ') and needs to be split before export.') return None element_type = gltf2_io_constants.DataType.Scalar binary_data = gltf2_io_binary_data.BinaryData(indices.tobytes()) return gltf2_blender_gather_accessors.gather_accessor( binary_data, component_type, len(indices), None, None, element_type, export_settings)
def __gather_colors(blender_primitive, export_settings): attributes = {} if export_settings[gltf2_blender_export_keys.COLORS]: color_index = 0 color_id = 'COLOR_' + str(color_index) while blender_primitive["attributes"].get(color_id) is not None: colors = blender_primitive["attributes"][color_id] if type(colors) is not np.ndarray: colors = np.array(colors, dtype=np.float32) colors = colors.reshape(len(colors) // 4, 4) # Convert to normalized ushorts colors *= 65535 colors += 0.5 # bias for rounding colors = colors.astype(np.uint16) attributes[color_id] = gltf2_io.Accessor( buffer_view=gltf2_io_binary_data.BinaryData(colors.tobytes()), byte_offset=None, component_type=gltf2_io_constants.ComponentType.UnsignedShort, count=len(colors), extensions=None, extras=None, max=None, min=None, name=None, normalized=True, sparse=None, type=gltf2_io_constants.DataType.Vec4, ) color_index += 1 color_id = 'COLOR_' + str(color_index) return attributes
def __gather_buffer_view(sockets_or_slots, export_settings): if export_settings['gltf_format'] != 'ASCII': image = __get_image_data(sockets_or_slots) return gltf2_io_binary_data.BinaryData( data=image.to_image_data(__gather_mime_type(sockets_or_slots, export_settings))) return None
def array_to_accessor(array, component_type, data_type, include_max_and_min=False): dtype = gltf2_io_constants.ComponentType.to_numpy_dtype(component_type) num_elems = gltf2_io_constants.DataType.num_elements(data_type) if type(array) is not np.ndarray: array = np.array(array, dtype=dtype) array = array.reshape(len(array) // num_elems, num_elems) assert array.dtype == dtype assert array.shape[1] == num_elems amax = None amin = None if include_max_and_min: amax = np.amax(array, axis=0).tolist() amin = np.amin(array, axis=0).tolist() return gltf2_io.Accessor( buffer_view=gltf2_io_binary_data.BinaryData(array.tobytes()), byte_offset=None, component_type=component_type, count=len(array), extensions=None, extras=None, max=amax, min=amin, name=None, normalized=None, sparse=None, type=data_type, )
def __gather_targets(blender_primitive, blender_object, export_settings): if export_settings['gltf_morph']: targets = [] blender_mesh = blender_object.data if blender_mesh.shape_keys is not None: morph_index = 0 for blender_shape_key in blender_mesh.shape_keys.key_blocks: if blender_shape_key != blender_shape_key.relative_key: target_position_id = 'MORPH_POSITION_' + str(morph_index) target_normal_id = 'MORPH_NORMAL_' + str(morph_index) target_tangent_id = 'MORPH_TANGENT_' + str(morph_index) if blender_primitive["attributes"].get(target_position_id): target = {} internal_target_position = blender_primitive[ "attributes"][target_position_id] target["POSITION"] = gltf2_io_binary_data.BinaryData( internal_target_position, gltf2_io_constants.GLTF_COMPONENT_TYPE_FLOAT, gltf2_io_constants.GLTF_DATA_TYPE_VEC3, group_label="primitives_targets") if export_settings['gltf_normals'] \ and export_settings['gltf_morph_normal'] \ and blender_primitive["attributes"].get(target_normal_id): internal_target_normal = blender_primitive[ "attributes"][target_normal_id] target['NORMAL'] = gltf2_io_binary_data.BinaryData( internal_target_normal, gltf2_io_constants.GLTF_COMPONENT_TYPE_FLOAT, gltf2_io_constants.GLTF_DATA_TYPE_VEC3, group_label="primitives_targets") if export_settings['gltf_tangents'] \ and export_settings['gltf_morph_tangent'] \ and blender_primitive["attributes"].get(target_tangent_id): internal_target_tangent = blender_primitive[ "attributes"][target_tangent_id] target['TANGENT'] = gltf2_io_binary_data.BinaryData( internal_target_tangent, gltf2_io_constants.GLTF_COMPONENT_TYPE_FLOAT, gltf2_io_constants.GLTF_DATA_TYPE_VEC3, group_label="primitives_targets") targets.append(target) morph_index += 1 return targets return None
def __gather_buffer_view(sockets_or_slots, export_settings): if export_settings[gltf2_blender_export_keys.FORMAT] != 'GLTF_SEPARATE': image = __get_image_data(sockets_or_slots, export_settings) if image is None: return None return gltf2_io_binary_data.BinaryData( data=image.to_image_data(__gather_mime_type())) return None
def __gather_position(blender_primitive, export_settings): position = blender_primitive["attributes"]["POSITION"] componentType = gltf2_io_constants.GLTF_COMPONENT_TYPE_FLOAT return { "POSITION": gltf2_io_binary_data.BinaryData( position, componentType, gltf2_io_constants.GLTF_DATA_TYPE_VEC3, group_label="primitives_attributes" ) }
def __gather_normal(blender_primitive, export_settings): if export_settings['gltf_normals']: normal = blender_primitive["attributes"]['NORMAL'] return { "NORMAL": gltf2_io_binary_data.BinaryData( normal, gltf2_io_constants.GLTF_COMPONENT_TYPE_FLOAT, gltf2_io_constants.GLTF_DATA_TYPE_VEC3, group_label="primitives_attributes" ) } return {}
def __gather_tangent(blender_primitive, export_settings): if export_settings['gltf_tangents']: if blender_primitive["attributes"].get('TANGENT') is not None: tangent = blender_primitive["attributes"]['TANGENT'] return { "TANGENT": gltf2_io_binary_data.BinaryData( tangent, gltf2_io_constants.GLTF_COMPONENT_TYPE_FLOAT, gltf2_io_constants.GLTF_DATA_TYPE_VEC3, group_label="primitives_attributes" ) } return {}
def __gather_skins(blender_primitive, export_settings): attributes = {} if export_settings['gltf_skins']: bone_index = 0 joint_id = 'JOINTS_' + str(bone_index) weight_id = 'WEIGHTS_' + str(bone_index) while blender_primitive["attributes"].get(joint_id) and blender_primitive["attributes"].get(weight_id): # joints internal_joint = blender_primitive["attributes"][joint_id] joint = gltf2_io_binary_data.BinaryData( internal_joint, gltf2_io_constants.GLTF_COMPONENT_TYPE_UNSIGNED_SHORT, gltf2_io_constants.GLTF_DATA_TYPE_VEC4, group_label="primitives_attributes" ), if joint < 0: # gltf2_io_debug('ERROR', 'Could not create accessor for ' + joint_id) break attributes[joint_id] = joint # weights internal_weight = blender_primitive["attributes"][weight_id] weight = gltf2_io_binary_data.BinaryData( internal_weight, gltf2_io_constants.GLTF_COMPONENT_TYPE_FLOAT, gltf2_io_constants.GLTF_DATA_TYPE_VEC4, group_label="primitives_attributes" ) if weight < 0: # print_console('ERROR', 'Could not create accessor for ' + weight_id) break attributes[weight_id] = weight bone_index += 1 joint_id = 'JOINTS_' + str(bone_index) weight_id = 'WEIGHTS_' + str(bone_index) return attributes
def __gather_colors(blender_primitive, export_settings): attributes = {} if export_settings['gltf_colors']: color_index = 0 color_id = 'COLOR_' + str(color_index) while blender_primitive["attributes"].get(color_id) is not None: internal_color = blender_primitive["attributes"][color_id] attributes[color_id] = gltf2_io_binary_data.BinaryData( internal_color, gltf2_io_constants.GLTF_COMPONENT_TYPE_FLOAT, gltf2_io_constants.GLTF_DATA_TYPE_VEC4, group_label="primitives_attributes" ) color_index += 1 color_id = 'COLOR_' + str(color_index) return attributes
def __gather_texcoord(blender_primitive, export_settings): attributes = {} if export_settings['gltf_texcoords']: texcoord_index = 0 texcoord_id = 'TEXCOORD_' + str(texcoord_index) while blender_primitive["attributes"].get(texcoord_id) is not None: texcoord = blender_primitive["attributes"][texcoord_id] attributes[texcoord_id] = gltf2_io_binary_data.BinaryData( texcoord, gltf2_io_constants.GLTF_COMPONENT_TYPE_FLOAT, gltf2_io_constants.GLTF_DATA_TYPE_VEC2, group_label="primitives_attributes" ) texcoord_index += 1 texcoord_id = 'TEXCOORD_' + str(texcoord_index) return attributes
def __get_keyframe_accessor(frame_start, frame_end, frame_step): # Gets an accessor for a range of keyframes. Used for sampler.input. fps = bpy.context.scene.render.fps keyframes = [ frame / fps for frame in range(frame_start, frame_end, frame_step) ] keyframe_data = array.array('f', keyframes).tobytes() return gltf2_io.Accessor( buffer_view=gltf2_io_binary_data.BinaryData(keyframe_data), component_type=gltf2_io_constants.ComponentType.Float, type=gltf2_io_constants.DataType.Scalar, count=len(keyframes), min=[keyframes[0]], max=[keyframes[-1]], byte_offset=None, extensions=None, extras=None, name='accessorAnimationInput', normalized=None, sparse=None, )
def __gather_inverse_bind_matrices(blender_object, export_settings): inverse_matrices = [] axis_basis_change = mathutils.Matrix.Identity(4) if export_settings['gltf_yup']: axis_basis_change = mathutils.Matrix( ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0))) for blender_bone in blender_object.pose.bones: inverse_bind_matrix = axis_basis_change * blender_bone.bone.matrix_local bind_shape_matrix = axis_basis_change * blender_object.matrix_world.inverted() * axis_basis_change.inverted() inverse_bind_matrix = inverse_bind_matrix.inverted() * bind_shape_matrix for column in range(0, 4): for row in range(0, 4): inverse_matrices.append(inverse_bind_matrix[row][column]) return gltf2_io_binary_data.BinaryData( data=inverse_matrices, component_type=gltf2_io_constants.GLTF_COMPONENT_TYPE_FLOAT, element_type=gltf2_io_constants.GLTF_DATA_TYPE_MAT4, group_label='inverse_bind_matrices' )
def __gather_indices(blender_primitive, export_settings): indices = blender_primitive['indices'] max_index = max(indices) if max_index < 256: component_type = gltf2_io_constants.GLTF_COMPONENT_TYPE_UNSIGNED_BYTE elif max_index < 65536: component_type = gltf2_io_constants.GLTF_COMPONENT_TYPE_UNSIGNED_SHORT elif max_index < 4294967296: component_type = gltf2_io_constants.GLTF_COMPONENT_TYPE_UNSIGNED_INT else: gltf2_io_debug.print_console('ERROR', 'Invalid max_index: ' + str(max_index)) return None if export_settings['gltf_force_indices']: component_type = export_settings['gltf_indices'] element_type = gltf2_io_constants.GLTF_DATA_TYPE_SCALAR return gltf2_io_binary_data.BinaryData(indices, component_type, element_type, group_label="primitive_indices")
def __encode_output_accessor(values, path): # Encodes a list of T, R, or S (Vector/Quaternion) values to an accessor. vals = [x for val in values for x in val] vals_data = array.array('f', vals).tobytes() name = { 'translation': 'accessorAnimationPositions', 'rotation': 'accessorAnimationRotations', 'scale': 'accessorAnimationScales' }.get(path) return gltf2_io.Accessor( buffer_view=gltf2_io_binary_data.BinaryData(vals_data), component_type=gltf2_io_constants.ComponentType.Float, type=gltf2_io_constants.DataType.vec_type_from_num(len(values[0])), count=len(values), min=None, max=None, byte_offset=None, extensions=None, extras=None, name=name, normalized=None, sparse=None, )
def custom_data_array_to_accessor(array): if type(array) is not np.ndarray: array = np.array(array, dtype=np.float32) assert array.dtype == np.float32 assert len(array.shape) == 1 # Calculate how big a sparse array would be and switch to it if smaller indices_nonzero = np.nonzero(array)[0] num_nonzero = len(indices_nonzero) if num_nonzero == 0: return gltf2_io.Accessor( count=len(array), component_type=gltf2_io_constants.ComponentType.Float, type=gltf2_io_constants.DataType.Scalar, buffer_view=None, byte_offset=None, extensions=None, extras=None, max=None, min=None, name=None, normalized=None, sparse=None, ) index_size = (1 if indices_nonzero[-1] < 256 else 2 if indices_nonzero[-1] < 65536 else 4) value_size = 4 # float32 dense_bin_size = len(array) * value_size sparse_bin_size = num_nonzero * (index_size + value_size) bin_size_increase = sparse_bin_size - dense_bin_size json_size_increase = 160 # approximate net_size_increase = bin_size_increase + json_size_increase if net_size_increase >= 0: # Dense is better return array_to_accessor( array, component_type=gltf2_io_constants.ComponentType.Float, data_type=gltf2_io_constants.DataType.Scalar, ) index_type = (gltf2_io_constants.ComponentType.UnsignedByte if index_size == 1 else gltf2_io_constants.ComponentType.UnsignedShort if index_size == 2 else gltf2_io_constants.ComponentType.UnsignedInt) index_dtype = gltf2_io_constants.ComponentType.to_numpy_dtype(index_type) indices_nonzero = indices_nonzero.astype(index_dtype) values_nonzero = array[indices_nonzero] return gltf2_io.Accessor( buffer_view=None, byte_offset=None, component_type=gltf2_io_constants.ComponentType.Float, count=len(array), extensions=None, extras=None, max=None, min=None, name=None, normalized=None, type=gltf2_io_constants.DataType.Scalar, sparse=gltf2_io.AccessorSparse( count=num_nonzero, indices=gltf2_io.AccessorSparseIndices( buffer_view=gltf2_io_binary_data.BinaryData( indices_nonzero.tobytes()), component_type=index_type, byte_offset=None, extensions=None, extras=None, ), values=gltf2_io.AccessorSparseValues( buffer_view=gltf2_io_binary_data.BinaryData( values_nonzero.tobytes()), byte_offset=None, extensions=None, extras=None, ), extensions=None, extras=None, ), )
def __gather_buffer_view(image_data: bytes, export_settings): if export_settings[gltf2_blender_export_keys.FORMAT] != 'GLTF_SEPARATE': return gltf2_io_binary_data.BinaryData(data=image_data) return None
def __gather_buffer_view(image_data, mime_type, name, export_settings): if export_settings[gltf2_blender_export_keys.FORMAT] != 'GLTF_SEPARATE': return gltf2_io_binary_data.BinaryData(data=image_data.encode(mime_type)) return None