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

Joint boilerplate #234

Merged
merged 10 commits into from
Mar 20, 2024
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

* Reduced some boilerplate code in `Joint` subclasses.
* Added argument `beams` to `Joint.__init__()` which expects tuple containing beams from implementing class instance.

### Removed

* Removed `joint_type` attributes from all `Joint` classes.
* Removed argument `cutoff` from `LMiterJoint` as it was not used anywhere.
* Removed argument `gap` from `TButtJoint` as it was not used anywhere.
* Removed argument `gap` from `FrenchRidgeLap` as it was not used anywhere.

## [0.7.0] 2024-02-15

Expand Down
18 changes: 4 additions & 14 deletions src/compas_timber/connections/french_ridge_lap.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,12 @@ class FrenchRidgeLapJoint(Joint):

SUPPORTED_TOPOLOGY = JointTopology.TOPO_L

def __init__(self, beam_a=None, beam_b=None, gap=0.0, frame=None, key=None):
super(FrenchRidgeLapJoint, self).__init__(frame=frame, key=key)
def __init__(self, beam_a=None, beam_b=None, **kwargs):
super(FrenchRidgeLapJoint, self).__init__(beams=(beam_a, beam_b), **kwargs)
self.beam_a = beam_a
self.beam_b = beam_b
self.beam_a_key = beam_a.key if beam_a else None
self.beam_b_key = beam_b.key if beam_b else None
self.gap = gap
self.features = []
self.reference_face_indices = {}
self.check_geometry()

Expand All @@ -54,26 +52,17 @@ def __data__(self):
data_dict = {
"beam_a_key": self.beam_a_key,
"beam_b_key": self.beam_b_key,
"gap": self.gap,
}
data_dict.update(super(FrenchRidgeLapJoint, self).__data__)
return data_dict

@classmethod
def __from_data__(cls, value):
instance = cls(frame=Frame.__from_data__(value["frame"]), key=value["key"], gap=value["gap"])
instance = cls(frame=Frame.__from_data__(value["frame"]), key=value["key"])
instance.beam_a_key = value["beam_a_key"]
instance.beam_b_key = value["beam_b_key"]
return instance

@property
def beams(self):
return [self.beam_a, self.beam_b]

@property
def joint_type(self):
return "French Ridge Lap"

@property
def cutting_plane_top(self):
_, cfr = self.get_face_most_towards_beam(self.beam_a, self.beam_b, ignore_ends=True)
Expand All @@ -89,6 +78,7 @@ def restore_beams_from_keys(self, assemly):
"""After de-serialization, restores references to the top and bottom beams saved in the assembly."""
self.beam_a = assemly.find_by_key(self.beam_a_key)
self.beam_b = assemly.find_by_key(self.beam_b_key)
self._beams = (self.beam_a, self.beam_b)

def check_geometry(self):
"""
Expand Down
16 changes: 13 additions & 3 deletions src/compas_timber/connections/joint.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,29 @@ class Joint(Data):

Attributes
----------
beams : list(:class:`~compas_timber.parts.Beam`)
beams : tuple(:class:`~compas_timber.parts.Beam`)
The beams joined by this joint.
ends : dict(:class:`~compas_timber.parts.Beam`, str)
A map of which end of each beam is joined by this joint.
frame : :class:`~compas.geometry.Frame`
The frame of the joint.
key : str
A unique identifier for this joint.
features : list(:class:`~compas_timber.parts.Feature`)
A list of features that were added to the beams by this joint.
attributes : dict
A dictionary of additional attributes for this joint.

"""

SUPPORTED_TOPOLOGY = JointTopology.TOPO_UNKNOWN

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

@property
Expand All @@ -63,7 +73,7 @@ def __data__(self):

@property
def beams(self):
raise NotImplementedError
return self._beams

def add_features(self):
"""Adds the features defined by this joint to affected beam(s).
Expand Down
27 changes: 12 additions & 15 deletions src/compas_timber/connections/l_butt.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ class LButtJoint(Joint):

Parameters
----------
assembly : :class:`~compas_timber.assembly.TimberAssembly`
The assembly associated with the beams to be joined.
main_beam : :class:`~compas_timber.parts.Beam`
The main beam to be joined.
cross_beam : :class:`~compas_timber.parts.Beam`
Expand All @@ -30,10 +28,16 @@ class LButtJoint(Joint):

Attributes
----------
beams : list(:class:`~compas_timber.parts.Beam`)
The beams joined by this joint.
joint_type : str
A string representation of this joint's type.
main_beam : :class:`~compas_timber.parts.Beam`
The main beam to be joined.
cross_beam : :class:`~compas_timber.parts.Beam`
The cross beam to be joined.
small_beam_butts : bool, default False
If True, the beam with the smaller cross-section will be trimmed. Otherwise, the main beam will be trimmed.
modify_cross : bool, default True
If True, the cross beam will be extended to the opposite face of the main beam and cut with the same plane.
reject_i : bool, default False
If True, the joint will be rejected if the beams are not in I topology (i.e. main butts at crosses end).

"""

Expand All @@ -42,7 +46,7 @@ class LButtJoint(Joint):
def __init__(
self, main_beam=None, cross_beam=None, small_beam_butts=False, modify_cross=True, reject_i=False, **kwargs
):
super(LButtJoint, self).__init__(**kwargs)
super(LButtJoint, self).__init__(beams=(main_beam, cross_beam), **kwargs)

if small_beam_butts and main_beam and cross_beam:
if main_beam.width * main_beam.height > cross_beam.width * cross_beam.height:
Expand Down Expand Up @@ -82,14 +86,6 @@ def __from_data__(cls, value):
instance.cross_beam_key = value["cross_beam_key"]
return instance

@property
def beams(self):
return [self.main_beam, self.cross_beam]

@property
def joint_type(self):
return "L-Butt"

def get_main_cutting_plane(self):
assert self.main_beam and self.cross_beam

Expand All @@ -112,6 +108,7 @@ def restore_beams_from_keys(self, assemly):
"""After de-serialization, resotres references to the main and cross beams saved in the assembly."""
self.main_beam = assemly.find_by_key(self.main_beam_key)
self.cross_beam = assemly.find_by_key(self.cross_beam_key)
self._beams = (self.main_beam, self.cross_beam)

def add_features(self):
"""Adds the required extension and trimming features to both beams.
Expand Down
20 changes: 9 additions & 11 deletions src/compas_timber/connections/l_halflap.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,12 @@ class LHalfLapJoint(LapJoint):
joint_type : str
A string representation of this joint's type.


"""

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)

@property
def joint_type(self):
return "L-HalfLap"
def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5, **kwargs):
super(LHalfLapJoint, self).__init__(main_beam, cross_beam, flip_lap_side, cut_plane_bias, **kwargs)

def add_features(self):
assert self.main_beam and self.cross_beam
Expand All @@ -74,14 +69,17 @@ def add_features(self):
start_cross + extension_tolerance, end_cross + extension_tolerance, self.key
)

self.main_beam.add_features(MillVolume(negative_brep_main_beam))
self.cross_beam.add_features(MillVolume(negative_brep_cross_beam))
main_volume = MillVolume(negative_brep_main_beam)
cross_volume = MillVolume(negative_brep_cross_beam)

self.main_beam.add_features(main_volume)
self.cross_beam.add_features(cross_volume)

f_cross = CutFeature(cross_cutting_frame)
self.cross_beam.add_features(f_cross)
self.features.append(f_cross)

trim_frame = Frame(main_cutting_frame.point, main_cutting_frame.xaxis, -main_cutting_frame.yaxis)
f_main = CutFeature(trim_frame)
self.main_beam.add_features(f_main)
self.features.append(f_main)

self.features = [main_volume, cross_volume, f_main, f_cross]
38 changes: 12 additions & 26 deletions src/compas_timber/connections/l_miter.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,62 +22,47 @@ class LMiterJoint(Joint):

Parameters
----------
assembly : :class:`~compas_timber.assembly.TimberAssembly`
The assembly associated with the beams to be joined.
beam_a : :class:`~compas_timber.parts.Beam`
First beam to be joined.
beam_b : :class:`~compas_timber.parts.Beam`
Second beam to be joined.

Attributes
----------
beams : list(:class:`~compas_timber.parts.Beam`)
The beams joined by this joint.
cutting_planes : tuple(:class:`~compas.geometry.Frame`, :class:`~compas.geometry.Frame`)
A trimming plane for each of the beams. The normals of the planes point at opposite directions.
joint_type : str
A string representation of this joint's type.
beam_a : :class:`~compas_timber.parts.Beam`
First beam to be joined.
beam_b : :class:`~compas_timber.parts.Beam`
Second beam to be joined.

