Source code for NaxToPy.Core.Classes.N2PComponent

import gc

from NaxToPy.Core.Classes.N2PSection import N2PSection
from NaxToPy.Core.Errors.N2PLog import N2PLog
from NaxToModel import N2ParamInputResults
from NaxToModel import N2TensorTransformation
from array import array
import numpy as np
from NaxToPy.Core._AuxFunc._NetToPython import _numpytonet
from typing import Union


# Clase Component de Python
# -----------------------------------------------------------------------
[docs] class N2PComponent: '''Class which contains the information associated to a component of a N2PResult instance. Attributes: Name: str. Sections: list[N2PSection]. IsTransformable: bool. ''' __slots__ = ( "_name", "_sections", "_result_father", ) # Constructor de N2PComponent # ----------------------------------------------------------------- def __init__(self, name, sections, result_father): """ Python Component Constructor Args: name: str -> name of the component sections: list[N2Sections] -> list with instances of N2Sections result_father: N2PResult """ self._name = name self._sections = [N2PSection(sections[i].Name, sections[i].Number) for i in range(0, len(sections))] self._result_father = result_father # --------------------------------------------------------------------------------------------- # Metodo para Obtener el Nombre de la Componente # ---------------------------------------------- @property def Name(self) -> str: return(str(self._name)) # --------------------------------------------------------------------------------------------- # Metodo para Obtener una lista con las secciones de la componente ---------------------------- @property def Sections(self) -> list[N2PSection, ...]: """ Returns a list of N2PSection objects with all the sections contained in the component """ return(self._sections) # --------------------------------------------------------------------------------------------- @property def IsTransformable(self) -> bool: """Property that keeps if the component of a result is transformable in other coordinate system. """ # No se guarda como atributo privado durante la instanciacion de un objeto porque algunas variables # aun no se han construido. Por eso el metodo de la propiedad es la que busca los argumentos y llama # al metodo dentro de Vizzer classes. solver = self._result_father._loadCase_father._N2PLoadCase__solver result = self._result_father.Name component = self.Name return bool(N2TensorTransformation.IsResultSupported(solver, result, component)) # Metodo para llamar a la funcion de NaxToModel que devueleve el array de resultados-----------------------------
[docs] def get_result_array(self, sections=None, aveSections=-1, cornerData=False, aveNodes=-1, variation=100, realPolar=0, coordsys: int = -1000, v1: tuple = (1, 0, 0), v2: tuple = (0, 1, 0)) -> tuple[list, str]: """Deprecated method. Please, use get_result_list instead for asking the results as a list. If a numpy array is preferred, use get_result_ndarray""" return self.get_result_list(sections, aveSections, cornerData, aveNodes, variation, realPolar, coordsys, v1, v2)
# ------------------------------------------------------------------------------------------------------------------ # Metodo para llamar a la funcion de NaxToModel que devueleve el array de resultados como lista------------------
[docs] def get_result_list(self, sections=None, 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)) -> tuple[list, str]: """ Returns a tuple with the list of floats with the result array asked and a str with the position of the results Args: sections: list[str] | list[N2PSection] -> Optional. Sections which operations are done. None (Default) = All Sections aveSections: int -> Optional. Operation Among Sections. -1 : Maximum (Default), -2 : Minimum, -3 : Average, -4 : Extreme, -6 : Difference. cornerData: bool -> Optional. Flag to get results in element nodal. True : Results in Element-Nodal, False : Results in centroid (Default). aveNodes: int -> Optional. Operation among nodes when cornerData is selected. 0 : None, -1 : Maximum (Default), -2 : Minimum, -3 : Average, -5 : Average with variation parameter, -6 : Difference. variation: int -> Optional. Integer between 0 & 100 to select. 0 : No average between nodes, 100 : Total average between nodes (Default). realPolar: int -> Optional. Data type when complex result. 1 : Real / Imaginary, 2 : Magnitude / Phase. coordsys: int -> Optional. Coordinate System where the result_array will be represented. By default, the output is in element system: 0 : Global, -1 : Material Coordinate System, -10 : User defined, -20 : Element/Node User Defined, >0 : Solver ID of the Predefined Coordinate System. v1: tuple -> Optional. v2: tuple -> Optional. Directions vectors that generate the coordinate system axis: x=v1, z=v1^v2, y=z^x. Returns: results: tuple(list[float], str) """ # Aquí se prepara la llamada a la funcion de NaxToModel.N2PModelContent.getArraytoPlot(). Esta usa como # argumento un objeto de la clase N2ParamImputResults. Por se instancia y se modifican sus propiedades antes # de hacer la llamada a la funcion. # Aqui se mete el sistema de coordenadas en el que queremos que nos saque la informacion. # Para ello primero comprobamos que es viable hacer la tansformacion (puede que aun no este implementada o que # la componente simplemente no la admita al ser un escalar a secas) #resultsParameters.coordSys = -10 #orientationcoordinatesystem = algo resultsParameters = self._create_n2paraminputresult(sections, aveSections, cornerData, aveNodes, variation, realPolar, coordsys, v1, v2) results = list( self._result_father._loadCase_father._modelFather._N2PModelContent__vzmodel.CalculateArrayLastResult( resultsParameters, 0)) if results[1] == 1: return(list(results[0]),'NODES') elif results[1] == 2: return(list(results[0]), 'ELEMENTS') elif results[1] == 3: return(list(results[0]), 'ELEMENT NODAL') else: N2PLog.Error.E206(self.Name) return(-1, 'NO RESULTS')
# ------------------------------------------------------------------------------------------------------------------ # Metodo para llamar a la funcion de NaxToModel que devuelve el array de resultados como array de numpy----------
[docs] def get_result_ndarray(self, sections=None, 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)) -> tuple[np.array, str]: """ Returns a tuple with the numpy array of floats with the result array asked and a str with the position of the results Args: sections: list[str] | list[N2PSection] -> Optional. Sections which operations are done. None (Default) = All Sections aveSections: int -> Optional. Operation Among Sections. -1 : Maximum (Default), -2 : Minimum, -3 : Average, -4 : Extreme, -6 : Difference. cornerData: bool -> Optional. Flag to get results in element nodal. True : Results in Element-Nodal, False : Results in centroid (Default). aveNodes: int -> Optional. Operation among nodes when cornerData is selected. 0 : None, -1 : Maximum (Default), -2 : Minimum, -3 : Average, -5 : Average with variation parameter, -6 : Difference. variation: int -> Optional. Integer between 0 & 100 to select. 0 : No average between nodes, 100 : Total average between nodes (Default). realPolar: int -> Optional. Data type when complex result. 1 : Real / Imaginary, 2 : Magnitude / Phase. coordsys: int -> Optional. Coordinate System where the result_array will be represented. By default, the output is in element system: 0 : Global, -1 : Material Coordinate System, -10 : User defined, -20 : Element/Node User Defined, >0 : Solver ID of the Predefined Coordinate System. v1: tuple -> Optional. v2: tuple -> Optional. Directions vectors that generate the coordinate system axis: x=v1, z=v1^v2, y=z^x. Returns: results: tuple(np.array, str) Examples >>> # N2PComponent is a N2PLoadCase.get_results("DISPLACEMENTS").get_component("X") >>> displ_X_array, where = N2PComponent.get_result_ndarray(aveSections=-3, coordsys=-1) >>> # N2PComponent is a N2PLoadCase.get_results("STRESSES").get_component("XX") >>> stress_array, where = N2PComponent.get_result_ndarray(v1=(-1, 0, 0), v2=(0, -1, 0)) """ resultsParameters = self._create_n2paraminputresult(sections, aveSections, cornerData, aveNodes, variation, realPolar, coordsys, v1, v2) results = \ self._result_father._loadCase_father._modelFather._N2PModelContent__vzmodel.CalculateArrayLastResult( resultsParameters, 0) if results[1] == 1: return (np.array(results[0], np.float64),'NODES') elif results[1] == 2: return (np.array(results[0], np.float64), 'ELEMENTS') elif results[1] == 3: return (np.array(results[0], np.float64), 'ELEMENT NODAL') else: N2PLog.Error.E206(self.Name) return (-1, 'NO RESULTS')
# ------------------------------------------------------------------------------------------------------------------ # # Metodo que devulve un dataframe con los resulados tras llamar a la funcion de NaxToModel-- # def get_result_dataframe(self, sections=None, aveSections=-1, cornerData=False, aveNodes=-1, # variation=100, realPolar=0, coordsys: int = -1000, # v1: tuple = (1,0,0), v2: tuple = (0,1,0)) -> DataFrame: # """Returns a DataFrame of pandas with the results array of a component as data for the active increment. # # Args: # # sections: list[str] | list[N2PSection] -> Sections which operations are done. # None (Default) = All Sections # # aveSections: int -> Operation Among Sections. # -1 : Maximum (Default), # -2 : Minimum, # -3 : Average, # -4 : Extreme, # -6 : Difference. # # cornerData: bool -> flag to get results in element nodal. # True : Results in Element-Nodal, # False : Results in centroid (Default). # # aveNodes: int -> Operation among nodes when cornerData is selected. # 0 : None, # -1 : Maximum (Default), # -2 : Minimum, # -3 : Average, # -5 : Average with variation parameter, # -6 : Difference. # # variation: int -> Integer between 0 & 100 to select. # 0 : No average between nodes, # 100 : Total average between nodes (Default). # # realPolar: int -> data type when complex result. # 1 : Real / Imaginary, # 2 : Magnitude / Phase. # # coordsys: int -> Coordinate System where the result_array will be represented. # 0 : Global, # -1 : Material Coordinate System, # -10 : User defined, # -20 : Element/Node User Defined, # >0 : Solver ID of the Predefined Coordinate System. # # v1: tuple # # v2: tuple -> Directions vectors that generate the coordinate system axis: # x=v1, # z=v1^v2, # y=z^x. # # Returns: # DataFrame: # data: float64: results array # index: int | tuple: id of the element/node or tuple (id, part) # it could be a tuple (nodo_id, element_id, part) for cornerData # column: str: component.Name # ---------- # """ # ids = list() # indexname = list() # # resultlist = self.get_result_array(sections, aveSections, cornerData, aveNodes, variation, realPolar, coordsys, # v1, v2) # # if resultlist[0] == -1: # N2PLog.Error.E208() # return DataFrame() # if len(self.__result_father__._loadCase_father._modelFather._N2PModelContent__StrPartToID) == 1: # noparts = True # else: # noparts = False # # if resultlist[1] == "NODES": # nodos = self.__result_father__._loadCase_father._modelFather.get_nodes() # if noparts: # ids = [nodo.ID for nodo in nodos] # indexname = ["Grid"] # else: # ids = [(nodo.PartID, nodo.ID) for nodo in nodos] # indexname = ["Part", "Grid"] # del nodos # elif resultlist[1] == "ELEMENTS": # elements = self.__result_father__._loadCase_father._modelFather.get_elements() # connectors = self.__result_father__._loadCase_father._modelFather.get_connectors() # if noparts: # # La lista de connectores puede contener N2PConnector o lista de N2PConector. Esto es porque # # los MPC puden tener ids iguales en Nastran. Por eso en la comprension de la lista de conectores # # itero sobre la lista de conetores con igual id dentro de la lista de conectores general y si no # # resulta ser una lista lo transformo en lista e itero. # ids = [element.ID for element in elements] + \ # [c.ID for con in connectors for c in (con if isinstance(con, list) else [con])] # indexname = ["Element"] # else: # ids = [(element.PartID, element.ID) for element in elements] +\ # [(c.PartID, c.ID) for con in connectors for c in (con if isinstance(con, list) else [con])] # indexname = ["Part", "Element"] # del elements, connectors # elif resultlist[1] == "ELEMENT NODAL": # ids = list(self.__result_father__._loadCase_father._modelFather._elementnodal().values()) # indexname = ["Part", "Grid", "Element"] # # # Se revisa que la lista de malla y la lista de resultados sean del mismo tamaño # if not len(ids) == len(resultlist[0]): # N2PLog.Warning.W202() # length = min(len(ids), len(resultlist[0])) # ids = ids[:length] # resultlist = (resultlist[0][:length], resultlist[1]) # # data = {self.Name: resultlist[0]} # if isinstance(ids[0], tuple): # index = MultiIndex.from_tuples(ids, names=indexname) # else: # index = ids # # df = DataFrame(data=data, index=index, columns=[self.Name]) # # if not isinstance(ids[0], tuple): # df = df.rename_axis(indexname, axis='index') # # return df # # ------------------------------------------------------------------------------------------------------------------ # Método que devuelve un diccionario con la tupla (LC, Incr) como clave y una lista de resultados como valor ------- def _get_result_by_LCs_Incr(self, list_lc_incr: list[tuple["N2PLoadCase", "N2PIncrement"],...], sections=None, aveSections=-1, cornerData=False, aveNodes=-1, variation=100, realPolar=0, coordsys: int = -1000, v1: tuple = (1,0,0), v2: tuple = (0,1,0)) -> dict[tuple: list[float,...]]: formula = self._create_formula(list_lc_incr) if not formula: return None resultsParameters = self._create_n2paraminputresult(sections, aveSections, cornerData, aveNodes, variation, realPolar, coordsys, v1, v2) cs_dict = self._result_father._loadCase_father._modelFather._N2PModelContent__vzmodel.CalculateArrayLastResult( formula, resultsParameters, 0) py_dict = {(int(key.split(":")[0].replace("DoF_LCD", "-").replace("DoF_LC", "")), int(key.split(":")[1][2:])): np.array(value, np.float64) for key, value in dict(cs_dict[0]).items()} del cs_dict gc.collect() return py_dict # ------------------------------------------------------------------------------------------------------------------ # Metodo que crea la formula que necesita la funcion de NaxToModel ---------------------------------------------- def _create_formula(self, list_lc_incr: list[tuple["N2PLoadCase", "N2PIncrement"],...]) -> str: if isinstance(list_lc_incr, tuple): list_lc_incr = [list_lc_incr] N2PLoadCase = type(self._result_father._loadCase_father) N2PIncrement = type(self._result_father._loadCase_father.Increments[0]) if isinstance(list_lc_incr[0][0], N2PLoadCase) and isinstance(list_lc_incr[0][1], N2PIncrement): return ",".join(f"<LC{lc[0].ID}:FR{lc[1].ID}>" for lc in list_lc_incr) elif isinstance(list_lc_incr[0][0], int) and isinstance(list_lc_incr[0][1], int): return ",".join(f"<LC{lc[0]}:FR{lc[1]}>" for lc in list_lc_incr) else: N2PLog.Error.E310() return None # ------------------------------------------------------------------------------------------------------------------ # Metodo que crea el N2ParamInputResult ---------------------------------------------------------------------------- def _create_n2paraminputresult(self, sections, aveSections, cornerData, aveNodes, variation, realPolar, coordsys, v1, v2) -> N2ParamInputResults: # Aqui comprobamos que si queremos hacer una transformacion y si esta es posible: if self.IsTransformable and (coordsys != -1000 or (v1 != (1, 0, 0) or v2 != (0, 1, 0))): # si es un sistema definido por el usuarios es -10 y se busca el GlobalID. Si es positivo es de solver. # si es material es -1. if coordsys > 0: resultsParameters = N2ParamInputResults.structResults(0) # , x_array, y_array, z_array) resultsParameters.coordSys = coordsys elif coordsys < 0 and (v1 != (1, 0, 0) or v2 != (0, 1, 0)): # Esto es una array de doubles. Si se quiere hacer una transformacion tanto en sistemas ya definidos en # el solver como por el usuario hay que pasarlo como argumento de N2ParamInputResult new_sys = _get_axis(v1, v2) new_sys = _numpytonet(new_sys) resultsParameters = N2ParamInputResults.structResults(0) resultsParameters.coordSys = -10 resultsParameters.orientationcoordinatesystem = new_sys elif coordsys == -1: resultsParameters = N2ParamInputResults.structResults(0) resultsParameters.coordSys = coordsys elif coordsys == 0: resultsParameters = N2ParamInputResults.structResults(0) resultsParameters.coordSys = coordsys elif coordsys == -20: resultsParameters = N2ParamInputResults.structResults(0) resultsParameters.coordSys = coordsys else: N2PLog.Error.E220() return (-1, 'NO RESULTS') elif not self.IsTransformable and (coordsys != -1000 or (v1 != (1, 0, 0) or v2 != (0, 1, 0))): N2PLog.Error.E219() return (-1, 'NO RESULTS') else: # Initializer if it is no transformable resultsParameters = N2ParamInputResults.structResults(0) # Component Selected resultsParameters.Component = str(self.Name) # Entity Selection (So Far 'ALL') resultsParameters.IdsEntities = '' # Active Increment resultsParameters.IncrementID = str(self._result_father._loadCase_father._activeIncrement) # Load Case Selected resultsParameters.LoadCaseID = str(self._result_father._loadCase_father.ID) # Parts Selected (So Far 'ALL') resultsParameters.Part = 'all' # Results Selected resultsParameters.Result = str(self._result_father.Name) resultsParameters.Sets = '' resultsParameters.TypeElement = '' # Sections Selected (By Default 'ALL') & Section Average sectionParam = '' if sections is None: for section in self._sections: sectionParam += str(section.Name) + '#' else: if not isinstance(sections, list): N2PLog.Error.E218() for section in sections: if type(section) is N2PSection: sectionParam += str(section.Name) + '#' else: sectionParam += str(section) + '#' resultsParameters.sectPoint = sectionParam resultsParameters.aveSection = aveSections # CornerData Selection, Average & Variation resultsParameters.cornerData = cornerData __aveIntraSupported__ = (0, -1, -2, -3, -6) if (aveNodes not in __aveIntraSupported__): return (-1) else: resultsParameters.aveIntra = aveNodes variation = variation if variation > 100: variation = 100 elif variation < 0: variation = 0 resultsParameters.variation = variation # Real/Imaginary or Magnitude/Phase __complexSupported__ = (0, 1, 2) if (realPolar not in __complexSupported__): return (-1) else: resultsParameters.real_cartesian_polar = realPolar return resultsParameters # ------------------------------------------------------------------------------------------------------------------ # Special Method for Object Representation ------------------------------------------------------------------------- def __repr__(self): loadcasestr = f"N2PComponent(\'{self.Name}\')" return loadcasestr
# ------------------------------------------------------------------------------------------------------------------ # ---------------------------------------------------------------------------------------------------------------------- def _get_axis(v1: Union[tuple, np.ndarray], v2: Union[tuple, np.ndarray]) -> np.ndarray: """Function that returns the components of the three vectors of a rectangular coordinate system Args: v1: tuple|ndarray v2: tuple|ndarray Returns: ndarray """ v1 = np.array(v1) v1 = v1/np.linalg.norm(v1) v2 = np.array(v2) v2 = v2/np.linalg.norm(v2) z = np.cross(v1, v2) y = np.cross(z, v1) return np.vstack((v1, y, z))