Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changed beam to generic element #263

Merged
merged 17 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
obucklin marked this conversation as resolved.
Show resolved Hide resolved
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
Loading