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

gltf2_io_draco_compression_extension.py « exp « io « io_scene_gltf2 - git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 1db5ca91e930381568910b50e29ab1cac898c4e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# 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 bpy
import sys
from ctypes import *
from pathlib import Path

from io_scene_gltf2.io.exp.gltf2_io_binary_data import BinaryData
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 = "{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() -> 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, lambda node: __compress_node(node, dll, export_settings))

    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
            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

def __compress_node(node, dll, export_settings):
    """Compress a single node."""
    if not (node.mesh is None):
        print_console("INFO", "Compressing mesh " + 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):
    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)

    # 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 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.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

    # Afterwards, the compressor can be released.
    dll.disposeCompressor(compressor)

    pass