Skip to content

Commit a919b4e

Browse files
committed
duplicate existing ipy script
1 parent 5a57499 commit a919b4e

File tree

1 file changed

+396
-0
lines changed

1 file changed

+396
-0
lines changed

componentize_ipy_v2.py

Lines changed: 396 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,396 @@
1+
from __future__ import print_function
2+
3+
import argparse
4+
import base64
5+
import json
6+
import os
7+
import re
8+
import sys
9+
import tempfile
10+
import urllib
11+
import zipfile
12+
import StringIO
13+
14+
import clr
15+
import System
16+
import System.IO
17+
18+
GHPYTHON_SCRIPT_GUID = System.Guid("410755b1-224a-4c1e-a407-bf32fb45ea7e")
19+
TEMPLATE_VER = re.compile("{{version}}")
20+
TEMPLATE_NAME = re.compile("{{name}}")
21+
TEMPLATE_GHUSER_NAME = re.compile("{{ghuser_name}}")
22+
23+
TYPES_MAP = dict(
24+
none="35915213-5534-4277-81b8-1bdc9e7383d2",
25+
ghdoc="87f87f55-5b71-41f4-8aea-21d494016f81",
26+
float="39fbc626-7a01-46ab-a18e-ec1c0c41685b",
27+
bool="d60527f5-b5af-4ef6-8970-5f96fe412559",
28+
int="48d01794-d3d8-4aef-990e-127168822244",
29+
complex="309690df-6229-4774-91bb-b1c9c0bfa54d",
30+
str="37261734-eec7-4f50-b6a8-b8d1f3c4396b",
31+
datetime="09bcf900-fe83-4efa-8d32-33d89f7a3e66",
32+
guid="5325b8e1-51d7-4d36-837a-d98394626c35",
33+
color="24b1d1a3-ab79-498c-9e44-c5b14607c4d3",
34+
point="e1937b56-b1da-4c12-8bd8-e34ee81746ef",
35+
vector="15a50725-e3d3-4075-9f7c-142ba5f40747",
36+
plane="3897522d-58e9-4d60-b38c-978ddacfedd8",
37+
interval="589748aa-e558-4dd9-976f-78e3ab91fc77",
38+
uvinterval="74c906f3-db02-4cea-bd58-de375cb5ae73",
39+
box="f29cb021-de79-4e63-9f04-fc8e0df5f8b6",
40+
transform="c4b38e4c-21ff-415f-a0d1-406d282428dd",
41+
line="f802a8cd-e699-4a94-97ea-83b5406271de",
42+
circle="3c5409a1-3293-4181-a6fa-c24c37fc0c32",
43+
arc="9c80ec18-b48c-41b0-bc6e-cd93d9c916aa",
44+
polyline="66fa617b-e3e8-4480-9f1e-2c0688c1d21b",
45+
rectangle="83da014b-a550-4bf5-89ff-16e54225bd5d",
46+
curve="9ba89ec2-5315-435f-a621-b66c5fa2f301",
47+
mesh="794a1f9d-21d5-4379-b987-9e8bbf433912",
48+
surface="f4070a37-c822-410f-9057-100d2e22a22d",
49+
subd="20f4ca9c-6c90-4fd6-ba8a-5bf9ca79db08",
50+
brep="2ceb0405-fdfe-403d-a4d6-8786da45fb9d",
51+
geometrybase="c37956f4-d39c-49c7-af71-1e87f8031b26",
52+
)
53+
54+
EXPOSURE = dict(valid=set([-1, 2, 4, 8, 16, 32, 64, 128]), default=2)
55+
ACCESS = dict(valid=set([0, 1, 2]), map=dict(item=0, list=1, tree=2), default=0)
56+
PARAM_TYPE = dict(
57+
valid=set(TYPES_MAP.values()), map=TYPES_MAP, default=TYPES_MAP["ghdoc"]
58+
)
59+
WIRE_DISPLAY = dict(
60+
valid=set([0, 1, 2]), map=dict(default=0, faint=1, hidden=2), default=0
61+
)
62+
63+
64+
def fetch_ghio_lib(target_folder="temp"):
65+
"""Fetch the GH_IO.dll library from the NuGet packaging system."""
66+
ghio_dll = "GH_IO.dll"
67+
filename = "lib/net48/" + ghio_dll
68+
69+
response = urllib.urlopen("https://www.nuget.org/api/v2/package/Grasshopper/")
70+
dst_file = os.path.join(target_folder, ghio_dll)
71+
zip_file = zipfile.ZipFile(StringIO.StringIO(response.read()))
72+
73+
with zip_file.open(filename, "r") as zipped_dll:
74+
with open(dst_file, "wb") as fp:
75+
fp.write(zipped_dll.read())
76+
77+
return dst_file
78+
79+
80+
def find_ghio_assembly(libdir):
81+
for root, _dirs, files in os.walk(libdir):
82+
for basename in files:
83+
if basename.upper() == "GH_IO.DLL":
84+
filename = os.path.join(root, basename)
85+
return filename
86+
87+
88+
def bitmap_from_image_path(image_path):
89+
with open(image_path, "rb") as imageFile:
90+
img_string = base64.b64encode(imageFile.read())
91+
return System.Convert.FromBase64String(img_string)
92+
93+
94+
def validate_source_bundle(source):
95+
icon = os.path.join(source, "icon.png")
96+
code = os.path.join(source, "code.py")
97+
data = os.path.join(source, "metadata.json")
98+
99+
if not os.path.exists(icon):
100+
raise ValueError(
101+
"icon missing, make sure icon.png is present in the source bundle: {}".format(
102+
source
103+
)
104+
)
105+
if not os.path.exists(code):
106+
raise ValueError(
107+
"code missing, make sure code.py is present in the source bundle: {}".format(
108+
source
109+
)
110+
)
111+
if not os.path.exists(data):
112+
raise ValueError(
113+
"metadata missing, make sure metadata.json is present in the source bundle: {}".format(
114+
source
115+
)
116+
)
117+
118+
icon = bitmap_from_image_path(icon)
119+
120+
with open(code, "r") as f:
121+
python_code = f.read()
122+
123+
with open(data, "r") as f:
124+
data = json.load(f)
125+
126+
if "exposure" not in data:
127+
data["exposure"] = EXPOSURE["default"]
128+
129+
if data["exposure"] not in EXPOSURE["valid"]:
130+
raise ValueError(
131+
"Invalid exposure value. Accepted values are {}".format(
132+
sorted(EXPOSURE["valid"])
133+
)
134+
)
135+
136+
ghpython = data.get("ghpython")
137+
sdk_mode = ghpython and ghpython.get("isAdvancedMode", False)
138+
139+
if r'"""' not in python_code and sdk_mode is False:
140+
python_code = r'"""{}"""{}{}'.format(
141+
data.get("description", "Generated by Componentizer"),
142+
os.linesep,
143+
python_code,
144+
)
145+
146+
return icon, python_code, data
147+
148+
149+
def parse_param_access(access):
150+
try:
151+
access = int(access)
152+
except ValueError:
153+
# Maybe string?
154+
access = ACCESS["map"].get(access)
155+
156+
if access not in ACCESS["valid"]:
157+
raise ValueError(
158+
"Invalid param access value. Valid values are {}".format(
159+
sorted(ACCESS["valid"])
160+
)
161+
)
162+
163+
return access
164+
165+
166+
def parse_wire_display(wire_display):
167+
try:
168+
wire_display = int(wire_display)
169+
except ValueError:
170+
wire_display = WIRE_DISPLAY["map"].get(wire_display)
171+
172+
if wire_display not in WIRE_DISPLAY["valid"]:
173+
raise ValueError(
174+
"Invalid wire display value. Valid values are {}".format(
175+
sorted(WIRE_DISPLAY["valid"])
176+
)
177+
)
178+
179+
return wire_display
180+
181+
182+
def parse_param_type_hint(type_hint_id):
183+
type_hint_id = type_hint_id or PARAM_TYPE["default"]
184+
185+
if type_hint_id in TYPES_MAP:
186+
type_hint_id = TYPES_MAP[type_hint_id]
187+
188+
if type_hint_id not in PARAM_TYPE["valid"]:
189+
raise ValueError(
190+
'Invalid param type hint ID ("{}"). Valid values are {}'.format(
191+
type_hint_id, sorted(PARAM_TYPE["valid"])
192+
)
193+
)
194+
195+
try:
196+
type_hint_id = System.Guid.Parse(type_hint_id)
197+
except SystemError:
198+
raise ValueError("Unable to parse type hint ID: {}".format(type_hint_id))
199+
200+
return type_hint_id
201+
202+
203+
def replace_templates(code, version, name, ghuser_name):
204+
if version:
205+
code = TEMPLATE_VER.sub(version, code)
206+
207+
code = TEMPLATE_NAME.sub(name, code)
208+
code = TEMPLATE_GHUSER_NAME.sub(ghuser_name, code)
209+
210+
return code
211+
212+
213+
def create_ghuser_component(source, target, version=None, prefix=None):
214+
from GH_IO.Serialization import GH_LooseChunk
215+
216+
icon, code, data = validate_source_bundle(source)
217+
218+
code = replace_templates(code, version, data["name"], os.path.basename(target))
219+
220+
instance_guid = data.get("instanceGuid")
221+
if not instance_guid:
222+
instance_guid = System.Guid.NewGuid()
223+
else:
224+
instance_guid = System.Guid.Parse(instance_guid)
225+
226+
prefix = prefix or ""
227+
228+
root = GH_LooseChunk("UserObject")
229+
230+
root.SetGuid("BaseID", GHPYTHON_SCRIPT_GUID)
231+
root.SetString("Name", prefix + data["name"])
232+
root.SetString("NickName", data["nickname"])
233+
root.SetString("Description", data.get("description", ""))
234+
root.SetInt32("Exposure", data.get("exposure", EXPOSURE["default"]))
235+
root.SetString("Category", data["category"])
236+
root.SetString("SubCategory", data["subcategory"])
237+
root.SetGuid("InstanceGuid", instance_guid)
238+
root.SetByteArray("Icon", icon)
239+
240+
ghpython_data = data["ghpython"]
241+
ghpython_root = GH_LooseChunk("UserObject")
242+
ghpython_root.SetString("Description", data.get("description", ""))
243+
ghpython_root.SetBoolean("HideOutput", ghpython_data.get("hideOutput", True))
244+
ghpython_root.SetBoolean("HideInput", ghpython_data.get("hideInput", True))
245+
ghpython_root.SetBoolean(
246+
"IsAdvancedMode", ghpython_data.get("isAdvancedMode", False)
247+
)
248+
ghpython_root.SetInt32("IconDisplay", ghpython_data.get("iconDisplay", 0))
249+
ghpython_root.SetString("Name", data["name"])
250+
ghpython_root.SetString("NickName", data["nickname"])
251+
ghpython_root.SetBoolean(
252+
"MarshalOutGuids", ghpython_data.get("marshalOutGuids", True)
253+
)
254+
ghpython_root.SetString("CodeInput", code)
255+
256+
# ghpython_root.CreateChunk('Attributes')
257+
# for mf in ('Bounds', 'Pivot', 'Selected'):
258+
params = ghpython_root.CreateChunk("ParameterData")
259+
inputParam = ghpython_data.get("inputParameters", [])
260+
outputParam = ghpython_data.get("outputParameters", [])
261+
262+
params.SetInt32("InputCount", len(inputParam))
263+
for i, _pi in enumerate(inputParam):
264+
params.SetGuid(
265+
"InputId", i, System.Guid.Parse("84fa917c-1ed8-4db3-8be1-7bdc4a6495a2")
266+
)
267+
params.SetInt32("OutputCount", len(outputParam))
268+
for i, _po in enumerate(outputParam):
269+
params.SetGuid(
270+
"OutputId", i, System.Guid.Parse("8ec86459-bf01-4409-baee-174d0d2b13d0")
271+
)
272+
273+
for i, pi in enumerate(inputParam):
274+
input_instance_guid = System.Guid.NewGuid()
275+
pi_chunk = params.CreateChunk("InputParam", i)
276+
pi_chunk.SetString("Name", pi["name"])
277+
pi_chunk.SetString("NickName", pi.get("nickname") or pi["name"])
278+
pi_chunk.SetString("Description", pi.get("description"))
279+
pi_chunk.SetBoolean("Optional", pi.get("optional", True))
280+
pi_chunk.SetBoolean("AllowTreeAccess", pi.get("allowTreeAccess", True))
281+
pi_chunk.SetBoolean("ShowTypeHints", pi.get("showTypeHints", True))
282+
pi_chunk.SetInt32(
283+
"ScriptParamAccess",
284+
parse_param_access(pi.get("scriptParamAccess", ACCESS["default"])),
285+
)
286+
pi_chunk.SetInt32("SourceCount", 0)
287+
pi_chunk.SetGuid("InstanceGuid", input_instance_guid)
288+
pi_chunk.SetGuid("TypeHintID", parse_param_type_hint(pi.get("typeHintID")))
289+
pi_chunk.SetInt32(
290+
"WireDisplay",
291+
parse_wire_display(pi.get("wireDisplay", WIRE_DISPLAY["default"])),
292+
)
293+
pi_chunk.SetBoolean("ReverseData", pi.get("reverse", False))
294+
pi_chunk.SetBoolean("SimplifyData", pi.get("simplify", False))
295+
# Mutually exclusive options
296+
if pi.get("flatten", False):
297+
pi_chunk.SetInt32("Mapping", 1)
298+
elif pi.get("graft", False):
299+
pi_chunk.SetInt32("Mapping", 2)
300+
301+
for i, po in enumerate(outputParam):
302+
output_instance_guid = System.Guid.NewGuid()
303+
po_chunk = params.CreateChunk("OutputParam", i)
304+
po_chunk.SetString("Name", po["name"])
305+
po_chunk.SetString("NickName", po.get("nickname") or po["name"])
306+
po_chunk.SetString("Description", po.get("description"))
307+
po_chunk.SetBoolean("Optional", po.get("optional", False))
308+
po_chunk.SetInt32("SourceCount", 0)
309+
po_chunk.SetGuid("InstanceGuid", output_instance_guid)
310+
po_chunk.SetBoolean("ReverseData", po.get("reverse", False))
311+
po_chunk.SetBoolean("SimplifyData", po.get("simplify", False))
312+
# Mutually exclusive options
313+
if po.get("flatten", False):
314+
po_chunk.SetInt32("Mapping", 1)
315+
elif po.get("graft", False):
316+
po_chunk.SetInt32("Mapping", 2)
317+
318+
# print(ghpython_root.Serialize_Xml())
319+
root.SetByteArray("Object", ghpython_root.Serialize_Binary())
320+
321+
System.IO.File.WriteAllBytes(target, root.Serialize_Binary())
322+
323+
324+
if __name__ == "__main__":
325+
parser = argparse.ArgumentParser(
326+
description="Create GHUser components out of python code."
327+
)
328+
parser.add_argument(
329+
"source",
330+
type=str,
331+
help="Source directory where code for all components is stored",
332+
)
333+
parser.add_argument("target", type=str, help="Target directory for ghuser files")
334+
parser.add_argument(
335+
"--ghio",
336+
type=str,
337+
required=False,
338+
help="Folder where the GH_IO.dll assembly is located. Defaults to ./lib",
339+
)
340+
parser.add_argument(
341+
"--version", type=str, required=False, help="Version to tag components"
342+
)
343+
parser.add_argument(
344+
"--prefix",
345+
type=str,
346+
required=False,
347+
help="Add this prefix to the name of each generated component",
348+
)
349+
args = parser.parse_args()
350+
351+
sourcedir = args.source
352+
if not os.path.isabs(sourcedir):
353+
sourcedir = os.path.abspath(sourcedir)
354+
355+
targetdir = args.target
356+
if not os.path.isabs(targetdir):
357+
targetdir = os.path.abspath(targetdir)
358+
359+
if args.ghio is None:
360+
libdir = tempfile.mkdtemp("ghio")
361+
fetch_ghio_lib(libdir)
362+
else:
363+
libdir = os.path.abspath(args.ghio)
364+
gh_io = find_ghio_assembly(libdir)
365+
source_bundles = [
366+
d
367+
for d in os.listdir(sourcedir)
368+
if os.path.isdir(os.path.join(sourcedir, d))
369+
and d not in ("__pycache__", ".git")
370+
]
371+
372+
print("GHPython componentizer")
373+
print("======================")
374+
375+
print("[x] Source: {} ({} components)".format(sourcedir, len(source_bundles)))
376+
print("[ ] Target: {}\r".format(targetdir), end="")
377+
if not os.path.exists(targetdir):
378+
os.mkdir(targetdir)
379+
print("[x]")
380+
381+
print("[ ] GH_IO assembly: {}\r".format(gh_io or args.ghio), end="")
382+
if not gh_io:
383+
print("[-]")
384+
print(" Cannot find GH_IO Assembly! Aborting.")
385+
sys.exit(-1)
386+
clr.AddReferenceToFileAndPath(gh_io)
387+
print("[x]")
388+
print()
389+
390+
print("Processing component bundles:")
391+
for d in source_bundles:
392+
source = os.path.join(sourcedir, d)
393+
target = os.path.join(targetdir, d + ".ghuser")
394+
print(" [ ] {}\r".format(d), end="")
395+
create_ghuser_component(source, target, args.version, args.prefix)
396+
print(" [x] {} => {}".format(d, target))

0 commit comments

Comments
 (0)