Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

10 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ghpython_componentizer

Build Grasshopper .ghuser components out of plain Python source bundles. 🐡

Write your Grasshopper components as real Python files, in a real editor, under version control, and compile them into .ghuser components for the CPython (Python 3) interpreter of Rhino 8.

This package is the standalone version of the componentizer behind the compas-actions.ghpython_components GitHub action: same tool, usable from your own scripts, build systems or CI, without going through the action.

Requirements

  • Python 3.9 or newer
  • pythonnet (installed automatically) and a .NET runtime, i.e. Windows
  • The GH_IO.dll assembly, which is downloaded automatically from NuGet unless you point at a local copy
  • On macOS, Mono and libgdiplus: brew install mono mono-libgdiplus

Installation

pip install ghpython_componentizer

Usage

From the command line

ghpython-componentizer <source> <target>

For example, to build all component bundles under components into a build folder:

ghpython-componentizer components build

Optionally, tag the components with a version, which replaces the {{version}} template variable in the code:

ghpython-componentizer components build --version 0.1.2

An optional name prefix can help tell components apart from other similarly named ones:

ghpython-componentizer components build --prefix "(PACKAGE-NAME)"

If you already have a copy of GH_IO.dll (e.g. from a NuGet restore), point the tool at the folder containing it to skip the download:

ghpython-componentizer components build --ghio ./lib

The tool is also runnable as a module, which is handy when the scripts folder is not on PATH:

python -m ghpython_componentizer components build

From Python

from ghpython_componentizer import build_components

build_components("components", "build", version="0.1.2", prefix="(COMPAS)")

Or one component at a time:

from ghpython_componentizer import create_ghuser_component

create_ghuser_component("components/MyComponent", "build/MyComponent.ghuser")

Both functions download GH_IO.dll from NuGet on first use, unless the ghio_dir argument points at a folder that contains it.

From a build script, on any platform

from ghpython_componentizer import run_componentizer

run_componentizer("components", "build", version="0.1.2", prefix="(COMPAS)")

This does the same as build_components, but in a subprocess started with the environment the platform needs. On macOS that is the only thing that works from a process that is already running, see below. Everywhere else the two are interchangeable.

macOS

Only Mono can load the net48 assemblies this tool needs, and Mono draws the component icons through the native libgdiplus library. Homebrew installs libgdiplus in a prefix the dynamic loader does not search by default, so without help the build fails with a System.TypeInitializationException while embedding the icon.

DYLD_LIBRARY_PATH is only read when a process starts, so it cannot be fixed from inside a running process. The command line therefore relaunches itself once, with the right environment, and everything works out of the box:

brew install mono mono-libgdiplus
ghpython-componentizer components build

From Python, build_components and create_ghuser_component cannot fix the environment of the process calling them: they raise a RuntimeError explaining what to do. Use run_componentizer instead, or start your own subprocess with the environment returned by componentizer_env:

import subprocess
import sys

from ghpython_componentizer import componentizer_env

# Note: no shell in between, macOS strips DYLD_* variables when it starts one.
subprocess.run(
    [sys.executable, "-m", "ghpython_componentizer", "components", "build"],
    env=componentizer_env(),
    check=True,
)

The same caveat applies to the interpreter itself: macOS strips DYLD_* variables when starting a protected binary, so build with a Python from python.org, Homebrew or uv rather than /usr/bin/python3.

How to create components

  1. Create a folder to contain your components.
  2. Each component goes into its own folder, called a source bundle.
  3. The name of the folder determines the name of the .ghuser file created.
  4. Inside the component folder:
    1. Create a metadata.json file containing all required details of the component.
    2. Add a lovely icon named icon.png (24x24).
    3. Add a code.py file with the Python script of the component.
components/
└── My_Component/
    β”œβ”€β”€ code.py
    β”œβ”€β”€ icon.png
    └── metadata.json

Specification

Icon

  • Icon name should be icon.png
  • Icon dimensions should be 24x24

Python code

Supports a small set of templated variables that can be used in code:

  • {{version}}: Gets replaced with the version, if specified.
  • {{name}}: Gets replaced with the name of the component as defined in the metadata file.
  • {{ghuser_name}}: Gets replaced with the name of the .ghuser file being generated.

Metadata

  • name: Name of the component. Keep it short, single words are best.
  • nickname: Abbreviation of the component. Keep it short, 1~5 character words are best.
  • category: Category of the component. The category controls in which tab the component will end up.
  • subcategory: Subcategory for this component. The subcategory controls in which panel the component will end up.
  • description: (optional) Description of the component. Be succinct but clear.
  • exposure: (optional) Controls where the component will be exposed. Defaults to 2 (primary). Accepts one of the following integer values:
    • -1: Hidden. Do not expose the object anywhere.
    • 2: Primary. Expose the object in the first section on the toolbar.
    • 4: Secondary. Expose the object in the second section on the toolbar.
    • 8, 16, 32, 64, 128: Expose the object in the third to seventh section on the toolbar.
  • instanceGuid: (optional) Statically define a GUID for this instance. Defaults to a new Guid.
  • ghpython
    • marshalGuids: (optional) Defines whether input Guids will be looked up or not. Defaults to True. Change to False to preserve input Guids.
    • iconDisplay: (optional) Defines whether to display the icon or not. Defaults to 0.
      • 0: Application setting
      • 1: Text display
      • 2: Icon display
    • inputParameters: List of input parameters.
      • name: Name of the input parameter.
      • nickname: (optional) Abbreviation of the input parameter. Defaults to the same as name.
      • description: (optional) Description of the input parameter.
      • optional: (optional) Defines whether the input parameter is optional or not. Defaults to True.
      • allowTreeAccess: (optional) Defines whether to allow tree access for this input parameter. Defaults to True.
      • showTypeHints: (optional) Defines whether to show type hints for this input parameter. Defaults to True.
      • scriptParamAccess: (optional) Defines access type of the parameter. Defaults to item. Accepts either integer value or string value.
        • 0 / item: item access
        • 1 / list: list access
        • 2 / tree: tree access
      • wireDisplay: (optional) Defines wire display type. Accepts either integer value or string value.
        • 0 / default: Wire display is controlled by the application settings.
        • 1 / faint: Wires are displayed faintly while the parameter is not selected.
        • 2 / hidden: Wires are not displayed at all while the parameter is not selected.
      • typeHintID: (optional) Defines the type hint of the input parameter. Defaults to ghdoc. Accepts either a Guid value or one of the following string values: none, ghdoc, float, bool, int, complex, str, datetime, guid, color, point, vector, plane, interval, uvinterval, box, transform, line, circle, arc, polyline, rectangle, curve, mesh, surface, subd, brep, pointcloud, geometrybase.
      • reverse: (optional) Defines whether data inside the parameter is reversed. Defaults to False.
      • simplify: (optional) Defines whether data inside the parameter is simplified. Defaults to False.
      • flatten: (optional) Defines whether data inside the parameter is flattened. Mutually exclusive with graft. Defaults to False.
      • graft: (optional) Defines whether data inside the parameter is grafted. Mutually exclusive with flatten. Defaults to False.
    • outputParameters: List of output parameters.
      • name: Name of the output parameter.
      • nickname: (optional) Abbreviation of the output parameter. Defaults to the same as name.
      • description: (optional) Description of the output parameter.
      • optional: (optional) Defines whether the output parameter is optional or not. Defaults to False.
      • reverse: (optional) Defines whether data inside the parameter is reversed. Defaults to False.
      • simplify: (optional) Defines whether data inside the parameter is simplified. Defaults to False.
      • flatten: (optional) Defines whether data inside the parameter is flattened. Mutually exclusive with graft. Defaults to False.
      • graft: (optional) Defines whether data inside the parameter is grafted. Mutually exclusive with flatten. Defaults to False.

Caveats

GHUser components have one important limitation: once used in a document, they forget who they are. They don't know they were created out of a ghuser component, they will be simple script components. This has an important consequence: if you update the ghuser components, those already in use will NOT be automatically updated.

IronPython components

This package builds components for the CPython (Python 3) interpreter of Rhino 8 only. Components for the IronPython interpreters of Rhino 7 and Rhino 8 are still built with the scripts of the GitHub action.

Development

pip install -e ".[dev]"
pytest
ruff check .
ruff format .

Most of the test suite covers the parts that do not require a .NET runtime, so it runs on any platform. The tests marked dotnet build the examples into actual components and read them back, and skip themselves when there is no runtime to do it with:

pytest -m "not dotnet"    # unit tests only
pytest -m dotnet          # build the example components

Linting and formatting follow the same ruff rules as the other COMPAS packages, configured in pyproject.toml.

Releasing

Releases are prepared and published by pull request, with the shared compas-actions.

  1. Describe the changes under ## Unreleased in CHANGELOG.md, as they are merged.
  2. Run the prepare release workflow from the Actions tab, choosing patch, minor or major. It bumps the version, closes the changelog section and opens a release/vX.Y.Z pull request.
  3. Review and merge that pull request.

Merging it runs the release workflow: it builds the distributions, publishes them to PyPI through trusted publishing and creates the tagged GitHub release, with the changelog section as release notes. No API token is involved: PyPI needs a publisher configured for this repository, the release.yml workflow and the pypi environment.

License

This package is maintained by Gramazio Kohler Research @gramaziokohler and it is published under an MIT License.

About

Build Grasshopper .ghuser components out of plain Python source bundles

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages