From 25cbb50d85c8cfbb73c34f07c304464c74f11efb Mon Sep 17 00:00:00 2001 From: AU Date: Sun, 23 Nov 2025 15:14:44 +0100 Subject: [PATCH 01/15] Add GUI elements to fig.Figure --- cadquery/fig.py | 281 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 228 insertions(+), 53 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index a68a43e59..f45372f54 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -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 @@ -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 @@ -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: list = [] + self.state.selected: None | str = 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(): @@ -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. """ @@ -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: @@ -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) @@ -202,76 +302,151 @@ 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 = [] + self._update_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: + self.actors.remove(s) + self.ren.RemoveActor(s) + + 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] From 808629e49e3b4ccb9b4237854ce53f0e4da343d3 Mon Sep 17 00:00:00 2001 From: AU Date: Sun, 23 Nov 2025 15:32:26 +0100 Subject: [PATCH 02/15] Mypy fix --- cadquery/fig.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index f45372f54..4b87ded30 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -43,7 +43,7 @@ class Figure: ren: vtkRenderer view: vtk_widgets.VtkRemoteView shapes: dict[ShapeLike, list[vtkProp3D]] - actors: dict[str, tuple[vtkProp3D]] + actors: dict[str, tuple[vtkProp3D, ...]] loop: AbstractEventLoop thread: Thread empty: bool @@ -117,8 +117,8 @@ def __init__(self, port: int = 18081): # state self.state = self.server.state - self.state.actors: list = [] - self.state.selected: None | str = None + self.state.actors = [] + self.state.selected = None # layout self.layout = SinglePageWithDrawerLayout(server, show_drawer=False) From 92d66ed6bea7b8805c6da6aef6a1a113116020bd Mon Sep 17 00:00:00 2001 From: AU Date: Sun, 23 Nov 2025 15:51:44 +0100 Subject: [PATCH 03/15] Update deps for CI --- environment.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/environment.yml b/environment.yml index f30b0fbf5..5c0cfb269 100644 --- a/environment.yml +++ b/environment.yml @@ -27,6 +27,8 @@ dependencies: - appdirs - trame - trame-vtk + - trame-components + - trame-vuetify - pip - pip: - --editable=. From 831e11ca0f099a3969b6118963e959c68ce820b0 Mon Sep 17 00:00:00 2001 From: AU Date: Sun, 23 Nov 2025 15:52:22 +0100 Subject: [PATCH 04/15] Update deps in meta.yaml --- conda/meta.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conda/meta.yaml b/conda/meta.yaml index 44003ec4a..40025a0ca 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -28,6 +28,8 @@ requirements: - typish - trame - trame-vtk + - trame-components + - trame-vuetify test: requires: From fca20617c76d6a254ef8ef761373bd62c4ddc77c Mon Sep 17 00:00:00 2001 From: AU Date: Sun, 23 Nov 2025 16:35:19 +0100 Subject: [PATCH 05/15] Quick fix of clear --- cadquery/fig.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index 4b87ded30..b0ae3a8a9 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -378,7 +378,6 @@ async def _clear(self, shapes): self.shapes.clear() self.state.actors = [] - self._update_state("actors") for s in shapes: if instance_of(s, ShapeLike): @@ -387,9 +386,14 @@ async def _clear(self, shapes): del self.shapes[s] else: - self.actors.remove(s) - self.ren.RemoveActor(s) + 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): From 7bb1eb05b1ef7ab9b89fd013e3dc349183a0822b Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Tue, 20 Jan 2026 08:08:20 +0100 Subject: [PATCH 06/15] State handling tweaks --- cadquery/fig.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index b0ae3a8a9..e13dd31ae 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -117,8 +117,8 @@ def __init__(self, port: int = 18081): # state self.state = self.server.state - self.state.actors = [] - self.state.selected = None + self.state.setdefault("selected", []) + self.state.setdefault("actors", []) # layout self.layout = SinglePageWithDrawerLayout(server, show_drawer=False) @@ -198,7 +198,8 @@ def __init__(self, port: int = 18081): with layout.drawer: self.tree = trame.GitTree( - sources=("actors",), + sources=("actors", []), + actives=("selected", []), visibility_change=(self.onVisibility, "[$event]"), actives_change=(self.onSelection, "[$event]"), ) @@ -378,6 +379,7 @@ async def _clear(self, shapes): self.shapes.clear() self.state.actors = [] + self.active = None for s in shapes: if instance_of(s, ShapeLike): @@ -453,4 +455,5 @@ def onVisibility(self, event): def onSelection(self, event): + self.state.selected = event self.active = event[0] From cbceb6d26bb1962fff556a621f16132532d207ed Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Wed, 21 Jan 2026 08:41:38 +0100 Subject: [PATCH 07/15] Fix ui sync --- cadquery/fig.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index 755d7d158..f30c25ddb 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -17,6 +17,7 @@ from trame.ui.vuetify3 import SinglePageWithDrawerLayout from . import Shape from .vis import style, Showable, ShapeLike, _split_showables +from .utils import BiDict from vtkmodules.vtkRenderingCore import ( vtkRenderer, @@ -106,8 +107,8 @@ def __init__(self, port: int = 18081): self.win = win self.ren = renderer - self.shapes = {} - self.actors = {} + self.shapes = BiDict() + self.actors = BiDict() self.active = None # server @@ -258,6 +259,9 @@ def show( Show objects. """ + # genreate an uuid + uuid = str(uuid1()) + # split objects shapes, vecs, locs, props = _split_showables(showables) @@ -273,7 +277,7 @@ def show( kwargs["markersize"] = 0 actors = style(s, **kwargs) - self.shapes[s] = actors + self.shapes[s] = uuid for actor in actors: self.ren.AddActor(actor) @@ -304,7 +308,6 @@ async def _show(): self.empty = False # update actors - uuid = str(uuid1()) self.state.actors.append( { "id": uuid, @@ -382,19 +385,30 @@ async def _clear(self, shapes): self.active = None for s in shapes: + # handle shapes if instance_of(s, ShapeLike): - for a in self.shapes[s]: + uuid = self.shapes[s] + for a in self.actors.pop(uuid): self.ren.RemoveActor(a) del self.shapes[s] + + # handle other actors else: - for k, v in self.actors.items(): - if s in v: - for el in self.actors.pop(k): + for uuid, acts in self.actors.items(): + if s in acts: + for el in self.actors.pop(uuid): self.ren.RemoveActor(el) break + # remove the id==k row from actors + for ix, el in enumerate(self.state.actors): + if el["id"] == uuid: + break + + self.state.actors.pop(ix) + self._update_state("actors") self.view.update() From 29167c08ec378eec95b90b53ea4f92093ba8795e Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Wed, 21 Jan 2026 18:49:05 +0100 Subject: [PATCH 08/15] mypy fix --- cadquery/fig.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index f30c25ddb..b34e64779 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -17,7 +17,6 @@ from trame.ui.vuetify3 import SinglePageWithDrawerLayout from . import Shape from .vis import style, Showable, ShapeLike, _split_showables -from .utils import BiDict from vtkmodules.vtkRenderingCore import ( vtkRenderer, @@ -43,7 +42,7 @@ class Figure: win: vtkRenderWindow ren: vtkRenderer view: vtk_widgets.VtkRemoteView - shapes: dict[ShapeLike, list[vtkProp3D]] + shapes: dict[ShapeLike, str] actors: dict[str, tuple[vtkProp3D, ...]] loop: AbstractEventLoop thread: Thread @@ -107,8 +106,8 @@ def __init__(self, port: int = 18081): self.win = win self.ren = renderer - self.shapes = BiDict() - self.actors = BiDict() + self.shapes = {} + self.actors = {} self.active = None # server From 6b5f4f3aa8532570c05c8929a476896bcd57cf97 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:12:49 +0100 Subject: [PATCH 09/15] Bidict fixes --- cadquery/fig.py | 37 ++++++++++++++++++++++--------------- cadquery/utils.py | 19 +++++++++++++++++++ 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index b34e64779..cd0e8c915 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -31,7 +31,7 @@ from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTrackballCamera -from .utils import instance_of +from .utils import instance_of, BiDict FULL_SCREEN = "position:absolute; left:0; top:0; width:100vw; height:100vh;" @@ -42,8 +42,8 @@ class Figure: win: vtkRenderWindow ren: vtkRenderer view: vtk_widgets.VtkRemoteView - shapes: dict[ShapeLike, str] - actors: dict[str, tuple[vtkProp3D, ...]] + shapes: BiDict[str, ShapeLike] + actors: BiDict[str, tuple[vtkProp3D, ...]] loop: AbstractEventLoop thread: Thread empty: bool @@ -106,8 +106,8 @@ def __init__(self, port: int = 18081): self.win = win self.ren = renderer - self.shapes = {} - self.actors = {} + self.shapes = BiDict() + self.actors = BiDict() self.active = None # server @@ -276,7 +276,7 @@ def show( kwargs["markersize"] = 0 actors = style(s, **kwargs) - self.shapes[s] = uuid + self.shapes[uuid] = s for actor in actors: self.ren.AddActor(actor) @@ -386,11 +386,14 @@ async def _clear(self, shapes): for s in shapes: # handle shapes if instance_of(s, ShapeLike): - uuid = self.shapes[s] - for a in self.actors.pop(uuid): - self.ren.RemoveActor(a) + uuids = tuple(self.shapes.inv[s]) + for uuid in uuids: + for a in self.actors.pop(uuid): + self.ren.RemoveActor(a) - del self.shapes[s] + del self.shapes[ + uuid + ] # NB this will remove all uuids pointing to the shape # handle other actors else: @@ -401,12 +404,13 @@ async def _clear(self, shapes): break - # remove the id==k row from actors - for ix, el in enumerate(self.state.actors): - if el["id"] == uuid: - break + # remove the id==k rows from actors + new_state = [] + for el in self.state.actors: + if el["id"] not in uuids: + new_state.append(el) - self.state.actors.pop(ix) + self.state.actors = new_state self._update_state("actors") self.view.update() @@ -434,6 +438,9 @@ async def _pop(self): self.actors.pop(self.active) + # update shapes if needed + self.shapes.pop(self.active) + # update corresponding state for i, el in enumerate(self.state.actors): if el["id"] == self.active: diff --git a/cadquery/utils.py b/cadquery/utils.py index 150320064..957dc1e21 100644 --- a/cadquery/utils.py +++ b/cadquery/utils.py @@ -118,6 +118,25 @@ def inv(self) -> dict[V, list[K]]: return self._inv + def clear(self): + + super().clear() + self._inv.clear() + + def __delitem__(self, k: K): + + v = self.data.pop(k) + + # if needed in one-many cases + if v in self._inv: + # remove the inverse mapping + inv = self._inv[v] + inv.remove(k) + + # if needed remove the item completely + if not inv: + del self._inv[v] + def instance_of(obj: object, *args: object) -> bool: """ From 3b77784723a197804f4c71e2ef1dd2f5363e5cd5 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Sun, 25 Jan 2026 12:22:11 +0100 Subject: [PATCH 10/15] Fix test failure --- cadquery/fig.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cadquery/fig.py b/cadquery/fig.py index cd0e8c915..790dc91ce 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -402,6 +402,9 @@ async def _clear(self, shapes): for el in self.actors.pop(uuid): self.ren.RemoveActor(el) + # store the uuid for state update + uuids = [uuid] + break # remove the id==k rows from actors From 446308bd4527a7be620ee6b1151f03cfb2a2bfad Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Sun, 25 Jan 2026 19:37:41 +0100 Subject: [PATCH 11/15] Update tests --- tests/test_fig.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/test_fig.py b/tests/test_fig.py index ffa09264c..701ace5d2 100644 --- a/tests/test_fig.py +++ b/tests/test_fig.py @@ -27,7 +27,7 @@ def test_fig(fig): loc = Location() act = vtkAxesActor() - showables = (s, wp, assy, sk, ctrl_pts, v, loc, act) + showables = (s, s.copy(), wp, assy, sk, ctrl_pts, v, loc, act) # individual showables fig.show(*showables) @@ -35,12 +35,19 @@ def test_fig(fig): # fit fig.fit() + # views + fig.iso() + fig.up() + fig.front() + fig.side() + # clear fig.clear() # clear with an arg for el in (s, wp, assy, sk, ctrl_pts): - fig.show(el).clear(el) + fig.clear().show(*showables) + fig.clear(el) # lists of showables fig.show(s.Edges()).show([Vector(), Vector(0, 1)]) From 90d1b9339057d78e0468b54c739e05c21b9ae9c7 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:52:09 +0100 Subject: [PATCH 12/15] Fix pop --- cadquery/fig.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index 790dc91ce..5325a61e6 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -293,6 +293,10 @@ def show( new_actors.append(*pts) + # if nothing to show return early + if not new_actors: + return self + # store to enable pop self.last = (shapes, axs, pts if vecs else None, props) @@ -433,7 +437,7 @@ def clear(self, *shapes: Shape | vtkProp3D): async def _pop(self): if self.active is None: - self.active = self.actors[-1]["id"] + self.active = self.state.actors[-1]["id"] if self.active in self.actors: for act in self.actors[self.active]: @@ -453,9 +457,6 @@ async def _pop(self): self.active = None - else: - return - self.view.update() def pop(self): From ec12f7038abc567f923cad0b57f9ed05a5e8af16 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Tue, 27 Jan 2026 08:47:05 +0100 Subject: [PATCH 13/15] Additional annotations --- cadquery/fig.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index 5325a61e6..8feff024e 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -47,6 +47,7 @@ class Figure: loop: AbstractEventLoop thread: Thread empty: bool + active: Optional[str] last: Optional[ tuple[ list[ShapeLike], list[vtkProp3D], Optional[list[vtkProp3D]], list[vtkProp3D] @@ -468,7 +469,7 @@ def pop(self): return self - def onVisibility(self, event): + def onVisibility(self, event: dict): actors = self.actors[event["id"]] @@ -477,7 +478,7 @@ def onVisibility(self, event): self.view.update() - def onSelection(self, event): + def onSelection(self, event: list[str]): self.state.selected = event self.active = event[0] From d98b75af185f6112ca53f0f0a65fad2c33544665 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Tue, 27 Jan 2026 08:47:19 +0100 Subject: [PATCH 14/15] Better coverage --- tests/test_fig.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/test_fig.py b/tests/test_fig.py index 701ace5d2..cacee20c4 100644 --- a/tests/test_fig.py +++ b/tests/test_fig.py @@ -45,10 +45,19 @@ def test_fig(fig): fig.clear() # clear with an arg + for showable in showables: + fig.show(showable) + for el in (s, wp, assy, sk, ctrl_pts): - fig.clear().show(*showables) fig.clear(el) + # show multiple showables at once + fig.clear() + fig.show(*showables) + + # more than one Solid showable -> more than 2 actors + assert len(list(fig.actors.values())[-1]) > 2 + # lists of showables fig.show(s.Edges()).show([Vector(), Vector(0, 1)]) @@ -63,3 +72,9 @@ def test_fig(fig): # test singleton behavior of fig fig2 = Figure() assert fig is fig2 + + # test onSelection + fig.onVisibility(fig.state.actors[0]) + + # test onVisbility + fig.onSelection([fig.state.actors[0]]) From e99d2e7620694a7f9356fdfaa02a288989ebb3bc Mon Sep 17 00:00:00 2001 From: AU Date: Thu, 29 Jan 2026 15:29:20 +0100 Subject: [PATCH 15/15] Update setup.py too --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 2674ee82f..2bcbf98fd 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,8 @@ "path", "trame", "trame-vtk", + "trame-components", + "trame-vuetify", "pyparsing>=3.0.0", ]