Source code for NaxToPy.Core.N2PModelContent

from NaxToModel import N2ModelContent
from NaxToModel import N2LoadCase
from NaxToModel.Classes import N2AdjacencyFunctions
from NaxToModel.Classes import N2FreeEdges
from NaxToModel import EnvelopGroup
from NaxToModel import EnvelopCriteria
from NaxToModel import N2ArithmeticSolver
from NaxToModel import N2Enums
from NaxToModel import N2CoordUserDefined, N2Coord
from NaxToModel.Classes import N2SelectionFunctions

from System.Runtime.CompilerServices import RuntimeHelpers
import System

from NaxToPy.Core.Errors.N2PLog import N2PLog
from NaxToPy.Core.Classes.N2PLoadCase import N2PLoadCase
from NaxToPy.Core.Classes.N2PIncrement import N2PIncrement
from NaxToPy.Core.Classes.N2PElement import N2PElement
from NaxToPy.Core.Classes.N2PNode import N2PNode
from NaxToPy.Core.Classes.N2PCoord import N2PCoord
from NaxToPy.Core.Classes.N2PConnector import N2PConnector
from NaxToPy.Core.Classes.N2PFreeBody import N2PFreeBody
from NaxToPy.Core.Classes.N2PMaterial import N2PMaterial, N2PMatE, N2PMatMisc, N2PMatI
from NaxToPy.Core.Classes.N2PProperty import N2PProperty, N2PComp, N2PShell, N2PSolid, N2PBeam, N2PRod, N2PBush, N2PFast, N2PMass, N2PElas, N2PGap, N2PWeld, N2PBeamL, N2PPropMisc
from NaxToPy.Core.Classes.N2PSet import N2PSet
from NaxToPy.Core.Classes.N2PNastranInputData import N2PNastranInputData, CONSTRUCTDICT, N2PCard
from NaxToPy.Core.Constants.Constants import BINARY_EXTENSIONS
from NaxToPy.Core.Classes.N2PReport import N2PReport
from NaxToPy.Core.Classes.N2PAbaqusInputData import N2PAbaqusInputData
from NaxToPy.Core._AuxFunc._NetToPython import _numpytonet

import time
import numpy as np
import os
import array
from collections import OrderedDict
from typing import Union, overload, Literal


