Source code for py3dtiles.gltf

# -*- coding: utf-8 -*-
import struct
import numpy as np
import json


[docs] class GlTF(object): 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 = 28 + len(self.body) + len(scene) + len(padding) binaryHeader = np.array([0x46546C67, # "glTF" magic 2, # version length], dtype=np.uint32) jsonChunkHeader = np.array([len(scene), # JSON chunck length 0x4E4F534A], dtype=np.uint32) # "JSON" binChunkHeader = np.array([len(self.body) + len(padding), # BIN chunck length 0x004E4942], dtype=np.uint32) # "BIN" return np.concatenate((binaryHeader.view(np.uint8), jsonChunkHeader.view(np.uint8), np.frombuffer(scene.encode('utf-8'), dtype=np.uint8), binChunkHeader.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") if struct.unpack("i", array[4:8])[0] != 1: raise RuntimeError("Unsupported glTF version") length = struct.unpack("i", array[8:12])[0] content_length = struct.unpack("i", array[12:16])[0] if struct.unpack("i", array[16:20])[0] != 0: raise RuntimeError("Unsupported binary glTF content type") header = struct.unpack(str(content_length) + "s", array[20:20 + content_length])[0] glTF.header = json.loads(header.decode("ascii")) glTF.body = array[20 + content_length:length] return glTF
[docs] @staticmethod def from_binary_arrays(arrays, transform, binary=True, batched=True, uri=None, textureUri=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] binVertices = [] binNormals = [] binIds = [] binUvs = [] nVertices = [] bb = [] batchLength = 0 for i, geometry in enumerate(arrays): binVertices.append(geometry['position']) binNormals.append(geometry['normal']) n = round(len(geometry['position']) / 12) nVertices.append(n) bb.append(geometry['bbox']) if batched: binIds.append(np.full(n, i, dtype=np.float32)) if textured: binUvs.append(geometry['uv']) if batched: binVertices = [b''.join(binVertices)] binNormals = [b''.join(binNormals)] binUvs = [b''.join(binUvs)] binIds = [b''.join(binIds)] nVertices = [sum(nVertices)] batchLength = 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(binVertices, nVertices, bb, transform, textured, batched, batchLength, uri, textureUri) glTF.body = np.frombuffer(compute_binary(binVertices, binNormals, binIds, binUvs), dtype=np.uint8) return glTF
[docs] def compute_binary(binVertices, binNormals, binIds, binUvs): bv = b''.join(binVertices) bn = b''.join(binNormals) bid = b''.join(binIds) buv = b''.join(binUvs) return bv + bn + buv + bid
[docs] def compute_header(binVertices, nVertices, bb, transform, textured, batched, batchLength, uri, textureUri): # Buffer meshNb = len(binVertices) sizeVce = [] for i in range(0, meshNb): sizeVce.append(len(binVertices[i])) byteLength = 2 * sum(sizeVce) if textured: byteLength += int(round(2 * sum(sizeVce) / 3)) if batched: byteLength += int(round(sum(sizeVce) / 3)) buffers = [{ 'byteLength': byteLength }] if uri is not None: buffers["binary_glTF"]["uri"] = uri # Buffer view bufferViews = [] # vertices bufferViews.append({ 'buffer': 0, 'byteLength': sum(sizeVce), 'byteOffset': 0, 'target': 34962 }) bufferViews.append({ 'buffer': 0, 'byteLength': sum(sizeVce), 'byteOffset': sum(sizeVce), 'target': 34962 }) if textured: bufferViews.append({ 'buffer': 0, 'byteLength': int(round(2 * sum(sizeVce) / 3)), 'byteOffset': 2 * sum(sizeVce), 'target': 34962 }) if batched: bufferViews.append({ 'buffer': 0, 'byteLength': int(round(sum(sizeVce) / 3)), 'byteOffset': int(round(8 / 3 * sum(sizeVce))) if textured else 2 * sum(sizeVce), 'target': 34962 }) # Accessor accessors = [] for i in range(0, meshNb): # vertices accessors.append({ 'bufferView': 0, 'byteOffset': sum(sizeVce[0:i]), 'componentType': 5126, 'count': nVertices[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(sizeVce[0:i]), 'componentType': 5126, 'count': nVertices[i], 'max': [1, 1, 1], 'min': [-1, -1, -1], 'type': "VEC3" }) if textured: accessors.append({ 'bufferView': 2, 'byteOffset': int(round(2 / 3 * sum(sizeVce[0:i]))), 'componentType': 5126, 'count': sum(nVertices), 'max': [1, 1], 'min': [0, 0], 'type': "VEC2" }) if batched: accessors.append({ 'bufferView': 3 if textured else 2, 'byteOffset': 0, 'componentType': 5126, 'count': nVertices[0], 'max': [batchLength], 'min': [0], 'type': "SCALAR" }) # Meshes meshes = [] nAttributes = 3 if textured else 2 for i in range(0, meshNb): meshes.append({ 'primitives': [{ 'attributes': { "POSITION": nAttributes * i, "NORMAL": nAttributes * i + 1 }, "material": 0, "mode": 4 }] }) if textured: meshes[i]['primitives'][0]['attributes']['TEXCOORD_0'] = ( nAttributes * i + 2) if batched: meshes[0]['primitives'][0]['attributes']['_BATCHID'] = nAttributes # Nodes nodes = [] for i in range(0, meshNb): 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': bufferViews, 'buffers': buffers } # Texture data if textured: header['textures'] = [{ 'sampler': 0, 'source': 0 }] header['images'] = [{ 'uri': textureUri }] header['samplers'] = [{ "magFilter": 9729, "minFilter": 9987, "wrapS": 10497, "wrapT": 10497 }] header['materials'][0]['pbrMetallicRoughness']['baseColorTexture'] = { 'index': 0 } return header