Skip to content

Commit

Permalink
fix: bug in tapered beam section handling (#123)
Browse files Browse the repository at this point in the history
  • Loading branch information
Krande authored Dec 20, 2024
1 parent 293f7c9 commit dd095f7
Show file tree
Hide file tree
Showing 38 changed files with 1,298 additions and 772 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# SCM syntax highlighting
pixi.lock linguist-language=YAML linguist-generated=true
4 changes: 0 additions & 4 deletions examples/simple_beam.py

This file was deleted.

19 changes: 19 additions & 0 deletions examples/various_beams.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import ada
from ada.api.beams.base_bm import TaperTypes

bm = ada.Beam("bm1", (0, 0, 0), (1, 0, 0), "IPE300")
beams = [bm]

bm2 = ada.BeamTapered("bm2", (0, 1, 0), (1, 1, 0), "IPE600", "IPE300")
beams.append(bm2)

bm3 = ada.BeamTapered("bm3", (0, 2, 0), (1, 2, 0), "IPE600", "IPE300", taper_type=TaperTypes.FLUSH_TOP)
beams.append(bm3)

bm4 = ada.BeamTapered("bm4", (0, 3, 0), (1, 3, 0), "IPE600", "IPE300", taper_type=TaperTypes.FLUSH_BOTTOM)
beams.append(bm4)

pl = ada.Plate("pl1", [(0, 0), (1, 0), (1, 3), (0, 3)], 0.01)
plates = [pl]

(ada.Part("Beams") / (beams + plates)).show()
4 changes: 2 additions & 2 deletions pixi.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ test = "pytest tests --ignore=tests/profiling --ignore=tests/fem --durations=0"
test-core = "pytest tests --ignore=tests/profiling --ignore=tests/fem --ignore=tests/full --durations=0"
test-all = "pytest tests --durations=0"
test-fem = "pytest tests --ignore=tests/profiling --ignore=tests/core --ignore=tests/full --durations=0"

fem-doc = { cmd="python build_verification_report.py true true --export-format=docx", cwd="./tests/fem" }
fem-doc-dev = { cmd="python doc_live_html.py", cwd="./tests/fem" }

Expand Down
42 changes: 41 additions & 1 deletion src/ada/api/beams/base_bm.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import numpy as np

import ada
import ada.api.beams.geom_beams as geo_conv
from ada.api.beams.helpers import BeamConnectionProps
from ada.api.bounding_box import BoundingBox
Expand All @@ -24,7 +25,9 @@
vector_length,
)
from ada.geom import Geometry
from ada.geom.curves import IndexedPolyCurve
from ada.geom.placement import Direction
from ada.geom.surfaces import ArbitraryProfileDef
from ada.materials import Material
from ada.materials.utils import get_material
from ada.sections import Section
Expand Down Expand Up @@ -497,7 +500,44 @@ def taper_type(self, value: TaperTypes):
self._taper_type = value

def solid_geom(self) -> Geometry:
return geo_conv.straight_tapered_beam_to_geom(self)
geo = geo_conv.straight_tapered_beam_to_geom(self)
if self.taper_type == TaperTypes.CENTERED:
return geo

if self.up.is_equal(ada.Direction(0, 0, 1)):
off_dir = -1
elif self.up.is_equal(ada.Direction(0, 0, -1)):
off_dir = 1
else:
logger.warning("Tapered beam is not aligned with global z-axis")
off_dir = 0

if self.taper_type == TaperTypes.FLUSH_TOP:
offset_dir_1 = ada.Direction(0, off_dir * self.section.h / 2)
offset_dir_2 = ada.Direction(0, off_dir * self.taper.h / 2)
elif self.taper_type == TaperTypes.FLUSH_BOTTOM:
offset_dir_1 = ada.Direction(0, -off_dir * self.section.h / 2)
offset_dir_2 = ada.Direction(0, -off_dir * self.taper.h / 2)
else:
raise ValueError(f"Unknown taper type {self.taper_type}")

profile_1 = geo.geometry.swept_area

if isinstance(profile_1, ArbitraryProfileDef):
if isinstance(profile_1.outer_curve, IndexedPolyCurve):
for curve in profile_1.outer_curve.segments:
curve.start = ada.Point(curve.start + offset_dir_1)
curve.end = ada.Point(curve.end + offset_dir_1)

profile_2 = geo.geometry.end_swept_area

if isinstance(profile_2, ArbitraryProfileDef):
if isinstance(profile_2.outer_curve, IndexedPolyCurve):
for curve in profile_2.outer_curve.segments:
curve.start = ada.Point(curve.start + offset_dir_2)
curve.end = ada.Point(curve.end + offset_dir_2)

return geo

def shell_geom(self) -> Geometry:
geom = geo_conv.straight_tapered_beam_to_geom(self, is_solid=False)
Expand Down
22 changes: 18 additions & 4 deletions src/ada/api/beams/geom_beams.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,16 @@ def straight_tapered_beam_to_geom(beam: BeamTapered, is_solid=True) -> Geometry:
return ibeam_taper_to_geom(beam)
else:
return ibeam_taper_to_face_geom(beam)
elif beam.section.type == beam.section.TYPES.BOX:
elif beam.section.type == beam.section.TYPES.TPROFILE:
if is_solid:
return boxbeam_taper_to_geom(beam)
return tbeam_taper_to_geom(beam)
else:
raise NotImplementedError("Box beam taper to face geometry not implemented")
return ibeam_taper_to_face_geom(beam)
elif beam.section.type in (beam.section.TYPES.BOX, beam.section.TYPES.POLY):
if is_solid:
return arbitrary_section_profile_taper_to_geom(beam)
else:
raise NotImplementedError("Arbitrary section profile beam taper to face geometry not implemented")
else:
raise NotImplementedError(f"Beam section type {beam.section.type} not implemented")

Expand Down Expand Up @@ -121,7 +126,7 @@ def section_to_arbitrary_profile_def_with_voids(section: Section, solid=True) ->
return geo_su.ArbitraryProfileDef(profile_type, outer_curve, inner_curves, profile_name=section.name)


def boxbeam_taper_to_geom(beam: BeamTapered) -> Geometry:
def arbitrary_section_profile_taper_to_geom(beam: BeamTapered) -> Geometry:
profile1 = section_to_arbitrary_profile_def_with_voids(beam.section)
profile2 = section_to_arbitrary_profile_def_with_voids(beam.taper)

Expand All @@ -139,6 +144,15 @@ def ibeam_taper_to_geom(beam: BeamTapered) -> Geometry:
return Geometry(beam.guid, geom, beam.color)


def tbeam_taper_to_geom(beam: BeamTapered) -> Geometry:
profile1 = section_to_arbitrary_profile_def_with_voids(beam.section)
profile2 = section_to_arbitrary_profile_def_with_voids(beam.taper)

place = Axis2Placement3D(location=beam.n1.p, axis=beam.xvec, ref_direction=beam.yvec)
geom = geo_so.ExtrudedAreaSolidTapered(profile1, place, beam.length, Direction(0, 0, 1), profile2)
return Geometry(beam.guid, geom, beam.color)


def ibeam_taper_to_face_geom(beam: BeamTapered) -> Geometry:
profile1 = section_to_arbitrary_profile_def_with_voids(beam.section, solid=False)
profile2 = section_to_arbitrary_profile_def_with_voids(beam.taper, solid=False)
Expand Down
23 changes: 18 additions & 5 deletions src/ada/api/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,8 @@ def name_map(self) -> dict[str, Section]:
return self._name_map

def add(self, section: Section) -> Section:
from ada import BeamTapered

if section.name is None:
raise Exception("Name is not allowed to be None.")

Expand All @@ -722,21 +724,32 @@ def add(self, section: Section) -> Section:
index = self._sections.index(section)
existing_section = self._sections[index]
for elem in section.refs:
elem.section = existing_section
if isinstance(elem, BeamTapered):
if existing_section == elem.section:
elem.section = existing_section
else:
elem.taper = existing_section
else:
elem.section = existing_section
if elem not in existing_section.refs:
existing_section.refs.append(elem)
return existing_section

if section.name in self._name_map.keys():
logger.info(f'Section with same name "{section.name}" already exists. Will use that section instead')
existing_section = self._name_map[section.name]
existing_section: Section = self._name_map[section.name]
for elem in section.refs:
if section == elem.section:
elem.section = existing_section
if isinstance(elem, BeamTapered):
if existing_section.equal_props(elem.section):
elem.section = existing_section
elif existing_section.equal_props(elem.taper):
elem.taper = existing_section
else:
elem.taper = existing_section
if existing_section.equal_props(elem.section):
elem.section = existing_section
if elem not in existing_section.refs:
existing_section.refs.append(elem)

