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