Source code for haskoning_atr_tools.vibration_contour_plot.mesh.mesh

### ===================================================================================================================
###  Class definition for Mesh for the vibration simulation
### ===================================================================================================================
# Copyright ©2026 Haskoning Nederland B.V.

### ===================================================================================================================
###  1. Import modules
### ===================================================================================================================

# General imports
import os
import numpy as np
import matplotlib.pyplot as plt
from dataclasses import dataclass
from typing import List, Union, Optional, Tuple

# References for functions and classes in the haskoning_atr_tools package
from haskoning_atr_tools.vibration_contour_plot.mesh.mesh_node import MeshNode
from haskoning_atr_tools.vibration_contour_plot.mesh.mesh_element import MeshElement
from haskoning_atr_tools.vibration_contour_plot.helper_functions import fem_compare_coordinates


### ===================================================================================================================
###  2. Mesh class
### ===================================================================================================================

[docs] @dataclass class Mesh: def __init__(self, mesh_elements: List['MeshElement']): self.mesh_elements = mesh_elements @property def project(self): return self.__project @project.setter def project(self, new_project): self.__project = new_project @property def mesh_elements(self): return self.__mesh_elements @mesh_elements.setter def mesh_elements(self, new_mesh_elements): if not isinstance(new_mesh_elements, list) or \ not all(isinstance(element, MeshElement) for element in new_mesh_elements): raise ValueError("ERROR: Input for mesh nodes should be a list of MeshElement objects.") else: self.__mesh_elements = new_mesh_elements
[docs] def dimensions(self) -> List[Tuple[float, float]]: """ Method of 'Mesh' to collect the bounding box of the mesh.""" nodes = np.array([elem.mesh_nodes for elem in self.mesh_elements]).ravel() coords = np.array([n.coordinates for n in nodes], dtype=float) mins = coords.min(axis=0) maxs = coords.max(axis=0) return [(a, b) for a, b in zip(mins, maxs)]
[docs] def get_all_mesh_nodes(self) -> List[MeshNode]: """Returns a list of all unique MeshNode objects in the mesh.""" unique_nodes = {} for element in self.mesh_elements: for node in element.mesh_nodes: if node.id not in unique_nodes: unique_nodes[node.id] = node return list(unique_nodes.values())
[docs] def plot_mesh(self, figure_size: Union[list, tuple] = (10, 10), file_name: str = "mesh_plot.png", line_segments=None, constraint_points: Optional[List[Tuple[float, float, float]]] = None): """Plots the mesh elements.""" plt.figure(figsize=figure_size) # Process input for plotting (convert inner lists to tuples for consistency) processed_line_segments = [] if line_segments: for seg in line_segments: processed_line_segments.append(tuple(tuple(pt) for pt in seg)) if constraint_points: processed_constraint_points = [tuple(pt) for pt in constraint_points] # Plot triangles for mesh_element in self.mesh_elements: pts = [] for node in mesh_element.mesh_nodes: pts.append([node.x, node.y]) polygon = plt.Polygon(pts, edgecolor='gray', facecolor='none', linewidth=0.5) plt.gca().add_patch(polygon) # Plot line segments if provided (using processed_line_segments) if processed_line_segments: for seg_tuple in processed_line_segments: # seg_tuple is ((x1, y1), (x2, y2)) x_vals = [seg_tuple[0][0], seg_tuple[1][0]] y_vals = [seg_tuple[0][1], seg_tuple[1][1]] plt.plot(x_vals, y_vals, color='red', linewidth=2, label='Constrained Segment') # Plot constraint points if provided if processed_constraint_points: # Extract x and y coordinates for plotting x_constraint_coords = [pt[0] for pt in processed_constraint_points] y_constraint_coords = [pt[1] for pt in processed_constraint_points] plt.scatter(x_constraint_coords, y_constraint_coords, s=50, color='red', marker='X', label='Constraint Points', zorder=999) # zorder to ensure it's on top # Plot mesh nodes x_coords = [node.x for node in self.get_all_mesh_nodes()] y_coords = [node.y for node in self.get_all_mesh_nodes()] plt.scatter(x_coords, y_coords, s=8, color='blue', label='Mesh Nodes') # Avoid duplicate legend entries handles, labels = plt.gca().get_legend_handles_labels() by_label = dict(zip(labels, handles)) plt.legend(by_label.values(), by_label.keys(), loc='upper right') plt.title('Constrained Mesh Visualisation') plt.xlabel('X-coordinate (m)') plt.ylabel('Y-coordinate (m)') plt.grid(True) plt.axis('equal') plt.tight_layout() plt.savefig(os.path.join(self.project.results_folder, file_name)) print(f"Mesh plot saved as '{file_name}' in folder: {self.project.results_folder}") plt.close()
def get_mesh_node_by_coordinates(self, coordinates: List[float]) -> Optional['MeshNode']: found = False for node in self.get_all_mesh_nodes(): if fem_compare_coordinates(node.coordinates, coordinates): return node raise ValueError(f"ERROR: No mesh node found with coordinates {coordinates}.") def get_mesh_node_by_coordinates_2d(self, coordinates: List[float]) -> Optional['MeshNode']: # Ensure the input coordinates list has at least two elements (x and y) if len(coordinates) < 2: raise ValueError("Input coordinates must contain at least x and y values.") # Only compare the first two (x and y) coordinates of the input target_xy = coordinates[:2] for node in self.get_all_mesh_nodes(): node_coords = node.coordinates # Ensure node coordinates have at least two elements if len(node_coords) < 2: continue # Skip nodes that don't have enough coordinates # Get only the first two (x and y) coordinates of the mesh node node_xy = node_coords[:2] # Use your custom comparison function, passing only the X and Y slices if fem_compare_coordinates(node_xy, target_xy): return node
# # The 'found' flag is removed, as raising the error handles the not found case. # raise ValueError(f"ERROR: No mesh node found with XY coordinates {target_xy}.") ### =================================================================================================================== ### 3. End of script ### ===================================================================================================================