Skip to content

liblaf.melon.tri Β€

Classes:

  • MeshQuery –

Functions:

  • cell_data_to_point_data –
  • compute_area –
  • compute_point_area –
  • contains –
  • extract_cells –
  • extract_groups –
  • extract_points –
  • fast_wrapping –
  • fix_inversion –
  • icp –
  • intersection –
  • is_volume –
  • merge_points –
  • point_data_to_cell_data –
  • select_groups –
  • signed_distance –

MeshQuery Β€

Parameters:

Methods:

  • bounds_contains –
  • contains –
  • signed_distance –

Attributes:

bounds property Β€

bounds: Float[ndarray, '2 3']

mesh instance-attribute Β€

mesh: Any

mesh_tm cached property Β€

mesh_tm: Trimesh

mesh_wp cached property Β€

mesh_wp: Mesh

scale property Β€

scale: float

bounds_contains Β€

bounds_contains(pcl: Any) -> Bool[Array, ' N']
Source code in src/liblaf/melon/tri/_query.py
36
37
38
39
def bounds_contains(self, pcl: Any) -> Bool[Array, " N"]:
    pcl: pv.PointSet = io.as_pointset(pcl)
    points_jax: Float[Array, " N 3"] = jnp.asarray(pcl.points, jnp.float32)
    return bounds_contains(self.bounds, points_jax)

contains Β€

contains(pcl: Any) -> Bool[Array, ' N']
Source code in src/liblaf/melon/tri/_query.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def contains(self, pcl: Any) -> Bool[Array, " N"]:
    pcl: pv.PointSet = io.as_pointset(pcl)
    points_jax: Float[Array, " N 3"] = jnp.asarray(pcl.points, jnp.float32)
    output_jax: Bool[Array, " N"] = bounds_contains(self.bounds, points_jax)
    points_wp: wp.array = wp.from_jax(points_jax[output_jax], dtype=wp.vec3f)
    output_wp: wp.array = wp.zeros(points_wp.shape, dtype=wp.bool)
    wp.launch(
        _contains_kernel,
        dim=points_wp.shape,
        inputs=[self.mesh_wp.id, points_wp, self.scale],
        outputs=[output_wp],
    )
    output_jax = output_jax.at[output_jax].set(wp.to_jax(output_wp))
    return output_jax

signed_distance Β€

signed_distance(pcl: Any) -> Float[Array, ' N']
Source code in src/liblaf/melon/tri/_query.py
56
57
58
59
60
61
62
63
64
65
66
67
68
def signed_distance(self, pcl: Any) -> Float[Array, " N"]:
    pcl: pv.PointSet = io.as_pointset(pcl)
    points_jax: Float[Array, "N 3"] = jnp.asarray(pcl.points, jnp.float32)
    points_wp: wp.array = wp.from_jax(points_jax, wp.vec3f)
    output_wp: wp.array = wp.zeros(points_wp.shape, wp.float32)
    wp.launch(
        _signed_distance_kernel,
        dim=points_wp.shape,
        inputs=[self.mesh_wp.id, points_wp, self.scale],
        outputs=[output_wp],
    )
    output_jax: Float[Array, " N"] = wp.to_jax(output_wp)
    return output_jax

cell_data_to_point_data Β€

cell_data_to_point_data(
    mesh: PolyData, data: str | Iterable[str] | None = None
) -> PolyData
Source code in src/liblaf/melon/tri/_transform_data.py
 8
 9
10
11
12
13
14
15
16
17
18
19
def cell_data_to_point_data(
    mesh: pv.PolyData, data: str | Iterable[str] | None = None
) -> pv.PolyData:
    mesh: pv.PolyData = io.as_polydata(mesh)
    data: Collection[str] = utils.as_names(data, mesh.cell_data)
    source: pv.PolyData = pv.PolyData()
    source.copy_structure(mesh)
    source.cell_data.update({name: mesh.cell_data[name] for name in data}, copy=False)
    output: pv.PolyData = source.cell_data_to_point_data()  # pyright: ignore[reportAssignmentType]
    for name in data:
        mesh.point_data[name] = output.point_data[name]
    return mesh

compute_area Β€

compute_area(mesh: Any) -> PolyData
Source code in src/liblaf/melon/tri/_compute.py
10
11
12
13
def compute_area(mesh: Any) -> pv.PolyData:
    mesh: pv.PolyData = io.as_polydata(mesh)
    result: pv.PolyData = mesh.compute_cell_sizes(length=False, area=True, volume=False)  # pyright: ignore[reportAssignmentType]
    return result

compute_point_area Β€

compute_point_area(mesh: Any) -> PolyData
Source code in src/liblaf/melon/tri/_compute.py
16
17
18
19
20
21
22
23
def compute_point_area(mesh: Any) -> pv.PolyData:
    mesh: pv.PolyData = compute_area(mesh)
    mesh.point_data["Area"] = jax.ops.segment_sum(  # pyright: ignore[reportArgumentType]
        einops.repeat(mesh.cell_data["Area"], "c -> (c p)", p=3),
        mesh.regular_faces.flatten(),
        num_segments=mesh.n_points,
    )
    return mesh

