Skip to content

icon_positions

Utilities to store and compute 3D icon positions.

Classes:

Name Description
IconPosition

Helper class to compute and store positions for icons.

Functions:

Name Description
height_at_xy

Compute a value for the height of a mesh at the given x and y coordinates, based on vertices around the given position.

icon_position_from_mesh

Compute a hopefully good position for an icon for the given mesh.

IconPosition

Helper class to compute and store positions for icons.

Source code in python/src/data_pipeline/utils/icon_positions.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
class IconPosition:
    """
    Helper class to compute and store positions for icons.
    """

    def __init__(self, x: float, y: float, z: float) -> None:
        self.x = x
        self.y = y
        self.z = z

    @classmethod
    def from_list(cls, xyz: list[float]) -> Self:
        if len(xyz) != 3:
            raise ValueError(
                f"IconPosition.from_list requires a input of length 3, not '{xyz}'"
            )
        return cls(x=xyz[0], y=xyz[1], z=xyz[2])

    @classmethod
    def from_mesh(cls, mesh: trimesh.Trimesh, z_offset: float) -> Self:
        if not isinstance(mesh, trimesh.Trimesh):
            raise TypeError(
                f"IconPosition.from_geometry expects a `MultiSurface` instance, not `{type(mesh)}`."
            )

        pos_array = icon_position_from_mesh(mesh=mesh, z_offset=z_offset)
        return cls(x=pos_array[0], y=pos_array[1], z=pos_array[2])

    def to_list(self) -> list[float]:
        return [self.x, self.y, self.z]

height_at_xy(mesh, xy, radii=[1, 3, 10, 30, 100], z_offset=0.5)

Compute a value for the height of a mesh at the given x and y coordinates, based on vertices around the given position. It selects points in a radius around the queried position and takes the highest value.

Parameters:

Name Type Description Default
mesh trimesh.Trimesh

The mesh to compute the height of.

required
xy numpy.typing.NDArray[numpy.float64]

The (N, 2) array storing the position to compute the height at.

required
radii list[float]

The radii to try successively if no point is found in the previous one. By default [1, 3, 10, 30, 100].

[1, 3, 10, 30, 100]
z_offset float

The offset to add to the final value. By default 0.5.

0.5

Returns:

Type Description
float

The final computed height with the offset.

Raises:

Type Description
RuntimeError

If no point was found after trying all radii.

Source code in python/src/data_pipeline/utils/icon_positions.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
56
57
def height_at_xy(
    mesh: trimesh.Trimesh,
    xy: NDArray[np.float64],
    radii: list[float] = [1, 3, 10, 30, 100],
    z_offset: float = 0.5,
) -> float:
    """
    Compute a value for the height of a mesh at the given x and y coordinates, based on vertices around the given position.
    It selects points in a radius around the queried position and takes the highest value.

    Parameters
    ----------
    mesh : trimesh.Trimesh
        The mesh to compute the height of.
    xy : NDArray[np.float64]
        The (N, 2) array storing the position to compute the height at.
    radii : list[float], optional
        The radii to try successively if no point is found in the previous one.
        By default `[1, 3, 10, 30, 100]`.
    z_offset : float, optional
        The offset to add to the final value.
        By default `0.5`.

    Returns
    -------
    float
        The final computed height with the offset.

    Raises
    ------
    RuntimeError
        If no point was found after trying all radii.
    """
    # Compute distances from every vertex to the XY query point
    verts_xy = mesh.vertices[:, :2]
    dists = np.linalg.norm(verts_xy - xy, axis=1)

    # Keep vertices inside the radius
    for radius in radii:
        mask = dists <= radius
        if np.any(mask):
            return mesh.vertices[mask, 2].max() + z_offset

    raise RuntimeError("No vertex was found in any of the given radii from the point.")

icon_position_from_mesh(mesh, neighbourhood_radii=[1, 3, 10, 30, 100], z_offset=0.5)

Compute a hopefully good position for an icon for the given mesh. Puts a point at the center of the axis-aligned bounding box and computes a height for it based on the points in the mesh that are closest to it.

Parameters:

Name Type Description Default
mesh trimesh.Trimesh

The mesh to compute an icon position for.

required
neighbourhood_radii list[float]

The radii to try successively if no point is found in the previous one. By default [1, 3, 10, 30, 100].

[1, 3, 10, 30, 100]
z_offset float

The offset to add to the final value. By default 0.5.

0.5

Returns:

Type Description
numpy.ndarray

The final icon position.

Source code in python/src/data_pipeline/utils/icon_positions.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def icon_position_from_mesh(
    mesh: trimesh.Trimesh,
    neighbourhood_radii: list[float] = [1, 3, 10, 30, 100],
    z_offset: float = 0.5,
) -> np.ndarray:
    """
    Compute a hopefully good position for an icon for the given mesh.
    Puts a point at the center of the axis-aligned bounding box and computes a height for it based on the points in the mesh that are closest to it.

    Parameters
    ----------
    mesh : trimesh.Trimesh
        The mesh to compute an icon position for.
    neighbourhood_radii : list[float], optional
        The radii to try successively if no point is found in the previous one.
        By default `[1, 3, 10, 30, 100]`.
    z_offset : float, optional
        The offset to add to the final value.
        By default `0.5`.

    Returns
    -------
    np.ndarray
        The final icon position.
    """
    logging.debug("Start computing icon position...")

    # # Flatten the mesh
    # flat_mesh = flatten_trimesh(mesh=mesh)

    # Find the centroid
    bounds = mesh.bounds
    xy_center = np.array((bounds[0, :2] + bounds[1, :2]) / 2.0, dtype=np.float64)
    chosen_xy = xy_center

    # Compute a proper Z value
    chosen_z = height_at_xy(
        mesh, chosen_xy, radii=neighbourhood_radii, z_offset=z_offset
    )

    return np.array([chosen_xy[0], chosen_xy[1], chosen_z])