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