contains Β€

contains(mesh: Any, pcl: Any) -> Bool[Array, ' N']
Source code in src/liblaf/melon/tri/_query.py
71
72
73
def contains(mesh: Any, pcl: Any) -> Bool[Array, " N"]:
    solver = MeshQuery(mesh)
    return solver.contains(pcl)

extract_cells Β€

extract_cells(
    mesh: Any,
    ind: int | VectorLike[int] | VectorLike[bool],
    *,
    invert: bool = False,
) -> PolyData
Source code in src/liblaf/melon/tri/_extract.py
19
20
21
22
23
24
25
26
27
28
def extract_cells(
    mesh: Any, ind: int | VectorLike[int] | VectorLike[bool], *, invert: bool = False
) -> pv.PolyData:
    mesh: pv.PolyData = io.as_polydata(mesh)
    ind = np.asarray(ind)
    if np.isdtype(ind.dtype, "bool"):
        ind = np.flatnonzero(ind)
    cells: pv.UnstructuredGrid = mesh.extract_cells(ind, invert=invert)  # pyright: ignore[reportAssignmentType]
    surface: pv.PolyData = cells.extract_surface(algorithm=None)  # pyright: ignore[reportAssignmentType]
    return surface

extract_groups Β€

extract_groups(
    mesh: Any,
    groups: int | str | Iterable[int | str],
    *,
    invert: bool = False,
) -> PolyData
Source code in src/liblaf/melon/tri/_extract.py
31
32
33
34
def extract_groups(
    mesh: Any, groups: int | str | Iterable[int | str], *, invert: bool = False
) -> pv.PolyData:
    return extract_cells(mesh, select_groups(mesh, groups), invert=invert)

extract_points Β€

extract_points(
    mesh: Any,
    ind: int | VectorLike[int] | VectorLike[bool],
    *,
    adjacent_cells: bool = True,
    include_cells: bool = True,
) -> PolyData
Source code in src/liblaf/melon/tri/_extract.py
37
38
39
40
41
42
43
44
45
46
47
48
49
def extract_points(
    mesh: Any,
    ind: int | VectorLike[int] | VectorLike[bool],
    *,
    adjacent_cells: bool = True,
    include_cells: bool = True,
) -> pv.PolyData:
    mesh: pv.PolyData = io.as_polydata(mesh)
    points: pv.UnstructuredGrid = mesh.extract_points(
        ind, adjacent_cells=adjacent_cells, include_cells=include_cells
    )  # pyright: ignore[reportAssignmentType]
    surface: pv.PolyData = points.extract_surface(algorithm=None)  # pyright: ignore[reportAssignmentType]
    return surface

fast_wrapping Β€

fast_wrapping(
    source: Any,
    target: Any,
    *,
    reflection: bool = True,
    translation: bool = True,
    scale: bool = True,
    source_landmarks: Float[ArrayLike, "L 3"] | None = None,
    target_landmarks: Float[ArrayLike, "L 3"] | None = None,
    free_polygons_floating: Bool[ArrayLike, " full"]
    | Integer[ArrayLike, " free"]
    | None = None,
    verbose: bool = False,
) -> PolyData
Source code in src/liblaf/melon/tri/_wrapping.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def fast_wrapping(
    source: Any,
    target: Any,
    *,
    # procrustes options
    reflection: bool = True,
    translation: bool = True,
    scale: bool = True,
    # FastWrapping options
    source_landmarks: Float[ArrayLike, "L 3"] | None = None,
    target_landmarks: Float[ArrayLike, "L 3"] | None = None,
    free_polygons_floating: Bool[ArrayLike, " full"]
    | Integer[ArrayLike, " free"]
    | None = None,
    verbose: bool = False,
) -> pv.PolyData:
    from liblaf.melon.ext import wrap

    if source_landmarks is not None and target_landmarks is not None:
        matrix: Float[np.ndarray, "4 4"]
        transformed: Float[np.ndarray, "L 3"]
        cost: float
        matrix, transformed, cost = tm.registration.procrustes(
            source_landmarks,
            target_landmarks,
            reflection=reflection,
            translation=translation,
            scale=scale,
        )
        logger.debug("procrustes cost: %g", cost)
        source: pv.PolyData = io.as_polydata(source)
        source = source.transform(matrix, inplace=False)  # pyright: ignore[reportAssignmentType]
        source_landmarks = transformed
    result: pv.PolyData = wrap.fast_wrapping(
        source,
        target,
        source_landmarks=source_landmarks,
        target_landmarks=target_landmarks,
        free_polygons_floating=free_polygons_floating,
        verbose=verbose,
    )
    return result

fix_inversion Β€

fix_inversion(mesh: PolyData) -> PolyData
Source code in src/liblaf/melon/tri/_repair.py
 7
 8
 9
