Skip to content

Instantly share code, notes, and snippets.

@yuhan0
Created April 4, 2018 03:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yuhan0/3c6ef309ef3205e972ac6eeaed32383c to your computer and use it in GitHub Desktop.
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
"""
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