from __future__ import annotations
import struct
import numpy as np
import numpy.typing as npt
from py3dtiles.exceptions import InvalidB3dmError
from .batch_table import BatchTable
from .gltf import GlTF
from .tile_content import (
TileContent,
TileContentBody,
TileContentHeader,
)
[docs]
class B3dm(TileContent):
def __init__(self, header: B3dmHeader, body: B3dmBody) -> None:
super().__init__()
self.header: B3dmHeader = header
self.body: B3dmBody = body
[docs]
def sync(self) -> None:
"""
Allow to synchronize headers with contents.
"""
# extract array
gltf_arr = self.body.gltf.to_array()
# sync the tile header with feature table contents
self.header.tile_byte_length = len(gltf_arr) + B3dmHeader.BYTE_LENGTH
self.header.bt_json_byte_length = 0
self.header.bt_bin_byte_length = 0
self.header.ft_json_byte_length = 0
self.header.ft_bin_byte_length = 0
if self.body.batch_table is not None:
bth_arr = self.body.batch_table.to_array()
self.header.tile_byte_length += len(bth_arr)
self.header.bt_json_byte_length = len(bth_arr)
[docs]
def print_info(self) -> None:
if self.header:
th = self.header
print("Tile Header")
print("-----------")
print("Magic Value: ", th.magic_value)
print("Version: ", th.version)
print("Tile byte length: ", th.tile_byte_length)
print("Feature table json byte length: ", th.ft_json_byte_length)
print("Feature table bin byte length: ", th.ft_bin_byte_length)
print("Batch table json byte length: ", th.bt_json_byte_length)
print("Batch table bin byte length: ", th.bt_bin_byte_length)
else:
print("Tile with no header")
if self.body:
gltf_header = self.body.gltf.header
print("")
print("glTF Header")
print("-----------")
print(gltf_header)
else:
print("Tile with no body")
[docs]
@staticmethod
def from_gltf(gltf: GlTF, batch_table: BatchTable | None = None) -> B3dm:
b3dm_body = B3dmBody()
b3dm_body.gltf = gltf
if batch_table is not None:
b3dm_body.batch_table = batch_table
b3dm_header = B3dmHeader()
b3dm = B3dm(b3dm_header, b3dm_body)
b3dm.sync()
return b3dm
[docs]
@staticmethod
def from_array(array: npt.NDArray[np.uint8]) -> B3dm:
# build tile header
h_arr = array[0 : B3dmHeader.BYTE_LENGTH]
b3dm_header = B3dmHeader.from_array(h_arr)
if b3dm_header.tile_byte_length != len(array):
raise InvalidB3dmError(
f"Invalid byte length in header, the size of array is {len(array)}, "
f"the tile_byte_length for header is {b3dm_header.tile_byte_length}"
)
# build tile body
b_arr = array[B3dmHeader.BYTE_LENGTH : b3dm_header.tile_byte_length]
b3dm_body = B3dmBody.from_array(b3dm_header, b_arr)
# build tile with header and body
return B3dm(b3dm_header, b3dm_body)
[docs]
class B3dmBody(TileContentBody):
def __init__(self) -> None:
self.batch_table = BatchTable()
self.gltf = GlTF()
[docs]
def to_array(self) -> npt.NDArray[np.uint8]:
# TODO : export feature table, reminder, the glTF part must start on an 8-byte boundary.
if self.batch_table:
batch_table = self.batch_table.to_array()
else:
batch_table = np.array([], dtype=np.uint8)
# The glTF part must start and end on an 8-byte boundary
gltf_array = self.gltf.to_array()
return np.concatenate((batch_table, gltf_array))
[docs]
@staticmethod
def from_gltf(gltf: GlTF) -> B3dmBody:
# build tile body
b = B3dmBody()
b.gltf = gltf
return b
[docs]
@staticmethod
def from_array(b3dm_header: B3dmHeader, array: npt.NDArray[np.uint8]) -> B3dmBody:
# build feature table
ft_len = b3dm_header.ft_json_byte_length + b3dm_header.ft_bin_byte_length
# build batch table
bt_len = b3dm_header.bt_json_byte_length + b3dm_header.bt_bin_byte_length
# build glTF
gltf_len = (
b3dm_header.tile_byte_length - ft_len - bt_len - B3dmHeader.BYTE_LENGTH
)
gltf_arr = array[ft_len + bt_len : ft_len + bt_len + gltf_len]
gltf = GlTF.from_array(gltf_arr)
# build tile body with batch table
b = B3dmBody()
b.gltf = gltf
if b3dm_header.bt_json_byte_length > 0:
b.batch_table = BatchTable.from_array(
b3dm_header, array[0 : b3dm_header.bt_json_byte_length]
)
return b