Source code for nanomesh.image2mesh._mesher3d._helpers

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

from nanomesh._doc import doc
from nanomesh.region_markers import RegionMarker, RegionMarkerList
from nanomesh.utils import pairwise

from ._bounding_box import BoundingBox

if TYPE_CHECKING:
    from nanomesh.mesh import TriangleMesh


[docs]@doc(prefix='Pad a contour triangle mesh (3D)') def pad( mesh: TriangleMesh, *, side: str, width: int, label: int = None, name: str = None, ) -> TriangleMesh: """{prefix}. Note that tetgen will assign a different label for physically separate regions, even when they are given the same label/name. Parameters ---------- mesh : TriangleMesh The mesh to pad. side : str Side to pad, must be one of `left`, `right`, `top`, `bottom`, `back`, `front`. width : int Width of the padded area. label : int, optional The label to assign to the padded area. If not defined, generates the next unique label based on the existing ones. name : str, optional Name of the added region. Note that in case of conflicts, the `label` takes presedence over the `name`. Returns ------- new_mesh : TriangleMesh Padded contour triangle mesh. Raises ------ ValueError When the value of `side` is invalid. """ labels = mesh.region_markers.labels names = mesh.region_markers.names if (label in labels) and (name is None): name = [m.name for m in mesh.region_markers if m.label == label][0] if name and (name in names) and (label is None): label = [m.label for m in mesh.region_markers if m.name == name][0] if label is None: label = max(max(labels) + 1, 2) if labels else 2 if width == 0: return mesh bbox = BoundingBox.from_points(mesh.points) if side == 'top': edge_col = 2 edge_value = bbox.zmax extra_coords = np.array([ [bbox.xmin, bbox.ymin, bbox.zmax + width], [bbox.xmin, bbox.ymax, bbox.zmax + width], [bbox.xmax, bbox.ymin, bbox.zmax + width], [bbox.xmax, bbox.ymax, bbox.zmax + width], ]) column_order = (0, 1, 1, 0) elif side == 'bottom': edge_col = 2 edge_value = bbox.zmin extra_coords = np.array([ [bbox.xmin, bbox.ymin, bbox.zmin - width], [bbox.xmin, bbox.ymax, bbox.zmin - width], [bbox.xmax, bbox.ymin, bbox.zmin - width], [bbox.xmax, bbox.ymax, bbox.zmin - width], ]) column_order = (0, 1, 1, 0) elif side == 'left': edge_col = 1 edge_value = bbox.ymin extra_coords = np.array([ [bbox.xmin, bbox.ymin - width, bbox.zmin], [bbox.xmin, bbox.ymin - width, bbox.zmax], [bbox.xmax, bbox.ymin - width, bbox.zmin], [bbox.xmax, bbox.ymin - width, bbox.zmax], ]) column_order = (0, 2, 2, 0) elif side == 'right': edge_col = 1 edge_value = bbox.ymax extra_coords = np.array([ [bbox.xmin, bbox.ymax + width, bbox.zmin], [bbox.xmin, bbox.ymax + width, bbox.zmax], [bbox.xmax, bbox.ymax + width, bbox.zmin], [bbox.xmax, bbox.ymax + width, bbox.zmax], ]) column_order = (0, 2, 2, 0) elif side == 'front': edge_col = 0 edge_value = bbox.xmin extra_coords = np.array([ [bbox.xmin - width, bbox.ymin, bbox.zmin], [bbox.xmin - width, bbox.ymin, bbox.zmax], [bbox.xmin - width, bbox.ymax, bbox.zmin], [bbox.xmin - width, bbox.ymax, bbox.zmax], ]) column_order = (1, 2, 2, 1) elif side == 'back': edge_col = 0 edge_value = bbox.xmax extra_coords = np.array([ [bbox.xmax + width, bbox.ymin, bbox.zmin], [bbox.xmax + width, bbox.ymin, bbox.zmax], [bbox.xmax + width, bbox.ymax, bbox.zmin], [bbox.xmax + width, bbox.ymax, bbox.zmax], ]) column_order = (1, 2, 2, 1) else: raise ValueError('Side must be one of `right`, `left`, `bottom`' f'`top`, `front`, `back`. Got {side=}') n_points = len(mesh.points) all_points = np.vstack([mesh.points, extra_coords]) new_triangles = [ np.array((0, 1, 2)) + n_points, np.array((3, 1, 2)) + n_points, ] for corner, col in zip(extra_coords, column_order): connect_to = np.argwhere((all_points[:, edge_col] == edge_value) & (all_points[:, col] == corner[col])) additional_points = np.argwhere( extra_coords[:, col] == corner[col]) + n_points first, last = additional_points first_point = all_points[first] sorted_by_distance = np.argsort( np.linalg.norm(first_point - all_points[connect_to].squeeze(), axis=1)) connect_to = connect_to[sorted_by_distance] connect_to = np.vstack([connect_to, last]).squeeze() for pair in pairwise(connect_to): tri = np.hstack((first, pair)) new_triangles.append(tri) new_triangles = np.array(new_triangles).squeeze() cells = np.vstack([mesh.cells, new_triangles]) # add marker for new region center = extra_coords.mean(axis=0) center[col] = (center[col] + edge_value) / 2 region_markers = RegionMarkerList( (*mesh.region_markers, RegionMarker(label, center, name))) new_mesh = mesh.__class__( points=all_points, cells=cells, region_markers=region_markers, ) return new_mesh