Skip to content

Commit

Permalink
Merge pull request #263 from gramaziokohler/make_model_elements_generic
Browse files Browse the repository at this point in the history
Changed beam to generic element
  • Loading branch information
chenkasirer authored Sep 16, 2024
2 parents f665c80 + e43c35a commit 75769e4
Show file tree
Hide file tree
Showing 14 changed files with 109 additions and 83 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

* Fixed wrong image file paths in the Documentation.
* Changed `TimberModel.beams` to return generator of `Beam` elements.
* Changed `TimberModel.walls` to return generator of `Wall` elements.
* Changed `TimberModel.plates` to return generator of `Plate` elements.
* Changed `TimberModel.joints` to return generator of `Joint` elements.

### Removed

Expand Down
2 changes: 1 addition & 1 deletion examples/model/0001_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def create_viewer():
for line in lines:
model.add_element(Beam.from_centerline(centerline=line, height=HEIGHT, width=WIDTH))

beams = model.beams
beams = list(model.beams)

# Assign joints - Frame - Frame
LMiterJoint.create(model, beams[5], beams[3])
Expand Down
2 changes: 1 addition & 1 deletion examples/model/0002_stand.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def create_viewer():

# find neighboring beams
solver = ConnectionSolver()
beam_pairs = solver.find_intersecting_pairs(model.beams, rtree=True)
beam_pairs = solver.find_intersecting_pairs(list(model.beams), rtree=True)

for pair in beam_pairs:
beam_a, beam_b = pair
Expand Down
4 changes: 2 additions & 2 deletions examples/model/0003_wall.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ def create_viewer():

wall_frame = Frame.worldXY()
model = TimberModel()
model.add_element(Wall(wall_frame, 3000, 140, 2000))
model.add_element(Wall(3000, 140, 2000, frame=wall_frame))

# setup the viewer
viewer = create_viewer()

wall = model.walls[0]
wall = list(model.walls)[0]

# draw centerline
viewer.scene.add(wall.origin)
Expand Down
4 changes: 2 additions & 2 deletions src/compas_timber/connections/butt_joint.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ def beams(self):

def restore_beams_from_keys(self, model):
"""After de-serialization, restors references to the main and cross beams saved in the model."""
self.main_beam = model.beam_by_guid(self.main_beam_guid)
self.cross_beam = model.beam_by_guid(self.cross_beam_guid)
self.main_beam = model.element_by_guid(self.main_beam_guid)
self.cross_beam = model.element_by_guid(self.cross_beam_guid)

def side_surfaces_cross(self):
assert self.main_beam and self.cross_beam
Expand Down
4 changes: 2 additions & 2 deletions src/compas_timber/connections/t_butt.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, birdsmouth=Fal

def restore_beams_from_keys(self, model):
"""After de-serialization, restores references to the main and cross beams saved in the model."""
self.main_beam = model.beam_by_guid(self.main_beam_guid)
self.cross_beam = model.beam_by_guid(self.cross_beam_guid)
self.main_beam = model.element_by_guid(self.main_beam_guid)
self.cross_beam = model.element_by_guid(self.cross_beam_guid)

