Skip to content

Commit 67244d8

Browse files
committed
Include GH user objects in a separate module for cleaner dependencies
1 parent 1ebe485 commit 67244d8

File tree

8 files changed

+184
-21
lines changed

8 files changed

+184
-21
lines changed

CHANGELOG.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22
Changelog
33
*********
44

5+
1.3.1 (2020-04-01)
6+
------------------
7+
8+
Changes
9+
^^^^^^^
10+
- Allow access to GH user objects from CPython. Access them from a PythonToGrasshopperRemote object as `py2gh.gh_remote_userobjects.<UO Name>`
11+
- Remove GhCompService.get_component. Access remote GH components from a PythonToGrasshopperRemote object as `py2gh.gh_remote_components.<Component Name>`
12+
13+
Fix
14+
^^^
15+
- Fix CPython -> Rhino Python connections for Rhino 6.
16+
517
1.3.0 (2020-03-20)
618
------------------
719

ghpythonremote/connectors.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,8 @@ def __init__(
263263
self.port = port
264264
self.rhino_popen = self._launch_rhino()
265265
self.connection = self._get_connection()
266-
self.gh_remote_components = self.connection.root.get_component
267-
# TODO: improve ghcomp to get clusters the same way we get compiled components,
268-
# thus removing the need for a custom getter
266+
self.gh_remote_components = self.connection.root.ghcomp
267+
self.gh_remote_userobjects = self.connection.root.ghuo
269268

270269
def __enter__(self):
271270
return self

ghpythonremote/examples/CPython_to_GH.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@
1717
rhino_file_path, rpyc_server_py, rhino_ver=6, timeout=60
1818
) as py2gh:
1919
# Stuff that we can reach
20-
rghcomp = (
21-
py2gh.gh_remote_components
22-
) # A getter function for a named Grasshopper compiled component or cluster
20+
rghcomp = py2gh.gh_remote_components # Named Grasshopper compiled components
21+
rghuo = py2gh.gh_remote_userobjects # Named Grasshopper user objects
2322
rgh = py2gh.connection # Represents the remote instance root object
2423
Rhino = (
2524
rgh.modules.Rhino
@@ -40,5 +39,7 @@
4039
c.Id for c in curves
4140
) # rhinoscriptsyntax doesn't like mutable objects through the connection
4241
gh_curves = rs.coerceguidlist(curves_id)
43-
area = rghcomp("Area", is_cluster_component=False)
44-
print(sum(area(gh_curves)[0]))
42+
# Call a GH component
43+
print(sum(rghcomp.Area(gh_curves)[0]))
44+
# Call a GH user object, previously created with the name "TestClusterGHPR"
45+
print(rghuo.TestClusterGHPR(3, y=4))

ghpythonremote/ghcompservice.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,14 @@ def on_connect(self, conn):
77
print("Incoming connection.")
88
super(GhcompService, self).on_connect(conn)
99
import ghpythonlib.components as ghcomp
10+
from ghpythonremote import ghuserobjects as ghuo
1011

1112
self.ghcomp = ghcomp
13+
self.ghuo = ghuo
1214

1315
def on_disconnect(self, conn):
1416
print("Disconnected.")
1517

16-
def get_component(self, component_name, is_cluster_component=False):
17-
component = getattr(self.ghcomp, component_name)
18-
if is_cluster_component:
19-
component = getattr(
20-
component, component_name
21-
)
22-
# TODO: improve ghcomp to get clusters the same way we get compiled
23-
# components, thus removing the need for a custom getter
24-
return component
25-
2618

2719
if __name__ == "__main__":
2820
import rhinoscriptsyntax as rs

