Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulien Duroure <julien.duroure@gmail.com>2019-04-02 23:13:43 +0300
committerJulien Duroure <julien.duroure@gmail.com>2019-04-02 23:13:43 +0300
commit0a26f6cce99ce064c48a75c2df7533b2b3156d42 (patch)
treeb27384f3e62b1ecb3d22687f21e35d1df5566cc7
parent8521155924da9dbe3016d344b2bcaef5d889d363 (diff)
glTF exporter: Draco compression
Note that blender must use this patch to use Draco compression: https://developer.blender.org/D4501 Currenlty, this patch is not merged yet into master
-rwxr-xr-xio_scene_gltf2/__init__.py70
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_export.py6
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py9
-rw-r--r--io_scene_gltf2/io/exp/gltf2_io_draco_compression_extension.py212
4 files changed, 294 insertions, 3 deletions
diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py
index 7318487e..8c4d8db4 100755
--- a/io_scene_gltf2/__init__.py
+++ b/io_scene_gltf2/__init__.py
@@ -14,7 +14,7 @@
bl_info = {
'name': 'glTF 2.0 format',
- 'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen',
+ 'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein',
"version": (0, 0, 1),
'blender': (2, 80, 0),
'location': 'File > Import-Export',
@@ -26,6 +26,7 @@ bl_info = {
'category': 'Import-Export',
}
+
#
# Script reloading (if the user calls 'Reload Scripts' from Blender)
#
@@ -33,6 +34,7 @@ bl_info = {
def reload_package(module_dict_main):
import importlib
from pathlib import Path
+
def reload_package_recursive(current_dir, module_dict):
for path in current_dir.iterdir():
if "__init__" in str(path) or path.stem not in module_dict:
@@ -56,6 +58,7 @@ from bpy.props import (StringProperty,
IntProperty)
from bpy.types import Operator
from bpy_extras.io_utils import ImportHelper, ExportHelper
+from io_scene_gltf2.io.exp import gltf2_io_draco_compression_extension
#
@@ -64,9 +67,11 @@ from bpy_extras.io_utils import ImportHelper, ExportHelper
class ExportGLTF2_Base:
-
# TODO: refactor to avoid boilerplate
+ def __init__(self):
+ self.is_draco_available = gltf2_io_draco_compression_extension.dll_exists()
+
bl_options = {'UNDO', 'PRESET'}
export_format: EnumProperty(
@@ -114,6 +119,44 @@ class ExportGLTF2_Base:
default=True
)
+ export_draco_mesh_compression_enable: BoolProperty(
+ name='Draco mesh compression',
+ description='Compress mesh using Draco',
+ default=False
+ )
+
+ export_draco_mesh_compression_level: IntProperty(
+ name='Compression level',
+ description='Compression level (0 = most speed, 6 = most compression, higher values currently not supported)',
+ default=6,
+ min=0,
+ max=6
+ )
+
+ export_draco_position_quantization: IntProperty(
+ name='Position quantization bits',
+ description='Quantization bits for position values (0 = no quantization)',
+ default=14,
+ min=0,
+ max=30
+ )
+
+ export_draco_normal_quantization: IntProperty(
+ name='Normal quantization bits',
+ description='Quantization bits for normal values (0 = no quantization)',
+ default=10,
+ min=0,
+ max=30
+ )
+
+ export_draco_texcoord_quantization: IntProperty(
+ name='Texcoord quantization bits',
+ description='Quantization bits for texture coordinate values (0 = no quantization)',
+ default=12,
+ min=0,
+ max=30
+ )
+
export_tangents: BoolProperty(
name='Tangents',
description='Export vertex tangents with meshes',
@@ -309,11 +352,21 @@ class ExportGLTF2_Base:
export_settings['gltf_texcoords'] = self.export_texcoords
export_settings['gltf_normals'] = self.export_normals
export_settings['gltf_tangents'] = self.export_tangents and self.export_normals
+
+ if self.is_draco_available:
+ export_settings['gltf_draco_mesh_compression'] = self.export_draco_mesh_compression_enable
+ export_settings['gltf_draco_mesh_compression_level'] = self.export_draco_mesh_compression_level
+ 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
+ else:
+ export_settings['gltf_draco_mesh_compression'] = False
+
export_settings['gltf_materials'] = self.export_materials
export_settings['gltf_colors'] = self.export_colors
export_settings['gltf_cameras'] = self.export_cameras
export_settings['gltf_selected'] = self.export_selected
- export_settings['gltf_layers'] = True #self.export_layers
+ export_settings['gltf_layers'] = True # self.export_layers
export_settings['gltf_extras'] = self.export_extras
export_settings['gltf_yup'] = self.export_yup
export_settings['gltf_apply'] = self.export_apply
@@ -385,6 +438,17 @@ class ExportGLTF2_Base:
col.prop(self, 'export_colors')
col.prop(self, 'export_materials')
+ # Add Draco compression option only if the DLL could be found.
+ if self.is_draco_available:
+ col.prop(self, 'export_draco_mesh_compression_enable')
+
+ # Display options when Draco compression is enabled.
+ if self.export_draco_mesh_compression_enable:
+ col.prop(self, 'export_draco_mesh_compression_level')
+ col.prop(self, 'export_draco_position_quantization')
+ col.prop(self, 'export_draco_normal_quantization')
+ col.prop(self, 'export_draco_texcoord_quantization')
+
def draw_object_settings(self):
col = self.layout.box().column()
col.prop(self, 'export_cameras')
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_export.py b/io_scene_gltf2/blender/exp/gltf2_blender_export.py
index 071b4884..ad08a851 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_export.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_export.py
@@ -23,6 +23,7 @@ from io_scene_gltf2.blender.exp import gltf2_blender_gather
from io_scene_gltf2.blender.exp.gltf2_blender_gltf2_exporter import GlTF2Exporter
from io_scene_gltf2.io.com.gltf2_io_debug import print_console, print_newline
from io_scene_gltf2.io.exp import gltf2_io_export
+from io_scene_gltf2.io.exp import gltf2_io_draco_compression_extension
def save(context, export_settings):
@@ -66,6 +67,11 @@ def __get_copyright(export_settings):
def __gather_gltf(exporter, export_settings):
scenes, animations = gltf2_blender_gather.gather_gltf2(export_settings)
+
+ if export_settings['gltf_draco_mesh_compression']:
+ gltf2_io_draco_compression_extension.compress_scene_primitives(scenes, export_settings)
+ exporter.add_draco_extension()
+
for scene in scenes:
exporter.add_scene(scene)
for animation in animations:
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py b/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py
index ea26eb1c..7d4d3a80 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py
@@ -135,6 +135,15 @@ class GlTF2Exporter:
if is_glb:
return self.__buffer.to_bytes()
+ def add_draco_extension(self):
+ """
+ Register Draco extension as *used* and *required*.
+
+ :return:
+ """
+ self.__gltf.extensions_required.append('KHR_draco_mesh_compression')
+ self.__gltf.extensions_used.append('KHR_draco_mesh_compression')
+
def finalize_images(self, output_path):
"""
Write all images.
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
new file mode 100644
index 00000000..845e2d2d
--- /dev/null
+++ b/io_scene_gltf2/io/exp/gltf2_io_draco_compression_extension.py
@@ -0,0 +1,212 @@
+import bpy
+import sys
+from ctypes import *
+from pathlib import Path
+
+from io_scene_gltf2.io.exp.gltf2_io_binary_data import BinaryData
+
+
+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('2.80/python/lib')
+ paths = {
+ 'win32': blender_root/python_lib/'site-packages'/'{}.dll'.format(lib_name),
+ 'linux': blender_root/python_lib/'python3.7'/'site-packages'/'lib{}.so'.format(lib_name),
+ 'darwin': blender_root.parent/'Resources'/python_lib/'python3.7'/'site-packages'/'lib{}.dylib'.format(lib_name)
+ }
+
+ path = paths.get(sys.platform)
+ return path if path is not None else ''
+
+
+def dll_exists() -> bool:
+ """
+ Checks whether the DLL path exists.
+ :return: True if the DLL exists.
+ """
+ exists = dll_path().exists()
+ 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):
+ """
+ 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.
+ """
+
+ # Load DLL and setup function signatures.
+ # Nearly all functions take the compressor as the first argument.
+ dll = cdll.LoadLibrary(str(dll_path().resolve()))
+
+ dll.createCompressor.restype = c_void_p
+ dll.createCompressor.argtypes = []
+
+ dll.setCompressionLevel.restype = None
+ dll.setCompressionLevel.argtypes = [c_void_p, c_uint32]
+
+ dll.setPositionQuantizationBits.restype = None
+ dll.setPositionQuantizationBits.argtypes = [c_void_p, c_uint32]
+
+ dll.setNormalQuantizationBits.restype = None
+ dll.setNormalQuantizationBits.argtypes = [c_void_p, c_uint32]
+
+ dll.setTexCoordQuantizationBits.restype = None
+ dll.setTexCoordQuantizationBits.argtypes = [c_void_p, c_uint32]
+
+ dll.compress.restype = c_bool
+ dll.compress.argtypes = [c_void_p]
+
+ dll.compressedSize.restype = c_uint64
+ dll.compressedSize.argtypes = [c_void_p]
+
+ dll.disposeCompressor.restype = None
+ dll.disposeCompressor.argtypes = [c_void_p]
+
+ dll.setFaces.restype = None
+ dll.setFaces.argtypes = [c_void_p, c_uint32, c_uint32, c_void_p]
+
+ dll.addPositionAttribute.restype = None
+ dll.addPositionAttribute.argtypes = [c_void_p, c_uint32, c_char_p]
+
+ dll.addNormalAttribute.restype = None
+ dll.addNormalAttribute.argtypes = [c_void_p, c_uint32, c_char_p]
+
+ dll.addTexCoordAttribute.restype = None
+ dll.addTexCoordAttribute.argtypes = [c_void_p, c_uint32, c_char_p]
+
+ dll.copyToBytes.restype = None
+ dll.copyToBytes.argtypes = [c_void_p, c_char_p]
+
+ dll.getTexCoordAttributeIdCount.restype = c_uint32
+ dll.getTexCoordAttributeIdCount.argtypes = [c_void_p]
+
+ dll.getTexCoordAttributeId.restype = c_uint32
+ dll.getTexCoordAttributeId.argtypes = [c_void_p, c_uint32]
+
+ dll.getPositionAttributeId.restype = c_uint32
+ dll.getPositionAttributeId.argtypes = [c_void_p]
+
+ dll.getNormalAttributeId.restype = c_uint32
+ dll.getNormalAttributeId.argtypes = [c_void_p]
+
+ dll.setCompressionLevel.restype = None
+ dll.setCompressionLevel.argtypes = [c_void_p, c_uint32]
+
+ dll.setPositionQuantizationBits.restype = None
+ dll.setPositionQuantizationBits.argtypes = [c_void_p, c_uint32]
+
+ dll.setNormalQuantizationBits.restype = None
+ dll.setNormalQuantizationBits.argtypes = [c_void_p, c_uint32]
+
+ dll.setTexCoordQuantizationBits.restype = None
+ dll.setTexCoordQuantizationBits.argtypes = [c_void_p, c_uint32]
+
+ for scene in scenes:
+ for node in scene.nodes:
+ __traverse_node(node, dll, export_settings)
+
+
+def __traverse_node(node, dll, export_settings):
+ if not (node.mesh is None):
+ print("Compressing mesh " + node.name)
+ for primitive in node.mesh.primitives:
+ __compress_primitive(primitive, dll, export_settings)
+
+ if not (node.children is None):
+ for child in node.children:
+ __traverse_node(child, dll, export_settings)
+
+
+def __compress_primitive(primitive, dll, export_settings):
+ attributes = primitive.attributes
+
+ # Begin mesh.
+ compressor = dll.createCompressor()
+
+ # Process position attributes.
+ dll.addPositionAttribute(compressor, attributes['POSITION'].count, attributes['POSITION'].buffer_view.data)
+
+ # Process normal attributes.
+ dll.addNormalAttribute(compressor, attributes['NORMAL'].count, attributes['NORMAL'].buffer_view.data)
+
+ # Process texture coordinate attributes.
+ for attribute in [attributes[attr] for attr in attributes if attr.startswith('TEXCOORD_')]:
+ dll.addTexCoordAttribute(compressor, attribute.count, attribute.buffer_view.data)
+
+ # Process faces.
+ index_byte_length = {
+ 'Byte': 1,
+ 'UnsignedByte': 1,
+ 'Short': 2,
+ 'UnsignedShort': 2,
+ 'UnsignedInt': 4,
+ }
+ indices = primitive.indices
+ dll.setFaces(compressor, indices.count, index_byte_length[indices.component_type.name], indices.buffer_view.data)
+ indices.buffer_view = None
+
+ # Set compression parameters.
+ dll.setCompressionLevel(compressor, export_settings['gltf_draco_mesh_compression_level'])
+ dll.setPositionQuantizationBits(compressor, export_settings['gltf_draco_position_quantization'])
+ dll.setNormalQuantizationBits(compressor, export_settings['gltf_draco_normal_quantization'])
+ dll.setTexCoordQuantizationBits(compressor, export_settings['gltf_draco_texcoord_quantization'])
+
+ # After all point and connectivity data has been written to the compressor,
+ # it can finally be compressed.
+ if dll.compress(compressor):
+
+ # Compression was successfull.
+ # 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.compressedSize(compressor)
+
+ # Allocate byte buffer and write compressed data to it.
+ compressed_data = bytes(compression_size)
+ dll.copyToBytes(compressor, compressed_data)
+
+ if primitive.extensions is None:
+ primitive.extensions = {}
+
+ tex_coord_ids = {}
+ for id in range(0, dll.getTexCoordAttributeIdCount(compressor)):
+ tex_coord_ids["TEXCOORD_" + str(id)] = dll.getTexCoordAttributeId(compressor, id)
+
+ # Register draco compression extension into primitive.
+ primitive.extensions["KHR_draco_mesh_compression"] = {
+ 'bufferView': BinaryData(compressed_data),
+ 'attributes': {
+ 'POSITION': dll.getPositionAttributeId(compressor),
+ 'NORMAL': dll.getNormalAttributeId(compressor),
+ **tex_coord_ids,
+ }
+ }
+
+ # Set to triangle list mode.
+ primitive.mode = 4
+
+ # Remove buffers from attribute, since the data now resides inside the compressed Draco buffer.
+ attributes['POSITION'].buffer_view = None
+ attributes['NORMAL'].buffer_view = None
+ for attribute in [attributes[attr] for attr in attributes if attr.startswith('TEXCOORD_')]:
+ attribute.buffer_view = None
+
+ # Afterwards, the compressor can be released.
+ dll.disposeCompressor(compressor)
+
+ pass
+