Source code for NaxToPy.Modules.Fasteners.Joints.N2PJoint

from NaxToPy.Core.N2PModelContent import N2PModelContent 
from NaxToPy.Core.Classes.N2PElement import N2PElement 
from NaxToPy.Core.Classes.N2PNode import N2PNode
from NaxToPy.Core.Classes.N2PNastranInputData import * 
from NaxToPy.Core.Classes.N2PAbaqusInputData import * 
from NaxToPy.Modules.Fasteners.Joints.N2PBolt import N2PBolt 
from NaxToPy.Modules.Fasteners.Joints.N2PPlate import N2PPlate 
from NaxToPy.Modules.Fasteners.Joints.N2PAttachment import N2PAttachment 
from NaxToPy import N2PLog
from NaxToPy.Modules.Fasteners._N2PFastenerAnalysis.Core.Functions.N2PRotation import rotate_tensor2D
from NaxToPy.Modules.Fasteners._N2PFastenerAnalysis.Core.Functions.N2PRotation import angle_between_2_systems
from NaxToPy.Modules.Fasteners._N2PFastenerAnalysis.Core.Functions.N2PRotation import project_vector
from NaxToPy.Modules.Fasteners._N2PFastenerAnalysis.Core.Functions.N2PRotation import transformation_for_interpolation
from NaxToPy.Modules.Fasteners._N2PFastenerAnalysis.Core.Functions.N2PInterpolation import interpolation
from typing import Literal 
import numpy as np 
import csv

