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/__init__.py +16 -9
- bspy/_spline_domain.py +83 -47
- bspy/_spline_evaluation.py +44 -62
- bspy/_spline_fitting.py +353 -75
- bspy/_spline_intersection.py +332 -59
- bspy/_spline_operations.py +33 -38
- bspy/hyperplane.py +540 -0
- bspy/manifold.py +391 -0
- bspy/solid.py +839 -0
- bspy/spline.py +310 -77
- bspy/splineOpenGLFrame.py +683 -19
- bspy/viewer.py +795 -0
- {bspy-3.0.1.dist-info → bspy-4.1.dist-info}/METADATA +25 -13
- bspy-4.1.dist-info/RECORD +17 -0
- {bspy-3.0.1.dist-info → bspy-4.1.dist-info}/WHEEL +1 -1
- bspy/bspyApp.py +0 -426
- bspy/drawableSpline.py +0 -585
- bspy-3.0.1.dist-info/RECORD +0 -15
- {bspy-3.0.1.dist-info → bspy-4.1.dist-info}/LICENSE +0 -0
- {bspy-3.0.1.dist-info → bspy-4.1.dist-info}/top_level.txt +0 -0
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
|