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:
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
|
@ -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")
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -54,6 +54,7 @@ class glTFImporter():
|
|||
'KHR_texture_transform',
|
||||
'KHR_materials_clearcoat',
|
||||
'KHR_mesh_quantization',
|
||||
'KHR_draco_mesh_compression'
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
|
|
Loading…
Reference in New Issue