[docs] class N2PJoint: """ Class that represents a single joint, that is, a N2PBolt and a series of N2PPlate objects. Attributes: diameter: float = None -> joint's diameter. bolt: N2PBolt -> N2PBolt associated. plates: list[N2PPlate] -> list of unique N2PPlates associated to the N2PJoint. attachment: N2PAttachment -> joint's attachment. pitch: float -> joint's pitch. """ # N2PJoint constructor --------------------------------------------------------------------------------------------- def __init__(self, info, input_data_father): """ In this constructor, the N2PBolt associated to the N2PJoint is created. Also, all N2PPlates associated to the N2PJoint are created and then some of them are removed if two (or more) of them share the same solver ID (that is, the same N2PElements are associated to both N2PPlates). """ self.__info__ = info self.__input_data_father__ = input_data_father self._diameter: float = None self._bolt: N2PBolt = N2PBolt(self.__info__.Bolt, self.__input_data_father__) self._bolt._joint = self allPlates = list(N2PPlate(self.__info__.Plates[i], self.__input_data_father__) for i in range(len(self.__info__.Plates))) platesID = [] listPlates = [] for i in allPlates: if i.SolverID not in platesID: platesID.append(i.SolverID) listPlates.append(i) self._plates: list[N2PPlate] = listPlates self._attachment: N2PAttachment = None self._pitch: float = None # ------------------------------------------------------------------------------------------------------------------ # Getters ---------------------------------------------------------------------------------------------------------- @property def Diameter(self) -> float: """ Property that returns the diameter attribute, that is, the joint's diameter. """ return self._diameter # ------------------------------------------------------------------------------------------------------------------ @property def Bolt(self) -> N2PBolt: """ Property that returns the bolt attribute, that is, the N2PBolt associated to the N2PJoint. """ return self._bolt # ------------------------------------------------------------------------------------------------------------------ @property def ID(self) -> int: """ Property that returns the joint's internal identificator. """ return self.Bolt.ID # ------------------------------------------------------------------------------------------------------------------ @property def TypeFastener(self) -> str: """ Property that returns the type of joint that is being used. """ return self.Bolt.Type # ------------------------------------------------------------------------------------------------------------------ @property def Plates(self) -> list[N2PPlate]: """ Property that returns the plates attribute, that is, the list of N2PPlates associated to the N2PJoint. """ return self._plates # ------------------------------------------------------------------------------------------------------------------ @property def PartID(self) -> int: """ Property that returns the part ID of the elements that make up the bolt. """ return self.Bolt.PartID # ------------------------------------------------------------------------------------------------------------------ @property def BoltElements(self) -> list[N2PElement]: """ Property that returns the list of N2PElements that make up the joint's bolt. """ return self.Bolt.Elements # ------------------------------------------------------------------------------------------------------------------ @property def BoltElementsID(self) -> list[int]: """ Property that returns the list of the IDs of the N2PElements that make up the joint's bolt. """ return self.Bolt.ElementsID # ------------------------------------------------------------------------------------------------------------------ @property def BoltElementsInternalID(self) -> list[int]: """ Property that returns the list of the internal IDs of the N2PElements that make up the joint's bolt. """ return self.Bolt.ElementsInternalID # ------------------------------------------------------------------------------------------------------------------ @property def BoltNodes(self) -> list[N2PNode]: """ Property that returns the list of N2PNodes that make up the joint's bolt. """ return self.Bolt.Nodes # ------------------------------------------------------------------------------------------------------------------ @property def PlateElements(self) -> list[N2PElement]: """ Property that returns the list of N2PElements that make up the joint's plates. """ return [j.Elements for j in self.Plates] # ------------------------------------------------------------------------------------------------------------------ @property def PlateElementsID(self) -> list[int]: """ Property that returns the list of the IDs of the N2PElements that make up the joint's plates. """ return [j.ElementsID for j in self.Plates] # ------------------------------------------------------------------------------------------------------------------ @property def PlateElementsInternalID(self) -> list[int]: """ Property that returns the internal ID of the N2PElements that make up the joint's plates. """ return [j.ElementsInternalID for j in self.Plates] # ------------------------------------------------------------------------------------------------------------------ @property def PlateNodes(self) -> list[N2PNode]: """ Property that returns the list of N2PNodes that make up the joint's plates. """ return [j.Nodes for j in self.Plates] # ------------------------------------------------------------------------------------------------------------------ @property def PlatePartID(self) -> list[str]: """ Property that returns the part ID of each element that makes up the plates. """ return [j.PartID for j in self.Plates] # ------------------------------------------------------------------------------------------------------------------ @property def Attachment(self) -> N2PAttachment: """ Property that returns the attachment attribute, that is, the joint's N2PAttachment. """ return self._attachment # ------------------------------------------------------------------------------------------------------------------ @property def Pitch(self) -> float: """ Property that returns the pitch attribute, that is, the joint's pitch. """ return self._pitch # ------------------------------------------------------------------------------------------------------------------ # Setters ---------------------------------------------------------------------------------------------------------- @Diameter.setter def Diameter(self, value: float) -> None: self._diameter = value # ------------------------------------------------------------------------------------------------------------------ # Method used to obtain the distance from an N2PJoint to its closest N2PPlate's edge -------------------------------
[docs] def get_distance(self, model: N2PModelContent): """ Method that calculates the distance from each N2PJoint to the edge of its corresponding N2PPlates. Args: model: N2PModelContent The following steps are followed: 1. The intersection points between every N2PPlate and its N2PJoint is obtained. 2. All the elements that are attached to the element where the intersection point is are retrieved using the function “get_elements_attached”. Right after this, “get_free_edges” obtains a list of segments from the attached elements which define the free edges of the selection. 3. Finally, the distance between the intersection point to each segments is obtained and compared to the rest in order to get the minimum one, which is of course the desired value. Calling example: >>> myJoint.get_distance(model1) """ # Only CQUAD4 and CTRIA3 elements are supported. supportedElements = ["CQUAD4", "CTRIA3"] domain = [i for i in model.get_elements() if i.TypeElement in supportedElements] boltNodes = [j for i in self.BoltNodes for j in i] boltNodes2 = [] for i in boltNodes: if i not in boltNodes2: boltNodes2.append(i) intersection = [] for i in boltNodes2: intersection.append(i.GlobalCoords) # Of course, one distance will be needed form every N2PPlate in the N2PJoint. for p in range(len(self.Plates)): if len(self.Plates[p].Elements) == 0: N2PLog.Warning.W512(p, self) continue plateElem = self.Plates[p].Elements[0] node1 = np.array([plateElem.Nodes[0].X, plateElem.Nodes[0].Y, plateElem.Nodes[0].Z]) node2 = np.array([plateElem.Nodes[1].X, plateElem.Nodes[1].Y, plateElem.Nodes[1].Z]) node3 = np.array([plateElem.Nodes[2].X, plateElem.Nodes[2].Y, plateElem.Nodes[2].Z]) normalPlane = np.cross(node3 - node1, node2 - node1) / np.linalg.norm(np.cross(node3 - node1, node2 - node1)) self._plates[p]._normal = normalPlane.tolist() self._plates[p]._intersection = list(intersection[p]) # The plate's free edges are obtained freeEdges = model.get_free_edges(model.get_elements_attached(cells = [plateElem], domain = domain)) savedDistance = float("inf") # Of course, the desired distance is the minimum distance to an edge, so all free edges must be searched. for i in freeEdges: A = np.array(i[1].GlobalCoords) B = np.array(i[2].GlobalCoords) length = np.linalg.norm(B - A) ts = np.dot(intersection[p] - A, B - A) / length ** 2 if ts <= 0: distance = np.linalg.norm(A - intersection[p]) # A is the closest point. elif ts >= 1: distance = np.linalg.norm(B - intersection[p]) # B is the closest point. else: distance = np.linalg.norm(A + ts * (B - A) - intersection[p]) # The closest point is elsewhere. if distance < savedDistance: savedDistance = distance self._plates[p]._distance = float(savedDistance)
# ------------------------------------------------------------------------------------------------------------------ # Method used to organise some forces ------------------------------------------------------------------------------ def _organise_forces(self, forces: list) -> list: """ Method which takes a list of forces defined in the following format: [[FX, FY, FZ], [FX, FY, FZ], ..., [FX, FY, FZ]] and organises it in order to be consistent with the plates order and to be comfortable to export. Args: forces: list -> list of forces to be transformed Returns: forcesOrganised: list Calling example: >>> forcesOrg = myJoint._organise_forces(forces) """ organisedForces = [] if len(forces) == 1: organisedForces = [[forces[0], [0, 0, 0]], [forces[0], [0, 0, 0]]] else: organisedForces.append([forces[0], [0, 0, 0]]) for i in range(1, len(forces)): organisedForces.append([forces[i - 1], forces[i]]) organisedForces.append([forces[len(forces) - 1], [0, 0, 0]]) return organisedForces # ------------------------------------------------------------------------------------------------------------------ # Method used to obtain the model's forces -------------------------------------------------------------------------
[docs] def get_forces(self, results: dict): """ Method that takes an N2PJoint element and obtains its 1D forces, as well as the 1D forces associated to each of its N2PPlates. Forces will be obtained as N2PPlate or N2PJoint attributes as dictionaries in the form: {Load Case ID: [FX, FY, FZ]} or {Load Case ID: F or Angle} depending on what is obtained. Args: results: dict -> results dictionary. The following attributes are obtained: - shear_force: dictionary in the form {Load Case ID: Bolt Element ID: F} which represents the 1D force in the bolt's element reference frame. It is a N2PBolt attribute. - axial_force: dictionary in the form {Load Case ID: Bolt Element ID: F} which represents the axial force in the 1st plate's material reference frame. It will be positive if the fastener is extender or 0 if it is compressed. It is a N2PBolt attribute. - element_local_system_force: dictionary in the form {Load Case ID: Bolt Element ID: [FX, FY, FZ]} which represents the 1D force in the 1st plate's material reference frame. It is a N2PBolt attribute. - plates_force: dictionary in the form {Load Case ID: [[FX, FY, FZ], [FX, FY, FZ]]} which represents the 1D forces that each the N2PElements associated to the N2PBolt associated to the N2PPlate experience. It is represented in a local reference frame, in which the x-axis is the same as the N2PPlate's material reference frame's x-axis, the z-axis is coincident with the axial direction of the bolt and the y-axis is obtained via the cross product. If there is only one fastener attached to the plate, the second list will be filled with zeros. It is a N2PPlate attribute. - altair_force: dictionary in the form {Load Case ID: [FX, FY, FZ]} which represents the 1D force experienced by the joint, as calculated by Altair. It takes into account if there are two joints attached to the plate and, if so, sums up their contributions. It is represented in the local reference frame and it is a N2PPlate attribute. - max_axial_force: dictionary in the form {Load Case ID: Bolt Element ID: F} which represents the maximum axial force of the whole joint. It is a N2PBolt attribute. - load_angle: dictionary in the form {Load Case ID: Bolt Element ID: Angle} which represents the joint's load angle in degrees. It is a N2PBolt attribute. The following steps are followed: 1. All reference frames are obtained: the 1st plate's material reference frame, the bolt's N2PElement reference frame and the local reference frame. 2. All forces are calculated according to their definition. If necessary, they are rotated so that they are expressed in the correct reference frame. 3. If there are two joints attached to the same plate, their contributions are added. It must be added that, in the original code, there was a distinction between top and bottom plates and, as such, the force that the top and bottom plates experienced was also calculated. As in the modern code this distinction no longer exists, this cannot be calculated. Calling example: >>> myForces = myJoint.get_forces(fasteners.Results) """ plateElement = self.Plates[0].Elements[0] materialSystem = plateElement.MaterialSystemArray xlocal = materialSystem[0:3] for i, j in results.items(): elementForcesMaterial = [] elementForcesLocal = [] self._bolt._element_local_system_force[i] = {} self._bolt._axial_force[i] = {} self._bolt._shear_force[i] = {} self._bolt._load_angle[i] = {} for k in self.BoltElements: id = k.InternalID system1D = k.ElemSystemArray force1D = [j.get("FZ1D")[id], j.get("FY1D")[id], j.get("FX1D")[id]] # Local reference frame is defined zlocal = system1D[0:3] ylocal = np.cross(zlocal, xlocal) localSystem = [xlocal[0], xlocal[1], xlocal[2], ylocal[0], ylocal[1], ylocal[2], zlocal[0], zlocal[1], zlocal[2]] # Calculations regarding the shear force shearForce = np.array([force1D[1], force1D[2]]) alpha = angle_between_2_systems(system1D, materialSystem, materialSystem[6:9]) R = np.array([[np.cos(alpha), np.sin(alpha)], [-np.sin(alpha), np.cos(alpha)]]) rotShearForce = np.matmul(R, shearForce) forcesToRotate = [force1D[0], force1D[1]] # Plate's reference frame is defined betaLocal = angle_between_2_systems(system1D, localSystem, localSystem[6:9]) R2Local = np.array([[np.cos(betaLocal), np.sin(betaLocal)], [-np.sin(betaLocal), np.cos(betaLocal)]]) F1DRotLocalXY = np.matmul(R2Local, forcesToRotate) F1DRotLocal = [F1DRotLocalXY[0], F1DRotLocalXY[1], force1D[2]] elementForcesLocal.append(F1DRotLocal) beta = angle_between_2_systems(system1D, materialSystem, materialSystem[6:9]) R2 = np.array([[np.cos(beta), np.sin(beta)], [-np.sin(beta), np.cos(beta)]]) F1DRotPrev = np.matmul(R2, forcesToRotate) F1DRot = [float(F1DRotPrev[0]), float(F1DRotPrev[1]), float(force1D[2])] elementForcesMaterial.append(F1DRot) # N2PBolt attributes are set self._bolt._element_local_system_force[i][k.ID] = F1DRot self._bolt._axial_force[i][k.ID] = max(0, F1DRot[2]) self._bolt._shear_force[i][k.ID] = float(np.linalg.norm(np.array([force1D[1], force1D[2]]))) self._bolt._load_angle[i][k.ID] = float((np.rad2deg(np.arctan2(rotShearForce[1], rotShearForce[0])) + 360) % 360) # Some forces are organised to be easier to export later on elementForcesMaterialOrg = self._organise_forces(elementForcesMaterial) elementForcesLocalOrg = self._organise_forces(elementForcesLocal) self._bolt._max_axial_force[i] = max(self.Bolt.AxialForce[i].values()) # Calculations regarding the 1D forces applied on each plate for k, l in enumerate(self.Plates): elementForcesLocalOrg[k][0] = [float(m) for m in elementForcesLocalOrg[k][0]] elementForcesLocalOrg[k][1] = [float(m) for m in elementForcesLocalOrg[k][1]] l._plates_force[i] = elementForcesLocalOrg[k] forces = project_vector(elementForcesMaterialOrg[k], materialSystem, l.Elements[0].MaterialSystemArray) if type(forces[0].tolist()) == list: forces = [x - y for x, y in zip(forces[1], forces[0])] else: forces = forces.tolist() forces = [float(m) for m in forces] l._altair_force[i] = forces
# ------------------------------------------------------------------------------------------------------------------ # Method used to calculate the model's bypass loads ----------------------------------------------------------------
[docs] def get_bypass_loads(self, model: N2PModelContent, results: dict, cornerData: bool = False, materialFactorMetal: float = 4.0, materialFactorComposite: float = 4.5, areaFactor: float = 2.5, maxIterations: int = 200, boxTol: float = 1e-3, projTol: float = 0): """ Method that takes an N2PJoint, as well as some other inputs obtained throughout the code, and obtains the bypass loads of each of its associated N2PPlates. It is highly recommended to use the default inputs, but the user could manually alter any of them. Args: model: N2PModelContent results: dict -> result dictionary. cornerData: bool = False -> boolean which shows whether the corner data is to be used or not. materialFactorMetal: float = 4.0 -> material factor corresponding to metallic materials. materialFactorComposite: float = 4.5 -> material factor corresponding to composite materials. areaFactor: float = 2.5 maxIterations: int = 200: maximum number of iterations. boxTol: float = 1e-3 -> tolerance used to determine whether a point is inside the box or not. projTol: float = 0 -> tolerance used in the projections. Calling example: >>> bypassLoads = myJoint.get_bypass_loads(model1, fasteners.Results) Procedure and methodology: - The procedure is based on the crown method, so the fluxes will be calculated using a square-shaped box around the joint in the plate's plane. - Firstly, the box where the calculations are to be made is obtained. Its dimension is a = 0.4 * areaFactor * materialFactor * Diameter - Knowing its dimension, the box should be defined with a specific orientation and order. The orientation is defined by the box reference frame, which coincides with the material system of the element where the joint is pierced. This may cause a small variation because the z-axis is defined as the joint's axial direction, and sometimes the material system's z-axis does not coincide with it. - Then, the box system's origin would be placed in the center of the box and the first point would be located in (-a, a) and the other points would be placed in the clockwise direction. - This does not always coincide with what Altair displays, but the final results should be the same. - Adjacent elements will be evaluated until the distance from the joint to them is greater to the box's semidiagonal. After this, no more points will be found further away, so the search stops here. If there are still points to be assigned, it is concluded that they lie outside of the edge of the plate and therefore they will be projected. - If all points lie within the free edges, the process is simple. The adjacent elements to the pierced one are evaluated in case that any point lies inside of it, which is done taking into consideration the so-called box tolerance, stopping the iterations when the element that is being analysed is far from the box location. - However, there are two cases where the box point location is not as simple. Firstly, if there are points outside the free edges, they are orthogonally projected onto the mesh. In the FastPPH tool used by Altair, this projection does not always follow the same procedure but, to simplify, in this tool an orthogonal projection is always used. - The second critical case occurs when a box crosses a T-edge or gets out of a surface that does not finish in a free edge. If the box crosses a T-edge, it is considered that all points are located within the free edges and should not be projected. If the box gets out of the borders of a surface, and these borders are not free edges, they are treated as so, and the same procedure is followed as when they were outside of free edges (they are orthogonally projected). - Now, the fluxes in each of the points of the boxes, for each load case, must be obtained, in order to calculate the final values for bypass and total loads. There are two options to be analyzed: 1. cornerData = True If the user asks for results in the corner when running the model and obtaining the corresponding results, these results will be given by node and not by element, giving several values for a node, related to the element where it is. This can be achieved by selecting the CORNER or BILIN describer in the FORCE card. Results will be more accurate if corner data is used, as there are more values to be used. Taking each of the box's points, the same procedure is carried out. Firstly, the results for all nodes that form the element where the box point is are retrieved. They are represented in their element reference frame, so they are transformed into the same reference frame. Once 3 or 4 values for the nodes are obtained (depending on wether the element is a TRIA or QUAD), a bilinear interpolation to the box point from the node locations is used. 2. cornerData = False The results in the result files are retrieved in the centroid of each element, leading to results that will be less precise, since results in the corners must be approximated instead of actually calculated. This approximation is made by averaging the adjacent elements. Besides this, the same procedure is used as in the previous case. - Finally, all results are transformed into the material reference frame corresponding to the element where the joint is pierced. There are several checks during the program that help the user understand if there are any problems in the analysis. In any case, an error appears. The checks are the following: 1. There are less than two plates connected to the joint. 2. A plate does not have the necessary information (intersection, normal, distance and elements). 3. The joint has no diameter or it is negative. 4. The maximum number of iterations is reached. 5. There are no candidate elements to search. This should not actually occur, though, because of a previous check. 6. There are not enough adjacent elements in the model. 7. Certain arrays do not have, for whatever reason, six elements. 8. The final rotation matrix is not well defined. """ # Only CQUAD4 and CTRIA3 elements are supported supportedElements = ["CQUAD4", "CTRIA3"] domain = [i for i in model.get_elements() if i.TypeElement in supportedElements] # If there are less than two plates connected to the joint, an error occurs if len(self.Plates) < 2: N2PLog.Error.E505(self) return None # Every plate in the joint is checked for p in self.Plates: if p.Intersection is None: N2PLog.Warning.W513(self) continue if p.Normal is None: N2PLog.Warning.W514(self) continue if p.Distance is None: N2PLog.Warning.W515(self) continue if p.Elements is None or type(p.Elements) == list and len(p.Elements) == 0: N2PLog.Warning.W516(self) continue # STEP 1. The box is obtained # The material factor is selected depending on whether the material is composite or not if model.PropertyDict.get(p.Elements[0].Prop).PropertyType == "PCOMP": materialFactor = materialFactorComposite else: materialFactor = materialFactorMetal if self.Diameter is None or self.Diameter <= 0: N2PLog.Error.E506(self) return None boxDimension = 0.4*areaFactor*materialFactor*self.Diameter boxSemiDiag = 0.5*boxDimension*(2**0.5) boltElement = [i for i in p.Elements if i.ID == p.PlateCentralCellSolverID][0] intersectionPlate = np.array(p.Intersection) p._box_dimension = boxDimension # Box reference frame is defined xMat = np.array(boltElement.MaterialSystemArray[0:3]) xBox = xMat zBox = np.array(boltElement.ElemSystemArray[6:9]) yBox = np.cross(zBox, xBox) boxSystem = [xBox[0], xBox[1], xBox[2], yBox[0], yBox[1], yBox[2], zBox[0], zBox[1], zBox[2]] p._box_system = [float(i) for i in boxSystem] # The box's boundary is created ax = np.array([[-1.0, 0.0, 1.0, 1.0, 1.0, 0.0, -1.0, -1.0]]) ay = np.array([[ 1.0, 1.0, 1.0, 0.0, -1.0, -1.0, -1.0, 0.0]]) boxPoints = intersectionPlate + 0.5*boxDimension*(np.matmul(ax.transpose(), xBox.reshape((1,3))) + np.matmul(ay.transpose(), yBox.reshape((1,3)))) boxPoints = {i + 1: boxPoints[i] for i in range(len(boxPoints))} # STEP 2. Elements contained in the box are identified boxPointsFound = {i: False for i in boxPoints.keys()} boxPointsElements = {i: None for i in boxPoints.keys()} candidateElements = [boltElement] seenCandidates = [] minDistance = 0 iterations = 0 # The loop will keep going as long as there are elements to be found while not all(boxPointsFound.values()): iterations += 1 if iterations > maxIterations: N2PLog.Error.E507(self) return 0 noAdjacents = False # Adjacent elements will be evaluated until the distance from the bolt to them is greater than the # semidiagonal of the box. After this it is assured that no more points will be found far away and it # does not make sense to keep looking. The exception is when the element size is greater than the # box. In this case the distance condition alone fails, that is why while iterations are smaller than # 2 it is ignored. In the case that some points are still be assigned, we can conclude that they lie # outside the edge of the plate and should be projected. # STEP 2.1. All points within free edges are located if minDistance < boxSemiDiag or iterations < 3: for i in boxPoints.keys(): if not boxPointsFound.get(i): for j in candidateElements: vertices = [np.array(k.GlobalCoords) for k in j.Nodes] # Check if element is inside the element normal = np.cross(vertices[1] - vertices[0], vertices[2] - vertices[0]) normal = normal/np.linalg.norm(normal) pointPlaneDistance = abs(np.dot(normal, boxPoints.get(i)) - np.dot(normal, vertices[0])) if pointPlaneDistance > boxTol: continue # Check if the point is inside the convex hull of the element inside = [False] * len(vertices) for k in range(len(vertices)): edgeVector = vertices[(k + 1) % len(vertices)] - vertices[k] toPointVector = boxPoints.get(i) - vertices[k] crossProduct = np.cross(edgeVector, toPointVector) if np.dot(normal, crossProduct) > 0: inside[k] = True # If all conditions are not met, the element is inside the convex hull if all(inside): boxPointsFound[i] = [True] boxPointsElements[i] = j # Candidate element list is updated seenCandidates = list(set(seenCandidates + candidateElements)) adjacentElements = [i for i in model.get_elements_adjacent(candidateElements, domain) if (isinstance(i, N2PElement) and i.TypeElement in supportedElements)] # If there are not enough adjacent elements, bypass loads are not calculated in this plate if len(adjacentElements) < 2: noAdjacents = True break candidateElements = list(set(adjacentElements).difference(set(seenCandidates))) if len(candidateElements) == 0: N2PLog.Error.E508(self) return None candidateElementsNodes = np.array(list(set([i.GlobalCoords for j in candidateElements for i in j.Nodes]))) # This few lines of code should not be implemented in this way, originally there was no check to # see if candidateElementsNodes had any elements, it just happened always if len(candidateElementsNodes) > 0: minDistance = np.min(np.linalg.norm(intersectionPlate.transpose() - candidateElementsNodes, axis = 1)) else: minDistance = np.min(np.linalg.norm(intersectionPlate.transpose())) # STEP 2.2. All points outside of T-edges or free edges are located else: # Since only the plane where the bolt is is considered, T-edges are also considered free edges faceElements = model.get_elements_by_face(boltElement, domain = seenCandidates) freeEdges = model.get_free_edges(domain = faceElements) for i in boxPoints.keys(): # Of course, points that have been found are skipped if not boxPointsFound.get(i): A = np.array([[j[1].X, j[1].Y, j[1].Z] for j in freeEdges]) B = np.array([[j[2].X, j[2].Y, j[2].Z] for j in freeEdges]) segmentVector = B - A pointVector = boxPoints.get(i) - A projection = np.sum(pointVector * segmentVector, axis = 1) / np.sum(segmentVector * segmentVector, axis = 1) projectedPoint = [] for j in range(A.shape[0]): if projection[j] < np.linalg.norm(segmentVector)*projTol: projectedPoint.append(A[j]) elif projection[j] > 1 - np.linalg.norm(segmentVector)*projTol: projectedPoint.append(B[j]) else: projectedPoint.append(A[j] + projection[j]*segmentVector[j]) distanceToProjectedPoint = [np.linalg.norm(boxPoints.get(i) - j) for j in projectedPoint] # Of course, only the minimum distance will be saved index = distanceToProjectedPoint.index(np.nanmin(distanceToProjectedPoint)) boxPointsElements[i] = freeEdges[index][0] boxPoints[i] = projectedPoint[index] boxPointsFound[i] = True # If there are not enough adjacent elements (2 or less), an error is displayed and all dictionaries are # filled with zeros. if noAdjacents: N2PLog.Warning.W520(p) for i in list(results.keys()): p._nx_bypass[i] = 0 p._nx_total[i] = 0 p._ny_bypass[i] = 0 p._ny_total[i] = 0 p._nxy_bypass[i] = 0 p._nxy_total[i] = 0 p._mx_total[i] = 0 p._my_total[i] = 0 p._mxy_total[i] = 0 p._bypass_max[i] = 0 p._bypass_min[i] = 0 p._box_fluxes[i] = {1: np.zeros(6).tolist(), 2: np.zeros(6).tolist(), 3: np.zeros(6).tolist(), 4: np.zeros(6).tolist(), 5: np.zeros(6).tolist(), 6: np.zeros(6).tolist(), 7: np.zeros(6).tolist(), 8: np.zeros(6).tolist()} p._box_points = {1: np.zeros(3), 2: np.zeros(3), 3: np.zeros(3), 4: np.zeros(3), 5: np.zeros(3), 6: np.zeros(3), 7: np.zeros(3), 8: np.zeros(3)} continue # All box points are placed and their container elements are identified p._box_points = boxPoints if cornerData: resultDict = {} elementNodal = model.elementnodal() boxPointForces = {i: None for i in boxPoints.keys()} # Forces and moments are obtained in each box points for i in boxPoints.keys(): pointElement = boxPointsElements.get(i) elementSystemBoxPoint = pointElement.ElemSystemArray elementForces = [] resultForPoint = {j: None for j in results.keys()} resultForNode = {j.ID: None for j in pointElement.Nodes} for j in pointElement.Nodes: unsewNode = [k for k in elementNodal.keys() if j.ID == elementNodal.get(k)[1]] unsewElementIDs = [elementNodal.get(k)[2] for k in elementNodal.keys() if j.ID == elementNodal.get(k)[1]] unsewElement2 = [k for k in j.Connectivity if (isinstance(k, N2PElement) and k.TypeElement in supportedElements)] unsewElement2IDs = [k.ID for k in unsewElement2] indexNoElement = [k for k, l in enumerate(unsewElementIDs) if l not in unsewElement2IDs] for k in reversed(indexNoElement): del unsewElementIDs[k] del unsewNode[k] unsewElement = sorted(unsewElement2, key = lambda x: unsewElementIDs.index(x.ID)) elementFrames = [k.ElemSystemArray for k in unsewElement] # It looks like Altair ignores elements that are not coplanar when obtaining the forces in the box points adjacentElements = [k for k in model.get_elements_adjacent(pointElement, domain = domain) if (isinstance(k, N2PElement) and k.TypeElement in supportedElements)] adjacentElementsBolt = [k for k in model.get_elements_adjacent(boltElement, domain = domain) if (isinstance(k, N2PElement) and k.TypeElement in supportedElements)] faceElements = model.get_elements_by_face(boltElement, domain = adjacentElements + adjacentElementsBolt, tolerance_angle = 15) eliminateIndex = [k for k, l in enumerate(unsewElement) if l not in faceElements] # Eliminate elements from lists using the stored indexes. for k in reversed(eliminateIndex): del elementFrames[k] del unsewNode[k] del unsewElement[k] del unsewElementIDs[k] resultForNodeLC = {k: None for k in results.keys()} for k, l in results.items(): # Results are obtained in the corners fxC = l.get("FX CORNER") fyC = l.get("FY CORNER") fxyC = l.get("FXY CORNER") mxC = l.get("MX CORNER") myC = l.get("MY CORNER") mxyC = l.get("MXY CORNER") elementForces = [] unsewNodesForces = [[fxC[m], fyC[m], fxyC[m], mxC[m], myC[m], mxyC[m]] for m in unsewNode] for m in unsewNodesForces: if len(m) != 6: N2PLog.Error.E509(self) return None unsewNodesForcesRot = [rotate_tensor2D(elementFrames[m], elementSystemBoxPoint, elementSystemBoxPoint[6:9], unsewNodesForces[m]) for m in range(len(unsewNode))] # Mean forces are obtained resultForNodeLC[k] = np.mean(unsewNodesForcesRot, axis = 0) resultForNode[j.ID] = resultForNodeLC keysFirstElem = set(resultForNode[next(iter(resultForNode))].keys()) elementForces = {k: [] for k in keysFirstElem} for k in keysFirstElem: for l in resultForNode.values(): if k in l: elementForces[k].append(l[k]) else: elementForces[k].append(None) for k, l in elementForces.items(): # Interpolation from corner to box points cornerCoordsGlobal = np.array([m.GlobalCoords for m in pointElement.Nodes]) centroid = pointElement.Centroid cornerCoordElem, pointCoordElem = transformation_for_interpolation(cornerCoordsGlobal, centroid, boxPoints.get(i), elementSystemBoxPoint) interpolatedForces = interpolation(pointCoordElem, cornerCoordElem, l) interpolatedForcesRot = rotate_tensor2D(elementSystemBoxPoint, boltElement.MaterialSystemArray, elementSystemBoxPoint[6:9], interpolatedForces) resultForPoint[k] = interpolatedForcesRot boxPointForces[i] = resultForPoint for i, j in boxPointForces.items(): for k, l in j.items(): if k not in resultDict: resultDict[k] = {} resultDict[k][i] = l else: resultDict = {} # Forces and moments are obtained in each box points boxPointForces = {i: None for i in boxPoints.keys()} for i in boxPoints.keys(): pointElement = boxPointsElements.get(i) elementSystemBoxPoint = pointElement.ElemSystemArray neighborElements = [j for j in model.get_elements_adjacent(cells = pointElement) if (isinstance(j, N2PElement) and j.TypeElement in supportedElements)] # It is determined whether the elements are coplanar or not adjacentElementsBolt = [j for j in model.get_elements_adjacent(boltElement, domain = domain) if (isinstance(j, N2PElement) and j.TypeElement in supportedElements)] # It looks like Altair ignores elements that are not coplanar when obtaining the forces in the box points faceElementsPrev = model.get_elements_by_face(boltElement, domain = neighborElements + adjacentElementsBolt) \ + model.get_elements_by_face(pointElement, domain = neighborElements + adjacentElementsBolt) faceElements = [j for j in neighborElements if j in faceElementsPrev] neighborElements = faceElements resultForPoint = {j: None for j in results.keys()} for j, k in results.items(): # Results are obtained in the centroid fx = k.get("FX") fy = k.get("FY") fxy = k.get("FXY") mx = k.get("MX") my = k.get("MY") mxy = k.get("MXY") elementForces = [] for l in pointElement.Nodes: nodeForces = [] for m in neighborElements: if l.InternalID in [n.InternalID for n in m.Nodes]: elemSystemNeighbor = m.ElemSystemArray n = m.InternalID neighborForces = [fx[n], fy[n], fxy[n], mx[n], my[n], mxy[n]] if len(neighborForces) != 6: N2PLog.Error.E509(self) return None neighborForces = rotate_tensor2D(elemSystemNeighbor, elementSystemBoxPoint, elementSystemBoxPoint[6:9], neighborForces) nodeForces.append(neighborForces) nodeForces = np.array(nodeForces) averageNodeForces = np.mean(nodeForces, axis = 0) elementForces.append(averageNodeForces.tolist()) elementForces = np.array(elementForces) # Interpolation from corners to box points cornerCoordsGlobal = np.array([l.GlobalCoords for l in pointElement.Nodes]) centroid = pointElement.Centroid cornerCoordElem, pointCoordElem = transformation_for_interpolation(cornerCoordsGlobal, centroid, boxPoints.get(i), elementSystemBoxPoint) interpolatedForces = interpolation(pointCoordElem, cornerCoordElem, elementForces) interpolatedForcesRot = rotate_tensor2D(elementSystemBoxPoint, boltElement.MaterialSystemArray, elementSystemBoxPoint[6:9], interpolatedForces) resultForPoint[j] = interpolatedForcesRot boxPointForces[i] = resultForPoint for i, j in boxPointForces.items(): for k, l in j.items(): if k not in resultDict: resultDict[k] = {} resultDict[k][i] = l p._box_fluxes = resultDict # STEP 3. Bypass and total forces and moments are obtained for i, j in resultDict.items(): # Fluxes are obtained side = {1: [1, 2, 3], 2: [3, 4, 5], 3: [5, 6, 7], 4: [7, 8, 1]} # X fluxes nx2 = np.array([j.get(k)[0] for k in side[2]]).mean() nx4 = np.array([j.get(k)[0] for k in side[4]]).mean() nxBypass = min((nx2, nx4), key = abs) nxTotal = max((nx2, nx4), key = abs) mx2 = np.array([j.get(k)[3] for k in side[2]]).mean() mx4 = np.array([j.get(k)[3] for k in side[4]]).mean() mxTotal = -0.5*(mx2 + mx4) # Y fluxes ny1 = np.array([j.get(k)[1] for k in side[1]]).mean() ny3 = np.array([j.get(k)[1] for k in side[3]]).mean() nyBypass = min((ny1, ny3), key = abs) nyTotal = max((ny1, ny3), key = abs) my1 = np.array([j.get(k)[4] for k in side[1]]).mean() my3 = np.array([j.get(k)[4] for k in side[3]]).mean() myTotal = -0.5*(my1 + my3) # XY fluxes nxy1 = np.array([j.get(k)[2] for k in side[1]]).mean() nxy2 = np.array([j.get(k)[2] for k in side[2]]).mean() nxy3 = np.array([j.get(k)[2] for k in side[3]]).mean() nxy4 = np.array([j.get(k)[2] for k in side[4]]).mean() nxyBypass = min((nxy1, nxy2, nxy3, nxy4), key = abs) nxyTotal = max((nxy1, nxy2, nxy3, nxy4), key = abs) mxy1 = np.array([j.get(k)[5] for k in side[1]]).mean() mxy2 = np.array([j.get(k)[5] for k in side[2]]).mean() mxy3 = np.array([j.get(k)[5] for k in side[3]]).mean() mxy4 = np.array([j.get(k)[5] for k in side[4]]).mean() mxyTotal = -0.25*(mxy1 + mxy2 + mxy3 + mxy4) # STEP 4. The bypass tensor is rotated materialSystem = boltElement.MaterialSystemArray tensorToRotate = [nxBypass, nyBypass, nxyBypass, nxTotal, nyTotal, nxyTotal, mxTotal, myTotal, mxyTotal] for k in tensorToRotate: if not isinstance(k, float): N2PLog.Error.E509(self) return None rotatedTensor = rotate_tensor2D(boxSystem, materialSystem, materialSystem[6:9], tensorToRotate) p._nx_bypass[i] = rotatedTensor[0] p._ny_bypass[i] = rotatedTensor[1] p._nxy_bypass[i] = rotatedTensor[2] p._nx_total[i] = rotatedTensor[3] p._ny_total[i] = rotatedTensor[4] p._nxy_total[i] = rotatedTensor[5] p._mx_total[i] = rotatedTensor[6] p._my_total[i] = rotatedTensor[7] p._mxy_total[i] = rotatedTensor[8] p._bypass_max[i] = 0.5*(rotatedTensor[0] + rotatedTensor[1]) + (0.5*(rotatedTensor[0] - rotatedTensor[1])**2 + (rotatedTensor[2])**2)**0.5 p._bypass_min[i] = 0.5*(rotatedTensor[0] + rotatedTensor[1]) - (0.5*(rotatedTensor[0] - rotatedTensor[1])**2 + (rotatedTensor[2])**2)**0.5
# ------------------------------------------------------------------------------------------------------------------ # Method used to adequately export forces --------------------------------------------------------------------------
[docs] def export_forces(self, model: N2PModelContent, pathFile: str, analysisName: str, results: dict, typeExport: Literal["NAXTOPY", "ALTAIR"] = "NAXTOPY"): """ Method used to export the obtained forces and bypass loads to a CSV file in the path described by the user. Args: model: N2PModelContent pathFile: str -> path file. analysisName: str -> file's name. results: dict -> results dictionary. typeExport: Literal["NAXTOPY", "ALTAIR"] = "NAXTOPY" -> export style. By default, results are exported in the NAXTOPY style. The CSV file has the following information, in this order, for the NAXTOPY style: Bolt ID Plate Global ID Plate Local ID Plate Part ID Plate Element ID -> solver ID of the central N2PElement associated to the N2PPlate. Plate Element Property ID -> ID of the N2PProperty associated to the previous N2PElement. Bolt Element 1 ID -> ID of the 1st N2PElement associated to the N2PBolt. Bolt Element 2 ID -> ID of the 2nd N2PElement associated to the N2PBolt (if there is no 2nd element, '0' is displayed instead). Load Case ID Analysis Name -> as defined by the user. Box Dimension Box System Intersection -> intersection point between the plate and its bolt FX Altair; FY Altair; FZ Altair FX Connector 1; FY Connector 1; FZ Connector 1; FX Connector 2; FY Connector 2; FZ Connector 2 FZ max p1; p2; ...; p8 -> nth box point Fxx p1; Fxx p2; ...; Fxx p8 -> FXX flux associated to the nth box point. Fyy p1; ... -> FYY flux associated to the nth box point. Fxy p1; ... -> FXY flux associated to the nth box point. Mxx p1; ... -> MXX flux associated to the nth box point. Myy p1; ... -> MYY flux associated to the nth box point. Mxy p1; ... -> MXY flux associated to the nth box point. Nx bypass; Nx total; Ny bypass; Ny total; Nxy bypass; Nxy total; Mx total; My total; Mxy total It has the following information, in this order, for the ALTAIR style: DDP -> joint ID. elementid -> ID of the central N2PElement associated to the N2PPlate. Component Name elem 1 id -> ID of the first connector. elem 1 node id -> ID of the first node of the first connector. elem 2 id -> ID of the second connector. elem 2 node id -> ID of the first node of the second connector. box dimension loadcase -> loadcase ID. file name -> analysis name. LoadCase Name Time Step Name -> 'N/A' pierced location -> intersection point. FX, FY, FZ MaxFz p1; p2; ...; p8 -> nth box point Fxx p1; Fxx p2; ...; Fxx p8 -> FXX flux associated to the nth box point. Fyy p1; ... -> FYY flux associated to the nth box point. Fxy p1; ... -> FXY flux associated to the nth box point. Mxx p1; ... -> MXX flux associated to the nth box point. Myy p1; ... -> MYY flux associated to the nth box point. Mxy p1; ... -> MXY flux associated to the nth box point. Nx bypass; Nx total; Ny bypass; Ny total; Nxy bypass; Nxy total; Mx total; My total; Mxy total There is one row for each loadcase and each distinct N2PPlate, so there are a total of Numer of Loadcases * Number of different N2PPlates total rows, without taking into consideration the header. Calling example: >>> myJoint.export_forces(pathFile, "test", fasteners.Results, "ALTAIR") """ # A dictionary of N2PElements attached to each N2PPlate is created. If there is only one N2PElement attached, # '0' is displayed jointElements = self.BoltElements connectors = [] if len(jointElements) == 1: connectors = [[jointElements[0].ID, 0], [jointElements[0].ID, 0]] else: connectors.append([jointElements[0].ID, 0]) for i in range(1, len(jointElements)): connectors.append([jointElements[i - 1].ID, jointElements[i].ID]) connectors.append([jointElements[-1].ID, 0]) jointElementsDict = {} for i, j in enumerate(self.Plates): jointElementsDict[j] = connectors[i] if typeExport == "ALTAIR": jointNodes = self.BoltNodes connectors2 = [] connectors2.append([jointNodes[0][0].ID, 0]) for i in range(1, len(jointNodes)): connectors2.append([jointNodes[i - 1][1].ID, jointNodes[i][0].ID]) connectors2.append([jointNodes[-1][0].ID, 0]) jointNodesDict = {} for i,j in enumerate(self.Plates): jointNodesDict[j] = connectors2[i] loadCaseID = list(results.keys()) lc = model.LoadCases lcDict = {} for i in lc: for j in loadCaseID: if j == i.ID: lcDict[j] = i propDict = model.PropertyDict # The header is created if typeExport == "NAXTOPY": headline = ["Bolt ID", "Plate Global ID", "Plate Local ID", "Plate Part ID", "Plate Element ID", "Plate Element Property ID", "Bolt Element 1 ID", "Bolt Element 2 ID", "Load Case ID", "Analysis Name", "Box Dimension", "Box System", "Intersection", "FX Altair", "FY Altair", "FZ Altair", "FX Connector 1", "FY Connector 1", "FZ Connector 1", "FX Connector 2", "FY Connector 2", "FZ Connector 2", "FZ max", "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "Fxx p1", "Fxx p2", "Fxx p3", "Fxx p4", "Fxx p5", "Fxx p6", "Fxx p7", "Fxx p8", "Fyy p1", "Fyy p2", "Fyy p3", "Fyy p4", "Fyy p5", "Fyy p6", "Fyy p7", "Fyy p8", "Fxy p1", "Fxy p2", "Fxy p3", "Fxy p4", "Fxy p5", "Fxy p6", "Fxy p7", "Fxy p8", "Mxx p1", "Mxx p2", "Mxx p3", "Mxx p4", "Mxx p5", "Mxx p6", "Mxx p7", "Mxx p8", "Myy p1", "Myy p2", "Myy p3", "Myy p4", "Myy p5", "Myy p6", "Myy p7", "Myy p8", "Mxy p1", "Mxy p2", "Mxy p3", "Mxy p4", "Mxy p5", "Mxy p6", "Mxy p7", "Mxy p8", "Nx bypass", "Nx total", "Ny bypass", "Ny total", "Nxy bypass", "Nxy total", "Mx total", "My total", "Mxy total"] elif typeExport == "ALTAIR": headline = ["DDP", "elementid", "Component Name", "elem 1 id", "elem 1 Node id", "elem 2 id", "elem 2 Node id", "box dimension", "loadcase", "file Name", "LoadCase Name", "Time Step Name", "pierced location", "Fx", "Fy", "Fz", "MaxFz", "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "Fxx p1", "Fxx p2", "Fxx p3", "Fxx p4", "Fxx p5", "Fxx p6", "Fxx p7", "Fxx p8", "Fyy p1", "Fyy p2", "Fyy p3", "Fyy p4", "Fyy p5", "Fyy p6", "Fyy p7", "Fyy p8", "Fxy p1", "Fxy p2", "Fxy p3", "Fxy p4", "Fxy p5", "Fxy p6", "Fxy p7", "Fxy p8", "Mxx p1", "Mxx p2", "Mxx p3", "Mxx p4", "Mxx p5", "Mxx p6", "Mxx p7", "Mxx p8", "Myy p1", "Myy p2", "Myy p3", "Myy p4", "Myy p5", "Myy p6", "Myy p7", "Myy p8", "Mxy p1", "Mxy p2", "Mxy p3", "Mxy p4", "Mxy p5", "Mxy p6", "Mxy p7", "Mxy p8", "Nx bypass", "Nx total", "Ny bypass", "Ny total", "Nxy bypass", "Nxy total", "Mx total", "My total", "Mxy total"] else: N2PLog.Error.E526(typeExport) # The path file is defined newPathFile = "{}\\{}_fastpph.csv".format(pathFile, analysisName) with open(newPathFile, "a+", newline = "") as i: writerCSV = csv.writer(i) if i.tell() == 0: writerCSV.writerow(headline) for p in self.Plates: for j, k in results.items(): if typeExport == "NAXTOPY": data = [p.Bolt.ID, p.GlobalID[0], p.ID, p.Joint.PartID, p.PlateCentralCellSolverID, p.Elements[0].Prop, jointElementsDict[p][0], jointElementsDict[p][1], j, analysisName, p.BoxDimension, p.BoxSystem, p.Intersection, p.AltairForce[j][0], p.AltairForce[j][1], p.AltairForce[j][2], p.PlatesForce[j][0][0], p.PlatesForce[j][0][1], p.PlatesForce[j][0][2], p.PlatesForce[j][1][0], p.PlatesForce[j][1][1], p.PlatesForce[j][1][2], self.Bolt.MaxAxialForce[j], p.BoxPoints[1].tolist(), p.BoxPoints[2].tolist(), p.BoxPoints[3].tolist(), p.BoxPoints[4].tolist(), p.BoxPoints[5].tolist(), p.BoxPoints[6].tolist(), p.BoxPoints[7].tolist(), p.BoxPoints[8].tolist(), p.BoxFluxes[j][1][0], p.BoxFluxes[j][2][0], p.BoxFluxes[j][3][0], p.BoxFluxes[j][4][0], p.BoxFluxes[j][5][0], p.BoxFluxes[j][6][0], p.BoxFluxes[j][7][0], p.BoxFluxes[j][8][0], p.BoxFluxes[j][1][1], p.BoxFluxes[j][2][1], p.BoxFluxes[j][3][0], p.BoxFluxes[j][4][1], p.BoxFluxes[j][5][1], p.BoxFluxes[j][6][1], p.BoxFluxes[j][7][0], p.BoxFluxes[j][8][1], p.BoxFluxes[j][1][2], p.BoxFluxes[j][2][2], p.BoxFluxes[j][3][0], p.BoxFluxes[j][4][2], p.BoxFluxes[j][5][2], p.BoxFluxes[j][6][2], p.BoxFluxes[j][7][0], p.BoxFluxes[j][8][2], p.BoxFluxes[j][1][3], p.BoxFluxes[j][2][3], p.BoxFluxes[j][3][0], p.BoxFluxes[j][4][3], p.BoxFluxes[j][5][3], p.BoxFluxes[j][6][3], p.BoxFluxes[j][7][0], p.BoxFluxes[j][8][3], p.BoxFluxes[j][1][4], p.BoxFluxes[j][2][4], p.BoxFluxes[j][3][0], p.BoxFluxes[j][4][4], p.BoxFluxes[j][5][4], p.BoxFluxes[j][6][4], p.BoxFluxes[j][7][0], p.BoxFluxes[j][8][4], p.BoxFluxes[j][1][5], p.BoxFluxes[j][2][5], p.BoxFluxes[j][3][0], p.BoxFluxes[j][4][5], p.BoxFluxes[j][5][5], p.BoxFluxes[j][6][5], p.BoxFluxes[j][7][0], p.BoxFluxes[j][8][5], p.NxBypass[j], p.NxTotal[j], p.NyBypass[j], p.NyTotal[j], p.NxyBypass[j], p.NxyTotal[j], p.MxTotal[j], p.MyTotal[j], p.MxyTotal[j]] else: data = [p.Bolt.ID, p.PlateCentralCellSolverID, propDict[p.Elements[0].Prop] + '_' + str(p.Elements[0].Prop), jointElementsDict[p][0], jointNodesDict[p][0], jointElementsDict[p][1], jointNodesDict[p][1], p.BoxDimension, j, analysisName, lcDict[j].Name, 'N/A', p.Intersection, p.AltairForce[j][0], p.AltairForce[j][1], p.AltairForce[j][2], self.Bolt.MaxAxialForce[j], p.BoxPoints[1].tolist(), p.BoxPoints[2].tolist(), p.BoxPoints[3].tolist(), p.BoxPoints[4].tolist(), p.BoxPoints[5].tolist(), p.BoxPoints[6].tolist(), p.BoxPoints[7].tolist(), p.BoxPoints[8].tolist(), p.BoxFluxes[j][1][0], p.BoxFluxes[j][2][0], p.BoxFluxes[j][3][0], p.BoxFluxes[j][4][0], p.BoxFluxes[j][5][0], p.BoxFluxes[j][6][0], p.BoxFluxes[j][7][0], p.BoxFluxes[j][8][0], p.BoxFluxes[j][1][1], p.BoxFluxes[j][2][1], p.BoxFluxes[j][3][0], p.BoxFluxes[j][4][1], p.BoxFluxes[j][5][1], p.BoxFluxes[j][6][1], p.BoxFluxes[j][7][0], p.BoxFluxes[j][8][1], p.BoxFluxes[j][1][2], p.BoxFluxes[j][2][2], p.BoxFluxes[j][3][0], p.BoxFluxes[j][4][2], p.BoxFluxes[j][5][2], p.BoxFluxes[j][6][2], p.BoxFluxes[j][7][0], p.BoxFluxes[j][8][2], p.BoxFluxes[j][1][3], p.BoxFluxes[j][2][3], p.BoxFluxes[j][3][0], p.BoxFluxes[j][4][3], p.BoxFluxes[j][5][3], p.BoxFluxes[j][6][3], p.BoxFluxes[j][7][0], p.BoxFluxes[j][8][3], p.BoxFluxes[j][1][4], p.BoxFluxes[j][2][4], p.BoxFluxes[j][3][0], p.BoxFluxes[j][4][4], p.BoxFluxes[j][5][4], p.BoxFluxes[j][6][4], p.BoxFluxes[j][7][0], p.BoxFluxes[j][8][4], p.BoxFluxes[j][1][5], p.BoxFluxes[j][2][5], p.BoxFluxes[j][3][0], p.BoxFluxes[j][4][5], p.BoxFluxes[j][5][5], p.BoxFluxes[j][6][5], p.BoxFluxes[j][7][0], p.BoxFluxes[j][8][5], p.NxBypass[j], p.NxTotal[j], p.NyBypass[j], p.NyTotal[j], p.NxyBypass[j], p.NxyTotal[j], p.MxTotal[j], p.MyTotal[j], p.MxyTotal[j]] dataNew = [] # The data list is slightly altered, so that nothing is shown as a numpy array or numpy float. for l in data: if type(l) == float: dataNew.append(str(l)) elif type(l) == list or type(l) == tuple: for m in range(len(l)): if type(l[m]) == float: l[m] = str(l[m]) dataNew.append(l) elif type(l) == str or type(l) == int: dataNew.append(l) else: dataNew.append(str(float(l))) writerCSV.writerow(dataNew)
# ------------------------------------------------------------------------------------------------------------------