From fe4e6c7fb0fe296c3fa4cd683a015386db92347a Mon Sep 17 00:00:00 2001 From: jonashaldemann <104436733+jonashaldemann@users.noreply.github.com> Date: Mon, 8 Apr 2024 15:51:27 +0200 Subject: [PATCH 01/82] Sketch for Stirnversatz-Joint --- src/compas_timber/connections/__init__.py | 2 + .../connections/t_stirnversatz.py | 268 ++++++++++++++++++ .../CT_Joint_Options_TStirnversatz/code.py | 15 + .../CT_Joint_Options_TStirnversatz/icon.png | Bin 0 -> 580 bytes .../metadata.json | 26 ++ 5 files changed, 311 insertions(+) create mode 100644 src/compas_timber/connections/t_stirnversatz.py create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/icon.png create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/metadata.json diff --git a/src/compas_timber/connections/__init__.py b/src/compas_timber/connections/__init__.py index d610fac5ae..9d422da309 100644 --- a/src/compas_timber/connections/__init__.py +++ b/src/compas_timber/connections/__init__.py @@ -8,6 +8,7 @@ from .x_halflap import XHalfLapJoint from .t_halflap import THalfLapJoint from .l_halflap import LHalfLapJoint +from .t_stirnversatz import TStirnversatzJoint from .solver import ConnectionSolver from .solver import JointTopology from .solver import find_neighboring_beams @@ -24,6 +25,7 @@ "LMiterJoint", "XHalfLapJoint", "THalfLapJoint", + "TStirnversatzJoint", "LHalfLapJoint", "NullJoint", "FrenchRidgeLapJoint", diff --git a/src/compas_timber/connections/t_stirnversatz.py b/src/compas_timber/connections/t_stirnversatz.py new file mode 100644 index 0000000000..31fd5cb29a --- /dev/null +++ b/src/compas_timber/connections/t_stirnversatz.py @@ -0,0 +1,268 @@ +from .joint import Joint +from .solver import JointTopology +from .joint import BeamJoinningError +from compas_timber.parts import CutFeature +from compas_timber.parts import MillVolume +from compas.geometry import Plane, Polyhedron, Brep, Polyline, Vector, Box, Frame, Point +from compas.geometry import Rotation +from compas.geometry import intersection_plane_plane +from compas.geometry import intersection_plane_plane_plane +from compas.geometry import intersection_line_plane +from compas.geometry import intersection_polyline_plane +from compas.geometry import angle_vectors +from compas.geometry import angle_planes +from compas.geometry import distance_point_point +from compas.geometry import midpoint_line +from compas.geometry import project_point_plane +from compas.geometry import length_vector +from compas.geometry import translate_points +import math + +# BREP HACK CHEN +# from compas.artists import Artist +# import rhinoscriptsyntax as rs +# from Rhino.Geometry import Brep as RhinoBrep +# from Rhino.Geometry import Plane as RhinoPlane + + +class TStirnversatzJoint(Joint): + SUPPORTED_TOPOLOGY = JointTopology.TOPO_T + + def __init__(self, assembly=None, main_beam=None, cross_beam=None): + super(TStirnversatzJoint, self).__init__(assembly, [main_beam, cross_beam]) + self.main_beam = main_beam + self.cross_beam = cross_beam + self.main_beam_key = None + self.cross_beam_key = None + self.cut_depth = 0.25 # TODO How to make this changable by user? + self.features = [] + + @property + def data(self): + data_dict = { + "main_beam": self.main_beam_key, + "cross_beam": self.cross_beam_key, + } + data_dict.update(Joint.data.fget(self)) + return data_dict + + # @data.setter + # def data(self, value): + # Joint.data.fset(self, value) + # self.main_beam_key = value["main_beam"] + # self.cross_beam_key = value["cross_beam"] + + @property + def joint_type(self): + return "Stirnversatz" + + @property + def beams(self): + return [self.main_beam, self.cross_beam] + + @staticmethod + def _get_planes(list): + # Get Planes from Beams + output = [] + for i in list: + result = Plane.from_frame(i) + output.append(result) + return output + + @staticmethod + def _get_plane_normals(list): + output = [] + for i in list: + output.append(i[1]) + return output + + @staticmethod + def _bisector_plane(plane1, plane2, angle_factor): + bisector = plane1[1] + plane2[1] * angle_factor + intersection = intersection_plane_plane(plane1, plane2) + rotation_axis = Vector.from_start_end(*intersection) + origin = intersection[0] + R = Rotation.from_axis_and_angle(rotation_axis, math.radians(90)) + bisector.transform(R) + plane = Plane(origin, bisector) + return plane, plane1[1], plane2[1] + + @staticmethod + def _plane_between(plane1, plane2, shift_factor): + # Sets a Plane between plane1 and plane2 with a Shift Factor + vector = Vector.from_start_end(plane1[0], plane2[0]) + vector = vector * shift_factor + origin = plane1[0] + vector + return Plane(origin, plane1[1]) + + @staticmethod + def _plane_dir_correction(targetplane, operationplane): + # Flips targetplane if its normal points into the opposite direction of operationplane + a = angle_vectors(targetplane[1], operationplane[1]) + b = angle_vectors(targetplane[1] * -1, operationplane[1]) + if a > b: + return targetplane + else: + return Plane(targetplane[0], targetplane[1] * -1) + + @staticmethod + def _mesh_to_brep(negative_polyhedron): + # Show Polyhedrons + negative_polyhedron = Artist(negative_polyhedron).draw() + + # Breps from Polyhedrons + negative_brep = RhinoBrep.CreateFromMesh(rs.coercemesh(negative_polyhedron), True) + return negative_brep + + def _create_features(self): + # Get Planes and Normals for the Beam Faces + planes_a = self._get_planes(self.main_beam.faces[:4]) + planes_b = self._get_planes(self.cross_beam.faces[:4]) + normals_b = self._get_plane_normals(planes_b) + + # Find plane_a0 where cross_beam meets main_beam and shift it to position 0 in planes_a + b_centerline = self.cross_beam.centerline[0], self.cross_beam.centerline[1] + b_centerline = Polyline(b_centerline) + for idx, i in enumerate(planes_a): + intersect = intersection_polyline_plane(b_centerline, i) + if intersect != []: + plane_a0 = i + index = idx + planes_a = planes_a[index:] + planes_a[:index] + + # Sort planes_b by their angle in relation to plane_a0 + angles = [] + for i in normals_b: + angle = angle_vectors(i, plane_a0[1]) + angles.append(angle) + angles, planes_b = zip(*sorted(zip(angles, planes_b))) + + # Plane b1 as Feature for cross_beam + plane_cross_beam1 = self._bisector_plane(plane_a0, planes_b[0], 1)[0] + + # Plane b2 as Feature for cross_beam + planebetween = self._plane_between(planes_a[0], planes_a[2], self.cut_depth) + point1 = intersection_plane_plane_plane(plane_cross_beam1, planebetween, planes_b[1]) + point2 = intersection_plane_plane_plane(planes_a[0], planes_b[3], planes_b[1]) + point3 = intersection_plane_plane_plane(planes_a[0], planes_b[3], planes_b[2]) + plane_cross_beam2 = Plane.from_three_points(point1, point2, point3) + plane_cross_beam2 = self._plane_dir_correction(plane_cross_beam2, planes_b[0]) + + # Triangular Prism as Negative Volume for main_beam: Find 3 Prism Edges Lines + edge_1 = intersection_plane_plane(plane_a0, plane_cross_beam1) + edge_2 = intersection_plane_plane(plane_a0, plane_cross_beam2) + edge_3 = intersection_plane_plane(plane_cross_beam1, plane_cross_beam2) + + # Find 6 intersection Points for constructing the Prism + points = [] + points.append(intersection_line_plane(edge_1, planes_a[1])) + points.append(intersection_line_plane(edge_2, planes_a[1])) + points.append(intersection_line_plane(edge_3, planes_a[1])) + points.append(intersection_line_plane(edge_1, planes_a[3])) + points.append(intersection_line_plane(edge_2, planes_a[3])) + points.append(intersection_line_plane(edge_3, planes_a[3])) + + # Create the Prism from Points 0-5 + polyhedron = Polyhedron(points, [[0, 1, 2], [3, 4, 5], [0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]) + + # Mesh to Brep + negative_brep_main_beam = self._mesh_to_brep(polyhedron) + + return plane_cross_beam1, plane_cross_beam2, negative_brep_main_beam + + def get_main_cutting_frame(self): + assert self.beams + main_beam, cross_beam = self.beams + + _, cfr = self.get_face_most_ortho_to_beam(main_beam, cross_beam, True) + 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 + main_beam, cross_beam = self.beams + _, cfr = self.get_face_most_towards_beam(cross_beam, main_beam) + return cfr + + def get_main_intersection_frame(self): + #find the Face on main_beam where cross_beam intersects + #TODO simplify with Chen! + diagonal = math.sqrt(self.main_beam.width ** 2 + self.main_beam.height ** 2) + main_frames = self.main_beam.faces[:4] + cross_centerline = self.cross_beam.centerline + cross_centerpoint = midpoint_line(self.cross_beam.centerline) + projectionplane = self.main_beam.faces[5] + frames, distances = [], [] + for i in main_frames: + int_centerline_frame = intersection_line_plane(cross_centerline, Plane.from_frame(i)) + if int_centerline_frame == None: + pass + else: + projected_int = project_point_plane(int_centerline_frame, Plane.from_frame(projectionplane)) + distance = distance_point_point(projected_int, projectionplane.point) + if distance > diagonal / 2: + pass + else: + distance = distance_point_point(cross_centerpoint, int_centerline_frame) + distances.append(distance) + frames.append(i) + distances, frames = zip(*sorted(zip(distances, frames))) + return frames[0] + + @staticmethod + def _sort_frames_according_normals(frame, frames): + angles = [] + for i in frames: + angles.append(angle_vectors(frame.normal, i.normal)) + angles, frames = zip(*sorted(zip(angles, frames))) + return frames + + @staticmethod + def _rotation_plane(plane1, plane2): + line = intersection_plane_plane(plane1, plane2) + vector = Vector.from_start_end(line[0], line[1]) + plane = Plane(line[0], vector) + return plane + + @staticmethod + def _angle_plane_normals(plane1, plane2): + return angle_vectors(plane1.normal, plane2.normal) + + + def add_features(self): + # Cross Cutting Plane 1 + main_intersection_frame = self.get_main_intersection_frame() + main_intersection_plane = Plane.from_frame(main_intersection_frame) + cross_frames = self.cross_beam.faces + cross_frames_sorted = self._sort_frames_according_normals(main_intersection_frame, cross_frames[:4]) + cross_frame = cross_frames_sorted[0] + bisector_plane = self._bisector_plane(main_intersection_plane, Plane.from_frame(cross_frame), 0.5) + cross_cutting_plane1 = bisector_plane[0] + + # Cut Depth + cut_depth_point = project_point_plane(self.main_beam.frame.point, main_intersection_plane) + cut_depth = distance_point_point(self.main_beam.frame.point, cut_depth_point) / 2 #TODO implement cut depth factor + + # SplitPlane + split_plane = Plane(main_intersection_frame.point, main_intersection_frame.yaxis) + + # Cross Cutting Plane 2 + p1 = intersection_plane_plane_plane(Plane.from_frame(main_intersection_frame), Plane.from_frame(cross_frames_sorted[3]), split_plane) + origin = translate_points([p1], main_intersection_frame.zaxis * -cut_depth)[0] + cut_depth_plane = Plane(origin, main_intersection_frame.zaxis) + p2 = intersection_plane_plane_plane(cut_depth_plane, bisector_plane[0], split_plane) + cross_cutting_plane2 = Plane.from_frame(Frame(p1, Vector.from_start_end(p1, p2), split_plane.normal)) + + # Main Cutting Volume + l1 = intersection_plane_plane(cross_cutting_plane1, main_intersection_plane) + l2 = intersection_plane_plane(cross_cutting_plane1, cross_cutting_plane2) + l3 = intersection_plane_plane(cross_cutting_plane2, main_intersection_plane) + main_frames_sorted = self._sort_frames_according_normals(main_intersection_frame, self.main_beam.faces[:4]) + p4 = Plane.from_frame(main_frames_sorted[1]) + p5 = Plane.from_frame(main_frames_sorted[3]) + #TODO Polyhedron!!! + + self.cross_beam.add_features(cross_cutting_plane1) + self.cross_beam.add_features(cross_cutting_plane2) + + return cross_cutting_plane1, cross_cutting_plane2 \ No newline at end of file diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py new file mode 100644 index 0000000000..b7fc47e7dd --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py @@ -0,0 +1,15 @@ +from ghpythonlib.componentbase import executingcomponent as component + + +from compas_timber.connections import TStirnversatzJoint +from compas_timber.ghpython import JointOptions + + +class TStirnversatzJointOptions(component): + def RunScript(self, Gap): + args = {} + if Gap: + args["gap"] = Gap + options = JointOptions(TButtJoint, **args) + + return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..053ab8dd589ed1cf9e82f82ac2d240b5237cf108 GIT binary patch literal 580 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_s7& zo-U3d9-T>l{{OdUUfD2lVqnAM$)8n(g(tuI^769e)hkyvNFO|K0H~ee|No>#OCRz8 z0feV?k|~HcJs}|h%Du%@#IwKjwHPy4tLJZYHBC&wn6b2XcWH9m-%$1`t`NjSaVuxJ}Ux+=nG4 zBtRYp>Sc;{&_?1Wr6eRQn2F@I=8*}GSgi<|qQ*7>>C?B-vNc8l}a z*x2xV`}U2etgMX3&CQKx&z?O$_MU=;8q-gPH$@W;o;`au;h=u}z6M^_i>p?xN^o|Z zCM;Z|(a_k)2yyps1~raN39qlMEf8zopr04$&18~^|S literal 0 HcmV?d00001 diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/metadata.json new file mode 100644 index 0000000000..ba051cff69 --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/metadata.json @@ -0,0 +1,26 @@ +{ + "name": "T-Stirnversatz Options", + "nickname": "T-Stirnversatz", + "category": "COMPAS Timber", + "subcategory": "Joints", + "description": "defines and gives access to T-Stirnversatz joint and its parameters", + "exposure": 2, + "ghpython": { + "isAdvancedMode": true, + "iconDisplay": 0, + "inputParameters": [ + { + "name": "Gap", + "description": "Gap for tolerance.", + "typeHintID": "float", + "scriptParamAccess": 0 + } + ], + "outputParameters": [ + { + "name": "T-Stirnversatz", + "description": "T-Stirnversatz Joint Options." + } + ] + } +} From ffc67033a5d2a5902a39d2472d8eda4be182a7f6 Mon Sep 17 00:00:00 2001 From: jonashaldemann <104436733+jonashaldemann@users.noreply.github.com> Date: Mon, 8 Apr 2024 16:54:38 +0200 Subject: [PATCH 02/82] stirnversatz joint created --- .../connections/t_stirnversatz.py | 135 ++++-------------- 1 file changed, 27 insertions(+), 108 deletions(-) diff --git a/src/compas_timber/connections/t_stirnversatz.py b/src/compas_timber/connections/t_stirnversatz.py index 31fd5cb29a..ccd7e5b235 100644 --- a/src/compas_timber/connections/t_stirnversatz.py +++ b/src/compas_timber/connections/t_stirnversatz.py @@ -60,22 +60,6 @@ def joint_type(self): def beams(self): return [self.main_beam, self.cross_beam] - @staticmethod - def _get_planes(list): - # Get Planes from Beams - output = [] - for i in list: - result = Plane.from_frame(i) - output.append(result) - return output - - @staticmethod - def _get_plane_normals(list): - output = [] - for i in list: - output.append(i[1]) - return output - @staticmethod def _bisector_plane(plane1, plane2, angle_factor): bisector = plane1[1] + plane2[1] * angle_factor @@ -86,90 +70,8 @@ def _bisector_plane(plane1, plane2, angle_factor): bisector.transform(R) plane = Plane(origin, bisector) return plane, plane1[1], plane2[1] - - @staticmethod - def _plane_between(plane1, plane2, shift_factor): - # Sets a Plane between plane1 and plane2 with a Shift Factor - vector = Vector.from_start_end(plane1[0], plane2[0]) - vector = vector * shift_factor - origin = plane1[0] + vector - return Plane(origin, plane1[1]) - - @staticmethod - def _plane_dir_correction(targetplane, operationplane): - # Flips targetplane if its normal points into the opposite direction of operationplane - a = angle_vectors(targetplane[1], operationplane[1]) - b = angle_vectors(targetplane[1] * -1, operationplane[1]) - if a > b: - return targetplane - else: - return Plane(targetplane[0], targetplane[1] * -1) - - @staticmethod - def _mesh_to_brep(negative_polyhedron): - # Show Polyhedrons - negative_polyhedron = Artist(negative_polyhedron).draw() - - # Breps from Polyhedrons - negative_brep = RhinoBrep.CreateFromMesh(rs.coercemesh(negative_polyhedron), True) - return negative_brep - - def _create_features(self): - # Get Planes and Normals for the Beam Faces - planes_a = self._get_planes(self.main_beam.faces[:4]) - planes_b = self._get_planes(self.cross_beam.faces[:4]) - normals_b = self._get_plane_normals(planes_b) - - # Find plane_a0 where cross_beam meets main_beam and shift it to position 0 in planes_a - b_centerline = self.cross_beam.centerline[0], self.cross_beam.centerline[1] - b_centerline = Polyline(b_centerline) - for idx, i in enumerate(planes_a): - intersect = intersection_polyline_plane(b_centerline, i) - if intersect != []: - plane_a0 = i - index = idx - planes_a = planes_a[index:] + planes_a[:index] - - # Sort planes_b by their angle in relation to plane_a0 - angles = [] - for i in normals_b: - angle = angle_vectors(i, plane_a0[1]) - angles.append(angle) - angles, planes_b = zip(*sorted(zip(angles, planes_b))) - - # Plane b1 as Feature for cross_beam - plane_cross_beam1 = self._bisector_plane(plane_a0, planes_b[0], 1)[0] - - # Plane b2 as Feature for cross_beam - planebetween = self._plane_between(planes_a[0], planes_a[2], self.cut_depth) - point1 = intersection_plane_plane_plane(plane_cross_beam1, planebetween, planes_b[1]) - point2 = intersection_plane_plane_plane(planes_a[0], planes_b[3], planes_b[1]) - point3 = intersection_plane_plane_plane(planes_a[0], planes_b[3], planes_b[2]) - plane_cross_beam2 = Plane.from_three_points(point1, point2, point3) - plane_cross_beam2 = self._plane_dir_correction(plane_cross_beam2, planes_b[0]) - - # Triangular Prism as Negative Volume for main_beam: Find 3 Prism Edges Lines - edge_1 = intersection_plane_plane(plane_a0, plane_cross_beam1) - edge_2 = intersection_plane_plane(plane_a0, plane_cross_beam2) - edge_3 = intersection_plane_plane(plane_cross_beam1, plane_cross_beam2) - - # Find 6 intersection Points for constructing the Prism - points = [] - points.append(intersection_line_plane(edge_1, planes_a[1])) - points.append(intersection_line_plane(edge_2, planes_a[1])) - points.append(intersection_line_plane(edge_3, planes_a[1])) - points.append(intersection_line_plane(edge_1, planes_a[3])) - points.append(intersection_line_plane(edge_2, planes_a[3])) - points.append(intersection_line_plane(edge_3, planes_a[3])) - - # Create the Prism from Points 0-5 - polyhedron = Polyhedron(points, [[0, 1, 2], [3, 4, 5], [0, 3, 4, 1], [1, 4, 5, 2], [2, 5, 3, 0]]) - - # Mesh to Brep - negative_brep_main_beam = self._mesh_to_brep(polyhedron) - - return plane_cross_beam1, plane_cross_beam2, negative_brep_main_beam + #TODO Remove if not used def get_main_cutting_frame(self): assert self.beams main_beam, cross_beam = self.beams @@ -178,15 +80,16 @@ def get_main_cutting_frame(self): cfr = Frame(cfr.point, cfr.yaxis, cfr.xaxis) # flip normal towards the inside of main beam return cfr + # TODO Remove if not used def get_cross_cutting_frame(self): assert self.beams main_beam, cross_beam = self.beams _, cfr = self.get_face_most_towards_beam(cross_beam, main_beam) return cfr + #find the Face on main_beam where cross_beam intersects + #TODO simplify with Chen! def get_main_intersection_frame(self): - #find the Face on main_beam where cross_beam intersects - #TODO simplify with Chen! diagonal = math.sqrt(self.main_beam.width ** 2 + self.main_beam.height ** 2) main_frames = self.main_beam.faces[:4] cross_centerline = self.cross_beam.centerline @@ -254,15 +157,31 @@ def add_features(self): cross_cutting_plane2 = Plane.from_frame(Frame(p1, Vector.from_start_end(p1, p2), split_plane.normal)) # Main Cutting Volume - l1 = intersection_plane_plane(cross_cutting_plane1, main_intersection_plane) - l2 = intersection_plane_plane(cross_cutting_plane1, cross_cutting_plane2) - l3 = intersection_plane_plane(cross_cutting_plane2, main_intersection_plane) + l1 = intersection_plane_plane(main_intersection_plane, cross_cutting_plane1) + l2 = intersection_plane_plane(main_intersection_plane, cross_cutting_plane2) + l3 = intersection_plane_plane(cross_cutting_plane1, cross_cutting_plane2) main_frames_sorted = self._sort_frames_according_normals(main_intersection_frame, self.main_beam.faces[:4]) - p4 = Plane.from_frame(main_frames_sorted[1]) - p5 = Plane.from_frame(main_frames_sorted[3]) - #TODO Polyhedron!!! + pl1 = Plane.from_frame(main_frames_sorted[1]) + pl2 = Plane.from_frame(main_frames_sorted[3]) + lines = [l1, l2, l3] + points = [] + for i in lines: + points.append(intersection_line_plane(i, pl1)) + points.append(intersection_line_plane(i, pl2)) + + main_cutting_volume = Polyhedron(points, + [ + [0, 1, 2], # front + [5, 4, 3], # back + [0, 3, 4, 1], # first + [1, 4, 5, 2], # second + [2, 5, 3, 0], # third + ], + ) self.cross_beam.add_features(cross_cutting_plane1) self.cross_beam.add_features(cross_cutting_plane2) - return cross_cutting_plane1, cross_cutting_plane2 \ No newline at end of file + volume = MillVolume(main_cutting_volume) + self.main_beam.add_features(volume) + self.features = volume \ No newline at end of file From ff7c55bb142af79d6c5a55e197ef8e952722f06b Mon Sep 17 00:00:00 2001 From: jonashaldemann <104436733+jonashaldemann@users.noreply.github.com> Date: Mon, 8 Apr 2024 16:58:05 +0200 Subject: [PATCH 03/82] correction gh component --- .../ghpython/components/CT_Joint_Options_TStirnversatz/code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py index b7fc47e7dd..263f3b58dd 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py @@ -10,6 +10,6 @@ def RunScript(self, Gap): args = {} if Gap: args["gap"] = Gap - options = JointOptions(TButtJoint, **args) + options = JointOptions(TStirnversatzJoint, **args) return options From 67d06fae64ca9c19e0dc94ebd8e96fbcc4497fae Mon Sep 17 00:00:00 2001 From: jonashaldemann <104436733+jonashaldemann@users.noreply.github.com> Date: Mon, 8 Apr 2024 21:57:22 +0200 Subject: [PATCH 04/82] adjustments --- .../connections/t_stirnversatz.py | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/compas_timber/connections/t_stirnversatz.py b/src/compas_timber/connections/t_stirnversatz.py index ccd7e5b235..d4fc5d32c0 100644 --- a/src/compas_timber/connections/t_stirnversatz.py +++ b/src/compas_timber/connections/t_stirnversatz.py @@ -3,33 +3,25 @@ from .joint import BeamJoinningError from compas_timber.parts import CutFeature from compas_timber.parts import MillVolume -from compas.geometry import Plane, Polyhedron, Brep, Polyline, Vector, Box, Frame, Point +from compas.geometry import Plane, Polyhedron, Vector, Frame from compas.geometry import Rotation from compas.geometry import intersection_plane_plane from compas.geometry import intersection_plane_plane_plane from compas.geometry import intersection_line_plane -from compas.geometry import intersection_polyline_plane from compas.geometry import angle_vectors -from compas.geometry import angle_planes from compas.geometry import distance_point_point from compas.geometry import midpoint_line from compas.geometry import project_point_plane -from compas.geometry import length_vector from compas.geometry import translate_points import math -# BREP HACK CHEN -# from compas.artists import Artist -# import rhinoscriptsyntax as rs -# from Rhino.Geometry import Brep as RhinoBrep -# from Rhino.Geometry import Plane as RhinoPlane - class TStirnversatzJoint(Joint): + SUPPORTED_TOPOLOGY = JointTopology.TOPO_T - def __init__(self, assembly=None, main_beam=None, cross_beam=None): - super(TStirnversatzJoint, self).__init__(assembly, [main_beam, cross_beam]) + def __init__(self, cross_beam=None, main_beam=None): #TODO Why main & cross swapped??? + super(TStirnversatzJoint, self).__init__(main_beam, cross_beam) self.main_beam = main_beam self.cross_beam = cross_beam self.main_beam_key = None @@ -40,8 +32,8 @@ def __init__(self, assembly=None, main_beam=None, cross_beam=None): @property def data(self): data_dict = { - "main_beam": self.main_beam_key, "cross_beam": self.cross_beam_key, + "main_beam": self.main_beam_key, } data_dict.update(Joint.data.fget(self)) return data_dict @@ -49,8 +41,8 @@ def data(self): # @data.setter # def data(self, value): # Joint.data.fset(self, value) - # self.main_beam_key = value["main_beam"] # self.cross_beam_key = value["cross_beam"] + # self.main_beam_key = value["main_beam"] @property def joint_type(self): @@ -74,7 +66,7 @@ def _bisector_plane(plane1, plane2, angle_factor): #TODO Remove if not used def get_main_cutting_frame(self): assert self.beams - main_beam, cross_beam = self.beams + cross_beam, main_beam = self.beams _, cfr = self.get_face_most_ortho_to_beam(main_beam, cross_beam, True) cfr = Frame(cfr.point, cfr.yaxis, cfr.xaxis) # flip normal towards the inside of main beam @@ -83,11 +75,11 @@ def get_main_cutting_frame(self): # TODO Remove if not used def get_cross_cutting_frame(self): assert self.beams - main_beam, cross_beam = self.beams - _, cfr = self.get_face_most_towards_beam(cross_beam, main_beam) + cross_beam, main_beam = self.beams + _, cfr = self.get_face_most_towards_beam(main_beam, cross_beam) return cfr - #find the Face on main_beam where cross_beam intersects + #find the Face on cross_beam where main_beam intersects #TODO simplify with Chen! def get_main_intersection_frame(self): diagonal = math.sqrt(self.main_beam.width ** 2 + self.main_beam.height ** 2) @@ -120,6 +112,7 @@ def _sort_frames_according_normals(frame, frames): angles, frames = zip(*sorted(zip(angles, frames))) return frames + #TODO Delete if not needed @staticmethod def _rotation_plane(plane1, plane2): line = intersection_plane_plane(plane1, plane2) @@ -127,12 +120,15 @@ def _rotation_plane(plane1, plane2): plane = Plane(line[0], vector) return plane + #TODO Delete if not needed @staticmethod def _angle_plane_normals(plane1, plane2): return angle_vectors(plane1.normal, plane2.normal) - def add_features(self): + + assert self.main_beam and self.cross_beam # should never happen + # Cross Cutting Plane 1 main_intersection_frame = self.get_main_intersection_frame() main_intersection_plane = Plane.from_frame(main_intersection_frame) @@ -162,7 +158,7 @@ def add_features(self): l3 = intersection_plane_plane(cross_cutting_plane1, cross_cutting_plane2) main_frames_sorted = self._sort_frames_according_normals(main_intersection_frame, self.main_beam.faces[:4]) pl1 = Plane.from_frame(main_frames_sorted[1]) - pl2 = Plane.from_frame(main_frames_sorted[3]) + pl2 = Plane.from_frame(main_frames_sorted[2]) lines = [l1, l2, l3] points = [] for i in lines: @@ -171,17 +167,21 @@ def add_features(self): main_cutting_volume = Polyhedron(points, [ - [0, 1, 2], # front - [5, 4, 3], # back - [0, 3, 4, 1], # first - [1, 4, 5, 2], # second - [2, 5, 3, 0], # third + [0, 2, 4], # front + [1, 5, 3], # back + [0, 1, 3, 2], # first + [2, 3, 5, 4], # second + [4, 5, 1, 0], # third ], ) + + print(main_cutting_volume) #TODO just for debugging, remove... + print("polyhedron is closed: " + str(main_cutting_volume.is_closed())) #TODO just for debugging, remove... - self.cross_beam.add_features(cross_cutting_plane1) - self.cross_beam.add_features(cross_cutting_plane2) + trim_feature = CutFeature(cross_cutting_plane1) + self.cross_beam.add_features(trim_feature) + trim_feature = CutFeature(cross_cutting_plane2) + self.cross_beam.add_features(trim_feature) volume = MillVolume(main_cutting_volume) - self.main_beam.add_features(volume) - self.features = volume \ No newline at end of file + self.main_beam.add_features(volume) \ No newline at end of file From 0bdc58c13efc5738e3df316ac2a5321db863d15a Mon Sep 17 00:00:00 2001 From: jonashaldemann <104436733+jonashaldemann@users.noreply.github.com> Date: Wed, 10 Apr 2024 10:39:42 +0200 Subject: [PATCH 05/82] changes --- .../connections/t_stirnversatz.py | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/compas_timber/connections/t_stirnversatz.py b/src/compas_timber/connections/t_stirnversatz.py index d4fc5d32c0..850a588307 100644 --- a/src/compas_timber/connections/t_stirnversatz.py +++ b/src/compas_timber/connections/t_stirnversatz.py @@ -124,39 +124,39 @@ def _rotation_plane(plane1, plane2): @staticmethod def _angle_plane_normals(plane1, plane2): return angle_vectors(plane1.normal, plane2.normal) + + def get_cross_cutting_planes(self): + main_int_frame = self.get_main_intersection_frame() + main_int_plane = Plane.from_frame(main_int_frame) + cross_faces = self.cross_beam.faces[:4] + cross_faces_sorted = self._sort_frames_according_normals(main_int_frame, cross_faces) + cross_face = Plane.from_frame(cross_faces_sorted[0]) + cutplane_1 = self._bisector_plane(main_int_plane, cross_face, 0.5) + + cut_depth_point = project_point_plane(self.main_beam.frame.point, main_int_plane) + cut_depth = distance_point_point(self.main_beam.frame.point, cut_depth_point) / 2 #TODO implement cut depth factor + split_plane = Plane(main_int_frame.point, main_int_frame.yaxis) + p1 = intersection_plane_plane_plane(main_int_plane, Plane.from_frame(cross_faces_sorted[3]), split_plane) + origin = translate_points([p1], main_int_frame.zaxis * -cut_depth)[0] + cut_depth_plane = Plane(origin, main_int_frame.zaxis) + p2 = intersection_plane_plane_plane(cut_depth_plane, cutplane_1[0], split_plane) + cutplane_2 = Plane.from_frame(Frame(p1, Vector.from_start_end(p1, p2), split_plane.normal)) + print(cutplane_1[0], cutplane_2[0]) + return cutplane_1[0], cutplane_2 def add_features(self): assert self.main_beam and self.cross_beam # should never happen - # Cross Cutting Plane 1 - main_intersection_frame = self.get_main_intersection_frame() - main_intersection_plane = Plane.from_frame(main_intersection_frame) - cross_frames = self.cross_beam.faces - cross_frames_sorted = self._sort_frames_according_normals(main_intersection_frame, cross_frames[:4]) - cross_frame = cross_frames_sorted[0] - bisector_plane = self._bisector_plane(main_intersection_plane, Plane.from_frame(cross_frame), 0.5) - cross_cutting_plane1 = bisector_plane[0] - - # Cut Depth - cut_depth_point = project_point_plane(self.main_beam.frame.point, main_intersection_plane) - cut_depth = distance_point_point(self.main_beam.frame.point, cut_depth_point) / 2 #TODO implement cut depth factor - - # SplitPlane - split_plane = Plane(main_intersection_frame.point, main_intersection_frame.yaxis) - - # Cross Cutting Plane 2 - p1 = intersection_plane_plane_plane(Plane.from_frame(main_intersection_frame), Plane.from_frame(cross_frames_sorted[3]), split_plane) - origin = translate_points([p1], main_intersection_frame.zaxis * -cut_depth)[0] - cut_depth_plane = Plane(origin, main_intersection_frame.zaxis) - p2 = intersection_plane_plane_plane(cut_depth_plane, bisector_plane[0], split_plane) - cross_cutting_plane2 = Plane.from_frame(Frame(p1, Vector.from_start_end(p1, p2), split_plane.normal)) + cross_cutting_plane1, cross_cutting_plane2 = self.get_cross_cutting_planes() # Main Cutting Volume - l1 = intersection_plane_plane(main_intersection_plane, cross_cutting_plane1) - l2 = intersection_plane_plane(main_intersection_plane, cross_cutting_plane2) + main_int_frame = self.get_main_intersection_frame() + main_int_plane = Plane.from_frame(main_int_frame) + l1 = intersection_plane_plane(main_int_plane, cross_cutting_plane1) + l2 = intersection_plane_plane(main_int_plane, cross_cutting_plane2) l3 = intersection_plane_plane(cross_cutting_plane1, cross_cutting_plane2) - main_frames_sorted = self._sort_frames_according_normals(main_intersection_frame, self.main_beam.faces[:4]) + main_frames_sorted = self._sort_frames_according_normals(main_int_frame, self.main_beam.faces[:4]) pl1 = Plane.from_frame(main_frames_sorted[1]) pl2 = Plane.from_frame(main_frames_sorted[2]) lines = [l1, l2, l3] From 6a151b629b6de6b50e14393f1d4dbabf82491e05 Mon Sep 17 00:00:00 2001 From: jonashaldemann <104436733+jonashaldemann@users.noreply.github.com> Date: Thu, 11 Apr 2024 10:32:42 +0200 Subject: [PATCH 06/82] corrections --- .../connections/t_stirnversatz.py | 110 ++++++++++-------- 1 file changed, 59 insertions(+), 51 deletions(-) diff --git a/src/compas_timber/connections/t_stirnversatz.py b/src/compas_timber/connections/t_stirnversatz.py index 850a588307..3621456249 100644 --- a/src/compas_timber/connections/t_stirnversatz.py +++ b/src/compas_timber/connections/t_stirnversatz.py @@ -28,6 +28,12 @@ def __init__(self, cross_beam=None, main_beam=None): #TODO Why main & cross swap self.cross_beam_key = None self.cut_depth = 0.25 # TODO How to make this changable by user? self.features = [] + self.cross_cutting_plane_1 = None + self.cross_cutting_plane_2 = None + self.planetogh = [] # TODO Remove + self.linetogh = [] # TODO Remove + self.pointtogh = [] # TODO Remove + self.polyhedrontogh = [] # TODO Remove @property def data(self): @@ -54,31 +60,15 @@ def beams(self): @staticmethod def _bisector_plane(plane1, plane2, angle_factor): - bisector = plane1[1] + plane2[1] * angle_factor + bisector = plane1.normal + plane2.normal * angle_factor intersection = intersection_plane_plane(plane1, plane2) rotation_axis = Vector.from_start_end(*intersection) origin = intersection[0] R = Rotation.from_axis_and_angle(rotation_axis, math.radians(90)) bisector.transform(R) plane = Plane(origin, bisector) - return plane, plane1[1], plane2[1] - - #TODO Remove if not used - def get_main_cutting_frame(self): - assert self.beams - cross_beam, main_beam = self.beams - - _, cfr = self.get_face_most_ortho_to_beam(main_beam, cross_beam, True) - cfr = Frame(cfr.point, cfr.yaxis, cfr.xaxis) # flip normal towards the inside of main beam - return cfr - - # TODO Remove if not used - def get_cross_cutting_frame(self): - assert self.beams - cross_beam, main_beam = self.beams - _, cfr = self.get_face_most_towards_beam(main_beam, cross_beam) - return cfr - + return plane + #find the Face on cross_beam where main_beam intersects #TODO simplify with Chen! def get_main_intersection_frame(self): @@ -112,19 +102,20 @@ def _sort_frames_according_normals(frame, frames): angles, frames = zip(*sorted(zip(angles, frames))) return frames - #TODO Delete if not needed @staticmethod - def _rotation_plane(plane1, plane2): - line = intersection_plane_plane(plane1, plane2) - vector = Vector.from_start_end(line[0], line[1]) - plane = Plane(line[0], vector) - return plane + def _get_vector_most_same_direction(vectors, checkvector): + angles = [] + for i in vectors: + angles.append(angle_vectors(i, checkvector)) + angles, vectors = zip(*sorted(zip(angles, vectors))) + return vectors[0] - #TODO Delete if not needed @staticmethod - def _angle_plane_normals(plane1, plane2): - return angle_vectors(plane1.normal, plane2.normal) - + def _flip_plane_according_vector(plane, vector): + if angle_vectors(plane.normal, vector, True) > 90: + plane = Plane(plane.point, plane.normal * -1) + return plane + def get_cross_cutting_planes(self): main_int_frame = self.get_main_intersection_frame() main_int_plane = Plane.from_frame(main_int_frame) @@ -132,39 +123,42 @@ def get_cross_cutting_planes(self): cross_faces_sorted = self._sort_frames_according_normals(main_int_frame, cross_faces) cross_face = Plane.from_frame(cross_faces_sorted[0]) cutplane_1 = self._bisector_plane(main_int_plane, cross_face, 0.5) - cut_depth_point = project_point_plane(self.main_beam.frame.point, main_int_plane) cut_depth = distance_point_point(self.main_beam.frame.point, cut_depth_point) / 2 #TODO implement cut depth factor split_plane = Plane(main_int_frame.point, main_int_frame.yaxis) p1 = intersection_plane_plane_plane(main_int_plane, Plane.from_frame(cross_faces_sorted[3]), split_plane) origin = translate_points([p1], main_int_frame.zaxis * -cut_depth)[0] cut_depth_plane = Plane(origin, main_int_frame.zaxis) - p2 = intersection_plane_plane_plane(cut_depth_plane, cutplane_1[0], split_plane) + p2 = intersection_plane_plane_plane(cut_depth_plane, cutplane_1, split_plane) cutplane_2 = Plane.from_frame(Frame(p1, Vector.from_start_end(p1, p2), split_plane.normal)) - print(cutplane_1[0], cutplane_2[0]) - return cutplane_1[0], cutplane_2 - - def add_features(self): + cutplane_2 = self._flip_plane_according_vector(cutplane_2, main_int_frame.zaxis * -1) - assert self.main_beam and self.cross_beam # should never happen - - cross_cutting_plane1, cross_cutting_plane2 = self.get_cross_cutting_planes() - - # Main Cutting Volume + self.cross_cutting_plane_1 = cutplane_1 + self.cross_cutting_plane_2 = cutplane_2 + return self.cross_cutting_plane_1, self.cross_cutting_plane_2 + + def get_main_cutting_volume(self): main_int_frame = self.get_main_intersection_frame() main_int_plane = Plane.from_frame(main_int_frame) - l1 = intersection_plane_plane(main_int_plane, cross_cutting_plane1) - l2 = intersection_plane_plane(main_int_plane, cross_cutting_plane2) - l3 = intersection_plane_plane(cross_cutting_plane1, cross_cutting_plane2) + l1 = intersection_plane_plane(main_int_plane, self.cross_cutting_plane_1) + l2 = intersection_plane_plane(main_int_plane, self.cross_cutting_plane_2) + l3 = intersection_plane_plane(self.cross_cutting_plane_1, self.cross_cutting_plane_2) main_frames_sorted = self._sort_frames_according_normals(main_int_frame, self.main_beam.faces[:4]) - pl1 = Plane.from_frame(main_frames_sorted[1]) - pl2 = Plane.from_frame(main_frames_sorted[2]) + plane_side_1 = Plane.from_frame(main_frames_sorted[1]) + plane_side_2 = Plane.from_frame(main_frames_sorted[2]) lines = [l1, l2, l3] points = [] for i in lines: - points.append(intersection_line_plane(i, pl1)) - points.append(intersection_line_plane(i, pl2)) + points.append(intersection_line_plane(i, plane_side_1)) + points.append(intersection_line_plane(i, plane_side_2)) + + self.linetogh.append(l1) + self.linetogh.append(l2) + self.linetogh.append(l3) + self.pointtogh = points + + #TODO fix with Chen: Polyhedron.from_planes not working because numpy missing ????? main_cutting_volume = Polyhedron(points, [ [0, 2, 4], # front @@ -174,14 +168,28 @@ def add_features(self): [4, 5, 1, 0], # third ], ) + main_cutting_volume = Polyhedron(points, [[0, 2, 4]]) - print(main_cutting_volume) #TODO just for debugging, remove... - print("polyhedron is closed: " + str(main_cutting_volume.is_closed())) #TODO just for debugging, remove... + planes = [main_int_plane, self.cross_cutting_plane_1, self.cross_cutting_plane_2, plane_side_1, plane_side_2] + self.planetogh = planes + #main_cutting_volume = Polyhedron.from_planes(planes) + + self.polyhedrontogh = main_cutting_volume + + return main_cutting_volume + + def add_features(self): + + assert self.main_beam and self.cross_beam # should never happen + + cross_cutting_plane1, cross_cutting_plane2 = self.get_cross_cutting_planes() + main_cutting_vol = self.get_main_cutting_volume() + + print("polyhedron is closed: " + str(main_cutting_vol.is_closed())) #TODO just for debugging, remove... trim_feature = CutFeature(cross_cutting_plane1) self.cross_beam.add_features(trim_feature) trim_feature = CutFeature(cross_cutting_plane2) self.cross_beam.add_features(trim_feature) - - volume = MillVolume(main_cutting_volume) + volume = MillVolume(main_cutting_vol) self.main_beam.add_features(volume) \ No newline at end of file From c71afa719af274008bf942382282de510b49da72 Mon Sep 17 00:00:00 2001 From: jonashaldemann <104436733+jonashaldemann@users.noreply.github.com> Date: Thu, 11 Apr 2024 14:46:26 +0200 Subject: [PATCH 07/82] errors removed, CutDepth option implemented --- .../connections/t_stirnversatz.py | 46 +++++++----------- .../CT_Joint_Options_TStirnversatz/code.py | 4 +- .../CT_Joint_Options_TStirnversatz/icon.png | Bin 580 -> 841 bytes .../metadata.json | 6 +++ 4 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/compas_timber/connections/t_stirnversatz.py b/src/compas_timber/connections/t_stirnversatz.py index 3621456249..c26c3351c3 100644 --- a/src/compas_timber/connections/t_stirnversatz.py +++ b/src/compas_timber/connections/t_stirnversatz.py @@ -3,7 +3,7 @@ from .joint import BeamJoinningError from compas_timber.parts import CutFeature from compas_timber.parts import MillVolume -from compas.geometry import Plane, Polyhedron, Vector, Frame +from compas.geometry import Plane, Polyhedron, Vector, Frame, Point from compas.geometry import Rotation from compas.geometry import intersection_plane_plane from compas.geometry import intersection_plane_plane_plane @@ -13,20 +13,20 @@ from compas.geometry import midpoint_line from compas.geometry import project_point_plane from compas.geometry import translate_points +from compas.geometry import cross_vectors import math - class TStirnversatzJoint(Joint): SUPPORTED_TOPOLOGY = JointTopology.TOPO_T - def __init__(self, cross_beam=None, main_beam=None): #TODO Why main & cross swapped??? - super(TStirnversatzJoint, self).__init__(main_beam, cross_beam) + def __init__(self, cross_beam=None, main_beam=None, cut_depth=0.25): #TODO Why main & cross swapped??? + super(TStirnversatzJoint, self).__init__(main_beam, cross_beam, cut_depth) self.main_beam = main_beam self.cross_beam = cross_beam self.main_beam_key = None self.cross_beam_key = None - self.cut_depth = 0.25 # TODO How to make this changable by user? + self.cut_depth = cut_depth self.features = [] self.cross_cutting_plane_1 = None self.cross_cutting_plane_2 = None @@ -95,10 +95,10 @@ def get_main_intersection_frame(self): return frames[0] @staticmethod - def _sort_frames_according_normals(frame, frames): + def _sort_frames_according_normals(frames, checkvector): angles = [] for i in frames: - angles.append(angle_vectors(frame.normal, i.normal)) + angles.append(angle_vectors(checkvector, i.normal)) angles, frames = zip(*sorted(zip(angles, frames))) return frames @@ -120,11 +120,11 @@ def get_cross_cutting_planes(self): main_int_frame = self.get_main_intersection_frame() main_int_plane = Plane.from_frame(main_int_frame) cross_faces = self.cross_beam.faces[:4] - cross_faces_sorted = self._sort_frames_according_normals(main_int_frame, cross_faces) + cross_faces_sorted = self._sort_frames_according_normals(cross_faces, main_int_frame.zaxis) cross_face = Plane.from_frame(cross_faces_sorted[0]) cutplane_1 = self._bisector_plane(main_int_plane, cross_face, 0.5) cut_depth_point = project_point_plane(self.main_beam.frame.point, main_int_plane) - cut_depth = distance_point_point(self.main_beam.frame.point, cut_depth_point) / 2 #TODO implement cut depth factor + cut_depth = distance_point_point(self.main_beam.frame.point, cut_depth_point) * self.cut_depth * 2 split_plane = Plane(main_int_frame.point, main_int_frame.yaxis) p1 = intersection_plane_plane_plane(main_int_plane, Plane.from_frame(cross_faces_sorted[3]), split_plane) origin = translate_points([p1], main_int_frame.zaxis * -cut_depth)[0] @@ -143,20 +143,17 @@ def get_main_cutting_volume(self): l1 = intersection_plane_plane(main_int_plane, self.cross_cutting_plane_1) l2 = intersection_plane_plane(main_int_plane, self.cross_cutting_plane_2) l3 = intersection_plane_plane(self.cross_cutting_plane_1, self.cross_cutting_plane_2) - main_frames_sorted = self._sort_frames_according_normals(main_int_frame, self.main_beam.faces[:4]) - plane_side_1 = Plane.from_frame(main_frames_sorted[1]) - plane_side_2 = Plane.from_frame(main_frames_sorted[2]) + main_frames_sorted = self._sort_frames_according_normals(self.main_beam.faces[:4], main_int_frame.zaxis) + plane_side = [Plane.from_frame(main_frames_sorted[1]), Plane.from_frame(main_frames_sorted[2])] + + crossvector = cross_vectors(self.cross_cutting_plane_2.normal, self.cross_cutting_plane_1.normal) + plane_side = self._sort_frames_according_normals(plane_side, crossvector) + lines = [l1, l2, l3] points = [] for i in lines: - points.append(intersection_line_plane(i, plane_side_1)) - points.append(intersection_line_plane(i, plane_side_2)) - - self.linetogh.append(l1) - self.linetogh.append(l2) - self.linetogh.append(l3) - - self.pointtogh = points + points.append(intersection_line_plane(i, plane_side[0])) + points.append(intersection_line_plane(i, plane_side[1])) #TODO fix with Chen: Polyhedron.from_planes not working because numpy missing ????? main_cutting_volume = Polyhedron(points, @@ -168,13 +165,6 @@ def get_main_cutting_volume(self): [4, 5, 1, 0], # third ], ) - main_cutting_volume = Polyhedron(points, [[0, 2, 4]]) - - planes = [main_int_plane, self.cross_cutting_plane_1, self.cross_cutting_plane_2, plane_side_1, plane_side_2] - self.planetogh = planes - #main_cutting_volume = Polyhedron.from_planes(planes) - - self.polyhedrontogh = main_cutting_volume return main_cutting_volume @@ -185,8 +175,6 @@ def add_features(self): cross_cutting_plane1, cross_cutting_plane2 = self.get_cross_cutting_planes() main_cutting_vol = self.get_main_cutting_volume() - print("polyhedron is closed: " + str(main_cutting_vol.is_closed())) #TODO just for debugging, remove... - trim_feature = CutFeature(cross_cutting_plane1) self.cross_beam.add_features(trim_feature) trim_feature = CutFeature(cross_cutting_plane2) diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py index 263f3b58dd..c488108936 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py @@ -6,10 +6,12 @@ class TStirnversatzJointOptions(component): - def RunScript(self, Gap): + def RunScript(self, Gap, CutDepth): args = {} if Gap: args["gap"] = Gap + if 0.05 < CutDepth < 0.9 and CutDepth is not None: + args["cut_depth"] = CutDepth options = JointOptions(TStirnversatzJoint, **args) return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/icon.png index 053ab8dd589ed1cf9e82f82ac2d240b5237cf108..fcf0368423eecafbeb3d0279298a2a1a7697fa06 100644 GIT binary patch literal 841 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_q|z zo-U3d9-T>l{{OdU)@+zK(Xg?xG4bD)%*zrzJv|??VmBtSG6OaL|DU9Iv4Alw?Al95 zM@JUk|9^fOA6^r=`5z-#Qt2d9S=qNt9UYxFnwpv{SN&!f0L^K9XbX|O#pLGZ#?Hpp zc4FBwHMY38xIJ0t=UN}%Q2IKI4JaxCR0Xm@=^Drs(YCfWo^Rj2v0VN3{k!pp4uj`6?$|LZ|cgDaMkn`f%ftgNKOv8(Rf97~@4b$@?-{QdpC z*9Hb=W{B4hWiu)(Co?iLHy7sS>N+0Izq{+`hN7ol0>-a_E|Wjd#KXe_ao!chfEm*e9?E+K45iSmn7+~Q3XWF3vH|`~KUf#P<8ylOPBTFg+j?A;Kmpj7pQ-V=K z0u*ULUmkkJ95Czuudl1K0|Nzx7n<;&-uULto6|sJ>KR-o!5n|+1EWTUg{5Wq%vrO9 zrap6g`0w4_-SPYD|DQWFAtfOJYSe~?_V(qE?(eTZ)Y{t0!dw6EkEO*^0|SE)C3hVe zAPrHL1k~W5=@%IpDcRD}B4L(yr{iiszyukq5{?*|H~06?e|Ubr{emT(cUj?nO|mI` z#L~s85YEcRX4Eig+4k+`F>f9mY);_m^2BR01_s7& zo-U3d9-T>l{{OdUUfD2lVqnAM$)8n(g(tuI^769e)hkyvNFO|K0H~ee|No>#OCRz8 z0feV?k|~HcJs}|h%Du%@#IwKjwHPy4tLJZYHBC&wn6b2XcWH9m-%$1`t`NjSaVuxJ}Ux+=nG4 zBtRYp>Sc;{&_?1Wr6eRQn2F@I=8*}GSgi<|qQ*7>>C?B-vNc8l}a z*x2xV`}U2etgMX3&CQKx&z?O$_MU=;8q-gPH$@W;o;`au;h=u}z6M^_i>p?xN^o|Z zCM;Z|(a_k)2yyps1~raN39qlMEf8zopr04$&18~^|S diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/metadata.json index ba051cff69..5677263d51 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/metadata.json +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/metadata.json @@ -14,6 +14,12 @@ "description": "Gap for tolerance.", "typeHintID": "float", "scriptParamAccess": 0 + }, + { + "name": "CutDepth", + "description": "Cut Depth into Main Beam.", + "typeHintID": "float", + "scriptParamAccess": 0 } ], "outputParameters": [ From 279ffabb367d6ea3d3115327aaa86641675c5861 Mon Sep 17 00:00:00 2001 From: jonashaldemann <104436733+jonashaldemann@users.noreply.github.com> Date: Thu, 11 Apr 2024 14:50:48 +0200 Subject: [PATCH 08/82] removed code-leftovers --- src/compas_timber/connections/t_stirnversatz.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/compas_timber/connections/t_stirnversatz.py b/src/compas_timber/connections/t_stirnversatz.py index c26c3351c3..13a4723abc 100644 --- a/src/compas_timber/connections/t_stirnversatz.py +++ b/src/compas_timber/connections/t_stirnversatz.py @@ -102,14 +102,6 @@ def _sort_frames_according_normals(frames, checkvector): angles, frames = zip(*sorted(zip(angles, frames))) return frames - @staticmethod - def _get_vector_most_same_direction(vectors, checkvector): - angles = [] - for i in vectors: - angles.append(angle_vectors(i, checkvector)) - angles, vectors = zip(*sorted(zip(angles, vectors))) - return vectors[0] - @staticmethod def _flip_plane_according_vector(plane, vector): if angle_vectors(plane.normal, vector, True) > 90: From 7830624d1fb16143741e43e2e1c1c865e6efb6d3 Mon Sep 17 00:00:00 2001 From: jonashaldemann <104436733+jonashaldemann@users.noreply.github.com> Date: Wed, 17 Apr 2024 19:58:59 +0200 Subject: [PATCH 09/82] added extend cut option --- src/compas_timber/connections/t_stirnversatz.py | 11 +++++++++-- .../components/CT_Joint_Options_TStirnversatz/code.py | 4 +++- .../CT_Joint_Options_TStirnversatz/metadata.json | 6 ++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/compas_timber/connections/t_stirnversatz.py b/src/compas_timber/connections/t_stirnversatz.py index 13a4723abc..870804bd21 100644 --- a/src/compas_timber/connections/t_stirnversatz.py +++ b/src/compas_timber/connections/t_stirnversatz.py @@ -20,13 +20,14 @@ class TStirnversatzJoint(Joint): SUPPORTED_TOPOLOGY = JointTopology.TOPO_T - def __init__(self, cross_beam=None, main_beam=None, cut_depth=0.25): #TODO Why main & cross swapped??? + def __init__(self, cross_beam=None, main_beam=None, cut_depth=0.25, extend_cut=True): #TODO Why main & cross swapped??? super(TStirnversatzJoint, self).__init__(main_beam, cross_beam, cut_depth) self.main_beam = main_beam self.cross_beam = cross_beam self.main_beam_key = None self.cross_beam_key = None self.cut_depth = cut_depth + self.extend_cut = extend_cut self.features = [] self.cross_cutting_plane_1 = None self.cross_cutting_plane_2 = None @@ -136,7 +137,13 @@ def get_main_cutting_volume(self): l2 = intersection_plane_plane(main_int_plane, self.cross_cutting_plane_2) l3 = intersection_plane_plane(self.cross_cutting_plane_1, self.cross_cutting_plane_2) main_frames_sorted = self._sort_frames_according_normals(self.main_beam.faces[:4], main_int_frame.zaxis) - plane_side = [Plane.from_frame(main_frames_sorted[1]), Plane.from_frame(main_frames_sorted[2])] + cut_frames_sorted = self._sort_frames_according_normals(self.cross_beam.faces[:4], main_int_frame.zaxis) + + # Extend Cut True or False + if self.extend_cut == True: + plane_side = [Plane.from_frame(main_frames_sorted[1]), Plane.from_frame(main_frames_sorted[2])] + else: + plane_side = [Plane.from_frame(cut_frames_sorted[1]), Plane.from_frame(cut_frames_sorted[2])] crossvector = cross_vectors(self.cross_cutting_plane_2.normal, self.cross_cutting_plane_1.normal) plane_side = self._sort_frames_according_normals(plane_side, crossvector) diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py index c488108936..4f925d29e3 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py @@ -6,12 +6,14 @@ class TStirnversatzJointOptions(component): - def RunScript(self, Gap, CutDepth): + def RunScript(self, Gap, CutDepth, ExtendCut): args = {} if Gap: args["gap"] = Gap if 0.05 < CutDepth < 0.9 and CutDepth is not None: args["cut_depth"] = CutDepth + if ExtendCut is not None: + args["extend_cut"] = ExtendCut options = JointOptions(TStirnversatzJoint, **args) return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/metadata.json index 5677263d51..8323cfb3ca 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/metadata.json +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/metadata.json @@ -20,6 +20,12 @@ "description": "Cut Depth into Main Beam.", "typeHintID": "float", "scriptParamAccess": 0 + }, + { + "name": "ExtendCut", + "description": "Extend Cut to Main Beam: True or False", + "typeHintID": "bool", + "scriptParamAccess": 0 } ], "outputParameters": [ From c162442dc7ce4918a2aaebeb06f080a10c2a858f Mon Sep 17 00:00:00 2001 From: obucklin Date: Tue, 23 Apr 2024 11:06:36 +0200 Subject: [PATCH 10/82] commit_for Panos --- src/compas_timber/connections/butt_joint.py | 3 +- src/compas_timber/fabrication/btlx.py | 61 ++++++++++++ .../btlx_processes/btlx_doublecut.py | 82 ++++++++++++++++ .../joint_factories/l_miter_factory.py | 10 +- .../joint_factories/t_butt_factory.py | 97 ++++++++++++++++++- 5 files changed, 244 insertions(+), 9 deletions(-) create mode 100644 src/compas_timber/fabrication/btlx_processes/btlx_doublecut.py diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 6458e38739..547e6b4e0d 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -42,13 +42,14 @@ class ButtJoint(Joint): """ - def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, **kwargs): + def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, birdsmouth = False, **kwargs): super(ButtJoint, self).__init__(**kwargs) 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.birdsmouth = birdsmouth self.btlx_params_main = {} self.btlx_params_cross = {} self.features = [] diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index f81e5ebca9..f3f2cec919 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -166,6 +166,7 @@ def __init__(self, beam): self.processings = [] self._et_element = None self.faces = beam.faces + self._reference_surfaces = {} @property def attr(self): @@ -223,6 +224,66 @@ def et_point_vals(self, point): "Z": "{:.{prec}f}".format(point.z, prec=BTLx.POINT_PRECISION), } + + def reference_surface_from_beam_face(self, beam_face): + """Finds the reference surface with normal that matches the normal of the beam face argument + + Parameters + ----------- + beam_face : :class:`~compas.geometry.Frame` + The frame of a beam face from beam.faces. + + Returns + -------- + key : str + The key(index 1-6) of the reference surface. + + """ + for key, face in self.reference_surfaces.items(): + if face.normal == beam_face.normal: + return key + + def reference_surface_planes(self, index): + """Returns the reference surface planes for a given index per BTLx docs. + + Parameters + ---------- + index : int + The index of the reference surface. + + Returns + ------- + dict + The BTLx reference surface frame. + + """ + if len(self._reference_surfaces) != 6: + self._reference_surfaces = { + "1": Frame(self.frame.point, self.frame.xaxis, self.frame.zaxis), + "2": Frame( + self.frame.point + self.frame.yaxis * self.width, + self.frame.xaxis, + -self.frame.yaxis, + ), + "3": Frame( + self.frame.point + self.frame.yaxis * self.width + self.frame.zaxis * self.height, + self.frame.xaxis, + -self.frame.zaxis, + ), + "4": Frame( + self.frame.point + self.frame.zaxis * self.height, + self.frame.xaxis, + self.frame.yaxis, + ), + "5": Frame(self.frame.point, self.frame.zaxis, self.frame.yaxis), + "6": Frame( + self.frame.point + self.frame.xaxis * self.blank_length + self.frame.yaxis * self.width, + self.frame.zaxis, + -self.frame.yaxis, + ), + } + return self._reference_surfaces[str(index)] + @property def et_element(self): if not self._et_element: diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_doublecut.py b/src/compas_timber/fabrication/btlx_processes/btlx_doublecut.py new file mode 100644 index 0000000000..bafc232710 --- /dev/null +++ b/src/compas_timber/fabrication/btlx_processes/btlx_doublecut.py @@ -0,0 +1,82 @@ +from collections import OrderedDict +from compas_timber.fabrication import BTLx +from compas_timber.fabrication import BTLxProcess + + +class BTLxDoubleCut(object): + """ + Represents a double cut process for timber fabrication. + + Parameters + ---------- + param_dict : dict + A dictionary containing the parameters for the BTLx lap process. + joint_name : str + The name of the joint. If not provided, the default name is "lap". + kwargs : dict + Additional keyword arguments to be added to the object. + + """ + + PROCESS_TYPE = "DoubleCut" + + def __init__(self, param_dict, joint_name=None, **kwargs): + self.apply_process = True + self.reference_plane_id = param_dict["ReferencePlaneID"] + self.orientation = param_dict["Orientation"] + self.start_x = param_dict["StartX"] + self.start_y = param_dict["StartY"] + self.angle1 = param_dict["Angle1"] + self.inclination1 = param_dict["Inclination1"] + self.angle2 = param_dict["Angle2"] + self.inclination2 = param_dict["Inclination2"] + + for key, value in param_dict.items(): + setattr(self, key, value) + + for key, value in kwargs.items(): + setattr(self, key, value) + + if joint_name: + self.name = joint_name + else: + self.name = "lap" + + @property + def header_attributes(self): + """the following attributes are required for all processes, but the keys and values of header_attributes are process specific.""" + return { + "Name": self.name, + "Process": "yes", + "Priority": "0", + "ProcessID": "0", + "ReferencePlaneID": str(self.reference_plane_id + 1), + } + + @property + def process_params(self): + """This property is required for all process types. It returns a dict with the geometric parameters to fabricate the joint.""" + + if self.apply_process: + """the following attributes are specific to Lap""" + od = OrderedDict( + [ + ("Orientation", str(self.orientation)), + ("StartX", "{:.{prec}f}".format(self.start_x, prec=BTLx.POINT_PRECISION)), + ("StartY", "{:.{prec}f}".format(self.start_y, prec=BTLx.POINT_PRECISION)), + ("Angle1", "{:.{prec}f}".format(self.angle1, prec=BTLx.ANGLE_PRECISION)), + ("Inclination1", "{:.{prec}f}".format(self.inclination1, prec=BTLx.ANGLE_PRECISION)), + ("Angle2", "{:.{prec}f}".format(self.angle2, prec=BTLx.ANGLE_PRECISION)), + ("Inclination2", "{:.{prec}f}".format(self.inclination2, prec=BTLx.ANGLE_PRECISION)) + ] + ) + print("param dict", od) + return od + else: + return None + + @classmethod + def create_process(cls, param_dict, joint_name=None, **kwargs): + """Creates a lap process from a dictionary of parameters.""" + lap = BTLxDoubleCut(param_dict, joint_name, **kwargs) + return BTLxProcess(BTLxDoubleCut.PROCESS_TYPE, lap.header_attributes, lap.process_params) diff --git a/src/compas_timber/fabrication/joint_factories/l_miter_factory.py b/src/compas_timber/fabrication/joint_factories/l_miter_factory.py index f6627ecd4d..43add22e42 100644 --- a/src/compas_timber/fabrication/joint_factories/l_miter_factory.py +++ b/src/compas_timber/fabrication/joint_factories/l_miter_factory.py @@ -28,12 +28,12 @@ def apply_processings(cls, joint, parts): None """ - - parts[str(joint.beams[0].key)].processings.append( - BTLxJackCut.create_process(parts[str(joint.beams[0].key)], joint.get_cutting_planes()[0], "L-Miter Joint") + beams = [joint.beam_a, joint.beam_b] + parts[str(beams[0].key)].processings.append( + BTLxJackCut.create_process(parts[str(beams[0].key)], joint.get_cutting_planes()[0], "L-Miter Joint") ) - parts[str(joint.beams[1].key)].processings.append( - BTLxJackCut.create_process(parts[str(joint.beams[1].key)], joint.get_cutting_planes()[1], "L-Miter Joint") + parts[str(beams[1].key)].processings.append( + BTLxJackCut.create_process(parts[str(beams[1].key)], joint.get_cutting_planes()[1], "L-Miter Joint") ) diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index 38031f111f..e93135bfd7 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -2,7 +2,9 @@ from compas_timber.fabrication import BTLx from compas_timber.fabrication import BTLxJackCut from compas_timber.fabrication.btlx_processes.btlx_lap import BTLxLap - +from compas_timber.fabrication.btlx_processes.btlx_doublecut import BTLxDoubleCut +from compas.geometry import intersection_plane_plane, intersection_plane_plane_plane, Vector, Plane, Frame, Transformation, Point +import math class TButtFactory(object): """Factory class for creating T-Butt joints.""" @@ -10,6 +12,88 @@ class TButtFactory(object): def __init__(self): pass + + @staticmethod + def calc_params_birdsmouth(joint, main_part, cross_part): + """ + Calculate the parameters for a birdsmouth joint. + + Parameters: + ---------- + joint (object): The joint object. + main_part (object): The main part object. + cross_part (object): The cross part object. + + Returns: + ---------- + dict: A dictionary containing the calculated parameters for the birdsmouth joint + + """ + face_dict = joint._beam_side_incidence(main_part.beam, cross_part.beam, ignore_ends=True) + sorted_keys = sorted(face_dict.keys(), key=face_dict.get) + + + + + + frame1, frame2 = cross_part.beam.faces[sorted_keys[0]], cross_part.beam.faces[sorted_keys[1]] + + # frame1 = Frame(frame1.point, frame1.xaxis, -frame1.yaxis) + + print(frame1, cross_part.beam.faces[sorted_keys[0]]) + plane1, plane2 = Plane.from_frame(frame1), Plane.from_frame(frame2) + intersect_vec = Vector.from_start_end(*intersection_plane_plane(plane2, plane1)) + + angles_dict = {} + for i, face in enumerate(main_part.beam.faces[0:4]): + angles_dict[i] = (face.normal.angle(intersect_vec)) + ref_frame_id = min(angles_dict, key=angles_dict.get) + print("ref_frame_id", ref_frame_id) + ref_frame = main_part.reference_surface_planes(ref_frame_id+1) + + dot_frame1 = plane1.normal.dot(ref_frame.yaxis) + if dot_frame1 > 0: + plane1, plane2 = plane2, plane1 + + start_point = Point(*intersection_plane_plane_plane(plane1, plane2, Plane.from_frame(ref_frame))) + start_point.transform(Transformation.from_frame_to_frame(ref_frame, Frame.worldXY())) + StartX, StartY = start_point[0], start_point[1] + + intersect_vec1 = Vector.from_start_end(*intersection_plane_plane(plane1, Plane.from_frame(ref_frame))) + intersect_vec2 = Vector.from_start_end(*intersection_plane_plane(plane2, Plane.from_frame(ref_frame))) + + dot_2 = math.degrees(intersect_vec1.dot(ref_frame.yaxis)) + if dot_2 < 0: + intersect_vec1 = -intersect_vec1 + + dot_1 = math.degrees(intersect_vec2.dot(ref_frame.yaxis)) + if dot_1 < 0: + intersect_vec2 = -intersect_vec2 + + if joint.ends[str(main_part.key)] == "start": + reference_frame = ref_frame.xaxis + else: + reference_frame = -ref_frame.xaxis + + Angle1 = math.degrees(intersect_vec1.angle(reference_frame)) + Angle2 = math.degrees(intersect_vec2.angle(reference_frame)) + + Inclination1 = math.degrees(plane1.normal.angle(ref_frame.zaxis)) + Inclination2 = math.degrees(plane2.normal.angle(ref_frame.zaxis)) + + return { + "Orientation": joint.ends[str(main_part.key)], + "StartX": StartX, + "StartY": StartY, + "Angle1": Angle1, + "Inclination1": Inclination1, + "Angle2": Angle2, + "Inclination2": Inclination2, + "ReferencePlaneID": ref_frame_id + } + + + @classmethod def apply_processings(cls, joint, parts): """ @@ -29,13 +113,20 @@ def apply_processings(cls, joint, parts): """ main_part = parts[str(joint.main_beam.key)] + cross_part = parts[str(joint.cross_beam.key)] cut_plane = joint.get_main_cutting_plane()[0] - main_part.processings.append(BTLxJackCut.create_process(main_part, cut_plane, "T-Butt Joint")) + if joint.birdsmouth == True: + #calculate the process params + joint_params = TButtFactory.calc_params_birdsmouth(joint, main_part, cross_part) + main_part.processings.append(BTLxDoubleCut.create_process(joint_params, "T-Butt Joint")) + #put processing here + else: + main_part.processings.append(BTLxJackCut.create_process(main_part, cut_plane, "T-Butt Joint")) if joint.mill_depth > 0: cross_part = parts[str(joint.cross_beam.key)] - joint.btlx_params_cross["machining_limits"] = {"FaceLimitedFront": "no", "FaceLimitedBack": "no"} cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, "T-Butt Joint")) + BTLx.register_joint(TButtJoint, TButtFactory) From 16e02263dea246242ca73bdead49e45d0274e865 Mon Sep 17 00:00:00 2001 From: obucklin Date: Tue, 23 Apr 2024 12:01:13 +0200 Subject: [PATCH 11/82] ready for testing --- src/compas_timber/fabrication/btlx.py | 6 ++--- .../joint_factories/t_butt_factory.py | 26 +++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index f3f2cec919..86b71ad622 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -261,17 +261,17 @@ def reference_surface_planes(self, index): self._reference_surfaces = { "1": Frame(self.frame.point, self.frame.xaxis, self.frame.zaxis), "2": Frame( - self.frame.point + self.frame.yaxis * self.width, + self.frame.point + self.frame.yaxis * self.height, self.frame.xaxis, -self.frame.yaxis, ), "3": Frame( - self.frame.point + self.frame.yaxis * self.width + self.frame.zaxis * self.height, + self.frame.point + self.frame.yaxis * self.height + self.frame.zaxis * self.width, self.frame.xaxis, -self.frame.zaxis, ), "4": Frame( - self.frame.point + self.frame.zaxis * self.height, + self.frame.point + self.frame.zaxis * self.width, self.frame.xaxis, self.frame.yaxis, ), diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index e93135bfd7..b766968acd 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -3,7 +3,7 @@ from compas_timber.fabrication import BTLxJackCut from compas_timber.fabrication.btlx_processes.btlx_lap import BTLxLap from compas_timber.fabrication.btlx_processes.btlx_doublecut import BTLxDoubleCut -from compas.geometry import intersection_plane_plane, intersection_plane_plane_plane, Vector, Plane, Frame, Transformation, Point +from compas.geometry import intersection_plane_plane, intersection_plane_plane_plane, Vector, Plane, Frame, Transformation, Point, angle_vectors import math class TButtFactory(object): @@ -32,14 +32,14 @@ def calc_params_birdsmouth(joint, main_part, cross_part): face_dict = joint._beam_side_incidence(main_part.beam, cross_part.beam, ignore_ends=True) sorted_keys = sorted(face_dict.keys(), key=face_dict.get) + cross_vector = main_part.beam.centerline.direction.cross(cross_part.beam.centerline.direction) + frame1, frame2 = joint.get_main_cutting_plane()[0], cross_part.beam.faces[sorted_keys[1]] + angle = angle_vectors(cross_vector, frame2.normal, deg=True) + if angle<1.0 or angle>179.0: + return False - - - frame1, frame2 = cross_part.beam.faces[sorted_keys[0]], cross_part.beam.faces[sorted_keys[1]] - - # frame1 = Frame(frame1.point, frame1.xaxis, -frame1.yaxis) - + frame1 = Frame(frame1.point, frame1.xaxis, -frame1.yaxis) print(frame1, cross_part.beam.faces[sorted_keys[0]]) plane1, plane2 = Plane.from_frame(frame1), Plane.from_frame(frame2) intersect_vec = Vector.from_start_end(*intersection_plane_plane(plane2, plane1)) @@ -48,9 +48,12 @@ def calc_params_birdsmouth(joint, main_part, cross_part): for i, face in enumerate(main_part.beam.faces[0:4]): angles_dict[i] = (face.normal.angle(intersect_vec)) ref_frame_id = min(angles_dict, key=angles_dict.get) - print("ref_frame_id", ref_frame_id) ref_frame = main_part.reference_surface_planes(ref_frame_id+1) + print("ref_frame", ref_frame) + joint.test.append(ref_frame) + + dot_frame1 = plane1.normal.dot(ref_frame.yaxis) if dot_frame1 > 0: plane1, plane2 = plane2, plane1 @@ -116,10 +119,11 @@ def apply_processings(cls, joint, parts): cross_part = parts[str(joint.cross_beam.key)] cut_plane = joint.get_main_cutting_plane()[0] if joint.birdsmouth == True: - #calculate the process params joint_params = TButtFactory.calc_params_birdsmouth(joint, main_part, cross_part) - main_part.processings.append(BTLxDoubleCut.create_process(joint_params, "T-Butt Joint")) - #put processing here + if joint_params == False: + main_part.processings.append(BTLxDoubleCut.create_process(joint_params, "T-Butt Joint")) + else: + main_part.processings.append(BTLxJackCut.create_process(main_part, cut_plane, "T-Butt Joint")) else: main_part.processings.append(BTLxJackCut.create_process(main_part, cut_plane, "T-Butt Joint")) From 69effe816820b69dd3e74b35089742c401e883e2 Mon Sep 17 00:00:00 2001 From: obucklin Date: Tue, 23 Apr 2024 13:36:50 +0200 Subject: [PATCH 12/82] almost_there_for_panos --- src/compas_timber/connections/butt_joint.py | 6 ++++-- .../fabrication/joint_factories/t_butt_factory.py | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 547e6b4e0d..1851e08ecc 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -88,7 +88,9 @@ def side_surfaces_cross(self): 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]] + return self.cross_beam.faces[(face_indices[0]+1)%4], self.cross_beam.faces[(face_indices[0]+3)%4] + + # 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 @@ -132,7 +134,7 @@ def subtraction_volume(self): dots = [dot_vectors(v, self.cross_beam.centerline.direction) for v in pv] dots, points = zip(*sorted(zip(dots, points))) min_pt, max_pt = points[0], points[-1] - if i == 1: + if i == 0: self.btlx_params_cross["start_x"] = abs(dots[0]) top_line = Line(*intersection_plane_plane(Plane.from_frame(side), Plane.from_frame(top_frame))) diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index b766968acd..e5bf26b895 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -118,9 +118,10 @@ def apply_processings(cls, joint, parts): main_part = parts[str(joint.main_beam.key)] cross_part = parts[str(joint.cross_beam.key)] cut_plane = joint.get_main_cutting_plane()[0] - if joint.birdsmouth == True: + if joint.birdsmouth: joint_params = TButtFactory.calc_params_birdsmouth(joint, main_part, cross_part) - if joint_params == False: + print(joint_params) + if joint_params: main_part.processings.append(BTLxDoubleCut.create_process(joint_params, "T-Butt Joint")) else: main_part.processings.append(BTLxJackCut.create_process(main_part, cut_plane, "T-Butt Joint")) From 5705af2ad8a520e61eb8a3425867c058531bc763 Mon Sep 17 00:00:00 2001 From: obucklin Date: Tue, 23 Apr 2024 15:23:02 +0200 Subject: [PATCH 13/82] temp --- .../joint_factories/t_butt_factory.py | 60 +++++++++++++++++-- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index e5bf26b895..09a53eee23 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -3,9 +3,11 @@ from compas_timber.fabrication import BTLxJackCut from compas_timber.fabrication.btlx_processes.btlx_lap import BTLxLap from compas_timber.fabrication.btlx_processes.btlx_doublecut import BTLxDoubleCut -from compas.geometry import intersection_plane_plane, intersection_plane_plane_plane, Vector, Plane, Frame, Transformation, Point, angle_vectors +from compas.geometry import intersection_plane_plane, intersection_plane_plane_plane, Vector, Plane, Frame, Transformation, Point, angle_vectors, Line import math +from compas_timber.utils.compas_extra import intersection_line_plane + class TButtFactory(object): """Factory class for creating T-Butt joints.""" @@ -13,6 +15,46 @@ def __init__(self): pass + @staticmethod + def line_intersects_face(line, face, x_max, y_max): + """ + Check if a line intersects a face. + + Parameters: + ---------- + line (object): The line object. + face (object): The face object. + + Returns: + ---------- + bool: True if the line intersects the face, False otherwise. + + """ + point = intersection_line_plane(line, Plane.from_frame(face))[0] + point.transform(Transformation.from_frame_to_frame(face, Frame.worldXY())) + print(point) + if point.x >= 0 and point.x <= x_max: + if point.y >= 0 and point.y <= y_max: + print("found it!!!!") + return True + return False + + + + @staticmethod + def get_intersecting_face(intersect_line, main_part, cross_part): + print(main_part, cross_part) + print(cross_part.beam.faces) + for i, face in enumerate(main_part.beam.faces[0:4]): + if i % 2 == 0: + if TButtFactory.line_intersects_face(intersect_line, face, cross_part.blank_length, cross_part.width): + return i, face + else: + if TButtFactory.line_intersects_face(intersect_line, face, cross_part.blank_length, cross_part.height): + return i, face + + + @staticmethod def calc_params_birdsmouth(joint, main_part, cross_part): """ @@ -43,14 +85,24 @@ def calc_params_birdsmouth(joint, main_part, cross_part): print(frame1, cross_part.beam.faces[sorted_keys[0]]) plane1, plane2 = Plane.from_frame(frame1), Plane.from_frame(frame2) intersect_vec = Vector.from_start_end(*intersection_plane_plane(plane2, plane1)) + intersect_line = Line(*intersection_plane_plane(plane2, plane1)) + + ind, main_ref_frame = TButtFactory.get_intersecting_face(intersect_line, main_part, cross_part) + + print (ind, main_ref_frame) + + + angles_dict = {} for i, face in enumerate(main_part.beam.faces[0:4]): - angles_dict[i] = (face.normal.angle(intersect_vec)) - ref_frame_id = min(angles_dict, key=angles_dict.get) + angles_dict[i] = (face.normal.angle(cross_part.beam.centerline.direction)) + ref_frame_id = min(angles_dict.keys(), key=angles_dict.get) ref_frame = main_part.reference_surface_planes(ref_frame_id+1) - print("ref_frame", ref_frame) + print(angles_dict) + + print("ref_frame", ref_frame_id) joint.test.append(ref_frame) From 84cffcf617710cc75c629d66f3bba3d949398196 Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 23 Apr 2024 19:37:03 +0200 Subject: [PATCH 14/82] dovetail (mortise & tenon) btlx_process file --- .../btlx_processes/btlx_dovetail.py | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 src/compas_timber/fabrication/btlx_processes/btlx_dovetail.py diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_dovetail.py b/src/compas_timber/fabrication/btlx_processes/btlx_dovetail.py new file mode 100644 index 0000000000..f9d2605699 --- /dev/null +++ b/src/compas_timber/fabrication/btlx_processes/btlx_dovetail.py @@ -0,0 +1,206 @@ +from collections import OrderedDict +from compas_timber.fabrication import BTLx +from compas_timber.fabrication import BTLxProcess + +FLANK_ANGLE = 15.0 +SHAPE_RADIUS = 30##CHECK +LENGTH_LIMITED_BOTTOM = True + +class BTLxDoveTailTenon(object): + """ + Represents a dovetail_tenon process for timber fabrication. + + Parameters + ---------- + param_dict : dict + A dictionary containing the parameters for the BTLx lap process. + joint_name : str + The name of the joint. If not provided, the default name is "lap". + kwargs : dict + Additional keyword arguments to be added to the object. + + """ + + PROCESS_TYPE = "DoveTail_Tenon" + + + + def __init__(self, param_dict, joint_name=None, **kwargs): + self.apply_process = True + self.reference_plane_id = param_dict["ReferencePlaneID"] + self.orientation = param_dict["Orientation"] + self.start_x = param_dict["StartX"] + self.start_y = param_dict["StartY"] + self.start_depth = param_dict["StartDepth"] + self.angle = param_dict["Angle"] + self.inclination = 90.0 + self.rotation = 90.0 + self.length_limited_top = bool(False) + self.length_limited_bottom = bool(LENGTH_LIMITED_BOTTOM) + self.length = param_dict["Length"] + self.width = param_dict["Width"] + self.height = param_dict["Height"] + self.cone_angle = param_dict["ConeAngle"] + self.use_flank_angle = bool(True) + self.flank_angle = FLANK_ANGLE ##check + self.shape = str("automatic") + self.shape_radius = SHAPE_RADIUS #check + + for key, value in param_dict.items(): + setattr(self, key, value) + + for key, value in kwargs.items(): + setattr(self, key, value) + + if joint_name: + self.name = joint_name + else: + self.name = "dovetail_tenon" + + @property + def header_attributes(self): + """the following attributes are required for all processes, but the keys and values of header_attributes are process specific.""" + return { + "Name": self.name, + "Process": "yes", + "Priority": "0", + "ProcessID": "0", + "ReferencePlaneID": str(self.reference_plane_id + 1), + } + + @property + def process_params(self): + """This property is required for all process types. It returns a dict with the geometric parameters to fabricate the joint.""" + + if self.apply_process: + """the following attributes are specific to Dovetail_Tenon""" + od = OrderedDict( + [ + ("Orientation", str(self.orientation)), + ("StartX", "{:.{prec}f}".format(self.start_x, prec=BTLx.POINT_PRECISION)), + ("StartY", "{:.{prec}f}".format(self.start_y, prec=BTLx.POINT_PRECISION)), + ("StartDepth", "{:.{prec}f}".format(self.start_depth, prec=BTLx.POINT_PRECISION)), + ("Angle", "{:.{prec}f}".format(self.angle, prec=BTLx.ANGLE_PRECISION)), + ("Inclination", "{:.{prec}f}".format(self.inclination, prec=BTLx.ANGLE_PRECISION)), + ("Rotation", "{:.{prec}f}".format(self.rotation, prec=BTLx.ANGLE_PRECISION)), + ("LengthLimitedTop", bool(self.length_limited_top)), + ("LengthLimitedBottom", bool(self.length_limited_bottom)), + ("Length", "{:.{prec}f}".format(self.length, prec=BTLx.POINT_PRECISION)), + ("Width", "{:.{prec}f}".format(self.width, prec=BTLx.POINT_PRECISION)), + ("Height", "{:.{prec}f}".format(self.height, prec=BTLx.POINT_PRECISION)), + ("ConeAngle", "{:.{prec}f}".format(self.cone_angle, prec=BTLx.POINT_PRECISION)), + ("UseFlankAngle", bool(self.use_flank_angle)), + ("FlankAngle", "{:.{prec}f}".format(self.flank_angle, prec=BTLx.POINT_PRECISION)), + ("Shape", str(self.shape)), + ("ShapeRadius", "{:.{prec}f}".format(self.shape_radius, prec=BTLx.POINT_PRECISION)) + ] + ) + return od + else: + return None + + @classmethod + def create_process(cls, param_dict, joint_name=None, **kwargs): + """Creates a dovetail_tenon process from a dictionary of parameters.""" + dovetail_t = BTLxDoveTailTenon(param_dict, joint_name, **kwargs) + return BTLxProcess(BTLxDoveTailTenon.PROCESS_TYPE, dovetail_t.header_attributes, dovetail_t.process_params) + + + +class BTLxDoveTailMortise(object): + """ + Represents a dovetail_mortise process for timber fabrication. + + Parameters + ---------- + param_dict : dict + A dictionary containing the parameters for the BTLx lap process. + joint_name : str + The name of the joint. If not provided, the default name is "lap". + kwargs : dict + Additional keyword arguments to be added to the object. + + """ + + PROCESS_TYPE = "DoveTail_Mortise" + + + + def __init__(self, param_dict, joint_name=None, **kwargs): + self.apply_process = True + self.reference_plane_id = param_dict["ReferencePlaneID"] + + self.start_x = param_dict["StartX"] + self.start_y = param_dict["StartY"] + self.start_depth = param_dict["StartDepth"] + self.angle = param_dict["Angle"] + self.slope = 90.0 + self.inclination = 90.0 + self.limitation_top = str("unlimited") + self.length_limited_bottom = bool(LENGTH_LIMITED_BOTTOM) + self.length = param_dict["Length"] + self.width = param_dict["Width"] + self.depth = param_dict["Depth"] + self.cone_angle = param_dict["ConeAngle"] + self.use_flank_angle = bool(True) + self.flank_angle = FLANK_ANGLE ##check + self.shape = str("automatic") + self.shape_radius = SHAPE_RADIUS #check + + for key, value in param_dict.items(): + setattr(self, key, value) + + for key, value in kwargs.items(): + setattr(self, key, value) + + if joint_name: + self.name = joint_name + else: + self.name = "dovetail_mortise" + + @property + def header_attributes(self): + """the following attributes are required for all processes, but the keys and values of header_attributes are process specific.""" + return { + "Name": self.name, + "Process": "yes", + "Priority": "0", + "ProcessID": "0", + "ReferencePlaneID": str(self.reference_plane_id + 1), + } + + @property + def process_params(self): + """This property is required for all process types. It returns a dict with the geometric parameters to fabricate the joint.""" + + if self.apply_process: + """the following attributes are specific to Dovetail_Mortise""" + od = OrderedDict( + [ + ("StartX", "{:.{prec}f}".format(self.start_x, prec=BTLx.POINT_PRECISION)), + ("StartY", "{:.{prec}f}".format(self.start_y, prec=BTLx.POINT_PRECISION)), + ("StartDepth", "{:.{prec}f}".format(self.start_depth, prec=BTLx.POINT_PRECISION)), + ("Angle", "{:.{prec}f}".format(self.angle, prec=BTLx.ANGLE_PRECISION)), + ("Slope", "{:.{prec}f}".format(self.slope, prec=BTLx.ANGLE_PRECISION)), + ("Inclination", "{:.{prec}f}".format(self.inclination, prec=BTLx.ANGLE_PRECISION)), + ("LimitationTop", bool(self.limitation_top)), + ("LengthLimitedBottom", bool(self.length_limited_bottom)), + ("Length", "{:.{prec}f}".format(self.length, prec=BTLx.POINT_PRECISION)), + ("Width", "{:.{prec}f}".format(self.width, prec=BTLx.POINT_PRECISION)), + ("Depth", "{:.{prec}f}".format(self.depth, prec=BTLx.POINT_PRECISION)), + ("ConeAngle", "{:.{prec}f}".format(self.cone_angle, prec=BTLx.POINT_PRECISION)), + ("UseFlankAngle", bool(self.use_flank_angle)), + ("FlankAngle", "{:.{prec}f}".format(self.flank_angle, prec=BTLx.POINT_PRECISION)), + ("Shape", str(self.shape)), + ("ShapeRadius", "{:.{prec}f}".format(self.shape_radius, prec=BTLx.POINT_PRECISION)) + ] + ) + return od + else: + return None + + @classmethod + def create_process(cls, param_dict, joint_name=None, **kwargs): + """Creates a dovetail_mortise process from a dictionary of parameters.""" + dovetail_m = BTLxDoveTailMortise(param_dict, joint_name, **kwargs) + return BTLxProcess(BTLxDoveTailMortise.PROCESS_TYPE, dovetail_m.header_attributes, dovetail_m.process_params) From 41e3e32653ff5ff70087634317f8219c62bf4810 Mon Sep 17 00:00:00 2001 From: ananya Date: Wed, 1 May 2024 09:29:44 +0200 Subject: [PATCH 15/82] french ridge --- .../connections/french_ridge_lap.py | 27 +++++++++++++---- .../btlx_processes/btlx_french_ridge_lap.py | 29 +++++++++++++------ .../joint_factories/french_ridge_factory.py | 8 ++--- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/compas_timber/connections/french_ridge_lap.py b/src/compas_timber/connections/french_ridge_lap.py index d4d332c703..dd9f5192cd 100644 --- a/src/compas_timber/connections/french_ridge_lap.py +++ b/src/compas_timber/connections/french_ridge_lap.py @@ -71,7 +71,7 @@ def cutting_plane_top(self): @property def cutting_plane_bottom(self): - _, cfr = self.get_face_most_towards_beam(self.beam_b, self.beam_b, ignore_ends=True) + _, cfr = self.get_face_most_towards_beam(self.beam_b, self.beam_a, ignore_ends=True) return cfr def restore_beams_from_keys(self, assemly): @@ -80,15 +80,20 @@ def restore_beams_from_keys(self, assemly): self.beam_b = assemly.find_by_key(self.beam_b_key) self._beams = (self.beam_a, self.beam_b) + def add_features(self): + self.beam_a.add_blank_extension(*self.beam_a.extension_to_plane(self.cutting_plane_top), joint_key = self.key) + self.beam_b.add_blank_extension(*self.beam_b.extension_to_plane(self.cutting_plane_bottom), joint_key = self.key) + self.features = [] + def check_geometry(self): """ This method checks whether the parts are aligned as necessary to create French Ridge Lap and determines which face is used as reference face for machining. """ if not (self.beam_a and self.beam_b): - raise (BeamJoinningError("French Ridge Lap requires 2 beams")) + raise (BeamJoinningError(beams=self.beams, joint=self, debug_info="beams not set")) if not (self.beam_a.width == self.beam_b.width and self.beam_a.height == self.beam_b.height): - raise (BeamJoinningError("widths and heights for both beams must match for the French Ridge Lap")) + raise (BeamJoinningError(beams=self.beams, joint=self, debug_info="beams are not of same size")) normal = cross_vectors(self.beam_a.frame.xaxis, self.beam_b.frame.xaxis) @@ -103,7 +108,13 @@ def check_geometry(self): elif angle_vectors(normal, -self.beam_a.frame.zaxis) < 0.001: indices.append(2) else: - raise (BeamJoinningError("part not aligned with corner normal, no French Ridge Lap possible")) + raise ( + BeamJoinningError( + beams=self.beams, + joint=self, + debug_info="part not aligned with corner normal, no French Ridge Lap possible", + ) + ) if abs(angle_vectors(normal, self.beam_b.frame.yaxis) - math.pi) < 0.001: indices.append(3) @@ -114,5 +125,11 @@ def check_geometry(self): elif abs(angle_vectors(normal, -self.beam_b.frame.zaxis) - math.pi) < 0.001: indices.append(2) else: - raise (BeamJoinningError("part not aligned with corner normal, no French Ridge Lap possible")) + raise ( + BeamJoinningError( + beams=self.beams, + joint=self, + debug_info="part not aligned with corner normal, no French Ridge Lap possible", + ) + ) self.reference_face_indices = {str(self.beam_a.key): indices[0], str(self.beam_b.key): indices[1]} diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_french_ridge_lap.py b/src/compas_timber/fabrication/btlx_processes/btlx_french_ridge_lap.py index e274649bc4..f29df98c74 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_french_ridge_lap.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_french_ridge_lap.py @@ -1,7 +1,7 @@ import math from collections import OrderedDict -from compas.geometry import angle_vectors_signed +from compas.geometry import angle_vectors_signed, angle_vectors from compas_timber.fabrication import BTLx from compas_timber.fabrication import BTLxProcess @@ -66,7 +66,7 @@ def __init__(self, part, joint, is_top): self.drill_hole_diameter = 10.0 self.ref_face_index = self.joint.reference_face_indices[str(self.beam.key)] - self.ref_face = self.part.reference_surfaces[str(self.ref_face_index)] + self.ref_face = self.part.reference_surface_planes(str(self.ref_face_index)) """ the following attributes are required for all processes, but the keys and values of header_attributes are process specific. @@ -127,23 +127,31 @@ def get_params(self): other_vector = -other_vector self.angle_rad = angle_vectors_signed(self.ref_face.xaxis, other_vector, self.ref_face.normal) + self.angle_lines = angle_vectors(self.ref_face.xaxis, other_vector) if self.orientation == "start": - if self.angle_rad < math.pi / 2 and self.angle_rad > -math.pi / 2: - raise Exception("french ridge lap joint beams must join at 90-180 degrees") - elif self.angle_rad < -math.pi / 2: + # if self.angle_rad < math.pi / 3 and self.angle_rad > -math.pi / 2: + # raise Exception("french ridge lap joint beams must join at 90-180 degrees") + if self.angle_rad < 0: self._ref_edge = False self.angle_rad = abs(self.angle_rad) + self.startX = abs(self.beam.width / math.tan(self.angle_rad)) + print(self.angle_lines, "angle_lines") + print(self.startX) + if self.angle_lines < math.pi / 2: + self.startX = 0.0 + else: - if self.angle_rad < -math.pi / 2 or self.angle_rad > math.pi / 2: - raise Exception("french ridge lap joint beams must join at 90-180 degrees") - elif self.angle_rad < 0: + # if self.angle_rad < -math.pi / 2 or self.angle_rad > math.pi / 2: + # raise Exception("french ridge lap joint beams must join at 90-180 degrees") + if self.angle_rad < 0: self.angle_rad = abs(self.angle_rad) self._ref_edge = False + self.angle_rad = math.pi - self.angle_rad + self.startX = abs(self.beam.width / math.tan(self.angle_rad)) - self.startX = self.beam.width / abs(math.tan(self.angle_rad)) if self.orientation == "end": if self._ref_edge: @@ -151,6 +159,9 @@ def get_params(self): else: self.startX = self.beam.blank_length + self.startX + print("orientation: ", self.orientation, " angle: ", self.angle_rad, " start: ", self.startX) + print("ref_edge: ", self.ref_edge, " drill_hole: ", self.drill_hole, " drill_hole_diameter: ", self.drill_hole_diameter) + @classmethod def create_process(cls, part, joint, is_top): frl_process = BTLxFrenchRidgeLap(part, joint, is_top) diff --git a/src/compas_timber/fabrication/joint_factories/french_ridge_factory.py b/src/compas_timber/fabrication/joint_factories/french_ridge_factory.py index 8d2ec7411f..0d5805b0a3 100644 --- a/src/compas_timber/fabrication/joint_factories/french_ridge_factory.py +++ b/src/compas_timber/fabrication/joint_factories/french_ridge_factory.py @@ -31,14 +31,14 @@ def apply_processings(cls, joint, parts): top_key = joint.beams[0].key top_part = parts[str(top_key)] - top_part._test.append(top_part.reference_surface_planes(joint.reference_face_indices[str(top_part.beam.key)])) + # top_part._test.append(top_part.reference_surface_planes(joint.reference_face_indices[str(top_part.beam.key)])) top_part.processings.append(BTLxFrenchRidgeLap.create_process(top_part, joint, True)) bottom_key = joint.beams[1].key bottom_part = parts[str(bottom_key)] - bottom_part._test.append( - bottom_part.reference_surface_planes(joint.reference_face_indices[str(bottom_part.beam.key)]) - ) + # bottom_part._test.append( + # bottom_part.reference_surface_planes(joint.reference_face_indices[str(bottom_part.beam.key)]) + # ) bottom_part.processings.append(BTLxFrenchRidgeLap.create_process(bottom_part, joint, False)) From 2411bd5e778612a60d3deb58f7b2da09e15d7854 Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 2 May 2024 12:10:23 +0200 Subject: [PATCH 16/82] inclination adjustment --- src/compas_timber/connections/butt_joint.py | 17 ++++++++++++++++- .../joint_factories/t_butt_factory.py | 7 ++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 21d37ba216..8ab395b7d2 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -14,7 +14,7 @@ from compas.geometry import angle_vectors_signed from compas.geometry import angle_vectors from .joint import Joint - +import math class ButtJoint(Joint): """Abstract Lap type joint with functions common to L-Butt and T-Butt Joints. @@ -117,6 +117,21 @@ def get_main_cutting_plane(self): 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 + + # if self.birdsmouth: + # face_dict = self._beam_side_incidence(self.main_beam, self.cross_beam, ignore_ends=True) + # face_keys = sorted([key for key in face_dict.keys()], key=face_dict.get) + # frame2 = self.cross_beam.faces[face_keys[1]] + + # plane1, plane2 = Plane(cfr.point, -cfr.zaxis), Plane.from_frame(frame2) + # intersection_points = intersection_plane_plane(plane2, plane1) + # intersect_vec = Vector.from_start_end(*intersection_points) + + # # rotate main_cutting plane to create inclined pocket + # tolerance_offset = 0.1 + # offset_angle = math.atan(self.mill_depth / self.cross_beam.width) + # cfr.rotate(offset_angle, intersect_vec, intersection_points[0]) + return cfr, cross_mating_frame def subtraction_volume(self): diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index 8823a8ab9b..0b01466a33 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -3,8 +3,9 @@ from compas_timber.fabrication import BTLxJackCut from compas_timber.fabrication.btlx_processes.btlx_lap import BTLxLap from compas_timber.fabrication.btlx_processes.btlx_double_cut import BTLxDoubleCut - +from compas.geometry import Frame, Plane, Transformation, Point, Vector, Line, intersection_plane_plane, intersection_plane_plane_plane, angle_vectors from compas_timber.utils.compas_extra import intersection_line_plane +import math class TButtFactory(object): """Factory class for creating T-Butt joints.""" @@ -41,8 +42,8 @@ def line_intersects_face(line, face, x_max, y_max): @staticmethod def get_intersecting_face(intersect_line, main_part, cross_part): - print(main_part, cross_part) - print(cross_part.beam.faces) + print(main_part, cross_part, "intersecting_face") + print(cross_part.beam.faces, "cross_faces") for i, face in enumerate(main_part.beam.faces[0:4]): if i % 2 == 0: if TButtFactory.line_intersects_face(intersect_line, face, cross_part.blank_length, cross_part.width): From 54de3df84e911ff65c828a84b45543461753b474 Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 2 May 2024 14:24:07 +0200 Subject: [PATCH 17/82] add missing JointOptions gh_components --- .../CT_Joint_Options_FrenchRidgeLap/code.py | 15 +++++++ .../CT_Joint_Options_FrenchRidgeLap/icon.png | Bin 0 -> 781 bytes .../metadata.json | 27 +++++++++++++ .../components/CT_Joint_Options_LButt/code.py | 20 +++++++++ .../CT_Joint_Options_LButt/icon.png | Bin 0 -> 409 bytes .../CT_Joint_Options_LButt/metadata.json | 38 ++++++++++++++++++ .../CT_Joint_Options_LHalfLap/code.py | 18 +++++++++ .../CT_Joint_Options_LHalfLap/icon.png | Bin 0 -> 417 bytes .../CT_Joint_Options_LHalfLap/metadata.json | 32 +++++++++++++++ .../CT_Joint_Options_LMiter/code.py | 15 +++++++ .../CT_Joint_Options_LMiter/icon.png | Bin 0 -> 453 bytes .../CT_Joint_Options_LMiter/metadata.json | 26 ++++++++++++ .../CT_Joint_Options_NullJoint/code.py | 12 ++++++ .../CT_Joint_Options_NullJoint/icon.png | Bin 0 -> 647 bytes .../CT_Joint_Options_NullJoint/metadata.json | 20 +++++++++ .../components/CT_Joint_Options_TButt/code.py | 15 +++++++ .../CT_Joint_Options_TButt/icon.png | Bin 0 -> 407 bytes .../CT_Joint_Options_TButt/metadata.json | 26 ++++++++++++ .../CT_Joint_Options_THalfLap/code.py | 16 ++++++++ .../CT_Joint_Options_THalfLap/icon.png | Bin 0 -> 423 bytes .../CT_Joint_Options_THalfLap/metadata.json | 32 +++++++++++++++ .../CT_Joint_Options_XHalfLap/code.py | 17 ++++++++ .../CT_Joint_Options_XHalfLap/icon.png | Bin 0 -> 424 bytes .../CT_Joint_Options_XHalfLap/metadata.json | 32 +++++++++++++++ 24 files changed, 361 insertions(+) create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/code.py create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/icon.png create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/metadata.json create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LButt/code.py create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LButt/icon.png create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LButt/metadata.json create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/code.py create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/icon.png create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/metadata.json create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/code.py create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/icon.png create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/metadata.json create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/code.py create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/icon.png create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/metadata.json create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_TButt/code.py create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_TButt/icon.png create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_TButt/metadata.json create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/code.py create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/icon.png create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/metadata.json create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/code.py create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/icon.png create mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/metadata.json diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/code.py new file mode 100644 index 0000000000..d341e9af63 --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/code.py @@ -0,0 +1,15 @@ +from ghpythonlib.componentbase import executingcomponent as component + + +from compas_timber.connections import FrenchRidgeLapJoint +from compas_timber.ghpython import JointOptions + + +class FrenchRidgeLapOptions(component): + def RunScript(self, Cutoff): + args = {} + if Cutoff: + args["cutoff"] = Cutoff + options = JointOptions(FrenchRidgeLapJoint, **args) + + return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bf0bdf2c8ee44d2d3e1d2f2cb7f1cf86e5960512 GIT binary patch literal 781 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_q}6 zo-U3d9-T=h7cTBi$jx3XFD-w*fmivQs+=rgV*~jS=rpEi2OpQy+9!-_{{9jbl03qMDwCa=*_bKH z>dL<8EYP_SEltr3N*`QJ#Ldl{H*7G-6g&&_CeW-L7i|qKqmLgx+z`mMVrFIr+tKv1 zVMPbq)AsxO>u(Dp34^T8&dfIa^XF%9h^u2@gT`e^2~Z3G-PIKB5Yjhodb?p~H#^)( zP8%6q1iCUTJ5-M>Re_5p{rUgjzUlA@j%RZ$3YpGq->~7pD$fb=4UQfS9Ra|Qg&3IR z_)Ox(i#Qh1y9Rb=lu`;lJaB9i)ebZ8nQu3D10uYf_>>kYMfv*nKK%Cfwu|IBcqlt< zY?#q8ZORmu%jf6YtLMNycWA?dtGCT7l%-he{{A|;A?K#ip=HJf1`tbu4xX~O{vS|0 z|9;(`SvEE{65rn4^}c}!f2YnAfd`Ks)RcdHC8}66Yt^b%9Y%8+8ygwHq2R=?)b{`X z|JX_gg+-lg@JKzhp`mr=(;us^ujiO0C?Mc)Lk=DoPA4-I7*^eQZ&yC h+|2s?{B?++WoC#7U$6P3+f)zaNKaQkmvv4FO#p7NJDdOj literal 0 HcmV?d00001 diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/metadata.json new file mode 100644 index 0000000000..4ba260ffa1 --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/metadata.json @@ -0,0 +1,27 @@ + +{ + "name": "French Ridge Lap Joint Options", + "nickname": "French Ridge", + "category": "COMPAS Timber", + "subcategory": "Joints", + "description": "defines and gives access to French Ridge Lap joint and its parameters", + "exposure": 2, + "ghpython": { + "isAdvancedMode": true, + "iconDisplay": 0, + "inputParameters": [ + { + "name": "Cutoff", + "description": "For very acute angles, limit the extension of the tip/beak of the joint.", + "typeHintID": "float", + "scriptParamAccess": 0 + } + ], + "outputParameters": [ + { + "name": "French Ridge", + "description": "French Ridge Lap Joint Options." + } + ] + } +} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/code.py new file mode 100644 index 0000000000..8096ebdffe --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/code.py @@ -0,0 +1,20 @@ +from ghpythonlib.componentbase import executingcomponent as component + + +from compas_timber.connections import LButtJoint +from compas_timber.ghpython import JointOptions + + +class LButtJointOptions(component): + def RunScript(self, small_beam_butts, modify_cross, reject_i): + args = {} + if small_beam_butts is not None: + args["small_beam_butts"] = small_beam_butts + if modify_cross is not None: + args["modify_cross"] = modify_cross + if reject_i is not None: + args["reject_i"] = reject_i + + options = JointOptions(LButtJoint, **args) + + return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3d0760fe79a45b1474eed4684eded132a0f6b28d GIT binary patch literal 409 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_nlN zPZ!4!kItl!Y2iQimptWSt5LABX?l3B`FBr+Xd*KY4-Zf$!~g$DO76lJ8yXuK!MsIl zS>ZAR#ZF=ryuLnmaqmmGOPHoFGcleEb0gF5hJZp}vxI~Mhz}O!$`}|JK-tuCf390H b%zS2sxv_j%?HY!4AUix={an^LB{Ts58?Sk) literal 0 HcmV?d00001 diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/metadata.json new file mode 100644 index 0000000000..2575d448b4 --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/metadata.json @@ -0,0 +1,38 @@ +{ + "name": "L-Butt Options", + "nickname": "L-Butt", + "category": "COMPAS Timber", + "subcategory": "Joints", + "description": "defines and gives access to L-Butt joint and its parameters", + "exposure": 2, + "ghpython": { + "isAdvancedMode": true, + "iconDisplay": 0, + "inputParameters": [ + { + "name": "SmallBeamButts", + "description": "If true, the beam with a larger cross-section is considered as the cross beam.", + "typeHintID": "bool", + "scriptParamAccess": 0 + }, + { + "name": "ModifyCross", + "description": "If true, the cross beam is extended to the opposite face of the main beam and cut flush with it.", + "typeHintID": "bool", + "scriptParamAccess": 0 + }, + { + "name": "RejectI", + "description": "If true, joint will not apply when the cross beam meets the main beam in an I topology.", + "typeHintID": "bool", + "scriptParamAccess": 0 + } + ], + "outputParameters": [ + { + "name": "L-Butt", + "description": "L-Butt Joint Options." + } + ] + } +} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/code.py new file mode 100644 index 0000000000..b41f6daea5 --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/code.py @@ -0,0 +1,18 @@ +from ghpythonlib.componentbase import executingcomponent as component + + +from compas_timber.connections import LHalfLapJoint +from compas_timber.ghpython import JointOptions + + +class LHalfLapJointOptions(component): + def RunScript(self, flip_lap_side, cut_plane_bias): + args = {} + if flip_lap_side: + args["flip_lap_side"] = flip_lap_side + if cut_plane_bias: + args["cut_plane_bias"] = cut_plane_bias + + options = JointOptions(LHalfLapJoint, **args) + + return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..835c6f6788b4e51f6b0a9872ce584f96852cec77 GIT binary patch literal 417 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_s7p zPZ!4!kItl&goGdUkB)G%)f_lB~%v=fd2`^t)lkqgfNA*1%n@q{Vl_bWS5o!J=Fh zV*>+-CzMVx1qe)Jm5=}h8qm9o*776RxiSVYbvVUy-I5a$AbOb@GVhDMpJ(X@4j@lg KKbLh*2~7Y%AA>Of literal 0 HcmV?d00001 diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/metadata.json new file mode 100644 index 0000000000..56a5b12e8f --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/metadata.json @@ -0,0 +1,32 @@ +{ + "name": "L-Half Lap Joint Options", + "nickname": "L-Half Lap", + "category": "COMPAS Timber", + "subcategory": "Joints", + "description": "defines and gives access to L-Half Lap joint and its parameters", + "exposure": 2, + "ghpython": { + "isAdvancedMode": true, + "iconDisplay": 0, + "inputParameters": [ + { + "name": "FlipLapSide", + "description": "flip the side of the lap joint", + "typeHintID": "bool", + "scriptParamAccess": 0 + }, + { + "name": "CutPlaneBias", + "description": "determines the depth of lap cuts on the beams", + "typeHintID": "float", + "scriptParamAccess": 0 + } + ], + "outputParameters": [ + { + "name": "L-Half Lap", + "description": "L-Half Lap Joint Options." + } + ] + } +} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/code.py new file mode 100644 index 0000000000..f1c3358545 --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/code.py @@ -0,0 +1,15 @@ +from ghpythonlib.componentbase import executingcomponent as component + + +from compas_timber.connections import LMiterJoint +from compas_timber.ghpython import JointOptions + + +class LMiterJointOptions(component): + def RunScript(self, Cutoff): + args = {} + if Cutoff: + args["cutoff"] = Cutoff + options = JointOptions(LMiterJoint, **args) + + return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b335abbc68261d00b94035d9bd36fab1e96bfe13 GIT binary patch literal 453 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_s7* zPZ!4!kItl&goGdUkB)G%)f_l>>h1So_-VUw1SkTGG( zlqH=HfuRBo7N+f54m?p3=MEe=08s_hBn;;;GXyuA2QR+#n;9hO>FVdQ&MBb@0DJeD A$N&HU literal 0 HcmV?d00001 diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/metadata.json new file mode 100644 index 0000000000..3d9225a1fc --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/metadata.json @@ -0,0 +1,26 @@ +{ + "name": "L-Miter Joint Options", + "nickname": "L-Miter", + "category": "COMPAS Timber", + "subcategory": "Joints", + "description": "defines and gives access to L-Miter joint and its parameters", + "exposure": 2, + "ghpython": { + "isAdvancedMode": true, + "iconDisplay": 0, + "inputParameters": [ + { + "name": "Cutoff", + "description": "For very acute angles, limit the extension of the tip/beak of the joint.", + "typeHintID": "float", + "scriptParamAccess": 0 + } + ], + "outputParameters": [ + { + "name": "L-Miter", + "description": "L-Miter Joint Options." + } + ] + } +} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/code.py new file mode 100644 index 0000000000..30de1c26a7 --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/code.py @@ -0,0 +1,12 @@ +from ghpythonlib.componentbase import executingcomponent as component + + +from compas_timber.connections import NullJoint +from compas_timber.ghpython import JointOptions + + +class NullJointComponent(component): + def RunScript(self): + + options = JointOptions(NullJoint, **{}) + return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d6855f0072c173b645b15439ff641d64e0cd61b0 GIT binary patch literal 647 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_mZe zPZ!4!kItl!Y2iQimptWSt5LABX?l3B`FBr+Xd*KY4-Zf$!~g$DO76lJ8yXuK!MsIl zS>ZCYW+yp{<|e|Yu$|B^)^GbDFhOiBIS&t+g>pp%f0Q1M~MgbEbQIY!Gc$udaA*U=lOonUw>I|pi8_hTnLHbL zuBRs?>^O1YzyyaWNTx;2ayzbS_=js{;tzeL;|G3x!<9ab7%k>OOav&1r-OVy;$F zLIPA@9;3Cm#UJ_F-~45k9L+ag2>J^EeF%|cI?lN3;f4?Fu4RkOj3XpcEHoY^CcW^K z&<6S+q9WbP0l+XkKvo!9j literal 0 HcmV?d00001 diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/metadata.json new file mode 100644 index 0000000000..6bc831eda0 --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/metadata.json @@ -0,0 +1,20 @@ +{ + "name": "Null-Joint Options", + "nickname": "NullJoint", + "category": "COMPAS Timber", + "subcategory": "Joints", + "description": "defines and gives access to NullJoint joint and its parameters", + "exposure": 2, + "ghpython": { + "isAdvancedMode": true, + "iconDisplay": 0, + "inputParameters": [ + ], + "outputParameters": [ + { + "name": "NullJoint", + "description": "NullJoint Joint Options." + } + ] + } +} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/code.py new file mode 100644 index 0000000000..15c040d6e8 --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/code.py @@ -0,0 +1,15 @@ +from ghpythonlib.componentbase import executingcomponent as component + + +from compas_timber.connections import TButtJoint +from compas_timber.ghpython import JointOptions + + +class TButtJointOptions(component): + def RunScript(self, Gap): + args = {} + if Gap: + args["gap"] = Gap + options = JointOptions(TButtJoint, **args) + + return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d4db8698d2ccf08066cb0912638936e4c1c5492a GIT binary patch literal 407 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_nk? zPZ!4!kIs^l8wD8{IG7A?bp6%dR&_q^^05ZHZJEFSc_|%8_mTqY{{KJe(CXPi2M!zn za)7)=Ygu`Ccm@xfX=>As?qDxQW@cuv`;rtxWP({FBtXFca&E4Gp@9LFotx{HoR9!9 albIpShp#!|_H0mCF?hQAxvX2BR01_s7R zPZ!4!kIs^l8wD8{IG7A?bp6%dR&_q^^05ZHZJEFSc_|%8_mTqY{{KJe(CXPi2M!zn za)7)=Ygu`Ccm@xfX=>As?qDxQW@cuv`;rcZ%QSZ-C3R)b$&!!&1qR3qHieH^-qs*I zkt<_hU;vRjl+AeJcMC(DDU!GN;W`%~sa&)chj^}AazX;cG-d|JZ?fm7Z59jz@jYGr KT-G@yGywoEeufPI literal 0 HcmV?d00001 diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/metadata.json new file mode 100644 index 0000000000..9d662baa79 --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/metadata.json @@ -0,0 +1,32 @@ +{ + "name": "T-Half Lap Joint Options", + "nickname": "T-Half Lap", + "category": "COMPAS Timber", + "subcategory": "Joints", + "description": "defines and gives access to T-Half Lap joint and its parameters", + "exposure": 2, + "ghpython": { + "isAdvancedMode": true, + "iconDisplay": 0, + "inputParameters": [ + { + "name": "FlipLapSide", + "description": "flip the side of the lap joint", + "typeHintID": "bool", + "scriptParamAccess": 0 + }, + { + "name": "CutPlaneBias", + "description": "determines the depth of lap cuts on the beams", + "typeHintID": "float", + "scriptParamAccess": 0 + } + ], + "outputParameters": [ + { + "name": "T-Half Lap", + "description": "T-Half Lap Joint Options." + } + ] + } +} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/code.py new file mode 100644 index 0000000000..501e6df4e4 --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/code.py @@ -0,0 +1,17 @@ +from ghpythonlib.componentbase import executingcomponent as component + + +from compas_timber.connections import XHalfLapJoint +from compas_timber.ghpython import JointOptions + + +class XHalfLapJointOptions(component): + def RunScript(self, flip_lap_side, cut_plane_bias): + args = {} + if flip_lap_side: + args["flip_lap_side"] = flip_lap_side + if cut_plane_bias: + args["cut_plane_bias"] = cut_plane_bias + options = JointOptions(XHalfLapJoint, **args) + + return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e2c42f97828d599c367b066df2d968cfb73a28e6 GIT binary patch literal 424 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_s6` zPZ!4!kIs^l8wD8{IG7A?bp6%dR&_q^^05ZHZJEFSc_|%8_mTqY{{KJe(CXPi2M!zn za)7)=Ygu`Cc*tWjsp{W(DZ-WDDLO@E Date: Thu, 2 May 2024 15:14:54 +0200 Subject: [PATCH 18/82] inclination adjustment --- src/compas_timber/connections/butt_joint.py | 17 ++++++++++++++++- .../joint_factories/t_butt_factory.py | 7 ++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 21d37ba216..8ab395b7d2 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -14,7 +14,7 @@ from compas.geometry import angle_vectors_signed from compas.geometry import angle_vectors from .joint import Joint - +import math class ButtJoint(Joint): """Abstract Lap type joint with functions common to L-Butt and T-Butt Joints. @@ -117,6 +117,21 @@ def get_main_cutting_plane(self): 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 + + # if self.birdsmouth: + # face_dict = self._beam_side_incidence(self.main_beam, self.cross_beam, ignore_ends=True) + # face_keys = sorted([key for key in face_dict.keys()], key=face_dict.get) + # frame2 = self.cross_beam.faces[face_keys[1]] + + # plane1, plane2 = Plane(cfr.point, -cfr.zaxis), Plane.from_frame(frame2) + # intersection_points = intersection_plane_plane(plane2, plane1) + # intersect_vec = Vector.from_start_end(*intersection_points) + + # # rotate main_cutting plane to create inclined pocket + # tolerance_offset = 0.1 + # offset_angle = math.atan(self.mill_depth / self.cross_beam.width) + # cfr.rotate(offset_angle, intersect_vec, intersection_points[0]) + return cfr, cross_mating_frame def subtraction_volume(self): diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index 8823a8ab9b..0b01466a33 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -3,8 +3,9 @@ from compas_timber.fabrication import BTLxJackCut from compas_timber.fabrication.btlx_processes.btlx_lap import BTLxLap from compas_timber.fabrication.btlx_processes.btlx_double_cut import BTLxDoubleCut - +from compas.geometry import Frame, Plane, Transformation, Point, Vector, Line, intersection_plane_plane, intersection_plane_plane_plane, angle_vectors from compas_timber.utils.compas_extra import intersection_line_plane +import math class TButtFactory(object): """Factory class for creating T-Butt joints.""" @@ -41,8 +42,8 @@ def line_intersects_face(line, face, x_max, y_max): @staticmethod def get_intersecting_face(intersect_line, main_part, cross_part): - print(main_part, cross_part) - print(cross_part.beam.faces) + print(main_part, cross_part, "intersecting_face") + print(cross_part.beam.faces, "cross_faces") for i, face in enumerate(main_part.beam.faces[0:4]): if i % 2 == 0: if TButtFactory.line_intersects_face(intersect_line, face, cross_part.blank_length, cross_part.width): From d4681506bc3fa15e3e9ceb5fe0d21a8a38519017 Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 2 May 2024 15:30:46 +0200 Subject: [PATCH 19/82] clean_up --- .../CT_Joint_Options_FrenchRidgeLap/code.py | 15 ------- .../CT_Joint_Options_FrenchRidgeLap/icon.png | Bin 781 -> 0 bytes .../metadata.json | 27 ------------- .../components/CT_Joint_Options_LButt/code.py | 20 --------- .../CT_Joint_Options_LButt/icon.png | Bin 409 -> 0 bytes .../CT_Joint_Options_LButt/metadata.json | 38 ------------------ .../CT_Joint_Options_LHalfLap/code.py | 18 --------- .../CT_Joint_Options_LHalfLap/icon.png | Bin 417 -> 0 bytes .../CT_Joint_Options_LHalfLap/metadata.json | 32 --------------- .../CT_Joint_Options_LMiter/code.py | 15 ------- .../CT_Joint_Options_LMiter/icon.png | Bin 453 -> 0 bytes .../CT_Joint_Options_LMiter/metadata.json | 26 ------------ .../CT_Joint_Options_NullJoint/code.py | 12 ------ .../CT_Joint_Options_NullJoint/icon.png | Bin 647 -> 0 bytes .../CT_Joint_Options_NullJoint/metadata.json | 20 --------- .../components/CT_Joint_Options_TButt/code.py | 15 ------- .../CT_Joint_Options_TButt/icon.png | Bin 407 -> 0 bytes .../CT_Joint_Options_TButt/metadata.json | 26 ------------ .../CT_Joint_Options_THalfLap/code.py | 16 -------- .../CT_Joint_Options_THalfLap/icon.png | Bin 423 -> 0 bytes .../CT_Joint_Options_THalfLap/metadata.json | 32 --------------- .../CT_Joint_Options_TStirnversatz/code.py | 19 --------- .../CT_Joint_Options_TStirnversatz/icon.png | Bin 841 -> 0 bytes .../metadata.json | 38 ------------------ .../CT_Joint_Options_XHalfLap/code.py | 17 -------- .../CT_Joint_Options_XHalfLap/icon.png | Bin 424 -> 0 bytes .../CT_Joint_Options_XHalfLap/metadata.json | 32 --------------- 27 files changed, 418 deletions(-) delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/code.py delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/icon.png delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/metadata.json delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LButt/code.py delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LButt/icon.png delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LButt/metadata.json delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/code.py delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/icon.png delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/metadata.json delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/code.py delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/icon.png delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/metadata.json delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/code.py delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/icon.png delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/metadata.json delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_TButt/code.py delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_TButt/icon.png delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_TButt/metadata.json delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/code.py delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/icon.png delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/metadata.json delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/icon.png delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/metadata.json delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/code.py delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/icon.png delete mode 100644 src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/metadata.json diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/code.py deleted file mode 100644 index d341e9af63..0000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/code.py +++ /dev/null @@ -1,15 +0,0 @@ -from ghpythonlib.componentbase import executingcomponent as component - - -from compas_timber.connections import FrenchRidgeLapJoint -from compas_timber.ghpython import JointOptions - - -class FrenchRidgeLapOptions(component): - def RunScript(self, Cutoff): - args = {} - if Cutoff: - args["cutoff"] = Cutoff - options = JointOptions(FrenchRidgeLapJoint, **args) - - return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/icon.png deleted file mode 100644 index bf0bdf2c8ee44d2d3e1d2f2cb7f1cf86e5960512..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 781 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_q}6 zo-U3d9-T=h7cTBi$jx3XFD-w*fmivQs+=rgV*~jS=rpEi2OpQy+9!-_{{9jbl03qMDwCa=*_bKH z>dL<8EYP_SEltr3N*`QJ#Ldl{H*7G-6g&&_CeW-L7i|qKqmLgx+z`mMVrFIr+tKv1 zVMPbq)AsxO>u(Dp34^T8&dfIa^XF%9h^u2@gT`e^2~Z3G-PIKB5Yjhodb?p~H#^)( zP8%6q1iCUTJ5-M>Re_5p{rUgjzUlA@j%RZ$3YpGq->~7pD$fb=4UQfS9Ra|Qg&3IR z_)Ox(i#Qh1y9Rb=lu`;lJaB9i)ebZ8nQu3D10uYf_>>kYMfv*nKK%Cfwu|IBcqlt< zY?#q8ZORmu%jf6YtLMNycWA?dtGCT7l%-he{{A|;A?K#ip=HJf1`tbu4xX~O{vS|0 z|9;(`SvEE{65rn4^}c}!f2YnAfd`Ks)RcdHC8}66Yt^b%9Y%8+8ygwHq2R=?)b{`X z|JX_gg+-lg@JKzhp`mr=(;us^ujiO0C?Mc)Lk=DoPA4-I7*^eQZ&yC h+|2s?{B?++WoC#7U$6P3+f)zaNKaQkmvv4FO#p7NJDdOj diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/metadata.json deleted file mode 100644 index 4ba260ffa1..0000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/metadata.json +++ /dev/null @@ -1,27 +0,0 @@ - -{ - "name": "French Ridge Lap Joint Options", - "nickname": "French Ridge", - "category": "COMPAS Timber", - "subcategory": "Joints", - "description": "defines and gives access to French Ridge Lap joint and its parameters", - "exposure": 2, - "ghpython": { - "isAdvancedMode": true, - "iconDisplay": 0, - "inputParameters": [ - { - "name": "Cutoff", - "description": "For very acute angles, limit the extension of the tip/beak of the joint.", - "typeHintID": "float", - "scriptParamAccess": 0 - } - ], - "outputParameters": [ - { - "name": "French Ridge", - "description": "French Ridge Lap Joint Options." - } - ] - } -} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/code.py deleted file mode 100644 index 8096ebdffe..0000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/code.py +++ /dev/null @@ -1,20 +0,0 @@ -from ghpythonlib.componentbase import executingcomponent as component - - -from compas_timber.connections import LButtJoint -from compas_timber.ghpython import JointOptions - - -class LButtJointOptions(component): - def RunScript(self, small_beam_butts, modify_cross, reject_i): - args = {} - if small_beam_butts is not None: - args["small_beam_butts"] = small_beam_butts - if modify_cross is not None: - args["modify_cross"] = modify_cross - if reject_i is not None: - args["reject_i"] = reject_i - - options = JointOptions(LButtJoint, **args) - - return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/icon.png deleted file mode 100644 index 3d0760fe79a45b1474eed4684eded132a0f6b28d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 409 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_nlN zPZ!4!kItl!Y2iQimptWSt5LABX?l3B`FBr+Xd*KY4-Zf$!~g$DO76lJ8yXuK!MsIl zS>ZAR#ZF=ryuLnmaqmmGOPHoFGcleEb0gF5hJZp}vxI~Mhz}O!$`}|JK-tuCf390H b%zS2sxv_j%?HY!4AUix={an^LB{Ts58?Sk) diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/metadata.json deleted file mode 100644 index 2575d448b4..0000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/metadata.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "L-Butt Options", - "nickname": "L-Butt", - "category": "COMPAS Timber", - "subcategory": "Joints", - "description": "defines and gives access to L-Butt joint and its parameters", - "exposure": 2, - "ghpython": { - "isAdvancedMode": true, - "iconDisplay": 0, - "inputParameters": [ - { - "name": "SmallBeamButts", - "description": "If true, the beam with a larger cross-section is considered as the cross beam.", - "typeHintID": "bool", - "scriptParamAccess": 0 - }, - { - "name": "ModifyCross", - "description": "If true, the cross beam is extended to the opposite face of the main beam and cut flush with it.", - "typeHintID": "bool", - "scriptParamAccess": 0 - }, - { - "name": "RejectI", - "description": "If true, joint will not apply when the cross beam meets the main beam in an I topology.", - "typeHintID": "bool", - "scriptParamAccess": 0 - } - ], - "outputParameters": [ - { - "name": "L-Butt", - "description": "L-Butt Joint Options." - } - ] - } -} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/code.py deleted file mode 100644 index b41f6daea5..0000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/code.py +++ /dev/null @@ -1,18 +0,0 @@ -from ghpythonlib.componentbase import executingcomponent as component - - -from compas_timber.connections import LHalfLapJoint -from compas_timber.ghpython import JointOptions - - -class LHalfLapJointOptions(component): - def RunScript(self, flip_lap_side, cut_plane_bias): - args = {} - if flip_lap_side: - args["flip_lap_side"] = flip_lap_side - if cut_plane_bias: - args["cut_plane_bias"] = cut_plane_bias - - options = JointOptions(LHalfLapJoint, **args) - - return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/icon.png deleted file mode 100644 index 835c6f6788b4e51f6b0a9872ce584f96852cec77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 417 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_s7p zPZ!4!kItl&goGdUkB)G%)f_lB~%v=fd2`^t)lkqgfNA*1%n@q{Vl_bWS5o!J=Fh zV*>+-CzMVx1qe)Jm5=}h8qm9o*776RxiSVYbvVUy-I5a$AbOb@GVhDMpJ(X@4j@lg KKbLh*2~7Y%AA>Of diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/metadata.json deleted file mode 100644 index 56a5b12e8f..0000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/metadata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "L-Half Lap Joint Options", - "nickname": "L-Half Lap", - "category": "COMPAS Timber", - "subcategory": "Joints", - "description": "defines and gives access to L-Half Lap joint and its parameters", - "exposure": 2, - "ghpython": { - "isAdvancedMode": true, - "iconDisplay": 0, - "inputParameters": [ - { - "name": "FlipLapSide", - "description": "flip the side of the lap joint", - "typeHintID": "bool", - "scriptParamAccess": 0 - }, - { - "name": "CutPlaneBias", - "description": "determines the depth of lap cuts on the beams", - "typeHintID": "float", - "scriptParamAccess": 0 - } - ], - "outputParameters": [ - { - "name": "L-Half Lap", - "description": "L-Half Lap Joint Options." - } - ] - } -} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/code.py deleted file mode 100644 index f1c3358545..0000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/code.py +++ /dev/null @@ -1,15 +0,0 @@ -from ghpythonlib.componentbase import executingcomponent as component - - -from compas_timber.connections import LMiterJoint -from compas_timber.ghpython import JointOptions - - -class LMiterJointOptions(component): - def RunScript(self, Cutoff): - args = {} - if Cutoff: - args["cutoff"] = Cutoff - options = JointOptions(LMiterJoint, **args) - - return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/icon.png deleted file mode 100644 index b335abbc68261d00b94035d9bd36fab1e96bfe13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 453 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_s7* zPZ!4!kItl&goGdUkB)G%)f_l>>h1So_-VUw1SkTGG( zlqH=HfuRBo7N+f54m?p3=MEe=08s_hBn;;;GXyuA2QR+#n;9hO>FVdQ&MBb@0DJeD A$N&HU diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/metadata.json deleted file mode 100644 index 3d9225a1fc..0000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/metadata.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "L-Miter Joint Options", - "nickname": "L-Miter", - "category": "COMPAS Timber", - "subcategory": "Joints", - "description": "defines and gives access to L-Miter joint and its parameters", - "exposure": 2, - "ghpython": { - "isAdvancedMode": true, - "iconDisplay": 0, - "inputParameters": [ - { - "name": "Cutoff", - "description": "For very acute angles, limit the extension of the tip/beak of the joint.", - "typeHintID": "float", - "scriptParamAccess": 0 - } - ], - "outputParameters": [ - { - "name": "L-Miter", - "description": "L-Miter Joint Options." - } - ] - } -} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/code.py deleted file mode 100644 index 30de1c26a7..0000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/code.py +++ /dev/null @@ -1,12 +0,0 @@ -from ghpythonlib.componentbase import executingcomponent as component - - -from compas_timber.connections import NullJoint -from compas_timber.ghpython import JointOptions - - -class NullJointComponent(component): - def RunScript(self): - - options = JointOptions(NullJoint, **{}) - return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/icon.png deleted file mode 100644 index d6855f0072c173b645b15439ff641d64e0cd61b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 647 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_mZe zPZ!4!kItl!Y2iQimptWSt5LABX?l3B`FBr+Xd*KY4-Zf$!~g$DO76lJ8yXuK!MsIl zS>ZCYW+yp{<|e|Yu$|B^)^GbDFhOiBIS&t+g>pp%f0Q1M~MgbEbQIY!Gc$udaA*U=lOonUw>I|pi8_hTnLHbL zuBRs?>^O1YzyyaWNTx;2ayzbS_=js{;tzeL;|G3x!<9ab7%k>OOav&1r-OVy;$F zLIPA@9;3Cm#UJ_F-~45k9L+ag2>J^EeF%|cI?lN3;f4?Fu4RkOj3XpcEHoY^CcW^K z&<6S+q9WbP0l+XkKvo!9j diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/metadata.json deleted file mode 100644 index 6bc831eda0..0000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/metadata.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Null-Joint Options", - "nickname": "NullJoint", - "category": "COMPAS Timber", - "subcategory": "Joints", - "description": "defines and gives access to NullJoint joint and its parameters", - "exposure": 2, - "ghpython": { - "isAdvancedMode": true, - "iconDisplay": 0, - "inputParameters": [ - ], - "outputParameters": [ - { - "name": "NullJoint", - "description": "NullJoint Joint Options." - } - ] - } -} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/code.py deleted file mode 100644 index 15c040d6e8..0000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/code.py +++ /dev/null @@ -1,15 +0,0 @@ -from ghpythonlib.componentbase import executingcomponent as component - - -from compas_timber.connections import TButtJoint -from compas_timber.ghpython import JointOptions - - -class TButtJointOptions(component): - def RunScript(self, Gap): - args = {} - if Gap: - args["gap"] = Gap - options = JointOptions(TButtJoint, **args) - - return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/icon.png deleted file mode 100644 index d4db8698d2ccf08066cb0912638936e4c1c5492a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 407 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_nk? zPZ!4!kIs^l8wD8{IG7A?bp6%dR&_q^^05ZHZJEFSc_|%8_mTqY{{KJe(CXPi2M!zn za)7)=Ygu`Ccm@xfX=>As?qDxQW@cuv`;rtxWP({FBtXFca&E4Gp@9LFotx{HoR9!9 albIpShp#!|_H0mCF?hQAxvX2BR01_s7R zPZ!4!kIs^l8wD8{IG7A?bp6%dR&_q^^05ZHZJEFSc_|%8_mTqY{{KJe(CXPi2M!zn za)7)=Ygu`Ccm@xfX=>As?qDxQW@cuv`;rcZ%QSZ-C3R)b$&!!&1qR3qHieH^-qs*I zkt<_hU;vRjl+AeJcMC(DDU!GN;W`%~sa&)chj^}AazX;cG-d|JZ?fm7Z59jz@jYGr KT-G@yGywoEeufPI diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/metadata.json deleted file mode 100644 index 9d662baa79..0000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/metadata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "T-Half Lap Joint Options", - "nickname": "T-Half Lap", - "category": "COMPAS Timber", - "subcategory": "Joints", - "description": "defines and gives access to T-Half Lap joint and its parameters", - "exposure": 2, - "ghpython": { - "isAdvancedMode": true, - "iconDisplay": 0, - "inputParameters": [ - { - "name": "FlipLapSide", - "description": "flip the side of the lap joint", - "typeHintID": "bool", - "scriptParamAccess": 0 - }, - { - "name": "CutPlaneBias", - "description": "determines the depth of lap cuts on the beams", - "typeHintID": "float", - "scriptParamAccess": 0 - } - ], - "outputParameters": [ - { - "name": "T-Half Lap", - "description": "T-Half Lap Joint Options." - } - ] - } -} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py deleted file mode 100644 index 4f925d29e3..0000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/code.py +++ /dev/null @@ -1,19 +0,0 @@ -from ghpythonlib.componentbase import executingcomponent as component - - -from compas_timber.connections import TStirnversatzJoint -from compas_timber.ghpython import JointOptions - - -class TStirnversatzJointOptions(component): - def RunScript(self, Gap, CutDepth, ExtendCut): - args = {} - if Gap: - args["gap"] = Gap - if 0.05 < CutDepth < 0.9 and CutDepth is not None: - args["cut_depth"] = CutDepth - if ExtendCut is not None: - args["extend_cut"] = ExtendCut - options = JointOptions(TStirnversatzJoint, **args) - - return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_TStirnversatz/icon.png deleted file mode 100644 index fcf0368423eecafbeb3d0279298a2a1a7697fa06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 841 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_q|z zo-U3d9-T>l{{OdU)@+zK(Xg?xG4bD)%*zrzJv|??VmBtSG6OaL|DU9Iv4Alw?Al95 zM@JUk|9^fOA6^r=`5z-#Qt2d9S=qNt9UYxFnwpv{SN&!f0L^K9XbX|O#pLGZ#?Hpp zc4FBwHMY38xIJ0t=UN}%Q2IKI4JaxCR0Xm@=^Drs(YCfWo^Rj2v0VN3{k!pp4uj`6?$|LZ|cgDaMkn`f%ftgNKOv8(Rf97~@4b$@?-{QdpC z*9Hb=W{B4hWiu)(Co?iLHy7sS>N+0Izq{+`hN7ol0>-a_E|Wjd#KXe_ao!chfEm*e9?E+K45iSmn7+~Q3XWF3vH|`~KUf#P<8ylOPBTFg+j?A;Kmpj7pQ-V=K z0u*ULUmkkJ95Czuudl1K0|Nzx7n<;&-uULto6|sJ>KR-o!5n|+1EWTUg{5Wq%vrO9 zrap6g`0w4_-SPYD|DQWFAtfOJYSe~?_V(qE?(eTZ)Y{t0!dw6EkEO*^0|SE)C3hVe zAPrHL1k~W5=@%IpDcRD}B4L(yr{iiszyukq5{?*|H~06?e|Ubr{emT(cUj?nO|mI` z#L~s85YEcRX4Eig+4k+`F>f9mY);_m^2BR01_s6` zPZ!4!kIs^l8wD8{IG7A?bp6%dR&_q^^05ZHZJEFSc_|%8_mTqY{{KJe(CXPi2M!zn za)7)=Ygu`Cc*tWjsp{W(DZ-WDDLO@E Date: Thu, 2 May 2024 17:35:02 +0200 Subject: [PATCH 20/82] pause --- src/compas_timber/connections/butt_joint.py | 14 +---- src/compas_timber/fabrication/btlx.py | 59 ------------------- .../fabrication/btlx_processes/btlx_lap.py | 1 + .../joint_factories/l_butt_factory.py | 5 +- 4 files changed, 6 insertions(+), 73 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 8ab395b7d2..995e4f5140 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -118,19 +118,7 @@ def get_main_cutting_plane(self): cfr = Frame(cfr.point, cfr.xaxis, cfr.yaxis * -1.0) # flip normal cfr.point = cfr.point + cfr.zaxis * self.mill_depth - # if self.birdsmouth: - # face_dict = self._beam_side_incidence(self.main_beam, self.cross_beam, ignore_ends=True) - # face_keys = sorted([key for key in face_dict.keys()], key=face_dict.get) - # frame2 = self.cross_beam.faces[face_keys[1]] - - # plane1, plane2 = Plane(cfr.point, -cfr.zaxis), Plane.from_frame(frame2) - # intersection_points = intersection_plane_plane(plane2, plane1) - # intersect_vec = Vector.from_start_end(*intersection_points) - - # # rotate main_cutting plane to create inclined pocket - # tolerance_offset = 0.1 - # offset_angle = math.atan(self.mill_depth / self.cross_beam.width) - # cfr.rotate(offset_angle, intersect_vec, intersection_points[0]) + return cfr, cross_mating_frame diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index 8db5cb7194..78e6f71b50 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -287,65 +287,6 @@ def et_point_vals(self, point): } - def reference_surface_from_beam_face(self, beam_face): - """Finds the reference surface with normal that matches the normal of the beam face argument - - Parameters - ----------- - beam_face : :class:`~compas.geometry.Frame` - The frame of a beam face from beam.faces. - - Returns - -------- - key : str - The key(index 1-6) of the reference surface. - - """ - for key, face in self.reference_surfaces.items(): - if face.normal == beam_face.normal: - return key - - def reference_surface_planes(self, index): - """Returns the reference surface planes for a given index per BTLx docs. - - Parameters - ---------- - index : int - The index of the reference surface. - - Returns - ------- - dict - The BTLx reference surface frame. - - """ - if len(self._reference_surfaces) != 6: - self._reference_surfaces = { - "1": Frame(self.frame.point, self.frame.xaxis, self.frame.zaxis), - "2": Frame( - self.frame.point + self.frame.yaxis * self.height, - self.frame.xaxis, - -self.frame.yaxis, - ), - "3": Frame( - self.frame.point + self.frame.yaxis * self.height + self.frame.zaxis * self.width, - self.frame.xaxis, - -self.frame.zaxis, - ), - "4": Frame( - self.frame.point + self.frame.zaxis * self.width, - self.frame.xaxis, - self.frame.yaxis, - ), - "5": Frame(self.frame.point, self.frame.zaxis, self.frame.yaxis), - "6": Frame( - self.frame.point + self.frame.xaxis * self.blank_length + self.frame.yaxis * self.width, - self.frame.zaxis, - -self.frame.yaxis, - ), - } - return self._reference_surfaces[str(index)] - @property def et_element(self): if not self._et_element: diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_lap.py b/src/compas_timber/fabrication/btlx_processes/btlx_lap.py index bb243b4c67..c388b42744 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_lap.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_lap.py @@ -52,6 +52,7 @@ def __init__(self, param_dict, joint_name=None, **kwargs): @property def header_attributes(self): """the following attributes are required for all processes, but the keys and values of header_attributes are process specific.""" + return { "Name": self.name, "Process": "yes", diff --git a/src/compas_timber/fabrication/joint_factories/l_butt_factory.py b/src/compas_timber/fabrication/joint_factories/l_butt_factory.py index 33aec8c14f..84ef9e418a 100644 --- a/src/compas_timber/fabrication/joint_factories/l_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/l_butt_factory.py @@ -38,7 +38,10 @@ def apply_processings(cls, joint, parts): BTLxJackCut.create_process(cross_part, joint.get_cross_cutting_plane(), "L-Butt Joint") ) if joint.mill_depth > 0: - if joint.ends[1] == "start": + ref_face = cross_part.beam.faces[joint.reference_side_index_cross] + joint.btlx_params_cross["reference_plane_id"] = str(cross_part.reference_surface_from_beam_face(ref_face)) + print("ref ID", joint.btlx_params_cross["reference_plane_id"]) + if joint.ends[str(cross_part.key)] == "start": joint.btlx_params_cross["machining_limits"] = { "FaceLimitedStart": "no", "FaceLimitedFront": "no", From f427d49b1eda23f7fb277ffdba8f83dc8a380731 Mon Sep 17 00:00:00 2001 From: obucklin Date: Thu, 2 May 2024 18:26:31 +0200 Subject: [PATCH 21/82] fixed face index --- src/compas_timber/connections/butt_joint.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 995e4f5140..028b41c9de 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -92,7 +92,7 @@ def side_surfaces_cross(self): angles = face_dict.values() angles, face_indices = zip(*sorted(zip(angles, face_indices))) - return self.cross_beam.faces[(face_indices[0] + 1) % 4], self.cross_beam.faces[(face_indices[0] + 3) % 4] + return [self.cross_beam.faces[(face_indices[0] + 1) % 4], self.cross_beam.faces[(face_indices[0] + 3) % 4]] def front_back_surface_main(self): assert self.main_beam and self.cross_beam @@ -124,6 +124,8 @@ def get_main_cutting_plane(self): def subtraction_volume(self): """Returns the volume to be subtracted from the cross beam.""" + print("main_beam extensions", self.main_beam._blank_extensions) + print("cross_beam extensions", self.cross_beam._blank_extensions) vertices = [] front_frame, back_frame = self.front_back_surface_main() top_frame, bottom_frame = self.get_main_cutting_plane() @@ -141,9 +143,9 @@ def subtraction_volume(self): dots = [dot_vectors(v, self.cross_beam.centerline.direction) for v in pv] dots, points = zip(*sorted(zip(dots, points))) min_pt, max_pt = points[0], points[-1] - if i == 0: + if i == 1: self.btlx_params_cross["start_x"] = abs(dots[0]) - + print("start_x", self.btlx_params_cross["start_x"]) top_line = Line(*intersection_plane_plane(Plane.from_frame(side), Plane.from_frame(top_frame))) top_min = Point(*closest_point_on_line(min_pt, top_line)) top_max = Point(*closest_point_on_line(max_pt, top_line)) From 75bc236e9c9e4f33a03502c87757dd206298b41d Mon Sep 17 00:00:00 2001 From: paulocinco Date: Fri, 3 May 2024 09:48:11 +0200 Subject: [PATCH 22/82] StepJoint WIP and Drilling WIP --- .../btlx_processes/btlx_drilling.py | 81 ++++++ .../btlx_processes/btlx_stepjoint.py | 86 ++++++ .../t_butt_factory_stepjoint_WIP.py | 248 ++++++++++++++++++ 3 files changed, 415 insertions(+) create mode 100644 src/compas_timber/fabrication/btlx_processes/btlx_drilling.py create mode 100644 src/compas_timber/fabrication/btlx_processes/btlx_stepjoint.py create mode 100644 src/compas_timber/fabrication/joint_factories/t_butt_factory_stepjoint_WIP.py diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_drilling.py b/src/compas_timber/fabrication/btlx_processes/btlx_drilling.py new file mode 100644 index 0000000000..34a51fe373 --- /dev/null +++ b/src/compas_timber/fabrication/btlx_processes/btlx_drilling.py @@ -0,0 +1,81 @@ +from collections import OrderedDict +from compas_timber.fabrication import BTLx +from compas_timber.fabrication import BTLxProcess + + +class BTLxDrilling(object): + """ + Represents a drilling process for timber fabrication. + + Parameters + ---------- + param_dict : dict + A dictionary containing the parameters for the BTLx lap process. + joint_name : str + The name of the joint. If not provided, the default name is "lap". + kwargs : dict + Additional keyword arguments to be added to the object. + + """ + + PROCESS_TYPE = "Drilling" + + def __init__(self, param_dict, joint_name=None, **kwargs): # joint_name replace by "feature_name"? + self.apply_process = True + self.reference_plane_id = param_dict["ReferencePlaneID"] + self.start_x = param_dict["StartX"] + self.start_y = param_dict["StartY"] + self.angle = param_dict["Angle"] + self.inclination = param_dict["Inclination"] + self.depth_limited = param_dict["DepthLimited"] + self.depth = param_dict["Depth"] + self.diameter = param_dict["Diameter"] + + for key, value in param_dict.items(): + setattr(self, key, value) + + for key, value in kwargs.items(): + setattr(self, key, value) + + if joint_name: # to delete since no joint? + self.name = joint_name + else: + self.name = "lap" # what instead? + + @property + def header_attributes(self): + """the following attributes are required for all processes, but the keys and values of header_attributes are process specific.""" + return { + "Name": self.name, + "Process": "yes", + "Priority": "0", + "ProcessID": "0", + "ReferencePlaneID": self.reference_plane_id, + } + + @property + def process_params(self): + """This property is required for all process types. It returns a dict with the geometric parameters to fabricate the joint.""" + + if self.apply_process: + """the following attributes are specific to Lap""" + od = OrderedDict( + [ + ("StartX", "{:.{prec}f}".format(self.start_x, prec=BTLx.POINT_PRECISION)), + ("StartY", "{:.{prec}f}".format(self.start_y, prec=BTLx.POINT_PRECISION)), + ("Angle", "{:.{prec}f}".format(self.angle, prec=BTLx.ANGLE_PRECISION)), + ("Inclination", "{:.{prec}f}".format(self.inclination, prec=BTLx.ANGLE_PRECISION)), + ("DepthLimited", str(self.depth_limited)), + ("Depth", "{:.{prec}f}".format(self.depth, prec=BTLx.POINT_PRECISION)), + ("Diameter", "{:.{prec}f}".format(self.diameter, prec=BTLx.POINT_PRECISION)), + ] + ) + return od + else: + return None + + @classmethod + def create_process(cls, param_dict, joint_name=None, **kwargs): # joint_name replace by "feature_name"? + """Creates a lap process from a dictionary of parameters.""" + lap = BTLxDrilling(param_dict, joint_name, **kwargs) ###change lap??? + return BTLxProcess(BTLxDrilling.PROCESS_TYPE, lap.header_attributes, lap.process_params) diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_stepjoint.py b/src/compas_timber/fabrication/btlx_processes/btlx_stepjoint.py new file mode 100644 index 0000000000..36f7ad4e1c --- /dev/null +++ b/src/compas_timber/fabrication/btlx_processes/btlx_stepjoint.py @@ -0,0 +1,86 @@ +from collections import OrderedDict +from compas_timber.fabrication import BTLx +from compas_timber.fabrication import BTLxProcess + + +class BTLxStepJoint(object): + """ + Represents a step joint process for timber fabrication. + + Parameters + ---------- + param_dict : dict + A dictionary containing the parameters for the BTLx lap process. + joint_name : str + The name of the joint. If not provided, the default name is "lap". + kwargs : dict + Additional keyword arguments to be added to the object. + + """ + + PROCESS_TYPE = "StepJoint" + + def __init__(self, param_dict, joint_name=None, **kwargs): + self.apply_process = True + self.reference_plane_id = param_dict["ReferencePlaneID"] + self.orientation = param_dict["Orientation"] + self.start_x = param_dict["StartX"] + self.strut_inclination = param_dict["StrutInclination"] + self.step_depth = param_dict["StepDepth"] + self.hell_depth = param_dict["HeelDepth"] + self.step_shape = param_dict["StepShape"] + self.tenon = param_dict["Tenon"] + self.tenon_width = param_dict["TenonWidth"] + self.tenon_height = param_dict["TenonHeight"] + + for key, value in param_dict.items(): + setattr(self, key, value) + + for key, value in kwargs.items(): + setattr(self, key, value) + + if joint_name: + self.name = joint_name + else: + self.name = "lap" + + @property + def header_attributes(self): + """the following attributes are required for all processes, but the keys and values of header_attributes are process specific.""" + return { + "Name": self.name, + "Process": "yes", + "Priority": "0", + "ProcessID": "0", + "ReferencePlaneID": self.reference_plane_id, + } + + @property + def process_params(self): + """This property is required for all process types. It returns a dict with the geometric parameters to fabricate the joint.""" + + if self.apply_process: + """the following attributes are specific to Lap""" + od = OrderedDict( + [ + ("Orientation", str(self.orientation)), + ("StartX", "{:.{prec}f}".format(self.start_x, prec=BTLx.POINT_PRECISION)), + ("StrutInclination", "{:.{prec}f}".format(self.strut_inclination, prec=BTLx.ANGLE_PRECISION)), + ("StepDepth", "{:.{prec}f}".format(self.step_depth, prec=BTLx.POINT_PRECISION)), + ("HeelDepth", "{:.{prec}f}".format(self.hell_depth, prec=BTLx.POINT_PRECISION)), + ("StepShape", str(self.step_shape)), + ("Tenon", str(self.tenon)), + ("TenonWidth", "{:.{prec}f}".format(self.tenon_width, prec=BTLx.POINT_PRECISION)), + ("TenonHeight", "{:.{prec}f}".format(self.tenon_height, prec=BTLx.POINT_PRECISION)), + ] + ) + print("param dict", od) + return od + else: + return None + + @classmethod + def create_process(cls, param_dict, joint_name=None, **kwargs): + """Creates a lap process from a dictionary of parameters.""" + lap = BTLxStepJoint(param_dict, joint_name, **kwargs) + return BTLxProcess(BTLxStepJoint.PROCESS_TYPE, lap.header_attributes, lap.process_params) diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory_stepjoint_WIP.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory_stepjoint_WIP.py new file mode 100644 index 0000000000..76794b7992 --- /dev/null +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory_stepjoint_WIP.py @@ -0,0 +1,248 @@ +from compas_timber.connections import TButtJoint +from compas_timber.fabrication import BTLx +from compas_timber.fabrication import BTLxJackCut +from compas_timber.fabrication.btlx_processes.btlx_lap import BTLxLap +from compas_timber.fabrication.btlx_processes.btlx_double_cut import BTLxDoubleCut +from compas_timber.fabrication.btlx_processes.btlx_stepjoint import BTLxStepJoint + +from compas_timber.utils.compas_extra import intersection_line_plane + +class TButtFactory(object): + """Factory class for creating T-Butt joints.""" + + def __init__(self): + pass + + + @staticmethod + def line_intersects_face(line, face, x_max, y_max): + """ + Check if a line intersects a face. + + Parameters: + ---------- + line (object): The line object. + face (object): The face object. + + Returns: + ---------- + bool: True if the line intersects the face, False otherwise. + + """ + point = intersection_line_plane(line, Plane.from_frame(face))[0] + point.transform(Transformation.from_frame_to_frame(face, Frame.worldXY())) + print(point) + if point.x >= 0 and point.x <= x_max: + if point.y >= 0 and point.y <= y_max: + print("found it!!!!") + return True + return False + + + + @staticmethod + def get_intersecting_face(intersect_line, main_part, cross_part): + print(main_part, cross_part) + print(cross_part.beam.faces) + for i, face in enumerate(main_part.beam.faces[0:4]): + if i % 2 == 0: + if TButtFactory.line_intersects_face(intersect_line, face, cross_part.blank_length, cross_part.width): + return i, face + else: + if TButtFactory.line_intersects_face(intersect_line, face, cross_part.blank_length, cross_part.height): + return i, face + + + + @staticmethod + def calc_params_birdsmouth(joint, main_part, cross_part): + """ + Calculate the parameters for a birdsmouth joint. + + Parameters: + ---------- + joint (object): The joint object. + main_part (object): The main part object. + cross_part (object): The cross part object. + + Returns: + ---------- + dict: A dictionary containing the calculated parameters for the birdsmouth joint + + """ + face_dict = joint._beam_side_incidence(main_part.beam, cross_part.beam, ignore_ends=True) + sorted_keys = sorted(face_dict.keys(), key=face_dict.get) + + cross_vector = main_part.beam.centerline.direction.cross(cross_part.beam.centerline.direction) + + frame1, frame2 = joint.get_main_cutting_plane()[0], cross_part.beam.faces[sorted_keys[1]] + angle = angle_vectors(cross_vector, frame2.normal, deg=True) + if angle<1.0 or angle>179.0: + return False + + frame1 = Frame(frame1.point, frame1.xaxis, -frame1.yaxis) + print(frame1, cross_part.beam.faces[sorted_keys[0]]) + plane1, plane2 = Plane.from_frame(frame1), Plane.from_frame(frame2) + intersect_vec = Vector.from_start_end(*intersection_plane_plane(plane2, plane1)) + intersect_line = Line(*intersection_plane_plane(plane2, plane1)) + + ind, main_ref_frame = TButtFactory.get_intersecting_face(intersect_line, main_part, cross_part) + + print (ind, main_ref_frame) + + + + + angles_dict = {} + for i, face in enumerate(main_part.beam.faces[0:4]): + angles_dict[i] = (face.normal.angle(cross_part.beam.centerline.direction)) + ref_frame_id = min(angles_dict.keys(), key=angles_dict.get) + ref_frame = main_part.reference_surface_planes(ref_frame_id+1) + + print(angles_dict) + + print("ref_frame", ref_frame_id) + joint.test.append(ref_frame) + + + dot_frame1 = plane1.normal.dot(ref_frame.yaxis) + if dot_frame1 > 0: + plane1, plane2 = plane2, plane1 + + start_point = Point(*intersection_plane_plane_plane(plane1, plane2, Plane.from_frame(ref_frame))) + start_point.transform(Transformation.from_frame_to_frame(ref_frame, Frame.worldXY())) + StartX, StartY = start_point[0], start_point[1] + + intersect_vec1 = Vector.from_start_end(*intersection_plane_plane(plane1, Plane.from_frame(ref_frame))) + intersect_vec2 = Vector.from_start_end(*intersection_plane_plane(plane2, Plane.from_frame(ref_frame))) + + dot_2 = math.degrees(intersect_vec1.dot(ref_frame.yaxis)) + if dot_2 < 0: + intersect_vec1 = -intersect_vec1 + + dot_1 = math.degrees(intersect_vec2.dot(ref_frame.yaxis)) + if dot_1 < 0: + intersect_vec2 = -intersect_vec2 + + if joint.ends[str(main_part.key)] == "start": + reference_frame = ref_frame.xaxis + else: + reference_frame = -ref_frame.xaxis + + Angle1 = math.degrees(intersect_vec1.angle(reference_frame)) + Angle2 = math.degrees(intersect_vec2.angle(reference_frame)) + + Inclination1 = math.degrees(plane1.normal.angle(ref_frame.zaxis)) + Inclination2 = math.degrees(plane2.normal.angle(ref_frame.zaxis)) + + return { + "Orientation": joint.ends[str(main_part.key)], + "StartX": StartX, + "StartY": StartY, + "Angle1": Angle1, + "Inclination1": Inclination1, + "Angle2": Angle2, + "Inclination2": Inclination2, + "ReferencePlaneID": ref_frame_id + } + + + @staticmethod + def calc_params_stepjoint_heel(joint, main_part, cross_part, StepDepth=15): + """ + Calculate the parameters for a step joint. + + Parameters: + ---------- + joint (object): The joint object. + main_part (object): The main part object. + cross_part (object): The cross part object. + StepDepth (float): The depth of the step joint. + + Returns: + ---------- + dict: A dictionary containing the calculated parameters for the step joint + + """ + + # only valid for Heel Step Joint + HeelDepth = 0 + StepShape = "heel" + Tenon = "no" + TenonWidth = 0 + TenonHeight = 0 + + # finding face less facing the cross beam the least + face_dict = joint._beam_side_incidence(cross_part.beam, main_part.beam, ignore_ends=True) + ref_frame_id = min(face_dict, key=face_dict.get) + ref_frame = main_part.beam.faces[ref_frame_id] + + # finding the inclination of the strut based on the two centerlines + StrutInclination = math.degrees(cross_part.beam.centerline.direction.angle(main_part.beam.centerline.direction)) + + # find StartX according to StepDepth + blank_edge_depth = 3-(math.sin(90-StrutInclination)*main_part.beam.width/2) + startx = blank_edge_depth/math.sin(StrutInclination) + + if joint.ends[str(main_part.key)] == "start": + StartX = startx + else: + StartX = main_part.beam.blank_length - startx + + return { + "Orientation": joint.ends[str(main_part.key)], + "StartX": StartX, + "StrutInclination": StrutInclination, + "StepDepth": StepDepth, + "HeelDepth": HeelDepth, + "StepShape": StepShape, + "Tenon": Tenon, + "TenonWidth": TenonWidth, + "TenonHeight": TenonHeight, + "ReferencePlaneID": ref_frame_id + } + + + @classmethod + def apply_processings(cls, joint, parts): + """ + Apply processings to the joint and its associated parts. + + Parameters + ---------- + joint : :class:`~compas_timber.connections.joint.Joint` + The joint object. + parts : dict + A dictionary of the BTLxParts connected by this joint, with part keys as the dictionary keys. + + Returns + ------- + None + + """ + + main_part = parts[str(joint.main_beam.key)] + cross_part = parts[str(joint.cross_beam.key)] + cut_plane, ref_plane = joint.get_main_cutting_plane() + + if joint.birdsmouth: + joint.calc_params_birdsmouth() + ref_face = main_part.beam.faces[joint.btlx_params_main["ReferencePlaneID"]] + joint.btlx_params_main["ReferencePlaneID"] = str(main_part.reference_surface_from_beam_face(ref_face)) + main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_main, "T-Butt Joint")) + elif joint.stepjoint: + joint.calc_params_stepjoint_heel() + ref_face = main_part.beam.faces[joint.btlx_params_main["ReferencePlaneID"]] + joint.btlx_params_main["ReferencePlaneID"] = str(main_part.reference_surface_from_beam_face(ref_face)) + main_part.processings.append(BTLxStepJoint.create_process(joint.btlx_params_main, "T-Butt Joint")) + else: + main_part.processings.append(BTLxJackCut.create_process(main_part, cut_plane, "T-Butt Joint")) + + joint.btlx_params_cross["reference_plane_id"] = cross_part.reference_surface_from_beam_face(ref_plane) + if joint.mill_depth > 0: + joint.btlx_params_cross["machining_limits"] = {"FaceLimitedFront": "no", "FaceLimitedBack": "no"} + cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, "T-Butt Joint")) + + + +BTLx.register_joint(TButtJoint, TButtFactory) From 10de9f39f8967851b06f28c20625ee4a010e57c8 Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 3 May 2024 09:48:32 +0200 Subject: [PATCH 23/82] WIP - birdsmouth inclined pocket adjustment --- src/compas_timber/connections/butt_joint.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index f0c1b24635..7850f492f2 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -117,14 +117,29 @@ def get_main_cutting_plane(self): 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 + + # if self.birdsmouth: + # face_dict = self._beam_side_incidence(self.main_beam, self.cross_beam, ignore_ends=True) + # face_keys = sorted([key for key in face_dict.keys()], key=face_dict.get) + # frame2 = self.cross_beam.faces[face_keys[1]] + + # plane1, plane2 = Plane(cfr.point, -cfr.zaxis), Plane.from_frame(frame2) + # intersection_points = intersection_plane_plane(plane2, plane1) + # intersect_vec = Vector.from_start_end(*intersection_points) + + # # rotate main_cutting plane to create inclined pocket + # tolerance_offset = 0.1 + # offset_angle = math.atan(self.mill_depth / self.cross_beam.width) + # cfr.rotate(offset_angle, intersect_vec, intersection_points[0]) + return cfr, cross_mating_frame def subtraction_volume(self): """Returns the volume to be subtracted from the cross beam.""" vertices = [] - front_frame, back_frame = self.front_back_surface_main() - top_frame, bottom_frame = self.get_main_cutting_plane() - sides = self.side_surfaces_cross() + front_frame, back_frame = self.front_back_surface_main() #main_beam + top_frame, bottom_frame = self.get_main_cutting_plane() #cross_beam -- cutting/offsetted_cutting plane + sides = self.side_surfaces_cross() #cross_beam -- side faces for i, side in enumerate(sides): points = [] for frame in [bottom_frame, top_frame]: From 3672e8e4d4be5fd8cb81584fab68ba3db7497060 Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 3 May 2024 09:58:51 +0200 Subject: [PATCH 24/82] fix name attributes --- .../fabrication/btlx_processes/btlx_double_cut.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_double_cut.py b/src/compas_timber/fabrication/btlx_processes/btlx_double_cut.py index bfe30f7b3c..a7a0157dde 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_double_cut.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_double_cut.py @@ -40,7 +40,7 @@ def __init__(self, param_dict, joint_name=None, **kwargs): if joint_name: self.name = joint_name else: - self.name = "lap" + self.name = "double_cut" @property def header_attributes(self): @@ -58,7 +58,7 @@ def process_params(self): """This property is required for all process types. It returns a dict with the geometric parameters to fabricate the joint.""" if self.apply_process: - """the following attributes are specific to Lap""" + """the following attributes are specific to a Double Cut process.""" od = OrderedDict( [ ("Orientation", str(self.orientation)), @@ -76,6 +76,6 @@ def process_params(self): @classmethod def create_process(cls, param_dict, joint_name=None, **kwargs): - """Creates a lap process from a dictionary of parameters.""" - lap = BTLxDoubleCut(param_dict, joint_name, **kwargs) - return BTLxProcess(BTLxDoubleCut.PROCESS_TYPE, lap.header_attributes, lap.process_params) + """Creates a double cut process from a dictionary of parameters.""" + double_cut = BTLxDoubleCut(param_dict, joint_name, **kwargs) + return BTLxProcess(BTLxDoubleCut.PROCESS_TYPE, double_cut.header_attributes, double_cut.process_params) From 92d585265b274dea78962030229f99f5784c86b4 Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 3 May 2024 09:59:22 +0200 Subject: [PATCH 25/82] text processing initiation --- .../fabrication/btlx_processes/btlx_text.py | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/compas_timber/fabrication/btlx_processes/btlx_text.py diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_text.py b/src/compas_timber/fabrication/btlx_processes/btlx_text.py new file mode 100644 index 0000000000..62eb698c85 --- /dev/null +++ b/src/compas_timber/fabrication/btlx_processes/btlx_text.py @@ -0,0 +1,81 @@ +from collections import OrderedDict +from compas_timber.fabrication import BTLx +from compas_timber.fabrication import BTLxProcess + + +class BTLxText(object): + """ + Represents an engraving process of a text for timber fabrication. + + Parameters + ---------- + param_dict : dict + A dictionary containing the parameters for the BTLx lap process. + joint_name : str + The name of the joint. If not provided, the default name is "lap". + kwargs : dict + Additional keyword arguments to be added to the object. + + """ + + PROCESS_TYPE = "Text" + + def __init__(self, param_dict, joint_name=None, **kwargs): # joint_name replace by "feature_name"? + self.apply_process = True + self.reference_plane_id = param_dict["ReferencePlaneID"] + self.start_x = param_dict["StartX"] + self.start_y = param_dict["StartY"] + self.angle = param_dict["Angle"] + self.inclination = param_dict["Inclination"] + self.depth_limited = param_dict["DepthLimited"] + self.depth = param_dict["Depth"] + self.diameter = param_dict["Diameter"] + + for key, value in param_dict.items(): + setattr(self, key, value) + + for key, value in kwargs.items(): + setattr(self, key, value) + + if joint_name: # to delete since no joint? + self.name = joint_name + else: + self.name = "lap" # what instead? + + @property + def header_attributes(self): + """the following attributes are required for all processes, but the keys and values of header_attributes are process specific.""" + return { + "Name": self.name, + "Process": "yes", + "Priority": "0", + "ProcessID": "0", + "ReferencePlaneID": self.reference_plane_id, + } + + @property + def process_params(self): + """This property is required for all process types. It returns a dict with the geometric parameters to fabricate the joint.""" + + if self.apply_process: + """the following attributes are specific to Lap""" + od = OrderedDict( + [ + ("StartX", "{:.{prec}f}".format(self.start_x, prec=BTLx.POINT_PRECISION)), + ("StartY", "{:.{prec}f}".format(self.start_y, prec=BTLx.POINT_PRECISION)), + ("Angle", "{:.{prec}f}".format(self.angle, prec=BTLx.ANGLE_PRECISION)), + ("Inclination", "{:.{prec}f}".format(self.inclination, prec=BTLx.ANGLE_PRECISION)), + ("DepthLimited", str(self.depth_limited)), + ("Depth", "{:.{prec}f}".format(self.depth, prec=BTLx.POINT_PRECISION)), + ("Diameter", "{:.{prec}f}".format(self.diameter, prec=BTLx.POINT_PRECISION)), + ] + ) + return od + else: + return None + + @classmethod + def create_process(cls, param_dict, joint_name=None, **kwargs): # joint_name replace by "feature_name"? + """Creates a lap process from a dictionary of parameters.""" + lap = BTLxDrilling(param_dict, joint_name, **kwargs) ###change lap??? + return BTLxProcess(BTLxDrilling.PROCESS_TYPE, lap.header_attributes, lap.process_params) From de4b6e69262bf21717236994ac2ed96e6f86796a Mon Sep 17 00:00:00 2001 From: paulocinco Date: Fri, 3 May 2024 09:59:35 +0200 Subject: [PATCH 26/82] changed naming according to process --- .../fabrication/btlx_processes/btlx_drilling.py | 10 +++++----- .../fabrication/btlx_processes/btlx_stepjoint.py | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_drilling.py b/src/compas_timber/fabrication/btlx_processes/btlx_drilling.py index 34a51fe373..5708948d6d 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_drilling.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_drilling.py @@ -40,7 +40,7 @@ def __init__(self, param_dict, joint_name=None, **kwargs): # joint_name replace if joint_name: # to delete since no joint? self.name = joint_name else: - self.name = "lap" # what instead? + self.name = "drilling" # what instead? @property def header_attributes(self): @@ -58,7 +58,7 @@ def process_params(self): """This property is required for all process types. It returns a dict with the geometric parameters to fabricate the joint.""" if self.apply_process: - """the following attributes are specific to Lap""" + """the following attributes are specific to Drilling""" od = OrderedDict( [ ("StartX", "{:.{prec}f}".format(self.start_x, prec=BTLx.POINT_PRECISION)), @@ -76,6 +76,6 @@ def process_params(self): @classmethod def create_process(cls, param_dict, joint_name=None, **kwargs): # joint_name replace by "feature_name"? - """Creates a lap process from a dictionary of parameters.""" - lap = BTLxDrilling(param_dict, joint_name, **kwargs) ###change lap??? - return BTLxProcess(BTLxDrilling.PROCESS_TYPE, lap.header_attributes, lap.process_params) + """Creates a drilling process from a dictionary of parameters.""" + drilling = BTLxDrilling(param_dict, joint_name, **kwargs) + return BTLxProcess(BTLxDrilling.PROCESS_TYPE, drilling.header_attributes, drilling.process_params) diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_stepjoint.py b/src/compas_timber/fabrication/btlx_processes/btlx_stepjoint.py index 36f7ad4e1c..f19ced3542 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_stepjoint.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_stepjoint.py @@ -10,9 +10,9 @@ class BTLxStepJoint(object): Parameters ---------- param_dict : dict - A dictionary containing the parameters for the BTLx lap process. + A dictionary containing the parameters for the BTLx Step Joint process. joint_name : str - The name of the joint. If not provided, the default name is "lap". + The name of the joint. If not provided, the default name is "step joint". kwargs : dict Additional keyword arguments to be added to the object. @@ -42,7 +42,7 @@ def __init__(self, param_dict, joint_name=None, **kwargs): if joint_name: self.name = joint_name else: - self.name = "lap" + self.name = "step joint" @property def header_attributes(self): @@ -60,7 +60,7 @@ def process_params(self): """This property is required for all process types. It returns a dict with the geometric parameters to fabricate the joint.""" if self.apply_process: - """the following attributes are specific to Lap""" + """the following attributes are specific to Step Joint""" od = OrderedDict( [ ("Orientation", str(self.orientation)), @@ -81,6 +81,6 @@ def process_params(self): @classmethod def create_process(cls, param_dict, joint_name=None, **kwargs): - """Creates a lap process from a dictionary of parameters.""" - lap = BTLxStepJoint(param_dict, joint_name, **kwargs) - return BTLxProcess(BTLxStepJoint.PROCESS_TYPE, lap.header_attributes, lap.process_params) + """Creates a Step Joint process from a dictionary of parameters.""" + step_joint = BTLxStepJoint(param_dict, joint_name, **kwargs) + return BTLxProcess(BTLxStepJoint.PROCESS_TYPE, step_joint.header_attributes, step_joint.process_params) From b63a6686cd5ac4d7ef9fe230e63b039bf1ae7017 Mon Sep 17 00:00:00 2001 From: paulocinco Date: Fri, 3 May 2024 11:06:19 +0200 Subject: [PATCH 27/82] WIP drilling --- src/compas_timber/connections/butt_joint.py | 39 ++++++++++++++++++++- src/compas_timber/connections/t_butt.py | 4 +-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index f0c1b24635..730e5bfc5d 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -45,16 +45,19 @@ class ButtJoint(Joint): """ - def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, birdsmouth=False, **kwargs): + def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, drill_diameter=0, drill_depth=0, birdsmouth=False, **kwargs): super(ButtJoint, self).__init__(**kwargs) 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.drill_diameter = drill_diameter + self.drill_depth = drill_depth self.birdsmouth = birdsmouth self.btlx_params_main = {} self.btlx_params_cross = {} + self.btlx_drilling_params_cross = {} self.features = [] self.test = [] @@ -335,3 +338,37 @@ def calc_params_birdsmouth(self): "Inclination2": Inclination2, "ReferencePlaneID": ref_frame_id, } + + def calc_params_drilling(self): + """ + Calculate the parameters for a drilling joint. + + Parameters: + ---------- + joint (object): The joint object. + main_part (object): The main part object. + cross_part (object): The cross part object. + + Returns: + ---------- + dict: A dictionary containing the calculated parameters for the drilling joint + + """ + if self.drill_depth > 0: + DepthLimited = "yes" + else: + DepthLimited = "no" + + + + + self.btlx_drilling_params_cross = { + "ReferencePlaneID": ref_frame_id, + "StartX": StartX, + "StartY": StartY, + "Angle": Angle, + "Inclination": Inclination, + "DepthLimited": DepthLimited, + "Depth": self.drill_depth, + "Diameter": self.drill_diameter + } diff --git a/src/compas_timber/connections/t_butt.py b/src/compas_timber/connections/t_butt.py index 9b72305015..8d0159d077 100644 --- a/src/compas_timber/connections/t_butt.py +++ b/src/compas_timber/connections/t_butt.py @@ -33,8 +33,8 @@ class TButtJoint(ButtJoint): SUPPORTED_TOPOLOGY = JointTopology.TOPO_T - def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, birdsmouth=False, **kwargs): - super(TButtJoint, self).__init__(main_beam, cross_beam, mill_depth, birdsmouth, **kwargs) + def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, drill_diameter=0, drill_depth=0, birdsmouth=False, **kwargs): + super(TButtJoint, self).__init__(main_beam, cross_beam, mill_depth, drill_diameter, drill_depth, birdsmouth, **kwargs) def add_features(self): """Adds the trimming plane to the main beam (no features for the cross beam). From 4c654b50c04dcb05074480d4c81e15ee23549c82 Mon Sep 17 00:00:00 2001 From: obucklin Date: Fri, 3 May 2024 14:54:52 +0200 Subject: [PATCH 28/82] fixed bugs --- src/compas_timber/connections/butt_joint.py | 77 +--------- .../connections/french_ridge_lap.py | 6 +- src/compas_timber/connections/joint.py | 15 +- src/compas_timber/connections/l_butt.py | 40 +++-- src/compas_timber/connections/l_halflap.py | 29 ++-- src/compas_timber/connections/l_miter.py | 25 ++- src/compas_timber/connections/t_butt.py | 22 ++- src/compas_timber/connections/t_halflap.py | 18 ++- .../connections/t_stirnversatz.py | 47 +++--- src/compas_timber/connections/x_halflap.py | 3 + src/compas_timber/fabrication/__init__.py | 2 + src/compas_timber/fabrication/btlx.py | 1 - .../btlx_processes/btlx_double_cut.py | 2 +- .../btlx_processes/btlx_doublecut.py | 2 +- .../btlx_processes/btlx_dovetail.py | 22 ++- .../btlx_processes/btlx_french_ridge_lap.py | 10 +- .../fabrication/btlx_processes/btlx_lap.py | 2 +- .../joint_factories/french_ridge_factory.py | 2 +- .../joint_factories/l_butt_factory.py | 24 +-- .../joint_factories/t_butt_factory.py | 143 +----------------- 20 files changed, 191 insertions(+), 301 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 028b41c9de..6a8d91dea3 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -16,6 +16,7 @@ from .joint import Joint import math + class ButtJoint(Joint): """Abstract Lap type joint with functions common to L-Butt and T-Butt Joints. @@ -118,8 +119,6 @@ def get_main_cutting_plane(self): 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): @@ -191,79 +190,6 @@ def subtraction_volume(self): return ph - # @staticmethod - # def calc_params_birdsmouth(joint, main_part, cross_part): - # """ - # Calculate the parameters for a birdsmouth joint. - - # Parameters: - # ---------- - # joint (object): The joint object. - # main_part (object): The main part object. - # cross_part (object): The cross part object. - - # Returns: - # ---------- - # dict: A dictionary containing the calculated parameters for the birdsmouth joint - - # """ - # face_dict = joint._beam_side_incidence(main_part.beam, cross_part.beam, ignore_ends=True) - # face_dict = sorted(face_dict, key=face_dict.get) - - # # frame1 = joint.get_main_cutting_plane()[0] - # frame1 = joint.get_main_cutting_plane()[0] - # frame2 = cross_part.beam.faces[face_dict[1]] - - # plane1, plane2 = Plane.from_frame(frame1), Plane.from_frame(frame2) - # intersect_vec = Vector.from_start_end(*intersection_plane_plane(plane2, plane1)) - - # angles_dict = {} - # for i, face in enumerate(main_part.beam.faces): - # angles_dict[i] = (face.normal.angle(intersect_vec)) - # ref_frame_id = min(angles_dict, key=angles_dict.get) - # ref_frame = main_part.reference_surface_planes(ref_frame_id+1) - - # dot_frame1 = plane1.normal.dot(ref_frame.yaxis) - # if dot_frame1 > 0: - # plane1, plane2 = plane2, plane1 - - # start_point = Point(*intersection_plane_plane_plane(plane1, plane2, Plane.from_frame(ref_frame))) - # start_point.transform(Transformation.from_frame_to_frame(ref_frame, Frame.worldXY())) - # StartX, StartY = start_point[0], start_point[1] - - # intersect_vec1 = Vector.from_start_end(*intersection_plane_plane(plane1, Plane.from_frame(ref_frame))) - # intersect_vec2 = Vector.from_start_end(*intersection_plane_plane(plane2, Plane.from_frame(ref_frame))) - - # dot_2 = math.degrees(intersect_vec1.dot(ref_frame.yaxis)) - # if dot_2 < 0: - # intersect_vec1 = -intersect_vec1 - - # dot_1 = math.degrees(intersect_vec2.dot(ref_frame.yaxis)) - # if dot_1 < 0: - # intersect_vec2 = -intersect_vec2 - - # if joint.ends[str(main_part.key)] == "start": - # reference_frame = ref_frame.xaxis - # else: - # reference_frame = -ref_frame.xaxis - - # Angle1 = math.degrees(intersect_vec1.angle(reference_frame)) - # Angle2 = math.degrees(intersect_vec2.angle(reference_frame)) - - # Inclination1 = math.degrees(plane1.normal.angle(ref_frame.zaxis)) - # Inclination2 = math.degrees(plane2.normal.angle(ref_frame.zaxis)) - - # return { - # "Orientation": joint.ends[str(main_part.key)], - # "StartX": StartX, - # "StartY": StartY, - # "Angle1": Angle1, - # "Inclination1": Inclination1, - # "Angle2": Angle2, - # "Inclination2": Inclination2, - # "ReferencePlaneID": ref_frame_id - # } - def calc_params_birdsmouth(self): """ Calculate the parameters for a birdsmouth joint. @@ -301,7 +227,6 @@ def calc_params_birdsmouth(self): else: ref_frame.point = ref_frame.point - ref_frame.yaxis * self.main_beam.width * 0.5 ref_frame.point = ref_frame.point + ref_frame.zaxis * self.main_beam.height * 0.5 - self.test.append(ref_frame) start_point = Point(*intersection_plane_plane_plane(plane1, plane2, Plane.from_frame(ref_frame))) start_point.transform(Transformation.from_frame_to_frame(ref_frame, Frame.worldXY())) diff --git a/src/compas_timber/connections/french_ridge_lap.py b/src/compas_timber/connections/french_ridge_lap.py index dd9f5192cd..c7c3524ac6 100644 --- a/src/compas_timber/connections/french_ridge_lap.py +++ b/src/compas_timber/connections/french_ridge_lap.py @@ -80,9 +80,11 @@ def restore_beams_from_keys(self, assemly): self.beam_b = assemly.find_by_key(self.beam_b_key) self._beams = (self.beam_a, self.beam_b) + def add_extensions(self): + self.beam_a.add_blank_extension(*self.beam_a.extension_to_plane(self.cutting_plane_top), joint_key=self.key) + self.beam_b.add_blank_extension(*self.beam_b.extension_to_plane(self.cutting_plane_bottom), joint_key=self.key) + def add_features(self): - self.beam_a.add_blank_extension(*self.beam_a.extension_to_plane(self.cutting_plane_top), joint_key = self.key) - self.beam_b.add_blank_extension(*self.beam_b.extension_to_plane(self.cutting_plane_bottom), joint_key = self.key) self.features = [] def check_geometry(self): diff --git a/src/compas_timber/connections/joint.py b/src/compas_timber/connections/joint.py index 0a9a246f12..e49195c9d4 100644 --- a/src/compas_timber/connections/joint.py +++ b/src/compas_timber/connections/joint.py @@ -76,6 +76,17 @@ def __data__(self): def beams(self): return self._beams + def add_extensions(self): + """Adds the features defined by this joint to affected beam(s). + + Raises + ------ + :class:`~compas_timber.connections.BeamJoinningError` + Should be raised whenever the joint was not able to calculate the features to be applied to the beams. + + """ + raise NotImplementedError + def add_features(self): """Adds the features defined by this joint to affected beam(s). @@ -134,7 +145,7 @@ def create(cls, assembly, *beams, **kwargs): raise ValueError("Expected at least 2 beams. Got instead: {}".format(len(beams))) joint = cls(*beams, **kwargs) assembly.add_joint(joint, beams) - joint.add_features() + joint.add_extensions() return joint @property @@ -238,7 +249,7 @@ def _beam_side_incidence(beam_a, beam_b, ignore_ends=True): raise AssertionError("No intersection found") end, _ = beam_a.endpoint_closest_to_point(Point(*p1x)) - + print(end) if end == "start": centerline_vec = beam_a.centerline.vector else: diff --git a/src/compas_timber/connections/l_butt.py b/src/compas_timber/connections/l_butt.py index acc961a6ce..2fb2c1007c 100644 --- a/src/compas_timber/connections/l_butt.py +++ b/src/compas_timber/connections/l_butt.py @@ -49,6 +49,7 @@ def __init__( main_beam=None, cross_beam=None, mill_depth=0, + birdsmouth=False, small_beam_butts=False, modify_cross=True, reject_i=False, @@ -58,7 +59,7 @@ def __init__( if main_beam.width * main_beam.height > cross_beam.width * cross_beam.height: main_beam, cross_beam = cross_beam, main_beam - super(LButtJoint, self).__init__(main_beam, cross_beam, mill_depth, **kwargs) + super(LButtJoint, self).__init__(main_beam, cross_beam, mill_depth, birdsmouth, **kwargs) self.modify_cross = modify_cross self.small_beam_butts = small_beam_butts self.reject_i = reject_i @@ -88,6 +89,32 @@ def get_main_cutting_plane(self): ) return super(LButtJoint, self).get_main_cutting_plane() + def add_extensions(self): + """Adds the required extensions 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 + extension_tolerance = 0.01 # TODO: this should be proportional to the unit used + if self.birdsmouth: + extension_plane_main = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True)[ + 1 + ] + else: + extension_plane_main = self.get_face_most_towards_beam(self.main_beam, self.cross_beam, ignore_ends=True)[1] + start_main, end_main = self.main_beam.extension_to_plane(extension_plane_main) + self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.key) + + extension_plane_cross = self.get_face_most_towards_beam(self.cross_beam, self.main_beam, ignore_ends=True)[1] + print("EPC = ", extension_plane_cross) + self.test.append(extension_plane_cross) + start_cross, end_cross = self.cross_beam.extension_to_plane(extension_plane_cross) + self.cross_beam.add_blank_extension( + start_cross + extension_tolerance, end_cross + extension_tolerance, self.key + ) + print("lbutt extensions", self.cross_beam._blank_extensions) + def add_features(self): """Adds the required extension and trimming features to both beams. @@ -102,8 +129,7 @@ def add_features(self): 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: @@ -113,18 +139,12 @@ def add_features(self): 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) if self.mill_depth: self.cross_beam.add_features(MillVolume(self.subtraction_volume())) diff --git a/src/compas_timber/connections/l_halflap.py b/src/compas_timber/connections/l_halflap.py index 86d3d5734e..e5dc603517 100644 --- a/src/compas_timber/connections/l_halflap.py +++ b/src/compas_timber/connections/l_halflap.py @@ -50,6 +50,26 @@ class LHalfLapJoint(LapJoint): 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_extensions(self): + """Adds the extensions to the main beam and cross beam. + + This method is automatically called when joint is created by the call to `Joint.create()`. + + """ + + assert self.main_beam and self.cross_beam + extension_tolerance = 0.01 # TODO: this should be proportional to the unit used + + extension_plane_main = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True)[1] + start_main, end_main = self.main_beam.extension_to_plane(extension_plane_main) + self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.key) + + extension_plane_cross = self.get_face_most_ortho_to_beam(self.cross_beam, self.main_beam, ignore_ends=True)[1] + start_cross, end_cross = self.cross_beam.extension_to_plane(extension_plane_cross) + self.cross_beam.add_blank_extension( + start_cross + extension_tolerance, end_cross + extension_tolerance, self.key + ) + def add_features(self): assert self.main_beam and self.cross_beam @@ -60,15 +80,6 @@ def add_features(self): except Exception as ex: raise BeamJoinningError(beams=self.beams, joint=self, debug_info=str(ex)) - start_main, end_main = self.main_beam.extension_to_plane(main_cutting_frame) - 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.cross_beam.add_blank_extension( - start_cross + extension_tolerance, end_cross + extension_tolerance, self.key - ) - main_volume = MillVolume(negative_brep_main_beam) cross_volume = MillVolume(negative_brep_cross_beam) diff --git a/src/compas_timber/connections/l_miter.py b/src/compas_timber/connections/l_miter.py index c931777c8d..39173323cb 100644 --- a/src/compas_timber/connections/l_miter.py +++ b/src/compas_timber/connections/l_miter.py @@ -100,6 +100,26 @@ def get_cutting_planes(self): plnB = Frame.from_plane(plnB) return plnA, plnB + def add_extensions(self): + """Adds the extensions to the main beam and cross beam. + + This method is automatically called when joint is created by the call to `Joint.create()`. + + """ + + assert self.main_beam and self.cross_beam + extension_tolerance = 0.01 # TODO: this should be proportional to the unit used + + extension_plane_main = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True)[1] + start_main, end_main = self.main_beam.extension_to_plane(extension_plane_main) + self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.key) + + extension_plane_cross = self.get_face_most_ortho_to_beam(self.cross_beam, self.main_beam, ignore_ends=True)[1] + start_cross, end_cross = self.cross_beam.extension_to_plane(extension_plane_cross) + self.cross_beam.add_blank_extension( + start_cross + extension_tolerance, end_cross + extension_tolerance, self.key + ) + def add_features(self): """Adds the required extension and trimming features to both beams. @@ -115,8 +135,6 @@ def add_features(self): start_a, start_b = None, None try: plane_a, plane_b = self.get_cutting_planes() - start_a, end_a = self.beam_a.extension_to_plane(plane_a) - start_b, end_b = self.beam_b.extension_to_plane(plane_b) except AttributeError as ae: # I want here just the plane that caused the error geometries = [plane_b] if start_a is not None else [plane_a] @@ -124,9 +142,6 @@ def add_features(self): except Exception as ex: raise BeamJoinningError(self.beams, self, debug_info=str(ex)) - self.beam_a.add_blank_extension(start_a, end_a, self.key) - self.beam_b.add_blank_extension(start_b, end_b, self.key) - f1, f2 = CutFeature(plane_a), CutFeature(plane_b) self.beam_a.add_features(f1) self.beam_b.add_features(f2) diff --git a/src/compas_timber/connections/t_butt.py b/src/compas_timber/connections/t_butt.py index 9b72305015..83dfb8711c 100644 --- a/src/compas_timber/connections/t_butt.py +++ b/src/compas_timber/connections/t_butt.py @@ -36,6 +36,23 @@ class TButtJoint(ButtJoint): def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, birdsmouth=False, **kwargs): super(TButtJoint, self).__init__(main_beam, cross_beam, mill_depth, birdsmouth, **kwargs) + def add_extensions(self): + """Adds the extensions to the main beam and cross beam. + + This method is automatically called when joint is created by the call to `Joint.create()`. + + """ + assert self.main_beam and self.cross_beam + extension_tolerance = 0.01 # TODO: this should be proportional to the unit used + if self.birdsmouth: + extension_plane_main = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True)[ + 1 + ] + else: + extension_plane_main = self.get_face_most_towards_beam(self.main_beam, self.cross_beam, ignore_ends=True)[1] + start_main, end_main = self.main_beam.extension_to_plane(extension_plane_main) + self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.key) + def add_features(self): """Adds the trimming plane to the main beam (no features for the cross beam). @@ -49,15 +66,12 @@ def add_features(self): cutting_plane = None try: cutting_plane = self.get_main_cutting_plane()[0] - start_main, end_main = self.main_beam.extension_to_plane(cutting_plane) + except AttributeError as ae: raise BeamJoinningError(beams=self.beams, joint=self, debug_info=str(ae), debug_geometries=[cutting_plane]) 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 - self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.key) - trim_feature = CutFeature(cutting_plane) if self.mill_depth: self.cross_beam.add_features(MillVolume(self.subtraction_volume())) diff --git a/src/compas_timber/connections/t_halflap.py b/src/compas_timber/connections/t_halflap.py index 65cf24e278..9d21426a3b 100644 --- a/src/compas_timber/connections/t_halflap.py +++ b/src/compas_timber/connections/t_halflap.py @@ -34,6 +34,20 @@ class THalfLapJoint(LapJoint): 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_extensions(self): + """Adds the extensions to the main beam and cross beam. + + This method is automatically called when joint is created by the call to `Joint.create()`. + + """ + + assert self.main_beam and self.cross_beam + extension_tolerance = 0.01 # TODO: this should be proportional to the unit used + + extension_plane_main = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True)[1] + start_main, end_main = self.main_beam.extension_to_plane(extension_plane_main) + self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.key) + def add_features(self): assert self.main_beam and self.cross_beam # should never happen @@ -41,7 +55,6 @@ def add_features(self): try: main_cutting_frame = self.get_main_cutting_frame() negative_brep_main_beam, negative_brep_cross_beam = self._create_negative_volumes() - start_main, end_main = self.main_beam.extension_to_plane(main_cutting_frame) except AttributeError as ae: raise BeamJoinningError( beams=self.beams, joint=self, debug_info=str(ae), debug_geometries=[main_cutting_frame] @@ -49,9 +62,6 @@ def add_features(self): 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 - self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.key) - main_volume = MillVolume(negative_brep_main_beam) cross_volume = MillVolume(negative_brep_cross_beam) self.main_beam.add_features(main_volume) diff --git a/src/compas_timber/connections/t_stirnversatz.py b/src/compas_timber/connections/t_stirnversatz.py index 870804bd21..30ef490aad 100644 --- a/src/compas_timber/connections/t_stirnversatz.py +++ b/src/compas_timber/connections/t_stirnversatz.py @@ -16,11 +16,14 @@ from compas.geometry import cross_vectors import math + class TStirnversatzJoint(Joint): - + SUPPORTED_TOPOLOGY = JointTopology.TOPO_T - def __init__(self, cross_beam=None, main_beam=None, cut_depth=0.25, extend_cut=True): #TODO Why main & cross swapped??? + def __init__( + self, cross_beam=None, main_beam=None, cut_depth=0.25, extend_cut=True + ): # TODO Why main & cross swapped??? super(TStirnversatzJoint, self).__init__(main_beam, cross_beam, cut_depth) self.main_beam = main_beam self.cross_beam = cross_beam @@ -31,10 +34,10 @@ def __init__(self, cross_beam=None, main_beam=None, cut_depth=0.25, extend_cut=T self.features = [] self.cross_cutting_plane_1 = None self.cross_cutting_plane_2 = None - self.planetogh = [] # TODO Remove - self.linetogh = [] # TODO Remove - self.pointtogh = [] # TODO Remove - self.polyhedrontogh = [] # TODO Remove + self.planetogh = [] # TODO Remove + self.linetogh = [] # TODO Remove + self.pointtogh = [] # TODO Remove + self.polyhedrontogh = [] # TODO Remove @property def data(self): @@ -69,11 +72,11 @@ def _bisector_plane(plane1, plane2, angle_factor): bisector.transform(R) plane = Plane(origin, bisector) return plane - - #find the Face on cross_beam where main_beam intersects - #TODO simplify with Chen! + + # find the Face on cross_beam where main_beam intersects + # TODO simplify with Chen! def get_main_intersection_frame(self): - diagonal = math.sqrt(self.main_beam.width ** 2 + self.main_beam.height ** 2) + diagonal = math.sqrt(self.main_beam.width**2 + self.main_beam.height**2) main_frames = self.main_beam.faces[:4] cross_centerline = self.cross_beam.centerline cross_centerpoint = midpoint_line(self.cross_beam.centerline) @@ -102,13 +105,13 @@ def _sort_frames_according_normals(frames, checkvector): angles.append(angle_vectors(checkvector, i.normal)) angles, frames = zip(*sorted(zip(angles, frames))) return frames - + @staticmethod def _flip_plane_according_vector(plane, vector): if angle_vectors(plane.normal, vector, True) > 90: plane = Plane(plane.point, plane.normal * -1) return plane - + def get_cross_cutting_planes(self): main_int_frame = self.get_main_intersection_frame() main_int_plane = Plane.from_frame(main_int_frame) @@ -129,7 +132,7 @@ def get_cross_cutting_planes(self): self.cross_cutting_plane_1 = cutplane_1 self.cross_cutting_plane_2 = cutplane_2 return self.cross_cutting_plane_1, self.cross_cutting_plane_2 - + def get_main_cutting_volume(self): main_int_frame = self.get_main_intersection_frame() main_int_plane = Plane.from_frame(main_int_frame) @@ -138,13 +141,13 @@ def get_main_cutting_volume(self): l3 = intersection_plane_plane(self.cross_cutting_plane_1, self.cross_cutting_plane_2) main_frames_sorted = self._sort_frames_according_normals(self.main_beam.faces[:4], main_int_frame.zaxis) cut_frames_sorted = self._sort_frames_according_normals(self.cross_beam.faces[:4], main_int_frame.zaxis) - + # Extend Cut True or False if self.extend_cut == True: plane_side = [Plane.from_frame(main_frames_sorted[1]), Plane.from_frame(main_frames_sorted[2])] else: plane_side = [Plane.from_frame(cut_frames_sorted[1]), Plane.from_frame(cut_frames_sorted[2])] - + crossvector = cross_vectors(self.cross_cutting_plane_2.normal, self.cross_cutting_plane_1.normal) plane_side = self._sort_frames_according_normals(plane_side, crossvector) @@ -154,19 +157,23 @@ def get_main_cutting_volume(self): points.append(intersection_line_plane(i, plane_side[0])) points.append(intersection_line_plane(i, plane_side[1])) - #TODO fix with Chen: Polyhedron.from_planes not working because numpy missing ????? - main_cutting_volume = Polyhedron(points, - [ + # TODO fix with Chen: Polyhedron.from_planes not working because numpy missing ????? + main_cutting_volume = Polyhedron( + points, + [ [0, 2, 4], # front [1, 5, 3], # back [0, 1, 3, 2], # first [2, 3, 5, 4], # second [4, 5, 1, 0], # third ], - ) + ) return main_cutting_volume + def add_extensions(self): + pass + def add_features(self): assert self.main_beam and self.cross_beam # should never happen @@ -179,4 +186,4 @@ def add_features(self): trim_feature = CutFeature(cross_cutting_plane2) self.cross_beam.add_features(trim_feature) volume = MillVolume(main_cutting_vol) - self.main_beam.add_features(volume) \ No newline at end of file + self.main_beam.add_features(volume) diff --git a/src/compas_timber/connections/x_halflap.py b/src/compas_timber/connections/x_halflap.py index 67553a2355..2ba9cb257f 100644 --- a/src/compas_timber/connections/x_halflap.py +++ b/src/compas_timber/connections/x_halflap.py @@ -32,6 +32,9 @@ class XHalfLapJoint(LapJoint): 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_extensions(self): + pass + def add_features(self): assert self.main_beam and self.cross_beam # should never happen diff --git a/src/compas_timber/fabrication/__init__.py b/src/compas_timber/fabrication/__init__.py index 0f680f849c..f886cb02e7 100644 --- a/src/compas_timber/fabrication/__init__.py +++ b/src/compas_timber/fabrication/__init__.py @@ -3,6 +3,7 @@ from .btlx_processes.btlx_french_ridge_lap import BTLxFrenchRidgeLap from .btlx_processes.btlx_jack_cut import BTLxJackCut from .btlx_processes.btlx_lap import BTLxLap +from .btlx_processes.btlx_double_cut import BTLxDoubleCut from .joint_factories.french_ridge_factory import FrenchRidgeFactory from .joint_factories.l_butt_factory import LButtFactory from .joint_factories.l_miter_factory import LMiterFactory @@ -13,6 +14,7 @@ "BTLxProcess", "BTLxJackCut", "BTLxLap", + "BTLxDoubleCut", "BTLxFrenchRidgeLap", "LButtFactory", "TButtFactory", diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index 78e6f71b50..e194c4a767 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -286,7 +286,6 @@ def et_point_vals(self, point): "Z": "{:.{prec}f}".format(point.z, prec=BTLx.POINT_PRECISION), } - @property def et_element(self): if not self._et_element: diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_double_cut.py b/src/compas_timber/fabrication/btlx_processes/btlx_double_cut.py index bfe30f7b3c..1e412e843f 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_double_cut.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_double_cut.py @@ -22,7 +22,7 @@ class BTLxDoubleCut(object): def __init__(self, param_dict, joint_name=None, **kwargs): self.apply_process = True - self.reference_plane_id = param_dict["ReferencePlaneID"] + self.reference_plane_id = str(param_dict["ReferencePlaneID"]) self.orientation = param_dict["Orientation"] self.start_x = param_dict["StartX"] self.start_y = param_dict["StartY"] diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_doublecut.py b/src/compas_timber/fabrication/btlx_processes/btlx_doublecut.py index bafc232710..8972cc4cce 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_doublecut.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_doublecut.py @@ -67,7 +67,7 @@ def process_params(self): ("Angle1", "{:.{prec}f}".format(self.angle1, prec=BTLx.ANGLE_PRECISION)), ("Inclination1", "{:.{prec}f}".format(self.inclination1, prec=BTLx.ANGLE_PRECISION)), ("Angle2", "{:.{prec}f}".format(self.angle2, prec=BTLx.ANGLE_PRECISION)), - ("Inclination2", "{:.{prec}f}".format(self.inclination2, prec=BTLx.ANGLE_PRECISION)) + ("Inclination2", "{:.{prec}f}".format(self.inclination2, prec=BTLx.ANGLE_PRECISION)), ] ) print("param dict", od) diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_dovetail.py b/src/compas_timber/fabrication/btlx_processes/btlx_dovetail.py index f9d2605699..c373b70b5b 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_dovetail.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_dovetail.py @@ -3,9 +3,10 @@ from compas_timber.fabrication import BTLxProcess FLANK_ANGLE = 15.0 -SHAPE_RADIUS = 30##CHECK +SHAPE_RADIUS = 30 ##CHECK LENGTH_LIMITED_BOTTOM = True + class BTLxDoveTailTenon(object): """ Represents a dovetail_tenon process for timber fabrication. @@ -23,8 +24,6 @@ class BTLxDoveTailTenon(object): PROCESS_TYPE = "DoveTail_Tenon" - - def __init__(self, param_dict, joint_name=None, **kwargs): self.apply_process = True self.reference_plane_id = param_dict["ReferencePlaneID"] @@ -42,9 +41,9 @@ def __init__(self, param_dict, joint_name=None, **kwargs): self.height = param_dict["Height"] self.cone_angle = param_dict["ConeAngle"] self.use_flank_angle = bool(True) - self.flank_angle = FLANK_ANGLE ##check + self.flank_angle = FLANK_ANGLE ##check self.shape = str("automatic") - self.shape_radius = SHAPE_RADIUS #check + self.shape_radius = SHAPE_RADIUS # check for key, value in param_dict.items(): setattr(self, key, value) @@ -92,7 +91,7 @@ def process_params(self): ("UseFlankAngle", bool(self.use_flank_angle)), ("FlankAngle", "{:.{prec}f}".format(self.flank_angle, prec=BTLx.POINT_PRECISION)), ("Shape", str(self.shape)), - ("ShapeRadius", "{:.{prec}f}".format(self.shape_radius, prec=BTLx.POINT_PRECISION)) + ("ShapeRadius", "{:.{prec}f}".format(self.shape_radius, prec=BTLx.POINT_PRECISION)), ] ) return od @@ -106,7 +105,6 @@ def create_process(cls, param_dict, joint_name=None, **kwargs): return BTLxProcess(BTLxDoveTailTenon.PROCESS_TYPE, dovetail_t.header_attributes, dovetail_t.process_params) - class BTLxDoveTailMortise(object): """ Represents a dovetail_mortise process for timber fabrication. @@ -124,8 +122,6 @@ class BTLxDoveTailMortise(object): PROCESS_TYPE = "DoveTail_Mortise" - - def __init__(self, param_dict, joint_name=None, **kwargs): self.apply_process = True self.reference_plane_id = param_dict["ReferencePlaneID"] @@ -140,12 +136,12 @@ def __init__(self, param_dict, joint_name=None, **kwargs): self.length_limited_bottom = bool(LENGTH_LIMITED_BOTTOM) self.length = param_dict["Length"] self.width = param_dict["Width"] - self.depth = param_dict["Depth"] + self.depth = param_dict["Depth"] self.cone_angle = param_dict["ConeAngle"] self.use_flank_angle = bool(True) - self.flank_angle = FLANK_ANGLE ##check + self.flank_angle = FLANK_ANGLE ##check self.shape = str("automatic") - self.shape_radius = SHAPE_RADIUS #check + self.shape_radius = SHAPE_RADIUS # check for key, value in param_dict.items(): setattr(self, key, value) @@ -192,7 +188,7 @@ def process_params(self): ("UseFlankAngle", bool(self.use_flank_angle)), ("FlankAngle", "{:.{prec}f}".format(self.flank_angle, prec=BTLx.POINT_PRECISION)), ("Shape", str(self.shape)), - ("ShapeRadius", "{:.{prec}f}".format(self.shape_radius, prec=BTLx.POINT_PRECISION)) + ("ShapeRadius", "{:.{prec}f}".format(self.shape_radius, prec=BTLx.POINT_PRECISION)), ] ) return od diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_french_ridge_lap.py b/src/compas_timber/fabrication/btlx_processes/btlx_french_ridge_lap.py index f29df98c74..121fea60f2 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_french_ridge_lap.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_french_ridge_lap.py @@ -152,7 +152,6 @@ def get_params(self): self.angle_rad = math.pi - self.angle_rad self.startX = abs(self.beam.width / math.tan(self.angle_rad)) - if self.orientation == "end": if self._ref_edge: self.startX = self.beam.blank_length - self.startX @@ -160,7 +159,14 @@ def get_params(self): self.startX = self.beam.blank_length + self.startX print("orientation: ", self.orientation, " angle: ", self.angle_rad, " start: ", self.startX) - print("ref_edge: ", self.ref_edge, " drill_hole: ", self.drill_hole, " drill_hole_diameter: ", self.drill_hole_diameter) + print( + "ref_edge: ", + self.ref_edge, + " drill_hole: ", + self.drill_hole, + " drill_hole_diameter: ", + self.drill_hole_diameter, + ) @classmethod def create_process(cls, part, joint, is_top): diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_lap.py b/src/compas_timber/fabrication/btlx_processes/btlx_lap.py index c388b42744..3d835a66ed 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_lap.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_lap.py @@ -22,7 +22,7 @@ class BTLxLap(object): def __init__(self, param_dict, joint_name=None, **kwargs): self.apply_process = True - self.reference_plane_id = 0 + self.reference_plane_id = param_dict["ReferencePlaneID"] self.orientation = "start" self.start_x = 0.0 self.start_y = 0.0 diff --git a/src/compas_timber/fabrication/joint_factories/french_ridge_factory.py b/src/compas_timber/fabrication/joint_factories/french_ridge_factory.py index 0d5805b0a3..41e156810e 100644 --- a/src/compas_timber/fabrication/joint_factories/french_ridge_factory.py +++ b/src/compas_timber/fabrication/joint_factories/french_ridge_factory.py @@ -37,7 +37,7 @@ def apply_processings(cls, joint, parts): bottom_key = joint.beams[1].key bottom_part = parts[str(bottom_key)] # bottom_part._test.append( - # bottom_part.reference_surface_planes(joint.reference_face_indices[str(bottom_part.beam.key)]) + # bottom_part.reference_surface_planes(joint.reference_face_indices[str(bottom_part.beam.key)]) # ) bottom_part.processings.append(BTLxFrenchRidgeLap.create_process(bottom_part, joint, False)) diff --git a/src/compas_timber/fabrication/joint_factories/l_butt_factory.py b/src/compas_timber/fabrication/joint_factories/l_butt_factory.py index 84ef9e418a..5b29e1b052 100644 --- a/src/compas_timber/fabrication/joint_factories/l_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/l_butt_factory.py @@ -2,6 +2,7 @@ from compas_timber.fabrication import BTLx from compas_timber.fabrication import BTLxJackCut from compas_timber.fabrication import BTLxLap +from compas_timber.fabrication import BTLxDoubleCut class LButtFactory(object): @@ -31,16 +32,11 @@ def apply_processings(cls, joint, parts): main_part = parts[str(joint.main_beam.key)] cross_part = parts[str(joint.cross_beam.key)] - main_part.processings.append( - BTLxJackCut.create_process(main_part, joint.get_main_cutting_plane()[0], "L-Butt Joint") - ) - cross_part.processings.append( - BTLxJackCut.create_process(cross_part, joint.get_cross_cutting_plane(), "L-Butt Joint") - ) + main_cut_plane, ref_plane = joint.get_main_cutting_plane() + cross_cut_plane = joint.get_cross_cutting_plane() + cross_part.processings.append(BTLxJackCut.create_process(cross_part, cross_cut_plane, "L-Butt Joint")) + if joint.mill_depth > 0: - ref_face = cross_part.beam.faces[joint.reference_side_index_cross] - joint.btlx_params_cross["reference_plane_id"] = str(cross_part.reference_surface_from_beam_face(ref_face)) - print("ref ID", joint.btlx_params_cross["reference_plane_id"]) if joint.ends[str(cross_part.key)] == "start": joint.btlx_params_cross["machining_limits"] = { "FaceLimitedStart": "no", @@ -53,7 +49,17 @@ def apply_processings(cls, joint, parts): "FaceLimitedFront": "no", "FaceLimitedBack": "no", } + + joint.btlx_params_cross["ReferencePlaneID"] = str(cross_part.reference_surface_from_beam_face(ref_plane)) cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, "L-Butt Joint")) + if joint.birdsmouth: + joint.calc_params_birdsmouth() + ref_face = main_part.beam.faces[joint.btlx_params_main["ReferencePlaneID"]] + joint.btlx_params_main["ReferencePlaneID"] = str(main_part.reference_surface_from_beam_face(ref_face)) + main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_main, "L-Butt Joint")) + else: + main_part.processings.append(BTLxJackCut.create_process(main_part, main_cut_plane, "L-Butt Joint")) + BTLx.register_joint(LButtJoint, LButtFactory) diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index 0b01466a33..5faf098577 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -3,9 +3,7 @@ from compas_timber.fabrication import BTLxJackCut from compas_timber.fabrication.btlx_processes.btlx_lap import BTLxLap from compas_timber.fabrication.btlx_processes.btlx_double_cut import BTLxDoubleCut -from compas.geometry import Frame, Plane, Transformation, Point, Vector, Line, intersection_plane_plane, intersection_plane_plane_plane, angle_vectors -from compas_timber.utils.compas_extra import intersection_line_plane -import math + class TButtFactory(object): """Factory class for creating T-Butt joints.""" @@ -13,141 +11,6 @@ class TButtFactory(object): def __init__(self): pass - - @staticmethod - def line_intersects_face(line, face, x_max, y_max): - """ - Check if a line intersects a face. - - Parameters: - ---------- - line (object): The line object. - face (object): The face object. - - Returns: - ---------- - bool: True if the line intersects the face, False otherwise. - - """ - point = intersection_line_plane(line, Plane.from_frame(face))[0] - point.transform(Transformation.from_frame_to_frame(face, Frame.worldXY())) - print(point) - if point.x >= 0 and point.x <= x_max: - if point.y >= 0 and point.y <= y_max: - print("found it!!!!") - return True - return False - - - - @staticmethod - def get_intersecting_face(intersect_line, main_part, cross_part): - print(main_part, cross_part, "intersecting_face") - print(cross_part.beam.faces, "cross_faces") - for i, face in enumerate(main_part.beam.faces[0:4]): - if i % 2 == 0: - if TButtFactory.line_intersects_face(intersect_line, face, cross_part.blank_length, cross_part.width): - return i, face - else: - if TButtFactory.line_intersects_face(intersect_line, face, cross_part.blank_length, cross_part.height): - return i, face - - - - @staticmethod - def calc_params_birdsmouth(joint, main_part, cross_part): - """ - Calculate the parameters for a birdsmouth joint. - - Parameters: - ---------- - joint (object): The joint object. - main_part (object): The main part object. - cross_part (object): The cross part object. - - Returns: - ---------- - dict: A dictionary containing the calculated parameters for the birdsmouth joint - - """ - face_dict = joint._beam_side_incidence(main_part.beam, cross_part.beam, ignore_ends=True) - sorted_keys = sorted(face_dict.keys(), key=face_dict.get) - - cross_vector = main_part.beam.centerline.direction.cross(cross_part.beam.centerline.direction) - - frame1, frame2 = joint.get_main_cutting_plane()[0], cross_part.beam.faces[sorted_keys[1]] - angle = angle_vectors(cross_vector, frame2.normal, deg=True) - if angle<1.0 or angle>179.0: - return False - - frame1 = Frame(frame1.point, frame1.xaxis, -frame1.yaxis) - print(frame1, cross_part.beam.faces[sorted_keys[0]]) - plane1, plane2 = Plane.from_frame(frame1), Plane.from_frame(frame2) - intersect_vec = Vector.from_start_end(*intersection_plane_plane(plane2, plane1)) - intersect_line = Line(*intersection_plane_plane(plane2, plane1)) - - ind, main_ref_frame = TButtFactory.get_intersecting_face(intersect_line, main_part, cross_part) - - print (ind, main_ref_frame) - - - - - angles_dict = {} - for i, face in enumerate(main_part.beam.faces[0:4]): - angles_dict[i] = (face.normal.angle(cross_part.beam.centerline.direction)) - ref_frame_id = min(angles_dict.keys(), key=angles_dict.get) - ref_frame = main_part.reference_surface_planes(ref_frame_id+1) - - print(angles_dict) - - print("ref_frame", ref_frame_id) - joint.test.append(ref_frame) - - - dot_frame1 = plane1.normal.dot(ref_frame.yaxis) - if dot_frame1 > 0: - plane1, plane2 = plane2, plane1 - - start_point = Point(*intersection_plane_plane_plane(plane1, plane2, Plane.from_frame(ref_frame))) - start_point.transform(Transformation.from_frame_to_frame(ref_frame, Frame.worldXY())) - StartX, StartY = start_point[0], start_point[1] - - intersect_vec1 = Vector.from_start_end(*intersection_plane_plane(plane1, Plane.from_frame(ref_frame))) - intersect_vec2 = Vector.from_start_end(*intersection_plane_plane(plane2, Plane.from_frame(ref_frame))) - - dot_2 = math.degrees(intersect_vec1.dot(ref_frame.yaxis)) - if dot_2 < 0: - intersect_vec1 = -intersect_vec1 - - dot_1 = math.degrees(intersect_vec2.dot(ref_frame.yaxis)) - if dot_1 < 0: - intersect_vec2 = -intersect_vec2 - - if joint.ends[str(main_part.key)] == "start": - reference_frame = ref_frame.xaxis - else: - reference_frame = -ref_frame.xaxis - - Angle1 = math.degrees(intersect_vec1.angle(reference_frame)) - Angle2 = math.degrees(intersect_vec2.angle(reference_frame)) - - Inclination1 = math.degrees(plane1.normal.angle(ref_frame.zaxis)) - Inclination2 = math.degrees(plane2.normal.angle(ref_frame.zaxis)) - - return { - "Orientation": joint.ends[str(main_part.key)], - "StartX": StartX, - "StartY": StartY, - "Angle1": Angle1, - "Inclination1": Inclination1, - "Angle2": Angle2, - "Inclination2": Inclination2, - "ReferencePlaneID": ref_frame_id - } - - - @classmethod def apply_processings(cls, joint, parts): """ @@ -178,11 +41,11 @@ def apply_processings(cls, joint, parts): else: main_part.processings.append(BTLxJackCut.create_process(main_part, cut_plane, "T-Butt Joint")) - joint.btlx_params_cross["reference_plane_id"] = cross_part.reference_surface_from_beam_face(ref_plane) + joint.btlx_params_cross["reference_plane_id"] = str(cross_part.reference_surface_from_beam_face(ref_plane)) if joint.mill_depth > 0: joint.btlx_params_cross["machining_limits"] = {"FaceLimitedFront": "no", "FaceLimitedBack": "no"} + joint.btlx_params_cross["ReferencePlaneID"] = str(cross_part.reference_surface_from_beam_face(ref_plane)) cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, "T-Butt Joint")) - BTLx.register_joint(TButtJoint, TButtFactory) From b0555f50e926a1eb2f06d4be1b7ee3fa995e8a07 Mon Sep 17 00:00:00 2001 From: obucklin Date: Fri, 3 May 2024 14:59:39 +0200 Subject: [PATCH 29/82] updated Assembly GH component with add_features() --- src/compas_timber/ghpython/components/CT_Assembly/code.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compas_timber/ghpython/components/CT_Assembly/code.py b/src/compas_timber/ghpython/components/CT_Assembly/code.py index d07a92742b..5ea8fd18c1 100644 --- a/src/compas_timber/ghpython/components/CT_Assembly/code.py +++ b/src/compas_timber/ghpython/components/CT_Assembly/code.py @@ -168,7 +168,8 @@ def RunScript(self, Beams, JointRules, Features, MaxDistance, CreateGeometry): debug_info.add_joint_error(bje) else: handled_beams.append(beam_pair_ids) - + for joint in Assembly.joints: + joint.add_features() if Features: features = [f for f in Features if f is not None] for f_def in features: From f2d527bd7435a57dfaa6fcad481d80a73cb51781 Mon Sep 17 00:00:00 2001 From: obucklin Date: Fri, 3 May 2024 17:06:37 +0200 Subject: [PATCH 30/82] added GEOMETRY --- src/compas_timber/connections/butt_joint.py | 28 ++++++++++++------- src/compas_timber/connections/joint.py | 1 - src/compas_timber/connections/l_butt.py | 18 ++++++++---- src/compas_timber/connections/t_butt.py | 14 ++++++++-- .../joint_factories/l_butt_factory.py | 4 +-- .../joint_factories/t_butt_factory.py | 3 +- 6 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 6a8d91dea3..906c1b0901 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -13,6 +13,8 @@ from compas.geometry import Transformation from compas.geometry import angle_vectors_signed from compas.geometry import angle_vectors +from compas.geometry import Brep +from compas.geometry import Scale from .joint import Joint import math @@ -123,8 +125,6 @@ def get_main_cutting_plane(self): def subtraction_volume(self): """Returns the volume to be subtracted from the cross beam.""" - print("main_beam extensions", self.main_beam._blank_extensions) - print("cross_beam extensions", self.cross_beam._blank_extensions) vertices = [] front_frame, back_frame = self.front_back_surface_main() top_frame, bottom_frame = self.get_main_cutting_plane() @@ -144,7 +144,6 @@ def subtraction_volume(self): min_pt, max_pt = points[0], points[-1] if i == 1: self.btlx_params_cross["start_x"] = abs(dots[0]) - print("start_x", self.btlx_params_cross["start_x"]) top_line = Line(*intersection_plane_plane(Plane.from_frame(side), Plane.from_frame(top_frame))) top_min = Point(*closest_point_on_line(min_pt, top_line)) top_max = Point(*closest_point_on_line(max_pt, top_line)) @@ -208,7 +207,7 @@ def calc_params_birdsmouth(self): face_dict = self._beam_side_incidence(self.main_beam, self.cross_beam, ignore_ends=True) face_keys = sorted([key for key in face_dict.keys()], key=face_dict.get) - frame1 = self.get_main_cutting_plane()[0] # offset pocket mill plane + frame1, og_frame = self.get_main_cutting_plane() # offset pocket mill plane frame2 = self.cross_beam.faces[face_keys[1]] plane1, plane2 = Plane(frame1.point, -frame1.zaxis), Plane.from_frame(frame2) @@ -217,11 +216,11 @@ def calc_params_birdsmouth(self): angles_dict = {} for i, face in enumerate(self.main_beam.faces[0:4]): angles_dict[i] = face.normal.angle(intersect_vec) - ref_frame_id = min(angles_dict.keys(), key=angles_dict.get) - ref_frame = self.main_beam.faces[ref_frame_id] + self.main_face_index = min(angles_dict.keys(), key=angles_dict.get) + ref_frame = self.main_beam.faces[self.main_face_index] ref_frame.point = self.main_beam.blank_frame.point - if ref_frame_id % 2 == 0: + if self.main_face_index % 2 == 0: ref_frame.point = ref_frame.point - ref_frame.yaxis * self.main_beam.height * 0.5 ref_frame.point = ref_frame.point + ref_frame.zaxis * self.main_beam.width * 0.5 else: @@ -229,8 +228,14 @@ def calc_params_birdsmouth(self): ref_frame.point = ref_frame.point + ref_frame.zaxis * self.main_beam.height * 0.5 start_point = Point(*intersection_plane_plane_plane(plane1, plane2, Plane.from_frame(ref_frame))) - start_point.transform(Transformation.from_frame_to_frame(ref_frame, Frame.worldXY())) - StartX, StartY = start_point[0], start_point[1] + coord_point = start_point.transformed(Transformation.from_frame_to_frame(ref_frame, Frame.worldXY())) + StartX, StartY = coord_point[0], coord_point[1] + + self.bm_sub_volume = Brep.from_box(self.cross_beam.blank) + self.bm_sub_volume.translate(Vector.from_start_end(og_frame.point, frame1.point)) + s = Scale.from_factors([10.0, 10.0, 10.0], Frame(start_point, ref_frame.xaxis, ref_frame.yaxis)) + self.bm_sub_volume.transform(s) + dot_frame1 = plane1.normal.dot(ref_frame.yaxis) if dot_frame1 > 0: @@ -263,5 +268,8 @@ def calc_params_birdsmouth(self): "Inclination1": Inclination1, "Angle2": Angle2, "Inclination2": Inclination2, - "ReferencePlaneID": ref_frame_id, + "ReferencePlaneID": self.main_face_index, } + + + diff --git a/src/compas_timber/connections/joint.py b/src/compas_timber/connections/joint.py index e49195c9d4..52d411d539 100644 --- a/src/compas_timber/connections/joint.py +++ b/src/compas_timber/connections/joint.py @@ -249,7 +249,6 @@ def _beam_side_incidence(beam_a, beam_b, ignore_ends=True): raise AssertionError("No intersection found") end, _ = beam_a.endpoint_closest_to_point(Point(*p1x)) - print(end) if end == "start": centerline_vec = beam_a.centerline.vector else: diff --git a/src/compas_timber/connections/l_butt.py b/src/compas_timber/connections/l_butt.py index 2fb2c1007c..b12fcae0a5 100644 --- a/src/compas_timber/connections/l_butt.py +++ b/src/compas_timber/connections/l_butt.py @@ -1,5 +1,6 @@ from compas_timber.parts import CutFeature from compas_timber.parts import MillVolume +from compas_timber.parts import BrepSubtraction from .joint import BeamJoinningError from .solver import JointTopology @@ -107,13 +108,11 @@ def add_extensions(self): self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.key) extension_plane_cross = self.get_face_most_towards_beam(self.cross_beam, self.main_beam, ignore_ends=True)[1] - print("EPC = ", extension_plane_cross) self.test.append(extension_plane_cross) start_cross, end_cross = self.cross_beam.extension_to_plane(extension_plane_cross) self.cross_beam.add_blank_extension( start_cross + extension_tolerance, end_cross + extension_tolerance, self.key ) - print("lbutt extensions", self.cross_beam._blank_extensions) def add_features(self): """Adds the required extension and trimming features to both beams. @@ -145,8 +144,17 @@ def add_features(self): self.cross_beam.add_features(f_cross) self.features.append(f_cross) - f_main = CutFeature(main_cutting_plane) if self.mill_depth: self.cross_beam.add_features(MillVolume(self.subtraction_volume())) - self.main_beam.add_features(f_main) - self.features.append(f_main) + self.features.append(MillVolume(self.subtraction_volume())) + + if self.birdsmouth: + self.calc_params_birdsmouth() + self.main_beam.add_features(BrepSubtraction(self.bm_sub_volume)) + self.test.append(self.bm_sub_volume) + self.features.append(BrepSubtraction(self.bm_sub_volume)) + else: + f_main = CutFeature(main_cutting_plane) + self.main_beam.add_features(f_main) + self.features.append(f_main) + diff --git a/src/compas_timber/connections/t_butt.py b/src/compas_timber/connections/t_butt.py index 83dfb8711c..a61a398afe 100644 --- a/src/compas_timber/connections/t_butt.py +++ b/src/compas_timber/connections/t_butt.py @@ -1,6 +1,7 @@ from compas_timber.connections.butt_joint import ButtJoint from compas_timber.parts import CutFeature +from compas_timber.parts import BrepSubtraction from compas_timber.parts import MillVolume from .joint import BeamJoinningError @@ -72,8 +73,15 @@ def add_features(self): except Exception as ex: raise BeamJoinningError(beams=self.beams, joint=self, debug_info=str(ex)) - trim_feature = CutFeature(cutting_plane) + self.features = [] + if self.mill_depth: self.cross_beam.add_features(MillVolume(self.subtraction_volume())) - self.main_beam.add_features(trim_feature) - self.features = [trim_feature] + self.features.append(MillVolume(self.subtraction_volume())) + if self.birdsmouth: + self.calc_params_birdsmouth() + self.main_beam.add_features(BrepSubtraction(self.bm_sub_volume)) + self.features.append(BrepSubtraction(self.bm_sub_volume)) + else: + self.main_beam.add_features(CutFeature(cutting_plane)) + self.features.append(cutting_plane) diff --git a/src/compas_timber/fabrication/joint_factories/l_butt_factory.py b/src/compas_timber/fabrication/joint_factories/l_butt_factory.py index 5b29e1b052..44563dc37f 100644 --- a/src/compas_timber/fabrication/joint_factories/l_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/l_butt_factory.py @@ -54,8 +54,8 @@ def apply_processings(cls, joint, parts): cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, "L-Butt Joint")) if joint.birdsmouth: - joint.calc_params_birdsmouth() - ref_face = main_part.beam.faces[joint.btlx_params_main["ReferencePlaneID"]] + print("Birdsmouth") + ref_face = main_part.beam.faces[joint.main_face_index] joint.btlx_params_main["ReferencePlaneID"] = str(main_part.reference_surface_from_beam_face(ref_face)) main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_main, "L-Butt Joint")) else: diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index 5faf098577..763bc5d93d 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -34,8 +34,7 @@ def apply_processings(cls, joint, parts): cut_plane, ref_plane = joint.get_main_cutting_plane() if joint.birdsmouth: - joint.calc_params_birdsmouth() - ref_face = main_part.beam.faces[joint.btlx_params_main["ReferencePlaneID"]] + ref_face = main_part.beam.faces[joint.main_face_index] joint.btlx_params_main["ReferencePlaneID"] = str(main_part.reference_surface_from_beam_face(ref_face)) main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_main, "T-Butt Joint")) else: From 271e25391010f5f4dae2224a353bb88d6b619855 Mon Sep 17 00:00:00 2001 From: obucklin Date: Fri, 3 May 2024 17:17:34 +0200 Subject: [PATCH 31/82] fixed beam extension on birdsmouth --- src/compas_timber/connections/l_butt.py | 11 ++++------- src/compas_timber/connections/t_butt.py | 6 ++---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/compas_timber/connections/l_butt.py b/src/compas_timber/connections/l_butt.py index b12fcae0a5..a329f18b96 100644 --- a/src/compas_timber/connections/l_butt.py +++ b/src/compas_timber/connections/l_butt.py @@ -99,20 +99,17 @@ def add_extensions(self): assert self.main_beam and self.cross_beam extension_tolerance = 0.01 # TODO: this should be proportional to the unit used if self.birdsmouth: - extension_plane_main = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True)[ - 1 - ] - else: extension_plane_main = self.get_face_most_towards_beam(self.main_beam, self.cross_beam, ignore_ends=True)[1] + else: + extension_plane_main = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True)[1] start_main, end_main = self.main_beam.extension_to_plane(extension_plane_main) + print("start_main, end_main", start_main, end_main) self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.key) extension_plane_cross = self.get_face_most_towards_beam(self.cross_beam, self.main_beam, ignore_ends=True)[1] self.test.append(extension_plane_cross) start_cross, end_cross = self.cross_beam.extension_to_plane(extension_plane_cross) - self.cross_beam.add_blank_extension( - start_cross + extension_tolerance, end_cross + extension_tolerance, self.key - ) + self.cross_beam.add_blank_extension(start_cross + extension_tolerance, end_cross + extension_tolerance, self.key) 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 a61a398afe..bfbe450339 100644 --- a/src/compas_timber/connections/t_butt.py +++ b/src/compas_timber/connections/t_butt.py @@ -46,11 +46,9 @@ def add_extensions(self): assert self.main_beam and self.cross_beam extension_tolerance = 0.01 # TODO: this should be proportional to the unit used if self.birdsmouth: - extension_plane_main = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True)[ - 1 - ] - else: extension_plane_main = self.get_face_most_towards_beam(self.main_beam, self.cross_beam, ignore_ends=True)[1] + else: + extension_plane_main = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True)[1] start_main, end_main = self.main_beam.extension_to_plane(extension_plane_main) self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.key) From 840c75dde512c8cb948ca8171f3b923a19cee5a4 Mon Sep 17 00:00:00 2001 From: obucklin Date: Fri, 3 May 2024 17:23:38 +0200 Subject: [PATCH 32/82] cleaned up old debug prints and tests --- src/compas_timber/connections/l_butt.py | 3 --- .../fabrication/joint_factories/french_ridge_factory.py | 4 ---- .../fabrication/joint_factories/l_butt_factory.py | 1 - .../ghpython/components/CT_Beam_fromCurve/code.py | 1 - .../ghpython/components/CT_Joint_Rule_Category/code.py | 1 - 5 files changed, 10 deletions(-) diff --git a/src/compas_timber/connections/l_butt.py b/src/compas_timber/connections/l_butt.py index a329f18b96..6cca09c276 100644 --- a/src/compas_timber/connections/l_butt.py +++ b/src/compas_timber/connections/l_butt.py @@ -103,11 +103,9 @@ def add_extensions(self): else: extension_plane_main = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True)[1] start_main, end_main = self.main_beam.extension_to_plane(extension_plane_main) - print("start_main, end_main", start_main, end_main) self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.key) extension_plane_cross = self.get_face_most_towards_beam(self.cross_beam, self.main_beam, ignore_ends=True)[1] - self.test.append(extension_plane_cross) start_cross, end_cross = self.cross_beam.extension_to_plane(extension_plane_cross) self.cross_beam.add_blank_extension(start_cross + extension_tolerance, end_cross + extension_tolerance, self.key) @@ -148,7 +146,6 @@ def add_features(self): if self.birdsmouth: self.calc_params_birdsmouth() self.main_beam.add_features(BrepSubtraction(self.bm_sub_volume)) - self.test.append(self.bm_sub_volume) self.features.append(BrepSubtraction(self.bm_sub_volume)) else: f_main = CutFeature(main_cutting_plane) diff --git a/src/compas_timber/fabrication/joint_factories/french_ridge_factory.py b/src/compas_timber/fabrication/joint_factories/french_ridge_factory.py index 41e156810e..f4ca677c31 100644 --- a/src/compas_timber/fabrication/joint_factories/french_ridge_factory.py +++ b/src/compas_timber/fabrication/joint_factories/french_ridge_factory.py @@ -31,14 +31,10 @@ def apply_processings(cls, joint, parts): top_key = joint.beams[0].key top_part = parts[str(top_key)] - # top_part._test.append(top_part.reference_surface_planes(joint.reference_face_indices[str(top_part.beam.key)])) top_part.processings.append(BTLxFrenchRidgeLap.create_process(top_part, joint, True)) bottom_key = joint.beams[1].key bottom_part = parts[str(bottom_key)] - # bottom_part._test.append( - # bottom_part.reference_surface_planes(joint.reference_face_indices[str(bottom_part.beam.key)]) - # ) bottom_part.processings.append(BTLxFrenchRidgeLap.create_process(bottom_part, joint, False)) diff --git a/src/compas_timber/fabrication/joint_factories/l_butt_factory.py b/src/compas_timber/fabrication/joint_factories/l_butt_factory.py index 44563dc37f..860d3cfbf8 100644 --- a/src/compas_timber/fabrication/joint_factories/l_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/l_butt_factory.py @@ -54,7 +54,6 @@ def apply_processings(cls, joint, parts): cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, "L-Butt Joint")) if joint.birdsmouth: - print("Birdsmouth") ref_face = main_part.beam.faces[joint.main_face_index] joint.btlx_params_main["ReferencePlaneID"] = str(main_part.reference_surface_from_beam_face(ref_face)) main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_main, "L-Butt Joint")) diff --git a/src/compas_timber/ghpython/components/CT_Beam_fromCurve/code.py b/src/compas_timber/ghpython/components/CT_Beam_fromCurve/code.py index 0852fe09fd..49dd919d17 100644 --- a/src/compas_timber/ghpython/components/CT_Beam_fromCurve/code.py +++ b/src/compas_timber/ghpython/components/CT_Beam_fromCurve/code.py @@ -73,7 +73,6 @@ def RunScript(self, centerline, z_vector, width, height, category, updateRefObj) beam = CTBeam.from_centerline(centerline=line, width=w, height=h, z_vector=z) beam.attributes["rhino_guid"] = str(guid) if guid else None beam.attributes["category"] = c - print(guid) if updateRefObj and guid: update_rhobj_attributes_name(guid, "width", str(w)) update_rhobj_attributes_name(guid, "height", str(h)) diff --git a/src/compas_timber/ghpython/components/CT_Joint_Rule_Category/code.py b/src/compas_timber/ghpython/components/CT_Joint_Rule_Category/code.py index 7e5a337d0a..f9f537c236 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Rule_Category/code.py +++ b/src/compas_timber/ghpython/components/CT_Joint_Rule_Category/code.py @@ -44,7 +44,6 @@ def RunScript(self, *args): for i, val in enumerate(args[2:]): if val is not None: kwargs[self.arg_names()[i + 2]] = val - print(kwargs) if not cat_a: self.AddRuntimeMessage( Warning, "Input parameter {} failed to collect data.".format(self.arg_names()[0]) From ff6f54a2294df6eae0d77fe42c1d9d85d87e9905 Mon Sep 17 00:00:00 2001 From: paulocinco Date: Fri, 3 May 2024 17:53:06 +0200 Subject: [PATCH 33/82] drilling btlx WIP --- src/compas_timber/connections/butt_joint.py | 42 ++++++++++++++----- src/compas_timber/connections/t_butt.py | 7 +++- .../btlx_processes/btlx_drilling.py | 2 +- .../joint_factories/t_butt_factory.py | 5 +++ 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 4d53685653..735c5598d7 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -5,6 +5,7 @@ from compas.geometry import closest_point_on_line from compas.geometry import distance_line_line from compas.geometry import intersection_plane_plane +from compas.geometry import intersection_line_plane from compas.geometry import Plane from compas.geometry import Line from compas.geometry import Polyhedron @@ -14,7 +15,7 @@ from compas.geometry import angle_vectors_signed from compas.geometry import angle_vectors from .joint import Joint - +import math class ButtJoint(Joint): """Abstract Lap type joint with functions common to L-Butt and T-Butt Joints. @@ -45,7 +46,7 @@ class ButtJoint(Joint): """ - def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, drill_diameter=0, drill_depth=0, birdsmouth=False, **kwargs): + def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, drill_diameter=0, birdsmouth=False, **kwargs): super(ButtJoint, self).__init__(**kwargs) self.main_beam = main_beam self.cross_beam = cross_beam @@ -53,7 +54,6 @@ def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, drill_diameter self.cross_beam_key = cross_beam.key if cross_beam else None self.mill_depth = mill_depth self.drill_diameter = drill_diameter - self.drill_depth = drill_depth self.birdsmouth = birdsmouth self.btlx_params_main = {} self.btlx_params_cross = {} @@ -369,12 +369,26 @@ def calc_params_drilling(self): dict: A dictionary containing the calculated parameters for the drilling joint """ - if self.drill_depth > 0: - DepthLimited = "yes" + ref_frame_id, ref_frame = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True) + ref_plane = Plane.from_frame(ref_frame) + point_xyz = (intersection_line_plane(self.main_beam.centerline, ref_plane)) + start_point = Point(*point_xyz) + ref_point = start_point.transformed(Transformation.from_frame_to_frame(ref_frame, Frame.worldXY())) + StartX, StartY = ref_point[0], ref_point[1] + + param_point_on_line = self.main_beam.centerline.closest_point(start_point, True)[1] + if param_point_on_line > 0.5: + line_point = self.main_beam.centerline.end else: - DepthLimited = "no" - + line_point = self.main_beam.centerline.start + projected_point = ref_plane.projected_point(line_point) + center_line_vec = Vector.from_start_end(start_point, line_point) + projected_vec = Vector.from_start_end(start_point, projected_point) + Angle = ref_frame.xaxis.angle(projected_vec, True) + print "Angle = ", Angle + Inclination = projected_vec.angle(center_line_vec, True) + print "Inclination = ", Inclination self.btlx_drilling_params_cross = { @@ -383,7 +397,15 @@ def calc_params_drilling(self): "StartY": StartY, "Angle": Angle, "Inclination": Inclination, - "DepthLimited": DepthLimited, - "Depth": self.drill_depth, - "Diameter": self.drill_diameter + "Diameter": self.drill_diameter, + "DepthLimited": False, + "Depth": 0.0 + } + + # Rhino geometry visualization + line = Line(start_point, line_point) + line.start.translate(-line.vector) + normal_centerline_angle = 180-math.degrees(ref_frame.zaxis.angle(self.main_beam.centerline.direction)) + length = self.cross_beam.width/(math.cos(math.radians(normal_centerline_angle))) + return line, self.drill_diameter, length*3 diff --git a/src/compas_timber/connections/t_butt.py b/src/compas_timber/connections/t_butt.py index 8d0159d077..1300d20d07 100644 --- a/src/compas_timber/connections/t_butt.py +++ b/src/compas_timber/connections/t_butt.py @@ -2,6 +2,7 @@ from compas_timber.parts import CutFeature from compas_timber.parts import MillVolume +from compas_timber.parts import DrillFeature from .joint import BeamJoinningError from .solver import JointTopology @@ -33,8 +34,8 @@ class TButtJoint(ButtJoint): SUPPORTED_TOPOLOGY = JointTopology.TOPO_T - def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, drill_diameter=0, drill_depth=0, birdsmouth=False, **kwargs): - super(TButtJoint, self).__init__(main_beam, cross_beam, mill_depth, drill_diameter, drill_depth, birdsmouth, **kwargs) + def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, drill_diameter=0, birdsmouth=False, **kwargs): + super(TButtJoint, self).__init__(main_beam, cross_beam, mill_depth, drill_diameter, birdsmouth, **kwargs) def add_features(self): """Adds the trimming plane to the main beam (no features for the cross beam). @@ -61,5 +62,7 @@ def add_features(self): trim_feature = CutFeature(cutting_plane) if self.mill_depth: self.cross_beam.add_features(MillVolume(self.subtraction_volume())) + if self.drill_diameter: + self.cross_beam.add_features(DrillFeature(*self.calc_params_drilling())) self.main_beam.add_features(trim_feature) self.features = [trim_feature] diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_drilling.py b/src/compas_timber/fabrication/btlx_processes/btlx_drilling.py index 5708948d6d..b5dfcf67c8 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_drilling.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_drilling.py @@ -75,7 +75,7 @@ def process_params(self): return None @classmethod - def create_process(cls, param_dict, joint_name=None, **kwargs): # joint_name replace by "feature_name"? + def create_process(cls, param_dict, joint_name=None, **kwargs): """Creates a drilling process from a dictionary of parameters.""" drilling = BTLxDrilling(param_dict, joint_name, **kwargs) return BTLxProcess(BTLxDrilling.PROCESS_TYPE, drilling.header_attributes, drilling.process_params) diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index 288387ad23..7a88a42e5f 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -1,6 +1,7 @@ from compas_timber.connections import TButtJoint from compas_timber.fabrication import BTLx from compas_timber.fabrication import BTLxJackCut +from compas_timber.fabrication.btlx_processes.btlx_drilling import BTLxDrilling from compas_timber.fabrication.btlx_processes.btlx_lap import BTLxLap from compas_timber.fabrication.btlx_processes.btlx_double_cut import BTLxDoubleCut @@ -46,5 +47,9 @@ def apply_processings(cls, joint, parts): joint.btlx_params_cross["machining_limits"] = {"FaceLimitedFront": "no", "FaceLimitedBack": "no"} cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, "T-Butt Joint")) + if joint.drill_diameter > 0: + joint.btlx_drilling_params_cross["reference_plane_id"] = cross_part.reference_surface_from_beam_face(ref_plane) + cross_part.processings.append(BTLxDrilling.create_process(joint.btlx_drilling_params_cross, "T-Butt Joint")) + BTLx.register_joint(TButtJoint, TButtFactory) From e8a338b2aadf861ed1eb330abeee91b067191278 Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 3 May 2024 17:53:29 +0200 Subject: [PATCH 34/82] implement engraving processing on btlx.part --- src/compas_timber/fabrication/btlx.py | 39 ++++++++++++++++++- .../fabrication/btlx_processes/btlx_text.py | 37 +++++++++++------- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index e194c4a767..8969e80f27 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -10,7 +10,7 @@ from compas.geometry import Frame from compas.geometry import angle_vectors from compas.geometry import Transformation - +from compas_timber.fabrication.btlx_processes.btlx_text import BTLxText class BTLx(object): """Class representing a BTLx object. @@ -92,6 +92,13 @@ def process_assembly(self): factory_type = self.REGISTERED_JOINTS.get(str(type(joint))) factory_type.apply_processings(joint, self.parts) + for part in self.parts.values(): + if part.processings: + ref_plane_id = part.processing[0].header_attributes.get("ReferencePlaneID", 1) + params_dict = part.get_text_engraving_params() + params_dict["ReferencePlaneID"] = ref_plane_id + part.processings.append(BTLxText.create_process(params_dict)) + @classmethod def register_joint(cls, joint_type, joint_factory): """Registers a joint type and its corresponding factory. @@ -148,6 +155,8 @@ class BTLxPart(object): The frame of the blank. blank_length : float The blank length of the beam. + intersections : list + A list of the intersection parameters on the beam processings : list A list of the processings applied to the beam. et_element : :class:`~xml.etree.ElementTree.Element` @@ -167,6 +176,7 @@ def __init__(self, beam): beam.frame.yaxis, ) # I used long_edge[2] because it is in Y and Z negative. Using that as reference puts the beam entirely in positive coordinates. self.blank_length = beam.blank_length + self.intersections = beam.intersections self._reference_surfaces = [] self.processings = [] self._et_element = None @@ -230,6 +240,33 @@ def reference_surface_planes(self, index): } return self._reference_surfaces[str(index)] + def get_engraving_position(self): + """Finds the optimal parameter on the line for the text engraving process.""" + all_points = sorted([0] + self.intersections + [1]) + + max_length = 0 + optimal_midpoint = None + for i in range(len(all_points) - 1): + seg_length = all_points[i+1] - all_points[i] + if seg_length > max_length: + max_length = seg_length + optimal_midpoint = (all_points[i] + all_points[i+1]) / 2 + return optimal_midpoint + + def get_text_engraving_params(self): + """Returns the text engraving parameters for the BTLx part.""" + return { + "ReferencePlaneID": 1, #default face + "StartX": self.get_engraving_position(), + "StartY": self.width/2, #always set it to the middle of the beam + "Angle": 0, + "AlignmentVertical": "center", + "AlignmentHorizontal": "center", + "AlignmentMultiline": "center", + "TextHeight": 20, + "Text": self.beam.attributes["module_number"] + "_" + self.beam.attributes["assembly_number"] + "_" + self.beam.attributes["beam_number"] + } + @property def attr(self): return { diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_text.py b/src/compas_timber/fabrication/btlx_processes/btlx_text.py index 62eb698c85..19071d0c1e 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_text.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_text.py @@ -26,10 +26,13 @@ def __init__(self, param_dict, joint_name=None, **kwargs): # joint_name replace self.start_x = param_dict["StartX"] self.start_y = param_dict["StartY"] self.angle = param_dict["Angle"] - self.inclination = param_dict["Inclination"] - self.depth_limited = param_dict["DepthLimited"] - self.depth = param_dict["Depth"] - self.diameter = param_dict["Diameter"] + self.alignment_vertical = param_dict["AlignmentVertical"] + self.alignment_horizontal = param_dict["AlignmentHorizontal"] + self.alignment_multiline = param_dict["AlignmentMultiline"] + self.stacked_marking = param_dict["StackedMarking"] + self.text_height_auto = param_dict["TextHeightAuto"] + self.text_height = param_dict["TextHeight"] + self.text = param_dict["Text"] for key, value in param_dict.items(): setattr(self, key, value) @@ -37,10 +40,10 @@ def __init__(self, param_dict, joint_name=None, **kwargs): # joint_name replace for key, value in kwargs.items(): setattr(self, key, value) - if joint_name: # to delete since no joint? + if joint_name: self.name = joint_name else: - self.name = "lap" # what instead? + self.name = "text_engraving" @property def header_attributes(self): @@ -58,16 +61,20 @@ def process_params(self): """This property is required for all process types. It returns a dict with the geometric parameters to fabricate the joint.""" if self.apply_process: - """the following attributes are specific to Lap""" + """the following attributes are specific to a text engraving""" od = OrderedDict( [ ("StartX", "{:.{prec}f}".format(self.start_x, prec=BTLx.POINT_PRECISION)), ("StartY", "{:.{prec}f}".format(self.start_y, prec=BTLx.POINT_PRECISION)), ("Angle", "{:.{prec}f}".format(self.angle, prec=BTLx.ANGLE_PRECISION)), - ("Inclination", "{:.{prec}f}".format(self.inclination, prec=BTLx.ANGLE_PRECISION)), - ("DepthLimited", str(self.depth_limited)), - ("Depth", "{:.{prec}f}".format(self.depth, prec=BTLx.POINT_PRECISION)), - ("Diameter", "{:.{prec}f}".format(self.diameter, prec=BTLx.POINT_PRECISION)), + ("AlignmentVertical", str(self.alignment_vertical)), + ("AlignmentHorizontal", str(self.alignment_horizontal)), + ("AlignmentMultiline", str(self.alignment_multiline)), + ("StackedMarking", bool(self.stacked_marking)), + ("TextHeightAuto", bool(self.text_height_auto)), + ("TextHeight", "{:.{prec}f}".format(self.text_height, prec=BTLx.POINT_PRECISION)), + ("Text", str(self.text)), + ] ) return od @@ -75,7 +82,7 @@ def process_params(self): return None @classmethod - def create_process(cls, param_dict, joint_name=None, **kwargs): # joint_name replace by "feature_name"? - """Creates a lap process from a dictionary of parameters.""" - lap = BTLxDrilling(param_dict, joint_name, **kwargs) ###change lap??? - return BTLxProcess(BTLxDrilling.PROCESS_TYPE, lap.header_attributes, lap.process_params) + def create_process(cls, param_dict, joint_name=None, **kwargs): + """Creates a text process from a dictionary of parameters.""" + text = BTLxText(param_dict, joint_name, **kwargs) + return BTLxProcess(BTLxText.PROCESS_TYPE, text.header_attributes, text.process_params) From 0d49fa78a65cd8a727d6a869db7b3377b4b6194c Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 3 May 2024 17:53:36 +0200 Subject: [PATCH 35/82] store intersections to each beam when calling the pairing solver --- src/compas_timber/connections/solver.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/compas_timber/connections/solver.py b/src/compas_timber/connections/solver.py index bb15130fcc..919891bc4d 100644 --- a/src/compas_timber/connections/solver.py +++ b/src/compas_timber/connections/solver.py @@ -10,6 +10,7 @@ from compas.geometry import dot_vectors from compas.geometry import scale_vector from compas.geometry import subtract_vectors +from compas.geometry import intersection_line_line from compas.plugins import pluggable @@ -89,7 +90,7 @@ class ConnectionSolver(object): @classmethod def find_intersecting_pairs(cls, beams, rtree=False, max_distance=None): - """Finds pairs of intersecting beams in the given list of beams. + """Finds pairs of intersecting beams in the given list of beams and stores the intersection parameters for each specific beam. Parameters ---------- @@ -107,7 +108,20 @@ def find_intersecting_pairs(cls, beams, rtree=False, max_distance=None): List containing sets or neightboring pairs beams. """ - return find_neighboring_beams(beams, inflate_by=max_distance) if rtree else itertools.combinations(beams, 2) + + pair_indexes = find_neighboring_beams(beams, inflate_by=max_distance) if rtree else itertools.combinations(beams, 2) + for i, beam in enumerate(beams): + beam[i].attributes["intersecitons"]=[] + for pair in pair_indexes: + if i in pair: + a = pair[0] + b = pair[1] + intersection_points = intersection_line_line(beam[a].centerline, beam[b].centerline)[0] + intersection_param = beam[i].centerline.closest_point(intersection_points[0], return_parameter=True) + beam[i].attributes["intersecitons"].append(intersection_param) + + return pair_indexes + def find_topology(self, beam_a, beam_b, tol=TOLERANCE, max_distance=None): """If `beam_a` and `beam_b` intersect within the given `max_distance`, return the topology type of the intersection. From 0b3eda9752015914c6c63d1a347fbdbdc6abc88f Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 3 May 2024 18:01:35 +0200 Subject: [PATCH 36/82] ID attributes for beam and btlx.part --- src/compas_timber/fabrication/btlx.py | 2 +- src/compas_timber/parts/beam.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index 8969e80f27..7d54167d4f 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -264,7 +264,7 @@ def get_text_engraving_params(self): "AlignmentHorizontal": "center", "AlignmentMultiline": "center", "TextHeight": 20, - "Text": self.beam.attributes["module_number"] + "_" + self.beam.attributes["assembly_number"] + "_" + self.beam.attributes["beam_number"] + "Text": self.beam.attributes["airModule_no"] + "_" + self.beam.attributes["assemblyModule_no"] + "_" + self.beam.attributes["beam_no"] } @property diff --git a/src/compas_timber/parts/beam.py b/src/compas_timber/parts/beam.py index 1440767c71..6bcd8850dd 100644 --- a/src/compas_timber/parts/beam.py +++ b/src/compas_timber/parts/beam.py @@ -79,7 +79,12 @@ class Beam(Part): A list containing the 4 lines along the long axis of this beam. midpoint : :class:`~compas.geometry.Point` The point at the middle of the centerline of this beam. - + airModule_no : string + The air module number the assembly module of the beam is part of. + assemblyModule_no : string + The assembly module number the beam is part of. + beam_no : string + The beam number. (irrelevant of the assembly sequence) """ def __init__(self, frame, length, width, height, **kwargs): @@ -89,6 +94,10 @@ def __init__(self, frame, length, width, height, **kwargs): self.length = length self.features = [] self._blank_extensions = {} + self.intersections = [] + self.airModule_no = [] ### TODO names to be defined + self.assemblyModule_no = [] ### TODO names to be defined + self.beam_no = [] ### TODO names to be defined @property def __data__(self): From 6f6f860867bf455bfb91e5c663f141eca42c166b Mon Sep 17 00:00:00 2001 From: obucklin Date: Fri, 3 May 2024 18:02:48 +0200 Subject: [PATCH 37/82] added cutoff so birdsmouth doesnt form when beam is perpendicular to face --- src/compas_timber/connections/butt_joint.py | 19 ++++++++++++++++++- src/compas_timber/connections/l_butt.py | 12 ++++++++---- src/compas_timber/connections/t_butt.py | 12 ++++++++---- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 906c1b0901..a07864197a 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -13,6 +13,7 @@ from compas.geometry import Transformation from compas.geometry import angle_vectors_signed from compas.geometry import angle_vectors +from compas.geometry import cross_vectors from compas.geometry import Brep from compas.geometry import Scale from .joint import Joint @@ -201,7 +202,7 @@ def calc_params_birdsmouth(self): Returns: ---------- - dict: A dictionary containing the calculated parameters for the birdsmouth joint + bool: True if the joint creation is successful, False otherwise. """ face_dict = self._beam_side_incidence(self.main_beam, self.cross_beam, ignore_ends=True) @@ -210,6 +211,8 @@ def calc_params_birdsmouth(self): frame1, og_frame = self.get_main_cutting_plane() # offset pocket mill plane frame2 = self.cross_beam.faces[face_keys[1]] + self.test.append(og_frame) + plane1, plane2 = Plane(frame1.point, -frame1.zaxis), Plane.from_frame(frame2) intersect_vec = Vector.from_start_end(*intersection_plane_plane(plane2, plane1)) @@ -219,6 +222,10 @@ def calc_params_birdsmouth(self): self.main_face_index = min(angles_dict.keys(), key=angles_dict.get) ref_frame = self.main_beam.faces[self.main_face_index] + if angle_vectors(og_frame.zaxis, self.main_beam.centerline.direction, deg = True) < 1: + self.birdsmouth = False + return False + ref_frame.point = self.main_beam.blank_frame.point if self.main_face_index % 2 == 0: ref_frame.point = ref_frame.point - ref_frame.yaxis * self.main_beam.height * 0.5 @@ -227,6 +234,15 @@ def calc_params_birdsmouth(self): ref_frame.point = ref_frame.point - ref_frame.yaxis * self.main_beam.width * 0.5 ref_frame.point = ref_frame.point + ref_frame.zaxis * self.main_beam.height * 0.5 + + cross_ref_main = cross_vectors(og_frame.zaxis, self.main_beam.centerline.direction) + self.test.append(Line(og_frame.point, og_frame.point + cross_ref_main * 100)) + angle = angle_vectors(cross_ref_main, og_frame.yaxis, deg=True) + if angle < 1.0 or angle > 179.0: + print("angle 2", angle) + self.birdsmouth = False + return False + start_point = Point(*intersection_plane_plane_plane(plane1, plane2, Plane.from_frame(ref_frame))) coord_point = start_point.transformed(Transformation.from_frame_to_frame(ref_frame, Frame.worldXY())) StartX, StartY = coord_point[0], coord_point[1] @@ -271,5 +287,6 @@ def calc_params_birdsmouth(self): "ReferencePlaneID": self.main_face_index, } + return True diff --git a/src/compas_timber/connections/l_butt.py b/src/compas_timber/connections/l_butt.py index 6cca09c276..35b515322d 100644 --- a/src/compas_timber/connections/l_butt.py +++ b/src/compas_timber/connections/l_butt.py @@ -143,11 +143,15 @@ def add_features(self): self.cross_beam.add_features(MillVolume(self.subtraction_volume())) self.features.append(MillVolume(self.subtraction_volume())) + do_jack = False if self.birdsmouth: - self.calc_params_birdsmouth() - self.main_beam.add_features(BrepSubtraction(self.bm_sub_volume)) - self.features.append(BrepSubtraction(self.bm_sub_volume)) - else: + if self.calc_params_birdsmouth(): + self.main_beam.add_features(BrepSubtraction(self.bm_sub_volume)) + self.features.append(BrepSubtraction(self.bm_sub_volume)) + + else: + do_jack = True + if do_jack: f_main = CutFeature(main_cutting_plane) self.main_beam.add_features(f_main) self.features.append(f_main) diff --git a/src/compas_timber/connections/t_butt.py b/src/compas_timber/connections/t_butt.py index bfbe450339..92a3f1357e 100644 --- a/src/compas_timber/connections/t_butt.py +++ b/src/compas_timber/connections/t_butt.py @@ -76,10 +76,14 @@ def add_features(self): if self.mill_depth: self.cross_beam.add_features(MillVolume(self.subtraction_volume())) self.features.append(MillVolume(self.subtraction_volume())) + do_jack = False if self.birdsmouth: - self.calc_params_birdsmouth() - self.main_beam.add_features(BrepSubtraction(self.bm_sub_volume)) - self.features.append(BrepSubtraction(self.bm_sub_volume)) - else: + if self.calc_params_birdsmouth(): + self.main_beam.add_features(BrepSubtraction(self.bm_sub_volume)) + self.features.append(BrepSubtraction(self.bm_sub_volume)) + + else: + do_jack = True + if do_jack: self.main_beam.add_features(CutFeature(cutting_plane)) self.features.append(cutting_plane) From d9cf6dd17b920ea6703ae50d45091fe0381e6c11 Mon Sep 17 00:00:00 2001 From: obucklin Date: Fri, 3 May 2024 18:04:19 +0200 Subject: [PATCH 38/82] removed debug print funciton --- src/compas_timber/connections/butt_joint.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index a07864197a..cb5c959e46 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -239,7 +239,6 @@ def calc_params_birdsmouth(self): self.test.append(Line(og_frame.point, og_frame.point + cross_ref_main * 100)) angle = angle_vectors(cross_ref_main, og_frame.yaxis, deg=True) if angle < 1.0 or angle > 179.0: - print("angle 2", angle) self.birdsmouth = False return False From 0274a6f48954ecdb52d018d2374c762b97d6a54d Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 3 May 2024 18:38:02 +0200 Subject: [PATCH 39/82] update __init__ module with new implemented processes --- src/compas_timber/fabrication/__init__.py | 6 ++++++ src/compas_timber/fabrication/btlx.py | 13 +++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/compas_timber/fabrication/__init__.py b/src/compas_timber/fabrication/__init__.py index 0f680f849c..e5a0938e30 100644 --- a/src/compas_timber/fabrication/__init__.py +++ b/src/compas_timber/fabrication/__init__.py @@ -3,6 +3,9 @@ from .btlx_processes.btlx_french_ridge_lap import BTLxFrenchRidgeLap from .btlx_processes.btlx_jack_cut import BTLxJackCut from .btlx_processes.btlx_lap import BTLxLap +from .btlx_processes.btlx_text import BTLxText +from .btlx_processes.btlx_double_cut import BTLxDoubleCut +from .btlx_processes.btlx_drilling import BTLxDrilling from .joint_factories.french_ridge_factory import FrenchRidgeFactory from .joint_factories.l_butt_factory import LButtFactory from .joint_factories.l_miter_factory import LMiterFactory @@ -13,6 +16,9 @@ "BTLxProcess", "BTLxJackCut", "BTLxLap", + "BTLxText", + "BTLxDoubleCut", + "BTLxDrilling", "BTLxFrenchRidgeLap", "LButtFactory", "TButtFactory", diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index 7d54167d4f..33dc0af725 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -10,7 +10,7 @@ from compas.geometry import Frame from compas.geometry import angle_vectors from compas.geometry import Transformation -from compas_timber.fabrication.btlx_processes.btlx_text import BTLxText +from compas_timber.fabrication import BTLxText class BTLx(object): """Class representing a BTLx object. @@ -93,11 +93,12 @@ def process_assembly(self): factory_type.apply_processings(joint, self.parts) for part in self.parts.values(): - if part.processings: - ref_plane_id = part.processing[0].header_attributes.get("ReferencePlaneID", 1) - params_dict = part.get_text_engraving_params() - params_dict["ReferencePlaneID"] = ref_plane_id - part.processings.append(BTLxText.create_process(params_dict)) + if self.beam.engrave: + if part.processings: + ref_plane_id = part.processing[0].header_attributes.get("ReferencePlaneID", 1) + params_dict = part.get_text_engraving_params() + params_dict["ReferencePlaneID"] = ref_plane_id + part.processings.append(BTLxText.create_process(params_dict)) @classmethod def register_joint(cls, joint_type, joint_factory): From 48766b7e3b4bca32ba0c3e39da4fdfdafefc0e1e Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 3 May 2024 18:48:33 +0200 Subject: [PATCH 40/82] correct names --- src/compas_timber/connections/solver.py | 6 +++--- src/compas_timber/parts/beam.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/compas_timber/connections/solver.py b/src/compas_timber/connections/solver.py index 919891bc4d..299cae45ae 100644 --- a/src/compas_timber/connections/solver.py +++ b/src/compas_timber/connections/solver.py @@ -111,14 +111,14 @@ def find_intersecting_pairs(cls, beams, rtree=False, max_distance=None): pair_indexes = find_neighboring_beams(beams, inflate_by=max_distance) if rtree else itertools.combinations(beams, 2) for i, beam in enumerate(beams): - beam[i].attributes["intersecitons"]=[] + beam.attributes["intersecitons"]=[] for pair in pair_indexes: if i in pair: a = pair[0] b = pair[1] intersection_points = intersection_line_line(beam[a].centerline, beam[b].centerline)[0] - intersection_param = beam[i].centerline.closest_point(intersection_points[0], return_parameter=True) - beam[i].attributes["intersecitons"].append(intersection_param) + intersection_param = beam.centerline.closest_point(intersection_points[0], return_parameter=True) + beam.attributes["intersecitons"].append(intersection_param) return pair_indexes diff --git a/src/compas_timber/parts/beam.py b/src/compas_timber/parts/beam.py index 6bcd8850dd..5a674c0365 100644 --- a/src/compas_timber/parts/beam.py +++ b/src/compas_timber/parts/beam.py @@ -95,9 +95,9 @@ def __init__(self, frame, length, width, height, **kwargs): self.features = [] self._blank_extensions = {} self.intersections = [] - self.airModule_no = [] ### TODO names to be defined - self.assemblyModule_no = [] ### TODO names to be defined - self.beam_no = [] ### TODO names to be defined + self.attributes["airModule_no"] = [] ### TODO names to be defined + self.attributes["assemblyModule_no"] = [] ### TODO names to be defined + self.attributes["beam_no"] = [] ### TODO names to be defined @property def __data__(self): From e2565065a3c7cdfa6db319630461d210e1845309 Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 3 May 2024 19:19:46 +0200 Subject: [PATCH 41/82] hash out changes --- src/compas_timber/fabrication/btlx.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index 33dc0af725..c172be8617 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -10,7 +10,7 @@ from compas.geometry import Frame from compas.geometry import angle_vectors from compas.geometry import Transformation -from compas_timber.fabrication import BTLxText +# from compas_timber.fabrication import BTLxText class BTLx(object): """Class representing a BTLx object. @@ -92,13 +92,13 @@ def process_assembly(self): factory_type = self.REGISTERED_JOINTS.get(str(type(joint))) factory_type.apply_processings(joint, self.parts) - for part in self.parts.values(): - if self.beam.engrave: - if part.processings: - ref_plane_id = part.processing[0].header_attributes.get("ReferencePlaneID", 1) - params_dict = part.get_text_engraving_params() - params_dict["ReferencePlaneID"] = ref_plane_id - part.processings.append(BTLxText.create_process(params_dict)) + # for part in self.parts.values(): + # if self.beam.engrave: + # if part.processings: + # ref_plane_id = part.processing[0].header_attributes.get("ReferencePlaneID", 1) + # params_dict = part.get_text_engraving_params() + # params_dict["ReferencePlaneID"] = ref_plane_id + # part.processings.append(BTLxText.create_process(params_dict)) @classmethod def register_joint(cls, joint_type, joint_factory): From ee9e4b22b9ee3a92a93e8c04c789c7fd057ff34f Mon Sep 17 00:00:00 2001 From: papachap Date: Mon, 6 May 2024 12:39:39 +0200 Subject: [PATCH 42/82] avoid import loop of modules (BTLx) --- .../fabrication/btlx_processes/btlx_text.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_text.py b/src/compas_timber/fabrication/btlx_processes/btlx_text.py index 19071d0c1e..509de2d6be 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_text.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_text.py @@ -1,7 +1,10 @@ from collections import OrderedDict -from compas_timber.fabrication import BTLx +# from compas_timber.fabrication import BTLx ##TODO if not disabled it creates a loop when we import it in the BTLx module from compas_timber.fabrication import BTLxProcess +##TODO hardcode the values in here instead of the BTLx module +POINT_PRECISION = 3 +ANGLE_PRECISION = 3 class BTLxText(object): """ @@ -20,7 +23,7 @@ class BTLxText(object): PROCESS_TYPE = "Text" - def __init__(self, param_dict, joint_name=None, **kwargs): # joint_name replace by "feature_name"? + def __init__(self, param_dict, joint_name=None, **kwargs): self.apply_process = True self.reference_plane_id = param_dict["ReferencePlaneID"] self.start_x = param_dict["StartX"] @@ -64,15 +67,15 @@ def process_params(self): """the following attributes are specific to a text engraving""" od = OrderedDict( [ - ("StartX", "{:.{prec}f}".format(self.start_x, prec=BTLx.POINT_PRECISION)), - ("StartY", "{:.{prec}f}".format(self.start_y, prec=BTLx.POINT_PRECISION)), - ("Angle", "{:.{prec}f}".format(self.angle, prec=BTLx.ANGLE_PRECISION)), + ("StartX", "{:.{prec}f}".format(self.start_x, prec=POINT_PRECISION)), + ("StartY", "{:.{prec}f}".format(self.start_y, prec=POINT_PRECISION)), + ("Angle", "{:.{prec}f}".format(self.angle, prec=ANGLE_PRECISION)), ("AlignmentVertical", str(self.alignment_vertical)), ("AlignmentHorizontal", str(self.alignment_horizontal)), ("AlignmentMultiline", str(self.alignment_multiline)), ("StackedMarking", bool(self.stacked_marking)), ("TextHeightAuto", bool(self.text_height_auto)), - ("TextHeight", "{:.{prec}f}".format(self.text_height, prec=BTLx.POINT_PRECISION)), + ("TextHeight", "{:.{prec}f}".format(self.text_height, prec=POINT_PRECISION)), ("Text", str(self.text)), ] From 690d231b0def49cd4c64cee60f601c6176fd92f2 Mon Sep 17 00:00:00 2001 From: papachap Date: Mon, 6 May 2024 12:42:01 +0200 Subject: [PATCH 43/82] avoid import loop of modules (BTLx) --- src/compas_timber/fabrication/btlx.py | 16 ++++++++-------- .../fabrication/btlx_processes/btlx_text.py | 15 +++++++++------ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index c172be8617..33dc0af725 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -10,7 +10,7 @@ from compas.geometry import Frame from compas.geometry import angle_vectors from compas.geometry import Transformation -# from compas_timber.fabrication import BTLxText +from compas_timber.fabrication import BTLxText class BTLx(object): """Class representing a BTLx object. @@ -92,13 +92,13 @@ def process_assembly(self): factory_type = self.REGISTERED_JOINTS.get(str(type(joint))) factory_type.apply_processings(joint, self.parts) - # for part in self.parts.values(): - # if self.beam.engrave: - # if part.processings: - # ref_plane_id = part.processing[0].header_attributes.get("ReferencePlaneID", 1) - # params_dict = part.get_text_engraving_params() - # params_dict["ReferencePlaneID"] = ref_plane_id - # part.processings.append(BTLxText.create_process(params_dict)) + for part in self.parts.values(): + if self.beam.engrave: + if part.processings: + ref_plane_id = part.processing[0].header_attributes.get("ReferencePlaneID", 1) + params_dict = part.get_text_engraving_params() + params_dict["ReferencePlaneID"] = ref_plane_id + part.processings.append(BTLxText.create_process(params_dict)) @classmethod def register_joint(cls, joint_type, joint_factory): diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_text.py b/src/compas_timber/fabrication/btlx_processes/btlx_text.py index 19071d0c1e..509de2d6be 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_text.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_text.py @@ -1,7 +1,10 @@ from collections import OrderedDict -from compas_timber.fabrication import BTLx +# from compas_timber.fabrication import BTLx ##TODO if not disabled it creates a loop when we import it in the BTLx module from compas_timber.fabrication import BTLxProcess +##TODO hardcode the values in here instead of the BTLx module +POINT_PRECISION = 3 +ANGLE_PRECISION = 3 class BTLxText(object): """ @@ -20,7 +23,7 @@ class BTLxText(object): PROCESS_TYPE = "Text" - def __init__(self, param_dict, joint_name=None, **kwargs): # joint_name replace by "feature_name"? + def __init__(self, param_dict, joint_name=None, **kwargs): self.apply_process = True self.reference_plane_id = param_dict["ReferencePlaneID"] self.start_x = param_dict["StartX"] @@ -64,15 +67,15 @@ def process_params(self): """the following attributes are specific to a text engraving""" od = OrderedDict( [ - ("StartX", "{:.{prec}f}".format(self.start_x, prec=BTLx.POINT_PRECISION)), - ("StartY", "{:.{prec}f}".format(self.start_y, prec=BTLx.POINT_PRECISION)), - ("Angle", "{:.{prec}f}".format(self.angle, prec=BTLx.ANGLE_PRECISION)), + ("StartX", "{:.{prec}f}".format(self.start_x, prec=POINT_PRECISION)), + ("StartY", "{:.{prec}f}".format(self.start_y, prec=POINT_PRECISION)), + ("Angle", "{:.{prec}f}".format(self.angle, prec=ANGLE_PRECISION)), ("AlignmentVertical", str(self.alignment_vertical)), ("AlignmentHorizontal", str(self.alignment_horizontal)), ("AlignmentMultiline", str(self.alignment_multiline)), ("StackedMarking", bool(self.stacked_marking)), ("TextHeightAuto", bool(self.text_height_auto)), - ("TextHeight", "{:.{prec}f}".format(self.text_height, prec=BTLx.POINT_PRECISION)), + ("TextHeight", "{:.{prec}f}".format(self.text_height, prec=POINT_PRECISION)), ("Text", str(self.text)), ] From d0f45b98250f416c4fc82a599e828741133f6ee7 Mon Sep 17 00:00:00 2001 From: papachap Date: Mon, 6 May 2024 12:42:38 +0200 Subject: [PATCH 44/82] hash_out --- src/compas_timber/fabrication/btlx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index 33dc0af725..a733292a38 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -10,7 +10,7 @@ from compas.geometry import Frame from compas.geometry import angle_vectors from compas.geometry import Transformation -from compas_timber.fabrication import BTLxText +from compas_timber.fabrication import BTLxText ##TODO this creates an importing loop // we need to redefine the conceptual logic class BTLx(object): """Class representing a BTLx object. From 302c08bf77a081918b75295a02263ecf9f03c6ab Mon Sep 17 00:00:00 2001 From: paulocinco Date: Mon, 6 May 2024 13:07:41 +0200 Subject: [PATCH 45/82] calc_params_drilling --- src/compas_timber/connections/butt_joint.py | 57 +++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 6c70d1379f..0e1e21cb33 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -292,3 +292,60 @@ def calc_params_birdsmouth(self): return True + def calc_params_drilling(self): + """ + Calculate the parameters for a drilling joint. + + Parameters: + ---------- + joint (object): The joint object. + main_part (object): The main part object. + cross_part (object): The cross part object. + + Returns: + ---------- + dict: A dictionary containing the calculated parameters for the drilling joint + + """ + ref_frame_id, ref_frame = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True) + ref_plane = Plane.from_frame(ref_frame) + point_xyz = (intersection_line_plane(self.main_beam.centerline, ref_plane)) + start_point = Point(*point_xyz) + ref_point = start_point.transformed(Transformation.from_frame_to_frame(ref_frame, Frame.worldXY())) + StartX, StartY = ref_point[0], ref_point[1] + print StartX, StartY + + param_point_on_line = self.main_beam.centerline.closest_point(start_point, True)[1] + if param_point_on_line > 0.5: + line_point = self.main_beam.centerline.end + else: + line_point = self.main_beam.centerline.start + projected_point = ref_plane.projected_point(line_point) + + center_line_vec = Vector.from_start_end(start_point, line_point) + projected_vec = Vector.from_start_end(start_point, projected_point) + Angle = ref_frame.xaxis.angle(projected_vec, True) + print "Angle = ", Angle + Inclination = projected_vec.angle(center_line_vec, True) + print "Inclination = ", Inclination + + + self.btlx_drilling_params_cross = { + "ReferencePlaneID": ref_frame_id, + "StartX": StartX, + "StartY": StartY, + "Angle": Angle, + "Inclination": Inclination, + "Diameter": self.drill_diameter, + "DepthLimited": False, + "Depth": 0.0 + + } + + # Rhino geometry visualization + line = Line(start_point, line_point) + line.start.translate(-line.vector) + normal_centerline_angle = 180-math.degrees(ref_frame.zaxis.angle(self.main_beam.centerline.direction)) + length = self.cross_beam.width/(math.cos(math.radians(normal_centerline_angle))) + print length + return line, self.drill_diameter, length*3 From 3cedbe953c71bbbb2cefbac3fef1e3c9aefddc43 Mon Sep 17 00:00:00 2001 From: paulocinco Date: Mon, 6 May 2024 13:08:28 +0200 Subject: [PATCH 46/82] added drilling feature&code --- src/compas_timber/connections/t_butt.py | 11 ++++++----- .../fabrication/joint_factories/t_butt_factory.py | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/compas_timber/connections/t_butt.py b/src/compas_timber/connections/t_butt.py index c264698c13..1f0ace2b5b 100644 --- a/src/compas_timber/connections/t_butt.py +++ b/src/compas_timber/connections/t_butt.py @@ -76,24 +76,21 @@ def add_features(self): """ assert self.main_beam and self.cross_beam # should never happen - + print "add_features" if self.features: self.main_beam.remove_features(self.features) cutting_plane = None try: cutting_plane = self.get_main_cutting_plane()[0] - except AttributeError as ae: raise BeamJoinningError(beams=self.beams, joint=self, debug_info=str(ae), debug_geometries=[cutting_plane]) except Exception as ex: raise BeamJoinningError(beams=self.beams, joint=self, debug_info=str(ex)) self.features = [] - + print self.mill_depth, self.birdsmouth, self.drill_diameter if self.mill_depth: self.cross_beam.add_features(MillVolume(self.subtraction_volume())) - if self.drill_diameter: - self.cross_beam.add_features(DrillFeature(*self.calc_params_drilling())) self.features.append(MillVolume(self.subtraction_volume())) do_jack = False if self.birdsmouth: @@ -106,3 +103,7 @@ def add_features(self): if do_jack: self.main_beam.add_features(CutFeature(cutting_plane)) self.features.append(cutting_plane) + if self.drill_diameter > 0: + print "drilling calc" + self.cross_beam.add_features(DrillFeature(*self.calc_params_drilling())) + self.features.append(DrillFeature(*self.calc_params_drilling())) diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index b2e296e554..90431d53c1 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -48,7 +48,8 @@ def apply_processings(cls, joint, parts): cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, "T-Butt Joint")) if joint.drill_diameter > 0: - joint.btlx_drilling_params_cross["reference_plane_id"] = cross_part.reference_surface_from_beam_face(ref_plane) + + joint.btlx_drilling_params_cross["ReferencePlaneID"] = str(cross_part.reference_surface_from_beam_face(ref_plane)) cross_part.processings.append(BTLxDrilling.create_process(joint.btlx_drilling_params_cross, "T-Butt Joint")) From e2e92b59604111e19d2e7813cbce321a30b54b8a Mon Sep 17 00:00:00 2001 From: paulocinco Date: Mon, 6 May 2024 17:01:10 +0200 Subject: [PATCH 47/82] Btlx drilling for t-butt completed --- src/compas_timber/connections/butt_joint.py | 38 +++++++++++++++---- .../joint_factories/t_butt_factory.py | 5 ++- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 0e1e21cb33..9397380ca7 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -307,8 +307,29 @@ def calc_params_drilling(self): dict: A dictionary containing the calculated parameters for the drilling joint """ - ref_frame_id, ref_frame = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True) - ref_plane = Plane.from_frame(ref_frame) + # ref_frame_id, ref_frame = self.get_face_most_towards_beam(self.main_beam, self.cross_beam, ignore_ends=True) + # print ref_frame_id + + _cut_plane, cutting_frame = self.get_main_cutting_plane() + print "ref_ frame type : ", type(cutting_frame) + ref_plane = Plane.from_frame(cutting_frame) + + angles_dict = {} + for i, face in enumerate(self.cross_beam.faces[0:4]): + angles_dict[i] = face.normal.angle(cutting_frame.normal) + cross_face_index = min(angles_dict.keys(), key=angles_dict.get) + print cross_face_index + ref_frame = self.cross_beam.faces[cross_face_index] + + ref_frame.point = self.cross_beam.blank_frame.point + if cross_face_index % 2 == 0: + ref_frame.point = ref_frame.point - ref_frame.yaxis * self.cross_beam.height * 0.5 + ref_frame.point = ref_frame.point + ref_frame.zaxis * self.cross_beam.width * 0.5 + else: + ref_frame.point = ref_frame.point - ref_frame.yaxis * self.cross_beam.width * 0.5 + ref_frame.point = ref_frame.point + ref_frame.zaxis * self.cross_beam.height * 0.5 + + # ref_plane = Plane.from_frame(ref_frame) point_xyz = (intersection_line_plane(self.main_beam.centerline, ref_plane)) start_point = Point(*point_xyz) ref_point = start_point.transformed(Transformation.from_frame_to_frame(ref_frame, Frame.worldXY())) @@ -324,20 +345,21 @@ def calc_params_drilling(self): center_line_vec = Vector.from_start_end(start_point, line_point) projected_vec = Vector.from_start_end(start_point, projected_point) - Angle = ref_frame.xaxis.angle(projected_vec, True) - print "Angle = ", Angle + Angle = 180 - math.degrees(ref_frame.xaxis.angle_signed(projected_vec, ref_frame.zaxis)) + # Angle = ref_frame.xaxis.angle(projected_vec, True) + # print "Angle = ", Angle Inclination = projected_vec.angle(center_line_vec, True) - print "Inclination = ", Inclination + # print "Inclination = ", Inclination self.btlx_drilling_params_cross = { - "ReferencePlaneID": ref_frame_id, + "ReferencePlaneID": cross_face_index, # "0" is a placeholder, should be replaced with the actual reference plane id "StartX": StartX, "StartY": StartY, "Angle": Angle, "Inclination": Inclination, "Diameter": self.drill_diameter, - "DepthLimited": False, + "DepthLimited": "no", "Depth": 0.0 } @@ -347,5 +369,5 @@ def calc_params_drilling(self): line.start.translate(-line.vector) normal_centerline_angle = 180-math.degrees(ref_frame.zaxis.angle(self.main_beam.centerline.direction)) length = self.cross_beam.width/(math.cos(math.radians(normal_centerline_angle))) - print length + # print length return line, self.drill_diameter, length*3 diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index 90431d53c1..eefb8dc6a8 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -48,8 +48,9 @@ def apply_processings(cls, joint, parts): cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, "T-Butt Joint")) if joint.drill_diameter > 0: - - joint.btlx_drilling_params_cross["ReferencePlaneID"] = str(cross_part.reference_surface_from_beam_face(ref_plane)) + print("drill_diameter", joint.btlx_drilling_params_cross) + print(str(joint.btlx_params_cross["ReferencePlaneID"])) + joint.btlx_drilling_params_cross["ReferencePlaneID"] = str(joint.btlx_params_cross["ReferencePlaneID"]) cross_part.processings.append(BTLxDrilling.create_process(joint.btlx_drilling_params_cross, "T-Butt Joint")) From 7aca1d68a5ff35c33488bfad9aef3168ccae03ab Mon Sep 17 00:00:00 2001 From: paulocinco Date: Mon, 6 May 2024 17:11:03 +0200 Subject: [PATCH 48/82] cleanup --- src/compas_timber/connections/butt_joint.py | 9 +-------- src/compas_timber/connections/t_butt.py | 3 --- src/compas_timber/fabrication/btlx.py | 16 ++++++++-------- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 9397380ca7..336efc00ff 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -311,14 +311,12 @@ def calc_params_drilling(self): # print ref_frame_id _cut_plane, cutting_frame = self.get_main_cutting_plane() - print "ref_ frame type : ", type(cutting_frame) ref_plane = Plane.from_frame(cutting_frame) angles_dict = {} for i, face in enumerate(self.cross_beam.faces[0:4]): angles_dict[i] = face.normal.angle(cutting_frame.normal) cross_face_index = min(angles_dict.keys(), key=angles_dict.get) - print cross_face_index ref_frame = self.cross_beam.faces[cross_face_index] ref_frame.point = self.cross_beam.blank_frame.point @@ -334,7 +332,6 @@ def calc_params_drilling(self): start_point = Point(*point_xyz) ref_point = start_point.transformed(Transformation.from_frame_to_frame(ref_frame, Frame.worldXY())) StartX, StartY = ref_point[0], ref_point[1] - print StartX, StartY param_point_on_line = self.main_beam.centerline.closest_point(start_point, True)[1] if param_point_on_line > 0.5: @@ -346,14 +343,11 @@ def calc_params_drilling(self): center_line_vec = Vector.from_start_end(start_point, line_point) projected_vec = Vector.from_start_end(start_point, projected_point) Angle = 180 - math.degrees(ref_frame.xaxis.angle_signed(projected_vec, ref_frame.zaxis)) - # Angle = ref_frame.xaxis.angle(projected_vec, True) - # print "Angle = ", Angle Inclination = projected_vec.angle(center_line_vec, True) - # print "Inclination = ", Inclination self.btlx_drilling_params_cross = { - "ReferencePlaneID": cross_face_index, # "0" is a placeholder, should be replaced with the actual reference plane id + "ReferencePlaneID": cross_face_index, "StartX": StartX, "StartY": StartY, "Angle": Angle, @@ -369,5 +363,4 @@ def calc_params_drilling(self): line.start.translate(-line.vector) normal_centerline_angle = 180-math.degrees(ref_frame.zaxis.angle(self.main_beam.centerline.direction)) length = self.cross_beam.width/(math.cos(math.radians(normal_centerline_angle))) - # print length return line, self.drill_diameter, length*3 diff --git a/src/compas_timber/connections/t_butt.py b/src/compas_timber/connections/t_butt.py index 1f0ace2b5b..ff5ccc56ec 100644 --- a/src/compas_timber/connections/t_butt.py +++ b/src/compas_timber/connections/t_butt.py @@ -76,7 +76,6 @@ def add_features(self): """ assert self.main_beam and self.cross_beam # should never happen - print "add_features" if self.features: self.main_beam.remove_features(self.features) cutting_plane = None @@ -88,7 +87,6 @@ def add_features(self): raise BeamJoinningError(beams=self.beams, joint=self, debug_info=str(ex)) self.features = [] - print self.mill_depth, self.birdsmouth, self.drill_diameter if self.mill_depth: self.cross_beam.add_features(MillVolume(self.subtraction_volume())) self.features.append(MillVolume(self.subtraction_volume())) @@ -104,6 +102,5 @@ def add_features(self): self.main_beam.add_features(CutFeature(cutting_plane)) self.features.append(cutting_plane) if self.drill_diameter > 0: - print "drilling calc" self.cross_beam.add_features(DrillFeature(*self.calc_params_drilling())) self.features.append(DrillFeature(*self.calc_params_drilling())) diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index a733292a38..e890c4a7ca 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -10,7 +10,7 @@ from compas.geometry import Frame from compas.geometry import angle_vectors from compas.geometry import Transformation -from compas_timber.fabrication import BTLxText ##TODO this creates an importing loop // we need to redefine the conceptual logic +# from compas_timber.fabrication import BTLxText ##TODO this creates an importing loop // we need to redefine the conceptual logic class BTLx(object): """Class representing a BTLx object. @@ -92,13 +92,13 @@ def process_assembly(self): factory_type = self.REGISTERED_JOINTS.get(str(type(joint))) factory_type.apply_processings(joint, self.parts) - for part in self.parts.values(): - if self.beam.engrave: - if part.processings: - ref_plane_id = part.processing[0].header_attributes.get("ReferencePlaneID", 1) - params_dict = part.get_text_engraving_params() - params_dict["ReferencePlaneID"] = ref_plane_id - part.processings.append(BTLxText.create_process(params_dict)) + # for part in self.parts.values(): + # if self.beam.engrave: + # if part.processings: + # ref_plane_id = part.processing[0].header_attributes.get("ReferencePlaneID", 1) + # params_dict = part.get_text_engraving_params() + # params_dict["ReferencePlaneID"] = ref_plane_id + # part.processings.append(BTLxText.create_process(params_dict)) @classmethod def register_joint(cls, joint_type, joint_factory): From 2af9df061658d13f7d08aca29a531a199321eb23 Mon Sep 17 00:00:00 2001 From: paulocinco Date: Mon, 6 May 2024 18:09:45 +0200 Subject: [PATCH 49/82] added drilling to french ridge btlx paramters --- .../connections/french_ridge_lap.py | 34 ++++++++++++++++++- .../btlx_processes/btlx_french_ridge_lap.py | 10 +++--- .../joint_factories/french_ridge_factory.py | 5 +-- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/compas_timber/connections/french_ridge_lap.py b/src/compas_timber/connections/french_ridge_lap.py index c7c3524ac6..bdab7f6e6f 100644 --- a/src/compas_timber/connections/french_ridge_lap.py +++ b/src/compas_timber/connections/french_ridge_lap.py @@ -38,10 +38,11 @@ class FrenchRidgeLapJoint(Joint): SUPPORTED_TOPOLOGY = JointTopology.TOPO_L - def __init__(self, beam_a=None, beam_b=None, **kwargs): + def __init__(self, beam_a=None, beam_b=None, drill_diameter=0, **kwargs): super(FrenchRidgeLapJoint, self).__init__(beams=(beam_a, beam_b), **kwargs) self.beam_a = beam_a self.beam_b = beam_b + self.drill_diameter = drill_diameter 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.reference_face_indices = {} @@ -135,3 +136,34 @@ def check_geometry(self): ) ) self.reference_face_indices = {str(self.beam_a.key): indices[0], str(self.beam_b.key): indices[1]} + + def calc_params_dilling(self): + """Calculates the parameters for drilling process.""" + + _cut_plane, cutting_frame = self.get_main_cutting_plane() + ref_plane = Plane.from_frame(cutting_frame) + + angles_dict = {} + for i, face in enumerate(self.cross_beam.faces[0:4]): + angles_dict[i] = face.normal.angle(cutting_frame.normal) + cross_face_index = min(angles_dict.keys(), key=angles_dict.get) + ref_frame = self.cross_beam.faces[cross_face_index] + + ref_frame.point = self.cross_beam.blank_frame.point + if cross_face_index % 2 == 0: + ref_frame.point = ref_frame.point - ref_frame.yaxis * self.cross_beam.height * 0.5 + ref_frame.point = ref_frame.point + ref_frame.zaxis * self.cross_beam.width * 0.5 + else: + ref_frame.point = ref_frame.point - ref_frame.yaxis * self.cross_beam.width * 0.5 + ref_frame.point = ref_frame.point + ref_frame.zaxis * self.cross_beam.height * 0.5 + + self.btlx_drilling_params_cross = { + "ReferencePlaneID": cross_face_index, + "StartX": StartX, + "StartY": StartY, + "Angle": Angle, + "Inclination": Inclination, + "Diameter": self.drill_diameter, + "DepthLimited": "no", + "Depth": 0.0 + } diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_french_ridge_lap.py b/src/compas_timber/fabrication/btlx_processes/btlx_french_ridge_lap.py index 121fea60f2..37a3abef4f 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_french_ridge_lap.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_french_ridge_lap.py @@ -51,7 +51,7 @@ class BTLxFrenchRidgeLap(object): PROCESS_TYPE = "FrenchRidgeLap" - def __init__(self, part, joint, is_top): + def __init__(self, part, joint, is_top, drill_diameter=0.0): for beam in joint.beams: if beam.key == part.key: self.beam = beam @@ -62,8 +62,8 @@ def __init__(self, part, joint, is_top): self.is_top = is_top self.orientation = joint.ends[str(part.key)] self._ref_edge = True - self._drill_hole = True - self.drill_hole_diameter = 10.0 + self._drill_hole = True if drill_diameter > 0 else False + self.drill_hole_diameter = drill_diameter self.ref_face_index = self.joint.reference_face_indices[str(self.beam.key)] self.ref_face = self.part.reference_surface_planes(str(self.ref_face_index)) @@ -169,8 +169,8 @@ def get_params(self): ) @classmethod - def create_process(cls, part, joint, is_top): - frl_process = BTLxFrenchRidgeLap(part, joint, is_top) + def create_process(cls, part, joint, is_top, drill_diameter): + frl_process = BTLxFrenchRidgeLap(part, joint, is_top, drill_diameter) return BTLxProcess( BTLxFrenchRidgeLap.PROCESS_TYPE, frl_process.header_attributes, frl_process.process_parameters ) diff --git a/src/compas_timber/fabrication/joint_factories/french_ridge_factory.py b/src/compas_timber/fabrication/joint_factories/french_ridge_factory.py index f4ca677c31..c28535faa0 100644 --- a/src/compas_timber/fabrication/joint_factories/french_ridge_factory.py +++ b/src/compas_timber/fabrication/joint_factories/french_ridge_factory.py @@ -1,6 +1,7 @@ from compas_timber.connections import FrenchRidgeLapJoint from compas_timber.fabrication import BTLx from compas_timber.fabrication import BTLxFrenchRidgeLap +from compas_timber.fabrication.btlx_processes.btlx_drilling import BTLxDrilling class FrenchRidgeFactory(object): @@ -31,11 +32,11 @@ def apply_processings(cls, joint, parts): top_key = joint.beams[0].key top_part = parts[str(top_key)] - top_part.processings.append(BTLxFrenchRidgeLap.create_process(top_part, joint, True)) + top_part.processings.append(BTLxFrenchRidgeLap.create_process(top_part, joint, True, joint.drill_diameter)) bottom_key = joint.beams[1].key bottom_part = parts[str(bottom_key)] - bottom_part.processings.append(BTLxFrenchRidgeLap.create_process(bottom_part, joint, False)) + bottom_part.processings.append(BTLxFrenchRidgeLap.create_process(bottom_part, joint, False, 0.0)) BTLx.register_joint(FrenchRidgeLapJoint, FrenchRidgeFactory) From 67081e49550b2ab5b44c261cab7cb042951f3499 Mon Sep 17 00:00:00 2001 From: paulocinco Date: Tue, 7 May 2024 09:13:14 +0200 Subject: [PATCH 50/82] cleanup --- .../connections/french_ridge_lap.py | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/src/compas_timber/connections/french_ridge_lap.py b/src/compas_timber/connections/french_ridge_lap.py index bdab7f6e6f..61a41843e7 100644 --- a/src/compas_timber/connections/french_ridge_lap.py +++ b/src/compas_timber/connections/french_ridge_lap.py @@ -136,34 +136,3 @@ def check_geometry(self): ) ) self.reference_face_indices = {str(self.beam_a.key): indices[0], str(self.beam_b.key): indices[1]} - - def calc_params_dilling(self): - """Calculates the parameters for drilling process.""" - - _cut_plane, cutting_frame = self.get_main_cutting_plane() - ref_plane = Plane.from_frame(cutting_frame) - - angles_dict = {} - for i, face in enumerate(self.cross_beam.faces[0:4]): - angles_dict[i] = face.normal.angle(cutting_frame.normal) - cross_face_index = min(angles_dict.keys(), key=angles_dict.get) - ref_frame = self.cross_beam.faces[cross_face_index] - - ref_frame.point = self.cross_beam.blank_frame.point - if cross_face_index % 2 == 0: - ref_frame.point = ref_frame.point - ref_frame.yaxis * self.cross_beam.height * 0.5 - ref_frame.point = ref_frame.point + ref_frame.zaxis * self.cross_beam.width * 0.5 - else: - ref_frame.point = ref_frame.point - ref_frame.yaxis * self.cross_beam.width * 0.5 - ref_frame.point = ref_frame.point + ref_frame.zaxis * self.cross_beam.height * 0.5 - - self.btlx_drilling_params_cross = { - "ReferencePlaneID": cross_face_index, - "StartX": StartX, - "StartY": StartY, - "Angle": Angle, - "Inclination": Inclination, - "Diameter": self.drill_diameter, - "DepthLimited": "no", - "Depth": 0.0 - } From f7732fa2684ca3a9261e297a6ea151424b971cda Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 7 May 2024 11:43:36 +0200 Subject: [PATCH 51/82] import TextFactory in init --- src/compas_timber/fabrication/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/compas_timber/fabrication/__init__.py b/src/compas_timber/fabrication/__init__.py index e5a0938e30..5e2c0c9aed 100644 --- a/src/compas_timber/fabrication/__init__.py +++ b/src/compas_timber/fabrication/__init__.py @@ -10,6 +10,7 @@ from .joint_factories.l_butt_factory import LButtFactory from .joint_factories.l_miter_factory import LMiterFactory from .joint_factories.t_butt_factory import TButtFactory +from .joint_factories.text_factory import TextFactory __all__ = [ "BTLx", @@ -24,4 +25,5 @@ "TButtFactory", "LMiterFactory", "FrenchRidgeFactory", + "TextFactory", ] From 415738538e06be4fd60f402e198675865f986940 Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 7 May 2024 11:46:11 +0200 Subject: [PATCH 52/82] implement new method for intersct params in solver --- src/compas_timber/connections/solver.py | 27 ++++++++++++++----------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/compas_timber/connections/solver.py b/src/compas_timber/connections/solver.py index 299cae45ae..0794ec45bd 100644 --- a/src/compas_timber/connections/solver.py +++ b/src/compas_timber/connections/solver.py @@ -109,18 +109,21 @@ def find_intersecting_pairs(cls, beams, rtree=False, max_distance=None): """ - pair_indexes = find_neighboring_beams(beams, inflate_by=max_distance) if rtree else itertools.combinations(beams, 2) - for i, beam in enumerate(beams): - beam.attributes["intersecitons"]=[] - for pair in pair_indexes: - if i in pair: - a = pair[0] - b = pair[1] - intersection_points = intersection_line_line(beam[a].centerline, beam[b].centerline)[0] - intersection_param = beam.centerline.closest_point(intersection_points[0], return_parameter=True) - beam.attributes["intersecitons"].append(intersection_param) - - return pair_indexes + neighboring_pairs = find_neighboring_beams(beams, inflate_by=max_distance) if rtree else itertools.combinations(beams, 2) + return neighboring_pairs + + @classmethod + def find_intersection_parameters(cls, beams, tol=None): + generic_pair_indeces = itertools.combinations(range(len(beams)), 2) + for pair in generic_pair_indeces: + pair_centerlines = [beams[p].centerline for p in pair] + intersection_points = intersection_line_line(*pair_centerlines, tol=10.0) + if intersection_points: + if all(abs(p1 - p2) < cls.TOLERANCE for p1, p2 in zip(intersection_points[0], intersection_points[1])): + intersection_point = Point(*intersection_points[0]) + intersection_params = [beams[p].centerline.closest_point(intersection_point, return_parameter=True)[1] for p in pair] + for i, p in enumerate(pair): + beams[p].intersections.append(float("{:.2f}".format(intersection_params[i]))) def find_topology(self, beam_a, beam_b, tol=TOLERANCE, max_distance=None): From bb396b1ec2dc8614a47f23d88aacef82bb5c3f9d Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 7 May 2024 11:48:56 +0200 Subject: [PATCH 53/82] implement new logic w/ registering features from factory --- src/compas_timber/fabrication/btlx.py | 58 ++++++--------- .../joint_factories/text_factory.py | 71 +++++++++++++++++++ 2 files changed, 94 insertions(+), 35 deletions(-) create mode 100644 src/compas_timber/fabrication/joint_factories/text_factory.py diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index a733292a38..eaa6ced74f 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -10,7 +10,6 @@ from compas.geometry import Frame from compas.geometry import angle_vectors from compas.geometry import Transformation -from compas_timber.fabrication import BTLxText ##TODO this creates an importing loop // we need to redefine the conceptual logic class BTLx(object): """Class representing a BTLx object. @@ -38,6 +37,7 @@ class BTLx(object): POINT_PRECISION = 3 ANGLE_PRECISION = 3 REGISTERED_JOINTS = {} + REGISTERED_FEATURES = {} FILE_ATTRIBUTES = OrderedDict( [ ("xmlns", "https://www.design2machine.com"), @@ -91,14 +91,9 @@ def process_assembly(self): for joint in self.joints: factory_type = self.REGISTERED_JOINTS.get(str(type(joint))) factory_type.apply_processings(joint, self.parts) - for part in self.parts.values(): - if self.beam.engrave: - if part.processings: - ref_plane_id = part.processing[0].header_attributes.get("ReferencePlaneID", 1) - params_dict = part.get_text_engraving_params() - params_dict["ReferencePlaneID"] = ref_plane_id - part.processings.append(BTLxText.create_process(params_dict)) + factory_type = self.REGISTERED_FEATURES.get("TextID") + factory_type.apply_processings(part) @classmethod def register_joint(cls, joint_type, joint_factory): @@ -118,6 +113,24 @@ def register_joint(cls, joint_type, joint_factory): """ cls.REGISTERED_JOINTS[str(joint_type)] = joint_factory + @classmethod + def register_feature(cls, feature_type, feature_factory): + """Registers a feature type and its corresponding factory. + + Parameters + ---------- + feature_type : type + The type of the feature. + feature_factory : : class:`~compas_timber.fabrication.feature_factories.feature_factory.FeatureFactory` + The factory for creating the feature. + + Returns + ------- + None + + """ + cls.REGISTERED_FEATURES[str(feature_type)] = feature_factory + @property def file_history(self): """Returns the file history element.""" @@ -177,11 +190,13 @@ def __init__(self, beam): beam.frame.yaxis, ) # I used long_edge[2] because it is in Y and Z negative. Using that as reference puts the beam entirely in positive coordinates. self.blank_length = beam.blank_length + # self.intersections = beam.attributes["intersections"] self.intersections = beam.intersections self._reference_surfaces = [] self.processings = [] self._et_element = None + def reference_surface_from_beam_face(self, beam_face): """Finds the reference surface with normal that matches the normal of the beam face argument @@ -241,33 +256,6 @@ def reference_surface_planes(self, index): } return self._reference_surfaces[str(index)] - def get_engraving_position(self): - """Finds the optimal parameter on the line for the text engraving process.""" - all_points = sorted([0] + self.intersections + [1]) - - max_length = 0 - optimal_midpoint = None - for i in range(len(all_points) - 1): - seg_length = all_points[i+1] - all_points[i] - if seg_length > max_length: - max_length = seg_length - optimal_midpoint = (all_points[i] + all_points[i+1]) / 2 - return optimal_midpoint - - def get_text_engraving_params(self): - """Returns the text engraving parameters for the BTLx part.""" - return { - "ReferencePlaneID": 1, #default face - "StartX": self.get_engraving_position(), - "StartY": self.width/2, #always set it to the middle of the beam - "Angle": 0, - "AlignmentVertical": "center", - "AlignmentHorizontal": "center", - "AlignmentMultiline": "center", - "TextHeight": 20, - "Text": self.beam.attributes["airModule_no"] + "_" + self.beam.attributes["assemblyModule_no"] + "_" + self.beam.attributes["beam_no"] - } - @property def attr(self): return { diff --git a/src/compas_timber/fabrication/joint_factories/text_factory.py b/src/compas_timber/fabrication/joint_factories/text_factory.py new file mode 100644 index 0000000000..3c1797b059 --- /dev/null +++ b/src/compas_timber/fabrication/joint_factories/text_factory.py @@ -0,0 +1,71 @@ +from compas_timber.fabrication import BTLx +from compas_timber.fabrication import BTLxText + +class TextFactory(object): + """ + Factory class for creating Text engraving. + """ + + def __init__(self): + pass + + @staticmethod + def get_engraving_position(part): + """Finds the optimal parameter on the line for the text engraving process.""" + [part.intersections.append(i) for i in {0, 1} if i not in part.intersections] + all_points = sorted(part.intersections) + print("intersections", all_points) + max_length = 0 + optimal_midpoint = 0.5 + for i in range(len(all_points) - 1): + seg_length = all_points[i+1] - all_points[i] + if seg_length > max_length: + max_length = seg_length + optimal_midpoint = (all_points[i] + all_points[i+1]) / 2 + print("Optimal midpoint: ", optimal_midpoint*part.length, "optimal parameter", optimal_midpoint) + return float(optimal_midpoint*part.length) + + @staticmethod + def get_text_engraving_params(part): + """Returns the text engraving parameters for the BTLx part.""" + return { + "ReferencePlaneID": 1, #default face + "StartX": TextFactory.get_engraving_position(part), + "StartY": part.width/2, #always set it to the middle of the beam + "Angle": 0.0, + "AlignmentVertical": "center", + "AlignmentHorizontal": "center", + "AlignmentMultiline": "center", + "TextHeight": 20.0, + # "Text": part.beam.attributes["airModule_no"] + "Text": "Hello you 023" + } + + @classmethod + def apply_processings(cls, part): + """ + Apply processings to the joint and parts. + + Parameters + ---------- + joint : :class:`~compas_timber.connections.joint.Joint` + The joint object. + parts : dict + A dictionary of the BTLxParts connected by this joint, with part keys as the dictionary keys. + + Returns + ------- + None + + """ + + if part.processings: + ref_plane_id = part.processings[0].header_attributes.get("ReferencePlaneID", 1) + else: + ref_plane_id = "1" + params_dict = TextFactory.get_text_engraving_params(part) + params_dict["ReferencePlaneID"] = ref_plane_id + part.processings.append(BTLxText.create_process(params_dict, "Text")) + +BTLx.register_feature("TextID", TextFactory) + From 5a78051d579166f861ffb9167b0f7712e9159b84 Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 7 May 2024 11:49:11 +0200 Subject: [PATCH 54/82] remove unnecessary params --- .../fabrication/btlx_processes/btlx_text.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_text.py b/src/compas_timber/fabrication/btlx_processes/btlx_text.py index 509de2d6be..eada1ceabb 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_text.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_text.py @@ -1,11 +1,7 @@ from collections import OrderedDict -# from compas_timber.fabrication import BTLx ##TODO if not disabled it creates a loop when we import it in the BTLx module +from compas_timber.fabrication import BTLx from compas_timber.fabrication import BTLxProcess -##TODO hardcode the values in here instead of the BTLx module -POINT_PRECISION = 3 -ANGLE_PRECISION = 3 - class BTLxText(object): """ Represents an engraving process of a text for timber fabrication. @@ -32,8 +28,6 @@ def __init__(self, param_dict, joint_name=None, **kwargs): self.alignment_vertical = param_dict["AlignmentVertical"] self.alignment_horizontal = param_dict["AlignmentHorizontal"] self.alignment_multiline = param_dict["AlignmentMultiline"] - self.stacked_marking = param_dict["StackedMarking"] - self.text_height_auto = param_dict["TextHeightAuto"] self.text_height = param_dict["TextHeight"] self.text = param_dict["Text"] @@ -67,15 +61,13 @@ def process_params(self): """the following attributes are specific to a text engraving""" od = OrderedDict( [ - ("StartX", "{:.{prec}f}".format(self.start_x, prec=POINT_PRECISION)), - ("StartY", "{:.{prec}f}".format(self.start_y, prec=POINT_PRECISION)), - ("Angle", "{:.{prec}f}".format(self.angle, prec=ANGLE_PRECISION)), + ("StartX", "{:.{prec}f}".format(self.start_x, prec=BTLx.POINT_PRECISION)), + ("StartY", "{:.{prec}f}".format(self.start_y, prec=BTLx.POINT_PRECISION)), + ("Angle", "{:.{prec}f}".format(self.angle, prec=BTLx.ANGLE_PRECISION)), ("AlignmentVertical", str(self.alignment_vertical)), ("AlignmentHorizontal", str(self.alignment_horizontal)), ("AlignmentMultiline", str(self.alignment_multiline)), - ("StackedMarking", bool(self.stacked_marking)), - ("TextHeightAuto", bool(self.text_height_auto)), - ("TextHeight", "{:.{prec}f}".format(self.text_height, prec=POINT_PRECISION)), + ("TextHeight", "{:.{prec}f}".format(self.text_height, prec=BTLx.POINT_PRECISION)), ("Text", str(self.text)), ] From 5ca47e718f1283922c7457feb3f4242a4a8d8153 Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 7 May 2024 19:24:33 +0200 Subject: [PATCH 55/82] debug intersection solver --- src/compas_timber/connections/solver.py | 24 +++++++++++++------ .../joint_factories/text_factory.py | 23 ++++++++++-------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/compas_timber/connections/solver.py b/src/compas_timber/connections/solver.py index 0794ec45bd..d77582776e 100644 --- a/src/compas_timber/connections/solver.py +++ b/src/compas_timber/connections/solver.py @@ -118,13 +118,15 @@ def find_intersection_parameters(cls, beams, tol=None): for pair in generic_pair_indeces: pair_centerlines = [beams[p].centerline for p in pair] intersection_points = intersection_line_line(*pair_centerlines, tol=10.0) - if intersection_points: - if all(abs(p1 - p2) < cls.TOLERANCE for p1, p2 in zip(intersection_points[0], intersection_points[1])): - intersection_point = Point(*intersection_points[0]) - intersection_params = [beams[p].centerline.closest_point(intersection_point, return_parameter=True)[1] for p in pair] - for i, p in enumerate(pair): - beams[p].intersections.append(float("{:.2f}".format(intersection_params[i]))) - + if None in intersection_points: + continue + if all(abs(p1 - p2) < cls.TOLERANCE for p1, p2 in zip(intersection_points[0], intersection_points[1])): + intersection_point = Point(*intersection_points[0]) + intersection_params = [cls._parameter_on_line(intersection_point, beams[p].centerline) for p in pair] + for i, p in enumerate(pair): + # Check if the parameter is within [0, 1], if not, adjust it + intersection_params[i]= max(0, min(1,(intersection_params[i]))) #//TODO: this is just a temporal solution + beams[p].intersections.append(intersection_params[i]) def find_topology(self, beam_a, beam_b, tol=TOLERANCE, max_distance=None): """If `beam_a` and `beam_b` intersect within the given `max_distance`, return the topology type of the intersection. @@ -235,6 +237,14 @@ def find_topology(self, beam_a, beam_b, tol=TOLERANCE, max_distance=None): # X-joint (both meeting somewhere along the line) return JointTopology.TOPO_X, beam_a, beam_b + @staticmethod + def _parameter_on_line(point, line): + # Vector from the start of the line segment to the given point + point_vector = [(point.x - line.start.x), (point.y - line.start.y), (point.z - line.start.z)] + # Calculate the parameter (t) using dot product + t = dot_vectors(point_vector, line.vector) / dot_vectors(line.vector, line.vector) + return t + @staticmethod def _calc_t(line, plane): a, b = line diff --git a/src/compas_timber/fabrication/joint_factories/text_factory.py b/src/compas_timber/fabrication/joint_factories/text_factory.py index 3c1797b059..e4bc5f09ed 100644 --- a/src/compas_timber/fabrication/joint_factories/text_factory.py +++ b/src/compas_timber/fabrication/joint_factories/text_factory.py @@ -12,18 +12,21 @@ def __init__(self): @staticmethod def get_engraving_position(part): """Finds the optimal parameter on the line for the text engraving process.""" - [part.intersections.append(i) for i in {0, 1} if i not in part.intersections] - all_points = sorted(part.intersections) - print("intersections", all_points) + intersections = set(part.intersections) + intersections.update({0, 1}) # Ensure 0 and 1 are included + all_intersections = sorted(intersections) + max_length = 0 - optimal_midpoint = 0.5 - for i in range(len(all_points) - 1): - seg_length = all_points[i+1] - all_points[i] + optimal_parameter = 0.5 + for i in range(len(all_intersections) - 1): + seg_length = all_intersections[i+1] - all_intersections[i] if seg_length > max_length: max_length = seg_length - optimal_midpoint = (all_points[i] + all_points[i+1]) / 2 - print("Optimal midpoint: ", optimal_midpoint*part.length, "optimal parameter", optimal_midpoint) - return float(optimal_midpoint*part.length) + optimal_parameter = (all_intersections[i] + all_intersections[i+1]) / 2 + + optimal_parameter = 0.5 if optimal_parameter in {0, 1} else optimal_parameter #//TODO: this is a temporal fix + optimal_position = optimal_parameter * part.length + return optimal_position @staticmethod def get_text_engraving_params(part): @@ -38,7 +41,7 @@ def get_text_engraving_params(part): "AlignmentMultiline": "center", "TextHeight": 20.0, # "Text": part.beam.attributes["airModule_no"] - "Text": "Hello you 023" + "Text": "AM01_05" } @classmethod From 1402c6b45005140ae66f75b4fcd9b191d678d9fb Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 7 May 2024 19:26:13 +0200 Subject: [PATCH 56/82] debug drilling feature - float everywhere --- src/compas_timber/connections/butt_joint.py | 8 +++---- .../connections/french_ridge_lap.py | 4 ++-- .../btlx_processes/btlx_drilling.py | 12 +++++----- .../btlx_processes/btlx_french_ridge_lap.py | 24 +++++++++---------- .../joint_factories/t_butt_factory.py | 2 -- src/compas_timber/parts/beam.py | 4 ++-- 6 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 336efc00ff..561953a070 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -50,14 +50,14 @@ class ButtJoint(Joint): """ - def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, drill_diameter=0, birdsmouth=False, **kwargs): + def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, drill_diameter=0.0, birdsmouth=False, **kwargs): super(ButtJoint, self).__init__(**kwargs) 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.drill_diameter = drill_diameter + self.drill_diameter = float(drill_diameter) self.birdsmouth = birdsmouth self.btlx_params_main = {} self.btlx_params_cross = {} @@ -350,8 +350,8 @@ def calc_params_drilling(self): "ReferencePlaneID": cross_face_index, "StartX": StartX, "StartY": StartY, - "Angle": Angle, - "Inclination": Inclination, + "Angle": float(Angle), + "Inclination": float(Inclination), "Diameter": self.drill_diameter, "DepthLimited": "no", "Depth": 0.0 diff --git a/src/compas_timber/connections/french_ridge_lap.py b/src/compas_timber/connections/french_ridge_lap.py index 61a41843e7..b359e035e7 100644 --- a/src/compas_timber/connections/french_ridge_lap.py +++ b/src/compas_timber/connections/french_ridge_lap.py @@ -38,11 +38,11 @@ class FrenchRidgeLapJoint(Joint): SUPPORTED_TOPOLOGY = JointTopology.TOPO_L - def __init__(self, beam_a=None, beam_b=None, drill_diameter=0, **kwargs): + def __init__(self, beam_a=None, beam_b=None, drill_diameter=0.0, **kwargs): super(FrenchRidgeLapJoint, self).__init__(beams=(beam_a, beam_b), **kwargs) self.beam_a = beam_a self.beam_b = beam_b - self.drill_diameter = drill_diameter + self.drill_diameter = float(drill_diameter) 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.reference_face_indices = {} diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_drilling.py b/src/compas_timber/fabrication/btlx_processes/btlx_drilling.py index b5dfcf67c8..a32e3e72a4 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_drilling.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_drilling.py @@ -23,13 +23,13 @@ class BTLxDrilling(object): def __init__(self, param_dict, joint_name=None, **kwargs): # joint_name replace by "feature_name"? self.apply_process = True self.reference_plane_id = param_dict["ReferencePlaneID"] - self.start_x = param_dict["StartX"] - self.start_y = param_dict["StartY"] - self.angle = param_dict["Angle"] - self.inclination = param_dict["Inclination"] + self.start_x = float(param_dict["StartX"]) + self.start_y = float(param_dict["StartY"]) + self.angle = float(param_dict["Angle"]) + self.inclination = float(param_dict["Inclination"]) self.depth_limited = param_dict["DepthLimited"] - self.depth = param_dict["Depth"] - self.diameter = param_dict["Diameter"] + self.depth = float(param_dict["Depth"]) + self.diameter = float(param_dict["Diameter"]) for key, value in param_dict.items(): setattr(self, key, value) diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_french_ridge_lap.py b/src/compas_timber/fabrication/btlx_processes/btlx_french_ridge_lap.py index 37a3abef4f..71f895ce14 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_french_ridge_lap.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_french_ridge_lap.py @@ -63,7 +63,7 @@ def __init__(self, part, joint, is_top, drill_diameter=0.0): self.orientation = joint.ends[str(part.key)] self._ref_edge = True self._drill_hole = True if drill_diameter > 0 else False - self.drill_hole_diameter = drill_diameter + self.drill_hole_diameter = float(drill_diameter) self.ref_face_index = self.joint.reference_face_indices[str(self.beam.key)] self.ref_face = self.part.reference_surface_planes(str(self.ref_face_index)) @@ -137,8 +137,8 @@ def get_params(self): self.angle_rad = abs(self.angle_rad) self.startX = abs(self.beam.width / math.tan(self.angle_rad)) - print(self.angle_lines, "angle_lines") - print(self.startX) + # print(self.angle_lines, "angle_lines") + # print(self.startX) if self.angle_lines < math.pi / 2: self.startX = 0.0 @@ -158,15 +158,15 @@ def get_params(self): else: self.startX = self.beam.blank_length + self.startX - print("orientation: ", self.orientation, " angle: ", self.angle_rad, " start: ", self.startX) - print( - "ref_edge: ", - self.ref_edge, - " drill_hole: ", - self.drill_hole, - " drill_hole_diameter: ", - self.drill_hole_diameter, - ) + # print("orientation: ", self.orientation, " angle: ", self.angle_rad, " start: ", self.startX) + # print( + # "ref_edge: ", + # self.ref_edge, + # " drill_hole: ", + # self.drill_hole, + # " drill_hole_diameter: ", + # self.drill_hole_diameter, + # ) @classmethod def create_process(cls, part, joint, is_top, drill_diameter): diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index eefb8dc6a8..96da866ddc 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -48,8 +48,6 @@ def apply_processings(cls, joint, parts): cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, "T-Butt Joint")) if joint.drill_diameter > 0: - print("drill_diameter", joint.btlx_drilling_params_cross) - print(str(joint.btlx_params_cross["ReferencePlaneID"])) joint.btlx_drilling_params_cross["ReferencePlaneID"] = str(joint.btlx_params_cross["ReferencePlaneID"]) cross_part.processings.append(BTLxDrilling.create_process(joint.btlx_drilling_params_cross, "T-Butt Joint")) diff --git a/src/compas_timber/parts/beam.py b/src/compas_timber/parts/beam.py index 5a674c0365..af7fc00eff 100644 --- a/src/compas_timber/parts/beam.py +++ b/src/compas_timber/parts/beam.py @@ -95,8 +95,8 @@ def __init__(self, frame, length, width, height, **kwargs): self.features = [] self._blank_extensions = {} self.intersections = [] - self.attributes["airModule_no"] = [] ### TODO names to be defined - self.attributes["assemblyModule_no"] = [] ### TODO names to be defined + self.attributes["air_no"] = [] ### TODO names to be defined + self.attributes["assembly_no"] = [] ### TODO names to be defined self.attributes["beam_no"] = [] ### TODO names to be defined @property From 7db914452283c587ca3678db19360c49b4a86b7d Mon Sep 17 00:00:00 2001 From: paulocinco Date: Wed, 8 May 2024 09:31:50 +0200 Subject: [PATCH 57/82] correction drilling params and stepjoint (doublecut) WIP --- src/compas_timber/connections/butt_joint.py | 135 +++++++++++++++++- .../connections/french_ridge_lap.py | 2 +- src/compas_timber/connections/t_butt.py | 10 +- .../joint_factories/t_butt_factory.py | 6 +- 4 files changed, 144 insertions(+), 9 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 336efc00ff..7eb0a37a1a 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -50,7 +50,7 @@ class ButtJoint(Joint): """ - def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, drill_diameter=0, birdsmouth=False, **kwargs): + def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, drill_diameter=0, birdsmouth=False, stepjoint=False, **kwargs): super(ButtJoint, self).__init__(**kwargs) self.main_beam = main_beam self.cross_beam = cross_beam @@ -59,9 +59,12 @@ def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, drill_diameter self.mill_depth = mill_depth self.drill_diameter = drill_diameter self.birdsmouth = birdsmouth + self.stepjoint = stepjoint self.btlx_params_main = {} self.btlx_params_cross = {} self.btlx_drilling_params_cross = {} + self.btlx_stepjoint_params_main = {} + self.btlx_params_stepjoint_main = {} self.features = [] self.test = [] @@ -278,6 +281,15 @@ def calc_params_birdsmouth(self): Inclination1 = angle_vectors(ref_frame.zaxis, plane1.normal, deg=True) Inclination2 = angle_vectors(ref_frame.zaxis, plane2.normal, deg=True) + print "orientation: ", self.ends[str(self.main_beam.key)] + print "StartX: ", StartX + print "StartY: ", StartY + print "Angle1: ", Angle1 + print "Inclination1: ", Inclination1 + print "Angle2: ", Angle2 + print "Inclination2: ", Inclination2 + print "ReferencePlaneID: ", self.main_face_index + self.btlx_params_main = { "Orientation": self.ends[str(self.main_beam.key)], "StartX": StartX, @@ -343,15 +355,26 @@ def calc_params_drilling(self): center_line_vec = Vector.from_start_end(start_point, line_point) projected_vec = Vector.from_start_end(start_point, projected_point) Angle = 180 - math.degrees(ref_frame.xaxis.angle_signed(projected_vec, ref_frame.zaxis)) - Inclination = projected_vec.angle(center_line_vec, True) + inclination = projected_vec.angle(center_line_vec, True) + if inclination == 0: + Inclination = 90.0 + else: + Inclination = inclination + + print "ReferencePlaneID: ", cross_face_index + print "StartX: ", StartX + print "StartY: ", StartY + print "Angle: ", Angle + print "Inclination: ", Inclination + print "Diameter: ", self.drill_diameter self.btlx_drilling_params_cross = { "ReferencePlaneID": cross_face_index, "StartX": StartX, "StartY": StartY, "Angle": Angle, - "Inclination": Inclination, + "Inclination": float(Inclination), "Diameter": self.drill_diameter, "DepthLimited": "no", "Depth": 0.0 @@ -364,3 +387,109 @@ def calc_params_drilling(self): normal_centerline_angle = 180-math.degrees(ref_frame.zaxis.angle(self.main_beam.centerline.direction)) length = self.cross_beam.width/(math.cos(math.radians(normal_centerline_angle))) return line, self.drill_diameter, length*3 + + def calc_params_stepjoint(self): + """ + Calculate the parameters for a step joint based on a Double Cut BTLx process. + + Parameters: + ---------- + joint (object): The joint object. + main_part (object): The main part object. + cross_part (object): The cross part object. + StepDepth (float): The depth of the step joint. + + Returns: + ---------- + dict: A dictionary containing the calculated parameters for the step joint (double cut process) + + """ + + # only valid for Heel Step Joint at 15mm depth + StepDepth = 0.0 + HeelDepth = 15.0 + StepShape = "heel" + Tenon = "no" + TenonWidth = 0.0 + TenonHeight = 0.0 + + # finding face facing the cross beam the least + ref_face_id, ref_face = self.get_face_most_ortho_to_beam(self.cross_beam, self.main_beam, ignore_ends=True) + print "ref_face_id: ", ref_face_id + + + # face_dict = joint._beam_side_incidence(cross_part.beam, main_part.beam, ignore_ends=True) + # ref_frame_id = min(face_dict, key=face_dict.get) + # ref_frame = main_part.beam.faces[ref_frame_id] + + Inclination1 = 90.0 + Inclination2 = 90.0 + + + # finding the inclination of the strut based on the two centerlines + StrutInclination = math.degrees(self.cross_beam.centerline.direction.angle(self.main_beam.centerline.direction)) + # print (StrutInclination) + angle1 = (180 - StrutInclination)/2 + + # find StartX + buried_depth = math.sin(math.radians(90-StrutInclination))*self.main_beam.width/2 + blank_vert_depth = self.cross_beam.width/2 - buried_depth + blank_edge_depth = abs(blank_vert_depth)/math.sin(math.radians(StrutInclination)) + # print blank_edge_depth + startx = blank_edge_depth/2 + starty = self.main_beam.width/4 + + outside_length = self.main_beam.width/math.tan(math.radians(StrutInclination)) + x_main_cutting_face = outside_length + blank_edge_depth + + vec_angle2 = Vector.from_start_end(Point(startx, self.cross_beam.width - starty), Point(x_main_cutting_face, 0)) + vec_xaxis = Vector.from_start_end(Point(startx, self.cross_beam.width - starty), Point(0, self.cross_beam.width - starty)) + angle2 = vec_xaxis.angle(vec_angle2, True) + + if self.ends[str(self.main_beam.key)] == "start": + StartX = startx + StartY = self.main_beam.width - starty + Angle1 = angle2 + Angle2 = angle1 + else: + StartX = self.main_beam.blank_length - startx + StartY = starty + Angle1 = 180 - angle1 + Angle2 = 180 - angle2 + + self.bm_sub_volume = Brep.from_box(self.cross_beam.blank) + print "orientation: ", self.ends[str(self.main_beam.key)] + print "StartX: ", StartX + print "StartY: ", StartY + print "Angle1: ", Angle1 + print "Inclination1: ", Inclination1 + print "Angle2: ", Angle2 + print "Inclination2: ", Inclination2 + print "ReferencePlaneID: ", ref_face_id + + + self.btlx_stepjoint_params_main = { + "Orientation": self.ends[str(self.main_beam.key)], + "StartX": StartX, + "StrutInclination": StrutInclination, + "StepDepth": StepDepth, + "HeelDepth": HeelDepth, + "StepShape": StepShape, + "Tenon": Tenon, + "TenonWidth": TenonWidth, + "TenonHeight": TenonHeight, + "ReferencePlaneID": ref_face_id + } + + self.btlx_params_stepjoint_main = { + "Orientation": self.ends[str(self.main_beam.key)], + "StartX": float(StartX), + "StartY": float(StartY), + "Angle1": float(Angle1), + "Inclination1": float(Inclination1), + "Angle2": Angle2, + "Inclination2": Inclination2, + "ReferencePlaneID": ref_face_id, + } + + return True diff --git a/src/compas_timber/connections/french_ridge_lap.py b/src/compas_timber/connections/french_ridge_lap.py index 61a41843e7..46f458d0cb 100644 --- a/src/compas_timber/connections/french_ridge_lap.py +++ b/src/compas_timber/connections/french_ridge_lap.py @@ -38,7 +38,7 @@ class FrenchRidgeLapJoint(Joint): SUPPORTED_TOPOLOGY = JointTopology.TOPO_L - def __init__(self, beam_a=None, beam_b=None, drill_diameter=0, **kwargs): + def __init__(self, beam_a=None, beam_b=None, drill_diameter=0.0, **kwargs): super(FrenchRidgeLapJoint, self).__init__(beams=(beam_a, beam_b), **kwargs) self.beam_a = beam_a self.beam_b = beam_b diff --git a/src/compas_timber/connections/t_butt.py b/src/compas_timber/connections/t_butt.py index ff5ccc56ec..db55d02943 100644 --- a/src/compas_timber/connections/t_butt.py +++ b/src/compas_timber/connections/t_butt.py @@ -35,8 +35,8 @@ class TButtJoint(ButtJoint): SUPPORTED_TOPOLOGY = JointTopology.TOPO_T - def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, drill_diameter=0, birdsmouth=False, **kwargs): - super(TButtJoint, self).__init__(main_beam, cross_beam, mill_depth, drill_diameter, birdsmouth, **kwargs) + def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, drill_diameter=0, birdsmouth=False, stepjoint=False, **kwargs): + super(TButtJoint, self).__init__(main_beam, cross_beam, mill_depth, drill_diameter, birdsmouth, stepjoint, **kwargs) def add_extensions(self): @@ -95,7 +95,6 @@ def add_features(self): if self.calc_params_birdsmouth(): self.main_beam.add_features(BrepSubtraction(self.bm_sub_volume)) self.features.append(BrepSubtraction(self.bm_sub_volume)) - else: do_jack = True if do_jack: @@ -104,3 +103,8 @@ def add_features(self): if self.drill_diameter > 0: self.cross_beam.add_features(DrillFeature(*self.calc_params_drilling())) self.features.append(DrillFeature(*self.calc_params_drilling())) + if self.stepjoint: + if self.calc_params_stepjoint(): + self.main_beam.add_features(BrepSubtraction(self.bm_sub_volume))#not correct + self.features.append(BrepSubtraction(self.bm_sub_volume))#not correct + diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index eefb8dc6a8..c01a88667f 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -4,6 +4,7 @@ from compas_timber.fabrication.btlx_processes.btlx_drilling import BTLxDrilling from compas_timber.fabrication.btlx_processes.btlx_lap import BTLxLap from compas_timber.fabrication.btlx_processes.btlx_double_cut import BTLxDoubleCut +from compas_timber.fabrication.btlx_processes.btlx_stepjoint import BTLxStepJoint class TButtFactory(object): @@ -48,10 +49,11 @@ def apply_processings(cls, joint, parts): cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, "T-Butt Joint")) if joint.drill_diameter > 0: - print("drill_diameter", joint.btlx_drilling_params_cross) - print(str(joint.btlx_params_cross["ReferencePlaneID"])) joint.btlx_drilling_params_cross["ReferencePlaneID"] = str(joint.btlx_params_cross["ReferencePlaneID"]) cross_part.processings.append(BTLxDrilling.create_process(joint.btlx_drilling_params_cross, "T-Butt Joint")) + if joint.stepjoint: + joint.btlx_params_stepjoint_main["ReferencePlaneID"] = str(4) + main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_stepjoint_main, "T-Butt Joint")) BTLx.register_joint(TButtJoint, TButtFactory) From 59ffe215e853af0bc2bb89f6ec736f3098c50aeb Mon Sep 17 00:00:00 2001 From: paulocinco Date: Wed, 8 May 2024 13:37:37 +0200 Subject: [PATCH 58/82] small corrections --- src/compas_timber/connections/butt_joint.py | 56 +++++++++++-------- .../joint_factories/t_butt_factory.py | 9 ++- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index c20f254049..898bcac17d 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -361,14 +361,6 @@ def calc_params_drilling(self): else: Inclination = inclination - - print "ReferencePlaneID: ", cross_face_index - print "StartX: ", StartX - print "StartY: ", StartY - print "Angle: ", Angle - print "Inclination: ", Inclination - print "Diameter: ", self.drill_diameter - self.btlx_drilling_params_cross = { "ReferencePlaneID": cross_face_index, "StartX": StartX, @@ -385,7 +377,7 @@ def calc_params_drilling(self): line = Line(start_point, line_point) line.start.translate(-line.vector) normal_centerline_angle = 180-math.degrees(ref_frame.zaxis.angle(self.main_beam.centerline.direction)) - length = self.cross_beam.width/(math.cos(math.radians(normal_centerline_angle))) + length = abs(self.cross_beam.width/(math.cos(math.radians(normal_centerline_angle)))) return line, self.drill_diameter, length*3 def calc_params_stepjoint(self): @@ -428,34 +420,52 @@ def calc_params_stepjoint(self): # finding the inclination of the strut based on the two centerlines StrutInclination = math.degrees(self.cross_beam.centerline.direction.angle(self.main_beam.centerline.direction)) + print "StrutInclination: ", StrutInclination # print (StrutInclination) - angle1 = (180 - StrutInclination)/2 + if StrutInclination < 90: + angle1 = (180 - StrutInclination)/2 + strutinclination = StrutInclination + else: + angle1 = StrutInclination/2 + strutinclination = 180 - StrutInclination # find StartX - buried_depth = math.sin(math.radians(90-StrutInclination))*self.main_beam.width/2 + buried_depth = math.sin(math.radians(90-strutinclination))*self.main_beam.width/2 blank_vert_depth = self.cross_beam.width/2 - buried_depth - blank_edge_depth = abs(blank_vert_depth)/math.sin(math.radians(StrutInclination)) + blank_edge_depth = abs(blank_vert_depth)/math.sin(math.radians(strutinclination)) # print blank_edge_depth startx = blank_edge_depth/2 starty = self.main_beam.width/4 - outside_length = self.main_beam.width/math.tan(math.radians(StrutInclination)) + outside_length = self.main_beam.width/math.tan(math.radians(strutinclination)) x_main_cutting_face = outside_length + blank_edge_depth vec_angle2 = Vector.from_start_end(Point(startx, self.cross_beam.width - starty), Point(x_main_cutting_face, 0)) vec_xaxis = Vector.from_start_end(Point(startx, self.cross_beam.width - starty), Point(0, self.cross_beam.width - starty)) angle2 = vec_xaxis.angle(vec_angle2, True) - - if self.ends[str(self.main_beam.key)] == "start": - StartX = startx - StartY = self.main_beam.width - starty - Angle1 = angle2 - Angle2 = angle1 + print StrutInclination + if StrutInclination < 90: + if self.ends[str(self.main_beam.key)] == "start": + StartX = startx + StartY = self.main_beam.width - starty + Angle1 = angle2 + Angle2 = angle1 + else: + StartX = self.main_beam.blank_length - startx + StartY = starty + Angle1 = 180 - angle1 + Angle2 = 180 - angle2 else: - StartX = self.main_beam.blank_length - startx - StartY = starty - Angle1 = 180 - angle1 - Angle2 = 180 - angle2 + if self.ends[str(self.main_beam.key)] == "start": + StartX = startx + StartY = starty + Angle1 = 180-angle1 + Angle2 = 180-angle2 + else: + StartX = self.main_beam.blank_length - startx + StartY = self.main_beam.width - starty + Angle1 = angle2 + Angle2 = angle1 self.bm_sub_volume = Brep.from_box(self.cross_beam.blank) print "orientation: ", self.ends[str(self.main_beam.key)] diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index c01a88667f..92ed6989f1 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -39,6 +39,9 @@ def apply_processings(cls, joint, parts): ref_face = main_part.beam.faces[joint.main_face_index] joint.btlx_params_main["ReferencePlaneID"] = str(main_part.reference_surface_from_beam_face(ref_face)) main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_main, "T-Butt Joint")) + elif joint.stepjoint: + joint.btlx_params_stepjoint_main["ReferencePlaneID"] = str(4) + main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_stepjoint_main, "T-Butt Joint")) else: main_part.processings.append(BTLxJackCut.create_process(main_part, cut_plane, "T-Butt Joint")) @@ -49,11 +52,7 @@ def apply_processings(cls, joint, parts): cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, "T-Butt Joint")) if joint.drill_diameter > 0: - joint.btlx_drilling_params_cross["ReferencePlaneID"] = str(joint.btlx_params_cross["ReferencePlaneID"]) + joint.btlx_drilling_params_cross["ReferencePlaneID"] = str(cross_part.reference_surface_from_beam_face(ref_plane)) cross_part.processings.append(BTLxDrilling.create_process(joint.btlx_drilling_params_cross, "T-Butt Joint")) - if joint.stepjoint: - joint.btlx_params_stepjoint_main["ReferencePlaneID"] = str(4) - main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_stepjoint_main, "T-Butt Joint")) - BTLx.register_joint(TButtJoint, TButtFactory) From b5ea233c3e3babfb8f05f4c555f402c6650c2ec3 Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 9 May 2024 12:01:57 +0200 Subject: [PATCH 59/82] id attribute --- src/compas_timber/fabrication/btlx.py | 1 + .../fabrication/joint_factories/text_factory.py | 5 +++-- src/compas_timber/parts/beam.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index eaa6ced74f..72c7c04e7c 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -191,6 +191,7 @@ def __init__(self, beam): ) # I used long_edge[2] because it is in Y and Z negative. Using that as reference puts the beam entirely in positive coordinates. self.blank_length = beam.blank_length # self.intersections = beam.attributes["intersections"] + self.ID = beam.attributes["ID"] self.intersections = beam.intersections self._reference_surfaces = [] self.processings = [] diff --git a/src/compas_timber/fabrication/joint_factories/text_factory.py b/src/compas_timber/fabrication/joint_factories/text_factory.py index e4bc5f09ed..d6086ddfab 100644 --- a/src/compas_timber/fabrication/joint_factories/text_factory.py +++ b/src/compas_timber/fabrication/joint_factories/text_factory.py @@ -40,8 +40,8 @@ def get_text_engraving_params(part): "AlignmentHorizontal": "center", "AlignmentMultiline": "center", "TextHeight": 20.0, - # "Text": part.beam.attributes["airModule_no"] - "Text": "AM01_05" + "Text": part.ID + # "Text": "AM01_05" } @classmethod @@ -68,6 +68,7 @@ def apply_processings(cls, part): ref_plane_id = "1" params_dict = TextFactory.get_text_engraving_params(part) params_dict["ReferencePlaneID"] = ref_plane_id + print(params_dict["Text"]) part.processings.append(BTLxText.create_process(params_dict, "Text")) BTLx.register_feature("TextID", TextFactory) diff --git a/src/compas_timber/parts/beam.py b/src/compas_timber/parts/beam.py index af7fc00eff..e2ad2375b5 100644 --- a/src/compas_timber/parts/beam.py +++ b/src/compas_timber/parts/beam.py @@ -95,7 +95,7 @@ def __init__(self, frame, length, width, height, **kwargs): self.features = [] self._blank_extensions = {} self.intersections = [] - self.attributes["air_no"] = [] ### TODO names to be defined + self.attributes["ID"] = [] ### TODO names to be defined self.attributes["assembly_no"] = [] ### TODO names to be defined self.attributes["beam_no"] = [] ### TODO names to be defined From 5daa9de96152b0acf2df968c9d8b848043dc85be Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 9 May 2024 12:11:09 +0200 Subject: [PATCH 60/82] call intersection_parameters solver in gh component --- src/compas_timber/ghpython/components/CT_Assembly/code.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compas_timber/ghpython/components/CT_Assembly/code.py b/src/compas_timber/ghpython/components/CT_Assembly/code.py index 5ea8fd18c1..17871e716d 100644 --- a/src/compas_timber/ghpython/components/CT_Assembly/code.py +++ b/src/compas_timber/ghpython/components/CT_Assembly/code.py @@ -151,6 +151,7 @@ def RunScript(self, Beams, JointRules, Features, MaxDistance, CreateGeometry): self._beam_map[id(beam)] = c_beam beams = Assembly.beams + solver.find_intersection_parameters(Assembly.beams) joints = self.get_joints_from_rules(beams, JointRules, topologies) if joints: From 11bb6a642c9aae1f1e3cc4a47e89940c3faf05ce Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 9 May 2024 14:42:25 +0200 Subject: [PATCH 61/82] remove print statements (silent error when compiling) --- src/compas_timber/connections/butt_joint.py | 22 --------------------- 1 file changed, 22 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 898bcac17d..39db3b4226 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -281,15 +281,6 @@ def calc_params_birdsmouth(self): Inclination1 = angle_vectors(ref_frame.zaxis, plane1.normal, deg=True) Inclination2 = angle_vectors(ref_frame.zaxis, plane2.normal, deg=True) - print "orientation: ", self.ends[str(self.main_beam.key)] - print "StartX: ", StartX - print "StartY: ", StartY - print "Angle1: ", Angle1 - print "Inclination1: ", Inclination1 - print "Angle2: ", Angle2 - print "Inclination2: ", Inclination2 - print "ReferencePlaneID: ", self.main_face_index - self.btlx_params_main = { "Orientation": self.ends[str(self.main_beam.key)], "StartX": StartX, @@ -407,7 +398,6 @@ def calc_params_stepjoint(self): # finding face facing the cross beam the least ref_face_id, ref_face = self.get_face_most_ortho_to_beam(self.cross_beam, self.main_beam, ignore_ends=True) - print "ref_face_id: ", ref_face_id # face_dict = joint._beam_side_incidence(cross_part.beam, main_part.beam, ignore_ends=True) @@ -420,8 +410,6 @@ def calc_params_stepjoint(self): # finding the inclination of the strut based on the two centerlines StrutInclination = math.degrees(self.cross_beam.centerline.direction.angle(self.main_beam.centerline.direction)) - print "StrutInclination: ", StrutInclination - # print (StrutInclination) if StrutInclination < 90: angle1 = (180 - StrutInclination)/2 strutinclination = StrutInclination @@ -443,7 +431,6 @@ def calc_params_stepjoint(self): vec_angle2 = Vector.from_start_end(Point(startx, self.cross_beam.width - starty), Point(x_main_cutting_face, 0)) vec_xaxis = Vector.from_start_end(Point(startx, self.cross_beam.width - starty), Point(0, self.cross_beam.width - starty)) angle2 = vec_xaxis.angle(vec_angle2, True) - print StrutInclination if StrutInclination < 90: if self.ends[str(self.main_beam.key)] == "start": StartX = startx @@ -468,15 +455,6 @@ def calc_params_stepjoint(self): Angle2 = angle1 self.bm_sub_volume = Brep.from_box(self.cross_beam.blank) - print "orientation: ", self.ends[str(self.main_beam.key)] - print "StartX: ", StartX - print "StartY: ", StartY - print "Angle1: ", Angle1 - print "Inclination1: ", Inclination1 - print "Angle2: ", Angle2 - print "Inclination2: ", Inclination2 - print "ReferencePlaneID: ", ref_face_id - self.btlx_stepjoint_params_main = { "Orientation": self.ends[str(self.main_beam.key)], From 8ae13eb83c170faf35a0ddf79ba78a33b0bbe105 Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 9 May 2024 14:42:45 +0200 Subject: [PATCH 62/82] delete duplicate doublecut (old) --- .../btlx_processes/btlx_doublecut.py | 82 ------------------- 1 file changed, 82 deletions(-) delete mode 100644 src/compas_timber/fabrication/btlx_processes/btlx_doublecut.py diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_doublecut.py b/src/compas_timber/fabrication/btlx_processes/btlx_doublecut.py deleted file mode 100644 index 8972cc4cce..0000000000 --- a/src/compas_timber/fabrication/btlx_processes/btlx_doublecut.py +++ /dev/null @@ -1,82 +0,0 @@ -from collections import OrderedDict -from compas_timber.fabrication import BTLx -from compas_timber.fabrication import BTLxProcess - - -class BTLxDoubleCut(object): - """ - Represents a double cut process for timber fabrication. - - Parameters - ---------- - param_dict : dict - A dictionary containing the parameters for the BTLx lap process. - joint_name : str - The name of the joint. If not provided, the default name is "lap". - kwargs : dict - Additional keyword arguments to be added to the object. - - """ - - PROCESS_TYPE = "DoubleCut" - - def __init__(self, param_dict, joint_name=None, **kwargs): - self.apply_process = True - self.reference_plane_id = param_dict["ReferencePlaneID"] - self.orientation = param_dict["Orientation"] - self.start_x = param_dict["StartX"] - self.start_y = param_dict["StartY"] - self.angle1 = param_dict["Angle1"] - self.inclination1 = param_dict["Inclination1"] - self.angle2 = param_dict["Angle2"] - self.inclination2 = param_dict["Inclination2"] - - for key, value in param_dict.items(): - setattr(self, key, value) - - for key, value in kwargs.items(): - setattr(self, key, value) - - if joint_name: - self.name = joint_name - else: - self.name = "lap" - - @property - def header_attributes(self): - """the following attributes are required for all processes, but the keys and values of header_attributes are process specific.""" - return { - "Name": self.name, - "Process": "yes", - "Priority": "0", - "ProcessID": "0", - "ReferencePlaneID": str(self.reference_plane_id + 1), - } - - @property - def process_params(self): - """This property is required for all process types. It returns a dict with the geometric parameters to fabricate the joint.""" - - if self.apply_process: - """the following attributes are specific to Lap""" - od = OrderedDict( - [ - ("Orientation", str(self.orientation)), - ("StartX", "{:.{prec}f}".format(self.start_x, prec=BTLx.POINT_PRECISION)), - ("StartY", "{:.{prec}f}".format(self.start_y, prec=BTLx.POINT_PRECISION)), - ("Angle1", "{:.{prec}f}".format(self.angle1, prec=BTLx.ANGLE_PRECISION)), - ("Inclination1", "{:.{prec}f}".format(self.inclination1, prec=BTLx.ANGLE_PRECISION)), - ("Angle2", "{:.{prec}f}".format(self.angle2, prec=BTLx.ANGLE_PRECISION)), - ("Inclination2", "{:.{prec}f}".format(self.inclination2, prec=BTLx.ANGLE_PRECISION)), - ] - ) - print("param dict", od) - return od - else: - return None - - @classmethod - def create_process(cls, param_dict, joint_name=None, **kwargs): - """Creates a lap process from a dictionary of parameters.""" - lap = BTLxDoubleCut(param_dict, joint_name, **kwargs) - return BTLxProcess(BTLxDoubleCut.PROCESS_TYPE, lap.header_attributes, lap.process_params) From 382697529bd3dc2ae914a960c211651d67e605db Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 10 May 2024 15:52:27 +0200 Subject: [PATCH 63/82] use default params for positioning --- src/compas_timber/connections/solver.py | 2 +- .../joint_factories/text_factory.py | 41 +++++++++++++++---- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/compas_timber/connections/solver.py b/src/compas_timber/connections/solver.py index d77582776e..45057e2720 100644 --- a/src/compas_timber/connections/solver.py +++ b/src/compas_timber/connections/solver.py @@ -242,7 +242,7 @@ def _parameter_on_line(point, line): # Vector from the start of the line segment to the given point point_vector = [(point.x - line.start.x), (point.y - line.start.y), (point.z - line.start.z)] # Calculate the parameter (t) using dot product - t = dot_vectors(point_vector, line.vector) / dot_vectors(line.vector, line.vector) + t = dot_vectors(point_vector, line.vector) / dot_vectors(line.vector, line.vector) #denomenator is the length^2 of the vector return t @staticmethod diff --git a/src/compas_timber/fabrication/joint_factories/text_factory.py b/src/compas_timber/fabrication/joint_factories/text_factory.py index d6086ddfab..6276ed679a 100644 --- a/src/compas_timber/fabrication/joint_factories/text_factory.py +++ b/src/compas_timber/fabrication/joint_factories/text_factory.py @@ -1,3 +1,5 @@ +from compas_timber.parts import BrepSubtraction + from compas_timber.fabrication import BTLx from compas_timber.fabrication import BTLxText @@ -24,7 +26,7 @@ def get_engraving_position(part): max_length = seg_length optimal_parameter = (all_intersections[i] + all_intersections[i+1]) / 2 - optimal_parameter = 0.5 if optimal_parameter in {0, 1} else optimal_parameter #//TODO: this is a temporal fix + optimal_parameter = 0.5 if optimal_parameter in {0, 1} else optimal_parameter optimal_position = optimal_parameter * part.length return optimal_position @@ -33,17 +35,41 @@ def get_text_engraving_params(part): """Returns the text engraving parameters for the BTLx part.""" return { "ReferencePlaneID": 1, #default face - "StartX": TextFactory.get_engraving_position(part), - "StartY": part.width/2, #always set it to the middle of the beam + "StartX": TextFactory.get_engraving_position(part) - (7*20.0)/2, #7=number of characters in the ID + "StartY": part.width/2 - 10., #manually center it since text is not centered in easybeam "Angle": 0.0, - "AlignmentVertical": "center", - "AlignmentHorizontal": "center", - "AlignmentMultiline": "center", + "AlignmentVertical": "bottom", #default(bottom) in easybeam + "AlignmentHorizontal": "left", #default(left) in easybeam + "AlignmentMultiline": "left", #default(left) in easybeam "TextHeight": 20.0, "Text": part.ID - # "Text": "AM01_05" } + def add_features(self): + """Adds the trimming plane to the main beam (no features for the cross beam). + + This method is automatically called when joint is created by the call to `Joint.create()`. + + """ + pass + # assert self.main_beam and self.cross_beam # should never happen + # if self.features: + # self.main_beam.remove_features(self.features) + # cutting_plane = None + # try: + # cutting_plane = self.get_main_cutting_plane()[0] + # except AttributeError as ae: + # raise BeamJoinningError(beams=self.beams, joint=self, debug_info=str(ae), debug_geometries=[cutting_plane]) + # except Exception as ex: + # raise BeamJoinningError(beams=self.beams, joint=self, debug_info=str(ex)) + + # self.features = [] + # if self.birdsmouth: + # if self.calc_params_birdsmouth(): + # self.main_beam.add_features(BrepSubtraction(self.bm_sub_volume)) + # self.features.append(BrepSubtraction(self.bm_sub_volume)) + + @classmethod def apply_processings(cls, part): """ @@ -68,7 +94,6 @@ def apply_processings(cls, part): ref_plane_id = "1" params_dict = TextFactory.get_text_engraving_params(part) params_dict["ReferencePlaneID"] = ref_plane_id - print(params_dict["Text"]) part.processings.append(BTLxText.create_process(params_dict, "Text")) BTLx.register_feature("TextID", TextFactory) From a59639b958fe80a37d221f30f63320eed49db47a Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 10 May 2024 15:53:00 +0200 Subject: [PATCH 64/82] register features only if ID is fed --- src/compas_timber/fabrication/btlx.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index 72c7c04e7c..fede778539 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -92,8 +92,9 @@ def process_assembly(self): factory_type = self.REGISTERED_JOINTS.get(str(type(joint))) factory_type.apply_processings(joint, self.parts) for part in self.parts.values(): - factory_type = self.REGISTERED_FEATURES.get("TextID") - factory_type.apply_processings(part) + if part.ID: + factory_type = self.REGISTERED_FEATURES.get("TextID") + factory_type.apply_processings(part) @classmethod def register_joint(cls, joint_type, joint_factory): @@ -190,7 +191,6 @@ def __init__(self, beam): beam.frame.yaxis, ) # I used long_edge[2] because it is in Y and Z negative. Using that as reference puts the beam entirely in positive coordinates. self.blank_length = beam.blank_length - # self.intersections = beam.attributes["intersections"] self.ID = beam.attributes["ID"] self.intersections = beam.intersections self._reference_surfaces = [] From dd3136b42cc5323a9694154c567ee3e40041f4ba Mon Sep 17 00:00:00 2001 From: papachap Date: Sun, 12 May 2024 18:31:57 +0200 Subject: [PATCH 65/82] adjust pocket to 61.5mm --- src/compas_timber/fabrication/joint_factories/t_butt_factory.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index 92ed6989f1..c8c607a431 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -47,6 +47,8 @@ def apply_processings(cls, joint, parts): joint.btlx_params_cross["reference_plane_id"] = str(cross_part.reference_surface_from_beam_face(ref_plane)) if joint.mill_depth > 0: + if joint.btlx_params_cross["length"] <= 60: + joint.btlx_params_cross["length"] = 61.5 joint.btlx_params_cross["machining_limits"] = {"FaceLimitedFront": "no", "FaceLimitedBack": "no"} joint.btlx_params_cross["ReferencePlaneID"] = str(cross_part.reference_surface_from_beam_face(ref_plane)) cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, "T-Butt Joint")) From e9baf740274c1ddc7e57925f97873046eb655cbe Mon Sep 17 00:00:00 2001 From: papachap Date: Sun, 12 May 2024 19:59:24 +0200 Subject: [PATCH 66/82] 61->61.5 --- src/compas_timber/fabrication/joint_factories/t_butt_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index c8c607a431..96cfc74785 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -47,7 +47,7 @@ def apply_processings(cls, joint, parts): joint.btlx_params_cross["reference_plane_id"] = str(cross_part.reference_surface_from_beam_face(ref_plane)) if joint.mill_depth > 0: - if joint.btlx_params_cross["length"] <= 60: + if joint.btlx_params_cross["length"] <= 61: joint.btlx_params_cross["length"] = 61.5 joint.btlx_params_cross["machining_limits"] = {"FaceLimitedFront": "no", "FaceLimitedBack": "no"} joint.btlx_params_cross["ReferencePlaneID"] = str(cross_part.reference_surface_from_beam_face(ref_plane)) From 00236b04a3b30347b1114a46e48b16f2759339e3 Mon Sep 17 00:00:00 2001 From: obucklin Date: Mon, 13 May 2024 20:37:00 +0200 Subject: [PATCH 67/82] init branch --- src/compas_timber/connections/l_halflap.py | 6 +- src/compas_timber/fabrication/btlx.py | 76 +++++++++++++++---- .../joint_factories/l_butt_factory.py | 6 +- .../joint_factories/l_miter_factory.py | 4 +- .../joint_factories/t_butt_factory.py | 6 +- 5 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/compas_timber/connections/l_halflap.py b/src/compas_timber/connections/l_halflap.py index e5dc603517..172d3d1578 100644 --- a/src/compas_timber/connections/l_halflap.py +++ b/src/compas_timber/connections/l_halflap.py @@ -1,3 +1,4 @@ +from math import e from compas.geometry import Frame from compas_timber.parts import CutFeature from compas_timber.parts import MillVolume @@ -60,16 +61,17 @@ def add_extensions(self): assert self.main_beam and self.cross_beam extension_tolerance = 0.01 # TODO: this should be proportional to the unit used - extension_plane_main = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True)[1] + extension_plane_main = self.get_face_most_towards_beam(self.main_beam, self.cross_beam, ignore_ends=True)[1] start_main, end_main = self.main_beam.extension_to_plane(extension_plane_main) self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.key) - extension_plane_cross = self.get_face_most_ortho_to_beam(self.cross_beam, self.main_beam, ignore_ends=True)[1] + extension_plane_cross = self.get_face_most_towards_beam(self.cross_beam, self.main_beam, ignore_ends=True)[1] start_cross, end_cross = self.cross_beam.extension_to_plane(extension_plane_cross) self.cross_beam.add_blank_extension( start_cross + extension_tolerance, end_cross + extension_tolerance, self.key ) + def add_features(self): assert self.main_beam and self.cross_beam diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index fede778539..bce0b7bdec 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -1,4 +1,5 @@ import os +from re import split import uuid import xml.dom.minidom as MD import xml.etree.ElementTree as ET @@ -73,6 +74,37 @@ def history(self): "Comment": "", } + def get_split_strings(self, beam_key, split_into_two = True): + + if split_into_two: + split_lists = [[1,2,6],[3,4,5]] + else: + split_lists = [[1],[2],[3],[4,5,6]] + + part = self.parts[str(beam_key)] + + start_ref_plane = part.get_start_end_ref_plane() + print("start_ref_plane", start_ref_plane) + for i, list in enumerate(split_lists): + if start_ref_plane in list: + last_list = split_lists.pop(i) + split_lists.append(last_list) + break + print("split_lists", split_lists) + ET_element = ET.Element("BTLx", BTLx.FILE_ATTRIBUTES) + ET_element.append(self.file_history) + project_element = ET.SubElement(ET_element, "Project", Name="testProject") + parts_element = ET.SubElement(project_element, "Parts") + + for i, ref_plane_list in enumerate(split_lists): + part.element_number = str(i+1) + parts_element.append(part.et_element(ref_plane_list)) + + + return MD.parseString(ET.tostring(ET_element)).toprettyxml(indent=" ") + + + def btlx_string(self): """Returns a pretty XML string for visualization in GH, Terminal, etc.""" self.ET_element = ET.Element("BTLx", BTLx.FILE_ATTRIBUTES) @@ -81,7 +113,7 @@ def btlx_string(self): self.parts_element = ET.SubElement(self.project_element, "Parts") for part in self.parts.values(): - self.parts_element.append(part.et_element) + self.parts_element.append(part.et_element()) return MD.parseString(ET.tostring(self.ET_element)).toprettyxml(indent=" ") def process_assembly(self): @@ -192,10 +224,10 @@ def __init__(self, beam): ) # I used long_edge[2] because it is in Y and Z negative. Using that as reference puts the beam entirely in positive coordinates. self.blank_length = beam.blank_length self.ID = beam.attributes["ID"] + self.element_number = 0 self.intersections = beam.intersections self._reference_surfaces = [] self.processings = [] - self._et_element = None def reference_surface_from_beam_face(self, beam_face): @@ -278,7 +310,7 @@ def attr(self): "Weight": "0", "ProcessingQuality": "automatic", "StoreyType": "", - "ElementNumber": "00", + "ElementNumber": str(self.element_number), "Layer": "0", "ModuleNumber": "", } @@ -313,19 +345,33 @@ def et_point_vals(self, point): "Z": "{:.{prec}f}".format(point.z, prec=BTLx.POINT_PRECISION), } - @property - def et_element(self): - if not self._et_element: - self._et_element = ET.Element("Part", self.attr) - self._shape_strings = None - self._et_element.append(self.et_transformations) - self._et_element.append(ET.Element("GrainDirection", X="1", Y="0", Z="0", Align="no")) - self._et_element.append(ET.Element("ReferenceSide", Side="1", Align="no")) - processings_et = ET.Element("Processings") - for process in self.processings: + def get_start_end_ref_plane(self): + result = 0 + for process in self.processings: + name = process.header_attributes.get("Name") + if name.split(" ")[0] == "start": + result = process.header_attributes.get("ReferencePlaneID") + return int(result) + + + def et_element(self, ref_sides = [1,2,3,4,5,6]): + + self._et_element = ET.Element("Part", self.attr) + self._shape_strings = None + self._et_element.append(self.et_transformations) + self._et_element.append(ET.Element("GrainDirection", X="1", Y="0", Z="0", Align="no")) + self._et_element.append(ET.Element("ReferenceSide", Side="1", Align="no")) + processings_et = ET.Element("Processings") + + for process in self.processings: + print(process.header_attributes.get("ReferencePlaneID")) + print(ref_sides) + if int(process.header_attributes.get("ReferencePlaneID")) in ref_sides: + print("in ref sides") processings_et.append(process.et_element) - self._et_element.append(processings_et) - self._et_element.append(self.et_shape) + + self._et_element.append(processings_et) + self._et_element.append(self.et_shape) return self._et_element @property diff --git a/src/compas_timber/fabrication/joint_factories/l_butt_factory.py b/src/compas_timber/fabrication/joint_factories/l_butt_factory.py index 860d3cfbf8..639a580241 100644 --- a/src/compas_timber/fabrication/joint_factories/l_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/l_butt_factory.py @@ -51,14 +51,14 @@ def apply_processings(cls, joint, parts): } joint.btlx_params_cross["ReferencePlaneID"] = str(cross_part.reference_surface_from_beam_face(ref_plane)) - cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, "L-Butt Joint")) + cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, joint.ends[str(cross_part.key)] + " L-Butt Joint")) if joint.birdsmouth: ref_face = main_part.beam.faces[joint.main_face_index] joint.btlx_params_main["ReferencePlaneID"] = str(main_part.reference_surface_from_beam_face(ref_face)) - main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_main, "L-Butt Joint")) + main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_main, joint.ends[str(main_part.key)] + " L-Butt Joint")) else: - main_part.processings.append(BTLxJackCut.create_process(main_part, main_cut_plane, "L-Butt Joint")) + main_part.processings.append(BTLxJackCut.create_process(main_part, main_cut_plane, joint.ends[str(main_part.key)] + " L-Butt Joint")) BTLx.register_joint(LButtJoint, LButtFactory) diff --git a/src/compas_timber/fabrication/joint_factories/l_miter_factory.py b/src/compas_timber/fabrication/joint_factories/l_miter_factory.py index 43add22e42..6554e94310 100644 --- a/src/compas_timber/fabrication/joint_factories/l_miter_factory.py +++ b/src/compas_timber/fabrication/joint_factories/l_miter_factory.py @@ -30,10 +30,10 @@ def apply_processings(cls, joint, parts): """ beams = [joint.beam_a, joint.beam_b] parts[str(beams[0].key)].processings.append( - BTLxJackCut.create_process(parts[str(beams[0].key)], joint.get_cutting_planes()[0], "L-Miter Joint") + BTLxJackCut.create_process(parts[str(beams[0].key)], joint.get_cutting_planes()[0], joint.ends[str(beams[0].key)] + " L-Miter Joint") ) parts[str(beams[1].key)].processings.append( - BTLxJackCut.create_process(parts[str(beams[1].key)], joint.get_cutting_planes()[1], "L-Miter Joint") + BTLxJackCut.create_process(parts[str(beams[1].key)], joint.get_cutting_planes()[1], joint.ends[str(beams[1].key)] + " L-Miter Joint") ) diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index 96cfc74785..a10af25cd6 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -38,12 +38,12 @@ def apply_processings(cls, joint, parts): if joint.birdsmouth: ref_face = main_part.beam.faces[joint.main_face_index] joint.btlx_params_main["ReferencePlaneID"] = str(main_part.reference_surface_from_beam_face(ref_face)) - main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_main, "T-Butt Joint")) + main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_main, joint.ends[str(main_part.key)] + " T-Butt Joint")) elif joint.stepjoint: joint.btlx_params_stepjoint_main["ReferencePlaneID"] = str(4) - main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_stepjoint_main, "T-Butt Joint")) + main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_stepjoint_main, joint.ends[str(main_part.key)] + " T-Butt Joint")) else: - main_part.processings.append(BTLxJackCut.create_process(main_part, cut_plane, "T-Butt Joint")) + main_part.processings.append(BTLxJackCut.create_process(main_part, cut_plane, joint.ends[str(main_part.key)] + " T-Butt Joint")) joint.btlx_params_cross["reference_plane_id"] = str(cross_part.reference_surface_from_beam_face(ref_plane)) if joint.mill_depth > 0: From 2259dadb54711dc3202fdf7985f09e4a19105fad Mon Sep 17 00:00:00 2001 From: papachap Date: Mon, 13 May 2024 20:38:10 +0200 Subject: [PATCH 68/82] implement l_half_lap *extension not working **cannot serialize int (ET) --- src/compas_timber/connections/l_halflap.py | 139 +++++++++++++++++- src/compas_timber/fabrication/__init__.py | 4 +- .../joint_factories/l_halflap_factory.py | 43 ++++++ 3 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 src/compas_timber/fabrication/joint_factories/l_halflap_factory.py diff --git a/src/compas_timber/connections/l_halflap.py b/src/compas_timber/connections/l_halflap.py index e5dc603517..89ef533140 100644 --- a/src/compas_timber/connections/l_halflap.py +++ b/src/compas_timber/connections/l_halflap.py @@ -1,4 +1,5 @@ from compas.geometry import Frame +from compas.geometry import angle_vectors from compas_timber.parts import CutFeature from compas_timber.parts import MillVolume @@ -47,8 +48,48 @@ 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, **kwargs): - super(LHalfLapJoint, self).__init__(main_beam, cross_beam, flip_lap_side, cut_plane_bias, **kwargs) + def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5, drill_diameter=0.0, **kwargs): + super(LHalfLapJoint, self).__init__(**kwargs) + + 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.flip_lap_side = flip_lap_side + self.cut_plane_bias = cut_plane_bias + self.drill_diameter = float(drill_diameter) + self.btlx_params_main = {} + self.btlx_params_cross = {} + self.btlx_drilling_params_main = {} + self.features = [] + self.test = [] + self.top_cross_plane = self.get_world_top_bottom_faces(self.cross_beam)[0] + self.bottom_main_plane = self.get_world_top_bottom_faces(self.main_beam)[1] + + @property + def __data__(self): + data_dict = { + "main_beam_key": self.main_beam_key, + "cross_beam_key": self.cross_beam_key, + } + data_dict.update(super(LHalfLapJoint, self).__data__) + return data_dict + + @classmethod + def __from_data__(cls, value): + instance = cls(**value) + 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] + + 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 add_extensions(self): """Adds the extensions to the main beam and cross beam. @@ -74,12 +115,19 @@ def add_features(self): assert self.main_beam and self.cross_beam try: - main_cutting_frame = self.get_main_cutting_frame() - cross_cutting_frame = self.get_cross_cutting_frame() + main_cutting_frame = self.main_beam.faces[self.bottom_main_plane] + cross_cutting_frame = self.cross_beam.faces[self.top_cross_plane] negative_brep_main_beam, negative_brep_cross_beam = self._create_negative_volumes() except Exception as ex: + print(ex) raise BeamJoinningError(beams=self.beams, joint=self, debug_info=str(ex)) + #call functions to calculate the parameters + self.calc_params_cross() + self.calc_params_main() + if self.drill_diameter > 0: + self.calc_params_drilling_main() + main_volume = MillVolume(negative_brep_main_beam) cross_volume = MillVolume(negative_brep_cross_beam) @@ -94,3 +142,86 @@ def add_features(self): self.main_beam.add_features(f_main) self.features = [main_volume, cross_volume, f_main, f_cross] + + def get_world_top_bottom_faces(self, beam): + faces = beam.faces + face_normals = [face.zaxis for face in faces] + angles = [angle_vectors(face_normal, [0, 0, 1]) for face_normal in face_normals] + + top_face_index = angles.index(min(angles)) + bottom_face_index = angles.index(max(angles)) + return top_face_index, bottom_face_index + + def calc_params_main(self): + if self.ends[str(self.main_beam.key)] == "start": + start_x = 0.0 + else: + start_x = self.main_beam.blank_length + + self.btlx_params_main["ReferencePlaneID"] = str(self.bottom_main_plane) + self.btlx_params_cross["Orientation"] = self.ends[str(self.main_beam.key)] + self.btlx_params_cross["start_x"] = start_x + self.btlx_params_cross["start_y"] = 0.0 + self.btlx_params_main["depth"] = 30.0 + self.btlx_params_main["width"] = 60.0 + self.btlx_params_main["length"] = 60.0 + self.btlx_params_main["machining_limits"] = { + "FaceLimitedFront": "no", + "FaceLimitedBack": "no", + } + + def calc_params_cross(self): + if self.ends[str(self.cross_beam.key)] == "start": + start_x = 0.0 + else: + start_x = self.cross_beam.blank_length + + self.btlx_params_cross["ReferencePlaneID"] = str(self.top_cross_plane) + self.btlx_params_cross["Orientation"] = self.ends[str(self.cross_beam.key)] + self.btlx_params_cross["start_x"] = start_x + self.btlx_params_cross["start_y"] = 0.0 + self.btlx_params_cross["length"] = 60.0 + self.btlx_params_cross["width"] = 60.0 + self.btlx_params_cross["depth"] = 30.0 + self.btlx_params_cross["machining_limits"] = { + "FaceLimitedFront": "no", + "FaceLimitedBack": "no", + } + + def calc_params_drilling_main(self): + """ + Calculate the parameters for a drilling joint. + + Parameters: + ---------- + joint (object): The joint object. + main_part (object): The main part object. + + Returns: + ---------- + dict: A dictionary containing the calculated parameters for the drilling joint + + """ + if self.ends[str(self.cross_beam.key)] == "start": + start_x = 30.0 + else: + start_x = self.cross_beam.blank_length - 30.0 + + self.btlx_drilling_params_main = { + "ReferencePlaneID": self.bottom_main_plane, + "StartX": start_x, + "StartY": 30.0, + "Angle": 0.0, + "Inclination": 90.0, + "Diameter": self.drill_diameter, + "DepthLimited": "no", + "Depth": 0.0 + + } + + # # Rhino geometry visualization + # line = Line(start_point, line_point) + # line.start.translate(-line.vector) + # normal_centerline_angle = 180-math.degrees(ref_frame.zaxis.angle(self.main_beam.centerline.direction)) + # length = abs(self.cross_beam.width/(math.cos(math.radians(normal_centerline_angle)))) + # return line, self.drill_diameter, length*3 diff --git a/src/compas_timber/fabrication/__init__.py b/src/compas_timber/fabrication/__init__.py index 5e2c0c9aed..f723eeed29 100644 --- a/src/compas_timber/fabrication/__init__.py +++ b/src/compas_timber/fabrication/__init__.py @@ -9,6 +9,7 @@ from .joint_factories.french_ridge_factory import FrenchRidgeFactory from .joint_factories.l_butt_factory import LButtFactory from .joint_factories.l_miter_factory import LMiterFactory +from .joint_factories.l_halflap_factory import LHalfLapFactory from .joint_factories.t_butt_factory import TButtFactory from .joint_factories.text_factory import TextFactory @@ -21,7 +22,8 @@ "BTLxDoubleCut", "BTLxDrilling", "BTLxFrenchRidgeLap", - "LButtFactory", + "LButtFactory" + "LHalfLapFactory", "TButtFactory", "LMiterFactory", "FrenchRidgeFactory", diff --git a/src/compas_timber/fabrication/joint_factories/l_halflap_factory.py b/src/compas_timber/fabrication/joint_factories/l_halflap_factory.py new file mode 100644 index 0000000000..2dfe14262d --- /dev/null +++ b/src/compas_timber/fabrication/joint_factories/l_halflap_factory.py @@ -0,0 +1,43 @@ +from compas_timber.connections import LHalfLapJoint +from compas_timber.fabrication import BTLx +from compas_timber.fabrication import BTLxLap +from compas_timber.fabrication import BTLxDrilling + + +class LHalfLapFactory(object): + """ + Factory class for creating L-Butt joints. + """ + + def __init__(self): + pass + + + @classmethod + def apply_processings(cls, joint, parts): + """Apply processings to the joint and its associated parts. + + Parameters + ---------- + joint : :class:`~compas_timber.connections.joint.Joint` + The joint object. + parts : dict + A dictionary of the BTLxParts connected by this joint, with part keys as the dictionary keys. + + Returns + ------- + None + + """ + + main_part = parts[str(joint.main_beam.key)] + cross_part = parts[str(joint.cross_beam.key)] + + cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, "L-HalfLap Joint")) + main_part.processings.append(BTLxLap.create_process(joint.btlx_params_main, "L-HalfLap Joint")) + + if joint.drill_diameter > 0: + main_part.processings.append(BTLxDrilling.create_process(joint.btlx_drilling_params_main, "L-HalfLap Joint")) + + +BTLx.register_joint(LHalfLapJoint, LHalfLapFactory) From 419b630068196c2872f3a0b357c25384bca00aa2 Mon Sep 17 00:00:00 2001 From: papachap Date: Mon, 13 May 2024 20:38:40 +0200 Subject: [PATCH 69/82] fix birdsmouth condition --- src/compas_timber/connections/butt_joint.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 39db3b4226..32c7e1f92e 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -242,9 +242,19 @@ def calc_params_birdsmouth(self): cross_ref_main = cross_vectors(og_frame.zaxis, self.main_beam.centerline.direction) + cross_centerlines = cross_vectors(self.main_beam.centerline.direction, self.cross_beam.centerline.direction) self.test.append(Line(og_frame.point, og_frame.point + cross_ref_main * 100)) angle = angle_vectors(cross_ref_main, og_frame.yaxis, deg=True) - if angle < 1.0 or angle > 179.0: + angle2 = angle_vectors(cross_centerlines, self.main_beam.frame.zaxis, deg=True) + angle2 = round(angle2, 1) - 180 + print("angle2", angle2+180) + threshold_angle = 3.0 + # if angle < 1.0 or angle > 179.0: + # self.birdsmouth = False + # return False + + if abs(angle2)%90 <= threshold_angle or abs((abs(angle2)-90)%90) <= threshold_angle: + print("smaller than threshold", abs(angle2%90)) self.birdsmouth = False return False From f14de0ea455d040bb371b5998f213b63fc72a877 Mon Sep 17 00:00:00 2001 From: obucklin Date: Mon, 13 May 2024 20:42:51 +0200 Subject: [PATCH 70/82] added component --- .../components/CT_BTLx_Flip_Beam/code.py | 24 +++++++++ .../components/CT_BTLx_Flip_Beam/icon.png | Bin 0 -> 689 bytes .../CT_BTLx_Flip_Beam/metadata.json | 50 ++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 src/compas_timber/ghpython/components/CT_BTLx_Flip_Beam/code.py create mode 100644 src/compas_timber/ghpython/components/CT_BTLx_Flip_Beam/icon.png create mode 100644 src/compas_timber/ghpython/components/CT_BTLx_Flip_Beam/metadata.json diff --git a/src/compas_timber/ghpython/components/CT_BTLx_Flip_Beam/code.py b/src/compas_timber/ghpython/components/CT_BTLx_Flip_Beam/code.py new file mode 100644 index 0000000000..edc3a0b210 --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_BTLx_Flip_Beam/code.py @@ -0,0 +1,24 @@ +import Rhino +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning + +from compas_timber.fabrication import BTLx + + +class WriteBTLx(component): + def RunScript(self, btlx, path, write, beam_key, split_into_two): + if not btlx: + self.AddRuntimeMessage(Warning, "Input parameter btlx failed to collect data") + return + btlx.history["FileName"] = Rhino.RhinoDoc.ActiveDoc.Name + + if write: + if not path: + self.AddRuntimeMessage(Warning, "Input parameter Path failed to collect data") + return + path = path.split(".")[0] if "." in path else path + path_end = "_" + str(beam_key) + "_SPLIT" + ".btlx" + path += path_end + with open(path, "w") as f: + f.write(btlx.get_split_strings(beam_key, split_into_two)) + return btlx.get_split_strings(beam_key, split_into_two) diff --git a/src/compas_timber/ghpython/components/CT_BTLx_Flip_Beam/icon.png b/src/compas_timber/ghpython/components/CT_BTLx_Flip_Beam/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b4ae4794c26317a0c2680cf24589d75b6463d460 GIT binary patch literal 689 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_q{N zPZ!4!kItk&|Nq-FpJiZXW(KPK|37Kb%9lJmJU}*(vuG_VR(6tNn4+?BGIP)0|NrYP zr1cXL5+E9stR_iHN*FOr7-BewV|x|0@_aN_jBB&15?2Xv55W zm~mFbp#ujXDw|w4*#U)EZ~j}%b|fn$C8dPnD7T2Z!%>Hv7D>29CqLIu0rM>cGTA+1 zZAF@O8#bLcHZsafNJ$X^x+MW(l#~CmKR_M7Q-Aj#GMT{rM^Zw9O9BWWvPn~3S~qD+ z`qso`@P@H`we!+qVimk1uJGXj(DB?8t{NB^)TAUN1VB_Dn$G`$?J7`)zoALQ*J5I0 zW1^jniG&0&NJ0KPG za5!ngdPhwmCb(0a_!k{H-g-?TJb_t$0?TV)2s%z{XyQ=$$Kd7faPh=}0*EP1kqlh| z?LdX%1rmHr3TGEwVPsaAekg|flk5qjfS~rqUZDHzA!>ns(Ky2)$=4kcpwcAc%#p+L zoEIJ&Og|Y?EH-`r7|G<-CB2|<0YjGyP%faQS%%#^kbOa4A|ebI Date: Tue, 14 May 2024 15:18:16 +0200 Subject: [PATCH 71/82] fix extensions --- src/compas_timber/connections/l_halflap.py | 35 +++++++++++----------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/compas_timber/connections/l_halflap.py b/src/compas_timber/connections/l_halflap.py index 89ef533140..1d75af9efa 100644 --- a/src/compas_timber/connections/l_halflap.py +++ b/src/compas_timber/connections/l_halflap.py @@ -48,7 +48,9 @@ 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, drill_diameter=0.0, **kwargs): + def __init__( + self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5, drill_diameter=0.0, **kwargs + ): super(LHalfLapJoint, self).__init__(**kwargs) self.main_beam = main_beam @@ -101,28 +103,26 @@ def add_extensions(self): assert self.main_beam and self.cross_beam extension_tolerance = 0.01 # TODO: this should be proportional to the unit used - extension_plane_main = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True)[1] + extension_plane_main = self.get_face_most_towards_beam(self.main_beam, self.cross_beam, ignore_ends=True)[1] start_main, end_main = self.main_beam.extension_to_plane(extension_plane_main) - self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.key) - - extension_plane_cross = self.get_face_most_ortho_to_beam(self.cross_beam, self.main_beam, ignore_ends=True)[1] + self.main_beam.add_blank_extension(start_main, end_main, self.key) + print("extension_plane_main", start_main, end_main, extension_plane_main) + extension_plane_cross = self.get_face_most_towards_beam(self.cross_beam, self.main_beam, ignore_ends=True)[1] start_cross, end_cross = self.cross_beam.extension_to_plane(extension_plane_cross) - self.cross_beam.add_blank_extension( - start_cross + extension_tolerance, end_cross + extension_tolerance, self.key - ) + self.cross_beam.add_blank_extension(start_cross, end_cross, self.key) def add_features(self): assert self.main_beam and self.cross_beam try: main_cutting_frame = self.main_beam.faces[self.bottom_main_plane] - cross_cutting_frame = self.cross_beam.faces[self.top_cross_plane] + cross_cutting_frame = self.cross_beam.faces[self.top_cross_plane] negative_brep_main_beam, negative_brep_cross_beam = self._create_negative_volumes() except Exception as ex: print(ex) raise BeamJoinningError(beams=self.beams, joint=self, debug_info=str(ex)) - #call functions to calculate the parameters + # call functions to calculate the parameters self.calc_params_cross() self.calc_params_main() if self.drill_diameter > 0: @@ -166,9 +166,9 @@ def calc_params_main(self): self.btlx_params_main["width"] = 60.0 self.btlx_params_main["length"] = 60.0 self.btlx_params_main["machining_limits"] = { - "FaceLimitedFront": "no", - "FaceLimitedBack": "no", - } + "FaceLimitedFront": "no", + "FaceLimitedBack": "no", + } def calc_params_cross(self): if self.ends[str(self.cross_beam.key)] == "start": @@ -184,9 +184,9 @@ def calc_params_cross(self): self.btlx_params_cross["width"] = 60.0 self.btlx_params_cross["depth"] = 30.0 self.btlx_params_cross["machining_limits"] = { - "FaceLimitedFront": "no", - "FaceLimitedBack": "no", - } + "FaceLimitedFront": "no", + "FaceLimitedBack": "no", + } def calc_params_drilling_main(self): """ @@ -215,8 +215,7 @@ def calc_params_drilling_main(self): "Inclination": 90.0, "Diameter": self.drill_diameter, "DepthLimited": "no", - "Depth": 0.0 - + "Depth": 0.0, } # # Rhino geometry visualization From dfbb1beb2216516563bfe29895330862234334ae Mon Sep 17 00:00:00 2001 From: ananya Date: Tue, 14 May 2024 15:46:09 +0200 Subject: [PATCH 72/82] fix btlx l lap --- src/compas_timber/connections/l_halflap.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/compas_timber/connections/l_halflap.py b/src/compas_timber/connections/l_halflap.py index 1d75af9efa..6b882178bd 100644 --- a/src/compas_timber/connections/l_halflap.py +++ b/src/compas_timber/connections/l_halflap.py @@ -157,11 +157,11 @@ def calc_params_main(self): start_x = 0.0 else: start_x = self.main_beam.blank_length - - self.btlx_params_main["ReferencePlaneID"] = str(self.bottom_main_plane) - self.btlx_params_cross["Orientation"] = self.ends[str(self.main_beam.key)] - self.btlx_params_cross["start_x"] = start_x - self.btlx_params_cross["start_y"] = 0.0 + self.btlx_params_main["ReferencePlaneID"] = str(self.bottom_main_plane + 1) + self.btlx_params_main["orientation"] = self.ends[str(self.main_beam.key)] + self.btlx_params_main["start_x"] = start_x + print("start_x", start_x) + self.btlx_params_main["start_y"] = 0.0 self.btlx_params_main["depth"] = 30.0 self.btlx_params_main["width"] = 60.0 self.btlx_params_main["length"] = 60.0 @@ -176,9 +176,10 @@ def calc_params_cross(self): else: start_x = self.cross_beam.blank_length - self.btlx_params_cross["ReferencePlaneID"] = str(self.top_cross_plane) - self.btlx_params_cross["Orientation"] = self.ends[str(self.cross_beam.key)] + self.btlx_params_cross["ReferencePlaneID"] = str(self.top_cross_plane + 1) + self.btlx_params_cross["orientation"] = self.ends[str(self.cross_beam.key)] self.btlx_params_cross["start_x"] = start_x + print("start_x", start_x) self.btlx_params_cross["start_y"] = 0.0 self.btlx_params_cross["length"] = 60.0 self.btlx_params_cross["width"] = 60.0 From b88392d6b0924a5b6edf406dcee56fd273d07aff Mon Sep 17 00:00:00 2001 From: paulocinco Date: Tue, 14 May 2024 18:20:11 +0200 Subject: [PATCH 73/82] stepjoint working for BTLx --- src/compas_timber/connections/butt_joint.py | 256 ++++++++++++------ src/compas_timber/connections/t_butt.py | 10 +- .../fabrication/btlx_processes/btlx_lap.py | 2 +- .../joint_factories/t_butt_factory.py | 8 +- .../t_butt_factory_stepjoint_WIP.py | 248 ----------------- 5 files changed, 185 insertions(+), 339 deletions(-) delete mode 100644 src/compas_timber/fabrication/joint_factories/t_butt_factory_stepjoint_WIP.py diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 898bcac17d..ff91e14987 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -6,12 +6,15 @@ from compas.geometry import distance_line_line from compas.geometry import intersection_plane_plane from compas.geometry import intersection_line_plane +from compas.geometry import intersection_line_line from compas.geometry import Plane from compas.geometry import Line from compas.geometry import Polyhedron from compas.geometry import Point from compas.geometry import Vector from compas.geometry import Transformation +from compas.geometry import Polyline +from compas.geometry import Curve from compas.geometry import angle_vectors_signed from compas.geometry import angle_vectors from compas.geometry import cross_vectors @@ -64,7 +67,7 @@ def __init__(self, main_beam=None, cross_beam=None, mill_depth=0, drill_diameter self.btlx_params_cross = {} self.btlx_drilling_params_cross = {} self.btlx_stepjoint_params_main = {} - self.btlx_params_stepjoint_main = {} + self.btlx_params_stepjoint_cross = {} self.features = [] self.test = [] @@ -281,15 +284,6 @@ def calc_params_birdsmouth(self): Inclination1 = angle_vectors(ref_frame.zaxis, plane1.normal, deg=True) Inclination2 = angle_vectors(ref_frame.zaxis, plane2.normal, deg=True) - print "orientation: ", self.ends[str(self.main_beam.key)] - print "StartX: ", StartX - print "StartY: ", StartY - print "Angle1: ", Angle1 - print "Inclination1: ", Inclination1 - print "Angle2: ", Angle2 - print "Inclination2: ", Inclination2 - print "ReferencePlaneID: ", self.main_face_index - self.btlx_params_main = { "Orientation": self.ends[str(self.main_beam.key)], "StartX": StartX, @@ -319,8 +313,6 @@ def calc_params_drilling(self): dict: A dictionary containing the calculated parameters for the drilling joint """ - # ref_frame_id, ref_frame = self.get_face_most_towards_beam(self.main_beam, self.cross_beam, ignore_ends=True) - # print ref_frame_id _cut_plane, cutting_frame = self.get_main_cutting_plane() ref_plane = Plane.from_frame(cutting_frame) @@ -339,7 +331,6 @@ def calc_params_drilling(self): ref_frame.point = ref_frame.point - ref_frame.yaxis * self.cross_beam.width * 0.5 ref_frame.point = ref_frame.point + ref_frame.zaxis * self.cross_beam.height * 0.5 - # ref_plane = Plane.from_frame(ref_frame) point_xyz = (intersection_line_plane(self.main_beam.centerline, ref_plane)) start_point = Point(*point_xyz) ref_point = start_point.transformed(Transformation.from_frame_to_frame(ref_frame, Frame.worldXY())) @@ -397,100 +388,76 @@ def calc_params_stepjoint(self): """ - # only valid for Heel Step Joint at 15mm depth - StepDepth = 0.0 - HeelDepth = 15.0 - StepShape = "heel" - Tenon = "no" - TenonWidth = 0.0 - TenonHeight = 0.0 + face_dict = self._beam_side_incidence(self.cross_beam, self.main_beam, ignore_ends=True) + face_keys = sorted([key for key in face_dict.keys()], key=face_dict.get) - # finding face facing the cross beam the least - ref_face_id, ref_face = self.get_face_most_ortho_to_beam(self.cross_beam, self.main_beam, ignore_ends=True) - print "ref_face_id: ", ref_face_id + # dot = self.main_beam.centerline.direction.dot(self.cross_beam.centerline.direction) + if self.main_beam.centerline.end.on_line(self.cross_beam.centerline): + centerline_vec = self.main_beam.centerline.direction + else: + centerline_vec = -self.main_beam.centerline.direction - # face_dict = joint._beam_side_incidence(cross_part.beam, main_part.beam, ignore_ends=True) - # ref_frame_id = min(face_dict, key=face_dict.get) - # ref_frame = main_part.beam.faces[ref_frame_id] + # finding the inclination of the strut based on the two centerlines + StrutInclination = math.degrees(self.cross_beam.centerline.direction.angle(centerline_vec)) - Inclination1 = 90.0 - Inclination2 = 90.0 + inter_centerlines = intersection_line_line(self.cross_beam.centerline, self.main_beam.centerline) + inter_param = self.cross_beam.centerline.closest_point(Point(*inter_centerlines[0]), True)[1] + angles_dict = {} + for i, face in enumerate(self.main_beam.faces[0:4]): + angles_dict[i] = face.normal.angle_signed(self.main_beam.faces[face_keys[0]].normal, centerline_vec) + faces_ordered = sorted(angles_dict.keys(), key=angles_dict.get) + if (inter_param > 0.5 and StrutInclination < 90) or (inter_param < 0.5 and StrutInclination > 90): + self.ref_face_id = faces_ordered[2] + else: + self.ref_face_id = faces_ordered[0] + + ref_face = self.main_beam.faces[self.ref_face_id] + + ref_face.point = self.main_beam.blank_frame.point + if self.ref_face_id % 2 == 0: + ref_face.point = ref_face.point - ref_face.yaxis * self.main_beam.height * 0.5 + ref_face.point = ref_face.point + ref_face.zaxis * self.main_beam.width * 0.5 + else: + ref_face.point = ref_face.point - ref_face.yaxis * self.main_beam.width * 0.5 + ref_face.point = ref_face.point + ref_face.zaxis * self.main_beam.height * 0.5 - # finding the inclination of the strut based on the two centerlines - StrutInclination = math.degrees(self.cross_beam.centerline.direction.angle(self.main_beam.centerline.direction)) - print "StrutInclination: ", StrutInclination - # print (StrutInclination) if StrutInclination < 90: angle1 = (180 - StrutInclination)/2 - strutinclination = StrutInclination + strut_inclination = StrutInclination else: angle1 = StrutInclination/2 - strutinclination = 180 - StrutInclination + strut_inclination = 180 - StrutInclination - # find StartX - buried_depth = math.sin(math.radians(90-strutinclination))*self.main_beam.width/2 + buried_depth = math.sin(math.radians(90-strut_inclination))*self.main_beam.width/2 blank_vert_depth = self.cross_beam.width/2 - buried_depth - blank_edge_depth = abs(blank_vert_depth)/math.sin(math.radians(strutinclination)) - # print blank_edge_depth + blank_edge_depth = abs(blank_vert_depth)/math.sin(math.radians(strut_inclination)) startx = blank_edge_depth/2 starty = self.main_beam.width/4 - outside_length = self.main_beam.width/math.tan(math.radians(strutinclination)) + outside_length = self.main_beam.width/math.tan(math.radians(strut_inclination)) x_main_cutting_face = outside_length + blank_edge_depth vec_angle2 = Vector.from_start_end(Point(startx, self.cross_beam.width - starty), Point(x_main_cutting_face, 0)) vec_xaxis = Vector.from_start_end(Point(startx, self.cross_beam.width - starty), Point(0, self.cross_beam.width - starty)) angle2 = vec_xaxis.angle(vec_angle2, True) - print StrutInclination - if StrutInclination < 90: - if self.ends[str(self.main_beam.key)] == "start": - StartX = startx - StartY = self.main_beam.width - starty - Angle1 = angle2 - Angle2 = angle1 - else: - StartX = self.main_beam.blank_length - startx - StartY = starty - Angle1 = 180 - angle1 - Angle2 = 180 - angle2 - else: - if self.ends[str(self.main_beam.key)] == "start": - StartX = startx - StartY = starty - Angle1 = 180-angle1 - Angle2 = 180-angle2 - else: - StartX = self.main_beam.blank_length - startx - StartY = self.main_beam.width - starty - Angle1 = angle2 - Angle2 = angle1 - - self.bm_sub_volume = Brep.from_box(self.cross_beam.blank) - print "orientation: ", self.ends[str(self.main_beam.key)] - print "StartX: ", StartX - print "StartY: ", StartY - print "Angle1: ", Angle1 - print "Inclination1: ", Inclination1 - print "Angle2: ", Angle2 - print "Inclination2: ", Inclination2 - print "ReferencePlaneID: ", ref_face_id + if self.ends[str(self.main_beam.key)] == "start": + StartX = startx + StartY = starty + Angle1 = 180-angle1 + Angle2 = 180-angle2 + else: + StartX = self.main_beam.blank_length - startx + StartY = self.main_beam.width - starty + Angle1 = angle2 + Angle2 = angle1 - self.btlx_stepjoint_params_main = { - "Orientation": self.ends[str(self.main_beam.key)], - "StartX": StartX, - "StrutInclination": StrutInclination, - "StepDepth": StepDepth, - "HeelDepth": HeelDepth, - "StepShape": StepShape, - "Tenon": Tenon, - "TenonWidth": TenonWidth, - "TenonHeight": TenonHeight, - "ReferencePlaneID": ref_face_id - } + self.sj_main_sub_volume = Brep.from_box(self.cross_beam.blank) + Inclination1 = 90.0 + Inclination2 = 90.0 self.btlx_params_stepjoint_main = { "Orientation": self.ends[str(self.main_beam.key)], "StartX": float(StartX), @@ -499,7 +466,126 @@ def calc_params_stepjoint(self): "Inclination1": float(Inclination1), "Angle2": Angle2, "Inclination2": Inclination2, - "ReferencePlaneID": ref_face_id, + "ReferencePlaneID": self.ref_face_id, + } + + #find params lap cross beam + + angles_dict_cross = {} + for i, face in enumerate(self.cross_beam.faces[0:4]): + angles_dict_cross[i] = face.normal.dot(ref_face.normal) + # print(angles_dict) + self.cross_face_id = max(angles_dict_cross.keys(), key=angles_dict_cross.get) + cross_face = self.cross_beam.faces[self.cross_face_id] + + cross_face.point = self.cross_beam.blank_frame.point + if self.cross_face_id % 2 == 0: + cross_face.point = cross_face.point - cross_face.yaxis * self.cross_beam.height * 0.5 + cross_face.point = cross_face.point + cross_face.zaxis * self.cross_beam.width * 0.5 + else: + cross_face.point = cross_face.point - cross_face.yaxis * self.cross_beam.width * 0.5 + cross_face.point = cross_face.point + cross_face.zaxis * self.cross_beam.height * 0.5 + + main_xypoint = Point(StartX, StartY, 0) + # print("main_xypoint", main_xypoint) + worldxy_xypoint = main_xypoint.transformed(Transformation.from_frame_to_frame(Frame.worldXY(), ref_face)) + # print("worldxy_xypoint", worldxy_xypoint) + cross_xy_point = worldxy_xypoint.transformed(Transformation.from_frame_to_frame(cross_face, Frame.worldXY())) + # print("cross_xy_point", cross_xy_point) + + StartX_cross = cross_xy_point[0] + StartY_cross = cross_xy_point[1] + + if (inter_param > 0.5 and StrutInclination < 90) or (inter_param < 0.5 and StrutInclination > 90): + orientation = self.ends[str(self.cross_beam.key)] + if self.ends[str(self.cross_beam.key)] == "start": + self.cross_face_id = min(angles_dict_cross.keys(), key=angles_dict_cross.get) + cross_face = self.cross_beam.faces[self.cross_face_id] + StartY_cross = self.cross_beam.width - StartY_cross + if self.ends[str(self.main_beam.key)] == "start": + Angle_cross = 180 - Angle1 + LeadAngle = 180 - (Angle1 - Angle2) + else: + Angle_cross = Angle2 + LeadAngle = 180 - (Angle1 - Angle2) + + else: + if self.ends[str(self.main_beam.key)] == "start": + Angle_cross = 180 - Angle1 + LeadAngle = 180 - (Angle1 - Angle2) + else: + Angle_cross = Angle2 + LeadAngle = 180 - (Angle1 - Angle2) + else: + if self.ends[str(self.cross_beam.key)] == "start": + if self.ends[str(self.main_beam.key)] == "start": + orientation = "end" + Angle_cross = 180 - Angle1 + LeadAngle = 180 - (Angle1 - Angle2) + else: + orientation = "end" + Angle_cross = Angle2 + LeadAngle = 180 - (Angle1 - Angle2) + else: + self.cross_face_id = min(angles_dict_cross.keys(), key=angles_dict_cross.get) + cross_face = self.cross_beam.faces[self.cross_face_id] + StartY_cross = self.cross_beam.width - StartY_cross + if self.ends[str(self.main_beam.key)] == "start": + orientation = "start" + Angle_cross = 180 - Angle1 + LeadAngle = 180 - (Angle1 - Angle2) + else: + orientation = "start" + Angle_cross = Angle2 + LeadAngle = (180 - Angle1) + Angle2 #correct + + + main_most_towards = self.get_face_most_towards_beam(self.cross_beam, self.main_beam, ignore_ends=True)[1] + cross_most_ortho = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=True)[1] + + main_most_ortho = self.get_face_most_ortho_to_beam(self.cross_beam, self.main_beam, ignore_ends=True)[1] + + intersection_pt = Point(*intersection_plane_plane_plane(Plane.from_frame(main_most_towards), Plane.from_frame(cross_most_ortho), Plane.from_frame(ref_face))) + intersection_pt2 = Point(*intersection_plane_plane_plane(Plane.from_frame(main_most_ortho), Plane.from_frame(cross_most_ortho), Plane.from_frame(ref_face))) + # print("intersection_pt", intersection_pt) + # print("intersection_pt2", intersection_pt2) + + face_angle1 = main_most_towards.rotated(Angle1, Vector.Zaxis(), intersection_pt) + face_angle2 = main_most_ortho.rotated(-Angle2, Vector.Zaxis(), intersection_pt2) + + doublecut_origin = Point(*intersection_plane_plane_plane(Plane.from_frame(face_angle1), Plane.from_frame(face_angle2), Plane.from_frame(ref_face))) + # print("doublecut_origin", doublecut_origin) + + self.sj_cross_sub_volume = Brep.from_box(self.main_beam.blank) + + self.btlx_params_stepjoint_cross = { + "orientation": orientation, + "start_x": StartX_cross, + "start_y": StartY_cross, + "angle": Angle_cross, + "depth": 60.0, + "lead_angle_parallel": "no", + "lead_angle": LeadAngle, + "ReferencePlaneID": self.cross_face_id, } + + #brep for cross beam sub volume + pts_ph = [worldxy_xypoint, intersection_pt, intersection_pt2] + vertices_ph_sj_cross = pts_ph + vertices_ph_sj_cross.extend([pt.translated(-ref_face.normal*60) for pt in pts_ph]) + self.ph_sj_cross = Polyhedron(vertices_ph_sj_cross, [[0, 1, 2], [3, 4, 5], [0, 1, 4, 3], [1, 2, 5, 4], [0, 3, 5, 2]]) + # self.brep_sj_cross = Brep.to_brep(self.ph_sj_cross) + # print self.ph_sj_cross + # print (vertices) + # polyline = Polyline([worldxy_xypoint, intersection_pt, intersection_pt2, worldxy_xypoint]) + # pl_curve = Curve([worldxy_xypoint, intersection_pt, intersection_pt2, worldxy_xypoint]) + # print(type(pl_curve)) + # print(polyline.is_closed) + # # print(-ref_face.normal*60) + # brep = Brep.from_extrusion(polyline, -ref_face.normal*60.0) + # print(brep) + # print(brep.is_closed) + # print(brep.vertices) + return True diff --git a/src/compas_timber/connections/t_butt.py b/src/compas_timber/connections/t_butt.py index db55d02943..e9cc90cd1a 100644 --- a/src/compas_timber/connections/t_butt.py +++ b/src/compas_timber/connections/t_butt.py @@ -105,6 +105,10 @@ def add_features(self): self.features.append(DrillFeature(*self.calc_params_drilling())) if self.stepjoint: if self.calc_params_stepjoint(): - self.main_beam.add_features(BrepSubtraction(self.bm_sub_volume))#not correct - self.features.append(BrepSubtraction(self.bm_sub_volume))#not correct - + self.main_beam.add_features(BrepSubtraction(self.sj_main_sub_volume))#not correct + self.features.append(BrepSubtraction(self.sj_main_sub_volume))#not correct + # print(self.ph_sj_cross) + self.cross_beam.add_features(BrepSubtraction(self.sj_cross_sub_volume))#not correct + self.features.append(BrepSubtraction(self.sj_cross_sub_volume))#not correct + # self.cross_beam.add_features(MillVolume(self.ph_sj_cross))#not correct + # self.features.append(MillVolume(self.ph_sj_cross))#not correct diff --git a/src/compas_timber/fabrication/btlx_processes/btlx_lap.py b/src/compas_timber/fabrication/btlx_processes/btlx_lap.py index 3d835a66ed..f36e62760b 100644 --- a/src/compas_timber/fabrication/btlx_processes/btlx_lap.py +++ b/src/compas_timber/fabrication/btlx_processes/btlx_lap.py @@ -78,7 +78,7 @@ def process_params(self): ("Length", "{:.{prec}f}".format(self.length, prec=BTLx.POINT_PRECISION)), ("Width", "{:.{prec}f}".format(self.width, prec=BTLx.POINT_PRECISION)), ("Depth", "{:.{prec}f}".format(float(self.depth), prec=BTLx.POINT_PRECISION)), - ("LeadAngleParallel", "yes"), + ("LeadAngleParallel", str(self.lead_angle_parallel)), ("LeadAngle", "{:.{prec}f}".format(self.lead_angle, prec=BTLx.ANGLE_PRECISION)), ("LeadInclinationParallel", "yes"), ("LeadInclination", "{:.{prec}f}".format(self.lead_inclination, prec=BTLx.ANGLE_PRECISION)), diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index 92ed6989f1..3183916feb 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -4,7 +4,6 @@ from compas_timber.fabrication.btlx_processes.btlx_drilling import BTLxDrilling from compas_timber.fabrication.btlx_processes.btlx_lap import BTLxLap from compas_timber.fabrication.btlx_processes.btlx_double_cut import BTLxDoubleCut -from compas_timber.fabrication.btlx_processes.btlx_stepjoint import BTLxStepJoint class TButtFactory(object): @@ -40,8 +39,13 @@ def apply_processings(cls, joint, parts): joint.btlx_params_main["ReferencePlaneID"] = str(main_part.reference_surface_from_beam_face(ref_face)) main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_main, "T-Butt Joint")) elif joint.stepjoint: - joint.btlx_params_stepjoint_main["ReferencePlaneID"] = str(4) + ref_face = main_part.beam.faces[joint.ref_face_id] + joint.btlx_params_stepjoint_main["ReferencePlaneID"] = str(main_part.reference_surface_from_beam_face(ref_face)) main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_stepjoint_main, "T-Butt Joint")) + + ref_face_cross = cross_part.beam.faces[joint.cross_face_id] + joint.btlx_params_stepjoint_cross["ReferencePlaneID"] = str(cross_part.reference_surface_from_beam_face(ref_face_cross)) + cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_stepjoint_cross, "T-Butt Joint pocket")) else: main_part.processings.append(BTLxJackCut.create_process(main_part, cut_plane, "T-Butt Joint")) diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory_stepjoint_WIP.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory_stepjoint_WIP.py deleted file mode 100644 index 76794b7992..0000000000 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory_stepjoint_WIP.py +++ /dev/null @@ -1,248 +0,0 @@ -from compas_timber.connections import TButtJoint -from compas_timber.fabrication import BTLx -from compas_timber.fabrication import BTLxJackCut -from compas_timber.fabrication.btlx_processes.btlx_lap import BTLxLap -from compas_timber.fabrication.btlx_processes.btlx_double_cut import BTLxDoubleCut -from compas_timber.fabrication.btlx_processes.btlx_stepjoint import BTLxStepJoint - -from compas_timber.utils.compas_extra import intersection_line_plane - -class TButtFactory(object): - """Factory class for creating T-Butt joints.""" - - def __init__(self): - pass - - - @staticmethod - def line_intersects_face(line, face, x_max, y_max): - """ - Check if a line intersects a face. - - Parameters: - ---------- - line (object): The line object. - face (object): The face object. - - Returns: - ---------- - bool: True if the line intersects the face, False otherwise. - - """ - point = intersection_line_plane(line, Plane.from_frame(face))[0] - point.transform(Transformation.from_frame_to_frame(face, Frame.worldXY())) - print(point) - if point.x >= 0 and point.x <= x_max: - if point.y >= 0 and point.y <= y_max: - print("found it!!!!") - return True - return False - - - - @staticmethod - def get_intersecting_face(intersect_line, main_part, cross_part): - print(main_part, cross_part) - print(cross_part.beam.faces) - for i, face in enumerate(main_part.beam.faces[0:4]): - if i % 2 == 0: - if TButtFactory.line_intersects_face(intersect_line, face, cross_part.blank_length, cross_part.width): - return i, face - else: - if TButtFactory.line_intersects_face(intersect_line, face, cross_part.blank_length, cross_part.height): - return i, face - - - - @staticmethod - def calc_params_birdsmouth(joint, main_part, cross_part): - """ - Calculate the parameters for a birdsmouth joint. - - Parameters: - ---------- - joint (object): The joint object. - main_part (object): The main part object. - cross_part (object): The cross part object. - - Returns: - ---------- - dict: A dictionary containing the calculated parameters for the birdsmouth joint - - """ - face_dict = joint._beam_side_incidence(main_part.beam, cross_part.beam, ignore_ends=True) - sorted_keys = sorted(face_dict.keys(), key=face_dict.get) - - cross_vector = main_part.beam.centerline.direction.cross(cross_part.beam.centerline.direction) - - frame1, frame2 = joint.get_main_cutting_plane()[0], cross_part.beam.faces[sorted_keys[1]] - angle = angle_vectors(cross_vector, frame2.normal, deg=True) - if angle<1.0 or angle>179.0: - return False - - frame1 = Frame(frame1.point, frame1.xaxis, -frame1.yaxis) - print(frame1, cross_part.beam.faces[sorted_keys[0]]) - plane1, plane2 = Plane.from_frame(frame1), Plane.from_frame(frame2) - intersect_vec = Vector.from_start_end(*intersection_plane_plane(plane2, plane1)) - intersect_line = Line(*intersection_plane_plane(plane2, plane1)) - - ind, main_ref_frame = TButtFactory.get_intersecting_face(intersect_line, main_part, cross_part) - - print (ind, main_ref_frame) - - - - - angles_dict = {} - for i, face in enumerate(main_part.beam.faces[0:4]): - angles_dict[i] = (face.normal.angle(cross_part.beam.centerline.direction)) - ref_frame_id = min(angles_dict.keys(), key=angles_dict.get) - ref_frame = main_part.reference_surface_planes(ref_frame_id+1) - - print(angles_dict) - - print("ref_frame", ref_frame_id) - joint.test.append(ref_frame) - - - dot_frame1 = plane1.normal.dot(ref_frame.yaxis) - if dot_frame1 > 0: - plane1, plane2 = plane2, plane1 - - start_point = Point(*intersection_plane_plane_plane(plane1, plane2, Plane.from_frame(ref_frame))) - start_point.transform(Transformation.from_frame_to_frame(ref_frame, Frame.worldXY())) - StartX, StartY = start_point[0], start_point[1] - - intersect_vec1 = Vector.from_start_end(*intersection_plane_plane(plane1, Plane.from_frame(ref_frame))) - intersect_vec2 = Vector.from_start_end(*intersection_plane_plane(plane2, Plane.from_frame(ref_frame))) - - dot_2 = math.degrees(intersect_vec1.dot(ref_frame.yaxis)) - if dot_2 < 0: - intersect_vec1 = -intersect_vec1 - - dot_1 = math.degrees(intersect_vec2.dot(ref_frame.yaxis)) - if dot_1 < 0: - intersect_vec2 = -intersect_vec2 - - if joint.ends[str(main_part.key)] == "start": - reference_frame = ref_frame.xaxis - else: - reference_frame = -ref_frame.xaxis - - Angle1 = math.degrees(intersect_vec1.angle(reference_frame)) - Angle2 = math.degrees(intersect_vec2.angle(reference_frame)) - - Inclination1 = math.degrees(plane1.normal.angle(ref_frame.zaxis)) - Inclination2 = math.degrees(plane2.normal.angle(ref_frame.zaxis)) - - return { - "Orientation": joint.ends[str(main_part.key)], - "StartX": StartX, - "StartY": StartY, - "Angle1": Angle1, - "Inclination1": Inclination1, - "Angle2": Angle2, - "Inclination2": Inclination2, - "ReferencePlaneID": ref_frame_id - } - - - @staticmethod - def calc_params_stepjoint_heel(joint, main_part, cross_part, StepDepth=15): - """ - Calculate the parameters for a step joint. - - Parameters: - ---------- - joint (object): The joint object. - main_part (object): The main part object. - cross_part (object): The cross part object. - StepDepth (float): The depth of the step joint. - - Returns: - ---------- - dict: A dictionary containing the calculated parameters for the step joint - - """ - - # only valid for Heel Step Joint - HeelDepth = 0 - StepShape = "heel" - Tenon = "no" - TenonWidth = 0 - TenonHeight = 0 - - # finding face less facing the cross beam the least - face_dict = joint._beam_side_incidence(cross_part.beam, main_part.beam, ignore_ends=True) - ref_frame_id = min(face_dict, key=face_dict.get) - ref_frame = main_part.beam.faces[ref_frame_id] - - # finding the inclination of the strut based on the two centerlines - StrutInclination = math.degrees(cross_part.beam.centerline.direction.angle(main_part.beam.centerline.direction)) - - # find StartX according to StepDepth - blank_edge_depth = 3-(math.sin(90-StrutInclination)*main_part.beam.width/2) - startx = blank_edge_depth/math.sin(StrutInclination) - - if joint.ends[str(main_part.key)] == "start": - StartX = startx - else: - StartX = main_part.beam.blank_length - startx - - return { - "Orientation": joint.ends[str(main_part.key)], - "StartX": StartX, - "StrutInclination": StrutInclination, - "StepDepth": StepDepth, - "HeelDepth": HeelDepth, - "StepShape": StepShape, - "Tenon": Tenon, - "TenonWidth": TenonWidth, - "TenonHeight": TenonHeight, - "ReferencePlaneID": ref_frame_id - } - - - @classmethod - def apply_processings(cls, joint, parts): - """ - Apply processings to the joint and its associated parts. - - Parameters - ---------- - joint : :class:`~compas_timber.connections.joint.Joint` - The joint object. - parts : dict - A dictionary of the BTLxParts connected by this joint, with part keys as the dictionary keys. - - Returns - ------- - None - - """ - - main_part = parts[str(joint.main_beam.key)] - cross_part = parts[str(joint.cross_beam.key)] - cut_plane, ref_plane = joint.get_main_cutting_plane() - - if joint.birdsmouth: - joint.calc_params_birdsmouth() - ref_face = main_part.beam.faces[joint.btlx_params_main["ReferencePlaneID"]] - joint.btlx_params_main["ReferencePlaneID"] = str(main_part.reference_surface_from_beam_face(ref_face)) - main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_main, "T-Butt Joint")) - elif joint.stepjoint: - joint.calc_params_stepjoint_heel() - ref_face = main_part.beam.faces[joint.btlx_params_main["ReferencePlaneID"]] - joint.btlx_params_main["ReferencePlaneID"] = str(main_part.reference_surface_from_beam_face(ref_face)) - main_part.processings.append(BTLxStepJoint.create_process(joint.btlx_params_main, "T-Butt Joint")) - else: - main_part.processings.append(BTLxJackCut.create_process(main_part, cut_plane, "T-Butt Joint")) - - joint.btlx_params_cross["reference_plane_id"] = cross_part.reference_surface_from_beam_face(ref_plane) - if joint.mill_depth > 0: - joint.btlx_params_cross["machining_limits"] = {"FaceLimitedFront": "no", "FaceLimitedBack": "no"} - cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_cross, "T-Butt Joint")) - - - -BTLx.register_joint(TButtJoint, TButtFactory) From 40e05a330a0ccf8636efd01306d1e77751375665 Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 14 May 2024 18:37:16 +0200 Subject: [PATCH 74/82] try to debud drilling params serialization issue --- src/compas_timber/connections/l_halflap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas_timber/connections/l_halflap.py b/src/compas_timber/connections/l_halflap.py index 6b882178bd..6fed6414bb 100644 --- a/src/compas_timber/connections/l_halflap.py +++ b/src/compas_timber/connections/l_halflap.py @@ -206,7 +206,7 @@ def calc_params_drilling_main(self): if self.ends[str(self.cross_beam.key)] == "start": start_x = 30.0 else: - start_x = self.cross_beam.blank_length - 30.0 + start_x = float(self.cross_beam.blank_length - 30.0) self.btlx_drilling_params_main = { "ReferencePlaneID": self.bottom_main_plane, From b3529b2f9cb7209e4a1259f5307c82cafc05cc77 Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 15 May 2024 17:21:55 +0200 Subject: [PATCH 75/82] fix width and height --- src/compas_timber/fabrication/btlx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index fede778539..157e6aaa9e 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -183,8 +183,8 @@ def __init__(self, beam): self.beam = beam self.key = beam.key self.length = beam.blank_length - self.width = beam.height - self.height = beam.width + self.width = beam.width + self.height = beam.height self.frame = Frame( self.beam.long_edges[2].closest_point(self.beam.blank_frame.point), beam.frame.xaxis, From 201b558be9081e26d1a7bfd4301e8e1e0bdab38e Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 15 May 2024 17:22:27 +0200 Subject: [PATCH 76/82] debug l_half_lap --- src/compas_timber/connections/l_halflap.py | 66 +++++++++++++--------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/src/compas_timber/connections/l_halflap.py b/src/compas_timber/connections/l_halflap.py index 6fed6414bb..9055f0bf66 100644 --- a/src/compas_timber/connections/l_halflap.py +++ b/src/compas_timber/connections/l_halflap.py @@ -1,7 +1,13 @@ from compas.geometry import Frame +from compas.geometry import Line +from compas.geometry import Point +from compas.geometry import Vector +from compas.geometry import intersection_line_line from compas.geometry import angle_vectors +from compas.geometry import cross_vectors from compas_timber.parts import CutFeature from compas_timber.parts import MillVolume +from compas_timber.parts import DrillFeature from .joint import BeamJoinningError from .solver import JointTopology @@ -65,8 +71,7 @@ def __init__( self.btlx_drilling_params_main = {} self.features = [] self.test = [] - self.top_cross_plane = self.get_world_top_bottom_faces(self.cross_beam)[0] - self.bottom_main_plane = self.get_world_top_bottom_faces(self.main_beam)[1] + self.top_plane, self.bottom_plane = self.get_world_top_bottom_faces(self.cross_beam) @property def __data__(self): @@ -106,7 +111,6 @@ def add_extensions(self): extension_plane_main = self.get_face_most_towards_beam(self.main_beam, self.cross_beam, ignore_ends=True)[1] start_main, end_main = self.main_beam.extension_to_plane(extension_plane_main) self.main_beam.add_blank_extension(start_main, end_main, self.key) - print("extension_plane_main", start_main, end_main, extension_plane_main) extension_plane_cross = self.get_face_most_towards_beam(self.cross_beam, self.main_beam, ignore_ends=True)[1] start_cross, end_cross = self.cross_beam.extension_to_plane(extension_plane_cross) self.cross_beam.add_blank_extension(start_cross, end_cross, self.key) @@ -115,18 +119,21 @@ def add_features(self): assert self.main_beam and self.cross_beam try: - main_cutting_frame = self.main_beam.faces[self.bottom_main_plane] - cross_cutting_frame = self.cross_beam.faces[self.top_cross_plane] + if self.main_beam.length < self.cross_beam.length: + main_cutting_frame = self.main_beam.faces[self.top_plane] + cross_cutting_frame = self.cross_beam.faces[self.bottom_plane] + + else: + main_cutting_frame = self.main_beam.faces[self.bottom_plane] + cross_cutting_frame = self.cross_beam.faces[self.top_plane] + negative_brep_main_beam, negative_brep_cross_beam = self._create_negative_volumes() except Exception as ex: - print(ex) raise BeamJoinningError(beams=self.beams, joint=self, debug_info=str(ex)) # call functions to calculate the parameters self.calc_params_cross() self.calc_params_main() - if self.drill_diameter > 0: - self.calc_params_drilling_main() main_volume = MillVolume(negative_brep_main_beam) cross_volume = MillVolume(negative_brep_cross_beam) @@ -141,6 +148,10 @@ def add_features(self): f_main = CutFeature(trim_frame) self.main_beam.add_features(f_main) + if self.drill_diameter > 0: + self.cross_beam.add_features(DrillFeature(*self.calc_params_drilling_main())) + self.features.append(DrillFeature(*self.calc_params_drilling_main())) + self.features = [main_volume, cross_volume, f_main, f_cross] def get_world_top_bottom_faces(self, beam): @@ -157,14 +168,14 @@ def calc_params_main(self): start_x = 0.0 else: start_x = self.main_beam.blank_length - self.btlx_params_main["ReferencePlaneID"] = str(self.bottom_main_plane + 1) + self.btlx_params_main["ReferencePlaneID"] = str(self.bottom_plane) self.btlx_params_main["orientation"] = self.ends[str(self.main_beam.key)] self.btlx_params_main["start_x"] = start_x - print("start_x", start_x) self.btlx_params_main["start_y"] = 0.0 - self.btlx_params_main["depth"] = 30.0 - self.btlx_params_main["width"] = 60.0 self.btlx_params_main["length"] = 60.0 + self.btlx_params_main["width"] = 30.0 + self.btlx_params_main["depth"] = 60.0 + self.btlx_params_main["machining_limits"] = { "FaceLimitedFront": "no", "FaceLimitedBack": "no", @@ -175,15 +186,13 @@ def calc_params_cross(self): start_x = 0.0 else: start_x = self.cross_beam.blank_length - - self.btlx_params_cross["ReferencePlaneID"] = str(self.top_cross_plane + 1) + self.btlx_params_cross["ReferencePlaneID"] = str(self.top_plane) self.btlx_params_cross["orientation"] = self.ends[str(self.cross_beam.key)] self.btlx_params_cross["start_x"] = start_x - print("start_x", start_x) self.btlx_params_cross["start_y"] = 0.0 self.btlx_params_cross["length"] = 60.0 - self.btlx_params_cross["width"] = 60.0 - self.btlx_params_cross["depth"] = 30.0 + self.btlx_params_cross["width"] = 30.0 + self.btlx_params_cross["depth"] = 60.0 self.btlx_params_cross["machining_limits"] = { "FaceLimitedFront": "no", "FaceLimitedBack": "no", @@ -203,13 +212,13 @@ def calc_params_drilling_main(self): dict: A dictionary containing the calculated parameters for the drilling joint """ - if self.ends[str(self.cross_beam.key)] == "start": + if self.ends[str(self.main_beam.key)] == "start": start_x = 30.0 else: - start_x = float(self.cross_beam.blank_length - 30.0) + start_x = self.main_beam.blank_length - 30.0 self.btlx_drilling_params_main = { - "ReferencePlaneID": self.bottom_main_plane, + "ReferencePlaneID": str(self.bottom_plane+1), "StartX": start_x, "StartY": 30.0, "Angle": 0.0, @@ -219,9 +228,14 @@ def calc_params_drilling_main(self): "Depth": 0.0, } - # # Rhino geometry visualization - # line = Line(start_point, line_point) - # line.start.translate(-line.vector) - # normal_centerline_angle = 180-math.degrees(ref_frame.zaxis.angle(self.main_beam.centerline.direction)) - # length = abs(self.cross_beam.width/(math.cos(math.radians(normal_centerline_angle)))) - # return line, self.drill_diameter, length*3 + # Rhino geometry visualization + point_xyz = intersection_line_line(self.cross_beam.centerline, self.main_beam.centerline)[1] + cross_product = cross_vectors(self.cross_beam.centerline.direction, self.main_beam.centerline.direction) + cross_vect = Vector(*cross_product)*(self.main_beam.width) + + mid_point = Point(*point_xyz) + start_point = mid_point.translated(cross_vect) + end_point = mid_point.translated(-cross_vect) + + line = Line(start_point, end_point) + return line, self.drill_diameter, line.length From 0bc62b6c84d22b95287fcda785077e1faadb9690 Mon Sep 17 00:00:00 2001 From: paulocinco Date: Wed, 15 May 2024 17:22:41 +0200 Subject: [PATCH 77/82] stepjoint + rhino geom operational for non-perp --- src/compas_timber/connections/butt_joint.py | 71 ++++++++++++------- src/compas_timber/connections/t_butt.py | 20 +++--- .../joint_factories/t_butt_factory.py | 1 - 3 files changed, 55 insertions(+), 37 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 841d1b4ac0..1d88b1f65b 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -397,6 +397,22 @@ def calc_params_stepjoint(self): dict: A dictionary containing the calculated parameters for the step joint (double cut process) """ + #check if beams are coplanar + cross_product_centerlines = self.main_beam.centerline.direction.cross(self.cross_beam.centerline.direction).unitized() + dot_product_cp_crossbnormal = float(abs(cross_product_centerlines.dot(self.cross_beam.frame.normal))) + dot_product_centerline = float(abs(self.main_beam.centerline.direction.dot(self.cross_beam.centerline.direction))) + if 0.999 < dot_product_cp_crossbnormal or dot_product_cp_crossbnormal < 0.001: + self.mill_depth = 0.0 + else: + self.stepjoint = False + return False + + # if 0.999 < dot_product_centerline or dot_product_centerline < 0.001: + # self.stepjoint = False + # return False + # else: + # self.mill_depth = 0.0 + face_dict = self._beam_side_incidence(self.cross_beam, self.main_beam, ignore_ends=True) face_keys = sorted([key for key in face_dict.keys()], key=face_dict.get) @@ -432,13 +448,15 @@ def calc_params_stepjoint(self): else: ref_face.point = ref_face.point - ref_face.yaxis * self.main_beam.width * 0.5 ref_face.point = ref_face.point + ref_face.zaxis * self.main_beam.height * 0.5 - + # print("StrutInclination", StrutInclination) if StrutInclination < 90: angle1 = (180 - StrutInclination)/2 strut_inclination = StrutInclination else: angle1 = StrutInclination/2 strut_inclination = 180 - StrutInclination + # print("angle1", angle1) + # print("strut_inclination", strut_inclination) buried_depth = math.sin(math.radians(90-strut_inclination))*self.main_beam.width/2 blank_vert_depth = self.cross_beam.width/2 - buried_depth @@ -464,7 +482,9 @@ def calc_params_stepjoint(self): Angle1 = angle2 Angle2 = angle1 - self.sj_main_sub_volume = Brep.from_box(self.cross_beam.blank) + # print("Angle1", Angle1) + # print("Angle2", Angle2) + Inclination1 = 90.0 Inclination2 = 90.0 @@ -480,7 +500,6 @@ def calc_params_stepjoint(self): } #find params lap cross beam - angles_dict_cross = {} for i, face in enumerate(self.cross_beam.faces[0:4]): angles_dict_cross[i] = face.normal.dot(ref_face.normal) @@ -547,7 +566,7 @@ def calc_params_stepjoint(self): else: orientation = "start" Angle_cross = Angle2 - LeadAngle = (180 - Angle1) + Angle2 #correct + LeadAngle = (180 - Angle1) + Angle2 main_most_towards = self.get_face_most_towards_beam(self.cross_beam, self.main_beam, ignore_ends=True)[1] @@ -560,14 +579,6 @@ def calc_params_stepjoint(self): # print("intersection_pt", intersection_pt) # print("intersection_pt2", intersection_pt2) - face_angle1 = main_most_towards.rotated(Angle1, Vector.Zaxis(), intersection_pt) - face_angle2 = main_most_ortho.rotated(-Angle2, Vector.Zaxis(), intersection_pt2) - - doublecut_origin = Point(*intersection_plane_plane_plane(Plane.from_frame(face_angle1), Plane.from_frame(face_angle2), Plane.from_frame(ref_face))) - # print("doublecut_origin", doublecut_origin) - - self.sj_cross_sub_volume = Brep.from_box(self.main_beam.blank) - self.btlx_params_stepjoint_cross = { "orientation": orientation, "start_x": StartX_cross, @@ -580,22 +591,32 @@ def calc_params_stepjoint(self): } + #brep for main beam sub volume + if (inter_param > 0.5 and StrutInclination < 90) or (inter_param < 0.5 and StrutInclination > 90): + self.sj_main_sub_volume0 = Brep.from_box(self.cross_beam.blank) + self.sj_main_sub_volume0.rotate(math.radians(180+Angle_cross+LeadAngle), ref_face.normal, intersection_pt2) + self.sj_main_sub_volume1 = Brep.from_box(self.cross_beam.blank) + self.sj_main_sub_volume1.rotate(math.radians(Angle_cross), ref_face.normal, intersection_pt) + else: + self.sj_main_sub_volume0 = Brep.from_box(self.cross_beam.blank) + self.sj_main_sub_volume0.rotate(math.radians(Angle_cross), ref_face.normal, intersection_pt2) + self.sj_main_sub_volume1 = Brep.from_box(self.cross_beam.blank) + self.sj_main_sub_volume1.rotate(math.radians(180+Angle_cross+LeadAngle), ref_face.normal, intersection_pt) + + #brep for cross beam sub volume pts_ph = [worldxy_xypoint, intersection_pt, intersection_pt2] vertices_ph_sj_cross = pts_ph vertices_ph_sj_cross.extend([pt.translated(-ref_face.normal*60) for pt in pts_ph]) - self.ph_sj_cross = Polyhedron(vertices_ph_sj_cross, [[0, 1, 2], [3, 4, 5], [0, 1, 4, 3], [1, 2, 5, 4], [0, 3, 5, 2]]) - # self.brep_sj_cross = Brep.to_brep(self.ph_sj_cross) - # print self.ph_sj_cross - # print (vertices) - # polyline = Polyline([worldxy_xypoint, intersection_pt, intersection_pt2, worldxy_xypoint]) - # pl_curve = Curve([worldxy_xypoint, intersection_pt, intersection_pt2, worldxy_xypoint]) - # print(type(pl_curve)) - # print(polyline.is_closed) - # # print(-ref_face.normal*60) - # brep = Brep.from_extrusion(polyline, -ref_face.normal*60.0) - # print(brep) - # print(brep.is_closed) - # print(brep.vertices) + # print(vertices_ph_sj_cross) + if (inter_param > 0.5 and StrutInclination < 90) or (inter_param < 0.5 and StrutInclination > 90): + # print("yes") + self.ph_sj_cross = Polyhedron(vertices_ph_sj_cross, [[0, 1, 2], [3, 5, 4], [0, 3, 4, 1], [1, 4, 5, 2], [0, 2, 5, 3]]) + else: + # print("no") + self.ph_sj_cross = Polyhedron(vertices_ph_sj_cross, [[0, 2, 1], [3, 4, 5], [0, 1, 4, 3], [1, 2, 5, 4], [0, 3, 5, 2]]) + self.brep_sj_cross = Brep.from_mesh(self.ph_sj_cross) + # print(self.brep_sj_cross) + return True diff --git a/src/compas_timber/connections/t_butt.py b/src/compas_timber/connections/t_butt.py index e9cc90cd1a..e472f9c7a7 100644 --- a/src/compas_timber/connections/t_butt.py +++ b/src/compas_timber/connections/t_butt.py @@ -86,8 +86,15 @@ def add_features(self): except Exception as ex: raise BeamJoinningError(beams=self.beams, joint=self, debug_info=str(ex)) - self.features = [] - if self.mill_depth: + if self.stepjoint: + if self.calc_params_stepjoint(): + self.main_beam.add_features(BrepSubtraction(self.sj_main_sub_volume0)) + self.features.append(BrepSubtraction(self.sj_main_sub_volume0)) + self.main_beam.add_features(BrepSubtraction(self.sj_main_sub_volume1)) + self.features.append(BrepSubtraction(self.sj_main_sub_volume1)) + self.cross_beam.add_features(BrepSubtraction(self.brep_sj_cross)) + self.features.append(BrepSubtraction(self.brep_sj_cross)) + if self.mill_depth > 0: self.cross_beam.add_features(MillVolume(self.subtraction_volume())) self.features.append(MillVolume(self.subtraction_volume())) do_jack = False @@ -103,12 +110,3 @@ def add_features(self): if self.drill_diameter > 0: self.cross_beam.add_features(DrillFeature(*self.calc_params_drilling())) self.features.append(DrillFeature(*self.calc_params_drilling())) - if self.stepjoint: - if self.calc_params_stepjoint(): - self.main_beam.add_features(BrepSubtraction(self.sj_main_sub_volume))#not correct - self.features.append(BrepSubtraction(self.sj_main_sub_volume))#not correct - # print(self.ph_sj_cross) - self.cross_beam.add_features(BrepSubtraction(self.sj_cross_sub_volume))#not correct - self.features.append(BrepSubtraction(self.sj_cross_sub_volume))#not correct - # self.cross_beam.add_features(MillVolume(self.ph_sj_cross))#not correct - # self.features.append(MillVolume(self.ph_sj_cross))#not correct diff --git a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py index 74fafae5ab..df2c4f1392 100644 --- a/src/compas_timber/fabrication/joint_factories/t_butt_factory.py +++ b/src/compas_timber/fabrication/joint_factories/t_butt_factory.py @@ -42,7 +42,6 @@ def apply_processings(cls, joint, parts): ref_face = main_part.beam.faces[joint.ref_face_id] joint.btlx_params_stepjoint_main["ReferencePlaneID"] = str(main_part.reference_surface_from_beam_face(ref_face)) main_part.processings.append(BTLxDoubleCut.create_process(joint.btlx_params_stepjoint_main, "T-Butt Joint")) - ref_face_cross = cross_part.beam.faces[joint.cross_face_id] joint.btlx_params_stepjoint_cross["ReferencePlaneID"] = str(cross_part.reference_surface_from_beam_face(ref_face_cross)) cross_part.processings.append(BTLxLap.create_process(joint.btlx_params_stepjoint_cross, "T-Butt Joint pocket")) From 73331a9c041e0e7a2b0bc9a241491d97787be2dc Mon Sep 17 00:00:00 2001 From: paulocinco Date: Wed, 15 May 2024 19:29:21 +0200 Subject: [PATCH 78/82] stepjoint operational in btlx and rhino, angled and perp --- src/compas_timber/connections/butt_joint.py | 49 ++++++++++++--------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index 1d88b1f65b..e4b068e2fe 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -250,7 +250,6 @@ def calc_params_birdsmouth(self): angle = angle_vectors(cross_ref_main, og_frame.yaxis, deg=True) angle2 = angle_vectors(cross_centerlines, self.main_beam.frame.zaxis, deg=True) angle2 = round(angle2, 1) - 180 - print("angle2", angle2+180) threshold_angle = 3.0 # if angle < 1.0 or angle > 179.0: # self.birdsmouth = False @@ -407,6 +406,7 @@ def calc_params_stepjoint(self): self.stepjoint = False return False + #######ACTIVATE THIS IF YOU DONT WANT STEPJOINT WHEN PERPENDICULAR # if 0.999 < dot_product_centerline or dot_product_centerline < 0.001: # self.stepjoint = False # return False @@ -417,8 +417,6 @@ def calc_params_stepjoint(self): face_dict = self._beam_side_incidence(self.cross_beam, self.main_beam, ignore_ends=True) face_keys = sorted([key for key in face_dict.keys()], key=face_dict.get) - # dot = self.main_beam.centerline.direction.dot(self.cross_beam.centerline.direction) - if self.main_beam.centerline.end.on_line(self.cross_beam.centerline): centerline_vec = self.main_beam.centerline.direction else: @@ -448,15 +446,13 @@ def calc_params_stepjoint(self): else: ref_face.point = ref_face.point - ref_face.yaxis * self.main_beam.width * 0.5 ref_face.point = ref_face.point + ref_face.zaxis * self.main_beam.height * 0.5 - # print("StrutInclination", StrutInclination) + if StrutInclination < 90: angle1 = (180 - StrutInclination)/2 strut_inclination = StrutInclination else: angle1 = StrutInclination/2 strut_inclination = 180 - StrutInclination - # print("angle1", angle1) - # print("strut_inclination", strut_inclination) buried_depth = math.sin(math.radians(90-strut_inclination))*self.main_beam.width/2 blank_vert_depth = self.cross_beam.width/2 - buried_depth @@ -482,9 +478,20 @@ def calc_params_stepjoint(self): Angle1 = angle2 Angle2 = angle1 - # print("Angle1", Angle1) - # print("Angle2", Angle2) - + if StrutInclination == 90.0: + startx_90deg = self.main_beam.width/4 + starty_90deg = self.main_beam.width/2 + angle_90deg = math.degrees(math.atan(startx_90deg/starty_90deg)) + if self.ends[str(self.main_beam.key)] == "start": + StartX = startx_90deg + StartY = starty_90deg + Angle1 = 90+angle_90deg + Angle2 = 90-angle_90deg + else: + StartX = self.main_beam.blank_length - startx_90deg + StartY = starty_90deg + Angle1 = 90+angle_90deg + Angle2 = 90-angle_90deg Inclination1 = 90.0 Inclination2 = 90.0 @@ -503,7 +510,6 @@ def calc_params_stepjoint(self): angles_dict_cross = {} for i, face in enumerate(self.cross_beam.faces[0:4]): angles_dict_cross[i] = face.normal.dot(ref_face.normal) - # print(angles_dict) self.cross_face_id = max(angles_dict_cross.keys(), key=angles_dict_cross.get) cross_face = self.cross_beam.faces[self.cross_face_id] @@ -516,11 +522,8 @@ def calc_params_stepjoint(self): cross_face.point = cross_face.point + cross_face.zaxis * self.cross_beam.height * 0.5 main_xypoint = Point(StartX, StartY, 0) - # print("main_xypoint", main_xypoint) worldxy_xypoint = main_xypoint.transformed(Transformation.from_frame_to_frame(Frame.worldXY(), ref_face)) - # print("worldxy_xypoint", worldxy_xypoint) cross_xy_point = worldxy_xypoint.transformed(Transformation.from_frame_to_frame(cross_face, Frame.worldXY())) - # print("cross_xy_point", cross_xy_point) StartX_cross = cross_xy_point[0] StartY_cross = cross_xy_point[1] @@ -537,7 +540,6 @@ def calc_params_stepjoint(self): else: Angle_cross = Angle2 LeadAngle = 180 - (Angle1 - Angle2) - else: if self.ends[str(self.main_beam.key)] == "start": Angle_cross = 180 - Angle1 @@ -545,6 +547,14 @@ def calc_params_stepjoint(self): else: Angle_cross = Angle2 LeadAngle = 180 - (Angle1 - Angle2) + elif StrutInclination == 90.0: + orientation = self.ends[str(self.cross_beam.key)] + Angle_cross = angle_90deg + LeadAngle = 180-angle_90deg*2 + if self.ends[str(self.cross_beam.key)] == "end": + self.cross_face_id = min(angles_dict_cross.keys(), key=angles_dict_cross.get) + cross_face = self.cross_beam.faces[self.cross_face_id] + StartY_cross = self.cross_beam.width - StartY_cross else: if self.ends[str(self.cross_beam.key)] == "start": if self.ends[str(self.main_beam.key)] == "start": @@ -576,8 +586,6 @@ def calc_params_stepjoint(self): intersection_pt = Point(*intersection_plane_plane_plane(Plane.from_frame(main_most_towards), Plane.from_frame(cross_most_ortho), Plane.from_frame(ref_face))) intersection_pt2 = Point(*intersection_plane_plane_plane(Plane.from_frame(main_most_ortho), Plane.from_frame(cross_most_ortho), Plane.from_frame(ref_face))) - # print("intersection_pt", intersection_pt) - # print("intersection_pt2", intersection_pt2) self.btlx_params_stepjoint_cross = { "orientation": orientation, @@ -597,6 +605,11 @@ def calc_params_stepjoint(self): self.sj_main_sub_volume0.rotate(math.radians(180+Angle_cross+LeadAngle), ref_face.normal, intersection_pt2) self.sj_main_sub_volume1 = Brep.from_box(self.cross_beam.blank) self.sj_main_sub_volume1.rotate(math.radians(Angle_cross), ref_face.normal, intersection_pt) + elif StrutInclination == 90.0: + self.sj_main_sub_volume0 = Brep.from_box(self.cross_beam.blank) + self.sj_main_sub_volume0.rotate(math.radians(angle_90deg), ref_face.normal, intersection_pt2) + self.sj_main_sub_volume1 = Brep.from_box(self.cross_beam.blank) + self.sj_main_sub_volume1.rotate(math.radians(-angle_90deg), ref_face.normal, intersection_pt) else: self.sj_main_sub_volume0 = Brep.from_box(self.cross_beam.blank) self.sj_main_sub_volume0.rotate(math.radians(Angle_cross), ref_face.normal, intersection_pt2) @@ -608,15 +621,11 @@ def calc_params_stepjoint(self): pts_ph = [worldxy_xypoint, intersection_pt, intersection_pt2] vertices_ph_sj_cross = pts_ph vertices_ph_sj_cross.extend([pt.translated(-ref_face.normal*60) for pt in pts_ph]) - # print(vertices_ph_sj_cross) if (inter_param > 0.5 and StrutInclination < 90) or (inter_param < 0.5 and StrutInclination > 90): - # print("yes") self.ph_sj_cross = Polyhedron(vertices_ph_sj_cross, [[0, 1, 2], [3, 5, 4], [0, 3, 4, 1], [1, 4, 5, 2], [0, 2, 5, 3]]) else: - # print("no") self.ph_sj_cross = Polyhedron(vertices_ph_sj_cross, [[0, 2, 1], [3, 4, 5], [0, 1, 4, 3], [1, 2, 5, 4], [0, 3, 5, 2]]) self.brep_sj_cross = Brep.from_mesh(self.ph_sj_cross) - # print(self.brep_sj_cross) return True From ab24c046a0944612d3b7435ab54234037fc5ac9f Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 16 May 2024 09:37:18 +0200 Subject: [PATCH 79/82] remove print statement --- src/compas_timber/connections/butt_joint.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compas_timber/connections/butt_joint.py b/src/compas_timber/connections/butt_joint.py index e4b068e2fe..a3c0f1e53f 100644 --- a/src/compas_timber/connections/butt_joint.py +++ b/src/compas_timber/connections/butt_joint.py @@ -256,7 +256,6 @@ def calc_params_birdsmouth(self): # return False if abs(angle2)%90 <= threshold_angle or abs((abs(angle2)-90)%90) <= threshold_angle: - print("smaller than threshold", abs(angle2%90)) self.birdsmouth = False return False From 0c5db64ef8dd2c71ecc304d8edf90a9ce7a17b20 Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 16 May 2024 14:50:48 +0200 Subject: [PATCH 80/82] put text to 15mm --- src/compas_timber/fabrication/joint_factories/text_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas_timber/fabrication/joint_factories/text_factory.py b/src/compas_timber/fabrication/joint_factories/text_factory.py index 6276ed679a..787fc4c89a 100644 --- a/src/compas_timber/fabrication/joint_factories/text_factory.py +++ b/src/compas_timber/fabrication/joint_factories/text_factory.py @@ -41,7 +41,7 @@ def get_text_engraving_params(part): "AlignmentVertical": "bottom", #default(bottom) in easybeam "AlignmentHorizontal": "left", #default(left) in easybeam "AlignmentMultiline": "left", #default(left) in easybeam - "TextHeight": 20.0, + "TextHeight": 15.0, "Text": part.ID } From 7786fd70e6f8330e8c758bf2dc3754f5dc54e382 Mon Sep 17 00:00:00 2001 From: obucklin Date: Fri, 17 May 2024 10:43:22 +0200 Subject: [PATCH 81/82] oops --- .../fabrication/joint_factories/l_halflap_factory.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compas_timber/fabrication/joint_factories/l_halflap_factory.py b/src/compas_timber/fabrication/joint_factories/l_halflap_factory.py index 2dfe14262d..34f7270563 100644 --- a/src/compas_timber/fabrication/joint_factories/l_halflap_factory.py +++ b/src/compas_timber/fabrication/joint_factories/l_halflap_factory.py @@ -39,5 +39,4 @@ def apply_processings(cls, joint, parts): if joint.drill_diameter > 0: main_part.processings.append(BTLxDrilling.create_process(joint.btlx_drilling_params_main, "L-HalfLap Joint")) - BTLx.register_joint(LHalfLapJoint, LHalfLapFactory) From 65f7bd683f4a938b0c8facb086e540b4141a7d08 Mon Sep 17 00:00:00 2001 From: obucklin Date: Fri, 17 May 2024 10:47:01 +0200 Subject: [PATCH 82/82] updated old BTLx component --- src/compas_timber/ghpython/components/CT_BTLx/code.py | 2 +- src/compas_timber/ghpython/components/CT_BTLx/metadata.json | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/compas_timber/ghpython/components/CT_BTLx/code.py b/src/compas_timber/ghpython/components/CT_BTLx/code.py index 8ec8dcf43e..a2d01ebe46 100644 --- a/src/compas_timber/ghpython/components/CT_BTLx/code.py +++ b/src/compas_timber/ghpython/components/CT_BTLx/code.py @@ -23,4 +23,4 @@ def RunScript(self, assembly, path, write): path += ".btlx" with open(path, "w") as f: f.write(btlx.btlx_string()) - return btlx.btlx_string() + return btlx, btlx.btlx_string() diff --git a/src/compas_timber/ghpython/components/CT_BTLx/metadata.json b/src/compas_timber/ghpython/components/CT_BTLx/metadata.json index 8dcbacdf25..84c1254492 100644 --- a/src/compas_timber/ghpython/components/CT_BTLx/metadata.json +++ b/src/compas_timber/ghpython/components/CT_BTLx/metadata.json @@ -31,7 +31,11 @@ ], "outputParameters": [ { - "name": "BTLx", + "name": "BTLx Object", + "description": "BTLx object to pass to other components." + }, + { + "name": "BTLx String", "description": "Pretty BTLx string" } ]