bspy 3.0.1__py3-none-any.whl → 4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bspy/viewer.py ADDED
@@ -0,0 +1,795 @@
1
+ import numpy as np
2
+ import tkinter as tk
3
+ from tkinter.ttk import Treeview
4
+ from tkinter.colorchooser import askcolor
5
+ from tkinter import filedialog
6
+ import queue, threading
7
+ from bspy import SplineOpenGLFrame, Solid, Boundary, Hyperplane, Spline
8
+
9
+ class _BitCheckbutton(tk.Checkbutton):
10
+ """A tkinter `CheckButton` that gets/sets its variable based on a given `bitmask`."""
11
+ def __init__(self, parent, bitMask, **kw):
12
+ self.bitMask = bitMask
13
+ self.variable = kw.get("variable")
14
+ self.command = kw.get("command")
15
+ self.var = tk.IntVar()
16
+ self.var.set(1 if self.variable.get() & self.bitMask else 0)
17
+ kw["variable"] = self.var
18
+ kw["onvalue"] = 1
19
+ kw["offvalue"] = 0
20
+ kw["command"] = self.Command
21
+ tk.Checkbutton.__init__(self, parent, **kw)
22
+
23
+ def Command(self):
24
+ """
25
+ Handles when the checkbutton is pushed, updating the variable based on the
26
+ bitmask and then calling the checkbutton command.
27
+ """
28
+ if self.var.get() == 1:
29
+ self.variable.set(self.variable.get() | self.bitMask)
30
+ else:
31
+ self.variable.set(self.variable.get() & ~self.bitMask)
32
+ self.command(self.variable.get())
33
+
34
+ def Update(self):
35
+ """Updates checkbutton state."""
36
+ self.var.set(1 if self.variable.get() & self.bitMask else 0)
37
+
38
+ class Viewer(tk.Tk):
39
+ """
40
+ A tkinter viewer (`tkinter.Tk`) that hosts a `SplineOpenGLFrame`, a treeview full of
41
+ splines, and a set of controls to adjust and view the selected splines.
42
+
43
+ See Also
44
+ --------
45
+ `Graphics` : A graphics engine to display splines. It launches a `Viewer` and issues commands to the viewer.
46
+
47
+ Examples
48
+ --------
49
+ Creates a Viewer, lists three splines, drawing (selecting) the third, and launches the viewer (blocks on the main loop).
50
+ >>> viewer = Viewer()
51
+ >>> viewer.list(spline1)
52
+ >>> viewer.list(spline2)
53
+ >>> viewer.draw(spline3)
54
+ >>> viewer.mainloop()
55
+ """
56
+
57
+ def __init__(self, *args, SplineOpenGLFrame=SplineOpenGLFrame, workQueue=None, **kw):
58
+ tk.Tk.__init__(self, *args, **kw)
59
+ self.title('BSpy Viewer')
60
+ self.geometry('600x500')
61
+
62
+ # Controls on the left
63
+ controls = tk.Frame(self)
64
+ controls.pack(side=tk.LEFT, fill=tk.Y)
65
+
66
+ buttons = tk.Frame(controls)
67
+ buttons.pack(side=tk.BOTTOM, fill=tk.X)
68
+ buttons.columnconfigure(0, weight=1)
69
+ buttons.columnconfigure(1, weight=1)
70
+ tk.Button(buttons, text='Load', command=self.load_splines).grid(row=0, column=0, sticky=tk.EW)
71
+ tk.Button(buttons, text='Save', command=self.save_splines).grid(row=0, column=1, sticky=tk.EW)
72
+ tk.Button(buttons, text='Remove', command=self.remove).grid(row=1, column=0, sticky=tk.EW)
73
+ tk.Button(buttons, text='Erase All', command=self.erase_all).grid(row=1, column=1, sticky=tk.EW)
74
+ tk.Button(buttons, text='Adjust', command=self._Adjust).grid(row=2, column=0, columnspan=2, sticky=tk.EW)
75
+
76
+ horizontalScroll = tk.Scrollbar(controls, orient=tk.HORIZONTAL)
77
+ horizontalScroll.pack(side=tk.BOTTOM, fill=tk.X)
78
+
79
+ treeFrame = tk.Frame(controls, width=120)
80
+ treeFrame.pack(side=tk.LEFT, fill=tk.Y)
81
+ treeFrame.pack_propagate(0)
82
+ self.treeview = Treeview(treeFrame, show='tree')
83
+ self.treeview.pack(side=tk.LEFT, fill=tk.Y)
84
+ self.treeview.bind('<<TreeviewSelect>>', self._ListSelectionChanged)
85
+ self.bind('<Control-a>', lambda *args: self.treeview.selection_add(self.treeview.get_children()))
86
+ verticalScroll = tk.Scrollbar(controls, orient=tk.VERTICAL)
87
+ verticalScroll.pack(side=tk.LEFT, fill=tk.Y)
88
+
89
+ horizontalScroll.config(command=self.treeview.xview)
90
+ self.treeview.configure(xscrollcommand=horizontalScroll.set)
91
+ verticalScroll.config(command=self.treeview.yview)
92
+ self.treeview.configure(yscrollcommand=verticalScroll.set)
93
+
94
+ # Controls on the right
95
+ controls = tk.Frame(self)
96
+ controls.pack(side=tk.RIGHT, fill=tk.BOTH, expand=tk.YES)
97
+
98
+ self.frame = SplineOpenGLFrame(controls, draw_func=self._DrawSplines)
99
+ self.frame.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.YES)
100
+
101
+ buttons = tk.Frame(controls)
102
+ buttons.pack(side=tk.BOTTOM)
103
+ tk.Button(buttons, text='Reset View', command=self.frame.Reset).pack(side=tk.LEFT)
104
+ self.frameMode = tk.IntVar()
105
+ tk.Radiobutton(buttons, text='Rotate', variable=self.frameMode, value=SplineOpenGLFrame.ROTATE, command=self._ChangeFrameMode).pack(side=tk.LEFT)
106
+ tk.Radiobutton(buttons, text='Pan', variable=self.frameMode, value=SplineOpenGLFrame.PAN, command=self._ChangeFrameMode).pack(side=tk.LEFT)
107
+ tk.Radiobutton(buttons, text='Fly', variable=self.frameMode, value=SplineOpenGLFrame.FLY, command=self._ChangeFrameMode).pack(side=tk.LEFT)
108
+ self.frameMode.set(SplineOpenGLFrame.ROTATE)
109
+ self.scale = tk.Scale(buttons, orient=tk.HORIZONTAL, from_=0, to=1, resolution=0.1, showvalue=0, command=self.frame.SetScale)
110
+ self.scale.pack(side=tk.LEFT)
111
+ self.scale.set(0.5)
112
+
113
+ self.splineList = {}
114
+ self.solidList = []
115
+ self.splineDrawList = []
116
+ self.splineRadius = 0.0
117
+ self.adjust = None
118
+ self.workQueue = workQueue
119
+ if self.workQueue is not None:
120
+ self.work = {
121
+ "list" : self.list,
122
+ "erase_all" : self.erase_all,
123
+ "empty" : self.empty,
124
+ "set_background_color" : self.set_background_color,
125
+ "update" : self.update
126
+ }
127
+ self.after(1000, self._check_for_work)
128
+
129
+ def _check_for_work(self):
130
+ """Check queue for calls to make."""
131
+ while not self.workQueue.empty():
132
+ try:
133
+ work = self.workQueue.get_nowait()
134
+ except queue.Empty:
135
+ break
136
+ else:
137
+ if work[0] in self.work:
138
+ self.work[work[0]](*work[1])
139
+ self.after(200, self._check_for_work)
140
+
141
+ def list(self, spline, name = None, fillColor=None, lineColor=None, options=None, draw=False, parentIID = ''):
142
+ """List a `Spline`, `Boundary`, or `Solid` in the treeview. Can be called before viewer is running."""
143
+ if isinstance(spline, Solid):
144
+ solid = spline
145
+ if solid.dimension != 3:
146
+ return
147
+ if name is None:
148
+ name = "Solid"
149
+ iid = self.treeview.insert('', 'end', text=name, open=False)
150
+ self.splineList[iid] = solid
151
+ self.solidList.append(solid)
152
+ if draw:
153
+ self.treeview.selection_add(iid)
154
+ for i, boundary in enumerate(solid.boundaries):
155
+ self.list(boundary, f"{name} boundary {i+1}", fillColor, lineColor, options, False, iid)
156
+ elif isinstance(spline, Boundary):
157
+ boundary = spline
158
+ if isinstance(boundary.manifold, Hyperplane):
159
+ uvMin = boundary.domain.bounds[:,0]
160
+ uvMax = boundary.domain.bounds[:,1]
161
+ xyzMinMin = boundary.manifold.evaluate(uvMin)
162
+ xyzMinMax = boundary.manifold.evaluate((uvMin[0], uvMax[1]))
163
+ xyzMaxMin = boundary.manifold.evaluate((uvMax[0], uvMin[1]))
164
+ xyzMaxMax = boundary.manifold.evaluate(uvMax)
165
+ spline = Spline(2, 3, (2, 2), (2, 2),
166
+ np.array((uvMin, uvMin, uvMax, uvMax), np.float32).T,
167
+ np.array(((xyzMinMin, xyzMaxMin), (xyzMinMax, xyzMaxMax)), np.float32).T)
168
+ elif isinstance(boundary.manifold, Spline):
169
+ spline = boundary.manifold
170
+ if not hasattr(spline, "cache"):
171
+ spline.cache = {}
172
+ spline.cache["trim"] = self.frame.tessellate2DSolid(boundary.domain)
173
+ self.list(spline, name, fillColor, lineColor, options, draw, parentIID)
174
+ else:
175
+ if "Name" not in spline.metadata:
176
+ spline.metadata["Name"] = f"Spline({spline.nInd}, {spline.nDep})" if name is None else name
177
+ if fillColor is not None:
178
+ spline.metadata["fillColor"] = fillColor
179
+ if lineColor is not None:
180
+ spline.metadata["lineColor"] = lineColor
181
+ if options is not None:
182
+ spline.metadata["options"] = options
183
+ self.frame.make_drawable(spline)
184
+ iid = self.treeview.insert(parentIID, 'end', text=spline.metadata["Name"])
185
+ self.splineList[iid] = spline
186
+ if draw:
187
+ self.treeview.selection_add(iid)
188
+
189
+ show = list
190
+
191
+ def draw(self, spline, name = None, fillColor=None, lineColor=None, options=None):
192
+ """List a `Spline`, `Boundary`, or `Solid` in the treeview and draw it in the viewer. Can be called before viewer is running."""
193
+ self.list(spline, name, fillColor, lineColor, options, True)
194
+
195
+ def save_splines(self):
196
+ splines = [self.splineList[item] for item in self.treeview.selection()]
197
+ if splines:
198
+ initialName = self.treeview.item(self.treeview.selection()[0])["text"] + ".json"
199
+ fileName = filedialog.asksaveasfilename(title="Save splines", initialfile=initialName,
200
+ defaultextension=".json", filetypes=(('Json files', '*.json'),('All files', '*.*')))
201
+ if fileName:
202
+ Solid.save(fileName, *splines)
203
+
204
+ def load_splines(self):
205
+ fileName = filedialog.askopenfilename(title="Load splines",
206
+ defaultextension=".json", filetypes=(('Json files', '*.json'),('All files', '*.*')))
207
+ if fileName:
208
+ splines = Solid.load(fileName)
209
+ for spline in splines:
210
+ if isinstance(spline, Spline):
211
+ self.list(spline)
212
+ else:
213
+ self.list_solid(spline)
214
+
215
+ def erase_all(self):
216
+ """Stop drawing all splines. Splines remain in the treeview."""
217
+ self.treeview.selection_set()
218
+ self.splineRadius = 0.0
219
+ self.frame.ResetView()
220
+ self.update()
221
+
222
+ def remove(self):
223
+ """Remove splines from the treeview."""
224
+ for item in self.treeview.selection():
225
+ spline = self.splineList.pop(item)
226
+ self.splineDrawList.remove(spline)
227
+ self.treeview.delete(*self.treeview.selection())
228
+ self.update()
229
+
230
+ def empty(self):
231
+ """Stop drawing all splines and remove them from the treeview."""
232
+ self.splineList = {}
233
+ self.treeview.delete(*self.treeview.get_children())
234
+ self.splineRadius = 0.0
235
+ self.frame.ResetView()
236
+ self.update()
237
+
238
+ def set_background_color(self, r, g=None, b=None, a=None):
239
+ """
240
+ Set the background color.
241
+
242
+ Parameters
243
+ ----------
244
+ r : `float`, `int` or array-like of floats or ints
245
+ The red value [0, 1] as a float, [0, 255] as an int, or the rgb or rgba value as floats or ints (default).
246
+
247
+ g: `float` or `int`
248
+ The green value [0, 1] as a float or [0, 255] as an int.
249
+
250
+ b: `float` or `int`
251
+ The blue value [0, 1] as a float or [0, 255] as an int.
252
+
253
+ a: `float`, `int`, or None
254
+ The alpha value [0, 1] as a float or [0, 255] as an int. If `None` then alpha is set to 1.
255
+ """
256
+ self.frame.SetBackgroundColor(r, g, b, a)
257
+ self.frame.Update()
258
+
259
+ def update(self):
260
+ """Update the spline draw list, set the default view, reset the bounds, and refresh the frame."""
261
+ self.splineDrawList = []
262
+ gotOne = False
263
+ for item in self.treeview.selection():
264
+ spline = self.splineList[item]
265
+ if isinstance(spline, Spline):
266
+ coefs = spline.cache["coefs32"].T[:3]
267
+ coefsAxis = tuple(range(1, spline.nInd + 1))
268
+ if gotOne:
269
+ splineMin = np.minimum(splineMin, coefs.min(axis=coefsAxis))
270
+ splineMax = np.maximum(splineMax, coefs.max(axis=coefsAxis))
271
+ else:
272
+ splineMin = coefs.min(axis=coefsAxis)
273
+ splineMax = coefs.max(axis=coefsAxis)
274
+ gotOne = True
275
+ self.splineDrawList.append(spline)
276
+ elif isinstance(spline, Solid):
277
+ for subitem in self.treeview.get_children(item):
278
+ spline = self.splineList[subitem]
279
+ coefs = spline.cache["coefs32"].T[:3]
280
+ coefsAxis = tuple(range(1, spline.nInd + 1))
281
+ if gotOne:
282
+ splineMin = np.minimum(splineMin, coefs.min(axis=coefsAxis))
283
+ splineMax = np.maximum(splineMax, coefs.max(axis=coefsAxis))
284
+ else:
285
+ splineMin = coefs.min(axis=coefsAxis)
286
+ splineMax = coefs.max(axis=coefsAxis)
287
+ gotOne = True
288
+ self.splineDrawList.append(spline)
289
+
290
+ if gotOne:
291
+ newRadius = 0.5 * np.max(splineMax - splineMin)
292
+ self.splineRadius = newRadius
293
+ atDefaultEye = np.allclose(self.frame.eye, self.frame.defaultEye)
294
+ center = 0.5 * (splineMax + splineMin)
295
+ self.frame.SetDefaultView(center + (0.0, 0.0, 3.0 * newRadius), center, (0.0, 1.0, 0.0))
296
+ self.frame.ResetBounds()
297
+ if atDefaultEye:
298
+ self.frame.ResetView()
299
+ else:
300
+ self.splineRadius = 0.0
301
+
302
+ if self.adjust is not None:
303
+ if self.splineDrawList:
304
+ self.bits.set(self.get_options(self.splineDrawList[0]))
305
+ animate = self.get_animate(self.splineDrawList[0])
306
+ else:
307
+ self.bits.set(0)
308
+ animate = None
309
+ for button in self.checkButtons.winfo_children():
310
+ button.Update()
311
+ self.animate.set(next(key for key, value in self.animateOptions.items() if value == animate))
312
+
313
+ self.frame.Update()
314
+
315
+ def _DrawSplines(self, frame, transform):
316
+ """Handle when frame needs to be redrawn."""
317
+ for spline in self.splineDrawList:
318
+ frame.DrawSpline(spline, transform)
319
+
320
+ def _ListSelectionChanged(self, event):
321
+ """Handle when the treeview selection has changed."""
322
+ self.update()
323
+
324
+ def _ChangeFrameMode(self):
325
+ """Handle when the view mode has changed."""
326
+ self.frame.SetMode(self.frameMode.get())
327
+
328
+ def _Adjust(self):
329
+ """Handle when the Adjust button is pressed."""
330
+ if self.adjust is None:
331
+ self.adjust = tk.Toplevel()
332
+ self.adjust.title("Adjust")
333
+ self.adjust.bind('<Destroy>', self._AdjustDestroy)
334
+
335
+ self.checkButtons = tk.LabelFrame(self.adjust, text="Decoration")
336
+ self.checkButtons.pack(side=tk.LEFT, fill=tk.BOTH, expand=tk.YES)
337
+
338
+ self.bits = tk.IntVar()
339
+ if self.splineDrawList:
340
+ self.bits.set(self.get_options(self.splineDrawList[0]))
341
+ else:
342
+ self.bits.set(0)
343
+ _BitCheckbutton(self.checkButtons, self.frame.SHADED, text="Shaded", anchor=tk.W, variable=self.bits, command=self._ChangeOptions).pack(side=tk.TOP, fill=tk.X)
344
+ _BitCheckbutton(self.checkButtons, self.frame.BOUNDARY, text="Boundary", anchor=tk.W, variable=self.bits, command=self._ChangeOptions).pack(side=tk.TOP, fill=tk.X)
345
+ _BitCheckbutton(self.checkButtons, self.frame.ISOPARMS, text="Isoparms", anchor=tk.W, variable=self.bits, command=self._ChangeOptions).pack(side=tk.TOP, fill=tk.X)
346
+ _BitCheckbutton(self.checkButtons, self.frame.HULL, text="Hull", anchor=tk.W, variable=self.bits, command=self._ChangeOptions).pack(side=tk.TOP, fill=tk.X)
347
+
348
+ buttons = tk.LabelFrame(self.adjust, text="Options")
349
+ buttons.pack(side=tk.LEFT, fill=tk.BOTH, expand=tk.YES)
350
+ tk.Button(buttons, text='Fill color', command=self._FillColorChange).pack(side=tk.TOP, fill=tk.X)
351
+ tk.Button(buttons, text='Line color', command=self._LineColorChange).pack(side=tk.TOP, fill=tk.X)
352
+ self.animate = tk.StringVar()
353
+ self.animateOptions = {"Animate: Off" : None, "Animate: u(0)" : 0, "Animate: v(1)" : 1, "Animate: w(2)" : 2}
354
+ if self.splineDrawList:
355
+ animate = self.get_animate(self.splineDrawList[0])
356
+ else:
357
+ animate = None
358
+ self.animate.set(next(key for key, value in self.animateOptions.items() if value == animate))
359
+ tk.OptionMenu(buttons, self.animate, *self.animateOptions.keys(), command=self._ChangeAnimate).pack(side=tk.TOP, fill=tk.X)
360
+ tk.Button(buttons, text='Dismiss', command=self.adjust.withdraw).pack(side=tk.TOP, fill=tk.X)
361
+
362
+ self.adjust.update()
363
+ self.adjust.resizable(False, False)
364
+ else:
365
+ self.adjust.deiconify()
366
+
367
+ if self.winfo_x() + self.winfo_width() + 205 <= self.winfo_screenwidth():
368
+ self.adjust.geometry("{width}x{height}+{x}+{y}".format(width=205, height=self.adjust.winfo_height(), x=self.winfo_x() + self.winfo_width(), y=self.winfo_y()))
369
+ else:
370
+ self.adjust.geometry("{width}x{height}+{x}+{y}".format(width=205, height=self.adjust.winfo_height(), x=self.winfo_screenwidth() - 205, y=self.winfo_y()))
371
+
372
+ def _AdjustDestroy(self, event):
373
+ """Handle when the adjust dialog is destroyed."""
374
+ self.adjust = None
375
+ self.checkButtons = None
376
+
377
+ def _ChangeOptions(self, options):
378
+ """Handle when the spline options are changed."""
379
+ for spline in self.splineDrawList:
380
+ self.set_options(spline, options)
381
+ self.frame.Update()
382
+
383
+ def _ChangeAnimate(self, value):
384
+ """Handle when the spline animation is changed."""
385
+ nInd = self.animateOptions[value]
386
+ animating = False
387
+ for spline in self.splineDrawList:
388
+ if nInd is None or nInd < spline.nInd:
389
+ self.set_animate(spline, nInd)
390
+ animating = True
391
+ self.frame.SetAnimating(animating)
392
+ self.frame.Update()
393
+
394
+ def _FillColorChange(self):
395
+ """Handle when the fill color changed."""
396
+ if self.splineDrawList:
397
+ oldColor = 255.0 * self.get_fill_color(self.splineDrawList[0])
398
+ newColor = askcolor(title="Set spline fill color", color="#%02x%02x%02x" % (int(oldColor[0]), int(oldColor[1]), int(oldColor[2])))
399
+ if newColor[0] is not None:
400
+ for spline in self.splineDrawList:
401
+ self.set_fill_color(spline, newColor[0])
402
+ self.frame.Update()
403
+
404
+ def _LineColorChange(self):
405
+ """Handle when the line color changed."""
406
+ if self.splineDrawList:
407
+ oldColor = 255.0 * self.get_line_color(self.splineDrawList[0])
408
+ newColor = askcolor(title="Set spline line color", color="#%02x%02x%02x" % (int(oldColor[0]), int(oldColor[1]), int(oldColor[2])))
409
+ if newColor[0] is not None:
410
+ for spline in self.splineDrawList:
411
+ self.set_line_color(spline, newColor[0])
412
+ self.frame.Update()
413
+
414
+ @staticmethod
415
+ def get_fill_color(spline):
416
+ """
417
+ Gets the fill color of the spline (only useful for nInd >= 2).
418
+
419
+ Parameters
420
+ ----------
421
+ spline : `Spline`
422
+ The referenced spline.
423
+
424
+ Returns
425
+ -------
426
+ fillColor : `numpy.array`
427
+ Array of four floats (r, g, b, a) in the range [0, 1].
428
+ """
429
+ return spline.metadata["fillColor"]
430
+
431
+ @staticmethod
432
+ def set_fill_color(spline, r, g=None, b=None, a=None):
433
+ """
434
+ Set the fill color of the spline (only useful for nInd >= 2).
435
+
436
+ Parameters
437
+ ----------
438
+ spline : `Spline`
439
+ The referenced spline.
440
+
441
+ r : `float`, `int` or array-like of floats or ints
442
+ The red value [0, 1] as a float, [0, 255] as an int, or the rgb or rgba value as floats or ints (default).
443
+
444
+ g: `float` or `int`
445
+ The green value [0, 1] as a float or [0, 255] as an int.
446
+
447
+ b: `float` or `int`
448
+ The blue value [0, 1] as a float or [0, 255] as an int.
449
+
450
+ a: `float`, `int`, or None
451
+ The alpha value [0, 1] as a float or [0, 255] as an int. If `None` then alpha is set to 1.
452
+ """
453
+ spline.metadata["fillColor"] = SplineOpenGLFrame.compute_color_vector(r, g, b, a)
454
+
455
+ @staticmethod
456
+ def get_line_color(spline):
457
+ """
458
+ Gets the line color of the spline.
459
+
460
+ Parameters
461
+ ----------
462
+ spline : `Spline`
463
+ The referenced spline.
464
+
465
+ Returns
466
+ -------
467
+ lineColor : `numpy.array`
468
+ Array of four floats (r, g, b, a) in the range [0, 1].
469
+ """
470
+ return spline.metadata["lineColor"]
471
+
472
+ @staticmethod
473
+ def set_line_color(spline, r, g=None, b=None, a=None):
474
+ """
475
+ Set the line color of the spline.
476
+
477
+ Parameters
478
+ ----------
479
+ spline : `Spline`
480
+ The referenced spline.
481
+
482
+ r : `float`, `int` or array-like of floats or ints
483
+ The red value [0, 1] as a float, [0, 255] as an int, or the rgb or rgba value as floats or ints (default).
484
+
485
+ g: `float` or `int`
486
+ The green value [0, 1] as a float or [0, 255] as an int.
487
+
488
+ b: `float` or `int`
489
+ The blue value [0, 1] as a float or [0, 255] as an int.
490
+
491
+ a: `float`, `int`, or None
492
+ The alpha value [0, 1] as a float or [0, 255] as an int. If `None` then alpha is set to 1.
493
+ """
494
+ spline.metadata["lineColor"] = SplineOpenGLFrame.compute_color_vector(r, g, b, a)
495
+
496
+ @staticmethod
497
+ def get_options(spline):
498
+ """
499
+ Gets the draw options for the spline.
500
+
501
+ Parameters
502
+ ----------
503
+ spline : `Spline`
504
+ The referenced spline.
505
+
506
+ Returns
507
+ -------
508
+ options : `int` bitwise or (`|`) of zero or more of the following values:
509
+ * `SplineOpenGLFrame.HULL` Draw the convex hull of the spline (the coefficients). Off by default.
510
+ * `SplineOpenGLFrame.SHADED` Draw the spline shaded (only useful for nInd >= 2). On by default.
511
+ * `SplineOpenGLFrame.BOUNDARY` Draw the boundary of the spline in the line color (only useful for nInd >= 2). On by default.
512
+ * `SplineOpenGLFrame.ISOPARMS` Draw the lines of constant knot values of the spline in the line color (only useful for nInd >= 2). Off by default.
513
+ """
514
+ return spline.metadata["options"]
515
+
516
+ @staticmethod
517
+ def set_options(spline, options):
518
+ """
519
+ Set the draw options for the spline.
520
+
521
+ Parameters
522
+ ----------
523
+ spline : `Spline`
524
+ The referenced spline.
525
+
526
+ options : `int` bitwise or (`|`) of zero or more of the following values:
527
+ * `SplineOpenGLFrame.HULL` Draw the convex hull of the spline (the coefficients). Off by default.
528
+ * `SplineOpenGLFrame.SHADED` Draw the spline shaded (only useful for nInd >= 2). On by default.
529
+ * `SplineOpenGLFrame.BOUNDARY` Draw the boundary of the spline in the line color (only useful for nInd >= 2). On by default.
530
+ * `SplineOpenGLFrame.ISOPARMS` Draw the lines of constant knot values of the spline in the line color (only useful for nInd >= 2). Off by default.
531
+ """
532
+ spline.metadata["options"] = options
533
+
534
+ @staticmethod
535
+ def get_animate(spline):
536
+ """
537
+ Get the independent variable that is animated (None if there is none).
538
+
539
+ Parameters
540
+ ----------
541
+ spline : `Spline`
542
+ The referenced spline.
543
+
544
+ Returns
545
+ -------
546
+ animate : `int` or `None`
547
+ The index of the independent variable that is animated (None is there is none).
548
+ """
549
+ return spline.metadata["animate"]
550
+
551
+ @staticmethod
552
+ def set_animate(spline, animate):
553
+ """
554
+ Set the independent variable that is animated (None if there is none).
555
+
556
+ Parameters
557
+ ----------
558
+ spline : `Spline`
559
+ The referenced spline.
560
+
561
+ animate : `int` or `None`
562
+ The index of the independent variable that is animated (None is there is none).
563
+ """
564
+ spline.metadata["animate"] = animate
565
+
566
+ class Graphics:
567
+ """
568
+ A graphics engine to script and display splines. It launches a `Viewer` and issues commands to the viewer.
569
+
570
+ Parameters
571
+ ----------
572
+ variableDictionary : `dict`
573
+ A dictionary of variable names, typically `locals()`, used to assign names to splines.
574
+
575
+ See Also
576
+ --------
577
+ `Viewer` : A tkinter app (`tkinter.Tk`) that hosts a `SplineOpenGLFrame`, a treeview full of
578
+ splines, and a set of controls to adjust and view the selected splines.
579
+
580
+ Examples
581
+ --------
582
+ Launch a Viewer and tell it to draw some splines.
583
+ >>> graphics = Graphics(locals())
584
+ >>> graphics.draw(spline1)
585
+ >>> graphics.draw(spline2)
586
+ >>> graphics.draw(spline3)
587
+ """
588
+
589
+ def __init__(self, variableDictionary):
590
+ self.workQueue = queue.Queue()
591
+ self.appThread = threading.Thread(target=self._app_thread)
592
+ self.appThread.start()
593
+ self.variableDictionary = variableDictionary
594
+
595
+ def _app_thread(self):
596
+ viewer = Viewer(workQueue=self.workQueue)
597
+ viewer.mainloop()
598
+
599
+ def list(self, spline, name = None, fillColor=None, lineColor=None, options=None, draw=False):
600
+ """List a `Spline`, `Boundary`, or `Solid` in the treeview. Can be called before viewer is running."""
601
+ if name is None:
602
+ for name, value in self.variableDictionary.items():
603
+ if value is spline:
604
+ break
605
+ self.workQueue.put(("list", (spline, name, fillColor, lineColor, options, draw)))
606
+
607
+ show = list
608
+
609
+ def draw(self, spline, name = None, fillColor=None, lineColor=None, options=None):
610
+ """List a `Spline`, `Boundary`, or `Solid` in the treeview and draw it in the viewer. Can be called before viewer is running."""
611
+ self.list(spline, name, fillColor, lineColor, options, True)
612
+
613
+ def erase_all(self):
614
+ """Stop drawing all splines. Splines remain in the treeview."""
615
+ self.workQueue.put(("erase_all", ()))
616
+
617
+ def empty(self):
618
+ """Stop drawing all splines and remove them from the treeview."""
619
+ self.workQueue.put(("empty", ()))
620
+
621
+ def set_background_color(self, r, g=None, b=None, a=None):
622
+ """
623
+ Set the background color.
624
+
625
+ Parameters
626
+ ----------
627
+ r : `float`, `int` or array-like of floats or ints
628
+ The red value [0, 1] as a float, [0, 255] as an int, or the rgb or rgba value as floats or ints (default).
629
+
630
+ g: `float` or `int`
631
+ The green value [0, 1] as a float or [0, 255] as an int.
632
+
633
+ b: `float` or `int`
634
+ The blue value [0, 1] as a float or [0, 255] as an int.
635
+
636
+ a: `float`, `int`, or None
637
+ The alpha value [0, 1] as a float or [0, 255] as an int. If `None` then alpha is set to 1.
638
+ """
639
+ self.workQueue.put(("set_background_color", (r, g, b, a)))
640
+
641
+ def update(self):
642
+ """Update the spline draw list and refresh the frame."""
643
+ self.workQueue.put(("update", ()))
644
+
645
+ @staticmethod
646
+ def get_fill_color(spline):
647
+ """
648
+ Gets the fill color of the spline (only useful for nInd >= 2).
649
+
650
+ Parameters
651
+ ----------
652
+ spline : `Spline`
653
+ The referenced spline.
654
+
655
+ Returns
656
+ -------
657
+ fillColor : `numpy.array`
658
+ Array of four floats (r, g, b, a) in the range [0, 1].
659
+ """
660
+ return spline.metadata["fillColor"]
661
+
662
+ @staticmethod
663
+ def set_fill_color(spline, r, g=None, b=None, a=None):
664
+ """
665
+ Set the fill color of the spline (only useful for nInd >= 2).
666
+
667
+ Parameters
668
+ ----------
669
+ spline : `Spline`
670
+ The referenced spline.
671
+
672
+ r : `float`, `int` or array-like of floats or ints
673
+ The red value [0, 1] as a float, [0, 255] as an int, or the rgb or rgba value as floats or ints (default).
674
+
675
+ g: `float` or `int`
676
+ The green value [0, 1] as a float or [0, 255] as an int.
677
+
678
+ b: `float` or `int`
679
+ The blue value [0, 1] as a float or [0, 255] as an int.
680
+
681
+ a: `float`, `int`, or None
682
+ The alpha value [0, 1] as a float or [0, 255] as an int. If `None` then alpha is set to 1.
683
+ """
684
+ spline.metadata["fillColor"] = SplineOpenGLFrame.compute_color_vector(r, g, b, a)
685
+
686
+ @staticmethod
687
+ def get_line_color(spline):
688
+ """
689
+ Gets the line color of the spline.
690
+
691
+ Parameters
692
+ ----------
693
+ spline : `Spline`
694
+ The referenced spline.
695
+
696
+ Returns
697
+ -------
698
+ lineColor : `numpy.array`
699
+ Array of four floats (r, g, b, a) in the range [0, 1].
700
+ """
701
+ return spline.metadata["lineColor"]
702
+
703
+ @staticmethod
704
+ def set_line_color(spline, r, g=None, b=None, a=None):
705
+ """
706
+ Set the line color of the spline.
707
+
708
+ Parameters
709
+ ----------
710
+ spline : `Spline`
711
+ The referenced spline.
712
+
713
+ r : `float`, `int` or array-like of floats or ints
714
+ The red value [0, 1] as a float, [0, 255] as an int, or the rgb or rgba value as floats or ints (default).
715
+
716
+ g: `float` or `int`
717
+ The green value [0, 1] as a float or [0, 255] as an int.
718
+
719
+ b: `float` or `int`
720
+ The blue value [0, 1] as a float or [0, 255] as an int.
721
+
722
+ a: `float`, `int`, or None
723
+ The alpha value [0, 1] as a float or [0, 255] as an int. If `None` then alpha is set to 1.
724
+ """
725
+ spline.metadata["lineColor"] = SplineOpenGLFrame.compute_color_vector(r, g, b, a)
726
+
727
+ @staticmethod
728
+ def get_options(spline):
729
+ """
730
+ Gets the draw options for the spline.
731
+
732
+ Parameters
733
+ ----------
734
+ spline : `Spline`
735
+ The referenced spline.
736
+
737
+ Returns
738
+ -------
739
+ options : `int` bitwise or (`|`) of zero or more of the following values:
740
+ * `SplineOpenGLFrame.HULL` Draw the convex hull of the spline (the coefficients). Off by default.
741
+ * `SplineOpenGLFrame.SHADED` Draw the spline shaded (only useful for nInd >= 2). On by default.
742
+ * `SplineOpenGLFrame.BOUNDARY` Draw the boundary of the spline in the line color (only useful for nInd >= 2). On by default.
743
+ * `SplineOpenGLFrame.ISOPARMS` Draw the lines of constant knot values of the spline in the line color (only useful for nInd >= 2). Off by default.
744
+ """
745
+ return spline.metadata["options"]
746
+
747
+ @staticmethod
748
+ def set_options(spline, options):
749
+ """
750
+ Set the draw options for the spline.
751
+
752
+ Parameters
753
+ ----------
754
+ spline : `Spline`
755
+ The referenced spline.
756
+
757
+ options : `int` bitwise or (`|`) of zero or more of the following values:
758
+ * `SplineOpenGLFrame.HULL` Draw the convex hull of the spline (the coefficients). Off by default.
759
+ * `SplineOpenGLFrame.SHADED` Draw the spline shaded (only useful for nInd >= 2). On by default.
760
+ * `SplineOpenGLFrame.BOUNDARY` Draw the boundary of the spline in the line color (only useful for nInd >= 2). On by default.
761
+ * `SplineOpenGLFrame.ISOPARMS` Draw the lines of constant knot values of the spline in the line color (only useful for nInd >= 2). Off by default.
762
+ """
763
+ spline.metadata["options"] = options
764
+
765
+ @staticmethod
766
+ def get_animate(spline):
767
+ """
768
+ Get the independent variable that is animated (None if there is none).
769
+
770
+ Parameters
771
+ ----------
772
+ spline : `Spline`
773
+ The referenced spline.
774
+
775
+ Returns
776
+ -------
777
+ animate : `int` or `None`
778
+ The index of the independent variable that is animated (None is there is none).
779
+ """
780
+ return spline.metadata["animate"]
781
+
782
+ @staticmethod
783
+ def set_animate(spline, animate):
784
+ """
785
+ Set the independent variable that is animated (None if there is none).
786
+
787
+ Parameters
788
+ ----------
789
+ spline : `Spline`
790
+ The referenced spline.
791
+
792
+ animate : `int` or `None`
793
+ The index of the independent variable that is animated (None is there is none).
794
+ """
795
+ spline.metadata["animate"] = animate