return existing_section

if section.id is None:
Expand Down
4 changes: 3 additions & 1 deletion src/ada/api/spatial/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,9 @@ def ifc_store(self) -> IfcStore:
from ada.cadit.ifc.store import IfcStore
from ada.cadit.ifc.utils import assembly_to_ifc_file

self._ifc_file = assembly_to_ifc_file(self)
f = assembly_to_ifc_file(self)

self._ifc_file = f
self._ifc_store = IfcStore(assembly=self)

return self._ifc_store
Expand Down
13 changes: 9 additions & 4 deletions src/ada/api/spatial/part.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def __init__(
self._presentation_layers = PresentationLayers()
self.fem = FEM(name + "-1", parent=self) if fem is None else fem

def add_beam(self, beam: Beam, add_to_layer: str = None) -> Beam:
def add_beam(self, beam: Beam, add_to_layer: str = None) -> Beam | BeamTapered:
if beam.units != self.units:
beam.units = self.units
beam.parent = self
Expand Down Expand Up @@ -545,9 +545,14 @@ def consolidate_sections(self, include_self=True):
if elem not in res.refs:
res.refs.append(elem)
if isinstance(elem, (Beam, FemSection)):
if isinstance(elem, BeamTapered) and sec.guid == elem.taper.guid:
if isinstance(elem, BeamTapered) and res.guid == elem.taper.guid:
if not res.equal_props(elem.taper):
raise ValueError(f"Section {res} and {elem.taper} have different properties")
elem.taper = res
elem.section = res
else:
if not res.equal_props(elem.section):
raise ValueError(f"Section {res} and {elem.section} have different properties")
elem.section = res
else:
raise NotImplementedError(f"Not yet support section {type(elem)=}")

Expand Down Expand Up @@ -634,7 +639,7 @@ def get_all_physical_objects(
filter_by_guids: list[str] = None,
pipe_to_segments=False,
by_metadata: dict = None,
) -> Iterable[Beam | Plate | Wall | Pipe | Shape]:
) -> Iterable[Beam | BeamTapered | Plate | Wall | Pipe | Shape]:
physical_objects = []
if sub_elements_only:
iter_parts = iter([self])
Expand Down
14 changes: 11 additions & 3 deletions src/ada/cadit/gxml/write/write_beams.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import itertools
import xml.etree.ElementTree as ET
from typing import TYPE_CHECKING

Expand All @@ -11,16 +12,18 @@


def add_beams(root: ET.Element, part: Part, sw: SatWriter = None):
from ada import Beam
from ada import Beam, BeamTapered

for beam in part.get_all_physical_objects(by_type=Beam):
iter_beams = part.get_all_physical_objects(by_type=Beam)
iter_taper = part.get_all_physical_objects(by_type=BeamTapered)

for beam in itertools.chain(iter_beams, iter_taper):
add_straight_beam(beam, root)


def add_straight_beam(beam: Beam, xml_root: ET.Element):
structure_elem = ET.SubElement(xml_root, "structure")
straight_beam = ET.SubElement(structure_elem, "straight_beam", {"name": beam.name})
# add_curve_orientation(beam, straight_beam)
straight_beam.append(add_local_system(beam.xvec, beam.yvec, beam.up))
straight_beam.append(add_segments(beam))
curve_offset = ET.SubElement(straight_beam, "curve_offset")
Expand All @@ -38,8 +41,13 @@ def add_curve_orientation(beam: Beam, straight_beam: ET.Element):


def add_segments(beam: Beam):
from ada import BeamTapered

segments = ET.Element("segments")
props = dict(index="1", section_ref=beam.section.name, material_ref=beam.material.name)
if isinstance(beam, BeamTapered):
props.update(dict(section_ref=f"{beam.section.name}_{beam.taper.name}"))

straight_segment = ET.SubElement(segments, "straight_segment", props)

d = ["x", "y", "z"]
Expand Down
Loading

0 comments on commit dd095f7

Please sign in to comment.