vedo.visual
Base classes to manage visualization and apperance of objects and their properties.
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3import os 4from typing import Union 5from typing_extensions import Self 6from weakref import ref as weak_ref_to 7import numpy as np 8 9import vedo.vtkclasses as vtki 10 11import vedo 12from vedo import colors 13from vedo import utils 14 15 16__docformat__ = "google" 17 18__doc__ = """ 19Base classes to manage visualization and apperance of objects and their properties. 20""" 21 22__all__ = [ 23 "CommonVisual", 24 "PointsVisual", 25 "VolumeVisual", 26 "MeshVisual", 27 "ImageVisual", 28 "Actor2D", 29 "LightKit", 30] 31 32 33################################################### 34class CommonVisual: 35 """Class to manage the visual aspects common to all objects.""" 36 37 def __init__(self): 38 # print("init CommonVisual", type(self)) 39 40 self.properties = None 41 42 self.scalarbar = None 43 self.pipeline = None 44 45 self.trail = None 46 self.trail_points = [] 47 self.trail_segment_size = 0 48 self.trail_offset = None 49 50 self.shadows = [] 51 52 self.axes = None 53 self.picked3d = None 54 55 self.rendered_at = set() 56 57 self._ligthingnr = 0 # index of the lighting mode changed from CLI 58 self._cmap_name = "" # remember the cmap name for self._keypress 59 self._caption = None 60 61 62 def print(self): 63 """Print object info.""" 64 print(self.__str__()) 65 return self 66 67 @property 68 def LUT(self) -> np.ndarray: 69 """Return the lookup table of the object as a numpy object.""" 70 try: 71 _lut = self.mapper.GetLookupTable() 72 values = [] 73 for i in range(_lut.GetTable().GetNumberOfTuples()): 74 # print("LUT i =", i, "value =", _lut.GetTableValue(i)) 75 values.append(_lut.GetTableValue(i)) 76 return np.array(values) 77 except AttributeError: 78 return np.array([], dtype=float) 79 80 @LUT.setter 81 def LUT(self, arr): 82 """ 83 Set the lookup table of the object from a numpy or `vtkLookupTable` object. 84 Consider using `cmap()` or `build_lut()` instead as it allows 85 to set the range of the LUT and to use a string name for the color map. 86 """ 87 if isinstance(arr, vtki.vtkLookupTable): 88 newlut = arr 89 self.mapper.SetScalarRange(newlut.GetRange()) 90 else: 91 newlut = vtki.vtkLookupTable() 92 newlut.SetNumberOfTableValues(len(arr)) 93 if len(arr[0]) == 3: 94 arr = np.insert(arr, 3, 1, axis=1) 95 for i, v in enumerate(arr): 96 newlut.SetTableValue(i, v) 97 newlut.SetRange(self.mapper.GetScalarRange()) 98 # print("newlut.GetRange() =", newlut.GetRange()) 99 # print("self.mapper.GetScalarRange() =", self.mapper.GetScalarRange()) 100 newlut.Build() 101 self.mapper.SetLookupTable(newlut) 102 self.mapper.ScalarVisibilityOn() 103 104 def scalar_range(self, vmin=None, vmax=None): 105 """Set the range of the scalar value for visualization.""" 106 if vmin is None and vmax is None: 107 return np.array(self.mapper.GetScalarRange()) 108 if vmax is None: 109 vmin, vmax = vmin # assume it is a list 110 self.mapper.SetScalarRange(float(vmin), float(vmax)) 111 return self 112 113 def add_observer(self, event_name, func, priority=0) -> int: 114 """Add a callback function that will be called when an event occurs.""" 115 event_name = utils.get_vtk_name_event(event_name) 116 idd = self.actor.AddObserver(event_name, func, priority) 117 return idd 118 119 def invoke_event(self, event_name) -> Self: 120 """Invoke an event.""" 121 event_name = utils.get_vtk_name_event(event_name) 122 self.actor.InvokeEvent(event_name) 123 return self 124 125 # def abort_event(self, obs_id): 126 # """Abort an event.""" 127 # cmd = self.actor.GetCommand(obs_id) # vtkCommand 128 # if cmd: 129 # cmd.AbortFlagOn() 130 # return self 131 132 def show(self, **options) -> Union["vedo.Plotter", None]: 133 """ 134 Create on the fly an instance of class `Plotter` or use the last existing one to 135 show one single object. 136 137 This method is meant as a shortcut. If more than one object needs to be visualised 138 please use the syntax `show(mesh1, mesh2, volume, ..., options)`. 139 140 Returns the `Plotter` class instance. 141 """ 142 return vedo.plotter.show(self, **options) 143 144 def thumbnail(self, zoom=1.25, size=(200, 200), bg="white", azimuth=0, elevation=0, axes=False) -> np.ndarray: 145 """Build a thumbnail of the object and return it as an array.""" 146 # speed is about 20Hz for size=[200,200] 147 ren = vtki.vtkRenderer() 148 149 actor = self.actor 150 if isinstance(self, vedo.UnstructuredGrid): 151 geo = vtki.new("GeometryFilter") 152 geo.SetInputData(self.dataset) 153 geo.Update() 154 actor = vedo.Mesh(geo.GetOutput()).cmap("rainbow").actor 155 156 ren.AddActor(actor) 157 if axes: 158 axes = vedo.addons.Axes(self) 159 ren.AddActor(axes.actor) 160 ren.ResetCamera() 161 cam = ren.GetActiveCamera() 162 cam.Zoom(zoom) 163 cam.Elevation(elevation) 164 cam.Azimuth(azimuth) 165 166 ren_win = vtki.vtkRenderWindow() 167 ren_win.SetOffScreenRendering(True) 168 ren_win.SetSize(size) 169 ren.SetBackground(colors.get_color(bg)) 170 ren_win.AddRenderer(ren) 171 ren.ResetCameraClippingRange() 172 ren_win.Render() 173 174 nx, ny = ren_win.GetSize() 175 arr = vtki.vtkUnsignedCharArray() 176 ren_win.GetRGBACharPixelData(0, 0, nx - 1, ny - 1, 0, arr) 177 narr = utils.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3]) 178 narr = np.ascontiguousarray(np.flip(narr, axis=0)) 179 180 ren.RemoveActor(actor) 181 if axes: 182 ren.RemoveActor(axes.actor) 183 ren_win.Finalize() 184 del ren_win 185 return narr 186 187 def pickable(self, value=None) -> Self: 188 """Set/get the pickability property of an object.""" 189 if value is None: 190 return self.actor.GetPickable() 191 self.actor.SetPickable(value) 192 return self 193 194 def use_bounds(self, value=True) -> Self: 195 """ 196 Instruct the current camera to either take into account or ignore 197 the object bounds when resetting. 198 """ 199 self.actor.SetUseBounds(value) 200 return self 201 202 def draggable(self, value=None) -> Self: # NOT FUNCTIONAL? 203 """Set/get the draggability property of an object.""" 204 if value is None: 205 return self.actor.GetDragable() 206 self.actor.SetDragable(value) 207 return self 208 209 def on(self) -> Self: 210 """Switch on object visibility. Object is not removed.""" 211 self.actor.VisibilityOn() 212 try: 213 self.scalarbar.actor.VisibilityOn() 214 except AttributeError: 215 pass 216 try: 217 self.trail.actor.VisibilityOn() 218 except AttributeError: 219 pass 220 try: 221 for sh in self.shadows: 222 sh.actor.VisibilityOn() 223 except AttributeError: 224 pass 225 return self 226 227 def off(self) -> Self: 228 """Switch off object visibility. Object is not removed.""" 229 self.actor.VisibilityOff() 230 try: 231 self.scalarbar.actor.VisibilityOff() 232 except AttributeError: 233 pass 234 try: 235 self.trail.actor.VisibilityOff() 236 except AttributeError: 237 pass 238 try: 239 for sh in self.shadows: 240 sh.actor.VisibilityOff() 241 except AttributeError: 242 pass 243 return self 244 245 def toggle(self) -> Self: 246 """Toggle object visibility on/off.""" 247 v = self.actor.GetVisibility() 248 if v: 249 self.off() 250 else: 251 self.on() 252 return self 253 254 def add_scalarbar( 255 self, 256 title="", 257 pos=(), 258 size=(80, 400), 259 font_size=14, 260 title_yoffset=15, 261 nlabels=None, 262 c=None, 263 horizontal=False, 264 use_alpha=True, 265 label_format=":6.3g", 266 ) -> Self: 267 """ 268 Add a 2D scalar bar for the specified object. 269 270 Arguments: 271 title : (str) 272 scalar bar title 273 pos : (list) 274 position coordinates of the bottom left corner. 275 Can also be a pair of (x,y) values in the range [0,1] 276 to indicate the position of the bottom left and top right corners. 277 size : (float,float) 278 size of the scalarbar in number of pixels (width, height) 279 font_size : (float) 280 size of font for title and numeric labels 281 title_yoffset : (float) 282 vertical space offset between title and color scalarbar 283 nlabels : (int) 284 number of numeric labels 285 c : (list) 286 color of the scalar bar text 287 horizontal : (bool) 288 lay the scalarbar horizontally 289 use_alpha : (bool) 290 render transparency in the color bar itself 291 label_format : (str) 292 c-style format string for numeric labels 293 294 Examples: 295 - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py) 296 - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py) 297 """ 298 plt = vedo.plotter_instance 299 300 if plt and plt.renderer: 301 c = (0.9, 0.9, 0.9) 302 if np.sum(plt.renderer.GetBackground()) > 1.5: 303 c = (0.1, 0.1, 0.1) 304 if isinstance(self.scalarbar, vtki.vtkActor): 305 plt.renderer.RemoveActor(self.scalarbar) 306 elif isinstance(self.scalarbar, vedo.Assembly): 307 for a in self.scalarbar.unpack(): 308 plt.renderer.RemoveActor(a) 309 if c is None: 310 c = "gray" 311 312 sb = vedo.addons.ScalarBar( 313 self, 314 title, 315 pos, 316 size, 317 font_size, 318 title_yoffset, 319 nlabels, 320 c, 321 horizontal, 322 use_alpha, 323 label_format, 324 ) 325 self.scalarbar = sb 326 return self 327 328 def add_scalarbar3d( 329 self, 330 title="", 331 pos=None, 332 size=(0, 0), 333 title_font="", 334 title_xoffset=-1.2, 335 title_yoffset=0.0, 336 title_size=1.5, 337 title_rotation=0.0, 338 nlabels=9, 339 label_font="", 340 label_size=1, 341 label_offset=0.375, 342 label_rotation=0, 343 label_format="", 344 italic=0, 345 c=None, 346 draw_box=True, 347 above_text=None, 348 below_text=None, 349 nan_text="NaN", 350 categories=None, 351 ) -> Self: 352 """ 353 Associate a 3D scalar bar to the object and add it to the scene. 354 The new scalarbar object (Assembly) will be accessible as obj.scalarbar 355 356 Arguments: 357 size : (list) 358 (thickness, length) of scalarbar 359 title : (str) 360 scalar bar title 361 title_xoffset : (float) 362 horizontal space btw title and color scalarbar 363 title_yoffset : (float) 364 vertical space offset 365 title_size : (float) 366 size of title wrt numeric labels 367 title_rotation : (float) 368 title rotation in degrees 369 nlabels : (int) 370 number of numeric labels 371 label_font : (str) 372 font type for labels 373 label_size : (float) 374 label scale factor 375 label_offset : (float) 376 space btw numeric labels and scale 377 label_rotation : (float) 378 label rotation in degrees 379 label_format : (str) 380 label format for floats and integers (e.g. `':.2f'`) 381 draw_box : (bool) 382 draw a box around the colorbar 383 categories : (list) 384 make a categorical scalarbar, 385 the input list will have the format `[value, color, alpha, textlabel]` 386 387 Examples: 388 - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py) 389 """ 390 plt = vedo.plotter_instance 391 if plt and c is None: # automatic black or white 392 c = (0.9, 0.9, 0.9) 393 if np.sum(vedo.get_color(plt.backgrcol)) > 1.5: 394 c = (0.1, 0.1, 0.1) 395 if c is None: 396 c = (0, 0, 0) 397 c = vedo.get_color(c) 398 399 self.scalarbar = vedo.addons.ScalarBar3D( 400 self, 401 title, 402 pos, 403 size, 404 title_font, 405 title_xoffset, 406 title_yoffset, 407 title_size, 408 title_rotation, 409 nlabels, 410 label_font, 411 label_size, 412 label_offset, 413 label_rotation, 414 label_format, 415 italic, 416 c, 417 draw_box, 418 above_text, 419 below_text, 420 nan_text, 421 categories, 422 ) 423 return self 424 425 def color(self, col, alpha=None, vmin=None, vmax=None): 426 """ 427 Assign a color or a set of colors along the range of the scalar value. 428 A single constant color can also be assigned. 429 Any matplotlib color map name is also accepted, e.g. `volume.color('jet')`. 430 431 E.g.: say that your cells scalar runs from -3 to 6, 432 and you want -3 to show red and 1.5 violet and 6 green, then just set: 433 434 `volume.color(['red', 'violet', 'green'])` 435 436 You can also assign a specific color to a aspecific value with eg.: 437 438 `volume.color([(0,'red'), (0.5,'violet'), (1,'green')])` 439 440 Arguments: 441 alpha : (list) 442 use a list to specify transparencies along the scalar range 443 vmin : (float) 444 force the min of the scalar range to be this value 445 vmax : (float) 446 force the max of the scalar range to be this value 447 """ 448 # supersedes method in Points, Mesh 449 450 if col is None: 451 return self 452 453 if vmin is None: 454 vmin, _ = self.dataset.GetScalarRange() 455 if vmax is None: 456 _, vmax = self.dataset.GetScalarRange() 457 ctf = self.properties.GetRGBTransferFunction() 458 ctf.RemoveAllPoints() 459 460 if utils.is_sequence(col): 461 if utils.is_sequence(col[0]) and len(col[0]) == 2: 462 # user passing [(value1, color1), ...] 463 for x, ci in col: 464 r, g, b = colors.get_color(ci) 465 ctf.AddRGBPoint(x, r, g, b) 466 # colors.printc('color at', round(x, 1), 467 # 'set to', colors.get_color_name((r, g, b)), bold=0) 468 else: 469 # user passing [color1, color2, ..] 470 for i, ci in enumerate(col): 471 r, g, b = colors.get_color(ci) 472 x = vmin + (vmax - vmin) * i / (len(col) - 1) 473 ctf.AddRGBPoint(x, r, g, b) 474 elif isinstance(col, str): 475 if col in colors.colors.keys() or col in colors.color_nicks.keys(): 476 r, g, b = colors.get_color(col) 477 ctf.AddRGBPoint(vmin, r, g, b) # constant color 478 ctf.AddRGBPoint(vmax, r, g, b) 479 else: # assume it's a colormap 480 for x in np.linspace(vmin, vmax, num=64, endpoint=True): 481 r, g, b = colors.color_map(x, name=col, vmin=vmin, vmax=vmax) 482 ctf.AddRGBPoint(x, r, g, b) 483 elif isinstance(col, int): 484 r, g, b = colors.get_color(col) 485 ctf.AddRGBPoint(vmin, r, g, b) # constant color 486 ctf.AddRGBPoint(vmax, r, g, b) 487 else: 488 vedo.logger.warning(f"in color() unknown input type {type(col)}") 489 490 if alpha is not None: 491 self.alpha(alpha, vmin=vmin, vmax=vmax) 492 return self 493 494 def alpha(self, alpha, vmin=None, vmax=None) -> Self: 495 """ 496 Assign a set of tranparencies along the range of the scalar value. 497 A single constant value can also be assigned. 498 499 E.g.: say `alpha=(0.0, 0.3, 0.9, 1)` and the scalar range goes from -10 to 150. 500 Then all cells with a value close to -10 will be completely transparent, cells at 1/4 501 of the range will get an alpha equal to 0.3 and voxels with value close to 150 502 will be completely opaque. 503 504 As a second option one can set explicit (x, alpha_x) pairs to define the transfer function. 505 506 E.g.: say `alpha=[(-5, 0), (35, 0.4) (123,0.9)]` and the scalar range goes from -10 to 150. 507 Then all cells below -5 will be completely transparent, cells with a scalar value of 35 508 will get an opacity of 40% and above 123 alpha is set to 90%. 509 """ 510 if vmin is None: 511 vmin, _ = self.dataset.GetScalarRange() 512 if vmax is None: 513 _, vmax = self.dataset.GetScalarRange() 514 otf = self.properties.GetScalarOpacity() 515 otf.RemoveAllPoints() 516 517 if utils.is_sequence(alpha): 518 alpha = np.array(alpha) 519 if len(alpha.shape) == 1: # user passing a flat list e.g. (0.0, 0.3, 0.9, 1) 520 for i, al in enumerate(alpha): 521 xalpha = vmin + (vmax - vmin) * i / (len(alpha) - 1) 522 # Create transfer mapping scalar value to opacity 523 otf.AddPoint(xalpha, al) 524 # print("alpha at", round(xalpha, 1), "\tset to", al) 525 elif len(alpha.shape) == 2: # user passing [(x0,alpha0), ...] 526 otf.AddPoint(vmin, alpha[0][1]) 527 for xalpha, al in alpha: 528 # Create transfer mapping scalar value to opacity 529 otf.AddPoint(xalpha, al) 530 otf.AddPoint(vmax, alpha[-1][1]) 531 532 else: 533 534 otf.AddPoint(vmin, alpha) # constant alpha 535 otf.AddPoint(vmax, alpha) 536 537 return self 538 539 540######################################################################################## 541class Actor2D(vtki.vtkActor2D): 542 """Wrapping of `vtkActor2D`.""" 543 544 def __init__(self): 545 """Manage 2D objects.""" 546 super().__init__() 547 548 self.dataset = None 549 self.properties = self.GetProperty() 550 self.name = "" 551 self.filename = "" 552 self.file_size = 0 553 self.pipeline = None 554 self.shape = [] # for images 555 556 @property 557 def mapper(self): 558 """Get the internal vtkMapper.""" 559 return self.GetMapper() 560 561 @mapper.setter 562 def mapper(self, amapper): 563 """Set the internal vtkMapper.""" 564 self.SetMapper(amapper) 565 566 def layer(self, value=None): 567 """Set/Get the layer number in the overlay planes into which to render.""" 568 if value is None: 569 return self.GetLayerNumber() 570 self.SetLayerNumber(value) 571 return self 572 573 def pos(self, px=None, py=None) -> Union[np.ndarray, Self]: 574 """Set/Get the screen-coordinate position.""" 575 if isinstance(px, str): 576 vedo.logger.error("Use string descriptors only inside the constructor") 577 return self 578 if px is None: 579 return np.array(self.GetPosition(), dtype=int) 580 if py is not None: 581 p = [px, py] 582 else: 583 p = px 584 assert len(p) == 2, "Error: len(pos) must be 2 for Actor2D" 585 self.SetPosition(p) 586 return self 587 588 def coordinate_system(self, value=None) -> Self: 589 """ 590 Set/get the coordinate system which this coordinate is defined in. 591 592 The options are: 593 0. Display 594 1. Normalized Display 595 2. Viewport 596 3. Normalized Viewport 597 4. View 598 5. Pose 599 6. World 600 """ 601 coor = self.GetPositionCoordinate() 602 if value is None: 603 return coor.GetCoordinateSystem() 604 coor.SetCoordinateSystem(value) 605 return self 606 607 def on(self) -> Self: 608 """Set object visibility.""" 609 self.VisibilityOn() 610 return self 611 612 def off(self) -> Self: 613 """Set object visibility.""" 614 self.VisibilityOn() 615 return self 616 617 def toggle(self) -> Self: 618 """Toggle object visibility.""" 619 self.SetVisibility(not self.GetVisibility()) 620 return self 621 622 def visibility(value=None) -> bool: 623 """Get/Set object visibility.""" 624 if value is not None: 625 self.SetVisibility(value) 626 return self.GetVisibility() 627 628 def pickable(self, value=True) -> Self: 629 """Set object pickability.""" 630 self.SetPickable(value) 631 return self 632 633 def color(self, value=None) -> Union[np.ndarray, Self]: 634 """Set/Get the object color.""" 635 if value is None: 636 return self.properties.GetColor() 637 self.properties.SetColor(colors.get_color(value)) 638 return self 639 640 def c(self, value=None) -> Union[np.ndarray, Self]: 641 """Set/Get the object color.""" 642 return self.color(value) 643 644 def alpha(self, value=None) -> Union[float, Self]: 645 """Set/Get the object opacity.""" 646 if value is None: 647 return self.properties.GetOpacity() 648 self.properties.SetOpacity(value) 649 return self 650 651 def ps(self, point_size=None) -> Union[int, Self]: 652 if point_size is None: 653 return self.properties.GetPointSize() 654 self.properties.SetPointSize(point_size) 655 return self 656 657 def lw(self, line_width=None) -> Union[int, Self]: 658 if line_width is None: 659 return self.properties.GetLineWidth() 660 self.properties.SetLineWidth(line_width) 661 return self 662 663 def ontop(self, value=True) -> Self: 664 """Keep the object always on top of everything else.""" 665 if value: 666 self.properties.SetDisplayLocationToForeground() 667 else: 668 self.properties.SetDisplayLocationToBackground() 669 return self 670 671 def add_observer(self, event_name, func, priority=0) -> int: 672 """Add a callback function that will be called when an event occurs.""" 673 event_name = utils.get_vtk_name_event(event_name) 674 idd = self.AddObserver(event_name, func, priority) 675 return idd 676 677######################################################################################## 678class Actor3DHelper: 679 680 def apply_transform(self, LT) -> Self: 681 """Apply a linear transformation to the actor.""" 682 self.transform.concatenate(LT) 683 self.actor.SetPosition(self.transform.T.GetPosition()) 684 self.actor.SetOrientation(self.transform.T.GetOrientation()) 685 self.actor.SetScale(self.transform.T.GetScale()) 686 return self 687 688 def pos(self, x=None, y=None, z=None) -> Union[np.ndarray, Self]: 689 """Set/Get object position.""" 690 if x is None: # get functionality 691 return self.transform.position 692 693 if z is None and y is None: # assume x is of the form (x,y,z) 694 if len(x) == 3: 695 x, y, z = x 696 else: 697 x, y = x 698 z = 0 699 elif z is None: # assume x,y is of the form x, y 700 z = 0 701 702 q = self.transform.position 703 LT = vedo.LinearTransform().translate([x,y,z]-q) 704 return self.apply_transform(LT) 705 706 def shift(self, dx, dy=0, dz=0) -> Self: 707 """Add a vector to the current object position.""" 708 if vedo.utils.is_sequence(dx): 709 vedo.utils.make3d(dx) 710 dx, dy, dz = dx 711 LT = vedo.LinearTransform().translate([dx, dy, dz]) 712 return self.apply_transform(LT) 713 714 def origin(self, point=None) -> Union[np.ndarray, Self]: 715 """ 716 Set/get origin of object. 717 Useful for defining pivoting point when rotating and/or scaling. 718 """ 719 if point is None: 720 return np.array(self.actor.GetOrigin()) 721 self.actor.SetOrigin(point) 722 return self 723 724 def scale(self, s, origin=True) -> Self: 725 """Multiply object size by `s` factor.""" 726 LT = vedo.LinearTransform().scale(s, origin=origin) 727 return self.apply_transform(LT) 728 729 def x(self, val=None) -> Union[float, Self]: 730 """Set/Get object position along x axis.""" 731 p = self.transform.position 732 if val is None: 733 return p[0] 734 self.pos(val, p[1], p[2]) 735 return self 736 737 def y(self, val=None) -> Union[float, Self]: 738 """Set/Get object position along y axis.""" 739 p = self.transform.position 740 if val is None: 741 return p[1] 742 self.pos(p[0], val, p[2]) 743 return self 744 745 def z(self, val=None) -> Union[float, Self]: 746 """Set/Get object position along z axis.""" 747 p = self.transform.position 748 if val is None: 749 return p[2] 750 self.pos(p[0], p[1], val) 751 return self 752 753 def rotate_x(self, angle) -> Self: 754 """Rotate object around x axis.""" 755 LT = vedo.LinearTransform().rotate_x(angle) 756 return self.apply_transform(LT) 757 758 def rotate_y(self, angle) -> Self: 759 """Rotate object around y axis.""" 760 LT = vedo.LinearTransform().rotate_y(angle) 761 return self.apply_transform(LT) 762 763 def rotate_z(self, angle) -> Self: 764 """Rotate object around z axis.""" 765 LT = vedo.LinearTransform().rotate_z(angle) 766 return self.apply_transform(LT) 767 768 def reorient(self, old_axis, new_axis, rotation=0, rad=False) -> Self: 769 """Rotate object to a new orientation.""" 770 if rad: 771 rotation *= 180 / np.pi 772 axis = old_axis / np.linalg.norm(old_axis) 773 direction = new_axis / np.linalg.norm(new_axis) 774 angle = np.arccos(np.dot(axis, direction)) * 180 / np.pi 775 self.actor.RotateZ(rotation) 776 a,b,c = np.cross(axis, direction) 777 self.actor.RotateWXYZ(angle, c,b,a) 778 return self 779 780 def bounds(self) -> np.ndarray: 781 """ 782 Get the object bounds. 783 Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`. 784 """ 785 return np.array(self.actor.GetBounds()) 786 787 def xbounds(self, i=None) -> Union[float, tuple]: 788 """Get the bounds `[xmin,xmax]`. Can specify upper or lower with i (0,1).""" 789 b = self.bounds() 790 if i is not None: 791 return b[i] 792 return (b[0], b[1]) 793 794 def ybounds(self, i=None) -> Union[float, tuple]: 795 """Get the bounds `[ymin,ymax]`. Can specify upper or lower with i (0,1).""" 796 b = self.bounds() 797 if i == 0: 798 return b[2] 799 if i == 1: 800 return b[3] 801 return (b[2], b[3]) 802 803 def zbounds(self, i=None) -> Union[float, tuple]: 804 """Get the bounds `[zmin,zmax]`. Can specify upper or lower with i (0,1).""" 805 b = self.bounds() 806 if i == 0: 807 return b[4] 808 if i == 1: 809 return b[5] 810 return (b[4], b[5]) 811 812 def diagonal_size(self) -> float: 813 """Get the diagonal size of the bounding box.""" 814 b = self.bounds() 815 return np.sqrt((b[1]-b[0])**2 + (b[3]-b[2])**2 + (b[5]-b[4])**2) 816 817################################################### 818class PointsVisual(CommonVisual): 819 """Class to manage the visual aspects of a ``Points`` object.""" 820 821 def __init__(self): 822 # print("init PointsVisual") 823 super().__init__() 824 self.properties_backface = None 825 self._cmap_name = None 826 self.trail = None 827 self.trail_offset = 0 828 self.trail_points = [] 829 self._caption = None 830 831 832 def clone2d(self, size=None, offset=(), scale=None): 833 """ 834 Turn a 3D `Points` or `Mesh` into a flat 2D actor. 835 Returns a `Actor2D`. 836 837 Arguments: 838 size : (float) 839 size as scaling factor for the 2D actor 840 offset : (list) 841 2D (x, y) position of the actor in the range [-1, 1] 842 scale : (float) 843 Deprecated. Use `size` instead. 844 845 Examples: 846 - [clone2d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/clone2d.py) 847 848  849 """ 850 # assembly.Assembly.clone2d() superseeds this method 851 if scale is not None: 852 vedo.logger.warning("clone2d(): use keyword size not scale") 853 size = scale 854 855 if size is None: 856 # work out a reasonable scale 857 msiz = self.diagonal_size() 858 if vedo.plotter_instance and vedo.plotter_instance.window: 859 sz = vedo.plotter_instance.window.GetSize() 860 dsiz = utils.mag(sz) 861 size = dsiz / msiz / 10 862 else: 863 size = 350 / msiz 864 865 tp = vtki.new("TransformPolyDataFilter") 866 transform = vtki.vtkTransform() 867 transform.Scale(size, size, size) 868 if len(offset) == 0: 869 offset = self.pos() 870 transform.Translate(-utils.make3d(offset)) 871 tp.SetTransform(transform) 872 tp.SetInputData(self.dataset) 873 tp.Update() 874 poly = tp.GetOutput() 875 876 cm = self.mapper.GetColorMode() 877 lut = self.mapper.GetLookupTable() 878 sv = self.mapper.GetScalarVisibility() 879 use_lut = self.mapper.GetUseLookupTableScalarRange() 880 vrange = self.mapper.GetScalarRange() 881 sm = self.mapper.GetScalarMode() 882 883 act2d = Actor2D() 884 act2d.dataset = poly 885 mapper2d = vtki.new("PolyDataMapper2D") 886 mapper2d.SetInputData(poly) 887 mapper2d.SetColorMode(cm) 888 mapper2d.SetLookupTable(lut) 889 mapper2d.SetScalarVisibility(sv) 890 mapper2d.SetUseLookupTableScalarRange(use_lut) 891 mapper2d.SetScalarRange(vrange) 892 mapper2d.SetScalarMode(sm) 893 act2d.mapper = mapper2d 894 895 act2d.GetPositionCoordinate().SetCoordinateSystem(4) 896 act2d.properties.SetColor(self.color()) 897 act2d.properties.SetOpacity(self.alpha()) 898 act2d.properties.SetLineWidth(self.properties.GetLineWidth()) 899 act2d.properties.SetPointSize(self.properties.GetPointSize()) 900 act2d.properties.SetDisplayLocation(0) # 0 = back, 1 = front 901 act2d.PickableOff() 902 return act2d 903 904 ################################################## 905 def copy_properties_from(self, source, deep=True, actor_related=True) -> Self: 906 """ 907 Copy properties from another ``Points`` object. 908 """ 909 pr = vtki.vtkProperty() 910 try: 911 sp = source.properties 912 mp = source.mapper 913 sa = source.actor 914 except AttributeError: 915 sp = source.GetProperty() 916 mp = source.GetMapper() 917 sa = source 918 919 if deep: 920 pr.DeepCopy(sp) 921 else: 922 pr.ShallowCopy(sp) 923 self.actor.SetProperty(pr) 924 self.properties = pr 925 926 if self.actor.GetBackfaceProperty(): 927 bfpr = vtki.vtkProperty() 928 bfpr.DeepCopy(sa.GetBackfaceProperty()) 929 self.actor.SetBackfaceProperty(bfpr) 930 self.properties_backface = bfpr 931 932 if not actor_related: 933 return self 934 935 # mapper related: 936 self.mapper.SetScalarVisibility(mp.GetScalarVisibility()) 937 self.mapper.SetScalarMode(mp.GetScalarMode()) 938 self.mapper.SetScalarRange(mp.GetScalarRange()) 939 self.mapper.SetLookupTable(mp.GetLookupTable()) 940 self.mapper.SetColorMode(mp.GetColorMode()) 941 self.mapper.SetInterpolateScalarsBeforeMapping( 942 mp.GetInterpolateScalarsBeforeMapping() 943 ) 944 self.mapper.SetUseLookupTableScalarRange( 945 mp.GetUseLookupTableScalarRange() 946 ) 947 948 self.actor.SetPickable(sa.GetPickable()) 949 self.actor.SetDragable(sa.GetDragable()) 950 self.actor.SetTexture(sa.GetTexture()) 951 self.actor.SetVisibility(sa.GetVisibility()) 952 return self 953 954 def color(self, c=False, alpha=None) -> Union[np.ndarray, Self]: 955 """ 956 Set/get mesh's color. 957 If None is passed as input, will use colors from active scalars. 958 Same as `mesh.c()`. 959 """ 960 if c is False: 961 return np.array(self.properties.GetColor()) 962 if c is None: 963 self.mapper.ScalarVisibilityOn() 964 return self 965 self.mapper.ScalarVisibilityOff() 966 cc = colors.get_color(c) 967 self.properties.SetColor(cc) 968 if self.trail: 969 self.trail.properties.SetColor(cc) 970 if alpha is not None: 971 self.alpha(alpha) 972 return self 973 974 def c(self, color=False, alpha=None) -> Union[np.ndarray, Self]: 975 """ 976 Shortcut for `color()`. 977 If None is passed as input, will use colors from current active scalars. 978 """ 979 return self.color(color, alpha) 980 981 def alpha(self, opacity=None) -> Union[float, Self]: 982 """Set/get mesh's transparency. Same as `mesh.opacity()`.""" 983 if opacity is None: 984 return self.properties.GetOpacity() 985 986 self.properties.SetOpacity(opacity) 987 bfp = self.actor.GetBackfaceProperty() 988 if bfp: 989 if opacity < 1: 990 self.properties_backface = bfp 991 self.actor.SetBackfaceProperty(None) 992 else: 993 self.actor.SetBackfaceProperty(self.properties_backface) 994 return self 995 996 def lut_color_at(self, value) -> np.ndarray: 997 """ 998 Return the color and alpha in the lookup table at given value. 999 """ 1000 lut = self.mapper.GetLookupTable() 1001 if not lut: 1002 return None 1003 rgb = [0,0,0] 1004 lut.GetColor(value, rgb) 1005 alpha = lut.GetOpacity(value) 1006 return np.array(rgb + [alpha]) 1007 1008 def opacity(self, alpha=None) -> Union[float, Self]: 1009 """Set/get mesh's transparency. Same as `mesh.alpha()`.""" 1010 return self.alpha(alpha) 1011 1012 def force_opaque(self, value=True) -> Self: 1013 """ Force the Mesh, Line or point cloud to be treated as opaque""" 1014 ## force the opaque pass, fixes picking in vtk9 1015 # but causes other bad troubles with lines.. 1016 self.actor.SetForceOpaque(value) 1017 return self 1018 1019 def force_translucent(self, value=True) -> Self: 1020 """ Force the Mesh, Line or point cloud to be treated as translucent""" 1021 self.actor.SetForceTranslucent(value) 1022 return self 1023 1024 def point_size(self, value=None) -> Union[int, Self]: 1025 """Set/get mesh's point size of vertices. Same as `mesh.ps()`""" 1026 if value is None: 1027 return self.properties.GetPointSize() 1028 # self.properties.SetRepresentationToSurface() 1029 else: 1030 self.properties.SetRepresentationToPoints() 1031 self.properties.SetPointSize(value) 1032 return self 1033 1034 def ps(self, pointsize=None) -> Union[int, Self]: 1035 """Set/get mesh's point size of vertices. Same as `mesh.point_size()`""" 1036 return self.point_size(pointsize) 1037 1038 def render_points_as_spheres(self, value=True) -> Self: 1039 """Make points look spheric or else make them look as squares.""" 1040 self.properties.SetRenderPointsAsSpheres(value) 1041 return self 1042 1043 def lighting( 1044 self, 1045 style="", 1046 ambient=None, 1047 diffuse=None, 1048 specular=None, 1049 specular_power=None, 1050 specular_color=None, 1051 metallicity=None, 1052 roughness=None, 1053 ) -> Self: 1054 """ 1055 Set the ambient, diffuse, specular and specular_power lighting constants. 1056 1057 Arguments: 1058 style : (str) 1059 preset style, options are `[metallic, plastic, shiny, glossy, ambient, off]` 1060 ambient : (float) 1061 ambient fraction of emission [0-1] 1062 diffuse : (float) 1063 emission of diffused light in fraction [0-1] 1064 specular : (float) 1065 fraction of reflected light [0-1] 1066 specular_power : (float) 1067 precision of reflection [1-100] 1068 specular_color : (color) 1069 color that is being reflected by the surface 1070 1071 <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" alt="", width=700px> 1072 1073 Examples: 1074 - [specular.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/specular.py) 1075 """ 1076 pr = self.properties 1077 1078 if style: 1079 1080 if style != "off": 1081 pr.LightingOn() 1082 1083 if style == "off": 1084 pr.SetInterpolationToFlat() 1085 pr.LightingOff() 1086 return self ############## 1087 1088 if hasattr(pr, "GetColor"): # could be Volume 1089 c = pr.GetColor() 1090 else: 1091 c = (1, 1, 0.99) 1092 mpr = self.mapper 1093 if hasattr(mpr, 'GetScalarVisibility') and mpr.GetScalarVisibility(): 1094 c = (1,1,0.99) 1095 if style=='metallic': pars = [0.1, 0.3, 1.0, 10, c] 1096 elif style=='plastic' : pars = [0.3, 0.4, 0.3, 5, c] 1097 elif style=='shiny' : pars = [0.2, 0.6, 0.8, 50, c] 1098 elif style=='glossy' : pars = [0.1, 0.7, 0.9, 90, (1,1,0.99)] 1099 elif style=='ambient' : pars = [0.8, 0.1, 0.0, 1, (1,1,1)] 1100 elif style=='default' : pars = [0.1, 1.0, 0.05, 5, c] 1101 else: 1102 vedo.logger.error("in lighting(): Available styles are") 1103 vedo.logger.error("[default, metallic, plastic, shiny, glossy, ambient, off]") 1104 raise RuntimeError() 1105 pr.SetAmbient(pars[0]) 1106 pr.SetDiffuse(pars[1]) 1107 pr.SetSpecular(pars[2]) 1108 pr.SetSpecularPower(pars[3]) 1109 if hasattr(pr, "GetColor"): 1110 pr.SetSpecularColor(pars[4]) 1111 1112 if ambient is not None: pr.SetAmbient(ambient) 1113 if diffuse is not None: pr.SetDiffuse(diffuse) 1114 if specular is not None: pr.SetSpecular(specular) 1115 if specular_power is not None: pr.SetSpecularPower(specular_power) 1116 if specular_color is not None: pr.SetSpecularColor(colors.get_color(specular_color)) 1117 if metallicity is not None: 1118 pr.SetInterpolationToPBR() 1119 pr.SetMetallic(metallicity) 1120 if roughness is not None: 1121 pr.SetInterpolationToPBR() 1122 pr.SetRoughness(roughness) 1123 1124 return self 1125 1126 def point_blurring(self, r=1, alpha=1.0, emissive=False) -> Self: 1127 """Set point blurring. 1128 Apply a gaussian convolution filter to the points. 1129 In this case the radius `r` is in absolute units of the mesh coordinates. 1130 With emissive set, the halo of point becomes light-emissive. 1131 """ 1132 self.properties.SetRepresentationToPoints() 1133 if emissive: 1134 self.mapper.SetEmissive(bool(emissive)) 1135 self.mapper.SetScaleFactor(r * 1.4142) 1136 1137 # https://kitware.github.io/vtk-examples/site/Python/Meshes/PointInterpolator/ 1138 if alpha < 1: 1139 self.mapper.SetSplatShaderCode( 1140 "//VTK::Color::Impl\n" 1141 "float dist = dot(offsetVCVSOutput.xy,offsetVCVSOutput.xy);\n" 1142 "if (dist > 1.0) {\n" 1143 " discard;\n" 1144 "} else {\n" 1145 f" float scale = ({alpha} - dist);\n" 1146 " ambientColor *= scale;\n" 1147 " diffuseColor *= scale;\n" 1148 "}\n" 1149 ) 1150 alpha = 1 1151 1152 self.mapper.Modified() 1153 self.actor.Modified() 1154 self.properties.SetOpacity(alpha) 1155 self.actor.SetMapper(self.mapper) 1156 return self 1157 1158 @property 1159 def cellcolors(self): 1160 """ 1161 Colorize each cell (face) of a mesh by passing 1162 a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. 1163 Colors levels and opacities must be in the range [0,255]. 1164 1165 A single constant color can also be passed as string or RGBA. 1166 1167 A cell array named "CellsRGBA" is automatically created. 1168 1169 Examples: 1170 - [color_mesh_cells1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells1.py) 1171 - [color_mesh_cells2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells2.py) 1172 1173  1174 """ 1175 if "CellsRGBA" not in self.celldata.keys(): 1176 lut = self.mapper.GetLookupTable() 1177 vscalars = self.dataset.GetCellData().GetScalars() 1178 if vscalars is None or lut is None: 1179 arr = np.zeros([self.ncells, 4], dtype=np.uint8) 1180 col = np.array(self.properties.GetColor()) 1181 col = np.round(col * 255).astype(np.uint8) 1182 alf = self.properties.GetOpacity() 1183 alf = np.round(alf * 255).astype(np.uint8) 1184 arr[:, (0, 1, 2)] = col 1185 arr[:, 3] = alf 1186 else: 1187 cols = lut.MapScalars(vscalars, 0, 0) 1188 arr = utils.vtk2numpy(cols) 1189 self.celldata["CellsRGBA"] = arr 1190 self.celldata.select("CellsRGBA") 1191 return self.celldata["CellsRGBA"] 1192 1193 @cellcolors.setter 1194 def cellcolors(self, value): 1195 if isinstance(value, str): 1196 c = colors.get_color(value) 1197 value = np.array([*c, 1]) * 255 1198 value = np.round(value) 1199 1200 value = np.asarray(value) 1201 n = self.ncells 1202 1203 if value.ndim == 1: 1204 value = np.repeat([value], n, axis=0) 1205 1206 if value.shape[1] == 3: 1207 z = np.zeros((n, 1), dtype=np.uint8) 1208 value = np.append(value, z + 255, axis=1) 1209 1210 assert n == value.shape[0] 1211 1212 self.celldata["CellsRGBA"] = value.astype(np.uint8) 1213 # self.mapper.SetColorModeToDirectScalars() # done in select() 1214 self.celldata.select("CellsRGBA") 1215 1216 @property 1217 def pointcolors(self): 1218 """ 1219 Colorize each point (or vertex of a mesh) by passing 1220 a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. 1221 Colors levels and opacities must be in the range [0,255]. 1222 1223 A single constant color can also be passed as string or RGBA. 1224 1225 A point array named "PointsRGBA" is automatically created. 1226 """ 1227 if "PointsRGBA" not in self.pointdata.keys(): 1228 lut = self.mapper.GetLookupTable() 1229 vscalars = self.dataset.GetPointData().GetScalars() 1230 if vscalars is None or lut is None: 1231 # create a constant array 1232 arr = np.zeros([self.npoints, 4], dtype=np.uint8) 1233 col = np.array(self.properties.GetColor()) 1234 col = np.round(col * 255).astype(np.uint8) 1235 alf = self.properties.GetOpacity() 1236 alf = np.round(alf * 255).astype(np.uint8) 1237 arr[:, (0, 1, 2)] = col 1238 arr[:, 3] = alf 1239 else: 1240 cols = lut.MapScalars(vscalars, 0, 0) 1241 arr = utils.vtk2numpy(cols) 1242 self.pointdata["PointsRGBA"] = arr 1243 self.pointdata.select("PointsRGBA") 1244 return self.pointdata["PointsRGBA"] 1245 1246 @pointcolors.setter 1247 def pointcolors(self, value): 1248 if isinstance(value, str): 1249 c = colors.get_color(value) 1250 value = np.array([*c, 1]) * 255 1251 value = np.round(value) 1252 1253 value = np.asarray(value) 1254 n = self.npoints 1255 1256 if value.ndim == 1: 1257 value = np.repeat([value], n, axis=0) 1258 1259 if value.shape[1] == 3: 1260 z = np.zeros((n, 1), dtype=np.uint8) 1261 value = np.append(value, z + 255, axis=1) 1262 1263 assert n == value.shape[0] 1264 1265 self.pointdata["PointsRGBA"] = value.astype(np.uint8) 1266 # self.mapper.SetColorModeToDirectScalars() # done in select() 1267 self.pointdata.select("PointsRGBA") 1268 1269 ##################################################################################### 1270 def cmap( 1271 self, 1272 input_cmap, 1273 input_array=None, 1274 on="", 1275 name="Scalars", 1276 vmin=None, 1277 vmax=None, 1278 n_colors=256, 1279 alpha=1.0, 1280 logscale=False, 1281 ) -> Self: 1282 """ 1283 Set individual point/cell colors by providing a list of scalar values and a color map. 1284 1285 Arguments: 1286 input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap) 1287 color map scheme to transform a real number into a color. 1288 input_array : (str, list, vtkArray) 1289 can be the string name of an existing array, a new array or a `vtkArray`. 1290 on : (str) 1291 either 'points' or 'cells' or blank (automatic). 1292 Apply the color map to data which is defined on either points or cells. 1293 name : (str) 1294 give a name to the provided array (if input_array is an array) 1295 vmin : (float) 1296 clip scalars to this minimum value 1297 vmax : (float) 1298 clip scalars to this maximum value 1299 n_colors : (int) 1300 number of distinct colors to be used in colormap table. 1301 alpha : (float, list) 1302 Mesh transparency. Can be a `list` of values one for each vertex. 1303 logscale : (bool) 1304 Use logscale 1305 1306 Examples: 1307 - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py) 1308 - [mesh_alphas.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_alphas.py) 1309 - [mesh_custom.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_custom.py) 1310 - (and many others) 1311 1312  1313 """ 1314 self._cmap_name = input_cmap 1315 1316 if on == "": 1317 try: 1318 on = self.mapper.GetScalarModeAsString().replace("Use", "") 1319 if on not in ["PointData", "CellData"]: # can be "Default" 1320 on = "points" 1321 self.mapper.SetScalarModeToUsePointData() 1322 except AttributeError: 1323 on = "points" 1324 elif on == "Default": 1325 on = "points" 1326 self.mapper.SetScalarModeToUsePointData() 1327 1328 if input_array is None: 1329 if not self.pointdata.keys() and self.celldata.keys(): 1330 on = "cells" 1331 if not self.dataset.GetCellData().GetScalars(): 1332 input_array = 0 # pick the first at hand 1333 1334 if "point" in on.lower(): 1335 data = self.dataset.GetPointData() 1336 n = self.dataset.GetNumberOfPoints() 1337 elif "cell" in on.lower(): 1338 data = self.dataset.GetCellData() 1339 n = self.dataset.GetNumberOfCells() 1340 else: 1341 vedo.logger.error( 1342 f"Must specify in cmap(on=...) to either 'cells' or 'points', not {on}") 1343 raise RuntimeError() 1344 1345 if input_array is None: # if None try to fetch the active scalars 1346 arr = data.GetScalars() 1347 if not arr: 1348 vedo.logger.error(f"in cmap(), cannot find any {on} active array ...skip coloring.") 1349 return self 1350 1351 if not arr.GetName(): # sometimes arrays dont have a name.. 1352 arr.SetName(name) 1353 1354 elif isinstance(input_array, str): # if a string is passed 1355 arr = data.GetArray(input_array) 1356 if not arr: 1357 vedo.logger.error(f"in cmap(), cannot find {on} array {input_array} ...skip coloring.") 1358 return self 1359 1360 elif isinstance(input_array, int): # if an int is passed 1361 if input_array < data.GetNumberOfArrays(): 1362 arr = data.GetArray(input_array) 1363 else: 1364 vedo.logger.error(f"in cmap(), cannot find {on} array at {input_array} ...skip coloring.") 1365 return self 1366 1367 elif utils.is_sequence(input_array): # if a numpy array is passed 1368 npts = len(input_array) 1369 if npts != n: 1370 vedo.logger.error(f"in cmap(), nr. of input {on} scalars {npts} != {n} ...skip coloring.") 1371 return self 1372 arr = utils.numpy2vtk(input_array, name=name, dtype=float) 1373 data.AddArray(arr) 1374 data.Modified() 1375 1376 elif isinstance(input_array, vtki.vtkArray): # if a vtkArray is passed 1377 arr = input_array 1378 data.AddArray(arr) 1379 data.Modified() 1380 1381 else: 1382 vedo.logger.error(f"in cmap(), cannot understand input type {type(input_array)}") 1383 raise RuntimeError() 1384 1385 # Now we have array "arr" 1386 array_name = arr.GetName() 1387 1388 if arr.GetNumberOfComponents() == 1: 1389 if vmin is None: 1390 vmin = arr.GetRange()[0] 1391 if vmax is None: 1392 vmax = arr.GetRange()[1] 1393 else: 1394 if vmin is None or vmax is None: 1395 vn = utils.mag(utils.vtk2numpy(arr)) 1396 if vmin is None: 1397 vmin = vn.min() 1398 if vmax is None: 1399 vmax = vn.max() 1400 1401 # interpolate alphas if they are not constant 1402 if not utils.is_sequence(alpha): 1403 alpha = [alpha] * n_colors 1404 else: 1405 v = np.linspace(0, 1, n_colors, endpoint=True) 1406 xp = np.linspace(0, 1, len(alpha), endpoint=True) 1407 alpha = np.interp(v, xp, alpha) 1408 1409 ########################### build the look-up table 1410 if isinstance(input_cmap, vtki.vtkLookupTable): # vtkLookupTable 1411 lut = input_cmap 1412 1413 elif utils.is_sequence(input_cmap): # manual sequence of colors 1414 lut = vtki.vtkLookupTable() 1415 if logscale: 1416 lut.SetScaleToLog10() 1417 lut.SetRange(vmin, vmax) 1418 ncols = len(input_cmap) 1419 lut.SetNumberOfTableValues(ncols) 1420 1421 for i, c in enumerate(input_cmap): 1422 r, g, b = colors.get_color(c) 1423 lut.SetTableValue(i, r, g, b, alpha[i]) 1424 lut.Build() 1425 1426 else: 1427 # assume string cmap name OR matplotlib.colors.LinearSegmentedColormap 1428 lut = vtki.vtkLookupTable() 1429 if logscale: 1430 lut.SetScaleToLog10() 1431 lut.SetVectorModeToMagnitude() 1432 lut.SetRange(vmin, vmax) 1433 lut.SetNumberOfTableValues(n_colors) 1434 mycols = colors.color_map(range(n_colors), input_cmap, 0, n_colors) 1435 for i, c in enumerate(mycols): 1436 r, g, b = c 1437 lut.SetTableValue(i, r, g, b, alpha[i]) 1438 lut.Build() 1439 1440 # TEST NEW WAY 1441 self.mapper.SetLookupTable(lut) 1442 self.mapper.ScalarVisibilityOn() 1443 self.mapper.SetColorModeToMapScalars() 1444 self.mapper.SetScalarRange(lut.GetRange()) 1445 if "point" in on.lower(): 1446 self.pointdata.select(array_name) 1447 else: 1448 self.celldata.select(array_name) 1449 return self 1450 1451 # # TEST this is the old way: 1452 # # arr.SetLookupTable(lut) # wrong! causes weird instabilities with LUT 1453 # # if data.GetScalars(): 1454 # # data.GetScalars().SetLookupTable(lut) 1455 # # data.GetScalars().Modified() 1456 1457 # data.SetActiveScalars(array_name) 1458 # # data.SetScalars(arr) # wrong! it deletes array in position 0, never use SetScalars 1459 # # data.SetActiveAttribute(array_name, 0) # boh! 1460 1461 # self.mapper.SetLookupTable(lut) 1462 # self.mapper.SetColorModeToMapScalars() # so we dont need to convert uint8 scalars 1463 1464 # self.mapper.ScalarVisibilityOn() 1465 # self.mapper.SetScalarRange(lut.GetRange()) 1466 1467 # if on.startswith("point"): 1468 # self.mapper.SetScalarModeToUsePointData() 1469 # else: 1470 # self.mapper.SetScalarModeToUseCellData() 1471 # if hasattr(self.mapper, "SetArrayName"): 1472 # self.mapper.SetArrayName(array_name) 1473 # return self 1474 1475 def add_trail(self, offset=(0, 0, 0), n=50, c=None, alpha=1.0, lw=2) -> Self: 1476 """ 1477 Add a trailing line to mesh. 1478 This new mesh is accessible through `mesh.trail`. 1479 1480 Arguments: 1481 offset : (float) 1482 set an offset vector from the object center. 1483 n : (int) 1484 number of segments 1485 lw : (float) 1486 line width of the trail 1487 1488 Examples: 1489 - [trail.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/trail.py) 1490 1491  1492 1493 - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py) 1494 - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py) 1495 """ 1496 if self.trail is None: 1497 pos = self.pos() 1498 self.trail_offset = np.asarray(offset) 1499 self.trail_points = [pos] * n 1500 1501 if c is None: 1502 col = self.properties.GetColor() 1503 else: 1504 col = colors.get_color(c) 1505 1506 tline = vedo.shapes.Line(pos, pos, res=n, c=col, alpha=alpha, lw=lw) 1507 self.trail = tline # holds the Line 1508 self.trail.initilized = False # so the first update will be a reset 1509 return self 1510 1511 def update_trail(self) -> Self: 1512 """ 1513 Update the trailing line of a moving object. 1514 """ 1515 currentpos = self.pos() 1516 if not self.trail.initilized: 1517 self.trail_points = [currentpos] * self.trail.npoints 1518 self.trail.initilized = True 1519 return self 1520 self.trail_points.append(currentpos) # cycle 1521 self.trail_points.pop(0) 1522 1523 data = np.array(self.trail_points) + self.trail_offset 1524 tpoly = self.trail.dataset 1525 tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32)) 1526 return self 1527 1528 def _compute_shadow(self, plane, point, direction): 1529 shad = self.clone() 1530 shad.name = "Shadow" 1531 1532 tarr = shad.dataset.GetPointData().GetTCoords() 1533 if tarr: # remove any texture coords 1534 tname = tarr.GetName() 1535 shad.pointdata.remove(tname) 1536 shad.dataset.GetPointData().SetTCoords(None) 1537 shad.actor.SetTexture(None) 1538 1539 pts = shad.vertices 1540 if plane == "x": 1541 # shad = shad.project_on_plane('x') 1542 # instead do it manually so in case of alpha<1 1543 # we dont see glitches due to coplanar points 1544 # we leave a small tolerance of 0.1% in thickness 1545 x0, x1 = self.xbounds() 1546 pts[:, 0] = (pts[:, 0] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[0] 1547 shad.vertices = pts 1548 shad.x(point) 1549 elif plane == "y": 1550 x0, x1 = self.ybounds() 1551 pts[:, 1] = (pts[:, 1] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[1] 1552 shad.vertices = pts 1553 shad.y(point) 1554 elif plane == "z": 1555 x0, x1 = self.zbounds() 1556 pts[:, 2] = (pts[:, 2] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[2] 1557 shad.vertices = pts 1558 shad.z(point) 1559 else: 1560 shad = shad.project_on_plane(plane, point, direction) 1561 return shad 1562 1563 def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0) -> Self: 1564 """ 1565 Generate a shadow out of an `Mesh` on one of the three Cartesian planes. 1566 The output is a new `Mesh` representing the shadow. 1567 This new mesh is accessible through `mesh.shadow`. 1568 By default the shadow mesh is placed on the bottom wall of the bounding box. 1569 1570 See also `pointcloud.project_on_plane()`. 1571 1572 Arguments: 1573 plane : (str, Plane) 1574 if plane is `str`, plane can be one of `['x', 'y', 'z']`, 1575 represents x-plane, y-plane and z-plane, respectively. 1576 Otherwise, plane should be an instance of `vedo.shapes.Plane` 1577 point : (float, array) 1578 if plane is `str`, point should be a float represents the intercept. 1579 Otherwise, point is the camera point of perspective projection 1580 direction : (list) 1581 direction of oblique projection 1582 culling : (int) 1583 choose between front [1] or backface [-1] culling or None. 1584 1585 Examples: 1586 - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py) 1587 - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py) 1588 - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py) 1589 1590  1591 """ 1592 shad = self._compute_shadow(plane, point, direction) 1593 shad.c(c).alpha(alpha) 1594 1595 try: 1596 # Points dont have these methods 1597 shad.flat() 1598 if culling in (1, True): 1599 shad.frontface_culling() 1600 elif culling == -1: 1601 shad.backface_culling() 1602 except AttributeError: 1603 pass 1604 1605 shad.properties.LightingOff() 1606 shad.actor.SetPickable(False) 1607 shad.actor.SetUseBounds(True) 1608 1609 if shad not in self.shadows: 1610 self.shadows.append(shad) 1611 shad.info = dict(plane=plane, point=point, direction=direction) 1612 # shad.metadata["plane"] = plane 1613 # shad.metadata["point"] = point 1614 # print("AAAA", direction, plane, point) 1615 # if direction is None: 1616 # direction = [0,0,0] 1617 # shad.metadata["direction"] = direction 1618 return self 1619 1620 def update_shadows(self) -> Self: 1621 """Update the shadows of a moving object.""" 1622 for sha in self.shadows: 1623 plane = sha.info["plane"] 1624 point = sha.info["point"] 1625 direction = sha.info["direction"] 1626 # print("update_shadows direction", direction,plane,point ) 1627 # plane = sha.metadata["plane"] 1628 # point = sha.metadata["point"] 1629 # direction = sha.metadata["direction"] 1630 # if direction[0]==0 and direction[1]==0 and direction[2]==0: 1631 # direction = None 1632 # print("BBBB", sha.metadata["direction"], 1633 # sha.metadata["plane"], sha.metadata["point"]) 1634 new_sha = self._compute_shadow(plane, point, direction) 1635 sha._update(new_sha.dataset) 1636 if self.trail: 1637 self.trail.update_shadows() 1638 return self 1639 1640 def labels( 1641 self, 1642 content=None, 1643 on="points", 1644 scale=None, 1645 xrot=0.0, 1646 yrot=0.0, 1647 zrot=0.0, 1648 ratio=1, 1649 precision=None, 1650 italic=False, 1651 font="", 1652 justify="", 1653 c="black", 1654 alpha=1.0, 1655 ) -> Union["vedo.Mesh", None]: 1656 """ 1657 Generate value or ID labels for mesh cells or points. 1658 For large nr. of labels use `font="VTK"` which is much faster. 1659 1660 See also: 1661 `labels2d()`, `flagpole()`, `caption()` and `legend()`. 1662 1663 Arguments: 1664 content : (list,int,str) 1665 either 'id', 'cellid', array name or array number. 1666 A array can also be passed (must match the nr. of points or cells). 1667 on : (str) 1668 generate labels for "cells" instead of "points" 1669 scale : (float) 1670 absolute size of labels, if left as None it is automatic 1671 zrot : (float) 1672 local rotation angle of label in degrees 1673 ratio : (int) 1674 skipping ratio, to reduce nr of labels for large meshes 1675 precision : (int) 1676 numeric precision of labels 1677 1678 ```python 1679 from vedo import * 1680 s = Sphere(res=10).linewidth(1).c("orange").compute_normals() 1681 point_ids = s.labels('id', on="points").c('green') 1682 cell_ids = s.labels('id', on="cells" ).c('black') 1683 show(s, point_ids, cell_ids) 1684 ``` 1685  1686 1687 Examples: 1688 - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py) 1689 1690  1691 """ 1692 1693 cells = False 1694 if "cell" in on or "face" in on: 1695 cells = True 1696 justify = "centered" if justify == "" else justify 1697 1698 if isinstance(content, str): 1699 if content in ("pointid", "pointsid"): 1700 cells = False 1701 content = "id" 1702 justify = "bottom-left" if justify == "" else justify 1703 if content in ("cellid", "cellsid"): 1704 cells = True 1705 content = "id" 1706 justify = "centered" if justify == "" else justify 1707 1708 try: 1709 if cells: 1710 ns = np.sqrt(self.ncells) 1711 elems = self.cell_centers().points 1712 norms = self.cell_normals 1713 justify = "centered" if justify == "" else justify 1714 else: 1715 ns = np.sqrt(self.npoints) 1716 elems = self.vertices 1717 norms = self.vertex_normals 1718 except AttributeError: 1719 norms = [] 1720 1721 if not justify: 1722 justify = "bottom-left" 1723 1724 hasnorms = False 1725 if len(norms) > 0: 1726 hasnorms = True 1727 1728 if scale is None: 1729 if not ns: 1730 ns = 100 1731 scale = self.diagonal_size() / ns / 10 1732 1733 arr = None 1734 mode = 0 1735 if content is None: 1736 mode = 0 1737 if cells: 1738 if self.dataset.GetCellData().GetScalars(): 1739 name = self.dataset.GetCellData().GetScalars().GetName() 1740 arr = self.celldata[name] 1741 else: 1742 if self.dataset.GetPointData().GetScalars(): 1743 name = self.dataset.GetPointData().GetScalars().GetName() 1744 arr = self.pointdata[name] 1745 elif isinstance(content, (str, int)): 1746 if content == "id": 1747 mode = 1 1748 elif cells: 1749 mode = 0 1750 arr = self.celldata[content] 1751 else: 1752 mode = 0 1753 arr = self.pointdata[content] 1754 elif utils.is_sequence(content): 1755 mode = 0 1756 arr = content 1757 1758 if arr is None and mode == 0: 1759 vedo.logger.error("in labels(), array not found in point or cell data") 1760 return None 1761 1762 ratio = int(ratio+0.5) 1763 tapp = vtki.new("AppendPolyData") 1764 has_inputs = False 1765 1766 for i, e in enumerate(elems): 1767 if i % ratio: 1768 continue 1769 1770 if mode == 1: 1771 txt_lab = str(i) 1772 else: 1773 if precision: 1774 txt_lab = utils.precision(arr[i], precision) 1775 else: 1776 txt_lab = str(arr[i]) 1777 1778 if not txt_lab: 1779 continue 1780 1781 if font == "VTK": 1782 tx = vtki.new("VectorText") 1783 tx.SetText(txt_lab) 1784 tx.Update() 1785 tx_poly = tx.GetOutput() 1786 else: 1787 tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify).dataset 1788 1789 if tx_poly.GetNumberOfPoints() == 0: 1790 continue ###################### 1791 1792 T = vtki.vtkTransform() 1793 T.PostMultiply() 1794 if italic: 1795 T.Concatenate([1, 0.2, 0, 0, 1796 0, 1 , 0, 0, 1797 0, 0 , 1, 0, 1798 0, 0 , 0, 1]) 1799 if hasnorms: 1800 ni = norms[i] 1801 if cells and font=="VTK": # center-justify 1802 bb = tx_poly.GetBounds() 1803 dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2 1804 T.Translate(-dx, -dy, 0) 1805 if xrot: T.RotateX(xrot) 1806 if yrot: T.RotateY(yrot) 1807 if zrot: T.RotateZ(zrot) 1808 crossvec = np.cross([0, 0, 1], ni) 1809 angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3 1810 T.RotateWXYZ(float(angle), crossvec.tolist()) 1811 T.Translate(ni / 100) 1812 else: 1813 if xrot: T.RotateX(xrot) 1814 if yrot: T.RotateY(yrot) 1815 if zrot: T.RotateZ(zrot) 1816 T.Scale(scale, scale, scale) 1817 T.Translate(e) 1818 tf = vtki.new("TransformPolyDataFilter") 1819 tf.SetInputData(tx_poly) 1820 tf.SetTransform(T) 1821 tf.Update() 1822 tapp.AddInputData(tf.GetOutput()) 1823 has_inputs = True 1824 1825 if has_inputs: 1826 tapp.Update() 1827 lpoly = tapp.GetOutput() 1828 else: 1829 lpoly = vtki.vtkPolyData() 1830 ids = vedo.Mesh(lpoly, c=c, alpha=alpha) 1831 ids.properties.LightingOff() 1832 ids.actor.PickableOff() 1833 ids.actor.SetUseBounds(False) 1834 ids.name = "Labels" 1835 return ids 1836 1837 def labels2d( 1838 self, 1839 content="id", 1840 on="points", 1841 scale=1.0, 1842 precision=4, 1843 font="Calco", 1844 justify="bottom-left", 1845 angle=0.0, 1846 frame=False, 1847 c="black", 1848 bc=None, 1849 alpha=1.0, 1850 ) -> Union["Actor2D", None]: 1851 """ 1852 Generate value or ID bi-dimensional labels for mesh cells or points. 1853 1854 See also: `labels()`, `flagpole()`, `caption()` and `legend()`. 1855 1856 Arguments: 1857 content : (str) 1858 either 'id', 'cellid', or array name 1859 on : (str) 1860 generate labels for "cells" instead of "points" (the default) 1861 scale : (float) 1862 size scaling of labels 1863 precision : (int) 1864 precision of numeric labels 1865 angle : (float) 1866 local rotation angle of label in degrees 1867 frame : (bool) 1868 draw a frame around the label 1869 bc : (str) 1870 background color of the label 1871 1872 ```python 1873 from vedo import Sphere, show 1874 sph = Sphere(quads=True, res=4).compute_normals().wireframe() 1875 sph.celldata["zvals"] = sph.cell_centers().coordinates[:,2] 1876 l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9') 1877 show(sph, l2d, axes=1).close() 1878 ``` 1879  1880 """ 1881 cells = False 1882 if "cell" in on: 1883 cells = True 1884 1885 if isinstance(content, str): 1886 if content in ("id", "pointid", "pointsid"): 1887 cells = False 1888 content = "id" 1889 if content in ("cellid", "cellsid"): 1890 cells = True 1891 content = "id" 1892 1893 if cells: 1894 if content != "id" and content not in self.celldata.keys(): 1895 vedo.logger.error(f"In labels2d: cell array {content} does not exist.") 1896 return None 1897 arr = self.dataset.GetCellData().GetScalars() 1898 poly = self.cell_centers().dataset 1899 poly.GetPointData().SetScalars(arr) 1900 else: 1901 arr = self.dataset.GetPointData().GetScalars() 1902 poly = self.dataset 1903 if content != "id" and content not in self.pointdata.keys(): 1904 vedo.logger.error(f"In labels2d: point array {content} does not exist.") 1905 return None 1906 1907 mp = vtki.new("LabeledDataMapper") 1908 1909 if content == "id": 1910 mp.SetLabelModeToLabelIds() 1911 else: 1912 mp.SetLabelModeToLabelScalars() 1913 if precision is not None: 1914 dtype = arr.GetDataType() 1915 if dtype in (vtki.VTK_FLOAT, vtki.VTK_DOUBLE): 1916 mp.SetLabelFormat(f"%-#.{precision}g") 1917 1918 pr = mp.GetLabelTextProperty() 1919 c = colors.get_color(c) 1920 pr.SetColor(c) 1921 pr.SetOpacity(alpha) 1922 pr.SetFrame(frame) 1923 pr.SetFrameColor(c) 1924 pr.SetItalic(False) 1925 pr.BoldOff() 1926 pr.ShadowOff() 1927 pr.UseTightBoundingBoxOn() 1928 pr.SetOrientation(angle) 1929 pr.SetFontFamily(vtki.VTK_FONT_FILE) 1930 fl = utils.get_font_path(font) 1931 pr.SetFontFile(fl) 1932 pr.SetFontSize(int(20 * scale)) 1933 1934 if "cent" in justify or "mid" in justify: 1935 pr.SetJustificationToCentered() 1936 elif "rig" in justify: 1937 pr.SetJustificationToRight() 1938 elif "left" in justify: 1939 pr.SetJustificationToLeft() 1940 # ------ 1941 if "top" in justify: 1942 pr.SetVerticalJustificationToTop() 1943 else: 1944 pr.SetVerticalJustificationToBottom() 1945 1946 if bc is not None: 1947 bc = colors.get_color(bc) 1948 pr.SetBackgroundColor(bc) 1949 pr.SetBackgroundOpacity(alpha) 1950 1951 mp.SetInputData(poly) 1952 a2d = Actor2D() 1953 a2d.PickableOff() 1954 a2d.SetMapper(mp) 1955 return a2d 1956 1957 def legend(self, txt) -> Self: 1958 """Book a legend text.""" 1959 self.info["legend"] = txt 1960 # self.metadata["legend"] = txt 1961 return self 1962 1963 def flagpole( 1964 self, 1965 txt=None, 1966 point=None, 1967 offset=None, 1968 s=None, 1969 font="Calco", 1970 rounded=True, 1971 c=None, 1972 alpha=1.0, 1973 lw=2, 1974 italic=0.0, 1975 padding=0.1, 1976 ) -> Union["vedo.Mesh", None]: 1977 """ 1978 Generate a flag pole style element to describe an object. 1979 Returns a `Mesh` object. 1980 1981 Use flagpole.follow_camera() to make it face the camera in the scene. 1982 1983 Consider using `settings.use_parallel_projection = True` 1984 to avoid perspective distortions. 1985 1986 See also `flagpost()`. 1987 1988 Arguments: 1989 txt : (str) 1990 Text to display. The default is the filename or the object name. 1991 point : (list) 1992 position of the flagpole pointer. 1993 offset : (list) 1994 text offset wrt the application point. 1995 s : (float) 1996 size of the flagpole. 1997 font : (str) 1998 font face. Check [available fonts here](https://vedo.embl.es/fonts). 1999 rounded : (bool) 2000 draw a rounded or squared box around the text. 2001 c : (list) 2002 text and box color. 2003 alpha : (float) 2004 opacity of text and box. 2005 lw : (float) 2006 line with of box frame. 2007 italic : (float) 2008 italicness of text. 2009 2010 Examples: 2011 - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py) 2012 2013  2014 2015 - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py) 2016 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 2017 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 2018 """ 2019 objs = [] 2020 2021 if txt is None: 2022 if self.filename: 2023 txt = self.filename.split("/")[-1] 2024 elif self.name: 2025 txt = self.name 2026 else: 2027 return None 2028 2029 x0, x1, y0, y1, z0, z1 = self.bounds() 2030 d = self.diagonal_size() 2031 if point is None: 2032 if d: 2033 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2034 # point = self.closest_point([x1, y0, z1]) 2035 else: # it's a Point 2036 point = self.transform.position 2037 2038 pt = utils.make3d(point) 2039 2040 if offset is None: 2041 offset = [(x1 - x0) / 1.75, (y1 - y0) / 5, 0] 2042 offset = utils.make3d(offset) 2043 2044 if s is None: 2045 s = d / 20 2046 2047 sph = None 2048 if d and (z1 - z0) / d > 0.1: 2049 sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6) 2050 2051 if c is None: 2052 c = np.array(self.color()) / 1.4 2053 2054 lab = vedo.shapes.Text3D( 2055 txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center" 2056 ) 2057 objs.append(lab) 2058 2059 if d and not sph: 2060 sph = vedo.shapes.Circle(pt, r=s / 3, res=16) 2061 objs.append(sph) 2062 2063 x0, x1, y0, y1, z0, z1 = lab.bounds() 2064 aline = [(x0,y0,z0), (x1,y0,z0), (x1,y1,z0), (x0,y1,z0)] 2065 if rounded: 2066 box = vedo.shapes.KSpline(aline, closed=True) 2067 else: 2068 box = vedo.shapes.Line(aline, closed=True) 2069 2070 cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2] 2071 2072 # box.actor.SetOrigin(cnt) 2073 box.scale([1 + padding, 1 + 2 * padding, 1], origin=cnt) 2074 objs.append(box) 2075 2076 x0, x1, y0, y1, z0, z1 = box.bounds() 2077 if x0 < pt[0] < x1: 2078 c0 = box.closest_point(pt) 2079 c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]] 2080 elif (pt[0] - x0) < (x1 - pt[0]): 2081 c0 = [x0, (y0 + y1) / 2, pt[2]] 2082 c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]] 2083 else: 2084 c0 = [x1, (y0 + y1) / 2, pt[2]] 2085 c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]] 2086 2087 con = vedo.shapes.Line([c0, c1, pt]) 2088 objs.append(con) 2089 2090 mobjs = vedo.merge(objs).c(c).alpha(alpha) 2091 mobjs.name = "FlagPole" 2092 mobjs.bc("tomato").pickable(False) 2093 mobjs.properties.LightingOff() 2094 mobjs.properties.SetLineWidth(lw) 2095 mobjs.actor.UseBoundsOff() 2096 mobjs.actor.SetPosition([0,0,0]) 2097 mobjs.actor.SetOrigin(pt) 2098 return mobjs 2099 2100 # mobjs = vedo.Assembly(objs)#.c(c).alpha(alpha) 2101 # mobjs.name = "FlagPole" 2102 # # mobjs.bc("tomato").pickable(False) 2103 # # mobjs.properties.LightingOff() 2104 # # mobjs.properties.SetLineWidth(lw) 2105 # # mobjs.actor.UseBoundsOff() 2106 # # mobjs.actor.SetPosition([0,0,0]) 2107 # # mobjs.actor.SetOrigin(pt) 2108 # # print(pt) 2109 # return mobjs 2110 2111 def flagpost( 2112 self, 2113 txt=None, 2114 point=None, 2115 offset=None, 2116 s=1.0, 2117 c="k9", 2118 bc="k1", 2119 alpha=1, 2120 lw=0, 2121 font="Calco", 2122 justify="center-left", 2123 vspacing=1.0, 2124 ) -> Union["vedo.addons.Flagpost", None]: 2125 """ 2126 Generate a flag post style element to describe an object. 2127 2128 Arguments: 2129 txt : (str) 2130 Text to display. The default is the filename or the object name. 2131 point : (list) 2132 position of the flag anchor point. The default is None. 2133 offset : (list) 2134 a 3D displacement or offset. The default is None. 2135 s : (float) 2136 size of the text to be shown 2137 c : (list) 2138 color of text and line 2139 bc : (list) 2140 color of the flag background 2141 alpha : (float) 2142 opacity of text and box. 2143 lw : (int) 2144 line with of box frame. The default is 0. 2145 font : (str) 2146 font name. Use a monospace font for better rendering. The default is "Calco". 2147 Type `vedo -r fonts` for a font demo. 2148 Check [available fonts here](https://vedo.embl.es/fonts). 2149 justify : (str) 2150 internal text justification. The default is "center-left". 2151 vspacing : (float) 2152 vertical spacing between lines. 2153 2154 Examples: 2155 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py) 2156 2157  2158 """ 2159 if txt is None: 2160 if self.filename: 2161 txt = self.filename.split("/")[-1] 2162 elif self.name: 2163 txt = self.name 2164 else: 2165 return None 2166 2167 x0, x1, y0, y1, z0, z1 = self.bounds() 2168 d = self.diagonal_size() 2169 if point is None: 2170 if d: 2171 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2172 else: # it's a Point 2173 point = self.transform.position 2174 2175 point = utils.make3d(point) 2176 2177 if offset is None: 2178 offset = [0, 0, (z1 - z0) / 2] 2179 offset = utils.make3d(offset) 2180 2181 fpost = vedo.addons.Flagpost( 2182 txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing 2183 ) 2184 self._caption = fpost 2185 return fpost 2186 2187 def caption( 2188 self, 2189 txt=None, 2190 point=None, 2191 size=(0.30, 0.15), 2192 padding=5, 2193 font="Calco", 2194 justify="center-right", 2195 vspacing=1.0, 2196 c=None, 2197 alpha=1.0, 2198 lw=1, 2199 ontop=True, 2200 ) -> Union["vtki.vtkCaptionActor2D", None]: 2201 """ 2202 Create a 2D caption to an object which follows the camera movements. 2203 Latex is not supported. Returns the same input object for concatenation. 2204 2205 See also `flagpole()`, `flagpost()`, `labels()` and `legend()` 2206 with similar functionality. 2207 2208 Arguments: 2209 txt : (str) 2210 text to be rendered. The default is the file name. 2211 point : (list) 2212 anchoring point. The default is None. 2213 size : (list) 2214 (width, height) of the caption box. The default is (0.30, 0.15). 2215 padding : (float) 2216 padding space of the caption box in pixels. The default is 5. 2217 font : (str) 2218 font name. Use a monospace font for better rendering. The default is "VictorMono". 2219 Type `vedo -r fonts` for a font demo. 2220 Check [available fonts here](https://vedo.embl.es/fonts). 2221 justify : (str) 2222 internal text justification. The default is "center-right". 2223 vspacing : (float) 2224 vertical spacing between lines. The default is 1. 2225 c : (str) 2226 text and box color. The default is 'lb'. 2227 alpha : (float) 2228 text and box transparency. The default is 1. 2229 lw : (int) 2230 line width in pixels. The default is 1. 2231 ontop : (bool) 2232 keep the 2d caption always on top. The default is True. 2233 2234 Examples: 2235 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 2236 2237  2238 2239 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 2240 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 2241 """ 2242 if txt is None: 2243 if self.filename: 2244 txt = self.filename.split("/")[-1] 2245 elif self.name: 2246 txt = self.name 2247 2248 if not txt: # disable it 2249 self._caption = None 2250 return None 2251 2252 for r in vedo.shapes._reps: 2253 txt = txt.replace(r[0], r[1]) 2254 2255 if c is None: 2256 c = np.array(self.properties.GetColor()) / 2 2257 else: 2258 c = colors.get_color(c) 2259 2260 if point is None: 2261 x0, x1, y0, y1, _, z1 = self.dataset.GetBounds() 2262 pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1] 2263 point = self.closest_point(pt) 2264 2265 capt = vtki.vtkCaptionActor2D() 2266 capt.SetAttachmentPoint(point) 2267 capt.SetBorder(True) 2268 capt.SetLeader(True) 2269 sph = vtki.new("SphereSource") 2270 sph.Update() 2271 capt.SetLeaderGlyphData(sph.GetOutput()) 2272 capt.SetMaximumLeaderGlyphSize(5) 2273 capt.SetPadding(int(padding)) 2274 capt.SetCaption(txt) 2275 capt.SetWidth(size[0]) 2276 capt.SetHeight(size[1]) 2277 capt.SetThreeDimensionalLeader(not ontop) 2278 2279 pra = capt.GetProperty() 2280 pra.SetColor(c) 2281 pra.SetOpacity(alpha) 2282 pra.SetLineWidth(lw) 2283 2284 pr = capt.GetCaptionTextProperty() 2285 pr.SetFontFamily(vtki.VTK_FONT_FILE) 2286 fl = utils.get_font_path(font) 2287 pr.SetFontFile(fl) 2288 pr.ShadowOff() 2289 pr.BoldOff() 2290 pr.FrameOff() 2291 pr.SetColor(c) 2292 pr.SetOpacity(alpha) 2293 pr.SetJustificationToLeft() 2294 if "top" in justify: 2295 pr.SetVerticalJustificationToTop() 2296 if "bottom" in justify: 2297 pr.SetVerticalJustificationToBottom() 2298 if "cent" in justify: 2299 pr.SetVerticalJustificationToCentered() 2300 pr.SetJustificationToCentered() 2301 if "left" in justify: 2302 pr.SetJustificationToLeft() 2303 if "right" in justify: 2304 pr.SetJustificationToRight() 2305 pr.SetLineSpacing(vspacing) 2306 return capt 2307 2308 2309##################################################################### 2310class MeshVisual(PointsVisual): 2311 """Class to manage the visual aspects of a ``Maesh`` object.""" 2312 2313 def __init__(self) -> None: 2314 # print("INIT MeshVisual", super()) 2315 super().__init__() 2316 2317 def follow_camera(self, camera=None, origin=None) -> Self: 2318 """ 2319 Return an object that will follow camera movements and stay locked to it. 2320 Use `mesh.follow_camera(False)` to disable it. 2321 2322 A `vtkCamera` object can also be passed. 2323 """ 2324 if camera is False: 2325 try: 2326 self.SetCamera(None) 2327 return self 2328 except AttributeError: 2329 return self 2330 2331 factor = vtki.vtkFollower() 2332 factor.SetMapper(self.mapper) 2333 factor.SetProperty(self.properties) 2334 factor.SetBackfaceProperty(self.actor.GetBackfaceProperty()) 2335 factor.SetTexture(self.actor.GetTexture()) 2336 factor.SetScale(self.actor.GetScale()) 2337 # factor.SetOrientation(self.actor.GetOrientation()) 2338 factor.SetPosition(self.actor.GetPosition()) 2339 factor.SetUseBounds(self.actor.GetUseBounds()) 2340 2341 if origin is None: 2342 factor.SetOrigin(self.actor.GetOrigin()) 2343 else: 2344 factor.SetOrigin(origin) 2345 2346 factor.PickableOff() 2347 2348 if isinstance(camera, vtki.vtkCamera): 2349 factor.SetCamera(camera) 2350 else: 2351 plt = vedo.plotter_instance 2352 if plt and plt.renderer and plt.renderer.GetActiveCamera(): 2353 factor.SetCamera(plt.renderer.GetActiveCamera()) 2354 2355 self.actor = None 2356 factor.retrieve_object = weak_ref_to(self) 2357 self.actor = factor 2358 return self 2359 2360 def wireframe(self, value=True) -> Self: 2361 """Set mesh's representation as wireframe or solid surface.""" 2362 if value: 2363 self.properties.SetRepresentationToWireframe() 2364 else: 2365 self.properties.SetRepresentationToSurface() 2366 return self 2367 2368 def flat(self) -> Self: 2369 """Set surface interpolation to flat. 2370 2371 <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" width="700"> 2372 """ 2373 self.properties.SetInterpolationToFlat() 2374 return self 2375 2376 def phong(self) -> Self: 2377 """Set surface interpolation to "phong".""" 2378 self.properties.SetInterpolationToPhong() 2379 return self 2380 2381 def backface_culling(self, value=True) -> Self: 2382 """Set culling of polygons based on orientation of normal with respect to camera.""" 2383 self.properties.SetBackfaceCulling(value) 2384 return self 2385 2386 def render_lines_as_tubes(self, value=True) -> Self: 2387 """Wrap a fake tube around a simple line for visualization""" 2388 self.properties.SetRenderLinesAsTubes(value) 2389 return self 2390 2391 def frontface_culling(self, value=True) -> Self: 2392 """Set culling of polygons based on orientation of normal with respect to camera.""" 2393 self.properties.SetFrontfaceCulling(value) 2394 return self 2395 2396 def backcolor(self, bc=None) -> Union[Self, np.ndarray]: 2397 """ 2398 Set/get mesh's backface color. 2399 """ 2400 back_prop = self.actor.GetBackfaceProperty() 2401 2402 if bc is None: 2403 if back_prop: 2404 return back_prop.GetDiffuseColor() 2405 return self 2406 2407 if self.properties.GetOpacity() < 1: 2408 return self 2409 2410 if not back_prop: 2411 back_prop = vtki.vtkProperty() 2412 2413 back_prop.SetDiffuseColor(colors.get_color(bc)) 2414 back_prop.SetOpacity(self.properties.GetOpacity()) 2415 self.actor.SetBackfaceProperty(back_prop) 2416 self.mapper.ScalarVisibilityOff() 2417 return self 2418 2419 def bc(self, backcolor=False) -> Union[Self, np.ndarray]: 2420 """Shortcut for `mesh.backcolor()`.""" 2421 return self.backcolor(backcolor) 2422 2423 def linewidth(self, lw=None) -> Union[Self, int]: 2424 """Set/get width of mesh edges. Same as `lw()`.""" 2425 if lw is not None: 2426 if lw == 0: 2427 self.properties.EdgeVisibilityOff() 2428 self.properties.SetRepresentationToSurface() 2429 return self 2430 self.properties.EdgeVisibilityOn() 2431 self.properties.SetLineWidth(lw) 2432 else: 2433 return self.properties.GetLineWidth() 2434 return self 2435 2436 def lw(self, linewidth=None) -> Union[Self, int]: 2437 """Set/get width of mesh edges. Same as `linewidth()`.""" 2438 return self.linewidth(linewidth) 2439 2440 def linecolor(self, lc=None) -> Union[Self, np.ndarray]: 2441 """Set/get color of mesh edges. Same as `lc()`.""" 2442 if lc is None: 2443 return np.array(self.properties.GetEdgeColor()) 2444 self.properties.EdgeVisibilityOn() 2445 self.properties.SetEdgeColor(colors.get_color(lc)) 2446 return self 2447 2448 def lc(self, linecolor=None) -> Union[Self, np.ndarray]: 2449 """Set/get color of mesh edges. Same as `linecolor()`.""" 2450 return self.linecolor(linecolor) 2451 2452 def texture( 2453 self, 2454 tname, 2455 tcoords=None, 2456 interpolate=True, 2457 repeat=True, 2458 edge_clamp=False, 2459 scale=None, 2460 ushift=None, 2461 vshift=None, 2462 ) -> Self: 2463 """ 2464 Assign a texture to mesh from image file or predefined texture `tname`. 2465 If tname is set to `None` texture is disabled. 2466 Input tname can also be an array or a `vtkTexture`. 2467 2468 Arguments: 2469 tname : (numpy.array, str, Image, vtkTexture, None) 2470 the input texture to be applied. Can be a numpy array, a path to an image file, 2471 a vedo Image. The None value disables texture. 2472 tcoords : (numpy.array, str) 2473 this is the (u,v) texture coordinate array. Can also be a string of an existing array 2474 in the mesh. 2475 interpolate : (bool) 2476 turn on/off linear interpolation of the texture map when rendering. 2477 repeat : (bool) 2478 repeat of the texture when tcoords extend beyond the [0,1] range. 2479 edge_clamp : (bool) 2480 turn on/off the clamping of the texture map when 2481 the texture coords extend beyond the [0,1] range. 2482 Only used when repeat is False, and edge clamping is supported by the graphics card. 2483 scale : (bool) 2484 scale the texture image by this factor 2485 ushift : (bool) 2486 shift u-coordinates of texture by this amount 2487 vshift : (bool) 2488 shift v-coordinates of texture by this amount 2489 2490 Examples: 2491 - [texturecubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/texturecubes.py) 2492 2493  2494 """ 2495 pd = self.dataset 2496 out_img = None 2497 2498 if tname is None: # disable texture 2499 pd.GetPointData().SetTCoords(None) 2500 pd.GetPointData().Modified() 2501 return self ###################################### 2502 2503 if isinstance(tname, vtki.vtkTexture): 2504 tu = tname 2505 2506 elif isinstance(tname, vedo.Image): 2507 tu = vtki.vtkTexture() 2508 out_img = tname.dataset 2509 2510 elif utils.is_sequence(tname): 2511 tu = vtki.vtkTexture() 2512 out_img = vedo.image._get_img(tname) 2513 2514 elif isinstance(tname, str): 2515 tu = vtki.vtkTexture() 2516 2517 if "https://" in tname: 2518 try: 2519 tname = vedo.file_io.download(tname, verbose=False) 2520 except: 2521 vedo.logger.error(f"texture {tname} could not be downloaded") 2522 return self 2523 2524 fn = tname + ".jpg" 2525 if os.path.exists(tname): 2526 fn = tname 2527 else: 2528 vedo.logger.error(f"texture file {tname} does not exist") 2529 return self 2530 2531 fnl = fn.lower() 2532 if ".jpg" in fnl or ".jpeg" in fnl: 2533 reader = vtki.new("JPEGReader") 2534 elif ".png" in fnl: 2535 reader = vtki.new("PNGReader") 2536 elif ".bmp" in fnl: 2537 reader = vtki.new("BMPReader") 2538 else: 2539 vedo.logger.error("in texture() supported files are only PNG, BMP or JPG") 2540 return self 2541 reader.SetFileName(fn) 2542 reader.Update() 2543 out_img = reader.GetOutput() 2544 2545 else: 2546 vedo.logger.error(f"in texture() cannot understand input {type(tname)}") 2547 return self 2548 2549 if tcoords is not None: 2550 2551 if isinstance(tcoords, str): 2552 vtarr = pd.GetPointData().GetArray(tcoords) 2553 2554 else: 2555 tcoords = np.asarray(tcoords) 2556 if tcoords.ndim != 2: 2557 vedo.logger.error("tcoords must be a 2-dimensional array") 2558 return self 2559 if tcoords.shape[0] != pd.GetNumberOfPoints(): 2560 vedo.logger.error("nr of texture coords must match nr of points") 2561 return self 2562 if tcoords.shape[1] != 2: 2563 vedo.logger.error("tcoords texture vector must have 2 components") 2564 vtarr = utils.numpy2vtk(tcoords) 2565 vtarr.SetName("TCoordinates") 2566 2567 pd.GetPointData().SetTCoords(vtarr) 2568 pd.GetPointData().Modified() 2569 2570 elif not pd.GetPointData().GetTCoords(): 2571 2572 # TCoords still void.. 2573 # check that there are no texture-like arrays: 2574 names = self.pointdata.keys() 2575 candidate_arr = "" 2576 for name in names: 2577 vtarr = pd.GetPointData().GetArray(name) 2578 if vtarr.GetNumberOfComponents() != 2: 2579 continue 2580 t0, t1 = vtarr.GetRange() 2581 if t0 >= 0 and t1 <= 1: 2582 candidate_arr = name 2583 2584 if candidate_arr: 2585 2586 vtarr = pd.GetPointData().GetArray(candidate_arr) 2587 pd.GetPointData().SetTCoords(vtarr) 2588 pd.GetPointData().Modified() 2589 2590 else: 2591 # last resource is automatic mapping 2592 tmapper = vtki.new("TextureMapToPlane") 2593 tmapper.AutomaticPlaneGenerationOn() 2594 tmapper.SetInputData(pd) 2595 tmapper.Update() 2596 tc = tmapper.GetOutput().GetPointData().GetTCoords() 2597 if scale or ushift or vshift: 2598 ntc = utils.vtk2numpy(tc) 2599 if scale: 2600 ntc *= scale 2601 if ushift: 2602 ntc[:, 0] += ushift 2603 if vshift: 2604 ntc[:, 1] += vshift 2605 tc = utils.numpy2vtk(tc) 2606 pd.GetPointData().SetTCoords(tc) 2607 pd.GetPointData().Modified() 2608 2609 if out_img: 2610 tu.SetInputData(out_img) 2611 tu.SetInterpolate(interpolate) 2612 tu.SetRepeat(repeat) 2613 tu.SetEdgeClamp(edge_clamp) 2614 2615 self.properties.SetColor(1, 1, 1) 2616 self.mapper.ScalarVisibilityOff() 2617 self.actor.SetTexture(tu) 2618 2619 # if seam_threshold is not None: 2620 # tname = self.dataset.GetPointData().GetTCoords().GetName() 2621 # grad = self.gradient(tname) 2622 # ugrad, vgrad = np.split(grad, 2, axis=1) 2623 # ugradm, vgradm = utils.mag2(ugrad), utils.mag2(vgrad) 2624 # gradm = np.log(ugradm + vgradm) 2625 # largegrad_ids = np.arange(len(grad))[gradm > seam_threshold * 4] 2626 # uvmap = self.pointdata[tname] 2627 # # collapse triangles that have large gradient 2628 # new_points = self.points.copy() 2629 # for f in self.cells: 2630 # if np.isin(f, largegrad_ids).all(): 2631 # id1, id2, id3 = f 2632 # uv1, uv2, uv3 = uvmap[f] 2633 # d12 = utils.mag2(uv1 - uv2) 2634 # d23 = utils.mag2(uv2 - uv3) 2635 # d31 = utils.mag2(uv3 - uv1) 2636 # idm = np.argmin([d12, d23, d31]) 2637 # if idm == 0: 2638 # new_points[id1] = new_points[id3] 2639 # new_points[id2] = new_points[id3] 2640 # elif idm == 1: 2641 # new_points[id2] = new_points[id1] 2642 # new_points[id3] = new_points[id1] 2643 # self.points = new_points 2644 2645 self.dataset.Modified() 2646 return self 2647 2648######################################################################################## 2649class VolumeVisual(CommonVisual): 2650 """Class to manage the visual aspects of a ``Volume`` object.""" 2651 2652 def __init__(self) -> None: 2653 # print("INIT VolumeVisual") 2654 super().__init__() 2655 2656 def alpha_unit(self, u=None) -> Union[Self, float]: 2657 """ 2658 Defines light attenuation per unit length. Default is 1. 2659 The larger the unit length, the further light has to travel to attenuate the same amount. 2660 2661 E.g., if you set the unit distance to 0, you will get full opacity. 2662 It means that when light travels 0 distance it's already attenuated a finite amount. 2663 Thus, any finite distance should attenuate all light. 2664 The larger you make the unit distance, the more transparent the rendering becomes. 2665 """ 2666 if u is None: 2667 return self.properties.GetScalarOpacityUnitDistance() 2668 self.properties.SetScalarOpacityUnitDistance(u) 2669 return self 2670 2671 def alpha_gradient(self, alpha_grad, vmin=None, vmax=None) -> Self: 2672 """ 2673 Assign a set of tranparencies to a volume's gradient 2674 along the range of the scalar value. 2675 A single constant value can also be assigned. 2676 The gradient function is used to decrease the opacity 2677 in the "flat" regions of the volume while maintaining the opacity 2678 at the boundaries between material types. The gradient is measured 2679 as the amount by which the intensity changes over unit distance. 2680 2681 The format for alpha_grad is the same as for method `volume.alpha()`. 2682 """ 2683 if vmin is None: 2684 vmin, _ = self.dataset.GetScalarRange() 2685 if vmax is None: 2686 _, vmax = self.dataset.GetScalarRange() 2687 2688 if alpha_grad is None: 2689 self.properties.DisableGradientOpacityOn() 2690 return self 2691 2692 self.properties.DisableGradientOpacityOff() 2693 2694 gotf = self.properties.GetGradientOpacity() 2695 if utils.is_sequence(alpha_grad): 2696 alpha_grad = np.array(alpha_grad) 2697 if len(alpha_grad.shape) == 1: # user passing a flat list e.g. (0.0, 0.3, 0.9, 1) 2698 for i, al in enumerate(alpha_grad): 2699 xalpha = vmin + (vmax - vmin) * i / (len(alpha_grad) - 1) 2700 # Create transfer mapping scalar value to gradient opacity 2701 gotf.AddPoint(xalpha, al) 2702 elif len(alpha_grad.shape) == 2: # user passing [(x0,alpha0), ...] 2703 gotf.AddPoint(vmin, alpha_grad[0][1]) 2704 for xalpha, al in alpha_grad: 2705 # Create transfer mapping scalar value to opacity 2706 gotf.AddPoint(xalpha, al) 2707 gotf.AddPoint(vmax, alpha_grad[-1][1]) 2708 # print("alpha_grad at", round(xalpha, 1), "\tset to", al) 2709 else: 2710 gotf.AddPoint(vmin, alpha_grad) # constant alpha_grad 2711 gotf.AddPoint(vmax, alpha_grad) 2712 return self 2713 2714 def cmap(self, c, alpha=None, vmin=None, vmax=None) -> Self: 2715 """Same as `color()`. 2716 2717 Arguments: 2718 alpha : (list) 2719 use a list to specify transparencies along the scalar range 2720 vmin : (float) 2721 force the min of the scalar range to be this value 2722 vmax : (float) 2723 force the max of the scalar range to be this value 2724 """ 2725 return self.color(c, alpha, vmin, vmax) 2726 2727 def jittering(self, status=None) -> Union[Self, bool]: 2728 """ 2729 If `True`, each ray traversal direction will be perturbed slightly 2730 using a noise-texture to get rid of wood-grain effects. 2731 """ 2732 if hasattr(self.mapper, "SetUseJittering"): # tetmesh doesnt have it 2733 if status is None: 2734 return self.mapper.GetUseJittering() 2735 self.mapper.SetUseJittering(status) 2736 return self 2737 2738 def hide_voxels(self, ids) -> Self: 2739 """ 2740 Hide voxels (cells) from visualization. 2741 2742 Example: 2743 ```python 2744 from vedo import * 2745 embryo = Volume(dataurl+'embryo.tif') 2746 embryo.hide_voxels(list(range(400000))) 2747 show(embryo, axes=1).close() 2748 ``` 2749 2750 See also: 2751 `volume.mask()` 2752 """ 2753 ghost_mask = np.zeros(self.ncells, dtype=np.uint8) 2754 ghost_mask[ids] = vtki.vtkDataSetAttributes.HIDDENCELL 2755 name = vtki.vtkDataSetAttributes.GhostArrayName() 2756 garr = utils.numpy2vtk(ghost_mask, name=name, dtype=np.uint8) 2757 self.dataset.GetCellData().AddArray(garr) 2758 self.dataset.GetCellData().Modified() 2759 return self 2760 2761 def mask(self, data) -> Self: 2762 """ 2763 Mask a volume visualization with a binary value. 2764 Needs to specify `volume.mapper = "gpu"`. 2765 2766 Example: 2767 ```python 2768 from vedo import np, Volume, show 2769 data_matrix = np.zeros([75, 75, 75], dtype=np.uint8) 2770 # all voxels have value zero except: 2771 data_matrix[ 0:35, 0:35, 0:35] = 1 2772 data_matrix[35:55, 35:55, 35:55] = 2 2773 data_matrix[55:74, 55:74, 55:74] = 3 2774 vol = Volume(data_matrix).cmap('Blues') 2775 vol.mapper = "gpu" 2776 data_mask = np.zeros_like(data_matrix) 2777 data_mask[10:65, 10:60, 20:70] = 1 2778 vol.mask(data_mask) 2779 show(vol, axes=1).close() 2780 ``` 2781 See also: 2782 `volume.hide_voxels()` 2783 """ 2784 rdata = data.astype(np.uint8).ravel(order="F") 2785 varr = utils.numpy2vtk(rdata, name="input_mask") 2786 2787 img = vtki.vtkImageData() 2788 img.SetDimensions(self.dimensions()) 2789 img.GetPointData().AddArray(varr) 2790 img.GetPointData().SetActiveScalars(varr.GetName()) 2791 2792 try: 2793 self.mapper.SetMaskTypeToBinary() 2794 self.mapper.SetMaskInput(img) 2795 except AttributeError: 2796 vedo.logger.error("volume.mask() must specify volume.mapper = 'gpu'") 2797 return self 2798 2799 2800 def mode(self, mode=None) -> Union[Self, int]: 2801 """ 2802 Define the volumetric rendering mode following this: 2803 - 0, composite rendering 2804 - 1, maximum projection rendering 2805 - 2, minimum projection rendering 2806 - 3, average projection rendering 2807 - 4, additive mode 2808 2809 The default mode is "composite" where the scalar values are sampled through 2810 the volume and composited in a front-to-back scheme through alpha blending. 2811 The final color and opacity is determined using the color and opacity transfer 2812 functions specified in alpha keyword. 2813 2814 Maximum and minimum intensity blend modes use the maximum and minimum 2815 scalar values, respectively, along the sampling ray. 2816 The final color and opacity is determined by passing the resultant value 2817 through the color and opacity transfer functions. 2818 2819 Additive blend mode accumulates scalar values by passing each value 2820 through the opacity transfer function and then adding up the product 2821 of the value and its opacity. In other words, the scalar values are scaled 2822 using the opacity transfer function and summed to derive the final color. 2823 Note that the resulting image is always grayscale i.e. aggregated values 2824 are not passed through the color transfer function. 2825 This is because the final value is a derived value and not a real data value 2826 along the sampling ray. 2827 2828 Average intensity blend mode works similar to the additive blend mode where 2829 the scalar values are multiplied by opacity calculated from the opacity 2830 transfer function and then added. 2831 The additional step here is to divide the sum by the number of samples 2832 taken through the volume. 2833 As is the case with the additive intensity projection, the final image will 2834 always be grayscale i.e. the aggregated values are not passed through the 2835 color transfer function. 2836 """ 2837 if mode is None: 2838 return self.mapper.GetBlendMode() 2839 2840 if isinstance(mode, str): 2841 if "comp" in mode: 2842 mode = 0 2843 elif "proj" in mode: 2844 if "max" in mode: 2845 mode = 1 2846 elif "min" in mode: 2847 mode = 2 2848 elif "ave" in mode: 2849 mode = 3 2850 else: 2851 vedo.logger.warning(f"unknown mode {mode}") 2852 mode = 0 2853 elif "add" in mode: 2854 mode = 4 2855 else: 2856 vedo.logger.warning(f"unknown mode {mode}") 2857 mode = 0 2858 2859 self.mapper.SetBlendMode(mode) 2860 return self 2861 2862 def shade(self, status=None) -> Union[Self, bool]: 2863 """ 2864 Set/Get the shading of a Volume. 2865 Shading can be further controlled with `volume.lighting()` method. 2866 2867 If shading is turned on, the mapper may perform shading calculations. 2868 In some cases shading does not apply 2869 (for example, in maximum intensity projection mode). 2870 """ 2871 if status is None: 2872 return self.properties.GetShade() 2873 self.properties.SetShade(status) 2874 return self 2875 2876 def interpolation(self, itype) -> Self: 2877 """ 2878 Set interpolation type. 2879 2880 0=nearest neighbour, 1=linear 2881 """ 2882 self.properties.SetInterpolationType(itype) 2883 return self 2884 2885 2886######################################################################################## 2887class ImageVisual(CommonVisual, Actor3DHelper): 2888 2889 def __init__(self) -> None: 2890 # print("init ImageVisual") 2891 super().__init__() 2892 2893 def memory_size(self) -> int: 2894 """ 2895 Return the size in bytes of the object in memory. 2896 """ 2897 return self.dataset.GetActualMemorySize() 2898 2899 def scalar_range(self) -> np.ndarray: 2900 """ 2901 Return the scalar range of the image. 2902 """ 2903 return np.array(self.dataset.GetScalarRange()) 2904 2905 def alpha(self, a=None) -> Union[Self, float]: 2906 """Set/get image's transparency in the rendering scene.""" 2907 if a is not None: 2908 self.properties.SetOpacity(a) 2909 return self 2910 return self.properties.GetOpacity() 2911 2912 def level(self, value=None) -> Union[Self, float]: 2913 """Get/Set the image color level (brightness) in the rendering scene.""" 2914 if value is None: 2915 return self.properties.GetColorLevel() 2916 self.properties.SetColorLevel(value) 2917 return self 2918 2919 def window(self, value=None) -> Union[Self, float]: 2920 """Get/Set the image color window (contrast) in the rendering scene.""" 2921 if value is None: 2922 return self.properties.GetColorWindow() 2923 self.properties.SetColorWindow(value) 2924 return self 2925 2926 2927class LightKit: 2928 """ 2929 A LightKit consists of three lights, a 'key light', a 'fill light', and a 'head light'. 2930 2931 The main light is the key light. It is usually positioned so that it appears like 2932 an overhead light (like the sun, or a ceiling light). 2933 It is generally positioned to shine down on the scene from about a 45 degree angle vertically 2934 and at least a little offset side to side. The key light usually at least about twice as bright 2935 as the total of all other lights in the scene to provide good modeling of object features. 2936 2937 The other lights in the kit (the fill light, headlight, and a pair of back lights) 2938 are weaker sources that provide extra illumination to fill in the spots that the key light misses. 2939 The fill light is usually positioned across from or opposite from the key light 2940 (though still on the same side of the object as the camera) in order to simulate diffuse reflections 2941 from other objects in the scene. 2942 2943 The headlight, always located at the position of the camera, reduces the contrast between areas lit 2944 by the key and fill light. The two back lights, one on the left of the object as seen from the observer 2945 and one on the right, fill on the high-contrast areas behind the object. 2946 To enforce the relationship between the different lights, the intensity of the fill, back and headlights 2947 are set as a ratio to the key light brightness. 2948 Thus, the brightness of all the lights in the scene can be changed by changing the key light intensity. 2949 2950 All lights are directional lights, infinitely far away with no falloff. Lights move with the camera. 2951 2952 For simplicity, the position of lights in the LightKit can only be specified using angles: 2953 the elevation (latitude) and azimuth (longitude) of each light with respect to the camera, in degrees. 2954 For example, a light at (elevation=0, azimuth=0) is located at the camera (a headlight). 2955 A light at (elevation=90, azimuth=0) is above the lookat point, shining down. 2956 Negative azimuth values move the lights clockwise as seen above, positive values counter-clockwise. 2957 So, a light at (elevation=45, azimuth=-20) is above and in front of the object and shining 2958 slightly from the left side. 2959 2960 LightKit limits the colors that can be assigned to any light to those of incandescent sources such as 2961 light bulbs and sunlight. It defines a special color spectrum called "warmth" from which light colors 2962 can be chosen, where 0 is cold blue, 0.5 is neutral white, and 1 is deep sunset red. 2963 Colors close to 0.5 are "cool whites" and "warm whites," respectively. 2964 2965 Since colors far from white on the warmth scale appear less bright, key-to-fill and key-to-headlight 2966 ratios are skewed by key, fill, and headlight colors. If `maintain_luminance` is set, LightKit will 2967 attempt to compensate for these perceptual differences by increasing the brightness of more saturated colors. 2968 2969 To specify the color of a light, positioning etc you can pass a dictionary with the following keys: 2970 - `intensity` : (float) The intensity of the key light. Default is 1. 2971 - `ratio` : (float) The ratio of the light intensity wrt key light. 2972 - `warmth` : (float) The warmth of the light. Default is 0.5. 2973 - `elevation` : (float) The elevation of the light in degrees. 2974 - `azimuth` : (float) The azimuth of the light in degrees. 2975 2976 Example: 2977 ```python 2978 from vedo import * 2979 lightkit = LightKit(head={"warmth":0.6}) 2980 mesh = Mesh(dataurl+"bunny.obj") 2981 plt = Plotter() 2982 plt.remove_lights().add(mesh, lightkit) 2983 plt.show().close() 2984 ``` 2985 """ 2986 def __init__(self, key=(), fill=(), back=(), head=(), maintain_luminance=False) -> None: 2987 2988 self.lightkit = vtki.new("LightKit") 2989 self.lightkit.SetMaintainLuminance(maintain_luminance) 2990 self.key = dict(key) 2991 self.head = dict(head) 2992 self.fill = dict(fill) 2993 self.back = dict(back) 2994 self.update() 2995 2996 def update(self) -> None: 2997 """Update the LightKit properties.""" 2998 if "warmth" in self.key: 2999 self.lightkit.SetKeyLightWarmth(self.key["warmth"]) 3000 if "warmth" in self.fill: 3001 self.lightkit.SetFillLightWarmth(self.fill["warmth"]) 3002 if "warmth" in self.head: 3003 self.lightkit.SetHeadLightWarmth(self.head["warmth"]) 3004 if "warmth" in self.back: 3005 self.lightkit.SetBackLightWarmth(self.back["warmth"]) 3006 3007 if "intensity" in self.key: 3008 self.lightkit.SetKeyLightIntensity(self.key["intensity"]) 3009 if "ratio" in self.fill: 3010 self.lightkit.SetKeyToFillRatio(self.key["ratio"]) 3011 if "ratio" in self.head: 3012 self.lightkit.SetKeyToHeadRatio(self.key["ratio"]) 3013 if "ratio" in self.back: 3014 self.lightkit.SetKeyToBackRatio(self.key["ratio"]) 3015 3016 if "elevation" in self.key: 3017 self.lightkit.SetKeyLightElevation(self.key["elevation"]) 3018 if "elevation" in self.fill: 3019 self.lightkit.SetFillLightElevation(self.fill["elevation"]) 3020 if "elevation" in self.head: 3021 self.lightkit.SetHeadLightElevation(self.head["elevation"]) 3022 if "elevation" in self.back: 3023 self.lightkit.SetBackLightElevation(self.back["elevation"]) 3024 3025 if "azimuth" in self.key: 3026 self.lightkit.SetKeyLightAzimuth(self.key["azimuth"]) 3027 if "azimuth" in self.fill: 3028 self.lightkit.SetFillLightAzimuth(self.fill["azimuth"]) 3029 if "azimuth" in self.head: 3030 self.lightkit.SetHeadLightAzimuth(self.head["azimuth"]) 3031 if "azimuth" in self.back: 3032 self.lightkit.SetBackLightAzimuth(self.back["azimuth"])
35class CommonVisual: 36 """Class to manage the visual aspects common to all objects.""" 37 38 def __init__(self): 39 # print("init CommonVisual", type(self)) 40 41 self.properties = None 42 43 self.scalarbar = None 44 self.pipeline = None 45 46 self.trail = None 47 self.trail_points = [] 48 self.trail_segment_size = 0 49 self.trail_offset = None 50 51 self.shadows = [] 52 53 self.axes = None 54 self.picked3d = None 55 56 self.rendered_at = set() 57 58 self._ligthingnr = 0 # index of the lighting mode changed from CLI 59 self._cmap_name = "" # remember the cmap name for self._keypress 60 self._caption = None 61 62 63 def print(self): 64 """Print object info.""" 65 print(self.__str__()) 66 return self 67 68 @property 69 def LUT(self) -> np.ndarray: 70 """Return the lookup table of the object as a numpy object.""" 71 try: 72 _lut = self.mapper.GetLookupTable() 73 values = [] 74 for i in range(_lut.GetTable().GetNumberOfTuples()): 75 # print("LUT i =", i, "value =", _lut.GetTableValue(i)) 76 values.append(_lut.GetTableValue(i)) 77 return np.array(values) 78 except AttributeError: 79 return np.array([], dtype=float) 80 81 @LUT.setter 82 def LUT(self, arr): 83 """ 84 Set the lookup table of the object from a numpy or `vtkLookupTable` object. 85 Consider using `cmap()` or `build_lut()` instead as it allows 86 to set the range of the LUT and to use a string name for the color map. 87 """ 88 if isinstance(arr, vtki.vtkLookupTable): 89 newlut = arr 90 self.mapper.SetScalarRange(newlut.GetRange()) 91 else: 92 newlut = vtki.vtkLookupTable() 93 newlut.SetNumberOfTableValues(len(arr)) 94 if len(arr[0]) == 3: 95 arr = np.insert(arr, 3, 1, axis=1) 96 for i, v in enumerate(arr): 97 newlut.SetTableValue(i, v) 98 newlut.SetRange(self.mapper.GetScalarRange()) 99 # print("newlut.GetRange() =", newlut.GetRange()) 100 # print("self.mapper.GetScalarRange() =", self.mapper.GetScalarRange()) 101 newlut.Build() 102 self.mapper.SetLookupTable(newlut) 103 self.mapper.ScalarVisibilityOn() 104 105 def scalar_range(self, vmin=None, vmax=None): 106 """Set the range of the scalar value for visualization.""" 107 if vmin is None and vmax is None: 108 return np.array(self.mapper.GetScalarRange()) 109 if vmax is None: 110 vmin, vmax = vmin # assume it is a list 111 self.mapper.SetScalarRange(float(vmin), float(vmax)) 112 return self 113 114 def add_observer(self, event_name, func, priority=0) -> int: 115 """Add a callback function that will be called when an event occurs.""" 116 event_name = utils.get_vtk_name_event(event_name) 117 idd = self.actor.AddObserver(event_name, func, priority) 118 return idd 119 120 def invoke_event(self, event_name) -> Self: 121 """Invoke an event.""" 122 event_name = utils.get_vtk_name_event(event_name) 123 self.actor.InvokeEvent(event_name) 124 return self 125 126 # def abort_event(self, obs_id): 127 # """Abort an event.""" 128 # cmd = self.actor.GetCommand(obs_id) # vtkCommand 129 # if cmd: 130 # cmd.AbortFlagOn() 131 # return self 132 133 def show(self, **options) -> Union["vedo.Plotter", None]: 134 """ 135 Create on the fly an instance of class `Plotter` or use the last existing one to 136 show one single object. 137 138 This method is meant as a shortcut. If more than one object needs to be visualised 139 please use the syntax `show(mesh1, mesh2, volume, ..., options)`. 140 141 Returns the `Plotter` class instance. 142 """ 143 return vedo.plotter.show(self, **options) 144 145 def thumbnail(self, zoom=1.25, size=(200, 200), bg="white", azimuth=0, elevation=0, axes=False) -> np.ndarray: 146 """Build a thumbnail of the object and return it as an array.""" 147 # speed is about 20Hz for size=[200,200] 148 ren = vtki.vtkRenderer() 149 150 actor = self.actor 151 if isinstance(self, vedo.UnstructuredGrid): 152 geo = vtki.new("GeometryFilter") 153 geo.SetInputData(self.dataset) 154 geo.Update() 155 actor = vedo.Mesh(geo.GetOutput()).cmap("rainbow").actor 156 157 ren.AddActor(actor) 158 if axes: 159 axes = vedo.addons.Axes(self) 160 ren.AddActor(axes.actor) 161 ren.ResetCamera() 162 cam = ren.GetActiveCamera() 163 cam.Zoom(zoom) 164 cam.Elevation(elevation) 165 cam.Azimuth(azimuth) 166 167 ren_win = vtki.vtkRenderWindow() 168 ren_win.SetOffScreenRendering(True) 169 ren_win.SetSize(size) 170 ren.SetBackground(colors.get_color(bg)) 171 ren_win.AddRenderer(ren) 172 ren.ResetCameraClippingRange() 173 ren_win.Render() 174 175 nx, ny = ren_win.GetSize() 176 arr = vtki.vtkUnsignedCharArray() 177 ren_win.GetRGBACharPixelData(0, 0, nx - 1, ny - 1, 0, arr) 178 narr = utils.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3]) 179 narr = np.ascontiguousarray(np.flip(narr, axis=0)) 180 181 ren.RemoveActor(actor) 182 if axes: 183 ren.RemoveActor(axes.actor) 184 ren_win.Finalize() 185 del ren_win 186 return narr 187 188 def pickable(self, value=None) -> Self: 189 """Set/get the pickability property of an object.""" 190 if value is None: 191 return self.actor.GetPickable() 192 self.actor.SetPickable(value) 193 return self 194 195 def use_bounds(self, value=True) -> Self: 196 """ 197 Instruct the current camera to either take into account or ignore 198 the object bounds when resetting. 199 """ 200 self.actor.SetUseBounds(value) 201 return self 202 203 def draggable(self, value=None) -> Self: # NOT FUNCTIONAL? 204 """Set/get the draggability property of an object.""" 205 if value is None: 206 return self.actor.GetDragable() 207 self.actor.SetDragable(value) 208 return self 209 210 def on(self) -> Self: 211 """Switch on object visibility. Object is not removed.""" 212 self.actor.VisibilityOn() 213 try: 214 self.scalarbar.actor.VisibilityOn() 215 except AttributeError: 216 pass 217 try: 218 self.trail.actor.VisibilityOn() 219 except AttributeError: 220 pass 221 try: 222 for sh in self.shadows: 223 sh.actor.VisibilityOn() 224 except AttributeError: 225 pass 226 return self 227 228 def off(self) -> Self: 229 """Switch off object visibility. Object is not removed.""" 230 self.actor.VisibilityOff() 231 try: 232 self.scalarbar.actor.VisibilityOff() 233 except AttributeError: 234 pass 235 try: 236 self.trail.actor.VisibilityOff() 237 except AttributeError: 238 pass 239 try: 240 for sh in self.shadows: 241 sh.actor.VisibilityOff() 242 except AttributeError: 243 pass 244 return self 245 246 def toggle(self) -> Self: 247 """Toggle object visibility on/off.""" 248 v = self.actor.GetVisibility() 249 if v: 250 self.off() 251 else: 252 self.on() 253 return self 254 255 def add_scalarbar( 256 self, 257 title="", 258 pos=(), 259 size=(80, 400), 260 font_size=14, 261 title_yoffset=15, 262 nlabels=None, 263 c=None, 264 horizontal=False, 265 use_alpha=True, 266 label_format=":6.3g", 267 ) -> Self: 268 """ 269 Add a 2D scalar bar for the specified object. 270 271 Arguments: 272 title : (str) 273 scalar bar title 274 pos : (list) 275 position coordinates of the bottom left corner. 276 Can also be a pair of (x,y) values in the range [0,1] 277 to indicate the position of the bottom left and top right corners. 278 size : (float,float) 279 size of the scalarbar in number of pixels (width, height) 280 font_size : (float) 281 size of font for title and numeric labels 282 title_yoffset : (float) 283 vertical space offset between title and color scalarbar 284 nlabels : (int) 285 number of numeric labels 286 c : (list) 287 color of the scalar bar text 288 horizontal : (bool) 289 lay the scalarbar horizontally 290 use_alpha : (bool) 291 render transparency in the color bar itself 292 label_format : (str) 293 c-style format string for numeric labels 294 295 Examples: 296 - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py) 297 - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py) 298 """ 299 plt = vedo.plotter_instance 300 301 if plt and plt.renderer: 302 c = (0.9, 0.9, 0.9) 303 if np.sum(plt.renderer.GetBackground()) > 1.5: 304 c = (0.1, 0.1, 0.1) 305 if isinstance(self.scalarbar, vtki.vtkActor): 306 plt.renderer.RemoveActor(self.scalarbar) 307 elif isinstance(self.scalarbar, vedo.Assembly): 308 for a in self.scalarbar.unpack(): 309 plt.renderer.RemoveActor(a) 310 if c is None: 311 c = "gray" 312 313 sb = vedo.addons.ScalarBar( 314 self, 315 title, 316 pos, 317 size, 318 font_size, 319 title_yoffset, 320 nlabels, 321 c, 322 horizontal, 323 use_alpha, 324 label_format, 325 ) 326 self.scalarbar = sb 327 return self 328 329 def add_scalarbar3d( 330 self, 331 title="", 332 pos=None, 333 size=(0, 0), 334 title_font="", 335 title_xoffset=-1.2, 336 title_yoffset=0.0, 337 title_size=1.5, 338 title_rotation=0.0, 339 nlabels=9, 340 label_font="", 341 label_size=1, 342 label_offset=0.375, 343 label_rotation=0, 344 label_format="", 345 italic=0, 346 c=None, 347 draw_box=True, 348 above_text=None, 349 below_text=None, 350 nan_text="NaN", 351 categories=None, 352 ) -> Self: 353 """ 354 Associate a 3D scalar bar to the object and add it to the scene. 355 The new scalarbar object (Assembly) will be accessible as obj.scalarbar 356 357 Arguments: 358 size : (list) 359 (thickness, length) of scalarbar 360 title : (str) 361 scalar bar title 362 title_xoffset : (float) 363 horizontal space btw title and color scalarbar 364 title_yoffset : (float) 365 vertical space offset 366 title_size : (float) 367 size of title wrt numeric labels 368 title_rotation : (float) 369 title rotation in degrees 370 nlabels : (int) 371 number of numeric labels 372 label_font : (str) 373 font type for labels 374 label_size : (float) 375 label scale factor 376 label_offset : (float) 377 space btw numeric labels and scale 378 label_rotation : (float) 379 label rotation in degrees 380 label_format : (str) 381 label format for floats and integers (e.g. `':.2f'`) 382 draw_box : (bool) 383 draw a box around the colorbar 384 categories : (list) 385 make a categorical scalarbar, 386 the input list will have the format `[value, color, alpha, textlabel]` 387 388 Examples: 389 - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py) 390 """ 391 plt = vedo.plotter_instance 392 if plt and c is None: # automatic black or white 393 c = (0.9, 0.9, 0.9) 394 if np.sum(vedo.get_color(plt.backgrcol)) > 1.5: 395 c = (0.1, 0.1, 0.1) 396 if c is None: 397 c = (0, 0, 0) 398 c = vedo.get_color(c) 399 400 self.scalarbar = vedo.addons.ScalarBar3D( 401 self, 402 title, 403 pos, 404 size, 405 title_font, 406 title_xoffset, 407 title_yoffset, 408 title_size, 409 title_rotation, 410 nlabels, 411 label_font, 412 label_size, 413 label_offset, 414 label_rotation, 415 label_format, 416 italic, 417 c, 418 draw_box, 419 above_text, 420 below_text, 421 nan_text, 422 categories, 423 ) 424 return self 425 426 def color(self, col, alpha=None, vmin=None, vmax=None): 427 """ 428 Assign a color or a set of colors along the range of the scalar value. 429 A single constant color can also be assigned. 430 Any matplotlib color map name is also accepted, e.g. `volume.color('jet')`. 431 432 E.g.: say that your cells scalar runs from -3 to 6, 433 and you want -3 to show red and 1.5 violet and 6 green, then just set: 434 435 `volume.color(['red', 'violet', 'green'])` 436 437 You can also assign a specific color to a aspecific value with eg.: 438 439 `volume.color([(0,'red'), (0.5,'violet'), (1,'green')])` 440 441 Arguments: 442 alpha : (list) 443 use a list to specify transparencies along the scalar range 444 vmin : (float) 445 force the min of the scalar range to be this value 446 vmax : (float) 447 force the max of the scalar range to be this value 448 """ 449 # supersedes method in Points, Mesh 450 451 if col is None: 452 return self 453 454 if vmin is None: 455 vmin, _ = self.dataset.GetScalarRange() 456 if vmax is None: 457 _, vmax = self.dataset.GetScalarRange() 458 ctf = self.properties.GetRGBTransferFunction() 459 ctf.RemoveAllPoints() 460 461 if utils.is_sequence(col): 462 if utils.is_sequence(col[0]) and len(col[0]) == 2: 463 # user passing [(value1, color1), ...] 464 for x, ci in col: 465 r, g, b = colors.get_color(ci) 466 ctf.AddRGBPoint(x, r, g, b) 467 # colors.printc('color at', round(x, 1), 468 # 'set to', colors.get_color_name((r, g, b)), bold=0) 469 else: 470 # user passing [color1, color2, ..] 471 for i, ci in enumerate(col): 472 r, g, b = colors.get_color(ci) 473 x = vmin + (vmax - vmin) * i / (len(col) - 1) 474 ctf.AddRGBPoint(x, r, g, b) 475 elif isinstance(col, str): 476 if col in colors.colors.keys() or col in colors.color_nicks.keys(): 477 r, g, b = colors.get_color(col) 478 ctf.AddRGBPoint(vmin, r, g, b) # constant color 479 ctf.AddRGBPoint(vmax, r, g, b) 480 else: # assume it's a colormap 481 for x in np.linspace(vmin, vmax, num=64, endpoint=True): 482 r, g, b = colors.color_map(x, name=col, vmin=vmin, vmax=vmax) 483 ctf.AddRGBPoint(x, r, g, b) 484 elif isinstance(col, int): 485 r, g, b = colors.get_color(col) 486 ctf.AddRGBPoint(vmin, r, g, b) # constant color 487 ctf.AddRGBPoint(vmax, r, g, b) 488 else: 489 vedo.logger.warning(f"in color() unknown input type {type(col)}") 490 491 if alpha is not None: 492 self.alpha(alpha, vmin=vmin, vmax=vmax) 493 return self 494 495 def alpha(self, alpha, vmin=None, vmax=None) -> Self: 496 """ 497 Assign a set of tranparencies along the range of the scalar value. 498 A single constant value can also be assigned. 499 500 E.g.: say `alpha=(0.0, 0.3, 0.9, 1)` and the scalar range goes from -10 to 150. 501 Then all cells with a value close to -10 will be completely transparent, cells at 1/4 502 of the range will get an alpha equal to 0.3 and voxels with value close to 150 503 will be completely opaque. 504 505 As a second option one can set explicit (x, alpha_x) pairs to define the transfer function. 506 507 E.g.: say `alpha=[(-5, 0), (35, 0.4) (123,0.9)]` and the scalar range goes from -10 to 150. 508 Then all cells below -5 will be completely transparent, cells with a scalar value of 35 509 will get an opacity of 40% and above 123 alpha is set to 90%. 510 """ 511 if vmin is None: 512 vmin, _ = self.dataset.GetScalarRange() 513 if vmax is None: 514 _, vmax = self.dataset.GetScalarRange() 515 otf = self.properties.GetScalarOpacity() 516 otf.RemoveAllPoints() 517 518 if utils.is_sequence(alpha): 519 alpha = np.array(alpha) 520 if len(alpha.shape) == 1: # user passing a flat list e.g. (0.0, 0.3, 0.9, 1) 521 for i, al in enumerate(alpha): 522 xalpha = vmin + (vmax - vmin) * i / (len(alpha) - 1) 523 # Create transfer mapping scalar value to opacity 524 otf.AddPoint(xalpha, al) 525 # print("alpha at", round(xalpha, 1), "\tset to", al) 526 elif len(alpha.shape) == 2: # user passing [(x0,alpha0), ...] 527 otf.AddPoint(vmin, alpha[0][1]) 528 for xalpha, al in alpha: 529 # Create transfer mapping scalar value to opacity 530 otf.AddPoint(xalpha, al) 531 otf.AddPoint(vmax, alpha[-1][1]) 532 533 else: 534 535 otf.AddPoint(vmin, alpha) # constant alpha 536 otf.AddPoint(vmax, alpha) 537 538 return self
Class to manage the visual aspects common to all objects.
38 def __init__(self): 39 # print("init CommonVisual", type(self)) 40 41 self.properties = None 42 43 self.scalarbar = None 44 self.pipeline = None 45 46 self.trail = None 47 self.trail_points = [] 48 self.trail_segment_size = 0 49 self.trail_offset = None 50 51 self.shadows = [] 52 53 self.axes = None 54 self.picked3d = None 55 56 self.rendered_at = set() 57 58 self._ligthingnr = 0 # index of the lighting mode changed from CLI 59 self._cmap_name = "" # remember the cmap name for self._keypress 60 self._caption = None
68 @property 69 def LUT(self) -> np.ndarray: 70 """Return the lookup table of the object as a numpy object.""" 71 try: 72 _lut = self.mapper.GetLookupTable() 73 values = [] 74 for i in range(_lut.GetTable().GetNumberOfTuples()): 75 # print("LUT i =", i, "value =", _lut.GetTableValue(i)) 76 values.append(_lut.GetTableValue(i)) 77 return np.array(values) 78 except AttributeError: 79 return np.array([], dtype=float)
Return the lookup table of the object as a numpy object.
105 def scalar_range(self, vmin=None, vmax=None): 106 """Set the range of the scalar value for visualization.""" 107 if vmin is None and vmax is None: 108 return np.array(self.mapper.GetScalarRange()) 109 if vmax is None: 110 vmin, vmax = vmin # assume it is a list 111 self.mapper.SetScalarRange(float(vmin), float(vmax)) 112 return self
Set the range of the scalar value for visualization.
114 def add_observer(self, event_name, func, priority=0) -> int: 115 """Add a callback function that will be called when an event occurs.""" 116 event_name = utils.get_vtk_name_event(event_name) 117 idd = self.actor.AddObserver(event_name, func, priority) 118 return idd
Add a callback function that will be called when an event occurs.
120 def invoke_event(self, event_name) -> Self: 121 """Invoke an event.""" 122 event_name = utils.get_vtk_name_event(event_name) 123 self.actor.InvokeEvent(event_name) 124 return self
Invoke an event.
133 def show(self, **options) -> Union["vedo.Plotter", None]: 134 """ 135 Create on the fly an instance of class `Plotter` or use the last existing one to 136 show one single object. 137 138 This method is meant as a shortcut. If more than one object needs to be visualised 139 please use the syntax `show(mesh1, mesh2, volume, ..., options)`. 140 141 Returns the `Plotter` class instance. 142 """ 143 return vedo.plotter.show(self, **options)
Create on the fly an instance of class Plotter
or use the last existing one to
show one single object.
This method is meant as a shortcut. If more than one object needs to be visualised
please use the syntax show(mesh1, mesh2, volume, ..., options)
.
Returns the Plotter
class instance.
145 def thumbnail(self, zoom=1.25, size=(200, 200), bg="white", azimuth=0, elevation=0, axes=False) -> np.ndarray: 146 """Build a thumbnail of the object and return it as an array.""" 147 # speed is about 20Hz for size=[200,200] 148 ren = vtki.vtkRenderer() 149 150 actor = self.actor 151 if isinstance(self, vedo.UnstructuredGrid): 152 geo = vtki.new("GeometryFilter") 153 geo.SetInputData(self.dataset) 154 geo.Update() 155 actor = vedo.Mesh(geo.GetOutput()).cmap("rainbow").actor 156 157 ren.AddActor(actor) 158 if axes: 159 axes = vedo.addons.Axes(self) 160 ren.AddActor(axes.actor) 161 ren.ResetCamera() 162 cam = ren.GetActiveCamera() 163 cam.Zoom(zoom) 164 cam.Elevation(elevation) 165 cam.Azimuth(azimuth) 166 167 ren_win = vtki.vtkRenderWindow() 168 ren_win.SetOffScreenRendering(True) 169 ren_win.SetSize(size) 170 ren.SetBackground(colors.get_color(bg)) 171 ren_win.AddRenderer(ren) 172 ren.ResetCameraClippingRange() 173 ren_win.Render() 174 175 nx, ny = ren_win.GetSize() 176 arr = vtki.vtkUnsignedCharArray() 177 ren_win.GetRGBACharPixelData(0, 0, nx - 1, ny - 1, 0, arr) 178 narr = utils.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3]) 179 narr = np.ascontiguousarray(np.flip(narr, axis=0)) 180 181 ren.RemoveActor(actor) 182 if axes: 183 ren.RemoveActor(axes.actor) 184 ren_win.Finalize() 185 del ren_win 186 return narr
Build a thumbnail of the object and return it as an array.
188 def pickable(self, value=None) -> Self: 189 """Set/get the pickability property of an object.""" 190 if value is None: 191 return self.actor.GetPickable() 192 self.actor.SetPickable(value) 193 return self
Set/get the pickability property of an object.
195 def use_bounds(self, value=True) -> Self: 196 """ 197 Instruct the current camera to either take into account or ignore 198 the object bounds when resetting. 199 """ 200 self.actor.SetUseBounds(value) 201 return self
Instruct the current camera to either take into account or ignore the object bounds when resetting.
203 def draggable(self, value=None) -> Self: # NOT FUNCTIONAL? 204 """Set/get the draggability property of an object.""" 205 if value is None: 206 return self.actor.GetDragable() 207 self.actor.SetDragable(value) 208 return self
Set/get the draggability property of an object.
210 def on(self) -> Self: 211 """Switch on object visibility. Object is not removed.""" 212 self.actor.VisibilityOn() 213 try: 214 self.scalarbar.actor.VisibilityOn() 215 except AttributeError: 216 pass 217 try: 218 self.trail.actor.VisibilityOn() 219 except AttributeError: 220 pass 221 try: 222 for sh in self.shadows: 223 sh.actor.VisibilityOn() 224 except AttributeError: 225 pass 226 return self
Switch on object visibility. Object is not removed.
228 def off(self) -> Self: 229 """Switch off object visibility. Object is not removed.""" 230 self.actor.VisibilityOff() 231 try: 232 self.scalarbar.actor.VisibilityOff() 233 except AttributeError: 234 pass 235 try: 236 self.trail.actor.VisibilityOff() 237 except AttributeError: 238 pass 239 try: 240 for sh in self.shadows: 241 sh.actor.VisibilityOff() 242 except AttributeError: 243 pass 244 return self
Switch off object visibility. Object is not removed.
246 def toggle(self) -> Self: 247 """Toggle object visibility on/off.""" 248 v = self.actor.GetVisibility() 249 if v: 250 self.off() 251 else: 252 self.on() 253 return self
Toggle object visibility on/off.
255 def add_scalarbar( 256 self, 257 title="", 258 pos=(), 259 size=(80, 400), 260 font_size=14, 261 title_yoffset=15, 262 nlabels=None, 263 c=None, 264 horizontal=False, 265 use_alpha=True, 266 label_format=":6.3g", 267 ) -> Self: 268 """ 269 Add a 2D scalar bar for the specified object. 270 271 Arguments: 272 title : (str) 273 scalar bar title 274 pos : (list) 275 position coordinates of the bottom left corner. 276 Can also be a pair of (x,y) values in the range [0,1] 277 to indicate the position of the bottom left and top right corners. 278 size : (float,float) 279 size of the scalarbar in number of pixels (width, height) 280 font_size : (float) 281 size of font for title and numeric labels 282 title_yoffset : (float) 283 vertical space offset between title and color scalarbar 284 nlabels : (int) 285 number of numeric labels 286 c : (list) 287 color of the scalar bar text 288 horizontal : (bool) 289 lay the scalarbar horizontally 290 use_alpha : (bool) 291 render transparency in the color bar itself 292 label_format : (str) 293 c-style format string for numeric labels 294 295 Examples: 296 - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py) 297 - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py) 298 """ 299 plt = vedo.plotter_instance 300 301 if plt and plt.renderer: 302 c = (0.9, 0.9, 0.9) 303 if np.sum(plt.renderer.GetBackground()) > 1.5: 304 c = (0.1, 0.1, 0.1) 305 if isinstance(self.scalarbar, vtki.vtkActor): 306 plt.renderer.RemoveActor(self.scalarbar) 307 elif isinstance(self.scalarbar, vedo.Assembly): 308 for a in self.scalarbar.unpack(): 309 plt.renderer.RemoveActor(a) 310 if c is None: 311 c = "gray" 312 313 sb = vedo.addons.ScalarBar( 314 self, 315 title, 316 pos, 317 size, 318 font_size, 319 title_yoffset, 320 nlabels, 321 c, 322 horizontal, 323 use_alpha, 324 label_format, 325 ) 326 self.scalarbar = sb 327 return self
Add a 2D scalar bar for the specified object.
Arguments:
- title : (str) scalar bar title
- pos : (list) position coordinates of the bottom left corner. Can also be a pair of (x,y) values in the range [0,1] to indicate the position of the bottom left and top right corners.
- size : (float,float) size of the scalarbar in number of pixels (width, height)
- font_size : (float) size of font for title and numeric labels
- title_yoffset : (float) vertical space offset between title and color scalarbar
- nlabels : (int) number of numeric labels
- c : (list) color of the scalar bar text
- horizontal : (bool) lay the scalarbar horizontally
- use_alpha : (bool) render transparency in the color bar itself
- label_format : (str) c-style format string for numeric labels
Examples:
329 def add_scalarbar3d( 330 self, 331 title="", 332 pos=None, 333 size=(0, 0), 334 title_font="", 335 title_xoffset=-1.2, 336 title_yoffset=0.0, 337 title_size=1.5, 338 title_rotation=0.0, 339 nlabels=9, 340 label_font="", 341 label_size=1, 342 label_offset=0.375, 343 label_rotation=0, 344 label_format="", 345 italic=0, 346 c=None, 347 draw_box=True, 348 above_text=None, 349 below_text=None, 350 nan_text="NaN", 351 categories=None, 352 ) -> Self: 353 """ 354 Associate a 3D scalar bar to the object and add it to the scene. 355 The new scalarbar object (Assembly) will be accessible as obj.scalarbar 356 357 Arguments: 358 size : (list) 359 (thickness, length) of scalarbar 360 title : (str) 361 scalar bar title 362 title_xoffset : (float) 363 horizontal space btw title and color scalarbar 364 title_yoffset : (float) 365 vertical space offset 366 title_size : (float) 367 size of title wrt numeric labels 368 title_rotation : (float) 369 title rotation in degrees 370 nlabels : (int) 371 number of numeric labels 372 label_font : (str) 373 font type for labels 374 label_size : (float) 375 label scale factor 376 label_offset : (float) 377 space btw numeric labels and scale 378 label_rotation : (float) 379 label rotation in degrees 380 label_format : (str) 381 label format for floats and integers (e.g. `':.2f'`) 382 draw_box : (bool) 383 draw a box around the colorbar 384 categories : (list) 385 make a categorical scalarbar, 386 the input list will have the format `[value, color, alpha, textlabel]` 387 388 Examples: 389 - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py) 390 """ 391 plt = vedo.plotter_instance 392 if plt and c is None: # automatic black or white 393 c = (0.9, 0.9, 0.9) 394 if np.sum(vedo.get_color(plt.backgrcol)) > 1.5: 395 c = (0.1, 0.1, 0.1) 396 if c is None: 397 c = (0, 0, 0) 398 c = vedo.get_color(c) 399 400 self.scalarbar = vedo.addons.ScalarBar3D( 401 self, 402 title, 403 pos, 404 size, 405 title_font, 406 title_xoffset, 407 title_yoffset, 408 title_size, 409 title_rotation, 410 nlabels, 411 label_font, 412 label_size, 413 label_offset, 414 label_rotation, 415 label_format, 416 italic, 417 c, 418 draw_box, 419 above_text, 420 below_text, 421 nan_text, 422 categories, 423 ) 424 return self
Associate a 3D scalar bar to the object and add it to the scene. The new scalarbar object (Assembly) will be accessible as obj.scalarbar
Arguments:
- size : (list) (thickness, length) of scalarbar
- title : (str) scalar bar title
- title_xoffset : (float) horizontal space btw title and color scalarbar
- title_yoffset : (float) vertical space offset
- title_size : (float) size of title wrt numeric labels
- title_rotation : (float) title rotation in degrees
- nlabels : (int) number of numeric labels
- label_font : (str) font type for labels
- label_size : (float) label scale factor
- label_offset : (float) space btw numeric labels and scale
- label_rotation : (float) label rotation in degrees
- label_format : (str)
label format for floats and integers (e.g.
':.2f'
) - draw_box : (bool) draw a box around the colorbar
- categories : (list)
make a categorical scalarbar,
the input list will have the format
[value, color, alpha, textlabel]
Examples:
426 def color(self, col, alpha=None, vmin=None, vmax=None): 427 """ 428 Assign a color or a set of colors along the range of the scalar value. 429 A single constant color can also be assigned. 430 Any matplotlib color map name is also accepted, e.g. `volume.color('jet')`. 431 432 E.g.: say that your cells scalar runs from -3 to 6, 433 and you want -3 to show red and 1.5 violet and 6 green, then just set: 434 435 `volume.color(['red', 'violet', 'green'])` 436 437 You can also assign a specific color to a aspecific value with eg.: 438 439 `volume.color([(0,'red'), (0.5,'violet'), (1,'green')])` 440 441 Arguments: 442 alpha : (list) 443 use a list to specify transparencies along the scalar range 444 vmin : (float) 445 force the min of the scalar range to be this value 446 vmax : (float) 447 force the max of the scalar range to be this value 448 """ 449 # supersedes method in Points, Mesh 450 451 if col is None: 452 return self 453 454 if vmin is None: 455 vmin, _ = self.dataset.GetScalarRange() 456 if vmax is None: 457 _, vmax = self.dataset.GetScalarRange() 458 ctf = self.properties.GetRGBTransferFunction() 459 ctf.RemoveAllPoints() 460 461 if utils.is_sequence(col): 462 if utils.is_sequence(col[0]) and len(col[0]) == 2: 463 # user passing [(value1, color1), ...] 464 for x, ci in col: 465 r, g, b = colors.get_color(ci) 466 ctf.AddRGBPoint(x, r, g, b) 467 # colors.printc('color at', round(x, 1), 468 # 'set to', colors.get_color_name((r, g, b)), bold=0) 469 else: 470 # user passing [color1, color2, ..] 471 for i, ci in enumerate(col): 472 r, g, b = colors.get_color(ci) 473 x = vmin + (vmax - vmin) * i / (len(col) - 1) 474 ctf.AddRGBPoint(x, r, g, b) 475 elif isinstance(col, str): 476 if col in colors.colors.keys() or col in colors.color_nicks.keys(): 477 r, g, b = colors.get_color(col) 478 ctf.AddRGBPoint(vmin, r, g, b) # constant color 479 ctf.AddRGBPoint(vmax, r, g, b) 480 else: # assume it's a colormap 481 for x in np.linspace(vmin, vmax, num=64, endpoint=True): 482 r, g, b = colors.color_map(x, name=col, vmin=vmin, vmax=vmax) 483 ctf.AddRGBPoint(x, r, g, b) 484 elif isinstance(col, int): 485 r, g, b = colors.get_color(col) 486 ctf.AddRGBPoint(vmin, r, g, b) # constant color 487 ctf.AddRGBPoint(vmax, r, g, b) 488 else: 489 vedo.logger.warning(f"in color() unknown input type {type(col)}") 490 491 if alpha is not None: 492 self.alpha(alpha, vmin=vmin, vmax=vmax) 493 return self
Assign a color or a set of colors along the range of the scalar value.
A single constant color can also be assigned.
Any matplotlib color map name is also accepted, e.g. volume.color('jet')
.
E.g.: say that your cells scalar runs from -3 to 6, and you want -3 to show red and 1.5 violet and 6 green, then just set:
volume.color(['red', 'violet', 'green'])
You can also assign a specific color to a aspecific value with eg.:
volume.color([(0,'red'), (0.5,'violet'), (1,'green')])
Arguments:
- alpha : (list) use a list to specify transparencies along the scalar range
- vmin : (float) force the min of the scalar range to be this value
- vmax : (float) force the max of the scalar range to be this value
495 def alpha(self, alpha, vmin=None, vmax=None) -> Self: 496 """ 497 Assign a set of tranparencies along the range of the scalar value. 498 A single constant value can also be assigned. 499 500 E.g.: say `alpha=(0.0, 0.3, 0.9, 1)` and the scalar range goes from -10 to 150. 501 Then all cells with a value close to -10 will be completely transparent, cells at 1/4 502 of the range will get an alpha equal to 0.3 and voxels with value close to 150 503 will be completely opaque. 504 505 As a second option one can set explicit (x, alpha_x) pairs to define the transfer function. 506 507 E.g.: say `alpha=[(-5, 0), (35, 0.4) (123,0.9)]` and the scalar range goes from -10 to 150. 508 Then all cells below -5 will be completely transparent, cells with a scalar value of 35 509 will get an opacity of 40% and above 123 alpha is set to 90%. 510 """ 511 if vmin is None: 512 vmin, _ = self.dataset.GetScalarRange() 513 if vmax is None: 514 _, vmax = self.dataset.GetScalarRange() 515 otf = self.properties.GetScalarOpacity() 516 otf.RemoveAllPoints() 517 518 if utils.is_sequence(alpha): 519 alpha = np.array(alpha) 520 if len(alpha.shape) == 1: # user passing a flat list e.g. (0.0, 0.3, 0.9, 1) 521 for i, al in enumerate(alpha): 522 xalpha = vmin + (vmax - vmin) * i / (len(alpha) - 1) 523 # Create transfer mapping scalar value to opacity 524 otf.AddPoint(xalpha, al) 525 # print("alpha at", round(xalpha, 1), "\tset to", al) 526 elif len(alpha.shape) == 2: # user passing [(x0,alpha0), ...] 527 otf.AddPoint(vmin, alpha[0][1]) 528 for xalpha, al in alpha: 529 # Create transfer mapping scalar value to opacity 530 otf.AddPoint(xalpha, al) 531 otf.AddPoint(vmax, alpha[-1][1]) 532 533 else: 534 535 otf.AddPoint(vmin, alpha) # constant alpha 536 otf.AddPoint(vmax, alpha) 537 538 return self
Assign a set of tranparencies along the range of the scalar value. A single constant value can also be assigned.
E.g.: say alpha=(0.0, 0.3, 0.9, 1)
and the scalar range goes from -10 to 150.
Then all cells with a value close to -10 will be completely transparent, cells at 1/4
of the range will get an alpha equal to 0.3 and voxels with value close to 150
will be completely opaque.
As a second option one can set explicit (x, alpha_x) pairs to define the transfer function.
E.g.: say alpha=[(-5, 0), (35, 0.4) (123,0.9)]
and the scalar range goes from -10 to 150.
Then all cells below -5 will be completely transparent, cells with a scalar value of 35
will get an opacity of 40% and above 123 alpha is set to 90%.
819class PointsVisual(CommonVisual): 820 """Class to manage the visual aspects of a ``Points`` object.""" 821 822 def __init__(self): 823 # print("init PointsVisual") 824 super().__init__() 825 self.properties_backface = None 826 self._cmap_name = None 827 self.trail = None 828 self.trail_offset = 0 829 self.trail_points = [] 830 self._caption = None 831 832 833 def clone2d(self, size=None, offset=(), scale=None): 834 """ 835 Turn a 3D `Points` or `Mesh` into a flat 2D actor. 836 Returns a `Actor2D`. 837 838 Arguments: 839 size : (float) 840 size as scaling factor for the 2D actor 841 offset : (list) 842 2D (x, y) position of the actor in the range [-1, 1] 843 scale : (float) 844 Deprecated. Use `size` instead. 845 846 Examples: 847 - [clone2d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/clone2d.py) 848 849  850 """ 851 # assembly.Assembly.clone2d() superseeds this method 852 if scale is not None: 853 vedo.logger.warning("clone2d(): use keyword size not scale") 854 size = scale 855 856 if size is None: 857 # work out a reasonable scale 858 msiz = self.diagonal_size() 859 if vedo.plotter_instance and vedo.plotter_instance.window: 860 sz = vedo.plotter_instance.window.GetSize() 861 dsiz = utils.mag(sz) 862 size = dsiz / msiz / 10 863 else: 864 size = 350 / msiz 865 866 tp = vtki.new("TransformPolyDataFilter") 867 transform = vtki.vtkTransform() 868 transform.Scale(size, size, size) 869 if len(offset) == 0: 870 offset = self.pos() 871 transform.Translate(-utils.make3d(offset)) 872 tp.SetTransform(transform) 873 tp.SetInputData(self.dataset) 874 tp.Update() 875 poly = tp.GetOutput() 876 877 cm = self.mapper.GetColorMode() 878 lut = self.mapper.GetLookupTable() 879 sv = self.mapper.GetScalarVisibility() 880 use_lut = self.mapper.GetUseLookupTableScalarRange() 881 vrange = self.mapper.GetScalarRange() 882 sm = self.mapper.GetScalarMode() 883 884 act2d = Actor2D() 885 act2d.dataset = poly 886 mapper2d = vtki.new("PolyDataMapper2D") 887 mapper2d.SetInputData(poly) 888 mapper2d.SetColorMode(cm) 889 mapper2d.SetLookupTable(lut) 890 mapper2d.SetScalarVisibility(sv) 891 mapper2d.SetUseLookupTableScalarRange(use_lut) 892 mapper2d.SetScalarRange(vrange) 893 mapper2d.SetScalarMode(sm) 894 act2d.mapper = mapper2d 895 896 act2d.GetPositionCoordinate().SetCoordinateSystem(4) 897 act2d.properties.SetColor(self.color()) 898 act2d.properties.SetOpacity(self.alpha()) 899 act2d.properties.SetLineWidth(self.properties.GetLineWidth()) 900 act2d.properties.SetPointSize(self.properties.GetPointSize()) 901 act2d.properties.SetDisplayLocation(0) # 0 = back, 1 = front 902 act2d.PickableOff() 903 return act2d 904 905 ################################################## 906 def copy_properties_from(self, source, deep=True, actor_related=True) -> Self: 907 """ 908 Copy properties from another ``Points`` object. 909 """ 910 pr = vtki.vtkProperty() 911 try: 912 sp = source.properties 913 mp = source.mapper 914 sa = source.actor 915 except AttributeError: 916 sp = source.GetProperty() 917 mp = source.GetMapper() 918 sa = source 919 920 if deep: 921 pr.DeepCopy(sp) 922 else: 923 pr.ShallowCopy(sp) 924 self.actor.SetProperty(pr) 925 self.properties = pr 926 927 if self.actor.GetBackfaceProperty(): 928 bfpr = vtki.vtkProperty() 929 bfpr.DeepCopy(sa.GetBackfaceProperty()) 930 self.actor.SetBackfaceProperty(bfpr) 931 self.properties_backface = bfpr 932 933 if not actor_related: 934 return self 935 936 # mapper related: 937 self.mapper.SetScalarVisibility(mp.GetScalarVisibility()) 938 self.mapper.SetScalarMode(mp.GetScalarMode()) 939 self.mapper.SetScalarRange(mp.GetScalarRange()) 940 self.mapper.SetLookupTable(mp.GetLookupTable()) 941 self.mapper.SetColorMode(mp.GetColorMode()) 942 self.mapper.SetInterpolateScalarsBeforeMapping( 943 mp.GetInterpolateScalarsBeforeMapping() 944 ) 945 self.mapper.SetUseLookupTableScalarRange( 946 mp.GetUseLookupTableScalarRange() 947 ) 948 949 self.actor.SetPickable(sa.GetPickable()) 950 self.actor.SetDragable(sa.GetDragable()) 951 self.actor.SetTexture(sa.GetTexture()) 952 self.actor.SetVisibility(sa.GetVisibility()) 953 return self 954 955 def color(self, c=False, alpha=None) -> Union[np.ndarray, Self]: 956 """ 957 Set/get mesh's color. 958 If None is passed as input, will use colors from active scalars. 959 Same as `mesh.c()`. 960 """ 961 if c is False: 962 return np.array(self.properties.GetColor()) 963 if c is None: 964 self.mapper.ScalarVisibilityOn() 965 return self 966 self.mapper.ScalarVisibilityOff() 967 cc = colors.get_color(c) 968 self.properties.SetColor(cc) 969 if self.trail: 970 self.trail.properties.SetColor(cc) 971 if alpha is not None: 972 self.alpha(alpha) 973 return self 974 975 def c(self, color=False, alpha=None) -> Union[np.ndarray, Self]: 976 """ 977 Shortcut for `color()`. 978 If None is passed as input, will use colors from current active scalars. 979 """ 980 return self.color(color, alpha) 981 982 def alpha(self, opacity=None) -> Union[float, Self]: 983 """Set/get mesh's transparency. Same as `mesh.opacity()`.""" 984 if opacity is None: 985 return self.properties.GetOpacity() 986 987 self.properties.SetOpacity(opacity) 988 bfp = self.actor.GetBackfaceProperty() 989 if bfp: 990 if opacity < 1: 991 self.properties_backface = bfp 992 self.actor.SetBackfaceProperty(None) 993 else: 994 self.actor.SetBackfaceProperty(self.properties_backface) 995 return self 996 997 def lut_color_at(self, value) -> np.ndarray: 998 """ 999 Return the color and alpha in the lookup table at given value. 1000 """ 1001 lut = self.mapper.GetLookupTable() 1002 if not lut: 1003 return None 1004 rgb = [0,0,0] 1005 lut.GetColor(value, rgb) 1006 alpha = lut.GetOpacity(value) 1007 return np.array(rgb + [alpha]) 1008 1009 def opacity(self, alpha=None) -> Union[float, Self]: 1010 """Set/get mesh's transparency. Same as `mesh.alpha()`.""" 1011 return self.alpha(alpha) 1012 1013 def force_opaque(self, value=True) -> Self: 1014 """ Force the Mesh, Line or point cloud to be treated as opaque""" 1015 ## force the opaque pass, fixes picking in vtk9 1016 # but causes other bad troubles with lines.. 1017 self.actor.SetForceOpaque(value) 1018 return self 1019 1020 def force_translucent(self, value=True) -> Self: 1021 """ Force the Mesh, Line or point cloud to be treated as translucent""" 1022 self.actor.SetForceTranslucent(value) 1023 return self 1024 1025 def point_size(self, value=None) -> Union[int, Self]: 1026 """Set/get mesh's point size of vertices. Same as `mesh.ps()`""" 1027 if value is None: 1028 return self.properties.GetPointSize() 1029 # self.properties.SetRepresentationToSurface() 1030 else: 1031 self.properties.SetRepresentationToPoints() 1032 self.properties.SetPointSize(value) 1033 return self 1034 1035 def ps(self, pointsize=None) -> Union[int, Self]: 1036 """Set/get mesh's point size of vertices. Same as `mesh.point_size()`""" 1037 return self.point_size(pointsize) 1038 1039 def render_points_as_spheres(self, value=True) -> Self: 1040 """Make points look spheric or else make them look as squares.""" 1041 self.properties.SetRenderPointsAsSpheres(value) 1042 return self 1043 1044 def lighting( 1045 self, 1046 style="", 1047 ambient=None, 1048 diffuse=None, 1049 specular=None, 1050 specular_power=None, 1051 specular_color=None, 1052 metallicity=None, 1053 roughness=None, 1054 ) -> Self: 1055 """ 1056 Set the ambient, diffuse, specular and specular_power lighting constants. 1057 1058 Arguments: 1059 style : (str) 1060 preset style, options are `[metallic, plastic, shiny, glossy, ambient, off]` 1061 ambient : (float) 1062 ambient fraction of emission [0-1] 1063 diffuse : (float) 1064 emission of diffused light in fraction [0-1] 1065 specular : (float) 1066 fraction of reflected light [0-1] 1067 specular_power : (float) 1068 precision of reflection [1-100] 1069 specular_color : (color) 1070 color that is being reflected by the surface 1071 1072 <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" alt="", width=700px> 1073 1074 Examples: 1075 - [specular.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/specular.py) 1076 """ 1077 pr = self.properties 1078 1079 if style: 1080 1081 if style != "off": 1082 pr.LightingOn() 1083 1084 if style == "off": 1085 pr.SetInterpolationToFlat() 1086 pr.LightingOff() 1087 return self ############## 1088 1089 if hasattr(pr, "GetColor"): # could be Volume 1090 c = pr.GetColor() 1091 else: 1092 c = (1, 1, 0.99) 1093 mpr = self.mapper 1094 if hasattr(mpr, 'GetScalarVisibility') and mpr.GetScalarVisibility(): 1095 c = (1,1,0.99) 1096 if style=='metallic': pars = [0.1, 0.3, 1.0, 10, c] 1097 elif style=='plastic' : pars = [0.3, 0.4, 0.3, 5, c] 1098 elif style=='shiny' : pars = [0.2, 0.6, 0.8, 50, c] 1099 elif style=='glossy' : pars = [0.1, 0.7, 0.9, 90, (1,1,0.99)] 1100 elif style=='ambient' : pars = [0.8, 0.1, 0.0, 1, (1,1,1)] 1101 elif style=='default' : pars = [0.1, 1.0, 0.05, 5, c] 1102 else: 1103 vedo.logger.error("in lighting(): Available styles are") 1104 vedo.logger.error("[default, metallic, plastic, shiny, glossy, ambient, off]") 1105 raise RuntimeError() 1106 pr.SetAmbient(pars[0]) 1107 pr.SetDiffuse(pars[1]) 1108 pr.SetSpecular(pars[2]) 1109 pr.SetSpecularPower(pars[3]) 1110 if hasattr(pr, "GetColor"): 1111 pr.SetSpecularColor(pars[4]) 1112 1113 if ambient is not None: pr.SetAmbient(ambient) 1114 if diffuse is not None: pr.SetDiffuse(diffuse) 1115 if specular is not None: pr.SetSpecular(specular) 1116 if specular_power is not None: pr.SetSpecularPower(specular_power) 1117 if specular_color is not None: pr.SetSpecularColor(colors.get_color(specular_color)) 1118 if metallicity is not None: 1119 pr.SetInterpolationToPBR() 1120 pr.SetMetallic(metallicity) 1121 if roughness is not None: 1122 pr.SetInterpolationToPBR() 1123 pr.SetRoughness(roughness) 1124 1125 return self 1126 1127 def point_blurring(self, r=1, alpha=1.0, emissive=False) -> Self: 1128 """Set point blurring. 1129 Apply a gaussian convolution filter to the points. 1130 In this case the radius `r` is in absolute units of the mesh coordinates. 1131 With emissive set, the halo of point becomes light-emissive. 1132 """ 1133 self.properties.SetRepresentationToPoints() 1134 if emissive: 1135 self.mapper.SetEmissive(bool(emissive)) 1136 self.mapper.SetScaleFactor(r * 1.4142) 1137 1138 # https://kitware.github.io/vtk-examples/site/Python/Meshes/PointInterpolator/ 1139 if alpha < 1: 1140 self.mapper.SetSplatShaderCode( 1141 "//VTK::Color::Impl\n" 1142 "float dist = dot(offsetVCVSOutput.xy,offsetVCVSOutput.xy);\n" 1143 "if (dist > 1.0) {\n" 1144 " discard;\n" 1145 "} else {\n" 1146 f" float scale = ({alpha} - dist);\n" 1147 " ambientColor *= scale;\n" 1148 " diffuseColor *= scale;\n" 1149 "}\n" 1150 ) 1151 alpha = 1 1152 1153 self.mapper.Modified() 1154 self.actor.Modified() 1155 self.properties.SetOpacity(alpha) 1156 self.actor.SetMapper(self.mapper) 1157 return self 1158 1159 @property 1160 def cellcolors(self): 1161 """ 1162 Colorize each cell (face) of a mesh by passing 1163 a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. 1164 Colors levels and opacities must be in the range [0,255]. 1165 1166 A single constant color can also be passed as string or RGBA. 1167 1168 A cell array named "CellsRGBA" is automatically created. 1169 1170 Examples: 1171 - [color_mesh_cells1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells1.py) 1172 - [color_mesh_cells2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells2.py) 1173 1174  1175 """ 1176 if "CellsRGBA" not in self.celldata.keys(): 1177 lut = self.mapper.GetLookupTable() 1178 vscalars = self.dataset.GetCellData().GetScalars() 1179 if vscalars is None or lut is None: 1180 arr = np.zeros([self.ncells, 4], dtype=np.uint8) 1181 col = np.array(self.properties.GetColor()) 1182 col = np.round(col * 255).astype(np.uint8) 1183 alf = self.properties.GetOpacity() 1184 alf = np.round(alf * 255).astype(np.uint8) 1185 arr[:, (0, 1, 2)] = col 1186 arr[:, 3] = alf 1187 else: 1188 cols = lut.MapScalars(vscalars, 0, 0) 1189 arr = utils.vtk2numpy(cols) 1190 self.celldata["CellsRGBA"] = arr 1191 self.celldata.select("CellsRGBA") 1192 return self.celldata["CellsRGBA"] 1193 1194 @cellcolors.setter 1195 def cellcolors(self, value): 1196 if isinstance(value, str): 1197 c = colors.get_color(value) 1198 value = np.array([*c, 1]) * 255 1199 value = np.round(value) 1200 1201 value = np.asarray(value) 1202 n = self.ncells 1203 1204 if value.ndim == 1: 1205 value = np.repeat([value], n, axis=0) 1206 1207 if value.shape[1] == 3: 1208 z = np.zeros((n, 1), dtype=np.uint8) 1209 value = np.append(value, z + 255, axis=1) 1210 1211 assert n == value.shape[0] 1212 1213 self.celldata["CellsRGBA"] = value.astype(np.uint8) 1214 # self.mapper.SetColorModeToDirectScalars() # done in select() 1215 self.celldata.select("CellsRGBA") 1216 1217 @property 1218 def pointcolors(self): 1219 """ 1220 Colorize each point (or vertex of a mesh) by passing 1221 a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. 1222 Colors levels and opacities must be in the range [0,255]. 1223 1224 A single constant color can also be passed as string or RGBA. 1225 1226 A point array named "PointsRGBA" is automatically created. 1227 """ 1228 if "PointsRGBA" not in self.pointdata.keys(): 1229 lut = self.mapper.GetLookupTable() 1230 vscalars = self.dataset.GetPointData().GetScalars() 1231 if vscalars is None or lut is None: 1232 # create a constant array 1233 arr = np.zeros([self.npoints, 4], dtype=np.uint8) 1234 col = np.array(self.properties.GetColor()) 1235 col = np.round(col * 255).astype(np.uint8) 1236 alf = self.properties.GetOpacity() 1237 alf = np.round(alf * 255).astype(np.uint8) 1238 arr[:, (0, 1, 2)] = col 1239 arr[:, 3] = alf 1240 else: 1241 cols = lut.MapScalars(vscalars, 0, 0) 1242 arr = utils.vtk2numpy(cols) 1243 self.pointdata["PointsRGBA"] = arr 1244 self.pointdata.select("PointsRGBA") 1245 return self.pointdata["PointsRGBA"] 1246 1247 @pointcolors.setter 1248 def pointcolors(self, value): 1249 if isinstance(value, str): 1250 c = colors.get_color(value) 1251 value = np.array([*c, 1]) * 255 1252 value = np.round(value) 1253 1254 value = np.asarray(value) 1255 n = self.npoints 1256 1257 if value.ndim == 1: 1258 value = np.repeat([value], n, axis=0) 1259 1260 if value.shape[1] == 3: 1261 z = np.zeros((n, 1), dtype=np.uint8) 1262 value = np.append(value, z + 255, axis=1) 1263 1264 assert n == value.shape[0] 1265 1266 self.pointdata["PointsRGBA"] = value.astype(np.uint8) 1267 # self.mapper.SetColorModeToDirectScalars() # done in select() 1268 self.pointdata.select("PointsRGBA") 1269 1270 ##################################################################################### 1271 def cmap( 1272 self, 1273 input_cmap, 1274 input_array=None, 1275 on="", 1276 name="Scalars", 1277 vmin=None, 1278 vmax=None, 1279 n_colors=256, 1280 alpha=1.0, 1281 logscale=False, 1282 ) -> Self: 1283 """ 1284 Set individual point/cell colors by providing a list of scalar values and a color map. 1285 1286 Arguments: 1287 input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap) 1288 color map scheme to transform a real number into a color. 1289 input_array : (str, list, vtkArray) 1290 can be the string name of an existing array, a new array or a `vtkArray`. 1291 on : (str) 1292 either 'points' or 'cells' or blank (automatic). 1293 Apply the color map to data which is defined on either points or cells. 1294 name : (str) 1295 give a name to the provided array (if input_array is an array) 1296 vmin : (float) 1297 clip scalars to this minimum value 1298 vmax : (float) 1299 clip scalars to this maximum value 1300 n_colors : (int) 1301 number of distinct colors to be used in colormap table. 1302 alpha : (float, list) 1303 Mesh transparency. Can be a `list` of values one for each vertex. 1304 logscale : (bool) 1305 Use logscale 1306 1307 Examples: 1308 - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py) 1309 - [mesh_alphas.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_alphas.py) 1310 - [mesh_custom.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_custom.py) 1311 - (and many others) 1312 1313  1314 """ 1315 self._cmap_name = input_cmap 1316 1317 if on == "": 1318 try: 1319 on = self.mapper.GetScalarModeAsString().replace("Use", "") 1320 if on not in ["PointData", "CellData"]: # can be "Default" 1321 on = "points" 1322 self.mapper.SetScalarModeToUsePointData() 1323 except AttributeError: 1324 on = "points" 1325 elif on == "Default": 1326 on = "points" 1327 self.mapper.SetScalarModeToUsePointData() 1328 1329 if input_array is None: 1330 if not self.pointdata.keys() and self.celldata.keys(): 1331 on = "cells" 1332 if not self.dataset.GetCellData().GetScalars(): 1333 input_array = 0 # pick the first at hand 1334 1335 if "point" in on.lower(): 1336 data = self.dataset.GetPointData() 1337 n = self.dataset.GetNumberOfPoints() 1338 elif "cell" in on.lower(): 1339 data = self.dataset.GetCellData() 1340 n = self.dataset.GetNumberOfCells() 1341 else: 1342 vedo.logger.error( 1343 f"Must specify in cmap(on=...) to either 'cells' or 'points', not {on}") 1344 raise RuntimeError() 1345 1346 if input_array is None: # if None try to fetch the active scalars 1347 arr = data.GetScalars() 1348 if not arr: 1349 vedo.logger.error(f"in cmap(), cannot find any {on} active array ...skip coloring.") 1350 return self 1351 1352 if not arr.GetName(): # sometimes arrays dont have a name.. 1353 arr.SetName(name) 1354 1355 elif isinstance(input_array, str): # if a string is passed 1356 arr = data.GetArray(input_array) 1357 if not arr: 1358 vedo.logger.error(f"in cmap(), cannot find {on} array {input_array} ...skip coloring.") 1359 return self 1360 1361 elif isinstance(input_array, int): # if an int is passed 1362 if input_array < data.GetNumberOfArrays(): 1363 arr = data.GetArray(input_array) 1364 else: 1365 vedo.logger.error(f"in cmap(), cannot find {on} array at {input_array} ...skip coloring.") 1366 return self 1367 1368 elif utils.is_sequence(input_array): # if a numpy array is passed 1369 npts = len(input_array) 1370 if npts != n: 1371 vedo.logger.error(f"in cmap(), nr. of input {on} scalars {npts} != {n} ...skip coloring.") 1372 return self 1373 arr = utils.numpy2vtk(input_array, name=name, dtype=float) 1374 data.AddArray(arr) 1375 data.Modified() 1376 1377 elif isinstance(input_array, vtki.vtkArray): # if a vtkArray is passed 1378 arr = input_array 1379 data.AddArray(arr) 1380 data.Modified() 1381 1382 else: 1383 vedo.logger.error(f"in cmap(), cannot understand input type {type(input_array)}") 1384 raise RuntimeError() 1385 1386 # Now we have array "arr" 1387 array_name = arr.GetName() 1388 1389 if arr.GetNumberOfComponents() == 1: 1390 if vmin is None: 1391 vmin = arr.GetRange()[0] 1392 if vmax is None: 1393 vmax = arr.GetRange()[1] 1394 else: 1395 if vmin is None or vmax is None: 1396 vn = utils.mag(utils.vtk2numpy(arr)) 1397 if vmin is None: 1398 vmin = vn.min() 1399 if vmax is None: 1400 vmax = vn.max() 1401 1402 # interpolate alphas if they are not constant 1403 if not utils.is_sequence(alpha): 1404 alpha = [alpha] * n_colors 1405 else: 1406 v = np.linspace(0, 1, n_colors, endpoint=True) 1407 xp = np.linspace(0, 1, len(alpha), endpoint=True) 1408 alpha = np.interp(v, xp, alpha) 1409 1410 ########################### build the look-up table 1411 if isinstance(input_cmap, vtki.vtkLookupTable): # vtkLookupTable 1412 lut = input_cmap 1413 1414 elif utils.is_sequence(input_cmap): # manual sequence of colors 1415 lut = vtki.vtkLookupTable() 1416 if logscale: 1417 lut.SetScaleToLog10() 1418 lut.SetRange(vmin, vmax) 1419 ncols = len(input_cmap) 1420 lut.SetNumberOfTableValues(ncols) 1421 1422 for i, c in enumerate(input_cmap): 1423 r, g, b = colors.get_color(c) 1424 lut.SetTableValue(i, r, g, b, alpha[i]) 1425 lut.Build() 1426 1427 else: 1428 # assume string cmap name OR matplotlib.colors.LinearSegmentedColormap 1429 lut = vtki.vtkLookupTable() 1430 if logscale: 1431 lut.SetScaleToLog10() 1432 lut.SetVectorModeToMagnitude() 1433 lut.SetRange(vmin, vmax) 1434 lut.SetNumberOfTableValues(n_colors) 1435 mycols = colors.color_map(range(n_colors), input_cmap, 0, n_colors) 1436 for i, c in enumerate(mycols): 1437 r, g, b = c 1438 lut.SetTableValue(i, r, g, b, alpha[i]) 1439 lut.Build() 1440 1441 # TEST NEW WAY 1442 self.mapper.SetLookupTable(lut) 1443 self.mapper.ScalarVisibilityOn() 1444 self.mapper.SetColorModeToMapScalars() 1445 self.mapper.SetScalarRange(lut.GetRange()) 1446 if "point" in on.lower(): 1447 self.pointdata.select(array_name) 1448 else: 1449 self.celldata.select(array_name) 1450 return self 1451 1452 # # TEST this is the old way: 1453 # # arr.SetLookupTable(lut) # wrong! causes weird instabilities with LUT 1454 # # if data.GetScalars(): 1455 # # data.GetScalars().SetLookupTable(lut) 1456 # # data.GetScalars().Modified() 1457 1458 # data.SetActiveScalars(array_name) 1459 # # data.SetScalars(arr) # wrong! it deletes array in position 0, never use SetScalars 1460 # # data.SetActiveAttribute(array_name, 0) # boh! 1461 1462 # self.mapper.SetLookupTable(lut) 1463 # self.mapper.SetColorModeToMapScalars() # so we dont need to convert uint8 scalars 1464 1465 # self.mapper.ScalarVisibilityOn() 1466 # self.mapper.SetScalarRange(lut.GetRange()) 1467 1468 # if on.startswith("point"): 1469 # self.mapper.SetScalarModeToUsePointData() 1470 # else: 1471 # self.mapper.SetScalarModeToUseCellData() 1472 # if hasattr(self.mapper, "SetArrayName"): 1473 # self.mapper.SetArrayName(array_name) 1474 # return self 1475 1476 def add_trail(self, offset=(0, 0, 0), n=50, c=None, alpha=1.0, lw=2) -> Self: 1477 """ 1478 Add a trailing line to mesh. 1479 This new mesh is accessible through `mesh.trail`. 1480 1481 Arguments: 1482 offset : (float) 1483 set an offset vector from the object center. 1484 n : (int) 1485 number of segments 1486 lw : (float) 1487 line width of the trail 1488 1489 Examples: 1490 - [trail.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/trail.py) 1491 1492  1493 1494 - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py) 1495 - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py) 1496 """ 1497 if self.trail is None: 1498 pos = self.pos() 1499 self.trail_offset = np.asarray(offset) 1500 self.trail_points = [pos] * n 1501 1502 if c is None: 1503 col = self.properties.GetColor() 1504 else: 1505 col = colors.get_color(c) 1506 1507 tline = vedo.shapes.Line(pos, pos, res=n, c=col, alpha=alpha, lw=lw) 1508 self.trail = tline # holds the Line 1509 self.trail.initilized = False # so the first update will be a reset 1510 return self 1511 1512 def update_trail(self) -> Self: 1513 """ 1514 Update the trailing line of a moving object. 1515 """ 1516 currentpos = self.pos() 1517 if not self.trail.initilized: 1518 self.trail_points = [currentpos] * self.trail.npoints 1519 self.trail.initilized = True 1520 return self 1521 self.trail_points.append(currentpos) # cycle 1522 self.trail_points.pop(0) 1523 1524 data = np.array(self.trail_points) + self.trail_offset 1525 tpoly = self.trail.dataset 1526 tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32)) 1527 return self 1528 1529 def _compute_shadow(self, plane, point, direction): 1530 shad = self.clone() 1531 shad.name = "Shadow" 1532 1533 tarr = shad.dataset.GetPointData().GetTCoords() 1534 if tarr: # remove any texture coords 1535 tname = tarr.GetName() 1536 shad.pointdata.remove(tname) 1537 shad.dataset.GetPointData().SetTCoords(None) 1538 shad.actor.SetTexture(None) 1539 1540 pts = shad.vertices 1541 if plane == "x": 1542 # shad = shad.project_on_plane('x') 1543 # instead do it manually so in case of alpha<1 1544 # we dont see glitches due to coplanar points 1545 # we leave a small tolerance of 0.1% in thickness 1546 x0, x1 = self.xbounds() 1547 pts[:, 0] = (pts[:, 0] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[0] 1548 shad.vertices = pts 1549 shad.x(point) 1550 elif plane == "y": 1551 x0, x1 = self.ybounds() 1552 pts[:, 1] = (pts[:, 1] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[1] 1553 shad.vertices = pts 1554 shad.y(point) 1555 elif plane == "z": 1556 x0, x1 = self.zbounds() 1557 pts[:, 2] = (pts[:, 2] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[2] 1558 shad.vertices = pts 1559 shad.z(point) 1560 else: 1561 shad = shad.project_on_plane(plane, point, direction) 1562 return shad 1563 1564 def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0) -> Self: 1565 """ 1566 Generate a shadow out of an `Mesh` on one of the three Cartesian planes. 1567 The output is a new `Mesh` representing the shadow. 1568 This new mesh is accessible through `mesh.shadow`. 1569 By default the shadow mesh is placed on the bottom wall of the bounding box. 1570 1571 See also `pointcloud.project_on_plane()`. 1572 1573 Arguments: 1574 plane : (str, Plane) 1575 if plane is `str`, plane can be one of `['x', 'y', 'z']`, 1576 represents x-plane, y-plane and z-plane, respectively. 1577 Otherwise, plane should be an instance of `vedo.shapes.Plane` 1578 point : (float, array) 1579 if plane is `str`, point should be a float represents the intercept. 1580 Otherwise, point is the camera point of perspective projection 1581 direction : (list) 1582 direction of oblique projection 1583 culling : (int) 1584 choose between front [1] or backface [-1] culling or None. 1585 1586 Examples: 1587 - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py) 1588 - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py) 1589 - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py) 1590 1591  1592 """ 1593 shad = self._compute_shadow(plane, point, direction) 1594 shad.c(c).alpha(alpha) 1595 1596 try: 1597 # Points dont have these methods 1598 shad.flat() 1599 if culling in (1, True): 1600 shad.frontface_culling() 1601 elif culling == -1: 1602 shad.backface_culling() 1603 except AttributeError: 1604 pass 1605 1606 shad.properties.LightingOff() 1607 shad.actor.SetPickable(False) 1608 shad.actor.SetUseBounds(True) 1609 1610 if shad not in self.shadows: 1611 self.shadows.append(shad) 1612 shad.info = dict(plane=plane, point=point, direction=direction) 1613 # shad.metadata["plane"] = plane 1614 # shad.metadata["point"] = point 1615 # print("AAAA", direction, plane, point) 1616 # if direction is None: 1617 # direction = [0,0,0] 1618 # shad.metadata["direction"] = direction 1619 return self 1620 1621 def update_shadows(self) -> Self: 1622 """Update the shadows of a moving object.""" 1623 for sha in self.shadows: 1624 plane = sha.info["plane"] 1625 point = sha.info["point"] 1626 direction = sha.info["direction"] 1627 # print("update_shadows direction", direction,plane,point ) 1628 # plane = sha.metadata["plane"] 1629 # point = sha.metadata["point"] 1630 # direction = sha.metadata["direction"] 1631 # if direction[0]==0 and direction[1]==0 and direction[2]==0: 1632 # direction = None 1633 # print("BBBB", sha.metadata["direction"], 1634 # sha.metadata["plane"], sha.metadata["point"]) 1635 new_sha = self._compute_shadow(plane, point, direction) 1636 sha._update(new_sha.dataset) 1637 if self.trail: 1638 self.trail.update_shadows() 1639 return self 1640 1641 def labels( 1642 self, 1643 content=None, 1644 on="points", 1645 scale=None, 1646 xrot=0.0, 1647 yrot=0.0, 1648 zrot=0.0, 1649 ratio=1, 1650 precision=None, 1651 italic=False, 1652 font="", 1653 justify="", 1654 c="black", 1655 alpha=1.0, 1656 ) -> Union["vedo.Mesh", None]: 1657 """ 1658 Generate value or ID labels for mesh cells or points. 1659 For large nr. of labels use `font="VTK"` which is much faster. 1660 1661 See also: 1662 `labels2d()`, `flagpole()`, `caption()` and `legend()`. 1663 1664 Arguments: 1665 content : (list,int,str) 1666 either 'id', 'cellid', array name or array number. 1667 A array can also be passed (must match the nr. of points or cells). 1668 on : (str) 1669 generate labels for "cells" instead of "points" 1670 scale : (float) 1671 absolute size of labels, if left as None it is automatic 1672 zrot : (float) 1673 local rotation angle of label in degrees 1674 ratio : (int) 1675 skipping ratio, to reduce nr of labels for large meshes 1676 precision : (int) 1677 numeric precision of labels 1678 1679 ```python 1680 from vedo import * 1681 s = Sphere(res=10).linewidth(1).c("orange").compute_normals() 1682 point_ids = s.labels('id', on="points").c('green') 1683 cell_ids = s.labels('id', on="cells" ).c('black') 1684 show(s, point_ids, cell_ids) 1685 ``` 1686  1687 1688 Examples: 1689 - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py) 1690 1691  1692 """ 1693 1694 cells = False 1695 if "cell" in on or "face" in on: 1696 cells = True 1697 justify = "centered" if justify == "" else justify 1698 1699 if isinstance(content, str): 1700 if content in ("pointid", "pointsid"): 1701 cells = False 1702 content = "id" 1703 justify = "bottom-left" if justify == "" else justify 1704 if content in ("cellid", "cellsid"): 1705 cells = True 1706 content = "id" 1707 justify = "centered" if justify == "" else justify 1708 1709 try: 1710 if cells: 1711 ns = np.sqrt(self.ncells) 1712 elems = self.cell_centers().points 1713 norms = self.cell_normals 1714 justify = "centered" if justify == "" else justify 1715 else: 1716 ns = np.sqrt(self.npoints) 1717 elems = self.vertices 1718 norms = self.vertex_normals 1719 except AttributeError: 1720 norms = [] 1721 1722 if not justify: 1723 justify = "bottom-left" 1724 1725 hasnorms = False 1726 if len(norms) > 0: 1727 hasnorms = True 1728 1729 if scale is None: 1730 if not ns: 1731 ns = 100 1732 scale = self.diagonal_size() / ns / 10 1733 1734 arr = None 1735 mode = 0 1736 if content is None: 1737 mode = 0 1738 if cells: 1739 if self.dataset.GetCellData().GetScalars(): 1740 name = self.dataset.GetCellData().GetScalars().GetName() 1741 arr = self.celldata[name] 1742 else: 1743 if self.dataset.GetPointData().GetScalars(): 1744 name = self.dataset.GetPointData().GetScalars().GetName() 1745 arr = self.pointdata[name] 1746 elif isinstance(content, (str, int)): 1747 if content == "id": 1748 mode = 1 1749 elif cells: 1750 mode = 0 1751 arr = self.celldata[content] 1752 else: 1753 mode = 0 1754 arr = self.pointdata[content] 1755 elif utils.is_sequence(content): 1756 mode = 0 1757 arr = content 1758 1759 if arr is None and mode == 0: 1760 vedo.logger.error("in labels(), array not found in point or cell data") 1761 return None 1762 1763 ratio = int(ratio+0.5) 1764 tapp = vtki.new("AppendPolyData") 1765 has_inputs = False 1766 1767 for i, e in enumerate(elems): 1768 if i % ratio: 1769 continue 1770 1771 if mode == 1: 1772 txt_lab = str(i) 1773 else: 1774 if precision: 1775 txt_lab = utils.precision(arr[i], precision) 1776 else: 1777 txt_lab = str(arr[i]) 1778 1779 if not txt_lab: 1780 continue 1781 1782 if font == "VTK": 1783 tx = vtki.new("VectorText") 1784 tx.SetText(txt_lab) 1785 tx.Update() 1786 tx_poly = tx.GetOutput() 1787 else: 1788 tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify).dataset 1789 1790 if tx_poly.GetNumberOfPoints() == 0: 1791 continue ###################### 1792 1793 T = vtki.vtkTransform() 1794 T.PostMultiply() 1795 if italic: 1796 T.Concatenate([1, 0.2, 0, 0, 1797 0, 1 , 0, 0, 1798 0, 0 , 1, 0, 1799 0, 0 , 0, 1]) 1800 if hasnorms: 1801 ni = norms[i] 1802 if cells and font=="VTK": # center-justify 1803 bb = tx_poly.GetBounds() 1804 dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2 1805 T.Translate(-dx, -dy, 0) 1806 if xrot: T.RotateX(xrot) 1807 if yrot: T.RotateY(yrot) 1808 if zrot: T.RotateZ(zrot) 1809 crossvec = np.cross([0, 0, 1], ni) 1810 angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3 1811 T.RotateWXYZ(float(angle), crossvec.tolist()) 1812 T.Translate(ni / 100) 1813 else: 1814 if xrot: T.RotateX(xrot) 1815 if yrot: T.RotateY(yrot) 1816 if zrot: T.RotateZ(zrot) 1817 T.Scale(scale, scale, scale) 1818 T.Translate(e) 1819 tf = vtki.new("TransformPolyDataFilter") 1820 tf.SetInputData(tx_poly) 1821 tf.SetTransform(T) 1822 tf.Update() 1823 tapp.AddInputData(tf.GetOutput()) 1824 has_inputs = True 1825 1826 if has_inputs: 1827 tapp.Update() 1828 lpoly = tapp.GetOutput() 1829 else: 1830 lpoly = vtki.vtkPolyData() 1831 ids = vedo.Mesh(lpoly, c=c, alpha=alpha) 1832 ids.properties.LightingOff() 1833 ids.actor.PickableOff() 1834 ids.actor.SetUseBounds(False) 1835 ids.name = "Labels" 1836 return ids 1837 1838 def labels2d( 1839 self, 1840 content="id", 1841 on="points", 1842 scale=1.0, 1843 precision=4, 1844 font="Calco", 1845 justify="bottom-left", 1846 angle=0.0, 1847 frame=False, 1848 c="black", 1849 bc=None, 1850 alpha=1.0, 1851 ) -> Union["Actor2D", None]: 1852 """ 1853 Generate value or ID bi-dimensional labels for mesh cells or points. 1854 1855 See also: `labels()`, `flagpole()`, `caption()` and `legend()`. 1856 1857 Arguments: 1858 content : (str) 1859 either 'id', 'cellid', or array name 1860 on : (str) 1861 generate labels for "cells" instead of "points" (the default) 1862 scale : (float) 1863 size scaling of labels 1864 precision : (int) 1865 precision of numeric labels 1866 angle : (float) 1867 local rotation angle of label in degrees 1868 frame : (bool) 1869 draw a frame around the label 1870 bc : (str) 1871 background color of the label 1872 1873 ```python 1874 from vedo import Sphere, show 1875 sph = Sphere(quads=True, res=4).compute_normals().wireframe() 1876 sph.celldata["zvals"] = sph.cell_centers().coordinates[:,2] 1877 l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9') 1878 show(sph, l2d, axes=1).close() 1879 ``` 1880  1881 """ 1882 cells = False 1883 if "cell" in on: 1884 cells = True 1885 1886 if isinstance(content, str): 1887 if content in ("id", "pointid", "pointsid"): 1888 cells = False 1889 content = "id" 1890 if content in ("cellid", "cellsid"): 1891 cells = True 1892 content = "id" 1893 1894 if cells: 1895 if content != "id" and content not in self.celldata.keys(): 1896 vedo.logger.error(f"In labels2d: cell array {content} does not exist.") 1897 return None 1898 arr = self.dataset.GetCellData().GetScalars() 1899 poly = self.cell_centers().dataset 1900 poly.GetPointData().SetScalars(arr) 1901 else: 1902 arr = self.dataset.GetPointData().GetScalars() 1903 poly = self.dataset 1904 if content != "id" and content not in self.pointdata.keys(): 1905 vedo.logger.error(f"In labels2d: point array {content} does not exist.") 1906 return None 1907 1908 mp = vtki.new("LabeledDataMapper") 1909 1910 if content == "id": 1911 mp.SetLabelModeToLabelIds() 1912 else: 1913 mp.SetLabelModeToLabelScalars() 1914 if precision is not None: 1915 dtype = arr.GetDataType() 1916 if dtype in (vtki.VTK_FLOAT, vtki.VTK_DOUBLE): 1917 mp.SetLabelFormat(f"%-#.{precision}g") 1918 1919 pr = mp.GetLabelTextProperty() 1920 c = colors.get_color(c) 1921 pr.SetColor(c) 1922 pr.SetOpacity(alpha) 1923 pr.SetFrame(frame) 1924 pr.SetFrameColor(c) 1925 pr.SetItalic(False) 1926 pr.BoldOff() 1927 pr.ShadowOff() 1928 pr.UseTightBoundingBoxOn() 1929 pr.SetOrientation(angle) 1930 pr.SetFontFamily(vtki.VTK_FONT_FILE) 1931 fl = utils.get_font_path(font) 1932 pr.SetFontFile(fl) 1933 pr.SetFontSize(int(20 * scale)) 1934 1935 if "cent" in justify or "mid" in justify: 1936 pr.SetJustificationToCentered() 1937 elif "rig" in justify: 1938 pr.SetJustificationToRight() 1939 elif "left" in justify: 1940 pr.SetJustificationToLeft() 1941 # ------ 1942 if "top" in justify: 1943 pr.SetVerticalJustificationToTop() 1944 else: 1945 pr.SetVerticalJustificationToBottom() 1946 1947 if bc is not None: 1948 bc = colors.get_color(bc) 1949 pr.SetBackgroundColor(bc) 1950 pr.SetBackgroundOpacity(alpha) 1951 1952 mp.SetInputData(poly) 1953 a2d = Actor2D() 1954 a2d.PickableOff() 1955 a2d.SetMapper(mp) 1956 return a2d 1957 1958 def legend(self, txt) -> Self: 1959 """Book a legend text.""" 1960 self.info["legend"] = txt 1961 # self.metadata["legend"] = txt 1962 return self 1963 1964 def flagpole( 1965 self, 1966 txt=None, 1967 point=None, 1968 offset=None, 1969 s=None, 1970 font="Calco", 1971 rounded=True, 1972 c=None, 1973 alpha=1.0, 1974 lw=2, 1975 italic=0.0, 1976 padding=0.1, 1977 ) -> Union["vedo.Mesh", None]: 1978 """ 1979 Generate a flag pole style element to describe an object. 1980 Returns a `Mesh` object. 1981 1982 Use flagpole.follow_camera() to make it face the camera in the scene. 1983 1984 Consider using `settings.use_parallel_projection = True` 1985 to avoid perspective distortions. 1986 1987 See also `flagpost()`. 1988 1989 Arguments: 1990 txt : (str) 1991 Text to display. The default is the filename or the object name. 1992 point : (list) 1993 position of the flagpole pointer. 1994 offset : (list) 1995 text offset wrt the application point. 1996 s : (float) 1997 size of the flagpole. 1998 font : (str) 1999 font face. Check [available fonts here](https://vedo.embl.es/fonts). 2000 rounded : (bool) 2001 draw a rounded or squared box around the text. 2002 c : (list) 2003 text and box color. 2004 alpha : (float) 2005 opacity of text and box. 2006 lw : (float) 2007 line with of box frame. 2008 italic : (float) 2009 italicness of text. 2010 2011 Examples: 2012 - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py) 2013 2014  2015 2016 - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py) 2017 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 2018 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 2019 """ 2020 objs = [] 2021 2022 if txt is None: 2023 if self.filename: 2024 txt = self.filename.split("/")[-1] 2025 elif self.name: 2026 txt = self.name 2027 else: 2028 return None 2029 2030 x0, x1, y0, y1, z0, z1 = self.bounds() 2031 d = self.diagonal_size() 2032 if point is None: 2033 if d: 2034 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2035 # point = self.closest_point([x1, y0, z1]) 2036 else: # it's a Point 2037 point = self.transform.position 2038 2039 pt = utils.make3d(point) 2040 2041 if offset is None: 2042 offset = [(x1 - x0) / 1.75, (y1 - y0) / 5, 0] 2043 offset = utils.make3d(offset) 2044 2045 if s is None: 2046 s = d / 20 2047 2048 sph = None 2049 if d and (z1 - z0) / d > 0.1: 2050 sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6) 2051 2052 if c is None: 2053 c = np.array(self.color()) / 1.4 2054 2055 lab = vedo.shapes.Text3D( 2056 txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center" 2057 ) 2058 objs.append(lab) 2059 2060 if d and not sph: 2061 sph = vedo.shapes.Circle(pt, r=s / 3, res=16) 2062 objs.append(sph) 2063 2064 x0, x1, y0, y1, z0, z1 = lab.bounds() 2065 aline = [(x0,y0,z0), (x1,y0,z0), (x1,y1,z0), (x0,y1,z0)] 2066 if rounded: 2067 box = vedo.shapes.KSpline(aline, closed=True) 2068 else: 2069 box = vedo.shapes.Line(aline, closed=True) 2070 2071 cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2] 2072 2073 # box.actor.SetOrigin(cnt) 2074 box.scale([1 + padding, 1 + 2 * padding, 1], origin=cnt) 2075 objs.append(box) 2076 2077 x0, x1, y0, y1, z0, z1 = box.bounds() 2078 if x0 < pt[0] < x1: 2079 c0 = box.closest_point(pt) 2080 c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]] 2081 elif (pt[0] - x0) < (x1 - pt[0]): 2082 c0 = [x0, (y0 + y1) / 2, pt[2]] 2083 c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]] 2084 else: 2085 c0 = [x1, (y0 + y1) / 2, pt[2]] 2086 c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]] 2087 2088 con = vedo.shapes.Line([c0, c1, pt]) 2089 objs.append(con) 2090 2091 mobjs = vedo.merge(objs).c(c).alpha(alpha) 2092 mobjs.name = "FlagPole" 2093 mobjs.bc("tomato").pickable(False) 2094 mobjs.properties.LightingOff() 2095 mobjs.properties.SetLineWidth(lw) 2096 mobjs.actor.UseBoundsOff() 2097 mobjs.actor.SetPosition([0,0,0]) 2098 mobjs.actor.SetOrigin(pt) 2099 return mobjs 2100 2101 # mobjs = vedo.Assembly(objs)#.c(c).alpha(alpha) 2102 # mobjs.name = "FlagPole" 2103 # # mobjs.bc("tomato").pickable(False) 2104 # # mobjs.properties.LightingOff() 2105 # # mobjs.properties.SetLineWidth(lw) 2106 # # mobjs.actor.UseBoundsOff() 2107 # # mobjs.actor.SetPosition([0,0,0]) 2108 # # mobjs.actor.SetOrigin(pt) 2109 # # print(pt) 2110 # return mobjs 2111 2112 def flagpost( 2113 self, 2114 txt=None, 2115 point=None, 2116 offset=None, 2117 s=1.0, 2118 c="k9", 2119 bc="k1", 2120 alpha=1, 2121 lw=0, 2122 font="Calco", 2123 justify="center-left", 2124 vspacing=1.0, 2125 ) -> Union["vedo.addons.Flagpost", None]: 2126 """ 2127 Generate a flag post style element to describe an object. 2128 2129 Arguments: 2130 txt : (str) 2131 Text to display. The default is the filename or the object name. 2132 point : (list) 2133 position of the flag anchor point. The default is None. 2134 offset : (list) 2135 a 3D displacement or offset. The default is None. 2136 s : (float) 2137 size of the text to be shown 2138 c : (list) 2139 color of text and line 2140 bc : (list) 2141 color of the flag background 2142 alpha : (float) 2143 opacity of text and box. 2144 lw : (int) 2145 line with of box frame. The default is 0. 2146 font : (str) 2147 font name. Use a monospace font for better rendering. The default is "Calco". 2148 Type `vedo -r fonts` for a font demo. 2149 Check [available fonts here](https://vedo.embl.es/fonts). 2150 justify : (str) 2151 internal text justification. The default is "center-left". 2152 vspacing : (float) 2153 vertical spacing between lines. 2154 2155 Examples: 2156 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py) 2157 2158  2159 """ 2160 if txt is None: 2161 if self.filename: 2162 txt = self.filename.split("/")[-1] 2163 elif self.name: 2164 txt = self.name 2165 else: 2166 return None 2167 2168 x0, x1, y0, y1, z0, z1 = self.bounds() 2169 d = self.diagonal_size() 2170 if point is None: 2171 if d: 2172 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2173 else: # it's a Point 2174 point = self.transform.position 2175 2176 point = utils.make3d(point) 2177 2178 if offset is None: 2179 offset = [0, 0, (z1 - z0) / 2] 2180 offset = utils.make3d(offset) 2181 2182 fpost = vedo.addons.Flagpost( 2183 txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing 2184 ) 2185 self._caption = fpost 2186 return fpost 2187 2188 def caption( 2189 self, 2190 txt=None, 2191 point=None, 2192 size=(0.30, 0.15), 2193 padding=5, 2194 font="Calco", 2195 justify="center-right", 2196 vspacing=1.0, 2197 c=None, 2198 alpha=1.0, 2199 lw=1, 2200 ontop=True, 2201 ) -> Union["vtki.vtkCaptionActor2D", None]: 2202 """ 2203 Create a 2D caption to an object which follows the camera movements. 2204 Latex is not supported. Returns the same input object for concatenation. 2205 2206 See also `flagpole()`, `flagpost()`, `labels()` and `legend()` 2207 with similar functionality. 2208 2209 Arguments: 2210 txt : (str) 2211 text to be rendered. The default is the file name. 2212 point : (list) 2213 anchoring point. The default is None. 2214 size : (list) 2215 (width, height) of the caption box. The default is (0.30, 0.15). 2216 padding : (float) 2217 padding space of the caption box in pixels. The default is 5. 2218 font : (str) 2219 font name. Use a monospace font for better rendering. The default is "VictorMono". 2220 Type `vedo -r fonts` for a font demo. 2221 Check [available fonts here](https://vedo.embl.es/fonts). 2222 justify : (str) 2223 internal text justification. The default is "center-right". 2224 vspacing : (float) 2225 vertical spacing between lines. The default is 1. 2226 c : (str) 2227 text and box color. The default is 'lb'. 2228 alpha : (float) 2229 text and box transparency. The default is 1. 2230 lw : (int) 2231 line width in pixels. The default is 1. 2232 ontop : (bool) 2233 keep the 2d caption always on top. The default is True. 2234 2235 Examples: 2236 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 2237 2238  2239 2240 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 2241 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 2242 """ 2243 if txt is None: 2244 if self.filename: 2245 txt = self.filename.split("/")[-1] 2246 elif self.name: 2247 txt = self.name 2248 2249 if not txt: # disable it 2250 self._caption = None 2251 return None 2252 2253 for r in vedo.shapes._reps: 2254 txt = txt.replace(r[0], r[1]) 2255 2256 if c is None: 2257 c = np.array(self.properties.GetColor()) / 2 2258 else: 2259 c = colors.get_color(c) 2260 2261 if point is None: 2262 x0, x1, y0, y1, _, z1 = self.dataset.GetBounds() 2263 pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1] 2264 point = self.closest_point(pt) 2265 2266 capt = vtki.vtkCaptionActor2D() 2267 capt.SetAttachmentPoint(point) 2268 capt.SetBorder(True) 2269 capt.SetLeader(True) 2270 sph = vtki.new("SphereSource") 2271 sph.Update() 2272 capt.SetLeaderGlyphData(sph.GetOutput()) 2273 capt.SetMaximumLeaderGlyphSize(5) 2274 capt.SetPadding(int(padding)) 2275 capt.SetCaption(txt) 2276 capt.SetWidth(size[0]) 2277 capt.SetHeight(size[1]) 2278 capt.SetThreeDimensionalLeader(not ontop) 2279 2280 pra = capt.GetProperty() 2281 pra.SetColor(c) 2282 pra.SetOpacity(alpha) 2283 pra.SetLineWidth(lw) 2284 2285 pr = capt.GetCaptionTextProperty() 2286 pr.SetFontFamily(vtki.VTK_FONT_FILE) 2287 fl = utils.get_font_path(font) 2288 pr.SetFontFile(fl) 2289 pr.ShadowOff() 2290 pr.BoldOff() 2291 pr.FrameOff() 2292 pr.SetColor(c) 2293 pr.SetOpacity(alpha) 2294 pr.SetJustificationToLeft() 2295 if "top" in justify: 2296 pr.SetVerticalJustificationToTop() 2297 if "bottom" in justify: 2298 pr.SetVerticalJustificationToBottom() 2299 if "cent" in justify: 2300 pr.SetVerticalJustificationToCentered() 2301 pr.SetJustificationToCentered() 2302 if "left" in justify: 2303 pr.SetJustificationToLeft() 2304 if "right" in justify: 2305 pr.SetJustificationToRight() 2306 pr.SetLineSpacing(vspacing) 2307 return capt
Class to manage the visual aspects of a Points
object.
833 def clone2d(self, size=None, offset=(), scale=None): 834 """ 835 Turn a 3D `Points` or `Mesh` into a flat 2D actor. 836 Returns a `Actor2D`. 837 838 Arguments: 839 size : (float) 840 size as scaling factor for the 2D actor 841 offset : (list) 842 2D (x, y) position of the actor in the range [-1, 1] 843 scale : (float) 844 Deprecated. Use `size` instead. 845 846 Examples: 847 - [clone2d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/clone2d.py) 848 849  850 """ 851 # assembly.Assembly.clone2d() superseeds this method 852 if scale is not None: 853 vedo.logger.warning("clone2d(): use keyword size not scale") 854 size = scale 855 856 if size is None: 857 # work out a reasonable scale 858 msiz = self.diagonal_size() 859 if vedo.plotter_instance and vedo.plotter_instance.window: 860 sz = vedo.plotter_instance.window.GetSize() 861 dsiz = utils.mag(sz) 862 size = dsiz / msiz / 10 863 else: 864 size = 350 / msiz 865 866 tp = vtki.new("TransformPolyDataFilter") 867 transform = vtki.vtkTransform() 868 transform.Scale(size, size, size) 869 if len(offset) == 0: 870 offset = self.pos() 871 transform.Translate(-utils.make3d(offset)) 872 tp.SetTransform(transform) 873 tp.SetInputData(self.dataset) 874 tp.Update() 875 poly = tp.GetOutput() 876 877 cm = self.mapper.GetColorMode() 878 lut = self.mapper.GetLookupTable() 879 sv = self.mapper.GetScalarVisibility() 880 use_lut = self.mapper.GetUseLookupTableScalarRange() 881 vrange = self.mapper.GetScalarRange() 882 sm = self.mapper.GetScalarMode() 883 884 act2d = Actor2D() 885 act2d.dataset = poly 886 mapper2d = vtki.new("PolyDataMapper2D") 887 mapper2d.SetInputData(poly) 888 mapper2d.SetColorMode(cm) 889 mapper2d.SetLookupTable(lut) 890 mapper2d.SetScalarVisibility(sv) 891 mapper2d.SetUseLookupTableScalarRange(use_lut) 892 mapper2d.SetScalarRange(vrange) 893 mapper2d.SetScalarMode(sm) 894 act2d.mapper = mapper2d 895 896 act2d.GetPositionCoordinate().SetCoordinateSystem(4) 897 act2d.properties.SetColor(self.color()) 898 act2d.properties.SetOpacity(self.alpha()) 899 act2d.properties.SetLineWidth(self.properties.GetLineWidth()) 900 act2d.properties.SetPointSize(self.properties.GetPointSize()) 901 act2d.properties.SetDisplayLocation(0) # 0 = back, 1 = front 902 act2d.PickableOff() 903 return act2d
Turn a 3D Points
or Mesh
into a flat 2D actor.
Returns a Actor2D
.
Arguments:
- size : (float) size as scaling factor for the 2D actor
- offset : (list) 2D (x, y) position of the actor in the range [-1, 1]
- scale : (float)
Deprecated. Use
size
instead.
Examples:
906 def copy_properties_from(self, source, deep=True, actor_related=True) -> Self: 907 """ 908 Copy properties from another ``Points`` object. 909 """ 910 pr = vtki.vtkProperty() 911 try: 912 sp = source.properties 913 mp = source.mapper 914 sa = source.actor 915 except AttributeError: 916 sp = source.GetProperty() 917 mp = source.GetMapper() 918 sa = source 919 920 if deep: 921 pr.DeepCopy(sp) 922 else: 923 pr.ShallowCopy(sp) 924 self.actor.SetProperty(pr) 925 self.properties = pr 926 927 if self.actor.GetBackfaceProperty(): 928 bfpr = vtki.vtkProperty() 929 bfpr.DeepCopy(sa.GetBackfaceProperty()) 930 self.actor.SetBackfaceProperty(bfpr) 931 self.properties_backface = bfpr 932 933 if not actor_related: 934 return self 935 936 # mapper related: 937 self.mapper.SetScalarVisibility(mp.GetScalarVisibility()) 938 self.mapper.SetScalarMode(mp.GetScalarMode()) 939 self.mapper.SetScalarRange(mp.GetScalarRange()) 940 self.mapper.SetLookupTable(mp.GetLookupTable()) 941 self.mapper.SetColorMode(mp.GetColorMode()) 942 self.mapper.SetInterpolateScalarsBeforeMapping( 943 mp.GetInterpolateScalarsBeforeMapping() 944 ) 945 self.mapper.SetUseLookupTableScalarRange( 946 mp.GetUseLookupTableScalarRange() 947 ) 948 949 self.actor.SetPickable(sa.GetPickable()) 950 self.actor.SetDragable(sa.GetDragable()) 951 self.actor.SetTexture(sa.GetTexture()) 952 self.actor.SetVisibility(sa.GetVisibility()) 953 return self
Copy properties from another Points
object.
955 def color(self, c=False, alpha=None) -> Union[np.ndarray, Self]: 956 """ 957 Set/get mesh's color. 958 If None is passed as input, will use colors from active scalars. 959 Same as `mesh.c()`. 960 """ 961 if c is False: 962 return np.array(self.properties.GetColor()) 963 if c is None: 964 self.mapper.ScalarVisibilityOn() 965 return self 966 self.mapper.ScalarVisibilityOff() 967 cc = colors.get_color(c) 968 self.properties.SetColor(cc) 969 if self.trail: 970 self.trail.properties.SetColor(cc) 971 if alpha is not None: 972 self.alpha(alpha) 973 return self
Set/get mesh's color.
If None is passed as input, will use colors from active scalars.
Same as mesh.c()
.
975 def c(self, color=False, alpha=None) -> Union[np.ndarray, Self]: 976 """ 977 Shortcut for `color()`. 978 If None is passed as input, will use colors from current active scalars. 979 """ 980 return self.color(color, alpha)
Shortcut for color()
.
If None is passed as input, will use colors from current active scalars.
982 def alpha(self, opacity=None) -> Union[float, Self]: 983 """Set/get mesh's transparency. Same as `mesh.opacity()`.""" 984 if opacity is None: 985 return self.properties.GetOpacity() 986 987 self.properties.SetOpacity(opacity) 988 bfp = self.actor.GetBackfaceProperty() 989 if bfp: 990 if opacity < 1: 991 self.properties_backface = bfp 992 self.actor.SetBackfaceProperty(None) 993 else: 994 self.actor.SetBackfaceProperty(self.properties_backface) 995 return self
Set/get mesh's transparency. Same as mesh.opacity()
.
997 def lut_color_at(self, value) -> np.ndarray: 998 """ 999 Return the color and alpha in the lookup table at given value. 1000 """ 1001 lut = self.mapper.GetLookupTable() 1002 if not lut: 1003 return None 1004 rgb = [0,0,0] 1005 lut.GetColor(value, rgb) 1006 alpha = lut.GetOpacity(value) 1007 return np.array(rgb + [alpha])
Return the color and alpha in the lookup table at given value.
1009 def opacity(self, alpha=None) -> Union[float, Self]: 1010 """Set/get mesh's transparency. Same as `mesh.alpha()`.""" 1011 return self.alpha(alpha)
Set/get mesh's transparency. Same as mesh.alpha()
.
1013 def force_opaque(self, value=True) -> Self: 1014 """ Force the Mesh, Line or point cloud to be treated as opaque""" 1015 ## force the opaque pass, fixes picking in vtk9 1016 # but causes other bad troubles with lines.. 1017 self.actor.SetForceOpaque(value) 1018 return self
Force the Mesh, Line or point cloud to be treated as opaque
1020 def force_translucent(self, value=True) -> Self: 1021 """ Force the Mesh, Line or point cloud to be treated as translucent""" 1022 self.actor.SetForceTranslucent(value) 1023 return self
Force the Mesh, Line or point cloud to be treated as translucent
1025 def point_size(self, value=None) -> Union[int, Self]: 1026 """Set/get mesh's point size of vertices. Same as `mesh.ps()`""" 1027 if value is None: 1028 return self.properties.GetPointSize() 1029 # self.properties.SetRepresentationToSurface() 1030 else: 1031 self.properties.SetRepresentationToPoints() 1032 self.properties.SetPointSize(value) 1033 return self
Set/get mesh's point size of vertices. Same as mesh.ps()
1035 def ps(self, pointsize=None) -> Union[int, Self]: 1036 """Set/get mesh's point size of vertices. Same as `mesh.point_size()`""" 1037 return self.point_size(pointsize)
Set/get mesh's point size of vertices. Same as mesh.point_size()
1039 def render_points_as_spheres(self, value=True) -> Self: 1040 """Make points look spheric or else make them look as squares.""" 1041 self.properties.SetRenderPointsAsSpheres(value) 1042 return self
Make points look spheric or else make them look as squares.
1044 def lighting( 1045 self, 1046 style="", 1047 ambient=None, 1048 diffuse=None, 1049 specular=None, 1050 specular_power=None, 1051 specular_color=None, 1052 metallicity=None, 1053 roughness=None, 1054 ) -> Self: 1055 """ 1056 Set the ambient, diffuse, specular and specular_power lighting constants. 1057 1058 Arguments: 1059 style : (str) 1060 preset style, options are `[metallic, plastic, shiny, glossy, ambient, off]` 1061 ambient : (float) 1062 ambient fraction of emission [0-1] 1063 diffuse : (float) 1064 emission of diffused light in fraction [0-1] 1065 specular : (float) 1066 fraction of reflected light [0-1] 1067 specular_power : (float) 1068 precision of reflection [1-100] 1069 specular_color : (color) 1070 color that is being reflected by the surface 1071 1072 <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" alt="", width=700px> 1073 1074 Examples: 1075 - [specular.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/specular.py) 1076 """ 1077 pr = self.properties 1078 1079 if style: 1080 1081 if style != "off": 1082 pr.LightingOn() 1083 1084 if style == "off": 1085 pr.SetInterpolationToFlat() 1086 pr.LightingOff() 1087 return self ############## 1088 1089 if hasattr(pr, "GetColor"): # could be Volume 1090 c = pr.GetColor() 1091 else: 1092 c = (1, 1, 0.99) 1093 mpr = self.mapper 1094 if hasattr(mpr, 'GetScalarVisibility') and mpr.GetScalarVisibility(): 1095 c = (1,1,0.99) 1096 if style=='metallic': pars = [0.1, 0.3, 1.0, 10, c] 1097 elif style=='plastic' : pars = [0.3, 0.4, 0.3, 5, c] 1098 elif style=='shiny' : pars = [0.2, 0.6, 0.8, 50, c] 1099 elif style=='glossy' : pars = [0.1, 0.7, 0.9, 90, (1,1,0.99)] 1100 elif style=='ambient' : pars = [0.8, 0.1, 0.0, 1, (1,1,1)] 1101 elif style=='default' : pars = [0.1, 1.0, 0.05, 5, c] 1102 else: 1103 vedo.logger.error("in lighting(): Available styles are") 1104 vedo.logger.error("[default, metallic, plastic, shiny, glossy, ambient, off]") 1105 raise RuntimeError() 1106 pr.SetAmbient(pars[0]) 1107 pr.SetDiffuse(pars[1]) 1108 pr.SetSpecular(pars[2]) 1109 pr.SetSpecularPower(pars[3]) 1110 if hasattr(pr, "GetColor"): 1111 pr.SetSpecularColor(pars[4]) 1112 1113 if ambient is not None: pr.SetAmbient(ambient) 1114 if diffuse is not None: pr.SetDiffuse(diffuse) 1115 if specular is not None: pr.SetSpecular(specular) 1116 if specular_power is not None: pr.SetSpecularPower(specular_power) 1117 if specular_color is not None: pr.SetSpecularColor(colors.get_color(specular_color)) 1118 if metallicity is not None: 1119 pr.SetInterpolationToPBR() 1120 pr.SetMetallic(metallicity) 1121 if roughness is not None: 1122 pr.SetInterpolationToPBR() 1123 pr.SetRoughness(roughness) 1124 1125 return self
Set the ambient, diffuse, specular and specular_power lighting constants.
Arguments:
- style : (str)
preset style, options are
[metallic, plastic, shiny, glossy, ambient, off]
- ambient : (float) ambient fraction of emission [0-1]
- diffuse : (float) emission of diffused light in fraction [0-1]
- specular : (float) fraction of reflected light [0-1]
- specular_power : (float) precision of reflection [1-100]
- specular_color : (color) color that is being reflected by the surface
Examples:
1127 def point_blurring(self, r=1, alpha=1.0, emissive=False) -> Self: 1128 """Set point blurring. 1129 Apply a gaussian convolution filter to the points. 1130 In this case the radius `r` is in absolute units of the mesh coordinates. 1131 With emissive set, the halo of point becomes light-emissive. 1132 """ 1133 self.properties.SetRepresentationToPoints() 1134 if emissive: 1135 self.mapper.SetEmissive(bool(emissive)) 1136 self.mapper.SetScaleFactor(r * 1.4142) 1137 1138 # https://kitware.github.io/vtk-examples/site/Python/Meshes/PointInterpolator/ 1139 if alpha < 1: 1140 self.mapper.SetSplatShaderCode( 1141 "//VTK::Color::Impl\n" 1142 "float dist = dot(offsetVCVSOutput.xy,offsetVCVSOutput.xy);\n" 1143 "if (dist > 1.0) {\n" 1144 " discard;\n" 1145 "} else {\n" 1146 f" float scale = ({alpha} - dist);\n" 1147 " ambientColor *= scale;\n" 1148 " diffuseColor *= scale;\n" 1149 "}\n" 1150 ) 1151 alpha = 1 1152 1153 self.mapper.Modified() 1154 self.actor.Modified() 1155 self.properties.SetOpacity(alpha) 1156 self.actor.SetMapper(self.mapper) 1157 return self
Set point blurring.
Apply a gaussian convolution filter to the points.
In this case the radius r
is in absolute units of the mesh coordinates.
With emissive set, the halo of point becomes light-emissive.
1159 @property 1160 def cellcolors(self): 1161 """ 1162 Colorize each cell (face) of a mesh by passing 1163 a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. 1164 Colors levels and opacities must be in the range [0,255]. 1165 1166 A single constant color can also be passed as string or RGBA. 1167 1168 A cell array named "CellsRGBA" is automatically created. 1169 1170 Examples: 1171 - [color_mesh_cells1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells1.py) 1172 - [color_mesh_cells2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells2.py) 1173 1174  1175 """ 1176 if "CellsRGBA" not in self.celldata.keys(): 1177 lut = self.mapper.GetLookupTable() 1178 vscalars = self.dataset.GetCellData().GetScalars() 1179 if vscalars is None or lut is None: 1180 arr = np.zeros([self.ncells, 4], dtype=np.uint8) 1181 col = np.array(self.properties.GetColor()) 1182 col = np.round(col * 255).astype(np.uint8) 1183 alf = self.properties.GetOpacity() 1184 alf = np.round(alf * 255).astype(np.uint8) 1185 arr[:, (0, 1, 2)] = col 1186 arr[:, 3] = alf 1187 else: 1188 cols = lut.MapScalars(vscalars, 0, 0) 1189 arr = utils.vtk2numpy(cols) 1190 self.celldata["CellsRGBA"] = arr 1191 self.celldata.select("CellsRGBA") 1192 return self.celldata["CellsRGBA"]
Colorize each cell (face) of a mesh by passing a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. Colors levels and opacities must be in the range [0,255].
A single constant color can also be passed as string or RGBA.
A cell array named "CellsRGBA" is automatically created.
Examples:
1217 @property 1218 def pointcolors(self): 1219 """ 1220 Colorize each point (or vertex of a mesh) by passing 1221 a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. 1222 Colors levels and opacities must be in the range [0,255]. 1223 1224 A single constant color can also be passed as string or RGBA. 1225 1226 A point array named "PointsRGBA" is automatically created. 1227 """ 1228 if "PointsRGBA" not in self.pointdata.keys(): 1229 lut = self.mapper.GetLookupTable() 1230 vscalars = self.dataset.GetPointData().GetScalars() 1231 if vscalars is None or lut is None: 1232 # create a constant array 1233 arr = np.zeros([self.npoints, 4], dtype=np.uint8) 1234 col = np.array(self.properties.GetColor()) 1235 col = np.round(col * 255).astype(np.uint8) 1236 alf = self.properties.GetOpacity() 1237 alf = np.round(alf * 255).astype(np.uint8) 1238 arr[:, (0, 1, 2)] = col 1239 arr[:, 3] = alf 1240 else: 1241 cols = lut.MapScalars(vscalars, 0, 0) 1242 arr = utils.vtk2numpy(cols) 1243 self.pointdata["PointsRGBA"] = arr 1244 self.pointdata.select("PointsRGBA") 1245 return self.pointdata["PointsRGBA"]
Colorize each point (or vertex of a mesh) by passing a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. Colors levels and opacities must be in the range [0,255].
A single constant color can also be passed as string or RGBA.
A point array named "PointsRGBA" is automatically created.
1271 def cmap( 1272 self, 1273 input_cmap, 1274 input_array=None, 1275 on="", 1276 name="Scalars", 1277 vmin=None, 1278 vmax=None, 1279 n_colors=256, 1280 alpha=1.0, 1281 logscale=False, 1282 ) -> Self: 1283 """ 1284 Set individual point/cell colors by providing a list of scalar values and a color map. 1285 1286 Arguments: 1287 input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap) 1288 color map scheme to transform a real number into a color. 1289 input_array : (str, list, vtkArray) 1290 can be the string name of an existing array, a new array or a `vtkArray`. 1291 on : (str) 1292 either 'points' or 'cells' or blank (automatic). 1293 Apply the color map to data which is defined on either points or cells. 1294 name : (str) 1295 give a name to the provided array (if input_array is an array) 1296 vmin : (float) 1297 clip scalars to this minimum value 1298 vmax : (float) 1299 clip scalars to this maximum value 1300 n_colors : (int) 1301 number of distinct colors to be used in colormap table. 1302 alpha : (float, list) 1303 Mesh transparency. Can be a `list` of values one for each vertex. 1304 logscale : (bool) 1305 Use logscale 1306 1307 Examples: 1308 - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py) 1309 - [mesh_alphas.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_alphas.py) 1310 - [mesh_custom.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_custom.py) 1311 - (and many others) 1312 1313  1314 """ 1315 self._cmap_name = input_cmap 1316 1317 if on == "": 1318 try: 1319 on = self.mapper.GetScalarModeAsString().replace("Use", "") 1320 if on not in ["PointData", "CellData"]: # can be "Default" 1321 on = "points" 1322 self.mapper.SetScalarModeToUsePointData() 1323 except AttributeError: 1324 on = "points" 1325 elif on == "Default": 1326 on = "points" 1327 self.mapper.SetScalarModeToUsePointData() 1328 1329 if input_array is None: 1330 if not self.pointdata.keys() and self.celldata.keys(): 1331 on = "cells" 1332 if not self.dataset.GetCellData().GetScalars(): 1333 input_array = 0 # pick the first at hand 1334 1335 if "point" in on.lower(): 1336 data = self.dataset.GetPointData() 1337 n = self.dataset.GetNumberOfPoints() 1338 elif "cell" in on.lower(): 1339 data = self.dataset.GetCellData() 1340 n = self.dataset.GetNumberOfCells() 1341 else: 1342 vedo.logger.error( 1343 f"Must specify in cmap(on=...) to either 'cells' or 'points', not {on}") 1344 raise RuntimeError() 1345 1346 if input_array is None: # if None try to fetch the active scalars 1347 arr = data.GetScalars() 1348 if not arr: 1349 vedo.logger.error(f"in cmap(), cannot find any {on} active array ...skip coloring.") 1350 return self 1351 1352 if not arr.GetName(): # sometimes arrays dont have a name.. 1353 arr.SetName(name) 1354 1355 elif isinstance(input_array, str): # if a string is passed 1356 arr = data.GetArray(input_array) 1357 if not arr: 1358 vedo.logger.error(f"in cmap(), cannot find {on} array {input_array} ...skip coloring.") 1359 return self 1360 1361 elif isinstance(input_array, int): # if an int is passed 1362 if input_array < data.GetNumberOfArrays(): 1363 arr = data.GetArray(input_array) 1364 else: 1365 vedo.logger.error(f"in cmap(), cannot find {on} array at {input_array} ...skip coloring.") 1366 return self 1367 1368 elif utils.is_sequence(input_array): # if a numpy array is passed 1369 npts = len(input_array) 1370 if npts != n: 1371 vedo.logger.error(f"in cmap(), nr. of input {on} scalars {npts} != {n} ...skip coloring.") 1372 return self 1373 arr = utils.numpy2vtk(input_array, name=name, dtype=float) 1374 data.AddArray(arr) 1375 data.Modified() 1376 1377 elif isinstance(input_array, vtki.vtkArray): # if a vtkArray is passed 1378 arr = input_array 1379 data.AddArray(arr) 1380 data.Modified() 1381 1382 else: 1383 vedo.logger.error(f"in cmap(), cannot understand input type {type(input_array)}") 1384 raise RuntimeError() 1385 1386 # Now we have array "arr" 1387 array_name = arr.GetName() 1388 1389 if arr.GetNumberOfComponents() == 1: 1390 if vmin is None: 1391 vmin = arr.GetRange()[0] 1392 if vmax is None: 1393 vmax = arr.GetRange()[1] 1394 else: 1395 if vmin is None or vmax is None: 1396 vn = utils.mag(utils.vtk2numpy(arr)) 1397 if vmin is None: 1398 vmin = vn.min() 1399 if vmax is None: 1400 vmax = vn.max() 1401 1402 # interpolate alphas if they are not constant 1403 if not utils.is_sequence(alpha): 1404 alpha = [alpha] * n_colors 1405 else: 1406 v = np.linspace(0, 1, n_colors, endpoint=True) 1407 xp = np.linspace(0, 1, len(alpha), endpoint=True) 1408 alpha = np.interp(v, xp, alpha) 1409 1410 ########################### build the look-up table 1411 if isinstance(input_cmap, vtki.vtkLookupTable): # vtkLookupTable 1412 lut = input_cmap 1413 1414 elif utils.is_sequence(input_cmap): # manual sequence of colors 1415 lut = vtki.vtkLookupTable() 1416 if logscale: 1417 lut.SetScaleToLog10() 1418 lut.SetRange(vmin, vmax) 1419 ncols = len(input_cmap) 1420 lut.SetNumberOfTableValues(ncols) 1421 1422 for i, c in enumerate(input_cmap): 1423 r, g, b = colors.get_color(c) 1424 lut.SetTableValue(i, r, g, b, alpha[i]) 1425 lut.Build() 1426 1427 else: 1428 # assume string cmap name OR matplotlib.colors.LinearSegmentedColormap 1429 lut = vtki.vtkLookupTable() 1430 if logscale: 1431 lut.SetScaleToLog10() 1432 lut.SetVectorModeToMagnitude() 1433 lut.SetRange(vmin, vmax) 1434 lut.SetNumberOfTableValues(n_colors) 1435 mycols = colors.color_map(range(n_colors), input_cmap, 0, n_colors) 1436 for i, c in enumerate(mycols): 1437 r, g, b = c 1438 lut.SetTableValue(i, r, g, b, alpha[i]) 1439 lut.Build() 1440 1441 # TEST NEW WAY 1442 self.mapper.SetLookupTable(lut) 1443 self.mapper.ScalarVisibilityOn() 1444 self.mapper.SetColorModeToMapScalars() 1445 self.mapper.SetScalarRange(lut.GetRange()) 1446 if "point" in on.lower(): 1447 self.pointdata.select(array_name) 1448 else: 1449 self.celldata.select(array_name) 1450 return self 1451 1452 # # TEST this is the old way: 1453 # # arr.SetLookupTable(lut) # wrong! causes weird instabilities with LUT 1454 # # if data.GetScalars(): 1455 # # data.GetScalars().SetLookupTable(lut) 1456 # # data.GetScalars().Modified() 1457 1458 # data.SetActiveScalars(array_name) 1459 # # data.SetScalars(arr) # wrong! it deletes array in position 0, never use SetScalars 1460 # # data.SetActiveAttribute(array_name, 0) # boh! 1461 1462 # self.mapper.SetLookupTable(lut) 1463 # self.mapper.SetColorModeToMapScalars() # so we dont need to convert uint8 scalars 1464 1465 # self.mapper.ScalarVisibilityOn() 1466 # self.mapper.SetScalarRange(lut.GetRange()) 1467 1468 # if on.startswith("point"): 1469 # self.mapper.SetScalarModeToUsePointData() 1470 # else: 1471 # self.mapper.SetScalarModeToUseCellData() 1472 # if hasattr(self.mapper, "SetArrayName"): 1473 # self.mapper.SetArrayName(array_name) 1474 # return self
Set individual point/cell colors by providing a list of scalar values and a color map.
Arguments:
- input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap) color map scheme to transform a real number into a color.
- input_array : (str, list, vtkArray)
can be the string name of an existing array, a new array or a
vtkArray
. - on : (str) either 'points' or 'cells' or blank (automatic). Apply the color map to data which is defined on either points or cells.
- name : (str) give a name to the provided array (if input_array is an array)
- vmin : (float) clip scalars to this minimum value
- vmax : (float) clip scalars to this maximum value
- n_colors : (int) number of distinct colors to be used in colormap table.
- alpha : (float, list)
Mesh transparency. Can be a
list
of values one for each vertex. - logscale : (bool) Use logscale
Examples:
- mesh_coloring.py
- mesh_alphas.py
- mesh_custom.py
(and many others)
1476 def add_trail(self, offset=(0, 0, 0), n=50, c=None, alpha=1.0, lw=2) -> Self: 1477 """ 1478 Add a trailing line to mesh. 1479 This new mesh is accessible through `mesh.trail`. 1480 1481 Arguments: 1482 offset : (float) 1483 set an offset vector from the object center. 1484 n : (int) 1485 number of segments 1486 lw : (float) 1487 line width of the trail 1488 1489 Examples: 1490 - [trail.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/trail.py) 1491 1492  1493 1494 - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py) 1495 - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py) 1496 """ 1497 if self.trail is None: 1498 pos = self.pos() 1499 self.trail_offset = np.asarray(offset) 1500 self.trail_points = [pos] * n 1501 1502 if c is None: 1503 col = self.properties.GetColor() 1504 else: 1505 col = colors.get_color(c) 1506 1507 tline = vedo.shapes.Line(pos, pos, res=n, c=col, alpha=alpha, lw=lw) 1508 self.trail = tline # holds the Line 1509 self.trail.initilized = False # so the first update will be a reset 1510 return self
Add a trailing line to mesh.
This new mesh is accessible through mesh.trail
.
Arguments:
- offset : (float) set an offset vector from the object center.
- n : (int) number of segments
- lw : (float) line width of the trail
Examples:
1512 def update_trail(self) -> Self: 1513 """ 1514 Update the trailing line of a moving object. 1515 """ 1516 currentpos = self.pos() 1517 if not self.trail.initilized: 1518 self.trail_points = [currentpos] * self.trail.npoints 1519 self.trail.initilized = True 1520 return self 1521 self.trail_points.append(currentpos) # cycle 1522 self.trail_points.pop(0) 1523 1524 data = np.array(self.trail_points) + self.trail_offset 1525 tpoly = self.trail.dataset 1526 tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32)) 1527 return self
Update the trailing line of a moving object.
1564 def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0) -> Self: 1565 """ 1566 Generate a shadow out of an `Mesh` on one of the three Cartesian planes. 1567 The output is a new `Mesh` representing the shadow. 1568 This new mesh is accessible through `mesh.shadow`. 1569 By default the shadow mesh is placed on the bottom wall of the bounding box. 1570 1571 See also `pointcloud.project_on_plane()`. 1572 1573 Arguments: 1574 plane : (str, Plane) 1575 if plane is `str`, plane can be one of `['x', 'y', 'z']`, 1576 represents x-plane, y-plane and z-plane, respectively. 1577 Otherwise, plane should be an instance of `vedo.shapes.Plane` 1578 point : (float, array) 1579 if plane is `str`, point should be a float represents the intercept. 1580 Otherwise, point is the camera point of perspective projection 1581 direction : (list) 1582 direction of oblique projection 1583 culling : (int) 1584 choose between front [1] or backface [-1] culling or None. 1585 1586 Examples: 1587 - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py) 1588 - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py) 1589 - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py) 1590 1591  1592 """ 1593 shad = self._compute_shadow(plane, point, direction) 1594 shad.c(c).alpha(alpha) 1595 1596 try: 1597 # Points dont have these methods 1598 shad.flat() 1599 if culling in (1, True): 1600 shad.frontface_culling() 1601 elif culling == -1: 1602 shad.backface_culling() 1603 except AttributeError: 1604 pass 1605 1606 shad.properties.LightingOff() 1607 shad.actor.SetPickable(False) 1608 shad.actor.SetUseBounds(True) 1609 1610 if shad not in self.shadows: 1611 self.shadows.append(shad) 1612 shad.info = dict(plane=plane, point=point, direction=direction) 1613 # shad.metadata["plane"] = plane 1614 # shad.metadata["point"] = point 1615 # print("AAAA", direction, plane, point) 1616 # if direction is None: 1617 # direction = [0,0,0] 1618 # shad.metadata["direction"] = direction 1619 return self
Generate a shadow out of an Mesh
on one of the three Cartesian planes.
The output is a new Mesh
representing the shadow.
This new mesh is accessible through mesh.shadow
.
By default the shadow mesh is placed on the bottom wall of the bounding box.
See also pointcloud.project_on_plane()
.
Arguments:
- plane : (str, Plane)
if plane is
str
, plane can be one of['x', 'y', 'z']
, represents x-plane, y-plane and z-plane, respectively. Otherwise, plane should be an instance ofvedo.shapes.Plane
- point : (float, array)
if plane is
str
, point should be a float represents the intercept. Otherwise, point is the camera point of perspective projection - direction : (list) direction of oblique projection
- culling : (int) choose between front [1] or backface [-1] culling or None.
Examples:
1621 def update_shadows(self) -> Self: 1622 """Update the shadows of a moving object.""" 1623 for sha in self.shadows: 1624 plane = sha.info["plane"] 1625 point = sha.info["point"] 1626 direction = sha.info["direction"] 1627 # print("update_shadows direction", direction,plane,point ) 1628 # plane = sha.metadata["plane"] 1629 # point = sha.metadata["point"] 1630 # direction = sha.metadata["direction"] 1631 # if direction[0]==0 and direction[1]==0 and direction[2]==0: 1632 # direction = None 1633 # print("BBBB", sha.metadata["direction"], 1634 # sha.metadata["plane"], sha.metadata["point"]) 1635 new_sha = self._compute_shadow(plane, point, direction) 1636 sha._update(new_sha.dataset) 1637 if self.trail: 1638 self.trail.update_shadows() 1639 return self
Update the shadows of a moving object.
1641 def labels( 1642 self, 1643 content=None, 1644 on="points", 1645 scale=None, 1646 xrot=0.0, 1647 yrot=0.0, 1648 zrot=0.0, 1649 ratio=1, 1650 precision=None, 1651 italic=False, 1652 font="", 1653 justify="", 1654 c="black", 1655 alpha=1.0, 1656 ) -> Union["vedo.Mesh", None]: 1657 """ 1658 Generate value or ID labels for mesh cells or points. 1659 For large nr. of labels use `font="VTK"` which is much faster. 1660 1661 See also: 1662 `labels2d()`, `flagpole()`, `caption()` and `legend()`. 1663 1664 Arguments: 1665 content : (list,int,str) 1666 either 'id', 'cellid', array name or array number. 1667 A array can also be passed (must match the nr. of points or cells). 1668 on : (str) 1669 generate labels for "cells" instead of "points" 1670 scale : (float) 1671 absolute size of labels, if left as None it is automatic 1672 zrot : (float) 1673 local rotation angle of label in degrees 1674 ratio : (int) 1675 skipping ratio, to reduce nr of labels for large meshes 1676 precision : (int) 1677 numeric precision of labels 1678 1679 ```python 1680 from vedo import * 1681 s = Sphere(res=10).linewidth(1).c("orange").compute_normals() 1682 point_ids = s.labels('id', on="points").c('green') 1683 cell_ids = s.labels('id', on="cells" ).c('black') 1684 show(s, point_ids, cell_ids) 1685 ``` 1686  1687 1688 Examples: 1689 - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py) 1690 1691  1692 """ 1693 1694 cells = False 1695 if "cell" in on or "face" in on: 1696 cells = True 1697 justify = "centered" if justify == "" else justify 1698 1699 if isinstance(content, str): 1700 if content in ("pointid", "pointsid"): 1701 cells = False 1702 content = "id" 1703 justify = "bottom-left" if justify == "" else justify 1704 if content in ("cellid", "cellsid"): 1705 cells = True 1706 content = "id" 1707 justify = "centered" if justify == "" else justify 1708 1709 try: 1710 if cells: 1711 ns = np.sqrt(self.ncells) 1712 elems = self.cell_centers().points 1713 norms = self.cell_normals 1714 justify = "centered" if justify == "" else justify 1715 else: 1716 ns = np.sqrt(self.npoints) 1717 elems = self.vertices 1718 norms = self.vertex_normals 1719 except AttributeError: 1720 norms = [] 1721 1722 if not justify: 1723 justify = "bottom-left" 1724 1725 hasnorms = False 1726 if len(norms) > 0: 1727 hasnorms = True 1728 1729 if scale is None: 1730 if not ns: 1731 ns = 100 1732 scale = self.diagonal_size() / ns / 10 1733 1734 arr = None 1735 mode = 0 1736 if content is None: 1737 mode = 0 1738 if cells: 1739 if self.dataset.GetCellData().GetScalars(): 1740 name = self.dataset.GetCellData().GetScalars().GetName() 1741 arr = self.celldata[name] 1742 else: 1743 if self.dataset.GetPointData().GetScalars(): 1744 name = self.dataset.GetPointData().GetScalars().GetName() 1745 arr = self.pointdata[name] 1746 elif isinstance(content, (str, int)): 1747 if content == "id": 1748 mode = 1 1749 elif cells: 1750 mode = 0 1751 arr = self.celldata[content] 1752 else: 1753 mode = 0 1754 arr = self.pointdata[content] 1755 elif utils.is_sequence(content): 1756 mode = 0 1757 arr = content 1758 1759 if arr is None and mode == 0: 1760 vedo.logger.error("in labels(), array not found in point or cell data") 1761 return None 1762 1763 ratio = int(ratio+0.5) 1764 tapp = vtki.new("AppendPolyData") 1765 has_inputs = False 1766 1767 for i, e in enumerate(elems): 1768 if i % ratio: 1769 continue 1770 1771 if mode == 1: 1772 txt_lab = str(i) 1773 else: 1774 if precision: 1775 txt_lab = utils.precision(arr[i], precision) 1776 else: 1777 txt_lab = str(arr[i]) 1778 1779 if not txt_lab: 1780 continue 1781 1782 if font == "VTK": 1783 tx = vtki.new("VectorText") 1784 tx.SetText(txt_lab) 1785 tx.Update() 1786 tx_poly = tx.GetOutput() 1787 else: 1788 tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify).dataset 1789 1790 if tx_poly.GetNumberOfPoints() == 0: 1791 continue ###################### 1792 1793 T = vtki.vtkTransform() 1794 T.PostMultiply() 1795 if italic: 1796 T.Concatenate([1, 0.2, 0, 0, 1797 0, 1 , 0, 0, 1798 0, 0 , 1, 0, 1799 0, 0 , 0, 1]) 1800 if hasnorms: 1801 ni = norms[i] 1802 if cells and font=="VTK": # center-justify 1803 bb = tx_poly.GetBounds() 1804 dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2 1805 T.Translate(-dx, -dy, 0) 1806 if xrot: T.RotateX(xrot) 1807 if yrot: T.RotateY(yrot) 1808 if zrot: T.RotateZ(zrot) 1809 crossvec = np.cross([0, 0, 1], ni) 1810 angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3 1811 T.RotateWXYZ(float(angle), crossvec.tolist()) 1812 T.Translate(ni / 100) 1813 else: 1814 if xrot: T.RotateX(xrot) 1815 if yrot: T.RotateY(yrot) 1816 if zrot: T.RotateZ(zrot) 1817 T.Scale(scale, scale, scale) 1818 T.Translate(e) 1819 tf = vtki.new("TransformPolyDataFilter") 1820 tf.SetInputData(tx_poly) 1821 tf.SetTransform(T) 1822 tf.Update() 1823 tapp.AddInputData(tf.GetOutput()) 1824 has_inputs = True 1825 1826 if has_inputs: 1827 tapp.Update() 1828 lpoly = tapp.GetOutput() 1829 else: 1830 lpoly = vtki.vtkPolyData() 1831 ids = vedo.Mesh(lpoly, c=c, alpha=alpha) 1832 ids.properties.LightingOff() 1833 ids.actor.PickableOff() 1834 ids.actor.SetUseBounds(False) 1835 ids.name = "Labels" 1836 return ids
Generate value or ID labels for mesh cells or points.
For large nr. of labels use font="VTK"
which is much faster.
See also:
labels2d()
,flagpole()
,caption()
andlegend()
.
Arguments:
- content : (list,int,str) either 'id', 'cellid', array name or array number. A array can also be passed (must match the nr. of points or cells).
- on : (str) generate labels for "cells" instead of "points"
- scale : (float) absolute size of labels, if left as None it is automatic
- zrot : (float) local rotation angle of label in degrees
- ratio : (int) skipping ratio, to reduce nr of labels for large meshes
- precision : (int) numeric precision of labels
from vedo import *
s = Sphere(res=10).linewidth(1).c("orange").compute_normals()
point_ids = s.labels('id', on="points").c('green')
cell_ids = s.labels('id', on="cells" ).c('black')
show(s, point_ids, cell_ids)
Examples:
1838 def labels2d( 1839 self, 1840 content="id", 1841 on="points", 1842 scale=1.0, 1843 precision=4, 1844 font="Calco", 1845 justify="bottom-left", 1846 angle=0.0, 1847 frame=False, 1848 c="black", 1849 bc=None, 1850 alpha=1.0, 1851 ) -> Union["Actor2D", None]: 1852 """ 1853 Generate value or ID bi-dimensional labels for mesh cells or points. 1854 1855 See also: `labels()`, `flagpole()`, `caption()` and `legend()`. 1856 1857 Arguments: 1858 content : (str) 1859 either 'id', 'cellid', or array name 1860 on : (str) 1861 generate labels for "cells" instead of "points" (the default) 1862 scale : (float) 1863 size scaling of labels 1864 precision : (int) 1865 precision of numeric labels 1866 angle : (float) 1867 local rotation angle of label in degrees 1868 frame : (bool) 1869 draw a frame around the label 1870 bc : (str) 1871 background color of the label 1872 1873 ```python 1874 from vedo import Sphere, show 1875 sph = Sphere(quads=True, res=4).compute_normals().wireframe() 1876 sph.celldata["zvals"] = sph.cell_centers().coordinates[:,2] 1877 l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9') 1878 show(sph, l2d, axes=1).close() 1879 ``` 1880  1881 """ 1882 cells = False 1883 if "cell" in on: 1884 cells = True 1885 1886 if isinstance(content, str): 1887 if content in ("id", "pointid", "pointsid"): 1888 cells = False 1889 content = "id" 1890 if content in ("cellid", "cellsid"): 1891 cells = True 1892 content = "id" 1893 1894 if cells: 1895 if content != "id" and content not in self.celldata.keys(): 1896 vedo.logger.error(f"In labels2d: cell array {content} does not exist.") 1897 return None 1898 arr = self.dataset.GetCellData().GetScalars() 1899 poly = self.cell_centers().dataset 1900 poly.GetPointData().SetScalars(arr) 1901 else: 1902 arr = self.dataset.GetPointData().GetScalars() 1903 poly = self.dataset 1904 if content != "id" and content not in self.pointdata.keys(): 1905 vedo.logger.error(f"In labels2d: point array {content} does not exist.") 1906 return None 1907 1908 mp = vtki.new("LabeledDataMapper") 1909 1910 if content == "id": 1911 mp.SetLabelModeToLabelIds() 1912 else: 1913 mp.SetLabelModeToLabelScalars() 1914 if precision is not None: 1915 dtype = arr.GetDataType() 1916 if dtype in (vtki.VTK_FLOAT, vtki.VTK_DOUBLE): 1917 mp.SetLabelFormat(f"%-#.{precision}g") 1918 1919 pr = mp.GetLabelTextProperty() 1920 c = colors.get_color(c) 1921 pr.SetColor(c) 1922 pr.SetOpacity(alpha) 1923 pr.SetFrame(frame) 1924 pr.SetFrameColor(c) 1925 pr.SetItalic(False) 1926 pr.BoldOff() 1927 pr.ShadowOff() 1928 pr.UseTightBoundingBoxOn() 1929 pr.SetOrientation(angle) 1930 pr.SetFontFamily(vtki.VTK_FONT_FILE) 1931 fl = utils.get_font_path(font) 1932 pr.SetFontFile(fl) 1933 pr.SetFontSize(int(20 * scale)) 1934 1935 if "cent" in justify or "mid" in justify: 1936 pr.SetJustificationToCentered() 1937 elif "rig" in justify: 1938 pr.SetJustificationToRight() 1939 elif "left" in justify: 1940 pr.SetJustificationToLeft() 1941 # ------ 1942 if "top" in justify: 1943 pr.SetVerticalJustificationToTop() 1944 else: 1945 pr.SetVerticalJustificationToBottom() 1946 1947 if bc is not None: 1948 bc = colors.get_color(bc) 1949 pr.SetBackgroundColor(bc) 1950 pr.SetBackgroundOpacity(alpha) 1951 1952 mp.SetInputData(poly) 1953 a2d = Actor2D() 1954 a2d.PickableOff() 1955 a2d.SetMapper(mp) 1956 return a2d
Generate value or ID bi-dimensional labels for mesh cells or points.
See also: labels()
, flagpole()
, caption()
and legend()
.
Arguments:
- content : (str) either 'id', 'cellid', or array name
- on : (str) generate labels for "cells" instead of "points" (the default)
- scale : (float) size scaling of labels
- precision : (int) precision of numeric labels
- angle : (float) local rotation angle of label in degrees
- frame : (bool) draw a frame around the label
- bc : (str) background color of the label
from vedo import Sphere, show
sph = Sphere(quads=True, res=4).compute_normals().wireframe()
sph.celldata["zvals"] = sph.cell_centers().coordinates[:,2]
l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9')
show(sph, l2d, axes=1).close()
1958 def legend(self, txt) -> Self: 1959 """Book a legend text.""" 1960 self.info["legend"] = txt 1961 # self.metadata["legend"] = txt 1962 return self
Book a legend text.
1964 def flagpole( 1965 self, 1966 txt=None, 1967 point=None, 1968 offset=None, 1969 s=None, 1970 font="Calco", 1971 rounded=True, 1972 c=None, 1973 alpha=1.0, 1974 lw=2, 1975 italic=0.0, 1976 padding=0.1, 1977 ) -> Union["vedo.Mesh", None]: 1978 """ 1979 Generate a flag pole style element to describe an object. 1980 Returns a `Mesh` object. 1981 1982 Use flagpole.follow_camera() to make it face the camera in the scene. 1983 1984 Consider using `settings.use_parallel_projection = True` 1985 to avoid perspective distortions. 1986 1987 See also `flagpost()`. 1988 1989 Arguments: 1990 txt : (str) 1991 Text to display. The default is the filename or the object name. 1992 point : (list) 1993 position of the flagpole pointer. 1994 offset : (list) 1995 text offset wrt the application point. 1996 s : (float) 1997 size of the flagpole. 1998 font : (str) 1999 font face. Check [available fonts here](https://vedo.embl.es/fonts). 2000 rounded : (bool) 2001 draw a rounded or squared box around the text. 2002 c : (list) 2003 text and box color. 2004 alpha : (float) 2005 opacity of text and box. 2006 lw : (float) 2007 line with of box frame. 2008 italic : (float) 2009 italicness of text. 2010 2011 Examples: 2012 - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py) 2013 2014  2015 2016 - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py) 2017 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 2018 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 2019 """ 2020 objs = [] 2021 2022 if txt is None: 2023 if self.filename: 2024 txt = self.filename.split("/")[-1] 2025 elif self.name: 2026 txt = self.name 2027 else: 2028 return None 2029 2030 x0, x1, y0, y1, z0, z1 = self.bounds() 2031 d = self.diagonal_size() 2032 if point is None: 2033 if d: 2034 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2035 # point = self.closest_point([x1, y0, z1]) 2036 else: # it's a Point 2037 point = self.transform.position 2038 2039 pt = utils.make3d(point) 2040 2041 if offset is None: 2042 offset = [(x1 - x0) / 1.75, (y1 - y0) / 5, 0] 2043 offset = utils.make3d(offset) 2044 2045 if s is None: 2046 s = d / 20 2047 2048 sph = None 2049 if d and (z1 - z0) / d > 0.1: 2050 sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6) 2051 2052 if c is None: 2053 c = np.array(self.color()) / 1.4 2054 2055 lab = vedo.shapes.Text3D( 2056 txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center" 2057 ) 2058 objs.append(lab) 2059 2060 if d and not sph: 2061 sph = vedo.shapes.Circle(pt, r=s / 3, res=16) 2062 objs.append(sph) 2063 2064 x0, x1, y0, y1, z0, z1 = lab.bounds() 2065 aline = [(x0,y0,z0), (x1,y0,z0), (x1,y1,z0), (x0,y1,z0)] 2066 if rounded: 2067 box = vedo.shapes.KSpline(aline, closed=True) 2068 else: 2069 box = vedo.shapes.Line(aline, closed=True) 2070 2071 cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2] 2072 2073 # box.actor.SetOrigin(cnt) 2074 box.scale([1 + padding, 1 + 2 * padding, 1], origin=cnt) 2075 objs.append(box) 2076 2077 x0, x1, y0, y1, z0, z1 = box.bounds() 2078 if x0 < pt[0] < x1: 2079 c0 = box.closest_point(pt) 2080 c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]] 2081 elif (pt[0] - x0) < (x1 - pt[0]): 2082 c0 = [x0, (y0 + y1) / 2, pt[2]] 2083 c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]] 2084 else: 2085 c0 = [x1, (y0 + y1) / 2, pt[2]] 2086 c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]] 2087 2088 con = vedo.shapes.Line([c0, c1, pt]) 2089 objs.append(con) 2090 2091 mobjs = vedo.merge(objs).c(c).alpha(alpha) 2092 mobjs.name = "FlagPole" 2093 mobjs.bc("tomato").pickable(False) 2094 mobjs.properties.LightingOff() 2095 mobjs.properties.SetLineWidth(lw) 2096 mobjs.actor.UseBoundsOff() 2097 mobjs.actor.SetPosition([0,0,0]) 2098 mobjs.actor.SetOrigin(pt) 2099 return mobjs 2100 2101 # mobjs = vedo.Assembly(objs)#.c(c).alpha(alpha) 2102 # mobjs.name = "FlagPole" 2103 # # mobjs.bc("tomato").pickable(False) 2104 # # mobjs.properties.LightingOff() 2105 # # mobjs.properties.SetLineWidth(lw) 2106 # # mobjs.actor.UseBoundsOff() 2107 # # mobjs.actor.SetPosition([0,0,0]) 2108 # # mobjs.actor.SetOrigin(pt) 2109 # # print(pt) 2110 # return mobjs
Generate a flag pole style element to describe an object.
Returns a Mesh
object.
Use flagpole.follow_camera() to make it face the camera in the scene.
Consider using settings.use_parallel_projection = True
to avoid perspective distortions.
See also flagpost()
.
Arguments:
- txt : (str) Text to display. The default is the filename or the object name.
- point : (list) position of the flagpole pointer.
- offset : (list) text offset wrt the application point.
- s : (float) size of the flagpole.
- font : (str) font face. Check available fonts here.
- rounded : (bool) draw a rounded or squared box around the text.
- c : (list) text and box color.
- alpha : (float) opacity of text and box.
- lw : (float) line with of box frame.
- italic : (float) italicness of text.
Examples:
2112 def flagpost( 2113 self, 2114 txt=None, 2115 point=None, 2116 offset=None, 2117 s=1.0, 2118 c="k9", 2119 bc="k1", 2120 alpha=1, 2121 lw=0, 2122 font="Calco", 2123 justify="center-left", 2124 vspacing=1.0, 2125 ) -> Union["vedo.addons.Flagpost", None]: 2126 """ 2127 Generate a flag post style element to describe an object. 2128 2129 Arguments: 2130 txt : (str) 2131 Text to display. The default is the filename or the object name. 2132 point : (list) 2133 position of the flag anchor point. The default is None. 2134 offset : (list) 2135 a 3D displacement or offset. The default is None. 2136 s : (float) 2137 size of the text to be shown 2138 c : (list) 2139 color of text and line 2140 bc : (list) 2141 color of the flag background 2142 alpha : (float) 2143 opacity of text and box. 2144 lw : (int) 2145 line with of box frame. The default is 0. 2146 font : (str) 2147 font name. Use a monospace font for better rendering. The default is "Calco". 2148 Type `vedo -r fonts` for a font demo. 2149 Check [available fonts here](https://vedo.embl.es/fonts). 2150 justify : (str) 2151 internal text justification. The default is "center-left". 2152 vspacing : (float) 2153 vertical spacing between lines. 2154 2155 Examples: 2156 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py) 2157 2158  2159 """ 2160 if txt is None: 2161 if self.filename: 2162 txt = self.filename.split("/")[-1] 2163 elif self.name: 2164 txt = self.name 2165 else: 2166 return None 2167 2168 x0, x1, y0, y1, z0, z1 = self.bounds() 2169 d = self.diagonal_size() 2170 if point is None: 2171 if d: 2172 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2173 else: # it's a Point 2174 point = self.transform.position 2175 2176 point = utils.make3d(point) 2177 2178 if offset is None: 2179 offset = [0, 0, (z1 - z0) / 2] 2180 offset = utils.make3d(offset) 2181 2182 fpost = vedo.addons.Flagpost( 2183 txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing 2184 ) 2185 self._caption = fpost 2186 return fpost
Generate a flag post style element to describe an object.
Arguments:
- txt : (str) Text to display. The default is the filename or the object name.
- point : (list) position of the flag anchor point. The default is None.
- offset : (list) a 3D displacement or offset. The default is None.
- s : (float) size of the text to be shown
- c : (list) color of text and line
- bc : (list) color of the flag background
- alpha : (float) opacity of text and box.
- lw : (int) line with of box frame. The default is 0.
- font : (str)
font name. Use a monospace font for better rendering. The default is "Calco".
Type
vedo -r fonts
for a font demo. Check available fonts here. - justify : (str) internal text justification. The default is "center-left".
- vspacing : (float) vertical spacing between lines.
Examples:
2188 def caption( 2189 self, 2190 txt=None, 2191 point=None, 2192 size=(0.30, 0.15), 2193 padding=5, 2194 font="Calco", 2195 justify="center-right", 2196 vspacing=1.0, 2197 c=None, 2198 alpha=1.0, 2199 lw=1, 2200 ontop=True, 2201 ) -> Union["vtki.vtkCaptionActor2D", None]: 2202 """ 2203 Create a 2D caption to an object which follows the camera movements. 2204 Latex is not supported. Returns the same input object for concatenation. 2205 2206 See also `flagpole()`, `flagpost()`, `labels()` and `legend()` 2207 with similar functionality. 2208 2209 Arguments: 2210 txt : (str) 2211 text to be rendered. The default is the file name. 2212 point : (list) 2213 anchoring point. The default is None. 2214 size : (list) 2215 (width, height) of the caption box. The default is (0.30, 0.15). 2216 padding : (float) 2217 padding space of the caption box in pixels. The default is 5. 2218 font : (str) 2219 font name. Use a monospace font for better rendering. The default is "VictorMono". 2220 Type `vedo -r fonts` for a font demo. 2221 Check [available fonts here](https://vedo.embl.es/fonts). 2222 justify : (str) 2223 internal text justification. The default is "center-right". 2224 vspacing : (float) 2225 vertical spacing between lines. The default is 1. 2226 c : (str) 2227 text and box color. The default is 'lb'. 2228 alpha : (float) 2229 text and box transparency. The default is 1. 2230 lw : (int) 2231 line width in pixels. The default is 1. 2232 ontop : (bool) 2233 keep the 2d caption always on top. The default is True. 2234 2235 Examples: 2236 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 2237 2238  2239 2240 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 2241 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 2242 """ 2243 if txt is None: 2244 if self.filename: 2245 txt = self.filename.split("/")[-1] 2246 elif self.name: 2247 txt = self.name 2248 2249 if not txt: # disable it 2250 self._caption = None 2251 return None 2252 2253 for r in vedo.shapes._reps: 2254 txt = txt.replace(r[0], r[1]) 2255 2256 if c is None: 2257 c = np.array(self.properties.GetColor()) / 2 2258 else: 2259 c = colors.get_color(c) 2260 2261 if point is None: 2262 x0, x1, y0, y1, _, z1 = self.dataset.GetBounds() 2263 pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1] 2264 point = self.closest_point(pt) 2265 2266 capt = vtki.vtkCaptionActor2D() 2267 capt.SetAttachmentPoint(point) 2268 capt.SetBorder(True) 2269 capt.SetLeader(True) 2270 sph = vtki.new("SphereSource") 2271 sph.Update() 2272 capt.SetLeaderGlyphData(sph.GetOutput()) 2273 capt.SetMaximumLeaderGlyphSize(5) 2274 capt.SetPadding(int(padding)) 2275 capt.SetCaption(txt) 2276 capt.SetWidth(size[0]) 2277 capt.SetHeight(size[1]) 2278 capt.SetThreeDimensionalLeader(not ontop) 2279 2280 pra = capt.GetProperty() 2281 pra.SetColor(c) 2282 pra.SetOpacity(alpha) 2283 pra.SetLineWidth(lw) 2284 2285 pr = capt.GetCaptionTextProperty() 2286 pr.SetFontFamily(vtki.VTK_FONT_FILE) 2287 fl = utils.get_font_path(font) 2288 pr.SetFontFile(fl) 2289 pr.ShadowOff() 2290 pr.BoldOff() 2291 pr.FrameOff() 2292 pr.SetColor(c) 2293 pr.SetOpacity(alpha) 2294 pr.SetJustificationToLeft() 2295 if "top" in justify: 2296 pr.SetVerticalJustificationToTop() 2297 if "bottom" in justify: 2298 pr.SetVerticalJustificationToBottom() 2299 if "cent" in justify: 2300 pr.SetVerticalJustificationToCentered() 2301 pr.SetJustificationToCentered() 2302 if "left" in justify: 2303 pr.SetJustificationToLeft() 2304 if "right" in justify: 2305 pr.SetJustificationToRight() 2306 pr.SetLineSpacing(vspacing) 2307 return capt
Create a 2D caption to an object which follows the camera movements. Latex is not supported. Returns the same input object for concatenation.
See also flagpole()
, flagpost()
, labels()
and legend()
with similar functionality.
Arguments:
- txt : (str) text to be rendered. The default is the file name.
- point : (list) anchoring point. The default is None.
- size : (list) (width, height) of the caption box. The default is (0.30, 0.15).
- padding : (float) padding space of the caption box in pixels. The default is 5.
- font : (str)
font name. Use a monospace font for better rendering. The default is "VictorMono".
Type
vedo -r fonts
for a font demo. Check available fonts here. - justify : (str) internal text justification. The default is "center-right".
- vspacing : (float) vertical spacing between lines. The default is 1.
- c : (str) text and box color. The default is 'lb'.
- alpha : (float) text and box transparency. The default is 1.
- lw : (int) line width in pixels. The default is 1.
- ontop : (bool) keep the 2d caption always on top. The default is True.
Examples:
2650class VolumeVisual(CommonVisual): 2651 """Class to manage the visual aspects of a ``Volume`` object.""" 2652 2653 def __init__(self) -> None: 2654 # print("INIT VolumeVisual") 2655 super().__init__() 2656 2657 def alpha_unit(self, u=None) -> Union[Self, float]: 2658 """ 2659 Defines light attenuation per unit length. Default is 1. 2660 The larger the unit length, the further light has to travel to attenuate the same amount. 2661 2662 E.g., if you set the unit distance to 0, you will get full opacity. 2663 It means that when light travels 0 distance it's already attenuated a finite amount. 2664 Thus, any finite distance should attenuate all light. 2665 The larger you make the unit distance, the more transparent the rendering becomes. 2666 """ 2667 if u is None: 2668 return self.properties.GetScalarOpacityUnitDistance() 2669 self.properties.SetScalarOpacityUnitDistance(u) 2670 return self 2671 2672 def alpha_gradient(self, alpha_grad, vmin=None, vmax=None) -> Self: 2673 """ 2674 Assign a set of tranparencies to a volume's gradient 2675 along the range of the scalar value. 2676 A single constant value can also be assigned. 2677 The gradient function is used to decrease the opacity 2678 in the "flat" regions of the volume while maintaining the opacity 2679 at the boundaries between material types. The gradient is measured 2680 as the amount by which the intensity changes over unit distance. 2681 2682 The format for alpha_grad is the same as for method `volume.alpha()`. 2683 """ 2684 if vmin is None: 2685 vmin, _ = self.dataset.GetScalarRange() 2686 if vmax is None: 2687 _, vmax = self.dataset.GetScalarRange() 2688 2689 if alpha_grad is None: 2690 self.properties.DisableGradientOpacityOn() 2691 return self 2692 2693 self.properties.DisableGradientOpacityOff() 2694 2695 gotf = self.properties.GetGradientOpacity() 2696 if utils.is_sequence(alpha_grad): 2697 alpha_grad = np.array(alpha_grad) 2698 if len(alpha_grad.shape) == 1: # user passing a flat list e.g. (0.0, 0.3, 0.9, 1) 2699 for i, al in enumerate(alpha_grad): 2700 xalpha = vmin + (vmax - vmin) * i / (len(alpha_grad) - 1) 2701 # Create transfer mapping scalar value to gradient opacity 2702 gotf.AddPoint(xalpha, al) 2703 elif len(alpha_grad.shape) == 2: # user passing [(x0,alpha0), ...] 2704 gotf.AddPoint(vmin, alpha_grad[0][1]) 2705 for xalpha, al in alpha_grad: 2706 # Create transfer mapping scalar value to opacity 2707 gotf.AddPoint(xalpha, al) 2708 gotf.AddPoint(vmax, alpha_grad[-1][1]) 2709 # print("alpha_grad at", round(xalpha, 1), "\tset to", al) 2710 else: 2711 gotf.AddPoint(vmin, alpha_grad) # constant alpha_grad 2712 gotf.AddPoint(vmax, alpha_grad) 2713 return self 2714 2715 def cmap(self, c, alpha=None, vmin=None, vmax=None) -> Self: 2716 """Same as `color()`. 2717 2718 Arguments: 2719 alpha : (list) 2720 use a list to specify transparencies along the scalar range 2721 vmin : (float) 2722 force the min of the scalar range to be this value 2723 vmax : (float) 2724 force the max of the scalar range to be this value 2725 """ 2726 return self.color(c, alpha, vmin, vmax) 2727 2728 def jittering(self, status=None) -> Union[Self, bool]: 2729 """ 2730 If `True`, each ray traversal direction will be perturbed slightly 2731 using a noise-texture to get rid of wood-grain effects. 2732 """ 2733 if hasattr(self.mapper, "SetUseJittering"): # tetmesh doesnt have it 2734 if status is None: 2735 return self.mapper.GetUseJittering() 2736 self.mapper.SetUseJittering(status) 2737 return self 2738 2739 def hide_voxels(self, ids) -> Self: 2740 """ 2741 Hide voxels (cells) from visualization. 2742 2743 Example: 2744 ```python 2745 from vedo import * 2746 embryo = Volume(dataurl+'embryo.tif') 2747 embryo.hide_voxels(list(range(400000))) 2748 show(embryo, axes=1).close() 2749 ``` 2750 2751 See also: 2752 `volume.mask()` 2753 """ 2754 ghost_mask = np.zeros(self.ncells, dtype=np.uint8) 2755 ghost_mask[ids] = vtki.vtkDataSetAttributes.HIDDENCELL 2756 name = vtki.vtkDataSetAttributes.GhostArrayName() 2757 garr = utils.numpy2vtk(ghost_mask, name=name, dtype=np.uint8) 2758 self.dataset.GetCellData().AddArray(garr) 2759 self.dataset.GetCellData().Modified() 2760 return self 2761 2762 def mask(self, data) -> Self: 2763 """ 2764 Mask a volume visualization with a binary value. 2765 Needs to specify `volume.mapper = "gpu"`. 2766 2767 Example: 2768 ```python 2769 from vedo import np, Volume, show 2770 data_matrix = np.zeros([75, 75, 75], dtype=np.uint8) 2771 # all voxels have value zero except: 2772 data_matrix[ 0:35, 0:35, 0:35] = 1 2773 data_matrix[35:55, 35:55, 35:55] = 2 2774 data_matrix[55:74, 55:74, 55:74] = 3 2775 vol = Volume(data_matrix).cmap('Blues') 2776 vol.mapper = "gpu" 2777 data_mask = np.zeros_like(data_matrix) 2778 data_mask[10:65, 10:60, 20:70] = 1 2779 vol.mask(data_mask) 2780 show(vol, axes=1).close() 2781 ``` 2782 See also: 2783 `volume.hide_voxels()` 2784 """ 2785 rdata = data.astype(np.uint8).ravel(order="F") 2786 varr = utils.numpy2vtk(rdata, name="input_mask") 2787 2788 img = vtki.vtkImageData() 2789 img.SetDimensions(self.dimensions()) 2790 img.GetPointData().AddArray(varr) 2791 img.GetPointData().SetActiveScalars(varr.GetName()) 2792 2793 try: 2794 self.mapper.SetMaskTypeToBinary() 2795 self.mapper.SetMaskInput(img) 2796 except AttributeError: 2797 vedo.logger.error("volume.mask() must specify volume.mapper = 'gpu'") 2798 return self 2799 2800 2801 def mode(self, mode=None) -> Union[Self, int]: 2802 """ 2803 Define the volumetric rendering mode following this: 2804 - 0, composite rendering 2805 - 1, maximum projection rendering 2806 - 2, minimum projection rendering 2807 - 3, average projection rendering 2808 - 4, additive mode 2809 2810 The default mode is "composite" where the scalar values are sampled through 2811 the volume and composited in a front-to-back scheme through alpha blending. 2812 The final color and opacity is determined using the color and opacity transfer 2813 functions specified in alpha keyword. 2814 2815 Maximum and minimum intensity blend modes use the maximum and minimum 2816 scalar values, respectively, along the sampling ray. 2817 The final color and opacity is determined by passing the resultant value 2818 through the color and opacity transfer functions. 2819 2820 Additive blend mode accumulates scalar values by passing each value 2821 through the opacity transfer function and then adding up the product 2822 of the value and its opacity. In other words, the scalar values are scaled 2823 using the opacity transfer function and summed to derive the final color. 2824 Note that the resulting image is always grayscale i.e. aggregated values 2825 are not passed through the color transfer function. 2826 This is because the final value is a derived value and not a real data value 2827 along the sampling ray. 2828 2829 Average intensity blend mode works similar to the additive blend mode where 2830 the scalar values are multiplied by opacity calculated from the opacity 2831 transfer function and then added. 2832 The additional step here is to divide the sum by the number of samples 2833 taken through the volume. 2834 As is the case with the additive intensity projection, the final image will 2835 always be grayscale i.e. the aggregated values are not passed through the 2836 color transfer function. 2837 """ 2838 if mode is None: 2839 return self.mapper.GetBlendMode() 2840 2841 if isinstance(mode, str): 2842 if "comp" in mode: 2843 mode = 0 2844 elif "proj" in mode: 2845 if "max" in mode: 2846 mode = 1 2847 elif "min" in mode: 2848 mode = 2 2849 elif "ave" in mode: 2850 mode = 3 2851 else: 2852 vedo.logger.warning(f"unknown mode {mode}") 2853 mode = 0 2854 elif "add" in mode: 2855 mode = 4 2856 else: 2857 vedo.logger.warning(f"unknown mode {mode}") 2858 mode = 0 2859 2860 self.mapper.SetBlendMode(mode) 2861 return self 2862 2863 def shade(self, status=None) -> Union[Self, bool]: 2864 """ 2865 Set/Get the shading of a Volume. 2866 Shading can be further controlled with `volume.lighting()` method. 2867 2868 If shading is turned on, the mapper may perform shading calculations. 2869 In some cases shading does not apply 2870 (for example, in maximum intensity projection mode). 2871 """ 2872 if status is None: 2873 return self.properties.GetShade() 2874 self.properties.SetShade(status) 2875 return self 2876 2877 def interpolation(self, itype) -> Self: 2878 """ 2879 Set interpolation type. 2880 2881 0=nearest neighbour, 1=linear 2882 """ 2883 self.properties.SetInterpolationType(itype) 2884 return self
Class to manage the visual aspects of a Volume
object.
2657 def alpha_unit(self, u=None) -> Union[Self, float]: 2658 """ 2659 Defines light attenuation per unit length. Default is 1. 2660 The larger the unit length, the further light has to travel to attenuate the same amount. 2661 2662 E.g., if you set the unit distance to 0, you will get full opacity. 2663 It means that when light travels 0 distance it's already attenuated a finite amount. 2664 Thus, any finite distance should attenuate all light. 2665 The larger you make the unit distance, the more transparent the rendering becomes. 2666 """ 2667 if u is None: 2668 return self.properties.GetScalarOpacityUnitDistance() 2669 self.properties.SetScalarOpacityUnitDistance(u) 2670 return self
Defines light attenuation per unit length. Default is 1. The larger the unit length, the further light has to travel to attenuate the same amount.
E.g., if you set the unit distance to 0, you will get full opacity. It means that when light travels 0 distance it's already attenuated a finite amount. Thus, any finite distance should attenuate all light. The larger you make the unit distance, the more transparent the rendering becomes.
2672 def alpha_gradient(self, alpha_grad, vmin=None, vmax=None) -> Self: 2673 """ 2674 Assign a set of tranparencies to a volume's gradient 2675 along the range of the scalar value. 2676 A single constant value can also be assigned. 2677 The gradient function is used to decrease the opacity 2678 in the "flat" regions of the volume while maintaining the opacity 2679 at the boundaries between material types. The gradient is measured 2680 as the amount by which the intensity changes over unit distance. 2681 2682 The format for alpha_grad is the same as for method `volume.alpha()`. 2683 """ 2684 if vmin is None: 2685 vmin, _ = self.dataset.GetScalarRange() 2686 if vmax is None: 2687 _, vmax = self.dataset.GetScalarRange() 2688 2689 if alpha_grad is None: 2690 self.properties.DisableGradientOpacityOn() 2691 return self 2692 2693 self.properties.DisableGradientOpacityOff() 2694 2695 gotf = self.properties.GetGradientOpacity() 2696 if utils.is_sequence(alpha_grad): 2697 alpha_grad = np.array(alpha_grad) 2698 if len(alpha_grad.shape) == 1: # user passing a flat list e.g. (0.0, 0.3, 0.9, 1) 2699 for i, al in enumerate(alpha_grad): 2700 xalpha = vmin + (vmax - vmin) * i / (len(alpha_grad) - 1) 2701 # Create transfer mapping scalar value to gradient opacity 2702 gotf.AddPoint(xalpha, al) 2703 elif len(alpha_grad.shape) == 2: # user passing [(x0,alpha0), ...] 2704 gotf.AddPoint(vmin, alpha_grad[0][1]) 2705 for xalpha, al in alpha_grad: 2706 # Create transfer mapping scalar value to opacity 2707 gotf.AddPoint(xalpha, al) 2708 gotf.AddPoint(vmax, alpha_grad[-1][1]) 2709 # print("alpha_grad at", round(xalpha, 1), "\tset to", al) 2710 else: 2711 gotf.AddPoint(vmin, alpha_grad) # constant alpha_grad 2712 gotf.AddPoint(vmax, alpha_grad) 2713 return self
Assign a set of tranparencies to a volume's gradient along the range of the scalar value. A single constant value can also be assigned. The gradient function is used to decrease the opacity in the "flat" regions of the volume while maintaining the opacity at the boundaries between material types. The gradient is measured as the amount by which the intensity changes over unit distance.
The format for alpha_grad is the same as for method volume.alpha()
.
2715 def cmap(self, c, alpha=None, vmin=None, vmax=None) -> Self: 2716 """Same as `color()`. 2717 2718 Arguments: 2719 alpha : (list) 2720 use a list to specify transparencies along the scalar range 2721 vmin : (float) 2722 force the min of the scalar range to be this value 2723 vmax : (float) 2724 force the max of the scalar range to be this value 2725 """ 2726 return self.color(c, alpha, vmin, vmax)
Same as color()
.
Arguments:
- alpha : (list) use a list to specify transparencies along the scalar range
- vmin : (float) force the min of the scalar range to be this value
- vmax : (float) force the max of the scalar range to be this value
2728 def jittering(self, status=None) -> Union[Self, bool]: 2729 """ 2730 If `True`, each ray traversal direction will be perturbed slightly 2731 using a noise-texture to get rid of wood-grain effects. 2732 """ 2733 if hasattr(self.mapper, "SetUseJittering"): # tetmesh doesnt have it 2734 if status is None: 2735 return self.mapper.GetUseJittering() 2736 self.mapper.SetUseJittering(status) 2737 return self
If True
, each ray traversal direction will be perturbed slightly
using a noise-texture to get rid of wood-grain effects.
2739 def hide_voxels(self, ids) -> Self: 2740 """ 2741 Hide voxels (cells) from visualization. 2742 2743 Example: 2744 ```python 2745 from vedo import * 2746 embryo = Volume(dataurl+'embryo.tif') 2747 embryo.hide_voxels(list(range(400000))) 2748 show(embryo, axes=1).close() 2749 ``` 2750 2751 See also: 2752 `volume.mask()` 2753 """ 2754 ghost_mask = np.zeros(self.ncells, dtype=np.uint8) 2755 ghost_mask[ids] = vtki.vtkDataSetAttributes.HIDDENCELL 2756 name = vtki.vtkDataSetAttributes.GhostArrayName() 2757 garr = utils.numpy2vtk(ghost_mask, name=name, dtype=np.uint8) 2758 self.dataset.GetCellData().AddArray(garr) 2759 self.dataset.GetCellData().Modified() 2760 return self
Hide voxels (cells) from visualization.
Example:
from vedo import * embryo = Volume(dataurl+'embryo.tif') embryo.hide_voxels(list(range(400000))) show(embryo, axes=1).close()
See also:
volume.mask()
2762 def mask(self, data) -> Self: 2763 """ 2764 Mask a volume visualization with a binary value. 2765 Needs to specify `volume.mapper = "gpu"`. 2766 2767 Example: 2768 ```python 2769 from vedo import np, Volume, show 2770 data_matrix = np.zeros([75, 75, 75], dtype=np.uint8) 2771 # all voxels have value zero except: 2772 data_matrix[ 0:35, 0:35, 0:35] = 1 2773 data_matrix[35:55, 35:55, 35:55] = 2 2774 data_matrix[55:74, 55:74, 55:74] = 3 2775 vol = Volume(data_matrix).cmap('Blues') 2776 vol.mapper = "gpu" 2777 data_mask = np.zeros_like(data_matrix) 2778 data_mask[10:65, 10:60, 20:70] = 1 2779 vol.mask(data_mask) 2780 show(vol, axes=1).close() 2781 ``` 2782 See also: 2783 `volume.hide_voxels()` 2784 """ 2785 rdata = data.astype(np.uint8).ravel(order="F") 2786 varr = utils.numpy2vtk(rdata, name="input_mask") 2787 2788 img = vtki.vtkImageData() 2789 img.SetDimensions(self.dimensions()) 2790 img.GetPointData().AddArray(varr) 2791 img.GetPointData().SetActiveScalars(varr.GetName()) 2792 2793 try: 2794 self.mapper.SetMaskTypeToBinary() 2795 self.mapper.SetMaskInput(img) 2796 except AttributeError: 2797 vedo.logger.error("volume.mask() must specify volume.mapper = 'gpu'") 2798 return self
Mask a volume visualization with a binary value.
Needs to specify volume.mapper = "gpu"
.
Example:
from vedo import np, Volume, show
data_matrix = np.zeros([75, 75, 75], dtype=np.uint8)
# all voxels have value zero except:
data_matrix[ 0:35, 0:35, 0:35] = 1
data_matrix[35:55, 35:55, 35:55] = 2
data_matrix[55:74, 55:74, 55:74] = 3
vol = Volume(data_matrix).cmap('Blues')
vol.mapper = "gpu"
data_mask = np.zeros_like(data_matrix)
data_mask[10:65, 10:60, 20:70] = 1
vol.mask(data_mask)
show(vol, axes=1).close()
See also:
volume.hide_voxels()
2801 def mode(self, mode=None) -> Union[Self, int]: 2802 """ 2803 Define the volumetric rendering mode following this: 2804 - 0, composite rendering 2805 - 1, maximum projection rendering 2806 - 2, minimum projection rendering 2807 - 3, average projection rendering 2808 - 4, additive mode 2809 2810 The default mode is "composite" where the scalar values are sampled through 2811 the volume and composited in a front-to-back scheme through alpha blending. 2812 The final color and opacity is determined using the color and opacity transfer 2813 functions specified in alpha keyword. 2814 2815 Maximum and minimum intensity blend modes use the maximum and minimum 2816 scalar values, respectively, along the sampling ray. 2817 The final color and opacity is determined by passing the resultant value 2818 through the color and opacity transfer functions. 2819 2820 Additive blend mode accumulates scalar values by passing each value 2821 through the opacity transfer function and then adding up the product 2822 of the value and its opacity. In other words, the scalar values are scaled 2823 using the opacity transfer function and summed to derive the final color. 2824 Note that the resulting image is always grayscale i.e. aggregated values 2825 are not passed through the color transfer function. 2826 This is because the final value is a derived value and not a real data value 2827 along the sampling ray. 2828 2829 Average intensity blend mode works similar to the additive blend mode where 2830 the scalar values are multiplied by opacity calculated from the opacity 2831 transfer function and then added. 2832 The additional step here is to divide the sum by the number of samples 2833 taken through the volume. 2834 As is the case with the additive intensity projection, the final image will 2835 always be grayscale i.e. the aggregated values are not passed through the 2836 color transfer function. 2837 """ 2838 if mode is None: 2839 return self.mapper.GetBlendMode() 2840 2841 if isinstance(mode, str): 2842 if "comp" in mode: 2843 mode = 0 2844 elif "proj" in mode: 2845 if "max" in mode: 2846 mode = 1 2847 elif "min" in mode: 2848 mode = 2 2849 elif "ave" in mode: 2850 mode = 3 2851 else: 2852 vedo.logger.warning(f"unknown mode {mode}") 2853 mode = 0 2854 elif "add" in mode: 2855 mode = 4 2856 else: 2857 vedo.logger.warning(f"unknown mode {mode}") 2858 mode = 0 2859 2860 self.mapper.SetBlendMode(mode) 2861 return self
Define the volumetric rendering mode following this:
- 0, composite rendering
- 1, maximum projection rendering
- 2, minimum projection rendering
- 3, average projection rendering
- 4, additive mode
The default mode is "composite" where the scalar values are sampled through the volume and composited in a front-to-back scheme through alpha blending. The final color and opacity is determined using the color and opacity transfer functions specified in alpha keyword.
Maximum and minimum intensity blend modes use the maximum and minimum scalar values, respectively, along the sampling ray. The final color and opacity is determined by passing the resultant value through the color and opacity transfer functions.
Additive blend mode accumulates scalar values by passing each value through the opacity transfer function and then adding up the product of the value and its opacity. In other words, the scalar values are scaled using the opacity transfer function and summed to derive the final color. Note that the resulting image is always grayscale i.e. aggregated values are not passed through the color transfer function. This is because the final value is a derived value and not a real data value along the sampling ray.
Average intensity blend mode works similar to the additive blend mode where the scalar values are multiplied by opacity calculated from the opacity transfer function and then added. The additional step here is to divide the sum by the number of samples taken through the volume. As is the case with the additive intensity projection, the final image will always be grayscale i.e. the aggregated values are not passed through the color transfer function.
2863 def shade(self, status=None) -> Union[Self, bool]: 2864 """ 2865 Set/Get the shading of a Volume. 2866 Shading can be further controlled with `volume.lighting()` method. 2867 2868 If shading is turned on, the mapper may perform shading calculations. 2869 In some cases shading does not apply 2870 (for example, in maximum intensity projection mode). 2871 """ 2872 if status is None: 2873 return self.properties.GetShade() 2874 self.properties.SetShade(status) 2875 return self
Set/Get the shading of a Volume.
Shading can be further controlled with volume.lighting()
method.
If shading is turned on, the mapper may perform shading calculations. In some cases shading does not apply (for example, in maximum intensity projection mode).
2311class MeshVisual(PointsVisual): 2312 """Class to manage the visual aspects of a ``Maesh`` object.""" 2313 2314 def __init__(self) -> None: 2315 # print("INIT MeshVisual", super()) 2316 super().__init__() 2317 2318 def follow_camera(self, camera=None, origin=None) -> Self: 2319 """ 2320 Return an object that will follow camera movements and stay locked to it. 2321 Use `mesh.follow_camera(False)` to disable it. 2322 2323 A `vtkCamera` object can also be passed. 2324 """ 2325 if camera is False: 2326 try: 2327 self.SetCamera(None) 2328 return self 2329 except AttributeError: 2330 return self 2331 2332 factor = vtki.vtkFollower() 2333 factor.SetMapper(self.mapper) 2334 factor.SetProperty(self.properties) 2335 factor.SetBackfaceProperty(self.actor.GetBackfaceProperty()) 2336 factor.SetTexture(self.actor.GetTexture()) 2337 factor.SetScale(self.actor.GetScale()) 2338 # factor.SetOrientation(self.actor.GetOrientation()) 2339 factor.SetPosition(self.actor.GetPosition()) 2340 factor.SetUseBounds(self.actor.GetUseBounds()) 2341 2342 if origin is None: 2343 factor.SetOrigin(self.actor.GetOrigin()) 2344 else: 2345 factor.SetOrigin(origin) 2346 2347 factor.PickableOff() 2348 2349 if isinstance(camera, vtki.vtkCamera): 2350 factor.SetCamera(camera) 2351 else: 2352 plt = vedo.plotter_instance 2353 if plt and plt.renderer and plt.renderer.GetActiveCamera(): 2354 factor.SetCamera(plt.renderer.GetActiveCamera()) 2355 2356 self.actor = None 2357 factor.retrieve_object = weak_ref_to(self) 2358 self.actor = factor 2359 return self 2360 2361 def wireframe(self, value=True) -> Self: 2362 """Set mesh's representation as wireframe or solid surface.""" 2363 if value: 2364 self.properties.SetRepresentationToWireframe() 2365 else: 2366 self.properties.SetRepresentationToSurface() 2367 return self 2368 2369 def flat(self) -> Self: 2370 """Set surface interpolation to flat. 2371 2372 <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" width="700"> 2373 """ 2374 self.properties.SetInterpolationToFlat() 2375 return self 2376 2377 def phong(self) -> Self: 2378 """Set surface interpolation to "phong".""" 2379 self.properties.SetInterpolationToPhong() 2380 return self 2381 2382 def backface_culling(self, value=True) -> Self: 2383 """Set culling of polygons based on orientation of normal with respect to camera.""" 2384 self.properties.SetBackfaceCulling(value) 2385 return self 2386 2387 def render_lines_as_tubes(self, value=True) -> Self: 2388 """Wrap a fake tube around a simple line for visualization""" 2389 self.properties.SetRenderLinesAsTubes(value) 2390 return self 2391 2392 def frontface_culling(self, value=True) -> Self: 2393 """Set culling of polygons based on orientation of normal with respect to camera.""" 2394 self.properties.SetFrontfaceCulling(value) 2395 return self 2396 2397 def backcolor(self, bc=None) -> Union[Self, np.ndarray]: 2398 """ 2399 Set/get mesh's backface color. 2400 """ 2401 back_prop = self.actor.GetBackfaceProperty() 2402 2403 if bc is None: 2404 if back_prop: 2405 return back_prop.GetDiffuseColor() 2406 return self 2407 2408 if self.properties.GetOpacity() < 1: 2409 return self 2410 2411 if not back_prop: 2412 back_prop = vtki.vtkProperty() 2413 2414 back_prop.SetDiffuseColor(colors.get_color(bc)) 2415 back_prop.SetOpacity(self.properties.GetOpacity()) 2416 self.actor.SetBackfaceProperty(back_prop) 2417 self.mapper.ScalarVisibilityOff() 2418 return self 2419 2420 def bc(self, backcolor=False) -> Union[Self, np.ndarray]: 2421 """Shortcut for `mesh.backcolor()`.""" 2422 return self.backcolor(backcolor) 2423 2424 def linewidth(self, lw=None) -> Union[Self, int]: 2425 """Set/get width of mesh edges. Same as `lw()`.""" 2426 if lw is not None: 2427 if lw == 0: 2428 self.properties.EdgeVisibilityOff() 2429 self.properties.SetRepresentationToSurface() 2430 return self 2431 self.properties.EdgeVisibilityOn() 2432 self.properties.SetLineWidth(lw) 2433 else: 2434 return self.properties.GetLineWidth() 2435 return self 2436 2437 def lw(self, linewidth=None) -> Union[Self, int]: 2438 """Set/get width of mesh edges. Same as `linewidth()`.""" 2439 return self.linewidth(linewidth) 2440 2441 def linecolor(self, lc=None) -> Union[Self, np.ndarray]: 2442 """Set/get color of mesh edges. Same as `lc()`.""" 2443 if lc is None: 2444 return np.array(self.properties.GetEdgeColor()) 2445 self.properties.EdgeVisibilityOn() 2446 self.properties.SetEdgeColor(colors.get_color(lc)) 2447 return self 2448 2449 def lc(self, linecolor=None) -> Union[Self, np.ndarray]: 2450 """Set/get color of mesh edges. Same as `linecolor()`.""" 2451 return self.linecolor(linecolor) 2452 2453 def texture( 2454 self, 2455 tname, 2456 tcoords=None, 2457 interpolate=True, 2458 repeat=True, 2459 edge_clamp=False, 2460 scale=None, 2461 ushift=None, 2462 vshift=None, 2463 ) -> Self: 2464 """ 2465 Assign a texture to mesh from image file or predefined texture `tname`. 2466 If tname is set to `None` texture is disabled. 2467 Input tname can also be an array or a `vtkTexture`. 2468 2469 Arguments: 2470 tname : (numpy.array, str, Image, vtkTexture, None) 2471 the input texture to be applied. Can be a numpy array, a path to an image file, 2472 a vedo Image. The None value disables texture. 2473 tcoords : (numpy.array, str) 2474 this is the (u,v) texture coordinate array. Can also be a string of an existing array 2475 in the mesh. 2476 interpolate : (bool) 2477 turn on/off linear interpolation of the texture map when rendering. 2478 repeat : (bool) 2479 repeat of the texture when tcoords extend beyond the [0,1] range. 2480 edge_clamp : (bool) 2481 turn on/off the clamping of the texture map when 2482 the texture coords extend beyond the [0,1] range. 2483 Only used when repeat is False, and edge clamping is supported by the graphics card. 2484 scale : (bool) 2485 scale the texture image by this factor 2486 ushift : (bool) 2487 shift u-coordinates of texture by this amount 2488 vshift : (bool) 2489 shift v-coordinates of texture by this amount 2490 2491 Examples: 2492 - [texturecubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/texturecubes.py) 2493 2494  2495 """ 2496 pd = self.dataset 2497 out_img = None 2498 2499 if tname is None: # disable texture 2500 pd.GetPointData().SetTCoords(None) 2501 pd.GetPointData().Modified() 2502 return self ###################################### 2503 2504 if isinstance(tname, vtki.vtkTexture): 2505 tu = tname 2506 2507 elif isinstance(tname, vedo.Image): 2508 tu = vtki.vtkTexture() 2509 out_img = tname.dataset 2510 2511 elif utils.is_sequence(tname): 2512 tu = vtki.vtkTexture() 2513 out_img = vedo.image._get_img(tname) 2514 2515 elif isinstance(tname, str): 2516 tu = vtki.vtkTexture() 2517 2518 if "https://" in tname: 2519 try: 2520 tname = vedo.file_io.download(tname, verbose=False) 2521 except: 2522 vedo.logger.error(f"texture {tname} could not be downloaded") 2523 return self 2524 2525 fn = tname + ".jpg" 2526 if os.path.exists(tname): 2527 fn = tname 2528 else: 2529 vedo.logger.error(f"texture file {tname} does not exist") 2530 return self 2531 2532 fnl = fn.lower() 2533 if ".jpg" in fnl or ".jpeg" in fnl: 2534 reader = vtki.new("JPEGReader") 2535 elif ".png" in fnl: 2536 reader = vtki.new("PNGReader") 2537 elif ".bmp" in fnl: 2538 reader = vtki.new("BMPReader") 2539 else: 2540 vedo.logger.error("in texture() supported files are only PNG, BMP or JPG") 2541 return self 2542 reader.SetFileName(fn) 2543 reader.Update() 2544 out_img = reader.GetOutput() 2545 2546 else: 2547 vedo.logger.error(f"in texture() cannot understand input {type(tname)}") 2548 return self 2549 2550 if tcoords is not None: 2551 2552 if isinstance(tcoords, str): 2553 vtarr = pd.GetPointData().GetArray(tcoords) 2554 2555 else: 2556 tcoords = np.asarray(tcoords) 2557 if tcoords.ndim != 2: 2558 vedo.logger.error("tcoords must be a 2-dimensional array") 2559 return self 2560 if tcoords.shape[0] != pd.GetNumberOfPoints(): 2561 vedo.logger.error("nr of texture coords must match nr of points") 2562 return self 2563 if tcoords.shape[1] != 2: 2564 vedo.logger.error("tcoords texture vector must have 2 components") 2565 vtarr = utils.numpy2vtk(tcoords) 2566 vtarr.SetName("TCoordinates") 2567 2568 pd.GetPointData().SetTCoords(vtarr) 2569 pd.GetPointData().Modified() 2570 2571 elif not pd.GetPointData().GetTCoords(): 2572 2573 # TCoords still void.. 2574 # check that there are no texture-like arrays: 2575 names = self.pointdata.keys() 2576 candidate_arr = "" 2577 for name in names: 2578 vtarr = pd.GetPointData().GetArray(name) 2579 if vtarr.GetNumberOfComponents() != 2: 2580 continue 2581 t0, t1 = vtarr.GetRange() 2582 if t0 >= 0 and t1 <= 1: 2583 candidate_arr = name 2584 2585 if candidate_arr: 2586 2587 vtarr = pd.GetPointData().GetArray(candidate_arr) 2588 pd.GetPointData().SetTCoords(vtarr) 2589 pd.GetPointData().Modified() 2590 2591 else: 2592 # last resource is automatic mapping 2593 tmapper = vtki.new("TextureMapToPlane") 2594 tmapper.AutomaticPlaneGenerationOn() 2595 tmapper.SetInputData(pd) 2596 tmapper.Update() 2597 tc = tmapper.GetOutput().GetPointData().GetTCoords() 2598 if scale or ushift or vshift: 2599 ntc = utils.vtk2numpy(tc) 2600 if scale: 2601 ntc *= scale 2602 if ushift: 2603 ntc[:, 0] += ushift 2604 if vshift: 2605 ntc[:, 1] += vshift 2606 tc = utils.numpy2vtk(tc) 2607 pd.GetPointData().SetTCoords(tc) 2608 pd.GetPointData().Modified() 2609 2610 if out_img: 2611 tu.SetInputData(out_img) 2612 tu.SetInterpolate(interpolate) 2613 tu.SetRepeat(repeat) 2614 tu.SetEdgeClamp(edge_clamp) 2615 2616 self.properties.SetColor(1, 1, 1) 2617 self.mapper.ScalarVisibilityOff() 2618 self.actor.SetTexture(tu) 2619 2620 # if seam_threshold is not None: 2621 # tname = self.dataset.GetPointData().GetTCoords().GetName() 2622 # grad = self.gradient(tname) 2623 # ugrad, vgrad = np.split(grad, 2, axis=1) 2624 # ugradm, vgradm = utils.mag2(ugrad), utils.mag2(vgrad) 2625 # gradm = np.log(ugradm + vgradm) 2626 # largegrad_ids = np.arange(len(grad))[gradm > seam_threshold * 4] 2627 # uvmap = self.pointdata[tname] 2628 # # collapse triangles that have large gradient 2629 # new_points = self.points.copy() 2630 # for f in self.cells: 2631 # if np.isin(f, largegrad_ids).all(): 2632 # id1, id2, id3 = f 2633 # uv1, uv2, uv3 = uvmap[f] 2634 # d12 = utils.mag2(uv1 - uv2) 2635 # d23 = utils.mag2(uv2 - uv3) 2636 # d31 = utils.mag2(uv3 - uv1) 2637 # idm = np.argmin([d12, d23, d31]) 2638 # if idm == 0: 2639 # new_points[id1] = new_points[id3] 2640 # new_points[id2] = new_points[id3] 2641 # elif idm == 1: 2642 # new_points[id2] = new_points[id1] 2643 # new_points[id3] = new_points[id1] 2644 # self.points = new_points 2645 2646 self.dataset.Modified() 2647 return self
Class to manage the visual aspects of a Maesh
object.
2318 def follow_camera(self, camera=None, origin=None) -> Self: 2319 """ 2320 Return an object that will follow camera movements and stay locked to it. 2321 Use `mesh.follow_camera(False)` to disable it. 2322 2323 A `vtkCamera` object can also be passed. 2324 """ 2325 if camera is False: 2326 try: 2327 self.SetCamera(None) 2328 return self 2329 except AttributeError: 2330 return self 2331 2332 factor = vtki.vtkFollower() 2333 factor.SetMapper(self.mapper) 2334 factor.SetProperty(self.properties) 2335 factor.SetBackfaceProperty(self.actor.GetBackfaceProperty()) 2336 factor.SetTexture(self.actor.GetTexture()) 2337 factor.SetScale(self.actor.GetScale()) 2338 # factor.SetOrientation(self.actor.GetOrientation()) 2339 factor.SetPosition(self.actor.GetPosition()) 2340 factor.SetUseBounds(self.actor.GetUseBounds()) 2341 2342 if origin is None: 2343 factor.SetOrigin(self.actor.GetOrigin()) 2344 else: 2345 factor.SetOrigin(origin) 2346 2347 factor.PickableOff() 2348 2349 if isinstance(camera, vtki.vtkCamera): 2350 factor.SetCamera(camera) 2351 else: 2352 plt = vedo.plotter_instance 2353 if plt and plt.renderer and plt.renderer.GetActiveCamera(): 2354 factor.SetCamera(plt.renderer.GetActiveCamera()) 2355 2356 self.actor = None 2357 factor.retrieve_object = weak_ref_to(self) 2358 self.actor = factor 2359 return self
Return an object that will follow camera movements and stay locked to it.
Use mesh.follow_camera(False)
to disable it.
A vtkCamera
object can also be passed.
2361 def wireframe(self, value=True) -> Self: 2362 """Set mesh's representation as wireframe or solid surface.""" 2363 if value: 2364 self.properties.SetRepresentationToWireframe() 2365 else: 2366 self.properties.SetRepresentationToSurface() 2367 return self
Set mesh's representation as wireframe or solid surface.
2369 def flat(self) -> Self: 2370 """Set surface interpolation to flat. 2371 2372 <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" width="700"> 2373 """ 2374 self.properties.SetInterpolationToFlat() 2375 return self
Set surface interpolation to flat.
2377 def phong(self) -> Self: 2378 """Set surface interpolation to "phong".""" 2379 self.properties.SetInterpolationToPhong() 2380 return self
Set surface interpolation to "phong".
2382 def backface_culling(self, value=True) -> Self: 2383 """Set culling of polygons based on orientation of normal with respect to camera.""" 2384 self.properties.SetBackfaceCulling(value) 2385 return self
Set culling of polygons based on orientation of normal with respect to camera.
2387 def render_lines_as_tubes(self, value=True) -> Self: 2388 """Wrap a fake tube around a simple line for visualization""" 2389 self.properties.SetRenderLinesAsTubes(value) 2390 return self
Wrap a fake tube around a simple line for visualization
2392 def frontface_culling(self, value=True) -> Self: 2393 """Set culling of polygons based on orientation of normal with respect to camera.""" 2394 self.properties.SetFrontfaceCulling(value) 2395 return self
Set culling of polygons based on orientation of normal with respect to camera.
2397 def backcolor(self, bc=None) -> Union[Self, np.ndarray]: 2398 """ 2399 Set/get mesh's backface color. 2400 """ 2401 back_prop = self.actor.GetBackfaceProperty() 2402 2403 if bc is None: 2404 if back_prop: 2405 return back_prop.GetDiffuseColor() 2406 return self 2407 2408 if self.properties.GetOpacity() < 1: 2409 return self 2410 2411 if not back_prop: 2412 back_prop = vtki.vtkProperty() 2413 2414 back_prop.SetDiffuseColor(colors.get_color(bc)) 2415 back_prop.SetOpacity(self.properties.GetOpacity()) 2416 self.actor.SetBackfaceProperty(back_prop) 2417 self.mapper.ScalarVisibilityOff() 2418 return self
Set/get mesh's backface color.
2420 def bc(self, backcolor=False) -> Union[Self, np.ndarray]: 2421 """Shortcut for `mesh.backcolor()`.""" 2422 return self.backcolor(backcolor)
Shortcut for mesh.backcolor()
.
2424 def linewidth(self, lw=None) -> Union[Self, int]: 2425 """Set/get width of mesh edges. Same as `lw()`.""" 2426 if lw is not None: 2427 if lw == 0: 2428 self.properties.EdgeVisibilityOff() 2429 self.properties.SetRepresentationToSurface() 2430 return self 2431 self.properties.EdgeVisibilityOn() 2432 self.properties.SetLineWidth(lw) 2433 else: 2434 return self.properties.GetLineWidth() 2435 return self
Set/get width of mesh edges. Same as lw()
.
2437 def lw(self, linewidth=None) -> Union[Self, int]: 2438 """Set/get width of mesh edges. Same as `linewidth()`.""" 2439 return self.linewidth(linewidth)
Set/get width of mesh edges. Same as linewidth()
.
2441 def linecolor(self, lc=None) -> Union[Self, np.ndarray]: 2442 """Set/get color of mesh edges. Same as `lc()`.""" 2443 if lc is None: 2444 return np.array(self.properties.GetEdgeColor()) 2445 self.properties.EdgeVisibilityOn() 2446 self.properties.SetEdgeColor(colors.get_color(lc)) 2447 return self
Set/get color of mesh edges. Same as lc()
.
2449 def lc(self, linecolor=None) -> Union[Self, np.ndarray]: 2450 """Set/get color of mesh edges. Same as `linecolor()`.""" 2451 return self.linecolor(linecolor)
Set/get color of mesh edges. Same as linecolor()
.
2453 def texture( 2454 self, 2455 tname, 2456 tcoords=None, 2457 interpolate=True, 2458 repeat=True, 2459 edge_clamp=False, 2460 scale=None, 2461 ushift=None, 2462 vshift=None, 2463 ) -> Self: 2464 """ 2465 Assign a texture to mesh from image file or predefined texture `tname`. 2466 If tname is set to `None` texture is disabled. 2467 Input tname can also be an array or a `vtkTexture`. 2468 2469 Arguments: 2470 tname : (numpy.array, str, Image, vtkTexture, None) 2471 the input texture to be applied. Can be a numpy array, a path to an image file, 2472 a vedo Image. The None value disables texture. 2473 tcoords : (numpy.array, str) 2474 this is the (u,v) texture coordinate array. Can also be a string of an existing array 2475 in the mesh. 2476 interpolate : (bool) 2477 turn on/off linear interpolation of the texture map when rendering. 2478 repeat : (bool) 2479 repeat of the texture when tcoords extend beyond the [0,1] range. 2480 edge_clamp : (bool) 2481 turn on/off the clamping of the texture map when 2482 the texture coords extend beyond the [0,1] range. 2483 Only used when repeat is False, and edge clamping is supported by the graphics card. 2484 scale : (bool) 2485 scale the texture image by this factor 2486 ushift : (bool) 2487 shift u-coordinates of texture by this amount 2488 vshift : (bool) 2489 shift v-coordinates of texture by this amount 2490 2491 Examples: 2492 - [texturecubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/texturecubes.py) 2493 2494  2495 """ 2496 pd = self.dataset 2497 out_img = None 2498 2499 if tname is None: # disable texture 2500 pd.GetPointData().SetTCoords(None) 2501 pd.GetPointData().Modified() 2502 return self ###################################### 2503 2504 if isinstance(tname, vtki.vtkTexture): 2505 tu = tname 2506 2507 elif isinstance(tname, vedo.Image): 2508 tu = vtki.vtkTexture() 2509 out_img = tname.dataset 2510 2511 elif utils.is_sequence(tname): 2512 tu = vtki.vtkTexture() 2513 out_img = vedo.image._get_img(tname) 2514 2515 elif isinstance(tname, str): 2516 tu = vtki.vtkTexture() 2517 2518 if "https://" in tname: 2519 try: 2520 tname = vedo.file_io.download(tname, verbose=False) 2521 except: 2522 vedo.logger.error(f"texture {tname} could not be downloaded") 2523 return self 2524 2525 fn = tname + ".jpg" 2526 if os.path.exists(tname): 2527 fn = tname 2528 else: 2529 vedo.logger.error(f"texture file {tname} does not exist") 2530 return self 2531 2532 fnl = fn.lower() 2533 if ".jpg" in fnl or ".jpeg" in fnl: 2534 reader = vtki.new("JPEGReader") 2535 elif ".png" in fnl: 2536 reader = vtki.new("PNGReader") 2537 elif ".bmp" in fnl: 2538 reader = vtki.new("BMPReader") 2539 else: 2540 vedo.logger.error("in texture() supported files are only PNG, BMP or JPG") 2541 return self 2542 reader.SetFileName(fn) 2543 reader.Update() 2544 out_img = reader.GetOutput() 2545 2546 else: 2547 vedo.logger.error(f"in texture() cannot understand input {type(tname)}") 2548 return self 2549 2550 if tcoords is not None: 2551 2552 if isinstance(tcoords, str): 2553 vtarr = pd.GetPointData().GetArray(tcoords) 2554 2555 else: 2556 tcoords = np.asarray(tcoords) 2557 if tcoords.ndim != 2: 2558 vedo.logger.error("tcoords must be a 2-dimensional array") 2559 return self 2560 if tcoords.shape[0] != pd.GetNumberOfPoints(): 2561 vedo.logger.error("nr of texture coords must match nr of points") 2562 return self 2563 if tcoords.shape[1] != 2: 2564 vedo.logger.error("tcoords texture vector must have 2 components") 2565 vtarr = utils.numpy2vtk(tcoords) 2566 vtarr.SetName("TCoordinates") 2567 2568 pd.GetPointData().SetTCoords(vtarr) 2569 pd.GetPointData().Modified() 2570 2571 elif not pd.GetPointData().GetTCoords(): 2572 2573 # TCoords still void.. 2574 # check that there are no texture-like arrays: 2575 names = self.pointdata.keys() 2576 candidate_arr = "" 2577 for name in names: 2578 vtarr = pd.GetPointData().GetArray(name) 2579 if vtarr.GetNumberOfComponents() != 2: 2580 continue 2581 t0, t1 = vtarr.GetRange() 2582 if t0 >= 0 and t1 <= 1: 2583 candidate_arr = name 2584 2585 if candidate_arr: 2586 2587 vtarr = pd.GetPointData().GetArray(candidate_arr) 2588 pd.GetPointData().SetTCoords(vtarr) 2589 pd.GetPointData().Modified() 2590 2591 else: 2592 # last resource is automatic mapping 2593 tmapper = vtki.new("TextureMapToPlane") 2594 tmapper.AutomaticPlaneGenerationOn() 2595 tmapper.SetInputData(pd) 2596 tmapper.Update() 2597 tc = tmapper.GetOutput().GetPointData().GetTCoords() 2598 if scale or ushift or vshift: 2599 ntc = utils.vtk2numpy(tc) 2600 if scale: 2601 ntc *= scale 2602 if ushift: 2603 ntc[:, 0] += ushift 2604 if vshift: 2605 ntc[:, 1] += vshift 2606 tc = utils.numpy2vtk(tc) 2607 pd.GetPointData().SetTCoords(tc) 2608 pd.GetPointData().Modified() 2609 2610 if out_img: 2611 tu.SetInputData(out_img) 2612 tu.SetInterpolate(interpolate) 2613 tu.SetRepeat(repeat) 2614 tu.SetEdgeClamp(edge_clamp) 2615 2616 self.properties.SetColor(1, 1, 1) 2617 self.mapper.ScalarVisibilityOff() 2618 self.actor.SetTexture(tu) 2619 2620 # if seam_threshold is not None: 2621 # tname = self.dataset.GetPointData().GetTCoords().GetName() 2622 # grad = self.gradient(tname) 2623 # ugrad, vgrad = np.split(grad, 2, axis=1) 2624 # ugradm, vgradm = utils.mag2(ugrad), utils.mag2(vgrad) 2625 # gradm = np.log(ugradm + vgradm) 2626 # largegrad_ids = np.arange(len(grad))[gradm > seam_threshold * 4] 2627 # uvmap = self.pointdata[tname] 2628 # # collapse triangles that have large gradient 2629 # new_points = self.points.copy() 2630 # for f in self.cells: 2631 # if np.isin(f, largegrad_ids).all(): 2632 # id1, id2, id3 = f 2633 # uv1, uv2, uv3 = uvmap[f] 2634 # d12 = utils.mag2(uv1 - uv2) 2635 # d23 = utils.mag2(uv2 - uv3) 2636 # d31 = utils.mag2(uv3 - uv1) 2637 # idm = np.argmin([d12, d23, d31]) 2638 # if idm == 0: 2639 # new_points[id1] = new_points[id3] 2640 # new_points[id2] = new_points[id3] 2641 # elif idm == 1: 2642 # new_points[id2] = new_points[id1] 2643 # new_points[id3] = new_points[id1] 2644 # self.points = new_points 2645 2646 self.dataset.Modified() 2647 return self
Assign a texture to mesh from image file or predefined texture tname
.
If tname is set to None
texture is disabled.
Input tname can also be an array or a vtkTexture
.
Arguments:
- tname : (numpy.array, str, Image, vtkTexture, None) the input texture to be applied. Can be a numpy array, a path to an image file, a vedo Image. The None value disables texture.
- tcoords : (numpy.array, str) this is the (u,v) texture coordinate array. Can also be a string of an existing array in the mesh.
- interpolate : (bool) turn on/off linear interpolation of the texture map when rendering.
- repeat : (bool) repeat of the texture when tcoords extend beyond the [0,1] range.
- edge_clamp : (bool) turn on/off the clamping of the texture map when the texture coords extend beyond the [0,1] range. Only used when repeat is False, and edge clamping is supported by the graphics card.
- scale : (bool) scale the texture image by this factor
- ushift : (bool) shift u-coordinates of texture by this amount
- vshift : (bool) shift v-coordinates of texture by this amount
Examples:
Inherited Members
2888class ImageVisual(CommonVisual, Actor3DHelper): 2889 2890 def __init__(self) -> None: 2891 # print("init ImageVisual") 2892 super().__init__() 2893 2894 def memory_size(self) -> int: 2895 """ 2896 Return the size in bytes of the object in memory. 2897 """ 2898 return self.dataset.GetActualMemorySize() 2899 2900 def scalar_range(self) -> np.ndarray: 2901 """ 2902 Return the scalar range of the image. 2903 """ 2904 return np.array(self.dataset.GetScalarRange()) 2905 2906 def alpha(self, a=None) -> Union[Self, float]: 2907 """Set/get image's transparency in the rendering scene.""" 2908 if a is not None: 2909 self.properties.SetOpacity(a) 2910 return self 2911 return self.properties.GetOpacity() 2912 2913 def level(self, value=None) -> Union[Self, float]: 2914 """Get/Set the image color level (brightness) in the rendering scene.""" 2915 if value is None: 2916 return self.properties.GetColorLevel() 2917 self.properties.SetColorLevel(value) 2918 return self 2919 2920 def window(self, value=None) -> Union[Self, float]: 2921 """Get/Set the image color window (contrast) in the rendering scene.""" 2922 if value is None: 2923 return self.properties.GetColorWindow() 2924 self.properties.SetColorWindow(value) 2925 return self
Class to manage the visual aspects common to all objects.
2894 def memory_size(self) -> int: 2895 """ 2896 Return the size in bytes of the object in memory. 2897 """ 2898 return self.dataset.GetActualMemorySize()
Return the size in bytes of the object in memory.
2900 def scalar_range(self) -> np.ndarray: 2901 """ 2902 Return the scalar range of the image. 2903 """ 2904 return np.array(self.dataset.GetScalarRange())
Return the scalar range of the image.
2906 def alpha(self, a=None) -> Union[Self, float]: 2907 """Set/get image's transparency in the rendering scene.""" 2908 if a is not None: 2909 self.properties.SetOpacity(a) 2910 return self 2911 return self.properties.GetOpacity()
Set/get image's transparency in the rendering scene.
2913 def level(self, value=None) -> Union[Self, float]: 2914 """Get/Set the image color level (brightness) in the rendering scene.""" 2915 if value is None: 2916 return self.properties.GetColorLevel() 2917 self.properties.SetColorLevel(value) 2918 return self
Get/Set the image color level (brightness) in the rendering scene.
2920 def window(self, value=None) -> Union[Self, float]: 2921 """Get/Set the image color window (contrast) in the rendering scene.""" 2922 if value is None: 2923 return self.properties.GetColorWindow() 2924 self.properties.SetColorWindow(value) 2925 return self
Get/Set the image color window (contrast) in the rendering scene.
Inherited Members
542class Actor2D(vtki.vtkActor2D): 543 """Wrapping of `vtkActor2D`.""" 544 545 def __init__(self): 546 """Manage 2D objects.""" 547 super().__init__() 548 549 self.dataset = None 550 self.properties = self.GetProperty() 551 self.name = "" 552 self.filename = "" 553 self.file_size = 0 554 self.pipeline = None 555 self.shape = [] # for images 556 557 @property 558 def mapper(self): 559 """Get the internal vtkMapper.""" 560 return self.GetMapper() 561 562 @mapper.setter 563 def mapper(self, amapper): 564 """Set the internal vtkMapper.""" 565 self.SetMapper(amapper) 566 567 def layer(self, value=None): 568 """Set/Get the layer number in the overlay planes into which to render.""" 569 if value is None: 570 return self.GetLayerNumber() 571 self.SetLayerNumber(value) 572 return self 573 574 def pos(self, px=None, py=None) -> Union[np.ndarray, Self]: 575 """Set/Get the screen-coordinate position.""" 576 if isinstance(px, str): 577 vedo.logger.error("Use string descriptors only inside the constructor") 578 return self 579 if px is None: 580 return np.array(self.GetPosition(), dtype=int) 581 if py is not None: 582 p = [px, py] 583 else: 584 p = px 585 assert len(p) == 2, "Error: len(pos) must be 2 for Actor2D" 586 self.SetPosition(p) 587 return self 588 589 def coordinate_system(self, value=None) -> Self: 590 """ 591 Set/get the coordinate system which this coordinate is defined in. 592 593 The options are: 594 0. Display 595 1. Normalized Display 596 2. Viewport 597 3. Normalized Viewport 598 4. View 599 5. Pose 600 6. World 601 """ 602 coor = self.GetPositionCoordinate() 603 if value is None: 604 return coor.GetCoordinateSystem() 605 coor.SetCoordinateSystem(value) 606 return self 607 608 def on(self) -> Self: 609 """Set object visibility.""" 610 self.VisibilityOn() 611 return self 612 613 def off(self) -> Self: 614 """Set object visibility.""" 615 self.VisibilityOn() 616 return self 617 618 def toggle(self) -> Self: 619 """Toggle object visibility.""" 620 self.SetVisibility(not self.GetVisibility()) 621 return self 622 623 def visibility(value=None) -> bool: 624 """Get/Set object visibility.""" 625 if value is not None: 626 self.SetVisibility(value) 627 return self.GetVisibility() 628 629 def pickable(self, value=True) -> Self: 630 """Set object pickability.""" 631 self.SetPickable(value) 632 return self 633 634 def color(self, value=None) -> Union[np.ndarray, Self]: 635 """Set/Get the object color.""" 636 if value is None: 637 return self.properties.GetColor() 638 self.properties.SetColor(colors.get_color(value)) 639 return self 640 641 def c(self, value=None) -> Union[np.ndarray, Self]: 642 """Set/Get the object color.""" 643 return self.color(value) 644 645 def alpha(self, value=None) -> Union[float, Self]: 646 """Set/Get the object opacity.""" 647 if value is None: 648 return self.properties.GetOpacity() 649 self.properties.SetOpacity(value) 650 return self 651 652 def ps(self, point_size=None) -> Union[int, Self]: 653 if point_size is None: 654 return self.properties.GetPointSize() 655 self.properties.SetPointSize(point_size) 656 return self 657 658 def lw(self, line_width=None) -> Union[int, Self]: 659 if line_width is None: 660 return self.properties.GetLineWidth() 661 self.properties.SetLineWidth(line_width) 662 return self 663 664 def ontop(self, value=True) -> Self: 665 """Keep the object always on top of everything else.""" 666 if value: 667 self.properties.SetDisplayLocationToForeground() 668 else: 669 self.properties.SetDisplayLocationToBackground() 670 return self 671 672 def add_observer(self, event_name, func, priority=0) -> int: 673 """Add a callback function that will be called when an event occurs.""" 674 event_name = utils.get_vtk_name_event(event_name) 675 idd = self.AddObserver(event_name, func, priority) 676 return idd
Wrapping of vtkActor2D
.
545 def __init__(self): 546 """Manage 2D objects.""" 547 super().__init__() 548 549 self.dataset = None 550 self.properties = self.GetProperty() 551 self.name = "" 552 self.filename = "" 553 self.file_size = 0 554 self.pipeline = None 555 self.shape = [] # for images
Manage 2D objects.
557 @property 558 def mapper(self): 559 """Get the internal vtkMapper.""" 560 return self.GetMapper()
Get the internal vtkMapper.
567 def layer(self, value=None): 568 """Set/Get the layer number in the overlay planes into which to render.""" 569 if value is None: 570 return self.GetLayerNumber() 571 self.SetLayerNumber(value) 572 return self
Set/Get the layer number in the overlay planes into which to render.
574 def pos(self, px=None, py=None) -> Union[np.ndarray, Self]: 575 """Set/Get the screen-coordinate position.""" 576 if isinstance(px, str): 577 vedo.logger.error("Use string descriptors only inside the constructor") 578 return self 579 if px is None: 580 return np.array(self.GetPosition(), dtype=int) 581 if py is not None: 582 p = [px, py] 583 else: 584 p = px 585 assert len(p) == 2, "Error: len(pos) must be 2 for Actor2D" 586 self.SetPosition(p) 587 return self
Set/Get the screen-coordinate position.
589 def coordinate_system(self, value=None) -> Self: 590 """ 591 Set/get the coordinate system which this coordinate is defined in. 592 593 The options are: 594 0. Display 595 1. Normalized Display 596 2. Viewport 597 3. Normalized Viewport 598 4. View 599 5. Pose 600 6. World 601 """ 602 coor = self.GetPositionCoordinate() 603 if value is None: 604 return coor.GetCoordinateSystem() 605 coor.SetCoordinateSystem(value) 606 return self
Set/get the coordinate system which this coordinate is defined in.
The options are:
- Display
- Normalized Display
- Viewport
- Normalized Viewport
- View
- Pose
- World
618 def toggle(self) -> Self: 619 """Toggle object visibility.""" 620 self.SetVisibility(not self.GetVisibility()) 621 return self
Toggle object visibility.
623 def visibility(value=None) -> bool: 624 """Get/Set object visibility.""" 625 if value is not None: 626 self.SetVisibility(value) 627 return self.GetVisibility()
Get/Set object visibility.
629 def pickable(self, value=True) -> Self: 630 """Set object pickability.""" 631 self.SetPickable(value) 632 return self
Set object pickability.
634 def color(self, value=None) -> Union[np.ndarray, Self]: 635 """Set/Get the object color.""" 636 if value is None: 637 return self.properties.GetColor() 638 self.properties.SetColor(colors.get_color(value)) 639 return self
Set/Get the object color.
641 def c(self, value=None) -> Union[np.ndarray, Self]: 642 """Set/Get the object color.""" 643 return self.color(value)
Set/Get the object color.
645 def alpha(self, value=None) -> Union[float, Self]: 646 """Set/Get the object opacity.""" 647 if value is None: 648 return self.properties.GetOpacity() 649 self.properties.SetOpacity(value) 650 return self
Set/Get the object opacity.
664 def ontop(self, value=True) -> Self: 665 """Keep the object always on top of everything else.""" 666 if value: 667 self.properties.SetDisplayLocationToForeground() 668 else: 669 self.properties.SetDisplayLocationToBackground() 670 return self
Keep the object always on top of everything else.
672 def add_observer(self, event_name, func, priority=0) -> int: 673 """Add a callback function that will be called when an event occurs.""" 674 event_name = utils.get_vtk_name_event(event_name) 675 idd = self.AddObserver(event_name, func, priority) 676 return idd
Add a callback function that will be called when an event occurs.
2928class LightKit: 2929 """ 2930 A LightKit consists of three lights, a 'key light', a 'fill light', and a 'head light'. 2931 2932 The main light is the key light. It is usually positioned so that it appears like 2933 an overhead light (like the sun, or a ceiling light). 2934 It is generally positioned to shine down on the scene from about a 45 degree angle vertically 2935 and at least a little offset side to side. The key light usually at least about twice as bright 2936 as the total of all other lights in the scene to provide good modeling of object features. 2937 2938 The other lights in the kit (the fill light, headlight, and a pair of back lights) 2939 are weaker sources that provide extra illumination to fill in the spots that the key light misses. 2940 The fill light is usually positioned across from or opposite from the key light 2941 (though still on the same side of the object as the camera) in order to simulate diffuse reflections 2942 from other objects in the scene. 2943 2944 The headlight, always located at the position of the camera, reduces the contrast between areas lit 2945 by the key and fill light. The two back lights, one on the left of the object as seen from the observer 2946 and one on the right, fill on the high-contrast areas behind the object. 2947 To enforce the relationship between the different lights, the intensity of the fill, back and headlights 2948 are set as a ratio to the key light brightness. 2949 Thus, the brightness of all the lights in the scene can be changed by changing the key light intensity. 2950 2951 All lights are directional lights, infinitely far away with no falloff. Lights move with the camera. 2952 2953 For simplicity, the position of lights in the LightKit can only be specified using angles: 2954 the elevation (latitude) and azimuth (longitude) of each light with respect to the camera, in degrees. 2955 For example, a light at (elevation=0, azimuth=0) is located at the camera (a headlight). 2956 A light at (elevation=90, azimuth=0) is above the lookat point, shining down. 2957 Negative azimuth values move the lights clockwise as seen above, positive values counter-clockwise. 2958 So, a light at (elevation=45, azimuth=-20) is above and in front of the object and shining 2959 slightly from the left side. 2960 2961 LightKit limits the colors that can be assigned to any light to those of incandescent sources such as 2962 light bulbs and sunlight. It defines a special color spectrum called "warmth" from which light colors 2963 can be chosen, where 0 is cold blue, 0.5 is neutral white, and 1 is deep sunset red. 2964 Colors close to 0.5 are "cool whites" and "warm whites," respectively. 2965 2966 Since colors far from white on the warmth scale appear less bright, key-to-fill and key-to-headlight 2967 ratios are skewed by key, fill, and headlight colors. If `maintain_luminance` is set, LightKit will 2968 attempt to compensate for these perceptual differences by increasing the brightness of more saturated colors. 2969 2970 To specify the color of a light, positioning etc you can pass a dictionary with the following keys: 2971 - `intensity` : (float) The intensity of the key light. Default is 1. 2972 - `ratio` : (float) The ratio of the light intensity wrt key light. 2973 - `warmth` : (float) The warmth of the light. Default is 0.5. 2974 - `elevation` : (float) The elevation of the light in degrees. 2975 - `azimuth` : (float) The azimuth of the light in degrees. 2976 2977 Example: 2978 ```python 2979 from vedo import * 2980 lightkit = LightKit(head={"warmth":0.6}) 2981 mesh = Mesh(dataurl+"bunny.obj") 2982 plt = Plotter() 2983 plt.remove_lights().add(mesh, lightkit) 2984 plt.show().close() 2985 ``` 2986 """ 2987 def __init__(self, key=(), fill=(), back=(), head=(), maintain_luminance=False) -> None: 2988 2989 self.lightkit = vtki.new("LightKit") 2990 self.lightkit.SetMaintainLuminance(maintain_luminance) 2991 self.key = dict(key) 2992 self.head = dict(head) 2993 self.fill = dict(fill) 2994 self.back = dict(back) 2995 self.update() 2996 2997 def update(self) -> None: 2998 """Update the LightKit properties.""" 2999 if "warmth" in self.key: 3000 self.lightkit.SetKeyLightWarmth(self.key["warmth"]) 3001 if "warmth" in self.fill: 3002 self.lightkit.SetFillLightWarmth(self.fill["warmth"]) 3003 if "warmth" in self.head: 3004 self.lightkit.SetHeadLightWarmth(self.head["warmth"]) 3005 if "warmth" in self.back: 3006 self.lightkit.SetBackLightWarmth(self.back["warmth"]) 3007 3008 if "intensity" in self.key: 3009 self.lightkit.SetKeyLightIntensity(self.key["intensity"]) 3010 if "ratio" in self.fill: 3011 self.lightkit.SetKeyToFillRatio(self.key["ratio"]) 3012 if "ratio" in self.head: 3013 self.lightkit.SetKeyToHeadRatio(self.key["ratio"]) 3014 if "ratio" in self.back: 3015 self.lightkit.SetKeyToBackRatio(self.key["ratio"]) 3016 3017 if "elevation" in self.key: 3018 self.lightkit.SetKeyLightElevation(self.key["elevation"]) 3019 if "elevation" in self.fill: 3020 self.lightkit.SetFillLightElevation(self.fill["elevation"]) 3021 if "elevation" in self.head: 3022 self.lightkit.SetHeadLightElevation(self.head["elevation"]) 3023 if "elevation" in self.back: 3024 self.lightkit.SetBackLightElevation(self.back["elevation"]) 3025 3026 if "azimuth" in self.key: 3027 self.lightkit.SetKeyLightAzimuth(self.key["azimuth"]) 3028 if "azimuth" in self.fill: 3029 self.lightkit.SetFillLightAzimuth(self.fill["azimuth"]) 3030 if "azimuth" in self.head: 3031 self.lightkit.SetHeadLightAzimuth(self.head["azimuth"]) 3032 if "azimuth" in self.back: 3033 self.lightkit.SetBackLightAzimuth(self.back["azimuth"])
A LightKit consists of three lights, a 'key light', a 'fill light', and a 'head light'.
The main light is the key light. It is usually positioned so that it appears like an overhead light (like the sun, or a ceiling light). It is generally positioned to shine down on the scene from about a 45 degree angle vertically and at least a little offset side to side. The key light usually at least about twice as bright as the total of all other lights in the scene to provide good modeling of object features.
The other lights in the kit (the fill light, headlight, and a pair of back lights) are weaker sources that provide extra illumination to fill in the spots that the key light misses. The fill light is usually positioned across from or opposite from the key light (though still on the same side of the object as the camera) in order to simulate diffuse reflections from other objects in the scene.
The headlight, always located at the position of the camera, reduces the contrast between areas lit by the key and fill light. The two back lights, one on the left of the object as seen from the observer and one on the right, fill on the high-contrast areas behind the object. To enforce the relationship between the different lights, the intensity of the fill, back and headlights are set as a ratio to the key light brightness. Thus, the brightness of all the lights in the scene can be changed by changing the key light intensity.
All lights are directional lights, infinitely far away with no falloff. Lights move with the camera.
For simplicity, the position of lights in the LightKit can only be specified using angles: the elevation (latitude) and azimuth (longitude) of each light with respect to the camera, in degrees. For example, a light at (elevation=0, azimuth=0) is located at the camera (a headlight). A light at (elevation=90, azimuth=0) is above the lookat point, shining down. Negative azimuth values move the lights clockwise as seen above, positive values counter-clockwise. So, a light at (elevation=45, azimuth=-20) is above and in front of the object and shining slightly from the left side.
LightKit limits the colors that can be assigned to any light to those of incandescent sources such as light bulbs and sunlight. It defines a special color spectrum called "warmth" from which light colors can be chosen, where 0 is cold blue, 0.5 is neutral white, and 1 is deep sunset red. Colors close to 0.5 are "cool whites" and "warm whites," respectively.
Since colors far from white on the warmth scale appear less bright, key-to-fill and key-to-headlight
ratios are skewed by key, fill, and headlight colors. If maintain_luminance
is set, LightKit will
attempt to compensate for these perceptual differences by increasing the brightness of more saturated colors.
To specify the color of a light, positioning etc you can pass a dictionary with the following keys:
- intensity
: (float) The intensity of the key light. Default is 1.
- ratio
: (float) The ratio of the light intensity wrt key light.
- warmth
: (float) The warmth of the light. Default is 0.5.
- elevation
: (float) The elevation of the light in degrees.
- azimuth
: (float) The azimuth of the light in degrees.
Example:
from vedo import * lightkit = LightKit(head={"warmth":0.6}) mesh = Mesh(dataurl+"bunny.obj") plt = Plotter() plt.remove_lights().add(mesh, lightkit) plt.show().close()
2987 def __init__(self, key=(), fill=(), back=(), head=(), maintain_luminance=False) -> None: 2988 2989 self.lightkit = vtki.new("LightKit") 2990 self.lightkit.SetMaintainLuminance(maintain_luminance) 2991 self.key = dict(key) 2992 self.head = dict(head) 2993 self.fill = dict(fill) 2994 self.back = dict(back) 2995 self.update()
2997 def update(self) -> None: 2998 """Update the LightKit properties.""" 2999 if "warmth" in self.key: 3000 self.lightkit.SetKeyLightWarmth(self.key["warmth"]) 3001 if "warmth" in self.fill: 3002 self.lightkit.SetFillLightWarmth(self.fill["warmth"]) 3003 if "warmth" in self.head: 3004 self.lightkit.SetHeadLightWarmth(self.head["warmth"]) 3005 if "warmth" in self.back: 3006 self.lightkit.SetBackLightWarmth(self.back["warmth"]) 3007 3008 if "intensity" in self.key: 3009 self.lightkit.SetKeyLightIntensity(self.key["intensity"]) 3010 if "ratio" in self.fill: 3011 self.lightkit.SetKeyToFillRatio(self.key["ratio"]) 3012 if "ratio" in self.head: 3013 self.lightkit.SetKeyToHeadRatio(self.key["ratio"]) 3014 if "ratio" in self.back: 3015 self.lightkit.SetKeyToBackRatio(self.key["ratio"]) 3016 3017 if "elevation" in self.key: 3018 self.lightkit.SetKeyLightElevation(self.key["elevation"]) 3019 if "elevation" in self.fill: 3020 self.lightkit.SetFillLightElevation(self.fill["elevation"]) 3021 if "elevation" in self.head: 3022 self.lightkit.SetHeadLightElevation(self.head["elevation"]) 3023 if "elevation" in self.back: 3024 self.lightkit.SetBackLightElevation(self.back["elevation"]) 3025 3026 if "azimuth" in self.key: 3027 self.lightkit.SetKeyLightAzimuth(self.key["azimuth"]) 3028 if "azimuth" in self.fill: 3029 self.lightkit.SetFillLightAzimuth(self.fill["azimuth"]) 3030 if "azimuth" in self.head: 3031 self.lightkit.SetHeadLightAzimuth(self.head["azimuth"]) 3032 if "azimuth" in self.back: 3033 self.lightkit.SetBackLightAzimuth(self.back["azimuth"])
Update the LightKit properties.