Source code for ascribe_link.mesh_utils
"""Mesh utilities for agent-generated data.
These utilities are designed to be used by the AI agent when generating
meshes with PyVista. They handle the common conversions needed to
submit mesh data via the submit_mesh tool.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import pyvista as pv
[docs]
def extract_mesh_data(
mesh: pv.PolyData, include_normals: bool = True
) -> tuple[list[list[float]], list[int], list[float] | None]:
"""Extract vertices, indices, and normals from a PyVista mesh.
Automatically triangulates the mesh if needed (converts quads to triangles)
and converts numpy arrays to plain Python lists.
Parameters
----------
mesh : pv.PolyData
A PyVista mesh object (sphere, box, cylinder, or any PolyData).
include_normals : bool
If True, compute and return normals.
Returns
-------
vertices : list[float]
Flat list of vertex coordinates [x, y, z, x, y, z, ...].
indices : list[int]
Flat list of vertex indices (every 3 = one triangle).
normals : list[float] | None
Flat list of normal components [nx, ny, nz, nx, ny, nz, ...] or None.
Example
-------
>>> import pyvista as pv
>>> from ascribe_link.mesh_utils import extract_mesh_data
>>>
>>> sphere = pv.Sphere(radius=1.0)
>>> vertices, indices, normals = extract_mesh_data(sphere)
"""
# Triangulate to ensure all faces are triangles (not quads)
triangulated = mesh.triangulate()
# Compute normals if requested
if include_normals:
triangulated = triangulated.compute_normals(
point_normals=True, cell_normals=False
)
# Extract vertices as flat list [x, y, z, x, y, z, ...]
vertices = triangulated.points.flatten().tolist()
# Extract face indices
# PyVista faces format: [n, i0, i1, i2, n, i0, i1, i2, ...]
# where n is the number of vertices per face (3 for triangles)
faces = triangulated.faces
if len(faces) > 0:
# Reshape to [n_faces, 4] and take columns 1:4 (skip the count)
indices = faces.reshape(-1, 4)[:, 1:].flatten().tolist()
else:
indices = []
# Extract normals as flat list
normals = None
if include_normals and triangulated.point_normals is not None:
normals = triangulated.point_normals.flatten().tolist()
return vertices, indices, normals
[docs]
def flatten_normals(normals_nested: list[list[float]]) -> list[float]:
"""Flatten nested normals [[nx, ny, nz], ...] to [nx, ny, nz, nx, ny, nz, ...].
Use this when you have normals from marching_cubes or other sources
that return per-vertex normal vectors as nested lists.
"""
result = []
for n in normals_nested:
result.extend(n)
return result