Created
April 4, 2018 03:51
-
-
Save yuhan0/3c6ef309ef3205e972ac6eeaed32383c to your computer and use it in GitHub Desktop.
Adds keyword-argument style parameters to Grasshopper components. Copy and paste this code into an empty GhPython component, it should automatically setup input parameters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Adds annotations to internalized input parameters for all components on the canvas. | |
using the format `X=Y`. Completely reversible via toggling the `enable` input. | |
Default enabled types are Boolean, Integer, Number, String, Interval, and Plane, | |
connect a Value List to the `types` input to enable others. | |
""" | |
from Grasshopper.Kernel import Types, Parameters, GH_Component | |
from Grasshopper.Kernel.Special import GH_ValueList, GH_ValueListItem, GH_ValueListMode | |
this = ghenv.Component | |
# region RUN_ONCE_ONLY | |
# Crazy self-modifying hack to remove default parameters "x, y, a" if they have no wires connected | |
# this block of code between #region and #endregion will be commented out after the first time the code runs. | |
from Grasshopper.Kernel import GH_ParameterSide | |
reversible = False | |
if reversible: | |
# comment out the region by surrounding it in triple quotes | |
newcode = this.Code.replace("\n# region RUN_ONCE_ONLY", "\n# region RUN_ONCE_ONLY\n\"\"\"") | |
newcode = newcode.replace("\n# endregion RUN_ONCE_ONLY", "\n\"\"\"# endregion RUN_ONCE_ONLY") | |
else: | |
# cut the entire region out irreversibly | |
newcode = this.Code.partition("\n# region RUN_ONCE_ONLY")[0] + this.Code.rpartition("\n# endregion RUN_ONCE_ONLY")[-1] | |
def callback(doc): | |
for pos, pm in enumerate(list(this.Params.Input)): | |
if pm.NickName in "xy" and pm.SourceCount == 0: | |
this.DestroyParameter(GH_ParameterSide.Input, pos) | |
this.Params.UnregisterInputParameter(pm) | |
for pos, pm in enumerate(list(this.Params.Output)): | |
if pm.NickName == "a" and pm.Recipients.Count == 0: | |
this.DestroyParameter(GH_ParameterSide.Output, pos) | |
this.Params.UnregisterOutputParameter(pm) | |
if this.IsCodeInputLinked(): | |
this.CodeInputParam.Sources.Clear() | |
this.CodeInputParam.Script_ClearPersistentData() | |
this.CodeInputParam.SetPersistentData(newcode) | |
this.VariableParameterMaintenance() | |
this.OnPingDocument().ScheduleSolution(1, callback) | |
def ensure_input(name, hint=None, access=0): | |
if this.Params.Input.Find(lambda p: p.NickName == name): | |
return | |
def callback(doc): | |
from Grasshopper.Kernel import GH_ParameterSide, GH_ParamAccess | |
pos = this.Params.Input.Count | |
pm = this.CreateParameter(GH_ParameterSide.Input, pos) | |
pm.NickName = name | |
default_hint = this.GetType().Assembly.GetType("GhPython.Component.NoChangeHint")() | |
pm.TypeHint = default_hint if not isinstance(hint, Parameters.IGH_TypeHint) else hint | |
pm.Access = GH_ParamAccess.ToObject(GH_ParamAccess, access) | |
this.Params.RegisterInputParam(pm, pos) | |
this.VariableParameterMaintenance() | |
this.OnPingDocument().ScheduleSolution(1, callback) | |
ensure_input("enable", hint=Parameters.Hints.GH_BooleanHint_CS()) | |
ensure_input("types", access=1) | |
# endregion RUN_ONCE_ONLY | |
ann_types = [Types.GH_Boolean, Types.GH_Integer, Types.GH_Number, Types.GH_String, Types.GH_Interval, Types.GH_Plane, | |
Types.GH_Point, Types.GH_Vector, Types.GH_ComplexNumber, Types.GH_Colour, Types.GH_Time] | |
# first N types in the list to be enabled for annotation. | |
# connect value list for more options | |
default_idx = 6 | |
# truncates the annotation if above N characters - | |
# prevents long strings from expanding the component width too much | |
maxlen = 20 | |
this.Name = "AutoAnnotate" | |
types_param = this.Params.Input.Find(lambda p: p.NickName == "types") | |
types_param.Description = "Connect a Value List for more options" | |
vallist = types_param.Sources[0] if types_param.SourceCount == 1 else None | |
if isinstance(vallist, GH_ValueList): | |
if len(ann_types) != vallist.ListItems.Count: | |
# clear and rebuild the list | |
vallist.ListMode = GH_ValueListMode.CheckList | |
vallist.ListItems.Clear() | |
for i, _type in enumerate(ann_types): | |
typename = _type().TypeName | |
item = GH_ValueListItem(typename, typename) | |
item.Selected = (i < default_idx) | |
vallist.ListItems.Insert(i, item) | |
vallist.Attributes.ExpireLayout() | |
vallist.Attributes.PerformLayout() | |
ann_types = [t for t, b in zip(ann_types, vallist.SaveState()) if b == "Y"] | |
else: | |
ann_types = ann_types[:default_idx] | |
for obj in this.OnPingDocument().Objects: | |
if not isinstance(obj, GH_Component): | |
continue | |
has_changed = False | |
for param in obj.Params.Input: | |
if not hasattr(param, "PersistentData"): | |
# less overhead than testing if assignable from generic type GH_PersistentParam[T] | |
continue | |
if isinstance(param, Parameters.Param_ScriptVariable) or param.Name == "code": | |
# script variables depend on their nicknames, do not mess with them | |
# also adding an exception for the "code" input of GhPython component | |
continue | |
try: | |
assert enable | |
assert param.SourceCount == 0 | |
assert param.PersistentDataCount == 1 | |
pers_data = param.PersistentData[0][0] | |
assert pers_data.GetType() in ann_types | |
data_repr = pers_data.ToString() | |
if isinstance(pers_data, Types.GH_String): | |
data_repr = '"{}"'.format(data_repr) | |
if len(data_repr) > maxlen: | |
data_repr = data_repr[:maxlen] + u"\u2026" # ellipsis | |
param.NickName = param.NickName.partition("=")[0] + "=" + data_repr | |
has_changed = True | |
except AssertionError: | |
if "=" in param.NickName: | |
param.NickName = param.NickName.partition("=")[0] | |
has_changed = True | |
if has_changed: | |
obj.Attributes.ExpireLayout() | |
obj.Attributes.PerformLayout() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment