From 50d5fef4b15b4485cd0603b961847b9e334c3687 Mon Sep 17 00:00:00 2001 From: papachap Date: Mon, 22 Jul 2024 20:49:21 +0200 Subject: [PATCH 01/63] initialization of step_joint_feature --- src/compas_timber/_fabrication/step_joint.py | 319 +++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 src/compas_timber/_fabrication/step_joint.py diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py new file mode 100644 index 000000000..ad4ec46d3 --- /dev/null +++ b/src/compas_timber/_fabrication/step_joint.py @@ -0,0 +1,319 @@ +import math + +from compas.geometry import BrepTrimmingError +from compas.geometry import Frame +from compas.geometry import Line +from compas.geometry import Plane +from compas.geometry import Rotation +from compas.geometry import Vector +from compas.geometry import angle_vectors_signed +from compas.geometry import distance_point_point +from compas.geometry import intersection_line_plane +from compas.geometry import is_point_behind_plane + +from compas_timber.elements import FeatureApplicationError + +from .btlx_process import BTLxProcess +from .btlx_process import OrientationType + + +class StepJoint(BTLxProcess): + """Represents a Step Joint feature to be made on a beam. + + Parameters + ---------- + orientation : int + The orientation of the cut. Must be either OrientationType.START or OrientationType.END. + start_x : float + The start x-coordinate of the cut in parametric space of the reference side. -100000.0 < start_x < 100000.0. + strut_inclination : float + The inclination of the strut. 0.1 < strut_inclination < 179.9. + step_depth : float + The depth of the step. 0.0 < step_depth < 50000.0. + heel_depth : float + The depth of the heel. 0.0 < heel_depth < 50000.0. + step_shape : str + The shape of the step. Must be either 'double', 'step', 'heel', or 'taperedheel'. + tenon : str + The presence of a tenon. Must be either 'no' or 'yes'. + tenon_width : float + The width of the tenon. 0.0 < tenon_width < 1000.0. + tenon_height : float + The height of the tenon. 0.0 < tenon_height < 1000.0. + + """ + + @property + def __data__(self): + data = super(StepJoint, self).__data__ + data["orientation"] = self.orientation + data["start_x"] = self.start_x + data["strut_inclination"] = self.strut_inclination + data["step_depth"] = self.step_depth + data["heel_depth"] = self.heel_depth + data["step_shape"] = self.step_shape + data["tenon"] = self.tenon + data["tenon_width"] = self.tenon_width + data["tenon_height"] = self.tenon_height + return data + + def __init__(self, orientation, start_x=0.0, strut_inclination=90.0, step_depth=20.0, heel_depth=20.0, step_shape="double", tenon="no", tenon_width=40.0, tenon_height=40.0, **kwargs): + super(StepJoint, self).__init__(**kwargs) + self._orientation = None + self._start_x = None + self._strut_inclination = None + self._step_depth = None + self._heel_depth = None + self._step_shape = None + self._tenon = None + self._tenon_width = None + self._tenon_height = None + + self.orientation = orientation + self.start_x = start_x + self.strut_inclination = strut_inclination + self.step_depth = step_depth + self.heel_depth = heel_depth + self.step_shape = step_shape + self.tenon = tenon + self.tenon_width = tenon_width + self.tenon_height = tenon_height + + ######################################################################## + # Properties + ######################################################################## + + @property + def orientation(self): + return self._orientation + + @orientation.setter + def orientation(self, orientation): + if orientation not in [OrientationType.START, OrientationType.END]: + raise ValueError("Orientation must be either OrientationType.START or OrientationType.END.") + self._orientation = orientation + + @property + def start_x(self): + return self._start_x + + @start_x.setter + def start_x(self, start_x): + if start_x > 100000.0 or start_x < -100000.0: + raise ValueError("StartX must be between -100000.0 and 100000.") + self._start_x = start_x + + @property + def strut_inclination(self): + return self._strut_inclination + + @strut_inclination.setter + def strut_inclination(self, strut_inclination): + if strut_inclination < 0.1 or strut_inclination > 179.9: + raise ValueError("StrutInclination must be between 0.1 and 179.9.") + self._strut_inclination = strut_inclination + + @property + def step_depth(self): + return self._step_depth + + @step_depth.setter + def step_depth(self, step_depth): + if step_depth > 50000.0: + raise ValueError("StepDepth must be less than 50000.0.") + self._step_depth = step_depth + + @property + def heel_depth(self): + return self._heel_depth + + @heel_depth.setter + def heel_depth(self, heel_depth): + if heel_depth > 50000.0: + raise ValueError("HeelDepth must be less than 50000.0.") + self._heel_depth = heel_depth + + @property + def step_shape(self): + return self._step_shape + + @step_shape.setter + def step_shape(self, step_shape): + if step_shape not in ["double", "step", "heel", "taperedheel"]: + raise ValueError("StepShape must be either 'double', 'step', 'heel', or 'taperedheel'.") + self._step_shape = step_shape + + @property + def tenon(self): + return self._tenon + + @tenon.setter + def tenon(self, tenon): + if tenon not in ["no", "yes"]: + raise ValueError("Tenon must be either 'no' or 'yes'.") + self._tenon = tenon + + @property + def tenon_width(self): + return self._tenon_width + + @tenon_width.setter + def tenon_width(self, tenon_width): + if tenon_width > 1000.0: + raise ValueError("TenonWidth must be less than 1000.0.") + self._tenon_width = tenon_width + + @property + def tenon_height(self): + return self._tenon_height + + @tenon_height.setter + def tenon_height(self, tenon_height): + if tenon_height > 1000.0: + raise ValueError("TenonHeight must be less than 1000.0.") + self._tenon_height = tenon_height + + ######################################################################## + # Alternative constructors + ######################################################################## + + @classmethod + def from_plane_and_beam(cls, plane, beam, ref_side_index=0): + """Create a JackRafterCut instance from a cutting plane and the beam it should cut. + + Parameters + ---------- + plane : :class:`~compas.geometry.Plane` or :class:`~compas.geometry.Frame` + The cutting plane. + beam : :class:`~compas_timber.elements.Beam` + The beam that is cut by this instance. + ref_side_index : int, optional + The reference side index of the beam to be cut. Default is 0 (i.e. RS1). + + Returns + ------- + :class:`~compas_timber.fabrication.JackRafterCut` + + """ + # type: (Plane | Frame, Beam, int) -> JackRafterCut + if isinstance(plane, Frame): + plane = Plane.from_frame(plane) + start_y = 0.0 + start_depth = 0.0 + ref_side = beam.ref_sides[ref_side_index] # TODO: is this arbitrary? + ref_edge = Line.from_point_and_vector(ref_side.point, ref_side.xaxis) + orientation = cls._calculate_orientation(ref_side, plane) + + point_start_x = intersection_line_plane(ref_edge, plane) + if point_start_x is None: + raise ValueError("Plane does not intersect with beam.") + + start_x = distance_point_point(ref_edge.point, point_start_x) + angle = cls._calculate_angle(ref_side, plane, orientation) + inclination = cls._calculate_inclination(ref_side, plane, orientation) + return cls(orientation, start_x, start_y, start_depth, angle, inclination, ref_side_index=ref_side_index) + + @staticmethod + def _calculate_orientation(ref_side, cutting_plane): + # orientation is START if cutting plane normal points towards the start of the beam and END otherwise + # essentially if the start is being cut or the end + if is_point_behind_plane(ref_side.point, cutting_plane): + return OrientationType.END + else: + return OrientationType.START + + @staticmethod + def _calculate_angle(ref_side, plane, orientation): + # vector rotation direction of the plane's normal in the vertical direction + angle_vector = Vector.cross(ref_side.zaxis, plane.normal) + angle = angle_vectors_signed(ref_side.xaxis, angle_vector, ref_side.zaxis, deg=True) + if orientation == OrientationType.START: + return 180 - abs(angle) # get the other side of the angle + else: + return abs(angle) + + @staticmethod + def _calculate_inclination(ref_side, plane, orientation): + # vector rotation direction of the plane's normal in the horizontal direction + inclination_vector = Vector.cross(ref_side.yaxis, plane.normal) + inclination = angle_vectors_signed(ref_side.xaxis, inclination_vector, ref_side.yaxis, deg=True) + if orientation == OrientationType.START: + return 180 - abs(inclination) # get the other side of the angle + else: + return abs(inclination) + + ######################################################################## + # Methods + ######################################################################## + + def apply(self, geometry, beam): + """Apply the feature to the beam geometry. + + Parameters + ---------- + geometry : :class:`~compas.geometry.Brep` + The beam geometry to be cut. + beam : :class:`compas_timber.elements.Beam` + The beam that is cut by this instance. + + Raises + ------ + :class:`~compas_timber.elements.FeatureApplicationError` + If the cutting plane does not intersect with beam geometry. + + Returns + ------- + :class:`~compas.geometry.Brep` + The resulting geometry after processing + + """ + # type: (Brep, Beam) -> Brep + cutting_plane = self.plane_from_params_and_beam(beam) + try: + return geometry.trimmed(cutting_plane) + except BrepTrimmingError: + raise FeatureApplicationError( + cutting_plane, + geometry, + "The cutting plane does not intersect with beam geometry.", + ) + + def plane_from_params_and_beam(self, beam): + """Calculates the cutting plane from the machining parameters in this instance and the given beam + + Parameters + ---------- + beam : :class:`compas_timber.elements.Beam` + The beam that is cut by this instance. + + Returns + ------- + :class:`compas.geometry.Plane` + The cutting plane. + + """ + # type: (Beam) -> Plane + assert self.angle is not None + assert self.inclination is not None + + # start with a plane aligned with the ref side but shifted to the start_x of the cut + ref_side = beam.side_as_surface(self.ref_side_index) + p_origin = ref_side.point_at(self.start_x, 0.0) + cutting_plane = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # normal pointing towards xaxis so just need the delta + horizontal_angle = math.radians(self.angle - 90) + rot_a = Rotation.from_axis_and_angle(cutting_plane.zaxis, horizontal_angle, point=p_origin) + + # normal pointing towards xaxis so just need the delta + vertical_angle = math.radians(self.inclination - 90) + rot_b = Rotation.from_axis_and_angle(cutting_plane.yaxis, vertical_angle, point=p_origin) + + cutting_plane.transform(rot_a * rot_b) + # for simplicity, we always start with normal pointing towards xaxis. + # if start is cut, we need to flip the normal + if self.orientation == OrientationType.END: + plane_normal = cutting_plane.xaxis + else: + plane_normal = -cutting_plane.xaxis + return Plane(cutting_plane.point, plane_normal) From a686051c32994efbc4e0b03b3eedb6a216fded17 Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 23 Jul 2024 17:27:00 +0200 Subject: [PATCH 02/63] initialization of step_joint_notch and method from surface and beam --- src/compas_timber/_fabrication/step_joint.py | 246 +++++++++++++------ 1 file changed, 175 insertions(+), 71 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index ad4ec46d3..d5059599d 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -6,19 +6,22 @@ from compas.geometry import Plane from compas.geometry import Rotation from compas.geometry import Vector +from compas.geometry import Surface +from compas.geometry import PlanarSurface from compas.geometry import angle_vectors_signed from compas.geometry import distance_point_point from compas.geometry import intersection_line_plane from compas.geometry import is_point_behind_plane + from compas_timber.elements import FeatureApplicationError from .btlx_process import BTLxProcess from .btlx_process import OrientationType -class StepJoint(BTLxProcess): - """Represents a Step Joint feature to be made on a beam. +class StepJointNotch(BTLxProcess): + """Represents a Step Joint Notch feature to be made on a beam. Parameters ---------- @@ -26,58 +29,78 @@ class StepJoint(BTLxProcess): The orientation of the cut. Must be either OrientationType.START or OrientationType.END. start_x : float The start x-coordinate of the cut in parametric space of the reference side. -100000.0 < start_x < 100000.0. + start_y : float + The start y-coordinate of the notch in parametric space of the reference side. -50000.0 < start_y < 50000.0. strut_inclination : float The inclination of the strut. 0.1 < strut_inclination < 179.9. + notch_limited : bool + Whether the notch is limited. If True, the notch is limited by the start_y and notch_width values. + notch_width : float + The width of the notch. notch_width < 50000.0. step_depth : float - The depth of the step. 0.0 < step_depth < 50000.0. + The depth of the step. step_depth < 50000.0. heel_depth : float - The depth of the heel. 0.0 < heel_depth < 50000.0. + The depth of the heel. heel_depth < 50000.0. + strut_height : float + The height of the strut. It is the cross beam's height. strut_height < 50000.0. step_shape : str The shape of the step. Must be either 'double', 'step', 'heel', or 'taperedheel'. - tenon : str - The presence of a tenon. Must be either 'no' or 'yes'. - tenon_width : float - The width of the tenon. 0.0 < tenon_width < 1000.0. - tenon_height : float - The height of the tenon. 0.0 < tenon_height < 1000.0. + mortise : str + The presence of a mortise. Must be either 'no' or 'yes'. + mortise_width : float + The width of the mortise. mortise_width < 1000.0. + mortise_height : float + The height of the mortise. mortise_height < 1000.0. """ @property def __data__(self): - data = super(StepJoint, self).__data__ + data = super(StepJointNotch, self).__data__ data["orientation"] = self.orientation data["start_x"] = self.start_x + data["start_y"] = self.start_y data["strut_inclination"] = self.strut_inclination + data["notch_limited"] = self.notch_limited + data["notch_width"] = self.notch_width data["step_depth"] = self.step_depth data["heel_depth"] = self.heel_depth + data["strut_height"] = self.strut_height data["step_shape"] = self.step_shape - data["tenon"] = self.tenon - data["tenon_width"] = self.tenon_width - data["tenon_height"] = self.tenon_height + data["mortise"] = self.mortise + data["mortise_width"] = self.mortise_width + data["mortise_height"] = self.mortise_height return data - def __init__(self, orientation, start_x=0.0, strut_inclination=90.0, step_depth=20.0, heel_depth=20.0, step_shape="double", tenon="no", tenon_width=40.0, tenon_height=40.0, **kwargs): - super(StepJoint, self).__init__(**kwargs) + def __init__(self, orientation, start_x=0.0, start_y=0.0, strut_inclination=90.0, notch_limited="no", notch_width=20.0, step_depth=20.0, heel_depth=20.0, strut_height=20.0, step_shape="double", mortise="no", mortise_width=40.0, mortise_height=40.0, **kwargs): + super(StepJointNotch, self).__init__(**kwargs) self._orientation = None self._start_x = None + self._start_y = None self._strut_inclination = None + self._notch_limited = None + self._notch_width = None self._step_depth = None self._heel_depth = None + self._strut_height = None self._step_shape = None - self._tenon = None - self._tenon_width = None - self._tenon_height = None + self._mortise = None + self._mortise_width = None + self._mortise_height = None self.orientation = orientation self.start_x = start_x + self.start_y = start_y self.strut_inclination = strut_inclination + self.notch_limited = notch_limited + self.notch_width = notch_width self.step_depth = step_depth self.heel_depth = heel_depth + self.strut_height = strut_height self.step_shape = step_shape - self.tenon = tenon - self.tenon_width = tenon_width - self.tenon_height = tenon_height + self.mortise = mortise + self.mortise_width = mortise_width + self.mortise_height = mortise_height ######################################################################## # Properties @@ -103,6 +126,16 @@ def start_x(self, start_x): raise ValueError("StartX must be between -100000.0 and 100000.") self._start_x = start_x + @property + def start_y(self): + return self._start_y + + @start_y.setter + def start_y(self, start_y): + if start_y > 50000.0 or start_y < -50000.0: + raise ValueError("StartY must be between -50000.0 and 50000.") + self._start_y = start_y + @property def strut_inclination(self): return self._strut_inclination @@ -113,6 +146,26 @@ def strut_inclination(self, strut_inclination): raise ValueError("StrutInclination must be between 0.1 and 179.9.") self._strut_inclination = strut_inclination + @property + def notch_limited(self): + return self._notch_limited + + @notch_limited.setter + def notch_limited(self, notch_limited): + if notch_limited not in ["no", "yes"]: + raise ValueError("NotchLimited must be either 'no' or 'yes'.") + self._notch_limited = notch_limited + + @property + def notch_width(self): + return self._notch_width + + @notch_width.setter + def notch_width(self, notch_width): + if notch_width > 50000.0: + raise ValueError("NotchWidth must be less than 50000.0.") + self._notch_width = notch_width + @property def step_depth(self): return self._step_depth @@ -133,58 +186,68 @@ def heel_depth(self, heel_depth): raise ValueError("HeelDepth must be less than 50000.0.") self._heel_depth = heel_depth + @property + def strut_height(self): + return self._strut_height + + @strut_height.setter + def strut_height(self, strut_height): + if strut_height > 50000.0: + raise ValueError("StrutHeight must be less than 50000.0.") + self._strut_height = strut_height + @property def step_shape(self): return self._step_shape - @step_shape.setter + @step_shape.setter #TODO: should this be defined automatically depending on the other parameters? (ie. if heel_depth > 0 and step_depth > 0, then step_shape = "double") def step_shape(self, step_shape): if step_shape not in ["double", "step", "heel", "taperedheel"]: raise ValueError("StepShape must be either 'double', 'step', 'heel', or 'taperedheel'.") self._step_shape = step_shape @property - def tenon(self): - return self._tenon + def mortise(self): + return self._mortise - @tenon.setter - def tenon(self, tenon): - if tenon not in ["no", "yes"]: - raise ValueError("Tenon must be either 'no' or 'yes'.") - self._tenon = tenon + @mortise.setter + def mortise(self, mortise): + if mortise not in ["no", "yes"]: + raise ValueError("Mortise must be either 'no' or 'yes'.") + self._mortise = mortise @property - def tenon_width(self): - return self._tenon_width + def mortise_width(self): + return self._mortise_width - @tenon_width.setter - def tenon_width(self, tenon_width): - if tenon_width > 1000.0: - raise ValueError("TenonWidth must be less than 1000.0.") - self._tenon_width = tenon_width + @mortise_width.setter + def mortise_width(self, mortise_width): + if mortise_width > 1000.0: + raise ValueError("MortiseWidth must be less than 1000.0.") + self._mortise_width = mortise_width @property - def tenon_height(self): - return self._tenon_height + def mortise_height(self): + return self._mortise_height - @tenon_height.setter - def tenon_height(self, tenon_height): - if tenon_height > 1000.0: - raise ValueError("TenonHeight must be less than 1000.0.") - self._tenon_height = tenon_height + @mortise_height.setter + def mortise_height(self, mortise_height): + if mortise_height > 1000.0: + raise ValueError("MortiseHeight must be less than 1000.0.") + self._mortise_height = mortise_height ######################################################################## # Alternative constructors ######################################################################## @classmethod - def from_plane_and_beam(cls, plane, beam, ref_side_index=0): - """Create a JackRafterCut instance from a cutting plane and the beam it should cut. + def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=0.0, heel_depth=0.0, tapered_heel=False, mortise_height=0.0, ref_side_index=0): + """Create a StepJointNotch instance from a cutting surface and the beam it should cut. Parameters ---------- - plane : :class:`~compas.geometry.Plane` or :class:`~compas.geometry.Frame` - The cutting plane. + surface : :class:`~compas.geometry.PlanarSurface` or :class:`~compas.geometry.Surface` + The cutting surface. beam : :class:`~compas_timber.elements.Beam` The beam that is cut by this instance. ref_side_index : int, optional @@ -192,26 +255,48 @@ def from_plane_and_beam(cls, plane, beam, ref_side_index=0): Returns ------- - :class:`~compas_timber.fabrication.JackRafterCut` + :class:`~compas_timber.fabrication.StepJointNotch` """ - # type: (Plane | Frame, Beam, int) -> JackRafterCut - if isinstance(plane, Frame): - plane = Plane.from_frame(plane) - start_y = 0.0 - start_depth = 0.0 + # type: (PlanarSurface | Surface, Beam, bool, float, float, bool, float, int) -> StepJointNotch + + # define ref_side & ref_edge ref_side = beam.ref_sides[ref_side_index] # TODO: is this arbitrary? ref_edge = Line.from_point_and_vector(ref_side.point, ref_side.xaxis) - orientation = cls._calculate_orientation(ref_side, plane) + plane = Plane.from_frame(surface.frame_at()) + # calculate orientation + orientation = cls._calculate_orientation(ref_side, plane) + # calculate start_x point_start_x = intersection_line_plane(ref_edge, plane) if point_start_x is None: raise ValueError("Plane does not intersect with beam.") - start_x = distance_point_point(ref_edge.point, point_start_x) - angle = cls._calculate_angle(ref_side, plane, orientation) - inclination = cls._calculate_inclination(ref_side, plane, orientation) - return cls(orientation, start_x, start_y, start_depth, angle, inclination, ref_side_index=ref_side_index) + # calculate start_y + point_start_y = ref_side.intersections_with_surface(surface)[0] + if point_start_y is None: + raise ValueError("Surface does not intersect with beam.") + start_y = distance_point_point(point_start_x, point_start_y) + # calculate strut_inclination + strut_inclination = cls._calculate_strut_inclination(ref_side, plane, orientation) + # calculate notch_width + if notch_limited == True: + notch_width = surface.ysize + notch_limited = "yes" + else: + notch_width = beam.width + notch_limited = "no" + # restrain step_depth & heel_depth #TODO: should those be defined automatically based on the angle? + step_depth = beam.height/2 if step_depth > beam.height/2 else step_depth # TODO: should it be constrained? + heel_depth = beam.height/2 if heel_depth > beam.height/2 else heel_depth # TODO: should it be constrained? + # define step_shape + step_shape = cls._define_step_shape(step_depth, heel_depth, tapered_heel) + # define mortise + mortise, mortise_width, mortise_height = cls._calculate_mortise(beam, mortise_height) + # define strut_height + strut_height = surface.ysize #TODO: Wrong! should have been defined by the main beam height instead + + return cls(orientation, start_x, start_y, strut_inclination, notch_limited, notch_width, step_depth, heel_depth, step_shape, mortise, mortise_width, mortise_width, strut_height) @staticmethod def _calculate_orientation(ref_side, cutting_plane): @@ -223,24 +308,43 @@ def _calculate_orientation(ref_side, cutting_plane): return OrientationType.START @staticmethod - def _calculate_angle(ref_side, plane, orientation): + def _calculate_strut_inclination(ref_side, plane, orientation): # vector rotation direction of the plane's normal in the vertical direction - angle_vector = Vector.cross(ref_side.zaxis, plane.normal) - angle = angle_vectors_signed(ref_side.xaxis, angle_vector, ref_side.zaxis, deg=True) + strut_inclination_vector = Vector.cross(ref_side.zaxis, plane.normal) + strut_inclination = angle_vectors_signed(ref_side.zaxis, plane.normal, strut_inclination_vector, deg=True) if orientation == OrientationType.START: - return 180 - abs(angle) # get the other side of the angle + return 180 - abs(strut_inclination) # get the other side of the angle else: - return abs(angle) + return abs(strut_inclination) @staticmethod - def _calculate_inclination(ref_side, plane, orientation): - # vector rotation direction of the plane's normal in the horizontal direction - inclination_vector = Vector.cross(ref_side.yaxis, plane.normal) - inclination = angle_vectors_signed(ref_side.xaxis, inclination_vector, ref_side.yaxis, deg=True) - if orientation == OrientationType.START: - return 180 - abs(inclination) # get the other side of the angle + def _define_step_shape(step_depth, heel_depth, tapered_heel): + # step_shape based on step_depth and heel_depth variables + if step_depth > 0.0 and heel_depth == 0.0: + step_shape = "step" + elif step_depth == 0.0 and heel_depth > 0.0: + if tapered_heel: + step_shape = "heel_tapered" + else: + step_shape = "heel" + elif step_depth > 0.0 and heel_depth > 0.0: + step_shape = "double" + else: + raise ValueError("at least one of step_depth or heel_depth must be greater than 0.0.") + return step_shape + + @staticmethod + def _calculate_mortise(beam, mortise_height): + # mortise based on mortise_height variable and beam dimensions + if mortise_height > 0.0: + mortise = "yes" + mortise_width = beam.width/4 #TODO: should this be predefined? Also 1/3 or 1/4? + mortise_height = beam.height if mortise_height > beam.height else mortise_height else: - return abs(inclination) + mortise = "no" + mortise_width = 0.0 + return mortise, mortise_width, mortise_height + ######################################################################## # Methods From 61698a982e367a22c4a78a631ebc4d6b7a644b30 Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 23 Jul 2024 17:42:01 +0200 Subject: [PATCH 03/63] separate method for creating a mortise --- src/compas_timber/_fabrication/step_joint.py | 22 ++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index d5059599d..ef40a2503 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -241,7 +241,7 @@ def mortise_height(self, mortise_height): ######################################################################## @classmethod - def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=0.0, heel_depth=0.0, tapered_heel=False, mortise_height=0.0, ref_side_index=0): + def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=0.0, heel_depth=0.0, tapered_heel=False, ref_side_index=0): """Create a StepJointNotch instance from a cutting surface and the beam it should cut. Parameters @@ -291,12 +291,26 @@ def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=0. heel_depth = beam.height/2 if heel_depth > beam.height/2 else heel_depth # TODO: should it be constrained? # define step_shape step_shape = cls._define_step_shape(step_depth, heel_depth, tapered_heel) - # define mortise - mortise, mortise_width, mortise_height = cls._calculate_mortise(beam, mortise_height) # define strut_height strut_height = surface.ysize #TODO: Wrong! should have been defined by the main beam height instead - return cls(orientation, start_x, start_y, strut_inclination, notch_limited, notch_width, step_depth, heel_depth, step_shape, mortise, mortise_width, mortise_width, strut_height) + return cls(orientation, start_x, start_y, strut_inclination, notch_limited, notch_width, step_depth, heel_depth, step_shape, strut_height) + + def add_mortise(self, mortise_width, mortise_height): + """Add a mortise to the existing StepJointNotch instance. + + Parameters + ---------- + mortise_width : float + The width of the mortise. mortise_width < 1000.0. + mortise_height : float + The height of the mortise. mortise_height < 1000.0. + """ + self.mortise = "yes" + self.mortise_width = mortise_width + self.mortise_height = mortise_height + # self.mortise_width = beam.width / 4 # TODO: should this be related to a beam? 1/3 or 1/4 of beam.width? + # self.mortise_height = beam.height if mortise_height > beam.height else mortise_height #TODO: should this be constrained? @staticmethod def _calculate_orientation(ref_side, cutting_plane): From bea39ff3429e4d77e089a8b58daa14924be4625d Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 23 Jul 2024 17:44:01 +0200 Subject: [PATCH 04/63] separate method for creating a mortise --- src/compas_timber/_fabrication/step_joint.py | 35 ++++++++++---------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index d5059599d..7487f842a 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -241,7 +241,7 @@ def mortise_height(self, mortise_height): ######################################################################## @classmethod - def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=0.0, heel_depth=0.0, tapered_heel=False, mortise_height=0.0, ref_side_index=0): + def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=0.0, heel_depth=0.0, tapered_heel=False, ref_side_index=0): """Create a StepJointNotch instance from a cutting surface and the beam it should cut. Parameters @@ -291,12 +291,26 @@ def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=0. heel_depth = beam.height/2 if heel_depth > beam.height/2 else heel_depth # TODO: should it be constrained? # define step_shape step_shape = cls._define_step_shape(step_depth, heel_depth, tapered_heel) - # define mortise - mortise, mortise_width, mortise_height = cls._calculate_mortise(beam, mortise_height) # define strut_height strut_height = surface.ysize #TODO: Wrong! should have been defined by the main beam height instead - return cls(orientation, start_x, start_y, strut_inclination, notch_limited, notch_width, step_depth, heel_depth, step_shape, mortise, mortise_width, mortise_width, strut_height) + return cls(orientation, start_x, start_y, strut_inclination, notch_limited, notch_width, step_depth, heel_depth, step_shape, strut_height) + + def add_mortise(self, mortise_width, mortise_height): + """Add a mortise to the existing StepJointNotch instance. + + Parameters + ---------- + mortise_width : float + The width of the mortise. mortise_width < 1000.0. + mortise_height : float + The height of the mortise. mortise_height < 1000.0. + """ + self.mortise = "yes" + self.mortise_width = mortise_width + self.mortise_height = mortise_height + # self.mortise_width = beam.width / 4 # TODO: should this be related to a beam? 1/3 or 1/4 of beam.width? + # self.mortise_height = beam.height if mortise_height > beam.height else mortise_height #TODO: should this be constrained? @staticmethod def _calculate_orientation(ref_side, cutting_plane): @@ -333,19 +347,6 @@ def _define_step_shape(step_depth, heel_depth, tapered_heel): raise ValueError("at least one of step_depth or heel_depth must be greater than 0.0.") return step_shape - @staticmethod - def _calculate_mortise(beam, mortise_height): - # mortise based on mortise_height variable and beam dimensions - if mortise_height > 0.0: - mortise = "yes" - mortise_width = beam.width/4 #TODO: should this be predefined? Also 1/3 or 1/4? - mortise_height = beam.height if mortise_height > beam.height else mortise_height - else: - mortise = "no" - mortise_width = 0.0 - return mortise, mortise_width, mortise_height - - ######################################################################## # Methods ######################################################################## From 017cc922c36a5d5a7a820cb858721abe750726d4 Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 24 Jul 2024 14:22:51 +0200 Subject: [PATCH 05/63] move add mortise method down --- src/compas_timber/_fabrication/step_joint.py | 111 ++++++++++++------- 1 file changed, 73 insertions(+), 38 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index 7487f842a..ac74af97f 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -271,7 +271,7 @@ def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=0. point_start_x = intersection_line_plane(ref_edge, plane) if point_start_x is None: raise ValueError("Plane does not intersect with beam.") - start_x = distance_point_point(ref_edge.point, point_start_x) + start_x = distance_point_point(ref_side.point, point_start_x) # calculate start_y point_start_y = ref_side.intersections_with_surface(surface)[0] if point_start_y is None: @@ -296,22 +296,6 @@ def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=0. return cls(orientation, start_x, start_y, strut_inclination, notch_limited, notch_width, step_depth, heel_depth, step_shape, strut_height) - def add_mortise(self, mortise_width, mortise_height): - """Add a mortise to the existing StepJointNotch instance. - - Parameters - ---------- - mortise_width : float - The width of the mortise. mortise_width < 1000.0. - mortise_height : float - The height of the mortise. mortise_height < 1000.0. - """ - self.mortise = "yes" - self.mortise_width = mortise_width - self.mortise_height = mortise_height - # self.mortise_width = beam.width / 4 # TODO: should this be related to a beam? 1/3 or 1/4 of beam.width? - # self.mortise_height = beam.height if mortise_height > beam.height else mortise_height #TODO: should this be constrained? - @staticmethod def _calculate_orientation(ref_side, cutting_plane): # orientation is START if cutting plane normal points towards the start of the beam and END otherwise @@ -347,6 +331,30 @@ def _define_step_shape(step_depth, heel_depth, tapered_heel): raise ValueError("at least one of step_depth or heel_depth must be greater than 0.0.") return step_shape + @classmethod + def from_two_planes_and_beam(cls, plane_a, plane_b, beam, ref_side_index=0): + """Create a StepJointNotch instance from two planes and the beam it should cut. + + Parameters + ---------- + plane_a : :class:`~compas.geometry.Plane` + The first cutting plane. + plane_b : :class:`~compas.geometry.Plane` + The second cutting plane. + beam : :class:`~compas_timber.elements.Beam` + The beam that is cut by this instance. + ref_side_index : int, optional + The reference side index of the beam to be cut. Default is 0 (i.e. RS1). + + Returns + ------- + :class:`~compas_timber.fabrication.StepJointNotch` + + """ + # type: (Plane, Plane, Beam, int) -> StepJointNotch + # define ref_side & ref_edge + ref_side = beam.ref_sides[ref_side_index] + ######################################################################## # Methods ######################################################################## @@ -373,7 +381,7 @@ def apply(self, geometry, beam): """ # type: (Brep, Beam) -> Brep - cutting_plane = self.plane_from_params_and_beam(beam) + cutting_planes = self.planes_from_params_and_beam(beam) try: return geometry.trimmed(cutting_plane) except BrepTrimmingError: @@ -383,8 +391,24 @@ def apply(self, geometry, beam): "The cutting plane does not intersect with beam geometry.", ) - def plane_from_params_and_beam(self, beam): - """Calculates the cutting plane from the machining parameters in this instance and the given beam + def add_mortise(self, mortise_width, mortise_height): + """Add a mortise to the existing StepJointNotch instance. + + Parameters + ---------- + mortise_width : float + The width of the mortise. mortise_width < 1000.0. + mortise_height : float + The height of the mortise. mortise_height < 1000.0. + """ + self.mortise = "yes" + self.mortise_width = mortise_width + self.mortise_height = mortise_height + # self.mortise_width = beam.width / 4 # TODO: should this be related to a beam? 1/3 or 1/4 of beam.width? + # self.mortise_height = beam.height if mortise_height > beam.height else mortise_height #TODO: should this be constrained? + + def planes_from_params_and_beam(self, beam): + """Calculates the cutting planes from the machining parameters in this instance and the given beam Parameters ---------- @@ -394,27 +418,38 @@ def plane_from_params_and_beam(self, beam): Returns ------- :class:`compas.geometry.Plane` - The cutting plane. + The cutting planes. """ - # type: (Beam) -> Plane - assert self.angle is not None - assert self.inclination is not None - - # start with a plane aligned with the ref side but shifted to the start_x of the cut - ref_side = beam.side_as_surface(self.ref_side_index) - p_origin = ref_side.point_at(self.start_x, 0.0) - cutting_plane = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # normal pointing towards xaxis so just need the delta - horizontal_angle = math.radians(self.angle - 90) - rot_a = Rotation.from_axis_and_angle(cutting_plane.zaxis, horizontal_angle, point=p_origin) - - # normal pointing towards xaxis so just need the delta - vertical_angle = math.radians(self.inclination - 90) - rot_b = Rotation.from_axis_and_angle(cutting_plane.yaxis, vertical_angle, point=p_origin) + # type: (Beam) -> Planes + assert self.strut_inclination is not None + assert self.step_shape is not None + assert self.strut_height is not None + + # start with a plane aligned with the ref side but shifted to the start of the first cut + ref_side = beam.side_as_surface(self.ref_side_index) #TODO: missing a way of defining the ref_side_index for the class + + # orientation=start strut_inclination>90 #TODO: check if it applies to the other conditions + horizontal_angle = math.radians(self.strut_inclination - 90) + + if self.step_type == "step": + #move the frames to the start and end of the notch to create the cuts + start_displacement = self.strut_height / math.sin(horizontal_angle) + p_origin = ref_side.point_at(self.start_x, self.start_y) + p_displaced = ref_side.point_at(self.start_x + start_displacement, self.start_y) + cutting_plane_a = Frame(p_displaced, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_b = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # rotate first cutting plane (small side of the step) + angle_a = math.tan(math.radians(self.strut_inclination/2)) + rot_a = Rotation.from_axis_and_angle(-cutting_plane_a.yaxis, angle_a, point=p_displaced) + cutting_plane_a.transform(rot_a) + + # rotate second cutting plane (large side of the step) + angle_b = math.atan(self.step_depth / (start_displacement - self.step_depth / math.tan(angle_a))) + rot_b = Rotation.from_axis_and_angle(cutting_plane_b.xaxis, angle_b, point=p_origin) + cutting_plane_b.transform(rot_b) - cutting_plane.transform(rot_a * rot_b) # for simplicity, we always start with normal pointing towards xaxis. # if start is cut, we need to flip the normal if self.orientation == OrientationType.END: From 6a6e14e317fd35a94841fed76cade7a73bb2398b Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 24 Jul 2024 19:08:36 +0200 Subject: [PATCH 06/63] from_surface_and_beam cleanup --- src/compas_timber/_fabrication/step_joint.py | 55 +++++++++++++------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index ac74af97f..3efe80769 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -241,7 +241,7 @@ def mortise_height(self, mortise_height): ######################################################################## @classmethod - def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=0.0, heel_depth=0.0, tapered_heel=False, ref_side_index=0): + def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=20.0, heel_depth=0.0, strut_height=20.0, tapered_heel=False, ref_side_index=0): """Create a StepJointNotch instance from a cutting surface and the beam it should cut. Parameters @@ -258,43 +258,47 @@ def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=0. :class:`~compas_timber.fabrication.StepJointNotch` """ - # type: (PlanarSurface | Surface, Beam, bool, float, float, bool, float, int) -> StepJointNotch - + # type: (PlanarSurface|Surface, Beam, bool, float, float, float, bool, int) -> StepJointNotch + #TODO: the stepjointnotch is always orthogonal, this means that the surface should be perpendicular to the beam's ref_side | should there be a check for that? + #TODO: I am using a PlanarSurface instead of a Plane because otherwise there is no way to define the start_y of the Notch + #TODO: The alternative in order to use a Plane instead would be to have start_y and notch_width as parameters of the class # define ref_side & ref_edge ref_side = beam.ref_sides[ref_side_index] # TODO: is this arbitrary? ref_edge = Line.from_point_and_vector(ref_side.point, ref_side.xaxis) - plane = Plane.from_frame(surface.frame_at()) + plane = surface.to_plane() + intersection_line = Line(*ref_side.intersections_with_surface(surface)) #TODO NotImplementedError: Plane|Surface // COMPAS core issue # calculate orientation orientation = cls._calculate_orientation(ref_side, plane) + # calculate start_x point_start_x = intersection_line_plane(ref_edge, plane) if point_start_x is None: - raise ValueError("Plane does not intersect with beam.") + raise ValueError("Surface does not intersect with beam.") start_x = distance_point_point(ref_side.point, point_start_x) + # calculate start_y - point_start_y = ref_side.intersections_with_surface(surface)[0] - if point_start_y is None: - raise ValueError("Surface does not intersect with beam.") - start_y = distance_point_point(point_start_x, point_start_y) + start_y = cls._calculate_start_y(orientation, intersection_line, point_start_x, ref_side) + # calculate strut_inclination strut_inclination = cls._calculate_strut_inclination(ref_side, plane, orientation) + # calculate notch_width if notch_limited == True: - notch_width = surface.ysize + notch_width = intersection_line.length notch_limited = "yes" else: notch_width = beam.width notch_limited = "no" - # restrain step_depth & heel_depth #TODO: should those be defined automatically based on the angle? - step_depth = beam.height/2 if step_depth > beam.height/2 else step_depth # TODO: should it be constrained? - heel_depth = beam.height/2 if heel_depth > beam.height/2 else heel_depth # TODO: should it be constrained? + + # restrain step_depth & heel_depth to beam's height # TODO: should it be restrained? + step_depth = beam.height if step_depth > beam.height else step_depth + heel_depth = beam.height if heel_depth > beam.height else heel_depth + # define step_shape step_shape = cls._define_step_shape(step_depth, heel_depth, tapered_heel) - # define strut_height - strut_height = surface.ysize #TODO: Wrong! should have been defined by the main beam height instead - return cls(orientation, start_x, start_y, strut_inclination, notch_limited, notch_width, step_depth, heel_depth, step_shape, strut_height) + return cls(orientation, start_x, start_y, strut_inclination, notch_limited, notch_width, step_depth, heel_depth, step_shape, strut_height, ref_side_index=ref_side_index) @staticmethod def _calculate_orientation(ref_side, cutting_plane): @@ -305,19 +309,32 @@ def _calculate_orientation(ref_side, cutting_plane): else: return OrientationType.START + @staticmethod + def _calculate_start_y(orientation, intersection_line, point_start_x, ref_side): + # checks if the start of the intersection line is out of the beam's ref_side + # if out then start_y = 0.0, otherwise it calculates the displacement + if orientation == OrientationType.START: + point_start_y = intersection_line.start + else: + point_start_y = intersection_line.end + startxy_vect = Vector.from_start_end(point_start_x, point_start_y) + dot_product = startxy_vect.dot(ref_side.yaxis) + start_y = abs(dot_product) if dot_product > 0 else 0.0 + return start_y + @staticmethod def _calculate_strut_inclination(ref_side, plane, orientation): # vector rotation direction of the plane's normal in the vertical direction strut_inclination_vector = Vector.cross(ref_side.zaxis, plane.normal) strut_inclination = angle_vectors_signed(ref_side.zaxis, plane.normal, strut_inclination_vector, deg=True) if orientation == OrientationType.START: - return 180 - abs(strut_inclination) # get the other side of the angle - else: return abs(strut_inclination) + else: + return 180 - abs(strut_inclination) # get the other side of the angle @staticmethod def _define_step_shape(step_depth, heel_depth, tapered_heel): - # step_shape based on step_depth and heel_depth variables + # step_shape based on step_depth and heel_depth and tapered_heel variables if step_depth > 0.0 and heel_depth == 0.0: step_shape = "step" elif step_depth == 0.0 and heel_depth > 0.0: From e2c4adfdb00ad771bbe95a63e85fd02f6d4cdaea Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 24 Jul 2024 19:22:19 +0200 Subject: [PATCH 07/63] implement BTLxProcessParams --- .../{step_joint.py => step_joint_notch.py} | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) rename src/compas_timber/_fabrication/{step_joint.py => step_joint_notch.py} (90%) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint_notch.py similarity index 90% rename from src/compas_timber/_fabrication/step_joint.py rename to src/compas_timber/_fabrication/step_joint_notch.py index 3efe80769..920e82f67 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -12,11 +12,13 @@ from compas.geometry import distance_point_point from compas.geometry import intersection_line_plane from compas.geometry import is_point_behind_plane +from compas.tolerance import TOL from compas_timber.elements import FeatureApplicationError from .btlx_process import BTLxProcess +from .btlx_process import BTLxProcessParams from .btlx_process import OrientationType @@ -54,6 +56,8 @@ class StepJointNotch(BTLxProcess): """ + PROCESS_NAME = "StepJointNotch" # type: ignore + @property def __data__(self): data = super(StepJointNotch, self).__data__ @@ -106,6 +110,10 @@ def __init__(self, orientation, start_x=0.0, start_y=0.0, strut_inclination=90.0 # Properties ######################################################################## + @property + def params_dict(self): + return StepJointNotchParams(self).as_dict() + @property def orientation(self): return self._orientation @@ -474,3 +482,41 @@ def planes_from_params_and_beam(self, beam): else: plane_normal = -cutting_plane.xaxis return Plane(cutting_plane.point, plane_normal) + +class StepJointNotchParams(BTLxProcessParams): + """A class to store the parameters of a Jack Rafter Cut feature. + + Parameters + ---------- + instance : :class:`~compas_timber._fabrication.JackRafterCut` + The instance of the Jack Rafter Cut feature. + """ + + def __init__(self, instance): + # type: (StepJointNotch) -> None + super(StepJointNotchParams, self).__init__(instance) + + def as_dict(self): + """Returns the parameters of the Step Joint Notch feature as a dictionary. + + Returns + ------- + dict + The parameters of the Step Joint Notch as a dictionary. + """ + # type: () -> OrderedDict + result = super(StepJointNotchParams, self).as_dict() + result["Orientation"] = self._instance.orientation + result["StartX"] = "{:.{prec}f}".format(self._instance.start_x, prec=TOL.precision) + result["StartY"] = "{:.{prec}f}".format(self._instance.start_y, prec=TOL.precision) + result["StrutInclination"] = "{:.{prec}f}".format(self._instance.strut_inclination, prec=TOL.precision) + result["NotchLimited"] = self._instance.notch_limited + result["NotchWidth"] = "{:.{prec}f}".format(self._instance.notch_width, prec=TOL.precision) + result["StepDepth"] = "{:.{prec}f}".format(self._instance.step_depth, prec=TOL.precision) + result["HeelDepth"] = "{:.{prec}f}".format(self._instance.heel_depth, prec=TOL.precision) + result["StrutHeight"] = "{:.{prec}f}".format(self._instance.strut_height, prec=TOL.precision) + result["StepShape"] = self._instance.step_shape + result["Mortise"] = self._instance.mortise + result["MortiseWidth"] = "{:.{prec}f}".format(self._instance.mortise_width, prec=TOL.precision) + result["MortiseHeight"] = "{:.{prec}f}".format(self._instance.mortise_height, prec=TOL.precision) + return result From 47ef6fe696045ae8ad33ceff7f7f9a2a4cf5f819 Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 25 Jul 2024 19:15:36 +0200 Subject: [PATCH 08/63] planes from params and beam for step/heel/heeltappered --- .../_fabrication/step_joint_notch.py | 150 +++++++++++++++--- 1 file changed, 124 insertions(+), 26 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index 920e82f67..0a8a9e402 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -250,7 +250,7 @@ def mortise_height(self, mortise_height): @classmethod def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=20.0, heel_depth=0.0, strut_height=20.0, tapered_heel=False, ref_side_index=0): - """Create a StepJointNotch instance from a cutting surface and the beam it should cut. + """Create a StepJointNotch instance from a cutting surface and the beam it should cut. This could be the ref_side of the main beam of a Joint. Parameters ---------- @@ -299,7 +299,7 @@ def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=20 notch_width = beam.width notch_limited = "no" - # restrain step_depth & heel_depth to beam's height # TODO: should it be restrained? + # restrain step_depth & heel_depth to beam's height # TODO: should it be restrained? should they be proportional to the beam's dimensions? step_depth = beam.height if step_depth > beam.height else step_depth heel_depth = beam.height if heel_depth > beam.height else heel_depth @@ -379,6 +379,7 @@ def from_two_planes_and_beam(cls, plane_a, plane_b, beam, ref_side_index=0): # type: (Plane, Plane, Beam, int) -> StepJointNotch # define ref_side & ref_edge ref_side = beam.ref_sides[ref_side_index] + pass ######################################################################## # Methods @@ -452,36 +453,133 @@ def planes_from_params_and_beam(self, beam): assert self.strut_height is not None # start with a plane aligned with the ref side but shifted to the start of the first cut - ref_side = beam.side_as_surface(self.ref_side_index) #TODO: missing a way of defining the ref_side_index for the class + ref_side = beam.side_as_surface(self.ref_side_index) - # orientation=start strut_inclination>90 #TODO: check if it applies to the other conditions - horizontal_angle = math.radians(self.strut_inclination - 90) if self.step_type == "step": + start_displacement = self.strut_height / math.sin(math.radians(self.strut_inclination)) + rot_axis = ref_side.frame.yaxis + if self.orientation==OrientationType.END: + start_displacement = -start_displacement # negative displacement for the end cut + rot_axis = -rot_axis # negative rotation axis for the end cut + #move the frames to the start and end of the notch to create the cuts - start_displacement = self.strut_height / math.sin(horizontal_angle) p_origin = ref_side.point_at(self.start_x, self.start_y) p_displaced = ref_side.point_at(self.start_x + start_displacement, self.start_y) - cutting_plane_a = Frame(p_displaced, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_b = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # rotate first cutting plane (small side of the step) - angle_a = math.tan(math.radians(self.strut_inclination/2)) - rot_a = Rotation.from_axis_and_angle(-cutting_plane_a.yaxis, angle_a, point=p_displaced) - cutting_plane_a.transform(rot_a) - - # rotate second cutting plane (large side of the step) - angle_b = math.atan(self.step_depth / (start_displacement - self.step_depth / math.tan(angle_a))) - rot_b = Rotation.from_axis_and_angle(cutting_plane_b.xaxis, angle_b, point=p_origin) - cutting_plane_b.transform(rot_b) - - # for simplicity, we always start with normal pointing towards xaxis. - # if start is cut, we need to flip the normal - if self.orientation == OrientationType.END: - plane_normal = cutting_plane.xaxis - else: - plane_normal = -cutting_plane.xaxis - return Plane(cutting_plane.point, plane_normal) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_displaced = Frame(p_displaced, ref_side.frame.xaxis, ref_side.frame.yaxis) + + if self.strut_inclination>90: + # rotate first cutting plane at the start of the notch (large side of the step) + angle_long_side = math.atan(self.step_depth / (start_displacement - self.step_depth/math.tan(math.radians(self.strut_inclination/2)))) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) + cutting_plane_origin.transform(rot_long_side) + # rotate second cutting plane at the end of the notch (short side of the step) + angle_short_side = math.radians(180-self.strut_inclination/2) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_displaced) + cutting_plane_displaced.transform(rot_short_side) + else: + # rotate first cutting plane at the start of the notch (short side of the step) + angle_short_side = math.radians(90+self.strut_inclination/2) #math.radians(180-(180-self.strut_inclination)/2) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) + cutting_plane_origin.transform(rot_short_side) + # rotate second cutting plane at the end of the notch (large side of the step) + angle_long_side = 180 - math.atan(self.step_depth / (start_displacement - self.step_depth/math.tan(math.radians(90-self.strut_inclination/2)))) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_displaced) + cutting_plane_displaced.transform(rot_long_side) + cutting_planes = [cutting_plane_origin, cutting_plane_displaced] + + elif self.step_type == "heel": + if self.strut_inclination>90: + start_displacement = abs(self.heel_depth / (math.sin(math.radians(self.strut_inclination))*math.cos(math.radians(self.strut_inclination)))) #abs() because cos(180-x)-->gives negative + rot_axis = ref_side.frame.yaxis + if self.orientation==OrientationType.END: + start_displacement = -start_displacement # negative displacement for the end cut + rot_axis = -rot_axis # negative rotation axis for the end cut + + #move the frames to the start and end of the notch to create the cuts + p_origin = ref_side.point_at(self.start_x, self.start_y) + p_displaced = ref_side.point_at(self.start_x + start_displacement, self.start_y) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_displaced = Frame(p_displaced, ref_side.frame.xaxis, ref_side.frame.yaxis) + # rotate first cutting plane at the start of the notch (short side of the heel) + angle_short_side = math.rad(180-self.strut_inclination) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) + cutting_plane_origin.transform(rot_short_side) + # rotate second cutting plane at the end of the notch (long side of the heel) + angle_long_side = math.radians(270-self.strut_inclination) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_displaced) + cutting_plane_displaced.transform(rot_long_side) + else: + start_displacement_2 = self.heel_depth / (math.sin(math.radians(self.strut_inclination))*math.cos(math.radians(self.strut_inclination))) + start_displacement_1 = start_displacement_2 - (self.strut_height / math.sin(math.radians(self.strut_inclination))) + rot_axis = ref_side.frame.yaxis + if self.orientation==OrientationType.END: + start_displacement_1, start_displacement_2 = -start_displacement_1, -start_displacement_2 # negative displacement for the start and end cut + rot_axis = -rot_axis # negative rotation axis for the end cut + + #move the frames to the start and end of the notch to create the cuts + p_origin = ref_side.point_at(self.start_x+start_displacement_1, self.start_y) + p_displaced = ref_side.point_at(self.start_x+start_displacement_2, self.start_y) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_displaced = Frame(p_displaced, ref_side.frame.xaxis, ref_side.frame.yaxis) + # rotate first cutting plane at the translated start of the notch (long side of the heel) + angle_long_side = math.radians(270+self.strut_inclination) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) + cutting_plane_origin.transform(rot_long_side) + # rotate second cutting plane at the end of the notch (short side of the heel) + angle_short_side = math.radians(180+self.strut_inclination) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_displaced) + cutting_plane_displaced.transform(rot_short_side) + cutting_planes = [cutting_plane_origin, cutting_plane_displaced] + + elif self.step_type == "heel_tapered": + start_displacement = self.strut_height / math.sin(math.radians(self.strut_inclination)) + rot_axis = ref_side.frame.yaxis + if self.orientation==OrientationType.END: + start_displacement = -start_displacement # negative displacement for the end cut + rot_axis = -rot_axis # negative rotation axis for the end cut + + #move the frames to the start and end of the notch to create the cuts + p_origin = ref_side.point_at(self.start_x, self.start_y) + p_displaced = ref_side.point_at(self.start_x + start_displacement, self.start_y) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_displaced = Frame(p_displaced, ref_side.frame.xaxis, ref_side.frame.yaxis) + if self.strut_inclination>90: + # rotate first cutting plane at the start of the notch (short side of the heel) + angle_short_side = math.radians(180-self.strut_inclination) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) + cutting_plane_origin.transform(rot_short_side) + # rotate second cutting plane at the end of the notch (long side of the heel) + angle_long_side = math.radians(180) - math.atan(self.heel_depth / (abs(start_displacement) - abs(self.heel_depth/math.tan(math.radians(self.strut_inclination))))) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_displaced) + cutting_plane_displaced.transform(rot_long_side) + else: + # rotate first cutting plane at the start of the notch (long side of the heel) + angle_long_side = math.atan(self.heel_depth / (abs(start_displacement) - abs(self.heel_depth/math.tan(math.radians(self.strut_inclination))))) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) + cutting_plane_origin.transform(rot_long_side) + # rotate second cutting plane at the end of the notch (short side of the heel) + angle_short_side = math.radians(180-self.strut_inclination) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_displaced) + cutting_plane_displaced.transform(rot_short_side) + cutting_planes = [cutting_plane_origin, cutting_plane_displaced] + + elif self.step_type == "double": + pass + + + + + + # # for simplicity, we always start with normal pointing towards xaxis. + # # if start is cut, we need to flip the normal + # if self.orientation == OrientationType.END: + # plane_normal = cutting_plane.xaxis + # else: + # plane_normal = -cutting_plane.xaxis + # return Plane(cutting_plane.point, plane_normal) + return cutting_planes class StepJointNotchParams(BTLxProcessParams): """A class to store the parameters of a Jack Rafter Cut feature. From 4dcad658685e7224834a446b6929d0c667cfd329 Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 26 Jul 2024 15:43:51 +0200 Subject: [PATCH 09/63] brep boolean difference with notch volume --- src/compas_timber/_fabrication/__init__.py | 4 +- .../_fabrication/step_joint_notch.py | 127 ++++++++++-------- 2 files changed, 72 insertions(+), 59 deletions(-) diff --git a/src/compas_timber/_fabrication/__init__.py b/src/compas_timber/_fabrication/__init__.py index 6ee04ee8c..f45ae14ac 100644 --- a/src/compas_timber/_fabrication/__init__.py +++ b/src/compas_timber/_fabrication/__init__.py @@ -4,6 +4,8 @@ from .btlx_process import OrientationType from .jack_cut import JackRafterCut from .jack_cut import JackRafterCutParams +from .step_joint_notch import StepJointNotch +from .step_joint_notch import StepJointNotchParams -__all__ = ["JackRafterCut", "BTLxProcess", "OrientationType", "JackRafterCutParams"] +__all__ = ["JackRafterCut", "BTLxProcess", "OrientationType", "JackRafterCutParams", "StepJointNotch", "StepJointNotchParams"] diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index 0a8a9e402..e16a8c605 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -6,15 +6,14 @@ from compas.geometry import Plane from compas.geometry import Rotation from compas.geometry import Vector -from compas.geometry import Surface -from compas.geometry import PlanarSurface +from compas.geometry import Polyhedron +from compas.geometry import Brep from compas.geometry import angle_vectors_signed from compas.geometry import distance_point_point from compas.geometry import intersection_line_plane from compas.geometry import is_point_behind_plane from compas.tolerance import TOL - from compas_timber.elements import FeatureApplicationError from .btlx_process import BTLxProcess @@ -269,7 +268,7 @@ def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=20 # type: (PlanarSurface|Surface, Beam, bool, float, float, float, bool, int) -> StepJointNotch #TODO: the stepjointnotch is always orthogonal, this means that the surface should be perpendicular to the beam's ref_side | should there be a check for that? #TODO: I am using a PlanarSurface instead of a Plane because otherwise there is no way to define the start_y of the Notch - #TODO: The alternative in order to use a Plane instead would be to have start_y and notch_width as parameters of the class + #TODO: The alternative solutionin order to use a Plane instead would be to have start_y and notch_width as parameters of the class # define ref_side & ref_edge ref_side = beam.ref_sides[ref_side_index] # TODO: is this arbitrary? ref_edge = Line.from_point_and_vector(ref_side.point, ref_side.xaxis) @@ -320,7 +319,7 @@ def _calculate_orientation(ref_side, cutting_plane): @staticmethod def _calculate_start_y(orientation, intersection_line, point_start_x, ref_side): # checks if the start of the intersection line is out of the beam's ref_side - # if out then start_y = 0.0, otherwise it calculates the displacement + # if it's out then start_y = 0.0, otherwise it calculates the displacement if orientation == OrientationType.START: point_start_y = intersection_line.start else: @@ -356,31 +355,6 @@ def _define_step_shape(step_depth, heel_depth, tapered_heel): raise ValueError("at least one of step_depth or heel_depth must be greater than 0.0.") return step_shape - @classmethod - def from_two_planes_and_beam(cls, plane_a, plane_b, beam, ref_side_index=0): - """Create a StepJointNotch instance from two planes and the beam it should cut. - - Parameters - ---------- - plane_a : :class:`~compas.geometry.Plane` - The first cutting plane. - plane_b : :class:`~compas.geometry.Plane` - The second cutting plane. - beam : :class:`~compas_timber.elements.Beam` - The beam that is cut by this instance. - ref_side_index : int, optional - The reference side index of the beam to be cut. Default is 0 (i.e. RS1). - - Returns - ------- - :class:`~compas_timber.fabrication.StepJointNotch` - - """ - # type: (Plane, Plane, Beam, int) -> StepJointNotch - # define ref_side & ref_edge - ref_side = beam.ref_sides[ref_side_index] - pass - ######################################################################## # Methods ######################################################################## @@ -391,14 +365,14 @@ def apply(self, geometry, beam): Parameters ---------- geometry : :class:`~compas.geometry.Brep` - The beam geometry to be cut. + The beam geometry to be milled. beam : :class:`compas_timber.elements.Beam` - The beam that is cut by this instance. + The beam that is milled by this instance. Raises ------ :class:`~compas_timber.elements.FeatureApplicationError` - If the cutting plane does not intersect with beam geometry. + If the cutting planes do not create a volume that itersects with beam geometry or any step fails. Returns ------- @@ -407,17 +381,67 @@ def apply(self, geometry, beam): """ # type: (Brep, Beam) -> Brep - cutting_planes = self.planes_from_params_and_beam(beam) + # get cutting planes from params + try: + cutting_planes = self.planes_from_params_and_beam(beam) + except ValueError as e: + raise FeatureApplicationError( + None, + geometry, + f"Failed to generate cutting planes from parameters and beam: {str(e)}" + ) + # create notch polyedron from planes + cutting_planes.append(Plane.from_frame(beam.ref_sides[self.ref_side_index])) #add ref_side plane to create a polyhedron + try: + notch_polyhedron = Polyhedron.from_planes(cutting_planes) + except Exception as e: + raise FeatureApplicationError( + cutting_planes, + geometry, + f"Failed to create polyhedron from cutting planes: {str(e)}" + ) + # convert polyhedron to mesh + try: + notch_mesh = notch_polyhedron.to_mesh() + except Exception as e: + raise FeatureApplicationError( + notch_polyhedron, + geometry, + f"Failed to convert polyhedron to mesh: {str(e)}" + ) + # convert mesh to brep + try: + notch_brep = Brep.from_mesh(notch_mesh) + except Exception as e: + raise FeatureApplicationError( + notch_mesh, + geometry, + f"Failed to convert mesh to Brep: {str(e)}" + ) + # apply boolean difference try: - return geometry.trimmed(cutting_plane) - except BrepTrimmingError: + brep_with_notch = Brep.from_boolean_difference(geometry, notch_brep) + except Exception as e: raise FeatureApplicationError( - cutting_plane, + notch_brep, geometry, - "The cutting plane does not intersect with beam geometry.", + f"Boolean difference operation failed: {str(e)}" ) + # check if the notch is empty + if not brep_with_notch: + raise FeatureApplicationError( + notch_brep, + geometry, + "The cutting planes do not create a volume that intersects with beam geometry." + ) + + if self.mortise == "yes": #TODO: implement mortise + # create mortise volume and subtract from brep_with_notch + pass + + return brep_with_notch - def add_mortise(self, mortise_width, mortise_height): + def add_mortise(self, mortise_width, mortise_height, beam): """Add a mortise to the existing StepJointNotch instance. Parameters @@ -428,10 +452,9 @@ def add_mortise(self, mortise_width, mortise_height): The height of the mortise. mortise_height < 1000.0. """ self.mortise = "yes" + # self.mortise_width = beam.width / 4 # TODO: should this relate to the beam? typically 1/3 or 1/4 of beam.width self.mortise_width = mortise_width - self.mortise_height = mortise_height - # self.mortise_width = beam.width / 4 # TODO: should this be related to a beam? 1/3 or 1/4 of beam.width? - # self.mortise_height = beam.height if mortise_height > beam.height else mortise_height #TODO: should this be constrained? + self.mortise_height = beam.height if mortise_height > beam.height else mortise_height #TODO: should this be constrained? def planes_from_params_and_beam(self, beam): """Calculates the cutting planes from the machining parameters in this instance and the given beam @@ -455,7 +478,6 @@ def planes_from_params_and_beam(self, beam): # start with a plane aligned with the ref side but shifted to the start of the first cut ref_side = beam.side_as_surface(self.ref_side_index) - if self.step_type == "step": start_displacement = self.strut_height / math.sin(math.radians(self.strut_inclination)) rot_axis = ref_side.frame.yaxis @@ -487,7 +509,7 @@ def planes_from_params_and_beam(self, beam): angle_long_side = 180 - math.atan(self.step_depth / (start_displacement - self.step_depth/math.tan(math.radians(90-self.strut_inclination/2)))) rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_displaced) cutting_plane_displaced.transform(rot_long_side) - cutting_planes = [cutting_plane_origin, cutting_plane_displaced] + cutting_planes = [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_displaced)] elif self.step_type == "heel": if self.strut_inclination>90: @@ -531,7 +553,7 @@ def planes_from_params_and_beam(self, beam): angle_short_side = math.radians(180+self.strut_inclination) rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_displaced) cutting_plane_displaced.transform(rot_short_side) - cutting_planes = [cutting_plane_origin, cutting_plane_displaced] + cutting_planes = [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_displaced)] elif self.step_type == "heel_tapered": start_displacement = self.strut_height / math.sin(math.radians(self.strut_inclination)) @@ -563,22 +585,11 @@ def planes_from_params_and_beam(self, beam): angle_short_side = math.radians(180-self.strut_inclination) rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_displaced) cutting_plane_displaced.transform(rot_short_side) - cutting_planes = [cutting_plane_origin, cutting_plane_displaced] + cutting_planes = [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_displaced)] - elif self.step_type == "double": + elif self.step_type == "double": #TODO: implement double step pass - - - - - # # for simplicity, we always start with normal pointing towards xaxis. - # # if start is cut, we need to flip the normal - # if self.orientation == OrientationType.END: - # plane_normal = cutting_plane.xaxis - # else: - # plane_normal = -cutting_plane.xaxis - # return Plane(cutting_plane.point, plane_normal) return cutting_planes class StepJointNotchParams(BTLxProcessParams): From e0f6230859954320a5dd85e9557abbc236f93f42 Mon Sep 17 00:00:00 2001 From: papachap Date: Sat, 27 Jul 2024 23:26:29 +0300 Subject: [PATCH 10/63] split process for generating the planes into private methods for different notch shapes --- .../_fabrication/step_joint_notch.py | 391 +++++++++++++----- 1 file changed, 278 insertions(+), 113 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index e16a8c605..4ad38a9b3 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -8,6 +8,7 @@ from compas.geometry import Vector from compas.geometry import Polyhedron from compas.geometry import Brep +from compas.geometry import Polyline from compas.geometry import angle_vectors_signed from compas.geometry import distance_point_point from compas.geometry import intersection_line_plane @@ -243,6 +244,14 @@ def mortise_height(self, mortise_height): raise ValueError("MortiseHeight must be less than 1000.0.") self._mortise_height = mortise_height + @property #TODO: how should these be better implemented? + def displacement_end(self): + return self._calculate_displacement_end(self.strut_height, self.strut_inclination, self.orientation) + + @property #TODO: how should these be better implemented? + def displacement_heel(self): + return self._calculate_displacement_heel(self.heel_depth, self.strut_inclination, self.orientation) + ######################################################################## # Alternative constructors ######################################################################## @@ -267,8 +276,8 @@ def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=20 """ # type: (PlanarSurface|Surface, Beam, bool, float, float, float, bool, int) -> StepJointNotch #TODO: the stepjointnotch is always orthogonal, this means that the surface should be perpendicular to the beam's ref_side | should there be a check for that? - #TODO: I am using a PlanarSurface instead of a Plane because otherwise there is no way to define the start_y of the Notch - #TODO: The alternative solutionin order to use a Plane instead would be to have start_y and notch_width as parameters of the class + #TODO: I am using a PlanarSurface instead of a Plane because otherwise there is no way to define the start_y of the Notch. This makes a case for the default ref_side to be a PlanarSurface + #TODO: The alternative solution in order to use a Plane instead would be to have start_y and notch_width as parameters of the class # define ref_side & ref_edge ref_side = beam.ref_sides[ref_side_index] # TODO: is this arbitrary? ref_edge = Line.from_point_and_vector(ref_side.point, ref_side.xaxis) @@ -355,6 +364,22 @@ def _define_step_shape(step_depth, heel_depth, tapered_heel): raise ValueError("at least one of step_depth or heel_depth must be greater than 0.0.") return step_shape + @staticmethod + def _calculate_displacement_end(strut_height, strut_inclination, orientation): + #Calculates the linear displacement from the origin point to the end of the notch based on the strut_height and strut_inclination. + displacement_end = strut_height / math.sin(math.radians(strut_inclination)) + if orientation==OrientationType.END: + displacement_end = -displacement_end # negative displacement for the end cut + return displacement_end + + @staticmethod + def _calculate_displacement_heel(heel_depth, strut_inclination, orientation): + #Calculates the linear displacement from the origin point to the heel of the notch based on the heel_depth and strut_inclination. + displacement_heel = abs(heel_depth / (math.sin(math.radians(strut_inclination))*math.cos(math.radians(strut_inclination)))) + if orientation==OrientationType.END: + displacement_heel = -displacement_heel + return displacement_heel + ######################################################################## # Methods ######################################################################## @@ -391,14 +416,15 @@ def apply(self, geometry, beam): f"Failed to generate cutting planes from parameters and beam: {str(e)}" ) # create notch polyedron from planes - cutting_planes.append(Plane.from_frame(beam.ref_sides[self.ref_side_index])) #add ref_side plane to create a polyhedron + # add ref_side plane to create a polyhedron + cutting_planes.append(Plane.from_frame(beam.ref_sides[self.ref_side_index])) #TODO: the beam's ref_side Plane might need to be offsetted to create a valid polyhedron when step_type is "double" try: notch_polyhedron = Polyhedron.from_planes(cutting_planes) except Exception as e: raise FeatureApplicationError( cutting_planes, geometry, - f"Failed to create polyhedron from cutting planes: {str(e)}" + f"Failed to create valid polyhedron from cutting planes: {str(e)}" ) # convert polyhedron to mesh try: @@ -468,129 +494,268 @@ def planes_from_params_and_beam(self, beam): ------- :class:`compas.geometry.Plane` The cutting planes. - """ - # type: (Beam) -> Planes assert self.strut_inclination is not None assert self.step_shape is not None assert self.strut_height is not None - # start with a plane aligned with the ref side but shifted to the start of the first cut + # Start with a plane aligned with the ref side but shifted to the start of the first cut ref_side = beam.side_as_surface(self.ref_side_index) + rot_axis = ref_side.frame.yaxis - if self.step_type == "step": - start_displacement = self.strut_height / math.sin(math.radians(self.strut_inclination)) - rot_axis = ref_side.frame.yaxis - if self.orientation==OrientationType.END: - start_displacement = -start_displacement # negative displacement for the end cut - rot_axis = -rot_axis # negative rotation axis for the end cut + if self.orientation == OrientationType.END: + rot_axis = -rot_axis # Negative rotation axis for the end cut - #move the frames to the start and end of the notch to create the cuts + if self.step_type == "step": + return self._calculate_step_planes(ref_side, rot_axis) + elif self.step_type == "heel": + return self._calculate_heel_planes(ref_side, rot_axis) + elif self.step_type == "heel_tapered": + return self._calculate_heel_tapered_planes(ref_side, rot_axis) + elif self.step_type == "double": + return self._calculate_double_planes(ref_side, rot_axis) + + def _calculate_step_planes(self, ref_side, rot_axis): + """Calculate cutting planes for a step notch.""" + # Move the frames to the start and end of the notch to create the cuts + p_origin = ref_side.point_at(self.start_x, self.start_y) + p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # Calculate step cutting planes angles + if self.strut_inclination > 90: + # Rotate first cutting plane at the start of the notch (large side of the step) + angle_long_side = math.atan(self.step_depth / (self.displacement_end - self.step_depth / math.tan(math.radians(self.strut_inclination / 2)))) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) + cutting_plane_origin.transform(rot_long_side) + + # Rotate second cutting plane at the end of the notch (short side of the step) + angle_short_side = math.radians(180 - self.strut_inclination / 2) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) + cutting_plane_end.transform(rot_short_side) + else: + # Rotate first cutting plane at the start of the notch (short side of the step) + angle_short_side = math.radians(90 + self.strut_inclination / 2) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) + cutting_plane_origin.transform(rot_short_side) + + # Rotate second cutting plane at the end of the notch (large side of the step) + angle_long_side = math.radians(180) - math.atan(self.step_depth / (self.displacement_end - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2)))) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) + cutting_plane_end.transform(rot_long_side) + + return [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_end)] + + def _calculate_heel_planes(self, ref_side, rot_axis): + """Calculate cutting planes for a heel notch.""" + if self.strut_inclination > 90: + # Move the frames to the start and end of the notch to create the cuts p_origin = ref_side.point_at(self.start_x, self.start_y) - p_displaced = ref_side.point_at(self.start_x + start_displacement, self.start_y) + p_heel = ref_side.point_at(self.start_x + self.displacement_heel, self.start_y) cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_displaced = Frame(p_displaced, ref_side.frame.xaxis, ref_side.frame.yaxis) - - if self.strut_inclination>90: - # rotate first cutting plane at the start of the notch (large side of the step) - angle_long_side = math.atan(self.step_depth / (start_displacement - self.step_depth/math.tan(math.radians(self.strut_inclination/2)))) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) - cutting_plane_origin.transform(rot_long_side) - # rotate second cutting plane at the end of the notch (short side of the step) - angle_short_side = math.radians(180-self.strut_inclination/2) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_displaced) - cutting_plane_displaced.transform(rot_short_side) - else: - # rotate first cutting plane at the start of the notch (short side of the step) - angle_short_side = math.radians(90+self.strut_inclination/2) #math.radians(180-(180-self.strut_inclination)/2) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) - cutting_plane_origin.transform(rot_short_side) - # rotate second cutting plane at the end of the notch (large side of the step) - angle_long_side = 180 - math.atan(self.step_depth / (start_displacement - self.step_depth/math.tan(math.radians(90-self.strut_inclination/2)))) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_displaced) - cutting_plane_displaced.transform(rot_long_side) - cutting_planes = [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_displaced)] + cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # Calculate heel cutting planes angles + # Rotate first cutting plane at the start of the notch (short side of the heel) + angle_short_side = math.radians(180 - self.strut_inclination) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) + cutting_plane_origin.transform(rot_short_side) + + # Rotate second cutting plane at the end of the notch (long side of the heel) + angle_long_side = math.radians(270 - self.strut_inclination) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_heel) + cutting_plane_heel.transform(rot_long_side) + else: + # Move the frames to the start and end of the notch to create the cuts + p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) + p_heel = ref_side.point_at(self.start_x + (self.displacement_end - self.displacement_heel), self.start_y) + cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # Calculate heel cutting planes angles + # Rotate first cutting plane at the displaced start of the notch (long side of the heel) + angle_long_side = math.radians(90 - self.strut_inclination) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_heel) + cutting_plane_heel.transform(rot_long_side) + + # Rotate second cutting plane at the end of the notch (short side of the heel) + angle_short_side = math.radians(180 - self.strut_inclination) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) + cutting_plane_end.transform(rot_short_side) + + return [Plane.from_frame(cutting_plane_heel), Plane.from_frame(cutting_plane_end)] + + def _calculate_heel_tapered_planes(self, ref_side, rot_axis): + """Calculate cutting planes for a tapered heel notch.""" + # Move the frames to the start and end of the notch to create the cuts + p_origin = ref_side.point_at(self.start_x, self.start_y) + p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # Calculate tapered heel cutting planes angles + if self.strut_inclination > 90: + # Rotate first cutting plane at the start of the notch (short side of the heel) + angle_short_side = math.radians(180 - self.strut_inclination) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) + cutting_plane_origin.transform(rot_short_side) + + # Rotate second cutting plane at the end of the notch (long side of the heel) + angle_long_side = math.radians(180) - math.atan(self.heel_depth / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination))))) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) + cutting_plane_end.transform(rot_long_side) + else: + # Rotate first cutting plane at the start of the notch (long side of the heel) + angle_long_side = math.atan(self.heel_depth / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination))))) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) + cutting_plane_origin.transform(rot_long_side) + + # Rotate second cutting plane at the end of the notch (short side of the heel) + angle_short_side = math.radians(180 - self.strut_inclination) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) + cutting_plane_end.transform(rot_short_side) + + return [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_end)] + + def _calculate_double_planes(self, ref_side, rot_axis): + """Calculate cutting planes for a double notch.""" + if self.strut_inclination > 90: + # Move the frames to the start and end of the notch to create the cuts + p_origin = ref_side.point_at(self.start_x, self.start_y) + p_heel = ref_side.point_at(self.start_x + self.displacement_heel, self.start_y) + p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_heel_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_heel_step = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # Calculate heel cutting planes angles + # Rotate first cutting plane at the start of the notch (short side of the heel) + angle_short_side_heel = math.radians(180 - self.strut_inclination) + rot_short_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_short_side_heel, point=p_origin) + cutting_plane_origin.transform(rot_short_side_heel) + + # Rotate second cutting plane at the end of the notch (long side of the heel) + angle_long_side_heel = math.radians(270 - self.strut_inclination) + rot_long_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_long_side_heel, point=p_heel) + cutting_plane_heel_heel.transform(rot_long_side_heel) + + # Calculate step cutting planes angles + # Rotate first cutting plane at the end of the heel of the notch (long side of the step) + angle_long_side_step = math.atan(self.step_depth / (self.displacement_end - self.displacement_heel - self.step_depth / math.tan(math.radians(self.strut_inclination / 2)))) + rot_long_side_step = Rotation.from_axis_and_angle(rot_axis, angle_long_side_step, point=p_heel) + cutting_plane_heel_step.transform(rot_long_side_step) + + # Rotate second cutting plane at the end of the notch (short side of the step) + angle_short_side_step = math.radians(180 - self.strut_inclination / 2) + rot_short_side_step = Rotation.from_axis_and_angle(rot_axis, angle_short_side_step, point=p_end) + cutting_plane_end.transform(rot_short_side_step) + else: + # Move the frames to the start and end of the notch to create the cuts + p_origin = ref_side.point_at(self.start_x, self.start_y) + p_heel = ref_side.point_at(self.start_x + (self.displacement_end - self.displacement_heel), self.start_y) + p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_heel_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_heel_step = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # Calculate step cutting planes angles + # Rotate first cutting plane at the start of the notch (short side of the step) + angle_short_side_step = math.radians(90 + self.strut_inclination / 2) + rot_short_side_step = Rotation.from_axis_and_angle(rot_axis, angle_short_side_step, point=p_origin) + cutting_plane_origin.transform(rot_short_side_step) + + # Rotate second cutting plane at the end of the notch (large side of the step) + angle_long_side_step = math.radians(180) - math.atan(self.step_depth / (self.displacement_end - self.displacement_heel - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2)))) + rot_long_side_step = Rotation.from_axis_and_angle(rot_axis, angle_long_side_step, point=p_heel) + cutting_plane_heel_step.transform(rot_long_side_step) + + # Calculate heel cutting planes angles + # Rotate first cutting plane at the displaced start of the notch (long side of the heel) + angle_long_side_heel = math.radians(90 - self.strut_inclination) + rot_long_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_long_side_heel, point=p_heel) + cutting_plane_heel_heel.transform(rot_long_side_heel) + + # Rotate second cutting plane at the end of the notch (short side of the heel) + angle_short_side_heel = math.radians(180 - self.strut_inclination) + rot_short_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_short_side_heel, point=p_end) + cutting_plane_end.transform(rot_short_side_heel) + + return [ + Plane.from_frame(cutting_plane_origin), + Plane.from_frame(cutting_plane_heel_heel), + Plane.from_frame(cutting_plane_heel_step), + Plane.from_frame(cutting_plane_end) + ] + + + def mortise_volume_from_params_and_beam(self, beam): + """Calculates the mortise volume from the machining parameters in this instance and the given beam - elif self.step_type == "heel": - if self.strut_inclination>90: - start_displacement = abs(self.heel_depth / (math.sin(math.radians(self.strut_inclination))*math.cos(math.radians(self.strut_inclination)))) #abs() because cos(180-x)-->gives negative - rot_axis = ref_side.frame.yaxis - if self.orientation==OrientationType.END: - start_displacement = -start_displacement # negative displacement for the end cut - rot_axis = -rot_axis # negative rotation axis for the end cut - - #move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x, self.start_y) - p_displaced = ref_side.point_at(self.start_x + start_displacement, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_displaced = Frame(p_displaced, ref_side.frame.xaxis, ref_side.frame.yaxis) - # rotate first cutting plane at the start of the notch (short side of the heel) - angle_short_side = math.rad(180-self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) - cutting_plane_origin.transform(rot_short_side) - # rotate second cutting plane at the end of the notch (long side of the heel) - angle_long_side = math.radians(270-self.strut_inclination) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_displaced) - cutting_plane_displaced.transform(rot_long_side) - else: - start_displacement_2 = self.heel_depth / (math.sin(math.radians(self.strut_inclination))*math.cos(math.radians(self.strut_inclination))) - start_displacement_1 = start_displacement_2 - (self.strut_height / math.sin(math.radians(self.strut_inclination))) - rot_axis = ref_side.frame.yaxis - if self.orientation==OrientationType.END: - start_displacement_1, start_displacement_2 = -start_displacement_1, -start_displacement_2 # negative displacement for the start and end cut - rot_axis = -rot_axis # negative rotation axis for the end cut - - #move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x+start_displacement_1, self.start_y) - p_displaced = ref_side.point_at(self.start_x+start_displacement_2, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_displaced = Frame(p_displaced, ref_side.frame.xaxis, ref_side.frame.yaxis) - # rotate first cutting plane at the translated start of the notch (long side of the heel) - angle_long_side = math.radians(270+self.strut_inclination) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) - cutting_plane_origin.transform(rot_long_side) - # rotate second cutting plane at the end of the notch (short side of the heel) - angle_short_side = math.radians(180+self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_displaced) - cutting_plane_displaced.transform(rot_short_side) - cutting_planes = [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_displaced)] + Parameters + ---------- + beam : :class:`compas_timber.elements.Beam` + The beam that is cut by this instance. - elif self.step_type == "heel_tapered": - start_displacement = self.strut_height / math.sin(math.radians(self.strut_inclination)) - rot_axis = ref_side.frame.yaxis - if self.orientation==OrientationType.END: - start_displacement = -start_displacement # negative displacement for the end cut - rot_axis = -rot_axis # negative rotation axis for the end cut + Returns + ------- + :class:`compas.geometry.Polyhedron` + The mortise volume. - #move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x, self.start_y) - p_displaced = ref_side.point_at(self.start_x + start_displacement, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_displaced = Frame(p_displaced, ref_side.frame.xaxis, ref_side.frame.yaxis) - if self.strut_inclination>90: - # rotate first cutting plane at the start of the notch (short side of the heel) - angle_short_side = math.radians(180-self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) - cutting_plane_origin.transform(rot_short_side) - # rotate second cutting plane at the end of the notch (long side of the heel) - angle_long_side = math.radians(180) - math.atan(self.heel_depth / (abs(start_displacement) - abs(self.heel_depth/math.tan(math.radians(self.strut_inclination))))) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_displaced) - cutting_plane_displaced.transform(rot_long_side) - else: - # rotate first cutting plane at the start of the notch (long side of the heel) - angle_long_side = math.atan(self.heel_depth / (abs(start_displacement) - abs(self.heel_depth/math.tan(math.radians(self.strut_inclination))))) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) - cutting_plane_origin.transform(rot_long_side) - # rotate second cutting plane at the end of the notch (short side of the heel) - angle_short_side = math.radians(180-self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_displaced) - cutting_plane_displaced.transform(rot_short_side) - cutting_planes = [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_displaced)] - - elif self.step_type == "double": #TODO: implement double step - pass + """ + # type: (Beam) -> Mesh + + assert self.strut_inclination is not None + assert self.step_shape is not None + assert self.strut_height is not None + assert self.notch_width is not None + assert self.mortise == "yes" + assert self.mortise_width is not None + assert self.mortise_height is not None - return cutting_planes + # start with a plane aligned with the ref side but shifted to the start of the first cut + ref_side = beam.side_as_surface(self.ref_side_index) + rot_axis = ref_side.frame.yaxis + + start_x = self.start_x + displacement_x = self.strut_height / math.sin(math.radians(self.strut_inclination)) + start_y = self.start_y + (self.notch_width - self.mortise_width)/2 + displacement_y = self.mortise_width + + step_cutting_planes = self._calculate_step_planes(ref_side, rot_axis) + step_cutting_plane = step_cutting_planes[1] # the second cutting plane is the one at the end of the step + + if self.orientation==OrientationType.END: + displacement_x = -displacement_x # negative displacement for the end cut + rot_axis = -rot_axis # negative rotation axis for the end cut + step_cutting_plane = step_cutting_planes[0] # the first cutting plane is the one at the start of the step + + #find the points that create the top face of the mortise + p_1 = ref_side.point_at(start_x, start_y) + p_2 = ref_side.point_at(start_x + displacement_x, start_y) + p_3 = ref_side.point_at(start_x + displacement_x, start_y + displacement_y) + p_4 = ref_side.point_at(start_x, start_y + displacement_y) + + #construct polyline for the top face of the mortise + mortise_polyline = Polyline([p_1, p_2, p_3, p_4, p_1]) + #calcutate the plane for the extrusion of the polyline + extr_plane = Plane(p_1, ref_side.frame.xaxis) + extr_vector_length = self.mortise_height/math.sin(math.radians(self.strut_inclination)) + extr_vector = extr_plane.normal * extr_vector_length + if self.strut_inclination>90: + vector_angle = math.radians(180-self.strut_inclination) + else: + vector_angle = math.radians(self.strut_inclination) + rot_vect = Rotation.from_axis_and_angle(rot_axis, vector_angle) + extr_vector.transform(rot_vect) + #extrude the polyline to create the mortise volume as a Brep + mortise_volume = Brep.from_extrusion(mortise_polyline, extr_vector, cap_ends=True) + #trim brep with step cutting planes + mortise_volume.trim(step_cutting_plane) #TODO: check if the trimming works correctly // add checks + + return mortise_volume class StepJointNotchParams(BTLxProcessParams): """A class to store the parameters of a Jack Rafter Cut feature. From 906fd1d43a8c58ab1f908cfcf0906e396460fcca Mon Sep 17 00:00:00 2001 From: papachap Date: Mon, 5 Aug 2024 10:00:50 +0200 Subject: [PATCH 11/63] fix linting with pylint/flake8 --- .../_fabrication/step_joint_notch.py | 61 +++++++++---------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index 4ad38a9b3..efe79b2f8 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -1,6 +1,5 @@ import math -from compas.geometry import BrepTrimmingError from compas.geometry import Frame from compas.geometry import Line from compas.geometry import Plane @@ -208,7 +207,7 @@ def strut_height(self, strut_height): def step_shape(self): return self._step_shape - @step_shape.setter #TODO: should this be defined automatically depending on the other parameters? (ie. if heel_depth > 0 and step_depth > 0, then step_shape = "double") + @step_shape.setter # TODO: should this be defined automatically depending on the other parameters? (ie. if heel_depth > 0 and step_depth > 0, then step_shape = "double") def step_shape(self, step_shape): if step_shape not in ["double", "step", "heel", "taperedheel"]: raise ValueError("StepShape must be either 'double', 'step', 'heel', or 'taperedheel'.") @@ -244,11 +243,11 @@ def mortise_height(self, mortise_height): raise ValueError("MortiseHeight must be less than 1000.0.") self._mortise_height = mortise_height - @property #TODO: how should these be better implemented? + @property # TODO: how should these be better implemented? def displacement_end(self): return self._calculate_displacement_end(self.strut_height, self.strut_inclination, self.orientation) - @property #TODO: how should these be better implemented? + @property # TODO: how should these be better implemented? def displacement_heel(self): return self._calculate_displacement_heel(self.heel_depth, self.strut_inclination, self.orientation) @@ -275,14 +274,14 @@ def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=20 """ # type: (PlanarSurface|Surface, Beam, bool, float, float, float, bool, int) -> StepJointNotch - #TODO: the stepjointnotch is always orthogonal, this means that the surface should be perpendicular to the beam's ref_side | should there be a check for that? - #TODO: I am using a PlanarSurface instead of a Plane because otherwise there is no way to define the start_y of the Notch. This makes a case for the default ref_side to be a PlanarSurface - #TODO: The alternative solution in order to use a Plane instead would be to have start_y and notch_width as parameters of the class + # TODO: the stepjointnotch is always orthogonal, this means that the surface should be perpendicular to the beam's ref_side | should there be a check for that? + # TODO: I am using a PlanarSurface instead of a Plane because otherwise there is no way to define the start_y of the Notch. This makes a case for the default ref_side to be a PlanarSurface + # TODO: The alternative solution in order to use a Plane instead would be to have start_y and notch_width as parameters of the class # define ref_side & ref_edge ref_side = beam.ref_sides[ref_side_index] # TODO: is this arbitrary? ref_edge = Line.from_point_and_vector(ref_side.point, ref_side.xaxis) plane = surface.to_plane() - intersection_line = Line(*ref_side.intersections_with_surface(surface)) #TODO NotImplementedError: Plane|Surface // COMPAS core issue + intersection_line = Line(*ref_side.intersections_with_surface(surface)) # TODO NotImplementedError: Plane|Surface // COMPAS core issue # calculate orientation orientation = cls._calculate_orientation(ref_side, plane) @@ -300,7 +299,7 @@ def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=20 strut_inclination = cls._calculate_strut_inclination(ref_side, plane, orientation) # calculate notch_width - if notch_limited == True: + if notch_limited is True: notch_width = intersection_line.length notch_limited = "yes" else: @@ -346,7 +345,7 @@ def _calculate_strut_inclination(ref_side, plane, orientation): if orientation == OrientationType.START: return abs(strut_inclination) else: - return 180 - abs(strut_inclination) # get the other side of the angle + return 180 - abs(strut_inclination) # get the other side of the angle @staticmethod def _define_step_shape(step_depth, heel_depth, tapered_heel): @@ -366,17 +365,17 @@ def _define_step_shape(step_depth, heel_depth, tapered_heel): @staticmethod def _calculate_displacement_end(strut_height, strut_inclination, orientation): - #Calculates the linear displacement from the origin point to the end of the notch based on the strut_height and strut_inclination. + # Calculates the linear displacement from the origin point to the end of the notch based on the strut_height and strut_inclination. displacement_end = strut_height / math.sin(math.radians(strut_inclination)) - if orientation==OrientationType.END: - displacement_end = -displacement_end # negative displacement for the end cut + if orientation == OrientationType.END: + displacement_end = -displacement_end # negative displacement for the end cut return displacement_end @staticmethod def _calculate_displacement_heel(heel_depth, strut_inclination, orientation): - #Calculates the linear displacement from the origin point to the heel of the notch based on the heel_depth and strut_inclination. + # Calculates the linear displacement from the origin point to the heel of the notch based on the heel_depth and strut_inclination. displacement_heel = abs(heel_depth / (math.sin(math.radians(strut_inclination))*math.cos(math.radians(strut_inclination)))) - if orientation==OrientationType.END: + if orientation == OrientationType.END: displacement_heel = -displacement_heel return displacement_heel @@ -417,7 +416,7 @@ def apply(self, geometry, beam): ) # create notch polyedron from planes # add ref_side plane to create a polyhedron - cutting_planes.append(Plane.from_frame(beam.ref_sides[self.ref_side_index])) #TODO: the beam's ref_side Plane might need to be offsetted to create a valid polyhedron when step_type is "double" + cutting_planes.append(Plane.from_frame(beam.ref_sides[self.ref_side_index])) # TODO: the beam's ref_side Plane might need to be offsetted to create a valid polyhedron when step_type is "double" try: notch_polyhedron = Polyhedron.from_planes(cutting_planes) except Exception as e: @@ -461,7 +460,7 @@ def apply(self, geometry, beam): "The cutting planes do not create a volume that intersects with beam geometry." ) - if self.mortise == "yes": #TODO: implement mortise + if self.mortise == "yes": # TODO: implement mortise # create mortise volume and subtract from brep_with_notch pass @@ -480,7 +479,7 @@ def add_mortise(self, mortise_width, mortise_height, beam): self.mortise = "yes" # self.mortise_width = beam.width / 4 # TODO: should this relate to the beam? typically 1/3 or 1/4 of beam.width self.mortise_width = mortise_width - self.mortise_height = beam.height if mortise_height > beam.height else mortise_height #TODO: should this be constrained? + self.mortise_height = beam.height if mortise_height > beam.height else mortise_height # TODO: should this be constrained? def planes_from_params_and_beam(self, beam): """Calculates the cutting planes from the machining parameters in this instance and the given beam @@ -690,7 +689,6 @@ def _calculate_double_planes(self, ref_side, rot_axis): Plane.from_frame(cutting_plane_end) ] - def mortise_volume_from_params_and_beam(self, beam): """Calculates the mortise volume from the machining parameters in this instance and the given beam @@ -725,38 +723,39 @@ def mortise_volume_from_params_and_beam(self, beam): displacement_y = self.mortise_width step_cutting_planes = self._calculate_step_planes(ref_side, rot_axis) - step_cutting_plane = step_cutting_planes[1] # the second cutting plane is the one at the end of the step + step_cutting_plane = step_cutting_planes[1] # the second cutting plane is the one at the end of the step - if self.orientation==OrientationType.END: - displacement_x = -displacement_x # negative displacement for the end cut - rot_axis = -rot_axis # negative rotation axis for the end cut - step_cutting_plane = step_cutting_planes[0] # the first cutting plane is the one at the start of the step + if self.orientation == OrientationType.END: + displacement_x = -displacement_x # negative displacement for the end cut + rot_axis = -rot_axis # negative rotation axis for the end cut + step_cutting_plane = step_cutting_planes[0] # the first cutting plane is the one at the start of the step - #find the points that create the top face of the mortise + # find the points that create the top face of the mortise p_1 = ref_side.point_at(start_x, start_y) p_2 = ref_side.point_at(start_x + displacement_x, start_y) p_3 = ref_side.point_at(start_x + displacement_x, start_y + displacement_y) p_4 = ref_side.point_at(start_x, start_y + displacement_y) - #construct polyline for the top face of the mortise + # construct polyline for the top face of the mortise mortise_polyline = Polyline([p_1, p_2, p_3, p_4, p_1]) - #calcutate the plane for the extrusion of the polyline + # calcutate the plane for the extrusion of the polyline extr_plane = Plane(p_1, ref_side.frame.xaxis) extr_vector_length = self.mortise_height/math.sin(math.radians(self.strut_inclination)) extr_vector = extr_plane.normal * extr_vector_length - if self.strut_inclination>90: + if self.strut_inclination > 90: vector_angle = math.radians(180-self.strut_inclination) else: vector_angle = math.radians(self.strut_inclination) rot_vect = Rotation.from_axis_and_angle(rot_axis, vector_angle) extr_vector.transform(rot_vect) - #extrude the polyline to create the mortise volume as a Brep + # extrude the polyline to create the mortise volume as a Brep mortise_volume = Brep.from_extrusion(mortise_polyline, extr_vector, cap_ends=True) - #trim brep with step cutting planes - mortise_volume.trim(step_cutting_plane) #TODO: check if the trimming works correctly // add checks + # trim brep with step cutting planes + mortise_volume.trim(step_cutting_plane) # TODO: check if the trimming works correctly // add checks return mortise_volume + class StepJointNotchParams(BTLxProcessParams): """A class to store the parameters of a Jack Rafter Cut feature. From e4ad54af1d6cb37518c2060e117254bff2152caa Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 6 Aug 2024 14:55:13 +0200 Subject: [PATCH 12/63] Enum for StepShape --- .../_fabrication/btlx_process.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/compas_timber/_fabrication/btlx_process.py b/src/compas_timber/_fabrication/btlx_process.py index d8673a300..e6405bec6 100644 --- a/src/compas_timber/_fabrication/btlx_process.py +++ b/src/compas_timber/_fabrication/btlx_process.py @@ -85,3 +85,24 @@ class OrientationType(object): START = "start" END = "end" + + +class StepShape(object): + """Enum for the step shape of the cut. + + Attributes + ---------- + STEP : literal("step") + A step shape. + HEEL : literal("heel") + A heel shape. + TAPERED_HEEL : literal("taperedheel") + A tapered heel shape. + DOUBLE : literal("double") + A double shape. + """ + + STEP = "step" + HEEL = "heel" + TAPERED_HEEL = "taperedheel" + DOUBLE = "double" From 320a4647b731569d1ae287f91af1d4c873cb9522 Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 6 Aug 2024 15:01:08 +0200 Subject: [PATCH 13/63] first round of comments + formatting --- src/compas_timber/_fabrication/__init__.py | 9 +- .../_fabrication/step_joint_notch.py | 202 +++++++++++------- 2 files changed, 135 insertions(+), 76 deletions(-) diff --git a/src/compas_timber/_fabrication/__init__.py b/src/compas_timber/_fabrication/__init__.py index f45ae14ac..c17d800a7 100644 --- a/src/compas_timber/_fabrication/__init__.py +++ b/src/compas_timber/_fabrication/__init__.py @@ -8,4 +8,11 @@ from .step_joint_notch import StepJointNotchParams -__all__ = ["JackRafterCut", "BTLxProcess", "OrientationType", "JackRafterCutParams", "StepJointNotch", "StepJointNotchParams"] +__all__ = [ + "JackRafterCut", + "BTLxProcess", + "OrientationType", + "JackRafterCutParams", + "StepJointNotch", + "StepJointNotchParams", +] diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index efe79b2f8..db9405fb9 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -19,6 +19,7 @@ from .btlx_process import BTLxProcess from .btlx_process import BTLxProcessParams from .btlx_process import OrientationType +from .btlx_process import StepShape class StepJointNotch(BTLxProcess): @@ -45,7 +46,7 @@ class StepJointNotch(BTLxProcess): strut_height : float The height of the strut. It is the cross beam's height. strut_height < 50000.0. step_shape : str - The shape of the step. Must be either 'double', 'step', 'heel', or 'taperedheel'. + The shape of the step. Must be either StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL or StepShape.TAPERED_HEEL. mortise : str The presence of a mortise. Must be either 'no' or 'yes'. mortise_width : float @@ -75,7 +76,23 @@ def __data__(self): data["mortise_height"] = self.mortise_height return data - def __init__(self, orientation, start_x=0.0, start_y=0.0, strut_inclination=90.0, notch_limited="no", notch_width=20.0, step_depth=20.0, heel_depth=20.0, strut_height=20.0, step_shape="double", mortise="no", mortise_width=40.0, mortise_height=40.0, **kwargs): + def __init__( + self, + orientation, + start_x=0.0, + start_y=0.0, + strut_inclination=90.0, + notch_limited=False, + notch_width=20.0, + step_depth=20.0, + heel_depth=20.0, + strut_height=20.0, + step_shape=StepShape.DOUBLE, + mortise=False, + mortise_width=40.0, + mortise_height=40.0, + **kwargs, + ): super(StepJointNotch, self).__init__(**kwargs) self._orientation = None self._start_x = None @@ -159,8 +176,8 @@ def notch_limited(self): @notch_limited.setter def notch_limited(self, notch_limited): - if notch_limited not in ["no", "yes"]: - raise ValueError("NotchLimited must be either 'no' or 'yes'.") + if not isinstance(notch_limited, bool): + raise ValueError("NotchLimited must be either True or False.") self._notch_limited = notch_limited @property @@ -209,8 +226,10 @@ def step_shape(self): @step_shape.setter # TODO: should this be defined automatically depending on the other parameters? (ie. if heel_depth > 0 and step_depth > 0, then step_shape = "double") def step_shape(self, step_shape): - if step_shape not in ["double", "step", "heel", "taperedheel"]: - raise ValueError("StepShape must be either 'double', 'step', 'heel', or 'taperedheel'.") + if step_shape not in [StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL, StepShape.TAPERED_HEEL]: + raise ValueError( + "StepShape must be either StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL or StepShape.TAPERED_HEEL." + ) self._step_shape = step_shape @property @@ -219,8 +238,8 @@ def mortise(self): @mortise.setter def mortise(self, mortise): - if mortise not in ["no", "yes"]: - raise ValueError("Mortise must be either 'no' or 'yes'.") + if not isinstance(mortise, bool): + raise ValueError("Mortise must be either True or False.") self._mortise = mortise @property @@ -243,11 +262,11 @@ def mortise_height(self, mortise_height): raise ValueError("MortiseHeight must be less than 1000.0.") self._mortise_height = mortise_height - @property # TODO: how should these be better implemented? + @property def displacement_end(self): return self._calculate_displacement_end(self.strut_height, self.strut_inclination, self.orientation) - @property # TODO: how should these be better implemented? + @property def displacement_heel(self): return self._calculate_displacement_heel(self.heel_depth, self.strut_inclination, self.orientation) @@ -256,8 +275,18 @@ def displacement_heel(self): ######################################################################## @classmethod - def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=20.0, heel_depth=0.0, strut_height=20.0, tapered_heel=False, ref_side_index=0): - """Create a StepJointNotch instance from a cutting surface and the beam it should cut. This could be the ref_side of the main beam of a Joint. + def from_surface_and_beam( + cls, + surface, + beam, + notch_limited=False, + step_depth=20.0, + heel_depth=0.0, + strut_height=20.0, + tapered_heel=False, + ref_side_index=0, + ): + """Create a StepJointNotch instance from a cutting surface and the beam it should cut. This could be the ref_side of the main beam of a Joint and the cross beam. Parameters ---------- @@ -281,7 +310,7 @@ def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=20 ref_side = beam.ref_sides[ref_side_index] # TODO: is this arbitrary? ref_edge = Line.from_point_and_vector(ref_side.point, ref_side.xaxis) plane = surface.to_plane() - intersection_line = Line(*ref_side.intersections_with_surface(surface)) # TODO NotImplementedError: Plane|Surface // COMPAS core issue + intersection_line = Line(*ref_side.intersections_with_surface(surface)) # calculate orientation orientation = cls._calculate_orientation(ref_side, plane) @@ -299,12 +328,10 @@ def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=20 strut_inclination = cls._calculate_strut_inclination(ref_side, plane, orientation) # calculate notch_width - if notch_limited is True: + if notch_limited: notch_width = intersection_line.length - notch_limited = "yes" else: notch_width = beam.width - notch_limited = "no" # restrain step_depth & heel_depth to beam's height # TODO: should it be restrained? should they be proportional to the beam's dimensions? step_depth = beam.height if step_depth > beam.height else step_depth @@ -313,7 +340,19 @@ def from_surface_and_beam(cls, surface, beam, notch_limited=False, step_depth=20 # define step_shape step_shape = cls._define_step_shape(step_depth, heel_depth, tapered_heel) - return cls(orientation, start_x, start_y, strut_inclination, notch_limited, notch_width, step_depth, heel_depth, step_shape, strut_height, ref_side_index=ref_side_index) + return cls( + orientation, + start_x, + start_y, + strut_inclination, + notch_limited, + notch_width, + step_depth, + heel_depth, + step_shape, + strut_height, + ref_side_index=ref_side_index, + ) @staticmethod def _calculate_orientation(ref_side, cutting_plane): @@ -351,17 +390,16 @@ def _calculate_strut_inclination(ref_side, plane, orientation): def _define_step_shape(step_depth, heel_depth, tapered_heel): # step_shape based on step_depth and heel_depth and tapered_heel variables if step_depth > 0.0 and heel_depth == 0.0: - step_shape = "step" + return StepShape.STEP elif step_depth == 0.0 and heel_depth > 0.0: if tapered_heel: - step_shape = "heel_tapered" + return StepShape.TAPERED_HEEL else: - step_shape = "heel" + return StepShape.HEEL elif step_depth > 0.0 and heel_depth > 0.0: - step_shape = "double" + return StepShape.DOUBLE else: - raise ValueError("at least one of step_depth or heel_depth must be greater than 0.0.") - return step_shape + raise ValueError("At least one of step_depth or heel_depth must be greater than 0.0.") @staticmethod def _calculate_displacement_end(strut_height, strut_inclination, orientation): @@ -374,7 +412,9 @@ def _calculate_displacement_end(strut_height, strut_inclination, orientation): @staticmethod def _calculate_displacement_heel(heel_depth, strut_inclination, orientation): # Calculates the linear displacement from the origin point to the heel of the notch based on the heel_depth and strut_inclination. - displacement_heel = abs(heel_depth / (math.sin(math.radians(strut_inclination))*math.cos(math.radians(strut_inclination)))) + displacement_heel = abs( + heel_depth / (math.sin(math.radians(strut_inclination)) * math.cos(math.radians(strut_inclination))) + ) if orientation == OrientationType.END: displacement_heel = -displacement_heel return displacement_heel @@ -410,57 +450,41 @@ def apply(self, geometry, beam): cutting_planes = self.planes_from_params_and_beam(beam) except ValueError as e: raise FeatureApplicationError( - None, - geometry, - f"Failed to generate cutting planes from parameters and beam: {str(e)}" + None, geometry, f"Failed to generate cutting planes from parameters and beam: {str(e)}" ) # create notch polyedron from planes # add ref_side plane to create a polyhedron - cutting_planes.append(Plane.from_frame(beam.ref_sides[self.ref_side_index])) # TODO: the beam's ref_side Plane might need to be offsetted to create a valid polyhedron when step_type is "double" + cutting_planes.append( + Plane.from_frame(beam.ref_sides[self.ref_side_index]) + ) # !: the beam's ref_side Plane might need to be offsetted to create a valid polyhedron when step_type is "double" try: notch_polyhedron = Polyhedron.from_planes(cutting_planes) except Exception as e: raise FeatureApplicationError( - cutting_planes, - geometry, - f"Failed to create valid polyhedron from cutting planes: {str(e)}" + cutting_planes, geometry, f"Failed to create valid polyhedron from cutting planes: {str(e)}" ) # convert polyhedron to mesh try: notch_mesh = notch_polyhedron.to_mesh() except Exception as e: - raise FeatureApplicationError( - notch_polyhedron, - geometry, - f"Failed to convert polyhedron to mesh: {str(e)}" - ) + raise FeatureApplicationError(notch_polyhedron, geometry, f"Failed to convert polyhedron to mesh: {str(e)}") # convert mesh to brep try: notch_brep = Brep.from_mesh(notch_mesh) except Exception as e: - raise FeatureApplicationError( - notch_mesh, - geometry, - f"Failed to convert mesh to Brep: {str(e)}" - ) + raise FeatureApplicationError(notch_mesh, geometry, f"Failed to convert mesh to Brep: {str(e)}") # apply boolean difference try: brep_with_notch = Brep.from_boolean_difference(geometry, notch_brep) except Exception as e: - raise FeatureApplicationError( - notch_brep, - geometry, - f"Boolean difference operation failed: {str(e)}" - ) + raise FeatureApplicationError(notch_brep, geometry, f"Boolean difference operation failed: {str(e)}") # check if the notch is empty if not brep_with_notch: raise FeatureApplicationError( - notch_brep, - geometry, - "The cutting planes do not create a volume that intersects with beam geometry." + notch_brep, geometry, "The cutting planes do not create a volume that intersects with beam geometry." ) - if self.mortise == "yes": # TODO: implement mortise + if self.mortise: # !: implement mortise # create mortise volume and subtract from brep_with_notch pass @@ -476,10 +500,12 @@ def add_mortise(self, mortise_width, mortise_height, beam): mortise_height : float The height of the mortise. mortise_height < 1000.0. """ - self.mortise = "yes" + self.mortise = True # self.mortise_width = beam.width / 4 # TODO: should this relate to the beam? typically 1/3 or 1/4 of beam.width self.mortise_width = mortise_width - self.mortise_height = beam.height if mortise_height > beam.height else mortise_height # TODO: should this be constrained? + self.mortise_height = ( + beam.height if mortise_height > beam.height else mortise_height + ) # TODO: should this be constrained? def planes_from_params_and_beam(self, beam): """Calculates the cutting planes from the machining parameters in this instance and the given beam @@ -505,13 +531,13 @@ def planes_from_params_and_beam(self, beam): if self.orientation == OrientationType.END: rot_axis = -rot_axis # Negative rotation axis for the end cut - if self.step_type == "step": + if self.step_shape == StepShape.STEP: return self._calculate_step_planes(ref_side, rot_axis) - elif self.step_type == "heel": + elif self.step_shape == StepShape.HEEL: return self._calculate_heel_planes(ref_side, rot_axis) - elif self.step_type == "heel_tapered": - return self._calculate_heel_tapered_planes(ref_side, rot_axis) - elif self.step_type == "double": + elif self.step_shape == StepShape.TAPERED_HEEL: + return self._calculate_tapered_heel_planes(ref_side, rot_axis) + elif self.step_shape == StepShape.DOUBLE: return self._calculate_double_planes(ref_side, rot_axis) def _calculate_step_planes(self, ref_side, rot_axis): @@ -525,7 +551,10 @@ def _calculate_step_planes(self, ref_side, rot_axis): # Calculate step cutting planes angles if self.strut_inclination > 90: # Rotate first cutting plane at the start of the notch (large side of the step) - angle_long_side = math.atan(self.step_depth / (self.displacement_end - self.step_depth / math.tan(math.radians(self.strut_inclination / 2)))) + angle_long_side = math.atan( + self.step_depth + / (self.displacement_end - self.step_depth / math.tan(math.radians(self.strut_inclination / 2))) + ) rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) cutting_plane_origin.transform(rot_long_side) @@ -540,7 +569,10 @@ def _calculate_step_planes(self, ref_side, rot_axis): cutting_plane_origin.transform(rot_short_side) # Rotate second cutting plane at the end of the notch (large side of the step) - angle_long_side = math.radians(180) - math.atan(self.step_depth / (self.displacement_end - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2)))) + angle_long_side = math.radians(180) - math.atan( + self.step_depth + / (self.displacement_end - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2))) + ) rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) cutting_plane_end.transform(rot_long_side) @@ -585,7 +617,7 @@ def _calculate_heel_planes(self, ref_side, rot_axis): return [Plane.from_frame(cutting_plane_heel), Plane.from_frame(cutting_plane_end)] - def _calculate_heel_tapered_planes(self, ref_side, rot_axis): + def _calculate_tapered_heel_planes(self, ref_side, rot_axis): """Calculate cutting planes for a tapered heel notch.""" # Move the frames to the start and end of the notch to create the cuts p_origin = ref_side.point_at(self.start_x, self.start_y) @@ -601,12 +633,18 @@ def _calculate_heel_tapered_planes(self, ref_side, rot_axis): cutting_plane_origin.transform(rot_short_side) # Rotate second cutting plane at the end of the notch (long side of the heel) - angle_long_side = math.radians(180) - math.atan(self.heel_depth / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination))))) + angle_long_side = math.radians(180) - math.atan( + self.heel_depth + / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination)))) + ) rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) cutting_plane_end.transform(rot_long_side) else: # Rotate first cutting plane at the start of the notch (long side of the heel) - angle_long_side = math.atan(self.heel_depth / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination))))) + angle_long_side = math.atan( + self.heel_depth + / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination)))) + ) rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) cutting_plane_origin.transform(rot_long_side) @@ -642,7 +680,14 @@ def _calculate_double_planes(self, ref_side, rot_axis): # Calculate step cutting planes angles # Rotate first cutting plane at the end of the heel of the notch (long side of the step) - angle_long_side_step = math.atan(self.step_depth / (self.displacement_end - self.displacement_heel - self.step_depth / math.tan(math.radians(self.strut_inclination / 2)))) + angle_long_side_step = math.atan( + self.step_depth + / ( + self.displacement_end + - self.displacement_heel + - self.step_depth / math.tan(math.radians(self.strut_inclination / 2)) + ) + ) rot_long_side_step = Rotation.from_axis_and_angle(rot_axis, angle_long_side_step, point=p_heel) cutting_plane_heel_step.transform(rot_long_side_step) @@ -667,7 +712,14 @@ def _calculate_double_planes(self, ref_side, rot_axis): cutting_plane_origin.transform(rot_short_side_step) # Rotate second cutting plane at the end of the notch (large side of the step) - angle_long_side_step = math.radians(180) - math.atan(self.step_depth / (self.displacement_end - self.displacement_heel - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2)))) + angle_long_side_step = math.radians(180) - math.atan( + self.step_depth + / ( + self.displacement_end + - self.displacement_heel + - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2)) + ) + ) rot_long_side_step = Rotation.from_axis_and_angle(rot_axis, angle_long_side_step, point=p_heel) cutting_plane_heel_step.transform(rot_long_side_step) @@ -686,7 +738,7 @@ def _calculate_double_planes(self, ref_side, rot_axis): Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_heel_heel), Plane.from_frame(cutting_plane_heel_step), - Plane.from_frame(cutting_plane_end) + Plane.from_frame(cutting_plane_end), ] def mortise_volume_from_params_and_beam(self, beam): @@ -709,7 +761,7 @@ def mortise_volume_from_params_and_beam(self, beam): assert self.step_shape is not None assert self.strut_height is not None assert self.notch_width is not None - assert self.mortise == "yes" + assert self.mortise == True assert self.mortise_width is not None assert self.mortise_height is not None @@ -719,7 +771,7 @@ def mortise_volume_from_params_and_beam(self, beam): start_x = self.start_x displacement_x = self.strut_height / math.sin(math.radians(self.strut_inclination)) - start_y = self.start_y + (self.notch_width - self.mortise_width)/2 + start_y = self.start_y + (self.notch_width - self.mortise_width) / 2 displacement_y = self.mortise_width step_cutting_planes = self._calculate_step_planes(ref_side, rot_axis) @@ -740,10 +792,10 @@ def mortise_volume_from_params_and_beam(self, beam): mortise_polyline = Polyline([p_1, p_2, p_3, p_4, p_1]) # calcutate the plane for the extrusion of the polyline extr_plane = Plane(p_1, ref_side.frame.xaxis) - extr_vector_length = self.mortise_height/math.sin(math.radians(self.strut_inclination)) + extr_vector_length = self.mortise_height / math.sin(math.radians(self.strut_inclination)) extr_vector = extr_plane.normal * extr_vector_length if self.strut_inclination > 90: - vector_angle = math.radians(180-self.strut_inclination) + vector_angle = math.radians(180 - self.strut_inclination) else: vector_angle = math.radians(self.strut_inclination) rot_vect = Rotation.from_axis_and_angle(rot_axis, vector_angle) @@ -751,18 +803,18 @@ def mortise_volume_from_params_and_beam(self, beam): # extrude the polyline to create the mortise volume as a Brep mortise_volume = Brep.from_extrusion(mortise_polyline, extr_vector, cap_ends=True) # trim brep with step cutting planes - mortise_volume.trim(step_cutting_plane) # TODO: check if the trimming works correctly // add checks + mortise_volume.trim(step_cutting_plane) # !: check if the trimming works correctly // add checks return mortise_volume class StepJointNotchParams(BTLxProcessParams): - """A class to store the parameters of a Jack Rafter Cut feature. + """A class to store the parameters of a Step Joint Notch feature. Parameters ---------- - instance : :class:`~compas_timber._fabrication.JackRafterCut` - The instance of the Jack Rafter Cut feature. + instance : :class:`~compas_timber._fabrication.StepJointNotch` + The instance of the Step Joint Notch feature. """ def __init__(self, instance): @@ -783,13 +835,13 @@ def as_dict(self): result["StartX"] = "{:.{prec}f}".format(self._instance.start_x, prec=TOL.precision) result["StartY"] = "{:.{prec}f}".format(self._instance.start_y, prec=TOL.precision) result["StrutInclination"] = "{:.{prec}f}".format(self._instance.strut_inclination, prec=TOL.precision) - result["NotchLimited"] = self._instance.notch_limited + result["NotchLimited"] = "yes" if self._instance.notch_limited else "no" result["NotchWidth"] = "{:.{prec}f}".format(self._instance.notch_width, prec=TOL.precision) result["StepDepth"] = "{:.{prec}f}".format(self._instance.step_depth, prec=TOL.precision) result["HeelDepth"] = "{:.{prec}f}".format(self._instance.heel_depth, prec=TOL.precision) result["StrutHeight"] = "{:.{prec}f}".format(self._instance.strut_height, prec=TOL.precision) result["StepShape"] = self._instance.step_shape - result["Mortise"] = self._instance.mortise + result["Mortise"] = "yes" if self._instance.mortise else "no" result["MortiseWidth"] = "{:.{prec}f}".format(self._instance.mortise_width, prec=TOL.precision) result["MortiseHeight"] = "{:.{prec}f}".format(self._instance.mortise_height, prec=TOL.precision) return result From 1ee8919328a356fe5980914dfc09aef0b95b354e Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 6 Aug 2024 15:01:29 +0200 Subject: [PATCH 14/63] initialization of step_joint --- src/compas_timber/_fabrication/step_joint.py | 746 +++++++++++++++++++ 1 file changed, 746 insertions(+) create mode 100644 src/compas_timber/_fabrication/step_joint.py diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py new file mode 100644 index 000000000..112a72112 --- /dev/null +++ b/src/compas_timber/_fabrication/step_joint.py @@ -0,0 +1,746 @@ +import math + +from compas.geometry import Frame +from compas.geometry import Line +from compas.geometry import Plane +from compas.geometry import Rotation +from compas.geometry import Vector +from compas.geometry import Polyhedron +from compas.geometry import Brep +from compas.geometry import Polyline +from compas.geometry import angle_vectors_signed +from compas.geometry import distance_point_point +from compas.geometry import intersection_line_plane +from compas.geometry import is_point_behind_plane +from compas.tolerance import TOL + +from compas_timber.elements import FeatureApplicationError + +from .btlx_process import BTLxProcess +from .btlx_process import BTLxProcessParams +from .btlx_process import OrientationType +from .btlx_process import StepShape + + +class StepJoint(BTLxProcess): + """Represents a Step Joint feature to be made on a beam. + + Parameters + ---------- + orientation : int + The orientation of the cut. Must be either OrientationType.START or OrientationType.END. + start_x : float + The start x-coordinate of the cut in parametric space of the reference side. -100000.0 < start_x < 100000.0. + strut_inclination : float + The inclination of the strut. 0.1 < strut_inclination < 179.9. + step_depth : float + The depth of the step. step_depth < 50000.0. + heel_depth : float + The depth of the heel. heel_depth < 50000.0. + step_shape : str + The shape of the step. Must be either 'double', 'step', 'heel', or 'taperedheel'. + tenon : str + The presence of a tenon. Must be either 'no' or 'yes'. + tenon_width : float + The width of the tenon. tenon_width < 1000.0. + tenon_height : float + The height of the tenon. tenon_height < 1000.0. + + """ + + PROCESS_NAME = "StepJoint" # type: ignore + + @property + def __data__(self): + data = super(StepJoint, self).__data__ + data["orientation"] = self.orientation + data["start_x"] = self.start_x + data["strut_inclination"] = self.strut_inclination + data["step_depth"] = self.step_depth + data["heel_depth"] = self.heel_depth + data["step_shape"] = self.step_shape + data["tenon"] = self.tenon + data["tenon_width"] = self.tenon_width + data["tenon_height"] = self.tenon_height + return data + + def __init__( + self, + orientation, + start_x=0.0, + strut_inclination=90.0, + step_depth=20.0, + heel_depth=20.0, + step_shape=StepShape.DOUBLE, + tenon=False, + tenon_width=40.0, + tenon_height=40.0, + **kwargs, + ): + super(StepJoint, self).__init__(**kwargs) + self._orientation = None + self._start_x = None + self._strut_inclination = None + self._step_depth = None + self._heel_depth = None + self._step_shape = None + self._tenon = None + self._tenon_width = None + self._tenon_height = None + + self.orientation = orientation + self.start_x = start_x + self.strut_inclination = strut_inclination + self.step_depth = step_depth + self.heel_depth = heel_depth + self.step_shape = step_shape + self.tenon = tenon + self.tenon_width = tenon_width + self.tenon_height = tenon_height + + ######################################################################## + # Properties + ######################################################################## + + @property + def params_dict(self): + return StepJointNotchParams(self).as_dict() + + @property + def orientation(self): + return self._orientation + + @orientation.setter + def orientation(self, orientation): + if orientation not in [OrientationType.START, OrientationType.END]: + raise ValueError("Orientation must be either OrientationType.START or OrientationType.END.") + self._orientation = orientation + + @property + def start_x(self): + return self._start_x + + @start_x.setter + def start_x(self, start_x): + if start_x > 100000.0 or start_x < -100000.0: + raise ValueError("StartX must be between -100000.0 and 100000.") + self._start_x = start_x + + @property + def strut_inclination(self): + return self._strut_inclination + + @strut_inclination.setter + def strut_inclination(self, strut_inclination): + if strut_inclination < 0.1 or strut_inclination > 179.9: + raise ValueError("StrutInclination must be between 0.1 and 179.9.") + self._strut_inclination = strut_inclination + + @property + def step_depth(self): + return self._step_depth + + @step_depth.setter + def step_depth(self, step_depth): + if step_depth > 50000.0: + raise ValueError("StepDepth must be less than 50000.0.") + self._step_depth = step_depth + + @property + def heel_depth(self): + return self._heel_depth + + @heel_depth.setter + def heel_depth(self, heel_depth): + if heel_depth > 50000.0: + raise ValueError("HeelDepth must be less than 50000.0.") + self._heel_depth = heel_depth + + @property + def step_shape(self): + return self._step_shape + + @step_shape.setter # TODO: should this be defined automatically depending on the other parameters? (ie. if heel_depth > 0 and step_depth > 0, then step_shape = "double") + def step_shape(self, step_shape): + if step_shape not in [StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL, StepShape.TAPEREDHEEL]: + raise ValueError( + "StepShape must be either StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL, or StepShape.TAPEREDHEEL." + ) + self._step_shape = step_shape + + @property + def tenon(self): + return self._tenon + + @tenon.setter + def tenon(self, tenon): + if not isinstance(tenon, bool): + raise ValueError("tenon must be either True or False.") + self._tenon = tenon + + @property + def tenon_width(self): + return self._tenon_width + + @tenon_width.setter + def tenon_width(self, tenon_width): + if tenon_width > 1000.0: + raise ValueError("tenonWidth must be less than 1000.0.") + self._tenon_width = tenon_width + + @property + def tenon_height(self): + return self._tenon_height + + @tenon_height.setter + def tenon_height(self, tenon_height): + if tenon_height > 1000.0: + raise ValueError("tenonHeight must be less than 1000.0.") + self._tenon_height = tenon_height + + @property # TODO: how should these be better implemented? + def displacement_end(self): + return self._calculate_displacement_end(self.strut_height, self.strut_inclination, self.orientation) + + @property # TODO: how should these be better implemented? + def displacement_heel(self): + return self._calculate_displacement_heel(self.heel_depth, self.strut_inclination, self.orientation) + + ######################################################################## + # Alternative constructors + ######################################################################## + + @classmethod + def from_plane_and_beam(cls, plane, beam, step_depth=20.0, heel_depth=0.0, tapered_heel=False, ref_side_index=0): + """Create a StepJoint instance from a cutting surface and the beam it should cut. This could be the ref_side of the cross beam of a Joint and the main beam. + + Parameters + ---------- + plane : :class:`~compas.geometry.Plane` or :class:`~compas.geometry.Frame` + The cutting plane. + beam : :class:`~compas_timber.elements.Beam` + The beam that is cut by this instance. + ref_side_index : int, optional + The reference side index of the beam to be cut. Default is 0 (i.e. RS1). + + Returns + ------- + :class:`~compas_timber.fabrication.StepJoint` + + """ + # type: (Plane|Frame, Beam, float, float, bool, int) -> StepJoint + # TODO: the stepjointnotch is always orthogonal, this means that the surface should be perpendicular to the beam's ref_side | should there be a check for that? + if isinstance(plane, Frame): + plane = Plane.from_frame(plane) + # define ref_side & ref_edge + ref_side = beam.ref_sides[ref_side_index] # TODO: is this arbitrary? + ref_edge = Line.from_point_and_vector(ref_side.point, ref_side.xaxis) + + # calculate orientation + orientation = cls._calculate_orientation(ref_side, plane) + + # calculate start_x + point_start_x = intersection_line_plane(ref_edge, plane) + if point_start_x is None: + raise ValueError("Plane does not intersect with beam.") + start_x = distance_point_point(ref_side.point, point_start_x) + + # calculate strut_inclination + strut_inclination = cls._calculate_strut_inclination(ref_side, plane, orientation) + + # restrain step_depth & heel_depth to beam's height and the maximum possible heel depth for the beam # TODO: should it be restrained? should they be proportional to the beam's dimensions? + step_depth = beam.height if step_depth > beam.height else step_depth + max_heel_depth = beam.height / math.tan(math.radians(strut_inclination)) + heel_depth = max_heel_depth if heel_depth > max_heel_depth else heel_depth + + # define step_shape + step_shape = cls._define_step_shape(step_depth, heel_depth, tapered_heel) + + return cls( + orientation, start_x, strut_inclination, step_depth, heel_depth, step_shape, ref_side_index=ref_side_index + ) + + @staticmethod + def _calculate_orientation(ref_side, cutting_plane): + # orientation is START if cutting plane normal points towards the start of the beam and END otherwise + # essentially if the start is being cut or the end + if is_point_behind_plane(ref_side.point, cutting_plane): + return OrientationType.END + else: + return OrientationType.START + + @staticmethod + def _calculate_strut_inclination(ref_side, plane, orientation): + # vector rotation direction of the plane's normal in the vertical direction + strut_inclination_vector = Vector.cross(ref_side.zaxis, plane.normal) + strut_inclination = angle_vectors_signed(ref_side.zaxis, plane.normal, strut_inclination_vector, deg=True) + if orientation == OrientationType.START: + return 180 - abs(strut_inclination) + else: + return abs(strut_inclination) # get the other side of the angle + + @staticmethod + def _define_step_shape(step_depth, heel_depth, tapered_heel): + # step_shape based on step_depth and heel_depth and tapered_heel variables + if step_depth > 0.0 and heel_depth == 0.0: + return StepShape.STEP + elif step_depth == 0.0 and heel_depth > 0.0: + if tapered_heel: + return StepShape.TAPERED_HEEL + else: + return StepShape.HEEL + elif step_depth > 0.0 and heel_depth > 0.0: + return StepShape.DOUBLE + else: + raise ValueError("At least one of step_depth or heel_depth must be greater than 0.0.") + + @staticmethod + def _calculate_displacement_end(strut_height, strut_inclination, orientation): + # Calculates the linear displacement from the origin point to the end of the notch based on the strut_height and strut_inclination. + displacement_end = strut_height / math.sin(math.radians(strut_inclination)) + if orientation == OrientationType.END: + displacement_end = -displacement_end # negative displacement for the end cut + return displacement_end + + @staticmethod + def _calculate_displacement_heel(heel_depth, strut_inclination, orientation): + # Calculates the linear displacement from the origin point to the heel of the notch based on the heel_depth and strut_inclination. + displacement_heel = abs( + heel_depth / (math.sin(math.radians(strut_inclination)) * math.cos(math.radians(strut_inclination))) + ) + if orientation == OrientationType.END: + displacement_heel = -displacement_heel + return displacement_heel + + ######################################################################## + # Methods + ######################################################################## + + def apply(self, geometry, beam): + """Apply the feature to the beam geometry. + + Parameters + ---------- + geometry : :class:`~compas.geometry.Brep` + The beam geometry to be milled. + beam : :class:`compas_timber.elements.Beam` + The beam that is milled by this instance. + + Raises + ------ + :class:`~compas_timber.elements.FeatureApplicationError` + If the cutting planes do not create a volume that itersects with beam geometry or any step fails. + + Returns + ------- + :class:`~compas.geometry.Brep` + The resulting geometry after processing + + """ + # type: (Brep, Beam) -> Brep + # get cutting planes from params + try: + cutting_planes = self.planes_from_params_and_beam(beam) + except ValueError as e: + raise FeatureApplicationError( + None, geometry, f"Failed to generate cutting planes from parameters and beam: {str(e)}" + ) + # create notch polyedron from planes + # add ref_side plane to create a polyhedron + cutting_planes.append( + Plane.from_frame(beam.ref_sides[self.ref_side_index]) + ) # !: the beam's ref_side Plane might need to be offsetted to create a valid polyhedron when step_type is "double" + try: + notch_polyhedron = Polyhedron.from_planes(cutting_planes) + except Exception as e: + raise FeatureApplicationError( + cutting_planes, geometry, f"Failed to create valid polyhedron from cutting planes: {str(e)}" + ) + # convert polyhedron to mesh + try: + notch_mesh = notch_polyhedron.to_mesh() + except Exception as e: + raise FeatureApplicationError(notch_polyhedron, geometry, f"Failed to convert polyhedron to mesh: {str(e)}") + # convert mesh to brep + try: + notch_brep = Brep.from_mesh(notch_mesh) + except Exception as e: + raise FeatureApplicationError(notch_mesh, geometry, f"Failed to convert mesh to Brep: {str(e)}") + # apply boolean difference + try: + brep_with_notch = Brep.from_boolean_difference(geometry, notch_brep) + except Exception as e: + raise FeatureApplicationError(notch_brep, geometry, f"Boolean difference operation failed: {str(e)}") + # check if the notch is empty + if not brep_with_notch: + raise FeatureApplicationError( + notch_brep, geometry, "The cutting planes do not create a volume that intersects with beam geometry." + ) + + if self.tenon: # !: implement tenon + # create tenon volume and subtract from brep_with_notch + pass + + return brep_with_notch + + def add_tenon(self, tenon_width, tenon_height): + """Add a tenon to the existing StepJointNotch instance. + + Parameters + ---------- + tenon_width : float + The width of the tenon. tenon_width < 1000.0. + tenon_height : float + The height of the tenon. tenon_height < 1000.0. + """ + self.tenon = True + # self.tenon_width = beam.width / 4 # TODO: should this relate to the beam? typically 1/3 or 1/4 of beam.width + self.tenon_width = tenon_width + self.tenon_height = ( + self.step_depth if tenon_height < self.step_depth else tenon_height + ) # TODO: should this be constrained? + + def planes_from_params_and_beam(self, beam): + """Calculates the cutting planes from the machining parameters in this instance and the given beam + + Parameters + ---------- + beam : :class:`compas_timber.elements.Beam` + The beam that is cut by this instance. + + Returns + ------- + :class:`compas.geometry.Plane` + The cutting planes. + """ + assert self.strut_inclination is not None + assert self.step_shape is not None + + # Get the reference side as a PlanarSurface for the first cut + ref_side = beam.side_as_surface(self.ref_side_index) + # Get the opposite side as a PlanarSurface for the second cut and calculate the additional displacement along the xaxis + opp_side = beam.side_as_surface((self.ref_side_index + 2) % 4) + opp_displacement = beam.height / abs(math.tan(math.radians(self.strut_inclination))) + + rot_axis = ref_side.frame.yaxis + + if self.orientation == OrientationType.END: + opp_displacement = -opp_displacement # Negative displacement for the end cut + rot_axis = -rot_axis # Negative rotation axis for the end cut + + p_ref = ref_side.point_at(self.start_x, 0) + p_opp = opp_side.point_at(self.start_x + opp_displacement, opp_side.ysize) + cutting_plane_ref = Frame(p_ref, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_opp = Frame(p_opp, opp_side.frame.xaxis, opp_side.frame.yaxis) + if self.strut_inclination < 90: + rot_angle_ref = math.radians((180 - self.strut_inclination) / 2) + + if self.step_type == StepShape.STEP: + return self._calculate_step_planes(ref_side, rot_axis) + elif self.step_type == StepShape.HEEL: + return self._calculate_heel_planes(ref_side, rot_axis) + elif self.step_type == StepShape.TAPERED_HEEL: + return self._calculate_heel_tapered_planes(ref_side, rot_axis) + elif self.step_type == StepShape.DOUBLE: + return self._calculate_double_planes(ref_side, rot_axis) + + def _calculate_step_planes(self, ref_side, opp_side, rot_axis): + """Calculate cutting planes for a step.""" + # Move the frames to the start and end of the notch to create the cuts + p_origin = ref_side.point_at(self.start_x, 0) + p_end = opp_side.point_at(self.start_x + self.displacement_end, opp_side.ysize) + + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # Calculate step cutting planes angles + if self.strut_inclination > 90: + # Rotate first cutting plane at the start of the notch (large side of the step) + angle_long_side = math.atan( + self.step_depth + / (self.displacement_end - self.step_depth / math.tan(math.radians(self.strut_inclination / 2))) + ) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) + cutting_plane_origin.transform(rot_long_side) + + # Rotate second cutting plane at the end of the notch (short side of the step) + angle_short_side = math.radians(180 - self.strut_inclination / 2) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) + cutting_plane_end.transform(rot_short_side) + else: + # Rotate first cutting plane at the start of the notch (short side of the step) + angle_short_side = math.radians(90 + self.strut_inclination / 2) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) + cutting_plane_origin.transform(rot_short_side) + + # Rotate second cutting plane at the end of the notch (large side of the step) + angle_long_side = math.radians(180) - math.atan( + self.step_depth + / (self.displacement_end - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2))) + ) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) + cutting_plane_end.transform(rot_long_side) + + return [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_end)] + + def _calculate_heel_planes(self, ref_side, rot_axis): + """Calculate cutting planes for a heel notch.""" + if self.strut_inclination > 90: + # Move the frames to the start and end of the notch to create the cuts + p_origin = ref_side.point_at(self.start_x, self.start_y) + p_heel = ref_side.point_at(self.start_x + self.displacement_heel, self.start_y) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # Calculate heel cutting planes angles + # Rotate first cutting plane at the start of the notch (short side of the heel) + angle_short_side = math.radians(180 - self.strut_inclination) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) + cutting_plane_origin.transform(rot_short_side) + + # Rotate second cutting plane at the end of the notch (long side of the heel) + angle_long_side = math.radians(270 - self.strut_inclination) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_heel) + cutting_plane_heel.transform(rot_long_side) + else: + # Move the frames to the start and end of the notch to create the cuts + p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) + p_heel = ref_side.point_at(self.start_x + (self.displacement_end - self.displacement_heel), self.start_y) + cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # Calculate heel cutting planes angles + # Rotate first cutting plane at the displaced start of the notch (long side of the heel) + angle_long_side = math.radians(90 - self.strut_inclination) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_heel) + cutting_plane_heel.transform(rot_long_side) + + # Rotate second cutting plane at the end of the notch (short side of the heel) + angle_short_side = math.radians(180 - self.strut_inclination) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) + cutting_plane_end.transform(rot_short_side) + + return [Plane.from_frame(cutting_plane_heel), Plane.from_frame(cutting_plane_end)] + + def _calculate_heel_tapered_planes(self, ref_side, rot_axis): + """Calculate cutting planes for a tapered heel notch.""" + # Move the frames to the start and end of the notch to create the cuts + p_origin = ref_side.point_at(self.start_x, self.start_y) + p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # Calculate tapered heel cutting planes angles + if self.strut_inclination > 90: + # Rotate first cutting plane at the start of the notch (short side of the heel) + angle_short_side = math.radians(180 - self.strut_inclination) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) + cutting_plane_origin.transform(rot_short_side) + + # Rotate second cutting plane at the end of the notch (long side of the heel) + angle_long_side = math.radians(180) - math.atan( + self.heel_depth + / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination)))) + ) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) + cutting_plane_end.transform(rot_long_side) + else: + # Rotate first cutting plane at the start of the notch (long side of the heel) + angle_long_side = math.atan( + self.heel_depth + / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination)))) + ) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) + cutting_plane_origin.transform(rot_long_side) + + # Rotate second cutting plane at the end of the notch (short side of the heel) + angle_short_side = math.radians(180 - self.strut_inclination) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) + cutting_plane_end.transform(rot_short_side) + + return [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_end)] + + def _calculate_double_planes(self, ref_side, rot_axis): + """Calculate cutting planes for a double notch.""" + if self.strut_inclination > 90: + # Move the frames to the start and end of the notch to create the cuts + p_origin = ref_side.point_at(self.start_x, self.start_y) + p_heel = ref_side.point_at(self.start_x + self.displacement_heel, self.start_y) + p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_heel_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_heel_step = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # Calculate heel cutting planes angles + # Rotate first cutting plane at the start of the notch (short side of the heel) + angle_short_side_heel = math.radians(180 - self.strut_inclination) + rot_short_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_short_side_heel, point=p_origin) + cutting_plane_origin.transform(rot_short_side_heel) + + # Rotate second cutting plane at the end of the notch (long side of the heel) + angle_long_side_heel = math.radians(270 - self.strut_inclination) + rot_long_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_long_side_heel, point=p_heel) + cutting_plane_heel_heel.transform(rot_long_side_heel) + + # Calculate step cutting planes angles + # Rotate first cutting plane at the end of the heel of the notch (long side of the step) + angle_long_side_step = math.atan( + self.step_depth + / ( + self.displacement_end + - self.displacement_heel + - self.step_depth / math.tan(math.radians(self.strut_inclination / 2)) + ) + ) + rot_long_side_step = Rotation.from_axis_and_angle(rot_axis, angle_long_side_step, point=p_heel) + cutting_plane_heel_step.transform(rot_long_side_step) + + # Rotate second cutting plane at the end of the notch (short side of the step) + angle_short_side_step = math.radians(180 - self.strut_inclination / 2) + rot_short_side_step = Rotation.from_axis_and_angle(rot_axis, angle_short_side_step, point=p_end) + cutting_plane_end.transform(rot_short_side_step) + else: + # Move the frames to the start and end of the notch to create the cuts + p_origin = ref_side.point_at(self.start_x, self.start_y) + p_heel = ref_side.point_at(self.start_x + (self.displacement_end - self.displacement_heel), self.start_y) + p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_heel_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_heel_step = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # Calculate step cutting planes angles + # Rotate first cutting plane at the start of the notch (short side of the step) + angle_short_side_step = math.radians(90 + self.strut_inclination / 2) + rot_short_side_step = Rotation.from_axis_and_angle(rot_axis, angle_short_side_step, point=p_origin) + cutting_plane_origin.transform(rot_short_side_step) + + # Rotate second cutting plane at the end of the notch (large side of the step) + angle_long_side_step = math.radians(180) - math.atan( + self.step_depth + / ( + self.displacement_end + - self.displacement_heel + - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2)) + ) + ) + rot_long_side_step = Rotation.from_axis_and_angle(rot_axis, angle_long_side_step, point=p_heel) + cutting_plane_heel_step.transform(rot_long_side_step) + + # Calculate heel cutting planes angles + # Rotate first cutting plane at the displaced start of the notch (long side of the heel) + angle_long_side_heel = math.radians(90 - self.strut_inclination) + rot_long_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_long_side_heel, point=p_heel) + cutting_plane_heel_heel.transform(rot_long_side_heel) + + # Rotate second cutting plane at the end of the notch (short side of the heel) + angle_short_side_heel = math.radians(180 - self.strut_inclination) + rot_short_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_short_side_heel, point=p_end) + cutting_plane_end.transform(rot_short_side_heel) + + return [ + Plane.from_frame(cutting_plane_origin), + Plane.from_frame(cutting_plane_heel_heel), + Plane.from_frame(cutting_plane_heel_step), + Plane.from_frame(cutting_plane_end), + ] + + def tenon_volume_from_params_and_beam(self, beam): + """Calculates the tenon volume from the machining parameters in this instance and the given beam + + Parameters + ---------- + beam : :class:`compas_timber.elements.Beam` + The beam that is cut by this instance. + + Returns + ------- + :class:`compas.geometry.Polyhedron` + The tenon volume. + + """ + # type: (Beam) -> Mesh + + assert self.strut_inclination is not None + assert self.step_shape is not None + assert self.tenon == True + assert self.tenon_width is not None + assert self.tenon_height is not None + + # start with a plane aligned with the ref side but shifted to the start of the first cut + ref_side = beam.side_as_surface(self.ref_side_index) + rot_axis = ref_side.frame.yaxis + + start_x = self.start_x + displacement_x = self.strut_height / math.sin(math.radians(self.strut_inclination)) + start_y = self.start_y + (self.notch_width - self.tenon_width) / 2 + displacement_y = self.tenon_width + + step_cutting_planes = self._calculate_step_planes(ref_side, rot_axis) + step_cutting_plane = step_cutting_planes[1] # the second cutting plane is the one at the end of the step + + if self.orientation == OrientationType.END: + displacement_x = -displacement_x # negative displacement for the end cut + rot_axis = -rot_axis # negative rotation axis for the end cut + step_cutting_plane = step_cutting_planes[0] # the first cutting plane is the one at the start of the step + + # find the points that create the top face of the tenon + p_1 = ref_side.point_at(start_x, start_y) + p_2 = ref_side.point_at(start_x + displacement_x, start_y) + p_3 = ref_side.point_at(start_x + displacement_x, start_y + displacement_y) + p_4 = ref_side.point_at(start_x, start_y + displacement_y) + + # construct polyline for the top face of the tenon + tenon_polyline = Polyline([p_1, p_2, p_3, p_4, p_1]) + # calcutate the plane for the extrusion of the polyline + extr_plane = Plane(p_1, ref_side.frame.xaxis) + extr_vector_length = self.tenon_height / math.sin(math.radians(self.strut_inclination)) + extr_vector = extr_plane.normal * extr_vector_length + if self.strut_inclination > 90: + vector_angle = math.radians(180 - self.strut_inclination) + else: + vector_angle = math.radians(self.strut_inclination) + rot_vect = Rotation.from_axis_and_angle(rot_axis, vector_angle) + extr_vector.transform(rot_vect) + # extrude the polyline to create the tenon volume as a Brep + tenon_volume = Brep.from_extrusion(tenon_polyline, extr_vector, cap_ends=True) + # trim brep with step cutting planes + tenon_volume.trim(step_cutting_plane) # !: check if the trimming works correctly // add checks + + return tenon_volume + + +class StepJointParams(BTLxProcessParams): + """A class to store the parameters of a Step Joint feature. + + Parameters + ---------- + instance : :class:`~compas_timber._fabrication.StepJoint` + The instance of the Step Joint feature. + """ + + def __init__(self, instance): + # type: (StepJoint) -> None + super(StepJointParams, self).__init__(instance) + + def as_dict(self): + """Returns the parameters of the Step Joint feature as a dictionary. + + Returns + ------- + dict + The parameters of the Step Joint as a dictionary. + """ + # type: () -> OrderedDict + result = super(StepJointParams, self).as_dict() + result["Orientation"] = self._instance.orientation + result["StartX"] = "{:.{prec}f}".format(self._instance.start_x, prec=TOL.precision) + result["StrutInclination"] = "{:.{prec}f}".format(self._instance.strut_inclination, prec=TOL.precision) + result["StepDepth"] = "{:.{prec}f}".format(self._instance.step_depth, prec=TOL.precision) + result["HeelDepth"] = "{:.{prec}f}".format(self._instance.heel_depth, prec=TOL.precision) + result["StepShape"] = self._instance.step_shape + result["tenon"] = "yes" if self._instance.tenon else "no" + result["tenonWidth"] = "{:.{prec}f}".format(self._instance.tenon_width, prec=TOL.precision) + result["tenonHeight"] = "{:.{prec}f}".format(self._instance.tenon_height, prec=TOL.precision) + return result From 9f73d9e2bb453248f003fad7936feb950f8b6ef4 Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 6 Aug 2024 15:16:00 +0200 Subject: [PATCH 15/63] de-linting --- .../_fabrication/step_joint_notch.py | 21 ++++++++----------- tests/compas_timber/test_btlx.py | 1 - 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index db9405fb9..c647d203b 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -1,13 +1,13 @@ import math +from compas.geometry import Brep from compas.geometry import Frame from compas.geometry import Line from compas.geometry import Plane -from compas.geometry import Rotation -from compas.geometry import Vector from compas.geometry import Polyhedron -from compas.geometry import Brep from compas.geometry import Polyline +from compas.geometry import Rotation +from compas.geometry import Vector from compas.geometry import angle_vectors_signed from compas.geometry import distance_point_point from compas.geometry import intersection_line_plane @@ -224,7 +224,7 @@ def strut_height(self, strut_height): def step_shape(self): return self._step_shape - @step_shape.setter # TODO: should this be defined automatically depending on the other parameters? (ie. if heel_depth > 0 and step_depth > 0, then step_shape = "double") + @step_shape.setter def step_shape(self, step_shape): if step_shape not in [StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL, StepShape.TAPERED_HEEL]: raise ValueError( @@ -304,7 +304,7 @@ def from_surface_and_beam( """ # type: (PlanarSurface|Surface, Beam, bool, float, float, float, bool, int) -> StepJointNotch # TODO: the stepjointnotch is always orthogonal, this means that the surface should be perpendicular to the beam's ref_side | should there be a check for that? - # TODO: I am using a PlanarSurface instead of a Plane because otherwise there is no way to define the start_y of the Notch. This makes a case for the default ref_side to be a PlanarSurface + # TODO: I am using a PlanarSurface instead of a Plane because otherwise there is no way to define the start_y of the Notch. # TODO: The alternative solution in order to use a Plane instead would be to have start_y and notch_width as parameters of the class # define ref_side & ref_edge ref_side = beam.ref_sides[ref_side_index] # TODO: is this arbitrary? @@ -454,9 +454,8 @@ def apply(self, geometry, beam): ) # create notch polyedron from planes # add ref_side plane to create a polyhedron - cutting_planes.append( - Plane.from_frame(beam.ref_sides[self.ref_side_index]) - ) # !: the beam's ref_side Plane might need to be offsetted to create a valid polyhedron when step_type is "double" + # !: the beam's ref_side Plane might need to be offsetted to create a valid polyhedron when step_type is "double" + cutting_planes.append(Plane.from_frame(beam.ref_sides[self.ref_side_index])) try: notch_polyhedron = Polyhedron.from_planes(cutting_planes) except Exception as e: @@ -503,9 +502,7 @@ def add_mortise(self, mortise_width, mortise_height, beam): self.mortise = True # self.mortise_width = beam.width / 4 # TODO: should this relate to the beam? typically 1/3 or 1/4 of beam.width self.mortise_width = mortise_width - self.mortise_height = ( - beam.height if mortise_height > beam.height else mortise_height - ) # TODO: should this be constrained? + self.mortise_height = beam.height if mortise_height > beam.height else mortise_height def planes_from_params_and_beam(self, beam): """Calculates the cutting planes from the machining parameters in this instance and the given beam @@ -761,7 +758,7 @@ def mortise_volume_from_params_and_beam(self, beam): assert self.step_shape is not None assert self.strut_height is not None assert self.notch_width is not None - assert self.mortise == True + assert self.mortise assert self.mortise_width is not None assert self.mortise_height is not None diff --git a/tests/compas_timber/test_btlx.py b/tests/compas_timber/test_btlx.py index 011a1d9ed..8e899ed0d 100644 --- a/tests/compas_timber/test_btlx.py +++ b/tests/compas_timber/test_btlx.py @@ -80,7 +80,6 @@ def test_beam_ref_faces_attribute(mock_beam): def test_beam_ref_edges(mock_beam): - ref_edges_expected = ( Line( Point(x=-48.67193560518159, y=20.35704602012424, z=0.0005429194857271558), From aff475eeb8d99530a31b5995a7c36f5f501bb6fe Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 6 Aug 2024 15:24:02 +0200 Subject: [PATCH 16/63] remove step_joint from branch for clarity --- src/compas_timber/_fabrication/step_joint.py | 746 ------------------- 1 file changed, 746 deletions(-) delete mode 100644 src/compas_timber/_fabrication/step_joint.py diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py deleted file mode 100644 index 112a72112..000000000 --- a/src/compas_timber/_fabrication/step_joint.py +++ /dev/null @@ -1,746 +0,0 @@ -import math - -from compas.geometry import Frame -from compas.geometry import Line -from compas.geometry import Plane -from compas.geometry import Rotation -from compas.geometry import Vector -from compas.geometry import Polyhedron -from compas.geometry import Brep -from compas.geometry import Polyline -from compas.geometry import angle_vectors_signed -from compas.geometry import distance_point_point -from compas.geometry import intersection_line_plane -from compas.geometry import is_point_behind_plane -from compas.tolerance import TOL - -from compas_timber.elements import FeatureApplicationError - -from .btlx_process import BTLxProcess -from .btlx_process import BTLxProcessParams -from .btlx_process import OrientationType -from .btlx_process import StepShape - - -class StepJoint(BTLxProcess): - """Represents a Step Joint feature to be made on a beam. - - Parameters - ---------- - orientation : int - The orientation of the cut. Must be either OrientationType.START or OrientationType.END. - start_x : float - The start x-coordinate of the cut in parametric space of the reference side. -100000.0 < start_x < 100000.0. - strut_inclination : float - The inclination of the strut. 0.1 < strut_inclination < 179.9. - step_depth : float - The depth of the step. step_depth < 50000.0. - heel_depth : float - The depth of the heel. heel_depth < 50000.0. - step_shape : str - The shape of the step. Must be either 'double', 'step', 'heel', or 'taperedheel'. - tenon : str - The presence of a tenon. Must be either 'no' or 'yes'. - tenon_width : float - The width of the tenon. tenon_width < 1000.0. - tenon_height : float - The height of the tenon. tenon_height < 1000.0. - - """ - - PROCESS_NAME = "StepJoint" # type: ignore - - @property - def __data__(self): - data = super(StepJoint, self).__data__ - data["orientation"] = self.orientation - data["start_x"] = self.start_x - data["strut_inclination"] = self.strut_inclination - data["step_depth"] = self.step_depth - data["heel_depth"] = self.heel_depth - data["step_shape"] = self.step_shape - data["tenon"] = self.tenon - data["tenon_width"] = self.tenon_width - data["tenon_height"] = self.tenon_height - return data - - def __init__( - self, - orientation, - start_x=0.0, - strut_inclination=90.0, - step_depth=20.0, - heel_depth=20.0, - step_shape=StepShape.DOUBLE, - tenon=False, - tenon_width=40.0, - tenon_height=40.0, - **kwargs, - ): - super(StepJoint, self).__init__(**kwargs) - self._orientation = None - self._start_x = None - self._strut_inclination = None - self._step_depth = None - self._heel_depth = None - self._step_shape = None - self._tenon = None - self._tenon_width = None - self._tenon_height = None - - self.orientation = orientation - self.start_x = start_x - self.strut_inclination = strut_inclination - self.step_depth = step_depth - self.heel_depth = heel_depth - self.step_shape = step_shape - self.tenon = tenon - self.tenon_width = tenon_width - self.tenon_height = tenon_height - - ######################################################################## - # Properties - ######################################################################## - - @property - def params_dict(self): - return StepJointNotchParams(self).as_dict() - - @property - def orientation(self): - return self._orientation - - @orientation.setter - def orientation(self, orientation): - if orientation not in [OrientationType.START, OrientationType.END]: - raise ValueError("Orientation must be either OrientationType.START or OrientationType.END.") - self._orientation = orientation - - @property - def start_x(self): - return self._start_x - - @start_x.setter - def start_x(self, start_x): - if start_x > 100000.0 or start_x < -100000.0: - raise ValueError("StartX must be between -100000.0 and 100000.") - self._start_x = start_x - - @property - def strut_inclination(self): - return self._strut_inclination - - @strut_inclination.setter - def strut_inclination(self, strut_inclination): - if strut_inclination < 0.1 or strut_inclination > 179.9: - raise ValueError("StrutInclination must be between 0.1 and 179.9.") - self._strut_inclination = strut_inclination - - @property - def step_depth(self): - return self._step_depth - - @step_depth.setter - def step_depth(self, step_depth): - if step_depth > 50000.0: - raise ValueError("StepDepth must be less than 50000.0.") - self._step_depth = step_depth - - @property - def heel_depth(self): - return self._heel_depth - - @heel_depth.setter - def heel_depth(self, heel_depth): - if heel_depth > 50000.0: - raise ValueError("HeelDepth must be less than 50000.0.") - self._heel_depth = heel_depth - - @property - def step_shape(self): - return self._step_shape - - @step_shape.setter # TODO: should this be defined automatically depending on the other parameters? (ie. if heel_depth > 0 and step_depth > 0, then step_shape = "double") - def step_shape(self, step_shape): - if step_shape not in [StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL, StepShape.TAPEREDHEEL]: - raise ValueError( - "StepShape must be either StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL, or StepShape.TAPEREDHEEL." - ) - self._step_shape = step_shape - - @property - def tenon(self): - return self._tenon - - @tenon.setter - def tenon(self, tenon): - if not isinstance(tenon, bool): - raise ValueError("tenon must be either True or False.") - self._tenon = tenon - - @property - def tenon_width(self): - return self._tenon_width - - @tenon_width.setter - def tenon_width(self, tenon_width): - if tenon_width > 1000.0: - raise ValueError("tenonWidth must be less than 1000.0.") - self._tenon_width = tenon_width - - @property - def tenon_height(self): - return self._tenon_height - - @tenon_height.setter - def tenon_height(self, tenon_height): - if tenon_height > 1000.0: - raise ValueError("tenonHeight must be less than 1000.0.") - self._tenon_height = tenon_height - - @property # TODO: how should these be better implemented? - def displacement_end(self): - return self._calculate_displacement_end(self.strut_height, self.strut_inclination, self.orientation) - - @property # TODO: how should these be better implemented? - def displacement_heel(self): - return self._calculate_displacement_heel(self.heel_depth, self.strut_inclination, self.orientation) - - ######################################################################## - # Alternative constructors - ######################################################################## - - @classmethod - def from_plane_and_beam(cls, plane, beam, step_depth=20.0, heel_depth=0.0, tapered_heel=False, ref_side_index=0): - """Create a StepJoint instance from a cutting surface and the beam it should cut. This could be the ref_side of the cross beam of a Joint and the main beam. - - Parameters - ---------- - plane : :class:`~compas.geometry.Plane` or :class:`~compas.geometry.Frame` - The cutting plane. - beam : :class:`~compas_timber.elements.Beam` - The beam that is cut by this instance. - ref_side_index : int, optional - The reference side index of the beam to be cut. Default is 0 (i.e. RS1). - - Returns - ------- - :class:`~compas_timber.fabrication.StepJoint` - - """ - # type: (Plane|Frame, Beam, float, float, bool, int) -> StepJoint - # TODO: the stepjointnotch is always orthogonal, this means that the surface should be perpendicular to the beam's ref_side | should there be a check for that? - if isinstance(plane, Frame): - plane = Plane.from_frame(plane) - # define ref_side & ref_edge - ref_side = beam.ref_sides[ref_side_index] # TODO: is this arbitrary? - ref_edge = Line.from_point_and_vector(ref_side.point, ref_side.xaxis) - - # calculate orientation - orientation = cls._calculate_orientation(ref_side, plane) - - # calculate start_x - point_start_x = intersection_line_plane(ref_edge, plane) - if point_start_x is None: - raise ValueError("Plane does not intersect with beam.") - start_x = distance_point_point(ref_side.point, point_start_x) - - # calculate strut_inclination - strut_inclination = cls._calculate_strut_inclination(ref_side, plane, orientation) - - # restrain step_depth & heel_depth to beam's height and the maximum possible heel depth for the beam # TODO: should it be restrained? should they be proportional to the beam's dimensions? - step_depth = beam.height if step_depth > beam.height else step_depth - max_heel_depth = beam.height / math.tan(math.radians(strut_inclination)) - heel_depth = max_heel_depth if heel_depth > max_heel_depth else heel_depth - - # define step_shape - step_shape = cls._define_step_shape(step_depth, heel_depth, tapered_heel) - - return cls( - orientation, start_x, strut_inclination, step_depth, heel_depth, step_shape, ref_side_index=ref_side_index - ) - - @staticmethod - def _calculate_orientation(ref_side, cutting_plane): - # orientation is START if cutting plane normal points towards the start of the beam and END otherwise - # essentially if the start is being cut or the end - if is_point_behind_plane(ref_side.point, cutting_plane): - return OrientationType.END - else: - return OrientationType.START - - @staticmethod - def _calculate_strut_inclination(ref_side, plane, orientation): - # vector rotation direction of the plane's normal in the vertical direction - strut_inclination_vector = Vector.cross(ref_side.zaxis, plane.normal) - strut_inclination = angle_vectors_signed(ref_side.zaxis, plane.normal, strut_inclination_vector, deg=True) - if orientation == OrientationType.START: - return 180 - abs(strut_inclination) - else: - return abs(strut_inclination) # get the other side of the angle - - @staticmethod - def _define_step_shape(step_depth, heel_depth, tapered_heel): - # step_shape based on step_depth and heel_depth and tapered_heel variables - if step_depth > 0.0 and heel_depth == 0.0: - return StepShape.STEP - elif step_depth == 0.0 and heel_depth > 0.0: - if tapered_heel: - return StepShape.TAPERED_HEEL - else: - return StepShape.HEEL - elif step_depth > 0.0 and heel_depth > 0.0: - return StepShape.DOUBLE - else: - raise ValueError("At least one of step_depth or heel_depth must be greater than 0.0.") - - @staticmethod - def _calculate_displacement_end(strut_height, strut_inclination, orientation): - # Calculates the linear displacement from the origin point to the end of the notch based on the strut_height and strut_inclination. - displacement_end = strut_height / math.sin(math.radians(strut_inclination)) - if orientation == OrientationType.END: - displacement_end = -displacement_end # negative displacement for the end cut - return displacement_end - - @staticmethod - def _calculate_displacement_heel(heel_depth, strut_inclination, orientation): - # Calculates the linear displacement from the origin point to the heel of the notch based on the heel_depth and strut_inclination. - displacement_heel = abs( - heel_depth / (math.sin(math.radians(strut_inclination)) * math.cos(math.radians(strut_inclination))) - ) - if orientation == OrientationType.END: - displacement_heel = -displacement_heel - return displacement_heel - - ######################################################################## - # Methods - ######################################################################## - - def apply(self, geometry, beam): - """Apply the feature to the beam geometry. - - Parameters - ---------- - geometry : :class:`~compas.geometry.Brep` - The beam geometry to be milled. - beam : :class:`compas_timber.elements.Beam` - The beam that is milled by this instance. - - Raises - ------ - :class:`~compas_timber.elements.FeatureApplicationError` - If the cutting planes do not create a volume that itersects with beam geometry or any step fails. - - Returns - ------- - :class:`~compas.geometry.Brep` - The resulting geometry after processing - - """ - # type: (Brep, Beam) -> Brep - # get cutting planes from params - try: - cutting_planes = self.planes_from_params_and_beam(beam) - except ValueError as e: - raise FeatureApplicationError( - None, geometry, f"Failed to generate cutting planes from parameters and beam: {str(e)}" - ) - # create notch polyedron from planes - # add ref_side plane to create a polyhedron - cutting_planes.append( - Plane.from_frame(beam.ref_sides[self.ref_side_index]) - ) # !: the beam's ref_side Plane might need to be offsetted to create a valid polyhedron when step_type is "double" - try: - notch_polyhedron = Polyhedron.from_planes(cutting_planes) - except Exception as e: - raise FeatureApplicationError( - cutting_planes, geometry, f"Failed to create valid polyhedron from cutting planes: {str(e)}" - ) - # convert polyhedron to mesh - try: - notch_mesh = notch_polyhedron.to_mesh() - except Exception as e: - raise FeatureApplicationError(notch_polyhedron, geometry, f"Failed to convert polyhedron to mesh: {str(e)}") - # convert mesh to brep - try: - notch_brep = Brep.from_mesh(notch_mesh) - except Exception as e: - raise FeatureApplicationError(notch_mesh, geometry, f"Failed to convert mesh to Brep: {str(e)}") - # apply boolean difference - try: - brep_with_notch = Brep.from_boolean_difference(geometry, notch_brep) - except Exception as e: - raise FeatureApplicationError(notch_brep, geometry, f"Boolean difference operation failed: {str(e)}") - # check if the notch is empty - if not brep_with_notch: - raise FeatureApplicationError( - notch_brep, geometry, "The cutting planes do not create a volume that intersects with beam geometry." - ) - - if self.tenon: # !: implement tenon - # create tenon volume and subtract from brep_with_notch - pass - - return brep_with_notch - - def add_tenon(self, tenon_width, tenon_height): - """Add a tenon to the existing StepJointNotch instance. - - Parameters - ---------- - tenon_width : float - The width of the tenon. tenon_width < 1000.0. - tenon_height : float - The height of the tenon. tenon_height < 1000.0. - """ - self.tenon = True - # self.tenon_width = beam.width / 4 # TODO: should this relate to the beam? typically 1/3 or 1/4 of beam.width - self.tenon_width = tenon_width - self.tenon_height = ( - self.step_depth if tenon_height < self.step_depth else tenon_height - ) # TODO: should this be constrained? - - def planes_from_params_and_beam(self, beam): - """Calculates the cutting planes from the machining parameters in this instance and the given beam - - Parameters - ---------- - beam : :class:`compas_timber.elements.Beam` - The beam that is cut by this instance. - - Returns - ------- - :class:`compas.geometry.Plane` - The cutting planes. - """ - assert self.strut_inclination is not None - assert self.step_shape is not None - - # Get the reference side as a PlanarSurface for the first cut - ref_side = beam.side_as_surface(self.ref_side_index) - # Get the opposite side as a PlanarSurface for the second cut and calculate the additional displacement along the xaxis - opp_side = beam.side_as_surface((self.ref_side_index + 2) % 4) - opp_displacement = beam.height / abs(math.tan(math.radians(self.strut_inclination))) - - rot_axis = ref_side.frame.yaxis - - if self.orientation == OrientationType.END: - opp_displacement = -opp_displacement # Negative displacement for the end cut - rot_axis = -rot_axis # Negative rotation axis for the end cut - - p_ref = ref_side.point_at(self.start_x, 0) - p_opp = opp_side.point_at(self.start_x + opp_displacement, opp_side.ysize) - cutting_plane_ref = Frame(p_ref, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_opp = Frame(p_opp, opp_side.frame.xaxis, opp_side.frame.yaxis) - if self.strut_inclination < 90: - rot_angle_ref = math.radians((180 - self.strut_inclination) / 2) - - if self.step_type == StepShape.STEP: - return self._calculate_step_planes(ref_side, rot_axis) - elif self.step_type == StepShape.HEEL: - return self._calculate_heel_planes(ref_side, rot_axis) - elif self.step_type == StepShape.TAPERED_HEEL: - return self._calculate_heel_tapered_planes(ref_side, rot_axis) - elif self.step_type == StepShape.DOUBLE: - return self._calculate_double_planes(ref_side, rot_axis) - - def _calculate_step_planes(self, ref_side, opp_side, rot_axis): - """Calculate cutting planes for a step.""" - # Move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x, 0) - p_end = opp_side.point_at(self.start_x + self.displacement_end, opp_side.ysize) - - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # Calculate step cutting planes angles - if self.strut_inclination > 90: - # Rotate first cutting plane at the start of the notch (large side of the step) - angle_long_side = math.atan( - self.step_depth - / (self.displacement_end - self.step_depth / math.tan(math.radians(self.strut_inclination / 2))) - ) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) - cutting_plane_origin.transform(rot_long_side) - - # Rotate second cutting plane at the end of the notch (short side of the step) - angle_short_side = math.radians(180 - self.strut_inclination / 2) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) - cutting_plane_end.transform(rot_short_side) - else: - # Rotate first cutting plane at the start of the notch (short side of the step) - angle_short_side = math.radians(90 + self.strut_inclination / 2) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) - cutting_plane_origin.transform(rot_short_side) - - # Rotate second cutting plane at the end of the notch (large side of the step) - angle_long_side = math.radians(180) - math.atan( - self.step_depth - / (self.displacement_end - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2))) - ) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) - cutting_plane_end.transform(rot_long_side) - - return [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_end)] - - def _calculate_heel_planes(self, ref_side, rot_axis): - """Calculate cutting planes for a heel notch.""" - if self.strut_inclination > 90: - # Move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x, self.start_y) - p_heel = ref_side.point_at(self.start_x + self.displacement_heel, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # Calculate heel cutting planes angles - # Rotate first cutting plane at the start of the notch (short side of the heel) - angle_short_side = math.radians(180 - self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) - cutting_plane_origin.transform(rot_short_side) - - # Rotate second cutting plane at the end of the notch (long side of the heel) - angle_long_side = math.radians(270 - self.strut_inclination) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_heel) - cutting_plane_heel.transform(rot_long_side) - else: - # Move the frames to the start and end of the notch to create the cuts - p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) - p_heel = ref_side.point_at(self.start_x + (self.displacement_end - self.displacement_heel), self.start_y) - cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # Calculate heel cutting planes angles - # Rotate first cutting plane at the displaced start of the notch (long side of the heel) - angle_long_side = math.radians(90 - self.strut_inclination) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_heel) - cutting_plane_heel.transform(rot_long_side) - - # Rotate second cutting plane at the end of the notch (short side of the heel) - angle_short_side = math.radians(180 - self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) - cutting_plane_end.transform(rot_short_side) - - return [Plane.from_frame(cutting_plane_heel), Plane.from_frame(cutting_plane_end)] - - def _calculate_heel_tapered_planes(self, ref_side, rot_axis): - """Calculate cutting planes for a tapered heel notch.""" - # Move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x, self.start_y) - p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # Calculate tapered heel cutting planes angles - if self.strut_inclination > 90: - # Rotate first cutting plane at the start of the notch (short side of the heel) - angle_short_side = math.radians(180 - self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) - cutting_plane_origin.transform(rot_short_side) - - # Rotate second cutting plane at the end of the notch (long side of the heel) - angle_long_side = math.radians(180) - math.atan( - self.heel_depth - / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination)))) - ) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) - cutting_plane_end.transform(rot_long_side) - else: - # Rotate first cutting plane at the start of the notch (long side of the heel) - angle_long_side = math.atan( - self.heel_depth - / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination)))) - ) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) - cutting_plane_origin.transform(rot_long_side) - - # Rotate second cutting plane at the end of the notch (short side of the heel) - angle_short_side = math.radians(180 - self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) - cutting_plane_end.transform(rot_short_side) - - return [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_end)] - - def _calculate_double_planes(self, ref_side, rot_axis): - """Calculate cutting planes for a double notch.""" - if self.strut_inclination > 90: - # Move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x, self.start_y) - p_heel = ref_side.point_at(self.start_x + self.displacement_heel, self.start_y) - p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel_step = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # Calculate heel cutting planes angles - # Rotate first cutting plane at the start of the notch (short side of the heel) - angle_short_side_heel = math.radians(180 - self.strut_inclination) - rot_short_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_short_side_heel, point=p_origin) - cutting_plane_origin.transform(rot_short_side_heel) - - # Rotate second cutting plane at the end of the notch (long side of the heel) - angle_long_side_heel = math.radians(270 - self.strut_inclination) - rot_long_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_long_side_heel, point=p_heel) - cutting_plane_heel_heel.transform(rot_long_side_heel) - - # Calculate step cutting planes angles - # Rotate first cutting plane at the end of the heel of the notch (long side of the step) - angle_long_side_step = math.atan( - self.step_depth - / ( - self.displacement_end - - self.displacement_heel - - self.step_depth / math.tan(math.radians(self.strut_inclination / 2)) - ) - ) - rot_long_side_step = Rotation.from_axis_and_angle(rot_axis, angle_long_side_step, point=p_heel) - cutting_plane_heel_step.transform(rot_long_side_step) - - # Rotate second cutting plane at the end of the notch (short side of the step) - angle_short_side_step = math.radians(180 - self.strut_inclination / 2) - rot_short_side_step = Rotation.from_axis_and_angle(rot_axis, angle_short_side_step, point=p_end) - cutting_plane_end.transform(rot_short_side_step) - else: - # Move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x, self.start_y) - p_heel = ref_side.point_at(self.start_x + (self.displacement_end - self.displacement_heel), self.start_y) - p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel_step = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # Calculate step cutting planes angles - # Rotate first cutting plane at the start of the notch (short side of the step) - angle_short_side_step = math.radians(90 + self.strut_inclination / 2) - rot_short_side_step = Rotation.from_axis_and_angle(rot_axis, angle_short_side_step, point=p_origin) - cutting_plane_origin.transform(rot_short_side_step) - - # Rotate second cutting plane at the end of the notch (large side of the step) - angle_long_side_step = math.radians(180) - math.atan( - self.step_depth - / ( - self.displacement_end - - self.displacement_heel - - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2)) - ) - ) - rot_long_side_step = Rotation.from_axis_and_angle(rot_axis, angle_long_side_step, point=p_heel) - cutting_plane_heel_step.transform(rot_long_side_step) - - # Calculate heel cutting planes angles - # Rotate first cutting plane at the displaced start of the notch (long side of the heel) - angle_long_side_heel = math.radians(90 - self.strut_inclination) - rot_long_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_long_side_heel, point=p_heel) - cutting_plane_heel_heel.transform(rot_long_side_heel) - - # Rotate second cutting plane at the end of the notch (short side of the heel) - angle_short_side_heel = math.radians(180 - self.strut_inclination) - rot_short_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_short_side_heel, point=p_end) - cutting_plane_end.transform(rot_short_side_heel) - - return [ - Plane.from_frame(cutting_plane_origin), - Plane.from_frame(cutting_plane_heel_heel), - Plane.from_frame(cutting_plane_heel_step), - Plane.from_frame(cutting_plane_end), - ] - - def tenon_volume_from_params_and_beam(self, beam): - """Calculates the tenon volume from the machining parameters in this instance and the given beam - - Parameters - ---------- - beam : :class:`compas_timber.elements.Beam` - The beam that is cut by this instance. - - Returns - ------- - :class:`compas.geometry.Polyhedron` - The tenon volume. - - """ - # type: (Beam) -> Mesh - - assert self.strut_inclination is not None - assert self.step_shape is not None - assert self.tenon == True - assert self.tenon_width is not None - assert self.tenon_height is not None - - # start with a plane aligned with the ref side but shifted to the start of the first cut - ref_side = beam.side_as_surface(self.ref_side_index) - rot_axis = ref_side.frame.yaxis - - start_x = self.start_x - displacement_x = self.strut_height / math.sin(math.radians(self.strut_inclination)) - start_y = self.start_y + (self.notch_width - self.tenon_width) / 2 - displacement_y = self.tenon_width - - step_cutting_planes = self._calculate_step_planes(ref_side, rot_axis) - step_cutting_plane = step_cutting_planes[1] # the second cutting plane is the one at the end of the step - - if self.orientation == OrientationType.END: - displacement_x = -displacement_x # negative displacement for the end cut - rot_axis = -rot_axis # negative rotation axis for the end cut - step_cutting_plane = step_cutting_planes[0] # the first cutting plane is the one at the start of the step - - # find the points that create the top face of the tenon - p_1 = ref_side.point_at(start_x, start_y) - p_2 = ref_side.point_at(start_x + displacement_x, start_y) - p_3 = ref_side.point_at(start_x + displacement_x, start_y + displacement_y) - p_4 = ref_side.point_at(start_x, start_y + displacement_y) - - # construct polyline for the top face of the tenon - tenon_polyline = Polyline([p_1, p_2, p_3, p_4, p_1]) - # calcutate the plane for the extrusion of the polyline - extr_plane = Plane(p_1, ref_side.frame.xaxis) - extr_vector_length = self.tenon_height / math.sin(math.radians(self.strut_inclination)) - extr_vector = extr_plane.normal * extr_vector_length - if self.strut_inclination > 90: - vector_angle = math.radians(180 - self.strut_inclination) - else: - vector_angle = math.radians(self.strut_inclination) - rot_vect = Rotation.from_axis_and_angle(rot_axis, vector_angle) - extr_vector.transform(rot_vect) - # extrude the polyline to create the tenon volume as a Brep - tenon_volume = Brep.from_extrusion(tenon_polyline, extr_vector, cap_ends=True) - # trim brep with step cutting planes - tenon_volume.trim(step_cutting_plane) # !: check if the trimming works correctly // add checks - - return tenon_volume - - -class StepJointParams(BTLxProcessParams): - """A class to store the parameters of a Step Joint feature. - - Parameters - ---------- - instance : :class:`~compas_timber._fabrication.StepJoint` - The instance of the Step Joint feature. - """ - - def __init__(self, instance): - # type: (StepJoint) -> None - super(StepJointParams, self).__init__(instance) - - def as_dict(self): - """Returns the parameters of the Step Joint feature as a dictionary. - - Returns - ------- - dict - The parameters of the Step Joint as a dictionary. - """ - # type: () -> OrderedDict - result = super(StepJointParams, self).as_dict() - result["Orientation"] = self._instance.orientation - result["StartX"] = "{:.{prec}f}".format(self._instance.start_x, prec=TOL.precision) - result["StrutInclination"] = "{:.{prec}f}".format(self._instance.strut_inclination, prec=TOL.precision) - result["StepDepth"] = "{:.{prec}f}".format(self._instance.step_depth, prec=TOL.precision) - result["HeelDepth"] = "{:.{prec}f}".format(self._instance.heel_depth, prec=TOL.precision) - result["StepShape"] = self._instance.step_shape - result["tenon"] = "yes" if self._instance.tenon else "no" - result["tenonWidth"] = "{:.{prec}f}".format(self._instance.tenon_width, prec=TOL.precision) - result["tenonHeight"] = "{:.{prec}f}".format(self._instance.tenon_height, prec=TOL.precision) - return result From 1b3acb45d4390f9ff335c7e002654571427aff0f Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 6 Aug 2024 18:58:34 +0200 Subject: [PATCH 17/63] remove f-strings for ironpython import --- src/compas_timber/_fabrication/step_joint_notch.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index c647d203b..ba74dfc79 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -91,7 +91,7 @@ def __init__( mortise=False, mortise_width=40.0, mortise_height=40.0, - **kwargs, + **kwargs ): super(StepJointNotch, self).__init__(**kwargs) self._orientation = None @@ -450,7 +450,7 @@ def apply(self, geometry, beam): cutting_planes = self.planes_from_params_and_beam(beam) except ValueError as e: raise FeatureApplicationError( - None, geometry, f"Failed to generate cutting planes from parameters and beam: {str(e)}" + None, geometry, "Failed to generate cutting planes from parameters and beam: {}".format(str(e)) ) # create notch polyedron from planes # add ref_side plane to create a polyhedron @@ -460,23 +460,23 @@ def apply(self, geometry, beam): notch_polyhedron = Polyhedron.from_planes(cutting_planes) except Exception as e: raise FeatureApplicationError( - cutting_planes, geometry, f"Failed to create valid polyhedron from cutting planes: {str(e)}" + cutting_planes, geometry, "Failed to create valid polyhedron from cutting planes: {}".format(str(e)) ) # convert polyhedron to mesh try: notch_mesh = notch_polyhedron.to_mesh() except Exception as e: - raise FeatureApplicationError(notch_polyhedron, geometry, f"Failed to convert polyhedron to mesh: {str(e)}") + raise FeatureApplicationError(notch_polyhedron, geometry, "Failed to convert polyhedron to mesh: {}".format(str(e))) # convert mesh to brep try: notch_brep = Brep.from_mesh(notch_mesh) except Exception as e: - raise FeatureApplicationError(notch_mesh, geometry, f"Failed to convert mesh to Brep: {str(e)}") + raise FeatureApplicationError(notch_mesh, geometry, "Failed to convert mesh to Brep: {}".format(str(e))) # apply boolean difference try: brep_with_notch = Brep.from_boolean_difference(geometry, notch_brep) except Exception as e: - raise FeatureApplicationError(notch_brep, geometry, f"Boolean difference operation failed: {str(e)}") + raise FeatureApplicationError(notch_brep, geometry, "Boolean difference operation failed: {}".format(str(e))) # check if the notch is empty if not brep_with_notch: raise FeatureApplicationError( From 5c9866cb9400b26f3ad5940508e61063be5e586f Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 6 Aug 2024 20:35:37 +0200 Subject: [PATCH 18/63] bypass plane|surface intersection for gh test --- src/compas_timber/_fabrication/step_joint_notch.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index ba74dfc79..00f94cf20 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -310,7 +310,7 @@ def from_surface_and_beam( ref_side = beam.ref_sides[ref_side_index] # TODO: is this arbitrary? ref_edge = Line.from_point_and_vector(ref_side.point, ref_side.xaxis) plane = surface.to_plane() - intersection_line = Line(*ref_side.intersections_with_surface(surface)) + # intersection_line = Line(*Plane.from_frame(ref_side).intersections_with_surface(surface)) # calculate orientation orientation = cls._calculate_orientation(ref_side, plane) @@ -322,14 +322,16 @@ def from_surface_and_beam( start_x = distance_point_point(ref_side.point, point_start_x) # calculate start_y - start_y = cls._calculate_start_y(orientation, intersection_line, point_start_x, ref_side) + # start_y = cls._calculate_start_y(orientation, intersection_line, point_start_x, ref_side) + start_y = 0.0 # calculate strut_inclination strut_inclination = cls._calculate_strut_inclination(ref_side, plane, orientation) # calculate notch_width if notch_limited: - notch_width = intersection_line.length + pass + # notch_width = intersection_line.length else: notch_width = beam.width @@ -349,8 +351,8 @@ def from_surface_and_beam( notch_width, step_depth, heel_depth, - step_shape, strut_height, + step_shape, ref_side_index=ref_side_index, ) From d0ad5399e0b059ec5ac5653df9ef18b5eb8e741c Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 6 Aug 2024 20:37:22 +0200 Subject: [PATCH 19/63] re-format --- src/compas_timber/_fabrication/step_joint_notch.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index 00f94cf20..d8819162a 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -468,7 +468,9 @@ def apply(self, geometry, beam): try: notch_mesh = notch_polyhedron.to_mesh() except Exception as e: - raise FeatureApplicationError(notch_polyhedron, geometry, "Failed to convert polyhedron to mesh: {}".format(str(e))) + raise FeatureApplicationError( + notch_polyhedron, geometry, "Failed to convert polyhedron to mesh: {}".format(str(e)) + ) # convert mesh to brep try: notch_brep = Brep.from_mesh(notch_mesh) @@ -478,7 +480,9 @@ def apply(self, geometry, beam): try: brep_with_notch = Brep.from_boolean_difference(geometry, notch_brep) except Exception as e: - raise FeatureApplicationError(notch_brep, geometry, "Boolean difference operation failed: {}".format(str(e))) + raise FeatureApplicationError( + notch_brep, geometry, "Boolean difference operation failed: {}".format(str(e)) + ) # check if the notch is empty if not brep_with_notch: raise FeatureApplicationError( From 3098461228f35f6825f30d964188091f12da0d9c Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 7 Aug 2024 15:14:34 +0200 Subject: [PATCH 20/63] adjustments after check in rhino --- .../_fabrication/step_joint_notch.py | 298 +++++++++--------- 1 file changed, 157 insertions(+), 141 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index d8819162a..7942073d6 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -277,7 +277,7 @@ def displacement_heel(self): @classmethod def from_surface_and_beam( cls, - surface, + plane, beam, notch_limited=False, step_depth=20.0, @@ -309,7 +309,9 @@ def from_surface_and_beam( # define ref_side & ref_edge ref_side = beam.ref_sides[ref_side_index] # TODO: is this arbitrary? ref_edge = Line.from_point_and_vector(ref_side.point, ref_side.xaxis) - plane = surface.to_plane() + # plane = surface.to_plane() + if isinstance(plane, Frame): + plane = Plane.from_frame(plane) # intersection_line = Line(*Plane.from_frame(ref_side).intersections_with_surface(surface)) # calculate orientation @@ -531,8 +533,8 @@ def planes_from_params_and_beam(self, beam): ref_side = beam.side_as_surface(self.ref_side_index) rot_axis = ref_side.frame.yaxis - if self.orientation == OrientationType.END: - rot_axis = -rot_axis # Negative rotation axis for the end cut + # if self.orientation == OrientationType.END: + # rot_axis = -rot_axis # Negative rotation axis for the end cut if self.step_shape == StepShape.STEP: return self._calculate_step_planes(ref_side, rot_axis) @@ -546,8 +548,13 @@ def planes_from_params_and_beam(self, beam): def _calculate_step_planes(self, ref_side, rot_axis): """Calculate cutting planes for a step notch.""" # Move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x, self.start_y) - p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) + if self.strut_inclination > 90: + p_origin = ref_side.point_at(self.start_x, self.start_y) + p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) + else: + p_origin = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) + p_end = ref_side.point_at(self.start_x, self.start_y) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) @@ -567,14 +574,13 @@ def _calculate_step_planes(self, ref_side, rot_axis): cutting_plane_end.transform(rot_short_side) else: # Rotate first cutting plane at the start of the notch (short side of the step) - angle_short_side = math.radians(90 + self.strut_inclination / 2) + angle_short_side = math.radians((180-self.strut_inclination)/2) rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) cutting_plane_origin.transform(rot_short_side) - - # Rotate second cutting plane at the end of the notch (large side of the step) - angle_long_side = math.radians(180) - math.atan( + #Rotate second cutting plane at the end of the notch (large side of the step) + angle_long_side = math.atan( self.step_depth - / (self.displacement_end - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2))) + / (self.displacement_end + (self.step_depth / math.tan(math.radians((180 - self.strut_inclination)/2)))) ) rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) cutting_plane_end.transform(rot_long_side) @@ -583,40 +589,40 @@ def _calculate_step_planes(self, ref_side, rot_axis): def _calculate_heel_planes(self, ref_side, rot_axis): """Calculate cutting planes for a heel notch.""" - if self.strut_inclination > 90: - # Move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x, self.start_y) - p_heel = ref_side.point_at(self.start_x + self.displacement_heel, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # Calculate heel cutting planes angles - # Rotate first cutting plane at the start of the notch (short side of the heel) - angle_short_side = math.radians(180 - self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) - cutting_plane_origin.transform(rot_short_side) - - # Rotate second cutting plane at the end of the notch (long side of the heel) - angle_long_side = math.radians(270 - self.strut_inclination) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_heel) - cutting_plane_heel.transform(rot_long_side) - else: - # Move the frames to the start and end of the notch to create the cuts - p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) - p_heel = ref_side.point_at(self.start_x + (self.displacement_end - self.displacement_heel), self.start_y) - cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # Calculate heel cutting planes angles - # Rotate first cutting plane at the displaced start of the notch (long side of the heel) - angle_long_side = math.radians(90 - self.strut_inclination) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_heel) - cutting_plane_heel.transform(rot_long_side) - - # Rotate second cutting plane at the end of the notch (short side of the heel) - angle_short_side = math.radians(180 - self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) - cutting_plane_end.transform(rot_short_side) + # if self.strut_inclination > 90: + # Move the frames to the start and end of the notch to create the cuts + p_origin = ref_side.point_at(self.start_x, self.start_y) + p_heel = ref_side.point_at(self.start_x + self.displacement_heel, self.start_y) + cutting_plane_end = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # Calculate heel cutting planes angles + # Rotate first cutting plane at the start of the notch (short side of the heel) + angle_short_side = math.radians(180 - self.strut_inclination) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) + cutting_plane_end.transform(rot_short_side) + + # Rotate second cutting plane at the end of the notch (long side of the heel) + angle_long_side = math.radians(270 - self.strut_inclination) + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_heel) + cutting_plane_heel.transform(rot_long_side) + # else: + # # Move the frames to the start and end of the notch to create the cuts + # p_end = ref_side.point_at(self.start_x, self.start_y) + # p_heel = ref_side.point_at(self.start_x + self.displacement_heel, self.start_y) + # cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, -ref_side.frame.yaxis) + # cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, -ref_side.frame.yaxis) + + # # Calculate heel cutting planes angles + # # Rotate first cutting plane at the displaced start of the notch (long side of the heel) + # angle_long_side = math.radians(90 - self.strut_inclination) + # rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_heel) + # cutting_plane_heel.transform(rot_long_side) + + # # Rotate second cutting plane at the end of the notch (short side of the heel) + # angle_short_side = math.radians(180 - self.strut_inclination) + # rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) + # cutting_plane_end.transform(rot_short_side) return [Plane.from_frame(cutting_plane_heel), Plane.from_frame(cutting_plane_end)] @@ -629,113 +635,123 @@ def _calculate_tapered_heel_planes(self, ref_side, rot_axis): cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) # Calculate tapered heel cutting planes angles - if self.strut_inclination > 90: - # Rotate first cutting plane at the start of the notch (short side of the heel) - angle_short_side = math.radians(180 - self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) - cutting_plane_origin.transform(rot_short_side) + # if self.strut_inclination > 90: + # Rotate first cutting plane at the start of the notch (short side of the heel) + angle_short_side = math.radians(180 - self.strut_inclination) + rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) + cutting_plane_origin.transform(rot_short_side) - # Rotate second cutting plane at the end of the notch (long side of the heel) - angle_long_side = math.radians(180) - math.atan( - self.heel_depth - / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination)))) - ) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) - cutting_plane_end.transform(rot_long_side) - else: - # Rotate first cutting plane at the start of the notch (long side of the heel) - angle_long_side = math.atan( - self.heel_depth - / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination)))) - ) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) - cutting_plane_origin.transform(rot_long_side) + # Rotate second cutting plane at the end of the notch (long side of the heel) - # Rotate second cutting plane at the end of the notch (short side of the heel) - angle_short_side = math.radians(180 - self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) - cutting_plane_end.transform(rot_short_side) + angle_long_side = math.atan( + self.heel_depth + / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination)))) + ) + + if self.strut_inclination > 90: + angle_long_side = math.radians(180) - angle_long_side + + rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) + cutting_plane_end.transform(rot_long_side) + # else: + # # Rotate first cutting plane at the start of the notch (long side of the heel) + # angle_long_side = math.atan( + # self.heel_depth + # / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination)))) + # ) + # rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) + # cutting_plane_origin.transform(rot_long_side) + + # # Rotate second cutting plane at the end of the notch (short side of the heel) + # angle_short_side = math.radians(180 - self.strut_inclination) + # rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) + # cutting_plane_end.transform(rot_short_side) return [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_end)] def _calculate_double_planes(self, ref_side, rot_axis): """Calculate cutting planes for a double notch.""" - if self.strut_inclination > 90: - # Move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x, self.start_y) - p_heel = ref_side.point_at(self.start_x + self.displacement_heel, self.start_y) - p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel_step = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # Calculate heel cutting planes angles - # Rotate first cutting plane at the start of the notch (short side of the heel) - angle_short_side_heel = math.radians(180 - self.strut_inclination) - rot_short_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_short_side_heel, point=p_origin) - cutting_plane_origin.transform(rot_short_side_heel) - - # Rotate second cutting plane at the end of the notch (long side of the heel) - angle_long_side_heel = math.radians(270 - self.strut_inclination) - rot_long_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_long_side_heel, point=p_heel) - cutting_plane_heel_heel.transform(rot_long_side_heel) - - # Calculate step cutting planes angles - # Rotate first cutting plane at the end of the heel of the notch (long side of the step) - angle_long_side_step = math.atan( - self.step_depth - / ( - self.displacement_end - - self.displacement_heel - - self.step_depth / math.tan(math.radians(self.strut_inclination / 2)) - ) - ) - rot_long_side_step = Rotation.from_axis_and_angle(rot_axis, angle_long_side_step, point=p_heel) - cutting_plane_heel_step.transform(rot_long_side_step) + # if self.strut_inclination > 90: + # Move the frames to the start and end of the notch to create the cuts + p_origin = ref_side.point_at(self.start_x, self.start_y) + p_heel = ref_side.point_at(self.start_x + self.displacement_heel, self.start_y) + p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_heel_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_heel_step = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) - # Rotate second cutting plane at the end of the notch (short side of the step) - angle_short_side_step = math.radians(180 - self.strut_inclination / 2) - rot_short_side_step = Rotation.from_axis_and_angle(rot_axis, angle_short_side_step, point=p_end) - cutting_plane_end.transform(rot_short_side_step) - else: - # Move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x, self.start_y) - p_heel = ref_side.point_at(self.start_x + (self.displacement_end - self.displacement_heel), self.start_y) - p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel_step = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) + # Calculate heel cutting planes angles + # Rotate first cutting plane at the start of the notch (short side of the heel) + angle_short_side_heel = math.radians(180 - self.strut_inclination) + rot_short_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_short_side_heel, point=p_origin) + cutting_plane_origin.transform(rot_short_side_heel) - # Calculate step cutting planes angles - # Rotate first cutting plane at the start of the notch (short side of the step) - angle_short_side_step = math.radians(90 + self.strut_inclination / 2) - rot_short_side_step = Rotation.from_axis_and_angle(rot_axis, angle_short_side_step, point=p_origin) - cutting_plane_origin.transform(rot_short_side_step) + # Rotate second cutting plane at the end of the notch (long side of the heel) + angle_long_side_heel = math.radians(270 - self.strut_inclination) + rot_long_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_long_side_heel, point=p_heel) + cutting_plane_heel_heel.transform(rot_long_side_heel) - # Rotate second cutting plane at the end of the notch (large side of the step) - angle_long_side_step = math.radians(180) - math.atan( - self.step_depth - / ( - self.displacement_end - - self.displacement_heel - - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2)) - ) + # Calculate step cutting planes angles + # Rotate first cutting plane at the end of the heel of the notch (long side of the step) + angle_long_side_step = math.atan( + self.step_depth + / ( + self.displacement_end + - self.displacement_heel + - self.step_depth / math.tan(math.radians(self.strut_inclination / 2)) ) - rot_long_side_step = Rotation.from_axis_and_angle(rot_axis, angle_long_side_step, point=p_heel) - cutting_plane_heel_step.transform(rot_long_side_step) - - # Calculate heel cutting planes angles - # Rotate first cutting plane at the displaced start of the notch (long side of the heel) - angle_long_side_heel = math.radians(90 - self.strut_inclination) - rot_long_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_long_side_heel, point=p_heel) - cutting_plane_heel_heel.transform(rot_long_side_heel) - - # Rotate second cutting plane at the end of the notch (short side of the heel) - angle_short_side_heel = math.radians(180 - self.strut_inclination) - rot_short_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_short_side_heel, point=p_end) - cutting_plane_end.transform(rot_short_side_heel) + ) + + if self.strut_inclination < 90: + angle_long_side_step = math.atan(self.step_depth / (self.displacement_end - self.displacement_heel + (self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2))))) + rot_long_side_step = Rotation.from_axis_and_angle(rot_axis, angle_long_side_step, point=p_heel) + cutting_plane_heel_step.transform(rot_long_side_step) + + # Rotate second cutting plane at the end of the notch (short side of the step) + angle_short_side_step = math.radians(180 - self.strut_inclination / 2) + if self.strut_inclination < 90: + angle_short_side_step = math.radians(90) + angle_short_side_step + rot_short_side_step = Rotation.from_axis_and_angle(rot_axis, angle_short_side_step, point=p_end) + cutting_plane_end.transform(rot_short_side_step) + # else: + # # Move the frames to the start and end of the notch to create the cuts + # p_origin = ref_side.point_at(self.start_x, self.start_y) + # p_heel = ref_side.point_at(self.start_x + (self.displacement_end - self.displacement_heel), self.start_y) + # p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) + # cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + # cutting_plane_heel_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + # cutting_plane_heel_step = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + # cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # # Calculate step cutting planes angles + # # Rotate first cutting plane at the start of the notch (short side of the step) + # angle_short_side_step = math.radians(90 + self.strut_inclination / 2) + # rot_short_side_step = Rotation.from_axis_and_angle(rot_axis, angle_short_side_step, point=p_origin) + # cutting_plane_origin.transform(rot_short_side_step) + + # # Rotate second cutting plane at the end of the notch (large side of the step) + # angle_long_side_step = math.radians(180) - math.atan( + # self.step_depth + # / ( + # self.displacement_end + # - self.displacement_heel + # - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2)) + # ) + # ) + # rot_long_side_step = Rotation.from_axis_and_angle(rot_axis, angle_long_side_step, point=p_heel) + # cutting_plane_heel_step.transform(rot_long_side_step) + + # # Calculate heel cutting planes angles + # # Rotate first cutting plane at the displaced start of the notch (long side of the heel) + # angle_long_side_heel = math.radians(90 - self.strut_inclination) + # rot_long_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_long_side_heel, point=p_heel) + # cutting_plane_heel_heel.transform(rot_long_side_heel) + + # # Rotate second cutting plane at the end of the notch (short side of the heel) + # angle_short_side_heel = math.radians(180 - self.strut_inclination) + # rot_short_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_short_side_heel, point=p_end) + # cutting_plane_end.transform(rot_short_side_heel) return [ Plane.from_frame(cutting_plane_origin), From 53209936476580c95d1779d07a3d493160dab6cb Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 7 Aug 2024 17:08:51 +0200 Subject: [PATCH 21/63] gh test --- .../_fabrication/step_joint_notch.py | 11 +- .../gh_tests/test_step_joint_notch.ghx | 4616 +++++++++++++++++ 2 files changed, 4624 insertions(+), 3 deletions(-) create mode 100644 tests/compas_timber/gh_tests/test_step_joint_notch.ghx diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index 7942073d6..1a8628c9e 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -817,14 +817,19 @@ def mortise_volume_from_params_and_beam(self, beam): vector_angle = math.radians(180 - self.strut_inclination) else: vector_angle = math.radians(self.strut_inclination) + extr_vector = extr_vector * -1 rot_vect = Rotation.from_axis_and_angle(rot_axis, vector_angle) extr_vector.transform(rot_vect) + + + mortise_polyline_extrusion = mortise_polyline.translated(extr_vector) # extrude the polyline to create the mortise volume as a Brep - mortise_volume = Brep.from_extrusion(mortise_polyline, extr_vector, cap_ends=True) + # mortise_volume = Brep.from_extrusion(mortise_polyline, extr_vector, cap_ends=True) # trim brep with step cutting planes - mortise_volume.trim(step_cutting_plane) # !: check if the trimming works correctly // add checks + # mortise_volume.trim(step_cutting_plane) # !: check if the trimming works correctly // add checks - return mortise_volume + # return mortise_volume + return mortise_polyline, mortise_polyline_extrusion class StepJointNotchParams(BTLxProcessParams): diff --git a/tests/compas_timber/gh_tests/test_step_joint_notch.ghx b/tests/compas_timber/gh_tests/test_step_joint_notch.ghx new file mode 100644 index 000000000..9d8ba94d0 --- /dev/null +++ b/tests/compas_timber/gh_tests/test_step_joint_notch.ghx @@ -0,0 +1,4616 @@ + + + + + + + + 0 + 2 + 2 + + + + + + + 1 + 0 + 7 + + + + + + fa5d732c-b31a-4064-bc30-fdd75169872b + Shaded + 1 + + 100;150;0;0 + + + 100;0;150;0 + + + + + + 638584536051543966 + + test_step_joint_notch.ghx + + + + + 0 + + + + + + -21 + 17 + + 0.246093154 + + + + + 0 + + + + + + + 0 + + + + + 2 + + + + + GhPython, Version=7.34.23267.11001, Culture=neutral, PublicKeyToken=null + 7.34.23267.11001 + + 00000000-0000-0000-0000-000000000000 + + + + + + + Human, Version=1.7.3.0, Culture=neutral, PublicKeyToken=null + 1.7.3.0 + + 5f86fa9f-c62b-50e8-157b-b454ef3e00fa + Human + 1.2.0 + + + + + + + 45 + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + GhPython Script + + + + + from compas_rhino.conversions import surface_to_rhino +from compas_timber._fabrication import StepJointNotch +from compas_timber._fabrication import StepJointNotchParams + +from compas_timber.model import TimberModel +from compas_timber.fabrication import BTLx + +from compas_rhino import unload_modules +from compas.scene import SceneObject + +from compas_rhino.conversions import frame_to_rhino, plane_to_rhino, surface_to_rhino, polyline_to_rhino + +import Rhino.Geometry as rg +unload_modules("compas_timber") + +#define beam and cutting plane +beam = cross_beam +plane = main_beam.ref_sides[index] + +#create step_joint_notch +step_joint_notch = StepJointNotch.from_surface_and_beam(plane, beam, notch_limited=False, step_depth=step, heel_depth=heel, strut_height=100.0, tapered_heel=tapered, ref_side_index=ref_side_index) +cutting_planes = step_joint_notch.planes_from_params_and_beam(beam) + +print("Orientation: ", step_joint_notch.orientation) +print("StartX: ", step_joint_notch.start_x) +print("StrutInclination: ", step_joint_notch.strut_inclination) + + +#add mortise +if mortise_height > 0: + step_joint_notch.add_mortise(beam.width/4, mortise_height, beam) + mortise_polylines = step_joint_notch.mortise_volume_from_params_and_beam(beam) + +##apply geometric features +#step_joint_notch.apply(brep, beam) + +#get btlx params +step_joint_notch_params = StepJointNotchParams(step_joint_notch).as_dict() +btlx_params = [] +for key, value in step_joint_notch_params.items(): + btlx_params.append("{0}: {1}".format(key, value)) + + +#vizualize in rhino +rg_ref_side = frame_to_rhino(beam.ref_sides[ref_side_index]) +rg_cutting_plane = frame_to_rhino(plane) +rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) + +rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_polylines] + + + + GhPython provides a Python script component + + 154 + 194 + + + 1232 + 796 + + true + true + false + false + ca34b946-50be-42d7-8e6d-080419aa1aba + false + true + GhPython Script + Python + + + + + + 1987 + 695 + 222 + 164 + + + 2083 + 777 + + + + + + 8 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 7 + 3ede854e-c753-40eb-84cb-b48008f14fd4 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + true + Script variable Python + 050b78a7-8649-45a7-8e2b-9f2c8fe617b8 + cross_beam + cross_beam + true + 0 + true + c38af54d-dfec-411f-ab38-22c657830b82 + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1989 + 697 + 79 + 20 + + + 2030 + 707 + + + + + + + + true + Script input main_beam. + 85e336ca-36fd-4257-ae26-80a079e9d946 + main_beam + main_beam + true + 0 + true + 19ba4d57-8c61-4bac-90d7-412dfcb8ac0b + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1989 + 717 + 79 + 20 + + + 2030 + 727 + + + + + + + + true + Script input index. + 63012ab7-70b5-4b48-adbc-da1744ff7b8e + index + index + true + 0 + true + c18f9e62-d6f5-42c4-a426-10688f24ca61 + 1 + 48d01794-d3d8-4aef-990e-127168822244 + + + + + + 1989 + 737 + 79 + 20 + + + 2030 + 747 + + + + + + + + true + Script input ref_side_index. + a5edb702-630e-4672-9612-89a6f43ae14c + ref_side_index + ref_side_index + true + 0 + true + fd67193c-c124-4e9b-bf38-3d6a6e085438 + 1 + 48d01794-d3d8-4aef-990e-127168822244 + + + + + + 1989 + 757 + 79 + 20 + + + 2030 + 767 + + + + + + + + true + Script input step. + 7d77a23a-b3b1-4179-80e8-6045dbf22259 + step + step + true + 0 + true + d5f970b8-28fa-4f28-b038-8f3eed5c682d + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1989 + 777 + 79 + 20 + + + 2030 + 787 + + + + + + + + true + Script input heel. + 62418504-ee4a-4be5-84d5-25d7f04b2671 + heel + heel + true + 0 + true + c1e88145-a742-40e8-9b8b-a549422a646d + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1989 + 797 + 79 + 20 + + + 2030 + 807 + + + + + + + + true + Script input tapered. + 1ff95643-005d-47e6-a338-fca927af62e5 + tapered + tapered + true + 0 + true + c925fa54-d90e-456f-863a-2f4f0a2857ba + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1989 + 817 + 79 + 20 + + + 2030 + 827 + + + + + + + + true + Script input mortise_height. + 9355bd0c-5761-49ff-8c36-c610f66d7569 + mortise_height + mortise_height + true + 0 + true + 654077fb-9447-42ca-a4ed-d6a4dd8d1874 + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1989 + 837 + 79 + 20 + + + 2030 + 847 + + + + + + + + The execution information, as output and error streams + 40308c84-a0ba-4c41-b158-38a3341beb2c + out + out + false + 0 + + + + + + 2098 + 697 + 109 + 22 + + + 2152.5 + 708.4286 + + + + + + + + Script output surface. + 43d74bb6-8d1a-4853-8757-d522804215d3 + surface + surface + false + 0 + + + + + + 2098 + 719 + 109 + 23 + + + 2152.5 + 731.2857 + + + + + + + + Script output rg_cutting_plane. + 602ccbf4-1274-4cd9-8349-ba4c5ba030fe + rg_cutting_plane + rg_cutting_plane + false + 0 + + + + + + 2098 + 742 + 109 + 23 + + + 2152.5 + 754.1429 + + + + + + + + Script output rg_planes. + 16bc55f9-a035-4099-834e-e9dc0931b695 + rg_planes + rg_planes + false + 0 + + + + + + 2098 + 765 + 109 + 23 + + + 2152.5 + 777 + + + + + + + + Script output rg_ref_side. + ddc1d959-4491-4f72-b7d5-a74d74b99385 + rg_ref_side + rg_ref_side + false + 0 + + + + + + 2098 + 788 + 109 + 23 + + + 2152.5 + 799.8572 + + + + + + + + Script output rg_mortise_polylines. + 1a4781d4-bfe8-4573-86bb-90d0f0ffc197 + rg_mortise_polylines + rg_mortise_polylines + false + 0 + + + + + + 2098 + 811 + 109 + 23 + + + 2152.5 + 822.7143 + + + + + + + + Script output btlx_params. + d525eccb-5bff-4563-b75e-472cbbdc5901 + step_joint_notch_params + btlx_params + false + 0 + + + + + + 2098 + 834 + 109 + 22 + + + 2152.5 + 845.5714 + + + + + + + + + + + + + + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + Curve + + + + + Contains a collection of generic curves + true + 6bbc142b-2536-41bf-a215-ea52704d32b6 + Curve + Crv + false + 0 + + + + + + 521 + 459 + 50 + 24 + + + 546.5278 + 471.428 + + + + + + 1 + + + + + 1 + {0} + + + + + -1 + + Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesYmekbGRmbmeoaGBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS9f/4eDiZgQx+EDEViJl+1TMxdEL99fs/E4MIlP3synU/95eXBfc/ZRBoZlT64Ddn9+0/QPlAqLwAyNzYbxpcrDPvOXz8/18+rHjlAYmi2O741CCH48eNlj7zvu9wBCoe0mqltvDcBAcmBgTY2xy1xs52sgPIJY3vmdz+1zM1wOS4GQYTAAA= + + 00000000-0000-0000-0000-000000000000 + + + + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + CT: Beam + + + + + """Creates a Beam from a LineCurve.""" + +import rhinoscriptsyntax as rs +from compas.scene import Scene +from compas_rhino.conversions import line_to_compas +from compas_rhino.conversions import vector_to_compas +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Error +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning +from Rhino.RhinoDoc import ActiveDoc + +from compas_timber.elements import Beam as CTBeam +from compas_timber.ghpython.rhino_object_name_attributes import update_rhobj_attributes_name + + +class Beam_fromCurve(component): + def RunScript(self, centerline, z_vector, width, height, category, updateRefObj): + # minimum inputs required + if not centerline: + self.AddRuntimeMessage(Warning, "Input parameter 'Centerline' failed to collect data") + if not width: + self.AddRuntimeMessage(Warning, "Input parameter 'Width' failed to collect data") + if not height: + self.AddRuntimeMessage(Warning, "Input parameter 'Height' failed to collect data") + + # reformat unset parameters for consistency + if not z_vector: + z_vector = [None] + if not category: + category = [None] + + beams = [] + blanks = [] + scene = Scene() + + if centerline and height and width: + # check list lengths for consistency + N = len(centerline) + if len(z_vector) not in (0, 1, N): + self.AddRuntimeMessage( + Error, " In 'ZVector' I need either none, one or the same number of inputs as the Crv parameter." + ) + if len(width) not in (1, N): + self.AddRuntimeMessage( + Error, " In 'W' I need either one or the same number of inputs as the Crv parameter." + ) + if len(height) not in (1, N): + self.AddRuntimeMessage( + Error, " In 'H' I need either one or the same number of inputs as the Crv parameter." + ) + if len(category) not in (0, 1, N): + self.AddRuntimeMessage( + Error, " In 'Category' I need either none, one or the same number of inputs as the Crv parameter." + ) + + # duplicate data if None or single value + if len(z_vector) != N: + z_vector = [z_vector[0] for _ in range(N)] + if len(width) != N: + width = [width[0] for _ in range(N)] + if len(height) != N: + height = [height[0] for _ in range(N)] + if len(category) != N: + category = [category[0] for _ in range(N)] + + for line, z, w, h, c in zip(centerline, z_vector, width, height, category): + guid, geometry = self._get_guid_and_geometry(line) + rhino_line = rs.coerceline(geometry) + line = line_to_compas(rhino_line) + + z = vector_to_compas(z) if z else None + 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)) + update_rhobj_attributes_name(guid, "zvector", str(list(beam.frame.zaxis))) + update_rhobj_attributes_name(guid, "category", c) + + beams.append(beam) + scene.add(beam.blank) + + blanks = scene.draw() + + return beams, blanks + + def _get_guid_and_geometry(self, line): + # internalized curves and GH geometry will not have persistent GUIDs, referenced Rhino objects will + # type hint on the input has to be 'ghdoc' for this to work + guid = None + geometry = line + rhino_obj = ActiveDoc.Objects.FindId(line) + if rhino_obj: + guid = line + geometry = rhino_obj.Geometry + return guid, geometry + + Creates a Beam from a LineCurve. + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDwAACw8BkvkDpQAAAhRJREFUSEulVLtKA0EUzSfkD3zlrUKMIcHGB6ghIj5QxEoDiiA2gtqGYBEfRRQiSRc7iXU6IWKZwh8Q/AI/wEcRr/cMmWUZ7ya7Whw2uXvvOTNnzqyPiP6Nob7+Q0bBQJ0xLQ64BRP4R6OxZ37Swf4+nRwdKyxmFyg4MEhcvxYH3YCH4+GhwHsqMd6+r9ep1WrRY7OpyGcmp2hvZxcCBXG4F3gQligykIIcIulkknJb26qGnXgW4AF/NBi6AzkIQAxcnl+AjAr5vFXzLMDN8ZFI9JXxfVMuW0RYMZ8D3dZqVs2zADeuRALBD3jbaDQUAZ74D2ib/iQwHI5UudHyFsNYLVaN5JjEGj0F+KUVQXisB+EzyO01CV0F+IUYwY21dZUUXXMCemGdKMBFMYIYgIDktx2wD0FAILBQO7FjBGGJveYE2Id5Jn8AH3g1uRhBHCLI7TUJ2BVCAHLuP9eLVgJc3GS/vyZSaboqldQWq5UKZeczyhYdSyfouCLGzLViJ9cCT1A2sbq03NNv7Ay7joXCLzzTb5JbAqa/mdm5XzfTRCeG1Dk35bcEUQAJchLArpAmkDMOJVI7PAkgrrgbuCM8F5cITbgWQFyHI9F2x29HS0y4EkBcuU/5LZF0Q1cB+I3fHMFP7stJBL3gKHBWLOoIvnGPK78lKAEQQkQjOZZQlnS+pq79lgABfNxw2UycSgPeQL4fsKzVEEQlKEcAAAAASUVORK5CYII= + + false + 61e455d1-1197-42f2-9b19-c9255984e680 + true + true + CT: Beam + Beam + + + + + + 1076 + 480 + 145 + 124 + + + 1167 + 542 + + + + + + 6 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 1 + true + Referenced curve or line, Guid of curve or line in the active Rhino document. + af8ecabe-1f50-44f9-9732-a28f0101da66 + Centerline + Centerline + true + 1 + true + 82ddb61d-d21e-49ce-a61c-851130becb9a + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1078 + 482 + 74 + 20 + + + 1116.5 + 492 + + + + + + + + 1 + true + Vector defining the orientation of the cross-section (corresponds to the height dimension). If None, a default will be taken. + e24b6965-6eb8-44c9-a93f-921178776153 + ZVector + ZVector + true + 1 + true + 0 + 15a50725-e3d3-4075-9f7c-142ba5f40747 + + + + + + 1078 + 502 + 74 + 20 + + + 1116.5 + 512 + + + + + + + + 1 + true + Width of the cross-section (usually the smaller dimension). + 3141853d-6310-4d57-8fcb-4485e2106585 + Width + Width + true + 1 + true + 5cc940ad-f898-479c-8389-be3142764477 + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1078 + 522 + 74 + 20 + + + 1116.5 + 532 + + + + + + + + 1 + true + Height of the cross-section (usually the larger dimension). + 649d67fb-cb07-4166-b6a6-1412ba66780a + Height + Height + true + 1 + true + 5d71a959-57a2-4f08-b6ec-584164b09c95 + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1078 + 542 + 74 + 20 + + + 1116.5 + 552 + + + + + + + + 1 + true + Category of a beam. + e7afb315-d6b9-4da4-8b6f-5c05aa8b1895 + Category + Category + true + 1 + true + 0 + 37261734-eec7-4f50-b6a8-b8d1f3c4396b + + + + + + 1078 + 562 + 74 + 20 + + + 1116.5 + 572 + + + + + + + + true + (optional) If True, the attributes in the referenced object will be updated/overwritten with the new values given here. + b9a3c1d6-5e9a-407b-8be2-e4e66ceda80e + updateRefObj + updateRefObj + true + 0 + true + 0 + d60527f5-b5af-4ef6-8970-5f96fe412559 + + + + + + 1078 + 582 + 74 + 20 + + + 1116.5 + 592 + + + + + + + + Beam object(s). + 19ba4d57-8c61-4bac-90d7-412dfcb8ac0b + Beam + Beam + false + 0 + + + + + + 1182 + 482 + 37 + 60 + + + 1200.5 + 512 + + + + + + + + Shape of the beam's blank. + 7ccf81c5-136d-420f-b550-4a331abbcb49 + Blank + Blank + false + 0 + + + + + + 1182 + 542 + 37 + 60 + + + 1200.5 + 572 + + + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 5d71a959-57a2-4f08-b6ec-584164b09c95 + Number Slider + + false + 0 + + + + + + 853 + 550 + 166 + 20 + + + 853.4926 + 550.1252 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 100 + + + + + + + + + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + Curve + + + + + Contains a collection of generic curves + true + 2f78b16d-80d1-4502-954e-49040fe761a3 + Curve + Crv + false + 0 + + + + + + 472 + 684 + 50 + 24 + + + 497.5087 + 696.4502 + + + + + + 1 + + + + + 1 + {0} + + + + + -1 + + Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesYmekbGRmbmeoaGBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS9f/4eDiZgQx+EDEViJl+1TMxdEL99fs/E4MIlP3synU/95eXBfc/ZRBoZlT64Ddn9+0/QPlAqLwAyFyF5Ln1s/nvOXz8/18+rHjlAYmi2O741CCHCRvP7T9b98DhM5o4EwMaKFjuAHKJ94LooP/1TA0wYW50dQMKAA== + + 00000000-0000-0000-0000-000000000000 + + + + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + CT: Beam + + + + + """Creates a Beam from a LineCurve.""" + +import rhinoscriptsyntax as rs +from compas.scene import Scene +from compas_rhino.conversions import line_to_compas +from compas_rhino.conversions import vector_to_compas +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Error +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning +from Rhino.RhinoDoc import ActiveDoc + +from compas_timber.elements import Beam as CTBeam +from compas_timber.ghpython.rhino_object_name_attributes import update_rhobj_attributes_name + + +class Beam_fromCurve(component): + def RunScript(self, centerline, z_vector, width, height, category, updateRefObj): + # minimum inputs required + if not centerline: + self.AddRuntimeMessage(Warning, "Input parameter 'Centerline' failed to collect data") + if not width: + self.AddRuntimeMessage(Warning, "Input parameter 'Width' failed to collect data") + if not height: + self.AddRuntimeMessage(Warning, "Input parameter 'Height' failed to collect data") + + # reformat unset parameters for consistency + if not z_vector: + z_vector = [None] + if not category: + category = [None] + + beams = [] + blanks = [] + scene = Scene() + + if centerline and height and width: + # check list lengths for consistency + N = len(centerline) + if len(z_vector) not in (0, 1, N): + self.AddRuntimeMessage( + Error, " In 'ZVector' I need either none, one or the same number of inputs as the Crv parameter." + ) + if len(width) not in (1, N): + self.AddRuntimeMessage( + Error, " In 'W' I need either one or the same number of inputs as the Crv parameter." + ) + if len(height) not in (1, N): + self.AddRuntimeMessage( + Error, " In 'H' I need either one or the same number of inputs as the Crv parameter." + ) + if len(category) not in (0, 1, N): + self.AddRuntimeMessage( + Error, " In 'Category' I need either none, one or the same number of inputs as the Crv parameter." + ) + + # duplicate data if None or single value + if len(z_vector) != N: + z_vector = [z_vector[0] for _ in range(N)] + if len(width) != N: + width = [width[0] for _ in range(N)] + if len(height) != N: + height = [height[0] for _ in range(N)] + if len(category) != N: + category = [category[0] for _ in range(N)] + + for line, z, w, h, c in zip(centerline, z_vector, width, height, category): + guid, geometry = self._get_guid_and_geometry(line) + rhino_line = rs.coerceline(geometry) + line = line_to_compas(rhino_line) + + z = vector_to_compas(z) if z else None + 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)) + update_rhobj_attributes_name(guid, "zvector", str(list(beam.frame.zaxis))) + update_rhobj_attributes_name(guid, "category", c) + + beams.append(beam) + scene.add(beam.blank) + + blanks = scene.draw() + + return beams, blanks + + def _get_guid_and_geometry(self, line): + # internalized curves and GH geometry will not have persistent GUIDs, referenced Rhino objects will + # type hint on the input has to be 'ghdoc' for this to work + guid = None + geometry = line + rhino_obj = ActiveDoc.Objects.FindId(line) + if rhino_obj: + guid = line + geometry = rhino_obj.Geometry + return guid, geometry + + Creates a Beam from a LineCurve. + true + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDgAACw4BQL7hQQAAAhRJREFUSEulVLtKA0EUzSfkD3zlrUKMIcHGB6ghIj5QxEoDiiA2gtqGYBEfRRQiSRc7iXU6IWKZwh8Q/AI/wEcRr/cMmWUZ7ya7Whw2uXvvOTNnzqyPiP6Nob7+Q0bBQJ0xLQ64BRP4R6OxZ37Swf4+nRwdKyxmFyg4MEhcvxYH3YCH4+GhwHsqMd6+r9ep1WrRY7OpyGcmp2hvZxcCBXG4F3gQligykIIcIulkknJb26qGnXgW4AF/NBi6AzkIQAxcnl+AjAr5vFXzLMDN8ZFI9JXxfVMuW0RYMZ8D3dZqVs2zADeuRALBD3jbaDQUAZ74D2ib/iQwHI5UudHyFsNYLVaN5JjEGj0F+KUVQXisB+EzyO01CV0F+IUYwY21dZUUXXMCemGdKMBFMYIYgIDktx2wD0FAILBQO7FjBGGJveYE2Id5Jn8AH3g1uRhBHCLI7TUJ2BVCAHLuP9eLVgJc3GS/vyZSaboqldQWq5UKZeczyhYdSyfouCLGzLViJ9cCT1A2sbq03NNv7Ay7joXCLzzTb5JbAqa/mdm5XzfTRCeG1Dk35bcEUQAJchLArpAmkDMOJVI7PAkgrrgbuCM8F5cITbgWQFyHI9F2x29HS0y4EkBcuU/5LZF0Q1cB+I3fHMFP7stJBL3gKHBWLOoIvnGPK78lKAEQQkQjOZZQlnS+pq79lgABfNxw2UycSgPeQL4fsKzVEEQlKEcAAAAASUVORK5CYII= + + false + efe8bffe-0848-4d32-993f-7f43d93bbf01 + true + true + CT: Beam + Beam + + + + + + 1074 + 688 + 145 + 124 + + + 1165 + 750 + + + + + + 6 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 1 + true + Referenced curve or line, Guid of curve or line in the active Rhino document. + 8ca4719b-64bb-4a1e-bd21-47829a6da8fb + Centerline + Centerline + true + 1 + true + 919e0b0e-50c5-4983-bf15-4bb7b9f39c0a + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1076 + 690 + 74 + 20 + + + 1114.5 + 700 + + + + + + + + 1 + true + Vector defining the orientation of the cross-section (corresponds to the height dimension). If None, a default will be taken. + e4854a14-8e9a-421e-a9c2-b070f3e54f84 + ZVector + ZVector + true + 1 + true + 0 + 15a50725-e3d3-4075-9f7c-142ba5f40747 + + + + + + 1076 + 710 + 74 + 20 + + + 1114.5 + 720 + + + + + + + + 1 + true + Width of the cross-section (usually the smaller dimension). + 4941e70a-0c52-4f0e-829c-88c706d76cab + Width + Width + true + 1 + true + f40578da-c08b-4e1d-b010-8b988d3269a2 + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1076 + 730 + 74 + 20 + + + 1114.5 + 740 + + + + + + + + 1 + true + Height of the cross-section (usually the larger dimension). + 86c7e8a6-883d-41bb-8889-46964d8e84b3 + Height + Height + true + 1 + true + f40578da-c08b-4e1d-b010-8b988d3269a2 + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1076 + 750 + 74 + 20 + + + 1114.5 + 760 + + + + + + + + 1 + true + Category of a beam. + f30f6c3e-93b2-40b0-aa59-ae87fdde3fe1 + Category + Category + true + 1 + true + 0 + 37261734-eec7-4f50-b6a8-b8d1f3c4396b + + + + + + 1076 + 770 + 74 + 20 + + + 1114.5 + 780 + + + + + + + + true + (optional) If True, the attributes in the referenced object will be updated/overwritten with the new values given here. + 63f2f6d5-c8f8-446e-af8e-245bc6bd3b84 + updateRefObj + updateRefObj + true + 0 + true + 0 + d60527f5-b5af-4ef6-8970-5f96fe412559 + + + + + + 1076 + 790 + 74 + 20 + + + 1114.5 + 800 + + + + + + + + Beam object(s). + c38af54d-dfec-411f-ab38-22c657830b82 + Beam + Beam + false + 0 + + + + + + 1180 + 690 + 37 + 60 + + + 1198.5 + 720 + + + + + + + + Shape of the beam's blank. + 489d49f6-b379-49e3-8aed-197b4a1468a4 + Blank + Blank + false + 0 + + + + + + 1180 + 750 + 37 + 60 + + + 1198.5 + 780 + + + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + f40578da-c08b-4e1d-b010-8b988d3269a2 + Number Slider + + false + 0 + + + + + + 888 + 745 + 166 + 20 + + + 888.4611 + 745.2539 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 100 + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 5cc940ad-f898-479c-8389-be3142764477 + Number Slider + + false + 0 + + + + + + 853 + 526 + 166 + 20 + + + 853.5906 + 526.7526 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 80 + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + CT: DecomposeBeam + + + + + # flake8: noqa +from compas.geometry import Line +from compas_rhino.conversions import frame_to_rhino_plane +from compas_rhino.conversions import line_to_rhino +from compas_rhino.conversions import point_to_rhino +from compas_rhino.conversions import box_to_rhino +from ghpythonlib.componentbase import executingcomponent as component +from System.Drawing import Color + + +class BeamDecompose(component): + RED = Color.FromArgb(255, 255, 100, 100) + GREEN = Color.FromArgb(200, 50, 220, 100) + BLUE = Color.FromArgb(200, 50, 150, 255) + WHITE = Color.FromArgb(255, 255, 255, 255) + YELLOW = Color.FromArgb(255, 255, 255, 0) + SCREEN_SIZE = 10 + RELATIVE_SIZE = 0 + + def RunScript(self, beam, show_frame, show_faces): + self.show_faces = show_faces if show_faces is not None else False + self.show_frame = show_frame if show_frame is not None else False + self.frames = [] + self.rhino_frames = [] + self.scales = [] + self.faces = [] + self.width = [] + self.height = [] + self.centerline = [] + self.shapes = [] + + for b in beam: + self.frames.append(b.frame) + self.rhino_frames.append(frame_to_rhino_plane(b.frame)) + self.scales.append(b.width + b.height) + self.centerline.append(line_to_rhino(b.centerline)) + self.shapes.append(box_to_rhino(b.shape)) + self.width.append(b.width) + self.height.append(b.height) + self.faces.append(b.faces) + + return self.rhino_frames, self.centerline, self.shapes, self.width, self.height + + def DrawViewportWires(self, arg): + if self.Locked: + return + + for f, s, faces in zip(self.frames, self.scales, self.faces): + if self.show_frame: + self._draw_frame(arg.Display, f, s) + if self.show_faces: + self._draw_faces(arg.Display, faces, s) + + def _draw_frame(self, display, frame, scale): + x = Line.from_point_and_vector(frame.point, frame.xaxis * scale) + y = Line.from_point_and_vector(frame.point, frame.yaxis * scale) + z = Line.from_point_and_vector(frame.point, frame.zaxis * scale) + display.DrawArrow(line_to_rhino(x), self.RED, self.SCREEN_SIZE, self.RELATIVE_SIZE) + display.DrawArrow(line_to_rhino(y), self.GREEN, self.SCREEN_SIZE, self.RELATIVE_SIZE) + display.DrawArrow(line_to_rhino(z), self.BLUE, self.SCREEN_SIZE, self.RELATIVE_SIZE) + + x_loc = x.end + x.vector * scale * 1.1 + y_loc = y.end + y.vector * scale * 1.1 + z_loc = z.end + z.vector * scale * 1.1 + display.Draw2dText("X", self.RED, point_to_rhino(x_loc), True, 16, "Verdana") + display.Draw2dText("Y", self.GREEN, point_to_rhino(y_loc), True, 16, "Verdana") + display.Draw2dText("Z", self.BLUE, point_to_rhino(z_loc), True, 16, "Verdana") + + def _draw_faces(self, display, faces, scale): + for index, face in enumerate(faces): + normal = Line.from_point_and_vector(face.point, face.normal * scale) + text = str(index) + display.Draw2dText(text, self.WHITE, point_to_rhino(face.point), True, 16, "Verdana") + display.DrawArrow(line_to_rhino(normal), self.YELLOW, self.SCREEN_SIZE, self.RELATIVE_SIZE) + + GhPython provides a Python script component + true + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDwAACw8BkvkDpQAAAohJREFUSEuVlj9rFEEYxvMR7gMEdvFy/6NZDGijuQ3aKCKLiqVuIUKwOdBYWIQtkxQWJhAQvFgdEeyuUrggIniFhWAlCPoJ1MoESRyfZ5z3nN2b+7PFj7nbmX2emXfe972bUUoNMfv43RsQuOayFD0/AokDn/NDL0A4Boom2TkbCBRqpXIHo4pv3dbwc3WudIgxNiSplyBaAD+NAYnseQEv+vPV2hfwZ3trS+2224qf+QxzgRi7DBJLnHwDBXsNXoqwy4Plpabqdrtq9f4DvXOIv8bo1cuVz41q7ZjzKQMI+ZawTSJrGpXqDsUYDorfvH5Di4MWCCvFuV/h+aWjF3t7YjxkEIKWEf5ovgdYWDhZq3+AwdHm+oaiwJnTi8cUpLAx0Ib7vZ7q9/vDBpYRRWmgL5kCFKIghWnAEDAUmPMk3hSksDCVARakdiaZYkLl00Qu2hafysB71PlOsWRtTcebl2alYOqis+JjDby7G3do4K+2dfplUxB3sU5jnkbi7cIkQNoAD+JS8+pvGlx8+lZ2oRYa8y8xeiYV9alcooSmVy5dltNGIjyoynsrK3pRJgUDnoAn4Ylcws9evVfN7Z46ey2W7NKtRldl+UTxkxEbIIsA433IeI8LycMnu1I3anZzv4PxXy+CwDnAXWbx7MKyxQZC42HLSWjAzpfavYEF5Ix3RmgU/w2yRXIhXP4hBqNiznCZ1D3AughiUj/kORiEKLcBK5oXfqre+Io1+jKNAX9HQn4XchuwVfA5exPGVKd1kcuAKcxnvHyXmIupDDJ1EbuERjHRgFnEy7SLJw8TDYhpzTor8qIN2DtoIiwuBEw9bWBayMTLHAUNKJT9y0F80HK9ND1q5i+sgg6R/W1M2wAAAABJRU5ErkJggg== + + false + eb9d5147-e225-45a5-a44b-61953c4955eb + true + true + CT: DecomposeBeam + DecomposeBeam + + + + + + 1382 + 621 + 156 + 104 + + + 1462 + 673 + + + + + + 3 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 5 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 1 + true + Beam + 8e04df08-7ba8-497d-81a4-3df28baa4429 + Beam + Beam + true + 1 + true + 19ba4d57-8c61-4bac-90d7-412dfcb8ac0b + c38af54d-dfec-411f-ab38-22c657830b82 + 2 + 35915213-5534-4277-81b8-1bdc9e7383d2 + + + + + + 1384 + 623 + 63 + 33 + + + 1417 + 639.6667 + + + + + + + + true + Script input ShowFrame. + 55d797db-e8b9-4a25-83f9-fb3ce5e97dda + ShowFrame + ShowFrame + true + 0 + true + 4589d281-06a1-48a8-ab9c-fa318fb66883 + 1 + d60527f5-b5af-4ef6-8970-5f96fe412559 + + + + + + 1384 + 656 + 63 + 33 + + + 1417 + 673 + + + + + + + + true + Script input ShowFaces. + 38380630-dd0c-4f25-ba4d-8be79af14f88 + ShowFaces + ShowFaces + true + 0 + true + 4589d281-06a1-48a8-ab9c-fa318fb66883 + 1 + d60527f5-b5af-4ef6-8970-5f96fe412559 + + + + + + 1384 + 689 + 63 + 34 + + + 1417 + 706.3334 + + + + + + + + Script output Frame. + eaa8a578-60ae-4948-8427-7ef3e9061069 + Frame + Frame + false + 0 + + + + + + 1477 + 623 + 59 + 20 + + + 1506.5 + 633 + + + + + + + + Script output Centerline. + d5904a7c-0c03-4764-8d8c-d828062b20dd + Centerline + Centerline + false + 0 + + + + + + 1477 + 643 + 59 + 20 + + + 1506.5 + 653 + + + + + + + + Script output Box. + e778515e-1975-4634-93fa-0a12d895cfb0 + Box + Box + false + 0 + + + + + + 1477 + 663 + 59 + 20 + + + 1506.5 + 673 + + + + + + + + Script output Width. + d3ee9e41-73c8-4e9b-8793-3de14a036ce6 + Width + Width + false + 0 + + + + + + 1477 + 683 + 59 + 20 + + + 1506.5 + 693 + + + + + + + + Script output Height. + 712b50a9-aa16-4a06-850a-5ccaa6c1e4a1 + Height + Height + false + 0 + + + + + + 1477 + 703 + 59 + 20 + + + 1506.5 + 713 + + + + + + + + + + + + + + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a + Boolean Toggle + + + + + Boolean (true/false) toggle + 4589d281-06a1-48a8-ab9c-fa318fb66883 + Boolean Toggle + Toggle + false + 0 + true + + + + + + 1257 + 679 + 104 + 22 + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + c18f9e62-d6f5-42c4-a426-10688f24ca61 + Number Slider + + false + 0 + + + + + + 1762 + 694 + 159 + 20 + + + 1762.14 + 694.5345 + + + + + + 3 + 1 + 1 + 15 + 0 + 0 + 0 + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + fd67193c-c124-4e9b-bf38-3d6a6e085438 + Number Slider + + false + 0 + + + + + + 1722 + 718 + 201 + 20 + + + 1722.123 + 718.8546 + + + + + + 3 + 1 + 1 + 5 + 0 + 0 + 2 + + + + + + + + + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel + + + + + A panel for custom notes and text values + cfb4b1f8-a02e-44ce-9c95-80b1164d7fca + Panel + BTLx Params + false + 0 + d525eccb-5bff-4563-b75e-472cbbdc5901 + 1 + Double click to edit panel content… + + + + + + 2400 + 926 + 212 + 333 + + 0 + 0 + 0 + + 2400.01 + 926.6734 + + + + + + + 255;255;255;255 + + true + true + true + false + false + true + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + d5f970b8-28fa-4f28-b038-8f3eed5c682d + Number Slider + + false + 0 + + + + + + 1727 + 786 + 160 + 20 + + + 1727.211 + 786.5431 + + + + + + 3 + 1 + 1 + 200 + 0 + 0 + 30 + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + c1e88145-a742-40e8-9b8b-a549422a646d + Number Slider + + false + 0 + + + + + + 1727 + 809 + 160 + 20 + + + 1727.21 + 809.6162 + + + + + + 3 + 1 + 1 + 200 + 0 + 0 + 15 + + + + + + + + + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a + Boolean Toggle + + + + + Boolean (true/false) toggle + c925fa54-d90e-456f-863a-2f4f0a2857ba + Boolean Toggle + tapered_heel + false + 0 + false + + + + + + 1752 + 831 + 133 + 22 + + + + + + + + + + f31d8d7a-7536-4ac8-9c96-fde6ecda4d0a + Cluster + + + + + + 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 + + Contains a cluster of Grasshopper components + true + a5869824-14fc-400f-a69e-f5050392632f + Cluster + Cluster + false + + + + + 5 + 3f3af581-d64e-4909-91e4-3576cf2e9e86 + 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 + d28e1b27-74ad-41ab-b017-540fa9111876 + ebd18bdb-78c3-4d40-8a69-e1af30a53c27 + fd03a9d4-272c-4590-9595-4d3107dc0fdc + 3c631e1e-b12f-4297-9535-87b4fdc7b45e + 5d665740-a9cc-4f15-8a38-0dc75e214ae2 + 5d32325d-62cf-40bd-93fe-74af56a2c91e + b360d350-2b53-401b-9420-d9402019cb30 + 796ac502-faba-4bb6-a612-7e3dfb448d98 + + + + + + 2381 + 756 + 72 + 84 + + + 2420 + 798 + + + + + + 4 + 919e146f-30ae-4aae-be34-4d72f555e7da + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + 1 + 919e146f-30ae-4aae-be34-4d72f555e7da + + + + + Brep to split + ebd18bdb-78c3-4d40-8a69-e1af30a53c27 + Brep + B + true + 489d49f6-b379-49e3-8aed-197b4a1468a4 + 1 + + + + + + 2383 + 758 + 22 + 20 + + + 2395.5 + 768 + + + + + + + + Contains a collection of three-dimensional axis-systems + d28e1b27-74ad-41ab-b017-540fa9111876 + Plane + Pln + true + 16bc55f9-a035-4099-834e-e9dc0931b695 + 1 + + + + + + 2383 + 778 + 22 + 20 + + + 2395.5 + 788 + + + + + + + + Contains a collection of three-dimensional axis-systems + fd03a9d4-272c-4590-9595-4d3107dc0fdc + Plane + Pln + true + ddc1d959-4491-4f72-b7d5-a74d74b99385 + 1 + + + + + + 2383 + 798 + 22 + 20 + + + 2395.5 + 808 + + + + + + + + 1 + Section curves + 3f3af581-d64e-4909-91e4-3576cf2e9e86 + Curves + C + true + 1a4781d4-bfe8-4573-86bb-90d0f0ffc197 + 1 + + + + + + 2383 + 818 + 22 + 20 + + + 2395.5 + 828 + + + + + + + + 1 + Joined Breps + 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 + Breps + B + false + 0 + + + + + + 2435 + 758 + 16 + 80 + + + 2443 + 798 + + + + + + + + + + + + + + 537b0419-bbc2-4ff4-bf08-afe526367b2c + Custom Preview + + + + + Allows for customized geometry previews + true + true + 9e4f0b21-759b-4c9a-8dfb-6654484027c5 + Custom Preview + Preview + + + + + + + 2569 + 841 + 48 + 44 + + + 2603 + 863 + + + + + + Geometry to preview + true + bc0fc1fe-a18e-40ca-ab8a-8ba765dde1f9 + Geometry + G + false + 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 + 1 + + + + + + 2571 + 843 + 17 + 20 + + + 2581 + 853 + + + + + + + + The material override + 8cbb31fa-4051-471d-87be-5ce455d7f325 + Material + M + false + 0 + + + + + + 2571 + 863 + 17 + 20 + + + 2581 + 873 + + + + + + 1 + + + + + 1 + {0} + + + + + + 255;221;160;221 + + + 255;66;48;66 + + 0.5 + + 255;255;255;255 + + 0 + + + + + + + + + + + + + + + 0148a65d-6f42-414a-9db7-9a9b2eb78437 + Brep Edges + + + + + Extract the edge curves of a brep. + true + be8a15e2-8773-4720-9d61-dfdc10761646 + Brep Edges + Edges + + + + + + 2469 + 766 + 72 + 64 + + + 2499 + 798 + + + + + + Base Brep + 0056a757-f1c7-4722-83e9-e00c5e697cfc + Brep + B + false + 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 + 1 + + + + + + 2471 + 768 + 13 + 60 + + + 2479 + 798 + + + + + + + + 1 + Naked edge curves + 2fc3daa1-27be-494a-9118-8afa0df8edc7 + Naked + En + false + 0 + + + + + + 2514 + 768 + 25 + 20 + + + 2526.5 + 778 + + + + + + + + 1 + Interior edge curves + 7db965ba-79d8-42a1-926c-cc7c5ea6a716 + Interior + Ei + false + 0 + + + + + + 2514 + 788 + 25 + 20 + + + 2526.5 + 798 + + + + + + + + 1 + Non-Manifold edge curves + dfe49992-1aa8-4a4e-bf86-f66139ca06c5 + Non-Manifold + Em + false + 0 + + + + + + 2514 + 808 + 25 + 20 + + + 2526.5 + 818 + + + + + + + + + + + + 22990b1f-9be6-477c-ad89-f775cd347105 + Flip Curve + + + + + Flip a curve using an optional guide curve. + true + 0c07a9ce-439d-466f-98d1-fcc3dcd14023 + Flip Curve + Flip + + + + + + 876 + 695 + 66 + 44 + + + 908 + 717 + + + + + + Curve to flip + 082a270f-f37d-4c27-8266-d4348e1e059b + Curve + C + false + 2f78b16d-80d1-4502-954e-49040fe761a3 + 1 + + + + + + 878 + 697 + 15 + 20 + + + 887 + 707 + + + + + + + + Optional guide curve + 94c738fb-f46a-4531-af70-ea11c88263f2 + Guide + G + true + 0 + + + + + + 878 + 717 + 15 + 20 + + + 887 + 727 + + + + + + + + Flipped curve + c284b03b-3c25-4136-b35d-211207e5e246 + Curve + C + false + 0 + + + + + + 923 + 697 + 17 + 20 + + + 931.5 + 707 + + + + + + + + Flip action + a48ad8ba-3072-486f-9f31-f1cbfcaa31e2 + Flag + F + false + 0 + + + + + + 923 + 717 + 17 + 20 + + + 931.5 + 727 + + + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + 482410b9-724c-46d6-be3a-3d63785bc853 + Plane + Pln + false + 16bc55f9-a035-4099-834e-e9dc0931b695 + 1 + + + + + + 2566 + 703 + 50 + 24 + + + 2591.305 + 715.5306 + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + 9c473eb6-ff8f-4c7f-bd69-56527d0257f6 + Plane + Pln + false + ddc1d959-4491-4f72-b7d5-a74d74b99385 + 1 + + + + + + 2565 + 632 + 50 + 24 + + + 2590.664 + 644.5862 + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + 88b4a335-192d-48a1-81d5-163278eaecbe + Plane + Pln + false + 602ccbf4-1274-4cd9-8349-ba4c5ba030fe + 1 + + + + + + 2565 + 564 + 50 + 24 + + + 2590.097 + 576.5297 + + + + + + + + + + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel + + + + + A panel for custom notes and text values + 84325f58-aa64-43ef-9c59-6631e3ff4da9 + Panel + + false + 1 + 40308c84-a0ba-4c41-b158-38a3341beb2c + 1 + Double click to edit panel content… + + + + + + 1988 + 610 + 221 + 85 + + 0 + 0 + 0 + + 1988.603 + 610.7073 + + + + + + + 255;255;250;90 + + true + true + true + false + false + true + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 88b4a335-192d-48a1-81d5-163278eaecbe + 1 + 234eec62-a3cc-4178-9760-678a11912fb0 + Group + CUTTING PLANE + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 9c473eb6-ff8f-4c7f-bd69-56527d0257f6 + 1 + 86bc7ac3-bdd8-443c-ae9b-e22ea6ec103a + Group + REF_SIDE + + + + + + + + + + a77d0879-94c2-4101-be44-e4a616ffeb0c + 5f86fa9f-c62b-50e8-157b-b454ef3e00fa + Custom Preview Lineweights + + + + + Custom Preview with Lineweights + 56020ec2-ccb1-4ae9-a538-84dcb2857db9 + Custom Preview Lineweights + PreviewLW + + + + + + + 2570 + 747 + 46 + 84 + + + 2602 + 789 + + + + + + Geometry to preview + true + 11bd5cdd-8b99-4483-950a-c0cb8f29576c + Geometry + G + false + 7db965ba-79d8-42a1-926c-cc7c5ea6a716 + 1 + + + + + + 2572 + 749 + 15 + 20 + + + 2581 + 759 + + + + + + + + The preview shader override + 7eb996a5-eb09-476d-8a9f-c49f7a1c895e + Shader + S + false + 0 + + + + + + 2572 + 769 + 15 + 20 + + + 2581 + 779 + + + + + + 1 + + + + + 1 + {0} + + + + + + 255;255;105;180 + + + 255;76;32;54 + + 0.5 + + 255;255;255;255 + + 0 + + + + + + + + + + + The thickness of the wire display + c3053791-290f-494e-8384-81e8464e4dc4 + Thickness + T + true + 0 + + + + + + 2572 + 789 + 15 + 20 + + + 2581 + 799 + + + + + + + + Set to true to try to render curves with an absolute dimension. + 073aa3ff-0ed7-42b6-bdd5-c5b25159731c + Absolute + A + false + 0 + + + + + + 2572 + 809 + 15 + 20 + + + 2581 + 819 + + + + + + 1 + + + + + 1 + {0} + + + + + false + + + + + + + + + + + + + + + 11bbd48b-bb0a-4f1b-8167-fa297590390d + End Points + + + + + Extract the end points of a curve. + true + 715464ea-9048-4ae7-9c5c-942cc0fa2486 + End Points + End + + + + + + 727 + 448 + 64 + 44 + + + 758 + 470 + + + + + + Curve to evaluate + ba457b2a-de34-4922-929d-926d8adedc5a + Curve + C + false + 6bbc142b-2536-41bf-a215-ea52704d32b6 + 1 + + + + + + 729 + 450 + 14 + 40 + + + 737.5 + 470 + + + + + + + + Curve start point + 77f5802b-abc0-49e5-91cc-3376ae99a75c + Start + S + false + 0 + + + + + + 773 + 450 + 16 + 20 + + + 781 + 460 + + + + + + + + Curve end point + 5a7d2dd5-1871-45dd-b9c0-e6289501fd3a + End + E + false + 0 + + + + + + 773 + 470 + 16 + 20 + + + 781 + 480 + + + + + + + + + + + + e9eb1dcf-92f6-4d4d-84ae-96222d60f56b + Move + + + + + Translate (move) an object along a vector. + true + 22d84b32-205f-41d5-8663-8af45f94307c + Move + Move + + + + + + 833 + 468 + 83 + 44 + + + 881 + 490 + + + + + + Base geometry + 4f7e8528-a89b-42ec-8171-f6004589fdcb + Geometry + G + true + 5a7d2dd5-1871-45dd-b9c0-e6289501fd3a + 1 + + + + + + 835 + 470 + 31 + 20 + + + 860 + 480 + + + + + + + + Translation vector + 21c38b75-b524-4c4f-93e3-89a660e1a69b + -x + Motion + T + false + 1e474aea-db61-424d-be9a-804e3fd04359 + 1 + + + + + + 835 + 490 + 31 + 20 + + + 860 + 500 + + + + + + 1 + + + + + 1 + {0} + + + + + + 0 + 0 + 10 + + + + + + + + + + + + Translated geometry + 9348c98e-479b-416c-9b0b-54c72c33be75 + Geometry + G + false + 0 + + + + + + 896 + 470 + 18 + 20 + + + 905 + 480 + + + + + + + + Transformation data + 35ee0f80-11b3-44ca-8fde-aa3bba3f7c43 + Transform + X + false + 0 + + + + + + 896 + 490 + 18 + 20 + + + 905 + 500 + + + + + + + + + + + + 79f9fbb3-8f1d-4d9a-88a9-f7961b1012cd + Unit X + + + + + Unit vector parallel to the world {x} axis. + true + 73ddbf47-b58a-451f-8f84-85c767ffb835 + Unit X + X + + + + + + 513 + 491 + 63 + 28 + + + 542 + 505 + + + + + + Unit multiplication + 4b128ece-c9a7-4204-9394-3fda75fa3d6f + Factor + F + false + 912e5dfb-d2a2-463b-a0eb-2610982bf536 + 1 + + + + + + 515 + 493 + 12 + 24 + + + 522.5 + 505 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + World {x} vector + 323481aa-7502-4bf4-8393-f5ea30a2e8ed + Unit vector + V + false + 0 + + + + + + 557 + 493 + 17 + 24 + + + 565.5 + 505 + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 912e5dfb-d2a2-463b-a0eb-2610982bf536 + Number Slider + + false + 0 + + + + + + 323 + 497 + 163 + 20 + + + 323.5465 + 497.3116 + + + + + + 3 + 1 + 1 + 2000 + 0 + 0 + 0 + + + + + + + + + 4c4e56eb-2f04-43f9-95a3-cc46a14f495a + Line + + + + + Create a line between two points. + true + 25e3b03c-eedc-4418-babd-0df09c1d6284 + Line + Ln + + + + + + 943 + 448 + 63 + 44 + + + 974 + 470 + + + + + + Line start point + 99feaba8-239b-40e1-ae13-db12fa1af53e + Start Point + A + false + 77f5802b-abc0-49e5-91cc-3376ae99a75c + 1 + + + + + + 945 + 450 + 14 + 20 + + + 953.5 + 460 + + + + + + + + Line end point + 5d3fa723-a735-4608-9a27-c304c4c3aa44 + End Point + B + false + 9348c98e-479b-416c-9b0b-54c72c33be75 + 1 + + + + + + 945 + 470 + 14 + 20 + + + 953.5 + 480 + + + + + + + + Line segment + 82ddb61d-d21e-49ce-a61c-851130becb9a + Line + L + false + 0 + + + + + + 989 + 450 + 15 + 40 + + + 996.5 + 470 + + + + + + + + + + + + 9103c240-a6a9-4223-9b42-dbd19bf38e2b + Unit Z + + + + + Unit vector parallel to the world {z} axis. + true + eaf786cc-99fc-4d46-b6b3-cf6b5314011a + Unit Z + Z + + + + + + 515 + 525 + 63 + 28 + + + 544 + 539 + + + + + + Unit multiplication + 88ec5468-52fb-4929-b59a-79cb5d549828 + Factor + F + false + 68472b2f-1682-4c03-a570-0bd3dafb7255 + 1 + + + + + + 517 + 527 + 12 + 24 + + + 524.5 + 539 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + World {z} vector + f08f6ccf-8731-4561-b2d4-cf43dcb5070e + Unit vector + V + false + 0 + + + + + + 559 + 527 + 17 + 24 + + + 567.5 + 539 + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 68472b2f-1682-4c03-a570-0bd3dafb7255 + Number Slider + + false + 0 + + + + + + 329 + 529 + 163 + 20 + + + 329.7557 + 529.9963 + + + + + + 3 + 1 + 1 + 2000 + 0 + 0 + 0 + + + + + + + + + a0d62394-a118-422d-abb3-6af115c75b25 + Addition + + + + + Mathematical addition + true + 362a8c85-9ff1-4541-8448-a78e3c79ff60 + Addition + A+B + + + + + + 728 + 493 + 65 + 44 + + + 759 + 515 + + + + + + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + First item for addition + 0b1b606a-bd74-4af5-928f-2813c43db4f7 + A + A + true + 323481aa-7502-4bf4-8393-f5ea30a2e8ed + 1 + + + + + + 730 + 495 + 14 + 20 + + + 738.5 + 505 + + + + + + + + Second item for addition + 29d1f2a4-5fa6-44f6-af85-bc0232c7f3ed + B + B + true + f08f6ccf-8731-4561-b2d4-cf43dcb5070e + 1 + + + + + + 730 + 515 + 14 + 20 + + + 738.5 + 525 + + + + + + + + Result of addition + 1e474aea-db61-424d-be9a-804e3fd04359 + Result + R + false + 0 + + + + + + 774 + 495 + 17 + 40 + + + 782.5 + 515 + + + + + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 482410b9-724c-46d6-be3a-3d63785bc853 + 1 + 541e5f17-7a28-48ff-8820-37e3076647dd + Group + notch_planes + + + + + + + + + + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a + Boolean Toggle + + + + + Boolean (true/false) toggle + 1f1c2959-2344-4152-a5d6-0e718f54669a + Boolean Toggle + FlipCurve + false + 0 + true + + + + + + 462 + 647 + 115 + 22 + + + + + + + + + + eeafc956-268e-461d-8e73-ee05c6f72c01 + Stream Filter + + + + + Filters a collection of input streams + true + 46298eb6-2834-4153-b072-4cc235a21b8d + Stream Filter + Filter + + + + + + 957 + 655 + 77 + 64 + + + 989 + 687 + + + + + + 3 + 2e3ab970-8545-46bb-836c-1c11e5610bce + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + Index of Gate stream + 376909ae-471f-4338-bd76-0550fd56a4cb + Gate + G + false + 1f1c2959-2344-4152-a5d6-0e718f54669a + 1 + + + + + + 959 + 657 + 15 + 20 + + + 968 + 667 + + + + + + 1 + + + + + 1 + {0} + + + + + 0 + + + + + + + + + + + 2 + Input stream at index 0 + acea90f0-616d-4203-908a-45991c3b043f + false + Stream 0 + 0 + true + 2f78b16d-80d1-4502-954e-49040fe761a3 + 1 + + + + + + 959 + 677 + 15 + 20 + + + 968 + 687 + + + + + + + + 2 + Input stream at index 1 + 282b385d-4abd-455c-848a-9a06b88edbbc + false + Stream 1 + 1 + true + c284b03b-3c25-4136-b35d-211207e5e246 + 1 + + + + + + 959 + 697 + 15 + 20 + + + 968 + 707 + + + + + + + + 2 + Filtered stream + 919e0b0e-50c5-4983-bf15-4bb7b9f39c0a + false + Stream + S(1) + false + 0 + + + + + + 1004 + 657 + 28 + 60 + + + 1018 + 687 + + + + + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 2f78b16d-80d1-4502-954e-49040fe761a3 + 1f1c2959-2344-4152-a5d6-0e718f54669a + 2 + 507139ff-df02-4319-9176-8b2ce4935cf5 + Group + cross_centerline + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 6bbc142b-2536-41bf-a215-ea52704d32b6 + 73ddbf47-b58a-451f-8f84-85c767ffb835 + 912e5dfb-d2a2-463b-a0eb-2610982bf536 + eaf786cc-99fc-4d46-b6b3-cf6b5314011a + 68472b2f-1682-4c03-a570-0bd3dafb7255 + 5 + 917db72d-186f-4f9b-9bae-68106080e5ea + Group + main_centerline + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 654077fb-9447-42ca-a4ed-d6a4dd8d1874 + Number Slider + + false + 0 + + + + + + 1721 + 902 + 205 + 20 + + + 1721.597 + 902.3441 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 40 + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + c18f9e62-d6f5-42c4-a426-10688f24ca61 + fd67193c-c124-4e9b-bf38-3d6a6e085438 + 2 + b916d0a2-94ed-4de0-9991-6f37b3e59ce4 + Group + Ref_Side + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + d5f970b8-28fa-4f28-b038-8f3eed5c682d + c1e88145-a742-40e8-9b8b-a549422a646d + c925fa54-d90e-456f-863a-2f4f0a2857ba + 3 + 280e3dc8-45ed-4759-8d99-df91689dd4d8 + Group + StepShape + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 654077fb-9447-42ca-a4ed-d6a4dd8d1874 + 1 + 98458446-0073-4c25-ac14-bdda3bc5b7ad + Group + Mortise + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;56;56 + + A group of Grasshopper objects + cfb4b1f8-a02e-44ce-9c95-80b1164d7fca + 1 + d22d460d-f45b-4227-b1b7-0d4f280ddbcc + Group + + + + + + + + + + + + + + + + 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 + + + + + \ No newline at end of file From 3c7d17a746f2c856b583e4ff0dfca23009c26073 Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 7 Aug 2024 17:30:10 +0200 Subject: [PATCH 22/63] pull from notch --- src/compas_timber/_fabrication/step_joint.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index 112a72112..b484e9a30 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -104,7 +104,7 @@ def __init__( @property def params_dict(self): - return StepJointNotchParams(self).as_dict() + return StepJointParams(self).as_dict() @property def orientation(self): @@ -198,11 +198,11 @@ def tenon_height(self, tenon_height): raise ValueError("tenonHeight must be less than 1000.0.") self._tenon_height = tenon_height - @property # TODO: how should these be better implemented? + @property def displacement_end(self): return self._calculate_displacement_end(self.strut_height, self.strut_inclination, self.orientation) - @property # TODO: how should these be better implemented? + @property def displacement_heel(self): return self._calculate_displacement_heel(self.heel_depth, self.strut_inclination, self.orientation) From 5021e041d4bf745b82c1cedf4722503b6e3a5ecd Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 7 Aug 2024 18:14:59 +0200 Subject: [PATCH 23/63] planes for step --- src/compas_timber/_fabrication/step_joint.py | 88 +++++++++++--------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index b484e9a30..8ecdd5802 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -75,7 +75,7 @@ def __init__( tenon=False, tenon_width=40.0, tenon_height=40.0, - **kwargs, + **kwargs ): super(StepJoint, self).__init__(**kwargs) self._orientation = None @@ -316,7 +316,7 @@ def _calculate_displacement_heel(heel_depth, strut_inclination, orientation): # Methods ######################################################################## - def apply(self, geometry, beam): + # def apply(self, geometry, beam): """Apply the feature to the beam geometry. Parameters @@ -428,10 +428,12 @@ def planes_from_params_and_beam(self, beam): opp_displacement = -opp_displacement # Negative displacement for the end cut rot_axis = -rot_axis # Negative rotation axis for the end cut + # Get the points at the start of the step and at the end p_ref = ref_side.point_at(self.start_x, 0) - p_opp = opp_side.point_at(self.start_x + opp_displacement, opp_side.ysize) + p_opp = opp_side.point_at(self.start_x + opp_displacement, 0) cutting_plane_ref = Frame(p_ref, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_opp = Frame(p_opp, opp_side.frame.xaxis, opp_side.frame.yaxis) + cutting_plane_opp = Frame(p_opp, ref_side.frame.xaxis, ref_side.frame.yaxis) + if self.strut_inclination < 90: rot_angle_ref = math.radians((180 - self.strut_inclination) / 2) @@ -444,44 +446,48 @@ def planes_from_params_and_beam(self, beam): elif self.step_type == StepShape.DOUBLE: return self._calculate_double_planes(ref_side, rot_axis) - def _calculate_step_planes(self, ref_side, opp_side, rot_axis): + def _calculate_step_planes(self, cutting_plane_ref, cutting_plane_opp): """Calculate cutting planes for a step.""" - # Move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x, 0) - p_end = opp_side.point_at(self.start_x + self.displacement_end, opp_side.ysize) - - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # Calculate step cutting planes angles - if self.strut_inclination > 90: - # Rotate first cutting plane at the start of the notch (large side of the step) - angle_long_side = math.atan( - self.step_depth - / (self.displacement_end - self.step_depth / math.tan(math.radians(self.strut_inclination / 2))) - ) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) - cutting_plane_origin.transform(rot_long_side) - - # Rotate second cutting plane at the end of the notch (short side of the step) - angle_short_side = math.radians(180 - self.strut_inclination / 2) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) - cutting_plane_end.transform(rot_short_side) - else: - # Rotate first cutting plane at the start of the notch (short side of the step) - angle_short_side = math.radians(90 + self.strut_inclination / 2) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) - cutting_plane_origin.transform(rot_short_side) - - # Rotate second cutting plane at the end of the notch (large side of the step) - angle_long_side = math.radians(180) - math.atan( - self.step_depth - / (self.displacement_end - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2))) - ) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) - cutting_plane_end.transform(rot_long_side) - - return [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_end)] + # Calculate rotation angles + angle_opp = math.radians((180 - self.strut_inclination) / 2) + angle_ref = math.radians(180 - self.strut_inclination) - math.radians(self.strut_inclination / (self.displacement_end - self.step_depth / math.tan(angle_opp))) + + # Rotate cutting plane at ref_side + rot_ref = Rotation.from_axis_and_angle(cutting_plane_ref.yaxis, angle_ref, point=cutting_plane_ref.point) + cutting_plane_ref.transform(rot_ref) + # Rotate cutting plane at opp_side + rot_opp = Rotation.from_axis_and_angle(cutting_plane_opp.yaxis, angle_opp, point=cutting_plane_opp.point) + cutting_plane_opp.transform(rot_opp) + + # # Calculate step cutting planes angles + # if self.strut_inclination > 90: + # # Rotate first cutting plane at the start of the notch (large side of the step) + # angle_long_side = math.atan( + # self.step_depth + # / (self.displacement_end - self.step_depth / math.tan(math.radians(self.strut_inclination / 2))) + # ) + # rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) + # cutting_plane_origin.transform(rot_long_side) + + # # Rotate second cutting plane at the end of the notch (short side of the step) + # angle_short_side = math.radians(180 - self.strut_inclination / 2) + # rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) + # cutting_plane_end.transform(rot_short_side) + # else: + # # Rotate first cutting plane at the start of the notch (short side of the step) + # angle_short_side = math.radians(90 + self.strut_inclination / 2) + # rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) + # cutting_plane_origin.transform(rot_short_side) + + # # Rotate second cutting plane at the end of the notch (large side of the step) + # angle_long_side = math.radians(180) - math.atan( + # self.step_depth + # / (self.displacement_end - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2))) + # ) + # rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) + # cutting_plane_end.transform(rot_long_side) + + return [Plane.from_frame(cutting_plane_ref), Plane.from_frame(cutting_plane_opp)] def _calculate_heel_planes(self, ref_side, rot_axis): """Calculate cutting planes for a heel notch.""" From 16dc81b1030cf2c15716ba3e93849a5d62b48fa5 Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 7 Aug 2024 20:04:34 +0200 Subject: [PATCH 24/63] planes for heel --- src/compas_timber/_fabrication/__init__.py | 2 + src/compas_timber/_fabrication/step_joint.py | 135 +- tests/compas_timber/gh/test_step_joint.ghx | 9067 ++++++++++++++++++ 3 files changed, 9141 insertions(+), 63 deletions(-) create mode 100644 tests/compas_timber/gh/test_step_joint.ghx diff --git a/src/compas_timber/_fabrication/__init__.py b/src/compas_timber/_fabrication/__init__.py index c17d800a7..b57f6193c 100644 --- a/src/compas_timber/_fabrication/__init__.py +++ b/src/compas_timber/_fabrication/__init__.py @@ -6,6 +6,7 @@ from .jack_cut import JackRafterCutParams from .step_joint_notch import StepJointNotch from .step_joint_notch import StepJointNotchParams +from .step_joint import StepJoint __all__ = [ @@ -15,4 +16,5 @@ "JackRafterCutParams", "StepJointNotch", "StepJointNotchParams", + "StepJoint", ] diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index 8ecdd5802..047fbf485 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -162,9 +162,9 @@ def step_shape(self): @step_shape.setter # TODO: should this be defined automatically depending on the other parameters? (ie. if heel_depth > 0 and step_depth > 0, then step_shape = "double") def step_shape(self, step_shape): - if step_shape not in [StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL, StepShape.TAPEREDHEEL]: + if step_shape not in [StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL, StepShape.TAPERED_HEEL]: raise ValueError( - "StepShape must be either StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL, or StepShape.TAPEREDHEEL." + "StepShape must be either StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL, or StepShape.TAPERED_HEEL." ) self._step_shape = step_shape @@ -250,7 +250,7 @@ def from_plane_and_beam(cls, plane, beam, step_depth=20.0, heel_depth=0.0, taper # restrain step_depth & heel_depth to beam's height and the maximum possible heel depth for the beam # TODO: should it be restrained? should they be proportional to the beam's dimensions? step_depth = beam.height if step_depth > beam.height else step_depth - max_heel_depth = beam.height / math.tan(math.radians(strut_inclination)) + max_heel_depth = abs(beam.height / math.tan(math.radians(strut_inclination))) heel_depth = max_heel_depth if heel_depth > max_heel_depth else heel_depth # define step_shape @@ -298,8 +298,6 @@ def _define_step_shape(step_depth, heel_depth, tapered_heel): def _calculate_displacement_end(strut_height, strut_inclination, orientation): # Calculates the linear displacement from the origin point to the end of the notch based on the strut_height and strut_inclination. displacement_end = strut_height / math.sin(math.radians(strut_inclination)) - if orientation == OrientationType.END: - displacement_end = -displacement_end # negative displacement for the end cut return displacement_end @staticmethod @@ -316,7 +314,7 @@ def _calculate_displacement_heel(heel_depth, strut_inclination, orientation): # Methods ######################################################################## - # def apply(self, geometry, beam): + def apply(self, geometry, beam): """Apply the feature to the beam geometry. Parameters @@ -343,42 +341,41 @@ def _calculate_displacement_heel(heel_depth, strut_inclination, orientation): cutting_planes = self.planes_from_params_and_beam(beam) except ValueError as e: raise FeatureApplicationError( - None, geometry, f"Failed to generate cutting planes from parameters and beam: {str(e)}" + None, geometry, "Failed to generate cutting planes from parameters and beam: {}".format(str(e)) ) # create notch polyedron from planes # add ref_side plane to create a polyhedron - cutting_planes.append( - Plane.from_frame(beam.ref_sides[self.ref_side_index]) - ) # !: the beam's ref_side Plane might need to be offsetted to create a valid polyhedron when step_type is "double" + # !: the beam's ref_side Plane might need to be offsetted to create a valid polyhedron when step_type is "double" + cutting_planes.append(Plane.from_frame(beam.ref_sides[self.ref_side_index])) try: notch_polyhedron = Polyhedron.from_planes(cutting_planes) except Exception as e: raise FeatureApplicationError( - cutting_planes, geometry, f"Failed to create valid polyhedron from cutting planes: {str(e)}" + cutting_planes, geometry, "Failed to create valid polyhedron from cutting planes: {}".format(str(e)) ) # convert polyhedron to mesh try: notch_mesh = notch_polyhedron.to_mesh() except Exception as e: - raise FeatureApplicationError(notch_polyhedron, geometry, f"Failed to convert polyhedron to mesh: {str(e)}") + raise FeatureApplicationError(notch_polyhedron, geometry, "Failed to convert polyhedron to mesh: {}".format(str(e))) # convert mesh to brep try: notch_brep = Brep.from_mesh(notch_mesh) except Exception as e: - raise FeatureApplicationError(notch_mesh, geometry, f"Failed to convert mesh to Brep: {str(e)}") + raise FeatureApplicationError(notch_mesh, geometry, "Failed to convert mesh to Brep: {}".format(str(e))) # apply boolean difference try: brep_with_notch = Brep.from_boolean_difference(geometry, notch_brep) except Exception as e: - raise FeatureApplicationError(notch_brep, geometry, f"Boolean difference operation failed: {str(e)}") + raise FeatureApplicationError(notch_brep, geometry, "Boolean difference operation failed: {}".format(str(e))) # check if the notch is empty if not brep_with_notch: raise FeatureApplicationError( notch_brep, geometry, "The cutting planes do not create a volume that intersects with beam geometry." ) - if self.tenon: # !: implement tenon - # create tenon volume and subtract from brep_with_notch + if self.mortise: # !: implement mortise + # create mortise volume and subtract from brep_with_notch pass return brep_with_notch @@ -416,6 +413,13 @@ def planes_from_params_and_beam(self, beam): assert self.strut_inclination is not None assert self.step_shape is not None + print("orientation: ", self.orientation) + print("start_x: ", self.start_x) + print("strut_inclination: ", self.strut_inclination) + + displacement_end = self._calculate_displacement_end(beam.height, self.strut_inclination, self.orientation) + displacement_heel = self._calculate_displacement_heel(self.heel_depth, self.strut_inclination, self.orientation) + # Get the reference side as a PlanarSurface for the first cut ref_side = beam.side_as_surface(self.ref_side_index) # Get the opposite side as a PlanarSurface for the second cut and calculate the additional displacement along the xaxis @@ -426,70 +430,75 @@ def planes_from_params_and_beam(self, beam): if self.orientation == OrientationType.END: opp_displacement = -opp_displacement # Negative displacement for the end cut - rot_axis = -rot_axis # Negative rotation axis for the end cut + # rot_axis = -rot_axis # Negative rotation axis for the end cut # Get the points at the start of the step and at the end p_ref = ref_side.point_at(self.start_x, 0) p_opp = opp_side.point_at(self.start_x + opp_displacement, 0) + p_heel = p_ref + Vector.from_start_end(p_ref, p_opp) * displacement_heel + + cutting_plane_ref = Frame(p_ref, ref_side.frame.xaxis, ref_side.frame.yaxis) cutting_plane_opp = Frame(p_opp, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + + # if self.strut_inclination < 90: + # rot_angle_ref = math.radians((180 - self.strut_inclination) / 2) + + if self.step_shape == StepShape.STEP: + return self._calculate_step_planes(cutting_plane_ref, cutting_plane_opp, displacement_end) + elif self.step_shape == StepShape.HEEL: + return self._calculate_heel_planes(cutting_plane_ref, cutting_plane_heel) + # elif self.step_shape == StepShape.TAPERED_HEEL: + # return self._calculate_heel_tapered_planes(ref_side, rot_axis) + # elif self.step_shape == StepShape.DOUBLE: + # return self._calculate_double_planes(ref_side, rot_axis) + else: + pass - if self.strut_inclination < 90: - rot_angle_ref = math.radians((180 - self.strut_inclination) / 2) + def _calculate_step_planes(self, cutting_plane_ref, cutting_plane_opp, displacement_end): + """Calculate cutting planes for a step.""" + if self.strut_inclination > 90: + ini_angle = self.strut_inclination + rot_axis = cutting_plane_ref.yaxis + else: + ini_angle = 180 - self.strut_inclination + rot_axis = -cutting_plane_ref.yaxis - if self.step_type == StepShape.STEP: - return self._calculate_step_planes(ref_side, rot_axis) - elif self.step_type == StepShape.HEEL: - return self._calculate_heel_planes(ref_side, rot_axis) - elif self.step_type == StepShape.TAPERED_HEEL: - return self._calculate_heel_tapered_planes(ref_side, rot_axis) - elif self.step_type == StepShape.DOUBLE: - return self._calculate_double_planes(ref_side, rot_axis) + # Calculate rotation angles + angle_opp = math.radians(ini_angle / 2) + angle_ref = math.radians(ini_angle) + self.step_depth / (displacement_end - self.step_depth / math.tan(angle_opp)) + # Rotate cutting plane at ref_side + rot_ref = Rotation.from_axis_and_angle(rot_axis, angle_ref, point=cutting_plane_ref.point) + cutting_plane_ref.transform(rot_ref) + # Rotate cutting plane at opp_side + rot_opp = Rotation.from_axis_and_angle(rot_axis, angle_opp, point=cutting_plane_opp.point) + cutting_plane_opp.transform(rot_opp) - def _calculate_step_planes(self, cutting_plane_ref, cutting_plane_opp): + return [Plane.from_frame(cutting_plane_ref), Plane.from_frame(cutting_plane_opp)] + + def _calculate_heel_planes(self, cutting_plane_ref, cutting_plane_heel): """Calculate cutting planes for a step.""" - # Calculate rotation angles - angle_opp = math.radians((180 - self.strut_inclination) / 2) - angle_ref = math.radians(180 - self.strut_inclination) - math.radians(self.strut_inclination / (self.displacement_end - self.step_depth / math.tan(angle_opp))) + if self.strut_inclination > 90: + ini_angle = self.strut_inclination + rot_axis = cutting_plane_ref.yaxis + else: + ini_angle = 180 - self.strut_inclination + rot_axis = -cutting_plane_ref.yaxis + # Calculate rotation angles + angle_heel = math.radians(ini_angle) + angle_ref = math.radians(90) # Rotate cutting plane at ref_side - rot_ref = Rotation.from_axis_and_angle(cutting_plane_ref.yaxis, angle_ref, point=cutting_plane_ref.point) + rot_ref = Rotation.from_axis_and_angle(rot_axis, angle_ref, point=cutting_plane_ref.point) cutting_plane_ref.transform(rot_ref) # Rotate cutting plane at opp_side - rot_opp = Rotation.from_axis_and_angle(cutting_plane_opp.yaxis, angle_opp, point=cutting_plane_opp.point) - cutting_plane_opp.transform(rot_opp) + rot_heel = Rotation.from_axis_and_angle(rot_axis, angle_heel, point=cutting_plane_heel.point) + cutting_plane_heel.transform(rot_heel) - # # Calculate step cutting planes angles - # if self.strut_inclination > 90: - # # Rotate first cutting plane at the start of the notch (large side of the step) - # angle_long_side = math.atan( - # self.step_depth - # / (self.displacement_end - self.step_depth / math.tan(math.radians(self.strut_inclination / 2))) - # ) - # rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) - # cutting_plane_origin.transform(rot_long_side) - - # # Rotate second cutting plane at the end of the notch (short side of the step) - # angle_short_side = math.radians(180 - self.strut_inclination / 2) - # rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) - # cutting_plane_end.transform(rot_short_side) - # else: - # # Rotate first cutting plane at the start of the notch (short side of the step) - # angle_short_side = math.radians(90 + self.strut_inclination / 2) - # rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) - # cutting_plane_origin.transform(rot_short_side) - - # # Rotate second cutting plane at the end of the notch (large side of the step) - # angle_long_side = math.radians(180) - math.atan( - # self.step_depth - # / (self.displacement_end - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2))) - # ) - # rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) - # cutting_plane_end.transform(rot_long_side) + return [Plane.from_frame(cutting_plane_ref), Plane.from_frame(cutting_plane_heel)] - return [Plane.from_frame(cutting_plane_ref), Plane.from_frame(cutting_plane_opp)] - def _calculate_heel_planes(self, ref_side, rot_axis): """Calculate cutting planes for a heel notch.""" if self.strut_inclination > 90: # Move the frames to the start and end of the notch to create the cuts diff --git a/tests/compas_timber/gh/test_step_joint.ghx b/tests/compas_timber/gh/test_step_joint.ghx new file mode 100644 index 000000000..db8ae90b3 --- /dev/null +++ b/tests/compas_timber/gh/test_step_joint.ghx @@ -0,0 +1,9067 @@ + + + + + + + + 0 + 2 + 2 + + + + + + + 1 + 0 + 7 + + + + + + fa5d732c-b31a-4064-bc30-fdd75169872b + Shaded + 1 + + 100;150;0;0 + + + 100;0;150;0 + + + + + + 638584536051543966 + + test_step_joint.ghx + + + + + 0 + + + + + + -2212 + -1459 + + 1.06250012 + + + + + 0 + + + + + + + 0 + + + + + 2 + + + + + GhPython, Version=7.34.23267.11001, Culture=neutral, PublicKeyToken=null + 7.34.23267.11001 + + 00000000-0000-0000-0000-000000000000 + + + + + + + Human, Version=1.7.3.0, Culture=neutral, PublicKeyToken=null + 1.7.3.0 + + 5f86fa9f-c62b-50e8-157b-b454ef3e00fa + Human + 1.2.0 + + + + + + + 90 + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + GhPython Script + + + + + from compas_rhino.conversions import surface_to_rhino +from compas_timber._fabrication import StepJointNotch +from compas_timber._fabrication import StepJointNotchParams + +from compas_timber.model import TimberModel +from compas_timber.fabrication import BTLx + +from compas_rhino import unload_modules +from compas.scene import SceneObject + +from compas_rhino.conversions import frame_to_rhino, plane_to_rhino, surface_to_rhino, polyline_to_rhino + +import Rhino.Geometry as rg +unload_modules("compas_timber") + +#define beam and cutting plane +beam = cross_beam +plane = main_beam.ref_sides[index] + +#create step_joint_notch +step_joint_notch = StepJointNotch.from_surface_and_beam(plane, beam, notch_limited=False, step_depth=step, heel_depth=heel, strut_height=100.0, tapered_heel=tapered, ref_side_index=ref_side_index) +cutting_planes = step_joint_notch.planes_from_params_and_beam(beam) + +print("Orientation: ", step_joint_notch.orientation) +print("StartX: ", step_joint_notch.start_x) +print("StrutInclination: ", step_joint_notch.strut_inclination) + + +#add mortise +if mortise_height > 0: + step_joint_notch.add_mortise(beam.width/4, mortise_height, beam) + mortise_polylines = step_joint_notch.mortise_volume_from_params_and_beam(beam) + +##apply geometric features +#step_joint_notch.apply(brep, beam) + +#get btlx params +step_joint_notch_params = StepJointNotchParams(step_joint_notch).as_dict() +btlx_params = [] +for key, value in step_joint_notch_params.items(): + btlx_params.append("{0}: {1}".format(key, value)) + + +#vizualize in rhino +rg_ref_side = frame_to_rhino(beam.ref_sides[ref_side_index]) +rg_cutting_plane = frame_to_rhino(plane) +rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) + +rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_polylines] + + + + GhPython provides a Python script component + + 154 + 194 + + + 1232 + 796 + + true + true + false + false + ca34b946-50be-42d7-8e6d-080419aa1aba + false + true + GhPython Script + Python + + + + + + 1987 + 695 + 222 + 164 + + + 2083 + 777 + + + + + + 8 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 7 + 3ede854e-c753-40eb-84cb-b48008f14fd4 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + true + Script variable Python + 050b78a7-8649-45a7-8e2b-9f2c8fe617b8 + cross_beam + cross_beam + true + 0 + true + c38af54d-dfec-411f-ab38-22c657830b82 + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1989 + 697 + 79 + 20 + + + 2030 + 707 + + + + + + + + true + Script input main_beam. + 85e336ca-36fd-4257-ae26-80a079e9d946 + main_beam + main_beam + true + 0 + true + 19ba4d57-8c61-4bac-90d7-412dfcb8ac0b + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1989 + 717 + 79 + 20 + + + 2030 + 727 + + + + + + + + true + Script input index. + 63012ab7-70b5-4b48-adbc-da1744ff7b8e + index + index + true + 0 + true + c18f9e62-d6f5-42c4-a426-10688f24ca61 + 1 + 48d01794-d3d8-4aef-990e-127168822244 + + + + + + 1989 + 737 + 79 + 20 + + + 2030 + 747 + + + + + + + + true + Script input ref_side_index. + a5edb702-630e-4672-9612-89a6f43ae14c + ref_side_index + ref_side_index + true + 0 + true + fd67193c-c124-4e9b-bf38-3d6a6e085438 + 1 + 48d01794-d3d8-4aef-990e-127168822244 + + + + + + 1989 + 757 + 79 + 20 + + + 2030 + 767 + + + + + + + + true + Script input step. + 7d77a23a-b3b1-4179-80e8-6045dbf22259 + step + step + true + 0 + true + d5f970b8-28fa-4f28-b038-8f3eed5c682d + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1989 + 777 + 79 + 20 + + + 2030 + 787 + + + + + + + + true + Script input heel. + 62418504-ee4a-4be5-84d5-25d7f04b2671 + heel + heel + true + 0 + true + c1e88145-a742-40e8-9b8b-a549422a646d + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1989 + 797 + 79 + 20 + + + 2030 + 807 + + + + + + + + true + Script input tapered. + 1ff95643-005d-47e6-a338-fca927af62e5 + tapered + tapered + true + 0 + true + c925fa54-d90e-456f-863a-2f4f0a2857ba + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1989 + 817 + 79 + 20 + + + 2030 + 827 + + + + + + + + true + Script input mortise_height. + 9355bd0c-5761-49ff-8c36-c610f66d7569 + mortise_height + mortise_height + true + 0 + true + 654077fb-9447-42ca-a4ed-d6a4dd8d1874 + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1989 + 837 + 79 + 20 + + + 2030 + 847 + + + + + + + + The execution information, as output and error streams + 40308c84-a0ba-4c41-b158-38a3341beb2c + out + out + false + 0 + + + + + + 2098 + 697 + 109 + 22 + + + 2152.5 + 708.4286 + + + + + + + + Script output surface. + 43d74bb6-8d1a-4853-8757-d522804215d3 + surface + surface + false + 0 + + + + + + 2098 + 719 + 109 + 23 + + + 2152.5 + 731.2857 + + + + + + + + Script output rg_cutting_plane. + 602ccbf4-1274-4cd9-8349-ba4c5ba030fe + rg_cutting_plane + rg_cutting_plane + false + 0 + + + + + + 2098 + 742 + 109 + 23 + + + 2152.5 + 754.1429 + + + + + + + + Script output rg_planes. + 16bc55f9-a035-4099-834e-e9dc0931b695 + rg_planes + rg_planes + false + 0 + + + + + + 2098 + 765 + 109 + 23 + + + 2152.5 + 777 + + + + + + + + Script output rg_ref_side. + ddc1d959-4491-4f72-b7d5-a74d74b99385 + rg_ref_side + rg_ref_side + false + 0 + + + + + + 2098 + 788 + 109 + 23 + + + 2152.5 + 799.8572 + + + + + + + + Script output rg_mortise_polylines. + 1a4781d4-bfe8-4573-86bb-90d0f0ffc197 + rg_mortise_polylines + rg_mortise_polylines + false + 0 + + + + + + 2098 + 811 + 109 + 23 + + + 2152.5 + 822.7143 + + + + + + + + Script output btlx_params. + d525eccb-5bff-4563-b75e-472cbbdc5901 + btlx_params + btlx_params + false + 0 + + + + + + 2098 + 834 + 109 + 22 + + + 2152.5 + 845.5714 + + + + + + + + + + + + + + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + Curve + + + + + Contains a collection of generic curves + true + 6bbc142b-2536-41bf-a215-ea52704d32b6 + Curve + Crv + false + 0 + + + + + + 521 + 459 + 50 + 24 + + + 546.5278 + 471.428 + + + + + + 1 + + + + + 1 + {0} + + + + + -1 + + Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesYmekbGRmbmeoaGBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS9f/4eDiZgQx+EDEViJl+1TMxdEL99fs/E4MIlP3synU/95eXBfc/ZRBoZlT64Ddn9+0/QPlAqLwAyNzYbxpcrDPvOXz8/18+rHjlAYmi2O741CCH48eNlj7zvu9wBCoe0mqltvDcBAcmBgTY2xy1xs52sgPIJY3vmdz+1zM1wOS4GQYTAAA= + + 00000000-0000-0000-0000-000000000000 + + + + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + CT: Beam + + + + + """Creates a Beam from a LineCurve.""" + +import rhinoscriptsyntax as rs +from compas.scene import Scene +from compas_rhino.conversions import line_to_compas +from compas_rhino.conversions import vector_to_compas +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Error +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning +from Rhino.RhinoDoc import ActiveDoc + +from compas_timber.elements import Beam as CTBeam +from compas_timber.ghpython.rhino_object_name_attributes import update_rhobj_attributes_name + + +class Beam_fromCurve(component): + def RunScript(self, centerline, z_vector, width, height, category, updateRefObj): + # minimum inputs required + if not centerline: + self.AddRuntimeMessage(Warning, "Input parameter 'Centerline' failed to collect data") + if not width: + self.AddRuntimeMessage(Warning, "Input parameter 'Width' failed to collect data") + if not height: + self.AddRuntimeMessage(Warning, "Input parameter 'Height' failed to collect data") + + # reformat unset parameters for consistency + if not z_vector: + z_vector = [None] + if not category: + category = [None] + + beams = [] + blanks = [] + scene = Scene() + + if centerline and height and width: + # check list lengths for consistency + N = len(centerline) + if len(z_vector) not in (0, 1, N): + self.AddRuntimeMessage( + Error, " In 'ZVector' I need either none, one or the same number of inputs as the Crv parameter." + ) + if len(width) not in (1, N): + self.AddRuntimeMessage( + Error, " In 'W' I need either one or the same number of inputs as the Crv parameter." + ) + if len(height) not in (1, N): + self.AddRuntimeMessage( + Error, " In 'H' I need either one or the same number of inputs as the Crv parameter." + ) + if len(category) not in (0, 1, N): + self.AddRuntimeMessage( + Error, " In 'Category' I need either none, one or the same number of inputs as the Crv parameter." + ) + + # duplicate data if None or single value + if len(z_vector) != N: + z_vector = [z_vector[0] for _ in range(N)] + if len(width) != N: + width = [width[0] for _ in range(N)] + if len(height) != N: + height = [height[0] for _ in range(N)] + if len(category) != N: + category = [category[0] for _ in range(N)] + + for line, z, w, h, c in zip(centerline, z_vector, width, height, category): + guid, geometry = self._get_guid_and_geometry(line) + rhino_line = rs.coerceline(geometry) + line = line_to_compas(rhino_line) + + z = vector_to_compas(z) if z else None + 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)) + update_rhobj_attributes_name(guid, "zvector", str(list(beam.frame.zaxis))) + update_rhobj_attributes_name(guid, "category", c) + + beams.append(beam) + scene.add(beam.blank) + + blanks = scene.draw() + + return beams, blanks + + def _get_guid_and_geometry(self, line): + # internalized curves and GH geometry will not have persistent GUIDs, referenced Rhino objects will + # type hint on the input has to be 'ghdoc' for this to work + guid = None + geometry = line + rhino_obj = ActiveDoc.Objects.FindId(line) + if rhino_obj: + guid = line + geometry = rhino_obj.Geometry + return guid, geometry + + Creates a Beam from a LineCurve. + true + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDwAACw8BkvkDpQAAAhRJREFUSEulVLtKA0EUzSfkD3zlrUKMIcHGB6ghIj5QxEoDiiA2gtqGYBEfRRQiSRc7iXU6IWKZwh8Q/AI/wEcRr/cMmWUZ7ya7Whw2uXvvOTNnzqyPiP6Nob7+Q0bBQJ0xLQ64BRP4R6OxZ37Swf4+nRwdKyxmFyg4MEhcvxYH3YCH4+GhwHsqMd6+r9ep1WrRY7OpyGcmp2hvZxcCBXG4F3gQligykIIcIulkknJb26qGnXgW4AF/NBi6AzkIQAxcnl+AjAr5vFXzLMDN8ZFI9JXxfVMuW0RYMZ8D3dZqVs2zADeuRALBD3jbaDQUAZ74D2ib/iQwHI5UudHyFsNYLVaN5JjEGj0F+KUVQXisB+EzyO01CV0F+IUYwY21dZUUXXMCemGdKMBFMYIYgIDktx2wD0FAILBQO7FjBGGJveYE2Id5Jn8AH3g1uRhBHCLI7TUJ2BVCAHLuP9eLVgJc3GS/vyZSaboqldQWq5UKZeczyhYdSyfouCLGzLViJ9cCT1A2sbq03NNv7Ay7joXCLzzTb5JbAqa/mdm5XzfTRCeG1Dk35bcEUQAJchLArpAmkDMOJVI7PAkgrrgbuCM8F5cITbgWQFyHI9F2x29HS0y4EkBcuU/5LZF0Q1cB+I3fHMFP7stJBL3gKHBWLOoIvnGPK78lKAEQQkQjOZZQlnS+pq79lgABfNxw2UycSgPeQL4fsKzVEEQlKEcAAAAASUVORK5CYII= + + false + 61e455d1-1197-42f2-9b19-c9255984e680 + true + true + CT: Beam + Beam + + + + + + 1076 + 480 + 145 + 124 + + + 1167 + 542 + + + + + + 6 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 1 + true + Referenced curve or line, Guid of curve or line in the active Rhino document. + af8ecabe-1f50-44f9-9732-a28f0101da66 + Centerline + Centerline + true + 1 + true + 82ddb61d-d21e-49ce-a61c-851130becb9a + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1078 + 482 + 74 + 20 + + + 1116.5 + 492 + + + + + + + + 1 + true + Vector defining the orientation of the cross-section (corresponds to the height dimension). If None, a default will be taken. + e24b6965-6eb8-44c9-a93f-921178776153 + ZVector + ZVector + true + 1 + true + 0 + 15a50725-e3d3-4075-9f7c-142ba5f40747 + + + + + + 1078 + 502 + 74 + 20 + + + 1116.5 + 512 + + + + + + + + 1 + true + Width of the cross-section (usually the smaller dimension). + 3141853d-6310-4d57-8fcb-4485e2106585 + Width + Width + true + 1 + true + 5cc940ad-f898-479c-8389-be3142764477 + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1078 + 522 + 74 + 20 + + + 1116.5 + 532 + + + + + + + + 1 + true + Height of the cross-section (usually the larger dimension). + 649d67fb-cb07-4166-b6a6-1412ba66780a + Height + Height + true + 1 + true + 5d71a959-57a2-4f08-b6ec-584164b09c95 + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1078 + 542 + 74 + 20 + + + 1116.5 + 552 + + + + + + + + 1 + true + Category of a beam. + e7afb315-d6b9-4da4-8b6f-5c05aa8b1895 + Category + Category + true + 1 + true + 0 + 37261734-eec7-4f50-b6a8-b8d1f3c4396b + + + + + + 1078 + 562 + 74 + 20 + + + 1116.5 + 572 + + + + + + + + true + (optional) If True, the attributes in the referenced object will be updated/overwritten with the new values given here. + b9a3c1d6-5e9a-407b-8be2-e4e66ceda80e + updateRefObj + updateRefObj + true + 0 + true + 0 + d60527f5-b5af-4ef6-8970-5f96fe412559 + + + + + + 1078 + 582 + 74 + 20 + + + 1116.5 + 592 + + + + + + + + Beam object(s). + 19ba4d57-8c61-4bac-90d7-412dfcb8ac0b + Beam + Beam + false + 0 + + + + + + 1182 + 482 + 37 + 60 + + + 1200.5 + 512 + + + + + + + + Shape of the beam's blank. + 7ccf81c5-136d-420f-b550-4a331abbcb49 + Blank + Blank + false + 0 + + + + + + 1182 + 542 + 37 + 60 + + + 1200.5 + 572 + + + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 5d71a959-57a2-4f08-b6ec-584164b09c95 + Number Slider + + false + 0 + + + + + + 853 + 550 + 166 + 20 + + + 853.4926 + 550.1252 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 100 + + + + + + + + + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + Curve + + + + + Contains a collection of generic curves + true + 2f78b16d-80d1-4502-954e-49040fe761a3 + Curve + Crv + false + 0 + + + + + + 472 + 684 + 50 + 24 + + + 497.5087 + 696.4502 + + + + + + 1 + + + + + 1 + {0} + + + + + -1 + + Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesYmekbGRmbmeoaGBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS9f/4eDiZgQx+EDEViJl+1TMxdEL99fs/E4MIlP3synU/95eXBfc/ZRBoZlT64Ddn9+0/QPlAqLwAyFyF5Ln1s/nvOXz8/18+rHjlAYmi2O741CCHCRvP7T9b98DhM5o4EwMaKFjuAHKJ94LooP/1TA0wYW50dQMKAA== + + 00000000-0000-0000-0000-000000000000 + + + + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + CT: Beam + + + + + """Creates a Beam from a LineCurve.""" + +import rhinoscriptsyntax as rs +from compas.scene import Scene +from compas_rhino.conversions import line_to_compas +from compas_rhino.conversions import vector_to_compas +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Error +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning +from Rhino.RhinoDoc import ActiveDoc + +from compas_timber.elements import Beam as CTBeam +from compas_timber.ghpython.rhino_object_name_attributes import update_rhobj_attributes_name + + +class Beam_fromCurve(component): + def RunScript(self, centerline, z_vector, width, height, category, updateRefObj): + # minimum inputs required + if not centerline: + self.AddRuntimeMessage(Warning, "Input parameter 'Centerline' failed to collect data") + if not width: + self.AddRuntimeMessage(Warning, "Input parameter 'Width' failed to collect data") + if not height: + self.AddRuntimeMessage(Warning, "Input parameter 'Height' failed to collect data") + + # reformat unset parameters for consistency + if not z_vector: + z_vector = [None] + if not category: + category = [None] + + beams = [] + blanks = [] + scene = Scene() + + if centerline and height and width: + # check list lengths for consistency + N = len(centerline) + if len(z_vector) not in (0, 1, N): + self.AddRuntimeMessage( + Error, " In 'ZVector' I need either none, one or the same number of inputs as the Crv parameter." + ) + if len(width) not in (1, N): + self.AddRuntimeMessage( + Error, " In 'W' I need either one or the same number of inputs as the Crv parameter." + ) + if len(height) not in (1, N): + self.AddRuntimeMessage( + Error, " In 'H' I need either one or the same number of inputs as the Crv parameter." + ) + if len(category) not in (0, 1, N): + self.AddRuntimeMessage( + Error, " In 'Category' I need either none, one or the same number of inputs as the Crv parameter." + ) + + # duplicate data if None or single value + if len(z_vector) != N: + z_vector = [z_vector[0] for _ in range(N)] + if len(width) != N: + width = [width[0] for _ in range(N)] + if len(height) != N: + height = [height[0] for _ in range(N)] + if len(category) != N: + category = [category[0] for _ in range(N)] + + for line, z, w, h, c in zip(centerline, z_vector, width, height, category): + guid, geometry = self._get_guid_and_geometry(line) + rhino_line = rs.coerceline(geometry) + line = line_to_compas(rhino_line) + + z = vector_to_compas(z) if z else None + 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)) + update_rhobj_attributes_name(guid, "zvector", str(list(beam.frame.zaxis))) + update_rhobj_attributes_name(guid, "category", c) + + beams.append(beam) + scene.add(beam.blank) + + blanks = scene.draw() + + return beams, blanks + + def _get_guid_and_geometry(self, line): + # internalized curves and GH geometry will not have persistent GUIDs, referenced Rhino objects will + # type hint on the input has to be 'ghdoc' for this to work + guid = None + geometry = line + rhino_obj = ActiveDoc.Objects.FindId(line) + if rhino_obj: + guid = line + geometry = rhino_obj.Geometry + return guid, geometry + + Creates a Beam from a LineCurve. + true + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDgAACw4BQL7hQQAAAhRJREFUSEulVLtKA0EUzSfkD3zlrUKMIcHGB6ghIj5QxEoDiiA2gtqGYBEfRRQiSRc7iXU6IWKZwh8Q/AI/wEcRr/cMmWUZ7ya7Whw2uXvvOTNnzqyPiP6Nob7+Q0bBQJ0xLQ64BRP4R6OxZ37Swf4+nRwdKyxmFyg4MEhcvxYH3YCH4+GhwHsqMd6+r9ep1WrRY7OpyGcmp2hvZxcCBXG4F3gQligykIIcIulkknJb26qGnXgW4AF/NBi6AzkIQAxcnl+AjAr5vFXzLMDN8ZFI9JXxfVMuW0RYMZ8D3dZqVs2zADeuRALBD3jbaDQUAZ74D2ib/iQwHI5UudHyFsNYLVaN5JjEGj0F+KUVQXisB+EzyO01CV0F+IUYwY21dZUUXXMCemGdKMBFMYIYgIDktx2wD0FAILBQO7FjBGGJveYE2Id5Jn8AH3g1uRhBHCLI7TUJ2BVCAHLuP9eLVgJc3GS/vyZSaboqldQWq5UKZeczyhYdSyfouCLGzLViJ9cCT1A2sbq03NNv7Ay7joXCLzzTb5JbAqa/mdm5XzfTRCeG1Dk35bcEUQAJchLArpAmkDMOJVI7PAkgrrgbuCM8F5cITbgWQFyHI9F2x29HS0y4EkBcuU/5LZF0Q1cB+I3fHMFP7stJBL3gKHBWLOoIvnGPK78lKAEQQkQjOZZQlnS+pq79lgABfNxw2UycSgPeQL4fsKzVEEQlKEcAAAAASUVORK5CYII= + + false + efe8bffe-0848-4d32-993f-7f43d93bbf01 + true + true + CT: Beam + Beam + + + + + + 1074 + 688 + 145 + 124 + + + 1165 + 750 + + + + + + 6 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 1 + true + Referenced curve or line, Guid of curve or line in the active Rhino document. + 8ca4719b-64bb-4a1e-bd21-47829a6da8fb + Centerline + Centerline + true + 1 + true + 919e0b0e-50c5-4983-bf15-4bb7b9f39c0a + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1076 + 690 + 74 + 20 + + + 1114.5 + 700 + + + + + + + + 1 + true + Vector defining the orientation of the cross-section (corresponds to the height dimension). If None, a default will be taken. + e4854a14-8e9a-421e-a9c2-b070f3e54f84 + ZVector + ZVector + true + 1 + true + 0 + 15a50725-e3d3-4075-9f7c-142ba5f40747 + + + + + + 1076 + 710 + 74 + 20 + + + 1114.5 + 720 + + + + + + + + 1 + true + Width of the cross-section (usually the smaller dimension). + 4941e70a-0c52-4f0e-829c-88c706d76cab + Width + Width + true + 1 + true + f40578da-c08b-4e1d-b010-8b988d3269a2 + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1076 + 730 + 74 + 20 + + + 1114.5 + 740 + + + + + + + + 1 + true + Height of the cross-section (usually the larger dimension). + 86c7e8a6-883d-41bb-8889-46964d8e84b3 + Height + Height + true + 1 + true + f40578da-c08b-4e1d-b010-8b988d3269a2 + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1076 + 750 + 74 + 20 + + + 1114.5 + 760 + + + + + + + + 1 + true + Category of a beam. + f30f6c3e-93b2-40b0-aa59-ae87fdde3fe1 + Category + Category + true + 1 + true + 0 + 37261734-eec7-4f50-b6a8-b8d1f3c4396b + + + + + + 1076 + 770 + 74 + 20 + + + 1114.5 + 780 + + + + + + + + true + (optional) If True, the attributes in the referenced object will be updated/overwritten with the new values given here. + 63f2f6d5-c8f8-446e-af8e-245bc6bd3b84 + updateRefObj + updateRefObj + true + 0 + true + 0 + d60527f5-b5af-4ef6-8970-5f96fe412559 + + + + + + 1076 + 790 + 74 + 20 + + + 1114.5 + 800 + + + + + + + + Beam object(s). + c38af54d-dfec-411f-ab38-22c657830b82 + Beam + Beam + false + 0 + + + + + + 1180 + 690 + 37 + 60 + + + 1198.5 + 720 + + + + + + + + Shape of the beam's blank. + 489d49f6-b379-49e3-8aed-197b4a1468a4 + Blank + Blank + false + 0 + + + + + + 1180 + 750 + 37 + 60 + + + 1198.5 + 780 + + + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + f40578da-c08b-4e1d-b010-8b988d3269a2 + Number Slider + + false + 0 + + + + + + 888 + 745 + 166 + 20 + + + 888.4611 + 745.2539 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 100 + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 5cc940ad-f898-479c-8389-be3142764477 + Number Slider + + false + 0 + + + + + + 853 + 526 + 166 + 20 + + + 853.5906 + 526.7526 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 80 + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + CT: DecomposeBeam + + + + + # flake8: noqa +from compas.geometry import Line +from compas_rhino.conversions import frame_to_rhino_plane +from compas_rhino.conversions import line_to_rhino +from compas_rhino.conversions import point_to_rhino +from compas_rhino.conversions import box_to_rhino +from ghpythonlib.componentbase import executingcomponent as component +from System.Drawing import Color + + +class BeamDecompose(component): + RED = Color.FromArgb(255, 255, 100, 100) + GREEN = Color.FromArgb(200, 50, 220, 100) + BLUE = Color.FromArgb(200, 50, 150, 255) + WHITE = Color.FromArgb(255, 255, 255, 255) + YELLOW = Color.FromArgb(255, 255, 255, 0) + SCREEN_SIZE = 10 + RELATIVE_SIZE = 0 + + def RunScript(self, beam, show_frame, show_faces): + self.show_faces = show_faces if show_faces is not None else False + self.show_frame = show_frame if show_frame is not None else False + self.frames = [] + self.rhino_frames = [] + self.scales = [] + self.faces = [] + self.width = [] + self.height = [] + self.centerline = [] + self.shapes = [] + + for b in beam: + self.frames.append(b.frame) + self.rhino_frames.append(frame_to_rhino_plane(b.frame)) + self.scales.append(b.width + b.height) + self.centerline.append(line_to_rhino(b.centerline)) + self.shapes.append(box_to_rhino(b.shape)) + self.width.append(b.width) + self.height.append(b.height) + self.faces.append(b.faces) + + return self.rhino_frames, self.centerline, self.shapes, self.width, self.height + + def DrawViewportWires(self, arg): + if self.Locked: + return + + for f, s, faces in zip(self.frames, self.scales, self.faces): + if self.show_frame: + self._draw_frame(arg.Display, f, s) + if self.show_faces: + self._draw_faces(arg.Display, faces, s) + + def _draw_frame(self, display, frame, scale): + x = Line.from_point_and_vector(frame.point, frame.xaxis * scale) + y = Line.from_point_and_vector(frame.point, frame.yaxis * scale) + z = Line.from_point_and_vector(frame.point, frame.zaxis * scale) + display.DrawArrow(line_to_rhino(x), self.RED, self.SCREEN_SIZE, self.RELATIVE_SIZE) + display.DrawArrow(line_to_rhino(y), self.GREEN, self.SCREEN_SIZE, self.RELATIVE_SIZE) + display.DrawArrow(line_to_rhino(z), self.BLUE, self.SCREEN_SIZE, self.RELATIVE_SIZE) + + x_loc = x.end + x.vector * scale * 1.1 + y_loc = y.end + y.vector * scale * 1.1 + z_loc = z.end + z.vector * scale * 1.1 + display.Draw2dText("X", self.RED, point_to_rhino(x_loc), True, 16, "Verdana") + display.Draw2dText("Y", self.GREEN, point_to_rhino(y_loc), True, 16, "Verdana") + display.Draw2dText("Z", self.BLUE, point_to_rhino(z_loc), True, 16, "Verdana") + + def _draw_faces(self, display, faces, scale): + for index, face in enumerate(faces): + normal = Line.from_point_and_vector(face.point, face.normal * scale) + text = str(index) + display.Draw2dText(text, self.WHITE, point_to_rhino(face.point), True, 16, "Verdana") + display.DrawArrow(line_to_rhino(normal), self.YELLOW, self.SCREEN_SIZE, self.RELATIVE_SIZE) + + GhPython provides a Python script component + true + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDwAACw8BkvkDpQAAAohJREFUSEuVlj9rFEEYxvMR7gMEdvFy/6NZDGijuQ3aKCKLiqVuIUKwOdBYWIQtkxQWJhAQvFgdEeyuUrggIniFhWAlCPoJ1MoESRyfZ5z3nN2b+7PFj7nbmX2emXfe972bUUoNMfv43RsQuOayFD0/AokDn/NDL0A4Boom2TkbCBRqpXIHo4pv3dbwc3WudIgxNiSplyBaAD+NAYnseQEv+vPV2hfwZ3trS+2224qf+QxzgRi7DBJLnHwDBXsNXoqwy4Plpabqdrtq9f4DvXOIv8bo1cuVz41q7ZjzKQMI+ZawTSJrGpXqDsUYDorfvH5Di4MWCCvFuV/h+aWjF3t7YjxkEIKWEf5ovgdYWDhZq3+AwdHm+oaiwJnTi8cUpLAx0Ib7vZ7q9/vDBpYRRWmgL5kCFKIghWnAEDAUmPMk3hSksDCVARakdiaZYkLl00Qu2hafysB71PlOsWRtTcebl2alYOqis+JjDby7G3do4K+2dfplUxB3sU5jnkbi7cIkQNoAD+JS8+pvGlx8+lZ2oRYa8y8xeiYV9alcooSmVy5dltNGIjyoynsrK3pRJgUDnoAn4Ylcws9evVfN7Z46ey2W7NKtRldl+UTxkxEbIIsA433IeI8LycMnu1I3anZzv4PxXy+CwDnAXWbx7MKyxQZC42HLSWjAzpfavYEF5Ix3RmgU/w2yRXIhXP4hBqNiznCZ1D3AughiUj/kORiEKLcBK5oXfqre+Io1+jKNAX9HQn4XchuwVfA5exPGVKd1kcuAKcxnvHyXmIupDDJ1EbuERjHRgFnEy7SLJw8TDYhpzTor8qIN2DtoIiwuBEw9bWBayMTLHAUNKJT9y0F80HK9ND1q5i+sgg6R/W1M2wAAAABJRU5ErkJggg== + + false + eb9d5147-e225-45a5-a44b-61953c4955eb + true + true + CT: DecomposeBeam + DecomposeBeam + + + + + + 1382 + 621 + 156 + 104 + + + 1462 + 673 + + + + + + 3 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 5 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 1 + true + Beam + 8e04df08-7ba8-497d-81a4-3df28baa4429 + Beam + Beam + true + 1 + true + 19ba4d57-8c61-4bac-90d7-412dfcb8ac0b + c38af54d-dfec-411f-ab38-22c657830b82 + 2 + 35915213-5534-4277-81b8-1bdc9e7383d2 + + + + + + 1384 + 623 + 63 + 33 + + + 1417 + 639.6667 + + + + + + + + true + Script input ShowFrame. + 55d797db-e8b9-4a25-83f9-fb3ce5e97dda + ShowFrame + ShowFrame + true + 0 + true + 4589d281-06a1-48a8-ab9c-fa318fb66883 + 1 + d60527f5-b5af-4ef6-8970-5f96fe412559 + + + + + + 1384 + 656 + 63 + 33 + + + 1417 + 673 + + + + + + + + true + Script input ShowFaces. + 38380630-dd0c-4f25-ba4d-8be79af14f88 + ShowFaces + ShowFaces + true + 0 + true + 4589d281-06a1-48a8-ab9c-fa318fb66883 + 1 + d60527f5-b5af-4ef6-8970-5f96fe412559 + + + + + + 1384 + 689 + 63 + 34 + + + 1417 + 706.3334 + + + + + + + + Script output Frame. + eaa8a578-60ae-4948-8427-7ef3e9061069 + Frame + Frame + false + 0 + + + + + + 1477 + 623 + 59 + 20 + + + 1506.5 + 633 + + + + + + + + Script output Centerline. + d5904a7c-0c03-4764-8d8c-d828062b20dd + Centerline + Centerline + false + 0 + + + + + + 1477 + 643 + 59 + 20 + + + 1506.5 + 653 + + + + + + + + Script output Box. + e778515e-1975-4634-93fa-0a12d895cfb0 + Box + Box + false + 0 + + + + + + 1477 + 663 + 59 + 20 + + + 1506.5 + 673 + + + + + + + + Script output Width. + d3ee9e41-73c8-4e9b-8793-3de14a036ce6 + Width + Width + false + 0 + + + + + + 1477 + 683 + 59 + 20 + + + 1506.5 + 693 + + + + + + + + Script output Height. + 712b50a9-aa16-4a06-850a-5ccaa6c1e4a1 + Height + Height + false + 0 + + + + + + 1477 + 703 + 59 + 20 + + + 1506.5 + 713 + + + + + + + + + + + + + + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a + Boolean Toggle + + + + + Boolean (true/false) toggle + 4589d281-06a1-48a8-ab9c-fa318fb66883 + Boolean Toggle + Toggle + false + 0 + true + + + + + + 1257 + 679 + 104 + 22 + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + c18f9e62-d6f5-42c4-a426-10688f24ca61 + Number Slider + + false + 0 + + + + + + 1762 + 694 + 159 + 20 + + + 1762.14 + 694.5345 + + + + + + 3 + 1 + 1 + 15 + 0 + 0 + 0 + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + fd67193c-c124-4e9b-bf38-3d6a6e085438 + Number Slider + + false + 0 + + + + + + 1722 + 718 + 201 + 20 + + + 1722.123 + 718.8546 + + + + + + 3 + 1 + 1 + 5 + 0 + 0 + 2 + + + + + + + + + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel + + + + + A panel for custom notes and text values + cfb4b1f8-a02e-44ce-9c95-80b1164d7fca + Panel + BTLx Params + false + 0 + d525eccb-5bff-4563-b75e-472cbbdc5901 + 1 + Double click to edit panel content… + + + + + + 2400 + 926 + 212 + 333 + + 0 + 0 + 0 + + 2400.01 + 926.6734 + + + + + + + 255;255;255;255 + + true + true + true + false + false + true + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + d5f970b8-28fa-4f28-b038-8f3eed5c682d + Number Slider + + false + 0 + + + + + + 1727 + 786 + 160 + 20 + + + 1727.211 + 786.5431 + + + + + + 3 + 1 + 1 + 200 + 0 + 0 + 30 + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + c1e88145-a742-40e8-9b8b-a549422a646d + Number Slider + + false + 0 + + + + + + 1727 + 809 + 160 + 20 + + + 1727.21 + 809.6162 + + + + + + 3 + 1 + 1 + 200 + 0 + 0 + 15 + + + + + + + + + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a + Boolean Toggle + + + + + Boolean (true/false) toggle + c925fa54-d90e-456f-863a-2f4f0a2857ba + Boolean Toggle + tapered_heel + false + 0 + false + + + + + + 1752 + 831 + 133 + 22 + + + + + + + + + + f31d8d7a-7536-4ac8-9c96-fde6ecda4d0a + Cluster + + + + + + 7X0HWBRX9/5KlyaKYtfBHiu2qFGT3WWX3hQLGgsLDLC67K5bFBuCimJDFEVEUOzYAEvUYCPFFk1iNLElGmJvUWwJ9v+9s7PIzN4ZdtmFJb//x/Pw5XPuzDD3Pee859x7zr3XTiCLVMfhUtUH8FOHw+FYgV9nuUQdI5aOn4wrlGKZFDaFgMuwGf7Ywlu0z/ngoihcAW+xIZvttU2+Ani5Lrj0yYo2Pjfrneat/qIg94+TU4bYhijwyWJ8Cmy3B+02obHgLVFO5GUvsUSleSdsrBuKS/BIFfiQemR7IK6MHTZVjsM7LMkP0z4bJFPEiSSwpR3xNRlR2qc0r8GjytsyOFENBXi0WCqGLw9RyOS4QiXGldrXwl8rgUhF/B078I+1T2JTuV/9YecgwJWRCrFcRYIDv5JjFSSKw8u/WanC5eN7dY+JrTsU/HGIolKLMPxx0l71lKk12Ftq0QPfMUHTXXjZgrxsM0ykiMGJO1uBf2559OHDiX8/fLAaLZPFwYvWBAwpX1iPAD2l/Km68IrOn6k7NFIeIJoqU6sq3mvvrZCp5To31/+IUnAE/LjyP9AV/DporlGegtdtNNfhCyxIIVl5D/+oFN/PXRA8bYY4sHBDk0GHejthFACtQyQiKV7XUyZVicRSjTLYkm9Bwd+PvFOJibBImYRUGEwWjaliFTjeLUoMNBICLpJgonixsptyKhBRnNLRV6pUiaSRuLdaHKX9sqb7LgQUnU/wPbQh0mXvrHHfIL7MLkgcObHiZcsQidQumPgmjfoRmNqEytSKSJxAHPyO7dVr7M8RR7jp71UFIzacbuWgaaZAB5+z56lUCnGEWqVRxnIl4IMbo4hLbeG/C72FHOx3AYfjw+dwinnWIeLJMuI9raHNBfgKfXb/ISDFUOe/J4buDos3/3n2L96qRasUe8e8/85kYrA4PmFq0ev9/lsaJAx+I1iSaaQYiqEYbjGIQRDqKxy96bZWDBZMYhC27315j7ubd8bN5FHb5ksbUzprJxAr5SJVZCxVEpCrrRkk0Vv7CEAex8QQZEwsBWKRiJUq8P9UMkw1BfwStEJcVHZHSmHcjvUDWlrLPFfeC/kiKKD7E/SH6QiivEVPEDkBpC4nAhA9+FQQOSWBQk7JNYGDXKQQxY0XS+VqglfsSCRteJGRuFJZUXYoTJwDYN9Bz6MJB4PsbmMXi54L3S5zd7VKmHW5+7QcSnet4At0ulonoFKNozOKkRqHAbDCIVghPA4nl6ZxnFzQmvsHBSz4bltS9fQEy6VcgcD/ArSkSLj2zrtadMfiDS/T/tXWbl/aTqHApfMKXehCdKFDYcMxDBugKizYYH8KnENgaAOoR6oCDl6k/dvw11pHKDZ8Beh2LIfEz0LnJnjFKkSkii1noekeM618gc1xOBXCpgiZTIKLpOV/jLijDssdHEeNCIGXJhVey716yrD1RxLQ2Hm0TIENU6hxbLJIosbRtGtz99ic1IOJwjWbxQnnk7t0oYjUhjAhnq4geSYX5LkgUslTUIIMCyaUnIJQHcMRwlAIeYkkSlaIfO+VbIga2cd/Wey3FvfV0Y0QEPF1IeJXC0SErjNCBHSd9DxaRdXxPJuHZ3mu7REdnHOj17PgR91fUjrjRLhZLFStiBZF4vq7HzdPBQ4iZ+Bv5MQLlJoXINGUJdqduRnV1b+wLS949p6v7rB8gK6bIZpDFdF6IpaSIuRw75FuJpHuZhIXgNZHOm5Gy5yojjYgvwyLEAGdITqL7GRM3ZZN1nnJ+RmZA1+uLpqRrk8wg2JHmmOhG6uRjiUTwJMI4QlHKdQfoLX4vlHkib6pcvK0JpCFt/hwKvsp/ULfdiYXiRJ0U4E2diUCqTAsSqzQhLdIeWdc33F6Qt1L/snHWw137jpTRKWIMCxUPA0h8LBKBV5f1LUoo9HfAfvPihJOn4lxNYHAOQ/ZBA7swRwCt/OVgohhsgaIQeWya1dM/pdbUXYWhsluVCWy+6Lwmuts69OBqxqpSzMdN8qoshvFILtR5pBd4t+sxvq4dspOJ66x4TC76MZDcaVaohJLY/TwJVEpvLe7to7krwhZ5HVl+27UaLYmgtCyBSSPJgLRyOlupv9CwqxIx2zFqa2OeYj34lnhbeK8trY+Pv50TtjAanfMpayO+UW1OOZTx5+vvDvxgs+aFn/mTw99G2gix0wPEU1h66Wstv70f45ZH8dc1vbv7fff3PHNDp/y9MzynAG12TE/Z3XML2onuVejYy71HzTzt7NS3pz0Nutmhgditdkxv2Q11n9qp+yqzzGv+tD/Sof+Abz9/pPFWc3e/W5Ox1zK6phfaB2zNae2OubufQc7zZH5eCYPuvPdbW6aqjodcxpQV+wVk2NOAXhi76rFMfdaor7e3q3UM2mhxZ8hn12YZCLHfGnO2PmKV9352171Xxp2v29/I209F8AT/orJ1m+B1tzX/3PM+jjmd9LvYnIe3RBuscrP6Dn0ukstdcxQ4CVv2AQO7KFWkns1OuYNQWm/d2z7Onj/jfVHi35o+7aWOmbCWN+zGuuH2im76nPMBc2Gz4qPTw5e6Harx6in/j+ZyTFzFpI8inTM3IWEWZGO2YbD4JjXrDg/KC/oTfBWxzH9663zekDpS30BHgnUWaVQR6owRF7bikQVhWi3is9qPTSRRBWrlFikLE4ukwJ9wYCUmPKoy6cPdmr+3jlgVfOz2wqOjf+ykm/TwdxWgBMNeuKZGAKU+Q7AczZw23l0PMOHAhJ7aJDbbqgJK0CXoz5+K7Kn+2d5TQiz/sVnh2PRg8TQjZ61c0SdCwAquaPNE9IBwoYQAP3fcdwGEYhjsEIcA2hfLgM6jpRxol/B+z2nZ3nucR03x7ndyblUwtc8rivkYJPTRkgoKcU0FKOXgFbsnm6SkK3vTmHdePFiJTYZEI0MXSNwtN3Y7DXrGgQm+Y3+Kic8fictUiGe1ytSMUHnw++zdT73AbXzFpV1flSlnR/Rs3TKzKElgo2SdNfCpNmvaa6eofMIV28KyT9klfzf1M5bVtb50ZV2/t6NjT3eDd7nlbPr4v4F6r9oid/RDJ0fXT2Sf8wq+Sdab6kldR1vGV/8WG29bb134fm6fZp0aTWY0hlHDd1rLFnXUVoxYNjGM1YkjcGJoiNZBRKBJWAilsHevotNWr95s0e4sVGThY+tnjdm/hYdeOuGSMgmPcGTDyfrtVJQpUbykQSjoFwjk+LY89lHsmfHr76w5BEvOLfBrH+ezLB8T60pgg/rVSxA84f08jgj/WEaQCUcoiJHqRQ2AqjUbWRNERMqTYPwKTo6wAxS6vsL0x/42gs34VcjU0J5/1bZo9BgonsqE8BEOBxGmOgOh1MJ7bhotDuqvMYWCQ992kKfoMoiRGJy5gkPI9UkE/QfoxtPWRiBDsk8dkzMM8I/KM3Pp6HP3vHb/Qs+dadauwNfgcuxGRj8j/7TZ51CZZLJREwOIjay7BSfDOI2JVHSA2sd4QsZgvOAxjeTJ1/dJ1x6K6Jdh0WSRYwfpFtwyueH6TvEWUyk/TQhuQ7vlIFWzhPDeMdLrIClRuC7kL06PMzb8pOldX2OnkjN/yFzJ7U4z4bolX6VXDSToidZjTQpF9DzxEdMo/JhoLX4b4OYxyEUjlCimIHZ2RR/9/bxhMBjqUdbW/RpMR4BjH71WzRg6JPcJgAGqAQLMNxSXa4xsALO1beiyUSqFZMZit6y0wvD+6U89ll5aXMrP9dew6igeRIP6oLmaXIGki8h1QVZ9LZvCaEuxtYFUlEh3BcalTGfnzhed/lC3vx3eZuf2R6nUplNCPFgTcyfQFQIXWFEBegKyct1/zO8XHhTrZp99xe/r4Zs/enSrJ8yqoWX7SDDPGPiZc4SIplnSl6+em6cxCNlJi/n7tIevUPHTDMVL5uYfposZktBj1msU5JhNC8n3/3Bjv9jz8CUT3tcPnPlRWtT8TJ9jtMEwDDndyEwxf/UHC97/9vi7b2QcUHZpRPtmqU2EpmLl+OXkOqCZKDDSwh1qTFeflC6+mjb5ScDvo45tXPYKtc0c/Fy/BJSVxhRAbpC8rL9f4aX49tl7JUuCPJfM9035OY89dlqi5cTy9jiZe4bk/JyY6stzcfHewnS/17R8VmKUl2L4+XiMrawkPPatLz8oKjJrol3W3tv8hn+ZMLhSZ1rKS+7aFSCBZjEtzXHy2ea/NFwQuI+3lqHcyu8+0w7Ys54mVAXxsgQqEuN8fKlbM6ovfE9fA5keGev/jXP1ZzxMqErjKgAXSF52YGJl1feGiudXHSOvzp587f4/fwfqHU4Q9USPIq5EIhpCrVdeSGQUlsog6um4LiUIGWNwqFZmdebX9C/bwpvccpnF/8677ab5XN004uwWf+qoE5p5FwGcrkmtszguQwHDTcT3UOXh/i0aOgSnxtUWG/xghEp3tQ8tS1hTlVjZ/q41kgS6p/GNpsxIw05m2HHgowjyc7M0My7IUm99ruzT2bs9pndXgWOR0FTJX6+sj7v6slr2UEbj6/3aT40+hTZXIdsXtzw+ryBllb8bU37OGf9068BCjkLw5Bjnu6AyKGmO9imVltqdJ5uRzxMBL0dEss32LvXLY8+CNpxenmfm7eHz6FiyWQ9dUJNTlCZy0g9SkRNtF7WWBhJUI5MBJV89lqzoMI7Xkf8ZJL0YwnvqJtUEI7bT0bP71izkFNreDugJqk6LgJXwBl9IkrEVLIYXBWLK2x8xFFR+Mc1rkiMk065h8dP8Rbkpt6J/DRzoSvDV+mgbAWv6jtGSwcAwQxZLoqgUlYAMyzVIai6HIOcnpO279gE8GHIrhbJy+63nSoPSrv5eqgtNq65baBILhdLYyq+mjqVT7yzKsZK11wjeawTAJALAbyH0r976QSAxgZTjlCiuCbyRMcLERvtQ6Y8n+CThU07dm/NpKCqgmVsedJKEg1kuCAArYlPjA6i2npKZEqARrREFEOMwnBRZCymKC/mYozPXU7cSNiWeo2/Id/u2YfgzSdpoSbx1poINSFKxaVsKHGeaTnLiYmzXnSY+AQru+hTeDx5QWpZCjUXYx8ql4hViLEuG2m10DwkkwIXAMllilgVC1yADBJWd70YSzmu5xnVbzmCxFM5fZOfvvmN6Zt0QLYm2vTduiADqNFDpgFvk1WEChoSVBEEBflJCb8CPX9d555/w9v7+QtCZSs39gj7i5abRvVJDy4KuRC+N7RLc+4K7AoXa3rGw0gu8gDAFDOuUlWBVo5hGSInT7WKMCllrEiODqrmhjh7rHQV844+b2EZMKHBMfrwDW5ooY9N1aFBQyc0E0BDEBMjNHRiqgJNOxN6FK0QxcB9vdBEPejrFy3nd/nSJym/YNmv2aeGU/HSVKTq4jXU5ByUtorUlRSU3/pVY0QkBzkzcVDtjJs6PT05d0uwIPhY67nb87nXe1VH3OSymSBphrgpcQsAV3cdpr1h6qRH3NT1+h5B9/o9vWfHyTo9/PEbVTXGTb97XB99fjzfa3e79SWLrqSepA1yvoztHDTs6N3ANJ/v4lxPPJ9s5CAHA/hynzGFVSWbCXyrP6yaFXrlsPpUhNcczu+vFsUP2G2msKpsC4kGMmAYtBWw13PzhVXX/C6lOrkneK78krNlndUcP3OFVWUaq2NBiVOeQ6jHRGmyhmuX5Xvk+x3tE6h4OfzuVV1Hr/82bwMYt3nTWHYnogMixVRsqDAEAA2chgjeofwEiXNr7k8ujZzsBCsL3LKzLk24rkcMorlamWnTtdxIX1u6leRG1EZvnIN5hLaScnBhkkNtDG+XXikTJV3vIkjb3vvnbW5pA6snvJWvA2r8gCm8jc0ldrMwcXj7tXLt2c8/z+Cm3/llU9m1pW1NE96aOIZLXMe2Ccv363Q2JTJBeJv6aczidkljvfOXqC9NWff0uKnCW7o3NQE0zHucQGiKH9dIeHur4+GDAV7zvfY0Oz1fOqDZDXOFtyW5pK4gw9t26wm4SA6qz8RBqudXR7kfHuc3e/ZwqzfT7udTA0li/zK4KkWXguDb6sGC5WACa19pFB5fsRModNsPxUHH8MlERkOOR4qjxZHEhpQAcVkcptmREp3QeDe55d+RnZsLDrlFzAo6nbiN4Tt1vQO8qieg9wBkHG0mQ2d9c9kG0FoqcAqBGoYDm9AuA7IhCdneFxokRZrw15a47BulNYtRUadS6xyrKyh63DjQqYvrOW27NsqUf/1Zd2HyIa85khb1b4scftK2W5DtC35feQYX7ffM5o20OPzl034OGgHomJUdKRjGP6z5XqI78BYDd9CsS1S6Q4Eh5XWzg1XwN5m23quen6vXKWwEdX+GKm+eSTc9IymlbD2ZukJGUz4bCAuqABMBD4d5YZw9VDZMDE0BCUqQKilsRfgkv2Pdel1a3UjlQA21CRPSRUVs+iByPcGVLN0Gem6O9W42mjExpUsf0deuWGJcqDxSIZJr0IeRALG7bQTRc6Qw1k52dG72zyphFt75xOJbd2OoGgrfpSuLkdUiC+5TVhV8ZhZZ6OxRSvJMOV+wRR2OhCGIVNh0cceZSPTpY2nbQLVKFCHBy3G10mHxmjAN+UaSEeBSUJ21x5s2EqZB+tQGHAaf2tphdZs7a2Z471rwr/rHPpOsqTG0l0Qsx4hMrP4lWl2Ih0SaxC+mVsKATgRGWWTvsRiALK5pRbvPV2P37yz8osBz8ZCj/Y407xnO9Em6/hO26bsgZClZPTsXFdWHpxlcPeukSVjDbZrBV6DzHXnLdvZsOpWfVLYn0SnafSOVUtF9Qo3LaY6GXklppKORLyULIpFLhQ4uNbh+tmEwQvRIgCbdWzNzktcOweq5ZQrh3mxqhbE1vBMBkHelwb2JSyUgQERtJCNAqDpa1iWcUHHlOFuxBL2moaq6YyTnXE5jq5ZtsQxdLcvWdwcNXTBv0uEya7Gkq9SCf+j8y6D281KogQgweVGMbse9qqXjzAWxsOMfC2Jd/0Nke/7BtOMe1x5wV1wcNOzlzLhL1UK2KcAmSl4xkW0KgDb3ranJ9svSHyJwd3efxe8PdXf7/HyxiciWXh5p7J5rABnsNROXlIDW8DfVRLbXPyw9fuc3tdeyZ5vebDxw5ErtJFsIEFAOFoBK3pmcbOkVYmYiW84yUjnQdQnLCOUwLdm6iRLadkm4FLDZ6c/lHkfvLzcP2cKOE0Jn7HhJ+a46DZnI9ojt1ZE7Ho3l5cwr+PSS7WeHqWWlQZqMZqgEqLmCyrds4zYMPIcrxJGYkniQyMtA0pWwbrwfNsJjedqBq16r3D+XjW2R1pflU3Tg5ZgeXRVwVtCHCzx10VXYqoRZl58LbDSfAy/bat8jEMeIVeWTLfDXwRvgA88ikIimVjRuysZQ2ouWgaJ4Lb6aHzHXMlAspV6DJ2dJRbqnOVmPgPjSnyeVoBGTEqxM3fBLxs+f+h7rs+gp/1JPajmEi2f55kUCWZzIkA0jepVXO0tJhYgi3qCZHIQVz9rreLxKgccx1T6PX53b6UynGwErtzTb82OOeAD79+kuSwHX9ZX6XDILpEIuS5mLzJCz7azUMlQlUqg0ag9zaFQgkL2d03u9q7Lr14IDXXf7HVrTkVqP5KjpI6aEr61KRXRzazu3Y406BRel1f/l2FuHgcYuy5hLJnnRyzLmEmkzM888VTQbQ/Y4bC6URhkkuYgz9U4O5ztzZ193GLJZIHlEDRNJyeFSRF658rQUnR1NIDci7cwoN455trFFyq30C4OClzZBVLrR1oZP580kqsOn89FTWPQdCal5IAZ2qeNr+jmrZNKoQlB5oE3JhOhIUnfjMJA616XfxqG3T/kXfnDJ47ZPTaGe7RWEx4hUYtQgypIBVMxTFgew12z/IyUf12z9Q9gImrpdVvELnCceDEoqPhzewDK3F/ordCkbtOg7bEoi9kfXUHYJXZPlswkGMmi1CjFBrekUsk8rAn7p22DswsDdAWendpyef4Ya+BIeWFdL4mvawjOTSFfG5SEKGn5N0i2/qcysHDXzxSzI0J2LfonUqSY3oHuzyb7LUX1vN6diMUdjJgOKvpAuW7fsauDGcUWW1yKmrqEmKENlwK3DrJeuBTH5kxbEM+QpfRU8ykR8KkPgs2fdy6+6TxD77pFPc7o0LdSS4RN0px1gk76eYDVZZIqs3LDLAkg+NSxHCv8SPUd6dMviz/PeFfh9VTxo2KlDygb0HCk9lYnKgcL36uRA6S8uv4HpzcYlSRsFkMJTAoRheoOQHtqbpHe0/PHD554rXdueuXhtCHXTKyt/8JiuKfhXyhL0UlxjTxsE0k9kLDGOAq3FT+gJUwMh61Y+2aJVfM0gkCjugSqsnAriBoVMKlMrJVORUB6cG/t20JvvPA/87ljw3dBRo6heZITmdXotEKRPxFQDnJynbHACv86Ud9MTTwdo3HgUs+LJG6xMyPr+O97XBXuehI8coKyi4hnJwYlZpGYhpye+zyI0qyIUdQyHonHoR9XRahWI93hIWIIjPR5nXL7FX7ot7XThSzyu6kpkCmg4jCliCA23fClNE85/vM5nn71HfEJMjNeGie4OGf/+crA66nyK15Brk3JRdT4hOUTA/H+2zsfAlY2V1Pk0f7lk4nv+He+sgsKTfgfuhFSxzsdhKA6PmMcpA0ca+dIt0kjyPbeGNCsrPup02GzCrExY/PN2afrZd0/Ffkff747/fHPPPDMV/8BuJz5n63axeU4TqtHin5Wnd3x76rqbb5Jlar8ZfnObmaf4h1DBf1hV8N//i8U/9MrgWlL8cyqHZARk8c/bnIoTKU05DI7W7cYSQYPPfvZc3/fmsYJTUwSI3VmpTlbLx/U91UqVLA4BKMde00RJYTiT1+ipDZRE+jEu0lDFKnC8W5T2hA8QdYvixcpuyqlKeEw8Unhje/Ua+3PEEW76e1XBiA2nW+mz/axliERaZXGVEwR93otHnhMvJ9ZgkIJp9v+tYCyOT5ha9Hq//5YGCYPfCJZkmk0wiTxyV22KYJpXSTCI5Ug1IBc9lpPQ1zVXcTmJkVAXwxPkTtChbsEE9fvkPZtcu1vx949pkrVFHrgZNeNGBdu++sGuLxBHR+MKHABMLsVDAs4dGT327LZGwQv7O50c5+5X9WUXtLDyWOPHzQaum8nPu6b+U+a/OUPPsJJJIh4FQk74B7pEWjJJxL7lpq0p7tOCZ59t1jUo6BQ9fpZFI2YMmRKo/csTqBLwXIXdbgCZyNQxsXA0hmumoyh7lqFHYm0vfXbym3mtBUdVb4fferZngO6X6Q7C4FV9ne18Ieeci5Bh26hTQK89GgjpE/EGziU5h1a+N5tVUdOwutHZvknht7f2SimjLiwxZG82mlq1ahU5qMfuVfyVfefve3dy3zgjRyuXAVylEC70ZvMArrT6Qnq6ki1gdoSyIova0Mg4/LO/309Hz3gteZcRu5FTcJm6/5Gm/whoTH+yC+w7UAaWvp9rIKzJMLm8eoMXNQHw3EcV0CwELv+3HcRYSHZF22niYqg4Tq5zcdhUOV7xou1QPEItlkRprxF+fCgeLSb+IhTu9Ib53n9Nm2tYvrPCmWCEDpAsgVaCwIZneq/wH+Q979wfv07qd6mVHiyAHN8bpwHcBRW0XyfFOWMBoR8k1bbiMFCt2c/qPPHt4Uf9ZWt8Ds4YMzZ6lWhHdZ7VGbtQyClxFTKsZQtfBFob6dCrKc7qXH2npM7Z/Kue85xdLtw8+dcjfaJQPY78arrvQkDR+QTfQxsiXfbOGveNkXQaD+DhNBQyzLwfBq25DWuUUrThxH/urE7e3t//fRjgGHjwcZLU8cDW9qY6q3PhX2XW9fMwwYbhA9Vdsrs9NIHAuY3YBA7soVae91iNZ3X+9KLpd+u2D/ZZNysrmVfnyBZTndVZDbJLdGOTHda4dsqu+s7qnNZgTvPre/cIdsYV/Ij7r5lTVZo1NjRbRPIo8qxOuDs+MCvSMbfmMDjmWlRVHCF19ngtlXK3b3tQ2HHfL7vMW1VcnAjCeqj4qKriKycThdOXuwn/W1XFGJMS1Paq4n2jLdN4D5v7LJnfzfP0mz192b/PmKpiORjupEFXlYUaGd8Dw51SN+TI2IRVxeHbstLuOTnz1i/5KvvRI5utDQg1AaotjJcrwMi7wtstusWbtuaYboPG7hsC4CyEcJaivIcAwBniZhbvYY6a4zNzb9UfftLSP7d7Zuv9QSX3TVlzXA1yK3Vjk1uaeby+OWuO6ZGVuWqOwxeQRoWsOV6xgBAdSfnunKpM/JNTbzU+9a/PnCF9bq+6z3NgzIDNJedQKXPNbZjw9mix+3V2t0aBOwa1WfA0f4w71YWFykAcgX2ci9ffxXYFRgjCK1jYoyReEvVxQl8mLT9ABk4+MzjXEzfxsNLJ8z2X/j0zMf39xMPsX4bYeww26ssrO8F44T1TBWv8LiFHzjF64vnj8TKw18g+Rxd+MecN9rP/OofXY79+miGkTq9qds+r0ikG9D30jD3mFOAV/p6pSPFX0Jr7gfEUAz3xqlfh2BlGwM6fynRo/Ftn/90vuz7r9qLpExRgVTrboN6hopu8BRL/OWtm1P1nfIm7CQADKsQCmEsdobEblemZNKMnt8y1V1npLlKJkHuV9cknECP5qy0Tf9XifSvpKmSyfStTu3xy+8Enw3nrf1m6aMzOVslG6mbYdhDuQt1E7VuZ32GHcESrOlo5tGOSQ+8rPvsfXFMHzQlYKuuXfrM+tUbTUyTHfGQSuutmWyzUGj4jkkiIeQ+RAouFj2PELskQJLTPKLbqvHzZ7qv+X6tanljc/Us+w1fojsRAk76uYiFAqz5AawZysdAiZI6SdbGQtq4gUoTeYaH4XI6iw8TdQQd23a2zJu/FMtNsUklP4hhLcAAWeX0y+NNZL3NuIQGLQeGwA5CJHGc5duxQhxkR/v/KuEdLW3jHnFy+tXqrLRgXCi0iO56I6ninxRXTUO2ZrIfb4Zll4ocgn0OhmyzGjN9uS+mK68cdVrFAOBkolxgQiHVAbv+KRajBqAdSG7EPLEMI9mrKgoCQnK3Bm2a1cdjZZ9GGSj9LN0VF3ASaDTgPsQSmdZGBmDxVyOE2NMi69KjaGVTakX//cETA+gf98+P3zaxq1Q7NvujaaaR9pcCD2FyZAoizoDXXVSfXb2iEStkXFj3MkY7OG3x/fW5wUfY763XfZ1MrQGw1G8PqNc6hV3LTJ69NgBeXMZkH8SppaHTApc/OsOd6fv6wc9K94FxO+7r45pPx5oq27qWSCoSMtrouJeAieaoDE0/9V1aMDFzm30bp3oy3vUN0cMrZM40ZvtOoFSPnlgs5iVDDkCtGwlcQ+an/rRghMWdfMXI268SBAfXSubMnHMmV3z1G24fbpCtGLr6/cvvZ3SS/RQMsepc+H5NgJM+ULCfKAhjK9QvTiQyzCVeM2A7Z3y8xIY67d0SxZOZuCfVAnppbMQK7XdKIrduJZp9Ar/4VI/tF14Ie1+8j3Nbh/UPb+9/WM8+KESgLrDGbLIrNMylezStGeu09mpmwea/f5sfd109odMKllqwYubyCZATkihHHlYRpkI62I4fB0daCnQP63/3khyZ4U5/8jut+Fne8OI3hE4zaOSAEntrpyhTu+ywjkPzfzgEksgbsHBA5sltMRA/M65h7hFV+mR3PNDsH0GNZI30n3P03nHFMsymNKCMx+84BeRteXPBrwRPkW831Kpq4mnp6olE7B1QDnLmMQx4IJwhFqnvngCdLHo+6nfDcd7FdD68i9bi4KiqesUUyy0jNQi6Pt1pOaFZN7hxAjzrNt3NA8TJSSxih4ZZXZ3XiMLinEf5BaX4+DX32jt/uX/CpO3V8pZnQnIE4poitaLpTqEwChnriigfFg2geDKuJMi1twpDBWR3nHn7XddFrvyMRD5sVzdvxmPGDdKd++fwwfSvOM4j6AgZv1X+VkFPaxKDJKfuPKUFkrx5eXnbt2+xjQcljNi8/OzDga+rcAdGrKmUDTTzXEgJwKWzMRDwrQGtIE525KdYp8QqpPyQwifiv7t67ngasnnI7b/hYwTYEMFXK+tFDShMAA1SCBZi0pkZPQrn6VjQZlgqFcdYPR19xWuFT2OXF+fMJyzdVd4UCEyqFq0h1QTLQi1WEuhh7qh8VFblMzDQ/V7j64rfq9imBiTcyR6Vs4YRSUQkhHqyJqtnCVaSuMKICdIXk5U+YeNnz4Ppzg+e3DMwY8xV/YN/nHSmdcQZcLIXllqgqGkjNNgxYttQ+CNcKTlJrajiiWRcK3j0Syd30zbWg3d1+m9R5d04/tg/RQdeWbNcTuYOZJC3DXc/z6LR8cLWQ49HM2OKN+ppPJdJ0mo9DdrunZ96gkos3eStXfPso16Vpb1MtHKSbrpG8dCqTtEDk4jm4nRuCsNnmTT6DuiSWqsWqqVAzwNAE/Dmsk8dguUwphjd2xXoOBmjFAN2Z2hXrNRjqjkilVuDo1Hns4noeEx9+yc35fmRn1eSHH6gliR//mD4bpxu9A0QmaZiMYAHDrCVTXfR1IWwya1deBB2pOQBVIpPJyal1iYTNkaw40rLYd+ePPhua9P0x8Od5B2jbtsO31cQCQCgYYNksgjnXrJbMe1UQi2VlYuGrJTE4Fi2KVMk0KxRIwiESczhLpinvYc7xNq1n++4dkv6Dnzjvb9qhvfC1NZEQh2KRN2cTi0sLc9tLlWt025NHJ2sFQRTmVhAQi9W8t/lj56Ob3wi33XcZt3L4tW/MdNiBR1bF6IvuLVVZhE2RcUZnDkOcga9p9VvQ9ovBqyQ/PR/Lb0k9YKV+eZkUuTzUgKofHklKZNGPdhmyNuNHrcqK0P4dPCoGZw1H7jRcsez5waX8PVl1EnoGpHxayffqljFob9E38F8DXGhTpiIhbrYpQpJ65Z/NonQx340fKvb7lbt66L/ipMFdt1OVTghwQ8QjwkrjEbouGztzBeCSN2UqHspdQ8Bl7Dip+celdOWKw7rMfcu0oiEnDuziL3/mJhp2PjWCOmfDqCt1Qk1uswezSXyQNUZl2RVttguTzSafvdYsqPCO1xE/mST9WMI76nw+MWaG5KZ/ZVFreLtmDRVcAVdeJqmSxeCwqAgJau+ygHjB68YBWy79+OST+F/XMnyFblYBXtXX+tYKOVgjMjOvM09TvI7IwNCtz94wbXLS9hWbIGNYVyOdd8R+9vy1vKQ0Xn/+rUu4baBILgfqR/FaVB8N32mKeQuyWZuGoGsySh0tDDDXteS66XuoUhJMg6+x5upIeloCE/QZ1bQy2KpiaaRx9s8l0UAO3GfkEolyY6cz2mp2EcGiJaIYIijERZGxZJE3ZDTGmbJpYU3GfOXt77s65rOyPtjJU7QRKfHWmgg7IErEem1GlLDGwv8H + + Contains a cluster of Grasshopper components + true + a5869824-14fc-400f-a69e-f5050392632f + Cluster + Cluster + false + + + + + 5 + 3f3af581-d64e-4909-91e4-3576cf2e9e86 + 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 + d28e1b27-74ad-41ab-b017-540fa9111876 + ebd18bdb-78c3-4d40-8a69-e1af30a53c27 + fd03a9d4-272c-4590-9595-4d3107dc0fdc + 3c631e1e-b12f-4297-9535-87b4fdc7b45e + 5d665740-a9cc-4f15-8a38-0dc75e214ae2 + 5d32325d-62cf-40bd-93fe-74af56a2c91e + b360d350-2b53-401b-9420-d9402019cb30 + 796ac502-faba-4bb6-a612-7e3dfb448d98 + + + + + + 2381 + 756 + 72 + 84 + + + 2420 + 798 + + + + + + 4 + 919e146f-30ae-4aae-be34-4d72f555e7da + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + 1 + 919e146f-30ae-4aae-be34-4d72f555e7da + + + + + Brep to split + ebd18bdb-78c3-4d40-8a69-e1af30a53c27 + Brep + B + true + 489d49f6-b379-49e3-8aed-197b4a1468a4 + 1 + + + + + + 2383 + 758 + 22 + 20 + + + 2395.5 + 768 + + + + + + + + Contains a collection of three-dimensional axis-systems + d28e1b27-74ad-41ab-b017-540fa9111876 + Plane + Pln + true + 16bc55f9-a035-4099-834e-e9dc0931b695 + 1 + + + + + + 2383 + 778 + 22 + 20 + + + 2395.5 + 788 + + + + + + + + Contains a collection of three-dimensional axis-systems + fd03a9d4-272c-4590-9595-4d3107dc0fdc + Plane + Pln + true + ddc1d959-4491-4f72-b7d5-a74d74b99385 + 1 + + + + + + 2383 + 798 + 22 + 20 + + + 2395.5 + 808 + + + + + + + + 1 + Section curves + 3f3af581-d64e-4909-91e4-3576cf2e9e86 + Curves + C + true + 1a4781d4-bfe8-4573-86bb-90d0f0ffc197 + 1 + + + + + + 2383 + 818 + 22 + 20 + + + 2395.5 + 828 + + + + + + + + 1 + Joined Breps + 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 + Breps + B + false + 0 + + + + + + 2435 + 758 + 16 + 80 + + + 2443 + 798 + + + + + + + + + + + + + + 537b0419-bbc2-4ff4-bf08-afe526367b2c + Custom Preview + + + + + Allows for customized geometry previews + true + true + 9e4f0b21-759b-4c9a-8dfb-6654484027c5 + Custom Preview + Preview + + + + + + + 2569 + 841 + 48 + 44 + + + 2603 + 863 + + + + + + Geometry to preview + true + bc0fc1fe-a18e-40ca-ab8a-8ba765dde1f9 + Geometry + G + false + 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 + 1 + + + + + + 2571 + 843 + 17 + 20 + + + 2581 + 853 + + + + + + + + The material override + 8cbb31fa-4051-471d-87be-5ce455d7f325 + Material + M + false + 0 + + + + + + 2571 + 863 + 17 + 20 + + + 2581 + 873 + + + + + + 1 + + + + + 1 + {0} + + + + + + 255;221;160;221 + + + 255;66;48;66 + + 0.5 + + 255;255;255;255 + + 0 + + + + + + + + + + + + + + + 0148a65d-6f42-414a-9db7-9a9b2eb78437 + Brep Edges + + + + + Extract the edge curves of a brep. + true + be8a15e2-8773-4720-9d61-dfdc10761646 + Brep Edges + Edges + + + + + + 2469 + 766 + 72 + 64 + + + 2499 + 798 + + + + + + Base Brep + 0056a757-f1c7-4722-83e9-e00c5e697cfc + Brep + B + false + 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 + 1 + + + + + + 2471 + 768 + 13 + 60 + + + 2479 + 798 + + + + + + + + 1 + Naked edge curves + 2fc3daa1-27be-494a-9118-8afa0df8edc7 + Naked + En + false + 0 + + + + + + 2514 + 768 + 25 + 20 + + + 2526.5 + 778 + + + + + + + + 1 + Interior edge curves + 7db965ba-79d8-42a1-926c-cc7c5ea6a716 + Interior + Ei + false + 0 + + + + + + 2514 + 788 + 25 + 20 + + + 2526.5 + 798 + + + + + + + + 1 + Non-Manifold edge curves + dfe49992-1aa8-4a4e-bf86-f66139ca06c5 + Non-Manifold + Em + false + 0 + + + + + + 2514 + 808 + 25 + 20 + + + 2526.5 + 818 + + + + + + + + + + + + 22990b1f-9be6-477c-ad89-f775cd347105 + Flip Curve + + + + + Flip a curve using an optional guide curve. + true + 0c07a9ce-439d-466f-98d1-fcc3dcd14023 + Flip Curve + Flip + + + + + + 876 + 695 + 66 + 44 + + + 908 + 717 + + + + + + Curve to flip + 082a270f-f37d-4c27-8266-d4348e1e059b + Curve + C + false + 2f78b16d-80d1-4502-954e-49040fe761a3 + 1 + + + + + + 878 + 697 + 15 + 20 + + + 887 + 707 + + + + + + + + Optional guide curve + 94c738fb-f46a-4531-af70-ea11c88263f2 + Guide + G + true + 0 + + + + + + 878 + 717 + 15 + 20 + + + 887 + 727 + + + + + + + + Flipped curve + c284b03b-3c25-4136-b35d-211207e5e246 + Curve + C + false + 0 + + + + + + 923 + 697 + 17 + 20 + + + 931.5 + 707 + + + + + + + + Flip action + a48ad8ba-3072-486f-9f31-f1cbfcaa31e2 + Flag + F + false + 0 + + + + + + 923 + 717 + 17 + 20 + + + 931.5 + 727 + + + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + 482410b9-724c-46d6-be3a-3d63785bc853 + Plane + Pln + false + 16bc55f9-a035-4099-834e-e9dc0931b695 + 1 + + + + + + 2566 + 703 + 50 + 24 + + + 2591.305 + 715.5306 + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + 9c473eb6-ff8f-4c7f-bd69-56527d0257f6 + Plane + Pln + false + ddc1d959-4491-4f72-b7d5-a74d74b99385 + 1 + + + + + + 2565 + 632 + 50 + 24 + + + 2590.664 + 644.5862 + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + 88b4a335-192d-48a1-81d5-163278eaecbe + Plane + Pln + false + 602ccbf4-1274-4cd9-8349-ba4c5ba030fe + 1 + + + + + + 2565 + 564 + 50 + 24 + + + 2590.097 + 576.5297 + + + + + + + + + + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel + + + + + A panel for custom notes and text values + 84325f58-aa64-43ef-9c59-6631e3ff4da9 + Panel + + false + 1 + 40308c84-a0ba-4c41-b158-38a3341beb2c + 1 + Double click to edit panel content… + + + + + + 1988 + 610 + 221 + 85 + + 0 + 0 + 0 + + 1988.603 + 610.7073 + + + + + + + 255;255;250;90 + + true + true + true + false + false + true + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 88b4a335-192d-48a1-81d5-163278eaecbe + 1 + 234eec62-a3cc-4178-9760-678a11912fb0 + Group + CUTTING PLANE + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 9c473eb6-ff8f-4c7f-bd69-56527d0257f6 + 1 + 86bc7ac3-bdd8-443c-ae9b-e22ea6ec103a + Group + REF_SIDE + + + + + + + + + + a77d0879-94c2-4101-be44-e4a616ffeb0c + 5f86fa9f-c62b-50e8-157b-b454ef3e00fa + Custom Preview Lineweights + + + + + Custom Preview with Lineweights + 56020ec2-ccb1-4ae9-a538-84dcb2857db9 + Custom Preview Lineweights + PreviewLW + + + + + + + 2570 + 747 + 46 + 84 + + + 2602 + 789 + + + + + + Geometry to preview + true + 11bd5cdd-8b99-4483-950a-c0cb8f29576c + Geometry + G + false + 7db965ba-79d8-42a1-926c-cc7c5ea6a716 + 1 + + + + + + 2572 + 749 + 15 + 20 + + + 2581 + 759 + + + + + + + + The preview shader override + 7eb996a5-eb09-476d-8a9f-c49f7a1c895e + Shader + S + false + 0 + + + + + + 2572 + 769 + 15 + 20 + + + 2581 + 779 + + + + + + 1 + + + + + 1 + {0} + + + + + + 255;255;105;180 + + + 255;76;32;54 + + 0.5 + + 255;255;255;255 + + 0 + + + + + + + + + + + The thickness of the wire display + c3053791-290f-494e-8384-81e8464e4dc4 + Thickness + T + true + 0 + + + + + + 2572 + 789 + 15 + 20 + + + 2581 + 799 + + + + + + + + Set to true to try to render curves with an absolute dimension. + 073aa3ff-0ed7-42b6-bdd5-c5b25159731c + Absolute + A + false + 0 + + + + + + 2572 + 809 + 15 + 20 + + + 2581 + 819 + + + + + + 1 + + + + + 1 + {0} + + + + + false + + + + + + + + + + + + + + + 11bbd48b-bb0a-4f1b-8167-fa297590390d + End Points + + + + + Extract the end points of a curve. + true + 715464ea-9048-4ae7-9c5c-942cc0fa2486 + End Points + End + + + + + + 727 + 448 + 64 + 44 + + + 758 + 470 + + + + + + Curve to evaluate + ba457b2a-de34-4922-929d-926d8adedc5a + Curve + C + false + 6bbc142b-2536-41bf-a215-ea52704d32b6 + 1 + + + + + + 729 + 450 + 14 + 40 + + + 737.5 + 470 + + + + + + + + Curve start point + 77f5802b-abc0-49e5-91cc-3376ae99a75c + Start + S + false + 0 + + + + + + 773 + 450 + 16 + 20 + + + 781 + 460 + + + + + + + + Curve end point + 5a7d2dd5-1871-45dd-b9c0-e6289501fd3a + End + E + false + 0 + + + + + + 773 + 470 + 16 + 20 + + + 781 + 480 + + + + + + + + + + + + e9eb1dcf-92f6-4d4d-84ae-96222d60f56b + Move + + + + + Translate (move) an object along a vector. + true + 22d84b32-205f-41d5-8663-8af45f94307c + Move + Move + + + + + + 833 + 468 + 83 + 44 + + + 881 + 490 + + + + + + Base geometry + 4f7e8528-a89b-42ec-8171-f6004589fdcb + Geometry + G + true + 5a7d2dd5-1871-45dd-b9c0-e6289501fd3a + 1 + + + + + + 835 + 470 + 31 + 20 + + + 860 + 480 + + + + + + + + Translation vector + 21c38b75-b524-4c4f-93e3-89a660e1a69b + -x + Motion + T + false + 1e474aea-db61-424d-be9a-804e3fd04359 + 1 + + + + + + 835 + 490 + 31 + 20 + + + 860 + 500 + + + + + + 1 + + + + + 1 + {0} + + + + + + 0 + 0 + 10 + + + + + + + + + + + + Translated geometry + 9348c98e-479b-416c-9b0b-54c72c33be75 + Geometry + G + false + 0 + + + + + + 896 + 470 + 18 + 20 + + + 905 + 480 + + + + + + + + Transformation data + 35ee0f80-11b3-44ca-8fde-aa3bba3f7c43 + Transform + X + false + 0 + + + + + + 896 + 490 + 18 + 20 + + + 905 + 500 + + + + + + + + + + + + 79f9fbb3-8f1d-4d9a-88a9-f7961b1012cd + Unit X + + + + + Unit vector parallel to the world {x} axis. + true + 73ddbf47-b58a-451f-8f84-85c767ffb835 + Unit X + X + + + + + + 513 + 491 + 63 + 28 + + + 542 + 505 + + + + + + Unit multiplication + 4b128ece-c9a7-4204-9394-3fda75fa3d6f + Factor + F + false + 912e5dfb-d2a2-463b-a0eb-2610982bf536 + 1 + + + + + + 515 + 493 + 12 + 24 + + + 522.5 + 505 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + World {x} vector + 323481aa-7502-4bf4-8393-f5ea30a2e8ed + Unit vector + V + false + 0 + + + + + + 557 + 493 + 17 + 24 + + + 565.5 + 505 + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 912e5dfb-d2a2-463b-a0eb-2610982bf536 + Number Slider + + false + 0 + + + + + + 323 + 497 + 163 + 20 + + + 323.5465 + 497.3116 + + + + + + 3 + 1 + 1 + 2000 + 0 + 0 + 0 + + + + + + + + + 4c4e56eb-2f04-43f9-95a3-cc46a14f495a + Line + + + + + Create a line between two points. + true + 25e3b03c-eedc-4418-babd-0df09c1d6284 + Line + Ln + + + + + + 943 + 448 + 63 + 44 + + + 974 + 470 + + + + + + Line start point + 99feaba8-239b-40e1-ae13-db12fa1af53e + Start Point + A + false + 77f5802b-abc0-49e5-91cc-3376ae99a75c + 1 + + + + + + 945 + 450 + 14 + 20 + + + 953.5 + 460 + + + + + + + + Line end point + 5d3fa723-a735-4608-9a27-c304c4c3aa44 + End Point + B + false + 9348c98e-479b-416c-9b0b-54c72c33be75 + 1 + + + + + + 945 + 470 + 14 + 20 + + + 953.5 + 480 + + + + + + + + Line segment + 82ddb61d-d21e-49ce-a61c-851130becb9a + Line + L + false + 0 + + + + + + 989 + 450 + 15 + 40 + + + 996.5 + 470 + + + + + + + + + + + + 9103c240-a6a9-4223-9b42-dbd19bf38e2b + Unit Z + + + + + Unit vector parallel to the world {z} axis. + true + eaf786cc-99fc-4d46-b6b3-cf6b5314011a + Unit Z + Z + + + + + + 515 + 525 + 63 + 28 + + + 544 + 539 + + + + + + Unit multiplication + 88ec5468-52fb-4929-b59a-79cb5d549828 + Factor + F + false + 68472b2f-1682-4c03-a570-0bd3dafb7255 + 1 + + + + + + 517 + 527 + 12 + 24 + + + 524.5 + 539 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + World {z} vector + f08f6ccf-8731-4561-b2d4-cf43dcb5070e + Unit vector + V + false + 0 + + + + + + 559 + 527 + 17 + 24 + + + 567.5 + 539 + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 68472b2f-1682-4c03-a570-0bd3dafb7255 + Number Slider + + false + 0 + + + + + + 329 + 529 + 163 + 20 + + + 329.7557 + 529.9963 + + + + + + 3 + 1 + 1 + 2000 + 0 + 0 + 0 + + + + + + + + + a0d62394-a118-422d-abb3-6af115c75b25 + Addition + + + + + Mathematical addition + true + 362a8c85-9ff1-4541-8448-a78e3c79ff60 + Addition + A+B + + + + + + 728 + 493 + 65 + 44 + + + 759 + 515 + + + + + + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + First item for addition + 0b1b606a-bd74-4af5-928f-2813c43db4f7 + A + A + true + 323481aa-7502-4bf4-8393-f5ea30a2e8ed + 1 + + + + + + 730 + 495 + 14 + 20 + + + 738.5 + 505 + + + + + + + + Second item for addition + 29d1f2a4-5fa6-44f6-af85-bc0232c7f3ed + B + B + true + f08f6ccf-8731-4561-b2d4-cf43dcb5070e + 1 + + + + + + 730 + 515 + 14 + 20 + + + 738.5 + 525 + + + + + + + + Result of addition + 1e474aea-db61-424d-be9a-804e3fd04359 + Result + R + false + 0 + + + + + + 774 + 495 + 17 + 40 + + + 782.5 + 515 + + + + + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 482410b9-724c-46d6-be3a-3d63785bc853 + 1 + 541e5f17-7a28-48ff-8820-37e3076647dd + Group + notch_planes + + + + + + + + + + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a + Boolean Toggle + + + + + Boolean (true/false) toggle + 1f1c2959-2344-4152-a5d6-0e718f54669a + Boolean Toggle + FlipCurve + false + 0 + false + + + + + + 462 + 647 + 115 + 22 + + + + + + + + + + eeafc956-268e-461d-8e73-ee05c6f72c01 + Stream Filter + + + + + Filters a collection of input streams + true + 46298eb6-2834-4153-b072-4cc235a21b8d + Stream Filter + Filter + + + + + + 957 + 655 + 77 + 64 + + + 989 + 687 + + + + + + 3 + 2e3ab970-8545-46bb-836c-1c11e5610bce + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + Index of Gate stream + 376909ae-471f-4338-bd76-0550fd56a4cb + Gate + G + false + 1f1c2959-2344-4152-a5d6-0e718f54669a + 1 + + + + + + 959 + 657 + 15 + 20 + + + 968 + 667 + + + + + + 1 + + + + + 1 + {0} + + + + + 0 + + + + + + + + + + + 2 + Input stream at index 0 + acea90f0-616d-4203-908a-45991c3b043f + false + Stream 0 + 0 + true + 2f78b16d-80d1-4502-954e-49040fe761a3 + 1 + + + + + + 959 + 677 + 15 + 20 + + + 968 + 687 + + + + + + + + 2 + Input stream at index 1 + 282b385d-4abd-455c-848a-9a06b88edbbc + false + Stream 1 + 1 + true + c284b03b-3c25-4136-b35d-211207e5e246 + 1 + + + + + + 959 + 697 + 15 + 20 + + + 968 + 707 + + + + + + + + 2 + Filtered stream + 919e0b0e-50c5-4983-bf15-4bb7b9f39c0a + false + Stream + S(0) + false + 0 + + + + + + 1004 + 657 + 28 + 60 + + + 1018 + 687 + + + + + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 2f78b16d-80d1-4502-954e-49040fe761a3 + 1f1c2959-2344-4152-a5d6-0e718f54669a + 2 + 507139ff-df02-4319-9176-8b2ce4935cf5 + Group + cross_centerline + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 6bbc142b-2536-41bf-a215-ea52704d32b6 + 73ddbf47-b58a-451f-8f84-85c767ffb835 + 912e5dfb-d2a2-463b-a0eb-2610982bf536 + eaf786cc-99fc-4d46-b6b3-cf6b5314011a + 68472b2f-1682-4c03-a570-0bd3dafb7255 + 5 + 917db72d-186f-4f9b-9bae-68106080e5ea + Group + main_centerline + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 654077fb-9447-42ca-a4ed-d6a4dd8d1874 + Number Slider + + false + 0 + + + + + + 1721 + 902 + 205 + 20 + + + 1721.597 + 902.3441 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 40 + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + c18f9e62-d6f5-42c4-a426-10688f24ca61 + fd67193c-c124-4e9b-bf38-3d6a6e085438 + 2 + b916d0a2-94ed-4de0-9991-6f37b3e59ce4 + Group + Ref_Side + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + d5f970b8-28fa-4f28-b038-8f3eed5c682d + c1e88145-a742-40e8-9b8b-a549422a646d + c925fa54-d90e-456f-863a-2f4f0a2857ba + 3 + 280e3dc8-45ed-4759-8d99-df91689dd4d8 + Group + StepShape + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 654077fb-9447-42ca-a4ed-d6a4dd8d1874 + 1 + 98458446-0073-4c25-ac14-bdda3bc5b7ad + Group + Mortise + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;56;56 + + A group of Grasshopper objects + cfb4b1f8-a02e-44ce-9c95-80b1164d7fca + 1 + d22d460d-f45b-4227-b1b7-0d4f280ddbcc + Group + + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + GhPython Script + + + + + from compas_rhino.conversions import surface_to_rhino +from compas_timber._fabrication import StepJointNotch +from compas_timber._fabrication import StepJointNotchParams +from compas_timber._fabrication import StepJoint + +from compas_timber.model import TimberModel +from compas_timber.fabrication import BTLx + +from compas_rhino import unload_modules +from compas.scene import SceneObject + +from compas_rhino.conversions import frame_to_rhino, plane_to_rhino, surface_to_rhino, polyline_to_rhino + +import Rhino.Geometry as rg +unload_modules("compas_timber") + +#define beam and cutting plane +beam = main_beam +plane = cross_beam.ref_sides[index] + +#create step_joint +step_joint = StepJoint.from_plane_and_beam(plane, beam, step, heel, tapered, ref_side_index) +cutting_planes = step_joint.planes_from_params_and_beam(beam) +# +###create step_joint_notch +##step_joint_notch = StepJointNotch.from_surface_and_beam(plane, beam, notch_limited=False, step_depth=step, heel_depth=heel, strut_height=100.0, tapered_heel=tapered, ref_side_index=ref_side_index) +##cutting_planes = step_joint_notch.planes_from_params_and_beam(beam) +## +##print("Orientation: ", step_joint_notch.orientation) +##print("StartX: ", step_joint_notch.start_x) +##print("StrutInclination: ", step_joint_notch.strut_inclination) +## +## +###add mortise +##if mortise_height > 0: +## step_joint_notch.add_mortise(beam.width/4, mortise_height, beam) +## mortise_polylines = step_joint_notch.mortise_volume_from_params_and_beam(beam) +## +####apply geometric features +###step_joint_notch.apply(brep, beam) +## +###get btlx params +##step_joint_notch_params = StepJointNotchParams(step_joint_notch).as_dict() +##btlx_params = [] +##for key, value in step_joint_notch_params.items(): +## btlx_params.append("{0}: {1}".format(key, value)) +## +## +##vizualize in rhino +rg_ref_side = frame_to_rhino(beam.ref_sides[ref_side_index]) +rg_cutting_plane = frame_to_rhino(plane) +rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) +## +##rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_polylines] + + + + GhPython provides a Python script component + + 103 + 145 + + + 1232 + 796 + + true + true + false + false + d61a37f6-bab7-4ae1-ba80-a47758fff264 + false + true + GhPython Script + Python + + + + + + 1955 + 1578 + 222 + 164 + + + 2051 + 1660 + + + + + + 8 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 7 + 3ede854e-c753-40eb-84cb-b48008f14fd4 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + true + Script variable Python + 89f4c804-fb16-4265-9e6e-caed7fd7d204 + cross_beam + cross_beam + true + 0 + true + 3984d0df-294e-462f-b103-b87bb94e3021 + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1957 + 1580 + 79 + 20 + + + 1998 + 1590 + + + + + + + + true + Script input main_beam. + 85dee51e-2525-4470-aa5b-a8dca308f7c3 + main_beam + main_beam + true + 0 + true + 90fae02f-7750-462d-828f-cf5f2a0d39ae + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1957 + 1600 + 79 + 20 + + + 1998 + 1610 + + + + + + + + true + Script input index. + b6fa8f96-678e-41b5-afd0-053ec6fd5ad0 + index + index + true + 0 + true + 37c9fe3d-b5bb-4362-ba0f-d5053fcb2141 + 1 + 48d01794-d3d8-4aef-990e-127168822244 + + + + + + 1957 + 1620 + 79 + 20 + + + 1998 + 1630 + + + + + + + + true + Script input ref_side_index. + 2d345ebe-38ca-4427-b0ec-c749512b75fd + ref_side_index + ref_side_index + true + 0 + true + 7e4bcb53-01cc-446e-8792-764d02b1451e + 1 + 48d01794-d3d8-4aef-990e-127168822244 + + + + + + 1957 + 1640 + 79 + 20 + + + 1998 + 1650 + + + + + + + + true + Script input step. + 2d3faeb6-6f29-4e99-ad70-d5c0711d5865 + step + step + true + 0 + true + c3c1dc6d-7ceb-4d7f-add6-fa924b4cc936 + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1957 + 1660 + 79 + 20 + + + 1998 + 1670 + + + + + + + + true + Script input heel. + 5124721f-0a65-441b-863d-ed7dfa0eedab + heel + heel + true + 0 + true + ab977350-6a8d-472b-9276-52a400da755b + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1957 + 1680 + 79 + 20 + + + 1998 + 1690 + + + + + + + + true + Script input tapered. + 79536ba5-02e1-4e89-814b-5d3beff1df9b + tapered + tapered + true + 0 + true + dec5089d-c452-417b-a948-4034d6df42ce + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1957 + 1700 + 79 + 20 + + + 1998 + 1710 + + + + + + + + true + Script input mortise_height. + 74b917c7-fc1a-4dec-abae-860ed10a0b11 + mortise_height + mortise_height + true + 0 + true + 1f382d1c-8a89-4e8a-8102-597801cd81a3 + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1957 + 1720 + 79 + 20 + + + 1998 + 1730 + + + + + + + + The execution information, as output and error streams + 8d656ac0-a47a-48cd-bd30-510e0169b258 + out + out + false + 0 + + + + + + 2066 + 1580 + 109 + 22 + + + 2120.5 + 1591.429 + + + + + + + + Script output surface. + 8579e7d8-c52b-4634-b03d-5bc56067fd74 + surface + surface + false + 0 + + + + + + 2066 + 1602 + 109 + 23 + + + 2120.5 + 1614.286 + + + + + + + + Script output rg_cutting_plane. + 9fe4a86c-3c6e-4f70-8cbe-630b57b5a0b1 + rg_cutting_plane + rg_cutting_plane + false + 0 + + + + + + 2066 + 1625 + 109 + 23 + + + 2120.5 + 1637.143 + + + + + + + + Script output rg_planes. + 6270f634-eb6d-402e-a88f-24f478dcf237 + rg_planes + rg_planes + false + 0 + + + + + + 2066 + 1648 + 109 + 23 + + + 2120.5 + 1660 + + + + + + + + Script output rg_ref_side. + 9c129a89-24d5-45f5-bcea-890db46f3da4 + rg_ref_side + rg_ref_side + false + 0 + + + + + + 2066 + 1671 + 109 + 23 + + + 2120.5 + 1682.857 + + + + + + + + Script output rg_mortise_polylines. + 9ba6c2c7-77fe-4ea5-96f9-edfa7f161fb3 + rg_mortise_polylines + rg_mortise_polylines + false + 0 + + + + + + 2066 + 1694 + 109 + 23 + + + 2120.5 + 1705.714 + + + + + + + + Script output btlx_params. + 269c79e7-4269-4669-b62a-9869376ea11c + btlx_params + btlx_params + false + 0 + + + + + + 2066 + 1717 + 109 + 23 + + + 2120.5 + 1728.571 + + + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 37c9fe3d-b5bb-4362-ba0f-d5053fcb2141 + Number Slider + + false + 0 + + + + + + 1730 + 1578 + 160 + 20 + + + 1730.692 + 1578.391 + + + + + + 3 + 1 + 1 + 15 + 0 + 0 + 2 + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 7e4bcb53-01cc-446e-8792-764d02b1451e + Number Slider + + false + 0 + + + + + + 1690 + 1602 + 201 + 20 + + + 1690.675 + 1602.711 + + + + + + 3 + 1 + 1 + 5 + 0 + 0 + 0 + + + + + + + + + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel + + + + + A panel for custom notes and text values + 52c22682-fb60-48d8-ae45-8e60302c086f + Panel + BTLx Params + false + 0 + 269c79e7-4269-4669-b62a-9869376ea11c + 1 + Double click to edit panel content… + + + + + + 2368 + 1810 + 212 + 333 + + 0 + 0 + 0 + + 2368.562 + 1810.53 + + + + + + + 255;255;255;255 + + true + true + true + false + false + true + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + c3c1dc6d-7ceb-4d7f-add6-fa924b4cc936 + Number Slider + + false + 0 + + + + + + 1695 + 1670 + 160 + 20 + + + 1695.763 + 1670.399 + + + + + + 3 + 1 + 1 + 200 + 0 + 0 + 0 + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + ab977350-6a8d-472b-9276-52a400da755b + Number Slider + + false + 0 + + + + + + 1695 + 1693 + 160 + 20 + + + 1695.762 + 1693.472 + + + + + + 3 + 1 + 1 + 200 + 0 + 0 + 17 + + + + + + + + + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a + Boolean Toggle + + + + + Boolean (true/false) toggle + dec5089d-c452-417b-a948-4034d6df42ce + Boolean Toggle + tapered_heel + false + 0 + false + + + + + + 1720 + 1714 + 133 + 22 + + + + + + + + + + f31d8d7a-7536-4ac8-9c96-fde6ecda4d0a + Cluster + + + + + + 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 + + Contains a cluster of Grasshopper components + true + 7154ee57-be53-4a07-8da4-9858a1faea73 + Cluster + Cluster + false + + + + + 5 + 065c8b93-533c-4d8c-b22d-d9bc535f688e + 47dba7fc-39e4-4a3b-80ca-c2043391f528 + 6f553506-3c94-4dbc-b2ec-f549b4c30b0c + 8afe827d-4dab-4dde-aea7-476d34f57a1b + e7de4017-7053-4063-8078-ab0ab768ffd7 + 3c631e1e-b12f-4297-9535-87b4fdc7b45e + 796ac502-faba-4bb6-a612-7e3dfb448d98 + b360d350-2b53-401b-9420-d9402019cb30 + 5d32325d-62cf-40bd-93fe-74af56a2c91e + 5d665740-a9cc-4f15-8a38-0dc75e214ae2 + + + + + + 2349 + 1639 + 72 + 84 + + + 2388 + 1681 + + + + + + 4 + 919e146f-30ae-4aae-be34-4d72f555e7da + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + 1 + 919e146f-30ae-4aae-be34-4d72f555e7da + + + + + Brep to split + 6f553506-3c94-4dbc-b2ec-f549b4c30b0c + Brep + B + true + 55c66290-a177-4518-824a-6a531a1e8086 + 1 + + + + + + 2351 + 1641 + 22 + 20 + + + 2363.5 + 1651 + + + + + + + + Contains a collection of three-dimensional axis-systems + 8afe827d-4dab-4dde-aea7-476d34f57a1b + Plane + Pln + true + 6270f634-eb6d-402e-a88f-24f478dcf237 + 1 + + + + + + 2351 + 1661 + 22 + 20 + + + 2363.5 + 1671 + + + + + + + + Contains a collection of three-dimensional axis-systems + 47dba7fc-39e4-4a3b-80ca-c2043391f528 + Plane + Pln + true + 0 + + + + + + 2351 + 1681 + 22 + 20 + + + 2363.5 + 1691 + + + + + + + + 1 + Section curves + 065c8b93-533c-4d8c-b22d-d9bc535f688e + Curves + C + true + 9ba6c2c7-77fe-4ea5-96f9-edfa7f161fb3 + 1 + + + + + + 2351 + 1701 + 22 + 20 + + + 2363.5 + 1711 + + + + + + + + 1 + Joined Breps + e7de4017-7053-4063-8078-ab0ab768ffd7 + Breps + B + false + 0 + + + + + + 2403 + 1641 + 16 + 80 + + + 2411 + 1681 + + + + + + + + + + + + + + 537b0419-bbc2-4ff4-bf08-afe526367b2c + Custom Preview + + + + + Allows for customized geometry previews + true + true + def048ab-0443-4189-81d6-03594953cdd1 + Custom Preview + Preview + + + + + + + 2537 + 1724 + 48 + 44 + + + 2571 + 1746 + + + + + + Geometry to preview + true + 977e596e-9c8a-4d5e-a490-c2c39fe7f008 + Geometry + G + false + e7de4017-7053-4063-8078-ab0ab768ffd7 + 1 + + + + + + 2539 + 1726 + 17 + 20 + + + 2549 + 1736 + + + + + + + + The material override + 5f9e9e88-be8b-4453-b5f6-8e8fcf54697c + Material + M + false + 0 + + + + + + 2539 + 1746 + 17 + 20 + + + 2549 + 1756 + + + + + + 1 + + + + + 1 + {0} + + + + + + 255;221;160;221 + + + 255;66;48;66 + + 0.5 + + 255;255;255;255 + + 0 + + + + + + + + + + + + + + + 0148a65d-6f42-414a-9db7-9a9b2eb78437 + Brep Edges + + + + + Extract the edge curves of a brep. + true + aa461d11-c653-411c-a341-b7900f1ccbd6 + Brep Edges + Edges + + + + + + 2437 + 1649 + 72 + 64 + + + 2467 + 1681 + + + + + + Base Brep + d6ae26e7-62fd-4655-9999-cf54b249138d + Brep + B + false + e7de4017-7053-4063-8078-ab0ab768ffd7 + 1 + + + + + + 2439 + 1651 + 13 + 60 + + + 2447 + 1681 + + + + + + + + 1 + Naked edge curves + ef4b92cd-9e89-42cc-aed4-b855fa6a4238 + Naked + En + false + 0 + + + + + + 2482 + 1651 + 25 + 20 + + + 2494.5 + 1661 + + + + + + + + 1 + Interior edge curves + 920e2adf-799f-431e-ada8-588d6ab271ba + Interior + Ei + false + 0 + + + + + + 2482 + 1671 + 25 + 20 + + + 2494.5 + 1681 + + + + + + + + 1 + Non-Manifold edge curves + 269805b0-ae89-4079-9ee5-97986acf75e1 + Non-Manifold + Em + false + 0 + + + + + + 2482 + 1691 + 25 + 20 + + + 2494.5 + 1701 + + + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + 5ad83719-a82f-419e-8606-51aa09e83d11 + Plane + Pln + false + 6270f634-eb6d-402e-a88f-24f478dcf237 + 1 + + + + + + 2534 + 1587 + 50 + 24 + + + 2559.857 + 1599.387 + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + bec3cac8-0564-45af-9acc-545be5c1d193 + Plane + Pln + false + 9c129a89-24d5-45f5-bcea-890db46f3da4 + 1 + + + + + + 2534 + 1516 + 50 + 24 + + + 2559.216 + 1528.442 + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + 6e7fcdb6-9b16-4ad8-90a6-6d7bd2fe16a2 + Plane + Pln + false + 9fe4a86c-3c6e-4f70-8cbe-630b57b5a0b1 + 1 + + + + + + 2533 + 1448 + 50 + 24 + + + 2558.649 + 1460.386 + + + + + + + + + + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel + + + + + A panel for custom notes and text values + b8e83b08-cfb9-49e9-b847-ee42b020c654 + Panel + + false + 1 + 8d656ac0-a47a-48cd-bd30-510e0169b258 + 1 + Double click to edit panel content… + + + + + + 1957 + 1494 + 221 + 85 + + 0 + 0 + 0 + + 1957.155 + 1494.563 + + + + + + + 255;255;250;90 + + true + true + true + false + false + true + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 6e7fcdb6-9b16-4ad8-90a6-6d7bd2fe16a2 + 1 + 1045a7ec-3546-4cfc-9e61-e9796268138a + Group + CUTTING PLANE + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + bec3cac8-0564-45af-9acc-545be5c1d193 + 1 + 927c55d5-99d0-47f9-b656-3cef4c9f5214 + Group + REF_SIDE + + + + + + + + + + a77d0879-94c2-4101-be44-e4a616ffeb0c + 5f86fa9f-c62b-50e8-157b-b454ef3e00fa + Custom Preview Lineweights + + + + + Custom Preview with Lineweights + 0cbceed4-3434-411c-b4d1-46985ef14dea + Custom Preview Lineweights + PreviewLW + + + + + + + 2538 + 1630 + 46 + 84 + + + 2570 + 1672 + + + + + + Geometry to preview + true + bd3da4c6-368d-4f8f-afa0-7241f56b0323 + Geometry + G + false + 920e2adf-799f-431e-ada8-588d6ab271ba + 1 + + + + + + 2540 + 1632 + 15 + 20 + + + 2549 + 1642 + + + + + + + + The preview shader override + f7c73e31-145c-457b-8538-038ea41e36ce + Shader + S + false + 0 + + + + + + 2540 + 1652 + 15 + 20 + + + 2549 + 1662 + + + + + + 1 + + + + + 1 + {0} + + + + + + 255;255;105;180 + + + 255;76;32;54 + + 0.5 + + 255;255;255;255 + + 0 + + + + + + + + + + + The thickness of the wire display + be5283ef-c093-4c55-a00a-f176aa83c876 + Thickness + T + true + 0 + + + + + + 2540 + 1672 + 15 + 20 + + + 2549 + 1682 + + + + + + + + Set to true to try to render curves with an absolute dimension. + fb708cb7-0800-40a7-b44a-8228329e5775 + Absolute + A + false + 0 + + + + + + 2540 + 1692 + 15 + 20 + + + 2549 + 1702 + + + + + + 1 + + + + + 1 + {0} + + + + + false + + + + + + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 5ad83719-a82f-419e-8606-51aa09e83d11 + 1 + e05aff55-35f8-4083-ad48-522d9cd323c3 + Group + notch_planes + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 1f382d1c-8a89-4e8a-8102-597801cd81a3 + Number Slider + + false + 0 + + + + + + 1690 + 1786 + 205 + 20 + + + 1690.149 + 1786.2 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 40 + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 37c9fe3d-b5bb-4362-ba0f-d5053fcb2141 + 7e4bcb53-01cc-446e-8792-764d02b1451e + 2 + ba1c263a-665b-4a0d-a1a6-e771de90f422 + Group + Ref_Side + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + c3c1dc6d-7ceb-4d7f-add6-fa924b4cc936 + ab977350-6a8d-472b-9276-52a400da755b + dec5089d-c452-417b-a948-4034d6df42ce + 3 + 62e63fca-99eb-4ff3-8b10-df2b88807b8d + Group + StepShape + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 1f382d1c-8a89-4e8a-8102-597801cd81a3 + 1 + d6206efa-67b1-4c9e-b762-42a2cf302219 + Group + Mortise + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;56;56 + + A group of Grasshopper objects + 52c22682-fb60-48d8-ae45-8e60302c086f + 1 + 838171ab-f4f9-4bf2-a5f1-ee9d41fdbb93 + Group + + + + + + + + + + + 59daf374-bc21-4a5e-8282-5504fb7ae9ae + List Item + + + + + 0 + Retrieve a specific item from a list. + true + d45f18dd-f301-494f-8070-d01705d5c6bf + List Item + Item + + + + + + 2647 + 1522 + 64 + 64 + + + 2681 + 1554 + + + + + + 3 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 2e3ab970-8545-46bb-836c-1c11e5610bce + cb95db89-6165-43b6-9c41-5702bc5bf137 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 1 + Base list + e6e0f605-ab46-4e72-8626-57f9f1213f8d + List + L + false + 5ad83719-a82f-419e-8606-51aa09e83d11 + 1 + + + + + + 2649 + 1524 + 17 + 20 + + + 2659 + 1534 + + + + + + + + Item index + 475b0420-2ae8-475b-9881-2395b48054d3 + Index + i + false + 0 + + + + + + 2649 + 1544 + 17 + 20 + + + 2659 + 1554 + + + + + + 1 + + + + + 1 + {0} + + + + + 0 + + + + + + + + + + + Wrap index to list bounds + fab10091-9245-436d-9bdb-e0526813f593 + Wrap + W + false + 0 + + + + + + 2649 + 1564 + 17 + 20 + + + 2659 + 1574 + + + + + + 1 + + + + + 1 + {0} + + + + + true + + + + + + + + + + + Item at {i'} + 87d62127-d0d0-469f-8b72-3b18edc264dd + false + Item + i + false + 0 + + + + + + 2696 + 1524 + 13 + 60 + + + 2702.5 + 1554 + + + + + + + + + + + + + + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + Curve + + + + + Contains a collection of generic curves + true + 1631fe30-c80f-49ec-9128-96481121ad52 + Curve + Crv + false + 0 + + + + + + 494 + 1158 + 50 + 24 + + + 519.3805 + 1170.715 + + + + + + 1 + + + + + 1 + {0} + + + + + -1 + + Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesYmekbGRmbmeoaGBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS9f/4eDiZgQx+EDEViJl+1TMxdEL99fs/E4MIlP3synU/95eXBfc/ZRBoZlT64Ddn9+0/QPlAqLwAyNzYbxpcrDPvOXz8/18+rHjlAYmi2O741CCH48eNlj7zvu9wBCoe0mqltvDcBAcmBgTY2xy1xs52sgPIJY3vmdz+1zM1wOS4GQYTAAA= + + 00000000-0000-0000-0000-000000000000 + + + + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + CT: Beam + + + + + """Creates a Beam from a LineCurve.""" + +import rhinoscriptsyntax as rs +from compas.scene import Scene +from compas_rhino.conversions import line_to_compas +from compas_rhino.conversions import vector_to_compas +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Error +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning +from Rhino.RhinoDoc import ActiveDoc + +from compas_timber.elements import Beam as CTBeam +from compas_timber.ghpython.rhino_object_name_attributes import update_rhobj_attributes_name + + +class Beam_fromCurve(component): + def RunScript(self, centerline, z_vector, width, height, category, updateRefObj): + # minimum inputs required + if not centerline: + self.AddRuntimeMessage(Warning, "Input parameter 'Centerline' failed to collect data") + if not width: + self.AddRuntimeMessage(Warning, "Input parameter 'Width' failed to collect data") + if not height: + self.AddRuntimeMessage(Warning, "Input parameter 'Height' failed to collect data") + + # reformat unset parameters for consistency + if not z_vector: + z_vector = [None] + if not category: + category = [None] + + beams = [] + blanks = [] + scene = Scene() + + if centerline and height and width: + # check list lengths for consistency + N = len(centerline) + if len(z_vector) not in (0, 1, N): + self.AddRuntimeMessage( + Error, " In 'ZVector' I need either none, one or the same number of inputs as the Crv parameter." + ) + if len(width) not in (1, N): + self.AddRuntimeMessage( + Error, " In 'W' I need either one or the same number of inputs as the Crv parameter." + ) + if len(height) not in (1, N): + self.AddRuntimeMessage( + Error, " In 'H' I need either one or the same number of inputs as the Crv parameter." + ) + if len(category) not in (0, 1, N): + self.AddRuntimeMessage( + Error, " In 'Category' I need either none, one or the same number of inputs as the Crv parameter." + ) + + # duplicate data if None or single value + if len(z_vector) != N: + z_vector = [z_vector[0] for _ in range(N)] + if len(width) != N: + width = [width[0] for _ in range(N)] + if len(height) != N: + height = [height[0] for _ in range(N)] + if len(category) != N: + category = [category[0] for _ in range(N)] + + for line, z, w, h, c in zip(centerline, z_vector, width, height, category): + guid, geometry = self._get_guid_and_geometry(line) + rhino_line = rs.coerceline(geometry) + line = line_to_compas(rhino_line) + + z = vector_to_compas(z) if z else None + 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)) + update_rhobj_attributes_name(guid, "zvector", str(list(beam.frame.zaxis))) + update_rhobj_attributes_name(guid, "category", c) + + beams.append(beam) + scene.add(beam.blank) + + blanks = scene.draw() + + return beams, blanks + + def _get_guid_and_geometry(self, line): + # internalized curves and GH geometry will not have persistent GUIDs, referenced Rhino objects will + # type hint on the input has to be 'ghdoc' for this to work + guid = None + geometry = line + rhino_obj = ActiveDoc.Objects.FindId(line) + if rhino_obj: + guid = line + geometry = rhino_obj.Geometry + return guid, geometry + + Creates a Beam from a LineCurve. + true + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDgAACw4BQL7hQQAAAhRJREFUSEulVLtKA0EUzSfkD3zlrUKMIcHGB6ghIj5QxEoDiiA2gtqGYBEfRRQiSRc7iXU6IWKZwh8Q/AI/wEcRr/cMmWUZ7ya7Whw2uXvvOTNnzqyPiP6Nob7+Q0bBQJ0xLQ64BRP4R6OxZ37Swf4+nRwdKyxmFyg4MEhcvxYH3YCH4+GhwHsqMd6+r9ep1WrRY7OpyGcmp2hvZxcCBXG4F3gQligykIIcIulkknJb26qGnXgW4AF/NBi6AzkIQAxcnl+AjAr5vFXzLMDN8ZFI9JXxfVMuW0RYMZ8D3dZqVs2zADeuRALBD3jbaDQUAZ74D2ib/iQwHI5UudHyFsNYLVaN5JjEGj0F+KUVQXisB+EzyO01CV0F+IUYwY21dZUUXXMCemGdKMBFMYIYgIDktx2wD0FAILBQO7FjBGGJveYE2Id5Jn8AH3g1uRhBHCLI7TUJ2BVCAHLuP9eLVgJc3GS/vyZSaboqldQWq5UKZeczyhYdSyfouCLGzLViJ9cCT1A2sbq03NNv7Ay7joXCLzzTb5JbAqa/mdm5XzfTRCeG1Dk35bcEUQAJchLArpAmkDMOJVI7PAkgrrgbuCM8F5cITbgWQFyHI9F2x29HS0y4EkBcuU/5LZF0Q1cB+I3fHMFP7stJBL3gKHBWLOoIvnGPK78lKAEQQkQjOZZQlnS+pq79lgABfNxw2UycSgPeQL4fsKzVEEQlKEcAAAAASUVORK5CYII= + + false + e28e26d5-b791-48bc-902c-a1929ace9685 + true + true + CT: Beam + Beam + + + + + + 1053 + 1031 + 145 + 124 + + + 1144 + 1093 + + + + + + 6 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 1 + true + Referenced curve or line, Guid of curve or line in the active Rhino document. + 1bee6a78-7b3a-4d65-9fa6-ee03a6a467d7 + Centerline + Centerline + true + 1 + true + 18d2fa4a-de1b-4cfd-bf80-4b8d5fa0182f + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1055 + 1033 + 74 + 20 + + + 1093.5 + 1043 + + + + + + + + 1 + true + Vector defining the orientation of the cross-section (corresponds to the height dimension). If None, a default will be taken. + 9cde7a1d-c80f-42e0-9a2a-232ea2038830 + ZVector + ZVector + true + 1 + true + 0 + 15a50725-e3d3-4075-9f7c-142ba5f40747 + + + + + + 1055 + 1053 + 74 + 20 + + + 1093.5 + 1063 + + + + + + + + 1 + true + Width of the cross-section (usually the smaller dimension). + 640d61a5-30a2-4d6d-9d06-693c7998daa3 + Width + Width + true + 1 + true + da50cc8d-94c7-4348-b6e9-2f46272647d3 + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1055 + 1073 + 74 + 20 + + + 1093.5 + 1083 + + + + + + + + 1 + true + Height of the cross-section (usually the larger dimension). + c55fd807-c67f-4bc9-8acb-00f1fb7639c8 + Height + Height + true + 1 + true + d9d51b18-d5af-4f8f-822f-9fbd0f30d642 + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1055 + 1093 + 74 + 20 + + + 1093.5 + 1103 + + + + + + + + 1 + true + Category of a beam. + 3cabcb03-288e-45d6-a8c0-e9ddc8b859ce + Category + Category + true + 1 + true + 0 + 37261734-eec7-4f50-b6a8-b8d1f3c4396b + + + + + + 1055 + 1113 + 74 + 20 + + + 1093.5 + 1123 + + + + + + + + true + (optional) If True, the attributes in the referenced object will be updated/overwritten with the new values given here. + bb55e50a-3bee-410d-8466-5dfca9d565d1 + updateRefObj + updateRefObj + true + 0 + true + 0 + d60527f5-b5af-4ef6-8970-5f96fe412559 + + + + + + 1055 + 1133 + 74 + 20 + + + 1093.5 + 1143 + + + + + + + + Beam object(s). + 3984d0df-294e-462f-b103-b87bb94e3021 + Beam + Beam + false + 0 + + + + + + 1159 + 1033 + 37 + 60 + + + 1177.5 + 1063 + + + + + + + + Shape of the beam's blank. + b0a6568e-06d8-4c68-b393-d8274f661640 + Blank + Blank + false + 0 + + + + + + 1159 + 1093 + 37 + 60 + + + 1177.5 + 1123 + + + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + d9d51b18-d5af-4f8f-822f-9fbd0f30d642 + Number Slider + + false + 0 + + + + + + 831 + 1101 + 166 + 20 + + + 831.0512 + 1101.648 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 100 + + + + + + + + + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + Curve + + + + + Contains a collection of generic curves + 18d2fa4a-de1b-4cfd-bf80-4b8d5fa0182f + Curve + Crv + false + 0 + + + + + + 495 + 1001 + 50 + 24 + + + 520.374 + 1013.907 + + + + + + 1 + + + + + 1 + {0} + + + + + -1 + + Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesYmekbGRmbmeoaGBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS9f/4eDiZgQx+EDEViJl+1TMxdEL99fs/E4MIlP3synU/95eXBfc/ZRBoZlT64Ddn9+0/QPlAqLwAyFyF5Ln1s/nvOXz8/18+rHjlAYmi2O741CCHCRvP7T9b98DhM5o4EwMaKFjuAHKJ94LooP/1TA0wYW50dQMKAA== + + 00000000-0000-0000-0000-000000000000 + + + + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + CT: Beam + + + + + """Creates a Beam from a LineCurve.""" + +import rhinoscriptsyntax as rs +from compas.scene import Scene +from compas_rhino.conversions import line_to_compas +from compas_rhino.conversions import vector_to_compas +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Error +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning +from Rhino.RhinoDoc import ActiveDoc + +from compas_timber.elements import Beam as CTBeam +from compas_timber.ghpython.rhino_object_name_attributes import update_rhobj_attributes_name + + +class Beam_fromCurve(component): + def RunScript(self, centerline, z_vector, width, height, category, updateRefObj): + # minimum inputs required + if not centerline: + self.AddRuntimeMessage(Warning, "Input parameter 'Centerline' failed to collect data") + if not width: + self.AddRuntimeMessage(Warning, "Input parameter 'Width' failed to collect data") + if not height: + self.AddRuntimeMessage(Warning, "Input parameter 'Height' failed to collect data") + + # reformat unset parameters for consistency + if not z_vector: + z_vector = [None] + if not category: + category = [None] + + beams = [] + blanks = [] + scene = Scene() + + if centerline and height and width: + # check list lengths for consistency + N = len(centerline) + if len(z_vector) not in (0, 1, N): + self.AddRuntimeMessage( + Error, " In 'ZVector' I need either none, one or the same number of inputs as the Crv parameter." + ) + if len(width) not in (1, N): + self.AddRuntimeMessage( + Error, " In 'W' I need either one or the same number of inputs as the Crv parameter." + ) + if len(height) not in (1, N): + self.AddRuntimeMessage( + Error, " In 'H' I need either one or the same number of inputs as the Crv parameter." + ) + if len(category) not in (0, 1, N): + self.AddRuntimeMessage( + Error, " In 'Category' I need either none, one or the same number of inputs as the Crv parameter." + ) + + # duplicate data if None or single value + if len(z_vector) != N: + z_vector = [z_vector[0] for _ in range(N)] + if len(width) != N: + width = [width[0] for _ in range(N)] + if len(height) != N: + height = [height[0] for _ in range(N)] + if len(category) != N: + category = [category[0] for _ in range(N)] + + for line, z, w, h, c in zip(centerline, z_vector, width, height, category): + guid, geometry = self._get_guid_and_geometry(line) + rhino_line = rs.coerceline(geometry) + line = line_to_compas(rhino_line) + + z = vector_to_compas(z) if z else None + 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)) + update_rhobj_attributes_name(guid, "zvector", str(list(beam.frame.zaxis))) + update_rhobj_attributes_name(guid, "category", c) + + beams.append(beam) + scene.add(beam.blank) + + blanks = scene.draw() + + return beams, blanks + + def _get_guid_and_geometry(self, line): + # internalized curves and GH geometry will not have persistent GUIDs, referenced Rhino objects will + # type hint on the input has to be 'ghdoc' for this to work + guid = None + geometry = line + rhino_obj = ActiveDoc.Objects.FindId(line) + if rhino_obj: + guid = line + geometry = rhino_obj.Geometry + return guid, geometry + + Creates a Beam from a LineCurve. + true + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDQAACw0B7QfALAAAAhRJREFUSEulVLtKA0EUzSfkD3zlrUKMIcHGB6ghIj5QxEoDiiA2gtqGYBEfRRQiSRc7iXU6IWKZwh8Q/AI/wEcRr/cMmWUZ7ya7Whw2uXvvOTNnzqyPiP6Nob7+Q0bBQJ0xLQ64BRP4R6OxZ37Swf4+nRwdKyxmFyg4MEhcvxYH3YCH4+GhwHsqMd6+r9ep1WrRY7OpyGcmp2hvZxcCBXG4F3gQligykIIcIulkknJb26qGnXgW4AF/NBi6AzkIQAxcnl+AjAr5vFXzLMDN8ZFI9JXxfVMuW0RYMZ8D3dZqVs2zADeuRALBD3jbaDQUAZ74D2ib/iQwHI5UudHyFsNYLVaN5JjEGj0F+KUVQXisB+EzyO01CV0F+IUYwY21dZUUXXMCemGdKMBFMYIYgIDktx2wD0FAILBQO7FjBGGJveYE2Id5Jn8AH3g1uRhBHCLI7TUJ2BVCAHLuP9eLVgJc3GS/vyZSaboqldQWq5UKZeczyhYdSyfouCLGzLViJ9cCT1A2sbq03NNv7Ay7joXCLzzTb5JbAqa/mdm5XzfTRCeG1Dk35bcEUQAJchLArpAmkDMOJVI7PAkgrrgbuCM8F5cITbgWQFyHI9F2x29HS0y4EkBcuU/5LZF0Q1cB+I3fHMFP7stJBL3gKHBWLOoIvnGPK78lKAEQQkQjOZZQlnS+pq79lgABfNxw2UycSgPeQL4fsKzVEEQlKEcAAAAASUVORK5CYII= + + false + ea292354-f0cd-4b58-ba78-57c730de54ad + true + true + CT: Beam + Beam + + + + + + 1051 + 1239 + 145 + 124 + + + 1142 + 1301 + + + + + + 6 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 1 + true + Referenced curve or line, Guid of curve or line in the active Rhino document. + 53757008-8b2d-49b8-ada7-e0c81d24586f + Centerline + Centerline + true + 1 + true + 09fe95fb-9551-4690-abf6-4a0665002914 + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1053 + 1241 + 74 + 20 + + + 1091.5 + 1251 + + + + + + + + 1 + true + Vector defining the orientation of the cross-section (corresponds to the height dimension). If None, a default will be taken. + db7b0ca0-f838-405d-ae51-0e61596dd253 + ZVector + ZVector + true + 1 + true + 0 + 15a50725-e3d3-4075-9f7c-142ba5f40747 + + + + + + 1053 + 1261 + 74 + 20 + + + 1091.5 + 1271 + + + + + + + + 1 + true + Width of the cross-section (usually the smaller dimension). + 4b9bf139-421a-4a01-b027-ec23d68ace87 + Width + Width + true + 1 + true + 478fc3b1-c4f8-4087-8fe6-c2905e21be9a + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1053 + 1281 + 74 + 20 + + + 1091.5 + 1291 + + + + + + + + 1 + true + Height of the cross-section (usually the larger dimension). + 4ae02a01-6e0a-4998-bca5-654916c862fc + Height + Height + true + 1 + true + 478fc3b1-c4f8-4087-8fe6-c2905e21be9a + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1053 + 1301 + 74 + 20 + + + 1091.5 + 1311 + + + + + + + + 1 + true + Category of a beam. + 98e31428-399a-4dde-a0e3-585981226918 + Category + Category + true + 1 + true + 0 + 37261734-eec7-4f50-b6a8-b8d1f3c4396b + + + + + + 1053 + 1321 + 74 + 20 + + + 1091.5 + 1331 + + + + + + + + true + (optional) If True, the attributes in the referenced object will be updated/overwritten with the new values given here. + 32189ead-4252-4352-bf45-514c147d7b57 + updateRefObj + updateRefObj + true + 0 + true + 0 + d60527f5-b5af-4ef6-8970-5f96fe412559 + + + + + + 1053 + 1341 + 74 + 20 + + + 1091.5 + 1351 + + + + + + + + Beam object(s). + 90fae02f-7750-462d-828f-cf5f2a0d39ae + Beam + Beam + false + 0 + + + + + + 1157 + 1241 + 37 + 60 + + + 1175.5 + 1271 + + + + + + + + Shape of the beam's blank. + 55c66290-a177-4518-824a-6a531a1e8086 + Blank + Blank + false + 0 + + + + + + 1157 + 1301 + 37 + 60 + + + 1175.5 + 1331 + + + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 478fc3b1-c4f8-4087-8fe6-c2905e21be9a + Number Slider + + false + 0 + + + + + + 866 + 1296 + 166 + 20 + + + 866.0197 + 1296.777 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 100 + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + da50cc8d-94c7-4348-b6e9-2f46272647d3 + Number Slider + + false + 0 + + + + + + 831 + 1078 + 166 + 20 + + + 831.1492 + 1078.275 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 80 + + + + + + + + + 22990b1f-9be6-477c-ad89-f775cd347105 + Flip Curve + + + + + Flip a curve using an optional guide curve. + true + 2098ce01-e0b3-4e52-b0ed-3c62bb419a62 + Flip Curve + Flip + + + + + + 853 + 1246 + 66 + 44 + + + 885 + 1268 + + + + + + Curve to flip + 417fea5c-f458-4cb9-81ea-3c456faf67b1 + Curve + C + false + b37fb167-edb3-4a28-b8fd-a498a7811210 + 1 + + + + + + 855 + 1248 + 15 + 20 + + + 864 + 1258 + + + + + + + + Optional guide curve + 0cee7869-6730-4b7d-832a-4aa6b5d4749a + Guide + G + true + 0 + + + + + + 855 + 1268 + 15 + 20 + + + 864 + 1278 + + + + + + + + Flipped curve + 0b2eef87-f147-4a36-b443-85031493ef71 + Curve + C + false + 0 + + + + + + 900 + 1248 + 17 + 20 + + + 908.5 + 1258 + + + + + + + + Flip action + d77f6082-7255-4694-8fae-7a31bce87565 + Flag + F + false + 0 + + + + + + 900 + 1268 + 17 + 20 + + + 908.5 + 1278 + + + + + + + + + + + + 11bbd48b-bb0a-4f1b-8167-fa297590390d + End Points + + + + + Extract the end points of a curve. + true + 3606e2e7-dfd8-4977-a4d6-75b937b9b9f1 + End Points + End + + + + + + 704 + 999 + 64 + 44 + + + 735 + 1021 + + + + + + Curve to evaluate + c35e0949-31c0-4148-833b-aeed3fc97aa3 + Curve + C + false + 1631fe30-c80f-49ec-9128-96481121ad52 + 1 + + + + + + 706 + 1001 + 14 + 40 + + + 714.5 + 1021 + + + + + + + + Curve start point + 349dd0c5-02cc-4111-abf4-8728c5a86c2c + Start + S + false + 0 + + + + + + 750 + 1001 + 16 + 20 + + + 758 + 1011 + + + + + + + + Curve end point + 08d2450a-ba8f-45b3-a29d-274a84997a85 + End + E + false + 0 + + + + + + 750 + 1021 + 16 + 20 + + + 758 + 1031 + + + + + + + + + + + + e9eb1dcf-92f6-4d4d-84ae-96222d60f56b + Move + + + + + Translate (move) an object along a vector. + true + 2a5a867a-a76c-4f02-bce5-209e79f61aac + Move + Move + + + + + + 810 + 1019 + 83 + 44 + + + 858 + 1041 + + + + + + Base geometry + a0edbf9a-3853-4ab0-b53f-aef5027fa137 + Geometry + G + true + 08d2450a-ba8f-45b3-a29d-274a84997a85 + 1 + + + + + + 812 + 1021 + 31 + 20 + + + 837 + 1031 + + + + + + + + Translation vector + 8acddbb8-4abe-4d9b-81aa-f36b38b9dda4 + -x + Motion + T + false + 3441a608-3853-45f3-92c7-1d1941125460 + 1 + + + + + + 812 + 1041 + 31 + 20 + + + 837 + 1051 + + + + + + 1 + + + + + 1 + {0} + + + + + + 0 + 0 + 10 + + + + + + + + + + + + Translated geometry + 3b2b25a6-3d8c-4a41-b0a2-3ea3be957972 + Geometry + G + false + 0 + + + + + + 873 + 1021 + 18 + 20 + + + 882 + 1031 + + + + + + + + Transformation data + d8dfb3a4-eb51-4258-aa5c-b6584682d0ef + Transform + X + false + 0 + + + + + + 873 + 1041 + 18 + 20 + + + 882 + 1051 + + + + + + + + + + + + 79f9fbb3-8f1d-4d9a-88a9-f7961b1012cd + Unit X + + + + + Unit vector parallel to the world {x} axis. + true + a8a91d2c-6d94-4451-b47e-a81520b31d82 + Unit X + X + + + + + + 490 + 1042 + 63 + 28 + + + 519 + 1056 + + + + + + Unit multiplication + 0640aeed-9882-4ebf-a1b1-5080f3b20bb9 + Factor + F + false + ae229730-bb17-41d8-826e-9f750d58d128 + 1 + + + + + + 492 + 1044 + 12 + 24 + + + 499.5 + 1056 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + World {x} vector + 20ce5606-82c9-43d0-b182-e8db425945b5 + Unit vector + V + false + 0 + + + + + + 534 + 1044 + 17 + 24 + + + 542.5 + 1056 + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + ae229730-bb17-41d8-826e-9f750d58d128 + Number Slider + + false + 0 + + + + + + 301 + 1048 + 163 + 20 + + + 301.1051 + 1048.834 + + + + + + 3 + 1 + 1 + 2000 + 0 + 0 + 0 + + + + + + + + + 4c4e56eb-2f04-43f9-95a3-cc46a14f495a + Line + + + + + Create a line between two points. + true + f29282aa-9bfd-49b7-a1a0-1f7fc87af0e9 + Line + Ln + + + + + + 920 + 999 + 63 + 44 + + + 951 + 1021 + + + + + + Line start point + cb99cc28-9c59-4191-95b9-3cae3e6f1e9c + Start Point + A + false + 349dd0c5-02cc-4111-abf4-8728c5a86c2c + 1 + + + + + + 922 + 1001 + 14 + 20 + + + 930.5 + 1011 + + + + + + + + Line end point + fb12ca80-6fc6-404b-8d93-4fdb6c2c6520 + End Point + B + false + 3b2b25a6-3d8c-4a41-b0a2-3ea3be957972 + 1 + + + + + + 922 + 1021 + 14 + 20 + + + 930.5 + 1031 + + + + + + + + Line segment + b37fb167-edb3-4a28-b8fd-a498a7811210 + Line + L + false + 0 + + + + + + 966 + 1001 + 15 + 40 + + + 973.5 + 1021 + + + + + + + + + + + + 9103c240-a6a9-4223-9b42-dbd19bf38e2b + Unit Z + + + + + Unit vector parallel to the world {z} axis. + true + 54426880-0968-40ad-8b1e-e50de0a67289 + Unit Z + Z + + + + + + 492 + 1076 + 63 + 28 + + + 521 + 1090 + + + + + + Unit multiplication + 45ecdc35-f4be-4e29-97b3-ae200336ed76 + Factor + F + false + 46feaa45-4a40-458f-a4fd-9de5989af911 + 1 + + + + + + 494 + 1078 + 12 + 24 + + + 501.5 + 1090 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + World {z} vector + 31dfa7fb-7b92-4e78-8a6c-51149c8b2402 + Unit vector + V + false + 0 + + + + + + 536 + 1078 + 17 + 24 + + + 544.5 + 1090 + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 46feaa45-4a40-458f-a4fd-9de5989af911 + Number Slider + + false + 0 + + + + + + 307 + 1081 + 163 + 20 + + + 307.3143 + 1081.519 + + + + + + 3 + 1 + 1 + 2000 + 0 + 0 + 0 + + + + + + + + + a0d62394-a118-422d-abb3-6af115c75b25 + Addition + + + + + Mathematical addition + true + 31a8ef0b-5079-4e66-9612-dff6a751c5a8 + Addition + A+B + + + + + + 705 + 1044 + 65 + 44 + + + 736 + 1066 + + + + + + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + First item for addition + d9d3d34d-5080-4bb5-aa7b-9e02635fbae5 + A + A + true + 20ce5606-82c9-43d0-b182-e8db425945b5 + 1 + + + + + + 707 + 1046 + 14 + 20 + + + 715.5 + 1056 + + + + + + + + Second item for addition + 3898220f-37d7-48fc-b80c-31e2b32274f5 + B + B + true + 31dfa7fb-7b92-4e78-8a6c-51149c8b2402 + 1 + + + + + + 707 + 1066 + 14 + 20 + + + 715.5 + 1076 + + + + + + + + Result of addition + 3441a608-3853-45f3-92c7-1d1941125460 + Result + R + false + 0 + + + + + + 751 + 1046 + 17 + 40 + + + 759.5 + 1066 + + + + + + + + + + + + + + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a + Boolean Toggle + + + + + Boolean (true/false) toggle + ca0ea429-34fb-4ce1-a30e-2c14ddc1552e + Boolean Toggle + FlipCurve + false + 0 + true + + + + + + 439 + 1198 + 115 + 22 + + + + + + + + + + eeafc956-268e-461d-8e73-ee05c6f72c01 + Stream Filter + + + + + Filters a collection of input streams + true + 9c98408b-5774-4f45-bb43-c16e5d4c92b4 + Stream Filter + Filter + + + + + + 934 + 1206 + 77 + 64 + + + 966 + 1238 + + + + + + 3 + 2e3ab970-8545-46bb-836c-1c11e5610bce + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + Index of Gate stream + be384cd0-c014-4922-86ee-f8be45625fad + Gate + G + false + ca0ea429-34fb-4ce1-a30e-2c14ddc1552e + 1 + + + + + + 936 + 1208 + 15 + 20 + + + 945 + 1218 + + + + + + 1 + + + + + 1 + {0} + + + + + 0 + + + + + + + + + + + 2 + Input stream at index 0 + b069b652-f7a4-47dc-a04c-9b3003b0e168 + false + Stream 0 + 0 + true + b37fb167-edb3-4a28-b8fd-a498a7811210 + 1 + + + + + + 936 + 1228 + 15 + 20 + + + 945 + 1238 + + + + + + + + 2 + Input stream at index 1 + 4bbd6850-b99a-47c4-a371-8adee5564955 + false + Stream 1 + 1 + true + 0b2eef87-f147-4a36-b443-85031493ef71 + 1 + + + + + + 936 + 1248 + 15 + 20 + + + 945 + 1258 + + + + + + + + 2 + Filtered stream + 09fe95fb-9551-4690-abf6-4a0665002914 + false + Stream + S(1) + false + 0 + + + + + + 981 + 1208 + 28 + 60 + + + 995 + 1238 + + + + + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 1631fe30-c80f-49ec-9128-96481121ad52 + ca0ea429-34fb-4ce1-a30e-2c14ddc1552e + 2 + 1dc4a321-0021-4a13-90df-e852c7e4e773 + Group + + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 18d2fa4a-de1b-4cfd-bf80-4b8d5fa0182f + a8a91d2c-6d94-4451-b47e-a81520b31d82 + ae229730-bb17-41d8-826e-9f750d58d128 + 54426880-0968-40ad-8b1e-e50de0a67289 + 46feaa45-4a40-458f-a4fd-9de5989af911 + 5 + f05fce76-e87f-48e1-a86d-07f6d4084049 + Group + + + + + + + + + + + 0148a65d-6f42-414a-9db7-9a9b2eb78437 + Brep Edges + + + + + Extract the edge curves of a brep. + 2253210f-5171-4c3a-9ada-77a25c570614 + Brep Edges + Edges + + + + + + 1234 + 1091 + 72 + 64 + + + 1264 + 1123 + + + + + + Base Brep + a1ae6357-26a0-48f8-87ed-a42ad7d83c0c + Brep + B + false + b0a6568e-06d8-4c68-b393-d8274f661640 + 1 + + + + + + 1236 + 1093 + 13 + 60 + + + 1244 + 1123 + + + + + + + + 1 + Naked edge curves + 50b5c643-729b-4fa5-96ea-b704123acb76 + Naked + En + false + 0 + + + + + + 1279 + 1093 + 25 + 20 + + + 1291.5 + 1103 + + + + + + + + 1 + Interior edge curves + 97b2db50-f0aa-43bd-8e24-5e5454f72868 + Interior + Ei + false + 0 + + + + + + 1279 + 1113 + 25 + 20 + + + 1291.5 + 1123 + + + + + + + + 1 + Non-Manifold edge curves + 5d1a1624-ff28-44ae-a91f-5c01f8a5243d + Non-Manifold + Em + false + 0 + + + + + + 1279 + 1133 + 25 + 20 + + + 1291.5 + 1143 + + + + + + + + + + + + + + + + + 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 + + + + + \ No newline at end of file From bcae5fed4b45c218d499b0ce59d5d7af9d6bfeba Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 8 Aug 2024 16:55:21 +0200 Subject: [PATCH 25/63] tested cutting_planes in gh --- src/compas_timber/_fabrication/step_joint.py | 429 +- tests/compas_timber/gh/test_step_joint.ghx | 6028 +++++------------- 2 files changed, 1694 insertions(+), 4763 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index 047fbf485..34507c32d 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -295,17 +295,21 @@ def _define_step_shape(step_depth, heel_depth, tapered_heel): raise ValueError("At least one of step_depth or heel_depth must be greater than 0.0.") @staticmethod - def _calculate_displacement_end(strut_height, strut_inclination, orientation): - # Calculates the linear displacement from the origin point to the end of the notch based on the strut_height and strut_inclination. - displacement_end = strut_height / math.sin(math.radians(strut_inclination)) + def _calculate_y_displacement_end(beam_height, strut_inclination): + # Calculates the linear displacement along the y-axis of the ref_side from the origin point to the opposite end of the step. + displacement_end = beam_height / math.sin(math.radians(strut_inclination)) return displacement_end @staticmethod - def _calculate_displacement_heel(heel_depth, strut_inclination, orientation): - # Calculates the linear displacement from the origin point to the heel of the notch based on the heel_depth and strut_inclination. - displacement_heel = abs( - heel_depth / (math.sin(math.radians(strut_inclination)) * math.cos(math.radians(strut_inclination))) - ) + def _calculate_x_displacement_end(beam_height, strut_inclination): + # Calculates the linear displacement along the x-axis of the ref_side from the origin point to the opposite end of the step. + displacement_end = beam_height / math.tan(math.radians(strut_inclination)) + return displacement_end + + @staticmethod + def _calculate_x_displacement_heel(heel_depth, strut_inclination, orientation): + # Calculates the linear displacement alond the x-axis of the ref_side from the origin point to the heel. + displacement_heel = heel_depth / (math.sin(math.radians(strut_inclination))) if orientation == OrientationType.END: displacement_heel = -displacement_heel return displacement_heel @@ -314,71 +318,71 @@ def _calculate_displacement_heel(heel_depth, strut_inclination, orientation): # Methods ######################################################################## - def apply(self, geometry, beam): - """Apply the feature to the beam geometry. - - Parameters - ---------- - geometry : :class:`~compas.geometry.Brep` - The beam geometry to be milled. - beam : :class:`compas_timber.elements.Beam` - The beam that is milled by this instance. - - Raises - ------ - :class:`~compas_timber.elements.FeatureApplicationError` - If the cutting planes do not create a volume that itersects with beam geometry or any step fails. - - Returns - ------- - :class:`~compas.geometry.Brep` - The resulting geometry after processing - - """ - # type: (Brep, Beam) -> Brep - # get cutting planes from params - try: - cutting_planes = self.planes_from_params_and_beam(beam) - except ValueError as e: - raise FeatureApplicationError( - None, geometry, "Failed to generate cutting planes from parameters and beam: {}".format(str(e)) - ) - # create notch polyedron from planes - # add ref_side plane to create a polyhedron - # !: the beam's ref_side Plane might need to be offsetted to create a valid polyhedron when step_type is "double" - cutting_planes.append(Plane.from_frame(beam.ref_sides[self.ref_side_index])) - try: - notch_polyhedron = Polyhedron.from_planes(cutting_planes) - except Exception as e: - raise FeatureApplicationError( - cutting_planes, geometry, "Failed to create valid polyhedron from cutting planes: {}".format(str(e)) - ) - # convert polyhedron to mesh - try: - notch_mesh = notch_polyhedron.to_mesh() - except Exception as e: - raise FeatureApplicationError(notch_polyhedron, geometry, "Failed to convert polyhedron to mesh: {}".format(str(e))) - # convert mesh to brep - try: - notch_brep = Brep.from_mesh(notch_mesh) - except Exception as e: - raise FeatureApplicationError(notch_mesh, geometry, "Failed to convert mesh to Brep: {}".format(str(e))) - # apply boolean difference - try: - brep_with_notch = Brep.from_boolean_difference(geometry, notch_brep) - except Exception as e: - raise FeatureApplicationError(notch_brep, geometry, "Boolean difference operation failed: {}".format(str(e))) - # check if the notch is empty - if not brep_with_notch: - raise FeatureApplicationError( - notch_brep, geometry, "The cutting planes do not create a volume that intersects with beam geometry." - ) - - if self.mortise: # !: implement mortise - # create mortise volume and subtract from brep_with_notch - pass - - return brep_with_notch + # def apply(self, geometry, beam): + # """Apply the feature to the beam geometry. + + # Parameters + # ---------- + # geometry : :class:`~compas.geometry.Brep` + # The beam geometry to be milled. + # beam : :class:`compas_timber.elements.Beam` + # The beam that is milled by this instance. + + # Raises + # ------ + # :class:`~compas_timber.elements.FeatureApplicationError` + # If the cutting planes do not create a volume that itersects with beam geometry or any step fails. + + # Returns + # ------- + # :class:`~compas.geometry.Brep` + # The resulting geometry after processing + + # """ + # # type: (Brep, Beam) -> Brep + # # get cutting planes from params + # try: + # cutting_planes = self.planes_from_params_and_beam(beam) + # except ValueError as e: + # raise FeatureApplicationError( + # None, geometry, "Failed to generate cutting planes from parameters and beam: {}".format(str(e)) + # ) + # # create notch polyedron from planes + # # add ref_side plane to create a polyhedron + # # !: the beam's ref_side Plane might need to be offsetted to create a valid polyhedron when step_type is "double" + # cutting_planes.append(Plane.from_frame(beam.ref_sides[self.ref_side_index])) + # try: + # notch_polyhedron = Polyhedron.from_planes(cutting_planes) + # except Exception as e: + # raise FeatureApplicationError( + # cutting_planes, geometry, "Failed to create valid polyhedron from cutting planes: {}".format(str(e)) + # ) + # # convert polyhedron to mesh + # try: + # notch_mesh = notch_polyhedron.to_mesh() + # except Exception as e: + # raise FeatureApplicationError(notch_polyhedron, geometry, "Failed to convert polyhedron to mesh: {}".format(str(e))) + # # convert mesh to brep + # try: + # notch_brep = Brep.from_mesh(notch_mesh) + # except Exception as e: + # raise FeatureApplicationError(notch_mesh, geometry, "Failed to convert mesh to Brep: {}".format(str(e))) + # # apply boolean difference + # try: + # brep_with_notch = Brep.from_boolean_difference(geometry, notch_brep) + # except Exception as e: + # raise FeatureApplicationError(notch_brep, geometry, "Boolean difference operation failed: {}".format(str(e))) + # # check if the notch is empty + # if not brep_with_notch: + # raise FeatureApplicationError( + # notch_brep, geometry, "The cutting planes do not create a volume that intersects with beam geometry." + # ) + + # if self.tenon: # !: implement tenon + # # create tenon volume and subtract from brep + # pass + + # return brep_with_notch def add_tenon(self, tenon_width, tenon_height): """Add a tenon to the existing StepJointNotch instance. @@ -410,256 +414,104 @@ def planes_from_params_and_beam(self, beam): :class:`compas.geometry.Plane` The cutting planes. """ + assert self.orientation is not None assert self.strut_inclination is not None assert self.step_shape is not None - print("orientation: ", self.orientation) - print("start_x: ", self.start_x) - print("strut_inclination: ", self.strut_inclination) - - displacement_end = self._calculate_displacement_end(beam.height, self.strut_inclination, self.orientation) - displacement_heel = self._calculate_displacement_heel(self.heel_depth, self.strut_inclination, self.orientation) - # Get the reference side as a PlanarSurface for the first cut ref_side = beam.side_as_surface(self.ref_side_index) # Get the opposite side as a PlanarSurface for the second cut and calculate the additional displacement along the xaxis opp_side = beam.side_as_surface((self.ref_side_index + 2) % 4) - opp_displacement = beam.height / abs(math.tan(math.radians(self.strut_inclination))) - rot_axis = ref_side.frame.yaxis + # Calculate the displacements for the cutting planes along the y-axis and x-axis + y_displacement_end = self._calculate_y_displacement_end(beam.height, self.strut_inclination) + x_displacement_end = self._calculate_x_displacement_end(beam.height, self.strut_inclination) + x_displacement_heel = self._calculate_x_displacement_heel(self.heel_depth, self.strut_inclination, self.orientation) - if self.orientation == OrientationType.END: - opp_displacement = -opp_displacement # Negative displacement for the end cut - # rot_axis = -rot_axis # Negative rotation axis for the end cut - - # Get the points at the start of the step and at the end + # Get the points at the start of the step, at the end and at the heel p_ref = ref_side.point_at(self.start_x, 0) - p_opp = opp_side.point_at(self.start_x + opp_displacement, 0) - p_heel = p_ref + Vector.from_start_end(p_ref, p_opp) * displacement_heel - - + p_opp = opp_side.point_at(self.start_x + x_displacement_end, beam.width) + p_heel = ref_side.point_at(self.start_x + x_displacement_heel, 0) + # Create cutting planes at the start of the step, at the end and at the heel cutting_plane_ref = Frame(p_ref, ref_side.frame.xaxis, ref_side.frame.yaxis) cutting_plane_opp = Frame(p_opp, ref_side.frame.xaxis, ref_side.frame.yaxis) cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - # if self.strut_inclination < 90: - # rot_angle_ref = math.radians((180 - self.strut_inclination) / 2) - - if self.step_shape == StepShape.STEP: - return self._calculate_step_planes(cutting_plane_ref, cutting_plane_opp, displacement_end) - elif self.step_shape == StepShape.HEEL: - return self._calculate_heel_planes(cutting_plane_ref, cutting_plane_heel) - # elif self.step_shape == StepShape.TAPERED_HEEL: - # return self._calculate_heel_tapered_planes(ref_side, rot_axis) - # elif self.step_shape == StepShape.DOUBLE: - # return self._calculate_double_planes(ref_side, rot_axis) - else: - pass - - def _calculate_step_planes(self, cutting_plane_ref, cutting_plane_opp, displacement_end): - """Calculate cutting planes for a step.""" if self.strut_inclination > 90: - ini_angle = self.strut_inclination + incl_angle = math.radians(self.strut_inclination) rot_axis = cutting_plane_ref.yaxis else: - ini_angle = 180 - self.strut_inclination + incl_angle = math.radians(180 - self.strut_inclination) rot_axis = -cutting_plane_ref.yaxis - # Calculate rotation angles - angle_opp = math.radians(ini_angle / 2) - angle_ref = math.radians(ini_angle) + self.step_depth / (displacement_end - self.step_depth / math.tan(angle_opp)) - # Rotate cutting plane at ref_side - rot_ref = Rotation.from_axis_and_angle(rot_axis, angle_ref, point=cutting_plane_ref.point) - cutting_plane_ref.transform(rot_ref) + if self.step_shape == StepShape.STEP: + return self._calculate_step_planes(cutting_plane_ref, cutting_plane_opp, y_displacement_end, incl_angle, rot_axis) + elif self.step_shape == StepShape.HEEL: + return self._calculate_heel_planes(cutting_plane_heel, cutting_plane_opp, incl_angle, rot_axis) + elif self.step_shape == StepShape.TAPERED_HEEL: + return self._calculate_heel_tapered_planes(cutting_plane_opp, y_displacement_end, incl_angle, rot_axis) + elif self.step_shape == StepShape.DOUBLE: + return self._calculate_double_planes(cutting_plane_heel, cutting_plane_opp, y_displacement_end, x_displacement_heel, incl_angle, rot_axis) + + def _calculate_step_planes(self, cutting_plane_ref, cutting_plane_opp, displacement_end, incl_angle, rot_axis): + """Calculate cutting planes for a step.""" # Rotate cutting plane at opp_side + angle_opp = incl_angle/2 rot_opp = Rotation.from_axis_and_angle(rot_axis, angle_opp, point=cutting_plane_opp.point) cutting_plane_opp.transform(rot_opp) - return [Plane.from_frame(cutting_plane_ref), Plane.from_frame(cutting_plane_opp)] - - def _calculate_heel_planes(self, cutting_plane_ref, cutting_plane_heel): - """Calculate cutting planes for a step.""" - if self.strut_inclination > 90: - ini_angle = self.strut_inclination - rot_axis = cutting_plane_ref.yaxis - else: - ini_angle = 180 - self.strut_inclination - rot_axis = -cutting_plane_ref.yaxis - - # Calculate rotation angles - angle_heel = math.radians(ini_angle) - angle_ref = math.radians(90) # Rotate cutting plane at ref_side + angle_ref = incl_angle + math.atan(self.step_depth / (displacement_end - self.step_depth / math.tan(angle_opp))) rot_ref = Rotation.from_axis_and_angle(rot_axis, angle_ref, point=cutting_plane_ref.point) cutting_plane_ref.transform(rot_ref) - # Rotate cutting plane at opp_side + + return [Plane.from_frame(cutting_plane_ref), Plane.from_frame(cutting_plane_opp)] + + def _calculate_heel_planes(self, cutting_plane_heel, cutting_plane_opp, incl_angle, rot_axis): + """Calculate cutting planes for a heel.""" + # Rotate cutting plane at displaced origin + angle_heel = math.radians(90) rot_heel = Rotation.from_axis_and_angle(rot_axis, angle_heel, point=cutting_plane_heel.point) cutting_plane_heel.transform(rot_heel) - return [Plane.from_frame(cutting_plane_ref), Plane.from_frame(cutting_plane_heel)] + # Rotate cutting plane at opp_side + rot_opp = Rotation.from_axis_and_angle(rot_axis, incl_angle, point=cutting_plane_opp.point) + cutting_plane_opp.transform(rot_opp) + return [Plane.from_frame(cutting_plane_heel), Plane.from_frame(cutting_plane_opp)] - """Calculate cutting planes for a heel notch.""" - if self.strut_inclination > 90: - # Move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x, self.start_y) - p_heel = ref_side.point_at(self.start_x + self.displacement_heel, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # Calculate heel cutting planes angles - # Rotate first cutting plane at the start of the notch (short side of the heel) - angle_short_side = math.radians(180 - self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) - cutting_plane_origin.transform(rot_short_side) - - # Rotate second cutting plane at the end of the notch (long side of the heel) - angle_long_side = math.radians(270 - self.strut_inclination) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_heel) - cutting_plane_heel.transform(rot_long_side) - else: - # Move the frames to the start and end of the notch to create the cuts - p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) - p_heel = ref_side.point_at(self.start_x + (self.displacement_end - self.displacement_heel), self.start_y) - cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # Calculate heel cutting planes angles - # Rotate first cutting plane at the displaced start of the notch (long side of the heel) - angle_long_side = math.radians(90 - self.strut_inclination) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_heel) - cutting_plane_heel.transform(rot_long_side) - - # Rotate second cutting plane at the end of the notch (short side of the heel) - angle_short_side = math.radians(180 - self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) - cutting_plane_end.transform(rot_short_side) - - return [Plane.from_frame(cutting_plane_heel), Plane.from_frame(cutting_plane_end)] - - def _calculate_heel_tapered_planes(self, ref_side, rot_axis): - """Calculate cutting planes for a tapered heel notch.""" - # Move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x, self.start_y) - p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # Calculate tapered heel cutting planes angles - if self.strut_inclination > 90: - # Rotate first cutting plane at the start of the notch (short side of the heel) - angle_short_side = math.radians(180 - self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) - cutting_plane_origin.transform(rot_short_side) - - # Rotate second cutting plane at the end of the notch (long side of the heel) - angle_long_side = math.radians(180) - math.atan( - self.heel_depth - / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination)))) - ) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) - cutting_plane_end.transform(rot_long_side) - else: - # Rotate first cutting plane at the start of the notch (long side of the heel) - angle_long_side = math.atan( - self.heel_depth - / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination)))) - ) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) - cutting_plane_origin.transform(rot_long_side) + def _calculate_heel_tapered_planes(self, cutting_plane_opp, displacement_end, incl_angle, rot_axis): + """Calculate cutting planes for a tapered heel.""" + # Rotate cutting plane at opp_side + angle_opp = incl_angle - math.atan(self.heel_depth / (displacement_end-(self.heel_depth / abs(math.tan(math.radians(self.strut_inclination)))))) + rot_opp = Rotation.from_axis_and_angle(rot_axis, angle_opp, point=cutting_plane_opp.point) + cutting_plane_opp.transform(rot_opp) - # Rotate second cutting plane at the end of the notch (short side of the heel) - angle_short_side = math.radians(180 - self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) - cutting_plane_end.transform(rot_short_side) + return [Plane.from_frame(cutting_plane_opp)] - return [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_end)] + def _calculate_double_planes(self, cutting_plane_heel, cutting_plane_opp, displacement_end, displacement_heel, incl_angle, rot_axis): + """Calculate cutting planes for a double step.""" + # Rotate first cutting plane at displaced origin + rot_origin = Rotation.from_axis_and_angle(rot_axis, math.radians(90), point=cutting_plane_heel.point) + cutting_plane_heel.transform(rot_origin) - def _calculate_double_planes(self, ref_side, rot_axis): - """Calculate cutting planes for a double notch.""" - if self.strut_inclination > 90: - # Move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x, self.start_y) - p_heel = ref_side.point_at(self.start_x + self.displacement_heel, self.start_y) - p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel_step = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # Calculate heel cutting planes angles - # Rotate first cutting plane at the start of the notch (short side of the heel) - angle_short_side_heel = math.radians(180 - self.strut_inclination) - rot_short_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_short_side_heel, point=p_origin) - cutting_plane_origin.transform(rot_short_side_heel) - - # Rotate second cutting plane at the end of the notch (long side of the heel) - angle_long_side_heel = math.radians(270 - self.strut_inclination) - rot_long_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_long_side_heel, point=p_heel) - cutting_plane_heel_heel.transform(rot_long_side_heel) - - # Calculate step cutting planes angles - # Rotate first cutting plane at the end of the heel of the notch (long side of the step) - angle_long_side_step = math.atan( - self.step_depth - / ( - self.displacement_end - - self.displacement_heel - - self.step_depth / math.tan(math.radians(self.strut_inclination / 2)) - ) - ) - rot_long_side_step = Rotation.from_axis_and_angle(rot_axis, angle_long_side_step, point=p_heel) - cutting_plane_heel_step.transform(rot_long_side_step) + # Rotate last cutting plane at opp_side + rot_opp = Rotation.from_axis_and_angle(rot_axis, incl_angle/2, point=cutting_plane_opp.point) + cutting_plane_opp.transform(rot_opp) - # Rotate second cutting plane at the end of the notch (short side of the step) - angle_short_side_step = math.radians(180 - self.strut_inclination / 2) - rot_short_side_step = Rotation.from_axis_and_angle(rot_axis, angle_short_side_step, point=p_end) - cutting_plane_end.transform(rot_short_side_step) - else: - # Move the frames to the start and end of the notch to create the cuts - p_origin = ref_side.point_at(self.start_x, self.start_y) - p_heel = ref_side.point_at(self.start_x + (self.displacement_end - self.displacement_heel), self.start_y) - p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel_step = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # Calculate step cutting planes angles - # Rotate first cutting plane at the start of the notch (short side of the step) - angle_short_side_step = math.radians(90 + self.strut_inclination / 2) - rot_short_side_step = Rotation.from_axis_and_angle(rot_axis, angle_short_side_step, point=p_origin) - cutting_plane_origin.transform(rot_short_side_step) - - # Rotate second cutting plane at the end of the notch (large side of the step) - angle_long_side_step = math.radians(180) - math.atan( - self.step_depth - / ( - self.displacement_end - - self.displacement_heel - - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2)) - ) - ) - rot_long_side_step = Rotation.from_axis_and_angle(rot_axis, angle_long_side_step, point=p_heel) - cutting_plane_heel_step.transform(rot_long_side_step) - - # Calculate heel cutting planes angles - # Rotate first cutting plane at the displaced start of the notch (long side of the heel) - angle_long_side_heel = math.radians(90 - self.strut_inclination) - rot_long_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_long_side_heel, point=p_heel) - cutting_plane_heel_heel.transform(rot_long_side_heel) - - # Rotate second cutting plane at the end of the notch (short side of the heel) - angle_short_side_heel = math.radians(180 - self.strut_inclination) - rot_short_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_short_side_heel, point=p_end) - cutting_plane_end.transform(rot_short_side_heel) - - return [ - Plane.from_frame(cutting_plane_origin), - Plane.from_frame(cutting_plane_heel_heel), - Plane.from_frame(cutting_plane_heel_step), - Plane.from_frame(cutting_plane_end), - ] + # Translate first cutting plane at heel + trans_len = math.tan(math.radians(self.strut_inclination)) * abs(displacement_heel) + trans_vect = -cutting_plane_heel.xaxis + cutting_plane_heel_mid = cutting_plane_heel.translated(trans_vect * trans_len) + # Calculate rotation angle for middle cutting plane + heel_hypotenus = math.sqrt(math.pow(trans_len, 2) + math.pow(displacement_heel, 2)) + angle_heel = incl_angle - math.radians(90) + math.atan(self.step_depth / (displacement_end - heel_hypotenus - self.step_depth / math.tan(incl_angle/2))) + + # Rotate middle cutting plane at heel + rot_heel = Rotation.from_axis_and_angle(rot_axis, angle_heel, point=cutting_plane_heel_mid.point) + cutting_plane_heel_mid.transform(rot_heel) + + return [Plane.from_frame(cutting_plane_heel), Plane.from_frame(cutting_plane_heel_mid), Plane.from_frame(cutting_plane_opp)] def tenon_volume_from_params_and_beam(self, beam): """Calculates the tenon volume from the machining parameters in this instance and the given beam @@ -725,7 +577,6 @@ def tenon_volume_from_params_and_beam(self, beam): return tenon_volume - class StepJointParams(BTLxProcessParams): """A class to store the parameters of a Step Joint feature. diff --git a/tests/compas_timber/gh/test_step_joint.ghx b/tests/compas_timber/gh/test_step_joint.ghx index db8ae90b3..4e2bcd73b 100644 --- a/tests/compas_timber/gh/test_step_joint.ghx +++ b/tests/compas_timber/gh/test_step_joint.ghx @@ -48,10 +48,10 @@ - -2212 - -1459 + 78 + 222 - 1.06250012 + 0.151132 @@ -95,10 +95,157 @@ - 90 + 68 - + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 1631fe30-c80f-49ec-9128-96481121ad52 + a8a91d2c-6d94-4451-b47e-a81520b31d82 + ae229730-bb17-41d8-826e-9f750d58d128 + 54426880-0968-40ad-8b1e-e50de0a67289 + 46feaa45-4a40-458f-a4fd-9de5989af911 + ca0ea429-34fb-4ce1-a30e-2c14ddc1552e + 1dc4a321-0021-4a13-90df-e852c7e4e773 + f05fce76-e87f-48e1-a86d-07f6d4084049 + 8 + 0b9fb065-5070-4c3f-96bb-831d0aa1aedf + Group + + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 37c9fe3d-b5bb-4362-ba0f-d5053fcb2141 + 7e4bcb53-01cc-446e-8792-764d02b1451e + c3c1dc6d-7ceb-4d7f-add6-fa924b4cc936 + ab977350-6a8d-472b-9276-52a400da755b + dec5089d-c452-417b-a948-4034d6df42ce + 1f382d1c-8a89-4e8a-8102-597801cd81a3 + ba1c263a-665b-4a0d-a1a6-e771de90f422 + 62e63fca-99eb-4ff3-8b10-df2b88807b8d + d6206efa-67b1-4c9e-b762-42a2cf302219 + 9 + c67c3391-fbfa-4fd4-bd01-edf5270cbdc6 + Group + + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + d61a37f6-bab7-4ae1-ba80-a47758fff264 + 52c22682-fb60-48d8-ae45-8e60302c086f + 7154ee57-be53-4a07-8da4-9858a1faea73 + def048ab-0443-4189-81d6-03594953cdd1 + aa461d11-c653-411c-a341-b7900f1ccbd6 + 5ad83719-a82f-419e-8606-51aa09e83d11 + bec3cac8-0564-45af-9acc-545be5c1d193 + 6e7fcdb6-9b16-4ad8-90a6-6d7bd2fe16a2 + b8e83b08-cfb9-49e9-b847-ee42b020c654 + 1045a7ec-3546-4cfc-9e61-e9796268138a + 927c55d5-99d0-47f9-b656-3cef4c9f5214 + 0cbceed4-3434-411c-b4d1-46985ef14dea + e05aff55-35f8-4083-ad48-522d9cd323c3 + 838171ab-f4f9-4bf2-a5f1-ee9d41fdbb93 + 1a7e2e2e-920a-46d7-9dbf-b472992570ce + 8af59bcf-e43d-4808-b965-440e7d003dfd + 14f73845-d137-4e4f-939d-fc35194fd72e + b6362569-cd6b-4fdf-a6bc-af4ac10ea9bf + 18 + be8cdbba-cb4f-49c1-b803-b14c701a75ca + Group + + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + ca34b946-50be-42d7-8e6d-080419aa1aba + cfb4b1f8-a02e-44ce-9c95-80b1164d7fca + a5869824-14fc-400f-a69e-f5050392632f + 9e4f0b21-759b-4c9a-8dfb-6654484027c5 + be8a15e2-8773-4720-9d61-dfdc10761646 + 482410b9-724c-46d6-be3a-3d63785bc853 + 9c473eb6-ff8f-4c7f-bd69-56527d0257f6 + 88b4a335-192d-48a1-81d5-163278eaecbe + 84325f58-aa64-43ef-9c59-6631e3ff4da9 + 234eec62-a3cc-4178-9760-678a11912fb0 + 86bc7ac3-bdd8-443c-ae9b-e22ea6ec103a + 56020ec2-ccb1-4ae9-a538-84dcb2857db9 + 541e5f17-7a28-48ff-8820-37e3076647dd + d22d460d-f45b-4227-b1b7-0d4f280ddbcc + c0a53353-9802-4df7-a063-6228afad4537 + e5f92ef7-182d-4ea0-8d5a-b01aada26ebb + 16 + 79ebf2fa-3075-4ae3-8ba4-692a9210d262 + Group + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e 00000000-0000-0000-0000-000000000000 @@ -130,15 +277,16 @@ plane = main_beam.ref_sides[index] step_joint_notch = StepJointNotch.from_surface_and_beam(plane, beam, notch_limited=False, step_depth=step, heel_depth=heel, strut_height=100.0, tapered_heel=tapered, ref_side_index=ref_side_index) cutting_planes = step_joint_notch.planes_from_params_and_beam(beam) -print("Orientation: ", step_joint_notch.orientation) -print("StartX: ", step_joint_notch.start_x) -print("StrutInclination: ", step_joint_notch.strut_inclination) +#print("Orientation: ", step_joint_notch.orientation) +#print("StartX: ", step_joint_notch.start_x) +#print("StrutInclination: ", step_joint_notch.strut_inclination) #add mortise if mortise_height > 0: step_joint_notch.add_mortise(beam.width/4, mortise_height, beam) mortise_polylines = step_joint_notch.mortise_volume_from_params_and_beam(beam) + rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_polylines] ##apply geometric features #step_joint_notch.apply(brep, beam) @@ -155,7 +303,7 @@ rg_ref_side = frame_to_rhino(beam.ref_sides[ref_side_index]) rg_cutting_plane = frame_to_rhino(plane) rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) -rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_polylines] + @@ -182,14 +330,14 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - 1987 - 695 + 2259 + 368 222 164 - 2083 - 777 + 2355 + 450 @@ -224,7 +372,7 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly true 0 true - c38af54d-dfec-411f-ab38-22c657830b82 + 3984d0df-294e-462f-b103-b87bb94e3021 1 87f87f55-5b71-41f4-8aea-21d494016f81 @@ -232,14 +380,14 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - 1989 - 697 + 2261 + 370 79 20 - 2030 - 707 + 2302 + 380 @@ -255,7 +403,7 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly true 0 true - 19ba4d57-8c61-4bac-90d7-412dfcb8ac0b + 90fae02f-7750-462d-828f-cf5f2a0d39ae 1 87f87f55-5b71-41f4-8aea-21d494016f81 @@ -263,14 +411,14 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - 1989 - 717 + 2261 + 390 79 20 - 2030 - 727 + 2302 + 400 @@ -286,7 +434,7 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly true 0 true - c18f9e62-d6f5-42c4-a426-10688f24ca61 + 7e4bcb53-01cc-446e-8792-764d02b1451e 1 48d01794-d3d8-4aef-990e-127168822244 @@ -294,14 +442,14 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - 1989 - 737 + 2261 + 410 79 20 - 2030 - 747 + 2302 + 420 @@ -317,7 +465,7 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly true 0 true - fd67193c-c124-4e9b-bf38-3d6a6e085438 + 37c9fe3d-b5bb-4362-ba0f-d5053fcb2141 1 48d01794-d3d8-4aef-990e-127168822244 @@ -325,14 +473,14 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - 1989 - 757 + 2261 + 430 79 20 - 2030 - 767 + 2302 + 440 @@ -348,7 +496,7 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly true 0 true - d5f970b8-28fa-4f28-b038-8f3eed5c682d + c3c1dc6d-7ceb-4d7f-add6-fa924b4cc936 1 87f87f55-5b71-41f4-8aea-21d494016f81 @@ -356,14 +504,14 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - 1989 - 777 + 2261 + 450 79 20 - 2030 - 787 + 2302 + 460 @@ -379,7 +527,7 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly true 0 true - c1e88145-a742-40e8-9b8b-a549422a646d + ab977350-6a8d-472b-9276-52a400da755b 1 87f87f55-5b71-41f4-8aea-21d494016f81 @@ -387,14 +535,14 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - 1989 - 797 + 2261 + 470 79 20 - 2030 - 807 + 2302 + 480 @@ -410,7 +558,7 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly true 0 true - c925fa54-d90e-456f-863a-2f4f0a2857ba + dec5089d-c452-417b-a948-4034d6df42ce 1 87f87f55-5b71-41f4-8aea-21d494016f81 @@ -418,14 +566,14 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - 1989 - 817 + 2261 + 490 79 20 - 2030 - 827 + 2302 + 500 @@ -441,7 +589,7 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly true 0 true - 654077fb-9447-42ca-a4ed-d6a4dd8d1874 + 1f382d1c-8a89-4e8a-8102-597801cd81a3 1 87f87f55-5b71-41f4-8aea-21d494016f81 @@ -449,14 +597,14 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - 1989 - 837 + 2261 + 510 79 20 - 2030 - 847 + 2302 + 520 @@ -475,14 +623,14 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - 2098 - 697 + 2370 + 370 109 22 - 2152.5 - 708.4286 + 2424.5 + 381.4286 @@ -501,14 +649,14 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - 2098 - 719 + 2370 + 392 109 23 - 2152.5 - 731.2857 + 2424.5 + 404.2857 @@ -527,14 +675,14 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - 2098 - 742 + 2370 + 415 109 23 - 2152.5 - 754.1429 + 2424.5 + 427.1429 @@ -553,14 +701,14 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - 2098 - 765 + 2370 + 438 109 23 - 2152.5 - 777 + 2424.5 + 450 @@ -579,14 +727,14 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - 2098 - 788 + 2370 + 461 109 23 - 2152.5 - 799.8572 + 2424.5 + 472.8571 @@ -605,14 +753,14 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - 2098 - 811 + 2370 + 484 109 23 - 2152.5 - 822.7143 + 2424.5 + 495.7143 @@ -631,14 +779,14 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - 2098 - 834 + 2370 + 507 109 - 22 + 23 - 2152.5 - 845.5714 + 2424.5 + 518.5714 @@ -650,436 +798,234 @@ rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_poly - + - d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 - Curve + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel - - Contains a collection of generic curves - true - 6bbc142b-2536-41bf-a215-ea52704d32b6 - Curve - Crv + + A panel for custom notes and text values + cfb4b1f8-a02e-44ce-9c95-80b1164d7fca + Panel + BTLx Params false - 0 + 0 + d525eccb-5bff-4563-b75e-472cbbdc5901 + 1 + Double click to edit panel content… - + - 521 - 459 - 50 - 24 + 2672 + 601 + 212 + 333 + 0 + 0 + 0 - 546.5278 - 471.428 + 2672.977 + 601.1305 - - - 1 + + + + 255;255;255;255 + + true + true + true + false + false + true - - - - 1 - {0} - - - - - -1 - - Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesYmekbGRmbmeoaGBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS9f/4eDiZgQx+EDEViJl+1TMxdEL99fs/E4MIlP3synU/95eXBfc/ZRBoZlT64Ddn9+0/QPlAqLwAyNzYbxpcrDPvOXz8/18+rHjlAYmi2O741CCH48eNlj7zvu9wBCoe0mqltvDcBAcmBgTY2xy1xs52sgPIJY3vmdz+1zM1wOS4GQYTAAA= - - 00000000-0000-0000-0000-000000000000 - - - - - - - - 410755b1-224a-4c1e-a407-bf32fb45ea7e - 00000000-0000-0000-0000-000000000000 - CT: Beam + + + f31d8d7a-7536-4ac8-9c96-fde6ecda4d0a + Cluster - - """Creates a Beam from a LineCurve.""" - -import rhinoscriptsyntax as rs -from compas.scene import Scene -from compas_rhino.conversions import line_to_compas -from compas_rhino.conversions import vector_to_compas -from ghpythonlib.componentbase import executingcomponent as component -from Grasshopper.Kernel.GH_RuntimeMessageLevel import Error -from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning -from Rhino.RhinoDoc import ActiveDoc - -from compas_timber.elements import Beam as CTBeam -from compas_timber.ghpython.rhino_object_name_attributes import update_rhobj_attributes_name - - -class Beam_fromCurve(component): - def RunScript(self, centerline, z_vector, width, height, category, updateRefObj): - # minimum inputs required - if not centerline: - self.AddRuntimeMessage(Warning, "Input parameter 'Centerline' failed to collect data") - if not width: - self.AddRuntimeMessage(Warning, "Input parameter 'Width' failed to collect data") - if not height: - self.AddRuntimeMessage(Warning, "Input parameter 'Height' failed to collect data") - - # reformat unset parameters for consistency - if not z_vector: - z_vector = [None] - if not category: - category = [None] - - beams = [] - blanks = [] - scene = Scene() - - if centerline and height and width: - # check list lengths for consistency - N = len(centerline) - if len(z_vector) not in (0, 1, N): - self.AddRuntimeMessage( - Error, " In 'ZVector' I need either none, one or the same number of inputs as the Crv parameter." - ) - if len(width) not in (1, N): - self.AddRuntimeMessage( - Error, " In 'W' I need either one or the same number of inputs as the Crv parameter." - ) - if len(height) not in (1, N): - self.AddRuntimeMessage( - Error, " In 'H' I need either one or the same number of inputs as the Crv parameter." - ) - if len(category) not in (0, 1, N): - self.AddRuntimeMessage( - Error, " In 'Category' I need either none, one or the same number of inputs as the Crv parameter." - ) - - # duplicate data if None or single value - if len(z_vector) != N: - z_vector = [z_vector[0] for _ in range(N)] - if len(width) != N: - width = [width[0] for _ in range(N)] - if len(height) != N: - height = [height[0] for _ in range(N)] - if len(category) != N: - category = [category[0] for _ in range(N)] - - for line, z, w, h, c in zip(centerline, z_vector, width, height, category): - guid, geometry = self._get_guid_and_geometry(line) - rhino_line = rs.coerceline(geometry) - line = line_to_compas(rhino_line) - - z = vector_to_compas(z) if z else None - 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)) - update_rhobj_attributes_name(guid, "zvector", str(list(beam.frame.zaxis))) - update_rhobj_attributes_name(guid, "category", c) - - beams.append(beam) - scene.add(beam.blank) - - blanks = scene.draw() - - return beams, blanks - - def _get_guid_and_geometry(self, line): - # internalized curves and GH geometry will not have persistent GUIDs, referenced Rhino objects will - # type hint on the input has to be 'ghdoc' for this to work - guid = None - geometry = line - rhino_obj = ActiveDoc.Objects.FindId(line) - if rhino_obj: - guid = line - geometry = rhino_obj.Geometry - return guid, geometry - - Creates a Beam from a LineCurve. - true - true - true - - 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 - false - 61e455d1-1197-42f2-9b19-c9255984e680 - true - true - CT: Beam - Beam + Contains a cluster of Grasshopper components + true + a5869824-14fc-400f-a69e-f5050392632f + Cluster + Cluster + false - + + + + 5 + 3f3af581-d64e-4909-91e4-3576cf2e9e86 + 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 + d28e1b27-74ad-41ab-b017-540fa9111876 + ebd18bdb-78c3-4d40-8a69-e1af30a53c27 + fd03a9d4-272c-4590-9595-4d3107dc0fdc + 3c631e1e-b12f-4297-9535-87b4fdc7b45e + 5d665740-a9cc-4f15-8a38-0dc75e214ae2 + 5d32325d-62cf-40bd-93fe-74af56a2c91e + b360d350-2b53-401b-9420-d9402019cb30 + 796ac502-faba-4bb6-a612-7e3dfb448d98 + + - 1076 - 480 - 145 - 124 + 2653 + 429 + 72 + 84 - 1167 - 542 + 2692 + 471 - - 6 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 2 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 + + 4 + 919e146f-30ae-4aae-be34-4d72f555e7da + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + 1 + 919e146f-30ae-4aae-be34-4d72f555e7da - + - - 1 - true - Referenced curve or line, Guid of curve or line in the active Rhino document. - af8ecabe-1f50-44f9-9732-a28f0101da66 - Centerline - Centerline + + Brep to split + ebd18bdb-78c3-4d40-8a69-e1af30a53c27 + Brep + B true - 1 - true - 82ddb61d-d21e-49ce-a61c-851130becb9a + b0a6568e-06d8-4c68-b393-d8274f661640 1 - 87f87f55-5b71-41f4-8aea-21d494016f81 - 1078 - 482 - 74 + 2655 + 431 + 22 20 - 1116.5 - 492 + 2667.5 + 441 - - 1 - true - Vector defining the orientation of the cross-section (corresponds to the height dimension). If None, a default will be taken. - e24b6965-6eb8-44c9-a93f-921178776153 - ZVector - ZVector + + Contains a collection of three-dimensional axis-systems + d28e1b27-74ad-41ab-b017-540fa9111876 + Plane + Pln true - 1 - true - 0 - 15a50725-e3d3-4075-9f7c-142ba5f40747 + 16bc55f9-a035-4099-834e-e9dc0931b695 + 1 - 1078 - 502 - 74 + 2655 + 451 + 22 20 - 1116.5 - 512 + 2667.5 + 461 - - 1 - true - Width of the cross-section (usually the smaller dimension). - 3141853d-6310-4d57-8fcb-4485e2106585 - Width - Width + + Contains a collection of three-dimensional axis-systems + fd03a9d4-272c-4590-9595-4d3107dc0fdc + Plane + Pln true - 1 - true - 5cc940ad-f898-479c-8389-be3142764477 + ddc1d959-4491-4f72-b7d5-a74d74b99385 1 - 39fbc626-7a01-46ab-a18e-ec1c0c41685b - 1078 - 522 - 74 + 2655 + 471 + 22 20 - 1116.5 - 532 + 2667.5 + 481 - + 1 - true - Height of the cross-section (usually the larger dimension). - 649d67fb-cb07-4166-b6a6-1412ba66780a - Height - Height + Section curves + 3f3af581-d64e-4909-91e4-3576cf2e9e86 + Curves + C true - 1 - true - 5d71a959-57a2-4f08-b6ec-584164b09c95 + 1a4781d4-bfe8-4573-86bb-90d0f0ffc197 1 - 39fbc626-7a01-46ab-a18e-ec1c0c41685b - - - - - - 1078 - 542 - 74 - 20 - - - 1116.5 - 552 - - - - - - - - 1 - true - Category of a beam. - e7afb315-d6b9-4da4-8b6f-5c05aa8b1895 - Category - Category - true - 1 - true - 0 - 37261734-eec7-4f50-b6a8-b8d1f3c4396b - - - - - - 1078 - 562 - 74 - 20 - - - 1116.5 - 572 - - - - - - - - true - (optional) If True, the attributes in the referenced object will be updated/overwritten with the new values given here. - b9a3c1d6-5e9a-407b-8be2-e4e66ceda80e - updateRefObj - updateRefObj - true - 0 - true - 0 - d60527f5-b5af-4ef6-8970-5f96fe412559 - 1078 - 582 - 74 + 2655 + 491 + 22 20 - 1116.5 - 592 + 2667.5 + 501 - - Beam object(s). - 19ba4d57-8c61-4bac-90d7-412dfcb8ac0b - Beam - Beam - false - 0 - - - - - - 1182 - 482 - 37 - 60 - - - 1200.5 - 512 - - - - - - - - Shape of the beam's blank. - 7ccf81c5-136d-420f-b550-4a331abbcb49 - Blank - Blank + + 1 + Joined Breps + 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 + Breps + B false 0 @@ -1087,14 +1033,14 @@ class Beam_fromCurve(component): - 1182 - 542 - 37 - 60 + 2707 + 431 + 16 + 80 - 1200.5 - 572 + 2715 + 471 @@ -1106,2577 +1052,87 @@ class Beam_fromCurve(component): - + - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider + 537b0419-bbc2-4ff4-bf08-afe526367b2c + Custom Preview - - Numeric slider for single values - 5d71a959-57a2-4f08-b6ec-584164b09c95 - Number Slider - - false - 0 + + Allows for customized geometry previews + true + false + 9e4f0b21-759b-4c9a-8dfb-6654484027c5 + Custom Preview + Preview + - + - 853 - 550 - 166 - 20 - - - 853.4926 - 550.1252 - - - - - - 3 - 1 - 1 - 100 - 0 - 0 - 100 - - - - - - - - - d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 - Curve - - - - - Contains a collection of generic curves - true - 2f78b16d-80d1-4502-954e-49040fe761a3 - Curve - Crv - false - 0 - - - - - - 472 - 684 - 50 - 24 - - - 497.5087 - 696.4502 - - - - - - 1 - - - - - 1 - {0} - - - - - -1 - - Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesYmekbGRmbmeoaGBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS9f/4eDiZgQx+EDEViJl+1TMxdEL99fs/E4MIlP3synU/95eXBfc/ZRBoZlT64Ddn9+0/QPlAqLwAyFyF5Ln1s/nvOXz8/18+rHjlAYmi2O741CCHCRvP7T9b98DhM5o4EwMaKFjuAHKJ94LooP/1TA0wYW50dQMKAA== - - 00000000-0000-0000-0000-000000000000 - - - - - - - - - - - - - 410755b1-224a-4c1e-a407-bf32fb45ea7e - 00000000-0000-0000-0000-000000000000 - CT: Beam - - - - - """Creates a Beam from a LineCurve.""" - -import rhinoscriptsyntax as rs -from compas.scene import Scene -from compas_rhino.conversions import line_to_compas -from compas_rhino.conversions import vector_to_compas -from ghpythonlib.componentbase import executingcomponent as component -from Grasshopper.Kernel.GH_RuntimeMessageLevel import Error -from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning -from Rhino.RhinoDoc import ActiveDoc - -from compas_timber.elements import Beam as CTBeam -from compas_timber.ghpython.rhino_object_name_attributes import update_rhobj_attributes_name - - -class Beam_fromCurve(component): - def RunScript(self, centerline, z_vector, width, height, category, updateRefObj): - # minimum inputs required - if not centerline: - self.AddRuntimeMessage(Warning, "Input parameter 'Centerline' failed to collect data") - if not width: - self.AddRuntimeMessage(Warning, "Input parameter 'Width' failed to collect data") - if not height: - self.AddRuntimeMessage(Warning, "Input parameter 'Height' failed to collect data") - - # reformat unset parameters for consistency - if not z_vector: - z_vector = [None] - if not category: - category = [None] - - beams = [] - blanks = [] - scene = Scene() - - if centerline and height and width: - # check list lengths for consistency - N = len(centerline) - if len(z_vector) not in (0, 1, N): - self.AddRuntimeMessage( - Error, " In 'ZVector' I need either none, one or the same number of inputs as the Crv parameter." - ) - if len(width) not in (1, N): - self.AddRuntimeMessage( - Error, " In 'W' I need either one or the same number of inputs as the Crv parameter." - ) - if len(height) not in (1, N): - self.AddRuntimeMessage( - Error, " In 'H' I need either one or the same number of inputs as the Crv parameter." - ) - if len(category) not in (0, 1, N): - self.AddRuntimeMessage( - Error, " In 'Category' I need either none, one or the same number of inputs as the Crv parameter." - ) - - # duplicate data if None or single value - if len(z_vector) != N: - z_vector = [z_vector[0] for _ in range(N)] - if len(width) != N: - width = [width[0] for _ in range(N)] - if len(height) != N: - height = [height[0] for _ in range(N)] - if len(category) != N: - category = [category[0] for _ in range(N)] - - for line, z, w, h, c in zip(centerline, z_vector, width, height, category): - guid, geometry = self._get_guid_and_geometry(line) - rhino_line = rs.coerceline(geometry) - line = line_to_compas(rhino_line) - - z = vector_to_compas(z) if z else None - 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)) - update_rhobj_attributes_name(guid, "zvector", str(list(beam.frame.zaxis))) - update_rhobj_attributes_name(guid, "category", c) - - beams.append(beam) - scene.add(beam.blank) - - blanks = scene.draw() - - return beams, blanks - - def _get_guid_and_geometry(self, line): - # internalized curves and GH geometry will not have persistent GUIDs, referenced Rhino objects will - # type hint on the input has to be 'ghdoc' for this to work - guid = None - geometry = line - rhino_obj = ActiveDoc.Objects.FindId(line) - if rhino_obj: - guid = line - geometry = rhino_obj.Geometry - return guid, geometry - - Creates a Beam from a LineCurve. - true - true - true - - iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDgAACw4BQL7hQQAAAhRJREFUSEulVLtKA0EUzSfkD3zlrUKMIcHGB6ghIj5QxEoDiiA2gtqGYBEfRRQiSRc7iXU6IWKZwh8Q/AI/wEcRr/cMmWUZ7ya7Whw2uXvvOTNnzqyPiP6Nob7+Q0bBQJ0xLQ64BRP4R6OxZ37Swf4+nRwdKyxmFyg4MEhcvxYH3YCH4+GhwHsqMd6+r9ep1WrRY7OpyGcmp2hvZxcCBXG4F3gQligykIIcIulkknJb26qGnXgW4AF/NBi6AzkIQAxcnl+AjAr5vFXzLMDN8ZFI9JXxfVMuW0RYMZ8D3dZqVs2zADeuRALBD3jbaDQUAZ74D2ib/iQwHI5UudHyFsNYLVaN5JjEGj0F+KUVQXisB+EzyO01CV0F+IUYwY21dZUUXXMCemGdKMBFMYIYgIDktx2wD0FAILBQO7FjBGGJveYE2Id5Jn8AH3g1uRhBHCLI7TUJ2BVCAHLuP9eLVgJc3GS/vyZSaboqldQWq5UKZeczyhYdSyfouCLGzLViJ9cCT1A2sbq03NNv7Ay7joXCLzzTb5JbAqa/mdm5XzfTRCeG1Dk35bcEUQAJchLArpAmkDMOJVI7PAkgrrgbuCM8F5cITbgWQFyHI9F2x29HS0y4EkBcuU/5LZF0Q1cB+I3fHMFP7stJBL3gKHBWLOoIvnGPK78lKAEQQkQjOZZQlnS+pq79lgABfNxw2UycSgPeQL4fsKzVEEQlKEcAAAAASUVORK5CYII= - - false - efe8bffe-0848-4d32-993f-7f43d93bbf01 - true - true - CT: Beam - Beam - - - - - - 1074 - 688 - 145 - 124 - - - 1165 - 750 - - - - - - 6 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 2 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - - - - - 1 - true - Referenced curve or line, Guid of curve or line in the active Rhino document. - 8ca4719b-64bb-4a1e-bd21-47829a6da8fb - Centerline - Centerline - true - 1 - true - 919e0b0e-50c5-4983-bf15-4bb7b9f39c0a - 1 - 87f87f55-5b71-41f4-8aea-21d494016f81 - - - - - - 1076 - 690 - 74 - 20 - - - 1114.5 - 700 - - - - - - - - 1 - true - Vector defining the orientation of the cross-section (corresponds to the height dimension). If None, a default will be taken. - e4854a14-8e9a-421e-a9c2-b070f3e54f84 - ZVector - ZVector - true - 1 - true - 0 - 15a50725-e3d3-4075-9f7c-142ba5f40747 - - - - - - 1076 - 710 - 74 - 20 - - - 1114.5 - 720 - - - - - - - - 1 - true - Width of the cross-section (usually the smaller dimension). - 4941e70a-0c52-4f0e-829c-88c706d76cab - Width - Width - true - 1 - true - f40578da-c08b-4e1d-b010-8b988d3269a2 - 1 - 39fbc626-7a01-46ab-a18e-ec1c0c41685b - - - - - - 1076 - 730 - 74 - 20 - - - 1114.5 - 740 - - - - - - - - 1 - true - Height of the cross-section (usually the larger dimension). - 86c7e8a6-883d-41bb-8889-46964d8e84b3 - Height - Height - true - 1 - true - f40578da-c08b-4e1d-b010-8b988d3269a2 - 1 - 39fbc626-7a01-46ab-a18e-ec1c0c41685b - - - - - - 1076 - 750 - 74 - 20 - - - 1114.5 - 760 - - - - - - - - 1 - true - Category of a beam. - f30f6c3e-93b2-40b0-aa59-ae87fdde3fe1 - Category - Category - true - 1 - true - 0 - 37261734-eec7-4f50-b6a8-b8d1f3c4396b - - - - - - 1076 - 770 - 74 - 20 - - - 1114.5 - 780 - - - - - - - - true - (optional) If True, the attributes in the referenced object will be updated/overwritten with the new values given here. - 63f2f6d5-c8f8-446e-af8e-245bc6bd3b84 - updateRefObj - updateRefObj - true - 0 - true - 0 - d60527f5-b5af-4ef6-8970-5f96fe412559 - - - - - - 1076 - 790 - 74 - 20 - - - 1114.5 - 800 - - - - - - - - Beam object(s). - c38af54d-dfec-411f-ab38-22c657830b82 - Beam - Beam - false - 0 - - - - - - 1180 - 690 - 37 - 60 - - - 1198.5 - 720 - - - - - - - - Shape of the beam's blank. - 489d49f6-b379-49e3-8aed-197b4a1468a4 - Blank - Blank - false - 0 - - - - - - 1180 - 750 - 37 - 60 - - - 1198.5 - 780 - - - - - - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - f40578da-c08b-4e1d-b010-8b988d3269a2 - Number Slider - - false - 0 - - - - - - 888 - 745 - 166 - 20 - - - 888.4611 - 745.2539 - - - - - - 3 - 1 - 1 - 100 - 0 - 0 - 100 - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - 5cc940ad-f898-479c-8389-be3142764477 - Number Slider - - false - 0 - - - - - - 853 - 526 - 166 - 20 - - - 853.5906 - 526.7526 - - - - - - 3 - 1 - 1 - 100 - 0 - 0 - 80 - - - - - - - - - 410755b1-224a-4c1e-a407-bf32fb45ea7e - 00000000-0000-0000-0000-000000000000 - CT: DecomposeBeam - - - - - # flake8: noqa -from compas.geometry import Line -from compas_rhino.conversions import frame_to_rhino_plane -from compas_rhino.conversions import line_to_rhino -from compas_rhino.conversions import point_to_rhino -from compas_rhino.conversions import box_to_rhino -from ghpythonlib.componentbase import executingcomponent as component -from System.Drawing import Color - - -class BeamDecompose(component): - RED = Color.FromArgb(255, 255, 100, 100) - GREEN = Color.FromArgb(200, 50, 220, 100) - BLUE = Color.FromArgb(200, 50, 150, 255) - WHITE = Color.FromArgb(255, 255, 255, 255) - YELLOW = Color.FromArgb(255, 255, 255, 0) - SCREEN_SIZE = 10 - RELATIVE_SIZE = 0 - - def RunScript(self, beam, show_frame, show_faces): - self.show_faces = show_faces if show_faces is not None else False - self.show_frame = show_frame if show_frame is not None else False - self.frames = [] - self.rhino_frames = [] - self.scales = [] - self.faces = [] - self.width = [] - self.height = [] - self.centerline = [] - self.shapes = [] - - for b in beam: - self.frames.append(b.frame) - self.rhino_frames.append(frame_to_rhino_plane(b.frame)) - self.scales.append(b.width + b.height) - self.centerline.append(line_to_rhino(b.centerline)) - self.shapes.append(box_to_rhino(b.shape)) - self.width.append(b.width) - self.height.append(b.height) - self.faces.append(b.faces) - - return self.rhino_frames, self.centerline, self.shapes, self.width, self.height - - def DrawViewportWires(self, arg): - if self.Locked: - return - - for f, s, faces in zip(self.frames, self.scales, self.faces): - if self.show_frame: - self._draw_frame(arg.Display, f, s) - if self.show_faces: - self._draw_faces(arg.Display, faces, s) - - def _draw_frame(self, display, frame, scale): - x = Line.from_point_and_vector(frame.point, frame.xaxis * scale) - y = Line.from_point_and_vector(frame.point, frame.yaxis * scale) - z = Line.from_point_and_vector(frame.point, frame.zaxis * scale) - display.DrawArrow(line_to_rhino(x), self.RED, self.SCREEN_SIZE, self.RELATIVE_SIZE) - display.DrawArrow(line_to_rhino(y), self.GREEN, self.SCREEN_SIZE, self.RELATIVE_SIZE) - display.DrawArrow(line_to_rhino(z), self.BLUE, self.SCREEN_SIZE, self.RELATIVE_SIZE) - - x_loc = x.end + x.vector * scale * 1.1 - y_loc = y.end + y.vector * scale * 1.1 - z_loc = z.end + z.vector * scale * 1.1 - display.Draw2dText("X", self.RED, point_to_rhino(x_loc), True, 16, "Verdana") - display.Draw2dText("Y", self.GREEN, point_to_rhino(y_loc), True, 16, "Verdana") - display.Draw2dText("Z", self.BLUE, point_to_rhino(z_loc), True, 16, "Verdana") - - def _draw_faces(self, display, faces, scale): - for index, face in enumerate(faces): - normal = Line.from_point_and_vector(face.point, face.normal * scale) - text = str(index) - display.Draw2dText(text, self.WHITE, point_to_rhino(face.point), True, 16, "Verdana") - display.DrawArrow(line_to_rhino(normal), self.YELLOW, self.SCREEN_SIZE, self.RELATIVE_SIZE) - - GhPython provides a Python script component - true - true - true - - iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDwAACw8BkvkDpQAAAohJREFUSEuVlj9rFEEYxvMR7gMEdvFy/6NZDGijuQ3aKCKLiqVuIUKwOdBYWIQtkxQWJhAQvFgdEeyuUrggIniFhWAlCPoJ1MoESRyfZ5z3nN2b+7PFj7nbmX2emXfe972bUUoNMfv43RsQuOayFD0/AokDn/NDL0A4Boom2TkbCBRqpXIHo4pv3dbwc3WudIgxNiSplyBaAD+NAYnseQEv+vPV2hfwZ3trS+2224qf+QxzgRi7DBJLnHwDBXsNXoqwy4Plpabqdrtq9f4DvXOIv8bo1cuVz41q7ZjzKQMI+ZawTSJrGpXqDsUYDorfvH5Di4MWCCvFuV/h+aWjF3t7YjxkEIKWEf5ovgdYWDhZq3+AwdHm+oaiwJnTi8cUpLAx0Ib7vZ7q9/vDBpYRRWmgL5kCFKIghWnAEDAUmPMk3hSksDCVARakdiaZYkLl00Qu2hafysB71PlOsWRtTcebl2alYOqis+JjDby7G3do4K+2dfplUxB3sU5jnkbi7cIkQNoAD+JS8+pvGlx8+lZ2oRYa8y8xeiYV9alcooSmVy5dltNGIjyoynsrK3pRJgUDnoAn4Ylcws9evVfN7Z46ey2W7NKtRldl+UTxkxEbIIsA433IeI8LycMnu1I3anZzv4PxXy+CwDnAXWbx7MKyxQZC42HLSWjAzpfavYEF5Ix3RmgU/w2yRXIhXP4hBqNiznCZ1D3AughiUj/kORiEKLcBK5oXfqre+Io1+jKNAX9HQn4XchuwVfA5exPGVKd1kcuAKcxnvHyXmIupDDJ1EbuERjHRgFnEy7SLJw8TDYhpzTor8qIN2DtoIiwuBEw9bWBayMTLHAUNKJT9y0F80HK9ND1q5i+sgg6R/W1M2wAAAABJRU5ErkJggg== - - false - eb9d5147-e225-45a5-a44b-61953c4955eb - true - true - CT: DecomposeBeam - DecomposeBeam - - - - - - 1382 - 621 - 156 - 104 - - - 1462 - 673 - - - - - - 3 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 5 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - - - - - 1 - true - Beam - 8e04df08-7ba8-497d-81a4-3df28baa4429 - Beam - Beam - true - 1 - true - 19ba4d57-8c61-4bac-90d7-412dfcb8ac0b - c38af54d-dfec-411f-ab38-22c657830b82 - 2 - 35915213-5534-4277-81b8-1bdc9e7383d2 - - - - - - 1384 - 623 - 63 - 33 - - - 1417 - 639.6667 - - - - - - - - true - Script input ShowFrame. - 55d797db-e8b9-4a25-83f9-fb3ce5e97dda - ShowFrame - ShowFrame - true - 0 - true - 4589d281-06a1-48a8-ab9c-fa318fb66883 - 1 - d60527f5-b5af-4ef6-8970-5f96fe412559 - - - - - - 1384 - 656 - 63 - 33 - - - 1417 - 673 - - - - - - - - true - Script input ShowFaces. - 38380630-dd0c-4f25-ba4d-8be79af14f88 - ShowFaces - ShowFaces - true - 0 - true - 4589d281-06a1-48a8-ab9c-fa318fb66883 - 1 - d60527f5-b5af-4ef6-8970-5f96fe412559 - - - - - - 1384 - 689 - 63 - 34 - - - 1417 - 706.3334 - - - - - - - - Script output Frame. - eaa8a578-60ae-4948-8427-7ef3e9061069 - Frame - Frame - false - 0 - - - - - - 1477 - 623 - 59 - 20 - - - 1506.5 - 633 - - - - - - - - Script output Centerline. - d5904a7c-0c03-4764-8d8c-d828062b20dd - Centerline - Centerline - false - 0 - - - - - - 1477 - 643 - 59 - 20 - - - 1506.5 - 653 - - - - - - - - Script output Box. - e778515e-1975-4634-93fa-0a12d895cfb0 - Box - Box - false - 0 - - - - - - 1477 - 663 - 59 - 20 - - - 1506.5 - 673 - - - - - - - - Script output Width. - d3ee9e41-73c8-4e9b-8793-3de14a036ce6 - Width - Width - false - 0 - - - - - - 1477 - 683 - 59 - 20 - - - 1506.5 - 693 - - - - - - - - Script output Height. - 712b50a9-aa16-4a06-850a-5ccaa6c1e4a1 - Height - Height - false - 0 - - - - - - 1477 - 703 - 59 - 20 - - - 1506.5 - 713 - - - - - - - - - - - - - - 2e78987b-9dfb-42a2-8b76-3923ac8bd91a - Boolean Toggle - - - - - Boolean (true/false) toggle - 4589d281-06a1-48a8-ab9c-fa318fb66883 - Boolean Toggle - Toggle - false - 0 - true - - - - - - 1257 - 679 - 104 - 22 - - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - c18f9e62-d6f5-42c4-a426-10688f24ca61 - Number Slider - - false - 0 - - - - - - 1762 - 694 - 159 - 20 - - - 1762.14 - 694.5345 - - - - - - 3 - 1 - 1 - 15 - 0 - 0 - 0 - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - fd67193c-c124-4e9b-bf38-3d6a6e085438 - Number Slider - - false - 0 - - - - - - 1722 - 718 - 201 - 20 - - - 1722.123 - 718.8546 - - - - - - 3 - 1 - 1 - 5 - 0 - 0 - 2 - - - - - - - - - 59e0b89a-e487-49f8-bab8-b5bab16be14c - Panel - - - - - A panel for custom notes and text values - cfb4b1f8-a02e-44ce-9c95-80b1164d7fca - Panel - BTLx Params - false - 0 - d525eccb-5bff-4563-b75e-472cbbdc5901 - 1 - Double click to edit panel content… - - - - - - 2400 - 926 - 212 - 333 - - 0 - 0 - 0 - - 2400.01 - 926.6734 - - - - - - - 255;255;255;255 - - true - true - true - false - false - true - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - d5f970b8-28fa-4f28-b038-8f3eed5c682d - Number Slider - - false - 0 - - - - - - 1727 - 786 - 160 - 20 - - - 1727.211 - 786.5431 - - - - - - 3 - 1 - 1 - 200 - 0 - 0 - 30 - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - c1e88145-a742-40e8-9b8b-a549422a646d - Number Slider - - false - 0 - - - - - - 1727 - 809 - 160 - 20 - - - 1727.21 - 809.6162 - - - - - - 3 - 1 - 1 - 200 - 0 - 0 - 15 - - - - - - - - - 2e78987b-9dfb-42a2-8b76-3923ac8bd91a - Boolean Toggle - - - - - Boolean (true/false) toggle - c925fa54-d90e-456f-863a-2f4f0a2857ba - Boolean Toggle - tapered_heel - false - 0 - false - - - - - - 1752 - 831 - 133 - 22 - - - - - - - - - - f31d8d7a-7536-4ac8-9c96-fde6ecda4d0a - Cluster - - - - - - 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 - - Contains a cluster of Grasshopper components - true - a5869824-14fc-400f-a69e-f5050392632f - Cluster - Cluster - false - - - - - 5 - 3f3af581-d64e-4909-91e4-3576cf2e9e86 - 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 - d28e1b27-74ad-41ab-b017-540fa9111876 - ebd18bdb-78c3-4d40-8a69-e1af30a53c27 - fd03a9d4-272c-4590-9595-4d3107dc0fdc - 3c631e1e-b12f-4297-9535-87b4fdc7b45e - 5d665740-a9cc-4f15-8a38-0dc75e214ae2 - 5d32325d-62cf-40bd-93fe-74af56a2c91e - b360d350-2b53-401b-9420-d9402019cb30 - 796ac502-faba-4bb6-a612-7e3dfb448d98 - - - - - - 2381 - 756 - 72 - 84 - - - 2420 - 798 - - - - - - 4 - 919e146f-30ae-4aae-be34-4d72f555e7da - 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 - 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 - d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 - 1 - 919e146f-30ae-4aae-be34-4d72f555e7da - - - - - Brep to split - ebd18bdb-78c3-4d40-8a69-e1af30a53c27 - Brep - B - true - 489d49f6-b379-49e3-8aed-197b4a1468a4 - 1 - - - - - - 2383 - 758 - 22 - 20 - - - 2395.5 - 768 - - - - - - - - Contains a collection of three-dimensional axis-systems - d28e1b27-74ad-41ab-b017-540fa9111876 - Plane - Pln - true - 16bc55f9-a035-4099-834e-e9dc0931b695 - 1 - - - - - - 2383 - 778 - 22 - 20 - - - 2395.5 - 788 - - - - - - - - Contains a collection of three-dimensional axis-systems - fd03a9d4-272c-4590-9595-4d3107dc0fdc - Plane - Pln - true - ddc1d959-4491-4f72-b7d5-a74d74b99385 - 1 - - - - - - 2383 - 798 - 22 - 20 - - - 2395.5 - 808 - - - - - - - - 1 - Section curves - 3f3af581-d64e-4909-91e4-3576cf2e9e86 - Curves - C - true - 1a4781d4-bfe8-4573-86bb-90d0f0ffc197 - 1 - - - - - - 2383 - 818 - 22 - 20 - - - 2395.5 - 828 - - - - - - - - 1 - Joined Breps - 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 - Breps - B - false - 0 - - - - - - 2435 - 758 - 16 - 80 - - - 2443 - 798 - - - - - - - - - - - - - - 537b0419-bbc2-4ff4-bf08-afe526367b2c - Custom Preview - - - - - Allows for customized geometry previews - true - true - 9e4f0b21-759b-4c9a-8dfb-6654484027c5 - Custom Preview - Preview - - - - - - - 2569 - 841 - 48 - 44 - - - 2603 - 863 - - - - - - Geometry to preview - true - bc0fc1fe-a18e-40ca-ab8a-8ba765dde1f9 - Geometry - G - false - 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 - 1 - - - - - - 2571 - 843 - 17 - 20 - - - 2581 - 853 - - - - - - - - The material override - 8cbb31fa-4051-471d-87be-5ce455d7f325 - Material - M - false - 0 - - - - - - 2571 - 863 - 17 - 20 - - - 2581 - 873 - - - - - - 1 - - - - - 1 - {0} - - - - - - 255;221;160;221 - - - 255;66;48;66 - - 0.5 - - 255;255;255;255 - - 0 - - - - - - - - - - - - - - - 0148a65d-6f42-414a-9db7-9a9b2eb78437 - Brep Edges - - - - - Extract the edge curves of a brep. - true - be8a15e2-8773-4720-9d61-dfdc10761646 - Brep Edges - Edges - - - - - - 2469 - 766 - 72 - 64 - - - 2499 - 798 - - - - - - Base Brep - 0056a757-f1c7-4722-83e9-e00c5e697cfc - Brep - B - false - 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 - 1 - - - - - - 2471 - 768 - 13 - 60 - - - 2479 - 798 - - - - - - - - 1 - Naked edge curves - 2fc3daa1-27be-494a-9118-8afa0df8edc7 - Naked - En - false - 0 - - - - - - 2514 - 768 - 25 - 20 - - - 2526.5 - 778 - - - - - - - - 1 - Interior edge curves - 7db965ba-79d8-42a1-926c-cc7c5ea6a716 - Interior - Ei - false - 0 - - - - - - 2514 - 788 - 25 - 20 - - - 2526.5 - 798 - - - - - - - - 1 - Non-Manifold edge curves - dfe49992-1aa8-4a4e-bf86-f66139ca06c5 - Non-Manifold - Em - false - 0 - - - - - - 2514 - 808 - 25 - 20 - - - 2526.5 - 818 - - - - - - - - - - - - 22990b1f-9be6-477c-ad89-f775cd347105 - Flip Curve - - - - - Flip a curve using an optional guide curve. - true - 0c07a9ce-439d-466f-98d1-fcc3dcd14023 - Flip Curve - Flip - - - - - - 876 - 695 - 66 - 44 - - - 908 - 717 - - - - - - Curve to flip - 082a270f-f37d-4c27-8266-d4348e1e059b - Curve - C - false - 2f78b16d-80d1-4502-954e-49040fe761a3 - 1 - - - - - - 878 - 697 - 15 - 20 - - - 887 - 707 - - - - - - - - Optional guide curve - 94c738fb-f46a-4531-af70-ea11c88263f2 - Guide - G - true - 0 - - - - - - 878 - 717 - 15 - 20 - - - 887 - 727 - - - - - - - - Flipped curve - c284b03b-3c25-4136-b35d-211207e5e246 - Curve - C - false - 0 - - - - - - 923 - 697 - 17 - 20 - - - 931.5 - 707 - - - - - - - - Flip action - a48ad8ba-3072-486f-9f31-f1cbfcaa31e2 - Flag - F - false - 0 - - - - - - 923 - 717 - 17 - 20 - - - 931.5 - 727 - - - - - - - - - - - - 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 - Plane - - - - - Contains a collection of three-dimensional axis-systems - true - 482410b9-724c-46d6-be3a-3d63785bc853 - Plane - Pln - false - 16bc55f9-a035-4099-834e-e9dc0931b695 - 1 - - - - - - 2566 - 703 - 50 - 24 - - - 2591.305 - 715.5306 - - - - - - - - - - 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 - Plane - - - - - Contains a collection of three-dimensional axis-systems - true - 9c473eb6-ff8f-4c7f-bd69-56527d0257f6 - Plane - Pln - false - ddc1d959-4491-4f72-b7d5-a74d74b99385 - 1 - - - - - - 2565 - 632 - 50 - 24 - - - 2590.664 - 644.5862 - - - - - - - - - - 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 - Plane - - - - - Contains a collection of three-dimensional axis-systems - true - 88b4a335-192d-48a1-81d5-163278eaecbe - Plane - Pln - false - 602ccbf4-1274-4cd9-8349-ba4c5ba030fe - 1 - - - - - - 2565 - 564 - 50 - 24 - - - 2590.097 - 576.5297 - - - - - - - - - - 59e0b89a-e487-49f8-bab8-b5bab16be14c - Panel - - - - - A panel for custom notes and text values - 84325f58-aa64-43ef-9c59-6631e3ff4da9 - Panel - - false - 1 - 40308c84-a0ba-4c41-b158-38a3341beb2c - 1 - Double click to edit panel content… - - - - - - 1988 - 610 - 221 - 85 - - 0 - 0 - 0 - - 1988.603 - 610.7073 - - - - - - - 255;255;250;90 - - true - true - true - false - false - true - - - - - - - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 88b4a335-192d-48a1-81d5-163278eaecbe - 1 - 234eec62-a3cc-4178-9760-678a11912fb0 - Group - CUTTING PLANE - - - - - - - - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 9c473eb6-ff8f-4c7f-bd69-56527d0257f6 - 1 - 86bc7ac3-bdd8-443c-ae9b-e22ea6ec103a - Group - REF_SIDE - - - - - - - - - - a77d0879-94c2-4101-be44-e4a616ffeb0c - 5f86fa9f-c62b-50e8-157b-b454ef3e00fa - Custom Preview Lineweights - - - - - Custom Preview with Lineweights - 56020ec2-ccb1-4ae9-a538-84dcb2857db9 - Custom Preview Lineweights - PreviewLW - - - - - - - 2570 - 747 - 46 - 84 - - - 2602 - 789 - - - - - - Geometry to preview - true - 11bd5cdd-8b99-4483-950a-c0cb8f29576c - Geometry - G - false - 7db965ba-79d8-42a1-926c-cc7c5ea6a716 - 1 - - - - - - 2572 - 749 - 15 - 20 - - - 2581 - 759 - - - - - - - - The preview shader override - 7eb996a5-eb09-476d-8a9f-c49f7a1c895e - Shader - S - false - 0 - - - - - - 2572 - 769 - 15 - 20 - - - 2581 - 779 - - - - - - 1 - - - - - 1 - {0} - - - - - - 255;255;105;180 - - - 255;76;32;54 - - 0.5 - - 255;255;255;255 - - 0 - - - - - - - - - - - The thickness of the wire display - c3053791-290f-494e-8384-81e8464e4dc4 - Thickness - T - true - 0 - - - - - - 2572 - 789 - 15 - 20 - - - 2581 - 799 - - - - - - - - Set to true to try to render curves with an absolute dimension. - 073aa3ff-0ed7-42b6-bdd5-c5b25159731c - Absolute - A - false - 0 - - - - - - 2572 - 809 - 15 - 20 - - - 2581 - 819 - - - - - - 1 - - - - - 1 - {0} - - - - - false - - - - - - - - - - - - - - - 11bbd48b-bb0a-4f1b-8167-fa297590390d - End Points - - - - - Extract the end points of a curve. - true - 715464ea-9048-4ae7-9c5c-942cc0fa2486 - End Points - End - - - - - - 727 - 448 - 64 - 44 - - - 758 - 470 - - - - - - Curve to evaluate - ba457b2a-de34-4922-929d-926d8adedc5a - Curve - C - false - 6bbc142b-2536-41bf-a215-ea52704d32b6 - 1 - - - - - - 729 - 450 - 14 - 40 - - - 737.5 - 470 - - - - - - - - Curve start point - 77f5802b-abc0-49e5-91cc-3376ae99a75c - Start - S - false - 0 - - - - - - 773 - 450 - 16 - 20 - - - 781 - 460 - - - - - - - - Curve end point - 5a7d2dd5-1871-45dd-b9c0-e6289501fd3a - End - E - false - 0 - - - - - - 773 - 470 - 16 - 20 - - - 781 - 480 - - - - - - - - - - - - e9eb1dcf-92f6-4d4d-84ae-96222d60f56b - Move - - - - - Translate (move) an object along a vector. - true - 22d84b32-205f-41d5-8663-8af45f94307c - Move - Move - - - - - - 833 - 468 - 83 + 2841 + 514 + 48 44 - - 881 - 490 - - - - - - Base geometry - 4f7e8528-a89b-42ec-8171-f6004589fdcb - Geometry - G - true - 5a7d2dd5-1871-45dd-b9c0-e6289501fd3a - 1 - - - - - - 835 - 470 - 31 - 20 - - - 860 - 480 - - - - - - - - Translation vector - 21c38b75-b524-4c4f-93e3-89a660e1a69b - -x - Motion - T - false - 1e474aea-db61-424d-be9a-804e3fd04359 - 1 - - - - - - 835 - 490 - 31 - 20 - - - 860 - 500 - - - - - - 1 - - - - - 1 - {0} - - - - - - 0 - 0 - 10 - - - - - - - - + + 2875 + 536 + + - - - Translated geometry - 9348c98e-479b-416c-9b0b-54c72c33be75 + + + Geometry to preview + true + bc0fc1fe-a18e-40ca-ab8a-8ba765dde1f9 Geometry G false - 0 - - - - - - 896 - 470 - 18 - 20 - - - 905 - 480 - - - - - - - - Transformation data - 35ee0f80-11b3-44ca-8fde-aa3bba3f7c43 - Transform - X - false - 0 + 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 + 1 - 896 - 490 - 18 + 2843 + 516 + 17 20 - 905 - 500 + 2853 + 526 - - - - - - - 79f9fbb3-8f1d-4d9a-88a9-f7961b1012cd - Unit X - - - - - Unit vector parallel to the world {x} axis. - true - 73ddbf47-b58a-451f-8f84-85c767ffb835 - Unit X - X - - - - - - 513 - 491 - 63 - 28 - - - 542 - 505 - - - - + - Unit multiplication - 4b128ece-c9a7-4204-9394-3fda75fa3d6f - Factor - F + The material override + 8cbb31fa-4051-471d-87be-5ce455d7f325 + Material + M false - 912e5dfb-d2a2-463b-a0eb-2610982bf536 + c0a53353-9802-4df7-a063-6228afad4537 1 - 515 - 493 - 12 - 24 + 2843 + 536 + 17 + 20 - 522.5 - 505 + 2853 + 546 @@ -3692,8 +1148,18 @@ class BeamDecompose(component): - - 1 + + + 255;221;160;221 + + + 255;66;48;66 + + 0.5 + + 255;255;255;255 + + 0 @@ -3702,276 +1168,73 @@ class BeamDecompose(component): - - - World {x} vector - 323481aa-7502-4bf4-8393-f5ea30a2e8ed - Unit vector - V - false - 0 - - - - - - 557 - 493 - 17 - 24 - - - 565.5 - 505 - - - - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - 912e5dfb-d2a2-463b-a0eb-2610982bf536 - Number Slider - - false - 0 - - - - - - 323 - 497 - 163 - 20 - - - 323.5465 - 497.3116 - - - - - - 3 - 1 - 1 - 2000 - 0 - 0 - 0 - - - - - - - - - 4c4e56eb-2f04-43f9-95a3-cc46a14f495a - Line - - - - - Create a line between two points. - true - 25e3b03c-eedc-4418-babd-0df09c1d6284 - Line - Ln - - - - - - 943 - 448 - 63 - 44 - - - 974 - 470 - - - - - - Line start point - 99feaba8-239b-40e1-ae13-db12fa1af53e - Start Point - A - false - 77f5802b-abc0-49e5-91cc-3376ae99a75c - 1 - - - - - - 945 - 450 - 14 - 20 - - - 953.5 - 460 - - - - - - - - Line end point - 5d3fa723-a735-4608-9a27-c304c4c3aa44 - End Point - B - false - 9348c98e-479b-416c-9b0b-54c72c33be75 - 1 - - - - - - 945 - 470 - 14 - 20 - - - 953.5 - 480 - - - - - - - - Line segment - 82ddb61d-d21e-49ce-a61c-851130becb9a - Line - L - false - 0 - - - - - - 989 - 450 - 15 - 40 - - - 996.5 - 470 - - - - - - + - 9103c240-a6a9-4223-9b42-dbd19bf38e2b - Unit Z + 0148a65d-6f42-414a-9db7-9a9b2eb78437 + Brep Edges - Unit vector parallel to the world {z} axis. + Extract the edge curves of a brep. true - eaf786cc-99fc-4d46-b6b3-cf6b5314011a - Unit Z - Z + be8a15e2-8773-4720-9d61-dfdc10761646 + Brep Edges + Edges - + - 515 - 525 - 63 - 28 + 2741 + 439 + 72 + 64 - 544 - 539 + 2771 + 471 - Unit multiplication - 88ec5468-52fb-4929-b59a-79cb5d549828 - Factor - F + Base Brep + 0056a757-f1c7-4722-83e9-e00c5e697cfc + Brep + B false - 68472b2f-1682-4c03-a570-0bd3dafb7255 + 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 1 - + - 517 - 527 - 12 - 24 + 2743 + 441 + 13 + 60 - 524.5 - 539 + 2751 + 471 - - - 1 - - - - - 1 - {0} - - - - - 1 - - - - - - - - World {z} vector - f08f6ccf-8731-4561-b2d4-cf43dcb5070e - Unit vector - V + + 1 + Naked edge curves + 2fc3daa1-27be-494a-9118-8afa0df8edc7 + Naked + En false 0 @@ -3979,185 +1242,70 @@ class BeamDecompose(component): - 559 - 527 - 17 - 24 + 2786 + 441 + 25 + 20 - 567.5 - 539 + 2798.5 + 451 - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - 68472b2f-1682-4c03-a570-0bd3dafb7255 - Number Slider - - false - 0 - - - - - - 329 - 529 - 163 - 20 - - - 329.7557 - 529.9963 - - - - + - 3 - 1 - 1 - 2000 - 0 - 0 - 0 - - - - - - - - - a0d62394-a118-422d-abb3-6af115c75b25 - Addition - - - - - Mathematical addition - true - 362a8c85-9ff1-4541-8448-a78e3c79ff60 - Addition - A+B - - - - - - 728 - 493 - 65 - 44 - - - 759 - 515 - - - - - - 2 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 1 - 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + Interior edge curves + 7db965ba-79d8-42a1-926c-cc7c5ea6a716 + Interior + Ei + false + 0 - - - - First item for addition - 0b1b606a-bd74-4af5-928f-2813c43db4f7 - A - A - true - 323481aa-7502-4bf4-8393-f5ea30a2e8ed - 1 - - - - - - 730 - 495 - 14 - 20 - - - 738.5 - 505 - - - - - - - - Second item for addition - 29d1f2a4-5fa6-44f6-af85-bc0232c7f3ed - B - B - true - f08f6ccf-8731-4561-b2d4-cf43dcb5070e - 1 - - - - - - 730 - 515 - 14 - 20 - - - 738.5 - 525 - - - - - - - - Result of addition - 1e474aea-db61-424d-be9a-804e3fd04359 - Result - R - false - 0 + + + + + 2786 + 461 + 25 + 20 + + + 2798.5 + 471 + + + + + + + + 1 + Non-Manifold edge curves + dfe49992-1aa8-4a4e-bf86-f66139ca06c5 + Non-Manifold + Em + false + 0 + + + + + + 2786 + 481 + 25 + 20 + + + 2798.5 + 491 + - - - - - 774 - 495 - 17 - 40 - - - 782.5 - 515 - - - - @@ -4165,55 +1313,35 @@ class BeamDecompose(component): - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 482410b9-724c-46d6-be3a-3d63785bc853 - 1 - 541e5f17-7a28-48ff-8820-37e3076647dd - Group - notch_planes - - - - - - - - + - 2e78987b-9dfb-42a2-8b76-3923ac8bd91a - Boolean Toggle + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane - - Boolean (true/false) toggle - 1f1c2959-2344-4152-a5d6-0e718f54669a - Boolean Toggle - FlipCurve + + Contains a collection of three-dimensional axis-systems + true + 482410b9-724c-46d6-be3a-3d63785bc853 + Plane + Pln false - 0 - false + 16bc55f9-a035-4099-834e-e9dc0931b695 + 1 - + - 462 - 647 - 115 - 22 + 2839 + 377 + 50 + 24 + + + 2864.272 + 389.9877 @@ -4221,303 +1349,149 @@ class BeamDecompose(component): - + - eeafc956-268e-461d-8e73-ee05c6f72c01 - Stream Filter + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane - - Filters a collection of input streams + + Contains a collection of three-dimensional axis-systems true - 46298eb6-2834-4153-b072-4cc235a21b8d - Stream Filter - Filter + 9c473eb6-ff8f-4c7f-bd69-56527d0257f6 + Plane + Pln + false + ddc1d959-4491-4f72-b7d5-a74d74b99385 + 1 - + - 957 - 655 - 77 - 64 + 2838 + 307 + 50 + 24 - 989 - 687 + 2863.631 + 319.0432 - - - 3 - 2e3ab970-8545-46bb-836c-1c11e5610bce - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 1 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - - - - - Index of Gate stream - 376909ae-471f-4338-bd76-0550fd56a4cb - Gate - G - false - 1f1c2959-2344-4152-a5d6-0e718f54669a - 1 - - - - - - 959 - 657 - 15 - 20 - - - 968 - 667 - - - - - - 1 - - - - - 1 - {0} - - - - - 0 - - - - - - - - - - - 2 - Input stream at index 0 - acea90f0-616d-4203-908a-45991c3b043f - false - Stream 0 - 0 - true - 2f78b16d-80d1-4502-954e-49040fe761a3 - 1 - - - - - - 959 - 677 - 15 - 20 - - - 968 - 687 - - - - - - - - 2 - Input stream at index 1 - 282b385d-4abd-455c-848a-9a06b88edbbc - false - Stream 1 - 1 - true - c284b03b-3c25-4136-b35d-211207e5e246 - 1 - - - - - - 959 - 697 - 15 - 20 - - - 968 - 707 - - - - - - - - 2 - Filtered stream - 919e0b0e-50c5-4983-bf15-4bb7b9f39c0a - false - Stream - S(0) - false - 0 - - - - - - 1004 - 657 - 28 - 60 - - - 1018 - 687 - - - - - - - - - - - - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 2f78b16d-80d1-4502-954e-49040fe761a3 - 1f1c2959-2344-4152-a5d6-0e718f54669a - 2 - 507139ff-df02-4319-9176-8b2ce4935cf5 - Group - cross_centerline - - - - + - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 6bbc142b-2536-41bf-a215-ea52704d32b6 - 73ddbf47-b58a-451f-8f84-85c767ffb835 - 912e5dfb-d2a2-463b-a0eb-2610982bf536 - eaf786cc-99fc-4d46-b6b3-cf6b5314011a - 68472b2f-1682-4c03-a570-0bd3dafb7255 - 5 - 917db72d-186f-4f9b-9bae-68106080e5ea - Group - main_centerline + + Contains a collection of three-dimensional axis-systems + true + 88b4a335-192d-48a1-81d5-163278eaecbe + Plane + Pln + false + 602ccbf4-1274-4cd9-8349-ba4c5ba030fe + 1 - + + + + 2837 + 242 + 50 + 24 + + + 2862.671 + 254.6153 + + + - + - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel - - Numeric slider for single values - 654077fb-9447-42ca-a4ed-d6a4dd8d1874 - Number Slider + + A panel for custom notes and text values + 84325f58-aa64-43ef-9c59-6631e3ff4da9 + Panel false - 0 + 1 + 40308c84-a0ba-4c41-b158-38a3341beb2c + 1 + Double click to edit panel content… - + - 1721 - 902 - 205 - 20 + 2261 + 285 + 221 + 85 + 0 + 0 + 0 - 1721.597 - 902.3441 + 2261.57 + 285.1643 - + - 3 - 1 - 1 - 100 - 0 - 0 - 40 + + 255;255;250;90 + + true + true + true + false + false + true - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group - + 1 150;255;255;255 A group of Grasshopper objects - c18f9e62-d6f5-42c4-a426-10688f24ca61 - fd67193c-c124-4e9b-bf38-3d6a6e085438 - 2 - b916d0a2-94ed-4de0-9991-6f37b3e59ce4 + 88b4a335-192d-48a1-81d5-163278eaecbe + 1 + 234eec62-a3cc-4178-9760-678a11912fb0 Group - Ref_Side + CUTTING PLANE @@ -4525,26 +1499,24 @@ class BeamDecompose(component): - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group - + 1 150;255;255;255 A group of Grasshopper objects - d5f970b8-28fa-4f28-b038-8f3eed5c682d - c1e88145-a742-40e8-9b8b-a549422a646d - c925fa54-d90e-456f-863a-2f4f0a2857ba - 3 - 280e3dc8-45ed-4759-8d99-df91689dd4d8 + 9c473eb6-ff8f-4c7f-bd69-56527d0257f6 + 1 + 86bc7ac3-bdd8-443c-ae9b-e22ea6ec103a Group - StepShape + REF_SIDE @@ -4552,7 +1524,198 @@ class BeamDecompose(component): - + + + a77d0879-94c2-4101-be44-e4a616ffeb0c + 5f86fa9f-c62b-50e8-157b-b454ef3e00fa + Custom Preview Lineweights + + + + + Custom Preview with Lineweights + 56020ec2-ccb1-4ae9-a538-84dcb2857db9 + Custom Preview Lineweights + PreviewLW + + + + + + + 2842 + 420 + 46 + 84 + + + 2874 + 462 + + + + + + Geometry to preview + true + 11bd5cdd-8b99-4483-950a-c0cb8f29576c + Geometry + G + false + 7db965ba-79d8-42a1-926c-cc7c5ea6a716 + 1 + + + + + + 2844 + 422 + 15 + 20 + + + 2853 + 432 + + + + + + + + The preview shader override + 7eb996a5-eb09-476d-8a9f-c49f7a1c895e + Shader + S + false + c0a53353-9802-4df7-a063-6228afad4537 + 1 + + + + + + 2844 + 442 + 15 + 20 + + + 2853 + 452 + + + + + + 1 + + + + + 1 + {0} + + + + + + 255;255;105;180 + + + 255;76;32;54 + + 0.5 + + 255;255;255;255 + + 0 + + + + + + + + + + + The thickness of the wire display + c3053791-290f-494e-8384-81e8464e4dc4 + Thickness + T + true + 0 + + + + + + 2844 + 462 + 15 + 20 + + + 2853 + 472 + + + + + + + + Set to true to try to render curves with an absolute dimension. + 073aa3ff-0ed7-42b6-bdd5-c5b25159731c + Absolute + A + false + 0 + + + + + + 2844 + 482 + 15 + 20 + + + 2853 + 492 + + + + + + 1 + + + + + 1 + {0} + + + + + false + + + + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -4565,11 +1728,11 @@ class BeamDecompose(component): 150;255;255;255 A group of Grasshopper objects - 654077fb-9447-42ca-a4ed-d6a4dd8d1874 + 482410b9-724c-46d6-be3a-3d63785bc853 1 - 98458446-0073-4c25-ac14-bdda3bc5b7ad + 541e5f17-7a28-48ff-8820-37e3076647dd Group - Mortise + notch_planes @@ -4577,7 +1740,7 @@ class BeamDecompose(component): - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -4602,7 +1765,7 @@ class BeamDecompose(component): - + 410755b1-224a-4c1e-a407-bf32fb45ea7e 00000000-0000-0000-0000-000000000000 @@ -4670,8 +1833,8 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) GhPython provides a Python script component - 103 - 145 + 248 + 94 1232 @@ -4691,14 +1854,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1955 - 1578 + 2256 + 1265 222 164 - 2051 - 1660 + 2352 + 1347 @@ -4741,14 +1904,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1957 - 1580 + 2258 + 1267 79 20 - 1998 - 1590 + 2299 + 1277 @@ -4772,14 +1935,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1957 - 1600 + 2258 + 1287 79 20 - 1998 - 1610 + 2299 + 1297 @@ -4803,14 +1966,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1957 - 1620 + 2258 + 1307 79 20 - 1998 - 1630 + 2299 + 1317 @@ -4834,14 +1997,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1957 - 1640 + 2258 + 1327 79 20 - 1998 - 1650 + 2299 + 1337 @@ -4865,14 +2028,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1957 - 1660 + 2258 + 1347 79 20 - 1998 - 1670 + 2299 + 1357 @@ -4896,14 +2059,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1957 - 1680 + 2258 + 1367 79 20 - 1998 - 1690 + 2299 + 1377 @@ -4927,14 +2090,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1957 - 1700 + 2258 + 1387 79 20 - 1998 - 1710 + 2299 + 1397 @@ -4958,14 +2121,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1957 - 1720 + 2258 + 1407 79 20 - 1998 - 1730 + 2299 + 1417 @@ -4984,14 +2147,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2066 - 1580 + 2367 + 1267 109 22 - 2120.5 - 1591.429 + 2421.5 + 1278.429 @@ -5010,14 +2173,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2066 - 1602 + 2367 + 1289 109 23 - 2120.5 - 1614.286 + 2421.5 + 1301.286 @@ -5036,14 +2199,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2066 - 1625 + 2367 + 1312 109 23 - 2120.5 - 1637.143 + 2421.5 + 1324.143 @@ -5062,14 +2225,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2066 - 1648 + 2367 + 1335 109 23 - 2120.5 - 1660 + 2421.5 + 1347 @@ -5088,14 +2251,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2066 - 1671 + 2367 + 1358 109 23 - 2120.5 - 1682.857 + 2421.5 + 1369.857 @@ -5114,14 +2277,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2066 - 1694 + 2367 + 1381 109 23 - 2120.5 - 1705.714 + 2421.5 + 1392.714 @@ -5140,14 +2303,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2066 - 1717 + 2367 + 1404 109 23 - 2120.5 - 1728.571 + 2421.5 + 1415.571 @@ -5159,7 +2322,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -5178,14 +2341,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1730 - 1578 + 1675 + 873 160 20 - 1730.692 - 1578.391 + 1675.708 + 873.239 @@ -5204,7 +2367,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -5223,14 +2386,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1690 - 1602 + 1635 + 897 201 20 - 1690.675 - 1602.711 + 1635.692 + 897.5591 @@ -5249,7 +2412,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 59e0b89a-e487-49f8-bab8-b5bab16be14c Panel @@ -5271,8 +2434,8 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2368 - 1810 + 2670 + 1498 212 333 @@ -5280,8 +2443,8 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 0 0 - 2368.562 - 1810.53 + 2670.888 + 1498.868 @@ -5302,7 +2465,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -5321,14 +2484,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1695 - 1670 + 1640 + 965 160 20 - 1695.763 - 1670.399 + 1640.78 + 965.2471 @@ -5340,14 +2503,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 200 0 0 - 0 + 30 - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -5366,14 +2529,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1695 - 1693 + 1640 + 988 160 20 - 1695.762 - 1693.472 + 1640.779 + 988.3201 @@ -5385,14 +2548,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 200 0 0 - 17 + 15 - + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a Boolean Toggle @@ -5406,14 +2569,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) tapered_heel false 0 - false + true - 1720 - 1714 + 1663 + 1006 133 22 @@ -5423,7 +2586,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + f31d8d7a-7536-4ac8-9c96-fde6ecda4d0a Cluster @@ -5432,7 +2595,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 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 Contains a cluster of Grasshopper components true @@ -5446,28 +2609,28 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 5 065c8b93-533c-4d8c-b22d-d9bc535f688e - 47dba7fc-39e4-4a3b-80ca-c2043391f528 - 6f553506-3c94-4dbc-b2ec-f549b4c30b0c - 8afe827d-4dab-4dde-aea7-476d34f57a1b - e7de4017-7053-4063-8078-ab0ab768ffd7 + 6f553506-3c94-4dbc-b2ec-f549b4c30b0c + 8afe827d-4dab-4dde-aea7-476d34f57a1b + e7de4017-7053-4063-8078-ab0ab768ffd7 + fc6af981-1cbe-4ef5-aa1c-23484b153ef3 3c631e1e-b12f-4297-9535-87b4fdc7b45e - 796ac502-faba-4bb6-a612-7e3dfb448d98 - b360d350-2b53-401b-9420-d9402019cb30 - 5d32325d-62cf-40bd-93fe-74af56a2c91e - 5d665740-a9cc-4f15-8a38-0dc75e214ae2 + b360d350-2b53-401b-9420-d9402019cb30 + 5d32325d-62cf-40bd-93fe-74af56a2c91e + 5d665740-a9cc-4f15-8a38-0dc75e214ae2 + fc820447-d987-4fb0-931e-987ca527842b - 2349 - 1639 - 72 + 2643 + 1326 + 79 84 - 2388 - 1681 + 2689 + 1368 @@ -5476,8 +2639,8 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 4 919e146f-30ae-4aae-be34-4d72f555e7da 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 - 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 - d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + cb95db89-6165-43b6-9c41-5702bc5bf137 1 919e146f-30ae-4aae-be34-4d72f555e7da @@ -5496,14 +2659,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2351 - 1641 - 22 + 2645 + 1328 + 29 20 - 2363.5 - 1651 + 2661 + 1338 @@ -5523,68 +2686,69 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2351 - 1661 - 22 + 2645 + 1348 + 29 20 - 2363.5 - 1671 + 2661 + 1358 - - Contains a collection of three-dimensional axis-systems - 47dba7fc-39e4-4a3b-80ca-c2043391f528 - Plane - Pln + + 1 + Section curves + 065c8b93-533c-4d8c-b22d-d9bc535f688e + Curves + C true - 0 + 9ba6c2c7-77fe-4ea5-96f9-edfa7f161fb3 + 1 - 2351 - 1681 - 22 + 2645 + 1368 + 29 20 - 2363.5 - 1691 + 2661 + 1378 - - 1 - Section curves - 065c8b93-533c-4d8c-b22d-d9bc535f688e - Curves - C + + Contains a collection of boolean values + fc6af981-1cbe-4ef5-aa1c-23484b153ef3 + Boolean + Bool true - 9ba6c2c7-77fe-4ea5-96f9-edfa7f161fb3 + 8af59bcf-e43d-4808-b965-440e7d003dfd 1 - 2351 - 1701 - 22 + 2645 + 1388 + 29 20 - 2363.5 - 1711 + 2661 + 1398 @@ -5604,14 +2768,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2403 - 1641 + 2704 + 1328 16 80 - 2411 - 1681 + 2712 + 1368 @@ -5623,17 +2787,16 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 537b0419-bbc2-4ff4-bf08-afe526367b2c Custom Preview - + Allows for customized geometry previews - true - true + false def048ab-0443-4189-81d6-03594953cdd1 Custom Preview Preview @@ -5643,14 +2806,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2537 - 1724 + 2840 + 1407 48 44 - 2571 - 1746 + 2874 + 1429 @@ -5669,14 +2832,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2539 - 1726 + 2842 + 1409 17 20 - 2549 - 1736 + 2852 + 1419 @@ -5695,14 +2858,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2539 - 1746 + 2842 + 1429 17 20 - 2549 - 1756 + 2852 + 1439 @@ -5742,7 +2905,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 0148a65d-6f42-414a-9db7-9a9b2eb78437 Brep Edges @@ -5760,14 +2923,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2437 - 1649 + 2738 + 1336 72 64 - 2467 - 1681 + 2768 + 1368 @@ -5785,14 +2948,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2439 - 1651 + 2740 + 1338 13 60 - 2447 - 1681 + 2748 + 1368 @@ -5812,14 +2975,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2482 - 1651 + 2783 + 1338 25 20 - 2494.5 - 1661 + 2795.5 + 1348 @@ -5839,14 +3002,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2482 - 1671 + 2783 + 1358 25 20 - 2494.5 - 1681 + 2795.5 + 1368 @@ -5866,14 +3029,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2482 - 1691 + 2783 + 1378 25 20 - 2494.5 - 1701 + 2795.5 + 1388 @@ -5883,15 +3046,16 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 Plane - + Contains a collection of three-dimensional axis-systems + true 5ad83719-a82f-419e-8606-51aa09e83d11 Plane Pln @@ -5903,14 +3067,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2534 - 1587 + 2837 + 1275 50 24 - 2559.857 - 1599.387 + 2862.183 + 1287.725 @@ -5918,7 +3082,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 Plane @@ -5939,14 +3103,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2534 - 1516 + 2836 + 1204 50 24 - 2559.216 - 1528.442 + 2861.542 + 1216.78 @@ -5954,7 +3118,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 Plane @@ -5975,14 +3139,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2533 - 1448 + 2835 + 1136 50 24 - 2558.649 - 1460.386 + 2860.975 + 1148.724 @@ -5990,7 +3154,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 59e0b89a-e487-49f8-bab8-b5bab16be14c Panel @@ -6012,17 +3176,17 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1957 - 1494 - 221 + 2258 + 1180 + 220 85 0 0 0 - 1957.155 - 1494.563 + 2258.007 + 1180.445 @@ -6043,7 +3207,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -6068,7 +3232,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -6093,7 +3257,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + a77d0879-94c2-4101-be44-e4a616ffeb0c 5f86fa9f-c62b-50e8-157b-b454ef3e00fa @@ -6112,14 +3276,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2538 - 1630 + 2839 + 1317 46 84 - 2570 - 1672 + 2871 + 1359 @@ -6138,14 +3302,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2540 - 1632 + 2841 + 1319 15 20 - 2549 - 1642 + 2850 + 1329 @@ -6164,14 +3328,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2540 - 1652 + 2841 + 1339 15 20 - 2549 - 1662 + 2850 + 1349 @@ -6220,14 +3384,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2540 - 1672 + 2841 + 1359 15 20 - 2549 - 1682 + 2850 + 1369 @@ -6246,14 +3410,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2540 - 1692 + 2841 + 1379 15 20 - 2549 - 1702 + 2850 + 1389 @@ -6283,7 +3447,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -6308,7 +3472,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -6319,7 +3483,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) Numeric slider for single values 1f382d1c-8a89-4e8a-8102-597801cd81a3 Number Slider - + Tenon/Mortise Height false 0 @@ -6327,14 +3491,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1690 - 1786 - 205 + 1635 + 1081 + 240 20 - 1690.149 - 1786.2 + 1635.166 + 1081.048 @@ -6346,14 +3510,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 100 0 0 - 40 + 30 - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -6379,7 +3543,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -6406,7 +3570,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -6423,7 +3587,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 1 d6206efa-67b1-4c9e-b762-42a2cf302219 Group - Mortise + Tenon/Mortise @@ -6431,7 +3595,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -6456,200 +3620,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - - - 59daf374-bc21-4a5e-8282-5504fb7ae9ae - List Item - - - - - 0 - Retrieve a specific item from a list. - true - d45f18dd-f301-494f-8070-d01705d5c6bf - List Item - Item - - - - - - 2647 - 1522 - 64 - 64 - - - 2681 - 1554 - - - - - - 3 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 2e3ab970-8545-46bb-836c-1c11e5610bce - cb95db89-6165-43b6-9c41-5702bc5bf137 - 1 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - - - - - 1 - Base list - e6e0f605-ab46-4e72-8626-57f9f1213f8d - List - L - false - 5ad83719-a82f-419e-8606-51aa09e83d11 - 1 - - - - - - 2649 - 1524 - 17 - 20 - - - 2659 - 1534 - - - - - - - - Item index - 475b0420-2ae8-475b-9881-2395b48054d3 - Index - i - false - 0 - - - - - - 2649 - 1544 - 17 - 20 - - - 2659 - 1554 - - - - - - 1 - - - - - 1 - {0} - - - - - 0 - - - - - - - - - - - Wrap index to list bounds - fab10091-9245-436d-9bdb-e0526813f593 - Wrap - W - false - 0 - - - - - - 2649 - 1564 - 17 - 20 - - - 2659 - 1574 - - - - - - 1 - - - - - 1 - {0} - - - - - true - - - - - - - - - - - Item at {i'} - 87d62127-d0d0-469f-8b72-3b18edc264dd - false - Item - i - false - 0 - - - - - - 2696 - 1524 - 13 - 60 - - - 2702.5 - 1554 - - - - - - - - - - - - + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 Curve @@ -6669,14 +3640,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 494 - 1158 + 354 + 548 50 24 - 519.3805 - 1170.715 + 379.472 + 560.2095 @@ -6708,7 +3679,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 410755b1-224a-4c1e-a407-bf32fb45ea7e 00000000-0000-0000-0000-000000000000 @@ -6821,7 +3792,7 @@ class Beam_fromCurve(component): true true - iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDgAACw4BQL7hQQAAAhRJREFUSEulVLtKA0EUzSfkD3zlrUKMIcHGB6ghIj5QxEoDiiA2gtqGYBEfRRQiSRc7iXU6IWKZwh8Q/AI/wEcRr/cMmWUZ7ya7Whw2uXvvOTNnzqyPiP6Nob7+Q0bBQJ0xLQ64BRP4R6OxZ37Swf4+nRwdKyxmFyg4MEhcvxYH3YCH4+GhwHsqMd6+r9ep1WrRY7OpyGcmp2hvZxcCBXG4F3gQligykIIcIulkknJb26qGnXgW4AF/NBi6AzkIQAxcnl+AjAr5vFXzLMDN8ZFI9JXxfVMuW0RYMZ8D3dZqVs2zADeuRALBD3jbaDQUAZ74D2ib/iQwHI5UudHyFsNYLVaN5JjEGj0F+KUVQXisB+EzyO01CV0F+IUYwY21dZUUXXMCemGdKMBFMYIYgIDktx2wD0FAILBQO7FjBGGJveYE2Id5Jn8AH3g1uRhBHCLI7TUJ2BVCAHLuP9eLVgJc3GS/vyZSaboqldQWq5UKZeczyhYdSyfouCLGzLViJ9cCT1A2sbq03NNv7Ay7joXCLzzTb5JbAqa/mdm5XzfTRCeG1Dk35bcEUQAJchLArpAmkDMOJVI7PAkgrrgbuCM8F5cITbgWQFyHI9F2x29HS0y4EkBcuU/5LZF0Q1cB+I3fHMFP7stJBL3gKHBWLOoIvnGPK78lKAEQQkQjOZZQlnS+pq79lgABfNxw2UycSgPeQL4fsKzVEEQlKEcAAAAASUVORK5CYII= + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDQAACw0B7QfALAAAAhRJREFUSEulVLtKA0EUzSfkD3zlrUKMIcHGB6ghIj5QxEoDiiA2gtqGYBEfRRQiSRc7iXU6IWKZwh8Q/AI/wEcRr/cMmWUZ7ya7Whw2uXvvOTNnzqyPiP6Nob7+Q0bBQJ0xLQ64BRP4R6OxZ37Swf4+nRwdKyxmFyg4MEhcvxYH3YCH4+GhwHsqMd6+r9ep1WrRY7OpyGcmp2hvZxcCBXG4F3gQligykIIcIulkknJb26qGnXgW4AF/NBi6AzkIQAxcnl+AjAr5vFXzLMDN8ZFI9JXxfVMuW0RYMZ8D3dZqVs2zADeuRALBD3jbaDQUAZ74D2ib/iQwHI5UudHyFsNYLVaN5JjEGj0F+KUVQXisB+EzyO01CV0F+IUYwY21dZUUXXMCemGdKMBFMYIYgIDktx2wD0FAILBQO7FjBGGJveYE2Id5Jn8AH3g1uRhBHCLI7TUJ2BVCAHLuP9eLVgJc3GS/vyZSaboqldQWq5UKZeczyhYdSyfouCLGzLViJ9cCT1A2sbq03NNv7Ay7joXCLzzTb5JbAqa/mdm5XzfTRCeG1Dk35bcEUQAJchLArpAmkDMOJVI7PAkgrrgbuCM8F5cITbgWQFyHI9F2x29HS0y4EkBcuU/5LZF0Q1cB+I3fHMFP7stJBL3gKHBWLOoIvnGPK78lKAEQQkQjOZZQlnS+pq79lgABfNxw2UycSgPeQL4fsKzVEEQlKEcAAAAASUVORK5CYII= false e28e26d5-b791-48bc-902c-a1929ace9685 @@ -6834,14 +3805,14 @@ class Beam_fromCurve(component): - 1053 - 1031 + 1018 + 403 145 124 - 1144 - 1093 + 1109 + 465 @@ -6878,14 +3849,14 @@ class Beam_fromCurve(component): - 1055 - 1033 + 1020 + 405 74 20 - 1093.5 - 1043 + 1058.5 + 415 @@ -6909,14 +3880,14 @@ class Beam_fromCurve(component): - 1055 - 1053 + 1020 + 425 74 20 - 1093.5 - 1063 + 1058.5 + 435 @@ -6933,7 +3904,7 @@ class Beam_fromCurve(component): true 1 true - da50cc8d-94c7-4348-b6e9-2f46272647d3 + d9d51b18-d5af-4f8f-822f-9fbd0f30d642 1 39fbc626-7a01-46ab-a18e-ec1c0c41685b @@ -6941,14 +3912,14 @@ class Beam_fromCurve(component): - 1055 - 1073 + 1020 + 445 74 20 - 1093.5 - 1083 + 1058.5 + 455 @@ -6973,14 +3944,14 @@ class Beam_fromCurve(component): - 1055 - 1093 + 1020 + 465 74 20 - 1093.5 - 1103 + 1058.5 + 475 @@ -7004,14 +3975,14 @@ class Beam_fromCurve(component): - 1055 - 1113 + 1020 + 485 74 20 - 1093.5 - 1123 + 1058.5 + 495 @@ -7034,14 +4005,14 @@ class Beam_fromCurve(component): - 1055 - 1133 + 1020 + 505 74 20 - 1093.5 - 1143 + 1058.5 + 515 @@ -7060,14 +4031,14 @@ class Beam_fromCurve(component): - 1159 - 1033 + 1124 + 405 37 60 - 1177.5 - 1063 + 1142.5 + 435 @@ -7086,14 +4057,14 @@ class Beam_fromCurve(component): - 1159 - 1093 + 1124 + 465 37 60 - 1177.5 - 1123 + 1142.5 + 495 @@ -7105,7 +4076,7 @@ class Beam_fromCurve(component): - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -7124,14 +4095,14 @@ class Beam_fromCurve(component): - 831 - 1101 + 798 + 475 166 20 - 831.0512 - 1101.648 + 798.3945 + 475.1427 @@ -7150,15 +4121,16 @@ class Beam_fromCurve(component): - + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 Curve - + Contains a collection of generic curves + true 18d2fa4a-de1b-4cfd-bf80-4b8d5fa0182f Curve Crv @@ -7169,14 +4141,14 @@ class Beam_fromCurve(component): - 495 - 1001 + 350 + 404 50 24 - 520.374 - 1013.907 + 375.6421 + 416.2252 @@ -7208,7 +4180,7 @@ class Beam_fromCurve(component): - + 410755b1-224a-4c1e-a407-bf32fb45ea7e 00000000-0000-0000-0000-000000000000 @@ -7321,7 +4293,7 @@ class Beam_fromCurve(component): true true - iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDQAACw0B7QfALAAAAhRJREFUSEulVLtKA0EUzSfkD3zlrUKMIcHGB6ghIj5QxEoDiiA2gtqGYBEfRRQiSRc7iXU6IWKZwh8Q/AI/wEcRr/cMmWUZ7ya7Whw2uXvvOTNnzqyPiP6Nob7+Q0bBQJ0xLQ64BRP4R6OxZ37Swf4+nRwdKyxmFyg4MEhcvxYH3YCH4+GhwHsqMd6+r9ep1WrRY7OpyGcmp2hvZxcCBXG4F3gQligykIIcIulkknJb26qGnXgW4AF/NBi6AzkIQAxcnl+AjAr5vFXzLMDN8ZFI9JXxfVMuW0RYMZ8D3dZqVs2zADeuRALBD3jbaDQUAZ74D2ib/iQwHI5UudHyFsNYLVaN5JjEGj0F+KUVQXisB+EzyO01CV0F+IUYwY21dZUUXXMCemGdKMBFMYIYgIDktx2wD0FAILBQO7FjBGGJveYE2Id5Jn8AH3g1uRhBHCLI7TUJ2BVCAHLuP9eLVgJc3GS/vyZSaboqldQWq5UKZeczyhYdSyfouCLGzLViJ9cCT1A2sbq03NNv7Ay7joXCLzzTb5JbAqa/mdm5XzfTRCeG1Dk35bcEUQAJchLArpAmkDMOJVI7PAkgrrgbuCM8F5cITbgWQFyHI9F2x29HS0y4EkBcuU/5LZF0Q1cB+I3fHMFP7stJBL3gKHBWLOoIvnGPK78lKAEQQkQjOZZQlnS+pq79lgABfNxw2UycSgPeQL4fsKzVEEQlKEcAAAAASUVORK5CYII= + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDAAACwwBP0AiyAAAAhRJREFUSEulVLtKA0EUzSfkD3zlrUKMIcHGB6ghIj5QxEoDiiA2gtqGYBEfRRQiSRc7iXU6IWKZwh8Q/AI/wEcRr/cMmWUZ7ya7Whw2uXvvOTNnzqyPiP6Nob7+Q0bBQJ0xLQ64BRP4R6OxZ37Swf4+nRwdKyxmFyg4MEhcvxYH3YCH4+GhwHsqMd6+r9ep1WrRY7OpyGcmp2hvZxcCBXG4F3gQligykIIcIulkknJb26qGnXgW4AF/NBi6AzkIQAxcnl+AjAr5vFXzLMDN8ZFI9JXxfVMuW0RYMZ8D3dZqVs2zADeuRALBD3jbaDQUAZ74D2ib/iQwHI5UudHyFsNYLVaN5JjEGj0F+KUVQXisB+EzyO01CV0F+IUYwY21dZUUXXMCemGdKMBFMYIYgIDktx2wD0FAILBQO7FjBGGJveYE2Id5Jn8AH3g1uRhBHCLI7TUJ2BVCAHLuP9eLVgJc3GS/vyZSaboqldQWq5UKZeczyhYdSyfouCLGzLViJ9cCT1A2sbq03NNv7Ay7joXCLzzTb5JbAqa/mdm5XzfTRCeG1Dk35bcEUQAJchLArpAmkDMOJVI7PAkgrrgbuCM8F5cITbgWQFyHI9F2x29HS0y4EkBcuU/5LZF0Q1cB+I3fHMFP7stJBL3gKHBWLOoIvnGPK78lKAEQQkQjOZZQlnS+pq79lgABfNxw2UycSgPeQL4fsKzVEEQlKEcAAAAASUVORK5CYII= false ea292354-f0cd-4b58-ba78-57c730de54ad @@ -7334,14 +4306,14 @@ class Beam_fromCurve(component): - 1051 - 1239 + 1016 + 611 145 124 - 1142 - 1301 + 1107 + 673 @@ -7378,14 +4350,14 @@ class Beam_fromCurve(component): - 1053 - 1241 + 1018 + 613 74 20 - 1091.5 - 1251 + 1056.5 + 623 @@ -7409,14 +4381,14 @@ class Beam_fromCurve(component): - 1053 - 1261 + 1018 + 633 74 20 - 1091.5 - 1271 + 1056.5 + 643 @@ -7433,7 +4405,7 @@ class Beam_fromCurve(component): true 1 true - 478fc3b1-c4f8-4087-8fe6-c2905e21be9a + da50cc8d-94c7-4348-b6e9-2f46272647d3 1 39fbc626-7a01-46ab-a18e-ec1c0c41685b @@ -7441,14 +4413,14 @@ class Beam_fromCurve(component): - 1053 - 1281 + 1018 + 653 74 20 - 1091.5 - 1291 + 1056.5 + 663 @@ -7473,14 +4445,14 @@ class Beam_fromCurve(component): - 1053 - 1301 + 1018 + 673 74 20 - 1091.5 - 1311 + 1056.5 + 683 @@ -7504,14 +4476,14 @@ class Beam_fromCurve(component): - 1053 - 1321 + 1018 + 693 74 20 - 1091.5 - 1331 + 1056.5 + 703 @@ -7534,14 +4506,14 @@ class Beam_fromCurve(component): - 1053 - 1341 + 1018 + 713 74 20 - 1091.5 - 1351 + 1056.5 + 723 @@ -7560,14 +4532,14 @@ class Beam_fromCurve(component): - 1157 - 1241 + 1122 + 613 37 60 - 1175.5 - 1271 + 1140.5 + 643 @@ -7586,14 +4558,14 @@ class Beam_fromCurve(component): - 1157 - 1301 + 1122 + 673 37 60 - 1175.5 - 1331 + 1140.5 + 703 @@ -7605,7 +4577,7 @@ class Beam_fromCurve(component): - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -7624,14 +4596,14 @@ class Beam_fromCurve(component): - 866 - 1296 + 833 + 670 166 20 - 866.0197 - 1296.777 + 833.363 + 670.2716 @@ -7650,7 +4622,7 @@ class Beam_fromCurve(component): - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -7669,14 +4641,14 @@ class Beam_fromCurve(component): - 831 - 1078 + 820 + 697 166 20 - 831.1492 - 1078.275 + 820.9324 + 697.9297 @@ -7695,7 +4667,7 @@ class Beam_fromCurve(component): - + 22990b1f-9be6-477c-ad89-f775cd347105 Flip Curve @@ -7713,14 +4685,14 @@ class Beam_fromCurve(component): - 853 - 1246 + 818 + 618 66 44 - 885 - 1268 + 850 + 640 @@ -7738,14 +4710,14 @@ class Beam_fromCurve(component): - 855 - 1248 + 820 + 620 15 20 - 864 - 1258 + 829 + 630 @@ -7764,14 +4736,14 @@ class Beam_fromCurve(component): - 855 - 1268 + 820 + 640 15 20 - 864 - 1278 + 829 + 650 @@ -7790,14 +4762,14 @@ class Beam_fromCurve(component): - 900 - 1248 + 865 + 620 17 20 - 908.5 - 1258 + 873.5 + 630 @@ -7816,14 +4788,14 @@ class Beam_fromCurve(component): - 900 - 1268 + 865 + 640 17 20 - 908.5 - 1278 + 873.5 + 650 @@ -7833,7 +4805,7 @@ class Beam_fromCurve(component): - + 11bbd48b-bb0a-4f1b-8167-fa297590390d End Points @@ -7851,14 +4823,14 @@ class Beam_fromCurve(component): - 704 - 999 + 513 + 588 64 44 - 735 - 1021 + 544 + 610 @@ -7876,14 +4848,14 @@ class Beam_fromCurve(component): - 706 - 1001 + 515 + 590 14 40 - 714.5 - 1021 + 523.5 + 610 @@ -7902,14 +4874,14 @@ class Beam_fromCurve(component): - 750 - 1001 + 559 + 590 16 20 - 758 - 1011 + 567 + 600 @@ -7928,14 +4900,14 @@ class Beam_fromCurve(component): - 750 - 1021 + 559 + 610 16 20 - 758 - 1031 + 567 + 620 @@ -7945,7 +4917,7 @@ class Beam_fromCurve(component): - + e9eb1dcf-92f6-4d4d-84ae-96222d60f56b Move @@ -7963,14 +4935,14 @@ class Beam_fromCurve(component): - 810 - 1019 + 619 + 608 83 44 - 858 - 1041 + 667 + 630 @@ -7988,14 +4960,14 @@ class Beam_fromCurve(component): - 812 - 1021 + 621 + 610 31 20 - 837 - 1031 + 646 + 620 @@ -8016,14 +4988,14 @@ class Beam_fromCurve(component): - 812 - 1041 + 621 + 630 31 20 - 837 - 1051 + 646 + 640 @@ -8066,14 +5038,14 @@ class Beam_fromCurve(component): - 873 - 1021 + 682 + 610 18 20 - 882 - 1031 + 691 + 620 @@ -8092,14 +5064,14 @@ class Beam_fromCurve(component): - 873 - 1041 + 682 + 630 18 20 - 882 - 1051 + 691 + 640 @@ -8109,7 +5081,7 @@ class Beam_fromCurve(component): - + 79f9fbb3-8f1d-4d9a-88a9-f7961b1012cd Unit X @@ -8127,14 +5099,14 @@ class Beam_fromCurve(component): - 490 - 1042 + 337 + 641 63 28 - 519 - 1056 + 366 + 655 @@ -8152,14 +5124,14 @@ class Beam_fromCurve(component): - 492 - 1044 + 339 + 643 12 24 - 499.5 - 1056 + 346.5 + 655 @@ -8198,14 +5170,14 @@ class Beam_fromCurve(component): - 534 - 1044 + 381 + 643 17 24 - 542.5 - 1056 + 389.5 + 655 @@ -8215,7 +5187,7 @@ class Beam_fromCurve(component): - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -8234,14 +5206,14 @@ class Beam_fromCurve(component): - 301 - 1048 + 154 + 648 163 20 - 301.1051 - 1048.834 + 154.723 + 648.6322 @@ -8260,7 +5232,7 @@ class Beam_fromCurve(component): - + 4c4e56eb-2f04-43f9-95a3-cc46a14f495a Line @@ -8278,14 +5250,14 @@ class Beam_fromCurve(component): - 920 - 999 + 729 + 588 63 44 - 951 - 1021 + 760 + 610 @@ -8303,14 +5275,14 @@ class Beam_fromCurve(component): - 922 - 1001 + 731 + 590 14 20 - 930.5 - 1011 + 739.5 + 600 @@ -8330,14 +5302,14 @@ class Beam_fromCurve(component): - 922 - 1021 + 731 + 610 14 20 - 930.5 - 1031 + 739.5 + 620 @@ -8356,14 +5328,14 @@ class Beam_fromCurve(component): - 966 - 1001 + 775 + 590 15 40 - 973.5 - 1021 + 782.5 + 610 @@ -8373,7 +5345,7 @@ class Beam_fromCurve(component): - + 9103c240-a6a9-4223-9b42-dbd19bf38e2b Unit Z @@ -8391,14 +5363,14 @@ class Beam_fromCurve(component): - 492 - 1076 + 339 + 675 63 28 - 521 - 1090 + 368 + 689 @@ -8416,14 +5388,14 @@ class Beam_fromCurve(component): - 494 - 1078 + 341 + 677 12 24 - 501.5 - 1090 + 348.5 + 689 @@ -8462,14 +5434,14 @@ class Beam_fromCurve(component): - 536 - 1078 + 383 + 677 17 24 - 544.5 - 1090 + 391.5 + 689 @@ -8479,7 +5451,7 @@ class Beam_fromCurve(component): - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -8498,14 +5470,14 @@ class Beam_fromCurve(component): - 307 - 1081 + 157 + 683 163 20 - 307.3143 - 1081.519 + 157.0529 + 683.7195 @@ -8524,7 +5496,7 @@ class Beam_fromCurve(component): - + a0d62394-a118-422d-abb3-6af115c75b25 Addition @@ -8542,14 +5514,14 @@ class Beam_fromCurve(component): - 705 - 1044 + 514 + 633 65 44 - 736 - 1066 + 545 + 655 @@ -8576,14 +5548,14 @@ class Beam_fromCurve(component): - 707 - 1046 + 516 + 635 14 20 - 715.5 - 1056 + 524.5 + 645 @@ -8603,14 +5575,14 @@ class Beam_fromCurve(component): - 707 - 1066 + 516 + 655 14 20 - 715.5 - 1076 + 524.5 + 665 @@ -8629,14 +5601,14 @@ class Beam_fromCurve(component): - 751 - 1046 + 560 + 635 17 40 - 759.5 - 1066 + 568.5 + 655 @@ -8648,7 +5620,7 @@ class Beam_fromCurve(component): - + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a Boolean Toggle @@ -8662,14 +5634,14 @@ class Beam_fromCurve(component): FlipCurve false 0 - true + false - 439 - 1198 + 286 + 580 115 22 @@ -8679,7 +5651,7 @@ class Beam_fromCurve(component): - + eeafc956-268e-461d-8e73-ee05c6f72c01 Stream Filter @@ -8697,14 +5669,14 @@ class Beam_fromCurve(component): - 934 - 1206 + 899 + 578 77 64 - 966 - 1238 + 931 + 610 @@ -8732,14 +5704,14 @@ class Beam_fromCurve(component): - 936 - 1208 + 901 + 580 15 20 - 945 - 1218 + 910 + 590 @@ -8781,14 +5753,14 @@ class Beam_fromCurve(component): - 936 - 1228 + 901 + 600 15 20 - 945 - 1238 + 910 + 610 @@ -8810,14 +5782,14 @@ class Beam_fromCurve(component): - 936 - 1248 + 901 + 620 15 20 - 945 - 1258 + 910 + 630 @@ -8830,7 +5802,7 @@ class Beam_fromCurve(component): 09fe95fb-9551-4690-abf6-4a0665002914 false Stream - S(1) + S(0) false 0 @@ -8838,14 +5810,14 @@ class Beam_fromCurve(component): - 981 - 1208 + 946 + 580 28 60 - 995 - 1238 + 960 + 610 @@ -8857,7 +5829,7 @@ class Beam_fromCurve(component): - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -8875,7 +5847,7 @@ class Beam_fromCurve(component): 2 1dc4a321-0021-4a13-90df-e852c7e4e773 Group - + MainBeam @@ -8883,25 +5855,24 @@ class Beam_fromCurve(component): - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group - + 1 150;255;255;255 A group of Grasshopper objects - 18d2fa4a-de1b-4cfd-bf80-4b8d5fa0182f - a8a91d2c-6d94-4451-b47e-a81520b31d82 - ae229730-bb17-41d8-826e-9f750d58d128 - 54426880-0968-40ad-8b1e-e50de0a67289 - 46feaa45-4a40-458f-a4fd-9de5989af911 - 5 + a8a91d2c-6d94-4451-b47e-a81520b31d82 + ae229730-bb17-41d8-826e-9f750d58d128 + 54426880-0968-40ad-8b1e-e50de0a67289 + 46feaa45-4a40-458f-a4fd-9de5989af911 + 4 f05fce76-e87f-48e1-a86d-07f6d4084049 Group @@ -8912,146 +5883,255 @@ class Beam_fromCurve(component): - + - 0148a65d-6f42-414a-9db7-9a9b2eb78437 - Brep Edges + 9c53bac0-ba66-40bd-8154-ce9829b9db1a + Colour Swatch - - Extract the edge curves of a brep. - 2253210f-5171-4c3a-9ada-77a25c570614 - Brep Edges - Edges + + Colour (palette) swatch + c0a53353-9802-4df7-a063-6228afad4537 + Colour Swatch + Swatch + false + 0 + + 34;168;168;168 + - + - 1234 - 1091 - 72 - 64 + 2737 + 544 + 88 + 20 - 1264 - 1123 + 2737.545 + 544.3754 - - - Base Brep - a1ae6357-26a0-48f8-87ed-a42ad7d83c0c - Brep - B - false - b0a6568e-06d8-4c68-b393-d8274f661640 - 1 - - - - - - 1236 - 1093 - 13 - 60 - - - 1244 - 1123 - - - - - - - - 1 - Naked edge curves - 50b5c643-729b-4fa5-96ea-b704123acb76 - Naked - En - false - 0 + + + + + + + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a + Boolean Toggle + + + + + Boolean (true/false) toggle + 8af59bcf-e43d-4808-b965-440e7d003dfd + Boolean Toggle + Toggle + false + 0 + false + + + + + + 2526 + 1387 + 104 + 22 + - - - - - 1279 - 1093 - 25 - 20 - - - 1291.5 - 1103 - - - - - - - 1 - Interior edge curves - 97b2db50-f0aa-43bd-8e24-5e5454f72868 - Interior - Ei - false - 0 + + + + + + + 7f5c6c55-f846-4a08-9c9a-cfdc285cc6fe + Scribble + + + + + true + + 2405.003 + 171.456 + + + 2762.522 + 171.456 + + + 2762.522 + 218.7949 + + + 2405.003 + 218.7949 + + A quick note + Microsoft Sans Serif + 1fda4fd4-ed01-4e84-8338-ca710b993b83 + false + Scribble + Scribble + 50 + StepJointNotch + + + + + + 2400.003 + 166.456 + 367.5195 + 57.33887 + + + 2405.003 + 171.456 + - - - - - 1279 - 1113 - 25 - 20 - - - 1291.5 - 1123 - - - - - - - 1 - Non-Manifold edge curves - 5d1a1624-ff28-44ae-a91f-5c01f8a5243d - Non-Manifold - Em - false - 0 + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 1fda4fd4-ed01-4e84-8338-ca710b993b83 + 1 + e5f92ef7-182d-4ea0-8d5a-b01aada26ebb + Group + + + + + + + + + + + 7f5c6c55-f846-4a08-9c9a-cfdc285cc6fe + Scribble + + + + + true + + 2470.777 + 1060.245 + + + 2691.896 + 1060.245 + + + 2691.896 + 1107.584 + + + 2470.777 + 1107.584 + + A quick note + Microsoft Sans Serif + 14f73845-d137-4e4f-939d-fc35194fd72e + false + Scribble + Scribble + 50 + StepJoint + + + + + + 2465.777 + 1055.245 + 231.1182 + 57.33887 + + + 2470.777 + 1060.245 + - - - - - 1279 - 1133 - 25 - 20 - - - 1291.5 - 1143 - - - - + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 14f73845-d137-4e4f-939d-fc35194fd72e + 1 + b6362569-cd6b-4fdf-a6bc-af4ac10ea9bf + Group + + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 18d2fa4a-de1b-4cfd-bf80-4b8d5fa0182f + 1 + e22e52d8-5a24-494f-8bd6-2f5c368cced0 + Group + CrossBeam + + + + + + + @@ -9059,7 +6139,7 @@ class Beam_fromCurve(component): - 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 From dcf7b0eef3c1274511189fb77e209d619c5d941d Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 9 Aug 2024 15:15:57 +0200 Subject: [PATCH 26/63] apply geometric implementetion --- src/compas_timber/_fabrication/step_joint.py | 254 +++++++++++-------- 1 file changed, 147 insertions(+), 107 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index 34507c32d..ec920d512 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -5,7 +5,6 @@ from compas.geometry import Plane from compas.geometry import Rotation from compas.geometry import Vector -from compas.geometry import Polyhedron from compas.geometry import Brep from compas.geometry import Polyline from compas.geometry import angle_vectors_signed @@ -248,7 +247,7 @@ def from_plane_and_beam(cls, plane, beam, step_depth=20.0, heel_depth=0.0, taper # calculate strut_inclination strut_inclination = cls._calculate_strut_inclination(ref_side, plane, orientation) - # restrain step_depth & heel_depth to beam's height and the maximum possible heel depth for the beam # TODO: should it be restrained? should they be proportional to the beam's dimensions? + # restrain step_depth & heel_depth to beam's height and the maximum possible heel depth for the beam step_depth = beam.height if step_depth > beam.height else step_depth max_heel_depth = abs(beam.height / math.tan(math.radians(strut_inclination))) heel_depth = max_heel_depth if heel_depth > max_heel_depth else heel_depth @@ -318,71 +317,114 @@ def _calculate_x_displacement_heel(heel_depth, strut_inclination, orientation): # Methods ######################################################################## - # def apply(self, geometry, beam): - # """Apply the feature to the beam geometry. - - # Parameters - # ---------- - # geometry : :class:`~compas.geometry.Brep` - # The beam geometry to be milled. - # beam : :class:`compas_timber.elements.Beam` - # The beam that is milled by this instance. - - # Raises - # ------ - # :class:`~compas_timber.elements.FeatureApplicationError` - # If the cutting planes do not create a volume that itersects with beam geometry or any step fails. - - # Returns - # ------- - # :class:`~compas.geometry.Brep` - # The resulting geometry after processing - - # """ - # # type: (Brep, Beam) -> Brep - # # get cutting planes from params - # try: - # cutting_planes = self.planes_from_params_and_beam(beam) - # except ValueError as e: - # raise FeatureApplicationError( - # None, geometry, "Failed to generate cutting planes from parameters and beam: {}".format(str(e)) - # ) - # # create notch polyedron from planes - # # add ref_side plane to create a polyhedron - # # !: the beam's ref_side Plane might need to be offsetted to create a valid polyhedron when step_type is "double" - # cutting_planes.append(Plane.from_frame(beam.ref_sides[self.ref_side_index])) - # try: - # notch_polyhedron = Polyhedron.from_planes(cutting_planes) - # except Exception as e: - # raise FeatureApplicationError( - # cutting_planes, geometry, "Failed to create valid polyhedron from cutting planes: {}".format(str(e)) - # ) - # # convert polyhedron to mesh - # try: - # notch_mesh = notch_polyhedron.to_mesh() - # except Exception as e: - # raise FeatureApplicationError(notch_polyhedron, geometry, "Failed to convert polyhedron to mesh: {}".format(str(e))) - # # convert mesh to brep - # try: - # notch_brep = Brep.from_mesh(notch_mesh) - # except Exception as e: - # raise FeatureApplicationError(notch_mesh, geometry, "Failed to convert mesh to Brep: {}".format(str(e))) - # # apply boolean difference - # try: - # brep_with_notch = Brep.from_boolean_difference(geometry, notch_brep) - # except Exception as e: - # raise FeatureApplicationError(notch_brep, geometry, "Boolean difference operation failed: {}".format(str(e))) - # # check if the notch is empty - # if not brep_with_notch: - # raise FeatureApplicationError( - # notch_brep, geometry, "The cutting planes do not create a volume that intersects with beam geometry." - # ) - - # if self.tenon: # !: implement tenon - # # create tenon volume and subtract from brep - # pass - - # return brep_with_notch + def apply(self, beam): + """Apply the feature to the beam geometry. + + Parameters + ---------- + beam : :class:`compas_timber.elements.Beam` + The beam that is milled by this instance. + + Raises + ------ + :class:`~compas_timber.elements.FeatureApplicationError` + If the cutting planes do not create a volume that itersects with beam geometry or any step fails. + + Returns + ------- + :class:`~compas.geometry.Brep` + The resulting geometry after processing + + """ + # type: (Beam) -> Brep + + # compute the geometry of the beam as a Brep + geometry = beam.compute_geometry() + + # get cutting planes from params and beam + try: + cutting_planes = self.planes_from_params_and_beam(beam) + except ValueError as e: + raise FeatureApplicationError( + None, geometry, "Failed to generate cutting planes from parameters and beam: {}".format(str(e)) + ) + + if self.step_shape == StepShape.STEP: + for cutting_plane in cutting_planes: + cutting_plane.normal = cutting_plane.normal * -1 + try: + geometry.trim(cutting_plane) + except Exception as e: + raise FeatureApplicationError( + cutting_plane, geometry, "Failed to trim geometry with cutting planes: {}".format(str(e)) + ) + return geometry + + elif self.step_shape == StepShape.HEEL: + trimmed_geometies = [] + for cutting_plane in cutting_planes: + cutting_plane.normal = cutting_plane.normal * -1 + try: + trimmed_geometies.append(geometry.trimmed(cutting_plane)) + except Exception as e: + raise FeatureApplicationError( + cutting_plane, geometry, "Failed to trim geometry with cutting plane: {}".format(str(e)) + ) + try: + heel_geometry = Brep.from_boolean_union( + *trimmed_geometies + ) #! (gh) Error: Runtime error (Exception): PluginNotInstalledError + except Exception as e: + raise FeatureApplicationError( + trimmed_geometies, geometry, "Failed to union trimmed geometries: {}".format(str(e)) + ) + return heel_geometry + + elif self.step_shape == StepShape.TAPERED_HEEL: + try: + cutting_plane = cutting_planes[0] + cutting_plane.normal = cutting_plane.normal * -1 + geometry.trim(cutting_plane) + except Exception as e: + raise FeatureApplicationError( + cutting_planes, geometry, "Failed to trim geometry with cutting plane: {}".format(str(e)) + ) + return geometry + + elif self.step_shape == StepShape.DOUBLE: + # trim geometry with last cutting plane + cutting_planes[-1].normal = cutting_planes[-1].normal * -1 + try: + geometry.trim(cutting_planes[-1]) + except Exception as e: + raise FeatureApplicationError( + cutting_planes[-1], geometry, "Failed to trim geometry with cutting plane: {}".format(str(e)) + ) + # trim geometry with first two cutting planes + trimmed_geometies = [] + for cutting_plane in cutting_planes[:2]: + cutting_plane.normal = cutting_plane.normal * -1 + try: + trimmed_geometies.append(geometry.trimmed(cutting_plane)) + except Exception as e: + raise FeatureApplicationError( + cutting_plane, geometry, "Failed to trim geometry with cutting plane: {}".format(str(e)) + ) + try: + double_geometry = Brep.from_boolean_union( + *trimmed_geometies + ) #! (gh) Error: Runtime error (Exception): PluginNotInstalledError + except Exception as e: + raise FeatureApplicationError( + trimmed_geometies, geometry, "Failed to union trimmed geometries: {}".format(str(e)) + ) + return double_geometry + + if self.tenon: + # create tenon volume and subtract from brep + pass + + return geometry def add_tenon(self, tenon_width, tenon_height): """Add a tenon to the existing StepJointNotch instance. @@ -529,53 +571,51 @@ def tenon_volume_from_params_and_beam(self, beam): """ # type: (Beam) -> Mesh - assert self.strut_inclination is not None - assert self.step_shape is not None - assert self.tenon == True + assert self.tenon assert self.tenon_width is not None assert self.tenon_height is not None # start with a plane aligned with the ref side but shifted to the start of the first cut ref_side = beam.side_as_surface(self.ref_side_index) - rot_axis = ref_side.frame.yaxis - - start_x = self.start_x - displacement_x = self.strut_height / math.sin(math.radians(self.strut_inclination)) - start_y = self.start_y + (self.notch_width - self.tenon_width) / 2 - displacement_y = self.tenon_width + opp_side = beam.side_as_surface((self.ref_side_index + 2) % 4) - step_cutting_planes = self._calculate_step_planes(ref_side, rot_axis) - step_cutting_plane = step_cutting_planes[1] # the second cutting plane is the one at the end of the step + x_displacement_end = self._calculate_x_displacement_end(beam.height, self.strut_inclination) - if self.orientation == OrientationType.END: - displacement_x = -displacement_x # negative displacement for the end cut - rot_axis = -rot_axis # negative rotation axis for the end cut - step_cutting_plane = step_cutting_planes[0] # the first cutting plane is the one at the start of the step - - # find the points that create the top face of the tenon - p_1 = ref_side.point_at(start_x, start_y) - p_2 = ref_side.point_at(start_x + displacement_x, start_y) - p_3 = ref_side.point_at(start_x + displacement_x, start_y + displacement_y) - p_4 = ref_side.point_at(start_x, start_y + displacement_y) - - # construct polyline for the top face of the tenon - tenon_polyline = Polyline([p_1, p_2, p_3, p_4, p_1]) - # calcutate the plane for the extrusion of the polyline - extr_plane = Plane(p_1, ref_side.frame.xaxis) + # Get the points of the top face of the tenon on the ref_side and opp_side + # x-displcement + start_x_ref = self.start_x + start_x_opp = self.start_x + x_displacement_end + # y-displacement + start_y = (beam.width - self.tenon_width) / 2 + end_y = start_y + self.tenon_width + # points at ref_side + p_ref_start = ref_side.point_at(start_x_ref, start_y) + p_ref_end = ref_side.point_at(start_x_ref, end_y) + # points at opp_side + p_opp_start = opp_side.point_at(start_x_opp, start_y) + p_opp_end = opp_side.point_at(start_x_opp, end_y) + + # construct the polyline for the top face of the tenon + tenon_polyline = Polyline([p_ref_start, p_ref_end, p_opp_start, p_opp_end, p_ref_start]) + + # calcutate the extrusion vector of the tenon extr_vector_length = self.tenon_height / math.sin(math.radians(self.strut_inclination)) - extr_vector = extr_plane.normal * extr_vector_length - if self.strut_inclination > 90: - vector_angle = math.radians(180 - self.strut_inclination) - else: - vector_angle = math.radians(self.strut_inclination) - rot_vect = Rotation.from_axis_and_angle(rot_axis, vector_angle) - extr_vector.transform(rot_vect) - # extrude the polyline to create the tenon volume as a Brep - tenon_volume = Brep.from_extrusion(tenon_polyline, extr_vector, cap_ends=True) + extr_vector = ref_side.frame.xaxis * extr_vector_length + if self.orientation == OrientationType.END: + extr_vector = -extr_vector + + # translate the polyline to create the tenon volume + tenon_polyline_extrusion = tenon_polyline.translated(extr_vector) + + return tenon_polyline, tenon_polyline_extrusion + + ## extrude the polyline to create the tenon volume as a Brep + # tenon_volume = Brep.from_extrusion(tenon_polyline, extr_vector, cap_ends=True) # trim brep with step cutting planes - tenon_volume.trim(step_cutting_plane) # !: check if the trimming works correctly // add checks + # tenon_volume.trim(step_cutting_plane) # !: check if the trimming works correctly // add checks + + # return tenon_volume - return tenon_volume class StepJointParams(BTLxProcessParams): """A class to store the parameters of a Step Joint feature. @@ -606,7 +646,7 @@ def as_dict(self): result["StepDepth"] = "{:.{prec}f}".format(self._instance.step_depth, prec=TOL.precision) result["HeelDepth"] = "{:.{prec}f}".format(self._instance.heel_depth, prec=TOL.precision) result["StepShape"] = self._instance.step_shape - result["tenon"] = "yes" if self._instance.tenon else "no" - result["tenonWidth"] = "{:.{prec}f}".format(self._instance.tenon_width, prec=TOL.precision) - result["tenonHeight"] = "{:.{prec}f}".format(self._instance.tenon_height, prec=TOL.precision) + result["Tenon"] = "yes" if self._instance.tenon else "no" + result["TenonWidth"] = "{:.{prec}f}".format(self._instance.tenon_width, prec=TOL.precision) + result["TenonHeight"] = "{:.{prec}f}".format(self._instance.tenon_height, prec=TOL.precision) return result From 4b2cea65b23c2be53548df9b66ae9dc4b0a6c54b Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 9 Aug 2024 15:19:27 +0200 Subject: [PATCH 27/63] formatting --- src/compas_timber/_fabrication/__init__.py | 2 + src/compas_timber/_fabrication/step_joint.py | 39 +++++++++++++++----- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/compas_timber/_fabrication/__init__.py b/src/compas_timber/_fabrication/__init__.py index b57f6193c..93fcf3b57 100644 --- a/src/compas_timber/_fabrication/__init__.py +++ b/src/compas_timber/_fabrication/__init__.py @@ -7,6 +7,7 @@ from .step_joint_notch import StepJointNotch from .step_joint_notch import StepJointNotchParams from .step_joint import StepJoint +from .step_joint import StepJointParams __all__ = [ @@ -17,4 +18,5 @@ "StepJointNotch", "StepJointNotchParams", "StepJoint", + "StepJointParams", ] diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index ec920d512..cfe3de506 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -468,7 +468,9 @@ def planes_from_params_and_beam(self, beam): # Calculate the displacements for the cutting planes along the y-axis and x-axis y_displacement_end = self._calculate_y_displacement_end(beam.height, self.strut_inclination) x_displacement_end = self._calculate_x_displacement_end(beam.height, self.strut_inclination) - x_displacement_heel = self._calculate_x_displacement_heel(self.heel_depth, self.strut_inclination, self.orientation) + x_displacement_heel = self._calculate_x_displacement_heel( + self.heel_depth, self.strut_inclination, self.orientation + ) # Get the points at the start of the step, at the end and at the heel p_ref = ref_side.point_at(self.start_x, 0) @@ -487,18 +489,22 @@ def planes_from_params_and_beam(self, beam): rot_axis = -cutting_plane_ref.yaxis if self.step_shape == StepShape.STEP: - return self._calculate_step_planes(cutting_plane_ref, cutting_plane_opp, y_displacement_end, incl_angle, rot_axis) + return self._calculate_step_planes( + cutting_plane_ref, cutting_plane_opp, y_displacement_end, incl_angle, rot_axis + ) elif self.step_shape == StepShape.HEEL: return self._calculate_heel_planes(cutting_plane_heel, cutting_plane_opp, incl_angle, rot_axis) elif self.step_shape == StepShape.TAPERED_HEEL: return self._calculate_heel_tapered_planes(cutting_plane_opp, y_displacement_end, incl_angle, rot_axis) elif self.step_shape == StepShape.DOUBLE: - return self._calculate_double_planes(cutting_plane_heel, cutting_plane_opp, y_displacement_end, x_displacement_heel, incl_angle, rot_axis) + return self._calculate_double_planes( + cutting_plane_heel, cutting_plane_opp, y_displacement_end, x_displacement_heel, incl_angle, rot_axis + ) def _calculate_step_planes(self, cutting_plane_ref, cutting_plane_opp, displacement_end, incl_angle, rot_axis): """Calculate cutting planes for a step.""" # Rotate cutting plane at opp_side - angle_opp = incl_angle/2 + angle_opp = incl_angle / 2 rot_opp = Rotation.from_axis_and_angle(rot_axis, angle_opp, point=cutting_plane_opp.point) cutting_plane_opp.transform(rot_opp) @@ -525,20 +531,25 @@ def _calculate_heel_planes(self, cutting_plane_heel, cutting_plane_opp, incl_ang def _calculate_heel_tapered_planes(self, cutting_plane_opp, displacement_end, incl_angle, rot_axis): """Calculate cutting planes for a tapered heel.""" # Rotate cutting plane at opp_side - angle_opp = incl_angle - math.atan(self.heel_depth / (displacement_end-(self.heel_depth / abs(math.tan(math.radians(self.strut_inclination)))))) + angle_opp = incl_angle - math.atan( + self.heel_depth + / (displacement_end - (self.heel_depth / abs(math.tan(math.radians(self.strut_inclination))))) + ) rot_opp = Rotation.from_axis_and_angle(rot_axis, angle_opp, point=cutting_plane_opp.point) cutting_plane_opp.transform(rot_opp) return [Plane.from_frame(cutting_plane_opp)] - def _calculate_double_planes(self, cutting_plane_heel, cutting_plane_opp, displacement_end, displacement_heel, incl_angle, rot_axis): + def _calculate_double_planes( + self, cutting_plane_heel, cutting_plane_opp, displacement_end, displacement_heel, incl_angle, rot_axis + ): """Calculate cutting planes for a double step.""" # Rotate first cutting plane at displaced origin rot_origin = Rotation.from_axis_and_angle(rot_axis, math.radians(90), point=cutting_plane_heel.point) cutting_plane_heel.transform(rot_origin) # Rotate last cutting plane at opp_side - rot_opp = Rotation.from_axis_and_angle(rot_axis, incl_angle/2, point=cutting_plane_opp.point) + rot_opp = Rotation.from_axis_and_angle(rot_axis, incl_angle / 2, point=cutting_plane_opp.point) cutting_plane_opp.transform(rot_opp) # Translate first cutting plane at heel @@ -547,13 +558,23 @@ def _calculate_double_planes(self, cutting_plane_heel, cutting_plane_opp, displa cutting_plane_heel_mid = cutting_plane_heel.translated(trans_vect * trans_len) # Calculate rotation angle for middle cutting plane heel_hypotenus = math.sqrt(math.pow(trans_len, 2) + math.pow(displacement_heel, 2)) - angle_heel = incl_angle - math.radians(90) + math.atan(self.step_depth / (displacement_end - heel_hypotenus - self.step_depth / math.tan(incl_angle/2))) + angle_heel = ( + incl_angle + - math.radians(90) + + math.atan( + self.step_depth / (displacement_end - heel_hypotenus - self.step_depth / math.tan(incl_angle / 2)) + ) + ) # Rotate middle cutting plane at heel rot_heel = Rotation.from_axis_and_angle(rot_axis, angle_heel, point=cutting_plane_heel_mid.point) cutting_plane_heel_mid.transform(rot_heel) - return [Plane.from_frame(cutting_plane_heel), Plane.from_frame(cutting_plane_heel_mid), Plane.from_frame(cutting_plane_opp)] + return [ + Plane.from_frame(cutting_plane_heel), + Plane.from_frame(cutting_plane_heel_mid), + Plane.from_frame(cutting_plane_opp), + ] def tenon_volume_from_params_and_beam(self, beam): """Calculates the tenon volume from the machining parameters in this instance and the given beam From a16d85236c92b9a0c7180523abc5053d62694efc Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 9 Aug 2024 17:00:25 +0200 Subject: [PATCH 28/63] fully implement step joint --- src/compas_timber/_fabrication/step_joint.py | 51 ++++++++++++-------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index cfe3de506..0f007ff68 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -5,6 +5,7 @@ from compas.geometry import Plane from compas.geometry import Rotation from compas.geometry import Vector +from compas.geometry import Box from compas.geometry import Brep from compas.geometry import Polyline from compas.geometry import angle_vectors_signed @@ -358,7 +359,6 @@ def apply(self, beam): raise FeatureApplicationError( cutting_plane, geometry, "Failed to trim geometry with cutting planes: {}".format(str(e)) ) - return geometry elif self.step_shape == StepShape.HEEL: trimmed_geometies = [] @@ -371,14 +371,13 @@ def apply(self, beam): cutting_plane, geometry, "Failed to trim geometry with cutting plane: {}".format(str(e)) ) try: - heel_geometry = Brep.from_boolean_union( - *trimmed_geometies - ) #! (gh) Error: Runtime error (Exception): PluginNotInstalledError + geometry = ( + trimmed_geometies[0] + trimmed_geometies[1] + ) # TODO: should be swed (.sew()) for a cleaner Brep except Exception as e: raise FeatureApplicationError( trimmed_geometies, geometry, "Failed to union trimmed geometries: {}".format(str(e)) ) - return heel_geometry elif self.step_shape == StepShape.TAPERED_HEEL: try: @@ -389,7 +388,6 @@ def apply(self, beam): raise FeatureApplicationError( cutting_planes, geometry, "Failed to trim geometry with cutting plane: {}".format(str(e)) ) - return geometry elif self.step_shape == StepShape.DOUBLE: # trim geometry with last cutting plane @@ -411,18 +409,32 @@ def apply(self, beam): cutting_plane, geometry, "Failed to trim geometry with cutting plane: {}".format(str(e)) ) try: - double_geometry = Brep.from_boolean_union( - *trimmed_geometies - ) #! (gh) Error: Runtime error (Exception): PluginNotInstalledError + geometry = ( + trimmed_geometies[0] + trimmed_geometies[1] + ) # TODO: should be swed (.sew()) for a cleaner Brep except Exception as e: raise FeatureApplicationError( trimmed_geometies, geometry, "Failed to union trimmed geometries: {}".format(str(e)) ) - return double_geometry - if self.tenon: + if self.tenon and self.step_shape == StepShape.STEP: # TODO: check if tenon applies only to step in BTLx # create tenon volume and subtract from brep - pass + tenon_volume = self.tenon_volume_from_params_and_beam(beam) + cutting_planes[0].normal = cutting_planes[0].normal * -1 + # trim tenon volume with cutting plane + try: + tenon_volume.trim(cutting_planes[0]) + except Exception as e: + raise FeatureApplicationError( + cutting_planes[0], tenon_volume, "Failed to trim tenon volume with cutting plane: {}".format(str(e)) + ) + # add tenon volume to geometry + try: + geometry += tenon_volume + except Exception as e: + raise FeatureApplicationError( + tenon_volume, geometry, "Failed to add tenon volume to geometry: {}".format(str(e)) + ) return geometry @@ -628,14 +640,15 @@ def tenon_volume_from_params_and_beam(self, beam): # translate the polyline to create the tenon volume tenon_polyline_extrusion = tenon_polyline.translated(extr_vector) - return tenon_polyline, tenon_polyline_extrusion - - ## extrude the polyline to create the tenon volume as a Brep - # tenon_volume = Brep.from_extrusion(tenon_polyline, extr_vector, cap_ends=True) - # trim brep with step cutting planes - # tenon_volume.trim(step_cutting_plane) # !: check if the trimming works correctly // add checks + # create Box from tenon points # TODO: should create Brep directly by extruding the polyline + tenon_points = tenon_polyline.points + tenon_polyline_extrusion.points + tenon_box = Box.from_points(tenon_points) - # return tenon_volume + # convert to Brep and trim with ref_side and opp_side + tenon_brep = Brep.from_box(tenon_box) + tenon_brep.trim(ref_side.to_plane()) + tenon_brep.trim(opp_side.to_plane()) + return tenon_brep class StepJointParams(BTLxProcessParams): From f8a0c4c66791198b035ee44e3aaa4b3a40d5a368 Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 9 Aug 2024 19:47:44 +0200 Subject: [PATCH 29/63] fully implement step and notch --- src/compas_timber/_fabrication/step_joint.py | 25 +- .../_fabrication/step_joint_notch.py | 316 ++-- tests/compas_timber/gh/test_step_joint.ghx | 1348 +++++++---------- 3 files changed, 702 insertions(+), 987 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index 0f007ff68..b9c96c29a 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -1,13 +1,13 @@ import math +from compas.geometry import Box +from compas.geometry import Brep from compas.geometry import Frame from compas.geometry import Line from compas.geometry import Plane +from compas.geometry import Polyline from compas.geometry import Rotation from compas.geometry import Vector -from compas.geometry import Box -from compas.geometry import Brep -from compas.geometry import Polyline from compas.geometry import angle_vectors_signed from compas.geometry import distance_point_point from compas.geometry import intersection_line_plane @@ -428,6 +428,16 @@ def apply(self, beam): raise FeatureApplicationError( cutting_planes[0], tenon_volume, "Failed to trim tenon volume with cutting plane: {}".format(str(e)) ) + # trim tenon volume with second cutting plane if tenon height is greater than step depth + if self.tenon_height > self.step_depth: + try: + tenon_volume.trim(cutting_planes[1]) + except Exception as e: + raise FeatureApplicationError( + cutting_planes[1], + tenon_volume, + "Failed to trim tenon volume with second cutting plane: {}".format(str(e)), + ) # add tenon volume to geometry try: geometry += tenon_volume @@ -646,8 +656,13 @@ def tenon_volume_from_params_and_beam(self, beam): # convert to Brep and trim with ref_side and opp_side tenon_brep = Brep.from_box(tenon_box) - tenon_brep.trim(ref_side.to_plane()) - tenon_brep.trim(opp_side.to_plane()) + try: + tenon_brep.trim(ref_side.to_plane()) + tenon_brep.trim(opp_side.to_plane()) + except Exception as e: + raise FeatureApplicationError( + None, tenon_brep, "Failed to trim tenon volume with cutting planes: {}".format(str(e)) + ) return tenon_brep diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index 1a8628c9e..c9f944b8a 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -1,10 +1,10 @@ import math +from compas.geometry import Box from compas.geometry import Brep from compas.geometry import Frame from compas.geometry import Line from compas.geometry import Plane -from compas.geometry import Polyhedron from compas.geometry import Polyline from compas.geometry import Rotation from compas.geometry import Vector @@ -427,7 +427,7 @@ def _calculate_displacement_heel(heel_depth, strut_inclination, orientation): # Methods ######################################################################## - def apply(self, geometry, beam): + def apply(self, beam): """Apply the feature to the beam geometry. Parameters @@ -449,6 +449,11 @@ def apply(self, geometry, beam): """ # type: (Brep, Beam) -> Brep + + # compute the geometry of the beam as a Brep and get the reference side + geometry = beam.compute_geometry() + ref_side = beam.side_as_surface(self.ref_side_index) + # get cutting planes from params try: cutting_planes = self.planes_from_params_and_beam(beam) @@ -456,46 +461,87 @@ def apply(self, geometry, beam): raise FeatureApplicationError( None, geometry, "Failed to generate cutting planes from parameters and beam: {}".format(str(e)) ) - # create notch polyedron from planes - # add ref_side plane to create a polyhedron - # !: the beam's ref_side Plane might need to be offsetted to create a valid polyhedron when step_type is "double" - cutting_planes.append(Plane.from_frame(beam.ref_sides[self.ref_side_index])) - try: - notch_polyhedron = Polyhedron.from_planes(cutting_planes) - except Exception as e: - raise FeatureApplicationError( - cutting_planes, geometry, "Failed to create valid polyhedron from cutting planes: {}".format(str(e)) - ) - # convert polyhedron to mesh - try: - notch_mesh = notch_polyhedron.to_mesh() - except Exception as e: - raise FeatureApplicationError( - notch_polyhedron, geometry, "Failed to convert polyhedron to mesh: {}".format(str(e)) - ) - # convert mesh to brep - try: - notch_brep = Brep.from_mesh(notch_mesh) - except Exception as e: - raise FeatureApplicationError(notch_mesh, geometry, "Failed to convert mesh to Brep: {}".format(str(e))) - # apply boolean difference - try: - brep_with_notch = Brep.from_boolean_difference(geometry, notch_brep) - except Exception as e: - raise FeatureApplicationError( - notch_brep, geometry, "Boolean difference operation failed: {}".format(str(e)) - ) - # check if the notch is empty - if not brep_with_notch: - raise FeatureApplicationError( - notch_brep, geometry, "The cutting planes do not create a volume that intersects with beam geometry." - ) - if self.mortise: # !: implement mortise - # create mortise volume and subtract from brep_with_notch - pass + # get box volume for subtracting from geometry + corner_1 = ref_side.point_at(self.start_x, self.start_y) + corner_2 = ref_side.point_at(self.start_x + self.displacement_end, self.start_y + self.notch_width) + subtraction_box = Box.from_corner_corner_height(corner_1, corner_2, max(self.step_depth, self.heel_depth)) + subtraction_box.translate(-ref_side.frame.zaxis * max(self.step_depth, self.heel_depth)) + subtraction_volume = Brep.from_box(subtraction_box) + + if self.step_shape == StepShape.DOUBLE: + # trim geometry with first and last cutting plane + try: + for cutting_plane in [cutting_planes[-1], cutting_planes[0]]: + cutting_plane.normal = cutting_plane.normal * -1 + subtraction_volume.trim(cutting_plane) + except Exception as e: + raise FeatureApplicationError( + cutting_planes[-1], + subtraction_volume, + "Failed to trim geometry with cutting plane: {}".format(str(e)), + ) + # trim geometry with two middle cutting planes + trimmed_geometies = [] + for cutting_plane in cutting_planes[1:-1]: + cutting_plane.normal = cutting_plane.normal * -1 + try: + trimmed_geometies.append(subtraction_volume.trimmed(cutting_plane)) + except Exception as e: + raise FeatureApplicationError( + cutting_plane, + subtraction_volume, + "Failed to trim geometry with cutting plane: {}".format(str(e)), + ) + subtraction_volume = trimmed_geometies + + else: + for cutting_plane in cutting_planes: + cutting_plane.normal = cutting_plane.normal * -1 + try: + subtraction_volume.trim(cutting_plane) + except Exception as e: + raise FeatureApplicationError( + cutting_plane, + subtraction_volume, + "Failed to trim geometry with cutting planes: {}".format(str(e)), + ) + + if self.mortise and self.step_shape == StepShape.STEP: # TODO: check if mortise applies only to step in BTLx + # create mortise volume and subtract from brep + mortise_volume = self.mortise_volume_from_params_and_beam(beam) + # trim mortise volume with cutting plane + try: + mortise_volume.trim(cutting_planes[0]) + except Exception as e: + raise FeatureApplicationError( + cutting_planes[0], + mortise_volume, + "Failed to trim mortise volume with cutting plane: {}".format(str(e)), + ) + # subtract mortise volume from geometry + try: + geometry -= mortise_volume + except Exception as e: + raise FeatureApplicationError( + mortise_volume, + subtraction_volume, + "Failed to subtract mortise volume from geometry: {}".format(str(e)), + ) + + # subtract volume from geometry + if isinstance(subtraction_volume, list): + for sub_vol in subtraction_volume: + try: + geometry -= sub_vol + except Exception as e: + raise FeatureApplicationError( + sub_vol, geometry, "Failed to subtract volume from geometry: {}".format(str(e)) + ) + else: + geometry - subtraction_volume - return brep_with_notch + return geometry def add_mortise(self, mortise_width, mortise_height, beam): """Add a mortise to the existing StepJointNotch instance. @@ -510,7 +556,13 @@ def add_mortise(self, mortise_width, mortise_height, beam): self.mortise = True # self.mortise_width = beam.width / 4 # TODO: should this relate to the beam? typically 1/3 or 1/4 of beam.width self.mortise_width = mortise_width - self.mortise_height = beam.height if mortise_height > beam.height else mortise_height + + if mortise_height > beam.height: # TODO: should this be constrained? + self.mortise_height = beam.height + elif mortise_height < self.step_depth: + self.mortise_height = self.step_depth + else: + self.mortise_height = mortise_height def planes_from_params_and_beam(self, beam): """Calculates the cutting planes from the machining parameters in this instance and the given beam @@ -533,19 +585,16 @@ def planes_from_params_and_beam(self, beam): ref_side = beam.side_as_surface(self.ref_side_index) rot_axis = ref_side.frame.yaxis - # if self.orientation == OrientationType.END: - # rot_axis = -rot_axis # Negative rotation axis for the end cut - if self.step_shape == StepShape.STEP: - return self._calculate_step_planes(ref_side, rot_axis) + return self._calculate_step_planes(ref_side) elif self.step_shape == StepShape.HEEL: - return self._calculate_heel_planes(ref_side, rot_axis) + return self._calculate_heel_planes(ref_side) elif self.step_shape == StepShape.TAPERED_HEEL: - return self._calculate_tapered_heel_planes(ref_side, rot_axis) + return self._calculate_tapered_heel_planes(ref_side) elif self.step_shape == StepShape.DOUBLE: - return self._calculate_double_planes(ref_side, rot_axis) + return self._calculate_double_planes(ref_side) - def _calculate_step_planes(self, ref_side, rot_axis): + def _calculate_step_planes(self, ref_side): """Calculate cutting planes for a step notch.""" # Move the frames to the start and end of the notch to create the cuts if self.strut_inclination > 90: @@ -565,84 +614,66 @@ def _calculate_step_planes(self, ref_side, rot_axis): self.step_depth / (self.displacement_end - self.step_depth / math.tan(math.radians(self.strut_inclination / 2))) ) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) + rot_long_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_long_side, point=p_origin) cutting_plane_origin.transform(rot_long_side) # Rotate second cutting plane at the end of the notch (short side of the step) angle_short_side = math.radians(180 - self.strut_inclination / 2) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) + rot_short_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_short_side, point=p_end) cutting_plane_end.transform(rot_short_side) else: # Rotate first cutting plane at the start of the notch (short side of the step) - angle_short_side = math.radians((180-self.strut_inclination)/2) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) + angle_short_side = math.radians((180 - self.strut_inclination) / 2) + rot_short_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_short_side, point=p_origin) cutting_plane_origin.transform(rot_short_side) - #Rotate second cutting plane at the end of the notch (large side of the step) + # Rotate second cutting plane at the end of the notch (large side of the step) angle_long_side = math.atan( self.step_depth - / (self.displacement_end + (self.step_depth / math.tan(math.radians((180 - self.strut_inclination)/2)))) + / ( + self.displacement_end + + (self.step_depth / math.tan(math.radians((180 - self.strut_inclination) / 2))) + ) ) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) + rot_long_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_long_side, point=p_end) cutting_plane_end.transform(rot_long_side) return [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_end)] - def _calculate_heel_planes(self, ref_side, rot_axis): + def _calculate_heel_planes(self, ref_side): """Calculate cutting planes for a heel notch.""" - # if self.strut_inclination > 90: # Move the frames to the start and end of the notch to create the cuts p_origin = ref_side.point_at(self.start_x, self.start_y) p_heel = ref_side.point_at(self.start_x + self.displacement_heel, self.start_y) - cutting_plane_end = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_end = Frame(p_origin, ref_side.frame.xaxis, -ref_side.frame.yaxis) + cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, -ref_side.frame.yaxis) # Calculate heel cutting planes angles # Rotate first cutting plane at the start of the notch (short side of the heel) angle_short_side = math.radians(180 - self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) + rot_short_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_short_side, point=p_origin) cutting_plane_end.transform(rot_short_side) # Rotate second cutting plane at the end of the notch (long side of the heel) angle_long_side = math.radians(270 - self.strut_inclination) - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_heel) + rot_long_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_long_side, point=p_heel) cutting_plane_heel.transform(rot_long_side) - # else: - # # Move the frames to the start and end of the notch to create the cuts - # p_end = ref_side.point_at(self.start_x, self.start_y) - # p_heel = ref_side.point_at(self.start_x + self.displacement_heel, self.start_y) - # cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, -ref_side.frame.yaxis) - # cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, -ref_side.frame.yaxis) - - # # Calculate heel cutting planes angles - # # Rotate first cutting plane at the displaced start of the notch (long side of the heel) - # angle_long_side = math.radians(90 - self.strut_inclination) - # rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_heel) - # cutting_plane_heel.transform(rot_long_side) - - # # Rotate second cutting plane at the end of the notch (short side of the heel) - # angle_short_side = math.radians(180 - self.strut_inclination) - # rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) - # cutting_plane_end.transform(rot_short_side) return [Plane.from_frame(cutting_plane_heel), Plane.from_frame(cutting_plane_end)] - def _calculate_tapered_heel_planes(self, ref_side, rot_axis): + def _calculate_tapered_heel_planes(self, ref_side): """Calculate cutting planes for a tapered heel notch.""" # Move the frames to the start and end of the notch to create the cuts p_origin = ref_side.point_at(self.start_x, self.start_y) p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, -ref_side.frame.yaxis) cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) - # Calculate tapered heel cutting planes angles - # if self.strut_inclination > 90: # Rotate first cutting plane at the start of the notch (short side of the heel) angle_short_side = math.radians(180 - self.strut_inclination) - rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_origin) + rot_short_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_short_side, point=p_origin) cutting_plane_origin.transform(rot_short_side) # Rotate second cutting plane at the end of the notch (long side of the heel) - angle_long_side = math.atan( self.heel_depth / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination)))) @@ -651,45 +682,32 @@ def _calculate_tapered_heel_planes(self, ref_side, rot_axis): if self.strut_inclination > 90: angle_long_side = math.radians(180) - angle_long_side - rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_end) + rot_long_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_long_side, point=p_end) cutting_plane_end.transform(rot_long_side) - # else: - # # Rotate first cutting plane at the start of the notch (long side of the heel) - # angle_long_side = math.atan( - # self.heel_depth - # / (abs(self.displacement_end) - abs(self.heel_depth / math.tan(math.radians(self.strut_inclination)))) - # ) - # rot_long_side = Rotation.from_axis_and_angle(rot_axis, angle_long_side, point=p_origin) - # cutting_plane_origin.transform(rot_long_side) - - # # Rotate second cutting plane at the end of the notch (short side of the heel) - # angle_short_side = math.radians(180 - self.strut_inclination) - # rot_short_side = Rotation.from_axis_and_angle(rot_axis, angle_short_side, point=p_end) - # cutting_plane_end.transform(rot_short_side) return [Plane.from_frame(cutting_plane_origin), Plane.from_frame(cutting_plane_end)] - def _calculate_double_planes(self, ref_side, rot_axis): + def _calculate_double_planes(self, ref_side): """Calculate cutting planes for a double notch.""" # if self.strut_inclination > 90: # Move the frames to the start and end of the notch to create the cuts p_origin = ref_side.point_at(self.start_x, self.start_y) p_heel = ref_side.point_at(self.start_x + self.displacement_heel, self.start_y) p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_heel_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, -ref_side.frame.yaxis) + cutting_plane_heel_heel = Frame(p_heel, ref_side.frame.xaxis, -ref_side.frame.yaxis) cutting_plane_heel_step = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) + cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, -ref_side.frame.yaxis) # Calculate heel cutting planes angles # Rotate first cutting plane at the start of the notch (short side of the heel) angle_short_side_heel = math.radians(180 - self.strut_inclination) - rot_short_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_short_side_heel, point=p_origin) + rot_short_side_heel = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_short_side_heel, point=p_origin) cutting_plane_origin.transform(rot_short_side_heel) # Rotate second cutting plane at the end of the notch (long side of the heel) angle_long_side_heel = math.radians(270 - self.strut_inclination) - rot_long_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_long_side_heel, point=p_heel) + rot_long_side_heel = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_long_side_heel, point=p_heel) cutting_plane_heel_heel.transform(rot_long_side_heel) # Calculate step cutting planes angles @@ -704,54 +722,23 @@ def _calculate_double_planes(self, ref_side, rot_axis): ) if self.strut_inclination < 90: - angle_long_side_step = math.atan(self.step_depth / (self.displacement_end - self.displacement_heel + (self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2))))) - rot_long_side_step = Rotation.from_axis_and_angle(rot_axis, angle_long_side_step, point=p_heel) + angle_long_side_step = math.atan( + self.step_depth + / ( + self.displacement_end + - self.displacement_heel + + (self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2))) + ) + ) + rot_long_side_step = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_long_side_step, point=p_heel) cutting_plane_heel_step.transform(rot_long_side_step) # Rotate second cutting plane at the end of the notch (short side of the step) angle_short_side_step = math.radians(180 - self.strut_inclination / 2) if self.strut_inclination < 90: angle_short_side_step = math.radians(90) + angle_short_side_step - rot_short_side_step = Rotation.from_axis_and_angle(rot_axis, angle_short_side_step, point=p_end) + rot_short_side_step = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_short_side_step, point=p_end) cutting_plane_end.transform(rot_short_side_step) - # else: - # # Move the frames to the start and end of the notch to create the cuts - # p_origin = ref_side.point_at(self.start_x, self.start_y) - # p_heel = ref_side.point_at(self.start_x + (self.displacement_end - self.displacement_heel), self.start_y) - # p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) - # cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) - # cutting_plane_heel_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - # cutting_plane_heel_step = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - # cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) - - # # Calculate step cutting planes angles - # # Rotate first cutting plane at the start of the notch (short side of the step) - # angle_short_side_step = math.radians(90 + self.strut_inclination / 2) - # rot_short_side_step = Rotation.from_axis_and_angle(rot_axis, angle_short_side_step, point=p_origin) - # cutting_plane_origin.transform(rot_short_side_step) - - # # Rotate second cutting plane at the end of the notch (large side of the step) - # angle_long_side_step = math.radians(180) - math.atan( - # self.step_depth - # / ( - # self.displacement_end - # - self.displacement_heel - # - self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2)) - # ) - # ) - # rot_long_side_step = Rotation.from_axis_and_angle(rot_axis, angle_long_side_step, point=p_heel) - # cutting_plane_heel_step.transform(rot_long_side_step) - - # # Calculate heel cutting planes angles - # # Rotate first cutting plane at the displaced start of the notch (long side of the heel) - # angle_long_side_heel = math.radians(90 - self.strut_inclination) - # rot_long_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_long_side_heel, point=p_heel) - # cutting_plane_heel_heel.transform(rot_long_side_heel) - - # # Rotate second cutting plane at the end of the notch (short side of the heel) - # angle_short_side_heel = math.radians(180 - self.strut_inclination) - # rot_short_side_heel = Rotation.from_axis_and_angle(rot_axis, angle_short_side_heel, point=p_end) - # cutting_plane_end.transform(rot_short_side_heel) return [ Plane.from_frame(cutting_plane_origin), @@ -770,11 +757,11 @@ def mortise_volume_from_params_and_beam(self, beam): Returns ------- - :class:`compas.geometry.Polyhedron` + :class:`compas.geometry.Brep` The mortise volume. """ - # type: (Beam) -> Mesh + # type: (Beam) -> Brep assert self.strut_inclination is not None assert self.step_shape is not None @@ -793,7 +780,7 @@ def mortise_volume_from_params_and_beam(self, beam): start_y = self.start_y + (self.notch_width - self.mortise_width) / 2 displacement_y = self.mortise_width - step_cutting_planes = self._calculate_step_planes(ref_side, rot_axis) + step_cutting_planes = self._calculate_step_planes(ref_side) step_cutting_plane = step_cutting_planes[1] # the second cutting plane is the one at the end of the step if self.orientation == OrientationType.END: @@ -809,10 +796,10 @@ def mortise_volume_from_params_and_beam(self, beam): # construct polyline for the top face of the mortise mortise_polyline = Polyline([p_1, p_2, p_3, p_4, p_1]) - # calcutate the plane for the extrusion of the polyline - extr_plane = Plane(p_1, ref_side.frame.xaxis) + + # calcutate extrusion vector extr_vector_length = self.mortise_height / math.sin(math.radians(self.strut_inclination)) - extr_vector = extr_plane.normal * extr_vector_length + extr_vector = ref_side.frame.xaxis * extr_vector_length if self.strut_inclination > 90: vector_angle = math.radians(180 - self.strut_inclination) else: @@ -821,15 +808,26 @@ def mortise_volume_from_params_and_beam(self, beam): rot_vect = Rotation.from_axis_and_angle(rot_axis, vector_angle) extr_vector.transform(rot_vect) - + # translate the polyline to create the bottom face of the mortise mortise_polyline_extrusion = mortise_polyline.translated(extr_vector) - # extrude the polyline to create the mortise volume as a Brep - # mortise_volume = Brep.from_extrusion(mortise_polyline, extr_vector, cap_ends=True) - # trim brep with step cutting planes - # mortise_volume.trim(step_cutting_plane) # !: check if the trimming works correctly // add checks - # return mortise_volume - return mortise_polyline, mortise_polyline_extrusion + # create Box from mortise points # TODO: should create Brep directly by extruding the polyline + mortise_points = mortise_polyline.points + mortise_polyline_extrusion.points + mortise_box = Box.from_points(mortise_points) + + # convert to Brep and trim with cutting_planes + mortise_brep = Brep.from_box(mortise_box) + trimming_plane_start = Plane.from_point_and_two_vectors(p_1, extr_vector, ref_side.frame.yaxis) + trimming_plane_end = Plane.from_point_and_two_vectors(p_2, -extr_vector, ref_side.frame.yaxis) + try: + mortise_brep.trim(trimming_plane_start) + mortise_brep.trim(trimming_plane_end) + except Exception as e: + raise FeatureApplicationError( + mortise_brep, None, "Failed to trim mortise volume with cutting planes: {}".format(str(e)) + ) + + return mortise_brep class StepJointNotchParams(BTLxProcessParams): diff --git a/tests/compas_timber/gh/test_step_joint.ghx b/tests/compas_timber/gh/test_step_joint.ghx index 4e2bcd73b..7c11ad7dc 100644 --- a/tests/compas_timber/gh/test_step_joint.ghx +++ b/tests/compas_timber/gh/test_step_joint.ghx @@ -48,10 +48,10 @@ - 78 - 222 + 207 + 153 - 0.151132 + 0.2460933 @@ -95,9 +95,9 @@ - 68 + 70 - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 @@ -264,7 +264,7 @@ from compas_timber.fabrication import BTLx from compas_rhino import unload_modules from compas.scene import SceneObject -from compas_rhino.conversions import frame_to_rhino, plane_to_rhino, surface_to_rhino, polyline_to_rhino +from compas_rhino.conversions import frame_to_rhino, plane_to_rhino, surface_to_rhino, polyline_to_rhino, brep_to_rhino import Rhino.Geometry as rg unload_modules("compas_timber") @@ -277,19 +277,17 @@ plane = main_beam.ref_sides[index] step_joint_notch = StepJointNotch.from_surface_and_beam(plane, beam, notch_limited=False, step_depth=step, heel_depth=heel, strut_height=100.0, tapered_heel=tapered, ref_side_index=ref_side_index) cutting_planes = step_joint_notch.planes_from_params_and_beam(beam) -#print("Orientation: ", step_joint_notch.orientation) -#print("StartX: ", step_joint_notch.start_x) -#print("StrutInclination: ", step_joint_notch.strut_inclination) - +print("Orientation: ", step_joint_notch.orientation) +print("StrutInclination: ", step_joint_notch.strut_inclination) #add mortise if mortise_height > 0: step_joint_notch.add_mortise(beam.width/4, mortise_height, beam) - mortise_polylines = step_joint_notch.mortise_volume_from_params_and_beam(beam) - rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_polylines] + mortise_brep = step_joint_notch.mortise_volume_from_params_and_beam(beam) -##apply geometric features -#step_joint_notch.apply(brep, beam) +#apply geometric features +geometry = step_joint_notch.apply(beam) +rg_geometry = brep_to_rhino(geometry) #get btlx params step_joint_notch_params = StepJointNotchParams(step_joint_notch).as_dict() @@ -309,8 +307,8 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) GhPython provides a Python script component - 154 - 194 + 247 + 383 1232 @@ -331,13 +329,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2259 - 368 - 222 + 380 + 203 164 2355 - 450 + 462 @@ -381,13 +379,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 370 + 382 79 20 2302 - 380 + 392 @@ -412,13 +410,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 390 + 402 79 20 2302 - 400 + 412 @@ -443,13 +441,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 410 + 422 79 20 2302 - 420 + 432 @@ -474,13 +472,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 430 + 442 79 20 2302 - 440 + 452 @@ -505,13 +503,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 450 + 462 79 20 2302 - 460 + 472 @@ -536,13 +534,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 470 + 482 79 20 2302 - 480 + 492 @@ -567,13 +565,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 490 + 502 79 20 2302 - 500 + 512 @@ -598,13 +596,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 510 + 522 79 20 2302 - 520 + 532 @@ -624,13 +622,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2370 - 370 - 109 + 382 + 90 22 - 2424.5 - 381.4286 + 2415 + 393.4286 @@ -650,13 +648,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2370 - 392 - 109 + 404 + 90 23 - 2424.5 - 404.2857 + 2415 + 416.2857 @@ -676,13 +674,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2370 - 415 - 109 + 427 + 90 23 - 2424.5 - 427.1429 + 2415 + 439.1429 @@ -702,13 +700,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2370 - 438 - 109 + 450 + 90 23 - 2424.5 - 450 + 2415 + 462 @@ -728,13 +726,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2370 - 461 - 109 + 473 + 90 23 - 2424.5 - 472.8571 + 2415 + 484.8571 @@ -742,10 +740,10 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - Script output rg_mortise_polylines. - 1a4781d4-bfe8-4573-86bb-90d0f0ffc197 - rg_mortise_polylines - rg_mortise_polylines + Script output rg_geometry. + b10f1b85-d84a-4d8b-84d3-f956ea4e2805 + rg_geometry + rg_geometry false 0 @@ -754,13 +752,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2370 - 484 - 109 + 496 + 90 23 - 2424.5 - 495.7143 + 2415 + 507.7143 @@ -780,13 +778,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2370 - 507 - 109 - 23 + 519 + 90 + 22 - 2424.5 - 518.5714 + 2415 + 530.5714 @@ -852,207 +850,6 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - - f31d8d7a-7536-4ac8-9c96-fde6ecda4d0a - Cluster - - - - - - 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 - - Contains a cluster of Grasshopper components - true - a5869824-14fc-400f-a69e-f5050392632f - Cluster - Cluster - false - - - - - 5 - 3f3af581-d64e-4909-91e4-3576cf2e9e86 - 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 - d28e1b27-74ad-41ab-b017-540fa9111876 - ebd18bdb-78c3-4d40-8a69-e1af30a53c27 - fd03a9d4-272c-4590-9595-4d3107dc0fdc - 3c631e1e-b12f-4297-9535-87b4fdc7b45e - 5d665740-a9cc-4f15-8a38-0dc75e214ae2 - 5d32325d-62cf-40bd-93fe-74af56a2c91e - b360d350-2b53-401b-9420-d9402019cb30 - 796ac502-faba-4bb6-a612-7e3dfb448d98 - - - - - - 2653 - 429 - 72 - 84 - - - 2692 - 471 - - - - - - 4 - 919e146f-30ae-4aae-be34-4d72f555e7da - 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 - 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 - d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 - 1 - 919e146f-30ae-4aae-be34-4d72f555e7da - - - - - Brep to split - ebd18bdb-78c3-4d40-8a69-e1af30a53c27 - Brep - B - true - b0a6568e-06d8-4c68-b393-d8274f661640 - 1 - - - - - - 2655 - 431 - 22 - 20 - - - 2667.5 - 441 - - - - - - - - Contains a collection of three-dimensional axis-systems - d28e1b27-74ad-41ab-b017-540fa9111876 - Plane - Pln - true - 16bc55f9-a035-4099-834e-e9dc0931b695 - 1 - - - - - - 2655 - 451 - 22 - 20 - - - 2667.5 - 461 - - - - - - - - Contains a collection of three-dimensional axis-systems - fd03a9d4-272c-4590-9595-4d3107dc0fdc - Plane - Pln - true - ddc1d959-4491-4f72-b7d5-a74d74b99385 - 1 - - - - - - 2655 - 471 - 22 - 20 - - - 2667.5 - 481 - - - - - - - - 1 - Section curves - 3f3af581-d64e-4909-91e4-3576cf2e9e86 - Curves - C - true - 1a4781d4-bfe8-4573-86bb-90d0f0ffc197 - 1 - - - - - - 2655 - 491 - 22 - 20 - - - 2667.5 - 501 - - - - - - - - 1 - Joined Breps - 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 - Breps - B - false - 0 - - - - - - 2707 - 431 - 16 - 80 - - - 2715 - 471 - - - - - - - - - - - - 537b0419-bbc2-4ff4-bf08-afe526367b2c Custom Preview @@ -1062,7 +859,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) Allows for customized geometry previews true - false + true 9e4f0b21-759b-4c9a-8dfb-6654484027c5 Custom Preview Preview @@ -1073,13 +870,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2841 - 514 + 526 48 44 2875 - 536 + 548 @@ -1091,7 +888,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) Geometry G false - 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 + 91868622-66e7-4d7d-a5a0-9480a9e4a02a 1 @@ -1099,13 +896,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2843 - 516 + 528 17 20 2853 - 526 + 538 @@ -1126,13 +923,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2843 - 536 + 548 17 20 2853 - 546 + 558 @@ -1172,7 +969,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 0148a65d-6f42-414a-9db7-9a9b2eb78437 Brep Edges @@ -1191,13 +988,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2741 - 439 + 419 72 64 2771 - 471 + 451 @@ -1208,7 +1005,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) Brep B false - 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 + 91868622-66e7-4d7d-a5a0-9480a9e4a02a 1 @@ -1216,13 +1013,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2743 - 441 + 421 13 60 2751 - 471 + 451 @@ -1243,13 +1040,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2786 - 441 + 421 25 20 2798.5 - 451 + 431 @@ -1270,13 +1067,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2786 - 461 + 441 25 20 2798.5 - 471 + 451 @@ -1297,13 +1094,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2786 - 481 + 461 25 20 2798.5 - 491 + 471 @@ -1313,7 +1110,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 Plane @@ -1335,13 +1132,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2839 - 377 + 371 50 24 2864.272 - 389.9877 + 383.8677 @@ -1349,7 +1146,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 Plane @@ -1371,13 +1168,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2838 - 307 + 300 50 24 2863.631 - 319.0432 + 312.9232 @@ -1385,7 +1182,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 Plane @@ -1407,13 +1204,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2837 - 242 + 236 50 24 2862.671 - 254.6153 + 248.4953 @@ -1421,7 +1218,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 59e0b89a-e487-49f8-bab8-b5bab16be14c Panel @@ -1444,16 +1241,16 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 285 - 221 + 293 + 201 85 0 0 0 - 2261.57 - 285.1643 + 2261.519 + 293.1643 @@ -1474,7 +1271,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -1499,7 +1296,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -1524,7 +1321,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + a77d0879-94c2-4101-be44-e4a616ffeb0c 5f86fa9f-c62b-50e8-157b-b454ef3e00fa @@ -1544,13 +1341,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2842 - 420 + 440 46 84 2874 - 462 + 482 @@ -1570,13 +1367,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2844 - 422 + 442 15 20 2853 - 432 + 452 @@ -1597,13 +1394,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2844 - 442 + 462 15 20 2853 - 452 + 472 @@ -1653,13 +1450,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2844 - 462 + 482 15 20 2853 - 472 + 492 @@ -1679,13 +1476,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2844 - 482 + 502 15 20 2853 - 492 + 512 @@ -1715,7 +1512,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -1740,7 +1537,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -1765,7 +1562,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 410755b1-224a-4c1e-a407-bf32fb45ea7e 00000000-0000-0000-0000-000000000000 @@ -1774,10 +1571,12 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - from compas_rhino.conversions import surface_to_rhino + import compas +from compas_rhino.conversions import surface_to_rhino from compas_timber._fabrication import StepJointNotch from compas_timber._fabrication import StepJointNotchParams from compas_timber._fabrication import StepJoint +from compas_timber._fabrication import StepJointParams from compas_timber.model import TimberModel from compas_timber.fabrication import BTLx @@ -1785,7 +1584,7 @@ from compas_timber.fabrication import BTLx from compas_rhino import unload_modules from compas.scene import SceneObject -from compas_rhino.conversions import frame_to_rhino, plane_to_rhino, surface_to_rhino, polyline_to_rhino +from compas_rhino.conversions import frame_to_rhino, plane_to_rhino, surface_to_rhino, polyline_to_rhino, brep_to_rhino, mesh_to_rhino import Rhino.Geometry as rg unload_modules("compas_timber") @@ -1797,44 +1596,35 @@ plane = cross_beam.ref_sides[index] #create step_joint step_joint = StepJoint.from_plane_and_beam(plane, beam, step, heel, tapered, ref_side_index) cutting_planes = step_joint.planes_from_params_and_beam(beam) -# -###create step_joint_notch -##step_joint_notch = StepJointNotch.from_surface_and_beam(plane, beam, notch_limited=False, step_depth=step, heel_depth=heel, strut_height=100.0, tapered_heel=tapered, ref_side_index=ref_side_index) -##cutting_planes = step_joint_notch.planes_from_params_and_beam(beam) -## -##print("Orientation: ", step_joint_notch.orientation) -##print("StartX: ", step_joint_notch.start_x) -##print("StrutInclination: ", step_joint_notch.strut_inclination) -## -## -###add mortise -##if mortise_height > 0: -## step_joint_notch.add_mortise(beam.width/4, mortise_height, beam) -## mortise_polylines = step_joint_notch.mortise_volume_from_params_and_beam(beam) -## -####apply geometric features -###step_joint_notch.apply(brep, beam) -## -###get btlx params -##step_joint_notch_params = StepJointNotchParams(step_joint_notch).as_dict() -##btlx_params = [] -##for key, value in step_joint_notch_params.items(): -## btlx_params.append("{0}: {1}".format(key, value)) -## -## -##vizualize in rhino +print("Orientation: ", step_joint.orientation) +print("StrutInclination: ", step_joint.strut_inclination) + +#add tenon +if tenon_height > 0: + step_joint.add_tenon(cross_beam.width/4, tenon_height) + tenon_volume = step_joint.tenon_volume_from_params_and_beam(beam) + +#apply geometric features +geometry = step_joint.apply(beam) +rg_geometry = brep_to_rhino(geometry) +rg_geometry.MergeCoplanarFaces(0.01) + +#get btlx params +step_joint_params = StepJointParams(step_joint).as_dict() +btlx_params = [] +for key, value in step_joint_params.items(): + btlx_params.append("{0}: {1}".format(key, value)) + +#vizualize in rhino rg_ref_side = frame_to_rhino(beam.ref_sides[ref_side_index]) rg_cutting_plane = frame_to_rhino(plane) rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) -## -##rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_polylines] - GhPython provides a Python script component - 248 - 94 + 278 + 159 1232 @@ -1854,19 +1644,19 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2256 - 1265 - 222 + 2258 + 1269 + 199 164 - 2352 - 1347 + 2350 + 1351 - + 8 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 @@ -1876,16 +1666,15 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 7 + 6 3ede854e-c753-40eb-84cb-b48008f14fd4 8ec86459-bf01-4409-baee-174d0d2b13d0 8ec86459-bf01-4409-baee-174d0d2b13d0 8ec86459-bf01-4409-baee-174d0d2b13d0 8ec86459-bf01-4409-baee-174d0d2b13d0 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - + true @@ -1904,14 +1693,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2258 - 1267 - 79 + 2260 + 1271 + 75 20 2299 - 1277 + 1281 @@ -1935,14 +1724,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2258 - 1287 - 79 + 2260 + 1291 + 75 20 2299 - 1297 + 1301 @@ -1966,14 +1755,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2258 - 1307 - 79 + 2260 + 1311 + 75 20 2299 - 1317 + 1321 @@ -1997,14 +1786,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2258 - 1327 - 79 + 2260 + 1331 + 75 20 2299 - 1337 + 1341 @@ -2028,14 +1817,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2258 - 1347 - 79 + 2260 + 1351 + 75 20 2299 - 1357 + 1361 @@ -2059,14 +1848,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2258 - 1367 - 79 + 2260 + 1371 + 75 20 2299 - 1377 + 1381 @@ -2090,14 +1879,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2258 - 1387 - 79 + 2260 + 1391 + 75 20 2299 - 1397 + 1401 @@ -2106,10 +1895,10 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) true - Script input mortise_height. + Script input tenon_height. 74b917c7-fc1a-4dec-abae-860ed10a0b11 - mortise_height - mortise_height + tenon_height + tenon_height true 0 true @@ -2121,14 +1910,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2258 - 1407 - 79 + 2260 + 1411 + 75 20 2299 - 1417 + 1421 @@ -2147,46 +1936,20 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2367 - 1267 - 109 - 22 + 2365 + 1271 + 90 + 26 - 2421.5 - 1278.429 + 2410 + 1284.333 - - Script output surface. - 8579e7d8-c52b-4634-b03d-5bc56067fd74 - surface - surface - false - 0 - - - - - - 2367 - 1289 - 109 - 23 - - - 2421.5 - 1301.286 - - - - - - Script output rg_cutting_plane. 9fe4a86c-3c6e-4f70-8cbe-630b57b5a0b1 @@ -2199,20 +1962,20 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2367 - 1312 - 109 - 23 + 2365 + 1297 + 90 + 27 - 2421.5 - 1324.143 + 2410 + 1311 - + Script output rg_planes. 6270f634-eb6d-402e-a88f-24f478dcf237 @@ -2225,20 +1988,20 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2367 - 1335 - 109 - 23 + 2365 + 1324 + 90 + 27 - 2421.5 - 1347 + 2410 + 1337.667 - + Script output rg_ref_side. 9c129a89-24d5-45f5-bcea-890db46f3da4 @@ -2251,25 +2014,25 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2367 - 1358 - 109 - 23 + 2365 + 1351 + 90 + 26 - 2421.5 - 1369.857 + 2410 + 1364.333 - + - Script output rg_mortise_polylines. - 9ba6c2c7-77fe-4ea5-96f9-edfa7f161fb3 - rg_mortise_polylines - rg_mortise_polylines + Script output rg_geometry. + 441ff4ac-6060-44cf-965a-55428a9d4cc6 + rg_geometry + rg_geometry false 0 @@ -2277,20 +2040,20 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2367 - 1381 - 109 - 23 + 2365 + 1377 + 90 + 27 - 2421.5 - 1392.714 + 2410 + 1391 - + Script output btlx_params. 269c79e7-4269-4669-b62a-9869376ea11c @@ -2303,14 +2066,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2367 + 2365 1404 - 109 - 23 + 90 + 27 - 2421.5 - 1415.571 + 2410 + 1417.667 @@ -2322,7 +2085,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -2343,7 +2106,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 1675 873 - 160 + 201 20 @@ -2367,7 +2130,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -2412,7 +2175,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 59e0b89a-e487-49f8-bab8-b5bab16be14c Panel @@ -2465,7 +2228,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -2510,7 +2273,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -2555,7 +2318,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a Boolean Toggle @@ -2566,238 +2329,39 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) Boolean (true/false) toggle dec5089d-c452-417b-a948-4034d6df42ce Boolean Toggle - tapered_heel - false - 0 - true - - - - - - 1663 - 1006 - 133 - 22 - - - - - - - - - - f31d8d7a-7536-4ac8-9c96-fde6ecda4d0a - Cluster - - - - - - 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 - - Contains a cluster of Grasshopper components - true - 7154ee57-be53-4a07-8da4-9858a1faea73 - Cluster - Cluster - false - - - - - 5 - 065c8b93-533c-4d8c-b22d-d9bc535f688e - 6f553506-3c94-4dbc-b2ec-f549b4c30b0c - 8afe827d-4dab-4dde-aea7-476d34f57a1b - e7de4017-7053-4063-8078-ab0ab768ffd7 - fc6af981-1cbe-4ef5-aa1c-23484b153ef3 - 3c631e1e-b12f-4297-9535-87b4fdc7b45e - b360d350-2b53-401b-9420-d9402019cb30 - 5d32325d-62cf-40bd-93fe-74af56a2c91e - 5d665740-a9cc-4f15-8a38-0dc75e214ae2 - fc820447-d987-4fb0-931e-987ca527842b - - - - - - 2643 - 1326 - 79 - 84 - - - 2689 - 1368 - - - - - - 4 - 919e146f-30ae-4aae-be34-4d72f555e7da - 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 - d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 - cb95db89-6165-43b6-9c41-5702bc5bf137 - 1 - 919e146f-30ae-4aae-be34-4d72f555e7da - - - - - Brep to split - 6f553506-3c94-4dbc-b2ec-f549b4c30b0c - Brep - B - true - 55c66290-a177-4518-824a-6a531a1e8086 - 1 - - - - - - 2645 - 1328 - 29 - 20 - - - 2661 - 1338 - - - - - - - - Contains a collection of three-dimensional axis-systems - 8afe827d-4dab-4dde-aea7-476d34f57a1b - Plane - Pln - true - 6270f634-eb6d-402e-a88f-24f478dcf237 - 1 - - - - - - 2645 - 1348 - 29 - 20 - - - 2661 - 1358 - - - - - - - - 1 - Section curves - 065c8b93-533c-4d8c-b22d-d9bc535f688e - Curves - C - true - 9ba6c2c7-77fe-4ea5-96f9-edfa7f161fb3 - 1 - - - - - - 2645 - 1368 - 29 - 20 - - - 2661 - 1378 - - - - - - - - Contains a collection of boolean values - fc6af981-1cbe-4ef5-aa1c-23484b153ef3 - Boolean - Bool - true - 8af59bcf-e43d-4808-b965-440e7d003dfd - 1 - - - - - - 2645 - 1388 - 29 - 20 - - - 2661 - 1398 - - - - - - - - 1 - Joined Breps - e7de4017-7053-4063-8078-ab0ab768ffd7 - Breps - B - false - 0 - - - - - - 2704 - 1328 - 16 - 80 - - - 2712 - 1368 - - - - - - + tapered_heel + false + 0 + false + + + + + + 1663 + 1006 + 133 + 22 + + - + 537b0419-bbc2-4ff4-bf08-afe526367b2c Custom Preview - + Allows for customized geometry previews - false + true + true def048ab-0443-4189-81d6-03594953cdd1 + true Custom Preview Preview @@ -2807,25 +2371,26 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2840 - 1407 + 1423 48 44 2874 - 1429 + 1445 - + Geometry to preview true 977e596e-9c8a-4d5e-a490-c2c39fe7f008 + true Geometry G false - e7de4017-7053-4063-8078-ab0ab768ffd7 + b1d2aad0-ff8f-419b-9c56-f82b9b6378df 1 @@ -2833,39 +2398,41 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2842 - 1409 + 1425 17 20 2852 - 1419 + 1435 - + The material override 5f9e9e88-be8b-4453-b5f6-8e8fcf54697c + true Material M false - 0 + d0dd1043-724e-45c1-a8b5-6f7ff06d8073 + 1 2842 - 1429 + 1445 17 20 2852 - 1439 + 1455 @@ -2905,7 +2472,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 0148a65d-6f42-414a-9db7-9a9b2eb78437 Brep Edges @@ -2923,14 +2490,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2738 - 1336 + 2739 + 1313 72 64 - 2768 - 1368 + 2769 + 1345 @@ -2941,21 +2508,21 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) Brep B false - e7de4017-7053-4063-8078-ab0ab768ffd7 + b1d2aad0-ff8f-419b-9c56-f82b9b6378df 1 - 2740 - 1338 + 2741 + 1315 13 60 - 2748 - 1368 + 2749 + 1345 @@ -2975,14 +2542,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2783 - 1338 + 2784 + 1315 25 20 - 2795.5 - 1348 + 2796.5 + 1325 @@ -3002,14 +2569,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2783 - 1358 + 2784 + 1335 25 20 - 2795.5 - 1368 + 2796.5 + 1345 @@ -3029,14 +2596,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 2783 - 1378 + 2784 + 1355 25 20 - 2795.5 - 1388 + 2796.5 + 1365 @@ -3046,7 +2613,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 Plane @@ -3068,13 +2635,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2837 - 1275 + 1254 50 24 2862.183 - 1287.725 + 1266.925 @@ -3082,7 +2649,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 Plane @@ -3104,13 +2671,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2836 - 1204 + 1185 50 24 2861.542 - 1216.78 + 1197.58 @@ -3118,7 +2685,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 Plane @@ -3140,13 +2707,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2835 - 1136 + 1115 50 24 2860.975 - 1148.724 + 1127.124 @@ -3154,7 +2721,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 59e0b89a-e487-49f8-bab8-b5bab16be14c Panel @@ -3177,16 +2744,16 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2258 - 1180 - 220 - 85 + 1185 + 199 + 82 0 0 0 - 2258.007 - 1180.445 + 2258.006 + 1185.64 @@ -3207,7 +2774,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -3232,7 +2799,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -3257,7 +2824,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + a77d0879-94c2-4101-be44-e4a616ffeb0c 5f86fa9f-c62b-50e8-157b-b454ef3e00fa @@ -3277,13 +2844,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2839 - 1317 + 1333 46 84 2871 - 1359 + 1375 @@ -3303,13 +2870,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2841 - 1319 + 1335 15 20 2850 - 1329 + 1345 @@ -3329,13 +2896,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2841 - 1339 + 1355 15 20 2850 - 1349 + 1365 @@ -3385,13 +2952,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2841 - 1359 + 1375 15 20 2850 - 1369 + 1385 @@ -3411,13 +2978,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2841 - 1379 + 1395 15 20 2850 - 1389 + 1405 @@ -3447,7 +3014,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -3472,7 +3039,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -3510,14 +3077,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 100 0 0 - 30 + 50 - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -3543,7 +3110,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -3570,7 +3137,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -3595,7 +3162,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -3620,7 +3187,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 Curve @@ -3679,7 +3246,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 410755b1-224a-4c1e-a407-bf32fb45ea7e 00000000-0000-0000-0000-000000000000 @@ -3792,7 +3359,7 @@ class Beam_fromCurve(component): true true - iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDQAACw0B7QfALAAAAhRJREFUSEulVLtKA0EUzSfkD3zlrUKMIcHGB6ghIj5QxEoDiiA2gtqGYBEfRRQiSRc7iXU6IWKZwh8Q/AI/wEcRr/cMmWUZ7ya7Whw2uXvvOTNnzqyPiP6Nob7+Q0bBQJ0xLQ64BRP4R6OxZ37Swf4+nRwdKyxmFyg4MEhcvxYH3YCH4+GhwHsqMd6+r9ep1WrRY7OpyGcmp2hvZxcCBXG4F3gQligykIIcIulkknJb26qGnXgW4AF/NBi6AzkIQAxcnl+AjAr5vFXzLMDN8ZFI9JXxfVMuW0RYMZ8D3dZqVs2zADeuRALBD3jbaDQUAZ74D2ib/iQwHI5UudHyFsNYLVaN5JjEGj0F+KUVQXisB+EzyO01CV0F+IUYwY21dZUUXXMCemGdKMBFMYIYgIDktx2wD0FAILBQO7FjBGGJveYE2Id5Jn8AH3g1uRhBHCLI7TUJ2BVCAHLuP9eLVgJc3GS/vyZSaboqldQWq5UKZeczyhYdSyfouCLGzLViJ9cCT1A2sbq03NNv7Ay7joXCLzzTb5JbAqa/mdm5XzfTRCeG1Dk35bcEUQAJchLArpAmkDMOJVI7PAkgrrgbuCM8F5cITbgWQFyHI9F2x29HS0y4EkBcuU/5LZF0Q1cB+I3fHMFP7stJBL3gKHBWLOoIvnGPK78lKAEQQkQjOZZQlnS+pq79lgABfNxw2UycSgPeQL4fsKzVEEQlKEcAAAAASUVORK5CYII= + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDAAACwwBP0AiyAAAAhRJREFUSEulVLtKA0EUzSfkD3zlrUKMIcHGB6ghIj5QxEoDiiA2gtqGYBEfRRQiSRc7iXU6IWKZwh8Q/AI/wEcRr/cMmWUZ7ya7Whw2uXvvOTNnzqyPiP6Nob7+Q0bBQJ0xLQ64BRP4R6OxZ37Swf4+nRwdKyxmFyg4MEhcvxYH3YCH4+GhwHsqMd6+r9ep1WrRY7OpyGcmp2hvZxcCBXG4F3gQligykIIcIulkknJb26qGnXgW4AF/NBi6AzkIQAxcnl+AjAr5vFXzLMDN8ZFI9JXxfVMuW0RYMZ8D3dZqVs2zADeuRALBD3jbaDQUAZ74D2ib/iQwHI5UudHyFsNYLVaN5JjEGj0F+KUVQXisB+EzyO01CV0F+IUYwY21dZUUXXMCemGdKMBFMYIYgIDktx2wD0FAILBQO7FjBGGJveYE2Id5Jn8AH3g1uRhBHCLI7TUJ2BVCAHLuP9eLVgJc3GS/vyZSaboqldQWq5UKZeczyhYdSyfouCLGzLViJ9cCT1A2sbq03NNv7Ay7joXCLzzTb5JbAqa/mdm5XzfTRCeG1Dk35bcEUQAJchLArpAmkDMOJVI7PAkgrrgbuCM8F5cITbgWQFyHI9F2x29HS0y4EkBcuU/5LZF0Q1cB+I3fHMFP7stJBL3gKHBWLOoIvnGPK78lKAEQQkQjOZZQlnS+pq79lgABfNxw2UycSgPeQL4fsKzVEEQlKEcAAAAASUVORK5CYII= false e28e26d5-b791-48bc-902c-a1929ace9685 @@ -4076,7 +3643,7 @@ class Beam_fromCurve(component): - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -4121,7 +3688,7 @@ class Beam_fromCurve(component): - + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 Curve @@ -4180,7 +3747,7 @@ class Beam_fromCurve(component): - + 410755b1-224a-4c1e-a407-bf32fb45ea7e 00000000-0000-0000-0000-000000000000 @@ -4577,7 +4144,7 @@ class Beam_fromCurve(component): - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -4622,7 +4189,7 @@ class Beam_fromCurve(component): - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -4667,7 +4234,7 @@ class Beam_fromCurve(component): - + 22990b1f-9be6-477c-ad89-f775cd347105 Flip Curve @@ -4805,7 +4372,7 @@ class Beam_fromCurve(component): - + 11bbd48b-bb0a-4f1b-8167-fa297590390d End Points @@ -4917,7 +4484,7 @@ class Beam_fromCurve(component): - + e9eb1dcf-92f6-4d4d-84ae-96222d60f56b Move @@ -5081,7 +4648,7 @@ class Beam_fromCurve(component): - + 79f9fbb3-8f1d-4d9a-88a9-f7961b1012cd Unit X @@ -5187,7 +4754,7 @@ class Beam_fromCurve(component): - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -5232,7 +4799,7 @@ class Beam_fromCurve(component): - + 4c4e56eb-2f04-43f9-95a3-cc46a14f495a Line @@ -5345,7 +4912,7 @@ class Beam_fromCurve(component): - + 9103c240-a6a9-4223-9b42-dbd19bf38e2b Unit Z @@ -5451,7 +5018,7 @@ class Beam_fromCurve(component): - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -5496,7 +5063,7 @@ class Beam_fromCurve(component): - + a0d62394-a118-422d-abb3-6af115c75b25 Addition @@ -5620,7 +5187,7 @@ class Beam_fromCurve(component): - + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a Boolean Toggle @@ -5634,7 +5201,7 @@ class Beam_fromCurve(component): FlipCurve false 0 - false + true @@ -5651,7 +5218,7 @@ class Beam_fromCurve(component): - + eeafc956-268e-461d-8e73-ee05c6f72c01 Stream Filter @@ -5802,7 +5369,7 @@ class Beam_fromCurve(component): 09fe95fb-9551-4690-abf6-4a0665002914 false Stream - S(0) + S(1) false 0 @@ -5829,7 +5396,7 @@ class Beam_fromCurve(component): - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -5855,7 +5422,7 @@ class Beam_fromCurve(component): - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -5883,7 +5450,7 @@ class Beam_fromCurve(component): - + 9c53bac0-ba66-40bd-8154-ce9829b9db1a Colour Swatch @@ -5906,44 +5473,13 @@ class Beam_fromCurve(component): 2737 - 544 + 557 88 20 2737.545 - 544.3754 - - - - - - - - - - 2e78987b-9dfb-42a2-8b76-3923ac8bd91a - Boolean Toggle - - - - - Boolean (true/false) toggle - 8af59bcf-e43d-4808-b965-440e7d003dfd - Boolean Toggle - Toggle - false - 0 - false - - - - - - 2526 - 1387 - 104 - 22 + 557.0914 @@ -5951,7 +5487,7 @@ class Beam_fromCurve(component): - + 7f5c6c55-f846-4a08-9c9a-cfdc285cc6fe Scribble @@ -6004,7 +5540,7 @@ class Beam_fromCurve(component): - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -6029,7 +5565,7 @@ class Beam_fromCurve(component): - + 7f5c6c55-f846-4a08-9c9a-cfdc285cc6fe Scribble @@ -6043,11 +5579,11 @@ class Beam_fromCurve(component): 1060.245 - 2691.896 + 2691.895 1060.245 - 2691.896 + 2691.895 1107.584 @@ -6082,7 +5618,7 @@ class Beam_fromCurve(component): - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -6107,7 +5643,7 @@ class Beam_fromCurve(component): - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -6132,6 +5668,172 @@ class Beam_fromCurve(component): + + + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep + + + + + Contains a collection of Breps (Boundary REPresentations) + true + b1d2aad0-ff8f-419b-9c56-f82b9b6378df + Brep + Brep + false + 441ff4ac-6060-44cf-965a-55428a9d4cc6 + 1 + + + + + + 2651 + 1378 + 50 + 24 + + + 2676.568 + 1390.515 + + + + + + + + + + 9c53bac0-ba66-40bd-8154-ce9829b9db1a + Colour Swatch + + + + + Colour (palette) swatch + d0dd1043-724e-45c1-a8b5-6f7ff06d8073 + Colour Swatch + Swatch + false + 0 + + 29;0;102;255 + + + + + + + 2728 + 1445 + 88 + 20 + + + 2728.811 + 1445.6 + + + + + + + + + + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep + + + + + Contains a collection of Breps (Boundary REPresentations) + 91868622-66e7-4d7d-a5a0-9480a9e4a02a + Brep + Brep + false + b10f1b85-d84a-4d8b-84d3-f956ea4e2805 + 1 + + + + + + 2660 + 496 + 50 + 24 + + + 2685.648 + 508.1772 + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + def048ab-0443-4189-81d6-03594953cdd1 + aa461d11-c653-411c-a341-b7900f1ccbd6 + 0cbceed4-3434-411c-b4d1-46985ef14dea + b1d2aad0-ff8f-419b-9c56-f82b9b6378df + d0dd1043-724e-45c1-a8b5-6f7ff06d8073 + 5 + 7dff34a0-670b-4fd3-8912-0a128d903453 + Group + GEOMETRY + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 9e4f0b21-759b-4c9a-8dfb-6654484027c5 + be8a15e2-8773-4720-9d61-dfdc10761646 + 56020ec2-ccb1-4ae9-a538-84dcb2857db9 + c0a53353-9802-4df7-a063-6228afad4537 + 91868622-66e7-4d7d-a5a0-9480a9e4a02a + 5 + 1994870e-f2d3-43af-9b16-274f462b9ef7 + Group + GEOMETRY + + + + + + + @@ -6139,7 +5841,7 @@ class Beam_fromCurve(component): - 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 From 3c4396c66bc77bb8bea58875ceb7423e95b1d732 Mon Sep 17 00:00:00 2001 From: papachap Date: Fri, 9 Aug 2024 20:07:13 +0200 Subject: [PATCH 30/63] minor fixes --- src/compas_timber/_fabrication/step_joint_notch.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index c9f944b8a..45c6cfb76 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -538,10 +538,9 @@ def apply(self, beam): raise FeatureApplicationError( sub_vol, geometry, "Failed to subtract volume from geometry: {}".format(str(e)) ) + return geometry else: - geometry - subtraction_volume - - return geometry + return geometry - subtraction_volume def add_mortise(self, mortise_width, mortise_height, beam): """Add a mortise to the existing StepJointNotch instance. @@ -583,7 +582,6 @@ def planes_from_params_and_beam(self, beam): # Start with a plane aligned with the ref side but shifted to the start of the first cut ref_side = beam.side_as_surface(self.ref_side_index) - rot_axis = ref_side.frame.yaxis if self.step_shape == StepShape.STEP: return self._calculate_step_planes(ref_side) @@ -780,13 +778,9 @@ def mortise_volume_from_params_and_beam(self, beam): start_y = self.start_y + (self.notch_width - self.mortise_width) / 2 displacement_y = self.mortise_width - step_cutting_planes = self._calculate_step_planes(ref_side) - step_cutting_plane = step_cutting_planes[1] # the second cutting plane is the one at the end of the step - if self.orientation == OrientationType.END: displacement_x = -displacement_x # negative displacement for the end cut rot_axis = -rot_axis # negative rotation axis for the end cut - step_cutting_plane = step_cutting_planes[0] # the first cutting plane is the one at the start of the step # find the points that create the top face of the mortise p_1 = ref_side.point_at(start_x, start_y) From 00b6ee6ff5c4186341d9184ec8a193d6719daa0b Mon Sep 17 00:00:00 2001 From: papachap Date: Sat, 10 Aug 2024 14:25:52 +0200 Subject: [PATCH 31/63] change alternative constructor from surface to plane --- .../_fabrication/step_joint_notch.py | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index 45c6cfb76..bbd28fe82 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -275,11 +275,13 @@ def displacement_heel(self): ######################################################################## @classmethod - def from_surface_and_beam( + def from_plane_and_beam( cls, plane, beam, + start_y=0.0, notch_limited=False, + notch_width=20.0, step_depth=20.0, heel_depth=0.0, strut_height=20.0, @@ -290,8 +292,8 @@ def from_surface_and_beam( Parameters ---------- - surface : :class:`~compas.geometry.PlanarSurface` or :class:`~compas.geometry.Surface` - The cutting surface. + plane: :class:`~compas.geometry.Planae` or :class:`~compas.geometry.Frame` + The cutting plane. beam : :class:`~compas_timber.elements.Beam` The beam that is cut by this instance. ref_side_index : int, optional @@ -302,17 +304,15 @@ def from_surface_and_beam( :class:`~compas_timber.fabrication.StepJointNotch` """ - # type: (PlanarSurface|Surface, Beam, bool, float, float, float, bool, int) -> StepJointNotch + # type: (Plane|Frame, Beam, float, bool, float, float, float, float, bool, int) -> StepJointNotch # TODO: the stepjointnotch is always orthogonal, this means that the surface should be perpendicular to the beam's ref_side | should there be a check for that? - # TODO: I am using a PlanarSurface instead of a Plane because otherwise there is no way to define the start_y of the Notch. - # TODO: The alternative solution in order to use a Plane instead would be to have start_y and notch_width as parameters of the class + # define ref_side & ref_edge ref_side = beam.ref_sides[ref_side_index] # TODO: is this arbitrary? ref_edge = Line.from_point_and_vector(ref_side.point, ref_side.xaxis) - # plane = surface.to_plane() + if isinstance(plane, Frame): plane = Plane.from_frame(plane) - # intersection_line = Line(*Plane.from_frame(ref_side).intersections_with_surface(surface)) # calculate orientation orientation = cls._calculate_orientation(ref_side, plane) @@ -323,17 +323,12 @@ def from_surface_and_beam( raise ValueError("Surface does not intersect with beam.") start_x = distance_point_point(ref_side.point, point_start_x) - # calculate start_y - # start_y = cls._calculate_start_y(orientation, intersection_line, point_start_x, ref_side) - start_y = 0.0 - # calculate strut_inclination strut_inclination = cls._calculate_strut_inclination(ref_side, plane, orientation) # calculate notch_width if notch_limited: - pass - # notch_width = intersection_line.length + notch_width = notch_width if notch_width < beam.width else beam.width else: notch_width = beam.width @@ -616,7 +611,7 @@ def _calculate_step_planes(self, ref_side): cutting_plane_origin.transform(rot_long_side) # Rotate second cutting plane at the end of the notch (short side of the step) - angle_short_side = math.radians(180 - self.strut_inclination / 2) + angle_short_side = math.radians(360 - self.strut_inclination / 2) rot_short_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_short_side, point=p_end) cutting_plane_end.transform(rot_short_side) else: From a141528671315468a09630cf2c74dee97065d35f Mon Sep 17 00:00:00 2001 From: papachap Date: Sat, 10 Aug 2024 14:31:12 +0200 Subject: [PATCH 32/63] implement step_joint joint --- src/compas_timber/connections/t_step_joint.py | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/compas_timber/connections/t_step_joint.py diff --git a/src/compas_timber/connections/t_step_joint.py b/src/compas_timber/connections/t_step_joint.py new file mode 100644 index 000000000..727d22d07 --- /dev/null +++ b/src/compas_timber/connections/t_step_joint.py @@ -0,0 +1,158 @@ +from compas_timber._fabrication import StepJoint +from compas_timber._fabrication import StepJointNotch + +from .joint import Joint +from .solver import JointTopology + + +class TStepJoint(Joint): + """Represents an T-Step type joint which joins two beams, one of them at it's end (main) and the other one along it's centerline (cross). + Two or more cuts are is made on the main beam and a notch is made on the cross beam to fit the main beam. + + This joint type is compatible with beams in T topology. + + Please use `TStepJoint.create()` to properly create an instance of this class and associate it with an model. + + Parameters + ---------- + main_beam : :class:`~compas_timber.parts.Beam` + First beam to be joined. + cross_beam : :class:`~compas_timber.parts.Beam` + Second beam to be joined. + step_depth : float + Depth of the step cut. Combined with a heel cut it generates a double step cut. + heel_depth : float + Depth of the heel cut. Combined with a step cut it generates a double step cut. + tapered_heel : bool + If True, the heel cut is tapered. + tenon_mortise_height : float + Height of the tenon (main beam) mortise (cross beam) of the Step Joint. If None, the tenon and mortise featrue is not created. + + Attributes + ---------- + main_beam : :class:`~compas_timber.parts.Beam` + First beam to be joined. + cross_beam : :class:`~compas_timber.parts.Beam` + Second beam to be joined. + step_depth : float + Depth of the step cut. Combined with a heel cut it generates a double step cut. + heel_depth : float + Depth of the heel cut. Combined with a step cut it generates a double step cut. + tapered_heel : bool + If True, the heel cut is tapered. + tenon_mortise_height : float + Height of the tenon (main beam) mortise (cross beam) of the Step Joint. If None, the tenon and mortise featrue is not created. + + """ + + SUPPORTED_TOPOLOGY = JointTopology.TOPO_T + + @property + def __data__(self): + data = super(TStepJoint, self).__data__ + data["main_beam"] = self.main_beam_guid + data["cross_beam"] = self.cross_beam_guid + data["step_depth"] = self.step_depth + data["heel_depth"] = self.heel_depth + data["tapered_heel"] = self.tapered_heel + data["tenon_mortise_height"] = self.tenon_mortise_height + return data + + def __init__( + self, + main_beam=None, + cross_beam=None, + step_depth=None, + heel_depth=None, + tapered_heel=None, + tenon_mortise_height=None, + ): + super(TStepJoint, self).__init__() + self.main_beam = main_beam + self.cross_beam = cross_beam + self.main_beam_guid = str(main_beam.guid) if main_beam else None + self.cross_beam_guid = str(cross_beam.guid) if cross_beam else None + + self.step_depth = step_depth + self.heel_depth = heel_depth + self.tapered_heel = tapered_heel + self.tenon_mortise_height = tenon_mortise_height + + self.start_y = ( + (self.cross_beam.width - self.main_beam.width) / 2 if self.cross_beam.width > self.main_beam.width else 0.0 + ) + self.notch_limited = False if self.main_beam.width >= self.cross_beam.width else True + self.notch_width = self.main_beam.width + self.strut_height = self.main_beam.height + self.tenon_mortise_width = self.main_beam.width / 4 + + self.features = [] + + @property + def beams(self): + return [self.main_beam, self.cross_beam] + + @property + def cross_beam_ref_face_index(self): + face_dict = self._beam_side_incidence(self.main_beam, self.cross_beam, ignore_ends=True) + face_index = min(face_dict, key=face_dict.get) + return face_index + + @property + def main_beam_ref_face_index(self): + face_dict = self._beam_side_incidence(self.cross_beam, self.main_beam, ignore_ends=True) + face_index = max(face_dict, key=face_dict.get) + return face_index + + def add_features(self): + """Adds the required trimming features to both beams. + + This method is automatically called when joint is created by the call to `Joint.create()`. + + """ + assert self.main_beam and self.cross_beam # should never happen + + if self.features: + self.main_beam.remove_features(self.features) + self.cross_beam.remove_features(self.features) + + main_beam_ref_face = self.main_beam.faces(self.main_beam_ref_face_index) + cross_beam_ref_face = self.cross_beam.faces(self.cross_beam_ref_face_index) + + # generate step joint notch features + cross_feature = StepJointNotch.from_plane_and_beam( + main_beam_ref_face, + self.cross_beam, + self.start_y, + self.notch_limited, + self.notch_width, + self.step_depth, + self.heel_depth, + self.tapered_heel, + self.strut_height, + self.cross_beam_ref_face_index, + ) + # generate step joint features + main_feature = StepJoint.from_plane_and_beam( + cross_beam_ref_face, + self.main_beam, + self.step_depth, + self.heel_depth, + self.tapered_heel, + self.main_beam_ref_face_index, + ) + # generate tenon and mortise features + if self.tenon_mortise_height: + cross_feature.add_mortise(self.tenon_mortise_height, self.tenon_mortise_width, self.cross_beam) + main_feature.add_tenon(self.tenon_mortise_height, self.tenon_mortise_width) + + # add features to beams + self.main_beam.add_features(main_feature) + self.cross_beam.add_features(cross_feature) + # add features to joint + self.features = [cross_feature, main_feature] + + def restore_beams_from_keys(self, model): + """After de-serialization, restores references to the main and cross beams saved in the model.""" + self.main_beam = model.elementdict[self.main_beam_guid] + self.cross_beam = model.elementdict[self.cross_beam_guid] From 506f8d55bb0fb9b886d4d21a1d79a779bd28216a Mon Sep 17 00:00:00 2001 From: papachap Date: Sun, 11 Aug 2024 15:52:49 +0200 Subject: [PATCH 33/63] wip stepjointjoint --- src/compas_timber/connections/__init__.py | 2 ++ src/compas_timber/connections/t_step_joint.py | 1 + 2 files changed, 3 insertions(+) diff --git a/src/compas_timber/connections/__init__.py b/src/compas_timber/connections/__init__.py index 39022428b..8fd4b43bb 100644 --- a/src/compas_timber/connections/__init__.py +++ b/src/compas_timber/connections/__init__.py @@ -11,6 +11,7 @@ from .solver import JointTopology from .solver import find_neighboring_beams from .t_butt import TButtJoint +from .t_step_joint import TStepJoint from .t_halflap import THalfLapJoint from .x_halflap import XHalfLapJoint @@ -22,6 +23,7 @@ "TButtJoint", "LButtJoint", "TButtJoint", + "TStepJoint", "LMiterJoint", "XHalfLapJoint", "THalfLapJoint", diff --git a/src/compas_timber/connections/t_step_joint.py b/src/compas_timber/connections/t_step_joint.py index 727d22d07..3511cbf3e 100644 --- a/src/compas_timber/connections/t_step_joint.py +++ b/src/compas_timber/connections/t_step_joint.py @@ -147,6 +147,7 @@ def add_features(self): main_feature.add_tenon(self.tenon_mortise_height, self.tenon_mortise_width) # add features to beams + print(cross_feature) self.main_beam.add_features(main_feature) self.cross_beam.add_features(cross_feature) # add features to joint From 8b3b27f5535259c9e4da9da4b5a09dbc80e47d2d Mon Sep 17 00:00:00 2001 From: papachap Date: Sun, 11 Aug 2024 18:43:03 +0200 Subject: [PATCH 34/63] bring back geometry as a parameter of the apply() method --- src/compas_timber/_fabrication/step_joint.py | 10 +++++----- src/compas_timber/_fabrication/step_joint_notch.py | 7 +++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index b9c96c29a..9b3df8ea2 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -318,11 +318,14 @@ def _calculate_x_displacement_heel(heel_depth, strut_inclination, orientation): # Methods ######################################################################## - def apply(self, beam): + def apply(self, geometry, beam): """Apply the feature to the beam geometry. Parameters ---------- + geometry : :class:`compas.geometry.Brep` + The geometry to be processed. + beam : :class:`compas_timber.elements.Beam` The beam that is milled by this instance. @@ -337,10 +340,7 @@ def apply(self, beam): The resulting geometry after processing """ - # type: (Beam) -> Brep - - # compute the geometry of the beam as a Brep - geometry = beam.compute_geometry() + # type: (Brep, Beam) -> Brep # get cutting planes from params and beam try: diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index bbd28fe82..b562e8cfd 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -422,13 +422,13 @@ def _calculate_displacement_heel(heel_depth, strut_inclination, orientation): # Methods ######################################################################## - def apply(self, beam): + def apply(self, geometry, beam): """Apply the feature to the beam geometry. Parameters ---------- geometry : :class:`~compas.geometry.Brep` - The beam geometry to be milled. + The beam geometry to be processed. beam : :class:`compas_timber.elements.Beam` The beam that is milled by this instance. @@ -445,8 +445,7 @@ def apply(self, beam): """ # type: (Brep, Beam) -> Brep - # compute the geometry of the beam as a Brep and get the reference side - geometry = beam.compute_geometry() + # Get the reference side ref_side = beam.side_as_surface(self.ref_side_index) # get cutting planes from params From 704be9f274a6af7d986d53e7c0e7ff9c3c4e9dcd Mon Sep 17 00:00:00 2001 From: papachap Date: Sun, 11 Aug 2024 18:44:49 +0200 Subject: [PATCH 35/63] fix splitting function for maintaining order in dict --- src/compas_timber/fabrication/btlx.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index fd2da7edf..dd470dab1 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -97,7 +97,7 @@ def process_model(self): factory_type.apply_processings(joint, self.parts) # TODO: to slowly integrate the new system, iterate here once more, this time on beams and their features - # add processings from features that are part of the new system AKA have the attribute `PROCESSING_NAME` + # add processings from features that are part of the new system AKA have the attribute `PROCESS_NAME` # joints that are already part of the new system should be skipped above (e.g. L-Miter) for beam in self.model.beams: features = list(filter(lambda feature: hasattr(feature, "PROCESS_NAME"), beam.features)) @@ -114,15 +114,23 @@ def _apply_process_features(self, beam_part, features): def _split_params_dict(self, feature): whole_dict = feature.params_dict - header_keys = { + header_keys = [ "Name", "Process", "Priority", "ProcessID", "ReferencePlaneID", - } - header_only = {k: v for k, v in whole_dict.items() if k in header_keys} - process_only = {k: v for k, v in whole_dict.items() if k not in header_keys} + ] + + header_only = OrderedDict() + process_only = OrderedDict() + + for k, v in whole_dict.items(): + if k in header_keys: + header_only[k] = v + else: + process_only[k] = v + return header_only, process_only @classmethod From 6f2ded26fdf3a644ff34b1a561b96906b9c14fbf Mon Sep 17 00:00:00 2001 From: papachap Date: Mon, 12 Aug 2024 00:20:38 +0200 Subject: [PATCH 36/63] functioning gh+btlx notch (planes need cleaning) --- .../_fabrication/step_joint_notch.py | 57 ++++++++++++------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index b562e8cfd..115ffc894 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -380,7 +380,7 @@ def _calculate_strut_inclination(ref_side, plane, orientation): # vector rotation direction of the plane's normal in the vertical direction strut_inclination_vector = Vector.cross(ref_side.zaxis, plane.normal) strut_inclination = angle_vectors_signed(ref_side.zaxis, plane.normal, strut_inclination_vector, deg=True) - if orientation == OrientationType.START: + if orientation == OrientationType.END: return abs(strut_inclination) else: return 180 - abs(strut_inclination) # get the other side of the angle @@ -610,13 +610,13 @@ def _calculate_step_planes(self, ref_side): cutting_plane_origin.transform(rot_long_side) # Rotate second cutting plane at the end of the notch (short side of the step) - angle_short_side = math.radians(360 - self.strut_inclination / 2) + angle_short_side = math.radians(self.strut_inclination / 2) rot_short_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_short_side, point=p_end) cutting_plane_end.transform(rot_short_side) else: # Rotate first cutting plane at the start of the notch (short side of the step) angle_short_side = math.radians((180 - self.strut_inclination) / 2) - rot_short_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_short_side, point=p_origin) + rot_short_side = Rotation.from_axis_and_angle(-ref_side.frame.yaxis, angle_short_side, point=p_origin) cutting_plane_origin.transform(rot_short_side) # Rotate second cutting plane at the end of the notch (large side of the step) angle_long_side = math.atan( @@ -641,12 +641,15 @@ def _calculate_heel_planes(self, ref_side): # Calculate heel cutting planes angles # Rotate first cutting plane at the start of the notch (short side of the heel) - angle_short_side = math.radians(180 - self.strut_inclination) + if self.strut_inclination < 90: + angle_short_side = math.radians(180 + self.strut_inclination) + else: + angle_short_side = math.radians(self.strut_inclination) rot_short_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_short_side, point=p_origin) cutting_plane_end.transform(rot_short_side) # Rotate second cutting plane at the end of the notch (long side of the heel) - angle_long_side = math.radians(270 - self.strut_inclination) + angle_long_side = math.radians(90 + self.strut_inclination) rot_long_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_long_side, point=p_heel) cutting_plane_heel.transform(rot_long_side) @@ -657,11 +660,14 @@ def _calculate_tapered_heel_planes(self, ref_side): # Move the frames to the start and end of the notch to create the cuts p_origin = ref_side.point_at(self.start_x, self.start_y) p_end = ref_side.point_at(self.start_x + self.displacement_end, self.start_y) - cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, -ref_side.frame.yaxis) + cutting_plane_origin = Frame(p_origin, ref_side.frame.xaxis, ref_side.frame.yaxis) cutting_plane_end = Frame(p_end, ref_side.frame.xaxis, ref_side.frame.yaxis) # Rotate first cutting plane at the start of the notch (short side of the heel) - angle_short_side = math.radians(180 - self.strut_inclination) + if self.strut_inclination > 90: + angle_short_side = math.radians(180 + self.strut_inclination) + else: + angle_short_side = math.radians(self.strut_inclination) rot_short_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_short_side, point=p_origin) cutting_plane_origin.transform(rot_short_side) @@ -672,7 +678,9 @@ def _calculate_tapered_heel_planes(self, ref_side): ) if self.strut_inclination > 90: - angle_long_side = math.radians(180) - angle_long_side + angle_long_side = angle_long_side + else: + angle_long_side = -angle_long_side rot_long_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_long_side, point=p_end) cutting_plane_end.transform(rot_long_side) @@ -693,41 +701,46 @@ def _calculate_double_planes(self, ref_side): # Calculate heel cutting planes angles # Rotate first cutting plane at the start of the notch (short side of the heel) - angle_short_side_heel = math.radians(180 - self.strut_inclination) + angle_short_side_heel = math.radians(self.strut_inclination) + if self.strut_inclination < 90: + angle_short_side_heel = math.radians(180) + angle_short_side_heel rot_short_side_heel = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_short_side_heel, point=p_origin) cutting_plane_origin.transform(rot_short_side_heel) # Rotate second cutting plane at the end of the notch (long side of the heel) - angle_long_side_heel = math.radians(270 - self.strut_inclination) + angle_long_side_heel = math.radians(90 + self.strut_inclination) rot_long_side_heel = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_long_side_heel, point=p_heel) cutting_plane_heel_heel.transform(rot_long_side_heel) # Calculate step cutting planes angles # Rotate first cutting plane at the end of the heel of the notch (long side of the step) - angle_long_side_step = math.atan( - self.step_depth - / ( - self.displacement_end - - self.displacement_heel - - self.step_depth / math.tan(math.radians(self.strut_inclination / 2)) - ) - ) - if self.strut_inclination < 90: angle_long_side_step = math.atan( self.step_depth / ( self.displacement_end - self.displacement_heel - + (self.step_depth / math.tan(math.radians(90 - self.strut_inclination / 2))) + + (self.step_depth / math.tan(math.radians(90 + self.strut_inclination / 2))) ) ) + else: + angle_long_side_step = math.atan( + self.step_depth + / ( + self.displacement_end + - self.displacement_heel + - self.step_depth / math.tan(math.radians(180 - self.strut_inclination / 2)) + ) + ) + rot_long_side_step = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_long_side_step, point=p_heel) cutting_plane_heel_step.transform(rot_long_side_step) # Rotate second cutting plane at the end of the notch (short side of the step) - angle_short_side_step = math.radians(180 - self.strut_inclination / 2) - if self.strut_inclination < 90: + angle_short_side_step = math.radians(self.strut_inclination / 2) + if self.strut_inclination > 90: + angle_short_side_step = math.radians(180) + angle_short_side_step + else: angle_short_side_step = math.radians(90) + angle_short_side_step rot_short_side_step = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_short_side_step, point=p_end) cutting_plane_end.transform(rot_short_side_step) From dd5f8f54cd441cb20bc71aff35ff40de56ed7829 Mon Sep 17 00:00:00 2001 From: papachap Date: Mon, 12 Aug 2024 00:21:10 +0200 Subject: [PATCH 37/63] functioning TStepJoint --- src/compas_timber/connections/t_step_joint.py | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/compas_timber/connections/t_step_joint.py b/src/compas_timber/connections/t_step_joint.py index 3511cbf3e..ae01becf2 100644 --- a/src/compas_timber/connections/t_step_joint.py +++ b/src/compas_timber/connections/t_step_joint.py @@ -96,12 +96,14 @@ def beams(self): def cross_beam_ref_face_index(self): face_dict = self._beam_side_incidence(self.main_beam, self.cross_beam, ignore_ends=True) face_index = min(face_dict, key=face_dict.get) + # print("cross_beam_ref_face_index", face_index) return face_index @property def main_beam_ref_face_index(self): face_dict = self._beam_side_incidence(self.cross_beam, self.main_beam, ignore_ends=True) - face_index = max(face_dict, key=face_dict.get) + face_index = min(face_dict, key=face_dict.get) + # print("main_beam_ref_face_index", face_index) return face_index def add_features(self): @@ -112,13 +114,13 @@ def add_features(self): """ assert self.main_beam and self.cross_beam # should never happen - if self.features: - self.main_beam.remove_features(self.features) - self.cross_beam.remove_features(self.features) - - main_beam_ref_face = self.main_beam.faces(self.main_beam_ref_face_index) - cross_beam_ref_face = self.cross_beam.faces(self.cross_beam_ref_face_index) + if self.main_beam.features: + self.main_beam.remove_features(self.main_beam.features) + if self.cross_beam.features: + self.cross_beam.remove_features(self.cross_beam.features) + main_beam_ref_face = self.main_beam.faces[self.main_beam_ref_face_index] + cross_beam_ref_face = self.cross_beam.faces[self.cross_beam_ref_face_index] # generate step joint notch features cross_feature = StepJointNotch.from_plane_and_beam( main_beam_ref_face, @@ -128,10 +130,11 @@ def add_features(self): self.notch_width, self.step_depth, self.heel_depth, - self.tapered_heel, self.strut_height, - self.cross_beam_ref_face_index, + self.tapered_heel, + self.cross_beam_ref_face_index - 1, ) + # generate step joint features main_feature = StepJoint.from_plane_and_beam( cross_beam_ref_face, @@ -139,15 +142,13 @@ def add_features(self): self.step_depth, self.heel_depth, self.tapered_heel, - self.main_beam_ref_face_index, + self.main_beam_ref_face_index - 1, ) # generate tenon and mortise features if self.tenon_mortise_height: cross_feature.add_mortise(self.tenon_mortise_height, self.tenon_mortise_width, self.cross_beam) main_feature.add_tenon(self.tenon_mortise_height, self.tenon_mortise_width) - # add features to beams - print(cross_feature) self.main_beam.add_features(main_feature) self.cross_beam.add_features(cross_feature) # add features to joint From 4b0f4499cd23f937a6bb5774442632be2430940f Mon Sep 17 00:00:00 2001 From: papachap Date: Mon, 12 Aug 2024 12:43:28 +0200 Subject: [PATCH 38/63] coherent gh & btlx --- src/compas_timber/_fabrication/step_joint.py | 70 +- .../_fabrication/step_joint_notch.py | 8 +- src/compas_timber/connections/t_step_joint.py | 2 - tests/compas_timber/gh/test_step_joint.ghx | 1061 ++++++++++++++--- 4 files changed, 922 insertions(+), 219 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index 9b3df8ea2..8d5941e17 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -232,6 +232,7 @@ def from_plane_and_beam(cls, plane, beam, step_depth=20.0, heel_depth=0.0, taper # TODO: the stepjointnotch is always orthogonal, this means that the surface should be perpendicular to the beam's ref_side | should there be a check for that? if isinstance(plane, Frame): plane = Plane.from_frame(plane) + plane.normal = plane.normal * -1 # flip the plane normal to point towards the beam # define ref_side & ref_edge ref_side = beam.ref_sides[ref_side_index] # TODO: is this arbitrary? ref_edge = Line.from_point_and_vector(ref_side.point, ref_side.xaxis) @@ -273,11 +274,10 @@ def _calculate_orientation(ref_side, cutting_plane): def _calculate_strut_inclination(ref_side, plane, orientation): # vector rotation direction of the plane's normal in the vertical direction strut_inclination_vector = Vector.cross(ref_side.zaxis, plane.normal) - strut_inclination = angle_vectors_signed(ref_side.zaxis, plane.normal, strut_inclination_vector, deg=True) - if orientation == OrientationType.START: - return 180 - abs(strut_inclination) - else: - return abs(strut_inclination) # get the other side of the angle + strut_inclination = 180 - abs( + angle_vectors_signed(ref_side.zaxis, plane.normal, strut_inclination_vector, deg=True) + ) + return strut_inclination @staticmethod def _define_step_shape(step_depth, heel_depth, tapered_heel): @@ -301,16 +301,18 @@ def _calculate_y_displacement_end(beam_height, strut_inclination): return displacement_end @staticmethod - def _calculate_x_displacement_end(beam_height, strut_inclination): + def _calculate_x_displacement_end(beam_height, strut_inclination, orientation): # Calculates the linear displacement along the x-axis of the ref_side from the origin point to the opposite end of the step. displacement_end = beam_height / math.tan(math.radians(strut_inclination)) + if orientation == OrientationType.END: + displacement_end = -displacement_end return displacement_end @staticmethod def _calculate_x_displacement_heel(heel_depth, strut_inclination, orientation): # Calculates the linear displacement alond the x-axis of the ref_side from the origin point to the heel. displacement_heel = heel_depth / (math.sin(math.radians(strut_inclination))) - if orientation == OrientationType.END: + if orientation == OrientationType.START: displacement_heel = -displacement_heel return displacement_heel @@ -325,7 +327,7 @@ def apply(self, geometry, beam): ---------- geometry : :class:`compas.geometry.Brep` The geometry to be processed. - + beam : :class:`compas_timber.elements.Beam` The beam that is milled by this instance. @@ -489,7 +491,7 @@ def planes_from_params_and_beam(self, beam): # Calculate the displacements for the cutting planes along the y-axis and x-axis y_displacement_end = self._calculate_y_displacement_end(beam.height, self.strut_inclination) - x_displacement_end = self._calculate_x_displacement_end(beam.height, self.strut_inclination) + x_displacement_end = self._calculate_x_displacement_end(beam.height, self.strut_inclination, self.orientation) x_displacement_heel = self._calculate_x_displacement_heel( self.heel_depth, self.strut_inclination, self.orientation ) @@ -503,41 +505,39 @@ def planes_from_params_and_beam(self, beam): cutting_plane_opp = Frame(p_opp, ref_side.frame.xaxis, ref_side.frame.yaxis) cutting_plane_heel = Frame(p_heel, ref_side.frame.xaxis, ref_side.frame.yaxis) - if self.strut_inclination > 90: - incl_angle = math.radians(self.strut_inclination) + if self.orientation == OrientationType.START: rot_axis = cutting_plane_ref.yaxis else: - incl_angle = math.radians(180 - self.strut_inclination) rot_axis = -cutting_plane_ref.yaxis if self.step_shape == StepShape.STEP: - return self._calculate_step_planes( - cutting_plane_ref, cutting_plane_opp, y_displacement_end, incl_angle, rot_axis - ) + return self._calculate_step_planes(cutting_plane_ref, cutting_plane_opp, y_displacement_end, rot_axis) elif self.step_shape == StepShape.HEEL: - return self._calculate_heel_planes(cutting_plane_heel, cutting_plane_opp, incl_angle, rot_axis) + return self._calculate_heel_planes(cutting_plane_heel, cutting_plane_opp, rot_axis) elif self.step_shape == StepShape.TAPERED_HEEL: - return self._calculate_heel_tapered_planes(cutting_plane_opp, y_displacement_end, incl_angle, rot_axis) + return self._calculate_heel_tapered_planes(cutting_plane_opp, y_displacement_end, rot_axis) elif self.step_shape == StepShape.DOUBLE: return self._calculate_double_planes( - cutting_plane_heel, cutting_plane_opp, y_displacement_end, x_displacement_heel, incl_angle, rot_axis + cutting_plane_heel, cutting_plane_opp, y_displacement_end, x_displacement_heel, rot_axis ) - def _calculate_step_planes(self, cutting_plane_ref, cutting_plane_opp, displacement_end, incl_angle, rot_axis): + def _calculate_step_planes(self, cutting_plane_ref, cutting_plane_opp, displacement_end, rot_axis): """Calculate cutting planes for a step.""" # Rotate cutting plane at opp_side - angle_opp = incl_angle / 2 + angle_opp = math.radians(self.strut_inclination / 2) rot_opp = Rotation.from_axis_and_angle(rot_axis, angle_opp, point=cutting_plane_opp.point) cutting_plane_opp.transform(rot_opp) # Rotate cutting plane at ref_side - angle_ref = incl_angle + math.atan(self.step_depth / (displacement_end - self.step_depth / math.tan(angle_opp))) + angle_ref = math.radians(self.strut_inclination) + math.atan( + self.step_depth / (displacement_end - self.step_depth / math.tan(angle_opp)) + ) rot_ref = Rotation.from_axis_and_angle(rot_axis, angle_ref, point=cutting_plane_ref.point) cutting_plane_ref.transform(rot_ref) return [Plane.from_frame(cutting_plane_ref), Plane.from_frame(cutting_plane_opp)] - def _calculate_heel_planes(self, cutting_plane_heel, cutting_plane_opp, incl_angle, rot_axis): + def _calculate_heel_planes(self, cutting_plane_heel, cutting_plane_opp, rot_axis): """Calculate cutting planes for a heel.""" # Rotate cutting plane at displaced origin angle_heel = math.radians(90) @@ -545,15 +545,16 @@ def _calculate_heel_planes(self, cutting_plane_heel, cutting_plane_opp, incl_ang cutting_plane_heel.transform(rot_heel) # Rotate cutting plane at opp_side - rot_opp = Rotation.from_axis_and_angle(rot_axis, incl_angle, point=cutting_plane_opp.point) + angle_opp = math.radians(self.strut_inclination) + rot_opp = Rotation.from_axis_and_angle(rot_axis, angle_opp, point=cutting_plane_opp.point) cutting_plane_opp.transform(rot_opp) return [Plane.from_frame(cutting_plane_heel), Plane.from_frame(cutting_plane_opp)] - def _calculate_heel_tapered_planes(self, cutting_plane_opp, displacement_end, incl_angle, rot_axis): + def _calculate_heel_tapered_planes(self, cutting_plane_opp, displacement_end, rot_axis): """Calculate cutting planes for a tapered heel.""" # Rotate cutting plane at opp_side - angle_opp = incl_angle - math.atan( + angle_opp = math.radians(self.strut_inclination) - math.atan( self.heel_depth / (displacement_end - (self.heel_depth / abs(math.tan(math.radians(self.strut_inclination))))) ) @@ -563,7 +564,7 @@ def _calculate_heel_tapered_planes(self, cutting_plane_opp, displacement_end, in return [Plane.from_frame(cutting_plane_opp)] def _calculate_double_planes( - self, cutting_plane_heel, cutting_plane_opp, displacement_end, displacement_heel, incl_angle, rot_axis + self, cutting_plane_heel, cutting_plane_opp, displacement_end, displacement_heel, rot_axis ): """Calculate cutting planes for a double step.""" # Rotate first cutting plane at displaced origin @@ -571,20 +572,27 @@ def _calculate_double_planes( cutting_plane_heel.transform(rot_origin) # Rotate last cutting plane at opp_side - rot_opp = Rotation.from_axis_and_angle(rot_axis, incl_angle / 2, point=cutting_plane_opp.point) + rot_opp = Rotation.from_axis_and_angle( + rot_axis, math.radians(self.strut_inclination / 2), point=cutting_plane_opp.point + ) cutting_plane_opp.transform(rot_opp) # Translate first cutting plane at heel - trans_len = math.tan(math.radians(self.strut_inclination)) * abs(displacement_heel) - trans_vect = -cutting_plane_heel.xaxis + trans_len = math.tan(math.radians(self.strut_inclination)) * displacement_heel + trans_vect = cutting_plane_heel.xaxis cutting_plane_heel_mid = cutting_plane_heel.translated(trans_vect * trans_len) # Calculate rotation angle for middle cutting plane heel_hypotenus = math.sqrt(math.pow(trans_len, 2) + math.pow(displacement_heel, 2)) angle_heel = ( - incl_angle + math.radians(self.strut_inclination) - math.radians(90) + math.atan( - self.step_depth / (displacement_end - heel_hypotenus - self.step_depth / math.tan(incl_angle / 2)) + self.step_depth + / ( + displacement_end + - heel_hypotenus + - self.step_depth / math.tan(math.radians(self.strut_inclination / 2)) + ) ) ) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index 115ffc894..071b6fb84 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -604,9 +604,9 @@ def _calculate_step_planes(self, ref_side): # Rotate first cutting plane at the start of the notch (large side of the step) angle_long_side = math.atan( self.step_depth - / (self.displacement_end - self.step_depth / math.tan(math.radians(self.strut_inclination / 2))) + / (abs(self.displacement_end) - self.step_depth / math.tan(math.radians((self.strut_inclination) / 2))) ) - rot_long_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_long_side, point=p_origin) + rot_long_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, -angle_long_side, point=p_origin) cutting_plane_origin.transform(rot_long_side) # Rotate second cutting plane at the end of the notch (short side of the step) @@ -622,8 +622,8 @@ def _calculate_step_planes(self, ref_side): angle_long_side = math.atan( self.step_depth / ( - self.displacement_end - + (self.step_depth / math.tan(math.radians((180 - self.strut_inclination) / 2))) + abs(self.displacement_end) + - (self.step_depth / math.tan(math.radians((180 - self.strut_inclination) / 2))) ) ) rot_long_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_long_side, point=p_end) diff --git a/src/compas_timber/connections/t_step_joint.py b/src/compas_timber/connections/t_step_joint.py index ae01becf2..2a7e2a974 100644 --- a/src/compas_timber/connections/t_step_joint.py +++ b/src/compas_timber/connections/t_step_joint.py @@ -96,14 +96,12 @@ def beams(self): def cross_beam_ref_face_index(self): face_dict = self._beam_side_incidence(self.main_beam, self.cross_beam, ignore_ends=True) face_index = min(face_dict, key=face_dict.get) - # print("cross_beam_ref_face_index", face_index) return face_index @property def main_beam_ref_face_index(self): face_dict = self._beam_side_incidence(self.cross_beam, self.main_beam, ignore_ends=True) face_index = min(face_dict, key=face_dict.get) - # print("main_beam_ref_face_index", face_index) return face_index def add_features(self): diff --git a/tests/compas_timber/gh/test_step_joint.ghx b/tests/compas_timber/gh/test_step_joint.ghx index 7c11ad7dc..7edd06e08 100644 --- a/tests/compas_timber/gh/test_step_joint.ghx +++ b/tests/compas_timber/gh/test_step_joint.ghx @@ -48,10 +48,10 @@ - 207 - 153 + 16 + 212 - 0.2460933 + 0.151132 @@ -95,9 +95,9 @@ - 70 + 78 - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 @@ -274,7 +274,7 @@ beam = cross_beam plane = main_beam.ref_sides[index] #create step_joint_notch -step_joint_notch = StepJointNotch.from_surface_and_beam(plane, beam, notch_limited=False, step_depth=step, heel_depth=heel, strut_height=100.0, tapered_heel=tapered, ref_side_index=ref_side_index) +step_joint_notch = StepJointNotch.from_plane_and_beam(plane, beam, start_y=0.0, notch_limited=False, step_depth=step, heel_depth=heel, strut_height=100.0, tapered_heel=tapered, ref_side_index=ref_side_index) cutting_planes = step_joint_notch.planes_from_params_and_beam(beam) print("Orientation: ", step_joint_notch.orientation) @@ -286,7 +286,8 @@ if mortise_height > 0: mortise_brep = step_joint_notch.mortise_volume_from_params_and_beam(beam) #apply geometric features -geometry = step_joint_notch.apply(beam) +geo = beam.compute_geometry(False) +geometry = step_joint_notch.apply(geo, beam) rg_geometry = brep_to_rhino(geometry) #get btlx params @@ -307,8 +308,8 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) GhPython provides a Python script component - 247 - 383 + 344 + 182 1232 @@ -636,10 +637,10 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - Script output surface. - 43d74bb6-8d1a-4853-8757-d522804215d3 - surface - surface + Script output step_joint_notch. + b61795d0-9c3c-4a69-b52e-e2b15de8d4c0 + step_joint_notch + step_joint_notch false 0 @@ -859,7 +860,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) Allows for customized geometry previews true - true + false 9e4f0b21-759b-4c9a-8dfb-6654484027c5 Custom Preview Preview @@ -1591,6 +1592,7 @@ unload_modules("compas_timber") #define beam and cutting plane beam = main_beam +geo = beam.compute_geometry(False) plane = cross_beam.ref_sides[index] #create step_joint @@ -1605,7 +1607,7 @@ if tenon_height > 0: tenon_volume = step_joint.tenon_volume_from_params_and_beam(beam) #apply geometric features -geometry = step_joint.apply(beam) +geometry = step_joint.apply(geo, beam) rg_geometry = brep_to_rhino(geometry) rg_geometry.MergeCoplanarFaces(0.01) @@ -1623,8 +1625,8 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) GhPython provides a Python script component - 278 - 159 + 240 + 154 1232 @@ -1656,7 +1658,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 8 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 @@ -1666,15 +1668,16 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 6 + 7 3ede854e-c753-40eb-84cb-b48008f14fd4 8ec86459-bf01-4409-baee-174d0d2b13d0 8ec86459-bf01-4409-baee-174d0d2b13d0 8ec86459-bf01-4409-baee-174d0d2b13d0 8ec86459-bf01-4409-baee-174d0d2b13d0 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 - + true @@ -1939,17 +1942,43 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2365 1271 90 - 26 + 22 2410 - 1284.333 + 1282.429 + + Script output step_joint. + 144c99d6-3d48-4727-b642-5c61dd368619 + step_joint + step_joint + false + 0 + + + + + + 2365 + 1293 + 90 + 23 + + + 2410 + 1305.286 + + + + + + Script output rg_cutting_plane. 9fe4a86c-3c6e-4f70-8cbe-630b57b5a0b1 @@ -1963,19 +1992,19 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2365 - 1297 + 1316 90 - 27 + 23 2410 - 1311 + 1328.143 - + Script output rg_planes. 6270f634-eb6d-402e-a88f-24f478dcf237 @@ -1989,19 +2018,19 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2365 - 1324 + 1339 90 - 27 + 23 2410 - 1337.667 + 1351 - + Script output rg_ref_side. 9c129a89-24d5-45f5-bcea-890db46f3da4 @@ -2015,19 +2044,19 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2365 - 1351 + 1362 90 - 26 + 23 2410 - 1364.333 + 1373.857 - + Script output rg_geometry. 441ff4ac-6060-44cf-965a-55428a9d4cc6 @@ -2041,19 +2070,19 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2365 - 1377 + 1385 90 - 27 + 23 2410 - 1391 + 1396.714 - + Script output btlx_params. 269c79e7-4269-4669-b62a-9869376ea11c @@ -2067,13 +2096,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2365 - 1404 + 1408 90 - 27 + 23 2410 - 1417.667 + 1419.571 @@ -2104,13 +2133,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1675 + 1635 873 201 20 - 1675.708 + 1635.588 873.239 @@ -2247,13 +2276,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1640 + 1635 965 160 20 - 1640.78 + 1635.78 965.2471 @@ -2266,7 +2295,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 200 0 0 - 30 + 0 @@ -2292,13 +2321,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1640 + 1635 988 160 20 - 1640.779 + 1635.779 988.3201 @@ -2311,7 +2340,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 200 0 0 - 15 + 40 @@ -2338,8 +2367,8 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1663 - 1006 + 1635 + 1010 133 22 @@ -2356,12 +2385,11 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + Allows for customized geometry previews true - true + false def048ab-0443-4189-81d6-03594953cdd1 - true Custom Preview Preview @@ -2382,11 +2410,10 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + Geometry to preview true 977e596e-9c8a-4d5e-a490-c2c39fe7f008 - true Geometry G false @@ -2411,10 +2438,9 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + The material override 5f9e9e88-be8b-4453-b5f6-8e8fcf54697c - true Material M false @@ -2883,13 +2909,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + The preview shader override f7c73e31-145c-457b-8538-038ea41e36ce Shader S false - 0 + d0dd1043-724e-45c1-a8b5-6f7ff06d8073 + 1 @@ -3077,7 +3104,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 100 0 0 - 50 + 0 @@ -3208,13 +3235,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 354 - 548 + 513 50 24 379.472 - 560.2095 + 525.6697 @@ -3373,13 +3400,13 @@ class Beam_fromCurve(component): 1018 - 403 + 368 145 124 1109 - 465 + 430 @@ -3417,13 +3444,13 @@ class Beam_fromCurve(component): 1020 - 405 + 370 74 20 1058.5 - 415 + 380 @@ -3448,13 +3475,13 @@ class Beam_fromCurve(component): 1020 - 425 + 390 74 20 1058.5 - 435 + 400 @@ -3480,13 +3507,13 @@ class Beam_fromCurve(component): 1020 - 445 + 410 74 20 1058.5 - 455 + 420 @@ -3512,13 +3539,13 @@ class Beam_fromCurve(component): 1020 - 465 + 430 74 20 1058.5 - 475 + 440 @@ -3543,13 +3570,13 @@ class Beam_fromCurve(component): 1020 - 485 + 450 74 20 1058.5 - 495 + 460 @@ -3573,13 +3600,13 @@ class Beam_fromCurve(component): 1020 - 505 + 470 74 20 1058.5 - 515 + 480 @@ -3599,13 +3626,13 @@ class Beam_fromCurve(component): 1124 - 405 + 370 37 60 1142.5 - 435 + 400 @@ -3625,13 +3652,13 @@ class Beam_fromCurve(component): 1124 - 465 + 430 37 60 1142.5 - 495 + 460 @@ -3663,13 +3690,13 @@ class Beam_fromCurve(component): 798 - 475 + 440 166 20 798.3945 - 475.1427 + 440.6029 @@ -3709,13 +3736,13 @@ class Beam_fromCurve(component): 350 - 404 + 369 50 24 375.6421 - 416.2252 + 381.6854 @@ -3874,13 +3901,13 @@ class Beam_fromCurve(component): 1016 - 611 + 576 145 124 1107 - 673 + 638 @@ -3918,13 +3945,13 @@ class Beam_fromCurve(component): 1018 - 613 + 578 74 20 1056.5 - 623 + 588 @@ -3949,13 +3976,13 @@ class Beam_fromCurve(component): 1018 - 633 + 598 74 20 1056.5 - 643 + 608 @@ -3981,13 +4008,13 @@ class Beam_fromCurve(component): 1018 - 653 + 618 74 20 1056.5 - 663 + 628 @@ -4013,13 +4040,13 @@ class Beam_fromCurve(component): 1018 - 673 + 638 74 20 1056.5 - 683 + 648 @@ -4044,13 +4071,13 @@ class Beam_fromCurve(component): 1018 - 693 + 658 74 20 1056.5 - 703 + 668 @@ -4074,13 +4101,13 @@ class Beam_fromCurve(component): 1018 - 713 + 678 74 20 1056.5 - 723 + 688 @@ -4100,13 +4127,13 @@ class Beam_fromCurve(component): 1122 - 613 + 578 37 60 1140.5 - 643 + 608 @@ -4126,13 +4153,13 @@ class Beam_fromCurve(component): 1122 - 673 + 638 37 60 1140.5 - 703 + 668 @@ -4164,13 +4191,13 @@ class Beam_fromCurve(component): 833 - 670 + 635 166 20 833.363 - 670.2716 + 635.7318 @@ -4209,13 +4236,13 @@ class Beam_fromCurve(component): 820 - 697 + 663 166 20 820.9324 - 697.9297 + 663.3899 @@ -4253,13 +4280,13 @@ class Beam_fromCurve(component): 818 - 618 + 583 66 44 850 - 640 + 605 @@ -4278,13 +4305,13 @@ class Beam_fromCurve(component): 820 - 620 + 585 15 20 829 - 630 + 595 @@ -4304,13 +4331,13 @@ class Beam_fromCurve(component): 820 - 640 + 605 15 20 829 - 650 + 615 @@ -4330,13 +4357,13 @@ class Beam_fromCurve(component): 865 - 620 + 585 17 20 873.5 - 630 + 595 @@ -4356,13 +4383,13 @@ class Beam_fromCurve(component): 865 - 640 + 605 17 20 873.5 - 650 + 615 @@ -4391,13 +4418,13 @@ class Beam_fromCurve(component): 513 - 588 + 553 64 44 544 - 610 + 575 @@ -4416,13 +4443,13 @@ class Beam_fromCurve(component): 515 - 590 + 555 14 40 523.5 - 610 + 575 @@ -4442,13 +4469,13 @@ class Beam_fromCurve(component): 559 - 590 + 555 16 20 567 - 600 + 565 @@ -4468,13 +4495,13 @@ class Beam_fromCurve(component): 559 - 610 + 575 16 20 567 - 620 + 585 @@ -4503,13 +4530,13 @@ class Beam_fromCurve(component): 619 - 608 + 573 83 44 667 - 630 + 595 @@ -4528,13 +4555,13 @@ class Beam_fromCurve(component): 621 - 610 + 575 31 20 646 - 620 + 585 @@ -4556,13 +4583,13 @@ class Beam_fromCurve(component): 621 - 630 + 595 31 20 646 - 640 + 605 @@ -4606,13 +4633,13 @@ class Beam_fromCurve(component): 682 - 610 + 575 18 20 691 - 620 + 585 @@ -4632,13 +4659,13 @@ class Beam_fromCurve(component): 682 - 630 + 595 18 20 691 - 640 + 605 @@ -4667,13 +4694,13 @@ class Beam_fromCurve(component): 337 - 641 + 606 63 28 366 - 655 + 620 @@ -4692,13 +4719,13 @@ class Beam_fromCurve(component): 339 - 643 + 608 12 24 346.5 - 655 + 620 @@ -4738,13 +4765,13 @@ class Beam_fromCurve(component): 381 - 643 + 608 17 24 389.5 - 655 + 620 @@ -4774,13 +4801,13 @@ class Beam_fromCurve(component): 154 - 648 + 614 163 20 154.723 - 648.6322 + 614.0924 @@ -4789,7 +4816,7 @@ class Beam_fromCurve(component): 3 1 1 - 2000 + 4000 0 0 0 @@ -4818,13 +4845,13 @@ class Beam_fromCurve(component): 729 - 588 + 553 63 44 760 - 610 + 575 @@ -4843,13 +4870,13 @@ class Beam_fromCurve(component): 731 - 590 + 555 14 20 739.5 - 600 + 565 @@ -4870,13 +4897,13 @@ class Beam_fromCurve(component): 731 - 610 + 575 14 20 739.5 - 620 + 585 @@ -4896,13 +4923,13 @@ class Beam_fromCurve(component): 775 - 590 + 555 15 40 782.5 - 610 + 575 @@ -4931,13 +4958,13 @@ class Beam_fromCurve(component): 339 - 675 + 640 63 28 368 - 689 + 654 @@ -4956,13 +4983,13 @@ class Beam_fromCurve(component): 341 - 677 + 642 12 24 348.5 - 689 + 654 @@ -5002,13 +5029,13 @@ class Beam_fromCurve(component): 383 - 677 + 642 17 24 391.5 - 689 + 654 @@ -5038,13 +5065,13 @@ class Beam_fromCurve(component): 157 - 683 + 649 163 20 157.0529 - 683.7195 + 649.1797 @@ -5082,13 +5109,13 @@ class Beam_fromCurve(component): 514 - 633 + 598 65 44 545 - 655 + 620 @@ -5116,13 +5143,13 @@ class Beam_fromCurve(component): 516 - 635 + 600 14 20 524.5 - 645 + 610 @@ -5143,13 +5170,13 @@ class Beam_fromCurve(component): 516 - 655 + 620 14 20 524.5 - 665 + 630 @@ -5169,13 +5196,13 @@ class Beam_fromCurve(component): 560 - 635 + 600 17 40 568.5 - 655 + 620 @@ -5208,7 +5235,7 @@ class Beam_fromCurve(component): 286 - 580 + 545 115 22 @@ -5237,13 +5264,13 @@ class Beam_fromCurve(component): 899 - 578 + 543 77 64 931 - 610 + 575 @@ -5272,13 +5299,13 @@ class Beam_fromCurve(component): 901 - 580 + 545 15 20 910 - 590 + 555 @@ -5321,13 +5348,13 @@ class Beam_fromCurve(component): 901 - 600 + 565 15 20 910 - 610 + 575 @@ -5350,13 +5377,13 @@ class Beam_fromCurve(component): 901 - 620 + 585 15 20 910 - 630 + 595 @@ -5378,13 +5405,13 @@ class Beam_fromCurve(component): 946 - 580 + 545 28 60 960 - 610 + 575 @@ -5465,7 +5492,7 @@ class Beam_fromCurve(component): false 0 - 34;168;168;168 + 34;240;180;137 @@ -5748,8 +5775,9 @@ class Beam_fromCurve(component): - + Contains a collection of Breps (Boundary REPresentations) + true 91868622-66e7-4d7d-a5a0-9480a9e4a02a Brep Brep @@ -5834,6 +5862,675 @@ class Beam_fromCurve(component): + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + GhPython Script + + + + + from compas_timber.connections import TStepJoint + +from compas_timber.model import TimberModel +from compas_timber.fabrication import BTLx + +from compas_rhino import unload_modules + +unload_modules("compas_timber") + +step_joint_joint = TStepJoint(main_beam, cross_beam, step_depth, heel_depth, tapered_heel, tenon_mortise_height) +step_joint_joint.add_features() + +timber_model = TimberModel() +for beam in step_joint_joint.beams: + timber_model.add_beam(beam) + +timber_model.add_joint(step_joint_joint, step_joint_joint.beams) + + +btlx = BTLx(timber_model) +btlx.process_model() +BTLx = btlx.btlx_string() + GhPython provides a Python script component + + 126 + 122 + + + 901 + 818 + + true + true + false + false + 20be8b4b-0eba-41a5-8f13-25defa3e633d + false + true + GhPython Script + Python + + + + + + 3506 + 915 + 223 + 124 + + + 3635 + 977 + + + + + + 6 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 3 + 3ede854e-c753-40eb-84cb-b48008f14fd4 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + true + Script input cross_beam. + 36ebe6e6-4cdb-4b9d-8c4a-65feaf8cee5a + cross_beam + cross_beam + true + 0 + true + 3984d0df-294e-462f-b103-b87bb94e3021 + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 3508 + 917 + 112 + 20 + + + 3565.5 + 927 + + + + + + + + true + Script input main_beam. + 407e65c9-4ebe-4581-9401-a364b25f96c2 + main_beam + main_beam + true + 0 + true + 90fae02f-7750-462d-828f-cf5f2a0d39ae + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 3508 + 937 + 112 + 20 + + + 3565.5 + 947 + + + + + + + + true + Script input step_depth. + fbd107db-8f8a-4baa-876f-623012dd3bb0 + step_depth + step_depth + true + 0 + true + c3c1dc6d-7ceb-4d7f-add6-fa924b4cc936 + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 3508 + 957 + 112 + 20 + + + 3565.5 + 967 + + + + + + + + true + Script input heel_depth. + dab8fd89-b668-48ad-91eb-c695241b0e94 + heel_depth + heel_depth + true + 0 + true + ab977350-6a8d-472b-9276-52a400da755b + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 3508 + 977 + 112 + 20 + + + 3565.5 + 987 + + + + + + + + true + Script input tapered_heel. + f4ca3577-e0dc-42fa-8923-d574fb19c330 + tapered_heel + tapered_heel + true + 0 + true + dec5089d-c452-417b-a948-4034d6df42ce + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 3508 + 997 + 112 + 20 + + + 3565.5 + 1007 + + + + + + + + true + Script input tenon_mortise_height. + e6b1d326-8808-4533-930e-154d3cd939d3 + tenon_mortise_height + tenon_mortise_height + true + 0 + true + 1f382d1c-8a89-4e8a-8102-597801cd81a3 + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 3508 + 1017 + 112 + 20 + + + 3565.5 + 1027 + + + + + + + + The execution information, as output and error streams + fb496e59-6b43-4266-9c61-c466f3a89f8e + out + out + false + 0 + + + + + + 3650 + 917 + 77 + 40 + + + 3688.5 + 937 + + + + + + + + Script output timber_model. + b0d6f1d5-5017-448f-8e9f-d5b614d7e076 + timber_model + timber_model + false + 0 + + + + + + 3650 + 957 + 77 + 40 + + + 3688.5 + 977 + + + + + + + + Script output BTLx. + 8674ed0a-6d18-41d1-9209-ae7aed0e558d + BTLx + BTLx + false + 0 + + + + + + 3650 + 997 + 77 + 40 + + + 3688.5 + 1017 + + + + + + + + + + + + + + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel + + + + + A panel for custom notes and text values + a31a3c97-3e91-47a8-add8-793f530b94e1 + Panel + + false + 1 + 8674ed0a-6d18-41d1-9209-ae7aed0e558d + 1 + G:\Shared drives\2024_MAS\T2\03_finalization\Fabrication - Joints\BTLx\step_joint_joint_test.btlx + + + + + + 4213 + 938 + 446 + 420 + + 0 + 0 + 0 + + 4213.571 + 938.6143 + + + + + + + 255;255;250;90 + + true + true + true + false + true + D:\Papachap\Desktop\btlx\TStepJoint.btlx + true + + + + + + + + + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel + + + + + A panel for custom notes and text values + dfdc288d-17d2-4daf-93bc-82b8f3f1816b + Panel + StepJoint BTLx Params + false + 0 + 269c79e7-4269-4669-b62a-9869376ea11c + 1 + Double click to edit panel content… + + + + + + 3995 + 1004 + 212 + 333 + + 0 + 0 + 0 + + 3995.862 + 1004.686 + + + + + + + 255;255;255;255 + + true + true + true + false + false + true + + + + + + + + + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel + + + + + A panel for custom notes and text values + 1efbf060-c389-495d-aacf-3ce7dc0fcafc + Panel + StepJointNotch BTLx Params + false + 0 + d525eccb-5bff-4563-b75e-472cbbdc5901 + 1 + Double click to edit panel content… + + + + + + 4658 + 1009 + 212 + 333 + + 0 + 0 + 0 + + 4658.117 + 1009.509 + + + + + + + 255;255;255;255 + + true + true + true + false + false + true + + + + + + + + + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel + + + + + A panel for custom notes and text values + d0efa1ab-d893-4335-8af7-dd6d94484ea7 + Panel + + false + 0.73333331942558289 + fb496e59-6b43-4266-9c61-c466f3a89f8e + 1 + Double click to edit panel content… + + + + + + 3505 + 862 + 224 + 53 + + 0 + 0 + 0 + + 3505.034 + 862.5032 + + + + + + + 255;255;250;90 + + true + true + true + false + false + true + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 20be8b4b-0eba-41a5-8f13-25defa3e633d + d0efa1ab-d893-4335-8af7-dd6d94484ea7 + 9f3e0877-7fa6-4696-83ae-f6281f1d75f8 + 3c27b464-0da3-49d8-922b-780384eb4127 + 4 + c6445b86-caf6-4180-b0be-5dcfc6eaea9a + Group + + + + + + + + + + + 7f5c6c55-f846-4a08-9c9a-cfdc285cc6fe + Scribble + + + + + true + + 3482.384 + 775.9541 + + + 3737.145 + 775.9541 + + + 3737.145 + 823.293 + + + 3482.384 + 823.293 + + A quick note + Microsoft Sans Serif + 9f3e0877-7fa6-4696-83ae-f6281f1d75f8 + false + Scribble + Scribble + 50 + TStepJoint + + + + + + 3477.384 + 770.9541 + 264.7607 + 57.33887 + + + 3482.384 + 775.9541 + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 9f3e0877-7fa6-4696-83ae-f6281f1d75f8 + 1 + 3c27b464-0da3-49d8-922b-780384eb4127 + Group + + + + + + + + @@ -5841,7 +6538,7 @@ class Beam_fromCurve(component): - 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 From 5742f78a26c1f8180d44af1d4d435fb0454fce4a Mon Sep 17 00:00:00 2001 From: papachap Date: Mon, 12 Aug 2024 14:13:52 +0200 Subject: [PATCH 39/63] minor fixes for tenon and mortise --- src/compas_timber/_fabrication/step_joint.py | 4 ++-- src/compas_timber/_fabrication/step_joint_notch.py | 14 +++++++++----- src/compas_timber/connections/t_step_joint.py | 5 +++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index 8d5941e17..656d3bf02 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -630,7 +630,7 @@ def tenon_volume_from_params_and_beam(self, beam): ref_side = beam.side_as_surface(self.ref_side_index) opp_side = beam.side_as_surface((self.ref_side_index + 2) % 4) - x_displacement_end = self._calculate_x_displacement_end(beam.height, self.strut_inclination) + x_displacement_end = self._calculate_x_displacement_end(beam.height, self.strut_inclination, self.orientation) # Get the points of the top face of the tenon on the ref_side and opp_side # x-displcement @@ -652,7 +652,7 @@ def tenon_volume_from_params_and_beam(self, beam): # calcutate the extrusion vector of the tenon extr_vector_length = self.tenon_height / math.sin(math.radians(self.strut_inclination)) extr_vector = ref_side.frame.xaxis * extr_vector_length - if self.orientation == OrientationType.END: + if self.orientation == OrientationType.START: extr_vector = -extr_vector # translate the polyline to create the tenon volume diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index 071b6fb84..38b90fc49 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -607,12 +607,12 @@ def _calculate_step_planes(self, ref_side): / (abs(self.displacement_end) - self.step_depth / math.tan(math.radians((self.strut_inclination) / 2))) ) rot_long_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, -angle_long_side, point=p_origin) - cutting_plane_origin.transform(rot_long_side) + cutting_plane_end.transform(rot_long_side) # Rotate second cutting plane at the end of the notch (short side of the step) angle_short_side = math.radians(self.strut_inclination / 2) rot_short_side = Rotation.from_axis_and_angle(ref_side.frame.yaxis, angle_short_side, point=p_end) - cutting_plane_end.transform(rot_short_side) + cutting_plane_origin.transform(rot_short_side) else: # Rotate first cutting plane at the start of the notch (short side of the step) angle_short_side = math.radians((180 - self.strut_inclination) / 2) @@ -803,9 +803,9 @@ def mortise_volume_from_params_and_beam(self, beam): extr_vector = ref_side.frame.xaxis * extr_vector_length if self.strut_inclination > 90: vector_angle = math.radians(180 - self.strut_inclination) + extr_vector = extr_vector * -1 else: vector_angle = math.radians(self.strut_inclination) - extr_vector = extr_vector * -1 rot_vect = Rotation.from_axis_and_angle(rot_axis, vector_angle) extr_vector.transform(rot_vect) @@ -818,8 +818,12 @@ def mortise_volume_from_params_and_beam(self, beam): # convert to Brep and trim with cutting_planes mortise_brep = Brep.from_box(mortise_box) - trimming_plane_start = Plane.from_point_and_two_vectors(p_1, extr_vector, ref_side.frame.yaxis) - trimming_plane_end = Plane.from_point_and_two_vectors(p_2, -extr_vector, ref_side.frame.yaxis) + if self.strut_inclination > 90: + trimming_plane_start = Plane.from_point_and_two_vectors(p_1, extr_vector, ref_side.frame.yaxis) + trimming_plane_end = Plane.from_point_and_two_vectors(p_2, -extr_vector, ref_side.frame.yaxis) + else: + trimming_plane_start = Plane.from_point_and_two_vectors(p_1, -extr_vector, ref_side.frame.yaxis) + trimming_plane_end = Plane.from_point_and_two_vectors(p_2, extr_vector, ref_side.frame.yaxis) try: mortise_brep.trim(trimming_plane_start) mortise_brep.trim(trimming_plane_end) diff --git a/src/compas_timber/connections/t_step_joint.py b/src/compas_timber/connections/t_step_joint.py index 2a7e2a974..d09877681 100644 --- a/src/compas_timber/connections/t_step_joint.py +++ b/src/compas_timber/connections/t_step_joint.py @@ -85,6 +85,7 @@ def __init__( self.notch_width = self.main_beam.width self.strut_height = self.main_beam.height self.tenon_mortise_width = self.main_beam.width / 4 + print(self.tenon_mortise_width) self.features = [] @@ -144,8 +145,8 @@ def add_features(self): ) # generate tenon and mortise features if self.tenon_mortise_height: - cross_feature.add_mortise(self.tenon_mortise_height, self.tenon_mortise_width, self.cross_beam) - main_feature.add_tenon(self.tenon_mortise_height, self.tenon_mortise_width) + cross_feature.add_mortise(self.tenon_mortise_width, self.tenon_mortise_height, self.cross_beam) + main_feature.add_tenon(self.tenon_mortise_width, self.tenon_mortise_height) # add features to beams self.main_beam.add_features(main_feature) self.cross_beam.add_features(cross_feature) From 0c4d8760c53cfd68d5ff1b3835d092bfa0275d18 Mon Sep 17 00:00:00 2001 From: papachap Date: Mon, 12 Aug 2024 16:01:00 +0200 Subject: [PATCH 40/63] update _beam_side_incidence (face-->ref_side) --- src/compas_timber/connections/joint.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/compas_timber/connections/joint.py b/src/compas_timber/connections/joint.py index 8ab005d93..c9e5d6171 100644 --- a/src/compas_timber/connections/joint.py +++ b/src/compas_timber/connections/joint.py @@ -196,7 +196,7 @@ def get_face_most_ortho_to_beam(beam_a, beam_b, ignore_ends=True): @staticmethod def _beam_side_incidence(beam_a, beam_b, ignore_ends=True): - """Returns a map of face indices of beam_b and the angle of their normal with beam_a's centerline. + """Returns a map of ref_side indices of beam_b and the angle of their normal with beam_a's centerline. This is used to find a cutting plane when joining the two beams. @@ -207,18 +207,18 @@ def _beam_side_incidence(beam_a, beam_b, ignore_ends=True): beam_b : :class:`~compas_timber.parts.Beam` The other beam. ignore_ends : bool, optional - If True, only the first four faces of `beam_b` are considered. Otherwise all faces are considered. + If True, only the first four ref_sides of `beam_b` are considered. Otherwise all ref_sides are considered. Examples -------- - >>> face_angles = Joint.beam_side_incidence(beam_a, beam_b) - >>> closest_face_index = min(face_angles, key=face_angles.get) - >>> cutting_plane = beam_b.faces[closest_face_index] + >>> ref_side_angles = Joint.beam_side_incidence(beam_a, beam_b) + >>> closest_ref_side_index = min(ref_side_angles, key=ref_side_angles.get) + >>> cutting_plane = beam_b.ref_sides[closest_ref_side_index] Returns ------- dict(int, float) - A map of face indices of beam_b and their respective angle with beam_a's centerline. + A map of ref_side indices of beam_b and their respective angle with beam_a's centerline. """ # find the orientation of beam_a's centerline so that it's pointing outward of the joint @@ -235,12 +235,12 @@ def _beam_side_incidence(beam_a, beam_b, ignore_ends=True): centerline_vec = beam_a.centerline.vector * -1 if ignore_ends: - beam_b_faces = beam_b.faces[:4] + beam_b_ref_sides = beam_b.ref_sides[:4] else: - beam_b_faces = beam_b.faces + beam_b_ref_sides = beam_b.ref_sides - face_angles = {} - for face_index, face in enumerate(beam_b_faces): - face_angles[face_index] = angle_vectors(face.normal, centerline_vec) + ref_side_angles = {} + for ref_side_index, ref_side in enumerate(beam_b_ref_sides): + ref_side_angles[ref_side_index] = angle_vectors(ref_side.normal, centerline_vec) - return face_angles + return ref_side_angles From 6d5d0293677c1a16a93122fd18f906a8cd8c1839 Mon Sep 17 00:00:00 2001 From: papachap Date: Mon, 12 Aug 2024 16:04:36 +0200 Subject: [PATCH 41/63] adjustments after comments --- src/compas_timber/connections/t_step_joint.py | 36 +- tests/compas_timber/gh/test_step_joint.ghx | 1068 ++++++++++++++++- 2 files changed, 1021 insertions(+), 83 deletions(-) diff --git a/src/compas_timber/connections/t_step_joint.py b/src/compas_timber/connections/t_step_joint.py index d09877681..022aab7c9 100644 --- a/src/compas_timber/connections/t_step_joint.py +++ b/src/compas_timber/connections/t_step_joint.py @@ -85,7 +85,6 @@ def __init__( self.notch_width = self.main_beam.width self.strut_height = self.main_beam.height self.tenon_mortise_width = self.main_beam.width / 4 - print(self.tenon_mortise_width) self.features = [] @@ -94,16 +93,16 @@ def beams(self): return [self.main_beam, self.cross_beam] @property - def cross_beam_ref_face_index(self): - face_dict = self._beam_side_incidence(self.main_beam, self.cross_beam, ignore_ends=True) - face_index = min(face_dict, key=face_dict.get) - return face_index + def cross_beam_ref_side_index(self): + ref_side_dict = self._beam_side_incidence(self.main_beam, self.cross_beam, ignore_ends=True) + ref_side_index = min(ref_side_dict, key=ref_side_dict.get) + return ref_side_index @property - def main_beam_ref_face_index(self): - face_dict = self._beam_side_incidence(self.cross_beam, self.main_beam, ignore_ends=True) - face_index = min(face_dict, key=face_dict.get) - return face_index + def main_beam_ref_side_index(self): + ref_side_dict = self._beam_side_incidence(self.cross_beam, self.main_beam, ignore_ends=True) + ref_side_index = min(ref_side_dict, key=ref_side_dict.get) + return ref_side_index def add_features(self): """Adds the required trimming features to both beams. @@ -113,16 +112,15 @@ def add_features(self): """ assert self.main_beam and self.cross_beam # should never happen - if self.main_beam.features: - self.main_beam.remove_features(self.main_beam.features) - if self.cross_beam.features: - self.cross_beam.remove_features(self.cross_beam.features) + if self.features: + self.main_beam.remove_features(self.features) + self.cross_beam.remove_features(self.features) - main_beam_ref_face = self.main_beam.faces[self.main_beam_ref_face_index] - cross_beam_ref_face = self.cross_beam.faces[self.cross_beam_ref_face_index] + main_beam_ref_side = self.main_beam.ref_sides[self.main_beam_ref_side_index] + cross_beam_ref_side = self.cross_beam.ref_sides[self.cross_beam_ref_side_index] # generate step joint notch features cross_feature = StepJointNotch.from_plane_and_beam( - main_beam_ref_face, + main_beam_ref_side, self.cross_beam, self.start_y, self.notch_limited, @@ -131,17 +129,17 @@ def add_features(self): self.heel_depth, self.strut_height, self.tapered_heel, - self.cross_beam_ref_face_index - 1, + self.cross_beam_ref_side_index, ) # generate step joint features main_feature = StepJoint.from_plane_and_beam( - cross_beam_ref_face, + cross_beam_ref_side, self.main_beam, self.step_depth, self.heel_depth, self.tapered_heel, - self.main_beam_ref_face_index - 1, + self.main_beam_ref_side_index, ) # generate tenon and mortise features if self.tenon_mortise_height: diff --git a/tests/compas_timber/gh/test_step_joint.ghx b/tests/compas_timber/gh/test_step_joint.ghx index 7edd06e08..02f99d70a 100644 --- a/tests/compas_timber/gh/test_step_joint.ghx +++ b/tests/compas_timber/gh/test_step_joint.ghx @@ -48,10 +48,10 @@ - 16 - 212 + -1382 + -273 - 0.151132 + 0.5546324 @@ -95,9 +95,9 @@ - 78 + 87 - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 @@ -284,6 +284,7 @@ print("StrutInclination: ", step_joint_notch.strut_inclination) if mortise_height > 0: step_joint_notch.add_mortise(beam.width/4, mortise_height, beam) mortise_brep = step_joint_notch.mortise_volume_from_params_and_beam(beam) + mortise_brep = brep_to_rhino(mortise_brep) #apply geometric features geo = beam.compute_geometry(False) @@ -308,8 +309,8 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) GhPython provides a Python script component - 344 - 182 + 282 + 266 1232 @@ -1330,8 +1331,9 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + Custom Preview with Lineweights + true 56020ec2-ccb1-4ae9-a538-84dcb2857db9 Custom Preview Lineweights PreviewLW @@ -1605,6 +1607,7 @@ print("StrutInclination: ", step_joint.strut_inclination) if tenon_height > 0: step_joint.add_tenon(cross_beam.width/4, tenon_height) tenon_volume = step_joint.tenon_volume_from_params_and_beam(beam) + tenon_volume = brep_to_rhino(tenon_volume) #apply geometric features geometry = step_joint.apply(geo, beam) @@ -1625,8 +1628,8 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) GhPython provides a Python script component - 240 - 154 + 351 + 113 1232 @@ -2295,7 +2298,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 200 0 0 - 0 + 30 @@ -2340,7 +2343,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 200 0 0 - 40 + 0 @@ -2858,8 +2861,9 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + Custom Preview with Lineweights + true 0cbceed4-3434-411c-b4d1-46985ef14dea Custom Preview Lineweights PreviewLW @@ -3104,7 +3108,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 100 0 0 - 0 + 40 @@ -5228,7 +5232,7 @@ class Beam_fromCurve(component): FlipCurve false 0 - true + false @@ -5396,7 +5400,7 @@ class Beam_fromCurve(component): 09fe95fb-9551-4690-abf6-4a0665002914 false Stream - S(1) + S(0) false 0 @@ -5789,14 +5793,14 @@ class Beam_fromCurve(component): - 2660 - 496 + 2663 + 480 50 24 - 2685.648 - 508.1772 + 2688.471 + 492.1772 @@ -5876,27 +5880,39 @@ class Beam_fromCurve(component): from compas_timber.model import TimberModel from compas_timber.fabrication import BTLx +from compas_rhino.conversions import brep_to_rhino + from compas_rhino import unload_modules unload_modules("compas_timber") -step_joint_joint = TStepJoint(main_beam, cross_beam, step_depth, heel_depth, tapered_heel, tenon_mortise_height) -step_joint_joint.add_features() - +# Instantiate TimberModel & Add Beams timber_model = TimberModel() -for beam in step_joint_joint.beams: +beams = [cross_beam, main_beam] +for beam in beams: timber_model.add_beam(beam) -timber_model.add_joint(step_joint_joint, step_joint_joint.beams) +# Create TStepJoint joint +step_joint_joint = TStepJoint.create( + timber_model, + main_beam, + cross_beam, + step_depth=step_depth, + heel_depth=heel_depth, + tapered_heel=tapered_heel, + tenon_mortise_height=tenon_mortise_height + ) +# Generate Geometry +geometry = [brep_to_rhino(beam.compute_geometry()) for beam in timber_model.beams] +# Write BTLx btlx = BTLx(timber_model) -btlx.process_model() BTLx = btlx.btlx_string() GhPython provides a Python script component - 126 - 122 + 418 + 209 901 @@ -5918,7 +5934,7 @@ BTLx = btlx.btlx_string() 3506 915 - 223 + 203 124 @@ -6143,11 +6159,11 @@ BTLx = btlx.btlx_string() 3650 917 - 77 + 57 40 - 3688.5 + 3678.5 937 @@ -6156,10 +6172,10 @@ BTLx = btlx.btlx_string() - Script output timber_model. - b0d6f1d5-5017-448f-8e9f-d5b614d7e076 - timber_model - timber_model + Script output geometry. + 188326e4-b130-4230-9b7f-f89e9a5dca84 + geometry + geometry false 0 @@ -6169,11 +6185,11 @@ BTLx = btlx.btlx_string() 3650 957 - 77 + 57 40 - 3688.5 + 3678.5 977 @@ -6195,11 +6211,11 @@ BTLx = btlx.btlx_string() 3650 997 - 77 + 57 40 - 3688.5 + 3678.5 1017 @@ -6225,7 +6241,7 @@ BTLx = btlx.btlx_string() Panel false - 1 + 0 8674ed0a-6d18-41d1-9209-ae7aed0e558d 1 G:\Shared drives\2024_MAS\T2\03_finalization\Fabrication - Joints\BTLx\step_joint_joint_test.btlx @@ -6234,8 +6250,8 @@ BTLx = btlx.btlx_string() - 4213 - 938 + 3564 + 1119 446 420 @@ -6243,8 +6259,8 @@ BTLx = btlx.btlx_string() 0 0 - 4213.571 - 938.6143 + 3564.741 + 1119.944 @@ -6288,8 +6304,8 @@ BTLx = btlx.btlx_string() - 3995 - 1004 + 3350 + 1184 212 333 @@ -6297,8 +6313,8 @@ BTLx = btlx.btlx_string() 0 0 - 3995.862 - 1004.686 + 3350.94 + 1184.713 @@ -6341,8 +6357,8 @@ BTLx = btlx.btlx_string() - 4658 - 1009 + 4014 + 1190 212 333 @@ -6350,8 +6366,8 @@ BTLx = btlx.btlx_string() 0 0 - 4658.117 - 1009.509 + 4014.498 + 1190.838 @@ -6385,7 +6401,7 @@ BTLx = btlx.btlx_string() Panel false - 0.73333331942558289 + 0 fb496e59-6b43-4266-9c61-c466f3a89f8e 1 Double click to edit panel content… @@ -6396,14 +6412,14 @@ BTLx = btlx.btlx_string() 3505 862 - 224 + 204 53 0 0 0 - 3505.034 + 3505.031 862.5032 @@ -6432,7 +6448,7 @@ BTLx = btlx.btlx_string() - + 1 150;255;255;255 @@ -6442,7 +6458,15 @@ BTLx = btlx.btlx_string() d0efa1ab-d893-4335-8af7-dd6d94484ea7 9f3e0877-7fa6-4696-83ae-f6281f1d75f8 3c27b464-0da3-49d8-922b-780384eb4127 - 4 + ba663821-ec0e-4ff9-9124-fe98533cab66 + 97e9efc3-9035-45ee-bac9-e1c0876defe3 + 47c9ac3d-5335-4e4b-8cf3-b22cd1284a8e + e9fbb74f-6b87-4a1e-a684-8202331f1480 + f11ad7e4-429f-4eac-92db-ff79b89a4fa3 + f02972d5-c58c-4216-b53b-7f078d1f7fdc + 5b8e4ee4-cb19-450a-ba61-866a3686f2b2 + c6f9d24c-2b1e-4276-8e76-0fd2049e3d3a + 12 c6445b86-caf6-4180-b0be-5dcfc6eaea9a Group @@ -6463,19 +6487,19 @@ BTLx = btlx.btlx_string() true - 3482.384 + 3654.336 775.9541 - 3737.145 + 3909.097 775.9541 - 3737.145 + 3909.097 823.293 - 3482.384 + 3654.336 823.293 A quick note @@ -6491,13 +6515,13 @@ BTLx = btlx.btlx_string() - 3477.384 + 3649.336 770.9541 264.7607 57.33887 - 3482.384 + 3654.336 775.9541 @@ -6531,6 +6555,922 @@ BTLx = btlx.btlx_string() + + + 59daf374-bc21-4a5e-8282-5504fb7ae9ae + List Item + + + + + 0 + Retrieve a specific item from a list. + true + d87eb305-f0b0-4b4a-9ee0-a52b770164cc + List Item + Item + + + + + + 2943 + 371 + 64 + 64 + + + 2977 + 403 + + + + + + 3 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 2e3ab970-8545-46bb-836c-1c11e5610bce + cb95db89-6165-43b6-9c41-5702bc5bf137 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 1 + Base list + 3f880763-347f-478e-8e82-836a0cc3f94c + List + L + false + 482410b9-724c-46d6-be3a-3d63785bc853 + 1 + + + + + + 2945 + 373 + 17 + 20 + + + 2955 + 383 + + + + + + + + Item index + 6acddfe7-ca80-424f-a759-2f45f82bf107 + Index + i + false + 0 + + + + + + 2945 + 393 + 17 + 20 + + + 2955 + 403 + + + + + + 1 + + + + + 1 + {0} + + + + + 0 + + + + + + + + + + + Wrap index to list bounds + 3911a313-de41-48b4-8f20-9e6b901bc766 + Wrap + W + false + 0 + + + + + + 2945 + 413 + 17 + 20 + + + 2955 + 423 + + + + + + 1 + + + + + 1 + {0} + + + + + true + + + + + + + + + + + Item at {i'} + 0735b968-42f2-4221-86dc-b8eb7484f4e5 + false + Item + i + false + 0 + + + + + + 2992 + 373 + 13 + 60 + + + 2998.5 + 403 + + + + + + + + + + + + + + 537b0419-bbc2-4ff4-bf08-afe526367b2c + Custom Preview + + + + + Allows for customized geometry previews + true + false + ba663821-ec0e-4ff9-9124-fe98533cab66 + Custom Preview + Preview + + + + + + + 3998 + 1014 + 64 + 44 + + + 4048 + 1036 + + + + + + Geometry to preview + true + c34e1843-bfd6-4a82-b983-7194c9f9c18c + Geometry + G + false + e9fbb74f-6b87-4a1e-a684-8202331f1480 + 1 + + + + + + 4000 + 1016 + 33 + 20 + + + 4026 + 1026 + + + + + + + + The material override + 710c97a8-7564-4390-af09-089f9958717d + 1 + Material + M + false + d5779e9d-d95c-4279-bd0c-9036111f46eb + 1 + + + + + + 4000 + 1036 + 33 + 20 + + + 4026 + 1046 + + + + + + 1 + + + + + 1 + {0} + + + + + + 255;221;160;221 + + + 255;66;48;66 + + 0.5 + + 255;255;255;255 + + 0 + + + + + + + + + + + + + + + 0148a65d-6f42-414a-9db7-9a9b2eb78437 + Brep Edges + + + + + Extract the edge curves of a brep. + true + 97e9efc3-9035-45ee-bac9-e1c0876defe3 + Brep Edges + Edges + + + + + + 3892 + 944 + 72 + 64 + + + 3922 + 976 + + + + + + Base Brep + d76deb57-3846-4215-a6d3-b4cc65f5f623 + Brep + B + false + e9fbb74f-6b87-4a1e-a684-8202331f1480 + 1 + + + + + + 3894 + 946 + 13 + 60 + + + 3902 + 976 + + + + + + + + 1 + Naked edge curves + d6302059-08b9-4456-a7f4-fc4b73a03458 + Naked + En + false + 0 + + + + + + 3937 + 946 + 25 + 20 + + + 3949.5 + 956 + + + + + + + + 1 + Interior edge curves + 8db6122b-9378-4026-a86e-58c5dd430f2a + Interior + Ei + false + 0 + + + + + + 3937 + 966 + 25 + 20 + + + 3949.5 + 976 + + + + + + + + 1 + Non-Manifold edge curves + 0c2574f5-76b7-475f-80aa-c87143496df3 + Non-Manifold + Em + false + 0 + + + + + + 3937 + 986 + 25 + 20 + + + 3949.5 + 996 + + + + + + + + + + + + a77d0879-94c2-4101-be44-e4a616ffeb0c + 5f86fa9f-c62b-50e8-157b-b454ef3e00fa + Custom Preview Lineweights + + + + + Custom Preview with Lineweights + 47c9ac3d-5335-4e4b-8cf3-b22cd1284a8e + Custom Preview Lineweights + PreviewLW + + + + + + + 3998 + 924 + 62 + 84 + + + 4046 + 966 + + + + + + Geometry to preview + true + b386acb8-8321-49bf-ac12-f062d85c969d + Geometry + G + false + 8db6122b-9378-4026-a86e-58c5dd430f2a + 1 + + + + + + 4000 + 926 + 31 + 20 + + + 4025 + 936 + + + + + + + + The preview shader override + 11af4056-9698-4b92-b067-fae857f62252 + 2 + Shader + S + false + d5779e9d-d95c-4279-bd0c-9036111f46eb + 1 + + + + + + 4000 + 946 + 31 + 20 + + + 4025 + 956 + + + + + + 1 + + + + + 1 + {0} + + + + + + 255;255;105;180 + + + 255;76;32;54 + + 0.5 + + 255;255;255;255 + + 0 + + + + + + + + + + + The thickness of the wire display + f4113c88-a8ac-49cd-a656-a1e84f635519 + Thickness + T + true + 0 + + + + + + 4000 + 966 + 31 + 20 + + + 4025 + 976 + + + + + + + + Set to true to try to render curves with an absolute dimension. + 56fb3908-c9cb-478b-be37-49785e21754d + Absolute + A + false + 0 + + + + + + 4000 + 986 + 31 + 20 + + + 4025 + 996 + + + + + + 1 + + + + + 1 + {0} + + + + + false + + + + + + + + + + + + + + + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep + + + + + Contains a collection of Breps (Boundary REPresentations) + true + e9fbb74f-6b87-4a1e-a684-8202331f1480 + Brep + Brep + false + 188326e4-b130-4230-9b7f-f89e9a5dca84 + 1 + + + + + + 3805 + 965 + 50 + 24 + + + 3830.458 + 977.9061 + + + + + + + + + + 9c53bac0-ba66-40bd-8154-ce9829b9db1a + Colour Swatch + + + + + Colour (palette) swatch + f11ad7e4-429f-4eac-92db-ff79b89a4fa3 + Colour Swatch + Swatch + false + 0 + + 29;0;102;255 + + + + + + + 3788 + 1014 + 88 + 20 + + + 3788.823 + 1014.352 + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + ba663821-ec0e-4ff9-9124-fe98533cab66 + 97e9efc3-9035-45ee-bac9-e1c0876defe3 + 47c9ac3d-5335-4e4b-8cf3-b22cd1284a8e + e9fbb74f-6b87-4a1e-a684-8202331f1480 + f11ad7e4-429f-4eac-92db-ff79b89a4fa3 + 5b8e4ee4-cb19-450a-ba61-866a3686f2b2 + c6f9d24c-2b1e-4276-8e76-0fd2049e3d3a + 7 + f02972d5-c58c-4216-b53b-7f078d1f7fdc + Group + GEOMETRY + + + + + + + + + + 9c53bac0-ba66-40bd-8154-ce9829b9db1a + Colour Swatch + + + + + Colour (palette) swatch + 5b8e4ee4-cb19-450a-ba61-866a3686f2b2 + Colour Swatch + Swatch + false + 0 + + 34;240;180;137 + + + + + + + 3788 + 1038 + 88 + 20 + + + 3788.585 + 1038.001 + + + + + + + + + + c9785b8e-2f30-4f90-8ee3-cca710f82402 + Entwine + + + + + Flatten and combine a collection of data streams + true + c6f9d24c-2b1e-4276-8e76-0fd2049e3d3a + Entwine + Entwine + + + + + + 3891 + 1011 + 79 + 44 + + + 3936 + 1033 + + + + + + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 2 + Data to entwine + f1b60494-3bf8-4181-99bd-69acd3c3059a + false + Branch {0;0} + {0;0} + true + f11ad7e4-429f-4eac-92db-ff79b89a4fa3 + 1 + + + + + + 3893 + 1013 + 28 + 20 + + + 3908.5 + 1023 + + + + + + + + 2 + Data to entwine + 56b1c6dd-1a2e-4764-8c90-316095f2007a + false + Branch {0;1} + {0;1} + true + 5b8e4ee4-cb19-450a-ba61-866a3686f2b2 + 1 + + + + + + 3893 + 1033 + 28 + 20 + + + 3908.5 + 1043 + + + + + + + + Entwined result + d5779e9d-d95c-4279-bd0c-9036111f46eb + Result + R + false + 0 + + + + + + 3951 + 1013 + 17 + 40 + + + 3959.5 + 1033 + + + + + + + + + + + @@ -6538,7 +7478,7 @@ BTLx = btlx.btlx_string() - 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 From 0166cceb63c5da9b6145dfd584aefe4219408bb4 Mon Sep 17 00:00:00 2001 From: papachap Date: Mon, 12 Aug 2024 16:57:01 +0200 Subject: [PATCH 42/63] stepshape->stepshapetype --- .../_fabrication/btlx_process.py | 2 +- src/compas_timber/_fabrication/step_joint.py | 36 +++++++++---------- .../_fabrication/step_joint_notch.py | 32 ++++++++--------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/compas_timber/_fabrication/btlx_process.py b/src/compas_timber/_fabrication/btlx_process.py index e6405bec6..0bb929743 100644 --- a/src/compas_timber/_fabrication/btlx_process.py +++ b/src/compas_timber/_fabrication/btlx_process.py @@ -87,7 +87,7 @@ class OrientationType(object): END = "end" -class StepShape(object): +class StepShapeType(object): """Enum for the step shape of the cut. Attributes diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index 656d3bf02..b8ed1e86f 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -19,7 +19,7 @@ from .btlx_process import BTLxProcess from .btlx_process import BTLxProcessParams from .btlx_process import OrientationType -from .btlx_process import StepShape +from .btlx_process import StepShapeType class StepJoint(BTLxProcess): @@ -71,7 +71,7 @@ def __init__( strut_inclination=90.0, step_depth=20.0, heel_depth=20.0, - step_shape=StepShape.DOUBLE, + step_shape=StepShapeType.DOUBLE, tenon=False, tenon_width=40.0, tenon_height=40.0, @@ -162,9 +162,9 @@ def step_shape(self): @step_shape.setter # TODO: should this be defined automatically depending on the other parameters? (ie. if heel_depth > 0 and step_depth > 0, then step_shape = "double") def step_shape(self, step_shape): - if step_shape not in [StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL, StepShape.TAPERED_HEEL]: + if step_shape not in [StepShapeType.DOUBLE, StepShapeType.STEP, StepShapeType.HEEL, StepShapeType.TAPERED_HEEL]: raise ValueError( - "StepShape must be either StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL, or StepShape.TAPERED_HEEL." + "StepShapeType must be either StepShapeType.DOUBLE, StepShapeType.STEP, StepShapeType.HEEL, or StepShapeType.TAPERED_HEEL." ) self._step_shape = step_shape @@ -283,14 +283,14 @@ def _calculate_strut_inclination(ref_side, plane, orientation): def _define_step_shape(step_depth, heel_depth, tapered_heel): # step_shape based on step_depth and heel_depth and tapered_heel variables if step_depth > 0.0 and heel_depth == 0.0: - return StepShape.STEP + return StepShapeType.STEP elif step_depth == 0.0 and heel_depth > 0.0: if tapered_heel: - return StepShape.TAPERED_HEEL + return StepShapeType.TAPERED_HEEL else: - return StepShape.HEEL + return StepShapeType.HEEL elif step_depth > 0.0 and heel_depth > 0.0: - return StepShape.DOUBLE + return StepShapeType.DOUBLE else: raise ValueError("At least one of step_depth or heel_depth must be greater than 0.0.") @@ -352,7 +352,7 @@ def apply(self, geometry, beam): None, geometry, "Failed to generate cutting planes from parameters and beam: {}".format(str(e)) ) - if self.step_shape == StepShape.STEP: + if self.step_shape == StepShapeType.STEP: for cutting_plane in cutting_planes: cutting_plane.normal = cutting_plane.normal * -1 try: @@ -362,7 +362,7 @@ def apply(self, geometry, beam): cutting_plane, geometry, "Failed to trim geometry with cutting planes: {}".format(str(e)) ) - elif self.step_shape == StepShape.HEEL: + elif self.step_shape == StepShapeType.HEEL: trimmed_geometies = [] for cutting_plane in cutting_planes: cutting_plane.normal = cutting_plane.normal * -1 @@ -381,7 +381,7 @@ def apply(self, geometry, beam): trimmed_geometies, geometry, "Failed to union trimmed geometries: {}".format(str(e)) ) - elif self.step_shape == StepShape.TAPERED_HEEL: + elif self.step_shape == StepShapeType.TAPERED_HEEL: try: cutting_plane = cutting_planes[0] cutting_plane.normal = cutting_plane.normal * -1 @@ -391,7 +391,7 @@ def apply(self, geometry, beam): cutting_planes, geometry, "Failed to trim geometry with cutting plane: {}".format(str(e)) ) - elif self.step_shape == StepShape.DOUBLE: + elif self.step_shape == StepShapeType.DOUBLE: # trim geometry with last cutting plane cutting_planes[-1].normal = cutting_planes[-1].normal * -1 try: @@ -419,7 +419,7 @@ def apply(self, geometry, beam): trimmed_geometies, geometry, "Failed to union trimmed geometries: {}".format(str(e)) ) - if self.tenon and self.step_shape == StepShape.STEP: # TODO: check if tenon applies only to step in BTLx + if self.tenon and self.step_shape == StepShapeType.STEP: # TODO: check if tenon applies only to step in BTLx # create tenon volume and subtract from brep tenon_volume = self.tenon_volume_from_params_and_beam(beam) cutting_planes[0].normal = cutting_planes[0].normal * -1 @@ -510,13 +510,13 @@ def planes_from_params_and_beam(self, beam): else: rot_axis = -cutting_plane_ref.yaxis - if self.step_shape == StepShape.STEP: + if self.step_shape == StepShapeType.STEP: return self._calculate_step_planes(cutting_plane_ref, cutting_plane_opp, y_displacement_end, rot_axis) - elif self.step_shape == StepShape.HEEL: + elif self.step_shape == StepShapeType.HEEL: return self._calculate_heel_planes(cutting_plane_heel, cutting_plane_opp, rot_axis) - elif self.step_shape == StepShape.TAPERED_HEEL: + elif self.step_shape == StepShapeType.TAPERED_HEEL: return self._calculate_heel_tapered_planes(cutting_plane_opp, y_displacement_end, rot_axis) - elif self.step_shape == StepShape.DOUBLE: + elif self.step_shape == StepShapeType.DOUBLE: return self._calculate_double_planes( cutting_plane_heel, cutting_plane_opp, y_displacement_end, x_displacement_heel, rot_axis ) @@ -702,7 +702,7 @@ def as_dict(self): result["StrutInclination"] = "{:.{prec}f}".format(self._instance.strut_inclination, prec=TOL.precision) result["StepDepth"] = "{:.{prec}f}".format(self._instance.step_depth, prec=TOL.precision) result["HeelDepth"] = "{:.{prec}f}".format(self._instance.heel_depth, prec=TOL.precision) - result["StepShape"] = self._instance.step_shape + result["StepShapeType"] = self._instance.step_shape result["Tenon"] = "yes" if self._instance.tenon else "no" result["TenonWidth"] = "{:.{prec}f}".format(self._instance.tenon_width, prec=TOL.precision) result["TenonHeight"] = "{:.{prec}f}".format(self._instance.tenon_height, prec=TOL.precision) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index 38b90fc49..bd2b03a54 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -19,7 +19,7 @@ from .btlx_process import BTLxProcess from .btlx_process import BTLxProcessParams from .btlx_process import OrientationType -from .btlx_process import StepShape +from .btlx_process import StepShapeType class StepJointNotch(BTLxProcess): @@ -46,7 +46,7 @@ class StepJointNotch(BTLxProcess): strut_height : float The height of the strut. It is the cross beam's height. strut_height < 50000.0. step_shape : str - The shape of the step. Must be either StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL or StepShape.TAPERED_HEEL. + The shape of the step. Must be either StepShapeType.DOUBLE, StepShapeType.STEP, StepShapeType.HEEL or StepShapeType.TAPERED_HEEL. mortise : str The presence of a mortise. Must be either 'no' or 'yes'. mortise_width : float @@ -87,7 +87,7 @@ def __init__( step_depth=20.0, heel_depth=20.0, strut_height=20.0, - step_shape=StepShape.DOUBLE, + step_shape=StepShapeType.DOUBLE, mortise=False, mortise_width=40.0, mortise_height=40.0, @@ -226,9 +226,9 @@ def step_shape(self): @step_shape.setter def step_shape(self, step_shape): - if step_shape not in [StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL, StepShape.TAPERED_HEEL]: + if step_shape not in [StepShapeType.DOUBLE, StepShapeType.STEP, StepShapeType.HEEL, StepShapeType.TAPERED_HEEL]: raise ValueError( - "StepShape must be either StepShape.DOUBLE, StepShape.STEP, StepShape.HEEL or StepShape.TAPERED_HEEL." + "StepShapeType must be either StepShapeType.DOUBLE, StepShapeType.STEP, StepShapeType.HEEL or StepShapeType.TAPERED_HEEL." ) self._step_shape = step_shape @@ -389,14 +389,14 @@ def _calculate_strut_inclination(ref_side, plane, orientation): def _define_step_shape(step_depth, heel_depth, tapered_heel): # step_shape based on step_depth and heel_depth and tapered_heel variables if step_depth > 0.0 and heel_depth == 0.0: - return StepShape.STEP + return StepShapeType.STEP elif step_depth == 0.0 and heel_depth > 0.0: if tapered_heel: - return StepShape.TAPERED_HEEL + return StepShapeType.TAPERED_HEEL else: - return StepShape.HEEL + return StepShapeType.HEEL elif step_depth > 0.0 and heel_depth > 0.0: - return StepShape.DOUBLE + return StepShapeType.DOUBLE else: raise ValueError("At least one of step_depth or heel_depth must be greater than 0.0.") @@ -463,7 +463,7 @@ def apply(self, geometry, beam): subtraction_box.translate(-ref_side.frame.zaxis * max(self.step_depth, self.heel_depth)) subtraction_volume = Brep.from_box(subtraction_box) - if self.step_shape == StepShape.DOUBLE: + if self.step_shape == StepShapeType.DOUBLE: # trim geometry with first and last cutting plane try: for cutting_plane in [cutting_planes[-1], cutting_planes[0]]: @@ -501,7 +501,7 @@ def apply(self, geometry, beam): "Failed to trim geometry with cutting planes: {}".format(str(e)), ) - if self.mortise and self.step_shape == StepShape.STEP: # TODO: check if mortise applies only to step in BTLx + if self.mortise and self.step_shape == StepShapeType.STEP: # TODO: check if mortise applies only to step in BTLx # create mortise volume and subtract from brep mortise_volume = self.mortise_volume_from_params_and_beam(beam) # trim mortise volume with cutting plane @@ -577,13 +577,13 @@ def planes_from_params_and_beam(self, beam): # Start with a plane aligned with the ref side but shifted to the start of the first cut ref_side = beam.side_as_surface(self.ref_side_index) - if self.step_shape == StepShape.STEP: + if self.step_shape == StepShapeType.STEP: return self._calculate_step_planes(ref_side) - elif self.step_shape == StepShape.HEEL: + elif self.step_shape == StepShapeType.HEEL: return self._calculate_heel_planes(ref_side) - elif self.step_shape == StepShape.TAPERED_HEEL: + elif self.step_shape == StepShapeType.TAPERED_HEEL: return self._calculate_tapered_heel_planes(ref_side) - elif self.step_shape == StepShape.DOUBLE: + elif self.step_shape == StepShapeType.DOUBLE: return self._calculate_double_planes(ref_side) def _calculate_step_planes(self, ref_side): @@ -867,7 +867,7 @@ def as_dict(self): result["StepDepth"] = "{:.{prec}f}".format(self._instance.step_depth, prec=TOL.precision) result["HeelDepth"] = "{:.{prec}f}".format(self._instance.heel_depth, prec=TOL.precision) result["StrutHeight"] = "{:.{prec}f}".format(self._instance.strut_height, prec=TOL.precision) - result["StepShape"] = self._instance.step_shape + result["StepShapeType"] = self._instance.step_shape result["Mortise"] = "yes" if self._instance.mortise else "no" result["MortiseWidth"] = "{:.{prec}f}".format(self._instance.mortise_width, prec=TOL.precision) result["MortiseHeight"] = "{:.{prec}f}".format(self._instance.mortise_height, prec=TOL.precision) From 10d8bd5bcd2a01ff23cda994bced26d346b51877 Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 13 Aug 2024 09:39:33 +0200 Subject: [PATCH 43/63] revert back to beam_side_incidence and write a new one for using ref_sides --- src/compas_timber/connections/joint.py | 52 +++++++++++++++++++ src/compas_timber/connections/t_step_joint.py | 4 +- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/compas_timber/connections/joint.py b/src/compas_timber/connections/joint.py index c9e5d6171..f0763a9ae 100644 --- a/src/compas_timber/connections/joint.py +++ b/src/compas_timber/connections/joint.py @@ -196,6 +196,58 @@ def get_face_most_ortho_to_beam(beam_a, beam_b, ignore_ends=True): @staticmethod def _beam_side_incidence(beam_a, beam_b, ignore_ends=True): + """Returns a map of face indices of beam_b and the angle of their normal with beam_a's centerline. + + This is used to find a cutting plane when joining the two beams. + + Parameters + ---------- + beam_a : :class:`~compas_timber.parts.Beam` + The beam that attaches with one of its ends to the side of beam_b. + beam_b : :class:`~compas_timber.parts.Beam` + The other beam. + ignore_ends : bool, optional + If True, only the first four faces of `beam_b` are considered. Otherwise all faces are considered. + + Examples + -------- + >>> face_angles = Joint.beam_side_incidence(beam_a, beam_b) + >>> closest_face_index = min(face_angles, key=face_angles.get) + >>> cutting_plane = beam_b.faces[closest_face_index] + + Returns + ------- + dict(int, float) + A map of face indices of beam_b and their respective angle with beam_a's centerline. + + """ + # find the orientation of beam_a's centerline so that it's pointing outward of the joint + # find the closest end + p1x, _ = intersection_line_line(beam_a.centerline, beam_b.centerline) + if p1x is None: + raise AssertionError("No intersection found") + + end, _ = beam_a.endpoint_closest_to_point(Point(*p1x)) + + if end == "start": + centerline_vec = beam_a.centerline.vector + else: + centerline_vec = beam_a.centerline.vector * -1 + + if ignore_ends: + beam_b_faces = beam_b.faces[:4] + else: + beam_b_faces = beam_b.faces + + face_angles = {} + for face_index, face in enumerate(beam_b_faces): + face_angles[face_index] = angle_vectors(face.normal, centerline_vec) + + return face_angles + + @staticmethod + def _beam_ref_side_incidence(beam_a, beam_b, ignore_ends=True): + # compared to beam_side_incidence, this function considers the ref_sides and not faces and forms part of the transition to the new system """Returns a map of ref_side indices of beam_b and the angle of their normal with beam_a's centerline. This is used to find a cutting plane when joining the two beams. diff --git a/src/compas_timber/connections/t_step_joint.py b/src/compas_timber/connections/t_step_joint.py index 022aab7c9..82dfdfea8 100644 --- a/src/compas_timber/connections/t_step_joint.py +++ b/src/compas_timber/connections/t_step_joint.py @@ -94,13 +94,13 @@ def beams(self): @property def cross_beam_ref_side_index(self): - ref_side_dict = self._beam_side_incidence(self.main_beam, self.cross_beam, ignore_ends=True) + ref_side_dict = self._beam_ref_side_incidence(self.main_beam, self.cross_beam, ignore_ends=True) ref_side_index = min(ref_side_dict, key=ref_side_dict.get) return ref_side_index @property def main_beam_ref_side_index(self): - ref_side_dict = self._beam_side_incidence(self.cross_beam, self.main_beam, ignore_ends=True) + ref_side_dict = self._beam_ref_side_incidence(self.cross_beam, self.main_beam, ignore_ends=True) ref_side_index = min(ref_side_dict, key=ref_side_dict.get) return ref_side_index From 3c17231b7a084fbece93cf2dd35cc24c79b595aa Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 13 Aug 2024 09:41:50 +0200 Subject: [PATCH 44/63] format --- src/compas_timber/_fabrication/step_joint_notch.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index bd2b03a54..6c2a0f345 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -501,7 +501,9 @@ def apply(self, geometry, beam): "Failed to trim geometry with cutting planes: {}".format(str(e)), ) - if self.mortise and self.step_shape == StepShapeType.STEP: # TODO: check if mortise applies only to step in BTLx + if ( + self.mortise and self.step_shape == StepShapeType.STEP + ): # TODO: check if mortise applies only to step in BTLx # create mortise volume and subtract from brep mortise_volume = self.mortise_volume_from_params_and_beam(beam) # trim mortise volume with cutting plane From b43c34414e71b545edc05f83c9c0aa025279d186 Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 14 Aug 2024 10:07:41 +0200 Subject: [PATCH 45/63] flip height and width --- src/compas_timber/fabrication/btlx.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index dd470dab1..53b309d7f 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -217,23 +217,23 @@ def reference_surfaces(self): return ( Frame(self.frame.point, self.frame.xaxis, self.frame.zaxis), # Frame( - self.frame.point + self.frame.yaxis * self.width, + self.frame.point + self.frame.yaxis * self.height, self.frame.xaxis, -self.frame.yaxis, ), 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, ), Frame( - self.frame.point + self.frame.zaxis * self.height, + self.frame.point + self.frame.zaxis * self.width, self.frame.xaxis, self.frame.yaxis, ), Frame(self.frame.point, self.frame.zaxis, self.frame.yaxis), Frame( - self.frame.point + self.frame.xaxis * self.blank_length + self.frame.yaxis * self.width, + self.frame.point + self.frame.xaxis * self.blank_length + self.frame.yaxis * self.height, self.frame.zaxis, -self.frame.yaxis, ), From c82285aa1760078a0d53affd14daa6f07ae445d0 Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 14 Aug 2024 10:18:55 +0200 Subject: [PATCH 46/63] match beam faces to BTLxPart reference_surfaces --- src/compas_timber/elements/beam.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/compas_timber/elements/beam.py b/src/compas_timber/elements/beam.py index d11dedff5..84c6847f9 100644 --- a/src/compas_timber/elements/beam.py +++ b/src/compas_timber/elements/beam.py @@ -152,31 +152,31 @@ def faces(self): # type: () -> list[Frame] assert self.frame return [ - Frame( - Point(*add_vectors(self.midpoint, self.frame.yaxis * self.width * 0.5)), - self.frame.xaxis, - -self.frame.zaxis, - ), Frame( Point(*add_vectors(self.midpoint, -self.frame.zaxis * self.height * 0.5)), self.frame.xaxis, -self.frame.yaxis, ), Frame( - Point(*add_vectors(self.midpoint, -self.frame.yaxis * self.width * 0.5)), + Point(*add_vectors(self.midpoint, self.frame.yaxis * self.width * 0.5)), self.frame.xaxis, - self.frame.zaxis, + -self.frame.zaxis, ), Frame( Point(*add_vectors(self.midpoint, self.frame.zaxis * self.height * 0.5)), self.frame.xaxis, self.frame.yaxis, ), + Frame( + Point(*add_vectors(self.midpoint, -self.frame.yaxis * self.width * 0.5)), + self.frame.xaxis, + self.frame.zaxis, + ), Frame(self.frame.point, -self.frame.yaxis, self.frame.zaxis), # small face at start point Frame( Point(*add_vectors(self.frame.point, self.frame.xaxis * self.length)), - self.frame.yaxis, - self.frame.zaxis, + -self.frame.yaxis, + -self.frame.zaxis, ), # small face at end point ] @@ -431,7 +431,10 @@ def add_features(self, features): """ if not isinstance(features, list): features = [features] - self.features.extend(features) # type: ignore + for feature in features: + if feature not in self.features: + self.features.append(feature) + # self.features.extend(features) # type: ignore @reset_computed def remove_features(self, features=None): From 45589effb4da8ad109af10a37256754c154a52a6 Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 14 Aug 2024 10:23:30 +0200 Subject: [PATCH 47/63] adjust unittest to i+1 aligning beam.faces to btlxpart.reference_surfaces --- tests/compas_timber/test_btlx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/compas_timber/test_btlx.py b/tests/compas_timber/test_btlx.py index 874678ffc..bad0ffa47 100644 --- a/tests/compas_timber/test_btlx.py +++ b/tests/compas_timber/test_btlx.py @@ -31,9 +31,9 @@ def test_beam_ref_faces(mock_beam): # https://www.design2machine.com/btlx/btlx_20.pdf page 5 btlx_part = BTLxPart(mock_beam, 0) - assert btlx_part.ref_side_from_face(mock_beam.faces[0]) == 3 + assert btlx_part.ref_side_from_face(mock_beam.faces[0]) == 1 assert btlx_part.ref_side_from_face(mock_beam.faces[1]) == 2 - assert btlx_part.ref_side_from_face(mock_beam.faces[2]) == 1 + assert btlx_part.ref_side_from_face(mock_beam.faces[2]) == 3 assert btlx_part.ref_side_from_face(mock_beam.faces[3]) == 4 assert btlx_part.ref_side_from_face(mock_beam.faces[4]) == 5 assert btlx_part.ref_side_from_face(mock_beam.faces[5]) == 6 From ba782f7326618e74eb8c3649f173ed9486fe38e8 Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 14 Aug 2024 10:49:02 +0200 Subject: [PATCH 48/63] remove ghx and fill change log --- CHANGELOG.md | 16 + .../gh_tests/test_step_joint_notch.ghx | 4616 ----------------- 2 files changed, 16 insertions(+), 4616 deletions(-) delete mode 100644 tests/compas_timber/gh_tests/test_step_joint_notch.ghx diff --git a/CHANGELOG.md b/CHANGELOG.md index 66df7bd2d..e163b3dc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +* Added new `compas_timber._fabrication.StepJoint`. +* Aded new `compas_timber._fabrication.StepJointNotch`. +* Aded new `compas_timber.connections.TStepJoint`. +* Added `side_as_surface` to `compas_timber.elements.Beam`. + +### Changed +* Adjusted the `faces` attribute of `Beam` to match the `reference_surfaces` of the `BTLxPart` +* Fixed the discrepancies between `height` and `width` attributes of `Beam` + +### Removed + + ## Unreleased ### Added diff --git a/tests/compas_timber/gh_tests/test_step_joint_notch.ghx b/tests/compas_timber/gh_tests/test_step_joint_notch.ghx deleted file mode 100644 index 9d8ba94d0..000000000 --- a/tests/compas_timber/gh_tests/test_step_joint_notch.ghx +++ /dev/null @@ -1,4616 +0,0 @@ - - - - - - - - 0 - 2 - 2 - - - - - - - 1 - 0 - 7 - - - - - - fa5d732c-b31a-4064-bc30-fdd75169872b - Shaded - 1 - - 100;150;0;0 - - - 100;0;150;0 - - - - - - 638584536051543966 - - test_step_joint_notch.ghx - - - - - 0 - - - - - - -21 - 17 - - 0.246093154 - - - - - 0 - - - - - - - 0 - - - - - 2 - - - - - GhPython, Version=7.34.23267.11001, Culture=neutral, PublicKeyToken=null - 7.34.23267.11001 - - 00000000-0000-0000-0000-000000000000 - - - - - - - Human, Version=1.7.3.0, Culture=neutral, PublicKeyToken=null - 1.7.3.0 - - 5f86fa9f-c62b-50e8-157b-b454ef3e00fa - Human - 1.2.0 - - - - - - - 45 - - - - - 410755b1-224a-4c1e-a407-bf32fb45ea7e - 00000000-0000-0000-0000-000000000000 - GhPython Script - - - - - from compas_rhino.conversions import surface_to_rhino -from compas_timber._fabrication import StepJointNotch -from compas_timber._fabrication import StepJointNotchParams - -from compas_timber.model import TimberModel -from compas_timber.fabrication import BTLx - -from compas_rhino import unload_modules -from compas.scene import SceneObject - -from compas_rhino.conversions import frame_to_rhino, plane_to_rhino, surface_to_rhino, polyline_to_rhino - -import Rhino.Geometry as rg -unload_modules("compas_timber") - -#define beam and cutting plane -beam = cross_beam -plane = main_beam.ref_sides[index] - -#create step_joint_notch -step_joint_notch = StepJointNotch.from_surface_and_beam(plane, beam, notch_limited=False, step_depth=step, heel_depth=heel, strut_height=100.0, tapered_heel=tapered, ref_side_index=ref_side_index) -cutting_planes = step_joint_notch.planes_from_params_and_beam(beam) - -print("Orientation: ", step_joint_notch.orientation) -print("StartX: ", step_joint_notch.start_x) -print("StrutInclination: ", step_joint_notch.strut_inclination) - - -#add mortise -if mortise_height > 0: - step_joint_notch.add_mortise(beam.width/4, mortise_height, beam) - mortise_polylines = step_joint_notch.mortise_volume_from_params_and_beam(beam) - -##apply geometric features -#step_joint_notch.apply(brep, beam) - -#get btlx params -step_joint_notch_params = StepJointNotchParams(step_joint_notch).as_dict() -btlx_params = [] -for key, value in step_joint_notch_params.items(): - btlx_params.append("{0}: {1}".format(key, value)) - - -#vizualize in rhino -rg_ref_side = frame_to_rhino(beam.ref_sides[ref_side_index]) -rg_cutting_plane = frame_to_rhino(plane) -rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - -rg_mortise_polylines = [polyline_to_rhino(polyline) for polyline in mortise_polylines] - - - - GhPython provides a Python script component - - 154 - 194 - - - 1232 - 796 - - true - true - false - false - ca34b946-50be-42d7-8e6d-080419aa1aba - false - true - GhPython Script - Python - - - - - - 1987 - 695 - 222 - 164 - - - 2083 - 777 - - - - - - 8 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 7 - 3ede854e-c753-40eb-84cb-b48008f14fd4 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - - - - - true - Script variable Python - 050b78a7-8649-45a7-8e2b-9f2c8fe617b8 - cross_beam - cross_beam - true - 0 - true - c38af54d-dfec-411f-ab38-22c657830b82 - 1 - 87f87f55-5b71-41f4-8aea-21d494016f81 - - - - - - 1989 - 697 - 79 - 20 - - - 2030 - 707 - - - - - - - - true - Script input main_beam. - 85e336ca-36fd-4257-ae26-80a079e9d946 - main_beam - main_beam - true - 0 - true - 19ba4d57-8c61-4bac-90d7-412dfcb8ac0b - 1 - 87f87f55-5b71-41f4-8aea-21d494016f81 - - - - - - 1989 - 717 - 79 - 20 - - - 2030 - 727 - - - - - - - - true - Script input index. - 63012ab7-70b5-4b48-adbc-da1744ff7b8e - index - index - true - 0 - true - c18f9e62-d6f5-42c4-a426-10688f24ca61 - 1 - 48d01794-d3d8-4aef-990e-127168822244 - - - - - - 1989 - 737 - 79 - 20 - - - 2030 - 747 - - - - - - - - true - Script input ref_side_index. - a5edb702-630e-4672-9612-89a6f43ae14c - ref_side_index - ref_side_index - true - 0 - true - fd67193c-c124-4e9b-bf38-3d6a6e085438 - 1 - 48d01794-d3d8-4aef-990e-127168822244 - - - - - - 1989 - 757 - 79 - 20 - - - 2030 - 767 - - - - - - - - true - Script input step. - 7d77a23a-b3b1-4179-80e8-6045dbf22259 - step - step - true - 0 - true - d5f970b8-28fa-4f28-b038-8f3eed5c682d - 1 - 87f87f55-5b71-41f4-8aea-21d494016f81 - - - - - - 1989 - 777 - 79 - 20 - - - 2030 - 787 - - - - - - - - true - Script input heel. - 62418504-ee4a-4be5-84d5-25d7f04b2671 - heel - heel - true - 0 - true - c1e88145-a742-40e8-9b8b-a549422a646d - 1 - 87f87f55-5b71-41f4-8aea-21d494016f81 - - - - - - 1989 - 797 - 79 - 20 - - - 2030 - 807 - - - - - - - - true - Script input tapered. - 1ff95643-005d-47e6-a338-fca927af62e5 - tapered - tapered - true - 0 - true - c925fa54-d90e-456f-863a-2f4f0a2857ba - 1 - 87f87f55-5b71-41f4-8aea-21d494016f81 - - - - - - 1989 - 817 - 79 - 20 - - - 2030 - 827 - - - - - - - - true - Script input mortise_height. - 9355bd0c-5761-49ff-8c36-c610f66d7569 - mortise_height - mortise_height - true - 0 - true - 654077fb-9447-42ca-a4ed-d6a4dd8d1874 - 1 - 87f87f55-5b71-41f4-8aea-21d494016f81 - - - - - - 1989 - 837 - 79 - 20 - - - 2030 - 847 - - - - - - - - The execution information, as output and error streams - 40308c84-a0ba-4c41-b158-38a3341beb2c - out - out - false - 0 - - - - - - 2098 - 697 - 109 - 22 - - - 2152.5 - 708.4286 - - - - - - - - Script output surface. - 43d74bb6-8d1a-4853-8757-d522804215d3 - surface - surface - false - 0 - - - - - - 2098 - 719 - 109 - 23 - - - 2152.5 - 731.2857 - - - - - - - - Script output rg_cutting_plane. - 602ccbf4-1274-4cd9-8349-ba4c5ba030fe - rg_cutting_plane - rg_cutting_plane - false - 0 - - - - - - 2098 - 742 - 109 - 23 - - - 2152.5 - 754.1429 - - - - - - - - Script output rg_planes. - 16bc55f9-a035-4099-834e-e9dc0931b695 - rg_planes - rg_planes - false - 0 - - - - - - 2098 - 765 - 109 - 23 - - - 2152.5 - 777 - - - - - - - - Script output rg_ref_side. - ddc1d959-4491-4f72-b7d5-a74d74b99385 - rg_ref_side - rg_ref_side - false - 0 - - - - - - 2098 - 788 - 109 - 23 - - - 2152.5 - 799.8572 - - - - - - - - Script output rg_mortise_polylines. - 1a4781d4-bfe8-4573-86bb-90d0f0ffc197 - rg_mortise_polylines - rg_mortise_polylines - false - 0 - - - - - - 2098 - 811 - 109 - 23 - - - 2152.5 - 822.7143 - - - - - - - - Script output btlx_params. - d525eccb-5bff-4563-b75e-472cbbdc5901 - step_joint_notch_params - btlx_params - false - 0 - - - - - - 2098 - 834 - 109 - 22 - - - 2152.5 - 845.5714 - - - - - - - - - - - - - - d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 - Curve - - - - - Contains a collection of generic curves - true - 6bbc142b-2536-41bf-a215-ea52704d32b6 - Curve - Crv - false - 0 - - - - - - 521 - 459 - 50 - 24 - - - 546.5278 - 471.428 - - - - - - 1 - - - - - 1 - {0} - - - - - -1 - - Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesYmekbGRmbmeoaGBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS9f/4eDiZgQx+EDEViJl+1TMxdEL99fs/E4MIlP3synU/95eXBfc/ZRBoZlT64Ddn9+0/QPlAqLwAyNzYbxpcrDPvOXz8/18+rHjlAYmi2O741CCH48eNlj7zvu9wBCoe0mqltvDcBAcmBgTY2xy1xs52sgPIJY3vmdz+1zM1wOS4GQYTAAA= - - 00000000-0000-0000-0000-000000000000 - - - - - - - - - - - - - 410755b1-224a-4c1e-a407-bf32fb45ea7e - 00000000-0000-0000-0000-000000000000 - CT: Beam - - - - - """Creates a Beam from a LineCurve.""" - -import rhinoscriptsyntax as rs -from compas.scene import Scene -from compas_rhino.conversions import line_to_compas -from compas_rhino.conversions import vector_to_compas -from ghpythonlib.componentbase import executingcomponent as component -from Grasshopper.Kernel.GH_RuntimeMessageLevel import Error -from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning -from Rhino.RhinoDoc import ActiveDoc - -from compas_timber.elements import Beam as CTBeam -from compas_timber.ghpython.rhino_object_name_attributes import update_rhobj_attributes_name - - -class Beam_fromCurve(component): - def RunScript(self, centerline, z_vector, width, height, category, updateRefObj): - # minimum inputs required - if not centerline: - self.AddRuntimeMessage(Warning, "Input parameter 'Centerline' failed to collect data") - if not width: - self.AddRuntimeMessage(Warning, "Input parameter 'Width' failed to collect data") - if not height: - self.AddRuntimeMessage(Warning, "Input parameter 'Height' failed to collect data") - - # reformat unset parameters for consistency - if not z_vector: - z_vector = [None] - if not category: - category = [None] - - beams = [] - blanks = [] - scene = Scene() - - if centerline and height and width: - # check list lengths for consistency - N = len(centerline) - if len(z_vector) not in (0, 1, N): - self.AddRuntimeMessage( - Error, " In 'ZVector' I need either none, one or the same number of inputs as the Crv parameter." - ) - if len(width) not in (1, N): - self.AddRuntimeMessage( - Error, " In 'W' I need either one or the same number of inputs as the Crv parameter." - ) - if len(height) not in (1, N): - self.AddRuntimeMessage( - Error, " In 'H' I need either one or the same number of inputs as the Crv parameter." - ) - if len(category) not in (0, 1, N): - self.AddRuntimeMessage( - Error, " In 'Category' I need either none, one or the same number of inputs as the Crv parameter." - ) - - # duplicate data if None or single value - if len(z_vector) != N: - z_vector = [z_vector[0] for _ in range(N)] - if len(width) != N: - width = [width[0] for _ in range(N)] - if len(height) != N: - height = [height[0] for _ in range(N)] - if len(category) != N: - category = [category[0] for _ in range(N)] - - for line, z, w, h, c in zip(centerline, z_vector, width, height, category): - guid, geometry = self._get_guid_and_geometry(line) - rhino_line = rs.coerceline(geometry) - line = line_to_compas(rhino_line) - - z = vector_to_compas(z) if z else None - 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)) - update_rhobj_attributes_name(guid, "zvector", str(list(beam.frame.zaxis))) - update_rhobj_attributes_name(guid, "category", c) - - beams.append(beam) - scene.add(beam.blank) - - blanks = scene.draw() - - return beams, blanks - - def _get_guid_and_geometry(self, line): - # internalized curves and GH geometry will not have persistent GUIDs, referenced Rhino objects will - # type hint on the input has to be 'ghdoc' for this to work - guid = None - geometry = line - rhino_obj = ActiveDoc.Objects.FindId(line) - if rhino_obj: - guid = line - geometry = rhino_obj.Geometry - return guid, geometry - - Creates a Beam from a LineCurve. - true - true - - iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDwAACw8BkvkDpQAAAhRJREFUSEulVLtKA0EUzSfkD3zlrUKMIcHGB6ghIj5QxEoDiiA2gtqGYBEfRRQiSRc7iXU6IWKZwh8Q/AI/wEcRr/cMmWUZ7ya7Whw2uXvvOTNnzqyPiP6Nob7+Q0bBQJ0xLQ64BRP4R6OxZ37Swf4+nRwdKyxmFyg4MEhcvxYH3YCH4+GhwHsqMd6+r9ep1WrRY7OpyGcmp2hvZxcCBXG4F3gQligykIIcIulkknJb26qGnXgW4AF/NBi6AzkIQAxcnl+AjAr5vFXzLMDN8ZFI9JXxfVMuW0RYMZ8D3dZqVs2zADeuRALBD3jbaDQUAZ74D2ib/iQwHI5UudHyFsNYLVaN5JjEGj0F+KUVQXisB+EzyO01CV0F+IUYwY21dZUUXXMCemGdKMBFMYIYgIDktx2wD0FAILBQO7FjBGGJveYE2Id5Jn8AH3g1uRhBHCLI7TUJ2BVCAHLuP9eLVgJc3GS/vyZSaboqldQWq5UKZeczyhYdSyfouCLGzLViJ9cCT1A2sbq03NNv7Ay7joXCLzzTb5JbAqa/mdm5XzfTRCeG1Dk35bcEUQAJchLArpAmkDMOJVI7PAkgrrgbuCM8F5cITbgWQFyHI9F2x29HS0y4EkBcuU/5LZF0Q1cB+I3fHMFP7stJBL3gKHBWLOoIvnGPK78lKAEQQkQjOZZQlnS+pq79lgABfNxw2UycSgPeQL4fsKzVEEQlKEcAAAAASUVORK5CYII= - - false - 61e455d1-1197-42f2-9b19-c9255984e680 - true - true - CT: Beam - Beam - - - - - - 1076 - 480 - 145 - 124 - - - 1167 - 542 - - - - - - 6 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 2 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - - - - - 1 - true - Referenced curve or line, Guid of curve or line in the active Rhino document. - af8ecabe-1f50-44f9-9732-a28f0101da66 - Centerline - Centerline - true - 1 - true - 82ddb61d-d21e-49ce-a61c-851130becb9a - 1 - 87f87f55-5b71-41f4-8aea-21d494016f81 - - - - - - 1078 - 482 - 74 - 20 - - - 1116.5 - 492 - - - - - - - - 1 - true - Vector defining the orientation of the cross-section (corresponds to the height dimension). If None, a default will be taken. - e24b6965-6eb8-44c9-a93f-921178776153 - ZVector - ZVector - true - 1 - true - 0 - 15a50725-e3d3-4075-9f7c-142ba5f40747 - - - - - - 1078 - 502 - 74 - 20 - - - 1116.5 - 512 - - - - - - - - 1 - true - Width of the cross-section (usually the smaller dimension). - 3141853d-6310-4d57-8fcb-4485e2106585 - Width - Width - true - 1 - true - 5cc940ad-f898-479c-8389-be3142764477 - 1 - 39fbc626-7a01-46ab-a18e-ec1c0c41685b - - - - - - 1078 - 522 - 74 - 20 - - - 1116.5 - 532 - - - - - - - - 1 - true - Height of the cross-section (usually the larger dimension). - 649d67fb-cb07-4166-b6a6-1412ba66780a - Height - Height - true - 1 - true - 5d71a959-57a2-4f08-b6ec-584164b09c95 - 1 - 39fbc626-7a01-46ab-a18e-ec1c0c41685b - - - - - - 1078 - 542 - 74 - 20 - - - 1116.5 - 552 - - - - - - - - 1 - true - Category of a beam. - e7afb315-d6b9-4da4-8b6f-5c05aa8b1895 - Category - Category - true - 1 - true - 0 - 37261734-eec7-4f50-b6a8-b8d1f3c4396b - - - - - - 1078 - 562 - 74 - 20 - - - 1116.5 - 572 - - - - - - - - true - (optional) If True, the attributes in the referenced object will be updated/overwritten with the new values given here. - b9a3c1d6-5e9a-407b-8be2-e4e66ceda80e - updateRefObj - updateRefObj - true - 0 - true - 0 - d60527f5-b5af-4ef6-8970-5f96fe412559 - - - - - - 1078 - 582 - 74 - 20 - - - 1116.5 - 592 - - - - - - - - Beam object(s). - 19ba4d57-8c61-4bac-90d7-412dfcb8ac0b - Beam - Beam - false - 0 - - - - - - 1182 - 482 - 37 - 60 - - - 1200.5 - 512 - - - - - - - - Shape of the beam's blank. - 7ccf81c5-136d-420f-b550-4a331abbcb49 - Blank - Blank - false - 0 - - - - - - 1182 - 542 - 37 - 60 - - - 1200.5 - 572 - - - - - - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - 5d71a959-57a2-4f08-b6ec-584164b09c95 - Number Slider - - false - 0 - - - - - - 853 - 550 - 166 - 20 - - - 853.4926 - 550.1252 - - - - - - 3 - 1 - 1 - 100 - 0 - 0 - 100 - - - - - - - - - d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 - Curve - - - - - Contains a collection of generic curves - true - 2f78b16d-80d1-4502-954e-49040fe761a3 - Curve - Crv - false - 0 - - - - - - 472 - 684 - 50 - 24 - - - 497.5087 - 696.4502 - - - - - - 1 - - - - - 1 - {0} - - - - - -1 - - Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesYmekbGRmbmeoaGBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS9f/4eDiZgQx+EDEViJl+1TMxdEL99fs/E4MIlP3synU/95eXBfc/ZRBoZlT64Ddn9+0/QPlAqLwAyFyF5Ln1s/nvOXz8/18+rHjlAYmi2O741CCHCRvP7T9b98DhM5o4EwMaKFjuAHKJ94LooP/1TA0wYW50dQMKAA== - - 00000000-0000-0000-0000-000000000000 - - - - - - - - - - - - - 410755b1-224a-4c1e-a407-bf32fb45ea7e - 00000000-0000-0000-0000-000000000000 - CT: Beam - - - - - """Creates a Beam from a LineCurve.""" - -import rhinoscriptsyntax as rs -from compas.scene import Scene -from compas_rhino.conversions import line_to_compas -from compas_rhino.conversions import vector_to_compas -from ghpythonlib.componentbase import executingcomponent as component -from Grasshopper.Kernel.GH_RuntimeMessageLevel import Error -from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning -from Rhino.RhinoDoc import ActiveDoc - -from compas_timber.elements import Beam as CTBeam -from compas_timber.ghpython.rhino_object_name_attributes import update_rhobj_attributes_name - - -class Beam_fromCurve(component): - def RunScript(self, centerline, z_vector, width, height, category, updateRefObj): - # minimum inputs required - if not centerline: - self.AddRuntimeMessage(Warning, "Input parameter 'Centerline' failed to collect data") - if not width: - self.AddRuntimeMessage(Warning, "Input parameter 'Width' failed to collect data") - if not height: - self.AddRuntimeMessage(Warning, "Input parameter 'Height' failed to collect data") - - # reformat unset parameters for consistency - if not z_vector: - z_vector = [None] - if not category: - category = [None] - - beams = [] - blanks = [] - scene = Scene() - - if centerline and height and width: - # check list lengths for consistency - N = len(centerline) - if len(z_vector) not in (0, 1, N): - self.AddRuntimeMessage( - Error, " In 'ZVector' I need either none, one or the same number of inputs as the Crv parameter." - ) - if len(width) not in (1, N): - self.AddRuntimeMessage( - Error, " In 'W' I need either one or the same number of inputs as the Crv parameter." - ) - if len(height) not in (1, N): - self.AddRuntimeMessage( - Error, " In 'H' I need either one or the same number of inputs as the Crv parameter." - ) - if len(category) not in (0, 1, N): - self.AddRuntimeMessage( - Error, " In 'Category' I need either none, one or the same number of inputs as the Crv parameter." - ) - - # duplicate data if None or single value - if len(z_vector) != N: - z_vector = [z_vector[0] for _ in range(N)] - if len(width) != N: - width = [width[0] for _ in range(N)] - if len(height) != N: - height = [height[0] for _ in range(N)] - if len(category) != N: - category = [category[0] for _ in range(N)] - - for line, z, w, h, c in zip(centerline, z_vector, width, height, category): - guid, geometry = self._get_guid_and_geometry(line) - rhino_line = rs.coerceline(geometry) - line = line_to_compas(rhino_line) - - z = vector_to_compas(z) if z else None - 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)) - update_rhobj_attributes_name(guid, "zvector", str(list(beam.frame.zaxis))) - update_rhobj_attributes_name(guid, "category", c) - - beams.append(beam) - scene.add(beam.blank) - - blanks = scene.draw() - - return beams, blanks - - def _get_guid_and_geometry(self, line): - # internalized curves and GH geometry will not have persistent GUIDs, referenced Rhino objects will - # type hint on the input has to be 'ghdoc' for this to work - guid = None - geometry = line - rhino_obj = ActiveDoc.Objects.FindId(line) - if rhino_obj: - guid = line - geometry = rhino_obj.Geometry - return guid, geometry - - Creates a Beam from a LineCurve. - true - true - true - - iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDgAACw4BQL7hQQAAAhRJREFUSEulVLtKA0EUzSfkD3zlrUKMIcHGB6ghIj5QxEoDiiA2gtqGYBEfRRQiSRc7iXU6IWKZwh8Q/AI/wEcRr/cMmWUZ7ya7Whw2uXvvOTNnzqyPiP6Nob7+Q0bBQJ0xLQ64BRP4R6OxZ37Swf4+nRwdKyxmFyg4MEhcvxYH3YCH4+GhwHsqMd6+r9ep1WrRY7OpyGcmp2hvZxcCBXG4F3gQligykIIcIulkknJb26qGnXgW4AF/NBi6AzkIQAxcnl+AjAr5vFXzLMDN8ZFI9JXxfVMuW0RYMZ8D3dZqVs2zADeuRALBD3jbaDQUAZ74D2ib/iQwHI5UudHyFsNYLVaN5JjEGj0F+KUVQXisB+EzyO01CV0F+IUYwY21dZUUXXMCemGdKMBFMYIYgIDktx2wD0FAILBQO7FjBGGJveYE2Id5Jn8AH3g1uRhBHCLI7TUJ2BVCAHLuP9eLVgJc3GS/vyZSaboqldQWq5UKZeczyhYdSyfouCLGzLViJ9cCT1A2sbq03NNv7Ay7joXCLzzTb5JbAqa/mdm5XzfTRCeG1Dk35bcEUQAJchLArpAmkDMOJVI7PAkgrrgbuCM8F5cITbgWQFyHI9F2x29HS0y4EkBcuU/5LZF0Q1cB+I3fHMFP7stJBL3gKHBWLOoIvnGPK78lKAEQQkQjOZZQlnS+pq79lgABfNxw2UycSgPeQL4fsKzVEEQlKEcAAAAASUVORK5CYII= - - false - efe8bffe-0848-4d32-993f-7f43d93bbf01 - true - true - CT: Beam - Beam - - - - - - 1074 - 688 - 145 - 124 - - - 1165 - 750 - - - - - - 6 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 2 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - - - - - 1 - true - Referenced curve or line, Guid of curve or line in the active Rhino document. - 8ca4719b-64bb-4a1e-bd21-47829a6da8fb - Centerline - Centerline - true - 1 - true - 919e0b0e-50c5-4983-bf15-4bb7b9f39c0a - 1 - 87f87f55-5b71-41f4-8aea-21d494016f81 - - - - - - 1076 - 690 - 74 - 20 - - - 1114.5 - 700 - - - - - - - - 1 - true - Vector defining the orientation of the cross-section (corresponds to the height dimension). If None, a default will be taken. - e4854a14-8e9a-421e-a9c2-b070f3e54f84 - ZVector - ZVector - true - 1 - true - 0 - 15a50725-e3d3-4075-9f7c-142ba5f40747 - - - - - - 1076 - 710 - 74 - 20 - - - 1114.5 - 720 - - - - - - - - 1 - true - Width of the cross-section (usually the smaller dimension). - 4941e70a-0c52-4f0e-829c-88c706d76cab - Width - Width - true - 1 - true - f40578da-c08b-4e1d-b010-8b988d3269a2 - 1 - 39fbc626-7a01-46ab-a18e-ec1c0c41685b - - - - - - 1076 - 730 - 74 - 20 - - - 1114.5 - 740 - - - - - - - - 1 - true - Height of the cross-section (usually the larger dimension). - 86c7e8a6-883d-41bb-8889-46964d8e84b3 - Height - Height - true - 1 - true - f40578da-c08b-4e1d-b010-8b988d3269a2 - 1 - 39fbc626-7a01-46ab-a18e-ec1c0c41685b - - - - - - 1076 - 750 - 74 - 20 - - - 1114.5 - 760 - - - - - - - - 1 - true - Category of a beam. - f30f6c3e-93b2-40b0-aa59-ae87fdde3fe1 - Category - Category - true - 1 - true - 0 - 37261734-eec7-4f50-b6a8-b8d1f3c4396b - - - - - - 1076 - 770 - 74 - 20 - - - 1114.5 - 780 - - - - - - - - true - (optional) If True, the attributes in the referenced object will be updated/overwritten with the new values given here. - 63f2f6d5-c8f8-446e-af8e-245bc6bd3b84 - updateRefObj - updateRefObj - true - 0 - true - 0 - d60527f5-b5af-4ef6-8970-5f96fe412559 - - - - - - 1076 - 790 - 74 - 20 - - - 1114.5 - 800 - - - - - - - - Beam object(s). - c38af54d-dfec-411f-ab38-22c657830b82 - Beam - Beam - false - 0 - - - - - - 1180 - 690 - 37 - 60 - - - 1198.5 - 720 - - - - - - - - Shape of the beam's blank. - 489d49f6-b379-49e3-8aed-197b4a1468a4 - Blank - Blank - false - 0 - - - - - - 1180 - 750 - 37 - 60 - - - 1198.5 - 780 - - - - - - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - f40578da-c08b-4e1d-b010-8b988d3269a2 - Number Slider - - false - 0 - - - - - - 888 - 745 - 166 - 20 - - - 888.4611 - 745.2539 - - - - - - 3 - 1 - 1 - 100 - 0 - 0 - 100 - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - 5cc940ad-f898-479c-8389-be3142764477 - Number Slider - - false - 0 - - - - - - 853 - 526 - 166 - 20 - - - 853.5906 - 526.7526 - - - - - - 3 - 1 - 1 - 100 - 0 - 0 - 80 - - - - - - - - - 410755b1-224a-4c1e-a407-bf32fb45ea7e - 00000000-0000-0000-0000-000000000000 - CT: DecomposeBeam - - - - - # flake8: noqa -from compas.geometry import Line -from compas_rhino.conversions import frame_to_rhino_plane -from compas_rhino.conversions import line_to_rhino -from compas_rhino.conversions import point_to_rhino -from compas_rhino.conversions import box_to_rhino -from ghpythonlib.componentbase import executingcomponent as component -from System.Drawing import Color - - -class BeamDecompose(component): - RED = Color.FromArgb(255, 255, 100, 100) - GREEN = Color.FromArgb(200, 50, 220, 100) - BLUE = Color.FromArgb(200, 50, 150, 255) - WHITE = Color.FromArgb(255, 255, 255, 255) - YELLOW = Color.FromArgb(255, 255, 255, 0) - SCREEN_SIZE = 10 - RELATIVE_SIZE = 0 - - def RunScript(self, beam, show_frame, show_faces): - self.show_faces = show_faces if show_faces is not None else False - self.show_frame = show_frame if show_frame is not None else False - self.frames = [] - self.rhino_frames = [] - self.scales = [] - self.faces = [] - self.width = [] - self.height = [] - self.centerline = [] - self.shapes = [] - - for b in beam: - self.frames.append(b.frame) - self.rhino_frames.append(frame_to_rhino_plane(b.frame)) - self.scales.append(b.width + b.height) - self.centerline.append(line_to_rhino(b.centerline)) - self.shapes.append(box_to_rhino(b.shape)) - self.width.append(b.width) - self.height.append(b.height) - self.faces.append(b.faces) - - return self.rhino_frames, self.centerline, self.shapes, self.width, self.height - - def DrawViewportWires(self, arg): - if self.Locked: - return - - for f, s, faces in zip(self.frames, self.scales, self.faces): - if self.show_frame: - self._draw_frame(arg.Display, f, s) - if self.show_faces: - self._draw_faces(arg.Display, faces, s) - - def _draw_frame(self, display, frame, scale): - x = Line.from_point_and_vector(frame.point, frame.xaxis * scale) - y = Line.from_point_and_vector(frame.point, frame.yaxis * scale) - z = Line.from_point_and_vector(frame.point, frame.zaxis * scale) - display.DrawArrow(line_to_rhino(x), self.RED, self.SCREEN_SIZE, self.RELATIVE_SIZE) - display.DrawArrow(line_to_rhino(y), self.GREEN, self.SCREEN_SIZE, self.RELATIVE_SIZE) - display.DrawArrow(line_to_rhino(z), self.BLUE, self.SCREEN_SIZE, self.RELATIVE_SIZE) - - x_loc = x.end + x.vector * scale * 1.1 - y_loc = y.end + y.vector * scale * 1.1 - z_loc = z.end + z.vector * scale * 1.1 - display.Draw2dText("X", self.RED, point_to_rhino(x_loc), True, 16, "Verdana") - display.Draw2dText("Y", self.GREEN, point_to_rhino(y_loc), True, 16, "Verdana") - display.Draw2dText("Z", self.BLUE, point_to_rhino(z_loc), True, 16, "Verdana") - - def _draw_faces(self, display, faces, scale): - for index, face in enumerate(faces): - normal = Line.from_point_and_vector(face.point, face.normal * scale) - text = str(index) - display.Draw2dText(text, self.WHITE, point_to_rhino(face.point), True, 16, "Verdana") - display.DrawArrow(line_to_rhino(normal), self.YELLOW, self.SCREEN_SIZE, self.RELATIVE_SIZE) - - GhPython provides a Python script component - true - true - true - - iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDwAACw8BkvkDpQAAAohJREFUSEuVlj9rFEEYxvMR7gMEdvFy/6NZDGijuQ3aKCKLiqVuIUKwOdBYWIQtkxQWJhAQvFgdEeyuUrggIniFhWAlCPoJ1MoESRyfZ5z3nN2b+7PFj7nbmX2emXfe972bUUoNMfv43RsQuOayFD0/AokDn/NDL0A4Boom2TkbCBRqpXIHo4pv3dbwc3WudIgxNiSplyBaAD+NAYnseQEv+vPV2hfwZ3trS+2224qf+QxzgRi7DBJLnHwDBXsNXoqwy4Plpabqdrtq9f4DvXOIv8bo1cuVz41q7ZjzKQMI+ZawTSJrGpXqDsUYDorfvH5Di4MWCCvFuV/h+aWjF3t7YjxkEIKWEf5ovgdYWDhZq3+AwdHm+oaiwJnTi8cUpLAx0Ib7vZ7q9/vDBpYRRWmgL5kCFKIghWnAEDAUmPMk3hSksDCVARakdiaZYkLl00Qu2hafysB71PlOsWRtTcebl2alYOqis+JjDby7G3do4K+2dfplUxB3sU5jnkbi7cIkQNoAD+JS8+pvGlx8+lZ2oRYa8y8xeiYV9alcooSmVy5dltNGIjyoynsrK3pRJgUDnoAn4Ylcws9evVfN7Z46ey2W7NKtRldl+UTxkxEbIIsA433IeI8LycMnu1I3anZzv4PxXy+CwDnAXWbx7MKyxQZC42HLSWjAzpfavYEF5Ix3RmgU/w2yRXIhXP4hBqNiznCZ1D3AughiUj/kORiEKLcBK5oXfqre+Io1+jKNAX9HQn4XchuwVfA5exPGVKd1kcuAKcxnvHyXmIupDDJ1EbuERjHRgFnEy7SLJw8TDYhpzTor8qIN2DtoIiwuBEw9bWBayMTLHAUNKJT9y0F80HK9ND1q5i+sgg6R/W1M2wAAAABJRU5ErkJggg== - - false - eb9d5147-e225-45a5-a44b-61953c4955eb - true - true - CT: DecomposeBeam - DecomposeBeam - - - - - - 1382 - 621 - 156 - 104 - - - 1462 - 673 - - - - - - 3 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 5 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - - - - - 1 - true - Beam - 8e04df08-7ba8-497d-81a4-3df28baa4429 - Beam - Beam - true - 1 - true - 19ba4d57-8c61-4bac-90d7-412dfcb8ac0b - c38af54d-dfec-411f-ab38-22c657830b82 - 2 - 35915213-5534-4277-81b8-1bdc9e7383d2 - - - - - - 1384 - 623 - 63 - 33 - - - 1417 - 639.6667 - - - - - - - - true - Script input ShowFrame. - 55d797db-e8b9-4a25-83f9-fb3ce5e97dda - ShowFrame - ShowFrame - true - 0 - true - 4589d281-06a1-48a8-ab9c-fa318fb66883 - 1 - d60527f5-b5af-4ef6-8970-5f96fe412559 - - - - - - 1384 - 656 - 63 - 33 - - - 1417 - 673 - - - - - - - - true - Script input ShowFaces. - 38380630-dd0c-4f25-ba4d-8be79af14f88 - ShowFaces - ShowFaces - true - 0 - true - 4589d281-06a1-48a8-ab9c-fa318fb66883 - 1 - d60527f5-b5af-4ef6-8970-5f96fe412559 - - - - - - 1384 - 689 - 63 - 34 - - - 1417 - 706.3334 - - - - - - - - Script output Frame. - eaa8a578-60ae-4948-8427-7ef3e9061069 - Frame - Frame - false - 0 - - - - - - 1477 - 623 - 59 - 20 - - - 1506.5 - 633 - - - - - - - - Script output Centerline. - d5904a7c-0c03-4764-8d8c-d828062b20dd - Centerline - Centerline - false - 0 - - - - - - 1477 - 643 - 59 - 20 - - - 1506.5 - 653 - - - - - - - - Script output Box. - e778515e-1975-4634-93fa-0a12d895cfb0 - Box - Box - false - 0 - - - - - - 1477 - 663 - 59 - 20 - - - 1506.5 - 673 - - - - - - - - Script output Width. - d3ee9e41-73c8-4e9b-8793-3de14a036ce6 - Width - Width - false - 0 - - - - - - 1477 - 683 - 59 - 20 - - - 1506.5 - 693 - - - - - - - - Script output Height. - 712b50a9-aa16-4a06-850a-5ccaa6c1e4a1 - Height - Height - false - 0 - - - - - - 1477 - 703 - 59 - 20 - - - 1506.5 - 713 - - - - - - - - - - - - - - 2e78987b-9dfb-42a2-8b76-3923ac8bd91a - Boolean Toggle - - - - - Boolean (true/false) toggle - 4589d281-06a1-48a8-ab9c-fa318fb66883 - Boolean Toggle - Toggle - false - 0 - true - - - - - - 1257 - 679 - 104 - 22 - - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - c18f9e62-d6f5-42c4-a426-10688f24ca61 - Number Slider - - false - 0 - - - - - - 1762 - 694 - 159 - 20 - - - 1762.14 - 694.5345 - - - - - - 3 - 1 - 1 - 15 - 0 - 0 - 0 - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - fd67193c-c124-4e9b-bf38-3d6a6e085438 - Number Slider - - false - 0 - - - - - - 1722 - 718 - 201 - 20 - - - 1722.123 - 718.8546 - - - - - - 3 - 1 - 1 - 5 - 0 - 0 - 2 - - - - - - - - - 59e0b89a-e487-49f8-bab8-b5bab16be14c - Panel - - - - - A panel for custom notes and text values - cfb4b1f8-a02e-44ce-9c95-80b1164d7fca - Panel - BTLx Params - false - 0 - d525eccb-5bff-4563-b75e-472cbbdc5901 - 1 - Double click to edit panel content… - - - - - - 2400 - 926 - 212 - 333 - - 0 - 0 - 0 - - 2400.01 - 926.6734 - - - - - - - 255;255;255;255 - - true - true - true - false - false - true - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - d5f970b8-28fa-4f28-b038-8f3eed5c682d - Number Slider - - false - 0 - - - - - - 1727 - 786 - 160 - 20 - - - 1727.211 - 786.5431 - - - - - - 3 - 1 - 1 - 200 - 0 - 0 - 30 - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - c1e88145-a742-40e8-9b8b-a549422a646d - Number Slider - - false - 0 - - - - - - 1727 - 809 - 160 - 20 - - - 1727.21 - 809.6162 - - - - - - 3 - 1 - 1 - 200 - 0 - 0 - 15 - - - - - - - - - 2e78987b-9dfb-42a2-8b76-3923ac8bd91a - Boolean Toggle - - - - - Boolean (true/false) toggle - c925fa54-d90e-456f-863a-2f4f0a2857ba - Boolean Toggle - tapered_heel - false - 0 - false - - - - - - 1752 - 831 - 133 - 22 - - - - - - - - - - f31d8d7a-7536-4ac8-9c96-fde6ecda4d0a - Cluster - - - - - - 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 - - Contains a cluster of Grasshopper components - true - a5869824-14fc-400f-a69e-f5050392632f - Cluster - Cluster - false - - - - - 5 - 3f3af581-d64e-4909-91e4-3576cf2e9e86 - 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 - d28e1b27-74ad-41ab-b017-540fa9111876 - ebd18bdb-78c3-4d40-8a69-e1af30a53c27 - fd03a9d4-272c-4590-9595-4d3107dc0fdc - 3c631e1e-b12f-4297-9535-87b4fdc7b45e - 5d665740-a9cc-4f15-8a38-0dc75e214ae2 - 5d32325d-62cf-40bd-93fe-74af56a2c91e - b360d350-2b53-401b-9420-d9402019cb30 - 796ac502-faba-4bb6-a612-7e3dfb448d98 - - - - - - 2381 - 756 - 72 - 84 - - - 2420 - 798 - - - - - - 4 - 919e146f-30ae-4aae-be34-4d72f555e7da - 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 - 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 - d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 - 1 - 919e146f-30ae-4aae-be34-4d72f555e7da - - - - - Brep to split - ebd18bdb-78c3-4d40-8a69-e1af30a53c27 - Brep - B - true - 489d49f6-b379-49e3-8aed-197b4a1468a4 - 1 - - - - - - 2383 - 758 - 22 - 20 - - - 2395.5 - 768 - - - - - - - - Contains a collection of three-dimensional axis-systems - d28e1b27-74ad-41ab-b017-540fa9111876 - Plane - Pln - true - 16bc55f9-a035-4099-834e-e9dc0931b695 - 1 - - - - - - 2383 - 778 - 22 - 20 - - - 2395.5 - 788 - - - - - - - - Contains a collection of three-dimensional axis-systems - fd03a9d4-272c-4590-9595-4d3107dc0fdc - Plane - Pln - true - ddc1d959-4491-4f72-b7d5-a74d74b99385 - 1 - - - - - - 2383 - 798 - 22 - 20 - - - 2395.5 - 808 - - - - - - - - 1 - Section curves - 3f3af581-d64e-4909-91e4-3576cf2e9e86 - Curves - C - true - 1a4781d4-bfe8-4573-86bb-90d0f0ffc197 - 1 - - - - - - 2383 - 818 - 22 - 20 - - - 2395.5 - 828 - - - - - - - - 1 - Joined Breps - 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 - Breps - B - false - 0 - - - - - - 2435 - 758 - 16 - 80 - - - 2443 - 798 - - - - - - - - - - - - - - 537b0419-bbc2-4ff4-bf08-afe526367b2c - Custom Preview - - - - - Allows for customized geometry previews - true - true - 9e4f0b21-759b-4c9a-8dfb-6654484027c5 - Custom Preview - Preview - - - - - - - 2569 - 841 - 48 - 44 - - - 2603 - 863 - - - - - - Geometry to preview - true - bc0fc1fe-a18e-40ca-ab8a-8ba765dde1f9 - Geometry - G - false - 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 - 1 - - - - - - 2571 - 843 - 17 - 20 - - - 2581 - 853 - - - - - - - - The material override - 8cbb31fa-4051-471d-87be-5ce455d7f325 - Material - M - false - 0 - - - - - - 2571 - 863 - 17 - 20 - - - 2581 - 873 - - - - - - 1 - - - - - 1 - {0} - - - - - - 255;221;160;221 - - - 255;66;48;66 - - 0.5 - - 255;255;255;255 - - 0 - - - - - - - - - - - - - - - 0148a65d-6f42-414a-9db7-9a9b2eb78437 - Brep Edges - - - - - Extract the edge curves of a brep. - true - be8a15e2-8773-4720-9d61-dfdc10761646 - Brep Edges - Edges - - - - - - 2469 - 766 - 72 - 64 - - - 2499 - 798 - - - - - - Base Brep - 0056a757-f1c7-4722-83e9-e00c5e697cfc - Brep - B - false - 4c2588f5-9e63-4ce5-a5e7-50f44fb19cb9 - 1 - - - - - - 2471 - 768 - 13 - 60 - - - 2479 - 798 - - - - - - - - 1 - Naked edge curves - 2fc3daa1-27be-494a-9118-8afa0df8edc7 - Naked - En - false - 0 - - - - - - 2514 - 768 - 25 - 20 - - - 2526.5 - 778 - - - - - - - - 1 - Interior edge curves - 7db965ba-79d8-42a1-926c-cc7c5ea6a716 - Interior - Ei - false - 0 - - - - - - 2514 - 788 - 25 - 20 - - - 2526.5 - 798 - - - - - - - - 1 - Non-Manifold edge curves - dfe49992-1aa8-4a4e-bf86-f66139ca06c5 - Non-Manifold - Em - false - 0 - - - - - - 2514 - 808 - 25 - 20 - - - 2526.5 - 818 - - - - - - - - - - - - 22990b1f-9be6-477c-ad89-f775cd347105 - Flip Curve - - - - - Flip a curve using an optional guide curve. - true - 0c07a9ce-439d-466f-98d1-fcc3dcd14023 - Flip Curve - Flip - - - - - - 876 - 695 - 66 - 44 - - - 908 - 717 - - - - - - Curve to flip - 082a270f-f37d-4c27-8266-d4348e1e059b - Curve - C - false - 2f78b16d-80d1-4502-954e-49040fe761a3 - 1 - - - - - - 878 - 697 - 15 - 20 - - - 887 - 707 - - - - - - - - Optional guide curve - 94c738fb-f46a-4531-af70-ea11c88263f2 - Guide - G - true - 0 - - - - - - 878 - 717 - 15 - 20 - - - 887 - 727 - - - - - - - - Flipped curve - c284b03b-3c25-4136-b35d-211207e5e246 - Curve - C - false - 0 - - - - - - 923 - 697 - 17 - 20 - - - 931.5 - 707 - - - - - - - - Flip action - a48ad8ba-3072-486f-9f31-f1cbfcaa31e2 - Flag - F - false - 0 - - - - - - 923 - 717 - 17 - 20 - - - 931.5 - 727 - - - - - - - - - - - - 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 - Plane - - - - - Contains a collection of three-dimensional axis-systems - true - 482410b9-724c-46d6-be3a-3d63785bc853 - Plane - Pln - false - 16bc55f9-a035-4099-834e-e9dc0931b695 - 1 - - - - - - 2566 - 703 - 50 - 24 - - - 2591.305 - 715.5306 - - - - - - - - - - 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 - Plane - - - - - Contains a collection of three-dimensional axis-systems - true - 9c473eb6-ff8f-4c7f-bd69-56527d0257f6 - Plane - Pln - false - ddc1d959-4491-4f72-b7d5-a74d74b99385 - 1 - - - - - - 2565 - 632 - 50 - 24 - - - 2590.664 - 644.5862 - - - - - - - - - - 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 - Plane - - - - - Contains a collection of three-dimensional axis-systems - true - 88b4a335-192d-48a1-81d5-163278eaecbe - Plane - Pln - false - 602ccbf4-1274-4cd9-8349-ba4c5ba030fe - 1 - - - - - - 2565 - 564 - 50 - 24 - - - 2590.097 - 576.5297 - - - - - - - - - - 59e0b89a-e487-49f8-bab8-b5bab16be14c - Panel - - - - - A panel for custom notes and text values - 84325f58-aa64-43ef-9c59-6631e3ff4da9 - Panel - - false - 1 - 40308c84-a0ba-4c41-b158-38a3341beb2c - 1 - Double click to edit panel content… - - - - - - 1988 - 610 - 221 - 85 - - 0 - 0 - 0 - - 1988.603 - 610.7073 - - - - - - - 255;255;250;90 - - true - true - true - false - false - true - - - - - - - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 88b4a335-192d-48a1-81d5-163278eaecbe - 1 - 234eec62-a3cc-4178-9760-678a11912fb0 - Group - CUTTING PLANE - - - - - - - - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 9c473eb6-ff8f-4c7f-bd69-56527d0257f6 - 1 - 86bc7ac3-bdd8-443c-ae9b-e22ea6ec103a - Group - REF_SIDE - - - - - - - - - - a77d0879-94c2-4101-be44-e4a616ffeb0c - 5f86fa9f-c62b-50e8-157b-b454ef3e00fa - Custom Preview Lineweights - - - - - Custom Preview with Lineweights - 56020ec2-ccb1-4ae9-a538-84dcb2857db9 - Custom Preview Lineweights - PreviewLW - - - - - - - 2570 - 747 - 46 - 84 - - - 2602 - 789 - - - - - - Geometry to preview - true - 11bd5cdd-8b99-4483-950a-c0cb8f29576c - Geometry - G - false - 7db965ba-79d8-42a1-926c-cc7c5ea6a716 - 1 - - - - - - 2572 - 749 - 15 - 20 - - - 2581 - 759 - - - - - - - - The preview shader override - 7eb996a5-eb09-476d-8a9f-c49f7a1c895e - Shader - S - false - 0 - - - - - - 2572 - 769 - 15 - 20 - - - 2581 - 779 - - - - - - 1 - - - - - 1 - {0} - - - - - - 255;255;105;180 - - - 255;76;32;54 - - 0.5 - - 255;255;255;255 - - 0 - - - - - - - - - - - The thickness of the wire display - c3053791-290f-494e-8384-81e8464e4dc4 - Thickness - T - true - 0 - - - - - - 2572 - 789 - 15 - 20 - - - 2581 - 799 - - - - - - - - Set to true to try to render curves with an absolute dimension. - 073aa3ff-0ed7-42b6-bdd5-c5b25159731c - Absolute - A - false - 0 - - - - - - 2572 - 809 - 15 - 20 - - - 2581 - 819 - - - - - - 1 - - - - - 1 - {0} - - - - - false - - - - - - - - - - - - - - - 11bbd48b-bb0a-4f1b-8167-fa297590390d - End Points - - - - - Extract the end points of a curve. - true - 715464ea-9048-4ae7-9c5c-942cc0fa2486 - End Points - End - - - - - - 727 - 448 - 64 - 44 - - - 758 - 470 - - - - - - Curve to evaluate - ba457b2a-de34-4922-929d-926d8adedc5a - Curve - C - false - 6bbc142b-2536-41bf-a215-ea52704d32b6 - 1 - - - - - - 729 - 450 - 14 - 40 - - - 737.5 - 470 - - - - - - - - Curve start point - 77f5802b-abc0-49e5-91cc-3376ae99a75c - Start - S - false - 0 - - - - - - 773 - 450 - 16 - 20 - - - 781 - 460 - - - - - - - - Curve end point - 5a7d2dd5-1871-45dd-b9c0-e6289501fd3a - End - E - false - 0 - - - - - - 773 - 470 - 16 - 20 - - - 781 - 480 - - - - - - - - - - - - e9eb1dcf-92f6-4d4d-84ae-96222d60f56b - Move - - - - - Translate (move) an object along a vector. - true - 22d84b32-205f-41d5-8663-8af45f94307c - Move - Move - - - - - - 833 - 468 - 83 - 44 - - - 881 - 490 - - - - - - Base geometry - 4f7e8528-a89b-42ec-8171-f6004589fdcb - Geometry - G - true - 5a7d2dd5-1871-45dd-b9c0-e6289501fd3a - 1 - - - - - - 835 - 470 - 31 - 20 - - - 860 - 480 - - - - - - - - Translation vector - 21c38b75-b524-4c4f-93e3-89a660e1a69b - -x - Motion - T - false - 1e474aea-db61-424d-be9a-804e3fd04359 - 1 - - - - - - 835 - 490 - 31 - 20 - - - 860 - 500 - - - - - - 1 - - - - - 1 - {0} - - - - - - 0 - 0 - 10 - - - - - - - - - - - - Translated geometry - 9348c98e-479b-416c-9b0b-54c72c33be75 - Geometry - G - false - 0 - - - - - - 896 - 470 - 18 - 20 - - - 905 - 480 - - - - - - - - Transformation data - 35ee0f80-11b3-44ca-8fde-aa3bba3f7c43 - Transform - X - false - 0 - - - - - - 896 - 490 - 18 - 20 - - - 905 - 500 - - - - - - - - - - - - 79f9fbb3-8f1d-4d9a-88a9-f7961b1012cd - Unit X - - - - - Unit vector parallel to the world {x} axis. - true - 73ddbf47-b58a-451f-8f84-85c767ffb835 - Unit X - X - - - - - - 513 - 491 - 63 - 28 - - - 542 - 505 - - - - - - Unit multiplication - 4b128ece-c9a7-4204-9394-3fda75fa3d6f - Factor - F - false - 912e5dfb-d2a2-463b-a0eb-2610982bf536 - 1 - - - - - - 515 - 493 - 12 - 24 - - - 522.5 - 505 - - - - - - 1 - - - - - 1 - {0} - - - - - 1 - - - - - - - - - - - World {x} vector - 323481aa-7502-4bf4-8393-f5ea30a2e8ed - Unit vector - V - false - 0 - - - - - - 557 - 493 - 17 - 24 - - - 565.5 - 505 - - - - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - 912e5dfb-d2a2-463b-a0eb-2610982bf536 - Number Slider - - false - 0 - - - - - - 323 - 497 - 163 - 20 - - - 323.5465 - 497.3116 - - - - - - 3 - 1 - 1 - 2000 - 0 - 0 - 0 - - - - - - - - - 4c4e56eb-2f04-43f9-95a3-cc46a14f495a - Line - - - - - Create a line between two points. - true - 25e3b03c-eedc-4418-babd-0df09c1d6284 - Line - Ln - - - - - - 943 - 448 - 63 - 44 - - - 974 - 470 - - - - - - Line start point - 99feaba8-239b-40e1-ae13-db12fa1af53e - Start Point - A - false - 77f5802b-abc0-49e5-91cc-3376ae99a75c - 1 - - - - - - 945 - 450 - 14 - 20 - - - 953.5 - 460 - - - - - - - - Line end point - 5d3fa723-a735-4608-9a27-c304c4c3aa44 - End Point - B - false - 9348c98e-479b-416c-9b0b-54c72c33be75 - 1 - - - - - - 945 - 470 - 14 - 20 - - - 953.5 - 480 - - - - - - - - Line segment - 82ddb61d-d21e-49ce-a61c-851130becb9a - Line - L - false - 0 - - - - - - 989 - 450 - 15 - 40 - - - 996.5 - 470 - - - - - - - - - - - - 9103c240-a6a9-4223-9b42-dbd19bf38e2b - Unit Z - - - - - Unit vector parallel to the world {z} axis. - true - eaf786cc-99fc-4d46-b6b3-cf6b5314011a - Unit Z - Z - - - - - - 515 - 525 - 63 - 28 - - - 544 - 539 - - - - - - Unit multiplication - 88ec5468-52fb-4929-b59a-79cb5d549828 - Factor - F - false - 68472b2f-1682-4c03-a570-0bd3dafb7255 - 1 - - - - - - 517 - 527 - 12 - 24 - - - 524.5 - 539 - - - - - - 1 - - - - - 1 - {0} - - - - - 1 - - - - - - - - - - - World {z} vector - f08f6ccf-8731-4561-b2d4-cf43dcb5070e - Unit vector - V - false - 0 - - - - - - 559 - 527 - 17 - 24 - - - 567.5 - 539 - - - - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - 68472b2f-1682-4c03-a570-0bd3dafb7255 - Number Slider - - false - 0 - - - - - - 329 - 529 - 163 - 20 - - - 329.7557 - 529.9963 - - - - - - 3 - 1 - 1 - 2000 - 0 - 0 - 0 - - - - - - - - - a0d62394-a118-422d-abb3-6af115c75b25 - Addition - - - - - Mathematical addition - true - 362a8c85-9ff1-4541-8448-a78e3c79ff60 - Addition - A+B - - - - - - 728 - 493 - 65 - 44 - - - 759 - 515 - - - - - - 2 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 1 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - - - - - First item for addition - 0b1b606a-bd74-4af5-928f-2813c43db4f7 - A - A - true - 323481aa-7502-4bf4-8393-f5ea30a2e8ed - 1 - - - - - - 730 - 495 - 14 - 20 - - - 738.5 - 505 - - - - - - - - Second item for addition - 29d1f2a4-5fa6-44f6-af85-bc0232c7f3ed - B - B - true - f08f6ccf-8731-4561-b2d4-cf43dcb5070e - 1 - - - - - - 730 - 515 - 14 - 20 - - - 738.5 - 525 - - - - - - - - Result of addition - 1e474aea-db61-424d-be9a-804e3fd04359 - Result - R - false - 0 - - - - - - 774 - 495 - 17 - 40 - - - 782.5 - 515 - - - - - - - - - - - - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 482410b9-724c-46d6-be3a-3d63785bc853 - 1 - 541e5f17-7a28-48ff-8820-37e3076647dd - Group - notch_planes - - - - - - - - - - 2e78987b-9dfb-42a2-8b76-3923ac8bd91a - Boolean Toggle - - - - - Boolean (true/false) toggle - 1f1c2959-2344-4152-a5d6-0e718f54669a - Boolean Toggle - FlipCurve - false - 0 - true - - - - - - 462 - 647 - 115 - 22 - - - - - - - - - - eeafc956-268e-461d-8e73-ee05c6f72c01 - Stream Filter - - - - - Filters a collection of input streams - true - 46298eb6-2834-4153-b072-4cc235a21b8d - Stream Filter - Filter - - - - - - 957 - 655 - 77 - 64 - - - 989 - 687 - - - - - - 3 - 2e3ab970-8545-46bb-836c-1c11e5610bce - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 1 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - - - - - Index of Gate stream - 376909ae-471f-4338-bd76-0550fd56a4cb - Gate - G - false - 1f1c2959-2344-4152-a5d6-0e718f54669a - 1 - - - - - - 959 - 657 - 15 - 20 - - - 968 - 667 - - - - - - 1 - - - - - 1 - {0} - - - - - 0 - - - - - - - - - - - 2 - Input stream at index 0 - acea90f0-616d-4203-908a-45991c3b043f - false - Stream 0 - 0 - true - 2f78b16d-80d1-4502-954e-49040fe761a3 - 1 - - - - - - 959 - 677 - 15 - 20 - - - 968 - 687 - - - - - - - - 2 - Input stream at index 1 - 282b385d-4abd-455c-848a-9a06b88edbbc - false - Stream 1 - 1 - true - c284b03b-3c25-4136-b35d-211207e5e246 - 1 - - - - - - 959 - 697 - 15 - 20 - - - 968 - 707 - - - - - - - - 2 - Filtered stream - 919e0b0e-50c5-4983-bf15-4bb7b9f39c0a - false - Stream - S(1) - false - 0 - - - - - - 1004 - 657 - 28 - 60 - - - 1018 - 687 - - - - - - - - - - - - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 2f78b16d-80d1-4502-954e-49040fe761a3 - 1f1c2959-2344-4152-a5d6-0e718f54669a - 2 - 507139ff-df02-4319-9176-8b2ce4935cf5 - Group - cross_centerline - - - - - - - - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 6bbc142b-2536-41bf-a215-ea52704d32b6 - 73ddbf47-b58a-451f-8f84-85c767ffb835 - 912e5dfb-d2a2-463b-a0eb-2610982bf536 - eaf786cc-99fc-4d46-b6b3-cf6b5314011a - 68472b2f-1682-4c03-a570-0bd3dafb7255 - 5 - 917db72d-186f-4f9b-9bae-68106080e5ea - Group - main_centerline - - - - - - - - - - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider - - - - - Numeric slider for single values - 654077fb-9447-42ca-a4ed-d6a4dd8d1874 - Number Slider - - false - 0 - - - - - - 1721 - 902 - 205 - 20 - - - 1721.597 - 902.3441 - - - - - - 3 - 1 - 1 - 100 - 0 - 0 - 40 - - - - - - - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - c18f9e62-d6f5-42c4-a426-10688f24ca61 - fd67193c-c124-4e9b-bf38-3d6a6e085438 - 2 - b916d0a2-94ed-4de0-9991-6f37b3e59ce4 - Group - Ref_Side - - - - - - - - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - d5f970b8-28fa-4f28-b038-8f3eed5c682d - c1e88145-a742-40e8-9b8b-a549422a646d - c925fa54-d90e-456f-863a-2f4f0a2857ba - 3 - 280e3dc8-45ed-4759-8d99-df91689dd4d8 - Group - StepShape - - - - - - - - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 654077fb-9447-42ca-a4ed-d6a4dd8d1874 - 1 - 98458446-0073-4c25-ac14-bdda3bc5b7ad - Group - Mortise - - - - - - - - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;56;56 - - A group of Grasshopper objects - cfb4b1f8-a02e-44ce-9c95-80b1164d7fca - 1 - d22d460d-f45b-4227-b1b7-0d4f280ddbcc - Group - - - - - - - - - - - - - - - - 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 - - - - - \ No newline at end of file From e6ee91d27101ac20d6be90cb6e314ac1e4619ae2 Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 5 Sep 2024 13:50:02 +0200 Subject: [PATCH 49/63] stepshapetype to stepshape --- src/compas_timber/_fabrication/step_joint.py | 2 +- src/compas_timber/_fabrication/step_joint_notch.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index b8ed1e86f..907c8ff6c 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -702,7 +702,7 @@ def as_dict(self): result["StrutInclination"] = "{:.{prec}f}".format(self._instance.strut_inclination, prec=TOL.precision) result["StepDepth"] = "{:.{prec}f}".format(self._instance.step_depth, prec=TOL.precision) result["HeelDepth"] = "{:.{prec}f}".format(self._instance.heel_depth, prec=TOL.precision) - result["StepShapeType"] = self._instance.step_shape + result["StepShape"] = self._instance.step_shape result["Tenon"] = "yes" if self._instance.tenon else "no" result["TenonWidth"] = "{:.{prec}f}".format(self._instance.tenon_width, prec=TOL.precision) result["TenonHeight"] = "{:.{prec}f}".format(self._instance.tenon_height, prec=TOL.precision) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index 6c2a0f345..198074450 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -869,7 +869,7 @@ def as_dict(self): result["StepDepth"] = "{:.{prec}f}".format(self._instance.step_depth, prec=TOL.precision) result["HeelDepth"] = "{:.{prec}f}".format(self._instance.heel_depth, prec=TOL.precision) result["StrutHeight"] = "{:.{prec}f}".format(self._instance.strut_height, prec=TOL.precision) - result["StepShapeType"] = self._instance.step_shape + result["StepShape"] = self._instance.step_shape result["Mortise"] = "yes" if self._instance.mortise else "no" result["MortiseWidth"] = "{:.{prec}f}".format(self._instance.mortise_width, prec=TOL.precision) result["MortiseHeight"] = "{:.{prec}f}".format(self._instance.mortise_height, prec=TOL.precision) From ef4b583353e525d7c5b277c92d8c3574a8c01f5e Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 5 Sep 2024 13:57:07 +0200 Subject: [PATCH 50/63] adjustments to notch --- .../_fabrication/step_joint_notch.py | 73 ++++++++++--------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index 198074450..eb92aafd3 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -333,8 +333,19 @@ def from_plane_and_beam( notch_width = beam.width # restrain step_depth & heel_depth to beam's height # TODO: should it be restrained? should they be proportional to the beam's dimensions? - step_depth = beam.height if step_depth > beam.height else step_depth - heel_depth = beam.height if heel_depth > beam.height else heel_depth + if step_depth > beam.height: + step_depth = beam.height + print("Step depth is too large for the beam's height. It has been adjusted to the beam's height.") + + max_heel_depth = abs(beam.height / math.tan(math.radians(strut_inclination))) + if heel_depth > max_heel_depth and not tapered_heel: + heel_depth = max_heel_depth + print( + "Heel depth is too large for the given strut inclination. It has been adjusted to the maximum possible value." + ) + + if not tapered_heel: + heel_depth = max_heel_depth if heel_depth > max_heel_depth else heel_depth # define step_shape step_shape = cls._define_step_shape(step_depth, heel_depth, tapered_heel) @@ -456,13 +467,8 @@ def apply(self, geometry, beam): None, geometry, "Failed to generate cutting planes from parameters and beam: {}".format(str(e)) ) - # get box volume for subtracting from geometry - corner_1 = ref_side.point_at(self.start_x, self.start_y) - corner_2 = ref_side.point_at(self.start_x + self.displacement_end, self.start_y + self.notch_width) - subtraction_box = Box.from_corner_corner_height(corner_1, corner_2, max(self.step_depth, self.heel_depth)) - subtraction_box.translate(-ref_side.frame.zaxis * max(self.step_depth, self.heel_depth)) - subtraction_volume = Brep.from_box(subtraction_box) - + # get notch volume + subtraction_volume = geometry.copy() if self.step_shape == StepShapeType.DOUBLE: # trim geometry with first and last cutting plane try: @@ -500,22 +506,33 @@ def apply(self, geometry, beam): subtraction_volume, "Failed to trim geometry with cutting planes: {}".format(str(e)), ) + ## subtract volume from geometry + if isinstance(subtraction_volume, list): + for sub_vol in subtraction_volume: + try: + geometry -= sub_vol + except Exception as e: + raise FeatureApplicationError( + sub_vol, geometry, "Failed to subtract volume from geometry: {}".format(str(e)) + ) + else: + geometry -= subtraction_volume - if ( - self.mortise and self.step_shape == StepShapeType.STEP - ): # TODO: check if mortise applies only to step in BTLx + ## add mortise + if self.mortise and self.step_shape != StepShapeType.DOUBLE: + # TODO: check if mortise applies only to step in BTLx # create mortise volume and subtract from brep mortise_volume = self.mortise_volume_from_params_and_beam(beam) # trim mortise volume with cutting plane - try: - mortise_volume.trim(cutting_planes[0]) - except Exception as e: - raise FeatureApplicationError( - cutting_planes[0], - mortise_volume, - "Failed to trim mortise volume with cutting plane: {}".format(str(e)), - ) - # subtract mortise volume from geometry + if self.step_shape == StepShapeType.STEP: + try: + mortise_volume.trim(cutting_planes[0]) + except Exception as e: + raise FeatureApplicationError( + cutting_planes[0], + mortise_volume, + "Failed to trim mortise volume with cutting plane: {}".format(str(e)), + ) try: geometry -= mortise_volume except Exception as e: @@ -524,19 +541,7 @@ def apply(self, geometry, beam): subtraction_volume, "Failed to subtract mortise volume from geometry: {}".format(str(e)), ) - - # subtract volume from geometry - if isinstance(subtraction_volume, list): - for sub_vol in subtraction_volume: - try: - geometry -= sub_vol - except Exception as e: - raise FeatureApplicationError( - sub_vol, geometry, "Failed to subtract volume from geometry: {}".format(str(e)) - ) - return geometry - else: - return geometry - subtraction_volume + return geometry def add_mortise(self, mortise_width, mortise_height, beam): """Add a mortise to the existing StepJointNotch instance. From 27060bd72c76fc0a288ffc69aa5b28ba71a12b50 Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 5 Sep 2024 13:58:12 +0200 Subject: [PATCH 51/63] adjustment to step --- src/compas_timber/_fabrication/step_joint.py | 40 ++++++++++++-------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index 907c8ff6c..829c2c73b 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -250,9 +250,16 @@ def from_plane_and_beam(cls, plane, beam, step_depth=20.0, heel_depth=0.0, taper strut_inclination = cls._calculate_strut_inclination(ref_side, plane, orientation) # restrain step_depth & heel_depth to beam's height and the maximum possible heel depth for the beam - step_depth = beam.height if step_depth > beam.height else step_depth + if step_depth > beam.height: + step_depth = beam.height + print("Step depth is too large for the beam's height. It has been adjusted to the beam's height.") + max_heel_depth = abs(beam.height / math.tan(math.radians(strut_inclination))) - heel_depth = max_heel_depth if heel_depth > max_heel_depth else heel_depth + if heel_depth > max_heel_depth and not tapered_heel: + heel_depth = max_heel_depth + print( + "Heel depth is too large for the given strut inclination. It has been adjusted to the maximum possible value." + ) # define step_shape step_shape = cls._define_step_shape(step_depth, heel_depth, tapered_heel) @@ -419,27 +426,30 @@ def apply(self, geometry, beam): trimmed_geometies, geometry, "Failed to union trimmed geometries: {}".format(str(e)) ) - if self.tenon and self.step_shape == StepShapeType.STEP: # TODO: check if tenon applies only to step in BTLx + if self.tenon and self.step_shape != StepShapeType.DOUBLE: # TODO: check if tenon applies only to step in BTLx # create tenon volume and subtract from brep tenon_volume = self.tenon_volume_from_params_and_beam(beam) cutting_planes[0].normal = cutting_planes[0].normal * -1 - # trim tenon volume with cutting plane - try: - tenon_volume.trim(cutting_planes[0]) - except Exception as e: - raise FeatureApplicationError( - cutting_planes[0], tenon_volume, "Failed to trim tenon volume with cutting plane: {}".format(str(e)) - ) - # trim tenon volume with second cutting plane if tenon height is greater than step depth - if self.tenon_height > self.step_depth: + if self.step_shape == StepShapeType.STEP: + # trim tenon volume with cutting plane try: - tenon_volume.trim(cutting_planes[1]) + tenon_volume.trim(cutting_planes[0]) except Exception as e: raise FeatureApplicationError( - cutting_planes[1], + cutting_planes[0], tenon_volume, - "Failed to trim tenon volume with second cutting plane: {}".format(str(e)), + "Failed to trim tenon volume with cutting plane: {}".format(str(e)), ) + # trim tenon volume with second cutting plane if tenon height is greater than step depth + if self.tenon_height > self.step_depth: + try: + tenon_volume.trim(cutting_planes[1]) + except Exception as e: + raise FeatureApplicationError( + cutting_planes[1], + tenon_volume, + "Failed to trim tenon volume with second cutting plane: {}".format(str(e)), + ) # add tenon volume to geometry try: geometry += tenon_volume From fcb4f601efc1e43cb4c15b884c31f4f0e4a30aa9 Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 5 Sep 2024 14:24:44 +0200 Subject: [PATCH 52/63] new joint methods --- src/compas_timber/connections/joint.py | 71 ++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/compas_timber/connections/joint.py b/src/compas_timber/connections/joint.py index f0763a9ae..4b637b1d6 100644 --- a/src/compas_timber/connections/joint.py +++ b/src/compas_timber/connections/joint.py @@ -296,3 +296,74 @@ def _beam_ref_side_incidence(beam_a, beam_b, ignore_ends=True): ref_side_angles[ref_side_index] = angle_vectors(ref_side.normal, centerline_vec) return ref_side_angles + + @staticmethod + def _beam_ref_side_incidence_with_vector(beam_b, vector, ignore_ends=True): + """ + Returns a map of ref_side indices of beam_b and the angle of their normal with a given vector. + + This is used to find a cutting plane when joining two beams where one beam is represented the normal of one of it's reference sides. + + Parameters + ---------- + beam_b : :class:`~compas_timber.parts.Beam` + The beam for which ref_side angles will be calculated. + vector : :class:`~compas.geometry.Vector` + The vector to compare against the ref_sides' normals. + ignore_ends : bool, optional + If True, only the first four ref_sides of `beam_b` are considered. Otherwise all ref_sides are considered. + + Examples + -------- + >>> vector = Vector(1, 0, 0) + >>> ref_side_angles = Joint.ref_side_incidence_with_vector(beam_b, vector) + >>> closest_ref_side_index = min(ref_side_angles, key=ref_side_angles.get) + >>> cutting_plane = beam_b.ref_sides[closest_ref_side_index] + + Returns + ------- + dict(int, float) + A map of ref_side indices of beam_b and their respective angle with the given vector. + + """ + if ignore_ends: + beam_b_ref_sides = beam_b.ref_sides[:4] + else: + beam_b_ref_sides = beam_b.ref_sides + + ref_side_angles = {} + for ref_side_index, ref_side in enumerate(beam_b_ref_sides): + ref_side_angles[ref_side_index] = angle_vectors(vector, ref_side.normal) + + return ref_side_angles + + def _are_beams_coplanar(beam_a, beam_b, tolerance=1e-3): + """ + Checks if two beams are coplanar based on the cross product of their centerline directions. + + Parameters + ---------- + beam_a : :class:`~compas_timber.parts.Beam` + The first beam. + beam_b : :class:`~compas_timber.parts.Beam` + The second beam. + tolerance : float, optional + The tolerance for the dot product comparison, default is 1e-3. + + Returns + ------- + bool + True if the beams are coplanar, False otherwise. + """ + # Compute the cross product of the centerline directions of the two beams + print(beam_a, beam_b) + cross_product = beam_a.centerline.direction.cross(beam_b.centerline.direction) + + # Check dot products of the cross product with the normals of both beams' frames + dot_with_beam_b_normal = abs(cross_product.dot(beam_b.frame.normal)) + dot_with_beam_a_normal = abs(cross_product.dot(beam_a.frame.normal)) + + # Check if both dot products are close to 0 or 1 (indicating coplanarity) + return ( + 1 - tolerance <= dot_with_beam_b_normal <= 1 + tolerance or 0 <= dot_with_beam_b_normal <= tolerance + ) and (1 - tolerance <= dot_with_beam_a_normal <= 1 + tolerance or 0 <= dot_with_beam_a_normal <= tolerance) From c24476d84023055ff1b942829559811996ebe514 Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 5 Sep 2024 14:39:13 +0200 Subject: [PATCH 53/63] coplanarity condition for t-stepjoint --- src/compas_timber/connections/joint.py | 2 +- src/compas_timber/connections/t_step_joint.py | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/compas_timber/connections/joint.py b/src/compas_timber/connections/joint.py index 4b637b1d6..d7541d164 100644 --- a/src/compas_timber/connections/joint.py +++ b/src/compas_timber/connections/joint.py @@ -337,6 +337,7 @@ def _beam_ref_side_incidence_with_vector(beam_b, vector, ignore_ends=True): return ref_side_angles + @staticmethod def _are_beams_coplanar(beam_a, beam_b, tolerance=1e-3): """ Checks if two beams are coplanar based on the cross product of their centerline directions. @@ -356,7 +357,6 @@ def _are_beams_coplanar(beam_a, beam_b, tolerance=1e-3): True if the beams are coplanar, False otherwise. """ # Compute the cross product of the centerline directions of the two beams - print(beam_a, beam_b) cross_product = beam_a.centerline.direction.cross(beam_b.centerline.direction) # Check dot products of the cross product with the normals of both beams' frames diff --git a/src/compas_timber/connections/t_step_joint.py b/src/compas_timber/connections/t_step_joint.py index 82dfdfea8..202965efa 100644 --- a/src/compas_timber/connections/t_step_joint.py +++ b/src/compas_timber/connections/t_step_joint.py @@ -100,8 +100,11 @@ def cross_beam_ref_side_index(self): @property def main_beam_ref_side_index(self): - ref_side_dict = self._beam_ref_side_incidence(self.cross_beam, self.main_beam, ignore_ends=True) - ref_side_index = min(ref_side_dict, key=ref_side_dict.get) + cross_beam_ref_side = self.cross_beam.ref_sides[self.cross_beam_ref_side_index] + ref_side_dict = self._beam_ref_side_incidence_with_vector( + self.main_beam, cross_beam_ref_side.normal, ignore_ends=True + ) + ref_side_index = max(ref_side_dict, key=ref_side_dict.get) return ref_side_index def add_features(self): @@ -110,7 +113,13 @@ def add_features(self): This method is automatically called when joint is created by the call to `Joint.create()`. """ + # TODO: In the future the step, heel and tenon mortise depths should be proportional to the cross beam section wheareas the proportions are defined by the national norms. + # TODO: As well the step shape should maybe be defined automatically by the shear reqirements of the joint. + assert self.main_beam and self.cross_beam # should never happen + assert self._are_beams_coplanar( + self.main_beam, self.cross_beam + ), "The beams are not coplanar, the joint cannot be created." if self.features: self.main_beam.remove_features(self.features) @@ -118,6 +127,7 @@ def add_features(self): main_beam_ref_side = self.main_beam.ref_sides[self.main_beam_ref_side_index] cross_beam_ref_side = self.cross_beam.ref_sides[self.cross_beam_ref_side_index] + # generate step joint notch features cross_feature = StepJointNotch.from_plane_and_beam( main_beam_ref_side, From b11c4b8c7a17f8ae9c3875e6617989cc307be833 Mon Sep 17 00:00:00 2001 From: papachap Date: Thu, 5 Sep 2024 14:45:17 +0200 Subject: [PATCH 54/63] linting --- src/compas_timber/_fabrication/step_joint_notch.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index eb92aafd3..5d3fa0be0 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -456,9 +456,6 @@ def apply(self, geometry, beam): """ # type: (Brep, Beam) -> Brep - # Get the reference side - ref_side = beam.side_as_surface(self.ref_side_index) - # get cutting planes from params try: cutting_planes = self.planes_from_params_and_beam(beam) From 71cef2ddb85c94a5a1bfaeae0af7598052a72dfe Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 15 Oct 2024 11:12:45 +0200 Subject: [PATCH 55/63] format & lint --- src/compas_timber/_fabrication/__init__.py | 4 +++- src/compas_timber/_fabrication/drilling.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/compas_timber/_fabrication/__init__.py b/src/compas_timber/_fabrication/__init__.py index 66826e3fd..e70a600d1 100644 --- a/src/compas_timber/_fabrication/__init__.py +++ b/src/compas_timber/_fabrication/__init__.py @@ -16,7 +16,9 @@ "JackRafterCut", "BTLxProcess", "OrientationType", - "JackRafterCutParams", "Drilling", "DrillingParams", + "JackRafterCutParams", + "Drilling", + "DrillingParams", "StepJointNotch", "StepJointNotchParams", "StepJoint", diff --git a/src/compas_timber/_fabrication/drilling.py b/src/compas_timber/_fabrication/drilling.py index 79de62127..5475222f8 100644 --- a/src/compas_timber/_fabrication/drilling.py +++ b/src/compas_timber/_fabrication/drilling.py @@ -10,8 +10,8 @@ from compas.geometry import Vector from compas.geometry import angle_vectors_signed from compas.geometry import distance_point_plane -from compas.geometry import intersection_segment_plane from compas.geometry import intersection_line_plane +from compas.geometry import intersection_segment_plane from compas.geometry import is_point_behind_plane from compas.geometry import is_point_in_polyhedron from compas.geometry import project_point_plane From 21004bc9de02c4dea03ee3da482cd2a648d2078e Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 15 Oct 2024 14:09:00 +0200 Subject: [PATCH 56/63] revert face indices --- src/compas_timber/elements/beam.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/compas_timber/elements/beam.py b/src/compas_timber/elements/beam.py index 661360daf..b4c190bc0 100644 --- a/src/compas_timber/elements/beam.py +++ b/src/compas_timber/elements/beam.py @@ -166,31 +166,31 @@ def faces(self): # type: () -> list[Frame] assert self.frame return [ - Frame( - Point(*add_vectors(self.midpoint, -self.frame.zaxis * self.height * 0.5)), - self.frame.xaxis, - -self.frame.yaxis, - ), Frame( Point(*add_vectors(self.midpoint, self.frame.yaxis * self.width * 0.5)), self.frame.xaxis, -self.frame.zaxis, ), Frame( - Point(*add_vectors(self.midpoint, self.frame.zaxis * self.height * 0.5)), + Point(*add_vectors(self.midpoint, -self.frame.zaxis * self.height * 0.5)), self.frame.xaxis, - self.frame.yaxis, + -self.frame.yaxis, ), Frame( Point(*add_vectors(self.midpoint, -self.frame.yaxis * self.width * 0.5)), self.frame.xaxis, self.frame.zaxis, ), + Frame( + Point(*add_vectors(self.midpoint, self.frame.zaxis * self.height * 0.5)), + self.frame.xaxis, + self.frame.yaxis, + ), Frame(self.frame.point, -self.frame.yaxis, self.frame.zaxis), # small face at start point Frame( Point(*add_vectors(self.frame.point, self.frame.xaxis * self.length)), - -self.frame.yaxis, - -self.frame.zaxis, + self.frame.yaxis, + self.frame.zaxis, ), # small face at end point ] @@ -450,10 +450,7 @@ def add_features(self, features): """ if not isinstance(features, list): features = [features] - for feature in features: - if feature not in self.features: - self.features.append(feature) - # self.features.extend(features) # type: ignore + self.features.extend(features) # type: ignore @reset_computed def remove_features(self, features=None): From 7e271a77a25b2bfe472ab830c22b7639537c39a9 Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 16 Oct 2024 16:13:57 +0200 Subject: [PATCH 57/63] conditional check for step shape and values --- src/compas_timber/_fabrication/step_joint.py | 29 +++++++-- .../_fabrication/step_joint_notch.py | 15 ++++- src/compas_timber/connections/t_step_joint.py | 60 ++++++++++++++----- 3 files changed, 82 insertions(+), 22 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index 829c2c73b..ae2ae0f96 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -160,9 +160,20 @@ def heel_depth(self, heel_depth): def step_shape(self): return self._step_shape - @step_shape.setter # TODO: should this be defined automatically depending on the other parameters? (ie. if heel_depth > 0 and step_depth > 0, then step_shape = "double") + @step_shape.setter def step_shape(self, step_shape): - if step_shape not in [StepShapeType.DOUBLE, StepShapeType.STEP, StepShapeType.HEEL, StepShapeType.TAPERED_HEEL]: + if step_shape == StepShapeType.DOUBLE: + if self.step_depth <= 0 or self.heel_depth <= 0: + raise ValueError("For a 'double' step_shape, both step_depth and heel_depth must be greater than 0.") + elif step_shape == StepShapeType.STEP: + if self.step_depth <= 0 or self.heel_depth != 0: + raise ValueError("For a 'step' step_shape, step_depth must be greater than 0 and heel_depth must be 0.") + elif step_shape in [StepShapeType.HEEL, StepShapeType.TAPERED_HEEL]: + if self.heel_depth <= 0 or self.step_depth != 0: + raise ValueError( + "For 'heel' or 'tapered heel' step_shape, heel_depth must be greater than 0 and step_depth must be 0." + ) + else: raise ValueError( "StepShapeType must be either StepShapeType.DOUBLE, StepShapeType.STEP, StepShapeType.HEEL, or StepShapeType.TAPERED_HEEL." ) @@ -247,7 +258,7 @@ def from_plane_and_beam(cls, plane, beam, step_depth=20.0, heel_depth=0.0, taper start_x = distance_point_point(ref_side.point, point_start_x) # calculate strut_inclination - strut_inclination = cls._calculate_strut_inclination(ref_side, plane, orientation) + strut_inclination = cls._calculate_strut_inclination(ref_side, plane) # restrain step_depth & heel_depth to beam's height and the maximum possible heel depth for the beam if step_depth > beam.height: @@ -278,7 +289,7 @@ def _calculate_orientation(ref_side, cutting_plane): return OrientationType.START @staticmethod - def _calculate_strut_inclination(ref_side, plane, orientation): + def _calculate_strut_inclination(ref_side, plane): # vector rotation direction of the plane's normal in the vertical direction strut_inclination_vector = Vector.cross(ref_side.zaxis, plane.normal) strut_inclination = 180 - abs( @@ -499,9 +510,15 @@ def planes_from_params_and_beam(self, beam): # Get the opposite side as a PlanarSurface for the second cut and calculate the additional displacement along the xaxis opp_side = beam.side_as_surface((self.ref_side_index + 2) % 4) + # Determine whether to use the beam's width or height based on the alignment of the reference side normal. + # If the reference side normal and the frame normal are aligned, use the beam's height as the "width" for calculations. + if abs(beam.ref_sides[self.ref_side_index].normal.dot(beam.frame.normal)) > 0.0: + beam_width = beam.height + else: + beam_width = beam.width # Calculate the displacements for the cutting planes along the y-axis and x-axis - y_displacement_end = self._calculate_y_displacement_end(beam.height, self.strut_inclination) - x_displacement_end = self._calculate_x_displacement_end(beam.height, self.strut_inclination, self.orientation) + y_displacement_end = self._calculate_y_displacement_end(beam_width, self.strut_inclination) + x_displacement_end = self._calculate_x_displacement_end(beam_width, self.strut_inclination, self.orientation) x_displacement_heel = self._calculate_x_displacement_heel( self.heel_depth, self.strut_inclination, self.orientation ) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index 5d3fa0be0..b358d7772 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -226,9 +226,20 @@ def step_shape(self): @step_shape.setter def step_shape(self, step_shape): - if step_shape not in [StepShapeType.DOUBLE, StepShapeType.STEP, StepShapeType.HEEL, StepShapeType.TAPERED_HEEL]: + if step_shape == StepShapeType.DOUBLE: + if self.step_depth <= 0 or self.heel_depth <= 0: + raise ValueError("For a 'double' step_shape, both step_depth and heel_depth must be greater than 0.") + elif step_shape == StepShapeType.STEP: + if self.step_depth <= 0 or self.heel_depth != 0: + raise ValueError("For a 'step' step_shape, step_depth must be greater than 0 and heel_depth must be 0.") + elif step_shape in [StepShapeType.HEEL, StepShapeType.TAPERED_HEEL]: + if self.heel_depth <= 0 or self.step_depth != 0: + raise ValueError( + "For 'heel' or 'tapered heel' step_shape, heel_depth must be greater than 0 and step_depth must be 0." + ) + else: raise ValueError( - "StepShapeType must be either StepShapeType.DOUBLE, StepShapeType.STEP, StepShapeType.HEEL or StepShapeType.TAPERED_HEEL." + "StepShapeType must be either StepShapeType.DOUBLE, StepShapeType.STEP, StepShapeType.HEEL, or StepShapeType.TAPERED_HEEL." ) self._step_shape = step_shape diff --git a/src/compas_timber/connections/t_step_joint.py b/src/compas_timber/connections/t_step_joint.py index 202965efa..d4097f7c5 100644 --- a/src/compas_timber/connections/t_step_joint.py +++ b/src/compas_timber/connections/t_step_joint.py @@ -1,5 +1,9 @@ from compas_timber._fabrication import StepJoint from compas_timber._fabrication import StepJointNotch +from compas_timber.connections.utilities import are_beams_coplanar +from compas_timber.connections.utilities import beam_ref_side_incidence +from compas_timber.connections.utilities import beam_ref_side_incidence_with_vector +from compas_timber.connections.utilities import check_beam_alignment from .joint import Joint from .solver import JointTopology @@ -19,6 +23,8 @@ class TStepJoint(Joint): First beam to be joined. cross_beam : :class:`~compas_timber.parts.Beam` Second beam to be joined. + step_shape : int + Shape of the step feature. 0: step, 1: heel, 2: double. step_depth : float Depth of the step cut. Combined with a heel cut it generates a double step cut. heel_depth : float @@ -34,6 +40,8 @@ class TStepJoint(Joint): First beam to be joined. cross_beam : :class:`~compas_timber.parts.Beam` Second beam to be joined. + step_shape : int + Shape of the step feature. 0: step, 1: heel, 2: double. step_depth : float Depth of the step cut. Combined with a heel cut it generates a double step cut. heel_depth : float @@ -52,6 +60,7 @@ def __data__(self): data = super(TStepJoint, self).__data__ data["main_beam"] = self.main_beam_guid data["cross_beam"] = self.cross_beam_guid + data["step_shape"] = self.step_shape data["step_depth"] = self.step_depth data["heel_depth"] = self.heel_depth data["tapered_heel"] = self.tapered_heel @@ -60,8 +69,9 @@ def __data__(self): def __init__( self, - main_beam=None, - cross_beam=None, + main_beam, + cross_beam, + step_shape, step_depth=None, heel_depth=None, tapered_heel=None, @@ -73,18 +83,23 @@ def __init__( self.main_beam_guid = str(main_beam.guid) if main_beam else None self.cross_beam_guid = str(cross_beam.guid) if cross_beam else None - self.step_depth = step_depth - self.heel_depth = heel_depth + self.step_shape = step_shape + self.step_depth, self.heel_depth = self.set_step_depths(step_depth, heel_depth) + self.tapered_heel = tapered_heel self.tenon_mortise_height = tenon_mortise_height - self.start_y = ( - (self.cross_beam.width - self.main_beam.width) / 2 if self.cross_beam.width > self.main_beam.width else 0.0 - ) - self.notch_limited = False if self.main_beam.width >= self.cross_beam.width else True - self.notch_width = self.main_beam.width - self.strut_height = self.main_beam.height - self.tenon_mortise_width = self.main_beam.width / 4 + # Check alignment to determine if the width and height of the main_beam (beam_b) should be swapped + swap_dimensions = check_beam_alignment(self.cross_beam, self.main_beam) + # For the main beam, use width or height based on the alignment result + main_width = self.main_beam.width if swap_dimensions else self.main_beam.height + main_height = self.main_beam.height if swap_dimensions else self.main_beam.width + + self.start_y = (self.cross_beam.width - main_width) / 2 if self.cross_beam.width > main_width else 0.0 + self.notch_limited = main_width < self.cross_beam.width + self.notch_width = main_width + self.strut_height = main_height + self.tenon_mortise_width = main_width / 4 self.features = [] @@ -94,19 +109,35 @@ def beams(self): @property def cross_beam_ref_side_index(self): - ref_side_dict = self._beam_ref_side_incidence(self.main_beam, self.cross_beam, ignore_ends=True) + ref_side_dict = beam_ref_side_incidence(self.main_beam, self.cross_beam, ignore_ends=True) ref_side_index = min(ref_side_dict, key=ref_side_dict.get) return ref_side_index @property def main_beam_ref_side_index(self): cross_beam_ref_side = self.cross_beam.ref_sides[self.cross_beam_ref_side_index] - ref_side_dict = self._beam_ref_side_incidence_with_vector( + ref_side_dict = beam_ref_side_incidence_with_vector( self.main_beam, cross_beam_ref_side.normal, ignore_ends=True ) ref_side_index = max(ref_side_dict, key=ref_side_dict.get) return ref_side_index + def set_step_depths(self, step_depth=None, heel_depth=None): + """Sets the default step and heel depths based on the joint type if they are not provided.""" + if self.step_shape == 0: # 'step' shape + step_depth = step_depth if step_depth is not None else self.cross_beam.height / 4 + heel_depth = 0.0 + elif self.step_shape == 1: # 'heel' shape + step_depth = 0.0 + heel_depth = heel_depth if heel_depth is not None else self.cross_beam.height / 4 + elif self.step_shape == 2: # 'double' shape + step_depth = step_depth if step_depth is not None else self.cross_beam.height / 6 + heel_depth = heel_depth if heel_depth is not None else self.cross_beam.height / 4 + else: + raise ValueError("Step shape must be ether: 0:step, 1:heel, 2:double.") + + return step_depth, heel_depth + def add_features(self): """Adds the required trimming features to both beams. @@ -117,7 +148,7 @@ def add_features(self): # TODO: As well the step shape should maybe be defined automatically by the shear reqirements of the joint. assert self.main_beam and self.cross_beam # should never happen - assert self._are_beams_coplanar( + assert are_beams_coplanar( self.main_beam, self.cross_beam ), "The beams are not coplanar, the joint cannot be created." @@ -151,6 +182,7 @@ def add_features(self): self.tapered_heel, self.main_beam_ref_side_index, ) + # generate tenon and mortise features if self.tenon_mortise_height: cross_feature.add_mortise(self.tenon_mortise_width, self.tenon_mortise_height, self.cross_beam) From 689bddda1568ed76effede0f848f68a8596318b5 Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 16 Oct 2024 16:14:26 +0200 Subject: [PATCH 58/63] create joint utilities module --- src/compas_timber/connections/joint.py | 123 ---------------- src/compas_timber/connections/utilities.py | 161 +++++++++++++++++++++ 2 files changed, 161 insertions(+), 123 deletions(-) create mode 100644 src/compas_timber/connections/utilities.py diff --git a/src/compas_timber/connections/joint.py b/src/compas_timber/connections/joint.py index f2b6accaa..1ac6b153a 100644 --- a/src/compas_timber/connections/joint.py +++ b/src/compas_timber/connections/joint.py @@ -259,126 +259,3 @@ def _beam_side_incidence(beam_a, beam_b, ignore_ends=True): face_angles[face_index] = angle_vectors(face.normal, centerline_vec) return face_angles - - @staticmethod - def _beam_ref_side_incidence(beam_a, beam_b, ignore_ends=True): - # compared to beam_side_incidence, this function considers the ref_sides and not faces and forms part of the transition to the new system - """Returns a map of ref_side indices of beam_b and the angle of their normal with beam_a's centerline. - - This is used to find a cutting plane when joining the two beams. - - Parameters - ---------- - beam_a : :class:`~compas_timber.parts.Beam` - The beam that attaches with one of its ends to the side of beam_b. - beam_b : :class:`~compas_timber.parts.Beam` - The other beam. - ignore_ends : bool, optional - If True, only the first four ref_sides of `beam_b` are considered. Otherwise all ref_sides are considered. - - Examples - -------- - >>> ref_side_angles = Joint.beam_side_incidence(beam_a, beam_b) - >>> closest_ref_side_index = min(ref_side_angles, key=ref_side_angles.get) - >>> cutting_plane = beam_b.ref_sides[closest_ref_side_index] - - Returns - ------- - dict(int, float) - A map of ref_side indices of beam_b and their respective angle with beam_a's centerline. - - """ - # find the orientation of beam_a's centerline so that it's pointing outward of the joint - # find the closest end - p1x, _ = intersection_line_line(beam_a.centerline, beam_b.centerline) - if p1x is None: - raise AssertionError("No intersection found") - - end, _ = beam_a.endpoint_closest_to_point(Point(*p1x)) - - if end == "start": - centerline_vec = beam_a.centerline.vector - else: - centerline_vec = beam_a.centerline.vector * -1 - - if ignore_ends: - beam_b_ref_sides = beam_b.ref_sides[:4] - else: - beam_b_ref_sides = beam_b.ref_sides - - ref_side_angles = {} - for ref_side_index, ref_side in enumerate(beam_b_ref_sides): - ref_side_angles[ref_side_index] = angle_vectors(ref_side.normal, centerline_vec) - - return ref_side_angles - - @staticmethod - def _beam_ref_side_incidence_with_vector(beam_b, vector, ignore_ends=True): - """ - Returns a map of ref_side indices of beam_b and the angle of their normal with a given vector. - - This is used to find a cutting plane when joining two beams where one beam is represented the normal of one of it's reference sides. - - Parameters - ---------- - beam_b : :class:`~compas_timber.parts.Beam` - The beam for which ref_side angles will be calculated. - vector : :class:`~compas.geometry.Vector` - The vector to compare against the ref_sides' normals. - ignore_ends : bool, optional - If True, only the first four ref_sides of `beam_b` are considered. Otherwise all ref_sides are considered. - - Examples - -------- - >>> vector = Vector(1, 0, 0) - >>> ref_side_angles = Joint.ref_side_incidence_with_vector(beam_b, vector) - >>> closest_ref_side_index = min(ref_side_angles, key=ref_side_angles.get) - >>> cutting_plane = beam_b.ref_sides[closest_ref_side_index] - - Returns - ------- - dict(int, float) - A map of ref_side indices of beam_b and their respective angle with the given vector. - - """ - if ignore_ends: - beam_b_ref_sides = beam_b.ref_sides[:4] - else: - beam_b_ref_sides = beam_b.ref_sides - - ref_side_angles = {} - for ref_side_index, ref_side in enumerate(beam_b_ref_sides): - ref_side_angles[ref_side_index] = angle_vectors(vector, ref_side.normal) - - return ref_side_angles - - @staticmethod - def _are_beams_coplanar(beam_a, beam_b, tolerance=1e-3): - """ - Checks if two beams are coplanar based on the cross product of their centerline directions. - - Parameters - ---------- - beam_a : :class:`~compas_timber.parts.Beam` - The first beam. - beam_b : :class:`~compas_timber.parts.Beam` - The second beam. - tolerance : float, optional - The tolerance for the dot product comparison, default is 1e-3. - - Returns - ------- - bool - True if the beams are coplanar, False otherwise. - """ - # Compute the cross product of the centerline directions of the two beams - cross_product = beam_a.centerline.direction.cross(beam_b.centerline.direction) - - # Check dot products of the cross product with the normals of both beams' frames - dot_with_beam_b_normal = abs(cross_product.dot(beam_b.frame.normal)) - dot_with_beam_a_normal = abs(cross_product.dot(beam_a.frame.normal)) - - # Check if both dot products are close to 0 or 1 (indicating coplanarity) - return ( - 1 - tolerance <= dot_with_beam_b_normal <= 1 + tolerance or 0 <= dot_with_beam_b_normal <= tolerance - ) and (1 - tolerance <= dot_with_beam_a_normal <= 1 + tolerance or 0 <= dot_with_beam_a_normal <= tolerance) diff --git a/src/compas_timber/connections/utilities.py b/src/compas_timber/connections/utilities.py new file mode 100644 index 000000000..21d7b3388 --- /dev/null +++ b/src/compas_timber/connections/utilities.py @@ -0,0 +1,161 @@ +from compas.geometry import Point +from compas.geometry import angle_vectors +from compas.geometry import intersection_line_line + + +def beam_ref_side_incidence(beam_a, beam_b, ignore_ends=True): + """Returns a map of ref_side indices of beam_b and the angle of their normal with beam_a's centerline. + + This is used to find a cutting plane when joining the two beams. + + Compared to beam_side_incidence, this function considers the ref_sides and not faces and forms part of the transition to the new implementation system + + Parameters + ---------- + beam_a : :class:`~compas_timber.parts.Beam` + The beam that attaches with one of its ends to the side of beam_b. + beam_b : :class:`~compas_timber.parts.Beam` + The other beam. + ignore_ends : bool, optional + If True, only the first four ref_sides of `beam_b` are considered. Otherwise all ref_sides are considered. + + Examples + -------- + >>> ref_side_angles = Joint.beam_side_incidence(beam_a, beam_b) + >>> closest_ref_side_index = min(ref_side_angles, key=ref_side_angles.get) + >>> cutting_plane = beam_b.ref_sides[closest_ref_side_index] + + Returns + ------- + dict(int, float) + A map of ref_side indices of beam_b and their respective angle with beam_a's centerline. + + """ + # find the orientation of beam_a's centerline so that it's pointing outward of the joint + # find the closest end + p1x, _ = intersection_line_line(beam_a.centerline, beam_b.centerline) + if p1x is None: + raise ValueError("The two beams do not intersect with each other") + + end, _ = beam_a.endpoint_closest_to_point(Point(*p1x)) + + if end == "start": + centerline_vec = beam_a.centerline.vector + else: + centerline_vec = beam_a.centerline.vector * -1 + + if ignore_ends: + beam_b_ref_sides = beam_b.ref_sides[:4] + else: + beam_b_ref_sides = beam_b.ref_sides + + ref_side_angles = {} + for ref_side_index, ref_side in enumerate(beam_b_ref_sides): + ref_side_angles[ref_side_index] = angle_vectors(ref_side.normal, centerline_vec) + + return ref_side_angles + + +def beam_ref_side_incidence_with_vector(beam_b, vector, ignore_ends=True): + """ + Returns a map of ref_side indices of beam_b and the angle of their normal with a given vector. + + This is used to find a cutting plane when joining two beams where one beam is represented by the normal of one of it's reference sides. + + Parameters + ---------- + beam_b : :class:`~compas_timber.parts.Beam` + The beam for which ref_side angles will be calculated. + vector : :class:`~compas.geometry.Vector` + The vector to compare against the ref_sides' normals. + ignore_ends : bool, optional + If True, only the first four ref_sides of `beam_b` are considered. Otherwise all ref_sides are considered. + + Examples + -------- + >>> vector = Vector(1, 0, 0) + >>> ref_side_angles = Joint.ref_side_incidence_with_vector(beam_b, vector) + >>> closest_ref_side_index = min(ref_side_angles, key=ref_side_angles.get) + >>> cutting_plane = beam_b.ref_sides[closest_ref_side_index] + + Returns + ------- + dict(int, float) + A map of ref_side indices of beam_b and their respective angle with the given vector. + + """ + if ignore_ends: + beam_b_ref_sides = beam_b.ref_sides[:4] + else: + beam_b_ref_sides = beam_b.ref_sides + + ref_side_angles = {} + for ref_side_index, ref_side in enumerate(beam_b_ref_sides): + ref_side_angles[ref_side_index] = angle_vectors(vector, ref_side.normal) + + return ref_side_angles + + +def are_beams_coplanar(beam_a, beam_b, tolerance=1e-3): + """ + Checks if two beams are coplanar based on the cross product of their centerline directions. + + Parameters + ---------- + beam_a : :class:`~compas_timber.parts.Beam` + The first beam. + beam_b : :class:`~compas_timber.parts.Beam` + The second beam. + tolerance : float, optional + The tolerance for the dot product comparison, default is 1e-3. + + Returns + ------- + bool + True if the beams are coplanar, False otherwise. + """ + # Compute the cross product of the centerline directions of the two beams + cross_vector = beam_a.centerline.direction.cross(beam_b.centerline.direction) + cross_vector.unitize() + + # Check dot products of the cross product with the normals of both beams' frames + dot_with_beam_b_normal = abs(cross_vector.dot(beam_b.frame.normal)) + dot_with_beam_a_normal = abs(cross_vector.dot(beam_a.frame.normal)) + + # Check if both dot products are close to 0 or 1 (indicating coplanarity) + return (1 - tolerance <= dot_with_beam_b_normal <= 1 + tolerance or 0 <= dot_with_beam_b_normal <= tolerance) and ( + 1 - tolerance <= dot_with_beam_a_normal <= 1 + tolerance or 0 <= dot_with_beam_a_normal <= tolerance + ) + + +def check_beam_alignment(beam_a, beam_b, tolerance=1e-3): + """ + Checks the alignment of two beams by comparing their centerline directions and the normal of beam_b's frame. + + This function calculates the cross product of the two beams' centerline directions and checks the alignment by + computing the dot product with the normal of beam_b's frame. The result can be used for further decisions based + on the alignment of the beams. + + Parameters + ---------- + beam_a : :class:`~compas_timber.parts.Beam` + The first beam, used to check the alignment with beam_b. + beam_b : :class:`~compas_timber.parts.Beam` + The second beam, whose alignment is checked relative to beam_a. + tolerance : float, optional + A tolerance value for determining near-perpendicular or near-parallel alignment. Default is 1e-3. + + Returns + ------- + bool + True if the beams are nearly perpendicular, False if they are not. + """ + # Compute the cross product of the centerline directions of the two beams + cross_vector = beam_a.centerline.direction.cross(beam_b.centerline.direction) + cross_vector.unitize() + + # Calculate the dot product of the cross vector with the normal of beam_b's frame + dot_with_beam_b_normal = abs(cross_vector.dot(beam_b.frame.normal)) + + # Return True if the beams are nearly perpendicular (dot product close to 0) + return 0 <= dot_with_beam_b_normal <= tolerance From 00f2e9d02590a536be29aa5a9c2d8bb980e60cf9 Mon Sep 17 00:00:00 2001 From: papachap Date: Wed, 16 Oct 2024 16:16:17 +0200 Subject: [PATCH 59/63] unittest, ghx, changelog --- CHANGELOG.md | 19 +- tests/compas_timber/gh/test_step_joint.ghx | 5013 ++++++++------------ tests/compas_timber/test_step_joint.py | 251 + 3 files changed, 2350 insertions(+), 2933 deletions(-) create mode 100644 tests/compas_timber/test_step_joint.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 80ea4c256..001e54fb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,21 +5,6 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased - -### Added - -* Added new `compas_timber._fabrication.StepJoint`. -* Aded new `compas_timber._fabrication.StepJointNotch`. -* Aded new `compas_timber.connections.TStepJoint`. -* Added `side_as_surface` to `compas_timber.elements.Beam`. - -### Changed -* Fixed the discrepancies between `height` and `width` attributes of `Beam` - -### Removed - - ## Unreleased ### Added @@ -37,6 +22,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added new `compas_timber._fabrication.JackRafterCutParams`. * Added new `compas_timber._fabrication.Drilling`. * Added new `compas_timber._fabrication.DrillingParams`. +* Added new `compas_timber._fabrication.StepJoint`. +* Added new `compas_timber._fabrication.StepJointNotch`. +* Added new `compas_timber.connections.TStepJoint`. +* Added new `utilities` module in `connections` package. ### Changed diff --git a/tests/compas_timber/gh/test_step_joint.ghx b/tests/compas_timber/gh/test_step_joint.ghx index 02f99d70a..2714dd7cb 100644 --- a/tests/compas_timber/gh/test_step_joint.ghx +++ b/tests/compas_timber/gh/test_step_joint.ghx @@ -48,10 +48,10 @@ - -1382 - -273 + 95 + 68 - 0.5546324 + 0.151132211 @@ -95,42 +95,10 @@ - 87 + 81 - + - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 1631fe30-c80f-49ec-9128-96481121ad52 - a8a91d2c-6d94-4451-b47e-a81520b31d82 - ae229730-bb17-41d8-826e-9f750d58d128 - 54426880-0968-40ad-8b1e-e50de0a67289 - 46feaa45-4a40-458f-a4fd-9de5989af911 - ca0ea429-34fb-4ce1-a30e-2c14ddc1552e - 1dc4a321-0021-4a13-90df-e852c7e4e773 - f05fce76-e87f-48e1-a86d-07f6d4084049 - 8 - 0b9fb065-5070-4c3f-96bb-831d0aa1aedf - Group - - - - - - - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -163,7 +131,7 @@ - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -205,14 +173,14 @@ - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group - + 1 150;255;255;255 @@ -234,7 +202,8 @@ d22d460d-f45b-4227-b1b7-0d4f280ddbcc c0a53353-9802-4df7-a063-6228afad4537 e5f92ef7-182d-4ea0-8d5a-b01aada26ebb - 16 + 1994870e-f2d3-43af-9b16-274f462b9ef7 + 17 79ebf2fa-3075-4ae3-8ba4-692a9210d262 Group @@ -245,7 +214,7 @@ - + 410755b1-224a-4c1e-a407-bf32fb45ea7e 00000000-0000-0000-0000-000000000000 @@ -253,7 +222,7 @@ - + from compas_rhino.conversions import surface_to_rhino from compas_timber._fabrication import StepJointNotch from compas_timber._fabrication import StepJointNotchParams @@ -322,6 +291,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) false ca34b946-50be-42d7-8e6d-080419aa1aba false + true true GhPython Script Python @@ -331,13 +301,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2259 - 380 + 425 203 164 2355 - 462 + 507 @@ -363,10 +333,11 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + true Script variable Python 050b78a7-8649-45a7-8e2b-9f2c8fe617b8 + true cross_beam cross_beam true @@ -381,23 +352,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 382 + 427 79 20 2302 - 392 + 437 - + true Script input main_beam. 85e336ca-36fd-4257-ae26-80a079e9d946 + true main_beam main_beam true @@ -412,23 +384,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 402 + 447 79 20 2302 - 412 + 457 - + true Script input index. 63012ab7-70b5-4b48-adbc-da1744ff7b8e + true index index true @@ -443,23 +416,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 422 + 467 79 20 2302 - 432 + 477 - + true Script input ref_side_index. a5edb702-630e-4672-9612-89a6f43ae14c + true ref_side_index ref_side_index true @@ -474,23 +448,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 442 + 487 79 20 2302 - 452 + 497 - + true Script input step. 7d77a23a-b3b1-4179-80e8-6045dbf22259 + true step step true @@ -505,23 +480,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 462 + 507 79 20 2302 - 472 + 517 - + true Script input heel. 62418504-ee4a-4be5-84d5-25d7f04b2671 + true heel heel true @@ -536,23 +512,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 482 + 527 79 20 2302 - 492 + 537 - + true Script input tapered. 1ff95643-005d-47e6-a338-fca927af62e5 + true tapered tapered true @@ -567,23 +544,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 502 + 547 79 20 2302 - 512 + 557 - + true Script input mortise_height. 9355bd0c-5761-49ff-8c36-c610f66d7569 + true mortise_height mortise_height true @@ -598,22 +576,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 522 + 567 79 20 2302 - 532 + 577 - + The execution information, as output and error streams 40308c84-a0ba-4c41-b158-38a3341beb2c + true out out false @@ -624,22 +603,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2370 - 382 + 427 90 22 2415 - 393.4286 + 438.4286 - + Script output step_joint_notch. b61795d0-9c3c-4a69-b52e-e2b15de8d4c0 + true step_joint_notch step_joint_notch false @@ -650,22 +630,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2370 - 404 + 449 90 23 2415 - 416.2857 + 461.2857 - + Script output rg_cutting_plane. 602ccbf4-1274-4cd9-8349-ba4c5ba030fe + true rg_cutting_plane rg_cutting_plane false @@ -676,22 +657,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2370 - 427 + 472 90 23 2415 - 439.1429 + 484.1429 - + Script output rg_planes. 16bc55f9-a035-4099-834e-e9dc0931b695 + true rg_planes rg_planes false @@ -702,22 +684,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2370 - 450 + 495 90 23 2415 - 462 + 507 - + Script output rg_ref_side. ddc1d959-4491-4f72-b7d5-a74d74b99385 + true rg_ref_side rg_ref_side false @@ -728,22 +711,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2370 - 473 + 518 90 23 2415 - 484.8571 + 529.8572 - + Script output rg_geometry. b10f1b85-d84a-4d8b-84d3-f956ea4e2805 + true rg_geometry rg_geometry false @@ -754,22 +738,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2370 - 496 + 541 90 23 2415 - 507.7143 + 552.7143 - + Script output btlx_params. d525eccb-5bff-4563-b75e-472cbbdc5901 + true btlx_params btlx_params false @@ -780,13 +765,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2370 - 519 + 564 90 22 2415 - 530.5714 + 575.5714 @@ -798,16 +783,17 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 59e0b89a-e487-49f8-bab8-b5bab16be14c Panel - + A panel for custom notes and text values cfb4b1f8-a02e-44ce-9c95-80b1164d7fca + true Panel BTLx Params false @@ -821,7 +807,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2672 - 601 + 647 212 333 @@ -830,7 +816,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 0 2672.977 - 601.1305 + 647.8235 @@ -851,18 +837,19 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 537b0419-bbc2-4ff4-bf08-afe526367b2c Custom Preview - + Allows for customized geometry previews true false 9e4f0b21-759b-4c9a-8dfb-6654484027c5 + true Custom Preview Preview @@ -872,21 +859,22 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2841 - 526 + 571 48 44 2875 - 548 + 593 - + Geometry to preview true bc0fc1fe-a18e-40ca-ab8a-8ba765dde1f9 + true Geometry G false @@ -898,22 +886,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2843 - 528 + 573 17 20 2853 - 538 + 583 - + The material override 8cbb31fa-4051-471d-87be-5ce455d7f325 + true Material M false @@ -925,13 +914,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2843 - 548 + 593 17 20 2853 - 558 + 603 @@ -971,17 +960,18 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 0148a65d-6f42-414a-9db7-9a9b2eb78437 Brep Edges - + Extract the edge curves of a brep. true be8a15e2-8773-4720-9d61-dfdc10761646 + true Brep Edges Edges @@ -990,20 +980,21 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2741 - 419 + 464 72 64 2771 - 451 + 496 - + Base Brep 0056a757-f1c7-4722-83e9-e00c5e697cfc + true Brep B false @@ -1015,23 +1006,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2743 - 421 + 466 13 60 2751 - 451 + 496 - + 1 Naked edge curves 2fc3daa1-27be-494a-9118-8afa0df8edc7 + true Naked En false @@ -1042,23 +1034,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2786 - 421 + 466 25 20 2798.5 - 431 + 476 - + 1 Interior edge curves 7db965ba-79d8-42a1-926c-cc7c5ea6a716 + true Interior Ei false @@ -1069,23 +1062,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2786 - 441 + 486 25 20 2798.5 - 451 + 496 - + 1 Non-Manifold edge curves dfe49992-1aa8-4a4e-bf86-f66139ca06c5 + true Non-Manifold Em false @@ -1096,13 +1090,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2786 - 461 + 506 25 20 2798.5 - 471 + 516 @@ -1112,17 +1106,18 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 Plane - + Contains a collection of three-dimensional axis-systems true 482410b9-724c-46d6-be3a-3d63785bc853 + true Plane Pln false @@ -1134,13 +1129,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2839 - 371 + 418 50 24 2864.272 - 383.8677 + 430.5607 @@ -1148,17 +1143,18 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 Plane - + Contains a collection of three-dimensional axis-systems true 9c473eb6-ff8f-4c7f-bd69-56527d0257f6 + true Plane Pln false @@ -1170,13 +1166,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2838 - 300 + 347 50 24 2863.631 - 312.9232 + 359.6162 @@ -1184,17 +1180,18 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 Plane - + Contains a collection of three-dimensional axis-systems true 88b4a335-192d-48a1-81d5-163278eaecbe + true Plane Pln false @@ -1206,13 +1203,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2837 - 236 + 283 50 24 2862.671 - 248.4953 + 295.1883 @@ -1220,20 +1217,21 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 59e0b89a-e487-49f8-bab8-b5bab16be14c Panel - + A panel for custom notes and text values 84325f58-aa64-43ef-9c59-6631e3ff4da9 + true Panel false - 1 + 0 40308c84-a0ba-4c41-b158-38a3341beb2c 1 Double click to edit panel content… @@ -1243,7 +1241,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2261 - 293 + 339 201 85 @@ -1252,7 +1250,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 0 2261.519 - 293.1643 + 339.8573 @@ -1273,7 +1271,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -1298,7 +1296,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -1323,7 +1321,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + a77d0879-94c2-4101-be44-e4a616ffeb0c 5f86fa9f-c62b-50e8-157b-b454ef3e00fa @@ -1331,10 +1329,11 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + Custom Preview with Lineweights true 56020ec2-ccb1-4ae9-a538-84dcb2857db9 + true Custom Preview Lineweights PreviewLW @@ -1344,21 +1343,22 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2842 - 440 + 485 46 84 2874 - 482 + 527 - + Geometry to preview true 11bd5cdd-8b99-4483-950a-c0cb8f29576c + true Geometry G false @@ -1370,22 +1370,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2844 - 442 + 487 15 20 2853 - 452 + 497 - + The preview shader override 7eb996a5-eb09-476d-8a9f-c49f7a1c895e + true Shader S false @@ -1397,13 +1398,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2844 - 462 + 507 15 20 2853 - 472 + 517 @@ -1440,9 +1441,10 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + The thickness of the wire display c3053791-290f-494e-8384-81e8464e4dc4 + true Thickness T true @@ -1453,22 +1455,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2844 - 482 + 527 15 20 2853 - 492 + 537 - + Set to true to try to render curves with an absolute dimension. 073aa3ff-0ed7-42b6-bdd5-c5b25159731c + true Absolute A false @@ -1479,13 +1482,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2844 - 502 + 547 15 20 2853 - 512 + 557 @@ -1515,7 +1518,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -1540,7 +1543,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -1565,7 +1568,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 410755b1-224a-4c1e-a407-bf32fb45ea7e 00000000-0000-0000-0000-000000000000 @@ -1573,7 +1576,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + import compas from compas_rhino.conversions import surface_to_rhino from compas_timber._fabrication import StepJointNotch @@ -1641,6 +1644,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) false d61a37f6-bab7-4ae1-ba80-a47758fff264 false + true true GhPython Script Python @@ -1650,13 +1654,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2258 - 1269 + 2066 199 164 2350 - 1351 + 2148 @@ -1682,10 +1686,11 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + true Script variable Python 89f4c804-fb16-4265-9e6e-caed7fd7d204 + true cross_beam cross_beam true @@ -1700,23 +1705,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2260 - 1271 + 2068 75 20 2299 - 1281 + 2078 - + true Script input main_beam. 85dee51e-2525-4470-aa5b-a8dca308f7c3 + true main_beam main_beam true @@ -1731,23 +1737,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2260 - 1291 + 2088 75 20 2299 - 1301 + 2098 - + true Script input index. b6fa8f96-678e-41b5-afd0-053ec6fd5ad0 + true index index true @@ -1762,23 +1769,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2260 - 1311 + 2108 75 20 2299 - 1321 + 2118 - + true Script input ref_side_index. 2d345ebe-38ca-4427-b0ec-c749512b75fd + true ref_side_index ref_side_index true @@ -1793,23 +1801,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2260 - 1331 + 2128 75 20 2299 - 1341 + 2138 - + true Script input step. 2d3faeb6-6f29-4e99-ad70-d5c0711d5865 + true step step true @@ -1824,23 +1833,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2260 - 1351 + 2148 75 20 2299 - 1361 + 2158 - + true Script input heel. 5124721f-0a65-441b-863d-ed7dfa0eedab + true heel heel true @@ -1855,23 +1865,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2260 - 1371 + 2168 75 20 2299 - 1381 + 2178 - + true Script input tapered. 79536ba5-02e1-4e89-814b-5d3beff1df9b + true tapered tapered true @@ -1886,23 +1897,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2260 - 1391 + 2188 75 20 2299 - 1401 + 2198 - + true Script input tenon_height. 74b917c7-fc1a-4dec-abae-860ed10a0b11 + true tenon_height tenon_height true @@ -1917,22 +1929,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2260 - 1411 + 2208 75 20 2299 - 1421 + 2218 - + The execution information, as output and error streams 8d656ac0-a47a-48cd-bd30-510e0169b258 + true out out false @@ -1943,22 +1956,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2365 - 1271 + 2068 90 22 2410 - 1282.429 + 2079.428 - + Script output step_joint. 144c99d6-3d48-4727-b642-5c61dd368619 + true step_joint step_joint false @@ -1969,22 +1983,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2365 - 1293 + 2090 90 23 2410 - 1305.286 + 2102.286 - + Script output rg_cutting_plane. 9fe4a86c-3c6e-4f70-8cbe-630b57b5a0b1 + true rg_cutting_plane rg_cutting_plane false @@ -1995,22 +2010,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2365 - 1316 + 2113 90 23 2410 - 1328.143 + 2125.143 - + Script output rg_planes. 6270f634-eb6d-402e-a88f-24f478dcf237 + true rg_planes rg_planes false @@ -2021,22 +2037,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2365 - 1339 + 2136 90 23 2410 - 1351 + 2148 - + Script output rg_ref_side. 9c129a89-24d5-45f5-bcea-890db46f3da4 + true rg_ref_side rg_ref_side false @@ -2047,22 +2064,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2365 - 1362 + 2159 90 23 2410 - 1373.857 + 2170.857 - + Script output rg_geometry. 441ff4ac-6060-44cf-965a-55428a9d4cc6 + true rg_geometry rg_geometry false @@ -2073,22 +2091,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2365 - 1385 + 2182 90 23 2410 - 1396.714 + 2193.714 - + Script output btlx_params. 269c79e7-4269-4669-b62a-9869376ea11c + true btlx_params btlx_params false @@ -2099,13 +2118,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2365 - 1408 + 2205 90 23 2410 - 1419.571 + 2216.571 @@ -2117,16 +2136,17 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider - + Numeric slider for single values 37c9fe3d-b5bb-4362-ba0f-d5053fcb2141 + true Number Slider false @@ -2136,14 +2156,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1635 - 873 + 896 + 1794 201 20 - 1635.588 - 873.239 + 896.0309 + 1794.528 @@ -2162,16 +2182,17 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider - + Numeric slider for single values 7e4bcb53-01cc-446e-8792-764d02b1451e + true Number Slider false @@ -2181,14 +2202,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1635 - 897 + 896 + 1818 201 20 - 1635.692 - 897.5591 + 896.1349 + 1818.849 @@ -2207,16 +2228,17 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 59e0b89a-e487-49f8-bab8-b5bab16be14c Panel - + A panel for custom notes and text values 52c22682-fb60-48d8-ae45-8e60302c086f + true Panel BTLx Params false @@ -2230,7 +2252,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2670 - 1498 + 2297 212 333 @@ -2239,7 +2261,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 0 2670.888 - 1498.868 + 2297.79 @@ -2260,16 +2282,17 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider - + Numeric slider for single values c3c1dc6d-7ceb-4d7f-add6-fa924b4cc936 + true Number Slider false @@ -2279,14 +2302,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1635 - 965 + 896 + 1886 160 20 - 1635.78 - 965.2471 + 896.2229 + 1886.536 @@ -2298,23 +2321,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 200 0 0 - 30 + 0 - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider - + Numeric slider for single values ab977350-6a8d-472b-9276-52a400da755b + true Number Slider false @@ -2324,14 +2348,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1635 - 988 + 896 + 1909 160 20 - 1635.779 - 988.3201 + 896.2219 + 1909.61 @@ -2343,35 +2367,36 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 200 0 0 - 0 + 20 - + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a Boolean Toggle - + Boolean (true/false) toggle dec5089d-c452-417b-a948-4034d6df42ce + true Boolean Toggle tapered_heel false 0 - false + true - 1635 - 1010 + 895 + 1931 133 22 @@ -2381,18 +2406,19 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 537b0419-bbc2-4ff4-bf08-afe526367b2c Custom Preview - + Allows for customized geometry previews true false def048ab-0443-4189-81d6-03594953cdd1 + true Custom Preview Preview @@ -2402,21 +2428,22 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2840 - 1423 + 2220 48 44 2874 - 1445 + 2242 - + Geometry to preview true 977e596e-9c8a-4d5e-a490-c2c39fe7f008 + true Geometry G false @@ -2428,22 +2455,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2842 - 1425 + 2222 17 20 2852 - 1435 + 2232 - + The material override 5f9e9e88-be8b-4453-b5f6-8e8fcf54697c + true Material M false @@ -2455,13 +2483,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2842 - 1445 + 2242 17 20 2852 - 1455 + 2252 @@ -2501,17 +2529,18 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 0148a65d-6f42-414a-9db7-9a9b2eb78437 Brep Edges - + Extract the edge curves of a brep. true aa461d11-c653-411c-a341-b7900f1ccbd6 + true Brep Edges Edges @@ -2520,20 +2549,21 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2739 - 1313 + 2110 72 64 2769 - 1345 + 2142 - + Base Brep d6ae26e7-62fd-4655-9999-cf54b249138d + true Brep B false @@ -2545,23 +2575,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2741 - 1315 + 2112 13 60 2749 - 1345 + 2142 - + 1 Naked edge curves ef4b92cd-9e89-42cc-aed4-b855fa6a4238 + true Naked En false @@ -2572,23 +2603,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2784 - 1315 + 2112 25 20 2796.5 - 1325 + 2122 - + 1 Interior edge curves 920e2adf-799f-431e-ada8-588d6ab271ba + true Interior Ei false @@ -2599,23 +2631,24 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2784 - 1335 + 2132 25 20 2796.5 - 1345 + 2142 - + 1 Non-Manifold edge curves 269805b0-ae89-4079-9ee5-97986acf75e1 + true Non-Manifold Em false @@ -2626,13 +2659,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2784 - 1355 + 2152 25 20 2796.5 - 1365 + 2162 @@ -2642,17 +2675,18 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 Plane - + Contains a collection of three-dimensional axis-systems true 5ad83719-a82f-419e-8606-51aa09e83d11 + true Plane Pln false @@ -2664,13 +2698,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2837 - 1254 + 2053 50 24 2862.183 - 1266.925 + 2065.847 @@ -2678,17 +2712,18 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 Plane - + Contains a collection of three-dimensional axis-systems true bec3cac8-0564-45af-9acc-545be5c1d193 + true Plane Pln false @@ -2700,13 +2735,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2836 - 1185 + 1984 50 24 2861.542 - 1197.58 + 1996.501 @@ -2714,17 +2749,18 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 Plane - + Contains a collection of three-dimensional axis-systems true 6e7fcdb6-9b16-4ad8-90a6-6d7bd2fe16a2 + true Plane Pln false @@ -2736,13 +2772,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2835 - 1115 + 1914 50 24 2860.975 - 1127.124 + 1926.046 @@ -2750,20 +2786,21 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 59e0b89a-e487-49f8-bab8-b5bab16be14c Panel - + A panel for custom notes and text values b8e83b08-cfb9-49e9-b847-ee42b020c654 + true Panel false - 1 + 0 8d656ac0-a47a-48cd-bd30-510e0169b258 1 Double click to edit panel content… @@ -2773,7 +2810,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2258 - 1185 + 1984 199 82 @@ -2782,7 +2819,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 0 2258.006 - 1185.64 + 1984.562 @@ -2803,7 +2840,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -2828,7 +2865,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -2853,7 +2890,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + a77d0879-94c2-4101-be44-e4a616ffeb0c 5f86fa9f-c62b-50e8-157b-b454ef3e00fa @@ -2861,10 +2898,11 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + Custom Preview with Lineweights true 0cbceed4-3434-411c-b4d1-46985ef14dea + true Custom Preview Lineweights PreviewLW @@ -2874,21 +2912,22 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2839 - 1333 + 2130 46 84 2871 - 1375 + 2172 - + Geometry to preview true bd3da4c6-368d-4f8f-afa0-7241f56b0323 + true Geometry G false @@ -2900,22 +2939,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2841 - 1335 + 2132 15 20 2850 - 1345 + 2142 - + The preview shader override f7c73e31-145c-457b-8538-038ea41e36ce + true Shader S false @@ -2927,13 +2967,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2841 - 1355 + 2152 15 20 2850 - 1365 + 2162 @@ -2970,9 +3010,10 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + The thickness of the wire display be5283ef-c093-4c55-a00a-f176aa83c876 + true Thickness T true @@ -2983,22 +3024,23 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2841 - 1375 + 2172 15 20 2850 - 1385 + 2182 - + Set to true to try to render curves with an absolute dimension. fb708cb7-0800-40a7-b44a-8228329e5775 + true Absolute A false @@ -3009,13 +3051,13 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 2841 - 1395 + 2192 15 20 2850 - 1405 + 2202 @@ -3045,7 +3087,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -3070,16 +3112,17 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider - + Numeric slider for single values 1f382d1c-8a89-4e8a-8102-597801cd81a3 + true Number Slider Tenon/Mortise Height false @@ -3089,14 +3132,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - 1635 - 1081 + 895 + 2002 240 20 - 1635.166 - 1081.048 + 895.6089 + 2002.337 @@ -3108,14 +3151,14 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) 100 0 0 - 40 + 0 - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -3141,7 +3184,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -3168,7 +3211,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -3193,7 +3236,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group @@ -3218,66 +3261,7 @@ rg_planes = (plane_to_rhino(plane) for plane in cutting_planes) - - - d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 - Curve - - - - - Contains a collection of generic curves - true - 1631fe30-c80f-49ec-9128-96481121ad52 - Curve - Crv - false - 0 - - - - - - 354 - 513 - 50 - 24 - - - 379.472 - 525.6697 - - - - - - 1 - - - - - 1 - {0} - - - - - -1 - - Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesYmekbGRmbmeoaGBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS9f/4eDiZgQx+EDEViJl+1TMxdEL99fs/E4MIlP3synU/95eXBfc/ZRBoZlT64Ddn9+0/QPlAqLwAyNzYbxpcrDPvOXz8/18+rHjlAYmi2O741CCH48eNlj7zvu9wBCoe0mqltvDcBAcmBgTY2xy1xs52sgPIJY3vmdz+1zM1wOS4GQYTAAA= - - 00000000-0000-0000-0000-000000000000 - - - - - - - - - - - + 410755b1-224a-4c1e-a407-bf32fb45ea7e 00000000-0000-0000-0000-000000000000 @@ -3404,13 +3388,13 @@ class Beam_fromCurve(component): 1018 - 368 + 1273 145 124 1109 - 430 + 1335 @@ -3448,13 +3432,13 @@ class Beam_fromCurve(component): 1020 - 370 + 1275 74 20 1058.5 - 380 + 1285 @@ -3479,13 +3463,13 @@ class Beam_fromCurve(component): 1020 - 390 + 1295 74 20 1058.5 - 400 + 1305 @@ -3502,7 +3486,7 @@ class Beam_fromCurve(component): true 1 true - d9d51b18-d5af-4f8f-822f-9fbd0f30d642 + c64fa590-0923-4f6c-bc02-60bf9bf5e33c 1 39fbc626-7a01-46ab-a18e-ec1c0c41685b @@ -3511,13 +3495,13 @@ class Beam_fromCurve(component): 1020 - 410 + 1315 74 20 1058.5 - 420 + 1325 @@ -3543,13 +3527,13 @@ class Beam_fromCurve(component): 1020 - 430 + 1335 74 20 1058.5 - 440 + 1345 @@ -3574,13 +3558,13 @@ class Beam_fromCurve(component): 1020 - 450 + 1355 74 20 1058.5 - 460 + 1365 @@ -3604,13 +3588,13 @@ class Beam_fromCurve(component): 1020 - 470 + 1375 74 20 1058.5 - 480 + 1385 @@ -3630,13 +3614,13 @@ class Beam_fromCurve(component): 1124 - 370 + 1275 37 60 1142.5 - 400 + 1305 @@ -3656,13 +3640,13 @@ class Beam_fromCurve(component): 1124 - 430 + 1335 37 60 1142.5 - 460 + 1365 @@ -3674,7 +3658,7 @@ class Beam_fromCurve(component): - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -3693,14 +3677,14 @@ class Beam_fromCurve(component): - 798 - 440 + 795 + 1340 166 20 - 798.3945 - 440.6029 + 795.5547 + 1340.486 @@ -3709,17 +3693,17 @@ class Beam_fromCurve(component): 3 1 1 - 100 + 120 0 0 - 100 + 120 - + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 Curve @@ -3740,13 +3724,13 @@ class Beam_fromCurve(component): 350 - 369 + 1274 50 24 375.6421 - 381.6854 + 1286.721 @@ -3778,7 +3762,7 @@ class Beam_fromCurve(component): - + 410755b1-224a-4c1e-a407-bf32fb45ea7e 00000000-0000-0000-0000-000000000000 @@ -3905,13 +3889,13 @@ class Beam_fromCurve(component): 1016 - 576 + 1481 145 124 1107 - 638 + 1543 @@ -3940,7 +3924,7 @@ class Beam_fromCurve(component): true 1 true - 09fe95fb-9551-4690-abf6-4a0665002914 + 1c09c813-ee95-4356-93ff-668de076cf9e 1 87f87f55-5b71-41f4-8aea-21d494016f81 @@ -3949,13 +3933,13 @@ class Beam_fromCurve(component): 1018 - 578 + 1483 74 20 1056.5 - 588 + 1493 @@ -3980,13 +3964,13 @@ class Beam_fromCurve(component): 1018 - 598 + 1503 74 20 1056.5 - 608 + 1513 @@ -4003,7 +3987,7 @@ class Beam_fromCurve(component): true 1 true - da50cc8d-94c7-4348-b6e9-2f46272647d3 + 478fc3b1-c4f8-4087-8fe6-c2905e21be9a 1 39fbc626-7a01-46ab-a18e-ec1c0c41685b @@ -4012,13 +3996,13 @@ class Beam_fromCurve(component): 1018 - 618 + 1523 74 20 1056.5 - 628 + 1533 @@ -4035,7 +4019,7 @@ class Beam_fromCurve(component): true 1 true - 478fc3b1-c4f8-4087-8fe6-c2905e21be9a + da50cc8d-94c7-4348-b6e9-2f46272647d3 1 39fbc626-7a01-46ab-a18e-ec1c0c41685b @@ -4044,13 +4028,13 @@ class Beam_fromCurve(component): 1018 - 638 + 1543 74 20 1056.5 - 648 + 1553 @@ -4075,13 +4059,13 @@ class Beam_fromCurve(component): 1018 - 658 + 1563 74 20 1056.5 - 668 + 1573 @@ -4105,13 +4089,13 @@ class Beam_fromCurve(component): 1018 - 678 + 1583 74 20 1056.5 - 688 + 1593 @@ -4131,13 +4115,13 @@ class Beam_fromCurve(component): 1122 - 578 + 1483 37 60 1140.5 - 608 + 1513 @@ -4157,13 +4141,13 @@ class Beam_fromCurve(component): 1122 - 638 + 1543 37 60 1140.5 - 668 + 1573 @@ -4175,7 +4159,7 @@ class Beam_fromCurve(component): - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -4194,14 +4178,14 @@ class Beam_fromCurve(component): - 833 - 635 + 824 + 1523 166 20 - 833.363 - 635.7318 + 824.523 + 1523.767 @@ -4213,14 +4197,14 @@ class Beam_fromCurve(component): 100 0 0 - 100 + 60 - + 57da07bd-ecab-415d-9d86-af36d7073abc Number Slider @@ -4239,14 +4223,14 @@ class Beam_fromCurve(component): - 820 - 663 + 824 + 1548 166 20 - 820.9324 - 663.3899 + 824.3324 + 1548.025 @@ -4265,1142 +4249,745 @@ class Beam_fromCurve(component): - + - 22990b1f-9be6-477c-ad89-f775cd347105 - Flip Curve + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group - - Flip a curve using an optional guide curve. - true - 2098ce01-e0b3-4e52-b0ed-3c62bb419a62 - Flip Curve - Flip + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 1631fe30-c80f-49ec-9128-96481121ad52 + ca0ea429-34fb-4ce1-a30e-2c14ddc1552e + 645777e9-3e35-4980-b9de-1af150e02dd0 + 0427415b-7064-4ecf-875e-ad8efb142eee + c285f14a-7ead-4d46-9545-12950096c0b4 + 5 + 1dc4a321-0021-4a13-90df-e852c7e4e773 + Group + MainBeam - + + + + + + + + + 9c53bac0-ba66-40bd-8154-ce9829b9db1a + Colour Swatch + + + + + Colour (palette) swatch + c0a53353-9802-4df7-a063-6228afad4537 + true + Colour Swatch + Swatch + false + 0 + + 34;240;180;137 + + + - 818 - 583 - 66 - 44 + 2737 + 603 + 88 + 20 - 850 - 605 + 2737.545 + 603.7844 - - - Curve to flip - 417fea5c-f458-4cb9-81ea-3c456faf67b1 - Curve - C - false - b37fb167-edb3-4a28-b8fd-a498a7811210 - 1 - - - - - - 820 - 585 - 15 - 20 - - - 829 - 595 - - - - - - - - Optional guide curve - 0cee7869-6730-4b7d-832a-4aa6b5d4749a - Guide - G - true - 0 - - - - - - 820 - 605 - 15 - 20 - - - 829 - 615 - - - - - - - - Flipped curve - 0b2eef87-f147-4a36-b443-85031493ef71 - Curve - C - false - 0 - - - - - - 865 - 585 - 17 - 20 - - - 873.5 - 595 - - - - - - - - Flip action - d77f6082-7255-4694-8fae-7a31bce87565 - Flag - F - false - 0 - - - - - - 865 - 605 - 17 - 20 - - - 873.5 - 615 - - - - - - + - 11bbd48b-bb0a-4f1b-8167-fa297590390d - End Points + 7f5c6c55-f846-4a08-9c9a-cfdc285cc6fe + Scribble - - Extract the end points of a curve. - true - 3606e2e7-dfd8-4977-a4d6-75b937b9b9f1 - End Points - End + + true + + 2405.003 + 218.149 + + + 2762.522 + 218.149 + + + 2762.522 + 265.4879 + + + 2405.003 + 265.4879 + + A quick note + Microsoft Sans Serif + 1fda4fd4-ed01-4e84-8338-ca710b993b83 + false + Scribble + Scribble + 50 + StepJointNotch - + - 513 - 553 - 64 - 44 + 2400.003 + 213.149 + 367.5195 + 57.33887 - 544 - 575 + 2405.003 + 218.149 - - - Curve to evaluate - c35e0949-31c0-4148-833b-aeed3fc97aa3 - Curve - C - false - 1631fe30-c80f-49ec-9128-96481121ad52 - 1 - - - - - - 515 - 555 - 14 - 40 - - - 523.5 - 575 - - - - - - - - Curve start point - 349dd0c5-02cc-4111-abf4-8728c5a86c2c - Start - S - false - 0 - - - - - - 559 - 555 - 16 - 20 - - - 567 - 565 - - - - - - - - Curve end point - 08d2450a-ba8f-45b3-a29d-274a84997a85 - End - E - false - 0 - - - - - - 559 - 575 - 16 - 20 - - - 567 - 585 - - - - - + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 1fda4fd4-ed01-4e84-8338-ca710b993b83 + 1 + e5f92ef7-182d-4ea0-8d5a-b01aada26ebb + Group + + + + - e9eb1dcf-92f6-4d4d-84ae-96222d60f56b - Move + 7f5c6c55-f846-4a08-9c9a-cfdc285cc6fe + Scribble - - Translate (move) an object along a vector. - true - 2a5a867a-a76c-4f02-bce5-209e79f61aac - Move - Move + + true + + 2470.777 + 1859.167 + + + 2691.895 + 1859.167 + + + 2691.895 + 1906.505 + + + 2470.777 + 1906.505 + + A quick note + Microsoft Sans Serif + 14f73845-d137-4e4f-939d-fc35194fd72e + false + Scribble + Scribble + 50 + StepJoint - + - 619 - 573 - 83 - 44 + 2465.777 + 1854.167 + 231.1182 + 57.33887 - 667 - 595 + 2470.777 + 1859.167 - - - Base geometry - a0edbf9a-3853-4ab0-b53f-aef5027fa137 - Geometry - G - true - 08d2450a-ba8f-45b3-a29d-274a84997a85 - 1 - - - - - - 621 - 575 - 31 - 20 - - - 646 - 585 - - - - - - - - Translation vector - 8acddbb8-4abe-4d9b-81aa-f36b38b9dda4 - -x - Motion - T - false - 3441a608-3853-45f3-92c7-1d1941125460 - 1 - - - - - - 621 - 595 - 31 - 20 - - - 646 - 605 - - - - - - 1 - - - - - 1 - {0} - - - - - - 0 - 0 - 10 - - - - - - - - - - - - Translated geometry - 3b2b25a6-3d8c-4a41-b0a2-3ea3be957972 - Geometry - G - false - 0 - - - - - - 682 - 575 - 18 - 20 - - - 691 - 585 - - - - - - - - Transformation data - d8dfb3a4-eb51-4258-aa5c-b6584682d0ef - Transform - X - false - 0 - - - - - - 682 - 595 - 18 - 20 - - - 691 - 605 - - - - - - 79f9fbb3-8f1d-4d9a-88a9-f7961b1012cd - Unit X + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group - - Unit vector parallel to the world {x} axis. + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 14f73845-d137-4e4f-939d-fc35194fd72e + 1 + b6362569-cd6b-4fdf-a6bc-af4ac10ea9bf + Group + + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 18d2fa4a-de1b-4cfd-bf80-4b8d5fa0182f + 1 + e22e52d8-5a24-494f-8bd6-2f5c368cced0 + Group + CrossBeam + + + + + + + + + + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep + + + + + Contains a collection of Breps (Boundary REPresentations) true - a8a91d2c-6d94-4451-b47e-a81520b31d82 - Unit X - X + b1d2aad0-ff8f-419b-9c56-f82b9b6378df + true + Brep + Brep + false + 441ff4ac-6060-44cf-965a-55428a9d4cc6 + 1 - + - 337 - 606 - 63 - 28 + 2651 + 2177 + 50 + 24 - 366 - 620 + 2676.568 + 2189.437 - - - Unit multiplication - 0640aeed-9882-4ebf-a1b1-5080f3b20bb9 - Factor - F - false - ae229730-bb17-41d8-826e-9f750d58d128 - 1 - - - - - - 339 - 608 - 12 - 24 - - - 346.5 - 620 - - - - - - 1 - - - - - 1 - {0} - - - - - 1 - - - - - - - - - - - World {x} vector - 20ce5606-82c9-43d0-b182-e8db425945b5 - Unit vector - V - false - 0 - - - - - - 381 - 608 - 17 - 24 - - - 389.5 - 620 - - - - - - + - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider + 9c53bac0-ba66-40bd-8154-ce9829b9db1a + Colour Swatch - - Numeric slider for single values - ae229730-bb17-41d8-826e-9f750d58d128 - Number Slider - + + Colour (palette) swatch + d0dd1043-724e-45c1-a8b5-6f7ff06d8073 + true + Colour Swatch + Swatch false 0 + + 29;0;102;255 + - + - 154 - 614 - 163 + 2728 + 2244 + 88 20 - 154.723 - 614.0924 + 2728.811 + 2244.521 - - - 3 - 1 - 1 - 4000 - 0 - 0 - 0 - - - + - 4c4e56eb-2f04-43f9-95a3-cc46a14f495a - Line + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep - - Create a line between two points. + + Contains a collection of Breps (Boundary REPresentations) true - f29282aa-9bfd-49b7-a1a0-1f7fc87af0e9 - Line - Ln + 91868622-66e7-4d7d-a5a0-9480a9e4a02a + true + Brep + Brep + false + b10f1b85-d84a-4d8b-84d3-f956ea4e2805 + 1 - + - 729 - 553 - 63 - 44 + 2663 + 526 + 50 + 24 - 760 - 575 + 2688.471 + 538.8702 - - - Line start point - cb99cc28-9c59-4191-95b9-3cae3e6f1e9c - Start Point - A - false - 349dd0c5-02cc-4111-abf4-8728c5a86c2c - 1 - - - - - - 731 - 555 - 14 - 20 - - - 739.5 - 565 - - - - - - - - Line end point - fb12ca80-6fc6-404b-8d93-4fdb6c2c6520 - End Point - B - false - 3b2b25a6-3d8c-4a41-b0a2-3ea3be957972 - 1 - - - - - - 731 - 575 - 14 - 20 - - - 739.5 - 585 - - - - - - - - Line segment - b37fb167-edb3-4a28-b8fd-a498a7811210 - Line - L - false - 0 - - - - - - 775 - 555 - 15 - 40 - - - 782.5 - 575 - - - - - - + - 9103c240-a6a9-4223-9b42-dbd19bf38e2b - Unit Z + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group - - Unit vector parallel to the world {z} axis. - true - 54426880-0968-40ad-8b1e-e50de0a67289 - Unit Z - Z + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + def048ab-0443-4189-81d6-03594953cdd1 + aa461d11-c653-411c-a341-b7900f1ccbd6 + 0cbceed4-3434-411c-b4d1-46985ef14dea + b1d2aad0-ff8f-419b-9c56-f82b9b6378df + d0dd1043-724e-45c1-a8b5-6f7ff06d8073 + 5 + 7dff34a0-670b-4fd3-8912-0a128d903453 + Group + GEOMETRY - - - - - 339 - 640 - 63 - 28 - - - 368 - 654 - - - - - - Unit multiplication - 45ecdc35-f4be-4e29-97b3-ae200336ed76 - Factor - F - false - 46feaa45-4a40-458f-a4fd-9de5989af911 - 1 - - - - - - 341 - 642 - 12 - 24 - - - 348.5 - 654 - - - - - - 1 - - - - - 1 - {0} - - - - - 1 - - - - - - - - - - - World {z} vector - 31dfa7fb-7b92-4e78-8a6c-51149c8b2402 - Unit vector - V - false - 0 - - - - - - 383 - 642 - 17 - 24 - - - 391.5 - 654 - - - - - + + - + - 57da07bd-ecab-415d-9d86-af36d7073abc - Number Slider + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group - - Numeric slider for single values - 46feaa45-4a40-458f-a4fd-9de5989af911 - Number Slider - - false - 0 - - - - - - 157 - 649 - 163 - 20 - - - 157.0529 - 649.1797 - - - - - - 3 - 1 - 1 - 2000 - 0 - 0 - 0 - - + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 9e4f0b21-759b-4c9a-8dfb-6654484027c5 + be8a15e2-8773-4720-9d61-dfdc10761646 + 56020ec2-ccb1-4ae9-a538-84dcb2857db9 + c0a53353-9802-4df7-a063-6228afad4537 + 91868622-66e7-4d7d-a5a0-9480a9e4a02a + 5 + 1994870e-f2d3-43af-9b16-274f462b9ef7 + Group + GEOMETRY + + + - - - a0d62394-a118-422d-abb3-6af115c75b25 - Addition + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + GhPython Script - - Mathematical addition + + from compas_timber.connections import TStepJoint + +from compas_timber.model import TimberModel +from compas_timber.fabrication import BTLx + +from compas_rhino.conversions import brep_to_rhino + +from compas_rhino import unload_modules + +unload_modules("compas_timber") + +# Instantiate TimberModel & Add Beams +timber_model = TimberModel() +beams = [cross_beam, main_beam] +for beam in beams: + beam.remove_features() + timber_model.add_element(beam) + +# Create TStepJoint joint +step_joint_joint = TStepJoint.create( + timber_model, + main_beam, + cross_beam, + step_shape=step_shape, + step_depth=step_depth, + heel_depth=heel_depth, + tapered_heel=tapered_heel, + tenon_mortise_height=tenon_mortise_height + ) + +# Process Joinery +timber_model.process_joinery() + +# Generate Geometry +geometry = [brep_to_rhino(beam.compute_geometry()) for beam in timber_model.beams] + +# Write BTLx +btlx = BTLx(timber_model) +BTLx = btlx.btlx_string() + GhPython provides a Python script component + + 924 + 209 + + + 901 + 818 + true - 31a8ef0b-5079-4e66-9612-dff6a751c5a8 - Addition - A+B + true + false + false + 20be8b4b-0eba-41a5-8f13-25defa3e633d + false + true + GhPython Script + Python - 514 - 598 - 65 - 44 + 2239 + 1399 + 203 + 144 - 545 - 620 + 2368 + 1471 - - 2 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 1 - 8ec86459-bf01-4409-baee-174d0d2b13d0 + + 7 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 3 + 3ede854e-c753-40eb-84cb-b48008f14fd4 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 - + - - First item for addition - d9d3d34d-5080-4bb5-aa7b-9e02635fbae5 - A - A + + true + Script input cross_beam. + 36ebe6e6-4cdb-4b9d-8c4a-65feaf8cee5a + cross_beam + cross_beam true - 20ce5606-82c9-43d0-b182-e8db425945b5 + 0 + true + 3984d0df-294e-462f-b103-b87bb94e3021 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 - 516 - 600 - 14 + 2241 + 1401 + 112 20 - 524.5 - 610 + 2298.5 + 1411 - - Second item for addition - 3898220f-37d7-48fc-b80c-31e2b32274f5 - B - B + + true + Script input main_beam. + 407e65c9-4ebe-4581-9401-a364b25f96c2 + main_beam + main_beam true - 31dfa7fb-7b92-4e78-8a6c-51149c8b2402 + 0 + true + 90fae02f-7750-462d-828f-cf5f2a0d39ae 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 - 516 - 620 - 14 + 2241 + 1421 + 112 20 - 524.5 - 630 + 2298.5 + 1431 - - - Result of addition - 3441a608-3853-45f3-92c7-1d1941125460 - Result - R - false - 0 + + + true + Script input step_shape. + 58ad3fdc-3692-40b4-bbbc-8848ec077c68 + step_shape + step_shape + true + 0 + true + da35a5d7-1b20-42d0-acad-65c4ca451910 + 1 + 48d01794-d3d8-4aef-990e-127168822244 - 560 - 600 - 17 - 40 + 2241 + 1441 + 112 + 20 - 568.5 - 620 + 2298.5 + 1451 - - - - - - - - - 2e78987b-9dfb-42a2-8b76-3923ac8bd91a - Boolean Toggle - - - - - Boolean (true/false) toggle - ca0ea429-34fb-4ce1-a30e-2c14ddc1552e - Boolean Toggle - FlipCurve - false - 0 - false - - - - - - 286 - 545 - 115 - 22 - - - - - - - - - - eeafc956-268e-461d-8e73-ee05c6f72c01 - Stream Filter - - - - - Filters a collection of input streams - true - 9c98408b-5774-4f45-bb43-c16e5d4c92b4 - Stream Filter - Filter - - - - - - 899 - 543 - 77 - 64 - - - 931 - 575 - - - - - - 3 - 2e3ab970-8545-46bb-836c-1c11e5610bce - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 1 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - - - - - Index of Gate stream - be384cd0-c014-4922-86ee-f8be45625fad - Gate - G - false - ca0ea429-34fb-4ce1-a30e-2c14ddc1552e + + + true + Script input step_depth. + fbd107db-8f8a-4baa-876f-623012dd3bb0 + step_depth + step_depth + true + 0 + true + 574904c9-0f6a-4dc7-9431-055e7aa620aa 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 - + - 901 - 545 - 15 + 2241 + 1461 + 112 20 - 910 - 555 + 2298.5 + 1471 - - - 1 - - - - - 1 - {0} - - - - - 0 - - - - - - - - - 2 - Input stream at index 0 - b069b652-f7a4-47dc-a04c-9b3003b0e168 - false - Stream 0 - 0 - true - b37fb167-edb3-4a28-b8fd-a498a7811210 - 1 + + + true + Script input heel_depth. + dab8fd89-b668-48ad-91eb-c695241b0e94 + heel_depth + heel_depth + true + 0 + true + fc245c73-88f5-4db4-a914-d3a9d191bd54 + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 - 901 - 565 - 15 + 2241 + 1481 + 112 20 - 910 - 575 + 2298.5 + 1491 - - - 2 - Input stream at index 1 - 4bbd6850-b99a-47c4-a371-8adee5564955 - false - Stream 1 - 1 + + + true + Script input tapered_heel. + f4ca3577-e0dc-42fa-8923-d574fb19c330 + tapered_heel + tapered_heel + true + 0 + true + 05345419-f1d5-4f4d-aea7-b888275c43f0 + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 2241 + 1501 + 112 + 20 + + + 2298.5 + 1511 + + + + + + + + true + Script input tenon_mortise_height. + e6b1d326-8808-4533-930e-154d3cd939d3 + tenon_mortise_height + tenon_mortise_height true - 0b2eef87-f147-4a36-b443-85031493ef71 + 0 + true + e4da7b43-f08d-4e84-a53a-6d4d965dca71 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 - 901 - 585 - 15 + 2241 + 1521 + 112 20 - 910 - 595 + 2298.5 + 1531 - - 2 - Filtered stream - 09fe95fb-9551-4690-abf6-4a0665002914 - false - Stream - S(0) + + The execution information, as output and error streams + fb496e59-6b43-4266-9c61-c466f3a89f8e + out + out false 0 @@ -5408,14 +4995,66 @@ class Beam_fromCurve(component): - 946 - 545 - 28 - 60 + 2383 + 1401 + 57 + 46 + + + 2411.5 + 1424.333 + + + + + + + + Script output geometry. + 188326e4-b130-4230-9b7f-f89e9a5dca84 + geometry + geometry + false + 0 + + + + + + 2383 + 1447 + 57 + 47 + + + 2411.5 + 1471 + + + + + + + + Script output BTLx. + 8674ed0a-6d18-41d1-9209-ae7aed0e558d + BTLx + BTLx + false + 0 + + + + + + 2383 + 1494 + 57 + 47 - 960 - 575 + 2411.5 + 1517.667 @@ -5429,91 +5068,143 @@ class Beam_fromCurve(component): - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 1631fe30-c80f-49ec-9128-96481121ad52 - ca0ea429-34fb-4ce1-a30e-2c14ddc1552e - 2 - 1dc4a321-0021-4a13-90df-e852c7e4e773 - Group - MainBeam + A panel for custom notes and text values + a31a3c97-3e91-47a8-add8-793f530b94e1 + Panel + + false + 0 + 8674ed0a-6d18-41d1-9209-ae7aed0e558d + 1 + G:\Shared drives\2024_MAS\T2\03_finalization\Fabrication - Joints\BTLx\step_joint_joint_test.btlx - - + + + + + 2885 + 1294 + 446 + 420 + + 0 + 0 + 0 + + 2885.897 + 1294.745 + + + + + + + 255;255;250;90 + + true + true + true + false + true + D:\Papachap\Desktop\btlx\TStepJoint.btlx + true + + - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - a8a91d2c-6d94-4451-b47e-a81520b31d82 - ae229730-bb17-41d8-826e-9f750d58d128 - 54426880-0968-40ad-8b1e-e50de0a67289 - 46feaa45-4a40-458f-a4fd-9de5989af911 - 4 - f05fce76-e87f-48e1-a86d-07f6d4084049 - Group + + A panel for custom notes and text values + d0efa1ab-d893-4335-8af7-dd6d94484ea7 + Panel + false + 1 + fb496e59-6b43-4266-9c61-c466f3a89f8e + 1 + Double click to edit panel content… - - + + + + + 2238 + 1357 + 204 + 41 + + 0 + 0 + 0 + + 2238.467 + 1357.654 + + + + + + + 255;255;250;90 + + true + true + true + false + false + true + + - 9c53bac0-ba66-40bd-8154-ce9829b9db1a - Colour Swatch + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group - - Colour (palette) swatch - c0a53353-9802-4df7-a063-6228afad4537 - Colour Swatch - Swatch - false - 0 - - 34;240;180;137 + + 1 + + 150;255;255;255 + A group of Grasshopper objects + 20be8b4b-0eba-41a5-8f13-25defa3e633d + d0efa1ab-d893-4335-8af7-dd6d94484ea7 + 9f3e0877-7fa6-4696-83ae-f6281f1d75f8 + 3c27b464-0da3-49d8-922b-780384eb4127 + ba663821-ec0e-4ff9-9124-fe98533cab66 + 97e9efc3-9035-45ee-bac9-e1c0876defe3 + 47c9ac3d-5335-4e4b-8cf3-b22cd1284a8e + e9fbb74f-6b87-4a1e-a684-8202331f1480 + f11ad7e4-429f-4eac-92db-ff79b89a4fa3 + f02972d5-c58c-4216-b53b-7f078d1f7fdc + 5b8e4ee4-cb19-450a-ba61-866a3686f2b2 + c6f9d24c-2b1e-4276-8e76-0fd2049e3d3a + 12 + c6445b86-caf6-4180-b0be-5dcfc6eaea9a + Group + - - - - 2737 - 557 - 88 - 20 - - - 2737.545 - 557.0914 - - - + @@ -5528,42 +5219,42 @@ class Beam_fromCurve(component): true - 2405.003 - 171.456 + 2387.48 + 1271.054 - 2762.522 - 171.456 + 2642.241 + 1271.054 - 2762.522 - 218.7949 + 2642.241 + 1318.393 - 2405.003 - 218.7949 + 2387.48 + 1318.393 A quick note Microsoft Sans Serif - 1fda4fd4-ed01-4e84-8338-ca710b993b83 + 9f3e0877-7fa6-4696-83ae-f6281f1d75f8 false Scribble Scribble 50 - StepJointNotch + TStepJoint - 2400.003 - 166.456 - 367.5195 - 57.33887 + 2382.48 + 1266.054 + 264.7607 + 57.33875 - 2405.003 - 171.456 + 2387.48 + 1271.054 @@ -5584,9 +5275,9 @@ class Beam_fromCurve(component): 150;255;255;255 A group of Grasshopper objects - 1fda4fd4-ed01-4e84-8338-ca710b993b83 + 9f3e0877-7fa6-4696-83ae-f6281f1d75f8 1 - e5f92ef7-182d-4ea0-8d5a-b01aada26ebb + 3c27b464-0da3-49d8-922b-780384eb4127 Group @@ -5598,627 +5289,448 @@ class Beam_fromCurve(component): - 7f5c6c55-f846-4a08-9c9a-cfdc285cc6fe - Scribble + 537b0419-bbc2-4ff4-bf08-afe526367b2c + Custom Preview - - true - - 2470.777 - 1060.245 - - - 2691.895 - 1060.245 - - - 2691.895 - 1107.584 - - - 2470.777 - 1107.584 - - A quick note - Microsoft Sans Serif - 14f73845-d137-4e4f-939d-fc35194fd72e - false - Scribble - Scribble - 50 - StepJoint + + Allows for customized geometry previews + false + ba663821-ec0e-4ff9-9124-fe98533cab66 + Custom Preview + Preview + - + - 2465.777 - 1055.245 - 231.1182 - 57.33887 + 2731 + 1508 + 64 + 44 - 2470.777 - 1060.245 + 2781 + 1530 + + + Geometry to preview + true + c34e1843-bfd6-4a82-b983-7194c9f9c18c + Geometry + G + false + e9fbb74f-6b87-4a1e-a684-8202331f1480 + 1 + + + + + + 2733 + 1510 + 33 + 20 + + + 2759 + 1520 + + + + + + + + The material override + 710c97a8-7564-4390-af09-089f9958717d + 1 + Material + M + false + d5779e9d-d95c-4279-bd0c-9036111f46eb + 1 + + + + + + 2733 + 1530 + 33 + 20 + + + 2759 + 1540 + + + + + + 1 + + + + + 1 + {0} + + + + + + 255;221;160;221 + + + 255;66;48;66 + + 0.5 + + 255;255;255;255 + + 0 + + + + + + + + - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 14f73845-d137-4e4f-939d-fc35194fd72e - 1 - b6362569-cd6b-4fdf-a6bc-af4ac10ea9bf - Group - - - - - - - - - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 18d2fa4a-de1b-4cfd-bf80-4b8d5fa0182f - 1 - e22e52d8-5a24-494f-8bd6-2f5c368cced0 - Group - CrossBeam - - - - - - - - - - 919e146f-30ae-4aae-be34-4d72f555e7da - Brep + 0148a65d-6f42-414a-9db7-9a9b2eb78437 + Brep Edges - - Contains a collection of Breps (Boundary REPresentations) + + Extract the edge curves of a brep. true - b1d2aad0-ff8f-419b-9c56-f82b9b6378df - Brep - Brep - false - 441ff4ac-6060-44cf-965a-55428a9d4cc6 - 1 + 97e9efc3-9035-45ee-bac9-e1c0876defe3 + Brep Edges + Edges - + - 2651 - 1378 - 50 - 24 + 2625 + 1438 + 72 + 64 - 2676.568 - 1390.515 + 2655 + 1470 - - - - - - - 9c53bac0-ba66-40bd-8154-ce9829b9db1a - Colour Swatch - - - - - Colour (palette) swatch - d0dd1043-724e-45c1-a8b5-6f7ff06d8073 - Colour Swatch - Swatch - false - 0 - - 29;0;102;255 - - - - - - - 2728 - 1445 - 88 - 20 - - - 2728.811 - 1445.6 - + + + Base Brep + d76deb57-3846-4215-a6d3-b4cc65f5f623 + Brep + B + false + e9fbb74f-6b87-4a1e-a684-8202331f1480 + 1 - - - - - - - - 919e146f-30ae-4aae-be34-4d72f555e7da - Brep - - - - - Contains a collection of Breps (Boundary REPresentations) - true - 91868622-66e7-4d7d-a5a0-9480a9e4a02a - Brep - Brep - false - b10f1b85-d84a-4d8b-84d3-f956ea4e2805 - 1 - - - - - - 2663 - 480 - 50 - 24 - - - 2688.471 - 492.1772 - + + + + + 2627 + 1440 + 13 + 60 + + + 2635 + 1470 + + + + + + + + 1 + Naked edge curves + d6302059-08b9-4456-a7f4-fc4b73a03458 + Naked + En + false + 0 + + + + + 2670 + 1440 + 25 + 20 + + + 2682.5 + 1450 + + + + + + + + 1 + Interior edge curves + 8db6122b-9378-4026-a86e-58c5dd430f2a + Interior + Ei + false + 0 + + + + + + 2670 + 1460 + 25 + 20 + + + 2682.5 + 1470 + + + + + + + + 1 + Non-Manifold edge curves + 0c2574f5-76b7-475f-80aa-c87143496df3 + Non-Manifold + Em + false + 0 + + + + + + 2670 + 1480 + 25 + 20 + + + 2682.5 + 1490 + + + + - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - def048ab-0443-4189-81d6-03594953cdd1 - aa461d11-c653-411c-a341-b7900f1ccbd6 - 0cbceed4-3434-411c-b4d1-46985ef14dea - b1d2aad0-ff8f-419b-9c56-f82b9b6378df - d0dd1043-724e-45c1-a8b5-6f7ff06d8073 - 5 - 7dff34a0-670b-4fd3-8912-0a128d903453 - Group - GEOMETRY - - - - - - - - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 9e4f0b21-759b-4c9a-8dfb-6654484027c5 - be8a15e2-8773-4720-9d61-dfdc10761646 - 56020ec2-ccb1-4ae9-a538-84dcb2857db9 - c0a53353-9802-4df7-a063-6228afad4537 - 91868622-66e7-4d7d-a5a0-9480a9e4a02a - 5 - 1994870e-f2d3-43af-9b16-274f462b9ef7 - Group - GEOMETRY - - - - - - - - + - 410755b1-224a-4c1e-a407-bf32fb45ea7e - 00000000-0000-0000-0000-000000000000 - GhPython Script + a77d0879-94c2-4101-be44-e4a616ffeb0c + 5f86fa9f-c62b-50e8-157b-b454ef3e00fa + Custom Preview Lineweights - - from compas_timber.connections import TStepJoint - -from compas_timber.model import TimberModel -from compas_timber.fabrication import BTLx - -from compas_rhino.conversions import brep_to_rhino - -from compas_rhino import unload_modules - -unload_modules("compas_timber") - -# Instantiate TimberModel & Add Beams -timber_model = TimberModel() -beams = [cross_beam, main_beam] -for beam in beams: - timber_model.add_beam(beam) - -# Create TStepJoint joint -step_joint_joint = TStepJoint.create( - timber_model, - main_beam, - cross_beam, - step_depth=step_depth, - heel_depth=heel_depth, - tapered_heel=tapered_heel, - tenon_mortise_height=tenon_mortise_height - ) - -# Generate Geometry -geometry = [brep_to_rhino(beam.compute_geometry()) for beam in timber_model.beams] - -# Write BTLx -btlx = BTLx(timber_model) -BTLx = btlx.btlx_string() - GhPython provides a Python script component - - 418 - 209 - - - 901 - 818 - - true - true - false - false - 20be8b4b-0eba-41a5-8f13-25defa3e633d - false - true - GhPython Script - Python + + Custom Preview with Lineweights + 47c9ac3d-5335-4e4b-8cf3-b22cd1284a8e + Custom Preview Lineweights + PreviewLW + - + - 3506 - 915 - 203 - 124 + 2731 + 1418 + 62 + 84 - 3635 - 977 + 2779 + 1460 - - - 6 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 3 - 3ede854e-c753-40eb-84cb-b48008f14fd4 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - - - - - true - Script input cross_beam. - 36ebe6e6-4cdb-4b9d-8c4a-65feaf8cee5a - cross_beam - cross_beam - true - 0 - true - 3984d0df-294e-462f-b103-b87bb94e3021 - 1 - 87f87f55-5b71-41f4-8aea-21d494016f81 - - - - - - 3508 - 917 - 112 - 20 - - - 3565.5 - 927 - - - - - - - - true - Script input main_beam. - 407e65c9-4ebe-4581-9401-a364b25f96c2 - main_beam - main_beam - true - 0 - true - 90fae02f-7750-462d-828f-cf5f2a0d39ae - 1 - 87f87f55-5b71-41f4-8aea-21d494016f81 - - - - - - 3508 - 937 - 112 - 20 - - - 3565.5 - 947 - - - - - - - - true - Script input step_depth. - fbd107db-8f8a-4baa-876f-623012dd3bb0 - step_depth - step_depth - true - 0 - true - c3c1dc6d-7ceb-4d7f-add6-fa924b4cc936 - 1 - 87f87f55-5b71-41f4-8aea-21d494016f81 - - - - - - 3508 - 957 - 112 - 20 - - - 3565.5 - 967 - - - - - - - - true - Script input heel_depth. - dab8fd89-b668-48ad-91eb-c695241b0e94 - heel_depth - heel_depth - true - 0 - true - ab977350-6a8d-472b-9276-52a400da755b - 1 - 87f87f55-5b71-41f4-8aea-21d494016f81 + + + Geometry to preview + true + b386acb8-8321-49bf-ac12-f062d85c969d + Geometry + G + false + 8db6122b-9378-4026-a86e-58c5dd430f2a + 1 + + + + + + 2733 + 1420 + 31 + 20 + + + 2758 + 1430 + - - - - - 3508 - 977 - 112 - 20 - - - 3565.5 - 987 - - - - - - - true - Script input tapered_heel. - f4ca3577-e0dc-42fa-8923-d574fb19c330 - tapered_heel - tapered_heel - true - 0 - true - dec5089d-c452-417b-a948-4034d6df42ce - 1 - 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + The preview shader override + 11af4056-9698-4b92-b067-fae857f62252 + 2 + Shader + S + false + d5779e9d-d95c-4279-bd0c-9036111f46eb + 1 + + + + + + 2733 + 1440 + 31 + 20 + + + 2758 + 1450 + - - - - - 3508 - 997 - 112 - 20 - - - 3565.5 - 1007 - - - - - - - true - Script input tenon_mortise_height. - e6b1d326-8808-4533-930e-154d3cd939d3 - tenon_mortise_height - tenon_mortise_height - true - 0 - true - 1f382d1c-8a89-4e8a-8102-597801cd81a3 - 1 - 87f87f55-5b71-41f4-8aea-21d494016f81 + + + 1 - + - - 3508 - 1017 - 112 - 20 - - - 3565.5 - 1027 - + 1 + {0} + + + + + 255;255;105;180 + + + 255;76;32;54 + + 0.5 + + 255;255;255;255 + + 0 + + + - - - The execution information, as output and error streams - fb496e59-6b43-4266-9c61-c466f3a89f8e - out - out - false - 0 + + + + + The thickness of the wire display + f4113c88-a8ac-49cd-a656-a1e84f635519 + Thickness + T + true + 0 + + + + + + 2733 + 1460 + 31 + 20 + + + 2758 + 1470 + - - - - - 3650 - 917 - 57 - 40 - - - 3678.5 - 937 - - - - - - - Script output geometry. - 188326e4-b130-4230-9b7f-f89e9a5dca84 - geometry - geometry - false - 0 + + + + + Set to true to try to render curves with an absolute dimension. + 56fb3908-c9cb-478b-be37-49785e21754d + Absolute + A + false + 0 + + + + + + 2733 + 1480 + 31 + 20 + + + 2758 + 1490 + - - - - - 3650 - 957 - 57 - 40 - - - 3678.5 - 977 - - - - - - - Script output BTLx. - 8674ed0a-6d18-41d1-9209-ae7aed0e558d - BTLx - BTLx - false - 0 + + + 1 - + - - 3650 - 997 - 57 - 40 - - - 3678.5 - 1017 - + 1 + {0} + + + + false + + + @@ -6228,334 +5740,369 @@ BTLx = btlx.btlx_string() - + - 59e0b89a-e487-49f8-bab8-b5bab16be14c - Panel + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep - - A panel for custom notes and text values - a31a3c97-3e91-47a8-add8-793f530b94e1 - Panel - + + Contains a collection of Breps (Boundary REPresentations) + true + e9fbb74f-6b87-4a1e-a684-8202331f1480 + Brep + Brep false - 0 - 8674ed0a-6d18-41d1-9209-ae7aed0e558d + 188326e4-b130-4230-9b7f-f89e9a5dca84 1 - G:\Shared drives\2024_MAS\T2\03_finalization\Fabrication - Joints\BTLx\step_joint_joint_test.btlx - + - + - 3564 - 1119 - 446 - 420 + 2538 + 1461 + 50 + 24 - 0 - 0 - 0 - 3564.741 - 1119.944 + 2563.602 + 1473.006 - - - - 255;255;250;90 + + + + + + + 9c53bac0-ba66-40bd-8154-ce9829b9db1a + Colour Swatch + + + + + Colour (palette) swatch + f11ad7e4-429f-4eac-92db-ff79b89a4fa3 + Colour Swatch + Swatch + false + 0 + + 29;0;102;255 + + + + + + + 2521 + 1509 + 88 + 20 + + + 2521.967 + 1509.452 - true - true - true - false - true - D:\Papachap\Desktop\btlx\TStepJoint.btlx - true - + - 59e0b89a-e487-49f8-bab8-b5bab16be14c - Panel + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group - - A panel for custom notes and text values - dfdc288d-17d2-4daf-93bc-82b8f3f1816b - Panel - StepJoint BTLx Params + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + ba663821-ec0e-4ff9-9124-fe98533cab66 + 97e9efc3-9035-45ee-bac9-e1c0876defe3 + 47c9ac3d-5335-4e4b-8cf3-b22cd1284a8e + e9fbb74f-6b87-4a1e-a684-8202331f1480 + f11ad7e4-429f-4eac-92db-ff79b89a4fa3 + 5b8e4ee4-cb19-450a-ba61-866a3686f2b2 + c6f9d24c-2b1e-4276-8e76-0fd2049e3d3a + 7 + f02972d5-c58c-4216-b53b-7f078d1f7fdc + Group + GEOMETRY + + + + + + + + + + 9c53bac0-ba66-40bd-8154-ce9829b9db1a + Colour Swatch + + + + + Colour (palette) swatch + 5b8e4ee4-cb19-450a-ba61-866a3686f2b2 + Colour Swatch + Swatch false - 0 - 269c79e7-4269-4669-b62a-9869376ea11c - 1 - Double click to edit panel content… + 0 + + 34;240;180;137 + - + - + - 3350 - 1184 - 212 - 333 + 2521 + 1533 + 88 + 20 - 0 - 0 - 0 - 3350.94 - 1184.713 + 2521.729 + 1533.101 - - - - 255;255;255;255 + + + + + + + c9785b8e-2f30-4f90-8ee3-cca710f82402 + Entwine + + + + + Flatten and combine a collection of data streams + true + true + c6f9d24c-2b1e-4276-8e76-0fd2049e3d3a + Entwine + Entwine + + + + + + 2624 + 1505 + 79 + 44 - true - true - true - false - false - true + + 2669 + 1527 + + + + + + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + 2 + Data to entwine + f1b60494-3bf8-4181-99bd-69acd3c3059a + false + Branch {0;0} + {0;0} + true + f11ad7e4-429f-4eac-92db-ff79b89a4fa3 + 1 + + + + + + 2626 + 1507 + 28 + 20 + + + 2641.5 + 1517 + + + + + + + + 2 + Data to entwine + 56b1c6dd-1a2e-4764-8c90-316095f2007a + false + Branch {0;1} + {0;1} + true + 5b8e4ee4-cb19-450a-ba61-866a3686f2b2 + 1 + + + + + + 2626 + 1527 + 28 + 20 + + + 2641.5 + 1537 + + + + + + + + Entwined result + d5779e9d-d95c-4279-bd0c-9036111f46eb + Result + R + false + 0 + + + + + + 2684 + 1507 + 17 + 40 + + + 2692.5 + 1527 + + + + + + - + - 59e0b89a-e487-49f8-bab8-b5bab16be14c - Panel + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider - - A panel for custom notes and text values - 1efbf060-c389-495d-aacf-3ce7dc0fcafc - Panel - StepJointNotch BTLx Params + + Numeric slider for single values + fc245c73-88f5-4db4-a914-d3a9d191bd54 + true + Number Slider + false - 0 - d525eccb-5bff-4563-b75e-472cbbdc5901 - 1 - Double click to edit panel content… + 0 - + - 4014 - 1190 - 212 - 333 + 1961 + 1494 + 187 + 20 - 0 - 0 - 0 - 4014.498 - 1190.838 + 1961.03 + 1494.398 - + - - 255;255;255;255 - - true - true - true - false - false - true + 3 + 1 + 1 + 100 + 0 + 0 + 10 - + - 59e0b89a-e487-49f8-bab8-b5bab16be14c - Panel + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider - - A panel for custom notes and text values - d0efa1ab-d893-4335-8af7-dd6d94484ea7 - Panel + + Numeric slider for single values + e4da7b43-f08d-4e84-a53a-6d4d965dca71 + true + Number Slider false - 0 - fb496e59-6b43-4266-9c61-c466f3a89f8e - 1 - Double click to edit panel content… + 0 - + - 3505 - 862 - 204 - 53 + 1961 + 1544 + 238 + 20 - 0 - 0 - 0 - 3505.031 - 862.5032 + 1961.12 + 1544.358 - + - - 255;255;250;90 - - true - true - true - false - false - true - - - - - - - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 20be8b4b-0eba-41a5-8f13-25defa3e633d - d0efa1ab-d893-4335-8af7-dd6d94484ea7 - 9f3e0877-7fa6-4696-83ae-f6281f1d75f8 - 3c27b464-0da3-49d8-922b-780384eb4127 - ba663821-ec0e-4ff9-9124-fe98533cab66 - 97e9efc3-9035-45ee-bac9-e1c0876defe3 - 47c9ac3d-5335-4e4b-8cf3-b22cd1284a8e - e9fbb74f-6b87-4a1e-a684-8202331f1480 - f11ad7e4-429f-4eac-92db-ff79b89a4fa3 - f02972d5-c58c-4216-b53b-7f078d1f7fdc - 5b8e4ee4-cb19-450a-ba61-866a3686f2b2 - c6f9d24c-2b1e-4276-8e76-0fd2049e3d3a - 12 - c6445b86-caf6-4180-b0be-5dcfc6eaea9a - Group - - - - - - - - - - - 7f5c6c55-f846-4a08-9c9a-cfdc285cc6fe - Scribble - - - - - true - - 3654.336 - 775.9541 - - - 3909.097 - 775.9541 - - - 3909.097 - 823.293 - - - 3654.336 - 823.293 - - A quick note - Microsoft Sans Serif - 9f3e0877-7fa6-4696-83ae-f6281f1d75f8 - false - Scribble - Scribble - 50 - TStepJoint - - - - - - 3649.336 - 770.9541 - 264.7607 - 57.33887 - - - 3654.336 - 775.9541 - + 3 + 1 + 1 + 100 + 0 + 0 + 0 - - - c552a431-af5b-46a9-a8a4-0fcbc27ef596 - Group - - - - - 1 - - 150;255;255;255 - - A group of Grasshopper objects - 9f3e0877-7fa6-4696-83ae-f6281f1d75f8 - 1 - 3c27b464-0da3-49d8-922b-780384eb4127 - Group - - - - - - - - - + 59daf374-bc21-4a5e-8282-5504fb7ae9ae List Item @@ -6566,7 +6113,7 @@ BTLx = btlx.btlx_string() 0 Retrieve a specific item from a list. true - d87eb305-f0b0-4b4a-9ee0-a52b770164cc + 645777e9-3e35-4980-b9de-1af150e02dd0 List Item Item @@ -6574,14 +6121,14 @@ BTLx = btlx.btlx_string() - 2943 - 371 + 518 + 1461 64 64 - 2977 - 403 + 552 + 1493 @@ -6599,97 +6146,52 @@ BTLx = btlx.btlx_string() 1 Base list - 3f880763-347f-478e-8e82-836a0cc3f94c + 43423b3e-64ff-4e92-a077-9e59debece3d List L false - 482410b9-724c-46d6-be3a-3d63785bc853 + c285f14a-7ead-4d46-9545-12950096c0b4 1 - 2945 - 373 + 520 + 1463 17 20 - 2955 - 383 + 530 + 1473 - - Item index - 6acddfe7-ca80-424f-a759-2f45f82bf107 - Index - i - false - 0 - - - - - - 2945 - 393 - 17 - 20 - - - 2955 - 403 - - - - - - 1 - - - - - 1 - {0} - - - - - 0 - - - - - - - - - - - Wrap index to list bounds - 3911a313-de41-48b4-8f20-9e6b901bc766 - Wrap - W + + Item index + 1f84b569-7192-4e9c-9c72-eac0579e6fb5 + Index + i false - 0 + 0427415b-7064-4ecf-875e-ad8efb142eee + 1 - 2945 - 413 + 520 + 1483 17 20 - 2955 - 423 + 530 + 1493 @@ -6706,7 +6208,7 @@ BTLx = btlx.btlx_string() - true + 0 @@ -6715,294 +6217,78 @@ BTLx = btlx.btlx_string() - - - Item at {i'} - 0735b968-42f2-4221-86dc-b8eb7484f4e5 - false - Item - i + + + Wrap index to list bounds + 8659d981-c16a-4820-bae2-1011ae70ffb5 + Wrap + W false 0 - + - 2992 - 373 - 13 - 60 + 520 + 1503 + 17 + 20 - 2998.5 - 403 + 530 + 1513 - - - - - - - - - - - 537b0419-bbc2-4ff4-bf08-afe526367b2c - Custom Preview - - - - - Allows for customized geometry previews - true - false - ba663821-ec0e-4ff9-9124-fe98533cab66 - Custom Preview - Preview - - - - - - - 3998 - 1014 - 64 - 44 - - - 4048 - 1036 - - - - - - Geometry to preview - true - c34e1843-bfd6-4a82-b983-7194c9f9c18c - Geometry - G - false - e9fbb74f-6b87-4a1e-a684-8202331f1480 - 1 - - - - - - 4000 - 1016 - 33 - 20 - - - 4026 - 1026 - - - - - - - - The material override - 710c97a8-7564-4390-af09-089f9958717d - 1 - Material - M - false - d5779e9d-d95c-4279-bd0c-9036111f46eb - 1 - - - - - - 4000 - 1036 - 33 - 20 - - - 4026 - 1046 - - - - - - 1 - - - - + + 1 - {0} - - - - 255;221;160;221 - - - 255;66;48;66 - - 0.5 - - 255;255;255;255 - - 0 - - - - - - - - - - - - - - - 0148a65d-6f42-414a-9db7-9a9b2eb78437 - Brep Edges - - - - - Extract the edge curves of a brep. - true - 97e9efc3-9035-45ee-bac9-e1c0876defe3 - Brep Edges - Edges - - - - - - 3892 - 944 - 72 - 64 - - - 3922 - 976 - - - - - - Base Brep - d76deb57-3846-4215-a6d3-b4cc65f5f623 - Brep - B - false - e9fbb74f-6b87-4a1e-a684-8202331f1480 - 1 - - - - - - 3894 - 946 - 13 - 60 - - - 3902 - 976 - - - - - - - - 1 - Naked edge curves - d6302059-08b9-4456-a7f4-fc4b73a03458 - Naked - En - false - 0 - - - - - - 3937 - 946 - 25 - 20 - - - 3949.5 - 956 - - - - - - - - 1 - Interior edge curves - 8db6122b-9378-4026-a86e-58c5dd430f2a - Interior - Ei - false - 0 - - - - - - 3937 - 966 - 25 - 20 - - - 3949.5 - 976 - - - - - - - - 1 - Non-Manifold edge curves - 0c2574f5-76b7-475f-80aa-c87143496df3 - Non-Manifold - Em - false - 0 - - - - - - 3937 - 986 - 25 - 20 - - - 3949.5 - 996 - + + + 1 + {0} + + + + + true + + + + + + + + + + + Item at {i'} + 1c09c813-ee95-4356-93ff-668de076cf9e + false + Item + i + false + 0 + + + + + 567 + 1463 + 13 + 60 + + + 573.5 + 1493 + + + + @@ -7010,295 +6296,273 @@ BTLx = btlx.btlx_string() - - - a77d0879-94c2-4101-be44-e4a616ffeb0c - 5f86fa9f-c62b-50e8-157b-b454ef3e00fa - Custom Preview Lineweights + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider - - Custom Preview with Lineweights - 47c9ac3d-5335-4e4b-8cf3-b22cd1284a8e - Custom Preview Lineweights - PreviewLW - + + Numeric slider for single values + 0427415b-7064-4ecf-875e-ad8efb142eee + Number Slider + + false + 0 - + - 3998 - 924 - 62 - 84 + 339 + 1485 + 159 + 20 - 4046 - 966 + 339.6179 + 1485.098 - - - Geometry to preview - true - b386acb8-8321-49bf-ac12-f062d85c969d - Geometry - G - false - 8db6122b-9378-4026-a86e-58c5dd430f2a - 1 + + + 3 + 1 + 1 + 2 + 0 + 0 + 0 - - - - - 4000 - 926 - 31 - 20 - - - 4025 - 936 - - - - - - - The preview shader override - 11af4056-9698-4b92-b067-fae857f62252 - 2 - Shader - S - false - d5779e9d-d95c-4279-bd0c-9036111f46eb - 1 + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + c64fa590-0923-4f6c-bc02-60bf9bf5e33c + Number Slider + + false + 0 + + + + + + 796 + 1305 + 166 + 20 + + + 796.2646 + 1305.699 + - - - - - 4000 - 946 - 31 - 20 - - - 4025 - 956 - - - - - - 1 - - - - - 1 - {0} - - - - - - 255;255;105;180 - - - 255;76;32;54 - - 0.5 - - 255;255;255;255 - - 0 - - - - - - - - - - The thickness of the wire display - f4113c88-a8ac-49cd-a656-a1e84f635519 - Thickness - T - true - 0 + + + 3 + 1 + 1 + 100 + 0 + 0 + 80 - - - - - 4000 - 966 - 31 - 20 - - - 4025 - 976 - - - - - - - Set to true to try to render curves with an absolute dimension. - 56fb3908-c9cb-478b-be37-49785e21754d - Absolute - A - false - 0 - - - - - - 4000 - 986 - 31 - 20 - - - 4025 - 996 - - - - - - 1 - - - - - 1 - {0} - - - - - false - - - - - - - + + + + + + + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a + Boolean Toggle + + + + + Boolean (true/false) toggle + 05345419-f1d5-4f4d-aea7-b888275c43f0 + true + Boolean Toggle + Toggle + false + 0 + false + + + + + + 1959 + 1515 + 104 + 22 + + - + - 919e146f-30ae-4aae-be34-4d72f555e7da - Brep + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider - - Contains a collection of Breps (Boundary REPresentations) - true - e9fbb74f-6b87-4a1e-a684-8202331f1480 - Brep - Brep + + Numeric slider for single values + 574904c9-0f6a-4dc7-9431-055e7aa620aa + true + Number Slider + false - 188326e4-b130-4230-9b7f-f89e9a5dca84 - 1 + 0 - + - 3805 - 965 - 50 - 24 + 1960 + 1469 + 187 + 20 - 3830.458 - 977.9061 + 1960.432 + 1469.712 + + + 3 + 1 + 1 + 100 + 0 + 0 + 20 + + - + - 9c53bac0-ba66-40bd-8154-ce9829b9db1a - Colour Swatch + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + Curve - Colour (palette) swatch - f11ad7e4-429f-4eac-92db-ff79b89a4fa3 - Colour Swatch - Swatch + Contains a collection of generic curves + true + c285f14a-7ead-4d46-9545-12950096c0b4 + Curve + Crv false 0 - - 29;0;102;255 - - + - 3788 - 1014 - 88 - 20 + 446 + 1454 + 50 + 24 - 3788.823 - 1014.352 + 471.742 + 1466.838 + + + 1 + + + + + 3 + {0;0} + + + + + -1 + + Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesYmekbGRmbmeoaGBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS9f/4eDiZgQx+EDEViJl+1TMxdEL99fs/E4MIlP3synU/95eXBfc/ZRBoZlT64Ddn9+0/QPlAqLwAyNzYbxpcrDPvOXz8/18+rHjlAYmi2O741CCH48eNlj7zvu9wBCoe0mqltvDcBAcmBgTY2xy1xs52sgPIJY3vmdz+1zM1wOS4GQYTAAA= + + 00000000-0000-0000-0000-000000000000 + + + + + -1 + + Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesYmekbGRmbmeoaGBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS9f/4eDiZgQx+EDEViJl+1TMxdEL99fs/E4MIlP3synU/95eXBfc/ZRBoZlT64Ddn9+0/QPlAqLwAyNwtT5J42GPuO3z6/18+rHjlAYmi2O741CCH8Hxuq3ly9x0+OEg8DTDeABdnYkCAe5WXLGqv9ziAXPIxM3nP/3qmBpgcN8NgAgA= + + 00000000-0000-0000-0000-000000000000 + + + + + -1 + + Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesYmekbGRmbmeoaGBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS9f/4eDiZgQx+EDEViJl+1TMxdEL99fs/E4MIlP3synU/95eXBfc/ZRBoZlT64Ddn9+0/QPlAqLwAyFzH6U87Q4MfOAQe/SM0wXLRAYmi2O741CCH+Q80Tt0XfODw6f9/+bDilXBxkJ6WVzwmEs97DkDNaQC5hEeQUfN/PVMDVIyBm2EwAQA= + + 00000000-0000-0000-0000-000000000000 + + + + + + - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 Group - + 1 150;255;255;255 A group of Grasshopper objects - ba663821-ec0e-4ff9-9124-fe98533cab66 - 97e9efc3-9035-45ee-bac9-e1c0876defe3 - 47c9ac3d-5335-4e4b-8cf3-b22cd1284a8e - e9fbb74f-6b87-4a1e-a684-8202331f1480 - f11ad7e4-429f-4eac-92db-ff79b89a4fa3 - 5b8e4ee4-cb19-450a-ba61-866a3686f2b2 - c6f9d24c-2b1e-4276-8e76-0fd2049e3d3a - 7 - f02972d5-c58c-4216-b53b-7f078d1f7fdc + 539aeba5-2090-4272-bf3d-8d4bacf470d5 + fc245c73-88f5-4db4-a914-d3a9d191bd54 + e4da7b43-f08d-4e84-a53a-6d4d965dca71 + 05345419-f1d5-4f4d-aea7-b888275c43f0 + 574904c9-0f6a-4dc7-9431-055e7aa620aa + 5 + 9dd669ed-8fea-466e-8b3f-c6555794d56a Group - GEOMETRY + @@ -7306,36 +6570,52 @@ BTLx = btlx.btlx_string() - + - 9c53bac0-ba66-40bd-8154-ce9829b9db1a - Colour Swatch + 00027467-0d24-4fa7-b178-8dc0ac5f42ec + Value List - - Colour (palette) swatch - 5b8e4ee4-cb19-450a-ba61-866a3686f2b2 - Colour Swatch - Swatch + + Provides a list of preset values to choose from + da35a5d7-1b20-42d0-acad-65c4ca451910 + 3 + 1 + Value List + List false 0 - - 34;240;180;137 - - + + + + 0 + step + true + + + + + 1 + heel + false + + + + + 2 + double + false + + - + - 3788 - 1038 - 88 - 20 - - - 3788.585 - 1038.001 + 1974 + 1400 + 79 + 22 @@ -7343,130 +6623,27 @@ BTLx = btlx.btlx_string() - + - c9785b8e-2f30-4f90-8ee3-cca710f82402 - Entwine + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group - - Flatten and combine a collection of data streams - true - c6f9d24c-2b1e-4276-8e76-0fd2049e3d3a - Entwine - Entwine + + 5 + + 150;255;255;255 + + A group of Grasshopper objects + da35a5d7-1b20-42d0-acad-65c4ca451910 + 1 + 86a2746f-76cf-4e10-a542-88ffc1de0b83 + Group + - - - - - 3891 - 1011 - 79 - 44 - - - 3936 - 1033 - - - - - - 2 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - 1 - 8ec86459-bf01-4409-baee-174d0d2b13d0 - - - - - 2 - Data to entwine - f1b60494-3bf8-4181-99bd-69acd3c3059a - false - Branch {0;0} - {0;0} - true - f11ad7e4-429f-4eac-92db-ff79b89a4fa3 - 1 - - - - - - 3893 - 1013 - 28 - 20 - - - 3908.5 - 1023 - - - - - - - - 2 - Data to entwine - 56b1c6dd-1a2e-4764-8c90-316095f2007a - false - Branch {0;1} - {0;1} - true - 5b8e4ee4-cb19-450a-ba61-866a3686f2b2 - 1 - - - - - - 3893 - 1033 - 28 - 20 - - - 3908.5 - 1043 - - - - - - - - Entwined result - d5779e9d-d95c-4279-bd0c-9036111f46eb - Result - R - false - 0 - - - - - - 3951 - 1013 - 17 - 40 - - - 3959.5 - 1033 - - - - - - - + + @@ -7478,7 +6655,7 @@ BTLx = btlx.btlx_string() - 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 diff --git a/tests/compas_timber/test_step_joint.py b/tests/compas_timber/test_step_joint.py new file mode 100644 index 000000000..d746fa1ca --- /dev/null +++ b/tests/compas_timber/test_step_joint.py @@ -0,0 +1,251 @@ +import pytest + +from collections import OrderedDict + +from compas.geometry import Point +from compas.geometry import Line + +from compas_timber.elements import Beam +from compas_timber._fabrication import StepJointNotch +from compas_timber._fabrication import StepJoint + + +@pytest.fixture +def cross_beam(): + width = 80 + height = 120 + + centerline = Line( + Point(x=30782.4296640, y=-3257.66821289, z=73.5839565671), + Point(x=33782.4296640, y=-3257.66821289, z=73.5839565671), + ) + + return Beam.from_centerline(centerline, width, height) + + +@pytest.fixture +def main_beams(): + width = 60 + height = 80 + + centerlines = [ + Line( + Point(x=31332.0787451, y=-3257.66821289, z=73.5839565671), + Point(x=32047.6038329, y=-3257.66821289, z=1075.65737239), + ), + Line( + Point(x=32112.1101310, y=-3257.66821289, z=73.5839565671), + Point(x=31866.4722928, y=-4147.31599571, z=73.5839565671), + ), + Line( + Point(x=33434.6730831, y=-2332.78139486, z=73.5839565671), + Point(x=32910.9934277, y=-3257.66821289, z=73.5839565671), + ), + ] + + return [Beam.from_centerline(centerline, width, height) for centerline in centerlines] + + +EXPECTED_NOTCH_PARAMS = [ + OrderedDict( + [ + ("Name", "StepJointNotch"), + ("Priority", "0"), + ("Process", "yes"), + ("ProcessID", "0"), + ("ReferencePlaneID", "3"), + ("Orientation", "end"), + ("StartX", "641.642"), + ("StartY", "10.000"), + ("StrutInclination", "125.529"), + ("NotchLimited", "yes"), + ("NotchWidth", "60.000"), + ("StepDepth", "20.000"), + ("HeelDepth", "0.000"), + ("StrutHeight", "80.000"), + ("StepShape", "step"), + ("Mortise", "no"), + ("MortiseWidth", "40.000"), + ("MortiseHeight", "40.000"), + ] + ), + OrderedDict( + [ + ("Name", "StepJointNotch"), + ("Priority", "0"), + ("Process", "yes"), + ("ProcessID", "0"), + ("ReferencePlaneID", "4"), + ("Orientation", "start"), + ("StartX", "1287.514"), + ("StartY", "0.000"), + ("StrutInclination", "74.565"), + ("NotchLimited", "no"), + ("NotchWidth", "80.000"), + ("StepDepth", "0.000"), + ("HeelDepth", "10.000"), + ("StrutHeight", "60.000"), + ("StepShape", "heel"), + ("Mortise", "no"), + ("MortiseWidth", "40.000"), + ("MortiseHeight", "40.000"), + ] + ), + OrderedDict( + [ + ("Name", "StepJointNotch"), + ("Priority", "0"), + ("Process", "yes"), + ("ProcessID", "0"), + ("ReferencePlaneID", "2"), + ("Orientation", "end"), + ("StartX", "2185.687"), + ("StartY", "0.000"), + ("StrutInclination", "119.519"), + ("NotchLimited", "no"), + ("NotchWidth", "80.000"), + ("StepDepth", "20.000"), + ("HeelDepth", "10.000"), + ("StrutHeight", "60.000"), + ("StepShape", "double"), + ("Mortise", "no"), + ("MortiseWidth", "40.000"), + ("MortiseHeight", "40.000"), + ] + ), +] + +EXPECTED_STEP_PARAMS = [ + OrderedDict( + [ + ("Name", "StepJoint"), + ("Priority", "0"), + ("Process", "yes"), + ("ProcessID", "0"), + ("ReferencePlaneID", "1"), + ("Orientation", "start"), + ("StartX", "102.288"), + ("StrutInclination", "125.529"), + ("StepDepth", "20.000"), + ("HeelDepth", "0.000"), + ("StepShape", "step"), + ("Tenon", "no"), + ("TenonWidth", "40.000"), + ("TenonHeight", "40.000"), + ] + ), + OrderedDict( + [ + ("Name", "StepJoint"), + ("Priority", "0"), + ("Process", "yes"), + ("ProcessID", "0"), + ("ReferencePlaneID", "4"), + ("Orientation", "start"), + ("StartX", "49.780"), + ("StrutInclination", "105.435"), + ("StepDepth", "0.000"), + ("HeelDepth", "10.000"), + ("StepShape", "heel"), + ("Tenon", "no"), + ("TenonWidth", "40.000"), + ("TenonHeight", "40.000"), + ] + ), + OrderedDict( + [ + ("Name", "StepJoint"), + ("Priority", "0"), + ("Process", "yes"), + ("ProcessID", "0"), + ("ReferencePlaneID", "2"), + ("Orientation", "end"), + ("StartX", "999.900"), + ("StrutInclination", "119.519"), + ("StepDepth", "20.000"), + ("HeelDepth", "10.000"), + ("StepShape", "double"), + ("Tenon", "no"), + ("TenonWidth", "40.000"), + ("TenonHeight", "40.000"), + ] + ), +] + + +@pytest.mark.parametrize( + "main_beam_index, expected_notch_params, cutting_plane_index, start_y, notch_limited, notch_width, step_depth, heel_depth, strut_height, tapered_heel, ref_side_index", + [ + (0, EXPECTED_NOTCH_PARAMS[0], 0, 10.0, True, 60.0, 20.0, 0.0, 80.0, False, 2), # main_beam_a + (1, EXPECTED_NOTCH_PARAMS[1], 3, 0.0, False, 80.0, 0.0, 10.0, 60.0, False, 3), # main_beam_b + (2, EXPECTED_NOTCH_PARAMS[2], 1, 0.0, False, 80.0, 20.0, 10.0, 60.0, False, 1), # main_beam_c + ], +) +def test_stepjointnotch_params( + main_beams, + cross_beam, + main_beam_index, + expected_notch_params, + cutting_plane_index, + start_y, + notch_limited, + notch_width, + step_depth, + heel_depth, + strut_height, + tapered_heel, + ref_side_index, +): + # Create the StepJointNotch + step_joint_notch = StepJointNotch.from_plane_and_beam( + plane=main_beams[main_beam_index].ref_sides[cutting_plane_index], + beam=cross_beam, + start_y=start_y, + notch_limited=notch_limited, + notch_width=notch_width, + step_depth=step_depth, + heel_depth=heel_depth, + strut_height=strut_height, + tapered_heel=tapered_heel, + ref_side_index=ref_side_index, + ) + + # Validate generated parameters + generated_params = step_joint_notch.params_dict + for key, value in expected_notch_params.items(): + assert generated_params[key] == value + + +@pytest.mark.parametrize( + "main_beam_index, expected_step_params, cutting_plane_index, step_depth, heel_depth, tapered_heel, ref_side_index", + [ + (0, EXPECTED_STEP_PARAMS[0], 2, 20.0, 0.0, False, 0), # main_beam_a + (1, EXPECTED_STEP_PARAMS[1], 3, 0.0, 10.0, False, 3), # main_beam_b + (2, EXPECTED_STEP_PARAMS[2], 1, 20.0, 10.0, False, 1), # main_beam_c + ], +) +def test_stepjoint_params( + main_beams, + cross_beam, + main_beam_index, + expected_step_params, + cutting_plane_index, + step_depth, + heel_depth, + tapered_heel, + ref_side_index, +): + # Create the StepJoint + step_joint = StepJoint.from_plane_and_beam( + cross_beam.ref_sides[cutting_plane_index], + main_beams[main_beam_index], + step_depth, + heel_depth, + tapered_heel, + ref_side_index, + ) + + # Validate generated parameters + generated_params = step_joint.params_dict + for key, value in expected_step_params.items(): + assert generated_params[key] == value From 07260ce488d7e25603d028a4f2911f586d1120d8 Mon Sep 17 00:00:00 2001 From: papachap Date: Tue, 22 Oct 2024 14:21:02 +0200 Subject: [PATCH 60/63] TOL inclusion and cleaning --- src/compas_timber/_fabrication/step_joint.py | 5 ++-- .../_fabrication/step_joint_notch.py | 11 ++++----- src/compas_timber/connections/utilities.py | 23 +++++++++++-------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index ae2ae0f96..36aa08f07 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -240,12 +240,12 @@ def from_plane_and_beam(cls, plane, beam, step_depth=20.0, heel_depth=0.0, taper """ # type: (Plane|Frame, Beam, float, float, bool, int) -> StepJoint - # TODO: the stepjointnotch is always orthogonal, this means that the surface should be perpendicular to the beam's ref_side | should there be a check for that? + if isinstance(plane, Frame): plane = Plane.from_frame(plane) plane.normal = plane.normal * -1 # flip the plane normal to point towards the beam # define ref_side & ref_edge - ref_side = beam.ref_sides[ref_side_index] # TODO: is this arbitrary? + ref_side = beam.ref_sides[ref_side_index] ref_edge = Line.from_point_and_vector(ref_side.point, ref_side.xaxis) # calculate orientation @@ -482,7 +482,6 @@ def add_tenon(self, tenon_width, tenon_height): The height of the tenon. tenon_height < 1000.0. """ self.tenon = True - # self.tenon_width = beam.width / 4 # TODO: should this relate to the beam? typically 1/3 or 1/4 of beam.width self.tenon_width = tenon_width self.tenon_height = ( self.step_depth if tenon_height < self.step_depth else tenon_height diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index b358d7772..b9e3219c7 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -316,14 +316,12 @@ def from_plane_and_beam( """ # type: (Plane|Frame, Beam, float, bool, float, float, float, float, bool, int) -> StepJointNotch - # TODO: the stepjointnotch is always orthogonal, this means that the surface should be perpendicular to the beam's ref_side | should there be a check for that? - - # define ref_side & ref_edge - ref_side = beam.ref_sides[ref_side_index] # TODO: is this arbitrary? - ref_edge = Line.from_point_and_vector(ref_side.point, ref_side.xaxis) if isinstance(plane, Frame): plane = Plane.from_frame(plane) + # define ref_side & ref_edge + ref_side = beam.ref_sides[ref_side_index] + ref_edge = Line.from_point_and_vector(ref_side.point, ref_side.xaxis) # calculate orientation orientation = cls._calculate_orientation(ref_side, plane) @@ -343,7 +341,7 @@ def from_plane_and_beam( else: notch_width = beam.width - # restrain step_depth & heel_depth to beam's height # TODO: should it be restrained? should they be proportional to the beam's dimensions? + # restrain step_depth & heel_depth to beam's height if step_depth > beam.height: step_depth = beam.height print("Step depth is too large for the beam's height. It has been adjusted to the beam's height.") @@ -562,7 +560,6 @@ def add_mortise(self, mortise_width, mortise_height, beam): The height of the mortise. mortise_height < 1000.0. """ self.mortise = True - # self.mortise_width = beam.width / 4 # TODO: should this relate to the beam? typically 1/3 or 1/4 of beam.width self.mortise_width = mortise_width if mortise_height > beam.height: # TODO: should this be constrained? diff --git a/src/compas_timber/connections/utilities.py b/src/compas_timber/connections/utilities.py index 21d7b3388..890218c59 100644 --- a/src/compas_timber/connections/utilities.py +++ b/src/compas_timber/connections/utilities.py @@ -1,6 +1,7 @@ from compas.geometry import Point from compas.geometry import angle_vectors from compas.geometry import intersection_line_line +from compas.tolerance import TOL def beam_ref_side_incidence(beam_a, beam_b, ignore_ends=True): @@ -96,7 +97,7 @@ def beam_ref_side_incidence_with_vector(beam_b, vector, ignore_ends=True): return ref_side_angles -def are_beams_coplanar(beam_a, beam_b, tolerance=1e-3): +def are_beams_coplanar(beam_a, beam_b, tol=TOL): """ Checks if two beams are coplanar based on the cross product of their centerline directions. @@ -106,8 +107,8 @@ def are_beams_coplanar(beam_a, beam_b, tolerance=1e-3): The first beam. beam_b : :class:`~compas_timber.parts.Beam` The second beam. - tolerance : float, optional - The tolerance for the dot product comparison, default is 1e-3. + tol : float, optional + The tolerance for the dot product comparison. Returns ------- @@ -123,12 +124,14 @@ def are_beams_coplanar(beam_a, beam_b, tolerance=1e-3): dot_with_beam_a_normal = abs(cross_vector.dot(beam_a.frame.normal)) # Check if both dot products are close to 0 or 1 (indicating coplanarity) - return (1 - tolerance <= dot_with_beam_b_normal <= 1 + tolerance or 0 <= dot_with_beam_b_normal <= tolerance) and ( - 1 - tolerance <= dot_with_beam_a_normal <= 1 + tolerance or 0 <= dot_with_beam_a_normal <= tolerance - ) + is_beam_a_normal_coplanar = tol.is_close(dot_with_beam_a_normal, 1.0) or tol.is_zero(dot_with_beam_a_normal) + is_beam_b_normal_coplanar = tol.is_close(dot_with_beam_b_normal, 1.0) or tol.is_zero(dot_with_beam_b_normal) + # Return True if both beams are coplanar + return is_beam_a_normal_coplanar and is_beam_b_normal_coplanar -def check_beam_alignment(beam_a, beam_b, tolerance=1e-3): + +def check_beam_alignment(beam_a, beam_b, tol=TOL): """ Checks the alignment of two beams by comparing their centerline directions and the normal of beam_b's frame. @@ -142,8 +145,8 @@ def check_beam_alignment(beam_a, beam_b, tolerance=1e-3): The first beam, used to check the alignment with beam_b. beam_b : :class:`~compas_timber.parts.Beam` The second beam, whose alignment is checked relative to beam_a. - tolerance : float, optional - A tolerance value for determining near-perpendicular or near-parallel alignment. Default is 1e-3. + tol : float, optional + A tolerance value for determining near-perpendicular or near-parallel alignment. Returns ------- @@ -158,4 +161,4 @@ def check_beam_alignment(beam_a, beam_b, tolerance=1e-3): dot_with_beam_b_normal = abs(cross_vector.dot(beam_b.frame.normal)) # Return True if the beams are nearly perpendicular (dot product close to 0) - return 0 <= dot_with_beam_b_normal <= tolerance + return tol.is_zero(dot_with_beam_b_normal) From 633da79fdc0ee894fc475e67a2e8eda25850857a Mon Sep 17 00:00:00 2001 From: panos ppchr <145543755+papachap@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:32:11 +0200 Subject: [PATCH 61/63] Update src/compas_timber/connections/utilities.py Co-authored-by: Chen Kasirer --- src/compas_timber/connections/utilities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas_timber/connections/utilities.py b/src/compas_timber/connections/utilities.py index 890218c59..153f519ab 100644 --- a/src/compas_timber/connections/utilities.py +++ b/src/compas_timber/connections/utilities.py @@ -145,7 +145,7 @@ def check_beam_alignment(beam_a, beam_b, tol=TOL): The first beam, used to check the alignment with beam_b. beam_b : :class:`~compas_timber.parts.Beam` The second beam, whose alignment is checked relative to beam_a. - tol : float, optional + tol : :class:`compas.tolerance.Tolerance`, optional A tolerance value for determining near-perpendicular or near-parallel alignment. Returns From 3d3d08ec083c7477a053def4a2a6faa037befd96 Mon Sep 17 00:00:00 2001 From: panos ppchr <145543755+papachap@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:32:20 +0200 Subject: [PATCH 62/63] Update src/compas_timber/connections/utilities.py Co-authored-by: Chen Kasirer --- src/compas_timber/connections/utilities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas_timber/connections/utilities.py b/src/compas_timber/connections/utilities.py index 153f519ab..af0c64580 100644 --- a/src/compas_timber/connections/utilities.py +++ b/src/compas_timber/connections/utilities.py @@ -107,7 +107,7 @@ def are_beams_coplanar(beam_a, beam_b, tol=TOL): The first beam. beam_b : :class:`~compas_timber.parts.Beam` The second beam. - tol : float, optional + tol : :class:`compas.tolerance.Tolerance`, optional The tolerance for the dot product comparison. Returns From a6acf25cacf4c6479992f87695bd1647596b3c5b Mon Sep 17 00:00:00 2001 From: panos ppchr <145543755+papachap@users.noreply.github.com> Date: Tue, 22 Oct 2024 18:00:57 +0200 Subject: [PATCH 63/63] Default value for step --- src/compas_timber/connections/t_step_joint.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compas_timber/connections/t_step_joint.py b/src/compas_timber/connections/t_step_joint.py index d4097f7c5..1c7a593ac 100644 --- a/src/compas_timber/connections/t_step_joint.py +++ b/src/compas_timber/connections/t_step_joint.py @@ -71,7 +71,7 @@ def __init__( self, main_beam, cross_beam, - step_shape, + step_shape=None, step_depth=None, heel_depth=None, tapered_heel=None, @@ -83,7 +83,7 @@ def __init__( self.main_beam_guid = str(main_beam.guid) if main_beam else None self.cross_beam_guid = str(cross_beam.guid) if cross_beam else None - self.step_shape = step_shape + self.step_shape = 0 if step_shape is None else step_shape self.step_depth, self.heel_depth = self.set_step_depths(step_depth, heel_depth) self.tapered_heel = tapered_heel