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