diff --git a/CHANGELOG.md b/CHANGELOG.md index 86512f2fe..5456761dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed * Fixed `ValueErrorException` in `as_dict()` method of `BTLxProcessingParams` class by ensuring precision specifiers are used with floats. +* Removed model argument from `BTLxWriter` in the GH component and updated it to always return the BTLx string. +* Fixed a bug in `compas_timber.Fabrication.StepJointNotch` related to the `orientation` and `strut_inclination` parameters. * Fixed the error message when beam endpoints coincide, e.g. when a closed polyline is used as input. * Changed `index` input of `ShowFeatureErrors` and `ShowJoiningErrors` do have default value of 0. * Fixed spelling of `BeamJoinningError` to `BeamJoiningError`. * Changed `process_joinery()` method to handle `BeamJoiningError` exceptions and return them. Also updated `Model` GH component. * Updated `add_joint_error()` method in `DebugInformation` class to handle lists. +* Changed `compas_timber.fabrication.Lap` so that the volume is generated fully from the relevant BTLx params. +* Refactored `compas_timber.connections.LapJoint` to comply with the new system. +* Changed `THalfLapJoint`, `LHalfLapJoint`, `XHalfLapJoint` from `compas_timber.connections` so that they use the `Lap` BTLx processing. +* Renamed all `X/T/LHalfLapJoint` classes to `X/T/LLapJoint`. ### Removed @@ -37,8 +43,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Updated the API documentation for `connections`, `elements`, `fabrication`, `ghpython`, `planning` packages. * Refactored all btlx `process` references to `processing`, including base classes, properties, variables, and docstrings. * Refactored `BTLx` to `BTLxWriter` in the `compas_timber.Fabrication` package. -* Removed model argument from `BTLxWriter` in the GH component and updated it to always return the BTLx string. -* Fixed a bug in `compas_timber.Fabrication.StepJointNotch` related to the `orientation` and `strut_inclination` parameters. ### Removed diff --git a/docs/api/compas_timber.connections.rst b/docs/api/compas_timber.connections.rst index 6657aa9c3..dc7ae2d7f 100644 --- a/docs/api/compas_timber.connections.rst +++ b/docs/api/compas_timber.connections.rst @@ -18,16 +18,16 @@ Classes LapJoint LButtJoint LFrenchRidgeLapJoint - LHalfLapJoint + LLapJoint LMiterJoint NullJoint TBirdsmouthJoint TButtJoint TDovetailJoint - THalfLapJoint + TLapJoint TStepJoint TenonMortiseJoint - XHalfLapJoint + XLapJoint Functions ========= diff --git a/docs/tutorials/grasshopper/joint_rules.rst b/docs/tutorials/grasshopper/joint_rules.rst index 55fc8f329..3703e6759 100644 --- a/docs/tutorials/grasshopper/joint_rules.rst +++ b/docs/tutorials/grasshopper/joint_rules.rst @@ -7,7 +7,7 @@ The Joints between :doc:`beams` are defined by Joint Rules. There are four kinds .. note:: **Joint Topologies** - + There are three main topologies of how beams can connect to each other: **L**, **T** and **X**. .. image:: ../images/joint_topologies_diagramm.png @@ -23,7 +23,7 @@ Joint Rules Components **Dynamic Components** Joint Rules Components are dynamic: First place them on the Grasshopper Canvas. Now you can define the Joint they should apply by Right-Click & Selection from the Drop-Down List. The Inputs might change because every Joint has its own specific settings. - + .. image:: ../images/joint_rules_dynamic.gif :width: 55% @@ -32,11 +32,11 @@ Joint Rules Components Default Joint Rules ^^^^^^^^^^^^^^^^^^^ -This Component applies a L-Miter to all L-Topologies, a T-Butt to all T-Topologies and a X-HalfLap to all X-Topologies. +This Component applies a L-Miter to all L-Topologies, a T-Butt to all T-Topologies and a X-HalfLap to all X-Topologies. .. image:: ../images/gh_joint_rules_default.png :width: 20% - + | Topological Joint Rules @@ -57,7 +57,7 @@ These Joint Rules are more specific and will overwrite the Default Joint Rules. Category Joint Rules ^^^^^^^^^^^^^^^^^^^^ -This Joint Rule will overwrite all Topological Joint Rules. The Component defines a Joint type for all Joints between two beam Categories. The Categories are assigned through the string-input `Category` in the component :code:`Beam`. The inputs are variable and depend on the joint type. +This Joint Rule will overwrite all Topological Joint Rules. The Component defines a Joint type for all Joints between two beam Categories. The Categories are assigned through the string-input `Category` in the component :code:`Beam`. The inputs are variable and depend on the joint type. .. image:: ../images/gh_joint_rules_category.png :width: 40% @@ -104,7 +104,7 @@ Inputs: | -L-HalfLap +L-Lap ^^^^^^^^^ The *L-Half Lap* topology is when two beams meet at their ends at an angle. An L-Half Lap joint extends the two beams while removing the upper half of the overlap of one beam and the lower half of the overlaps the other to create a clean corner joint. @@ -166,7 +166,7 @@ Inputs: | -T-HalfLap +T-Lap ^^^^^^^^^ A T-Half Lap joint crates an overlap between the *main beam* and the *cross beam*. The *cross beam* is extended to the opposite face of the *main beam* and cut flush with it to create a planar surface. @@ -182,7 +182,7 @@ Inputs: | -X-HalfLap +X-Lap ^^^^^^^^^ The X-Half Lap joint removes the upper half of the overlap from one beam and the lower half from the other. @@ -207,7 +207,7 @@ Joint L Topology T Topology X Topology ============ =========== =========== =========== Butt X X Miter x -HalfLap X X X +Lap X X X French Ridge X ============ =========== =========== =========== diff --git a/examples/Grasshopper/tests/test_halflap.ghx b/examples/Grasshopper/tests/test_halflap.ghx new file mode 100644 index 000000000..5d02de605 --- /dev/null +++ b/examples/Grasshopper/tests/test_halflap.ghx @@ -0,0 +1,11780 @@ + + + + + + + + 0 + 2 + 2 + + + + + + + 1 + 0 + 7 + + + + + + fa5d732c-b31a-4064-bc30-fdd75169872b + Shaded + Selection + 1 + + 100;150;0;0 + + + 100;0;150;0 + + + + + + 638584536051543966 + + test_halflap.ghx + + + + + 0 + + + + + + -378 + -1252 + + 1.2750001 + + + + + 0 + + + + + + + 0 + + + + + 2 + + + + + GhPython, Version=7.37.24107.15001, Culture=neutral, PublicKeyToken=null + 7.37.24107.15001 + + 00000000-0000-0000-0000-000000000000 + + + + + + + Pufferfish, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null + 3.0.0.0 + Michael Pryor + 1c9de8a1-315f-4c56-af06-8f69fee80a7a + Pufferfish + 3.0.0.0 + + + + + + + 102 + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 18d2fa4a-de1b-4cfd-bf80-4b8d5fa0182f + e22e52d8-5a24-494f-8bd6-2f5c368cced0 + 9d82d577-6636-40a6-89a2-6f58d4a8a308 + 7f188938-0adc-40a1-aaa1-c82bf68c0621 + 13ac4c40-1353-44eb-ba9c-dfde3983e024 + 5f1aeb7c-013f-4d68-9e03-9abc339aaf8d + 6 + fe7c137b-ed91-4aea-b1db-a11f5362c7b3 + Group + + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + b86d8575-a631-44ae-831c-a26d103050c7 + d785f7da-1ae4-4767-9398-648e133a5bd2 + 98e83dd4-f0d0-4701-ba42-e474164cfd82 + 8fa01d08-afaf-4258-bace-d765ebf9577f + d475e429-c48f-4bea-b061-06ab3815cfd3 + 702b1bbc-2b58-478b-b3ba-16a48af876bd + bbf2811a-72c7-4b27-aabe-22952d508aa7 + ed5575ff-1733-49b5-87a3-30e4e79a9149 + 60dc433a-3e4b-4822-9277-ffa1be8ca93b + 2858ecb7-755a-4a0e-be46-21a44003594a + 1bf6c7ca-f719-453d-ae24-8fb4402a0f6e + a733d08f-1f60-40f4-b07a-a99476c2a8ab + 2cbb77cb-271b-4c58-b3b5-67700d482c61 + 13 + f1faf354-f330-48d3-84f1-385f98665086 + Group + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + d9d51b18-d5af-4f8f-822f-9fbd0f30d642 + Number Slider + + false + 0 + + + + + + 842 + 1368 + 166 + 20 + + + 842.9432 + 1368.29 + + + + + + 3 + 1 + 1 + 120 + 0 + 0 + 120 + + + + + + + + + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + Curve + + + + + Contains a collection of generic curves + true + 18d2fa4a-de1b-4cfd-bf80-4b8d5fa0182f + Curve + Crv + false + 0 + + + + + + 85 + 1307 + 50 + 24 + + + 110.3377 + 1319.804 + + + + + + 1 + + + + + 1 + {0} + + + + + -1 + + Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesbmekYmhgbmeoamBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS7oUCPJzMQAY/iJgKxEy/6pkYOqH++v2fiUEEyn525bqf+8vLgvufMgg0Myp98Juz+/YfoHwgVF4AZK6UdYMd5/q7Dh/+/5cPK155QKIotjs+Ncjh+PWXjcKMDx0+oYkzMSBABPuUNsuyJQ4gl7yqvNL2v56pASbHzTCYAAA= + + 00000000-0000-0000-0000-000000000000 + + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 478fc3b1-c4f8-4087-8fe6-c2905e21be9a + Number Slider + + false + 0 + + + + + + 869 + 1583 + 166 + 20 + + + 869.7459 + 1583.228 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 80 + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + da50cc8d-94c7-4348-b6e9-2f46272647d3 + Number Slider + + false + 0 + + + + + + 869 + 1607 + 169 + 20 + + + 869.5651 + 1607.486 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 100 + + + + + + + + + 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 + 645777e9-3e35-4980-b9de-1af150e02dd0 + 0427415b-7064-4ecf-875e-ad8efb142eee + c285f14a-7ead-4d46-9545-12950096c0b4 + 5 + 1dc4a321-0021-4a13-90df-e852c7e4e773 + Group + MainBeam + + + + + + + + + + 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 + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + c64fa590-0923-4f6c-bc02-60bf9bf5e33c + Number Slider + + false + 0 + + + + + + 844 + 1343 + 166 + 20 + + + 844.4375 + 1343.346 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 60 + + + + + + + + + 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: + length = self._get_centerline_length(centerline) + width = [length / 20] + if not height: + length = self._get_centerline_length(centerline) + height = [length / 10] + + # 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 + + def _get_centerline_length(self, centerline): + centerline_length = [] + for i in centerline: + centerline_length.append(rs.CurveLength(i)) + if centerline_length: + length_average = sum(centerline_length) / len(centerline_length) + else: + length_average = 1 + return length_average + + Creates a Beam from a LineCurve. + + 190 + 190 + + + 835 + 874 + + true + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDAAACwwBP0AiyAAAAWtJREFUSEutlDFqhEAYhecEegALS0vBOmJnqZWkEQQrGyHYWFhY5hIBj+AR3Bt4gYBlSrs0gUx4Q5aYf2Z2xmSFbwtn5v3sm2+Xcc7Zf+m67jEIgnfGGD9SVZW8+Sx1Xb84jvNJw6dp4tu2yQdsGYbBT9P0lQb7vs/XdeXLsnDXdeWDNugqSZKE7/vOx3H8eU8PmyjLcqbBAKEIz/P89xoN0IFK4jh+o8GoYZ5nUQvqoetSkApU4nneBz0chqG4SFyo6JuG2wwoiuIiHfpWEJUIFRXrxgGoJIqiXTpwUBDfgK5J0GDQtu2Tym1JQRqmgobrKlEqaMM1uO/7B5XbQKugAfzKRXjTNM+qSowKaoBxMA/ZLMuylW4AVgoqgBgQ5NoMPrSPUUEC7o/eKR5pI8BD3+lAvaiZht9lAMSAIDT4LgPwd33sW8WfB0BBGqbi9ICjgjacGkAVtMF6gEpBG4wDbilow80BJgVt+AJsC3YHyBQqjwAAAABJRU5ErkJggg== + + false + bf722eeb-fc58-4d41-9636-a4abe4ff1b05 + true + true + CT: Beam + Beam + + + + + + 1057 + 1288 + 145 + 124 + + + 1148 + 1350 + + + + + + 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. + a1e698be-e5cc-4007-ad45-7ad242daacb1 + Centerline + Centerline + true + 1 + true + 22a7c548-2a3a-46f3-9aed-df6d798fd1fe + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1059 + 1290 + 74 + 20 + + + 1097.5 + 1300 + + + + + + + + 1 + true + Vector defining the orientation of the cross-section (corresponds to the height dimension). If None, a default will be taken. + ea847ed7-5489-459a-97d1-5c8d044128c3 + ZVector + ZVector + true + 1 + true + 0 + 15a50725-e3d3-4075-9f7c-142ba5f40747 + + + + + + 1059 + 1310 + 74 + 20 + + + 1097.5 + 1320 + + + + + + + + 1 + true + Width of the cross-section (usually the smaller dimension). + 019cea01-9ec7-4a75-b6f2-0c67c3be7aef + Width + Width + true + 1 + true + da50cc8d-94c7-4348-b6e9-2f46272647d3 + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1059 + 1330 + 74 + 20 + + + 1097.5 + 1340 + + + + + + + + 1 + true + Height of the cross-section (usually the larger dimension). + 24087a33-ea83-440f-83af-61b49d8d317c + Height + Height + true + 1 + true + da50cc8d-94c7-4348-b6e9-2f46272647d3 + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1059 + 1350 + 74 + 20 + + + 1097.5 + 1360 + + + + + + + + 1 + true + Category of a beam. + 0c93adef-0bc0-421a-9c08-5af17ce6be3c + Category + Category + true + 1 + true + 0 + 37261734-eec7-4f50-b6a8-b8d1f3c4396b + + + + + + 1059 + 1370 + 74 + 20 + + + 1097.5 + 1380 + + + + + + + + true + (optional) If True, the attributes in the referenced object will be updated/overwritten with the new values given here. + 8203ee9f-c2d2-42b4-b17c-51f97c6eddd2 + updateRefObj + updateRefObj + true + 0 + true + 0 + d60527f5-b5af-4ef6-8970-5f96fe412559 + + + + + + 1059 + 1390 + 74 + 20 + + + 1097.5 + 1400 + + + + + + + + Beam object(s). + d2365ef0-f85c-4709-9361-522790ff3ed5 + Beam + Beam + false + 0 + + + + + + 1163 + 1290 + 37 + 60 + + + 1181.5 + 1320 + + + + + + + + Shape of the beam's blank. + b8e540ec-b143-467b-a0d4-9b8c96f0c6ee + Blank + Blank + false + 0 + + + + + + 1163 + 1350 + 37 + 60 + + + 1181.5 + 1380 + + + + + + + + + + + + + + 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: + length = self._get_centerline_length(centerline) + width = [length / 20] + if not height: + length = self._get_centerline_length(centerline) + height = [length / 10] + + # 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 + + def _get_centerline_length(self, centerline): + centerline_length = [] + for i in centerline: + centerline_length.append(rs.CurveLength(i)) + if centerline_length: + length_average = sum(centerline_length) / len(centerline_length) + else: + length_average = 1 + return length_average + + Creates a Beam from a LineCurve. + + 1352 + 1040 + + + 835 + 874 + + true + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDAAACwwBP0AiyAAAAWtJREFUSEutlDFqhEAYhecEegALS0vBOmJnqZWkEQQrGyHYWFhY5hIBj+AR3Bt4gYBlSrs0gUx4Q5aYf2Z2xmSFbwtn5v3sm2+Xcc7Zf+m67jEIgnfGGD9SVZW8+Sx1Xb84jvNJw6dp4tu2yQdsGYbBT9P0lQb7vs/XdeXLsnDXdeWDNugqSZKE7/vOx3H8eU8PmyjLcqbBAKEIz/P89xoN0IFK4jh+o8GoYZ5nUQvqoetSkApU4nneBz0chqG4SFyo6JuG2wwoiuIiHfpWEJUIFRXrxgGoJIqiXTpwUBDfgK5J0GDQtu2Tym1JQRqmgobrKlEqaMM1uO/7B5XbQKugAfzKRXjTNM+qSowKaoBxMA/ZLMuylW4AVgoqgBgQ5NoMPrSPUUEC7o/eKR5pI8BD3+lAvaiZht9lAMSAIDT4LgPwd33sW8WfB0BBGqbi9ICjgjacGkAVtMF6gEpBG4wDbilow80BJgVt+AJsC3YHyBQqjwAAAABJRU5ErkJggg== + + false + da647de1-dd10-4e78-b1d6-8bd3064b4856 + true + true + CT: Beam + Beam + + + + + + 1047 + 1495 + 145 + 124 + + + 1138 + 1557 + + + + + + 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. + 8fbbdfc0-ddf1-497d-bab5-127859a8497d + Centerline + Centerline + true + 1 + true + bd53ef51-7947-47ac-ad07-27b174cf4ead + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1049 + 1497 + 74 + 20 + + + 1087.5 + 1507 + + + + + + + + 1 + true + Vector defining the orientation of the cross-section (corresponds to the height dimension). If None, a default will be taken. + 30b9a2e1-c92a-4d4f-bfb7-b4e28b54225b + ZVector + ZVector + true + 1 + true + 0 + 15a50725-e3d3-4075-9f7c-142ba5f40747 + + + + + + 1049 + 1517 + 74 + 20 + + + 1087.5 + 1527 + + + + + + + + 1 + true + Width of the cross-section (usually the smaller dimension). + 99f9429c-66da-4746-9d35-3608cee70f2a + Width + Width + true + 1 + true + da50cc8d-94c7-4348-b6e9-2f46272647d3 + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1049 + 1537 + 74 + 20 + + + 1087.5 + 1547 + + + + + + + + 1 + true + Height of the cross-section (usually the larger dimension). + c72cc71d-f13e-4d93-a0f9-0ae82b991a79 + Height + Height + true + 1 + true + da50cc8d-94c7-4348-b6e9-2f46272647d3 + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1049 + 1557 + 74 + 20 + + + 1087.5 + 1567 + + + + + + + + 1 + true + Category of a beam. + 87270c6b-2d61-415d-a9e6-2b8997847e24 + Category + Category + true + 1 + true + 0 + 37261734-eec7-4f50-b6a8-b8d1f3c4396b + + + + + + 1049 + 1577 + 74 + 20 + + + 1087.5 + 1587 + + + + + + + + true + (optional) If True, the attributes in the referenced object will be updated/overwritten with the new values given here. + 24a45657-2e86-4574-b96b-fdfc1e768096 + updateRefObj + updateRefObj + true + 0 + true + 0 + d60527f5-b5af-4ef6-8970-5f96fe412559 + + + + + + 1049 + 1597 + 74 + 20 + + + 1087.5 + 1607 + + + + + + + + Beam object(s). + 287d3ad7-dfd0-4415-b5a0-ff41819a7aba + Beam + Beam + false + 0 + + + + + + 1153 + 1497 + 37 + 60 + + + 1171.5 + 1527 + + + + + + + + Shape of the beam's blank. + 1c9b9019-ed24-499d-a988-36f7f95ea194 + Blank + Blank + false + 0 + + + + + + 1153 + 1557 + 37 + 60 + + + 1171.5 + 1587 + + + + + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + GhPython Script + + + + + from compas_timber.fabrication import Lap +from compas_timber.fabrication import JackRafterCut +from compas_timber.connections import TLapJoint +from compas_timber.model import TimberModel +from compas_timber.connections.utilities import beam_ref_side_incidence +from compas_rhino.conversions import frame_to_rhino, brep_to_rhino, plane_to_rhino, box_to_rhino +from compas_rhino import unload_modules + +unload_modules('compas_timber') + +#cross +cross_ref_side_dict = beam_ref_side_incidence(main_beam, cross_beam, ignore_ends=True) +cross_ref_side_index = min(cross_ref_side_dict, key=cross_ref_side_dict.get) +# main +main_ref_side_dict = beam_ref_side_incidence(cross_beam, main_beam, ignore_ends=True) +main_ref_side_index = max(main_ref_side_dict, key=main_ref_side_dict.get) +#main_ref_side_indices = main_ref_side_index, (main_ref_side_index+2)%4 +main_plane = main_beam.ref_sides[main_ref_side_index] +cross_plane = cross_beam.ref_sides[cross_ref_side_index] +lap_width = main_beam.height if main_ref_side_index % 2 == 0 else main_beam.width + +# instanciate lap processing from plane and beam +lap = Lap.from_plane_and_beam(main_plane, cross_beam, lap_width, depth, cross_ref_side_index) +lap_frame = lap.planes_from_params_and_beam(cross_beam) +volume = lap.volume_from_params_and_beam(cross_beam) +cross_geo = cross_beam.compute_geometry() +cross_geo = lap.apply(cross_geo, cross_beam) + + +# get cutting plane for JackRafterCut processing +cross_plane = cross_beam.ref_sides[cross_ref_side_index] +cross_plane.xaxis = -cross_plane.xaxis +cross_plane.translate(cross_plane.normal*depth) +# instanciate jack cut processing +jack_cut = JackRafterCut.from_plane_and_beam(cross_plane, main_beam, main_ref_side_index) +cutting_plane_main = jack_cut.plane_from_params_and_beam(main_beam) +main_geo = main_beam.compute_geometry() +main_geo = jack_cut.apply(main_geo, main_beam) +lap_frame = [plane_to_rhino(frame) for frame in lap_frame] + +# viz frames +cross_ref_side = frame_to_rhino(cross_beam.ref_sides[3]) +main_ref_side = frame_to_rhino(main_beam.ref_sides[1]) + +cutting_plane_main = plane_to_rhino(cutting_plane_main) +# viz volumes +rg_cross = brep_to_rhino(cross_geo) +rg_main = brep_to_rhino(main_geo) +#rg_volume = brep_to_rhino(volume) + + + + + + + + + + + + +model = TimberModel() +model.add_element(main_beam) +model.add_element(cross_beam) +joint = TLapJoint.create(model, main_beam, cross_beam) + +model.process_joinery() + + + + + + + + + + + GhPython provides a Python script component + + 1181 + 104 + + + 1298 + 1537 + + true + true + false + false + d8159329-9c95-4b5c-9f51-eccf5cfb85ed + false + true + GhPython Script + Python + + + + + + 1742 + 1123 + 202 + 164 + + + 1823 + 1205 + + + + + + 3 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 8 + 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 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + true + Script variable Python + 18146bfd-6418-49f9-bdb0-cbb5fa7aa00d + main_beam + main_beam + true + 0 + true + 287d3ad7-dfd0-4415-b5a0-ff41819a7aba + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1744 + 1125 + 64 + 53 + + + 1777.5 + 1151.667 + + + + + + + + true + Script input cross_beam. + 340c8917-a319-4ccc-b2f4-c78f4fe6b675 + cross_beam + cross_beam + true + 0 + true + d2365ef0-f85c-4709-9361-522790ff3ed5 + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1744 + 1178 + 64 + 53 + + + 1777.5 + 1205 + + + + + + + + true + Script input depth. + d8004857-38e9-4a24-b6c1-564c840d7d92 + depth + depth + true + 0 + true + aa01afa4-173a-4459-9bf1-9dc2e745c93f + 1 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1744 + 1231 + 64 + 54 + + + 1777.5 + 1258.333 + + + + + + + + The execution information, as output and error streams + 457734f7-3478-4434-a17d-636820c0980c + out + out + false + 0 + + + + + + 1838 + 1125 + 104 + 20 + + + 1890 + 1135 + + + + + + + + Script output main_ref_side. + 2ed0ebc1-6a52-4a3c-84b2-d3f4a4b66f37 + main_ref_side + main_ref_side + false + 0 + + + + + + 1838 + 1145 + 104 + 20 + + + 1890 + 1155 + + + + + + + + Script output cross_ref_side. + bf27e312-db21-4a25-83f4-81fba65f4e87 + cross_ref_side + cross_ref_side + false + 0 + + + + + + 1838 + 1165 + 104 + 20 + + + 1890 + 1175 + + + + + + + + Script output rg_cross. + bac24a63-74e8-44a4-99df-2c4905b3d163 + rg_cross + rg_cross + false + 0 + + + + + + 1838 + 1185 + 104 + 20 + + + 1890 + 1195 + + + + + + + + Script output rg_main. + e274454e-7ad5-42e7-9751-2366475812a4 + rg_main + rg_main + false + 0 + + + + + + 1838 + 1205 + 104 + 20 + + + 1890 + 1215 + + + + + + + + Script output cutting_plane_main. + 5daa01c6-d25f-4807-ad0c-40e0e1768710 + cutting_plane_main + cutting_plane_main + false + 0 + + + + + + 1838 + 1225 + 104 + 20 + + + 1890 + 1235 + + + + + + + + Script output rg_volume. + 857693c0-ad1b-4e96-a368-7b18c111a11c + rg_volume + rg_volume + false + 0 + + + + + + 1838 + 1245 + 104 + 20 + + + 1890 + 1255 + + + + + + + + Script output lap_frame. + 502370ce-eac6-4444-b153-000dda5f7ad2 + lap_frame + lap_frame + false + 0 + + + + + + 1838 + 1265 + 104 + 20 + + + 1890 + 1275 + + + + + + + + + + + + + + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep + + + + + Contains a collection of Breps (Boundary REPresentations) + true + 9381ae59-064b-40ba-81bc-88ceddf37ae8 + Brep + Brep + false + bac24a63-74e8-44a4-99df-2c4905b3d163 + 1 + + + + + + 1984 + 1201 + 50 + 24 + + + 2009.32 + 1213.15 + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + CT: Model + + + + + from compas.scene import Scene +from compas.tolerance import TOL +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning + +from compas_timber.errors import BeamJoiningError +from compas_timber.connections import ConnectionSolver +from compas_timber.connections import JointTopology +from compas_timber.connections import LMiterJoint +from compas_timber.connections import TButtJoint +from compas_timber.connections import XLapJoint +from compas_timber.design import CategoryRule +from compas_timber.design import DebugInfomation +from compas_timber.design import DirectRule +from compas_timber.design import JointDefinition +from compas_timber.design import TopologyRule +from compas_timber.model import TimberModel +import warnings + +JOINT_DEFAULTS = { + JointTopology.TOPO_X: XLapJoint, + JointTopology.TOPO_T: TButtJoint, + JointTopology.TOPO_L: LMiterJoint, +} + + +# workaround for https://github.com/gramaziokohler/compas_timber/issues/280 +TOL.absolute = 1e-6 + + +class ModelComponent(component): + def get_joints_from_rules(self, beams, rules, topologies): + if not isinstance(rules, list): + rules = [rules] + rules = [r for r in rules if r is not None] + + joints = [] + # rules have to be resolved into joint definitions + topo_rules = {} + cat_rules = [] + direct_rules = [] + + # TODO: refactor this into some kind of a rule reloving class/function + for r in rules: # separate category and topo and direct joint rules + if isinstance(r, TopologyRule): + if topo_rules.get(r.topology_type, None): # if rule for this Topo exists + if (r.joint_type != JOINT_DEFAULTS[r.topology_type]) or ( + len(r.kwargs) != 0 + ): # if this rule is NOT default + topo_rules[r.topology_type] = r + else: + topo_rules[r.topology_type] = r + elif isinstance(r, CategoryRule): + cat_rules.append(r) + if isinstance(r, DirectRule): + direct_rules.append(r) + + for topo in topologies: + beam_a = topo["beam_a"] + beam_b = topo["beam_b"] + detected_topo = topo["detected_topo"] + pair = beam_a, beam_b + pair_joined = False + + if detected_topo == JointTopology.TOPO_UNKNOWN: + continue + + for rule in direct_rules: # apply direct rules first + if rule.comply(pair): + joints.append(JointDefinition(rule.joint_type, rule.beams, **rule.kwargs)) + pair_joined = True + break + + if not pair_joined: # if no direct rule applies, apply category rules next + for rule in cat_rules: + if not rule.comply(pair): + continue + if rule.joint_type.SUPPORTED_TOPOLOGY != detected_topo: + msg = "Conflict detected! Beams: {}, {} meet with topology: {} but rule assigns: {}" + self.AddRuntimeMessage( + Warning, + msg.format( + beam_a.guid, + beam_b.guid, + JointTopology.get_name(detected_topo), + rule.joint_type.__name__, + ), + ) + continue + if rule.topos and detected_topo not in rule.topos: + msg = "Conflict detected! Beams: {}, {} meet with topology: {} but rule allows: {}" + self.AddRuntimeMessage( + Warning, + msg.format( + beam_a.guid, + beam_b.guid, + JointTopology.get_name(detected_topo), + [JointTopology.get_name(topo) for topo in rule.topos], + ), + ) + continue + # sort by category to allow beam role by order (main beam first, cross beam second) + beam_a, beam_b = rule.reorder([beam_a, beam_b]) + joints.append(JointDefinition(rule.joint_type, [beam_a, beam_b], **rule.kwargs)) + break # first matching rule + + else: # no category rule applies, apply topology rules + if detected_topo not in topo_rules: + continue + else: + joints.append( + JointDefinition( + topo_rules[detected_topo].joint_type, + [beam_a, beam_b], + **topo_rules[detected_topo].kwargs + ) + ) + return joints + + def RunScript(self, Elements, JointRules, Features, MaxDistance, CreateGeometry): + if not Elements: + self.AddRuntimeMessage(Warning, "Input parameter Beams failed to collect data") + if not JointRules: + self.AddRuntimeMessage(Warning, "Input parameter JointRules failed to collect data") + if not (Elements): # shows beams even if no joints are found + return + if MaxDistance is None: + MaxDistance = TOL.ABSOLUTE # compared to calculted distance, so shouldn't be just 0.0 + + Model = TimberModel() + debug_info = DebugInfomation() + for element in Elements: + # prepare elements for downstream processing + if element is None: + continue + element.remove_features() + if hasattr(element, "remove_blank_extension"): + element.remove_blank_extension() + element.debug_info = [] + Model.add_element(element) + + topologies = [] + solver = ConnectionSolver() + found_pairs = solver.find_intersecting_pairs(list(Model.beams), rtree=True, max_distance=MaxDistance) + for pair in found_pairs: + beam_a, beam_b = pair + detected_topo, beam_a, beam_b = solver.find_topology(beam_a, beam_b, max_distance=MaxDistance) + if not detected_topo == JointTopology.TOPO_UNKNOWN: + topologies.append({"detected_topo": detected_topo, "beam_a": beam_a, "beam_b": beam_b}) + Model.set_topologies(topologies) + + joints = self.get_joints_from_rules(Model.beams, JointRules, topologies) + + if joints: + handled_beams = [] + joints = [j for j in joints if j is not None] + # apply reversed. later joints in orginal list override ealier ones + for joint in joints[::-1]: + beams_to_pair = joint.elements + beam_pair_ids = set([id(beam) for beam in beams_to_pair]) + if beam_pair_ids in handled_beams: + continue + try: + joint.joint_type.create(Model, *beams_to_pair, **joint.kwargs) + except BeamJoiningError as bje: + debug_info.add_joint_error(bje) + else: + handled_beams.append(beam_pair_ids) + +# # applies extensions and features resulting from joints +# Model.process_joinery() +# Catch warnings during the process_joinery method + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + Model.process_joinery() + for warning in w: + debug_info.add_joint_error(str(warning.message)) + if Features: + features = [f for f in Features if f is not None] + for f_def in features: + for element in f_def.elements: + element.add_features(f_def.feature) + + Geometry = None + scene = Scene() + for element in Model.elements(): + if CreateGeometry: + scene.add(element.geometry) + if element.debug_info: + debug_info.add_feature_error(element.debug_info) + else: + scene.add(element.blank) + + if debug_info.has_errors: + self.AddRuntimeMessage(Warning, "Error found during joint creation. See DebugInfo output for details.") + + Geometry = scene.draw() + return Model, Geometry, debug_info + + GhPython provides a Python script component + + 142 + 217 + + + 1130 + 1501 + + true + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDAAACwwBP0AiyAAAAY5JREFUSEu1lL1tAzEMhd8IHsEjeIQbIWVKj5ARPEJG8AguU3oEFy5cBpkgQLoggINPIQ80Jd2dkaR4gMSf9yhKlK7Xq5ZC0gPI9ilUhh4k7SSxALvs76EyZEhaSToY8cXAGtsqx2dUhkS+lnTyqhOw4VvnvEUCkgZJ74bSdye3Nffh/iHnTwpI2lqFr5I2wT4K2H5jMWy2macpIGlvCcfc4yxgNu6IWDb7ii8Fer+rwJ5A8HlhcIyFuXP2qHMC5q9amy9r7HcLcwIWQ7Hj48Dg/fsPwK1nSV/WQ55mBDZ66vtHg+/x9fLghLscm55xpJuhMd8xtyH4qTC/KoYTLjjJ/yExw+EPBPhC4Cq+KMDRiBin8l6ByFEJhITTLwS4k8hVCdA/Fk/3CpBjueUemwKBlEtishcJWCw58TRdAQ/miS0VILYU1fDVJGHkXxrDk+ExN1/MpEAIeEuDloGPmF7+pIA/uXP2hZhzfto9AfqHIeND0mfD7sBHTLaDculeKYsW+FOYzGx34CMm2x3DN86+0jzsl4EUAAAAAElFTkSuQmCC + + false + ff35723b-1edb-429a-9de3-da7ac4b770dc + true + true + CT: Model + Model + + + + + + 1844 + 1473 + 198 + 104 + + + 1963 + 1525 + + + + + + 5 + 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 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 1 + true + Collection of Beams + 8024edd6-0fa4-4ea8-9c75-08cd185bd595 + 1 + Elements + Elements + true + 1 + true + bf762047-bfb8-4813-a506-3cf4fb2c17bc + 1 + 35915213-5534-4277-81b8-1bdc9e7383d2 + + + + + + 1846 + 1475 + 102 + 20 + + + 1906.5 + 1485 + + + + + + + + 1 + true + Script input JointRules. + 207cda69-0cde-4ea9-a043-815a192121ed + 1 + JointRules + JointRules + true + 1 + true + debfa811-a60a-402d-a198-bd648e1658ee + 1 + 35915213-5534-4277-81b8-1bdc9e7383d2 + + + + + + 1846 + 1495 + 102 + 20 + + + 1906.5 + 1505 + + + + + + + + 1 + true + Script input Features. + bcf5d52c-f476-47f1-9b19-897ef988289d + Features + Features + true + 1 + true + 0 + 35915213-5534-4277-81b8-1bdc9e7383d2 + + + + + + 1846 + 1515 + 102 + 20 + + + 1906.5 + 1525 + + + + + + + + true + Script input MaxDistance. + 82534af0-5b75-42d2-a57b-575ee6d58878 + MaxDistance + MaxDistance + true + 0 + true + 10636707-b664-4e14-ba9a-d85ce2a0c401 + 1 + 39fbc626-7a01-46ab-a18e-ec1c0c41685b + + + + + + 1846 + 1535 + 102 + 20 + + + 1906.5 + 1545 + + + + + + + + true + Script input CreateGeometry. + a60a06af-12b9-40bf-804b-4fc8d061019c + CreateGeometry + CreateGeometry + true + 0 + true + 88d6deff-1f0b-49e8-894d-0a981b9177f1 + 1 + d60527f5-b5af-4ef6-8970-5f96fe412559 + + + + + + 1846 + 1555 + 102 + 20 + + + 1906.5 + 1565 + + + + + + + + Script output Model. + e36de40d-2fa1-4227-a9c2-9cdb47530003 + Model + Model + false + 0 + + + + + + 1978 + 1475 + 62 + 33 + + + 2009 + 1491.667 + + + + + + + + Script output Geometry. + 725ac33d-374c-4cc0-8f18-3e15182f1323 + Geometry + Geometry + false + 0 + + + + + + 1978 + 1508 + 62 + 33 + + + 2009 + 1525 + + + + + + + + Script output DebugInfo. + 2fb7adff-9b5d-45fd-bbca-5f311bb6fa46 + DebugInfo + DebugInfo + false + 0 + + + + + + 1978 + 1541 + 62 + 34 + + + 2009 + 1558.333 + + + + + + + + + + + + + + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a + Boolean Toggle + + + + + Boolean (true/false) toggle + 88d6deff-1f0b-49e8-894d-0a981b9177f1 + Boolean Toggle + Toggle + false + 0 + true + + + + + + 1697 + 1554 + 104 + 22 + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + CT: WriteBTLx + + + + + import Rhino +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning + +from compas_timber.fabrication import BTLx + + +class WriteBTLx(component): + def RunScript(self, model, path, write): + if not model: + self.AddRuntimeMessage(Warning, "Input parameter Model failed to collect data") + return + + btlx = BTLx(model) + btlx.history["FileName"] = Rhino.RhinoDoc.ActiveDoc.Name + + if write: + if not path: + self.AddRuntimeMessage(Warning, "Input parameter Path failed to collect data") + return + if path[-5:] != ".btlx": + path += ".btlx" + with open(path, "w") as f: + f.write(btlx.btlx_string()) + return btlx.btlx_string() + + GhPython provides a Python script component + + 152 + 152 + + + 938 + 975 + + true + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDAAACwwBP0AiyAAAARtJREFUSEu1lFERwjAQRFcCAiICCZWABCQgoQoylVAJlcBHBFRCJdRBmQ0Nc71L0o+GjwfM5u72klzAtm34J0ZojRFaY4TWGKEEgD6h12oYoQQAfkT0Wg04H0bnw00vaK4YbM6H+czkqgFZnA93HfALVAYAOnEvz13j70MNaUBW50OnixcMWCxpbxUzAYgnog0SsaMLBiROW8mADCcGuSOSBit3UTOYawY5aKRMXiWDMZMsE2vDMIu4PmeQfak8Z5mo1/cYHptsxBiYyxXJnAyZzE6HdAcAHgAWFdMlA45ncduF7iTcnZwqMsW8/RVXiwsTdsrpODM4vIPqX4Qmjt7XiAV5RCzO7zS2h4dqCrTGCK0xQmuM0BojtOYDGi90WTOaCA8AAAAASUVORK5CYII= + + false + 93817d7e-6043-4c58-8076-042c11e1fa95 + true + true + CT: WriteBTLx + WriteBTLx + + + + + + 2284 + 1651 + 104 + 64 + + + 2339 + 1683 + + + + + + 3 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + true + Model object. + 4de66b25-f265-4ab5-9f21-a98ac1f5a933 + Model + Model + true + 0 + true + e36de40d-2fa1-4227-a9c2-9cdb47530003 + 1 + 35915213-5534-4277-81b8-1bdc9e7383d2 + + + + + + 2286 + 1653 + 38 + 20 + + + 2306.5 + 1663 + + + + + + + + true + Script input Path. + 0b21d3af-2bd5-4b08-821e-59e6e2d20e69 + Path + Path + true + 0 + true + 52d39943-0ed8-46fe-b90e-5867d152bd71 + 1 + 35915213-5534-4277-81b8-1bdc9e7383d2 + + + + + + 2286 + 1673 + 38 + 20 + + + 2306.5 + 1683 + + + + + + + + true + Script input Write. + c7de61e4-ce28-45dc-9066-0f8e614137cd + Write + Write + true + 0 + true + d5a06678-cdf7-40ed-8120-797a2d71424d + 1 + d60527f5-b5af-4ef6-8970-5f96fe412559 + + + + + + 2286 + 1693 + 38 + 20 + + + 2306.5 + 1703 + + + + + + + + Script output BTLx. + 5946e9dc-f13c-4e3c-8873-1cc4030c843d + BTLx + BTLx + false + 0 + + + + + + 2354 + 1653 + 32 + 60 + + + 2370 + 1683 + + + + + + + + + + + + + + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel + + + + + A panel for custom notes and text values + 52d39943-0ed8-46fe-b90e-5867d152bd71 + Panel + + false + 0 + 0 + C:\Users\kapso\OneDrive\Desktop\BTLx\TButtJoint.btlx + + + + + + 1967 + 1617 + 258 + 116 + + 0 + 0 + 0 + + 1967.904 + 1617.126 + + + + + + + 255;255;250;90 + + true + true + true + false + false + true + + + + + + + + + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a + Boolean Toggle + + + + + Boolean (true/false) toggle + d5a06678-cdf7-40ed-8120-797a2d71424d + Boolean Toggle + Toggle + false + 0 + true + + + + + + 2121 + 1741 + 104 + 22 + + + + + + + + + + 2a5cfb31-028a-4b34-b4e1-9b20ae15312e + Cross Product + + + + + Compute vector cross product. + true + 8c89d0b6-be81-4671-8ea9-35eff688cefc + Cross Product + XProd + + + + + + 632 + 1438 + 66 + 64 + + + 664 + 1470 + + + + + + First vector + a1dec88f-fa57-42d9-848b-54a7703cf14d + Vector A + A + false + 22a7c548-2a3a-46f3-9aed-df6d798fd1fe + 1 + + + + + + 634 + 1440 + 15 + 20 + + + 643 + 1450 + + + + + + + + Second vector + e92373e4-0242-4076-8288-84d920d1a8db + Vector B + B + false + 0bc447d1-0b47-4d44-88e5-1c1a33db80fc + 1 + + + + + + 634 + 1460 + 15 + 20 + + + 643 + 1470 + + + + + + + + Unitize output + f80c9f76-42a2-4e06-bee0-b5f229d457da + Unitize + U + false + 0 + + + + + + 634 + 1480 + 15 + 20 + + + 643 + 1490 + + + + + + 1 + + + + + 1 + {0} + + + + + false + + + + + + + + + + + Cross product vector + 8384eec4-4bfc-4566-847f-bdd110cca2fa + Vector + V + false + 0 + + + + + + 679 + 1440 + 17 + 30 + + + 687.5 + 1455 + + + + + + + + Vector length + d85af46a-4140-44dc-ad79-8c4d6676cd48 + Length + L + false + 0 + + + + + + 679 + 1470 + 17 + 30 + + + 687.5 + 1485 + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + aa01afa4-173a-4459-9bf1-9dc2e745c93f + Number Slider + + false + 0 + + + + + + 1340 + 1436 + 184 + 20 + + + 1340.726 + 1436.466 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 20 + + + + + + + + + 2a3f7078-2e25-4dd4-96f7-0efb491bd61c + Vector Display + + + + + false + 0 + Preview vectors in the viewport + 0.1 + 15 + true + 08b55ce3-618a-4e25-8173-6488cf47d35b + Vector Display + VDis + + + + + 3 + false + false + + + + + + 255;255;0;0 + + + 255;255;0;0 + + 0 + 0113de22-d3ba-4eb3-adaa-506038766107 + + + + + + 255;255;165;0 + + + 255;255;165;0 + + 0.5 + cff317c6-83ff-42c6-9147-f440a2b1be97 + + + + + + 255;124;252;0 + + + 255;124;252;0 + + 1 + 89ecc415-fbe0-43d1-99f7-0e341f4caa6c + + + + + + + + 722 + 1423 + 61 + 44 + + + 769 + 1445 + + + + + + Anchor point for preview vector + eb44ba95-b3ee-4f85-80b4-0e6c59c56c20 + Anchor + A + true + 34c88e7e-45d4-4ff8-adfe-4ada2b4c2f45 + 1 + + + + + + 724 + 1425 + 30 + 20 + + + 748.5 + 1435 + + + + + + + + Vector to preview + 7188ccec-d71b-41dc-9e8b-fe742466db3a + x*100 + Vector + V + true + 8384eec4-4bfc-4566-847f-bdd110cca2fa + 1 + + + + + + 724 + 1445 + 30 + 20 + + + 748.5 + 1455 + + + + + + + + + + + + 6b7ba278-5c9d-42f1-a61d-6209cbd44907 + Curve Proximity + + + + + Find the pair of closest points between two curves. + true + 5d91225d-b2dc-4281-95d1-144b18e603c4 + Curve Proximity + CrvProx + + + + + + 631 + 1371 + 66 + 64 + + + 662 + 1403 + + + + + + First curve + 37fedea4-997f-42c0-9dd2-1d78203bad4f + Curve A + A + false + 22a7c548-2a3a-46f3-9aed-df6d798fd1fe + 1 + + + + + + 633 + 1373 + 14 + 30 + + + 641.5 + 1388 + + + + + + + + Second curve + fe20acfa-1112-4edf-80d9-67b518716863 + Curve B + B + false + 0bc447d1-0b47-4d44-88e5-1c1a33db80fc + 1 + + + + + + 633 + 1403 + 14 + 30 + + + 641.5 + 1418 + + + + + + + + Point on curve A closest to curve B + 34c88e7e-45d4-4ff8-adfe-4ada2b4c2f45 + Point A + A + false + 0 + + + + + + 677 + 1373 + 18 + 20 + + + 686 + 1383 + + + + + + + + Point on curve B closest to curve A + 7c0c8311-8374-409b-93e4-37e40bf928f7 + Point B + B + false + 0 + + + + + + 677 + 1393 + 18 + 20 + + + 686 + 1403 + + + + + + + + Smallest distance between two curves + acf2f4d0-c597-4176-a1b7-f5be911ca208 + Distance + D + false + 0 + + + + + + 677 + 1413 + 18 + 20 + + + 686 + 1423 + + + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + 00dffbd2-d634-4815-8e48-989dfd6df288 + Plane + Pln + false + bf27e312-db21-4a25-83f4-81fba65f4e87 + 1 + + + + + + 1998 + 1175 + 50 + 24 + + + 2023.607 + 1187.121 + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + 4d439952-5ee0-415f-bf9a-9558d86e4efd + Plane + Pln + false + 2ed0ebc1-6a52-4a3c-84b2-d3f4a4b66f37 + 1 + + + + + + 1987 + 1139 + 50 + 24 + + + 2012.421 + 1151.553 + + + + + + + + + + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep + + + + + Contains a collection of Breps (Boundary REPresentations) + true + a6425828-9538-4783-86e1-0c73c651f302 + Brep + Brep + false + e274454e-7ad5-42e7-9751-2366475812a4 + 1 + + + + + + 1981 + 1229 + 50 + 24 + + + 2006.616 + 1241.926 + + + + + + + + + + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep + + + + + Contains a collection of Breps (Boundary REPresentations) + true + b3588f0a-414e-4a3d-87e6-79cde3ae279b + Brep + Brep + false + 857693c0-ad1b-4e96-a368-7b18c111a11c + 1 + + + + + + 1989 + 1288 + 50 + 24 + + + 2014.739 + 1300.79 + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + 9f99b4bb-612a-492f-b8e1-a954c0e0a960 + Plane + Pln + false + 5daa01c6-d25f-4807-ad0c-40e0e1768710 + 1 + + + + + + 1989 + 1261 + 50 + 24 + + + 2014.707 + 1273.677 + + + + + + + + + + 11bbd48b-bb0a-4f1b-8167-fa297590390d + End Points + + + + + Extract the end points of a curve. + true + d785f7da-1ae4-4767-9398-648e133a5bd2 + End Points + End + + + + + + -129 + 1505 + 64 + 44 + + + -98 + 1527 + + + + + + Curve to evaluate + 70403e50-270d-44b1-a9d0-6f61a843054a + Curve + C + false + 2cbb77cb-271b-4c58-b3b5-67700d482c61 + 1 + + + + + + -127 + 1507 + 14 + 40 + + + -118.5 + 1527 + + + + + + + + Curve start point + bd842e1f-5ad6-4c4e-b40d-1faf08bfd10a + Start + S + false + 0 + + + + + + -83 + 1507 + 16 + 20 + + + -75 + 1517 + + + + + + + + Curve end point + 1aa64117-983f-4f8f-88a7-89988c38d0a5 + End + E + false + 0 + + + + + + -83 + 1527 + 16 + 20 + + + -75 + 1537 + + + + + + + + + + + + e9eb1dcf-92f6-4d4d-84ae-96222d60f56b + Move + + + + + Translate (move) an object along a vector. + true + 98e83dd4-f0d0-4701-ba42-e474164cfd82 + Move + Move + + + + + + -43 + 1552 + 83 + 44 + + + 5 + 1574 + + + + + + Base geometry + a2458f66-4a60-4e07-97e4-fa1e384391ff + Geometry + G + true + 1aa64117-983f-4f8f-88a7-89988c38d0a5 + 1 + + + + + + -41 + 1554 + 31 + 20 + + + -16 + 1564 + + + + + + + + Translation vector + fed7b565-45a1-4f02-b7e2-f6a966754fe6 + -x + Motion + T + false + 5c2a0f01-6a04-4bef-bd00-1e3da3fb5dc5 + 1 + + + + + + -41 + 1574 + 31 + 20 + + + -16 + 1584 + + + + + + 1 + + + + + 1 + {0} + + + + + + 0 + 0 + 10 + + + + + + + + + + + + Translated geometry + 73968214-4027-410c-a9b5-a33b92bba8d9 + Geometry + G + false + 0 + + + + + + 20 + 1554 + 18 + 20 + + + 29 + 1564 + + + + + + + + Transformation data + 042d236f-ef03-4870-acc9-d502f01eaf6d + Transform + X + false + 0 + + + + + + 20 + 1574 + 18 + 20 + + + 29 + 1584 + + + + + + + + + + + + 79f9fbb3-8f1d-4d9a-88a9-f7961b1012cd + Unit X + + + + + Unit vector parallel to the world {x} axis. + true + 8fa01d08-afaf-4258-bace-d765ebf9577f + Unit X + X + + + + + + -231 + 1550 + 79 + 28 + + + -186 + 1564 + + + + + + Unit multiplication + a7304964-d322-488c-865b-62f9088e559b + -x + Factor + F + false + d475e429-c48f-4bea-b061-06ab3815cfd3 + 1 + + + + + + -229 + 1552 + 28 + 24 + + + -205.5 + 1564 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + World {x} vector + cc80d251-10bb-4f29-8711-e24349bac9f2 + Unit vector + V + false + 0 + + + + + + -171 + 1552 + 17 + 24 + + + -162.5 + 1564 + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + d475e429-c48f-4bea-b061-06ab3815cfd3 + Number Slider + + false + 0 + + + + + + -400 + 1558 + 163 + 20 + + + -399.7307 + 1558.41 + + + + + + 3 + 1 + 1 + 4000 + 0 + 0 + 1000 + + + + + + + + + 4c4e56eb-2f04-43f9-95a3-cc46a14f495a + Line + + + + + Create a line between two points. + true + 702b1bbc-2b58-478b-b3ba-16a48af876bd + Line + Ln + + + + + + 65 + 1532 + 63 + 44 + + + 96 + 1554 + + + + + + Line start point + 6a018da5-25e2-4649-8e94-3e060a9f2195 + Start Point + A + false + bd842e1f-5ad6-4c4e-b40d-1faf08bfd10a + 1 + + + + + + 67 + 1534 + 14 + 20 + + + 75.5 + 1544 + + + + + + + + Line end point + 2d65d7c6-7c91-45db-b3ba-55405300a7c8 + End Point + B + false + 73968214-4027-410c-a9b5-a33b92bba8d9 + 1 + + + + + + 67 + 1554 + 14 + 20 + + + 75.5 + 1564 + + + + + + + + Line segment + 86caf817-445e-4796-b29c-e1d7203996ce + Line + L + false + 0 + + + + + + 111 + 1534 + 15 + 40 + + + 118.5 + 1554 + + + + + + + + + + + + 9103c240-a6a9-4223-9b42-dbd19bf38e2b + Unit Z + + + + + Unit vector parallel to the world {z} axis. + true + bbf2811a-72c7-4b27-aabe-22952d508aa7 + Unit Z + Z + + + + + + -229 + 1584 + 79 + 28 + + + -184 + 1598 + + + + + + Unit multiplication + 827ec58a-dd71-44e4-b991-26acf25b63f9 + -x + Factor + F + false + ed5575ff-1733-49b5-87a3-30e4e79a9149 + 1 + + + + + + -227 + 1586 + 28 + 24 + + + -203.5 + 1598 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + World {z} vector + b34169da-06f4-4b1b-a640-4619f7c456ef + Unit vector + V + false + 0 + + + + + + -169 + 1586 + 17 + 24 + + + -160.5 + 1598 + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + ed5575ff-1733-49b5-87a3-30e4e79a9149 + Number Slider + + false + 0 + + + + + + -402 + 1593 + 163 + 20 + + + -401.2582 + 1593.316 + + + + + + 3 + 1 + 1 + 2000 + 0 + 0 + 0 + + + + + + + + + a0d62394-a118-422d-abb3-6af115c75b25 + Addition + + + + + Mathematical addition + true + 60dc433a-3e4b-4822-9277-ffa1be8ca93b + Addition + A+B + + + + + + -127 + 1552 + 65 + 64 + + + -96 + 1584 + + + + + + 3 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + First item for addition + cc645842-011a-4863-b694-2f3420ff364e + A + A + true + cc80d251-10bb-4f29-8711-e24349bac9f2 + 1 + + + + + + -125 + 1554 + 14 + 20 + + + -116.5 + 1564 + + + + + + + + Second item for addition + 367929e0-5239-49bb-8eb9-620c98911008 + B + B + true + b34169da-06f4-4b1b-a640-4619f7c456ef + 1 + + + + + + -125 + 1574 + 14 + 20 + + + -116.5 + 1584 + + + + + + + + Third item for addition + f4ffb200-2275-4f34-8b77-096a6fa4eaa5 + C + C + true + 69d3e566-54c8-406e-b254-8c7c985b1f55 + 1 + + + + + + -125 + 1594 + 14 + 20 + + + -116.5 + 1604 + + + + + + + + Result of addition + 5c2a0f01-6a04-4bef-bd00-1e3da3fb5dc5 + Result + R + false + 0 + + + + + + -81 + 1554 + 17 + 60 + + + -72.5 + 1584 + + + + + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;255;255;255 + + A group of Grasshopper objects + 8fa01d08-afaf-4258-bace-d765ebf9577f + d475e429-c48f-4bea-b061-06ab3815cfd3 + bbf2811a-72c7-4b27-aabe-22952d508aa7 + ed5575ff-1733-49b5-87a3-30e4e79a9149 + 1bf6c7ca-f719-453d-ae24-8fb4402a0f6e + a733d08f-1f60-40f4-b07a-a99476c2a8ab + 6 + 2858ecb7-755a-4a0e-be46-21a44003594a + Group + + + + + + + + + + + d3d195ea-2d59-4ffa-90b1-8b7ff3369f69 + Unit Y + + + + + Unit vector parallel to the world {y} axis. + true + 1bf6c7ca-f719-453d-ae24-8fb4402a0f6e + Unit Y + Y + + + + + + -228 + 1618 + 79 + 28 + + + -183 + 1632 + + + + + + Unit multiplication + 5a12a759-a526-4b6d-9a80-940d6314b66f + -x + Factor + F + false + a733d08f-1f60-40f4-b07a-a99476c2a8ab + 1 + + + + + + -226 + 1620 + 28 + 24 + + + -202.5 + 1632 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + World {y} vector + 69d3e566-54c8-406e-b254-8c7c985b1f55 + Unit vector + V + false + 0 + + + + + + -168 + 1620 + 17 + 24 + + + -159.5 + 1632 + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + a733d08f-1f60-40f4-b07a-a99476c2a8ab + Number Slider + + false + 0 + + + + + + -396 + 1626 + 163 + 20 + + + -395.5972 + 1626.688 + + + + + + 3 + 1 + 1 + 4000 + 0 + 0 + 0 + + + + + + + + + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + Curve + + + + + Contains a collection of generic curves + true + 2cbb77cb-271b-4c58-b3b5-67700d482c61 + Curve + Crv + false + 0 + + + + + + -202 + 1515 + 50 + 24 + + + -176.9542 + 1527.093 + + + + + + 1 + + + + + 1 + {0} + + + + + -1 + + Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesbmekYmhgbmeoamBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS7oUCPJzMQAY/iJgKxEy/6pkYOqH++v2fiUEEyn525bqf+8vLgvufMgg0Myp98Juz+/YfoHwgVF4AZG5KV7LLreD7Dh/+/5cPK155QKIotjs+NcgBJu5Y1f/vevQGuDgTAwJ4XKouSRfsdQC5JL/F7Mz/eqYGmBw3w2ACAA== + + 00000000-0000-0000-0000-000000000000 + + + + + + + + + + + + + eeafc956-268e-461d-8e73-ee05c6f72c01 + Stream Filter + + + + + Filters a collection of input streams + true + db6d25a1-f31e-40f5-b194-fe0d4468cf6b + Stream Filter + Filter + + + + + + 244 + 1522 + 77 + 64 + + + 276 + 1554 + + + + + + 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 + cc69945a-bbbb-4131-905d-9d3c0fbd8438 + Gate + G + false + 1af3d673-3732-4271-8c59-52c07949abb2 + 1 + + + + + + 246 + 1524 + 15 + 20 + + + 255 + 1534 + + + + + + 1 + + + + + 1 + {0} + + + + + 0 + + + + + + + + + + + 2 + Input stream at index 0 + 8101c4eb-81c2-4bcb-a591-85ccea8356c5 + false + Stream 0 + 0 + true + 86caf817-445e-4796-b29c-e1d7203996ce + 1 + + + + + + 246 + 1544 + 15 + 20 + + + 255 + 1554 + + + + + + + + 2 + Input stream at index 1 + 2dc73a0a-c8da-43b5-ad87-6a8728ba0b8b + false + Stream 1 + 1 + true + 876f4851-1798-41c3-97d4-25f54e4be852 + 1 + + + + + + 246 + 1564 + 15 + 20 + + + 255 + 1574 + + + + + + + + 2 + Filtered stream + d0f79279-4a97-4aab-8e91-aeb5eceb2957 + false + Stream + S(1) + false + 0 + + + + + + 291 + 1524 + 28 + 60 + + + 305 + 1554 + + + + + + + + + + + + + + 22990b1f-9be6-477c-ad89-f775cd347105 + Flip Curve + + + + + Flip a curve using an optional guide curve. + true + e664175e-8617-41e1-988b-43fb771f53ba + Flip Curve + Flip + + + + + + 156 + 1562 + 66 + 44 + + + 188 + 1584 + + + + + + Curve to flip + 603ef112-78dd-43bd-9877-ef4b969b5ac9 + Curve + C + false + 86caf817-445e-4796-b29c-e1d7203996ce + 1 + + + + + + 158 + 1564 + 15 + 20 + + + 167 + 1574 + + + + + + + + Optional guide curve + b0daa22c-4428-4040-a3ca-800c9fe78aa2 + Guide + G + true + 0 + + + + + + 158 + 1584 + 15 + 20 + + + 167 + 1594 + + + + + + + + Flipped curve + 876f4851-1798-41c3-97d4-25f54e4be852 + Curve + C + false + 0 + + + + + + 203 + 1564 + 17 + 20 + + + 211.5 + 1574 + + + + + + + + Flip action + bb44d615-60b7-4dd3-a0b5-fcdc3f854a28 + Flag + F + false + 0 + + + + + + 203 + 1584 + 17 + 20 + + + 211.5 + 1594 + + + + + + + + + + + + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a + Boolean Toggle + + + + + Boolean (true/false) toggle + 1af3d673-3732-4271-8c59-52c07949abb2 + Boolean Toggle + Toggle + false + 0 + true + + + + + + -230 + 1442 + 104 + 22 + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;170;135;255 + + A group of Grasshopper objects + 1af3d673-3732-4271-8c59-52c07949abb2 + 1 + 7c27fde1-5d5b-46e1-bf58-79fb5f2a792e + Group + FLIP + + + + + + + + + + 8529dbdf-9b6f-42e9-8e1f-c7a2bde56a70 + Line + + + + + Contains a collection of line segments + true + 0bc447d1-0b47-4d44-88e5-1c1a33db80fc + Line + Line + false + 11ee2cb8-fea9-407e-8b4b-cbcf1fe58761 + 1 + + + + + + 472 + 1545 + 50 + 24 + + + 497.7238 + 1557.053 + + + + + + + + + + eeafc956-268e-461d-8e73-ee05c6f72c01 + Stream Filter + + + + + Filters a collection of input streams + true + 9d82d577-6636-40a6-89a2-6f58d4a8a308 + Stream Filter + Filter + + + + + + 244 + 1287 + 77 + 64 + + + 276 + 1319 + + + + + + 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 + df6a3ca1-e96c-4208-ad74-c94740172476 + Gate + G + false + 13ac4c40-1353-44eb-ba9c-dfde3983e024 + 1 + + + + + + 246 + 1289 + 15 + 20 + + + 255 + 1299 + + + + + + 1 + + + + + 1 + {0} + + + + + 0 + + + + + + + + + + + 2 + Input stream at index 0 + 43b0fae7-669c-4839-b79c-f8e495099b62 + false + Stream 0 + 0 + true + 18d2fa4a-de1b-4cfd-bf80-4b8d5fa0182f + 1 + + + + + + 246 + 1309 + 15 + 20 + + + 255 + 1319 + + + + + + + + 2 + Input stream at index 1 + ad90ebc4-1f44-4b98-b4b3-0977a5671062 + false + Stream 1 + 1 + true + cb376a9b-3404-4cd3-8d2d-4d51d2a446ae + 1 + + + + + + 246 + 1329 + 15 + 20 + + + 255 + 1339 + + + + + + + + 2 + Filtered stream + af62623b-9273-42c0-b386-24cd70a88cce + false + Stream + S(1) + false + 0 + + + + + + 291 + 1289 + 28 + 60 + + + 305 + 1319 + + + + + + + + + + + + + + 22990b1f-9be6-477c-ad89-f775cd347105 + Flip Curve + + + + + Flip a curve using an optional guide curve. + true + 7f188938-0adc-40a1-aaa1-c82bf68c0621 + Flip Curve + Flip + + + + + + 166 + 1327 + 66 + 44 + + + 198 + 1349 + + + + + + Curve to flip + 03f77cf3-47f0-4a80-8365-cc8ae15440fa + Curve + C + false + 18d2fa4a-de1b-4cfd-bf80-4b8d5fa0182f + 1 + + + + + + 168 + 1329 + 15 + 20 + + + 177 + 1339 + + + + + + + + Optional guide curve + e7359af5-c6bc-4783-824b-3a94bae25799 + Guide + G + true + 0 + + + + + + 168 + 1349 + 15 + 20 + + + 177 + 1359 + + + + + + + + Flipped curve + cb376a9b-3404-4cd3-8d2d-4d51d2a446ae + Curve + C + false + 0 + + + + + + 213 + 1329 + 17 + 20 + + + 221.5 + 1339 + + + + + + + + Flip action + 761c1b3d-b418-4360-9bf9-d5af6a50cf40 + Flag + F + false + 0 + + + + + + 213 + 1349 + 17 + 20 + + + 221.5 + 1359 + + + + + + + + + + + + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a + Boolean Toggle + + + + + Boolean (true/false) toggle + 13ac4c40-1353-44eb-ba9c-dfde3983e024 + Boolean Toggle + Toggle + false + 0 + true + + + + + + 30 + 1236 + 104 + 22 + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;170;135;255 + + A group of Grasshopper objects + 13ac4c40-1353-44eb-ba9c-dfde3983e024 + 1 + 5f1aeb7c-013f-4d68-9e03-9abc339aaf8d + Group + FLIP + + + + + + + + + + 8529dbdf-9b6f-42e9-8e1f-c7a2bde56a70 + Line + + + + + Contains a collection of line segments + true + 22a7c548-2a3a-46f3-9aed-df6d798fd1fe + Line + Line + false + af62623b-9273-42c0-b386-24cd70a88cce + 1 + + + + + + 478 + 1307 + 50 + 24 + + + 503.1081 + 1319.313 + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;170;135;255 + + A group of Grasshopper objects + aa01afa4-173a-4459-9bf1-9dc2e745c93f + 1 + 6fd57bc8-9d7e-48e5-9e88-112b89d3d3ff + Group + + + + + + + + + + + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel + + + + + A panel for custom notes and text values + fcab9592-e2af-4a82-ba96-1a6ae865e464 + Panel + + false + 0 + 457734f7-3478-4434-a17d-636820c0980c + 1 + Double click to edit panel content… + + + + + + 1742 + 1079 + 201 + 53 + + 0 + 0 + 0 + + 1742.733 + 1079.09 + + + + + + + 255;255;250;90 + + true + true + true + false + false + true + + + + + + + + + 59e0b89a-e487-49f8-bab8-b5bab16be14c + Panel + + + + + A panel for custom notes and text values + dc5d40be-772e-4800-9d83-6ba9f705272e + Panel + + false + 1 + 5946e9dc-f13c-4e3c-8873-1cc4030c843d + 1 + Double click to edit panel content… + + + + + + 2465 + 1467 + 941 + 630 + + 0 + 0 + 0 + + 2465.919 + 1467.103 + + + + + + + 255;255;250;90 + + true + true + true + false + false + true + + + + + + + + + 59daf374-bc21-4a5e-8282-5504fb7ae9ae + List Item + + + + + 0 + Retrieve a specific item from a list. + true + 6758fb57-bfa5-452b-afb1-29c442b80cae + List Item + Item + + + + + + 386 + 1770 + 64 + 64 + + + 420 + 1802 + + + + + + 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 + d59fad60-6959-4751-b1d2-d1f1508e8b63 + List + L + false + 65a4e860-1e2f-40f1-811c-db669445260d + 1 + + + + + + 388 + 1772 + 17 + 20 + + + 398 + 1782 + + + + + + + + Item index + fb30abd5-e590-4c92-8c2c-8ddd83edbd3c + Index + i + false + e23ba652-99eb-4b68-8aed-303901e36c47 + 1 + + + + + + 388 + 1792 + 17 + 20 + + + 398 + 1802 + + + + + + 1 + + + + + 1 + {0} + + + + + 0 + + + + + + + + + + + Wrap index to list bounds + da645836-39aa-4154-84c1-cba3540cdb57 + Wrap + W + false + 0 + + + + + + 388 + 1812 + 17 + 20 + + + 398 + 1822 + + + + + + 1 + + + + + 1 + {0} + + + + + true + + + + + + + + + + + Item at {i'} + d5fd1fb2-6a4b-451c-9dfc-5936004b2a19 + false + Item + i + false + 0 + + + + + + 435 + 1772 + 13 + 60 + + + 441.5 + 1802 + + + + + + + + + + + + + + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + Curve + + + + + Contains a collection of generic curves + true + 65a4e860-1e2f-40f1-811c-db669445260d + Curve + Crv + false + 0 + + + + + + 245 + 1770 + 50 + 24 + + + 270.2979 + 1782.105 + + + + + + 1 + + + + + 4 + {0;0} + + + + + -1 + + Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesbmekYmhgbmeoamBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS7oUCPJzMQAY/iJgKxEy/6pkYOqH++v2fiUEEyn525bqf+8vLgvufMgg0Myp98Juz+/YfoHwgVF4AZG5KV7LLreD7Dh/+/5cPK155QKIotjs+Ncjh9P7Z4r4RDxyWMVQaXru16UDzAbkVds4zHZgYEMDjUnVJumCvA8glmRHv9/6vZ2qAyXEzDCYAAA== + + 00000000-0000-0000-0000-000000000000 + + + + + -1 + + Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesbmekYmhgbmeoamBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS7oUCPJzMQAY/iJgKxEy/6pkYOqH++v2fiUEEyn525bqf+8vLgvufMgg0Myp98Juz+/YfoHwgVF4AZO6HeysX9uU8cGg93/ps/YwFB6ByDAm/T9hlbLnv8On/f/mw4pUHJIpiu+NTgxxAethULxQvqpkJU9sAcskak6dX/tczNcD0czMMJgAA + + 00000000-0000-0000-0000-000000000000 + + + + + -1 + + Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesbmekYmhgbmeoamBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS7oUCPJzMQAY/iJgKxEy/6pkYOqH++v2fiUEEyn525bqf+8vLgvufMgg0Myp98Juz+/YfoHwgVF4AZK7ijvjIvjUPHD7+/y8fVrzyQIGZ50mu61Mc2HTP3rgfjBCXKIrtjk8NcgDp8YuffGHLhskHoOY0gFwSWJWm8r+eqQEqxsDNMJgAAA== + + 00000000-0000-0000-0000-000000000000 + + + + + -1 + + Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesbmekYmhgbmeoamBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS7oUCPJzMQAY/iJgKxEy/6pkYOqH++v2fiUEEyn525bqf+8vLgvufMgg0Myp98Juz+/YfoHwgVF4AZC6TTnuX7OEHDh///5cPK155QKIotjs+NcihSmdq9CaXhw5RNoJsPupLDnyq/pDxhWXBASYGBNjZ33f2nvFiB5BLKs/eTv5fz9QAk+NmGEwAAA== + + 00000000-0000-0000-0000-000000000000 + + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + e23ba652-99eb-4b68-8aed-303901e36c47 + Number Slider + + false + 0 + + + + + + 195 + 1832 + 160 + 20 + + + 195.4534 + 1832.229 + + + + + + 3 + 1 + 1 + 3 + 0 + 0 + 3 + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + GhPython Script + + + + + from compas.geometry import Line, Plane, Vector, Point +from compas_timber.elements import Beam +from compas_timber._fabrication import JackRafterCut, JackRafterCutParams +from compas.scene import Scene, SceneObject +from compas_rhino.conversions import brep_to_rhino, plane_to_rhino + +centerline = Line(Point(x=270.0, y=270.0, z=590.0), Point(x=1220.0, y=680.0, z=590.0)) +cross_section = (60, 120) +beam = Beam.from_centerline(centerline, cross_section[0], cross_section[1]) + +# cut the start of the beam +normal = Vector(x=-0.996194698092, y=-0.0, z=-0.0871557427477) +plane = Plane(Point(x=460.346635340, y=445.167151490, z=473.942755901), normal) +instance = JackRafterCut.from_plane_and_beam(plane, beam, ref_side_index = 0) + +beam_geo = brep_to_rhino(beam.compute_geometry()) +plane_geo = plane_to_rhino(plane) +effective_plane = plane_to_rhino(instance.plane_from_params_and_beam(beam)) + +params = JackRafterCutParams(instance) +print(params.as_dict()) +print(centerline.length) + GhPython provides a Python script component + + 332 + 180 + + + 1339 + 874 + + true + true + false + false + 396034ae-3e76-4e48-90f5-696776da7e33 + false + true + GhPython Script + Python + + + + + + 108 + 511 + 128 + 124 + + + 137 + 573 + + + + + + 2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 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 + + + + + true + Script variable Python + 1f9df9f8-daeb-42f9-83e1-371ac54fcc49 + x + x + true + 0 + true + 0 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 110 + 513 + 12 + 60 + + + 117.5 + 543 + + + + + + + + true + Script input y. + ceb6a202-7655-4be9-bf4b-3520bc4f863d + y + y + true + 0 + true + 0 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 110 + 573 + 12 + 60 + + + 117.5 + 603 + + + + + + + + The execution information, as output and error streams + 141f06bc-8242-4c91-9cb6-fbb9f9b31a8f + out + out + false + 0 + + + + + + 152 + 513 + 82 + 20 + + + 193 + 523 + + + + + + + + Script output beam. + 145cad7b-4504-469e-a3dc-217e6685571a + beam + beam + false + 0 + + + + + + 152 + 533 + 82 + 20 + + + 193 + 543 + + + + + + + + Script output beam_geo. + 76eade1f-e1f8-4e17-8a12-abe10ba07b02 + beam_geo + beam_geo + false + 0 + + + + + + 152 + 553 + 82 + 20 + + + 193 + 563 + + + + + + + + Script output plane_geo. + 4a992867-df7d-43b8-a147-78af83357f87 + plane_geo + plane_geo + false + 0 + + + + + + 152 + 573 + 82 + 20 + + + 193 + 583 + + + + + + + + Script output effective_plane. + f0bb5cab-87bf-44e9-86bc-dcbb216e11ea + effective_plane + effective_plane + false + 0 + + + + + + 152 + 593 + 82 + 20 + + + 193 + 603 + + + + + + + + Script output centerline. + 63f119c4-bdf9-493f-8439-e08299b6407c + centerline + centerline + false + 0 + + + + + + 152 + 613 + 82 + 20 + + + 193 + 623 + + + + + + + + + + + + + + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep + + + + + Contains a collection of Breps (Boundary REPresentations) + true + 8223b16e-766a-4b72-9d1a-8fb8729b5e30 + Brep + Brep + false + 76eade1f-e1f8-4e17-8a12-abe10ba07b02 + 1 + + + + + + 421 + 727 + 50 + 24 + + + 446.2088 + 739.606 + + + + + + + + + + 439a55a5-2f9e-4f66-9de2-32f24fec2ef5 + Plane Surface + + + + + Create a plane surface + true + 3a33f254-d8e1-4caf-b1ca-12eb3546c7c6 + Plane Surface + PlaneSrf + + + + + + 379 + 494 + 64 + 64 + + + 410 + 526 + + + + + + Surface base plane + dcc16ef7-f494-4461-a6f4-695c12e7fb79 + Plane + P + false + f0bb5cab-87bf-44e9-86bc-dcbb216e11ea + 1 + + + + + + 381 + 496 + 14 + 20 + + + 389.5 + 506 + + + + + + 1 + + + + + 1 + {0} + + + + + + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 0 + + + + + + + + + + + + Dimensions in X direction + 73bce102-4815-413a-bbe4-0f6ff3396161 + X Size + X + false + 0 + + + + + + 381 + 516 + 14 + 20 + + + 389.5 + 526 + + + + + + 1 + + + + + 1 + {0} + + + + + + -10 + 10 + + + + + + + + + + + + Dimensions in Y direction + e87b4ed0-673f-428f-875f-e660dc9463f8 + Y Size + Y + false + 0 + + + + + + 381 + 536 + 14 + 20 + + + 389.5 + 546 + + + + + + 1 + + + + + 1 + {0} + + + + + + -10 + 10 + + + + + + + + + + + + Resulting plane surface + 27b3c795-713e-4696-8198-6aa099881517 + Plane + P + false + 0 + + + + + + 425 + 496 + 16 + 60 + + + 433 + 526 + + + + + + + + + + + + 75eec078-a905-47a1-b0d2-0934182b1e3d + Plane Origin + + + + + Change the origin point of a plane + true + 14d73357-a3db-43f9-b9f1-5a7e03d07931 + Plane Origin + Pl Origin + + + + + + 423 + 580 + 68 + 44 + + + 455 + 602 + + + + + + Base plane + 2b165994-e2b6-4270-a4c0-16201f3f097b + Base + B + false + 31719b32-f767-421c-b6dd-31299a177e0c + 1 + + + + + + 425 + 582 + 15 + 20 + + + 434 + 592 + + + + + + + + New origin point of plane + f6084a8b-5068-4842-9bf9-48fff940f3f6 + Origin + O + false + 53c5d372-4bbd-4caa-81c7-e73e707d2b36 + 1 + + + + + + 425 + 602 + 15 + 20 + + + 434 + 612 + + + + + + + + Plane definition + 0b5e337e-6ab5-4409-b9c8-6b10e7706a1a + Plane + Pl + false + 0 + + + + + + 470 + 582 + 19 + 40 + + + 479.5 + 602 + + + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + 53c5d372-4bbd-4caa-81c7-e73e707d2b36 + Plane + Pln + false + f0bb5cab-87bf-44e9-86bc-dcbb216e11ea + 1 + + + + + + 303 + 607 + 50 + 24 + + + 328.3172 + 619.2989 + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + 31719b32-f767-421c-b6dd-31299a177e0c + Plane + Pln + false + 4a992867-df7d-43b8-a147-78af83357f87 + 1 + + + + + + 301 + 572 + 50 + 24 + + + 326.9252 + 584.1504 + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + GhPython Script + + + + + from compas.geometry import Line, Plane, Vector, Point +from compas_timber.elements import Beam +from compas_timber._fabrication import JackRafterCut, JackRafterCutParams +from compas.scene import Scene, SceneObject +from compas_rhino.conversions import brep_to_rhino, plane_to_rhino +from compas_rhino import unload_modules + +unload_modules('compas_timber') + +centerline = Line(Point(x=270.0, y=270.0, z=590.0), Point(x=1220.0, y=680.0, z=590.0)) +cross_section = (60, 120) +beam = Beam.from_centerline(centerline, cross_section[0], cross_section[1]) + +# cut the end of the beam +normal = Vector(x=-0.996194698092, y=-0.0, z=-0.0871557427477) * -1.0 +plane = Plane(Point(x=460.346635340, y=445.167151490, z=473.942755901), normal) +instance = JackRafterCut.from_plane_and_beam(plane, beam) + +beam_geo = brep_to_rhino(beam.compute_geometry()) +plane_geo = plane_to_rhino(plane) +effective_plane = plane_to_rhino(instance.plane_from_params_and_beam(beam)) + +params = JackRafterCutParams(instance) +print(params.as_dict()) +print(centerline.length) + GhPython provides a Python script component + + 332 + 180 + + + 1754 + 874 + + true + true + false + false + 402482d0-c6fd-4f93-bdbc-53a84b48429e + false + true + GhPython Script + Python + + + + + + 113 + 203 + 128 + 124 + + + 142 + 265 + + + + + + 2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 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 + + + + + true + Script variable Python + ea8d2d0d-ffda-4ac1-b623-ee1e6c690ab5 + x + x + true + 0 + true + 0 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 115 + 205 + 12 + 60 + + + 122.5 + 235 + + + + + + + + true + Script input y. + 247b07da-edc9-4524-a26b-d09fa901f787 + y + y + true + 0 + true + 0 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 115 + 265 + 12 + 60 + + + 122.5 + 295 + + + + + + + + The execution information, as output and error streams + c100d79b-36ed-4030-b49a-62ed31700446 + out + out + false + 0 + + + + + + 157 + 205 + 82 + 20 + + + 198 + 215 + + + + + + + + Script output beam. + bd27d88c-317b-4f3b-8a6f-cb07235a5294 + beam + beam + false + 0 + + + + + + 157 + 225 + 82 + 20 + + + 198 + 235 + + + + + + + + Script output beam_geo. + 1e704349-7141-4809-b27a-136d687976c9 + beam_geo + beam_geo + false + 0 + + + + + + 157 + 245 + 82 + 20 + + + 198 + 255 + + + + + + + + Script output plane_geo. + 6ec5a5cf-4370-4b4b-a834-8355f200001d + plane_geo + plane_geo + false + 0 + + + + + + 157 + 265 + 82 + 20 + + + 198 + 275 + + + + + + + + Script output effective_plane. + 818d5658-15cd-4b2f-9914-36bdad5ac468 + effective_plane + effective_plane + false + 0 + + + + + + 157 + 285 + 82 + 20 + + + 198 + 295 + + + + + + + + Script output centerline. + 38caa466-2565-4d39-92db-9f048224a6cf + centerline + centerline + false + 0 + + + + + + 157 + 305 + 82 + 20 + + + 198 + 315 + + + + + + + + + + + + + + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep + + + + + Contains a collection of Breps (Boundary REPresentations) + true + 43f8eb2f-323f-4139-aa8d-d6bfe960cd52 + Brep + Brep + false + 1e704349-7141-4809-b27a-136d687976c9 + 1 + + + + + + 431 + 415 + 50 + 24 + + + 456.555 + 427.7627 + + + + + + + + + + 439a55a5-2f9e-4f66-9de2-32f24fec2ef5 + Plane Surface + + + + + Create a plane surface + true + 89082f8d-7656-4fb6-b348-0d927b2649b7 + Plane Surface + PlaneSrf + + + + + + 388 + 181 + 64 + 64 + + + 419 + 213 + + + + + + Surface base plane + 3e791009-e91f-4f92-abfe-584fa83a6404 + Plane + P + false + 818d5658-15cd-4b2f-9914-36bdad5ac468 + 1 + + + + + + 390 + 183 + 14 + 20 + + + 398.5 + 193 + + + + + + 1 + + + + + 1 + {0} + + + + + + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 0 + + + + + + + + + + + + Dimensions in X direction + f0b18f6f-9d53-401d-937f-964c25e28fb3 + X Size + X + false + 0 + + + + + + 390 + 203 + 14 + 20 + + + 398.5 + 213 + + + + + + 1 + + + + + 1 + {0} + + + + + + -10 + 10 + + + + + + + + + + + + Dimensions in Y direction + a33ad62e-5734-4be8-9f61-5e3c126cb2f3 + Y Size + Y + false + 0 + + + + + + 390 + 223 + 14 + 20 + + + 398.5 + 233 + + + + + + 1 + + + + + 1 + {0} + + + + + + -10 + 10 + + + + + + + + + + + + Resulting plane surface + cf2e8590-6cf8-44a5-b0f5-6042a0e8d9a2 + Plane + P + false + 0 + + + + + + 434 + 183 + 16 + 60 + + + 442 + 213 + + + + + + + + + + + + 75eec078-a905-47a1-b0d2-0934182b1e3d + Plane Origin + + + + + Change the origin point of a plane + true + c910a426-fe39-49f4-bea5-e1758e95c6e7 + Plane Origin + Pl Origin + + + + + + 432 + 267 + 68 + 44 + + + 464 + 289 + + + + + + Base plane + d66867b8-bf29-4872-bce1-af56bb6d3f48 + Base + B + false + e1b388f1-a19b-4f43-9a94-4e186be610e4 + 1 + + + + + + 434 + 269 + 15 + 20 + + + 443 + 279 + + + + + + + + New origin point of plane + 0b687328-839e-4777-8935-5a12e96ba70a + Origin + O + false + 6ec314ef-fad6-4428-9aa9-a6fb595fed58 + 1 + + + + + + 434 + 289 + 15 + 20 + + + 443 + 299 + + + + + + + + Plane definition + c85ef330-28fe-4365-9455-37d100bc359f + Plane + Pl + false + 0 + + + + + + 479 + 269 + 19 + 40 + + + 488.5 + 289 + + + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + 6ec314ef-fad6-4428-9aa9-a6fb595fed58 + Plane + Pln + false + 818d5658-15cd-4b2f-9914-36bdad5ac468 + 1 + + + + + + 313 + 295 + 50 + 24 + + + 338.6637 + 307.4556 + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + e1b388f1-a19b-4f43-9a94-4e186be610e4 + Plane + Pln + false + 6ec5a5cf-4370-4b4b-a834-8355f200001d + 1 + + + + + + 312 + 260 + 50 + 24 + + + 337.2716 + 272.3071 + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 3 + + 150;255;135;135 + + A group of Grasshopper objects + 65a4e860-1e2f-40f1-811c-db669445260d + 1 + 12377c43-e633-4311-af90-b012405de6f3 + Group + UNITTEST LINES + + + + + + + + + + c552a431-af5b-46a9-a8a4-0fcbc27ef596 + Group + + + + + 1 + + 150;170;135;255 + + A group of Grasshopper objects + 396034ae-3e76-4e48-90f5-696776da7e33 + 8223b16e-766a-4b72-9d1a-8fb8729b5e30 + 3a33f254-d8e1-4caf-b1ca-12eb3546c7c6 + 14d73357-a3db-43f9-b9f1-5a7e03d07931 + 53c5d372-4bbd-4caa-81c7-e73e707d2b36 + 31719b32-f767-421c-b6dd-31299a177e0c + 402482d0-c6fd-4f93-bdbc-53a84b48429e + 43f8eb2f-323f-4139-aa8d-d6bfe960cd52 + 89082f8d-7656-4fb6-b348-0d927b2649b7 + c910a426-fe39-49f4-bea5-e1758e95c6e7 + 6ec314ef-fad6-4428-9aa9-a6fb595fed58 + e1b388f1-a19b-4f43-9a94-4e186be610e4 + 12 + 9a873447-cf41-4dfc-a683-20af98a54e52 + Group + JackRafterCut Unittest + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + CT: T Topological Joint Rules + + + + + import inspect + +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning + +from compas_timber.connections import Joint +from compas_timber.connections import JointTopology +from compas_timber.connections import TButtJoint +from compas_timber.design import TopologyRule +from compas_timber.ghpython.ghcomponent_helpers import get_leaf_subclasses +from compas_timber.ghpython.ghcomponent_helpers import manage_dynamic_params +from compas_timber.ghpython.ghcomponent_helpers import rename_gh_output + + +class T_TopologyJointRule(component): + def __init__(self): + super(T_TopologyJointRule, self).__init__() + self.classes = {} + for cls in get_leaf_subclasses(Joint): + if cls.SUPPORTED_TOPOLOGY == JointTopology.TOPO_T: + self.classes[cls.__name__] = cls + if ghenv.Component.Params.Output[0].NickName == "Rule": + self.joint_type = TButtJoint + self.clicked = False + else: + self.joint_type = self.classes.get(ghenv.Component.Params.Output[0].NickName, None) + self.clicked = True + + def RunScript(self, *args): + if not self.clicked: + ghenv.Component.Message = "Default: TButtJoint" + self.AddRuntimeMessage(Warning, "TButtJoint is default, change in context menu (right click)") + return TopologyRule(JointTopology.TOPO_T, TButtJoint) + else: + ghenv.Component.Message = self.joint_type.__name__ + kwargs = {} + for i, val in enumerate(args): + if val is not None: + kwargs[self.arg_names()[i]] = val + if self.joint_type.SUPPORTED_TOPOLOGY != JointTopology.TOPO_T: + self.AddRuntimeMessage(Warning, "Joint type does not match topology. Joint may not be generated.") + return TopologyRule(JointTopology.TOPO_T, self.joint_type, **kwargs) + + def arg_names(self): + names = inspect.getargspec(self.joint_type.__init__)[0][3:] + return [name for name in names if (name != "key") and (name != "frame")] + + def AppendAdditionalMenuItems(self, menu): + for name in self.classes.keys(): + item = menu.Items.Add(name, None, self.on_item_click) + if self.joint_type and name == self.joint_type.__name__: + item.Checked = True + + def on_item_click(self, sender, event_info): + self.clicked = True + self.joint_type = self.classes[str(sender)] + rename_gh_output(self.joint_type.__name__, 0, ghenv) + manage_dynamic_params(self.arg_names(), ghenv, rename_count=0, permanent_param_count=0) + ghenv.Component.ExpireSolution(True) + + GhPython provides a Python script component + true + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDAAACwwBP0AiyAAAANVJREFUSEvtk70NwjAQhTNC6CP5JC/AKIzACHS0TGAxQmqqjJDCJQUjMAIbGJ3kk8yzsePIFEgpviL+eZ/0zumcc90viRYYrWjWipznivs1hKGkFfVa0SUIF054cSkSfkiEpnhqRXsMySECvohh35gwJIcIws6Z0dfFtT1gb8aQHCKYIKSXA1xJCwEOtrmAKwlD+JvraVYRziDHqiEvfaavVc/US7jrVCXMUc4Nxu4GY12BWyQIgZl8/MVNBDUMxt596Bn3mGihlk1QZBMU+X9BiWihNW9f8MWtv55wzQAAAABJRU5ErkJggg== + + false + 7934091a-9835-424d-9d20-74a1f5070b9a + true + true + CT: T Topological Joint Rules + T_Topo_Joint + + + + + + 1620 + 1479 + 151 + 28 + + + 1695 + 1493 + + + + + + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + mill_depth + 24baab98-afb4-4250-90ab-3223abbc9a79 + mill_depth + mill_depth + true + aa01afa4-173a-4459-9bf1-9dc2e745c93f + 1 + + + + + + 1622 + 1481 + 58 + 24 + + + 1652.5 + 1493 + + + + + + + + Script output TButtJoint. + f4bbf57e-0b0b-4949-958c-0cd0f6560057 + TButtJoint + TButtJoint + false + 0 + + + + + + 1710 + 1481 + 59 + 24 + + + 1739.5 + 1493 + + + + + + + + + + + + + + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + Curve + + + + + Contains a collection of generic curves + true + c752484d-c8b1-4dfe-b95c-052b1b9086ea + Curve + Crv + false + 0 + + + + + + 252 + 1891 + 50 + 24 + + + 277.9901 + 1903.796 + + + + + + 1 + + + + + 1 + {0} + + + + + -1 + + Y2BkYGD4DwQgGgR4mIBEeFBGZl6+c35ubn6ejkJYalFxZn6erbmesbmekYmhgbmeoamBgaGOgnNpTklpUaptXmppSVFijo5CQGlSTmayd2plSH52ap6tqamRkYVhqqV5srmpqamxASvIFhmw4Xruqfm5qSVFlXoB+TmVOZl5qc6lRWWpLEAF7GUQC7kSi5IzMstSjVNyOfMLUvPySouSillSEksSQYo4ODiYQG4VUGdgMALS7oUCPJzMQAY/iJgKxEy/6pkYOqH++v2fiUEEyn525bqf+8vLgvufMgg0Myp98Juz+/YfoHwgVF4AZO5Snxl77xs8cLi0+An/ha7NB55+jbgjuX+Ww/HrLxuFGR86fPr/Xz6seOWBI69XzPm3x8EBpGfS3F93NjIsOwA1pwHkkq0P80X/1zM1QMUYuBkGEwAA + + 00000000-0000-0000-0000-000000000000 + + + + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + CT: L Topological Joint Rules + + + + + import inspect + +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning + +from compas_timber.connections import Joint +from compas_timber.connections import JointTopology +from compas_timber.connections import LMiterJoint +from compas_timber.design import TopologyRule +from compas_timber.ghpython.ghcomponent_helpers import get_leaf_subclasses +from compas_timber.ghpython.ghcomponent_helpers import manage_dynamic_params +from compas_timber.ghpython.ghcomponent_helpers import rename_gh_output + + +class L_TopologyJointRule(component): + def __init__(self): + super(L_TopologyJointRule, self).__init__() + self.classes = {} + for cls in get_leaf_subclasses(Joint): + if cls.SUPPORTED_TOPOLOGY == JointTopology.TOPO_L: + self.classes[cls.__name__] = cls + if ghenv.Component.Params.Output[0].NickName == "Rule": + self.joint_type = LMiterJoint + self.clicked = False + else: + self.joint_type = self.classes.get(ghenv.Component.Params.Output[0].NickName, None) + self.clicked = True + + def RunScript(self, *args): + if not self.clicked: + ghenv.Component.Message = "Default: LMiterJoint" + self.AddRuntimeMessage(Warning, "LMiterJoint is default, change in context menu (right click)") + return TopologyRule(JointTopology.TOPO_L, LMiterJoint) + else: + ghenv.Component.Message = self.joint_type.__name__ + kwargs = {} + for i, val in enumerate(args): + if val is not None: + kwargs[self.arg_names()[i]] = val + if self.joint_type.SUPPORTED_TOPOLOGY != JointTopology.TOPO_L: + self.AddRuntimeMessage(Warning, "Joint type does not match topology. Joint may not be generated.") + return TopologyRule(JointTopology.TOPO_L, self.joint_type, **kwargs) + + def arg_names(self): + names = inspect.getargspec(self.joint_type.__init__)[0][3:] + return [name for name in names if (name != "key") and (name != "frame")] + + def AppendAdditionalMenuItems(self, menu): + for name in self.classes.keys(): + item = menu.Items.Add(name, None, self.on_item_click) + if self.joint_type and name == self.joint_type.__name__: + item.Checked = True + + def on_item_click(self, sender, event_info): + self.clicked = True + self.joint_type = self.classes[str(sender)] + rename_gh_output(self.joint_type.__name__, 0, ghenv) + manage_dynamic_params(self.arg_names(), ghenv, rename_count=0, permanent_param_count=0) + ghenv.Component.ExpireSolution(True) + + GhPython provides a Python script component + true + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDAAACwwBP0AiyAAAANBJREFUSEvtk70NwjAQhTNCBrDkk7wAo9DQMwIjMIHFCIyQEVK4TMEIjMAGRif5JOvlcEwwiCLFV8Q/75PeOV2MsfsmswXGWRqdpZi44P475KHkLPXO0jkLF054sRYJ3yuhGndnaYchJUTAFzHsFQOGlBBB3jlzTXVxbTfYGzGkhAgGCOnlAFfSQoCDbS7gSvIQ/uZ6mlWEMyixasi1z/Sx6pkmCXetVcIc8aLxYTI+ROPDAfdUQQ7MRP2LPxLUsAkW2QSL/FKgMf2/oJbZQmue50vIddCJVREAAAAASUVORK5CYII= + + false + 0944aec4-9862-4758-8cf6-b1210d2c9ec3 + true + true + CT: L Topological Joint Rules + L_Topo_Joint + + + + + + 1600 + 1342 + 185 + 84 + + + 1710 + 1384 + + + + + + 4 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + mill_depth + ed7bbabd-08ec-4457-b980-695ea636abec + mill_depth + mill_depth + true + aa01afa4-173a-4459-9bf1-9dc2e745c93f + 1 + + + + + + 1602 + 1344 + 93 + 20 + + + 1650 + 1354 + + + + + + + + Script input small_beam_butts. + 210d71de-22eb-4ef2-8dff-379dd889a511 + small_beam_butts + small_beam_butts + true + 0 + + + + + + 1602 + 1364 + 93 + 20 + + + 1650 + 1374 + + + + + + + + Script input modify_cross. + 4eeec5ec-06dd-4521-a720-5bfdc8d73b06 + modify_cross + modify_cross + true + 0 + + + + + + 1602 + 1384 + 93 + 20 + + + 1650 + 1394 + + + + + + + + Script input reject_i. + 68e5ce57-5068-4a59-b91c-b6abbf1a6370 + reject_i + reject_i + true + 0 + + + + + + 1602 + 1404 + 93 + 20 + + + 1650 + 1414 + + + + + + + + Script output LButtJoint. + 9e06f057-b0d5-495c-880e-79b82c83a1fe + LButtJoint + LButtJoint + false + 0 + + + + + + 1725 + 1344 + 58 + 80 + + + 1754 + 1384 + + + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + 10636707-b664-4e14-ba9a-d85ce2a0c401 + Number Slider + + false + 0 + + + + + + 1611 + 1530 + 195 + 20 + + + 1611.303 + 1530.793 + + + + + + 3 + 1 + 1 + 100 + 0 + 0 + 50 + + + + + + + + + 3cadddef-1e2b-4c09-9390-0e8f78f7609f + Merge + + + + + Merge a bunch of data streams + true + 50b4d783-2e1c-4a34-ba17-9ce6f72fbd7b + Merge + Merge + + + + + + 378 + 1871 + 88 + 64 + + + 416 + 1903 + + + + + + 3 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 2 + Data stream 1 + 42be7b8d-e20c-46e5-8570-12c12619037d + false + Data 1 + D1 + true + 65a4e860-1e2f-40f1-811c-db669445260d + 1 + + + + + + 380 + 1873 + 21 + 20 + + + 392 + 1883 + + + + + + + + 2 + Data stream 2 + 7acfd55e-46af-4e26-8693-ddbe69099f6d + false + Data 2 + D2 + true + c752484d-c8b1-4dfe-b95c-052b1b9086ea + 1 + + + + + + 380 + 1893 + 21 + 20 + + + 392 + 1903 + + + + + + + + 2 + Data stream 3 + d269cf8e-d381-46f9-81ea-22e91d57bbfa + false + Data 3 + D3 + true + 0 + + + + + + 380 + 1913 + 21 + 20 + + + 392 + 1923 + + + + + + + + 2 + Result of merge + 11ee2cb8-fea9-407e-8b4b-cbcf1fe58761 + 1 + Result + R + false + 0 + + + + + + 431 + 1873 + 33 + 60 + + + 439.5 + 1903 + + + + + + + + + + + + + + eeafc956-268e-461d-8e73-ee05c6f72c01 + Stream Filter + + + + + Filters a collection of input streams + true + 21576f20-ed8e-4c3d-ac74-f4c6622726c1 + Stream Filter + Filter + + + + + + 631 + 1582 + 77 + 64 + + + 663 + 1614 + + + + + + 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 + 5654947d-2496-4fda-aa9d-464d6556a204 + Gate + G + false + 1af3d673-3732-4271-8c59-52c07949abb2 + 1 + + + + + + 633 + 1584 + 15 + 20 + + + 642 + 1594 + + + + + + 1 + + + + + 1 + {0} + + + + + 0 + + + + + + + + + + + 2 + Input stream at index 0 + 754ae8d9-c10a-4fd4-9620-5e5a7054c6f6 + false + Stream 0 + 0 + true + 0bc447d1-0b47-4d44-88e5-1c1a33db80fc + 1 + + + + + + 633 + 1604 + 15 + 20 + + + 642 + 1614 + + + + + + + + 2 + Input stream at index 1 + d482ec97-792e-4152-90f0-4e5000ccf833 + false + Stream 1 + 1 + true + 353d7621-3cd9-4d30-ac48-2ba4334ede62 + 1 + + + + + + 633 + 1624 + 15 + 20 + + + 642 + 1634 + + + + + + + + 2 + Filtered stream + 5e8cc817-6357-4d57-8f5c-bb9d6ce9669c + false + Stream + S(1) + false + 0 + + + + + + 678 + 1584 + 28 + 60 + + + 692 + 1614 + + + + + + + + + + + + + + 22990b1f-9be6-477c-ad89-f775cd347105 + Flip Curve + + + + + Flip a curve using an optional guide curve. + true + 016c3b71-d2f1-4a70-be07-535a52db8a79 + Flip Curve + Flip + + + + + + 543 + 1622 + 66 + 44 + + + 575 + 1644 + + + + + + Curve to flip + 603a86f3-4555-4985-bfc4-a523884cf415 + Curve + C + false + 0bc447d1-0b47-4d44-88e5-1c1a33db80fc + 1 + + + + + + 545 + 1624 + 15 + 20 + + + 554 + 1634 + + + + + + + + Optional guide curve + e612372e-97c9-4956-83c1-7c0c87cd1d5f + Guide + G + true + 0 + + + + + + 545 + 1644 + 15 + 20 + + + 554 + 1654 + + + + + + + + Flipped curve + 353d7621-3cd9-4d30-ac48-2ba4334ede62 + Curve + C + false + 0 + + + + + + 590 + 1624 + 17 + 20 + + + 598.5 + 1634 + + + + + + + + Flip action + 3a7a88e4-e7f8-460d-ab66-2f6e0538533a + Flag + F + false + 0 + + + + + + 590 + 1644 + 17 + 20 + + + 598.5 + 1654 + + + + + + + + + + + + 28061aae-04fb-4cb5-ac45-16f3b66bc0a4 + Center Box + + + + + Create a box centered on a plane. + true + b914bb37-7b5a-46a9-a1fb-6b230797d09b + Center Box + Box + + + + + + 2280 + 1273 + 64 + 84 + + + 2311 + 1315 + + + + + + Base plane + 65a01b3f-9641-4df1-a831-f0ac4ddbc0c8 + Base + B + false + 0 + + + + + + 2282 + 1275 + 14 + 20 + + + 2290.5 + 1285 + + + + + + 1 + + + + + 1 + {0} + + + + + + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 0 + + + + + + + + + + + + Size of box in {x} direction. + be885264-6145-4d8b-84ce-3158683407e8 + X + X + false + 0 + + + + + + 2282 + 1295 + 14 + 20 + + + 2290.5 + 1305 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + Size of box in {y} direction. + b9985f26-d4b2-41fe-9963-3b6ad745502d + Y + Y + false + 0 + + + + + + 2282 + 1315 + 14 + 20 + + + 2290.5 + 1325 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + Size of box in {z} direction. + 7c5c3d48-fede-49be-a769-5b4472f051c3 + Z + Z + false + 0 + + + + + + 2282 + 1335 + 14 + 20 + + + 2290.5 + 1345 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + Resulting box + 9db4d62a-2de4-4b49-b116-d91d1fb3fbc1 + Box + B + false + 0 + + + + + + 2326 + 1275 + 16 + 80 + + + 2334 + 1315 + + + + + + + + + + + + 7c0523e8-79c9-45a2-8777-cf0d46bc5432 + Volume + + + + + Solve volume properties for closed breps and meshes. + true + 87adbf20-4372-408f-afd2-8de0206c319f + Volume + Volume + + + + + + 2086 + 1278 + 66 + 44 + + + 2118 + 1300 + + + + + + 1 + ac2bc2cb-70fb-4dd5-9c78-7e1ea97fe278 + 2 + 3e8ca6be-fda8-4aaf-b5c0-3c54c8bb7312 + fbac3e32-f100-4292-8692-77240a42fd1a + + + + + Closed brep or mesh for volume computation + 6653e031-0118-4585-9a5a-3c23bddc56e5 + Geometry + G + false + b3588f0a-414e-4a3d-87e6-79cde3ae279b + 1 + + + + + + 2088 + 1280 + 15 + 40 + + + 2097 + 1300 + + + + + + + + Volume of geometry + 84b4bf60-4f2c-410a-abe2-275faa062d0f + Volume + V + true + 0 + + + + + + 2133 + 1280 + 17 + 20 + + + 2141.5 + 1290 + + + + + + + + Volume centroid of geometry + c4969727-a064-4123-ae15-e612faccd299 + Centroid + C + true + 0 + + + + + + 2133 + 1300 + 17 + 20 + + + 2141.5 + 1310 + + + + + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + 63256972-bef4-4fe4-b953-922687ce5e7c + Plane + Pln + false + 502370ce-eac6-4444-b153-000dda5f7ad2 + 1 + + + + + + 1925 + 1329 + 50 + 24 + + + 1950.909 + 1341.371 + + + + + + + + + + 9103c240-a6a9-4223-9b42-dbd19bf38e2b + Unit Z + + + + + Unit vector parallel to the world {z} axis. + true + 69a1047d-365a-4696-ac21-d66891c3cd01 + Unit Z + Z + + + + + + 724 + 1525 + 63 + 28 + + + 753 + 1539 + + + + + + Unit multiplication + b815eb40-79e9-4d59-baf5-789e4733642b + Factor + F + false + 0 + + + + + + 726 + 1527 + 12 + 24 + + + 733.5 + 1539 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + World {z} vector + 7d37dfc6-37e3-4d21-81f9-feb71a9e7daf + Unit vector + V + false + 0 + + + + + + 768 + 1527 + 17 + 24 + + + 776.5 + 1539 + + + + + + + + + + + + 79f9fbb3-8f1d-4d9a-88a9-f7961b1012cd + Unit X + + + + + Unit vector parallel to the world {x} axis. + true + 0ee70b32-a69d-44dc-a0ef-f78b3accb6a6 + Unit X + X + + + + + + 744 + 1499 + 63 + 28 + + + 773 + 1513 + + + + + + Unit multiplication + 84f4a5d7-05c6-4323-aa8c-0f3aaafe88ab + Factor + F + false + 0 + + + + + + 746 + 1501 + 12 + 24 + + + 753.5 + 1513 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + World {x} vector + 985674c1-93e7-4bfa-a6b2-fa372e7b66a4 + Unit vector + V + false + 0 + + + + + + 788 + 1501 + 17 + 24 + + + 796.5 + 1513 + + + + + + + + + + + + a0d62394-a118-422d-abb3-6af115c75b25 + Addition + + + + + Mathematical addition + true + 3da3f49a-976d-4d1a-9944-be7361193132 + Addition + A+B + + + + + + 822 + 1499 + 65 + 44 + + + 853 + 1521 + + + + + + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + First item for addition + 5e583fc7-a180-47d9-806a-9671969c74cd + A + A + true + 985674c1-93e7-4bfa-a6b2-fa372e7b66a4 + 1 + + + + + + 824 + 1501 + 14 + 20 + + + 832.5 + 1511 + + + + + + + + Second item for addition + 1258f834-4314-420b-92a4-2ea8f8149a09 + B + B + true + 7d37dfc6-37e3-4d21-81f9-feb71a9e7daf + 1 + + + + + + 824 + 1521 + 14 + 20 + + + 832.5 + 1531 + + + + + + + + Result of addition + f0b81979-f783-450f-b862-9b53febcbe90 + Result + R + false + 0 + + + + + + 868 + 1501 + 17 + 40 + + + 876.5 + 1521 + + + + + + + + + + + + + + 59daf374-bc21-4a5e-8282-5504fb7ae9ae + List Item + + + + + 0 + Retrieve a specific item from a list. + true + 303f3345-f7ae-4d8a-a427-00633f5158c9 + List Item + Item + + + + + + 2261 + 1383 + 64 + 64 + + + 2295 + 1415 + + + + + + 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 + 02198cbf-b46c-409a-876a-ab7137c2c7c3 + List + L + false + 725ac33d-374c-4cc0-8f18-3e15182f1323 + 1 + + + + + + 2263 + 1385 + 17 + 20 + + + 2273 + 1395 + + + + + + + + Item index + a58fb182-d690-47fe-a531-395d7a504986 + Index + i + false + 0 + + + + + + 2263 + 1405 + 17 + 20 + + + 2273 + 1415 + + + + + + 1 + + + + + 1 + {0} + + + + + 0 + + + + + + + + + + + Wrap index to list bounds + cfacaccd-8c41-4b49-b062-31e2775b1e82 + Wrap + W + false + 0 + + + + + + 2263 + 1425 + 17 + 20 + + + 2273 + 1435 + + + + + + 1 + + + + + 1 + {0} + + + + + true + + + + + + + + + + + Item at {i'} + 53f6b1d1-c010-483d-b5aa-7c83fc14f9b6 + false + Item + i + false + 0 + + + + + + 2310 + 1385 + 13 + 60 + + + 2316.5 + 1415 + + + + + + + + + + + + + + 59daf374-bc21-4a5e-8282-5504fb7ae9ae + List Item + + + + + 0 + Retrieve a specific item from a list. + true + cf135943-38be-4663-ab8d-0adadaca146c + List Item + Item + + + + + + 2029 + 1336 + 74 + 84 + + + 2063 + 1378 + + + + + + 3 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 2e3ab970-8545-46bb-836c-1c11e5610bce + cb95db89-6165-43b6-9c41-5702bc5bf137 + 4 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 1 + Base list + 1522da3f-4c46-480e-9c80-988a4ab52cfd + List + L + false + 63256972-bef4-4fe4-b953-922687ce5e7c + 1 + + + + + + 2031 + 1338 + 17 + 26 + + + 2041 + 1351.333 + + + + + + + + Item index + 4b65d389-6766-4850-b495-164cce1c42de + Index + i + false + 0 + + + + + + 2031 + 1364 + 17 + 27 + + + 2041 + 1378 + + + + + + 1 + + + + + 1 + {0} + + + + + 0 + + + + + + + + + + + Wrap index to list bounds + b48d0cca-8f71-499e-a857-6d7f31da9575 + Wrap + W + false + 0 + + + + + + 2031 + 1391 + 17 + 27 + + + 2041 + 1404.667 + + + + + + 1 + + + + + 1 + {0} + + + + + true + + + + + + + + + + + Item at {i'} + 6a0268e5-201a-44ec-89af-473ef88f105f + false + Item + i + false + 0 + + + + + + 2078 + 1338 + 23 + 20 + + + 2089.5 + 1348 + + + + + + + + Item at {+1'} + d6d70e65-f2b9-4f33-807b-e4851b80613a + false + Item +1 + +1 + false + 0 + + + + + + 2078 + 1358 + 23 + 20 + + + 2089.5 + 1368 + + + + + + + + Item at {+2'} + a97d3de6-311e-495d-903f-7d5145e85a2a + false + Item +2 + +2 + false + 0 + + + + + + 2078 + 1378 + 23 + 20 + + + 2089.5 + 1388 + + + + + + + + Item at {+3'} + b54d49ae-2637-4951-a942-5be0ff2aeac8 + false + Item +3 + +3 + false + 0 + + + + + + 2078 + 1398 + 23 + 20 + + + 2089.5 + 1408 + + + + + + + + + + + + + + d8332545-21b2-4716-96e3-8559a9876e17 + Dispatch + + + + + Dispatch the items in a list into two target lists. + true + cb623ee7-06c3-4400-aaba-a9487b4ee237 + Dispatch + Dispatch + + + + + + 2145 + 1507 + 64 + 44 + + + 2175 + 1529 + + + + + + 1 + List to filter + f301c669-eabb-4535-b5ed-b3bf1b42699b + List + L + false + 725ac33d-374c-4cc0-8f18-3e15182f1323 + 1 + + + + + + 2147 + 1509 + 13 + 20 + + + 2155 + 1519 + + + + + + + + 1 + Dispatch pattern + 4b1f1469-cc85-4ab1-9812-a58999856e66 + Dispatch pattern + P + false + 0 + + + + + + 2147 + 1529 + 13 + 20 + + + 2155 + 1539 + + + + + + 1 + + + + + 2 + {0} + + + + + true + + + + + false + + + + + + + + + + + 1 + Dispatch target for True values + 070a7853-fe38-4e8a-b6d2-caeaaa0ae003 + List A + A + false + 0 + + + + + + 2190 + 1509 + 17 + 20 + + + 2198.5 + 1519 + + + + + + + + 1 + Dispatch target for False values + 90efa295-d4df-4bf6-9596-5f0142788873 + List B + B + false + 0 + + + + + + 2190 + 1529 + 17 + 20 + + + 2198.5 + 1539 + + + + + + + + + + + + 537b0419-bbc2-4ff4-bf08-afe526367b2c + Custom Preview + + + + + Allows for customized geometry previews + true + 5969f9b6-e599-48a8-a77a-9967f0e908ee + Custom Preview + Preview + + + + + + + 2300 + 1481 + 48 + 44 + + + 2334 + 1503 + + + + + + Geometry to preview + true + 6ffcc6ca-c0f2-4057-80d1-9394a508e988 + Geometry + G + false + 070a7853-fe38-4e8a-b6d2-caeaaa0ae003 + 1 + + + + + + 2302 + 1483 + 17 + 20 + + + 2312 + 1493 + + + + + + + + The material override + 4add57de-5031-447f-a497-bb416da407be + Material + M + false + 0 + + + + + + 2302 + 1503 + 17 + 20 + + + 2312 + 1513 + + + + + + 1 + + + + + 1 + {0} + + + + + + 255;221;160;221 + + + 255;66;48;66 + + 0.5 + + 255;255;255;255 + + 0 + + + + + + + + + + + + + + + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep + + + + + Contains a collection of Breps (Boundary REPresentations) + true + 05edbc97-20e7-47f4-ba39-16e952aca10f + Brep + Brep + false + 90efa295-d4df-4bf6-9596-5f0142788873 + 1 + + + + + + 2230 + 1577 + 50 + 24 + + + 2255.067 + 1589.683 + + + + + + + + + + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep + + + + + Contains a collection of Breps (Boundary REPresentations) + c17ecd4e-0c95-4f30-a30e-9d892e6426cc + Brep + Brep + false + 0 + + + + + + 2229 + 928 + 50 + 24 + + + 2254.2 + 940.6667 + + + + + + 1 + + + + + 2 + {0} + + + + + 29681234-09cf-497f-ae28-cbb735e46f22 + + + + + ee1a0b75-9176-4b97-b45c-8f17b5c16e99 + + + + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + 380a46d2-43bc-4a8c-8a22-b7aef89bbf72 + Plane + Pln + false + c17ecd4e-0c95-4f30-a30e-9d892e6426cc + 1 + + + + + + 2313 + 928 + 50 + 24 + + + 2338.067 + 940.9667 + + + + + + + + + + 6ea4c4c7-ddef-4313-a21f-8b445c20220c + 1c9de8a1-315f-4c56-af06-8f69fee80a7a + Tween Two Planes + + + + + Tween between two planes. + 8a7ee619-ca42-4727-a2cc-5cf2035c36c8 + Tween Two Planes + Twn2Plns + + + + + + 2493 + 939 + 65 + 84 + + + 2525 + 981 + + + + + + Plane to tween from + eaae4cc6-ae0d-4898-a8ae-a582ca50ce29 + Plane A + A + false + b70b10e6-e22b-4abe-a2d5-45a65332177c + 1 + + + + + + 2495 + 941 + 15 + 20 + + + 2504 + 951 + + + + + + + + Plane to tween to + 9d069cc0-02f3-475f-9b9b-4dd32f2ea790 + Plane B + B + false + fc33ea5e-8457-40ab-8a5f-4f6de1e193d7 + 1 + + + + + + 2495 + 961 + 15 + 20 + + + 2504 + 971 + + + + + + + + Tween factor (0.0 = Plane A, 1.0 = Plane B) + 06cd748f-8bc3-48ec-91f1-e33ddd966045 + Factor + F + false + 0 + + + + + + 2495 + 981 + 15 + 20 + + + 2504 + 991 + + + + + + 1 + + + + + 1 + {0} + + + + + 0.5 + + + + + + + + + + + Interpolate with quaternion rotation + 012b355a-6715-4ddf-9169-8b9e68bfcacb + Quaternion + Q + false + 0 + + + + + + 2495 + 1001 + 15 + 20 + + + 2504 + 1011 + + + + + + 1 + + + + + 1 + {0} + + + + + true + + + + + + + + + + + Resulting tween plane + 0a7cd565-45a8-4c93-8f75-04d086baa295 + Tween + T + false + 0 + + + + + + 2540 + 941 + 16 + 80 + + + 2548 + 981 + + + + + + + + + + + + d8332545-21b2-4716-96e3-8559a9876e17 + Dispatch + + + + + Dispatch the items in a list into two target lists. + 687ce77d-9faf-470b-aaea-ccbcc0ba4326 + Dispatch + Dispatch + + + + + + 2379 + 939 + 64 + 44 + + + 2409 + 961 + + + + + + 1 + List to filter + 8997e329-8f57-4af9-b7d4-64a6d829387b + List + L + false + 380a46d2-43bc-4a8c-8a22-b7aef89bbf72 + 1 + + + + + + 2381 + 941 + 13 + 20 + + + 2389 + 951 + + + + + + + + 1 + Dispatch pattern + 22526779-5169-4e54-9c8d-4e398ca1638c + Dispatch pattern + P + false + 0 + + + + + + 2381 + 961 + 13 + 20 + + + 2389 + 971 + + + + + + 1 + + + + + 2 + {0} + + + + + true + + + + + false + + + + + + + + + + + 1 + Dispatch target for True values + b70b10e6-e22b-4abe-a2d5-45a65332177c + List A + A + false + 0 + + + + + + 2424 + 941 + 17 + 20 + + + 2432.5 + 951 + + + + + + + + 1 + Dispatch target for False values + fc33ea5e-8457-40ab-8a5f-4f6de1e193d7 + List B + B + false + 0 + + + + + + 2424 + 961 + 17 + 20 + + + 2432.5 + 971 + + + + + + + + + + + + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep + + + + + Contains a collection of Breps (Boundary REPresentations) + ed4080d4-774f-4f9f-9f0d-d8ea182a21c7 + Brep + Brep + false + 0 + + + + + + 2225 + 1066 + 50 + 24 + + + 2250.564 + 1078.813 + + + + + + 1 + + + + + 1 + {0} + + + + + 5903e3bf-023e-4163-8e83-1513748adbcf + + + + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + GhPython Script + + + + + print(x) +print(not x) + GhPython provides a Python script component + + 152 + 152 + + + 835 + 874 + + true + false + false + 06d438ea-a8ad-4008-8d87-0fc64ee0605c + false + true + GhPython Script + Python + + + + + + 1194 + 1911 + 72 + 44 + + + 1223 + 1933 + + + + + + 2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 + 2 + 3ede854e-c753-40eb-84cb-b48008f14fd4 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + true + Script variable Python + 172a9d92-bdb0-4c5f-9ac6-67776b2ba60a + x + x + true + 0 + true + 78604bce-20bd-4b08-9cdd-ce914039a739 + 1 + d60527f5-b5af-4ef6-8970-5f96fe412559 + + + + + + 1196 + 1913 + 12 + 20 + + + 1203.5 + 1923 + + + + + + + + true + Script input y. + 360aaafe-bde7-44ec-9e56-d3dad003f712 + y + y + true + 0 + true + 0 + 87f87f55-5b71-41f4-8aea-21d494016f81 + + + + + + 1196 + 1933 + 12 + 20 + + + 1203.5 + 1943 + + + + + + + + The execution information, as output and error streams + 0a6920dc-ecd8-4d84-a7a0-b42f6f1108db + out + out + false + 0 + + + + + + 1238 + 1913 + 26 + 20 + + + 1251 + 1923 + + + + + + + + Script output a. + d52d55cd-bd7e-4e1d-91ed-05a4839349c7 + a + a + false + 0 + + + + + + 1238 + 1933 + 26 + 20 + + + 1251 + 1943 + + + + + + + + + + + + + + 2e78987b-9dfb-42a2-8b76-3923ac8bd91a + Boolean Toggle + + + + + Boolean (true/false) toggle + 78604bce-20bd-4b08-9cdd-ce914039a739 + Boolean Toggle + Toggle + false + 0 + false + + + + + + 1063 + 1845 + 104 + 22 + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + CT: L Topological Joint Rules + + + + + import inspect + +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning + +from compas_timber.connections import Joint +from compas_timber.connections import JointTopology +from compas_timber.connections import LMiterJoint +from compas_timber.design import TopologyRule +from compas_timber.ghpython.ghcomponent_helpers import get_leaf_subclasses +from compas_timber.ghpython.ghcomponent_helpers import manage_dynamic_params +from compas_timber.ghpython.ghcomponent_helpers import rename_gh_output + + +class L_TopologyJointRule(component): + def __init__(self): + super(L_TopologyJointRule, self).__init__() + self.classes = {} + for cls in get_leaf_subclasses(Joint): + supported_topo = cls.SUPPORTED_TOPOLOGY + if not isinstance(supported_topo, list): + supported_topo = [supported_topo] + if JointTopology.TOPO_L in supported_topo: + self.classes[cls.__name__] = cls + if ghenv.Component.Params.Output[0].NickName == "Rule": + self.joint_type = LMiterJoint + self.clicked = False + else: + self.joint_type = self.classes.get(ghenv.Component.Params.Output[0].NickName, None) + self.clicked = True + + def RunScript(self, *args): + if not self.clicked: + ghenv.Component.Message = "Default: LMiterJoint" + self.AddRuntimeMessage(Warning, "LMiterJoint is default, change in context menu (right click)") + return TopologyRule(JointTopology.TOPO_L, LMiterJoint) + else: + ghenv.Component.Message = self.joint_type.__name__ + kwargs = {} + for i, val in enumerate(args): + if val is not None: + kwargs[self.arg_names()[i]] = val + supported_topo = self.joint_type.SUPPORTED_TOPOLOGY + if not isinstance(supported_topo, list): + supported_topo = [supported_topo] + if JointTopology.TOPO_L not in supported_topo: + self.AddRuntimeMessage(Warning, "Joint type does not match topology. Joint may not be generated.") + return TopologyRule(JointTopology.TOPO_L, self.joint_type, **kwargs) + + def arg_names(self): + names = inspect.getargspec(self.joint_type.__init__)[0][3:] + return [name for name in names if (name != "key") and (name != "frame")] + + def AppendAdditionalMenuItems(self, menu): + for name in self.classes.keys(): + item = menu.Items.Add(name, None, self.on_item_click) + if self.joint_type and name == self.joint_type.__name__: + item.Checked = True + + def on_item_click(self, sender, event_info): + self.clicked = True + self.joint_type = self.classes[str(sender)] + rename_gh_output(self.joint_type.__name__, 0, ghenv) + manage_dynamic_params(self.arg_names(), ghenv, rename_count=0, permanent_param_count=0) + ghenv.Component.ExpireSolution(True) + + GhPython provides a Python script component + + 456 + 456 + + + 1826 + 1208 + + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDAAACwwBP0AiyAAAANBJREFUSEvtk70NwjAQhTNCBrDkk7wAo9DQMwIjMIHFCIyQEVK4TMEIjMAGRif5JOvlcEwwiCLFV8Q/75PeOV2MsfsmswXGWRqdpZi44P475KHkLPXO0jkLF054sRYJ3yuhGndnaYchJUTAFzHsFQOGlBBB3jlzTXVxbTfYGzGkhAgGCOnlAFfSQoCDbS7gSvIQ/uZ6mlWEMyixasi1z/Sx6pkmCXetVcIc8aLxYTI+ROPDAfdUQQ7MRP2LPxLUsAkW2QSL/FKgMf2/oJbZQmue50vIddCJVREAAAAASUVORK5CYII= + + false + ef35e456-a7dc-48b3-b82d-b710f70fc8bb + true + true + CT: L Topological Joint Rules + L_Topo_Joint + + + + + + 1338 + 1644 + 166 + 44 + + + 1432 + 1666 + + + + + + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + flip_lap_side + f8c7172a-482a-44b2-a271-28dd581abec2 + flip_lap_side + flip_lap_side + true + 78604bce-20bd-4b08-9cdd-ce914039a739 + 1 + + + + + + 1340 + 1646 + 77 + 20 + + + 1380 + 1656 + + + + + + + + Script input cut_plane_bias. + e6d6fabc-c329-41ae-b3f0-405db9ad2379 + cut_plane_bias + cut_plane_bias + true + 0 + + + + + + 1340 + 1666 + 77 + 20 + + + 1380 + 1676 + + + + + + + + Script output LLapJoint. + 51ae2a95-d349-4d07-9cd2-c6bc6a9d3544 + LLapJoint + LLapJoint + false + 0 + + + + + + 1447 + 1646 + 55 + 40 + + + 1474.5 + 1666 + + + + + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + CT: T Topological Joint Rules + + + + + import inspect + +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning + +from compas_timber.connections import Joint +from compas_timber.connections import JointTopology +from compas_timber.connections import TButtJoint +from compas_timber.design import TopologyRule +from compas_timber.ghpython.ghcomponent_helpers import get_leaf_subclasses +from compas_timber.ghpython.ghcomponent_helpers import manage_dynamic_params +from compas_timber.ghpython.ghcomponent_helpers import rename_gh_output + + +class T_TopologyJointRule(component): + def __init__(self): + super(T_TopologyJointRule, self).__init__() + self.classes = {} + for cls in get_leaf_subclasses(Joint): + supported_topo = cls.SUPPORTED_TOPOLOGY + if not isinstance(supported_topo, list): + supported_topo = [supported_topo] + if JointTopology.TOPO_T in supported_topo: + self.classes[cls.__name__] = cls + if ghenv.Component.Params.Output[0].NickName == "Rule": + self.joint_type = TButtJoint + self.clicked = False + else: + self.joint_type = self.classes.get(ghenv.Component.Params.Output[0].NickName, None) + self.clicked = True + + def RunScript(self, *args): + if not self.clicked: + ghenv.Component.Message = "Default: TButtJoint" + self.AddRuntimeMessage(Warning, "TButtJoint is default, change in context menu (right click)") + return TopologyRule(JointTopology.TOPO_T, TButtJoint) + else: + ghenv.Component.Message = self.joint_type.__name__ + kwargs = {} + for i, val in enumerate(args): + if val is not None: + kwargs[self.arg_names()[i]] = val + supported_topo = self.joint_type.SUPPORTED_TOPOLOGY + if not isinstance(supported_topo, list): + supported_topo = [supported_topo] + if JointTopology.TOPO_T not in supported_topo: + self.AddRuntimeMessage(Warning, "Joint type does not match topology. Joint may not be generated.") + return TopologyRule(JointTopology.TOPO_T, self.joint_type, **kwargs) + + def arg_names(self): + names = inspect.getargspec(self.joint_type.__init__)[0][3:] + return [name for name in names if (name != "key") and (name != "frame")] + + def AppendAdditionalMenuItems(self, menu): + for name in self.classes.keys(): + item = menu.Items.Add(name, None, self.on_item_click) + if self.joint_type and name == self.joint_type.__name__: + item.Checked = True + + def on_item_click(self, sender, event_info): + self.clicked = True + self.joint_type = self.classes[str(sender)] + rename_gh_output(self.joint_type.__name__, 0, ghenv) + manage_dynamic_params(self.arg_names(), ghenv, rename_count=0, permanent_param_count=0) + ghenv.Component.ExpireSolution(True) + + GhPython provides a Python script component + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDAAACwwBP0AiyAAAANVJREFUSEvtk70NwjAQhTNC6CP5JC/AKIzACHS0TGAxQmqqjJDCJQUjMAIbGJ3kk8yzsePIFEgpviL+eZ/0zumcc90viRYYrWjWipznivs1hKGkFfVa0SUIF054cSkSfkiEpnhqRXsMySECvohh35gwJIcIws6Z0dfFtT1gb8aQHCKYIKSXA1xJCwEOtrmAKwlD+JvraVYRziDHqiEvfaavVc/US7jrVCXMUc4Nxu4GY12BWyQIgZl8/MVNBDUMxt596Bn3mGihlk1QZBMU+X9BiWihNW9f8MWtv55wzQAAAABJRU5ErkJggg== + + false + ec8833a5-9777-4c3d-be2d-7255ad159bae + true + true + CT: T Topological Joint Rules + T_Topo_Joint + + + + + + 1348 + 1757 + 167 + 44 + + + 1442 + 1779 + + + + + + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + flip_lap_side + 1f179e6f-7230-4012-869a-62abf9a15ab2 + flip_lap_side + flip_lap_side + true + 78604bce-20bd-4b08-9cdd-ce914039a739 + 1 + + + + + + 1350 + 1759 + 77 + 20 + + + 1390 + 1769 + + + + + + + + Script input cut_plane_bias. + 4e714825-0a92-461c-beb9-8768403d1bc8 + cut_plane_bias + cut_plane_bias + true + e759cdbb-486d-4827-8fb6-1ddc9a863872 + 1 + + + + + + 1350 + 1779 + 77 + 20 + + + 1390 + 1789 + + + + + + + + Script output TLapJoint. + 53e57ab2-3b0f-4b55-bec0-7f58bf6e923c + TLapJoint + TLapJoint + false + 0 + + + + + + 1457 + 1759 + 56 + 40 + + + 1485 + 1779 + + + + + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + CT: X Topological Joint Rules + + + + + import inspect + +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning + +from compas_timber.connections import Joint +from compas_timber.connections import JointTopology +from compas_timber.connections import XLapJoint +from compas_timber.design import TopologyRule +from compas_timber.ghpython.ghcomponent_helpers import get_leaf_subclasses +from compas_timber.ghpython.ghcomponent_helpers import manage_dynamic_params +from compas_timber.ghpython.ghcomponent_helpers import rename_gh_output + + +class X_TopologyJointRule(component): + def __init__(self): + super(X_TopologyJointRule, self).__init__() + self.classes = {} + for cls in get_leaf_subclasses(Joint): + supported_topo = cls.SUPPORTED_TOPOLOGY + if not isinstance(supported_topo, list): + supported_topo = [supported_topo] + if JointTopology.TOPO_X in supported_topo: + self.classes[cls.__name__] = cls + if ghenv.Component.Params.Output[0].NickName == "Rule": + self.joint_type = XLapJoint + self.clicked = False + else: + self.joint_type = self.classes.get(ghenv.Component.Params.Output[0].NickName, None) + self.clicked = True + + def RunScript(self, *args): + if not self.clicked: + ghenv.Component.Message = "Default: XLapJoint" + self.AddRuntimeMessage(Warning, "XLapJoint is default, change in context menu (right click)") + return TopologyRule(JointTopology.TOPO_X, XLapJoint) + else: + ghenv.Component.Message = self.joint_type.__name__ + kwargs = {} + for i, val in enumerate(args): + if val is not None: + kwargs[self.arg_names()[i]] = val + supported_topo = self.joint_type.SUPPORTED_TOPOLOGY + if not isinstance(supported_topo, list): + supported_topo = [supported_topo] + if JointTopology.TOPO_X not in supported_topo: + self.AddRuntimeMessage(Warning, "Joint type does not match topology. Joint may not be generated.") + return TopologyRule(JointTopology.TOPO_X, self.joint_type, **kwargs) + + def arg_names(self): + names = inspect.getargspec(self.joint_type.__init__)[0][3:] + return [name for name in names if (name != "key") and (name != "frame")] + + def AppendAdditionalMenuItems(self, menu): + for name in self.classes.keys(): + item = menu.Items.Add(name, None, self.on_item_click) + if self.joint_type and name == self.joint_type.__name__: + item.Checked = True + + def on_item_click(self, sender, event_info): + self.clicked = True + self.joint_type = self.classes[str(sender)] + rename_gh_output(self.joint_type.__name__, 0, ghenv) + manage_dynamic_params(self.arg_names(), ghenv, rename_count=0, permanent_param_count=0) + ghenv.Component.ExpireSolution(True) + + GhPython provides a Python script component + + 494 + 494 + + + 835 + 874 + + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDAAACwwBP0AiyAAAAUBJREFUSEu1kyFuAzEQRXOEhFuyFV8gUq7QA5SEFxWXhQYGWaVlxUXhIQGGBb1ApJyg6g1cfWlmNZpxtt7EBU+7Gs/8r/3jnZVSZv+JKYDowyn6UIhXfT4FKRqiD/Pow06IMy96sBUWf6yI1rhEH1ZaZAw2wKAWu8ZBi4zBBjJz8E5xIbYvdXbSImOwwUGJzLkBkfQw0IvtboBIpAi+CPF0i0jvYIybltx6TX9uuqZkgqxrkYAn7nMpP7uUC7ER9U2tbhzJTO7E/MUu5SMJnV3KCwLvqH3IXiPeAgl+k+AbgXfUFncbAJfyVkTCbHWfGZyCS/lTiB/1OTCFKbiU98Jgr8+BKbTiUl5XIlrrPjPYiogHz+Fd95nBFtSC8SUP1xZthv/CpbwUV3TIXV3V5T0GHMe5csY/23CjjEBvTKE3vzBOrcjlhJXzAAAAAElFTkSuQmCC + + false + 280b9454-d1a2-4ad3-aad6-af62e475b7ef + true + true + CT: X Topological Joint Rules + X_Topo_Joint + + + + + + 1394 + 1852 + 168 + 44 + + + 1488 + 1874 + + + + + + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + flip_lap_side + 0225dff4-43d0-47ac-a7d1-b942d29fe8bb + flip_lap_side + flip_lap_side + true + 78604bce-20bd-4b08-9cdd-ce914039a739 + 1 + + + + + + 1396 + 1854 + 77 + 20 + + + 1436 + 1864 + + + + + + + + Script input cut_plane_bias. + 03afd280-26e1-44cb-a284-ae00b75e6859 + cut_plane_bias + cut_plane_bias + true + e759cdbb-486d-4827-8fb6-1ddc9a863872 + 1 + + + + + + 1396 + 1874 + 77 + 20 + + + 1436 + 1884 + + + + + + + + Script output XLapJoint. + 9117a2a2-1d2b-4cc7-ad6d-4872f949c9f0 + XLapJoint + XLapJoint + false + 0 + + + + + + 1503 + 1854 + 57 + 40 + + + 1531.5 + 1874 + + + + + + + + + + + + + + 3cadddef-1e2b-4c09-9390-0e8f78f7609f + Merge + + + + + Merge a bunch of data streams + 86edab8b-8fe6-404f-8e9d-a8534911d7d6 + Merge + Merge + + + + + + 1666 + 1770 + 72 + 84 + + + 1704 + 1812 + + + + + + 4 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 2 + Data stream 1 + 65317895-314f-4fbf-bdca-9a5fc9b7ff95 + false + Data 1 + D1 + true + 9117a2a2-1d2b-4cc7-ad6d-4872f949c9f0 + 1 + + + + + + 1668 + 1772 + 21 + 20 + + + 1680 + 1782 + + + + + + + + 2 + Data stream 2 + 4b79f66f-2b38-425f-9781-f3d34adb0a3d + false + Data 2 + D2 + true + 53e57ab2-3b0f-4b55-bec0-7f58bf6e923c + 1 + + + + + + 1668 + 1792 + 21 + 20 + + + 1680 + 1802 + + + + + + + + 2 + Data stream 3 + 700bc5fa-2a1d-44ac-81b7-88bef1fbeb2a + false + Data 3 + D3 + true + 51ae2a95-d349-4d07-9cd2-c6bc6a9d3544 + 1 + + + + + + 1668 + 1812 + 21 + 20 + + + 1680 + 1822 + + + + + + + + 2 + Data stream 4 + 5b84ee25-69fb-419f-bf2c-70556d0fc3b3 + false + Data 4 + D4 + true + 0 + + + + + + 1668 + 1832 + 21 + 20 + + + 1680 + 1842 + + + + + + + + 2 + Result of merge + debfa811-a60a-402d-a198-bd648e1658ee + Result + R + false + 0 + + + + + + 1719 + 1772 + 17 + 80 + + + 1727.5 + 1812 + + + + + + + + + + + + + + 3cadddef-1e2b-4c09-9390-0e8f78f7609f + Merge + + + + + Merge a bunch of data streams + bd2b9a40-f16e-4ed9-ae3c-f89ef94e847f + Merge + Merge + + + + + + 1294 + 1467 + 72 + 64 + + + 1332 + 1499 + + + + + + 3 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 2 + Data stream 1 + e77f140c-abc3-441e-aa6a-42ae5863807a + false + Data 1 + D1 + true + d2365ef0-f85c-4709-9361-522790ff3ed5 + 1 + + + + + + 1296 + 1469 + 21 + 20 + + + 1308 + 1479 + + + + + + + + 2 + Data stream 2 + 42a09df3-4b16-4c1d-923e-5b185de638fb + false + Data 2 + D2 + true + 287d3ad7-dfd0-4415-b5a0-ff41819a7aba + 1 + + + + + + 1296 + 1489 + 21 + 20 + + + 1308 + 1499 + + + + + + + + 2 + Data stream 3 + 6b7420b5-71f0-454a-a83d-e060fbc0a2b3 + false + Data 3 + D3 + true + 0 + + + + + + 1296 + 1509 + 21 + 20 + + + 1308 + 1519 + + + + + + + + 2 + Result of merge + bf762047-bfb8-4813-a506-3cf4fb2c17bc + Result + R + false + 0 + + + + + + 1347 + 1469 + 17 + 60 + + + 1355.5 + 1499 + + + + + + + + + + + + + + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + Curve + + + + + Contains a collection of generic curves + de54e310-969d-4c72-ba7d-ecaf11317449 + Curve + Crv + false + 0 + + + + + + 258 + 1460 + 50 + 24 + + + 283.5297 + 1472.018 + + + + + + 1 + + + + + 3 + {0} + + + + + -1 + 81b39e2c-86d1-49cd-bb21-ca528b17dd02 + + + + + -1 + 42711433-1a94-4a41-9af9-65953b894aeb + + + + + -1 + a6c72b9c-b92c-4bdc-aec2-d4a5e8a42c1e + + + + + + + + + + + + + 59daf374-bc21-4a5e-8282-5504fb7ae9ae + List Item + + + + + 0 + Retrieve a specific item from a list. + 0bc55f85-874d-476f-b20f-3bb2f9cb267a + List Item + Item + + + + + + 338 + 1441 + 74 + 64 + + + 372 + 1473 + + + + + + 3 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 2e3ab970-8545-46bb-836c-1c11e5610bce + cb95db89-6165-43b6-9c41-5702bc5bf137 + 3 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 1 + Base list + 3fe5eb37-2607-4061-9cf6-a1904015e985 + List + L + false + de54e310-969d-4c72-ba7d-ecaf11317449 + 1 + + + + + + 340 + 1443 + 17 + 20 + + + 350 + 1453 + + + + + + + + Item index + 8e8f3ff1-c74a-4c16-bde6-aeaab9f5b9c8 + Index + i + false + 0 + + + + + + 340 + 1463 + 17 + 20 + + + 350 + 1473 + + + + + + 1 + + + + + 1 + {0} + + + + + 0 + + + + + + + + + + + Wrap index to list bounds + e76ba91a-1cf3-4fe3-afbd-315559ed2bc1 + Wrap + W + false + 0 + + + + + + 340 + 1483 + 17 + 20 + + + 350 + 1493 + + + + + + 1 + + + + + 1 + {0} + + + + + true + + + + + + + + + + + Item at {i'} + 73f83a29-896b-4021-bc42-1f65db517ae2 + false + Item + i + false + 0 + + + + + + 387 + 1443 + 23 + 20 + + + 398.5 + 1453 + + + + + + + + Item at {+1'} + bd53ef51-7947-47ac-ad07-27b174cf4ead + false + Item +1 + +1 + false + 0 + + + + + + 387 + 1463 + 23 + 20 + + + 398.5 + 1473 + + + + + + + + Item at {+2'} + 0f1eae77-2136-460f-9f03-f19b3c29f7b8 + false + Item +2 + +2 + false + 0 + + + + + + 387 + 1483 + 23 + 20 + + + 398.5 + 1493 + + + + + + + + + + + + + + 57da07bd-ecab-415d-9d86-af36d7073abc + Number Slider + + + + + Numeric slider for single values + e759cdbb-486d-4827-8fb6-1ddc9a863872 + Number Slider + + false + 0 + + + + + + 1045 + 1790 + 203 + 20 + + + 1045.411 + 1790.174 + + + + + + 1 + 1 + 0 + 1 + 0 + 0 + 0.6 + + + + + + + + + + + + + + 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 + + + + + diff --git a/examples/Grasshopper/tests/test_lap.ghx b/examples/Grasshopper/tests/test_lap.ghx index 3b9e80d9f..836c1b9a5 100644 --- a/examples/Grasshopper/tests/test_lap.ghx +++ b/examples/Grasshopper/tests/test_lap.ghx @@ -49,10 +49,10 @@ - -3280 - -2238 + -718 + -919 - 1.76470578 + 1.2750001 @@ -69,9 +69,9 @@ - 1 + 2 - + GhPython, Version=7.37.24107.15001, Culture=neutral, PublicKeyToken=null @@ -82,13 +82,23 @@ + + + Pufferfish, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null + 3.0.0.0 + Michael Pryor + 1c9de8a1-315f-4c56-af06-8f69fee80a7a + Pufferfish + 3.0.0.0 + + - 74 + 94 - + c552a431-af5b-46a9-a8a4-0fcbc27ef596 @@ -175,13 +185,13 @@ - 843 + 842 1368 166 20 - 843.639 + 842.9432 1368.29 @@ -221,13 +231,13 @@ - 86 + 85 1307 50 24 - 111.0335 + 110.3377 1319.804 @@ -279,13 +289,13 @@ - 870 + 869 1583 166 20 - 870.4417 + 869.7459 1583.228 @@ -324,13 +334,13 @@ - 870 + 869 1607 166 20 - 870.251 + 869.5552 1607.486 @@ -423,13 +433,13 @@ - 845 + 844 1343 166 20 - 845.1333 + 844.4375 1343.346 @@ -442,7 +452,7 @@ 100 0 0 - 100 + 60 @@ -457,7 +467,7 @@ - + """Creates a Beam from a LineCurve.""" import rhinoscriptsyntax as rs @@ -578,6 +588,7 @@ class Beam_fromCurve(component): 835 874 + true true true @@ -594,14 +605,14 @@ class Beam_fromCurve(component): - 1056 - 1305 + 1055 + 1307 145 124 - 1147 - 1367 + 1146 + 1369 @@ -638,14 +649,14 @@ class Beam_fromCurve(component): - 1058 - 1307 + 1057 + 1309 74 20 - 1096.5 - 1317 + 1095.5 + 1319 @@ -669,14 +680,14 @@ class Beam_fromCurve(component): - 1058 - 1327 + 1057 + 1329 74 20 - 1096.5 - 1337 + 1095.5 + 1339 @@ -701,14 +712,14 @@ class Beam_fromCurve(component): - 1058 - 1347 + 1057 + 1349 74 20 - 1096.5 - 1357 + 1095.5 + 1359 @@ -733,14 +744,14 @@ class Beam_fromCurve(component): - 1058 - 1367 + 1057 + 1369 74 20 - 1096.5 - 1377 + 1095.5 + 1379 @@ -764,14 +775,14 @@ class Beam_fromCurve(component): - 1058 - 1387 + 1057 + 1389 74 20 - 1096.5 - 1397 + 1095.5 + 1399 @@ -794,14 +805,14 @@ class Beam_fromCurve(component): - 1058 - 1407 + 1057 + 1409 74 20 - 1096.5 - 1417 + 1095.5 + 1419 @@ -820,14 +831,14 @@ class Beam_fromCurve(component): - 1162 - 1307 + 1161 + 1309 37 60 - 1180.5 - 1337 + 1179.5 + 1339 @@ -846,14 +857,14 @@ class Beam_fromCurve(component): - 1162 - 1367 + 1161 + 1369 37 60 - 1180.5 - 1397 + 1179.5 + 1399 @@ -873,7 +884,7 @@ class Beam_fromCurve(component): - + """Creates a Beam from a LineCurve.""" import rhinoscriptsyntax as rs @@ -994,6 +1005,7 @@ class Beam_fromCurve(component): 835 874 + true true true @@ -1010,14 +1022,14 @@ class Beam_fromCurve(component): - 1052 - 1542 + 1051 + 1544 145 124 - 1143 - 1604 + 1142 + 1606 @@ -1046,7 +1058,7 @@ class Beam_fromCurve(component): true 1 true - 0bc447d1-0b47-4d44-88e5-1c1a33db80fc + 5e8cc817-6357-4d57-8f5c-bb9d6ce9669c 1 87f87f55-5b71-41f4-8aea-21d494016f81 @@ -1054,14 +1066,14 @@ class Beam_fromCurve(component): - 1054 - 1544 + 1053 + 1546 74 20 - 1092.5 - 1554 + 1091.5 + 1556 @@ -1085,14 +1097,14 @@ class Beam_fromCurve(component): - 1054 - 1564 + 1053 + 1566 74 20 - 1092.5 - 1574 + 1091.5 + 1576 @@ -1117,14 +1129,14 @@ class Beam_fromCurve(component): - 1054 - 1584 + 1053 + 1586 74 20 - 1092.5 - 1594 + 1091.5 + 1596 @@ -1149,14 +1161,14 @@ class Beam_fromCurve(component): - 1054 - 1604 + 1053 + 1606 74 20 - 1092.5 - 1614 + 1091.5 + 1616 @@ -1180,14 +1192,14 @@ class Beam_fromCurve(component): - 1054 - 1624 + 1053 + 1626 74 20 - 1092.5 - 1634 + 1091.5 + 1636 @@ -1210,14 +1222,14 @@ class Beam_fromCurve(component): - 1054 - 1644 + 1053 + 1646 74 20 - 1092.5 - 1654 + 1091.5 + 1656 @@ -1236,14 +1248,14 @@ class Beam_fromCurve(component): - 1158 - 1544 + 1157 + 1546 37 60 - 1176.5 - 1574 + 1175.5 + 1576 @@ -1262,14 +1274,14 @@ class Beam_fromCurve(component): - 1158 - 1604 + 1157 + 1606 37 60 - 1176.5 - 1634 + 1175.5 + 1636 @@ -1289,11 +1301,11 @@ class Beam_fromCurve(component): - + from compas_timber._fabrication import Lap from compas_timber._fabrication import JackRafterCut from compas_timber.connections.utilities import beam_ref_side_incidence -from compas_rhino.conversions import frame_to_rhino, brep_to_rhino, plane_to_rhino +from compas_rhino.conversions import frame_to_rhino, brep_to_rhino, plane_to_rhino, box_to_rhino from compas_rhino import unload_modules unload_modules('compas_timber') @@ -1307,9 +1319,11 @@ main_ref_side_index = min(main_ref_side_dict, key=main_ref_side_dict.get) main_ref_side_indices = main_ref_side_index, (main_ref_side_index+2)%4 main_plane = main_beam.ref_sides[main_ref_side_index] lap_width = main_beam.height if main_ref_side_index % 2 == 0 else main_beam.width +print(lap_width) # instanciate lap processing from plane and beam lap = Lap.from_plane_and_beam(main_plane, cross_beam, lap_width, depth, cross_ref_side_index) +lap_frame = lap.planes_from_params_and_beam(cross_beam) volume = lap.volume_from_params_and_beam(cross_beam) cross_geo = cross_beam.compute_geometry() cross_geo = lap.apply(cross_geo, cross_beam) @@ -1324,7 +1338,7 @@ jack_cut = JackRafterCut.from_plane_and_beam(cross_plane, main_beam, main_ref_si cutting_plane_main = jack_cut.plane_from_params_and_beam(main_beam) main_geo = main_beam.compute_geometry() main_geo = jack_cut.apply(main_geo, main_beam) - +lap_frame = [plane_to_rhino(frame) for frame in lap_frame] # viz frames cross_ref_side = frame_to_rhino(cross_beam.ref_sides[cross_ref_side_index]) @@ -1334,16 +1348,17 @@ cutting_plane_main = plane_to_rhino(cutting_plane_main) # viz volumes rg_cross = brep_to_rhino(cross_geo) rg_main = brep_to_rhino(main_geo) -#rg_volume = brep_to_rhino(volume) +rg_volume = brep_to_rhino(volume) GhPython provides a Python script component - 2335 - 49 + 1181 + 104 1298 1537 + true true false false @@ -1358,23 +1373,23 @@ rg_main = brep_to_rhino(main_geo) 1742 - 1134 + 1123 202 - 144 + 164 1823 - 1206 + 1205 - + 3 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 84fa917c-1ed8-4db3-8be1-7bdc4a6495a2 - 7 + 8 3ede854e-c753-40eb-84cb-b48008f14fd4 8ec86459-bf01-4409-baee-174d0d2b13d0 8ec86459-bf01-4409-baee-174d0d2b13d0 @@ -1382,8 +1397,9 @@ rg_main = brep_to_rhino(main_geo) 8ec86459-bf01-4409-baee-174d0d2b13d0 8ec86459-bf01-4409-baee-174d0d2b13d0 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 - + true @@ -1403,13 +1419,13 @@ rg_main = brep_to_rhino(main_geo) 1744 - 1136 + 1125 64 - 46 + 53 1777.5 - 1159.333 + 1151.667 @@ -1434,13 +1450,13 @@ rg_main = brep_to_rhino(main_geo) 1744 - 1182 + 1178 64 - 47 + 53 1777.5 - 1206 + 1205 @@ -1465,13 +1481,13 @@ rg_main = brep_to_rhino(main_geo) 1744 - 1229 + 1231 64 - 47 + 54 1777.5 - 1252.667 + 1258.333 @@ -1491,13 +1507,13 @@ rg_main = brep_to_rhino(main_geo) 1838 - 1136 + 1125 104 20 1890 - 1146 + 1135 @@ -1517,13 +1533,13 @@ rg_main = brep_to_rhino(main_geo) 1838 - 1156 + 1145 104 20 1890 - 1166 + 1155 @@ -1543,13 +1559,13 @@ rg_main = brep_to_rhino(main_geo) 1838 - 1176 + 1165 104 20 1890 - 1186 + 1175 @@ -1569,13 +1585,13 @@ rg_main = brep_to_rhino(main_geo) 1838 - 1196 + 1185 104 20 1890 - 1206 + 1195 @@ -1595,13 +1611,13 @@ rg_main = brep_to_rhino(main_geo) 1838 - 1216 + 1205 104 20 1890 - 1226 + 1215 @@ -1621,13 +1637,13 @@ rg_main = brep_to_rhino(main_geo) 1838 - 1236 + 1225 104 20 1890 - 1246 + 1235 @@ -1647,13 +1663,39 @@ rg_main = brep_to_rhino(main_geo) 1838 - 1256 + 1245 + 104 + 20 + + + 1890 + 1255 + + + + + + + + Script output lap_frame. + 502370ce-eac6-4444-b153-000dda5f7ad2 + lap_frame + lap_frame + false + 0 + + + + + + 1838 + 1265 104 20 1890 - 1266 + 1275 @@ -1672,11 +1714,10 @@ rg_main = brep_to_rhino(main_geo) - + Contains a collection of Breps (Boundary REPresentations) true 9381ae59-064b-40ba-81bc-88ceddf37ae8 - true Brep Brep false @@ -1687,13 +1728,13 @@ rg_main = brep_to_rhino(main_geo) - 1985 + 1984 1201 50 24 - 2010.016 + 2009.32 1213.15 @@ -1710,7 +1751,7 @@ rg_main = brep_to_rhino(main_geo) - + from compas.scene import Scene from compas.tolerance import TOL from ghpythonlib.componentbase import executingcomponent as component @@ -1913,6 +1954,7 @@ class ModelComponent(component): 835 1486 + true true true @@ -2219,7 +2261,7 @@ class ModelComponent(component): - 1698 + 1697 1554 104 22 @@ -2458,7 +2500,7 @@ class WriteBTLx(component): - 1968 + 1967 1617 258 116 @@ -2467,7 +2509,7 @@ class WriteBTLx(component): 0 0 - 1968.6 + 1967.904 1617.126 @@ -2509,7 +2551,7 @@ class WriteBTLx(component): - 2122 + 2121 1741 104 22 @@ -2538,13 +2580,13 @@ class WriteBTLx(component): - 633 + 632 1438 66 64 - 665 + 664 1470 @@ -2563,13 +2605,13 @@ class WriteBTLx(component): - 635 + 634 1440 15 20 - 644 + 643 1450 @@ -2590,13 +2632,13 @@ class WriteBTLx(component): - 635 + 634 1460 15 20 - 644 + 643 1470 @@ -2616,13 +2658,13 @@ class WriteBTLx(component): - 635 + 634 1480 15 20 - 644 + 643 1490 @@ -2662,13 +2704,13 @@ class WriteBTLx(component): - 680 + 679 1440 17 30 - 688.5 + 687.5 1455 @@ -2688,13 +2730,13 @@ class WriteBTLx(component): - 680 + 679 1470 17 30 - 688.5 + 687.5 1485 @@ -2724,13 +2766,13 @@ class WriteBTLx(component): - 1341 + 1340 1436 - 192 + 184 20 - 1341.447 + 1340.726 1436.466 @@ -2743,7 +2785,7 @@ class WriteBTLx(component): 100 0 0 - 10 + 20 @@ -2817,13 +2859,13 @@ class WriteBTLx(component): - 723 + 722 1423 61 44 - 770 + 769 1445 @@ -2842,13 +2884,13 @@ class WriteBTLx(component): - 725 + 724 1425 30 20 - 749.5 + 748.5 1435 @@ -2870,13 +2912,13 @@ class WriteBTLx(component): - 725 + 724 1445 30 20 - 749.5 + 748.5 1455 @@ -2905,13 +2947,13 @@ class WriteBTLx(component): - 632 + 631 1371 66 64 - 663 + 662 1403 @@ -2930,13 +2972,13 @@ class WriteBTLx(component): - 634 + 633 1373 14 30 - 642.5 + 641.5 1388 @@ -2957,13 +2999,13 @@ class WriteBTLx(component): - 634 + 633 1403 14 30 - 642.5 + 641.5 1418 @@ -2983,13 +3025,13 @@ class WriteBTLx(component): - 678 + 677 1373 18 20 - 687 + 686 1383 @@ -3009,13 +3051,13 @@ class WriteBTLx(component): - 678 + 677 1393 18 20 - 687 + 686 1403 @@ -3035,13 +3077,13 @@ class WriteBTLx(component): - 678 + 677 1413 18 20 - 687 + 686 1423 @@ -3061,8 +3103,8 @@ class WriteBTLx(component): Contains a collection of three-dimensional axis-systems + true 00dffbd2-d634-4815-8e48-989dfd6df288 - true Plane Pln false @@ -3073,13 +3115,13 @@ class WriteBTLx(component): - 1999 + 1998 1175 50 24 - 2024.303 + 2023.607 1187.121 @@ -3095,11 +3137,10 @@ class WriteBTLx(component): - + Contains a collection of three-dimensional axis-systems true 4d439952-5ee0-415f-bf9a-9558d86e4efd - true Plane Pln false @@ -3110,13 +3151,13 @@ class WriteBTLx(component): - 1988 + 1987 1139 50 24 - 2013.117 + 2012.421 1151.553 @@ -3132,11 +3173,10 @@ class WriteBTLx(component): - + Contains a collection of Breps (Boundary REPresentations) true a6425828-9538-4783-86e1-0c73c651f302 - true Brep Brep false @@ -3147,13 +3187,13 @@ class WriteBTLx(component): - 1982 + 1981 1229 50 24 - 2007.312 + 2006.616 1241.926 @@ -3183,13 +3223,13 @@ class WriteBTLx(component): - 1990 + 1989 1288 50 24 - 2015.435 + 2014.739 1300.79 @@ -3205,11 +3245,10 @@ class WriteBTLx(component): - + Contains a collection of three-dimensional axis-systems true 9f99b4bb-612a-492f-b8e1-a954c0e0a960 - true Plane Pln false @@ -3220,13 +3259,13 @@ class WriteBTLx(component): - 1990 + 1989 1261 50 24 - 2015.403 + 2014.707 1273.677 @@ -3253,13 +3292,13 @@ class WriteBTLx(component): - -128 + -129 1505 64 44 - -97 + -98 1527 @@ -3278,13 +3317,13 @@ class WriteBTLx(component): - -126 + -127 1507 14 40 - -117.5 + -118.5 1527 @@ -3304,13 +3343,13 @@ class WriteBTLx(component): - -82 + -83 1507 16 20 - -74 + -75 1517 @@ -3330,13 +3369,13 @@ class WriteBTLx(component): - -82 + -83 1527 16 20 - -74 + -75 1537 @@ -3365,13 +3404,13 @@ class WriteBTLx(component): - -42 + -43 1552 83 44 - 6 + 5 1574 @@ -3390,13 +3429,13 @@ class WriteBTLx(component): - -40 + -41 1554 31 20 - -15 + -16 1564 @@ -3418,13 +3457,13 @@ class WriteBTLx(component): - -40 + -41 1574 31 20 - -15 + -16 1584 @@ -3468,13 +3507,13 @@ class WriteBTLx(component): - 21 + 20 1554 18 20 - 30 + 29 1564 @@ -3494,13 +3533,13 @@ class WriteBTLx(component): - 21 + 20 1574 18 20 - 30 + 29 1584 @@ -3529,21 +3568,22 @@ class WriteBTLx(component): - -214 + -231 1550 - 63 + 79 28 - -185 + -186 1564 - + Unit multiplication a7304964-d322-488c-865b-62f9088e559b + -x Factor F false @@ -3554,13 +3594,13 @@ class WriteBTLx(component): - -212 + -229 1552 - 12 + 28 24 - -204.5 + -205.5 1564 @@ -3600,13 +3640,13 @@ class WriteBTLx(component): - -170 + -171 1552 17 24 - -161.5 + -162.5 1564 @@ -3642,7 +3682,7 @@ class WriteBTLx(component): 20 - -399.0349 + -399.7307 1558.41 @@ -3655,7 +3695,7 @@ class WriteBTLx(component): 4000 0 0 - 2000 + 1000 @@ -3680,13 +3720,13 @@ class WriteBTLx(component): - 66 + 65 1532 63 44 - 97 + 96 1554 @@ -3705,13 +3745,13 @@ class WriteBTLx(component): - 68 + 67 1534 14 20 - 76.5 + 75.5 1544 @@ -3732,13 +3772,13 @@ class WriteBTLx(component): - 68 + 67 1554 14 20 - 76.5 + 75.5 1564 @@ -3758,13 +3798,13 @@ class WriteBTLx(component): - 112 + 111 1534 15 40 - 119.5 + 118.5 1554 @@ -3793,21 +3833,22 @@ class WriteBTLx(component): - -212 + -229 1584 - 63 + 79 28 - -183 + -184 1598 - + Unit multiplication 827ec58a-dd71-44e4-b991-26acf25b63f9 + -x Factor F false @@ -3818,13 +3859,13 @@ class WriteBTLx(component): - -210 + -227 1586 - 12 + 28 24 - -202.5 + -203.5 1598 @@ -3864,13 +3905,13 @@ class WriteBTLx(component): - -168 + -169 1586 17 24 - -159.5 + -160.5 1598 @@ -3900,13 +3941,13 @@ class WriteBTLx(component): - -401 + -402 1593 163 20 - -400.5624 + -401.2582 1593.316 @@ -3919,7 +3960,7 @@ class WriteBTLx(component): 2000 0 0 - 2000 + 0 @@ -3944,13 +3985,13 @@ class WriteBTLx(component): - -126 + -127 1552 65 64 - -95 + -96 1584 @@ -3979,13 +4020,13 @@ class WriteBTLx(component): - -124 + -125 1554 14 20 - -115.5 + -116.5 1564 @@ -4006,13 +4047,13 @@ class WriteBTLx(component): - -124 + -125 1574 14 20 - -115.5 + -116.5 1584 @@ -4033,13 +4074,13 @@ class WriteBTLx(component): - -124 + -125 1594 14 20 - -115.5 + -116.5 1604 @@ -4059,13 +4100,13 @@ class WriteBTLx(component): - -80 + -81 1554 17 60 - -71.5 + -72.5 1584 @@ -4126,13 +4167,13 @@ class WriteBTLx(component): - -227 + -228 1618 79 28 - -182 + -183 1632 @@ -4152,13 +4193,13 @@ class WriteBTLx(component): - -225 + -226 1620 28 24 - -201.5 + -202.5 1632 @@ -4198,13 +4239,13 @@ class WriteBTLx(component): - -167 + -168 1620 17 24 - -158.5 + -159.5 1632 @@ -4234,13 +4275,13 @@ class WriteBTLx(component): - -395 + -396 1626 163 20 - -394.9014 + -395.5972 1626.688 @@ -4286,7 +4327,7 @@ class WriteBTLx(component): 24 - -176.2584 + -176.9542 1527.093 @@ -4337,13 +4378,13 @@ class WriteBTLx(component): - 245 + 244 1522 77 64 - 277 + 276 1554 @@ -4372,13 +4413,13 @@ class WriteBTLx(component): - 247 + 246 1524 15 20 - 256 + 255 1534 @@ -4421,13 +4462,13 @@ class WriteBTLx(component): - 247 + 246 1544 15 20 - 256 + 255 1554 @@ -4450,13 +4491,13 @@ class WriteBTLx(component): - 247 + 246 1564 15 20 - 256 + 255 1574 @@ -4478,13 +4519,13 @@ class WriteBTLx(component): - 292 + 291 1524 28 60 - 306 + 305 1554 @@ -4515,13 +4556,13 @@ class WriteBTLx(component): - 157 + 156 1562 66 44 - 189 + 188 1584 @@ -4540,13 +4581,13 @@ class WriteBTLx(component): - 159 + 158 1564 15 20 - 168 + 167 1574 @@ -4566,13 +4607,13 @@ class WriteBTLx(component): - 159 + 158 1584 15 20 - 168 + 167 1594 @@ -4592,13 +4633,13 @@ class WriteBTLx(component): - 204 + 203 1564 17 20 - 212.5 + 211.5 1574 @@ -4618,13 +4659,13 @@ class WriteBTLx(component): - 204 + 203 1584 17 20 - 212.5 + 211.5 1594 @@ -4655,7 +4696,7 @@ class WriteBTLx(component): - -229 + -230 1442 104 22 @@ -4712,14 +4753,14 @@ class WriteBTLx(component): - 481 - 1542 + 472 + 1545 50 24 - 506.3529 - 1554.078 + 497.7238 + 1557.053 @@ -4745,13 +4786,13 @@ class WriteBTLx(component): - 245 + 244 1287 77 64 - 277 + 276 1319 @@ -4780,13 +4821,13 @@ class WriteBTLx(component): - 247 + 246 1289 15 20 - 256 + 255 1299 @@ -4829,13 +4870,13 @@ class WriteBTLx(component): - 247 + 246 1309 15 20 - 256 + 255 1319 @@ -4858,13 +4899,13 @@ class WriteBTLx(component): - 247 + 246 1329 15 20 - 256 + 255 1339 @@ -4886,13 +4927,13 @@ class WriteBTLx(component): - 292 + 291 1289 28 60 - 306 + 305 1319 @@ -4923,13 +4964,13 @@ class WriteBTLx(component): - 167 + 166 1327 66 44 - 199 + 198 1349 @@ -4948,13 +4989,13 @@ class WriteBTLx(component): - 169 + 168 1329 15 20 - 178 + 177 1339 @@ -4974,13 +5015,13 @@ class WriteBTLx(component): - 169 + 168 1349 15 20 - 178 + 177 1359 @@ -5000,13 +5041,13 @@ class WriteBTLx(component): - 214 + 213 1329 17 20 - 222.5 + 221.5 1339 @@ -5026,13 +5067,13 @@ class WriteBTLx(component): - 214 + 213 1349 17 20 - 222.5 + 221.5 1359 @@ -5063,7 +5104,7 @@ class WriteBTLx(component): - 31 + 30 1236 104 22 @@ -5126,7 +5167,7 @@ class WriteBTLx(component): 24 - 503.8039 + 503.1081 1319.313 @@ -5173,7 +5214,7 @@ class WriteBTLx(component): Panel false - 0.64158892631530762 + 0 457734f7-3478-4434-a17d-636820c0980c 1 Double click to edit panel content… @@ -5182,7 +5223,7 @@ class WriteBTLx(component): - 1743 + 1742 1079 201 53 @@ -5191,7 +5232,7 @@ class WriteBTLx(component): 0 0 - 1743.429 + 1742.733 1079.09 @@ -5226,7 +5267,7 @@ class WriteBTLx(component): Panel false - 0 + 1 5946e9dc-f13c-4e3c-8873-1cc4030c843d 1 Double click to edit panel content… @@ -5273,9 +5314,10 @@ class WriteBTLx(component): - + 0 Retrieve a specific item from a list. + true 6758fb57-bfa5-452b-afb1-29c442b80cae List Item Item @@ -5284,13 +5326,13 @@ class WriteBTLx(component): - 387 + 386 1770 64 64 - 421 + 420 1802 @@ -5320,13 +5362,13 @@ class WriteBTLx(component): - 389 + 388 1772 17 20 - 399 + 398 1782 @@ -5347,13 +5389,13 @@ class WriteBTLx(component): - 389 + 388 1792 17 20 - 399 + 398 1802 @@ -5393,13 +5435,13 @@ class WriteBTLx(component): - 389 + 388 1812 17 20 - 399 + 398 1822 @@ -5440,13 +5482,13 @@ class WriteBTLx(component): - 436 + 435 1772 13 60 - 442.5 + 441.5 1802 @@ -5485,7 +5527,7 @@ class WriteBTLx(component): 24 - 270.9937 + 270.2979 1782.105 @@ -5564,13 +5606,13 @@ class WriteBTLx(component): - 196 + 195 1832 160 20 - 196.1492 + 195.4534 1832.229 @@ -5583,7 +5625,7 @@ class WriteBTLx(component): 3 0 0 - 2 + 3 @@ -5644,13 +5686,13 @@ print(centerline.length) - 109 + 108 511 128 124 - 138 + 137 573 @@ -5686,13 +5728,13 @@ print(centerline.length) - 111 + 110 513 12 60 - 118.5 + 117.5 543 @@ -5716,13 +5758,13 @@ print(centerline.length) - 111 + 110 573 12 60 - 118.5 + 117.5 603 @@ -5742,13 +5784,13 @@ print(centerline.length) - 153 + 152 513 82 20 - 194 + 193 523 @@ -5768,13 +5810,13 @@ print(centerline.length) - 153 + 152 533 82 20 - 194 + 193 543 @@ -5794,13 +5836,13 @@ print(centerline.length) - 153 + 152 553 82 20 - 194 + 193 563 @@ -5820,13 +5862,13 @@ print(centerline.length) - 153 + 152 573 82 20 - 194 + 193 583 @@ -5846,13 +5888,13 @@ print(centerline.length) - 153 + 152 593 82 20 - 194 + 193 603 @@ -5872,13 +5914,13 @@ print(centerline.length) - 153 + 152 613 82 20 - 194 + 193 623 @@ -5918,7 +5960,7 @@ print(centerline.length) 24 - 446.9046 + 446.2088 739.606 @@ -5945,13 +5987,13 @@ print(centerline.length) - 380 + 379 494 64 64 - 411 + 410 526 @@ -5970,13 +6012,13 @@ print(centerline.length) - 382 + 381 496 14 20 - 390.5 + 389.5 506 @@ -6026,13 +6068,13 @@ print(centerline.length) - 382 + 381 516 14 20 - 390.5 + 389.5 526 @@ -6075,13 +6117,13 @@ print(centerline.length) - 382 + 381 536 14 20 - 390.5 + 389.5 546 @@ -6124,13 +6166,13 @@ print(centerline.length) - 426 + 425 496 16 60 - 434 + 433 526 @@ -6159,13 +6201,13 @@ print(centerline.length) - 424 + 423 580 68 44 - 456 + 455 602 @@ -6184,13 +6226,13 @@ print(centerline.length) - 426 + 425 582 15 20 - 435 + 434 592 @@ -6211,13 +6253,13 @@ print(centerline.length) - 426 + 425 602 15 20 - 435 + 434 612 @@ -6237,13 +6279,13 @@ print(centerline.length) - 471 + 470 582 19 40 - 480.5 + 479.5 602 @@ -6275,13 +6317,13 @@ print(centerline.length) - 304 + 303 607 50 24 - 329.013 + 328.3172 619.2989 @@ -6311,13 +6353,13 @@ print(centerline.length) - 302 + 301 572 50 24 - 327.621 + 326.9252 584.1504 @@ -6383,13 +6425,13 @@ print(centerline.length) - 114 + 113 203 128 124 - 143 + 142 265 @@ -6425,13 +6467,13 @@ print(centerline.length) - 116 + 115 205 12 60 - 123.5 + 122.5 235 @@ -6455,13 +6497,13 @@ print(centerline.length) - 116 + 115 265 12 60 - 123.5 + 122.5 295 @@ -6481,13 +6523,13 @@ print(centerline.length) - 158 + 157 205 82 20 - 199 + 198 215 @@ -6507,13 +6549,13 @@ print(centerline.length) - 158 + 157 225 82 20 - 199 + 198 235 @@ -6533,13 +6575,13 @@ print(centerline.length) - 158 + 157 245 82 20 - 199 + 198 255 @@ -6559,13 +6601,13 @@ print(centerline.length) - 158 + 157 265 82 20 - 199 + 198 275 @@ -6585,13 +6627,13 @@ print(centerline.length) - 158 + 157 285 82 20 - 199 + 198 295 @@ -6611,13 +6653,13 @@ print(centerline.length) - 158 + 157 305 82 20 - 199 + 198 315 @@ -6651,13 +6693,13 @@ print(centerline.length) - 432 + 431 415 50 24 - 457.2508 + 456.555 427.7627 @@ -6684,13 +6726,13 @@ print(centerline.length) - 389 + 388 181 64 64 - 420 + 419 213 @@ -6709,13 +6751,13 @@ print(centerline.length) - 391 + 390 183 14 20 - 399.5 + 398.5 193 @@ -6765,13 +6807,13 @@ print(centerline.length) - 391 + 390 203 14 20 - 399.5 + 398.5 213 @@ -6814,13 +6856,13 @@ print(centerline.length) - 391 + 390 223 14 20 - 399.5 + 398.5 233 @@ -6863,13 +6905,13 @@ print(centerline.length) - 435 + 434 183 16 60 - 443 + 442 213 @@ -6898,13 +6940,13 @@ print(centerline.length) - 433 + 432 267 68 44 - 465 + 464 289 @@ -6923,13 +6965,13 @@ print(centerline.length) - 435 + 434 269 15 20 - 444 + 443 279 @@ -6950,13 +6992,13 @@ print(centerline.length) - 435 + 434 289 15 20 - 444 + 443 299 @@ -6976,13 +7018,13 @@ print(centerline.length) - 480 + 479 269 19 40 - 489.5 + 488.5 289 @@ -7014,13 +7056,13 @@ print(centerline.length) - 314 + 313 295 50 24 - 339.3595 + 338.6637 307.4556 @@ -7056,7 +7098,7 @@ print(centerline.length) 24 - 337.9674 + 337.2716 272.3071 @@ -7134,7 +7176,7 @@ print(centerline.length) - + import inspect from ghpythonlib.componentbase import executingcomponent as component @@ -7196,6 +7238,7 @@ class T_TopologyJointRule(component): ghenv.Component.ExpireSolution(True) GhPython provides a Python script component + true true true @@ -7212,13 +7255,13 @@ class T_TopologyJointRule(component): - 1621 + 1620 1479 151 28 - 1696 + 1695 1493 @@ -7245,13 +7288,13 @@ class T_TopologyJointRule(component): - 1623 + 1622 1481 58 24 - 1653.5 + 1652.5 1493 @@ -7271,13 +7314,13 @@ class T_TopologyJointRule(component): - 1711 + 1710 1481 59 24 - 1740.5 + 1739.5 1493 @@ -7310,13 +7353,13 @@ class T_TopologyJointRule(component): - 253 + 252 1891 50 24 - 278.6859 + 277.9901 1903.796 @@ -7357,7 +7400,7 @@ class T_TopologyJointRule(component): - + import inspect from ghpythonlib.componentbase import executingcomponent as component @@ -7419,10 +7462,11 @@ class L_TopologyJointRule(component): ghenv.Component.ExpireSolution(True) GhPython provides a Python script component + true true true - iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDwAACw8BkvkDpQAAANBJREFUSEvtk70NwjAQhTNCBrDkk7wAo9DQMwIjMIHFCIyQEVK4TMEIjMAGRif5JOvlcEwwiCLFV8Q/75PeOV2MsfsmswXGWRqdpZi44P475KHkLPXO0jkLF054sRYJ3yuhGndnaYchJUTAFzHsFQOGlBBB3jlzTXVxbTfYGzGkhAgGCOnlAFfSQoCDbS7gSvIQ/uZ6mlWEMyixasi1z/Sx6pkmCXetVcIc8aLxYTI+ROPDAfdUQQ7MRP2LPxLUsAkW2QSL/FKgMf2/oJbZQmue50vIddCJVREAAAAASUVORK5CYII= + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDAAACwwBP0AiyAAAANBJREFUSEvtk70NwjAQhTNCBrDkk7wAo9DQMwIjMIHFCIyQEVK4TMEIjMAGRif5JOvlcEwwiCLFV8Q/75PeOV2MsfsmswXGWRqdpZi44P475KHkLPXO0jkLF054sRYJ3yuhGndnaYchJUTAFzHsFQOGlBBB3jlzTXVxbTfYGzGkhAgGCOnlAFfSQoCDbS7gSvIQ/uZ6mlWEMyixasi1z/Sx6pkmCXetVcIc8aLxYTI+ROPDAfdUQQ7MRP2LPxLUsAkW2QSL/FKgMf2/oJbZQmue50vIddCJVREAAAAASUVORK5CYII= false 0944aec4-9862-4758-8cf6-b1210d2c9ec3 @@ -7435,14 +7479,14 @@ class L_TopologyJointRule(component): - 1603 - 1370 + 1600 + 1342 185 84 - 1713 - 1412 + 1710 + 1384 @@ -7471,14 +7515,14 @@ class L_TopologyJointRule(component): - 1605 - 1372 + 1602 + 1344 93 20 - 1653 - 1382 + 1650 + 1354 @@ -7497,14 +7541,14 @@ class L_TopologyJointRule(component): - 1605 - 1392 + 1602 + 1364 93 20 - 1653 - 1402 + 1650 + 1374 @@ -7523,14 +7567,14 @@ class L_TopologyJointRule(component): - 1605 - 1412 + 1602 + 1384 93 20 - 1653 - 1422 + 1650 + 1394 @@ -7549,14 +7593,14 @@ class L_TopologyJointRule(component): - 1605 - 1432 + 1602 + 1404 93 20 - 1653 - 1442 + 1650 + 1414 @@ -7575,14 +7619,14 @@ class L_TopologyJointRule(component): - 1728 - 1372 + 1725 + 1344 58 80 - 1757 - 1412 + 1754 + 1384 @@ -7613,13 +7657,13 @@ class L_TopologyJointRule(component): - 1610 + 1611 1530 195 20 - 1610.072 + 1611.303 1530.793 @@ -7632,7 +7676,7 @@ class L_TopologyJointRule(component): 100 0 0 - 30 + 50 @@ -7646,8 +7690,9 @@ class L_TopologyJointRule(component): - + Merge a bunch of data streams + true 50b4d783-2e1c-4a34-ba17-9ce6f72fbd7b Merge Merge @@ -7656,13 +7701,13 @@ class L_TopologyJointRule(component): - 379 + 378 1871 88 64 - 417 + 416 1903 @@ -7693,13 +7738,13 @@ class L_TopologyJointRule(component): - 381 + 380 1873 21 20 - 393 + 392 1883 @@ -7722,13 +7767,13 @@ class L_TopologyJointRule(component): - 381 + 380 1893 21 20 - 393 + 392 1903 @@ -7750,13 +7795,13 @@ class L_TopologyJointRule(component): - 381 + 380 1913 21 20 - 393 + 392 1923 @@ -7778,13 +7823,13 @@ class L_TopologyJointRule(component): - 432 + 431 1873 33 60 - 440.5 + 439.5 1903 @@ -7797,6 +7842,2623 @@ class L_TopologyJointRule(component): + + + eeafc956-268e-461d-8e73-ee05c6f72c01 + Stream Filter + + + + + Filters a collection of input streams + true + 21576f20-ed8e-4c3d-ac74-f4c6622726c1 + Stream Filter + Filter + + + + + + 631 + 1582 + 77 + 64 + + + 663 + 1614 + + + + + + 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 + 5654947d-2496-4fda-aa9d-464d6556a204 + Gate + G + false + 1af3d673-3732-4271-8c59-52c07949abb2 + 1 + + + + + + 633 + 1584 + 15 + 20 + + + 642 + 1594 + + + + + + 1 + + + + + 1 + {0} + + + + + 0 + + + + + + + + + + + 2 + Input stream at index 0 + 754ae8d9-c10a-4fd4-9620-5e5a7054c6f6 + false + Stream 0 + 0 + true + 0bc447d1-0b47-4d44-88e5-1c1a33db80fc + 1 + + + + + + 633 + 1604 + 15 + 20 + + + 642 + 1614 + + + + + + + + 2 + Input stream at index 1 + d482ec97-792e-4152-90f0-4e5000ccf833 + false + Stream 1 + 1 + true + 353d7621-3cd9-4d30-ac48-2ba4334ede62 + 1 + + + + + + 633 + 1624 + 15 + 20 + + + 642 + 1634 + + + + + + + + 2 + Filtered stream + 5e8cc817-6357-4d57-8f5c-bb9d6ce9669c + false + Stream + S(1) + false + 0 + + + + + + 678 + 1584 + 28 + 60 + + + 692 + 1614 + + + + + + + + + + + + + + 22990b1f-9be6-477c-ad89-f775cd347105 + Flip Curve + + + + + Flip a curve using an optional guide curve. + true + 016c3b71-d2f1-4a70-be07-535a52db8a79 + Flip Curve + Flip + + + + + + 543 + 1622 + 66 + 44 + + + 575 + 1644 + + + + + + Curve to flip + 603a86f3-4555-4985-bfc4-a523884cf415 + Curve + C + false + 0bc447d1-0b47-4d44-88e5-1c1a33db80fc + 1 + + + + + + 545 + 1624 + 15 + 20 + + + 554 + 1634 + + + + + + + + Optional guide curve + e612372e-97c9-4956-83c1-7c0c87cd1d5f + Guide + G + true + 0 + + + + + + 545 + 1644 + 15 + 20 + + + 554 + 1654 + + + + + + + + Flipped curve + 353d7621-3cd9-4d30-ac48-2ba4334ede62 + Curve + C + false + 0 + + + + + + 590 + 1624 + 17 + 20 + + + 598.5 + 1634 + + + + + + + + Flip action + 3a7a88e4-e7f8-460d-ab66-2f6e0538533a + Flag + F + false + 0 + + + + + + 590 + 1644 + 17 + 20 + + + 598.5 + 1654 + + + + + + + + + + + + 28061aae-04fb-4cb5-ac45-16f3b66bc0a4 + Center Box + + + + + Create a box centered on a plane. + true + b914bb37-7b5a-46a9-a1fb-6b230797d09b + Center Box + Box + + + + + + 2280 + 1273 + 64 + 84 + + + 2311 + 1315 + + + + + + Base plane + 65a01b3f-9641-4df1-a831-f0ac4ddbc0c8 + Base + B + false + 0 + + + + + + 2282 + 1275 + 14 + 20 + + + 2290.5 + 1285 + + + + + + 1 + + + + + 1 + {0} + + + + + + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 0 + + + + + + + + + + + + Size of box in {x} direction. + be885264-6145-4d8b-84ce-3158683407e8 + X + X + false + 0 + + + + + + 2282 + 1295 + 14 + 20 + + + 2290.5 + 1305 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + Size of box in {y} direction. + b9985f26-d4b2-41fe-9963-3b6ad745502d + Y + Y + false + 0 + + + + + + 2282 + 1315 + 14 + 20 + + + 2290.5 + 1325 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + Size of box in {z} direction. + 7c5c3d48-fede-49be-a769-5b4472f051c3 + Z + Z + false + 0 + + + + + + 2282 + 1335 + 14 + 20 + + + 2290.5 + 1345 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + Resulting box + 9db4d62a-2de4-4b49-b116-d91d1fb3fbc1 + Box + B + false + 0 + + + + + + 2326 + 1275 + 16 + 80 + + + 2334 + 1315 + + + + + + + + + + + + 7c0523e8-79c9-45a2-8777-cf0d46bc5432 + Volume + + + + + Solve volume properties for closed breps and meshes. + true + 87adbf20-4372-408f-afd2-8de0206c319f + Volume + Volume + + + + + + 2086 + 1278 + 66 + 44 + + + 2118 + 1300 + + + + + + 1 + ac2bc2cb-70fb-4dd5-9c78-7e1ea97fe278 + 2 + 3e8ca6be-fda8-4aaf-b5c0-3c54c8bb7312 + fbac3e32-f100-4292-8692-77240a42fd1a + + + + + Closed brep or mesh for volume computation + 6653e031-0118-4585-9a5a-3c23bddc56e5 + Geometry + G + false + b3588f0a-414e-4a3d-87e6-79cde3ae279b + 1 + + + + + + 2088 + 1280 + 15 + 40 + + + 2097 + 1300 + + + + + + + + Volume of geometry + 84b4bf60-4f2c-410a-abe2-275faa062d0f + Volume + V + true + 0 + + + + + + 2133 + 1280 + 17 + 20 + + + 2141.5 + 1290 + + + + + + + + Volume centroid of geometry + c4969727-a064-4123-ae15-e612faccd299 + Centroid + C + true + 0 + + + + + + 2133 + 1300 + 17 + 20 + + + 2141.5 + 1310 + + + + + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + true + 63256972-bef4-4fe4-b953-922687ce5e7c + Plane + Pln + false + 502370ce-eac6-4444-b153-000dda5f7ad2 + 1 + + + + + + 1925 + 1329 + 50 + 24 + + + 1950.909 + 1341.371 + + + + + + + + + + 9103c240-a6a9-4223-9b42-dbd19bf38e2b + Unit Z + + + + + Unit vector parallel to the world {z} axis. + true + 69a1047d-365a-4696-ac21-d66891c3cd01 + Unit Z + Z + + + + + + 724 + 1525 + 63 + 28 + + + 753 + 1539 + + + + + + Unit multiplication + b815eb40-79e9-4d59-baf5-789e4733642b + Factor + F + false + 0 + + + + + + 726 + 1527 + 12 + 24 + + + 733.5 + 1539 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + World {z} vector + 7d37dfc6-37e3-4d21-81f9-feb71a9e7daf + Unit vector + V + false + 0 + + + + + + 768 + 1527 + 17 + 24 + + + 776.5 + 1539 + + + + + + + + + + + + 79f9fbb3-8f1d-4d9a-88a9-f7961b1012cd + Unit X + + + + + Unit vector parallel to the world {x} axis. + true + 0ee70b32-a69d-44dc-a0ef-f78b3accb6a6 + Unit X + X + + + + + + 744 + 1499 + 63 + 28 + + + 773 + 1513 + + + + + + Unit multiplication + 84f4a5d7-05c6-4323-aa8c-0f3aaafe88ab + Factor + F + false + 0 + + + + + + 746 + 1501 + 12 + 24 + + + 753.5 + 1513 + + + + + + 1 + + + + + 1 + {0} + + + + + 1 + + + + + + + + + + + World {x} vector + 985674c1-93e7-4bfa-a6b2-fa372e7b66a4 + Unit vector + V + false + 0 + + + + + + 788 + 1501 + 17 + 24 + + + 796.5 + 1513 + + + + + + + + + + + + a0d62394-a118-422d-abb3-6af115c75b25 + Addition + + + + + Mathematical addition + true + 3da3f49a-976d-4d1a-9944-be7361193132 + Addition + A+B + + + + + + 822 + 1499 + 65 + 44 + + + 853 + 1521 + + + + + + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + First item for addition + 5e583fc7-a180-47d9-806a-9671969c74cd + A + A + true + 985674c1-93e7-4bfa-a6b2-fa372e7b66a4 + 1 + + + + + + 824 + 1501 + 14 + 20 + + + 832.5 + 1511 + + + + + + + + Second item for addition + 1258f834-4314-420b-92a4-2ea8f8149a09 + B + B + true + 7d37dfc6-37e3-4d21-81f9-feb71a9e7daf + 1 + + + + + + 824 + 1521 + 14 + 20 + + + 832.5 + 1531 + + + + + + + + Result of addition + f0b81979-f783-450f-b862-9b53febcbe90 + Result + R + false + 0 + + + + + + 868 + 1501 + 17 + 40 + + + 876.5 + 1521 + + + + + + + + + + + + + + 59daf374-bc21-4a5e-8282-5504fb7ae9ae + List Item + + + + + 0 + Retrieve a specific item from a list. + true + 303f3345-f7ae-4d8a-a427-00633f5158c9 + List Item + Item + + + + + + 2261 + 1383 + 64 + 64 + + + 2295 + 1415 + + + + + + 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 + 02198cbf-b46c-409a-876a-ab7137c2c7c3 + List + L + false + 725ac33d-374c-4cc0-8f18-3e15182f1323 + 1 + + + + + + 2263 + 1385 + 17 + 20 + + + 2273 + 1395 + + + + + + + + Item index + a58fb182-d690-47fe-a531-395d7a504986 + Index + i + false + 0 + + + + + + 2263 + 1405 + 17 + 20 + + + 2273 + 1415 + + + + + + 1 + + + + + 1 + {0} + + + + + 0 + + + + + + + + + + + Wrap index to list bounds + cfacaccd-8c41-4b49-b062-31e2775b1e82 + Wrap + W + false + 0 + + + + + + 2263 + 1425 + 17 + 20 + + + 2273 + 1435 + + + + + + 1 + + + + + 1 + {0} + + + + + true + + + + + + + + + + + Item at {i'} + 53f6b1d1-c010-483d-b5aa-7c83fc14f9b6 + false + Item + i + false + 0 + + + + + + 2310 + 1385 + 13 + 60 + + + 2316.5 + 1415 + + + + + + + + + + + + + + 59daf374-bc21-4a5e-8282-5504fb7ae9ae + List Item + + + + + 0 + Retrieve a specific item from a list. + true + cf135943-38be-4663-ab8d-0adadaca146c + List Item + Item + + + + + + 2029 + 1336 + 74 + 84 + + + 2063 + 1378 + + + + + + 3 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 2e3ab970-8545-46bb-836c-1c11e5610bce + cb95db89-6165-43b6-9c41-5702bc5bf137 + 4 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + 1 + Base list + 1522da3f-4c46-480e-9c80-988a4ab52cfd + List + L + false + 63256972-bef4-4fe4-b953-922687ce5e7c + 1 + + + + + + 2031 + 1338 + 17 + 26 + + + 2041 + 1351.333 + + + + + + + + Item index + 4b65d389-6766-4850-b495-164cce1c42de + Index + i + false + 0 + + + + + + 2031 + 1364 + 17 + 27 + + + 2041 + 1378 + + + + + + 1 + + + + + 1 + {0} + + + + + 0 + + + + + + + + + + + Wrap index to list bounds + b48d0cca-8f71-499e-a857-6d7f31da9575 + Wrap + W + false + 0 + + + + + + 2031 + 1391 + 17 + 27 + + + 2041 + 1404.667 + + + + + + 1 + + + + + 1 + {0} + + + + + true + + + + + + + + + + + Item at {i'} + 6a0268e5-201a-44ec-89af-473ef88f105f + false + Item + i + false + 0 + + + + + + 2078 + 1338 + 23 + 20 + + + 2089.5 + 1348 + + + + + + + + Item at {+1'} + d6d70e65-f2b9-4f33-807b-e4851b80613a + false + Item +1 + +1 + false + 0 + + + + + + 2078 + 1358 + 23 + 20 + + + 2089.5 + 1368 + + + + + + + + Item at {+2'} + a97d3de6-311e-495d-903f-7d5145e85a2a + false + Item +2 + +2 + false + 0 + + + + + + 2078 + 1378 + 23 + 20 + + + 2089.5 + 1388 + + + + + + + + Item at {+3'} + b54d49ae-2637-4951-a942-5be0ff2aeac8 + false + Item +3 + +3 + false + 0 + + + + + + 2078 + 1398 + 23 + 20 + + + 2089.5 + 1408 + + + + + + + + + + + + + + d8332545-21b2-4716-96e3-8559a9876e17 + Dispatch + + + + + Dispatch the items in a list into two target lists. + true + cb623ee7-06c3-4400-aaba-a9487b4ee237 + Dispatch + Dispatch + + + + + + 2145 + 1507 + 64 + 44 + + + 2175 + 1529 + + + + + + 1 + List to filter + f301c669-eabb-4535-b5ed-b3bf1b42699b + List + L + false + 725ac33d-374c-4cc0-8f18-3e15182f1323 + 1 + + + + + + 2147 + 1509 + 13 + 20 + + + 2155 + 1519 + + + + + + + + 1 + Dispatch pattern + 4b1f1469-cc85-4ab1-9812-a58999856e66 + Dispatch pattern + P + false + 0 + + + + + + 2147 + 1529 + 13 + 20 + + + 2155 + 1539 + + + + + + 1 + + + + + 2 + {0} + + + + + true + + + + + false + + + + + + + + + + + 1 + Dispatch target for True values + 070a7853-fe38-4e8a-b6d2-caeaaa0ae003 + List A + A + false + 0 + + + + + + 2190 + 1509 + 17 + 20 + + + 2198.5 + 1519 + + + + + + + + 1 + Dispatch target for False values + 90efa295-d4df-4bf6-9596-5f0142788873 + List B + B + false + 0 + + + + + + 2190 + 1529 + 17 + 20 + + + 2198.5 + 1539 + + + + + + + + + + + + 537b0419-bbc2-4ff4-bf08-afe526367b2c + Custom Preview + + + + + Allows for customized geometry previews + true + 5969f9b6-e599-48a8-a77a-9967f0e908ee + Custom Preview + Preview + + + + + + + 2300 + 1481 + 48 + 44 + + + 2334 + 1503 + + + + + + Geometry to preview + true + 6ffcc6ca-c0f2-4057-80d1-9394a508e988 + Geometry + G + false + 070a7853-fe38-4e8a-b6d2-caeaaa0ae003 + 1 + + + + + + 2302 + 1483 + 17 + 20 + + + 2312 + 1493 + + + + + + + + The material override + 4add57de-5031-447f-a497-bb416da407be + Material + M + false + 0 + + + + + + 2302 + 1503 + 17 + 20 + + + 2312 + 1513 + + + + + + 1 + + + + + 1 + {0} + + + + + + 255;221;160;221 + + + 255;66;48;66 + + 0.5 + + 255;255;255;255 + + 0 + + + + + + + + + + + + + + + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep + + + + + Contains a collection of Breps (Boundary REPresentations) + true + 05edbc97-20e7-47f4-ba39-16e952aca10f + Brep + Brep + false + 90efa295-d4df-4bf6-9596-5f0142788873 + 1 + + + + + + 2230 + 1577 + 50 + 24 + + + 2255.067 + 1589.683 + + + + + + + + + + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep + + + + + Contains a collection of Breps (Boundary REPresentations) + c17ecd4e-0c95-4f30-a30e-9d892e6426cc + Brep + Brep + false + 0 + + + + + + 2229 + 928 + 50 + 24 + + + 2254.2 + 940.6667 + + + + + + 1 + + + + + 2 + {0} + + + + + 29681234-09cf-497f-ae28-cbb735e46f22 + + + + + ee1a0b75-9176-4b97-b45c-8f17b5c16e99 + + + + + + + + + + + + + 4f8984c4-7c7a-4d69-b0a2-183cbb330d20 + Plane + + + + + Contains a collection of three-dimensional axis-systems + 380a46d2-43bc-4a8c-8a22-b7aef89bbf72 + Plane + Pln + false + c17ecd4e-0c95-4f30-a30e-9d892e6426cc + 1 + + + + + + 2313 + 928 + 50 + 24 + + + 2338.067 + 940.9667 + + + + + + + + + + 6ea4c4c7-ddef-4313-a21f-8b445c20220c + 1c9de8a1-315f-4c56-af06-8f69fee80a7a + Tween Two Planes + + + + + Tween between two planes. + 8a7ee619-ca42-4727-a2cc-5cf2035c36c8 + Tween Two Planes + Twn2Plns + + + + + + 2493 + 939 + 65 + 84 + + + 2525 + 981 + + + + + + Plane to tween from + eaae4cc6-ae0d-4898-a8ae-a582ca50ce29 + Plane A + A + false + b70b10e6-e22b-4abe-a2d5-45a65332177c + 1 + + + + + + 2495 + 941 + 15 + 20 + + + 2504 + 951 + + + + + + + + Plane to tween to + 9d069cc0-02f3-475f-9b9b-4dd32f2ea790 + Plane B + B + false + fc33ea5e-8457-40ab-8a5f-4f6de1e193d7 + 1 + + + + + + 2495 + 961 + 15 + 20 + + + 2504 + 971 + + + + + + + + Tween factor (0.0 = Plane A, 1.0 = Plane B) + 06cd748f-8bc3-48ec-91f1-e33ddd966045 + Factor + F + false + 0 + + + + + + 2495 + 981 + 15 + 20 + + + 2504 + 991 + + + + + + 1 + + + + + 1 + {0} + + + + + 0.5 + + + + + + + + + + + Interpolate with quaternion rotation + 012b355a-6715-4ddf-9169-8b9e68bfcacb + Quaternion + Q + false + 0 + + + + + + 2495 + 1001 + 15 + 20 + + + 2504 + 1011 + + + + + + 1 + + + + + 1 + {0} + + + + + true + + + + + + + + + + + Resulting tween plane + 0a7cd565-45a8-4c93-8f75-04d086baa295 + Tween + T + false + 0 + + + + + + 2540 + 941 + 16 + 80 + + + 2548 + 981 + + + + + + + + + + + + d8332545-21b2-4716-96e3-8559a9876e17 + Dispatch + + + + + Dispatch the items in a list into two target lists. + 687ce77d-9faf-470b-aaea-ccbcc0ba4326 + Dispatch + Dispatch + + + + + + 2379 + 939 + 64 + 44 + + + 2409 + 961 + + + + + + 1 + List to filter + 8997e329-8f57-4af9-b7d4-64a6d829387b + List + L + false + 380a46d2-43bc-4a8c-8a22-b7aef89bbf72 + 1 + + + + + + 2381 + 941 + 13 + 20 + + + 2389 + 951 + + + + + + + + 1 + Dispatch pattern + 22526779-5169-4e54-9c8d-4e398ca1638c + Dispatch pattern + P + false + 0 + + + + + + 2381 + 961 + 13 + 20 + + + 2389 + 971 + + + + + + 1 + + + + + 2 + {0} + + + + + true + + + + + false + + + + + + + + + + + 1 + Dispatch target for True values + b70b10e6-e22b-4abe-a2d5-45a65332177c + List A + A + false + 0 + + + + + + 2424 + 941 + 17 + 20 + + + 2432.5 + 951 + + + + + + + + 1 + Dispatch target for False values + fc33ea5e-8457-40ab-8a5f-4f6de1e193d7 + List B + B + false + 0 + + + + + + 2424 + 961 + 17 + 20 + + + 2432.5 + 971 + + + + + + + + + + + + 919e146f-30ae-4aae-be34-4d72f555e7da + Brep + + + + + Contains a collection of Breps (Boundary REPresentations) + ed4080d4-774f-4f9f-9f0d-d8ea182a21c7 + Brep + Brep + false + 0 + + + + + + 2225 + 1066 + 50 + 24 + + + 2250.564 + 1078.813 + + + + + + 1 + + + + + 1 + {0} + + + + + 5903e3bf-023e-4163-8e83-1513748adbcf + + + + + + + + + + + + + d5967b9f-e8ee-436b-a8ad-29fdcecf32d5 + Curve + + + + + Contains a collection of generic curves + 3258285e-9db1-4918-93f8-5a397ec40239 + Curve + Crv + false + 0 + + + + + + 342 + 1481 + 50 + 24 + + + 367.2433 + 1493.886 + + + + + + 1 + + + + + 1 + {0} + + + + + -1 + 2d99496e-781f-446b-b1f4-9cd8b275b220 + + + + + + + + + + + + + 410755b1-224a-4c1e-a407-bf32fb45ea7e + 00000000-0000-0000-0000-000000000000 + CT: X Topological Joint Rules + + + + + import inspect + +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning + +from compas_timber.connections import Joint +from compas_timber.connections import JointTopology +from compas_timber.connections import XHalfLapJoint +from compas_timber.design import TopologyRule +from compas_timber.ghpython.ghcomponent_helpers import get_leaf_subclasses +from compas_timber.ghpython.ghcomponent_helpers import manage_dynamic_params +from compas_timber.ghpython.ghcomponent_helpers import rename_gh_output + + +class X_TopologyJointRule(component): + def __init__(self): + super(X_TopologyJointRule, self).__init__() + self.classes = {} + for cls in get_leaf_subclasses(Joint): + if cls.SUPPORTED_TOPOLOGY == JointTopology.TOPO_X: + self.classes[cls.__name__] = cls + if ghenv.Component.Params.Output[0].NickName == "Rule": + self.joint_type = XHalfLapJoint + self.clicked = False + else: + self.joint_type = self.classes.get(ghenv.Component.Params.Output[0].NickName, None) + self.clicked = True + + def RunScript(self, *args): + if not self.clicked: + ghenv.Component.Message = "Default: XHalfLapJoint" + self.AddRuntimeMessage(Warning, "XHalfLapJoint is default, change in context menu (right click)") + return TopologyRule(JointTopology.TOPO_X, XHalfLapJoint) + else: + ghenv.Component.Message = self.joint_type.__name__ + kwargs = {} + for i, val in enumerate(args): + if val is not None: + kwargs[self.arg_names()[i]] = val + if self.joint_type.SUPPORTED_TOPOLOGY != JointTopology.TOPO_X: + self.AddRuntimeMessage(Warning, "Joint type does not match topology. Joint may not be generated.") + return TopologyRule(JointTopology.TOPO_X, self.joint_type, **kwargs) + + def arg_names(self): + names = inspect.getargspec(self.joint_type.__init__)[0][3:] + return [name for name in names if (name != "key") and (name != "frame")] + + def AppendAdditionalMenuItems(self, menu): + for name in self.classes.keys(): + item = menu.Items.Add(name, None, self.on_item_click) + if self.joint_type and name == self.joint_type.__name__: + item.Checked = True + + def on_item_click(self, sender, event_info): + self.clicked = True + self.joint_type = self.classes[str(sender)] + rename_gh_output(self.joint_type.__name__, 0, ghenv) + manage_dynamic_params(self.arg_names(), ghenv, rename_count=0, permanent_param_count=0) + ghenv.Component.ExpireSolution(True) + + GhPython provides a Python script component + true + true + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALDAAACwwBP0AiyAAAAUBJREFUSEu1kyFuAzEQRXOEhFuyFV8gUq7QA5SEFxWXhQYGWaVlxUXhIQGGBb1ApJyg6g1cfWlmNZpxtt7EBU+7Gs/8r/3jnZVSZv+JKYDowyn6UIhXfT4FKRqiD/Pow06IMy96sBUWf6yI1rhEH1ZaZAw2wKAWu8ZBi4zBBjJz8E5xIbYvdXbSImOwwUGJzLkBkfQw0IvtboBIpAi+CPF0i0jvYIybltx6TX9uuqZkgqxrkYAn7nMpP7uUC7ER9U2tbhzJTO7E/MUu5SMJnV3KCwLvqH3IXiPeAgl+k+AbgXfUFncbAJfyVkTCbHWfGZyCS/lTiB/1OTCFKbiU98Jgr8+BKbTiUl5XIlrrPjPYiogHz+Fd95nBFtSC8SUP1xZthv/CpbwUV3TIXV3V5T0GHMe5csY/23CjjEBvTKE3vzBOrcjlhJXzAAAAAElFTkSuQmCC + + false + 4b75bd16-28c5-4eff-8a7a-4e93bb804e8c + true + true + CT: X Topological Joint Rules + X_Topo_Joint + + + + + + 1383 + 1547 + 187 + 44 + + + 1477 + 1569 + + + + + + 2 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + 1 + 8ec86459-bf01-4409-baee-174d0d2b13d0 + + + + + flip_lap_side + 441ab140-096b-48fc-9343-101e952d3710 + flip_lap_side + flip_lap_side + true + 0 + + + + + + 1385 + 1549 + 77 + 20 + + + 1425 + 1559 + + + + + + + + Script input cut_plane_bias. + 8fd480bd-7940-4f8d-8495-522bc62e232a + cut_plane_bias + cut_plane_bias + true + 0 + + + + + + 1385 + 1569 + 77 + 20 + + + 1425 + 1579 + + + + + + + + Script output XHalfLapJoint. + d3300e29-859b-48e3-9c4d-cb903ab46c0c + XHalfLapJoint + XHalfLapJoint + false + 0 + + + + + + 1492 + 1549 + 76 + 40 + + + 1530 + 1569 + + + + + + + + + + + @@ -7804,7 +10466,7 @@ class L_TopologyJointRule(component): - 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 diff --git a/examples/model/0001_model.py b/examples/model/0001_model.py index 389134474..089ae2514 100644 --- a/examples/model/0001_model.py +++ b/examples/model/0001_model.py @@ -7,7 +7,7 @@ from compas_timber.elements import Beam from compas_timber.connections import LMiterJoint from compas_timber.connections import TButtJoint -from compas_timber.connections import THalfLapJoint +from compas_timber.connections import TLapJoint HERE = os.path.dirname(__file__) @@ -43,7 +43,7 @@ def create_viewer(): LMiterJoint.create(model, beams[0], beams[5]) # Assign joints - Inner - Inner -THalfLapJoint.create(model, beams[2], beams[1]) +TLapJoint.create(model, beams[2], beams[1]) # Assign joints - Frame - Inner diff --git a/src/compas_timber/connections/__init__.py b/src/compas_timber/connections/__init__.py index 76258bae2..328e35389 100644 --- a/src/compas_timber/connections/__init__.py +++ b/src/compas_timber/connections/__init__.py @@ -1,6 +1,6 @@ from .joint import Joint from .l_butt import LButtJoint -from .l_halflap import LHalfLapJoint +from .l_lap import LLapJoint from .l_miter import LMiterJoint from .l_french_ridge_lap import LFrenchRidgeLapJoint from .lap_joint import LapJoint @@ -11,11 +11,13 @@ from .t_butt import TButtJoint from .t_step_joint import TStepJoint from .t_birdsmouth import TBirdsmouthJoint -from .t_halflap import THalfLapJoint -from .x_halflap import XHalfLapJoint +from .t_lap import TLapJoint +from .x_lap import XLapJoint from .t_dovetail import TDovetailJoint from .t_tenon_mortise import TenonMortiseJoint from .ball_node import BallNodeJoint +from .utilities import beam_ref_side_incidence +from .utilities import beam_ref_side_incidence_with_vector __all__ = [ "Joint", @@ -26,9 +28,9 @@ "TStepJoint", "TBirdsmouthJoint", "LMiterJoint", - "XHalfLapJoint", - "THalfLapJoint", - "LHalfLapJoint", + "XLapJoint", + "TLapJoint", + "LLapJoint", "NullJoint", "LFrenchRidgeLapJoint", "JointTopology", @@ -37,4 +39,6 @@ "TDovetailJoint", "BallNodeJoint", "TenonMortiseJoint", + "beam_ref_side_incidence", + "beam_ref_side_incidence_with_vector", ] diff --git a/src/compas_timber/connections/l_butt.py b/src/compas_timber/connections/l_butt.py index 251bce416..b318aadf8 100644 --- a/src/compas_timber/connections/l_butt.py +++ b/src/compas_timber/connections/l_butt.py @@ -176,8 +176,15 @@ def add_features(self): # apply the pocket on the cross beam if self.mill_depth: cross_cutting_plane = self.main_beam.ref_sides[self.main_beam_ref_side_index] - lap_width = self.main_beam.height if self.main_beam_ref_side_index % 2 == 0 else self.main_beam.width - cross_feature = Lap.from_plane_and_beam(cross_cutting_plane, self.cross_beam, lap_width, self.mill_depth, self.cross_beam_ref_side_index) + lap_width = self.main_beam.get_dimensions_relative_to_side(self.main_beam_ref_side_index)[1] + cross_feature = Lap.from_plane_and_beam( + cross_cutting_plane, + self.cross_beam, + lap_width, + self.mill_depth, + is_pocket=True, + ref_side_index=self.cross_beam_ref_side_index, + ) self.cross_beam.add_features(cross_feature) self.features.append(cross_feature) diff --git a/src/compas_timber/connections/l_french_ridge_lap.py b/src/compas_timber/connections/l_french_ridge_lap.py index 106587c1f..b95e23eed 100644 --- a/src/compas_timber/connections/l_french_ridge_lap.py +++ b/src/compas_timber/connections/l_french_ridge_lap.py @@ -1,16 +1,13 @@ -from compas.tolerance import TOL - -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.errors import BeamJoiningError from compas_timber.fabrication import FrenchRidgeLap -from .joint import Joint +from .lap_joint import LapJoint from .solver import JointTopology +from .utilities import are_beams_coplanar -class LFrenchRidgeLapJoint(Joint): - """Represents an L-FrenchRidgeLap type joint which joins two beams in their ends, by lapping them with a ridge. +class LFrenchRidgeLapJoint(LapJoint): + """Represents an L-FrenchRidgeLap type joint which joins two beams at their ends, by lapping them with a ridge. The joint can only be created between two beams that are aligned and have the same dimensions. This joint type is compatible with beams in L topology. @@ -19,89 +16,39 @@ class LFrenchRidgeLapJoint(Joint): Parameters ---------- - beam_a : :class:`~compas_timber.parts.Beam` - First beam to be joined. - beam_b : :class:`~compas_timber.parts.Beam` - Second beam to be joined. + main_beam : :class:`~compas_timber.elements.Beam` + The main beam to be joined. + cross_beam : :class:`~compas_timber.elements.Beam` + The cross beam to be joined. + flip_lap_side : bool + If True, the lap is flipped to the other side of the beams. drillhole_diam : float Diameter of the drill hole to be made in the joint. - flip_beams : bool - If True, the beams will be flipped in the joint. Default is False. Attributes ---------- - beam_a : :class:`~compas_timber.parts.Beam` - First beam to be joined. - beam_b : :class:`~compas_timber.parts.Beam` - Second beam to be joined. + main_beam : :class:`~compas_timber.elements.Beam` + The main beam to be joined. + cross_beam : :class:`~compas_timber.elements.Beam` + The cross beam to be joined. + flip_lap_side : bool + If True, the lap is flipped to the other side of the beams. drillhole_diam : float Diameter of the drill hole to be made in the joint. - flip_beams : bool - If True, the beams will be flipped in the joint. Default is False. """ SUPPORTED_TOPOLOGY = JointTopology.TOPO_L - @property - def __data__(self): - data = super(LFrenchRidgeLapJoint, self).__data__ - data["beam_a_guid"] = self.beam_a_guid - data["beam_b_guid"] = self.beam_b_guid - data["drillhole_diam"] = self.drillhole_diam - data["flip_beams"] = self.flip_beams - return data - - def __init__(self, beam_a=None, beam_b=None, drillhole_diam=None, flip_beams=None, **kwargs): - super(LFrenchRidgeLapJoint, self).__init__(**kwargs) - self.beam_a = beam_a - self.beam_b = beam_b - self.beam_a_guid = kwargs.get("beam_a_guid", None) or str(beam_a.guid) - self.beam_b_guid = kwargs.get("beam_b_guid", None) or str(beam_b.guid) + def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, drillhole_diam=None, **kwargs): + super(LFrenchRidgeLapJoint, self).__init__(main_beam, cross_beam, flip_lap_side, drillhole_diam, **kwargs) self.drillhole_diam = drillhole_diam - self.flip_beams = flip_beams - self.features = [] - - @property - def elements(self): - return [self.beam_a, self.beam_b] - - @property - def beam_a_ref_side_index(self): - cross_vector = self.beam_a.centerline.direction.cross(self.beam_b.centerline.direction) - ref_side_dict = beam_ref_side_incidence_with_vector(self.beam_a, cross_vector, ignore_ends=True) - if self.flip_beams: - return max(ref_side_dict, key=ref_side_dict.get) - return min(ref_side_dict, key=ref_side_dict.get) - - @property - def beam_b_ref_side_index(self): - cross_vector = self.beam_a.centerline.direction.cross(self.beam_b.centerline.direction) - ref_side_dict = beam_ref_side_incidence_with_vector(self.beam_b, cross_vector, ignore_ends=True) - if self.flip_beams: - return min(ref_side_dict, key=ref_side_dict.get) - return max(ref_side_dict, key=ref_side_dict.get) - - @property - def cutting_plane_a(self): - # the plane that cuts beam_b - ref_side_dict = beam_ref_side_incidence(self.beam_b, self.beam_a, ignore_ends=True) - ref_side_index = max(ref_side_dict, key=ref_side_dict.get) - return self.beam_a.ref_sides[ref_side_index] - - @property - def cutting_plane_b(self): - # the plane that cuts beam_a - ref_side_dict = beam_ref_side_incidence(self.beam_a, self.beam_b, ignore_ends=True) - ref_side_index = max(ref_side_dict, key=ref_side_dict.get) - return self.beam_b.ref_sides[ref_side_index] def add_extensions(self): """Calculates and adds the necessary extensions to the beams. - This method is called during the `Model.process_joinery()` process after the joint - has been instantiated and added to the model. + This method is automatically called when joint is created by the call to `Joint.create()`. Raises ------ @@ -109,20 +56,20 @@ def add_extensions(self): If the extension could not be calculated. """ + assert self.main_beam and self.cross_beam - assert self.beam_a and self.beam_b - start_a, start_b = None, None + start_main, start_cross = None, None try: - start_a, end_a = self.beam_a.extension_to_plane(self.cutting_plane_b) - start_b, end_b = self.beam_b.extension_to_plane(self.cutting_plane_a) + start_main, end_main = self.main_beam.extension_to_plane(self.main_cutting_plane) + start_cross, end_cross = self.cross_beam.extension_to_plane(self.cross_cutting_plane) except AttributeError as ae: # I want here just the plane that caused the error - geometries = [self.cutting_plane_a] if start_a is not None else [self.cutting_plane_b] + geometries = [self.cross_cutting_plane] if start_main is not None else [self.main_cutting_plane] raise BeamJoiningError(self.elements, self, debug_info=str(ae), debug_geometries=geometries) except Exception as ex: raise BeamJoiningError(self.elements, self, debug_info=str(ex)) - self.beam_a.add_blank_extension(start_a, end_a, self.guid) - self.beam_b.add_blank_extension(start_b, end_b, self.guid) + self.main_beam.add_blank_extension(start_main, end_main, self.main_beam_guid) + self.cross_beam.add_blank_extension(start_cross, end_cross, self.cross_beam_guid) def add_features(self): """Adds the necessary features to the beams. @@ -132,38 +79,39 @@ def add_features(self): have been added via `Joint.add_extensions()`. """ - assert self.beam_a and self.beam_b + assert self.main_beam and self.cross_beam if self.features: - self.beam_a.remove_features(self.features) - self.beam_b.remove_features(self.features) + self.main_beam.remove_features(self.features) + self.cross_beam.remove_features(self.features) - frl_a = FrenchRidgeLap.from_beam_beam_and_plane(self.beam_a, self.beam_b, self.cutting_plane_b, self.drillhole_diam, self.beam_a_ref_side_index) - frl_b = FrenchRidgeLap.from_beam_beam_and_plane(self.beam_b, self.beam_a, self.cutting_plane_a, self.drillhole_diam, self.beam_b_ref_side_index) - self.beam_a.add_features(frl_a) - self.beam_b.add_features(frl_b) - self.features = [frl_a, frl_b] + main_frl_feature = FrenchRidgeLap.from_beam_beam_and_plane(self.main_beam, self.cross_beam, self.main_cutting_plane, self.drillhole_diam, self.main_ref_side_index) + cross_frl_feature = FrenchRidgeLap.from_beam_beam_and_plane(self.cross_beam, self.main_beam, self.cross_cutting_plane, self.drillhole_diam, self.cross_ref_side_index) + # store the features to the beams + self.main_beam.add_features(main_frl_feature) + self.cross_beam.add_features(cross_frl_feature) + # register the features in the joint + self.features = [main_frl_feature, cross_frl_feature] def check_elements_compatibility(self): """Checks if the elements are compatible for the creation of the joint. + Compared to the LapJoint's `check_elements_compatibility` method, this one additionally checks if dimensions of the beams match. + Raises ------ BeamJoiningError If the elements are not compatible for the creation of the joint. - """ - # check if the beams are aligned - cross_vect = self.beam_a.centerline.direction.cross(self.beam_b.centerline.direction) - for beam in self.elements: - beam_normal = beam.frame.normal.unitized() - dot = abs(beam_normal.dot(cross_vect.unitized())) - if not (TOL.is_zero(dot) or TOL.is_close(dot, 1)): - raise BeamJoiningError(self.elements, self, debug_info="The two beams are not aligned to create a French Ridge Lap joint.") - + if not are_beams_coplanar(*self.elements): + raise BeamJoiningError( + beams=self.elements, + joint=self, + debug_info="The two beams are not coplanar to create a Lap joint.", + ) # calculate widths and heights of the beams dimensions = [] - ref_side_indices = [self.beam_a_ref_side_index, self.beam_b_ref_side_index] + ref_side_indices = [self.main_ref_side_index, self.cross_ref_side_index] for i, beam in enumerate(self.elements): width = beam.side_as_surface(ref_side_indices[i]).ysize height = beam.height if ref_side_indices[i] % 2 == 0 else beam.width diff --git a/src/compas_timber/connections/l_halflap.py b/src/compas_timber/connections/l_halflap.py deleted file mode 100644 index f9cfead37..000000000 --- a/src/compas_timber/connections/l_halflap.py +++ /dev/null @@ -1,102 +0,0 @@ -from compas.geometry import Frame - -from compas_timber.elements import CutFeature -from compas_timber.elements import MillVolume -from compas_timber.errors import BeamJoiningError - -from .lap_joint import LapJoint -from .solver import JointTopology - - -class LHalfLapJoint(LapJoint): - """Represents a L-Lap type joint which joins the ends of two beams, - trimming the main beam. - - This joint type is compatible with beams in L topology. - - Please use `LHalfLapJoint.create()` to properly create an instance of this class and associate it with a model. - - Parameters - ---------- - main_beam : :class:`~compas_timber.parts.Beam` - The main beam to be joined. - cross_beam : :class:`~compas_timber.parts.Beam` - The cross beam to be joined. - flip_lap_side : bool - If True, the lap is flipped to the other side of the beams. - cut_plane_bias : float - Allows lap to be shifted deeper into one beam or the other. Value should be between 0 and 1.0 without completely cutting through either beam. Default is 0.5. - - Attributes - ---------- - beams : list(:class:`~compas_timber.parts.Beam`) - The beams joined by this joint. - main_beam : :class:`~compas_timber.parts.Beam` - The main beam to be joined. - cross_beam : :class:`~compas_timber.parts.Beam` - The cross beam to be joined. - main_beam_key : str - The key of the main beam. - cross_beam_key : str - The key of the cross beam. - features : list(:class:`~compas_timber.parts.Feature`) - The features created by this joint. - joint_type : str - A string representation of this joint's type. - - """ - - SUPPORTED_TOPOLOGY = JointTopology.TOPO_L - - def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5, **kwargs): - super(LHalfLapJoint, self).__init__(main_beam, cross_beam, flip_lap_side, cut_plane_bias, **kwargs) - - def add_extensions(self): - """Calculates and adds the necessary extensions to the beams. - - This method is automatically called when joint is created by the call to `Joint.create()`. - - Raises - ------ - BeamJoiningError - If the extension could not be calculated. - - """ - assert self.main_beam and self.cross_beam - try: - main_cutting_frame = self.get_main_cutting_frame() - cross_cutting_frame = self.get_cross_cutting_frame() - except Exception as ex: - raise BeamJoiningError(beams=self.elements, joint=self, debug_info=str(ex)) - - start_main, end_main = self.main_beam.extension_to_plane(main_cutting_frame) - start_cross, end_cross = self.cross_beam.extension_to_plane(cross_cutting_frame) - - extension_tolerance = 0.01 # TODO: this should be proportional to the unit used - self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.guid) - self.cross_beam.add_blank_extension(start_cross + extension_tolerance, end_cross + extension_tolerance, self.guid) - - def add_features(self): - assert self.main_beam and self.cross_beam - - try: - main_cutting_frame = self.get_main_cutting_frame() - cross_cutting_frame = self.get_cross_cutting_frame() - negative_brep_main_beam, negative_brep_cross_beam = self._create_negative_volumes() - except Exception as ex: - raise BeamJoiningError(beams=self.elements, joint=self, debug_info=str(ex)) - - main_volume = MillVolume(negative_brep_main_beam) - cross_volume = MillVolume(negative_brep_cross_beam) - - self.main_beam.add_features(main_volume) - self.cross_beam.add_features(cross_volume) - - f_cross = CutFeature(cross_cutting_frame) - self.cross_beam.add_features(f_cross) - - trim_frame = Frame(main_cutting_frame.point, main_cutting_frame.xaxis, -main_cutting_frame.yaxis) - f_main = CutFeature(trim_frame) - self.main_beam.add_features(f_main) - - self.features = [main_volume, cross_volume, f_main, f_cross] diff --git a/src/compas_timber/connections/l_lap.py b/src/compas_timber/connections/l_lap.py new file mode 100644 index 000000000..60d0499a7 --- /dev/null +++ b/src/compas_timber/connections/l_lap.py @@ -0,0 +1,146 @@ +from compas.geometry import Frame + +from compas_timber.elements.features import CutFeature +from compas_timber.elements.features import MillVolume +from compas_timber.errors import BeamJoiningError +from compas_timber.fabrication import JackRafterCut +from compas_timber.fabrication import Lap + +from .lap_joint import LapJoint +from .solver import JointTopology +from .utilities import are_beams_coplanar + + +class LLapJoint(LapJoint): + """Represents an L-Lap type joint which joins the ends of two beams with a lap. + + This joint type is compatible with beams in L topology. + + Please use `LLapJoint.create()` to properly create an instance of this class and associate it with a model. + + Parameters + ---------- + main_beam : :class:`~compas_timber.parts.Beam` + The first beam to be joined. + cross_beam : :class:`~compas_timber.parts.Beam` + The second beam to be joined. + flip_lap_side : bool + If True, the lap is flipped to the other side of the beams. + cut_plane_bias : float + Allows lap to be shifted deeper into one beam or the other. Value should be between 0 and 1.0 without completely cutting through either beam. Default is 0.5. + + Attributes + ---------- + main_beam : :class:`~compas_timber.parts.Beam` + The first beam to be joined. + cross_beam : :class:`~compas_timber.parts.Beam` + The second beam to be joined. + flip_lap_side : bool + If True, the lap is flipped to the other side of the beams. + cut_plane_bias : float + Allows lap to be shifted deeper into one beam or the other. Value should be between 0 and 1.0 without completely cutting through either beam. Default is 0.5. + """ + + SUPPORTED_TOPOLOGY = JointTopology.TOPO_L + + def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5, **kwargs): + super(LLapJoint, self).__init__(main_beam, cross_beam, flip_lap_side, cut_plane_bias, **kwargs) + + def add_extensions(self): + """Calculates and adds the necessary extensions to the beams. + + This method is automatically called when joint is created by the call to `Joint.create()`. + + Raises + ------ + BeamJoiningError + If the extension could not be calculated. + + """ + assert self.main_beam and self.cross_beam + + start_main, start_cross = None, None + try: + start_main, end_main = self.main_beam.extension_to_plane(self.main_cutting_plane) + start_cross, end_cross = self.cross_beam.extension_to_plane(self.cross_cutting_plane) + except AttributeError as ae: + # I want here just the plane that caused the error + geometries = [self.cross_cutting_plane] if start_main is not None else [self.main_cutting_plane] + raise BeamJoiningError(self.elements, self, debug_info=str(ae), debug_geometries=geometries) + except Exception as ex: + raise BeamJoiningError(self.elements, self, debug_info=str(ex)) + self.main_beam.add_blank_extension(start_main, end_main, self.main_beam_guid) + self.cross_beam.add_blank_extension(start_cross, end_cross, self.cross_beam_guid) + + def add_features(self): + """Adds the required joint 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 + + if self.features: + self.main_beam.remove_features(self.features) + self.cross_beam.remove_features(self.features) + + if are_beams_coplanar(*self.elements): # TODO: this is a temporal solution to allow the vizualization of non-coplanar lap joints. + # calculate the lap length and depth for each beam + main_lap_length, cross_lap_length = self._get_lap_lengths() + main_lap_depth, cross_lap_depth = self._get_lap_depths() + + ## main_beam + # lap feature on main_beam + main_lap_feature = Lap.from_plane_and_beam( + self.main_cutting_plane, + self.main_beam, + main_lap_length, + main_lap_depth, + ref_side_index=self.main_ref_side_index, + ) + # cutoff feature for main_beam + main_cut_feature = JackRafterCut.from_plane_and_beam(self.main_cutting_plane, self.main_beam) + main_features = [main_cut_feature, main_lap_feature] + self.main_beam.add_features(main_features) + self.features.extend(main_features) + + ## cross_beam + # lap feature on cross_beam + cross_lap_feature = Lap.from_plane_and_beam( + self.cross_cutting_plane, + self.cross_beam, + cross_lap_length, + cross_lap_depth, + ref_side_index=self.cross_ref_side_index, + ) + # cutoff feature for beam_b + cross_cut_feature = JackRafterCut.from_plane_and_beam(self.cross_cutting_plane, self.cross_beam) + cross_features = [cross_cut_feature, cross_lap_feature] + self.cross_beam.add_features(cross_features) + self.features.extend(cross_features) + + else: + # TODO: this is a temporal solution to avoid the error if beams are not coplanar and allow the visualization of the joint. + # TODO: this solution does not generate machining features and therefore will be ignored in the fabrication process. + # TODO: once the Lap BTLx processing implimentation allows for non-coplanar beams, this should be removed. + try: + main_cutting_frame = self.get_main_cutting_frame() + cross_cutting_frame = self.get_cross_cutting_frame() + negative_brep_main_beam, negative_brep_cross_beam = self._create_negative_volumes() + except Exception as ex: + raise BeamJoiningError(beams=self.elements, joint=self, debug_info=str(ex)) + + main_volume = MillVolume(negative_brep_main_beam) + cross_volume = MillVolume(negative_brep_cross_beam) + + self.main_beam.add_features(main_volume) + self.cross_beam.add_features(cross_volume) + + f_cross = CutFeature(cross_cutting_frame) + self.cross_beam.add_features(f_cross) + + trim_frame = Frame(main_cutting_frame.point, main_cutting_frame.xaxis, -main_cutting_frame.yaxis) + f_main = CutFeature(trim_frame) + self.main_beam.add_features(f_main) + + self.features = [main_volume, cross_volume, f_main, f_cross] diff --git a/src/compas_timber/connections/lap_joint.py b/src/compas_timber/connections/lap_joint.py index 8c57a53b8..f4b642b11 100644 --- a/src/compas_timber/connections/lap_joint.py +++ b/src/compas_timber/connections/lap_joint.py @@ -1,3 +1,5 @@ +import warnings + from compas.geometry import Frame from compas.geometry import Line from compas.geometry import Plane @@ -8,7 +10,12 @@ from compas.geometry import intersection_line_plane from compas.geometry import intersection_plane_plane_plane +from compas_timber.errors import BeamJoiningError + from .joint import Joint +from .utilities import are_beams_coplanar +from .utilities import beam_ref_side_incidence +from .utilities import beam_ref_side_incidence_with_vector class LapJoint(Joint): @@ -18,9 +25,9 @@ class LapJoint(Joint): Parameters ---------- - main_beam : :class:`~compas_timber.parts.Beam` + main_beam : :class:`~compas_timber.elements.Beam` The main beam to be joined. - cross_beam : :class:`~compas_timber.parts.Beam` + cross_beam : :class:`~compas_timber.elements.Beam` The cross beam to be joined. flip_lap_side : bool If True, the lap is flipped to the other side of the beams. @@ -29,9 +36,11 @@ class LapJoint(Joint): Attributes ---------- - main_beam : :class:`~compas_timber.parts.Beam` + elements : list of :class:`~compas_timber.elements.Beam` + The beams to be joined. + main_beam : :class:`~compas_timber.elements.Beam` The main beam to be joined. - cross_beam : :class:`~compas_timber.parts.Beam` + cross_beam : :class:`~compas_timber.elements.Beam` The cross beam to be joined. flip_lap_side : bool If True, the lap is flipped to the other side of the beams. @@ -49,24 +58,125 @@ def __data__(self): data["cut_plane_bias"] = self.cut_plane_bias return data - def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5): - super(LapJoint, self).__init__() + def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5, **kwargs): + super(LapJoint, self).__init__(**kwargs) self.main_beam = main_beam self.cross_beam = cross_beam + self.main_beam_guid = kwargs.get("main_beam_guid", None) or str(main_beam.guid) + self.cross_beam_guid = kwargs.get("cross_beam_guid", None) or str(cross_beam.guid) + self.flip_lap_side = flip_lap_side self.cut_plane_bias = cut_plane_bias - 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.features = [] + self._main_ref_side_index = None + self._cross_ref_side_index = None + self._main_cutting_plane = None + self._cross_cutting_plane = None + @property def elements(self): return [self.main_beam, self.cross_beam] + @property + def main_ref_side_index(self): + """The reference side index of the main beam.""" + if self._main_ref_side_index is None: + self._main_ref_side_index = self._get_beam_ref_side_index(self.main_beam, self.cross_beam, self.flip_lap_side) + return self._main_ref_side_index + + @property + def cross_ref_side_index(self): + """The reference side index of the cross beam.""" + if self._cross_ref_side_index is None: + self._cross_ref_side_index = self._get_beam_ref_side_index(self.cross_beam, self.main_beam, self.flip_lap_side) + return self._cross_ref_side_index + + @property + def main_cutting_plane(self): + """The face of the cross beam that cuts the main beam as a plane.""" + if self._main_cutting_plane is None: + self._main_cutting_plane = self._get_cutting_plane(self.cross_beam, self.main_beam) + return self._main_cutting_plane + + @property + def cross_cutting_plane(self): + """The face of the main beam that cuts the cross beam as a plane.""" + if self._cross_cutting_plane is None: + self._cross_cutting_plane = self._get_cutting_plane(self.main_beam, self.cross_beam) + return self._cross_cutting_plane + + @staticmethod + def _get_beam_ref_side_index(beam_a, beam_b, flip): + """Returns the reference side index of beam_a with respect to beam_b.""" + cross_vector = beam_a.centerline.direction.cross(beam_b.centerline.direction) + ref_side_dict = beam_ref_side_incidence_with_vector(beam_a, cross_vector, ignore_ends=True) + if flip: + return max(ref_side_dict, key=ref_side_dict.get) + return min(ref_side_dict, key=ref_side_dict.get) + + @staticmethod + def _get_cutting_plane(beam_a, beam_b): + """Returns the plane from beam_b that cuts beam_a.""" + ref_side_dict = beam_ref_side_incidence(beam_b, beam_a, ignore_ends=True) + ref_side_index = max(ref_side_dict, key=ref_side_dict.get) + return Plane.from_frame(beam_a.ref_sides[ref_side_index]) + + def check_elements_compatibility(self): + """Checks if the elements are compatible for the creation of the joint. + + Raises + ------ + UserWarning + If the elements are not compatible for the creation of the joint. + """ + # TODO: This warning should be providing more information to the caller in regards to the affected beams and joints. + if not are_beams_coplanar(*self.elements): + warnings.warn("The beams are not coplanar, therefore BTLxProcessings will not be generated for this joint", UserWarning) + 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.beam_by_guid(self.main_beam_guid) - self.cross_beam = model.beam_by_guid(self.cross_beam_guid) + self.main_beam = model.element_by_guid(self.main_beam_guid) + self.cross_beam = model.element_by_guid(self.cross_beam_guid) + + def _get_lap_lengths(self): + lap_length_main = self.cross_beam.side_as_surface(self.cross_ref_side_index).ysize + lap_length_cross = self.main_beam.side_as_surface(self.main_ref_side_index).ysize + return lap_length_main, lap_length_cross + + def _get_lap_depths(self): + """Returns the lap depths from the distance between the two lap faces and the bias value.""" + main_frame = self.main_beam.ref_sides[self.main_ref_side_index] + cross_frame = self.cross_beam.ref_sides[self.cross_ref_side_index] + + vect = main_frame.point - cross_frame.point + cross_vect = self.main_beam.centerline.direction.cross(self.cross_beam.centerline.direction) + cross_vect.unitize() + lap_depth = abs(cross_vect.dot(vect)) + + main_lap_depth = lap_depth * self.cut_plane_bias + cross_lap_depth = lap_depth * (1 - self.cut_plane_bias) + + main_height = self.main_beam.get_dimensions_relative_to_side(self.main_ref_side_index)[1] + cross_height = self.cross_beam.get_dimensions_relative_to_side(self.cross_ref_side_index)[1] + + if main_lap_depth >= main_height or cross_lap_depth >= cross_height: # TODO: should we instead bypass the bias and use the max. possible depth? + raise BeamJoiningError(beams=self.elements, joint=self, debug_info="Lap depth is bigger than the beam's height. Consider revising the bias.") + return main_lap_depth, cross_lap_depth + + def get_main_cutting_frame(self): + assert self.elements + beam_a, beam_b = self.elements + + _, cfr = self.get_face_most_towards_beam(beam_a, beam_b) + cfr = Frame(cfr.point, cfr.yaxis, cfr.xaxis) # flip normal towards the inside of main beam + return cfr + + def get_cross_cutting_frame(self): + assert self.elements + beam_a, beam_b = self.elements + _, cfr = self.get_face_most_towards_beam(beam_b, beam_a) + return cfr @staticmethod def _sort_beam_planes(beam, cutplane_vector): @@ -111,20 +221,6 @@ def _create_polyhedron(plane_a, lines, bias): # Hexahedron from 2 Planes and 4 ], ) - def get_main_cutting_frame(self): - assert self.elements - beam_a, beam_b = self.elements - - _, cfr = self.get_face_most_towards_beam(beam_a, beam_b) - cfr = Frame(cfr.point, cfr.yaxis, cfr.xaxis) # flip normal towards the inside of main beam - return cfr - - def get_cross_cutting_frame(self): - assert self.elements - beam_a, beam_b = self.elements - _, cfr = self.get_face_most_towards_beam(beam_b, beam_a) - return cfr - def _create_negative_volumes(self): assert self.elements beam_a, beam_b = self.elements diff --git a/src/compas_timber/connections/t_butt.py b/src/compas_timber/connections/t_butt.py index 2d2ef8835..d42180e99 100644 --- a/src/compas_timber/connections/t_butt.py +++ b/src/compas_timber/connections/t_butt.py @@ -155,13 +155,15 @@ def add_features(self): # apply the pocket on the cross beam if self.mill_depth: cross_cutting_plane = self.main_beam.ref_sides[self.main_beam_ref_side_index] - lap_width = self.main_beam.height if self.main_beam_ref_side_index % 2 == 0 else self.main_beam.width + lap_length = self.main_beam.get_dimensions_relative_to_side(self.main_beam_ref_side_index)[1] + cross_feature = Lap.from_plane_and_beam( cross_cutting_plane, self.cross_beam, - lap_width, + lap_length, self.mill_depth, - self.cross_beam_ref_side_index, + is_pocket=True, + ref_side_index=self.cross_beam_ref_side_index, ) self.cross_beam.add_features(cross_feature) self.features.append(cross_feature) diff --git a/src/compas_timber/connections/t_halflap.py b/src/compas_timber/connections/t_halflap.py deleted file mode 100644 index d21110bc0..000000000 --- a/src/compas_timber/connections/t_halflap.py +++ /dev/null @@ -1,86 +0,0 @@ -from compas.geometry import Frame - -from compas_timber.connections.lap_joint import LapJoint -from compas_timber.elements import CutFeature -from compas_timber.elements import MillVolume -from compas_timber.errors import BeamJoiningError - -from .solver import JointTopology - - -class THalfLapJoint(LapJoint): - """Represents a T-Lap type joint which joins the end of a beam along the length of another beam, - trimming the main beam. - - This joint type is compatible with beams in T topology. - - Please use `THalfLapJoint.create()` to properly create an instance of this class and associate it with a model. - - Parameters - ---------- - main_beam : :class:`~compas_timber.parts.Beam` - The main beam to be joined. - cross_beam : :class:`~compas_timber.parts.Beam` - The cross beam to be joined. - flip_lap_side : bool - If True, the lap is flipped to the other side of the beams. - cut_plane_bias : float - Allows lap to be shifted deeper into one beam or the other. Value should be between 0 and 1.0 without completely cutting through either beam. Default is 0.5. - - """ - - SUPPORTED_TOPOLOGY = JointTopology.TOPO_T - - def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5): - super(THalfLapJoint, self).__init__(main_beam, cross_beam, flip_lap_side, cut_plane_bias) - - def add_extensions(self): - """Calculates and adds the necessary extensions to the beams. - - This method is automatically called when joint is created by the call to `Joint.create()`. - - Raises - ------ - BeamJoiningError - If the extension could not be calculated. - - """ - assert self.main_beam and self.cross_beam # should never happen - - main_cutting_frame = None - try: - main_cutting_frame = self.get_main_cutting_frame() - start_main, end_main = self.main_beam.extension_to_plane(main_cutting_frame) - except AttributeError as ae: - raise BeamJoiningError(beams=self.beams, joint=self, debug_info=str(ae), debug_geometries=[main_cutting_frame]) - except Exception as ex: - raise BeamJoiningError(beams=self.beams, joint=self, debug_info=str(ex)) - - extension_tolerance = 0.01 # TODO: this should be proportional to the unit used - self.main_beam.add_blank_extension(start_main + extension_tolerance, end_main + extension_tolerance, self.guid) - - 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) - - main_cutting_frame = None - try: - main_cutting_frame = self.get_main_cutting_frame() - negative_brep_main_beam, negative_brep_cross_beam = self._create_negative_volumes() - except AttributeError as ae: - raise BeamJoiningError(beams=self.beams, joint=self, debug_info=str(ae), debug_geometries=[main_cutting_frame]) - except Exception as ex: - raise BeamJoiningError(beams=self.beams, joint=self, debug_info=str(ex)) - - main_volume = MillVolume(negative_brep_main_beam) - cross_volume = MillVolume(negative_brep_cross_beam) - self.main_beam.add_features(main_volume) - self.cross_beam.add_features(cross_volume) - - trim_frame = Frame(main_cutting_frame.point, main_cutting_frame.xaxis, -main_cutting_frame.yaxis) - f_main = CutFeature(trim_frame) - self.main_beam.add_features(f_main) - - self.features = [main_volume, cross_volume, f_main] diff --git a/src/compas_timber/connections/t_lap.py b/src/compas_timber/connections/t_lap.py new file mode 100644 index 000000000..105c3a999 --- /dev/null +++ b/src/compas_timber/connections/t_lap.py @@ -0,0 +1,148 @@ +from compas.geometry import Frame + +from compas_timber.elements.features import CutFeature +from compas_timber.elements.features import MillVolume +from compas_timber.errors import BeamJoiningError +from compas_timber.fabrication import JackRafterCut +from compas_timber.fabrication import Lap + +from .lap_joint import LapJoint +from .solver import JointTopology +from .utilities import are_beams_coplanar + + +class TLapJoint(LapJoint): + """Represents a T-Lap type joint which joins the end of a beam along the length of another beam with a lap. + + This joint type is compatible with beams in T topology. + + Please use `TLapJoint.create()` to properly create an instance of this class and associate it with a model. + + Parameters + ---------- + main_beam : :class:`~compas_timber.parts.Beam` + The main beam to be joined. + cross_beam : :class:`~compas_timber.parts.Beam` + The cross beam to be joined. + flip_lap_side : bool + If True, the lap is flipped to the other side of the beams. + cut_plane_bias : float + Allows lap to be shifted deeper into one beam or the other. Value should be between 0 and 1.0 without completely cutting through either beam. Default is 0.5. + + Attributes + ---------- + main_beam : :class:`~compas_timber.parts.Beam` + The main beam to be joined. + cross_beam : :class:`~compas_timber.parts.Beam` + The cross beam to be joined. + flip_lap_side : bool + If True, the lap is flipped to the other side of the beams. + cut_plane_bias : float + Allows lap to be shifted deeper into one beam or the other. Value should be between 0 and 1.0 without completely cutting through either beam. Default is 0.5. + + """ + + SUPPORTED_TOPOLOGY = JointTopology.TOPO_T + + def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5, **kwargs): + super(TLapJoint, self).__init__(main_beam, cross_beam, flip_lap_side, cut_plane_bias, **kwargs) + + def add_extensions(self): + """Calculates and adds the necessary extensions to the beams. + + This method is automatically called when joint is created by the call to `Joint.create()`. + + Raises + ------ + BeamJoiningError + If the extension could not be calculated. + + """ + assert self.main_beam and self.cross_beam + try: + start_main, end_main = self.main_beam.extension_to_plane(self.main_cutting_plane) + except AttributeError as ae: + raise BeamJoiningError( + beams=self.elements, + joint=self, + debug_info=str(ae), + debug_geometries=[self.main_cutting_plane], + ) + except Exception as ex: + raise BeamJoiningError(beams=self.elements, joint=self, debug_info=str(ex)) + extension_tolerance = 0.01 # TODO: this should be proportional to the unit used + self.main_beam.add_blank_extension( + start_main + extension_tolerance, + end_main + extension_tolerance, + self.guid, + ) + + def add_features(self): + """Adds the required extension and trimming features to both beams. + + This method is automatically called when joint is created by the call to `Joint.create()`. + + """ + assert self.main_beam and self.cross_beam + + if self.features: + self.main_beam.remove_features(self.features) + self.cross_beam.remove_features(self.features) + + if are_beams_coplanar(*self.elements): # TODO: this is a temporal solution to allow the vizualization of non-coplanar lap joints. + # calculate the lap length and depth for each beam + main_lap_length, cross_lap_length = self._get_lap_lengths() + main_lap_depth, cross_lap_depth = self._get_lap_depths() + + ## cross_beam features + # cross Lap feature + cross_lap_feature = Lap.from_plane_and_beam( + self.cross_cutting_plane, + self.cross_beam, + cross_lap_length, + cross_lap_depth, + ref_side_index=self.cross_ref_side_index, + ) + # register features to the joint + self.cross_beam.add_features(cross_lap_feature) + self.features.append(cross_lap_feature) + + ## main_beam features + # main Lap feature + main_lap_feature = Lap.from_plane_and_beam( + self.main_cutting_plane, + self.main_beam, + main_lap_length, + main_lap_depth, + ref_side_index=self.main_ref_side_index, + ) + # cutoff feature for main beam + main_cut_feature = JackRafterCut.from_plane_and_beam(self.main_cutting_plane, self.main_beam) + # register features to the joint + main_features = [main_lap_feature, main_cut_feature] + self.main_beam.add_features(main_features) + self.features.extend(main_features) + + else: + # TODO: this is a temporal solution to avoid the error if beams are not coplanar and allow the visualization of the joint. + # TODO: this solution does not generate machining features and therefore will be ignored in the fabrication process. + # TODO: once the Lap BTLx processing implimentation allows for non-coplanar beams, this should be removed. + main_cutting_frame = None + try: + main_cutting_frame = self.get_main_cutting_frame() + negative_brep_main_beam, negative_brep_cross_beam = self._create_negative_volumes() + except AttributeError as ae: + raise BeamJoiningError(beams=self.beams, joint=self, debug_info=str(ae), debug_geometries=[main_cutting_frame]) + except Exception as ex: + raise BeamJoiningError(beams=self.beams, joint=self, debug_info=str(ex)) + + main_volume = MillVolume(negative_brep_main_beam) + cross_volume = MillVolume(negative_brep_cross_beam) + self.main_beam.add_features(main_volume) + self.cross_beam.add_features(cross_volume) + + trim_frame = Frame(main_cutting_frame.point, main_cutting_frame.xaxis, -main_cutting_frame.yaxis) + f_main = CutFeature(trim_frame) + self.main_beam.add_features(f_main) + + self.features = [main_volume, cross_volume, f_main] diff --git a/src/compas_timber/connections/t_step_joint.py b/src/compas_timber/connections/t_step_joint.py index ef299e78c..9bbed6c17 100644 --- a/src/compas_timber/connections/t_step_joint.py +++ b/src/compas_timber/connections/t_step_joint.py @@ -97,7 +97,7 @@ def __init__( main_width = self.main_beam.width if swap_main_dimensions else self.main_beam.height main_height = self.main_beam.height if swap_main_dimensions else self.main_beam.width # For the cross beam, use width or height based on the alignment - cross_width = self.cross_beam.width if self.cross_beam_ref_side_index % 2 == 0 else self.cross_beam.height + cross_width = self.cross_beam.get_dimensions_relative_to_side(self.cross_beam_ref_side_index)[0] self.start_y = (cross_width - main_width) / 2 if cross_width > main_width else 0.0 self.notch_limited = False diff --git a/src/compas_timber/connections/t_tenon_mortise.py b/src/compas_timber/connections/t_tenon_mortise.py index 23ea3a003..5f33d1e63 100644 --- a/src/compas_timber/connections/t_tenon_mortise.py +++ b/src/compas_timber/connections/t_tenon_mortise.py @@ -157,8 +157,7 @@ def tenon_shape(self): def set_default_values(self): """Sets default values for attributes if they are not provided.""" - width = self.main_beam.width if self.main_beam_ref_side_index % 2 == 0 else self.main_beam.height - height = self.main_beam.height if self.main_beam_ref_side_index % 2 == 0 else self.main_beam.width + width, height = self.main_beam.get_dimensions_relative_to_side(self.main_beam_ref_side_index) # assign default values self.start_y = self.start_y or 0.0 self.start_depth = self.start_depth or 0.0 diff --git a/src/compas_timber/connections/utilities.py b/src/compas_timber/connections/utilities.py index d7edef055..61a147e25 100644 --- a/src/compas_timber/connections/utilities.py +++ b/src/compas_timber/connections/utilities.py @@ -168,16 +168,13 @@ def are_beams_coplanar(beam_a, beam_b, tol=TOL): 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) - 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 + for beam in [beam_a, beam_b]: + # Check if the cross product is parallel to the normal of the beam's frame + dot_with_beam_normal = abs(cross_vector.dot(beam.frame.normal)) + is_beam_normal_coplanar = tol.is_close(dot_with_beam_normal, 1.0) or tol.is_zero(dot_with_beam_normal) + if not is_beam_normal_coplanar: + return False + return True def point_centerline_towards_joint(beam_a, beam_b): diff --git a/src/compas_timber/connections/x_halflap.py b/src/compas_timber/connections/x_halflap.py deleted file mode 100644 index 8f5a5ae7b..000000000 --- a/src/compas_timber/connections/x_halflap.py +++ /dev/null @@ -1,45 +0,0 @@ -from compas_timber.elements import MillVolume -from compas_timber.errors import BeamJoiningError - -from .lap_joint import LapJoint -from .solver import JointTopology - - -class XHalfLapJoint(LapJoint): - """Represents a X-Lap type joint which joins the end of a beam along the length of another beam, - trimming the main beam. - - This joint type is compatible with beams in T topology. - - Please use `XHalfLapJoint.create()` to properly create an instance of this class and associate it with a model. - - Parameters - ---------- - main_beam : :class:`~compas_timber.parts.Beam` - The main beam to be joined. - cross_beam : :class:`~compas_timber.parts.Beam` - The cross beam to be joined. - flip_lap_side : bool - If True, the lap is flipped to the other side of the beams. - cut_plane_bias : float - Allows lap to be shifted deeper into one beam or the other. Value should be between 0 and 1.0 without completely cutting through either beam. Default is 0.5. - - """ - - SUPPORTED_TOPOLOGY = JointTopology.TOPO_X - - def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5, **kwargs): - super(XHalfLapJoint, self).__init__(main_beam, cross_beam, flip_lap_side, cut_plane_bias, **kwargs) - - def add_features(self): - assert self.main_beam and self.cross_beam # should never happen - - try: - negative_brep_beam_a, negative_brep_beam_b = self._create_negative_volumes() - except Exception as ex: - raise BeamJoiningError(beams=self.beams, joint=self, debug_info=str(ex)) - volume_a = MillVolume(negative_brep_beam_a) - volume_b = MillVolume(negative_brep_beam_b) - self.main_beam.add_features(volume_a) - self.cross_beam.add_features(volume_b) - self.features = [volume_a, volume_b] diff --git a/src/compas_timber/connections/x_lap.py b/src/compas_timber/connections/x_lap.py new file mode 100644 index 000000000..b3ae9f2d6 --- /dev/null +++ b/src/compas_timber/connections/x_lap.py @@ -0,0 +1,101 @@ +from compas_timber.elements.features import MillVolume +from compas_timber.errors import BeamJoiningError +from compas_timber.fabrication import Lap + +from .lap_joint import LapJoint +from .solver import JointTopology +from .utilities import are_beams_coplanar + + +class XLapJoint(LapJoint): + """Represents an X-Lap type joint which joins the two beams somewhere along their length with a lap. + + This joint type is compatible with beams in X topology. + + Please use `XLapJoint.create()` to properly create an instance of this class and associate it with a model. + + Parameters + ---------- + main_beam : :class:`~compas_timber.parts.Beam` + The first beam to be joined. + cross_beam : :class:`~compas_timber.parts.Beam` + The second beam to be joined. + flip_lap_side : bool + If True, the lap is flipped to the other side of the beams. + cut_plane_bias : float + Allows lap to be shifted deeper into one beam or the other. Value should be between 0 and 1.0 without completely cutting through either beam. Default is 0.5. + + Attributes + ---------- + main_beam : :class:`~compas_timber.parts.Beam` + The first beam to be joined. + cross_beam : :class:`~compas_timber.parts.Beam` + The second beam to be joined. + flip_lap_side : bool + If True, the lap is flipped to the other side of the beams. + cut_plane_bias : float + Allows lap to be shifted deeper into one beam or the other. Value should be between 0 and 1.0 without completely cutting through either beam. Default is 0.5. + """ + + SUPPORTED_TOPOLOGY = JointTopology.TOPO_X + + def __init__(self, main_beam=None, cross_beam=None, flip_lap_side=False, cut_plane_bias=0.5, **kwargs): + super(XLapJoint, self).__init__(main_beam, cross_beam, flip_lap_side, cut_plane_bias, **kwargs) + + def add_features(self): + """Adds the required extension and trimming features to both beams. + + This method is automatically called when joint is created by the call to `Joint.create()`. + + """ + assert self.main_beam and self.cross_beam + + if self.features: + self.main_beam.remove_features(self.features) + self.cross_beam.remove_features(self.features) + + if are_beams_coplanar(*self.elements): # TODO: this is a temporal solution to allow the vizualization of non-coplanar lap joints. + # calculate the lap length and depth for each beam + main_lap_length, cross_lap_length = self._get_lap_lengths() + main_lap_depth, cross_lap_depth = self._get_lap_depths() + + ## main_beam features + # main Lap feature + main_lap_feature = Lap.from_plane_and_beam( + self.main_cutting_plane, + self.main_beam, + main_lap_length, + main_lap_depth, + ref_side_index=self.main_ref_side_index, + ) + + ## cross_beam features + # cross Lap feature + cross_lap_feature = Lap.from_plane_and_beam( + self.cross_cutting_plane, + self.cross_beam, + cross_lap_length, + cross_lap_depth, + ref_side_index=self.cross_ref_side_index, + ) + + # add features to the beams + self.main_beam.add_features(main_lap_feature) + self.cross_beam.add_features(cross_lap_feature) + + # register features to the joint + self.features.extend([main_lap_feature, cross_lap_feature]) + + else: + # TODO: this is a temporal solution to avoid the error if beams are not coplanar and allow the visualization of the joint. + # TODO: this solution does not generate machining features and therefore will be ignored in the fabrication process. + # TODO: once the Lap BTLx processing implimentation allows for non-coplanar beams, this should be removed. + try: + negative_brep_beam_a, negative_brep_beam_b = self._create_negative_volumes() + except Exception as ex: + raise BeamJoiningError(beams=self.beams, joint=self, debug_info=str(ex)) + volume_a = MillVolume(negative_brep_beam_a) + volume_b = MillVolume(negative_brep_beam_b) + self.main_beam.add_features(volume_a) + self.cross_beam.add_features(volume_b) + self.features = [volume_a, volume_b] diff --git a/src/compas_timber/design/workflow.py b/src/compas_timber/design/workflow.py index 59b7a8b20..e7b86f3b0 100644 --- a/src/compas_timber/design/workflow.py +++ b/src/compas_timber/design/workflow.py @@ -2,7 +2,7 @@ from compas_timber.connections import JointTopology from compas_timber.connections import LMiterJoint from compas_timber.connections import TButtJoint -from compas_timber.connections import XHalfLapJoint +from compas_timber.connections import XLapJoint from compas_timber.utils import intersection_line_line_param @@ -52,7 +52,7 @@ def get_topology_rules(rules, use_defaults=False): topo_rules = { JointTopology.TOPO_L: TopologyRule(JointTopology.TOPO_L, LMiterJoint), JointTopology.TOPO_T: TopologyRule(JointTopology.TOPO_T, TButtJoint), - JointTopology.TOPO_X: TopologyRule(JointTopology.TOPO_X, XHalfLapJoint), + JointTopology.TOPO_X: TopologyRule(JointTopology.TOPO_X, XLapJoint), } for rule in rules: # separate category and topo and direct joint rules if rule.__class__.__name__ == "TopologyRule": diff --git a/src/compas_timber/elements/beam.py b/src/compas_timber/elements/beam.py index 292239745..23bd87306 100644 --- a/src/compas_timber/elements/beam.py +++ b/src/compas_timber/elements/beam.py @@ -629,3 +629,22 @@ def endpoint_closest_to_point(self, point): return "start", ps else: return "end", pe + + def get_dimensions_relative_to_side(self, ref_side_index): + """Returns the perpendicular and parallel dimensions of the beam to the given reference side. + + Parameters + ---------- + ref_side_index : int + The index of the reference side to which the dimensions should be calculated. + + Returns + ------- + tuple(float, float) + The perpendicular and parallel dimensions of the beam to the reference side. + - Perpendicular dimension: The measurement at a right angle to the reference side. + - Parallel dimension: The measurement along the same direction as the reference side. + """ + if ref_side_index in [1, 3]: + return self.height, self.width + return self.width, self.height diff --git a/src/compas_timber/fabrication/btlx.py b/src/compas_timber/fabrication/btlx.py index d02e8a446..4e0bd2d07 100644 --- a/src/compas_timber/fabrication/btlx.py +++ b/src/compas_timber/fabrication/btlx.py @@ -607,16 +607,29 @@ class MachiningLimits(object): Limit the front face. face_limited_back : bool Limit the back face. + face_limited_top : bool + Limit the top face. + face_limited_bottom : bool + Limit the bottom face. """ - EXPECTED_KEYS = ["FaceLimitedStart", "FaceLimitedEnd", "FaceLimitedFront", "FaceLimitedBack"] + EXPECTED_KEYS = [ + "FaceLimitedStart", + "FaceLimitedEnd", + "FaceLimitedFront", + "FaceLimitedBack", + "FaceLimitedTop", + "FaceLimitedBottom", + ] def __init__(self): self.face_limited_start = True self.face_limited_end = True self.face_limited_front = True self.face_limited_back = True + self.face_limited_top = True + self.face_limited_bottom = True @property def limits(self): @@ -626,6 +639,8 @@ def limits(self): "FaceLimitedEnd": self.face_limited_end, "FaceLimitedFront": self.face_limited_front, "FaceLimitedBack": self.face_limited_back, + "FaceLimitedTop": self.face_limited_top, + "FaceLimitedBottom": self.face_limited_bottom, } diff --git a/src/compas_timber/fabrication/french_ridge_lap.py b/src/compas_timber/fabrication/french_ridge_lap.py index 8200b0ae5..7118ffeaa 100644 --- a/src/compas_timber/fabrication/french_ridge_lap.py +++ b/src/compas_timber/fabrication/french_ridge_lap.py @@ -373,7 +373,7 @@ def lap_volume_from_params_and_beam(self, beam): opp_edge = ref_edge.translated(ref_side.yaxis * ref_side.ysize) # get the height of the beam and the edge length of the lap - height = beam.height if self.ref_side_index % 2 == 0 else beam.width + height = beam.get_dimensions_relative_to_side(self.ref_side_index)[1] edge_length = ref_side.ysize / math.sin(math.radians(self.angle)) if self.orientation == OrientationType.END: edge_length = -edge_length diff --git a/src/compas_timber/fabrication/lap.py b/src/compas_timber/fabrication/lap.py index 8220c8483..53245ce7b 100644 --- a/src/compas_timber/fabrication/lap.py +++ b/src/compas_timber/fabrication/lap.py @@ -1,17 +1,20 @@ import math -from compas.geometry import Box +from compas.datastructures import Mesh from compas.geometry import Brep from compas.geometry import Frame from compas.geometry import Line from compas.geometry import Plane from compas.geometry import Point +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 intersection_plane_plane_plane from compas.geometry import is_point_behind_plane from compas.tolerance import TOL +from compas.tolerance import Tolerance from compas_timber.errors import FeatureApplicationError @@ -287,31 +290,9 @@ def machining_limits(self, machining_limits): ######################################################################## @classmethod - def from_beam_and_beam(cls, main_beam, cross_beam, depth): - """Create a Lap instance from a main beam and a cross beam. The main beam is cut by the cross beam. - - Parameters - ---------- - main_beam : :class:`~compas_timber.elements.Beam` - The main beam to be cut. - cross_beam : :class:`~compas_timber.elements.Beam` - The cross beam that cuts the main beam. - depth : float - The depth of the lap. - - Returns - ------- - :class:`~compas_timber.fabrication.Lap` - - """ - # type: (Beam, Beam, float) -> Lap - - raise NotImplementedError - - - @classmethod - def from_plane_and_beam(cls, plane, beam, width, depth, ref_side_index=0): - """Create a Lap instance from a plane and a beam. The lap is defined by the plane given and a plane parallel to that at a distance defined by the width. + def from_plane_and_beam(cls, plane, beam, length, depth, is_pocket=False, ref_side_index=0): + """Create a Lap instance from a plane and a beam. The lap is defined by the plane given and a plane parallel to that at a distance defined by the length and a given depth. + This method is used to create pocket cuts. Parameters ---------- @@ -319,10 +300,12 @@ def from_plane_and_beam(cls, plane, beam, width, depth, ref_side_index=0): The plane that defines the lap. beam : :class:`~compas_timber.elements.Beam` The beam that the Lap instance is applied to. - width : float - The width of the lap. + length : float + The length of the lap. depth : float The depth of the lap. + is_pocket : bool, optional + If True, the lap is a pocket cut. Default is False ref_side_index : int, optional The reference side index of the main_beam to be cut. Default is 0 (i.e. RS1). @@ -336,7 +319,7 @@ def from_plane_and_beam(cls, plane, beam, width, depth, ref_side_index=0): plane = Plane.from_frame(plane) # create an offset plane at the depth of the lap - offset_plane = Plane(plane.point - plane.normal * width, -plane.normal) + offset_plane = Plane(plane.point - plane.normal * length, -plane.normal) planes = [plane, offset_plane] # get ref_side, and ref_edge from the beam @@ -348,28 +331,37 @@ def from_plane_and_beam(cls, plane, beam, width, depth, ref_side_index=0): planes.sort(key=lambda plane: abs(angle_vectors_signed(ref_side.normal, plane.normal, ref_side.yaxis, deg=True))) # calculate the orientation of the lap - orientation = cls._calculate_orientation(ref_side, planes) + orientation = cls._calculate_orientation(ref_side, planes[0]) # calculate the start_x of the lap - start_x = cls._calculate_start_x(ref_side, ref_edge, planes, orientation, depth) - - # calculate the width of the lap - width = ref_side_surface.ysize + start_x = cls._calculate_start_x(ref_side, ref_edge, planes, orientation, depth, is_pocket) # calculate the angle of the lap - angle = cls._calculate_angle(ref_side, planes) + angle = cls._calculate_angle(ref_side, planes[0]) + + # calculate the inclination of the lap + inclination = cls._calculate_inclination(ref_side, planes[0], is_pocket) + + # calculate the slope of the lap + slope = 0.0 # TODO: implement slope calculation # calculate length length = cls._calculate_length(planes, ref_side, ref_edge, depth, angle) + # define the width of the lap + width = ref_side_surface.ysize + # define machining limits machining_limits = MachiningLimits() + machining_limits.face_limited_top = False machining_limits.face_limited_back = False machining_limits.face_limited_front = False return cls(orientation=orientation, start_x=start_x, angle=angle, + inclination=inclination, + slope=slope, length=length, width=width, depth=depth, @@ -377,37 +369,44 @@ def from_plane_and_beam(cls, plane, beam, width, depth, ref_side_index=0): ref_side_index=ref_side_index) @staticmethod - def _calculate_orientation(ref_side, planes): + def _calculate_orientation(ref_side, plane): # orientation is START if cutting plane normal points towards the start of the beam and END otherwise - if is_point_behind_plane(ref_side.point, planes[0]): + if is_point_behind_plane(ref_side.point, plane): return OrientationType.END else: return OrientationType.START @staticmethod - def _calculate_start_x(ref_side, ref_edge, planes, orientation, depth): - # offset the reference edge by the depth of the lap - offseted_ref_edge = ref_edge.translated(-ref_side.normal*depth) - ref_edges = [offseted_ref_edge, ref_edge] - # find the intersection points of the planes with the reference edges and calculate the distances from the start of the beam + def _calculate_start_x(ref_side, ref_edge, planes, orientation, depth, is_pocket): + # calculate the start x distance based on intersections of planes with reference edges. + # if the lap is meant for a pocket one must consider the offseted reference edge + if is_pocket: + offseted_ref_edge = ref_edge.translated(-ref_side.normal * depth) + ref_edges = [offseted_ref_edge, ref_edge] + else: + ref_edges = [ref_edge] x_distances = [] - for plane, edge in zip(planes, ref_edges): + for edge, plane in zip(ref_edges, planes): if intersection_line_plane(edge, plane) is None: raise ValueError("One of the planes does not intersect with the beam.") - intersection_point = Point(*intersection_line_plane(edge, plane)) x_distances.append(distance_point_point(edge.start, intersection_point)) - if orientation == OrientationType.END: - return max(x_distances) - else: - return min(x_distances) + return max(x_distances) if orientation == OrientationType.END else min(x_distances) @staticmethod - def _calculate_angle(ref_side, planes): + def _calculate_angle(ref_side, plane): # vector rotation direction of the plane's normal in the vertical direction - angle_vector = Vector.cross(ref_side.normal, planes[0].normal) + angle_vector = Vector.cross(ref_side.normal, plane.normal) return abs(angle_vectors_signed(ref_side.xaxis, angle_vector, ref_side.normal, deg=True)) + @staticmethod + def _calculate_inclination(ref_side, plane, is_pocket): + # vector rotation direction of the plane's normal in the vertical direction + if is_pocket: + return 90.0 + else: + return abs(angle_vectors_signed(ref_side.normal, plane.normal, ref_side.normal, deg=True)) + @staticmethod def _calculate_length(planes, ref_side, ref_edge, depth, angle): # calculate the length of the lap based on the intersection points of the planes with the reference edge @@ -449,8 +448,19 @@ def apply(self, geometry, beam): """ # type: (Brep, Beam) -> Brep - box = self.volume_from_params_and_beam(beam) - lap_volume = Brep.from_box(box) + lap_volume = self.volume_from_params_and_beam(beam) + + # convert mesh to brep + try: + lap_volume = Brep.from_mesh(lap_volume) + except Exception: + raise FeatureApplicationError( + lap_volume, + geometry, + "Could not convert the lap volume to a Brep.", + ) + + # subtract the lap volume from the beam geometry try: return geometry - lap_volume except IndexError: @@ -460,8 +470,8 @@ def apply(self, geometry, beam): "The lap volume does not intersect with the beam geometry.", ) - def volume_from_params_and_beam(self, beam): - """Calculates the volume of the cut from the machining parameters in this instance and the given beam + def planes_from_params_and_beam(self, beam): + """Calculates the planes that create the lap from the machining parameters in this instance and the given beam Parameters ---------- @@ -470,29 +480,143 @@ def volume_from_params_and_beam(self, beam): Returns ------- - :class:`compas.geometry.Box` - The boxvolume of the cut as a box. + list of :class:`compas.geometry.Plane` + The planes of the cut as a list. """ - # type: (Beam) -> Brep - - ref_side = beam.side_as_surface(self.ref_side_index) - p_origin = ref_side.point_at(self.start_x, self.start_y) + # type: (Beam) -> List[Plane] + assert self.orientation is not None + assert self.start_x is not None + assert self.start_y is not None + assert self.angle is not None + assert self.inclination is not None + assert self.slope is not None + assert self.length is not None + assert self.width is not None + assert self.depth is not None + assert self.machining_limits is not None + + ref_surface = beam.side_as_surface(self.ref_side_index) + p_origin = ref_surface.point_at(self.start_x, self.start_y) + start_frame = Frame(p_origin, ref_surface.frame.xaxis, ref_surface.frame.yaxis) + + # define angle rotation matrix + angle_angle = self.angle if self.orientation == OrientationType.END else 180-self.angle + angle_rot = Rotation.from_axis_and_angle(start_frame.zaxis, math.radians(angle_angle), point=p_origin) + # define inclination rotation matrix + inclination_axis = start_frame.xaxis if self.orientation == OrientationType.END else -start_frame.xaxis + inclination_rot = Rotation.from_axis_and_angle(inclination_axis, math.radians(self.inclination), point=p_origin) + # define slope rotation matrix + slope_axis = -start_frame.zaxis + slope_rot = Rotation.from_axis_and_angle(slope_axis, math.radians(self.slope), point=p_origin) + + # get start_face and opp_face (two sides of the cut) + start_frame.transform(angle_rot*inclination_rot*slope_rot) + end_frame = start_frame.translated(-start_frame.zaxis * self.length) + end_frame.yaxis = -end_frame.yaxis + + # get top_face and bottom_face + lead_inclination_axis = -start_frame.xaxis if self.orientation == OrientationType.END else start_frame.xaxis + lead_inclination_rot = Rotation.from_axis_and_angle(lead_inclination_axis, math.radians(self.lead_inclination), point=p_origin) + top_frame = start_frame.transformed(lead_inclination_rot) + bottom_frame = top_frame.translated(-top_frame.zaxis * self.depth) + bottom_frame.xaxis = -bottom_frame.xaxis + + # get front_face and back_face #TODO: is this correct? + front_frame = start_frame.rotated(math.radians(self.lead_angle), start_frame.xaxis, point=start_frame.point) + back_frame = front_frame.translated(-front_frame.zaxis * self.width) + back_frame.xaxis = -back_frame.xaxis + + frames = [start_frame, end_frame, top_frame, bottom_frame, front_frame, back_frame] + + # replace frames according to machining limits + frames = self._update_frames_based_on_machining_limits(frames, beam) + + return [Plane.from_frame(frame) for frame in frames] - box_frame = Frame(p_origin, ref_side.frame.xaxis, -ref_side.frame.yaxis) - rot_angle = 90-self.angle if self.orientation == OrientationType.END else self.angle-90 - box_frame.rotate(math.radians(rot_angle), box_frame.normal, point=box_frame.point) + def volume_from_params_and_beam(self, beam): + """ + Calculates the trimming volume from the machining parameters in this instance and the given beam, + ensuring correct face orientation. - box = Box(xsize=self.length, ysize=self.width, zsize=self.depth, frame=box_frame) - box.translate(box_frame.xaxis * (self.length / 2) + box_frame.yaxis * (-self.width / 2) + box_frame.zaxis * (self.depth / 2)) + Parameters + ---------- + beam : :class:`compas_timber.elements.Beam` + The beam that is cut by this instance. + Returns + ------- + :class:`compas.geometry.Mesh` + The correctly oriented trimming volume of the cut. + """ + # Get cutting frames + + start_plane, end_plane, top_plane, bottom_plane, front_plane, back_plane = self.planes_from_params_and_beam(beam) + + # Calculate vertices using plane-plane-plane intersection + vertices = [ + Point(*intersection_plane_plane_plane(start_plane, bottom_plane, front_plane)), # v0 + Point(*intersection_plane_plane_plane(start_plane, bottom_plane, back_plane)), # v1 + Point(*intersection_plane_plane_plane(end_plane, bottom_plane, back_plane)), # v2 + Point(*intersection_plane_plane_plane(end_plane, bottom_plane, front_plane)), # v3 + Point(*intersection_plane_plane_plane(start_plane, top_plane, front_plane)), # v4 + Point(*intersection_plane_plane_plane(start_plane, top_plane, back_plane)), # v5 + Point(*intersection_plane_plane_plane(end_plane, top_plane, back_plane)), # v6 + Point(*intersection_plane_plane_plane(end_plane, top_plane, front_plane)), # v7 + ] + # define faces of the trimming volume + # ensure vertices are defined in counter-clockwise order when viewed from the outside + faces = [ + [3, 2, 1, 0], # Bottom face + [7, 6, 5, 4], # Top face + [0, 1, 5, 4], # Side face 1 + [1, 2, 6, 5], # Side face 2 + [2, 3, 7, 6], # Side face 3 + [3, 0, 4, 7], # Side face 4 + ] + # ensure proper vertex order based on orientation if self.orientation == OrientationType.END: - box.translate(box_frame.xaxis * -self.length) + faces = [face[::-1] for face in faces] - if not self.machining_limits["FaceLimitedFront"] or not self.machining_limits["FaceLimitedBack"]: - box.ysize = box.ysize*10 # make the box large enough to cut through the beam + return Mesh.from_vertices_and_faces(vertices, faces) - return box + def _update_frames_based_on_machining_limits(self, frames, beam): + """Updates the frames based on the machining limits. + + Parameters + ---------- + frames : list of :class:`compas.geometry.Frame` + The frames of the cut. + beam : :class:`compas_timber.elements.Beam` + The beam that is cut by this instance. + + Returns + ------- + list of :class:`compas.geometry.Frame` + The updated frames of the cut. + """ + tol = Tolerance() + tol.absolute=1e-3 + + if not self.machining_limits["FaceLimitedStart"]: + frames[0] = beam.ref_sides[4] + if not self.machining_limits["FaceLimitedEnd"]: + frames[1] = beam.ref_sides[5] + if not self.machining_limits["FaceLimitedTop"]: + frames[2] = beam.ref_sides[self.ref_side_index] + frames[2].translate(frames[2].zaxis * tol.absolute) # offset the top frame to avoid tolerance issues + if not self.machining_limits["FaceLimitedBottom"]: + opp_ref_side_index = beam.opposing_side_index(self.ref_side_index) + frames[3] = beam.ref_sides[opp_ref_side_index] + frames[3].translate(frames[3].zaxis * tol.absolute) # offset the bottom frame to avoid tolerance issues + if not self.machining_limits["FaceLimitedFront"]: + frames[4] = beam.ref_sides[(self.ref_side_index-1)%4] + frames[4].translate(frames[4].zaxis * tol.absolute) # offset the front frame to avoid tolerance issues + if not self.machining_limits["FaceLimitedBack"]: + frames[5] = beam.ref_sides[(self.ref_side_index+1)%4] + frames[5].translate(frames[5].zaxis * tol.absolute) # offset the back frame to avoid tolerance issues + + return frames class LapParams(BTLxProcessingParams): diff --git a/src/compas_timber/fabrication/tenon.py b/src/compas_timber/fabrication/tenon.py index 30d6a6cb8..c0da8bcc1 100644 --- a/src/compas_timber/fabrication/tenon.py +++ b/src/compas_timber/fabrication/tenon.py @@ -388,7 +388,7 @@ def from_plane_and_beam( if not length_limited_top: start_depth = 0.0 if not length_limited_bottom: - beam_height = beam.height if ref_side_index % 2 == 0 else beam.width + beam_height = beam.get_dimensions_relative_to_side(ref_side_index)[1] length = beam_height / math.sin(math.radians(inclination)) - start_depth # calculate start_x @@ -454,14 +454,14 @@ def _calculate_start_y(beam, orientation, start_y, ref_side_index): # calculate the start_y of the cut based on the beam, orientation, start_y and ref_side_index if orientation == OrientationType.END: start_y = -start_y - beam_width = beam.width if ref_side_index % 2 == 0 else beam.height + beam_width= beam.get_dimensions_relative_to_side(ref_side_index)[0] return start_y + beam_width / 2 @staticmethod def _calculate_start_depth(beam, inclination, length, ref_side_index): # calculate the start_depth of the tenon from height of the beam and the projected length of the tenon proj_length = (length * math.sin(math.radians(inclination))) - beam_height = beam.height if ref_side_index % 2 == 0 else beam.width + beam_height = beam.get_dimensions_relative_to_side(ref_side_index)[1] return (beam_height - proj_length)/2 @staticmethod diff --git a/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology/code.py b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology/code.py index b11987eb9..6f9b2bd12 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology/code.py +++ b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology/code.py @@ -3,7 +3,7 @@ from compas_timber.connections import JointTopology from compas_timber.connections import LMiterJoint from compas_timber.connections import TButtJoint -from compas_timber.connections import XHalfLapJoint +from compas_timber.connections import XLapJoint from compas_timber.design import TopologyRule @@ -12,6 +12,6 @@ def RunScript(self): topoRules = [] topoRules.append(TopologyRule(JointTopology.TOPO_L, LMiterJoint)) topoRules.append(TopologyRule(JointTopology.TOPO_T, TButtJoint)) - topoRules.append(TopologyRule(JointTopology.TOPO_X, XHalfLapJoint)) + topoRules.append(TopologyRule(JointTopology.TOPO_X, XLapJoint)) return topoRules diff --git a/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_X/code.py b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_X/code.py index ef2204b51..8f321a9b6 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_X/code.py +++ b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_X/code.py @@ -5,7 +5,7 @@ from compas_timber.connections import Joint from compas_timber.connections import JointTopology -from compas_timber.connections import XHalfLapJoint +from compas_timber.connections import XLapJoint from compas_timber.design import TopologyRule from compas_timber.ghpython.ghcomponent_helpers import get_leaf_subclasses from compas_timber.ghpython.ghcomponent_helpers import manage_dynamic_params @@ -23,7 +23,7 @@ def __init__(self): if JointTopology.TOPO_X in supported_topo: self.classes[cls.__name__] = cls if ghenv.Component.Params.Output[0].NickName == "Rule": - self.joint_type = XHalfLapJoint + self.joint_type = XLapJoint self.clicked = False else: self.joint_type = self.classes.get(ghenv.Component.Params.Output[0].NickName, None) @@ -31,9 +31,9 @@ def __init__(self): def RunScript(self, *args): if not self.clicked: - ghenv.Component.Message = "Default: XHalfLapJoint" - self.AddRuntimeMessage(Warning, "XHalfLapJoint is default, change in context menu (right click)") - return TopologyRule(JointTopology.TOPO_X, XHalfLapJoint) + ghenv.Component.Message = "Default: XLapJoint" + self.AddRuntimeMessage(Warning, "XLapJoint is default, change in context menu (right click)") + return TopologyRule(JointTopology.TOPO_X, XLapJoint) else: ghenv.Component.Message = self.joint_type.__name__ kwargs = {} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_X/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_X/metadata.json index a6bc36f9a..bb0574bd3 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_X/metadata.json +++ b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_X/metadata.json @@ -3,7 +3,7 @@ "nickname": "X_Topo_Joint", "category": "COMPAS Timber", "subcategory": "Joint Rules", - "description": "makes rule to apply joint type to X topology. Defualts to XHalfLapJoint", + "description": "makes rule to apply joint type to X topology. Defualts to XLapJoint", "exposure": 4, "ghpython": { "isAdvancedMode": true, diff --git a/src/compas_timber/ghpython/components/CT_Model/code.py b/src/compas_timber/ghpython/components/CT_Model/code.py index 9999b11cc..28e3c154f 100644 --- a/src/compas_timber/ghpython/components/CT_Model/code.py +++ b/src/compas_timber/ghpython/components/CT_Model/code.py @@ -6,7 +6,7 @@ from compas_timber.connections import JointTopology from compas_timber.connections import LMiterJoint from compas_timber.connections import TButtJoint -from compas_timber.connections import XHalfLapJoint +from compas_timber.connections import XLapJoint from compas_timber.design import DebugInfomation from compas_timber.design import JointRule from compas_timber.elements import Beam @@ -14,7 +14,7 @@ from compas_timber.model import TimberModel JOINT_DEFAULTS = { - JointTopology.TOPO_X: XHalfLapJoint, + JointTopology.TOPO_X: XLapJoint, JointTopology.TOPO_T: TButtJoint, JointTopology.TOPO_L: LMiterJoint, } diff --git a/tests/compas_timber/test_joint.py b/tests/compas_timber/test_joint.py index 8a0d31015..ad13a508d 100644 --- a/tests/compas_timber/test_joint.py +++ b/tests/compas_timber/test_joint.py @@ -9,10 +9,10 @@ from compas.geometry import Vector from compas_timber.connections import LButtJoint -from compas_timber.connections import LHalfLapJoint +from compas_timber.connections import LLapJoint from compas_timber.connections import TButtJoint -from compas_timber.connections import THalfLapJoint -from compas_timber.connections import XHalfLapJoint +from compas_timber.connections import TLapJoint +from compas_timber.connections import XLapJoint from compas_timber.connections import find_neighboring_beams from compas_timber.elements import Beam from compas_timber.model import TimberModel @@ -118,15 +118,15 @@ def test_joint_create_l_butt(l_topo_beams): assert joint.elements -def test_joint_create_x_half_lap(x_topo_beams): +def test_joint_create_x_lap(x_topo_beams): model = TimberModel() - beam_a, beam_b = x_topo_beams - model.add_element(beam_a) - model.add_element(beam_b) - joint = XHalfLapJoint.create(model, beam_a, beam_b) + main_beam, cross_beam = x_topo_beams + model.add_element(main_beam) + model.add_element(cross_beam) + joint = XLapJoint.create(model, main_beam, cross_beam) - assert joint.main_beam is beam_a - assert joint.cross_beam is beam_b + assert joint.main_beam is main_beam + assert joint.cross_beam is cross_beam assert joint.elements @@ -135,7 +135,7 @@ def test_joint_create_t_lap(t_topo_beams): main_beam, cross_beam = t_topo_beams model.add_element(main_beam) model.add_element(cross_beam) - joint = THalfLapJoint.create(model, main_beam, cross_beam) + joint = TLapJoint.create(model, main_beam, cross_beam) assert joint.main_beam is main_beam assert joint.cross_beam is cross_beam @@ -144,13 +144,13 @@ def test_joint_create_t_lap(t_topo_beams): def test_joint_create_l_lap(l_topo_beams): model = TimberModel() - beam_a, beam_b = l_topo_beams - model.add_element(beam_a) - model.add_element(beam_b) - joint = LHalfLapJoint.create(model, beam_a, beam_b) + main_beam, cross_beam = l_topo_beams + model.add_element(main_beam) + model.add_element(cross_beam) + joint = LLapJoint.create(model, main_beam, cross_beam) - assert joint.main_beam is beam_a - assert joint.cross_beam is beam_b + assert joint.main_beam is main_beam + assert joint.cross_beam is cross_beam assert joint.elements @@ -196,7 +196,7 @@ def test_joint_create_kwargs_passthrough_xhalflap(): model.add_element(beam_a) model.add_element(beam_b) - joint = XHalfLapJoint.create(model, beam_a, beam_b, cut_plane_bias=0.4) + joint = XLapJoint.create(model, beam_a, beam_b, cut_plane_bias=0.4) assert joint.cut_plane_bias == 0.4 diff --git a/tests/compas_timber/test_lap.py b/tests/compas_timber/test_lap.py index e7f296b01..c3663666d 100644 --- a/tests/compas_timber/test_lap.py +++ b/tests/compas_timber/test_lap.py @@ -1,9 +1,10 @@ import pytest -from collections import OrderedDict +from compas.data import json_dumps +from compas.data import json_loads -from compas.geometry import Box from compas.geometry import Point +from compas.geometry import Plane from compas.geometry import Frame from compas.geometry import Line from compas.geometry import Vector @@ -15,344 +16,263 @@ @pytest.fixture -def cross_beam(): - width = 100 - height = 120 +def tol(): + return Tolerance(unit="MM", absolute=1e-3, relative=1e-3) + +def test_lap_for_pocket_from_frame(tol): centerline = Line( Point(x=30396.1444398, y=-3257.66821289, z=73.5839565671), Point(x=34824.6096086, y=-3257.66821289, z=73.5839565671), ) + cross_section = [60, 120] + beam = Beam.from_centerline(centerline, cross_section[0], cross_section[1]) - return Beam.from_centerline(centerline, width, height) + cutting_frame = Frame( + point=Point(x=31108.527, y=-2416.770, z=123.584), + xaxis=Vector(x=0.708, y=-0.706, z=0.000), + yaxis=Vector(x=0.000, y=-0.000, z=-1.000), + ) + lap_length = 80.0 + lap_depth = 20.0 + ref_side_index = 1 + # Lap instance + instance = Lap.from_plane_and_beam( + cutting_frame, + beam, + lap_length, + lap_depth, + is_pocket=True, + ref_side_index=ref_side_index, + ) -@pytest.fixture -def main_beams(): - width = 80 - height = 100 - - centerlines = [ - Line( - Point(x=32079.4104241, y=-3257.66821289, z=73.5839565671), - Point(x=33474.4091319, y=-4826.83669239, z=1616.81118820), - ), - Line(Point(x=33636.4572343, y=-2124.34355562, z=0.0), Point(x=32465.6288321, y=-3257.66821289, z=73.5839565671)), - Line( - Point(x=34148.4484099, y=-3257.66821289, z=1333.76053347), - Point(x=33438.9952150, y=-3257.66821289, z=73.5839565671), - ), - Line( - Point(x=34328.9231602, y=-3257.66821289, z=73.5839565671), - Point(x=35365.5736795, y=-2579.64848379, z=-2050.47736312), - ), + # attribute assertions + assert instance.orientation == "end" + assert tol.is_close(instance.start_x, 1545.323) + assert tol.is_close(instance.angle, 90.0) + assert tol.is_close(instance.inclination, 90.0) + assert tol.is_close(instance.slope, 0.0) + assert tol.is_close(instance.length, 133.325) + assert tol.is_close(instance.width, 120.0) + assert tol.is_close(instance.depth, 20.0) + assert tol.is_close(instance.lead_angle, 90.0) + assert tol.is_close(instance.lead_inclination, 90.0) + assert instance.machining_limits == { + "FaceLimitedBack": False, + "FaceLimitedStart": True, + "FaceLimitedBottom": True, + "FaceLimitedTop": False, + "FaceLimitedEnd": True, + "FaceLimitedFront": False, + } + assert instance.ref_side_index == ref_side_index + + # volume from Lap instance + mesh_volume = instance.volume_from_params_and_beam(beam) + mesh_vertices, mesh_faces = mesh_volume.to_vertices_and_faces() + + # expected vertices and faces + expected_vertices = [ + [31941.467663941679, -3247.6682128906177, 13.58295656705431], + [31941.467663941679, -3247.6682128906177, 133.58495656705432], + [31808.142267843792, -3247.6682128906177, 133.58495656705432], + [31808.142267843792, -3247.6682128906177, 13.58295656705431], + [31941.467663941679, -3227.667212890618, 13.58295656705431], + [31941.467663941679, -3227.667212890618, 133.58495656705432], + [31808.142267843792, -3227.667212890618, 133.58495656705432], + [31808.142267843792, -3227.667212890618, 13.58295656705431], ] + expected_faces = [ + [0, 1, 2, 3], + [4, 5, 6, 7], + [4, 5, 1, 0], + [5, 6, 2, 1], + [6, 7, 3, 2], + [7, 4, 0, 3], + ] + + # assert vertices + assert len(mesh_vertices) == len(expected_vertices) + for vertex, expected_vertex in zip(mesh_vertices, expected_vertices): + for coord, expected_coord in zip(vertex, expected_vertex): + assert tol.is_close(coord, expected_coord) + # assert faces + assert len(mesh_faces) == len(expected_faces) + for face, expected_face in zip(mesh_faces, expected_faces): + assert face == expected_face - return [Beam.from_centerline(centerline, width, height) for centerline in centerlines] - - -EXPECTED_LAP_PARAMS = [ - OrderedDict( - [ - ("Name", "Lap"), - ("Priority", "0"), - ("Process", "yes"), - ("ProcessID", "0"), - ("ReferencePlaneID", "4"), - ("Orientation", "start"), - ("StartX", "1665.305"), - ("StartY", "0.000"), - ("Angle", "90.000"), - ("Inclination", "90.000"), - ("Slope", "0.000"), - ("Length", "115.933"), - ("Width", "120.000"), - ("Depth", "10.000"), - ("LeadAngleParallel", "yes"), - ("LeadAngle", "90.000"), - ("LeadInclinationParallel", "yes"), - ("LeadInclination", "90.000"), - ( - "MachiningLimits", - OrderedDict( - [ - ("FaceLimitedBack", "no"), - ("FaceLimitedEnd", "yes"), - ("FaceLimitedFront", "no"), - ("FaceLimitedStart", "yes"), - ] - ), - ), - ] - ), - OrderedDict( - [ - ("Name", "Lap"), - ("Priority", "0"), - ("Process", "yes"), - ("ProcessID", "0"), - ("ReferencePlaneID", "2"), - ("Orientation", "start"), - ("StartX", "2053.296"), - ("StartY", "0.000"), - ("Angle", "90.000"), - ("Inclination", "90.000"), - ("Slope", "0.000"), - ("Length", "125.355"), - ("Width", "120.000"), - ("Depth", "10.000"), - ("LeadAngleParallel", "yes"), - ("LeadAngle", "90.000"), - ("LeadInclinationParallel", "yes"), - ("LeadInclination", "90.000"), - ( - "MachiningLimits", - OrderedDict( - [ - ("FaceLimitedBack", "no"), - ("FaceLimitedEnd", "yes"), - ("FaceLimitedFront", "no"), - ("FaceLimitedStart", "yes"), - ] - ), - ), - ] - ), - OrderedDict( - [ - ("Name", "Lap"), - ("Priority", "0"), - ("Process", "yes"), - ("ProcessID", "0"), - ("ReferencePlaneID", "3"), - ("Orientation", "start"), - ("StartX", "3013.621"), - ("StartY", "0.000"), - ("Angle", "90.000"), - ("Inclination", "90.000"), - ("Slope", "0.000"), - ("Length", "120.388"), - ("Width", "100.000"), - ("Depth", "10.000"), - ("LeadAngleParallel", "yes"), - ("LeadAngle", "90.000"), - ("LeadInclinationParallel", "yes"), - ("LeadInclination", "90.000"), - ( - "MachiningLimits", - OrderedDict( - [ - ("FaceLimitedBack", "no"), - ("FaceLimitedEnd", "yes"), - ("FaceLimitedFront", "no"), - ("FaceLimitedStart", "yes"), - ] - ), - ), - ] - ), - OrderedDict( - [ - ("Name", "Lap"), - ("Priority", "0"), - ("Process", "yes"), - ("ProcessID", "0"), - ("ReferencePlaneID", "1"), - ("Orientation", "start"), - ("StartX", "3865.756"), - ("StartY", "0.000"), - ("Angle", "123.187"), - ("Inclination", "90.000"), - ("Slope", "0.000"), - ("Length", "121.594"), - ("Width", "100.000"), - ("Depth", "10.000"), - ("LeadAngleParallel", "yes"), - ("LeadAngle", "90.000"), - ("LeadInclinationParallel", "yes"), - ("LeadInclination", "90.000"), - ( - "MachiningLimits", - OrderedDict( - [ - ("FaceLimitedBack", "no"), - ("FaceLimitedEnd", "yes"), - ("FaceLimitedFront", "no"), - ("FaceLimitedStart", "yes"), - ] - ), - ), - ] - ), -] - -EXPECTED_CUTTING_FRAMES = [ - Frame( - point=Point(x=32089.6304179, y=-3208.96062535, z=113.871952827), - xaxis=Vector(x=0.535356833682, y=-0.602197739702, z=0.59224230086), - yaxis=Vector(x=0.393493090239, y=-0.442621882493, z=-0.805759925209), - ), - Frame( - point=Point(x=33665.8981258, y=-2151.51562805, z=49.9490979824), - xaxis=Vector(x=-0.717789410535, y=-0.6947973214, z=0.0451114652743), - yaxis=Vector(x=-0.0324135303495, y=-0.0313752665244, z=-0.998981959647), - ), - Frame( - point=Point(x=34104.8785573, y=-3217.66821289, z=1358.28945402), - xaxis=Vector(x=-0.490578411027, y=0.0, z=-0.871397052229), - yaxis=Vector(x=-0.0, y=-1.0, z=0.0), - ), - Frame( - point=Point(x=34270.8814019, y=-3247.83444818, z=48.3956383955), - xaxis=Vector(x=0.421598058227, y=0.275745582418, z=-0.863839945288), - yaxis=Vector(x=0.547367991266, y=-0.836892037325, z=-2.77555756156e-17), - ), -] - -EXPECTED_BOX = [ - Box( - xsize=115.932621049, - ysize=1200.0, - zsize=10.0, - frame=Frame( - point=Point(x=32119.4156515, y=-3302.66821289, z=73.5839565671), - xaxis=Vector(x=1.0, y=-2.05374699157e-16, z=0.0), - yaxis=Vector(x=1.25751580751e-32, y=6.12303176911e-17, z=-1.0), - ), - ), - Box( - xsize=125.355190923, - ysize=1200.0, - zsize=10.0, - frame=Frame( - point=Point(x=32512.1179628, y=-3212.66821289, z=73.5839565671), - xaxis=Vector(x=1.0, y=-2.05374699157e-16, z=0.0), - yaxis=Vector(x=1.25751580751e-32, y=6.12303176911e-17, z=1.0), - ), - ), - Box( - xsize=120.388041068, - ysize=1000.0, - zsize=10.0, - frame=Frame( - point=Point(x=33469.9590708, y=-3257.66821289, z=128.583956567), - xaxis=Vector(x=1.0, y=4.26515051461e-17, z=0.0), - yaxis=Vector(x=4.26515051461e-17, y=-1.0, z=1.22460635382e-16), - ), - ), - Box( - xsize=121.593895035, - ysize=1000.0, - zsize=10.0, - frame=Frame( - point=Point(x=34340.1491250, y=-3216.23451172, z=18.5839565671), - xaxis=Vector(x=0.836892037325, y=0.547367991266, z=0.0), - yaxis=Vector(x=-0.547367991266, y=0.836892037325, z=0.0), - ), - ), -] - - -@pytest.mark.parametrize( - "expected_lap_params, expected_cutting_frames, width, depth, ref_side_index", - [ - (EXPECTED_LAP_PARAMS[0], EXPECTED_CUTTING_FRAMES[0], 80, 10, 3), # main_beam_a - (EXPECTED_LAP_PARAMS[1], EXPECTED_CUTTING_FRAMES[1], 80, 10, 1), # main_beam_b - (EXPECTED_LAP_PARAMS[2], EXPECTED_CUTTING_FRAMES[2], 100, 10, 2), # main_beam_c - (EXPECTED_LAP_PARAMS[3], EXPECTED_CUTTING_FRAMES[3], 100, 10, 0), # main_beam_d - ], -) -def test_lap_params( - cross_beam, - expected_lap_params, - expected_cutting_frames, - width, - depth, - ref_side_index, -): - # Create the Lap object - lap = Lap.from_plane_and_beam(expected_cutting_frames, cross_beam, width, depth, ref_side_index) - - # Validate generated parameters - generated_params = lap.params_dict - for key, value in expected_lap_params.items(): - assert generated_params[key] == value - - -@pytest.mark.parametrize( - "expected_lap_params, expected_box", - [ - (EXPECTED_LAP_PARAMS[0], EXPECTED_BOX[0]), - (EXPECTED_LAP_PARAMS[1], EXPECTED_BOX[1]), - (EXPECTED_LAP_PARAMS[2], EXPECTED_BOX[2]), - (EXPECTED_LAP_PARAMS[3], EXPECTED_BOX[3]), - ], -) -def test_lap_box_from_params( - cross_beam, - expected_lap_params, - expected_box, -): - # convert string values to the appropriate types (float, bool, etc.) - def convert_ordered_dict(params): - def convert_value(value): - if isinstance(value, str): - # Convert to float if the string represents a number - try: - return float(value) - except ValueError: - pass - # Convert specific strings to booleans - if value.lower() == "yes": - return True - elif value.lower() == "no": - return False - # If the value is an OrderedDict, recursively convert its items - elif isinstance(value, OrderedDict): - return OrderedDict((key, convert_value(val)) for key, val in value.items()) - return value - - # Retain the original casing of keys in the OrderedDict - return OrderedDict((key, convert_value(value)) for key, value in params.items()) - - # convert the OrderedDict values to the expected types - params = convert_ordered_dict(expected_lap_params) - - # instantiate Lap with unpacked parameters from the OrderedDict - lap = Lap( - orientation=params["Orientation"], - start_x=params["StartX"], - start_y=params["StartY"], - angle=params["Angle"], - inclination=params["Inclination"], - slope=params["Slope"], - length=params["Length"], - width=params["Width"], - depth=params["Depth"], - lead_angle_parallel=params["LeadAngleParallel"], - lead_angle=params["LeadAngle"], - lead_inclination_parallel=params["LeadInclinationParallel"], - lead_inclination=params["LeadInclination"], - machining_limits=params["MachiningLimits"], - ref_side_index=int(params["ReferencePlaneID"] - 1), + +def test_lap_for_halflaps_from_plane(tol): + centerline = Line( + Point(x=32087.5161016, y=-4629.03501941, z=73.5839565671), + Point(x=33194.4503091, y=-1833.71037612, z=73.5839565671), ) + cross_cection = [80, 100] + beam = Beam.from_centerline(centerline, cross_cection[0], cross_cection[1]) + + cutting_plane = Plane(point=Point(x=30396.144, y=-3287.668, z=13.584), normal=Vector(x=-0.000, y=-1.000, z=0.000)) + lap_length = 60.0 + lap_depth = 88.0 + ref_side_index = 2 + + # Lap instance + instance = Lap.from_plane_and_beam(cutting_plane, beam, lap_length, lap_depth, is_pocket=False, ref_side_index=ref_side_index) + + # attribute assertions + assert instance.orientation == "start" + assert tol.is_close(instance.start_x, 1458.549) + assert tol.is_close(instance.angle, 68.397) + assert tol.is_close(instance.inclination, 90.0) + assert tol.is_close(instance.slope, 0.0) + assert tol.is_close(instance.length, 60.0) + assert tol.is_close(instance.width, 80.0) + assert tol.is_close(instance.depth, 88.0) + assert tol.is_close(instance.lead_angle, 90.0) + assert tol.is_close(instance.lead_inclination, 90.0) + assert instance.machining_limits == { + "FaceLimitedBack": False, + "FaceLimitedStart": True, + "FaceLimitedBottom": True, + "FaceLimitedTop": False, + "FaceLimitedEnd": True, + "FaceLimitedFront": False, + } + assert instance.ref_side_index == ref_side_index + + # volume from Lap instance + mesh_volume = instance.volume_from_params_and_beam(beam) + mesh_vertices, mesh_faces = mesh_volume.to_vertices_and_faces() + + # expected vertices and faces + expected_vertices = [ + [32575.667318015712, -3287.6682128900229, 35.583956567057157], + [32661.713622767158, -3287.6682128900229, 35.583956567057157], + [32685.473314726958, -3227.6682128899961, 35.583956567057157], + [32599.427009975512, -3227.6682128899961, 35.583956567057157], + [32575.667318015712, -3287.6682128900229, 123.58495656705432], + [32661.713622767158, -3287.6682128900229, 123.58495656705432], + [32685.473314726958, -3227.6682128899961, 123.58495656705433], + [32599.427009975512, -3227.6682128899961, 123.58495656705433], + ] + expected_faces = [ + [3, 2, 1, 0], + [7, 6, 5, 4], + [0, 1, 5, 4], + [1, 2, 6, 5], + [2, 3, 7, 6], + [3, 0, 4, 7], + ] + + # assert vertices + assert len(mesh_vertices) == len(expected_vertices) + for vertex, expected_vertex in zip(mesh_vertices, expected_vertices): + for coord, expected_coord in zip(vertex, expected_vertex): + assert tol.is_close(coord, expected_coord) + # assert faces + assert len(mesh_faces) == len(expected_faces) + for face, expected_face in zip(mesh_faces, expected_faces): + assert face == expected_face + + +def test_lap_data(): + machining_limits = { + "FaceLimitedBack": False, + "FaceLimitedStart": True, + "FaceLimitedBottom": True, + "FaceLimitedTop": False, + "FaceLimitedEnd": True, + "FaceLimitedFront": False, + } + + instance = Lap( + "end", + 2289.328, + 0.0, + 111.603, + 90.0, + 0.0, + 80.0, + 60.0, + 22.0, + True, + 90.0, + True, + 90.0, + machining_limits, + ref_side_index=0, + ) + copied_instance = json_loads(json_dumps(instance)) + + assert copied_instance.orientation == instance.orientation + assert copied_instance.start_x == instance.start_x + assert copied_instance.start_y == instance.start_y + assert copied_instance.angle == instance.angle + assert copied_instance.inclination == instance.inclination + assert copied_instance.slope == instance.slope + assert copied_instance.length == instance.length + assert copied_instance.width == instance.width + assert copied_instance.depth == instance.depth + assert copied_instance.lead_angle == instance.lead_angle + assert copied_instance.lead_inclination == instance.lead_inclination + assert copied_instance.machining_limits == instance.machining_limits + assert copied_instance.ref_side_index == instance.ref_side_index + + +def test_lap_params_obj(): + machining_limits = { + "FaceLimitedBack": False, + "FaceLimitedStart": True, + "FaceLimitedBottom": True, + "FaceLimitedTop": False, + "FaceLimitedEnd": True, + "FaceLimitedFront": False, + } + + instance = Lap( + "end", + 2289.328, + 0.0, + 111.603, + 90.0, + 0.0, + 80.0, + 60.0, + 22.0, + True, + 90.0, + True, + 90.0, + machining_limits, + ref_side_index=0, + ) + + params = instance.params_dict + + assert params["Name"] == "Lap" + assert params["Process"] == "yes" + assert params["Priority"] == "0" + assert params["ProcessID"] == "0" + assert params["ReferencePlaneID"] == "1" - # generate frame from the parameters - generated_box = lap.volume_from_params_and_beam(cross_beam) - print(generated_box) - - # set the tolerance for comparing the generated and expected boxes - tolerance = Tolerance() - tolerance.absolute = 1e-3 - - def approx_point(p1, p2, tolerance): - return tolerance.is_close(p1.x, p2.x) and tolerance.is_close(p1.y, p2.y) and tolerance.is_close(p1.z, p2.z) - - # compare generated planes to expected planes using `approx` - assert tolerance.is_close(generated_box.xsize, expected_box.xsize) - assert tolerance.is_close(generated_box.ysize, expected_box.ysize) - assert tolerance.is_close(generated_box.zsize, expected_box.zsize) - assert approx_point(generated_box.frame.point, expected_box.frame.point, tolerance) - assert approx_point(generated_box.frame.xaxis, expected_box.frame.xaxis, tolerance) - assert approx_point(generated_box.frame.yaxis, expected_box.frame.yaxis, tolerance) - assert approx_point(generated_box.frame.zaxis, expected_box.frame.zaxis, tolerance) + assert params["Orientation"] == "end" + assert params["StartX"] == "2289.328" + assert params["StartY"] == "0.000" + assert params["Angle"] == "111.603" + assert params["Inclination"] == "90.000" + assert params["Slope"] == "0.000" + assert params["Length"] == "80.000" + assert params["Width"] == "60.000" + assert params["Depth"] == "22.000" + assert params["LeadAngleParallel"] == "yes" + assert params["LeadAngle"] == "90.000" + assert params["LeadInclinationParallel"] == "yes" + assert params["LeadInclination"] == "90.000" + assert params["MachiningLimits"] == { + "FaceLimitedBack": "no", + "FaceLimitedStart": "yes", + "FaceLimitedBottom": "yes", + "FaceLimitedTop": "no", + "FaceLimitedEnd": "yes", + "FaceLimitedFront": "no", + }