vedo.addons
Create additional objects like axes, legends, lights, etc.
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3import numpy as np 4from typing import Union 5from typing_extensions import Self 6 7import vedo.vtkclasses as vtki # a wrapper for lazy imports 8 9import vedo 10from vedo import settings 11from vedo import utils 12from vedo import shapes 13from vedo.transformations import LinearTransform 14from vedo.assembly import Assembly, Group 15from vedo.colors import get_color, build_lut, color_map, printc 16from vedo.mesh import Mesh 17from vedo.pointcloud import Points, Point, merge 18from vedo.grids import TetMesh 19from vedo.volume import Volume 20 21__docformat__ = "google" 22 23__doc__ = """ 24Create additional objects like axes, legends, lights, etc. 25 26![](https://vedo.embl.es/images/pyplot/customAxes2.png) 27""" 28 29__all__ = [ 30 "ScalarBar", 31 "ScalarBar3D", 32 "Slider2D", 33 "Slider3D", 34 "Icon", 35 "LegendBox", 36 "Light", 37 "Axes", 38 "RendererFrame", 39 "Ruler2D", 40 "Ruler3D", 41 "RulerAxes", 42 "DistanceTool", 43 "SplineTool", 44 "DrawingWidget", 45 "Goniometer", 46 "Button", 47 "ButtonWidget", 48 "Flagpost", 49 "ProgressBarWidget", 50 "BoxCutter", 51 "PlaneCutter", 52 "SphereCutter", 53] 54 55######################################################################################## 56class Flagpost(vtki.vtkFlagpoleLabel): 57 """ 58 Create a flag post style element to describe an object. 59 """ 60 61 def __init__( 62 self, 63 txt="", 64 base=(0, 0, 0), 65 top=(0, 0, 1), 66 s=1, 67 c="k9", 68 bc="k1", 69 alpha=1, 70 lw=0, 71 font="Calco", 72 justify="center-left", 73 vspacing=1, 74 ): 75 """ 76 Create a flag post style element to describe an object. 77 78 Arguments: 79 txt : (str) 80 Text to display. The default is the filename or the object name. 81 base : (list) 82 position of the flag anchor point. 83 top : (list) 84 a 3D displacement or offset. 85 s : (float) 86 size of the text to be shown 87 c : (list) 88 color of text and line 89 bc : (list) 90 color of the flag background 91 alpha : (float) 92 opacity of text and box. 93 lw : (int) 94 line with of box frame. The default is 0. 95 font : (str) 96 font name. Use a monospace font for better rendering. The default is "Calco". 97 Type `vedo -r fonts` for a font demo. 98 Check [available fonts here](https://vedo.embl.es/fonts). 99 justify : (str) 100 internal text justification. The default is "center-left". 101 vspacing : (float) 102 vertical spacing between lines. 103 104 Examples: 105 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py) 106 107 ![](https://vedo.embl.es/images/other/flag_labels2.png) 108 """ 109 110 super().__init__() 111 112 base = utils.make3d(base) 113 top = utils.make3d(top) 114 115 self.SetBasePosition(*base) 116 self.SetTopPosition(*top) 117 118 self.SetFlagSize(s) 119 self.SetInput(txt) 120 self.PickableOff() 121 122 self.GetProperty().LightingOff() 123 self.GetProperty().SetLineWidth(lw + 1) 124 125 prop = self.GetTextProperty() 126 if bc is not None: 127 prop.SetBackgroundColor(get_color(bc)) 128 129 prop.SetOpacity(alpha) 130 prop.SetBackgroundOpacity(alpha) 131 if bc is not None and len(bc) == 4: 132 prop.SetBackgroundRGBA(alpha) 133 134 c = get_color(c) 135 prop.SetColor(c) 136 self.GetProperty().SetColor(c) 137 138 prop.SetFrame(bool(lw)) 139 prop.SetFrameWidth(lw) 140 prop.SetFrameColor(prop.GetColor()) 141 142 prop.SetFontFamily(vtki.VTK_FONT_FILE) 143 fl = utils.get_font_path(font) 144 prop.SetFontFile(fl) 145 prop.ShadowOff() 146 prop.BoldOff() 147 prop.SetOpacity(alpha) 148 prop.SetJustificationToLeft() 149 if "top" in justify: 150 prop.SetVerticalJustificationToTop() 151 if "bottom" in justify: 152 prop.SetVerticalJustificationToBottom() 153 if "cent" in justify: 154 prop.SetVerticalJustificationToCentered() 155 prop.SetJustificationToCentered() 156 if "left" in justify: 157 prop.SetJustificationToLeft() 158 if "right" in justify: 159 prop.SetJustificationToRight() 160 prop.SetLineSpacing(vspacing * 1.2) 161 self.SetUseBounds(False) 162 163 def text(self, value: str) -> Self: 164 self.SetInput(value) 165 return self 166 167 def on(self) -> Self: 168 self.VisibilityOn() 169 return self 170 171 def off(self) -> Self: 172 self.VisibilityOff() 173 return self 174 175 def toggle(self) -> Self: 176 self.SetVisibility(not self.GetVisibility()) 177 return self 178 179 def use_bounds(self, value=True) -> Self: 180 self.SetUseBounds(value) 181 return self 182 183 def color(self, c) -> Self: 184 c = get_color(c) 185 self.GetTextProperty().SetColor(c) 186 self.GetProperty().SetColor(c) 187 return self 188 189 def pos(self, p) -> Self: 190 p = np.asarray(p) 191 self.top = self.top - self.base + p 192 self.base = p 193 return self 194 195 @property 196 def base(self) -> np.ndarray: 197 return np.array(self.GetBasePosition()) 198 199 @base.setter 200 def base(self, value): 201 self.SetBasePosition(*value) 202 203 @property 204 def top(self) -> np.ndarray: 205 return np.array(self.GetTopPosition()) 206 207 @top.setter 208 def top(self, value): 209 self.SetTopPosition(*value) 210 211 212########################################################################################### 213class LegendBox(shapes.TextBase, vtki.vtkLegendBoxActor): 214 """ 215 Create a 2D legend box. 216 """ 217 218 def __init__( 219 self, 220 entries=(), 221 nmax=12, 222 c=None, 223 font="", 224 width=0.18, 225 height=None, 226 padding=2, 227 bg="k8", 228 alpha=0.25, 229 pos="top-right", 230 markers=None, 231 ): 232 """ 233 Create a 2D legend box for the list of specified objects. 234 235 Arguments: 236 nmax : (int) 237 max number of legend entries 238 c : (color) 239 text color, leave as None to pick the mesh color automatically 240 font : (str) 241 Check [available fonts here](https://vedo.embl.es/fonts) 242 width : (float) 243 width of the box as fraction of the window width 244 height : (float) 245 height of the box as fraction of the window height 246 padding : (int) 247 padding space in units of pixels 248 bg : (color) 249 background color of the box 250 alpha: (float) 251 opacity of the box 252 pos : (str, list) 253 position of the box, can be either a string or a (x,y) screen position in range [0,1] 254 255 Examples: 256 - [legendbox.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/legendbox.py) 257 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 258 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 259 260 ![](https://vedo.embl.es/images/other/flag_labels.png) 261 """ 262 super().__init__() 263 264 self.name = "LegendBox" 265 self.entries = entries[:nmax] 266 self.properties = self.GetEntryTextProperty() 267 268 n = 0 269 texts = [] 270 for e in self.entries: 271 ename = e.name 272 if "legend" in e.info.keys(): 273 if not e.info["legend"]: 274 ename = "" 275 else: 276 ename = str(e.info["legend"]) 277 if ename: 278 n += 1 279 texts.append(ename) 280 self.SetNumberOfEntries(n) 281 282 if not n: 283 return 284 285 self.ScalarVisibilityOff() 286 self.PickableOff() 287 self.SetPadding(padding) 288 289 self.properties.ShadowOff() 290 self.properties.BoldOff() 291 292 # self.properties.SetJustificationToLeft() # no effect 293 # self.properties.SetVerticalJustificationToTop() 294 295 if not font: 296 font = settings.default_font 297 298 self.font(font) 299 300 n = 0 301 for i in range(len(self.entries)): 302 ti = texts[i] 303 if not ti: 304 continue 305 e = entries[i] 306 if c is None: 307 col = e.properties.GetColor() 308 if col == (1, 1, 1): 309 col = (0.2, 0.2, 0.2) 310 else: 311 col = get_color(c) 312 if markers is None: # default 313 poly = e.dataset 314 else: 315 marker = markers[i] if utils.is_sequence(markers) else markers 316 if isinstance(marker, Points): 317 poly = marker.clone(deep=False).normalize().shift(0, 1, 0).dataset 318 else: # assume string marker 319 poly = vedo.shapes.Marker(marker, s=1).shift(0, 1, 0).dataset 320 321 self.SetEntry(n, poly, ti, col) 322 n += 1 323 324 self.SetWidth(width) 325 if height is None: 326 self.SetHeight(width / 3.0 * n) 327 else: 328 self.SetHeight(height) 329 330 self.pos(pos) 331 332 if alpha: 333 self.UseBackgroundOn() 334 self.SetBackgroundColor(get_color(bg)) 335 self.SetBackgroundOpacity(alpha) 336 else: 337 self.UseBackgroundOff() 338 self.LockBorderOn() 339 340 @property 341 def width(self): 342 """Return the width of the legend box.""" 343 return self.GetWidth() 344 345 @property 346 def height(self): 347 """Return the height of the legend box.""" 348 return self.GetHeight() 349 350 def pos(self, pos): 351 """Set the position of the legend box.""" 352 sx, sy = 1 - self.GetWidth(), 1 - self.GetHeight() 353 if pos == 1 or ("top" in pos and "left" in pos): 354 self.GetPositionCoordinate().SetValue(0, sy) 355 elif pos == 2 or ("top" in pos and "right" in pos): 356 self.GetPositionCoordinate().SetValue(sx, sy) 357 elif pos == 3 or ("bottom" in pos and "left" in pos): 358 self.GetPositionCoordinate().SetValue(0, 0) 359 elif pos == 4 or ("bottom" in pos and "right" in pos): 360 self.GetPositionCoordinate().SetValue(sx, 0) 361 elif "cent" in pos and "right" in pos: 362 self.GetPositionCoordinate().SetValue(sx, sy - 0.25) 363 elif "cent" in pos and "left" in pos: 364 self.GetPositionCoordinate().SetValue(0, sy - 0.25) 365 elif "cent" in pos and "bottom" in pos: 366 self.GetPositionCoordinate().SetValue(sx - 0.25, 0) 367 elif "cent" in pos and "top" in pos: 368 self.GetPositionCoordinate().SetValue(sx - 0.25, sy) 369 elif utils.is_sequence(pos): 370 self.GetPositionCoordinate().SetValue(pos[0], pos[1]) 371 else: 372 vedo.logger.error("LegendBox: pos must be in range [1-4] or a [x,y] list") 373 374 return self 375 376 377class ButtonWidget: 378 """ 379 Create a button widget. 380 """ 381 382 def __init__( 383 self, 384 function, 385 states=(), 386 c=("white"), 387 bc=("green4"), 388 alpha=1.0, 389 font="Calco", 390 size=100, 391 plotter=None, 392 ): 393 """ 394 Create a button widget. 395 396 States can be either text strings or images. 397 398 Arguments: 399 function : (function) 400 external function to be called by the widget 401 states : (list) 402 the list of possible states, eg. ['On', 'Off'] 403 c : (list) 404 the list of colors for each state eg. ['red3', 'green5'] 405 bc : (list) 406 the list of background colors for each state 407 alpha : (float) 408 opacity level 409 font : (str) 410 font type 411 size : (int) 412 size of button font 413 plotter : (Plotter) 414 the plotter object to which the widget is added 415 416 Example: 417 ```py 418 from vedo import * 419 420 def button_func(widget, evtname): 421 print("button_func called") 422 cone.color(button.state) 423 424 def on_mouse_click(event): 425 if event.object: 426 print("on_mouse_click", event) 427 cone.color(button.state) 428 429 # Create a cone 430 cone = Cone().color(0) 431 432 # Create a plotter 433 plt = Plotter(bg='bb', axes=1) 434 plt.add_callback('mouse click', on_mouse_click) 435 436 plt.add(cone) 437 438 # Create a button widget 439 img0 = Image("play-button.png") 440 img1 = Image("power-on.png") 441 442 button = ButtonWidget( 443 button_func, 444 # states=["State 0", "State 1"], 445 states=[img0, img1], 446 c=["red4", "blue4"], 447 bc=("k9", "k5"), 448 size=100, 449 plotter=plt, 450 ) 451 button.pos([0,0]).enable() 452 453 plt.show() 454 ``` 455 """ 456 457 self.widget = vtki.new("ButtonWidget") 458 459 self.function = function 460 self.states = states 461 self.colors = c 462 self.background_colors = bc 463 self.plotter = plotter 464 self.size = size 465 466 assert len(states) == len(c), "states and colors must have the same length" 467 assert len(states) == len(bc), "states and background colors must have the same length" 468 469 self.interactor = None 470 if plotter is not None: 471 self.interactor = plotter.interactor 472 self.widget.SetInteractor(plotter.interactor) 473 else: 474 if vedo.plotter_instance: 475 self.interactor = vedo.plotter_instance.interactor 476 self.widget.SetInteractor(self.interactor) 477 478 self.representation = vtki.new("TexturedButtonRepresentation2D") 479 self.representation.SetNumberOfStates(len(states)) 480 for i, state in enumerate(states): 481 482 if isinstance(state, vedo.Image): 483 state = state.dataset 484 485 elif isinstance(state, str): 486 txt = state 487 tp = vtki.vtkTextProperty() 488 tp.BoldOff() 489 tp.FrameOff() 490 col = c[i] 491 tp.SetColor(vedo.get_color(col)) 492 tp.ShadowOff() 493 tp.ItalicOff() 494 col = bc[i] 495 tp.SetBackgroundColor(vedo.get_color(col)) 496 tp.SetBackgroundOpacity(alpha) 497 tp.UseTightBoundingBoxOff() 498 499 # tp.SetJustificationToLeft() 500 # tp.SetVerticalJustificationToCentered() 501 # tp.SetJustificationToCentered() 502 width, height = 100 * len(txt), 1000 503 504 fpath = vedo.utils.get_font_path(font) 505 tp.SetFontFamily(vtki.VTK_FONT_FILE) 506 tp.SetFontFile(fpath) 507 508 tr = vtki.new("TextRenderer") 509 fs = tr.GetConstrainedFontSize(txt, tp, width, height, 500) 510 tp.SetFontSize(fs) 511 512 img = vtki.vtkImageData() 513 tr.RenderString(tp, txt, img, [width, height], 500) 514 state = img 515 516 self.representation.SetButtonTexture(i, state) 517 518 self.widget.SetRepresentation(self.representation) 519 self.widget.AddObserver("StateChangedEvent", function) 520 521 def __del__(self): 522 self.widget.Off() 523 self.widget.SetInteractor(None) 524 self.widget.SetRepresentation(None) 525 self.representation = None 526 self.interactor = None 527 self.function = None 528 self.states = () 529 self.widget = None 530 self.plotter = None 531 532 def pos(self, pos): 533 assert len(pos) == 2, "pos must be a 2D position" 534 if not self.plotter: 535 vedo.logger.warning("ButtonWidget: pos() can only be used if a Plotter is provided") 536 return self 537 coords = vtki.vtkCoordinate() 538 coords.SetCoordinateSystemToNormalizedDisplay() 539 coords.SetValue(pos[0], pos[1]) 540 sz = self.size 541 ren = self.plotter.renderer 542 p = coords.GetComputedDisplayValue(ren) 543 bds = [0, 0, 0, 0, 0, 0] 544 bds[0] = p[0] - sz 545 bds[1] = bds[0] + sz 546 bds[2] = p[1] - sz 547 bds[3] = bds[2] + sz 548 self.representation.SetPlaceFactor(1) 549 self.representation.PlaceWidget(bds) 550 return self 551 552 def enable(self): 553 self.widget.On() 554 return self 555 556 def disable(self): 557 self.widget.Off() 558 return self 559 560 def next_state(self): 561 self.representation.NextState() 562 return self 563 564 @property 565 def state(self): 566 return self.representation.GetState() 567 568 @state.setter 569 def state(self, i): 570 self.representation.SetState(i) 571 572 573class Button(vedo.shapes.Text2D): 574 """ 575 Build a Button object. 576 """ 577 578 def __init__( 579 self, 580 fnc=None, 581 states=("Button"), 582 c=("white"), 583 bc=("green4"), 584 pos=(0.7, 0.1), 585 size=24, 586 font="Courier", 587 bold=True, 588 italic=False, 589 alpha=1, 590 angle=0, 591 ): 592 """ 593 Build a Button object to be shown in the rendering window. 594 595 Arguments: 596 fnc : (function) 597 external function to be called by the widget 598 states : (list) 599 the list of possible states, eg. ['On', 'Off'] 600 c : (list) 601 the list of colors for each state eg. ['red3', 'green5'] 602 bc : (list) 603 the list of background colors for each state 604 pos : (list, str) 605 2D position in pixels from left-bottom corner 606 size : (int) 607 size of button font 608 font : (str) 609 font type 610 bold : (bool) 611 set bold font face 612 italic : (bool) 613 italic font face 614 alpha : (float) 615 opacity level 616 angle : (float) 617 anticlockwise rotation in degrees 618 619 Examples: 620 - [buttons1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons1.py) 621 - [buttons2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons2.py) 622 623 ![](https://user-images.githubusercontent.com/32848391/50738870-c0fe2500-11d8-11e9-9b78-92754f5c5968.jpg) 624 625 - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py) 626 627 ![](https://vedo.embl.es/images/advanced/timer_callback1.jpg) 628 """ 629 super().__init__() 630 631 self.status_idx = 0 632 633 self.spacer = " " 634 635 self.states = states 636 637 if not utils.is_sequence(c): 638 c = [c] 639 self.colors = c 640 641 if not utils.is_sequence(bc): 642 bc = [bc] 643 self.bcolors = bc 644 645 assert len(c) == len(bc), "in Button color number mismatch!" 646 647 self.function = fnc 648 self.function_id = None 649 650 self.status(0) 651 652 if font == "courier": 653 font = font.capitalize() 654 self.font(font).bold(bold).italic(italic) 655 656 self.alpha(alpha).angle(angle) 657 self.size(size / 20) 658 self.pos(pos, "center") 659 self.PickableOn() 660 661 def status(self, s=None) -> "Button": 662 """ 663 Set/Get the status of the button. 664 """ 665 if s is None: 666 return self.states[self.status_idx] 667 668 if isinstance(s, str): 669 s = self.states.index(s) 670 self.status_idx = s 671 self.text(self.spacer + self.states[s] + self.spacer) 672 s = s % len(self.bcolors) 673 self.color(self.colors[s]) 674 self.background(self.bcolors[s]) 675 return self 676 677 def switch(self) -> "Button": 678 """ 679 Change/cycle button status to the next defined status in states list. 680 """ 681 self.status_idx = (self.status_idx + 1) % len(self.states) 682 self.status(self.status_idx) 683 return self 684 685 686##################################################################### 687class SplineTool(vtki.vtkContourWidget): 688 """ 689 Spline tool, draw a spline through a set of points interactively. 690 """ 691 692 def __init__( 693 self, 694 points, 695 pc="k", 696 ps=8, 697 lc="r4", 698 ac="g5", 699 lw=2, 700 alpha=1, 701 closed=False, 702 ontop=True, 703 can_add_nodes=True, 704 ): 705 """ 706 Spline tool, draw a spline through a set of points interactively. 707 708 Arguments: 709 points : (list), Points 710 initial set of points. 711 pc : (str) 712 point color. 713 ps : (int) 714 point size. 715 lc : (str) 716 line color. 717 ac : (str) 718 active point color. 719 lw : (int) 720 line width. 721 alpha : (float) 722 line transparency level. 723 closed : (bool) 724 spline is closed or open. 725 ontop : (bool) 726 show it always on top of other objects. 727 can_add_nodes : (bool) 728 allow to add (or remove) new nodes interactively. 729 730 Examples: 731 - [spline_tool.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/spline_tool.py) 732 733 ![](https://vedo.embl.es/images/basic/spline_tool.png) 734 """ 735 super().__init__() 736 737 self.representation = self.GetRepresentation() 738 self.representation.SetAlwaysOnTop(ontop) 739 self.SetAllowNodePicking(can_add_nodes) 740 741 self.representation.GetLinesProperty().SetColor(get_color(lc)) 742 self.representation.GetLinesProperty().SetLineWidth(lw) 743 self.representation.GetLinesProperty().SetOpacity(alpha) 744 if lw == 0 or alpha == 0: 745 self.representation.GetLinesProperty().SetOpacity(0) 746 747 self.representation.GetActiveProperty().SetLineWidth(lw + 1) 748 self.representation.GetActiveProperty().SetColor(get_color(ac)) 749 750 self.representation.GetProperty().SetColor(get_color(pc)) 751 self.representation.GetProperty().SetPointSize(ps) 752 self.representation.GetProperty().RenderPointsAsSpheresOn() 753 754 # self.representation.BuildRepresentation() # crashes 755 756 self.SetRepresentation(self.representation) 757 758 if utils.is_sequence(points): 759 self.points = Points(points) 760 else: 761 self.points = points 762 763 self.closed = closed 764 765 @property 766 def interactor(self): 767 """Return the current interactor.""" 768 return self.GetInteractor() 769 770 @interactor.setter 771 def interactor(self, iren): 772 """Set the current interactor.""" 773 self.SetInteractor(iren) 774 775 def add(self, pt) -> "SplineTool": 776 """ 777 Add one point at a specified position in space if 3D, 778 or 2D screen-display position if 2D. 779 """ 780 if len(pt) == 2: 781 self.representation.AddNodeAtDisplayPosition(int(pt[0]), int(pt[1])) 782 else: 783 self.representation.AddNodeAtWorldPosition(pt) 784 return self 785 786 def add_observer(self, event, func, priority=1) -> int: 787 """Add an observer to the widget.""" 788 event = utils.get_vtk_name_event(event) 789 cid = self.AddObserver(event, func, priority) 790 return cid 791 792 def remove(self, i: int) -> "SplineTool": 793 """Remove specific node by its index""" 794 self.representation.DeleteNthNode(i) 795 return self 796 797 def on(self) -> "SplineTool": 798 """Activate/Enable the tool""" 799 self.On() 800 self.Render() 801 return self 802 803 def off(self) -> "SplineTool": 804 """Disactivate/Disable the tool""" 805 self.Off() 806 self.Render() 807 return self 808 809 def render(self) -> "SplineTool": 810 """Render the spline""" 811 self.Render() 812 return self 813 814 # def bounds(self) -> np.ndarray: 815 # """Retrieve the bounding box of the spline as [x0,x1, y0,y1, z0,z1]""" 816 # return np.array(self.GetBounds()) 817 818 def spline(self) -> vedo.Line: 819 """Return the vedo.Spline object.""" 820 self.representation.SetClosedLoop(self.closed) 821 self.representation.BuildRepresentation() 822 pd = self.representation.GetContourRepresentationAsPolyData() 823 ln = vedo.Line(pd, lw=2, c="k") 824 return ln 825 826 def nodes(self, onscreen=False) -> np.ndarray: 827 """Return the current position in space (or on 2D screen-display) of the spline nodes.""" 828 n = self.representation.GetNumberOfNodes() 829 pts = [] 830 for i in range(n): 831 p = [0.0, 0.0, 0.0] 832 if onscreen: 833 self.representation.GetNthNodeDisplayPosition(i, p) 834 else: 835 self.representation.GetNthNodeWorldPosition(i, p) 836 pts.append(p) 837 return np.array(pts) 838 839 840class DrawingWidget: 841 def __init__(self, obj, c="green5", lw=4, closed=False, snap_to_image=False): 842 """ 843 3D widget for tracing on planar props. 844 This is primarily designed for manually tracing over image data. 845 846 - Any object can be input rather than just 2D images 847 - The widget fires pick events at the input prop to decide where to move its handles 848 - The widget has 2D glyphs for handles instead of 3D spheres. 849 850 The button actions and key modifiers are as follows for controlling the widget: 851 1) left button click over the image, hold and drag draws a free hand line. 852 2) left button click and release erases the widget line, if it exists, and repositions the first handle. 853 3) middle button click starts a snap drawn line. 854 The line is terminated by clicking the middle button while ressing the ctrl key. 855 4) when tracing a continuous or snap drawn line, if the last cursor position is within a specified 856 tolerance to the first handle, the widget line will form a closed loop. 857 5) right button clicking and holding on any handle that is part of a snap drawn line allows handle dragging: 858 existing line segments are updated accordingly. If the path is open and closing_radius is set, 859 the path can be closed by repositioning the first and last points over one another. 860 6) Ctrl + right button down on any handle will erase it: existing snap drawn line segments are updated accordingly. 861 If the line was formed by continuous tracing, the line is deleted leaving one handle. 862 7) Shift + right button down on any snap drawn line segment will insert a handle at the cursor position. 863 The line segment is split accordingly. 864 865 Arguments: 866 obj : vtkProp 867 The prop to trace on. 868 c : str, optional 869 The color of the line. The default is "green5". 870 lw : int, optional 871 The line width. The default is 4. 872 closed : bool, optional 873 Whether to close the line. The default is False. 874 snap_to_image : bool, optional 875 Whether to snap to the image. The default is False. 876 877 Example: 878 - [spline_draw2.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/spline_draw2.py) 879 """ 880 881 self.widget = vtki.new("ImageTracerWidget") 882 883 self.line = None 884 self.line_properties = self.widget.GetLineProperty() 885 self.line_properties.SetColor(vedo.get_color(c)) 886 self.line_properties.SetLineWidth(lw) 887 self.callback_id = None 888 self.event_name = "EndInteractionEvent" 889 890 if vedo.plotter_instance: 891 self.widget.SetInteractor(vedo.plotter_instance.interactor) 892 if vedo.plotter_instance.renderer: 893 self.widget.SetDefaultRenderer(vedo.plotter_instance.renderer) 894 895 try: 896 self.widget.SetViewProp(obj.actor) 897 except AttributeError: 898 self.widget.SetViewProp(obj) 899 900 if closed: 901 closing_radius = 1e10 902 self.widget.SetAutoClose(1) 903 self.widget.SetCaptureRadius(closing_radius) 904 905 self.widget.SetProjectToPlane(0) 906 self.widget.SetProjectionNormal(2) # XY plane 907 self.widget.SetProjectionPosition(0) 908 self.widget.SetSnapToImage(snap_to_image) 909 910 def callback(self, widget, eventId) -> None: 911 path = vtki.vtkPolyData() 912 widget.GetPath(path) 913 self.line = vedo.shapes.Line(path, c=self.line_properties.GetColor()) 914 # print(f"There are {path.GetNumberOfPoints()} points in the line.") 915 916 def add_observer(self, event, func, priority=1) -> int: 917 """Add an observer to the widget.""" 918 event = utils.get_vtk_name_event(event) 919 cid = self.widget.AddObserver(event, func, priority) 920 return cid 921 922 @property 923 def interactor(self): 924 return self.widget.GetInteractor() 925 926 @interactor.setter 927 def interactor(self, value): 928 self.widget.SetInteractor(value) 929 930 @property 931 def renderer(self): 932 return self.widget.GetDefaultRenderer() 933 934 @renderer.setter 935 def renderer(self, value): 936 self.widget.SetDefaultRenderer(value) 937 938 def on(self) -> Self: 939 self.widget.On() 940 ev_name = vedo.utils.get_vtk_name_event(self.event_name) 941 self.callback_id = self.widget.AddObserver(ev_name, self.callback, 1000) 942 return self 943 944 def off(self) -> None: 945 self.widget.Off() 946 self.widget.RemoveObserver(self.callback_id) 947 948 def freeze(self, value=True) -> Self: 949 self.widget.SetInteraction(not value) 950 return self 951 952 def remove(self) -> None: 953 self.widget.Off() 954 self.widget.RemoveObserver(self.callback_id) 955 self.widget.SetInteractor(None) 956 self.line = None 957 self.line_properties = None 958 self.callback_id = None 959 self.widget = None 960 961 962##################################################################### 963class SliderWidget(vtki.vtkSliderWidget): 964 """Helper class for `vtkSliderWidget`""" 965 966 def __init__(self): 967 super().__init__() 968 969 @property 970 def interactor(self): 971 return self.GetInteractor() 972 973 @interactor.setter 974 def interactor(self, iren): 975 self.SetInteractor(iren) 976 977 @property 978 def representation(self): 979 return self.GetRepresentation() 980 981 @property 982 def value(self): 983 return self.GetRepresentation().GetValue() 984 985 @value.setter 986 def value(self, val): 987 self.GetRepresentation().SetValue(val) 988 989 @property 990 def renderer(self): 991 return self.GetCurrentRenderer() 992 993 @renderer.setter 994 def renderer(self, ren): 995 self.SetCurrentRenderer(ren) 996 997 @property 998 def title(self): 999 self.GetRepresentation().GetTitleText() 1000 1001 @title.setter 1002 def title(self, txt): 1003 self.GetRepresentation().SetTitleText(str(txt)) 1004 1005 @property 1006 def range(self): 1007 xmin = self.GetRepresentation().GetMinimumValue() 1008 xmax = self.GetRepresentation().GetMaximumValue() 1009 return [xmin, xmax] 1010 1011 @range.setter 1012 def range(self, vals): 1013 if vals[0] is not None: 1014 self.GetRepresentation().SetMinimumValue(vals[0]) 1015 if vals[1] is not None: 1016 self.GetRepresentation().SetMaximumValue(vals[1]) 1017 1018 def on(self) -> Self: 1019 self.EnabledOn() 1020 return self 1021 1022 def off(self) -> Self: 1023 self.EnabledOff() 1024 return self 1025 1026 def toggle(self) -> Self: 1027 self.SetEnabled(not self.GetEnabled()) 1028 return self 1029 1030 def add_observer(self, event, func, priority=1) -> int: 1031 """Add an observer to the widget.""" 1032 event = utils.get_vtk_name_event(event) 1033 cid = self.AddObserver(event, func, priority) 1034 return cid 1035 1036 1037##################################################################### 1038def Goniometer( 1039 p1, 1040 p2, 1041 p3, 1042 font="", 1043 arc_size=0.4, 1044 s=1, 1045 italic=0, 1046 rotation=0, 1047 prefix="", 1048 lc="k2", 1049 c="white", 1050 alpha=1, 1051 lw=2, 1052 precision=3, 1053): 1054 """ 1055 Build a graphical goniometer to measure the angle formed by 3 points in space. 1056 1057 Arguments: 1058 p1 : (list) 1059 first point 3D coordinates. 1060 p2 : (list) 1061 the vertex point. 1062 p3 : (list) 1063 the last point defining the angle. 1064 font : (str) 1065 Font face. Check [available fonts here](https://vedo.embl.es/fonts). 1066 arc_size : (float) 1067 dimension of the arc wrt the smallest axis. 1068 s : (float) 1069 size of the text. 1070 italic : (float, bool) 1071 italic text. 1072 rotation : (float) 1073 rotation of text in degrees. 1074 prefix : (str) 1075 append this string to the numeric value of the angle. 1076 lc : (list) 1077 color of the goniometer lines. 1078 c : (str) 1079 color of the goniometer angle filling. Set alpha=0 to remove it. 1080 alpha : (float) 1081 transparency level. 1082 lw : (float) 1083 line width. 1084 precision : (int) 1085 number of significant digits. 1086 1087 Examples: 1088 - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py) 1089 1090 ![](https://vedo.embl.es/images/pyplot/goniometer.png) 1091 """ 1092 if isinstance(p1, Points): p1 = p1.pos() 1093 if isinstance(p2, Points): p2 = p2.pos() 1094 if isinstance(p3, Points): p3 = p3.pos() 1095 if len(p1)==2: p1=[p1[0], p1[1], 0.0] 1096 if len(p2)==2: p2=[p2[0], p2[1], 0.0] 1097 if len(p3)==2: p3=[p3[0], p3[1], 0.0] 1098 p1, p2, p3 = np.array(p1), np.array(p2), np.array(p3) 1099 1100 acts = [] 1101 ln = shapes.Line([p1, p2, p3], lw=lw, c=lc) 1102 acts.append(ln) 1103 1104 va = utils.versor(p1 - p2) 1105 vb = utils.versor(p3 - p2) 1106 r = min(utils.mag(p3 - p2), utils.mag(p1 - p2)) * arc_size 1107 ptsarc = [] 1108 res = 120 1109 imed = int(res / 2) 1110 for i in range(res + 1): 1111 vi = utils.versor(vb * i / res + va * (res - i) / res) 1112 if i == imed: 1113 vc = np.array(vi) 1114 ptsarc.append(p2 + vi * r) 1115 arc = shapes.Line(ptsarc).lw(lw).c(lc) 1116 acts.append(arc) 1117 1118 angle = np.arccos(np.dot(va, vb)) * 180 / np.pi 1119 1120 lb = shapes.Text3D( 1121 prefix + utils.precision(angle, precision) + "º", 1122 s=r / 12 * s, 1123 font=font, 1124 italic=italic, 1125 justify="center", 1126 ) 1127 cr = np.cross(va, vb) 1128 lb.reorient([0, 0, 1], cr * np.sign(cr[2]), rotation=rotation, xyplane=False) 1129 lb.pos(p2 + vc * r / 1.75) 1130 lb.c(c).bc("tomato").lighting("off") 1131 acts.append(lb) 1132 1133 if alpha > 0: 1134 pts = [p2] + arc.vertices.tolist() + [p2] 1135 msh = Mesh([pts, [list(range(arc.npoints + 2))]], c=lc, alpha=alpha) 1136 msh.lighting("off") 1137 msh.triangulate() 1138 msh.shift(0, 0, -r / 10000) # to resolve 2d conflicts.. 1139 acts.append(msh) 1140 1141 asse = Assembly(acts) 1142 asse.name = "Goniometer" 1143 return asse 1144 1145 1146def Light(pos, focal_point=(0, 0, 0), angle=180, c=None, intensity=1): 1147 """ 1148 Generate a source of light placed at `pos` and directed to `focal point`. 1149 Returns a `vtkLight` object. 1150 1151 Arguments: 1152 focal_point : (list) 1153 focal point, if a `vedo` object is passed then will grab its position. 1154 angle : (float) 1155 aperture angle of the light source, in degrees 1156 c : (color) 1157 set the light color 1158 intensity : (float) 1159 intensity value between 0 and 1. 1160 1161 Check also: 1162 `plotter.Plotter.remove_lights()` 1163 1164 Examples: 1165 - [light_sources.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/light_sources.py) 1166 1167 ![](https://vedo.embl.es/images/basic/lights.png) 1168 """ 1169 if c is None: 1170 try: 1171 c = pos.color() 1172 except AttributeError: 1173 c = "white" 1174 1175 try: 1176 pos = pos.pos() 1177 except AttributeError: 1178 pass 1179 1180 try: 1181 focal_point = focal_point.pos() 1182 except AttributeError: 1183 pass 1184 1185 light = vtki.vtkLight() 1186 light.SetLightTypeToSceneLight() 1187 light.SetPosition(pos) 1188 light.SetConeAngle(angle) 1189 light.SetFocalPoint(focal_point) 1190 light.SetIntensity(intensity) 1191 light.SetColor(get_color(c)) 1192 return light 1193 1194 1195##################################################################### 1196def ScalarBar( 1197 obj, 1198 title="", 1199 pos=(), 1200 size=(80, 400), 1201 font_size=14, 1202 title_yoffset=20, 1203 nlabels=None, 1204 c="k", 1205 horizontal=False, 1206 use_alpha=True, 1207 label_format=":6.3g", 1208) -> Union[vtki.vtkScalarBarActor, None]: 1209 """ 1210 A 2D scalar bar for the specified object. 1211 1212 Arguments: 1213 title : (str) 1214 scalar bar title 1215 pos : (list) 1216 position coordinates of the bottom left corner. 1217 Can also be a pair of (x,y) values in the range [0,1] 1218 to indicate the position of the bottom-left and top-right corners. 1219 size : (float,float) 1220 size of the scalarbar in number of pixels (width, height) 1221 font_size : (float) 1222 size of font for title and numeric labels 1223 title_yoffset : (float) 1224 vertical space offset between title and color scalarbar 1225 nlabels : (int) 1226 number of numeric labels 1227 c : (list) 1228 color of the scalar bar text 1229 horizontal : (bool) 1230 lay the scalarbar horizontally 1231 use_alpha : (bool) 1232 render transparency in the color bar itself 1233 label_format : (str) 1234 c-style format string for numeric labels 1235 1236 Examples: 1237 - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py) 1238 1239 ![](https://user-images.githubusercontent.com/32848391/62940174-4bdc7900-bdd3-11e9-9713-e4f3e2fdab63.png) 1240 """ 1241 1242 if isinstance(obj, (Points, TetMesh, vedo.UnstructuredGrid)): 1243 vtkscalars = obj.dataset.GetPointData().GetScalars() 1244 if vtkscalars is None: 1245 vtkscalars = obj.dataset.GetCellData().GetScalars() 1246 if not vtkscalars: 1247 return None 1248 lut = vtkscalars.GetLookupTable() 1249 if not lut: 1250 lut = obj.mapper.GetLookupTable() 1251 if not lut: 1252 return None 1253 1254 elif isinstance(obj, Volume): 1255 lut = utils.ctf2lut(obj) 1256 1257 elif utils.is_sequence(obj) and len(obj) == 2: 1258 x = np.linspace(obj[0], obj[1], 256) 1259 data = [] 1260 for i in range(256): 1261 rgb = color_map(i, c, 0, 256) 1262 data.append([x[i], rgb]) 1263 lut = build_lut(data) 1264 1265 elif not hasattr(obj, "mapper"): 1266 vedo.logger.error(f"in add_scalarbar(): input is invalid {type(obj)}. Skip.") 1267 return None 1268 1269 else: 1270 return None 1271 1272 c = get_color(c) 1273 sb = vtki.vtkScalarBarActor() 1274 1275 # print("GetLabelFormat", sb.GetLabelFormat()) 1276 label_format = label_format.replace(":", "%-#") 1277 sb.SetLabelFormat(label_format) 1278 1279 sb.SetLookupTable(lut) 1280 sb.SetUseOpacity(use_alpha) 1281 sb.SetDrawFrame(0) 1282 sb.SetDrawBackground(0) 1283 if lut.GetUseBelowRangeColor(): 1284 sb.DrawBelowRangeSwatchOn() 1285 sb.SetBelowRangeAnnotation("") 1286 if lut.GetUseAboveRangeColor(): 1287 sb.DrawAboveRangeSwatchOn() 1288 sb.SetAboveRangeAnnotation("") 1289 if lut.GetNanColor() != (0.5, 0.0, 0.0, 1.0): 1290 sb.DrawNanAnnotationOn() 1291 sb.SetNanAnnotation("nan") 1292 1293 if title: 1294 if "\\" in repr(title): 1295 for r in shapes._reps: 1296 title = title.replace(r[0], r[1]) 1297 titprop = sb.GetTitleTextProperty() 1298 titprop.BoldOn() 1299 titprop.ItalicOff() 1300 titprop.ShadowOff() 1301 titprop.SetColor(c) 1302 titprop.SetVerticalJustificationToTop() 1303 titprop.SetFontSize(font_size) 1304 titprop.SetFontFamily(vtki.VTK_FONT_FILE) 1305 titprop.SetFontFile(utils.get_font_path(vedo.settings.default_font)) 1306 sb.SetTitle(title) 1307 sb.SetVerticalTitleSeparation(title_yoffset) 1308 sb.SetTitleTextProperty(titprop) 1309 1310 sb.SetTextPad(0) 1311 sb.UnconstrainedFontSizeOn() 1312 sb.DrawAnnotationsOn() 1313 sb.DrawTickLabelsOn() 1314 sb.SetMaximumNumberOfColors(256) 1315 if nlabels is not None: 1316 sb.SetNumberOfLabels(nlabels) 1317 1318 if len(pos) == 0 or utils.is_sequence(pos[0]): 1319 if len(pos) == 0: 1320 pos = ((0.87, 0.05), (0.97, 0.5)) 1321 if horizontal: 1322 pos = ((0.5, 0.05), (0.97, 0.15)) 1323 sb.SetTextPositionToPrecedeScalarBar() 1324 if horizontal: 1325 if not nlabels: sb.SetNumberOfLabels(3) 1326 sb.SetOrientationToHorizontal() 1327 sb.SetTextPositionToSucceedScalarBar() 1328 sb.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport() 1329 sb.GetPosition2Coordinate().SetCoordinateSystemToNormalizedViewport() 1330 1331 s = np.array(pos[1]) - np.array(pos[0]) 1332 sb.GetPositionCoordinate().SetValue(pos[0][0], pos[0][1]) 1333 sb.GetPosition2Coordinate().SetValue(s[0], s[1]) # size !!?? 1334 1335 else: 1336 1337 if horizontal: 1338 size = (size[1], size[0]) # swap size 1339 sb.SetPosition(pos[0]-0.7, pos[1]) 1340 if not nlabels: sb.SetNumberOfLabels(3) 1341 sb.SetOrientationToHorizontal() 1342 sb.SetTextPositionToSucceedScalarBar() 1343 else: 1344 sb.SetPosition(pos[0], pos[1]) 1345 if not nlabels: sb.SetNumberOfLabels(7) 1346 sb.SetTextPositionToPrecedeScalarBar() 1347 sb.SetHeight(1) 1348 sb.SetWidth(1) 1349 if size[0] is not None: sb.SetMaximumWidthInPixels(size[0]) 1350 if size[1] is not None: sb.SetMaximumHeightInPixels(size[1]) 1351 1352 sctxt = sb.GetLabelTextProperty() 1353 sctxt.SetFontFamily(vtki.VTK_FONT_FILE) 1354 sctxt.SetFontFile(utils.get_font_path(vedo.settings.default_font)) 1355 sctxt.SetColor(c) 1356 sctxt.SetShadow(0) 1357 sctxt.SetFontSize(font_size) 1358 sb.SetAnnotationTextProperty(sctxt) 1359 sb.PickableOff() 1360 return sb 1361 1362 1363##################################################################### 1364def ScalarBar3D( 1365 obj, 1366 title="", 1367 pos=None, 1368 size=(0, 0), 1369 title_font="", 1370 title_xoffset=-1.2, 1371 title_yoffset=0.0, 1372 title_size=1.5, 1373 title_rotation=0.0, 1374 nlabels=8, 1375 label_font="", 1376 label_size=1, 1377 label_offset=0.375, 1378 label_rotation=0, 1379 label_format="", 1380 italic=0, 1381 c="k", 1382 draw_box=True, 1383 above_text=None, 1384 below_text=None, 1385 nan_text="NaN", 1386 categories=None, 1387) -> Union[Assembly, None]: 1388 """ 1389 Create a 3D scalar bar for the specified object. 1390 1391 Input `obj` input can be: 1392 1393 - a list of numbers, 1394 - a list of two numbers in the form (min, max), 1395 - a Mesh already containing a set of scalars associated to vertices or cells, 1396 - if None the last object in the list of actors will be used. 1397 1398 Arguments: 1399 size : (list) 1400 (thickness, length) of scalarbar 1401 title : (str) 1402 scalar bar title 1403 title_xoffset : (float) 1404 horizontal space btw title and color scalarbar 1405 title_yoffset : (float) 1406 vertical space offset 1407 title_size : (float) 1408 size of title wrt numeric labels 1409 title_rotation : (float) 1410 title rotation in degrees 1411 nlabels : (int) 1412 number of numeric labels 1413 label_font : (str) 1414 font type for labels 1415 label_size : (float) 1416 label scale factor 1417 label_offset : (float) 1418 space btw numeric labels and scale 1419 label_rotation : (float) 1420 label rotation in degrees 1421 draw_box : (bool) 1422 draw a box around the colorbar 1423 categories : (list) 1424 make a categorical scalarbar, 1425 the input list will have the format [value, color, alpha, textlabel] 1426 1427 Examples: 1428 - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py) 1429 - [plot_fxy2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy2.py) 1430 """ 1431 1432 if isinstance(obj, (Points, TetMesh, vedo.UnstructuredGrid)): 1433 lut = obj.mapper.GetLookupTable() 1434 if not lut or lut.GetTable().GetNumberOfTuples() == 0: 1435 # create the most similar to the default 1436 obj.cmap("jet_r") 1437 lut = obj.mapper.GetLookupTable() 1438 vmin, vmax = lut.GetRange() 1439 1440 elif isinstance(obj, Volume): 1441 lut = utils.ctf2lut(obj) 1442 vmin, vmax = lut.GetRange() 1443 1444 else: 1445 vedo.logger.error("in ScalarBar3D(): input must be a vedo object with bounds.") 1446 return None 1447 1448 bns = obj.bounds() 1449 sx, sy = size 1450 if sy == 0 or sy is None: 1451 sy = bns[3] - bns[2] 1452 if sx == 0 or sx is None: 1453 sx = sy / 18 1454 1455 if categories is not None: ################################ 1456 ncats = len(categories) 1457 scale = shapes.Grid([-float(sx) * label_offset, 0, 0], 1458 c=c, alpha=1, s=(sx, sy), res=(1, ncats)) 1459 cols, alphas = [], [] 1460 ticks_pos, ticks_txt = [0.0], [""] 1461 for i, cat in enumerate(categories): 1462 cl = get_color(cat[1]) 1463 cols.append(cl) 1464 if len(cat) > 2: 1465 alphas.append(cat[2]) 1466 else: 1467 alphas.append(1) 1468 if len(cat) > 3: 1469 ticks_txt.append(cat[3]) 1470 else: 1471 ticks_txt.append("") 1472 ticks_pos.append((i + 0.5) / ncats) 1473 ticks_pos.append(1.0) 1474 ticks_txt.append("") 1475 rgba = np.c_[np.array(cols) * 255, np.array(alphas) * 255] 1476 scale.cellcolors = rgba 1477 1478 else: ######################################################## 1479 1480 # build the color scale part 1481 scale = shapes.Grid( 1482 [-float(sx) * label_offset, 0, 0], 1483 c=c, 1484 s=(sx, sy), 1485 res=(1, lut.GetTable().GetNumberOfTuples()), 1486 ) 1487 cscals = np.linspace(vmin, vmax, lut.GetTable().GetNumberOfTuples(), endpoint=True) 1488 1489 if lut.GetScale(): # logarithmic scale 1490 lut10 = vtki.vtkLookupTable() 1491 lut10.DeepCopy(lut) 1492 lut10.SetScaleToLinear() 1493 lut10.Build() 1494 scale.cmap(lut10, cscals, on="cells") 1495 tk = utils.make_ticks(vmin, vmax, nlabels, logscale=True, useformat=label_format) 1496 else: 1497 # for i in range(lut.GetTable().GetNumberOfTuples()): 1498 # print("LUT i=", i, lut.GetTableValue(i)) 1499 scale.cmap(lut, cscals, on="cells") 1500 tk = utils.make_ticks(vmin, vmax, nlabels, logscale=False, useformat=label_format) 1501 ticks_pos, ticks_txt = tk 1502 1503 scale.lw(0).wireframe(False).lighting("off") 1504 1505 scales = [scale] 1506 1507 xbns = scale.xbounds() 1508 1509 lsize = sy / 60 * label_size 1510 1511 tacts = [] 1512 for i, p in enumerate(ticks_pos): 1513 tx = ticks_txt[i] 1514 if i and tx: 1515 # build numeric text 1516 y = (p - 0.5) * sy 1517 if label_rotation: 1518 a = shapes.Text3D( 1519 tx, 1520 s=lsize, 1521 justify="center-top", 1522 c=c, 1523 italic=italic, 1524 font=label_font, 1525 ) 1526 a.rotate_z(label_rotation) 1527 a.pos(sx * label_offset, y, 0) 1528 else: 1529 a = shapes.Text3D( 1530 tx, 1531 pos=[sx * label_offset, y, 0], 1532 s=lsize, 1533 justify="center-left", 1534 c=c, 1535 italic=italic, 1536 font=label_font, 1537 ) 1538 1539 tacts.append(a) 1540 1541 # build ticks 1542 tic = shapes.Line([xbns[1], y, 0], [xbns[1] + sx * label_offset / 4, y, 0], lw=2, c=c) 1543 tacts.append(tic) 1544 1545 # build title 1546 if title: 1547 t = shapes.Text3D( 1548 title, 1549 pos=(0, 0, 0), 1550 s=sy / 50 * title_size, 1551 c=c, 1552 justify="centered-bottom", 1553 italic=italic, 1554 font=title_font, 1555 ) 1556 t.rotate_z(90 + title_rotation) 1557 t.pos(sx * title_xoffset, title_yoffset, 0) 1558 tacts.append(t) 1559 1560 if pos is None: 1561 tsize = 0 1562 if title: 1563 bbt = t.bounds() 1564 tsize = bbt[1] - bbt[0] 1565 pos = (bns[1] + tsize + sx * 1.5, (bns[2] + bns[3]) / 2, bns[4]) 1566 1567 # build below scale 1568 if lut.GetUseBelowRangeColor(): 1569 r, g, b, alfa = lut.GetBelowRangeColor() 1570 sx = float(sx) 1571 sy = float(sy) 1572 brect = shapes.Rectangle( 1573 [-sx * label_offset - sx / 2, -sy / 2 - sx - sx * 0.1, 0], 1574 [-sx * label_offset + sx / 2, -sy / 2 - sx * 0.1, 0], 1575 c=(r, g, b), 1576 alpha=alfa, 1577 ) 1578 brect.lw(1).lc(c).lighting("off") 1579 scales += [brect] 1580 if below_text is None: 1581 below_text = " <" + str(vmin) 1582 if below_text: 1583 if label_rotation: 1584 btx = shapes.Text3D( 1585 below_text, 1586 pos=(0, 0, 0), 1587 s=lsize, 1588 c=c, 1589 justify="center-top", 1590 italic=italic, 1591 font=label_font, 1592 ) 1593 btx.rotate_z(label_rotation) 1594 else: 1595 btx = shapes.Text3D( 1596 below_text, 1597 pos=(0, 0, 0), 1598 s=lsize, 1599 c=c, 1600 justify="center-left", 1601 italic=italic, 1602 font=label_font, 1603 ) 1604 1605 btx.pos(sx * label_offset, -sy / 2 - sx * 0.66, 0) 1606 tacts.append(btx) 1607 1608 # build above scale 1609 if lut.GetUseAboveRangeColor(): 1610 r, g, b, alfa = lut.GetAboveRangeColor() 1611 arect = shapes.Rectangle( 1612 [-sx * label_offset - sx / 2, sy / 2 + sx * 0.1, 0], 1613 [-sx * label_offset + sx / 2, sy / 2 + sx + sx * 0.1, 0], 1614 c=(r, g, b), 1615 alpha=alfa, 1616 ) 1617 arect.lw(1).lc(c).lighting("off") 1618 scales += [arect] 1619 if above_text is None: 1620 above_text = " >" + str(vmax) 1621 if above_text: 1622 if label_rotation: 1623 atx = shapes.Text3D( 1624 above_text, 1625 pos=(0, 0, 0), 1626 s=lsize, 1627 c=c, 1628 justify="center-top", 1629 italic=italic, 1630 font=label_font, 1631 ) 1632 atx.rotate_z(label_rotation) 1633 else: 1634 atx = shapes.Text3D( 1635 above_text, 1636 pos=(0, 0, 0), 1637 s=lsize, 1638 c=c, 1639 justify="center-left", 1640 italic=italic, 1641 font=label_font, 1642 ) 1643 1644 atx.pos(sx * label_offset, sy / 2 + sx * 0.66, 0) 1645 tacts.append(atx) 1646 1647 # build NaN scale 1648 if lut.GetNanColor() != (0.5, 0.0, 0.0, 1.0): 1649 nanshift = sx * 0.1 1650 if brect: 1651 nanshift += sx 1652 r, g, b, alfa = lut.GetNanColor() 1653 nanrect = shapes.Rectangle( 1654 [-sx * label_offset - sx / 2, -sy / 2 - sx - sx * 0.1 - nanshift, 0], 1655 [-sx * label_offset + sx / 2, -sy / 2 - sx * 0.1 - nanshift, 0], 1656 c=(r, g, b), 1657 alpha=alfa, 1658 ) 1659 nanrect.lw(1).lc(c).lighting("off") 1660 scales += [nanrect] 1661 if label_rotation: 1662 nantx = shapes.Text3D( 1663 nan_text, 1664 pos=(0, 0, 0), 1665 s=lsize, 1666 c=c, 1667 justify="center-left", 1668 italic=italic, 1669 font=label_font, 1670 ) 1671 nantx.rotate_z(label_rotation) 1672 else: 1673 nantx = shapes.Text3D( 1674 nan_text, 1675 pos=(0, 0, 0), 1676 s=lsize, 1677 c=c, 1678 justify="center-left", 1679 italic=italic, 1680 font=label_font, 1681 ) 1682 nantx.pos(sx * label_offset, -sy / 2 - sx * 0.66 - nanshift, 0) 1683 tacts.append(nantx) 1684 1685 if draw_box: 1686 tacts.append(scale.box().lw(1).c(c)) 1687 1688 for m in tacts + scales: 1689 m.shift(pos) 1690 m.actor.PickableOff() 1691 m.properties.LightingOff() 1692 1693 asse = Assembly(scales + tacts) 1694 1695 # asse.transform = LinearTransform().shift(pos) 1696 1697 bb = asse.GetBounds() 1698 # print("ScalarBar3D pos",pos, bb) 1699 # asse.SetOrigin(pos) 1700 1701 asse.SetOrigin(bb[0], bb[2], bb[4]) 1702 # asse.SetOrigin(bb[0],0,0) #in pyplot line 1312 1703 1704 asse.PickableOff() 1705 asse.UseBoundsOff() 1706 asse.name = "ScalarBar3D" 1707 return asse 1708 1709 1710##################################################################### 1711class Slider2D(SliderWidget): 1712 """ 1713 Add a slider which can call an external custom function. 1714 """ 1715 1716 def __init__( 1717 self, 1718 sliderfunc, 1719 xmin, 1720 xmax, 1721 value=None, 1722 pos=4, 1723 title="", 1724 font="Calco", 1725 title_size=1, 1726 c="k", 1727 alpha=1, 1728 show_value=True, 1729 delayed=False, 1730 **options, 1731 ): 1732 """ 1733 Add a slider which can call an external custom function. 1734 Set any value as float to increase the number of significant digits above the slider. 1735 1736 Use `play()` to start an animation between the current slider value and the last value. 1737 1738 Arguments: 1739 sliderfunc : (function) 1740 external function to be called by the widget 1741 xmin : (float) 1742 lower value of the slider 1743 xmax : (float) 1744 upper value 1745 value : (float) 1746 current value 1747 pos : (list, str) 1748 position corner number: horizontal [1-5] or vertical [11-15] 1749 it can also be specified by corners coordinates [(x1,y1), (x2,y2)] 1750 and also by a string descriptor (eg. "bottom-left") 1751 title : (str) 1752 title text 1753 font : (str) 1754 title font face. Check [available fonts here](https://vedo.embl.es/fonts). 1755 title_size : (float) 1756 title text scale [1.0] 1757 show_value : (bool) 1758 if True current value is shown 1759 delayed : (bool) 1760 if True the callback is delayed until when the mouse button is released 1761 alpha : (float) 1762 opacity of the scalar bar texts 1763 slider_length : (float) 1764 slider length 1765 slider_width : (float) 1766 slider width 1767 end_cap_length : (float) 1768 length of the end cap 1769 end_cap_width : (float) 1770 width of the end cap 1771 tube_width : (float) 1772 width of the tube 1773 title_height : (float) 1774 height of the title 1775 tformat : (str) 1776 format of the title 1777 1778 Examples: 1779 - [sliders1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders1.py) 1780 - [sliders2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders2.py) 1781 1782 ![](https://user-images.githubusercontent.com/32848391/50738848-be033480-11d8-11e9-9b1a-c13105423a79.jpg) 1783 """ 1784 slider_length = options.pop("slider_length", 0.015) 1785 slider_width = options.pop("slider_width", 0.025) 1786 end_cap_length= options.pop("end_cap_length", 0.0015) 1787 end_cap_width = options.pop("end_cap_width", 0.0125) 1788 tube_width = options.pop("tube_width", 0.0075) 1789 title_height = options.pop("title_height", 0.025) 1790 tformat = options.pop("tformat", None) 1791 1792 if options: 1793 vedo.logger.warning(f"in Slider2D unknown option(s): {options}") 1794 1795 c = get_color(c) 1796 1797 if value is None or value < xmin: 1798 value = xmin 1799 1800 slider_rep = vtki.new("SliderRepresentation2D") 1801 slider_rep.SetMinimumValue(xmin) 1802 slider_rep.SetMaximumValue(xmax) 1803 slider_rep.SetValue(value) 1804 slider_rep.SetSliderLength(slider_length) 1805 slider_rep.SetSliderWidth(slider_width) 1806 slider_rep.SetEndCapLength(end_cap_length) 1807 slider_rep.SetEndCapWidth(end_cap_width) 1808 slider_rep.SetTubeWidth(tube_width) 1809 slider_rep.GetPoint1Coordinate().SetCoordinateSystemToNormalizedDisplay() 1810 slider_rep.GetPoint2Coordinate().SetCoordinateSystemToNormalizedDisplay() 1811 1812 if isinstance(pos, str): 1813 if "top" in pos: 1814 if "left" in pos: 1815 if "vert" in pos: 1816 pos = 11 1817 else: 1818 pos = 1 1819 elif "right" in pos: 1820 if "vert" in pos: 1821 pos = 12 1822 else: 1823 pos = 2 1824 elif "bott" in pos: 1825 if "left" in pos: 1826 if "vert" in pos: 1827 pos = 13 1828 else: 1829 pos = 3 1830 elif "right" in pos: 1831 if "vert" in pos: 1832 if "span" in pos: 1833 pos = 15 1834 else: 1835 pos = 14 1836 else: 1837 pos = 4 1838 elif "span" in pos: 1839 pos = 5 1840 1841 if utils.is_sequence(pos): 1842 slider_rep.GetPoint1Coordinate().SetValue(pos[0][0], pos[0][1]) 1843 slider_rep.GetPoint2Coordinate().SetValue(pos[1][0], pos[1][1]) 1844 elif pos == 1: # top-left horizontal 1845 slider_rep.GetPoint1Coordinate().SetValue(0.04, 0.93) 1846 slider_rep.GetPoint2Coordinate().SetValue(0.45, 0.93) 1847 elif pos == 2: 1848 slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.93) 1849 slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.93) 1850 elif pos == 3: 1851 slider_rep.GetPoint1Coordinate().SetValue(0.05, 0.06) 1852 slider_rep.GetPoint2Coordinate().SetValue(0.45, 0.06) 1853 elif pos == 4: # bottom-right 1854 slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.06) 1855 slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06) 1856 elif pos == 5: # bottom span horizontal 1857 slider_rep.GetPoint1Coordinate().SetValue(0.04, 0.06) 1858 slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06) 1859 elif pos == 11: # top-left vertical 1860 slider_rep.GetPoint1Coordinate().SetValue(0.065, 0.54) 1861 slider_rep.GetPoint2Coordinate().SetValue(0.065, 0.9) 1862 elif pos == 12: 1863 slider_rep.GetPoint1Coordinate().SetValue(0.94, 0.54) 1864 slider_rep.GetPoint2Coordinate().SetValue(0.94, 0.9) 1865 elif pos == 13: 1866 slider_rep.GetPoint1Coordinate().SetValue(0.065, 0.1) 1867 slider_rep.GetPoint2Coordinate().SetValue(0.065, 0.54) 1868 elif pos == 14: # bottom-right vertical 1869 slider_rep.GetPoint1Coordinate().SetValue(0.94, 0.1) 1870 slider_rep.GetPoint2Coordinate().SetValue(0.94, 0.54) 1871 elif pos == 15: # right margin vertical 1872 slider_rep.GetPoint1Coordinate().SetValue(0.95, 0.1) 1873 slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.9) 1874 else: # bottom-right 1875 slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.06) 1876 slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06) 1877 1878 if show_value: 1879 if tformat is None: 1880 if isinstance(xmin, int) and isinstance(xmax, int) and isinstance(value, int): 1881 tformat = "%0.0f" 1882 else: 1883 tformat = "%0.2f" 1884 1885 slider_rep.SetLabelFormat(tformat) # default is '%0.3g' 1886 slider_rep.GetLabelProperty().SetShadow(0) 1887 slider_rep.GetLabelProperty().SetBold(0) 1888 slider_rep.GetLabelProperty().SetOpacity(alpha) 1889 slider_rep.GetLabelProperty().SetColor(c) 1890 if isinstance(pos, int) and pos > 10: 1891 slider_rep.GetLabelProperty().SetOrientation(90) 1892 else: 1893 slider_rep.ShowSliderLabelOff() 1894 slider_rep.GetTubeProperty().SetColor(c) 1895 slider_rep.GetTubeProperty().SetOpacity(0.75) 1896 slider_rep.GetSliderProperty().SetColor(c) 1897 slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c))) 1898 slider_rep.GetCapProperty().SetColor(c) 1899 1900 slider_rep.SetTitleHeight(title_height * title_size) 1901 slider_rep.GetTitleProperty().SetShadow(0) 1902 slider_rep.GetTitleProperty().SetColor(c) 1903 slider_rep.GetTitleProperty().SetOpacity(alpha) 1904 slider_rep.GetTitleProperty().SetBold(0) 1905 if font.lower() == "courier": 1906 slider_rep.GetTitleProperty().SetFontFamilyToCourier() 1907 elif font.lower() == "times": 1908 slider_rep.GetTitleProperty().SetFontFamilyToTimes() 1909 elif font.lower() == "arial": 1910 slider_rep.GetTitleProperty().SetFontFamilyToArial() 1911 else: 1912 if font == "": 1913 font = utils.get_font_path(settings.default_font) 1914 else: 1915 font = utils.get_font_path(font) 1916 slider_rep.GetTitleProperty().SetFontFamily(vtki.VTK_FONT_FILE) 1917 slider_rep.GetLabelProperty().SetFontFamily(vtki.VTK_FONT_FILE) 1918 slider_rep.GetTitleProperty().SetFontFile(font) 1919 slider_rep.GetLabelProperty().SetFontFile(font) 1920 1921 if title: 1922 slider_rep.SetTitleText(title) 1923 if not utils.is_sequence(pos): 1924 if isinstance(pos, int) and pos > 10: 1925 slider_rep.GetTitleProperty().SetOrientation(90) 1926 else: 1927 if abs(pos[0][0] - pos[1][0]) < 0.1: 1928 slider_rep.GetTitleProperty().SetOrientation(90) 1929 1930 super().__init__() 1931 1932 self.SetAnimationModeToJump() 1933 self.SetRepresentation(slider_rep) 1934 if delayed: 1935 self.AddObserver("EndInteractionEvent", sliderfunc) 1936 else: 1937 self.AddObserver("InteractionEvent", sliderfunc) 1938 1939 1940##################################################################### 1941class Slider3D(SliderWidget): 1942 """ 1943 Add a 3D slider which can call an external custom function. 1944 """ 1945 1946 def __init__( 1947 self, 1948 sliderfunc, 1949 pos1, 1950 pos2, 1951 xmin, 1952 xmax, 1953 value=None, 1954 s=0.03, 1955 t=1, 1956 title="", 1957 rotation=0, 1958 c=None, 1959 show_value=True, 1960 ): 1961 """ 1962 Add a 3D slider which can call an external custom function. 1963 1964 Arguments: 1965 sliderfunc : (function) 1966 external function to be called by the widget 1967 pos1 : (list) 1968 first position 3D coordinates 1969 pos2 : (list) 1970 second position 3D coordinates 1971 xmin : (float) 1972 lower value 1973 xmax : (float) 1974 upper value 1975 value : (float) 1976 initial value 1977 s : (float) 1978 label scaling factor 1979 t : (float) 1980 tube scaling factor 1981 title : (str) 1982 title text 1983 c : (color) 1984 slider color 1985 rotation : (float) 1986 title rotation around slider axis 1987 show_value : (bool) 1988 if True current value is shown on top of the slider 1989 1990 Examples: 1991 - [sliders3d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders3d.py) 1992 """ 1993 c = get_color(c) 1994 1995 if value is None or value < xmin: 1996 value = xmin 1997 1998 slider_rep = vtki.new("SliderRepresentation3D") 1999 slider_rep.SetMinimumValue(xmin) 2000 slider_rep.SetMaximumValue(xmax) 2001 slider_rep.SetValue(value) 2002 2003 slider_rep.GetPoint1Coordinate().SetCoordinateSystemToWorld() 2004 slider_rep.GetPoint2Coordinate().SetCoordinateSystemToWorld() 2005 slider_rep.GetPoint1Coordinate().SetValue(pos2) 2006 slider_rep.GetPoint2Coordinate().SetValue(pos1) 2007 2008 # slider_rep.SetPoint1InWorldCoordinates(pos2[0], pos2[1], pos2[2]) 2009 # slider_rep.SetPoint2InWorldCoordinates(pos1[0], pos1[1], pos1[2]) 2010 2011 slider_rep.SetSliderWidth(0.03 * t) 2012 slider_rep.SetTubeWidth(0.01 * t) 2013 slider_rep.SetSliderLength(0.04 * t) 2014 slider_rep.SetSliderShapeToCylinder() 2015 slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c))) 2016 slider_rep.GetSliderProperty().SetColor(np.array(c) / 1.5) 2017 slider_rep.GetCapProperty().SetOpacity(0) 2018 slider_rep.SetRotation(rotation) 2019 2020 if not show_value: 2021 slider_rep.ShowSliderLabelOff() 2022 2023 slider_rep.SetTitleText(title) 2024 slider_rep.SetTitleHeight(s * t) 2025 slider_rep.SetLabelHeight(s * t * 0.85) 2026 2027 slider_rep.GetTubeProperty().SetColor(c) 2028 2029 super().__init__() 2030 2031 self.SetRepresentation(slider_rep) 2032 self.SetAnimationModeToJump() 2033 self.AddObserver("InteractionEvent", sliderfunc) 2034 2035 2036class BaseCutter: 2037 """ 2038 Base class for Cutter widgets. 2039 """ 2040 2041 def __init__(self): 2042 self._implicit_func = None 2043 self.widget = None 2044 self.clipper = None 2045 self.cutter = None 2046 self.mesh = None 2047 self.remnant = None 2048 self._alpha = 0.5 2049 self._keypress_id = None 2050 2051 def invert(self) -> Self: 2052 """Invert selection.""" 2053 self.clipper.SetInsideOut(not self.clipper.GetInsideOut()) 2054 return self 2055 2056 def bounds(self, value=None) -> Union[Self, np.ndarray]: 2057 """Set or get the bounding box.""" 2058 if value is None: 2059 return self.cutter.GetBounds() 2060 else: 2061 self._implicit_func.SetBounds(value) 2062 return self 2063 2064 def on(self) -> Self: 2065 """Switch the widget on or off.""" 2066 self.widget.On() 2067 return self 2068 2069 def off(self) -> Self: 2070 """Switch the widget on or off.""" 2071 self.widget.Off() 2072 return self 2073 2074 def add_to(self, plt) -> Self: 2075 """Assign the widget to the provided `Plotter` instance.""" 2076 self.widget.SetInteractor(plt.interactor) 2077 self.widget.SetCurrentRenderer(plt.renderer) 2078 if self.widget not in plt.widgets: 2079 plt.widgets.append(self.widget) 2080 2081 cpoly = self.clipper.GetOutput() 2082 self.mesh._update(cpoly) 2083 2084 out = self.clipper.GetClippedOutputPort() 2085 if self._alpha: 2086 self.remnant.mapper.SetInputConnection(out) 2087 self.remnant.alpha(self._alpha).color((0.5, 0.5, 0.5)) 2088 self.remnant.lighting("off").wireframe() 2089 plt.add(self.mesh, self.remnant) 2090 else: 2091 plt.add(self.mesh) 2092 2093 self._keypress_id = plt.interactor.AddObserver( 2094 "KeyPressEvent", self._keypress 2095 ) 2096 if plt.interactor and plt.interactor.GetInitialized(): 2097 self.widget.On() 2098 self._select_polygons(self.widget, "InteractionEvent") 2099 plt.interactor.Render() 2100 return self 2101 2102 def remove_from(self, plt) -> Self: 2103 """Remove the widget to the provided `Plotter` instance.""" 2104 self.widget.Off() 2105 self.widget.RemoveAllObservers() ### NOT SURE 2106 plt.remove(self.remnant) 2107 if self.widget in plt.widgets: 2108 plt.widgets.remove(self.widget) 2109 if self._keypress_id: 2110 plt.interactor.RemoveObserver(self._keypress_id) 2111 return self 2112 2113 def add_observer(self, event, func, priority=1) -> int: 2114 """Add an observer to the widget.""" 2115 event = utils.get_vtk_name_event(event) 2116 cid = self.widget.AddObserver(event, func, priority) 2117 return cid 2118 2119 2120class PlaneCutter(vtki.vtkPlaneWidget, BaseCutter): 2121 """ 2122 Create a box widget to cut away parts of a Mesh. 2123 """ 2124 2125 def __init__( 2126 self, 2127 mesh, 2128 invert=False, 2129 can_translate=True, 2130 can_scale=True, 2131 origin=(), 2132 normal=(), 2133 padding=0.05, 2134 delayed=False, 2135 c=(0.25, 0.25, 0.25), 2136 alpha=0.05, 2137 ): 2138 """ 2139 Create a box widget to cut away parts of a `Mesh`. 2140 2141 Arguments: 2142 mesh : (Mesh) 2143 the input mesh 2144 invert : (bool) 2145 invert the clipping plane 2146 can_translate : (bool) 2147 enable translation of the widget 2148 can_scale : (bool) 2149 enable scaling of the widget 2150 origin : (list) 2151 origin of the plane 2152 normal : (list) 2153 normal to the plane 2154 padding : (float) 2155 padding around the input mesh 2156 delayed : (bool) 2157 if True the callback is delayed until 2158 when the mouse button is released (useful for large meshes) 2159 c : (color) 2160 color of the box cutter widget 2161 alpha : (float) 2162 transparency of the cut-off part of the input mesh 2163 2164 Examples: 2165 - [slice_plane3.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slice_plane3.py) 2166 """ 2167 super().__init__() 2168 2169 self.mesh = mesh 2170 self.remnant = Mesh() 2171 self.remnant.name = mesh.name + "Remnant" 2172 self.remnant.pickable(False) 2173 2174 self._alpha = alpha 2175 self._keypress_id = None 2176 2177 self._implicit_func = vtki.new("Plane") 2178 2179 poly = mesh.dataset 2180 self.clipper = vtki.new("ClipPolyData") 2181 self.clipper.GenerateClipScalarsOff() 2182 self.clipper.SetInputData(poly) 2183 self.clipper.SetClipFunction(self._implicit_func) 2184 self.clipper.SetInsideOut(invert) 2185 self.clipper.GenerateClippedOutputOn() 2186 self.clipper.Update() 2187 2188 self.widget = vtki.new("ImplicitPlaneWidget") 2189 2190 # self.widget.KeyPressActivationOff() 2191 # self.widget.SetKeyPressActivationValue('i') 2192 2193 self.widget.SetOriginTranslation(can_translate) 2194 self.widget.SetOutlineTranslation(can_translate) 2195 self.widget.SetScaleEnabled(can_scale) 2196 2197 self.widget.GetOutlineProperty().SetColor(get_color(c)) 2198 self.widget.GetOutlineProperty().SetOpacity(0.25) 2199 self.widget.GetOutlineProperty().SetLineWidth(1) 2200 self.widget.GetOutlineProperty().LightingOff() 2201 2202 self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3")) 2203 2204 self.widget.SetTubing(0) 2205 self.widget.SetDrawPlane(bool(alpha)) 2206 self.widget.GetPlaneProperty().LightingOff() 2207 self.widget.GetPlaneProperty().SetOpacity(alpha) 2208 self.widget.GetSelectedPlaneProperty().SetColor(get_color("red5")) 2209 self.widget.GetSelectedPlaneProperty().LightingOff() 2210 2211 self.widget.SetPlaceFactor(1.0 + padding) 2212 self.widget.SetInputData(poly) 2213 self.widget.PlaceWidget() 2214 if delayed: 2215 self.widget.AddObserver("EndInteractionEvent", self._select_polygons) 2216 else: 2217 self.widget.AddObserver("InteractionEvent", self._select_polygons) 2218 2219 if len(origin) == 3: 2220 self.widget.SetOrigin(origin) 2221 else: 2222 self.widget.SetOrigin(mesh.center_of_mass()) 2223 2224 if len(normal) == 3: 2225 self.widget.SetNormal(normal) 2226 else: 2227 self.widget.SetNormal((1, 0, 0)) 2228 2229 @property 2230 def origin(self): 2231 """Get the origin of the plane.""" 2232 return np.array(self.widget.GetOrigin()) 2233 2234 @origin.setter 2235 def origin(self, value): 2236 """Set the origin of the plane.""" 2237 self.widget.SetOrigin(value) 2238 2239 @property 2240 def normal(self): 2241 """Get the normal of the plane.""" 2242 return np.array(self.widget.GetNormal()) 2243 2244 @normal.setter 2245 def normal(self, value): 2246 """Set the normal of the plane.""" 2247 self.widget.SetNormal(value) 2248 2249 def _select_polygons(self, vobj, event) -> None: 2250 vobj.GetPlane(self._implicit_func) 2251 2252 def _keypress(self, vobj, event): 2253 if vobj.GetKeySym() == "r": # reset planes 2254 self.widget.GetPlane(self._implicit_func) 2255 self.widget.PlaceWidget() 2256 self.widget.GetInteractor().Render() 2257 elif vobj.GetKeySym() == "u": # invert cut 2258 self.invert() 2259 self.widget.GetInteractor().Render() 2260 elif vobj.GetKeySym() == "x": # set normal along x 2261 self.widget.SetNormal((1, 0, 0)) 2262 self.widget.GetPlane(self._implicit_func) 2263 self.widget.PlaceWidget() 2264 self.widget.GetInteractor().Render() 2265 elif vobj.GetKeySym() == "y": # set normal along y 2266 self.widget.SetNormal((0, 1, 0)) 2267 self.widget.GetPlane(self._implicit_func) 2268 self.widget.PlaceWidget() 2269 self.widget.GetInteractor().Render() 2270 elif vobj.GetKeySym() == "z": # set normal along z 2271 self.widget.SetNormal((0, 0, 1)) 2272 self.widget.GetPlane(self._implicit_func) 2273 self.widget.PlaceWidget() 2274 self.widget.GetInteractor().Render() 2275 elif vobj.GetKeySym() == "s": # Ctrl+s to save mesh 2276 if self.widget.GetInteractor(): 2277 if self.widget.GetInteractor().GetControlKey(): 2278 self.mesh.write("vedo_clipped.vtk") 2279 printc(":save: saved mesh to vedo_clipped.vtk") 2280 2281 2282class BoxCutter(vtki.vtkBoxWidget, BaseCutter): 2283 """ 2284 Create a box widget to cut away parts of a Mesh. 2285 """ 2286 2287 def __init__( 2288 self, 2289 mesh, 2290 invert=False, 2291 can_rotate=True, 2292 can_translate=True, 2293 can_scale=True, 2294 initial_bounds=(), 2295 padding=0.025, 2296 delayed=False, 2297 c=(0.25, 0.25, 0.25), 2298 alpha=0.05, 2299 ): 2300 """ 2301 Create a box widget to cut away parts of a Mesh. 2302 2303 Arguments: 2304 mesh : (Mesh) 2305 the input mesh 2306 invert : (bool) 2307 invert the clipping plane 2308 can_rotate : (bool) 2309 enable rotation of the widget 2310 can_translate : (bool) 2311 enable translation of the widget 2312 can_scale : (bool) 2313 enable scaling of the widget 2314 initial_bounds : (list) 2315 initial bounds of the box widget 2316 padding : (float) 2317 padding space around the input mesh 2318 delayed : (bool) 2319 if True the callback is delayed until 2320 when the mouse button is released (useful for large meshes) 2321 c : (color) 2322 color of the box cutter widget 2323 alpha : (float) 2324 transparency of the cut-off part of the input mesh 2325 """ 2326 super().__init__() 2327 2328 self.mesh = mesh 2329 self.remnant = Mesh() 2330 self.remnant.name = mesh.name + "Remnant" 2331 self.remnant.pickable(False) 2332 2333 self._alpha = alpha 2334 self._keypress_id = None 2335 self._init_bounds = initial_bounds 2336 if len(self._init_bounds) == 0: 2337 self._init_bounds = mesh.bounds() 2338 else: 2339 self._init_bounds = initial_bounds 2340 2341 self._implicit_func = vtki.new("Planes") 2342 self._implicit_func.SetBounds(self._init_bounds) 2343 2344 poly = mesh.dataset 2345 self.clipper = vtki.new("ClipPolyData") 2346 self.clipper.GenerateClipScalarsOff() 2347 self.clipper.SetInputData(poly) 2348 self.clipper.SetClipFunction(self._implicit_func) 2349 self.clipper.SetInsideOut(not invert) 2350 self.clipper.GenerateClippedOutputOn() 2351 self.clipper.Update() 2352 2353 self.widget = vtki.vtkBoxWidget() 2354 2355 self.widget.SetRotationEnabled(can_rotate) 2356 self.widget.SetTranslationEnabled(can_translate) 2357 self.widget.SetScalingEnabled(can_scale) 2358 2359 self.widget.OutlineCursorWiresOn() 2360 self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3")) 2361 self.widget.GetSelectedHandleProperty().SetColor(get_color("red5")) 2362 2363 self.widget.GetOutlineProperty().SetColor(c) 2364 self.widget.GetOutlineProperty().SetOpacity(1) 2365 self.widget.GetOutlineProperty().SetLineWidth(1) 2366 self.widget.GetOutlineProperty().LightingOff() 2367 2368 self.widget.GetSelectedFaceProperty().LightingOff() 2369 self.widget.GetSelectedFaceProperty().SetOpacity(0.1) 2370 2371 self.widget.SetPlaceFactor(1.0 + padding) 2372 self.widget.SetInputData(poly) 2373 self.widget.PlaceWidget() 2374 if delayed: 2375 self.widget.AddObserver("EndInteractionEvent", self._select_polygons) 2376 else: 2377 self.widget.AddObserver("InteractionEvent", self._select_polygons) 2378 2379 def _select_polygons(self, vobj, event): 2380 vobj.GetPlanes(self._implicit_func) 2381 2382 def _keypress(self, vobj, event): 2383 if vobj.GetKeySym() == "r": # reset planes 2384 self._implicit_func.SetBounds(self._init_bounds) 2385 self.widget.GetPlanes(self._implicit_func) 2386 self.widget.PlaceWidget() 2387 self.widget.GetInteractor().Render() 2388 elif vobj.GetKeySym() == "u": 2389 self.invert() 2390 self.widget.GetInteractor().Render() 2391 elif vobj.GetKeySym() == "s": # Ctrl+s to save mesh 2392 if self.widget.GetInteractor(): 2393 if self.widget.GetInteractor().GetControlKey(): 2394 self.mesh.write("vedo_clipped.vtk") 2395 printc(":save: saved mesh to vedo_clipped.vtk") 2396 2397 2398class SphereCutter(vtki.vtkSphereWidget, BaseCutter): 2399 """ 2400 Create a box widget to cut away parts of a Mesh. 2401 """ 2402 2403 def __init__( 2404 self, 2405 mesh, 2406 invert=False, 2407 can_translate=True, 2408 can_scale=True, 2409 origin=(), 2410 radius=0, 2411 res=60, 2412 delayed=False, 2413 c="white", 2414 alpha=0.05, 2415 ): 2416 """ 2417 Create a box widget to cut away parts of a Mesh. 2418 2419 Arguments: 2420 mesh : Mesh 2421 the input mesh 2422 invert : bool 2423 invert the clipping 2424 can_translate : bool 2425 enable translation of the widget 2426 can_scale : bool 2427 enable scaling of the widget 2428 origin : list 2429 initial position of the sphere widget 2430 radius : float 2431 initial radius of the sphere widget 2432 res : int 2433 resolution of the sphere widget 2434 delayed : bool 2435 if True the cutting callback is delayed until 2436 when the mouse button is released (useful for large meshes) 2437 c : color 2438 color of the box cutter widget 2439 alpha : float 2440 transparency of the cut-off part of the input mesh 2441 """ 2442 super().__init__() 2443 2444 self.mesh = mesh 2445 self.remnant = Mesh() 2446 self.remnant.name = mesh.name + "Remnant" 2447 self.remnant.pickable(False) 2448 2449 self._alpha = alpha 2450 self._keypress_id = None 2451 2452 self._implicit_func = vtki.new("Sphere") 2453 2454 if len(origin) == 3: 2455 self._implicit_func.SetCenter(origin) 2456 else: 2457 origin = mesh.center_of_mass() 2458 self._implicit_func.SetCenter(origin) 2459 2460 if radius > 0: 2461 self._implicit_func.SetRadius(radius) 2462 else: 2463 radius = mesh.average_size() * 2 2464 self._implicit_func.SetRadius(radius) 2465 2466 poly = mesh.dataset 2467 self.clipper = vtki.new("ClipPolyData") 2468 self.clipper.GenerateClipScalarsOff() 2469 self.clipper.SetInputData(poly) 2470 self.clipper.SetClipFunction(self._implicit_func) 2471 self.clipper.SetInsideOut(not invert) 2472 self.clipper.GenerateClippedOutputOn() 2473 self.clipper.Update() 2474 2475 self.widget = vtki.vtkSphereWidget() 2476 2477 self.widget.SetThetaResolution(res * 2) 2478 self.widget.SetPhiResolution(res) 2479 self.widget.SetRadius(radius) 2480 self.widget.SetCenter(origin) 2481 self.widget.SetRepresentation(2) 2482 self.widget.HandleVisibilityOff() 2483 2484 self.widget.SetTranslation(can_translate) 2485 self.widget.SetScale(can_scale) 2486 2487 self.widget.HandleVisibilityOff() 2488 self.widget.GetSphereProperty().SetColor(get_color(c)) 2489 self.widget.GetSphereProperty().SetOpacity(0.2) 2490 self.widget.GetSelectedSphereProperty().SetColor(get_color("red5")) 2491 self.widget.GetSelectedSphereProperty().SetOpacity(0.2) 2492 2493 self.widget.SetPlaceFactor(1.0) 2494 self.widget.SetInputData(poly) 2495 self.widget.PlaceWidget() 2496 if delayed: 2497 self.widget.AddObserver("EndInteractionEvent", self._select_polygons) 2498 else: 2499 self.widget.AddObserver("InteractionEvent", self._select_polygons) 2500 2501 def _select_polygons(self, vobj, event): 2502 vobj.GetSphere(self._implicit_func) 2503 2504 def _keypress(self, vobj, event): 2505 if vobj.GetKeySym() == "r": # reset planes 2506 self._implicit_func.SetBounds(self._init_bounds) 2507 self.widget.GetPlanes(self._implicit_func) 2508 self.widget.PlaceWidget() 2509 self.widget.GetInteractor().Render() 2510 elif vobj.GetKeySym() == "u": 2511 self.invert() 2512 self.widget.GetInteractor().Render() 2513 elif vobj.GetKeySym() == "s": # Ctrl+s to save mesh 2514 if self.widget.GetInteractor(): 2515 if self.widget.GetInteractor().GetControlKey(): 2516 self.mesh.write("vedo_clipped.vtk") 2517 printc(":save: saved mesh to vedo_clipped.vtk") 2518 2519 @property 2520 def center(self): 2521 """Get the center of the sphere.""" 2522 return np.array(self.widget.GetCenter()) 2523 2524 @center.setter 2525 def center(self, value): 2526 """Set the center of the sphere.""" 2527 self.widget.SetCenter(value) 2528 2529 @property 2530 def radius(self): 2531 """Get the radius of the sphere.""" 2532 return self.widget.GetRadius() 2533 2534 @radius.setter 2535 def radius(self, value): 2536 """Set the radius of the sphere.""" 2537 self.widget.SetRadius(value) 2538 2539 2540##################################################################### 2541class RendererFrame(vtki.vtkActor2D): 2542 """ 2543 Add a line around the renderer subwindow. 2544 """ 2545 2546 def __init__(self, c="k", alpha=None, lw=None, padding=None): 2547 """ 2548 Add a line around the renderer subwindow. 2549 2550 Arguments: 2551 c : (color) 2552 color of the line. 2553 alpha : (float) 2554 opacity. 2555 lw : (int) 2556 line width in pixels. 2557 padding : (int) 2558 padding in pixel units. 2559 """ 2560 2561 if lw is None: 2562 lw = settings.renderer_frame_width 2563 if lw == 0: 2564 return None 2565 2566 if alpha is None: 2567 alpha = settings.renderer_frame_alpha 2568 2569 if padding is None: 2570 padding = settings.renderer_frame_padding 2571 2572 c = get_color(c) 2573 2574 ppoints = vtki.vtkPoints() # Generate the polyline 2575 xy = 1 - padding 2576 psqr = [ 2577 [padding, padding], 2578 [padding, xy], 2579 [xy, xy], 2580 [xy, padding], 2581 [padding, padding], 2582 ] 2583 for i, pt in enumerate(psqr): 2584 ppoints.InsertPoint(i, pt[0], pt[1], 0) 2585 lines = vtki.vtkCellArray() 2586 lines.InsertNextCell(len(psqr)) 2587 for i in range(len(psqr)): 2588 lines.InsertCellPoint(i) 2589 pd = vtki.vtkPolyData() 2590 pd.SetPoints(ppoints) 2591 pd.SetLines(lines) 2592 2593 mapper = vtki.new("PolyDataMapper2D") 2594 mapper.SetInputData(pd) 2595 cs = vtki.new("Coordinate") 2596 cs.SetCoordinateSystemToNormalizedViewport() 2597 mapper.SetTransformCoordinate(cs) 2598 2599 super().__init__() 2600 2601 self.GetPositionCoordinate().SetValue(0, 0) 2602 self.GetPosition2Coordinate().SetValue(1, 1) 2603 self.SetMapper(mapper) 2604 self.GetProperty().SetColor(c) 2605 self.GetProperty().SetOpacity(alpha) 2606 self.GetProperty().SetLineWidth(lw) 2607 2608 2609##################################################################### 2610class ProgressBarWidget(vtki.vtkActor2D): 2611 """ 2612 Add a progress bar in the rendering window. 2613 """ 2614 2615 def __init__(self, n=None, c="blue5", alpha=0.8, lw=10, autohide=True): 2616 """ 2617 Add a progress bar window. 2618 2619 Arguments: 2620 n : (int) 2621 number of iterations. 2622 If None, you need to call `update(fraction)` manually. 2623 c : (color) 2624 color of the line. 2625 alpha : (float) 2626 opacity of the line. 2627 lw : (int) 2628 line width in pixels. 2629 autohide : (bool) 2630 if True, hide the progress bar when completed. 2631 """ 2632 self.n = 0 2633 self.iterations = n 2634 self.autohide = autohide 2635 2636 ppoints = vtki.vtkPoints() # Generate the line 2637 psqr = [[0, 0, 0], [1, 0, 0]] 2638 for i, pt in enumerate(psqr): 2639 ppoints.InsertPoint(i, *pt) 2640 lines = vtki.vtkCellArray() 2641 lines.InsertNextCell(len(psqr)) 2642 for i in range(len(psqr)): 2643 lines.InsertCellPoint(i) 2644 pd = vtki.vtkPolyData() 2645 pd.SetPoints(ppoints) 2646 pd.SetLines(lines) 2647 self.dataset = pd 2648 2649 mapper = vtki.new("PolyDataMapper2D") 2650 mapper.SetInputData(pd) 2651 cs = vtki.vtkCoordinate() 2652 cs.SetCoordinateSystemToNormalizedViewport() 2653 mapper.SetTransformCoordinate(cs) 2654 2655 super().__init__() 2656 2657 self.SetMapper(mapper) 2658 self.GetProperty().SetOpacity(alpha) 2659 self.GetProperty().SetColor(get_color(c)) 2660 self.GetProperty().SetLineWidth(lw * 2) 2661 2662 def lw(self, value: int) -> Self: 2663 """Set width.""" 2664 self.GetProperty().SetLineWidth(value * 2) 2665 return self 2666 2667 def c(self, color) -> Self: 2668 """Set color.""" 2669 c = get_color(color) 2670 self.GetProperty().SetColor(c) 2671 return self 2672 2673 def alpha(self, value) -> Self: 2674 """Set opacity.""" 2675 self.GetProperty().SetOpacity(value) 2676 return self 2677 2678 def update(self, fraction=None) -> Self: 2679 """Update progress bar to fraction of the window width.""" 2680 if fraction is None: 2681 if self.iterations is None: 2682 vedo.printc("Error in ProgressBarWindow: must specify iterations", c='r') 2683 return self 2684 self.n += 1 2685 fraction = self.n / self.iterations 2686 2687 if fraction >= 1 and self.autohide: 2688 fraction = 0 2689 2690 psqr = [[0, 0, 0], [fraction, 0, 0]] 2691 vpts = utils.numpy2vtk(psqr, dtype=np.float32) 2692 self.dataset.GetPoints().SetData(vpts) 2693 return self 2694 2695 def reset(self): 2696 """Reset progress bar.""" 2697 self.n = 0 2698 self.update(0) 2699 return self 2700 2701 2702##################################################################### 2703class Icon(vtki.vtkOrientationMarkerWidget): 2704 """ 2705 Add an inset icon mesh into the renderer. 2706 """ 2707 2708 def __init__(self, mesh, pos=3, size=0.08): 2709 """ 2710 Arguments: 2711 pos : (list, int) 2712 icon position in the range [1-4] indicating one of the 4 corners, 2713 or it can be a tuple (x,y) as a fraction of the renderer size. 2714 size : (float) 2715 size of the icon space as fraction of the window size. 2716 2717 Examples: 2718 - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py) 2719 """ 2720 super().__init__() 2721 2722 try: 2723 self.SetOrientationMarker(mesh.actor) 2724 except AttributeError: 2725 self.SetOrientationMarker(mesh) 2726 2727 if utils.is_sequence(pos): 2728 self.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size) 2729 else: 2730 if pos < 2: 2731 self.SetViewport(0, 1 - 2 * size, size * 2, 1) 2732 elif pos == 2: 2733 self.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1) 2734 elif pos == 3: 2735 self.SetViewport(0, 0, size * 2, size * 2) 2736 elif pos == 4: 2737 self.SetViewport(1 - 2 * size, 0, 1, size * 2) 2738 2739 2740##################################################################### 2741def compute_visible_bounds(objs=None) -> list: 2742 """Calculate max objects bounds and sizes.""" 2743 bns = [] 2744 2745 if objs is None and vedo.plotter_instance: 2746 objs = vedo.plotter_instance.actors 2747 elif not utils.is_sequence(objs): 2748 objs = [objs] 2749 2750 actors = [ob.actor for ob in objs if hasattr(ob, "actor") and ob.actor] 2751 2752 try: 2753 # this block fails for VolumeSlice as vtkImageSlice.GetBounds() returns a pointer.. 2754 # in any case we dont need axes for that one. 2755 for a in actors: 2756 if a and a.GetUseBounds(): 2757 b = a.GetBounds() 2758 if b: 2759 bns.append(b) 2760 if bns: 2761 max_bns = np.max(bns, axis=0) 2762 min_bns = np.min(bns, axis=0) 2763 vbb = [min_bns[0], max_bns[1], min_bns[2], max_bns[3], min_bns[4], max_bns[5]] 2764 elif vedo.plotter_instance: 2765 vbb = list(vedo.plotter_instance.renderer.ComputeVisiblePropBounds()) 2766 max_bns = vbb 2767 min_bns = vbb 2768 sizes = np.array( 2769 [max_bns[1] - min_bns[0], max_bns[3] - min_bns[2], max_bns[5] - min_bns[4]] 2770 ) 2771 return [vbb, sizes, min_bns, max_bns] 2772 2773 except: 2774 return [[0, 0, 0, 0, 0, 0], [0, 0, 0], 0, 0] 2775 2776 2777##################################################################### 2778def Ruler3D( 2779 p1, 2780 p2, 2781 units_scale=1, 2782 label="", 2783 s=None, 2784 font=None, 2785 italic=0, 2786 prefix="", 2787 units="", # eg.'μm' 2788 c=(0.2, 0.1, 0.1), 2789 alpha=1, 2790 lw=1, 2791 precision=3, 2792 label_rotation=0, 2793 axis_rotation=0, 2794 tick_angle=90, 2795) -> Mesh: 2796 """ 2797 Build a 3D ruler to indicate the distance of two points p1 and p2. 2798 2799 Arguments: 2800 label : (str) 2801 alternative fixed label to be shown 2802 units_scale : (float) 2803 factor to scale units (e.g. μm to mm) 2804 s : (float) 2805 size of the label 2806 font : (str) 2807 font face. Check [available fonts here](https://vedo.embl.es/fonts). 2808 italic : (float) 2809 italicness of the font in the range [0,1] 2810 units : (str) 2811 string to be appended to the numeric value 2812 lw : (int) 2813 line width in pixel units 2814 precision : (int) 2815 nr of significant digits to be shown 2816 label_rotation : (float) 2817 initial rotation of the label around the z-axis 2818 axis_rotation : (float) 2819 initial rotation of the line around the main axis 2820 tick_angle : (float) 2821 initial rotation of the line around the main axis 2822 2823 Examples: 2824 - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py) 2825 2826 ![](https://vedo.embl.es/images/pyplot/goniometer.png) 2827 """ 2828 2829 if units_scale != 1.0 and units == "": 2830 raise ValueError( 2831 "When setting 'units_scale' to a value other than 1, " 2832 + "a 'units' arguments must be specified." 2833 ) 2834 2835 try: 2836 p1 = p1.pos() 2837 except AttributeError: 2838 pass 2839 2840 try: 2841 p2 = p2.pos() 2842 except AttributeError: 2843 pass 2844 2845 if len(p1) == 2: 2846 p1 = [p1[0], p1[1], 0.0] 2847 if len(p2) == 2: 2848 p2 = [p2[0], p2[1], 0.0] 2849 2850 p1, p2 = np.asarray(p1), np.asarray(p2) 2851 q1, q2 = [0, 0, 0], [utils.mag(p2 - p1), 0, 0] 2852 q1, q2 = np.array(q1), np.array(q2) 2853 v = q2 - q1 2854 d = utils.mag(v) * units_scale 2855 2856 pos = np.array(p1) 2857 p1 = p1 - pos 2858 p2 = p2 - pos 2859 2860 if s is None: 2861 s = d * 0.02 * (1 / units_scale) 2862 2863 if not label: 2864 label = str(d) 2865 if precision: 2866 label = utils.precision(d, precision) 2867 if prefix: 2868 label = prefix + "~" + label 2869 if units: 2870 label += "~" + units 2871 2872 lb = shapes.Text3D(label, s=s, font=font, italic=italic, justify="center") 2873 if label_rotation: 2874 lb.rotate_z(label_rotation) 2875 lb.pos((q1 + q2) / 2) 2876 2877 x0, x1 = lb.xbounds() 2878 gap = [(x1 - x0) / 2, 0, 0] 2879 pc1 = (v / 2 - gap) * 0.9 + q1 2880 pc2 = q2 - (v / 2 - gap) * 0.9 2881 2882 lc1 = shapes.Line(q1 - v / 50, pc1).lw(lw) 2883 lc2 = shapes.Line(q2 + v / 50, pc2).lw(lw) 2884 2885 zs = np.array([0, d / 50 * (1 / units_scale), 0]) 2886 ml1 = shapes.Line(-zs, zs).lw(lw) 2887 ml2 = shapes.Line(-zs, zs).lw(lw) 2888 ml1.rotate_z(tick_angle - 90).pos(q1) 2889 ml2.rotate_z(tick_angle - 90).pos(q2) 2890 2891 c1 = shapes.Circle(q1, r=d / 180 * (1 / units_scale), res=24) 2892 c2 = shapes.Circle(q2, r=d / 180 * (1 / units_scale), res=24) 2893 2894 macts = merge(lb, lc1, lc2, c1, c2, ml1, ml2) 2895 macts.c(c).alpha(alpha) 2896 macts.properties.SetLineWidth(lw) 2897 macts.properties.LightingOff() 2898 macts.actor.UseBoundsOff() 2899 macts.rotate_x(axis_rotation) 2900 macts.reorient(q2 - q1, p2 - p1) 2901 macts.pos(pos) 2902 macts.bc("tomato").pickable(False) 2903 return macts 2904 2905 2906def RulerAxes( 2907 inputobj, 2908 xtitle="", 2909 ytitle="", 2910 ztitle="", 2911 xlabel="", 2912 ylabel="", 2913 zlabel="", 2914 xpadding=0.05, 2915 ypadding=0.04, 2916 zpadding=0, 2917 font="Normografo", 2918 s=None, 2919 italic=0, 2920 units="", 2921 c=(0.2, 0, 0), 2922 alpha=1, 2923 lw=1, 2924 precision=3, 2925 label_rotation=0, 2926 xaxis_rotation=0, 2927 yaxis_rotation=0, 2928 zaxis_rotation=0, 2929 xycross=True, 2930) -> Union[Mesh, None]: 2931 """ 2932 A 3D ruler axes to indicate the sizes of the input scene or object. 2933 2934 Arguments: 2935 xtitle : (str) 2936 name of the axis or title 2937 xlabel : (str) 2938 alternative fixed label to be shown instead of the distance 2939 s : (float) 2940 size of the label 2941 font : (str) 2942 font face. Check [available fonts here](https://vedo.embl.es/fonts). 2943 italic : (float) 2944 italicness of the font in the range [0,1] 2945 units : (str) 2946 string to be appended to the numeric value 2947 lw : (int) 2948 line width in pixel units 2949 precision : (int) 2950 nr of significant digits to be shown 2951 label_rotation : (float) 2952 initial rotation of the label around the z-axis 2953 [x,y,z]axis_rotation : (float) 2954 initial rotation of the line around the main axis in degrees 2955 xycross : (bool) 2956 show two back crossing lines in the xy plane 2957 2958 Examples: 2959 - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py) 2960 """ 2961 if utils.is_sequence(inputobj): 2962 x0, x1, y0, y1, z0, z1 = inputobj 2963 else: 2964 x0, x1, y0, y1, z0, z1 = inputobj.bounds() 2965 dx, dy, dz = (y1 - y0) * xpadding, (x1 - x0) * ypadding, (y1 - y0) * zpadding 2966 d = np.sqrt((y1 - y0) ** 2 + (x1 - x0) ** 2 + (z1 - z0) ** 2) 2967 2968 if not d: 2969 return None 2970 2971 if s is None: 2972 s = d / 75 2973 2974 acts, rx, ry = [], None, None 2975 if xtitle is not None and (x1 - x0) / d > 0.1: 2976 rx = Ruler3D( 2977 [x0, y0 - dx, z0], 2978 [x1, y0 - dx, z0], 2979 s=s, 2980 font=font, 2981 precision=precision, 2982 label_rotation=label_rotation, 2983 axis_rotation=xaxis_rotation, 2984 lw=lw, 2985 italic=italic, 2986 prefix=xtitle, 2987 label=xlabel, 2988 units=units, 2989 ) 2990 acts.append(rx) 2991 2992 if ytitle is not None and (y1 - y0) / d > 0.1: 2993 ry = Ruler3D( 2994 [x1 + dy, y0, z0], 2995 [x1 + dy, y1, z0], 2996 s=s, 2997 font=font, 2998 precision=precision, 2999 label_rotation=label_rotation, 3000 axis_rotation=yaxis_rotation, 3001 lw=lw, 3002 italic=italic, 3003 prefix=ytitle, 3004 label=ylabel, 3005 units=units, 3006 ) 3007 acts.append(ry) 3008 3009 if ztitle is not None and (z1 - z0) / d > 0.1: 3010 rz = Ruler3D( 3011 [x0 - dy, y0 + dz, z0], 3012 [x0 - dy, y0 + dz, z1], 3013 s=s, 3014 font=font, 3015 precision=precision, 3016 label_rotation=label_rotation, 3017 axis_rotation=zaxis_rotation + 90, 3018 lw=lw, 3019 italic=italic, 3020 prefix=ztitle, 3021 label=zlabel, 3022 units=units, 3023 ) 3024 acts.append(rz) 3025 3026 if xycross and rx and ry: 3027 lx = shapes.Line([x0, y0, z0], [x0, y1 + dx, z0]) 3028 ly = shapes.Line([x0 - dy, y1, z0], [x1, y1, z0]) 3029 d = min((x1 - x0), (y1 - y0)) / 200 3030 cxy = shapes.Circle([x0, y1, z0], r=d, res=15) 3031 acts.extend([lx, ly, cxy]) 3032 3033 macts = merge(acts) 3034 if not macts: 3035 return None 3036 macts.c(c).alpha(alpha).bc("t") 3037 macts.actor.UseBoundsOff() 3038 macts.actor.PickableOff() 3039 return macts 3040 3041 3042##################################################################### 3043class Ruler2D(vtki.vtkAxisActor2D): 3044 """ 3045 Create a ruler with tick marks, labels and a title. 3046 """ 3047 3048 def __init__( 3049 self, 3050 lw=2, 3051 ticks=True, 3052 labels=False, 3053 c="k", 3054 alpha=1, 3055 title="", 3056 font="Calco", 3057 font_size=24, 3058 bc=None, 3059 ): 3060 """ 3061 Create a ruler with tick marks, labels and a title. 3062 3063 Ruler2D is a 2D actor; that is, it is drawn on the overlay 3064 plane and is not occluded by 3D geometry. 3065 To use this class, specify two points defining the start and end 3066 with update_points() as 3D points. 3067 3068 This class decides decides how to create reasonable tick 3069 marks and labels. 3070 3071 Labels are drawn on the "right" side of the axis. 3072 The "right" side is the side of the axis on the right. 3073 The way the labels and title line up with the axis and tick marks 3074 depends on whether the line is considered horizontal or vertical. 3075 3076 Arguments: 3077 lw : (int) 3078 width of the line in pixel units 3079 ticks : (bool) 3080 control if drawing the tick marks 3081 labels : (bool) 3082 control if drawing the numeric labels 3083 c : (color) 3084 color of the object 3085 alpha : (float) 3086 opacity of the object 3087 title : (str) 3088 title of the ruler 3089 font : (str) 3090 font face name. Check [available fonts here](https://vedo.embl.es/fonts). 3091 font_size : (int) 3092 font size 3093 bc : (color) 3094 background color of the title 3095 3096 Example: 3097 ```python 3098 from vedo import * 3099 plt = Plotter(axes=1, interactive=False) 3100 plt.show(Cube()) 3101 rul = Ruler2D() 3102 rul.set_points([0,0,0], [0.5,0.5,0.5]) 3103 plt.add(rul) 3104 plt.interactive().close() 3105 ``` 3106 ![](https://vedo.embl.es/images/feats/dist_tool.png) 3107 """ 3108 super().__init__() 3109 3110 plt = vedo.plotter_instance 3111 if not plt: 3112 vedo.logger.error("Ruler2D need to initialize Plotter first.") 3113 raise RuntimeError() 3114 3115 self.p0 = [0, 0, 0] 3116 self.p1 = [0, 0, 0] 3117 self.distance = 0 3118 self.title = title 3119 3120 prop = self.GetProperty() 3121 tprop = self.GetTitleTextProperty() 3122 3123 self.SetTitle(title) 3124 self.SetNumberOfLabels(9) 3125 3126 if not font: 3127 font = settings.default_font 3128 if font.lower() == "courier": 3129 tprop.SetFontFamilyToCourier() 3130 elif font.lower() == "times": 3131 tprop.SetFontFamilyToTimes() 3132 elif font.lower() == "arial": 3133 tprop.SetFontFamilyToArial() 3134 else: 3135 tprop.SetFontFamily(vtki.VTK_FONT_FILE) 3136 tprop.SetFontFile(utils.get_font_path(font)) 3137 tprop.SetFontSize(font_size) 3138 tprop.BoldOff() 3139 tprop.ItalicOff() 3140 tprop.ShadowOff() 3141 tprop.SetColor(get_color(c)) 3142 tprop.SetOpacity(alpha) 3143 if bc is not None: 3144 bc = get_color(bc) 3145 tprop.SetBackgroundColor(bc) 3146 tprop.SetBackgroundOpacity(alpha) 3147 3148 lprop = vtki.vtkTextProperty() 3149 lprop.ShallowCopy(tprop) 3150 self.SetLabelTextProperty(lprop) 3151 3152 self.SetLabelFormat("%0.3g") 3153 self.SetTickVisibility(ticks) 3154 self.SetLabelVisibility(labels) 3155 prop.SetLineWidth(lw) 3156 prop.SetColor(get_color(c)) 3157 3158 self.renderer = plt.renderer 3159 self.cid = plt.interactor.AddObserver("RenderEvent", self._update_viz, 1.0) 3160 3161 def color(self, c) -> Self: 3162 """Assign a new color.""" 3163 c = get_color(c) 3164 self.GetTitleTextProperty().SetColor(c) 3165 self.GetLabelTextProperty().SetColor(c) 3166 self.GetProperty().SetColor(c) 3167 return self 3168 3169 def off(self) -> None: 3170 """Switch off the ruler completely.""" 3171 self.renderer.RemoveObserver(self.cid) 3172 self.renderer.RemoveActor(self) 3173 3174 def set_points(self, p0, p1) -> Self: 3175 """Set new values for the ruler start and end points.""" 3176 self.p0 = np.asarray(p0) 3177 self.p1 = np.asarray(p1) 3178 self._update_viz(0, 0) 3179 return self 3180 3181 def _update_viz(self, evt, name) -> None: 3182 ren = self.renderer 3183 view_size = np.array(ren.GetSize()) 3184 3185 ren.SetWorldPoint(*self.p0, 1) 3186 ren.WorldToDisplay() 3187 disp_point1 = ren.GetDisplayPoint()[:2] 3188 disp_point1 = np.array(disp_point1) / view_size 3189 3190 ren.SetWorldPoint(*self.p1, 1) 3191 ren.WorldToDisplay() 3192 disp_point2 = ren.GetDisplayPoint()[:2] 3193 disp_point2 = np.array(disp_point2) / view_size 3194 3195 self.SetPoint1(*disp_point1) 3196 self.SetPoint2(*disp_point2) 3197 self.distance = np.linalg.norm(self.p1 - self.p0) 3198 self.SetRange(0.0, float(self.distance)) 3199 if not self.title: 3200 self.SetTitle(utils.precision(self.distance, 3)) 3201 3202 3203##################################################################### 3204class DistanceTool(Group): 3205 """ 3206 Create a tool to measure the distance between two clicked points. 3207 """ 3208 3209 def __init__(self, plotter=None, c="k", lw=2): 3210 """ 3211 Create a tool to measure the distance between two clicked points. 3212 3213 Example: 3214 ```python 3215 from vedo import * 3216 mesh = ParametricShape("RandomHills").c("red5") 3217 plt = Plotter(axes=1) 3218 dtool = DistanceTool() 3219 dtool.on() 3220 plt.show(mesh, dtool) 3221 dtool.off() 3222 ``` 3223 ![](https://vedo.embl.es/images/feats/dist_tool.png) 3224 """ 3225 super().__init__() 3226 3227 self.p0 = [0, 0, 0] 3228 self.p1 = [0, 0, 0] 3229 self.distance = 0 3230 if plotter is None: 3231 plotter = vedo.plotter_instance 3232 self.plotter = plotter 3233 self.callback = None 3234 self.cid = None 3235 self.color = c 3236 self.linewidth = lw 3237 self.toggle = True 3238 self.ruler = None 3239 self.title = "" 3240 3241 def on(self) -> Self: 3242 """Switch tool on.""" 3243 self.cid = self.plotter.add_callback("click", self._onclick) 3244 self.VisibilityOn() 3245 self.plotter.render() 3246 return self 3247 3248 def off(self) -> None: 3249 """Switch tool off.""" 3250 self.plotter.remove_callback(self.cid) 3251 self.VisibilityOff() 3252 self.ruler.off() 3253 self.plotter.render() 3254 3255 def _onclick(self, event): 3256 if not event.actor: 3257 return 3258 3259 self.clear() 3260 3261 acts = [] 3262 if self.toggle: 3263 self.p0 = event.picked3d 3264 acts.append(Point(self.p0, c=self.color)) 3265 else: 3266 self.p1 = event.picked3d 3267 self.distance = np.linalg.norm(self.p1 - self.p0) 3268 acts.append(Point(self.p0, c=self.color)) 3269 acts.append(Point(self.p1, c=self.color)) 3270 self.ruler = Ruler2D(c=self.color) 3271 self.ruler.set_points(self.p0, self.p1) 3272 acts.append(self.ruler) 3273 3274 if self.callback is not None: 3275 self.callback(event) 3276 3277 for a in acts: 3278 try: 3279 self += a.actor 3280 except AttributeError: 3281 self += a 3282 self.toggle = not self.toggle 3283 3284 3285##################################################################### 3286def Axes( 3287 obj=None, 3288 xtitle='x', ytitle='y', ztitle='z', 3289 xrange=None, yrange=None, zrange=None, 3290 c=None, 3291 number_of_divisions=None, 3292 digits=None, 3293 limit_ratio=0.04, 3294 title_depth=0, 3295 title_font="", # grab settings.default_font 3296 text_scale=1.0, 3297 x_values_and_labels=None, y_values_and_labels=None, z_values_and_labels=None, 3298 htitle="", 3299 htitle_size=0.03, 3300 htitle_font=None, 3301 htitle_italic=False, 3302 htitle_color=None, htitle_backface_color=None, 3303 htitle_justify='bottom-left', 3304 htitle_rotation=0, 3305 htitle_offset=(0, 0.01, 0), 3306 xtitle_position=0.95, ytitle_position=0.95, ztitle_position=0.95, 3307 # xtitle_offset can be a list (dx,dy,dz) 3308 xtitle_offset=0.025, ytitle_offset=0.0275, ztitle_offset=0.02, 3309 xtitle_justify=None, ytitle_justify=None, ztitle_justify=None, 3310 # xtitle_rotation can be a list (rx,ry,rz) 3311 xtitle_rotation=0, ytitle_rotation=0, ztitle_rotation=0, 3312 xtitle_box=False, ytitle_box=False, 3313 xtitle_size=0.025, ytitle_size=0.025, ztitle_size=0.025, 3314 xtitle_color=None, ytitle_color=None, ztitle_color=None, 3315 xtitle_backface_color=None, ytitle_backface_color=None, ztitle_backface_color=None, 3316 xtitle_italic=0, ytitle_italic=0, ztitle_italic=0, 3317 grid_linewidth=1, 3318 xygrid=True, yzgrid=False, zxgrid=False, 3319 xygrid2=False, yzgrid2=False, zxgrid2=False, 3320 xygrid_transparent=False, yzgrid_transparent=False, zxgrid_transparent=False, 3321 xygrid2_transparent=False, yzgrid2_transparent=False, zxgrid2_transparent=False, 3322 xyplane_color=None, yzplane_color=None, zxplane_color=None, 3323 xygrid_color=None, yzgrid_color=None, zxgrid_color=None, 3324 xyalpha=0.075, yzalpha=0.075, zxalpha=0.075, 3325 xyframe_line=None, yzframe_line=None, zxframe_line=None, 3326 xyframe_color=None, yzframe_color=None, zxframe_color=None, 3327 axes_linewidth=1, 3328 xline_color=None, yline_color=None, zline_color=None, 3329 xhighlight_zero=False, yhighlight_zero=False, zhighlight_zero=False, 3330 xhighlight_zero_color='red4', yhighlight_zero_color='green4', zhighlight_zero_color='blue4', 3331 show_ticks=True, 3332 xtick_length=0.015, ytick_length=0.015, ztick_length=0.015, 3333 xtick_thickness=0.0025, ytick_thickness=0.0025, ztick_thickness=0.0025, 3334 xminor_ticks=1, yminor_ticks=1, zminor_ticks=1, 3335 tip_size=None, 3336 label_font="", # grab settings.default_font 3337 xlabel_color=None, ylabel_color=None, zlabel_color=None, 3338 xlabel_backface_color=None, ylabel_backface_color=None, zlabel_backface_color=None, 3339 xlabel_size=0.016, ylabel_size=0.016, zlabel_size=0.016, 3340 xlabel_offset=0.8, ylabel_offset=0.8, zlabel_offset=0.8, # each can be a list (dx,dy,dz) 3341 xlabel_justify=None, ylabel_justify=None, zlabel_justify=None, 3342 xlabel_rotation=0, ylabel_rotation=0, zlabel_rotation=0, # each can be a list (rx,ry,rz) 3343 xaxis_rotation=0, yaxis_rotation=0, zaxis_rotation=0, # rotate all elements around axis 3344 xyshift=0, yzshift=0, zxshift=0, 3345 xshift_along_y=0, xshift_along_z=0, 3346 yshift_along_x=0, yshift_along_z=0, 3347 zshift_along_x=0, zshift_along_y=0, 3348 x_use_bounds=True, y_use_bounds=True, z_use_bounds=False, 3349 x_inverted=False, y_inverted=False, z_inverted=False, 3350 use_global=False, 3351 tol=0.001, 3352 ) -> Union[Assembly, None]: 3353 """ 3354 Draw axes for the input object. 3355 Check [available fonts here](https://vedo.embl.es/fonts). 3356 3357 Returns an `vedo.Assembly` object. 3358 3359 Parameters 3360 ---------- 3361 3362 - `xtitle`, ['x'], x-axis title text 3363 - `xrange`, [None], x-axis range in format (xmin, ymin), default is automatic. 3364 - `number_of_divisions`, [None], approximate number of divisions on the longest axis 3365 - `axes_linewidth`, [1], width of the axes lines 3366 - `grid_linewidth`, [1], width of the grid lines 3367 - `title_depth`, [0], extrusion fractional depth of title text 3368 - `x_values_and_labels` [], assign custom tick positions and labels [(pos1, label1), ...] 3369 - `xygrid`, [True], show a gridded wall on plane xy 3370 - `yzgrid`, [True], show a gridded wall on plane yz 3371 - `zxgrid`, [True], show a gridded wall on plane zx 3372 - `yzgrid2`, [False], show yz plane on opposite side of the bounding box 3373 - `zxgrid2`, [False], show zx plane on opposite side of the bounding box 3374 - `xygrid_transparent` [False], make grid plane completely transparent 3375 - `xygrid2_transparent` [False], make grid plane completely transparent on opposite side box 3376 - `xyplane_color`, ['None'], color of the plane 3377 - `xygrid_color`, ['None'], grid line color 3378 - `xyalpha`, [0.15], grid plane opacity 3379 - `xyframe_line`, [0], add a frame for the plane, use value as the thickness 3380 - `xyframe_color`, [None], color for the frame of the plane 3381 - `show_ticks`, [True], show major ticks 3382 - `digits`, [None], use this number of significant digits in scientific notation 3383 - `title_font`, [''], font for axes titles 3384 - `label_font`, [''], font for numeric labels 3385 - `text_scale`, [1.0], global scaling factor for all text elements (titles, labels) 3386 - `htitle`, [''], header title 3387 - `htitle_size`, [0.03], header title size 3388 - `htitle_font`, [None], header font (defaults to `title_font`) 3389 - `htitle_italic`, [True], header font is italic 3390 - `htitle_color`, [None], header title color (defaults to `xtitle_color`) 3391 - `htitle_backface_color`, [None], header title color on its backface 3392 - `htitle_justify`, ['bottom-center'], origin of the title justification 3393 - `htitle_offset`, [(0,0.01,0)], control offsets of header title in x, y and z 3394 - `xtitle_position`, [0.32], title fractional positions along axis 3395 - `xtitle_offset`, [0.05], title fractional offset distance from axis line, can be a list 3396 - `xtitle_justify`, [None], choose the origin of the bounding box of title 3397 - `xtitle_rotation`, [0], add a rotation of the axis title, can be a list (rx,ry,rz) 3398 - `xtitle_box`, [False], add a box around title text 3399 - `xline_color`, [automatic], color of the x-axis 3400 - `xtitle_color`, [automatic], color of the axis title 3401 - `xtitle_backface_color`, [None], color of axis title on its backface 3402 - `xtitle_size`, [0.025], size of the axis title 3403 - `xtitle_italic`, [0], a bool or float to make the font italic 3404 - `xhighlight_zero`, [True], draw a line highlighting zero position if in range 3405 - `xhighlight_zero_color`, [auto], color of the line highlighting the zero position 3406 - `xtick_length`, [0.005], radius of the major ticks 3407 - `xtick_thickness`, [0.0025], thickness of the major ticks along their axis 3408 - `xminor_ticks`, [1], number of minor ticks between two major ticks 3409 - `xlabel_color`, [automatic], color of numeric labels and ticks 3410 - `xlabel_backface_color`, [auto], back face color of numeric labels and ticks 3411 - `xlabel_size`, [0.015], size of the numeric labels along axis 3412 - `xlabel_rotation`, [0,list], numeric labels rotation (can be a list of 3 rotations) 3413 - `xlabel_offset`, [0.8,list], offset of the numeric labels (can be a list of 3 offsets) 3414 - `xlabel_justify`, [None], choose the origin of the bounding box of labels 3415 - `xaxis_rotation`, [0], rotate the X axis elements (ticks and labels) around this same axis 3416 - `xyshift` [0.0], slide the xy-plane along z (the range is [0,1]) 3417 - `xshift_along_y` [0.0], slide x-axis along the y-axis (the range is [0,1]) 3418 - `tip_size`, [0.01], size of the arrow tip as a fraction of the bounding box diagonal 3419 - `limit_ratio`, [0.04], below this ratio don't plot smaller axis 3420 - `x_use_bounds`, [True], keep into account space occupied by labels when setting camera 3421 - `x_inverted`, [False], invert labels order and direction (only visually!) 3422 - `use_global`, [False], try to compute the global bounding box of visible actors 3423 3424 Example: 3425 ```python 3426 from vedo import Axes, Box, show 3427 box = Box(pos=(1,2,3), length=8, width=9, height=7).alpha(0.1) 3428 axs = Axes(box, c='k') # returns an Assembly object 3429 for a in axs.unpack(): 3430 print(a.name) 3431 show(box, axs).close() 3432 ``` 3433 ![](https://vedo.embl.es/images/feats/axes1.png) 3434 3435 Examples: 3436 - [custom_axes1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes1.py) 3437 - [custom_axes2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes2.py) 3438 - [custom_axes3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes3.py) 3439 - [custom_axes4.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes4.py) 3440 3441 ![](https://vedo.embl.es/images/pyplot/customAxes3.png) 3442 """ 3443 if not title_font: 3444 title_font = vedo.settings.default_font 3445 if not label_font: 3446 label_font = vedo.settings.default_font 3447 3448 if c is None: # automatic black or white 3449 c = (0.1, 0.1, 0.1) 3450 plt = vedo.plotter_instance 3451 if plt and plt.renderer: 3452 bgcol = plt.renderer.GetBackground() 3453 else: 3454 bgcol = (1, 1, 1) 3455 if np.sum(bgcol) < 1.5: 3456 c = (0.9, 0.9, 0.9) 3457 else: 3458 c = get_color(c) 3459 3460 # Check if obj has bounds, if so use those 3461 if obj is not None: 3462 try: 3463 bb = obj.bounds() 3464 except AttributeError: 3465 try: 3466 bb = obj.GetBounds() 3467 if xrange is None: xrange = (bb[0], bb[1]) 3468 if yrange is None: yrange = (bb[2], bb[3]) 3469 if zrange is None: zrange = (bb[4], bb[5]) 3470 obj = None # dont need it anymore 3471 except AttributeError: 3472 pass 3473 if utils.is_sequence(obj) and len(obj) == 6 and utils.is_number(obj[0]): 3474 # passing a list of numeric bounds 3475 if xrange is None: xrange = (obj[0], obj[1]) 3476 if yrange is None: yrange = (obj[2], obj[3]) 3477 if zrange is None: zrange = (obj[4], obj[5]) 3478 3479 if use_global: 3480 vbb, drange, min_bns, max_bns = compute_visible_bounds() 3481 else: 3482 if obj is not None: 3483 vbb, drange, min_bns, max_bns = compute_visible_bounds(obj) 3484 else: 3485 vbb = np.zeros(6) 3486 drange = np.zeros(3) 3487 if zrange is None: 3488 zrange = (0, 0) 3489 if xrange is None or yrange is None: 3490 vedo.logger.error("in Axes() must specify axes ranges!") 3491 return None ########################################### 3492 3493 if xrange is not None: 3494 if xrange[1] < xrange[0]: 3495 x_inverted = True 3496 xrange = [xrange[1], xrange[0]] 3497 vbb[0], vbb[1] = xrange 3498 drange[0] = vbb[1] - vbb[0] 3499 min_bns = vbb 3500 max_bns = vbb 3501 if yrange is not None: 3502 if yrange[1] < yrange[0]: 3503 y_inverted = True 3504 yrange = [yrange[1], yrange[0]] 3505 vbb[2], vbb[3] = yrange 3506 drange[1] = vbb[3] - vbb[2] 3507 min_bns = vbb 3508 max_bns = vbb 3509 if zrange is not None: 3510 if zrange[1] < zrange[0]: 3511 z_inverted = True 3512 zrange = [zrange[1], zrange[0]] 3513 vbb[4], vbb[5] = zrange 3514 drange[2] = vbb[5] - vbb[4] 3515 min_bns = vbb 3516 max_bns = vbb 3517 3518 drangemax = max(drange) 3519 if not drangemax: 3520 return None 3521 3522 if drange[0] / drangemax < limit_ratio: 3523 drange[0] = 0 3524 xtitle = "" 3525 if drange[1] / drangemax < limit_ratio: 3526 drange[1] = 0 3527 ytitle = "" 3528 if drange[2] / drangemax < limit_ratio: 3529 drange[2] = 0 3530 ztitle = "" 3531 3532 x0, x1, y0, y1, z0, z1 = vbb 3533 dx, dy, dz = drange 3534 3535 gscale = np.sqrt(dx * dx + dy * dy + dz * dz) * 0.75 3536 3537 if not xyplane_color: xyplane_color = c 3538 if not yzplane_color: yzplane_color = c 3539 if not zxplane_color: zxplane_color = c 3540 if not xygrid_color: xygrid_color = c 3541 if not yzgrid_color: yzgrid_color = c 3542 if not zxgrid_color: zxgrid_color = c 3543 if not xtitle_color: xtitle_color = c 3544 if not ytitle_color: ytitle_color = c 3545 if not ztitle_color: ztitle_color = c 3546 if not xline_color: xline_color = c 3547 if not yline_color: yline_color = c 3548 if not zline_color: zline_color = c 3549 if not xlabel_color: xlabel_color = xline_color 3550 if not ylabel_color: ylabel_color = yline_color 3551 if not zlabel_color: zlabel_color = zline_color 3552 3553 if tip_size is None: 3554 tip_size = 0.005 * gscale 3555 if not ztitle: 3556 tip_size = 0 # switch off in xy 2d 3557 3558 ndiv = 4 3559 if not ztitle or not ytitle or not xtitle: # make more default ticks if 2D 3560 ndiv = 6 3561 if not ztitle: 3562 if xyframe_line is None: 3563 xyframe_line = True 3564 if tip_size is None: 3565 tip_size = False 3566 3567 if utils.is_sequence(number_of_divisions): 3568 rx, ry, rz = number_of_divisions 3569 else: 3570 if not number_of_divisions: 3571 number_of_divisions = ndiv 3572 if not drangemax or np.any(np.isnan(drange)): 3573 rx, ry, rz = 1, 1, 1 3574 else: 3575 rx, ry, rz = np.ceil(drange / drangemax * number_of_divisions).astype(int) 3576 3577 if xtitle: 3578 xticks_float, xticks_str = utils.make_ticks(x0, x1, rx, x_values_and_labels, digits) 3579 xticks_float = xticks_float * dx 3580 if x_inverted: 3581 xticks_float = np.flip(-(xticks_float - xticks_float[-1])) 3582 xticks_str = list(reversed(xticks_str)) 3583 xticks_str[-1] = "" 3584 xhighlight_zero = False 3585 if ytitle: 3586 yticks_float, yticks_str = utils.make_ticks(y0, y1, ry, y_values_and_labels, digits) 3587 yticks_float = yticks_float * dy 3588 if y_inverted: 3589 yticks_float = np.flip(-(yticks_float - yticks_float[-1])) 3590 yticks_str = list(reversed(yticks_str)) 3591 yticks_str[-1] = "" 3592 yhighlight_zero = False 3593 if ztitle: 3594 zticks_float, zticks_str = utils.make_ticks(z0, z1, rz, z_values_and_labels, digits) 3595 zticks_float = zticks_float * dz 3596 if z_inverted: 3597 zticks_float = np.flip(-(zticks_float - zticks_float[-1])) 3598 zticks_str = list(reversed(zticks_str)) 3599 zticks_str[-1] = "" 3600 zhighlight_zero = False 3601 3602 ################################################ axes lines 3603 lines = [] 3604 if xtitle: 3605 axlinex = shapes.Line([0,0,0], [dx,0,0], c=xline_color, lw=axes_linewidth) 3606 axlinex.shift([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz]) 3607 axlinex.name = 'xAxis' 3608 lines.append(axlinex) 3609 if ytitle: 3610 axliney = shapes.Line([0,0,0], [0,dy,0], c=yline_color, lw=axes_linewidth) 3611 axliney.shift([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz]) 3612 axliney.name = 'yAxis' 3613 lines.append(axliney) 3614 if ztitle: 3615 axlinez = shapes.Line([0,0,0], [0,0,dz], c=zline_color, lw=axes_linewidth) 3616 axlinez.shift([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0]) 3617 axlinez.name = 'zAxis' 3618 lines.append(axlinez) 3619 3620 ################################################ grid planes 3621 # all shapes have a name to keep track of them in the Assembly 3622 # if user wants to unpack it 3623 grids = [] 3624 if xygrid and xtitle and ytitle: 3625 if not xygrid_transparent: 3626 gxy = shapes.Grid(s=(xticks_float, yticks_float)) 3627 gxy.alpha(xyalpha).c(xyplane_color).lw(0) 3628 if xyshift: gxy.shift([0,0,xyshift*dz]) 3629 elif tol: gxy.shift([0,0,-tol*gscale]) 3630 gxy.name = "xyGrid" 3631 grids.append(gxy) 3632 if grid_linewidth: 3633 gxy_lines = shapes.Grid(s=(xticks_float, yticks_float)) 3634 gxy_lines.c(xyplane_color).lw(grid_linewidth).alpha(xyalpha) 3635 if xyshift: gxy_lines.shift([0,0,xyshift*dz]) 3636 elif tol: gxy_lines.shift([0,0,-tol*gscale]) 3637 gxy_lines.name = "xyGridLines" 3638 grids.append(gxy_lines) 3639 3640 if yzgrid and ytitle and ztitle: 3641 if not yzgrid_transparent: 3642 gyz = shapes.Grid(s=(zticks_float, yticks_float)) 3643 gyz.alpha(yzalpha).c(yzplane_color).lw(0).rotate_y(-90) 3644 if yzshift: gyz.shift([yzshift*dx,0,0]) 3645 elif tol: gyz.shift([-tol*gscale,0,0]) 3646 gyz.name = "yzGrid" 3647 grids.append(gyz) 3648 if grid_linewidth: 3649 gyz_lines = shapes.Grid(s=(zticks_float, yticks_float)) 3650 gyz_lines.c(yzplane_color).lw(grid_linewidth).alpha(yzalpha).rotate_y(-90) 3651 if yzshift: gyz_lines.shift([yzshift*dx,0,0]) 3652 elif tol: gyz_lines.shift([-tol*gscale,0,0]) 3653 gyz_lines.name = "yzGridLines" 3654 grids.append(gyz_lines) 3655 3656 if zxgrid and ztitle and xtitle: 3657 if not zxgrid_transparent: 3658 gzx = shapes.Grid(s=(xticks_float, zticks_float)) 3659 gzx.alpha(zxalpha).c(zxplane_color).lw(0).rotate_x(90) 3660 if zxshift: gzx.shift([0,zxshift*dy,0]) 3661 elif tol: gzx.shift([0,-tol*gscale,0]) 3662 gzx.name = "zxGrid" 3663 grids.append(gzx) 3664 if grid_linewidth: 3665 gzx_lines = shapes.Grid(s=(xticks_float, zticks_float)) 3666 gzx_lines.c(zxplane_color).lw(grid_linewidth).alpha(zxalpha).rotate_x(90) 3667 if zxshift: gzx_lines.shift([0,zxshift*dy,0]) 3668 elif tol: gzx_lines.shift([0,-tol*gscale,0]) 3669 gzx_lines.name = "zxGridLines" 3670 grids.append(gzx_lines) 3671 3672 # Grid2 3673 if xygrid2 and xtitle and ytitle: 3674 if not xygrid2_transparent: 3675 gxy2 = shapes.Grid(s=(xticks_float, yticks_float)).z(dz) 3676 gxy2.alpha(xyalpha).c(xyplane_color).lw(0) 3677 gxy2.shift([0, tol * gscale, 0]) 3678 gxy2.name = "xyGrid2" 3679 grids.append(gxy2) 3680 if grid_linewidth: 3681 gxy2_lines = shapes.Grid(s=(xticks_float, yticks_float)).z(dz) 3682 gxy2_lines.c(xyplane_color).lw(grid_linewidth).alpha(xyalpha) 3683 gxy2_lines.shift([0, tol * gscale, 0]) 3684 gxy2_lines.name = "xygrid2Lines" 3685 grids.append(gxy2_lines) 3686 3687 if yzgrid2 and ytitle and ztitle: 3688 if not yzgrid2_transparent: 3689 gyz2 = shapes.Grid(s=(zticks_float, yticks_float)) 3690 gyz2.alpha(yzalpha).c(yzplane_color).lw(0) 3691 gyz2.rotate_y(-90).x(dx).shift([tol * gscale, 0, 0]) 3692 gyz2.name = "yzGrid2" 3693 grids.append(gyz2) 3694 if grid_linewidth: 3695 gyz2_lines = shapes.Grid(s=(zticks_float, yticks_float)) 3696 gyz2_lines.c(yzplane_color).lw(grid_linewidth).alpha(yzalpha) 3697 gyz2_lines.rotate_y(-90).x(dx).shift([tol * gscale, 0, 0]) 3698 gyz2_lines.name = "yzGrid2Lines" 3699 grids.append(gyz2_lines) 3700 3701 if zxgrid2 and ztitle and xtitle: 3702 if not zxgrid2_transparent: 3703 gzx2 = shapes.Grid(s=(xticks_float, zticks_float)) 3704 gzx2.alpha(zxalpha).c(zxplane_color).lw(0) 3705 gzx2.rotate_x(90).y(dy).shift([0, tol * gscale, 0]) 3706 gzx2.name = "zxGrid2" 3707 grids.append(gzx2) 3708 if grid_linewidth: 3709 gzx2_lines = shapes.Grid(s=(xticks_float, zticks_float)) 3710 gzx2_lines.c(zxplane_color).lw(grid_linewidth).alpha(zxalpha) 3711 gzx2_lines.rotate_x(90).y(dy).shift([0, tol * gscale, 0]) 3712 gzx2_lines.name = "zxGrid2Lines" 3713 grids.append(gzx2_lines) 3714 3715 ################################################ frame lines 3716 framelines = [] 3717 if xyframe_line and xtitle and ytitle: 3718 if not xyframe_color: 3719 xyframe_color = xygrid_color 3720 frxy = shapes.Line( 3721 [[0, dy, 0], [dx, dy, 0], [dx, 0, 0], [0, 0, 0], [0, dy, 0]], 3722 c=xyframe_color, 3723 lw=xyframe_line, 3724 ) 3725 frxy.shift([0, 0, xyshift * dz]) 3726 frxy.name = "xyFrameLine" 3727 framelines.append(frxy) 3728 if yzframe_line and ytitle and ztitle: 3729 if not yzframe_color: 3730 yzframe_color = yzgrid_color 3731 fryz = shapes.Line( 3732 [[0, 0, dz], [0, dy, dz], [0, dy, 0], [0, 0, 0], [0, 0, dz]], 3733 c=yzframe_color, 3734 lw=yzframe_line, 3735 ) 3736 fryz.shift([yzshift * dx, 0, 0]) 3737 fryz.name = "yzFrameLine" 3738 framelines.append(fryz) 3739 if zxframe_line and ztitle and xtitle: 3740 if not zxframe_color: 3741 zxframe_color = zxgrid_color 3742 frzx = shapes.Line( 3743 [[0, 0, dz], [dx, 0, dz], [dx, 0, 0], [0, 0, 0], [0, 0, dz]], 3744 c=zxframe_color, 3745 lw=zxframe_line, 3746 ) 3747 frzx.shift([0, zxshift * dy, 0]) 3748 frzx.name = "zxFrameLine" 3749 framelines.append(frzx) 3750 3751 ################################################ zero lines highlights 3752 highlights = [] 3753 if xygrid and xtitle and ytitle: 3754 if xhighlight_zero and min_bns[0] <= 0 and max_bns[1] > 0: 3755 xhl = -min_bns[0] 3756 hxy = shapes.Line([xhl, 0, 0], [xhl, dy, 0], c=xhighlight_zero_color) 3757 hxy.alpha(np.sqrt(xyalpha)).lw(grid_linewidth * 2) 3758 hxy.shift([0, 0, xyshift * dz]) 3759 hxy.name = "xyHighlightZero" 3760 highlights.append(hxy) 3761 if yhighlight_zero and min_bns[2] <= 0 and max_bns[3] > 0: 3762 yhl = -min_bns[2] 3763 hyx = shapes.Line([0, yhl, 0], [dx, yhl, 0], c=yhighlight_zero_color) 3764 hyx.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2) 3765 hyx.shift([0, 0, xyshift * dz]) 3766 hyx.name = "yxHighlightZero" 3767 highlights.append(hyx) 3768 3769 if yzgrid and ytitle and ztitle: 3770 if yhighlight_zero and min_bns[2] <= 0 and max_bns[3] > 0: 3771 yhl = -min_bns[2] 3772 hyz = shapes.Line([0, yhl, 0], [0, yhl, dz], c=yhighlight_zero_color) 3773 hyz.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2) 3774 hyz.shift([yzshift * dx, 0, 0]) 3775 hyz.name = "yzHighlightZero" 3776 highlights.append(hyz) 3777 if zhighlight_zero and min_bns[4] <= 0 and max_bns[5] > 0: 3778 zhl = -min_bns[4] 3779 hzy = shapes.Line([0, 0, zhl], [0, dy, zhl], c=zhighlight_zero_color) 3780 hzy.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2) 3781 hzy.shift([yzshift * dx, 0, 0]) 3782 hzy.name = "zyHighlightZero" 3783 highlights.append(hzy) 3784 3785 if zxgrid and ztitle and xtitle: 3786 if zhighlight_zero and min_bns[4] <= 0 and max_bns[5] > 0: 3787 zhl = -min_bns[4] 3788 hzx = shapes.Line([0, 0, zhl], [dx, 0, zhl], c=zhighlight_zero_color) 3789 hzx.alpha(np.sqrt(zxalpha)).lw(grid_linewidth * 2) 3790 hzx.shift([0, zxshift * dy, 0]) 3791 hzx.name = "zxHighlightZero" 3792 highlights.append(hzx) 3793 if xhighlight_zero and min_bns[0] <= 0 and max_bns[1] > 0: 3794 xhl = -min_bns[0] 3795 hxz = shapes.Line([xhl, 0, 0], [xhl, 0, dz], c=xhighlight_zero_color) 3796 hxz.alpha(np.sqrt(zxalpha)).lw(grid_linewidth * 2) 3797 hxz.shift([0, zxshift * dy, 0]) 3798 hxz.name = "xzHighlightZero" 3799 highlights.append(hxz) 3800 3801 ################################################ arrow cone 3802 cones = [] 3803 3804 if tip_size: 3805 3806 if xtitle: 3807 if x_inverted: 3808 cx = shapes.Cone( 3809 r=tip_size, 3810 height=tip_size * 2, 3811 axis=(-1, 0, 0), 3812 c=xline_color, 3813 res=12, 3814 ) 3815 else: 3816 cx = shapes.Cone( 3817 (dx, 0, 0), 3818 r=tip_size, 3819 height=tip_size * 2, 3820 axis=(1, 0, 0), 3821 c=xline_color, 3822 res=12, 3823 ) 3824 T = LinearTransform() 3825 T.translate( 3826 [ 3827 0, 3828 zxshift * dy + xshift_along_y * dy, 3829 xyshift * dz + xshift_along_z * dz, 3830 ] 3831 ) 3832 cx.apply_transform(T) 3833 cx.name = "xTipCone" 3834 cones.append(cx) 3835 3836 if ytitle: 3837 if y_inverted: 3838 cy = shapes.Cone( 3839 r=tip_size, 3840 height=tip_size * 2, 3841 axis=(0, -1, 0), 3842 c=yline_color, 3843 res=12, 3844 ) 3845 else: 3846 cy = shapes.Cone( 3847 (0, dy, 0), 3848 r=tip_size, 3849 height=tip_size * 2, 3850 axis=(0, 1, 0), 3851 c=yline_color, 3852 res=12, 3853 ) 3854 T = LinearTransform() 3855 T.translate( 3856 [ 3857 yzshift * dx + yshift_along_x * dx, 3858 0, 3859 xyshift * dz + yshift_along_z * dz, 3860 ] 3861 ) 3862 cy.apply_transform(T) 3863 cy.name = "yTipCone" 3864 cones.append(cy) 3865 3866 if ztitle: 3867 if z_inverted: 3868 cz = shapes.Cone( 3869 r=tip_size, 3870 height=tip_size * 2, 3871 axis=(0, 0, -1), 3872 c=zline_color, 3873 res=12, 3874 ) 3875 else: 3876 cz = shapes.Cone( 3877 (0, 0, dz), 3878 r=tip_size, 3879 height=tip_size * 2, 3880 axis=(0, 0, 1), 3881 c=zline_color, 3882 res=12, 3883 ) 3884 T = LinearTransform() 3885 T.translate( 3886 [ 3887 yzshift * dx + zshift_along_x * dx, 3888 zxshift * dy + zshift_along_y * dy, 3889 0, 3890 ] 3891 ) 3892 cz.apply_transform(T) 3893 cz.name = "zTipCone" 3894 cones.append(cz) 3895 3896 ################################################################# MAJOR ticks 3897 majorticks, minorticks = [], [] 3898 xticks, yticks, zticks = [], [], [] 3899 if show_ticks: 3900 if xtitle: 3901 tick_thickness = xtick_thickness * gscale / 2 3902 tick_length = xtick_length * gscale / 2 3903 for i in range(1, len(xticks_float) - 1): 3904 v1 = (xticks_float[i] - tick_thickness, -tick_length, 0) 3905 v2 = (xticks_float[i] + tick_thickness, tick_length, 0) 3906 xticks.append(shapes.Rectangle(v1, v2)) 3907 if len(xticks) > 1: 3908 xmajticks = merge(xticks).c(xlabel_color) 3909 T = LinearTransform() 3910 T.rotate_x(xaxis_rotation) 3911 T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz]) 3912 xmajticks.apply_transform(T) 3913 xmajticks.name = "xMajorTicks" 3914 majorticks.append(xmajticks) 3915 if ytitle: 3916 tick_thickness = ytick_thickness * gscale / 2 3917 tick_length = ytick_length * gscale / 2 3918 for i in range(1, len(yticks_float) - 1): 3919 v1 = (-tick_length, yticks_float[i] - tick_thickness, 0) 3920 v2 = (tick_length, yticks_float[i] + tick_thickness, 0) 3921 yticks.append(shapes.Rectangle(v1, v2)) 3922 if len(yticks) > 1: 3923 ymajticks = merge(yticks).c(ylabel_color) 3924 T = LinearTransform() 3925 T.rotate_y(yaxis_rotation) 3926 T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz]) 3927 ymajticks.apply_transform(T) 3928 ymajticks.name = "yMajorTicks" 3929 majorticks.append(ymajticks) 3930 if ztitle: 3931 tick_thickness = ztick_thickness * gscale / 2 3932 tick_length = ztick_length * gscale / 2.85 3933 for i in range(1, len(zticks_float) - 1): 3934 v1 = (zticks_float[i] - tick_thickness, -tick_length, 0) 3935 v2 = (zticks_float[i] + tick_thickness, tick_length, 0) 3936 zticks.append(shapes.Rectangle(v1, v2)) 3937 if len(zticks) > 1: 3938 zmajticks = merge(zticks).c(zlabel_color) 3939 T = LinearTransform() 3940 T.rotate_y(-90).rotate_z(-45 + zaxis_rotation) 3941 T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0]) 3942 zmajticks.apply_transform(T) 3943 zmajticks.name = "zMajorTicks" 3944 majorticks.append(zmajticks) 3945 3946 ############################################################# MINOR ticks 3947 if xtitle and xminor_ticks and len(xticks) > 1: 3948 tick_thickness = xtick_thickness * gscale / 4 3949 tick_length = xtick_length * gscale / 4 3950 xminor_ticks += 1 3951 ticks = [] 3952 for i in range(1, len(xticks)): 3953 t0, t1 = xticks[i - 1].pos(), xticks[i].pos() 3954 dt = t1 - t0 3955 for j in range(1, xminor_ticks): 3956 mt = dt * (j / xminor_ticks) + t0 3957 v1 = (mt[0] - tick_thickness, -tick_length, 0) 3958 v2 = (mt[0] + tick_thickness, tick_length, 0) 3959 ticks.append(shapes.Rectangle(v1, v2)) 3960 3961 # finish off the fist lower range from start to first tick 3962 t0, t1 = xticks[0].pos(), xticks[1].pos() 3963 dt = t1 - t0 3964 for j in range(1, xminor_ticks): 3965 mt = t0 - dt * (j / xminor_ticks) 3966 if mt[0] < 0: 3967 break 3968 v1 = (mt[0] - tick_thickness, -tick_length, 0) 3969 v2 = (mt[0] + tick_thickness, tick_length, 0) 3970 ticks.append(shapes.Rectangle(v1, v2)) 3971 3972 # finish off the last upper range from last tick to end 3973 t0, t1 = xticks[-2].pos(), xticks[-1].pos() 3974 dt = t1 - t0 3975 for j in range(1, xminor_ticks): 3976 mt = t1 + dt * (j / xminor_ticks) 3977 if mt[0] > dx: 3978 break 3979 v1 = (mt[0] - tick_thickness, -tick_length, 0) 3980 v2 = (mt[0] + tick_thickness, tick_length, 0) 3981 ticks.append(shapes.Rectangle(v1, v2)) 3982 3983 if ticks: 3984 xminticks = merge(ticks).c(xlabel_color) 3985 T = LinearTransform() 3986 T.rotate_x(xaxis_rotation) 3987 T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz]) 3988 xminticks.apply_transform(T) 3989 xminticks.name = "xMinorTicks" 3990 minorticks.append(xminticks) 3991 3992 if ytitle and yminor_ticks and len(yticks) > 1: ##### y 3993 tick_thickness = ytick_thickness * gscale / 4 3994 tick_length = ytick_length * gscale / 4 3995 yminor_ticks += 1 3996 ticks = [] 3997 for i in range(1, len(yticks)): 3998 t0, t1 = yticks[i - 1].pos(), yticks[i].pos() 3999 dt = t1 - t0 4000 for j in range(1, yminor_ticks): 4001 mt = dt * (j / yminor_ticks) + t0 4002 v1 = (-tick_length, mt[1] - tick_thickness, 0) 4003 v2 = (tick_length, mt[1] + tick_thickness, 0) 4004 ticks.append(shapes.Rectangle(v1, v2)) 4005 4006 # finish off the fist lower range from start to first tick 4007 t0, t1 = yticks[0].pos(), yticks[1].pos() 4008 dt = t1 - t0 4009 for j in range(1, yminor_ticks): 4010 mt = t0 - dt * (j / yminor_ticks) 4011 if mt[1] < 0: 4012 break 4013 v1 = (-tick_length, mt[1] - tick_thickness, 0) 4014 v2 = (tick_length, mt[1] + tick_thickness, 0) 4015 ticks.append(shapes.Rectangle(v1, v2)) 4016 4017 # finish off the last upper range from last tick to end 4018 t0, t1 = yticks[-2].pos(), yticks[-1].pos() 4019 dt = t1 - t0 4020 for j in range(1, yminor_ticks): 4021 mt = t1 + dt * (j / yminor_ticks) 4022 if mt[1] > dy: 4023 break 4024 v1 = (-tick_length, mt[1] - tick_thickness, 0) 4025 v2 = (tick_length, mt[1] + tick_thickness, 0) 4026 ticks.append(shapes.Rectangle(v1, v2)) 4027 4028 if ticks: 4029 yminticks = merge(ticks).c(ylabel_color) 4030 T = LinearTransform() 4031 T.rotate_y(yaxis_rotation) 4032 T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz]) 4033 yminticks.apply_transform(T) 4034 yminticks.name = "yMinorTicks" 4035 minorticks.append(yminticks) 4036 4037 if ztitle and zminor_ticks and len(zticks) > 1: ##### z 4038 tick_thickness = ztick_thickness * gscale / 4 4039 tick_length = ztick_length * gscale / 5 4040 zminor_ticks += 1 4041 ticks = [] 4042 for i in range(1, len(zticks)): 4043 t0, t1 = zticks[i - 1].pos(), zticks[i].pos() 4044 dt = t1 - t0 4045 for j in range(1, zminor_ticks): 4046 mt = dt * (j / zminor_ticks) + t0 4047 v1 = (mt[0] - tick_thickness, -tick_length, 0) 4048 v2 = (mt[0] + tick_thickness, tick_length, 0) 4049 ticks.append(shapes.Rectangle(v1, v2)) 4050 4051 # finish off the fist lower range from start to first tick 4052 t0, t1 = zticks[0].pos(), zticks[1].pos() 4053 dt = t1 - t0 4054 for j in range(1, zminor_ticks): 4055 mt = t0 - dt * (j / zminor_ticks) 4056 if mt[0] < 0: 4057 break 4058 v1 = (mt[0] - tick_thickness, -tick_length, 0) 4059 v2 = (mt[0] + tick_thickness, tick_length, 0) 4060 ticks.append(shapes.Rectangle(v1, v2)) 4061 4062 # finish off the last upper range from last tick to end 4063 t0, t1 = zticks[-2].pos(), zticks[-1].pos() 4064 dt = t1 - t0 4065 for j in range(1, zminor_ticks): 4066 mt = t1 + dt * (j / zminor_ticks) 4067 if mt[0] > dz: 4068 break 4069 v1 = (mt[0] - tick_thickness, -tick_length, 0) 4070 v2 = (mt[0] + tick_thickness, tick_length, 0) 4071 ticks.append(shapes.Rectangle(v1, v2)) 4072 4073 if ticks: 4074 zminticks = merge(ticks).c(zlabel_color) 4075 T = LinearTransform() 4076 T.rotate_y(-90).rotate_z(-45 + zaxis_rotation) 4077 T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0]) 4078 zminticks.apply_transform(T) 4079 zminticks.name = "zMinorTicks" 4080 minorticks.append(zminticks) 4081 4082 ################################################ axes NUMERIC text labels 4083 labels = [] 4084 xlab, ylab, zlab = None, None, None 4085 4086 if xlabel_size and xtitle: 4087 4088 xRot, yRot, zRot = 0, 0, 0 4089 if utils.is_sequence(xlabel_rotation): # unpck 3 rotations 4090 zRot, xRot, yRot = xlabel_rotation 4091 else: 4092 zRot = xlabel_rotation 4093 if zRot < 0: # deal with negative angles 4094 zRot += 360 4095 4096 jus = "center-top" 4097 if zRot: 4098 if zRot > 24: jus = "top-right" 4099 if zRot > 67: jus = "center-right" 4100 if zRot > 112: jus = "right-bottom" 4101 if zRot > 157: jus = "center-bottom" 4102 if zRot > 202: jus = "bottom-left" 4103 if zRot > 247: jus = "center-left" 4104 if zRot > 292: jus = "top-left" 4105 if zRot > 337: jus = "top-center" 4106 if xlabel_justify is not None: 4107 jus = xlabel_justify 4108 4109 for i in range(1, len(xticks_str)): 4110 t = xticks_str[i] 4111 if not t: 4112 continue 4113 if utils.is_sequence(xlabel_offset): 4114 xoffs, yoffs, zoffs = xlabel_offset 4115 else: 4116 xoffs, yoffs, zoffs = 0, xlabel_offset, 0 4117 4118 xlab = shapes.Text3D( 4119 t, s=xlabel_size * text_scale * gscale, font=label_font, justify=jus 4120 ) 4121 tb = xlab.ybounds() # must be ybounds: height of char 4122 4123 v = (xticks_float[i], 0, 0) 4124 offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0]) 4125 4126 T = LinearTransform() 4127 T.rotate_x(xaxis_rotation).rotate_y(yRot).rotate_x(xRot).rotate_z(zRot) 4128 T.translate(v + offs) 4129 T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz]) 4130 xlab.apply_transform(T) 4131 4132 xlab.use_bounds(x_use_bounds) 4133 4134 xlab.c(xlabel_color) 4135 if xlabel_backface_color is None: 4136 bfc = 1 - np.array(get_color(xlabel_color)) 4137 xlab.backcolor(bfc) 4138 xlab.name = f"xNumericLabel {i}" 4139 labels.append(xlab) 4140 4141 if ylabel_size and ytitle: 4142 4143 xRot, yRot, zRot = 0, 0, 0 4144 if utils.is_sequence(ylabel_rotation): # unpck 3 rotations 4145 zRot, yRot, xRot = ylabel_rotation 4146 else: 4147 zRot = ylabel_rotation 4148 if zRot < 0: 4149 zRot += 360 # deal with negative angles 4150 4151 jus = "center-right" 4152 if zRot: 4153 if zRot > 24: jus = "bottom-right" 4154 if zRot > 67: jus = "center-bottom" 4155 if zRot > 112: jus = "left-bottom" 4156 if zRot > 157: jus = "center-left" 4157 if zRot > 202: jus = "top-left" 4158 if zRot > 247: jus = "center-top" 4159 if zRot > 292: jus = "top-right" 4160 if zRot > 337: jus = "right-center" 4161 if ylabel_justify is not None: 4162 jus = ylabel_justify 4163 4164 for i in range(1, len(yticks_str)): 4165 t = yticks_str[i] 4166 if not t: 4167 continue 4168 if utils.is_sequence(ylabel_offset): 4169 xoffs, yoffs, zoffs = ylabel_offset 4170 else: 4171 xoffs, yoffs, zoffs = ylabel_offset, 0, 0 4172 ylab = shapes.Text3D( 4173 t, s=ylabel_size * text_scale * gscale, font=label_font, justify=jus 4174 ) 4175 tb = ylab.ybounds() # must be ybounds: height of char 4176 v = (0, yticks_float[i], 0) 4177 offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0]) 4178 4179 T = LinearTransform() 4180 T.rotate_y(yaxis_rotation).rotate_x(xRot).rotate_y(yRot).rotate_z(zRot) 4181 T.translate(v + offs) 4182 T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz]) 4183 ylab.apply_transform(T) 4184 4185 ylab.use_bounds(y_use_bounds) 4186 4187 ylab.c(ylabel_color) 4188 if ylabel_backface_color is None: 4189 bfc = 1 - np.array(get_color(ylabel_color)) 4190 ylab.backcolor(bfc) 4191 ylab.name = f"yNumericLabel {i}" 4192 labels.append(ylab) 4193 4194 if zlabel_size and ztitle: 4195 4196 xRot, yRot, zRot = 0, 0, 0 4197 if utils.is_sequence(zlabel_rotation): # unpck 3 rotations 4198 xRot, yRot, zRot = zlabel_rotation 4199 else: 4200 xRot = zlabel_rotation 4201 if xRot < 0: xRot += 360 # deal with negative angles 4202 4203 jus = "center-right" 4204 if xRot: 4205 if xRot > 24: jus = "bottom-right" 4206 if xRot > 67: jus = "center-bottom" 4207 if xRot > 112: jus = "left-bottom" 4208 if xRot > 157: jus = "center-left" 4209 if xRot > 202: jus = "top-left" 4210 if xRot > 247: jus = "center-top" 4211 if xRot > 292: jus = "top-right" 4212 if xRot > 337: jus = "right-center" 4213 if zlabel_justify is not None: 4214 jus = zlabel_justify 4215 4216 for i in range(1, len(zticks_str)): 4217 t = zticks_str[i] 4218 if not t: 4219 continue 4220 if utils.is_sequence(zlabel_offset): 4221 xoffs, yoffs, zoffs = zlabel_offset 4222 else: 4223 xoffs, yoffs, zoffs = zlabel_offset, zlabel_offset, 0 4224 zlab = shapes.Text3D(t, s=zlabel_size*text_scale*gscale, font=label_font, justify=jus) 4225 tb = zlab.ybounds() # must be ybounds: height of char 4226 4227 v = (0, 0, zticks_float[i]) 4228 offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0]) / 1.5 4229 angle = np.arctan2(dy, dx) * 57.3 4230 4231 T = LinearTransform() 4232 T.rotate_x(90 + zRot).rotate_y(-xRot).rotate_z(angle + yRot + zaxis_rotation) 4233 T.translate(v + offs) 4234 T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0]) 4235 zlab.apply_transform(T) 4236 4237 zlab.use_bounds(z_use_bounds) 4238 4239 zlab.c(zlabel_color) 4240 if zlabel_backface_color is None: 4241 bfc = 1 - np.array(get_color(zlabel_color)) 4242 zlab.backcolor(bfc) 4243 zlab.name = f"zNumericLabel {i}" 4244 labels.append(zlab) 4245 4246 ################################################ axes titles 4247 titles = [] 4248 4249 if xtitle: 4250 xRot, yRot, zRot = 0, 0, 0 4251 if utils.is_sequence(xtitle_rotation): # unpack 3 rotations 4252 zRot, xRot, yRot = xtitle_rotation 4253 else: 4254 zRot = xtitle_rotation 4255 if zRot < 0: # deal with negative angles 4256 zRot += 360 4257 4258 if utils.is_sequence(xtitle_offset): 4259 xoffs, yoffs, zoffs = xtitle_offset 4260 else: 4261 xoffs, yoffs, zoffs = 0, xtitle_offset, 0 4262 4263 if xtitle_justify is not None: 4264 jus = xtitle_justify 4265 else: 4266 # find best justfication for given rotation(s) 4267 jus = "right-top" 4268 if zRot: 4269 if zRot > 24: jus = "center-right" 4270 if zRot > 67: jus = "right-bottom" 4271 if zRot > 157: jus = "bottom-left" 4272 if zRot > 202: jus = "center-left" 4273 if zRot > 247: jus = "top-left" 4274 if zRot > 337: jus = "top-right" 4275 4276 xt = shapes.Text3D( 4277 xtitle, 4278 s=xtitle_size * text_scale * gscale, 4279 font=title_font, 4280 c=xtitle_color, 4281 justify=jus, 4282 depth=title_depth, 4283 italic=xtitle_italic, 4284 ) 4285 if xtitle_backface_color is None: 4286 xtitle_backface_color = 1 - np.array(get_color(xtitle_color)) 4287 xt.backcolor(xtitle_backface_color) 4288 4289 shift = 0 4290 if xlab: # xlab is the last created numeric text label.. 4291 lt0, lt1 = xlab.bounds()[2:4] 4292 shift = lt1 - lt0 4293 4294 T = LinearTransform() 4295 T.rotate_x(xRot).rotate_y(yRot).rotate_z(zRot) 4296 T.set_position( 4297 [(xoffs + xtitle_position) * dx, 4298 -(yoffs + xtick_length / 2) * dy - shift, 4299 zoffs * dz] 4300 ) 4301 T.rotate_x(xaxis_rotation) 4302 T.translate([0, xshift_along_y * dy, xyshift * dz + xshift_along_z * dz]) 4303 xt.apply_transform(T) 4304 4305 xt.use_bounds(x_use_bounds) 4306 if xtitle == " ": 4307 xt.use_bounds(False) 4308 xt.name = "xtitle" 4309 titles.append(xt) 4310 if xtitle_box: 4311 titles.append(xt.box(scale=1.1).use_bounds(x_use_bounds)) 4312 4313 if ytitle: 4314 xRot, yRot, zRot = 0, 0, 0 4315 if utils.is_sequence(ytitle_rotation): # unpck 3 rotations 4316 zRot, yRot, xRot = ytitle_rotation 4317 else: 4318 zRot = ytitle_rotation 4319 if len(ytitle) > 3: 4320 zRot += 90 4321 ytitle_position *= 0.975 4322 if zRot < 0: 4323 zRot += 360 # deal with negative angles 4324 4325 if utils.is_sequence(ytitle_offset): 4326 xoffs, yoffs, zoffs = ytitle_offset 4327 else: 4328 xoffs, yoffs, zoffs = ytitle_offset, 0, 0 4329 4330 if ytitle_justify is not None: 4331 jus = ytitle_justify 4332 else: 4333 jus = "center-right" 4334 if zRot: 4335 if zRot > 24: jus = "bottom-right" 4336 if zRot > 112: jus = "left-bottom" 4337 if zRot > 157: jus = "center-left" 4338 if zRot > 202: jus = "top-left" 4339 if zRot > 292: jus = "top-right" 4340 if zRot > 337: jus = "right-center" 4341 4342 yt = shapes.Text3D( 4343 ytitle, 4344 s=ytitle_size * text_scale * gscale, 4345 font=title_font, 4346 c=ytitle_color, 4347 justify=jus, 4348 depth=title_depth, 4349 italic=ytitle_italic, 4350 ) 4351 if ytitle_backface_color is None: 4352 ytitle_backface_color = 1 - np.array(get_color(ytitle_color)) 4353 yt.backcolor(ytitle_backface_color) 4354 4355 shift = 0 4356 if ylab: # this is the last created num label.. 4357 lt0, lt1 = ylab.bounds()[0:2] 4358 shift = lt1 - lt0 4359 4360 T = LinearTransform() 4361 T.rotate_x(xRot).rotate_y(yRot).rotate_z(zRot) 4362 T.set_position( 4363 [-(xoffs + ytick_length / 2) * dx - shift, 4364 (yoffs + ytitle_position) * dy, 4365 zoffs * dz] 4366 ) 4367 T.rotate_y(yaxis_rotation) 4368 T.translate([yshift_along_x * dx, 0, xyshift * dz + yshift_along_z * dz]) 4369 yt.apply_transform(T) 4370 4371 yt.use_bounds(y_use_bounds) 4372 if ytitle == " ": 4373 yt.use_bounds(False) 4374 yt.name = "ytitle" 4375 titles.append(yt) 4376 if ytitle_box: 4377 titles.append(yt.box(scale=1.1).use_bounds(y_use_bounds)) 4378 4379 if ztitle: 4380 xRot, yRot, zRot = 0, 0, 0 4381 if utils.is_sequence(ztitle_rotation): # unpck 3 rotations 4382 xRot, yRot, zRot = ztitle_rotation 4383 else: 4384 xRot = ztitle_rotation 4385 if len(ztitle) > 3: 4386 xRot += 90 4387 ztitle_position *= 0.975 4388 if xRot < 0: 4389 xRot += 360 # deal with negative angles 4390 4391 if ztitle_justify is not None: 4392 jus = ztitle_justify 4393 else: 4394 jus = "center-right" 4395 if xRot: 4396 if xRot > 24: jus = "bottom-right" 4397 if xRot > 112: jus = "left-bottom" 4398 if xRot > 157: jus = "center-left" 4399 if xRot > 202: jus = "top-left" 4400 if xRot > 292: jus = "top-right" 4401 if xRot > 337: jus = "right-center" 4402 4403 zt = shapes.Text3D( 4404 ztitle, 4405 s=ztitle_size * text_scale * gscale, 4406 font=title_font, 4407 c=ztitle_color, 4408 justify=jus, 4409 depth=title_depth, 4410 italic=ztitle_italic, 4411 ) 4412 if ztitle_backface_color is None: 4413 ztitle_backface_color = 1 - np.array(get_color(ztitle_color)) 4414 zt.backcolor(ztitle_backface_color) 4415 4416 angle = np.arctan2(dy, dx) * 57.3 4417 shift = 0 4418 if zlab: # this is the last created one.. 4419 lt0, lt1 = zlab.bounds()[0:2] 4420 shift = lt1 - lt0 4421 4422 T = LinearTransform() 4423 T.rotate_x(90 + zRot).rotate_y(-xRot).rotate_z(angle + yRot) 4424 T.set_position([ 4425 -(ztitle_offset + ztick_length / 5) * dx - shift, 4426 -(ztitle_offset + ztick_length / 5) * dy - shift, 4427 ztitle_position * dz] 4428 ) 4429 T.rotate_z(zaxis_rotation) 4430 T.translate([zshift_along_x * dx, zxshift * dy + zshift_along_y * dy, 0]) 4431 zt.apply_transform(T) 4432 4433 zt.use_bounds(z_use_bounds) 4434 if ztitle == " ": 4435 zt.use_bounds(False) 4436 zt.name = "ztitle" 4437 titles.append(zt) 4438 4439 ################################################### header title 4440 if htitle: 4441 if htitle_font is None: 4442 htitle_font = title_font 4443 if htitle_color is None: 4444 htitle_color = xtitle_color 4445 htit = shapes.Text3D( 4446 htitle, 4447 s=htitle_size * gscale * text_scale, 4448 font=htitle_font, 4449 c=htitle_color, 4450 justify=htitle_justify, 4451 depth=title_depth, 4452 italic=htitle_italic, 4453 ) 4454 if htitle_backface_color is None: 4455 htitle_backface_color = 1 - np.array(get_color(htitle_color)) 4456 htit.backcolor(htitle_backface_color) 4457 htit.rotate_x(htitle_rotation) 4458 wpos = [htitle_offset[0]*dx, (1 + htitle_offset[1])*dy, htitle_offset[2]*dz] 4459 htit.shift(np.array(wpos) + [0, 0, xyshift*dz]) 4460 htit.name = "htitle" 4461 titles.append(htit) 4462 4463 ###### 4464 acts = titles + lines + labels + grids + framelines 4465 acts += highlights + majorticks + minorticks + cones 4466 orig = (min_bns[0], min_bns[2], min_bns[4]) 4467 for a in acts: 4468 a.shift(orig) 4469 a.actor.PickableOff() 4470 a.properties.LightingOff() 4471 asse = Assembly(acts) 4472 asse.PickableOff() 4473 asse.name = "Axes" 4474 return asse 4475 4476 4477def add_global_axes(axtype=None, c=None, bounds=()) -> None: 4478 """ 4479 Draw axes on scene. Available axes types are 4480 4481 Parameters 4482 ---------- 4483 axtype : (int) 4484 - 0, no axes, 4485 - 1, draw three gray grid walls 4486 - 2, show cartesian axes from (0,0,0) 4487 - 3, show positive range of cartesian axes from (0,0,0) 4488 - 4, show a triad at bottom left 4489 - 5, show a cube at bottom left 4490 - 6, mark the corners of the bounding box 4491 - 7, draw a 3D ruler at each side of the cartesian axes 4492 - 8, show the `vtkCubeAxesActor` object 4493 - 9, show the bounding box outLine 4494 - 10, show three circles representing the maximum bounding box 4495 - 11, show a large grid on the x-y plane (use with zoom=8) 4496 - 12, show polar axes 4497 - 13, draw a simple ruler at the bottom of the window 4498 - 14, show the vtk default `vtkCameraOrientationWidget` object 4499 4500 Axis type-1 can be fully customized by passing a dictionary `axes=dict()`, 4501 see `vedo.Axes` for the complete list of options. 4502 4503 Example 4504 ------- 4505 .. code-block:: python 4506 4507 from vedo import Box, show 4508 b = Box(pos=(0, 0, 0), length=80, width=90, height=70).alpha(0.1) 4509 show( 4510 b, 4511 axes={ 4512 "xtitle": "Some long variable [a.u.]", 4513 "number_of_divisions": 4, 4514 # ... 4515 }, 4516 ) 4517 """ 4518 plt = vedo.plotter_instance 4519 if plt is None: 4520 return 4521 4522 if axtype is not None: 4523 plt.axes = axtype # override 4524 4525 r = plt.renderers.index(plt.renderer) 4526 4527 if not plt.axes: 4528 return 4529 4530 if c is None: # automatic black or white 4531 c = (0.9, 0.9, 0.9) 4532 if np.sum(plt.renderer.GetBackground()) > 1.5: 4533 c = (0.1, 0.1, 0.1) 4534 else: 4535 c = get_color(c) # for speed 4536 4537 if not plt.renderer: 4538 return 4539 4540 if plt.axes_instances[r]: 4541 return 4542 4543 ############################################################ 4544 # custom grid walls 4545 if plt.axes == 1 or plt.axes is True or isinstance(plt.axes, dict): 4546 4547 if len(bounds) == 6: 4548 bnds = bounds 4549 xrange = (bnds[0], bnds[1]) 4550 yrange = (bnds[2], bnds[3]) 4551 zrange = (bnds[4], bnds[5]) 4552 else: 4553 xrange = None 4554 yrange = None 4555 zrange = None 4556 4557 if isinstance(plt.axes, dict): 4558 plt.axes.update({"use_global": True}) 4559 # protect from invalid camelCase options from vedo<=2.3 4560 for k in plt.axes: 4561 if k.lower() != k: 4562 return 4563 if "xrange" in plt.axes: 4564 xrange = plt.axes.pop("xrange") 4565 if "yrange" in plt.axes: 4566 yrange = plt.axes.pop("yrange") 4567 if "zrange" in plt.axes: 4568 zrange = plt.axes.pop("zrange") 4569 asse = Axes(**plt.axes, xrange=xrange, yrange=yrange, zrange=zrange) 4570 else: 4571 asse = Axes(xrange=xrange, yrange=yrange, zrange=zrange) 4572 4573 plt.add(asse) 4574 plt.axes_instances[r] = asse 4575 4576 elif plt.axes in (2, 3): 4577 x0, x1, y0, y1, z0, z1 = plt.renderer.ComputeVisiblePropBounds() 4578 xcol, ycol, zcol = "dr", "dg", "db" 4579 s = 1 4580 alpha = 1 4581 centered = False 4582 dx, dy, dz = x1 - x0, y1 - y0, z1 - z0 4583 aves = np.sqrt(dx * dx + dy * dy + dz * dz) / 2 4584 x0, x1 = min(x0, 0), max(x1, 0) 4585 y0, y1 = min(y0, 0), max(y1, 0) 4586 z0, z1 = min(z0, 0), max(z1, 0) 4587 4588 if plt.axes == 3: 4589 if x1 > 0: 4590 x0 = 0 4591 if y1 > 0: 4592 y0 = 0 4593 if z1 > 0: 4594 z0 = 0 4595 4596 dx, dy, dz = x1 - x0, y1 - y0, z1 - z0 4597 acts = [] 4598 if x0 * x1 <= 0 or y0 * z1 <= 0 or z0 * z1 <= 0: # some ranges contain origin 4599 zero = shapes.Sphere(r=aves / 120 * s, c="k", alpha=alpha, res=10) 4600 acts += [zero] 4601 4602 if dx > aves / 100: 4603 xl = shapes.Cylinder([[x0, 0, 0], [x1, 0, 0]], r=aves / 250 * s, c=xcol, alpha=alpha) 4604 xc = shapes.Cone( 4605 pos=[x1, 0, 0], 4606 c=xcol, 4607 alpha=alpha, 4608 r=aves / 100 * s, 4609 height=aves / 25 * s, 4610 axis=[1, 0, 0], 4611 res=10, 4612 ) 4613 wpos = [x1, -aves / 25 * s, 0] # aligned to arrow tip 4614 if centered: 4615 wpos = [(x0 + x1) / 2, -aves / 25 * s, 0] 4616 xt = shapes.Text3D("x", pos=wpos, s=aves / 40 * s, c=xcol) 4617 acts += [xl, xc, xt] 4618 4619 if dy > aves / 100: 4620 yl = shapes.Cylinder([[0, y0, 0], [0, y1, 0]], r=aves / 250 * s, c=ycol, alpha=alpha) 4621 yc = shapes.Cone( 4622 pos=[0, y1, 0], 4623 c=ycol, 4624 alpha=alpha, 4625 r=aves / 100 * s, 4626 height=aves / 25 * s, 4627 axis=[0, 1, 0], 4628 res=10, 4629 ) 4630 wpos = [-aves / 40 * s, y1, 0] 4631 if centered: 4632 wpos = [-aves / 40 * s, (y0 + y1) / 2, 0] 4633 yt = shapes.Text3D("y", pos=(0, 0, 0), s=aves / 40 * s, c=ycol) 4634 yt.rotate_z(90) 4635 yt.pos(wpos) 4636 acts += [yl, yc, yt] 4637 4638 if dz > aves / 100: 4639 zl = shapes.Cylinder([[0, 0, z0], [0, 0, z1]], r=aves / 250 * s, c=zcol, alpha=alpha) 4640 zc = shapes.Cone( 4641 pos=[0, 0, z1], 4642 c=zcol, 4643 alpha=alpha, 4644 r=aves / 100 * s, 4645 height=aves / 25 * s, 4646 axis=[0, 0, 1], 4647 res=10, 4648 ) 4649 wpos = [-aves / 50 * s, -aves / 50 * s, z1] 4650 if centered: 4651 wpos = [-aves / 50 * s, -aves / 50 * s, (z0 + z1) / 2] 4652 zt = shapes.Text3D("z", pos=(0, 0, 0), s=aves / 40 * s, c=zcol) 4653 zt.rotate_z(45) 4654 zt.rotate_x(90) 4655 zt.pos(wpos) 4656 acts += [zl, zc, zt] 4657 for a in acts: 4658 a.actor.PickableOff() 4659 asse = Assembly(acts) 4660 asse.actor.PickableOff() 4661 plt.add(asse) 4662 plt.axes_instances[r] = asse 4663 4664 elif plt.axes == 4: 4665 axact = vtki.vtkAxesActor() 4666 axact.SetShaftTypeToCylinder() 4667 axact.SetCylinderRadius(0.03) 4668 axact.SetXAxisLabelText("x") 4669 axact.SetYAxisLabelText("y") 4670 axact.SetZAxisLabelText("z") 4671 axact.GetXAxisShaftProperty().SetColor(1, 0, 0) 4672 axact.GetYAxisShaftProperty().SetColor(0, 1, 0) 4673 axact.GetZAxisShaftProperty().SetColor(0, 0, 1) 4674 axact.GetXAxisTipProperty().SetColor(1, 0, 0) 4675 axact.GetYAxisTipProperty().SetColor(0, 1, 0) 4676 axact.GetZAxisTipProperty().SetColor(0, 0, 1) 4677 bc = np.array(plt.renderer.GetBackground()) 4678 if np.sum(bc) < 1.5: 4679 lc = (1, 1, 1) 4680 else: 4681 lc = (0, 0, 0) 4682 axact.GetXAxisCaptionActor2D().GetCaptionTextProperty().BoldOff() 4683 axact.GetYAxisCaptionActor2D().GetCaptionTextProperty().BoldOff() 4684 axact.GetZAxisCaptionActor2D().GetCaptionTextProperty().BoldOff() 4685 axact.GetXAxisCaptionActor2D().GetCaptionTextProperty().ItalicOff() 4686 axact.GetYAxisCaptionActor2D().GetCaptionTextProperty().ItalicOff() 4687 axact.GetZAxisCaptionActor2D().GetCaptionTextProperty().ItalicOff() 4688 axact.GetXAxisCaptionActor2D().GetCaptionTextProperty().ShadowOff() 4689 axact.GetYAxisCaptionActor2D().GetCaptionTextProperty().ShadowOff() 4690 axact.GetZAxisCaptionActor2D().GetCaptionTextProperty().ShadowOff() 4691 axact.GetXAxisCaptionActor2D().GetCaptionTextProperty().SetColor(lc) 4692 axact.GetYAxisCaptionActor2D().GetCaptionTextProperty().SetColor(lc) 4693 axact.GetZAxisCaptionActor2D().GetCaptionTextProperty().SetColor(lc) 4694 axact.PickableOff() 4695 icn = Icon(axact, size=0.1) 4696 plt.axes_instances[r] = icn 4697 icn.SetInteractor(plt.interactor) 4698 icn.EnabledOn() 4699 icn.InteractiveOff() 4700 plt.widgets.append(icn) 4701 4702 elif plt.axes == 5: 4703 axact = vtki.new("AnnotatedCubeActor") 4704 axact.GetCubeProperty().SetColor(get_color(settings.annotated_cube_color)) 4705 axact.SetTextEdgesVisibility(0) 4706 axact.SetFaceTextScale(settings.annotated_cube_text_scale) 4707 axact.SetXPlusFaceText(settings.annotated_cube_texts[0]) # XPlus 4708 axact.SetXMinusFaceText(settings.annotated_cube_texts[1]) # XMinus 4709 axact.SetYPlusFaceText(settings.annotated_cube_texts[2]) # YPlus 4710 axact.SetYMinusFaceText(settings.annotated_cube_texts[3]) # YMinus 4711 axact.SetZPlusFaceText(settings.annotated_cube_texts[4]) # ZPlus 4712 axact.SetZMinusFaceText(settings.annotated_cube_texts[5]) # ZMinus 4713 axact.SetZFaceTextRotation(90) 4714 4715 if settings.annotated_cube_text_color is None: # use default 4716 axact.GetXPlusFaceProperty().SetColor(get_color("r")) 4717 axact.GetXMinusFaceProperty().SetColor(get_color("dr")) 4718 axact.GetYPlusFaceProperty().SetColor(get_color("g")) 4719 axact.GetYMinusFaceProperty().SetColor(get_color("dg")) 4720 axact.GetZPlusFaceProperty().SetColor(get_color("b")) 4721 axact.GetZMinusFaceProperty().SetColor(get_color("db")) 4722 else: # use single user color 4723 ac = get_color(settings.annotated_cube_text_color) 4724 axact.GetXPlusFaceProperty().SetColor(ac) 4725 axact.GetXMinusFaceProperty().SetColor(ac) 4726 axact.GetYPlusFaceProperty().SetColor(ac) 4727 axact.GetYMinusFaceProperty().SetColor(ac) 4728 axact.GetZPlusFaceProperty().SetColor(ac) 4729 axact.GetZMinusFaceProperty().SetColor(ac) 4730 4731 axact.PickableOff() 4732 icn = Icon(axact, size=0.06) 4733 plt.axes_instances[r] = icn 4734 icn.SetInteractor(plt.interactor) 4735 icn.EnabledOn() 4736 icn.InteractiveOff() 4737 plt.widgets.append(icn) 4738 4739 elif plt.axes == 6: 4740 ocf = vtki.new("OutlineCornerFilter") 4741 ocf.SetCornerFactor(0.1) 4742 largestact, sz = None, -1 4743 for a in plt.objects: 4744 try: 4745 if a.pickable(): 4746 b = a.bounds() 4747 if b is None: 4748 return 4749 d = max(b[1] - b[0], b[3] - b[2], b[5] - b[4]) 4750 if sz < d: 4751 largestact = a 4752 sz = d 4753 except AttributeError: 4754 pass 4755 4756 try: 4757 ocf.SetInputData(largestact) 4758 except TypeError: 4759 try: 4760 ocf.SetInputData(largestact.dataset) 4761 except (TypeError, AttributeError): 4762 return 4763 ocf.Update() 4764 4765 oc_mapper = vtki.new("HierarchicalPolyDataMapper") 4766 oc_mapper.SetInputConnection(0, ocf.GetOutputPort(0)) 4767 oc_actor = vtki.vtkActor() 4768 oc_actor.SetMapper(oc_mapper) 4769 bc = np.array(plt.renderer.GetBackground()) 4770 if np.sum(bc) < 1.5: 4771 lc = (1, 1, 1) 4772 else: 4773 lc = (0, 0, 0) 4774 oc_actor.GetProperty().SetColor(lc) 4775 oc_actor.PickableOff() 4776 oc_actor.UseBoundsOn() 4777 plt.axes_instances[r] = oc_actor 4778 plt.add(oc_actor) 4779 4780 elif plt.axes == 7: 4781 vbb = compute_visible_bounds()[0] 4782 rulax = RulerAxes(vbb, c=c, xtitle="x - ", ytitle="y - ", ztitle="z - ") 4783 plt.axes_instances[r] = rulax 4784 if not rulax: 4785 return 4786 rulax.actor.UseBoundsOn() 4787 rulax.actor.PickableOff() 4788 plt.add(rulax) 4789 4790 elif plt.axes == 8: 4791 vbb = compute_visible_bounds()[0] 4792 ca = vtki.new("CubeAxesActor") 4793 ca.SetBounds(vbb) 4794 ca.SetCamera(plt.renderer.GetActiveCamera()) 4795 ca.GetXAxesLinesProperty().SetColor(c) 4796 ca.GetYAxesLinesProperty().SetColor(c) 4797 ca.GetZAxesLinesProperty().SetColor(c) 4798 for i in range(3): 4799 ca.GetLabelTextProperty(i).SetColor(c) 4800 ca.GetTitleTextProperty(i).SetColor(c) 4801 ca.SetTitleOffset(5) 4802 ca.SetFlyMode(3) 4803 ca.SetXTitle("x") 4804 ca.SetYTitle("y") 4805 ca.SetZTitle("z") 4806 ca.PickableOff() 4807 ca.UseBoundsOff() 4808 plt.axes_instances[r] = ca 4809 plt.renderer.AddActor(ca) 4810 4811 elif plt.axes == 9: 4812 vbb = compute_visible_bounds()[0] 4813 src = vtki.new("CubeSource") 4814 src.SetXLength(vbb[1] - vbb[0]) 4815 src.SetYLength(vbb[3] - vbb[2]) 4816 src.SetZLength(vbb[5] - vbb[4]) 4817 src.Update() 4818 ca = Mesh(src.GetOutput(), c, 0.5).wireframe(True) 4819 ca.pos((vbb[0] + vbb[1]) / 2, (vbb[3] + vbb[2]) / 2, (vbb[5] + vbb[4]) / 2) 4820 ca.actor.PickableOff() 4821 ca.actor.UseBoundsOff() 4822 plt.axes_instances[r] = ca 4823 plt.add(ca) 4824 4825 elif plt.axes == 10: 4826 vbb = compute_visible_bounds()[0] 4827 x0 = (vbb[0] + vbb[1]) / 2, (vbb[3] + vbb[2]) / 2, (vbb[5] + vbb[4]) / 2 4828 rx, ry, rz = (vbb[1] - vbb[0]) / 2, (vbb[3] - vbb[2]) / 2, (vbb[5] - vbb[4]) / 2 4829 # compute diagonal length of the bounding box 4830 rm = np.sqrt(rx ** 2 + ry ** 2 + rz ** 2) 4831 d = 0.005 * rm 4832 xc = shapes.Disc(x0, r1=rm, r2=rm+d, c="lr", res=(1, 120)) 4833 yc = shapes.Disc(x0, r1=rm, r2=rm+d, c="lg", res=(1, 120)).rotate_x(90) 4834 zc = shapes.Disc(x0, r1=rm, r2=rm+d, c="lb", res=(1, 120)).rotate_y(90) 4835 xc.pickable(0).lighting("off") 4836 yc.pickable(0).lighting("off") 4837 zc.pickable(0).lighting("off") 4838 ca = xc + yc + zc 4839 ca.PickableOff() 4840 ca.UseBoundsOff() 4841 plt.axes_instances[r] = ca 4842 plt.add(ca) 4843 4844 elif plt.axes == 11: 4845 vbb, ss = compute_visible_bounds()[0:2] 4846 xpos, ypos = (vbb[1] + vbb[0]) / 2, (vbb[3] + vbb[2]) / 2 4847 gs = sum(ss) * 3 4848 gr = shapes.Grid((xpos, ypos, vbb[4]), s=(gs, gs), res=(11, 11), c=c, alpha=0.1) 4849 gr.lighting("off").actor.PickableOff() 4850 gr.actor.UseBoundsOff() 4851 plt.axes_instances[r] = gr 4852 plt.add(gr) 4853 4854 elif plt.axes == 12: 4855 polaxes = vtki.new("PolarAxesActor") 4856 vbb = compute_visible_bounds()[0] 4857 4858 polaxes.SetPolarAxisTitle("radial distance") 4859 polaxes.SetPole(0, 0, vbb[4]) 4860 rd = max(abs(vbb[0]), abs(vbb[2]), abs(vbb[1]), abs(vbb[3])) 4861 polaxes.SetMaximumRadius(rd) 4862 polaxes.AutoSubdividePolarAxisOff() 4863 polaxes.SetNumberOfPolarAxisTicks(10) 4864 polaxes.SetCamera(plt.renderer.GetActiveCamera()) 4865 polaxes.SetPolarLabelFormat("%6.1f") 4866 polaxes.PolarLabelVisibilityOff() # due to bad overlap of labels 4867 4868 polaxes.GetPolarArcsProperty().SetColor(c) 4869 polaxes.GetPolarAxisProperty().SetColor(c) 4870 polaxes.GetPolarAxisTitleTextProperty().SetColor(c) 4871 polaxes.GetPolarAxisLabelTextProperty().SetColor(c) 4872 polaxes.GetLastRadialAxisTextProperty().SetColor(c) 4873 polaxes.GetSecondaryRadialAxesTextProperty().SetColor(c) 4874 polaxes.GetSecondaryRadialAxesProperty().SetColor(c) 4875 polaxes.GetSecondaryPolarArcsProperty().SetColor(c) 4876 4877 polaxes.SetMinimumAngle(0.0) 4878 polaxes.SetMaximumAngle(315.0) 4879 polaxes.SetNumberOfPolarAxisTicks(5) 4880 polaxes.UseBoundsOn() 4881 polaxes.PickableOff() 4882 plt.axes_instances[r] = polaxes 4883 plt.renderer.AddActor(polaxes) 4884 4885 elif plt.axes == 13: 4886 # draws a simple ruler at the bottom of the window 4887 ls = vtki.new("LegendScaleActor") 4888 ls.RightAxisVisibilityOff() 4889 ls.TopAxisVisibilityOff() 4890 ls.LeftAxisVisibilityOff() 4891 ls.LegendVisibilityOff() 4892 ls.SetBottomBorderOffset(50) 4893 ls.GetBottomAxis().SetNumberOfMinorTicks(1) 4894 ls.GetBottomAxis().SetFontFactor(1.1) 4895 ls.GetBottomAxis().GetProperty().SetColor(c) 4896 ls.GetBottomAxis().GetProperty().SetOpacity(1.0) 4897 ls.GetBottomAxis().GetProperty().SetLineWidth(2) 4898 ls.GetBottomAxis().GetLabelTextProperty().SetColor(c) 4899 ls.GetBottomAxis().GetLabelTextProperty().BoldOff() 4900 ls.GetBottomAxis().GetLabelTextProperty().ItalicOff() 4901 pr = ls.GetBottomAxis().GetLabelTextProperty() 4902 pr.SetFontFamily(vtki.VTK_FONT_FILE) 4903 pr.SetFontFile(utils.get_font_path(settings.default_font)) 4904 ls.PickableOff() 4905 # if not plt.renderer.GetActiveCamera().GetParallelProjection(): 4906 # vedo.logger.warning("Axes type 13 should be used with parallel projection") 4907 plt.axes_instances[r] = ls 4908 plt.renderer.AddActor(ls) 4909 4910 elif plt.axes == 14: 4911 try: 4912 cow = vtki.new("CameraOrientationWidget") 4913 cow.SetParentRenderer(plt.renderer) 4914 cow.On() 4915 plt.axes_instances[r] = cow 4916 except ImportError: 4917 vedo.logger.warning("axes mode 14 is unavailable in this vtk version") 4918 4919 else: 4920 e = "Keyword axes type must be in range [0-13]." 4921 e += "Available axes types are:\n\n" 4922 e += "0 = no axes\n" 4923 e += "1 = draw three customizable gray grid walls\n" 4924 e += "2 = show cartesian axes from (0,0,0)\n" 4925 e += "3 = show positive range of cartesian axes from (0,0,0)\n" 4926 e += "4 = show a triad at bottom left\n" 4927 e += "5 = show a cube at bottom left\n" 4928 e += "6 = mark the corners of the bounding box\n" 4929 e += "7 = draw a 3D ruler at each side of the cartesian axes\n" 4930 e += "8 = show the vtkCubeAxesActor object\n" 4931 e += "9 = show the bounding box outline\n" 4932 e += "10 = show three circles representing the maximum bounding box\n" 4933 e += "11 = show a large grid on the x-y plane (use with zoom=8)\n" 4934 e += "12 = show polar axes\n" 4935 e += "13 = draw a simple ruler at the bottom of the window\n" 4936 e += "14 = show the CameraOrientationWidget object" 4937 vedo.logger.warning(e) 4938 4939 if not plt.axes_instances[r]: 4940 plt.axes_instances[r] = True
1197def ScalarBar( 1198 obj, 1199 title="", 1200 pos=(), 1201 size=(80, 400), 1202 font_size=14, 1203 title_yoffset=20, 1204 nlabels=None, 1205 c="k", 1206 horizontal=False, 1207 use_alpha=True, 1208 label_format=":6.3g", 1209) -> Union[vtki.vtkScalarBarActor, None]: 1210 """ 1211 A 2D scalar bar for the specified object. 1212 1213 Arguments: 1214 title : (str) 1215 scalar bar title 1216 pos : (list) 1217 position coordinates of the bottom left corner. 1218 Can also be a pair of (x,y) values in the range [0,1] 1219 to indicate the position of the bottom-left and top-right corners. 1220 size : (float,float) 1221 size of the scalarbar in number of pixels (width, height) 1222 font_size : (float) 1223 size of font for title and numeric labels 1224 title_yoffset : (float) 1225 vertical space offset between title and color scalarbar 1226 nlabels : (int) 1227 number of numeric labels 1228 c : (list) 1229 color of the scalar bar text 1230 horizontal : (bool) 1231 lay the scalarbar horizontally 1232 use_alpha : (bool) 1233 render transparency in the color bar itself 1234 label_format : (str) 1235 c-style format string for numeric labels 1236 1237 Examples: 1238 - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py) 1239 1240 ![](https://user-images.githubusercontent.com/32848391/62940174-4bdc7900-bdd3-11e9-9713-e4f3e2fdab63.png) 1241 """ 1242 1243 if isinstance(obj, (Points, TetMesh, vedo.UnstructuredGrid)): 1244 vtkscalars = obj.dataset.GetPointData().GetScalars() 1245 if vtkscalars is None: 1246 vtkscalars = obj.dataset.GetCellData().GetScalars() 1247 if not vtkscalars: 1248 return None 1249 lut = vtkscalars.GetLookupTable() 1250 if not lut: 1251 lut = obj.mapper.GetLookupTable() 1252 if not lut: 1253 return None 1254 1255 elif isinstance(obj, Volume): 1256 lut = utils.ctf2lut(obj) 1257 1258 elif utils.is_sequence(obj) and len(obj) == 2: 1259 x = np.linspace(obj[0], obj[1], 256) 1260 data = [] 1261 for i in range(256): 1262 rgb = color_map(i, c, 0, 256) 1263 data.append([x[i], rgb]) 1264 lut = build_lut(data) 1265 1266 elif not hasattr(obj, "mapper"): 1267 vedo.logger.error(f"in add_scalarbar(): input is invalid {type(obj)}. Skip.") 1268 return None 1269 1270 else: 1271 return None 1272 1273 c = get_color(c) 1274 sb = vtki.vtkScalarBarActor() 1275 1276 # print("GetLabelFormat", sb.GetLabelFormat()) 1277 label_format = label_format.replace(":", "%-#") 1278 sb.SetLabelFormat(label_format) 1279 1280 sb.SetLookupTable(lut) 1281 sb.SetUseOpacity(use_alpha) 1282 sb.SetDrawFrame(0) 1283 sb.SetDrawBackground(0) 1284 if lut.GetUseBelowRangeColor(): 1285 sb.DrawBelowRangeSwatchOn() 1286 sb.SetBelowRangeAnnotation("") 1287 if lut.GetUseAboveRangeColor(): 1288 sb.DrawAboveRangeSwatchOn() 1289 sb.SetAboveRangeAnnotation("") 1290 if lut.GetNanColor() != (0.5, 0.0, 0.0, 1.0): 1291 sb.DrawNanAnnotationOn() 1292 sb.SetNanAnnotation("nan") 1293 1294 if title: 1295 if "\\" in repr(title): 1296 for r in shapes._reps: 1297 title = title.replace(r[0], r[1]) 1298 titprop = sb.GetTitleTextProperty() 1299 titprop.BoldOn() 1300 titprop.ItalicOff() 1301 titprop.ShadowOff() 1302 titprop.SetColor(c) 1303 titprop.SetVerticalJustificationToTop() 1304 titprop.SetFontSize(font_size) 1305 titprop.SetFontFamily(vtki.VTK_FONT_FILE) 1306 titprop.SetFontFile(utils.get_font_path(vedo.settings.default_font)) 1307 sb.SetTitle(title) 1308 sb.SetVerticalTitleSeparation(title_yoffset) 1309 sb.SetTitleTextProperty(titprop) 1310 1311 sb.SetTextPad(0) 1312 sb.UnconstrainedFontSizeOn() 1313 sb.DrawAnnotationsOn() 1314 sb.DrawTickLabelsOn() 1315 sb.SetMaximumNumberOfColors(256) 1316 if nlabels is not None: 1317 sb.SetNumberOfLabels(nlabels) 1318 1319 if len(pos) == 0 or utils.is_sequence(pos[0]): 1320 if len(pos) == 0: 1321 pos = ((0.87, 0.05), (0.97, 0.5)) 1322 if horizontal: 1323 pos = ((0.5, 0.05), (0.97, 0.15)) 1324 sb.SetTextPositionToPrecedeScalarBar() 1325 if horizontal: 1326 if not nlabels: sb.SetNumberOfLabels(3) 1327 sb.SetOrientationToHorizontal() 1328 sb.SetTextPositionToSucceedScalarBar() 1329 sb.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport() 1330 sb.GetPosition2Coordinate().SetCoordinateSystemToNormalizedViewport() 1331 1332 s = np.array(pos[1]) - np.array(pos[0]) 1333 sb.GetPositionCoordinate().SetValue(pos[0][0], pos[0][1]) 1334 sb.GetPosition2Coordinate().SetValue(s[0], s[1]) # size !!?? 1335 1336 else: 1337 1338 if horizontal: 1339 size = (size[1], size[0]) # swap size 1340 sb.SetPosition(pos[0]-0.7, pos[1]) 1341 if not nlabels: sb.SetNumberOfLabels(3) 1342 sb.SetOrientationToHorizontal() 1343 sb.SetTextPositionToSucceedScalarBar() 1344 else: 1345 sb.SetPosition(pos[0], pos[1]) 1346 if not nlabels: sb.SetNumberOfLabels(7) 1347 sb.SetTextPositionToPrecedeScalarBar() 1348 sb.SetHeight(1) 1349 sb.SetWidth(1) 1350 if size[0] is not None: sb.SetMaximumWidthInPixels(size[0]) 1351 if size[1] is not None: sb.SetMaximumHeightInPixels(size[1]) 1352 1353 sctxt = sb.GetLabelTextProperty() 1354 sctxt.SetFontFamily(vtki.VTK_FONT_FILE) 1355 sctxt.SetFontFile(utils.get_font_path(vedo.settings.default_font)) 1356 sctxt.SetColor(c) 1357 sctxt.SetShadow(0) 1358 sctxt.SetFontSize(font_size) 1359 sb.SetAnnotationTextProperty(sctxt) 1360 sb.PickableOff() 1361 return sb
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:
1365def ScalarBar3D( 1366 obj, 1367 title="", 1368 pos=None, 1369 size=(0, 0), 1370 title_font="", 1371 title_xoffset=-1.2, 1372 title_yoffset=0.0, 1373 title_size=1.5, 1374 title_rotation=0.0, 1375 nlabels=8, 1376 label_font="", 1377 label_size=1, 1378 label_offset=0.375, 1379 label_rotation=0, 1380 label_format="", 1381 italic=0, 1382 c="k", 1383 draw_box=True, 1384 above_text=None, 1385 below_text=None, 1386 nan_text="NaN", 1387 categories=None, 1388) -> Union[Assembly, None]: 1389 """ 1390 Create a 3D scalar bar for the specified object. 1391 1392 Input `obj` input can be: 1393 1394 - a list of numbers, 1395 - a list of two numbers in the form (min, max), 1396 - a Mesh already containing a set of scalars associated to vertices or cells, 1397 - if None the last object in the list of actors will be used. 1398 1399 Arguments: 1400 size : (list) 1401 (thickness, length) of scalarbar 1402 title : (str) 1403 scalar bar title 1404 title_xoffset : (float) 1405 horizontal space btw title and color scalarbar 1406 title_yoffset : (float) 1407 vertical space offset 1408 title_size : (float) 1409 size of title wrt numeric labels 1410 title_rotation : (float) 1411 title rotation in degrees 1412 nlabels : (int) 1413 number of numeric labels 1414 label_font : (str) 1415 font type for labels 1416 label_size : (float) 1417 label scale factor 1418 label_offset : (float) 1419 space btw numeric labels and scale 1420 label_rotation : (float) 1421 label rotation in degrees 1422 draw_box : (bool) 1423 draw a box around the colorbar 1424 categories : (list) 1425 make a categorical scalarbar, 1426 the input list will have the format [value, color, alpha, textlabel] 1427 1428 Examples: 1429 - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py) 1430 - [plot_fxy2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy2.py) 1431 """ 1432 1433 if isinstance(obj, (Points, TetMesh, vedo.UnstructuredGrid)): 1434 lut = obj.mapper.GetLookupTable() 1435 if not lut or lut.GetTable().GetNumberOfTuples() == 0: 1436 # create the most similar to the default 1437 obj.cmap("jet_r") 1438 lut = obj.mapper.GetLookupTable() 1439 vmin, vmax = lut.GetRange() 1440 1441 elif isinstance(obj, Volume): 1442 lut = utils.ctf2lut(obj) 1443 vmin, vmax = lut.GetRange() 1444 1445 else: 1446 vedo.logger.error("in ScalarBar3D(): input must be a vedo object with bounds.") 1447 return None 1448 1449 bns = obj.bounds() 1450 sx, sy = size 1451 if sy == 0 or sy is None: 1452 sy = bns[3] - bns[2] 1453 if sx == 0 or sx is None: 1454 sx = sy / 18 1455 1456 if categories is not None: ################################ 1457 ncats = len(categories) 1458 scale = shapes.Grid([-float(sx) * label_offset, 0, 0], 1459 c=c, alpha=1, s=(sx, sy), res=(1, ncats)) 1460 cols, alphas = [], [] 1461 ticks_pos, ticks_txt = [0.0], [""] 1462 for i, cat in enumerate(categories): 1463 cl = get_color(cat[1]) 1464 cols.append(cl) 1465 if len(cat) > 2: 1466 alphas.append(cat[2]) 1467 else: 1468 alphas.append(1) 1469 if len(cat) > 3: 1470 ticks_txt.append(cat[3]) 1471 else: 1472 ticks_txt.append("") 1473 ticks_pos.append((i + 0.5) / ncats) 1474 ticks_pos.append(1.0) 1475 ticks_txt.append("") 1476 rgba = np.c_[np.array(cols) * 255, np.array(alphas) * 255] 1477 scale.cellcolors = rgba 1478 1479 else: ######################################################## 1480 1481 # build the color scale part 1482 scale = shapes.Grid( 1483 [-float(sx) * label_offset, 0, 0], 1484 c=c, 1485 s=(sx, sy), 1486 res=(1, lut.GetTable().GetNumberOfTuples()), 1487 ) 1488 cscals = np.linspace(vmin, vmax, lut.GetTable().GetNumberOfTuples(), endpoint=True) 1489 1490 if lut.GetScale(): # logarithmic scale 1491 lut10 = vtki.vtkLookupTable() 1492 lut10.DeepCopy(lut) 1493 lut10.SetScaleToLinear() 1494 lut10.Build() 1495 scale.cmap(lut10, cscals, on="cells") 1496 tk = utils.make_ticks(vmin, vmax, nlabels, logscale=True, useformat=label_format) 1497 else: 1498 # for i in range(lut.GetTable().GetNumberOfTuples()): 1499 # print("LUT i=", i, lut.GetTableValue(i)) 1500 scale.cmap(lut, cscals, on="cells") 1501 tk = utils.make_ticks(vmin, vmax, nlabels, logscale=False, useformat=label_format) 1502 ticks_pos, ticks_txt = tk 1503 1504 scale.lw(0).wireframe(False).lighting("off") 1505 1506 scales = [scale] 1507 1508 xbns = scale.xbounds() 1509 1510 lsize = sy / 60 * label_size 1511 1512 tacts = [] 1513 for i, p in enumerate(ticks_pos): 1514 tx = ticks_txt[i] 1515 if i and tx: 1516 # build numeric text 1517 y = (p - 0.5) * sy 1518 if label_rotation: 1519 a = shapes.Text3D( 1520 tx, 1521 s=lsize, 1522 justify="center-top", 1523 c=c, 1524 italic=italic, 1525 font=label_font, 1526 ) 1527 a.rotate_z(label_rotation) 1528 a.pos(sx * label_offset, y, 0) 1529 else: 1530 a = shapes.Text3D( 1531 tx, 1532 pos=[sx * label_offset, y, 0], 1533 s=lsize, 1534 justify="center-left", 1535 c=c, 1536 italic=italic, 1537 font=label_font, 1538 ) 1539 1540 tacts.append(a) 1541 1542 # build ticks 1543 tic = shapes.Line([xbns[1], y, 0], [xbns[1] + sx * label_offset / 4, y, 0], lw=2, c=c) 1544 tacts.append(tic) 1545 1546 # build title 1547 if title: 1548 t = shapes.Text3D( 1549 title, 1550 pos=(0, 0, 0), 1551 s=sy / 50 * title_size, 1552 c=c, 1553 justify="centered-bottom", 1554 italic=italic, 1555 font=title_font, 1556 ) 1557 t.rotate_z(90 + title_rotation) 1558 t.pos(sx * title_xoffset, title_yoffset, 0) 1559 tacts.append(t) 1560 1561 if pos is None: 1562 tsize = 0 1563 if title: 1564 bbt = t.bounds() 1565 tsize = bbt[1] - bbt[0] 1566 pos = (bns[1] + tsize + sx * 1.5, (bns[2] + bns[3]) / 2, bns[4]) 1567 1568 # build below scale 1569 if lut.GetUseBelowRangeColor(): 1570 r, g, b, alfa = lut.GetBelowRangeColor() 1571 sx = float(sx) 1572 sy = float(sy) 1573 brect = shapes.Rectangle( 1574 [-sx * label_offset - sx / 2, -sy / 2 - sx - sx * 0.1, 0], 1575 [-sx * label_offset + sx / 2, -sy / 2 - sx * 0.1, 0], 1576 c=(r, g, b), 1577 alpha=alfa, 1578 ) 1579 brect.lw(1).lc(c).lighting("off") 1580 scales += [brect] 1581 if below_text is None: 1582 below_text = " <" + str(vmin) 1583 if below_text: 1584 if label_rotation: 1585 btx = shapes.Text3D( 1586 below_text, 1587 pos=(0, 0, 0), 1588 s=lsize, 1589 c=c, 1590 justify="center-top", 1591 italic=italic, 1592 font=label_font, 1593 ) 1594 btx.rotate_z(label_rotation) 1595 else: 1596 btx = shapes.Text3D( 1597 below_text, 1598 pos=(0, 0, 0), 1599 s=lsize, 1600 c=c, 1601 justify="center-left", 1602 italic=italic, 1603 font=label_font, 1604 ) 1605 1606 btx.pos(sx * label_offset, -sy / 2 - sx * 0.66, 0) 1607 tacts.append(btx) 1608 1609 # build above scale 1610 if lut.GetUseAboveRangeColor(): 1611 r, g, b, alfa = lut.GetAboveRangeColor() 1612 arect = shapes.Rectangle( 1613 [-sx * label_offset - sx / 2, sy / 2 + sx * 0.1, 0], 1614 [-sx * label_offset + sx / 2, sy / 2 + sx + sx * 0.1, 0], 1615 c=(r, g, b), 1616 alpha=alfa, 1617 ) 1618 arect.lw(1).lc(c).lighting("off") 1619 scales += [arect] 1620 if above_text is None: 1621 above_text = " >" + str(vmax) 1622 if above_text: 1623 if label_rotation: 1624 atx = shapes.Text3D( 1625 above_text, 1626 pos=(0, 0, 0), 1627 s=lsize, 1628 c=c, 1629 justify="center-top", 1630 italic=italic, 1631 font=label_font, 1632 ) 1633 atx.rotate_z(label_rotation) 1634 else: 1635 atx = shapes.Text3D( 1636 above_text, 1637 pos=(0, 0, 0), 1638 s=lsize, 1639 c=c, 1640 justify="center-left", 1641 italic=italic, 1642 font=label_font, 1643 ) 1644 1645 atx.pos(sx * label_offset, sy / 2 + sx * 0.66, 0) 1646 tacts.append(atx) 1647 1648 # build NaN scale 1649 if lut.GetNanColor() != (0.5, 0.0, 0.0, 1.0): 1650 nanshift = sx * 0.1 1651 if brect: 1652 nanshift += sx 1653 r, g, b, alfa = lut.GetNanColor() 1654 nanrect = shapes.Rectangle( 1655 [-sx * label_offset - sx / 2, -sy / 2 - sx - sx * 0.1 - nanshift, 0], 1656 [-sx * label_offset + sx / 2, -sy / 2 - sx * 0.1 - nanshift, 0], 1657 c=(r, g, b), 1658 alpha=alfa, 1659 ) 1660 nanrect.lw(1).lc(c).lighting("off") 1661 scales += [nanrect] 1662 if label_rotation: 1663 nantx = shapes.Text3D( 1664 nan_text, 1665 pos=(0, 0, 0), 1666 s=lsize, 1667 c=c, 1668 justify="center-left", 1669 italic=italic, 1670 font=label_font, 1671 ) 1672 nantx.rotate_z(label_rotation) 1673 else: 1674 nantx = shapes.Text3D( 1675 nan_text, 1676 pos=(0, 0, 0), 1677 s=lsize, 1678 c=c, 1679 justify="center-left", 1680 italic=italic, 1681 font=label_font, 1682 ) 1683 nantx.pos(sx * label_offset, -sy / 2 - sx * 0.66 - nanshift, 0) 1684 tacts.append(nantx) 1685 1686 if draw_box: 1687 tacts.append(scale.box().lw(1).c(c)) 1688 1689 for m in tacts + scales: 1690 m.shift(pos) 1691 m.actor.PickableOff() 1692 m.properties.LightingOff() 1693 1694 asse = Assembly(scales + tacts) 1695 1696 # asse.transform = LinearTransform().shift(pos) 1697 1698 bb = asse.GetBounds() 1699 # print("ScalarBar3D pos",pos, bb) 1700 # asse.SetOrigin(pos) 1701 1702 asse.SetOrigin(bb[0], bb[2], bb[4]) 1703 # asse.SetOrigin(bb[0],0,0) #in pyplot line 1312 1704 1705 asse.PickableOff() 1706 asse.UseBoundsOff() 1707 asse.name = "ScalarBar3D" 1708 return asse
Create a 3D scalar bar for the specified object.
Input obj
input can be:
- a list of numbers,
- a list of two numbers in the form (min, max),
- a Mesh already containing a set of scalars associated to vertices or cells,
- if None the last object in the list of actors will be used.
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
- 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:
1712class Slider2D(SliderWidget): 1713 """ 1714 Add a slider which can call an external custom function. 1715 """ 1716 1717 def __init__( 1718 self, 1719 sliderfunc, 1720 xmin, 1721 xmax, 1722 value=None, 1723 pos=4, 1724 title="", 1725 font="Calco", 1726 title_size=1, 1727 c="k", 1728 alpha=1, 1729 show_value=True, 1730 delayed=False, 1731 **options, 1732 ): 1733 """ 1734 Add a slider which can call an external custom function. 1735 Set any value as float to increase the number of significant digits above the slider. 1736 1737 Use `play()` to start an animation between the current slider value and the last value. 1738 1739 Arguments: 1740 sliderfunc : (function) 1741 external function to be called by the widget 1742 xmin : (float) 1743 lower value of the slider 1744 xmax : (float) 1745 upper value 1746 value : (float) 1747 current value 1748 pos : (list, str) 1749 position corner number: horizontal [1-5] or vertical [11-15] 1750 it can also be specified by corners coordinates [(x1,y1), (x2,y2)] 1751 and also by a string descriptor (eg. "bottom-left") 1752 title : (str) 1753 title text 1754 font : (str) 1755 title font face. Check [available fonts here](https://vedo.embl.es/fonts). 1756 title_size : (float) 1757 title text scale [1.0] 1758 show_value : (bool) 1759 if True current value is shown 1760 delayed : (bool) 1761 if True the callback is delayed until when the mouse button is released 1762 alpha : (float) 1763 opacity of the scalar bar texts 1764 slider_length : (float) 1765 slider length 1766 slider_width : (float) 1767 slider width 1768 end_cap_length : (float) 1769 length of the end cap 1770 end_cap_width : (float) 1771 width of the end cap 1772 tube_width : (float) 1773 width of the tube 1774 title_height : (float) 1775 height of the title 1776 tformat : (str) 1777 format of the title 1778 1779 Examples: 1780 - [sliders1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders1.py) 1781 - [sliders2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders2.py) 1782 1783 ![](https://user-images.githubusercontent.com/32848391/50738848-be033480-11d8-11e9-9b1a-c13105423a79.jpg) 1784 """ 1785 slider_length = options.pop("slider_length", 0.015) 1786 slider_width = options.pop("slider_width", 0.025) 1787 end_cap_length= options.pop("end_cap_length", 0.0015) 1788 end_cap_width = options.pop("end_cap_width", 0.0125) 1789 tube_width = options.pop("tube_width", 0.0075) 1790 title_height = options.pop("title_height", 0.025) 1791 tformat = options.pop("tformat", None) 1792 1793 if options: 1794 vedo.logger.warning(f"in Slider2D unknown option(s): {options}") 1795 1796 c = get_color(c) 1797 1798 if value is None or value < xmin: 1799 value = xmin 1800 1801 slider_rep = vtki.new("SliderRepresentation2D") 1802 slider_rep.SetMinimumValue(xmin) 1803 slider_rep.SetMaximumValue(xmax) 1804 slider_rep.SetValue(value) 1805 slider_rep.SetSliderLength(slider_length) 1806 slider_rep.SetSliderWidth(slider_width) 1807 slider_rep.SetEndCapLength(end_cap_length) 1808 slider_rep.SetEndCapWidth(end_cap_width) 1809 slider_rep.SetTubeWidth(tube_width) 1810 slider_rep.GetPoint1Coordinate().SetCoordinateSystemToNormalizedDisplay() 1811 slider_rep.GetPoint2Coordinate().SetCoordinateSystemToNormalizedDisplay() 1812 1813 if isinstance(pos, str): 1814 if "top" in pos: 1815 if "left" in pos: 1816 if "vert" in pos: 1817 pos = 11 1818 else: 1819 pos = 1 1820 elif "right" in pos: 1821 if "vert" in pos: 1822 pos = 12 1823 else: 1824 pos = 2 1825 elif "bott" in pos: 1826 if "left" in pos: 1827 if "vert" in pos: 1828 pos = 13 1829 else: 1830 pos = 3 1831 elif "right" in pos: 1832 if "vert" in pos: 1833 if "span" in pos: 1834 pos = 15 1835 else: 1836 pos = 14 1837 else: 1838 pos = 4 1839 elif "span" in pos: 1840 pos = 5 1841 1842 if utils.is_sequence(pos): 1843 slider_rep.GetPoint1Coordinate().SetValue(pos[0][0], pos[0][1]) 1844 slider_rep.GetPoint2Coordinate().SetValue(pos[1][0], pos[1][1]) 1845 elif pos == 1: # top-left horizontal 1846 slider_rep.GetPoint1Coordinate().SetValue(0.04, 0.93) 1847 slider_rep.GetPoint2Coordinate().SetValue(0.45, 0.93) 1848 elif pos == 2: 1849 slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.93) 1850 slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.93) 1851 elif pos == 3: 1852 slider_rep.GetPoint1Coordinate().SetValue(0.05, 0.06) 1853 slider_rep.GetPoint2Coordinate().SetValue(0.45, 0.06) 1854 elif pos == 4: # bottom-right 1855 slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.06) 1856 slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06) 1857 elif pos == 5: # bottom span horizontal 1858 slider_rep.GetPoint1Coordinate().SetValue(0.04, 0.06) 1859 slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06) 1860 elif pos == 11: # top-left vertical 1861 slider_rep.GetPoint1Coordinate().SetValue(0.065, 0.54) 1862 slider_rep.GetPoint2Coordinate().SetValue(0.065, 0.9) 1863 elif pos == 12: 1864 slider_rep.GetPoint1Coordinate().SetValue(0.94, 0.54) 1865 slider_rep.GetPoint2Coordinate().SetValue(0.94, 0.9) 1866 elif pos == 13: 1867 slider_rep.GetPoint1Coordinate().SetValue(0.065, 0.1) 1868 slider_rep.GetPoint2Coordinate().SetValue(0.065, 0.54) 1869 elif pos == 14: # bottom-right vertical 1870 slider_rep.GetPoint1Coordinate().SetValue(0.94, 0.1) 1871 slider_rep.GetPoint2Coordinate().SetValue(0.94, 0.54) 1872 elif pos == 15: # right margin vertical 1873 slider_rep.GetPoint1Coordinate().SetValue(0.95, 0.1) 1874 slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.9) 1875 else: # bottom-right 1876 slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.06) 1877 slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06) 1878 1879 if show_value: 1880 if tformat is None: 1881 if isinstance(xmin, int) and isinstance(xmax, int) and isinstance(value, int): 1882 tformat = "%0.0f" 1883 else: 1884 tformat = "%0.2f" 1885 1886 slider_rep.SetLabelFormat(tformat) # default is '%0.3g' 1887 slider_rep.GetLabelProperty().SetShadow(0) 1888 slider_rep.GetLabelProperty().SetBold(0) 1889 slider_rep.GetLabelProperty().SetOpacity(alpha) 1890 slider_rep.GetLabelProperty().SetColor(c) 1891 if isinstance(pos, int) and pos > 10: 1892 slider_rep.GetLabelProperty().SetOrientation(90) 1893 else: 1894 slider_rep.ShowSliderLabelOff() 1895 slider_rep.GetTubeProperty().SetColor(c) 1896 slider_rep.GetTubeProperty().SetOpacity(0.75) 1897 slider_rep.GetSliderProperty().SetColor(c) 1898 slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c))) 1899 slider_rep.GetCapProperty().SetColor(c) 1900 1901 slider_rep.SetTitleHeight(title_height * title_size) 1902 slider_rep.GetTitleProperty().SetShadow(0) 1903 slider_rep.GetTitleProperty().SetColor(c) 1904 slider_rep.GetTitleProperty().SetOpacity(alpha) 1905 slider_rep.GetTitleProperty().SetBold(0) 1906 if font.lower() == "courier": 1907 slider_rep.GetTitleProperty().SetFontFamilyToCourier() 1908 elif font.lower() == "times": 1909 slider_rep.GetTitleProperty().SetFontFamilyToTimes() 1910 elif font.lower() == "arial": 1911 slider_rep.GetTitleProperty().SetFontFamilyToArial() 1912 else: 1913 if font == "": 1914 font = utils.get_font_path(settings.default_font) 1915 else: 1916 font = utils.get_font_path(font) 1917 slider_rep.GetTitleProperty().SetFontFamily(vtki.VTK_FONT_FILE) 1918 slider_rep.GetLabelProperty().SetFontFamily(vtki.VTK_FONT_FILE) 1919 slider_rep.GetTitleProperty().SetFontFile(font) 1920 slider_rep.GetLabelProperty().SetFontFile(font) 1921 1922 if title: 1923 slider_rep.SetTitleText(title) 1924 if not utils.is_sequence(pos): 1925 if isinstance(pos, int) and pos > 10: 1926 slider_rep.GetTitleProperty().SetOrientation(90) 1927 else: 1928 if abs(pos[0][0] - pos[1][0]) < 0.1: 1929 slider_rep.GetTitleProperty().SetOrientation(90) 1930 1931 super().__init__() 1932 1933 self.SetAnimationModeToJump() 1934 self.SetRepresentation(slider_rep) 1935 if delayed: 1936 self.AddObserver("EndInteractionEvent", sliderfunc) 1937 else: 1938 self.AddObserver("InteractionEvent", sliderfunc)
Add a slider which can call an external custom function.
1717 def __init__( 1718 self, 1719 sliderfunc, 1720 xmin, 1721 xmax, 1722 value=None, 1723 pos=4, 1724 title="", 1725 font="Calco", 1726 title_size=1, 1727 c="k", 1728 alpha=1, 1729 show_value=True, 1730 delayed=False, 1731 **options, 1732 ): 1733 """ 1734 Add a slider which can call an external custom function. 1735 Set any value as float to increase the number of significant digits above the slider. 1736 1737 Use `play()` to start an animation between the current slider value and the last value. 1738 1739 Arguments: 1740 sliderfunc : (function) 1741 external function to be called by the widget 1742 xmin : (float) 1743 lower value of the slider 1744 xmax : (float) 1745 upper value 1746 value : (float) 1747 current value 1748 pos : (list, str) 1749 position corner number: horizontal [1-5] or vertical [11-15] 1750 it can also be specified by corners coordinates [(x1,y1), (x2,y2)] 1751 and also by a string descriptor (eg. "bottom-left") 1752 title : (str) 1753 title text 1754 font : (str) 1755 title font face. Check [available fonts here](https://vedo.embl.es/fonts). 1756 title_size : (float) 1757 title text scale [1.0] 1758 show_value : (bool) 1759 if True current value is shown 1760 delayed : (bool) 1761 if True the callback is delayed until when the mouse button is released 1762 alpha : (float) 1763 opacity of the scalar bar texts 1764 slider_length : (float) 1765 slider length 1766 slider_width : (float) 1767 slider width 1768 end_cap_length : (float) 1769 length of the end cap 1770 end_cap_width : (float) 1771 width of the end cap 1772 tube_width : (float) 1773 width of the tube 1774 title_height : (float) 1775 height of the title 1776 tformat : (str) 1777 format of the title 1778 1779 Examples: 1780 - [sliders1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders1.py) 1781 - [sliders2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders2.py) 1782 1783 ![](https://user-images.githubusercontent.com/32848391/50738848-be033480-11d8-11e9-9b1a-c13105423a79.jpg) 1784 """ 1785 slider_length = options.pop("slider_length", 0.015) 1786 slider_width = options.pop("slider_width", 0.025) 1787 end_cap_length= options.pop("end_cap_length", 0.0015) 1788 end_cap_width = options.pop("end_cap_width", 0.0125) 1789 tube_width = options.pop("tube_width", 0.0075) 1790 title_height = options.pop("title_height", 0.025) 1791 tformat = options.pop("tformat", None) 1792 1793 if options: 1794 vedo.logger.warning(f"in Slider2D unknown option(s): {options}") 1795 1796 c = get_color(c) 1797 1798 if value is None or value < xmin: 1799 value = xmin 1800 1801 slider_rep = vtki.new("SliderRepresentation2D") 1802 slider_rep.SetMinimumValue(xmin) 1803 slider_rep.SetMaximumValue(xmax) 1804 slider_rep.SetValue(value) 1805 slider_rep.SetSliderLength(slider_length) 1806 slider_rep.SetSliderWidth(slider_width) 1807 slider_rep.SetEndCapLength(end_cap_length) 1808 slider_rep.SetEndCapWidth(end_cap_width) 1809 slider_rep.SetTubeWidth(tube_width) 1810 slider_rep.GetPoint1Coordinate().SetCoordinateSystemToNormalizedDisplay() 1811 slider_rep.GetPoint2Coordinate().SetCoordinateSystemToNormalizedDisplay() 1812 1813 if isinstance(pos, str): 1814 if "top" in pos: 1815 if "left" in pos: 1816 if "vert" in pos: 1817 pos = 11 1818 else: 1819 pos = 1 1820 elif "right" in pos: 1821 if "vert" in pos: 1822 pos = 12 1823 else: 1824 pos = 2 1825 elif "bott" in pos: 1826 if "left" in pos: 1827 if "vert" in pos: 1828 pos = 13 1829 else: 1830 pos = 3 1831 elif "right" in pos: 1832 if "vert" in pos: 1833 if "span" in pos: 1834 pos = 15 1835 else: 1836 pos = 14 1837 else: 1838 pos = 4 1839 elif "span" in pos: 1840 pos = 5 1841 1842 if utils.is_sequence(pos): 1843 slider_rep.GetPoint1Coordinate().SetValue(pos[0][0], pos[0][1]) 1844 slider_rep.GetPoint2Coordinate().SetValue(pos[1][0], pos[1][1]) 1845 elif pos == 1: # top-left horizontal 1846 slider_rep.GetPoint1Coordinate().SetValue(0.04, 0.93) 1847 slider_rep.GetPoint2Coordinate().SetValue(0.45, 0.93) 1848 elif pos == 2: 1849 slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.93) 1850 slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.93) 1851 elif pos == 3: 1852 slider_rep.GetPoint1Coordinate().SetValue(0.05, 0.06) 1853 slider_rep.GetPoint2Coordinate().SetValue(0.45, 0.06) 1854 elif pos == 4: # bottom-right 1855 slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.06) 1856 slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06) 1857 elif pos == 5: # bottom span horizontal 1858 slider_rep.GetPoint1Coordinate().SetValue(0.04, 0.06) 1859 slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06) 1860 elif pos == 11: # top-left vertical 1861 slider_rep.GetPoint1Coordinate().SetValue(0.065, 0.54) 1862 slider_rep.GetPoint2Coordinate().SetValue(0.065, 0.9) 1863 elif pos == 12: 1864 slider_rep.GetPoint1Coordinate().SetValue(0.94, 0.54) 1865 slider_rep.GetPoint2Coordinate().SetValue(0.94, 0.9) 1866 elif pos == 13: 1867 slider_rep.GetPoint1Coordinate().SetValue(0.065, 0.1) 1868 slider_rep.GetPoint2Coordinate().SetValue(0.065, 0.54) 1869 elif pos == 14: # bottom-right vertical 1870 slider_rep.GetPoint1Coordinate().SetValue(0.94, 0.1) 1871 slider_rep.GetPoint2Coordinate().SetValue(0.94, 0.54) 1872 elif pos == 15: # right margin vertical 1873 slider_rep.GetPoint1Coordinate().SetValue(0.95, 0.1) 1874 slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.9) 1875 else: # bottom-right 1876 slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.06) 1877 slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06) 1878 1879 if show_value: 1880 if tformat is None: 1881 if isinstance(xmin, int) and isinstance(xmax, int) and isinstance(value, int): 1882 tformat = "%0.0f" 1883 else: 1884 tformat = "%0.2f" 1885 1886 slider_rep.SetLabelFormat(tformat) # default is '%0.3g' 1887 slider_rep.GetLabelProperty().SetShadow(0) 1888 slider_rep.GetLabelProperty().SetBold(0) 1889 slider_rep.GetLabelProperty().SetOpacity(alpha) 1890 slider_rep.GetLabelProperty().SetColor(c) 1891 if isinstance(pos, int) and pos > 10: 1892 slider_rep.GetLabelProperty().SetOrientation(90) 1893 else: 1894 slider_rep.ShowSliderLabelOff() 1895 slider_rep.GetTubeProperty().SetColor(c) 1896 slider_rep.GetTubeProperty().SetOpacity(0.75) 1897 slider_rep.GetSliderProperty().SetColor(c) 1898 slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c))) 1899 slider_rep.GetCapProperty().SetColor(c) 1900 1901 slider_rep.SetTitleHeight(title_height * title_size) 1902 slider_rep.GetTitleProperty().SetShadow(0) 1903 slider_rep.GetTitleProperty().SetColor(c) 1904 slider_rep.GetTitleProperty().SetOpacity(alpha) 1905 slider_rep.GetTitleProperty().SetBold(0) 1906 if font.lower() == "courier": 1907 slider_rep.GetTitleProperty().SetFontFamilyToCourier() 1908 elif font.lower() == "times": 1909 slider_rep.GetTitleProperty().SetFontFamilyToTimes() 1910 elif font.lower() == "arial": 1911 slider_rep.GetTitleProperty().SetFontFamilyToArial() 1912 else: 1913 if font == "": 1914 font = utils.get_font_path(settings.default_font) 1915 else: 1916 font = utils.get_font_path(font) 1917 slider_rep.GetTitleProperty().SetFontFamily(vtki.VTK_FONT_FILE) 1918 slider_rep.GetLabelProperty().SetFontFamily(vtki.VTK_FONT_FILE) 1919 slider_rep.GetTitleProperty().SetFontFile(font) 1920 slider_rep.GetLabelProperty().SetFontFile(font) 1921 1922 if title: 1923 slider_rep.SetTitleText(title) 1924 if not utils.is_sequence(pos): 1925 if isinstance(pos, int) and pos > 10: 1926 slider_rep.GetTitleProperty().SetOrientation(90) 1927 else: 1928 if abs(pos[0][0] - pos[1][0]) < 0.1: 1929 slider_rep.GetTitleProperty().SetOrientation(90) 1930 1931 super().__init__() 1932 1933 self.SetAnimationModeToJump() 1934 self.SetRepresentation(slider_rep) 1935 if delayed: 1936 self.AddObserver("EndInteractionEvent", sliderfunc) 1937 else: 1938 self.AddObserver("InteractionEvent", sliderfunc)
Add a slider which can call an external custom function. Set any value as float to increase the number of significant digits above the slider.
Use play()
to start an animation between the current slider value and the last value.
Arguments:
- sliderfunc : (function) external function to be called by the widget
- xmin : (float) lower value of the slider
- xmax : (float) upper value
- value : (float) current value
- pos : (list, str) position corner number: horizontal [1-5] or vertical [11-15] it can also be specified by corners coordinates [(x1,y1), (x2,y2)] and also by a string descriptor (eg. "bottom-left")
- title : (str) title text
- font : (str) title font face. Check available fonts here.
- title_size : (float) title text scale [1.0]
- show_value : (bool) if True current value is shown
- delayed : (bool) if True the callback is delayed until when the mouse button is released
- alpha : (float) opacity of the scalar bar texts
- slider_length : (float) slider length
- slider_width : (float) slider width
- end_cap_length : (float) length of the end cap
- end_cap_width : (float) width of the end cap
- tube_width : (float) width of the tube
- title_height : (float) height of the title
- tformat : (str) format of the title
Examples:
Inherited Members
1942class Slider3D(SliderWidget): 1943 """ 1944 Add a 3D slider which can call an external custom function. 1945 """ 1946 1947 def __init__( 1948 self, 1949 sliderfunc, 1950 pos1, 1951 pos2, 1952 xmin, 1953 xmax, 1954 value=None, 1955 s=0.03, 1956 t=1, 1957 title="", 1958 rotation=0, 1959 c=None, 1960 show_value=True, 1961 ): 1962 """ 1963 Add a 3D slider which can call an external custom function. 1964 1965 Arguments: 1966 sliderfunc : (function) 1967 external function to be called by the widget 1968 pos1 : (list) 1969 first position 3D coordinates 1970 pos2 : (list) 1971 second position 3D coordinates 1972 xmin : (float) 1973 lower value 1974 xmax : (float) 1975 upper value 1976 value : (float) 1977 initial value 1978 s : (float) 1979 label scaling factor 1980 t : (float) 1981 tube scaling factor 1982 title : (str) 1983 title text 1984 c : (color) 1985 slider color 1986 rotation : (float) 1987 title rotation around slider axis 1988 show_value : (bool) 1989 if True current value is shown on top of the slider 1990 1991 Examples: 1992 - [sliders3d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders3d.py) 1993 """ 1994 c = get_color(c) 1995 1996 if value is None or value < xmin: 1997 value = xmin 1998 1999 slider_rep = vtki.new("SliderRepresentation3D") 2000 slider_rep.SetMinimumValue(xmin) 2001 slider_rep.SetMaximumValue(xmax) 2002 slider_rep.SetValue(value) 2003 2004 slider_rep.GetPoint1Coordinate().SetCoordinateSystemToWorld() 2005 slider_rep.GetPoint2Coordinate().SetCoordinateSystemToWorld() 2006 slider_rep.GetPoint1Coordinate().SetValue(pos2) 2007 slider_rep.GetPoint2Coordinate().SetValue(pos1) 2008 2009 # slider_rep.SetPoint1InWorldCoordinates(pos2[0], pos2[1], pos2[2]) 2010 # slider_rep.SetPoint2InWorldCoordinates(pos1[0], pos1[1], pos1[2]) 2011 2012 slider_rep.SetSliderWidth(0.03 * t) 2013 slider_rep.SetTubeWidth(0.01 * t) 2014 slider_rep.SetSliderLength(0.04 * t) 2015 slider_rep.SetSliderShapeToCylinder() 2016 slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c))) 2017 slider_rep.GetSliderProperty().SetColor(np.array(c) / 1.5) 2018 slider_rep.GetCapProperty().SetOpacity(0) 2019 slider_rep.SetRotation(rotation) 2020 2021 if not show_value: 2022 slider_rep.ShowSliderLabelOff() 2023 2024 slider_rep.SetTitleText(title) 2025 slider_rep.SetTitleHeight(s * t) 2026 slider_rep.SetLabelHeight(s * t * 0.85) 2027 2028 slider_rep.GetTubeProperty().SetColor(c) 2029 2030 super().__init__() 2031 2032 self.SetRepresentation(slider_rep) 2033 self.SetAnimationModeToJump() 2034 self.AddObserver("InteractionEvent", sliderfunc)
Add a 3D slider which can call an external custom function.
1947 def __init__( 1948 self, 1949 sliderfunc, 1950 pos1, 1951 pos2, 1952 xmin, 1953 xmax, 1954 value=None, 1955 s=0.03, 1956 t=1, 1957 title="", 1958 rotation=0, 1959 c=None, 1960 show_value=True, 1961 ): 1962 """ 1963 Add a 3D slider which can call an external custom function. 1964 1965 Arguments: 1966 sliderfunc : (function) 1967 external function to be called by the widget 1968 pos1 : (list) 1969 first position 3D coordinates 1970 pos2 : (list) 1971 second position 3D coordinates 1972 xmin : (float) 1973 lower value 1974 xmax : (float) 1975 upper value 1976 value : (float) 1977 initial value 1978 s : (float) 1979 label scaling factor 1980 t : (float) 1981 tube scaling factor 1982 title : (str) 1983 title text 1984 c : (color) 1985 slider color 1986 rotation : (float) 1987 title rotation around slider axis 1988 show_value : (bool) 1989 if True current value is shown on top of the slider 1990 1991 Examples: 1992 - [sliders3d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders3d.py) 1993 """ 1994 c = get_color(c) 1995 1996 if value is None or value < xmin: 1997 value = xmin 1998 1999 slider_rep = vtki.new("SliderRepresentation3D") 2000 slider_rep.SetMinimumValue(xmin) 2001 slider_rep.SetMaximumValue(xmax) 2002 slider_rep.SetValue(value) 2003 2004 slider_rep.GetPoint1Coordinate().SetCoordinateSystemToWorld() 2005 slider_rep.GetPoint2Coordinate().SetCoordinateSystemToWorld() 2006 slider_rep.GetPoint1Coordinate().SetValue(pos2) 2007 slider_rep.GetPoint2Coordinate().SetValue(pos1) 2008 2009 # slider_rep.SetPoint1InWorldCoordinates(pos2[0], pos2[1], pos2[2]) 2010 # slider_rep.SetPoint2InWorldCoordinates(pos1[0], pos1[1], pos1[2]) 2011 2012 slider_rep.SetSliderWidth(0.03 * t) 2013 slider_rep.SetTubeWidth(0.01 * t) 2014 slider_rep.SetSliderLength(0.04 * t) 2015 slider_rep.SetSliderShapeToCylinder() 2016 slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c))) 2017 slider_rep.GetSliderProperty().SetColor(np.array(c) / 1.5) 2018 slider_rep.GetCapProperty().SetOpacity(0) 2019 slider_rep.SetRotation(rotation) 2020 2021 if not show_value: 2022 slider_rep.ShowSliderLabelOff() 2023 2024 slider_rep.SetTitleText(title) 2025 slider_rep.SetTitleHeight(s * t) 2026 slider_rep.SetLabelHeight(s * t * 0.85) 2027 2028 slider_rep.GetTubeProperty().SetColor(c) 2029 2030 super().__init__() 2031 2032 self.SetRepresentation(slider_rep) 2033 self.SetAnimationModeToJump() 2034 self.AddObserver("InteractionEvent", sliderfunc)
Add a 3D slider which can call an external custom function.
Arguments:
- sliderfunc : (function) external function to be called by the widget
- pos1 : (list) first position 3D coordinates
- pos2 : (list) second position 3D coordinates
- xmin : (float) lower value
- xmax : (float) upper value
- value : (float) initial value
- s : (float) label scaling factor
- t : (float) tube scaling factor
- title : (str) title text
- c : (color) slider color
- rotation : (float) title rotation around slider axis
- show_value : (bool) if True current value is shown on top of the slider
Examples:
Inherited Members
2704class Icon(vtki.vtkOrientationMarkerWidget): 2705 """ 2706 Add an inset icon mesh into the renderer. 2707 """ 2708 2709 def __init__(self, mesh, pos=3, size=0.08): 2710 """ 2711 Arguments: 2712 pos : (list, int) 2713 icon position in the range [1-4] indicating one of the 4 corners, 2714 or it can be a tuple (x,y) as a fraction of the renderer size. 2715 size : (float) 2716 size of the icon space as fraction of the window size. 2717 2718 Examples: 2719 - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py) 2720 """ 2721 super().__init__() 2722 2723 try: 2724 self.SetOrientationMarker(mesh.actor) 2725 except AttributeError: 2726 self.SetOrientationMarker(mesh) 2727 2728 if utils.is_sequence(pos): 2729 self.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size) 2730 else: 2731 if pos < 2: 2732 self.SetViewport(0, 1 - 2 * size, size * 2, 1) 2733 elif pos == 2: 2734 self.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1) 2735 elif pos == 3: 2736 self.SetViewport(0, 0, size * 2, size * 2) 2737 elif pos == 4: 2738 self.SetViewport(1 - 2 * size, 0, 1, size * 2)
Add an inset icon mesh into the renderer.
2709 def __init__(self, mesh, pos=3, size=0.08): 2710 """ 2711 Arguments: 2712 pos : (list, int) 2713 icon position in the range [1-4] indicating one of the 4 corners, 2714 or it can be a tuple (x,y) as a fraction of the renderer size. 2715 size : (float) 2716 size of the icon space as fraction of the window size. 2717 2718 Examples: 2719 - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py) 2720 """ 2721 super().__init__() 2722 2723 try: 2724 self.SetOrientationMarker(mesh.actor) 2725 except AttributeError: 2726 self.SetOrientationMarker(mesh) 2727 2728 if utils.is_sequence(pos): 2729 self.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size) 2730 else: 2731 if pos < 2: 2732 self.SetViewport(0, 1 - 2 * size, size * 2, 1) 2733 elif pos == 2: 2734 self.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1) 2735 elif pos == 3: 2736 self.SetViewport(0, 0, size * 2, size * 2) 2737 elif pos == 4: 2738 self.SetViewport(1 - 2 * size, 0, 1, size * 2)
Arguments:
- pos : (list, int) icon position in the range [1-4] indicating one of the 4 corners, or it can be a tuple (x,y) as a fraction of the renderer size.
- size : (float) size of the icon space as fraction of the window size.
Examples:
214class LegendBox(shapes.TextBase, vtki.vtkLegendBoxActor): 215 """ 216 Create a 2D legend box. 217 """ 218 219 def __init__( 220 self, 221 entries=(), 222 nmax=12, 223 c=None, 224 font="", 225 width=0.18, 226 height=None, 227 padding=2, 228 bg="k8", 229 alpha=0.25, 230 pos="top-right", 231 markers=None, 232 ): 233 """ 234 Create a 2D legend box for the list of specified objects. 235 236 Arguments: 237 nmax : (int) 238 max number of legend entries 239 c : (color) 240 text color, leave as None to pick the mesh color automatically 241 font : (str) 242 Check [available fonts here](https://vedo.embl.es/fonts) 243 width : (float) 244 width of the box as fraction of the window width 245 height : (float) 246 height of the box as fraction of the window height 247 padding : (int) 248 padding space in units of pixels 249 bg : (color) 250 background color of the box 251 alpha: (float) 252 opacity of the box 253 pos : (str, list) 254 position of the box, can be either a string or a (x,y) screen position in range [0,1] 255 256 Examples: 257 - [legendbox.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/legendbox.py) 258 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 259 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 260 261 ![](https://vedo.embl.es/images/other/flag_labels.png) 262 """ 263 super().__init__() 264 265 self.name = "LegendBox" 266 self.entries = entries[:nmax] 267 self.properties = self.GetEntryTextProperty() 268 269 n = 0 270 texts = [] 271 for e in self.entries: 272 ename = e.name 273 if "legend" in e.info.keys(): 274 if not e.info["legend"]: 275 ename = "" 276 else: 277 ename = str(e.info["legend"]) 278 if ename: 279 n += 1 280 texts.append(ename) 281 self.SetNumberOfEntries(n) 282 283 if not n: 284 return 285 286 self.ScalarVisibilityOff() 287 self.PickableOff() 288 self.SetPadding(padding) 289 290 self.properties.ShadowOff() 291 self.properties.BoldOff() 292 293 # self.properties.SetJustificationToLeft() # no effect 294 # self.properties.SetVerticalJustificationToTop() 295 296 if not font: 297 font = settings.default_font 298 299 self.font(font) 300 301 n = 0 302 for i in range(len(self.entries)): 303 ti = texts[i] 304 if not ti: 305 continue 306 e = entries[i] 307 if c is None: 308 col = e.properties.GetColor() 309 if col == (1, 1, 1): 310 col = (0.2, 0.2, 0.2) 311 else: 312 col = get_color(c) 313 if markers is None: # default 314 poly = e.dataset 315 else: 316 marker = markers[i] if utils.is_sequence(markers) else markers 317 if isinstance(marker, Points): 318 poly = marker.clone(deep=False).normalize().shift(0, 1, 0).dataset 319 else: # assume string marker 320 poly = vedo.shapes.Marker(marker, s=1).shift(0, 1, 0).dataset 321 322 self.SetEntry(n, poly, ti, col) 323 n += 1 324 325 self.SetWidth(width) 326 if height is None: 327 self.SetHeight(width / 3.0 * n) 328 else: 329 self.SetHeight(height) 330 331 self.pos(pos) 332 333 if alpha: 334 self.UseBackgroundOn() 335 self.SetBackgroundColor(get_color(bg)) 336 self.SetBackgroundOpacity(alpha) 337 else: 338 self.UseBackgroundOff() 339 self.LockBorderOn() 340 341 @property 342 def width(self): 343 """Return the width of the legend box.""" 344 return self.GetWidth() 345 346 @property 347 def height(self): 348 """Return the height of the legend box.""" 349 return self.GetHeight() 350 351 def pos(self, pos): 352 """Set the position of the legend box.""" 353 sx, sy = 1 - self.GetWidth(), 1 - self.GetHeight() 354 if pos == 1 or ("top" in pos and "left" in pos): 355 self.GetPositionCoordinate().SetValue(0, sy) 356 elif pos == 2 or ("top" in pos and "right" in pos): 357 self.GetPositionCoordinate().SetValue(sx, sy) 358 elif pos == 3 or ("bottom" in pos and "left" in pos): 359 self.GetPositionCoordinate().SetValue(0, 0) 360 elif pos == 4 or ("bottom" in pos and "right" in pos): 361 self.GetPositionCoordinate().SetValue(sx, 0) 362 elif "cent" in pos and "right" in pos: 363 self.GetPositionCoordinate().SetValue(sx, sy - 0.25) 364 elif "cent" in pos and "left" in pos: 365 self.GetPositionCoordinate().SetValue(0, sy - 0.25) 366 elif "cent" in pos and "bottom" in pos: 367 self.GetPositionCoordinate().SetValue(sx - 0.25, 0) 368 elif "cent" in pos and "top" in pos: 369 self.GetPositionCoordinate().SetValue(sx - 0.25, sy) 370 elif utils.is_sequence(pos): 371 self.GetPositionCoordinate().SetValue(pos[0], pos[1]) 372 else: 373 vedo.logger.error("LegendBox: pos must be in range [1-4] or a [x,y] list") 374 375 return self
Create a 2D legend box.
219 def __init__( 220 self, 221 entries=(), 222 nmax=12, 223 c=None, 224 font="", 225 width=0.18, 226 height=None, 227 padding=2, 228 bg="k8", 229 alpha=0.25, 230 pos="top-right", 231 markers=None, 232 ): 233 """ 234 Create a 2D legend box for the list of specified objects. 235 236 Arguments: 237 nmax : (int) 238 max number of legend entries 239 c : (color) 240 text color, leave as None to pick the mesh color automatically 241 font : (str) 242 Check [available fonts here](https://vedo.embl.es/fonts) 243 width : (float) 244 width of the box as fraction of the window width 245 height : (float) 246 height of the box as fraction of the window height 247 padding : (int) 248 padding space in units of pixels 249 bg : (color) 250 background color of the box 251 alpha: (float) 252 opacity of the box 253 pos : (str, list) 254 position of the box, can be either a string or a (x,y) screen position in range [0,1] 255 256 Examples: 257 - [legendbox.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/legendbox.py) 258 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 259 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 260 261 ![](https://vedo.embl.es/images/other/flag_labels.png) 262 """ 263 super().__init__() 264 265 self.name = "LegendBox" 266 self.entries = entries[:nmax] 267 self.properties = self.GetEntryTextProperty() 268 269 n = 0 270 texts = [] 271 for e in self.entries: 272 ename = e.name 273 if "legend" in e.info.keys(): 274 if not e.info["legend"]: 275 ename = "" 276 else: 277 ename = str(e.info["legend"]) 278 if ename: 279 n += 1 280 texts.append(ename) 281 self.SetNumberOfEntries(n) 282 283 if not n: 284 return 285 286 self.ScalarVisibilityOff() 287 self.PickableOff() 288 self.SetPadding(padding) 289 290 self.properties.ShadowOff() 291 self.properties.BoldOff() 292 293 # self.properties.SetJustificationToLeft() # no effect 294 # self.properties.SetVerticalJustificationToTop() 295 296 if not font: 297 font = settings.default_font 298 299 self.font(font) 300 301 n = 0 302 for i in range(len(self.entries)): 303 ti = texts[i] 304 if not ti: 305 continue 306 e = entries[i] 307 if c is None: 308 col = e.properties.GetColor() 309 if col == (1, 1, 1): 310 col = (0.2, 0.2, 0.2) 311 else: 312 col = get_color(c) 313 if markers is None: # default 314 poly = e.dataset 315 else: 316 marker = markers[i] if utils.is_sequence(markers) else markers 317 if isinstance(marker, Points): 318 poly = marker.clone(deep=False).normalize().shift(0, 1, 0).dataset 319 else: # assume string marker 320 poly = vedo.shapes.Marker(marker, s=1).shift(0, 1, 0).dataset 321 322 self.SetEntry(n, poly, ti, col) 323 n += 1 324 325 self.SetWidth(width) 326 if height is None: 327 self.SetHeight(width / 3.0 * n) 328 else: 329 self.SetHeight(height) 330 331 self.pos(pos) 332 333 if alpha: 334 self.UseBackgroundOn() 335 self.SetBackgroundColor(get_color(bg)) 336 self.SetBackgroundOpacity(alpha) 337 else: 338 self.UseBackgroundOff() 339 self.LockBorderOn()
Create a 2D legend box for the list of specified objects.
Arguments:
- nmax : (int) max number of legend entries
- c : (color) text color, leave as None to pick the mesh color automatically
- font : (str) Check available fonts here
- width : (float) width of the box as fraction of the window width
- height : (float) height of the box as fraction of the window height
- padding : (int) padding space in units of pixels
- bg : (color) background color of the box
- alpha: (float) opacity of the box
- pos : (str, list) position of the box, can be either a string or a (x,y) screen position in range [0,1]
Examples:
341 @property 342 def width(self): 343 """Return the width of the legend box.""" 344 return self.GetWidth()
Return the width of the legend box.
346 @property 347 def height(self): 348 """Return the height of the legend box.""" 349 return self.GetHeight()
Return the height of the legend box.
351 def pos(self, pos): 352 """Set the position of the legend box.""" 353 sx, sy = 1 - self.GetWidth(), 1 - self.GetHeight() 354 if pos == 1 or ("top" in pos and "left" in pos): 355 self.GetPositionCoordinate().SetValue(0, sy) 356 elif pos == 2 or ("top" in pos and "right" in pos): 357 self.GetPositionCoordinate().SetValue(sx, sy) 358 elif pos == 3 or ("bottom" in pos and "left" in pos): 359 self.GetPositionCoordinate().SetValue(0, 0) 360 elif pos == 4 or ("bottom" in pos and "right" in pos): 361 self.GetPositionCoordinate().SetValue(sx, 0) 362 elif "cent" in pos and "right" in pos: 363 self.GetPositionCoordinate().SetValue(sx, sy - 0.25) 364 elif "cent" in pos and "left" in pos: 365 self.GetPositionCoordinate().SetValue(0, sy - 0.25) 366 elif "cent" in pos and "bottom" in pos: 367 self.GetPositionCoordinate().SetValue(sx - 0.25, 0) 368 elif "cent" in pos and "top" in pos: 369 self.GetPositionCoordinate().SetValue(sx - 0.25, sy) 370 elif utils.is_sequence(pos): 371 self.GetPositionCoordinate().SetValue(pos[0], pos[1]) 372 else: 373 vedo.logger.error("LegendBox: pos must be in range [1-4] or a [x,y] list") 374 375 return self
Set the position of the legend box.
Inherited Members
1147def Light(pos, focal_point=(0, 0, 0), angle=180, c=None, intensity=1): 1148 """ 1149 Generate a source of light placed at `pos` and directed to `focal point`. 1150 Returns a `vtkLight` object. 1151 1152 Arguments: 1153 focal_point : (list) 1154 focal point, if a `vedo` object is passed then will grab its position. 1155 angle : (float) 1156 aperture angle of the light source, in degrees 1157 c : (color) 1158 set the light color 1159 intensity : (float) 1160 intensity value between 0 and 1. 1161 1162 Check also: 1163 `plotter.Plotter.remove_lights()` 1164 1165 Examples: 1166 - [light_sources.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/light_sources.py) 1167 1168 ![](https://vedo.embl.es/images/basic/lights.png) 1169 """ 1170 if c is None: 1171 try: 1172 c = pos.color() 1173 except AttributeError: 1174 c = "white" 1175 1176 try: 1177 pos = pos.pos() 1178 except AttributeError: 1179 pass 1180 1181 try: 1182 focal_point = focal_point.pos() 1183 except AttributeError: 1184 pass 1185 1186 light = vtki.vtkLight() 1187 light.SetLightTypeToSceneLight() 1188 light.SetPosition(pos) 1189 light.SetConeAngle(angle) 1190 light.SetFocalPoint(focal_point) 1191 light.SetIntensity(intensity) 1192 light.SetColor(get_color(c)) 1193 return light
Generate a source of light placed at pos
and directed to focal point
.
Returns a vtkLight
object.
Arguments:
- focal_point : (list)
focal point, if a
vedo
object is passed then will grab its position. - angle : (float) aperture angle of the light source, in degrees
- c : (color) set the light color
- intensity : (float) intensity value between 0 and 1.
Check also:
plotter.Plotter.remove_lights()
Examples:
3287def Axes( 3288 obj=None, 3289 xtitle='x', ytitle='y', ztitle='z', 3290 xrange=None, yrange=None, zrange=None, 3291 c=None, 3292 number_of_divisions=None, 3293 digits=None, 3294 limit_ratio=0.04, 3295 title_depth=0, 3296 title_font="", # grab settings.default_font 3297 text_scale=1.0, 3298 x_values_and_labels=None, y_values_and_labels=None, z_values_and_labels=None, 3299 htitle="", 3300 htitle_size=0.03, 3301 htitle_font=None, 3302 htitle_italic=False, 3303 htitle_color=None, htitle_backface_color=None, 3304 htitle_justify='bottom-left', 3305 htitle_rotation=0, 3306 htitle_offset=(0, 0.01, 0), 3307 xtitle_position=0.95, ytitle_position=0.95, ztitle_position=0.95, 3308 # xtitle_offset can be a list (dx,dy,dz) 3309 xtitle_offset=0.025, ytitle_offset=0.0275, ztitle_offset=0.02, 3310 xtitle_justify=None, ytitle_justify=None, ztitle_justify=None, 3311 # xtitle_rotation can be a list (rx,ry,rz) 3312 xtitle_rotation=0, ytitle_rotation=0, ztitle_rotation=0, 3313 xtitle_box=False, ytitle_box=False, 3314 xtitle_size=0.025, ytitle_size=0.025, ztitle_size=0.025, 3315 xtitle_color=None, ytitle_color=None, ztitle_color=None, 3316 xtitle_backface_color=None, ytitle_backface_color=None, ztitle_backface_color=None, 3317 xtitle_italic=0, ytitle_italic=0, ztitle_italic=0, 3318 grid_linewidth=1, 3319 xygrid=True, yzgrid=False, zxgrid=False, 3320 xygrid2=False, yzgrid2=False, zxgrid2=False, 3321 xygrid_transparent=False, yzgrid_transparent=False, zxgrid_transparent=False, 3322 xygrid2_transparent=False, yzgrid2_transparent=False, zxgrid2_transparent=False, 3323 xyplane_color=None, yzplane_color=None, zxplane_color=None, 3324 xygrid_color=None, yzgrid_color=None, zxgrid_color=None, 3325 xyalpha=0.075, yzalpha=0.075, zxalpha=0.075, 3326 xyframe_line=None, yzframe_line=None, zxframe_line=None, 3327 xyframe_color=None, yzframe_color=None, zxframe_color=None, 3328 axes_linewidth=1, 3329 xline_color=None, yline_color=None, zline_color=None, 3330 xhighlight_zero=False, yhighlight_zero=False, zhighlight_zero=False, 3331 xhighlight_zero_color='red4', yhighlight_zero_color='green4', zhighlight_zero_color='blue4', 3332 show_ticks=True, 3333 xtick_length=0.015, ytick_length=0.015, ztick_length=0.015, 3334 xtick_thickness=0.0025, ytick_thickness=0.0025, ztick_thickness=0.0025, 3335 xminor_ticks=1, yminor_ticks=1, zminor_ticks=1, 3336 tip_size=None, 3337 label_font="", # grab settings.default_font 3338 xlabel_color=None, ylabel_color=None, zlabel_color=None, 3339 xlabel_backface_color=None, ylabel_backface_color=None, zlabel_backface_color=None, 3340 xlabel_size=0.016, ylabel_size=0.016, zlabel_size=0.016, 3341 xlabel_offset=0.8, ylabel_offset=0.8, zlabel_offset=0.8, # each can be a list (dx,dy,dz) 3342 xlabel_justify=None, ylabel_justify=None, zlabel_justify=None, 3343 xlabel_rotation=0, ylabel_rotation=0, zlabel_rotation=0, # each can be a list (rx,ry,rz) 3344 xaxis_rotation=0, yaxis_rotation=0, zaxis_rotation=0, # rotate all elements around axis 3345 xyshift=0, yzshift=0, zxshift=0, 3346 xshift_along_y=0, xshift_along_z=0, 3347 yshift_along_x=0, yshift_along_z=0, 3348 zshift_along_x=0, zshift_along_y=0, 3349 x_use_bounds=True, y_use_bounds=True, z_use_bounds=False, 3350 x_inverted=False, y_inverted=False, z_inverted=False, 3351 use_global=False, 3352 tol=0.001, 3353 ) -> Union[Assembly, None]: 3354 """ 3355 Draw axes for the input object. 3356 Check [available fonts here](https://vedo.embl.es/fonts). 3357 3358 Returns an `vedo.Assembly` object. 3359 3360 Parameters 3361 ---------- 3362 3363 - `xtitle`, ['x'], x-axis title text 3364 - `xrange`, [None], x-axis range in format (xmin, ymin), default is automatic. 3365 - `number_of_divisions`, [None], approximate number of divisions on the longest axis 3366 - `axes_linewidth`, [1], width of the axes lines 3367 - `grid_linewidth`, [1], width of the grid lines 3368 - `title_depth`, [0], extrusion fractional depth of title text 3369 - `x_values_and_labels` [], assign custom tick positions and labels [(pos1, label1), ...] 3370 - `xygrid`, [True], show a gridded wall on plane xy 3371 - `yzgrid`, [True], show a gridded wall on plane yz 3372 - `zxgrid`, [True], show a gridded wall on plane zx 3373 - `yzgrid2`, [False], show yz plane on opposite side of the bounding box 3374 - `zxgrid2`, [False], show zx plane on opposite side of the bounding box 3375 - `xygrid_transparent` [False], make grid plane completely transparent 3376 - `xygrid2_transparent` [False], make grid plane completely transparent on opposite side box 3377 - `xyplane_color`, ['None'], color of the plane 3378 - `xygrid_color`, ['None'], grid line color 3379 - `xyalpha`, [0.15], grid plane opacity 3380 - `xyframe_line`, [0], add a frame for the plane, use value as the thickness 3381 - `xyframe_color`, [None], color for the frame of the plane 3382 - `show_ticks`, [True], show major ticks 3383 - `digits`, [None], use this number of significant digits in scientific notation 3384 - `title_font`, [''], font for axes titles 3385 - `label_font`, [''], font for numeric labels 3386 - `text_scale`, [1.0], global scaling factor for all text elements (titles, labels) 3387 - `htitle`, [''], header title 3388 - `htitle_size`, [0.03], header title size 3389 - `htitle_font`, [None], header font (defaults to `title_font`) 3390 - `htitle_italic`, [True], header font is italic 3391 - `htitle_color`, [None], header title color (defaults to `xtitle_color`) 3392 - `htitle_backface_color`, [None], header title color on its backface 3393 - `htitle_justify`, ['bottom-center'], origin of the title justification 3394 - `htitle_offset`, [(0,0.01,0)], control offsets of header title in x, y and z 3395 - `xtitle_position`, [0.32], title fractional positions along axis 3396 - `xtitle_offset`, [0.05], title fractional offset distance from axis line, can be a list 3397 - `xtitle_justify`, [None], choose the origin of the bounding box of title 3398 - `xtitle_rotation`, [0], add a rotation of the axis title, can be a list (rx,ry,rz) 3399 - `xtitle_box`, [False], add a box around title text 3400 - `xline_color`, [automatic], color of the x-axis 3401 - `xtitle_color`, [automatic], color of the axis title 3402 - `xtitle_backface_color`, [None], color of axis title on its backface 3403 - `xtitle_size`, [0.025], size of the axis title 3404 - `xtitle_italic`, [0], a bool or float to make the font italic 3405 - `xhighlight_zero`, [True], draw a line highlighting zero position if in range 3406 - `xhighlight_zero_color`, [auto], color of the line highlighting the zero position 3407 - `xtick_length`, [0.005], radius of the major ticks 3408 - `xtick_thickness`, [0.0025], thickness of the major ticks along their axis 3409 - `xminor_ticks`, [1], number of minor ticks between two major ticks 3410 - `xlabel_color`, [automatic], color of numeric labels and ticks 3411 - `xlabel_backface_color`, [auto], back face color of numeric labels and ticks 3412 - `xlabel_size`, [0.015], size of the numeric labels along axis 3413 - `xlabel_rotation`, [0,list], numeric labels rotation (can be a list of 3 rotations) 3414 - `xlabel_offset`, [0.8,list], offset of the numeric labels (can be a list of 3 offsets) 3415 - `xlabel_justify`, [None], choose the origin of the bounding box of labels 3416 - `xaxis_rotation`, [0], rotate the X axis elements (ticks and labels) around this same axis 3417 - `xyshift` [0.0], slide the xy-plane along z (the range is [0,1]) 3418 - `xshift_along_y` [0.0], slide x-axis along the y-axis (the range is [0,1]) 3419 - `tip_size`, [0.01], size of the arrow tip as a fraction of the bounding box diagonal 3420 - `limit_ratio`, [0.04], below this ratio don't plot smaller axis 3421 - `x_use_bounds`, [True], keep into account space occupied by labels when setting camera 3422 - `x_inverted`, [False], invert labels order and direction (only visually!) 3423 - `use_global`, [False], try to compute the global bounding box of visible actors 3424 3425 Example: 3426 ```python 3427 from vedo import Axes, Box, show 3428 box = Box(pos=(1,2,3), length=8, width=9, height=7).alpha(0.1) 3429 axs = Axes(box, c='k') # returns an Assembly object 3430 for a in axs.unpack(): 3431 print(a.name) 3432 show(box, axs).close() 3433 ``` 3434 ![](https://vedo.embl.es/images/feats/axes1.png) 3435 3436 Examples: 3437 - [custom_axes1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes1.py) 3438 - [custom_axes2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes2.py) 3439 - [custom_axes3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes3.py) 3440 - [custom_axes4.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes4.py) 3441 3442 ![](https://vedo.embl.es/images/pyplot/customAxes3.png) 3443 """ 3444 if not title_font: 3445 title_font = vedo.settings.default_font 3446 if not label_font: 3447 label_font = vedo.settings.default_font 3448 3449 if c is None: # automatic black or white 3450 c = (0.1, 0.1, 0.1) 3451 plt = vedo.plotter_instance 3452 if plt and plt.renderer: 3453 bgcol = plt.renderer.GetBackground() 3454 else: 3455 bgcol = (1, 1, 1) 3456 if np.sum(bgcol) < 1.5: 3457 c = (0.9, 0.9, 0.9) 3458 else: 3459 c = get_color(c) 3460 3461 # Check if obj has bounds, if so use those 3462 if obj is not None: 3463 try: 3464 bb = obj.bounds() 3465 except AttributeError: 3466 try: 3467 bb = obj.GetBounds() 3468 if xrange is None: xrange = (bb[0], bb[1]) 3469 if yrange is None: yrange = (bb[2], bb[3]) 3470 if zrange is None: zrange = (bb[4], bb[5]) 3471 obj = None # dont need it anymore 3472 except AttributeError: 3473 pass 3474 if utils.is_sequence(obj) and len(obj) == 6 and utils.is_number(obj[0]): 3475 # passing a list of numeric bounds 3476 if xrange is None: xrange = (obj[0], obj[1]) 3477 if yrange is None: yrange = (obj[2], obj[3]) 3478 if zrange is None: zrange = (obj[4], obj[5]) 3479 3480 if use_global: 3481 vbb, drange, min_bns, max_bns = compute_visible_bounds() 3482 else: 3483 if obj is not None: 3484 vbb, drange, min_bns, max_bns = compute_visible_bounds(obj) 3485 else: 3486 vbb = np.zeros(6) 3487 drange = np.zeros(3) 3488 if zrange is None: 3489 zrange = (0, 0) 3490 if xrange is None or yrange is None: 3491 vedo.logger.error("in Axes() must specify axes ranges!") 3492 return None ########################################### 3493 3494 if xrange is not None: 3495 if xrange[1] < xrange[0]: 3496 x_inverted = True 3497 xrange = [xrange[1], xrange[0]] 3498 vbb[0], vbb[1] = xrange 3499 drange[0] = vbb[1] - vbb[0] 3500 min_bns = vbb 3501 max_bns = vbb 3502 if yrange is not None: 3503 if yrange[1] < yrange[0]: 3504 y_inverted = True 3505 yrange = [yrange[1], yrange[0]] 3506 vbb[2], vbb[3] = yrange 3507 drange[1] = vbb[3] - vbb[2] 3508 min_bns = vbb 3509 max_bns = vbb 3510 if zrange is not None: 3511 if zrange[1] < zrange[0]: 3512 z_inverted = True 3513 zrange = [zrange[1], zrange[0]] 3514 vbb[4], vbb[5] = zrange 3515 drange[2] = vbb[5] - vbb[4] 3516 min_bns = vbb 3517 max_bns = vbb 3518 3519 drangemax = max(drange) 3520 if not drangemax: 3521 return None 3522 3523 if drange[0] / drangemax < limit_ratio: 3524 drange[0] = 0 3525 xtitle = "" 3526 if drange[1] / drangemax < limit_ratio: 3527 drange[1] = 0 3528 ytitle = "" 3529 if drange[2] / drangemax < limit_ratio: 3530 drange[2] = 0 3531 ztitle = "" 3532 3533 x0, x1, y0, y1, z0, z1 = vbb 3534 dx, dy, dz = drange 3535 3536 gscale = np.sqrt(dx * dx + dy * dy + dz * dz) * 0.75 3537 3538 if not xyplane_color: xyplane_color = c 3539 if not yzplane_color: yzplane_color = c 3540 if not zxplane_color: zxplane_color = c 3541 if not xygrid_color: xygrid_color = c 3542 if not yzgrid_color: yzgrid_color = c 3543 if not zxgrid_color: zxgrid_color = c 3544 if not xtitle_color: xtitle_color = c 3545 if not ytitle_color: ytitle_color = c 3546 if not ztitle_color: ztitle_color = c 3547 if not xline_color: xline_color = c 3548 if not yline_color: yline_color = c 3549 if not zline_color: zline_color = c 3550 if not xlabel_color: xlabel_color = xline_color 3551 if not ylabel_color: ylabel_color = yline_color 3552 if not zlabel_color: zlabel_color = zline_color 3553 3554 if tip_size is None: 3555 tip_size = 0.005 * gscale 3556 if not ztitle: 3557 tip_size = 0 # switch off in xy 2d 3558 3559 ndiv = 4 3560 if not ztitle or not ytitle or not xtitle: # make more default ticks if 2D 3561 ndiv = 6 3562 if not ztitle: 3563 if xyframe_line is None: 3564 xyframe_line = True 3565 if tip_size is None: 3566 tip_size = False 3567 3568 if utils.is_sequence(number_of_divisions): 3569 rx, ry, rz = number_of_divisions 3570 else: 3571 if not number_of_divisions: 3572 number_of_divisions = ndiv 3573 if not drangemax or np.any(np.isnan(drange)): 3574 rx, ry, rz = 1, 1, 1 3575 else: 3576 rx, ry, rz = np.ceil(drange / drangemax * number_of_divisions).astype(int) 3577 3578 if xtitle: 3579 xticks_float, xticks_str = utils.make_ticks(x0, x1, rx, x_values_and_labels, digits) 3580 xticks_float = xticks_float * dx 3581 if x_inverted: 3582 xticks_float = np.flip(-(xticks_float - xticks_float[-1])) 3583 xticks_str = list(reversed(xticks_str)) 3584 xticks_str[-1] = "" 3585 xhighlight_zero = False 3586 if ytitle: 3587 yticks_float, yticks_str = utils.make_ticks(y0, y1, ry, y_values_and_labels, digits) 3588 yticks_float = yticks_float * dy 3589 if y_inverted: 3590 yticks_float = np.flip(-(yticks_float - yticks_float[-1])) 3591 yticks_str = list(reversed(yticks_str)) 3592 yticks_str[-1] = "" 3593 yhighlight_zero = False 3594 if ztitle: 3595 zticks_float, zticks_str = utils.make_ticks(z0, z1, rz, z_values_and_labels, digits) 3596 zticks_float = zticks_float * dz 3597 if z_inverted: 3598 zticks_float = np.flip(-(zticks_float - zticks_float[-1])) 3599 zticks_str = list(reversed(zticks_str)) 3600 zticks_str[-1] = "" 3601 zhighlight_zero = False 3602 3603 ################################################ axes lines 3604 lines = [] 3605 if xtitle: 3606 axlinex = shapes.Line([0,0,0], [dx,0,0], c=xline_color, lw=axes_linewidth) 3607 axlinex.shift([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz]) 3608 axlinex.name = 'xAxis' 3609 lines.append(axlinex) 3610 if ytitle: 3611 axliney = shapes.Line([0,0,0], [0,dy,0], c=yline_color, lw=axes_linewidth) 3612 axliney.shift([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz]) 3613 axliney.name = 'yAxis' 3614 lines.append(axliney) 3615 if ztitle: 3616 axlinez = shapes.Line([0,0,0], [0,0,dz], c=zline_color, lw=axes_linewidth) 3617 axlinez.shift([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0]) 3618 axlinez.name = 'zAxis' 3619 lines.append(axlinez) 3620 3621 ################################################ grid planes 3622 # all shapes have a name to keep track of them in the Assembly 3623 # if user wants to unpack it 3624 grids = [] 3625 if xygrid and xtitle and ytitle: 3626 if not xygrid_transparent: 3627 gxy = shapes.Grid(s=(xticks_float, yticks_float)) 3628 gxy.alpha(xyalpha).c(xyplane_color).lw(0) 3629 if xyshift: gxy.shift([0,0,xyshift*dz]) 3630 elif tol: gxy.shift([0,0,-tol*gscale]) 3631 gxy.name = "xyGrid" 3632 grids.append(gxy) 3633 if grid_linewidth: 3634 gxy_lines = shapes.Grid(s=(xticks_float, yticks_float)) 3635 gxy_lines.c(xyplane_color).lw(grid_linewidth).alpha(xyalpha) 3636 if xyshift: gxy_lines.shift([0,0,xyshift*dz]) 3637 elif tol: gxy_lines.shift([0,0,-tol*gscale]) 3638 gxy_lines.name = "xyGridLines" 3639 grids.append(gxy_lines) 3640 3641 if yzgrid and ytitle and ztitle: 3642 if not yzgrid_transparent: 3643 gyz = shapes.Grid(s=(zticks_float, yticks_float)) 3644 gyz.alpha(yzalpha).c(yzplane_color).lw(0).rotate_y(-90) 3645 if yzshift: gyz.shift([yzshift*dx,0,0]) 3646 elif tol: gyz.shift([-tol*gscale,0,0]) 3647 gyz.name = "yzGrid" 3648 grids.append(gyz) 3649 if grid_linewidth: 3650 gyz_lines = shapes.Grid(s=(zticks_float, yticks_float)) 3651 gyz_lines.c(yzplane_color).lw(grid_linewidth).alpha(yzalpha).rotate_y(-90) 3652 if yzshift: gyz_lines.shift([yzshift*dx,0,0]) 3653 elif tol: gyz_lines.shift([-tol*gscale,0,0]) 3654 gyz_lines.name = "yzGridLines" 3655 grids.append(gyz_lines) 3656 3657 if zxgrid and ztitle and xtitle: 3658 if not zxgrid_transparent: 3659 gzx = shapes.Grid(s=(xticks_float, zticks_float)) 3660 gzx.alpha(zxalpha).c(zxplane_color).lw(0).rotate_x(90) 3661 if zxshift: gzx.shift([0,zxshift*dy,0]) 3662 elif tol: gzx.shift([0,-tol*gscale,0]) 3663 gzx.name = "zxGrid" 3664 grids.append(gzx) 3665 if grid_linewidth: 3666 gzx_lines = shapes.Grid(s=(xticks_float, zticks_float)) 3667 gzx_lines.c(zxplane_color).lw(grid_linewidth).alpha(zxalpha).rotate_x(90) 3668 if zxshift: gzx_lines.shift([0,zxshift*dy,0]) 3669 elif tol: gzx_lines.shift([0,-tol*gscale,0]) 3670 gzx_lines.name = "zxGridLines" 3671 grids.append(gzx_lines) 3672 3673 # Grid2 3674 if xygrid2 and xtitle and ytitle: 3675 if not xygrid2_transparent: 3676 gxy2 = shapes.Grid(s=(xticks_float, yticks_float)).z(dz) 3677 gxy2.alpha(xyalpha).c(xyplane_color).lw(0) 3678 gxy2.shift([0, tol * gscale, 0]) 3679 gxy2.name = "xyGrid2" 3680 grids.append(gxy2) 3681 if grid_linewidth: 3682 gxy2_lines = shapes.Grid(s=(xticks_float, yticks_float)).z(dz) 3683 gxy2_lines.c(xyplane_color).lw(grid_linewidth).alpha(xyalpha) 3684 gxy2_lines.shift([0, tol * gscale, 0]) 3685 gxy2_lines.name = "xygrid2Lines" 3686 grids.append(gxy2_lines) 3687 3688 if yzgrid2 and ytitle and ztitle: 3689 if not yzgrid2_transparent: 3690 gyz2 = shapes.Grid(s=(zticks_float, yticks_float)) 3691 gyz2.alpha(yzalpha).c(yzplane_color).lw(0) 3692 gyz2.rotate_y(-90).x(dx).shift([tol * gscale, 0, 0]) 3693 gyz2.name = "yzGrid2" 3694 grids.append(gyz2) 3695 if grid_linewidth: 3696 gyz2_lines = shapes.Grid(s=(zticks_float, yticks_float)) 3697 gyz2_lines.c(yzplane_color).lw(grid_linewidth).alpha(yzalpha) 3698 gyz2_lines.rotate_y(-90).x(dx).shift([tol * gscale, 0, 0]) 3699 gyz2_lines.name = "yzGrid2Lines" 3700 grids.append(gyz2_lines) 3701 3702 if zxgrid2 and ztitle and xtitle: 3703 if not zxgrid2_transparent: 3704 gzx2 = shapes.Grid(s=(xticks_float, zticks_float)) 3705 gzx2.alpha(zxalpha).c(zxplane_color).lw(0) 3706 gzx2.rotate_x(90).y(dy).shift([0, tol * gscale, 0]) 3707 gzx2.name = "zxGrid2" 3708 grids.append(gzx2) 3709 if grid_linewidth: 3710 gzx2_lines = shapes.Grid(s=(xticks_float, zticks_float)) 3711 gzx2_lines.c(zxplane_color).lw(grid_linewidth).alpha(zxalpha) 3712 gzx2_lines.rotate_x(90).y(dy).shift([0, tol * gscale, 0]) 3713 gzx2_lines.name = "zxGrid2Lines" 3714 grids.append(gzx2_lines) 3715 3716 ################################################ frame lines 3717 framelines = [] 3718 if xyframe_line and xtitle and ytitle: 3719 if not xyframe_color: 3720 xyframe_color = xygrid_color 3721 frxy = shapes.Line( 3722 [[0, dy, 0], [dx, dy, 0], [dx, 0, 0], [0, 0, 0], [0, dy, 0]], 3723 c=xyframe_color, 3724 lw=xyframe_line, 3725 ) 3726 frxy.shift([0, 0, xyshift * dz]) 3727 frxy.name = "xyFrameLine" 3728 framelines.append(frxy) 3729 if yzframe_line and ytitle and ztitle: 3730 if not yzframe_color: 3731 yzframe_color = yzgrid_color 3732 fryz = shapes.Line( 3733 [[0, 0, dz], [0, dy, dz], [0, dy, 0], [0, 0, 0], [0, 0, dz]], 3734 c=yzframe_color, 3735 lw=yzframe_line, 3736 ) 3737 fryz.shift([yzshift * dx, 0, 0]) 3738 fryz.name = "yzFrameLine" 3739 framelines.append(fryz) 3740 if zxframe_line and ztitle and xtitle: 3741 if not zxframe_color: 3742 zxframe_color = zxgrid_color 3743 frzx = shapes.Line( 3744 [[0, 0, dz], [dx, 0, dz], [dx, 0, 0], [0, 0, 0], [0, 0, dz]], 3745 c=zxframe_color, 3746 lw=zxframe_line, 3747 ) 3748 frzx.shift([0, zxshift * dy, 0]) 3749 frzx.name = "zxFrameLine" 3750 framelines.append(frzx) 3751 3752 ################################################ zero lines highlights 3753 highlights = [] 3754 if xygrid and xtitle and ytitle: 3755 if xhighlight_zero and min_bns[0] <= 0 and max_bns[1] > 0: 3756 xhl = -min_bns[0] 3757 hxy = shapes.Line([xhl, 0, 0], [xhl, dy, 0], c=xhighlight_zero_color) 3758 hxy.alpha(np.sqrt(xyalpha)).lw(grid_linewidth * 2) 3759 hxy.shift([0, 0, xyshift * dz]) 3760 hxy.name = "xyHighlightZero" 3761 highlights.append(hxy) 3762 if yhighlight_zero and min_bns[2] <= 0 and max_bns[3] > 0: 3763 yhl = -min_bns[2] 3764 hyx = shapes.Line([0, yhl, 0], [dx, yhl, 0], c=yhighlight_zero_color) 3765 hyx.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2) 3766 hyx.shift([0, 0, xyshift * dz]) 3767 hyx.name = "yxHighlightZero" 3768 highlights.append(hyx) 3769 3770 if yzgrid and ytitle and ztitle: 3771 if yhighlight_zero and min_bns[2] <= 0 and max_bns[3] > 0: 3772 yhl = -min_bns[2] 3773 hyz = shapes.Line([0, yhl, 0], [0, yhl, dz], c=yhighlight_zero_color) 3774 hyz.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2) 3775 hyz.shift([yzshift * dx, 0, 0]) 3776 hyz.name = "yzHighlightZero" 3777 highlights.append(hyz) 3778 if zhighlight_zero and min_bns[4] <= 0 and max_bns[5] > 0: 3779 zhl = -min_bns[4] 3780 hzy = shapes.Line([0, 0, zhl], [0, dy, zhl], c=zhighlight_zero_color) 3781 hzy.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2) 3782 hzy.shift([yzshift * dx, 0, 0]) 3783 hzy.name = "zyHighlightZero" 3784 highlights.append(hzy) 3785 3786 if zxgrid and ztitle and xtitle: 3787 if zhighlight_zero and min_bns[4] <= 0 and max_bns[5] > 0: 3788 zhl = -min_bns[4] 3789 hzx = shapes.Line([0, 0, zhl], [dx, 0, zhl], c=zhighlight_zero_color) 3790 hzx.alpha(np.sqrt(zxalpha)).lw(grid_linewidth * 2) 3791 hzx.shift([0, zxshift * dy, 0]) 3792 hzx.name = "zxHighlightZero" 3793 highlights.append(hzx) 3794 if xhighlight_zero and min_bns[0] <= 0 and max_bns[1] > 0: 3795 xhl = -min_bns[0] 3796 hxz = shapes.Line([xhl, 0, 0], [xhl, 0, dz], c=xhighlight_zero_color) 3797 hxz.alpha(np.sqrt(zxalpha)).lw(grid_linewidth * 2) 3798 hxz.shift([0, zxshift * dy, 0]) 3799 hxz.name = "xzHighlightZero" 3800 highlights.append(hxz) 3801 3802 ################################################ arrow cone 3803 cones = [] 3804 3805 if tip_size: 3806 3807 if xtitle: 3808 if x_inverted: 3809 cx = shapes.Cone( 3810 r=tip_size, 3811 height=tip_size * 2, 3812 axis=(-1, 0, 0), 3813 c=xline_color, 3814 res=12, 3815 ) 3816 else: 3817 cx = shapes.Cone( 3818 (dx, 0, 0), 3819 r=tip_size, 3820 height=tip_size * 2, 3821 axis=(1, 0, 0), 3822 c=xline_color, 3823 res=12, 3824 ) 3825 T = LinearTransform() 3826 T.translate( 3827 [ 3828 0, 3829 zxshift * dy + xshift_along_y * dy, 3830 xyshift * dz + xshift_along_z * dz, 3831 ] 3832 ) 3833 cx.apply_transform(T) 3834 cx.name = "xTipCone" 3835 cones.append(cx) 3836 3837 if ytitle: 3838 if y_inverted: 3839 cy = shapes.Cone( 3840 r=tip_size, 3841 height=tip_size * 2, 3842 axis=(0, -1, 0), 3843 c=yline_color, 3844 res=12, 3845 ) 3846 else: 3847 cy = shapes.Cone( 3848 (0, dy, 0), 3849 r=tip_size, 3850 height=tip_size * 2, 3851 axis=(0, 1, 0), 3852 c=yline_color, 3853 res=12, 3854 ) 3855 T = LinearTransform() 3856 T.translate( 3857 [ 3858 yzshift * dx + yshift_along_x * dx, 3859 0, 3860 xyshift * dz + yshift_along_z * dz, 3861 ] 3862 ) 3863 cy.apply_transform(T) 3864 cy.name = "yTipCone" 3865 cones.append(cy) 3866 3867 if ztitle: 3868 if z_inverted: 3869 cz = shapes.Cone( 3870 r=tip_size, 3871 height=tip_size * 2, 3872 axis=(0, 0, -1), 3873 c=zline_color, 3874 res=12, 3875 ) 3876 else: 3877 cz = shapes.Cone( 3878 (0, 0, dz), 3879 r=tip_size, 3880 height=tip_size * 2, 3881 axis=(0, 0, 1), 3882 c=zline_color, 3883 res=12, 3884 ) 3885 T = LinearTransform() 3886 T.translate( 3887 [ 3888 yzshift * dx + zshift_along_x * dx, 3889 zxshift * dy + zshift_along_y * dy, 3890 0, 3891 ] 3892 ) 3893 cz.apply_transform(T) 3894 cz.name = "zTipCone" 3895 cones.append(cz) 3896 3897 ################################################################# MAJOR ticks 3898 majorticks, minorticks = [], [] 3899 xticks, yticks, zticks = [], [], [] 3900 if show_ticks: 3901 if xtitle: 3902 tick_thickness = xtick_thickness * gscale / 2 3903 tick_length = xtick_length * gscale / 2 3904 for i in range(1, len(xticks_float) - 1): 3905 v1 = (xticks_float[i] - tick_thickness, -tick_length, 0) 3906 v2 = (xticks_float[i] + tick_thickness, tick_length, 0) 3907 xticks.append(shapes.Rectangle(v1, v2)) 3908 if len(xticks) > 1: 3909 xmajticks = merge(xticks).c(xlabel_color) 3910 T = LinearTransform() 3911 T.rotate_x(xaxis_rotation) 3912 T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz]) 3913 xmajticks.apply_transform(T) 3914 xmajticks.name = "xMajorTicks" 3915 majorticks.append(xmajticks) 3916 if ytitle: 3917 tick_thickness = ytick_thickness * gscale / 2 3918 tick_length = ytick_length * gscale / 2 3919 for i in range(1, len(yticks_float) - 1): 3920 v1 = (-tick_length, yticks_float[i] - tick_thickness, 0) 3921 v2 = (tick_length, yticks_float[i] + tick_thickness, 0) 3922 yticks.append(shapes.Rectangle(v1, v2)) 3923 if len(yticks) > 1: 3924 ymajticks = merge(yticks).c(ylabel_color) 3925 T = LinearTransform() 3926 T.rotate_y(yaxis_rotation) 3927 T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz]) 3928 ymajticks.apply_transform(T) 3929 ymajticks.name = "yMajorTicks" 3930 majorticks.append(ymajticks) 3931 if ztitle: 3932 tick_thickness = ztick_thickness * gscale / 2 3933 tick_length = ztick_length * gscale / 2.85 3934 for i in range(1, len(zticks_float) - 1): 3935 v1 = (zticks_float[i] - tick_thickness, -tick_length, 0) 3936 v2 = (zticks_float[i] + tick_thickness, tick_length, 0) 3937 zticks.append(shapes.Rectangle(v1, v2)) 3938 if len(zticks) > 1: 3939 zmajticks = merge(zticks).c(zlabel_color) 3940 T = LinearTransform() 3941 T.rotate_y(-90).rotate_z(-45 + zaxis_rotation) 3942 T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0]) 3943 zmajticks.apply_transform(T) 3944 zmajticks.name = "zMajorTicks" 3945 majorticks.append(zmajticks) 3946 3947 ############################################################# MINOR ticks 3948 if xtitle and xminor_ticks and len(xticks) > 1: 3949 tick_thickness = xtick_thickness * gscale / 4 3950 tick_length = xtick_length * gscale / 4 3951 xminor_ticks += 1 3952 ticks = [] 3953 for i in range(1, len(xticks)): 3954 t0, t1 = xticks[i - 1].pos(), xticks[i].pos() 3955 dt = t1 - t0 3956 for j in range(1, xminor_ticks): 3957 mt = dt * (j / xminor_ticks) + t0 3958 v1 = (mt[0] - tick_thickness, -tick_length, 0) 3959 v2 = (mt[0] + tick_thickness, tick_length, 0) 3960 ticks.append(shapes.Rectangle(v1, v2)) 3961 3962 # finish off the fist lower range from start to first tick 3963 t0, t1 = xticks[0].pos(), xticks[1].pos() 3964 dt = t1 - t0 3965 for j in range(1, xminor_ticks): 3966 mt = t0 - dt * (j / xminor_ticks) 3967 if mt[0] < 0: 3968 break 3969 v1 = (mt[0] - tick_thickness, -tick_length, 0) 3970 v2 = (mt[0] + tick_thickness, tick_length, 0) 3971 ticks.append(shapes.Rectangle(v1, v2)) 3972 3973 # finish off the last upper range from last tick to end 3974 t0, t1 = xticks[-2].pos(), xticks[-1].pos() 3975 dt = t1 - t0 3976 for j in range(1, xminor_ticks): 3977 mt = t1 + dt * (j / xminor_ticks) 3978 if mt[0] > dx: 3979 break 3980 v1 = (mt[0] - tick_thickness, -tick_length, 0) 3981 v2 = (mt[0] + tick_thickness, tick_length, 0) 3982 ticks.append(shapes.Rectangle(v1, v2)) 3983 3984 if ticks: 3985 xminticks = merge(ticks).c(xlabel_color) 3986 T = LinearTransform() 3987 T.rotate_x(xaxis_rotation) 3988 T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz]) 3989 xminticks.apply_transform(T) 3990 xminticks.name = "xMinorTicks" 3991 minorticks.append(xminticks) 3992 3993 if ytitle and yminor_ticks and len(yticks) > 1: ##### y 3994 tick_thickness = ytick_thickness * gscale / 4 3995 tick_length = ytick_length * gscale / 4 3996 yminor_ticks += 1 3997 ticks = [] 3998 for i in range(1, len(yticks)): 3999 t0, t1 = yticks[i - 1].pos(), yticks[i].pos() 4000 dt = t1 - t0 4001 for j in range(1, yminor_ticks): 4002 mt = dt * (j / yminor_ticks) + t0 4003 v1 = (-tick_length, mt[1] - tick_thickness, 0) 4004 v2 = (tick_length, mt[1] + tick_thickness, 0) 4005 ticks.append(shapes.Rectangle(v1, v2)) 4006 4007 # finish off the fist lower range from start to first tick 4008 t0, t1 = yticks[0].pos(), yticks[1].pos() 4009 dt = t1 - t0 4010 for j in range(1, yminor_ticks): 4011 mt = t0 - dt * (j / yminor_ticks) 4012 if mt[1] < 0: 4013 break 4014 v1 = (-tick_length, mt[1] - tick_thickness, 0) 4015 v2 = (tick_length, mt[1] + tick_thickness, 0) 4016 ticks.append(shapes.Rectangle(v1, v2)) 4017 4018 # finish off the last upper range from last tick to end 4019 t0, t1 = yticks[-2].pos(), yticks[-1].pos() 4020 dt = t1 - t0 4021 for j in range(1, yminor_ticks): 4022 mt = t1 + dt * (j / yminor_ticks) 4023 if mt[1] > dy: 4024 break 4025 v1 = (-tick_length, mt[1] - tick_thickness, 0) 4026 v2 = (tick_length, mt[1] + tick_thickness, 0) 4027 ticks.append(shapes.Rectangle(v1, v2)) 4028 4029 if ticks: 4030 yminticks = merge(ticks).c(ylabel_color) 4031 T = LinearTransform() 4032 T.rotate_y(yaxis_rotation) 4033 T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz]) 4034 yminticks.apply_transform(T) 4035 yminticks.name = "yMinorTicks" 4036 minorticks.append(yminticks) 4037 4038 if ztitle and zminor_ticks and len(zticks) > 1: ##### z 4039 tick_thickness = ztick_thickness * gscale / 4 4040 tick_length = ztick_length * gscale / 5 4041 zminor_ticks += 1 4042 ticks = [] 4043 for i in range(1, len(zticks)): 4044 t0, t1 = zticks[i - 1].pos(), zticks[i].pos() 4045 dt = t1 - t0 4046 for j in range(1, zminor_ticks): 4047 mt = dt * (j / zminor_ticks) + t0 4048 v1 = (mt[0] - tick_thickness, -tick_length, 0) 4049 v2 = (mt[0] + tick_thickness, tick_length, 0) 4050 ticks.append(shapes.Rectangle(v1, v2)) 4051 4052 # finish off the fist lower range from start to first tick 4053 t0, t1 = zticks[0].pos(), zticks[1].pos() 4054 dt = t1 - t0 4055 for j in range(1, zminor_ticks): 4056 mt = t0 - dt * (j / zminor_ticks) 4057 if mt[0] < 0: 4058 break 4059 v1 = (mt[0] - tick_thickness, -tick_length, 0) 4060 v2 = (mt[0] + tick_thickness, tick_length, 0) 4061 ticks.append(shapes.Rectangle(v1, v2)) 4062 4063 # finish off the last upper range from last tick to end 4064 t0, t1 = zticks[-2].pos(), zticks[-1].pos() 4065 dt = t1 - t0 4066 for j in range(1, zminor_ticks): 4067 mt = t1 + dt * (j / zminor_ticks) 4068 if mt[0] > dz: 4069 break 4070 v1 = (mt[0] - tick_thickness, -tick_length, 0) 4071 v2 = (mt[0] + tick_thickness, tick_length, 0) 4072 ticks.append(shapes.Rectangle(v1, v2)) 4073 4074 if ticks: 4075 zminticks = merge(ticks).c(zlabel_color) 4076 T = LinearTransform() 4077 T.rotate_y(-90).rotate_z(-45 + zaxis_rotation) 4078 T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0]) 4079 zminticks.apply_transform(T) 4080 zminticks.name = "zMinorTicks" 4081 minorticks.append(zminticks) 4082 4083 ################################################ axes NUMERIC text labels 4084 labels = [] 4085 xlab, ylab, zlab = None, None, None 4086 4087 if xlabel_size and xtitle: 4088 4089 xRot, yRot, zRot = 0, 0, 0 4090 if utils.is_sequence(xlabel_rotation): # unpck 3 rotations 4091 zRot, xRot, yRot = xlabel_rotation 4092 else: 4093 zRot = xlabel_rotation 4094 if zRot < 0: # deal with negative angles 4095 zRot += 360 4096 4097 jus = "center-top" 4098 if zRot: 4099 if zRot > 24: jus = "top-right" 4100 if zRot > 67: jus = "center-right" 4101 if zRot > 112: jus = "right-bottom" 4102 if zRot > 157: jus = "center-bottom" 4103 if zRot > 202: jus = "bottom-left" 4104 if zRot > 247: jus = "center-left" 4105 if zRot > 292: jus = "top-left" 4106 if zRot > 337: jus = "top-center" 4107 if xlabel_justify is not None: 4108 jus = xlabel_justify 4109 4110 for i in range(1, len(xticks_str)): 4111 t = xticks_str[i] 4112 if not t: 4113 continue 4114 if utils.is_sequence(xlabel_offset): 4115 xoffs, yoffs, zoffs = xlabel_offset 4116 else: 4117 xoffs, yoffs, zoffs = 0, xlabel_offset, 0 4118 4119 xlab = shapes.Text3D( 4120 t, s=xlabel_size * text_scale * gscale, font=label_font, justify=jus 4121 ) 4122 tb = xlab.ybounds() # must be ybounds: height of char 4123 4124 v = (xticks_float[i], 0, 0) 4125 offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0]) 4126 4127 T = LinearTransform() 4128 T.rotate_x(xaxis_rotation).rotate_y(yRot).rotate_x(xRot).rotate_z(zRot) 4129 T.translate(v + offs) 4130 T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz]) 4131 xlab.apply_transform(T) 4132 4133 xlab.use_bounds(x_use_bounds) 4134 4135 xlab.c(xlabel_color) 4136 if xlabel_backface_color is None: 4137 bfc = 1 - np.array(get_color(xlabel_color)) 4138 xlab.backcolor(bfc) 4139 xlab.name = f"xNumericLabel {i}" 4140 labels.append(xlab) 4141 4142 if ylabel_size and ytitle: 4143 4144 xRot, yRot, zRot = 0, 0, 0 4145 if utils.is_sequence(ylabel_rotation): # unpck 3 rotations 4146 zRot, yRot, xRot = ylabel_rotation 4147 else: 4148 zRot = ylabel_rotation 4149 if zRot < 0: 4150 zRot += 360 # deal with negative angles 4151 4152 jus = "center-right" 4153 if zRot: 4154 if zRot > 24: jus = "bottom-right" 4155 if zRot > 67: jus = "center-bottom" 4156 if zRot > 112: jus = "left-bottom" 4157 if zRot > 157: jus = "center-left" 4158 if zRot > 202: jus = "top-left" 4159 if zRot > 247: jus = "center-top" 4160 if zRot > 292: jus = "top-right" 4161 if zRot > 337: jus = "right-center" 4162 if ylabel_justify is not None: 4163 jus = ylabel_justify 4164 4165 for i in range(1, len(yticks_str)): 4166 t = yticks_str[i] 4167 if not t: 4168 continue 4169 if utils.is_sequence(ylabel_offset): 4170 xoffs, yoffs, zoffs = ylabel_offset 4171 else: 4172 xoffs, yoffs, zoffs = ylabel_offset, 0, 0 4173 ylab = shapes.Text3D( 4174 t, s=ylabel_size * text_scale * gscale, font=label_font, justify=jus 4175 ) 4176 tb = ylab.ybounds() # must be ybounds: height of char 4177 v = (0, yticks_float[i], 0) 4178 offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0]) 4179 4180 T = LinearTransform() 4181 T.rotate_y(yaxis_rotation).rotate_x(xRot).rotate_y(yRot).rotate_z(zRot) 4182 T.translate(v + offs) 4183 T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz]) 4184 ylab.apply_transform(T) 4185 4186 ylab.use_bounds(y_use_bounds) 4187 4188 ylab.c(ylabel_color) 4189 if ylabel_backface_color is None: 4190 bfc = 1 - np.array(get_color(ylabel_color)) 4191 ylab.backcolor(bfc) 4192 ylab.name = f"yNumericLabel {i}" 4193 labels.append(ylab) 4194 4195 if zlabel_size and ztitle: 4196 4197 xRot, yRot, zRot = 0, 0, 0 4198 if utils.is_sequence(zlabel_rotation): # unpck 3 rotations 4199 xRot, yRot, zRot = zlabel_rotation 4200 else: 4201 xRot = zlabel_rotation 4202 if xRot < 0: xRot += 360 # deal with negative angles 4203 4204 jus = "center-right" 4205 if xRot: 4206 if xRot > 24: jus = "bottom-right" 4207 if xRot > 67: jus = "center-bottom" 4208 if xRot > 112: jus = "left-bottom" 4209 if xRot > 157: jus = "center-left" 4210 if xRot > 202: jus = "top-left" 4211 if xRot > 247: jus = "center-top" 4212 if xRot > 292: jus = "top-right" 4213 if xRot > 337: jus = "right-center" 4214 if zlabel_justify is not None: 4215 jus = zlabel_justify 4216 4217 for i in range(1, len(zticks_str)): 4218 t = zticks_str[i] 4219 if not t: 4220 continue 4221 if utils.is_sequence(zlabel_offset): 4222 xoffs, yoffs, zoffs = zlabel_offset 4223 else: 4224 xoffs, yoffs, zoffs = zlabel_offset, zlabel_offset, 0 4225 zlab = shapes.Text3D(t, s=zlabel_size*text_scale*gscale, font=label_font, justify=jus) 4226 tb = zlab.ybounds() # must be ybounds: height of char 4227 4228 v = (0, 0, zticks_float[i]) 4229 offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0]) / 1.5 4230 angle = np.arctan2(dy, dx) * 57.3 4231 4232 T = LinearTransform() 4233 T.rotate_x(90 + zRot).rotate_y(-xRot).rotate_z(angle + yRot + zaxis_rotation) 4234 T.translate(v + offs) 4235 T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0]) 4236 zlab.apply_transform(T) 4237 4238 zlab.use_bounds(z_use_bounds) 4239 4240 zlab.c(zlabel_color) 4241 if zlabel_backface_color is None: 4242 bfc = 1 - np.array(get_color(zlabel_color)) 4243 zlab.backcolor(bfc) 4244 zlab.name = f"zNumericLabel {i}" 4245 labels.append(zlab) 4246 4247 ################################################ axes titles 4248 titles = [] 4249 4250 if xtitle: 4251 xRot, yRot, zRot = 0, 0, 0 4252 if utils.is_sequence(xtitle_rotation): # unpack 3 rotations 4253 zRot, xRot, yRot = xtitle_rotation 4254 else: 4255 zRot = xtitle_rotation 4256 if zRot < 0: # deal with negative angles 4257 zRot += 360 4258 4259 if utils.is_sequence(xtitle_offset): 4260 xoffs, yoffs, zoffs = xtitle_offset 4261 else: 4262 xoffs, yoffs, zoffs = 0, xtitle_offset, 0 4263 4264 if xtitle_justify is not None: 4265 jus = xtitle_justify 4266 else: 4267 # find best justfication for given rotation(s) 4268 jus = "right-top" 4269 if zRot: 4270 if zRot > 24: jus = "center-right" 4271 if zRot > 67: jus = "right-bottom" 4272 if zRot > 157: jus = "bottom-left" 4273 if zRot > 202: jus = "center-left" 4274 if zRot > 247: jus = "top-left" 4275 if zRot > 337: jus = "top-right" 4276 4277 xt = shapes.Text3D( 4278 xtitle, 4279 s=xtitle_size * text_scale * gscale, 4280 font=title_font, 4281 c=xtitle_color, 4282 justify=jus, 4283 depth=title_depth, 4284 italic=xtitle_italic, 4285 ) 4286 if xtitle_backface_color is None: 4287 xtitle_backface_color = 1 - np.array(get_color(xtitle_color)) 4288 xt.backcolor(xtitle_backface_color) 4289 4290 shift = 0 4291 if xlab: # xlab is the last created numeric text label.. 4292 lt0, lt1 = xlab.bounds()[2:4] 4293 shift = lt1 - lt0 4294 4295 T = LinearTransform() 4296 T.rotate_x(xRot).rotate_y(yRot).rotate_z(zRot) 4297 T.set_position( 4298 [(xoffs + xtitle_position) * dx, 4299 -(yoffs + xtick_length / 2) * dy - shift, 4300 zoffs * dz] 4301 ) 4302 T.rotate_x(xaxis_rotation) 4303 T.translate([0, xshift_along_y * dy, xyshift * dz + xshift_along_z * dz]) 4304 xt.apply_transform(T) 4305 4306 xt.use_bounds(x_use_bounds) 4307 if xtitle == " ": 4308 xt.use_bounds(False) 4309 xt.name = "xtitle" 4310 titles.append(xt) 4311 if xtitle_box: 4312 titles.append(xt.box(scale=1.1).use_bounds(x_use_bounds)) 4313 4314 if ytitle: 4315 xRot, yRot, zRot = 0, 0, 0 4316 if utils.is_sequence(ytitle_rotation): # unpck 3 rotations 4317 zRot, yRot, xRot = ytitle_rotation 4318 else: 4319 zRot = ytitle_rotation 4320 if len(ytitle) > 3: 4321 zRot += 90 4322 ytitle_position *= 0.975 4323 if zRot < 0: 4324 zRot += 360 # deal with negative angles 4325 4326 if utils.is_sequence(ytitle_offset): 4327 xoffs, yoffs, zoffs = ytitle_offset 4328 else: 4329 xoffs, yoffs, zoffs = ytitle_offset, 0, 0 4330 4331 if ytitle_justify is not None: 4332 jus = ytitle_justify 4333 else: 4334 jus = "center-right" 4335 if zRot: 4336 if zRot > 24: jus = "bottom-right" 4337 if zRot > 112: jus = "left-bottom" 4338 if zRot > 157: jus = "center-left" 4339 if zRot > 202: jus = "top-left" 4340 if zRot > 292: jus = "top-right" 4341 if zRot > 337: jus = "right-center" 4342 4343 yt = shapes.Text3D( 4344 ytitle, 4345 s=ytitle_size * text_scale * gscale, 4346 font=title_font, 4347 c=ytitle_color, 4348 justify=jus, 4349 depth=title_depth, 4350 italic=ytitle_italic, 4351 ) 4352 if ytitle_backface_color is None: 4353 ytitle_backface_color = 1 - np.array(get_color(ytitle_color)) 4354 yt.backcolor(ytitle_backface_color) 4355 4356 shift = 0 4357 if ylab: # this is the last created num label.. 4358 lt0, lt1 = ylab.bounds()[0:2] 4359 shift = lt1 - lt0 4360 4361 T = LinearTransform() 4362 T.rotate_x(xRot).rotate_y(yRot).rotate_z(zRot) 4363 T.set_position( 4364 [-(xoffs + ytick_length / 2) * dx - shift, 4365 (yoffs + ytitle_position) * dy, 4366 zoffs * dz] 4367 ) 4368 T.rotate_y(yaxis_rotation) 4369 T.translate([yshift_along_x * dx, 0, xyshift * dz + yshift_along_z * dz]) 4370 yt.apply_transform(T) 4371 4372 yt.use_bounds(y_use_bounds) 4373 if ytitle == " ": 4374 yt.use_bounds(False) 4375 yt.name = "ytitle" 4376 titles.append(yt) 4377 if ytitle_box: 4378 titles.append(yt.box(scale=1.1).use_bounds(y_use_bounds)) 4379 4380 if ztitle: 4381 xRot, yRot, zRot = 0, 0, 0 4382 if utils.is_sequence(ztitle_rotation): # unpck 3 rotations 4383 xRot, yRot, zRot = ztitle_rotation 4384 else: 4385 xRot = ztitle_rotation 4386 if len(ztitle) > 3: 4387 xRot += 90 4388 ztitle_position *= 0.975 4389 if xRot < 0: 4390 xRot += 360 # deal with negative angles 4391 4392 if ztitle_justify is not None: 4393 jus = ztitle_justify 4394 else: 4395 jus = "center-right" 4396 if xRot: 4397 if xRot > 24: jus = "bottom-right" 4398 if xRot > 112: jus = "left-bottom" 4399 if xRot > 157: jus = "center-left" 4400 if xRot > 202: jus = "top-left" 4401 if xRot > 292: jus = "top-right" 4402 if xRot > 337: jus = "right-center" 4403 4404 zt = shapes.Text3D( 4405 ztitle, 4406 s=ztitle_size * text_scale * gscale, 4407 font=title_font, 4408 c=ztitle_color, 4409 justify=jus, 4410 depth=title_depth, 4411 italic=ztitle_italic, 4412 ) 4413 if ztitle_backface_color is None: 4414 ztitle_backface_color = 1 - np.array(get_color(ztitle_color)) 4415 zt.backcolor(ztitle_backface_color) 4416 4417 angle = np.arctan2(dy, dx) * 57.3 4418 shift = 0 4419 if zlab: # this is the last created one.. 4420 lt0, lt1 = zlab.bounds()[0:2] 4421 shift = lt1 - lt0 4422 4423 T = LinearTransform() 4424 T.rotate_x(90 + zRot).rotate_y(-xRot).rotate_z(angle + yRot) 4425 T.set_position([ 4426 -(ztitle_offset + ztick_length / 5) * dx - shift, 4427 -(ztitle_offset + ztick_length / 5) * dy - shift, 4428 ztitle_position * dz] 4429 ) 4430 T.rotate_z(zaxis_rotation) 4431 T.translate([zshift_along_x * dx, zxshift * dy + zshift_along_y * dy, 0]) 4432 zt.apply_transform(T) 4433 4434 zt.use_bounds(z_use_bounds) 4435 if ztitle == " ": 4436 zt.use_bounds(False) 4437 zt.name = "ztitle" 4438 titles.append(zt) 4439 4440 ################################################### header title 4441 if htitle: 4442 if htitle_font is None: 4443 htitle_font = title_font 4444 if htitle_color is None: 4445 htitle_color = xtitle_color 4446 htit = shapes.Text3D( 4447 htitle, 4448 s=htitle_size * gscale * text_scale, 4449 font=htitle_font, 4450 c=htitle_color, 4451 justify=htitle_justify, 4452 depth=title_depth, 4453 italic=htitle_italic, 4454 ) 4455 if htitle_backface_color is None: 4456 htitle_backface_color = 1 - np.array(get_color(htitle_color)) 4457 htit.backcolor(htitle_backface_color) 4458 htit.rotate_x(htitle_rotation) 4459 wpos = [htitle_offset[0]*dx, (1 + htitle_offset[1])*dy, htitle_offset[2]*dz] 4460 htit.shift(np.array(wpos) + [0, 0, xyshift*dz]) 4461 htit.name = "htitle" 4462 titles.append(htit) 4463 4464 ###### 4465 acts = titles + lines + labels + grids + framelines 4466 acts += highlights + majorticks + minorticks + cones 4467 orig = (min_bns[0], min_bns[2], min_bns[4]) 4468 for a in acts: 4469 a.shift(orig) 4470 a.actor.PickableOff() 4471 a.properties.LightingOff() 4472 asse = Assembly(acts) 4473 asse.PickableOff() 4474 asse.name = "Axes" 4475 return asse
Draw axes for the input object. Check available fonts here.
Returns an vedo.Assembly
object.
Parameters
xtitle
, ['x'], x-axis title textxrange
, [None], x-axis range in format (xmin, ymin), default is automatic.number_of_divisions
, [None], approximate number of divisions on the longest axisaxes_linewidth
, [1], width of the axes linesgrid_linewidth
, [1], width of the grid linestitle_depth
, [0], extrusion fractional depth of title textx_values_and_labels
[], assign custom tick positions and labels [(pos1, label1), ...]xygrid
, [True], show a gridded wall on plane xyyzgrid
, [True], show a gridded wall on plane yzzxgrid
, [True], show a gridded wall on plane zxyzgrid2
, [False], show yz plane on opposite side of the bounding boxzxgrid2
, [False], show zx plane on opposite side of the bounding boxxygrid_transparent
[False], make grid plane completely transparentxygrid2_transparent
[False], make grid plane completely transparent on opposite side boxxyplane_color
, ['None'], color of the planexygrid_color
, ['None'], grid line colorxyalpha
, [0.15], grid plane opacityxyframe_line
, [0], add a frame for the plane, use value as the thicknessxyframe_color
, [None], color for the frame of the planeshow_ticks
, [True], show major ticksdigits
, [None], use this number of significant digits in scientific notationtitle_font
, [''], font for axes titleslabel_font
, [''], font for numeric labelstext_scale
, [1.0], global scaling factor for all text elements (titles, labels)htitle
, [''], header titlehtitle_size
, [0.03], header title sizehtitle_font
, [None], header font (defaults totitle_font
)htitle_italic
, [True], header font is italichtitle_color
, [None], header title color (defaults toxtitle_color
)htitle_backface_color
, [None], header title color on its backfacehtitle_justify
, ['bottom-center'], origin of the title justificationhtitle_offset
, [(0,0.01,0)], control offsets of header title in x, y and zxtitle_position
, [0.32], title fractional positions along axisxtitle_offset
, [0.05], title fractional offset distance from axis line, can be a listxtitle_justify
, [None], choose the origin of the bounding box of titlextitle_rotation
, [0], add a rotation of the axis title, can be a list (rx,ry,rz)xtitle_box
, [False], add a box around title textxline_color
, [automatic], color of the x-axisxtitle_color
, [automatic], color of the axis titlextitle_backface_color
, [None], color of axis title on its backfacextitle_size
, [0.025], size of the axis titlextitle_italic
, [0], a bool or float to make the font italicxhighlight_zero
, [True], draw a line highlighting zero position if in rangexhighlight_zero_color
, [auto], color of the line highlighting the zero positionxtick_length
, [0.005], radius of the major ticksxtick_thickness
, [0.0025], thickness of the major ticks along their axisxminor_ticks
, [1], number of minor ticks between two major ticksxlabel_color
, [automatic], color of numeric labels and ticksxlabel_backface_color
, [auto], back face color of numeric labels and ticksxlabel_size
, [0.015], size of the numeric labels along axisxlabel_rotation
, [0,list], numeric labels rotation (can be a list of 3 rotations)xlabel_offset
, [0.8,list], offset of the numeric labels (can be a list of 3 offsets)xlabel_justify
, [None], choose the origin of the bounding box of labelsxaxis_rotation
, [0], rotate the X axis elements (ticks and labels) around this same axisxyshift
[0.0], slide the xy-plane along z (the range is [0,1])xshift_along_y
[0.0], slide x-axis along the y-axis (the range is [0,1])tip_size
, [0.01], size of the arrow tip as a fraction of the bounding box diagonallimit_ratio
, [0.04], below this ratio don't plot smaller axisx_use_bounds
, [True], keep into account space occupied by labels when setting camerax_inverted
, [False], invert labels order and direction (only visually!)use_global
, [False], try to compute the global bounding box of visible actors
Example:
from vedo import Axes, Box, show box = Box(pos=(1,2,3), length=8, width=9, height=7).alpha(0.1) axs = Axes(box, c='k') # returns an Assembly object for a in axs.unpack(): print(a.name) show(box, axs).close()
Examples:
2542class RendererFrame(vtki.vtkActor2D): 2543 """ 2544 Add a line around the renderer subwindow. 2545 """ 2546 2547 def __init__(self, c="k", alpha=None, lw=None, padding=None): 2548 """ 2549 Add a line around the renderer subwindow. 2550 2551 Arguments: 2552 c : (color) 2553 color of the line. 2554 alpha : (float) 2555 opacity. 2556 lw : (int) 2557 line width in pixels. 2558 padding : (int) 2559 padding in pixel units. 2560 """ 2561 2562 if lw is None: 2563 lw = settings.renderer_frame_width 2564 if lw == 0: 2565 return None 2566 2567 if alpha is None: 2568 alpha = settings.renderer_frame_alpha 2569 2570 if padding is None: 2571 padding = settings.renderer_frame_padding 2572 2573 c = get_color(c) 2574 2575 ppoints = vtki.vtkPoints() # Generate the polyline 2576 xy = 1 - padding 2577 psqr = [ 2578 [padding, padding], 2579 [padding, xy], 2580 [xy, xy], 2581 [xy, padding], 2582 [padding, padding], 2583 ] 2584 for i, pt in enumerate(psqr): 2585 ppoints.InsertPoint(i, pt[0], pt[1], 0) 2586 lines = vtki.vtkCellArray() 2587 lines.InsertNextCell(len(psqr)) 2588 for i in range(len(psqr)): 2589 lines.InsertCellPoint(i) 2590 pd = vtki.vtkPolyData() 2591 pd.SetPoints(ppoints) 2592 pd.SetLines(lines) 2593 2594 mapper = vtki.new("PolyDataMapper2D") 2595 mapper.SetInputData(pd) 2596 cs = vtki.new("Coordinate") 2597 cs.SetCoordinateSystemToNormalizedViewport() 2598 mapper.SetTransformCoordinate(cs) 2599 2600 super().__init__() 2601 2602 self.GetPositionCoordinate().SetValue(0, 0) 2603 self.GetPosition2Coordinate().SetValue(1, 1) 2604 self.SetMapper(mapper) 2605 self.GetProperty().SetColor(c) 2606 self.GetProperty().SetOpacity(alpha) 2607 self.GetProperty().SetLineWidth(lw)
Add a line around the renderer subwindow.
2547 def __init__(self, c="k", alpha=None, lw=None, padding=None): 2548 """ 2549 Add a line around the renderer subwindow. 2550 2551 Arguments: 2552 c : (color) 2553 color of the line. 2554 alpha : (float) 2555 opacity. 2556 lw : (int) 2557 line width in pixels. 2558 padding : (int) 2559 padding in pixel units. 2560 """ 2561 2562 if lw is None: 2563 lw = settings.renderer_frame_width 2564 if lw == 0: 2565 return None 2566 2567 if alpha is None: 2568 alpha = settings.renderer_frame_alpha 2569 2570 if padding is None: 2571 padding = settings.renderer_frame_padding 2572 2573 c = get_color(c) 2574 2575 ppoints = vtki.vtkPoints() # Generate the polyline 2576 xy = 1 - padding 2577 psqr = [ 2578 [padding, padding], 2579 [padding, xy], 2580 [xy, xy], 2581 [xy, padding], 2582 [padding, padding], 2583 ] 2584 for i, pt in enumerate(psqr): 2585 ppoints.InsertPoint(i, pt[0], pt[1], 0) 2586 lines = vtki.vtkCellArray() 2587 lines.InsertNextCell(len(psqr)) 2588 for i in range(len(psqr)): 2589 lines.InsertCellPoint(i) 2590 pd = vtki.vtkPolyData() 2591 pd.SetPoints(ppoints) 2592 pd.SetLines(lines) 2593 2594 mapper = vtki.new("PolyDataMapper2D") 2595 mapper.SetInputData(pd) 2596 cs = vtki.new("Coordinate") 2597 cs.SetCoordinateSystemToNormalizedViewport() 2598 mapper.SetTransformCoordinate(cs) 2599 2600 super().__init__() 2601 2602 self.GetPositionCoordinate().SetValue(0, 0) 2603 self.GetPosition2Coordinate().SetValue(1, 1) 2604 self.SetMapper(mapper) 2605 self.GetProperty().SetColor(c) 2606 self.GetProperty().SetOpacity(alpha) 2607 self.GetProperty().SetLineWidth(lw)
Add a line around the renderer subwindow.
Arguments:
- c : (color) color of the line.
- alpha : (float) opacity.
- lw : (int) line width in pixels.
- padding : (int) padding in pixel units.
3044class Ruler2D(vtki.vtkAxisActor2D): 3045 """ 3046 Create a ruler with tick marks, labels and a title. 3047 """ 3048 3049 def __init__( 3050 self, 3051 lw=2, 3052 ticks=True, 3053 labels=False, 3054 c="k", 3055 alpha=1, 3056 title="", 3057 font="Calco", 3058 font_size=24, 3059 bc=None, 3060 ): 3061 """ 3062 Create a ruler with tick marks, labels and a title. 3063 3064 Ruler2D is a 2D actor; that is, it is drawn on the overlay 3065 plane and is not occluded by 3D geometry. 3066 To use this class, specify two points defining the start and end 3067 with update_points() as 3D points. 3068 3069 This class decides decides how to create reasonable tick 3070 marks and labels. 3071 3072 Labels are drawn on the "right" side of the axis. 3073 The "right" side is the side of the axis on the right. 3074 The way the labels and title line up with the axis and tick marks 3075 depends on whether the line is considered horizontal or vertical. 3076 3077 Arguments: 3078 lw : (int) 3079 width of the line in pixel units 3080 ticks : (bool) 3081 control if drawing the tick marks 3082 labels : (bool) 3083 control if drawing the numeric labels 3084 c : (color) 3085 color of the object 3086 alpha : (float) 3087 opacity of the object 3088 title : (str) 3089 title of the ruler 3090 font : (str) 3091 font face name. Check [available fonts here](https://vedo.embl.es/fonts). 3092 font_size : (int) 3093 font size 3094 bc : (color) 3095 background color of the title 3096 3097 Example: 3098 ```python 3099 from vedo import * 3100 plt = Plotter(axes=1, interactive=False) 3101 plt.show(Cube()) 3102 rul = Ruler2D() 3103 rul.set_points([0,0,0], [0.5,0.5,0.5]) 3104 plt.add(rul) 3105 plt.interactive().close() 3106 ``` 3107 ![](https://vedo.embl.es/images/feats/dist_tool.png) 3108 """ 3109 super().__init__() 3110 3111 plt = vedo.plotter_instance 3112 if not plt: 3113 vedo.logger.error("Ruler2D need to initialize Plotter first.") 3114 raise RuntimeError() 3115 3116 self.p0 = [0, 0, 0] 3117 self.p1 = [0, 0, 0] 3118 self.distance = 0 3119 self.title = title 3120 3121 prop = self.GetProperty() 3122 tprop = self.GetTitleTextProperty() 3123 3124 self.SetTitle(title) 3125 self.SetNumberOfLabels(9) 3126 3127 if not font: 3128 font = settings.default_font 3129 if font.lower() == "courier": 3130 tprop.SetFontFamilyToCourier() 3131 elif font.lower() == "times": 3132 tprop.SetFontFamilyToTimes() 3133 elif font.lower() == "arial": 3134 tprop.SetFontFamilyToArial() 3135 else: 3136 tprop.SetFontFamily(vtki.VTK_FONT_FILE) 3137 tprop.SetFontFile(utils.get_font_path(font)) 3138 tprop.SetFontSize(font_size) 3139 tprop.BoldOff() 3140 tprop.ItalicOff() 3141 tprop.ShadowOff() 3142 tprop.SetColor(get_color(c)) 3143 tprop.SetOpacity(alpha) 3144 if bc is not None: 3145 bc = get_color(bc) 3146 tprop.SetBackgroundColor(bc) 3147 tprop.SetBackgroundOpacity(alpha) 3148 3149 lprop = vtki.vtkTextProperty() 3150 lprop.ShallowCopy(tprop) 3151 self.SetLabelTextProperty(lprop) 3152 3153 self.SetLabelFormat("%0.3g") 3154 self.SetTickVisibility(ticks) 3155 self.SetLabelVisibility(labels) 3156 prop.SetLineWidth(lw) 3157 prop.SetColor(get_color(c)) 3158 3159 self.renderer = plt.renderer 3160 self.cid = plt.interactor.AddObserver("RenderEvent", self._update_viz, 1.0) 3161 3162 def color(self, c) -> Self: 3163 """Assign a new color.""" 3164 c = get_color(c) 3165 self.GetTitleTextProperty().SetColor(c) 3166 self.GetLabelTextProperty().SetColor(c) 3167 self.GetProperty().SetColor(c) 3168 return self 3169 3170 def off(self) -> None: 3171 """Switch off the ruler completely.""" 3172 self.renderer.RemoveObserver(self.cid) 3173 self.renderer.RemoveActor(self) 3174 3175 def set_points(self, p0, p1) -> Self: 3176 """Set new values for the ruler start and end points.""" 3177 self.p0 = np.asarray(p0) 3178 self.p1 = np.asarray(p1) 3179 self._update_viz(0, 0) 3180 return self 3181 3182 def _update_viz(self, evt, name) -> None: 3183 ren = self.renderer 3184 view_size = np.array(ren.GetSize()) 3185 3186 ren.SetWorldPoint(*self.p0, 1) 3187 ren.WorldToDisplay() 3188 disp_point1 = ren.GetDisplayPoint()[:2] 3189 disp_point1 = np.array(disp_point1) / view_size 3190 3191 ren.SetWorldPoint(*self.p1, 1) 3192 ren.WorldToDisplay() 3193 disp_point2 = ren.GetDisplayPoint()[:2] 3194 disp_point2 = np.array(disp_point2) / view_size 3195 3196 self.SetPoint1(*disp_point1) 3197 self.SetPoint2(*disp_point2) 3198 self.distance = np.linalg.norm(self.p1 - self.p0) 3199 self.SetRange(0.0, float(self.distance)) 3200 if not self.title: 3201 self.SetTitle(utils.precision(self.distance, 3))
Create a ruler with tick marks, labels and a title.
3049 def __init__( 3050 self, 3051 lw=2, 3052 ticks=True, 3053 labels=False, 3054 c="k", 3055 alpha=1, 3056 title="", 3057 font="Calco", 3058 font_size=24, 3059 bc=None, 3060 ): 3061 """ 3062 Create a ruler with tick marks, labels and a title. 3063 3064 Ruler2D is a 2D actor; that is, it is drawn on the overlay 3065 plane and is not occluded by 3D geometry. 3066 To use this class, specify two points defining the start and end 3067 with update_points() as 3D points. 3068 3069 This class decides decides how to create reasonable tick 3070 marks and labels. 3071 3072 Labels are drawn on the "right" side of the axis. 3073 The "right" side is the side of the axis on the right. 3074 The way the labels and title line up with the axis and tick marks 3075 depends on whether the line is considered horizontal or vertical. 3076 3077 Arguments: 3078 lw : (int) 3079 width of the line in pixel units 3080 ticks : (bool) 3081 control if drawing the tick marks 3082 labels : (bool) 3083 control if drawing the numeric labels 3084 c : (color) 3085 color of the object 3086 alpha : (float) 3087 opacity of the object 3088 title : (str) 3089 title of the ruler 3090 font : (str) 3091 font face name. Check [available fonts here](https://vedo.embl.es/fonts). 3092 font_size : (int) 3093 font size 3094 bc : (color) 3095 background color of the title 3096 3097 Example: 3098 ```python 3099 from vedo import * 3100 plt = Plotter(axes=1, interactive=False) 3101 plt.show(Cube()) 3102 rul = Ruler2D() 3103 rul.set_points([0,0,0], [0.5,0.5,0.5]) 3104 plt.add(rul) 3105 plt.interactive().close() 3106 ``` 3107 ![](https://vedo.embl.es/images/feats/dist_tool.png) 3108 """ 3109 super().__init__() 3110 3111 plt = vedo.plotter_instance 3112 if not plt: 3113 vedo.logger.error("Ruler2D need to initialize Plotter first.") 3114 raise RuntimeError() 3115 3116 self.p0 = [0, 0, 0] 3117 self.p1 = [0, 0, 0] 3118 self.distance = 0 3119 self.title = title 3120 3121 prop = self.GetProperty() 3122 tprop = self.GetTitleTextProperty() 3123 3124 self.SetTitle(title) 3125 self.SetNumberOfLabels(9) 3126 3127 if not font: 3128 font = settings.default_font 3129 if font.lower() == "courier": 3130 tprop.SetFontFamilyToCourier() 3131 elif font.lower() == "times": 3132 tprop.SetFontFamilyToTimes() 3133 elif font.lower() == "arial": 3134 tprop.SetFontFamilyToArial() 3135 else: 3136 tprop.SetFontFamily(vtki.VTK_FONT_FILE) 3137 tprop.SetFontFile(utils.get_font_path(font)) 3138 tprop.SetFontSize(font_size) 3139 tprop.BoldOff() 3140 tprop.ItalicOff() 3141 tprop.ShadowOff() 3142 tprop.SetColor(get_color(c)) 3143 tprop.SetOpacity(alpha) 3144 if bc is not None: 3145 bc = get_color(bc) 3146 tprop.SetBackgroundColor(bc) 3147 tprop.SetBackgroundOpacity(alpha) 3148 3149 lprop = vtki.vtkTextProperty() 3150 lprop.ShallowCopy(tprop) 3151 self.SetLabelTextProperty(lprop) 3152 3153 self.SetLabelFormat("%0.3g") 3154 self.SetTickVisibility(ticks) 3155 self.SetLabelVisibility(labels) 3156 prop.SetLineWidth(lw) 3157 prop.SetColor(get_color(c)) 3158 3159 self.renderer = plt.renderer 3160 self.cid = plt.interactor.AddObserver("RenderEvent", self._update_viz, 1.0)
Create a ruler with tick marks, labels and a title.
Ruler2D is a 2D actor; that is, it is drawn on the overlay plane and is not occluded by 3D geometry. To use this class, specify two points defining the start and end with update_points() as 3D points.
This class decides decides how to create reasonable tick marks and labels.
Labels are drawn on the "right" side of the axis. The "right" side is the side of the axis on the right. The way the labels and title line up with the axis and tick marks depends on whether the line is considered horizontal or vertical.
Arguments:
- lw : (int) width of the line in pixel units
- ticks : (bool) control if drawing the tick marks
- labels : (bool) control if drawing the numeric labels
- c : (color) color of the object
- alpha : (float) opacity of the object
- title : (str) title of the ruler
- font : (str) font face name. Check available fonts here.
- font_size : (int) font size
- bc : (color) background color of the title
Example:
from vedo import * plt = Plotter(axes=1, interactive=False) plt.show(Cube()) rul = Ruler2D() rul.set_points([0,0,0], [0.5,0.5,0.5]) plt.add(rul) plt.interactive().close()
3162 def color(self, c) -> Self: 3163 """Assign a new color.""" 3164 c = get_color(c) 3165 self.GetTitleTextProperty().SetColor(c) 3166 self.GetLabelTextProperty().SetColor(c) 3167 self.GetProperty().SetColor(c) 3168 return self
Assign a new color.
2779def Ruler3D( 2780 p1, 2781 p2, 2782 units_scale=1, 2783 label="", 2784 s=None, 2785 font=None, 2786 italic=0, 2787 prefix="", 2788 units="", # eg.'μm' 2789 c=(0.2, 0.1, 0.1), 2790 alpha=1, 2791 lw=1, 2792 precision=3, 2793 label_rotation=0, 2794 axis_rotation=0, 2795 tick_angle=90, 2796) -> Mesh: 2797 """ 2798 Build a 3D ruler to indicate the distance of two points p1 and p2. 2799 2800 Arguments: 2801 label : (str) 2802 alternative fixed label to be shown 2803 units_scale : (float) 2804 factor to scale units (e.g. μm to mm) 2805 s : (float) 2806 size of the label 2807 font : (str) 2808 font face. Check [available fonts here](https://vedo.embl.es/fonts). 2809 italic : (float) 2810 italicness of the font in the range [0,1] 2811 units : (str) 2812 string to be appended to the numeric value 2813 lw : (int) 2814 line width in pixel units 2815 precision : (int) 2816 nr of significant digits to be shown 2817 label_rotation : (float) 2818 initial rotation of the label around the z-axis 2819 axis_rotation : (float) 2820 initial rotation of the line around the main axis 2821 tick_angle : (float) 2822 initial rotation of the line around the main axis 2823 2824 Examples: 2825 - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py) 2826 2827 ![](https://vedo.embl.es/images/pyplot/goniometer.png) 2828 """ 2829 2830 if units_scale != 1.0 and units == "": 2831 raise ValueError( 2832 "When setting 'units_scale' to a value other than 1, " 2833 + "a 'units' arguments must be specified." 2834 ) 2835 2836 try: 2837 p1 = p1.pos() 2838 except AttributeError: 2839 pass 2840 2841 try: 2842 p2 = p2.pos() 2843 except AttributeError: 2844 pass 2845 2846 if len(p1) == 2: 2847 p1 = [p1[0], p1[1], 0.0] 2848 if len(p2) == 2: 2849 p2 = [p2[0], p2[1], 0.0] 2850 2851 p1, p2 = np.asarray(p1), np.asarray(p2) 2852 q1, q2 = [0, 0, 0], [utils.mag(p2 - p1), 0, 0] 2853 q1, q2 = np.array(q1), np.array(q2) 2854 v = q2 - q1 2855 d = utils.mag(v) * units_scale 2856 2857 pos = np.array(p1) 2858 p1 = p1 - pos 2859 p2 = p2 - pos 2860 2861 if s is None: 2862 s = d * 0.02 * (1 / units_scale) 2863 2864 if not label: 2865 label = str(d) 2866 if precision: 2867 label = utils.precision(d, precision) 2868 if prefix: 2869 label = prefix + "~" + label 2870 if units: 2871 label += "~" + units 2872 2873 lb = shapes.Text3D(label, s=s, font=font, italic=italic, justify="center") 2874 if label_rotation: 2875 lb.rotate_z(label_rotation) 2876 lb.pos((q1 + q2) / 2) 2877 2878 x0, x1 = lb.xbounds() 2879 gap = [(x1 - x0) / 2, 0, 0] 2880 pc1 = (v / 2 - gap) * 0.9 + q1 2881 pc2 = q2 - (v / 2 - gap) * 0.9 2882 2883 lc1 = shapes.Line(q1 - v / 50, pc1).lw(lw) 2884 lc2 = shapes.Line(q2 + v / 50, pc2).lw(lw) 2885 2886 zs = np.array([0, d / 50 * (1 / units_scale), 0]) 2887 ml1 = shapes.Line(-zs, zs).lw(lw) 2888 ml2 = shapes.Line(-zs, zs).lw(lw) 2889 ml1.rotate_z(tick_angle - 90).pos(q1) 2890 ml2.rotate_z(tick_angle - 90).pos(q2) 2891 2892 c1 = shapes.Circle(q1, r=d / 180 * (1 / units_scale), res=24) 2893 c2 = shapes.Circle(q2, r=d / 180 * (1 / units_scale), res=24) 2894 2895 macts = merge(lb, lc1, lc2, c1, c2, ml1, ml2) 2896 macts.c(c).alpha(alpha) 2897 macts.properties.SetLineWidth(lw) 2898 macts.properties.LightingOff() 2899 macts.actor.UseBoundsOff() 2900 macts.rotate_x(axis_rotation) 2901 macts.reorient(q2 - q1, p2 - p1) 2902 macts.pos(pos) 2903 macts.bc("tomato").pickable(False) 2904 return macts
Build a 3D ruler to indicate the distance of two points p1 and p2.
Arguments:
- label : (str) alternative fixed label to be shown
- units_scale : (float) factor to scale units (e.g. μm to mm)
- s : (float) size of the label
- font : (str) font face. Check available fonts here.
- italic : (float) italicness of the font in the range [0,1]
- units : (str) string to be appended to the numeric value
- lw : (int) line width in pixel units
- precision : (int) nr of significant digits to be shown
- label_rotation : (float) initial rotation of the label around the z-axis
- axis_rotation : (float) initial rotation of the line around the main axis
- tick_angle : (float) initial rotation of the line around the main axis
Examples:
2907def RulerAxes( 2908 inputobj, 2909 xtitle="", 2910 ytitle="", 2911 ztitle="", 2912 xlabel="", 2913 ylabel="", 2914 zlabel="", 2915 xpadding=0.05, 2916 ypadding=0.04, 2917 zpadding=0, 2918 font="Normografo", 2919 s=None, 2920 italic=0, 2921 units="", 2922 c=(0.2, 0, 0), 2923 alpha=1, 2924 lw=1, 2925 precision=3, 2926 label_rotation=0, 2927 xaxis_rotation=0, 2928 yaxis_rotation=0, 2929 zaxis_rotation=0, 2930 xycross=True, 2931) -> Union[Mesh, None]: 2932 """ 2933 A 3D ruler axes to indicate the sizes of the input scene or object. 2934 2935 Arguments: 2936 xtitle : (str) 2937 name of the axis or title 2938 xlabel : (str) 2939 alternative fixed label to be shown instead of the distance 2940 s : (float) 2941 size of the label 2942 font : (str) 2943 font face. Check [available fonts here](https://vedo.embl.es/fonts). 2944 italic : (float) 2945 italicness of the font in the range [0,1] 2946 units : (str) 2947 string to be appended to the numeric value 2948 lw : (int) 2949 line width in pixel units 2950 precision : (int) 2951 nr of significant digits to be shown 2952 label_rotation : (float) 2953 initial rotation of the label around the z-axis 2954 [x,y,z]axis_rotation : (float) 2955 initial rotation of the line around the main axis in degrees 2956 xycross : (bool) 2957 show two back crossing lines in the xy plane 2958 2959 Examples: 2960 - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py) 2961 """ 2962 if utils.is_sequence(inputobj): 2963 x0, x1, y0, y1, z0, z1 = inputobj 2964 else: 2965 x0, x1, y0, y1, z0, z1 = inputobj.bounds() 2966 dx, dy, dz = (y1 - y0) * xpadding, (x1 - x0) * ypadding, (y1 - y0) * zpadding 2967 d = np.sqrt((y1 - y0) ** 2 + (x1 - x0) ** 2 + (z1 - z0) ** 2) 2968 2969 if not d: 2970 return None 2971 2972 if s is None: 2973 s = d / 75 2974 2975 acts, rx, ry = [], None, None 2976 if xtitle is not None and (x1 - x0) / d > 0.1: 2977 rx = Ruler3D( 2978 [x0, y0 - dx, z0], 2979 [x1, y0 - dx, z0], 2980 s=s, 2981 font=font, 2982 precision=precision, 2983 label_rotation=label_rotation, 2984 axis_rotation=xaxis_rotation, 2985 lw=lw, 2986 italic=italic, 2987 prefix=xtitle, 2988 label=xlabel, 2989 units=units, 2990 ) 2991 acts.append(rx) 2992 2993 if ytitle is not None and (y1 - y0) / d > 0.1: 2994 ry = Ruler3D( 2995 [x1 + dy, y0, z0], 2996 [x1 + dy, y1, z0], 2997 s=s, 2998 font=font, 2999 precision=precision, 3000 label_rotation=label_rotation, 3001 axis_rotation=yaxis_rotation, 3002 lw=lw, 3003 italic=italic, 3004 prefix=ytitle, 3005 label=ylabel, 3006 units=units, 3007 ) 3008 acts.append(ry) 3009 3010 if ztitle is not None and (z1 - z0) / d > 0.1: 3011 rz = Ruler3D( 3012 [x0 - dy, y0 + dz, z0], 3013 [x0 - dy, y0 + dz, z1], 3014 s=s, 3015 font=font, 3016 precision=precision, 3017 label_rotation=label_rotation, 3018 axis_rotation=zaxis_rotation + 90, 3019 lw=lw, 3020 italic=italic, 3021 prefix=ztitle, 3022 label=zlabel, 3023 units=units, 3024 ) 3025 acts.append(rz) 3026 3027 if xycross and rx and ry: 3028 lx = shapes.Line([x0, y0, z0], [x0, y1 + dx, z0]) 3029 ly = shapes.Line([x0 - dy, y1, z0], [x1, y1, z0]) 3030 d = min((x1 - x0), (y1 - y0)) / 200 3031 cxy = shapes.Circle([x0, y1, z0], r=d, res=15) 3032 acts.extend([lx, ly, cxy]) 3033 3034 macts = merge(acts) 3035 if not macts: 3036 return None 3037 macts.c(c).alpha(alpha).bc("t") 3038 macts.actor.UseBoundsOff() 3039 macts.actor.PickableOff() 3040 return macts
A 3D ruler axes to indicate the sizes of the input scene or object.
Arguments:
- xtitle : (str) name of the axis or title
- xlabel : (str) alternative fixed label to be shown instead of the distance
- s : (float) size of the label
- font : (str) font face. Check available fonts here.
- italic : (float) italicness of the font in the range [0,1]
- units : (str) string to be appended to the numeric value
- lw : (int) line width in pixel units
- precision : (int) nr of significant digits to be shown
- label_rotation : (float) initial rotation of the label around the z-axis
- [x,y,z]axis_rotation : (float) initial rotation of the line around the main axis in degrees
- xycross : (bool) show two back crossing lines in the xy plane
Examples:
3205class DistanceTool(Group): 3206 """ 3207 Create a tool to measure the distance between two clicked points. 3208 """ 3209 3210 def __init__(self, plotter=None, c="k", lw=2): 3211 """ 3212 Create a tool to measure the distance between two clicked points. 3213 3214 Example: 3215 ```python 3216 from vedo import * 3217 mesh = ParametricShape("RandomHills").c("red5") 3218 plt = Plotter(axes=1) 3219 dtool = DistanceTool() 3220 dtool.on() 3221 plt.show(mesh, dtool) 3222 dtool.off() 3223 ``` 3224 ![](https://vedo.embl.es/images/feats/dist_tool.png) 3225 """ 3226 super().__init__() 3227 3228 self.p0 = [0, 0, 0] 3229 self.p1 = [0, 0, 0] 3230 self.distance = 0 3231 if plotter is None: 3232 plotter = vedo.plotter_instance 3233 self.plotter = plotter 3234 self.callback = None 3235 self.cid = None 3236 self.color = c 3237 self.linewidth = lw 3238 self.toggle = True 3239 self.ruler = None 3240 self.title = "" 3241 3242 def on(self) -> Self: 3243 """Switch tool on.""" 3244 self.cid = self.plotter.add_callback("click", self._onclick) 3245 self.VisibilityOn() 3246 self.plotter.render() 3247 return self 3248 3249 def off(self) -> None: 3250 """Switch tool off.""" 3251 self.plotter.remove_callback(self.cid) 3252 self.VisibilityOff() 3253 self.ruler.off() 3254 self.plotter.render() 3255 3256 def _onclick(self, event): 3257 if not event.actor: 3258 return 3259 3260 self.clear() 3261 3262 acts = [] 3263 if self.toggle: 3264 self.p0 = event.picked3d 3265 acts.append(Point(self.p0, c=self.color)) 3266 else: 3267 self.p1 = event.picked3d 3268 self.distance = np.linalg.norm(self.p1 - self.p0) 3269 acts.append(Point(self.p0, c=self.color)) 3270 acts.append(Point(self.p1, c=self.color)) 3271 self.ruler = Ruler2D(c=self.color) 3272 self.ruler.set_points(self.p0, self.p1) 3273 acts.append(self.ruler) 3274 3275 if self.callback is not None: 3276 self.callback(event) 3277 3278 for a in acts: 3279 try: 3280 self += a.actor 3281 except AttributeError: 3282 self += a 3283 self.toggle = not self.toggle
Create a tool to measure the distance between two clicked points.
3210 def __init__(self, plotter=None, c="k", lw=2): 3211 """ 3212 Create a tool to measure the distance between two clicked points. 3213 3214 Example: 3215 ```python 3216 from vedo import * 3217 mesh = ParametricShape("RandomHills").c("red5") 3218 plt = Plotter(axes=1) 3219 dtool = DistanceTool() 3220 dtool.on() 3221 plt.show(mesh, dtool) 3222 dtool.off() 3223 ``` 3224 ![](https://vedo.embl.es/images/feats/dist_tool.png) 3225 """ 3226 super().__init__() 3227 3228 self.p0 = [0, 0, 0] 3229 self.p1 = [0, 0, 0] 3230 self.distance = 0 3231 if plotter is None: 3232 plotter = vedo.plotter_instance 3233 self.plotter = plotter 3234 self.callback = None 3235 self.cid = None 3236 self.color = c 3237 self.linewidth = lw 3238 self.toggle = True 3239 self.ruler = None 3240 self.title = ""
Create a tool to measure the distance between two clicked points.
Example:
from vedo import * mesh = ParametricShape("RandomHills").c("red5") plt = Plotter(axes=1) dtool = DistanceTool() dtool.on() plt.show(mesh, dtool) dtool.off()
3242 def on(self) -> Self: 3243 """Switch tool on.""" 3244 self.cid = self.plotter.add_callback("click", self._onclick) 3245 self.VisibilityOn() 3246 self.plotter.render() 3247 return self
Switch tool on.
3249 def off(self) -> None: 3250 """Switch tool off.""" 3251 self.plotter.remove_callback(self.cid) 3252 self.VisibilityOff() 3253 self.ruler.off() 3254 self.plotter.render()
Switch tool off.
Inherited Members
688class SplineTool(vtki.vtkContourWidget): 689 """ 690 Spline tool, draw a spline through a set of points interactively. 691 """ 692 693 def __init__( 694 self, 695 points, 696 pc="k", 697 ps=8, 698 lc="r4", 699 ac="g5", 700 lw=2, 701 alpha=1, 702 closed=False, 703 ontop=True, 704 can_add_nodes=True, 705 ): 706 """ 707 Spline tool, draw a spline through a set of points interactively. 708 709 Arguments: 710 points : (list), Points 711 initial set of points. 712 pc : (str) 713 point color. 714 ps : (int) 715 point size. 716 lc : (str) 717 line color. 718 ac : (str) 719 active point color. 720 lw : (int) 721 line width. 722 alpha : (float) 723 line transparency level. 724 closed : (bool) 725 spline is closed or open. 726 ontop : (bool) 727 show it always on top of other objects. 728 can_add_nodes : (bool) 729 allow to add (or remove) new nodes interactively. 730 731 Examples: 732 - [spline_tool.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/spline_tool.py) 733 734 ![](https://vedo.embl.es/images/basic/spline_tool.png) 735 """ 736 super().__init__() 737 738 self.representation = self.GetRepresentation() 739 self.representation.SetAlwaysOnTop(ontop) 740 self.SetAllowNodePicking(can_add_nodes) 741 742 self.representation.GetLinesProperty().SetColor(get_color(lc)) 743 self.representation.GetLinesProperty().SetLineWidth(lw) 744 self.representation.GetLinesProperty().SetOpacity(alpha) 745 if lw == 0 or alpha == 0: 746 self.representation.GetLinesProperty().SetOpacity(0) 747 748 self.representation.GetActiveProperty().SetLineWidth(lw + 1) 749 self.representation.GetActiveProperty().SetColor(get_color(ac)) 750 751 self.representation.GetProperty().SetColor(get_color(pc)) 752 self.representation.GetProperty().SetPointSize(ps) 753 self.representation.GetProperty().RenderPointsAsSpheresOn() 754 755 # self.representation.BuildRepresentation() # crashes 756 757 self.SetRepresentation(self.representation) 758 759 if utils.is_sequence(points): 760 self.points = Points(points) 761 else: 762 self.points = points 763 764 self.closed = closed 765 766 @property 767 def interactor(self): 768 """Return the current interactor.""" 769 return self.GetInteractor() 770 771 @interactor.setter 772 def interactor(self, iren): 773 """Set the current interactor.""" 774 self.SetInteractor(iren) 775 776 def add(self, pt) -> "SplineTool": 777 """ 778 Add one point at a specified position in space if 3D, 779 or 2D screen-display position if 2D. 780 """ 781 if len(pt) == 2: 782 self.representation.AddNodeAtDisplayPosition(int(pt[0]), int(pt[1])) 783 else: 784 self.representation.AddNodeAtWorldPosition(pt) 785 return self 786 787 def add_observer(self, event, func, priority=1) -> int: 788 """Add an observer to the widget.""" 789 event = utils.get_vtk_name_event(event) 790 cid = self.AddObserver(event, func, priority) 791 return cid 792 793 def remove(self, i: int) -> "SplineTool": 794 """Remove specific node by its index""" 795 self.representation.DeleteNthNode(i) 796 return self 797 798 def on(self) -> "SplineTool": 799 """Activate/Enable the tool""" 800 self.On() 801 self.Render() 802 return self 803 804 def off(self) -> "SplineTool": 805 """Disactivate/Disable the tool""" 806 self.Off() 807 self.Render() 808 return self 809 810 def render(self) -> "SplineTool": 811 """Render the spline""" 812 self.Render() 813 return self 814 815 # def bounds(self) -> np.ndarray: 816 # """Retrieve the bounding box of the spline as [x0,x1, y0,y1, z0,z1]""" 817 # return np.array(self.GetBounds()) 818 819 def spline(self) -> vedo.Line: 820 """Return the vedo.Spline object.""" 821 self.representation.SetClosedLoop(self.closed) 822 self.representation.BuildRepresentation() 823 pd = self.representation.GetContourRepresentationAsPolyData() 824 ln = vedo.Line(pd, lw=2, c="k") 825 return ln 826 827 def nodes(self, onscreen=False) -> np.ndarray: 828 """Return the current position in space (or on 2D screen-display) of the spline nodes.""" 829 n = self.representation.GetNumberOfNodes() 830 pts = [] 831 for i in range(n): 832 p = [0.0, 0.0, 0.0] 833 if onscreen: 834 self.representation.GetNthNodeDisplayPosition(i, p) 835 else: 836 self.representation.GetNthNodeWorldPosition(i, p) 837 pts.append(p) 838 return np.array(pts)
Spline tool, draw a spline through a set of points interactively.
693 def __init__( 694 self, 695 points, 696 pc="k", 697 ps=8, 698 lc="r4", 699 ac="g5", 700 lw=2, 701 alpha=1, 702 closed=False, 703 ontop=True, 704 can_add_nodes=True, 705 ): 706 """ 707 Spline tool, draw a spline through a set of points interactively. 708 709 Arguments: 710 points : (list), Points 711 initial set of points. 712 pc : (str) 713 point color. 714 ps : (int) 715 point size. 716 lc : (str) 717 line color. 718 ac : (str) 719 active point color. 720 lw : (int) 721 line width. 722 alpha : (float) 723 line transparency level. 724 closed : (bool) 725 spline is closed or open. 726 ontop : (bool) 727 show it always on top of other objects. 728 can_add_nodes : (bool) 729 allow to add (or remove) new nodes interactively. 730 731 Examples: 732 - [spline_tool.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/spline_tool.py) 733 734 ![](https://vedo.embl.es/images/basic/spline_tool.png) 735 """ 736 super().__init__() 737 738 self.representation = self.GetRepresentation() 739 self.representation.SetAlwaysOnTop(ontop) 740 self.SetAllowNodePicking(can_add_nodes) 741 742 self.representation.GetLinesProperty().SetColor(get_color(lc)) 743 self.representation.GetLinesProperty().SetLineWidth(lw) 744 self.representation.GetLinesProperty().SetOpacity(alpha) 745 if lw == 0 or alpha == 0: 746 self.representation.GetLinesProperty().SetOpacity(0) 747 748 self.representation.GetActiveProperty().SetLineWidth(lw + 1) 749 self.representation.GetActiveProperty().SetColor(get_color(ac)) 750 751 self.representation.GetProperty().SetColor(get_color(pc)) 752 self.representation.GetProperty().SetPointSize(ps) 753 self.representation.GetProperty().RenderPointsAsSpheresOn() 754 755 # self.representation.BuildRepresentation() # crashes 756 757 self.SetRepresentation(self.representation) 758 759 if utils.is_sequence(points): 760 self.points = Points(points) 761 else: 762 self.points = points 763 764 self.closed = closed
Spline tool, draw a spline through a set of points interactively.
Arguments:
- points : (list), Points initial set of points.
- pc : (str) point color.
- ps : (int) point size.
- lc : (str) line color.
- ac : (str) active point color.
- lw : (int) line width.
- alpha : (float) line transparency level.
- closed : (bool) spline is closed or open.
- ontop : (bool) show it always on top of other objects.
- can_add_nodes : (bool) allow to add (or remove) new nodes interactively.
Examples:
766 @property 767 def interactor(self): 768 """Return the current interactor.""" 769 return self.GetInteractor()
Return the current interactor.
776 def add(self, pt) -> "SplineTool": 777 """ 778 Add one point at a specified position in space if 3D, 779 or 2D screen-display position if 2D. 780 """ 781 if len(pt) == 2: 782 self.representation.AddNodeAtDisplayPosition(int(pt[0]), int(pt[1])) 783 else: 784 self.representation.AddNodeAtWorldPosition(pt) 785 return self
Add one point at a specified position in space if 3D, or 2D screen-display position if 2D.
787 def add_observer(self, event, func, priority=1) -> int: 788 """Add an observer to the widget.""" 789 event = utils.get_vtk_name_event(event) 790 cid = self.AddObserver(event, func, priority) 791 return cid
Add an observer to the widget.
793 def remove(self, i: int) -> "SplineTool": 794 """Remove specific node by its index""" 795 self.representation.DeleteNthNode(i) 796 return self
Remove specific node by its index
798 def on(self) -> "SplineTool": 799 """Activate/Enable the tool""" 800 self.On() 801 self.Render() 802 return self
Activate/Enable the tool
804 def off(self) -> "SplineTool": 805 """Disactivate/Disable the tool""" 806 self.Off() 807 self.Render() 808 return self
Disactivate/Disable the tool
819 def spline(self) -> vedo.Line: 820 """Return the vedo.Spline object.""" 821 self.representation.SetClosedLoop(self.closed) 822 self.representation.BuildRepresentation() 823 pd = self.representation.GetContourRepresentationAsPolyData() 824 ln = vedo.Line(pd, lw=2, c="k") 825 return ln
Return the vedo.Spline object.
827 def nodes(self, onscreen=False) -> np.ndarray: 828 """Return the current position in space (or on 2D screen-display) of the spline nodes.""" 829 n = self.representation.GetNumberOfNodes() 830 pts = [] 831 for i in range(n): 832 p = [0.0, 0.0, 0.0] 833 if onscreen: 834 self.representation.GetNthNodeDisplayPosition(i, p) 835 else: 836 self.representation.GetNthNodeWorldPosition(i, p) 837 pts.append(p) 838 return np.array(pts)
Return the current position in space (or on 2D screen-display) of the spline nodes.
841class DrawingWidget: 842 def __init__(self, obj, c="green5", lw=4, closed=False, snap_to_image=False): 843 """ 844 3D widget for tracing on planar props. 845 This is primarily designed for manually tracing over image data. 846 847 - Any object can be input rather than just 2D images 848 - The widget fires pick events at the input prop to decide where to move its handles 849 - The widget has 2D glyphs for handles instead of 3D spheres. 850 851 The button actions and key modifiers are as follows for controlling the widget: 852 1) left button click over the image, hold and drag draws a free hand line. 853 2) left button click and release erases the widget line, if it exists, and repositions the first handle. 854 3) middle button click starts a snap drawn line. 855 The line is terminated by clicking the middle button while ressing the ctrl key. 856 4) when tracing a continuous or snap drawn line, if the last cursor position is within a specified 857 tolerance to the first handle, the widget line will form a closed loop. 858 5) right button clicking and holding on any handle that is part of a snap drawn line allows handle dragging: 859 existing line segments are updated accordingly. If the path is open and closing_radius is set, 860 the path can be closed by repositioning the first and last points over one another. 861 6) Ctrl + right button down on any handle will erase it: existing snap drawn line segments are updated accordingly. 862 If the line was formed by continuous tracing, the line is deleted leaving one handle. 863 7) Shift + right button down on any snap drawn line segment will insert a handle at the cursor position. 864 The line segment is split accordingly. 865 866 Arguments: 867 obj : vtkProp 868 The prop to trace on. 869 c : str, optional 870 The color of the line. The default is "green5". 871 lw : int, optional 872 The line width. The default is 4. 873 closed : bool, optional 874 Whether to close the line. The default is False. 875 snap_to_image : bool, optional 876 Whether to snap to the image. The default is False. 877 878 Example: 879 - [spline_draw2.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/spline_draw2.py) 880 """ 881 882 self.widget = vtki.new("ImageTracerWidget") 883 884 self.line = None 885 self.line_properties = self.widget.GetLineProperty() 886 self.line_properties.SetColor(vedo.get_color(c)) 887 self.line_properties.SetLineWidth(lw) 888 self.callback_id = None 889 self.event_name = "EndInteractionEvent" 890 891 if vedo.plotter_instance: 892 self.widget.SetInteractor(vedo.plotter_instance.interactor) 893 if vedo.plotter_instance.renderer: 894 self.widget.SetDefaultRenderer(vedo.plotter_instance.renderer) 895 896 try: 897 self.widget.SetViewProp(obj.actor) 898 except AttributeError: 899 self.widget.SetViewProp(obj) 900 901 if closed: 902 closing_radius = 1e10 903 self.widget.SetAutoClose(1) 904 self.widget.SetCaptureRadius(closing_radius) 905 906 self.widget.SetProjectToPlane(0) 907 self.widget.SetProjectionNormal(2) # XY plane 908 self.widget.SetProjectionPosition(0) 909 self.widget.SetSnapToImage(snap_to_image) 910 911 def callback(self, widget, eventId) -> None: 912 path = vtki.vtkPolyData() 913 widget.GetPath(path) 914 self.line = vedo.shapes.Line(path, c=self.line_properties.GetColor()) 915 # print(f"There are {path.GetNumberOfPoints()} points in the line.") 916 917 def add_observer(self, event, func, priority=1) -> int: 918 """Add an observer to the widget.""" 919 event = utils.get_vtk_name_event(event) 920 cid = self.widget.AddObserver(event, func, priority) 921 return cid 922 923 @property 924 def interactor(self): 925 return self.widget.GetInteractor() 926 927 @interactor.setter 928 def interactor(self, value): 929 self.widget.SetInteractor(value) 930 931 @property 932 def renderer(self): 933 return self.widget.GetDefaultRenderer() 934 935 @renderer.setter 936 def renderer(self, value): 937 self.widget.SetDefaultRenderer(value) 938 939 def on(self) -> Self: 940 self.widget.On() 941 ev_name = vedo.utils.get_vtk_name_event(self.event_name) 942 self.callback_id = self.widget.AddObserver(ev_name, self.callback, 1000) 943 return self 944 945 def off(self) -> None: 946 self.widget.Off() 947 self.widget.RemoveObserver(self.callback_id) 948 949 def freeze(self, value=True) -> Self: 950 self.widget.SetInteraction(not value) 951 return self 952 953 def remove(self) -> None: 954 self.widget.Off() 955 self.widget.RemoveObserver(self.callback_id) 956 self.widget.SetInteractor(None) 957 self.line = None 958 self.line_properties = None 959 self.callback_id = None 960 self.widget = None
842 def __init__(self, obj, c="green5", lw=4, closed=False, snap_to_image=False): 843 """ 844 3D widget for tracing on planar props. 845 This is primarily designed for manually tracing over image data. 846 847 - Any object can be input rather than just 2D images 848 - The widget fires pick events at the input prop to decide where to move its handles 849 - The widget has 2D glyphs for handles instead of 3D spheres. 850 851 The button actions and key modifiers are as follows for controlling the widget: 852 1) left button click over the image, hold and drag draws a free hand line. 853 2) left button click and release erases the widget line, if it exists, and repositions the first handle. 854 3) middle button click starts a snap drawn line. 855 The line is terminated by clicking the middle button while ressing the ctrl key. 856 4) when tracing a continuous or snap drawn line, if the last cursor position is within a specified 857 tolerance to the first handle, the widget line will form a closed loop. 858 5) right button clicking and holding on any handle that is part of a snap drawn line allows handle dragging: 859 existing line segments are updated accordingly. If the path is open and closing_radius is set, 860 the path can be closed by repositioning the first and last points over one another. 861 6) Ctrl + right button down on any handle will erase it: existing snap drawn line segments are updated accordingly. 862 If the line was formed by continuous tracing, the line is deleted leaving one handle. 863 7) Shift + right button down on any snap drawn line segment will insert a handle at the cursor position. 864 The line segment is split accordingly. 865 866 Arguments: 867 obj : vtkProp 868 The prop to trace on. 869 c : str, optional 870 The color of the line. The default is "green5". 871 lw : int, optional 872 The line width. The default is 4. 873 closed : bool, optional 874 Whether to close the line. The default is False. 875 snap_to_image : bool, optional 876 Whether to snap to the image. The default is False. 877 878 Example: 879 - [spline_draw2.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/spline_draw2.py) 880 """ 881 882 self.widget = vtki.new("ImageTracerWidget") 883 884 self.line = None 885 self.line_properties = self.widget.GetLineProperty() 886 self.line_properties.SetColor(vedo.get_color(c)) 887 self.line_properties.SetLineWidth(lw) 888 self.callback_id = None 889 self.event_name = "EndInteractionEvent" 890 891 if vedo.plotter_instance: 892 self.widget.SetInteractor(vedo.plotter_instance.interactor) 893 if vedo.plotter_instance.renderer: 894 self.widget.SetDefaultRenderer(vedo.plotter_instance.renderer) 895 896 try: 897 self.widget.SetViewProp(obj.actor) 898 except AttributeError: 899 self.widget.SetViewProp(obj) 900 901 if closed: 902 closing_radius = 1e10 903 self.widget.SetAutoClose(1) 904 self.widget.SetCaptureRadius(closing_radius) 905 906 self.widget.SetProjectToPlane(0) 907 self.widget.SetProjectionNormal(2) # XY plane 908 self.widget.SetProjectionPosition(0) 909 self.widget.SetSnapToImage(snap_to_image)
3D widget for tracing on planar props. This is primarily designed for manually tracing over image data.
- Any object can be input rather than just 2D images
- The widget fires pick events at the input prop to decide where to move its handles
- The widget has 2D glyphs for handles instead of 3D spheres.
The button actions and key modifiers are as follows for controlling the widget: 1) left button click over the image, hold and drag draws a free hand line. 2) left button click and release erases the widget line, if it exists, and repositions the first handle. 3) middle button click starts a snap drawn line. The line is terminated by clicking the middle button while ressing the ctrl key. 4) when tracing a continuous or snap drawn line, if the last cursor position is within a specified tolerance to the first handle, the widget line will form a closed loop. 5) right button clicking and holding on any handle that is part of a snap drawn line allows handle dragging: existing line segments are updated accordingly. If the path is open and closing_radius is set, the path can be closed by repositioning the first and last points over one another. 6) Ctrl + right button down on any handle will erase it: existing snap drawn line segments are updated accordingly. If the line was formed by continuous tracing, the line is deleted leaving one handle. 7) Shift + right button down on any snap drawn line segment will insert a handle at the cursor position. The line segment is split accordingly.
Arguments: obj : vtkProp The prop to trace on. c : str, optional The color of the line. The default is "green5". lw : int, optional The line width. The default is 4. closed : bool, optional Whether to close the line. The default is False. snap_to_image : bool, optional Whether to snap to the image. The default is False.
Example:
917 def add_observer(self, event, func, priority=1) -> int: 918 """Add an observer to the widget.""" 919 event = utils.get_vtk_name_event(event) 920 cid = self.widget.AddObserver(event, func, priority) 921 return cid
Add an observer to the widget.
1039def Goniometer( 1040 p1, 1041 p2, 1042 p3, 1043 font="", 1044 arc_size=0.4, 1045 s=1, 1046 italic=0, 1047 rotation=0, 1048 prefix="", 1049 lc="k2", 1050 c="white", 1051 alpha=1, 1052 lw=2, 1053 precision=3, 1054): 1055 """ 1056 Build a graphical goniometer to measure the angle formed by 3 points in space. 1057 1058 Arguments: 1059 p1 : (list) 1060 first point 3D coordinates. 1061 p2 : (list) 1062 the vertex point. 1063 p3 : (list) 1064 the last point defining the angle. 1065 font : (str) 1066 Font face. Check [available fonts here](https://vedo.embl.es/fonts). 1067 arc_size : (float) 1068 dimension of the arc wrt the smallest axis. 1069 s : (float) 1070 size of the text. 1071 italic : (float, bool) 1072 italic text. 1073 rotation : (float) 1074 rotation of text in degrees. 1075 prefix : (str) 1076 append this string to the numeric value of the angle. 1077 lc : (list) 1078 color of the goniometer lines. 1079 c : (str) 1080 color of the goniometer angle filling. Set alpha=0 to remove it. 1081 alpha : (float) 1082 transparency level. 1083 lw : (float) 1084 line width. 1085 precision : (int) 1086 number of significant digits. 1087 1088 Examples: 1089 - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py) 1090 1091 ![](https://vedo.embl.es/images/pyplot/goniometer.png) 1092 """ 1093 if isinstance(p1, Points): p1 = p1.pos() 1094 if isinstance(p2, Points): p2 = p2.pos() 1095 if isinstance(p3, Points): p3 = p3.pos() 1096 if len(p1)==2: p1=[p1[0], p1[1], 0.0] 1097 if len(p2)==2: p2=[p2[0], p2[1], 0.0] 1098 if len(p3)==2: p3=[p3[0], p3[1], 0.0] 1099 p1, p2, p3 = np.array(p1), np.array(p2), np.array(p3) 1100 1101 acts = [] 1102 ln = shapes.Line([p1, p2, p3], lw=lw, c=lc) 1103 acts.append(ln) 1104 1105 va = utils.versor(p1 - p2) 1106 vb = utils.versor(p3 - p2) 1107 r = min(utils.mag(p3 - p2), utils.mag(p1 - p2)) * arc_size 1108 ptsarc = [] 1109 res = 120 1110 imed = int(res / 2) 1111 for i in range(res + 1): 1112 vi = utils.versor(vb * i / res + va * (res - i) / res) 1113 if i == imed: 1114 vc = np.array(vi) 1115 ptsarc.append(p2 + vi * r) 1116 arc = shapes.Line(ptsarc).lw(lw).c(lc) 1117 acts.append(arc) 1118 1119 angle = np.arccos(np.dot(va, vb)) * 180 / np.pi 1120 1121 lb = shapes.Text3D( 1122 prefix + utils.precision(angle, precision) + "º", 1123 s=r / 12 * s, 1124 font=font, 1125 italic=italic, 1126 justify="center", 1127 ) 1128 cr = np.cross(va, vb) 1129 lb.reorient([0, 0, 1], cr * np.sign(cr[2]), rotation=rotation, xyplane=False) 1130 lb.pos(p2 + vc * r / 1.75) 1131 lb.c(c).bc("tomato").lighting("off") 1132 acts.append(lb) 1133 1134 if alpha > 0: 1135 pts = [p2] + arc.vertices.tolist() + [p2] 1136 msh = Mesh([pts, [list(range(arc.npoints + 2))]], c=lc, alpha=alpha) 1137 msh.lighting("off") 1138 msh.triangulate() 1139 msh.shift(0, 0, -r / 10000) # to resolve 2d conflicts.. 1140 acts.append(msh) 1141 1142 asse = Assembly(acts) 1143 asse.name = "Goniometer" 1144 return asse
Build a graphical goniometer to measure the angle formed by 3 points in space.
Arguments:
- p1 : (list) first point 3D coordinates.
- p2 : (list) the vertex point.
- p3 : (list) the last point defining the angle.
- font : (str) Font face. Check available fonts here.
- arc_size : (float) dimension of the arc wrt the smallest axis.
- s : (float) size of the text.
- italic : (float, bool) italic text.
- rotation : (float) rotation of text in degrees.
- prefix : (str) append this string to the numeric value of the angle.
- lc : (list) color of the goniometer lines.
- c : (str) color of the goniometer angle filling. Set alpha=0 to remove it.
- alpha : (float) transparency level.
- lw : (float) line width.
- precision : (int) number of significant digits.
Examples:
574class Button(vedo.shapes.Text2D): 575 """ 576 Build a Button object. 577 """ 578 579 def __init__( 580 self, 581 fnc=None, 582 states=("Button"), 583 c=("white"), 584 bc=("green4"), 585 pos=(0.7, 0.1), 586 size=24, 587 font="Courier", 588 bold=True, 589 italic=False, 590 alpha=1, 591 angle=0, 592 ): 593 """ 594 Build a Button object to be shown in the rendering window. 595 596 Arguments: 597 fnc : (function) 598 external function to be called by the widget 599 states : (list) 600 the list of possible states, eg. ['On', 'Off'] 601 c : (list) 602 the list of colors for each state eg. ['red3', 'green5'] 603 bc : (list) 604 the list of background colors for each state 605 pos : (list, str) 606 2D position in pixels from left-bottom corner 607 size : (int) 608 size of button font 609 font : (str) 610 font type 611 bold : (bool) 612 set bold font face 613 italic : (bool) 614 italic font face 615 alpha : (float) 616 opacity level 617 angle : (float) 618 anticlockwise rotation in degrees 619 620 Examples: 621 - [buttons1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons1.py) 622 - [buttons2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons2.py) 623 624 ![](https://user-images.githubusercontent.com/32848391/50738870-c0fe2500-11d8-11e9-9b78-92754f5c5968.jpg) 625 626 - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py) 627 628 ![](https://vedo.embl.es/images/advanced/timer_callback1.jpg) 629 """ 630 super().__init__() 631 632 self.status_idx = 0 633 634 self.spacer = " " 635 636 self.states = states 637 638 if not utils.is_sequence(c): 639 c = [c] 640 self.colors = c 641 642 if not utils.is_sequence(bc): 643 bc = [bc] 644 self.bcolors = bc 645 646 assert len(c) == len(bc), "in Button color number mismatch!" 647 648 self.function = fnc 649 self.function_id = None 650 651 self.status(0) 652 653 if font == "courier": 654 font = font.capitalize() 655 self.font(font).bold(bold).italic(italic) 656 657 self.alpha(alpha).angle(angle) 658 self.size(size / 20) 659 self.pos(pos, "center") 660 self.PickableOn() 661 662 def status(self, s=None) -> "Button": 663 """ 664 Set/Get the status of the button. 665 """ 666 if s is None: 667 return self.states[self.status_idx] 668 669 if isinstance(s, str): 670 s = self.states.index(s) 671 self.status_idx = s 672 self.text(self.spacer + self.states[s] + self.spacer) 673 s = s % len(self.bcolors) 674 self.color(self.colors[s]) 675 self.background(self.bcolors[s]) 676 return self 677 678 def switch(self) -> "Button": 679 """ 680 Change/cycle button status to the next defined status in states list. 681 """ 682 self.status_idx = (self.status_idx + 1) % len(self.states) 683 self.status(self.status_idx) 684 return self
Build a Button object.
579 def __init__( 580 self, 581 fnc=None, 582 states=("Button"), 583 c=("white"), 584 bc=("green4"), 585 pos=(0.7, 0.1), 586 size=24, 587 font="Courier", 588 bold=True, 589 italic=False, 590 alpha=1, 591 angle=0, 592 ): 593 """ 594 Build a Button object to be shown in the rendering window. 595 596 Arguments: 597 fnc : (function) 598 external function to be called by the widget 599 states : (list) 600 the list of possible states, eg. ['On', 'Off'] 601 c : (list) 602 the list of colors for each state eg. ['red3', 'green5'] 603 bc : (list) 604 the list of background colors for each state 605 pos : (list, str) 606 2D position in pixels from left-bottom corner 607 size : (int) 608 size of button font 609 font : (str) 610 font type 611 bold : (bool) 612 set bold font face 613 italic : (bool) 614 italic font face 615 alpha : (float) 616 opacity level 617 angle : (float) 618 anticlockwise rotation in degrees 619 620 Examples: 621 - [buttons1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons1.py) 622 - [buttons2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons2.py) 623 624 ![](https://user-images.githubusercontent.com/32848391/50738870-c0fe2500-11d8-11e9-9b78-92754f5c5968.jpg) 625 626 - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py) 627 628 ![](https://vedo.embl.es/images/advanced/timer_callback1.jpg) 629 """ 630 super().__init__() 631 632 self.status_idx = 0 633 634 self.spacer = " " 635 636 self.states = states 637 638 if not utils.is_sequence(c): 639 c = [c] 640 self.colors = c 641 642 if not utils.is_sequence(bc): 643 bc = [bc] 644 self.bcolors = bc 645 646 assert len(c) == len(bc), "in Button color number mismatch!" 647 648 self.function = fnc 649 self.function_id = None 650 651 self.status(0) 652 653 if font == "courier": 654 font = font.capitalize() 655 self.font(font).bold(bold).italic(italic) 656 657 self.alpha(alpha).angle(angle) 658 self.size(size / 20) 659 self.pos(pos, "center") 660 self.PickableOn()
Build a Button object to be shown in the rendering window.
Arguments:
- fnc : (function) external function to be called by the widget
- states : (list) the list of possible states, eg. ['On', 'Off']
- c : (list) the list of colors for each state eg. ['red3', 'green5']
- bc : (list) the list of background colors for each state
- pos : (list, str) 2D position in pixels from left-bottom corner
- size : (int) size of button font
- font : (str) font type
- bold : (bool) set bold font face
- italic : (bool) italic font face
- alpha : (float) opacity level
- angle : (float) anticlockwise rotation in degrees
Examples:
662 def status(self, s=None) -> "Button": 663 """ 664 Set/Get the status of the button. 665 """ 666 if s is None: 667 return self.states[self.status_idx] 668 669 if isinstance(s, str): 670 s = self.states.index(s) 671 self.status_idx = s 672 self.text(self.spacer + self.states[s] + self.spacer) 673 s = s % len(self.bcolors) 674 self.color(self.colors[s]) 675 self.background(self.bcolors[s]) 676 return self
Set/Get the status of the button.
678 def switch(self) -> "Button": 679 """ 680 Change/cycle button status to the next defined status in states list. 681 """ 682 self.status_idx = (self.status_idx + 1) % len(self.states) 683 self.status(self.status_idx) 684 return self
Change/cycle button status to the next defined status in states list.
378class ButtonWidget: 379 """ 380 Create a button widget. 381 """ 382 383 def __init__( 384 self, 385 function, 386 states=(), 387 c=("white"), 388 bc=("green4"), 389 alpha=1.0, 390 font="Calco", 391 size=100, 392 plotter=None, 393 ): 394 """ 395 Create a button widget. 396 397 States can be either text strings or images. 398 399 Arguments: 400 function : (function) 401 external function to be called by the widget 402 states : (list) 403 the list of possible states, eg. ['On', 'Off'] 404 c : (list) 405 the list of colors for each state eg. ['red3', 'green5'] 406 bc : (list) 407 the list of background colors for each state 408 alpha : (float) 409 opacity level 410 font : (str) 411 font type 412 size : (int) 413 size of button font 414 plotter : (Plotter) 415 the plotter object to which the widget is added 416 417 Example: 418 ```py 419 from vedo import * 420 421 def button_func(widget, evtname): 422 print("button_func called") 423 cone.color(button.state) 424 425 def on_mouse_click(event): 426 if event.object: 427 print("on_mouse_click", event) 428 cone.color(button.state) 429 430 # Create a cone 431 cone = Cone().color(0) 432 433 # Create a plotter 434 plt = Plotter(bg='bb', axes=1) 435 plt.add_callback('mouse click', on_mouse_click) 436 437 plt.add(cone) 438 439 # Create a button widget 440 img0 = Image("play-button.png") 441 img1 = Image("power-on.png") 442 443 button = ButtonWidget( 444 button_func, 445 # states=["State 0", "State 1"], 446 states=[img0, img1], 447 c=["red4", "blue4"], 448 bc=("k9", "k5"), 449 size=100, 450 plotter=plt, 451 ) 452 button.pos([0,0]).enable() 453 454 plt.show() 455 ``` 456 """ 457 458 self.widget = vtki.new("ButtonWidget") 459 460 self.function = function 461 self.states = states 462 self.colors = c 463 self.background_colors = bc 464 self.plotter = plotter 465 self.size = size 466 467 assert len(states) == len(c), "states and colors must have the same length" 468 assert len(states) == len(bc), "states and background colors must have the same length" 469 470 self.interactor = None 471 if plotter is not None: 472 self.interactor = plotter.interactor 473 self.widget.SetInteractor(plotter.interactor) 474 else: 475 if vedo.plotter_instance: 476 self.interactor = vedo.plotter_instance.interactor 477 self.widget.SetInteractor(self.interactor) 478 479 self.representation = vtki.new("TexturedButtonRepresentation2D") 480 self.representation.SetNumberOfStates(len(states)) 481 for i, state in enumerate(states): 482 483 if isinstance(state, vedo.Image): 484 state = state.dataset 485 486 elif isinstance(state, str): 487 txt = state 488 tp = vtki.vtkTextProperty() 489 tp.BoldOff() 490 tp.FrameOff() 491 col = c[i] 492 tp.SetColor(vedo.get_color(col)) 493 tp.ShadowOff() 494 tp.ItalicOff() 495 col = bc[i] 496 tp.SetBackgroundColor(vedo.get_color(col)) 497 tp.SetBackgroundOpacity(alpha) 498 tp.UseTightBoundingBoxOff() 499 500 # tp.SetJustificationToLeft() 501 # tp.SetVerticalJustificationToCentered() 502 # tp.SetJustificationToCentered() 503 width, height = 100 * len(txt), 1000 504 505 fpath = vedo.utils.get_font_path(font) 506 tp.SetFontFamily(vtki.VTK_FONT_FILE) 507 tp.SetFontFile(fpath) 508 509 tr = vtki.new("TextRenderer") 510 fs = tr.GetConstrainedFontSize(txt, tp, width, height, 500) 511 tp.SetFontSize(fs) 512 513 img = vtki.vtkImageData() 514 tr.RenderString(tp, txt, img, [width, height], 500) 515 state = img 516 517 self.representation.SetButtonTexture(i, state) 518 519 self.widget.SetRepresentation(self.representation) 520 self.widget.AddObserver("StateChangedEvent", function) 521 522 def __del__(self): 523 self.widget.Off() 524 self.widget.SetInteractor(None) 525 self.widget.SetRepresentation(None) 526 self.representation = None 527 self.interactor = None 528 self.function = None 529 self.states = () 530 self.widget = None 531 self.plotter = None 532 533 def pos(self, pos): 534 assert len(pos) == 2, "pos must be a 2D position" 535 if not self.plotter: 536 vedo.logger.warning("ButtonWidget: pos() can only be used if a Plotter is provided") 537 return self 538 coords = vtki.vtkCoordinate() 539 coords.SetCoordinateSystemToNormalizedDisplay() 540 coords.SetValue(pos[0], pos[1]) 541 sz = self.size 542 ren = self.plotter.renderer 543 p = coords.GetComputedDisplayValue(ren) 544 bds = [0, 0, 0, 0, 0, 0] 545 bds[0] = p[0] - sz 546 bds[1] = bds[0] + sz 547 bds[2] = p[1] - sz 548 bds[3] = bds[2] + sz 549 self.representation.SetPlaceFactor(1) 550 self.representation.PlaceWidget(bds) 551 return self 552 553 def enable(self): 554 self.widget.On() 555 return self 556 557 def disable(self): 558 self.widget.Off() 559 return self 560 561 def next_state(self): 562 self.representation.NextState() 563 return self 564 565 @property 566 def state(self): 567 return self.representation.GetState() 568 569 @state.setter 570 def state(self, i): 571 self.representation.SetState(i)
Create a button widget.
383 def __init__( 384 self, 385 function, 386 states=(), 387 c=("white"), 388 bc=("green4"), 389 alpha=1.0, 390 font="Calco", 391 size=100, 392 plotter=None, 393 ): 394 """ 395 Create a button widget. 396 397 States can be either text strings or images. 398 399 Arguments: 400 function : (function) 401 external function to be called by the widget 402 states : (list) 403 the list of possible states, eg. ['On', 'Off'] 404 c : (list) 405 the list of colors for each state eg. ['red3', 'green5'] 406 bc : (list) 407 the list of background colors for each state 408 alpha : (float) 409 opacity level 410 font : (str) 411 font type 412 size : (int) 413 size of button font 414 plotter : (Plotter) 415 the plotter object to which the widget is added 416 417 Example: 418 ```py 419 from vedo import * 420 421 def button_func(widget, evtname): 422 print("button_func called") 423 cone.color(button.state) 424 425 def on_mouse_click(event): 426 if event.object: 427 print("on_mouse_click", event) 428 cone.color(button.state) 429 430 # Create a cone 431 cone = Cone().color(0) 432 433 # Create a plotter 434 plt = Plotter(bg='bb', axes=1) 435 plt.add_callback('mouse click', on_mouse_click) 436 437 plt.add(cone) 438 439 # Create a button widget 440 img0 = Image("play-button.png") 441 img1 = Image("power-on.png") 442 443 button = ButtonWidget( 444 button_func, 445 # states=["State 0", "State 1"], 446 states=[img0, img1], 447 c=["red4", "blue4"], 448 bc=("k9", "k5"), 449 size=100, 450 plotter=plt, 451 ) 452 button.pos([0,0]).enable() 453 454 plt.show() 455 ``` 456 """ 457 458 self.widget = vtki.new("ButtonWidget") 459 460 self.function = function 461 self.states = states 462 self.colors = c 463 self.background_colors = bc 464 self.plotter = plotter 465 self.size = size 466 467 assert len(states) == len(c), "states and colors must have the same length" 468 assert len(states) == len(bc), "states and background colors must have the same length" 469 470 self.interactor = None 471 if plotter is not None: 472 self.interactor = plotter.interactor 473 self.widget.SetInteractor(plotter.interactor) 474 else: 475 if vedo.plotter_instance: 476 self.interactor = vedo.plotter_instance.interactor 477 self.widget.SetInteractor(self.interactor) 478 479 self.representation = vtki.new("TexturedButtonRepresentation2D") 480 self.representation.SetNumberOfStates(len(states)) 481 for i, state in enumerate(states): 482 483 if isinstance(state, vedo.Image): 484 state = state.dataset 485 486 elif isinstance(state, str): 487 txt = state 488 tp = vtki.vtkTextProperty() 489 tp.BoldOff() 490 tp.FrameOff() 491 col = c[i] 492 tp.SetColor(vedo.get_color(col)) 493 tp.ShadowOff() 494 tp.ItalicOff() 495 col = bc[i] 496 tp.SetBackgroundColor(vedo.get_color(col)) 497 tp.SetBackgroundOpacity(alpha) 498 tp.UseTightBoundingBoxOff() 499 500 # tp.SetJustificationToLeft() 501 # tp.SetVerticalJustificationToCentered() 502 # tp.SetJustificationToCentered() 503 width, height = 100 * len(txt), 1000 504 505 fpath = vedo.utils.get_font_path(font) 506 tp.SetFontFamily(vtki.VTK_FONT_FILE) 507 tp.SetFontFile(fpath) 508 509 tr = vtki.new("TextRenderer") 510 fs = tr.GetConstrainedFontSize(txt, tp, width, height, 500) 511 tp.SetFontSize(fs) 512 513 img = vtki.vtkImageData() 514 tr.RenderString(tp, txt, img, [width, height], 500) 515 state = img 516 517 self.representation.SetButtonTexture(i, state) 518 519 self.widget.SetRepresentation(self.representation) 520 self.widget.AddObserver("StateChangedEvent", function)
Create a button widget.
States can be either text strings or images.
Arguments:
- function : (function) external function to be called by the widget
- states : (list) the list of possible states, eg. ['On', 'Off']
- c : (list) the list of colors for each state eg. ['red3', 'green5']
- bc : (list) the list of background colors for each state
- alpha : (float) opacity level
- font : (str) font type
- size : (int) size of button font
- plotter : (Plotter) the plotter object to which the widget is added
Example:
from vedo import * def button_func(widget, evtname): print("button_func called") cone.color(button.state) def on_mouse_click(event): if event.object: print("on_mouse_click", event) cone.color(button.state) # Create a cone cone = Cone().color(0) # Create a plotter plt = Plotter(bg='bb', axes=1) plt.add_callback('mouse click', on_mouse_click) plt.add(cone) # Create a button widget img0 = Image("play-button.png") img1 = Image("power-on.png") button = ButtonWidget( button_func, # states=["State 0", "State 1"], states=[img0, img1], c=["red4", "blue4"], bc=("k9", "k5"), size=100, plotter=plt, ) button.pos([0,0]).enable() plt.show()
533 def pos(self, pos): 534 assert len(pos) == 2, "pos must be a 2D position" 535 if not self.plotter: 536 vedo.logger.warning("ButtonWidget: pos() can only be used if a Plotter is provided") 537 return self 538 coords = vtki.vtkCoordinate() 539 coords.SetCoordinateSystemToNormalizedDisplay() 540 coords.SetValue(pos[0], pos[1]) 541 sz = self.size 542 ren = self.plotter.renderer 543 p = coords.GetComputedDisplayValue(ren) 544 bds = [0, 0, 0, 0, 0, 0] 545 bds[0] = p[0] - sz 546 bds[1] = bds[0] + sz 547 bds[2] = p[1] - sz 548 bds[3] = bds[2] + sz 549 self.representation.SetPlaceFactor(1) 550 self.representation.PlaceWidget(bds) 551 return self
57class Flagpost(vtki.vtkFlagpoleLabel): 58 """ 59 Create a flag post style element to describe an object. 60 """ 61 62 def __init__( 63 self, 64 txt="", 65 base=(0, 0, 0), 66 top=(0, 0, 1), 67 s=1, 68 c="k9", 69 bc="k1", 70 alpha=1, 71 lw=0, 72 font="Calco", 73 justify="center-left", 74 vspacing=1, 75 ): 76 """ 77 Create a flag post style element to describe an object. 78 79 Arguments: 80 txt : (str) 81 Text to display. The default is the filename or the object name. 82 base : (list) 83 position of the flag anchor point. 84 top : (list) 85 a 3D displacement or offset. 86 s : (float) 87 size of the text to be shown 88 c : (list) 89 color of text and line 90 bc : (list) 91 color of the flag background 92 alpha : (float) 93 opacity of text and box. 94 lw : (int) 95 line with of box frame. The default is 0. 96 font : (str) 97 font name. Use a monospace font for better rendering. The default is "Calco". 98 Type `vedo -r fonts` for a font demo. 99 Check [available fonts here](https://vedo.embl.es/fonts). 100 justify : (str) 101 internal text justification. The default is "center-left". 102 vspacing : (float) 103 vertical spacing between lines. 104 105 Examples: 106 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py) 107 108 ![](https://vedo.embl.es/images/other/flag_labels2.png) 109 """ 110 111 super().__init__() 112 113 base = utils.make3d(base) 114 top = utils.make3d(top) 115 116 self.SetBasePosition(*base) 117 self.SetTopPosition(*top) 118 119 self.SetFlagSize(s) 120 self.SetInput(txt) 121 self.PickableOff() 122 123 self.GetProperty().LightingOff() 124 self.GetProperty().SetLineWidth(lw + 1) 125 126 prop = self.GetTextProperty() 127 if bc is not None: 128 prop.SetBackgroundColor(get_color(bc)) 129 130 prop.SetOpacity(alpha) 131 prop.SetBackgroundOpacity(alpha) 132 if bc is not None and len(bc) == 4: 133 prop.SetBackgroundRGBA(alpha) 134 135 c = get_color(c) 136 prop.SetColor(c) 137 self.GetProperty().SetColor(c) 138 139 prop.SetFrame(bool(lw)) 140 prop.SetFrameWidth(lw) 141 prop.SetFrameColor(prop.GetColor()) 142 143 prop.SetFontFamily(vtki.VTK_FONT_FILE) 144 fl = utils.get_font_path(font) 145 prop.SetFontFile(fl) 146 prop.ShadowOff() 147 prop.BoldOff() 148 prop.SetOpacity(alpha) 149 prop.SetJustificationToLeft() 150 if "top" in justify: 151 prop.SetVerticalJustificationToTop() 152 if "bottom" in justify: 153 prop.SetVerticalJustificationToBottom() 154 if "cent" in justify: 155 prop.SetVerticalJustificationToCentered() 156 prop.SetJustificationToCentered() 157 if "left" in justify: 158 prop.SetJustificationToLeft() 159 if "right" in justify: 160 prop.SetJustificationToRight() 161 prop.SetLineSpacing(vspacing * 1.2) 162 self.SetUseBounds(False) 163 164 def text(self, value: str) -> Self: 165 self.SetInput(value) 166 return self 167 168 def on(self) -> Self: 169 self.VisibilityOn() 170 return self 171 172 def off(self) -> Self: 173 self.VisibilityOff() 174 return self 175 176 def toggle(self) -> Self: 177 self.SetVisibility(not self.GetVisibility()) 178 return self 179 180 def use_bounds(self, value=True) -> Self: 181 self.SetUseBounds(value) 182 return self 183 184 def color(self, c) -> Self: 185 c = get_color(c) 186 self.GetTextProperty().SetColor(c) 187 self.GetProperty().SetColor(c) 188 return self 189 190 def pos(self, p) -> Self: 191 p = np.asarray(p) 192 self.top = self.top - self.base + p 193 self.base = p 194 return self 195 196 @property 197 def base(self) -> np.ndarray: 198 return np.array(self.GetBasePosition()) 199 200 @base.setter 201 def base(self, value): 202 self.SetBasePosition(*value) 203 204 @property 205 def top(self) -> np.ndarray: 206 return np.array(self.GetTopPosition()) 207 208 @top.setter 209 def top(self, value): 210 self.SetTopPosition(*value)
Create a flag post style element to describe an object.
62 def __init__( 63 self, 64 txt="", 65 base=(0, 0, 0), 66 top=(0, 0, 1), 67 s=1, 68 c="k9", 69 bc="k1", 70 alpha=1, 71 lw=0, 72 font="Calco", 73 justify="center-left", 74 vspacing=1, 75 ): 76 """ 77 Create a flag post style element to describe an object. 78 79 Arguments: 80 txt : (str) 81 Text to display. The default is the filename or the object name. 82 base : (list) 83 position of the flag anchor point. 84 top : (list) 85 a 3D displacement or offset. 86 s : (float) 87 size of the text to be shown 88 c : (list) 89 color of text and line 90 bc : (list) 91 color of the flag background 92 alpha : (float) 93 opacity of text and box. 94 lw : (int) 95 line with of box frame. The default is 0. 96 font : (str) 97 font name. Use a monospace font for better rendering. The default is "Calco". 98 Type `vedo -r fonts` for a font demo. 99 Check [available fonts here](https://vedo.embl.es/fonts). 100 justify : (str) 101 internal text justification. The default is "center-left". 102 vspacing : (float) 103 vertical spacing between lines. 104 105 Examples: 106 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py) 107 108 ![](https://vedo.embl.es/images/other/flag_labels2.png) 109 """ 110 111 super().__init__() 112 113 base = utils.make3d(base) 114 top = utils.make3d(top) 115 116 self.SetBasePosition(*base) 117 self.SetTopPosition(*top) 118 119 self.SetFlagSize(s) 120 self.SetInput(txt) 121 self.PickableOff() 122 123 self.GetProperty().LightingOff() 124 self.GetProperty().SetLineWidth(lw + 1) 125 126 prop = self.GetTextProperty() 127 if bc is not None: 128 prop.SetBackgroundColor(get_color(bc)) 129 130 prop.SetOpacity(alpha) 131 prop.SetBackgroundOpacity(alpha) 132 if bc is not None and len(bc) == 4: 133 prop.SetBackgroundRGBA(alpha) 134 135 c = get_color(c) 136 prop.SetColor(c) 137 self.GetProperty().SetColor(c) 138 139 prop.SetFrame(bool(lw)) 140 prop.SetFrameWidth(lw) 141 prop.SetFrameColor(prop.GetColor()) 142 143 prop.SetFontFamily(vtki.VTK_FONT_FILE) 144 fl = utils.get_font_path(font) 145 prop.SetFontFile(fl) 146 prop.ShadowOff() 147 prop.BoldOff() 148 prop.SetOpacity(alpha) 149 prop.SetJustificationToLeft() 150 if "top" in justify: 151 prop.SetVerticalJustificationToTop() 152 if "bottom" in justify: 153 prop.SetVerticalJustificationToBottom() 154 if "cent" in justify: 155 prop.SetVerticalJustificationToCentered() 156 prop.SetJustificationToCentered() 157 if "left" in justify: 158 prop.SetJustificationToLeft() 159 if "right" in justify: 160 prop.SetJustificationToRight() 161 prop.SetLineSpacing(vspacing * 1.2) 162 self.SetUseBounds(False)
Create a flag post style element to describe an object.
Arguments:
- txt : (str) Text to display. The default is the filename or the object name.
- base : (list) position of the flag anchor point.
- top : (list) a 3D displacement or offset.
- 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:
2611class ProgressBarWidget(vtki.vtkActor2D): 2612 """ 2613 Add a progress bar in the rendering window. 2614 """ 2615 2616 def __init__(self, n=None, c="blue5", alpha=0.8, lw=10, autohide=True): 2617 """ 2618 Add a progress bar window. 2619 2620 Arguments: 2621 n : (int) 2622 number of iterations. 2623 If None, you need to call `update(fraction)` manually. 2624 c : (color) 2625 color of the line. 2626 alpha : (float) 2627 opacity of the line. 2628 lw : (int) 2629 line width in pixels. 2630 autohide : (bool) 2631 if True, hide the progress bar when completed. 2632 """ 2633 self.n = 0 2634 self.iterations = n 2635 self.autohide = autohide 2636 2637 ppoints = vtki.vtkPoints() # Generate the line 2638 psqr = [[0, 0, 0], [1, 0, 0]] 2639 for i, pt in enumerate(psqr): 2640 ppoints.InsertPoint(i, *pt) 2641 lines = vtki.vtkCellArray() 2642 lines.InsertNextCell(len(psqr)) 2643 for i in range(len(psqr)): 2644 lines.InsertCellPoint(i) 2645 pd = vtki.vtkPolyData() 2646 pd.SetPoints(ppoints) 2647 pd.SetLines(lines) 2648 self.dataset = pd 2649 2650 mapper = vtki.new("PolyDataMapper2D") 2651 mapper.SetInputData(pd) 2652 cs = vtki.vtkCoordinate() 2653 cs.SetCoordinateSystemToNormalizedViewport() 2654 mapper.SetTransformCoordinate(cs) 2655 2656 super().__init__() 2657 2658 self.SetMapper(mapper) 2659 self.GetProperty().SetOpacity(alpha) 2660 self.GetProperty().SetColor(get_color(c)) 2661 self.GetProperty().SetLineWidth(lw * 2) 2662 2663 def lw(self, value: int) -> Self: 2664 """Set width.""" 2665 self.GetProperty().SetLineWidth(value * 2) 2666 return self 2667 2668 def c(self, color) -> Self: 2669 """Set color.""" 2670 c = get_color(color) 2671 self.GetProperty().SetColor(c) 2672 return self 2673 2674 def alpha(self, value) -> Self: 2675 """Set opacity.""" 2676 self.GetProperty().SetOpacity(value) 2677 return self 2678 2679 def update(self, fraction=None) -> Self: 2680 """Update progress bar to fraction of the window width.""" 2681 if fraction is None: 2682 if self.iterations is None: 2683 vedo.printc("Error in ProgressBarWindow: must specify iterations", c='r') 2684 return self 2685 self.n += 1 2686 fraction = self.n / self.iterations 2687 2688 if fraction >= 1 and self.autohide: 2689 fraction = 0 2690 2691 psqr = [[0, 0, 0], [fraction, 0, 0]] 2692 vpts = utils.numpy2vtk(psqr, dtype=np.float32) 2693 self.dataset.GetPoints().SetData(vpts) 2694 return self 2695 2696 def reset(self): 2697 """Reset progress bar.""" 2698 self.n = 0 2699 self.update(0) 2700 return self
Add a progress bar in the rendering window.
2616 def __init__(self, n=None, c="blue5", alpha=0.8, lw=10, autohide=True): 2617 """ 2618 Add a progress bar window. 2619 2620 Arguments: 2621 n : (int) 2622 number of iterations. 2623 If None, you need to call `update(fraction)` manually. 2624 c : (color) 2625 color of the line. 2626 alpha : (float) 2627 opacity of the line. 2628 lw : (int) 2629 line width in pixels. 2630 autohide : (bool) 2631 if True, hide the progress bar when completed. 2632 """ 2633 self.n = 0 2634 self.iterations = n 2635 self.autohide = autohide 2636 2637 ppoints = vtki.vtkPoints() # Generate the line 2638 psqr = [[0, 0, 0], [1, 0, 0]] 2639 for i, pt in enumerate(psqr): 2640 ppoints.InsertPoint(i, *pt) 2641 lines = vtki.vtkCellArray() 2642 lines.InsertNextCell(len(psqr)) 2643 for i in range(len(psqr)): 2644 lines.InsertCellPoint(i) 2645 pd = vtki.vtkPolyData() 2646 pd.SetPoints(ppoints) 2647 pd.SetLines(lines) 2648 self.dataset = pd 2649 2650 mapper = vtki.new("PolyDataMapper2D") 2651 mapper.SetInputData(pd) 2652 cs = vtki.vtkCoordinate() 2653 cs.SetCoordinateSystemToNormalizedViewport() 2654 mapper.SetTransformCoordinate(cs) 2655 2656 super().__init__() 2657 2658 self.SetMapper(mapper) 2659 self.GetProperty().SetOpacity(alpha) 2660 self.GetProperty().SetColor(get_color(c)) 2661 self.GetProperty().SetLineWidth(lw * 2)
Add a progress bar window.
Arguments:
- n : (int)
number of iterations.
If None, you need to call
update(fraction)
manually. - c : (color) color of the line.
- alpha : (float) opacity of the line.
- lw : (int) line width in pixels.
- autohide : (bool) if True, hide the progress bar when completed.
2663 def lw(self, value: int) -> Self: 2664 """Set width.""" 2665 self.GetProperty().SetLineWidth(value * 2) 2666 return self
Set width.
2668 def c(self, color) -> Self: 2669 """Set color.""" 2670 c = get_color(color) 2671 self.GetProperty().SetColor(c) 2672 return self
Set color.
2674 def alpha(self, value) -> Self: 2675 """Set opacity.""" 2676 self.GetProperty().SetOpacity(value) 2677 return self
Set opacity.
2679 def update(self, fraction=None) -> Self: 2680 """Update progress bar to fraction of the window width.""" 2681 if fraction is None: 2682 if self.iterations is None: 2683 vedo.printc("Error in ProgressBarWindow: must specify iterations", c='r') 2684 return self 2685 self.n += 1 2686 fraction = self.n / self.iterations 2687 2688 if fraction >= 1 and self.autohide: 2689 fraction = 0 2690 2691 psqr = [[0, 0, 0], [fraction, 0, 0]] 2692 vpts = utils.numpy2vtk(psqr, dtype=np.float32) 2693 self.dataset.GetPoints().SetData(vpts) 2694 return self
Update progress bar to fraction of the window width.
2283class BoxCutter(vtki.vtkBoxWidget, BaseCutter): 2284 """ 2285 Create a box widget to cut away parts of a Mesh. 2286 """ 2287 2288 def __init__( 2289 self, 2290 mesh, 2291 invert=False, 2292 can_rotate=True, 2293 can_translate=True, 2294 can_scale=True, 2295 initial_bounds=(), 2296 padding=0.025, 2297 delayed=False, 2298 c=(0.25, 0.25, 0.25), 2299 alpha=0.05, 2300 ): 2301 """ 2302 Create a box widget to cut away parts of a Mesh. 2303 2304 Arguments: 2305 mesh : (Mesh) 2306 the input mesh 2307 invert : (bool) 2308 invert the clipping plane 2309 can_rotate : (bool) 2310 enable rotation of the widget 2311 can_translate : (bool) 2312 enable translation of the widget 2313 can_scale : (bool) 2314 enable scaling of the widget 2315 initial_bounds : (list) 2316 initial bounds of the box widget 2317 padding : (float) 2318 padding space around the input mesh 2319 delayed : (bool) 2320 if True the callback is delayed until 2321 when the mouse button is released (useful for large meshes) 2322 c : (color) 2323 color of the box cutter widget 2324 alpha : (float) 2325 transparency of the cut-off part of the input mesh 2326 """ 2327 super().__init__() 2328 2329 self.mesh = mesh 2330 self.remnant = Mesh() 2331 self.remnant.name = mesh.name + "Remnant" 2332 self.remnant.pickable(False) 2333 2334 self._alpha = alpha 2335 self._keypress_id = None 2336 self._init_bounds = initial_bounds 2337 if len(self._init_bounds) == 0: 2338 self._init_bounds = mesh.bounds() 2339 else: 2340 self._init_bounds = initial_bounds 2341 2342 self._implicit_func = vtki.new("Planes") 2343 self._implicit_func.SetBounds(self._init_bounds) 2344 2345 poly = mesh.dataset 2346 self.clipper = vtki.new("ClipPolyData") 2347 self.clipper.GenerateClipScalarsOff() 2348 self.clipper.SetInputData(poly) 2349 self.clipper.SetClipFunction(self._implicit_func) 2350 self.clipper.SetInsideOut(not invert) 2351 self.clipper.GenerateClippedOutputOn() 2352 self.clipper.Update() 2353 2354 self.widget = vtki.vtkBoxWidget() 2355 2356 self.widget.SetRotationEnabled(can_rotate) 2357 self.widget.SetTranslationEnabled(can_translate) 2358 self.widget.SetScalingEnabled(can_scale) 2359 2360 self.widget.OutlineCursorWiresOn() 2361 self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3")) 2362 self.widget.GetSelectedHandleProperty().SetColor(get_color("red5")) 2363 2364 self.widget.GetOutlineProperty().SetColor(c) 2365 self.widget.GetOutlineProperty().SetOpacity(1) 2366 self.widget.GetOutlineProperty().SetLineWidth(1) 2367 self.widget.GetOutlineProperty().LightingOff() 2368 2369 self.widget.GetSelectedFaceProperty().LightingOff() 2370 self.widget.GetSelectedFaceProperty().SetOpacity(0.1) 2371 2372 self.widget.SetPlaceFactor(1.0 + padding) 2373 self.widget.SetInputData(poly) 2374 self.widget.PlaceWidget() 2375 if delayed: 2376 self.widget.AddObserver("EndInteractionEvent", self._select_polygons) 2377 else: 2378 self.widget.AddObserver("InteractionEvent", self._select_polygons) 2379 2380 def _select_polygons(self, vobj, event): 2381 vobj.GetPlanes(self._implicit_func) 2382 2383 def _keypress(self, vobj, event): 2384 if vobj.GetKeySym() == "r": # reset planes 2385 self._implicit_func.SetBounds(self._init_bounds) 2386 self.widget.GetPlanes(self._implicit_func) 2387 self.widget.PlaceWidget() 2388 self.widget.GetInteractor().Render() 2389 elif vobj.GetKeySym() == "u": 2390 self.invert() 2391 self.widget.GetInteractor().Render() 2392 elif vobj.GetKeySym() == "s": # Ctrl+s to save mesh 2393 if self.widget.GetInteractor(): 2394 if self.widget.GetInteractor().GetControlKey(): 2395 self.mesh.write("vedo_clipped.vtk") 2396 printc(":save: saved mesh to vedo_clipped.vtk")
Create a box widget to cut away parts of a Mesh.
2288 def __init__( 2289 self, 2290 mesh, 2291 invert=False, 2292 can_rotate=True, 2293 can_translate=True, 2294 can_scale=True, 2295 initial_bounds=(), 2296 padding=0.025, 2297 delayed=False, 2298 c=(0.25, 0.25, 0.25), 2299 alpha=0.05, 2300 ): 2301 """ 2302 Create a box widget to cut away parts of a Mesh. 2303 2304 Arguments: 2305 mesh : (Mesh) 2306 the input mesh 2307 invert : (bool) 2308 invert the clipping plane 2309 can_rotate : (bool) 2310 enable rotation of the widget 2311 can_translate : (bool) 2312 enable translation of the widget 2313 can_scale : (bool) 2314 enable scaling of the widget 2315 initial_bounds : (list) 2316 initial bounds of the box widget 2317 padding : (float) 2318 padding space around the input mesh 2319 delayed : (bool) 2320 if True the callback is delayed until 2321 when the mouse button is released (useful for large meshes) 2322 c : (color) 2323 color of the box cutter widget 2324 alpha : (float) 2325 transparency of the cut-off part of the input mesh 2326 """ 2327 super().__init__() 2328 2329 self.mesh = mesh 2330 self.remnant = Mesh() 2331 self.remnant.name = mesh.name + "Remnant" 2332 self.remnant.pickable(False) 2333 2334 self._alpha = alpha 2335 self._keypress_id = None 2336 self._init_bounds = initial_bounds 2337 if len(self._init_bounds) == 0: 2338 self._init_bounds = mesh.bounds() 2339 else: 2340 self._init_bounds = initial_bounds 2341 2342 self._implicit_func = vtki.new("Planes") 2343 self._implicit_func.SetBounds(self._init_bounds) 2344 2345 poly = mesh.dataset 2346 self.clipper = vtki.new("ClipPolyData") 2347 self.clipper.GenerateClipScalarsOff() 2348 self.clipper.SetInputData(poly) 2349 self.clipper.SetClipFunction(self._implicit_func) 2350 self.clipper.SetInsideOut(not invert) 2351 self.clipper.GenerateClippedOutputOn() 2352 self.clipper.Update() 2353 2354 self.widget = vtki.vtkBoxWidget() 2355 2356 self.widget.SetRotationEnabled(can_rotate) 2357 self.widget.SetTranslationEnabled(can_translate) 2358 self.widget.SetScalingEnabled(can_scale) 2359 2360 self.widget.OutlineCursorWiresOn() 2361 self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3")) 2362 self.widget.GetSelectedHandleProperty().SetColor(get_color("red5")) 2363 2364 self.widget.GetOutlineProperty().SetColor(c) 2365 self.widget.GetOutlineProperty().SetOpacity(1) 2366 self.widget.GetOutlineProperty().SetLineWidth(1) 2367 self.widget.GetOutlineProperty().LightingOff() 2368 2369 self.widget.GetSelectedFaceProperty().LightingOff() 2370 self.widget.GetSelectedFaceProperty().SetOpacity(0.1) 2371 2372 self.widget.SetPlaceFactor(1.0 + padding) 2373 self.widget.SetInputData(poly) 2374 self.widget.PlaceWidget() 2375 if delayed: 2376 self.widget.AddObserver("EndInteractionEvent", self._select_polygons) 2377 else: 2378 self.widget.AddObserver("InteractionEvent", self._select_polygons)
Create a box widget to cut away parts of a Mesh.
Arguments:
- mesh : (Mesh) the input mesh
- invert : (bool) invert the clipping plane
- can_rotate : (bool) enable rotation of the widget
- can_translate : (bool) enable translation of the widget
- can_scale : (bool) enable scaling of the widget
- initial_bounds : (list) initial bounds of the box widget
- padding : (float) padding space around the input mesh
- delayed : (bool) if True the callback is delayed until when the mouse button is released (useful for large meshes)
- c : (color) color of the box cutter widget
- alpha : (float) transparency of the cut-off part of the input mesh
Inherited Members
2121class PlaneCutter(vtki.vtkPlaneWidget, BaseCutter): 2122 """ 2123 Create a box widget to cut away parts of a Mesh. 2124 """ 2125 2126 def __init__( 2127 self, 2128 mesh, 2129 invert=False, 2130 can_translate=True, 2131 can_scale=True, 2132 origin=(), 2133 normal=(), 2134 padding=0.05, 2135 delayed=False, 2136 c=(0.25, 0.25, 0.25), 2137 alpha=0.05, 2138 ): 2139 """ 2140 Create a box widget to cut away parts of a `Mesh`. 2141 2142 Arguments: 2143 mesh : (Mesh) 2144 the input mesh 2145 invert : (bool) 2146 invert the clipping plane 2147 can_translate : (bool) 2148 enable translation of the widget 2149 can_scale : (bool) 2150 enable scaling of the widget 2151 origin : (list) 2152 origin of the plane 2153 normal : (list) 2154 normal to the plane 2155 padding : (float) 2156 padding around the input mesh 2157 delayed : (bool) 2158 if True the callback is delayed until 2159 when the mouse button is released (useful for large meshes) 2160 c : (color) 2161 color of the box cutter widget 2162 alpha : (float) 2163 transparency of the cut-off part of the input mesh 2164 2165 Examples: 2166 - [slice_plane3.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slice_plane3.py) 2167 """ 2168 super().__init__() 2169 2170 self.mesh = mesh 2171 self.remnant = Mesh() 2172 self.remnant.name = mesh.name + "Remnant" 2173 self.remnant.pickable(False) 2174 2175 self._alpha = alpha 2176 self._keypress_id = None 2177 2178 self._implicit_func = vtki.new("Plane") 2179 2180 poly = mesh.dataset 2181 self.clipper = vtki.new("ClipPolyData") 2182 self.clipper.GenerateClipScalarsOff() 2183 self.clipper.SetInputData(poly) 2184 self.clipper.SetClipFunction(self._implicit_func) 2185 self.clipper.SetInsideOut(invert) 2186 self.clipper.GenerateClippedOutputOn() 2187 self.clipper.Update() 2188 2189 self.widget = vtki.new("ImplicitPlaneWidget") 2190 2191 # self.widget.KeyPressActivationOff() 2192 # self.widget.SetKeyPressActivationValue('i') 2193 2194 self.widget.SetOriginTranslation(can_translate) 2195 self.widget.SetOutlineTranslation(can_translate) 2196 self.widget.SetScaleEnabled(can_scale) 2197 2198 self.widget.GetOutlineProperty().SetColor(get_color(c)) 2199 self.widget.GetOutlineProperty().SetOpacity(0.25) 2200 self.widget.GetOutlineProperty().SetLineWidth(1) 2201 self.widget.GetOutlineProperty().LightingOff() 2202 2203 self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3")) 2204 2205 self.widget.SetTubing(0) 2206 self.widget.SetDrawPlane(bool(alpha)) 2207 self.widget.GetPlaneProperty().LightingOff() 2208 self.widget.GetPlaneProperty().SetOpacity(alpha) 2209 self.widget.GetSelectedPlaneProperty().SetColor(get_color("red5")) 2210 self.widget.GetSelectedPlaneProperty().LightingOff() 2211 2212 self.widget.SetPlaceFactor(1.0 + padding) 2213 self.widget.SetInputData(poly) 2214 self.widget.PlaceWidget() 2215 if delayed: 2216 self.widget.AddObserver("EndInteractionEvent", self._select_polygons) 2217 else: 2218 self.widget.AddObserver("InteractionEvent", self._select_polygons) 2219 2220 if len(origin) == 3: 2221 self.widget.SetOrigin(origin) 2222 else: 2223 self.widget.SetOrigin(mesh.center_of_mass()) 2224 2225 if len(normal) == 3: 2226 self.widget.SetNormal(normal) 2227 else: 2228 self.widget.SetNormal((1, 0, 0)) 2229 2230 @property 2231 def origin(self): 2232 """Get the origin of the plane.""" 2233 return np.array(self.widget.GetOrigin()) 2234 2235 @origin.setter 2236 def origin(self, value): 2237 """Set the origin of the plane.""" 2238 self.widget.SetOrigin(value) 2239 2240 @property 2241 def normal(self): 2242 """Get the normal of the plane.""" 2243 return np.array(self.widget.GetNormal()) 2244 2245 @normal.setter 2246 def normal(self, value): 2247 """Set the normal of the plane.""" 2248 self.widget.SetNormal(value) 2249 2250 def _select_polygons(self, vobj, event) -> None: 2251 vobj.GetPlane(self._implicit_func) 2252 2253 def _keypress(self, vobj, event): 2254 if vobj.GetKeySym() == "r": # reset planes 2255 self.widget.GetPlane(self._implicit_func) 2256 self.widget.PlaceWidget() 2257 self.widget.GetInteractor().Render() 2258 elif vobj.GetKeySym() == "u": # invert cut 2259 self.invert() 2260 self.widget.GetInteractor().Render() 2261 elif vobj.GetKeySym() == "x": # set normal along x 2262 self.widget.SetNormal((1, 0, 0)) 2263 self.widget.GetPlane(self._implicit_func) 2264 self.widget.PlaceWidget() 2265 self.widget.GetInteractor().Render() 2266 elif vobj.GetKeySym() == "y": # set normal along y 2267 self.widget.SetNormal((0, 1, 0)) 2268 self.widget.GetPlane(self._implicit_func) 2269 self.widget.PlaceWidget() 2270 self.widget.GetInteractor().Render() 2271 elif vobj.GetKeySym() == "z": # set normal along z 2272 self.widget.SetNormal((0, 0, 1)) 2273 self.widget.GetPlane(self._implicit_func) 2274 self.widget.PlaceWidget() 2275 self.widget.GetInteractor().Render() 2276 elif vobj.GetKeySym() == "s": # Ctrl+s to save mesh 2277 if self.widget.GetInteractor(): 2278 if self.widget.GetInteractor().GetControlKey(): 2279 self.mesh.write("vedo_clipped.vtk") 2280 printc(":save: saved mesh to vedo_clipped.vtk")
Create a box widget to cut away parts of a Mesh.
2126 def __init__( 2127 self, 2128 mesh, 2129 invert=False, 2130 can_translate=True, 2131 can_scale=True, 2132 origin=(), 2133 normal=(), 2134 padding=0.05, 2135 delayed=False, 2136 c=(0.25, 0.25, 0.25), 2137 alpha=0.05, 2138 ): 2139 """ 2140 Create a box widget to cut away parts of a `Mesh`. 2141 2142 Arguments: 2143 mesh : (Mesh) 2144 the input mesh 2145 invert : (bool) 2146 invert the clipping plane 2147 can_translate : (bool) 2148 enable translation of the widget 2149 can_scale : (bool) 2150 enable scaling of the widget 2151 origin : (list) 2152 origin of the plane 2153 normal : (list) 2154 normal to the plane 2155 padding : (float) 2156 padding around the input mesh 2157 delayed : (bool) 2158 if True the callback is delayed until 2159 when the mouse button is released (useful for large meshes) 2160 c : (color) 2161 color of the box cutter widget 2162 alpha : (float) 2163 transparency of the cut-off part of the input mesh 2164 2165 Examples: 2166 - [slice_plane3.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slice_plane3.py) 2167 """ 2168 super().__init__() 2169 2170 self.mesh = mesh 2171 self.remnant = Mesh() 2172 self.remnant.name = mesh.name + "Remnant" 2173 self.remnant.pickable(False) 2174 2175 self._alpha = alpha 2176 self._keypress_id = None 2177 2178 self._implicit_func = vtki.new("Plane") 2179 2180 poly = mesh.dataset 2181 self.clipper = vtki.new("ClipPolyData") 2182 self.clipper.GenerateClipScalarsOff() 2183 self.clipper.SetInputData(poly) 2184 self.clipper.SetClipFunction(self._implicit_func) 2185 self.clipper.SetInsideOut(invert) 2186 self.clipper.GenerateClippedOutputOn() 2187 self.clipper.Update() 2188 2189 self.widget = vtki.new("ImplicitPlaneWidget") 2190 2191 # self.widget.KeyPressActivationOff() 2192 # self.widget.SetKeyPressActivationValue('i') 2193 2194 self.widget.SetOriginTranslation(can_translate) 2195 self.widget.SetOutlineTranslation(can_translate) 2196 self.widget.SetScaleEnabled(can_scale) 2197 2198 self.widget.GetOutlineProperty().SetColor(get_color(c)) 2199 self.widget.GetOutlineProperty().SetOpacity(0.25) 2200 self.widget.GetOutlineProperty().SetLineWidth(1) 2201 self.widget.GetOutlineProperty().LightingOff() 2202 2203 self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3")) 2204 2205 self.widget.SetTubing(0) 2206 self.widget.SetDrawPlane(bool(alpha)) 2207 self.widget.GetPlaneProperty().LightingOff() 2208 self.widget.GetPlaneProperty().SetOpacity(alpha) 2209 self.widget.GetSelectedPlaneProperty().SetColor(get_color("red5")) 2210 self.widget.GetSelectedPlaneProperty().LightingOff() 2211 2212 self.widget.SetPlaceFactor(1.0 + padding) 2213 self.widget.SetInputData(poly) 2214 self.widget.PlaceWidget() 2215 if delayed: 2216 self.widget.AddObserver("EndInteractionEvent", self._select_polygons) 2217 else: 2218 self.widget.AddObserver("InteractionEvent", self._select_polygons) 2219 2220 if len(origin) == 3: 2221 self.widget.SetOrigin(origin) 2222 else: 2223 self.widget.SetOrigin(mesh.center_of_mass()) 2224 2225 if len(normal) == 3: 2226 self.widget.SetNormal(normal) 2227 else: 2228 self.widget.SetNormal((1, 0, 0))
Create a box widget to cut away parts of a Mesh
.
Arguments:
- mesh : (Mesh) the input mesh
- invert : (bool) invert the clipping plane
- can_translate : (bool) enable translation of the widget
- can_scale : (bool) enable scaling of the widget
- origin : (list) origin of the plane
- normal : (list) normal to the plane
- padding : (float) padding around the input mesh
- delayed : (bool) if True the callback is delayed until when the mouse button is released (useful for large meshes)
- c : (color) color of the box cutter widget
- alpha : (float) transparency of the cut-off part of the input mesh
Examples:
2230 @property 2231 def origin(self): 2232 """Get the origin of the plane.""" 2233 return np.array(self.widget.GetOrigin())
Get the origin of the plane.
2240 @property 2241 def normal(self): 2242 """Get the normal of the plane.""" 2243 return np.array(self.widget.GetNormal())
Get the normal of the plane.
Inherited Members
2399class SphereCutter(vtki.vtkSphereWidget, BaseCutter): 2400 """ 2401 Create a box widget to cut away parts of a Mesh. 2402 """ 2403 2404 def __init__( 2405 self, 2406 mesh, 2407 invert=False, 2408 can_translate=True, 2409 can_scale=True, 2410 origin=(), 2411 radius=0, 2412 res=60, 2413 delayed=False, 2414 c="white", 2415 alpha=0.05, 2416 ): 2417 """ 2418 Create a box widget to cut away parts of a Mesh. 2419 2420 Arguments: 2421 mesh : Mesh 2422 the input mesh 2423 invert : bool 2424 invert the clipping 2425 can_translate : bool 2426 enable translation of the widget 2427 can_scale : bool 2428 enable scaling of the widget 2429 origin : list 2430 initial position of the sphere widget 2431 radius : float 2432 initial radius of the sphere widget 2433 res : int 2434 resolution of the sphere widget 2435 delayed : bool 2436 if True the cutting callback is delayed until 2437 when the mouse button is released (useful for large meshes) 2438 c : color 2439 color of the box cutter widget 2440 alpha : float 2441 transparency of the cut-off part of the input mesh 2442 """ 2443 super().__init__() 2444 2445 self.mesh = mesh 2446 self.remnant = Mesh() 2447 self.remnant.name = mesh.name + "Remnant" 2448 self.remnant.pickable(False) 2449 2450 self._alpha = alpha 2451 self._keypress_id = None 2452 2453 self._implicit_func = vtki.new("Sphere") 2454 2455 if len(origin) == 3: 2456 self._implicit_func.SetCenter(origin) 2457 else: 2458 origin = mesh.center_of_mass() 2459 self._implicit_func.SetCenter(origin) 2460 2461 if radius > 0: 2462 self._implicit_func.SetRadius(radius) 2463 else: 2464 radius = mesh.average_size() * 2 2465 self._implicit_func.SetRadius(radius) 2466 2467 poly = mesh.dataset 2468 self.clipper = vtki.new("ClipPolyData") 2469 self.clipper.GenerateClipScalarsOff() 2470 self.clipper.SetInputData(poly) 2471 self.clipper.SetClipFunction(self._implicit_func) 2472 self.clipper.SetInsideOut(not invert) 2473 self.clipper.GenerateClippedOutputOn() 2474 self.clipper.Update() 2475 2476 self.widget = vtki.vtkSphereWidget() 2477 2478 self.widget.SetThetaResolution(res * 2) 2479 self.widget.SetPhiResolution(res) 2480 self.widget.SetRadius(radius) 2481 self.widget.SetCenter(origin) 2482 self.widget.SetRepresentation(2) 2483 self.widget.HandleVisibilityOff() 2484 2485 self.widget.SetTranslation(can_translate) 2486 self.widget.SetScale(can_scale) 2487 2488 self.widget.HandleVisibilityOff() 2489 self.widget.GetSphereProperty().SetColor(get_color(c)) 2490 self.widget.GetSphereProperty().SetOpacity(0.2) 2491 self.widget.GetSelectedSphereProperty().SetColor(get_color("red5")) 2492 self.widget.GetSelectedSphereProperty().SetOpacity(0.2) 2493 2494 self.widget.SetPlaceFactor(1.0) 2495 self.widget.SetInputData(poly) 2496 self.widget.PlaceWidget() 2497 if delayed: 2498 self.widget.AddObserver("EndInteractionEvent", self._select_polygons) 2499 else: 2500 self.widget.AddObserver("InteractionEvent", self._select_polygons) 2501 2502 def _select_polygons(self, vobj, event): 2503 vobj.GetSphere(self._implicit_func) 2504 2505 def _keypress(self, vobj, event): 2506 if vobj.GetKeySym() == "r": # reset planes 2507 self._implicit_func.SetBounds(self._init_bounds) 2508 self.widget.GetPlanes(self._implicit_func) 2509 self.widget.PlaceWidget() 2510 self.widget.GetInteractor().Render() 2511 elif vobj.GetKeySym() == "u": 2512 self.invert() 2513 self.widget.GetInteractor().Render() 2514 elif vobj.GetKeySym() == "s": # Ctrl+s to save mesh 2515 if self.widget.GetInteractor(): 2516 if self.widget.GetInteractor().GetControlKey(): 2517 self.mesh.write("vedo_clipped.vtk") 2518 printc(":save: saved mesh to vedo_clipped.vtk") 2519 2520 @property 2521 def center(self): 2522 """Get the center of the sphere.""" 2523 return np.array(self.widget.GetCenter()) 2524 2525 @center.setter 2526 def center(self, value): 2527 """Set the center of the sphere.""" 2528 self.widget.SetCenter(value) 2529 2530 @property 2531 def radius(self): 2532 """Get the radius of the sphere.""" 2533 return self.widget.GetRadius() 2534 2535 @radius.setter 2536 def radius(self, value): 2537 """Set the radius of the sphere.""" 2538 self.widget.SetRadius(value)
Create a box widget to cut away parts of a Mesh.
2404 def __init__( 2405 self, 2406 mesh, 2407 invert=False, 2408 can_translate=True, 2409 can_scale=True, 2410 origin=(), 2411 radius=0, 2412 res=60, 2413 delayed=False, 2414 c="white", 2415 alpha=0.05, 2416 ): 2417 """ 2418 Create a box widget to cut away parts of a Mesh. 2419 2420 Arguments: 2421 mesh : Mesh 2422 the input mesh 2423 invert : bool 2424 invert the clipping 2425 can_translate : bool 2426 enable translation of the widget 2427 can_scale : bool 2428 enable scaling of the widget 2429 origin : list 2430 initial position of the sphere widget 2431 radius : float 2432 initial radius of the sphere widget 2433 res : int 2434 resolution of the sphere widget 2435 delayed : bool 2436 if True the cutting callback is delayed until 2437 when the mouse button is released (useful for large meshes) 2438 c : color 2439 color of the box cutter widget 2440 alpha : float 2441 transparency of the cut-off part of the input mesh 2442 """ 2443 super().__init__() 2444 2445 self.mesh = mesh 2446 self.remnant = Mesh() 2447 self.remnant.name = mesh.name + "Remnant" 2448 self.remnant.pickable(False) 2449 2450 self._alpha = alpha 2451 self._keypress_id = None 2452 2453 self._implicit_func = vtki.new("Sphere") 2454 2455 if len(origin) == 3: 2456 self._implicit_func.SetCenter(origin) 2457 else: 2458 origin = mesh.center_of_mass() 2459 self._implicit_func.SetCenter(origin) 2460 2461 if radius > 0: 2462 self._implicit_func.SetRadius(radius) 2463 else: 2464 radius = mesh.average_size() * 2 2465 self._implicit_func.SetRadius(radius) 2466 2467 poly = mesh.dataset 2468 self.clipper = vtki.new("ClipPolyData") 2469 self.clipper.GenerateClipScalarsOff() 2470 self.clipper.SetInputData(poly) 2471 self.clipper.SetClipFunction(self._implicit_func) 2472 self.clipper.SetInsideOut(not invert) 2473 self.clipper.GenerateClippedOutputOn() 2474 self.clipper.Update() 2475 2476 self.widget = vtki.vtkSphereWidget() 2477 2478 self.widget.SetThetaResolution(res * 2) 2479 self.widget.SetPhiResolution(res) 2480 self.widget.SetRadius(radius) 2481 self.widget.SetCenter(origin) 2482 self.widget.SetRepresentation(2) 2483 self.widget.HandleVisibilityOff() 2484 2485 self.widget.SetTranslation(can_translate) 2486 self.widget.SetScale(can_scale) 2487 2488 self.widget.HandleVisibilityOff() 2489 self.widget.GetSphereProperty().SetColor(get_color(c)) 2490 self.widget.GetSphereProperty().SetOpacity(0.2) 2491 self.widget.GetSelectedSphereProperty().SetColor(get_color("red5")) 2492 self.widget.GetSelectedSphereProperty().SetOpacity(0.2) 2493 2494 self.widget.SetPlaceFactor(1.0) 2495 self.widget.SetInputData(poly) 2496 self.widget.PlaceWidget() 2497 if delayed: 2498 self.widget.AddObserver("EndInteractionEvent", self._select_polygons) 2499 else: 2500 self.widget.AddObserver("InteractionEvent", self._select_polygons)
Create a box widget to cut away parts of a Mesh.
Arguments:
- mesh : Mesh the input mesh
- invert : bool invert the clipping
- can_translate : bool enable translation of the widget
- can_scale : bool enable scaling of the widget
- origin : list initial position of the sphere widget
- radius : float initial radius of the sphere widget
- res : int resolution of the sphere widget
- delayed : bool if True the cutting callback is delayed until when the mouse button is released (useful for large meshes)
- c : color color of the box cutter widget
- alpha : float transparency of the cut-off part of the input mesh
2520 @property 2521 def center(self): 2522 """Get the center of the sphere.""" 2523 return np.array(self.widget.GetCenter())
Get the center of the sphere.
2530 @property 2531 def radius(self): 2532 """Get the radius of the sphere.""" 2533 return self.widget.GetRadius()
Get the radius of the sphere.