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