diff --git a/CHANGELOG.md b/CHANGELOG.md index 6046ca8c7..b83e297bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Removed argument `cutoff` from `LMiterJoint` as it was not used anywhere. * Removed argument `gap` from `TButtJoint` as it was not used anywhere. * Removed argument `gap` from `FrenchRidgeLap` as it was not used anywhere. +* Removed class `JointOptions` as not used anymore. ## [0.7.0] 2024-02-15 @@ -32,6 +33,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added new `NullJoint`. * Added `mill_depth` argument to butt joints, with geometric representation of milled recess in cross beam. * Added `ButtJoint` class with methods common to `LButtJoint` and `TButtJoint` +* Added new `L_TopologyJointRule`, `T_TopologyJointRule`, `X_TopologyJointRule` GH components +* Added GH component param support functions in `compas_timber.ghpython.ghcomponent_helpers.py` +* Added `topos` attribute to `CategoryRule` to filter when joints get applied ### Changed @@ -43,6 +47,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Changed GH Categories for joint rules. * Made `beam_side_incident` a `staticmethod` of `Joint` and reworked it. * Extended `DecomposeBeam` component to optionally show beam frame and faces. +* Changed `CategoryJointRule` and `DirectJointRule` to a dynamic interface where joint type is selected with right click menu +* Changed `Assembly` GH component to apply category joints if the detected topology is in `CategoryRule.topos` +* Changed `TopologyJoints` GH component to `DefaultJoints` Component, which applies default joints based on topology. ### Removed @@ -53,6 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `DrillHole` has default diameter proportional to beam cross-section. * Removed input `Length` from `DrillHole` component. * Fixed broken `TrimmingFeature` component. +* Removed all `JointOption` components. these are accessed in context menu of joint rules. ## [0.6.1] 2024-02-02 diff --git a/examples/Grasshopper/CT_NEW_UI_Example.3dm b/examples/Grasshopper/CT_NEW_UI_Example.3dm deleted file mode 100644 index 83d6fa67a..000000000 Binary files a/examples/Grasshopper/CT_NEW_UI_Example.3dm and /dev/null differ diff --git a/examples/Grasshopper/CT_NEW_UI_Example.gh b/examples/Grasshopper/CT_NEW_UI_Example.gh deleted file mode 100644 index 55279f0dc..000000000 Binary files a/examples/Grasshopper/CT_NEW_UI_Example.gh and /dev/null differ diff --git a/examples/Grasshopper/dynamic_gh_demo.gh b/examples/Grasshopper/dynamic_gh_demo.gh new file mode 100644 index 000000000..940d0c75a Binary files /dev/null and b/examples/Grasshopper/dynamic_gh_demo.gh differ diff --git a/setup.cfg b/setup.cfg index e7f4c1234..147757541 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,6 +8,9 @@ extend-ignore = E203, # Only keep black line length check because flake8 forces it on comments as well E501, +per-file-ignores = + # GH components often have undefined imports (e.g. ghenv) + src/compas_timber/ghpython/components/*/*.py: F821 [doc8] max-line-length = 120 diff --git a/src/compas_timber/ghpython/__init__.py b/src/compas_timber/ghpython/__init__.py index 89682ec97..d912f405d 100644 --- a/src/compas_timber/ghpython/__init__.py +++ b/src/compas_timber/ghpython/__init__.py @@ -2,9 +2,12 @@ from .workflow import TopologyRule from .workflow import DirectRule from .workflow import FeatureDefinition -from .workflow import JointOptions from .workflow import JointDefinition from .workflow import DebugInfomation +from .ghcomponent_helpers import clear_gh_params +from .ghcomponent_helpers import add_gh_param +from .ghcomponent_helpers import manage_dynamic_params + __all__ = [ "JointDefinition", @@ -12,6 +15,8 @@ "TopologyRule", "DirectRule", "FeatureDefinition", - "JointOptions", "DebugInfomation", + "clear_gh_params", + "add_gh_param", + "manage_dynamic_params", ] diff --git a/src/compas_timber/ghpython/components/CT_Assembly/code.py b/src/compas_timber/ghpython/components/CT_Assembly/code.py index e17b06218..d07a92742 100644 --- a/src/compas_timber/ghpython/components/CT_Assembly/code.py +++ b/src/compas_timber/ghpython/components/CT_Assembly/code.py @@ -7,6 +7,9 @@ from compas_timber.connections import ConnectionSolver from compas_timber.connections import JointTopology from compas_timber.connections import BeamJoinningError +from compas_timber.connections import XHalfLapJoint +from compas_timber.connections import TButtJoint +from compas_timber.connections import LMiterJoint from compas_timber.ghpython import JointDefinition from compas_timber.ghpython import CategoryRule from compas_timber.ghpython import TopologyRule @@ -14,6 +17,13 @@ from compas_timber.ghpython import DebugInfomation +JOINT_DEFAULTS = { + JointTopology.TOPO_X: XHalfLapJoint, + JointTopology.TOPO_T: TButtJoint, + JointTopology.TOPO_L: LMiterJoint, +} + + class Assembly(component): def __init__(self): # maintains relationship of old_beam.id => new_beam_obj for referencing @@ -38,9 +48,16 @@ def get_joints_from_rules(self, beams, rules, topologies): 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): - topo_rules[r.topology_type] = r + 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): @@ -75,6 +92,18 @@ def get_joints_from_rules(self, beams, rules, topologies): ), ) 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.key, + beam_b.key, + 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)) diff --git a/src/compas_timber/ghpython/components/CT_Attributes_Check/code.py b/src/compas_timber/ghpython/components/CT_Attributes_Check/code.py index a2fa36560..396a139e8 100644 --- a/src/compas_timber/ghpython/components/CT_Attributes_Check/code.py +++ b/src/compas_timber/ghpython/components/CT_Attributes_Check/code.py @@ -10,13 +10,13 @@ class Attributes_Check(component): - def RunScript(self, RefObj): + def RunScript(self, ref_obj): self.data = [] - list_input_valid(self, RefObj, "RefObj") + list_input_valid(self, ref_obj, "RefObj") - for obj in RefObj: - d = {"refobj": RefObj, "crv": None, "msg": [], "ok": None, "pln": None, "pt": None} + for obj in ref_obj: + d = {"refobj": ref_obj, "crv": None, "msg": [], "ok": None, "pln": None, "pt": None} crv = Rhino.RhinoDoc.ActiveDoc.Objects.FindId(obj).Geometry if not crv: diff --git a/src/compas_timber/ghpython/components/CT_Attributes_Delete/code.py b/src/compas_timber/ghpython/components/CT_Attributes_Delete/code.py index ac220f731..043d1d59b 100644 --- a/src/compas_timber/ghpython/components/CT_Attributes_Delete/code.py +++ b/src/compas_timber/ghpython/components/CT_Attributes_Delete/code.py @@ -5,17 +5,17 @@ class Attributes_Delete(component): - def RunScript(self, RefObj, AttributeName, update): - if not item_input_valid(self, RefObj, "RefObj"): + def RunScript(self, ref_obj, attribute_name, update): + if not item_input_valid(self, ref_obj, "RefObj"): return - if update and RefObj: - if not AttributeName: + if update and ref_obj: + if not attribute_name: # clear all attributes from the refecenced object's name - update_rhobj_attributes_name(RefObj, operation="clear") + update_rhobj_attributes_name(ref_obj, operation="clear") else: # remove only the indicated attributes - for attr in AttributeName: - update_rhobj_attributes_name(RefObj, attribute=attr, operation="remove") + for attr in attribute_name: + update_rhobj_attributes_name(ref_obj, attribute=attr, operation="remove") return diff --git a/src/compas_timber/ghpython/components/CT_Attributes_Get/code.py b/src/compas_timber/ghpython/components/CT_Attributes_Get/code.py index 40cc0f98e..81b68e985 100644 --- a/src/compas_timber/ghpython/components/CT_Attributes_Get/code.py +++ b/src/compas_timber/ghpython/components/CT_Attributes_Get/code.py @@ -9,29 +9,29 @@ class Attributes_Get(component): - def RunScript(self, RefCrv): - if not RefCrv: + def RunScript(self, ref_crv): + if not ref_crv: self.AddRuntimeMessage(Warning, "Input parameter RefCrv failed to collect data") - ZVector = [] - Width = [] - Height = [] - Category = [] - Group = [] + z_vector = [] + width = [] + height = [] + category = [] + group = [] - guid = RefCrv + guid = ref_crv if guid: # get attributes from the name string ========================================== attr = get_obj_attributes(guid) if attr: if "width" in attr: - Width = float(attr["width"]) + width = float(attr["width"]) if "height" in attr: - Height = float(attr["height"]) + height = float(attr["height"]) if "category" in attr: - Category = attr["category"] + category = attr["category"] if "zvector" in attr: - ZVector = attr["zvector"] + z_vector = attr["zvector"] # it's a string, but Grasshopper will automatically cast this input as Vector3d # get the group if objects are grouped ========================================= @@ -44,9 +44,9 @@ def RunScript(self, RefCrv): self.AddRuntimeMessage( Remark, "Some objects belong to more than one group! (I will pick the first group I find.)" ) - Group = gl[0] + group = gl[0] else: - Group = [] + group = [] - return (ZVector, Width, Height, Category, Group) + return (z_vector, width, height, category, group) diff --git a/src/compas_timber/ghpython/components/CT_Attributes_Get_Custom/code.py b/src/compas_timber/ghpython/components/CT_Attributes_Get_Custom/code.py index 25a01bf35..53895a4c8 100644 --- a/src/compas_timber/ghpython/components/CT_Attributes_Get_Custom/code.py +++ b/src/compas_timber/ghpython/components/CT_Attributes_Get_Custom/code.py @@ -7,14 +7,14 @@ class Attributes_Get_Custom(component): - def RunScript(self, RefCrv): - if not RefCrv: + def RunScript(self, ref_crv): + if not ref_crv: self.AddRuntimeMessage(Warning, "Input parameter RefCrv failed to collect data") AttributeName = [] AttributeValue = [] - guid = RefCrv + guid = ref_crv if guid: attrdict = get_obj_attributes(guid) if attrdict: diff --git a/src/compas_timber/ghpython/components/CT_Attributes_Set/code.py b/src/compas_timber/ghpython/components/CT_Attributes_Set/code.py index 514f9f25a..ce1a09f6c 100644 --- a/src/compas_timber/ghpython/components/CT_Attributes_Set/code.py +++ b/src/compas_timber/ghpython/components/CT_Attributes_Set/code.py @@ -9,41 +9,41 @@ class Attributes_Set(component): - def RunScript(self, RefObj, ZVector, Width, Height, Category, update): - _o = list_input_valid(self, RefObj, "RefObj") + def RunScript(self, ref_obj, z_vector, width, height, category, update): + _o = list_input_valid(self, ref_obj, "RefObj") if not _o: return # requires at least one of these inputs to be not None - if ZVector or Width or Height or Category: + if z_vector or width or height or category: pass else: self.AddRuntimeMessage( Warning, "None of the input parameters 'ZVector', 'Width', 'Height', 'Category' collected any data." ) - n = len(RefObj) + n = len(ref_obj) - if ZVector: - if len(ZVector) not in (0, 1, n): + if z_vector: + if len(z_vector) not in (0, 1, n): self.AddRuntimeMessage( Error, " Input parameter 'ZVector' requires either none, one or the same number of values as in refObj.", ) - if Width: - if len(Width) not in (0, 1, n): + if width: + if len(width) not in (0, 1, n): self.AddRuntimeMessage( Error, " Input parameter 'Width' requires either none, one or the same number of values as in refObj.", ) - if Height: - if len(Height) not in (0, 1, n): + if height: + if len(height) not in (0, 1, n): self.AddRuntimeMessage( Error, " Input parameter 'Height' requires either none, one or the same number of values as in refObj.", ) - if Category: - if len(Category) not in (0, 1, n): + if category: + if len(category) not in (0, 1, n): self.AddRuntimeMessage( Error, " Input parameter 'Category' requires either none, one or the same number of values as in refObj.", @@ -58,23 +58,23 @@ def get_item(items, i): return items[i] if update: - for i, ro in enumerate(RefObj): + for i, ro in enumerate(ref_obj): guid = ro # note: with input type set to Vector, it accepts only a Vector3d, or a string {x,y,z} and casts it to Vector3d - z = get_item(ZVector, i) + z = get_item(z_vector, i) if z: update_rhobj_attributes_name(guid, "zvector", "{%s,%s,%s}" % (z.X, z.Y, z.Z)) - w = get_item(Width, i) + w = get_item(width, i) if w: update_rhobj_attributes_name(guid, "width", str(w)) - h = get_item(Height, i) + h = get_item(height, i) if h: update_rhobj_attributes_name(guid, "height", str(h)) - c = get_item(Category, i) + c = get_item(category, i) if c: update_rhobj_attributes_name(guid, "category", str(c)) diff --git a/src/compas_timber/ghpython/components/CT_Attributes_Set_Custom/code.py b/src/compas_timber/ghpython/components/CT_Attributes_Set_Custom/code.py index c723086a5..04d2ff530 100644 --- a/src/compas_timber/ghpython/components/CT_Attributes_Set_Custom/code.py +++ b/src/compas_timber/ghpython/components/CT_Attributes_Set_Custom/code.py @@ -5,14 +5,14 @@ class Attributes_Set_Custom(component): - def RunScript(self, RefObj, Attribute, update): - o = list_input_valid(self, RefObj, "RefObj") - a = list_input_valid(self, Attribute, "Attribute") + def RunScript(self, ref_obj, attribute, update): + o = list_input_valid(self, ref_obj, "RefObj") + a = list_input_valid(self, attribute, "Attribute") if update and o and a: - for attr in Attribute: + for attr in attribute: if attr: - for guid in RefObj: + for guid in ref_obj: if guid: update_rhobj_attributes_name(guid, attr.name, attr.value) diff --git a/src/compas_timber/ghpython/components/CT_BTLx/code.py b/src/compas_timber/ghpython/components/CT_BTLx/code.py index 2ec1cca4f..c33f599db 100644 --- a/src/compas_timber/ghpython/components/CT_BTLx/code.py +++ b/src/compas_timber/ghpython/components/CT_BTLx/code.py @@ -6,18 +6,18 @@ class WriteBTLx(component): - def RunScript(self, Assembly, Path, Write): - if not Assembly: + def RunScript(self, assembly, path, write): + if not assembly: self.AddRuntimeMessage(Warning, "Input parameter Assembly failed to collect data") return - btlx = BTLx(Assembly) + btlx = BTLx(assembly) btlx.history["FileName"] = Rhino.RhinoDoc.ActiveDoc.Name - if Write: - if not Path: + if write: + if not path: self.AddRuntimeMessage(Warning, "Input parameter Path failed to collect data") return - with open(Path, "w") as f: + with open(path, "w") as f: f.write(btlx.btlx_string()) return btlx.btlx_string() diff --git a/src/compas_timber/ghpython/components/CT_Bake_BoxMap/code.py b/src/compas_timber/ghpython/components/CT_Bake_BoxMap/code.py index d3e741950..45dbac3b6 100644 --- a/src/compas_timber/ghpython/components/CT_Bake_BoxMap/code.py +++ b/src/compas_timber/ghpython/components/CT_Bake_BoxMap/code.py @@ -16,32 +16,32 @@ class BakeBoxMap(component): - def RunScript(self, Assembly, MapSize, Bake): - if MapSize and len(MapSize) != 3: + def RunScript(self, assembly, map_size, bake): + if map_size and len(map_size) != 3: self.AddRuntimeMessage( Error, "Input parameter MapSize requires exactly three float values (scale factors in x,y,z directions)" ) return - if MapSize: - dimx, dimy, dimz = MapSize + if map_size: + dimx, dimy, dimz = map_size else: # for the pine 251 material bitmap, rotated dimx = 0.2 dimy = 0.2 dimz = 1.0 - if not Assembly: + if not assembly: self.AddRuntimeMessage(Warning, "Input parameters Assembly failed to collect any Beam objects.") return - if not Bake: + if not bake: return try: - geometries = BrepGeometryConsumer(Assembly).result + geometries = BrepGeometryConsumer(assembly).result - frames = [frame_to_rhino(b.frame) for b in Assembly.beams] + frames = [frame_to_rhino(b.frame) for b in assembly.beams] breps = [g.geometry.native_brep for g in geometries] if frames and breps: diff --git a/src/compas_timber/ghpython/components/CT_BeamDecompose/code.py b/src/compas_timber/ghpython/components/CT_BeamDecompose/code.py index f189cc964..db1dad2f6 100644 --- a/src/compas_timber/ghpython/components/CT_BeamDecompose/code.py +++ b/src/compas_timber/ghpython/components/CT_BeamDecompose/code.py @@ -9,7 +9,6 @@ class BeamDecompose(component): - RED = Color.FromArgb(255, 255, 100, 100) GREEN = Color.FromArgb(200, 50, 220, 100) BLUE = Color.FromArgb(200, 50, 150, 255) diff --git a/src/compas_timber/ghpython/components/CT_Beam_fromCurve/code.py b/src/compas_timber/ghpython/components/CT_Beam_fromCurve/code.py index 85c92b973..0852fe09f 100644 --- a/src/compas_timber/ghpython/components/CT_Beam_fromCurve/code.py +++ b/src/compas_timber/ghpython/components/CT_Beam_fromCurve/code.py @@ -15,56 +15,56 @@ class Beam_fromCurve(component): - def RunScript(self, Centerline, ZVector, Width, Height, Category, updateRefObj): + def RunScript(self, centerline, z_vector, width, height, category, updateRefObj): # minimum inputs required - if not Centerline: + if not centerline: self.AddRuntimeMessage(Warning, "Input parameter 'Centerline' failed to collect data") - if not Width: + if not width: self.AddRuntimeMessage(Warning, "Input parameter 'Width' failed to collect data") - if not Height: + if not height: self.AddRuntimeMessage(Warning, "Input parameter 'Height' failed to collect data") # reformat unset parameters for consistency - if not ZVector: - ZVector = [None] - if not Category: - Category = [None] + if not z_vector: + z_vector = [None] + if not category: + category = [None] - Beam = [] - Blank = [] + beams = [] + blanks = [] scene = Scene() - if Centerline and Height and Width: + if centerline and height and width: # check list lengths for consistency - N = len(Centerline) - if len(ZVector) not in (0, 1, N): + 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): + 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): + 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): + 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(ZVector) != N: - ZVector = [ZVector[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)] + 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, ZVector, Width, Height, Category): + 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) @@ -80,12 +80,12 @@ def RunScript(self, Centerline, ZVector, Width, Height, Category, updateRefObj): update_rhobj_attributes_name(guid, "zvector", str(list(beam.frame.zaxis))) update_rhobj_attributes_name(guid, "category", c) - Beam.append(beam) + beams.append(beam) scene.add(beam.blank) - Blank = scene.draw() + blanks = scene.draw() - return Beam, Blank + return beams, blanks def _get_guid_and_geometry(self, line): # internalized curves and GH geometry will not have persistent GUIDs, referenced Rhino objects will diff --git a/src/compas_timber/ghpython/components/CT_FindByGuid/code.py b/src/compas_timber/ghpython/components/CT_FindByGuid/code.py index 0ad33518a..b8b4443c8 100644 --- a/src/compas_timber/ghpython/components/CT_FindByGuid/code.py +++ b/src/compas_timber/ghpython/components/CT_FindByGuid/code.py @@ -3,15 +3,15 @@ class FindBeamByRhinoGuid(component): - def RunScript(self, Beams, Guid): - if not (Beams and Guid): + def RunScript(self, beams, Guid): + if not (beams and Guid): return if not isinstance(Guid, list): Guid = [Guid] Guid = [str(g) for g in Guid] FoundBeam = [] - for beam in Beams: + for beam in beams: if beam.attributes.get("rhino_guid", None) in Guid: FoundBeam.append(beam) diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/code.py deleted file mode 100644 index d341e9af6..000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/code.py +++ /dev/null @@ -1,15 +0,0 @@ -from ghpythonlib.componentbase import executingcomponent as component - - -from compas_timber.connections import FrenchRidgeLapJoint -from compas_timber.ghpython import JointOptions - - -class FrenchRidgeLapOptions(component): - def RunScript(self, Cutoff): - args = {} - if Cutoff: - args["cutoff"] = Cutoff - options = JointOptions(FrenchRidgeLapJoint, **args) - - return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/icon.png deleted file mode 100644 index bf0bdf2c8..000000000 Binary files a/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/icon.png and /dev/null differ diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/metadata.json deleted file mode 100644 index 4ba260ffa..000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/metadata.json +++ /dev/null @@ -1,27 +0,0 @@ - -{ - "name": "French Ridge Lap Joint Options", - "nickname": "French Ridge", - "category": "COMPAS Timber", - "subcategory": "Joints", - "description": "defines and gives access to French Ridge Lap joint and its parameters", - "exposure": 2, - "ghpython": { - "isAdvancedMode": true, - "iconDisplay": 0, - "inputParameters": [ - { - "name": "Cutoff", - "description": "For very acute angles, limit the extension of the tip/beak of the joint.", - "typeHintID": "float", - "scriptParamAccess": 0 - } - ], - "outputParameters": [ - { - "name": "French Ridge", - "description": "French Ridge Lap Joint Options." - } - ] - } -} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/code.py deleted file mode 100644 index 8096ebdff..000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/code.py +++ /dev/null @@ -1,20 +0,0 @@ -from ghpythonlib.componentbase import executingcomponent as component - - -from compas_timber.connections import LButtJoint -from compas_timber.ghpython import JointOptions - - -class LButtJointOptions(component): - def RunScript(self, small_beam_butts, modify_cross, reject_i): - args = {} - if small_beam_butts is not None: - args["small_beam_butts"] = small_beam_butts - if modify_cross is not None: - args["modify_cross"] = modify_cross - if reject_i is not None: - args["reject_i"] = reject_i - - options = JointOptions(LButtJoint, **args) - - return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/icon.png deleted file mode 100644 index 3d0760fe7..000000000 Binary files a/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/icon.png and /dev/null differ diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/metadata.json deleted file mode 100644 index 2575d448b..000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/metadata.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "L-Butt Options", - "nickname": "L-Butt", - "category": "COMPAS Timber", - "subcategory": "Joints", - "description": "defines and gives access to L-Butt joint and its parameters", - "exposure": 2, - "ghpython": { - "isAdvancedMode": true, - "iconDisplay": 0, - "inputParameters": [ - { - "name": "SmallBeamButts", - "description": "If true, the beam with a larger cross-section is considered as the cross beam.", - "typeHintID": "bool", - "scriptParamAccess": 0 - }, - { - "name": "ModifyCross", - "description": "If true, the cross beam is extended to the opposite face of the main beam and cut flush with it.", - "typeHintID": "bool", - "scriptParamAccess": 0 - }, - { - "name": "RejectI", - "description": "If true, joint will not apply when the cross beam meets the main beam in an I topology.", - "typeHintID": "bool", - "scriptParamAccess": 0 - } - ], - "outputParameters": [ - { - "name": "L-Butt", - "description": "L-Butt Joint Options." - } - ] - } -} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/code.py deleted file mode 100644 index b41f6daea..000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/code.py +++ /dev/null @@ -1,18 +0,0 @@ -from ghpythonlib.componentbase import executingcomponent as component - - -from compas_timber.connections import LHalfLapJoint -from compas_timber.ghpython import JointOptions - - -class LHalfLapJointOptions(component): - def RunScript(self, flip_lap_side, cut_plane_bias): - args = {} - if flip_lap_side: - args["flip_lap_side"] = flip_lap_side - if cut_plane_bias: - args["cut_plane_bias"] = cut_plane_bias - - options = JointOptions(LHalfLapJoint, **args) - - return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/icon.png deleted file mode 100644 index 835c6f678..000000000 Binary files a/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/icon.png and /dev/null differ diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/metadata.json deleted file mode 100644 index 56a5b12e8..000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/metadata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "L-Half Lap Joint Options", - "nickname": "L-Half Lap", - "category": "COMPAS Timber", - "subcategory": "Joints", - "description": "defines and gives access to L-Half Lap joint and its parameters", - "exposure": 2, - "ghpython": { - "isAdvancedMode": true, - "iconDisplay": 0, - "inputParameters": [ - { - "name": "FlipLapSide", - "description": "flip the side of the lap joint", - "typeHintID": "bool", - "scriptParamAccess": 0 - }, - { - "name": "CutPlaneBias", - "description": "determines the depth of lap cuts on the beams", - "typeHintID": "float", - "scriptParamAccess": 0 - } - ], - "outputParameters": [ - { - "name": "L-Half Lap", - "description": "L-Half Lap Joint Options." - } - ] - } -} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/code.py deleted file mode 100644 index f1c335854..000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/code.py +++ /dev/null @@ -1,15 +0,0 @@ -from ghpythonlib.componentbase import executingcomponent as component - - -from compas_timber.connections import LMiterJoint -from compas_timber.ghpython import JointOptions - - -class LMiterJointOptions(component): - def RunScript(self, Cutoff): - args = {} - if Cutoff: - args["cutoff"] = Cutoff - options = JointOptions(LMiterJoint, **args) - - return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/icon.png deleted file mode 100644 index b335abbc6..000000000 Binary files a/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/icon.png and /dev/null differ diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/metadata.json deleted file mode 100644 index 3d9225a1f..000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/metadata.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "L-Miter Joint Options", - "nickname": "L-Miter", - "category": "COMPAS Timber", - "subcategory": "Joints", - "description": "defines and gives access to L-Miter joint and its parameters", - "exposure": 2, - "ghpython": { - "isAdvancedMode": true, - "iconDisplay": 0, - "inputParameters": [ - { - "name": "Cutoff", - "description": "For very acute angles, limit the extension of the tip/beak of the joint.", - "typeHintID": "float", - "scriptParamAccess": 0 - } - ], - "outputParameters": [ - { - "name": "L-Miter", - "description": "L-Miter Joint Options." - } - ] - } -} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/code.py deleted file mode 100644 index 30de1c26a..000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/code.py +++ /dev/null @@ -1,12 +0,0 @@ -from ghpythonlib.componentbase import executingcomponent as component - - -from compas_timber.connections import NullJoint -from compas_timber.ghpython import JointOptions - - -class NullJointComponent(component): - def RunScript(self): - - options = JointOptions(NullJoint, **{}) - return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/icon.png deleted file mode 100644 index d6855f007..000000000 Binary files a/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/icon.png and /dev/null differ diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/metadata.json deleted file mode 100644 index 6bc831eda..000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/metadata.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Null-Joint Options", - "nickname": "NullJoint", - "category": "COMPAS Timber", - "subcategory": "Joints", - "description": "defines and gives access to NullJoint joint and its parameters", - "exposure": 2, - "ghpython": { - "isAdvancedMode": true, - "iconDisplay": 0, - "inputParameters": [ - ], - "outputParameters": [ - { - "name": "NullJoint", - "description": "NullJoint Joint Options." - } - ] - } -} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/code.py deleted file mode 100644 index 15c040d6e..000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/code.py +++ /dev/null @@ -1,15 +0,0 @@ -from ghpythonlib.componentbase import executingcomponent as component - - -from compas_timber.connections import TButtJoint -from compas_timber.ghpython import JointOptions - - -class TButtJointOptions(component): - def RunScript(self, Gap): - args = {} - if Gap: - args["gap"] = Gap - options = JointOptions(TButtJoint, **args) - - return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/icon.png deleted file mode 100644 index d4db8698d..000000000 Binary files a/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/icon.png and /dev/null differ diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/metadata.json deleted file mode 100644 index 2b8c10822..000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/metadata.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "T-Butt Options", - "nickname": "T-Butt", - "category": "COMPAS Timber", - "subcategory": "Joints", - "description": "defines and gives access to T-Butt joint and its parameters", - "exposure": 2, - "ghpython": { - "isAdvancedMode": true, - "iconDisplay": 0, - "inputParameters": [ - { - "name": "Gap", - "description": "Gap for tolerance.", - "typeHintID": "float", - "scriptParamAccess": 0 - } - ], - "outputParameters": [ - { - "name": "T-Butt", - "description": "T-Butt Joint Options." - } - ] - } -} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/code.py deleted file mode 100644 index 4a1ba4c66..000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/code.py +++ /dev/null @@ -1,16 +0,0 @@ -from ghpythonlib.componentbase import executingcomponent as component - -from compas_timber.connections import THalfLapJoint -from compas_timber.ghpython import JointOptions - - -class THalfLapJointOptions(component): - def RunScript(self, flip_lap_side, cut_plane_bias): - args = {} - if flip_lap_side: - args["flip_lap_side"] = flip_lap_side - if cut_plane_bias: - args["cut_plane_bias"] = cut_plane_bias - options = JointOptions(THalfLapJoint, **args) - - return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/icon.png deleted file mode 100644 index f03207a26..000000000 Binary files a/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/icon.png and /dev/null differ diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/metadata.json deleted file mode 100644 index 9d662baa7..000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/metadata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "T-Half Lap Joint Options", - "nickname": "T-Half Lap", - "category": "COMPAS Timber", - "subcategory": "Joints", - "description": "defines and gives access to T-Half Lap joint and its parameters", - "exposure": 2, - "ghpython": { - "isAdvancedMode": true, - "iconDisplay": 0, - "inputParameters": [ - { - "name": "FlipLapSide", - "description": "flip the side of the lap joint", - "typeHintID": "bool", - "scriptParamAccess": 0 - }, - { - "name": "CutPlaneBias", - "description": "determines the depth of lap cuts on the beams", - "typeHintID": "float", - "scriptParamAccess": 0 - } - ], - "outputParameters": [ - { - "name": "T-Half Lap", - "description": "T-Half Lap Joint Options." - } - ] - } -} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/code.py b/src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/code.py deleted file mode 100644 index 501e6df4e..000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/code.py +++ /dev/null @@ -1,17 +0,0 @@ -from ghpythonlib.componentbase import executingcomponent as component - - -from compas_timber.connections import XHalfLapJoint -from compas_timber.ghpython import JointOptions - - -class XHalfLapJointOptions(component): - def RunScript(self, flip_lap_side, cut_plane_bias): - args = {} - if flip_lap_side: - args["flip_lap_side"] = flip_lap_side - if cut_plane_bias: - args["cut_plane_bias"] = cut_plane_bias - options = JointOptions(XHalfLapJoint, **args) - - return options diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/icon.png deleted file mode 100644 index e2c42f978..000000000 Binary files a/src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/icon.png and /dev/null differ diff --git a/src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/metadata.json deleted file mode 100644 index cd62d5ac6..000000000 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/metadata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "X-Half Lap Joint Options", - "nickname": "X-Half Lap", - "category": "COMPAS Timber", - "subcategory": "Joints", - "description": "defines and gives access to X-Half Lap joint and its parameters", - "exposure": 2, - "ghpython": { - "isAdvancedMode": true, - "iconDisplay": 0, - "inputParameters": [ - { - "name": "FlipLapSide", - "description": "flips the lap side", - "typeHintID": "bool", - "scriptParamAccess": 0 - }, - { - "name": "CutPlaneBias", - "description": "determines the depth of lap cuts on the beams", - "typeHintID": "float", - "scriptParamAccess": 0 - } - ], - "outputParameters": [ - { - "name": "X-Half Lap", - "description": "X-Half Lap Joint Options." - } - ] - } -} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Rule_Category/code.py b/src/compas_timber/ghpython/components/CT_Joint_Rule_Category/code.py index 7ac6732d3..7e5a337d0 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Rule_Category/code.py +++ b/src/compas_timber/ghpython/components/CT_Joint_Rule_Category/code.py @@ -1,9 +1,102 @@ from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning +from System.Windows.Forms import ToolStripSeparator +from System.Windows.Forms import ToolStripMenuItem +import inspect +from collections import OrderedDict + +from compas_timber.connections import Joint +from compas_timber.ghpython.ghcomponent_helpers import manage_dynamic_params +from compas_timber.ghpython.ghcomponent_helpers import get_leaf_subclasses +from compas_timber.ghpython.ghcomponent_helpers import rename_gh_output from compas_timber.ghpython import CategoryRule class CategoryJointRule(component): - def RunScript(self, JointOptions, CatA, CatB): - if JointOptions and CatA and CatB: - return CategoryRule(JointOptions.type, CatA, CatB, **JointOptions.kwargs) + def __init__(self): + super(CategoryJointRule, self).__init__() + self.topo_bools = OrderedDict([("Unknown", False), ("I", False), ("L", False), ("T", False), ("X", False)]) + + self.classes = {} + for cls in get_leaf_subclasses(Joint): + self.classes[cls.__name__] = cls + + if ghenv.Component.Params.Output[0].NickName == "Rule": + self.joint_type = None + else: + parsed_output = ghenv.Component.Params.Output[0].NickName.split("_") + self.joint_type = self.classes.get(parsed_output[0]) + for key in parsed_output[1:]: + self.topo_bools[key] = True + + def RunScript(self, *args): + if not self.joint_type: + ghenv.Component.Message = "Select joint type from context menu (right click)" + self.AddRuntimeMessage(Warning, "Select joint type from context menu (right click)") + return None + else: + ghenv.Component.Message = self.joint_type.__name__ + cat_a = args[0] + cat_b = args[1] + + kwargs = {} + for i, val in enumerate(args[2:]): + if val is not None: + kwargs[self.arg_names()[i + 2]] = val + print(kwargs) + if not cat_a: + self.AddRuntimeMessage( + Warning, "Input parameter {} failed to collect data.".format(self.arg_names()[0]) + ) + if not cat_b: + self.AddRuntimeMessage( + Warning, "Input parameter {} failed to collect data.".format(self.arg_names()[1]) + ) + if not (cat_a and cat_b): + return + + topos = [] + for i, bool in enumerate(self.topo_bools.values()): + if bool: + topos.append(i) + + return CategoryRule(self.joint_type, cat_a, cat_b, topos, **kwargs) + + def arg_names(self): + names = inspect.getargspec(self.joint_type.__init__)[0][1:] + for i in range(2): + names[i] += " category" + 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 + + menu.Items.Add(ToolStripSeparator()) + topo_menu = ToolStripMenuItem("Apply to Topology") + menu.Items.Add(topo_menu) + for name, bool in self.topo_bools.items(): + item = ToolStripMenuItem(name, None, self.on_topo_click) + item.Checked = bool + topo_menu.DropDownItems.Add(item) + + def output_name(self): + name = self.joint_type.__name__ + for key, bool in self.topo_bools.items(): + if bool: + name += "_{}".format(key) + return name + + def on_topo_click(self, sender, event_info): + self.topo_bools[str(sender)] = not self.topo_bools[str(sender)] + rename_gh_output(self.output_name(), 0, ghenv) + ghenv.Component.ExpireSolution(True) + + def on_item_click(self, sender, event_info): + self.joint_type = self.classes[str(sender)] + rename_gh_output(self.output_name(), 0, ghenv) + manage_dynamic_params(self.arg_names(), ghenv, rename_count=2, permanent_param_count=0) + ghenv.Component.ExpireSolution(True) diff --git a/src/compas_timber/ghpython/components/CT_Joint_Rule_Category/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Rule_Category/metadata.json index 3ad908ae7..704ede36d 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Rule_Category/metadata.json +++ b/src/compas_timber/ghpython/components/CT_Joint_Rule_Category/metadata.json @@ -10,20 +10,14 @@ "iconDisplay": 0, "inputParameters": [ { - "name": "JointOptions", - "description": "Joint Options component.", - "typeHintID": "none", - "scriptParamAccess": 0 - }, - { - "name": "CatA", + "name": "Cat A", "description": "Categeory of the first Beam.", "typeHintID": "str", "scriptParamAccess": 0 }, { - "name": "CatB", - "description": "Category of the sectond Beam.", + "name": "Cat B", + "description": "Category of the second Beam.", "typeHintID": "str", "scriptParamAccess": 0 } diff --git a/src/compas_timber/ghpython/components/CT_Joint_Rule_Direct/code.py b/src/compas_timber/ghpython/components/CT_Joint_Rule_Direct/code.py index d7285d994..50e1f0e86 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Rule_Direct/code.py +++ b/src/compas_timber/ghpython/components/CT_Joint_Rule_Direct/code.py @@ -1,37 +1,88 @@ from ghpythonlib.componentbase import executingcomponent as component from Grasshopper.Kernel.GH_RuntimeMessageLevel import Error from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning +import inspect + +from compas_timber.connections import Joint from compas_timber.connections import ConnectionSolver from compas_timber.connections import JointTopology + +from compas_timber.ghpython.ghcomponent_helpers import manage_dynamic_params +from compas_timber.ghpython.ghcomponent_helpers import get_leaf_subclasses +from compas_timber.ghpython.ghcomponent_helpers import rename_gh_output from compas_timber.ghpython import DirectRule class DirectJointRule(component): - def RunScript(self, JointOptions, MainBeam, SecondaryBeam): - if not MainBeam: - self.AddRuntimeMessage(Warning, "Input parameter MainBeams failed to collect data.") - if not SecondaryBeam: - self.AddRuntimeMessage(Warning, "Input parameter CrossBeams failed to collect data.") - if not (MainBeam and SecondaryBeam): - return - if not isinstance(MainBeam, list): - MainBeam = [MainBeam] - if not isinstance(SecondaryBeam, list): - SecondaryBeam = [SecondaryBeam] - if len(MainBeam) != len(SecondaryBeam): - self.AddRuntimeMessage(Error, "Number of items in MainBeams and CrossBeams must match!") - return - Rules = [] - for main, secondary in zip(MainBeam, SecondaryBeam): - topology, _, _ = ConnectionSolver().find_topology(main, secondary) - if topology != JointOptions.type.SUPPORTED_TOPOLOGY: + def __init__(self): + super(DirectJointRule, self).__init__() + self.classes = {} + for cls in get_leaf_subclasses(Joint): + self.classes[cls.__name__] = cls + + if ghenv.Component.Params.Output[0].NickName == "Rule": + self.joint_type = None + else: + self.joint_type = self.classes.get(ghenv.Component.Params.Output[0].NickName, None) + + def RunScript(self, *args): + if not self.joint_type: + ghenv.Component.Message = "Select joint type from context menu (right click)" + self.AddRuntimeMessage(Warning, "Select joint type from context menu (right click)") + return None + else: + ghenv.Component.Message = self.joint_type.__name__ + beam_a = args[0] + beam_b = args[1] + kwargs = {} + for i, val in enumerate(args[2:]): + if val is not None: + kwargs[self.arg_names()[i + 2]] = val + + if not beam_a: + self.AddRuntimeMessage( + Warning, "Input parameter {} failed to collect data.".format(self.arg_names()[0]) + ) + if not beam_b: + self.AddRuntimeMessage( + Warning, "Input parameter {} failed to collect data.".format(self.arg_names()[1]) + ) + if not (args[0] and args[1]): + return + if not isinstance(beam_a, list): + beam_a = [beam_a] + if not isinstance(beam_b, list): + beam_b = [beam_b] + if len(beam_a) != len(beam_b): self.AddRuntimeMessage( - Warning, - "Beams meet with topology: {} which does not agree with joint of type: {}".format( - JointTopology.get_name(topology), JointOptions.type.__name__ - ), + Error, "Number of items in {} and {} must match!".format(self.arg_names()[0], self.arg_names()[1]) ) - continue - Rules.append(DirectRule(JointOptions.type, [secondary, main], **JointOptions.kwargs)) - return Rules + return + Rules = [] + for main, secondary in zip(beam_a, beam_b): + topology, _, _ = ConnectionSolver().find_topology(main, secondary) + if topology != self.joint_type.SUPPORTED_TOPOLOGY: + self.AddRuntimeMessage( + Warning, + "Beams meet with topology: {} which does not agree with joint of type: {}".format( + JointTopology.get_name(topology), self.joint_type.__name__ + ), + ) + Rules.append(DirectRule(self.joint_type, [secondary, main], **kwargs)) + return Rules + + def arg_names(self): + return inspect.getargspec(self.joint_type.__init__)[0][1:] + + 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.joint_type = self.classes[str(sender)] + rename_gh_output(self.joint_type.__name__, 0, ghenv) + manage_dynamic_params(self.arg_names(), ghenv, rename_count=2, permanent_param_count=0) + ghenv.Component.ExpireSolution(True) diff --git a/src/compas_timber/ghpython/components/CT_Joint_Rule_Direct/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Rule_Direct/metadata.json index 0d2087689..2b9caa3a7 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Rule_Direct/metadata.json +++ b/src/compas_timber/ghpython/components/CT_Joint_Rule_Direct/metadata.json @@ -10,20 +10,14 @@ "iconDisplay": 0, "inputParameters": [ { - "name": "JointOptions", - "description": "Joint Options Component", - "typeHintID": "none", - "scriptParamAccess": 0 - }, - { - "name": "MainBeam", - "description": "Main Beam.", + "name": "Beam A", + "description": "Beam A.", "typeHintID": "none", "scriptParamAccess": 1 }, { - "name": "SecondaryBeam", - "description": "Secondary Beam.", + "name": "Beam B", + "description": "Beam B.", "typeHintID": "none", "scriptParamAccess": 1 } 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 cbe2c026f..a8142e8d0 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 @@ -1,5 +1,4 @@ from ghpythonlib.componentbase import executingcomponent as component -from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning from compas_timber.connections import JointTopology from compas_timber.ghpython import TopologyRule @@ -9,27 +8,11 @@ from compas_timber.connections import XHalfLapJoint -class TopologyJointRule(component): - def RunScript(self, L, T, X): +class DefaultJointRule(component): + def RunScript(self): topoRules = [] - - if L: - if L.type.SUPPORTED_TOPOLOGY != JointTopology.TOPO_L: - self.AddRuntimeMessage(Warning, "Joint type does not match topology. Joint may not be generated.") - topoRules.append(TopologyRule(JointTopology.TOPO_L, L.type, **L.kwargs)) - else: - topoRules.append(TopologyRule(JointTopology.TOPO_L, LMiterJoint)) - if T: - if T.type.SUPPORTED_TOPOLOGY != JointTopology.TOPO_T: - self.AddRuntimeMessage(Warning, "Joint type does not match topology. Joint may not be generated.") - topoRules.append(TopologyRule(JointTopology.TOPO_T, T.type, **T.kwargs)) - else: - topoRules.append(TopologyRule(JointTopology.TOPO_T, TButtJoint)) - if X: - if X.type.SUPPORTED_TOPOLOGY != JointTopology.TOPO_X: - self.AddRuntimeMessage(Warning, "Joint type does not match topology. Joint may not be generated.") - topoRules.append(TopologyRule(JointTopology.TOPO_X, X.type, **X.kwargs)) - else: - topoRules.append(TopologyRule(JointTopology.TOPO_X, XHalfLapJoint)) + topoRules.append(TopologyRule(JointTopology.TOPO_L, LMiterJoint)) + topoRules.append(TopologyRule(JointTopology.TOPO_T, TButtJoint)) + topoRules.append(TopologyRule(JointTopology.TOPO_X, XHalfLapJoint)) return topoRules diff --git a/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology/metadata.json index 7296d4edf..de889c039 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology/metadata.json +++ b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology/metadata.json @@ -1,32 +1,14 @@ { - "name": "Topological Joint Rules", - "nickname": "JointsByTopo", + "name": "Default Joint Rules", + "nickname": "DefaultJoints", "category": "COMPAS Timber", "subcategory": "Joint Rules", - "description": "makes rules to apply joint types for each topology type.", + "description": "makes default rules to apply joints based on connection topology. To override with other rules, use the 'L_Topo_Joint' 'T_Topo_Joint' or 'X_Topo_Joint' components.", "exposure": 2, "ghpython": { "isAdvancedMode": true, "iconDisplay": 0, "inputParameters": [ - { - "name": "L", - "description": "Joint type to be applied to L topology. Default type is L-Miter. Input is a joint options component.", - "typeHintID": "none", - "scriptParamAccess": 0 - }, - { - "name": "T", - "description": "Joint type to be applied to T topology. Default type is T-Butt. Input is a joint options component.", - "typeHintID": "none", - "scriptParamAccess": 0 - }, - { - "name": "X", - "description": "Joint type to be applied to X topology. Default type is X-HalfLap. Input is a joint options component.", - "typeHintID": "none", - "scriptParamAccess": 0 - } ], "outputParameters": [ { diff --git a/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_L/code.py b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_L/code.py new file mode 100644 index 000000000..888f66ea6 --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_L/code.py @@ -0,0 +1,59 @@ +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning +import inspect + +from compas_timber.ghpython.ghcomponent_helpers import manage_dynamic_params +from compas_timber.ghpython.ghcomponent_helpers import get_leaf_subclasses +from compas_timber.ghpython.ghcomponent_helpers import rename_gh_output + +from compas_timber.connections import Joint +from compas_timber.connections import JointTopology +from compas_timber.connections import LMiterJoint +from compas_timber.ghpython import TopologyRule + + +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) diff --git a/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_L/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_L/icon.png new file mode 100644 index 000000000..a40b66867 Binary files /dev/null and b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_L/icon.png differ diff --git a/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_L/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_L/metadata.json new file mode 100644 index 000000000..95be6f3ba --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_L/metadata.json @@ -0,0 +1,20 @@ +{ + "name": "L Topological Joint Rules", + "nickname": "L_Topo_Joint", + "category": "COMPAS Timber", + "subcategory": "Joint Rules", + "description": "makes rule to apply joint type to L topology. Defualts to LMiterJoint", + "exposure": 2, + "ghpython": { + "isAdvancedMode": true, + "iconDisplay": 0, + "inputParameters": [ + ], + "outputParameters": [ + { + "name": "Rule", + "description": "List of JointDefinitions." + } + ] + } +} diff --git a/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_T/code.py b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_T/code.py new file mode 100644 index 000000000..65aa77d27 --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_T/code.py @@ -0,0 +1,59 @@ +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning +import inspect + +from compas_timber.ghpython.ghcomponent_helpers import manage_dynamic_params +from compas_timber.ghpython.ghcomponent_helpers import get_leaf_subclasses +from compas_timber.ghpython.ghcomponent_helpers import rename_gh_output + +from compas_timber.connections import Joint +from compas_timber.connections import JointTopology +from compas_timber.connections import TButtJoint +from compas_timber.ghpython import TopologyRule + + +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) diff --git a/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_T/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_T/icon.png new file mode 100644 index 000000000..a40b66867 Binary files /dev/null and b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_T/icon.png differ diff --git a/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_T/metadata.json b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_T/metadata.json new file mode 100644 index 000000000..7013e559f --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_T/metadata.json @@ -0,0 +1,20 @@ +{ + "name": "T Topological Joint Rules", + "nickname": "T_Topo_Joint", + "category": "COMPAS Timber", + "subcategory": "Joint Rules", + "description": "makes rule to apply joint type to T topology. Defualts to TButtJoint", + "exposure": 2, + "ghpython": { + "isAdvancedMode": true, + "iconDisplay": 0, + "inputParameters": [ + ], + "outputParameters": [ + { + "name": "Rule", + "description": "List of JointDefinitions." + } + ] + } +} 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 new file mode 100644 index 000000000..9bf59c010 --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_X/code.py @@ -0,0 +1,59 @@ +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning +import inspect + +from compas_timber.ghpython.ghcomponent_helpers import manage_dynamic_params +from compas_timber.ghpython.ghcomponent_helpers import get_leaf_subclasses +from compas_timber.ghpython.ghcomponent_helpers import rename_gh_output + +from compas_timber.connections import Joint +from compas_timber.connections import JointTopology +from compas_timber.connections import XHalfLapJoint +from compas_timber.ghpython import TopologyRule + + +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) diff --git a/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_X/icon.png b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_X/icon.png new file mode 100644 index 000000000..a40b66867 Binary files /dev/null and b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_X/icon.png differ 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 new file mode 100644 index 000000000..8a4e8937d --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Joint_Rule_Topology_X/metadata.json @@ -0,0 +1,20 @@ +{ + "name": "X Topological Joint Rules", + "nickname": "X_Topo_Joint", + "category": "COMPAS Timber", + "subcategory": "Joint Rules", + "description": "makes rule to apply joint type to X topology. Defualts to XHalfLapJoint", + "exposure": 2, + "ghpython": { + "isAdvancedMode": true, + "iconDisplay": 0, + "inputParameters": [ + ], + "outputParameters": [ + { + "name": "Rule", + "description": "List of JointDefinitions." + } + ] + } +} diff --git a/src/compas_timber/ghpython/components/CT_ShowBeamFaces/code.py b/src/compas_timber/ghpython/components/CT_ShowBeamFaces/code.py index a182472dc..3d5eec986 100644 --- a/src/compas_timber/ghpython/components/CT_ShowBeamFaces/code.py +++ b/src/compas_timber/ghpython/components/CT_ShowBeamFaces/code.py @@ -5,13 +5,13 @@ class ShowBeamFaces(component): - def RunScript(self, Assembly): + def RunScript(self, assembly): self.pt = [] self.txt = [] - if not Assembly: + if not assembly: return None - for beam in Assembly.beams: + for beam in assembly.beams: for f_index, face in enumerate(beam.faces): self.pt.append(point_to_rhino(face.point)) self.txt.append(str(f_index)) diff --git a/src/compas_timber/ghpython/components/CT_ShowBeamIndex/code.py b/src/compas_timber/ghpython/components/CT_ShowBeamIndex/code.py index 8d63630c9..17cb83603 100644 --- a/src/compas_timber/ghpython/components/CT_ShowBeamIndex/code.py +++ b/src/compas_timber/ghpython/components/CT_ShowBeamIndex/code.py @@ -5,13 +5,13 @@ class ShowBeamIndex(component): - def RunScript(self, Assembly): + def RunScript(self, assembly): self.pt = [] self.txt = [] - if not Assembly: + if not assembly: return None - for beam in Assembly.beams: + for beam in assembly.beams: self.pt.append(point_to_rhino(beam.midpoint)) self.txt.append(str(beam.key)) diff --git a/src/compas_timber/ghpython/components/CT_ShowJointTypes/code.py b/src/compas_timber/ghpython/components/CT_ShowJointTypes/code.py index d2306349c..ba1eb03bd 100644 --- a/src/compas_timber/ghpython/components/CT_ShowJointTypes/code.py +++ b/src/compas_timber/ghpython/components/CT_ShowJointTypes/code.py @@ -7,14 +7,14 @@ class ShowJointTypes(component): - def RunScript(self, Assembly): + def RunScript(self, assembly): self.pt = [] self.txt = [] - if not Assembly: + if not assembly: return - for joint in Assembly.joints: + for joint in assembly.joints: line_a, line_b = joint.beams[0].centerline, joint.beams[1].centerline [p1, t1], [p2, t2] = intersection_line_line_3D(line_a, line_b, float("inf"), False, 1e-3) p1 = point_to_rhino(p1) diff --git a/src/compas_timber/ghpython/components/CT_ShowTopologyTypes/code.py b/src/compas_timber/ghpython/components/CT_ShowTopologyTypes/code.py index 98a0d0f8c..c38dee109 100644 --- a/src/compas_timber/ghpython/components/CT_ShowTopologyTypes/code.py +++ b/src/compas_timber/ghpython/components/CT_ShowTopologyTypes/code.py @@ -7,13 +7,13 @@ class ShowTopologyTypes(component): - def RunScript(self, Assembly): + def RunScript(self, assembly): self.pt = [] self.txt = [] - if not Assembly: + if not assembly: return - for topo in Assembly.topologies: + for topo in assembly.topologies: beam_a = topo["beam_a"] beam_b = topo["beam_b"] topology = topo.get("detected_topo") diff --git a/src/compas_timber/ghpython/ghcomponent_helpers.py b/src/compas_timber/ghpython/ghcomponent_helpers.py index e87e38598..d8fe7d3de 100644 --- a/src/compas_timber/ghpython/ghcomponent_helpers.py +++ b/src/compas_timber/ghpython/ghcomponent_helpers.py @@ -1,5 +1,9 @@ -from Grasshopper.Kernel.GH_RuntimeMessageLevel import Remark -from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning +try: + from Grasshopper.Kernel.GH_RuntimeMessageLevel import Remark + from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning + import Grasshopper +except (ImportError, SyntaxError): + pass def list_input_valid(component, Param, name): @@ -22,3 +26,182 @@ def item_input_valid(component, Param, name): else: return True return False + + +def get_leaf_subclasses(cls): + subclasses = [] + for subclass in cls.__subclasses__(): + if not get_leaf_subclasses(subclass): + subclasses.append(subclass) + subclasses.extend(get_leaf_subclasses(subclass)) + return subclasses + + +def add_gh_param( + name, io, ghenv, index=None +): # we could also make beam_names a dict with more info e.g. NickName, Description, Access, hints, etc. this would be defined in joint_options components + """Adds a parameter to the Grasshopper component. + + Parameters + ---------- + name : str + The name of the parameter. + io : str + The direction of the parameter. Either "Input" or "Output". + ghenv : object + The Grasshopper environment object. + + Returns + ------- + None + + """ + assert io in ("Output", "Input") + params = [param.NickName for param in getattr(ghenv.Component.Params, io)] + if name not in params: + param = Grasshopper.Kernel.Parameters.Param_GenericObject() + param.NickName = name + param.Name = name + param.Description = name + param.Access = Grasshopper.Kernel.GH_ParamAccess.item + param.Optional = True + if not index: + index = getattr(ghenv.Component.Params, io).Count + + registers = dict(Input="RegisterInputParam", Output="RegisterOutputParam") + getattr(ghenv.Component.Params, registers[io])(param, index) + ghenv.Component.Params.OnParametersChanged() + + +def clear_gh_params(ghenv, permanent_param_count=1): + """Clears all input parameters from the component. + + Parameters + ---------- + ghenv : object + The Grasshopper environment object. + permanent_param_count : int, optional + The number of parameters that should not be deleted. Default is 1. + + Returns + ------- + None + + """ + changed = False + while len(ghenv.Component.Params.Input) > permanent_param_count: + ghenv.Component.Params.UnregisterInputParameter( + ghenv.Component.Params.Input[len(ghenv.Component.Params.Input) - 1], True + ) + changed = True + ghenv.Component.Params.OnParametersChanged() + return changed + + +def rename_gh_input(input_name, index, ghenv): + """Renames a parameter in the Grasshopper component. + + Parameters + ---------- + ghenv : object + The Grasshopper environment object. + input_name : str + The new name of the parameter. + index : int + The index of the parameter to rename. + + Returns + ------- + None + + """ + param = ghenv.Component.Params.Input[index] + param.NickName = input_name + param.Name = input_name + param.Description = input_name + ghenv.Component.Params.OnParametersChanged() + + +def rename_gh_output(output_name, index, ghenv): + """Renames a parameter in the Grasshopper component. + + Parameters + ---------- + output_name : str + The new name of the parameter. + index : int + The index of the parameter to rename. + ghenv : object + The Grasshopper environment object. + + Returns + ------- + None + + """ + param = ghenv.Component.Params.Output[index] + param.NickName = output_name + param.Name = output_name + param.Description = output_name + ghenv.Component.Params.OnParametersChanged() + + +def manage_dynamic_params(input_names, ghenv, rename_count=0, permanent_param_count=1, keep_connections=True): + """Clears all input parameters from the component. + + Parameters + ---------- + input_names : list(str) + The names of the input parameters. + ghenv : object + The Grasshopper environment object. + permanent_param_count : int, optional + The number of parameters that should not be deleted. Default is 1. + + Returns + ------- + None + + """ + if not input_names: # if no names are input + clear_gh_params(ghenv, permanent_param_count) + return + else: + if keep_connections: + to_remove = [] + for param in ghenv.Component.Params.Input[permanent_param_count + rename_count :]: + if param.Name not in input_names: + to_remove.append(param) + for param in to_remove: + param.IsolateObject() + ghenv.Component.Params.UnregisterInputParameter(param, True) + for i, name in enumerate(input_names): + if i < rename_count: + rename_gh_input(name, i + permanent_param_count, ghenv) + elif name not in [param.Name for param in ghenv.Component.Params.Input]: + add_gh_param(name, "Input", ghenv, index=i + permanent_param_count) + + else: + register_params = False + if ( + len(ghenv.Component.Params.Input) == len(input_names) + permanent_param_count + ): # if param count matches beam_names count + for i, name in enumerate(input_names): + if ( + ghenv.Component.Params.Input[i + permanent_param_count].Name != name + ): # if param names don't match + register_params = True + break + else: + register_params = True + if register_params: + clear_gh_params( + ghenv, permanent_param_count + rename_count + ) # we could consider renaming params if we don't want to disconnect GH component inputs + for i, name in enumerate(input_names): + if i < permanent_param_count: + continue + elif i < rename_count: + rename_gh_input(name, i, ghenv) + else: + add_gh_param(name, "Input", ghenv) diff --git a/src/compas_timber/ghpython/workflow.py b/src/compas_timber/ghpython/workflow.py index 30a6865b5..28eef2300 100644 --- a/src/compas_timber/ghpython/workflow.py +++ b/src/compas_timber/ghpython/workflow.py @@ -61,10 +61,11 @@ def comply(self, beams): class CategoryRule(JointRule): """Based on the category attribute attached to the beams, this rule assigns""" - def __init__(self, joint_type, category_a, category_b, **kwargs): + def __init__(self, joint_type, category_a, category_b, topos=None, **kwargs): self.joint_type = joint_type self.category_a = category_a self.category_b = category_b + self.topos = topos or [] self.kwargs = kwargs def ToString(self): @@ -72,8 +73,8 @@ def ToString(self): return repr(self) def __repr__(self): - return "{}({}, {}, {})".format( - CategoryRule.__name__, self.joint_type.__name__, self.category_a, self.category_b + return "{}({}, {}, {}, {})".format( + CategoryRule.__name__, self.joint_type.__name__, self.category_a, self.category_b, self.topos ) def comply(self, beams): @@ -260,41 +261,6 @@ def set_defaul_joints(model, x_default="x-lap", t_default="t-butt", l_default="l pass -class JointOptions(object): - """Container for options to be passed to a joint. - - This allows delaying the actual joining of the beams to a downstream component. - - Parameters - ---------- - type : cls(:class:`compas_timber.connections.Joint`) - The type of the joint. - kwargs : dict - The keyword arguments to be passed to the joint. - - Attributes - ---------- - type : cls(:class:`compas_timber.connections.Joint`) - The type of the joint. - kwargs : dict - The keyword arguments to be passed to the joint. - - """ - - def __init__(self, type, **kwargs): - self.type = type - self.kwargs = kwargs - - def __repr__(self): - return "{}({}{})".format(JointOptions.__name__, self.type, self.kwargs) - - def ToString(self): - return repr(self) - - def is_identical(self, other): - return isinstance(other, JointOptions) and self.kwargs == other.kwargs - - class DebugInfomation(object): """Container for debugging information allowing visual inspection of joint and features related errors.