glTF importer/exporter: Draco decoder + encoder fixes

We can now read Draco compressed files.
This also fix exporting vertex color Draco compressed files. Fix #T75550
This commit is contained in:
Julien Duroure 2021-01-04 20:55:25 +01:00
parent 48ec56bde5
commit eb29a12da4
Notes: blender-bot 2023-02-14 18:57:48 +01:00
Referenced by issue #75550, Exporting gltf with draco compression messes up the vertex color
7 changed files with 303 additions and 282 deletions

View File

@ -15,7 +15,7 @@
bl_info = {
'name': 'glTF 2.0 format',
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
"version": (1, 5, 11),
"version": (1, 5, 12),
'blender': (2, 91, 0),
'location': 'File > Import-Export',
'description': 'Import-Export as glTF 2.0',
@ -91,7 +91,7 @@ class ExportGLTF2_Base:
# TODO: refactor to avoid boilerplate
def __init__(self):
from io_scene_gltf2.io.exp import gltf2_io_draco_compression_extension
from io_scene_gltf2.io.com import gltf2_io_draco_compression_extension
self.is_draco_available = gltf2_io_draco_compression_extension.dll_exists()
bl_options = {'PRESET'}
@ -202,6 +202,14 @@ class ExportGLTF2_Base:
max=30
)
export_draco_color_quantization: IntProperty(
name='Color quantization bits',
description='Quantization bits for color values (0 = no quantization)',
default=10,
min=0,
max=30
)
export_draco_generic_quantization: IntProperty(
name='Generic quantization bits',
description='Quantization bits for generic coordinate values like weights or joints (0 = no quantization)',
@ -473,6 +481,7 @@ class ExportGLTF2_Base:
export_settings['gltf_draco_position_quantization'] = self.export_draco_position_quantization
export_settings['gltf_draco_normal_quantization'] = self.export_draco_normal_quantization
export_settings['gltf_draco_texcoord_quantization'] = self.export_draco_texcoord_quantization
export_settings['gltf_draco_color_quantization'] = self.export_draco_color_quantization
export_settings['gltf_draco_generic_quantization'] = self.export_draco_generic_quantization
else:
export_settings['gltf_draco_mesh_compression'] = False
@ -692,7 +701,7 @@ class GLTF_PT_export_geometry_compression(bpy.types.Panel):
bl_options = {'DEFAULT_CLOSED'}
def __init__(self):
from io_scene_gltf2.io.exp import gltf2_io_draco_compression_extension
from io_scene_gltf2.io.com import gltf2_io_draco_compression_extension
self.is_draco_available = gltf2_io_draco_compression_extension.dll_exists(quiet=True)
@classmethod
@ -721,7 +730,8 @@ class GLTF_PT_export_geometry_compression(bpy.types.Panel):
col = layout.column(align=True)
col.prop(operator, 'export_draco_position_quantization', text="Quantize Position")
col.prop(operator, 'export_draco_normal_quantization', text="Normal")
col.prop(operator, 'export_draco_texcoord_quantization', text="Tex Coords")
col.prop(operator, 'export_draco_texcoord_quantization', text="Tex Coord")
col.prop(operator, 'export_draco_color_quantization', text="Color")
col.prop(operator, 'export_draco_generic_quantization', text="Generic")

View File

@ -76,7 +76,7 @@ def __gather_gltf(exporter, export_settings):
active_scene_idx, scenes, animations = plan['active_scene_idx'], plan['scenes'], plan['animations']
if export_settings['gltf_draco_mesh_compression']:
gltf2_io_draco_compression_extension.compress_scene_primitives(scenes, export_settings)
gltf2_io_draco_compression_extension.encode_scene_primitives(scenes, export_settings)
exporter.add_draco_extension()
for idx, scene in enumerate(scenes):

View File

@ -19,6 +19,8 @@ import numpy as np
from ...io.imp.gltf2_io_binary import BinaryData
from ..com.gltf2_blender_extras import set_extras
from .gltf2_blender_material import BlenderMaterial
from ...io.com.gltf2_io_debug import print_console
from .gltf2_io_draco_compression_extension import decode_primitive
class BlenderMesh():
@ -134,6 +136,10 @@ def do_primitives(gltf, mesh_idx, skin_idx, mesh, ob):
vert_index_base = len(vert_locs)
if prim.extensions is not None and 'KHR_draco_mesh_compression' in prim.extensions:
print_console('INFO', 'Draco Decoder: Decode primitive {}'.format(pymesh.name or '[unnamed]'))
decode_primitive(gltf, prim)
if prim.indices is not None:
indices = BinaryData.decode_accessor(gltf, prim.indices)
indices = indices.reshape(len(indices))

View File

@ -0,0 +1,143 @@
# Copyright 2018-2019 The glTF-Blender-IO authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from ctypes import *
from io_scene_gltf2.io.com.gltf2_io import BufferView
from io_scene_gltf2.io.imp.gltf2_io_binary import BinaryData
from ...io.com.gltf2_io_debug import print_console
from io_scene_gltf2.io.com.gltf2_io_draco_compression_extension import dll_path
def decode_primitive(gltf, prim):
"""
Handles draco compression.
Moves decoded data into new buffers and buffer views held by the accessors of the given primitive.
"""
# Load DLL and setup function signatures.
dll = cdll.LoadLibrary(str(dll_path().resolve()))
dll.decoderCreate.restype = c_void_p
dll.decoderCreate.argtypes = []
dll.decoderRelease.restype = None
dll.decoderRelease.argtypes = [c_void_p]
dll.decoderDecode.restype = c_bool
dll.decoderDecode.argtypes = [c_void_p, c_void_p, c_size_t]
dll.decoderReadAttribute.restype = c_bool
dll.decoderReadAttribute.argtypes = [c_void_p, c_uint32, c_size_t, c_char_p]
dll.decoderGetVertexCount.restype = c_uint32
dll.decoderGetVertexCount.argtypes = [c_void_p]
dll.decoderGetIndexCount.restype = c_uint32
dll.decoderGetIndexCount.argtypes = [c_void_p]
dll.decoderAttributeIsNormalized.restype = c_bool
dll.decoderAttributeIsNormalized.argtypes = [c_void_p, c_uint32]
dll.decoderGetAttributeByteLength.restype = c_size_t
dll.decoderGetAttributeByteLength.argtypes = [c_void_p, c_uint32]
dll.decoderCopyAttribute.restype = None
dll.decoderCopyAttribute.argtypes = [c_void_p, c_uint32, c_void_p]
dll.decoderReadIndices.restype = c_bool
dll.decoderReadIndices.argtypes = [c_void_p, c_size_t]
dll.decoderGetIndicesByteLength.restype = c_size_t
dll.decoderGetIndicesByteLength.argtypes = [c_void_p]
dll.decoderCopyIndices.restype = None
dll.decoderCopyIndices.argtypes = [c_void_p, c_void_p]
decoder = dll.decoderCreate()
extension = prim.extensions['KHR_draco_mesh_compression']
name = prim.name if hasattr(prim, 'name') else '[unnamed]'
# Create Draco decoder.
draco_buffer = bytes(BinaryData.get_buffer_view(gltf, extension['bufferView']))
if not dll.decoderDecode(decoder, draco_buffer, len(draco_buffer)):
print_console('ERROR', 'Draco Decoder: Unable to decode. Skipping primitive {}.'.format(name))
return
# Choose a buffer index which does not yet exist, skipping over existing glTF buffers yet to be loaded
# and buffers which were generated and did not exist in the initial glTF file, like this decoder does.
base_buffer_idx = len(gltf.data.buffers)
for existing_buffer_idx in gltf.buffers:
if base_buffer_idx <= existing_buffer_idx:
base_buffer_idx = existing_buffer_idx + 1
# Read indices.
index_accessor = gltf.data.accessors[prim.indices]
if dll.decoderGetIndexCount(decoder) != index_accessor.count:
print_console('WARNING', 'Draco Decoder: Index count of accessor and decoded index count does not match. Updating accessor.')
index_accessor.count = dll.decoderGetIndexCount(decoder)
if not dll.decoderReadIndices(decoder, index_accessor.component_type):
print_console('ERROR', 'Draco Decoder: Unable to decode indices. Skipping primitive {}.'.format(name))
return
indices_byte_length = dll.decoderGetIndicesByteLength(decoder)
decoded_data = bytes(indices_byte_length)
dll.decoderCopyIndices(decoder, decoded_data)
# Generate a new buffer holding the decoded indices.
gltf.buffers[base_buffer_idx] = decoded_data
# Create a buffer view referencing the new buffer.
gltf.data.buffer_views.append(BufferView.from_dict({
'buffer': base_buffer_idx,
'byteLength': indices_byte_length
}))
# Update accessor to point to the new buffer view.
index_accessor.buffer_view = len(gltf.data.buffer_views) - 1
# Read each attribute.
for attr_idx, attr in enumerate(extension['attributes']):
dracoId = extension['attributes'][attr]
if attr not in prim.attributes:
print_console('ERROR', 'Draco Decoder: Draco attribute {} not in primitive attributes. Skipping primitive {}.'.format(attr, name))
return
accessor = gltf.data.accessors[prim.attributes[attr]]
if dll.decoderGetVertexCount(decoder) != accessor.count:
print_console('WARNING', 'Draco Decoder: Vertex count of accessor and decoded vertex count does not match for attribute {}. Updating accessor.'.format(attr, name))
accessor.count = dll.decoderGetVertexCount(decoder)
if not dll.decoderReadAttribute(decoder, dracoId, accessor.component_type, accessor.type.encode()):
print_console('ERROR', 'Draco Decoder: Could not decode attribute {}. Skipping primitive {}.'.format(attr, name))
return
byte_length = dll.decoderGetAttributeByteLength(decoder, dracoId)
decoded_data = bytes(byte_length)
dll.decoderCopyAttribute(decoder, dracoId, decoded_data)
# Generate a new buffer holding the decoded vertex data.
buffer_idx = base_buffer_idx + 1 + attr_idx
gltf.buffers[buffer_idx] = decoded_data
# Create a buffer view referencing the new buffer.
gltf.data.buffer_views.append(BufferView.from_dict({
'buffer': buffer_idx,
'byteLength': byte_length
}))
# Update accessor to point to the new buffer view.
accessor.buffer_view = len(gltf.data.buffer_views) - 1
dll.decoderRelease(decoder)

View File

@ -0,0 +1,64 @@
# Copyright 2018-2019 The glTF-Blender-IO authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
from pathlib import Path
import bpy
from ...io.com.gltf2_io_debug import print_console
def dll_path() -> Path:
"""
Get the DLL path depending on the underlying platform.
:return: DLL path.
"""
lib_name = 'extern_draco'
blender_root = Path(bpy.app.binary_path).parent
python_lib = Path('{v[0]}.{v[1]}/python/lib'.format(v=bpy.app.version))
python_version = 'python{v[0]}.{v[1]}'.format(v=sys.version_info)
path = os.environ.get('BLENDER_EXTERN_DRACO_LIBRARY_PATH')
if path is None:
path = {
'win32': blender_root / python_lib / 'site-packages',
'linux': blender_root / python_lib / python_version / 'site-packages',
'darwin': blender_root.parent / 'Resources' / python_lib / python_version / 'site-packages'
}.get(sys.platform)
else:
path = Path(path)
library_name = {
'win32': '{}.dll'.format(lib_name),
'linux': 'lib{}.so'.format(lib_name),
'darwin': 'lib{}.dylib'.format(lib_name)
}.get(sys.platform)
if path is None or library_name is None:
print_console('WARNING', 'Unsupported platform {}, Draco mesh compression is unavailable'.format(sys.platform))
return path / library_name
def dll_exists(quiet=False) -> bool:
"""
Checks whether the DLL path exists.
:return: True if the DLL exists.
"""
exists = dll_path().exists()
if quiet is False:
print("'{}' ".format(dll_path().absolute()) + ("exists, draco mesh compression is available" if exists else
"does not exist, draco mesh compression not available"))
return exists

View File

@ -12,334 +12,131 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import bpy
import sys
from ctypes import c_void_p, c_uint32, c_uint64, c_bool, c_char_p, cdll
from ctypes import *
from pathlib import Path
import struct
from io_scene_gltf2.io.exp.gltf2_io_binary_data import BinaryData
from ...io.com.gltf2_io_debug import print_console
from io_scene_gltf2.io.com.gltf2_io_draco_compression_extension import dll_path
def dll_path() -> Path:
"""
Get the DLL path depending on the underlying platform.
:return: DLL path.
"""
lib_name = 'extern_draco'
blender_root = Path(bpy.app.binary_path).parent
python_lib = Path("{v[0]}.{v[1]}/python/lib".format(v=bpy.app.version))
python_version = "python{v[0]}.{v[1]}".format(v=sys.version_info)
paths = {
'win32': blender_root/python_lib/'site-packages'/'{}.dll'.format(lib_name),
'linux': blender_root/python_lib/python_version/'site-packages'/'lib{}.so'.format(lib_name),
'darwin': blender_root.parent/'Resources'/python_lib/python_version/'site-packages'/'lib{}.dylib'.format(lib_name)
}
path = paths.get(sys.platform)
return path if path is not None else ''
def dll_exists(quiet=False) -> bool:
"""
Checks whether the DLL path exists.
:return: True if the DLL exists.
"""
exists = dll_path().exists()
if quiet is False:
print("'{}' ".format(dll_path().absolute()) + ("exists, draco mesh compression is available" if exists else
"does not exist, draco mesh compression not available"))
return exists
def compress_scene_primitives(scenes, export_settings):
def encode_scene_primitives(scenes, export_settings):
"""
Handles draco compression.
Invoked after data has been gathered, but before scenes get traversed.
Moves position, normal and texture coordinate attributes into a Draco compressed buffer.
Moves position, normal and texture coordinate attributes into a Draco encoded buffer.
"""
# Load DLL and setup function signatures.
# Nearly all functions take the compressor as the first argument.
dll = cdll.LoadLibrary(str(dll_path().resolve()))
# Initialization:
dll.encoderCreate.restype = c_void_p
dll.encoderCreate.argtypes = [c_uint32]
dll.create_compressor.restype = c_void_p
dll.create_compressor.argtypes = []
dll.encoderRelease.restype = None
dll.encoderRelease.argtypes = [c_void_p]
dll.destroy_compressor.restype = None
dll.destroy_compressor.argtypes = [c_void_p]
dll.encoderSetCompressionLevel.restype = None
dll.encoderSetCompressionLevel.argtypes = [c_void_p, c_uint32]
# Configuration:
dll.encoderSetQuantizationBits.restype = None
dll.encoderSetQuantizationBits.argtypes = [c_void_p, c_uint32, c_uint32, c_uint32, c_uint32, c_uint32]
dll.encoderSetIndices.restype = None
dll.encoderSetIndices.argtypes = [c_void_p, c_size_t, c_uint32, c_void_p]
dll.set_compression_level.restype = None
dll.set_compression_level.argtypes = [c_void_p, c_uint32]
dll.encoderSetAttribute.restype = c_uint32
dll.encoderSetAttribute.argtypes = [c_void_p, c_char_p, c_size_t, c_char_p, c_void_p]
dll.set_position_quantization.restype = None
dll.set_position_quantization.argtypes = [c_void_p, c_uint32]
dll.encoderEncode.restype = c_bool
dll.encoderEncode.argtypes = [c_void_p, c_uint8]
dll.set_normal_quantization.restype = None
dll.set_normal_quantization.argtypes = [c_void_p, c_uint32]
dll.encoderGetEncodedVertexCount.restype = c_uint32
dll.encoderGetEncodedVertexCount.argtypes = [c_void_p]
dll.set_uv_quantization.restype = None
dll.set_uv_quantization.argtypes = [c_void_p, c_uint32]
dll.encoderGetEncodedIndexCount.restype = c_uint32
dll.encoderGetEncodedIndexCount.argtypes = [c_void_p]
dll.set_generic_quantization.restype = None
dll.set_generic_quantization.argtypes = [c_void_p, c_uint32]
dll.encoderGetByteLength.restype = c_uint64
dll.encoderGetByteLength.argtypes = [c_void_p]
# Data transfer:
dll.encoderCopy.restype = None
dll.encoderCopy.argtypes = [c_void_p, c_void_p]
dll.set_faces.restype = None
dll.set_faces.argtypes = [
c_void_p, # Compressor
c_uint32, # Index count
c_uint32, # Index byte length
c_char_p # Indices
]
add_attribute_fn_restype = c_uint32 # Draco id
add_attribute_fn_argtypes = [
c_void_p, # Compressor
c_uint32, # Attribute count
c_char_p # Values
]
dll.add_positions_f32.restype = add_attribute_fn_restype
dll.add_positions_f32.argtypes = add_attribute_fn_argtypes
dll.add_normals_f32.restype = add_attribute_fn_restype
dll.add_normals_f32.argtypes = add_attribute_fn_argtypes
dll.add_uvs_f32.restype = add_attribute_fn_restype
dll.add_uvs_f32.argtypes = add_attribute_fn_argtypes
dll.add_weights_f32.restype = add_attribute_fn_restype
dll.add_weights_f32.argtypes = add_attribute_fn_argtypes
dll.add_joints_u16.restype = add_attribute_fn_restype
dll.add_joints_u16.argtypes = add_attribute_fn_argtypes
# Compression:
dll.compress.restype = c_bool
dll.compress.argtypes = [
c_void_p # Compressor
]
dll.compress_morphed.restype = c_bool
dll.compress_morphed.argtypes = [
c_void_p # Compressor
]
dll.get_compressed_size.restype = c_uint64
dll.get_compressed_size.argtypes = [
c_void_p # Compressor
]
dll.copy_to_bytes.restype = None
dll.copy_to_bytes.argtypes = [
c_void_p, # Compressor
c_char_p # Destination pointer
]
# Traverse nodes.
for scene in scenes:
for node in scene.nodes:
__traverse_node(node, lambda node: __compress_node(node, dll, export_settings))
__traverse_node(node, lambda node: __encode_node(node, dll, export_settings))
# Cleanup memory.
# May be shared amongst nodes because of non-unique primitive parents, so memory
# release happens delayed.
for scene in scenes:
for node in scene.nodes:
__traverse_node(node, __dispose_memory)
def __dispose_memory(node):
"""Remove buffers from attribute, since the data now resides inside the compressed Draco buffer."""
if not (node.mesh is None):
for primitive in node.mesh.primitives:
# Drop indices.
primitive.indices.buffer_view = None
# Drop attributes.
attributes = primitive.attributes
if 'NORMAL' in attributes:
attributes['NORMAL'].buffer_view = None
for attribute in [attributes[attr] for attr in attributes if attr.startswith('TEXCOORD_')]:
attribute.buffer_view = None
def __compress_node(node, dll, export_settings):
"""Compress a single node."""
if not (node.mesh is None):
print_console('INFO', 'Draco exporter: Compressing mesh "%s".' % node.name)
for primitive in node.mesh.primitives:
__compress_primitive(primitive, dll, export_settings)
def __traverse_node(node, f):
"""Calls f for each node and all child nodes, recursively."""
f(node)
if not (node.children is None):
for child in node.children:
__traverse_node(child, f)
def __compress_primitive(primitive, dll, export_settings):
def __encode_node(node, dll, export_settings):
if not (node.mesh is None):
print_console('INFO', 'Draco encoder: Encoding mesh {}.'.format(node.name))
for primitive in node.mesh.primitives:
__encode_primitive(primitive, dll, export_settings)
def __encode_primitive(primitive, dll, export_settings):
attributes = primitive.attributes
indices = primitive.indices
# Maps component types to their byte length.
component_type_byte_length = {
'Byte': 1,
'UnsignedByte': 1,
'Short': 2,
'UnsignedShort': 2,
'UnsignedInt': 4,
}
# Positions are the only attribute type required to be present.
if 'POSITION' not in attributes:
print_console('WARNING', 'Draco exporter: Primitive without positions encountered. Skipping.')
print_console('WARNING', 'Draco encoder: Primitive without positions encountered. Skipping.')
return
positions = attributes['POSITION']
# Skip nodes without a position buffer.
# This happens with Blender instances, i.e. multiple nodes sharing the same mesh.
# Skip nodes without a position buffer, e.g. a primitive from a Blender shared instance.
if attributes['POSITION'].buffer_view is None:
return
normals = attributes['NORMAL'] if 'NORMAL' in attributes else None
uvs = [attributes[attr] for attr in attributes if attr.startswith('TEXCOORD_')]
weights = [attributes[attr] for attr in attributes if attr.startswith('WEIGHTS_')]
joints = [attributes[attr] for attr in attributes if attr.startswith('JOINTS_')]
encoder = dll.encoderCreate(positions.count)
print_console('INFO', 'Draco exporter: %s normals, %d uvs, %d weights, %d joints' %
('without' if normals is None else 'with', len(uvs), len(weights), len(joints)))
draco_ids = {}
for attr_name in attributes:
attr = attributes[attr_name]
draco_id = dll.encoderSetAttribute(encoder, attr_name.encode(), attr.component_type, attr.type.encode(), attr.buffer_view.data)
draco_ids[attr_name] = draco_id
attr.buffer_view = None
# Begin mesh.
compressor = dll.create_compressor()
dll.encoderSetIndices(encoder, indices.component_type, indices.count, indices.buffer_view.data)
indices.buffer_view = None
# Each attribute must have the same count of elements.
count = positions.count
dll.encoderSetCompressionLevel(encoder, export_settings['gltf_draco_mesh_compression_level'])
dll.encoderSetQuantizationBits(encoder,
export_settings['gltf_draco_position_quantization'],
export_settings['gltf_draco_normal_quantization'],
export_settings['gltf_draco_texcoord_quantization'],
export_settings['gltf_draco_color_quantization'],
export_settings['gltf_draco_generic_quantization'])
if not dll.encoderEncode(encoder, primitive.targets is not None and len(primitive.targets) > 0):
print_console('ERROR', 'Could not encode primitive. Skipping primitive.')
# Add attributes to mesh compressor, remembering each attribute's Draco id.
byte_length = dll.encoderGetByteLength(encoder)
encoded_data = bytes(byte_length)
dll.encoderCopy(encoder, encoded_data)
position_id = dll.add_positions_f32(compressor, count, positions.buffer_view.data)
if primitive.extensions is None:
primitive.extensions = {}
primitive.extensions['KHR_draco_mesh_compression'] = {
'bufferView': BinaryData(encoded_data),
'attributes': draco_ids
}
normal_id = None
if normals is not None:
if normals.count != count:
print_console('INFO', 'Draco exporter: Mismatching normal count. Skipping.')
dll.disposeCompressor(compressor)
return
normal_id = dll.add_normals_f32(compressor, normals.count, normals.buffer_view.data)
# Set to triangle list mode.
primitive.mode = 4
uv_ids = []
for uv in uvs:
if uv.count != count:
print_console('INFO', 'Draco exporter: Mismatching uv count. Skipping.')
dll.disposeCompressor(compressor)
return
uv_ids.append(dll.add_uvs_f32(compressor, uv.count, uv.buffer_view.data))
# Update accessors to match encoded data.
indices.count = dll.encoderGetEncodedIndexCount(encoder)
encoded_vertices = dll.encoderGetEncodedVertexCount(encoder)
for attr_name in attributes:
attributes[attr_name].count = encoded_vertices
weight_ids = []
for weight in weights:
if weight.count != count:
print_console('INFO', 'Draco exporter: Mismatching weight count. Skipping.')
dll.disposeCompressor(compressor)
return
weight_ids.append(dll.add_weights_f32(compressor, weight.count, weight.buffer_view.data))
joint_ids = []
for joint in joints:
if joint.count != count:
print_console('INFO', 'Draco exporter: Mismatching joint count. Skipping.')
dll.disposeCompressor(compressor)
return
joint_ids.append(dll.add_joints_u16(compressor, joint.count, joint.buffer_view.data))
# Add face indices to mesh compressor.
dll.set_faces(compressor, indices.count, component_type_byte_length[indices.component_type.name], indices.buffer_view.data)
# Set compression parameters.
dll.set_compression_level(compressor, export_settings['gltf_draco_mesh_compression_level'])
dll.set_position_quantization(compressor, export_settings['gltf_draco_position_quantization'])
dll.set_normal_quantization(compressor, export_settings['gltf_draco_normal_quantization'])
dll.set_uv_quantization(compressor, export_settings['gltf_draco_texcoord_quantization'])
dll.set_generic_quantization(compressor, export_settings['gltf_draco_generic_quantization'])
compress_fn = dll.compress if not primitive.targets else dll.compress_morphed
# After all point and connectivity data has been written to the compressor,
# it can finally be compressed.
if dll.compress(compressor):
# Compression was successful.
# Move compressed data into a bytes object,
# which is referenced by a 'gltf2_io_binary_data.BinaryData':
#
# "KHR_draco_mesh_compression": {
# ....
# "buffer_view": Compressed data inside a 'gltf2_io_binary_data.BinaryData'.
# }
# Query size necessary to hold all the compressed data.
compression_size = dll.get_compressed_size(compressor)
# Allocate byte buffer and write compressed data to it.
compressed_data = bytes(compression_size)
dll.copy_to_bytes(compressor, compressed_data)
if primitive.extensions is None:
primitive.extensions = {}
# Write Draco extension into primitive, including attribute ids:
extension = {
'bufferView': BinaryData(compressed_data),
'attributes': {
'POSITION': position_id
}
}
if normals is not None:
extension['attributes']['NORMAL'] = normal_id
for (k, id) in enumerate(uv_ids):
extension['attributes']['TEXCOORD_' + str(k)] = id
for (k, id) in enumerate(weight_ids):
extension['attributes']['WEIGHTS_' + str(k)] = id
for (k, id) in enumerate(joint_ids):
extension['attributes']['JOINTS_' + str(k)] = id
primitive.extensions['KHR_draco_mesh_compression'] = extension
# Remove buffer views from the accessors of the attributes which compressed:
positions.buffer_view = None
if normals is not None:
normals.buffer_view = None
for uv in uvs:
uv.buffer_view = None
for weight in weights:
weight.buffer_view = None
for joint in joints:
joint.buffer_view = None
# Set to triangle list mode.
primitive.mode = 4
# Afterwards, the compressor can be released.
dll.destroy_compressor(compressor)
return
dll.encoderRelease(encoder)

View File

@ -54,6 +54,7 @@ class glTFImporter():
'KHR_texture_transform',
'KHR_materials_clearcoat',
'KHR_mesh_quantization',
'KHR_draco_mesh_compression'
]
@staticmethod