Note
Go to the end to download the full example code.
Mesh Export#
Block models can be exported as triangulated surface meshes.
PLY is the canonical format for lossless storage — it preserves all vertex and face attributes together with geometry metadata. GLB (glTF 2.0 binary) is a derived format suited for exchange with 3D viewers and optionally carries vertex colours derived from a scalar attribute.
import tempfile
from pathlib import Path
from parq_blockmodel import ParquetBlockModel
Create a Block Model#
Use the toy block-model helper to create a 5 × 5 × 5 dense model with a synthetic ellipsoidal grade distribution.
temp_dir = Path(tempfile.gettempdir()) / "mesh_export_example"
temp_dir.mkdir(parents=True, exist_ok=True)
pbm: ParquetBlockModel = ParquetBlockModel.create_toy_blockmodel(
filename=temp_dir / "sample.parquet",
shape=(5, 5, 5),
block_size=(10.0, 10.0, 10.0),
corner=(0.0, 0.0, 0.0),
grade_name="grade",
grade_min=50.0,
grade_max=100.0,
)
pbm
ParquetBlockModel(name=sample, path=/tmp/mesh_export_example/sample.pbm)
Triangulate the Mesh#
triangulate() returns a
TriangleMesh containing vertices, triangle
faces, and per-vertex/face attribute arrays. By default only the exterior
surface is generated (surface_only=True).
mesh = pbm.triangulate(attributes=["grade"], surface_only=True)
print(f"Vertices : {mesh.n_vertices}")
print(f"Faces : {mesh.n_faces}")
print(f"Attributes: {list(mesh.vertex_attributes.keys())}")
Vertices : 1000
Faces : 1500
Attributes: ['grade']
Export to PLY#
PLY is the canonical, lossless format. The file embeds geometry metadata
as comments and writes per-vertex i, j, k indices alongside
x, y, z so that every vertex can be traced back to its source
block.
ply_path = temp_dir / "sample.ply"
pbm.to_ply(ply_path, attributes=["grade"])
# Preview the header of the written file.
with open(ply_path) as fh:
for line in fh:
print(line, end="")
if line.strip() == "end_header":
break
ply
format ascii 1.0
comment corner=(0.0, 0.0, 0.0)
comment block_size=(10.0, 10.0, 10.0)
comment shape=(5, 5, 5)
comment axis_u=(1.0, 0.0, 0.0)
comment axis_v=(0.0, 1.0, 0.0)
comment axis_w=(0.0, 0.0, 1.0)
comment surface_only=True
comment sparse=False
element vertex 1000
property float x
property float y
property float z
property int i
property int j
property int k
property float grade
element face 1500
property list uchar int vertex_indices
property int i
property int j
property int k
property float grade
end_header
Export to GLB#
GLB (glTF 2.0 binary) is a single-file exchange format suitable for web
viewers such as Babylon.js, Three.js and Cesium. Passing
texture_attribute maps that scalar to vertex colours using the
requested Matplotlib colormap.
glb_path = temp_dir / "sample.glb"
pbm.to_glb(glb_path, texture_attribute="grade", colormap="viridis")
print(f"GLB file size: {glb_path.stat().st_size:,} bytes")
# Verify the GLB magic number.
with open(glb_path, "rb") as fh:
print(f"GLB magic : {fh.read(4)}")
GLB file size: 35,164 bytes
GLB magic : b'glTF'
Rotated Geometry#
Rotation is handled transparently. The axis vectors stored in
axis_u,
axis_v and
axis_w are applied when
computing world-space vertex coordinates, so no special handling is
required by the caller.
pbm_rot: ParquetBlockModel = ParquetBlockModel.create_toy_blockmodel(
filename=temp_dir / "rotated.parquet",
shape=(3, 3, 3),
block_size=(5.0, 5.0, 5.0),
corner=(0.0, 0.0, 0.0),
axis_azimuth=30.0,
axis_dip=15.0,
axis_plunge=0.0,
)
print(f"axis_u: {pbm_rot.geometry.axis_u}")
print(f"axis_v: {pbm_rot.geometry.axis_v}")
print(f"axis_w: {pbm_rot.geometry.axis_w}")
glb_rot_path = temp_dir / "rotated.glb"
pbm_rot.to_glb(glb_rot_path, texture_attribute="grade")
print(f"Rotated GLB file size: {glb_rot_path.stat().st_size:,} bytes")
axis_u: (0.8660254037844387, 0.49999999999999994, 0.0)
axis_v: (-0.4829629131445341, 0.8365163037378079, 0.25881904510252074)
axis_w: (0.12940952255126034, -0.2241438680420134, 0.9659258262890683)
Rotated GLB file size: 8,692 bytes
Total running time of the script: (0 minutes 0.188 seconds)