1# Copyright 2018-2019 The glTF-Blender-IO authors. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import struct 16import numpy as np 17 18from ..com.gltf2_io import Accessor 19from ..com.gltf2_io_constants import ComponentType, DataType 20 21 22class BinaryData(): 23 """Binary reader.""" 24 def __new__(cls, *args, **kwargs): 25 raise RuntimeError("%s should not be instantiated" % cls) 26 27 # Note that this function is not used in Blender importer, but is kept in 28 # Source code to be used in any pipeline that want to manage gltf/glb file in python 29 @staticmethod 30 def get_binary_from_accessor(gltf, accessor_idx): 31 """Get binary from accessor.""" 32 accessor = gltf.data.accessors[accessor_idx] 33 if accessor.buffer_view is None: 34 return None 35 36 data = BinaryData.get_buffer_view(gltf, accessor.buffer_view) 37 38 accessor_offset = accessor.byte_offset 39 if accessor_offset is None: 40 accessor_offset = 0 41 42 return data[accessor_offset:] 43 44 @staticmethod 45 def get_buffer_view(gltf, buffer_view_idx): 46 """Get binary data for buffer view.""" 47 buffer_view = gltf.data.buffer_views[buffer_view_idx] 48 49 if buffer_view.buffer in gltf.buffers.keys(): 50 buffer = gltf.buffers[buffer_view.buffer] 51 else: 52 # load buffer 53 gltf.load_buffer(buffer_view.buffer) 54 buffer = gltf.buffers[buffer_view.buffer] 55 56 byte_offset = buffer_view.byte_offset 57 if byte_offset is None: 58 byte_offset = 0 59 60 return buffer[byte_offset:byte_offset + buffer_view.byte_length] 61 62 @staticmethod 63 def get_data_from_accessor(gltf, accessor_idx, cache=False): 64 """Get data from accessor.""" 65 if accessor_idx in gltf.accessor_cache: 66 return gltf.accessor_cache[accessor_idx] 67 68 data = BinaryData.decode_accessor(gltf, accessor_idx).tolist() 69 70 if cache: 71 gltf.accessor_cache[accessor_idx] = data 72 73 return data 74 75 @staticmethod 76 def decode_accessor(gltf, accessor_idx, cache=False): 77 """Decodes accessor to 2D numpy array (count x num_components).""" 78 if accessor_idx in gltf.decode_accessor_cache: 79 return gltf.accessor_cache[accessor_idx] 80 81 accessor = gltf.data.accessors[accessor_idx] 82 array = BinaryData.decode_accessor_obj(gltf, accessor) 83 84 if cache: 85 gltf.accessor_cache[accessor_idx] = array 86 # Prevent accidentally modifying cached arrays 87 array.flags.writeable = False 88 89 return array 90 91 @staticmethod 92 def decode_accessor_obj(gltf, accessor): 93 # MAT2/3 have special alignment requirements that aren't handled. But it 94 # doesn't matter because nothing uses them. 95 assert accessor.type not in ['MAT2', 'MAT3'] 96 97 dtype = ComponentType.to_numpy_dtype(accessor.component_type) 98 component_nb = DataType.num_elements(accessor.type) 99 100 if accessor.buffer_view is not None: 101 bufferView = gltf.data.buffer_views[accessor.buffer_view] 102 buffer_data = BinaryData.get_buffer_view(gltf, accessor.buffer_view) 103 104 accessor_offset = accessor.byte_offset or 0 105 buffer_data = buffer_data[accessor_offset:] 106 107 bytes_per_elem = dtype(1).nbytes 108 default_stride = bytes_per_elem * component_nb 109 stride = bufferView.byte_stride or default_stride 110 111 if stride == default_stride: 112 array = np.frombuffer( 113 buffer_data, 114 dtype=np.dtype(dtype).newbyteorder('<'), 115 count=accessor.count * component_nb, 116 ) 117 array = array.reshape(accessor.count, component_nb) 118 119 else: 120 # The data looks like 121 # XXXppXXXppXXXppXXX 122 # where X are the components and p are padding. 123 # One XXXpp group is one stride's worth of data. 124 assert stride % bytes_per_elem == 0 125 elems_per_stride = stride // bytes_per_elem 126 num_elems = (accessor.count - 1) * elems_per_stride + component_nb 127 128 array = np.frombuffer( 129 buffer_data, 130 dtype=np.dtype(dtype).newbyteorder('<'), 131 count=num_elems, 132 ) 133 assert array.strides[0] == bytes_per_elem 134 array = np.lib.stride_tricks.as_strided( 135 array, 136 shape=(accessor.count, component_nb), 137 strides=(stride, bytes_per_elem), 138 ) 139 140 else: 141 # No buffer view; initialize to zeros 142 array = np.zeros((accessor.count, component_nb), dtype=dtype) 143 144 if accessor.sparse: 145 sparse_indices_obj = Accessor.from_dict({ 146 'count': accessor.sparse.count, 147 'bufferView': accessor.sparse.indices.buffer_view, 148 'byteOffset': accessor.sparse.indices.byte_offset or 0, 149 'componentType': accessor.sparse.indices.component_type, 150 'type': 'SCALAR', 151 }) 152 sparse_indices = BinaryData.decode_accessor_obj(gltf, sparse_indices_obj) 153 sparse_indices = sparse_indices.reshape(len(sparse_indices)) 154 155 sparse_values_obj = Accessor.from_dict({ 156 'count': accessor.sparse.count, 157 'bufferView': accessor.sparse.values.buffer_view, 158 'byteOffset': accessor.sparse.values.byte_offset or 0, 159 'componentType': accessor.component_type, 160 'type': accessor.type, 161 }) 162 sparse_values = BinaryData.decode_accessor_obj(gltf, sparse_values_obj) 163 164 if not array.flags.writeable: 165 array = array.copy() 166 array[sparse_indices] = sparse_values 167 168 # Normalization 169 if accessor.normalized: 170 if accessor.component_type == 5120: # int8 171 array = np.maximum(-1.0, array / 127.0) 172 elif accessor.component_type == 5121: # uint8 173 array = array / 255.0 174 elif accessor.component_type == 5122: # int16 175 array = np.maximum(-1.0, array / 32767.0) 176 elif accessor.component_type == 5123: # uint16 177 array = array / 65535.0 178 179 array = array.astype(np.float32, copy=False) 180 181 return array 182 183 @staticmethod 184 def get_image_data(gltf, img_idx): 185 """Get data from image.""" 186 pyimage = gltf.data.images[img_idx] 187 188 assert not ( 189 pyimage.uri is not None and 190 pyimage.buffer_view is not None 191 ) 192 193 if pyimage.uri is not None: 194 return gltf.load_uri(pyimage.uri) 195 if pyimage.buffer_view is not None: 196 return BinaryData.get_buffer_view(gltf, pyimage.buffer_view) 197 return None 198