From 47e039997100033050a267bbf97a489c5ff800c0 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Mon, 10 Mar 2025 08:09:55 +0100 Subject: [PATCH 01/24] Adding fig --- cadquery/fig.py | 90 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 cadquery/fig.py diff --git a/cadquery/fig.py b/cadquery/fig.py new file mode 100644 index 000000000..0c7843a7f --- /dev/null +++ b/cadquery/fig.py @@ -0,0 +1,90 @@ +from trame.app import get_server, Server +from trame.widgets import html, vtk as vtk_widgets, client +from trame.ui.html import DivLayout + +from cadquery import Shape +from cadquery.vis import style + +from vtkmodules.vtkRenderingCore import ( + vtkRenderer, + vtkRenderWindow, + vtkRenderWindowInteractor, + vtkActor, +) + +FULL_SCREEN = "position:absolute; left:0; top:0; width:100vw; height:100vh;" + + +class Figure: + + server: Server + win: vtkRenderWindow + ren: vtkRenderer + shapes: dict[Shape, tuple[vtkActor, ...]] + actors: list[vtkActor] + + def __init__(self, port: int): + + # vtk boilerplate + renderer = vtkRenderer() + win = vtkRenderWindow() + win.AddRenderer(renderer) + win.OffScreenRenderingOn() + + inter = vtkRenderWindowInteractor() + inter.SetRenderWindow(win) + inter.GetInteractorStyle().SetCurrentStyleToTrackballCamera() + + self.win = win + self.ren = renderer + + self.shapes = {} + self.actors = [] + + # server + server = get_server(123) + server.client_type = "vue3" + + # layout + with DivLayout(server): + client.Style("body { margin: 0; }") + + with html.Div(style=FULL_SCREEN): + vtk_widgets.VtkRemoteView( + win, interactive_ratio=1, interactive_quality=100 + ) + + server.state.flush() + server.start(thread=True, exec_mode="task", port=port, open_browser=True) + + self.server = server + + def show(self, s: Shape | vtkActor, *args, **kwargs): + + if isinstance(s, Shape): + actors = style(s, *args, **kwargs) + self.shapes[s] = actors + + for a in actors: + self.ren.AddActor(a) + else: + self.actors.append(s) + self.ren.AddActor(s) + + self.ren.ResetCamera() + + def hide(self, s: Shape | vtkActor): + + if isinstance(s, Shape): + actors = self.shapes[s] + + for a in actors: + self.ren.RemoveActor(a) + + del self.shapes[s] + + else: + self.actors.remove(s) + self.ren.RemoveActor(s) + + self.ren.ResetCamera() From a918517c3d99d4fec910c1f641df70f163a850ee Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Tue, 11 Mar 2025 08:10:08 +0100 Subject: [PATCH 02/24] Mypy fixes --- cadquery/fig.py | 69 +++++++++++++++++++++++++++-------- cadquery/occ_impl/assembly.py | 11 +++--- cadquery/vis.py | 64 +++++++++++++++++--------------- mypy.ini | 3 ++ 4 files changed, 96 insertions(+), 51 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index 0c7843a7f..759777e17 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -1,3 +1,7 @@ +import asyncio + +from typing import Any, cast + from trame.app import get_server, Server from trame.widgets import html, vtk as vtk_widgets, client from trame.ui.html import DivLayout @@ -10,8 +14,15 @@ vtkRenderWindow, vtkRenderWindowInteractor, vtkActor, + vtkProp3D, ) + +from vtkmodules.vtkInteractionWidgets import vtkOrientationMarkerWidget +from vtkmodules.vtkRenderingAnnotation import vtkAxesActor + +from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTrackballCamera + FULL_SCREEN = "position:absolute; left:0; top:0; width:100vw; height:100vh;" @@ -20,21 +31,44 @@ class Figure: server: Server win: vtkRenderWindow ren: vtkRenderer - shapes: dict[Shape, tuple[vtkActor, ...]] - actors: list[vtkActor] + view: vtk_widgets.VtkRemoteView + shapes: dict[Shape, vtkProp3D] + actors: list[vtkProp3D] + loop: Any def __init__(self, port: int): + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + # vtk boilerplate renderer = vtkRenderer() win = vtkRenderWindow() + w, h = win.GetScreenSize() + win.SetSize(w, h) win.AddRenderer(renderer) win.OffScreenRenderingOn() inter = vtkRenderWindowInteractor() + inter.SetInteractorStyle(vtkInteractorStyleTrackballCamera()) inter.SetRenderWindow(win) - inter.GetInteractorStyle().SetCurrentStyleToTrackballCamera() + # axes + axes = vtkAxesActor() + axes.SetDragable(0) + + orient_widget = vtkOrientationMarkerWidget() + + orient_widget.SetOrientationMarker(axes) + orient_widget.SetViewport(0.9, 0.0, 1.0, 0.2) + orient_widget.SetZoom(1.1) + orient_widget.SetInteractor(inter) + orient_widget.SetCurrentRenderer(renderer) + orient_widget.EnabledOn() + orient_widget.InteractiveOff() + + self.axes = axes + self.orient_widget = orient_widget self.win = win self.ren = renderer @@ -50,7 +84,7 @@ def __init__(self, port: int): client.Style("body { margin: 0; }") with html.Div(style=FULL_SCREEN): - vtk_widgets.VtkRemoteView( + self.view = vtk_widgets.VtkRemoteView( win, interactive_ratio=1, interactive_quality=100 ) @@ -59,27 +93,29 @@ def __init__(self, port: int): self.server = server - def show(self, s: Shape | vtkActor, *args, **kwargs): + def show(self, s: Shape | vtkActor | list[vtkProp3D], *args, **kwargs): if isinstance(s, Shape): - actors = style(s, *args, **kwargs) - self.shapes[s] = actors - - for a in actors: - self.ren.AddActor(a) - else: + actor = style(s, *args, **kwargs)[0] + self.shapes[s] = actor + self.ren.AddActor(actor) + elif isinstance(s, vtkActor): self.actors.append(s) self.ren.AddActor(s) + else: + self.actors.extend(s) + + for el in s: + self.ren.AddActor(el) self.ren.ResetCamera() + self.view.update() - def hide(self, s: Shape | vtkActor): + def clear(self, s: Shape | vtkActor): if isinstance(s, Shape): - actors = self.shapes[s] - - for a in actors: - self.ren.RemoveActor(a) + actor = self.shapes[s] + self.ren.RemoveActor(actor) del self.shapes[s] @@ -88,3 +124,4 @@ def hide(self, s: Shape | vtkActor): self.ren.RemoveActor(s) self.ren.ResetCamera() + self.view.update() diff --git a/cadquery/occ_impl/assembly.py b/cadquery/occ_impl/assembly.py index 24a9518fa..2cdb0e3a7 100644 --- a/cadquery/occ_impl/assembly.py +++ b/cadquery/occ_impl/assembly.py @@ -31,7 +31,7 @@ vtkActor, vtkPolyDataMapper as vtkMapper, vtkRenderer, - vtkAssembly, + vtkProp3D, ) from vtkmodules.vtkFiltersExtraction import vtkExtractCellsByType @@ -295,9 +295,9 @@ def toVTKAssy( linewidth: float = 2, tolerance: float = 1e-3, angularTolerance: float = 0.1, -) -> vtkAssembly: +) -> List[vtkProp3D]: - rv = vtkAssembly() + rv: List[vtkProp3D] = [] for shape, _, loc, col_ in assy: @@ -338,7 +338,7 @@ def toVTKAssy( actor.GetProperty().SetColor(*col[:3]) actor.GetProperty().SetOpacity(col[3]) - rv.AddPart(actor) + rv.append(actor) mapper = vtkMapper() mapper.AddInputDataObject(data_edges) @@ -350,9 +350,8 @@ def toVTKAssy( actor.GetProperty().SetLineWidth(linewidth) actor.SetVisibility(edges) actor.GetProperty().SetColor(*edgecolor[:3]) - actor.GetProperty().SetLineWidth(edgecolor[3]) - rv.AddPart(actor) + rv.append(actor) return rv diff --git a/cadquery/vis.py b/cadquery/vis.py index 5fbedb12f..c6c1f42d5 100644 --- a/cadquery/vis.py +++ b/cadquery/vis.py @@ -145,12 +145,12 @@ def _to_vtk_pts( return rv -def _to_vtk_axs(locs: List[Location], scale: float = 0.1) -> vtkAssembly: +def _to_vtk_axs(locs: List[Location], scale: float = 0.1) -> List[vtkProp3D]: """ Convert Locations to vtkActor. """ - rv = vtkAssembly() + rv: List[vtkProp3D] = [] for l in locs: trans, rot = _loc2vtk(l) @@ -161,7 +161,7 @@ def _to_vtk_axs(locs: List[Location], scale: float = 0.1) -> vtkAssembly: ax.SetOrientation(*rot) ax.SetScale(scale) - rv.AddPart(ax) + rv.append(ax) return rv @@ -174,7 +174,7 @@ def _to_vtk_shapes( linewidth: float = 2, alpha: float = 1, tolerance: float = 1e-3, -) -> vtkAssembly: +) -> List[vtkProp3D]: """ Convert Shapes to vtkAssembly. """ @@ -279,21 +279,18 @@ def ctrlPts( return rv -def _iterate_actors(obj: Union[vtkProp3D, vtkActor, vtkAssembly]) -> Iterable[vtkActor]: +def _iterate_actors( + obj: Union[vtkProp3D, vtkActor, List[vtkProp3D]] +) -> Iterable[vtkActor]: """ Iterate over vtkActors, other props are ignored. """ if isinstance(obj, vtkActor): yield obj - elif isinstance(obj, vtkAssembly): - coll = vtkPropCollection() - obj.GetActors(coll) - - coll.InitTraversal() - for i in range(0, coll.GetNumberOfItems()): - prop = coll.GetNextProp() - if isinstance(prop, vtkActor): - yield prop + elif isinstance(obj, list): + for el in obj: + if isinstance(el, vtkActor): + yield el def style( @@ -313,7 +310,7 @@ def style( meshcolor: str = "lightgrey", vertexcolor: str = "cyan", **kwargs, -) -> Union[vtkActor, vtkAssembly]: +) -> List[vtkProp3D]: """ Apply styling to CQ objects. To be used in conjunction with show. """ @@ -343,7 +340,7 @@ def _apply_color(actor): shapes, vecs, locs, actors = _split_showables([obj,]) # convert to a prop - rv: Union[vtkActor, vtkAssembly] + rv: Union[vtkActor, List[vtkProp3D]] if shapes: rv = _to_vtk_shapes( @@ -361,19 +358,22 @@ def _apply_color(actor): _apply_style(a) elif vecs: - rv = _to_vtk_pts(vecs) - _apply_style(rv) - _apply_color(rv) + tmp = _to_vtk_pts(vecs) + _apply_style(tmp) + _apply_color(tmp) + rv = [tmp] + elif locs: rv = _to_vtk_axs(locs, scale=scale) + else: - rv = vtkAssembly() + rv = [] for p in actors: for a in _iterate_actors(p): _apply_style(a) _apply_color(a) - rv.AddPart(a) + rv.append(a) return rv @@ -400,7 +400,7 @@ def show( gradient: bool = True, xpos: Union[int, float] = 0, ypos: Union[int, float] = 0, -): +) -> vtkRenderWindow: """ Show CQ objects using VTK. This functions optionally allows to make screenshots. """ @@ -417,7 +417,9 @@ def show( # assy+renderer renderer = vtkRenderer() - renderer.AddActor(toVTKAssy(assy, tolerance=tolerance)) + + for act in toVTKAssy(assy, tolerance=tolerance): + renderer.AddActor(act) # VTK window boilerplate win = vtkRenderWindow() @@ -455,12 +457,12 @@ def show( # construct an axes indicator axes = vtkAxesActor() axes.SetDragable(0) + axes.SetAxisLabels(0) + # tp = axes.GetXAxisCaptionActor2D().GetCaptionTextProperty() + # tp.SetColor(0, 0, 0) - tp = axes.GetXAxisCaptionActor2D().GetCaptionTextProperty() - tp.SetColor(0, 0, 0) - - axes.GetYAxisCaptionActor2D().GetCaptionTextProperty().ShallowCopy(tp) - axes.GetZAxisCaptionActor2D().GetCaptionTextProperty().ShallowCopy(tp) + # axes.GetYAxisCaptionActor2D().GetCaptionTextProperty().ShallowCopy(tp) + # axes.GetZAxisCaptionActor2D().GetCaptionTextProperty().ShallowCopy(tp) # add to an orientation widget if trihedron: @@ -483,7 +485,9 @@ def show( # add pts and locs renderer.AddActor(pts) - renderer.AddActor(axs) + + for ax in axs: + renderer.AddActor(ax) # add other vtk actors for p in props: @@ -538,6 +542,8 @@ def show( if interact: inter.Start() + return win + # alias show_object = show diff --git a/mypy.ini b/mypy.ini index 97bbf2b5d..7bc958faf 100644 --- a/mypy.ini +++ b/mypy.ini @@ -37,3 +37,6 @@ ignore_missing_imports = True [mypy-casadi.*] ignore_missing_imports = True +[mypy-trame.*] +ignore_missing_imports = True + From 3b0a8709021a1eddb116b6ecd0cbd28bae5f3c90 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Tue, 11 Mar 2025 19:28:03 +0100 Subject: [PATCH 03/24] Fix tests --- tests/test_vis.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/tests/test_vis.py b/tests/test_vis.py index 513ec2671..e22ff2eba 100644 --- a/tests/test_vis.py +++ b/tests/test_vis.py @@ -10,6 +10,7 @@ vtkWindowToImageFilter, vtkActor, vtkAssembly, + vtkProp3D, ) from vtkmodules.vtkRenderingAnnotation import vtkAnnotatedCubeActor from vtkmodules.vtkIOImage import vtkPNGWriter @@ -17,6 +18,9 @@ from pytest import fixture, raises from path import Path +from typish import instance_of +from typing import List + @fixture(scope="module") def tmpdir(tmp_path_factory): @@ -178,39 +182,39 @@ def test_style(wp, assy): # Shape act = style(t, color="red", alpha=0.5, tubes=True, spheres=True) - assert isinstance(act, (vtkActor, vtkAssembly)) + assert instance_of(act, List[vtkProp3D]) # Assy act = style(assy, color="red", alpha=0.5, tubes=True, spheres=True) - assert isinstance(act, (vtkActor, vtkAssembly)) + assert instance_of(act, List[vtkProp3D]) # Workplane act = style(wp, color="red", alpha=0.5, tubes=True, spheres=True) - assert isinstance(act, (vtkActor, vtkAssembly)) + assert instance_of(act, List[vtkProp3D]) # Shape act = style(e) - assert isinstance(act, (vtkActor, vtkAssembly)) + assert instance_of(act, List[vtkProp3D]) # Sketch act = style(Sketch().circle(1)) - assert isinstance(act, (vtkActor, vtkAssembly)) + assert instance_of(act, List[vtkProp3D]) # list[Vector] act = style(pts) - assert isinstance(act, (vtkActor, vtkAssembly)) + assert instance_of(act, List[vtkProp3D]) # list[Location] act = style(locs) - assert isinstance(act, (vtkActor, vtkAssembly)) + assert instance_of(act, List[vtkProp3D]) # vtkAssembly act = style(style(t)) - assert isinstance(act, (vtkActor, vtkAssembly)) + assert instance_of(act, List[vtkProp3D]) # vtkActor act = style(ctrlPts(e.toNURBS())) - assert isinstance(act, (vtkActor, vtkAssembly)) + assert instance_of(act, List[vtkProp3D]) def test_camera_position(wp, patch_vtk): From fef77aa023a426e2cdb84511e3e8318c9e05ae7d Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Tue, 11 Mar 2025 19:29:43 +0100 Subject: [PATCH 04/24] Run the asyncio loop in a thread --- cadquery/fig.py | 106 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 74 insertions(+), 32 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index 759777e17..46423a947 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -1,6 +1,10 @@ -import asyncio - -from typing import Any, cast +from asyncio import ( + new_event_loop, + set_event_loop, + run_coroutine_threadsafe, + AbstractEventLoop, +) +from threading import Thread from trame.app import get_server, Server from trame.widgets import html, vtk as vtk_widgets, client @@ -32,14 +36,15 @@ class Figure: win: vtkRenderWindow ren: vtkRenderer view: vtk_widgets.VtkRemoteView - shapes: dict[Shape, vtkProp3D] + shapes: dict[Shape, list[vtkProp3D]] actors: list[vtkProp3D] - loop: Any + loop: AbstractEventLoop + thread: Thread def __init__(self, port: int): - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(self.loop) + self.loop = new_event_loop() + set_event_loop(self.loop) # vtk boilerplate renderer = vtkRenderer() @@ -76,7 +81,7 @@ def __init__(self, port: int): self.actors = [] # server - server = get_server(123) + server = get_server("CQ") server.client_type = "vue3" # layout @@ -89,39 +94,76 @@ def __init__(self, port: int): ) server.state.flush() - server.start(thread=True, exec_mode="task", port=port, open_browser=True) + coro = server.start( + thread=True, exec_mode="coroutine", port=port, open_browser=True + ) self.server = server + self.loop = new_event_loop() + + def _run(): + set_event_loop(self.loop) + self.loop.run_forever() + + self.thread = Thread(target=_run, daemon=True) + self.thread.start() + + run_coroutine_threadsafe(coro, self.loop) + + def _run(self, coro): + + run_coroutine_threadsafe(coro, self.loop) def show(self, s: Shape | vtkActor | list[vtkProp3D], *args, **kwargs): + async def _show(): + + if isinstance(s, Shape): + # do not show markers by default + if "markersize" not in kwargs: + kwargs["markersize"] = 0 + + actors = style(s, *args, **kwargs) + self.shapes[s] = actors + + for actor in actors: + self.ren.AddActor(actor) + + elif isinstance(s, vtkActor): + self.actors.append(s) + self.ren.AddActor(s) + else: + self.actors.extend(s) + + for el in s: + self.ren.AddActor(el) + + self.ren.ResetCamera() + self.view.update() - if isinstance(s, Shape): - actor = style(s, *args, **kwargs)[0] - self.shapes[s] = actor - self.ren.AddActor(actor) - elif isinstance(s, vtkActor): - self.actors.append(s) - self.ren.AddActor(s) - else: - self.actors.extend(s) + self._run(_show()) - for el in s: - self.ren.AddActor(el) + def clear(self, *shapes: Shape | vtkActor): + async def _clear(): - self.ren.ResetCamera() - self.view.update() + if len(shapes) == 0: + for a in self.actors: + self.ren.RemoveActor(a) - def clear(self, s: Shape | vtkActor): + for actors in self.shapes.values(): + for a in actors: + self.ren.RemoveActor(a) - if isinstance(s, Shape): - actor = self.shapes[s] - self.ren.RemoveActor(actor) + for s in shapes: + if isinstance(s, Shape): + for a in self.shapes[s]: + self.ren.RemoveActor(a) - del self.shapes[s] + del self.shapes[s] + else: + self.actors.remove(s) + self.ren.RemoveActor(s) - else: - self.actors.remove(s) - self.ren.RemoveActor(s) + self.ren.ResetCamera() + self.view.update() - self.ren.ResetCamera() - self.view.update() + self._run(_clear()) From 524884e7674e7d2a0e6fab60ca759913f6c69794 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Wed, 12 Mar 2025 19:01:26 +0100 Subject: [PATCH 05/24] Revert some changes in vis --- cadquery/vis.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/cadquery/vis.py b/cadquery/vis.py index c6c1f42d5..47b522370 100644 --- a/cadquery/vis.py +++ b/cadquery/vis.py @@ -150,7 +150,7 @@ def _to_vtk_axs(locs: List[Location], scale: float = 0.1) -> List[vtkProp3D]: Convert Locations to vtkActor. """ - rv: List[vtkProp3D] = [] + rv = vtkAssembly() for l in locs: trans, rot = _loc2vtk(l) @@ -161,9 +161,9 @@ def _to_vtk_axs(locs: List[Location], scale: float = 0.1) -> List[vtkProp3D]: ax.SetOrientation(*rot) ax.SetScale(scale) - rv.append(ax) + rv.AddPart(ax) - return rv + return [rv] def _to_vtk_shapes( @@ -400,7 +400,7 @@ def show( gradient: bool = True, xpos: Union[int, float] = 0, ypos: Union[int, float] = 0, -) -> vtkRenderWindow: +): """ Show CQ objects using VTK. This functions optionally allows to make screenshots. """ @@ -458,11 +458,11 @@ def show( axes = vtkAxesActor() axes.SetDragable(0) axes.SetAxisLabels(0) - # tp = axes.GetXAxisCaptionActor2D().GetCaptionTextProperty() - # tp.SetColor(0, 0, 0) + tp = axes.GetXAxisCaptionActor2D().GetCaptionTextProperty() + tp.SetColor(0, 0, 0) - # axes.GetYAxisCaptionActor2D().GetCaptionTextProperty().ShallowCopy(tp) - # axes.GetZAxisCaptionActor2D().GetCaptionTextProperty().ShallowCopy(tp) + axes.GetYAxisCaptionActor2D().GetCaptionTextProperty().ShallowCopy(tp) + axes.GetZAxisCaptionActor2D().GetCaptionTextProperty().ShallowCopy(tp) # add to an orientation widget if trihedron: @@ -542,8 +542,6 @@ def show( if interact: inter.Start() - return win - # alias show_object = show From 3bb10364f0d0e6e0add28f302a33f0ed574063ff Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Wed, 12 Mar 2025 19:02:09 +0100 Subject: [PATCH 06/24] Smaller coros and bg color --- cadquery/fig.py | 54 ++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index 46423a947..5b121b4de 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -17,7 +17,6 @@ vtkRenderer, vtkRenderWindow, vtkRenderWindowInteractor, - vtkActor, vtkProp3D, ) @@ -58,6 +57,10 @@ def __init__(self, port: int): inter.SetInteractorStyle(vtkInteractorStyleTrackballCamera()) inter.SetRenderWindow(win) + # background + renderer.SetBackground(1, 1, 1) + renderer.GradientBackgroundOn() + # axes axes = vtkAxesActor() axes.SetDragable(0) @@ -94,55 +97,56 @@ def __init__(self, port: int): ) server.state.flush() - coro = server.start( - thread=True, exec_mode="coroutine", port=port, open_browser=True - ) self.server = server self.loop = new_event_loop() - def _run(): + def _run_loop(): set_event_loop(self.loop) self.loop.run_forever() - self.thread = Thread(target=_run, daemon=True) + self.thread = Thread(target=_run_loop, daemon=True) self.thread.start() - run_coroutine_threadsafe(coro, self.loop) + coro = server.start( + thread=True, exec_mode="coroutine", port=port, open_browser=True + ) + + self._run(coro) def _run(self, coro): run_coroutine_threadsafe(coro, self.loop) - def show(self, s: Shape | vtkActor | list[vtkProp3D], *args, **kwargs): - async def _show(): + def show(self, s: Shape | vtkProp3D | list[vtkProp3D], *args, **kwargs): - if isinstance(s, Shape): - # do not show markers by default - if "markersize" not in kwargs: - kwargs["markersize"] = 0 + if isinstance(s, Shape): + # do not show markers by default + if "markersize" not in kwargs: + kwargs["markersize"] = 0 - actors = style(s, *args, **kwargs) - self.shapes[s] = actors + actors = style(s, *args, **kwargs) + self.shapes[s] = actors - for actor in actors: - self.ren.AddActor(actor) + for actor in actors: + self.ren.AddActor(actor) - elif isinstance(s, vtkActor): - self.actors.append(s) - self.ren.AddActor(s) - else: - self.actors.extend(s) + elif isinstance(s, vtkProp3D): + self.actors.append(s) + self.ren.AddActor(s) + else: + self.actors.extend(s) - for el in s: - self.ren.AddActor(el) + for el in s: + self.ren.AddActor(el) + async def _show(): self.ren.ResetCamera() self.view.update() self._run(_show()) - def clear(self, *shapes: Shape | vtkActor): + def clear(self, *shapes: Shape | vtkProp3D): async def _clear(): if len(shapes) == 0: From ca12abaa5b34d7b6e05fe85421488fec644fb8e7 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Fri, 14 Mar 2025 22:11:50 +0100 Subject: [PATCH 07/24] Refactor into a singleton --- cadquery/fig.py | 98 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 21 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index 5b121b4de..d7528feff 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -5,13 +5,15 @@ AbstractEventLoop, ) from threading import Thread +from itertools import chain +from uuid import uuid1 as uuid from trame.app import get_server, Server from trame.widgets import html, vtk as vtk_widgets, client from trame.ui.html import DivLayout -from cadquery import Shape -from cadquery.vis import style +from . import Shape +from .vis import style, Showable, ShapeLike, _split_showables, _to_vtk_pts, _to_vtk_axs from vtkmodules.vtkRenderingCore import ( vtkRenderer, @@ -35,12 +37,26 @@ class Figure: win: vtkRenderWindow ren: vtkRenderer view: vtk_widgets.VtkRemoteView - shapes: dict[Shape, list[vtkProp3D]] + shapes: dict[ShapeLike, list[vtkProp3D]] actors: list[vtkProp3D] loop: AbstractEventLoop thread: Thread + empty: bool - def __init__(self, port: int): + _instance = None + _initialized: bool = False + + def __new__(cls, *args, **kwargs): + + if not cls._instance: + cls._instance = object.__new__(cls) + + return cls._instance + + def __init__(self, port: int = 18081): + + if self._initialized: + return self.loop = new_event_loop() set_event_loop(self.loop) @@ -84,7 +100,7 @@ def __init__(self, port: int): self.actors = [] # server - server = get_server("CQ") + server = get_server("CQ-server") server.client_type = "vue3" # layout @@ -112,33 +128,65 @@ def _run_loop(): thread=True, exec_mode="coroutine", port=port, open_browser=True ) - self._run(coro) + if coro: + self._run(coro) + + # prevent reinitialization + self._initialized = True + + # view is initialized as empty + self.empty = True def _run(self, coro): run_coroutine_threadsafe(coro, self.loop) - def show(self, s: Shape | vtkProp3D | list[vtkProp3D], *args, **kwargs): + def show(self, *showables: Showable | vtkProp3D | list[vtkProp3D], **kwargs): + """ + Show objects. + """ + + # split objects + shapes, vecs, locs, props = _split_showables(showables) + + pts = _to_vtk_pts(vecs) + axs = _to_vtk_axs(locs) - if isinstance(s, Shape): + for s in shapes: # do not show markers by default if "markersize" not in kwargs: kwargs["markersize"] = 0 - actors = style(s, *args, **kwargs) + actors = style(s, **kwargs) self.shapes[s] = actors for actor in actors: self.ren.AddActor(actor) - elif isinstance(s, vtkProp3D): - self.actors.append(s) - self.ren.AddActor(s) - else: - self.actors.extend(s) + for prop in chain(props, axs): + self.actors.append(prop) + self.ren.AddActor(prop) - for el in s: - self.ren.AddActor(el) + if vecs: + self.actors.append(pts) + self.ren.AddActor(pts) + + async def _show(): + self.view.update() + + self._run(_show()) + + # zoom to fit on 1st object added + if self.empty: + self.fit() + self.empty = False + + return self + + def fit(self): + """ + Update view to fit all objects. + """ async def _show(): self.ren.ResetCamera() @@ -146,16 +194,23 @@ async def _show(): self._run(_show()) + return self + def clear(self, *shapes: Shape | vtkProp3D): + """ + Clear specified objects. If no arguments are passed, clears all objects. + """ + async def _clear(): if len(shapes) == 0: - for a in self.actors: + for a in self.ren.GetActors(): self.ren.RemoveActor(a) - for actors in self.shapes.values(): - for a in actors: - self.ren.RemoveActor(a) + self.actors.clear() + self.shapes.clear() + + self.empty = True for s in shapes: if isinstance(s, Shape): @@ -167,7 +222,8 @@ async def _clear(): self.actors.remove(s) self.ren.RemoveActor(s) - self.ren.ResetCamera() self.view.update() self._run(_clear()) + + return self From 31f8c2854abd1447a4b7ad8aeb5237a63b564fca Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Fri, 14 Mar 2025 22:42:51 +0100 Subject: [PATCH 08/24] Update deps --- conda/meta.yaml | 2 ++ environment.yml | 2 ++ setup.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/conda/meta.yaml b/conda/meta.yaml index e3f6d3a28..2f976f844 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -26,6 +26,8 @@ requirements: - multimethod >=1.11,<2.0 - casadi - typish + - trame + - trame-vtk test: requires: diff --git a/environment.yml b/environment.yml index 6d03a2359..3d5f4ef69 100644 --- a/environment.yml +++ b/environment.yml @@ -25,6 +25,8 @@ dependencies: - pathspec - click - appdirs + - trame + - trame-vtk - pip - pip: - --editable=. diff --git a/setup.py b/setup.py index 45442d3e1..c8f162df8 100644 --- a/setup.py +++ b/setup.py @@ -33,6 +33,8 @@ "typish", "casadi", "path", + "trame", + "trame-vtk" ] From 44fbb64b06b861d976e01047132126ecdaa437bc Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Sat, 15 Mar 2025 15:52:35 +0100 Subject: [PATCH 09/24] Implemented pop and fixed clear --- cadquery/fig.py | 46 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index d7528feff..f013dcb20 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -4,9 +4,9 @@ run_coroutine_threadsafe, AbstractEventLoop, ) +from typing import Optional from threading import Thread from itertools import chain -from uuid import uuid1 as uuid from trame.app import get_server, Server from trame.widgets import html, vtk as vtk_widgets, client @@ -20,6 +20,7 @@ vtkRenderWindow, vtkRenderWindowInteractor, vtkProp3D, + vtkActor, ) @@ -42,6 +43,9 @@ class Figure: loop: AbstractEventLoop thread: Thread empty: bool + last: Optional[ + tuple[list[ShapeLike], list[vtkProp3D], Optional[vtkActor], list[vtkProp3D]] + ] _instance = None _initialized: bool = False @@ -136,6 +140,7 @@ def _run_loop(): # view is initialized as empty self.empty = True + self.last = None def _run(self, coro): @@ -150,7 +155,9 @@ def show(self, *showables: Showable | vtkProp3D | list[vtkProp3D], **kwargs): shapes, vecs, locs, props = _split_showables(showables) pts = _to_vtk_pts(vecs) - axs = _to_vtk_axs(locs) + axs = _to_vtk_axs( + locs, **({"scale": kwargs["scale"]} if "scale" in kwargs else {}) + ) for s in shapes: # do not show markers by default @@ -171,6 +178,9 @@ def show(self, *showables: Showable | vtkProp3D | list[vtkProp3D], **kwargs): self.actors.append(pts) self.ren.AddActor(pts) + # store to enable pop + self.last = (shapes, axs, pts if vecs else None, props) + async def _show(): self.view.update() @@ -204,8 +214,7 @@ def clear(self, *shapes: Shape | vtkProp3D): async def _clear(): if len(shapes) == 0: - for a in self.ren.GetActors(): - self.ren.RemoveActor(a) + self.ren.RemoveAllViewProps() self.actors.clear() self.shapes.clear() @@ -224,6 +233,35 @@ async def _clear(): self.view.update() + # reset last, bc we don't want to keep track of what was removed + self.last = None self._run(_clear()) return self + + def pop(self): + """ + Clear the last showable. + """ + + async def _pop(): + + (shapes, axs, pts, props) = self.last + + for s in shapes: + for act in self.shapes.pop(s): + self.ren.RemoveActor(act) + + for act in chain(axs, props): + self.ren.RemoveActor(act) + self.actors.remove(act) + + if pts: + self.ren.RemoveActor(pts) + self.actors.remove(pts) + + self.view.update() + + self._run(_pop()) + + return self From 125de0921f133d1dae122d0cb307d77f3bffe197 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Sat, 15 Mar 2025 16:10:55 +0100 Subject: [PATCH 10/24] Styling fix --- cadquery/fig.py | 14 +++++++------- cadquery/vis.py | 9 ++++++++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index f013dcb20..9e5e625eb 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -44,7 +44,9 @@ class Figure: thread: Thread empty: bool last: Optional[ - tuple[list[ShapeLike], list[vtkProp3D], Optional[vtkActor], list[vtkProp3D]] + tuple[ + list[ShapeLike], list[vtkProp3D], Optional[list[vtkProp3D]], list[vtkProp3D] + ] ] _instance = None @@ -154,10 +156,8 @@ def show(self, *showables: Showable | vtkProp3D | list[vtkProp3D], **kwargs): # split objects shapes, vecs, locs, props = _split_showables(showables) - pts = _to_vtk_pts(vecs) - axs = _to_vtk_axs( - locs, **({"scale": kwargs["scale"]} if "scale" in kwargs else {}) - ) + pts = style(vecs, **kwargs) + axs = style(locs, **kwargs) for s in shapes: # do not show markers by default @@ -175,8 +175,8 @@ def show(self, *showables: Showable | vtkProp3D | list[vtkProp3D], **kwargs): self.ren.AddActor(prop) if vecs: - self.actors.append(pts) - self.ren.AddActor(pts) + self.actors.append(*pts) + self.ren.AddActor(*pts) # store to enable pop self.last = (shapes, axs, pts if vecs else None, props) diff --git a/cadquery/vis.py b/cadquery/vis.py index 47b522370..93e9194be 100644 --- a/cadquery/vis.py +++ b/cadquery/vis.py @@ -53,7 +53,14 @@ ShapeLike = Union[Shape, Workplane, Assembly, Sketch, TopoDS_Shape] Showable = Union[ - ShapeLike, List[ShapeLike], Vector, List[Vector], vtkProp3D, List[vtkProp3D] + ShapeLike, + List[ShapeLike], + Vector, + List[Vector], + vtkProp3D, + List[vtkProp3D], + Location, + List[Location], ] From c3a4c634572981a9cbea5fe8fff0188906d59420 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Sat, 15 Mar 2025 16:16:50 +0100 Subject: [PATCH 11/24] Fix pop --- cadquery/fig.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index 9e5e625eb..ea6991aa0 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -257,8 +257,8 @@ async def _pop(): self.actors.remove(act) if pts: - self.ren.RemoveActor(pts) - self.actors.remove(pts) + self.ren.RemoveActor(*pts) + self.actors.remove(*pts) self.view.update() From 6f78783191eac0a8f9030916ab03b59e47cecf35 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Sun, 16 Mar 2025 13:51:11 +0100 Subject: [PATCH 12/24] Add simple data --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c8f162df8..80b5c3812 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ "casadi", "path", "trame", - "trame-vtk" + "trame-vtk", ] From 4a98f6abe979f3b5aeec20b9edc38fb23404d453 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Mon, 17 Mar 2025 07:47:29 +0100 Subject: [PATCH 13/24] Get rid of vtk msgs --- cadquery/fig.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index ea6991aa0..3784608eb 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -7,6 +7,7 @@ from typing import Optional from threading import Thread from itertools import chain +from webbrowser import open_new_tab from trame.app import get_server, Server from trame.widgets import html, vtk as vtk_widgets, client @@ -131,7 +132,11 @@ def _run_loop(): self.thread.start() coro = server.start( - thread=True, exec_mode="coroutine", port=port, open_browser=True + thread=True, + exec_mode="coroutine", + port=port, + open_browser=False, + show_connection_info=False, ) if coro: @@ -144,6 +149,9 @@ def _run_loop(): self.empty = True self.last = None + # open webbrowser + open_new_tab(f"http://localhost:{port}") + def _run(self, coro): run_coroutine_threadsafe(coro, self.loop) From b45fa6928b6e26d8a55b5d421048af689ea36104 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Mon, 31 Mar 2025 07:40:09 +0200 Subject: [PATCH 14/24] Wait for clear --- cadquery/fig.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index 3784608eb..bbfa5502d 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -3,6 +3,7 @@ set_event_loop, run_coroutine_threadsafe, AbstractEventLoop, + Future, ) from typing import Optional from threading import Thread @@ -152,9 +153,9 @@ def _run_loop(): # open webbrowser open_new_tab(f"http://localhost:{port}") - def _run(self, coro): + def _run(self, coro) -> Future: - run_coroutine_threadsafe(coro, self.loop) + return run_coroutine_threadsafe(coro, self.loop) def show(self, *showables: Showable | vtkProp3D | list[vtkProp3D], **kwargs): """ @@ -243,7 +244,8 @@ async def _clear(): # reset last, bc we don't want to keep track of what was removed self.last = None - self._run(_clear()) + future = self._run(_clear()) + future.result() return self From 56c6cfc79e8f874a63248a55cfe660c241132e81 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Mon, 31 Mar 2025 07:42:47 +0200 Subject: [PATCH 15/24] Display axis labels --- cadquery/vis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadquery/vis.py b/cadquery/vis.py index 93e9194be..fb644386d 100644 --- a/cadquery/vis.py +++ b/cadquery/vis.py @@ -464,7 +464,7 @@ def show( # construct an axes indicator axes = vtkAxesActor() axes.SetDragable(0) - axes.SetAxisLabels(0) + tp = axes.GetXAxisCaptionActor2D().GetCaptionTextProperty() tp.SetColor(0, 0, 0) From dd7803e479d2f84b5e97ea3743048ecdd1be57cc Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:50:31 +0200 Subject: [PATCH 16/24] Adding smoke test for fig --- tests/conftest.py | 22 ++++++++++++++++++++++ tests/test_fig.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 tests/conftest.py create mode 100644 tests/test_fig.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..d30b369de --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,22 @@ +import pytest + + +def pytest_addoption(parser): + parser.addoption("--gui", action="store_true", default=False, help="run gui tests") + + +def pytest_configure(config): + config.addinivalue_line("markers", "gui: mark gui test") + + +def pytest_collection_modifyitems(config, items): + + # run gui tests --gui option is proveded + if config.getoption("--gui"): + return + + # skip gui tests otherwise + skip_gui = pytest.mark.skip(reason="need --gui option to run") + for item in items: + if "gui" in item.keywords: + item.add_marker(skip_gui) diff --git a/tests/test_fig.py b/tests/test_fig.py new file mode 100644 index 000000000..c9cc5bf37 --- /dev/null +++ b/tests/test_fig.py @@ -0,0 +1,44 @@ +from cadquery import Workplane, Assembly, Sketch, Vector, Location +from cadquery.func import box +from cadquery.vis import vtkAxesActor, ctrlPts +from cadquery.fig import Figure + +from pytest import fixture, mark + + +@fixture(scope="module") +def fig(): + return Figure() + + +@mark.gui +def test_fig(fig): + + # showables + s = box(1, 1, 1) + wp = Workplane().box(1, 1, 1) + assy = Assembly().add(box(1, 1, 1)) + sk = Sketch().rect(1, 1) + ctrl_pts = ctrlPts(sk.val().toNURBS()) + v = Vector() + loc = Location() + act = vtkAxesActor() + + # individual showables + fig.show(s, wp, assy, sk, ctrl_pts, v, loc, act) + + # fit + fig.fit() + + # clear + fig.clear() + + # lists of showables + fig.show(s.Edges()).show([Vector(), Vector(0, 1)]) + + # displaying nonsense does not throw + fig.show("a").show(["a", 1234]) + + # pop + fig.show(s, color="red") + fig.pop() From 34a499afa4044f66793b99cc24cc418cfca26cb8 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:52:15 +0200 Subject: [PATCH 17/24] Run gui tests in appveyor --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 5364fb68a..6c1632e00 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -37,7 +37,7 @@ build: false test_script: - mamba run -n cadquery black . --diff --check - mamba run -n cadquery mypy cadquery - - mamba run -n cadquery pytest -v --cov + - mamba run -n cadquery pytest -v --gui --cov on_success: - mamba run -n cadquery codecov From 7d3df36cb7b992301da4f197ba6ee4a3864aea1c Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Thu, 12 Jun 2025 18:15:20 +0200 Subject: [PATCH 18/24] mypy fix --- cadquery/fig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index bbfa5502d..e9447ddf4 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -3,8 +3,8 @@ set_event_loop, run_coroutine_threadsafe, AbstractEventLoop, - Future, ) +from concurrent.futures import Future from typing import Optional from threading import Thread from itertools import chain From 30edd4a2ac5a51c2598a88e1573a3b3dc8712d61 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Thu, 12 Jun 2025 18:32:05 +0200 Subject: [PATCH 19/24] Misc fixes --- cadquery/fig.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index e9447ddf4..5143794e5 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -10,19 +10,19 @@ from itertools import chain from webbrowser import open_new_tab -from trame.app import get_server, Server +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 . import Shape -from .vis import style, Showable, ShapeLike, _split_showables, _to_vtk_pts, _to_vtk_axs +from .vis import style, Showable, ShapeLike, _split_showables from vtkmodules.vtkRenderingCore import ( vtkRenderer, vtkRenderWindow, vtkRenderWindowInteractor, vtkProp3D, - vtkActor, ) From 2ad988424266d9c86b7a50d3765b8e69256077d7 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Thu, 12 Jun 2025 19:15:12 +0200 Subject: [PATCH 20/24] Test GUI only on win --- tests/test_fig.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/test_fig.py b/tests/test_fig.py index c9cc5bf37..6789a57c7 100644 --- a/tests/test_fig.py +++ b/tests/test_fig.py @@ -5,6 +5,8 @@ from pytest import fixture, mark +from sys import platform + @fixture(scope="module") def fig(): @@ -12,6 +14,7 @@ def fig(): @mark.gui +@mark.skipif(platform != "win32", reason="CI with UI only works on win for now") def test_fig(fig): # showables @@ -24,8 +27,10 @@ def test_fig(fig): loc = Location() act = vtkAxesActor() + showables = (s, wp, assy, sk, ctrl_pts, v, loc, act) + # individual showables - fig.show(s, wp, assy, sk, ctrl_pts, v, loc, act) + fig.show(*showables) # fit fig.fit() @@ -33,6 +38,9 @@ def test_fig(fig): # clear fig.clear() + # clear with an arg + fig.show(s).clear(s) + # lists of showables fig.show(s.Edges()).show([Vector(), Vector(0, 1)]) @@ -40,5 +48,10 @@ def test_fig(fig): fig.show("a").show(["a", 1234]) # pop - fig.show(s, color="red") - fig.pop() + for el in showables: + fig.show(el, color="red") + fig.pop() + + # test singleton behavior of fig + fig2 = Figure() + assert fig is fig2 From d1c726a87ac6c13a4a5336827c15ab21bfd3a693 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Thu, 12 Jun 2025 19:42:17 +0200 Subject: [PATCH 21/24] Coverage tweak --- tests/test_fig.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_fig.py b/tests/test_fig.py index 6789a57c7..3d810f840 100644 --- a/tests/test_fig.py +++ b/tests/test_fig.py @@ -39,7 +39,8 @@ def test_fig(fig): fig.clear() # clear with an arg - fig.show(s).clear(s) + for el in showables: + fig.show(el).clear(el) # lists of showables fig.show(s.Edges()).show([Vector(), Vector(0, 1)]) From 99af75fdd71e625ab9b0d8ef234ca6421083e486 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Thu, 12 Jun 2025 21:26:44 +0200 Subject: [PATCH 22/24] Fix test --- cadquery/fig.py | 2 +- tests/test_fig.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index 5143794e5..5a3298946 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -231,7 +231,7 @@ async def _clear(): self.empty = True for s in shapes: - if isinstance(s, Shape): + if isinstance(s, ShapeLike): for a in self.shapes[s]: self.ren.RemoveActor(a) diff --git a/tests/test_fig.py b/tests/test_fig.py index 3d810f840..ffa09264c 100644 --- a/tests/test_fig.py +++ b/tests/test_fig.py @@ -39,7 +39,7 @@ def test_fig(fig): fig.clear() # clear with an arg - for el in showables: + for el in (s, wp, assy, sk, ctrl_pts): fig.show(el).clear(el) # lists of showables From f7bf1bf070ce421b11b5ff145b8eb7df3d0f954d Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Fri, 13 Jun 2025 08:05:07 +0200 Subject: [PATCH 23/24] Mypy fix --- cadquery/fig.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index 5a3298946..4d08201c9 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -10,6 +10,8 @@ from itertools import chain from webbrowser import open_new_tab +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 @@ -231,7 +233,7 @@ async def _clear(): self.empty = True for s in shapes: - if isinstance(s, ShapeLike): + if instance_of(s, ShapeLike): for a in self.shapes[s]: self.ren.RemoveActor(a) From 204df308ea090c553e01d589c050f58eb56c66e9 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:04:41 +0200 Subject: [PATCH 24/24] Change zoom reset behavior --- cadquery/fig.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cadquery/fig.py b/cadquery/fig.py index 4d08201c9..a68a43e59 100644 --- a/cadquery/fig.py +++ b/cadquery/fig.py @@ -230,8 +230,6 @@ async def _clear(): self.actors.clear() self.shapes.clear() - self.empty = True - for s in shapes: if instance_of(s, ShapeLike): for a in self.shapes[s]: