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)

Gallery generated by Sphinx-Gallery