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