From 286d64afbb315137008f1cb6d74ada4e710339d5 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Fri, 16 Feb 2024 16:44:27 +0100 Subject: [PATCH 1/9] reduced concrete joint boilerplate code, fixed docstrings, removed obsolete attributes --- .../connections/french_ridge_lap.py | 18 ++------- src/compas_timber/connections/joint.py | 16 ++++++-- src/compas_timber/connections/l_butt.py | 27 ++++++------- src/compas_timber/connections/l_halflap.py | 20 +++++----- src/compas_timber/connections/l_miter.py | 38 ++++++------------- src/compas_timber/connections/lap_joint.py | 33 +++++++--------- src/compas_timber/connections/null_joint.py | 31 ++++----------- src/compas_timber/connections/t_butt.py | 32 ++++------------ src/compas_timber/connections/t_halflap.py | 34 ++++------------- src/compas_timber/connections/x_halflap.py | 34 ++++------------- 10 files changed, 94 insertions(+), 189 deletions(-) diff --git a/src/compas_timber/connections/french_ridge_lap.py b/src/compas_timber/connections/french_ridge_lap.py index 023ecf084b..d4d332c703 100644 --- a/src/compas_timber/connections/french_ridge_lap.py +++ b/src/compas_timber/connections/french_ridge_lap.py @@ -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() @@ -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) @@ -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): """ diff --git a/src/compas_timber/connections/joint.py b/src/compas_timber/connections/joint.py index 8f887e50a1..dc1655c8b9 100644 --- a/src/compas_timber/connections/joint.py +++ b/src/compas_timber/connections/joint.py @@ -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 @@ -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). diff --git a/src/compas_timber/connections/l_butt.py b/src/compas_timber/connections/l_butt.py index 6c33091683..b172af8e50 100644 --- a/src/compas_timber/connections/l_butt.py +++ b/src/compas_timber/connections/l_butt.py @@ -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` @@ -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). """ @@ -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: @@ -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 @@ -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. diff --git a/src/compas_timber/connections/l_halflap.py b/src/compas_timber/connections/l_halflap.py index 5f32771d13..58acaf1867 100644 --- a/src/compas_timber/connections/l_halflap.py +++ b/src/compas_timber/connections/l_halflap.py @@ -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 @@ -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] diff --git a/src/compas_timber/connections/l_miter.py b/src/compas_timber/connections/l_miter.py index d026b716e9..0e238a1900 100644 --- a/src/compas_timber/connections/l_miter.py +++ b/src/compas_timber/connections/l_miter.py @@ -22,8 +22,6 @@ 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` @@ -31,53 +29,40 @@ class LMiterJoint(Joint): 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) @@ -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] diff --git a/src/compas_timber/connections/lap_joint.py b/src/compas_timber/connections/lap_joint.py index 380e378851..63456f13b1 100644 --- a/src/compas_timber/connections/lap_joint.py +++ b/src/compas_timber/connections/lap_joint.py @@ -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 @@ -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): @@ -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) diff --git a/src/compas_timber/connections/null_joint.py b/src/compas_timber/connections/null_joint.py index a6990213d1..36b00f4059 100644 --- a/src/compas_timber/connections/null_joint.py +++ b/src/compas_timber/connections/null_joint.py @@ -1,4 +1,5 @@ from compas.geometry import Frame + from .joint import Joint from .solver import JointTopology @@ -19,23 +20,21 @@ class NullJoint(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. + 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 # TODO: this really supports all.. def __init__(self, beam_a=None, beam_b=None, **kwargs): - super(NullJoint, self).__init__(**kwargs) - + super(NullJoint, 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.features = [] @property def __data__(self): @@ -51,31 +50,17 @@ 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.beam_a_key = value["main_beam_key"] instance.beam_b_key = value["cross_beam_key"] return instance - @property - def beams(self): - return [self.beam_a, self.beam_b] - - @property - def joint_type(self): - return "L-Butt" - def restore_beams_from_keys(self, assemly): """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._beams = (self.beam_a, self.beam_b) 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()`. - - """ + """This joint does not add any features to the beams.""" pass diff --git a/src/compas_timber/connections/t_butt.py b/src/compas_timber/connections/t_butt.py index 81ac3eb905..b9e992606e 100644 --- a/src/compas_timber/connections/t_butt.py +++ b/src/compas_timber/connections/t_butt.py @@ -17,8 +17,6 @@ class TButtJoint(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` @@ -26,53 +24,38 @@ class TButtJoint(Joint): Attributes ---------- - beams : list(:class:`~compas_timber.parts.Beam`) - The beams joined by this joint. - cutting_plane_main : :class:`~compas.geometry.Frame` - The frame by which the main beam is trimmed. - cutting_plane_cross : :class:`~compas.geometry.Frame` - The frame by which the cross beam is trimmed. - 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. """ SUPPORTED_TOPOLOGY = JointTopology.TOPO_T - def __init__(self, main_beam=None, cross_beam=None, gap=None, frame=None, key=None): - super(TButtJoint, self).__init__(frame, key) + def __init__(self, main_beam=None, cross_beam=None, **kwargs): + super(TButtJoint, self).__init__(beams=(main_beam, cross_beam), **kwargs) 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 = main_beam self.cross_beam = cross_beam - self.gap = gap - self.features = [] @property def __data__(self): data_dict = { "main_beam_key": self.main_beam_key, "cross_beam_key": self.cross_beam_key, - "gap": self.gap, } data_dict.update(super(TButtJoint, 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.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] - - @property - def joint_type(self): - return "T-Butt" - def get_cutting_plane(self): assert self.main_beam and self.cross_beam # should never happen @@ -84,6 +67,7 @@ def restore_beams_from_keys(self, assembly): """After de-serialization, resotres references to the main and cross beams saved in the assembly.""" self.main_beam = assembly.find_by_key(self.main_beam_key) self.cross_beam = assembly.find_by_key(self.cross_beam_key) + self._beams = (self.main_beam, self.cross_beam) def add_features(self): """Adds the trimming plane to the main beam (no features for the cross beam). diff --git a/src/compas_timber/connections/t_halflap.py b/src/compas_timber/connections/t_halflap.py index cfe5102ad3..65cf24e278 100644 --- a/src/compas_timber/connections/t_halflap.py +++ b/src/compas_timber/connections/t_halflap.py @@ -27,33 +27,12 @@ class THalfLapJoint(LapJoint): 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. - Attributes - ---------- - beams : list(:class:`~compas_timber.parts.Beam`) - The beams joined by this joint. - 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. - 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. - """ SUPPORTED_TOPOLOGY = JointTopology.TOPO_T - def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5, frame=None, key=None): - super(THalfLapJoint, self).__init__(main_beam, cross_beam, flip_lap_side, cut_plane_bias, frame, key) - - @property - def joint_type(self): - return "T-HalfLap" + def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5, **kwargs): + super(THalfLapJoint, 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 # should never happen @@ -73,10 +52,13 @@ def add_features(self): 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_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) 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] diff --git a/src/compas_timber/connections/x_halflap.py b/src/compas_timber/connections/x_halflap.py index 86fa136ec9..821d1300d5 100644 --- a/src/compas_timber/connections/x_halflap.py +++ b/src/compas_timber/connections/x_halflap.py @@ -24,34 +24,12 @@ class XHalfLapJoint(LapJoint): 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. - Attributes - ---------- - beams : list(:class:`~compas_timber.parts.Beam`) - The beams joined by this joint. - 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. - 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. - - """ SUPPORTED_TOPOLOGY = JointTopology.TOPO_X - def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5, frame=None, key=None): - super(XHalfLapJoint, self).__init__(main_beam, cross_beam, flip_lap_side, cut_plane_bias, frame, key) - - @property - def joint_type(self): - return "X-HalfLap" + def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5, **kwargs): + super(XHalfLapJoint, 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 # should never happen @@ -60,6 +38,8 @@ def add_features(self): negative_brep_beam_a, negative_brep_beam_b = self._create_negative_volumes() except Exception as ex: raise BeamJoinningError(beams=self.beams, joint=self, debug_info=str(ex)) - - self.main_beam.add_features(MillVolume(negative_brep_beam_a)) - self.cross_beam.add_features(MillVolume(negative_brep_beam_b)) + volume_a = MillVolume(negative_brep_beam_a) + volume_b = MillVolume(negative_brep_beam_b) + self.main_beam.add_features(volume_a) + self.cross_beam.add_features(volume_b) + self.features = [volume_a, volume_b] From 4d1267a2861d90c12d29a9bf1636b8946944e893 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Fri, 16 Feb 2024 17:48:40 +0100 Subject: [PATCH 2/9] show Joint class name in showjointtypes component --- src/compas_timber/ghpython/components/CT_ShowJointTypes/code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas_timber/ghpython/components/CT_ShowJointTypes/code.py b/src/compas_timber/ghpython/components/CT_ShowJointTypes/code.py index 58ce500cc0..d2306349cb 100644 --- a/src/compas_timber/ghpython/components/CT_ShowJointTypes/code.py +++ b/src/compas_timber/ghpython/components/CT_ShowJointTypes/code.py @@ -21,7 +21,7 @@ def RunScript(self, Assembly): p2 = point_to_rhino(p2) self.pt.append((p2 + p1) / 2) - self.txt.append(joint.joint_type) + self.txt.append(joint.__class__.__name__) def DrawViewportWires(self, arg): if self.Locked: From 4b45a4476d60287f7bd2a093333b9ed6b5e4ee65 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Fri, 16 Feb 2024 17:49:24 +0100 Subject: [PATCH 3/9] added some unittests --- tests/compas_timber/test_joint.py | 133 +++++++++++++++++++++++------- 1 file changed, 104 insertions(+), 29 deletions(-) diff --git a/tests/compas_timber/test_joint.py b/tests/compas_timber/test_joint.py index 5c22d085e7..5fef221b7b 100644 --- a/tests/compas_timber/test_joint.py +++ b/tests/compas_timber/test_joint.py @@ -7,12 +7,14 @@ from compas.geometry import Frame from compas.geometry import Point from compas.geometry import Vector +from compas.geometry import Line from compas_timber.assembly import TimberAssembly from compas_timber.connections import TButtJoint from compas_timber.connections import LButtJoint -from compas_timber.connections import LMiterJoint from compas_timber.connections import XHalfLapJoint +from compas_timber.connections import THalfLapJoint +from compas_timber.connections import LHalfLapJoint from compas_timber.connections import find_neighboring_beams from compas_timber.parts import Beam @@ -33,6 +35,37 @@ def example_beams(): return beams +@pytest.fixture +def l_topo_beams(): + w = 0.2 + h = 0.2 + lines = [ + Line(Point(x=0.0, y=1.0, z=0.0), Point(x=1.0, y=1.0, z=0.0)), + Line(Point(x=1.0, y=1.0, z=0.0), Point(x=1.0, y=0.0, z=0.0)), + ] + return [Beam.from_centerline(line, w, h) for line in lines] + + +@pytest.fixture +def t_topo_beams(): + w = 0.2 + h = 0.2 + cross = Line(Point(x=1.0, y=2.0, z=0.0), Point(x=3.0, y=2.0, z=0.0)) + main = Line(Point(x=2.0, y=2.0, z=0.0), Point(x=2.0, y=1.0, z=0.0)) + return Beam.from_centerline(main, w, h), Beam.from_centerline(cross, w, h) + + +@pytest.fixture +def x_topo_beams(): + w = 0.2 + h = 0.2 + lines = [ + Line(Point(x=3.0, y=1.0, z=0.0), Point(x=4.0, y=0.0, z=0.0)), + Line(Point(x=3.0, y=0.0, z=0.0), Point(x=4.0, y=1.0, z=0.0)), + ] + return [Beam.from_centerline(line, w, h) for line in lines] + + def test_create(mocker): mocker.patch("compas_timber.connections.Joint.add_features") # try create with beams @@ -82,28 +115,82 @@ def test_joint_override_protection(mocker): assert A.are_parts_joined([B1, B2]) is False -def test_deepcopy(mocker): +def test_deepcopy(mocker, t_topo_beams): mocker.patch("compas_timber.connections.Joint.add_features") - A = TimberAssembly() - B1 = Beam.from_endpoints(Point(0, 0, 0), Point(2, 0, 0), 0.1, 0.2, z_vector=Vector(0, 0, 1)) - B2 = Beam.from_endpoints(Point(1, 0, 0), Point(1, 1, 0), 0.1, 0.2, z_vector=Vector(0, 0, 1)) - A.add_beam(B1) - A.add_beam(B2) - J = TButtJoint.create(A, B1, B2) - J_copy = deepcopy(J) + assembly = TimberAssembly() + beam_a, beam_b = t_topo_beams + assembly.add_beam(beam_a) + assembly.add_beam(beam_b) + t_butt = TButtJoint.create(assembly, beam_a, beam_b) + assembly_copy = deepcopy(assembly) + + assert assembly_copy is not assembly + assert assembly_copy.beams + assert assembly_copy.joints + + t_butt_copy = assembly_copy.joints[0] + assert t_butt_copy is not t_butt + assert t_butt_copy.beams + + +def test_joint_create_t_butt(t_topo_beams): + assembly = TimberAssembly() + main_beam, cross_beam = t_topo_beams + assembly.add_beam(main_beam) + assembly.add_beam(cross_beam) + joint = TButtJoint.create(assembly, main_beam, cross_beam) + + assert joint.main_beam is main_beam + assert joint.cross_beam is cross_beam + assert joint.beams + + +def test_joint_create_l_butt(l_topo_beams): + assembly = TimberAssembly() + beam_a, beam_b = l_topo_beams + assembly.add_beam(beam_a) + assembly.add_beam(beam_b) + joint = LButtJoint.create(assembly, beam_a, beam_b) + + assert joint.main_beam is beam_a + assert joint.cross_beam is beam_b + assert joint.beams + + +def test_joint_create_x_half_lap(x_topo_beams): + assembly = TimberAssembly() + beam_a, beam_b = x_topo_beams + assembly.add_beam(beam_a) + assembly.add_beam(beam_b) + joint = XHalfLapJoint.create(assembly, beam_a, beam_b) + + assert joint.main_beam is beam_a + assert joint.cross_beam is beam_b + assert joint.beams + + +def test_joint_create_t_lap(t_topo_beams): + assembly = TimberAssembly() + main_beam, cross_beam = t_topo_beams + assembly.add_beam(main_beam) + assembly.add_beam(cross_beam) + joint = THalfLapJoint.create(assembly, main_beam, cross_beam) - assert J_copy is not J + assert joint.main_beam is main_beam + assert joint.cross_beam is cross_beam + assert joint.beams -def test_joint_create_kwargs_passthrough_tbutt(): +def test_joint_create_l_lap(l_topo_beams): assembly = TimberAssembly() - main = Beam.from_endpoints(Point(0.5, 0, 0), Point(0.5, 1, 0), 0.1, 0.2, z_vector=Vector(0, 0, 1)) - cross = Beam.from_endpoints(Point(0, 0, 0), Point(1, 0, 0), 0.1, 0.2, z_vector=Vector(0, 0, 1)) - assembly.add_beam(main) - assembly.add_beam(cross) + beam_a, beam_b = l_topo_beams + assembly.add_beam(beam_a) + assembly.add_beam(beam_b) + joint = LHalfLapJoint.create(assembly, beam_a, beam_b) - joint = TButtJoint.create(assembly, main, cross, gap=0.1) - assert joint.gap == 0.1 + assert joint.main_beam is beam_a + assert joint.cross_beam is beam_b + assert joint.beams def test_joint_create_kwargs_passthrough_lbutt(): @@ -141,18 +228,6 @@ def test_joint_create_kwargs_passthrough_lbutt(): assert joint_d.cross_beam is small -def test_joint_create_kwargs_passthrough_lmiter(): - assembly = TimberAssembly() - small = Beam.from_endpoints(Point(0, 0, 0), Point(0, 1, 0), 0.1, 0.1, z_vector=Vector(0, 0, 1)) - large = Beam.from_endpoints(Point(0, 0, 0), Point(1, 0, 0), 0.2, 0.2, z_vector=Vector(0, 0, 1)) - assembly.add_beam(small) - assembly.add_beam(large) - - joint = LMiterJoint.create(assembly, small, large, cutoff=42) - - assert joint.cutoff == 42 - - def test_joint_create_kwargs_passthrough_xhalflap(): assembly = TimberAssembly() beam_a = Beam.from_endpoints(Point(0.5, 0, 0), Point(0.5, 1, 0), 0.2, 0.2, z_vector=Vector(0, 0, 1)) From 18695c4f35fd39abcca22c036d9cfd13a13fa3af Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Fri, 16 Feb 2024 17:52:17 +0100 Subject: [PATCH 4/9] updated changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9bb4de998..b425246270 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +* Reduced some boilerplate code in `Joint` subclasses. + ### Removed +* Removed `joint_type` attributes from all `Joint` classes. ## [0.7.0] 2024-02-15 From cabd599ec2706aeabeb5f82e61cf2309b1651334 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 22 Feb 2024 15:06:39 +0100 Subject: [PATCH 5/9] fixed typo in docstring --- src/compas_timber/parts/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas_timber/parts/features.py b/src/compas_timber/parts/features.py index 2507f045cb..d0ef7dfc1f 100644 --- a/src/compas_timber/parts/features.py +++ b/src/compas_timber/parts/features.py @@ -4,7 +4,7 @@ class Feature(Data): """ - Attirbutes + Attributes ---------- is_joinery : bool Indicates whether this feature is a result of joinery. From 57eb2937c0d1a7e6b5d9d8002e112fbb308d1780 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 6 Mar 2024 10:01:13 +0100 Subject: [PATCH 6/9] updated changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b425246270..f5a937df02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,14 @@ 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 From 172d4451d4ed3d2a56e86ddbd9a7b8fc1ce18431 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 20 Mar 2024 18:53:17 +0100 Subject: [PATCH 7/9] fixed butt joints interface --- src/compas_timber/connections/butt_joint.py | 107 +++----------------- src/compas_timber/connections/l_butt.py | 37 ++----- src/compas_timber/connections/t_butt.py | 27 +---- 3 files changed, 27 insertions(+), 144 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index bcabc253f8..71b94f8f7f 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -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 @@ -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 @@ -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 @@ -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 @@ -101,31 +71,14 @@ 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() @@ -133,6 +86,8 @@ def side_surfaces_cross(self): 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() @@ -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 = [] @@ -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) diff --git a/src/compas_timber/connections/l_butt.py b/src/compas_timber/connections/l_butt.py index 253259e8b6..8bc7c342c1 100644 --- a/src/compas_timber/connections/l_butt.py +++ b/src/compas_timber/connections/l_butt.py @@ -1,4 +1,3 @@ -from compas.geometry import Frame from compas_timber.connections.butt_joint import ButtJoint from compas_timber.parts import CutFeature from compas_timber.parts import MillVolume @@ -54,28 +53,18 @@ def __init__( reject_i=False, **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: 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 + super(LButtJoint, self).__init__(main_beam, cross_beam, mill_depth, **kwargs) 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, - "mill_depth": self.mill_depth, "small_beam_butts": self.small_beam_butts, "modify_cross": self.modify_cross, "reject_i": self.reject_i, @@ -83,25 +72,21 @@ def __data__(self): 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"], - mill_depth=value["mill_depth"], - 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 - def get_cross_cutting_plane(self): assert self.main_beam and self.cross_beam _, cfr = self.get_face_most_towards_beam(self.cross_beam, self.main_beam, ignore_ends=True) return cfr + 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" + ) + return super(LButtJoint, self).get_main_cutting_plane() + def add_features(self): """Adds the required extension and trimming features to both beams. diff --git a/src/compas_timber/connections/t_butt.py b/src/compas_timber/connections/t_butt.py index 04fa4a4857..d66eb2d19f 100644 --- a/src/compas_timber/connections/t_butt.py +++ b/src/compas_timber/connections/t_butt.py @@ -1,4 +1,3 @@ -from compas.geometry import Frame from compas_timber.connections.butt_joint import ButtJoint from compas_timber.parts import CutFeature @@ -34,30 +33,8 @@ class TButtJoint(ButtJoint): SUPPORTED_TOPOLOGY = JointTopology.TOPO_T - def __init__(self, main_beam=None, cross_beam=None, **kwargs): - super(TButtJoint, self).__init__(beams=(main_beam, cross_beam), **kwargs) - 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 = main_beam - self.cross_beam = cross_beam - - @property - def __data__(self): - data_dict = { - "main_beam_key": self.main_beam_key, - "cross_beam_key": self.cross_beam_key, - } - data_dict.update(super(TButtJoint, self).__data__) - return data_dict - - @classmethod - def __from_data__(cls, value): - instance = cls(frame=Frame.__from_data__(value["frame"]), key=value["key"]) - instance.main_beam_key = value["main_beam_key"] - instance.cross_beam_key = value["cross_beam_key"] - instance.mill_depth = value["mill_depth"] - instance.gap = value["gap"] - return instance + def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, **kwargs): + super(TButtJoint, self).__init__(main_beam, cross_beam, mill_depth, **kwargs) def add_features(self): """Adds the trimming plane to the main beam (no features for the cross beam). From a92c57e06c479def898ae30176cecc708a95edfa Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 20 Mar 2024 18:53:48 +0100 Subject: [PATCH 8/9] fixed data interface in joints --- src/compas_timber/connections/joint.py | 4 +-- src/compas_timber/connections/l_miter.py | 12 ++++----- src/compas_timber/connections/lap_joint.py | 15 ++++-------- src/compas_timber/connections/null_joint.py | 9 ++----- tests/compas_timber/test_assembly.py | 27 +++++++++++++++++++++ 5 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/compas_timber/connections/joint.py b/src/compas_timber/connections/joint.py index dc1655c8b9..e79a86e26a 100644 --- a/src/compas_timber/connections/joint.py +++ b/src/compas_timber/connections/joint.py @@ -59,7 +59,7 @@ class Joint(Data): SUPPORTED_TOPOLOGY = JointTopology.TOPO_UNKNOWN - def __init__(self, frame=None, key=None, beams=None): + def __init__(self, frame=None, key=None, beams=None, **kwargs): super(Joint, self).__init__() self.frame = frame or Frame.worldXY() self.key = key @@ -69,7 +69,7 @@ def __init__(self, frame=None, key=None, beams=None): @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): diff --git a/src/compas_timber/connections/l_miter.py b/src/compas_timber/connections/l_miter.py index 0e238a1900..c931777c8d 100644 --- a/src/compas_timber/connections/l_miter.py +++ b/src/compas_timber/connections/l_miter.py @@ -39,7 +39,7 @@ class LMiterJoint(Joint): SUPPORTED_TOPOLOGY = JointTopology.TOPO_L def __init__(self, beam_a=None, beam_b=None, **kwargs): - super(LMiterJoint, self).__init__(beams=(beam_a, beam_b), **kwargs) + super(LMiterJoint, self).__init__(**kwargs) self.beam_a = beam_a self.beam_b = beam_b self.beam_a_key = beam_a.key if beam_a else None @@ -48,17 +48,17 @@ def __init__(self, beam_a=None, beam_b=None, **kwargs): @property def __data__(self): data_dict = { - "beam_a": self.beam_a_key, - "beam_b": self.beam_b_key, + "beam_a_key": self.beam_a_key, + "beam_b_key": self.beam_b_key, } 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"]) - instance.beam_a_key = value["beam_a"] - instance.beam_b_key = value["beam_b"] + instance = cls(**value) + instance.beam_a_key = value["beam_a_key"] + instance.beam_b_key = value["beam_b_key"] return instance def get_cutting_planes(self): diff --git a/src/compas_timber/connections/lap_joint.py b/src/compas_timber/connections/lap_joint.py index 63456f13b1..ce341ad03d 100644 --- a/src/compas_timber/connections/lap_joint.py +++ b/src/compas_timber/connections/lap_joint.py @@ -53,8 +53,8 @@ def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_pla @property def __data__(self): data_dict = { - "main_beam": self.main_beam_key, - "cross_beam": self.cross_beam_key, + "main_beam_key": self.main_beam_key, + "cross_beam_key": self.cross_beam_key, "flip_lap_side": self.flip_lap_side, "cut_plane_bias": self.cut_plane_bias, } @@ -63,14 +63,9 @@ def __data__(self): @classmethod def __from_data__(cls, value): - instance = cls( - frame=Frame.__from_data__(value["frame"]), - key=value["key"], - cut_plane_bias=value["cut_plane_bias"], - flip_lap_side=value["flip_lap_side"], - ) - instance.main_beam_key = value["main_beam"] - instance.cross_beam_key = value["cross_beam"] + instance = cls(**value) + instance.main_beam_key = value["main_beam_key"] + instance.cross_beam_key = value["cross_beam_key"] return instance def restore_beams_from_keys(self, assemly): diff --git a/src/compas_timber/connections/null_joint.py b/src/compas_timber/connections/null_joint.py index 36b00f4059..1201eb067c 100644 --- a/src/compas_timber/connections/null_joint.py +++ b/src/compas_timber/connections/null_joint.py @@ -1,5 +1,3 @@ -from compas.geometry import Frame - from .joint import Joint from .solver import JointTopology @@ -30,7 +28,7 @@ class NullJoint(Joint): SUPPORTED_TOPOLOGY = JointTopology.TOPO_L # TODO: this really supports all.. def __init__(self, beam_a=None, beam_b=None, **kwargs): - super(NullJoint, self).__init__(beams=(beam_a, beam_b), **kwargs) + super(NullJoint, self).__init__(**kwargs) self.beam_a = beam_a self.beam_b = beam_b self.beam_a_key = beam_a.key if beam_a else None @@ -47,10 +45,7 @@ def __data__(self): @classmethod def __from_data__(cls, value): - instance = cls( - frame=Frame.__from_data__(value["frame"]), - key=value["key"], - ) + instance = cls(**value) instance.beam_a_key = value["main_beam_key"] instance.beam_b_key = value["cross_beam_key"] return instance diff --git a/tests/compas_timber/test_assembly.py b/tests/compas_timber/test_assembly.py index 9ee0a07d5a..4e75fe7a50 100644 --- a/tests/compas_timber/test_assembly.py +++ b/tests/compas_timber/test_assembly.py @@ -8,6 +8,7 @@ from compas_timber.assembly import TimberAssembly from compas_timber.connections import LButtJoint +from compas_timber.connections import TButtJoint from compas_timber.parts import Beam @@ -129,3 +130,29 @@ def test_beams_have_keys_after_serialization(): A = json_loads(json_dumps(A)) assert keys == [beam.key for beam in A.beams] + + +def test_serialization_with_l_butt_joints(mocker): + mocker.patch("compas_timber.connections.LButtJoint.add_features") + F1 = Frame(Point(0, 0, 0), Vector(1, 0, 0), Vector(0, 1, 0)) + F2 = Frame(Point(0, 0, 0), Vector(1, 0, 0), Vector(0, 1, 0)) + B1 = Beam(F1, length=1.0, width=0.1, height=0.12) + B2 = Beam(F2, length=1.0, width=0.1, height=0.12) + A = TimberAssembly() + A.add_beam(B1) + A.add_beam(B2) + _ = LButtJoint.create(A, B1, B2) + + A = json_loads(json_dumps(A)) + + +def test_serialization_with_t_butt_joints(mocker): + mocker.patch("compas_timber.connections.LButtJoint.add_features") + A = TimberAssembly() + B1 = Beam(Frame.worldXY(), length=1.0, width=0.1, height=0.1) + B2 = Beam(Frame.worldYZ(), length=1.0, width=0.1, height=0.1) + A.add_beam(B1) + A.add_beam(B2) + _ = TButtJoint.create(A, B1, B2) + + A = json_loads(json_dumps(A)) From e02ac13a7da3a7d1f52232a0bd073fdb47e790f7 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 20 Mar 2024 19:20:42 +0100 Subject: [PATCH 9/9] relative imports --- src/compas_timber/connections/l_butt.py | 3 ++- src/compas_timber/connections/l_halflap.py | 2 +- src/compas_timber/connections/x_halflap.py | 3 ++- tests/compas_timber/test_joint.py | 1 - 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/compas_timber/connections/l_butt.py b/src/compas_timber/connections/l_butt.py index 8bc7c342c1..acc961a6ce 100644 --- a/src/compas_timber/connections/l_butt.py +++ b/src/compas_timber/connections/l_butt.py @@ -1,10 +1,11 @@ -from compas_timber.connections.butt_joint import ButtJoint from compas_timber.parts import CutFeature from compas_timber.parts import MillVolume from .joint import BeamJoinningError from .solver import JointTopology +from .butt_joint import ButtJoint + class LButtJoint(ButtJoint): """Represents an L-Butt type joint which joins two beam in their ends, trimming the main beam. diff --git a/src/compas_timber/connections/l_halflap.py b/src/compas_timber/connections/l_halflap.py index 58acaf1867..86d3d5734e 100644 --- a/src/compas_timber/connections/l_halflap.py +++ b/src/compas_timber/connections/l_halflap.py @@ -1,10 +1,10 @@ from compas.geometry import Frame from compas_timber.parts import CutFeature from compas_timber.parts import MillVolume -from compas_timber.connections.lap_joint import LapJoint from .joint import BeamJoinningError from .solver import JointTopology +from .lap_joint import LapJoint class LHalfLapJoint(LapJoint): diff --git a/src/compas_timber/connections/x_halflap.py b/src/compas_timber/connections/x_halflap.py index 821d1300d5..67553a2355 100644 --- a/src/compas_timber/connections/x_halflap.py +++ b/src/compas_timber/connections/x_halflap.py @@ -1,9 +1,10 @@ from compas_timber.parts import MillVolume -from compas_timber.connections import LapJoint from .solver import JointTopology from .joint import BeamJoinningError +from .lap_joint import LapJoint + class XHalfLapJoint(LapJoint): """Represents a X-Lap type joint which joins the end of a beam along the length of another beam, diff --git a/tests/compas_timber/test_joint.py b/tests/compas_timber/test_joint.py index 5fef221b7b..34a11cc547 100644 --- a/tests/compas_timber/test_joint.py +++ b/tests/compas_timber/test_joint.py @@ -94,7 +94,6 @@ def test_joint_beam_keys(mocker): assert len(list(A.graph.nodes())) == 3 assert len(list(A.graph.edges())) == 2 assert A.joints[0] == J - assert J.__data__["beams"] == [B1.key, B2.key] def test_joint_override_protection(mocker):