Skip to content

Commit

Permalink
Merge pull request #234 from gramaziokohler/joint_boilerplate
Browse files Browse the repository at this point in the history
Joint boilerplate
  • Loading branch information
chenkasirer authored Mar 20, 2024
2 parents f32c9f0 + e02ac13 commit 0cc8b66
Show file tree
Hide file tree
Showing 16 changed files with 268 additions and 385 deletions.
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
107 changes: 14 additions & 93 deletions src/compas_timber/connections/butt_joint.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from compas.geometry import Frame
from compas_timber.parts import CutFeature
from compas_timber.parts import MillVolume
from compas.geometry import intersection_plane_plane_plane
from compas.geometry import subtract_vectors
from compas.geometry import dot_vectors
Expand All @@ -11,7 +9,6 @@
from compas.geometry import Polyhedron
from compas.geometry import Point
from compas.geometry import angle_vectors_signed
from .joint import BeamJoinningError
from .joint import Joint


Expand Down Expand Up @@ -44,30 +41,13 @@ class ButtJoint(Joint):
"""

def __init__(
self,
main_beam=None,
cross_beam=None,
mill_depth=0,
small_beam_butts=False,
modify_cross=True,
reject_i=False,
**kwargs
):
def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, **kwargs):
super(ButtJoint, self).__init__(**kwargs)

if small_beam_butts and main_beam and cross_beam:
if main_beam.width * main_beam.height > cross_beam.width * cross_beam.height:
main_beam, cross_beam = cross_beam, main_beam

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.mill_depth = mill_depth
self.modify_cross = modify_cross
self.small_beam_butts = small_beam_butts
self.reject_i = reject_i
self.features = []

@property
Expand All @@ -76,23 +56,13 @@ def __data__(self):
"main_beam_key": self.main_beam_key,
"cross_beam_key": self.cross_beam_key,
"mill_depth": self.mill_depth,
"small_beam_butts": self.small_beam_butts,
"modify_cross": self.modify_cross,
"reject_i": self.reject_i,
}
data_dict.update(super(ButtJoint, self).__data__)
return data_dict

@classmethod
def __from_data__(cls, value):
instance = cls(
frame=Frame.__from_data__(value["frame"]),
key=value["key"],
mill_depth=value["mill_depth"],
small_beam_butts=value["small_beam_butts"],
modify_cross=value["modify_cross"],
reject_i=value["reject_i"],
)
instance = cls(**value)
instance.main_beam_key = value["main_beam_key"]
instance.cross_beam_key = value["cross_beam_key"]
return instance
Expand All @@ -101,38 +71,23 @@ def __from_data__(cls, value):
def beams(self):
return [self.main_beam, self.cross_beam]

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

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

index, _ = self.get_face_most_towards_beam(self.main_beam, self.cross_beam, ignore_ends=False)
if self.reject_i and index in [4, 5]:
raise BeamJoinningError(
beams=self.beams, joint=self, debug_info="Beams are in I topology and reject_i flag is True"
)

index, cfr = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True)
cross_mating_frame = cfr.copy()
cfr = Frame(cfr.point, cfr.xaxis, cfr.yaxis * -1.0) # flip normal
cfr.point = cfr.point + cfr.zaxis * self.mill_depth
return cfr, cross_mating_frame

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)

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

face_dict = Joint._beam_side_incidence(self.main_beam, self.cross_beam, ignore_ends=True)
face_indices = face_dict.keys()
angles = face_dict.values()
angles, face_indices = zip(*sorted(zip(angles, face_indices)))
return self.cross_beam.faces[face_indices[1]], self.cross_beam.faces[face_indices[2]]

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

face_dict = Joint._beam_side_incidence(self.cross_beam, self.main_beam, ignore_ends=True)
face_indices = face_dict.keys()
angles = face_dict.values()
Expand All @@ -144,6 +99,14 @@ def back_surface_main(self):
face_dict.sort(lambda x: x.values())
return face_dict.values()[3]

def get_main_cutting_plane(self):
assert self.main_beam and self.cross_beam
_, cfr = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True)
cross_mating_frame = cfr.copy()
cfr = Frame(cfr.point, cfr.xaxis, cfr.yaxis * -1.0) # flip normal
cfr.point = cfr.point + cfr.zaxis * self.mill_depth
return cfr, cross_mating_frame

def subtraction_volume(self):
dir_pts = []
vertices = []
Expand Down Expand Up @@ -187,45 +150,3 @@ def subtraction_volume(self):
)

return ph

def add_features(self):
"""Adds the required extension and trimming features to both beams.
This method is automatically called when joint is created by the call to `Joint.create()`.
"""
assert self.main_beam and self.cross_beam # should never happen
if self.features:
self.main_beam.remove_features(self.features)
start_main, start_cross = None, None

try:
main_cutting_plane = self.get_main_cutting_plane()[0]
cross_cutting_plane = self.get_cross_cutting_plane()
start_main, end_main = self.main_beam.extension_to_plane(main_cutting_plane)
start_cross, end_cross = self.cross_beam.extension_to_plane(cross_cutting_plane)
except BeamJoinningError as be:
raise be
except AttributeError as ae:
# I want here just the plane that caused the error
geometries = [cross_cutting_plane] if start_main is not None else [main_cutting_plane]
raise BeamJoinningError(beams=self.beams, joint=self, debug_info=str(ae), debug_geometries=geometries)
except Exception as ex:
raise BeamJoinningError(beams=self.beams, joint=self, debug_info=str(ex))

extension_tolerance = 0.01 # TODO: this should be proportional to the unit used

if self.modify_cross:
self.cross_beam.add_blank_extension(
start_cross + extension_tolerance, end_cross + extension_tolerance, self.key
)
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)

f_main = CutFeature(main_cutting_plane)
self.cross_beam.add_features(MillVolume(self.subtraction_volume()))
self.main_beam.add_features(f_main)
self.features.append(f_main)
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
18 changes: 14 additions & 4 deletions src/compas_timber/connections/joint.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,38 @@ 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, **kwargs):
super(Joint, self).__init__()
self.frame = frame or Frame.worldXY()
self.key = key
self._beams = beams
self.features = []
self.attributes = {}

@property
def __data__(self):
return {"frame": self.frame.__data__, "key": self.key, "beams": [beam.key for beam in self.beams]}
return {"frame": self.frame.__data__, "key": self.key}

@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
Loading

0 comments on commit 0cc8b66

Please sign in to comment.