# Clase Model Content de Python ----------------------------------------------------------------------------------------
[docs] class N2PModelContent: """Main Class of the NaxToPy package. Keeps almost all the information contained in the output result. The method that returns a :class:`N2PModelContent` is :meth:`load_model()` Example: >>> model1 = n2p.load_model(r"my_model1.op2") >>> solver = model1.Solver # Property of N2PModelContent >>> nodes = model1.get_nodes() # Method of N2PModelContent >>> model2 = n2p.load_model(r"my_model2.h5") >>> solver2 = model2.Solver # Property of N2PModelContent >>> nodes2 = model2.get_nodes() # Method of N2PModelContent """ #################################################################################################################### # region INIT #################################################################################################################### def __init__(self, path, parallelprocessing, solver=None, dict_gen: dict = None, filter: Literal['ELEMENTS', 'PROPERTIES', 'PARTS', 'NODES'] = None, n2v: N2ModelContent = None, loadconnectors = True): """ Python ModelContent Constructor. Args: path: str parallelprocessing: bool """ # 0 ) Genera el archivo .log y añade los logs de la importación ------------------------------------------------ # Configura el logger para que escriba automaticamente los logs ------------------------------------------------ if not N2PLog._N2PLog__fh.immediate: N2PLog._N2PLog__fh.write_buffered_records() N2PLog._N2PLog__fh.immediate_logging() # 1) Extraer Toda la Informacion Principal de NaxTo ------------------------------------------------------------ # Inicializador de atributos self.__LCs_by_ID__: dict[int: N2PLoadCase] = dict() self.__LCs_by_Name__: dict[str: N2PLoadCase] = dict() self.__load_cases__ = None self.__setlist = None self.__number_of_load_cases = None self.__material_dict = dict() self.__property_dict: dict[tuple[int, int]] = dict() self.__elements_coord_sys = None self.__material_coord_sys = None self.__cells_list = None # Lista con N2PElement y N2PConnector self.__connectivity_dict = {} self.__element_user_coord_systems = [] self.__node_user_coord_systems = [] self.__elementnodal_dict = None self.__elemIdInternalToId = dict() # Inicializador del modelo if n2v: self.__vzmodel = n2v self.__solver = self.__vzmodel.Solver.ToString() else: self.__vzmodel = N2ModelContent() if not solver: if _is_binary(path): self.__vzmodel.Solver = self.__vzmodel.Solver.Unknown else: solver = _check_file_extension(path) if solver == 'InputFileNastran': self.__vzmodel.Solver = self.__vzmodel.Solver.InputFileNastran elif solver == "InputFileOptistruct": N2PLog.Warning.user("Input files from Optistruct are not fully supported yet. They are loaded as Nastran input files.") self.__vzmodel.Solver = self.__vzmodel.Solver.InputFileNastran elif solver == 'InputFileAbaqus': self.__vzmodel.Solver = self.__vzmodel.Solver.InputFileAbaqus elif solver == "Binary": self.__vzmodel.Solver = self.__vzmodel.Solver.Unknown error = self.__vzmodel.Initialize(path, self.__vzmodel.Solver) # Si NaxTo devuelve un numero por debajo de 0 en el inicializador del modelo -> Error al inicializar modelo if error < 0: if not os.path.exists(path): msg = N2PLog.Critical.C108(path) raise FileNotFoundError(msg) else: msg = N2PLog.Critical.C100(path, error) raise RuntimeError(msg) else: N2PLog.Info.I111(path) # Activar banderas de NaxToPy en dll de Naxto # Es imprescindible hacer esta llamada para que se carguen los datos necesarios para que # el modulo funcione self.__vzmodel.Program_Call = self.__vzmodel.Program_Call.NaxToPy N2PLog.Debug.D102() # Opcion que indica que la lectura de la malla dentro de vizzer se haga en paralelo self.__vzmodel.LoadModelInParallel = parallelprocessing # No siempre es mas rapido N2PLog.Debug.D103(parallelprocessing) # Option that sets if the connectors should be loaded or not self.__vzmodel.LoadConnectorsOption = loadconnectors N2PLog.Debug.D104(loadconnectors) error = self.__vzmodel.BuildMesh(filter, dict_gen) # Si NaxTo devuelve un numero por debajo de 0 en el generador de malla -> Error al generar malla if error == -1003: msg = N2PLog.Critical.C112() raise RuntimeError(msg) elif error < 0: msg = N2PLog.Critical.C101(error) raise RuntimeError(msg) else: N2PLog.Info.I112() self.__solver = self.__vzmodel.Solver.ToString() # Generar Informacion Disponible (LCs, Result-Types ...) if self.__solver != "InputFileNastran" and self.__solver != "InputFileAbaqus": t1 = time.time() # Wait for all the process that load the mesh (resultsinfo and setinfo go in the secondary task) self.__vzmodel.WaitSecondaryParallelTask() # The load cases and all the results tree is loaded here self.__vzmodel.LoadInfoResults() t2 = time.time() N2PLog.Debug.D100("LoadInfoResults()", t2 - t1) # -------------------------------------------------------------------------------------------------------------- ################################################################################################################ ########################################### N2PLoadCase ################################################## ################################################################################################################ # 2) Obtener los casos de carga del modelo --------------------------------------------------------------------- t1 = time.time() self._import_load_cases() t2 = time.time() N2PLog.Debug.D100("LoadCases", t2 - t1) # -------------------------------------------------------------------------------------------------------------- ################################################################################################################ ########################################## GENERAL DICTS ################################################ ################################################################################################################ # Diccionarios obtenidos directamente de Vizzer --------------- t1 = time.time() self.__elementTypeStrtoID = dict(self.__vzmodel.elementTypeID) self.__propertiesID = dict(self.__vzmodel.propertiesID) self.__StrPartToID = dict(self.__vzmodel.partID) self.__materialsID = None self.__ConnectorsID = dict(self.__vzmodel.ConnectorsID) # -------------------------------------------------------------------------------------------------------------- # Diccionario de en el que entras con un ID de elemento dado por Vizzer y sacas # un string con el tipo de elemento de solver self.__elementTypeIDtoStr = {v: k for k, v in self.__elementTypeStrtoID.items()} # Diccionario en el que entras con el ID de la parte y te devuelve el nombre. self.__partIDtoStr = {v: k for k, v in self.__StrPartToID.items()} # Diccionario en el que entras con un Id de propiedad y te devuelve el string. self.__propertyIDtoStr = {v: k for k, v in self.__propertiesID.items()} # Diccionario en el que entras con un Id de tipo de conector y te devuelve un string con su nombre. self.__connectorTypeIDtoStr = {v: k for k, v in self.__ConnectorsID.items()} # Diccionario de en el que entras con un material y devuelve una lista de propiedades en las que está incluido # Al ser una lista no vale el metodo dict() tal cual. Hay que sacar sus claves y valores y construirlo nosotros aux = dict(self.__vzmodel.DictMaterialsProperties) self.__DictMaterialsProperties = {key: list(aux[key]) for key in aux} del aux # -------------------------------------------------------------------------------------------------------------- # Diccionario de en el que entras con una propiedad y devuelve una lista de materiales que tiene incluido # Se construye igual que el diccionario anterior aux = dict(self.__vzmodel.DictPropertiesMaterials) self.__DictPropertiesMaterials = {key: list(aux[key]) for key in aux} del aux t2 = time.time() N2PLog.Debug.D100("Direct Dictionaries", t2 - t1) # -------------------------------------------------------------------------------------------------------------- ################################################################################################################ ############################################# N2PCoord ################################################### ################################################################################################################ t1 = time.time() # Revisar porque es posible los sistemas de coordenadas nunca tengan partes self.__coord_dict = {((coord.ID, coord.PartID) if hasattr(coord, 'PartID') else (coord.ID, 0)): N2PCoord(coord, self) for coord in self.__vzmodel.coordList.Values} t2 = time.time() N2PLog.Debug.D100("N2PCoord Dictionary", t2 - t1) # -------------------------------------------------------------------------------------------------------------- ################################################################################################################ ############################################# N2PNode ################################################### ################################################################################################################ t1 = time.time() def __n2pnode_func(listadenodos): node_dict = OrderedDict(((nodo.ID, nodo.PartID), N2PNode(nodo, self)) for nodo in listadenodos) return node_dict self.__node_dict = __n2pnode_func(self.__vzmodel.N2PNodeList) t2 = time.time() N2PLog.Debug.D100("N2PNode Dictionary", t2 - t1) # -------------------------------------------------------------------------------------------------------------- ################################################################################################################ ############################################ N2PElement ################################################# ################################################################################################################ t1 = time.time() def __n2pelement_func(listadeelementos): elem_dict = OrderedDict(((elem.ID, elem.partID), N2PElement(elem, self)) for elem in listadeelementos) return elem_dict self.__element_dict = __n2pelement_func(self.__vzmodel.N2PElementList) self.__cells_list = list(self.__element_dict.values()) t2 = time.time() N2PLog.Debug.D100("N2PElement Dictionary", t2 - t1) # -------------------------------------------------------------------------------------------------------------- ################################################################################################################ ########################################### N2PConnector ################################################ ################################################################################################################ t1 = time.time() # Flag to print once the W203 mpc_warning = False self.__connector_dict = OrderedDict() for con in self.__vzmodel.connectorList: if not (con.ID, con.Part) in self.__connector_dict: self.__connector_dict[(con.ID, con.Part)] = N2PConnector(con, self) elif isinstance(self.__connector_dict.get((con.ID, con.Part)), list): self.__connector_dict[(con.ID, con.Part)].append(N2PConnector(con, self)) else: self.__connector_dict[(con.ID, con.Part)] = \ [self.__connector_dict.get((con.ID, con.Part)), (N2PConnector(con, self))] if not mpc_warning: N2PLog.Warning.W203() mpc_warning = True del mpc_warning # OJO. EN NASTRAN SE PUEDEN TENER VARIOS MPC CON EL MISMO ID, POR TANTO SE GUARDARAN COMO UNA LISTA DE # N2PConnectors self.__cells_list += [conn for conn_list in self.__connector_dict.values() for conn in (conn_list if isinstance(conn_list, list) else [conn_list])] t2 = time.time() N2PLog.Debug.D100("N2PConnector Dictionary", t2 - t1) # -------------------------------------------------------------------------------------------------------------- ################################################################################################################ ######################################## N2PModelInputData ############################################## ################################################################################################################ t1 = time.time() if self.__vzmodel.NasOptInputReader is not None: class LazyDict(dict): """This class keeps the N2Card as C# instances crated in the reader. Only when they are asked by key, the card asked is cast to the proper N2PCard before the value returning""" def __init__(self, keys_list: list): super().__init__() self._hash_to_cs = dict() self._collision_counter = {} # Track collision index per hash for cs_ref in keys_list: if cs_ref.GetN2PCardType() not in CONSTRUCTDICT: continue hash = RuntimeHelpers.GetHashCode(cs_ref) # Create unique key for collisions if hash in self._collision_counter: self._collision_counter[hash] += 1 unique_key = (hash, self._collision_counter[hash]) else: self._collision_counter[hash] = 0 unique_key = (hash, 0) self[unique_key] = None self._hash_to_cs[unique_key] = cs_ref def __getitem__(self, key): # Handle both (hash, index) and plain hash lookups if isinstance(key, tuple): hash, index = key else: hash = key index = 0 key = (hash, 0) if key not in self or self.get(key) is None: cs_ref = self._hash_to_cs.get(key) if not cs_ref: return None value = CONSTRUCTDICT.get(cs_ref.GetN2PCardType()) if value: constructed = value(cs_ref) self[key] = constructed return constructed return None else: return self.get(key) cslistbulkdatacards = self.__vzmodel.NasOptInputReader.BulkDataSection.GetCardDataBaseList() self.__modelinputdata = N2PNastranInputData( LazyDict(cslistbulkdatacards), self.__vzmodel.NasOptInputReader ) self.__vzmodel.NasOptInputReader.OperationLogger.Clear() #Necessary so that logs from reading the input file are not saved until the library is fully optimized. self.__vzmodel.NasOptInputReader.SetLoggingConfigurationErrors() elif self.__vzmodel.AbaqusInfoInputFileData is not None: self.__modelinputdata = N2PAbaqusInputData(self.__vzmodel.AbaqusInfoInputFileData) else: self.__modelinputdata = None t2 = time.time() - t1 N2PLog.Debug.D100("N2PModelInputData", t2) # -------------------------------------------------------------------------------------------------------------- # endregion #################################################################################################################### # region PROPERTIES #################################################################################################################### # Metodo para Obtener la Ruta del Modelo --------------------------------------------------------------------------- @property def FilePath(self) -> str: """Path (may be relative or absolute) file where the model was read from.""" return (str(self.__vzmodel.FilePath)) # ------------------------------------------------------------------------------------------------------------------ # Metodo para Obtener el Solver del Modelo ------------------------------------------------------------------------- @property def Solver(self) -> str: """Solver of the model""" return self.__solver # ------------------------------------------------------------------------------------------------------------------ # Metodo para Obtener la Version de Abaqus del Modelo -------------------------------------------------------------- @property def AbaqusVersion(self) -> str: """ Returns the Abaqus version used in the output result file if the solver is Abaqus """ if (self.__solver == 'Abaqus'): return (str(self.__vzmodel.version_abqs_model.ToString())) else: return None # ------------------------------------------------------------------------------------------------------------------ # Metodo para obtener el numero de casos de carga en el modelo ----------------------------------------------------- @property def LoadCases(self) -> list[N2PLoadCase]: """ Returns a list with all the of N2PLoadCase """ return self.__load_cases__ # ------------------------------------------------------------------------------------------------------------------ # Metodo para obtener el numero de casos de carga en el modelo ----------------------------------------------------- @property def NumberLoadCases(self) -> int: """Number of load cases of the model""" if self.__number_of_load_cases is None: self.__number_of_load_cases = int(self.__vzmodel.LoadCases.Count) return int(self.__vzmodel.LoadCases.Count) else: return int(self.__number_of_load_cases) # ------------------------------------------------------------------------------------------------------------------ @property def Parts(self) -> list[str]: """Returns the list of parts/superelements of the model""" return list(self.__partIDtoStr.values()) @property def ElementsDict(self) -> OrderedDict[tuple[int, int], N2PElement]: """Returns a dictionary with the :class:`N2PElement` of the model as values. The key is the tuple (id, partid)""" return self.__element_dict @property def NodesDict(self) -> OrderedDict[tuple[int, int], N2PNode]: """Returns a dictionary with the :class:`N2PNode` of the model as values. The key is the tuple (id, partid)""" return self.__node_dict @property def ConnectorsDict(self) -> OrderedDict[tuple[int, int], N2PConnector]: """Returns a dictionary with the :class:`N2PConnector` of the model as values. The key is the tuple (id, partid)""" return self.__connector_dict @property def SetList(self) -> list[N2PSet]: """Returns a list of :class:`N2PSet` kept in the model. It works for Abaqus, Optitruct (*.h3d), Nastran (*.op2, *.h5) Note: The sets in *.op2 Nastran are not saved by default. To keep this information the command `PARAM, POSTEXT, YES` must be used in the Bulk Data Section before running the solution. Warning: The type of sets it is not usually saved, so the IDs could be for Nodes or Elements. The user must know if the set was defined for Elements or Nodes. """ if not self.__setlist: self.__setlist = [N2PSet(_) for _ in list(self.__vzmodel.SetList)] return self.__setlist @property def ModelInputData(self) -> Union[N2PNastranInputData, N2PAbaqusInputData]: """Returns a :class:`N2PNastranInputData` or a :class:`N2PAbaqusInputData` with the information of the input data file""" if self.__modelinputdata is None: N2PLog.Warning.W204() return self.__modelinputdata #################################################################################################################### ########################################### N2PMaterial ##################################################### #################################################################################################################### @property def MaterialDict(self) -> dict[tuple[Union[int,str], str], N2PMaterial]: """ Dictionary of materials. Key is a tuple that is ID (a int for Nastran/Optistruct or str for Abaqus) and the part (str), and value is a :class:`N2PMaterial` It is not initialized at first, only when it is needed. Returns: dict[(ID, Part)] = N2PMaterial Example: >>> # For nastran the first item of the tuple is a int >>> nastran_model = n2p.load_model(r".\\nastran.op2") >>> my_mat_10 = model.MaterialDict[(10, "0")] >>> prop = model.PropertyDict(1000) >>> my_mat = model.MaterialDict[(prop.MatID, prop.Part)] >>> # For abaqus the first item of the tuple is a str >>> abaqus_model = n2p.load_model("abaqus.odb") >>> my_mat_Al = model.MaterialDict[("ALUMINIUM", "0")] >>> prop = model.PropertyDict(1000) >>> my_mat = model.MaterialDict[(prop.MatID, prop.Part)] """ t1 = time.time() if not self.__material_dict: dictmateriales = dict(self.__vzmodel.N2MatDict) if self.Solver == "Abaqus" or self.Solver == "InputFileAbaqus": # For Abaqus, the materials are stored as a dict with str keys self.__materialsID = list(self.__vzmodel.DictMatToStr) for key, value in dictmateriales.items(): if self.Solver == "Nastran" or self.Solver == "H3DOptistruct" or self.Solver == "InputFileNastran" or self.Solver == "InputFileH3DOptistruct": key = (int(key.Item1), str(key.Item2)) else: key = (str(key.Item1), str(key.Item2)) if value.GetType().ToString() == 'NaxToModel.N2MatE': self.__material_dict[key] = N2PMatE(value, self) elif value.GetType().ToString() == 'NaxToModel.N2MatI': self.__material_dict[key] = N2PMatI(value, self) elif value.GetType().ToString() == 'NaxToModel.N2MatMisc': self.__material_dict[key] = N2PMatMisc(value, self) t2 = time.time() N2PLog.Debug.D100("N2PMaterial Dictionary", t2 - t1) return self.__material_dict # ------------------------------------------------------------------------------------------------------------------ #################################################################################################################### ########################################### N2PProperty ##################################################### #################################################################################################################### @property def PropertyDict(self) -> dict[int, N2PProperty]: """ Dictionary of properties. Key is a tuple that is ID (a int for Nastran/Optistruct or str for Abaqus) and the part (str), and value is a :class:`N2PProperty` It is not initialized at first, only when it is needed. Returns: dict[(ID, Part)] = N2PProperty Example: >>> # For nastran the first item of the tuple is a int >>> nastran_model = n2p.load_model(r".\\nastran.op2") >>> my_prop_20 = model.PropertyDict[(20, "0")] >>> my_prop = model.PropertyDict[(ele.Prop, prop.Part)] >>> # For abaqus the first item of the tuple is a str >>> abaqus_model = n2p.load_model("abaqus.odb") >>> my_prop_shell = model.PropertyDict[("SHELL", "0")] >>> my_prop = model.PropertyDict[(ele.Prop, prop.Part)] """ t1 = time.time() if not self.__property_dict: dictprop = dict(self.__vzmodel.N2PropDict) for key, value in dictprop.items(): if self.Solver == "Nastran" or self.Solver == "H3DOptistruct" or self.Solver == "InputFileNastran" or self.Solver == "InputFileH3DOptistruct": key = (int(key.Item1), str(key.Item2)) if key[0] < 0: N2PLog.Error.E227(key) else: key = (str(key.Item1), str(key.Item2)) if value.GetType().ToString() == 'NaxToModel.N2Shell': self.__property_dict[key] = N2PShell(value, self) elif value.GetType().ToString() == 'NaxToModel.N2Comp': self.__property_dict[key] = N2PComp(value, self) elif value.GetType().ToString() == 'NaxToModel.N2Solid': self.__property_dict[key] = N2PSolid(value, self) elif value.GetType().ToString() == 'NaxToModel.N2Beam': self.__property_dict[key] = N2PBeam(value, self) elif value.GetType().ToString() == 'NaxToModel.N2Rod': self.__property_dict[key] = N2PRod(value, self) elif value.GetType().ToString() == 'NaxToModel.N2Bush': self.__property_dict[key] = N2PBush(value, self) elif value.GetType().ToString() == 'NaxToModel.N2Fast': self.__property_dict[key] = N2PFast(value, self) elif value.GetType().ToString() == 'NaxToModel.N2Mass': self.__property_dict[key] = N2PMass(value, self) elif value.GetType().ToString() == 'NaxToModel.N2Elas': self.__property_dict[key] = N2PElas(value, self) elif value.GetType().ToString() == 'NaxToModel.N2Gap': self.__property_dict[key] = N2PGap(value, self) elif value.GetType().ToString() == 'NaxToModel.N2Weld': self.__property_dict[key] = N2PWeld(value, self) elif value.GetType().ToString() == 'NaxToModel.N2BeamL': self.__property_dict[key] = N2PBeamL(value, self) elif value.GetType().ToString() == 'NaxToModel.N2PropMisc': self.__property_dict[key] = N2PPropMisc(value, self) t2 = time.time() N2PLog.Debug.D100("N2PProperty Dictionary", t2 - t1) return self.__property_dict # endregion #################################################################################################################### # region METHODS #################################################################################################################### # Metodo para obtener los casos de carga del modelo ---------------------------------------------------------------- @overload def get_load_case(self, id: int) -> Union[N2PLoadCase, None]: ... @overload def get_load_case(self, name: str) -> Union[N2PLoadCase, None]: ... @overload def get_load_case(self, id: list[int]) -> Union[list[N2PLoadCase], None]: ... @overload def get_load_case(self, name: list[str]) -> Union[list[N2PLoadCase], None]: ... # Actual implementation
[docs] def get_load_case(self, id: Union[int, None] = None, name: Union[str, None] = None) -> Union[list[N2PLoadCase], N2PLoadCase, None]: """Returns a list of :class:`N2PLoadCase` objects with all the load cases contained in the output result file. Args: id (int|list[int], optional): ID of the load case. Defaults to None. name (str|list[int], optional): Name of the load case. Defaults to None. Returns: N2PLoadCase: a load case list[N2PLoadCase]: load cases list Example: >>> a_loadcase = model.get_load_case(10) >>> a_loadcase = model.get_load_case("Mechanical") >>> some_loadcases = model.get_load_case([10, 20]) >>> some_loadcases = model.get_load_case(["Mechanical", "Thermal"]) """ # Check if the input ID is a string or a list of strings if isinstance(id, str) or (isinstance(id, list) and all(isinstance(item, str) for item in id)): name = id id = None # Check the value of ID as a int if id is not None and isinstance(id, int): if not self.__LCs_by_ID__: self.__LCs_by_ID__ = {loadcase.ID: loadcase for loadcase in self.LoadCases} aux = self.__LCs_by_ID__.get(id, None) else: aux = self.__LCs_by_ID__.get(id, None) if aux is None: N2PLog.Error.E221(id) return aux # Check the value of ID as list if id is not None and isinstance(id, list): if not self.__LCs_by_ID__: self.__LCs_by_ID__ = {loadcase.ID: loadcase for loadcase in self.LoadCases} aux = [] for id_i in id: lc = self.__LCs_by_ID__.get(id_i, None) aux.append(lc) if lc is None: N2PLog.Error.E221(id_i) return aux # Check the value of Name elif name is not None and isinstance(name, str): if not self.__LCs_by_Name__: self.__LCs_by_Name__ = {loadcase.Name: loadcase for loadcase in self.LoadCases} aux = self.__LCs_by_Name__.get(name, None) if aux is None: N2PLog.Error.E222(name) return aux # Check the value of name as list if name is not None and isinstance(name, list): if not self.__LCs_by_Name__: self.__LCs_by_Name__ = {loadcase.Name: loadcase for loadcase in self.LoadCases} aux = [] for n_i in name: lc = self.__LCs_by_Name__.get(n_i, None) aux.append(lc) if lc is None: N2PLog.Error.E222(n_i) return aux # Return all load cases else: return self.LoadCases
# ------------------------------------------------------------------------------------------------------------------ # Metodo general para elementos ------------------------------------------------------------------------------------ @overload def get_elements(self, ids: int = 0) -> N2PElement: ... @overload def get_elements(self, ids: tuple[int, str] = 0) -> N2PElement: ... @overload def get_elements(self, ids: list[int] = 0) -> list[N2PElement]: ... @overload def get_elements(self, ids: list[tuple[int, str]] = 0) -> list[N2PElement]: ... # Actual implementation
[docs] def get_elements(self, ids = 0): """ General method for obtain elements. If it has no argument or is 0: returns all the elements If it has one id as argument: returns one :class:`N2PElement` If it has a list as argument, it returns a list of :class:`N2PElement` The ids should be a tuple (id, part_id). If not, part_id = 0 by default Args: ids: int | list[int] | tuple[int, str] | list[tuple[int, str]] Returns: object: N2PElement | list[N2PElement] """ try: if ids == 0: elements = list(self.__element_dict.values()) return elements if ids != 0 and isinstance(ids, int): if self.Solver == "Nastran" or self.Solver == "H3DOptistruct" or self.Solver == "Ansys" or self.Solver == "InputFileNastran": if len(self.__partIDtoStr) > 1: N2PLog.Error.E223() elements = -2 else: elements = self.__element_dict.get((ids, 0), -1) elif self.Solver == "Abaqus": if len(self.__partIDtoStr) > 2: N2PLog.Error.E223() elements = -2 else: elements = self.__element_dict.get((ids, 1), -1) if elements == -1: N2PLog.Error.E202(ids) return elements if isinstance(ids, tuple) and len(ids) == 2: if isinstance(ids[1], str): aux = self.__StrPartToID.get(ids[1]) elements = self.__element_dict.get((ids[0], aux), -1) else: elements = self.__element_dict.get(ids, -1) if elements == -1: N2PLog.Error.E202(ids) return elements if (isinstance(ids, tuple) and len(ids) > 2) or isinstance(ids, list): elements = list() if isinstance(ids[0], tuple): for id in ids: if isinstance(id[1], str): aux = self.__StrPartToID.get(id[1]) elemento = self.__element_dict.get((id[0], aux), -1) else: elemento = self.__element_dict.get(id, -1) if elemento == -1: N2PLog.Error.E202(id) elements.append(elemento) return elements if isinstance(ids[0], int): for id in ids: if self.Solver == "Nastran" or self.Solver == "H3DOptistruct" or self.Solver == "Ansys" or self.Solver == "InputFileNastran": if len(self.__partIDtoStr) > 1: N2PLog.Error.E223() elemento = -2 else: elemento = self.__element_dict.get((id, 0), -1) elif self.Solver == "Abaqus": if len(self.__partIDtoStr) > 2: N2PLog.Error.E223() elements = -2 else: elemento = self.__element_dict.get((id, 1), -1) if elemento == -1: N2PLog.Error.E202(id) elements.append(elemento) return elements return elements except: elements = -1 N2PLog.Error.E205(ids) return elements
[docs] def get_elements_filtered(self, materials: Union[str, int, list[Union[str, int]]] = None, properties: Union[str, int, list[Union[str, int]]] = None, parts: Union[str, list[str]] = None, elementType: Union[str, list[str]] = None) -> list[N2PElement]: """ Filters and returns a list of :class:`N2PElement` objects based on the specified criteria. This function allows external users to retrieve model elements by applying different filters. Materials, properties, parts, and element types can be specified to refine the search. If multiple filters are provided, the function returns only elements that meet all conditions (AND logic). If no filters are specified, an empty list is returned. Args: materials (list[str], optional): List of material names to filter elements by material type. properties (list[str], optional): List of properties to filter elements based on attributes. parts (list[str], optional): List of part names to obtain elements belonging to specific parts. elementType (list[str], optional): List of element types to filter elements by type. Returns: list[N2PElement]: List of elements that meet the applied filters. Example: >>> model = n2p.load_model(r".\\abaqus_model.odb") >>> elements = model.get_elements_filtered(materials=['Steel'], properties=['Steel Shell']) >>> model = n2p.load_model(r".\\nastran_model.op2") >>> elements = model.get_elements_filtered(materials=[10001]) Note: - If both `materials` and `parts` are provided, elements from the specified materials within the given parts will be filtered. - It is recommended to use this function when a specific selection of model elements is needed. - This method is faster than making a comprehension list. """ modelContent = self.__vzmodel filters = [] # Lista para almacenar los conjuntos de IDs if isinstance(materials, (str, int)): materials = [materials] if isinstance(properties, (str, int)): properties = [properties] if isinstance(parts, str): parts = [parts] if isinstance(elementType, str): elementType = [elementType] if materials and any(isinstance(m, int) for m in materials): materials = [str(m) for m in materials] if properties and any(isinstance(p, int) for p in properties): properties = [str(p) for p in properties] # Realizamos comprobación de la existencia de los distintos filtros y convertimos los input en variables C# if properties is not None: csharp_properties = System.Array.CreateInstance(System.String, len(properties)) for i, property in enumerate(properties): csharp_properties[i] = property filterIdsByProperties = set(N2SelectionFunctions.FilterIdsByProperties( csharp_properties, modelContent )) filters.append(filterIdsByProperties) if elementType is not None: csharp_elementType = System.Array.CreateInstance(System.String, len(elementType)) for i, eleType in enumerate(elementType): csharp_elementType[i] = eleType filterIdsByElementType = set(N2SelectionFunctions.FilterIdsByElementType( csharp_elementType, modelContent )) filters.append(filterIdsByElementType) if parts is not None: csharp_parts = System.Array.CreateInstance(System.String, len(parts)) for i, part in enumerate(parts): csharp_parts[i] = part if materials is not None: csharp_materials = System.Array.CreateInstance(System.String, len(materials)) for i, material in enumerate(materials): csharp_materials[i] = material filterIdsByMaterials = set(N2SelectionFunctions.FilterIdsByMaterials( csharp_materials, modelContent, csharp_parts )) filters.append(filterIdsByMaterials) else: filterIdsByParts = set(N2SelectionFunctions.FilterIdsByParts( csharp_parts, modelContent )) filters.append(filterIdsByParts) else: if materials is not None: csharp_materials = System.Array.CreateInstance(System.String, len(materials)) for i, material in enumerate(materials): csharp_materials[i] = material filterIdsByMaterials = set(N2SelectionFunctions.FilterIdsByMaterials( csharp_materials, modelContent )) filters.append(filterIdsByMaterials) # Realizar la intersección de todos los filtros existentes if filters: result_ids = set.intersection(*filters) else: result_ids = set() # Si no hay filtros, devuelve un conjunto vacío return [self.__cells_list[id] for id in result_ids]
# Método para obtener materiales ----------------------------------------------------------------------------------- @overload def get_materials(self, ids: int = 0) -> N2PMaterial: ... @overload def get_materials(self, ids: tuple[int, str] = 0) -> N2PMaterial: ... @overload def get_materials(self, ids: list[int] = 0) -> list[N2PMaterial]: ... @overload def get_materials(self, ids: list[tuple[int, str]] = 0) -> list[N2PMaterial]: ... @overload def get_materials(self, ids: str = 0) -> N2PMaterial: ... @overload def get_materials(self, ids: list[tuple[str, str]] = 0) -> list[N2PMaterial]: ... # Actual implementation
[docs] def get_materials(self, ids = 0): """ General method for obtain materials. If it has no argument or is 0: returns all the materials If it has one id as argument: returns one :class:`N2PMaterial` \\ If it has a list as argument, it returns a list of :class:`N2PMaterial` The ids should be a tuple (id, part_id). If not, part_id = 0 by default Args: ids: int | str | list[int] | tuple[int, str] | list[tuple[int, str]] Returns: object: N2PMaterial | list[N2PMaterial] """ try: if ids == 0: materials = list(self.MaterialDict.values()) return materials if ids != 0 and isinstance(ids,(int,str)): if self.Solver == "Nastran" or self.Solver == "H3DOptistruct" or self.Solver == "Ansys" or self.Solver == "InputFileNastran": if len(self.__partIDtoStr) > 1: N2PLog.Error.E223() materials = -2 else: part = next(iter(self.__StrPartToID)) materials = self.MaterialDict.get((ids,part),-1) elif self.Solver == 'Abaqus': materials = self.MaterialDict.get((ids,'ASSEMBLY'),-1) #No necesito mirar si hay más de una parte porque siempre va a ser assembly para materiales if materials == -1: N2PLog.Error.E672(ids) return materials if isinstance(ids, tuple) and len(ids) == 2: # No necesito mirar si la parte de la tupla es un string porque siempre debe serlo materials = self.MaterialDict.get((ids[0],ids[1]),-1) if materials == -1: N2PLog.Error.E672(ids) return materials if isinstance(ids, list): materials = list() if isinstance(ids[0],tuple): for id in ids: material = self.MaterialDict.get((id[0],id[1]),-1) if material == -1: N2PLog.Error.E672(ids) materials.append(material) return materials if isinstance(ids[0], (int,str)): for id in ids: if self.Solver == "Nastran" or self.Solver == "H3DOptistruct" or self.Solver == "Ansys" or self.Solver == "InputFileNastran": if len(self.__partIDtoStr) > 1: N2PLog.Error.E223() material = -2 else: part = next(iter(self.__StrPartToID)) material = self.MaterialDict.get((id,part),-1) elif self.Solver == 'Abaqus': material = self.MaterialDict.get((id,'ASSEMBLY'),-1) #No necesito mirar si hay más de una parte porque siempre va a ser assembly para materiales if material == -1: N2PLog.Error.E672(id) materials.append(material) return materials return materials except: materials = -1 N2PLog.Error.E205(ids) return materials
# Método para obtener propiedades ----------------------------------------------------------------------------------- @overload def get_properties(self, ids: int = 0) -> N2PProperty: ... @overload def get_properties(self, ids: tuple[int, str] = 0) -> N2PProperty: ... @overload def get_properties(self, ids: list[int] = 0) -> list[N2PProperty]: ... @overload def get_properties(self, ids: list[tuple[int, str]] = 0) -> list[N2PProperty]: ... @overload def get_properties(self, ids: str = 0) -> N2PProperty: ... @overload def get_properties(self, ids: list[tuple[str, str]] = 0) -> list[N2PProperty]: ... # Actual implementation
[docs] def get_properties(self, ids = 0): """ General method for obtain properties. If it has no argument or is 0: returns all the properties If it has one id as argument: returns one :class:`N2PProperty` \\ If it has a list as argument, it returns a list of :class:`N2PProperty` The ids should be a tuple (id, part_id). If not, part_id = 0 by default Args: ids: int | str | list[int] | tuple[int, str] | list[tuple[int, str]] Returns: object: N2PProperty | list[N2PProperty] """ try: if ids == 0: properties = list(self.PropertyDict.values()) return properties if ids != 0 and isinstance(ids,(int,str)): if self.Solver == "Nastran" or self.Solver == "H3DOptistruct" or self.Solver == "Ansys" or self.Solver == "InputFileNastran": if len(self.__partIDtoStr) > 1: N2PLog.Error.E223() properties = -2 else: part = next(iter(self.__StrPartToID)) properties = self.PropertyDict.get((ids,part),-1) elif self.Solver == 'Abaqus': properties = self.PropertyDict.get((ids,'ASSEMBLY'),-1) #No necesito mirar si hay más de una parte porque siempre va a ser assembly para materiales if properties == -1: N2PLog.Error.E673(ids) return properties if isinstance(ids, tuple) and len(ids) == 2: # No necesito mirar si la parte de la tupla es un string porque siempre debe serlo properties = self.PropertyDict.get((ids[0],ids[1]),-1) if properties == -1: N2PLog.Error.E673(ids) return properties if isinstance(ids, list): properties = list() if isinstance(ids[0],tuple): for id in ids: prop = self.PropertyDict.get((id[0],id[1]),-1) if prop == -1: N2PLog.Error.E673(ids) properties.append(prop) return properties if isinstance(ids[0], (int,str)): for id in ids: if self.Solver == "Nastran" or self.Solver == "H3DOptistruct" or self.Solver == "Ansys" or self.Solver == "InputFileNastran": if len(self.__partIDtoStr) > 1: N2PLog.Error.E223() prop = -2 else: part = next(iter(self.__StrPartToID)) prop = self.PropertyDict.get((id,part),-1) elif self.Solver == 'Abaqus': prop = self.PropertyDict.get((id,'ASSEMBLY'),-1) #No necesito mirar si hay más de una parte porque siempre va a ser assembly para materiales if prop == -1: N2PLog.Error.E673(id) properties.append(prop) return properties return properties except Exception as e: properties = -1 N2PLog.Error.E205(str(e)) return properties
# Metodo general para nodos ----------------------------------------------------------------------------------------
[docs] def get_nodes(self, ids=0) -> Union[N2PNode, list[N2PNode]]: """ General method for obtain nodes. If it has no argument or is 0: returns all the nodes. If it has one id as argument: returns one :class:`N2PNode`. If it has a list as argument, it returns a list of :class:`N2PNode`. The ids should be a tuple (id, part_id). If not, part_id = 0 by default. Args: ids: int | list[int] | tuple[int, str] | list[tuple[int, str]] Returns: object: N2PNode | list[N2PNode] """ try: if ids == 0: nodes = list(self.__node_dict.values()) return nodes if ids != 0 and isinstance(ids, int): if self.Solver == "Nastran" or self.Solver == "H3DOptistruct" or self.Solver == "Ansys" or self.Solver == "InputFileNastran": if len(self.__partIDtoStr) > 1: N2PLog.Error.E224() nodes = -2 else: nodes = self.__node_dict.get((ids, 0), -1) elif self.Solver == "Abaqus": if len(self.__partIDtoStr) > 2: N2PLog.Error.E224() nodes = -2 else: nodes = self.__node_dict.get((ids, 1), -1) if nodes == -1: N2PLog.Error.E201(ids) return nodes if isinstance(ids, tuple) and len(ids) == 2: if isinstance(ids[1], str): aux = self.__StrPartToID.get(ids[1]) nodes = self.__node_dict.get((ids[0], aux), -1) else: nodes = self.__node_dict.get(ids, -1) if nodes == -1: N2PLog.Error.E201(ids) return nodes if (isinstance(ids, tuple) and len(ids) > 2) or isinstance(ids, list): nodes = list() if isinstance(ids[0], tuple): for id in ids: if isinstance(id[1], str): aux = self.__StrPartToID.get(id[1]) nodo = self.__node_dict.get((id[0], aux), -1) else: nodo = self.__node_dict.get(id, -1) if nodo == -1: N2PLog.Error.E201(id) nodes.append(nodo) return nodes if isinstance(ids[0], int): for id in ids: if self.Solver == "Nastran" or self.Solver == "H3DOptistruct" or self.Solver == "Ansys" or self.Solver == "InputFileNastran": if len(self.__partIDtoStr) > 1: N2PLog.Error.E224() nodo = -2 else: nodo = self.__node_dict.get((id, 0), -1) elif self.Solver == "Abaqus": if len(self.__partIDtoStr) > 2: N2PLog.Error.E224() nodes = -2 else: nodo = self.__node_dict.get((id, 1), -1) if nodo == -1: N2PLog.Error.E201(id) nodes.append(nodo) return nodes return nodes except: nodes = -1 N2PLog.Error.E205(ids) return nodes
# ------------------------------------------------------------------------------------------------------------------ # Metodo general para conectores -----------------------------------------------------------------------------------
[docs] def get_connectors(self, ids=0) -> Union[N2PConnector, list[N2PConnector]]: """ General method for obtain connectors. If it has no argument or is 0: returns all the elements. If it has one id as argument: returns one :class:`N2PConnector`. If it has a list as argument, it returns a list of :class:`N2PConnector`. The ids should be a tuple (id, part_id). If not, part_id = 0 by default. Args: ids: int | list[int] | tuple[int, str] | list[tuple[int, str]] Returns: object: N2PConnector | list[N2PConnector] """ try: if ids == 0: connectors = list(self.__connector_dict.values()) return connectors if ids != 0 and isinstance(ids, int): if self.Solver == "Nastran" or self.Solver == "H3DOptistruct" or self.Solver == "Ansys" or self.Solver == "InputFileNastran": if len(self.__partIDtoStr) > 1: N2PLog.Error.E225() connectors = -2 else: connectors = self.__connector_dict.get((ids, 0), -1) elif self.Solver == "Abaqus": if len(self.__partIDtoStr) > 2: N2PLog.Error.E225() connectors = -2 else: connectors = self.__connector_dict.get((ids, 1), -1) if connectors == -1: N2PLog.Error.E204(ids) return connectors if isinstance(ids, tuple) and len(ids) == 2: if isinstance(ids[1], str): aux = self.__StrPartToID.get(ids[1]) connectors = self.__connector_dict.get((ids[0], aux), -1) else: connectors = self.__connector_dict.get(ids, -1) if connectors == -1: N2PLog.Error.E204(ids) return connectors if (isinstance(ids, tuple) and len(ids) > 2) or isinstance(ids, list): connectors = list() if isinstance(ids[0], tuple): for id in ids: if isinstance(id[1], str): aux = self.__StrPartToID.get(id[1]) conector = self.__connector_dict.get((id[0], aux), -1) else: conector = self.__connector_dict.get(id, -1) if conector == -1: N2PLog.Error.E204(id) connectors.append(conector) return connectors if isinstance(ids[0], int): for id in ids: if self.Solver == "Nastran" or self.Solver == "H3DOptistruct" or self.Solver == "Ansys" or self.Solver == "InputFileNastran": if len(self.__partIDtoStr) > 1: N2PLog.Error.E225() conector = -2 else: conector = self.__connector_dict.get((id, 0), -1) elif self.Solver == "Abaqus": if len(self.__partIDtoStr) > 2: N2PLog.Error.E225() connectors = -2 else: conector = self.__connector_dict.get((id, 1), -1) if conector == -1: N2PLog.Error.E204(id) connectors.append(conector) return connectors return connectors except: connectors = -1 N2PLog.Error.E205(ids) return connectors
# ------------------------------------------------------------------------------------------------------------------ # Metodo para obtener todos los sistemas de coordenadas (como N2Coord)----------------------------------------------
[docs] def get_coords(self, ids=0) -> Union[N2PCoord, list[N2PCoord]]: """General method for obtain coordinate systems. If it has no argument or is 0: returns all the coordinate systems If it has one id as argument: returns one :class:`N2PCoord` If it has a list as argument, it returns a list of :class:`N2PCoord` The ids should be a tuple (id, part_id). If not, part_id = 0 by default Args: ids: int | list[int] | tuple[int, str] | list[tuple[int, str]] Returns: object: N2PCoord | list[N2PCoord] """ try: if ids == 0: coords = list(self.__coord_dict.values()) return coords if ids != 0 and isinstance(ids, int): coords = self.__coord_dict.get((ids, 0), -1) if coords == -1: N2PLog.Error.E203(ids) return coords if isinstance(ids, tuple) and len(ids) == 2: if isinstance(ids[1], str): aux = self.__StrPartToID.get(ids[1]) coords = self.__coord_dict.get((ids[0], aux), -1) else: coords = self.__coord_dict.get(ids, -1) if coords == -1: N2PLog.Error.E203(ids) return coords if (isinstance(ids, tuple) and len(ids) > 2) or isinstance(ids, list): coords = list() if isinstance(ids[0], tuple): for id in ids: if isinstance(id[1], str): aux = self.__StrPartToID.get(id[1]) coord = self.__coord_dict.get((id[0], aux), -1) else: coord = self.__coord_dict.get(id, -1) if coord == -1: N2PLog.Error.E203(id) coords.append(coord) return coords if isinstance(ids[0], int): for id in ids: coord = self.__coord_dict.get((id, 0), -1) if coord == -1: N2PLog.Error.E203(id) coords.append(coord) return coords return coords except: coords = -1 N2PLog.Error.E205(ids) return coords
# ------------------------------------------------------------------------------------------------------------------ #################################################################################################################### ######################################## ClearResultStorage ################################################# ####################################################################################################################
[docs] def clear_results_memory(self) -> None: """Method that deletes the results data store at low level. It is useful when a lot of results are asked and kept in Python memory""" self.__vzmodel.ClearResultsStorage()
# ------------------------------------------------------------------------------------------------------------------ #################################################################################################################### ########################################## Element-Nodal #################################################### #################################################################################################################### # Metodo oculto para generar el array de nodos descosidos-----------------------------------------------------------
[docs] def elementnodal(self, elements: list[N2PElement] = None) -> dict[int, tuple]: """ Method that generates the dictionary for the element-nodal mesh. Use the internal id for unsew nodes as key and a tuple (ID_Part_Solver, ID_Node_Solver, ID_Element_Solver) as a value. If the dictionary has already created it is pointed instead. Args: elements: list[N2PElement] (optional) If a list of N2PElements is provided it returns a dict for the unsew nodes of those elements Returns: dict[internal_node_unsew] = (Part_Solver, ID_Node_Solver, ID_Element_Solver) Example: >>> all_unsew_nodes = model.elementnodal() >>> elements = model.get_elements([1001, 1002, 1003]) >>> some_unsew_nodes = model.elementnodal(elements) """ # Si ya se ha creado no se vuelve a crear, directamente devuelve el valor if not elements: if self.__elementnodal_dict is not None: return self.__elementnodal_dict else: aux = self.__vzmodel.GetNodosDescosidos() aux2 = np.array(aux, dtype=np.int32) self.__elementnodal_dict = {i: (self.__partIDtoStr.get(aux2[i][2], None), aux2[i][0], aux2[i][1]) for i in range(aux2.shape[0])} return self.__elementnodal_dict else: elements_id = {ele.ID for ele in elements} aux = self.__vzmodel.GetNodosDescosidos() aux2 = np.array(aux, dtype=np.int32) return {i: (self.__partIDtoStr.get(aux2[i][2], None), aux2[i][0], aux2[i][1]) for i in range(aux2.shape[0]) if aux2[i][1] in elements_id}
# ------------------------------------------------------------------------------------------------------------------ #################################################################################################################### ########################################## Free Bodies #################################################### ####################################################################################################################
[docs] def create_free_body(self, name: str, loadcase: N2PLoadCase, increment: N2PIncrement = None, nodes: list[N2PNode] = None, elements: list[N2PElement] = None, outpoint: tuple[float, ...] = None, coordsysout: N2PCoord = None) -> N2PFreeBody: """Method that creates a :class:`N2PFreeBody` object, keeps in :meth:`FreeBodiesDict` and returns it. A :class:`N2PFreeBody` contains the information of the cross-section defined by the user and the forces and moments calculated by NaxToPy durin the instance of the object. Args: name: str -> Must be unique. If a new FreeBody is name as an old one, the old FreeBody will be deleted. loadcase: N2PLoadCase increment: N2PIncrement nodes: list[N2PNode] elements: list[N2PElement] outpoint: tuple[float] coordsysout: N2PCoord Returns: N2PFreeBody: freebody Example: >>> model = load_model("model.op2") >>> lc = model.get_load_case(5) >>> incr = lc.ActiveN2PIncrement >>> node_list = model.get_nodes([1000, 1001, 1002, 1003, 1004, 1005]) >>> elem_list = model.get_elements([1000, 1001, 1002, 1003, 1004]) """ if increment is None: increment = loadcase.ActiveN2PIncrement return N2PFreeBody(name, loadcase, increment, nodes, elements, self, coordsysout, outpoint)
# ------------------------------------------------------------------------------------------------------------------ def _import_load_cases(self): lcs = self.__vzmodel.LoadCases __number_of_load_cases = int(lcs.Count) self.__load_cases__ = [N2PLoadCase(lc.ID, lc.IDOriginal, lc.Increments, lc.IncrementsNumberList, lc.LCType, lc.Name, lc.Results, lc.SolutionType, lc.Solver, self, lc) for lc in lcs] #################################################################################################################### ######################################## import_results ################################################### ####################################################################################################################
[docs] def import_results_from_files(self, results_files: list[str]) -> None: """Method of :class:`N2PModelContent` that reads an output result file from Nastran, Abaqus, Optistruct or Ansys and add the results to the :class:`N2PModelContent` instance. Supports .op2, .xdb, .odb, .h5, .h3d and .rst file extensions read from a local filesystem or URL. Args: results_files: list[str] Example: >>> model1 = load_model("model1_lc1.odb") >>> model1.import_results_from_files(["model1_lc2.odb", "mode1l_lc3.odb"]) >>> model2 = load_model("model2.dat", "InputFileNastran") >>> model2.import_results_from_files(["model2_lc1.op2", "model2_lc2.op2"]) """ if isinstance(results_files, str): results_files = [results_files] for result_file in results_files: if os.path.isfile(result_file): self.__vzmodel.ImportResults(str(result_file)) N2PLog.Debug.D201(str(result_file)) else: N2PLog.Error.E232(str(result_file)) self._import_load_cases() self.__LCs_by_ID__ = {lc.ID: lc for lc in self.LoadCases}
# ------------------------------------------------------------------------------------------------------------------ #################################################################################################################### ################################## get_adjacent_from_elements ############################################# ####################################################################################################################
[docs] def get_elements_adjacent(self, cells: list[N2PElement, N2PConnector], domain: list[N2PElement, N2PConnector] = None) -> list[N2PElement, N2PConnector]: """Method of :class:`N2PModelContent` that returns a list of :class:`N2PElement` and :class:`N2PConnector` that are adjacent to selected ones. If a domain is selected, the adjacent elements only will be searched in that domain. Args: cells: list[N2PElement, N2PConnector, ...] domain: list[N2PElement, N2PConnector, ...] -> Optional. Set the domain where to look for the adjacent elements Returns: list[N2PElement, N2PConnector, ...] Example: >>> model = load_model("model.odb") >>> domain_S4 = [e for e in model.get_elements() if e.TypeElement == "S4"] >>> elems_adj = model.get_elements_adjacent(model.get_elements(17500), domain=domain_S4) """ if not isinstance(cells, list): cells = [cells] inernal_ids = array.array("q", [cell.InternalID for cell in cells]) if not domain: adjacent_ids = N2AdjacencyFunctions.GetAdjacent(self.__vzmodel.UMesh, inernal_ids) else: csharp_bool_array = System.Array.CreateInstance(System.Boolean, len(self.__element_dict) + len(self.__connector_dict)) for elem in domain: csharp_bool_array[elem.InternalID] = True adjacent_ids = N2AdjacencyFunctions.GetAdjacent(self.__vzmodel.UMesh, inernal_ids, csharp_bool_array) cell_list = self.__cells_list return [cell_list[ids] for ids in adjacent_ids]
# ------------------------------------------------------------------------------------------------------------------ #################################################################################################################### ################################## get_adjacent_from_elements ############################################# ####################################################################################################################
[docs] def get_elements_attached(self, cells: list[N2PElement, N2PConnector], domain: list[N2PElement, N2PConnector] = None) -> list[N2PElement, N2PConnector]: """Method of :class:`N2PModelContent` that returns a list of :class:`N2PElement` and :class:`N2PConnector` that are attached to selected ones. If a domain is selected, the attached elements only will be searched in that domain. Args: cells: list[N2PElement, N2PConnector, ...] domain: list[N2PElement, N2PConnector, ...] -> Optional. Set the domain where to look for the adjacent elements Returns: list[N2PElement, N2PConnector, ...] Example: >>> model = load_model("model.h3d") >>> domain_elems = model.get_elements() >>> elems_att = model.get_elements_attached(model.get_elements(17500), domain=domain_elems) """ if not isinstance(cells, list): cells = [cells] inernal_ids = array.array("q", [cell.InternalID for cell in cells]) if not domain: adjacent_ids = N2AdjacencyFunctions.GetAttached(self.__vzmodel.UMesh, inernal_ids) else: mesh_len = self.__vzmodel.UMesh.GetNumberOfCells() bool_array = np.zeros((mesh_len,), bool) domain_id = [ele.InternalID for ele in domain] bool_array[domain_id] = True adjacent_ids = N2AdjacencyFunctions.GetAttached(self.__vzmodel.UMesh, inernal_ids, _numpytonet(bool_array)) cell_list = self.__cells_list return [cell_list[ids] for ids in adjacent_ids]
# ------------------------------------------------------------------------------------------------------------------ #################################################################################################################### ######################################### get_by_face ##################################################### ####################################################################################################################
[docs] def get_elements_by_face(self, cells: list[N2PElement, N2PConnector], tolerance_angle: float = 30, one_element_adjacent: bool = True, domain: list[N2PElement, N2PConnector] = None) -> list[N2PElement, N2PConnector]: """ Method of :class:`N2PModelContent` that returns a list of :class:`N2PElement` and :class:`N2PConnector` that are in the same face as the selected ones. If a domain is selected, the face elements only will be searched in that domain. To be considered in the same face, the adjacent element must share an arist and the angle between the elements must be lower than the tolerance angle. Args: cells: list[N2PElement, N2PConnector, ...] tolerance_angle: float -> Optional (30º by default). Max angle[º] between two elements to be considered in the same face one_element_adjacent: bool -> Optional (True by default). If true, two elements are connected to edge of a selected element they will not be considered. domain: list[N2PElement, N2PConnector, ...] -> Optional. Set the domain where to look for the adjacent elements Returns: list[N2PElement, N2PConnector, ...] Example: >>> model = load_model("model.op2") >>> elems1 = model.get_elements_by_face(model.get_elements(17500)) >>> domain_cquad = [e for e in model.get_elements() if e.TypeElement == "CQUAD4"] >>> elems2 = model.get_elements_by_face(model.get_elements([17500, 17501]), 25, domain=domain_cquad) """ if not isinstance(cells, list): cells = [cells] inernal_ids = array.array("q", [cell.InternalID for cell in cells]) if not domain: adjacent_ids = N2AdjacencyFunctions.GetByFace(self.__vzmodel, inernal_ids, tolerance_angle, one_element_adjacent) else: csharp_bool_array = System.Array.CreateInstance(System.Boolean, len(self.__element_dict) + len(self.__connector_dict)) for elem in domain: csharp_bool_array[elem.InternalID] = True adjacent_ids = N2AdjacencyFunctions.GetByFace(self.__vzmodel, inernal_ids, tolerance_angle, one_element_adjacent, csharp_bool_array) cell_list = self.__cells_list return [cell_list[ids] for ids in adjacent_ids]
# ------------------------------------------------------------------------------------------------------------------ #################################################################################################################### ######################################### get_free_edges ################################################## ####################################################################################################################
[docs] def get_free_edges(self, domain: list[N2PElement, N2PConnector] = None) -> list[tuple[N2PElement, N2PNode, N2PNode]]: """ Method of :class:`N2PModelContent` that returns a list of tuples of a :class:`N2PElement` and two :class:`N2PNode` of that element. The two nodes define the edge of the element that is contained in the free edge of the mesh If a domain is selected, the tuple will only be searched in that domain. Notice that connectors, although they may be given in the domain, they are never included in the free edges. Args: domain: list[N2PElement, N2PConnector, ...] -> Optional. Set the domain where to look for the free edges Returns: list[tuple[N2PElement, N2PNode, N2PNode], ...] """ mesh_len = self.__vzmodel.UMesh.GetNumberOfCells() csharp_bool_array = System.Array.CreateInstance(System.Boolean, mesh_len) if not domain: for i in range(mesh_len): csharp_bool_array[i] = True else: for elem in domain: csharp_bool_array[elem.InternalID] = True free_edges_ids = N2FreeEdges.GetFreeEdgesFromVisibleIDs(self.__vzmodel.UMesh, csharp_bool_array) cell_list = self.__cells_list node_list = list(self.__node_dict.values()) return [(cell_list[id[0]], node_list[id[1]], node_list[id[2]]) for id in free_edges_ids]
# ------------------------------------------------------------------------------------------------------------------ #################################################################################################################### ###################################### Derived Load Case ################################################## ####################################################################################################################
[docs] def new_derived_loadcase(self, name: str, formula: str) -> N2PLoadCase: """Generate a new loadcase by linear combination of existing loadcases and frames. This method creates a :class:`N2PLoadCase` by combining multiple loadcase/frame pairs using linear arithmetic operations. The derived loadcase will have a negative ID and contains only one frame/increment with ID 0. Parameters ---------- name : ``str`` Name for the new derived loadcase. formula : ``str`` String defining the linear combination using loadcase/frame pairs and arithmetic operations. Loadcase/frame pairs must follow the structure ``<LCXX:FRYY>`` where XX is the loadcase ID and YY is the frame ID. Examples: * ``"<LC1:FR1>+2*<LC2:FR1>+0.5*<LC5:FR3>"`` * ``"0.5*<LC1:FR2>"`` * ``"5*<LC2:FR3>-2*<LC3:FR3>"`` Returns ------- N2PLoadCase Derived loadcase with negative ID containing the linear combination results. .. note:: The derived loadcase will have only one frame/increment with ID 0. When using a derived loadcase in formulas, reference it as ``<LC-X:FR0>`` where X is the absolute value of the negative ID. Examples -------- >>> dev_lc1 = new_derived_loadcase( ... "dev_lc1", ... "<LC1:FR1>+2*<LC2:FR1>+0.5*<LC5:FR3>" ... ) >>> dev_lc2 = new_derived_loadcase("dev_lc2", "0.5*<LC1:FR2>") >>> dev_lc3 = new_derived_loadcase( ... "dev_lc3", ... "5*<LC-1:FR0>+2*<LC3:FR3>" ... ) """ # Comprobamos que el nombre no se repite: i = 0 while name in [lc.Name for lc in self.LoadCases]: i += 1 name += f"-{i}" if i > 0: N2PLog.Warning.W301(name) # Argumentos para N2LoadCase envelopgroup = N2LoadCase # Genera una instancia de un N2LoadCase de NaxToModel cs_lc = N2LoadCase(self.__vzmodel, name, formula) # Método de N2LoadCase que pone todoo en su sitio cs_lc.RecalculateLCInfo2Formule() # Este metodo de N2ArithmeticSolver en NaxToModel comprueba que la formula introducida es correcta. # Devuelve False si está mal y True si está bien. err = N2ArithmeticSolver.CheckExpression(cs_lc.Formula, self.__vzmodel, N2ArithmeticSolver.ExpressionType.LOAD_CASE) if int(err) == 0: pass else: N2PLog.Error.E228(formula, str(N2Enums.GetDescription(err).upper())) # error return "ERROR" # Se transforma el caso de carga de NaxToModel en un N2PLoadCase de python py_lc = N2PLoadCase(cs_lc.ID, cs_lc.IDOriginal, cs_lc.Increments, cs_lc.IncrementsNumberList, cs_lc.TypeLoadCase, cs_lc.Name, cs_lc.Results, cs_lc.SolutionType, cs_lc.Solver, self, cs_lc) # Se añade el caso de carga de csharp a N2ModelContent de Vizzer self.__vzmodel.LoadCases.Add(cs_lc) # Se añade el case de carga de python a N2PModelContent self.__load_cases__.append(py_lc) # Se añade al diccionario si este ya existe if self.__LCs_by_ID__: self.__LCs_by_ID__[py_lc.ID] = py_lc return py_lc
_criteria_typing = Literal['ExtremeMax', 'ExtremeMin', 'Max', 'Min', 'Range'] _envelg_typing = Literal["ByContour", "ByLoadCaseID", "ByIncrement"]
[docs] def new_envelope_loadcase(self, name: str, formula: str, criteria:_criteria_typing = "ExtremeMax", envelgroup:_envelg_typing = "ByContour") -> N2PLoadCase: """Method of :class:`N2PModelContent` that generate a new :class:`N2PLoadCase` that is the envelope of the load cases and increments selected. The id is automatically generated. It will be negative, starting at -1. If the new load case use a derivated or envelope load case use LCD1 in the formula instead of LC-1: Args: name (`str`): Name of the envelope load case. It mustn't be repeated. formula (`str`): formula that define the loadcases and increments must have this structure: <LCXX:FRYY>. Example: `"<LC1:FR1>,<LCD2:FR1>,<LC2:FR10>"` criteria (`str`) :Criteria for selecting the results to build the new derived load case. Possible values: - 'ExtremeMax' - 'ExtremeMin' - 'Max' - 'Min' - 'Range' envelgroup (`str`): Criteria to select the data asociated to the elements/nodes: - 'ByContour': The data will be the actual value (XX stress for example) - 'ByLoadCaseID': The data will be the id of the original load case that is critical (LC 6363 for example) - 'ByIncrement': The data will be the id of the increment that is critical Returns: N2PLoadCase Examples: >>> env_lc1 = new_envelope_loadcase("env_lc1", formula="<LC1:FR1>,<LC2:FR1>") >>> env_lc2 = new_envelope_loadcase("env_lc2", formula="<LC1:FR1>,<LC2:FR8>,<LC-3:FR0>", criteria="Min", envelgroup="ByLoadCaseID") """ # Comprobamos que el nombre no se repite: i = 0 while name in [lc.Name for lc in self.LoadCases]: i += 1 name += f"-{i}" if i > 0: N2PLog.Warning.W301(name) # Comprobamos que la formula está bien: formula = formula.replace("-", "D").replace(" ", "") # Arguments for the constructor criteria_mapping = { 'Range': EnvelopCriteria.Range, 'ExtremeMin': EnvelopCriteria.ExtremeMin, 'Max': EnvelopCriteria.Max, 'Min': EnvelopCriteria.Min, 'ExtremeMax': EnvelopCriteria.ExtremeMax } envelopcriteria = criteria_mapping.get(criteria, EnvelopCriteria.ExtremeMax) envelgroup_maping = { 'ByContour': EnvelopGroup.ByContour, 'ByLoadCaseID': EnvelopGroup.ByLoadCaseID, 'ByIncrement': EnvelopGroup.ByIncrement } envelopgroup = envelgroup_maping.get(envelgroup, EnvelopGroup.ByContour) # Genera una instancia de un N2LoadCase de NaxToModel cs_lc = N2LoadCase(self.__vzmodel, name, formula, True, envelopgroup, False, envelopcriteria) # Método de N2LoadCase que pone todoo en su sitio cs_lc.RecalculateLCInfo2Formule() # Este metodo de N2ArithmeticSolver en NaxToModel comprueba que la formula introducida es correcta. # Devuelve False si está mal y True si está bien. err = N2ArithmeticSolver.CheckExpression(cs_lc.Formula, self.__vzmodel, N2ArithmeticSolver.ExpressionType.LOAD_CASE) if int(err) == 0: pass else: N2PLog.Error.E229(formula, str(N2ErrorWarnningHandling.GetDescription(err).upper())) # error return "ERROR" # Se transforma el caso de carga de NaxToModel en un N2PLoadCase de python py_lc = N2PLoadCase(cs_lc.ID, cs_lc.IDOriginal, cs_lc.Increments, cs_lc.IncrementsNumberList, cs_lc.TypeLoadCase, cs_lc.Name, cs_lc.Results, cs_lc.SolutionType, cs_lc.Solver, self, cs_lc) # Se añade el caso de carga de csharp a N2ModelContent de Vizzer self.__vzmodel.LoadCases.Add(cs_lc) # Se añade el case de carga de python a N2PModelContent self.__load_cases__.append(py_lc) # Se añade al diccionario si este ya existe if self.__LCs_by_ID__: self.__LCs_by_ID__[py_lc.ID] = py_lc return py_lc
# ------------------------------------------------------------------------------------------------------------------ #################################################################################################################### ##################################### Results by LCs & Incr ############################################### ####################################################################################################################
[docs] def new_report(self, lc_incr: str, allincr: bool, result: str, componentssections: str, ifenvelope: bool, selection: list[N2PNode, N2PElement], sortby: str, aveSections=-1, cornerData=False, aveNodes=-1, variation=100, realPolar=0, coordsys: int = -1000, v1: Union[tuple, np.ndarray] = (1, 0, 0), v2: Union[tuple, np.ndarray] = (0, 1, 0)) -> N2PReport: """Generate a report instance for extracting results from specified nodes or elements. This method creates a :class:`N2PReport` instance containing information about load cases, increments, components and sections for a specific result type. The report can calculate result arrays and export data to CSV format. Parameters ---------- lc_incr : ``str`` Formula specifying the loadcase-increment pairs. Each pair must follow the structure ``<LCX:FRY>`` where X is the loadcase ID and Y is the increment/frame ID. Example: ``"<LC1:FR1>,<LC1:FR2>,<LC2:FR1>,<LC2:FR2>"``. allincr : ``bool`` Flag to request all increments for the specified load cases. result : ``str`` Name of the result type to extract (e.g., "DISPLACEMENTS", "STRESSES", "STRAINS"). componentssections : ``str`` Formula specifying components and sections. Each component can have multiple sections following the structure ``<ComponentName:X#Y#Z#>`` where X, Y, Z are section names. Each section name must end with ``#``. Examples: * With sections: ``"<VONMISES:Z1#Z2#>,<MAX_SHEAR:Z1#Z2#>"`` * Without sections: ``"<FX:NONE#>,<FY:NONE#>"`` ifenvelope : ``bool`` Flag to extract only envelope results from the specified load cases. selection : ``list[N2PNode] | list[N2PElement]`` List of nodes or elements where results are requested. Must match the result type: * Displacements: Use list of ``N2PNode`` * Stresses: Use list of ``N2PElement`` sortby : ``str`` Sorting criterion for results: * "LC": Sort by load cases * "IDS": Sort by element/node IDs aveSections : ``int``, default -1 Optional. Operation to perform among sections: * -1: Maximum (default) * -2: Minimum * -3: Average * -4: Extreme * -6: Difference cornerData : ``bool``, default False Optional. Flag to control result location: * True: Results at element nodes * False: Results at element centroid (default) aveNodes : ``int``, default -1 Optional. Operation among nodes when ``cornerData=True``: * 0: None * -1: Maximum (default) * -2: Minimum * -3: Average * -5: Average with variation parameter * -6: Difference variation : ``int``, default 100 Optional. Averaging parameter between nodes (0-100): * 0: No averaging between nodes * 100: Total averaging between nodes (default) realPolar : ``int``, default 0 Optional. Data type for complex results: * 1: Real/Imaginary * 2: Magnitude/Phase coordsys : ``int``, default -1000 Optional. Coordinate system for result representation: * 0: Global coordinate system * -1: Material coordinate system * -10: User defined coordinate system * -20: Element/Node user defined coordinate system * -1000: Analysis coordinate system (default) * >0: Solver ID of predefined coordinate system v1 : ``tuple[float] | numpy.ndarray``, default (1, 0, 0) Optional. 3D vector defining the x-axis of the coordinate system. v2 : ``tuple[float] | numpy.ndarray``, default (0, 1, 0) Optional. 3D vector defining the xy-plane. The coordinate system axes are generated as: * x = v1 * z = v1 ^ v2 (cross product) * y = z ^ x (cross product) Returns ------- N2PReport Report instance containing the specified result configuration. The report provides methods to: * Calculate result arrays returning headers and results as ``numpy.ndarray`` (dtype=str) * Export data to CSV format .. note:: Only one result type is available per report. For example, you can extract all DISPLACEMENTS components for all load cases, but cannot include STRESSES in the same report. Examples -------- >>> report1 = model.new_report( ... "<LC1:FR1>,<LC2:FR1>", ... False, ... "DISPLACEMENTS", ... "<X:NONE#>,<Y:NONE#>", ... False, ... list_n2pnodes, ... "LC" ... ) >>> report2 = model.new_report( ... "<LC1:FR1>,<LC1:FR2>,<LC4:FR6>", ... False, ... "STRESSES", ... "<VON_MISES:Z1#Z2#>,<MAX_SHEAR:Z1#Z2#>", ... False, ... list_n2pelements, ... "IDS", ... aveSections=-4, ... coordsys=-1 ... ) """ # Here it is checkd if the components, the loadcases and the result exist and the formula is right try: lcs = [int(lc.split(":")[0][3:]) for lc in lc_incr.split(",")] components = [c.split(":")[0][1:] for c in componentssections.split(">,")] for lc in lcs: all_comps = list(self.get_load_case(lc).get_result(result).Components.keys()) + \ list(self.get_load_case(lc).get_result(result).DerivedComponents.keys()) if not all(c in all_comps for c in components): N2PLog.Error.E312(lc, result) return "ERROR" except: N2PLog.Error.E313() return "ERROR" return N2PReport(self.__vzmodel, lc_incr, allincr, result, componentssections, ifenvelope, selection, sortby, aveSections, cornerData, aveNodes, variation, realPolar, coordsys, v1, v2)
# ------------------------------------------------------------------------------------------------------------------ #################################################################################################################### ##################################### Results by LCs & Incr ############################################### #################################################################################################################### @overload def get_result_by_LCs_Incr( self, list_lc_incr: Union[list[tuple[N2PLoadCase, N2PIncrement]], list[tuple[int, int]]], result: str, component: list[str], # list of components. sections: list = None, aveSections: int = -1, cornerData: bool = False, aveNodes: int = -1, variation: int = 100, realPolar: int = 0, coordsys: int = -1000, v1: tuple = (1, 0, 0), v2: tuple = (0, 1, 0), filter_list: list[Union[N2PElement, N2PNode]] = None ) -> dict[tuple[int, int, str], np.ndarray]: ... @overload def get_result_by_LCs_Incr( self, list_lc_incr: Union[list[tuple[N2PLoadCase, N2PIncrement]], list[tuple[int, int]]], result: str, component: str, # Only one component sections: list = None, aveSections: int = -1, cornerData: bool = False, aveNodes: int = -1, variation: int = 100, realPolar: int = 0, coordsys: int = -1000, v1: tuple = (1, 0, 0), v2: tuple = (0, 1, 0), filter_list: list[Union[N2PElement, N2PNode]] = None ) -> dict[tuple[int, int], np.ndarray]: ...
[docs] def get_result_by_LCs_Incr( self, list_lc_incr, result, component, sections: list = None, aveSections: int = -1, cornerData: bool = False, aveNodes: int = -1, variation: int = 100, realPolar: int = 0, coordsys: int = -1000, v1: tuple = (1, 0, 0), v2: tuple = (0, 1, 0), filter_list = None ): """Get results of a component for multiple loadcases and increments. This method uses parallel subprocesses to speed up calculations and returns a dictionary with loadcase/increment tuples as keys and numpy arrays as values. Parameters ---------- list_lc_incr : ``list[tuple[int]]`` List of tuples containing (loadcase_id, increment_id) pairs for which to extract results. result : ``str`` Name of the result type to extract (e.g., "STRESSES", "STRAINS", "DISPLACEMENTS", "FORCES"). component : ``str | list[str]`` Component name(s) to extract. For stresses: "VON_MISES", "XX", "YY", etc. If list is provided, results for all components are returned. sections : ``list[str] | list[N2PSection]`` Optional. Sections on which to perform operations. If None (default), operations are performed on all sections. aveSections : ``int``, default -1 Optional. Operation to perform among sections: * -1: Maximum (default) * -2: Minimum * -3: Average * -4: Extreme * -6: Difference cornerData : ``bool``, default False Optional. Flag to control result location: * True: Results at element nodes * False: Results at element centroid (default) aveNodes : ``int``, default -1 Optional. Operation among nodes when ``cornerData=True``: * 0: None * -1: Maximum (default) * -2: Minimum * -3: Average * -6: Difference variation : ``int``, default 100 Optional. Averaging parameter between nodes (0-100): * 0: No averaging between nodes * 100: Total averaging between nodes (default) realPolar : ``int``, default 0 Optional. Data type for complex results: * 1: Real/Imaginary * 2: Magnitude/Phase coordsys : ``int``, default -1000 Optional. Coordinate system for result representation: * 0: Global coordinate system * -1: Material coordinate system * -10: User defined coordinate system * -20: Element/Node user defined coordinate system * -1000: Analysis coordinate system (default) * >0: Solver ID of predefined coordinate system .. note:: By default, results are in the analysis coordinate system. This is usually the element system, but in OP2 from Nastran (version >= 2023.3), it could be the material system if ``PARAM,OMID,YES`` is used. v1 : ``tuple[float]``, default (1, 0, 0) Optional. 3D vector defining the x-axis of the coordinate system. v2 : ``tuple[float]``, default (0, 1, 0) Optional. 3D vector defining the xy-plane. The coordinate system axes are generated as: * x = v1 * z = v1 ^ v2 (cross product) * y = z ^ x (cross product) filter_list : ``list[N2PElement] | list[N2PNode]`` Optional. Elements or nodes to filter results for. If None, results for all elements/nodes are returned. .. warning:: Filtering results can slow down extraction. If results are needed for multiple separate lists, extract all results first and filter afterward. Returns ------- dict Dictionary containing extracted results: * If **component** is ``str``: ``{(lc_id, incr_id): numpy.ndarray}`` * If **component** is ``list``: ``{(lc_id, incr_id, component_name): numpy.ndarray}`` Examples -------- **Basic usage with single component:** >>> vonmises = model.get_result_by_LCs_Incr( ... [(1, 2), (2, 2)], "STRESSES", "VON_MISES" ... ) >>> vonmises_1_2 = vonmises[(1, 2)] # Results for LC=1, Incr=2 **Using loadcase and increment objects:** >>> XX = model.get_result_by_LCs_Incr( ... [(loadcase1, increment2), (loadcase2, increment2)], ... "STRAINS", "XX" ... ) >>> XX_1_2 = XX.get((loadcase2.ID, increment2.ID)) **Filtering by sections:** >>> YY_Z1 = model.get_result_by_LCs_Incr( ... [(1, 2), (2, 2), (3, 1)], ... "STRESSES", "YY", ... sections=["Z1"] ... ) >>> YY_Z1_3_1 = YY_Z1.get((3, 1), None) **Using custom coordinate system:** >>> XY_transformed = model.get_result_by_LCs_Incr( ... [(2, 2), (3, 1)], ... "STRESSES", "XY", ... v1=(0, -2, 0), ... v2=(0, 0, -3) ... ) >>> XY_transformed_2_2 = XY_transformed.get((2, 2)) **Filtering by elements and nodes:** >>> elements_face = model.get_elements_by_face(model.get_elements(100)) >>> fx_inface = model.get_result_by_LCs_Incr( ... [(1, 2), (3, 1)], ... "FORCES", "FX", ... filter_list=elements_face ... ) >>> nodes_face = list({ ... node for element in elements_face for node in element.Nodes ... }) >>> x_inface = model.get_result_by_LCs_Incr( ... [(1, 2), (3, 1)], ... "DISPLACEMENTS", "X", ... filter_list=nodes_face ... ) **Multiple components:** >>> stresses = model.get_result_by_LCs_Incr( ... [(1, 2), (2, 2)], ... "STRESSES", ... ["VON_MISES", "MAXIMUM_PRINCIPAL"] ... ) >>> vonmises_1_2 = stresses[(1, 2, "VON_MISES")] >>> maxp_2_2 = stresses[(2, 2, "MAXIMUM_PRINCIPAL")] >>> strains = model.get_result_by_LCs_Incr( ... [(loadcase1, increment2), (loadcase2, increment2)], ... "STRAINS", ... ["XX", "XY", "YY"] ... ) >>> XX_1_2 = strains.get((loadcase2.ID, increment2.ID, "XX")) """ for lc, _ in list_lc_incr: if isinstance(lc, N2PLoadCase): aux_lc = lc if aux_lc._modelFather != self: N2PLog.Error.E240() return None elif isinstance(lc, int): aux_lc = self.get_load_case(lc) else: N2PLog.Error.E310() return None # I break the loop when I find the first load case that contains the result if result in aux_lc.Results: break if isinstance(component, list) and isinstance(component[0], str): n2_result = aux_lc.get_result(result) if not all(comp in n2_result.Components for comp in component) and not all(comp in n2_result.DerivedComponents for comp in component): N2PLog.Error.E319() return None n2pcomp = aux_lc.get_result(result).get_component(component[0]) elif isinstance(component, str): n2pcomp = aux_lc.get_result(result).get_component(component) component = None else: N2PLog.Error.E318() return None # Aqui se llama la funcion de verdad que es un metodo de N2PComponent. Se llama a ella porque hace uso de otras # funciones ya definidas (n2paraminputresult) en la clase y es mejor no repetir codigo. return n2pcomp._get_result_by_LCs_Incr(list_lc_incr, component, sections, aveSections, cornerData, aveNodes, variation, realPolar, coordsys, v1, v2, filter_list)
#################################################################################################################### ##################################### User Coordinate Systems ############################################# ####################################################################################################################
[docs] def load_user_coord_sys_from_csv(self, path: str, where: Literal["NODES", "ELEMENTS"]) -> None: """Method that loads a coordinate system for each node/element as a user coordinate system from a CSV file. The user can define a different system for each element or node and ask for results in that system using the optional argument "coordsys=-20" The structure of the csv must have the node/element identification, and the coordinates of the vectors v1 and v2 that generates the system: x = v1; z = v1^v2; y = z^v1. The structure for elements and nodes must be: node_id_1,part_id_1,x11,y11,z11,x12,y12,z12 user_system_elements_example.csv: 1001,0,1,0,0,0,1,0\n 1002,0,0.3,0.2,0,0,1,0\n 1003,0,0,1,0,1,1,0\n 1004,0,2,0,1,0.4,1,3\n Warnings: If a CSV is loaded and the another is loaded, the previous information will be deleted. Args: path: path for a CSV. where: "NODES"|"ELEMENTS". The user coordinate systems are for the nodes or the elements. Examples: >>> N2PModelContent.load_user_coord_sys_from_csv(path="user_system_elements_example.csv", where="ELEMENTS") >>> N2PModelContent.load_user_coord_sys_from_csv("node_sys.csv", "NODES") """ if where.upper() == "ELEMENTS": self.__vzmodel.ReadCSVUserElementCoordinateSystems(path) elif where.upper() == "NODES": self.__vzmodel.ReadCSVUserNodeCoordinateSystems(path) else: msg = N2PLog.Error.E231() raise ValueError(msg)
# ------------------------------------------------------------------------------------------------------------------ # Sets User Coordinate Systems -------------------------------------------------------------------------------------
[docs] def set_user_coord_sys(self, array2d: Union[list, np.ndarray], where: Literal["NODES", "ELEMENTS"]) -> None: """Method that sets a coordinate system for each node/element as a user coordinate system using an array defined by the user. The user can define a different system for each element or node and ask for results in that system using the optional argument "coordsys=-20" The structure of the array must have the node/element identification, and the coordinates of the vectors v1 and v2 that generates the system: x = v1; z = v1^v2; y = z^v1. The structure for elements and nodes must be: [ [node_id_1,part_id_1,x11,y11,z11,x12,y12,z12], [node_id_2,part_id_2,x21,y21,z21,x22,y22,z22], ... ] user_system_elements_example.csv: [ [1001,0,1,0,0,0,1,0], [1002,0,0.3,0.2,0,0,1,0], [1003,0,0,1,0,1,1,0], [1004,0,2,0,1,0.4,1,3] ] Warnings: If an array is passed to this method and the another is passed, the previous information will be deleted. Args: array2d: list|np.ndarray. The array can be a list of list or a two dimensional array of numpy with the type float where: "NODES"|"ELEMENTS". The user coordinate systems are for the nodes or the elements. Examples: >>> array_ele = [ [1001,0,1,0,0,0,1,0], [1002,0,0.3,0.2,0,0,1,0], [1003,0,0,1,0,1,1,0], >>> ... [1004,0,2,0,1,0.4,1,3] ] >>> array_node = np.array([ [2001,0,1,0,0,0,1,0], [2002,0,0.3,0.2,0,0,1,0], [2003,0,0,1,0,1,1,0], >>> ... [2004,0,2,0,1,0.4,1,3] ], dtype=float) >>> >>> N2PModelContent.set_user_coord_sys(array2d=array_ele, where="ELEMENTS") >>> N2PModelContent.set_user_coord_sys(array_node, "NODES") """ if isinstance(array2d, list): array2d = np.array(array2d) rows = array2d.shape[0] cols = array2d.shape[1] cs_array = System.Array.CreateInstance(System.Object, rows, cols) for i in range(rows): for j in range(cols): cs_array[i, j] = array2d[i, j] if where.upper() == "ELEMENTS": self.__vzmodel.SetElementUserCoordinateSystems(cs_array) elif where.upper() == "NODES": self.__vzmodel.SetNodeUserCoordinateSystems(cs_array) else: msg = N2PLog.Error.E231() raise ValueError(msg)
# ------------------------------------------------------------------------------------------------------------------ #################################################################################################################### ####################################### ElementsCoordSys ################################################## #################################################################################################################### def _elements_coord_sys(self): if not self.__elements_coord_sys: self.__elements_coord_sys = self.__vzmodel.ElementCoordSystem return self.__elements_coord_sys # ------------------------------------------------------------------------------------------------------------------
[docs] def new_coordinate_system(self, name: str, origin: tuple, v1: tuple, v2: tuple, type_coord: Literal["RECTANGULAR", "CILINDRICAL", "SPHERICAL"] = "RECTANGULAR") -> N2PCoord: """Returns a :class:`N2PCoord` and add it to the coordinate system list. Note: The ID of the coordinate system will be set automatically to the last ID + 1 . Args: name: str Name of the coordinate system. origin: tuple tuple with the origin coordinates. v1: tuple tuple with coordinates of the x vector. v2: tuple tuple with coordinates of the xy plane vector. type_coord: str (optional) The posible types are "RECTANGULAR", "CILINDRICAL" or "SPHERICAL". Default is "RECTANGULAR". Example: >>> mycoord = new_coordinate_system(name="MyCoordinate", origin=(0,0,0), v1=(0.5,0.5,0), v2=(0,1,0)) >>> new_coordinate_system("MatCoord2", (1,0,-1), (0,0.5,0), (0,0,1), "CILINDRICAL") >>> coord_mat = model.get_coords()[-1] """ if type_coord == "RECTANGULAR": n2type = N2Coord.TypeCoordSystem.RECTANGULAR elif type_coord == "CILINDRICAL": n2type = N2Coord.TypeCoordSystem.CILINDRICAL elif type_coord == "SPHERICAL": n2type = N2Coord.TypeCoordSystem.SPHERICAL else: raise Exception("Incorrect string for type_coord. It must be 'RECTANGULAR', 'CILINDRICAL' or 'SPHERICAL'") org = np.array(origin, dtype=np.double) x = np.array(v1, dtype=np.double) aux = np.array(v2, dtype=np.double) z = np.cross(x, aux) y = np.cross(z, x) n2coord = N2CoordUserDefined(self.__vzmodel, 1, n2type, org, x/np.linalg.norm(x), y/np.linalg.norm(y), z/np.linalg.norm(z), name) pedigree = N2ModelContent.TranslateIdsSolverToPedigree(n2coord.ID, 0) self.__vzmodel.userCoordList.Add(pedigree, n2coord) n2pcoord = N2PCoord(n2coord, self) self.__coord_dict[(n2coord.ID, 0)] = n2pcoord return n2pcoord
# endregion ######################################################################################################################## # region FUNCTION ######################################################################################################################## # Metodo para cargar un objeto N2PModelContent desde un archivo de resultados ------------------------------------------
[docs] def initialize(path: str, parallelprocessing: bool = False) -> N2PModelContent: """ Deprecated function. This funtion has been substituted by load_model() """ N2PLog.Warning.W205() return load_model(path, parallelprocessing, "Binary")
# ---------------------------------------------------------------------------------------------------------------------- # Metodo para cargar un objeto N2PModelContent desde un archivo de resultados o desde un archivo de texto --------------
[docs] def load_model(path: str, parallelprocessing: bool = True, file_type: Literal["InputFileNastran", "InputFileAbaqus", "Binary"] = None, dict_gen: dict = None, filter: Literal['ELEMENTS', 'PROPERTIES', 'PARTS', 'NODES'] = None, loadconnectors = True) -> N2PModelContent: """ Read an output result file in binary format from Nastran, Abaqus, Optistruct or Ansys and transform it into a N2PModelContent Object. It also can read models from input files in Nastran format. Supports .op2, .xdb, .odb, .h5, .h3d and .rst file extensions read from a local filesystem or URL. Supports Nastran Input Files (typically .bdf extension) and Abaqus Input File (typically .inp extension) Args: path: str. Mandatory. parallelprocessing: bool. Optional. If true, the low libraries open the result files in several processes. It is slightly faster. file_type: str. Optional. It specifies if it is Nastran input file ("InputFileNastran"), Abaqus input file ("InputFileAbaqus") or binary result file ("Binary"). filter: str. Optional. It specifies what to load in dict_gen (elements, properties, parts or nodes) dict_gen: dict. Optional. Dictionary that represents what elements/properties/parts/nodes to load. For elements and nodes, the dictionary is in the form {Part ID (str): [Elements ID/Nodes ID]}. For parts or properties, the dictionary could simply be in the form {Part ID (str) / Property ID (str): []}. Returns: N2PModelContent: Instance with all the model data. Examples: >>> model1 = load_model(r"C:\\MODELS\\FEM\\model1.dat") >>> # file_type is not needed. It only helps the program and saves a checking >>> model2 = load_model(r"C:\\MODELS\\FEM\\model2.dat", file_type="InputFileNastran") >>> # parallelprocessing is only aviable for Binary files. It is helpful in some big files. >>> model3 = load_model(r"C:\\MODELS\\RESULTS\\model1.op2", parallelprocessing=True) >>> model4 = load_model(r"C:\\MODELS\\RESULTS\\model2.xdb", file_type="Binary") >>> model5 = load_model(r"C:\\MODELS\\RESULTS\\model5.h3d") >>> model6 = load_model(r"C:\\MODELS\\RESULTS\\model6.odb", True, "Binary") """ if not os.path.exists(path): msg = N2PLog.Critical.C108(path) raise FileNotFoundError(msg) if path.split(".")[-1] in BINARY_EXTENSIONS and file_type == "InputFileNastran": N2PLog.Error.E111() if dict_gen and not filter: N2PLog.Error.E115() return N2PModelContent(path, parallelprocessing, file_type, __transform_dict(dict_gen), filter, loadconnectors=loadconnectors)
# ---------------------------------------------------------------------------------------------------------------------- # Metodo...
[docs] def OpenFiletoN2P(n2v_instance: N2ModelContent) -> N2PModelContent: """ Read a :class:`N2ModelContent` object and transform it into a :class:`N2PModelContent` object. Args: n2v_instance: N2ModelContent. Mandatory. """ aux = n2v_instance.CreateNodestoN2P() if aux == -1: return -1 else: n2v_instance.CreateElementstoN2P() return N2PModelContent(n2v_instance.FilePath, parallelprocessing = True, n2v=n2v_instance)
def _is_binary(path) -> str: """Function that returns if the file is a binary or text file""" count = 0 with open(path, 'rb') as f: for block in f: if b'\x00' in block: return "Binary" elif any(byte < 9 or (13 < byte < 32) for byte in block): return "Binary" if count > 100: break count += 1 return None def _check_file_extension(path: str) -> str: """Function that checks the file extension and returns the type of file""" extension = path.split(".")[-1].lower() if extension == "dat" or extension == "bdf": return "InputFileNastran" elif extension == "fem": return "InputFileOptistruct" elif extension == "inp": return "InputFileAbaqus" else: N2PLog.Error.E114(path) return None def __transform_dict(dict_gen: dict = None): """ Function that transforms a Python dictionary in the form dict[str, list[int]] to a C# dictionary in the form dict[Str, Array[Int32]]. If the input is None, then None is returned """ if dict_gen is not None: newDict = System.Collections.Generic.Dictionary[str, System.Array[int]]() for i, j in dict_gen.items(): newDict[i] = System.Array[int](j) return newDict else: return None # endregion