ghpythonremote/ghuserobjects.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import sys
2+
3+
if sys.platform != "cli":
4+
raise(RuntimeError, "This module is only intended to be run in Rhino Python")
5+
6+
import clr
7+
8+
clr.AddReference('Grasshopper, Culture=neutral, PublicKeyToken=dda4f5ec2cd80803')
9+
10+
import System
11+
import Rhino
12+
import Grasshopper as gh
13+
import sys
14+
import re
15+
16+
from System.Collections import IEnumerable, IEnumerator
17+
18+
19+
class namespace_object(object):
20+
pass
21+
22+
23+
def __make_function_uo__(helper):
24+
def component_function(*args, **kwargs):
25+
comp = helper.proxy.CreateInstance()
26+
comp.ClearData()
27+
if args:
28+
for i, arg in enumerate(args):
29+
if arg is None: continue
30+
param = comp.Params.Input[i]
31+
param.PersistentData.Clear()
32+
if hasattr(arg, '__iter__'): # TODO deal with polyline, str
33+
[param.AddPersistentData(a) for a in arg]
34+
else:
35+
param.AddPersistentData(arg)
36+
if kwargs:
37+
for param in comp.Params.Input:
38+
name = param.Name.lower()
39+
if name in kwargs:
40+
param.PersistentData.Clear()
41+
arg = kwargs[name]
42+
if hasattr(arg, '__iter__'): # TODO deal with polyline, str
43+
[param.AddPersistentData(a) for a in arg]
44+
else:
45+
param.AddPersistentData(arg)
46+
doc = gh.Kernel.GH_Document()
47+
doc.AddObject(comp, False, 0)
48+
comp.CollectData()
49+
comp.ComputeData()
50+
output = helper.create_output(comp.Params)
51+
comp.ClearData()
52+
doc.Dispose()
53+
return output
54+
55+
return component_function
56+
57+
58+
class function_helper(object):
59+
def __init__(self, proxy, name):
60+
self.proxy = proxy
61+
self.return_type = None
62+
63+
def create_output(self, params, output_values=None):
64+
if not output_values:
65+
output_values = []
66+
for output in params.Output:
67+
data = output.VolatileData.AllData(True)
68+
# We could call Value, but ScriptVariable seems to do a better job
69+
v = [x.ScriptVariable() for x in data]
70+
if len(v) < 1:
71+
output_values.append(None)
72+
elif len(v) == 1:
73+
output_values.append(v[0])
74+
else:
75+
output_values.append(v)
76+
if len(output_values) == 1: return output_values[0]
77+
if self.return_type is None:
78+
names = [output.Name.lower() for output in params.Output]
79+
try:
80+
self.return_type = namedtuple('Output', names, rename=True)
81+
except:
82+
self.return_type = False
83+
if not self.return_type: return output_values
84+
return self.return_type(*output_values)
85+
86+
def runfast(self, args, kwargs):
87+
return False, None
88+
89+
90+
def __build_module_uo():
91+
core_module = sys.modules[__name__]
92+
translate_from = u"|+-*\u2070\u00B9\u00B2\u00B3\u2074\u2075\u2076\u2077\u2078\u2079"
93+
translate_to = "X__x0123456789"
94+
transl = dict(zip(translate_from, translate_to))
95+
96+
def regex_helper(match):
97+
if match.group() in transl:
98+
return transl[match.group()]
99+
return ''
100+
101+
def function_description(description, params):
102+
rc = ['', description, "Input:"]
103+
for param in params.Input:
104+
s = "\t{0} [{1}] - {2}"
105+
if param.Optional:
106+
s = "\t{0} (in, optional) [{1}] - {2}"
107+
rc.append(s.format(param.Name.lower(), param.TypeName, param.Description))
108+
if params.Output.Count == 1:
109+
param = params.Output[0]
110+
rc.append("Returns: [{0}] - {1}".format(param.TypeName, param.Description))
111+
elif params.Output.Count > 1:
112+
rc.append("Returns:")
113+
for out in params.Output:
114+
s = "\t{0} [{1}] - {2}"
115+
rc.append(s.format(out.Name.lower(), out.TypeName, out.Description))
116+
return '\n'.join(rc)
117+
118+
for obj in gh.Instances.ComponentServer.ObjectProxies:
119+
if obj.Exposure == gh.Kernel.GH_Exposure.hidden or obj.Obsolete:
120+
continue
121+
122+
t = clr.GetClrType(gh.Kernel.IGH_Component)
123+
library_id = obj.LibraryGuid
124+
assembly = gh.Instances.ComponentServer.FindAssembly(library_id)
125+
name = obj.Desc.Name
126+
127+
if "LEGACY" in name or "#" in name:
128+
continue
129+
name = re.sub("[^a-zA-Z0-9]", regex_helper, name)
130+
if not name[0].isalpha():
131+
name = 'x' + name
132+
133+
m = core_module
134+
if assembly is None:
135+
# UserObjects
136+
pass
137+
elif not (t.IsAssignableFrom(obj.Type)):
138+
# Specialized inputs/outputs and misconfigured components
139+
# Discard
140+
continue
141+
elif not assembly.IsCoreLibrary:
142+
# Compiled components, leave them to ghpythonlib
143+
continue
144+
145+
function = __make_function_uo__(function_helper(obj, name))
146+
try:
147+
setattr(m, name, function)
148+
a = m.__dict__[name]
149+
a.__name__ = name
150+
comp = obj.CreateInstance()
151+
a.__doc__ = function_description(obj.Desc.Description, comp.Params)
152+
except Exception as err:
153+
Rhino.RhinoApp.WriteLine(str(err))
154+
Rhino.Runtime.HostUtils.ExceptionReport("ghpythonlib.components.py|" + name,
155+
err.clsException)
156+
157+
158+
__build_module_uo()

ghpythonremote/monkey.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import inspect
12
import sys
23

34
import rpyc
@@ -53,7 +54,7 @@ def get_id_pack(obj):
5354
obj.__class__.__module__, obj.__name__
5455
)
5556
elif inspect.ismodule(obj):
56-
name_pack = "{0}.{1}".format(obj__module__, obj.__name__)
57+
name_pack = "{0}.{1}".format(obj.__module__, obj.__name__)
5758
print(name_pack)
5859
elif hasattr(obj, "__module__"):
5960
name_pack = "{0}.{1}".format(obj.__module__, obj.__name__)
@@ -92,7 +93,7 @@ def _handle_inspect(self, id_pack): # request handler
9293
)
9394
)
9495

95-
# TODO: Remove that when ghpythonlib.componentns.__namedtuple.__getattr__ is fixed
96+
# TODO: Remove that when ghpythonlib.components.__namedtuple.__getattr__ is fixed
9697
rpyc.core.protocol.Connection._handle_inspect = _handle_inspect
9798

9899
if sys.version_info < (2, 7, 5):

ghpythonremote/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "1.3.0"
1+
__version__ = "1.3.1"
22
__version_info__ = tuple(
33
[
44
int(num) if num.isdigit() else num

ghpythonremote_README.pdf

1.7 KB
Binary file not shown.

0 commit comments

Comments
 (0)