"""

SUPPORTED_TOPOLOGY = JointTopology.TOPO_L

def __init__(self, beam_a=None, beam_b=None, cutoff=None, frame=None, key=None):
super(LMiterJoint, self).__init__(frame, key)
def __init__(self, beam_a=None, beam_b=None, **kwargs):
super(LMiterJoint, self).__init__(beams=(beam_a, beam_b), **kwargs)
self.beam_a = beam_a
self.beam_b = beam_b
self.beam_a_key = beam_a.key if beam_a else None
self.beam_b_key = beam_b.key if beam_b else None
self.cutoff = cutoff # for very acute angles, limit the extension of the tip/beak of the joint
self.features = []

@property
def __data__(self):
data_dict = {
"beam_a": self.beam_a_key,
"beam_b": self.beam_b_key,
"cutoff": self.cutoff,
}
data_dict.update(super(LMiterJoint, self).__data__)
return data_dict

@classmethod
def __from_data__(cls, value):
instance = cls(frame=Frame.__from_data__(value["frame"]), key=value["key"], cutoff=value["cutoff"])
instance = cls(frame=Frame.__from_data__(value["frame"]), key=value["key"])
instance.beam_a_key = value["beam_a"]
instance.beam_b_key = value["beam_b"]
instance.cutoff = value["cutoff"]
return instance

@property
def joint_type(self):
return "L-Miter"

@property
def beams(self):
return [self.beam_a, self.beam_b]

def get_cutting_planes(self):
assert self.beam_a and self.beam_b
vA = Vector(*self.beam_a.frame.xaxis) # frame.axis gives a reference, not a copy
vB = Vector(*self.beam_b.frame.xaxis)

Expand Down Expand Up @@ -147,7 +132,8 @@ def add_features(self):
self.beam_b.add_features(f2)
self.features = [f1, f2]

def restore_beams_from_keys(self, assemly):
def restore_beams_from_keys(self, assembly):
"""After de-serialization, resotres references to the main and cross beams saved in the assembly."""
self.beam_a = assemly.find_by_key(self.beam_a_key)
self.beam_b = assemly.find_by_key(self.beam_b_key)
self.beam_a = assembly.find_by_key(self.beam_a_key)
self.beam_b = assembly.find_by_key(self.beam_b_key)
self._beams = [self.beam_a, self.beam_b]
33 changes: 13 additions & 20 deletions src/compas_timber/connections/lap_joint.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,19 @@ class LapJoint(Joint):

Attributes
----------
beams : list(:class:`~compas_timber.parts.Beam`)
The beams joined by this joint.
beams : list(:class:`~compas_timber.parts.Beam`)
main_beam : :class:`~compas_timber.parts.Beam`
The main beam to be joined.
main_beam_key : str
The key of the main beam.
cross_beam_key : str
The key of the cross beam.
features : list(:class:`~compas_timber.parts.Feature`)
The features created by this joint.
joint_type : str
A string representation of this joint's type.
cross_beam : :class:`~compas_timber.parts.Beam`
The cross beam to be joined.
flip_lap_side : bool
If True, the lap is flipped to the other side of the beams.
cut_plane_bias : float
Allows lap to be shifted deeper into one beam or the other. Value should be between 0 and 1.0 without completely cutting through either beam. Default is 0.5.

"""

def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5, frame=None, key=None):
super(LapJoint, self).__init__(frame=frame, key=key)
def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5, **kwargs):
super(LapJoint, self).__init__(beams=(main_beam, cross_beam), **kwargs)
self.main_beam = main_beam
self.cross_beam = cross_beam
self.flip_lap_side = flip_lap_side
Expand Down Expand Up @@ -77,14 +73,11 @@ def __from_data__(cls, value):
instance.cross_beam_key = value["cross_beam"]
return instance

@property
def beams(self):
return [self.main_beam, self.cross_beam]

def restore_beams_from_keys(self, assemly):
"""After de-serialization, resotres references to the main and cross beams saved in the assembly."""
self.main_beam = assemly.find_by_key(self.main_beam_key)
self.cross_beam = assemly.find_by_key(self.cross_beam_key)
self._beams = (self.main_beam, self.cross_beam)

@staticmethod
def _sort_beam_planes(beam, cutplane_vector):
Expand Down Expand Up @@ -130,22 +123,22 @@ def _create_polyhedron(plane_a, lines, bias): # Hexahedron from 2 Planes and 4
)

def get_main_cutting_frame(self):
assert self.beams
beam_a, beam_b = self.beams
assert beam_a and beam_b

_, cfr = self.get_face_most_towards_beam(beam_a, beam_b)
cfr = Frame(cfr.point, cfr.yaxis, cfr.xaxis) # flip normal towards the inside of main beam
return cfr

def get_cross_cutting_frame(self):
assert self.beams
beam_a, beam_b = self.beams
assert beam_a and beam_b
_, cfr = self.get_face_most_towards_beam(beam_b, beam_a)
return cfr

def _create_negative_volumes(self):
assert self.beams
beam_a, beam_b = self.beams
assert beam_a and beam_b

# Get Cut Plane
plane_cut_vector = beam_a.centerline.vector.cross(beam_b.centerline.vector)
Expand Down
Loading
Loading