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