Skip to content

Commit 0ef98e3

Browse files
Rework fig
1 parent be570bb commit 0ef98e3

File tree

1 file changed

+232
-53
lines changed

1 file changed

+232
-53
lines changed

cadquery/fig.py

Lines changed: 232 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
from threading import Thread
1010
from itertools import chain
1111
from webbrowser import open_new_tab
12+
from uuid import uuid1
1213

1314
from typish import instance_of
1415

1516
from trame.app import get_server
1617
from trame.app.core import Server
17-
from trame.widgets import html, vtk as vtk_widgets, client
18-
from trame.ui.html import DivLayout
19-
18+
from trame.widgets import vtk as vtk_widgets, client, trame, vuetify3 as v3
19+
from trame.ui.vuetify3 import SinglePageWithDrawerLayout
2020
from . import Shape
2121
from .vis import style, Showable, ShapeLike, _split_showables
2222

@@ -43,7 +43,7 @@ class Figure:
4343
ren: vtkRenderer
4444
view: vtk_widgets.VtkRemoteView
4545
shapes: dict[ShapeLike, list[vtkProp3D]]
46-
actors: list[vtkProp3D]
46+
actors: dict[str, tuple[vtkProp3D, ...]]
4747
loop: AbstractEventLoop
4848
thread: Thread
4949
empty: bool
@@ -107,24 +107,104 @@ def __init__(self, port: int = 18081):
107107
self.ren = renderer
108108

109109
self.shapes = {}
110-
self.actors = []
110+
self.actors = {}
111+
self.active = None
111112

112113
# server
113-
server = get_server("CQ-server")
114-
server.client_type = "vue3"
114+
server = get_server("CQ-server", client_type="vue3")
115+
self.server = server
116+
117+
# state
118+
self.state = self.server.state
119+
120+
self.state.actors = []
121+
self.state.selected = None
115122

116123
# layout
117-
with DivLayout(server):
124+
self.layout = SinglePageWithDrawerLayout(server, show_drawer=False)
125+
with self.layout as layout:
118126
client.Style("body { margin: 0; }")
119127

