From eb29a12da48e89fa2a3518976e9223f2092ad22d Mon Sep 17 00:00:00 2001 From: Julien Duroure <julien.duroure@gmail.com> Date: Mon, 4 Jan 2021 20:55:25 +0100 Subject: [PATCH] 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 --- io_scene_gltf2/__init__.py | 18 +- .../blender/exp/gltf2_blender_export.py | 2 +- .../blender/imp/gltf2_blender_mesh.py | 6 + .../gltf2_io_draco_compression_extension.py | 143 +++++++ .../gltf2_io_draco_compression_extension.py | 64 ++++ .../gltf2_io_draco_compression_extension.py | 361 ++++-------------- io_scene_gltf2/io/imp/gltf2_io_gltf.py | 1 + 7 files changed, 308 insertions(+), 287 deletions(-) create mode 100644 io_scene_gltf2/blender/imp/gltf2_io_draco_compression_extension.py create mode 100644 io_scene_gltf2/io/com/gltf2_io_draco_compression_extension.py diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index 319c9c170..b8afe70fc 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -15,7 +15,7 @@ bl_info = { 'name': 'glTF 2.0 format', 'author': 'Julien Duroure, 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") diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_export.py b/io_scene_gltf2/blender/exp/gltf2_blender_export.py index fd433c7e9..a01390c9b 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_export.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_export.py @@ -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): diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py b/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py index a68964917..328bc1a85 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py @@ -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)) diff --git a/io_scene_gltf2/blender/imp/gltf2_io_draco_compression_extension.py b/io_scene_gltf2/blender/imp/gltf2_io_draco_compression_extension.py new file mode 100644 index 000000000..5a93673f9 --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_io_draco_compression_extension.py @@ -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) diff --git a/io_scene_gltf2/io/com/gltf2_io_draco_compression_extension.py b/io_scene_gltf2/io/com/gltf2_io_draco_compression_extension.py new file mode 100644 index 000000000..51359f826 --- /dev/null +++ b/io_scene_gltf2/io/com/gltf2_io_draco_compression_extension.py @@ -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 diff --git a/io_scene_gltf2/io/exp/gltf2_io_draco_compression_extension.py b/io_scene_gltf2/io/exp/gltf2_io_draco_compression_extension.py index 2bb79aa05..613bd2313 100644 --- a/io_scene_gltf2/io/exp/gltf2_io_draco_compression_extension.py +++ b/io_scene_gltf2/io/exp/gltf2_io_draco_compression_extension.py @@ -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.create_compressor.restype = c_void_p - dll.create_compressor.argtypes = [] - - dll.destroy_compressor.restype = None - dll.destroy_compressor.argtypes = [c_void_p] - - # Configuration: - - dll.set_compression_level.restype = None - dll.set_compression_level.argtypes = [c_void_p, c_uint32] - - dll.set_position_quantization.restype = None - dll.set_position_quantization.argtypes = [c_void_p, c_uint32] - - dll.set_normal_quantization.restype = None - dll.set_normal_quantization.argtypes = [c_void_p, c_uint32] - - dll.set_uv_quantization.restype = None - dll.set_uv_quantization.argtypes = [c_void_p, c_uint32] + dll.encoderCreate.restype = c_void_p + dll.encoderCreate.argtypes = [c_uint32] - dll.set_generic_quantization.restype = None - dll.set_generic_quantization.argtypes = [c_void_p, c_uint32] + dll.encoderRelease.restype = None + dll.encoderRelease.argtypes = [c_void_p] - # Data transfer: + dll.encoderSetCompressionLevel.restype = None + dll.encoderSetCompressionLevel.argtypes = [c_void_p, c_uint32] - 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 - ] + 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] - 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.encoderSetAttribute.restype = c_uint32 + dll.encoderSetAttribute.argtypes = [c_void_p, c_char_p, c_size_t, c_char_p, c_void_p] - dll.add_positions_f32.restype = add_attribute_fn_restype - dll.add_positions_f32.argtypes = add_attribute_fn_argtypes + dll.encoderEncode.restype = c_bool + dll.encoderEncode.argtypes = [c_void_p, c_uint8] - dll.add_normals_f32.restype = add_attribute_fn_restype - dll.add_normals_f32.argtypes = add_attribute_fn_argtypes + dll.encoderGetEncodedVertexCount.restype = c_uint32 + dll.encoderGetEncodedVertexCount.argtypes = [c_void_p] - dll.add_uvs_f32.restype = add_attribute_fn_restype - dll.add_uvs_f32.argtypes = add_attribute_fn_argtypes + dll.encoderGetEncodedIndexCount.restype = c_uint32 + dll.encoderGetEncodedIndexCount.argtypes = [c_void_p] - dll.add_weights_f32.restype = add_attribute_fn_restype - dll.add_weights_f32.argtypes = add_attribute_fn_argtypes + dll.encoderGetByteLength.restype = c_uint64 + dll.encoderGetByteLength.argtypes = [c_void_p] - dll.add_joints_u16.restype = add_attribute_fn_restype - dll.add_joints_u16.argtypes = add_attribute_fn_argtypes + dll.encoderCopy.restype = None + dll.encoderCopy.argtypes = [c_void_p, c_void_p] - # 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)) - - # 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) + __traverse_node(node, lambda node: __encode_node(node, dll, export_settings)) -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_')] - - 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))) - - # Begin mesh. - compressor = dll.create_compressor() - - # Each attribute must have the same count of elements. - count = positions.count - - # Add attributes to mesh compressor, remembering each attribute's Draco id. - - position_id = dll.add_positions_f32(compressor, count, positions.buffer_view.data) - - 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) - - 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)) - - 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 + encoder = dll.encoderCreate(positions.count) + + 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 + + dll.encoderSetIndices(encoder, indices.component_type, indices.count, indices.buffer_view.data) + indices.buffer_view = None + + 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.') + + byte_length = dll.encoderGetByteLength(encoder) + encoded_data = bytes(byte_length) + dll.encoderCopy(encoder, encoded_data) + + if primitive.extensions is None: + primitive.extensions = {} + + primitive.extensions['KHR_draco_mesh_compression'] = { + 'bufferView': BinaryData(encoded_data), + 'attributes': draco_ids + } - # Set to triangle list mode. - primitive.mode = 4 + # Set to triangle list mode. + primitive.mode = 4 - # Afterwards, the compressor can be released. - dll.destroy_compressor(compressor) + # 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 - return + dll.encoderRelease(encoder) diff --git a/io_scene_gltf2/io/imp/gltf2_io_gltf.py b/io_scene_gltf2/io/imp/gltf2_io_gltf.py index 348f89045..f0e23ea15 100755 --- a/io_scene_gltf2/io/imp/gltf2_io_gltf.py +++ b/io_scene_gltf2/io/imp/gltf2_io_gltf.py @@ -54,6 +54,7 @@ class glTFImporter(): 'KHR_texture_transform', 'KHR_materials_clearcoat', 'KHR_mesh_quantization', + 'KHR_draco_mesh_compression' ] @staticmethod -- GitLab