def meshio_read_fem(fem_file, fem_name=None): """Import a FEM file using the meshio package""" mesh = meshio.read(fem_file) name = fem_name if fem_name is not None else "Part-1" fem = FEM(name) def to_node(data): return Node(data[1], data[0]) point_ids = mesh.points_id if "points_id" in mesh.__dict__.keys() else [ i + 1 for i, x in enumerate(mesh.points) ] elem_counter = Counter(1) cell_ids = (mesh.cells_id if "cells_id" in mesh.__dict__.keys() else [[next(elem_counter) for cell in cellblock.data] for cellblock in mesh.cells]) fem.nodes = Nodes([to_node(p) for p in zip(point_ids, mesh.points)]) cell_block_counter = Counter(0) def to_elem(cellblock): block_id = next(cell_block_counter) return [ Elem( cell_ids[block_id][i], [fem.nodes.from_id(point_ids[c]) for c in cell], cellblock.type, ) for i, cell in enumerate(cellblock.data) ] fem.elements = FemElements(chain.from_iterable(map(to_elem, mesh.cells))) return Assembly("TempAssembly") / Part(name, fem=fem)
def __init__(self, beam: Beam): """ :param beam: """ self._beam = beam self._w = [] self._p = [] self._pnames = Counter(prefix="p-load-") self._wnames = Counter(prefix="w-load-")
def create_usfos_set_str(fem: FEM, nonstrus): """USFOS documentation `GroupDef <https://usfos.no/manuals/usfos/users/documents/Usfos_UM_06.pdf#page=119>`_""" gr_ids = Counter(1) def create_groupdef_str(elset: FemSet): if "include" not in elset.metadata.keys(): return None if "unique_id" in elset.metadata.keys(): gid = elset.metadata["unique_id"] else: gid = next(gr_ids) if "nonstru" in elset.metadata.keys(): nonstrus.append(gid) nline = NewLine(10) mem_str = " ".join([f"{x.id}{next(nline)}" for x in elset.members]) if elset.type == "elset": return f" Name Group {gid} {elset.name}\n GroupDef {gid} Elem\n {mem_str}\n\n" else: return f" Name Group {gid} {elset.name}\n GroupNod {gid} {mem_str}\n\n" gelset = "\n".join( filter( lambda x: x is not None, map(create_groupdef_str, fem.elsets.values()), ) ) gnset = "\n".join(filter(lambda x: x is not None, map(create_groupdef_str, fem.nsets.values()))) return gelset + gnset
def get_sh_sections_for_shape_obj(model: gmsh.model, model_obj: Shape, gmsh_data: GmshData, fem: FEM): from ada.core.utils import Counter sides = Counter(1, "S") for dim, ent in gmsh_data.entities: _, tag, _ = model.mesh.getElements(2, ent) _, _, param = model.mesh.getNodes(2, ent, True) elements = [fem.elements.from_id(x) for x in chain.from_iterable(tag)] thickness = 0.0 normal = np.array([ 0.0 if abs(x) == 0.0 else x for x in model.getNormal(ent, param)[:3] ]) s = next(sides) set_name = make_name_fem_ready(f"el{model_obj.name}{s}_sh") fem_sec_name = make_name_fem_ready(f"d{model_obj.name}{s}_sh") add_shell_section(set_name, fem_sec_name, normal, thickness, elements, model_obj, fem, is_rigid=True)
def univec_str(fem: FEM) -> str: out_str = "" uvec_id = Counter(1) unit_vecs = dict() def write_local_z(vec): tvec = tuple(vec) if tvec in unit_vecs.keys(): return unit_vecs[tvec], None trans_no = next(uvec_id) data = [tuple([trans_no, *vec])] unit_vecs[tvec] = trans_no return trans_no, write_ff("GUNIVEC", data) for el in fem.elements.stru_elements: local_z = el.fem_sec.local_z transno, res_str = write_local_z(local_z) if res_str is None: el.metadata["transno"] = transno continue out_str += res_str el.metadata["transno"] = transno return out_str
def _hinges_str(self): from ada.core.utils import Counter out_str = "" h = Counter(1) def write_hinge(hinge): dofs = [0 if i in hinge else 1 for i in range(1, 7)] fix_id = next(h) data = [tuple([fix_id, 3, 0, 0]), tuple(dofs[:4]), tuple(dofs[4:])] return fix_id, self.write_ff("BELFIX", data) for el in self._gelements: h1, h2 = el.metadata.get("h1", None), el.metadata.get("h2", None) if h2 is None and h1 is None: continue h1_fix, h2_fix = 0, 0 if h1 is not None: h1_fix, res_str = write_hinge(h1) out_str += res_str if h2 is not None: h2_fix, res_str = write_hinge(h2) out_str += res_str el.metadata["fixno"] = h1_fix, h2_fix return out_str
def get_shell_sections_from_inp(bulk_str, fem: FEM) -> Iterable[FemSection]: if bulk_str.lower().find("*shell section") == -1: return [] a = fem.parent.get_assembly() sh_name = Counter(1, "sh") return (get_shell_section(m, sh_name, fem, a) for m in cards.re_shell.finditer(bulk_str))
def convert_hinges_2_couplings(fem: "FEM"): """Convert beam hinges to coupling constraints""" from ada import Node from ada.core.utils import Counter from ada.fem.elements import Hinge constrain_ids = [] max_node_id = fem.nodes.max_nid new_node_id = Counter(int(max_node_id + 10000)) def convert_hinge(elem: Elem, hinge: Hinge): if hinge.constraint_ref is not None: return n = hinge.fem_node csys = hinge.csys d = hinge.retained_dofs n2 = Node(n.p, next(new_node_id), parent=elem.parent) elem.parent.nodes.add(n2, allow_coincident=True) i = elem.nodes.index(n) elem.nodes[i] = n2 if elem.eccentricity is not None: if elem.eccentricity.end1 is not None: if n == elem.eccentricity.end1.node: elem.eccentricity.end1.node = n2 if elem.eccentricity.end2 is not None: if n == elem.eccentricity.end2.node: elem.eccentricity.end2.node = n2 if n2.id not in constrain_ids: constrain_ids.append(n2.id) else: logging.error(f"Hinged node {n2} cannot be added twice to different couplings") return None m_set = FemSet(f"el{elem.id}_hinge{i + 1}_m", [n], "nset") s_set = FemSet(f"el{elem.id}_hinge{i + 1}_s", [n2], "nset") elem.parent.add_set(m_set) elem.parent.add_set(s_set) c = Constraint( f"el{elem.id}_hinge{i + 1}_co", Constraint.TYPES.COUPLING, m_set, s_set, d, csys=csys, ) elem.parent.add_constraint(c) hinge.constraint_ref = c logging.info(f"added constraint {c}") for el in fem.elements.lines_hinged: if el.hinge_prop.end1 is not None: convert_hinge(el, el.hinge_prop.end1) if el.hinge_prop.end2 is not None: convert_hinge(el, el.hinge_prop.end2)
def get_solid_sections_from_inp(bulk_str, fem): """ ** Section: Section-80-MAT2TH1 *Shell Section, elset=MAT2TH1, material=S3_BS__S355_16_T__40_M2 0.02, 5 :param bulk_str: :param fem: :type fem: ada.fem.FEM """ secnames = Counter(1, "solidsec") if bulk_str.lower().find("*solid section") == -1: return [] re_solid = re.compile( r"(?:\*\s*Section:\s*(.*?)\n|)\*\Solid Section,\s*elset=(.*?)\s*,\s*material=(.*?)\s*$", _re_in, ) solid_iter = re_solid.finditer(bulk_str) def grab_solid(m_in): name = m_in.group(1) if m_in.group(1) is not None else next(secnames) elset = m_in.group(2) material = m_in.group(3) return FemSection( name=name, sec_type="solid", elset=elset, material=material, parent=fem, ) return map(grab_solid, solid_iter)
def get_interactions_from_bulk_str(bulk_str, assembly): """ :param bulk_str: :param assembly: :type assembly: ada.Assembly :return: """ gen_name = Counter(1, "general") if bulk_str.find("** Interaction") == -1 and bulk_str.find( "*Contact") == -1: return def resolve_surface_ref(surf_ref): surf_name = surf_ref.split(".")[-1] if "." in surf_ref else surf_ref surf = None if surf_name in assembly.fem.surfaces.keys(): surf = assembly.fem.surfaces[surf_name] for p in assembly.get_all_parts_in_assembly(): if surf_name in p.fem.surfaces.keys(): surf = p.fem.surfaces[surf_name] if surf is None: raise ValueError("Unable to find surfaces in assembly parts") return surf for m in AbaCards.contact_pairs.regex.finditer(bulk_str): d = m.groupdict() intprop = assembly.fem.intprops[d["interaction"]] surf1 = resolve_surface_ref(d["surf1"]) surf2 = resolve_surface_ref(d["surf2"]) assembly.fem.add_interaction( Interaction(d["name"], "surface", surf1, surf2, intprop, metadata=d)) for m in AbaCards.contact_general.regex.finditer(bulk_str): s = m.start() e = m.endpos interact_str = bulk_str[s:e] d = m.groupdict() intprop = assembly.fem.intprops[d["interaction"]] # surf1 = resolve_surface_ref(d["surf1"]) # surf2 = resolve_surface_ref(d["surf2"]) assembly.fem.add_interaction( Interaction(next(gen_name), "general", None, None, intprop, metadata=dict(aba_bulk=interact_str)))
def meshio_read_fem(assembly, fem_file, fem_name=None): """ Import a FEM file using the meshio package. :param assembly: Assembly object :param fem_file: Path to fem file :param fem_name: Name of FEM model :type assembly: ada.Assembly """ from ada import Node, Part from . import meshio_to_ada_type mesh = meshio.read(fem_file) name = fem_name if fem_name is not None else "Part-1" fem = FEM(name) def to_node(data): return Node(data[1], data[0]) point_ids = mesh.points_id if "points_id" in mesh.__dict__.keys() else [ i + 1 for i, x in enumerate(mesh.points) ] elem_counter = Counter(0) cell_ids = (mesh.cells_id if "cells_id" in mesh.__dict__.keys() else [[next(elem_counter) for cell in cellblock.data] for cellblock in mesh.cells]) fem._nodes = Nodes([to_node(p) for p in zip(point_ids, mesh.points)]) cell_block_counter = Counter(-1) def to_elem(cellblock): block_id = next(cell_block_counter) return [ Elem( cell_ids[block_id][i], [fem.nodes.from_id(point_ids[c]) for c in cell], meshio_to_ada_type[cellblock.type], ) for i, cell in enumerate(cellblock.data) ] fem._elements = FemElements(chain.from_iterable(map(to_elem, mesh.cells))) assembly.add_part(Part(name, fem=fem))
def add_obj( self, obj: Union[BackendGeom, Shape, Beam, Plate, Pipe], geom_repr=ElemType.SOLID, el_order=1, silent=True, mesh_size=None, build_native_lines=False, point_tol=Settings.point_tol, use_native_pointer=True, ): from ada.core.utils import Counter from .utils import build_bm_lines geom_repr = geom_repr.upper() self.apply_settings() temp_dir = Settings.temp_dir os.makedirs(temp_dir, exist_ok=True) if build_native_lines is True and geom_repr == ElemType.LINE and type( obj) is Beam: entities = build_bm_lines(self.model, obj, point_tol) else: if use_native_pointer and hasattr(self.model.occ, "importShapesNativePointer"): # Use hasattr to ensure that it works for gmsh < 4.9.* if type(obj) is Pipe: entities = [] for seg in obj.segments: entities += import_into_gmsh_use_nativepointer( seg, geom_repr, self.model) else: entities = import_into_gmsh_use_nativepointer( obj, geom_repr, self.model) else: entities = import_into_gmsh_using_step(obj, geom_repr, self.model, temp_dir, silent) obj_name = Counter(1, f"{obj.name}_") for dim, ent in entities: ent_name = next(obj_name) self.model.set_physical_name(dim, ent, ent_name) self.model.set_entity_name(dim, ent, ent_name) self.model.occ.synchronize() self.model.geo.synchronize() gmsh_data = GmshData(entities, geom_repr, el_order, obj, mesh_size=mesh_size) self.model_map[obj] = gmsh_data return gmsh_data
def get_shell_sections_from_inp(bulk_str, fem): """ ** Section: Section-80-MAT2TH1 *Shell Section, elset=MAT2TH1, material=S3_BS__S355_16_T__40_M2 0.02, 5 :param bulk_str: :param fem: :type fem: ada.fem.FEM :return: map object containing list of FemSection objects """ shname = Counter(1, "sh") if bulk_str.lower().find("*shell section") == -1: return [] re_offset = r"(?:, offset=(?P<offset>.*?)|)" re_controls = r"(?:, controls=(?P<controls>.*?)|)" re_shell = re.compile( rf"(?:\*\s*Section:\s*(?P<name>.*?)\n|)\*\Shell Section, elset" rf"=(?P<elset>.*?)\s*, material=(?P<material>.*?){re_offset}{re_controls}\s*\n(?P<t>.*?)," rf"(?P<int_points>.*?)$", _re_in, ) def grab_shell(m): d = m.groupdict() name = d["name"] if d["name"] is not None else next(shname) elset = fem.sets.get_elset_from_name(d["elset"]) material = d["material"] thickness = float(d["t"]) offset = d["offset"] int_points = d["int_points"] metadata = dict(controls=d["controls"]) return FemSection( name=name, sec_type="shell", thickness=thickness, elset=elset, material=material, int_points=int_points, offset=offset, parent=fem, metadata=metadata, ) return map(grab_shell, re_shell.finditer(bulk_str))
def shorten_material_names(assembly): """:type assembly: ada.Assembly""" from ada.core.utils import Counter short_suffix = Counter(1) for p in assembly.get_all_parts_in_assembly(True): for mat in p.materials: name_len = 5 if len(mat.name) > name_len: short_mat_name = mat.name[:name_len] if short_mat_name in p.materials.name_map.keys(): short_mat_name = short_mat_name[:-3] + str( next(short_suffix)) mat.name = short_mat_name p.materials.recreate_name_and_id_maps(p.materials._materials)
def multisession_gmsh_tasker(fem: FEM, gmsh_tasks: List[GmshTask]): """Run multiple meshing operations within a single GmshSession.""" model_names = Counter(1, "gmsh") with GmshSession(silent=True) as gs: for gtask in gmsh_tasks: gs.model.add(next(model_names)) gs.options = gtask.options for obj in gtask.ada_obj: gs.add_obj(obj, gtask.geom_repr) gs.mesh(gtask.mesh_size) # TODO: Add operand type += for FEM tmp_fem = gs.get_fem() tmp_fem.parent = fem.parent fem += tmp_fem gs.model_map = dict() return fem
def get_mass_from_bulk(bulk_str, parent: "FEM") -> FemElements: """ *MASS,ELSET=MASS3001 2.00000000E+03, :return: """ mass_ids = Counter(int(parent.elements.max_el_id + 1)) re_masses = re.compile( r"\*(?P<mass_type>Nonstructural Mass|Mass|Rotary Inertia),\s*elset=(?P<elset>.*?)" r"(?:,\s*type=(?P<ptype>.*?)\s*|\s*)(?:, units=(?P<units>.*?)|\s*)\n\s*(?P<mass>.*?)$", _re_in, ) return FemElements( (get_mass(m, parent, mass_ids) for m in re_masses.finditer(bulk_str)), fem_obj=parent)
def _univec_str(self): from ada.core.utils import Counter out_str = "" unit_vector = Counter(1) def write_local_z(vec): transno = next(unit_vector) data = [tuple([transno, *vec])] return transno, self.write_ff("GUNIVEC", data) for el in self._gelements: local_z = el.fem_sec.local_z transno, res_str = write_local_z(local_z) out_str += res_str el.metadata["transno"] = transno return out_str
def create_usfos_set_str(self): """ From USFOS documentation `GroupDef <http://usfos.no/manuals/usfos/users/documents/Usfos_UM_06.pdf#page=119>`_ """ from ada.core.utils import Counter, NewLine gr_ids = Counter(1) def create_groupdef_str(elset): """ :param elset: :type elset: ada.fem.FemSet """ if "include" not in elset.metadata.keys(): return None if "unique_id" in elset.metadata.keys(): gid = elset.metadata["unique_id"] else: gid = next(gr_ids) if "nonstru" in elset.metadata.keys(): self._gnonstru.append(gid) nline = NewLine(10) mem_str = " ".join([f"{x.id}{next(nline)}" for x in elset.members]) if elset.type == "elset": return f" Name Group {gid} {elset.name}\n GroupDef {gid} Elem\n {mem_str}\n\n" else: return f" Name Group {gid} {elset.name}\n GroupNod {gid} {mem_str}\n\n" gelset = "\n".join( filter( lambda x: x is not None, map(create_groupdef_str, self._gelsets.values()), )) gnset = "\n".join( filter(lambda x: x is not None, map(create_groupdef_str, self._gnsets.values()))) return gelset + gnset
def renumber(self): from ada.core.utils import Counter elid = Counter(1) def mapid2(el): """ :param el: :type el: ada.fem.Elem :return: """ el.id = next(elid) list(map(mapid2, self._elements)) self._idmap = {e.id: e for e in self._elements } if len(self._elements) > 0 else dict() self._group_by_types()
class Penetration(BackendGeom): _name_gen = Counter(1, "Pen") """A penetration object. Wraps around a primitive""" # TODO: Maybe this class should be evaluated for removal? def __init__(self, primitive, metadata=None, parent=None, units="m", guid=None): if issubclass(type(primitive), Shape) is False: raise ValueError(f'Unsupported primitive type "{type(primitive)}"') super(Penetration, self).__init__(primitive.name, guid=guid, metadata=metadata, units=units) self._primitive = primitive self._parent = parent self._ifc_opening = None @property def primitive(self): return self._primitive @property def geom(self): return self.primitive.geom @property def units(self): return self._units @units.setter def units(self, value): if value != self._units: self.primitive.units = value self._units = value @property def ifc_opening(self): if self._ifc_opening is None: from ada.ifc.write.write_openings import generate_ifc_opening self._ifc_opening = generate_ifc_opening(self) return self._ifc_opening def __repr__(self): return f"Pen(type={self.primitive})"
def renumber(self): from ada.core.utils import Counter elid = Counter(0) def mapid2(el): """ :param el: :type el: ada.fem.Elem :return: """ el.id = next(elid) list(map(mapid2, self._elements)) self._idmap = {e.id: e for e in self._elements} if len(self._elements) > 0 else dict() if len(self._elements) > 0: self._by_types = groupby(self._elements, key=attrgetter("type", "elset")) else: self._by_types = dict()
def update_connector_data(bulk_str: str, fem: FEM): """Extract connector elements from bulk string""" nsuffix = Counter(1, "_") for m in cards.connector_section.regex.finditer(bulk_str): d = m.groupdict() csys_ref = d["csys"].replace('"', "") name = d["behavior"] + next(nsuffix) elset = fem.elsets[d["elset"]] connector: Connector = elset.members[0] con_sec = fem.connector_sections[d["behavior"]] csys_ref = csys_ref[:-1] if csys_ref[-1] == "," else csys_ref csys = fem.lcsys[csys_ref] con_type = d["contype"] if con_type[-1] == ",": con_type = con_type[:-1] connector.elset = elset connector.name = name connector.con_sec = con_sec connector.con_type = con_type connector.csys = csys
def test_edges_intersect(test_meshing_dir): bm_name = Counter(1, "bm") pl = ada.Plate("pl1", [(0, 0), (1, 0), (1, 1), (0, 1)], 10e-3) points = pl.poly.points3d objects = [pl] # Beams along 3 of 4 along circumference for p1, p2 in zip(points[:-1], points[1:]): objects.append(ada.Beam(next(bm_name), p1, p2, "IPE100")) # Beam along middle in x-dir objects.append( ada.Beam(next(bm_name), (0, 0.5, 0.0), (1, 0.5, 0), "IPE100")) # Beam along diagonal objects.append(ada.Beam(next(bm_name), (0, 0, 0.0), (1, 1, 0), "IPE100")) a = ada.Assembly() / (ada.Part("MyPart") / objects) p = a.get_part("MyPart") # p.connections.find() p.fem = p.to_fem_obj(0.1, interactive=False)
def get_solid_sections_from_inp(bulk_str, fem: FEM): secnames = Counter(1, "solidsec") a = fem.parent.get_assembly() if bulk_str.lower().find("*solid section") == -1: return [] solid_iter = cards.re_solid.finditer(bulk_str) def grab_solid(m_in): name = m_in.group(1) if m_in.group(1) is not None else next(secnames) elset = m_in.group(2) material = m_in.group(3) mat = a.materials.get_by_name(material) return FemSection( name=name, sec_type=ElemType.SOLID, elset=elset, material=mat, parent=fem, ) return map(grab_solid, solid_iter)
def __init__(self, sections: Iterable[Section] = None, parent: Union[Part, Assembly] = None, units="m"): sec_id = Counter(1) super(Sections, self).__init__(parent=parent) sections = [] if sections is None else sections self._units = units self._sections = sorted(sections, key=attrgetter("name")) def section_id_maker(section: Section) -> Section: if section.id is None: section.id = next(sec_id) return section [section_id_maker(sec) for sec in self._sections] self.recreate_name_and_id_maps(self._sections) if len(self._name_map.keys()) != len(self._id_map.keys()): logging.warning( f"Non-unique ids or name for section container belonging to part '{parent}'" )
def get_connectors_from_inp(bulk_str, fem): """ :param bulk_str: :param fem: :type fem: ada.fem.FEM :return: """ nsuffix = Counter(1, "_") cons = dict() for m in AbaCards.connector_section.regex.finditer(bulk_str): d = m.groupdict() name = d["behavior"] + next(nsuffix) elset = fem.elsets[d["elset"]] elem = elset.members[0] csys_ref = d["csys"].replace('"', "") csys_ref = csys_ref[:-1] if csys_ref[-1] == "," else csys_ref con_type = d["contype"] if con_type[-1] == ",": con_type = con_type[:-1] n1 = elem.nodes[0] n2 = elem.nodes[1] cons[name] = Connector( name, elem.id, n1, n2, con_type, fem.connector_sections[d["behavior"]], csys=fem.lcsys[csys_ref], ) return cons
from OCC.Core.STEPConstruct import stepconstruct_FindEntity from OCC.Core.STEPControl import ( STEPControl_AsIs, STEPControl_ShellBasedSurfaceModel, STEPControl_Writer, ) from OCC.Core.TCollection import TCollection_HAsciiString from ada import Assembly, Beam, Part, Pipe, Plate, Shape, Wall from ada.base.physical_objects import BackendGeom from ada.core.utils import Counter from ada.fem.shapes import ElemType # Reference: https://www.opencascade.com/doc/occt-7.4.0/overview/html/occt_user_guides__step.html#occt_step_3 shp_names = Counter(1, "shp") valid_types = Union[BackendGeom, Beam, Plate, Wall, Part, Assembly, Shape, Pipe] class StepExporter: def __init__(self, schema="AP242", assembly_mode=1): self.writer = STEPControl_Writer() fp = self.writer.WS().TransferWriter().FinderProcess() self.fp = fp Interface_Static_SetCVal("write.step.schema", schema) # Interface_Static_SetCVal('write.precision.val', '1e-5') Interface_Static_SetCVal("write.precision.mode", "1") Interface_Static_SetCVal("write.step.assembly", str(assembly_mode))
class FemSection(FemBase): id_count = Counter() SEC_TYPES = ElemType def __init__( self, name, sec_type: str, elset: FemSet, material: Material, section=None, local_z=None, local_y=None, thickness=None, int_points=5, metadata=None, parent=None, refs=None, sec_id=None, is_rigid=False, ): super().__init__(name, metadata, parent) if sec_type is None: raise ValueError("Section type cannot be None") sec_type = sec_type.upper() if sec_type not in ElemType.all: raise ValueError(f'Element section type "{sec_type}" is not supported. Must be in {ElemType.all}') self._id = sec_id if sec_id is not None else next(FemSection.id_count) self._sec_type = sec_type self._elset = elset self._material = material material.refs.append(self) self._section = section if section is not None: section.refs.append(self) if self._sec_type == ElemType.LINE: if local_y is None and local_z is None: raise ValueError("You need to specify either local_y or local_z") self._local_z = local_z self._local_y = local_y self._local_x = None if self._sec_type == ElemType.SHELL and thickness is None: raise ValueError("Thickness of shell cannot be None") self._thickness = thickness self._int_points = int_points self._refs = refs self._is_rigid = is_rigid def __hash__(self): return hash(f"{self.name}{self.id}") def link_elements(self): from .elements import Elem def link_elem(el: Elem): el.fem_sec = self list(map(link_elem, self.elset.members)) @property def type(self): return self._sec_type @property def id(self): return self._id @property def elset(self): return self._elset @elset.setter def elset(self, value): self._elset = value @property def local_z(self) -> np.ndarray: """Local Z describes the up vector of the cross section""" if self._local_z is not None: return self._local_z if self.type == ElemType.LINE: n1, n2 = self.elset.members[0].nodes[0], self.elset.members[0].nodes[-1] v = n2.p - n1.p if vector_length(v) == 0.0: logging.error(f"Element {self.elset.members[0].id} has zero length") xvec = [1, 0, 0] else: xvec = unit_vector(v) self._local_z = calc_zvec(xvec, self.local_y) elif self.type == ElemType.SHELL: self._local_z = normal_to_points_in_plane([n.p for n in self.elset.members[0].nodes]) else: from ada.core.constants import Z self._local_z = np.array(Z, dtype=float) return self._local_z @property def local_y(self) -> np.ndarray: """Local y describes the cross vector of the beams X and Z axis""" if self._local_y is not None: return self._local_y if self.type == ElemType.LINE: el = self.elset.members[0] n1, n2 = el.nodes[0], el.nodes[-1] v = n2.p - n1.p if vector_length(v) == 0.0: raise ValueError(f'Element "{el}" has no length. UNable to calculate y-vector') xvec = unit_vector(v) # See https://en.wikipedia.org/wiki/Cross_product#Coordinate_notation for order of cross product vec = calc_yvec(xvec, self.local_z) elif self.type == ElemType.SHELL: vec = calc_yvec(self.local_x, self.local_z) else: vec = calc_yvec(self.local_x, self.local_z) self._local_y = np.where(abs(vec) == 0, 0, vec) return self._local_y @property def local_x(self) -> np.ndarray: if self._local_x is not None: return self._local_x el = self.elset.members[0] if self.type == ElemType.LINE: vec = unit_vector(el.nodes[-1].p - el.nodes[0].p) elif self.type == ElemType.SHELL: vec = unit_vector(el.nodes[1].p - el.nodes[0].p) else: from ada.core.constants import X vec = np.array(X, dtype=float) self._local_x = np.round(np.where(abs(vec) == 0, 0, vec), 8) return self._local_x @property def csys(self): return [self.local_x, self.local_y, self.local_z] @property def section(self) -> Section: return self._section @property def material(self) -> Material: return self._material @material.setter def material(self, value): self._material = value @property def thickness(self): return self._thickness @thickness.setter def thickness(self, value): self._thickness = value @property def int_points(self): return self._int_points @property def refs(self) -> List[Union[Beam, Plate]]: return self._refs def unique_fem_section_permutation(self) -> Tuple[int, Material, Section, tuple, tuple, float]: if self.type == self.SEC_TYPES.LINE: return self.id, self.material, self.section, tuple(self.local_x), tuple(self.local_z), self.thickness elif self.type == self.SEC_TYPES.SHELL: return self.id, self.material, self.section, (None,), tuple(self.local_z), self.thickness else: return self.id, self.material, self.section, (None,), tuple(self.local_z), 0.0 def has_equal_props(self, other: FemSection): equal_mat = self.material == other.material if self.type == self.SEC_TYPES.SHELL: equal_sec = self.thickness == other.thickness elif self.type == self.SEC_TYPES.LINE: equal_sec = self.section.equal_props(other.section) else: equal_sec = True if equal_mat is True and equal_sec is True: return True return False # def __eq__(self, other: FemSection): # self_perm = self.unique_fem_section_permutation() # other_perm = other.unique_fem_section_permutation() # return self_perm == other_perm def __repr__(self): return ( f'FemSection({self.type} - name: "{self.name}", sec: "{self.section.name}", ' f'mat: "{self.material.name}", elset: "{self.elset.name}")' )
def to_stp(self, destination_file, geom_repr="solid", schema="AP242"): """ Write current assembly to STEP file OpenCascade reference: https://www.opencascade.com/doc/occt-7.4.0/overview/html/occt_user_guides__step.html#occt_step_3 :param destination_file: :param geom_repr: :param schema: STEP Schemas. """ from OCC.Core.IFSelect import IFSelect_RetError from OCC.Core.Interface import Interface_Static_SetCVal from OCC.Core.STEPConstruct import stepconstruct_FindEntity from OCC.Core.STEPControl import STEPControl_AsIs, STEPControl_Writer from OCC.Core.TCollection import TCollection_HAsciiString from ada.core.utils import Counter if geom_repr not in ["shell", "solid"]: raise ValueError( 'Geometry representation can only accept either "solid" or "shell" as input' ) destination_file = pathlib.Path(destination_file).with_suffix(".stp") assembly_mode = 1 shp_names = Counter(1, "shp") writer = STEPControl_Writer() fp = writer.WS().TransferWriter().FinderProcess() Interface_Static_SetCVal("write.step.schema", schema) # Interface_Static_SetCVal('write.precision.val', '1e-5') Interface_Static_SetCVal("write.precision.mode", "1") Interface_Static_SetCVal("write.step.assembly", str(assembly_mode)) from ada import Assembly, Beam, Part, Pipe, Plate, Shape, Wall def add_geom(geo, o): name = o.name if o.name is not None else next(shp_names) Interface_Static_SetCVal("write.step.product.name", name) stat = writer.Transfer(geo, STEPControl_AsIs) if int(stat) > int(IFSelect_RetError): raise Exception("Some Error occurred") item = stepconstruct_FindEntity(fp, geo) if not item: logging.debug("STEP item not found for FindEntity") else: item.SetName(TCollection_HAsciiString(name)) if type(self) is Shape: assert isinstance(self, Shape) add_geom(self.geom, self) elif type(self) in (Beam, Plate, Wall): assert isinstance(self, (Beam, Plate, Wall)) if geom_repr == "shell": add_geom(self.shell, self) else: add_geom(self.solid, self) elif type(self) is Pipe: assert isinstance(self, Pipe) for geom in self.geometries: add_geom(geom, self) elif type(self) in (Part, Assembly): assert isinstance(self, Part) for p in self.get_all_subparts() + [self]: for obj in list(p.plates) + list(p.beams) + list( p.shapes) + list(p.pipes) + list(p.walls): if type(obj) in (Plate, Beam, Wall): try: if geom_repr == "shell": add_geom(obj.shell, self) else: add_geom(obj.solid, self) except BaseException as e: logging.info(f'passing pl "{obj.name}" due to {e}') continue elif type(obj) in (Pipe, ): assert isinstance(obj, Pipe) for geom in obj.geometries: add_geom(geom, self) elif type(obj) is Shape: add_geom(obj.geom, self) else: raise ValueError("Unkown Geometry type") os.makedirs(destination_file.parent, exist_ok=True) status = writer.Write(str(destination_file)) if int(status) > int(IFSelect_RetError): raise Exception("Error during write operation") print(f'step file created at "{destination_file}"')
def beam_str(self): """ # USFOS Strings # Beam String ' Elem ID np1 np2 material geom lcoor ecc1 ecc2 BEAM 1127 1343 1344 1 1 1 # Unit Vector String ' Loc-Coo dx dy dz UNITVEC 60000001 0.00000 0.00000 1.00000 """ from ada.core.utils import Counter locvecs = [] eccen_counter = Counter(1) loc_str = "'\n' Loc-Coo dx dy dz\n" bm_str = "'\n' Elem ID np1 np2 material geom lcoor ecc1 ecc2\n" def write_elem(el): """ :param el: :type el: ada.fem.Elem """ nonlocal locvecs n1 = el.nodes[0] n2 = el.nodes[1] fem_sec = el.fem_sec mat = fem_sec.material sec = fem_sec.section xvec = fem_sec.local_z xvec_str = f"{xvec[0]:>13.5f}{xvec[1]:>15.5f}{xvec[2]:>15.5f}" mat_id = mat.id if xvec_str in locvecs: locid = locvecs.index(xvec_str) else: locvecs.append(xvec_str) locid = locvecs.index(xvec_str) if fem_sec.offset is not None: ecc1_str = " 0" ecc2_str = " 0" for n, e in fem_sec.offset: if n == n1: ecc1 = next(eccen_counter) self._geccen.append((ecc1, e)) ecc1_str = f" {ecc1}" if n == n2: ecc2 = next(eccen_counter) self._geccen.append((ecc2, e)) ecc2_str = f" {ecc2}" else: ecc1_str = "" ecc2_str = "" return f" BEAM{el.id:>15}{n1.id:>8}{n2.id:>9}{mat_id:>11}{sec.id:>7}{locid + 1:>9}{ecc1_str}{ecc2_str}" bm_str += "\n".join(list(map(write_elem, self._gelements.beams))) for i, loc in enumerate(locvecs): loc_str += " UNITVEC{:>13}{:<10}\n".format(i + 1, loc) return bm_str + "\n" + loc_str