### ===================================================================================================================
### Function to create a check document for nodes in DIANA
### ===================================================================================================================
# Copyright ©VIIA 2024
### ===================================================================================================================
### 1. Import modules
### ===================================================================================================================
# General imports
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING, List, Union, Dict
# References for functions and classes in the rhdhv_fem package
from rhdhv_fem.shape_geometries import Node
from rhdhv_fem.connections import Hinge, Interface, NoConnection, Unite
from rhdhv_fem.fem_math import fem_point_on_line, fem_round_coordinates
# References for functions and classes in the viiaPackage
if TYPE_CHECKING:
from viiapackage.viiaStatus import ViiaProject
# Import module fpdf, check if module is installed (for node data summary)
try:
from fpdf import FPDF
except ImportError:
FPDF = None
# Import module PIL, check if module is installed (for node data summary)
try:
from PIL import Image
except ImportError:
Image = None
import math
### ===================================================================================================================
### 2. Function to collect the data of a specified node in DIANA
### ===================================================================================================================
[docs]def get_connection_data(project: ViiaProject, node: Node) -> Dict[str, List[str]]:
"""
This function takes a node object and gets the connection information at that node from the python memory and
returns the information in a dictionary.
Input:
- project (obj): VIIA project object containing collections of fem objects and project variables.
- node (obj): Object reference of Node to be checked.
Output:
- A dictionary containing the connection data. The keys are the names of the connections and the corresponding
value is a list of names of the sources and the target shapes of the corresponding connection.
"""
node_connections = node.get_connections()
node_name = node.name
connection_data = dict()
coord_precision = project.rounding_precision
for node_connection in node_connections:
connection_name = node_connection.name
if node_connection.connection_type == 'line-line':
line_coord_start = node_connection.connecting_shapes['source_shape_geometry'].node_start.coordinates
line_coord_end = node_connection.connecting_shapes['source_shape_geometry'].node_end.coordinates
line_coord_start = [fem_round_coordinates(coord, coord_precision) for coord in line_coord_start]
line_coord_end = [fem_round_coordinates(coord, coord_precision) for coord in line_coord_end]
connection_name = "\n".join([connection_name, f"{line_coord_start} - {line_coord_end}"])
connection_data[connection_name] = list()
connection_data[connection_name].append(
node_connection.connecting_shapes['source_connecting_shape'].name)
if 'target_connecting_shape' in node_connection.connecting_shapes:
# For example boundary springs do not have a target
connection_data[connection_name].append(
node_connection.connecting_shapes['target_connecting_shape'].name)
# Auto unites are unordered so source and target info lost
auto_unites = project.rhdhvDIANA.auto_unites
for frozen_set, geom_name_auto, connection_name_auto in auto_unites:
if geom_name_auto == node_name and connection_name_auto != 'AutoUnite Excluded':
connection_data[connection_name_auto] = list()
for item in frozen_set:
connection_data[connection_name_auto].append(item)
# Return the collected information
return connection_data
[docs]def get_connection_data_diana(project: ViiaProject, node: List[float]) -> Dict[str, List[str]]:
"""
This function takes the coordinates of a node and checks whether a DIANA node is present at that coordinate and
retrieves the connection information at the coordinate location and returns the information in a dictionary.
.. note:: Function should be executed in DIANA, otherwise no info will be collected.
Input:
- project (obj): VIIA project object containing collections of fem objects and project variables.
- node (List): A list containing the coordinates of a node in DIANA.
Output:
- Returns a dictionary containing the connection data from DIANA. The keys are the names of the connections
and the corresponding value is a list of names of the sources and the target shapes of the corresponding
connection.
"""
tol = 1 / (10 ** project.check_precision)
diana_nodes = []
if project.rhdhvDIANA.run_diana:
diana_nodes = project.rhdhvDIANA.findNodesInBox(
node[0] - tol, node[0] + tol, node[1] - tol, node[1] + tol, node[2] - tol, node[2] + tol)
all_connections = project.collections.connections
# Only these connections are included till now
detail_types = [Hinge, Interface, NoConnection, Unite]
# Create a dictionary of dictionaries containing data for each detail type
all_connection_data = dict()
connection_data = dict()
for detail_type in detail_types:
all_connection_data[detail_type.__name__] = dict()
for connection in all_connections:
if isinstance(connection, detail_type):
all_connection_data[detail_type.__name__][connection.name] = dict()
all_connection_data[detail_type.__name__][connection.name]['source'] = connection.connecting_shapes[
'source_connecting_shape'].name
all_connection_data[detail_type.__name__][connection.name]['target'] = connection.connecting_shapes[
'target_connecting_shape'].name
if detail_type is Interface:
# Material name is needed to retrieve data from DIANA
all_connection_data[detail_type.__name__][connection.name][
'material'] = connection.material.name
if detail_type in [NoConnection, Unite]:
if connection.connection_type == 'line-line':
line_start = connection.connecting_shapes['source_shape_geometry'].node_start.coordinates
line_end = connection.connecting_shapes['source_shape_geometry'].node_end.coordinates
all_connection_data[detail_type.__name__][connection.name][
'line'] = [line_start, line_end]
# Including AutoUnites in all_connection_data
auto_unites = project.rhdhvDIANA.auto_unites
line_shapes = project.collections.line_shape_geometries
all_connection_data['AutoUnite'] = dict()
for frozen_set, geom_name_auto, connection_name_auto in auto_unites:
if connection_name_auto != 'AutoUnite Excluded':
[source_shape_auto, target_shape_auto] = list(frozen_set)
all_connection_data['AutoUnite'][connection_name_auto] = dict()
all_connection_data['AutoUnite'][connection_name_auto]['source'] = source_shape_auto
all_connection_data['AutoUnite'][connection_name_auto]['target'] = target_shape_auto
if 'AutoUnite-L' in connection_name_auto:
for line_shape in line_shapes:
if geom_name_auto == line_shape.name:
line_start = line_shape.node_start.coordinates
line_end = line_shape.node_end.coordinates
all_connection_data['AutoUnite'][connection_name_auto]['line'] = [line_start, line_end]
# Interfaces
if len(all_connection_data['Interface']) != 0:
if project.rhdhvDIANA.run_diana:
interface_data = all_connection_data['Interface']
# Extracting info from DIANA
for interface_name, data in interface_data.items():
interface_material = data['material']
# Selects elements and IDs of a specific material
project.rhdhvDIANA.selectByMaterial('ELEMENT', [interface_material])
element_ids = project.rhdhvDIANA.selectedElementIds()
# Nodes in the selected elements
interface_node_ids = project.rhdhvDIANA.nodeIds('ELEMENT', element_ids)
source_nodes = project.rhdhvDIANA.nodeIds('ELEMENTSET', data['source'])
target_nodes = project.rhdhvDIANA.nodeIds('ELEMENTSET', data['target'])
if set(diana_nodes).intersection(set(source_nodes)) and \
set(diana_nodes).intersection(set(target_nodes)):
for diana_node in diana_nodes:
if diana_node in interface_node_ids:
connection_data[interface_name] = list()
connection_data[interface_name].append(data['source'])
connection_data[interface_name].append(data['target'])
break
project.rhdhvDIANA.clearSelection('ELEMENT')
# Hinges
if len(all_connection_data['Hinge']) != 0:
if project.rhdhvDIANA.run_diana:
hinge_data = all_connection_data['Hinge']
# Extracting info from Diana
for hinge_name, data in hinge_data.items():
master_nodes = project.rhdhvDIANA.tyingMasterNodes(hinge_name, hinge_name)
slave_nodes = project.rhdhvDIANA.tyingSlaveNodes(hinge_name, hinge_name)
hinge_nodes = master_nodes + slave_nodes
for diana_node in diana_nodes:
if diana_node in hinge_nodes:
connection_data[hinge_name] = list()
connection_data[hinge_name].append(data['source'])
connection_data[hinge_name].append(data['target'])
break
# Unites
if len(all_connection_data['Unite']) != 0:
# Including only the line unites
unite_data = dict()
for unite_name, data in all_connection_data['Unite'].items():
for key, value in data.items():
if key == 'line':
unite_data[unite_name] = data
# Checking if the node coordinate lies on any of the line unites
for unite_name, data in unite_data.items():
line = data['line']
node_on_line = fem_point_on_line(node, line)
if node_on_line:
connection_data[unite_name] = list()
connection_data[unite_name].append(data['source'])
connection_data[unite_name].append(data['target'])
# No Connection
if len(all_connection_data['NoConnection']) != 0:
# Including only the line no connections
no_connection_data = dict()
for no_connection_name, data in all_connection_data['NoConnection'].items():
for key, value in data.items():
if key == 'line':
no_connection_data[no_connection_name] = data
# Checking if the node coordinate lies on any of the line no connections
for no_connection_name, data in no_connection_data.items():
line = data['line']
node_on_line = fem_point_on_line(node, line)
if node_on_line:
connection_data[no_connection_name] = list()
connection_data[no_connection_name].append(data['source'])
connection_data[no_connection_name].append(data['target'])
# Auto unites
if len(all_connection_data['AutoUnite']) != 0:
# Including only the line AutoUnites
auto_unite_data = dict()
for auto_unite_name, data in all_connection_data['AutoUnite'].items():
for key, value in data.items():
if key == 'line':
auto_unite_data[auto_unite_name] = data
# Checking if the node coordinate lies on any of the line unites
for auto_unite_name, data in auto_unite_data.items():
line = data['line']
node_on_line = fem_point_on_line(node, line)
if node_on_line:
connection_data[auto_unite_name] = list()
connection_data[auto_unite_name].append(data['source'])
connection_data[auto_unite_name].append(data['target'])
# Return the collected data
return connection_data
[docs]def get_node_data_diana(project: ViiaProject, node: Union[Node, List[float]], image_path: Path) -> Dict[str, List[str]]:
"""
This function takes a node object and looks in DIANA for mesh-nodes that share the same coordinates as the node
object. It then looks for element sets that contain the mesh nodes and stores this information in a dictionary. This
function also saves the image highlighting the selected node in the model.
Input:
- project (obj): VIIA project object containing collections of fem objects and project variables.
- node (obj or list): A Node object or a list containing 3 floats representing the coordinates of the node
- image_path (Path): Path where the image will be stored.
Output:
- A dictionary with keys as the DIANA node IDs as strings and their values as the list of names of the element
sets containing the corresponding node.
"""
# Handle node input
node_coord = node
if isinstance(node, Node):
node_coord = node.coordinates
if not isinstance(node_coord, list) or len(node_coord) != 3:
raise ValueError(
f"ERROR: Input for node is incorrect, expecting Node instance or list of 3 coordinates. Provided was "
f"{node}.")
# Collect the data from DIANA
node_data = {}
if project.rhdhvDIANA.run_diana:
tol = 1 / (10 ** project.check_precision)
diana_nodes = project.rhdhvDIANA.findNodesInBox(
node_coord[0] - tol, node_coord[0] + tol, node_coord[1] - tol, node_coord[1] + tol,
node_coord[2] - tol, node_coord[2] + tol)
# Collect all the element sets in DIANA
all_element_sets = project.rhdhvDIANA.names('ELEMENTSET')
for node in diana_nodes:
node_data[f'{node}'] = list()
for element_set in all_element_sets:
if node in project.rhdhvDIANA.nodeIds('ELEMENTSET', element_set):
node_data[f'{node}'].append(element_set)
# Save picture
project.rhdhvDIANA.hideView('GEOM')
project.rhdhvDIANA.hideView('MESH')
project.rhdhvDIANA.showView('MESH')
project.rhdhvDIANA.showAll("ELEMENTSET")
project.rhdhvDIANA.setViewPoint("ISO1")
project.rhdhvDIANA.highlight('NODE', diana_nodes)
project.rhdhvDIANA.saveImage(image_path.as_posix()) # Default width = 1024 pixels, default height = 768 pixels
# Return collected data
return node_data
[docs]def create_node_data_pdf(
project: ViiaProject, image_path: Path, node_data: Dict, connection_data: Dict, node_name: str,
pdf_path: Path) -> Path:
"""
This function is used to generate a pdf document from the node-data and connection-data. It contains the image
highlighting the node, a table consisting of node-data and another table consisting of connection data.
Input:
- project (obj): VIIA project object containing collections of fem objects and project variables.
- image_path (Path): Folder path where the image of the model highlighting the node is saved.
- node_data (dict): A dictionary containing the node data. The keys are the DIANA node ids as string and the
corresponding value is a list containing the names of the element sets containing the corresponding DIANA
node.
- connection_data (dict): A dictionary containing the connection data. The keys are the names of the connections
and the corresponding value is a list of names of the sources and the target shapes of the corresponding
connection.
- node_name (str): A string of the name of the node that is being viewed. Name can be the name of the rhdhv node
object followed by its coordinates, or only the coordinates in case it is not a node object. Only used in the
title of the report.
- pdf_path (Path): The folder path where the generated pdf-file needs to be saved.
Output:
- Creates a pdf-file with the node information from DIANA.
- Returns the path of the pdf-file.
"""
title_text_size = 12
sub_header_text_size = 10
normal_text_size = 8
normal_line_spacing = 4
standard_width = 190
table_width = 180
# start the pdf
pdf = FPDF(orientation='P', unit='mm', format='A4')
pdf.add_page()
pdf.set_font(family='Arial', style='B', size=title_text_size)
pdf.set_fill_color(r=220, g=220, b=220)
# Add title
title = node_name
pdf.cell(w=standard_width, h=normal_text_size, txt=title, ln=1, align='C')
# Load the image
if image_path.exists():
Image.open(image_path)
# Adding the image
pdf.image(image_path.as_posix(), x=210 / 2 - 50, y=20, w=100)
# Add the node data
pdf.set_y(105) # 20+75+10 (20: y dist of image, 75: image ht, 10: distance after the image)
def get_string_lines(value, cell_width):
"""
This function reads a string (value) and calculates the number of lines it will take to print that string.
It takes into account the width of the string relative to the cell width and if new line character is present
in the string and returns the number of lines (num_lines).
"""
num_lines = 0
lines = value.split('\n')
cell_width = cell_width - 2
for line in lines:
string_width = pdf.get_string_width(line)
num_lines = math.ceil(string_width / cell_width) + num_lines
return num_lines
if node_data:
headers = ['DIANA Node No.', 'Element Sets']
col_width = [table_width * 0.25, table_width * 0.75]
# Table heading
pdf.ln(normal_line_spacing)
pdf.set_font(family='Arial', style='B', size=sub_header_text_size)
pdf.cell(w=standard_width, h=normal_line_spacing, txt='Node data', ln=1, align='L')
pdf.set_font(family='Arial', style='B', size=normal_text_size)
# Header row
for i, header in enumerate(headers):
pdf.cell(w=col_width[i], h=normal_line_spacing, txt=header, border=1, ln=0, align='C', fill=True)
pdf.ln(normal_line_spacing)
pdf.set_font(family='Arial', style='', size=normal_text_size)
# Data rows
pdf.set_font(family='Arial', style='', size=normal_text_size)
for key, values in node_data.items():
# Calculate the cell height based on the number of lines and string width in the second column
value_text = "\n".join(str(value) for value in values)
num_lines = get_string_lines(value_text, col_width[1])
cell_height = normal_line_spacing * num_lines
# Add new page if the cell goes beyond the bottom margin
if pdf.get_y() + cell_height > pdf.page_break_trigger:
pdf.add_page()
# Save the current Y position
y_start = pdf.get_y()
# First column: Key
pdf.cell(w=col_width[0], h=cell_height, txt=str(key), border=1, ln=0, align='C')
# Second column: Values (each in a new line)
x_start = pdf.get_x() # Get current X position after the first cell
pdf.multi_cell(w=col_width[1], h=normal_line_spacing, txt=value_text, border=1, align='L')
# Set Y position for the next row
pdf.set_y(y_start + cell_height)
# Add connection data
pdf.ln(normal_line_spacing) # You can adjust this value as needed
# Add the connection data
if connection_data:
headers = ['Connection', 'Element Sets (Source and Target shapes)']
col_width = [table_width * 0.4, table_width * 0.6]
# Adding header row for Connection Data
pdf.set_font(family='Arial', style='B', size=sub_header_text_size)
pdf.cell(w=standard_width, h=normal_line_spacing, txt='Connection Data', ln=1, align='L')
pdf.set_font(family='Arial', style='B', size=normal_text_size)
for header in headers:
pdf.cell(
w=col_width[headers.index(header)], h=normal_line_spacing, txt=header, border=1, ln=0, align='C',
fill=True)
pdf.ln(normal_line_spacing)
# Adding data rows for Connection Data
pdf.set_font(family='Arial', style='', size=normal_text_size)
for key, values in connection_data.items():
# Calculate the cell height and line spacing based on the max number of lines between first and second cell
key_lines = get_string_lines(key, col_width[1])
value_text = "\n".join(str(value) for value in values)
val_lines = get_string_lines(value_text, col_width[1])
if key_lines >= val_lines:
cell_height = normal_line_spacing * key_lines
key_line_spacing = normal_line_spacing
val_line_spacing = cell_height / val_lines
else:
cell_height = normal_line_spacing * val_lines
key_line_spacing = cell_height / key_lines
val_line_spacing = normal_line_spacing
# Add new page if the cell goes beyond the bottom margin
if pdf.get_y() + cell_height > pdf.page_break_trigger:
pdf.add_page()
# Save the current Y position
y_start = pdf.get_y()
# First column: Key
if '\n' in key:
pdf.multi_cell(w=col_width[0], h=key_line_spacing, txt=str(key), border=1, align='L')
# Update X and Y position for the second column
pdf.set_xy(pdf.get_x() + col_width[0], y_start)
else:
pdf.cell(w=col_width[0], h=cell_height, txt=str(key), border=1, ln=0, align='L')
pdf.get_x() # Get current X position after the first cell
# Second column: Values (each in a new line)
pdf.multi_cell(w=col_width[1], h=val_line_spacing, txt=value_text, border=1, align='L')
# Set Y position for the next row
pdf.set_y(y_start + cell_height)
# Save the PDF and return the path of the pdf-file
pdf.output(pdf_path.as_posix())
return pdf_path
### ===================================================================================================================
### 3. End of script
### ===================================================================================================================