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