Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
285 changes: 232 additions & 53 deletions cadquery/fig.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
from threading import Thread
from itertools import chain
from webbrowser import open_new_tab
from uuid import uuid1

from typish import instance_of

from trame.app import get_server
from trame.app.core import Server
from trame.widgets import html, vtk as vtk_widgets, client
from trame.ui.html import DivLayout

from trame.widgets import vtk as vtk_widgets, client, trame, vuetify3 as v3
from trame.ui.vuetify3 import SinglePageWithDrawerLayout
from . import Shape
from .vis import style, Showable, ShapeLike, _split_showables

Expand All @@ -43,7 +43,7 @@ class Figure:
ren: vtkRenderer
view: vtk_widgets.VtkRemoteView
shapes: dict[ShapeLike, list[vtkProp3D]]
actors: list[vtkProp3D]
actors: dict[str, tuple[vtkProp3D, ...]]
loop: AbstractEventLoop
thread: Thread
empty: bool
Expand Down Expand Up @@ -107,24 +107,104 @@ def __init__(self, port: int = 18081):
self.ren = renderer

self.shapes = {}
self.actors = []
self.actors = {}
self.active = None

# server
server = get_server("CQ-server")
server.client_type = "vue3"
server = get_server("CQ-server", client_type="vue3")
self.server = server

# state
self.state = self.server.state

self.state.actors = []
self.state.selected = None

# layout
with DivLayout(server):
self.layout = SinglePageWithDrawerLayout(server, show_drawer=False)
with self.layout as layout:
client.Style("body { margin: 0; }")

