Skip to content

liblaf.melon.tri

Triangular surface geometry helpers.

Functions:

contains

contains(
    mesh: Mesh,
    points: Float[Tensor, "*p 3"],
    max_dist: float = inf,
) -> Bool[Tensor, "*p"]
Source code in src/liblaf/melon/tri/_contains.py
def contains(
    mesh: wp.Mesh, points: Float[Tensor, "*p 3"], max_dist: float = wp.inf
) -> Bool[Tensor, "*p"]:
    shape: Sequence[int] = points.shape[:-1]
    points: Float[Tensor, "p 3"] = points.reshape(-1, 3)
    n_points: int = points.shape[0]
    points_wp: wp.array = wp.from_torch(
        points.contiguous(), wp.vec3f, return_ctype=True
    )
    contains_wp: wp.array = wp.empty((n_points,), wp.bool)
    wp.launch(
        _mesh_query_point_kernel,
        dim=(n_points,),
        inputs=[mesh.id, points_wp, max_dist],
        outputs=[contains_wp],
        stream=warp_stream_from_torch(),
    )
    contains: Bool[Tensor, " p"] = wp.to_torch(contains_wp)
    contains: Bool[Tensor, "*p"] = contains.reshape(shape)
    return contains

edge_length

edge_length(mesh: PolyData) -> Float[ndarray, ' E']

Compute lengths for all extracted edges of a triangular surface.

Parameters:

  • mesh (PolyData) –

    Surface mesh.

Returns:

  • Float[ndarray, ' E']

    Edge lengths reported by PyVista's cell-size filter.

Source code in src/liblaf/melon/tri/_edge.py
def edge_length(mesh: pv.PolyData) -> Float[np.ndarray, " E"]:
    """Compute lengths for all extracted edges of a triangular surface.

    Args:
        mesh: Surface mesh.

    Returns:
        Edge lengths reported by PyVista's cell-size filter.
    """
    edges: pv.PolyData = mesh.extract_all_edges()
    edges: pv.PolyData = edges.compute_cell_sizes(length=True)
    return edges.cell_data["Length"]

extract_cells

extract_cells(
    mesh: Any,
    ind: int | VectorLike[int] | VectorLike[bool],
    *,
    invert: bool = False,
) -> PolyData

Extract selected cells from a surface-like mesh.

Parameters:

  • mesh (Any) –

    Object convertible to [pyvista.PolyData][].

  • ind (int | VectorLike[int] | VectorLike[bool]) –

    Cell index, integer indices, or boolean cell mask.

  • invert (bool, default: False ) –

    Extract all cells except the selected cells.

Returns:

  • PolyData

    Extracted surface.

Source code in src/liblaf/melon/tri/_group.py
def extract_cells(
    mesh: Any, ind: int | VectorLike[int] | VectorLike[bool], *, invert: bool = False
) -> pv.PolyData:
    """Extract selected cells from a surface-like mesh.

    Args:
        mesh: Object convertible to [`pyvista.PolyData`][pyvista.PolyData].
        ind: Cell index, integer indices, or boolean cell mask.
        invert: Extract all cells except the selected cells.

    Returns:
        Extracted surface.
    """
    mesh: pv.PolyData = io.as_polydata(mesh)
    ind: np.ndarray = np.asarray(ind)
    if np.isdtype(ind.dtype, "bool"):
        ind: np.ndarray = np.flatnonzero(ind)
    cells: pv.UnstructuredGrid = mesh.extract_cells(ind, invert=invert)
    surface: pv.PolyData = cells.extract_surface(algorithm=None)
    return surface

extract_groups

extract_groups(
    mesh: Any,
    groups: int | str | Iterable[int | str],
    *,
    invert: bool = False,
) -> PolyData

Extract cells whose GroupId matches numeric or named groups.

Parameters:

  • mesh (Any) –

    Mesh with GroupId cell data and optional GroupName field data.

  • groups (int | str | Iterable[int | str]) –

    Group id, group name, or iterable of ids and names.

  • invert (bool, default: False ) –

    Extract all cells outside the selected groups.

