I hadn’t seen this before, looks cool but also quite convoluted. Depending on what your requirements are, two components I developed back at CITA might be enough. I don’t think I ever did publish the code outside the Smart Geometry/Advances in Architectural Geometry workshops in 2016, but here it is:
"""
Iterates the GHPython components in the document and makes user objects of them
if the their Category property matches the input Category parameter. Also grabs
all the code and writes this to .py files.
Input:
Toggle: Activate the component using a boolean toggle {item,bool}
Folder: The folder/directory to save the user objects and source code to {item,str}
Category: The name of the category for which to make user objects {item,str}
Output:
TLOC: Lines of code in user object {item,int}
Remarks:
Author: Anders Holden Deleuran
License: Apache License 2.0
Version: 160812
"""
ghenv.Component.Name = "GHPythonToUserObject"
ghenv.Component.NickName = "PTUO"
ghenv.Component.Category = "CM_FAHS"
ghenv.Component.SubCategory = "0 Development"
import os
import Grasshopper as gh
def ghPythonToUserObject(ghPyComp,writeFolder):
""" Automates the creation of a GHPython user object. Based on this thread:
http://www.grasshopper3d.com/forum/topics/change-the-default-values-for-userobject-popup-menu """
# Make a user object
uo = gh.Kernel.GH_UserObject()
# Set its properties based on the GHPython component properties
uo.Icon = ghPyComp.Icon_24x24
uo.BaseGuid = ghPyComp.ComponentGuid
uo.Exposure = ghenv.Component.Exposure.primary
uo.Description.Name = ghPyComp.Name
uo.Description.Description = ghPyComp.Description
uo.Description.Category = ghPyComp.Category
uo.Description.SubCategory = ghPyComp.SubCategory
# Set the user object data and save to file
uo.SetDataFromObject(ghPyComp)
uo.Path = os.path.join(writeFolder,ghPyComp.Name+".ghuser")
uo.SaveToFile()
# Update the grasshopper ribbon UI (doesn't seem to work)
#gh.Kernel.GH_ComponentServer.UpdateRibbonUI()
def exportGHPythonSource(ghPyComp,writeFolder):
""" Export the source code of a GHPython component """
# Get code and lines of code
code = ghPyComp.Code
code = code.replace("\r","")
lines = code.splitlines()
loc = len(lines)
# Check/make source file folder
srcFolder = os.path.join(writeFolder,"src")
if not os.path.isdir(srcFolder):
os.makedirs(srcFolder)
# Write code to .py file
srcFile = os.path.join(srcFolder,ghPyComp.Name + ".py")
f = open(srcFile,"w")
f.write(code)
f.close()
return loc
if Toggle and Folder and Category:
# Make GH component warning handler
wh = gh.Kernel.GH_RuntimeMessageLevel.Warning
# Iterate the canvas and get to the GHPython components
TLOC = 0
for obj in ghenv.Component.OnPingDocument().Objects:
if type(obj) is type(ghenv.Component):
# Check that category matches and export to files
if obj.Category == Category:
ghPythonToUserObject(obj,Folder)
loc = exportGHPythonSource(obj,Folder)
TLOC += loc
obj.AddRuntimeMessage(wh,"I was just saved as a user object, hooray!")
And this one:
"""
Updates the source code of GHPython components on the canvas with the code
in the .py files in the Folder if the GHPython component Name property is
the same as the name of one of the .py files.
Inputs:
Toggle: Activate the component {item,bool}
Folder: The location of the .py source code files {item,str}
Outputs:
GUIDS: List of GHPython component that were updated {list,str}
Remarks:
Author: Anders Holden Deleuran
License: Apache License 2.0
Version: 160402
"""
ghenv.Component.Name = "UpdateGHPythonSourceCode"
ghenv.Component.NickName = "UPSC"
ghenv.Component.Category = "CM_FAHS"
ghenv.Component.SubCategory = "0 Development"
import os
import Grasshopper as gh
def getSrcCodeVersion(srcCode):
""" Attempts to get the first instance of the word "version" (or, "Version")
in a multi line string. Then attempts to extract an integer from this line
where the word "version" exists. So format version YYMMDD in the GHPython
docstring like so (or any other integer system): """
# Get first line with version in it
srcCodeLower = srcCode.lower()
verStr = [l for l in srcCodeLower.split('\n') if "version" in l]
if verStr:
# Get the first substring integer and return it
verInt = [int(s) for s in verStr[0].split() if s.isdigit()]
if verInt:
return int(verInt[0])
else:
return None
def updateGHPythonSrc(srcFolder,ghDocument):
""" Update the source code of GHPython components in ghDocument with the code
in the .py files in the srcFolder if the GHPython component Name property is
the same as the name of one of the .py files. """
# Get python source files in folder
pyFiles = [f for f in os.listdir(srcFolder) if f.endswith(".py")]
# Make dictionary with script name as key and source code as value
srcCode = {}
for f in pyFiles:
fOpen = open(os.path.join(srcFolder,f))
fRead = fOpen.read()
fName,fExt = os.path.splitext(f)
srcCode[fName] = fRead
# Make GH component warning handler
wh = gh.Kernel.GH_RuntimeMessageLevel.Warning
# Iterate the gh canvas and update ghpython source code
guids = []
for obj in ghDocument.Objects:
if type(obj) is type(ghenv.Component):
if obj.Name in srcCode:
# Check that srcCode file has higher version number than obj source code
srcCodeVer = getSrcCodeVersion(srcCode[obj.Name])
objSrcVer = getSrcCodeVersion(obj.Code)
if objSrcVer and srcCodeVer > objSrcVer:
# Update the obj source
obj.Code = srcCode[obj.Name]
obj.AddRuntimeMessage(wh,"GHPython code was automatically updated, input/output parameters might have changed")
guids.append(str(obj.InstanceGuid))
return guids
if Toggle and Folder:
GUIDS = updateGHPythonSrc(Folder,ghenv.Component.OnPingDocument())
Edit: This related “find and replace” for all GHPython components in a Grasshopper document is also quite useful for renaming variables and such:
"""
Find and replace substrings for all GHPython code in the grasshopper doc
Inputs:
Toggle: Activate the component using a boolean toggle {item,bool}
Old: String to replace {item,str}
New: String to replace with {item,str}
Outputs:
GUIDS: List of GHPython component that were updated {list,str}
Remarks:
Author: Anders Holden Deleuran
License: Apache License 2.0
Version: 160402
"""
ghenv.Component.Name = "ReplaceGHPythonString"
ghenv.Component.NickName = "RPS"
ghenv.Component.Category = "CM_FAHS"
ghenv.Component.SubCategory = "0 Development"
import os
import Grasshopper as gh
def replaceGHPythonString(oldStr,newStr,ghDocument):
""" Find and replace substrings for all GHPython code in the grasshopper doc """
# Make GH component warning handler
wh = gh.Kernel.GH_RuntimeMessageLevel.Warning
# Iterate the gh canvas and get to ghpython source code
guids = []
for obj in ghDocument.Objects:
if type(obj) is type(ghenv.Component):
if oldStr in obj.Code:
obj.Code = obj.Code.replace(oldStr,newStr)
obj.AddRuntimeMessage(wh,"GHPython code was updated, stuff might have changed!")
guids.append(str(obj.InstanceGuid))
return guids
if Toggle and Old and New:
GUIDS = replaceGHPythonString(Old,New,ghenv.Component.OnPingDocument())
else:
GUIDS = []