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