diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 69736fe..0ee9f05 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -3,22 +3,46 @@ name: build
on: [push]
jobs:
- build:
- name: windows-ironpython
+ build_cpy_ghuser_components:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: NuGet/setup-nuget@v1.0.5
+
+ - name: Install CPython and pythonnet package
+ run: |
+ choco install python --version=3.9.10
+ python -m pip install pythonnet==3.0.3
+
+ - name: Run
+ uses: ./
+ with:
+ source: examples/cpy
+ target: build
+ interpreter: cpython
+
+ - uses: actions/upload-artifact@v2
+ with:
+ name: cpy_ghuser-components
+ path: build
+
+ build_ipy_ghuser_components:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: NuGet/setup-nuget@v1.0.5
+
- name: Install IronPython
run: |
choco install ironpython --version=2.7.8.1
- - name: Install dependencies
- run: |
- nuget install Grasshopper -OutputDirectory ./lib -source https://api.nuget.org/v3/index.json
+
- name: Run
- run: |
- ipy componentize.py examples build
+ uses: ./
+ with:
+ source: examples/ipy
+ target: build
+
- uses: actions/upload-artifact@v2
with:
- name: ghuser-components
- path: build
+ name: ipy_ghuser-components
+ path: build
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index b6e4761..6881b55 100644
--- a/.gitignore
+++ b/.gitignore
@@ -127,3 +127,6 @@ dmypy.json
# Pyre type checker
.pyre/
+
+# avoid temp folder
+temp/
\ No newline at end of file
diff --git a/README.md b/README.md
index 151fa1f..06db679 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
# GHPython Componentizer
-> A github action to make Grasshopper development 164% [1] version-control friendlier and 82% more pleasant.
+> A github action to make Grasshopper development 165% [1] version-control friendlier and 83% more pleasant.
-Imagine if you could write your grasshopper components in Python code in an actual text file with a powerful editor?
+Imagine if you could write your grasshopper components in Python code (both IronPython for RhinoV7 and less, or CPython for RhinoV8) in an actual text file with a powerful editor?
Git wouldn't hate you and life would be so much beautiful. 🐵
Well, here's an action for you then! 🦸♀️
@@ -14,25 +14,29 @@ Well, here's an action for you then! 🦸♀️
### Usage from Github Actions
The recommended way to use this tool is as a Github Action.
-It needs to be run on a windows runner and IronPython/NuGet need to be pre-installed.
+It needs to be run on a windows runner and IronPython/NuGet or Python3/pythonnet/Nuget depending of which component you want to build, needs to be pre-installed.
Copy the following workflow code into a `.github/workflows/main.yml` file in your repository.
Make sure you have the components definition (see below for details) stored in a source folder.
Replace the `source` and `target` to match your folder structure.
+To specify the interpreter to use, you can define the action parameter `interpreter` to either `ironpython` or `python3` (by default it is `ironpython`).
+
+For IronPython (RhinoV7 and less):
```yaml
on: [push]
jobs:
- build_ghuser_components:
+ build_ipy_ghuser_components:
runs-on: windows-latest
- name: Build components
steps:
- uses: actions/checkout@v2
- uses: NuGet/setup-nuget@v1.0.5
+
- name: Install IronPython
run: |
choco install ironpython --version=2.7.8.1
+
- uses: compas-dev/compas-actions.ghpython_components@v2
with:
source: components
@@ -43,28 +47,60 @@ jobs:
# upload them as artifacts:
- uses: actions/upload-artifact@v2
with:
- name: ghuser-components
+ name: ipy_ghuser-components
path: build
+```
+For Python3 (RhinoV8):
+
+```yaml
+on: [push]
+
+jobs:
+ build_cpy_ghuser_components:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: NuGet/setup-nuget@v1.0.5
+
+ - name: Install CPython and pythonnet package
+ run: |
+ choco install python --version=3.9.10
+ python -m pip install pythonnet==3.0.3
+
+ - uses: compas-dev/compas-actions.ghpython_components@v2
+ with:
+ source: components
+ target: build
+ interpreter: cpython # optional, defaults to ironpython
+
+ - uses: actions/upload-artifact@v2
+ with:
+ name: cpy_ghuser-components
+ path: build
```
+
Commit, push and enjoy! 🍿
### Usage on the command line
Alternatively, you can also use this tool directly from the command line.
-Make sure to have IronPython installed and the `GH_IO.dll` assembly available.
+Make sure to have IronPython or Python3/pythonnet installed and the `GH_IO.dll` assembly available.
Then start the script pointing it to a source and target folder, e.g.:
- ipy componentize.py examples build
+ ipy componentize_ipy.py examples/ipy build
+ python componentize_cpy.py examples/cpy build
Optionally, tag it with a version:
- ipy componentize.py examples build --version 0.1.2
+ ipy componentize_ipy.py examples/ipy build --version 0.1.2
+ python componentize_cpy.py examples/cpy build --version 0.1.2
An optional name prefix can help tell components apart from other similarly named ones:
- ipy componentize.py examples build --prefix "(PACKAGE-NAME)"
+ ipy componentize_ipy.py examples/ipy build --prefix "(PACKAGE-NAME)"
+ python componentize_cpy.py examples/cpy build --prefix "(PACKAGE-NAME)"
## How to create components
@@ -96,8 +132,7 @@ An alternative is to include them in your packaging steps, e.g. calling `python
## Python code
-* Supports both procedural and GH_Component SDK modes (see `isAdvancedMode` in metadata)
-* Supports a small set of templated variables that can be used in code:
+Supports a small set of templated variables that can be used in code:
* `{{version}}`: Gets replaced with the version, if specified in the command-line.
* `{{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.
@@ -120,10 +155,11 @@ An alternative is to include them in your packaging steps, e.g. calling `python
* `128`: Expose the object in the seventh section on the toolbar.
* `instanceGuid`: **(optional)** Statically define a GUID for this instance. Defaults to a new Guid.
* `ghpython`
- * `hideOutput`: **(optional)** Defines whether to hide or not `out` output parameter. Defaults to `True`.
- * `hideInput`: **(optional)** Defines whether to hide or not the `code` input parameter. Defaults to `True`.
- * `isAdvancedMode`: **(optional)** Defines whether the script is in advanced mode (aka GH_Component SDK mode) or procedural mode. Defaults to `False`.
- * `marshalOutGuids`: **(optional)** Defines whether output Guids will be looked up or not. Defaults to `True`. Change to `False` to preserve output Guids.
+ * `hideOutput`: **(optional ⚠️ only IronPython)** Defines whether to hide or not `out` output parameter. Defaults to `True`.
+ * `hideInput`: **(optional ⚠️ only IronPython)** Defines whether to hide or not the `code` input parameter. Defaults to `True`.
+ * `isAdvancedMode`: **(optional ⚠️ only IronPython)** Defines whether the script is in advanced mode (aka GH_Component SDK mode) or procedural mode. Defaults to `False`.
+ * `marshalOutGuids`: **(optional ⚠️ only IronPython)** Defines whether output Guids will be looked up or not. Defaults to `True`. Change to `False` to preserve output Guids.
+ * `marshalGuids`: **(optional ⚠️ only CPython)** 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
diff --git a/action.yml b/action.yml
index a24768c..2628e73 100644
--- a/action.yml
+++ b/action.yml
@@ -10,15 +10,29 @@ inputs:
prefix:
description: 'Add this prefix to the name of each generated component'
required: false
+ interpreter:
+ description: 'Python interpreter to use: ironpython, or cpython'
+ required: false
+ default: 'ironpython'
+
runs:
using: 'composite'
steps:
- - run: nuget install Grasshopper -OutputDirectory ./lib -source https://api.nuget.org/v3/index.json
+ - name: Install Grasshopper
+ run: nuget install Grasshopper -OutputDirectory ./lib -source https://api.nuget.org/v3/index.json
shell: pwsh
- - run: |
- $command="ipy"
- $params="${{ github.action_path }}/componentize.py", "${{ inputs.source }}", "${{ inputs.target }}", "--ghio", "./lib"
- $prefix="${{ inputs.prefix }}"
+
+ - name: Launch componentizer
+ run: |
+ if ("${{ inputs.interpreter }}" -eq "cpython") {
+ $command="python"
+ $componentizer="${{ github.action_path }}/componentize_cpy.py"
+ } else {
+ $command="ipy"
+ $componentizer="${{ github.action_path }}/componentize_ipy.py"
+ }
+ $params=$componentizer, "${{ inputs.source }}", "${{ inputs.target }}", "--ghio", "./lib"
+ $prefix="${{ inputs.prefix }}"
if( $prefix )
{
$params=$params + "--prefix", "$prefix"
diff --git a/componentize_cpy.py b/componentize_cpy.py
new file mode 100644
index 0000000..cdffe82
--- /dev/null
+++ b/componentize_cpy.py
@@ -0,0 +1,408 @@
+import argparse
+import base64
+import json
+import os
+import re
+import sys
+import tempfile
+import urllib.request, urllib.parse, urllib.error
+import zipfile
+from io import BytesIO
+
+import clr
+import System
+import System.IO
+
+
+SCRIPT_COMPONENT_GUID = System.Guid("c9b2d725-6f87-4b07-af90-bd9aefef68eb")
+CPY_VER = "3.-1"
+TEMPLATE_VER = re.compile("{{version}}")
+TEMPLATE_NAME = re.compile("{{name}}")
+TEMPLATE_GHUSER_NAME = re.compile("{{ghuser_name}}")
+
+TYPES_MAP = dict(
+ none="6a184b65-baa3-42d1-a548-3915b401de53",
+ ghdoc="1c282eeb-dd16-439f-94e4-7d92b542fe8b",
+ float="9d51e32e-c038-4352-9554-f4137ca91b9a",
+ bool="d60527f5-b5af-4ef6-8970-5f96fe412559",
+ int="48d01794-d3d8-4aef-990e-127168822244",
+ complex="309690df-6229-4774-91bb-b1c9c0bfa54d",
+ str="3aceb454-6dbd-4c5b-9b6b-e71f8c1cdf88",
+ datetime="09bcf900-fe83-4efa-8d32-33d89f7a3e66",
+ guid="5325b8e1-51d7-4d36-837a-d98394626c35",
+ color="24b1d1a3-ab79-498c-9e44-c5b14607c4d3",
+ point="e1937b56-b1da-4c12-8bd8-e34ee81746ef",
+ vector="15a50725-e3d3-4075-9f7c-142ba5f40747",
+ plane="3897522d-58e9-4d60-b38c-978ddacfedd8",
+ interval="589748aa-e558-4dd9-976f-78e3ab91fc77",
+ uvinterval="74c906f3-db02-4cea-bd58-de375cb5ae73",
+ box="f29cb021-de79-4e63-9f04-fc8e0df5f8b6",
+ transform="c4b38e4c-21ff-415f-a0d1-406d282428dd",
+ line="f802a8cd-e699-4a94-97ea-83b5406271de",
+ circle="3c5409a1-3293-4181-a6fa-c24c37fc0c32",
+ arc="9c80ec18-b48c-41b0-bc6e-cd93d9c916aa",
+ polyline="66fa617b-e3e8-4480-9f1e-2c0688c1d21b",
+ rectangle="83da014b-a550-4bf5-89ff-16e54225bd5d",
+ curve="9ba89ec2-5315-435f-a621-b66c5fa2f301",
+ mesh="794a1f9d-21d5-4379-b987-9e8bbf433912",
+ surface="f4070a37-c822-410f-9057-100d2e22a22d",
+ subd="20f4ca9c-6c90-4fd6-ba8a-5bf9ca79db08",
+ brep="2ceb0405-fdfe-403d-a4d6-8786da45fb9d",
+ geometrybase="c37956f4-d39c-49c7-af71-1e87f8031b26"
+)
+
+EXPOSURE = dict(valid=set([-1, 2, 4, 8, 16, 32, 64, 128]), default=2)
+ACCESS = dict(valid=set([0, 1, 2]), map=dict(item=0, list=1, tree=2), default=0)
+PARAM_TYPE = dict(
+ valid=set(TYPES_MAP.values()), map=TYPES_MAP, default=TYPES_MAP["ghdoc"]
+)
+WIRE_DISPLAY = dict(
+ valid=set([0, 1, 2]), map=dict(default=0, faint=1, hidden=2), default=0
+)
+
+
+def fetch_ghio_lib(target_folder="temp"):
+ """Fetch the GH_IO.dll library from the NuGet packaging system."""
+ ghio_dll = "GH_IO.dll"
+ filename = "lib/net48/" + ghio_dll
+
+ response = urllib.request.urlopen("https://www.nuget.org/api/v2/package/Grasshopper/")
+ dst_file = os.path.join(target_folder, ghio_dll)
+ zip_file = zipfile.ZipFile(BytesIO(response.read()))
+
+ with zip_file.open(filename, "r") as zipped_dll:
+ with open(dst_file, "wb") as fp:
+ fp.write(zipped_dll.read())
+
+ return dst_file
+
+
+def find_ghio_assembly(libdir):
+ for root, _dirs, files in os.walk(libdir):
+ for basename in files:
+ if basename.upper() == "GH_IO.DLL":
+ filename = os.path.join(root, basename)
+ return filename
+
+
+def bitmap_from_image_path(image_path):
+ with open(image_path, "rb") as imageFile:
+ # Ensure img_string is a string, not a bytes object
+ img_string = base64.b64encode(imageFile.read())
+ if isinstance(img_string, bytes):
+ img_string = img_string.decode()
+
+ # Now you can pass img_string to the FromBase64String method
+ return System.Convert.FromBase64String(img_string)
+ # return System.Convert.FromBase64String(img_string)
+
+
+def validate_source_bundle(source):
+ icon = os.path.join(source, "icon.png")
+ code = os.path.join(source, "code.py")
+ data = os.path.join(source, "metadata.json")
+
+ if not os.path.exists(icon):
+ raise ValueError(
+ "icon missing, make sure icon.png is present in the source bundle: {}".format(
+ source
+ )
+ )
+ if not os.path.exists(code):
+ raise ValueError(
+ "code missing, make sure code.py is present in the source bundle: {}".format(
+ source
+ )
+ )
+ if not os.path.exists(data):
+ raise ValueError(
+ "metadata missing, make sure metadata.json is present in the source bundle: {}".format(
+ source
+ )
+ )
+
+ icon = bitmap_from_image_path(icon)
+
+ with open(code, "r") as f:
+ python_code = f.read()
+
+ with open(data, "r") as f:
+ data = json.load(f)
+
+ if "exposure" not in data:
+ data["exposure"] = EXPOSURE["default"]
+
+ if data["exposure"] not in EXPOSURE["valid"]:
+ raise ValueError(
+ "Invalid exposure value. Accepted values are {}".format(
+ sorted(EXPOSURE["valid"])
+ )
+ )
+
+ ghpython = data.get("ghpython")
+
+ if r'"""' not in python_code:
+ python_code = r'"""{}"""{}{}'.format(
+ data.get("description", "Generated by Componentizer"),
+ os.linesep,
+ python_code,
+ )
+
+ return icon, python_code, data
+
+
+def parse_param_access(access):
+ try:
+ access = int(access)
+ except ValueError:
+ # Maybe string?
+ access = ACCESS["map"].get(access)
+
+ if access not in ACCESS["valid"]:
+ raise ValueError(
+ "Invalid param access value. Valid values are {}".format(
+ sorted(ACCESS["valid"])
+ )
+ )
+
+ return access
+
+
+def parse_wire_display(wire_display):
+ try:
+ wire_display = int(wire_display)
+ except ValueError:
+ wire_display = WIRE_DISPLAY["map"].get(wire_display)
+
+ if wire_display not in WIRE_DISPLAY["valid"]:
+ raise ValueError(
+ "Invalid wire display value. Valid values are {}".format(
+ sorted(WIRE_DISPLAY["valid"])
+ )
+ )
+
+ return wire_display
+
+
+def parse_param_type_hint(type_hint_id):
+ type_hint_id = type_hint_id or PARAM_TYPE["default"]
+
+ if type_hint_id in TYPES_MAP:
+ type_hint_id = TYPES_MAP[type_hint_id]
+
+ if type_hint_id not in PARAM_TYPE["valid"]:
+ raise ValueError(
+ 'Invalid param type hint ID ("{}"). Valid values are {}'.format(
+ type_hint_id, sorted(PARAM_TYPE["valid"])
+ )
+ )
+
+ try:
+ type_hint_id = System.Guid.Parse(type_hint_id)
+ except SystemError:
+ raise ValueError("Unable to parse type hint ID: {}".format(type_hint_id))
+
+ return type_hint_id
+
+
+def replace_templates(code, version, name, ghuser_name):
+ if version:
+ code = TEMPLATE_VER.sub(version, code)
+
+ code = TEMPLATE_NAME.sub(name, code)
+ code = TEMPLATE_GHUSER_NAME.sub(ghuser_name, code)
+
+ return code
+
+
+def create_ghuser_component(source, target, version=None, prefix=None):
+ from GH_IO.Serialization import GH_LooseChunk
+
+ icon, code, data = validate_source_bundle(source)
+
+ code = replace_templates(code, version, data["name"], os.path.basename(target))
+
+ instance_guid = data.get("instanceGuid")
+ if not instance_guid:
+ instance_guid = System.Guid.NewGuid()
+ else:
+ instance_guid = System.Guid.Parse(instance_guid)
+
+ prefix = prefix or ""
+
+ root = GH_LooseChunk("UserObject")
+ root.SetGuid("BaseID", SCRIPT_COMPONENT_GUID)
+ root.SetString("Name", prefix + data["name"])
+ root.SetString("NickName", data["nickname"])
+ root.SetString("Description", data.get("description", ""))
+ root.SetString("ToolTip", data.get("description", ""))
+ root.SetInt32("Exposure", data.get("exposure", EXPOSURE["default"]))
+ root.SetString("Category", data["category"])
+ root.SetString("SubCategory", data["subcategory"])
+ root.SetGuid("InstanceGuid", instance_guid)
+ root.SetByteArray("Icon", icon)
+
+ ghpython_data = data["ghpython"]
+
+ ghpython_root = GH_LooseChunk("UserObject")
+ ghpython_root.SetString("Description", data.get("description", ""))
+ bitmap_icon = System.Drawing.Bitmap.FromStream(System.IO.MemoryStream(icon))
+ ghpython_root.SetDrawingBitmap("IconOverride", bitmap_icon)
+ ghpython_root.SetBoolean("UsingLibraryInputParam", False)
+ ghpython_root.SetBoolean("UsingScriptInputParam", False)
+ ghpython_root.SetBoolean("UsingStandardOutputParam", False)
+ ghpython_root.SetInt32("IconDisplay", ghpython_data.get("iconDisplay", 0))
+ ghpython_root.SetString("Name", data["name"])
+ ghpython_root.SetString("NickName", data["nickname"])
+ ghpython_root.SetBoolean("MarshalGuids", ghpython_data.get("marshalGuids", True))
+
+ # ghpython_root.CreateChunk('Attributes')
+ # for mf in ('Bounds', 'Pivot', 'Selected'):
+
+ params = ghpython_root.CreateChunk("ParameterData")
+ inputParam = ghpython_data.get("inputParameters", [])
+ outputParam = ghpython_data.get("outputParameters", [])
+
+ params.SetInt32("InputCount", len(inputParam))
+ for i, _pi in enumerate(inputParam):
+ params.SetGuid(
+ "InputId", i, System.Guid.Parse("08908df5-fa14-4982-9ab2-1aa0927566aa")
+ )
+ params.SetInt32("OutputCount", len(outputParam))
+ for i, _po in enumerate(outputParam):
+ params.SetGuid(
+ "OutputId", i, System.Guid.Parse("08908df5-fa14-4982-9ab2-1aa0927566aa")
+ )
+
+ for i, pi in enumerate(inputParam):
+ input_instance_guid = System.Guid.NewGuid()
+ pi_chunk = params.CreateChunk("InputParam", i)
+ pi_chunk.SetString("Name", pi["name"])
+ pi_chunk.SetString("NickName", pi.get("nickname") or pi["name"])
+ pi_chunk.SetString("Description", pi.get("description"))
+ pi_chunk.SetBoolean("Optional", pi.get("optional", True))
+ pi_chunk.SetString("ToolTip", pi.get("description", ""))
+ pi_chunk.SetBoolean("AllowTreeAccess", pi.get("allowTreeAccess", True))
+ pi_chunk.SetBoolean("ShowTypeHints", pi.get("showTypeHints", True))
+ pi_chunk.SetInt32(
+ "ScriptParamAccess",
+ parse_param_access(pi.get("scriptParamAccess", ACCESS["default"])),
+ )
+ pi_chunk.SetInt32("SourceCount", pi.get("sourceCount", 0))
+ pi_chunk.SetGuid("InstanceGuid", input_instance_guid)
+ pi_chunk.SetGuid("TypeHintID", parse_param_type_hint(pi.get("typeHintID")))
+ pi_chunk.SetInt32(
+ "WireDisplay",
+ parse_wire_display(pi.get("wireDisplay", WIRE_DISPLAY["default"])),
+ )
+ pi_chunk.SetBoolean("ReverseData", pi.get("reverse", False))
+ pi_chunk.SetBoolean("SimplifyData", pi.get("simplify", False))
+ if pi.get("flatten", False):
+ pi_chunk.SetInt32("Mapping", 1)
+ elif pi.get("graft", False):
+ pi_chunk.SetInt32("Mapping", 2)
+
+ for i, po in enumerate(outputParam):
+ output_instance_guid = System.Guid.NewGuid()
+ po_chunk = params.CreateChunk("OutputParam", i)
+ po_chunk.SetString("Name", po["name"])
+ po_chunk.SetString("NickName", po.get("nickname") or po["name"])
+ po_chunk.SetString("Description", po.get("description"))
+ po_chunk.SetBoolean("Optional", po.get("optional", False))
+ po_chunk.SetString("ToolTip", po.get("description", ""))
+ po_chunk.SetInt32("SourceCount", po.get("sourceCount", 0))
+ po_chunk.SetGuid("InstanceGuid", output_instance_guid)
+ po_chunk.SetBoolean("ReverseData", po.get("reverse", False))
+ po_chunk.SetBoolean("SimplifyData", po.get("simplify", False))
+ if po.get("flatten", False):
+ po_chunk.SetInt32("Mapping", 1)
+ elif po.get("graft", False):
+ po_chunk.SetInt32("Mapping", 2)
+
+ script = ghpython_root.CreateChunk("Script")
+
+ code_base64 = base64.b64encode(code.encode("utf-8"))
+ code_base64 = str(code_base64)[2:-1]
+ script.SetString("Text", code_base64)
+ script.SetString("Title", "S")
+ language_spec = script.CreateChunk("LanguageSpec")
+ language_spec.SetString("Taxon", "*.*.python")
+ language_spec.SetString("Version", CPY_VER)
+
+ # xml_serialized = ghpython_root.Serialize_Xml()
+ root.SetByteArray("Object", ghpython_root.Serialize_Binary())
+ System.IO.File.WriteAllBytes(target, root.Serialize_Binary())
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(
+ description="Create GHUser components out of python code."
+ )
+ parser.add_argument(
+ "source",
+ type=str,
+ help="Source directory where code for all components is stored",
+ )
+ parser.add_argument("target", type=str, help="Target directory for ghuser files")
+ parser.add_argument(
+ "--ghio",
+ type=str,
+ required=False,
+ help="Folder where the GH_IO.dll assembly is located. Defaults to ./lib",
+ )
+ parser.add_argument(
+ "--version", type=str, required=False, help="Version to tag components"
+ )
+ parser.add_argument(
+ "--prefix",
+ type=str,
+ required=False,
+ help="Add this prefix to the name of each generated component",
+ )
+ args = parser.parse_args()
+
+ sourcedir = args.source
+ if not os.path.isabs(sourcedir):
+ sourcedir = os.path.abspath(sourcedir)
+
+ targetdir = args.target
+ if not os.path.isabs(targetdir):
+ targetdir = os.path.abspath(targetdir)
+
+ if args.ghio is None:
+ libdir = tempfile.mkdtemp("ghio")
+ fetch_ghio_lib(libdir)
+ else:
+ libdir = os.path.abspath(args.ghio)
+ gh_io = find_ghio_assembly(libdir)
+ source_bundles = [
+ d
+ for d in os.listdir(sourcedir)
+ if os.path.isdir(os.path.join(sourcedir, d))
+ and d not in ("__pycache__", ".git")
+ ]
+
+ print("GHPython componentizer")
+ print("======================")
+
+ print("[x] Source: {} ({} components)".format(sourcedir, len(source_bundles)))
+ print("[ ] Target: {}\r".format(targetdir), end="")
+ if not os.path.exists(targetdir):
+ os.mkdir(targetdir)
+ print("[x]")
+
+ if not gh_io:
+ print("[-] Cannot find GH_IO Assembly! Aborting.")
+ sys.exit(-1)
+
+ clr.AddReference(os.path.splitext(gh_io)[0])
+
+ print("[x] GH_IO assembly: {}".format(gh_io))
+
+ print("Processing component bundles:")
+ for d in source_bundles:
+ source = os.path.join(sourcedir, d)
+ target = os.path.join(targetdir, d + ".ghuser")
+ print(" [ ] {}\r".format(d), end="")
+ create_ghuser_component(source, target, args.version, args.prefix)
+ print(" [x] {} => {}".format(d, target))
\ No newline at end of file
diff --git a/componentize.py b/componentize_ipy.py
similarity index 99%
rename from componentize.py
rename to componentize_ipy.py
index 53cf436..d737904 100644
--- a/componentize.py
+++ b/componentize_ipy.py
@@ -393,4 +393,4 @@ def create_ghuser_component(source, target, version=None, prefix=None):
target = os.path.join(targetdir, d + ".ghuser")
print(" [ ] {}\r".format(d), end="")
create_ghuser_component(source, target, args.version, args.prefix)
- print(" [x] {} => {}".format(d, target))
+ print(" [x] {} => {}".format(d, target))
\ No newline at end of file
diff --git a/examples/cpy/Test_KitchenSink/code.py b/examples/cpy/Test_KitchenSink/code.py
new file mode 100644
index 0000000..14465a4
--- /dev/null
+++ b/examples/cpy/Test_KitchenSink/code.py
@@ -0,0 +1,26 @@
+"""
+Do something silly in python3.
+
+This component does nothing useful, it's only a kitchen sink (but in python3) example showing most available options.
+
+ Args:
+ x: X value
+ y: Y value
+ z: Z value
+ Returns:
+ result: The sum of all three values.
+"""
+from ghpythonlib.componentbase import executingcomponent as component
+
+import System
+import platform
+import Rhino
+import Grasshopper
+import rhinoscriptsyntax as rs
+
+
+class MyComponent(component):
+ def RunScript(self, x: float, y: float, z: float):
+ ghenv.Component.Message = 'COMPONENT v{{version}}'
+ result = x + y + z
+ return result
diff --git a/examples/Test_KitchenSink/icon.png b/examples/cpy/Test_KitchenSink/icon.png
similarity index 100%
rename from examples/Test_KitchenSink/icon.png
rename to examples/cpy/Test_KitchenSink/icon.png
diff --git a/examples/cpy/Test_KitchenSink/metadata.json b/examples/cpy/Test_KitchenSink/metadata.json
new file mode 100644
index 0000000..3cfdda9
--- /dev/null
+++ b/examples/cpy/Test_KitchenSink/metadata.json
@@ -0,0 +1,65 @@
+{
+ "name": "Kitchen sink component example",
+ "nickname": "Everything",
+ "category": "Componentizer",
+ "subcategory": "ALL",
+ "description": "This is an example with the everything and the kitchen sink in it, just to show all available options.",
+ "exposure": 4,
+ "instanceGuid": "cdd47086-f902-4b77-825b-6b79c3aaecc1",
+ "ghpython": {
+ "marshalGuids": true,
+ "iconDisplay": 2,
+ "inputParameters": [
+ {
+ "name": "x",
+ "nickname": "x",
+ "description": "The X value of the component.",
+ "optional": true,
+ "allowTreeAccess": true,
+ "showTypeHints": true,
+ "scriptParamAccess": "item",
+ "wireDisplay": "faint",
+ "sourceCount": 0,
+ "typeHintID": "float",
+ "reverse": true,
+ "simplify": false
+ },
+ {
+ "name": "x",
+ "nickname": "y",
+ "description": "The Y value of the component.",
+ "optional": true,
+ "allowTreeAccess": true,
+ "showTypeHints": true,
+ "scriptParamAccess": "item",
+ "wireDisplay": "default",
+ "sourceCount": 0,
+ "typeHintID": "float",
+ "simplify": true
+ },
+ {
+ "name": "z",
+ "nickname": "z",
+ "description": "The Z value of the component.",
+ "optional": true,
+ "allowTreeAccess": true,
+ "showTypeHints": true,
+ "scriptParamAccess": "item",
+ "wireDisplay": "faint",
+ "sourceCount": 0,
+ "typeHintID": "float",
+ "flatten": true
+ }
+ ],
+ "outputParameters": [
+ {
+ "name": "result",
+ "nickname": "result",
+ "description": "Result of the computation",
+ "optional": false,
+ "sourceCount": 0,
+ "graft": true
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/examples/cpy/Test_Minimal/code.py b/examples/cpy/Test_Minimal/code.py
new file mode 100644
index 0000000..098c18d
--- /dev/null
+++ b/examples/cpy/Test_Minimal/code.py
@@ -0,0 +1,17 @@
+"""
+Do something silly in python3.
+
+This component does nothing useful, it's only a minimal example of python3 in grasshopper.
+
+ Args:
+ x: X value
+ y: Y value
+ z: Z value
+ Returns:
+ a: The sum of all three values.
+"""
+import platform
+
+ghenv.Component.Message = 'COMPONENT v{{version}}'
+
+a = x + y + z
diff --git a/examples/Test_Minimal/icon.png b/examples/cpy/Test_Minimal/icon.png
similarity index 100%
rename from examples/Test_Minimal/icon.png
rename to examples/cpy/Test_Minimal/icon.png
diff --git a/examples/Test_Minimal/metadata.json b/examples/cpy/Test_Minimal/metadata.json
similarity index 100%
rename from examples/Test_Minimal/metadata.json
rename to examples/cpy/Test_Minimal/metadata.json
diff --git a/examples/Test_KitchenSink/code.py b/examples/ipy/Test_KitchenSink/code.py
similarity index 100%
rename from examples/Test_KitchenSink/code.py
rename to examples/ipy/Test_KitchenSink/code.py
diff --git a/examples/Test_MinimalSDK/icon.png b/examples/ipy/Test_KitchenSink/icon.png
similarity index 100%
rename from examples/Test_MinimalSDK/icon.png
rename to examples/ipy/Test_KitchenSink/icon.png
diff --git a/examples/Test_KitchenSink/metadata.json b/examples/ipy/Test_KitchenSink/metadata.json
similarity index 100%
rename from examples/Test_KitchenSink/metadata.json
rename to examples/ipy/Test_KitchenSink/metadata.json
diff --git a/examples/Test_Minimal/code.py b/examples/ipy/Test_Minimal/code.py
similarity index 100%
rename from examples/Test_Minimal/code.py
rename to examples/ipy/Test_Minimal/code.py
diff --git a/examples/ipy/Test_Minimal/icon.png b/examples/ipy/Test_Minimal/icon.png
new file mode 100644
index 0000000..c8df236
Binary files /dev/null and b/examples/ipy/Test_Minimal/icon.png differ
diff --git a/examples/ipy/Test_Minimal/metadata.json b/examples/ipy/Test_Minimal/metadata.json
new file mode 100644
index 0000000..d4c5a89
--- /dev/null
+++ b/examples/ipy/Test_Minimal/metadata.json
@@ -0,0 +1,26 @@
+{
+ "name": "Minimal component example",
+ "nickname": "Minimal",
+ "description": "This is an example with the minimum of options set, relying on defaults for everything else",
+ "category": "Componentizer",
+ "subcategory": "MAIN",
+
+ "ghpython": {
+ "inputParameters": [
+ {
+ "name": "x"
+ },
+ {
+ "name": "y"
+ },
+ {
+ "name": "z"
+ }
+ ],
+ "outputParameters": [
+ {
+ "name": "a"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/examples/Test_MinimalSDK/code.py b/examples/ipy/Test_MinimalSDK/code.py
similarity index 100%
rename from examples/Test_MinimalSDK/code.py
rename to examples/ipy/Test_MinimalSDK/code.py
diff --git a/examples/ipy/Test_MinimalSDK/icon.png b/examples/ipy/Test_MinimalSDK/icon.png
new file mode 100644
index 0000000..c8df236
Binary files /dev/null and b/examples/ipy/Test_MinimalSDK/icon.png differ
diff --git a/examples/Test_MinimalSDK/metadata.json b/examples/ipy/Test_MinimalSDK/metadata.json
similarity index 100%
rename from examples/Test_MinimalSDK/metadata.json
rename to examples/ipy/Test_MinimalSDK/metadata.json