Source code for exporter

# SPDX-FileCopyrightText: 2023 SAP SE
#
# SPDX-License-Identifier: Apache-2.0
#
# This file is part of FEDEM - https://openfedem.org

"""
Python wrapper for the native VTFX exporter.
"""

from ctypes import byref, c_bool, c_char_p, c_double, c_float, c_int, cdll
from os import path, remove
from subprocess import run


[docs] class ExporterException(Exception): """ General exception-type for exporter exceptions. Parameters ---------- rc : int, default=0 Return code """ def __init__(self, rc=0): """ Constructor. """ super().__init__({"Failure": f"Return code {rc}"})
[docs] class Exporter: """ This class provides functionality for exporting fedem animations to Ceetron CUG format. Parameters ---------- fe_parts : dict Dictionary of finite element parts to include in visualization vis_parts : dict Dictionary of visualization/vrml parts to include in visualization lib_path : str Absolute path to the shared object library vtfxExporter.so vtfx_path : str, default=None Absolute path to vtfx-file, use a temporary file if None Methods ------- do_step: Executes a step of visualization export with the provided input data clean: Converts temporary generated vtfx-file to CUG database and cleans up """ def __init__(self, fe_parts, vis_parts, lib_path, vtfx_path=None): """ Constructor. Initializes the internal datastructure of the shared object library, and loads the finite element and visualization parts into memory. """ self._initialize(lib_path, vtfx_path) self._fem_parts = [] self._vis_parts = [] # Add FE parts for fe_part in fe_parts: if path.isfile(fe_part["path"]): self._add_fe_part( str.encode(fe_part["path"]), str.encode(fe_part["name"]), fe_part["base_id"], fe_part.get("surface_only", True), fe_part.get("include_spiders", False), ) if fe_part["recovery"]: self._fem_parts.append(fe_part["base_id"]) else: self._vis_parts.append(fe_part["base_id"]) # Add visualization parts for vis_part in vis_parts: if path.isfile(vis_part["path"]): self._add_visualization_part( str.encode(vis_part["path"]), str.encode(vis_part["name"]), vis_part["base_id"], ) self._vis_parts.append(vis_part["base_id"]) def _get_part_object(self, part_id): elements = self._get_elements(part_id) types = self._get_element_types(part_id) nodes = self._get_nodes(part_id) t_matrix = self._get_transformation_matrix(part_id) return { "nodes": nodes, "edges": elements, "elementTypes": types, "transformation": t_matrix, } def _export_geometry(self): parts = [] for part_id in self._fem_parts: parts.append(self._get_part_object(part_id)) for part_id in self._vis_parts: parts.append(self._get_part_object(part_id)) return {"parts": parts}
[docs] def do_step(self, time, transformation_in, deformation_in, stress_in): """ Execute a step of visualization export with the provided input data. Parameters ---------- time : float Simulation time transformation_in : list of c_double Array of transformation data from the fedem solver deformation_in : dict Arrays of part deformation data from the fedem solver stress_in : dict Arrays of part stress data from the fedem solver """ self._set_transformation_data(transformation_in) for base_id in self._fem_parts: self._set_part_deformation_data(base_id, deformation_in[base_id]) self._set_part_stress_data(base_id, stress_in[base_id]) rc = self.lib_exporter.doStep(c_double(time)) if rc != 0: raise ExporterException(rc)
[docs] def clean(self, time_step, lib_dir, out_dir=None, cug_path="/usr/bin/CugComposer"): """ Clears all data about included FE-parts and frs-files, and removes all transformation, stress and deformation results. Closes the vtfx-file and converts it to a CUG database. Parameters ---------- time_step : double Time step to use for animation speed lib_dir : str Absolute path to app location out_dir : str, default=None Absolute path for output folder of the CUG database If None, CUG data is not generated and the vtfx-file is retained cug_path : str, default="/usr/bin/CugComposer" Absolute path to Ceetron CUG composer executable """ print(" * Writing VTFX-file", self.vtfx_file_path, flush=True) rc = self.lib_exporter.finish(c_double(time_step)) if rc == 0 and out_dir and path.isfile(cug_path): logfile = lib_dir + "/Cug.log" if path.isdir(lib_dir) else "./Cug.log" with open(logfile, "w") as outfile: print(" * Creating CUG database in", out_dir, flush=True) rc = run( [cug_path, self.vtfx_file_path, out_dir], check=True, stdout=outfile ).returncode if rc != 0: print(f" *** Failed ({rc})") elif out_dir: remove(self.vtfx_file_path)
def _initialize(self, lib_path, vtfx_path): """ Initialization """ self.lib_exporter = cdll.LoadLibrary(lib_path) self.lib_exporter.initialize(c_bool(False)) self.vtfx_file_path = "/tmp/temp.vtfx" if vtfx_path is None else vtfx_path rc = self.lib_exporter.setVtfxPath( c_char_p(self.vtfx_file_path.encode("utf-8")) ) if rc != 0: raise ExporterException(rc) def _add_fe_part( self, ftl_path, part_name, base_id, surface_only=True, include_spiders=False ): """ Adds the FE-Part defined in the FEDEM ftl-file at ftl_path, with the specified part_name, and set up remapping of elements to the formats required by the Ceetron vtfx format. Parameters ---------- ftl_path : str Absolute path to the ftl-file containing the node and element definitions for the FE-part. part_name : str Name given to the FE-part in the exported vtfx-file. base_id : int The base_id of the part in the FEDEM model. surface_only : bool, default=True Set to True if all geometry should be converted to surface quad and triangle elements. include_spiders : bool, default=False Set to True to also include spider elements and beams """ if not path.isfile(ftl_path): raise ExporterException(1) rc = self.lib_exporter.addFEPart( c_char_p(ftl_path), c_char_p(part_name), c_int(base_id), c_bool(surface_only), c_bool(include_spiders), c_float(1.0), ) if rc != 0: raise ExporterException(rc) def _add_visualization_part(self, file_path, part_name, base_id): """ Adds the visualization part defined in the specified vrml-file, with the specified part_name, and set up remapping of elements to the formats required by the Ceetron vtfx format. Parameters ---------- file_path : str Absolute path to the visualization file containing the node and element definitions. part_name : str Name given to the FE-part in the exported vtfx-file. base_id : int The base_id of the part in the FEDEM model. """ if not path.isfile(file_path): raise ExporterException(1) rc = self.lib_exporter.addVisualizationPart( c_char_p(file_path), c_char_p(part_name), c_int(base_id), c_float(1.0), ) if rc != 0: raise ExporterException(rc) def _get_number_of_elements(self, base_id): """ Returns the total number of finite elements for the FE-part in the ftl-file. Parameters ---------- baseId : int The FEDEM BaseID of the FE-part. Returns ------- int The number of elements. """ return self.lib_exporter.getNumberOfElements(c_int(base_id)) def _get_number_of_element_nodes(self, base_id): """ Returns the total number of element-nodes for the FE-part with the specified baseId. Parameters ---------- baseId : int The FEDEM BaseID of the FE-part. Returns ------- int The number of element nodes. """ return self.lib_exporter.getNumberOfElementNodes(c_int(base_id)) def _get_number_of_nodes(self, base_id): """ Returns the total number of unique nodes for the FE-part with the specified baseId. Parameters ---------- baseId : int The FEDEM BaseID of the FE-part. Returns ------- int The number of nodes. """ return self.lib_exporter.getNumberOfNodes(c_int(base_id)) def _get_element_types(self, base_id, element_types=None): """ Fills the array given as input with the element types for all elements of the FE-part with the specified baseId. Each type is an integer-value corresponding to the number of nodes in the element(3 for triangles, 4 for quads) Parameters ---------- baseId : int The FEDEM BaseId of the FE-Part. elementTypes : list of c_int, default=None The array of integers that will be filled with the element-types. If none, a list will be allocated inside the function. Size of list must be equal to value from _get_number_of_elements(). Returns ------- list of ints List of element types """ if not element_types: element_types = (c_int * self._get_number_of_elements(base_id))() rc = self.lib_exporter.getElementTypes(c_int(base_id), byref(element_types)) if rc != 0: raise ExporterException(rc) return element_types[0:] def _get_elements(self, base_id, elements=None): """ Fills the array given as input with the node-indices of all elements of the FE-part with the specified baseId. Parameters ---------- baseId : int The FEDEM BaseID of the FE-part. elements : list of c_int, default=None List of integers that will be filled with the element-node indices. If None, a list will be allocated inside the function. The size must be equal to the sum of all nodes for the different elements. Returns ------- List of ints List of node indices for the elements """ if not elements: elements = (c_int * self._get_number_of_element_nodes(base_id))() rc = self.lib_exporter.getElements( c_int(base_id), byref(elements), c_bool(False) ) if rc != 0: raise ExporterException(rc) return elements[0:] def _get_nodes(self, base_id, nodes=None): """ Fills the array given as input with the x,y and z values for all the nodes of the FE-part with the specified baseId. Parameters ---------- baseId : int The FEDEM BaseID of the FE-part. nodes : list of c_double, default=None List of doubles that will be filled with the node coordinates. If None, a list will be allocated inside the function. The size must be equal to three times the value from _get_number_of_nodes(). Returns ------- List of doubles List of node coordinates """ if not nodes: nodes = (c_double * (self._get_number_of_nodes(base_id) * 3))() rc = self.lib_exporter.getNodes(c_int(base_id), byref(nodes)) if rc != 0: raise ExporterException(rc) return nodes[0:] def _get_transformation_matrix(self, base_id, transformation=None): """ Fills the array given as input with the 16 components of the transformation matrix of the FE-part with the specified baseId, for the current time-step. Row dominant. Final row is translation Parameters ---------- baseId : int The FEDEM BaseID of the part to get results from. transformation : list of c_double, default=None List of doubles that will be filled with the transformation matrix. If none, a list will be allocated inside the function. Size of array must be equal to 16. Returns ------- List of doubles List containing the transformation matrix """ if not transformation: transformation = (c_double * 16)() rc = self.lib_exporter.getTransformationMatrix( c_int(base_id), byref(transformation) ) if rc != 0: raise ExporterException(rc) return transformation[0:] def _get_deformation_vector(self, base_id, deformation=None): """ Fills the array given as input with the updated x,y and z values in the current time-step, for all the nodes of the FE-part with the specified baseId. Parameters ---------- baseId : int The FEDEM BaseID of the part to get results from. deformation : List of c_double, default=None List of doubles that will be filled with x,y,z coordinates of all nodes of the deformed part. If None, a list will be allocated inside the function. The size must be equal to three times the value from _get_number_of_nodes(). Returns ------- List of doubles List containing the nodal displacements """ if not deformation: deformation = (c_double * (self._get_number_of_nodes(base_id) * 3))() rc = self.lib_exporter.getDeformationVector(c_int(base_id), byref(deformation)) if rc != 0: raise ExporterException(rc) return deformation[0:] def _get_stress_vector(self, base_id, stress=None): """ Fills the array given as input with the von Mises stress values in the current time-step, for all the nodes of the FE-part in the ftl-file. Parameters ---------- baseId : int The FEDEM BaseID of the part to get results from. stress : List of c_double, default=None List of doubles that will be filled with von Mises stress value of all nodes of the deformed part. If none, a list will be allocated inside the function. The size must be equal to the value from _get_number_of_element_nodes(). Returns ------- List of doubles List containing the stress values for each node """ if not stress: stress = (c_double * self._get_number_of_nodes(base_id))() rc = self.lib_exporter.getStressVector(c_int(base_id), byref(stress)) if rc != 0: raise ExporterException(rc) return stress[0:] def _set_transformation_data(self, data): sz = len(data) rc = self.lib_exporter.setTransformationData(c_int(sz), data) if rc != 0: raise ExporterException(rc) def _set_part_deformation_data(self, base_id, data): sz = len(data) rc = self.lib_exporter.setPartDeformationData(c_int(base_id), c_int(sz), data) if rc != 0: raise ExporterException(rc) def _set_part_stress_data(self, base_id, data): sz = len(data) rc = self.lib_exporter.setPartStressData(c_int(base_id), c_int(sz), data) if rc != 0: raise ExporterException(rc)