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 return self 1503 1504 def update_trail(self) -> Self: 1505 """ 1506 Update the trailing line of a moving object. 1507 """ 1508 currentpos = self.pos() 1509 self.trail_points.append(currentpos) # cycle 1510 self.trail_points.pop(0) 1511 data = np.array(self.trail_points) + self.trail_offset 1512 tpoly = self.trail.dataset 1513 tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32)) 1514 return self 1515 1516 def _compute_shadow(self, plane, point, direction): 1517 shad = self.clone() 1518 shad.name = "Shadow" 1519 1520 tarr = shad.dataset.GetPointData().GetTCoords() 1521 if tarr: # remove any texture coords 1522 tname = tarr.GetName() 1523 shad.pointdata.remove(tname) 1524 shad.dataset.GetPointData().SetTCoords(None) 1525 shad.actor.SetTexture(None) 1526 1527 pts = shad.vertices 1528 if plane == "x": 1529 # shad = shad.project_on_plane('x') 1530 # instead do it manually so in case of alpha<1 1531 # we dont see glitches due to coplanar points 1532 # we leave a small tolerance of 0.1% in thickness 1533 x0, x1 = self.xbounds() 1534 pts[:, 0] = (pts[:, 0] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[0] 1535 shad.vertices = pts 1536 shad.x(point) 1537 elif plane == "y": 1538 x0, x1 = self.ybounds() 1539 pts[:, 1] = (pts[:, 1] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[1] 1540 shad.vertices = pts 1541 shad.y(point) 1542 elif plane == "z": 1543 x0, x1 = self.zbounds() 1544 pts[:, 2] = (pts[:, 2] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[2] 1545 shad.vertices = pts 1546 shad.z(point) 1547 else: 1548 shad = shad.project_on_plane(plane, point, direction) 1549 return shad 1550 1551 def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0) -> Self: 1552 """ 1553 Generate a shadow out of an `Mesh` on one of the three Cartesian planes. 1554 The output is a new `Mesh` representing the shadow. 1555 This new mesh is accessible through `mesh.shadow`. 1556 By default the shadow mesh is placed on the bottom wall of the bounding box. 1557 1558 See also `pointcloud.project_on_plane()`. 1559 1560 Arguments: 1561 plane : (str, Plane) 1562 if plane is `str`, plane can be one of `['x', 'y', 'z']`, 1563 represents x-plane, y-plane and z-plane, respectively. 1564 Otherwise, plane should be an instance of `vedo.shapes.Plane` 1565 point : (float, array) 1566 if plane is `str`, point should be a float represents the intercept. 1567 Otherwise, point is the camera point of perspective projection 1568 direction : (list) 1569 direction of oblique projection 1570 culling : (int) 1571 choose between front [1] or backface [-1] culling or None. 1572 1573 Examples: 1574 - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py) 1575 - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py) 1576 - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py) 1577 1578 ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif) 1579 """ 1580 shad = self._compute_shadow(plane, point, direction) 1581 shad.c(c).alpha(alpha) 1582 1583 try: 1584 # Points dont have these methods 1585 shad.flat() 1586 if culling in (1, True): 1587 shad.frontface_culling() 1588 elif culling == -1: 1589 shad.backface_culling() 1590 except AttributeError: 1591 pass 1592 1593 shad.properties.LightingOff() 1594 shad.actor.SetPickable(False) 1595 shad.actor.SetUseBounds(True) 1596 1597 if shad not in self.shadows: 1598 self.shadows.append(shad) 1599 shad.info = dict(plane=plane, point=point, direction=direction) 1600 # shad.metadata["plane"] = plane 1601 # shad.metadata["point"] = point 1602 # print("AAAA", direction, plane, point) 1603 # if direction is None: 1604 # direction = [0,0,0] 1605 # shad.metadata["direction"] = direction 1606 return self 1607 1608 def update_shadows(self) -> Self: 1609 """Update the shadows of a moving object.""" 1610 for sha in self.shadows: 1611 plane = sha.info["plane"] 1612 point = sha.info["point"] 1613 direction = sha.info["direction"] 1614 # print("update_shadows direction", direction,plane,point ) 1615 # plane = sha.metadata["plane"] 1616 # point = sha.metadata["point"] 1617 # direction = sha.metadata["direction"] 1618 # if direction[0]==0 and direction[1]==0 and direction[2]==0: 1619 # direction = None 1620 # print("BBBB", sha.metadata["direction"], 1621 # sha.metadata["plane"], sha.metadata["point"]) 1622 new_sha = self._compute_shadow(plane, point, direction) 1623 sha._update(new_sha.dataset) 1624 if self.trail: 1625 self.trail.update_shadows() 1626 return self 1627 1628 def labels( 1629 self, 1630 content=None, 1631 on="points", 1632 scale=None, 1633 xrot=0.0, 1634 yrot=0.0, 1635 zrot=0.0, 1636 ratio=1, 1637 precision=None, 1638 italic=False, 1639 font="", 1640 justify="", 1641 c="black", 1642 alpha=1.0, 1643 ) -> Union["vedo.Mesh", None]: 1644 """ 1645 Generate value or ID labels for mesh cells or points. 1646 For large nr. of labels use `font="VTK"` which is much faster. 1647 1648 See also: 1649 `labels2d()`, `flagpole()`, `caption()` and `legend()`. 1650 1651 Arguments: 1652 content : (list,int,str) 1653 either 'id', 'cellid', array name or array number. 1654 A array can also be passed (must match the nr. of points or cells). 1655 on : (str) 1656 generate labels for "cells" instead of "points" 1657 scale : (float) 1658 absolute size of labels, if left as None it is automatic 1659 zrot : (float) 1660 local rotation angle of label in degrees 1661 ratio : (int) 1662 skipping ratio, to reduce nr of labels for large meshes 1663 precision : (int) 1664 numeric precision of labels 1665 1666 ```python 1667 from vedo import * 1668 s = Sphere(res=10).linewidth(1).c("orange").compute_normals() 1669 point_ids = s.labels('id', on="points").c('green') 1670 cell_ids = s.labels('id', on="cells" ).c('black') 1671 show(s, point_ids, cell_ids) 1672 ``` 1673 ![](https://vedo.embl.es/images/feats/labels.png) 1674 1675 Examples: 1676 - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py) 1677 1678 ![](https://vedo.embl.es/images/basic/boundaries.png) 1679 """ 1680 1681 cells = False 1682 if "cell" in on or "face" in on: 1683 cells = True 1684 justify = "centered" if justify == "" else justify 1685 1686 if isinstance(content, str): 1687 if content in ("pointid", "pointsid"): 1688 cells = False 1689 content = "id" 1690 justify = "bottom-left" if justify == "" else justify 1691 if content in ("cellid", "cellsid"): 1692 cells = True 1693 content = "id" 1694 justify = "centered" if justify == "" else justify 1695 1696 try: 1697 if cells: 1698 ns = np.sqrt(self.ncells) 1699 elems = self.cell_centers 1700 norms = self.cell_normals 1701 justify = "centered" if justify == "" else justify 1702 else: 1703 ns = np.sqrt(self.npoints) 1704 elems = self.vertices 1705 norms = self.vertex_normals 1706 except AttributeError: 1707 norms = [] 1708 1709 if not justify: 1710 justify = "bottom-left" 1711 1712 hasnorms = False 1713 if len(norms) > 0: 1714 hasnorms = True 1715 1716 if scale is None: 1717 if not ns: 1718 ns = 100 1719 scale = self.diagonal_size() / ns / 10 1720 1721 arr = None 1722 mode = 0 1723 if content is None: 1724 mode = 0 1725 if cells: 1726 if self.dataset.GetCellData().GetScalars(): 1727 name = self.dataset.GetCellData().GetScalars().GetName() 1728 arr = self.celldata[name] 1729 else: 1730 if self.dataset.GetPointData().GetScalars(): 1731 name = self.dataset.GetPointData().GetScalars().GetName() 1732 arr = self.pointdata[name] 1733 elif isinstance(content, (str, int)): 1734 if content == "id": 1735 mode = 1 1736 elif cells: 1737 mode = 0 1738 arr = self.celldata[content] 1739 else: 1740 mode = 0 1741 arr = self.pointdata[content] 1742 elif utils.is_sequence(content): 1743 mode = 0 1744 arr = content 1745 1746 if arr is None and mode == 0: 1747 vedo.logger.error("in labels(), array not found in point or cell data") 1748 return None 1749 1750 ratio = int(ratio+0.5) 1751 tapp = vtki.new("AppendPolyData") 1752 has_inputs = False 1753 1754 for i, e in enumerate(elems): 1755 if i % ratio: 1756 continue 1757 1758 if mode == 1: 1759 txt_lab = str(i) 1760 else: 1761 if precision: 1762 txt_lab = utils.precision(arr[i], precision) 1763 else: 1764 txt_lab = str(arr[i]) 1765 1766 if not txt_lab: 1767 continue 1768 1769 if font == "VTK": 1770 tx = vtki.new("VectorText") 1771 tx.SetText(txt_lab) 1772 tx.Update() 1773 tx_poly = tx.GetOutput() 1774 else: 1775 tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify).dataset 1776 1777 if tx_poly.GetNumberOfPoints() == 0: 1778 continue ###################### 1779 1780 T = vtki.vtkTransform() 1781 T.PostMultiply() 1782 if italic: 1783 T.Concatenate([1, 0.2, 0, 0, 1784 0, 1 , 0, 0, 1785 0, 0 , 1, 0, 1786 0, 0 , 0, 1]) 1787 if hasnorms: 1788 ni = norms[i] 1789 if cells and font=="VTK": # center-justify 1790 bb = tx_poly.GetBounds() 1791 dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2 1792 T.Translate(-dx, -dy, 0) 1793 if xrot: T.RotateX(xrot) 1794 if yrot: T.RotateY(yrot) 1795 if zrot: T.RotateZ(zrot) 1796 crossvec = np.cross([0, 0, 1], ni) 1797 angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3 1798 T.RotateWXYZ(float(angle), crossvec.tolist()) 1799 T.Translate(ni / 100) 1800 else: 1801 if xrot: T.RotateX(xrot) 1802 if yrot: T.RotateY(yrot) 1803 if zrot: T.RotateZ(zrot) 1804 T.Scale(scale, scale, scale) 1805 T.Translate(e) 1806 tf = vtki.new("TransformPolyDataFilter") 1807 tf.SetInputData(tx_poly) 1808 tf.SetTransform(T) 1809 tf.Update() 1810 tapp.AddInputData(tf.GetOutput()) 1811 has_inputs = True 1812 1813 if has_inputs: 1814 tapp.Update() 1815 lpoly = tapp.GetOutput() 1816 else: 1817 lpoly = vtki.vtkPolyData() 1818 ids = vedo.Mesh(lpoly, c=c, alpha=alpha) 1819 ids.properties.LightingOff() 1820 ids.actor.PickableOff() 1821 ids.actor.SetUseBounds(False) 1822 ids.name = "Labels" 1823 return ids 1824 1825 def labels2d( 1826 self, 1827 content="id", 1828 on="points", 1829 scale=1.0, 1830 precision=4, 1831 font="Calco", 1832 justify="bottom-left", 1833 angle=0.0, 1834 frame=False, 1835 c="black", 1836 bc=None, 1837 alpha=1.0, 1838 ) -> Union["Actor2D", None]: 1839 """ 1840 Generate value or ID bi-dimensional labels for mesh cells or points. 1841 1842 See also: `labels()`, `flagpole()`, `caption()` and `legend()`. 1843 1844 Arguments: 1845 content : (str) 1846 either 'id', 'cellid', or array name 1847 on : (str) 1848 generate labels for "cells" instead of "points" (the default) 1849 scale : (float) 1850 size scaling of labels 1851 precision : (int) 1852 precision of numeric labels 1853 angle : (float) 1854 local rotation angle of label in degrees 1855 frame : (bool) 1856 draw a frame around the label 1857 bc : (str) 1858 background color of the label 1859 1860 ```python 1861 from vedo import Sphere, show 1862 sph = Sphere(quads=True, res=4).compute_normals().wireframe() 1863 sph.celldata["zvals"] = sph.cell_centers[:,2] 1864 l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9') 1865 show(sph, l2d, axes=1).close() 1866 ``` 1867 ![](https://vedo.embl.es/images/feats/labels2d.png) 1868 """ 1869 cells = False 1870 if "cell" in on: 1871 cells = True 1872 1873 if isinstance(content, str): 1874 if content in ("id", "pointid", "pointsid"): 1875 cells = False 1876 content = "id" 1877 if content in ("cellid", "cellsid"): 1878 cells = True 1879 content = "id" 1880 1881 if cells: 1882 if content != "id" and content not in self.celldata.keys(): 1883 vedo.logger.error(f"In labels2d: cell array {content} does not exist.") 1884 return None 1885 cellcloud = vedo.Points(self.cell_centers) 1886 arr = self.dataset.GetCellData().GetScalars() 1887 poly = cellcloud.dataset 1888 poly.GetPointData().SetScalars(arr) 1889 else: 1890 poly = self.dataset 1891 if content != "id" and content not in self.pointdata.keys(): 1892 vedo.logger.error(f"In labels2d: point array {content} does not exist.") 1893 return None 1894 1895 mp = vtki.new("LabeledDataMapper") 1896 1897 if content == "id": 1898 mp.SetLabelModeToLabelIds() 1899 else: 1900 mp.SetLabelModeToLabelScalars() 1901 if precision is not None: 1902 mp.SetLabelFormat(f"%-#.{precision}g") 1903 1904 pr = mp.GetLabelTextProperty() 1905 c = colors.get_color(c) 1906 pr.SetColor(c) 1907 pr.SetOpacity(alpha) 1908 pr.SetFrame(frame) 1909 pr.SetFrameColor(c) 1910 pr.SetItalic(False) 1911 pr.BoldOff() 1912 pr.ShadowOff() 1913 pr.UseTightBoundingBoxOn() 1914 pr.SetOrientation(angle) 1915 pr.SetFontFamily(vtki.VTK_FONT_FILE) 1916 fl = utils.get_font_path(font) 1917 pr.SetFontFile(fl) 1918 pr.SetFontSize(int(20 * scale)) 1919 1920 if "cent" in justify or "mid" in justify: 1921 pr.SetJustificationToCentered() 1922 elif "rig" in justify: 1923 pr.SetJustificationToRight() 1924 elif "left" in justify: 1925 pr.SetJustificationToLeft() 1926 # ------ 1927 if "top" in justify: 1928 pr.SetVerticalJustificationToTop() 1929 else: 1930 pr.SetVerticalJustificationToBottom() 1931 1932 if bc is not None: 1933 bc = colors.get_color(bc) 1934 pr.SetBackgroundColor(bc) 1935 pr.SetBackgroundOpacity(alpha) 1936 1937 mp.SetInputData(poly) 1938 a2d = Actor2D() 1939 a2d.PickableOff() 1940 a2d.SetMapper(mp) 1941 return a2d 1942 1943 def legend(self, txt) -> Self: 1944 """Book a legend text.""" 1945 self.info["legend"] = txt 1946 # self.metadata["legend"] = txt 1947 return self 1948 1949 def flagpole( 1950 self, 1951 txt=None, 1952 point=None, 1953 offset=None, 1954 s=None, 1955 font="Calco", 1956 rounded=True, 1957 c=None, 1958 alpha=1.0, 1959 lw=2, 1960 italic=0.0, 1961 padding=0.1, 1962 ) -> Union["vedo.Mesh", None]: 1963 """ 1964 Generate a flag pole style element to describe an object. 1965 Returns a `Mesh` object. 1966 1967 Use flagpole.follow_camera() to make it face the camera in the scene. 1968 1969 Consider using `settings.use_parallel_projection = True` 1970 to avoid perspective distortions. 1971 1972 See also `flagpost()`. 1973 1974 Arguments: 1975 txt : (str) 1976 Text to display. The default is the filename or the object name. 1977 point : (list) 1978 position of the flagpole pointer. 1979 offset : (list) 1980 text offset wrt the application point. 1981 s : (float) 1982 size of the flagpole. 1983 font : (str) 1984 font face. Check [available fonts here](https://vedo.embl.es/fonts). 1985 rounded : (bool) 1986 draw a rounded or squared box around the text. 1987 c : (list) 1988 text and box color. 1989 alpha : (float) 1990 opacity of text and box. 1991 lw : (float) 1992 line with of box frame. 1993 italic : (float) 1994 italicness of text. 1995 1996 Examples: 1997 - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py) 1998 1999 ![](https://vedo.embl.es/images/pyplot/intersect2d.png) 2000 2001 - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py) 2002 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 2003 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 2004 """ 2005 objs = [] 2006 2007 if txt is None: 2008 if self.filename: 2009 txt = self.filename.split("/")[-1] 2010 elif self.name: 2011 txt = self.name 2012 else: 2013 return None 2014 2015 x0, x1, y0, y1, z0, z1 = self.bounds() 2016 d = self.diagonal_size() 2017 if point is None: 2018 if d: 2019 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2020 # point = self.closest_point([x1, y0, z1]) 2021 else: # it's a Point 2022 point = self.transform.position 2023 2024 pt = utils.make3d(point) 2025 2026 if offset is None: 2027 offset = [(x1 - x0) / 1.75, (y1 - y0) / 5, 0] 2028 offset = utils.make3d(offset) 2029 2030 if s is None: 2031 s = d / 20 2032 2033 sph = None 2034 if d and (z1 - z0) / d > 0.1: 2035 sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6) 2036 2037 if c is None: 2038 c = np.array(self.color()) / 1.4 2039 2040 lab = vedo.shapes.Text3D( 2041 txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center" 2042 ) 2043 objs.append(lab) 2044 2045 if d and not sph: 2046 sph = vedo.shapes.Circle(pt, r=s / 3, res=16) 2047 objs.append(sph) 2048 2049 x0, x1, y0, y1, z0, z1 = lab.bounds() 2050 aline = [(x0,y0,z0), (x1,y0,z0), (x1,y1,z0), (x0,y1,z0)] 2051 if rounded: 2052 box = vedo.shapes.KSpline(aline, closed=True) 2053 else: 2054 box = vedo.shapes.Line(aline, closed=True) 2055 2056 cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2] 2057 2058 # box.actor.SetOrigin(cnt) 2059 box.scale([1 + padding, 1 + 2 * padding, 1], origin=cnt) 2060 objs.append(box) 2061 2062 x0, x1, y0, y1, z0, z1 = box.bounds() 2063 if x0 < pt[0] < x1: 2064 c0 = box.closest_point(pt) 2065 c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]] 2066 elif (pt[0] - x0) < (x1 - pt[0]): 2067 c0 = [x0, (y0 + y1) / 2, pt[2]] 2068 c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]] 2069 else: 2070 c0 = [x1, (y0 + y1) / 2, pt[2]] 2071 c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]] 2072 2073 con = vedo.shapes.Line([c0, c1, pt]) 2074 objs.append(con) 2075 2076 mobjs = vedo.merge(objs).c(c).alpha(alpha) 2077 mobjs.name = "FlagPole" 2078 mobjs.bc("tomato").pickable(False) 2079 mobjs.properties.LightingOff() 2080 mobjs.properties.SetLineWidth(lw) 2081 mobjs.actor.UseBoundsOff() 2082 mobjs.actor.SetPosition([0,0,0]) 2083 mobjs.actor.SetOrigin(pt) 2084 return mobjs 2085 2086 # mobjs = vedo.Assembly(objs)#.c(c).alpha(alpha) 2087 # mobjs.name = "FlagPole" 2088 # # mobjs.bc("tomato").pickable(False) 2089 # # mobjs.properties.LightingOff() 2090 # # mobjs.properties.SetLineWidth(lw) 2091 # # mobjs.actor.UseBoundsOff() 2092 # # mobjs.actor.SetPosition([0,0,0]) 2093 # # mobjs.actor.SetOrigin(pt) 2094 # # print(pt) 2095 # return mobjs 2096 2097 def flagpost( 2098 self, 2099 txt=None, 2100 point=None, 2101 offset=None, 2102 s=1.0, 2103 c="k9", 2104 bc="k1", 2105 alpha=1, 2106 lw=0, 2107 font="Calco", 2108 justify="center-left", 2109 vspacing=1.0, 2110 ) -> Union["vedo.addons.Flagpost", None]: 2111 """ 2112 Generate a flag post style element to describe an object. 2113 2114 Arguments: 2115 txt : (str) 2116 Text to display. The default is the filename or the object name. 2117 point : (list) 2118 position of the flag anchor point. The default is None. 2119 offset : (list) 2120 a 3D displacement or offset. The default is None. 2121 s : (float) 2122 size of the text to be shown 2123 c : (list) 2124 color of text and line 2125 bc : (list) 2126 color of the flag background 2127 alpha : (float) 2128 opacity of text and box. 2129 lw : (int) 2130 line with of box frame. The default is 0. 2131 font : (str) 2132 font name. Use a monospace font for better rendering. The default is "Calco". 2133 Type `vedo -r fonts` for a font demo. 2134 Check [available fonts here](https://vedo.embl.es/fonts). 2135 justify : (str) 2136 internal text justification. The default is "center-left". 2137 vspacing : (float) 2138 vertical spacing between lines. 2139 2140 Examples: 2141 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py) 2142 2143 ![](https://vedo.embl.es/images/other/flag_labels2.png) 2144 """ 2145 if txt is None: 2146 if self.filename: 2147 txt = self.filename.split("/")[-1] 2148 elif self.name: 2149 txt = self.name 2150 else: 2151 return None 2152 2153 x0, x1, y0, y1, z0, z1 = self.bounds() 2154 d = self.diagonal_size() 2155 if point is None: 2156 if d: 2157 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2158 else: # it's a Point 2159 point = self.transform.position 2160 2161 point = utils.make3d(point) 2162 2163 if offset is None: 2164 offset = [0, 0, (z1 - z0) / 2] 2165 offset = utils.make3d(offset) 2166 2167 fpost = vedo.addons.Flagpost( 2168 txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing 2169 ) 2170 self._caption = fpost 2171 return fpost 2172 2173 def caption( 2174 self, 2175 txt=None, 2176 point=None, 2177 size=(0.30, 0.15), 2178 padding=5, 2179 font="Calco", 2180 justify="center-right", 2181 vspacing=1.0, 2182 c=None, 2183 alpha=1.0, 2184 lw=1, 2185 ontop=True, 2186 ) -> Union["vtki.vtkCaptionActor2D", None]: 2187 """ 2188 Create a 2D caption to an object which follows the camera movements. 2189 Latex is not supported. Returns the same input object for concatenation. 2190 2191 See also `flagpole()`, `flagpost()`, `labels()` and `legend()` 2192 with similar functionality. 2193 2194 Arguments: 2195 txt : (str) 2196 text to be rendered. The default is the file name. 2197 point : (list) 2198 anchoring point. The default is None. 2199 size : (list) 2200 (width, height) of the caption box. The default is (0.30, 0.15). 2201 padding : (float) 2202 padding space of the caption box in pixels. The default is 5. 2203 font : (str) 2204 font name. Use a monospace font for better rendering. The default is "VictorMono". 2205 Type `vedo -r fonts` for a font demo. 2206 Check [available fonts here](https://vedo.embl.es/fonts). 2207 justify : (str) 2208 internal text justification. The default is "center-right". 2209 vspacing : (float) 2210 vertical spacing between lines. The default is 1. 2211 c : (str) 2212 text and box color. The default is 'lb'. 2213 alpha : (float) 2214 text and box transparency. The default is 1. 2215 lw : (int) 2216 line width in pixels. The default is 1. 2217 ontop : (bool) 2218 keep the 2d caption always on top. The default is True. 2219 2220 Examples: 2221 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 2222 2223 ![](https://vedo.embl.es/images/pyplot/caption.png) 2224 2225 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 2226 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 2227 """ 2228 if txt is None: 2229 if self.filename: 2230 txt = self.filename.split("/")[-1] 2231 elif self.name: 2232 txt = self.name 2233 2234 if not txt: # disable it 2235 self._caption = None 2236 return None 2237 2238 for r in vedo.shapes._reps: 2239 txt = txt.replace(r[0], r[1]) 2240 2241 if c is None: 2242 c = np.array(self.properties.GetColor()) / 2 2243 else: 2244 c = colors.get_color(c) 2245 2246 if point is None: 2247 x0, x1, y0, y1, _, z1 = self.dataset.GetBounds() 2248 pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1] 2249 point = self.closest_point(pt) 2250 2251 capt = vtki.vtkCaptionActor2D() 2252 capt.SetAttachmentPoint(point) 2253 capt.SetBorder(True) 2254 capt.SetLeader(True) 2255 sph = vtki.new("SphereSource") 2256 sph.Update() 2257 capt.SetLeaderGlyphData(sph.GetOutput()) 2258 capt.SetMaximumLeaderGlyphSize(5) 2259 capt.SetPadding(int(padding)) 2260 capt.SetCaption(txt) 2261 capt.SetWidth(size[0]) 2262 capt.SetHeight(size[1]) 2263 capt.SetThreeDimensionalLeader(not ontop) 2264 2265 pra = capt.GetProperty() 2266 pra.SetColor(c) 2267 pra.SetOpacity(alpha) 2268 pra.SetLineWidth(lw) 2269 2270 pr = capt.GetCaptionTextProperty() 2271 pr.SetFontFamily(vtki.VTK_FONT_FILE) 2272 fl = utils.get_font_path(font) 2273 pr.SetFontFile(fl) 2274 pr.ShadowOff() 2275 pr.BoldOff() 2276 pr.FrameOff() 2277 pr.SetColor(c) 2278 pr.SetOpacity(alpha) 2279 pr.SetJustificationToLeft() 2280 if "top" in justify: 2281 pr.SetVerticalJustificationToTop() 2282 if "bottom" in justify: 2283 pr.SetVerticalJustificationToBottom() 2284 if "cent" in justify: 2285 pr.SetVerticalJustificationToCentered() 2286 pr.SetJustificationToCentered() 2287 if "left" in justify: 2288 pr.SetJustificationToLeft() 2289 if "right" in justify: 2290 pr.SetJustificationToRight() 2291 pr.SetLineSpacing(vspacing) 2292 return capt 2293 2294 2295##################################################################### 2296class MeshVisual(PointsVisual): 2297 """Class to manage the visual aspects of a ``Maesh`` object.""" 2298 2299 def __init__(self) -> None: 2300 # print("INIT MeshVisual", super()) 2301 super().__init__() 2302 2303 def follow_camera(self, camera=None, origin=None) -> Self: 2304 """ 2305 Return an object that will follow camera movements and stay locked to it. 2306 Use `mesh.follow_camera(False)` to disable it. 2307 2308 A `vtkCamera` object can also be passed. 2309 """ 2310 if camera is False: 2311 try: 2312 self.SetCamera(None) 2313 return self 2314 except AttributeError: 2315 return self 2316 2317 factor = vtki.vtkFollower() 2318 factor.SetMapper(self.mapper) 2319 factor.SetProperty(self.properties) 2320 factor.SetBackfaceProperty(self.actor.GetBackfaceProperty()) 2321 factor.SetTexture(self.actor.GetTexture()) 2322 factor.SetScale(self.actor.GetScale()) 2323 # factor.SetOrientation(self.actor.GetOrientation()) 2324 factor.SetPosition(self.actor.GetPosition()) 2325 factor.SetUseBounds(self.actor.GetUseBounds()) 2326 2327 if origin is None: 2328 factor.SetOrigin(self.actor.GetOrigin()) 2329 else: 2330 factor.SetOrigin(origin) 2331 2332 factor.PickableOff() 2333 2334 if isinstance(camera, vtki.vtkCamera): 2335 factor.SetCamera(camera) 2336 else: 2337 plt = vedo.plotter_instance 2338 if plt and plt.renderer and plt.renderer.GetActiveCamera(): 2339 factor.SetCamera(plt.renderer.GetActiveCamera()) 2340 2341 self.actor = None 2342 factor.retrieve_object = weak_ref_to(self) 2343 self.actor = factor 2344 return self 2345 2346 def wireframe(self, value=True) -> Self: 2347 """Set mesh's representation as wireframe or solid surface.""" 2348 if value: 2349 self.properties.SetRepresentationToWireframe() 2350 else: 2351 self.properties.SetRepresentationToSurface() 2352 return self 2353 2354 def flat(self) -> Self: 2355 """Set surface interpolation to flat. 2356 2357 <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" width="700"> 2358 """ 2359 self.properties.SetInterpolationToFlat() 2360 return self 2361 2362 def phong(self) -> Self: 2363 """Set surface interpolation to "phong".""" 2364 self.properties.SetInterpolationToPhong() 2365 return self 2366 2367 def backface_culling(self, value=True) -> Self: 2368 """Set culling of polygons based on orientation of normal with respect to camera.""" 2369 self.properties.SetBackfaceCulling(value) 2370 return self 2371 2372 def render_lines_as_tubes(self, value=True) -> Self: 2373 """Wrap a fake tube around a simple line for visualization""" 2374 self.properties.SetRenderLinesAsTubes(value) 2375 return self 2376 2377 def frontface_culling(self, value=True) -> Self: 2378 """Set culling of polygons based on orientation of normal with respect to camera.""" 2379 self.properties.SetFrontfaceCulling(value) 2380 return self 2381 2382 def backcolor(self, bc=None) -> Union[Self, np.ndarray]: 2383 """ 2384 Set/get mesh's backface color. 2385 """ 2386 back_prop = self.actor.GetBackfaceProperty() 2387 2388 if bc is None: 2389 if back_prop: 2390 return back_prop.GetDiffuseColor() 2391 return self 2392 2393 if self.properties.GetOpacity() < 1: 2394 return self 2395 2396 if not back_prop: 2397 back_prop = vtki.vtkProperty() 2398 2399 back_prop.SetDiffuseColor(colors.get_color(bc)) 2400 back_prop.SetOpacity(self.properties.GetOpacity()) 2401 self.actor.SetBackfaceProperty(back_prop) 2402 self.mapper.ScalarVisibilityOff() 2403 return self 2404 2405 def bc(self, backcolor=False) -> Union[Self, np.ndarray]: 2406 """Shortcut for `mesh.backcolor()`.""" 2407 return self.backcolor(backcolor) 2408 2409 def linewidth(self, lw=None) -> Union[Self, int]: 2410 """Set/get width of mesh edges. Same as `lw()`.""" 2411 if lw is not None: 2412 if lw == 0: 2413 self.properties.EdgeVisibilityOff() 2414 self.properties.SetRepresentationToSurface() 2415 return self 2416 self.properties.EdgeVisibilityOn() 2417 self.properties.SetLineWidth(lw) 2418 else: 2419 return self.properties.GetLineWidth() 2420 return self 2421 2422 def lw(self, linewidth=None) -> Union[Self, int]: 2423 """Set/get width of mesh edges. Same as `linewidth()`.""" 2424 return self.linewidth(linewidth) 2425 2426 def linecolor(self, lc=None) -> Union[Self, np.ndarray]: 2427 """Set/get color of mesh edges. Same as `lc()`.""" 2428 if lc is None: 2429 return np.array(self.properties.GetEdgeColor()) 2430 self.properties.EdgeVisibilityOn() 2431 self.properties.SetEdgeColor(colors.get_color(lc)) 2432 return self 2433 2434 def lc(self, linecolor=None) -> Union[Self, np.ndarray]: 2435 """Set/get color of mesh edges. Same as `linecolor()`.""" 2436 return self.linecolor(linecolor) 2437 2438 def texture( 2439 self, 2440 tname, 2441 tcoords=None, 2442 interpolate=True, 2443 repeat=True, 2444 edge_clamp=False, 2445 scale=None, 2446 ushift=None, 2447 vshift=None, 2448 ) -> Self: 2449 """ 2450 Assign a texture to mesh from image file or predefined texture `tname`. 2451 If tname is set to `None` texture is disabled. 2452 Input tname can also be an array or a `vtkTexture`. 2453 2454 Arguments: 2455 tname : (numpy.array, str, Image, vtkTexture, None) 2456 the input texture to be applied. Can be a numpy array, a path to an image file, 2457 a vedo Image. The None value disables texture. 2458 tcoords : (numpy.array, str) 2459 this is the (u,v) texture coordinate array. Can also be a string of an existing array 2460 in the mesh. 2461 interpolate : (bool) 2462 turn on/off linear interpolation of the texture map when rendering. 2463 repeat : (bool) 2464 repeat of the texture when tcoords extend beyond the [0,1] range. 2465 edge_clamp : (bool) 2466 turn on/off the clamping of the texture map when 2467 the texture coords extend beyond the [0,1] range. 2468 Only used when repeat is False, and edge clamping is supported by the graphics card. 2469 scale : (bool) 2470 scale the texture image by this factor 2471 ushift : (bool) 2472 shift u-coordinates of texture by this amount 2473 vshift : (bool) 2474 shift v-coordinates of texture by this amount 2475 2476 Examples: 2477 - [texturecubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/texturecubes.py) 2478 2479 ![](https://vedo.embl.es/images/basic/texturecubes.png) 2480 """ 2481 pd = self.dataset 2482 out_img = None 2483 2484 if tname is None: # disable texture 2485 pd.GetPointData().SetTCoords(None) 2486 pd.GetPointData().Modified() 2487 return self ###################################### 2488 2489 if isinstance(tname, vtki.vtkTexture): 2490 tu = tname 2491 2492 elif isinstance(tname, vedo.Image): 2493 tu = vtki.vtkTexture() 2494 out_img = tname.dataset 2495 2496 elif utils.is_sequence(tname): 2497 tu = vtki.vtkTexture() 2498 out_img = vedo.image._get_img(tname) 2499 2500 elif isinstance(tname, str): 2501 tu = vtki.vtkTexture() 2502 2503 if "https://" in tname: 2504 try: 2505 tname = vedo.file_io.download(tname, verbose=False) 2506 except: 2507 vedo.logger.error(f"texture {tname} could not be downloaded") 2508 return self 2509 2510 fn = tname + ".jpg" 2511 if os.path.exists(tname): 2512 fn = tname 2513 else: 2514 vedo.logger.error(f"texture file {tname} does not exist") 2515 return self 2516 2517 fnl = fn.lower() 2518 if ".jpg" in fnl or ".jpeg" in fnl: 2519 reader = vtki.new("JPEGReader") 2520 elif ".png" in fnl: 2521 reader = vtki.new("PNGReader") 2522 elif ".bmp" in fnl: 2523 reader = vtki.new("BMPReader") 2524 else: 2525 vedo.logger.error("in texture() supported files are only PNG, BMP or JPG") 2526 return self 2527 reader.SetFileName(fn) 2528 reader.Update() 2529 out_img = reader.GetOutput() 2530 2531 else: 2532 vedo.logger.error(f"in texture() cannot understand input {type(tname)}") 2533 return self 2534 2535 if tcoords is not None: 2536 2537 if isinstance(tcoords, str): 2538 vtarr = pd.GetPointData().GetArray(tcoords) 2539 2540 else: 2541 tcoords = np.asarray(tcoords) 2542 if tcoords.ndim != 2: 2543 vedo.logger.error("tcoords must be a 2-dimensional array") 2544 return self 2545 if tcoords.shape[0] != pd.GetNumberOfPoints(): 2546 vedo.logger.error("nr of texture coords must match nr of points") 2547 return self 2548 if tcoords.shape[1] != 2: 2549 vedo.logger.error("tcoords texture vector must have 2 components") 2550 vtarr = utils.numpy2vtk(tcoords) 2551 vtarr.SetName("TCoordinates") 2552 2553 pd.GetPointData().SetTCoords(vtarr) 2554 pd.GetPointData().Modified() 2555 2556 elif not pd.GetPointData().GetTCoords(): 2557 2558 # TCoords still void.. 2559 # check that there are no texture-like arrays: 2560 names = self.pointdata.keys() 2561 candidate_arr = "" 2562 for name in names: 2563 vtarr = pd.GetPointData().GetArray(name) 2564 if vtarr.GetNumberOfComponents() != 2: 2565 continue 2566 t0, t1 = vtarr.GetRange() 2567 if t0 >= 0 and t1 <= 1: 2568 candidate_arr = name 2569 2570 if candidate_arr: 2571 2572 vtarr = pd.GetPointData().GetArray(candidate_arr) 2573 pd.GetPointData().SetTCoords(vtarr) 2574 pd.GetPointData().Modified() 2575 2576 else: 2577 # last resource is automatic mapping 2578 tmapper = vtki.new("TextureMapToPlane") 2579 tmapper.AutomaticPlaneGenerationOn() 2580 tmapper.SetInputData(pd) 2581 tmapper.Update() 2582 tc = tmapper.GetOutput().GetPointData().GetTCoords() 2583 if scale or ushift or vshift: 2584 ntc = utils.vtk2numpy(tc) 2585 if scale: 2586 ntc *= scale 2587 if ushift: 2588 ntc[:, 0] += ushift 2589 if vshift: 2590 ntc[:, 1] += vshift 2591 tc = utils.numpy2vtk(tc) 2592 pd.GetPointData().SetTCoords(tc) 2593 pd.GetPointData().Modified() 2594 2595 if out_img: 2596 tu.SetInputData(out_img) 2597 tu.SetInterpolate(interpolate) 2598 tu.SetRepeat(repeat) 2599 tu.SetEdgeClamp(edge_clamp) 2600 2601 self.properties.SetColor(1, 1, 1) 2602 self.mapper.ScalarVisibilityOff() 2603 self.actor.SetTexture(tu) 2604 2605 # if seam_threshold is not None: 2606 # tname = self.dataset.GetPointData().GetTCoords().GetName() 2607 # grad = self.gradient(tname) 2608 # ugrad, vgrad = np.split(grad, 2, axis=1) 2609 # ugradm, vgradm = utils.mag2(ugrad), utils.mag2(vgrad) 2610 # gradm = np.log(ugradm + vgradm) 2611 # largegrad_ids = np.arange(len(grad))[gradm > seam_threshold * 4] 2612 # uvmap = self.pointdata[tname] 2613 # # collapse triangles that have large gradient 2614 # new_points = self.vertices.copy() 2615 # for f in self.cells: 2616 # if np.isin(f, largegrad_ids).all(): 2617 # id1, id2, id3 = f 2618 # uv1, uv2, uv3 = uvmap[f] 2619 # d12 = utils.mag2(uv1 - uv2) 2620 # d23 = utils.mag2(uv2 - uv3) 2621 # d31 = utils.mag2(uv3 - uv1) 2622 # idm = np.argmin([d12, d23, d31]) 2623 # if idm == 0: 2624 # new_points[id1] = new_points[id3] 2625 # new_points[id2] = new_points[id3] 2626 # elif idm == 1: 2627 # new_points[id2] = new_points[id1] 2628 # new_points[id3] = new_points[id1] 2629 # self.vertices = new_points 2630 2631 self.dataset.Modified() 2632 return self 2633 2634######################################################################################## 2635class VolumeVisual(CommonVisual): 2636 """Class to manage the visual aspects of a ``Volume`` object.""" 2637 2638 def __init__(self) -> None: 2639 # print("INIT VolumeVisual") 2640 super().__init__() 2641 2642 def alpha_unit(self, u=None) -> Union[Self, float]: 2643 """ 2644 Defines light attenuation per unit length. Default is 1. 2645 The larger the unit length, the further light has to travel to attenuate the same amount. 2646 2647 E.g., if you set the unit distance to 0, you will get full opacity. 2648 It means that when light travels 0 distance it's already attenuated a finite amount. 2649 Thus, any finite distance should attenuate all light. 2650 The larger you make the unit distance, the more transparent the rendering becomes. 2651 """ 2652 if u is None: 2653 return self.properties.GetScalarOpacityUnitDistance() 2654 self.properties.SetScalarOpacityUnitDistance(u) 2655 return self 2656 2657 def alpha_gradient(self, alpha_grad, vmin=None, vmax=None) -> Self: 2658 """ 2659 Assign a set of tranparencies to a volume's gradient 2660 along the range of the scalar value. 2661 A single constant value can also be assigned. 2662 The gradient function is used to decrease the opacity 2663 in the "flat" regions of the volume while maintaining the opacity 2664 at the boundaries between material types. The gradient is measured 2665 as the amount by which the intensity changes over unit distance. 2666 2667 The format for alpha_grad is the same as for method `volume.alpha()`. 2668 """ 2669 if vmin is None: 2670 vmin, _ = self.dataset.GetScalarRange() 2671 if vmax is None: 2672 _, vmax = self.dataset.GetScalarRange() 2673 2674 if alpha_grad is None: 2675 self.properties.DisableGradientOpacityOn() 2676 return self 2677 2678 self.properties.DisableGradientOpacityOff() 2679 2680 gotf = self.properties.GetGradientOpacity() 2681 if utils.is_sequence(alpha_grad): 2682 alpha_grad = np.array(alpha_grad) 2683 if len(alpha_grad.shape) == 1: # user passing a flat list e.g. (0.0, 0.3, 0.9, 1) 2684 for i, al in enumerate(alpha_grad): 2685 xalpha = vmin + (vmax - vmin) * i / (len(alpha_grad) - 1) 2686 # Create transfer mapping scalar value to gradient opacity 2687 gotf.AddPoint(xalpha, al) 2688 elif len(alpha_grad.shape) == 2: # user passing [(x0,alpha0), ...] 2689 gotf.AddPoint(vmin, alpha_grad[0][1]) 2690 for xalpha, al in alpha_grad: 2691 # Create transfer mapping scalar value to opacity 2692 gotf.AddPoint(xalpha, al) 2693 gotf.AddPoint(vmax, alpha_grad[-1][1]) 2694 # print("alpha_grad at", round(xalpha, 1), "\tset to", al) 2695 else: 2696 gotf.AddPoint(vmin, alpha_grad) # constant alpha_grad 2697 gotf.AddPoint(vmax, alpha_grad) 2698 return self 2699 2700 def cmap(self, c, alpha=None, vmin=None, vmax=None) -> Self: 2701 """Same as `color()`. 2702 2703 Arguments: 2704 alpha : (list) 2705 use a list to specify transparencies along the scalar range 2706 vmin : (float) 2707 force the min of the scalar range to be this value 2708 vmax : (float) 2709 force the max of the scalar range to be this value 2710 """ 2711 return self.color(c, alpha, vmin, vmax) 2712 2713 def jittering(self, status=None) -> Union[Self, bool]: 2714 """ 2715 If `True`, each ray traversal direction will be perturbed slightly 2716 using a noise-texture to get rid of wood-grain effects. 2717 """ 2718 if hasattr(self.mapper, "SetUseJittering"): # tetmesh doesnt have it 2719 if status is None: 2720 return self.mapper.GetUseJittering() 2721 self.mapper.SetUseJittering(status) 2722 return self 2723 2724 def hide_voxels(self, ids) -> Self: 2725 """ 2726 Hide voxels (cells) from visualization. 2727 2728 Example: 2729 ```python 2730 from vedo import * 2731 embryo = Volume(dataurl+'embryo.tif') 2732 embryo.hide_voxels(list(range(400000))) 2733 show(embryo, axes=1).close() 2734 ``` 2735 2736 See also: 2737 `volume.mask()` 2738 """ 2739 ghost_mask = np.zeros(self.ncells, dtype=np.uint8) 2740 ghost_mask[ids] = vtki.vtkDataSetAttributes.HIDDENCELL 2741 name = vtki.vtkDataSetAttributes.GhostArrayName() 2742 garr = utils.numpy2vtk(ghost_mask, name=name, dtype=np.uint8) 2743 self.dataset.GetCellData().AddArray(garr) 2744 self.dataset.GetCellData().Modified() 2745 return self 2746 2747 def mask(self, data) -> Self: 2748 """ 2749 Mask a volume visualization with a binary value. 2750 Needs to specify `volume.mapper = "gpu"`. 2751 2752 Example: 2753 ```python 2754 from vedo import np, Volume, show 2755 data_matrix = np.zeros([75, 75, 75], dtype=np.uint8) 2756 # all voxels have value zero except: 2757 data_matrix[ 0:35, 0:35, 0:35] = 1 2758 data_matrix[35:55, 35:55, 35:55] = 2 2759 data_matrix[55:74, 55:74, 55:74] = 3 2760 vol = Volume(data_matrix).cmap('Blues') 2761 vol.mapper = "gpu" 2762 data_mask = np.zeros_like(data_matrix) 2763 data_mask[10:65, 10:60, 20:70] = 1 2764 vol.mask(data_mask) 2765 show(vol, axes=1).close() 2766 ``` 2767 See also: 2768 `volume.hide_voxels()` 2769 """ 2770 rdata = data.astype(np.uint8).ravel(order="F") 2771 varr = utils.numpy2vtk(rdata, name="input_mask") 2772 2773 img = vtki.vtkImageData() 2774 img.SetDimensions(self.dimensions()) 2775 img.GetPointData().AddArray(varr) 2776 img.GetPointData().SetActiveScalars(varr.GetName()) 2777 2778 try: 2779 self.mapper.SetMaskTypeToBinary() 2780 self.mapper.SetMaskInput(img) 2781 except AttributeError: 2782 vedo.logger.error("volume.mask() must specify volume.mapper = 'gpu'") 2783 return self 2784 2785 2786 def mode(self, mode=None) -> Union[Self, int]: 2787 """ 2788 Define the volumetric rendering mode following this: 2789 - 0, composite rendering 2790 - 1, maximum projection rendering 2791 - 2, minimum projection rendering 2792 - 3, average projection rendering 2793 - 4, additive mode 2794 2795 The default mode is "composite" where the scalar values are sampled through 2796 the volume and composited in a front-to-back scheme through alpha blending. 2797 The final color and opacity is determined using the color and opacity transfer 2798 functions specified in alpha keyword. 2799 2800 Maximum and minimum intensity blend modes use the maximum and minimum 2801 scalar values, respectively, along the sampling ray. 2802 The final color and opacity is determined by passing the resultant value 2803 through the color and opacity transfer functions. 2804 2805 Additive blend mode accumulates scalar values by passing each value 2806 through the opacity transfer function and then adding up the product 2807 of the value and its opacity. In other words, the scalar values are scaled 2808 using the opacity transfer function and summed to derive the final color. 2809 Note that the resulting image is always grayscale i.e. aggregated values 2810 are not passed through the color transfer function. 2811 This is because the final value is a derived value and not a real data value 2812 along the sampling ray. 2813 2814 Average intensity blend mode works similar to the additive blend mode where 2815 the scalar values are multiplied by opacity calculated from the opacity 2816 transfer function and then added. 2817 The additional step here is to divide the sum by the number of samples 2818 taken through the volume. 2819 As is the case with the additive intensity projection, the final image will 2820 always be grayscale i.e. the aggregated values are not passed through the 2821 color transfer function. 2822 """ 2823 if mode is None: 2824 return self.mapper.GetBlendMode() 2825 2826 if isinstance(mode, str): 2827 if "comp" in mode: 2828 mode = 0 2829 elif "proj" in mode: 2830 if "max" in mode: 2831 mode = 1 2832 elif "min" in mode: 2833 mode = 2 2834 elif "ave" in mode: 2835 mode = 3 2836 else: 2837 vedo.logger.warning(f"unknown mode {mode}") 2838 mode = 0 2839 elif "add" in mode: 2840 mode = 4 2841 else: 2842 vedo.logger.warning(f"unknown mode {mode}") 2843 mode = 0 2844 2845 self.mapper.SetBlendMode(mode) 2846 return self 2847 2848 def shade(self, status=None) -> Union[Self, bool]: 2849 """ 2850 Set/Get the shading of a Volume. 2851 Shading can be further controlled with `volume.lighting()` method. 2852 2853 If shading is turned on, the mapper may perform shading calculations. 2854 In some cases shading does not apply 2855 (for example, in maximum intensity projection mode). 2856 """ 2857 if status is None: 2858 return self.properties.GetShade() 2859 self.properties.SetShade(status) 2860 return self 2861 2862 def interpolation(self, itype) -> Self: 2863 """ 2864 Set interpolation type. 2865 2866 0=nearest neighbour, 1=linear 2867 """ 2868 self.properties.SetInterpolationType(itype) 2869 return self 2870 2871 2872######################################################################################## 2873class ImageVisual(CommonVisual, Actor3DHelper): 2874 2875 def __init__(self) -> None: 2876 # print("init ImageVisual") 2877 super().__init__() 2878 2879 def memory_size(self) -> int: 2880 """ 2881 Return the size in bytes of the object in memory. 2882 """ 2883 return self.dataset.GetActualMemorySize() 2884 2885 def scalar_range(self) -> np.ndarray: 2886 """ 2887 Return the scalar range of the image. 2888 """ 2889 return np.array(self.dataset.GetScalarRange()) 2890 2891 def alpha(self, a=None) -> Union[Self, float]: 2892 """Set/get image's transparency in the rendering scene.""" 2893 if a is not None: 2894 self.properties.SetOpacity(a) 2895 return self 2896 return self.properties.GetOpacity() 2897 2898 def level(self, value=None) -> Union[Self, float]: 2899 """Get/Set the image color level (brightness) in the rendering scene.""" 2900 if value is None: 2901 return self.properties.GetColorLevel() 2902 self.properties.SetColorLevel(value) 2903 return self 2904 2905 def window(self, value=None) -> Union[Self, float]: 2906 """Get/Set the image color window (contrast) in the rendering scene.""" 2907 if value is None: 2908 return self.properties.GetColorWindow() 2909 self.properties.SetColorWindow(value) 2910 return self 2911 2912 2913class LightKit: 2914 """ 2915 A LightKit consists of three lights, a 'key light', a 'fill light', and a 'head light'. 2916 2917 The main light is the key light. It is usually positioned so that it appears like 2918 an overhead light (like the sun, or a ceiling light). 2919 It is generally positioned to shine down on the scene from about a 45 degree angle vertically 2920 and at least a little offset side to side. The key light usually at least about twice as bright 2921 as the total of all other lights in the scene to provide good modeling of object features. 2922 2923 The other lights in the kit (the fill light, headlight, and a pair of back lights) 2924 are weaker sources that provide extra illumination to fill in the spots that the key light misses. 2925 The fill light is usually positioned across from or opposite from the key light 2926 (though still on the same side of the object as the camera) in order to simulate diffuse reflections 2927 from other objects in the scene. 2928 2929 The headlight, always located at the position of the camera, reduces the contrast between areas lit 2930 by the key and fill light. The two back lights, one on the left of the object as seen from the observer 2931 and one on the right, fill on the high-contrast areas behind the object. 2932 To enforce the relationship between the different lights, the intensity of the fill, back and headlights 2933 are set as a ratio to the key light brightness. 2934 Thus, the brightness of all the lights in the scene can be changed by changing the key light intensity. 2935 2936 All lights are directional lights, infinitely far away with no falloff. Lights move with the camera. 2937 2938 For simplicity, the position of lights in the LightKit can only be specified using angles: 2939 the elevation (latitude) and azimuth (longitude) of each light with respect to the camera, in degrees. 2940 For example, a light at (elevation=0, azimuth=0) is located at the camera (a headlight). 2941 A light at (elevation=90, azimuth=0) is above the lookat point, shining down. 2942 Negative azimuth values move the lights clockwise as seen above, positive values counter-clockwise. 2943 So, a light at (elevation=45, azimuth=-20) is above and in front of the object and shining 2944 slightly from the left side. 2945 2946 LightKit limits the colors that can be assigned to any light to those of incandescent sources such as 2947 light bulbs and sunlight. It defines a special color spectrum called "warmth" from which light colors 2948 can be chosen, where 0 is cold blue, 0.5 is neutral white, and 1 is deep sunset red. 2949 Colors close to 0.5 are "cool whites" and "warm whites," respectively. 2950 2951 Since colors far from white on the warmth scale appear less bright, key-to-fill and key-to-headlight 2952 ratios are skewed by key, fill, and headlight colors. If `maintain_luminance` is set, LightKit will 2953 attempt to compensate for these perceptual differences by increasing the brightness of more saturated colors. 2954 2955 To specify the color of a light, positioning etc you can pass a dictionary with the following keys: 2956 - `intensity` : (float) The intensity of the key light. Default is 1. 2957 - `ratio` : (float) The ratio of the light intensity wrt key light. 2958 - `warmth` : (float) The warmth of the light. Default is 0.5. 2959 - `elevation` : (float) The elevation of the light in degrees. 2960 - `azimuth` : (float) The azimuth of the light in degrees. 2961 2962 Example: 2963 ```python 2964 from vedo import * 2965 lightkit = LightKit(head={"warmth":0.6}) 2966 mesh = Mesh(dataurl+"bunny.obj") 2967 plt = Plotter() 2968 plt.remove_lights().add(mesh, lightkit) 2969 plt.show().close() 2970 ``` 2971 """ 2972 def __init__(self, key=(), fill=(), back=(), head=(), maintain_luminance=False) -> None: 2973 2974 self.lightkit = vtki.new("LightKit") 2975 self.lightkit.SetMaintainLuminance(maintain_luminance) 2976 self.key = dict(key) 2977 self.head = dict(head) 2978 self.fill = dict(fill) 2979 self.back = dict(back) 2980 self.update() 2981 2982 def update(self) -> None: 2983 """Update the LightKit properties.""" 2984 if "warmth" in self.key: 2985 self.lightkit.SetKeyLightWarmth(self.key["warmth"]) 2986 if "warmth" in self.fill: 2987 self.lightkit.SetFillLightWarmth(self.fill["warmth"]) 2988 if "warmth" in self.head: 2989 self.lightkit.SetHeadLightWarmth(self.head["warmth"]) 2990 if "warmth" in self.back: 2991 self.lightkit.SetBackLightWarmth(self.back["warmth"]) 2992 2993 if "intensity" in self.key: 2994 self.lightkit.SetKeyLightIntensity(self.key["intensity"]) 2995 if "ratio" in self.fill: 2996 self.lightkit.SetKeyToFillRatio(self.key["ratio"]) 2997 if "ratio" in self.head: 2998 self.lightkit.SetKeyToHeadRatio(self.key["ratio"]) 2999 if "ratio" in self.back: 3000 self.lightkit.SetKeyToBackRatio(self.key["ratio"]) 3001 3002 if "elevation" in self.key: 3003 self.lightkit.SetKeyLightElevation(self.key["elevation"]) 3004 if "elevation" in self.fill: 3005 self.lightkit.SetFillLightElevation(self.fill["elevation"]) 3006 if "elevation" in self.head: 3007 self.lightkit.SetHeadLightElevation(self.head["elevation"]) 3008 if "elevation" in self.back: 3009 self.lightkit.SetBackLightElevation(self.back["elevation"]) 3010 3011 if "azimuth" in self.key: 3012 self.lightkit.SetKeyLightAzimuth(self.key["azimuth"]) 3013 if "azimuth" in self.fill: 3014 self.lightkit.SetFillLightAzimuth(self.fill["azimuth"]) 3015 if "azimuth" in self.head: 3016 self.lightkit.SetHeadLightAzimuth(self.head["azimuth"]) 3017 if "azimuth" in self.back: 3018 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 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 self.trail_points.append(currentpos) # cycle 1511 self.trail_points.pop(0) 1512 data = np.array(self.trail_points) + self.trail_offset 1513 tpoly = self.trail.dataset 1514 tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32)) 1515 return self 1516 1517 def _compute_shadow(self, plane, point, direction): 1518 shad = self.clone() 1519 shad.name = "Shadow" 1520 1521 tarr = shad.dataset.GetPointData().GetTCoords() 1522 if tarr: # remove any texture coords 1523 tname = tarr.GetName() 1524 shad.pointdata.remove(tname) 1525 shad.dataset.GetPointData().SetTCoords(None) 1526 shad.actor.SetTexture(None) 1527 1528 pts = shad.vertices 1529 if plane == "x": 1530 # shad = shad.project_on_plane('x') 1531 # instead do it manually so in case of alpha<1 1532 # we dont see glitches due to coplanar points 1533 # we leave a small tolerance of 0.1% in thickness 1534 x0, x1 = self.xbounds() 1535 pts[:, 0] = (pts[:, 0] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[0] 1536 shad.vertices = pts 1537 shad.x(point) 1538 elif plane == "y": 1539 x0, x1 = self.ybounds() 1540 pts[:, 1] = (pts[:, 1] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[1] 1541 shad.vertices = pts 1542 shad.y(point) 1543 elif plane == "z": 1544 x0, x1 = self.zbounds() 1545 pts[:, 2] = (pts[:, 2] - (x0 + x1) / 2) / 1000 + self.actor.GetOrigin()[2] 1546 shad.vertices = pts 1547 shad.z(point) 1548 else: 1549 shad = shad.project_on_plane(plane, point, direction) 1550 return shad 1551 1552 def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0) -> Self: 1553 """ 1554 Generate a shadow out of an `Mesh` on one of the three Cartesian planes. 1555 The output is a new `Mesh` representing the shadow. 1556 This new mesh is accessible through `mesh.shadow`. 1557 By default the shadow mesh is placed on the bottom wall of the bounding box. 1558 1559 See also `pointcloud.project_on_plane()`. 1560 1561 Arguments: 1562 plane : (str, Plane) 1563 if plane is `str`, plane can be one of `['x', 'y', 'z']`, 1564 represents x-plane, y-plane and z-plane, respectively. 1565 Otherwise, plane should be an instance of `vedo.shapes.Plane` 1566 point : (float, array) 1567 if plane is `str`, point should be a float represents the intercept. 1568 Otherwise, point is the camera point of perspective projection 1569 direction : (list) 1570 direction of oblique projection 1571 culling : (int) 1572 choose between front [1] or backface [-1] culling or None. 1573 1574 Examples: 1575 - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py) 1576 - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py) 1577 - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py) 1578 1579 ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif) 1580 """ 1581 shad = self._compute_shadow(plane, point, direction) 1582 shad.c(c).alpha(alpha) 1583 1584 try: 1585 # Points dont have these methods 1586 shad.flat() 1587 if culling in (1, True): 1588 shad.frontface_culling() 1589 elif culling == -1: 1590 shad.backface_culling() 1591 except AttributeError: 1592 pass 1593 1594 shad.properties.LightingOff() 1595 shad.actor.SetPickable(False) 1596 shad.actor.SetUseBounds(True) 1597 1598 if shad not in self.shadows: 1599 self.shadows.append(shad) 1600 shad.info = dict(plane=plane, point=point, direction=direction) 1601 # shad.metadata["plane"] = plane 1602 # shad.metadata["point"] = point 1603 # print("AAAA", direction, plane, point) 1604 # if direction is None: 1605 # direction = [0,0,0] 1606 # shad.metadata["direction"] = direction 1607 return self 1608 1609 def update_shadows(self) -> Self: 1610 """Update the shadows of a moving object.""" 1611 for sha in self.shadows: 1612 plane = sha.info["plane"] 1613 point = sha.info["point"] 1614 direction = sha.info["direction"] 1615 # print("update_shadows direction", direction,plane,point ) 1616 # plane = sha.metadata["plane"] 1617 # point = sha.metadata["point"] 1618 # direction = sha.metadata["direction"] 1619 # if direction[0]==0 and direction[1]==0 and direction[2]==0: 1620 # direction = None 1621 # print("BBBB", sha.metadata["direction"], 1622 # sha.metadata["plane"], sha.metadata["point"]) 1623 new_sha = self._compute_shadow(plane, point, direction) 1624 sha._update(new_sha.dataset) 1625 if self.trail: 1626 self.trail.update_shadows() 1627 return self 1628 1629 def labels( 1630 self, 1631 content=None, 1632 on="points", 1633 scale=None, 1634 xrot=0.0, 1635 yrot=0.0, 1636 zrot=0.0, 1637 ratio=1, 1638 precision=None, 1639 italic=False, 1640 font="", 1641 justify="", 1642 c="black", 1643 alpha=1.0, 1644 ) -> Union["vedo.Mesh", None]: 1645 """ 1646 Generate value or ID labels for mesh cells or points. 1647 For large nr. of labels use `font="VTK"` which is much faster. 1648 1649 See also: 1650 `labels2d()`, `flagpole()`, `caption()` and `legend()`. 1651 1652 Arguments: 1653 content : (list,int,str) 1654 either 'id', 'cellid', array name or array number. 1655 A array can also be passed (must match the nr. of points or cells). 1656 on : (str) 1657 generate labels for "cells" instead of "points" 1658 scale : (float) 1659 absolute size of labels, if left as None it is automatic 1660 zrot : (float) 1661 local rotation angle of label in degrees 1662 ratio : (int) 1663 skipping ratio, to reduce nr of labels for large meshes 1664 precision : (int) 1665 numeric precision of labels 1666 1667 ```python 1668 from vedo import * 1669 s = Sphere(res=10).linewidth(1).c("orange").compute_normals() 1670 point_ids = s.labels('id', on="points").c('green') 1671 cell_ids = s.labels('id', on="cells" ).c('black') 1672 show(s, point_ids, cell_ids) 1673 ``` 1674 ![](https://vedo.embl.es/images/feats/labels.png) 1675 1676 Examples: 1677 - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py) 1678 1679 ![](https://vedo.embl.es/images/basic/boundaries.png) 1680 """ 1681 1682 cells = False 1683 if "cell" in on or "face" in on: 1684 cells = True 1685 justify = "centered" if justify == "" else justify 1686 1687 if isinstance(content, str): 1688 if content in ("pointid", "pointsid"): 1689 cells = False 1690 content = "id" 1691 justify = "bottom-left" if justify == "" else justify 1692 if content in ("cellid", "cellsid"): 1693 cells = True 1694 content = "id" 1695 justify = "centered" if justify == "" else justify 1696 1697 try: 1698 if cells: 1699 ns = np.sqrt(self.ncells) 1700 elems = self.cell_centers 1701 norms = self.cell_normals 1702 justify = "centered" if justify == "" else justify 1703 else: 1704 ns = np.sqrt(self.npoints) 1705 elems = self.vertices 1706 norms = self.vertex_normals 1707 except AttributeError: 1708 norms = [] 1709 1710 if not justify: 1711 justify = "bottom-left" 1712 1713 hasnorms = False 1714 if len(norms) > 0: 1715 hasnorms = True 1716 1717 if scale is None: 1718 if not ns: 1719 ns = 100 1720 scale = self.diagonal_size() / ns / 10 1721 1722 arr = None 1723 mode = 0 1724 if content is None: 1725 mode = 0 1726 if cells: 1727 if self.dataset.GetCellData().GetScalars(): 1728 name = self.dataset.GetCellData().GetScalars().GetName() 1729 arr = self.celldata[name] 1730 else: 1731 if self.dataset.GetPointData().GetScalars(): 1732 name = self.dataset.GetPointData().GetScalars().GetName() 1733 arr = self.pointdata[name] 1734 elif isinstance(content, (str, int)): 1735 if content == "id": 1736 mode = 1 1737 elif cells: 1738 mode = 0 1739 arr = self.celldata[content] 1740 else: 1741 mode = 0 1742 arr = self.pointdata[content] 1743 elif utils.is_sequence(content): 1744 mode = 0 1745 arr = content 1746 1747 if arr is None and mode == 0: 1748 vedo.logger.error("in labels(), array not found in point or cell data") 1749 return None 1750 1751 ratio = int(ratio+0.5) 1752 tapp = vtki.new("AppendPolyData") 1753 has_inputs = False 1754 1755 for i, e in enumerate(elems): 1756 if i % ratio: 1757 continue 1758 1759 if mode == 1: 1760 txt_lab = str(i) 1761 else: 1762 if precision: 1763 txt_lab = utils.precision(arr[i], precision) 1764 else: 1765 txt_lab = str(arr[i]) 1766 1767 if not txt_lab: 1768 continue 1769 1770 if font == "VTK": 1771 tx = vtki.new("VectorText") 1772 tx.SetText(txt_lab) 1773 tx.Update() 1774 tx_poly = tx.GetOutput() 1775 else: 1776 tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify).dataset 1777 1778 if tx_poly.GetNumberOfPoints() == 0: 1779 continue ###################### 1780 1781 T = vtki.vtkTransform() 1782 T.PostMultiply() 1783 if italic: 1784 T.Concatenate([1, 0.2, 0, 0, 1785 0, 1 , 0, 0, 1786 0, 0 , 1, 0, 1787 0, 0 , 0, 1]) 1788 if hasnorms: 1789 ni = norms[i] 1790 if cells and font=="VTK": # center-justify 1791 bb = tx_poly.GetBounds() 1792 dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2 1793 T.Translate(-dx, -dy, 0) 1794 if xrot: T.RotateX(xrot) 1795 if yrot: T.RotateY(yrot) 1796 if zrot: T.RotateZ(zrot) 1797 crossvec = np.cross([0, 0, 1], ni) 1798 angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3 1799 T.RotateWXYZ(float(angle), crossvec.tolist()) 1800 T.Translate(ni / 100) 1801 else: 1802 if xrot: T.RotateX(xrot) 1803 if yrot: T.RotateY(yrot) 1804 if zrot: T.RotateZ(zrot) 1805 T.Scale(scale, scale, scale) 1806 T.Translate(e) 1807 tf = vtki.new("TransformPolyDataFilter") 1808 tf.SetInputData(tx_poly) 1809 tf.SetTransform(T) 1810 tf.Update() 1811 tapp.AddInputData(tf.GetOutput()) 1812 has_inputs = True 1813 1814 if has_inputs: 1815 tapp.Update() 1816 lpoly = tapp.GetOutput() 1817 else: 1818 lpoly = vtki.vtkPolyData() 1819 ids = vedo.Mesh(lpoly, c=c, alpha=alpha) 1820 ids.properties.LightingOff() 1821 ids.actor.PickableOff() 1822 ids.actor.SetUseBounds(False) 1823 ids.name = "Labels" 1824 return ids 1825 1826 def labels2d( 1827 self, 1828 content="id", 1829 on="points", 1830 scale=1.0, 1831 precision=4, 1832 font="Calco", 1833 justify="bottom-left", 1834 angle=0.0, 1835 frame=False, 1836 c="black", 1837 bc=None, 1838 alpha=1.0, 1839 ) -> Union["Actor2D", None]: 1840 """ 1841 Generate value or ID bi-dimensional labels for mesh cells or points. 1842 1843 See also: `labels()`, `flagpole()`, `caption()` and `legend()`. 1844 1845 Arguments: 1846 content : (str) 1847 either 'id', 'cellid', or array name 1848 on : (str) 1849 generate labels for "cells" instead of "points" (the default) 1850 scale : (float) 1851 size scaling of labels 1852 precision : (int) 1853 precision of numeric labels 1854 angle : (float) 1855 local rotation angle of label in degrees 1856 frame : (bool) 1857 draw a frame around the label 1858 bc : (str) 1859 background color of the label 1860 1861 ```python 1862 from vedo import Sphere, show 1863 sph = Sphere(quads=True, res=4).compute_normals().wireframe() 1864 sph.celldata["zvals"] = sph.cell_centers[:,2] 1865 l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9') 1866 show(sph, l2d, axes=1).close() 1867 ``` 1868 ![](https://vedo.embl.es/images/feats/labels2d.png) 1869 """ 1870 cells = False 1871 if "cell" in on: 1872 cells = True 1873 1874 if isinstance(content, str): 1875 if content in ("id", "pointid", "pointsid"): 1876 cells = False 1877 content = "id" 1878 if content in ("cellid", "cellsid"): 1879 cells = True 1880 content = "id" 1881 1882 if cells: 1883 if content != "id" and content not in self.celldata.keys(): 1884 vedo.logger.error(f"In labels2d: cell array {content} does not exist.") 1885 return None 1886 cellcloud = vedo.Points(self.cell_centers) 1887 arr = self.dataset.GetCellData().GetScalars() 1888 poly = cellcloud.dataset 1889 poly.GetPointData().SetScalars(arr) 1890 else: 1891 poly = self.dataset 1892 if content != "id" and content not in self.pointdata.keys(): 1893 vedo.logger.error(f"In labels2d: point array {content} does not exist.") 1894 return None 1895 1896 mp = vtki.new("LabeledDataMapper") 1897 1898 if content == "id": 1899 mp.SetLabelModeToLabelIds() 1900 else: 1901 mp.SetLabelModeToLabelScalars() 1902 if precision is not None: 1903 mp.SetLabelFormat(f"%-#.{precision}g") 1904 1905 pr = mp.GetLabelTextProperty() 1906 c = colors.get_color(c) 1907 pr.SetColor(c) 1908 pr.SetOpacity(alpha) 1909 pr.SetFrame(frame) 1910 pr.SetFrameColor(c) 1911 pr.SetItalic(False) 1912 pr.BoldOff() 1913 pr.ShadowOff() 1914 pr.UseTightBoundingBoxOn() 1915 pr.SetOrientation(angle) 1916 pr.SetFontFamily(vtki.VTK_FONT_FILE) 1917 fl = utils.get_font_path(font) 1918 pr.SetFontFile(fl) 1919 pr.SetFontSize(int(20 * scale)) 1920 1921 if "cent" in justify or "mid" in justify: 1922 pr.SetJustificationToCentered() 1923 elif "rig" in justify: 1924 pr.SetJustificationToRight() 1925 elif "left" in justify: 1926 pr.SetJustificationToLeft() 1927 # ------ 1928 if "top" in justify: 1929 pr.SetVerticalJustificationToTop() 1930 else: 1931 pr.SetVerticalJustificationToBottom() 1932 1933 if bc is not None: 1934 bc = colors.get_color(bc) 1935 pr.SetBackgroundColor(bc) 1936 pr.SetBackgroundOpacity(alpha) 1937 1938 mp.SetInputData(poly) 1939 a2d = Actor2D() 1940 a2d.PickableOff() 1941 a2d.SetMapper(mp) 1942 return a2d 1943 1944 def legend(self, txt) -> Self: 1945 """Book a legend text.""" 1946 self.info["legend"] = txt 1947 # self.metadata["legend"] = txt 1948 return self 1949 1950 def flagpole( 1951 self, 1952 txt=None, 1953 point=None, 1954 offset=None, 1955 s=None, 1956 font="Calco", 1957 rounded=True, 1958 c=None, 1959 alpha=1.0, 1960 lw=2, 1961 italic=0.0, 1962 padding=0.1, 1963 ) -> Union["vedo.Mesh", None]: 1964 """ 1965 Generate a flag pole style element to describe an object. 1966 Returns a `Mesh` object. 1967 1968 Use flagpole.follow_camera() to make it face the camera in the scene. 1969 1970 Consider using `settings.use_parallel_projection = True` 1971 to avoid perspective distortions. 1972 1973 See also `flagpost()`. 1974 1975 Arguments: 1976 txt : (str) 1977 Text to display. The default is the filename or the object name. 1978 point : (list) 1979 position of the flagpole pointer. 1980 offset : (list) 1981 text offset wrt the application point. 1982 s : (float) 1983 size of the flagpole. 1984 font : (str) 1985 font face. Check [available fonts here](https://vedo.embl.es/fonts). 1986 rounded : (bool) 1987 draw a rounded or squared box around the text. 1988 c : (list) 1989 text and box color. 1990 alpha : (float) 1991 opacity of text and box. 1992 lw : (float) 1993 line with of box frame. 1994 italic : (float) 1995 italicness of text. 1996 1997 Examples: 1998 - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py) 1999 2000 ![](https://vedo.embl.es/images/pyplot/intersect2d.png) 2001 2002 - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py) 2003 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 2004 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 2005 """ 2006 objs = [] 2007 2008 if txt is None: 2009 if self.filename: 2010 txt = self.filename.split("/")[-1] 2011 elif self.name: 2012 txt = self.name 2013 else: 2014 return None 2015 2016 x0, x1, y0, y1, z0, z1 = self.bounds() 2017 d = self.diagonal_size() 2018 if point is None: 2019 if d: 2020 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2021 # point = self.closest_point([x1, y0, z1]) 2022 else: # it's a Point 2023 point = self.transform.position 2024 2025 pt = utils.make3d(point) 2026 2027 if offset is None: 2028 offset = [(x1 - x0) / 1.75, (y1 - y0) / 5, 0] 2029 offset = utils.make3d(offset) 2030 2031 if s is None: 2032 s = d / 20 2033 2034 sph = None 2035 if d and (z1 - z0) / d > 0.1: 2036 sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6) 2037 2038 if c is None: 2039 c = np.array(self.color()) / 1.4 2040 2041 lab = vedo.shapes.Text3D( 2042 txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center" 2043 ) 2044 objs.append(lab) 2045 2046 if d and not sph: 2047 sph = vedo.shapes.Circle(pt, r=s / 3, res=16) 2048 objs.append(sph) 2049 2050 x0, x1, y0, y1, z0, z1 = lab.bounds() 2051 aline = [(x0,y0,z0), (x1,y0,z0), (x1,y1,z0), (x0,y1,z0)] 2052 if rounded: 2053 box = vedo.shapes.KSpline(aline, closed=True) 2054 else: 2055 box = vedo.shapes.Line(aline, closed=True) 2056 2057 cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2] 2058 2059 # box.actor.SetOrigin(cnt) 2060 box.scale([1 + padding, 1 + 2 * padding, 1], origin=cnt) 2061 objs.append(box) 2062 2063 x0, x1, y0, y1, z0, z1 = box.bounds() 2064 if x0 < pt[0] < x1: 2065 c0 = box.closest_point(pt) 2066 c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]] 2067 elif (pt[0] - x0) < (x1 - pt[0]): 2068 c0 = [x0, (y0 + y1) / 2, pt[2]] 2069 c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]] 2070 else: 2071 c0 = [x1, (y0 + y1) / 2, pt[2]] 2072 c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]] 2073 2074 con = vedo.shapes.Line([c0, c1, pt]) 2075 objs.append(con) 2076 2077 mobjs = vedo.merge(objs).c(c).alpha(alpha) 2078 mobjs.name = "FlagPole" 2079 mobjs.bc("tomato").pickable(False) 2080 mobjs.properties.LightingOff() 2081 mobjs.properties.SetLineWidth(lw) 2082 mobjs.actor.UseBoundsOff() 2083 mobjs.actor.SetPosition([0,0,0]) 2084 mobjs.actor.SetOrigin(pt) 2085 return mobjs 2086 2087 # mobjs = vedo.Assembly(objs)#.c(c).alpha(alpha) 2088 # mobjs.name = "FlagPole" 2089 # # mobjs.bc("tomato").pickable(False) 2090 # # mobjs.properties.LightingOff() 2091 # # mobjs.properties.SetLineWidth(lw) 2092 # # mobjs.actor.UseBoundsOff() 2093 # # mobjs.actor.SetPosition([0,0,0]) 2094 # # mobjs.actor.SetOrigin(pt) 2095 # # print(pt) 2096 # return mobjs 2097 2098 def flagpost( 2099 self, 2100 txt=None, 2101 point=None, 2102 offset=None, 2103 s=1.0, 2104 c="k9", 2105 bc="k1", 2106 alpha=1, 2107 lw=0, 2108 font="Calco", 2109 justify="center-left", 2110 vspacing=1.0, 2111 ) -> Union["vedo.addons.Flagpost", None]: 2112 """ 2113 Generate a flag post style element to describe an object. 2114 2115 Arguments: 2116 txt : (str) 2117 Text to display. The default is the filename or the object name. 2118 point : (list) 2119 position of the flag anchor point. The default is None. 2120 offset : (list) 2121 a 3D displacement or offset. The default is None. 2122 s : (float) 2123 size of the text to be shown 2124 c : (list) 2125 color of text and line 2126 bc : (list) 2127 color of the flag background 2128 alpha : (float) 2129 opacity of text and box. 2130 lw : (int) 2131 line with of box frame. The default is 0. 2132 font : (str) 2133 font name. Use a monospace font for better rendering. The default is "Calco". 2134 Type `vedo -r fonts` for a font demo. 2135 Check [available fonts here](https://vedo.embl.es/fonts). 2136 justify : (str) 2137 internal text justification. The default is "center-left". 2138 vspacing : (float) 2139 vertical spacing between lines. 2140 2141 Examples: 2142 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py) 2143 2144 ![](https://vedo.embl.es/images/other/flag_labels2.png) 2145 """ 2146 if txt is None: 2147 if self.filename: 2148 txt = self.filename.split("/")[-1] 2149 elif self.name: 2150 txt = self.name 2151 else: 2152 return None 2153 2154 x0, x1, y0, y1, z0, z1 = self.bounds() 2155 d = self.diagonal_size() 2156 if point is None: 2157 if d: 2158 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2159 else: # it's a Point 2160 point = self.transform.position 2161 2162 point = utils.make3d(point) 2163 2164 if offset is None: 2165 offset = [0, 0, (z1 - z0) / 2] 2166 offset = utils.make3d(offset) 2167 2168 fpost = vedo.addons.Flagpost( 2169 txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing 2170 ) 2171 self._caption = fpost 2172 return fpost 2173 2174 def caption( 2175 self, 2176 txt=None, 2177 point=None, 2178 size=(0.30, 0.15), 2179 padding=5, 2180 font="Calco", 2181 justify="center-right", 2182 vspacing=1.0, 2183 c=None, 2184 alpha=1.0, 2185 lw=1, 2186 ontop=True, 2187 ) -> Union["vtki.vtkCaptionActor2D", None]: 2188 """ 2189 Create a 2D caption to an object which follows the camera movements. 2190 Latex is not supported. Returns the same input object for concatenation. 2191 2192 See also `flagpole()`, `flagpost()`, `labels()` and `legend()` 2193 with similar functionality. 2194 2195 Arguments: 2196 txt : (str) 2197 text to be rendered. The default is the file name. 2198 point : (list) 2199 anchoring point. The default is None. 2200 size : (list) 2201 (width, height) of the caption box. The default is (0.30, 0.15). 2202 padding : (float) 2203 padding space of the caption box in pixels. The default is 5. 2204 font : (str) 2205 font name. Use a monospace font for better rendering. The default is "VictorMono". 2206 Type `vedo -r fonts` for a font demo. 2207 Check [available fonts here](https://vedo.embl.es/fonts). 2208 justify : (str) 2209 internal text justification. The default is "center-right". 2210 vspacing : (float) 2211 vertical spacing between lines. The default is 1. 2212 c : (str) 2213 text and box color. The default is 'lb'. 2214 alpha : (float) 2215 text and box transparency. The default is 1. 2216 lw : (int) 2217 line width in pixels. The default is 1. 2218 ontop : (bool) 2219 keep the 2d caption always on top. The default is True. 2220 2221 Examples: 2222 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 2223 2224 ![](https://vedo.embl.es/images/pyplot/caption.png) 2225 2226 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 2227 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 2228 """ 2229 if txt is None: 2230 if self.filename: 2231 txt = self.filename.split("/")[-1] 2232 elif self.name: 2233 txt = self.name 2234 2235 if not txt: # disable it 2236 self._caption = None 2237 return None 2238 2239 for r in vedo.shapes._reps: 2240 txt = txt.replace(r[0], r[1]) 2241 2242 if c is None: 2243 c = np.array(self.properties.GetColor()) / 2 2244 else: 2245 c = colors.get_color(c) 2246 2247 if point is None: 2248 x0, x1, y0, y1, _, z1 = self.dataset.GetBounds() 2249 pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1] 2250 point = self.closest_point(pt) 2251 2252 capt = vtki.vtkCaptionActor2D() 2253 capt.SetAttachmentPoint(point) 2254 capt.SetBorder(True) 2255 capt.SetLeader(True) 2256 sph = vtki.new("SphereSource") 2257 sph.Update() 2258 capt.SetLeaderGlyphData(sph.GetOutput()) 2259 capt.SetMaximumLeaderGlyphSize(5) 2260 capt.SetPadding(int(padding)) 2261 capt.SetCaption(txt) 2262 capt.SetWidth(size[0]) 2263 capt.SetHeight(size[1]) 2264 capt.SetThreeDimensionalLeader(not ontop) 2265 2266 pra = capt.GetProperty() 2267 pra.SetColor(c) 2268 pra.SetOpacity(alpha) 2269 pra.SetLineWidth(lw) 2270 2271 pr = capt.GetCaptionTextProperty() 2272 pr.SetFontFamily(vtki.VTK_FONT_FILE) 2273 fl = utils.get_font_path(font) 2274 pr.SetFontFile(fl) 2275 pr.ShadowOff() 2276 pr.BoldOff() 2277 pr.FrameOff() 2278 pr.SetColor(c) 2279 pr.SetOpacity(alpha) 2280 pr.SetJustificationToLeft() 2281 if "top" in justify: 2282 pr.SetVerticalJustificationToTop() 2283 if "bottom" in justify: 2284 pr.SetVerticalJustificationToBottom() 2285 if "cent" in justify: 2286 pr.SetVerticalJustificationToCentered() 2287 pr.SetJustificationToCentered() 2288 if "left" in justify: 2289 pr.SetJustificationToLeft() 2290 if "right" in justify: 2291 pr.SetJustificationToRight() 2292 pr.SetLineSpacing(vspacing) 2293 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 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:
1505 def update_trail(self) -> Self: 1506 """ 1507 Update the trailing line of a moving object. 1508 """ 1509 currentpos = self.pos() 1510 self.trail_points.append(currentpos) # cycle 1511 self.trail_points.pop(0) 1512 data = np.array(self.trail_points) + self.trail_offset 1513 tpoly = self.trail.dataset 1514 tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32)) 1515 return self
Update the trailing line of a moving object.
1552 def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0) -> Self: 1553 """ 1554 Generate a shadow out of an `Mesh` on one of the three Cartesian planes. 1555 The output is a new `Mesh` representing the shadow. 1556 This new mesh is accessible through `mesh.shadow`. 1557 By default the shadow mesh is placed on the bottom wall of the bounding box. 1558 1559 See also `pointcloud.project_on_plane()`. 1560 1561 Arguments: 1562 plane : (str, Plane) 1563 if plane is `str`, plane can be one of `['x', 'y', 'z']`, 1564 represents x-plane, y-plane and z-plane, respectively. 1565 Otherwise, plane should be an instance of `vedo.shapes.Plane` 1566 point : (float, array) 1567 if plane is `str`, point should be a float represents the intercept. 1568 Otherwise, point is the camera point of perspective projection 1569 direction : (list) 1570 direction of oblique projection 1571 culling : (int) 1572 choose between front [1] or backface [-1] culling or None. 1573 1574 Examples: 1575 - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py) 1576 - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py) 1577 - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py) 1578 1579 ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif) 1580 """ 1581 shad = self._compute_shadow(plane, point, direction) 1582 shad.c(c).alpha(alpha) 1583 1584 try: 1585 # Points dont have these methods 1586 shad.flat() 1587 if culling in (1, True): 1588 shad.frontface_culling() 1589 elif culling == -1: 1590 shad.backface_culling() 1591 except AttributeError: 1592 pass 1593 1594 shad.properties.LightingOff() 1595 shad.actor.SetPickable(False) 1596 shad.actor.SetUseBounds(True) 1597 1598 if shad not in self.shadows: 1599 self.shadows.append(shad) 1600 shad.info = dict(plane=plane, point=point, direction=direction) 1601 # shad.metadata["plane"] = plane 1602 # shad.metadata["point"] = point 1603 # print("AAAA", direction, plane, point) 1604 # if direction is None: 1605 # direction = [0,0,0] 1606 # shad.metadata["direction"] = direction 1607 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:
1609 def update_shadows(self) -> Self: 1610 """Update the shadows of a moving object.""" 1611 for sha in self.shadows: 1612 plane = sha.info["plane"] 1613 point = sha.info["point"] 1614 direction = sha.info["direction"] 1615 # print("update_shadows direction", direction,plane,point ) 1616 # plane = sha.metadata["plane"] 1617 # point = sha.metadata["point"] 1618 # direction = sha.metadata["direction"] 1619 # if direction[0]==0 and direction[1]==0 and direction[2]==0: 1620 # direction = None 1621 # print("BBBB", sha.metadata["direction"], 1622 # sha.metadata["plane"], sha.metadata["point"]) 1623 new_sha = self._compute_shadow(plane, point, direction) 1624 sha._update(new_sha.dataset) 1625 if self.trail: 1626 self.trail.update_shadows() 1627 return self
Update the shadows of a moving object.
1629 def labels( 1630 self, 1631 content=None, 1632 on="points", 1633 scale=None, 1634 xrot=0.0, 1635 yrot=0.0, 1636 zrot=0.0, 1637 ratio=1, 1638 precision=None, 1639 italic=False, 1640 font="", 1641 justify="", 1642 c="black", 1643 alpha=1.0, 1644 ) -> Union["vedo.Mesh", None]: 1645 """ 1646 Generate value or ID labels for mesh cells or points. 1647 For large nr. of labels use `font="VTK"` which is much faster. 1648 1649 See also: 1650 `labels2d()`, `flagpole()`, `caption()` and `legend()`. 1651 1652 Arguments: 1653 content : (list,int,str) 1654 either 'id', 'cellid', array name or array number. 1655 A array can also be passed (must match the nr. of points or cells). 1656 on : (str) 1657 generate labels for "cells" instead of "points" 1658 scale : (float) 1659 absolute size of labels, if left as None it is automatic 1660 zrot : (float) 1661 local rotation angle of label in degrees 1662 ratio : (int) 1663 skipping ratio, to reduce nr of labels for large meshes 1664 precision : (int) 1665 numeric precision of labels 1666 1667 ```python 1668 from vedo import * 1669 s = Sphere(res=10).linewidth(1).c("orange").compute_normals() 1670 point_ids = s.labels('id', on="points").c('green') 1671 cell_ids = s.labels('id', on="cells" ).c('black') 1672 show(s, point_ids, cell_ids) 1673 ``` 1674 ![](https://vedo.embl.es/images/feats/labels.png) 1675 1676 Examples: 1677 - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py) 1678 1679 ![](https://vedo.embl.es/images/basic/boundaries.png) 1680 """ 1681 1682 cells = False 1683 if "cell" in on or "face" in on: 1684 cells = True 1685 justify = "centered" if justify == "" else justify 1686 1687 if isinstance(content, str): 1688 if content in ("pointid", "pointsid"): 1689 cells = False 1690 content = "id" 1691 justify = "bottom-left" if justify == "" else justify 1692 if content in ("cellid", "cellsid"): 1693 cells = True 1694 content = "id" 1695 justify = "centered" if justify == "" else justify 1696 1697 try: 1698 if cells: 1699 ns = np.sqrt(self.ncells) 1700 elems = self.cell_centers 1701 norms = self.cell_normals 1702 justify = "centered" if justify == "" else justify 1703 else: 1704 ns = np.sqrt(self.npoints) 1705 elems = self.vertices 1706 norms = self.vertex_normals 1707 except AttributeError: 1708 norms = [] 1709 1710 if not justify: 1711 justify = "bottom-left" 1712 1713 hasnorms = False 1714 if len(norms) > 0: 1715 hasnorms = True 1716 1717 if scale is None: 1718 if not ns: 1719 ns = 100 1720 scale = self.diagonal_size() / ns / 10 1721 1722 arr = None 1723 mode = 0 1724 if content is None: 1725 mode = 0 1726 if cells: 1727 if self.dataset.GetCellData().GetScalars(): 1728 name = self.dataset.GetCellData().GetScalars().GetName() 1729 arr = self.celldata[name] 1730 else: 1731 if self.dataset.GetPointData().GetScalars(): 1732 name = self.dataset.GetPointData().GetScalars().GetName() 1733 arr = self.pointdata[name] 1734 elif isinstance(content, (str, int)): 1735 if content == "id": 1736 mode = 1 1737 elif cells: 1738 mode = 0 1739 arr = self.celldata[content] 1740 else: 1741 mode = 0 1742 arr = self.pointdata[content] 1743 elif utils.is_sequence(content): 1744 mode = 0 1745 arr = content 1746 1747 if arr is None and mode == 0: 1748 vedo.logger.error("in labels(), array not found in point or cell data") 1749 return None 1750 1751 ratio = int(ratio+0.5) 1752 tapp = vtki.new("AppendPolyData") 1753 has_inputs = False 1754 1755 for i, e in enumerate(elems): 1756 if i % ratio: 1757 continue 1758 1759 if mode == 1: 1760 txt_lab = str(i) 1761 else: 1762 if precision: 1763 txt_lab = utils.precision(arr[i], precision) 1764 else: 1765 txt_lab = str(arr[i]) 1766 1767 if not txt_lab: 1768 continue 1769 1770 if font == "VTK": 1771 tx = vtki.new("VectorText") 1772 tx.SetText(txt_lab) 1773 tx.Update() 1774 tx_poly = tx.GetOutput() 1775 else: 1776 tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify).dataset 1777 1778 if tx_poly.GetNumberOfPoints() == 0: 1779 continue ###################### 1780 1781 T = vtki.vtkTransform() 1782 T.PostMultiply() 1783 if italic: 1784 T.Concatenate([1, 0.2, 0, 0, 1785 0, 1 , 0, 0, 1786 0, 0 , 1, 0, 1787 0, 0 , 0, 1]) 1788 if hasnorms: 1789 ni = norms[i] 1790 if cells and font=="VTK": # center-justify 1791 bb = tx_poly.GetBounds() 1792 dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2 1793 T.Translate(-dx, -dy, 0) 1794 if xrot: T.RotateX(xrot) 1795 if yrot: T.RotateY(yrot) 1796 if zrot: T.RotateZ(zrot) 1797 crossvec = np.cross([0, 0, 1], ni) 1798 angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3 1799 T.RotateWXYZ(float(angle), crossvec.tolist()) 1800 T.Translate(ni / 100) 1801 else: 1802 if xrot: T.RotateX(xrot) 1803 if yrot: T.RotateY(yrot) 1804 if zrot: T.RotateZ(zrot) 1805 T.Scale(scale, scale, scale) 1806 T.Translate(e) 1807 tf = vtki.new("TransformPolyDataFilter") 1808 tf.SetInputData(tx_poly) 1809 tf.SetTransform(T) 1810 tf.Update() 1811 tapp.AddInputData(tf.GetOutput()) 1812 has_inputs = True 1813 1814 if has_inputs: 1815 tapp.Update() 1816 lpoly = tapp.GetOutput() 1817 else: 1818 lpoly = vtki.vtkPolyData() 1819 ids = vedo.Mesh(lpoly, c=c, alpha=alpha) 1820 ids.properties.LightingOff() 1821 ids.actor.PickableOff() 1822 ids.actor.SetUseBounds(False) 1823 ids.name = "Labels" 1824 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:
1826 def labels2d( 1827 self, 1828 content="id", 1829 on="points", 1830 scale=1.0, 1831 precision=4, 1832 font="Calco", 1833 justify="bottom-left", 1834 angle=0.0, 1835 frame=False, 1836 c="black", 1837 bc=None, 1838 alpha=1.0, 1839 ) -> Union["Actor2D", None]: 1840 """ 1841 Generate value or ID bi-dimensional labels for mesh cells or points. 1842 1843 See also: `labels()`, `flagpole()`, `caption()` and `legend()`. 1844 1845 Arguments: 1846 content : (str) 1847 either 'id', 'cellid', or array name 1848 on : (str) 1849 generate labels for "cells" instead of "points" (the default) 1850 scale : (float) 1851 size scaling of labels 1852 precision : (int) 1853 precision of numeric labels 1854 angle : (float) 1855 local rotation angle of label in degrees 1856 frame : (bool) 1857 draw a frame around the label 1858 bc : (str) 1859 background color of the label 1860 1861 ```python 1862 from vedo import Sphere, show 1863 sph = Sphere(quads=True, res=4).compute_normals().wireframe() 1864 sph.celldata["zvals"] = sph.cell_centers[:,2] 1865 l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9') 1866 show(sph, l2d, axes=1).close() 1867 ``` 1868 ![](https://vedo.embl.es/images/feats/labels2d.png) 1869 """ 1870 cells = False 1871 if "cell" in on: 1872 cells = True 1873 1874 if isinstance(content, str): 1875 if content in ("id", "pointid", "pointsid"): 1876 cells = False 1877 content = "id" 1878 if content in ("cellid", "cellsid"): 1879 cells = True 1880 content = "id" 1881 1882 if cells: 1883 if content != "id" and content not in self.celldata.keys(): 1884 vedo.logger.error(f"In labels2d: cell array {content} does not exist.") 1885 return None 1886 cellcloud = vedo.Points(self.cell_centers) 1887 arr = self.dataset.GetCellData().GetScalars() 1888 poly = cellcloud.dataset 1889 poly.GetPointData().SetScalars(arr) 1890 else: 1891 poly = self.dataset 1892 if content != "id" and content not in self.pointdata.keys(): 1893 vedo.logger.error(f"In labels2d: point array {content} does not exist.") 1894 return None 1895 1896 mp = vtki.new("LabeledDataMapper") 1897 1898 if content == "id": 1899 mp.SetLabelModeToLabelIds() 1900 else: 1901 mp.SetLabelModeToLabelScalars() 1902 if precision is not None: 1903 mp.SetLabelFormat(f"%-#.{precision}g") 1904 1905 pr = mp.GetLabelTextProperty() 1906 c = colors.get_color(c) 1907 pr.SetColor(c) 1908 pr.SetOpacity(alpha) 1909 pr.SetFrame(frame) 1910 pr.SetFrameColor(c) 1911 pr.SetItalic(False) 1912 pr.BoldOff() 1913 pr.ShadowOff() 1914 pr.UseTightBoundingBoxOn() 1915 pr.SetOrientation(angle) 1916 pr.SetFontFamily(vtki.VTK_FONT_FILE) 1917 fl = utils.get_font_path(font) 1918 pr.SetFontFile(fl) 1919 pr.SetFontSize(int(20 * scale)) 1920 1921 if "cent" in justify or "mid" in justify: 1922 pr.SetJustificationToCentered() 1923 elif "rig" in justify: 1924 pr.SetJustificationToRight() 1925 elif "left" in justify: 1926 pr.SetJustificationToLeft() 1927 # ------ 1928 if "top" in justify: 1929 pr.SetVerticalJustificationToTop() 1930 else: 1931 pr.SetVerticalJustificationToBottom() 1932 1933 if bc is not None: 1934 bc = colors.get_color(bc) 1935 pr.SetBackgroundColor(bc) 1936 pr.SetBackgroundOpacity(alpha) 1937 1938 mp.SetInputData(poly) 1939 a2d = Actor2D() 1940 a2d.PickableOff() 1941 a2d.SetMapper(mp) 1942 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()
1944 def legend(self, txt) -> Self: 1945 """Book a legend text.""" 1946 self.info["legend"] = txt 1947 # self.metadata["legend"] = txt 1948 return self
Book a legend text.
1950 def flagpole( 1951 self, 1952 txt=None, 1953 point=None, 1954 offset=None, 1955 s=None, 1956 font="Calco", 1957 rounded=True, 1958 c=None, 1959 alpha=1.0, 1960 lw=2, 1961 italic=0.0, 1962 padding=0.1, 1963 ) -> Union["vedo.Mesh", None]: 1964 """ 1965 Generate a flag pole style element to describe an object. 1966 Returns a `Mesh` object. 1967 1968 Use flagpole.follow_camera() to make it face the camera in the scene. 1969 1970 Consider using `settings.use_parallel_projection = True` 1971 to avoid perspective distortions. 1972 1973 See also `flagpost()`. 1974 1975 Arguments: 1976 txt : (str) 1977 Text to display. The default is the filename or the object name. 1978 point : (list) 1979 position of the flagpole pointer. 1980 offset : (list) 1981 text offset wrt the application point. 1982 s : (float) 1983 size of the flagpole. 1984 font : (str) 1985 font face. Check [available fonts here](https://vedo.embl.es/fonts). 1986 rounded : (bool) 1987 draw a rounded or squared box around the text. 1988 c : (list) 1989 text and box color. 1990 alpha : (float) 1991 opacity of text and box. 1992 lw : (float) 1993 line with of box frame. 1994 italic : (float) 1995 italicness of text. 1996 1997 Examples: 1998 - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py) 1999 2000 ![](https://vedo.embl.es/images/pyplot/intersect2d.png) 2001 2002 - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py) 2003 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 2004 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 2005 """ 2006 objs = [] 2007 2008 if txt is None: 2009 if self.filename: 2010 txt = self.filename.split("/")[-1] 2011 elif self.name: 2012 txt = self.name 2013 else: 2014 return None 2015 2016 x0, x1, y0, y1, z0, z1 = self.bounds() 2017 d = self.diagonal_size() 2018 if point is None: 2019 if d: 2020 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2021 # point = self.closest_point([x1, y0, z1]) 2022 else: # it's a Point 2023 point = self.transform.position 2024 2025 pt = utils.make3d(point) 2026 2027 if offset is None: 2028 offset = [(x1 - x0) / 1.75, (y1 - y0) / 5, 0] 2029 offset = utils.make3d(offset) 2030 2031 if s is None: 2032 s = d / 20 2033 2034 sph = None 2035 if d and (z1 - z0) / d > 0.1: 2036 sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6) 2037 2038 if c is None: 2039 c = np.array(self.color()) / 1.4 2040 2041 lab = vedo.shapes.Text3D( 2042 txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center" 2043 ) 2044 objs.append(lab) 2045 2046 if d and not sph: 2047 sph = vedo.shapes.Circle(pt, r=s / 3, res=16) 2048 objs.append(sph) 2049 2050 x0, x1, y0, y1, z0, z1 = lab.bounds() 2051 aline = [(x0,y0,z0), (x1,y0,z0), (x1,y1,z0), (x0,y1,z0)] 2052 if rounded: 2053 box = vedo.shapes.KSpline(aline, closed=True) 2054 else: 2055 box = vedo.shapes.Line(aline, closed=True) 2056 2057 cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2] 2058 2059 # box.actor.SetOrigin(cnt) 2060 box.scale([1 + padding, 1 + 2 * padding, 1], origin=cnt) 2061 objs.append(box) 2062 2063 x0, x1, y0, y1, z0, z1 = box.bounds() 2064 if x0 < pt[0] < x1: 2065 c0 = box.closest_point(pt) 2066 c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]] 2067 elif (pt[0] - x0) < (x1 - pt[0]): 2068 c0 = [x0, (y0 + y1) / 2, pt[2]] 2069 c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]] 2070 else: 2071 c0 = [x1, (y0 + y1) / 2, pt[2]] 2072 c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]] 2073 2074 con = vedo.shapes.Line([c0, c1, pt]) 2075 objs.append(con) 2076 2077 mobjs = vedo.merge(objs).c(c).alpha(alpha) 2078 mobjs.name = "FlagPole" 2079 mobjs.bc("tomato").pickable(False) 2080 mobjs.properties.LightingOff() 2081 mobjs.properties.SetLineWidth(lw) 2082 mobjs.actor.UseBoundsOff() 2083 mobjs.actor.SetPosition([0,0,0]) 2084 mobjs.actor.SetOrigin(pt) 2085 return mobjs 2086 2087 # mobjs = vedo.Assembly(objs)#.c(c).alpha(alpha) 2088 # mobjs.name = "FlagPole" 2089 # # mobjs.bc("tomato").pickable(False) 2090 # # mobjs.properties.LightingOff() 2091 # # mobjs.properties.SetLineWidth(lw) 2092 # # mobjs.actor.UseBoundsOff() 2093 # # mobjs.actor.SetPosition([0,0,0]) 2094 # # mobjs.actor.SetOrigin(pt) 2095 # # print(pt) 2096 # 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:
2098 def flagpost( 2099 self, 2100 txt=None, 2101 point=None, 2102 offset=None, 2103 s=1.0, 2104 c="k9", 2105 bc="k1", 2106 alpha=1, 2107 lw=0, 2108 font="Calco", 2109 justify="center-left", 2110 vspacing=1.0, 2111 ) -> Union["vedo.addons.Flagpost", None]: 2112 """ 2113 Generate a flag post style element to describe an object. 2114 2115 Arguments: 2116 txt : (str) 2117 Text to display. The default is the filename or the object name. 2118 point : (list) 2119 position of the flag anchor point. The default is None. 2120 offset : (list) 2121 a 3D displacement or offset. The default is None. 2122 s : (float) 2123 size of the text to be shown 2124 c : (list) 2125 color of text and line 2126 bc : (list) 2127 color of the flag background 2128 alpha : (float) 2129 opacity of text and box. 2130 lw : (int) 2131 line with of box frame. The default is 0. 2132 font : (str) 2133 font name. Use a monospace font for better rendering. The default is "Calco". 2134 Type `vedo -r fonts` for a font demo. 2135 Check [available fonts here](https://vedo.embl.es/fonts). 2136 justify : (str) 2137 internal text justification. The default is "center-left". 2138 vspacing : (float) 2139 vertical spacing between lines. 2140 2141 Examples: 2142 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py) 2143 2144 ![](https://vedo.embl.es/images/other/flag_labels2.png) 2145 """ 2146 if txt is None: 2147 if self.filename: 2148 txt = self.filename.split("/")[-1] 2149 elif self.name: 2150 txt = self.name 2151 else: 2152 return None 2153 2154 x0, x1, y0, y1, z0, z1 = self.bounds() 2155 d = self.diagonal_size() 2156 if point is None: 2157 if d: 2158 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2159 else: # it's a Point 2160 point = self.transform.position 2161 2162 point = utils.make3d(point) 2163 2164 if offset is None: 2165 offset = [0, 0, (z1 - z0) / 2] 2166 offset = utils.make3d(offset) 2167 2168 fpost = vedo.addons.Flagpost( 2169 txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing 2170 ) 2171 self._caption = fpost 2172 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:
2636class VolumeVisual(CommonVisual): 2637 """Class to manage the visual aspects of a ``Volume`` object.""" 2638 2639 def __init__(self) -> None: 2640 # print("INIT VolumeVisual") 2641 super().__init__() 2642 2643 def alpha_unit(self, u=None) -> Union[Self, float]: 2644 """ 2645 Defines light attenuation per unit length. Default is 1. 2646 The larger the unit length, the further light has to travel to attenuate the same amount. 2647 2648 E.g., if you set the unit distance to 0, you will get full opacity. 2649 It means that when light travels 0 distance it's already attenuated a finite amount. 2650 Thus, any finite distance should attenuate all light. 2651 The larger you make the unit distance, the more transparent the rendering becomes. 2652 """ 2653 if u is None: 2654 return self.properties.GetScalarOpacityUnitDistance() 2655 self.properties.SetScalarOpacityUnitDistance(u) 2656 return self 2657 2658 def alpha_gradient(self, alpha_grad, vmin=None, vmax=None) -> Self: 2659 """ 2660 Assign a set of tranparencies to a volume's gradient 2661 along the range of the scalar value. 2662 A single constant value can also be assigned. 2663 The gradient function is used to decrease the opacity 2664 in the "flat" regions of the volume while maintaining the opacity 2665 at the boundaries between material types. The gradient is measured 2666 as the amount by which the intensity changes over unit distance. 2667 2668 The format for alpha_grad is the same as for method `volume.alpha()`. 2669 """ 2670 if vmin is None: 2671 vmin, _ = self.dataset.GetScalarRange() 2672 if vmax is None: 2673 _, vmax = self.dataset.GetScalarRange() 2674 2675 if alpha_grad is None: 2676 self.properties.DisableGradientOpacityOn() 2677 return self 2678 2679 self.properties.DisableGradientOpacityOff() 2680 2681 gotf = self.properties.GetGradientOpacity() 2682 if utils.is_sequence(alpha_grad): 2683 alpha_grad = np.array(alpha_grad) 2684 if len(alpha_grad.shape) == 1: # user passing a flat list e.g. (0.0, 0.3, 0.9, 1) 2685 for i, al in enumerate(alpha_grad): 2686 xalpha = vmin + (vmax - vmin) * i / (len(alpha_grad) - 1) 2687 # Create transfer mapping scalar value to gradient opacity 2688 gotf.AddPoint(xalpha, al) 2689 elif len(alpha_grad.shape) == 2: # user passing [(x0,alpha0), ...] 2690 gotf.AddPoint(vmin, alpha_grad[0][1]) 2691 for xalpha, al in alpha_grad: 2692 # Create transfer mapping scalar value to opacity 2693 gotf.AddPoint(xalpha, al) 2694 gotf.AddPoint(vmax, alpha_grad[-1][1]) 2695 # print("alpha_grad at", round(xalpha, 1), "\tset to", al) 2696 else: 2697 gotf.AddPoint(vmin, alpha_grad) # constant alpha_grad 2698 gotf.AddPoint(vmax, alpha_grad) 2699 return self 2700 2701 def cmap(self, c, alpha=None, vmin=None, vmax=None) -> Self: 2702 """Same as `color()`. 2703 2704 Arguments: 2705 alpha : (list) 2706 use a list to specify transparencies along the scalar range 2707 vmin : (float) 2708 force the min of the scalar range to be this value 2709 vmax : (float) 2710 force the max of the scalar range to be this value 2711 """ 2712 return self.color(c, alpha, vmin, vmax) 2713 2714 def jittering(self, status=None) -> Union[Self, bool]: 2715 """ 2716 If `True`, each ray traversal direction will be perturbed slightly 2717 using a noise-texture to get rid of wood-grain effects. 2718 """ 2719 if hasattr(self.mapper, "SetUseJittering"): # tetmesh doesnt have it 2720 if status is None: 2721 return self.mapper.GetUseJittering() 2722 self.mapper.SetUseJittering(status) 2723 return self 2724 2725 def hide_voxels(self, ids) -> Self: 2726 """ 2727 Hide voxels (cells) from visualization. 2728 2729 Example: 2730 ```python 2731 from vedo import * 2732 embryo = Volume(dataurl+'embryo.tif') 2733 embryo.hide_voxels(list(range(400000))) 2734 show(embryo, axes=1).close() 2735 ``` 2736 2737 See also: 2738 `volume.mask()` 2739 """ 2740 ghost_mask = np.zeros(self.ncells, dtype=np.uint8) 2741 ghost_mask[ids] = vtki.vtkDataSetAttributes.HIDDENCELL 2742 name = vtki.vtkDataSetAttributes.GhostArrayName() 2743 garr = utils.numpy2vtk(ghost_mask, name=name, dtype=np.uint8) 2744 self.dataset.GetCellData().AddArray(garr) 2745 self.dataset.GetCellData().Modified() 2746 return self 2747 2748 def mask(self, data) -> Self: 2749 """ 2750 Mask a volume visualization with a binary value. 2751 Needs to specify `volume.mapper = "gpu"`. 2752 2753 Example: 2754 ```python 2755 from vedo import np, Volume, show 2756 data_matrix = np.zeros([75, 75, 75], dtype=np.uint8) 2757 # all voxels have value zero except: 2758 data_matrix[ 0:35, 0:35, 0:35] = 1 2759 data_matrix[35:55, 35:55, 35:55] = 2 2760 data_matrix[55:74, 55:74, 55:74] = 3 2761 vol = Volume(data_matrix).cmap('Blues') 2762 vol.mapper = "gpu" 2763 data_mask = np.zeros_like(data_matrix) 2764 data_mask[10:65, 10:60, 20:70] = 1 2765 vol.mask(data_mask) 2766 show(vol, axes=1).close() 2767 ``` 2768 See also: 2769 `volume.hide_voxels()` 2770 """ 2771 rdata = data.astype(np.uint8).ravel(order="F") 2772 varr = utils.numpy2vtk(rdata, name="input_mask") 2773 2774 img = vtki.vtkImageData() 2775 img.SetDimensions(self.dimensions()) 2776 img.GetPointData().AddArray(varr) 2777 img.GetPointData().SetActiveScalars(varr.GetName()) 2778 2779 try: 2780 self.mapper.SetMaskTypeToBinary() 2781 self.mapper.SetMaskInput(img) 2782 except AttributeError: 2783 vedo.logger.error("volume.mask() must specify volume.mapper = 'gpu'") 2784 return self 2785 2786 2787 def mode(self, mode=None) -> Union[Self, int]: 2788 """ 2789 Define the volumetric rendering mode following this: 2790 - 0, composite rendering 2791 - 1, maximum projection rendering 2792 - 2, minimum projection rendering 2793 - 3, average projection rendering 2794 - 4, additive mode 2795 2796 The default mode is "composite" where the scalar values are sampled through 2797 the volume and composited in a front-to-back scheme through alpha blending. 2798 The final color and opacity is determined using the color and opacity transfer 2799 functions specified in alpha keyword. 2800 2801 Maximum and minimum intensity blend modes use the maximum and minimum 2802 scalar values, respectively, along the sampling ray. 2803 The final color and opacity is determined by passing the resultant value 2804 through the color and opacity transfer functions. 2805 2806 Additive blend mode accumulates scalar values by passing each value 2807 through the opacity transfer function and then adding up the product 2808 of the value and its opacity. In other words, the scalar values are scaled 2809 using the opacity transfer function and summed to derive the final color. 2810 Note that the resulting image is always grayscale i.e. aggregated values 2811 are not passed through the color transfer function. 2812 This is because the final value is a derived value and not a real data value 2813 along the sampling ray. 2814 2815 Average intensity blend mode works similar to the additive blend mode where 2816 the scalar values are multiplied by opacity calculated from the opacity 2817 transfer function and then added. 2818 The additional step here is to divide the sum by the number of samples 2819 taken through the volume. 2820 As is the case with the additive intensity projection, the final image will 2821 always be grayscale i.e. the aggregated values are not passed through the 2822 color transfer function. 2823 """ 2824 if mode is None: 2825 return self.mapper.GetBlendMode() 2826 2827 if isinstance(mode, str): 2828 if "comp" in mode: 2829 mode = 0 2830 elif "proj" in mode: 2831 if "max" in mode: 2832 mode = 1 2833 elif "min" in mode: 2834 mode = 2 2835 elif "ave" in mode: 2836 mode = 3 2837 else: 2838 vedo.logger.warning(f"unknown mode {mode}") 2839 mode = 0 2840 elif "add" in mode: 2841 mode = 4 2842 else: 2843 vedo.logger.warning(f"unknown mode {mode}") 2844 mode = 0 2845 2846 self.mapper.SetBlendMode(mode) 2847 return self 2848 2849 def shade(self, status=None) -> Union[Self, bool]: 2850 """ 2851 Set/Get the shading of a Volume. 2852 Shading can be further controlled with `volume.lighting()` method. 2853 2854 If shading is turned on, the mapper may perform shading calculations. 2855 In some cases shading does not apply 2856 (for example, in maximum intensity projection mode). 2857 """ 2858 if status is None: 2859 return self.properties.GetShade() 2860 self.properties.SetShade(status) 2861 return self 2862 2863 def interpolation(self, itype) -> Self: 2864 """ 2865 Set interpolation type. 2866 2867 0=nearest neighbour, 1=linear 2868 """ 2869 self.properties.SetInterpolationType(itype) 2870 return self
Class to manage the visual aspects of a Volume
object.
2643 def alpha_unit(self, u=None) -> Union[Self, float]: 2644 """ 2645 Defines light attenuation per unit length. Default is 1. 2646 The larger the unit length, the further light has to travel to attenuate the same amount. 2647 2648 E.g., if you set the unit distance to 0, you will get full opacity. 2649 It means that when light travels 0 distance it's already attenuated a finite amount. 2650 Thus, any finite distance should attenuate all light. 2651 The larger you make the unit distance, the more transparent the rendering becomes. 2652 """ 2653 if u is None: 2654 return self.properties.GetScalarOpacityUnitDistance() 2655 self.properties.SetScalarOpacityUnitDistance(u) 2656 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.
2658 def alpha_gradient(self, alpha_grad, vmin=None, vmax=None) -> Self: 2659 """ 2660 Assign a set of tranparencies to a volume's gradient 2661 along the range of the scalar value. 2662 A single constant value can also be assigned. 2663 The gradient function is used to decrease the opacity 2664 in the "flat" regions of the volume while maintaining the opacity 2665 at the boundaries between material types. The gradient is measured 2666 as the amount by which the intensity changes over unit distance. 2667 2668 The format for alpha_grad is the same as for method `volume.alpha()`. 2669 """ 2670 if vmin is None: 2671 vmin, _ = self.dataset.GetScalarRange() 2672 if vmax is None: 2673 _, vmax = self.dataset.GetScalarRange() 2674 2675 if alpha_grad is None: 2676 self.properties.DisableGradientOpacityOn() 2677 return self 2678 2679 self.properties.DisableGradientOpacityOff() 2680 2681 gotf = self.properties.GetGradientOpacity() 2682 if utils.is_sequence(alpha_grad): 2683 alpha_grad = np.array(alpha_grad) 2684 if len(alpha_grad.shape) == 1: # user passing a flat list e.g. (0.0, 0.3, 0.9, 1) 2685 for i, al in enumerate(alpha_grad): 2686 xalpha = vmin + (vmax - vmin) * i / (len(alpha_grad) - 1) 2687 # Create transfer mapping scalar value to gradient opacity 2688 gotf.AddPoint(xalpha, al) 2689 elif len(alpha_grad.shape) == 2: # user passing [(x0,alpha0), ...] 2690 gotf.AddPoint(vmin, alpha_grad[0][1]) 2691 for xalpha, al in alpha_grad: 2692 # Create transfer mapping scalar value to opacity 2693 gotf.AddPoint(xalpha, al) 2694 gotf.AddPoint(vmax, alpha_grad[-1][1]) 2695 # print("alpha_grad at", round(xalpha, 1), "\tset to", al) 2696 else: 2697 gotf.AddPoint(vmin, alpha_grad) # constant alpha_grad 2698 gotf.AddPoint(vmax, alpha_grad) 2699 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()
.
2701 def cmap(self, c, alpha=None, vmin=None, vmax=None) -> Self: 2702 """Same as `color()`. 2703 2704 Arguments: 2705 alpha : (list) 2706 use a list to specify transparencies along the scalar range 2707 vmin : (float) 2708 force the min of the scalar range to be this value 2709 vmax : (float) 2710 force the max of the scalar range to be this value 2711 """ 2712 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
2714 def jittering(self, status=None) -> Union[Self, bool]: 2715 """ 2716 If `True`, each ray traversal direction will be perturbed slightly 2717 using a noise-texture to get rid of wood-grain effects. 2718 """ 2719 if hasattr(self.mapper, "SetUseJittering"): # tetmesh doesnt have it 2720 if status is None: 2721 return self.mapper.GetUseJittering() 2722 self.mapper.SetUseJittering(status) 2723 return self
If True
, each ray traversal direction will be perturbed slightly
using a noise-texture to get rid of wood-grain effects.
2725 def hide_voxels(self, ids) -> Self: 2726 """ 2727 Hide voxels (cells) from visualization. 2728 2729 Example: 2730 ```python 2731 from vedo import * 2732 embryo = Volume(dataurl+'embryo.tif') 2733 embryo.hide_voxels(list(range(400000))) 2734 show(embryo, axes=1).close() 2735 ``` 2736 2737 See also: 2738 `volume.mask()` 2739 """ 2740 ghost_mask = np.zeros(self.ncells, dtype=np.uint8) 2741 ghost_mask[ids] = vtki.vtkDataSetAttributes.HIDDENCELL 2742 name = vtki.vtkDataSetAttributes.GhostArrayName() 2743 garr = utils.numpy2vtk(ghost_mask, name=name, dtype=np.uint8) 2744 self.dataset.GetCellData().AddArray(garr) 2745 self.dataset.GetCellData().Modified() 2746 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()
2748 def mask(self, data) -> Self: 2749 """ 2750 Mask a volume visualization with a binary value. 2751 Needs to specify `volume.mapper = "gpu"`. 2752 2753 Example: 2754 ```python 2755 from vedo import np, Volume, show 2756 data_matrix = np.zeros([75, 75, 75], dtype=np.uint8) 2757 # all voxels have value zero except: 2758 data_matrix[ 0:35, 0:35, 0:35] = 1 2759 data_matrix[35:55, 35:55, 35:55] = 2 2760 data_matrix[55:74, 55:74, 55:74] = 3 2761 vol = Volume(data_matrix).cmap('Blues') 2762 vol.mapper = "gpu" 2763 data_mask = np.zeros_like(data_matrix) 2764 data_mask[10:65, 10:60, 20:70] = 1 2765 vol.mask(data_mask) 2766 show(vol, axes=1).close() 2767 ``` 2768 See also: 2769 `volume.hide_voxels()` 2770 """ 2771 rdata = data.astype(np.uint8).ravel(order="F") 2772 varr = utils.numpy2vtk(rdata, name="input_mask") 2773 2774 img = vtki.vtkImageData() 2775 img.SetDimensions(self.dimensions()) 2776 img.GetPointData().AddArray(varr) 2777 img.GetPointData().SetActiveScalars(varr.GetName()) 2778 2779 try: 2780 self.mapper.SetMaskTypeToBinary() 2781 self.mapper.SetMaskInput(img) 2782 except AttributeError: 2783 vedo.logger.error("volume.mask() must specify volume.mapper = 'gpu'") 2784 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()
2787 def mode(self, mode=None) -> Union[Self, int]: 2788 """ 2789 Define the volumetric rendering mode following this: 2790 - 0, composite rendering 2791 - 1, maximum projection rendering 2792 - 2, minimum projection rendering 2793 - 3, average projection rendering 2794 - 4, additive mode 2795 2796 The default mode is "composite" where the scalar values are sampled through 2797 the volume and composited in a front-to-back scheme through alpha blending. 2798 The final color and opacity is determined using the color and opacity transfer 2799 functions specified in alpha keyword. 2800 2801 Maximum and minimum intensity blend modes use the maximum and minimum 2802 scalar values, respectively, along the sampling ray. 2803 The final color and opacity is determined by passing the resultant value 2804 through the color and opacity transfer functions. 2805 2806 Additive blend mode accumulates scalar values by passing each value 2807 through the opacity transfer function and then adding up the product 2808 of the value and its opacity. In other words, the scalar values are scaled 2809 using the opacity transfer function and summed to derive the final color. 2810 Note that the resulting image is always grayscale i.e. aggregated values 2811 are not passed through the color transfer function. 2812 This is because the final value is a derived value and not a real data value 2813 along the sampling ray. 2814 2815 Average intensity blend mode works similar to the additive blend mode where 2816 the scalar values are multiplied by opacity calculated from the opacity 2817 transfer function and then added. 2818 The additional step here is to divide the sum by the number of samples 2819 taken through the volume. 2820 As is the case with the additive intensity projection, the final image will 2821 always be grayscale i.e. the aggregated values are not passed through the 2822 color transfer function. 2823 """ 2824 if mode is None: 2825 return self.mapper.GetBlendMode() 2826 2827 if isinstance(mode, str): 2828 if "comp" in mode: 2829 mode = 0 2830 elif "proj" in mode: 2831 if "max" in mode: 2832 mode = 1 2833 elif "min" in mode: 2834 mode = 2 2835 elif "ave" in mode: 2836 mode = 3 2837 else: 2838 vedo.logger.warning(f"unknown mode {mode}") 2839 mode = 0 2840 elif "add" in mode: 2841 mode = 4 2842 else: 2843 vedo.logger.warning(f"unknown mode {mode}") 2844 mode = 0 2845 2846 self.mapper.SetBlendMode(mode) 2847 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.
2849 def shade(self, status=None) -> Union[Self, bool]: 2850 """ 2851 Set/Get the shading of a Volume. 2852 Shading can be further controlled with `volume.lighting()` method. 2853 2854 If shading is turned on, the mapper may perform shading calculations. 2855 In some cases shading does not apply 2856 (for example, in maximum intensity projection mode). 2857 """ 2858 if status is None: 2859 return self.properties.GetShade() 2860 self.properties.SetShade(status) 2861 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).
2297class MeshVisual(PointsVisual): 2298 """Class to manage the visual aspects of a ``Maesh`` object.""" 2299 2300 def __init__(self) -> None: 2301 # print("INIT MeshVisual", super()) 2302 super().__init__() 2303 2304 def follow_camera(self, camera=None, origin=None) -> Self: 2305 """ 2306 Return an object that will follow camera movements and stay locked to it. 2307 Use `mesh.follow_camera(False)` to disable it. 2308 2309 A `vtkCamera` object can also be passed. 2310 """ 2311 if camera is False: 2312 try: 2313 self.SetCamera(None) 2314 return self 2315 except AttributeError: 2316 return self 2317 2318 factor = vtki.vtkFollower() 2319 factor.SetMapper(self.mapper) 2320 factor.SetProperty(self.properties) 2321 factor.SetBackfaceProperty(self.actor.GetBackfaceProperty()) 2322 factor.SetTexture(self.actor.GetTexture()) 2323 factor.SetScale(self.actor.GetScale()) 2324 # factor.SetOrientation(self.actor.GetOrientation()) 2325 factor.SetPosition(self.actor.GetPosition()) 2326 factor.SetUseBounds(self.actor.GetUseBounds()) 2327 2328 if origin is None: 2329 factor.SetOrigin(self.actor.GetOrigin()) 2330 else: 2331 factor.SetOrigin(origin) 2332 2333 factor.PickableOff() 2334 2335 if isinstance(camera, vtki.vtkCamera): 2336 factor.SetCamera(camera) 2337 else: 2338 plt = vedo.plotter_instance 2339 if plt and plt.renderer and plt.renderer.GetActiveCamera(): 2340 factor.SetCamera(plt.renderer.GetActiveCamera()) 2341 2342 self.actor = None 2343 factor.retrieve_object = weak_ref_to(self) 2344 self.actor = factor 2345 return self 2346 2347 def wireframe(self, value=True) -> Self: 2348 """Set mesh's representation as wireframe or solid surface.""" 2349 if value: 2350 self.properties.SetRepresentationToWireframe() 2351 else: 2352 self.properties.SetRepresentationToSurface() 2353 return self 2354 2355 def flat(self) -> Self: 2356 """Set surface interpolation to flat. 2357 2358 <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" width="700"> 2359 """ 2360 self.properties.SetInterpolationToFlat() 2361 return self 2362 2363 def phong(self) -> Self: 2364 """Set surface interpolation to "phong".""" 2365 self.properties.SetInterpolationToPhong() 2366 return self 2367 2368 def backface_culling(self, value=True) -> Self: 2369 """Set culling of polygons based on orientation of normal with respect to camera.""" 2370 self.properties.SetBackfaceCulling(value) 2371 return self 2372 2373 def render_lines_as_tubes(self, value=True) -> Self: 2374 """Wrap a fake tube around a simple line for visualization""" 2375 self.properties.SetRenderLinesAsTubes(value) 2376 return self 2377 2378 def frontface_culling(self, value=True) -> Self: 2379 """Set culling of polygons based on orientation of normal with respect to camera.""" 2380 self.properties.SetFrontfaceCulling(value) 2381 return self 2382 2383 def backcolor(self, bc=None) -> Union[Self, np.ndarray]: 2384 """ 2385 Set/get mesh's backface color. 2386 """ 2387 back_prop = self.actor.GetBackfaceProperty() 2388 2389 if bc is None: 2390 if back_prop: 2391 return back_prop.GetDiffuseColor() 2392 return self 2393 2394 if self.properties.GetOpacity() < 1: 2395 return self 2396 2397 if not back_prop: 2398 back_prop = vtki.vtkProperty() 2399 2400 back_prop.SetDiffuseColor(colors.get_color(bc)) 2401 back_prop.SetOpacity(self.properties.GetOpacity()) 2402 self.actor.SetBackfaceProperty(back_prop) 2403 self.mapper.ScalarVisibilityOff() 2404 return self 2405 2406 def bc(self, backcolor=False) -> Union[Self, np.ndarray]: 2407 """Shortcut for `mesh.backcolor()`.""" 2408 return self.backcolor(backcolor) 2409 2410 def linewidth(self, lw=None) -> Union[Self, int]: 2411 """Set/get width of mesh edges. Same as `lw()`.""" 2412 if lw is not None: 2413 if lw == 0: 2414 self.properties.EdgeVisibilityOff() 2415 self.properties.SetRepresentationToSurface() 2416 return self 2417 self.properties.EdgeVisibilityOn() 2418 self.properties.SetLineWidth(lw) 2419 else: 2420 return self.properties.GetLineWidth() 2421 return self 2422 2423 def lw(self, linewidth=None) -> Union[Self, int]: 2424 """Set/get width of mesh edges. Same as `linewidth()`.""" 2425 return self.linewidth(linewidth) 2426 2427 def linecolor(self, lc=None) -> Union[Self, np.ndarray]: 2428 """Set/get color of mesh edges. Same as `lc()`.""" 2429 if lc is None: 2430 return np.array(self.properties.GetEdgeColor()) 2431 self.properties.EdgeVisibilityOn() 2432 self.properties.SetEdgeColor(colors.get_color(lc)) 2433 return self 2434 2435 def lc(self, linecolor=None) -> Union[Self, np.ndarray]: 2436 """Set/get color of mesh edges. Same as `linecolor()`.""" 2437 return self.linecolor(linecolor) 2438 2439 def texture( 2440 self, 2441 tname, 2442 tcoords=None, 2443 interpolate=True, 2444 repeat=True, 2445 edge_clamp=False, 2446 scale=None, 2447 ushift=None, 2448 vshift=None, 2449 ) -> Self: 2450 """ 2451 Assign a texture to mesh from image file or predefined texture `tname`. 2452 If tname is set to `None` texture is disabled. 2453 Input tname can also be an array or a `vtkTexture`. 2454 2455 Arguments: 2456 tname : (numpy.array, str, Image, vtkTexture, None) 2457 the input texture to be applied. Can be a numpy array, a path to an image file, 2458 a vedo Image. The None value disables texture. 2459 tcoords : (numpy.array, str) 2460 this is the (u,v) texture coordinate array. Can also be a string of an existing array 2461 in the mesh. 2462 interpolate : (bool) 2463 turn on/off linear interpolation of the texture map when rendering. 2464 repeat : (bool) 2465 repeat of the texture when tcoords extend beyond the [0,1] range. 2466 edge_clamp : (bool) 2467 turn on/off the clamping of the texture map when 2468 the texture coords extend beyond the [0,1] range. 2469 Only used when repeat is False, and edge clamping is supported by the graphics card. 2470 scale : (bool) 2471 scale the texture image by this factor 2472 ushift : (bool) 2473 shift u-coordinates of texture by this amount 2474 vshift : (bool) 2475 shift v-coordinates of texture by this amount 2476 2477 Examples: 2478 - [texturecubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/texturecubes.py) 2479 2480 ![](https://vedo.embl.es/images/basic/texturecubes.png) 2481 """ 2482 pd = self.dataset 2483 out_img = None 2484 2485 if tname is None: # disable texture 2486 pd.GetPointData().SetTCoords(None) 2487 pd.GetPointData().Modified() 2488 return self ###################################### 2489 2490 if isinstance(tname, vtki.vtkTexture): 2491 tu = tname 2492 2493 elif isinstance(tname, vedo.Image): 2494 tu = vtki.vtkTexture() 2495 out_img = tname.dataset 2496 2497 elif utils.is_sequence(tname): 2498 tu = vtki.vtkTexture() 2499 out_img = vedo.image._get_img(tname) 2500 2501 elif isinstance(tname, str): 2502 tu = vtki.vtkTexture() 2503 2504 if "https://" in tname: 2505 try: 2506 tname = vedo.file_io.download(tname, verbose=False) 2507 except: 2508 vedo.logger.error(f"texture {tname} could not be downloaded") 2509 return self 2510 2511 fn = tname + ".jpg" 2512 if os.path.exists(tname): 2513 fn = tname 2514 else: 2515 vedo.logger.error(f"texture file {tname} does not exist") 2516 return self 2517 2518 fnl = fn.lower() 2519 if ".jpg" in fnl or ".jpeg" in fnl: 2520 reader = vtki.new("JPEGReader") 2521 elif ".png" in fnl: 2522 reader = vtki.new("PNGReader") 2523 elif ".bmp" in fnl: 2524 reader = vtki.new("BMPReader") 2525 else: 2526 vedo.logger.error("in texture() supported files are only PNG, BMP or JPG") 2527 return self 2528 reader.SetFileName(fn) 2529 reader.Update() 2530 out_img = reader.GetOutput() 2531 2532 else: 2533 vedo.logger.error(f"in texture() cannot understand input {type(tname)}") 2534 return self 2535 2536 if tcoords is not None: 2537 2538 if isinstance(tcoords, str): 2539 vtarr = pd.GetPointData().GetArray(tcoords) 2540 2541 else: 2542 tcoords = np.asarray(tcoords) 2543 if tcoords.ndim != 2: 2544 vedo.logger.error("tcoords must be a 2-dimensional array") 2545 return self 2546 if tcoords.shape[0] != pd.GetNumberOfPoints(): 2547 vedo.logger.error("nr of texture coords must match nr of points") 2548 return self 2549 if tcoords.shape[1] != 2: 2550 vedo.logger.error("tcoords texture vector must have 2 components") 2551 vtarr = utils.numpy2vtk(tcoords) 2552 vtarr.SetName("TCoordinates") 2553 2554 pd.GetPointData().SetTCoords(vtarr) 2555 pd.GetPointData().Modified() 2556 2557 elif not pd.GetPointData().GetTCoords(): 2558 2559 # TCoords still void.. 2560 # check that there are no texture-like arrays: 2561 names = self.pointdata.keys() 2562 candidate_arr = "" 2563 for name in names: 2564 vtarr = pd.GetPointData().GetArray(name) 2565 if vtarr.GetNumberOfComponents() != 2: 2566 continue 2567 t0, t1 = vtarr.GetRange() 2568 if t0 >= 0 and t1 <= 1: 2569 candidate_arr = name 2570 2571 if candidate_arr: 2572 2573 vtarr = pd.GetPointData().GetArray(candidate_arr) 2574 pd.GetPointData().SetTCoords(vtarr) 2575 pd.GetPointData().Modified() 2576 2577 else: 2578 # last resource is automatic mapping 2579 tmapper = vtki.new("TextureMapToPlane") 2580 tmapper.AutomaticPlaneGenerationOn() 2581 tmapper.SetInputData(pd) 2582 tmapper.Update() 2583 tc = tmapper.GetOutput().GetPointData().GetTCoords() 2584 if scale or ushift or vshift: 2585 ntc = utils.vtk2numpy(tc) 2586 if scale: 2587 ntc *= scale 2588 if ushift: 2589 ntc[:, 0] += ushift 2590 if vshift: 2591 ntc[:, 1] += vshift 2592 tc = utils.numpy2vtk(tc) 2593 pd.GetPointData().SetTCoords(tc) 2594 pd.GetPointData().Modified() 2595 2596 if out_img: 2597 tu.SetInputData(out_img) 2598 tu.SetInterpolate(interpolate) 2599 tu.SetRepeat(repeat) 2600 tu.SetEdgeClamp(edge_clamp) 2601 2602 self.properties.SetColor(1, 1, 1) 2603 self.mapper.ScalarVisibilityOff() 2604 self.actor.SetTexture(tu) 2605 2606 # if seam_threshold is not None: 2607 # tname = self.dataset.GetPointData().GetTCoords().GetName() 2608 # grad = self.gradient(tname) 2609 # ugrad, vgrad = np.split(grad, 2, axis=1) 2610 # ugradm, vgradm = utils.mag2(ugrad), utils.mag2(vgrad) 2611 # gradm = np.log(ugradm + vgradm) 2612 # largegrad_ids = np.arange(len(grad))[gradm > seam_threshold * 4] 2613 # uvmap = self.pointdata[tname] 2614 # # collapse triangles that have large gradient 2615 # new_points = self.vertices.copy() 2616 # for f in self.cells: 2617 # if np.isin(f, largegrad_ids).all(): 2618 # id1, id2, id3 = f 2619 # uv1, uv2, uv3 = uvmap[f] 2620 # d12 = utils.mag2(uv1 - uv2) 2621 # d23 = utils.mag2(uv2 - uv3) 2622 # d31 = utils.mag2(uv3 - uv1) 2623 # idm = np.argmin([d12, d23, d31]) 2624 # if idm == 0: 2625 # new_points[id1] = new_points[id3] 2626 # new_points[id2] = new_points[id3] 2627 # elif idm == 1: 2628 # new_points[id2] = new_points[id1] 2629 # new_points[id3] = new_points[id1] 2630 # self.vertices = new_points 2631 2632 self.dataset.Modified() 2633 return self
Class to manage the visual aspects of a Maesh
object.
2304 def follow_camera(self, camera=None, origin=None) -> Self: 2305 """ 2306 Return an object that will follow camera movements and stay locked to it. 2307 Use `mesh.follow_camera(False)` to disable it. 2308 2309 A `vtkCamera` object can also be passed. 2310 """ 2311 if camera is False: 2312 try: 2313 self.SetCamera(None) 2314 return self 2315 except AttributeError: 2316 return self 2317 2318 factor = vtki.vtkFollower() 2319 factor.SetMapper(self.mapper) 2320 factor.SetProperty(self.properties) 2321 factor.SetBackfaceProperty(self.actor.GetBackfaceProperty()) 2322 factor.SetTexture(self.actor.GetTexture()) 2323 factor.SetScale(self.actor.GetScale()) 2324 # factor.SetOrientation(self.actor.GetOrientation()) 2325 factor.SetPosition(self.actor.GetPosition()) 2326 factor.SetUseBounds(self.actor.GetUseBounds()) 2327 2328 if origin is None: 2329 factor.SetOrigin(self.actor.GetOrigin()) 2330 else: 2331 factor.SetOrigin(origin) 2332 2333 factor.PickableOff() 2334 2335 if isinstance(camera, vtki.vtkCamera): 2336 factor.SetCamera(camera) 2337 else: 2338 plt = vedo.plotter_instance 2339 if plt and plt.renderer and plt.renderer.GetActiveCamera(): 2340 factor.SetCamera(plt.renderer.GetActiveCamera()) 2341 2342 self.actor = None 2343 factor.retrieve_object = weak_ref_to(self) 2344 self.actor = factor 2345 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.
2347 def wireframe(self, value=True) -> Self: 2348 """Set mesh's representation as wireframe or solid surface.""" 2349 if value: 2350 self.properties.SetRepresentationToWireframe() 2351 else: 2352 self.properties.SetRepresentationToSurface() 2353 return self
Set mesh's representation as wireframe or solid surface.
2355 def flat(self) -> Self: 2356 """Set surface interpolation to flat. 2357 2358 <img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png" width="700"> 2359 """ 2360 self.properties.SetInterpolationToFlat() 2361 return self
Set surface interpolation to flat.
2363 def phong(self) -> Self: 2364 """Set surface interpolation to "phong".""" 2365 self.properties.SetInterpolationToPhong() 2366 return self
Set surface interpolation to "phong".
2368 def backface_culling(self, value=True) -> Self: 2369 """Set culling of polygons based on orientation of normal with respect to camera.""" 2370 self.properties.SetBackfaceCulling(value) 2371 return self
Set culling of polygons based on orientation of normal with respect to camera.
2373 def render_lines_as_tubes(self, value=True) -> Self: 2374 """Wrap a fake tube around a simple line for visualization""" 2375 self.properties.SetRenderLinesAsTubes(value) 2376 return self
Wrap a fake tube around a simple line for visualization
2378 def frontface_culling(self, value=True) -> Self: 2379 """Set culling of polygons based on orientation of normal with respect to camera.""" 2380 self.properties.SetFrontfaceCulling(value) 2381 return self
Set culling of polygons based on orientation of normal with respect to camera.
2383 def backcolor(self, bc=None) -> Union[Self, np.ndarray]: 2384 """ 2385 Set/get mesh's backface color. 2386 """ 2387 back_prop = self.actor.GetBackfaceProperty() 2388 2389 if bc is None: 2390 if back_prop: 2391 return back_prop.GetDiffuseColor() 2392 return self 2393 2394 if self.properties.GetOpacity() < 1: 2395 return self 2396 2397 if not back_prop: 2398 back_prop = vtki.vtkProperty() 2399 2400 back_prop.SetDiffuseColor(colors.get_color(bc)) 2401 back_prop.SetOpacity(self.properties.GetOpacity()) 2402 self.actor.SetBackfaceProperty(back_prop) 2403 self.mapper.ScalarVisibilityOff() 2404 return self
Set/get mesh's backface color.
2406 def bc(self, backcolor=False) -> Union[Self, np.ndarray]: 2407 """Shortcut for `mesh.backcolor()`.""" 2408 return self.backcolor(backcolor)
Shortcut for mesh.backcolor()
.
2410 def linewidth(self, lw=None) -> Union[Self, int]: 2411 """Set/get width of mesh edges. Same as `lw()`.""" 2412 if lw is not None: 2413 if lw == 0: 2414 self.properties.EdgeVisibilityOff() 2415 self.properties.SetRepresentationToSurface() 2416 return self 2417 self.properties.EdgeVisibilityOn() 2418 self.properties.SetLineWidth(lw) 2419 else: 2420 return self.properties.GetLineWidth() 2421 return self
Set/get width of mesh edges. Same as lw()
.
2423 def lw(self, linewidth=None) -> Union[Self, int]: 2424 """Set/get width of mesh edges. Same as `linewidth()`.""" 2425 return self.linewidth(linewidth)
Set/get width of mesh edges. Same as linewidth()
.
2427 def linecolor(self, lc=None) -> Union[Self, np.ndarray]: 2428 """Set/get color of mesh edges. Same as `lc()`.""" 2429 if lc is None: 2430 return np.array(self.properties.GetEdgeColor()) 2431 self.properties.EdgeVisibilityOn() 2432 self.properties.SetEdgeColor(colors.get_color(lc)) 2433 return self
Set/get color of mesh edges. Same as lc()
.
2435 def lc(self, linecolor=None) -> Union[Self, np.ndarray]: 2436 """Set/get color of mesh edges. Same as `linecolor()`.""" 2437 return self.linecolor(linecolor)
Set/get color of mesh edges. Same as linecolor()
.
2439 def texture( 2440 self, 2441 tname, 2442 tcoords=None, 2443 interpolate=True, 2444 repeat=True, 2445 edge_clamp=False, 2446 scale=None, 2447 ushift=None, 2448 vshift=None, 2449 ) -> Self: 2450 """ 2451 Assign a texture to mesh from image file or predefined texture `tname`. 2452 If tname is set to `None` texture is disabled. 2453 Input tname can also be an array or a `vtkTexture`. 2454 2455 Arguments: 2456 tname : (numpy.array, str, Image, vtkTexture, None) 2457 the input texture to be applied. Can be a numpy array, a path to an image file, 2458 a vedo Image. The None value disables texture. 2459 tcoords : (numpy.array, str) 2460 this is the (u,v) texture coordinate array. Can also be a string of an existing array 2461 in the mesh. 2462 interpolate : (bool) 2463 turn on/off linear interpolation of the texture map when rendering. 2464 repeat : (bool) 2465 repeat of the texture when tcoords extend beyond the [0,1] range. 2466 edge_clamp : (bool) 2467 turn on/off the clamping of the texture map when 2468 the texture coords extend beyond the [0,1] range. 2469 Only used when repeat is False, and edge clamping is supported by the graphics card. 2470 scale : (bool) 2471 scale the texture image by this factor 2472 ushift : (bool) 2473 shift u-coordinates of texture by this amount 2474 vshift : (bool) 2475 shift v-coordinates of texture by this amount 2476 2477 Examples: 2478 - [texturecubes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/texturecubes.py) 2479 2480 ![](https://vedo.embl.es/images/basic/texturecubes.png) 2481 """ 2482 pd = self.dataset 2483 out_img = None 2484 2485 if tname is None: # disable texture 2486 pd.GetPointData().SetTCoords(None) 2487 pd.GetPointData().Modified() 2488 return self ###################################### 2489 2490 if isinstance(tname, vtki.vtkTexture): 2491 tu = tname 2492 2493 elif isinstance(tname, vedo.Image): 2494 tu = vtki.vtkTexture() 2495 out_img = tname.dataset 2496 2497 elif utils.is_sequence(tname): 2498 tu = vtki.vtkTexture() 2499 out_img = vedo.image._get_img(tname) 2500 2501 elif isinstance(tname, str): 2502 tu = vtki.vtkTexture() 2503 2504 if "https://" in tname: 2505 try: 2506 tname = vedo.file_io.download(tname, verbose=False) 2507 except: 2508 vedo.logger.error(f"texture {tname} could not be downloaded") 2509 return self 2510 2511 fn = tname + ".jpg" 2512 if os.path.exists(tname): 2513 fn = tname 2514 else: 2515 vedo.logger.error(f"texture file {tname} does not exist") 2516 return self 2517 2518 fnl = fn.lower() 2519 if ".jpg" in fnl or ".jpeg" in fnl: 2520 reader = vtki.new("JPEGReader") 2521 elif ".png" in fnl: 2522 reader = vtki.new("PNGReader") 2523 elif ".bmp" in fnl: 2524 reader = vtki.new("BMPReader") 2525 else: 2526 vedo.logger.error("in texture() supported files are only PNG, BMP or JPG") 2527 return self 2528 reader.SetFileName(fn) 2529 reader.Update() 2530 out_img = reader.GetOutput() 2531 2532 else: 2533 vedo.logger.error(f"in texture() cannot understand input {type(tname)}") 2534 return self 2535 2536 if tcoords is not None: 2537 2538 if isinstance(tcoords, str): 2539 vtarr = pd.GetPointData().GetArray(tcoords) 2540 2541 else: 2542 tcoords = np.asarray(tcoords) 2543 if tcoords.ndim != 2: 2544 vedo.logger.error("tcoords must be a 2-dimensional array") 2545 return self 2546 if tcoords.shape[0] != pd.GetNumberOfPoints(): 2547 vedo.logger.error("nr of texture coords must match nr of points") 2548 return self 2549 if tcoords.shape[1] != 2: 2550 vedo.logger.error("tcoords texture vector must have 2 components") 2551 vtarr = utils.numpy2vtk(tcoords) 2552 vtarr.SetName("TCoordinates") 2553 2554 pd.GetPointData().SetTCoords(vtarr) 2555 pd.GetPointData().Modified() 2556 2557 elif not pd.GetPointData().GetTCoords(): 2558 2559 # TCoords still void.. 2560 # check that there are no texture-like arrays: 2561 names = self.pointdata.keys() 2562 candidate_arr = "" 2563 for name in names: 2564 vtarr = pd.GetPointData().GetArray(name) 2565 if vtarr.GetNumberOfComponents() != 2: 2566 continue 2567 t0, t1 = vtarr.GetRange() 2568 if t0 >= 0 and t1 <= 1: 2569 candidate_arr = name 2570 2571 if candidate_arr: 2572 2573 vtarr = pd.GetPointData().GetArray(candidate_arr) 2574 pd.GetPointData().SetTCoords(vtarr) 2575 pd.GetPointData().Modified() 2576 2577 else: 2578 # last resource is automatic mapping 2579 tmapper = vtki.new("TextureMapToPlane") 2580 tmapper.AutomaticPlaneGenerationOn() 2581 tmapper.SetInputData(pd) 2582 tmapper.Update() 2583 tc = tmapper.GetOutput().GetPointData().GetTCoords() 2584 if scale or ushift or vshift: 2585 ntc = utils.vtk2numpy(tc) 2586 if scale: 2587 ntc *= scale 2588 if ushift: 2589 ntc[:, 0] += ushift 2590 if vshift: 2591 ntc[:, 1] += vshift 2592 tc = utils.numpy2vtk(tc) 2593 pd.GetPointData().SetTCoords(tc) 2594 pd.GetPointData().Modified() 2595 2596 if out_img: 2597 tu.SetInputData(out_img) 2598 tu.SetInterpolate(interpolate) 2599 tu.SetRepeat(repeat) 2600 tu.SetEdgeClamp(edge_clamp) 2601 2602 self.properties.SetColor(1, 1, 1) 2603 self.mapper.ScalarVisibilityOff() 2604 self.actor.SetTexture(tu) 2605 2606 # if seam_threshold is not None: 2607 # tname = self.dataset.GetPointData().GetTCoords().GetName() 2608 # grad = self.gradient(tname) 2609 # ugrad, vgrad = np.split(grad, 2, axis=1) 2610 # ugradm, vgradm = utils.mag2(ugrad), utils.mag2(vgrad) 2611 # gradm = np.log(ugradm + vgradm) 2612 # largegrad_ids = np.arange(len(grad))[gradm > seam_threshold * 4] 2613 # uvmap = self.pointdata[tname] 2614 # # collapse triangles that have large gradient 2615 # new_points = self.vertices.copy() 2616 # for f in self.cells: 2617 # if np.isin(f, largegrad_ids).all(): 2618 # id1, id2, id3 = f 2619 # uv1, uv2, uv3 = uvmap[f] 2620 # d12 = utils.mag2(uv1 - uv2) 2621 # d23 = utils.mag2(uv2 - uv3) 2622 # d31 = utils.mag2(uv3 - uv1) 2623 # idm = np.argmin([d12, d23, d31]) 2624 # if idm == 0: 2625 # new_points[id1] = new_points[id3] 2626 # new_points[id2] = new_points[id3] 2627 # elif idm == 1: 2628 # new_points[id2] = new_points[id1] 2629 # new_points[id3] = new_points[id1] 2630 # self.vertices = new_points 2631 2632 self.dataset.Modified() 2633 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
2874class ImageVisual(CommonVisual, Actor3DHelper): 2875 2876 def __init__(self) -> None: 2877 # print("init ImageVisual") 2878 super().__init__() 2879 2880 def memory_size(self) -> int: 2881 """ 2882 Return the size in bytes of the object in memory. 2883 """ 2884 return self.dataset.GetActualMemorySize() 2885 2886 def scalar_range(self) -> np.ndarray: 2887 """ 2888 Return the scalar range of the image. 2889 """ 2890 return np.array(self.dataset.GetScalarRange()) 2891 2892 def alpha(self, a=None) -> Union[Self, float]: 2893 """Set/get image's transparency in the rendering scene.""" 2894 if a is not None: 2895 self.properties.SetOpacity(a) 2896 return self 2897 return self.properties.GetOpacity() 2898 2899 def level(self, value=None) -> Union[Self, float]: 2900 """Get/Set the image color level (brightness) in the rendering scene.""" 2901 if value is None: 2902 return self.properties.GetColorLevel() 2903 self.properties.SetColorLevel(value) 2904 return self 2905 2906 def window(self, value=None) -> Union[Self, float]: 2907 """Get/Set the image color window (contrast) in the rendering scene.""" 2908 if value is None: 2909 return self.properties.GetColorWindow() 2910 self.properties.SetColorWindow(value) 2911 return self
Class to manage the visual aspects common to all objects.
2880 def memory_size(self) -> int: 2881 """ 2882 Return the size in bytes of the object in memory. 2883 """ 2884 return self.dataset.GetActualMemorySize()
Return the size in bytes of the object in memory.
2886 def scalar_range(self) -> np.ndarray: 2887 """ 2888 Return the scalar range of the image. 2889 """ 2890 return np.array(self.dataset.GetScalarRange())
Return the scalar range of the image.
2892 def alpha(self, a=None) -> Union[Self, float]: 2893 """Set/get image's transparency in the rendering scene.""" 2894 if a is not None: 2895 self.properties.SetOpacity(a) 2896 return self 2897 return self.properties.GetOpacity()
Set/get image's transparency in the rendering scene.
2899 def level(self, value=None) -> Union[Self, float]: 2900 """Get/Set the image color level (brightness) in the rendering scene.""" 2901 if value is None: 2902 return self.properties.GetColorLevel() 2903 self.properties.SetColorLevel(value) 2904 return self
Get/Set the image color level (brightness) in the rendering scene.
2906 def window(self, value=None) -> Union[Self, float]: 2907 """Get/Set the image color window (contrast) in the rendering scene.""" 2908 if value is None: 2909 return self.properties.GetColorWindow() 2910 self.properties.SetColorWindow(value) 2911 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.
2914class LightKit: 2915 """ 2916 A LightKit consists of three lights, a 'key light', a 'fill light', and a 'head light'. 2917 2918 The main light is the key light. It is usually positioned so that it appears like 2919 an overhead light (like the sun, or a ceiling light). 2920 It is generally positioned to shine down on the scene from about a 45 degree angle vertically 2921 and at least a little offset side to side. The key light usually at least about twice as bright 2922 as the total of all other lights in the scene to provide good modeling of object features. 2923 2924 The other lights in the kit (the fill light, headlight, and a pair of back lights) 2925 are weaker sources that provide extra illumination to fill in the spots that the key light misses. 2926 The fill light is usually positioned across from or opposite from the key light 2927 (though still on the same side of the object as the camera) in order to simulate diffuse reflections 2928 from other objects in the scene. 2929 2930 The headlight, always located at the position of the camera, reduces the contrast between areas lit 2931 by the key and fill light. The two back lights, one on the left of the object as seen from the observer 2932 and one on the right, fill on the high-contrast areas behind the object. 2933 To enforce the relationship between the different lights, the intensity of the fill, back and headlights 2934 are set as a ratio to the key light brightness. 2935 Thus, the brightness of all the lights in the scene can be changed by changing the key light intensity. 2936 2937 All lights are directional lights, infinitely far away with no falloff. Lights move with the camera. 2938 2939 For simplicity, the position of lights in the LightKit can only be specified using angles: 2940 the elevation (latitude) and azimuth (longitude) of each light with respect to the camera, in degrees. 2941 For example, a light at (elevation=0, azimuth=0) is located at the camera (a headlight). 2942 A light at (elevation=90, azimuth=0) is above the lookat point, shining down. 2943 Negative azimuth values move the lights clockwise as seen above, positive values counter-clockwise. 2944 So, a light at (elevation=45, azimuth=-20) is above and in front of the object and shining 2945 slightly from the left side. 2946 2947 LightKit limits the colors that can be assigned to any light to those of incandescent sources such as 2948 light bulbs and sunlight. It defines a special color spectrum called "warmth" from which light colors 2949 can be chosen, where 0 is cold blue, 0.5 is neutral white, and 1 is deep sunset red. 2950 Colors close to 0.5 are "cool whites" and "warm whites," respectively. 2951 2952 Since colors far from white on the warmth scale appear less bright, key-to-fill and key-to-headlight 2953 ratios are skewed by key, fill, and headlight colors. If `maintain_luminance` is set, LightKit will 2954 attempt to compensate for these perceptual differences by increasing the brightness of more saturated colors. 2955 2956 To specify the color of a light, positioning etc you can pass a dictionary with the following keys: 2957 - `intensity` : (float) The intensity of the key light. Default is 1. 2958 - `ratio` : (float) The ratio of the light intensity wrt key light. 2959 - `warmth` : (float) The warmth of the light. Default is 0.5. 2960 - `elevation` : (float) The elevation of the light in degrees. 2961 - `azimuth` : (float) The azimuth of the light in degrees. 2962 2963 Example: 2964 ```python 2965 from vedo import * 2966 lightkit = LightKit(head={"warmth":0.6}) 2967 mesh = Mesh(dataurl+"bunny.obj") 2968 plt = Plotter() 2969 plt.remove_lights().add(mesh, lightkit) 2970 plt.show().close() 2971 ``` 2972 """ 2973 def __init__(self, key=(), fill=(), back=(), head=(), maintain_luminance=False) -> None: 2974 2975 self.lightkit = vtki.new("LightKit") 2976 self.lightkit.SetMaintainLuminance(maintain_luminance) 2977 self.key = dict(key) 2978 self.head = dict(head) 2979 self.fill = dict(fill) 2980 self.back = dict(back) 2981 self.update() 2982 2983 def update(self) -> None: 2984 """Update the LightKit properties.""" 2985 if "warmth" in self.key: 2986 self.lightkit.SetKeyLightWarmth(self.key["warmth"]) 2987 if "warmth" in self.fill: 2988 self.lightkit.SetFillLightWarmth(self.fill["warmth"]) 2989 if "warmth" in self.head: 2990 self.lightkit.SetHeadLightWarmth(self.head["warmth"]) 2991 if "warmth" in self.back: 2992 self.lightkit.SetBackLightWarmth(self.back["warmth"]) 2993 2994 if "intensity" in self.key: 2995 self.lightkit.SetKeyLightIntensity(self.key["intensity"]) 2996 if "ratio" in self.fill: 2997 self.lightkit.SetKeyToFillRatio(self.key["ratio"]) 2998 if "ratio" in self.head: 2999 self.lightkit.SetKeyToHeadRatio(self.key["ratio"]) 3000 if "ratio" in self.back: 3001 self.lightkit.SetKeyToBackRatio(self.key["ratio"]) 3002 3003 if "elevation" in self.key: 3004 self.lightkit.SetKeyLightElevation(self.key["elevation"]) 3005 if "elevation" in self.fill: 3006 self.lightkit.SetFillLightElevation(self.fill["elevation"]) 3007 if "elevation" in self.head: 3008 self.lightkit.SetHeadLightElevation(self.head["elevation"]) 3009 if "elevation" in self.back: 3010 self.lightkit.SetBackLightElevation(self.back["elevation"]) 3011 3012 if "azimuth" in self.key: 3013 self.lightkit.SetKeyLightAzimuth(self.key["azimuth"]) 3014 if "azimuth" in self.fill: 3015 self.lightkit.SetFillLightAzimuth(self.fill["azimuth"]) 3016 if "azimuth" in self.head: 3017 self.lightkit.SetHeadLightAzimuth(self.head["azimuth"]) 3018 if "azimuth" in self.back: 3019 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()
2973 def __init__(self, key=(), fill=(), back=(), head=(), maintain_luminance=False) -> None: 2974 2975 self.lightkit = vtki.new("LightKit") 2976 self.lightkit.SetMaintainLuminance(maintain_luminance) 2977 self.key = dict(key) 2978 self.head = dict(head) 2979 self.fill = dict(fill) 2980 self.back = dict(back) 2981 self.update()
2983 def update(self) -> None: 2984 """Update the LightKit properties.""" 2985 if "warmth" in self.key: 2986 self.lightkit.SetKeyLightWarmth(self.key["warmth"]) 2987 if "warmth" in self.fill: 2988 self.lightkit.SetFillLightWarmth(self.fill["warmth"]) 2989 if "warmth" in self.head: 2990 self.lightkit.SetHeadLightWarmth(self.head["warmth"]) 2991 if "warmth" in self.back: 2992 self.lightkit.SetBackLightWarmth(self.back["warmth"]) 2993 2994 if "intensity" in self.key: 2995 self.lightkit.SetKeyLightIntensity(self.key["intensity"]) 2996 if "ratio" in self.fill: 2997 self.lightkit.SetKeyToFillRatio(self.key["ratio"]) 2998 if "ratio" in self.head: 2999 self.lightkit.SetKeyToHeadRatio(self.key["ratio"]) 3000 if "ratio" in self.back: 3001 self.lightkit.SetKeyToBackRatio(self.key["ratio"]) 3002 3003 if "elevation" in self.key: 3004 self.lightkit.SetKeyLightElevation(self.key["elevation"]) 3005 if "elevation" in self.fill: 3006 self.lightkit.SetFillLightElevation(self.fill["elevation"]) 3007 if "elevation" in self.head: 3008 self.lightkit.SetHeadLightElevation(self.head["elevation"]) 3009 if "elevation" in self.back: 3010 self.lightkit.SetBackLightElevation(self.back["elevation"]) 3011 3012 if "azimuth" in self.key: 3013 self.lightkit.SetKeyLightAzimuth(self.key["azimuth"]) 3014 if "azimuth" in self.fill: 3015 self.lightkit.SetFillLightAzimuth(self.fill["azimuth"]) 3016 if "azimuth" in self.head: 3017 self.lightkit.SetHeadLightAzimuth(self.head["azimuth"]) 3018 if "azimuth" in self.back: 3019 self.lightkit.SetBackLightAzimuth(self.back["azimuth"])
Update the LightKit properties.