/
renderer.py
4074 lines (3334 loc) · 131 KB
/
renderer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""Module containing pyvista implementation of vtkRenderer."""
import collections.abc
import contextlib
from functools import partial, wraps
from typing import ClassVar, Dict, Sequence, cast
import warnings
import numpy as np
import pyvista
from pyvista import MAX_N_COLOR_BARS, vtk_version_info
from pyvista.core._typing_core import BoundsLike
from pyvista.core.errors import PyVistaDeprecationWarning
from pyvista.core.utilities.helpers import wrap
from pyvista.core.utilities.misc import assert_empty_kwargs, try_callback
from . import _vtk
from .actor import Actor
from .camera import Camera
from .charts import Charts
from .colors import Color, get_cycler
from .errors import InvalidCameraError
from .helpers import view_vectors
from .mapper import DataSetMapper
from .render_passes import RenderPasses
from .tools import create_axes_marker, create_axes_orientation_box, parse_font_family
from .utilities.gl_checks import check_depth_peeling, uses_egl
ACTOR_LOC_MAP = [
'upper right',
'upper left',
'lower left',
'lower right',
'center left',
'center right',
'lower center',
'upper center',
'center',
]
def map_loc_to_pos(loc, size, border=0.05):
"""Map location and size to a VTK position and position2.
Parameters
----------
loc : str
Location of the actor. Can be a string with values such as 'right',
'left', 'upper', or 'lower'.
size : Sequence of length 2
Size of the actor. It must be a list of length 2.
border : float, default: 0.05
Size of the border around the actor.
Returns
-------
tuple
The VTK position and position2 coordinates. Tuple of the form (x, y, size).
Raises
------
ValueError
If the ``size`` parameter is not a list of length 2.
"""
if not isinstance(size, Sequence) or len(size) != 2:
raise ValueError(f'`size` must be a list of length 2. Passed value is {size}')
if 'right' in loc:
x = 1 - size[1] - border
elif 'left' in loc:
x = border
else:
x = 0.5 - size[1] / 2
if 'upper' in loc:
y = 1 - size[1] - border
elif 'lower' in loc:
y = border
else:
y = 0.5 - size[1] / 2
return x, y, size
def make_legend_face(face):
"""
Create the legend face based on the given face.
Parameters
----------
face : str | None | pyvista.PolyData
The shape of the legend face. Valid strings are:
'-', 'line', '^', 'triangle', 'o', 'circle', 'r', 'rectangle'.
Also accepts ``None`` and instances of ``pyvista.PolyData``.
Returns
-------
pyvista.PolyData
The legend face as a PolyData object.
Raises
------
ValueError
If the provided face value is invalid.
"""
if face is None:
legendface = pyvista.PolyData([0.0, 0.0, 0.0])
elif face in ["-", "line"]:
legendface = _line_for_legend()
elif face in ["^", "triangle"]:
legendface = pyvista.Triangle()
elif face in ["o", "circle"]:
legendface = pyvista.Circle()
elif face in ["r", "rectangle"]:
legendface = pyvista.Rectangle()
elif isinstance(face, pyvista.PolyData):
legendface = face
else:
raise ValueError(
f'Invalid face "{face}". Must be one of the following:\n'
'\t"triangle"\n'
'\t"circle"\n'
'\t"rectangle"\n'
'\tNone'
'\tpyvista.PolyData',
)
return legendface
def scale_point(camera, point, invert=False):
"""Scale a point using the camera's transform matrix.
Parameters
----------
camera : Camera
The camera who's matrix to use.
point : sequence[float]
Scale point coordinates.
invert : bool, default: False
If ``True``, invert the matrix to transform the point out of
the camera's transformed space. Default is ``False`` to
transform a point from world coordinates to the camera's
transformed space.
Returns
-------
tuple
Scaling of the camera in ``(x, y, z)``.
"""
if invert:
mtx = _vtk.vtkMatrix4x4()
mtx.DeepCopy(camera.GetModelTransformMatrix())
mtx.Invert()
else:
mtx = camera.GetModelTransformMatrix()
scaled = mtx.MultiplyDoublePoint((point[0], point[1], point[2], 0.0))
return (scaled[0], scaled[1], scaled[2])
class CameraPosition:
"""Container to hold camera location attributes.
Parameters
----------
position : sequence[float]
Position of the camera.
focal_point : sequence[float]
The focal point of the camera.
viewup : sequence[float]
View up of the camera.
"""
def __init__(self, position, focal_point, viewup):
"""Initialize a new camera position descriptor."""
self._position = position
self._focal_point = focal_point
self._viewup = viewup
def to_list(self):
"""Convert to a list of the position, focal point, and viewup.
Returns
-------
list
List of the position, focal point, and view up of the camera.
Examples
--------
>>> import pyvista as pv
>>> pl = pv.Plotter()
>>> pl.camera_position.to_list()
[(0.0, 0.0, 1.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0)]
"""
return [self._position, self._focal_point, self._viewup]
def __repr__(self):
"""List representation method."""
return "[{},\n {},\n {}]".format(*self.to_list())
def __getitem__(self, index):
"""Fetch a component by index location like a list."""
return self.to_list()[index]
def __eq__(self, other):
"""Comparison operator to act on list version of CameraPosition object."""
if isinstance(other, CameraPosition):
return self.to_list() == other.to_list()
return self.to_list() == other
@property
def position(self): # numpydoc ignore=RT01
"""Location of the camera in world coordinates."""
return self._position
@position.setter
def position(self, value): # numpydoc ignore=GL08
self._position = value
@property
def focal_point(self): # numpydoc ignore=RT01
"""Location of the camera's focus in world coordinates."""
return self._focal_point
@focal_point.setter
def focal_point(self, value): # numpydoc ignore=GL08
self._focal_point = value
@property
def viewup(self): # numpydoc ignore=RT01
"""Viewup vector of the camera."""
return self._viewup
@viewup.setter
def viewup(self, value): # numpydoc ignore=GL08
self._viewup = value
class Renderer(_vtk.vtkOpenGLRenderer):
"""Renderer class."""
# map camera_position string to an attribute
CAMERA_STR_ATTR_MAP: ClassVar[Dict[str, str]] = {
'xy': 'view_xy',
'xz': 'view_xz',
'yz': 'view_yz',
'yx': 'view_yx',
'zx': 'view_zx',
'zy': 'view_zy',
'iso': 'view_isometric',
}
def __init__(
self,
parent,
border=True,
border_color='w',
border_width=2.0,
): # numpydoc ignore=PR01,RT01
"""Initialize the renderer."""
super().__init__()
self._actors = {}
self.parent = parent # weakref.proxy to the plotter from Renderers
self._theme = parent.theme
self.bounding_box_actor = None
self.scale = [1.0, 1.0, 1.0]
self.AutomaticLightCreationOff()
self._labels = {} # tracks labeled actors
self._legend = None
self._floor = None
self._floors = []
self._floor_kwargs = []
# this keeps track of lights added manually to prevent garbage collection
self._lights = []
self._camera = Camera(self)
self.SetActiveCamera(self._camera)
self._empty_str = None # used to track reference to a vtkStringArray
self._shadow_pass = None
self._render_passes = RenderPasses(self)
self.cube_axes_actor = None
# This is a private variable to keep track of how many colorbars exist
# This allows us to keep adding colorbars without overlapping
self._scalar_bar_slots = set(range(MAX_N_COLOR_BARS))
self._scalar_bar_slot_lookup = {}
self._charts = None
self._border_actor = None
if border:
self.add_border(border_color, border_width)
self.set_color_cycler(self._theme.color_cycler)
@property
def camera_set(self) -> bool: # numpydoc ignore=RT01
"""Get or set whether this camera has been configured."""
if self.camera is None: # pragma: no cover
return False
return self.camera.is_set
@camera_set.setter
def camera_set(self, is_set: bool): # numpydoc ignore=GL08
self.camera.is_set = is_set
def set_color_cycler(self, color_cycler):
"""Set or reset this renderer's color cycler.
This color cycler is iterated over by each sequential :class:`add_mesh() <pyvista.Plotter.add_mesh>`
call to set the default color of the dataset being plotted.
When setting, the value must be either a list of color-like objects,
or a cycler of color-like objects. If the value passed is a single
string, it must be one of:
* ``'default'`` - Use the default color cycler (matches matplotlib's default)
* ``'matplotlib`` - Dynamically get matplotlib's current theme's color cycler.
* ``'all'`` - Cycle through all of the available colors in ``pyvista.plotting.colors.hexcolors``
Setting to ``None`` will disable the use of the color cycler on this
renderer.
Parameters
----------
color_cycler : str | cycler.Cycler | sequence[ColorLike]
The colors to cycle through.
Examples
--------
Set the default color cycler to iterate through red, green, and blue.
>>> import pyvista as pv
>>> pl = pv.Plotter()
>>> pl.renderer.set_color_cycler(['red', 'green', 'blue'])
>>> _ = pl.add_mesh(pv.Cone(center=(0, 0, 0))) # red
>>> _ = pl.add_mesh(pv.Cube(center=(1, 0, 0))) # green
>>> _ = pl.add_mesh(pv.Sphere(center=(1, 1, 0))) # blue
>>> _ = pl.add_mesh(pv.Cylinder(center=(0, 1, 0))) # red again
>>> pl.show()
"""
cycler = get_cycler(color_cycler)
if cycler is not None:
# Color cycler - call object to generate `cycle` instance
self._color_cycle = cycler()
else:
self._color_cycle = None
@property
def next_color(self): # numpydoc ignore=RT01
"""Return next color from this renderer's color cycler."""
if self._color_cycle is None:
return self._theme.color
return next(self._color_cycle)['color']
@property
def camera_position(self): # numpydoc ignore=RT01
"""Return or set the camera position of active render window.
Returns
-------
pyvista.CameraPosition
Camera position.
"""
return CameraPosition(
scale_point(self.camera, self.camera.position, invert=True),
scale_point(self.camera, self.camera.focal_point, invert=True),
self.camera.up,
)
@camera_position.setter
def camera_position(self, camera_location): # numpydoc ignore=GL08
if camera_location is None:
return
elif isinstance(camera_location, str):
camera_location = camera_location.lower()
if camera_location not in self.CAMERA_STR_ATTR_MAP:
raise InvalidCameraError(
'Invalid view direction. '
'Use one of the following:\n '
f'{", ".join(self.CAMERA_STR_ATTR_MAP)}',
)
getattr(self, self.CAMERA_STR_ATTR_MAP[camera_location])()
elif isinstance(camera_location[0], (int, float)):
if len(camera_location) != 3:
raise InvalidCameraError
self.view_vector(camera_location)
else:
# check if a valid camera position
if not isinstance(camera_location, CameraPosition):
if not len(camera_location) == 3 or any(len(item) != 3 for item in camera_location):
raise InvalidCameraError
# everything is set explicitly
self.camera.position = scale_point(self.camera, camera_location[0], invert=False)
self.camera.focal_point = scale_point(self.camera, camera_location[1], invert=False)
self.camera.up = camera_location[2]
# reset clipping range
self.reset_camera_clipping_range()
self.camera_set = True
self.Modified()
def reset_camera_clipping_range(self):
"""Reset the camera clipping range based on the bounds of the visible actors.
This ensures that no props are cut off
"""
self.ResetCameraClippingRange()
@property
def camera(self): # numpydoc ignore=RT01
"""Return the active camera for the rendering scene."""
return self._camera
@camera.setter
def camera(self, source): # numpydoc ignore=GL08
self._camera = source
self.SetActiveCamera(self._camera)
self.camera_position = CameraPosition(
scale_point(source, source.position, invert=True),
scale_point(source, source.focal_point, invert=True),
source.up,
)
self.Modified()
self.camera_set = True
@property
def bounds(self) -> BoundsLike: # numpydoc ignore=RT01
"""Return the bounds of all actors present in the rendering window."""
the_bounds = np.array([np.inf, -np.inf, np.inf, -np.inf, np.inf, -np.inf])
def _update_bounds(bounds):
def update_axis(ax):
if bounds[ax * 2] < the_bounds[ax * 2]:
the_bounds[ax * 2] = bounds[ax * 2]
if bounds[ax * 2 + 1] > the_bounds[ax * 2 + 1]:
the_bounds[ax * 2 + 1] = bounds[ax * 2 + 1]
for ax in range(3):
update_axis(ax)
for actor in self._actors.values():
if isinstance(actor, (_vtk.vtkCubeAxesActor, _vtk.vtkLightActor)):
continue
if (
hasattr(actor, 'GetBounds')
and actor.GetBounds() is not None
and id(actor) != id(self.bounding_box_actor)
):
_update_bounds(actor.GetBounds())
if np.any(np.abs(the_bounds)):
the_bounds[the_bounds == np.inf] = -1.0
the_bounds[the_bounds == -np.inf] = 1.0
return cast(BoundsLike, tuple(the_bounds))
@property
def length(self): # numpydoc ignore=RT01
"""Return the length of the diagonal of the bounding box of the scene.
Returns
-------
float
Length of the diagional of the bounding box.
"""
return pyvista.Box(self.bounds).length
@property
def center(self): # numpydoc ignore=RT01
"""Return the center of the bounding box around all data present in the scene.
Returns
-------
list
Cartesian coordinates of the center.
"""
bounds = self.bounds
x = (bounds[1] + bounds[0]) / 2
y = (bounds[3] + bounds[2]) / 2
z = (bounds[5] + bounds[4]) / 2
return [x, y, z]
@property
def background_color(self): # numpydoc ignore=RT01
"""Return the background color of this renderer."""
return Color(self.GetBackground())
@background_color.setter
def background_color(self, color): # numpydoc ignore=GL08
self.set_background(color)
self.Modified()
def _before_render_event(self, *args, **kwargs):
"""Notify all charts about render event."""
for chart in self._charts:
chart._render_event(*args, **kwargs)
def enable_depth_peeling(self, number_of_peels=None, occlusion_ratio=None):
"""Enable depth peeling to improve rendering of translucent geometry.
Parameters
----------
number_of_peels : int, optional
The maximum number of peeling layers. Initial value is 4
and is set in the ``pyvista.global_theme``. A special value of
0 means no maximum limit. It has to be a positive value.
occlusion_ratio : float, optional
The threshold under which the depth peeling algorithm
stops to iterate over peel layers. This is the ratio of
the number of pixels that have been touched by the last
layer over the total number of pixels of the viewport
area. Initial value is 0.0, meaning rendering has to be
exact. Greater values may speed up the rendering with
small impact on the quality.
Returns
-------
bool
If depth peeling is supported.
"""
if number_of_peels is None:
number_of_peels = self._theme.depth_peeling.number_of_peels
if occlusion_ratio is None:
occlusion_ratio = self._theme.depth_peeling.occlusion_ratio
depth_peeling_supported = check_depth_peeling(number_of_peels, occlusion_ratio)
if depth_peeling_supported:
self.SetUseDepthPeeling(True)
self.SetMaximumNumberOfPeels(number_of_peels)
self.SetOcclusionRatio(occlusion_ratio)
self.Modified()
return depth_peeling_supported
def disable_depth_peeling(self):
"""Disable depth peeling."""
self.SetUseDepthPeeling(False)
self.Modified()
def enable_anti_aliasing(self, aa_type='ssaa'):
"""Enable anti-aliasing.
Parameters
----------
aa_type : str, default: 'ssaa'
Anti-aliasing type. Either ``"fxaa"`` or ``"ssaa"``.
"""
if not isinstance(aa_type, str):
raise TypeError(f'`aa_type` must be a string, not {type(aa_type)}')
aa_type = aa_type.lower()
if aa_type == 'fxaa':
if uses_egl(): # pragma: no cover
# only display the warning when not building documentation
if not pyvista.BUILDING_GALLERY:
warnings.warn(
"VTK compiled with OSMesa/EGL does not properly support "
"FXAA anti-aliasing and SSAA will be used instead.",
)
self._render_passes.enable_ssaa_pass()
return
self._enable_fxaa()
elif aa_type == 'ssaa':
self._render_passes.enable_ssaa_pass()
else:
raise ValueError(f'Invalid `aa_type` "{aa_type}". Should be either "fxaa" or "ssaa"')
def disable_anti_aliasing(self):
"""Disable all anti-aliasing."""
self._render_passes.disable_ssaa_pass()
self.SetUseFXAA(False)
self.Modified()
def _enable_fxaa(self):
"""Enable FXAA anti-aliasing."""
self.SetUseFXAA(True)
self.Modified()
def _disable_fxaa(self):
"""Disable FXAA anti-aliasing."""
self.SetUseFXAA(False)
self.Modified()
def add_border(self, color='white', width=2.0):
"""Add borders around the frame.
Parameters
----------
color : ColorLike, default: "white"
Color of the border.
width : float, default: 2.0
Width of the border.
Returns
-------
vtk.vtkActor2D
Border actor.
"""
points = np.array([[1.0, 1.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 0.0], [1.0, 0.0, 0.0]])
lines = np.array([[2, 0, 1], [2, 1, 2], [2, 2, 3], [2, 3, 0]]).ravel()
poly = pyvista.PolyData()
poly.points = points
poly.lines = lines
coordinate = _vtk.vtkCoordinate()
coordinate.SetCoordinateSystemToNormalizedViewport()
mapper = _vtk.vtkPolyDataMapper2D()
mapper.SetInputData(poly)
mapper.SetTransformCoordinate(coordinate)
actor = _vtk.vtkActor2D()
actor.SetMapper(mapper)
actor.GetProperty().SetColor(Color(color).float_rgb)
actor.GetProperty().SetLineWidth(width)
self.AddViewProp(actor)
self.Modified()
self._border_actor = actor
return actor
@property
def has_border(self): # numpydoc ignore=RT01
"""Return if the renderer has a border."""
return self._border_actor is not None
@property
def border_width(self): # numpydoc ignore=RT01
"""Return the border width."""
if self.has_border:
return self._border_actor.GetProperty().GetLineWidth()
return 0
@property
def border_color(self): # numpydoc ignore=RT01
"""Return the border color."""
if self.has_border:
return Color(self._border_actor.GetProperty().GetColor())
return None
def add_chart(self, chart, *charts):
"""Add a chart to this renderer.
Parameters
----------
chart : Chart
Chart to add to renderer.
*charts : Chart
Charts to add to renderer.
Examples
--------
>>> import pyvista as pv
>>> chart = pv.Chart2D()
>>> _ = chart.plot(range(10), range(10))
>>> pl = pv.Plotter()
>>> pl.add_chart(chart)
>>> pl.show()
"""
if _vtk.vtkRenderingContextOpenGL2 is None: # pragma: no cover
from pyvista.core.errors import VTKVersionError
raise VTKVersionError(
"VTK is missing vtkRenderingContextOpenGL2. Try installing VTK v9.1.0 or newer.",
)
# lazy instantiation here to avoid creating the charts object unless needed.
if self._charts is None:
self._charts = Charts(self)
self.AddObserver("StartEvent", partial(try_callback, self._before_render_event))
self._charts.add_chart(chart, *charts)
@property
def has_charts(self): # numpydoc ignore=RT01
"""Return whether this renderer has charts."""
return self._charts is not None and len(self._charts) > 0
def get_charts(self): # numpydoc ignore=RT01
"""Return a list of all charts in this renderer.
Examples
--------
.. pyvista-plot::
:force_static:
>>> import pyvista as pv
>>> chart = pv.Chart2D()
>>> _ = chart.line([1, 2, 3], [0, 1, 0])
>>> pl = pv.Plotter()
>>> pl.add_chart(chart)
>>> chart is pl.renderer.get_charts()[0]
True
"""
return [*self._charts] if self.has_charts else []
@wraps(Charts.set_interaction)
def set_chart_interaction(self, interactive, toggle=False): # numpydoc ignore=PR01,RT01
"""Wrap ``Charts.set_interaction``."""
return self._charts.set_interaction(interactive, toggle) if self.has_charts else []
@wraps(Charts.get_charts_by_pos)
def _get_charts_by_pos(self, pos):
"""Wrap ``Charts.get_charts_by_pos``."""
return self._charts.get_charts_by_pos(pos) if self.has_charts else []
def remove_chart(self, chart_or_index):
"""Remove a chart from this renderer.
Parameters
----------
chart_or_index : Chart or int
Either the chart to remove from this renderer or its index in the collection of charts.
Examples
--------
First define a function to add two charts to a renderer.
>>> import pyvista as pv
>>> def plotter_with_charts():
... pl = pv.Plotter()
... pl.background_color = 'w'
... chart_left = pv.Chart2D(size=(0.5, 1))
... _ = chart_left.line([0, 1, 2], [2, 1, 3])
... pl.add_chart(chart_left)
... chart_right = pv.Chart2D(size=(0.5, 1), loc=(0.5, 0))
... _ = chart_right.line([0, 1, 2], [3, 1, 2])
... pl.add_chart(chart_right)
... return pl, chart_left, chart_right
...
>>> pl, *_ = plotter_with_charts()
>>> pl.show()
Now reconstruct the same plotter but remove the right chart by index.
>>> pl, *_ = plotter_with_charts()
>>> pl.remove_chart(1)
>>> pl.show()
Finally, remove the left chart by reference.
>>> pl, chart_left, chart_right = plotter_with_charts()
>>> pl.remove_chart(chart_left)
>>> pl.show()
"""
if self.has_charts:
self._charts.remove_chart(chart_or_index)
@property
def actors(self): # numpydoc ignore=RT01
"""Return a dictionary of actors assigned to this renderer."""
return self._actors
def add_actor(
self,
actor,
reset_camera=False,
name=None,
culling=False,
pickable=True,
render=True,
remove_existing_actor=True,
):
"""Add an actor to render window.
Creates an actor if input is a mapper.
Parameters
----------
actor : vtk.vtkActor | vtk.vtkMapper | pyvista.Actor
The actor to be added. Can be either ``vtkActor`` or ``vtkMapper``.
reset_camera : bool, default: False
Resets the camera when ``True``.
name : str, optional
Name to assign to the actor. Defaults to the memory address.
culling : str, default: False
Does not render faces that are culled. Options are
``'front'`` or ``'back'``. This can be helpful for dense
surface meshes, especially when edges are visible, but can
cause flat meshes to be partially displayed.
pickable : bool, default: True
Whether to allow this actor to be pickable within the
render window.
render : bool, default: True
If the render window is being shown, trigger a render
after adding the actor.
remove_existing_actor : bool, default: True
Removes any existing actor if the named actor ``name`` is already
present.
Returns
-------
actor : vtk.vtkActor or pyvista.Actor
The actor.
actor_properties : vtk.Properties
Actor properties.
"""
# Remove actor by that name if present
rv = None
if name and remove_existing_actor:
rv = self.remove_actor(name, reset_camera=False, render=False)
if isinstance(actor, _vtk.vtkMapper):
actor = Actor(mapper=actor, name=name)
if isinstance(actor, Actor) and name:
# WARNING: this will override the name if already set on Actor
actor.name = name
if name is None:
# Fallback for non-wrapped actors
# e.g., vtkScalarBarActor
name = actor.name if isinstance(actor, Actor) else actor.GetAddressAsString("")
actor.SetPickable(pickable)
# Apply this renderer's scale to the actor (which can be further scaled)
if hasattr(actor, 'SetScale'):
actor.SetScale(np.array(actor.GetScale()) * np.array(self.scale))
self.AddActor(actor) # must add actor before resetting camera
self._actors[name] = actor
if reset_camera or not self.camera_set and reset_camera is None and not rv:
self.reset_camera(render)
elif render:
self.parent.render()
self.update_bounds_axes()
if isinstance(culling, str):
culling = culling.lower()
if culling:
if culling in [True, 'back', 'backface', 'b']:
with contextlib.suppress(AttributeError):
actor.GetProperty().BackfaceCullingOn()
elif culling in ['front', 'frontface', 'f']:
with contextlib.suppress(AttributeError):
actor.GetProperty().FrontfaceCullingOn()
else:
raise ValueError(f'Culling option ({culling}) not understood.')
self.Modified()
prop = None
if hasattr(actor, 'GetProperty'):
prop = actor.GetProperty()
return actor, prop
def add_axes_at_origin(
self,
x_color=None,
y_color=None,
z_color=None,
xlabel='X',
ylabel='Y',
zlabel='Z',
line_width=2,
labels_off=False,
):
"""Add axes actor at origin.
Parameters
----------
x_color : ColorLike, optional
The color of the x axes arrow.
y_color : ColorLike, optional
The color of the y axes arrow.
z_color : ColorLike, optional
The color of the z axes arrow.
xlabel : str, default: "X"
The label of the x axes arrow.
ylabel : str, default: "Y"
The label of the y axes arrow.
zlabel : str, default: "Z"
The label of the z axes arrow.
line_width : int, default: 2
Width of the arrows.
labels_off : bool, default: False
Disables the label text when ``True``.
Returns
-------
vtk.vtkAxesActor
Actor of the axes.
Examples
--------
>>> import pyvista as pv
>>> pl = pv.Plotter()
>>> _ = pl.add_mesh(pv.Sphere(center=(2, 0, 0)), color='r')
>>> _ = pl.add_mesh(pv.Sphere(center=(0, 2, 0)), color='g')
>>> _ = pl.add_mesh(pv.Sphere(center=(0, 0, 2)), color='b')
>>> _ = pl.add_axes_at_origin()
>>> pl.show()
"""
self._marker_actor = create_axes_marker(
line_width=line_width,
x_color=x_color,
y_color=y_color,
z_color=z_color,
xlabel=xlabel,
ylabel=ylabel,
zlabel=zlabel,
labels_off=labels_off,
)
self.AddActor(self._marker_actor)
memory_address = self._marker_actor.GetAddressAsString("")
self._actors[memory_address] = self._marker_actor
self.Modified()
return self._marker_actor
def add_orientation_widget(
self,
actor,
interactive=None,
color=None,
opacity=1.0,
viewport=None,
):
"""Use the given actor in an orientation marker widget.
Color and opacity are only valid arguments if a mesh is passed.
Parameters
----------
actor : vtk.vtkActor | pyvista.DataSet
The mesh or actor to use as the marker.
interactive : bool, optional
Control if the orientation widget is interactive. By
default uses the value from
:attr:`pyvista.global_theme.interactive
<pyvista.plotting.themes.Theme.interactive>`.
color : ColorLike, optional
The color of the actor. This only applies if ``actor`` is
a :class:`pyvista.DataSet`.
opacity : int | float, default: 1.0
Opacity of the marker.
viewport : sequence[float], optional
Viewport ``(xstart, ystart, xend, yend)`` of the widget.
Returns
-------
vtk.vtkOrientationMarkerWidget
Orientation marker widget.
Examples
--------
Use an Arrow as the orientation widget.
>>> import pyvista as pv
>>> pl = pv.Plotter()
>>> actor = pl.add_mesh(pv.Cube(), show_edges=True)
>>> actor = pl.add_orientation_widget(pv.Arrow(), color='r')
>>> pl.show()
"""
if isinstance(actor, pyvista.DataSet):