-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathcore_funcs.py
More file actions
469 lines (387 loc) · 19 KB
/
core_funcs.py
File metadata and controls
469 lines (387 loc) · 19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
from signal import raise_signal
from js import sphere as js_sphere, box as js_box, shapes, paths, vec as js_vec, rate
from js import cylinder as js_cylinder, arrow as js_arrow, cone as js_cone, helix as js_helix
from js import label as js_label, scene as js_scene, textures
from js import pyramid as js_pyramid, ring as js_ring, text as js_text
from js import button as js_button, distant_light as js_distant_light, local_light as js_local_light
from js import slider as js_slider, wtext as js_wtext, radio as js_radio, checkbox as js_checkbox
from js import menu as js_menu, curve as js_curve, Object, get_library as js_get_library
from js import points as js_points, extrusion as js_extrusion, simple_sphere as js_simple_sphere
from js import window as js_window, fontloading as js_fontloading, waitforfonts as js_waitforfonts
from js import quad as js_quad, vertex as js_vertex, triangle as js_triangle, ellipsoid as js_ellipsoid
from js import canvas as js_canvas, attach_light as js_attach_light, compound as js_compound
from js import graph as js_graph, gcurve as js_gcurve, gvbars as js_gvbars, gdots as js_gdots
from pyodide.ffi import create_proxy, to_js
from .curveArgs import getStdForm
from .vec_js import vector_js as vector
from .vec_conversion import js2py_vec, py2js_vec
from .shapes_piodide import convertPyVecsToJSList
class GSRegistry(object):
"""
Manage a registry of objects so we can look up the python object
from the corresponding javascript object.
"""
def __init__(self):
self.counter = 0
self.registry = {}
def register(self, obj):
result = self.counter
self.registry[self.counter] = obj
self.counter += 1
return result
def get(self, index):
result = self.registry.get(index,None)
if result is None:
raise Exception("GSRegistry: object with index %d not found" % index)
return result
gsRegistry = GSRegistry()
gsRegKey = '__gsIndex__'
def js_debug(*args, convert=True):
if convert:
args = to_js(args)
js_window.__reportScriptError(args)
async def get_library(url):
"""
Load a library from a url and then fetch varname from the window object. If you need to convert from native js objec to python, pass in a constructor function.
"""
obj = None # maybe return nothing
try:
await js_get_library(url)
except Exception as e:
raise Exception("Error loading library %s: %s" % (url, e))
def translate_kwargs_rest(kwargs, notAttrs):
# Handle everything else
for attr in kwargs:
if attr not in notAttrs:
kwargs[attr] = to_js(kwargs[attr], dict_converter=Object.fromEntries)
return kwargs
def translate_kwargs_nest(kwargs, nestAttrs):
"""
Handle nested structures of lists of vectors.
"""
for attr in nestAttrs:
if attr in kwargs:
kwargs[attr] = convertPyVecsToJSList(kwargs[attr])
return kwargs
def translate_kwargs_vecs(kwargs, vecAttrs):
# Translate the vecAttrs from kwargs to js vectors.
for attr in vecAttrs:
if attr in kwargs:
kwargs[attr] = py2js_vec(kwargs[attr])
return kwargs
def translate_kwargs_lists(kwargs, listAttrs):
for attr in listAttrs:
if attr in kwargs:
kwargs[attr] = to_js(kwargs[attr])
return kwargs
def translate_kwargs_funcs(kwargs, funcAttrs):
for attr in funcAttrs:
if attr in kwargs:
kwargs[attr] = create_proxy(kwargs[attr])
return kwargs
class glowProxy(object):
"""
A proxy for a glowscript library object. Most attributes are stored as js objects/attributes of
the mirrored javascript object handled/used by the glowscript library.
vecAttrs is a list of names of vector attributes.
listAttrs is a list of names of list attributes (e.g., lights, points, etc.)
funcAttrs is a list of names of function attributes (e.g., bind)
oType is the name of the JS object type (e.g., sphere...)
GP_keys are the names of attributes stored in self.__dict__, not proxied from jsObj.
"""
GP_defaults = {'vecAttrs':[], 'oType':None, 'jsObj':None, 'listAttrs':[], 'funcAttrs':[], 'nestAttrs':[]}
GP_keys = list(GP_defaults.keys())
def __init__(self, *args, factory=None, jsObj=None, **kwargs):
"""
factory is JS function that constructs the jsObj.
kwArgs are the keyword arguments for the factory.
args are the positional arguments for the factory.
"""
for attr in glowProxy.GP_keys:
self.__dict__[attr] = kwargs.get(attr, glowProxy.GP_defaults.get(attr, None)) # grab kwargs for GP_keys
kwargs = self.translate_all_kwargs(kwargs)
for attr in glowProxy.GP_keys:
if attr in kwargs:
del kwargs[attr] # don't pass these to js
if jsObj is not None:
self.jsObj = jsObj
if (not hasattr(jsObj, gsRegKey)):
setattr(jsObj, gsRegKey, gsRegistry.register(self))
elif factory is not None:
self.jsObj = factory(*args, **kwargs)
setattr(self.jsObj, gsRegKey, gsRegistry.register(self))
else:
raise Exception("glowProxy: must specify either factory or jsObj")
def translate_all_kwargs(self, kwargs):
"""
Convert kwargs to save js objects.
"""
kwargs = translate_kwargs_vecs(kwargs, self.vecAttrs)
kwargs = translate_kwargs_lists(kwargs, self.listAttrs)
kwargs = translate_kwargs_funcs(kwargs, self.funcAttrs)
kwargs = translate_kwargs_nest(kwargs, self.nestAttrs)
kwargs = translate_kwargs_rest(kwargs, self.vecAttrs + self.listAttrs + self.funcAttrs + self.nestAttrs)
return kwargs
def __setattr__(self, name, value):
if name in glowProxy.GP_keys:
self.__dict__[name] = value
elif name in self.vecAttrs:
setattr(self.jsObj, name, py2js_vec(value))
elif name in self.listAttrs:
setattr(self.jsObj, name, to_js(value))
else:
setattr(self.jsObj, name, to_js(value, dict_converter=Object.fromEntries))
def __getattr__(self, name):
if name in glowProxy.GP_keys:
return self.__dict__.get(name,
glowProxy.GP_defaults.get(name, None))
elif name in self.vecAttrs:
return js2py_vec(getattr(self.jsObj, name), jsObj=getattr(self.jsObj,name)) # keep jsObj embedded in pyObj
else:
result = getattr(self.jsObj, name)
if hasattr(result,gsRegKey):
return gsRegistry.get(getattr(result,gsRegKey))
return result
def __str__(self):
return "glowProxy: " + self.oType
def rotate(self, *args, **kwargs): # We may need to intercept certain functions and assignements to do js/py vec conversions
"""
Rotate the object.
"""
kwargs = translate_kwargs_vecs(kwargs=kwargs, vecAttrs=['axis','origin'])
self.jsObj.rotate(*args, **kwargs)
return self
def append(self, *args, **kwargs):
"""
Append to the object.
"""
kwargs = translate_kwargs_vecs(kwargs=kwargs, vecAttrs=['origin'])
self.jsObj.append(*args, **kwargs)
return self
def modify(self, *args, **kwargs):
"""
Modify the object.
"""
kwargs = self.translate_all_kwargs(kwargs)
self.jsObj.modify(*args, **kwargs)
return self
def clone(self, *args, **kwargs):
if 'pos' in kwargs:
kwargs['pos'] = py2js_vec(kwargs['pos'])
cjs = self.jsObj.clone(*args, **kwargs)
ret = type(self)(*args, **kwargs, jsObj=cjs)
return ret
class sphere(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, vecAttrs=['pos','color','size','trail_color'], oType='sphere', factory=js_sphere, *args, **kwargs)
class simple_sphere(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, vecAttrs=['pos','color','size','trail_color'], oType='simple_sphere', factory=js_simple_sphere, *args, **kwargs)
class box(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, vecAttrs=['pos','color','size','axis'], oType='box', factory=js_box, *args, **kwargs)
class cylinder(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, vecAttrs=['pos', 'axis', 'color','size'], oType='cylinder', factory=js_cylinder, *args, **kwargs)
class arrow(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, vecAttrs=['pos', 'axis', 'color'], oType='arrow', factory=js_arrow, *args, **kwargs)
class cone(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, vecAttrs=['pos', 'axis', 'color','size'], oType='cone', factory=js_cone, *args, **kwargs)
class helix(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, vecAttrs=['pos', 'axis', 'color','size'], oType='helix', factory=js_helix, *args, **kwargs)
class label(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, vecAttrs=['pos', 'color'], oType='label', factory=js_label, *args, **kwargs)
class ellipsoid(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, vecAttrs=['pos', 'color', 'axis', 'size'], oType='ellipsoid', factory=js_ellipsoid, *args, **kwargs)
class pyramid(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, vecAttrs=['pos', 'color', 'axis', 'size'], oType='pyramid', factory=js_pyramid, *args, **kwargs)
class ring(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, vecAttrs=['pos', 'color', 'axis', 'size'], oType='ring', factory=js_ring, *args, **kwargs)
class text(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, vecAttrs=['pos', 'color', 'axis'], oType='text', factory=js_text, *args, **kwargs)
class button(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, funcAttrs=['bind'], vecAttrs=['textcolor','background'], oType='button', factory=js_button, *args, **kwargs)
class slider(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, funcAttrs=['bind'], oType='slider', factory=js_slider, *args, **kwargs)
class radio(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, funcAttrs=['bind'], oType='radio', factory=js_radio, *args, **kwargs)
class checkbox(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, funcAttrs=['bind'], oType='checkbox', factory=js_checkbox, *args, **kwargs)
class menu(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, funcAttrs=['bind'], listAttrs=['choices'], oType='menu', factory=js_menu, *args, **kwargs)
class wtext(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, oType='wtext', factory=js_wtext, *args, **kwargs)
class distant_light(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, vecAttrs=['color','direction'], oType='distant_light', factory=js_distant_light, *args, **kwargs)
class local_light(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, vecAttrs=['color','pos'], oType='local_light', factory=js_local_light, *args, **kwargs)
class vertex(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, vecAttrs=['pos','color','normal','texpos'], oType='vertex', factory=js_vertex, *args, **kwargs)
class extrusion(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, vecAttrs=['pos', 'axis', 'up', 'color', 'start_face_color','end_face_color'], nestAttrs=['shape','path'], oType='extrusion', factory=js_extrusion, *args, **kwargs)
class graph(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, oType='graph', listAttrs=['data','pos'], factory=js_graph, *args, **kwargs)
class graphPlotter(glowProxy):
def __init__(self, *args, **kwargs):
glowProxy.__init__(self, *args, **kwargs)
def plot(self, *args, **kwargs):
kwargs = translate_kwargs_lists(kwargs, self.listAttrs)
self.jsObj.plot(*args, **kwargs)
class gcurve(graphPlotter):
def __init__(self, *args, **kwargs):
graphPlotter.__init__(self, oType='gcurve', vecAttrs=['color','marker_color','dot_color'], listAttrs=['data','pos'], factory=js_gcurve, *args, **kwargs)
class gvbars(graphPlotter):
def __init__(self, *args, **kwargs):
graphPlotter.__init__(self, oType='gvbars', vecAttrs=['color','marker_color'], listAttrs=['data','pos'], factory=js_gvbars, *args, **kwargs)
class ghbars(graphPlotter):
def __init__(self, *args, **kwargs):
graphPlotter.__init__(self, oType='gdots', vecAttrs=['color','marker_color'], listAttrs=['data','pos'],factory=js_gdots, *args, **kwargs)
class gdots(graphPlotter):
def __init__(self, *args, **kwargs):
graphPlotter.__init__(self, oType='gdots', vecAttrs=['color','marker_color'], listAttrs=['data','pos'],factory=js_gdots, *args, **kwargs)
class triangle(glowProxy):
def __init__(self, *args, **kwargs):
if ('v0' in kwargs) and ('v1' in kwargs) and ('v2' in kwargs):
vDict = {**kwargs, 'v0':kwargs['v0'].jsObj, 'v1':kwargs['v1'].jsObj, 'v2':kwargs['v2'].jsObj}
elif 'vs' in kwargs:
vertices = to_js(list(map(lambda v: v.jsObj, kwargs['vs'])))
if len(vertices) != 3:
raise Exception("triangleProxy: must have exactly 3 vertices")
vDict = {**kwargs, 'vs':vertices}
else:
raise Exception("triangleProxy: must specify v0, v1, v2 or vs")
jsObj = js_triangle(**vDict)
super().__init__(oType='triangle', vecAttrs=['color','v0','v1','v2'], jsObj=jsObj)
class quad(glowProxy):
def __init__(self, *args, **kwargs):
if ('v0' in kwargs) and ('v1' in kwargs) and ('v2' in kwargs) and ('v3' in kwargs):
vDict = {**kwargs,
'v0':kwargs['v0'].jsObj,
'v1':kwargs['v1'].jsObj,
'v2':kwargs['v2'].jsObj,
'v3':kwargs['v3'].jsObj}
elif 'vs' in kwargs:
vertices = to_js(list(map(lambda v: v.jsObj, kwargs['vs'])))
del kwargs['vs']
if len(vertices) != 4:
raise Exception("quadProxy: must have exactly 4 vertices")
vDict = {**kwargs, 'vs':vertices}
else:
raise Exception("quadProxy: must specify v0, v1, v2, v3 or vs")
jsObj = js_quad(**vDict)
glowProxy.__init__(self, oType='quad', vecAttrs=['color'], jsObj=jsObj)
class canvasProxy(glowProxy):
"""
A proxy for a glowscript scene.
"""
def __init__(self, *args, jsObj=None, factory=None, **kwargs):
super().__init__(vecAttrs = ['forward', 'center', 'background', 'ambient','up'], listAttrs=['lights'], oType='canvas', jsObj = jsObj, factory=factory, **kwargs)
def bind(self, action, py_func):
self.jsObj.bind(action, create_proxy(py_func))
@property
def camera(self):
return cameraProxy(jsObj=self.jsObj.camera)
@property
def mouse(self):
return mouseProxy(jsObj=self.jsObj.mouse)
class canvas(canvasProxy):
def __init__(self, *args, **kwargs):
canvasProxy.__init__(self, factory=js_canvas, *args, **kwargs)
scene = canvas(jsObj=js_scene)
def curveDictToJS(d):
"""
Convert a curve dict with python vectors to js vectors.
"""
if 'pos' in d:
d['pos'] = py2js_vec(d['pos'])
if 'color' in d:
d['color'] = py2js_vec(d['color'])
return d
class curveProxy(glowProxy):
"""
curves are a bit special because there are so many ways to call the constructor.
"""
def __init__(self, *args, factory = None, oType = 'curve', **kwargs):
if not factory:
factory = js_curve
std_list, std_kwargs = getStdForm(*args, **kwargs)
if (len(std_list)) > 0:
std_list_js = to_js(list(map(lambda d: curveDictToJS(d), std_list)), dict_converter=Object.fromEntries)
super().__init__(oType=oType, factory = factory, vecAttrs=['color'], **std_kwargs) # doesn't like args + kwargs at the same time?
self.jsObj.append(std_list_js)
else:
super().__init__(oType=oType, factory = factory, vecAttrs=['color'], **std_kwargs)
def append(self, *args, **kwargs):
std_list,std_kwargs = getStdForm(*args, **kwargs)
if (len(std_list)) > 0:
std_list_js = to_js(list(map(lambda d: curveDictToJS(d), std_list)), dict_converter=Object.fromEntries)
self.jsObj.append(std_list_js)
else:
self.jsObj.append(**std_kwargs)
def point(self, n):
pt = self.jsObj.point(n)
return {'pos':pt.pos, 'color':pt.color, 'radius':pt.radius, 'visible':pt.visible}
class curve(curveProxy):
def __init__(self, *args, **kwargs):
curveProxy.__init__(self, *args, oType='curve', factory=js_curve, **kwargs)
class points(curveProxy):
def __init__(self, *args, **kwargs):
curveProxy.__init__(self, *args, oType='points', factory=js_points, **kwargs)
class compound(glowProxy):
def __init__(self, *args, **kwargs):
if 'jsObj' in kwargs:
jsObj = kwargs['jsObj']
del kwargs['jsObj']
return glowProxy.__init__(self, vecAttrs=['pos','axis','color'], *args, oType='compound', jsObj=jsObj, **kwargs)
else:
if len(args) != 1:
raise Exception("compound: must have exactly 1 unnamed argument")
if not isinstance(args[0],list) or isinstance(args[0],tuple):
raise Exception("compound: must be passed a list or tuple of objects")
newArgs = to_js(list(map(lambda o: o.jsObj, args[0])))
return glowProxy.__init__(self, vecAttrs=['pos','axis','color'], *(newArgs,) , oType='compound', factory=js_compound, **kwargs)
def attach_light(*args, **kwargs):
if (len(args) != 1):
raise Exception("attach_light: must have exactly 1 unnamed argument")
elif (not isinstance(args[0], glowProxy)):
raise Exception("attach_light: must have a glowProxy as the unnamed argument")
return glowProxy(vecAttrs=['offset','color'], oType='attach_light', factory=js_attach_light, *(args[0].jsObj,), **kwargs)
class cameraProxy(glowProxy):
def __init__(self, *args, jsObj=None):
if not jsObj:
raise Exception("must specify a camera as a jsObj")
super().__init__(vecAttrs=['pos','axis'], oType='camera', jsObj=jsObj)
class mouseProxy(glowProxy):
def __init__(self, *args, jsObj=None):
if not jsObj:
raise Exception("must specify a mouse as a jsObj")
super().__init__(vecAttrs=['pos'], oType='mouse', jsObj=jsObj)
@property
def pick(self):
picked = self.jsObj.pick()
if picked is None:
return None
index = getattr(picked,gsRegKey,None)
if index is None:
return picked # it's not in the registry? Just return the JS object
pyObj = gsRegistry.get(index)
return pyObj