Returns:

  • PolyData

    Extracted surface.

Source code in src/liblaf/melon/tri/_group.py
def extract_groups(
    mesh: Any, groups: int | str | Iterable[int | str], *, invert: bool = False
) -> pv.PolyData:
    """Extract cells whose `GroupId` matches numeric or named groups.

    Args:
        mesh: Mesh with `GroupId` cell data and optional `GroupName` field data.
        groups: Group id, group name, or iterable of ids and names.
        invert: Extract all cells outside the selected groups.

    Returns:
        Extracted surface.
    """
    return extract_cells(mesh, select_groups(mesh, groups), invert=invert)

fill_point

fill_point(
    mesh: PolyData,
    mask: Bool[ndarray, " P"],
    names: Iterable[str] | None = None,
    *,
    limit: float = inf,
) -> PolyData
Source code in src/liblaf/melon/tri/_fill_point.py
def fill_point(
    mesh: pv.PolyData,
    mask: Bool[np.ndarray, " P"],
    names: Iterable[str] | None = None,
    *,
    limit: float = math.inf,
) -> pv.PolyData:
    if names is None:
        names: list[str] = mesh.point_data.keys()
    csgraph: scipy.sparse.coo_array = _make_csgraph(mesh)
    _dist_matrix, _predecessors, sources = scipy.sparse.csgraph.dijkstra(
        csgraph,
        directed=False,
        indices=np.flatnonzero(~mask),
        return_predecessors=True,
        limit=limit * mesh.length,
        min_only=True,
    )
    mask &= sources >= 0
    for name in names:
        data: Float[np.ndarray, "S ..."] = mesh.point_data[name]
        data[mask] = data[sources[mask]]
        mesh.point_data[name] = data
    return mesh

fix_normals

fix_normals(
    mesh: Any, *, multibody: bool | None = None
) -> Trimesh

Orient triangle normals with Trimesh and return a Trimesh object.

Parameters:

  • mesh (Any) –

    Object convertible to [trimesh.Trimesh][].

  • multibody (bool | None, default: None ) –

    Forwarded to [trimesh.Trimesh.fix_normals][].

Returns:

  • Trimesh

    Mesh with repaired normal orientation.

Source code in src/liblaf/melon/tri/_repair.py
def fix_normals(mesh: Any, *, multibody: bool | None = None) -> tm.Trimesh:
    """Orient triangle normals with Trimesh and return a Trimesh object.

    Args:
        mesh: Object convertible to [`trimesh.Trimesh`][trimesh.Trimesh].
        multibody: Forwarded to [`trimesh.Trimesh.fix_normals`][].

    Returns:
        Mesh with repaired normal orientation.
    """
    mesh: tm.Trimesh = io.as_trimesh(mesh)
    mesh.fix_normals(multibody=multibody)
    return mesh

geodesic_path

geodesic_path(
    mesh: PolyData, v_start: int, v_end: int
) -> PolyData

Find an edge-flip geodesic path between two surface vertices.

Parameters:

  • mesh (PolyData) –

    Triangular surface mesh.

  • v_start (int) –

    Start vertex index.

  • v_end (int) –

    End vertex index.

Returns:

  • PolyData

    Polyline following the computed geodesic path.

Source code in src/liblaf/melon/tri/_geodestic.py
def geodesic_path(mesh: pv.PolyData, v_start: int, v_end: int) -> pv.PolyData:
    """Find an edge-flip geodesic path between two surface vertices.

    Args:
        mesh: Triangular surface mesh.
        v_start: Start vertex index.
        v_end: End vertex index.

    Returns:
        Polyline following the computed geodesic path.
    """
    solver = pp3d.EdgeFlipGeodesicSolver(mesh.points, mesh.regular_faces)
    points: Float[np.ndarray, "p 3"] = solver.find_geodesic_path(v_start, v_end)
    return pv.lines_from_points(points)

implicit_distance

