Source code for viiapackage.connections.node_data_diana

### ===================================================================================================================
###   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 ### ===================================================================================================================