10
11
12
def fix_inversion(mesh: pv.PolyData) -> pv.PolyData:
    # pyvista.PolyData.compute_normals(auto_orient_normals=True) sometimes produces inverted normals
    mesh_tm: tm.Trimesh = io.as_trimesh(mesh)
    mesh_tm = mesh_tm.fix_normals()
    mesh.copy_structure(io.as_polydata(mesh_tm))
    return mesh

icp Β€

icp(
    source: Any,
    target: Any,
    *,
    n_samples: int = 10000,
    initial: Float[ArrayLike, "4 4"] | None = None,
    threshold: float = 1e-06,
    max_iterations: int = 100,
    reflection: bool = True,
    translation: bool = True,
    scale: bool = True,
) -> tuple[Float[ndarray, "4 4"], float]
Source code in src/liblaf/melon/tri/_icp.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def icp(
    source: Any,
    target: Any,
    *,
    n_samples: int = 10000,
    initial: Float[ArrayLike, "4 4"] | None = None,
    threshold: float = 1e-6,
    max_iterations: int = 100,
    reflection: bool = True,
    translation: bool = True,
    scale: bool = True,
) -> tuple[Float[np.ndarray, "4 4"], float]:
    source: tm.Trimesh = io.as_trimesh(source)
    target: tm.Trimesh = io.as_trimesh(target)
    matrix: Float[np.ndarray, "4 4"]
    cost: float
    matrix, _, cost = tm.registration.icp(
        source.sample(n_samples),
        target.sample(n_samples),
        initial=initial,
        threshold=threshold,
        max_iterations=max_iterations,
        reflection=reflection,
        translation=translation,
        scale=scale,
    )
    return matrix, cost

intersection Β€

intersection(
    meshes: Sequence[Any],
    *,
    engine: Literal["blender", "manifold"] | None = None,
    check_volume: bool = True,
    **kwargs,
) -> Trimesh
Source code in src/liblaf/melon/tri/_boolean.py
 7
 8
 9
10
11
12
13
14
15
16
def intersection(
    meshes: Sequence[Any],
    *,
    engine: Literal["blender", "manifold"] | None = None,
    check_volume: bool = True,
    **kwargs,
) -> tm.Trimesh:
    return tm.boolean.intersection(
        meshes, engine=engine, check_volume=check_volume, **kwargs
    )

is_volume Β€

is_volume(mesh: Any) -> bool
Source code in src/liblaf/melon/tri/_is_volume.py
 8
 9
10
def is_volume(mesh: Any) -> bool:
    mesh: tm.Trimesh = io.as_trimesh(mesh)
    return mesh.is_volume

merge_points Β€

merge_points(mesh: Any, tolerance: float = 0.0) -> PolyData
Source code in src/liblaf/melon/tri/_merge_points.py
 8
 9
10
11
12
def merge_points(mesh: Any, tolerance: float = 0.0) -> pv.PolyData:
    mesh: pv.PolyData = io.as_polydata(mesh)
    result: pv.PolyData = cast("pv.PolyData", mesh.merge_points(tolerance=tolerance))
    result.field_data.update(mesh.field_data)
    return result

point_data_to_cell_data Β€

point_data_to_cell_data(
    mesh: PolyData, data: str | Iterable[str] | None = None
) -> PolyData
Source code in src/liblaf/melon/tri/_transform_data.py
22
23
24
25
26
27
28
29
30
31
32
33
def point_data_to_cell_data(
    mesh: pv.PolyData, data: str | Iterable[str] | None = None
) -> pv.PolyData:
    mesh: pv.PolyData = io.as_polydata(mesh)
    data: Collection[str] = utils.as_names(data, mesh.point_data)
    source: pv.PolyData = pv.PolyData()
    source.copy_structure(mesh)
    source.point_data.update({name: mesh.point_data[name] for name in data}, copy=False)
    output: pv.PolyData = source.point_data_to_cell_data()  # pyright: ignore[reportAssignmentType]
    for name in data:
        mesh.cell_data[name] = output.cell_data[name]
    return mesh

select_groups Β€

select_groups(
    mesh: Any,
    groups: int | str | Iterable[int | str],
    *,
    invert: bool = False,
) -> Bool[ndarray, " cells"]
Source code in src/liblaf/melon/tri/_group.py
12
13
14
15
16
17
18
19
20
def select_groups(
    mesh: Any, groups: int | str | Iterable[int | str], *, invert: bool = False
) -> Bool[np.ndarray, " cells"]:
    mesh: pv.PolyData = io.as_polydata(mesh)
    group_ids: list[int] = as_group_ids(mesh, groups)
    mask: Bool[np.ndarray, " cells"] = np.isin(
        compat.get_group_id(mesh), group_ids, invert=invert
    )
    return mask

signed_distance Β€

signed_distance(mesh: Any, pcl: Any) -> Float[Array, ' N']
Source code in src/liblaf/melon/tri/_query.py
76
77
78
def signed_distance(mesh: Any, pcl: Any) -> Float[Array, " N"]:
    solver = MeshQuery(mesh)
    return solver.signed_distance(pcl)