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