### ===================================================================================================================
### Functions to generate mesh for the vibration simulation
### ===================================================================================================================
# Copyright ©2026 Haskoning Nederland B.V.
### ===================================================================================================================
### 1. Import modules
### ===================================================================================================================
# General imports
import triangle
import numpy as np
from typing import Optional, List, Tuple
from collections.abc import Iterable, Sized, Sequence
# References for functions and classes in the haskoning_atr_tools package
from haskoning_atr_tools.vibration_contour_plot.mesh.mesh import Mesh
from haskoning_atr_tools.vibration_contour_plot.mesh.mesh_node import MeshNode
from haskoning_atr_tools.vibration_contour_plot.mesh.mesh_element import MeshElement
### ===================================================================================================================
### 2. Function to generate mesh for the vibration simulation
### ===================================================================================================================
[docs]
def generate_constrained_mesh(
project, x_range: Tuple[float, float], y_range: Tuple[float, float], mesh_area: float,
constraint_points: Optional[List] = None, line_segments: Optional[List[List[float]]] = None) -> Mesh:
"""
Function to generate mesh for the vibration simulation. The mesh is constrained for the positions of equipment and
targets.
Input:
- project (obj): Project object containing collections of objects and project variables.
- x_range (tuple): Tuple of lower and upper bound of the x-axis limits, values in [m].
- y_range (tuple): Tuple of lower and upper bound of the y-axis limits, values in [m].
- mesh_area (float): Maximum size of the mesh elements, in [m2].
- constraint_points (list of lists of 2 floats): The user can specify additional points that are used to
constrain the mesh on. Default value is None.
- line_segments (list of lists of 2 floats): The user can specify additional lines that are used to constrain
the mesh on. Default value is None.
Output:
- Returns the generated mesh.
- The mesh is created within the x- and y- range defined by the user. The equipment and targets in the project
constrain the mesh.
- The created mesh is added to the project.
"""
def is_valid_range_input(x) -> bool:
""" Checks if the input for range is valid."""
if isinstance(x, (str, bytes)):
return False
if not isinstance(x, Sized):
return False
if len(x) != 2:
return False
if not isinstance(x, Iterable):
return False
if isinstance(x, Sequence):
a = x[0]
b = x[1]
else:
a, b = tuple(x)
if isinstance(a, bool) or isinstance(b, bool):
return False
if not (isinstance(a, (float, int)) and isinstance(b, (float, int))):
return False
return a < b
# Collect constraining points
if constraint_points is None:
constraint_points = []
constraint_points += \
[(obj.coordinates[0], obj.coordinates[1]) for obj in project.equipment_list + project.target_list]
# 1. Process line segments: [[x1, y1], [x2, y2]] -> ((x1, y1), (x2, y2))
processed_line_segments = []
if line_segments:
# seg is the inner list: [[x1, y1], [x2, y2]]
for seg in line_segments:
# pt is a coordinate pair: [x, y]
processed_seg = tuple(tuple(pt) for pt in seg)
processed_line_segments.append(processed_seg)
# Rectangle boundary points
if not is_valid_range_input(x=x_range):
raise ValueError(
f"ERROR: Input for the x-range is invalid. Input should be a tuple with two floats, first one smaller than "
f"the second one. Provided was {x_range}.")
if not is_valid_range_input(x=y_range):
raise ValueError(
f"ERROR: Input for the y-range is invalid. Input should be a tuple with two floats, first one smaller than "
f"the second one. Provided was {y_range}.")
rect = [
(x_range[0], y_range[0]),
(x_range[1], y_range[0]),
(x_range[1], y_range[1]),
(x_range[0], y_range[1])]
points = rect[:]
point_idx = {pt: i for i, pt in enumerate(points)}
# Filter constrained points within range
constraint_points = list(set(
[pt for pt in constraint_points if x_range[0] <= pt[0] <= x_range[1] and y_range[0] <= pt[1] <= y_range[1]]))
# Add line segment points
# TODO Add filter for line segments
if processed_line_segments:
for seg in processed_line_segments:
for pt in seg:
if pt not in point_idx:
point_idx[pt] = len(points)
points.append(pt)
# Add the additional constraint points
for pt in constraint_points:
if pt not in point_idx:
point_idx[pt] = len(points)
points.append(pt)
# Rectangle boundary segments
segments = [[point_idx[rect[i]], point_idx[rect[(i + 1) % 4]]] for i in range(4)]
# User line segments
if processed_line_segments:
for seg in processed_line_segments:
segments.append([point_idx[seg[0]], point_idx[seg[1]]])
# Prepare input for triangle
A = {'vertices': np.array(points), 'segments': np.array(segments)}
# Triangulate with constraints, using mesh_area as the max area
if not isinstance(mesh_area, (float, int)) or isinstance(mesh_area, bool):
raise TypeError(f"ERROR: Input for mesh area should be a float. Provided was {type(mesh_area)}.")
if mesh_area <= 0:
raise ValueError(f"ERROR: Input for mesh area should be larger than 0. Provided was {mesh_area}.")
tri_mesh = triangle.triangulate(A, f'pq30a{mesh_area}')
# Create MeshNode objects
mesh_nodes = [
MeshNode(id=project.mesh_node_IdGenerator.get_next_id(), coordinates=point.tolist())
for point in tri_mesh['vertices']]
# Create MeshElement objects
mesh_elements = []
for tri in tri_mesh['triangles']:
id = project.mesh_element_IdGenerator.get_next_id()
tri_mesh_nodes = [node for node in mesh_nodes if node.id in tri.tolist()]
mesh_elements.append(MeshElement(id=id, mesh_nodes=tri_mesh_nodes))
# Create Mesh object
project.mesh = Mesh(mesh_elements=mesh_elements)
project.mesh.project = project
return project.mesh
### ===================================================================================================================
### 3. End of script
### ===================================================================================================================