Source code for py3dtiles.gltf

import json
import struct

import numpy as np


[docs] class GlTF: HEADER_LENGTH = 12 CHUNK_HEADER_LENGTH = 8 def __init__(self): self.header = {} self.body = None
[docs] def to_array(self): # glb scene = json.dumps(self.header, separators=(',', ':')) # body must be 4-byte aligned scene += ' ' * ((4 - len(scene) % 4) % 4) padding = np.array([0 for i in range(0, (4 - len(self.body) % 4) % 4)], dtype=np.uint8) length = GlTF.HEADER_LENGTH + (2 * GlTF.CHUNK_HEADER_LENGTH) length += len(self.body) + len(scene) + len(padding) binary_header = np.array([0x46546C67, # "glTF" magic 2, # version length], dtype=np.uint32) json_chunk_header = np.array([len(scene), # JSON chunck length 0x4E4F534A], dtype=np.uint32) # "JSON" bin_chunk_header = np.array([len(self.body) + len(padding), # BIN chunck length 0x004E4942], dtype=np.uint32) # "BIN" return np.concatenate((binary_header.view(np.uint8), json_chunk_header.view(np.uint8), np.frombuffer(scene.encode('utf-8'), dtype=np.uint8), bin_chunk_header.view(np.uint8), self.body, padding))
[docs] @staticmethod def from_array(array): """ Parameters ---------- array : numpy.array Returns ------- glTF : GlTf """ glTF = GlTF() if struct.unpack("4s", array[0:4])[0] != b"glTF": raise RuntimeError("Array does not contain a binary glTF") version = struct.unpack("i", array[4:8])[0] if version != 1 and version != 2: raise RuntimeError("Unsupported glTF version") length = struct.unpack("i", array[8:12])[0] json_chunk_length = struct.unpack("i", array[12:16])[0] chunk_type = struct.unpack("i", array[16:20])[0] if chunk_type != 0 and chunk_type != 1313821514: # 1313821514 => 'JSON' raise RuntimeError("Unsupported binary glTF content type") index = GlTF.HEADER_LENGTH + GlTF.CHUNK_HEADER_LENGTH # Skip the header and the JSON chunk header header = struct.unpack(str(json_chunk_length) + "s", array[index:index + json_chunk_length])[0] glTF.header = json.loads(header.decode("ascii")) index += json_chunk_length + GlTF.CHUNK_HEADER_LENGTH # Skip the JSON chunk data and the binary chunk header glTF.body = array[index:length] return glTF
[docs] @staticmethod def from_binary_arrays(arrays, transform, batched=True, uri=None, texture_uri=None): """ Parameters ---------- arrays : array of dictionaries Each dictionary has the data for one geometry arrays['position']: binary array of vertex positions arrays['normal']: binary array of vertex normals arrays['uv']: binary array of vertex texture coordinates (Not implemented yet) arrays['bbox']: geometry bounding box (numpy.array) transform : numpy.array World coordinates transformation flattend matrix Returns ------- glTF : GlTF """ glTF = GlTF() textured = 'uv' in arrays[0] bin_vertices = [] bin_normals = [] bin_ids = [] bin_uvs = [] n_vertices = [] bb = [] batch_length = 0 for i, geometry in enumerate(arrays): bin_vertices.append(geometry['position']) bin_normals.append(geometry['normal']) n = round(len(geometry['position']) / 12) n_vertices.append(n) bb.append(geometry['bbox']) if batched: bin_ids.append(np.full(n, i, dtype=np.float32)) if textured: bin_uvs.append(geometry['uv']) if batched: bin_vertices = [b''.join(bin_vertices)] bin_normals = [b''.join(bin_normals)] bin_uvs = [b''.join(bin_uvs)] bin_ids = [b''.join(bin_ids)] n_vertices = [sum(n_vertices)] batch_length = len(arrays) [minx, miny, minz] = bb[0][0] [maxx, maxy, maxz] = bb[0][1] for box in bb[1:]: minx = min(minx, box[0][0]) miny = min(miny, box[0][1]) minz = min(minz, box[0][2]) maxx = max(maxx, box[1][0]) maxy = max(maxy, box[1][1]) maxz = max(maxz, box[1][2]) bb = [[[minx, miny, minz], [maxx, maxy, maxz]]] glTF.header = compute_header(bin_vertices, n_vertices, bb, transform, textured, batched, batch_length, uri, texture_uri) glTF.body = np.frombuffer(compute_binary(bin_vertices, bin_normals, bin_ids, bin_uvs), dtype=np.uint8) return glTF
[docs] def compute_binary(bin_vertices, bin_normals, bin_ids, bin_uvs): bv = b''.join(bin_vertices) bn = b''.join(bin_normals) bid = b''.join(bin_ids) buv = b''.join(bin_uvs) return bv + bn + buv + bid
[docs] def compute_header(bin_vertices, n_vertices, bb, transform, textured, batched, batch_length, uri, texture_uri): # Buffer mesh_nb = len(bin_vertices) size_vce = [] for i in range(0, mesh_nb): size_vce.append(len(bin_vertices[i])) byte_length = 2 * sum(size_vce) if textured: byte_length += int(round(2 * sum(size_vce) / 3)) if batched: byte_length += int(round(sum(size_vce) / 3)) buffers = [{ 'byteLength': byte_length }] if uri is not None: buffers.append({"binary_glTF": {"uri": uri}}) # Buffer view buffer_views = [{ 'buffer': 0, 'byteLength': sum(size_vce), 'byteOffset': 0, 'target': 34962 }, { 'buffer': 0, 'byteLength': sum(size_vce), 'byteOffset': sum(size_vce), 'target': 34962 }] # vertices if textured: buffer_views.append({ 'buffer': 0, 'byteLength': int(round(2 * sum(size_vce) / 3)), 'byteOffset': 2 * sum(size_vce), 'target': 34962 }) if batched: buffer_views.append({ 'buffer': 0, 'byteLength': int(round(sum(size_vce) / 3)), 'byteOffset': int(round(8 / 3 * sum(size_vce))) if textured else 2 * sum(size_vce), 'target': 34962 }) # Accessor accessors = [] for i in range(0, mesh_nb): # vertices accessors.append({ 'bufferView': 0, 'byteOffset': sum(size_vce[0:i]), 'componentType': 5126, 'count': n_vertices[i], 'min': [bb[i][0][0], bb[i][0][1], bb[i][0][2]], 'max': [bb[i][1][0], bb[i][1][1], bb[i][1][2]], 'type': "VEC3" }) # normals accessors.append({ 'bufferView': 1, 'byteOffset': sum(size_vce[0:i]), 'componentType': 5126, 'count': n_vertices[i], 'max': [1, 1, 1], 'min': [-1, -1, -1], 'type': "VEC3" }) if textured: accessors.append({ 'bufferView': 2, 'byteOffset': int(round(2 / 3 * sum(size_vce[0:i]))), 'componentType': 5126, 'count': sum(n_vertices), 'max': [1, 1], 'min': [0, 0], 'type': "VEC2" }) if batched: accessors.append({ 'bufferView': 3 if textured else 2, 'byteOffset': 0, 'componentType': 5126, 'count': n_vertices[0], 'max': [batch_length], 'min': [0], 'type': "SCALAR" }) # Meshes meshes = [] n_attributes = 3 if textured else 2 for i in range(0, mesh_nb): meshes.append({ 'primitives': [{ 'attributes': { "POSITION": n_attributes * i, "NORMAL": n_attributes * i + 1 }, "material": 0, "mode": 4 }] }) if textured: meshes[i]['primitives'][0]['attributes']['TEXCOORD_0'] = ( n_attributes * i + 2) if batched: meshes[0]['primitives'][0]['attributes']['_BATCHID'] = n_attributes # Nodes nodes = [] for i in range(0, mesh_nb): nodes.append({ 'matrix': [float(e) for e in transform], 'mesh': i }) # Materials materials = [{ 'pbrMetallicRoughness': { 'metallicFactor': 0 }, 'name': 'Material', }] # Final glTF header = { 'asset': { "generator": "py3dtiles", "version": "2.0" }, 'scene': 0, 'scenes': [{ 'nodes': [i for i in range(0, len(nodes))] }], 'nodes': nodes, 'meshes': meshes, 'materials': materials, 'accessors': accessors, 'bufferViews': buffer_views, 'buffers': buffers } # Texture data if textured: header['textures'] = [{ 'sampler': 0, 'source': 0 }] header['images'] = [{ 'uri': texture_uri }] header['samplers'] = [{ "magFilter": 9729, "minFilter": 9987, "wrapS": 10497, "wrapT": 10497 }] header['materials'][0]['pbrMetallicRoughness']['baseColorTexture'] = { 'index': 0 } return header