120-
with html.Div(style=FULL_SCREEN):
121-
self.view = vtk_widgets.VtkRemoteView(
122-
win, interactive_ratio=1, interactive_quality=100
128+
layout.title.set_text("CQ viewer")
129+
layout.footer.hide()
130+
131+
with layout.toolbar:
132+
133+
BSTYLE = "display: block;"
134+
135+
v3.VBtn(
136+
click=lambda: self._fit(),
137+
flat=True,
138+
density="compact",
139+
icon="mdi-crop-free",
140+
style=BSTYLE,
141+
)
142+
143+
v3.VBtn(
144+
click=lambda: self._view((0, 0, 0), (1, 1, 1), (0, 0, 1)),
145+
flat=True,
146+
density="compact",
147+
icon="mdi-axis-arrow",
148+
style=BSTYLE,
149+
)
150+
151+
v3.VBtn(
152+
click=lambda: self._view((0, 0, 0), (1, 0, 0), (0, 0, 1)),
153+
flat=True,
154+
density="compact",
155+
icon="mdi-axis-x-arrow",
156+
style=BSTYLE,
157+
)
158+
159+
v3.VBtn(
160+
click=lambda: self._view((0, 0, 0), (0, 1, 0), (0, 0, 1)),
161+
flat=True,
162+
density="compact",
163+
icon="mdi-axis-y-arrow",
164+
style=BSTYLE,
165+
)
166+
167+
v3.VBtn(
168+
click=lambda: self._view((0, 0, 0), (0, 0, 1), (0, 1, 0)),
169+
flat=True,
170+
density="compact",
171+
icon="mdi-axis-z-arrow",
172+
style=BSTYLE,
173+
)
174+
175+
v3.VBtn(
176+
click=lambda: self._pop(),
177+
flat=True,
178+
density="compact",
179+
icon="mdi-file-document-remove-outline",
180+
style=BSTYLE,
181+
)
182+
183+
v3.VBtn(
184+
click=lambda: self._clear([]),
185+
flat=True,
186+
density="compact",
187+
icon="mdi-delete-outline",
188+
style=BSTYLE,
189+
)
190+
191+
with layout.content:
192+
with v3.VContainer(
193+
fluid=True, classes="pa-0 fill-height",
194+
):
195+
self.view = vtk_widgets.VtkRemoteView(
196+
win, interactive_ratio=1, interactive_quality=100
197+
)
198+
199+
with layout.drawer:
200+
self.tree = trame.GitTree(
201+
sources=("actors",),
202+
visibility_change=(self.onVisibility, "[$event]"),
203+
actives_change=(self.onSelection, "[$event]"),
123204
)
124205

125206
server.state.flush()
126207

127-
self.server = server
128208
self.loop = new_event_loop()
129209

130210
def _run_loop():
@@ -159,7 +239,20 @@ def _run(self, coro) -> Future:
159239

160240
return run_coroutine_threadsafe(coro, self.loop)
161241

162-
def show(self, *showables: Showable | vtkProp3D | list[vtkProp3D], **kwargs):
242+
def _update_state(self, name: str):
243+
async def _():
244+
245+
self.state.dirty(name)
246+
self.state.flush()
247+
248+
self._run(_())
249+
250+
def show(
251+
self,
252+
*showables: Showable | vtkProp3D | list[vtkProp3D],
253+
name: Optional[str] = None,
254+
**kwargs,
255+
):
163256
"""
164257
Show objects.
165258
"""
@@ -170,6 +263,9 @@ def show(self, *showables: Showable | vtkProp3D | list[vtkProp3D], **kwargs):
170263
pts = style(vecs, **kwargs)
171264
axs = style(locs, **kwargs)
172265

266+
# to be added to state
267+
new_actors = []
268+
173269
for s in shapes:
174270
# do not show markers by default
175271
if "markersize" not in kwargs:
@@ -181,14 +277,18 @@ def show(self, *showables: Showable | vtkProp3D | list[vtkProp3D], **kwargs):
181277
for actor in actors:
182278
self.ren.AddActor(actor)
183279

280+
new_actors.extend(actors)
281+
184282
for prop in chain(props, axs):
185-
self.actors.append(prop)
186283
self.ren.AddActor(prop)
187284

285+
new_actors.append(prop)
286+
188287
if vecs:
189-
self.actors.append(*pts)
190288
self.ren.AddActor(*pts)
191289

290+
new_actors.append(*pts)
291+
192292
# store to enable pop
193293
self.last = (shapes, axs, pts if vecs else None, props)
194294

@@ -202,76 +302,155 @@ async def _show():
202302
self.fit()
203303
self.empty = False
204304

305+
# update actors
306+
uuid = str(uuid1())
307+
self.state.actors.append(
308+
{
309+
"id": uuid,
310+
"parent": "0",
311+
"visible": 1,
312+
"name": f"{name if name else type(showables[0]).__name__} at {id(showables[0]):x}",
313+
}
314+
)
315+
self._update_state("actors")
316+
317+
self.actors[uuid] = tuple(new_actors)
318+
205319
return self
206320

321+
async def _fit(self):
322+
self.ren.ResetCamera()
323+
self.view.update()
324+
207325
def fit(self):
208326
"""
209327
Update view to fit all objects.
210328
"""
211329

212-
async def _show():
213-
self.ren.ResetCamera()
214-
self.view.update()
330+
self._run(self._fit())
215331

216-
self._run(_show())
332+
return self
333+
334+
async def _view(self, foc, pos, up):
335+
336+
cam = self.ren.GetActiveCamera()
337+
338+
cam.SetViewUp(*up)
339+
cam.SetFocalPoint(*foc)
340+
cam.SetPosition(*pos)
341+
342+
self.ren.ResetCamera()
343+
344+
self.view.update()
345+
346+
def iso(self):
347+
348+
self._run(self._view((0, 0, 0), (1, 1, 1), (0, 0, 1)))
217349

218350
return self
219351

220-
def clear(self, *shapes: Shape | vtkProp3D):
221-
"""
222-
Clear specified objects. If no arguments are passed, clears all objects.
223-
"""
352+
def up(self):
224353

225-
async def _clear():
354+
self._run(self._view((0, 0, 0), (0, 0, 1), (0, 1, 0)))
226355

227-
if len(shapes) == 0:
228-
self.ren.RemoveAllViewProps()
356+
return self
229357

230-
self.actors.clear()
231-
self.shapes.clear()
358+
pass
232359

233-
for s in shapes:
234-
if instance_of(s, ShapeLike):
235-
for a in self.shapes[s]:
236-
self.ren.RemoveActor(a)
360+
def front(self):
237361

238-
del self.shapes[s]
239-
else:
240-
self.actors.remove(s)
241-
self.ren.RemoveActor(s)
362+
self._run(self._view((0, 0, 0), (1, 0, 0), (0, 0, 1)))
242363

243-
self.view.update()
364+
return self
365+
366+
def side(self):
367+
368+
self._run(self._view((0, 0, 0), (0, 1, 0), (0, 0, 1)))
369+
370+
return self
371+
372+
async def _clear(self, shapes):
373+
374+
if len(shapes) == 0:
375+
self.ren.RemoveAllViewProps()
376+
377+
self.actors.clear()
378+
self.shapes.clear()
379+
380+
self.state.actors = []
381+
382+
for s in shapes:
383+
if instance_of(s, ShapeLike):
384+
for a in self.shapes[s]:
385+
self.ren.RemoveActor(a)
386+
387+
del self.shapes[s]
388+
else:
389+
for k, v in self.actors.items():
390+
if s in v:
391+
for el in self.actors.pop(k):
392+
self.ren.RemoveActor(el)
393+
394+
break
395+
396+
self._update_state("actors")
397+
self.view.update()
398+
399+
def clear(self, *shapes: Shape | vtkProp3D):
400+
"""
401+
Clear specified objects. If no arguments are passed, clears all objects.
402+
"""
244403

245404
# reset last, bc we don't want to keep track of what was removed
246405
self.last = None
247-
future = self._run(_clear())
406+
future = self._run(self._clear(shapes))
248407
future.result()
249408

250409
return self
251410

411+
async def _pop(self):
412+
413+
if self.active is None:
414+
self.active = self.actors[-1]["id"]
415+
416+
if self.active in self.actors:
417+
for act in self.actors[self.active]:
418+
self.ren.RemoveActor(act)
419+
420+
self.actors.pop(self.active)
421+
422+
# update corresponding state
423+
for i, el in enumerate(self.state.actors):
424+
if el["id"] == self.active:
425+
self.state.actors.pop(i)
426+
self._update_state("actors")
427+
break
428+
429+
self.active = None
430+
431+
else:
432+
return
433+
434+
self.view.update()
435+
252436
def pop(self):
253437
"""
254-
Clear the last showable.
438+
Clear the selected showable.
255439
"""
256440

257-
async def _pop():
441+
self._run(self._pop())
258442

259-
(shapes, axs, pts, props) = self.last
443+
return self
260444

261-
for s in shapes:
262-
for act in self.shapes.pop(s):
263-
self.ren.RemoveActor(act)
445+
def onVisibility(self, event):
264446

265-
for act in chain(axs, props):
266-
self.ren.RemoveActor(act)
267-
self.actors.remove(act)
447+
actors = self.actors[event["id"]]
268448

269-
if pts:
270-
self.ren.RemoveActor(*pts)
271-
self.actors.remove(*pts)
449+
for act in actors:
450+
act.SetVisibility(event["visible"])
272451

273-
self.view.update()
452+
self.view.update()
274453

275-
self._run(_pop())
454+
def onSelection(self, event):
276455

277-
return self
456+
self.active = event[0]

0 commit comments

Comments
 (0)