Skip to content

Commit

Permalink
WIP: TimberAssembly -> TimberModel
Browse files Browse the repository at this point in the history
  • Loading branch information
chenkasirer committed Mar 5, 2024
1 parent 8bf00b2 commit 51b6f1d
Show file tree
Hide file tree
Showing 12 changed files with 234 additions and 298 deletions.
4 changes: 2 additions & 2 deletions src/compas_timber/assembly/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .assembly import TimberAssembly
from .assembly import TimberModel

__all__ = ["TimberAssembly"]
__all__ = ["TimberModel"]
134 changes: 26 additions & 108 deletions src/compas_timber/assembly/assembly.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
from compas.datastructures import Assembly
from compas.datastructures import AssemblyError
from compas_model.model import Model

from compas_timber.connections import Joint
from compas_timber.connections import BeamJoinningError
from compas_timber.parts import Beam
from compas_timber.connections import Joint


class TimberAssembly(Assembly):
class TimberModel(Model):
"""Represents a timber assembly containing beams and joints etc.
Attributes
Expand All @@ -15,19 +13,25 @@ class TimberAssembly(Assembly):
A list of beams assigned to this assembly.
joints : list(:class:`~compas_timber.connections.Joint`)
A list of joints assigned to this assembly.
part_keys : list(int)
A list of the keys of the parts included in this assembly.
beam_keys : list(int)
A list of the keys of the beams included in this assembly.
joint_keys : list(int)
A list of the keys of the joints included in this assembly.
topologies : list(dict)
A list of JointTopology for assembly. dict is: {"detected_topo": detected_topo, "beam_a_key": beam_a_key, "beam_b_key":beam_b_key} See :class:`~compas_timber.connections.JointTopology`.
"""

@classmethod
def __from_data__(cls, data):
model = super(TimberModel, cls).__from_data__(data)
for element in model.elementlist:
if isinstance(element, Beam):
model._beams.append(element)
if isinstance(element, Joint):
model._joints.append(element)
for joint in model.joints:
joint.add_features()
return model

def __init__(self, *args, **kwargs):
super(TimberAssembly, self).__init__()
super(TimberModel, self).__init__()
self._beams = []
self._joints = []
self._topologies = [] # added to avoid calculating multiple times
Expand All @@ -44,19 +48,6 @@ def __str__(self):
self.guid, len(self.beams), len(self.joints)
)

@classmethod
def __from_data__(cls, data):
assembly = super(TimberAssembly, cls).__from_data__(data)
for part in assembly.parts():
if isinstance(part, Beam):
assembly._beams.append(part)
if isinstance(part, Joint):
assembly._joints.append(part)
part.restore_beams_from_keys(assembly)
for joint in assembly._joints:
joint.add_features()
return assembly

@property
def beams(self):
return self._beams
Expand All @@ -65,33 +56,6 @@ def beams(self):
def joints(self):
return self._joints

@property
def part_keys(self):
return [part.key for part in self.parts()]

@property
def beam_keys(self):
return [beam.key for beam in self._beams]

@property
def joint_keys(self):
return [joint.key for joint in self._joints]

def contains(self, obj):
"""Returns True if this assembly contains the given object, False otherwise.
Parameters
----------
obj: :class:`~compas.data.Data`
The object to look for.
Returns
-------
bool
"""
return obj.guid in self._parts

def add_beam(self, beam):
"""Adds a Beam to this assembly.
Expand All @@ -106,12 +70,8 @@ def add_beam(self, beam):
The graph key identifier of the added beam.
"""
if beam in self._beams:
raise AssemblyError("This beam has already been added to this assembly!")
key = self.add_part(part=beam, type="part_beam")
_ = self.add_element(beam)
self._beams.append(beam)
beam.is_added_to_assembly = True
return key

def add_joint(self, joint, parts):
"""Add a joint object to the assembly.
Expand All @@ -130,31 +90,15 @@ def add_joint(self, joint, parts):
The identifier of the joint in the current assembly graph.
"""
self._validate_joining_operation(joint, parts)
# self._validate_joining_operation(joint, parts)
# create an unconnected node in the graph for the joint object
key = self.add_part(part=joint, type="joint")
# joint.assembly = self
# TODO: for each two parts pairwise do.. in the meantime, allow only two parts
if len(parts) != 2:
raise ValueError("Expected 2 parts. Got instead: {}".format(len(parts)))
a, b = parts
_ = self.add_interaction(a, b, interaction=joint)
self._joints.append(joint)

# adds links to the beams
for part in parts:
self.add_connection(part, joint)
return key

def _validate_joining_operation(self, joint, parts):
if not parts:
raise AssemblyError("Cannot add this joint to assembly: no parts given.")

if self.contains(joint):
raise AssemblyError("This joint has already been added to this assembly.")

# TODO: rethink this assertion, maybe it should be possible to have more than 1 joint for the same set of parts
if not [self.contains(part) for part in parts]:
raise AssemblyError("Cannot add this joint to assembly: some of the parts are not in this assembly.")

if self.are_parts_joined(parts):
raise BeamJoinningError(beams=parts, joint=joint, debug_info="Beams are already joined.")

def remove_joint(self, joint):
"""Removes this joint object from the assembly.
Expand All @@ -164,36 +108,10 @@ def remove_joint(self, joint):
The joint to remove.
"""
del self._parts[joint.guid]
self.graph.delete_node(joint.key)
self._joints.remove(joint) # TODO: make it automatic
joint.assembly = None # TODO: should not be needed
# TODO: distroy joint?
a, b = joint.parts
self.remove_interaction(a, b)
self._joints.remove(joint)