implicit_distance(
    mesh: Mesh,
    points: Float[Tensor, "*q 3"],
    max_dist: float = inf,
) -> Float[Tensor, "*q"]
Source code in src/liblaf/melon/tri/_implicit_distance.py
def implicit_distance(
    mesh: wp.Mesh, points: Float[Tensor, "*q 3"], max_dist: float = wp.inf
) -> Float[Tensor, "*q"]:
    shape: Sequence[int] = points.shape[:-1]
    points: Float[Tensor, "q 3"] = points.reshape(-1, 3)
    n_points: int = points.shape[0]
    points_wp: wp.array = wp.from_torch(points, wp.vec3f, return_ctype=True)
    distance_wp: wp.array = wp.empty((n_points,), wp.float32)
    wp.launch(
        _mesh_query_point_kernel,
        (n_points,),
        inputs=[mesh.id, points_wp, max_dist],
        outputs=[distance_wp],
        stream=warp_stream_from_torch(),
    )
    distance: Float[Tensor, " q"] = wp.to_torch(distance_wp)
    distance: Float[Tensor, "*q"] = distance.reshape(shape)
    return distance

query_ray

query_ray(
    mesh: Mesh,
    start: Float[Tensor, "*q 3"],
    direction: Float[Tensor, "*q 3"],
    max_t: float = inf,
) -> Float[Tensor, "*q"]
Source code in src/liblaf/melon/tri/_query_ray.py
def query_ray(
    mesh: wp.Mesh,
    start: Float[Tensor, "*q 3"],
    direction: Float[Tensor, "*q 3"],
    max_t: float = wp.inf,
) -> Float[Tensor, "*q"]:
    shape: Sequence[int] = start.shape[:-1]
    start: Float[Tensor, "q 3"] = start.reshape(-1, 3).contiguous()
    direction: Float[Tensor, "q 3"] = direction.reshape(-1, 3).contiguous()
    start_wp: wp.array = wp.from_torch(start, wp.vec3f, return_ctype=True)
    direction_wp: wp.array = wp.from_torch(direction, wp.vec3f, return_ctype=True)
    distance: Float[Tensor, " q"] = torch.empty((start.shape[0],), dtype=torch.float32)
    distance_wp: wp.array = wp.from_torch(distance, wp.float32, return_ctype=True)
    wp.launch(
        _mesh_query_ray_kernel,
        dim=(start.shape[0],),
        inputs=[mesh.id, start_wp, direction_wp, max_t],
        outputs=[distance_wp],
        stream=warp_stream_from_torch(),
    )
    distance: Float[Tensor, "*q"] = distance.reshape(shape)
    return distance

select_groups

select_groups(
    mesh: Any,
    groups: int | str | Iterable[int | str],
    *,
    invert: bool = False,
    preference: FieldAssociation = CELL,
) -> Bool[ndarray, " cells"]

Build a boolean mask for cells in selected groups.

Parameters:

  • mesh (Any) –

    Mesh with a group-id array.

  • groups (int | str | Iterable[int | str]) –

    Group id, group name, or iterable of ids and names.

  • invert (bool, default: False ) –

    Invert the selection mask.

  • preference (FieldAssociation, default: CELL ) –

    PyVista association used to resolve the GroupId array.

Returns:

  • Bool[ndarray, ' cells']

    Boolean mask over mesh cells.

Source code in src/liblaf/melon/tri/_group.py
def select_groups(
    mesh: Any,
    groups: int | str | Iterable[int | str],
    *,
    invert: bool = False,
    preference: pv.FieldAssociation = pv.FieldAssociation.CELL,
) -> Bool[np.ndarray, " cells"]:
    """Build a boolean mask for cells in selected groups.

    Args:
        mesh: Mesh with a group-id array.
        groups: Group id, group name, or iterable of ids and names.
        invert: Invert the selection mask.
        preference: PyVista association used to resolve the `GroupId` array.

    Returns:
        Boolean mask over mesh cells.
    """
    mesh: pv.PolyData = io.as_polydata(mesh)
    group_ids: list[int] = _as_group_ids(mesh, groups)
    mask: Bool[np.ndarray, " cells"] = np.isin(
        mesh.get_array("GroupId", preference),  # ty:ignore[invalid-argument-type]
        group_ids,
        invert=invert,
    )
    return mask