with html.Div(style=FULL_SCREEN):
self.view = vtk_widgets.VtkRemoteView(
win, interactive_ratio=1, interactive_quality=100
layout.title.set_text("CQ viewer")
layout.footer.hide()

with layout.toolbar:

BSTYLE = "display: block;"

v3.VBtn(
click=lambda: self._fit(),
flat=True,
density="compact",
icon="mdi-crop-free",
style=BSTYLE,
)

v3.VBtn(
click=lambda: self._view((0, 0, 0), (1, 1, 1), (0, 0, 1)),
flat=True,
density="compact",
icon="mdi-axis-arrow",
style=BSTYLE,
)

v3.VBtn(
click=lambda: self._view((0, 0, 0), (1, 0, 0), (0, 0, 1)),
flat=True,
density="compact",
icon="mdi-axis-x-arrow",
style=BSTYLE,
)

v3.VBtn(
click=lambda: self._view((0, 0, 0), (0, 1, 0), (0, 0, 1)),
flat=True,
density="compact",
icon="mdi-axis-y-arrow",
style=BSTYLE,
)

v3.VBtn(
click=lambda: self._view((0, 0, 0), (0, 0, 1), (0, 1, 0)),
flat=True,
density="compact",
icon="mdi-axis-z-arrow",
style=BSTYLE,
)

v3.VBtn(
click=lambda: self._pop(),
flat=True,
density="compact",
icon="mdi-file-document-remove-outline",
style=BSTYLE,
)

v3.VBtn(
click=lambda: self._clear([]),
flat=True,
density="compact",
icon="mdi-delete-outline",
style=BSTYLE,
)

with layout.content:
with v3.VContainer(
fluid=True, classes="pa-0 fill-height",
):
self.view = vtk_widgets.VtkRemoteView(
win, interactive_ratio=1, interactive_quality=100
)

with layout.drawer:
self.tree = trame.GitTree(
sources=("actors",),
visibility_change=(self.onVisibility, "[$event]"),
actives_change=(self.onSelection, "[$event]"),
)

server.state.flush()

self.server = server
self.loop = new_event_loop()

def _run_loop():
Expand Down Expand Up @@ -159,7 +239,20 @@ def _run(self, coro) -> Future:

return run_coroutine_threadsafe(coro, self.loop)

def show(self, *showables: Showable | vtkProp3D | list[vtkProp3D], **kwargs):
def _update_state(self, name: str):
async def _():

self.state.dirty(name)
self.state.flush()

self._run(_())

def show(
self,
*showables: Showable | vtkProp3D | list[vtkProp3D],
name: Optional[str] = None,
**kwargs,
):
"""
Show objects.
"""
Expand All @@ -170,6 +263,9 @@ def show(self, *showables: Showable | vtkProp3D | list[vtkProp3D], **kwargs):
pts = style(vecs, **kwargs)
axs = style(locs, **kwargs)

# to be added to state
new_actors = []

for s in shapes:
# do not show markers by default
if "markersize" not in kwargs:
Expand All @@ -181,14 +277,18 @@ def show(self, *showables: Showable | vtkProp3D | list[vtkProp3D], **kwargs):
for actor in actors:
self.ren.AddActor(actor)

new_actors.extend(actors)

for prop in chain(props, axs):
self.actors.append(prop)
self.ren.AddActor(prop)

new_actors.append(prop)

if vecs:
self.actors.append(*pts)
self.ren.AddActor(*pts)

new_actors.append(*pts)

# store to enable pop
self.last = (shapes, axs, pts if vecs else None, props)

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

# update actors
uuid = str(uuid1())
self.state.actors.append(
{
"id": uuid,
"parent": "0",
"visible": 1,
"name": f"{name if name else type(showables[0]).__name__} at {id(showables[0]):x}",
}
)
self._update_state("actors")

self.actors[uuid] = tuple(new_actors)

return self

async def _fit(self):
self.ren.ResetCamera()
self.view.update()

def fit(self):
"""
Update view to fit all objects.
"""

async def _show():
self.ren.ResetCamera()
self.view.update()
self._run(self._fit())

self._run(_show())
return self

async def _view(self, foc, pos, up):

cam = self.ren.GetActiveCamera()

cam.SetViewUp(*up)
cam.SetFocalPoint(*foc)
cam.SetPosition(*pos)

self.ren.ResetCamera()

self.view.update()

def iso(self):

self._run(self._view((0, 0, 0), (1, 1, 1), (0, 0, 1)))

return self

def clear(self, *shapes: Shape | vtkProp3D):
"""
Clear specified objects. If no arguments are passed, clears all objects.
"""
def up(self):

async def _clear():
self._run(self._view((0, 0, 0), (0, 0, 1), (0, 1, 0)))

if len(shapes) == 0:
self.ren.RemoveAllViewProps()
return self

self.actors.clear()
self.shapes.clear()
pass

for s in shapes:
if instance_of(s, ShapeLike):
for a in self.shapes[s]:
self.ren.RemoveActor(a)
def front(self):

del self.shapes[s]
else:
self.actors.remove(s)
self.ren.RemoveActor(s)
self._run(self._view((0, 0, 0), (1, 0, 0), (0, 0, 1)))

self.view.update()
return self

def side(self):

self._run(self._view((0, 0, 0), (0, 1, 0), (0, 0, 1)))

return self

async def _clear(self, shapes):

if len(shapes) == 0:
self.ren.RemoveAllViewProps()

self.actors.clear()
self.shapes.clear()

self.state.actors = []

for s in shapes:
if instance_of(s, ShapeLike):
for a in self.shapes[s]:
self.ren.RemoveActor(a)

del self.shapes[s]
else:
for k, v in self.actors.items():
if s in v:
for el in self.actors.pop(k):
self.ren.RemoveActor(el)

break

self._update_state("actors")
self.view.update()

def clear(self, *shapes: Shape | vtkProp3D):
"""
Clear specified objects. If no arguments are passed, clears all objects.
"""

# reset last, bc we don't want to keep track of what was removed
self.last = None
future = self._run(_clear())
future = self._run(self._clear(shapes))
future.result()

return self

async def _pop(self):

if self.active is None:
self.active = self.actors[-1]["id"]

if self.active in self.actors:
for act in self.actors[self.active]:
self.ren.RemoveActor(act)

self.actors.pop(self.active)

# update corresponding state
for i, el in enumerate(self.state.actors):
if el["id"] == self.active:
self.state.actors.pop(i)
self._update_state("actors")
break

self.active = None

else:
return

self.view.update()

def pop(self):
"""
Clear the last showable.
Clear the selected showable.
"""

async def _pop():
self._run(self._pop())

(shapes, axs, pts, props) = self.last
return self

for s in shapes:
for act in self.shapes.pop(s):
self.ren.RemoveActor(act)
def onVisibility(self, event):

for act in chain(axs, props):
self.ren.RemoveActor(act)
self.actors.remove(act)
actors = self.actors[event["id"]]

if pts:
self.ren.RemoveActor(*pts)
self.actors.remove(*pts)
for act in actors:
act.SetVisibility(event["visible"])

self.view.update()
self.view.update()

self._run(_pop())
def onSelection(self, event):

return self
self.active = event[0]
2 changes: 2 additions & 0 deletions conda/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ requirements:
- typish
- trame
- trame-vtk
- trame-components
- trame-vuetify

test:
requires:
Expand Down
Loading