def add_extensions(self):
"""Calculates and adds the necessary extensions to the beams.
Expand Down
2 changes: 1 addition & 1 deletion src/compas_timber/design/wall_from_surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ def create_model(self):
model.add_element(element)
topologies = []
solver = ConnectionSolver()
found_pairs = solver.find_intersecting_pairs(model.beams, rtree=True, max_distance=self.dist_tolerance)
found_pairs = solver.find_intersecting_pairs(list(model.beams), rtree=True, max_distance=self.dist_tolerance)
for pair in found_pairs:
beam_a, beam_b = pair
detected_topo, beam_a, beam_b = solver.find_topology(beam_a, beam_b, max_distance=self.dist_tolerance)
Expand Down
2 changes: 1 addition & 1 deletion src/compas_timber/design/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ def is_near_end(t, tol=tol):


def set_defaul_joints(model, x_default="x-lap", t_default="t-butt", l_default="l-miter"):
beams = model.beams
beams = list(model.beams)
n = len(beams)

connectivity = {"L": [], "T": [], "X": []}
Expand Down
2 changes: 1 addition & 1 deletion src/compas_timber/ghpython/components/CT_Model/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def RunScript(self, Elements, JointRules, Features, MaxDistance, CreateGeometry)

topologies = []
solver = ConnectionSolver()
found_pairs = solver.find_intersecting_pairs(Model.beams, rtree=True, max_distance=MaxDistance)
found_pairs = solver.find_intersecting_pairs(list(Model.beams), rtree=True, max_distance=MaxDistance)
for pair in found_pairs:
beam_a, beam_b = pair
detected_topo, beam_a, beam_b = solver.find_topology(beam_a, beam_b, max_distance=MaxDistance)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from compas_timber.design import DebugInfomation
from compas_timber.design import SurfaceModel


class SurfaceModelComponent(component):
def RunScript(self, surface, stud_spacing, beam_width, frame_depth, z_axis, options, CreateGeometry=False):
# minimum inputs required
Expand Down
103 changes: 47 additions & 56 deletions src/compas_timber/model/model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import compas

if not compas.IPY:
from typing import Generator # noqa: F401

from compas.geometry import Point
from compas_model.models import Model

from compas_timber.connections import Joint
from compas_timber.elements import Beam
from compas_timber.elements import Plate
from compas_timber.elements import Wall
Expand All @@ -13,69 +19,70 @@ class TimberModel(Model):
Attributes
----------
beams : list(:class:`~compas_timber.elements.Beam`)
A list of beams assigned to this model.
beams : Generator[:class:`~compas_timber.elements.Beam`]
A Generator object of all beams assigned to this model.
plates : Generator[:class:`~compas_timber.elements.Plate`]
A Generator object of all plates assigned to this model.
joints : Generator[:class:`~compas_timber.connections.Joint`]
A Generator object of all joints assigned to this model.
walls : Generator[:class:`~compas_timber.elements.Wall`]
A Generator object of all walls assigned to this model.
center_of_mass : :class:`~compas.geometry.Point`
The calculated center of mass of the model.
joints : list(:class:`~compas_timber.connections.Joint`)
A list of joints assigned to this model.
topologies : list(dict)
A list of JointTopology for model. dict is: {"detected_topo": detected_topo, "beam_a_key": beam_a_key, "beam_b_key":beam_b_key}
See :class:`~compas_timber.connections.JointTopology`.
volume : float
The calculated total volume of the model.
walls : list(:class:~compas_timber.elements.Wall)
A list of walls assigned to this model.
"""

@classmethod
def __from_data__(cls, data):
model = super(TimberModel, cls).__from_data__(data)
for element in model.elements():
if isinstance(element, Beam):
model._beams.append(element)
elif isinstance(element, Plate):
model._plates.append(element)
elif isinstance(element, Wall):
model._walls.append(element)
for interaction in model.interactions():
model._joints.append(interaction)
interaction.restore_beams_from_keys(model)
interaction.restore_beams_from_keys(model) # type: ignore
return model

def __init__(self, *args, **kwargs):
super(TimberModel, self).__init__()
self._beams = []
self._plates = []
self._walls = []
self._joints = []
self._topologies = [] # added to avoid calculating multiple times

