From 385de90a2e905bbeb9678e6467e9b882c4eae284 Mon Sep 17 00:00:00 2001 From: obucklin Date: Thu, 15 Feb 2024 17:34:45 +0100 Subject: [PATCH] Adaptive_components_work --- CHANGELOG.md | 3 + .../CT_Joint_Options_FrenchRidgeLap/code.py | 2 +- .../components/CT_Joint_Options_LButt/code.py | 2 +- .../CT_Joint_Options_LHalfLap/code.py | 2 +- .../CT_Joint_Options_LMiter/code.py | 2 +- .../CT_Joint_Options_NullJoint/code.py | 2 +- .../components/CT_Joint_Options_TButt/code.py | 2 +- .../CT_Joint_Options_THalfLap/code.py | 2 +- .../CT_Joint_Options_XHalfLap/code.py | 2 +- .../components/CT_Joint_Rule_Category/code.py | 69 +++++++++++- .../CT_Joint_Rule_Category/metadata.json | 12 -- .../components/CT_Joint_Rule_Direct/code.py | 105 +++++++++++++----- .../CT_Joint_Rule_Direct/metadata.json | 12 -- src/compas_timber/ghpython/workflow.py | 7 +- 14 files changed, 162 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9bb4de99..8918f69cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added flag `modify_cross` to `L-Butt` joint. * Added flag `reject_i` to `L-Butt` joint. * Added new `NullJoint`. +* Added `beam_names` attribute to `JointOptions` class. +* Added `beam_names` attribute to all GH JointOption Components. ### Changed @@ -34,6 +36,7 @@ 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 `Direct Joint` and `Category Joint` GH Components to have adaptive inputs that change based on what type of joint is input. ### Removed 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 index d341e9af6..a2e6031c0 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/code.py +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_FrenchRidgeLap/code.py @@ -10,6 +10,6 @@ def RunScript(self, Cutoff): args = {} if Cutoff: args["cutoff"] = Cutoff - options = JointOptions(FrenchRidgeLapJoint, **args) + options = JointOptions(FrenchRidgeLapJoint, ['top_beam','bottom_beam'] **args) return 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 index 8096ebdff..95caa3768 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/code.py +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_LButt/code.py @@ -15,6 +15,6 @@ def RunScript(self, small_beam_butts, modify_cross, reject_i): if reject_i is not None: args["reject_i"] = reject_i - options = JointOptions(LButtJoint, **args) + options = JointOptions(LButtJoint, ["main_beam", "cross_beam"], **args) return 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 index b41f6daea..28370b4b3 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/code.py +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_LHalfLap/code.py @@ -13,6 +13,6 @@ def RunScript(self, flip_lap_side, cut_plane_bias): if cut_plane_bias: args["cut_plane_bias"] = cut_plane_bias - options = JointOptions(LHalfLapJoint, **args) + options = JointOptions(LHalfLapJoint, ["top_beam", "bottom_beam"],**args) return 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 index f1c335854..69fbf9316 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/code.py +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_LMiter/code.py @@ -10,6 +10,6 @@ def RunScript(self, Cutoff): args = {} if Cutoff: args["cutoff"] = Cutoff - options = JointOptions(LMiterJoint, **args) + options = JointOptions(LMiterJoint, ["first_beam", "second_beam"],**args) return 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 index 30de1c26a..1dd21822f 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/code.py +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_NullJoint/code.py @@ -8,5 +8,5 @@ class NullJointComponent(component): def RunScript(self): - options = JointOptions(NullJoint, **{}) + options = JointOptions(NullJoint, ["first_beam", "second_beam"], **{}) return 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 index 15c040d6e..809ea2a20 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/code.py +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_TButt/code.py @@ -10,6 +10,6 @@ def RunScript(self, Gap): args = {} if Gap: args["gap"] = Gap - options = JointOptions(TButtJoint, **args) + options = JointOptions(TButtJoint, ["main_beam", "cross_beam"], **args) return 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 index 4a1ba4c66..0d70186db 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/code.py +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_THalfLap/code.py @@ -11,6 +11,6 @@ def RunScript(self, flip_lap_side, cut_plane_bias): args["flip_lap_side"] = flip_lap_side if cut_plane_bias: args["cut_plane_bias"] = cut_plane_bias - options = JointOptions(THalfLapJoint, **args) + options = JointOptions(THalfLapJoint, ["top_beam", "bottom_beam"],**args) return 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 index 501e6df4e..a9c318857 100644 --- a/src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/code.py +++ b/src/compas_timber/ghpython/components/CT_Joint_Options_XHalfLap/code.py @@ -12,6 +12,6 @@ def RunScript(self, flip_lap_side, cut_plane_bias): args["flip_lap_side"] = flip_lap_side if cut_plane_bias: args["cut_plane_bias"] = cut_plane_bias - options = JointOptions(XHalfLapJoint, **args) + options = JointOptions(XHalfLapJoint, ["top_beam", "bottom_beam"], **args) return 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..ab731098a 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,70 @@ from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Error +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning +import Grasshopper 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 AddParam(name, IO, list = True): + 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 + " category" + param.Name = name + param.Description = name + param.Access = Grasshopper.Kernel.GH_ParamAccess.item + param.Optional = True + 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() + return param + +class DirectJointRule(component): + def __init__(self): + self.joint_type = None + + def ClearParams(self): + while len(ghenv.Component.Params.Input)>1: + ghenv.Component.Params.UnregisterInputParameter(ghenv.Component.Params.Input[len(ghenv.Component.Params.Input)-1]) + ghenv.Component.Params.OnParametersChanged() + ghenv.Component.ExpireSolution(True) + + + def RunScript(self, JointOptions, *args): + if not JointOptions: #if no JointOptions is input + print("no joint") + self.ClearParams() + self.joint_type = None + return + + if JointOptions.type != self.joint_type: # if JointOptions changes + if len(JointOptions.beam_names) != 2: + self.AddRuntimeMessage(Error, "Component currently only supports joint types with 2 beams.") + self.ClearParams() + self.joint_type = JointOptions.type + for name in JointOptions.beam_names: + AddParam(name, "Input") + + if len(ghenv.Component.Params.Input) != 3: # something went wrong and the number of input parameters is wrong + self.AddRuntimeMessage(Warning, "Input parameter error.") + return + + if len(args) < 2: + self.AddRuntimeMessage(Warning, "Input parameters failed to collect data.") + return + categories = [] + create_rule = True + for i in range(len(ghenv.Component.Params.Input)-1): + if not args[i]: + self.AddRuntimeMessage(Warning, "Input parameter {} {} failed to collect data.".format(JointOptions.beam_names[i], 'categories')) + create_rule = False + else: + categories.append(args[i]) + + if create_rule: + return CategoryRule(JointOptions.type, categories[0], categories[1], **JointOptions.kwargs) 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..abf90b67d 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 @@ -14,18 +14,6 @@ "description": "Joint Options component.", "typeHintID": "none", "scriptParamAccess": 0 - }, - { - "name": "CatA", - "description": "Categeory of the first Beam.", - "typeHintID": "str", - "scriptParamAccess": 0 - }, - { - "name": "CatB", - "description": "Category of the sectond Beam.", - "typeHintID": "str", - "scriptParamAccess": 0 } ], "outputParameters": [ 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..8bb779ab1 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,92 @@ from ghpythonlib.componentbase import executingcomponent as component from Grasshopper.Kernel.GH_RuntimeMessageLevel import Error from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning +import Grasshopper from compas_timber.connections import ConnectionSolver from compas_timber.connections import JointTopology from compas_timber.ghpython import DirectRule +def AddParam(name, IO, list = True): + 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.list + param.Optional = True + 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() + return param + + + + 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): + def __init__(self): + self.joint_type = None + + def ClearParams(self): + while len(ghenv.Component.Params.Input)>1: + ghenv.Component.Params.UnregisterInputParameter(ghenv.Component.Params.Input[len(ghenv.Component.Params.Input)-1]) + ghenv.Component.Params.OnParametersChanged() + + + def RunScript(self, JointOptions, *args): + if not JointOptions: #if no JointOptions is input + self.ClearParams() + self.joint_type = None 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!") + + if JointOptions.type != self.joint_type: # if JointOptions changes + if len(JointOptions.beam_names) != 2: + self.AddRuntimeMessage(Error, "Component currently only supports joint types with 2 beams.") + self.ClearParams() + self.joint_type = JointOptions.type + for name in JointOptions.beam_names: + AddParam(name, "Input") + + if len(ghenv.Component.Params.Input) != 3: + self.AddRuntimeMessage(Warning, "Input parameter error.") + return + + if len(args) < 2: + self.AddRuntimeMessage(Warning, "Input parameters failed to collect data.") return - Rules = [] - for main, secondary in zip(MainBeam, SecondaryBeam): - topology, _, _ = ConnectionSolver().find_topology(main, secondary) - if topology != JointOptions.type.SUPPORTED_TOPOLOGY: - self.AddRuntimeMessage( - Warning, - "Beams meet with topology: {} which does not agree with joint of type: {}".format( - JointTopology.get_name(topology), JointOptions.type.__name__ - ), - ) - continue - Rules.append(DirectRule(JointOptions.type, [secondary, main], **JointOptions.kwargs)) - return Rules + + beams = [] + create_rule = True + for i in range(len(ghenv.Component.Params.Input)-1): + if not args[i]: + self.AddRuntimeMessage(Warning, "Input parameter {} failed to collect data.".format(JointOptions.beam_names[i])) + create_rule = False + else: + arg_beams= args[i] + if not isinstance(arg_beams, list): + arg_beams = [arg_beams] + beams.append(arg_beams) + + if create_rule: + if len(beams[0]) != len(beams[1]): + self.AddRuntimeMessage(Error, "Number of items in {} and {} must match!".format(JointOptions.beam_names[0], JointOptions.beam_names[1])) + return + Rules = [] + for main, secondary in zip(beams[0], beams[1]): + topology, _, _ = ConnectionSolver().find_topology(main, secondary) + if topology != JointOptions.type.SUPPORTED_TOPOLOGY: + self.AddRuntimeMessage( + Warning, + "Beams meet with topology: {} which does not agree with joint of type: {}".format( + JointTopology.get_name(topology), JointOptions.type.__name__ + ), + ) + continue + Rules.append(DirectRule(JointOptions.type, [main, secondary], **JointOptions.kwargs)) + return Rules 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..0a1d2d196 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 @@ -14,18 +14,6 @@ "description": "Joint Options Component", "typeHintID": "none", "scriptParamAccess": 0 - }, - { - "name": "MainBeam", - "description": "Main Beam.", - "typeHintID": "none", - "scriptParamAccess": 1 - }, - { - "name": "SecondaryBeam", - "description": "Secondary Beam.", - "typeHintID": "none", - "scriptParamAccess": 1 } ], "outputParameters": [ diff --git a/src/compas_timber/ghpython/workflow.py b/src/compas_timber/ghpython/workflow.py index 30a6865b5..2baca1e2f 100644 --- a/src/compas_timber/ghpython/workflow.py +++ b/src/compas_timber/ghpython/workflow.py @@ -271,6 +271,8 @@ class JointOptions(object): The type of the joint. kwargs : dict The keyword arguments to be passed to the joint. + beam_names : list(str) + The names of the beams to be joined. Attributes ---------- @@ -278,12 +280,15 @@ class JointOptions(object): The type of the joint. kwargs : dict The keyword arguments to be passed to the joint. + beam_names : list(str) + The names of the beams to be joined. """ - def __init__(self, type, **kwargs): + def __init__(self, type, beam_names, **kwargs): self.type = type self.kwargs = kwargs + self.beam_names = beam_names def __repr__(self): return "{}({}{})".format(JointOptions.__name__, self.type, self.kwargs)