Skip to content

Commit 7cade87

Browse files
Implement screenshots in show (#1759)
* Implement screenshots * Use previous defaults * Add some docs * Render off-screen when not interacting * Add a test * Dummify the test" * Coverage tweak * Add a note about possible issues * Fix example * Add xpos and ypos --------- Co-authored-by: adam-urbanczyk <[email protected]>
1 parent 58f4cbf commit 7cade87

File tree

3 files changed

+125
-19
lines changed

3 files changed

+125
-19
lines changed

cadquery/vis.py

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from . import Shape, Workplane, Assembly, Sketch, Compound, Color, Vector, Location
22
from .occ_impl.assembly import _loc2vtk, toVTK
33

4-
from typing import Union, Any, List, Tuple, Iterable, cast
4+
from typing import Union, Any, List, Tuple, Iterable, cast, Optional
55

66
from typish import instance_of
77

@@ -18,10 +18,12 @@
1818
vtkPolyDataMapper,
1919
vtkAssembly,
2020
vtkRenderWindow,
21+
vtkWindowToImageFilter,
2122
)
2223
from vtkmodules.vtkCommonCore import vtkPoints
2324
from vtkmodules.vtkCommonDataModel import vtkCellArray, vtkPolyData
2425
from vtkmodules.vtkCommonColor import vtkNamedColors
26+
from vtkmodules.vtkIOImage import vtkPNGWriter
2527

2628

2729
DEFAULT_COLOR = [1, 0.8, 0, 1]
@@ -150,10 +152,22 @@ def show(
150152
edges: bool = False,
151153
specular: bool = True,
152154
title: str = "CQ viewer",
155+
screenshot: Optional[str] = None,
156+
interact: bool = True,
157+
zoom: float = 1.0,
158+
roll: float = -35,
159+
elevation: float = -45,
160+
width: Union[int, float] = 0.5,
161+
height: Union[int, float] = 0.5,
162+
trihedron: bool = True,
163+
bgcolor: tuple[float, float, float] = (1, 1, 1),
164+
gradient: bool = True,
165+
xpos: Union[int, float] = 0,
166+
ypos: Union[int, float] = 0,
153167
**kwrags: Any,
154168
):
155169
"""
156-
Show CQ objects using VTK.
170+
Show CQ objects using VTK. This functions optionally allows to make screenshots.
157171
"""
158172

159173
# split objects
@@ -171,6 +185,11 @@ def show(
171185

172186
# VTK window boilerplate
173187
win = vtkRenderWindow()
188+
189+
# Render off-screen when not interacting
190+
if not interact:
191+
win.SetOffScreenRendering(1)
192+
174193
win.SetWindowName(title)
175194
win.AddRenderer(renderer)
176195

@@ -208,26 +227,30 @@ def show(
208227
axes.GetZAxisCaptionActor2D().GetCaptionTextProperty().ShallowCopy(tp)
209228

210229
# add to an orientation widget
211-
orient_widget = vtkOrientationMarkerWidget()
212-
orient_widget.SetOrientationMarker(axes)
213-
orient_widget.SetViewport(0.9, 0.0, 1.0, 0.2)
214-
orient_widget.SetZoom(1.1)
215-
orient_widget.SetInteractor(inter)
216-
orient_widget.EnabledOn()
217-
orient_widget.InteractiveOff()
230+
if trihedron:
231+
orient_widget = vtkOrientationMarkerWidget()
232+
orient_widget.SetOrientationMarker(axes)
233+
orient_widget.SetViewport(0.9, 0.0, 1.0, 0.2)
234+
orient_widget.SetZoom(1.1)
235+
orient_widget.SetInteractor(inter)
236+
orient_widget.EnabledOn()
237+
orient_widget.InteractiveOff()
218238

219239
# use gradient background
220-
renderer.SetBackground(1, 1, 1)
221-
renderer.GradientBackgroundOn()
240+
renderer.SetBackground(*bgcolor)
241+
242+
if gradient:
243+
renderer.GradientBackgroundOn()
222244

223245
# use FXXAA
224246
renderer.UseFXAAOn()
225247

226248
# set camera
227249
camera = renderer.GetActiveCamera()
228-
camera.Roll(-35)
229-
camera.Elevation(-45)
250+
camera.Roll(roll)
251+
camera.Elevation(elevation)
230252
renderer.ResetCamera()
253+
camera.Zoom(zoom)
231254

232255
# add pts and locs
233256
renderer.AddActor(pts)
@@ -241,12 +264,36 @@ def show(
241264
inter.Initialize()
242265

243266
w, h = win.GetScreenSize()
244-
win.SetSize(w // 2, h // 2)
245-
win.SetPosition(-10, 0)
267+
win.SetSize(
268+
int(w * width) if isinstance(width, float) else width,
269+
int(h * height) if isinstance(height, float) else height,
270+
) # is height, width specified as float assume it is relative
271+
272+
# set position
273+
win.SetPosition(
274+
int(w * xpos) if isinstance(xpos, float) else xpos,
275+
int(h * ypos) if isinstance(ypos, float) else ypos,
276+
)
246277

247278
# show and return
248279
win.Render()
249-
inter.Start()
280+
281+
# make a screenshot
282+
if screenshot:
283+
win2image = vtkWindowToImageFilter()
284+
win2image.SetInput(win)
285+
win2image.SetInputBufferTypeToRGB()
286+
win2image.ReadFrontBufferOff()
287+
win2image.Update()
288+
289+
writer = vtkPNGWriter()
290+
writer.SetFileName(screenshot)
291+
writer.SetInputConnection(win2image.GetOutputPort())
292+
writer.Write()
293+
294+
# start interaction
295+
if interact:
296+
inter.Start()
250297

251298

252299
# alias

doc/vis.rst

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ One can visualize objects of type :class:`~cadquery.Workplane`, :class:`~cadquer
3333
.. code-block:: python
3434
3535
from cadquery import *
36-
from cadquery.occ_impl.shapes import *
36+
from cadquery.func import *
3737
from cadquery.vis import show
3838
3939
w = Workplane().sphere(0.5).split(keepTop=True)
@@ -62,7 +62,7 @@ Additionally it is possible to integrate with other libraries using VTK and disp
6262
.. code-block:: python
6363
6464
from cadquery.vis import show
65-
from cadquery.occ_impl.shapes import torus
65+
from cadquery.func import torus
6666
6767
from vtkmodules.vtkRenderingAnnotation import vtkAnnotatedCubeActor
6868
@@ -77,6 +77,23 @@ Additionally it is possible to integrate with other libraries using VTK and disp
7777

7878
Note that currently the show function is blocking.
7979

80+
Screenshots
81+
===========
82+
83+
`:meth:~cadquery.vis.show` allows additionally to take screenshots in `png` format. One can specify zoom,
84+
camera position and windows size.
85+
86+
.. code-block:: python
87+
88+
from cadquery.vis import show
89+
from cadquery.func import box
90+
91+
b = box(1,1,1)
92+
93+
show(b, width=800, height=800, screenshot='img.png', zoom=2, roll=-20, elevation=-30, interact=False)
94+
95+
NB: intermittent issues were observed with this functionality, please submit detailed bug reports in case
96+
of problems.
8097

8198
Jupyter/JupterLab
8299
=================

tests/test_vis.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,21 @@
33

44
import cadquery.vis as vis
55

6-
from vtkmodules.vtkRenderingCore import vtkRenderWindow, vtkRenderWindowInteractor
6+
from vtkmodules.vtkRenderingCore import (
7+
vtkRenderWindow,
8+
vtkRenderWindowInteractor,
9+
vtkWindowToImageFilter,
10+
)
711
from vtkmodules.vtkRenderingAnnotation import vtkAnnotatedCubeActor
12+
from vtkmodules.vtkIOImage import vtkPNGWriter
813

914
from pytest import fixture
15+
from path import Path
16+
17+
18+
@fixture(scope="module")
19+
def tmpdir(tmp_path_factory):
20+
return Path(tmp_path_factory.mktemp("screenshots"))
1021

1122

1223
@fixture
@@ -54,12 +65,29 @@ def SetPosition(*args):
5465

5566
pass
5667

68+
def SetOffScreenRendering(*args):
69+
70+
pass
71+
72+
73+
class FakeWin2Img(vtkWindowToImageFilter):
74+
def Update(*args):
75+
76+
pass
77+
78+
79+
class FakePNGWriter(vtkPNGWriter):
80+
def Write(*args):
81+
82+
pass
83+
5784

5885
def test_show(wp, assy, sk, monkeypatch):
5986

6087
# use some dummy vtk objects
6188
monkeypatch.setattr(vis, "vtkRenderWindowInteractor", FakeInteractor)
6289
monkeypatch.setattr(vis, "vtkRenderWindow", FakeWindow)
90+
monkeypatch.setattr(vis, "vtkWindowToImageFilter", FakeWin2Img)
6391

6492
# simple smoke test
6593
show(wp)
@@ -92,3 +120,17 @@ def test_show(wp, assy, sk, monkeypatch):
92120

93121
# show a raw vtkProp
94122
show(vtkAxesActor(), [vtkAnnotatedCubeActor()])
123+
124+
125+
def test_screenshot(wp, tmpdir, monkeypatch):
126+
127+
# smoke test for now
128+
129+
# use some dummy vtk objects
130+
monkeypatch.setattr(vis, "vtkRenderWindowInteractor", FakeInteractor)
131+
monkeypatch.setattr(vis, "vtkRenderWindow", FakeWindow)
132+
monkeypatch.setattr(vis, "vtkWindowToImageFilter", FakeWin2Img)
133+
monkeypatch.setattr(vis, "vtkPNGWriter", FakePNGWriter)
134+
135+
with tmpdir:
136+
show(wp, interact=False, screenshot="img.png", trihedron=False, gradient=False)

0 commit comments

Comments
 (0)