def __str__(self):
return "TimberModel ({}) with {} beam(s), {} plate(s) and {} joint(s).".format(
self.guid, len(self.beams), len(self._plates), len(self.joints)
# type: () -> str
return "TimberModel ({}) with {} beam(s) and {} joint(s).".format(
str(self.guid), len(list(self.elements())), len(list(self.joints))
)

@property
def beams(self):
# type: () -> list[Beam]
return self._beams
# type: () -> Generator[Beam, None, None]
# TODO: think about using `filter` instead of all these
# TODO: add `is_beam`, `is_plate` etc. to avoid using `isinstance`
for element in self.elements():
if isinstance(element, Beam):
yield element

@property
def plates(self):
# type: () -> list[Plate]
return self._plates
# type: () -> Generator[Plate, None, None]
for element in self.elements():
if isinstance(element, Plate):
yield element

@property
def joints(self):
# type: () -> list[Joint]
return self._joints
# type: () -> Generator[Joint, None, None]
for interaction in self.interactions():
if isinstance(interaction, Joint):
yield interaction # TODO: consider if there are other interaction types...

@property
def walls(self):
# type: () -> list[Wall]
return self._walls
# type: () -> Generator[Wall, None, None]
for element in self.elements():
if isinstance(element, Wall):
yield element

@property
def topologies(self):
Expand All @@ -87,10 +94,11 @@ def center_of_mass(self):
total_vol = 0
total_position = Point(0, 0, 0)

for beam in self._beams:
vol = beam.blank.volume
point = beam.blank_frame.point
point += beam.blank_frame.xaxis * (beam.blank_length / 2.0)
for element in self.elements():
vol = (
element.obb.volume
) # TODO: include material density...? this uses volume as proxy for mass, which assumes all parts have equal density
point = element.obb.frame.point
total_vol += vol
total_position += point * vol

Expand All @@ -99,9 +107,9 @@ def center_of_mass(self):
@property
def volume(self):
# type: () -> float
return sum([beam.blank.volume for beam in self._beams]) # TODO: add volume for plates
return sum([element.obb.volume for element in self.elements()])

def beam_by_guid(self, guid):
def element_by_guid(self, guid):
# type: (str) -> Beam
"""Get a beam by its unique identifier.
Expand All @@ -112,27 +120,12 @@ def beam_by_guid(self, guid):
Returns
-------
:class:`~compas_timber.elements.Beam`
The beam with the specified GUID.
:class:`~compas_model.elements.Element`
The element with the specified GUID.
"""
return self._guid_element[guid]

def add_element(self, element, **kwargs):
# TODO: make the distincion in the properties rather than here
# then get rid of this overrloading altogether.
node = super(TimberModel, self).add_element(element, **kwargs)

if isinstance(element, Beam):
self._beams.append(element)
elif isinstance(element, Wall):
self._walls.append(element)
elif isinstance(element, Plate):
self._plates.append(element)
else:
raise NotImplementedError("Element type not supported: {}".format(type(element)))
return node

def add_joint(self, joint, beams):
# type: (Joint, tuple[Beam]) -> None
"""Add a joint object to the model.
Expand All @@ -150,7 +143,6 @@ def add_joint(self, joint, beams):
raise ValueError("Expected 2 parts. Got instead: {}".format(len(beams)))
a, b = beams
_ = self.add_interaction(a, b, interaction=joint)
self._joints.append(joint)

def remove_joint(self, joint):
# type: (Joint) -> None
Expand All @@ -162,9 +154,8 @@ def remove_joint(self, joint):
The joint to remove.
"""
a, b = joint.beams
self.remove_interaction(a, b)
self._joints.remove(joint)
a, b = joint.beams # TODO: make this generic elements not beams
super(TimberModel, self).remove_interaction(a, b) # TODO: Can two elements share more than one interaction?

def set_topologies(self, topologies):
"""TODO: calculate the topologies inside the model using the ConnectionSolver."""
Expand Down
8 changes: 4 additions & 4 deletions tests/compas_timber/test_joint.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ def test_create(mocker):
model.add_element(b2)
_ = TButtJoint.create(model, b1, b2)

assert len(model.beams) == 2
assert len(model.joints) == 1
assert len(list(model.beams)) == 2
assert len(list(model.joints)) == 1


def test_deepcopy(mocker, t_topo_beams):
Expand All @@ -89,7 +89,7 @@ def test_deepcopy(mocker, t_topo_beams):
assert model_copy.beams
assert model_copy.joints

t_butt_copy = model_copy.joints[0]
t_butt_copy = list(model_copy.joints)[0]
assert t_butt_copy is not t_butt
assert t_butt_copy.beams

Expand Down Expand Up @@ -216,7 +216,7 @@ def test_find_neighbors(example_model):
set([3, 5]),
set([5, 6]),
]
result = find_neighboring_beams(example_model.beams)
result = find_neighboring_beams(list(example_model.beams))
# beam objects => sets of keys for easy comparison
key_sets = []
for pair in result:
Expand Down
Loading

0 comments on commit 75769e4

Please sign in to comment.