def __init__( self, name, colour=None, placement=Placement(), fem: FEM = None, settings: Settings = Settings(), metadata=None, parent=None, units="m", ifc_elem=None, guid=None, ifc_ref: IfcRef = None, ): super().__init__(name, guid=guid, metadata=metadata, units=units, parent=parent, ifc_elem=ifc_elem, ifc_ref=ifc_ref) self._nodes = Nodes(parent=self) self._beams = Beams(parent=self) self._plates = Plates(parent=self) self._pipes = list() self._walls = list() self._connections = Connections(parent=self) self._materials = Materials(parent=self) self._sections = Sections(parent=self) self._colour = colour self._placement = placement self._instances: Dict[Any, Instance] = dict() self._shapes = [] self._parts = dict() self._groups: Dict[str, Group] = dict() if ifc_elem is not None: self.metadata["ifctype"] = self._import_part_from_ifc(ifc_elem) else: if self.metadata.get("ifctype") is None: self.metadata["ifctype"] = "site" if type( self) is Assembly else "storey" self._props = settings if fem is not None: fem.parent = self self.fem = FEM(name + "-1", parent=self) if fem is None else fem
def get_sections_from_cache(part_cache, parent): sections_str = part_cache.get("SECTIONS_STR") sections_int = part_cache.get("SECTIONS_INT") if sections_str is None: return None def sec_from_list(sec_str, sec_int): guid, name, units, sec_type = str_fix(sec_str) r, wt, h, w_top, w_btn, t_w, t_ftop, t_fbtn, sec_id = [x if x != 0 else None for x in sec_int] return Section( name=name, guid=guid, sec_id=sec_id, units=units, sec_type=sec_type, r=r, wt=wt, h=h, w_top=w_top, w_btn=w_btn, t_w=t_w, t_ftop=t_ftop, t_fbtn=t_fbtn, ) return Sections( [sec_from_list(sec_str, sec_int) for sec_str, sec_int in zip(sections_str, sections_int)], parent=parent )
def move_all_mats_and_sec_here_from_subparts(self): for p in self.get_all_subparts(): self._materials += p.materials self._sections += p.sections p._materials = Materials(parent=p) p._sections = Sections(parent=p) self.sections.merge_sections_by_properties() self.materials.merge_materials_by_properties()
def get_sections(bulk_str, fem: FEM, mass_elem, spring_elem) -> FemSections: # Section Names sect_names = { sec_id: name for sec_id, name in map(get_section_names, cards.re_sectnames.finditer(bulk_str)) } # Local Coordinate Systems lcsysd = { transno: vec for transno, vec in map(get_lcsys, cards.re_lcsys.finditer(bulk_str)) } # Hinges hinges = { fixno: values for fixno, values in map(get_hinges, cards.re_belfix.finditer( bulk_str)) } # Thickness' thick = { geono: t for geono, t in map(get_thicknesses, cards.re_thick.finditer(bulk_str)) } # Eccentricities ecc = { eccno: values for eccno, values in map(get_eccentricities, cards.re_geccen.finditer(bulk_str)) } list_of_sections = chain( (get_isection(m, sect_names, fem) for m in cards.re_giorh.finditer(bulk_str)), (get_box_section(m, sect_names, fem) for m in cards.re_gbox.finditer(bulk_str)), (get_tubular_section(m, sect_names, fem) for m in cards.re_gpipe.finditer(bulk_str)), (get_flatbar(m, sect_names, fem) for m in cards.re_gbarm.finditer(bulk_str)), ) fem.parent._sections = Sections(list_of_sections, parent=fem.parent) [add_general_sections(m, fem) for m in cards.re_gbeamg.finditer(bulk_str)] geom = count(1) total_geo = count(1) res = (get_femsecs(m, total_geo, geom, lcsysd, hinges, ecc, thick, fem, mass_elem, spring_elem) for m in cards.re_gelref1.finditer(bulk_str)) sections = filter(lambda x: type(x) is FemSection, res) fem_sections = FemSections(sections, fem_obj=fem) logging.info( f"Successfully imported {next(geom) - 1} FEM sections out of {next(total_geo) - 1}" ) return fem_sections
def test_positive_contained(sec, sec2): sec_collection = Sections([sec, sec2]) assert sec2 in sec_collection
def test_negative_contained(sec, sec2): sec_collection = Sections([sec]) assert sec2 not in sec_collection
class Part(BackendGeom): """A Part superclass design to host all relevant information for cad and FEM modelling.""" def __init__( self, name, colour=None, placement=Placement(), fem: FEM = None, settings: Settings = Settings(), metadata=None, parent=None, units="m", ifc_elem=None, guid=None, ifc_ref: IfcRef = None, ): super().__init__(name, guid=guid, metadata=metadata, units=units, parent=parent, ifc_elem=ifc_elem, ifc_ref=ifc_ref) self._nodes = Nodes(parent=self) self._beams = Beams(parent=self) self._plates = Plates(parent=self) self._pipes = list() self._walls = list() self._connections = Connections(parent=self) self._materials = Materials(parent=self) self._sections = Sections(parent=self) self._colour = colour self._placement = placement self._instances: Dict[Any, Instance] = dict() self._shapes = [] self._parts = dict() self._groups: Dict[str, Group] = dict() if ifc_elem is not None: self.metadata["ifctype"] = self._import_part_from_ifc(ifc_elem) else: if self.metadata.get("ifctype") is None: self.metadata["ifctype"] = "site" if type( self) is Assembly else "storey" self._props = settings if fem is not None: fem.parent = self self.fem = FEM(name + "-1", parent=self) if fem is None else fem def add_beam(self, beam: Beam) -> Beam: if beam.units != self.units: beam.units = self.units beam.parent = self mat = self.add_material(beam.material) if mat != beam.material: beam.material = mat sec = self.add_section(beam.section) if sec != beam.section: beam.section = sec tap = self.add_section(beam.taper) if tap != beam.taper: beam.taper = tap old_node = self.nodes.add(beam.n1) if old_node != beam.n1: beam.n1 = old_node old_node = self.nodes.add(beam.n2) if old_node != beam.n2: beam.n2 = old_node self.beams.add(beam) return beam def add_plate(self, plate: Plate) -> Plate: if plate.units != self.units: plate.units = self.units plate.parent = self mat = self.add_material(plate.material) if mat is not None: plate.material = mat for n in plate.nodes: self.nodes.add(n) self._plates.add(plate) return plate def add_pipe(self, pipe: Pipe) -> Pipe: if pipe.units != self.units: pipe.units = self.units pipe.parent = self mat = self.add_material(pipe.material) if mat is not None: pipe.material = mat self._pipes.append(pipe) return pipe def add_wall(self, wall: Wall) -> Wall: if wall.units != self.units: wall.units = self.units wall.parent = self self._walls.append(wall) return wall def add_shape(self, shape: Shape) -> Shape: if shape.units != self.units: logger.info( f'shape "{shape}" has different units. changing from "{shape.units}" to "{self.units}"' ) shape.units = self.units shape.parent = self mat = self.add_material(shape.material) if mat != shape.material: shape.material = mat self._shapes.append(shape) return shape def add_part(self, part: Part) -> Part: if issubclass(type(part), Part) is False: raise ValueError( "Added Part must be a subclass or instance of Part") if part.units != self.units: part.units = self.units part.parent = self if part.name in self._parts.keys(): raise ValueError( f'Part name "{part.name}" already exists and cannot be overwritten' ) self._parts[part.name] = part try: part._on_import() except NotImplementedError: logger.info( f'Part "{part}" has not defined its "on_import()" method') return part def add_joint(self, joint: JointBase) -> JointBase: """ This method takes a Joint element containing two intersecting beams. It will check with the existing list of joints to see whether or not it is part of a larger more complex joint. It usese primarily two criteria. Criteria 1: If both elements are in an existing joint already, it will u Criteria 2: If the intersecting point coincides within a specified tolerance (currently 10mm) with an exisiting joint intersecting point. If so it will add the elements to this joint. If not it will create a new joint based on these two members. """ if joint.units != self.units: joint.units = self.units self._connections.add(joint) return joint def add_material(self, material: Material) -> Material: if material.units != self.units: material.units = self.units material.parent = self return self._materials.add(material) def add_section(self, section: Section) -> Section: if section.units != self.units: section.units = self.units return self._sections.add(section) def add_object(self, obj: Union[Part, Beam, Plate, Wall, Pipe, Shape]): from ada import Beam if isinstance(obj, Part): self.add_part(obj) elif isinstance(obj, Beam): self.add_beam(obj) else: raise NotImplementedError() def add_penetration(self, pen: Union[Penetration, PrimExtrude, PrimRevolve, PrimCyl, PrimBox], add_pen_to_subparts=True) -> Penetration: if type(pen) in (PrimExtrude, PrimRevolve, PrimCyl, PrimBox): pen = Penetration(pen, parent=self) for bm in self.beams: bm.add_penetration(pen) for pl in self.plates: pl.add_penetration(pen) for shp in self.shapes: shp.add_penetration(pen) for pipe in self.pipes: for seg in pipe.segments: seg.add_penetration(pen) for wall in self.walls: wall.add_penetration(pen) if add_pen_to_subparts: for p in self.get_all_subparts(): p.add_penetration(pen, False) return pen def add_instance(self, element, placement: Placement): if element not in self._instances.keys(): self._instances[element] = Instance(element) self._instances[element].placements.append(placement) def add_set( self, name, set_members: List[Union[Part, Beam, Plate, Wall, Pipe, Shape]]) -> Group: if name not in self.groups.keys(): self.groups[name] = Group(name, set_members, parent=self) else: logger.info(f'Appending set "{name}"') for mem in set_members: if mem not in self.groups[name].members: self.groups[name].members.append(mem) return self.groups[name] def add_elements_from_ifc(self, ifc_file_path: os.PathLike, data_only=False): a = Assembly("temp") a.read_ifc(ifc_file_path, data_only=data_only) all_shapes = [shp for p in a.get_all_subparts() for shp in p.shapes] + a.shapes for shp in all_shapes: self.add_shape(shp) all_beams = [bm for p in a.get_all_subparts() for bm in p.beams] + [bm for bm in a.beams] for bm in all_beams: ids = self.beams.dmap.keys() names = [b.name for b in self.beams.dmap.values()] if bm.guid in ids: raise NotImplementedError( "Have not considered merging ifc elements with identical IDs yet." ) if bm.name in names: start = max(ids) + 1 bm_name = f"bm{start}" if bm_name not in names: bm.name = bm_name else: while bm_name in names: bm_name = f"bm{start}" bm.name = bm_name start += 1 self.add_beam(bm) all_plates = [pl for p in a.get_all_subparts() for pl in p.plates] + [pl for pl in a.plates] for pl in all_plates: self.add_plate(pl) all_pipes = [pipe for p in a.get_all_subparts() for pipe in p.pipes] + a.pipes for pipe in all_pipes: self.add_pipe(pipe) all_walls = [wall for p in a.get_all_subparts() for wall in p.walls] + a.walls for wall in all_walls: self.add_wall(wall) def read_step_file(self, step_path, name=None, scale=None, transform=None, rotate=None, colour=None, opacity=1.0, source_units="m"): """ :param step_path: Can be path to stp file or path to directory of step files. :param name: Desired name of destination Shape object :param scale: Scale the step content upon import :param transform: Transform the step content upon import :param rotate: Rotate step content upon import :param colour: Assign a specific colour upon import :param opacity: Assign Opacity upon import :param source_units: Unit of the imported STEP file. Default is 'm' """ from ada.occ.utils import extract_shapes shapes = extract_shapes(step_path, scale, transform, rotate) if len(shapes) > 0: ada_name = name if name is not None else "CAD" + str( len(self.shapes) + 1) for i, shp in enumerate(shapes): ada_shape = Shape(ada_name + "_" + str(i), shp, colour, opacity, units=source_units) self.add_shape(ada_shape) def create_objects_from_fem(self, skip_plates=False, skip_beams=False) -> None: """Build Beams and Plates from the contents of the local FEM object""" from ada.fem.formats.utils import convert_part_objects if type(self) is Assembly: for p_ in self.get_all_parts_in_assembly(): logger.info( f'Beginning conversion from fem to structural objects for "{p_.name}"' ) convert_part_objects(p_, skip_plates, skip_beams) else: logger.info( f'Beginning conversion from fem to structural objects for "{self.name}"' ) convert_part_objects(self, skip_plates, skip_beams) logger.info("Conversion complete") def get_part(self, name: str) -> Part: key_map = {key.lower(): key for key in self.parts.keys()} return self.parts[key_map[name.lower()]] def get_by_name( self, name) -> Union[Part, Plate, Beam, Shape, Material, Pipe, None]: """Get element of any type by its name.""" for p in self.get_all_subparts() + [self]: if p.name == name: return p for bm in p.beams: if bm.name == name: return bm for pl in p.plates: if pl.name == name: return pl for shp in p.shapes: if shp.name == name: return shp for pi in p.pipes: if pi.name == name: return pi for mat in p.materials: if mat.name == name: return mat logger.debug( f'Unable to find"{name}". Check if the element type is evaluated in the algorithm' ) return None def get_all_parts_in_assembly(self, include_self=False) -> List[Part]: parent = self.get_assembly() list_of_ps = [] self._flatten_list_of_subparts(parent, list_of_ps) if include_self: list_of_ps += [self] return list_of_ps def get_all_subparts(self, include_self=False) -> List[Part]: list_of_parts = [] if include_self is False else [self] self._flatten_list_of_subparts(self, list_of_parts) return list_of_parts def get_all_physical_objects( self, sub_elements_only=False, by_type=None, filter_by_guids: Union[List[str]] = None ) -> Iterable[Union[Beam, Plate, Wall, Pipe, Shape]]: physical_objects = [] if sub_elements_only: iter_parts = iter([self]) else: iter_parts = iter(self.get_all_subparts(include_self=True)) for p in iter_parts: all_as_iterable = chain(p.plates, p.beams, p.shapes, p.pipes, p.walls) physical_objects.append(all_as_iterable) if by_type is not None: res = filter(lambda x: type(x) is by_type, chain.from_iterable(physical_objects)) else: res = chain.from_iterable(physical_objects) if filter_by_guids is not None: res = filter(lambda x: x.guid in filter_by_guids, res) return res def beam_clash_check(self, margins=5e-5): """ For all beams in a Assembly get all beams touching or within the beam. Essentially a clash check is performed and it returns a dictionary of all beam ids and the touching beams. A margin to the beam volume can be included. :param margins: Add margins to the volume box (equal in all directions). Input is in meters. Can be negative. :return: A map generator for the list of beams and resulting intersecting beams """ from ada.core.clash_check import basic_intersect all_parts = self.get_all_subparts() + [self] all_beams = [bm for p in all_parts for bm in p.beams] return filter( None, [basic_intersect(bm, margins, all_parts) for bm in all_beams]) def move_all_mats_and_sec_here_from_subparts(self): for p in self.get_all_subparts(): self._materials += p.materials self._sections += p.sections p._materials = Materials(parent=p) p._sections = Sections(parent=p) self.sections.merge_sections_by_properties() self.materials.merge_materials_by_properties() def _flatten_list_of_subparts(self, p, list_of_parts=None): for value in p.parts.values(): list_of_parts.append(value) self._flatten_list_of_subparts(value, list_of_parts) def get_ifc_elem(self): if self._ifc_elem is None: self._ifc_elem = self._generate_ifc_elem() return self._ifc_elem def _generate_ifc_elem(self): from ada.ifc.write.write_levels import write_ifc_part return write_ifc_part(self) def _import_part_from_ifc(self, ifc_elem): convert = dict( site="IfcSite", space="IfcSpace", building="IfcBuilding", storey="IfcBuildingStorey", spatial="IfcSpatialZone", ) opposite = {val: key for key, val in convert.items()} pr_type = ifc_elem.is_a() return opposite[pr_type] def _on_import(self): """A method call that will be triggered when a Part is imported into an existing Assembly/Part""" raise NotImplementedError() def to_fem_obj( self, mesh_size: float, bm_repr=ElemType.LINE, pl_repr=ElemType.SHELL, shp_repr=ElemType.SOLID, options: GmshOptions = None, silent=True, interactive=False, use_quads=False, use_hex=False, ) -> FEM: from ada import Beam, Plate, Shape from ada.fem.meshing import GmshOptions, GmshSession options = GmshOptions(Mesh_Algorithm=8) if options is None else options masses: List[Shape] = [] with GmshSession(silent=silent, options=options) as gs: for obj in self.get_all_physical_objects(sub_elements_only=False): if type(obj) is Beam: gs.add_obj(obj, geom_repr=bm_repr.upper(), build_native_lines=False) elif type(obj) is Plate: gs.add_obj(obj, geom_repr=pl_repr.upper()) elif issubclass(type(obj), Shape) and obj.mass is not None: masses.append(obj) elif issubclass(type(obj), Shape): gs.add_obj(obj, geom_repr=shp_repr.upper()) else: logger.error( f'Unsupported object type "{obj}". Should be either plate or beam objects' ) # if interactive is True: # gs.open_gui() gs.split_plates_by_beams() gs.mesh(mesh_size, use_quads=use_quads, use_hex=use_hex) if interactive is True: gs.open_gui() fem = gs.get_fem() for mass_shape in masses: cog_absolute = mass_shape.placement.absolute_placement( ) + mass_shape.cog n = fem.nodes.add(Node(cog_absolute)) fem.add_mass(Mass(f"{mass_shape.name}_mass", [n], mass_shape.mass)) return fem def to_vis_mesh(self, export_config=None, auto_merge_by_color=True, opt_func: Callable = None) -> VisMesh: from ada.visualize.concept import PartMesh, VisMesh from ada.visualize.config import ExportConfig from ada.visualize.formats.assembly_mesh.write_objects_to_mesh import ( filter_mesh_objects, obj_to_mesh, ) from ada.visualize.formats.assembly_mesh.write_part_to_mesh import generate_meta if export_config is None: export_config = ExportConfig() all_obj_num = len( list(self.get_all_physical_objects(sub_elements_only=False))) print( f"Exporting {all_obj_num} physical objects to custom json format.") obj_num = 1 part_array = [] for p in self.get_all_subparts(include_self=True): if export_config.max_convert_objects is not None and obj_num > export_config.max_convert_objects: break obj_list = filter_mesh_objects( p.get_all_physical_objects(sub_elements_only=True), export_config) if obj_list is None: continue id_map = dict() for obj in obj_list: print( f'Exporting "{obj.name}" [{obj.get_assembly().name}] ({obj_num} of {all_obj_num})' ) res = obj_to_mesh(obj, export_config, opt_func=opt_func) if res is None: continue id_map[obj.guid] = res obj_num += 1 if export_config.max_convert_objects is not None and obj_num >= export_config.max_convert_objects: print( f'Maximum number of converted objects of "{export_config.max_convert_objects}" reached' ) break if id_map is None: print(f'Part "{p.name}" has no physical members. Skipping.') continue for inst in p.instances.values(): id_map[ inst.instance_ref. guid].instances = inst.to_list_of_custom_json_matrices() part_array.append(PartMesh(name=p.name, id_map=id_map)) amesh = VisMesh( name=self.name, project=self.metadata.get("project", "DummyProject"), world=part_array, meta=generate_meta(self, export_config), ) if auto_merge_by_color: return amesh.merge_objects_in_parts_by_color() return amesh @property def parts(self) -> dict[str, Part]: return self._parts @property def shapes(self) -> List[Shape]: return self._shapes @shapes.setter def shapes(self, value: List[Shape]): self._shapes = value @property def beams(self) -> Beams: return self._beams @beams.setter def beams(self, value: Beams): self._beams = value @property def plates(self) -> Plates: return self._plates @plates.setter def plates(self, value: Plates): self._plates = value @property def pipes(self) -> List[Pipe]: return self._pipes @pipes.setter def pipes(self, value: List[Pipe]): self._pipes = value @property def walls(self) -> List[Wall]: return self._walls @walls.setter def walls(self, value: List[Wall]): self._walls = value @property def nodes(self) -> Nodes: return self._nodes @property def fem(self) -> FEM: return self._fem @fem.setter def fem(self, value: FEM): value.parent = self self._fem = value @property def connections(self) -> Connections: return self._connections @property def sections(self) -> Sections: return self._sections @sections.setter def sections(self, value: Sections): self._sections = value @property def materials(self) -> Materials: return self._materials @materials.setter def materials(self, value: Materials): self._materials = value @property def colour(self): if self._colour is None: from random import randint self._colour = randint(0, 255) / 255, randint( 0, 255) / 255, randint(0, 255) / 255 return self._colour @colour.setter def colour(self, value): self._colour = value @property def properties(self): return self._props @property def placement(self) -> Placement: return self._placement @placement.setter def placement(self, value: Placement): self._placement = value @property def instances(self) -> Dict[Any, Instance]: return self._instances @property def units(self): return self._units @units.setter def units(self, value): if value != self._units: for bm in self.beams: bm.units = value for pl in self.plates: pl.units = value for pipe in self._pipes: pipe.units = value for shp in self._shapes: shp.units = value for wall in self.walls: wall.units = value for pen in self.penetrations: pen.units = value for p in self.get_all_subparts(): p.units = value self.sections.units = value self.materials.units = value self._units = value if type(self) is Assembly: assert isinstance(self, Assembly) from ada.ifc.utils import assembly_to_ifc_file self._ifc_file = assembly_to_ifc_file(self) @property def groups(self) -> Dict[str, Group]: return self._groups def __truediv__(self, other_object): from ada import Beam, Part, Pipe, Plate, Shape, Wall if type(other_object) in [list, tuple]: for obj in other_object: if type(obj) is Beam: self.add_beam(obj) elif type(obj) is Plate: self.add_plate(obj) elif type(obj) is Pipe: self.add_pipe(obj) elif issubclass(type(obj), Part): self.add_part(obj) elif issubclass(type(obj), Shape): self.add_shape(obj) elif type(obj) is Wall: self.add_wall(obj) else: raise NotImplementedError( f'"{type(obj)}" is not yet supported for smart append') elif issubclass(type(other_object), Part): self.add_part(other_object) elif type(other_object) is Beam: self.add_beam(other_object) elif type(other_object) is Plate: self.add_plate(other_object) elif type(other_object) is Pipe: self.add_pipe(other_object) elif issubclass(type(other_object), Shape): self.add_shape(other_object) else: raise NotImplementedError( f'"{type(other_object)}" is not yet supported for smart append' ) return self def __repr__(self): nbms = len(self.beams) + len( [bm for p in self.get_all_subparts() for bm in p.beams]) npls = len(self.plates) + len( [pl for p in self.get_all_subparts() for pl in p.plates]) npipes = len(self.pipes) + len( [pl for p in self.get_all_subparts() for pl in p.pipes]) nshps = len(self.shapes) + len( [shp for p in self.get_all_subparts() for shp in p.shapes]) nels = len(self.fem.elements) + len( [el for p in self.get_all_subparts() for el in p.fem.elements]) nnodes = len(self.fem.nodes) + len( [no for p in self.get_all_subparts() for no in p.fem.nodes]) return ( f'Part("{self.name}": Beams: {nbms}, Plates: {npls}, ' f"Pipes: {npipes}, Shapes: {nshps}, Elements: {nels}, Nodes: {nnodes})" )