def are_parts_joined(self, parts):
"""Checks if there is already a joint defined for the same set of parts.
Parameters
----------
parts : list(:class:`~compas.datastructure.Part`)
The parts to check.
Returns
-------
bool
"""
n = len(parts)
neighbor_keys = [set(self.graph.neighborhood(self._parts[part.guid], ring=1)) for part in parts]
for i in range(n - 1):
nki = neighbor_keys[i]
for j in range(i + 1, n):
nkj = neighbor_keys[j]
nkx = nki.intersection(nkj)
for x in nkx:
if self.graph.node[x]["type"] == "joint":
return True
return False

def set_topologies(self, topologies):
self._topologies = topologies
Expand Down
15 changes: 5 additions & 10 deletions src/compas_timber/connections/joint.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from compas.geometry import angle_vectors
from compas.geometry import intersection_line_line

from compas_model.interactions import Interaction

from .solver import JointTopology


Expand Down Expand Up @@ -34,7 +36,7 @@ def __init__(self, beams, joint, debug_info=None, debug_geometries=None):
self.debug_geometries = debug_geometries or []


class Joint(Data):
class Joint(Interaction):
"""Base class for a joint connecting two beams.
This is a base class and should not be instantiated directly.
Expand All @@ -51,15 +53,8 @@ class Joint(Data):

SUPPORTED_TOPOLOGY = JointTopology.TOPO_UNKNOWN

def __init__(self, frame=None, key=None):
super(Joint, self).__init__()
self.frame = frame or Frame.worldXY()
self.key = key
self.attributes = {}

@property
def __data__(self):
return {"frame": self.frame.__data__, "key": self.key, "beams": [beam.key for beam in self.beams]}
def __init__(self):
super(Joint, self).__init__(name=self.__class__.__name__)

@property
def beams(self):
Expand Down
44 changes: 14 additions & 30 deletions src/compas_timber/connections/l_butt.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ class LButtJoint(Joint):

SUPPORTED_TOPOLOGY = JointTopology.TOPO_L

@property
def __data__(self):
data = super(LButtJoint, self).__data__
data["main_beam_key"] = self.main_beam_key
data["cross_beam_key"] = self.cross_beam_key
data["small_beam_butts"] = self.small_beam_butts
data["modify_cross"] = self.modify_cross
data["reject_i"] = self.reject_i
return data

def __init__(
self, main_beam=None, cross_beam=None, small_beam_butts=False, modify_cross=True, reject_i=False, **kwargs
):
Expand All @@ -50,38 +60,13 @@ def __init__(

self.main_beam = main_beam
self.cross_beam = cross_beam
self.main_beam_key = main_beam.key if main_beam else None
self.cross_beam_key = cross_beam.key if cross_beam else None
self.main_beam_key = main_beam.guid if main_beam else None
self.cross_beam_key = cross_beam.guid if cross_beam else None
self.modify_cross = modify_cross
self.small_beam_butts = small_beam_butts
self.reject_i = reject_i
self.features = []

@property
def __data__(self):
data_dict = {
"main_beam_key": self.main_beam_key,
"cross_beam_key": self.cross_beam_key,
"small_beam_butts": self.small_beam_butts,
"modify_cross": self.modify_cross,
"reject_i": self.reject_i,
}
data_dict.update(super(LButtJoint, self).__data__)
return data_dict

@classmethod
def __from_data__(cls, value):
instance = cls(
frame=Frame.__from_data__(value["frame"]),
key=value["key"],
small_beam_butts=value["small_beam_butts"],
modify_cross=value["modify_cross"],
reject_i=value["reject_i"],
)
instance.main_beam_key = value["main_beam_key"]
instance.cross_beam_key = value["cross_beam_key"]
return instance

@property
def beams(self):
return [self.main_beam, self.cross_beam]
Expand Down Expand Up @@ -142,14 +127,13 @@ def add_features(self):

if self.modify_cross:
self.cross_beam.add_blank_extension(
start_cross + extension_tolerance, end_cross + extension_tolerance, self.key
start_cross + extension_tolerance, end_cross + extension_tolerance, self.guid
)
f_cross = CutFeature(cross_cutting_plane)
self.cross_beam.add_features(f_cross)
self.features.append(f_cross)

self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.key)

self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.guid)
f_main = CutFeature(main_cutting_plane)
self.main_beam.add_features(f_main)
self.features.append(f_main)
8 changes: 4 additions & 4 deletions src/compas_timber/connections/l_halflap.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ class LHalfLapJoint(LapJoint):

SUPPORTED_TOPOLOGY = JointTopology.TOPO_L

def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5, frame=None, key=None):
super(LHalfLapJoint, self).__init__(main_beam, cross_beam, flip_lap_side, cut_plane_bias, frame, key)
def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5):
super(LHalfLapJoint, self).__init__(main_beam, cross_beam, flip_lap_side, cut_plane_bias)

@property
def joint_type(self):
Expand All @@ -69,9 +69,9 @@ def add_features(self):
start_cross, end_cross = self.cross_beam.extension_to_plane(cross_cutting_frame)

extension_tolerance = 0.01 # TODO: this should be proportional to the unit used
self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.key)
self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.guid)
self.cross_beam.add_blank_extension(
start_cross + extension_tolerance, end_cross + extension_tolerance, self.key
start_cross + extension_tolerance, end_cross + extension_tolerance, self.guid
)

self.main_beam.add_features(MillVolume(negative_brep_main_beam))
Expand Down
Loading

0 comments on commit 51b6f1d

Please sign in to comment.