vedo.addons

Create additional objects like axes, legends, lights, etc.

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

A 2D scalar bar for the specified object.

Arguments:
  • title : (str) scalar bar title
  • pos : (list) position coordinates of the bottom left corner. Can also be a pair of (x,y) values in the range [0,1] to indicate the position of the bottom-left and top-right corners.
  • size : (float,float) size of the scalarbar in number of pixels (width, height)
  • font_size : (float) size of font for title and numeric labels
  • title_yoffset : (float) vertical space offset between title and color scalarbar
  • nlabels : (int) number of numeric labels
  • c : (list) color of the scalar bar text
  • horizontal : (bool) lay the scalarbar horizontally
  • use_alpha : (bool) render transparency in the color bar itself
  • label_format : (str) c-style format string for numeric labels
Examples:

def ScalarBar3D( obj, title='', pos=None, size=(0, 0), title_font='', title_xoffset=-1.2, title_yoffset=0.0, title_size=1.5, title_rotation=0.0, nlabels=8, label_font='', label_size=1, label_offset=0.375, label_rotation=0, label_format='', italic=0, c='k', draw_box=True, above_text=None, below_text=None, nan_text='NaN', categories=None) -> Optional[vedo.assembly.Assembly]:
1368def ScalarBar3D(
1369    obj,
1370    title="",
1371    pos=None,
1372    size=(0, 0),
1373    title_font="",
1374    title_xoffset=-1.2,
1375    title_yoffset=0.0,
1376    title_size=1.5,
1377    title_rotation=0.0,
1378    nlabels=8,
1379    label_font="",
1380    label_size=1,
1381    label_offset=0.375,
1382    label_rotation=0,
1383    label_format="",
1384    italic=0,
1385    c="k",
1386    draw_box=True,
1387    above_text=None,
1388    below_text=None,
1389    nan_text="NaN",
1390    categories=None,
1391) -> Union[Assembly, None]:
1392    """
1393    Create a 3D scalar bar for the specified object.
1394
1395    Input `obj` input can be:
1396
1397        - a look-up-table,
1398        - a Mesh already containing a set of scalars associated to vertices or cells,
1399        - if None the last object in the list of actors will be used.
1400
1401    Arguments:
1402        size : (list)
1403            (thickness, length) of scalarbar
1404        title : (str)
1405            scalar bar title
1406        title_xoffset : (float)
1407            horizontal space btw title and color scalarbar
1408        title_yoffset : (float)
1409            vertical space offset
1410        title_size : (float)
1411            size of title wrt numeric labels
1412        title_rotation : (float)
1413            title rotation in degrees
1414        nlabels : (int)
1415            number of numeric labels
1416        label_font : (str)
1417            font type for labels
1418        label_size : (float)
1419            label scale factor
1420        label_offset : (float)
1421            space btw numeric labels and scale
1422        label_rotation : (float)
1423            label rotation in degrees
1424        draw_box : (bool)
1425            draw a box around the colorbar
1426        categories : (list)
1427            make a categorical scalarbar,
1428            the input list will have the format [value, color, alpha, textlabel]
1429
1430    Examples:
1431        - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py)
1432        - [plot_fxy2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy2.py)
1433    """
1434
1435    if isinstance(obj, (Points, TetMesh, vedo.UnstructuredGrid)):
1436        lut = obj.mapper.GetLookupTable()
1437        if not lut or lut.GetTable().GetNumberOfTuples() == 0:
1438            # create the most similar to the default
1439            obj.cmap("jet_r")
1440            lut = obj.mapper.GetLookupTable()
1441        vmin, vmax = lut.GetRange()
1442
1443    elif isinstance(obj, Volume):
1444        lut = utils.ctf2lut(obj)
1445        vmin, vmax = lut.GetRange()
1446
1447    elif isinstance(obj, vtki.vtkLookupTable):
1448        lut = obj
1449        vmin, vmax = lut.GetRange()
1450
1451    else:
1452        vedo.logger.error("in ScalarBar3D(): input must be a vedo object with bounds.")
1453        return None
1454
1455    bns = obj.bounds()
1456    sx, sy = size
1457    if sy == 0 or sy is None:
1458        sy = bns[3] - bns[2]
1459    if sx == 0 or sx is None:
1460        sx = sy / 18
1461
1462    if categories is not None:  ################################
1463        ncats = len(categories)
1464        scale = shapes.Grid([-float(sx) * label_offset, 0, 0],
1465                            c=c, alpha=1, s=(sx, sy), res=(1, ncats))
1466        cols, alphas = [], []
1467        ticks_pos, ticks_txt = [0.0], [""]
1468        for i, cat in enumerate(categories):
1469            cl = get_color(cat[1])
1470            cols.append(cl)
1471            if len(cat) > 2:
1472                alphas.append(cat[2])
1473            else:
1474                alphas.append(1)
1475            if len(cat) > 3:
1476                ticks_txt.append(cat[3])
1477            else:
1478                ticks_txt.append("")
1479            ticks_pos.append((i + 0.5) / ncats)
1480        ticks_pos.append(1.0)
1481        ticks_txt.append("")
1482        rgba = np.c_[np.array(cols) * 255, np.array(alphas) * 255]
1483        scale.cellcolors = rgba
1484
1485    else:  ########################################################
1486
1487        # build the color scale part
1488        scale = shapes.Grid(
1489            [-float(sx) * label_offset, 0, 0],
1490            c=c,
1491            s=(sx, sy),
1492            res=(1, lut.GetTable().GetNumberOfTuples()),
1493        )
1494        cscals = np.linspace(vmin, vmax, lut.GetTable().GetNumberOfTuples(), endpoint=True)
1495
1496        if lut.GetScale():  # logarithmic scale
1497            lut10 = vtki.vtkLookupTable()
1498            lut10.DeepCopy(lut)
1499            lut10.SetScaleToLinear()
1500            lut10.Build()
1501            scale.cmap(lut10, cscals, on="cells")
1502            tk = utils.make_ticks(vmin, vmax, nlabels, logscale=True, useformat=label_format)
1503        else:
1504            # for i in range(lut.GetTable().GetNumberOfTuples()):
1505            #     print("LUT i=", i, lut.GetTableValue(i))
1506            scale.cmap(lut, cscals, on="cells")
1507            tk = utils.make_ticks(vmin, vmax, nlabels, logscale=False, useformat=label_format)
1508        ticks_pos, ticks_txt = tk
1509
1510    scale.lw(0).wireframe(False).lighting("off")
1511
1512    scales = [scale]
1513
1514    xbns = scale.xbounds()
1515
1516    lsize = sy / 60 * label_size
1517
1518    tacts = []
1519    for i, p in enumerate(ticks_pos):
1520        tx = ticks_txt[i]
1521        if i and tx:
1522            # build numeric text
1523            y = (p - 0.5) * sy
1524            if label_rotation:
1525                a = shapes.Text3D(
1526                    tx,
1527                    s=lsize,
1528                    justify="center-top",
1529                    c=c,
1530                    italic=italic,
1531                    font=label_font,
1532                )
1533                a.rotate_z(label_rotation)
1534                a.pos(sx * label_offset, y, 0)
1535            else:
1536                a = shapes.Text3D(
1537                    tx,
1538                    pos=[sx * label_offset, y, 0],
1539                    s=lsize,
1540                    justify="center-left",
1541                    c=c,
1542                    italic=italic,
1543                    font=label_font,
1544                )
1545
1546            tacts.append(a)
1547
1548            # build ticks
1549            tic = shapes.Line([xbns[1], y, 0], [xbns[1] + sx * label_offset / 4, y, 0], lw=2, c=c)
1550            tacts.append(tic)
1551
1552    # build title
1553    if title:
1554        t = shapes.Text3D(
1555            title,
1556            pos=(0, 0, 0),
1557            s=sy / 50 * title_size,
1558            c=c,
1559            justify="centered-bottom",
1560            italic=italic,
1561            font=title_font,
1562        )
1563        t.rotate_z(90 + title_rotation)
1564        t.pos(sx * title_xoffset, title_yoffset, 0)
1565        tacts.append(t)
1566
1567    if pos is None:
1568        tsize = 0
1569        if title:
1570            bbt = t.bounds()
1571            tsize = bbt[1] - bbt[0]
1572        pos = (bns[1] + tsize + sx * 1.5, (bns[2] + bns[3]) / 2, bns[4])
1573
1574    # build below scale
1575    if lut.GetUseBelowRangeColor():
1576        r, g, b, alfa = lut.GetBelowRangeColor()
1577        sx = float(sx)
1578        sy = float(sy)
1579        brect = shapes.Rectangle(
1580            [-sx * label_offset - sx / 2, -sy / 2 - sx - sx * 0.1, 0],
1581            [-sx * label_offset + sx / 2, -sy / 2 - sx * 0.1, 0],
1582            c=(r, g, b),
1583            alpha=alfa,
1584        )
1585        brect.lw(1).lc(c).lighting("off")
1586        scales += [brect]
1587        if below_text is None:
1588            below_text = " <" + str(vmin)
1589        if below_text:
1590            if label_rotation:
1591                btx = shapes.Text3D(
1592                    below_text,
1593                    pos=(0, 0, 0),
1594                    s=lsize,
1595                    c=c,
1596                    justify="center-top",
1597                    italic=italic,
1598                    font=label_font,
1599                )
1600                btx.rotate_z(label_rotation)
1601            else:
1602                btx = shapes.Text3D(
1603                    below_text,
1604                    pos=(0, 0, 0),
1605                    s=lsize,
1606                    c=c,
1607                    justify="center-left",
1608                    italic=italic,
1609                    font=label_font,
1610                )
1611
1612            btx.pos(sx * label_offset, -sy / 2 - sx * 0.66, 0)
1613            tacts.append(btx)
1614
1615    # build above scale
1616    if lut.GetUseAboveRangeColor():
1617        r, g, b, alfa = lut.GetAboveRangeColor()
1618        arect = shapes.Rectangle(
1619            [-sx * label_offset - sx / 2, sy / 2 + sx * 0.1, 0],
1620            [-sx * label_offset + sx / 2, sy / 2 + sx + sx * 0.1, 0],
1621            c=(r, g, b),
1622            alpha=alfa,
1623        )
1624        arect.lw(1).lc(c).lighting("off")
1625        scales += [arect]
1626        if above_text is None:
1627            above_text = " >" + str(vmax)
1628        if above_text:
1629            if label_rotation:
1630                atx = shapes.Text3D(
1631                    above_text,
1632                    pos=(0, 0, 0),
1633                    s=lsize,
1634                    c=c,
1635                    justify="center-top",
1636                    italic=italic,
1637                    font=label_font,
1638                )
1639                atx.rotate_z(label_rotation)
1640            else:
1641                atx = shapes.Text3D(
1642                    above_text,
1643                    pos=(0, 0, 0),
1644                    s=lsize,
1645                    c=c,
1646                    justify="center-left",
1647                    italic=italic,
1648                    font=label_font,
1649                )
1650
1651            atx.pos(sx * label_offset, sy / 2 + sx * 0.66, 0)
1652            tacts.append(atx)
1653
1654    # build NaN scale
1655    if lut.GetNanColor() != (0.5, 0.0, 0.0, 1.0):
1656        nanshift = sx * 0.1
1657        if brect:
1658            nanshift += sx
1659        r, g, b, alfa = lut.GetNanColor()
1660        nanrect = shapes.Rectangle(
1661            [-sx * label_offset - sx / 2, -sy / 2 - sx - sx * 0.1 - nanshift, 0],
1662            [-sx * label_offset + sx / 2, -sy / 2 - sx * 0.1 - nanshift, 0],
1663            c=(r, g, b),
1664            alpha=alfa,
1665        )
1666        nanrect.lw(1).lc(c).lighting("off")
1667        scales += [nanrect]
1668        if label_rotation:
1669            nantx = shapes.Text3D(
1670                nan_text,
1671                pos=(0, 0, 0),
1672                s=lsize,
1673                c=c,
1674                justify="center-left",
1675                italic=italic,
1676                font=label_font,
1677            )
1678            nantx.rotate_z(label_rotation)
1679        else:
1680            nantx = shapes.Text3D(
1681                nan_text,
1682                pos=(0, 0, 0),
1683                s=lsize,
1684                c=c,
1685                justify="center-left",
1686                italic=italic,
1687                font=label_font,
1688            )
1689        nantx.pos(sx * label_offset, -sy / 2 - sx * 0.66 - nanshift, 0)
1690        tacts.append(nantx)
1691
1692    if draw_box:
1693        tacts.append(scale.box().lw(1).c(c))
1694
1695    for m in tacts + scales:
1696        m.shift(pos)
1697        m.actor.PickableOff()
1698        m.properties.LightingOff()
1699
1700    asse = Assembly(scales + tacts)
1701
1702    # asse.transform = LinearTransform().shift(pos)
1703
1704    bb = asse.GetBounds()
1705    # print("ScalarBar3D pos",pos, bb)
1706    # asse.SetOrigin(pos)
1707
1708    asse.SetOrigin(bb[0], bb[2], bb[4])
1709    # asse.SetOrigin(bb[0],0,0) #in pyplot line 1312
1710
1711    asse.PickableOff()
1712    asse.UseBoundsOff()
1713    asse.name = "ScalarBar3D"
1714    return asse

Create a 3D scalar bar for the specified object.

Input obj input can be:

- a look-up-table,
- a Mesh already containing a set of scalars associated to vertices or cells,
- if None the last object in the list of actors will be used.
Arguments:
  • size : (list) (thickness, length) of scalarbar
  • title : (str) scalar bar title
  • title_xoffset : (float) horizontal space btw title and color scalarbar
  • title_yoffset : (float) vertical space offset
  • title_size : (float) size of title wrt numeric labels
  • title_rotation : (float) title rotation in degrees
  • nlabels : (int) number of numeric labels
  • label_font : (str) font type for labels
  • label_size : (float) label scale factor
  • label_offset : (float) space btw numeric labels and scale
  • label_rotation : (float) label rotation in degrees
  • draw_box : (bool) draw a box around the colorbar
  • categories : (list) make a categorical scalarbar, the input list will have the format [value, color, alpha, textlabel]
Examples:
class Slider2D(SliderWidget):
1718class Slider2D(SliderWidget):
1719    """
1720    Add a slider which can call an external custom function.
1721    """
1722
1723    def __init__(
1724        self,
1725        sliderfunc,
1726        xmin,
1727        xmax,
1728        value=None,
1729        pos=4,
1730        title="",
1731        font="Calco",
1732        title_size=1,
1733        c="k",
1734        alpha=1,
1735        show_value=True,
1736        delayed=False,
1737        **options,
1738    ):
1739        """
1740        Add a slider which can call an external custom function.
1741        Set any value as float to increase the number of significant digits above the slider.
1742
1743        Use `play()` to start an animation between the current slider value and the last value.
1744
1745        Arguments:
1746            sliderfunc : (function)
1747                external function to be called by the widget
1748            xmin : (float)
1749                lower value of the slider
1750            xmax : (float)
1751                upper value
1752            value : (float)
1753                current value
1754            pos : (list, str)
1755                position corner number: horizontal [1-5] or vertical [11-15]
1756                it can also be specified by corners coordinates [(x1,y1), (x2,y2)]
1757                and also by a string descriptor (eg. "bottom-left")
1758            title : (str)
1759                title text
1760            font : (str)
1761                title font face. Check [available fonts here](https://vedo.embl.es/fonts).
1762            title_size : (float)
1763                title text scale [1.0]
1764            show_value : (bool)
1765                if True current value is shown
1766            delayed : (bool)
1767                if True the callback is delayed until when the mouse button is released
1768            alpha : (float)
1769                opacity of the scalar bar texts
1770            slider_length : (float)
1771                slider length
1772            slider_width : (float)
1773                slider width
1774            end_cap_length : (float)
1775                length of the end cap
1776            end_cap_width : (float)
1777                width of the end cap
1778            tube_width : (float)
1779                width of the tube
1780            title_height : (float)
1781                height of the title
1782            tformat : (str)
1783                format of the title
1784
1785        Examples:
1786            - [sliders1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders1.py)
1787            - [sliders2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders2.py)
1788
1789            ![](https://user-images.githubusercontent.com/32848391/50738848-be033480-11d8-11e9-9b1a-c13105423a79.jpg)
1790        """
1791        slider_length = options.pop("slider_length",  0.015)
1792        slider_width  = options.pop("slider_width",   0.025)
1793        end_cap_length= options.pop("end_cap_length", 0.0015)
1794        end_cap_width = options.pop("end_cap_width",  0.0125)
1795        tube_width    = options.pop("tube_width",     0.0075)
1796        title_height  = options.pop("title_height",   0.025)
1797        tformat       = options.pop("tformat",        None)
1798
1799        if options:
1800            vedo.logger.warning(f"in Slider2D unknown option(s): {options}")
1801
1802        c = get_color(c)
1803
1804        if value is None or value < xmin:
1805            value = xmin
1806
1807        slider_rep = vtki.new("SliderRepresentation2D")
1808        slider_rep.SetMinimumValue(xmin)
1809        slider_rep.SetMaximumValue(xmax)
1810        slider_rep.SetValue(value)
1811        slider_rep.SetSliderLength(slider_length)
1812        slider_rep.SetSliderWidth(slider_width)
1813        slider_rep.SetEndCapLength(end_cap_length)
1814        slider_rep.SetEndCapWidth(end_cap_width)
1815        slider_rep.SetTubeWidth(tube_width)
1816        slider_rep.GetPoint1Coordinate().SetCoordinateSystemToNormalizedDisplay()
1817        slider_rep.GetPoint2Coordinate().SetCoordinateSystemToNormalizedDisplay()
1818
1819        if isinstance(pos, str):
1820            if "top" in pos:
1821                if "left" in pos:
1822                    if "vert" in pos:
1823                        pos = 11
1824                    else:
1825                        pos = 1
1826                elif "right" in pos:
1827                    if "vert" in pos:
1828                        pos = 12
1829                    else:
1830                        pos = 2
1831            elif "bott" in pos:
1832                if "left" in pos:
1833                    if "vert" in pos:
1834                        pos = 13
1835                    else:
1836                        pos = 3
1837                elif "right" in pos:
1838                    if "vert" in pos:
1839                        if "span" in pos:
1840                            pos = 15
1841                        else:
1842                            pos = 14
1843                    else:
1844                        pos = 4
1845                elif "span" in pos:
1846                    pos = 5
1847
1848        if utils.is_sequence(pos):
1849            slider_rep.GetPoint1Coordinate().SetValue(pos[0][0], pos[0][1])
1850            slider_rep.GetPoint2Coordinate().SetValue(pos[1][0], pos[1][1])
1851        elif pos == 1:  # top-left horizontal
1852            slider_rep.GetPoint1Coordinate().SetValue(0.04, 0.93)
1853            slider_rep.GetPoint2Coordinate().SetValue(0.45, 0.93)
1854        elif pos == 2:
1855            slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.93)
1856            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.93)
1857        elif pos == 3:
1858            slider_rep.GetPoint1Coordinate().SetValue(0.05, 0.06)
1859            slider_rep.GetPoint2Coordinate().SetValue(0.45, 0.06)
1860        elif pos == 4:  # bottom-right
1861            slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.06)
1862            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06)
1863        elif pos == 5:  # bottom span horizontal
1864            slider_rep.GetPoint1Coordinate().SetValue(0.04, 0.06)
1865            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06)
1866        elif pos == 11:  # top-left vertical
1867            slider_rep.GetPoint1Coordinate().SetValue(0.065, 0.54)
1868            slider_rep.GetPoint2Coordinate().SetValue(0.065, 0.9)
1869        elif pos == 12:
1870            slider_rep.GetPoint1Coordinate().SetValue(0.94, 0.54)
1871            slider_rep.GetPoint2Coordinate().SetValue(0.94, 0.9)
1872        elif pos == 13:
1873            slider_rep.GetPoint1Coordinate().SetValue(0.065, 0.1)
1874            slider_rep.GetPoint2Coordinate().SetValue(0.065, 0.54)
1875        elif pos == 14:  # bottom-right vertical
1876            slider_rep.GetPoint1Coordinate().SetValue(0.94, 0.1)
1877            slider_rep.GetPoint2Coordinate().SetValue(0.94, 0.54)
1878        elif pos == 15:  # right margin vertical
1879            slider_rep.GetPoint1Coordinate().SetValue(0.95, 0.1)
1880            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.9)
1881        else:  # bottom-right
1882            slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.06)
1883            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06)
1884
1885        if show_value:
1886            if tformat is None:
1887                if isinstance(xmin, int) and isinstance(xmax, int) and isinstance(value, int):
1888                    tformat = "%0.0f"
1889                else:
1890                    tformat = "%0.2f"
1891
1892            slider_rep.SetLabelFormat(tformat)  # default is '%0.3g'
1893            slider_rep.GetLabelProperty().SetShadow(0)
1894            slider_rep.GetLabelProperty().SetBold(0)
1895            slider_rep.GetLabelProperty().SetOpacity(alpha)
1896            slider_rep.GetLabelProperty().SetColor(c)
1897            if isinstance(pos, int) and pos > 10:
1898                slider_rep.GetLabelProperty().SetOrientation(90)
1899        else:
1900            slider_rep.ShowSliderLabelOff()
1901        slider_rep.GetTubeProperty().SetColor(c)
1902        slider_rep.GetTubeProperty().SetOpacity(0.75)
1903        slider_rep.GetSliderProperty().SetColor(c)
1904        slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c)))
1905        slider_rep.GetCapProperty().SetColor(c)
1906
1907        slider_rep.SetTitleHeight(title_height * title_size)
1908        slider_rep.GetTitleProperty().SetShadow(0)
1909        slider_rep.GetTitleProperty().SetColor(c)
1910        slider_rep.GetTitleProperty().SetOpacity(alpha)
1911        slider_rep.GetTitleProperty().SetBold(0)
1912        if font.lower() == "courier":
1913            slider_rep.GetTitleProperty().SetFontFamilyToCourier()
1914        elif font.lower() == "times":
1915            slider_rep.GetTitleProperty().SetFontFamilyToTimes()
1916        elif font.lower() == "arial":
1917            slider_rep.GetTitleProperty().SetFontFamilyToArial()
1918        else:
1919            if font == "":
1920                font = utils.get_font_path(settings.default_font)
1921            else:
1922                font = utils.get_font_path(font)
1923            slider_rep.GetTitleProperty().SetFontFamily(vtki.VTK_FONT_FILE)
1924            slider_rep.GetLabelProperty().SetFontFamily(vtki.VTK_FONT_FILE)
1925            slider_rep.GetTitleProperty().SetFontFile(font)
1926            slider_rep.GetLabelProperty().SetFontFile(font)
1927
1928        if title:
1929            slider_rep.SetTitleText(title)
1930            if not utils.is_sequence(pos):
1931                if isinstance(pos, int) and pos > 10:
1932                    slider_rep.GetTitleProperty().SetOrientation(90)
1933            else:
1934                if abs(pos[0][0] - pos[1][0]) < 0.1:
1935                    slider_rep.GetTitleProperty().SetOrientation(90)
1936
1937        super().__init__()
1938
1939        self.SetAnimationModeToJump()
1940        self.SetRepresentation(slider_rep)
1941        if delayed:
1942            self.AddObserver("EndInteractionEvent", sliderfunc)
1943        else:
1944            self.AddObserver("InteractionEvent", sliderfunc)

Add a slider which can call an external custom function.

Slider2D( sliderfunc, xmin, xmax, value=None, pos=4, title='', font='Calco', title_size=1, c='k', alpha=1, show_value=True, delayed=False, **options)
1723    def __init__(
1724        self,
1725        sliderfunc,
1726        xmin,
1727        xmax,
1728        value=None,
1729        pos=4,
1730        title="",
1731        font="Calco",
1732        title_size=1,
1733        c="k",
1734        alpha=1,
1735        show_value=True,
1736        delayed=False,
1737        **options,
1738    ):
1739        """
1740        Add a slider which can call an external custom function.
1741        Set any value as float to increase the number of significant digits above the slider.
1742
1743        Use `play()` to start an animation between the current slider value and the last value.
1744
1745        Arguments:
1746            sliderfunc : (function)
1747                external function to be called by the widget
1748            xmin : (float)
1749                lower value of the slider
1750            xmax : (float)
1751                upper value
1752            value : (float)
1753                current value
1754            pos : (list, str)
1755                position corner number: horizontal [1-5] or vertical [11-15]
1756                it can also be specified by corners coordinates [(x1,y1), (x2,y2)]
1757                and also by a string descriptor (eg. "bottom-left")
1758            title : (str)
1759                title text
1760            font : (str)
1761                title font face. Check [available fonts here](https://vedo.embl.es/fonts).
1762            title_size : (float)
1763                title text scale [1.0]
1764            show_value : (bool)
1765                if True current value is shown
1766            delayed : (bool)
1767                if True the callback is delayed until when the mouse button is released
1768            alpha : (float)
1769                opacity of the scalar bar texts
1770            slider_length : (float)
1771                slider length
1772            slider_width : (float)
1773                slider width
1774            end_cap_length : (float)
1775                length of the end cap
1776            end_cap_width : (float)
1777                width of the end cap
1778            tube_width : (float)
1779                width of the tube
1780            title_height : (float)
1781                height of the title
1782            tformat : (str)
1783                format of the title
1784
1785        Examples:
1786            - [sliders1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders1.py)
1787            - [sliders2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders2.py)
1788
1789            ![](https://user-images.githubusercontent.com/32848391/50738848-be033480-11d8-11e9-9b1a-c13105423a79.jpg)
1790        """
1791        slider_length = options.pop("slider_length",  0.015)
1792        slider_width  = options.pop("slider_width",   0.025)
1793        end_cap_length= options.pop("end_cap_length", 0.0015)
1794        end_cap_width = options.pop("end_cap_width",  0.0125)
1795        tube_width    = options.pop("tube_width",     0.0075)
1796        title_height  = options.pop("title_height",   0.025)
1797        tformat       = options.pop("tformat",        None)
1798
1799        if options:
1800            vedo.logger.warning(f"in Slider2D unknown option(s): {options}")
1801
1802        c = get_color(c)
1803
1804        if value is None or value < xmin:
1805            value = xmin
1806
1807        slider_rep = vtki.new("SliderRepresentation2D")
1808        slider_rep.SetMinimumValue(xmin)
1809        slider_rep.SetMaximumValue(xmax)
1810        slider_rep.SetValue(value)
1811        slider_rep.SetSliderLength(slider_length)
1812        slider_rep.SetSliderWidth(slider_width)
1813        slider_rep.SetEndCapLength(end_cap_length)
1814        slider_rep.SetEndCapWidth(end_cap_width)
1815        slider_rep.SetTubeWidth(tube_width)
1816        slider_rep.GetPoint1Coordinate().SetCoordinateSystemToNormalizedDisplay()
1817        slider_rep.GetPoint2Coordinate().SetCoordinateSystemToNormalizedDisplay()
1818
1819        if isinstance(pos, str):
1820            if "top" in pos:
1821                if "left" in pos:
1822                    if "vert" in pos:
1823                        pos = 11
1824                    else:
1825                        pos = 1
1826                elif "right" in pos:
1827                    if "vert" in pos:
1828                        pos = 12
1829                    else:
1830                        pos = 2
1831            elif "bott" in pos:
1832                if "left" in pos:
1833                    if "vert" in pos:
1834                        pos = 13
1835                    else:
1836                        pos = 3
1837                elif "right" in pos:
1838                    if "vert" in pos:
1839                        if "span" in pos:
1840                            pos = 15
1841                        else:
1842                            pos = 14
1843                    else:
1844                        pos = 4
1845                elif "span" in pos:
1846                    pos = 5
1847
1848        if utils.is_sequence(pos):
1849            slider_rep.GetPoint1Coordinate().SetValue(pos[0][0], pos[0][1])
1850            slider_rep.GetPoint2Coordinate().SetValue(pos[1][0], pos[1][1])
1851        elif pos == 1:  # top-left horizontal
1852            slider_rep.GetPoint1Coordinate().SetValue(0.04, 0.93)
1853            slider_rep.GetPoint2Coordinate().SetValue(0.45, 0.93)
1854        elif pos == 2:
1855            slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.93)
1856            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.93)
1857        elif pos == 3:
1858            slider_rep.GetPoint1Coordinate().SetValue(0.05, 0.06)
1859            slider_rep.GetPoint2Coordinate().SetValue(0.45, 0.06)
1860        elif pos == 4:  # bottom-right
1861            slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.06)
1862            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06)
1863        elif pos == 5:  # bottom span horizontal
1864            slider_rep.GetPoint1Coordinate().SetValue(0.04, 0.06)
1865            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06)
1866        elif pos == 11:  # top-left vertical
1867            slider_rep.GetPoint1Coordinate().SetValue(0.065, 0.54)
1868            slider_rep.GetPoint2Coordinate().SetValue(0.065, 0.9)
1869        elif pos == 12:
1870            slider_rep.GetPoint1Coordinate().SetValue(0.94, 0.54)
1871            slider_rep.GetPoint2Coordinate().SetValue(0.94, 0.9)
1872        elif pos == 13:
1873            slider_rep.GetPoint1Coordinate().SetValue(0.065, 0.1)
1874            slider_rep.GetPoint2Coordinate().SetValue(0.065, 0.54)
1875        elif pos == 14:  # bottom-right vertical
1876            slider_rep.GetPoint1Coordinate().SetValue(0.94, 0.1)
1877            slider_rep.GetPoint2Coordinate().SetValue(0.94, 0.54)
1878        elif pos == 15:  # right margin vertical
1879            slider_rep.GetPoint1Coordinate().SetValue(0.95, 0.1)
1880            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.9)
1881        else:  # bottom-right
1882            slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.06)
1883            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06)
1884
1885        if show_value:
1886            if tformat is None:
1887                if isinstance(xmin, int) and isinstance(xmax, int) and isinstance(value, int):
1888                    tformat = "%0.0f"
1889                else:
1890                    tformat = "%0.2f"
1891
1892            slider_rep.SetLabelFormat(tformat)  # default is '%0.3g'
1893            slider_rep.GetLabelProperty().SetShadow(0)
1894            slider_rep.GetLabelProperty().SetBold(0)
1895            slider_rep.GetLabelProperty().SetOpacity(alpha)
1896            slider_rep.GetLabelProperty().SetColor(c)
1897            if isinstance(pos, int) and pos > 10:
1898                slider_rep.GetLabelProperty().SetOrientation(90)
1899        else:
1900            slider_rep.ShowSliderLabelOff()
1901        slider_rep.GetTubeProperty().SetColor(c)
1902        slider_rep.GetTubeProperty().SetOpacity(0.75)
1903        slider_rep.GetSliderProperty().SetColor(c)
1904        slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c)))
1905        slider_rep.GetCapProperty().SetColor(c)
1906
1907        slider_rep.SetTitleHeight(title_height * title_size)
1908        slider_rep.GetTitleProperty().SetShadow(0)
1909        slider_rep.GetTitleProperty().SetColor(c)
1910        slider_rep.GetTitleProperty().SetOpacity(alpha)
1911        slider_rep.GetTitleProperty().SetBold(0)
1912        if font.lower() == "courier":
1913            slider_rep.GetTitleProperty().SetFontFamilyToCourier()
1914        elif font.lower() == "times":
1915            slider_rep.GetTitleProperty().SetFontFamilyToTimes()
1916        elif font.lower() == "arial":
1917            slider_rep.GetTitleProperty().SetFontFamilyToArial()
1918        else:
1919            if font == "":
1920                font = utils.get_font_path(settings.default_font)
1921            else:
1922                font = utils.get_font_path(font)
1923            slider_rep.GetTitleProperty().SetFontFamily(vtki.VTK_FONT_FILE)
1924            slider_rep.GetLabelProperty().SetFontFamily(vtki.VTK_FONT_FILE)
1925            slider_rep.GetTitleProperty().SetFontFile(font)
1926            slider_rep.GetLabelProperty().SetFontFile(font)
1927
1928        if title:
1929            slider_rep.SetTitleText(title)
1930            if not utils.is_sequence(pos):
1931                if isinstance(pos, int) and pos > 10:
1932                    slider_rep.GetTitleProperty().SetOrientation(90)
1933            else:
1934                if abs(pos[0][0] - pos[1][0]) < 0.1:
1935                    slider_rep.GetTitleProperty().SetOrientation(90)
1936
1937        super().__init__()
1938
1939        self.SetAnimationModeToJump()
1940        self.SetRepresentation(slider_rep)
1941        if delayed:
1942            self.AddObserver("EndInteractionEvent", sliderfunc)
1943        else:
1944            self.AddObserver("InteractionEvent", sliderfunc)

Add a slider which can call an external custom function. Set any value as float to increase the number of significant digits above the slider.

Use play() to start an animation between the current slider value and the last value.

Arguments:
  • sliderfunc : (function) external function to be called by the widget
  • xmin : (float) lower value of the slider
  • xmax : (float) upper value
  • value : (float) current value
  • pos : (list, str) position corner number: horizontal [1-5] or vertical [11-15] it can also be specified by corners coordinates [(x1,y1), (x2,y2)] and also by a string descriptor (eg. "bottom-left")
  • title : (str) title text
  • font : (str) title font face. Check available fonts here.
  • title_size : (float) title text scale [1.0]
  • show_value : (bool) if True current value is shown
  • delayed : (bool) if True the callback is delayed until when the mouse button is released
  • alpha : (float) opacity of the scalar bar texts
  • slider_length : (float) slider length
  • slider_width : (float) slider width
  • end_cap_length : (float) length of the end cap
  • end_cap_width : (float) width of the end cap
  • tube_width : (float) width of the tube
  • title_height : (float) height of the title
  • tformat : (str) format of the title
Examples:

Inherited Members
SliderWidget
on
off
toggle
add_observer
class Slider3D(SliderWidget):
1948class Slider3D(SliderWidget):
1949    """
1950    Add a 3D slider which can call an external custom function.
1951    """
1952
1953    def __init__(
1954        self,
1955        sliderfunc,
1956        pos1,
1957        pos2,
1958        xmin,
1959        xmax,
1960        value=None,
1961        s=0.03,
1962        t=1,
1963        title="",
1964        rotation=0,
1965        c=None,
1966        show_value=True,
1967    ):
1968        """
1969        Add a 3D slider which can call an external custom function.
1970
1971        Arguments:
1972            sliderfunc : (function)
1973                external function to be called by the widget
1974            pos1 : (list)
1975                first position 3D coordinates
1976            pos2 : (list)
1977                second position 3D coordinates
1978            xmin : (float)
1979                lower value
1980            xmax : (float)
1981                upper value
1982            value : (float)
1983                initial value
1984            s : (float)
1985                label scaling factor
1986            t : (float)
1987                tube scaling factor
1988            title : (str)
1989                title text
1990            c : (color)
1991                slider color
1992            rotation : (float)
1993                title rotation around slider axis
1994            show_value : (bool)
1995                if True current value is shown on top of the slider
1996
1997        Examples:
1998            - [sliders3d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders3d.py)
1999        """
2000        c = get_color(c)
2001
2002        if value is None or value < xmin:
2003            value = xmin
2004
2005        slider_rep = vtki.new("SliderRepresentation3D")
2006        slider_rep.SetMinimumValue(xmin)
2007        slider_rep.SetMaximumValue(xmax)
2008        slider_rep.SetValue(value)
2009
2010        slider_rep.GetPoint1Coordinate().SetCoordinateSystemToWorld()
2011        slider_rep.GetPoint2Coordinate().SetCoordinateSystemToWorld()
2012        slider_rep.GetPoint1Coordinate().SetValue(pos2)
2013        slider_rep.GetPoint2Coordinate().SetValue(pos1)
2014
2015        # slider_rep.SetPoint1InWorldCoordinates(pos2[0], pos2[1], pos2[2])
2016        # slider_rep.SetPoint2InWorldCoordinates(pos1[0], pos1[1], pos1[2])
2017
2018        slider_rep.SetSliderWidth(0.03 * t)
2019        slider_rep.SetTubeWidth(0.01 * t)
2020        slider_rep.SetSliderLength(0.04 * t)
2021        slider_rep.SetSliderShapeToCylinder()
2022        slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c)))
2023        slider_rep.GetSliderProperty().SetColor(np.array(c) / 1.5)
2024        slider_rep.GetCapProperty().SetOpacity(0)
2025        slider_rep.SetRotation(rotation)
2026
2027        if not show_value:
2028            slider_rep.ShowSliderLabelOff()
2029
2030        slider_rep.SetTitleText(title)
2031        slider_rep.SetTitleHeight(s * t)
2032        slider_rep.SetLabelHeight(s * t * 0.85)
2033
2034        slider_rep.GetTubeProperty().SetColor(c)
2035
2036        super().__init__()
2037
2038        self.SetRepresentation(slider_rep)
2039        self.SetAnimationModeToJump()
2040        self.AddObserver("InteractionEvent", sliderfunc)

Add a 3D slider which can call an external custom function.

Slider3D( sliderfunc, pos1, pos2, xmin, xmax, value=None, s=0.03, t=1, title='', rotation=0, c=None, show_value=True)
1953    def __init__(
1954        self,
1955        sliderfunc,
1956        pos1,
1957        pos2,
1958        xmin,
1959        xmax,
1960        value=None,
1961        s=0.03,
1962        t=1,
1963        title="",
1964        rotation=0,
1965        c=None,
1966        show_value=True,
1967    ):
1968        """
1969        Add a 3D slider which can call an external custom function.
1970
1971        Arguments:
1972            sliderfunc : (function)
1973                external function to be called by the widget
1974            pos1 : (list)
1975                first position 3D coordinates
1976            pos2 : (list)
1977                second position 3D coordinates
1978            xmin : (float)
1979                lower value
1980            xmax : (float)
1981                upper value
1982            value : (float)
1983                initial value
1984            s : (float)
1985                label scaling factor
1986            t : (float)
1987                tube scaling factor
1988            title : (str)
1989                title text
1990            c : (color)
1991                slider color
1992            rotation : (float)
1993                title rotation around slider axis
1994            show_value : (bool)
1995                if True current value is shown on top of the slider
1996
1997        Examples:
1998            - [sliders3d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders3d.py)
1999        """
2000        c = get_color(c)
2001
2002        if value is None or value < xmin:
2003            value = xmin
2004
2005        slider_rep = vtki.new("SliderRepresentation3D")
2006        slider_rep.SetMinimumValue(xmin)
2007        slider_rep.SetMaximumValue(xmax)
2008        slider_rep.SetValue(value)
2009
2010        slider_rep.GetPoint1Coordinate().SetCoordinateSystemToWorld()
2011        slider_rep.GetPoint2Coordinate().SetCoordinateSystemToWorld()
2012        slider_rep.GetPoint1Coordinate().SetValue(pos2)
2013        slider_rep.GetPoint2Coordinate().SetValue(pos1)
2014
2015        # slider_rep.SetPoint1InWorldCoordinates(pos2[0], pos2[1], pos2[2])
2016        # slider_rep.SetPoint2InWorldCoordinates(pos1[0], pos1[1], pos1[2])
2017
2018        slider_rep.SetSliderWidth(0.03 * t)
2019        slider_rep.SetTubeWidth(0.01 * t)
2020        slider_rep.SetSliderLength(0.04 * t)
2021        slider_rep.SetSliderShapeToCylinder()
2022        slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c)))
2023        slider_rep.GetSliderProperty().SetColor(np.array(c) / 1.5)
2024        slider_rep.GetCapProperty().SetOpacity(0)
2025        slider_rep.SetRotation(rotation)
2026
2027        if not show_value:
2028            slider_rep.ShowSliderLabelOff()
2029
2030        slider_rep.SetTitleText(title)
2031        slider_rep.SetTitleHeight(s * t)
2032        slider_rep.SetLabelHeight(s * t * 0.85)
2033
2034        slider_rep.GetTubeProperty().SetColor(c)
2035
2036        super().__init__()
2037
2038        self.SetRepresentation(slider_rep)
2039        self.SetAnimationModeToJump()
2040        self.AddObserver("InteractionEvent", sliderfunc)

Add a 3D slider which can call an external custom function.

Arguments:
  • sliderfunc : (function) external function to be called by the widget
  • pos1 : (list) first position 3D coordinates
  • pos2 : (list) second position 3D coordinates
  • xmin : (float) lower value
  • xmax : (float) upper value
  • value : (float) initial value
  • s : (float) label scaling factor
  • t : (float) tube scaling factor
  • title : (str) title text
  • c : (color) slider color
  • rotation : (float) title rotation around slider axis
  • show_value : (bool) if True current value is shown on top of the slider
Examples:
Inherited Members
SliderWidget
on
off
toggle
add_observer
class Icon(vtkmodules.vtkInteractionWidgets.vtkOrientationMarkerWidget):
2710class Icon(vtki.vtkOrientationMarkerWidget):
2711    """
2712    Add an inset icon mesh into the renderer.
2713    """
2714
2715    def __init__(self, mesh, pos=3, size=0.08):
2716        """
2717        Arguments:
2718            pos : (list, int)
2719                icon position in the range [1-4] indicating one of the 4 corners,
2720                or it can be a tuple (x,y) as a fraction of the renderer size.
2721            size : (float)
2722                size of the icon space as fraction of the window size.
2723
2724        Examples:
2725            - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py)
2726        """
2727        super().__init__()
2728
2729        try:
2730            self.SetOrientationMarker(mesh.actor)
2731        except AttributeError:
2732            self.SetOrientationMarker(mesh)
2733
2734        if utils.is_sequence(pos):
2735            self.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size)
2736        else:
2737            if pos < 2:
2738                self.SetViewport(0, 1 - 2 * size, size * 2, 1)
2739            elif pos == 2:
2740                self.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1)
2741            elif pos == 3:
2742                self.SetViewport(0, 0, size * 2, size * 2)
2743            elif pos == 4:
2744                self.SetViewport(1 - 2 * size, 0, 1, size * 2)

Add an inset icon mesh into the renderer.

Icon(mesh, pos=3, size=0.08)
2715    def __init__(self, mesh, pos=3, size=0.08):
2716        """
2717        Arguments:
2718            pos : (list, int)
2719                icon position in the range [1-4] indicating one of the 4 corners,
2720                or it can be a tuple (x,y) as a fraction of the renderer size.
2721            size : (float)
2722                size of the icon space as fraction of the window size.
2723
2724        Examples:
2725            - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py)
2726        """
2727        super().__init__()
2728
2729        try:
2730            self.SetOrientationMarker(mesh.actor)
2731        except AttributeError:
2732            self.SetOrientationMarker(mesh)
2733
2734        if utils.is_sequence(pos):
2735            self.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size)
2736        else:
2737            if pos < 2:
2738                self.SetViewport(0, 1 - 2 * size, size * 2, 1)
2739            elif pos == 2:
2740                self.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1)
2741            elif pos == 3:
2742                self.SetViewport(0, 0, size * 2, size * 2)
2743            elif pos == 4:
2744                self.SetViewport(1 - 2 * size, 0, 1, size * 2)
Arguments:
  • pos : (list, int) icon position in the range [1-4] indicating one of the 4 corners, or it can be a tuple (x,y) as a fraction of the renderer size.
  • size : (float) size of the icon space as fraction of the window size.
Examples:
class LegendBox(vedo.shapes.TextBase, vtkmodules.vtkRenderingAnnotation.vtkLegendBoxActor):
214class LegendBox(shapes.TextBase, vtki.vtkLegendBoxActor):
215    """
216    Create a 2D legend box.
217    """
218
219    def __init__(
220        self,
221        entries=(),
222        nmax=12,
223        c=None,
224        font="",
225        width=0.18,
226        height=None,
227        padding=2,
228        bg="k8",
229        alpha=0.25,
230        pos="top-right",
231        markers=None,
232    ):
233        """
234        Create a 2D legend box for the list of specified objects.
235
236        Arguments:
237            nmax : (int)
238                max number of legend entries
239            c : (color)
240                text color, leave as None to pick the mesh color automatically
241            font : (str)
242                Check [available fonts here](https://vedo.embl.es/fonts)
243            width : (float)
244                width of the box as fraction of the window width
245            height : (float)
246                height of the box as fraction of the window height
247            padding : (int)
248                padding space in units of pixels
249            bg : (color)
250                background color of the box
251            alpha: (float)
252                opacity of the box
253            pos : (str, list)
254                position of the box, can be either a string or a (x,y) screen position in range [0,1]
255
256        Examples:
257            - [legendbox.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/legendbox.py)
258            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
259            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
260
261                ![](https://vedo.embl.es/images/other/flag_labels.png)
262        """
263        super().__init__()
264
265        self.name = "LegendBox"
266        self.entries = entries[:nmax]
267        self.properties = self.GetEntryTextProperty()
268
269        n = 0
270        texts = []
271        for e in self.entries:
272            ename = e.name
273            if "legend" in e.info.keys():
274                if not e.info["legend"]:
275                    ename = ""
276                else:
277                    ename = str(e.info["legend"])
278            if ename:
279                n += 1
280            texts.append(ename)
281        self.SetNumberOfEntries(n)
282
283        if not n:
284            return
285
286        self.ScalarVisibilityOff()
287        self.PickableOff()
288        self.SetPadding(padding)
289
290        self.properties.ShadowOff()
291        self.properties.BoldOff()
292
293        # self.properties.SetJustificationToLeft() # no effect
294        # self.properties.SetVerticalJustificationToTop()
295
296        if not font:
297            font = settings.default_font
298
299        self.font(font)
300
301        n = 0
302        for i in range(len(self.entries)):
303            ti = texts[i]
304            if not ti:
305                continue
306            e = entries[i]
307            if c is None:
308                col = e.properties.GetColor()
309                if col == (1, 1, 1):
310                    col = (0.2, 0.2, 0.2)
311            else:
312                col = get_color(c)
313            if markers is None:  # default
314                poly = e.dataset
315            else:
316                marker = markers[i] if utils.is_sequence(markers) else markers
317                if isinstance(marker, Points):
318                    poly = marker.clone(deep=False).normalize().shift(0, 1, 0).dataset
319                else:  # assume string marker
320                    poly = vedo.shapes.Marker(marker, s=1).shift(0, 1, 0).dataset
321
322            self.SetEntry(n, poly, ti, col)
323            n += 1
324
325        self.SetWidth(width)
326        if height is None:
327            self.SetHeight(width / 3.0 * n)
328        else:
329            self.SetHeight(height)
330
331        self.pos(pos)
332
333        if alpha:
334            self.UseBackgroundOn()
335            self.SetBackgroundColor(get_color(bg))
336            self.SetBackgroundOpacity(alpha)
337        else:
338            self.UseBackgroundOff()
339        self.LockBorderOn()
340
341    @property
342    def width(self):
343        """Return the width of the legend box."""
344        return self.GetWidth()
345
346    @property
347    def height(self):
348        """Return the height of the legend box."""
349        return self.GetHeight()
350
351    def pos(self, pos):
352        """Set the position of the legend box."""
353        sx, sy = 1 - self.GetWidth(), 1 - self.GetHeight()
354        if pos == 1 or ("top" in pos and "left" in pos):
355            self.GetPositionCoordinate().SetValue(0, sy)
356        elif pos == 2 or ("top" in pos and "right" in pos):
357            self.GetPositionCoordinate().SetValue(sx, sy)
358        elif pos == 3 or ("bottom" in pos and "left" in pos):
359            self.GetPositionCoordinate().SetValue(0, 0)
360        elif pos == 4 or ("bottom" in pos and "right" in pos):
361            self.GetPositionCoordinate().SetValue(sx, 0)
362        elif "cent" in pos and "right" in pos:
363            self.GetPositionCoordinate().SetValue(sx, sy - 0.25)
364        elif "cent" in pos and "left" in pos:
365            self.GetPositionCoordinate().SetValue(0, sy - 0.25)
366        elif "cent" in pos and "bottom" in pos:
367            self.GetPositionCoordinate().SetValue(sx - 0.25, 0)
368        elif "cent" in pos and "top" in pos:
369            self.GetPositionCoordinate().SetValue(sx - 0.25, sy)
370        elif utils.is_sequence(pos):
371            self.GetPositionCoordinate().SetValue(pos[0], pos[1])
372        else:
373            vedo.logger.error("LegendBox: pos must be in range [1-4] or a [x,y] list")
374
375        return self

Create a 2D legend box.

LegendBox( entries=(), nmax=12, c=None, font='', width=0.18, height=None, padding=2, bg='k8', alpha=0.25, pos='top-right', markers=None)
219    def __init__(
220        self,
221        entries=(),
222        nmax=12,
223        c=None,
224        font="",
225        width=0.18,
226        height=None,
227        padding=2,
228        bg="k8",
229        alpha=0.25,
230        pos="top-right",
231        markers=None,
232    ):
233        """
234        Create a 2D legend box for the list of specified objects.
235
236        Arguments:
237            nmax : (int)
238                max number of legend entries
239            c : (color)
240                text color, leave as None to pick the mesh color automatically
241            font : (str)
242                Check [available fonts here](https://vedo.embl.es/fonts)
243            width : (float)
244                width of the box as fraction of the window width
245            height : (float)
246                height of the box as fraction of the window height
247            padding : (int)
248                padding space in units of pixels
249            bg : (color)
250                background color of the box
251            alpha: (float)
252                opacity of the box
253            pos : (str, list)
254                position of the box, can be either a string or a (x,y) screen position in range [0,1]
255
256        Examples:
257            - [legendbox.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/legendbox.py)
258            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
259            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
260
261                ![](https://vedo.embl.es/images/other/flag_labels.png)
262        """
263        super().__init__()
264
265        self.name = "LegendBox"
266        self.entries = entries[:nmax]
267        self.properties = self.GetEntryTextProperty()
268
269        n = 0
270        texts = []
271        for e in self.entries:
272            ename = e.name
273            if "legend" in e.info.keys():
274                if not e.info["legend"]:
275                    ename = ""
276                else:
277                    ename = str(e.info["legend"])
278            if ename:
279                n += 1
280            texts.append(ename)
281        self.SetNumberOfEntries(n)
282
283        if not n:
284            return
285
286        self.ScalarVisibilityOff()
287        self.PickableOff()
288        self.SetPadding(padding)
289
290        self.properties.ShadowOff()
291        self.properties.BoldOff()
292
293        # self.properties.SetJustificationToLeft() # no effect
294        # self.properties.SetVerticalJustificationToTop()
295
296        if not font:
297            font = settings.default_font
298
299        self.font(font)
300
301        n = 0
302        for i in range(len(self.entries)):
303            ti = texts[i]
304            if not ti:
305                continue
306            e = entries[i]
307            if c is None:
308                col = e.properties.GetColor()
309                if col == (1, 1, 1):
310                    col = (0.2, 0.2, 0.2)
311            else:
312                col = get_color(c)
313            if markers is None:  # default
314                poly = e.dataset
315            else:
316                marker = markers[i] if utils.is_sequence(markers) else markers
317                if isinstance(marker, Points):
318                    poly = marker.clone(deep=False).normalize().shift(0, 1, 0).dataset
319                else:  # assume string marker
320                    poly = vedo.shapes.Marker(marker, s=1).shift(0, 1, 0).dataset
321
322            self.SetEntry(n, poly, ti, col)
323            n += 1
324
325        self.SetWidth(width)
326        if height is None:
327            self.SetHeight(width / 3.0 * n)
328        else:
329            self.SetHeight(height)
330
331        self.pos(pos)
332
333        if alpha:
334            self.UseBackgroundOn()
335            self.SetBackgroundColor(get_color(bg))
336            self.SetBackgroundOpacity(alpha)
337        else:
338            self.UseBackgroundOff()
339        self.LockBorderOn()

Create a 2D legend box for the list of specified objects.

Arguments:
  • nmax : (int) max number of legend entries
  • c : (color) text color, leave as None to pick the mesh color automatically
  • font : (str) Check available fonts here
  • width : (float) width of the box as fraction of the window width
  • height : (float) height of the box as fraction of the window height
  • padding : (int) padding space in units of pixels
  • bg : (color) background color of the box
  • alpha: (float) opacity of the box
  • pos : (str, list) position of the box, can be either a string or a (x,y) screen position in range [0,1]
Examples:
width
341    @property
342    def width(self):
343        """Return the width of the legend box."""
344        return self.GetWidth()

Return the width of the legend box.

height
346    @property
347    def height(self):
348        """Return the height of the legend box."""
349        return self.GetHeight()

Return the height of the legend box.

def pos(self, pos):
351    def pos(self, pos):
352        """Set the position of the legend box."""
353        sx, sy = 1 - self.GetWidth(), 1 - self.GetHeight()
354        if pos == 1 or ("top" in pos and "left" in pos):
355            self.GetPositionCoordinate().SetValue(0, sy)
356        elif pos == 2 or ("top" in pos and "right" in pos):
357            self.GetPositionCoordinate().SetValue(sx, sy)
358        elif pos == 3 or ("bottom" in pos and "left" in pos):
359            self.GetPositionCoordinate().SetValue(0, 0)
360        elif pos == 4 or ("bottom" in pos and "right" in pos):
361            self.GetPositionCoordinate().SetValue(sx, 0)
362        elif "cent" in pos and "right" in pos:
363            self.GetPositionCoordinate().SetValue(sx, sy - 0.25)
364        elif "cent" in pos and "left" in pos:
365            self.GetPositionCoordinate().SetValue(0, sy - 0.25)
366        elif "cent" in pos and "bottom" in pos:
367            self.GetPositionCoordinate().SetValue(sx - 0.25, 0)
368        elif "cent" in pos and "top" in pos:
369            self.GetPositionCoordinate().SetValue(sx - 0.25, sy)
370        elif utils.is_sequence(pos):
371            self.GetPositionCoordinate().SetValue(pos[0], pos[1])
372        else:
373            vedo.logger.error("LegendBox: pos must be in range [1-4] or a [x,y] list")
374
375        return self

Set the position of the legend box.

def Light(pos, focal_point=(0, 0, 0), angle=180, c=None, intensity=1):
1150def Light(pos, focal_point=(0, 0, 0), angle=180, c=None, intensity=1):
1151    """
1152    Generate a source of light placed at `pos` and directed to `focal point`.
1153    Returns a `vtkLight` object.
1154
1155    Arguments:
1156        focal_point : (list)
1157            focal point, if a `vedo` object is passed then will grab its position.
1158        angle : (float)
1159            aperture angle of the light source, in degrees
1160        c : (color)
1161            set the light color
1162        intensity : (float)
1163            intensity value between 0 and 1.
1164
1165    Check also:
1166        `plotter.Plotter.remove_lights()`
1167
1168    Examples:
1169        - [light_sources.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/light_sources.py)
1170
1171            ![](https://vedo.embl.es/images/basic/lights.png)
1172    """
1173    if c is None:
1174        try:
1175            c = pos.color()
1176        except AttributeError:
1177            c = "white"
1178
1179    try:
1180        pos = pos.pos()
1181    except AttributeError:
1182        pass
1183
1184    try:
1185        focal_point = focal_point.pos()
1186    except AttributeError:
1187        pass
1188
1189    light = vtki.vtkLight()
1190    light.SetLightTypeToSceneLight()
1191    light.SetPosition(pos)
1192    light.SetConeAngle(angle)
1193    light.SetFocalPoint(focal_point)
1194    light.SetIntensity(intensity)
1195    light.SetColor(get_color(c))
1196    return light

Generate a source of light placed at pos and directed to focal point. Returns a vtkLight object.

Arguments:
  • focal_point : (list) focal point, if a vedo object is passed then will grab its position.
  • angle : (float) aperture angle of the light source, in degrees
  • c : (color) set the light color
  • intensity : (float) intensity value between 0 and 1.
Check also:

plotter.Plotter.remove_lights()

Examples:
def Axes( obj=None, xtitle='x', ytitle='y', ztitle='z', xrange=None, yrange=None, zrange=None, c=None, number_of_divisions=None, digits=None, limit_ratio=0.04, title_depth=0, title_font='', text_scale=1.0, x_values_and_labels=None, y_values_and_labels=None, z_values_and_labels=None, htitle='', htitle_size=0.03, htitle_font=None, htitle_italic=False, htitle_color=None, htitle_backface_color=None, htitle_justify='bottom-left', htitle_rotation=0, htitle_offset=(0, 0.01, 0), xtitle_position=0.95, ytitle_position=0.95, ztitle_position=0.95, xtitle_offset=0.025, ytitle_offset=0.0275, ztitle_offset=0.02, xtitle_justify=None, ytitle_justify=None, ztitle_justify=None, xtitle_rotation=0, ytitle_rotation=0, ztitle_rotation=0, xtitle_box=False, ytitle_box=False, xtitle_size=0.025, ytitle_size=0.025, ztitle_size=0.025, xtitle_color=None, ytitle_color=None, ztitle_color=None, xtitle_backface_color=None, ytitle_backface_color=None, ztitle_backface_color=None, xtitle_italic=0, ytitle_italic=0, ztitle_italic=0, grid_linewidth=1, xygrid=True, yzgrid=False, zxgrid=False, xygrid2=False, yzgrid2=False, zxgrid2=False, xygrid_transparent=False, yzgrid_transparent=False, zxgrid_transparent=False, xygrid2_transparent=False, yzgrid2_transparent=False, zxgrid2_transparent=False, xyplane_color=None, yzplane_color=None, zxplane_color=None, xygrid_color=None, yzgrid_color=None, zxgrid_color=None, xyalpha=0.075, yzalpha=0.075, zxalpha=0.075, xyframe_line=None, yzframe_line=None, zxframe_line=None, xyframe_color=None, yzframe_color=None, zxframe_color=None, axes_linewidth=1, xline_color=None, yline_color=None, zline_color=None, xhighlight_zero=False, yhighlight_zero=False, zhighlight_zero=False, xhighlight_zero_color='red4', yhighlight_zero_color='green4', zhighlight_zero_color='blue4', show_ticks=True, xtick_length=0.015, ytick_length=0.015, ztick_length=0.015, xtick_thickness=0.0025, ytick_thickness=0.0025, ztick_thickness=0.0025, xminor_ticks=1, yminor_ticks=1, zminor_ticks=1, tip_size=None, label_font='', xlabel_color=None, ylabel_color=None, zlabel_color=None, xlabel_backface_color=None, ylabel_backface_color=None, zlabel_backface_color=None, xlabel_size=0.016, ylabel_size=0.016, zlabel_size=0.016, xlabel_offset=0.8, ylabel_offset=0.8, zlabel_offset=0.8, xlabel_justify=None, ylabel_justify=None, zlabel_justify=None, xlabel_rotation=0, ylabel_rotation=0, zlabel_rotation=0, xaxis_rotation=0, yaxis_rotation=0, zaxis_rotation=0, xyshift=0, yzshift=0, zxshift=0, xshift_along_y=0, xshift_along_z=0, yshift_along_x=0, yshift_along_z=0, zshift_along_x=0, zshift_along_y=0, x_use_bounds=True, y_use_bounds=True, z_use_bounds=False, x_inverted=False, y_inverted=False, z_inverted=False, use_global=False, tol=0.001) -> Optional[vedo.assembly.Assembly]:
3293def Axes(
3294        obj=None,
3295        xtitle='x', ytitle='y', ztitle='z',
3296        xrange=None, yrange=None, zrange=None,
3297        c=None,
3298        number_of_divisions=None,
3299        digits=None,
3300        limit_ratio=0.04,
3301        title_depth=0,
3302        title_font="", # grab settings.default_font
3303        text_scale=1.0,
3304        x_values_and_labels=None, y_values_and_labels=None, z_values_and_labels=None,
3305        htitle="",
3306        htitle_size=0.03,
3307        htitle_font=None,
3308        htitle_italic=False,
3309        htitle_color=None, htitle_backface_color=None,
3310        htitle_justify='bottom-left',
3311        htitle_rotation=0,
3312        htitle_offset=(0, 0.01, 0),
3313        xtitle_position=0.95, ytitle_position=0.95, ztitle_position=0.95,
3314        # xtitle_offset can be a list (dx,dy,dz)
3315        xtitle_offset=0.025,  ytitle_offset=0.0275, ztitle_offset=0.02,
3316        xtitle_justify=None,  ytitle_justify=None,  ztitle_justify=None,
3317        # xtitle_rotation can be a list (rx,ry,rz)
3318        xtitle_rotation=0, ytitle_rotation=0, ztitle_rotation=0,
3319        xtitle_box=False,  ytitle_box=False,
3320        xtitle_size=0.025, ytitle_size=0.025, ztitle_size=0.025,
3321        xtitle_color=None, ytitle_color=None, ztitle_color=None,
3322        xtitle_backface_color=None, ytitle_backface_color=None, ztitle_backface_color=None,
3323        xtitle_italic=0, ytitle_italic=0, ztitle_italic=0,
3324        grid_linewidth=1,
3325        xygrid=True,   yzgrid=False,  zxgrid=False,
3326        xygrid2=False, yzgrid2=False, zxgrid2=False,
3327        xygrid_transparent=False,  yzgrid_transparent=False,  zxgrid_transparent=False,
3328        xygrid2_transparent=False, yzgrid2_transparent=False, zxgrid2_transparent=False,
3329        xyplane_color=None, yzplane_color=None, zxplane_color=None,
3330        xygrid_color=None, yzgrid_color=None, zxgrid_color=None,
3331        xyalpha=0.075, yzalpha=0.075, zxalpha=0.075,
3332        xyframe_line=None, yzframe_line=None, zxframe_line=None,
3333        xyframe_color=None, yzframe_color=None, zxframe_color=None,
3334        axes_linewidth=1,
3335        xline_color=None, yline_color=None, zline_color=None,
3336        xhighlight_zero=False, yhighlight_zero=False, zhighlight_zero=False,
3337        xhighlight_zero_color='red4', yhighlight_zero_color='green4', zhighlight_zero_color='blue4',
3338        show_ticks=True,
3339        xtick_length=0.015, ytick_length=0.015, ztick_length=0.015,
3340        xtick_thickness=0.0025, ytick_thickness=0.0025, ztick_thickness=0.0025,
3341        xminor_ticks=1, yminor_ticks=1, zminor_ticks=1,
3342        tip_size=None,
3343        label_font="", # grab settings.default_font
3344        xlabel_color=None, ylabel_color=None, zlabel_color=None,
3345        xlabel_backface_color=None, ylabel_backface_color=None, zlabel_backface_color=None,
3346        xlabel_size=0.016, ylabel_size=0.016, zlabel_size=0.016,
3347        xlabel_offset=0.8, ylabel_offset=0.8, zlabel_offset=0.8, # each can be a list (dx,dy,dz)
3348        xlabel_justify=None, ylabel_justify=None, zlabel_justify=None,
3349        xlabel_rotation=0, ylabel_rotation=0, zlabel_rotation=0, # each can be a list (rx,ry,rz)
3350        xaxis_rotation=0, yaxis_rotation=0, zaxis_rotation=0,    # rotate all elements around axis
3351        xyshift=0, yzshift=0, zxshift=0,
3352        xshift_along_y=0, xshift_along_z=0,
3353        yshift_along_x=0, yshift_along_z=0,
3354        zshift_along_x=0, zshift_along_y=0,
3355        x_use_bounds=True, y_use_bounds=True, z_use_bounds=False,
3356        x_inverted=False, y_inverted=False, z_inverted=False,
3357        use_global=False,
3358        tol=0.001,
3359    ) -> Union[Assembly, None]:
3360    """
3361    Draw axes for the input object.
3362    Check [available fonts here](https://vedo.embl.es/fonts).
3363
3364    Returns an `vedo.Assembly` object.
3365
3366    Parameters
3367    ----------
3368
3369    - `xtitle`,                 ['x'], x-axis title text
3370    - `xrange`,                [None], x-axis range in format (xmin, ymin), default is automatic.
3371    - `number_of_divisions`,   [None], approximate number of divisions on the longest axis
3372    - `axes_linewidth`,           [1], width of the axes lines
3373    - `grid_linewidth`,           [1], width of the grid lines
3374    - `title_depth`,              [0], extrusion fractional depth of title text
3375    - `x_values_and_labels`        [], assign custom tick positions and labels [(pos1, label1), ...]
3376    - `xygrid`,                [True], show a gridded wall on plane xy
3377    - `yzgrid`,                [True], show a gridded wall on plane yz
3378    - `zxgrid`,                [True], show a gridded wall on plane zx
3379    - `yzgrid2`,              [False], show yz plane on opposite side of the bounding box
3380    - `zxgrid2`,              [False], show zx plane on opposite side of the bounding box
3381    - `xygrid_transparent`    [False], make grid plane completely transparent
3382    - `xygrid2_transparent`   [False], make grid plane completely transparent on opposite side box
3383    - `xyplane_color`,       ['None'], color of the plane
3384    - `xygrid_color`,        ['None'], grid line color
3385    - `xyalpha`,               [0.15], grid plane opacity
3386    - `xyframe_line`,             [0], add a frame for the plane, use value as the thickness
3387    - `xyframe_color`,         [None], color for the frame of the plane
3388    - `show_ticks`,            [True], show major ticks
3389    - `digits`,                [None], use this number of significant digits in scientific notation
3390    - `title_font`,              [''], font for axes titles
3391    - `label_font`,              [''], font for numeric labels
3392    - `text_scale`,             [1.0], global scaling factor for all text elements (titles, labels)
3393    - `htitle`,                  [''], header title
3394    - `htitle_size`,           [0.03], header title size
3395    - `htitle_font`,           [None], header font (defaults to `title_font`)
3396    - `htitle_italic`,         [True], header font is italic
3397    - `htitle_color`,          [None], header title color (defaults to `xtitle_color`)
3398    - `htitle_backface_color`, [None], header title color on its backface
3399    - `htitle_justify`, ['bottom-center'], origin of the title justification
3400    - `htitle_offset`,   [(0,0.01,0)], control offsets of header title in x, y and z
3401    - `xtitle_position`,       [0.32], title fractional positions along axis
3402    - `xtitle_offset`,         [0.05], title fractional offset distance from axis line, can be a list
3403    - `xtitle_justify`,        [None], choose the origin of the bounding box of title
3404    - `xtitle_rotation`,          [0], add a rotation of the axis title, can be a list (rx,ry,rz)
3405    - `xtitle_box`,           [False], add a box around title text
3406    - `xline_color`,      [automatic], color of the x-axis
3407    - `xtitle_color`,     [automatic], color of the axis title
3408    - `xtitle_backface_color`, [None], color of axis title on its backface
3409    - `xtitle_size`,          [0.025], size of the axis title
3410    - `xtitle_italic`,            [0], a bool or float to make the font italic
3411    - `xhighlight_zero`,       [True], draw a line highlighting zero position if in range
3412    - `xhighlight_zero_color`, [auto], color of the line highlighting the zero position
3413    - `xtick_length`,         [0.005], radius of the major ticks
3414    - `xtick_thickness`,     [0.0025], thickness of the major ticks along their axis
3415    - `xminor_ticks`,             [1], number of minor ticks between two major ticks
3416    - `xlabel_color`,     [automatic], color of numeric labels and ticks
3417    - `xlabel_backface_color`, [auto], back face color of numeric labels and ticks
3418    - `xlabel_size`,          [0.015], size of the numeric labels along axis
3419    - `xlabel_rotation`,     [0,list], numeric labels rotation (can be a list of 3 rotations)
3420    - `xlabel_offset`,     [0.8,list], offset of the numeric labels (can be a list of 3 offsets)
3421    - `xlabel_justify`,        [None], choose the origin of the bounding box of labels
3422    - `xaxis_rotation`,           [0], rotate the X axis elements (ticks and labels) around this same axis
3423    - `xyshift`                 [0.0], slide the xy-plane along z (the range is [0,1])
3424    - `xshift_along_y`          [0.0], slide x-axis along the y-axis (the range is [0,1])
3425    - `tip_size`,              [0.01], size of the arrow tip as a fraction of the bounding box diagonal
3426    - `limit_ratio`,           [0.04], below this ratio don't plot smaller axis
3427    - `x_use_bounds`,          [True], keep into account space occupied by labels when setting camera
3428    - `x_inverted`,           [False], invert labels order and direction (only visually!)
3429    - `use_global`,           [False], try to compute the global bounding box of visible actors
3430
3431    Example:
3432        ```python
3433        from vedo import Axes, Box, show
3434        box = Box(pos=(1,2,3), length=8, width=9, height=7).alpha(0.1)
3435        axs = Axes(box, c='k')  # returns an Assembly object
3436        for a in axs.unpack():
3437            print(a.name)
3438        show(box, axs).close()
3439        ```
3440        ![](https://vedo.embl.es/images/feats/axes1.png)
3441
3442    Examples:
3443        - [custom_axes1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes1.py)
3444        - [custom_axes2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes2.py)
3445        - [custom_axes3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes3.py)
3446        - [custom_axes4.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes4.py)
3447
3448        ![](https://vedo.embl.es/images/pyplot/customAxes3.png)
3449    """
3450    if not title_font:
3451        title_font = vedo.settings.default_font
3452    if not label_font:
3453        label_font = vedo.settings.default_font
3454
3455    if c is None:  # automatic black or white
3456        c = (0.1, 0.1, 0.1)
3457        plt = vedo.plotter_instance
3458        if plt and plt.renderer:
3459            bgcol = plt.renderer.GetBackground()
3460        else:
3461            bgcol = (1, 1, 1)
3462        if np.sum(bgcol) < 1.5:
3463            c = (0.9, 0.9, 0.9)
3464    else:
3465        c = get_color(c)
3466
3467    # Check if obj has bounds, if so use those
3468    if obj is not None:
3469        try:
3470            bb = obj.bounds()
3471        except AttributeError:
3472            try:
3473                bb = obj.GetBounds()
3474                if xrange is None: xrange = (bb[0], bb[1])
3475                if yrange is None: yrange = (bb[2], bb[3])
3476                if zrange is None: zrange = (bb[4], bb[5])
3477                obj = None # dont need it anymore
3478            except AttributeError:
3479                pass
3480        if utils.is_sequence(obj) and len(obj) == 6 and utils.is_number(obj[0]):
3481            # passing a list of numeric bounds
3482            if xrange is None: xrange = (obj[0], obj[1])
3483            if yrange is None: yrange = (obj[2], obj[3])
3484            if zrange is None: zrange = (obj[4], obj[5])
3485
3486    if use_global:
3487        vbb, drange, min_bns, max_bns = compute_visible_bounds()
3488    else:
3489        if obj is not None:
3490            vbb, drange, min_bns, max_bns = compute_visible_bounds(obj)
3491        else:
3492            vbb = np.zeros(6)
3493            drange = np.zeros(3)
3494            if zrange is None:
3495                zrange = (0, 0)
3496            if xrange is None or yrange is None:
3497                vedo.logger.error("in Axes() must specify axes ranges!")
3498                return None  ###########################################
3499
3500    if xrange is not None:
3501        if xrange[1] < xrange[0]:
3502            x_inverted = True
3503            xrange = [xrange[1], xrange[0]]
3504        vbb[0], vbb[1] = xrange
3505        drange[0] = vbb[1] - vbb[0]
3506        min_bns = vbb
3507        max_bns = vbb
3508    if yrange is not None:
3509        if yrange[1] < yrange[0]:
3510            y_inverted = True
3511            yrange = [yrange[1], yrange[0]]
3512        vbb[2], vbb[3] = yrange
3513        drange[1] = vbb[3] - vbb[2]
3514        min_bns = vbb
3515        max_bns = vbb
3516    if zrange is not None:
3517        if zrange[1] < zrange[0]:
3518            z_inverted = True
3519            zrange = [zrange[1], zrange[0]]
3520        vbb[4], vbb[5] = zrange
3521        drange[2] = vbb[5] - vbb[4]
3522        min_bns = vbb
3523        max_bns = vbb
3524
3525    drangemax = max(drange)
3526    if not drangemax:
3527        return None
3528
3529    if drange[0] / drangemax < limit_ratio:
3530        drange[0] = 0
3531        xtitle = ""
3532    if drange[1] / drangemax < limit_ratio:
3533        drange[1] = 0
3534        ytitle = ""
3535    if drange[2] / drangemax < limit_ratio:
3536        drange[2] = 0
3537        ztitle = ""
3538
3539    x0, x1, y0, y1, z0, z1 = vbb
3540    dx, dy, dz = drange
3541
3542    gscale = np.sqrt(dx * dx + dy * dy + dz * dz) * 0.75
3543
3544    if not xyplane_color: xyplane_color = c
3545    if not yzplane_color: yzplane_color = c
3546    if not zxplane_color: zxplane_color = c
3547    if not xygrid_color:  xygrid_color = c
3548    if not yzgrid_color:  yzgrid_color = c
3549    if not zxgrid_color:  zxgrid_color = c
3550    if not xtitle_color:  xtitle_color = c
3551    if not ytitle_color:  ytitle_color = c
3552    if not ztitle_color:  ztitle_color = c
3553    if not xline_color:   xline_color = c
3554    if not yline_color:   yline_color = c
3555    if not zline_color:   zline_color = c
3556    if not xlabel_color:  xlabel_color = xline_color
3557    if not ylabel_color:  ylabel_color = yline_color
3558    if not zlabel_color:  zlabel_color = zline_color
3559
3560    if tip_size is None:
3561        tip_size = 0.005 * gscale
3562        if not ztitle:
3563            tip_size = 0  # switch off in xy 2d
3564
3565    ndiv = 4
3566    if not ztitle or not ytitle or not xtitle:  # make more default ticks if 2D
3567        ndiv = 6
3568        if not ztitle:
3569            if xyframe_line is None:
3570                xyframe_line = True
3571            if tip_size is None:
3572                tip_size = False
3573
3574    if utils.is_sequence(number_of_divisions):
3575        rx, ry, rz = number_of_divisions
3576    else:
3577        if not number_of_divisions:
3578            number_of_divisions = ndiv
3579        if not drangemax or np.any(np.isnan(drange)):
3580            rx, ry, rz = 1, 1, 1
3581        else:
3582            rx, ry, rz = np.ceil(drange / drangemax * number_of_divisions).astype(int)
3583
3584    if xtitle:
3585        xticks_float, xticks_str = utils.make_ticks(x0, x1, rx, x_values_and_labels, digits)
3586        xticks_float = xticks_float * dx
3587        if x_inverted:
3588            xticks_float = np.flip(-(xticks_float - xticks_float[-1]))
3589            xticks_str = list(reversed(xticks_str))
3590            xticks_str[-1] = ""
3591            xhighlight_zero = False
3592    if ytitle:
3593        yticks_float, yticks_str = utils.make_ticks(y0, y1, ry, y_values_and_labels, digits)
3594        yticks_float = yticks_float * dy
3595        if y_inverted:
3596            yticks_float = np.flip(-(yticks_float - yticks_float[-1]))
3597            yticks_str = list(reversed(yticks_str))
3598            yticks_str[-1] = ""
3599            yhighlight_zero = False
3600    if ztitle:
3601        zticks_float, zticks_str = utils.make_ticks(z0, z1, rz, z_values_and_labels, digits)
3602        zticks_float = zticks_float * dz
3603        if z_inverted:
3604            zticks_float = np.flip(-(zticks_float - zticks_float[-1]))
3605            zticks_str = list(reversed(zticks_str))
3606            zticks_str[-1] = ""
3607            zhighlight_zero = False
3608
3609    ################################################ axes lines
3610    lines = []
3611    if xtitle:
3612        axlinex = shapes.Line([0,0,0], [dx,0,0], c=xline_color, lw=axes_linewidth)
3613        axlinex.shift([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3614        axlinex.name = 'xAxis'
3615        lines.append(axlinex)
3616    if ytitle:
3617        axliney = shapes.Line([0,0,0], [0,dy,0], c=yline_color, lw=axes_linewidth)
3618        axliney.shift([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3619        axliney.name = 'yAxis'
3620        lines.append(axliney)
3621    if ztitle:
3622        axlinez = shapes.Line([0,0,0], [0,0,dz], c=zline_color, lw=axes_linewidth)
3623        axlinez.shift([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3624        axlinez.name = 'zAxis'
3625        lines.append(axlinez)
3626
3627    ################################################ grid planes
3628    # all shapes have a name to keep track of them in the Assembly
3629    # if user wants to unpack it
3630    grids = []
3631    if xygrid and xtitle and ytitle:
3632        if not xygrid_transparent:
3633            gxy = shapes.Grid(s=(xticks_float, yticks_float))
3634            gxy.alpha(xyalpha).c(xyplane_color).lw(0)
3635            if xyshift: gxy.shift([0,0,xyshift*dz])
3636            elif tol:   gxy.shift([0,0,-tol*gscale])
3637            gxy.name = "xyGrid"
3638            grids.append(gxy)
3639        if grid_linewidth:
3640            gxy_lines = shapes.Grid(s=(xticks_float, yticks_float))
3641            gxy_lines.c(xyplane_color).lw(grid_linewidth).alpha(xyalpha)
3642            if xyshift: gxy_lines.shift([0,0,xyshift*dz])
3643            elif tol:   gxy_lines.shift([0,0,-tol*gscale])
3644            gxy_lines.name = "xyGridLines"
3645            grids.append(gxy_lines)
3646
3647    if yzgrid and ytitle and ztitle:
3648        if not yzgrid_transparent:
3649            gyz = shapes.Grid(s=(zticks_float, yticks_float))
3650            gyz.alpha(yzalpha).c(yzplane_color).lw(0).rotate_y(-90)
3651            if yzshift: gyz.shift([yzshift*dx,0,0])
3652            elif tol:   gyz.shift([-tol*gscale,0,0])
3653            gyz.name = "yzGrid"
3654            grids.append(gyz)
3655        if grid_linewidth:
3656            gyz_lines = shapes.Grid(s=(zticks_float, yticks_float))
3657            gyz_lines.c(yzplane_color).lw(grid_linewidth).alpha(yzalpha).rotate_y(-90)
3658            if yzshift: gyz_lines.shift([yzshift*dx,0,0])
3659            elif tol:   gyz_lines.shift([-tol*gscale,0,0])
3660            gyz_lines.name = "yzGridLines"
3661            grids.append(gyz_lines)
3662
3663    if zxgrid and ztitle and xtitle:
3664        if not zxgrid_transparent:
3665            gzx = shapes.Grid(s=(xticks_float, zticks_float))
3666            gzx.alpha(zxalpha).c(zxplane_color).lw(0).rotate_x(90)
3667            if zxshift: gzx.shift([0,zxshift*dy,0])
3668            elif tol:   gzx.shift([0,-tol*gscale,0])
3669            gzx.name = "zxGrid"
3670            grids.append(gzx)
3671        if grid_linewidth:
3672            gzx_lines = shapes.Grid(s=(xticks_float, zticks_float))
3673            gzx_lines.c(zxplane_color).lw(grid_linewidth).alpha(zxalpha).rotate_x(90)
3674            if zxshift: gzx_lines.shift([0,zxshift*dy,0])
3675            elif tol:   gzx_lines.shift([0,-tol*gscale,0])
3676            gzx_lines.name = "zxGridLines"
3677            grids.append(gzx_lines)
3678
3679    # Grid2
3680    if xygrid2 and xtitle and ytitle:
3681        if not xygrid2_transparent:
3682            gxy2 = shapes.Grid(s=(xticks_float, yticks_float)).z(dz)
3683            gxy2.alpha(xyalpha).c(xyplane_color).lw(0)
3684            gxy2.shift([0, tol * gscale, 0])
3685            gxy2.name = "xyGrid2"
3686            grids.append(gxy2)
3687        if grid_linewidth:
3688            gxy2_lines = shapes.Grid(s=(xticks_float, yticks_float)).z(dz)
3689            gxy2_lines.c(xyplane_color).lw(grid_linewidth).alpha(xyalpha)
3690            gxy2_lines.shift([0, tol * gscale, 0])
3691            gxy2_lines.name = "xygrid2Lines"
3692            grids.append(gxy2_lines)
3693
3694    if yzgrid2 and ytitle and ztitle:
3695        if not yzgrid2_transparent:
3696            gyz2 = shapes.Grid(s=(zticks_float, yticks_float))
3697            gyz2.alpha(yzalpha).c(yzplane_color).lw(0)
3698            gyz2.rotate_y(-90).x(dx).shift([tol * gscale, 0, 0])
3699            gyz2.name = "yzGrid2"
3700            grids.append(gyz2)
3701        if grid_linewidth:
3702            gyz2_lines = shapes.Grid(s=(zticks_float, yticks_float))
3703            gyz2_lines.c(yzplane_color).lw(grid_linewidth).alpha(yzalpha)
3704            gyz2_lines.rotate_y(-90).x(dx).shift([tol * gscale, 0, 0])
3705            gyz2_lines.name = "yzGrid2Lines"
3706            grids.append(gyz2_lines)
3707
3708    if zxgrid2 and ztitle and xtitle:
3709        if not zxgrid2_transparent:
3710            gzx2 = shapes.Grid(s=(xticks_float, zticks_float))
3711            gzx2.alpha(zxalpha).c(zxplane_color).lw(0)
3712            gzx2.rotate_x(90).y(dy).shift([0, tol * gscale, 0])
3713            gzx2.name = "zxGrid2"
3714            grids.append(gzx2)
3715        if grid_linewidth:
3716            gzx2_lines = shapes.Grid(s=(xticks_float, zticks_float))
3717            gzx2_lines.c(zxplane_color).lw(grid_linewidth).alpha(zxalpha)
3718            gzx2_lines.rotate_x(90).y(dy).shift([0, tol * gscale, 0])
3719            gzx2_lines.name = "zxGrid2Lines"
3720            grids.append(gzx2_lines)
3721
3722    ################################################ frame lines
3723    framelines = []
3724    if xyframe_line and xtitle and ytitle:
3725        if not xyframe_color:
3726            xyframe_color = xygrid_color
3727        frxy = shapes.Line(
3728            [[0, dy, 0], [dx, dy, 0], [dx, 0, 0], [0, 0, 0], [0, dy, 0]],
3729            c=xyframe_color,
3730            lw=xyframe_line,
3731        )
3732        frxy.shift([0, 0, xyshift * dz])
3733        frxy.name = "xyFrameLine"
3734        framelines.append(frxy)
3735    if yzframe_line and ytitle and ztitle:
3736        if not yzframe_color:
3737            yzframe_color = yzgrid_color
3738        fryz = shapes.Line(
3739            [[0, 0, dz], [0, dy, dz], [0, dy, 0], [0, 0, 0], [0, 0, dz]],
3740            c=yzframe_color,
3741            lw=yzframe_line,
3742        )
3743        fryz.shift([yzshift * dx, 0, 0])
3744        fryz.name = "yzFrameLine"
3745        framelines.append(fryz)
3746    if zxframe_line and ztitle and xtitle:
3747        if not zxframe_color:
3748            zxframe_color = zxgrid_color
3749        frzx = shapes.Line(
3750            [[0, 0, dz], [dx, 0, dz], [dx, 0, 0], [0, 0, 0], [0, 0, dz]],
3751            c=zxframe_color,
3752            lw=zxframe_line,
3753        )
3754        frzx.shift([0, zxshift * dy, 0])
3755        frzx.name = "zxFrameLine"
3756        framelines.append(frzx)
3757
3758    ################################################ zero lines highlights
3759    highlights = []
3760    if xygrid and xtitle and ytitle:
3761        if xhighlight_zero and min_bns[0] <= 0 and max_bns[1] > 0:
3762            xhl = -min_bns[0]
3763            hxy = shapes.Line([xhl, 0, 0], [xhl, dy, 0], c=xhighlight_zero_color)
3764            hxy.alpha(np.sqrt(xyalpha)).lw(grid_linewidth * 2)
3765            hxy.shift([0, 0, xyshift * dz])
3766            hxy.name = "xyHighlightZero"
3767            highlights.append(hxy)
3768        if yhighlight_zero and min_bns[2] <= 0 and max_bns[3] > 0:
3769            yhl = -min_bns[2]
3770            hyx = shapes.Line([0, yhl, 0], [dx, yhl, 0], c=yhighlight_zero_color)
3771            hyx.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2)
3772            hyx.shift([0, 0, xyshift * dz])
3773            hyx.name = "yxHighlightZero"
3774            highlights.append(hyx)
3775
3776    if yzgrid and ytitle and ztitle:
3777        if yhighlight_zero and min_bns[2] <= 0 and max_bns[3] > 0:
3778            yhl = -min_bns[2]
3779            hyz = shapes.Line([0, yhl, 0], [0, yhl, dz], c=yhighlight_zero_color)
3780            hyz.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2)
3781            hyz.shift([yzshift * dx, 0, 0])
3782            hyz.name = "yzHighlightZero"
3783            highlights.append(hyz)
3784        if zhighlight_zero and min_bns[4] <= 0 and max_bns[5] > 0:
3785            zhl = -min_bns[4]
3786            hzy = shapes.Line([0, 0, zhl], [0, dy, zhl], c=zhighlight_zero_color)
3787            hzy.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2)
3788            hzy.shift([yzshift * dx, 0, 0])
3789            hzy.name = "zyHighlightZero"
3790            highlights.append(hzy)
3791
3792    if zxgrid and ztitle and xtitle:
3793        if zhighlight_zero and min_bns[4] <= 0 and max_bns[5] > 0:
3794            zhl = -min_bns[4]
3795            hzx = shapes.Line([0, 0, zhl], [dx, 0, zhl], c=zhighlight_zero_color)
3796            hzx.alpha(np.sqrt(zxalpha)).lw(grid_linewidth * 2)
3797            hzx.shift([0, zxshift * dy, 0])
3798            hzx.name = "zxHighlightZero"
3799            highlights.append(hzx)
3800        if xhighlight_zero and min_bns[0] <= 0 and max_bns[1] > 0:
3801            xhl = -min_bns[0]
3802            hxz = shapes.Line([xhl, 0, 0], [xhl, 0, dz], c=xhighlight_zero_color)
3803            hxz.alpha(np.sqrt(zxalpha)).lw(grid_linewidth * 2)
3804            hxz.shift([0, zxshift * dy, 0])
3805            hxz.name = "xzHighlightZero"
3806            highlights.append(hxz)
3807
3808    ################################################ arrow cone
3809    cones = []
3810
3811    if tip_size:
3812
3813        if xtitle:
3814            if x_inverted:
3815                cx = shapes.Cone(
3816                    r=tip_size,
3817                    height=tip_size * 2,
3818                    axis=(-1, 0, 0),
3819                    c=xline_color,
3820                    res=12,
3821                )
3822            else:
3823                cx = shapes.Cone(
3824                    (dx, 0, 0),
3825                    r=tip_size,
3826                    height=tip_size * 2,
3827                    axis=(1, 0, 0),
3828                    c=xline_color,
3829                    res=12,
3830                )
3831            T = LinearTransform()
3832            T.translate(
3833                [
3834                    0,
3835                    zxshift * dy + xshift_along_y * dy,
3836                    xyshift * dz + xshift_along_z * dz,
3837                ]
3838            )
3839            cx.apply_transform(T)
3840            cx.name = "xTipCone"
3841            cones.append(cx)
3842
3843        if ytitle:
3844            if y_inverted:
3845                cy = shapes.Cone(
3846                    r=tip_size,
3847                    height=tip_size * 2,
3848                    axis=(0, -1, 0),
3849                    c=yline_color,
3850                    res=12,
3851                )
3852            else:
3853                cy = shapes.Cone(
3854                    (0, dy, 0),
3855                    r=tip_size,
3856                    height=tip_size * 2,
3857                    axis=(0, 1, 0),
3858                    c=yline_color,
3859                    res=12,
3860                )
3861            T = LinearTransform()
3862            T.translate(
3863                [
3864                    yzshift * dx + yshift_along_x * dx,
3865                    0,
3866                    xyshift * dz + yshift_along_z * dz,
3867                ]
3868            )
3869            cy.apply_transform(T)
3870            cy.name = "yTipCone"
3871            cones.append(cy)
3872
3873        if ztitle:
3874            if z_inverted:
3875                cz = shapes.Cone(
3876                    r=tip_size,
3877                    height=tip_size * 2,
3878                    axis=(0, 0, -1),
3879                    c=zline_color,
3880                    res=12,
3881                )
3882            else:
3883                cz = shapes.Cone(
3884                    (0, 0, dz),
3885                    r=tip_size,
3886                    height=tip_size * 2,
3887                    axis=(0, 0, 1),
3888                    c=zline_color,
3889                    res=12,
3890                )
3891            T = LinearTransform()
3892            T.translate(
3893                [
3894                    yzshift * dx + zshift_along_x * dx,
3895                    zxshift * dy + zshift_along_y * dy,
3896                    0,
3897                ]
3898            )
3899            cz.apply_transform(T)
3900            cz.name = "zTipCone"
3901            cones.append(cz)
3902
3903    ################################################################# MAJOR ticks
3904    majorticks, minorticks = [], []
3905    xticks, yticks, zticks = [], [], []
3906    if show_ticks:
3907        if xtitle:
3908            tick_thickness = xtick_thickness * gscale / 2
3909            tick_length = xtick_length * gscale / 2
3910            for i in range(1, len(xticks_float) - 1):
3911                v1 = (xticks_float[i] - tick_thickness, -tick_length, 0)
3912                v2 = (xticks_float[i] + tick_thickness, tick_length, 0)
3913                xticks.append(shapes.Rectangle(v1, v2))
3914            if len(xticks) > 1:
3915                xmajticks = merge(xticks).c(xlabel_color)
3916                T = LinearTransform()
3917                T.rotate_x(xaxis_rotation)
3918                T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3919                xmajticks.apply_transform(T)
3920                xmajticks.name = "xMajorTicks"
3921                majorticks.append(xmajticks)
3922        if ytitle:
3923            tick_thickness = ytick_thickness * gscale / 2
3924            tick_length = ytick_length * gscale / 2
3925            for i in range(1, len(yticks_float) - 1):
3926                v1 = (-tick_length, yticks_float[i] - tick_thickness, 0)
3927                v2 = (tick_length, yticks_float[i] + tick_thickness, 0)
3928                yticks.append(shapes.Rectangle(v1, v2))
3929            if len(yticks) > 1:
3930                ymajticks = merge(yticks).c(ylabel_color)
3931                T = LinearTransform()
3932                T.rotate_y(yaxis_rotation)
3933                T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3934                ymajticks.apply_transform(T)
3935                ymajticks.name = "yMajorTicks"
3936                majorticks.append(ymajticks)
3937        if ztitle:
3938            tick_thickness = ztick_thickness * gscale / 2
3939            tick_length = ztick_length * gscale / 2.85
3940            for i in range(1, len(zticks_float) - 1):
3941                v1 = (zticks_float[i] - tick_thickness, -tick_length, 0)
3942                v2 = (zticks_float[i] + tick_thickness, tick_length, 0)
3943                zticks.append(shapes.Rectangle(v1, v2))
3944            if len(zticks) > 1:
3945                zmajticks = merge(zticks).c(zlabel_color)
3946                T = LinearTransform()
3947                T.rotate_y(-90).rotate_z(-45 + zaxis_rotation)
3948                T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3949                zmajticks.apply_transform(T)
3950                zmajticks.name = "zMajorTicks"
3951                majorticks.append(zmajticks)
3952
3953        ############################################################# MINOR ticks
3954        if xtitle and xminor_ticks and len(xticks) > 1:
3955            tick_thickness = xtick_thickness * gscale / 4
3956            tick_length = xtick_length * gscale / 4
3957            xminor_ticks += 1
3958            ticks = []
3959            for i in range(1, len(xticks)):
3960                t0, t1 = xticks[i - 1].pos(), xticks[i].pos()
3961                dt = t1 - t0
3962                for j in range(1, xminor_ticks):
3963                    mt = dt * (j / xminor_ticks) + t0
3964                    v1 = (mt[0] - tick_thickness, -tick_length, 0)
3965                    v2 = (mt[0] + tick_thickness, tick_length, 0)
3966                    ticks.append(shapes.Rectangle(v1, v2))
3967
3968            # finish off the fist lower range from start to first tick
3969            t0, t1 = xticks[0].pos(), xticks[1].pos()
3970            dt = t1 - t0
3971            for j in range(1, xminor_ticks):
3972                mt = t0 - dt * (j / xminor_ticks)
3973                if mt[0] < 0:
3974                    break
3975                v1 = (mt[0] - tick_thickness, -tick_length, 0)
3976                v2 = (mt[0] + tick_thickness, tick_length, 0)
3977                ticks.append(shapes.Rectangle(v1, v2))
3978
3979            # finish off the last upper range from last tick to end
3980            t0, t1 = xticks[-2].pos(), xticks[-1].pos()
3981            dt = t1 - t0
3982            for j in range(1, xminor_ticks):
3983                mt = t1 + dt * (j / xminor_ticks)
3984                if mt[0] > dx:
3985                    break
3986                v1 = (mt[0] - tick_thickness, -tick_length, 0)
3987                v2 = (mt[0] + tick_thickness, tick_length, 0)
3988                ticks.append(shapes.Rectangle(v1, v2))
3989
3990            if ticks:
3991                xminticks = merge(ticks).c(xlabel_color)
3992                T = LinearTransform()
3993                T.rotate_x(xaxis_rotation)
3994                T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3995                xminticks.apply_transform(T)
3996                xminticks.name = "xMinorTicks"
3997                minorticks.append(xminticks)
3998
3999        if ytitle and yminor_ticks and len(yticks) > 1:  ##### y
4000            tick_thickness = ytick_thickness * gscale / 4
4001            tick_length = ytick_length * gscale / 4
4002            yminor_ticks += 1
4003            ticks = []
4004            for i in range(1, len(yticks)):
4005                t0, t1 = yticks[i - 1].pos(), yticks[i].pos()
4006                dt = t1 - t0
4007                for j in range(1, yminor_ticks):
4008                    mt = dt * (j / yminor_ticks) + t0
4009                    v1 = (-tick_length, mt[1] - tick_thickness, 0)
4010                    v2 = (tick_length, mt[1] + tick_thickness, 0)
4011                    ticks.append(shapes.Rectangle(v1, v2))
4012
4013            # finish off the fist lower range from start to first tick
4014            t0, t1 = yticks[0].pos(), yticks[1].pos()
4015            dt = t1 - t0
4016            for j in range(1, yminor_ticks):
4017                mt = t0 - dt * (j / yminor_ticks)
4018                if mt[1] < 0:
4019                    break
4020                v1 = (-tick_length, mt[1] - tick_thickness, 0)
4021                v2 = (tick_length, mt[1] + tick_thickness, 0)
4022                ticks.append(shapes.Rectangle(v1, v2))
4023
4024            # finish off the last upper range from last tick to end
4025            t0, t1 = yticks[-2].pos(), yticks[-1].pos()
4026            dt = t1 - t0
4027            for j in range(1, yminor_ticks):
4028                mt = t1 + dt * (j / yminor_ticks)
4029                if mt[1] > dy:
4030                    break
4031                v1 = (-tick_length, mt[1] - tick_thickness, 0)
4032                v2 = (tick_length, mt[1] + tick_thickness, 0)
4033                ticks.append(shapes.Rectangle(v1, v2))
4034
4035            if ticks:
4036                yminticks = merge(ticks).c(ylabel_color)
4037                T = LinearTransform()
4038                T.rotate_y(yaxis_rotation)
4039                T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
4040                yminticks.apply_transform(T)
4041                yminticks.name = "yMinorTicks"
4042                minorticks.append(yminticks)
4043
4044        if ztitle and zminor_ticks and len(zticks) > 1:  ##### z
4045            tick_thickness = ztick_thickness * gscale / 4
4046            tick_length = ztick_length * gscale / 5
4047            zminor_ticks += 1
4048            ticks = []
4049            for i in range(1, len(zticks)):
4050                t0, t1 = zticks[i - 1].pos(), zticks[i].pos()
4051                dt = t1 - t0
4052                for j in range(1, zminor_ticks):
4053                    mt = dt * (j / zminor_ticks) + t0
4054                    v1 = (mt[0] - tick_thickness, -tick_length, 0)
4055                    v2 = (mt[0] + tick_thickness, tick_length, 0)
4056                    ticks.append(shapes.Rectangle(v1, v2))
4057
4058            # finish off the fist lower range from start to first tick
4059            t0, t1 = zticks[0].pos(), zticks[1].pos()
4060            dt = t1 - t0
4061            for j in range(1, zminor_ticks):
4062                mt = t0 - dt * (j / zminor_ticks)
4063                if mt[0] < 0:
4064                    break
4065                v1 = (mt[0] - tick_thickness, -tick_length, 0)
4066                v2 = (mt[0] + tick_thickness, tick_length, 0)
4067                ticks.append(shapes.Rectangle(v1, v2))
4068
4069            # finish off the last upper range from last tick to end
4070            t0, t1 = zticks[-2].pos(), zticks[-1].pos()
4071            dt = t1 - t0
4072            for j in range(1, zminor_ticks):
4073                mt = t1 + dt * (j / zminor_ticks)
4074                if mt[0] > dz:
4075                    break
4076                v1 = (mt[0] - tick_thickness, -tick_length, 0)
4077                v2 = (mt[0] + tick_thickness, tick_length, 0)
4078                ticks.append(shapes.Rectangle(v1, v2))
4079
4080            if ticks:
4081                zminticks = merge(ticks).c(zlabel_color)
4082                T = LinearTransform()
4083                T.rotate_y(-90).rotate_z(-45 + zaxis_rotation)
4084                T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
4085                zminticks.apply_transform(T)
4086                zminticks.name = "zMinorTicks"
4087                minorticks.append(zminticks)
4088
4089    ################################################ axes NUMERIC text labels
4090    labels = []
4091    xlab, ylab, zlab = None, None, None
4092
4093    if xlabel_size and xtitle:
4094
4095        xRot, yRot, zRot = 0, 0, 0
4096        if utils.is_sequence(xlabel_rotation):  # unpck 3 rotations
4097            zRot, xRot, yRot = xlabel_rotation
4098        else:
4099            zRot = xlabel_rotation
4100        if zRot < 0:  # deal with negative angles
4101            zRot += 360
4102
4103        jus = "center-top"
4104        if zRot:
4105            if zRot >  24: jus = "top-right"
4106            if zRot >  67: jus = "center-right"
4107            if zRot > 112: jus = "right-bottom"
4108            if zRot > 157: jus = "center-bottom"
4109            if zRot > 202: jus = "bottom-left"
4110            if zRot > 247: jus = "center-left"
4111            if zRot > 292: jus = "top-left"
4112            if zRot > 337: jus = "top-center"
4113        if xlabel_justify is not None:
4114            jus = xlabel_justify
4115
4116        for i in range(1, len(xticks_str)):
4117            t = xticks_str[i]
4118            if not t:
4119                continue
4120            if utils.is_sequence(xlabel_offset):
4121                xoffs, yoffs, zoffs = xlabel_offset
4122            else:
4123                xoffs, yoffs, zoffs = 0, xlabel_offset, 0
4124
4125            xlab = shapes.Text3D(
4126                t, s=xlabel_size * text_scale * gscale, font=label_font, justify=jus
4127            )
4128            tb = xlab.ybounds()  # must be ybounds: height of char
4129
4130            v = (xticks_float[i], 0, 0)
4131            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0])
4132
4133            T = LinearTransform()
4134            T.rotate_x(xaxis_rotation).rotate_y(yRot).rotate_x(xRot).rotate_z(zRot)
4135            T.translate(v + offs)
4136            T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
4137            xlab.apply_transform(T)
4138
4139            xlab.use_bounds(x_use_bounds)
4140
4141            xlab.c(xlabel_color)
4142            if xlabel_backface_color is None:
4143                bfc = 1 - np.array(get_color(xlabel_color))
4144                xlab.backcolor(bfc)
4145            xlab.name = f"xNumericLabel {i}"
4146            labels.append(xlab)
4147
4148    if ylabel_size and ytitle:
4149
4150        xRot, yRot, zRot = 0, 0, 0
4151        if utils.is_sequence(ylabel_rotation):  # unpck 3 rotations
4152            zRot, yRot, xRot = ylabel_rotation
4153        else:
4154            zRot = ylabel_rotation
4155        if zRot < 0:
4156            zRot += 360  # deal with negative angles
4157
4158        jus = "center-right"
4159        if zRot:
4160            if zRot >  24: jus = "bottom-right"
4161            if zRot >  67: jus = "center-bottom"
4162            if zRot > 112: jus = "left-bottom"
4163            if zRot > 157: jus = "center-left"
4164            if zRot > 202: jus = "top-left"
4165            if zRot > 247: jus = "center-top"
4166            if zRot > 292: jus = "top-right"
4167            if zRot > 337: jus = "right-center"
4168        if ylabel_justify is not None:
4169            jus = ylabel_justify
4170
4171        for i in range(1, len(yticks_str)):
4172            t = yticks_str[i]
4173            if not t:
4174                continue
4175            if utils.is_sequence(ylabel_offset):
4176                xoffs, yoffs, zoffs = ylabel_offset
4177            else:
4178                xoffs, yoffs, zoffs = ylabel_offset, 0, 0
4179            ylab = shapes.Text3D(
4180                t, s=ylabel_size * text_scale * gscale, font=label_font, justify=jus
4181            )
4182            tb = ylab.ybounds()  # must be ybounds: height of char
4183            v = (0, yticks_float[i], 0)
4184            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0])
4185
4186            T = LinearTransform()
4187            T.rotate_y(yaxis_rotation).rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
4188            T.translate(v + offs)
4189            T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
4190            ylab.apply_transform(T)
4191
4192            ylab.use_bounds(y_use_bounds)
4193
4194            ylab.c(ylabel_color)
4195            if ylabel_backface_color is None:
4196                bfc = 1 - np.array(get_color(ylabel_color))
4197                ylab.backcolor(bfc)
4198            ylab.name = f"yNumericLabel {i}"
4199            labels.append(ylab)
4200
4201    if zlabel_size and ztitle:
4202
4203        xRot, yRot, zRot = 0, 0, 0
4204        if utils.is_sequence(zlabel_rotation):  # unpck 3 rotations
4205            xRot, yRot, zRot = zlabel_rotation
4206        else:
4207            xRot = zlabel_rotation
4208        if xRot < 0: xRot += 360 # deal with negative angles
4209
4210        jus = "center-right"
4211        if xRot:
4212            if xRot >  24: jus = "bottom-right"
4213            if xRot >  67: jus = "center-bottom"
4214            if xRot > 112: jus = "left-bottom"
4215            if xRot > 157: jus = "center-left"
4216            if xRot > 202: jus = "top-left"
4217            if xRot > 247: jus = "center-top"
4218            if xRot > 292: jus = "top-right"
4219            if xRot > 337: jus = "right-center"
4220        if zlabel_justify is not None:
4221            jus = zlabel_justify
4222
4223        for i in range(1, len(zticks_str)):
4224            t = zticks_str[i]
4225            if not t:
4226                continue
4227            if utils.is_sequence(zlabel_offset):
4228                xoffs, yoffs, zoffs = zlabel_offset
4229            else:
4230                xoffs, yoffs, zoffs = zlabel_offset, zlabel_offset, 0
4231            zlab = shapes.Text3D(t, s=zlabel_size*text_scale*gscale, font=label_font, justify=jus)
4232            tb = zlab.ybounds()  # must be ybounds: height of char
4233
4234            v = (0, 0, zticks_float[i])
4235            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0]) / 1.5
4236            angle = np.arctan2(dy, dx) * 57.3
4237
4238            T = LinearTransform()
4239            T.rotate_x(90 + zRot).rotate_y(-xRot).rotate_z(angle + yRot + zaxis_rotation)
4240            T.translate(v + offs)
4241            T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
4242            zlab.apply_transform(T)
4243
4244            zlab.use_bounds(z_use_bounds)
4245
4246            zlab.c(zlabel_color)
4247            if zlabel_backface_color is None:
4248                bfc = 1 - np.array(get_color(zlabel_color))
4249                zlab.backcolor(bfc)
4250            zlab.name = f"zNumericLabel {i}"
4251            labels.append(zlab)
4252
4253    ################################################ axes titles
4254    titles = []
4255
4256    if xtitle:
4257        xRot, yRot, zRot = 0, 0, 0
4258        if utils.is_sequence(xtitle_rotation):  # unpack 3 rotations
4259            zRot, xRot, yRot = xtitle_rotation
4260        else:
4261            zRot = xtitle_rotation
4262        if zRot < 0:  # deal with negative angles
4263            zRot += 360
4264
4265        if utils.is_sequence(xtitle_offset):
4266            xoffs, yoffs, zoffs = xtitle_offset
4267        else:
4268            xoffs, yoffs, zoffs = 0, xtitle_offset, 0
4269
4270        if xtitle_justify is not None:
4271            jus = xtitle_justify
4272        else:
4273            # find best justfication for given rotation(s)
4274            jus = "right-top"
4275            if zRot:
4276                if zRot >  24: jus = "center-right"
4277                if zRot >  67: jus = "right-bottom"
4278                if zRot > 157: jus = "bottom-left"
4279                if zRot > 202: jus = "center-left"
4280                if zRot > 247: jus = "top-left"
4281                if zRot > 337: jus = "top-right"
4282
4283        xt = shapes.Text3D(
4284            xtitle,
4285            s=xtitle_size * text_scale * gscale,
4286            font=title_font,
4287            c=xtitle_color,
4288            justify=jus,
4289            depth=title_depth,
4290            italic=xtitle_italic,
4291        )
4292        if xtitle_backface_color is None:
4293            xtitle_backface_color = 1 - np.array(get_color(xtitle_color))
4294            xt.backcolor(xtitle_backface_color)
4295
4296        shift = 0
4297        if xlab:  # xlab is the last created numeric text label..
4298            lt0, lt1 = xlab.bounds()[2:4]
4299            shift = lt1 - lt0
4300
4301        T = LinearTransform()
4302        T.rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
4303        T.set_position(
4304            [(xoffs + xtitle_position) * dx,
4305            -(yoffs + xtick_length / 2) * dy - shift,
4306            zoffs * dz]
4307        )
4308        T.rotate_x(xaxis_rotation)
4309        T.translate([0, xshift_along_y * dy, xyshift * dz + xshift_along_z * dz])
4310        xt.apply_transform(T)
4311
4312        xt.use_bounds(x_use_bounds)
4313        if xtitle == " ":
4314            xt.use_bounds(False)
4315        xt.name = "xtitle"
4316        titles.append(xt)
4317        if xtitle_box:
4318            titles.append(xt.box(scale=1.1).use_bounds(x_use_bounds))
4319
4320    if ytitle:
4321        xRot, yRot, zRot = 0, 0, 0
4322        if utils.is_sequence(ytitle_rotation):  # unpck 3 rotations
4323            zRot, yRot, xRot = ytitle_rotation
4324        else:
4325            zRot = ytitle_rotation
4326            if len(ytitle) > 3:
4327                zRot += 90
4328                ytitle_position *= 0.975
4329        if zRot < 0:
4330            zRot += 360  # deal with negative angles
4331
4332        if utils.is_sequence(ytitle_offset):
4333            xoffs, yoffs, zoffs = ytitle_offset
4334        else:
4335            xoffs, yoffs, zoffs = ytitle_offset, 0, 0
4336
4337        if ytitle_justify is not None:
4338            jus = ytitle_justify
4339        else:
4340            jus = "center-right"
4341            if zRot:
4342                if zRot >  24: jus = "bottom-right"
4343                if zRot > 112: jus = "left-bottom"
4344                if zRot > 157: jus = "center-left"
4345                if zRot > 202: jus = "top-left"
4346                if zRot > 292: jus = "top-right"
4347                if zRot > 337: jus = "right-center"
4348
4349        yt = shapes.Text3D(
4350            ytitle,
4351            s=ytitle_size * text_scale * gscale,
4352            font=title_font,
4353            c=ytitle_color,
4354            justify=jus,
4355            depth=title_depth,
4356            italic=ytitle_italic,
4357        )
4358        if ytitle_backface_color is None:
4359            ytitle_backface_color = 1 - np.array(get_color(ytitle_color))
4360            yt.backcolor(ytitle_backface_color)
4361
4362        shift = 0
4363        if ylab:  # this is the last created num label..
4364            lt0, lt1 = ylab.bounds()[0:2]
4365            shift = lt1 - lt0
4366
4367        T = LinearTransform()
4368        T.rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
4369        T.set_position(
4370            [-(xoffs + ytick_length / 2) * dx - shift,
4371            (yoffs + ytitle_position) * dy,
4372            zoffs * dz]
4373        )
4374        T.rotate_y(yaxis_rotation)
4375        T.translate([yshift_along_x * dx, 0, xyshift * dz + yshift_along_z * dz])
4376        yt.apply_transform(T)
4377
4378        yt.use_bounds(y_use_bounds)
4379        if ytitle == " ":
4380            yt.use_bounds(False)
4381        yt.name = "ytitle"
4382        titles.append(yt)
4383        if ytitle_box:
4384            titles.append(yt.box(scale=1.1).use_bounds(y_use_bounds))
4385
4386    if ztitle:
4387        xRot, yRot, zRot = 0, 0, 0
4388        if utils.is_sequence(ztitle_rotation):  # unpck 3 rotations
4389            xRot, yRot, zRot = ztitle_rotation
4390        else:
4391            xRot = ztitle_rotation
4392            if len(ztitle) > 3:
4393                xRot += 90
4394                ztitle_position *= 0.975
4395        if xRot < 0:
4396            xRot += 360  # deal with negative angles
4397
4398        if ztitle_justify is not None:
4399            jus = ztitle_justify
4400        else:
4401            jus = "center-right"
4402            if xRot:
4403                if xRot >  24: jus = "bottom-right"
4404                if xRot > 112: jus = "left-bottom"
4405                if xRot > 157: jus = "center-left"
4406                if xRot > 202: jus = "top-left"
4407                if xRot > 292: jus = "top-right"
4408                if xRot > 337: jus = "right-center"
4409
4410        zt = shapes.Text3D(
4411            ztitle,
4412            s=ztitle_size * text_scale * gscale,
4413            font=title_font,
4414            c=ztitle_color,
4415            justify=jus,
4416            depth=title_depth,
4417            italic=ztitle_italic,
4418        )
4419        if ztitle_backface_color is None:
4420            ztitle_backface_color = 1 - np.array(get_color(ztitle_color))
4421            zt.backcolor(ztitle_backface_color)
4422
4423        angle = np.arctan2(dy, dx) * 57.3
4424        shift = 0
4425        if zlab:  # this is the last created one..
4426            lt0, lt1 = zlab.bounds()[0:2]
4427            shift = lt1 - lt0
4428
4429        T = LinearTransform()
4430        T.rotate_x(90 + zRot).rotate_y(-xRot).rotate_z(angle + yRot)
4431        T.set_position([
4432            -(ztitle_offset + ztick_length / 5) * dx - shift,
4433            -(ztitle_offset + ztick_length / 5) * dy - shift,
4434            ztitle_position * dz]
4435        )
4436        T.rotate_z(zaxis_rotation)
4437        T.translate([zshift_along_x * dx, zxshift * dy + zshift_along_y * dy, 0])
4438        zt.apply_transform(T)
4439
4440        zt.use_bounds(z_use_bounds)
4441        if ztitle == " ":
4442            zt.use_bounds(False)
4443        zt.name = "ztitle"
4444        titles.append(zt)
4445
4446    ################################################### header title
4447    if htitle:
4448        if htitle_font is None:
4449            htitle_font = title_font
4450        if htitle_color is None:
4451            htitle_color = xtitle_color
4452        htit = shapes.Text3D(
4453            htitle,
4454            s=htitle_size * gscale * text_scale,
4455            font=htitle_font,
4456            c=htitle_color,
4457            justify=htitle_justify,
4458            depth=title_depth,
4459            italic=htitle_italic,
4460        )
4461        if htitle_backface_color is None:
4462            htitle_backface_color = 1 - np.array(get_color(htitle_color))
4463            htit.backcolor(htitle_backface_color)
4464        htit.rotate_x(htitle_rotation)
4465        wpos = [htitle_offset[0]*dx, (1 + htitle_offset[1])*dy, htitle_offset[2]*dz]
4466        htit.shift(np.array(wpos) + [0, 0, xyshift*dz])
4467        htit.name = "htitle"
4468        titles.append(htit)
4469
4470    ######
4471    acts = titles + lines + labels + grids + framelines
4472    acts += highlights + majorticks + minorticks + cones
4473    orig = (min_bns[0], min_bns[2], min_bns[4])
4474    for a in acts:
4475        a.shift(orig)
4476        a.actor.PickableOff()
4477        a.properties.LightingOff()
4478    asse = Assembly(acts)
4479    asse.PickableOff()
4480    asse.name = "Axes"
4481    return asse

Draw axes for the input object. Check available fonts here.

Returns an vedo.Assembly object.

Parameters

  • xtitle, ['x'], x-axis title text
  • xrange, [None], x-axis range in format (xmin, ymin), default is automatic.
  • number_of_divisions, [None], approximate number of divisions on the longest axis
  • axes_linewidth, [1], width of the axes lines
  • grid_linewidth, [1], width of the grid lines
  • title_depth, [0], extrusion fractional depth of title text
  • x_values_and_labels [], assign custom tick positions and labels [(pos1, label1), ...]
  • xygrid, [True], show a gridded wall on plane xy
  • yzgrid, [True], show a gridded wall on plane yz
  • zxgrid, [True], show a gridded wall on plane zx
  • yzgrid2, [False], show yz plane on opposite side of the bounding box
  • zxgrid2, [False], show zx plane on opposite side of the bounding box
  • xygrid_transparent [False], make grid plane completely transparent
  • xygrid2_transparent [False], make grid plane completely transparent on opposite side box
  • xyplane_color, ['None'], color of the plane
  • xygrid_color, ['None'], grid line color
  • xyalpha, [0.15], grid plane opacity
  • xyframe_line, [0], add a frame for the plane, use value as the thickness
  • xyframe_color, [None], color for the frame of the plane
  • show_ticks, [True], show major ticks
  • digits, [None], use this number of significant digits in scientific notation
  • title_font, [''], font for axes titles
  • label_font, [''], font for numeric labels
  • text_scale, [1.0], global scaling factor for all text elements (titles, labels)
  • htitle, [''], header title
  • htitle_size, [0.03], header title size
  • htitle_font, [None], header font (defaults to title_font)
  • htitle_italic, [True], header font is italic
  • htitle_color, [None], header title color (defaults to xtitle_color)
  • htitle_backface_color, [None], header title color on its backface
  • htitle_justify, ['bottom-center'], origin of the title justification
  • htitle_offset, [(0,0.01,0)], control offsets of header title in x, y and z
  • xtitle_position, [0.32], title fractional positions along axis
  • xtitle_offset, [0.05], title fractional offset distance from axis line, can be a list
  • xtitle_justify, [None], choose the origin of the bounding box of title
  • xtitle_rotation, [0], add a rotation of the axis title, can be a list (rx,ry,rz)
  • xtitle_box, [False], add a box around title text
  • xline_color, [automatic], color of the x-axis
  • xtitle_color, [automatic], color of the axis title
  • xtitle_backface_color, [None], color of axis title on its backface
  • xtitle_size, [0.025], size of the axis title
  • xtitle_italic, [0], a bool or float to make the font italic
  • xhighlight_zero, [True], draw a line highlighting zero position if in range
  • xhighlight_zero_color, [auto], color of the line highlighting the zero position
  • xtick_length, [0.005], radius of the major ticks
  • xtick_thickness, [0.0025], thickness of the major ticks along their axis
  • xminor_ticks, [1], number of minor ticks between two major ticks
  • xlabel_color, [automatic], color of numeric labels and ticks
  • xlabel_backface_color, [auto], back face color of numeric labels and ticks
  • xlabel_size, [0.015], size of the numeric labels along axis
  • xlabel_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 labels
  • xaxis_rotation, [0], rotate the X axis elements (ticks and labels) around this same axis
  • xyshift [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 diagonal
  • limit_ratio, [0.04], below this ratio don't plot smaller axis
  • x_use_bounds, [True], keep into account space occupied by labels when setting camera
  • x_inverted, [False], invert labels order and direction (only visually!)
  • use_global, [False], try to compute the global bounding box of visible actors
Example:
from vedo import Axes, Box, show
box = Box(pos=(1,2,3), length=8, width=9, height=7).alpha(0.1)
axs = Axes(box, c='k')  # returns an Assembly object
for a in axs.unpack():
    print(a.name)
show(box, axs).close()

Examples:

class RendererFrame(vtkmodules.vtkRenderingCore.vtkActor2D):
2548class RendererFrame(vtki.vtkActor2D):
2549    """
2550    Add a line around the renderer subwindow.
2551    """
2552
2553    def __init__(self, c="k", alpha=None, lw=None, padding=None):
2554        """
2555        Add a line around the renderer subwindow.
2556
2557        Arguments:
2558            c : (color)
2559                color of the line.
2560            alpha : (float)
2561                opacity.
2562            lw : (int)
2563                line width in pixels.
2564            padding : (int)
2565                padding in pixel units.
2566        """
2567
2568        if lw is None:
2569            lw = settings.renderer_frame_width
2570        if lw == 0:
2571            return None
2572
2573        if alpha is None:
2574            alpha = settings.renderer_frame_alpha
2575
2576        if padding is None:
2577            padding = settings.renderer_frame_padding
2578
2579        c = get_color(c)
2580
2581        ppoints = vtki.vtkPoints()  # Generate the polyline
2582        xy = 1 - padding
2583        psqr = [
2584            [padding, padding],
2585            [padding, xy],
2586            [xy, xy],
2587            [xy, padding],
2588            [padding, padding],
2589        ]
2590        for i, pt in enumerate(psqr):
2591            ppoints.InsertPoint(i, pt[0], pt[1], 0)
2592        lines = vtki.vtkCellArray()
2593        lines.InsertNextCell(len(psqr))
2594        for i in range(len(psqr)):
2595            lines.InsertCellPoint(i)
2596        pd = vtki.vtkPolyData()
2597        pd.SetPoints(ppoints)
2598        pd.SetLines(lines)
2599
2600        mapper = vtki.new("PolyDataMapper2D")
2601        mapper.SetInputData(pd)
2602        cs = vtki.new("Coordinate")
2603        cs.SetCoordinateSystemToNormalizedViewport()
2604        mapper.SetTransformCoordinate(cs)
2605
2606        super().__init__()
2607
2608        self.GetPositionCoordinate().SetValue(0, 0)
2609        self.GetPosition2Coordinate().SetValue(1, 1)
2610        self.SetMapper(mapper)
2611        self.GetProperty().SetColor(c)
2612        self.GetProperty().SetOpacity(alpha)
2613        self.GetProperty().SetLineWidth(lw)

Add a line around the renderer subwindow.

RendererFrame(c='k', alpha=None, lw=None, padding=None)
2553    def __init__(self, c="k", alpha=None, lw=None, padding=None):
2554        """
2555        Add a line around the renderer subwindow.
2556
2557        Arguments:
2558            c : (color)
2559                color of the line.
2560            alpha : (float)
2561                opacity.
2562            lw : (int)
2563                line width in pixels.
2564            padding : (int)
2565                padding in pixel units.
2566        """
2567
2568        if lw is None:
2569            lw = settings.renderer_frame_width
2570        if lw == 0:
2571            return None
2572
2573        if alpha is None:
2574            alpha = settings.renderer_frame_alpha
2575
2576        if padding is None:
2577            padding = settings.renderer_frame_padding
2578
2579        c = get_color(c)
2580
2581        ppoints = vtki.vtkPoints()  # Generate the polyline
2582        xy = 1 - padding
2583        psqr = [
2584            [padding, padding],
2585            [padding, xy],
2586            [xy, xy],
2587            [xy, padding],
2588            [padding, padding],
2589        ]
2590        for i, pt in enumerate(psqr):
2591            ppoints.InsertPoint(i, pt[0], pt[1], 0)
2592        lines = vtki.vtkCellArray()
2593        lines.InsertNextCell(len(psqr))
2594        for i in range(len(psqr)):
2595            lines.InsertCellPoint(i)
2596        pd = vtki.vtkPolyData()
2597        pd.SetPoints(ppoints)
2598        pd.SetLines(lines)
2599
2600        mapper = vtki.new("PolyDataMapper2D")
2601        mapper.SetInputData(pd)
2602        cs = vtki.new("Coordinate")
2603        cs.SetCoordinateSystemToNormalizedViewport()
2604        mapper.SetTransformCoordinate(cs)
2605
2606        super().__init__()
2607
2608        self.GetPositionCoordinate().SetValue(0, 0)
2609        self.GetPosition2Coordinate().SetValue(1, 1)
2610        self.SetMapper(mapper)
2611        self.GetProperty().SetColor(c)
2612        self.GetProperty().SetOpacity(alpha)
2613        self.GetProperty().SetLineWidth(lw)

Add a line around the renderer subwindow.

Arguments:
  • c : (color) color of the line.
  • alpha : (float) opacity.
  • lw : (int) line width in pixels.
  • padding : (int) padding in pixel units.
class Ruler2D(vtkmodules.vtkRenderingAnnotation.vtkAxisActor2D):
3050class Ruler2D(vtki.vtkAxisActor2D):
3051    """
3052    Create a ruler with tick marks, labels and a title.
3053    """
3054
3055    def __init__(
3056        self,
3057        lw=2,
3058        ticks=True,
3059        labels=False,
3060        c="k",
3061        alpha=1,
3062        title="",
3063        font="Calco",
3064        font_size=24,
3065        bc=None,
3066    ):
3067        """
3068        Create a ruler with tick marks, labels and a title.
3069
3070        Ruler2D is a 2D actor; that is, it is drawn on the overlay
3071        plane and is not occluded by 3D geometry.
3072        To use this class, specify two points defining the start and end
3073        with update_points() as 3D points.
3074
3075        This class decides decides how to create reasonable tick
3076        marks and labels.
3077
3078        Labels are drawn on the "right" side of the axis.
3079        The "right" side is the side of the axis on the right.
3080        The way the labels and title line up with the axis and tick marks
3081        depends on whether the line is considered horizontal or vertical.
3082
3083        Arguments:
3084            lw : (int)
3085                width of the line in pixel units
3086            ticks : (bool)
3087                control if drawing the tick marks
3088            labels : (bool)
3089                control if drawing the numeric labels
3090            c : (color)
3091                color of the object
3092            alpha : (float)
3093                opacity of the object
3094            title : (str)
3095                title of the ruler
3096            font : (str)
3097                font face name. Check [available fonts here](https://vedo.embl.es/fonts).
3098            font_size : (int)
3099                font size
3100            bc : (color)
3101                background color of the title
3102
3103        Example:
3104            ```python
3105            from vedo  import *
3106            plt = Plotter(axes=1, interactive=False)
3107            plt.show(Cube())
3108            rul = Ruler2D()
3109            rul.set_points([0,0,0], [0.5,0.5,0.5])
3110            plt.add(rul)
3111            plt.interactive().close()
3112            ```
3113            ![](https://vedo.embl.es/images/feats/dist_tool.png)
3114        """
3115        super().__init__()
3116
3117        plt = vedo.plotter_instance
3118        if not plt:
3119            vedo.logger.error("Ruler2D need to initialize Plotter first.")
3120            raise RuntimeError()
3121
3122        self.p0 = [0, 0, 0]
3123        self.p1 = [0, 0, 0]
3124        self.distance = 0
3125        self.title = title
3126
3127        prop = self.GetProperty()
3128        tprop = self.GetTitleTextProperty()
3129
3130        self.SetTitle(title)
3131        self.SetNumberOfLabels(9)
3132
3133        if not font:
3134            font = settings.default_font
3135        if font.lower() == "courier":
3136            tprop.SetFontFamilyToCourier()
3137        elif font.lower() == "times":
3138            tprop.SetFontFamilyToTimes()
3139        elif font.lower() == "arial":
3140            tprop.SetFontFamilyToArial()
3141        else:
3142            tprop.SetFontFamily(vtki.VTK_FONT_FILE)
3143            tprop.SetFontFile(utils.get_font_path(font))
3144        tprop.SetFontSize(font_size)
3145        tprop.BoldOff()
3146        tprop.ItalicOff()
3147        tprop.ShadowOff()
3148        tprop.SetColor(get_color(c))
3149        tprop.SetOpacity(alpha)
3150        if bc is not None:
3151            bc = get_color(bc)
3152            tprop.SetBackgroundColor(bc)
3153            tprop.SetBackgroundOpacity(alpha)
3154
3155        lprop = vtki.vtkTextProperty()
3156        lprop.ShallowCopy(tprop)
3157        self.SetLabelTextProperty(lprop)
3158
3159        self.SetLabelFormat("%0.3g")
3160        self.SetTickVisibility(ticks)
3161        self.SetLabelVisibility(labels)
3162        prop.SetLineWidth(lw)
3163        prop.SetColor(get_color(c))
3164
3165        self.renderer = plt.renderer
3166        self.cid = plt.interactor.AddObserver("RenderEvent", self._update_viz, 1.0)
3167
3168    def color(self, c) -> Self:
3169        """Assign a new color."""
3170        c = get_color(c)
3171        self.GetTitleTextProperty().SetColor(c)
3172        self.GetLabelTextProperty().SetColor(c)
3173        self.GetProperty().SetColor(c)
3174        return self
3175
3176    def off(self) -> None:
3177        """Switch off the ruler completely."""
3178        self.renderer.RemoveObserver(self.cid)
3179        self.renderer.RemoveActor(self)
3180
3181    def set_points(self, p0, p1) -> Self:
3182        """Set new values for the ruler start and end points."""
3183        self.p0 = np.asarray(p0)
3184        self.p1 = np.asarray(p1)
3185        self._update_viz(0, 0)
3186        return self
3187
3188    def _update_viz(self, evt, name) -> None:
3189        ren = self.renderer
3190        view_size = np.array(ren.GetSize())
3191
3192        ren.SetWorldPoint(*self.p0, 1)
3193        ren.WorldToDisplay()
3194        disp_point1 = ren.GetDisplayPoint()[:2]
3195        disp_point1 = np.array(disp_point1) / view_size
3196
3197        ren.SetWorldPoint(*self.p1, 1)
3198        ren.WorldToDisplay()
3199        disp_point2 = ren.GetDisplayPoint()[:2]
3200        disp_point2 = np.array(disp_point2) / view_size
3201
3202        self.SetPoint1(*disp_point1)
3203        self.SetPoint2(*disp_point2)
3204        self.distance = np.linalg.norm(self.p1 - self.p0)
3205        self.SetRange(0.0, float(self.distance))
3206        if not self.title:
3207            self.SetTitle(utils.precision(self.distance, 3))

Create a ruler with tick marks, labels and a title.

Ruler2D( lw=2, ticks=True, labels=False, c='k', alpha=1, title='', font='Calco', font_size=24, bc=None)
3055    def __init__(
3056        self,
3057        lw=2,
3058        ticks=True,
3059        labels=False,
3060        c="k",
3061        alpha=1,
3062        title="",
3063        font="Calco",
3064        font_size=24,
3065        bc=None,
3066    ):
3067        """
3068        Create a ruler with tick marks, labels and a title.
3069
3070        Ruler2D is a 2D actor; that is, it is drawn on the overlay
3071        plane and is not occluded by 3D geometry.
3072        To use this class, specify two points defining the start and end
3073        with update_points() as 3D points.
3074
3075        This class decides decides how to create reasonable tick
3076        marks and labels.
3077
3078        Labels are drawn on the "right" side of the axis.
3079        The "right" side is the side of the axis on the right.
3080        The way the labels and title line up with the axis and tick marks
3081        depends on whether the line is considered horizontal or vertical.
3082
3083        Arguments:
3084            lw : (int)
3085                width of the line in pixel units
3086            ticks : (bool)
3087                control if drawing the tick marks
3088            labels : (bool)
3089                control if drawing the numeric labels
3090            c : (color)
3091                color of the object
3092            alpha : (float)
3093                opacity of the object
3094            title : (str)
3095                title of the ruler
3096            font : (str)
3097                font face name. Check [available fonts here](https://vedo.embl.es/fonts).
3098            font_size : (int)
3099                font size
3100            bc : (color)
3101                background color of the title
3102
3103        Example:
3104            ```python
3105            from vedo  import *
3106            plt = Plotter(axes=1, interactive=False)
3107            plt.show(Cube())
3108            rul = Ruler2D()
3109            rul.set_points([0,0,0], [0.5,0.5,0.5])
3110            plt.add(rul)
3111            plt.interactive().close()
3112            ```
3113            ![](https://vedo.embl.es/images/feats/dist_tool.png)
3114        """
3115        super().__init__()
3116
3117        plt = vedo.plotter_instance
3118        if not plt:
3119            vedo.logger.error("Ruler2D need to initialize Plotter first.")
3120            raise RuntimeError()
3121
3122        self.p0 = [0, 0, 0]
3123        self.p1 = [0, 0, 0]
3124        self.distance = 0
3125        self.title = title
3126
3127        prop = self.GetProperty()
3128        tprop = self.GetTitleTextProperty()
3129
3130        self.SetTitle(title)
3131        self.SetNumberOfLabels(9)
3132
3133        if not font:
3134            font = settings.default_font
3135        if font.lower() == "courier":
3136            tprop.SetFontFamilyToCourier()
3137        elif font.lower() == "times":
3138            tprop.SetFontFamilyToTimes()
3139        elif font.lower() == "arial":
3140            tprop.SetFontFamilyToArial()
3141        else:
3142            tprop.SetFontFamily(vtki.VTK_FONT_FILE)
3143            tprop.SetFontFile(utils.get_font_path(font))
3144        tprop.SetFontSize(font_size)
3145        tprop.BoldOff()
3146        tprop.ItalicOff()
3147        tprop.ShadowOff()
3148        tprop.SetColor(get_color(c))
3149        tprop.SetOpacity(alpha)
3150        if bc is not None:
3151            bc = get_color(bc)
3152            tprop.SetBackgroundColor(bc)
3153            tprop.SetBackgroundOpacity(alpha)
3154
3155        lprop = vtki.vtkTextProperty()
3156        lprop.ShallowCopy(tprop)
3157        self.SetLabelTextProperty(lprop)
3158
3159        self.SetLabelFormat("%0.3g")
3160        self.SetTickVisibility(ticks)
3161        self.SetLabelVisibility(labels)
3162        prop.SetLineWidth(lw)
3163        prop.SetColor(get_color(c))
3164
3165        self.renderer = plt.renderer
3166        self.cid = plt.interactor.AddObserver("RenderEvent", self._update_viz, 1.0)

Create a ruler with tick marks, labels and a title.

Ruler2D is a 2D actor; that is, it is drawn on the overlay plane and is not occluded by 3D geometry. To use this class, specify two points defining the start and end with update_points() as 3D points.

This class decides decides how to create reasonable tick marks and labels.

Labels are drawn on the "right" side of the axis. The "right" side is the side of the axis on the right. The way the labels and title line up with the axis and tick marks depends on whether the line is considered horizontal or vertical.

Arguments:
  • lw : (int) width of the line in pixel units
  • ticks : (bool) control if drawing the tick marks
  • labels : (bool) control if drawing the numeric labels
  • c : (color) color of the object
  • alpha : (float) opacity of the object
  • title : (str) title of the ruler
  • font : (str) font face name. Check available fonts here.
  • font_size : (int) font size
  • bc : (color) background color of the title
Example:
from vedo  import *
plt = Plotter(axes=1, interactive=False)
plt.show(Cube())
rul = Ruler2D()
rul.set_points([0,0,0], [0.5,0.5,0.5])
plt.add(rul)
plt.interactive().close()

def color(self, c) -> Self:
3168    def color(self, c) -> Self:
3169        """Assign a new color."""
3170        c = get_color(c)
3171        self.GetTitleTextProperty().SetColor(c)
3172        self.GetLabelTextProperty().SetColor(c)
3173        self.GetProperty().SetColor(c)
3174        return self

Assign a new color.

def off(self) -> None:
3176    def off(self) -> None:
3177        """Switch off the ruler completely."""
3178        self.renderer.RemoveObserver(self.cid)
3179        self.renderer.RemoveActor(self)

Switch off the ruler completely.

def set_points(self, p0, p1) -> Self:
3181    def set_points(self, p0, p1) -> Self:
3182        """Set new values for the ruler start and end points."""
3183        self.p0 = np.asarray(p0)
3184        self.p1 = np.asarray(p1)
3185        self._update_viz(0, 0)
3186        return self

Set new values for the ruler start and end points.

def Ruler3D( p1, p2, units_scale=1, label='', s=None, font=None, italic=0, prefix='', units='', c=(0.2, 0.1, 0.1), alpha=1, lw=1, precision=3, label_rotation=0, axis_rotation=0, tick_angle=90) -> vedo.mesh.Mesh:
2785def Ruler3D(
2786    p1,
2787    p2,
2788    units_scale=1,
2789    label="",
2790    s=None,
2791    font=None,
2792    italic=0,
2793    prefix="",
2794    units="",  # eg.'μm'
2795    c=(0.2, 0.1, 0.1),
2796    alpha=1,
2797    lw=1,
2798    precision=3,
2799    label_rotation=0,
2800    axis_rotation=0,
2801    tick_angle=90,
2802) -> Mesh:
2803    """
2804    Build a 3D ruler to indicate the distance of two points p1 and p2.
2805
2806    Arguments:
2807        label : (str)
2808            alternative fixed label to be shown
2809        units_scale : (float)
2810            factor to scale units (e.g. μm to mm)
2811        s : (float)
2812            size of the label
2813        font : (str)
2814            font face.  Check [available fonts here](https://vedo.embl.es/fonts).
2815        italic : (float)
2816            italicness of the font in the range [0,1]
2817        units : (str)
2818            string to be appended to the numeric value
2819        lw : (int)
2820            line width in pixel units
2821        precision : (int)
2822            nr of significant digits to be shown
2823        label_rotation : (float)
2824            initial rotation of the label around the z-axis
2825        axis_rotation : (float)
2826            initial rotation of the line around the main axis
2827        tick_angle : (float)
2828            initial rotation of the line around the main axis
2829
2830    Examples:
2831        - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2832
2833        ![](https://vedo.embl.es/images/pyplot/goniometer.png)
2834    """
2835
2836    if units_scale != 1.0 and units == "":
2837        raise ValueError(
2838            "When setting 'units_scale' to a value other than 1, "
2839            + "a 'units' arguments must be specified."
2840        )
2841
2842    try:
2843        p1 = p1.pos()
2844    except AttributeError:
2845        pass
2846
2847    try:
2848        p2 = p2.pos()
2849    except AttributeError:
2850        pass
2851
2852    if len(p1) == 2:
2853        p1 = [p1[0], p1[1], 0.0]
2854    if len(p2) == 2:
2855        p2 = [p2[0], p2[1], 0.0]
2856
2857    p1, p2 = np.asarray(p1), np.asarray(p2)
2858    q1, q2 = [0, 0, 0], [utils.mag(p2 - p1), 0, 0]
2859    q1, q2 = np.array(q1), np.array(q2)
2860    v = q2 - q1
2861    d = utils.mag(v) * units_scale
2862
2863    pos = np.array(p1)
2864    p1 = p1 - pos
2865    p2 = p2 - pos
2866
2867    if s is None:
2868        s = d * 0.02 * (1 / units_scale)
2869
2870    if not label:
2871        label = str(d)
2872        if precision:
2873            label = utils.precision(d, precision)
2874    if prefix:
2875        label = prefix + "~" + label
2876    if units:
2877        label += "~" + units
2878
2879    lb = shapes.Text3D(label, s=s, font=font, italic=italic, justify="center")
2880    if label_rotation:
2881        lb.rotate_z(label_rotation)
2882    lb.pos((q1 + q2) / 2)
2883
2884    x0, x1 = lb.xbounds()
2885    gap = [(x1 - x0) / 2, 0, 0]
2886    pc1 = (v / 2 - gap) * 0.9 + q1
2887    pc2 = q2 - (v / 2 - gap) * 0.9
2888
2889    lc1 = shapes.Line(q1 - v / 50, pc1).lw(lw)
2890    lc2 = shapes.Line(q2 + v / 50, pc2).lw(lw)
2891
2892    zs = np.array([0, d / 50 * (1 / units_scale), 0])
2893    ml1 = shapes.Line(-zs, zs).lw(lw)
2894    ml2 = shapes.Line(-zs, zs).lw(lw)
2895    ml1.rotate_z(tick_angle - 90).pos(q1)
2896    ml2.rotate_z(tick_angle - 90).pos(q2)
2897
2898    c1 = shapes.Circle(q1, r=d / 180 * (1 / units_scale), res=24)
2899    c2 = shapes.Circle(q2, r=d / 180 * (1 / units_scale), res=24)
2900
2901    macts = merge(lb, lc1, lc2, c1, c2, ml1, ml2)
2902    macts.c(c).alpha(alpha)
2903    macts.properties.SetLineWidth(lw)
2904    macts.properties.LightingOff()
2905    macts.actor.UseBoundsOff()
2906    macts.rotate_x(axis_rotation)
2907    macts.reorient(q2 - q1, p2 - p1)
2908    macts.pos(pos)
2909    macts.bc("tomato").pickable(False)
2910    return macts

Build a 3D ruler to indicate the distance of two points p1 and p2.

Arguments:
  • label : (str) alternative fixed label to be shown
  • units_scale : (float) factor to scale units (e.g. μm to mm)
  • s : (float) size of the label
  • font : (str) font face. Check available fonts here.
  • italic : (float) italicness of the font in the range [0,1]
  • units : (str) string to be appended to the numeric value
  • lw : (int) line width in pixel units
  • precision : (int) nr of significant digits to be shown
  • label_rotation : (float) initial rotation of the label around the z-axis
  • axis_rotation : (float) initial rotation of the line around the main axis
  • tick_angle : (float) initial rotation of the line around the main axis
Examples:

def RulerAxes( inputobj, xtitle='', ytitle='', ztitle='', xlabel='', ylabel='', zlabel='', xpadding=0.05, ypadding=0.04, zpadding=0, font='Normografo', s=None, italic=0, units='', c=(0.2, 0, 0), alpha=1, lw=1, precision=3, label_rotation=0, xaxis_rotation=0, yaxis_rotation=0, zaxis_rotation=0, xycross=True) -> Optional[vedo.mesh.Mesh]:
2913def RulerAxes(
2914    inputobj,
2915    xtitle="",
2916    ytitle="",
2917    ztitle="",
2918    xlabel="",
2919    ylabel="",
2920    zlabel="",
2921    xpadding=0.05,
2922    ypadding=0.04,
2923    zpadding=0,
2924    font="Normografo",
2925    s=None,
2926    italic=0,
2927    units="",
2928    c=(0.2, 0, 0),
2929    alpha=1,
2930    lw=1,
2931    precision=3,
2932    label_rotation=0,
2933    xaxis_rotation=0,
2934    yaxis_rotation=0,
2935    zaxis_rotation=0,
2936    xycross=True,
2937) -> Union[Mesh, None]:
2938    """
2939    A 3D ruler axes to indicate the sizes of the input scene or object.
2940
2941    Arguments:
2942        xtitle : (str)
2943            name of the axis or title
2944        xlabel : (str)
2945            alternative fixed label to be shown instead of the distance
2946        s : (float)
2947            size of the label
2948        font : (str)
2949            font face. Check [available fonts here](https://vedo.embl.es/fonts).
2950        italic : (float)
2951            italicness of the font in the range [0,1]
2952        units : (str)
2953            string to be appended to the numeric value
2954        lw : (int)
2955            line width in pixel units
2956        precision : (int)
2957            nr of significant digits to be shown
2958        label_rotation : (float)
2959            initial rotation of the label around the z-axis
2960        [x,y,z]axis_rotation : (float)
2961            initial rotation of the line around the main axis in degrees
2962        xycross : (bool)
2963            show two back crossing lines in the xy plane
2964
2965    Examples:
2966        - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2967    """
2968    if utils.is_sequence(inputobj):
2969        x0, x1, y0, y1, z0, z1 = inputobj
2970    else:
2971        x0, x1, y0, y1, z0, z1 = inputobj.bounds()
2972    dx, dy, dz = (y1 - y0) * xpadding, (x1 - x0) * ypadding, (y1 - y0) * zpadding
2973    d = np.sqrt((y1 - y0) ** 2 + (x1 - x0) ** 2 + (z1 - z0) ** 2)
2974
2975    if not d:
2976        return None
2977
2978    if s is None:
2979        s = d / 75
2980
2981    acts, rx, ry = [], None, None
2982    if xtitle is not None and (x1 - x0) / d > 0.1:
2983        rx = Ruler3D(
2984            [x0, y0 - dx, z0],
2985            [x1, y0 - dx, z0],
2986            s=s,
2987            font=font,
2988            precision=precision,
2989            label_rotation=label_rotation,
2990            axis_rotation=xaxis_rotation,
2991            lw=lw,
2992            italic=italic,
2993            prefix=xtitle,
2994            label=xlabel,
2995            units=units,
2996        )
2997        acts.append(rx)
2998
2999    if ytitle is not None and (y1 - y0) / d > 0.1:
3000        ry = Ruler3D(
3001            [x1 + dy, y0, z0],
3002            [x1 + dy, y1, z0],
3003            s=s,
3004            font=font,
3005            precision=precision,
3006            label_rotation=label_rotation,
3007            axis_rotation=yaxis_rotation,
3008            lw=lw,
3009            italic=italic,
3010            prefix=ytitle,
3011            label=ylabel,
3012            units=units,
3013        )
3014        acts.append(ry)
3015
3016    if ztitle is not None and (z1 - z0) / d > 0.1:
3017        rz = Ruler3D(
3018            [x0 - dy, y0 + dz, z0],
3019            [x0 - dy, y0 + dz, z1],
3020            s=s,
3021            font=font,
3022            precision=precision,
3023            label_rotation=label_rotation,
3024            axis_rotation=zaxis_rotation + 90,
3025            lw=lw,
3026            italic=italic,
3027            prefix=ztitle,
3028            label=zlabel,
3029            units=units,
3030        )
3031        acts.append(rz)
3032
3033    if xycross and rx and ry:
3034        lx = shapes.Line([x0, y0, z0], [x0, y1 + dx, z0])
3035        ly = shapes.Line([x0 - dy, y1, z0], [x1, y1, z0])
3036        d = min((x1 - x0), (y1 - y0)) / 200
3037        cxy = shapes.Circle([x0, y1, z0], r=d, res=15)
3038        acts.extend([lx, ly, cxy])
3039
3040    macts = merge(acts)
3041    if not macts:
3042        return None
3043    macts.c(c).alpha(alpha).bc("t")
3044    macts.actor.UseBoundsOff()
3045    macts.actor.PickableOff()
3046    return macts

A 3D ruler axes to indicate the sizes of the input scene or object.

Arguments:
  • xtitle : (str) name of the axis or title
  • xlabel : (str) alternative fixed label to be shown instead of the distance
  • s : (float) size of the label
  • font : (str) font face. Check available fonts here.
  • italic : (float) italicness of the font in the range [0,1]
  • units : (str) string to be appended to the numeric value
  • lw : (int) line width in pixel units
  • precision : (int) nr of significant digits to be shown
  • label_rotation : (float) initial rotation of the label around the z-axis
  • [x,y,z]axis_rotation : (float) initial rotation of the line around the main axis in degrees
  • xycross : (bool) show two back crossing lines in the xy plane
Examples:
class DistanceTool(vedo.assembly.Group):
3211class DistanceTool(Group):
3212    """
3213    Create a tool to measure the distance between two clicked points.
3214    """
3215
3216    def __init__(self, plotter=None, c="k", lw=2):
3217        """
3218        Create a tool to measure the distance between two clicked points.
3219
3220        Example:
3221            ```python
3222            from vedo import *
3223            mesh = ParametricShape("RandomHills").c("red5")
3224            plt = Plotter(axes=1)
3225            dtool = DistanceTool()
3226            dtool.on()
3227            plt.show(mesh, dtool)
3228            dtool.off()
3229            ```
3230            ![](https://vedo.embl.es/images/feats/dist_tool.png)
3231        """
3232        super().__init__()
3233
3234        self.p0 = [0, 0, 0]
3235        self.p1 = [0, 0, 0]
3236        self.distance = 0
3237        if plotter is None:
3238            plotter = vedo.plotter_instance
3239        self.plotter = plotter
3240        self.callback = None
3241        self.cid = None
3242        self.color = c
3243        self.linewidth = lw
3244        self.toggle = True
3245        self.ruler = None
3246        self.title = ""
3247
3248    def on(self) -> Self:
3249        """Switch tool on."""
3250        self.cid = self.plotter.add_callback("click", self._onclick)
3251        self.VisibilityOn()
3252        self.plotter.render()
3253        return self
3254
3255    def off(self) -> None:
3256        """Switch tool off."""
3257        self.plotter.remove_callback(self.cid)
3258        self.VisibilityOff()
3259        self.ruler.off()
3260        self.plotter.render()
3261
3262    def _onclick(self, event):
3263        if not event.actor:
3264            return
3265
3266        self.clear()
3267
3268        acts = []
3269        if self.toggle:
3270            self.p0 = event.picked3d
3271            acts.append(Point(self.p0, c=self.color))
3272        else:
3273            self.p1 = event.picked3d
3274            self.distance = np.linalg.norm(self.p1 - self.p0)
3275            acts.append(Point(self.p0, c=self.color))
3276            acts.append(Point(self.p1, c=self.color))
3277            self.ruler = Ruler2D(c=self.color)
3278            self.ruler.set_points(self.p0, self.p1)
3279            acts.append(self.ruler)
3280
3281            if self.callback is not None:
3282                self.callback(event)
3283
3284        for a in acts:
3285            try:
3286                self += a.actor
3287            except AttributeError:
3288                self += a
3289        self.toggle = not self.toggle

Create a tool to measure the distance between two clicked points.

DistanceTool(plotter=None, c='k', lw=2)
3216    def __init__(self, plotter=None, c="k", lw=2):
3217        """
3218        Create a tool to measure the distance between two clicked points.
3219
3220        Example:
3221            ```python
3222            from vedo import *
3223            mesh = ParametricShape("RandomHills").c("red5")
3224            plt = Plotter(axes=1)
3225            dtool = DistanceTool()
3226            dtool.on()
3227            plt.show(mesh, dtool)
3228            dtool.off()
3229            ```
3230            ![](https://vedo.embl.es/images/feats/dist_tool.png)
3231        """
3232        super().__init__()
3233
3234        self.p0 = [0, 0, 0]
3235        self.p1 = [0, 0, 0]
3236        self.distance = 0
3237        if plotter is None:
3238            plotter = vedo.plotter_instance
3239        self.plotter = plotter
3240        self.callback = None
3241        self.cid = None
3242        self.color = c
3243        self.linewidth = lw
3244        self.toggle = True
3245        self.ruler = None
3246        self.title = ""

Create a tool to measure the distance between two clicked points.

Example:
from vedo import *
mesh = ParametricShape("RandomHills").c("red5")
plt = Plotter(axes=1)
dtool = DistanceTool()
dtool.on()
plt.show(mesh, dtool)
dtool.off()

def on(self) -> Self:
3248    def on(self) -> Self:
3249        """Switch tool on."""
3250        self.cid = self.plotter.add_callback("click", self._onclick)
3251        self.VisibilityOn()
3252        self.plotter.render()
3253        return self

Switch tool on.

def off(self) -> None:
3255    def off(self) -> None:
3256        """Switch tool off."""
3257        self.plotter.remove_callback(self.cid)
3258        self.VisibilityOff()
3259        self.ruler.off()
3260        self.plotter.render()

Switch tool off.

class SplineTool(vtkmodules.vtkInteractionWidgets.vtkContourWidget):
688class SplineTool(vtki.vtkContourWidget):
689    """
690    Spline tool, draw a spline through a set of points interactively.
691    """
692
693    def __init__(
694        self,
695        points,
696        pc="k",
697        ps=8,
698        lc="r4",
699        ac="g5",
700        lw=2,
701        alpha=1,
702        closed=False,
703        ontop=True,
704        can_add_nodes=True,
705    ):
706        """
707        Spline tool, draw a spline through a set of points interactively.
708
709        Arguments:
710            points : (list), Points
711                initial set of points.
712            pc : (str)
713                point color.
714            ps : (int)
715                point size.
716            lc : (str)
717                line color.
718            ac : (str)
719                active point color.
720            lw : (int)
721                line width.
722            alpha : (float)
723                line transparency level.
724            closed : (bool)
725                spline is closed or open.
726            ontop : (bool)
727                show it always on top of other objects.
728            can_add_nodes : (bool)
729                allow to add (or remove) new nodes interactively.
730
731        Examples:
732            - [spline_tool.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/spline_tool.py)
733
734                ![](https://vedo.embl.es/images/basic/spline_tool.png)
735        """
736        super().__init__()
737
738        self.representation = self.GetRepresentation()
739        self.representation.SetAlwaysOnTop(ontop)
740        self.SetAllowNodePicking(can_add_nodes)
741
742        self.representation.GetLinesProperty().SetColor(get_color(lc))
743        self.representation.GetLinesProperty().SetLineWidth(lw)
744        self.representation.GetLinesProperty().SetOpacity(alpha)
745        if lw == 0 or alpha == 0:
746            self.representation.GetLinesProperty().SetOpacity(0)
747
748        self.representation.GetActiveProperty().SetLineWidth(lw + 1)
749        self.representation.GetActiveProperty().SetColor(get_color(ac))
750
751        self.representation.GetProperty().SetColor(get_color(pc))
752        self.representation.GetProperty().SetPointSize(ps)
753        self.representation.GetProperty().RenderPointsAsSpheresOn()
754
755        # self.representation.BuildRepresentation() # crashes
756
757        self.SetRepresentation(self.representation)
758
759        if utils.is_sequence(points):
760            self.points = Points(points)
761        else:
762            self.points = points
763
764        self.closed = closed
765
766    @property
767    def interactor(self):
768        """Return the current interactor."""
769        return self.GetInteractor()
770
771    @interactor.setter
772    def interactor(self, iren):
773        """Set the current interactor."""
774        self.SetInteractor(iren)
775
776    def add(self, pt) -> "SplineTool":
777        """
778        Add one point at a specified position in space if 3D,
779        or 2D screen-display position if 2D.
780        """
781        if len(pt) == 2:
782            self.representation.AddNodeAtDisplayPosition(int(pt[0]), int(pt[1]))
783        else:
784            self.representation.AddNodeAtWorldPosition(pt)
785        return self
786
787    def add_observer(self, event, func, priority=1) -> int:
788        """Add an observer to the widget."""
789        event = utils.get_vtk_name_event(event)
790        cid = self.AddObserver(event, func, priority)
791        return cid
792
793    def remove(self, i: int) -> "SplineTool":
794        """Remove specific node by its index"""
795        self.representation.DeleteNthNode(i)
796        return self
797
798    def on(self) -> "SplineTool":
799        """Activate/Enable the tool"""
800        self.On()
801        self.Render()
802        return self
803
804    def off(self) -> "SplineTool":
805        """Disactivate/Disable the tool"""
806        self.Off()
807        self.Render()
808        return self
809
810    def render(self) -> "SplineTool":
811        """Render the spline"""
812        self.Render()
813        return self
814
815    # def bounds(self) -> np.ndarray:
816    #     """Retrieve the bounding box of the spline as [x0,x1, y0,y1, z0,z1]"""
817    #     return np.array(self.GetBounds())
818
819    def spline(self) -> vedo.Line:
820        """Return the vedo.Spline object."""
821        self.representation.SetClosedLoop(self.closed)
822        self.representation.BuildRepresentation()
823        pd = self.representation.GetContourRepresentationAsPolyData()
824        ln = vedo.Line(pd, lw=2, c="k")
825        return ln
826
827    def nodes(self, onscreen=False) -> np.ndarray:
828        """Return the current position in space (or on 2D screen-display) of the spline nodes."""
829        n = self.representation.GetNumberOfNodes()
830        pts = []
831        for i in range(n):
832            p = [0.0, 0.0, 0.0]
833            if onscreen:
834                self.representation.GetNthNodeDisplayPosition(i, p)
835            else:
836                self.representation.GetNthNodeWorldPosition(i, p)
837            pts.append(p)
838        return np.array(pts)

Spline tool, draw a spline through a set of points interactively.

SplineTool( points, pc='k', ps=8, lc='r4', ac='g5', lw=2, alpha=1, closed=False, ontop=True, can_add_nodes=True)
693    def __init__(
694        self,
695        points,
696        pc="k",
697        ps=8,
698        lc="r4",
699        ac="g5",
700        lw=2,
701        alpha=1,
702        closed=False,
703        ontop=True,
704        can_add_nodes=True,
705    ):
706        """
707        Spline tool, draw a spline through a set of points interactively.
708
709        Arguments:
710            points : (list), Points
711                initial set of points.
712            pc : (str)
713                point color.
714            ps : (int)
715                point size.
716            lc : (str)
717                line color.
718            ac : (str)
719                active point color.
720            lw : (int)
721                line width.
722            alpha : (float)
723                line transparency level.
724            closed : (bool)
725                spline is closed or open.
726            ontop : (bool)
727                show it always on top of other objects.
728            can_add_nodes : (bool)
729                allow to add (or remove) new nodes interactively.
730
731        Examples:
732            - [spline_tool.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/spline_tool.py)
733
734                ![](https://vedo.embl.es/images/basic/spline_tool.png)
735        """
736        super().__init__()
737
738        self.representation = self.GetRepresentation()
739        self.representation.SetAlwaysOnTop(ontop)
740        self.SetAllowNodePicking(can_add_nodes)
741
742        self.representation.GetLinesProperty().SetColor(get_color(lc))
743        self.representation.GetLinesProperty().SetLineWidth(lw)
744        self.representation.GetLinesProperty().SetOpacity(alpha)
745        if lw == 0 or alpha == 0:
746            self.representation.GetLinesProperty().SetOpacity(0)
747
748        self.representation.GetActiveProperty().SetLineWidth(lw + 1)
749        self.representation.GetActiveProperty().SetColor(get_color(ac))
750
751        self.representation.GetProperty().SetColor(get_color(pc))
752        self.representation.GetProperty().SetPointSize(ps)
753        self.representation.GetProperty().RenderPointsAsSpheresOn()
754
755        # self.representation.BuildRepresentation() # crashes
756
757        self.SetRepresentation(self.representation)
758
759        if utils.is_sequence(points):
760            self.points = Points(points)
761        else:
762            self.points = points
763
764        self.closed = closed

Spline tool, draw a spline through a set of points interactively.

Arguments:
  • points : (list), Points initial set of points.
  • pc : (str) point color.
  • ps : (int) point size.
  • lc : (str) line color.
  • ac : (str) active point color.
  • lw : (int) line width.
  • alpha : (float) line transparency level.
  • closed : (bool) spline is closed or open.
  • ontop : (bool) show it always on top of other objects.
  • can_add_nodes : (bool) allow to add (or remove) new nodes interactively.
Examples:
interactor
766    @property
767    def interactor(self):
768        """Return the current interactor."""
769        return self.GetInteractor()

Return the current interactor.

def add(self, pt) -> SplineTool:
776    def add(self, pt) -> "SplineTool":
777        """
778        Add one point at a specified position in space if 3D,
779        or 2D screen-display position if 2D.
780        """
781        if len(pt) == 2:
782            self.representation.AddNodeAtDisplayPosition(int(pt[0]), int(pt[1]))
783        else:
784            self.representation.AddNodeAtWorldPosition(pt)
785        return self

Add one point at a specified position in space if 3D, or 2D screen-display position if 2D.

def add_observer(self, event, func, priority=1) -> int:
787    def add_observer(self, event, func, priority=1) -> int:
788        """Add an observer to the widget."""
789        event = utils.get_vtk_name_event(event)
790        cid = self.AddObserver(event, func, priority)
791        return cid

Add an observer to the widget.

def remove(self, i: int) -> SplineTool:
793    def remove(self, i: int) -> "SplineTool":
794        """Remove specific node by its index"""
795        self.representation.DeleteNthNode(i)
796        return self

Remove specific node by its index

def on(self) -> SplineTool:
798    def on(self) -> "SplineTool":
799        """Activate/Enable the tool"""
800        self.On()
801        self.Render()
802        return self

Activate/Enable the tool

def off(self) -> SplineTool:
804    def off(self) -> "SplineTool":
805        """Disactivate/Disable the tool"""
806        self.Off()
807        self.Render()
808        return self

Disactivate/Disable the tool

def render(self) -> SplineTool:
810    def render(self) -> "SplineTool":
811        """Render the spline"""
812        self.Render()
813        return self

Render the spline

def spline(self) -> vedo.shapes.Line:
819    def spline(self) -> vedo.Line:
820        """Return the vedo.Spline object."""
821        self.representation.SetClosedLoop(self.closed)
822        self.representation.BuildRepresentation()
823        pd = self.representation.GetContourRepresentationAsPolyData()
824        ln = vedo.Line(pd, lw=2, c="k")
825        return ln

Return the vedo.Spline object.

def nodes(self, onscreen=False) -> numpy.ndarray:
827    def nodes(self, onscreen=False) -> np.ndarray:
828        """Return the current position in space (or on 2D screen-display) of the spline nodes."""
829        n = self.representation.GetNumberOfNodes()
830        pts = []
831        for i in range(n):
832            p = [0.0, 0.0, 0.0]
833            if onscreen:
834                self.representation.GetNthNodeDisplayPosition(i, p)
835            else:
836                self.representation.GetNthNodeWorldPosition(i, p)
837            pts.append(p)
838        return np.array(pts)

Return the current position in space (or on 2D screen-display) of the spline nodes.

class DrawingWidget:
841class DrawingWidget:
842    def __init__(self, obj, c="green5", lw=4, closed=False, snap_to_image=False):
843        """
844        3D widget for tracing on planar props.
845        This is primarily designed for manually tracing over image data.
846        
847        - Any object can be input rather than just 2D images
848        - The widget fires pick events at the input prop to decide where to move its handles
849        - The widget has 2D glyphs for handles instead of 3D spheres. 
850
851        The button actions and key modifiers are as follows for controlling the widget: 
852            1) left button click over the image, hold and drag draws a free hand line.
853            2) left button click and release erases the widget line, if it exists, and repositions the first handle.
854            3) middle button click starts a snap drawn line. 
855                The line is terminated by clicking the middle button while ressing the ctrl key.
856            4) when tracing a continuous or snap drawn line, if the last cursor position is within a specified 
857                tolerance to the first handle, the widget line will form a closed loop.
858            5) right button clicking and holding on any handle that is part of a snap drawn line allows handle dragging:
859                existing line segments are updated accordingly. If the path is open and closing_radius is set,
860                the path can be closed by repositioning the first and last points over one another.
861            6) Ctrl + right button down on any handle will erase it: existing snap drawn line segments are updated accordingly.
862                If the line was formed by continuous tracing, the line is deleted leaving one handle.
863            7) Shift + right button down on any snap drawn line segment will insert a handle at the cursor position.
864                The line segment is split accordingly.
865        
866        Arguments:
867        obj : vtkProp
868            The prop to trace on.
869        c : str, optional
870            The color of the line. The default is "green5".
871        lw : int, optional
872            The line width. The default is 4.
873        closed : bool, optional
874            Whether to close the line. The default is False.
875        snap_to_image : bool, optional
876            Whether to snap to the image. The default is False.
877
878        Example:
879            - [spline_draw2.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/spline_draw2.py)
880        """
881
882        self.widget = vtki.new("ImageTracerWidget")
883
884        self.line = None
885        self.line_properties = self.widget.GetLineProperty()
886        self.line_properties.SetColor(vedo.get_color(c))
887        self.line_properties.SetLineWidth(lw)
888        self.callback_id = None
889        self.event_name = "EndInteractionEvent"
890
891        if vedo.plotter_instance:
892            self.widget.SetInteractor(vedo.plotter_instance.interactor)
893        if vedo.plotter_instance.renderer:
894            self.widget.SetDefaultRenderer(vedo.plotter_instance.renderer)
895
896        try:
897            self.widget.SetViewProp(obj.actor)
898        except AttributeError:
899            self.widget.SetViewProp(obj)
900
901        if closed:
902            closing_radius = 1e10
903            self.widget.SetAutoClose(1)
904            self.widget.SetCaptureRadius(closing_radius)
905
906        self.widget.SetProjectToPlane(0)
907        self.widget.SetProjectionNormal(2)  # XY plane
908        self.widget.SetProjectionPosition(0)
909        self.widget.SetSnapToImage(snap_to_image)
910
911    def callback(self, widget, eventId) -> None:
912        path = vtki.vtkPolyData()
913        widget.GetPath(path)
914        self.line = vedo.shapes.Line(path, c=self.line_properties.GetColor())
915        # print(f"There are {path.GetNumberOfPoints()} points in the line.")
916
917    def add_observer(self, event, func, priority=1) -> int:
918        """Add an observer to the widget."""
919        event = utils.get_vtk_name_event(event)
920        cid = self.widget.AddObserver(event, func, priority)
921        return cid
922
923    @property
924    def interactor(self):
925        return self.widget.GetInteractor()
926
927    @interactor.setter
928    def interactor(self, value):
929        self.widget.SetInteractor(value)
930
931    @property
932    def renderer(self):
933        return self.widget.GetDefaultRenderer()
934
935    @renderer.setter
936    def renderer(self, value):
937        self.widget.SetDefaultRenderer(value)
938
939    def on(self) -> Self:
940        self.widget.On()
941        ev_name = vedo.utils.get_vtk_name_event(self.event_name)
942        self.callback_id = self.widget.AddObserver(ev_name, self.callback, 1000)
943        return self
944
945    def off(self) -> None:
946        self.widget.Off()
947        self.widget.RemoveObserver(self.callback_id)
948
949    def freeze(self, value=True) -> Self:
950        self.widget.SetInteraction(not value)
951        return self
952
953    def remove(self) -> None:
954        self.widget.Off()
955        self.widget.RemoveObserver(self.callback_id)
956        self.widget.SetInteractor(None)
957        self.line = None
958        self.line_properties = None
959        self.callback_id = None
960        self.widget = None
DrawingWidget(obj, c='green5', lw=4, closed=False, snap_to_image=False)
842    def __init__(self, obj, c="green5", lw=4, closed=False, snap_to_image=False):
843        """
844        3D widget for tracing on planar props.
845        This is primarily designed for manually tracing over image data.
846        
847        - Any object can be input rather than just 2D images
848        - The widget fires pick events at the input prop to decide where to move its handles
849        - The widget has 2D glyphs for handles instead of 3D spheres. 
850
851        The button actions and key modifiers are as follows for controlling the widget: 
852            1) left button click over the image, hold and drag draws a free hand line.
853            2) left button click and release erases the widget line, if it exists, and repositions the first handle.
854            3) middle button click starts a snap drawn line. 
855                The line is terminated by clicking the middle button while ressing the ctrl key.
856            4) when tracing a continuous or snap drawn line, if the last cursor position is within a specified 
857                tolerance to the first handle, the widget line will form a closed loop.
858            5) right button clicking and holding on any handle that is part of a snap drawn line allows handle dragging:
859                existing line segments are updated accordingly. If the path is open and closing_radius is set,
860                the path can be closed by repositioning the first and last points over one another.
861            6) Ctrl + right button down on any handle will erase it: existing snap drawn line segments are updated accordingly.
862                If the line was formed by continuous tracing, the line is deleted leaving one handle.
863            7) Shift + right button down on any snap drawn line segment will insert a handle at the cursor position.
864                The line segment is split accordingly.
865        
866        Arguments:
867        obj : vtkProp
868            The prop to trace on.
869        c : str, optional
870            The color of the line. The default is "green5".
871        lw : int, optional
872            The line width. The default is 4.
873        closed : bool, optional
874            Whether to close the line. The default is False.
875        snap_to_image : bool, optional
876            Whether to snap to the image. The default is False.
877
878        Example:
879            - [spline_draw2.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/spline_draw2.py)
880        """
881
882        self.widget = vtki.new("ImageTracerWidget")
883
884        self.line = None
885        self.line_properties = self.widget.GetLineProperty()
886        self.line_properties.SetColor(vedo.get_color(c))
887        self.line_properties.SetLineWidth(lw)
888        self.callback_id = None
889        self.event_name = "EndInteractionEvent"
890
891        if vedo.plotter_instance:
892            self.widget.SetInteractor(vedo.plotter_instance.interactor)
893        if vedo.plotter_instance.renderer:
894            self.widget.SetDefaultRenderer(vedo.plotter_instance.renderer)
895
896        try:
897            self.widget.SetViewProp(obj.actor)
898        except AttributeError:
899            self.widget.SetViewProp(obj)
900
901        if closed:
902            closing_radius = 1e10
903            self.widget.SetAutoClose(1)
904            self.widget.SetCaptureRadius(closing_radius)
905
906        self.widget.SetProjectToPlane(0)
907        self.widget.SetProjectionNormal(2)  # XY plane
908        self.widget.SetProjectionPosition(0)
909        self.widget.SetSnapToImage(snap_to_image)

3D widget for tracing on planar props. This is primarily designed for manually tracing over image data.

  • Any object can be input rather than just 2D images
  • The widget fires pick events at the input prop to decide where to move its handles
  • The widget has 2D glyphs for handles instead of 3D spheres.

The button actions and key modifiers are as follows for controlling the widget: 1) left button click over the image, hold and drag draws a free hand line. 2) left button click and release erases the widget line, if it exists, and repositions the first handle. 3) middle button click starts a snap drawn line. The line is terminated by clicking the middle button while ressing the ctrl key. 4) when tracing a continuous or snap drawn line, if the last cursor position is within a specified tolerance to the first handle, the widget line will form a closed loop. 5) right button clicking and holding on any handle that is part of a snap drawn line allows handle dragging: existing line segments are updated accordingly. If the path is open and closing_radius is set, the path can be closed by repositioning the first and last points over one another. 6) Ctrl + right button down on any handle will erase it: existing snap drawn line segments are updated accordingly. If the line was formed by continuous tracing, the line is deleted leaving one handle. 7) Shift + right button down on any snap drawn line segment will insert a handle at the cursor position. The line segment is split accordingly.

Arguments: obj : vtkProp The prop to trace on. c : str, optional The color of the line. The default is "green5". lw : int, optional The line width. The default is 4. closed : bool, optional Whether to close the line. The default is False. snap_to_image : bool, optional Whether to snap to the image. The default is False.

Example:
def callback(self, widget, eventId) -> None:
911    def callback(self, widget, eventId) -> None:
912        path = vtki.vtkPolyData()
913        widget.GetPath(path)
914        self.line = vedo.shapes.Line(path, c=self.line_properties.GetColor())
915        # print(f"There are {path.GetNumberOfPoints()} points in the line.")
def add_observer(self, event, func, priority=1) -> int:
917    def add_observer(self, event, func, priority=1) -> int:
918        """Add an observer to the widget."""
919        event = utils.get_vtk_name_event(event)
920        cid = self.widget.AddObserver(event, func, priority)
921        return cid

Add an observer to the widget.

def on(self) -> Self:
939    def on(self) -> Self:
940        self.widget.On()
941        ev_name = vedo.utils.get_vtk_name_event(self.event_name)
942        self.callback_id = self.widget.AddObserver(ev_name, self.callback, 1000)
943        return self
def off(self) -> None:
945    def off(self) -> None:
946        self.widget.Off()
947        self.widget.RemoveObserver(self.callback_id)
def freeze(self, value=True) -> Self:
949    def freeze(self, value=True) -> Self:
950        self.widget.SetInteraction(not value)
951        return self
def remove(self) -> None:
953    def remove(self) -> None:
954        self.widget.Off()
955        self.widget.RemoveObserver(self.callback_id)
956        self.widget.SetInteractor(None)
957        self.line = None
958        self.line_properties = None
959        self.callback_id = None
960        self.widget = None
def Goniometer( p1, p2, p3, font='', arc_size=0.4, s=1, italic=0, rotation=0, prefix='', lc='k2', c='white', alpha=1, lw=2, precision=3):
1042def Goniometer(
1043    p1,
1044    p2,
1045    p3,
1046    font="",
1047    arc_size=0.4,
1048    s=1,
1049    italic=0,
1050    rotation=0,
1051    prefix="",
1052    lc="k2",
1053    c="white",
1054    alpha=1,
1055    lw=2,
1056    precision=3,
1057):
1058    """
1059    Build a graphical goniometer to measure the angle formed by 3 points in space.
1060
1061    Arguments:
1062        p1 : (list)
1063            first point 3D coordinates.
1064        p2 : (list)
1065            the vertex point.
1066        p3 : (list)
1067            the last point defining the angle.
1068        font : (str)
1069            Font face. Check [available fonts here](https://vedo.embl.es/fonts).
1070        arc_size : (float)
1071            dimension of the arc wrt the smallest axis.
1072        s : (float)
1073            size of the text.
1074        italic : (float, bool)
1075            italic text.
1076        rotation : (float)
1077            rotation of text in degrees.
1078        prefix : (str)
1079            append this string to the numeric value of the angle.
1080        lc : (list)
1081            color of the goniometer lines.
1082        c : (str)
1083            color of the goniometer angle filling. Set alpha=0 to remove it.
1084        alpha : (float)
1085            transparency level.
1086        lw : (float)
1087            line width.
1088        precision : (int)
1089            number of significant digits.
1090
1091    Examples:
1092        - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
1093
1094            ![](https://vedo.embl.es/images/pyplot/goniometer.png)
1095    """
1096    if isinstance(p1, Points): p1 = p1.pos()
1097    if isinstance(p2, Points): p2 = p2.pos()
1098    if isinstance(p3, Points): p3 = p3.pos()
1099    if len(p1)==2: p1=[p1[0], p1[1], 0.0]
1100    if len(p2)==2: p2=[p2[0], p2[1], 0.0]
1101    if len(p3)==2: p3=[p3[0], p3[1], 0.0]
1102    p1, p2, p3 = np.array(p1), np.array(p2), np.array(p3)
1103
1104    acts = []
1105    ln = shapes.Line([p1, p2, p3], lw=lw, c=lc)
1106    acts.append(ln)
1107
1108    va = utils.versor(p1 - p2)
1109    vb = utils.versor(p3 - p2)
1110    r = min(utils.mag(p3 - p2), utils.mag(p1 - p2)) * arc_size
1111    ptsarc = []
1112    res = 120
1113    imed = int(res / 2)
1114    for i in range(res + 1):
1115        vi = utils.versor(vb * i / res + va * (res - i) / res)
1116        if i == imed:
1117            vc = np.array(vi)
1118        ptsarc.append(p2 + vi * r)
1119    arc = shapes.Line(ptsarc).lw(lw).c(lc)
1120    acts.append(arc)
1121
1122    angle = np.arccos(np.dot(va, vb)) * 180 / np.pi
1123
1124    lb = shapes.Text3D(
1125        prefix + utils.precision(angle, precision) + "º",
1126        s=r / 12 * s,
1127        font=font,
1128        italic=italic,
1129        justify="center",
1130    )
1131    cr = np.cross(va, vb)
1132    lb.reorient([0, 0, 1], cr * np.sign(cr[2]), rotation=rotation, xyplane=False)
1133    lb.pos(p2 + vc * r / 1.75)
1134    lb.c(c).bc("tomato").lighting("off")
1135    acts.append(lb)
1136
1137    if alpha > 0:
1138        pts = [p2] + arc.coordinates.tolist() + [p2]
1139        msh = Mesh([pts, [list(range(arc.npoints + 2))]], c=lc, alpha=alpha)
1140        msh.lighting("off")
1141        msh.triangulate()
1142        msh.shift(0, 0, -r / 10000)  # to resolve 2d conflicts..
1143        acts.append(msh)
1144
1145    asse = Assembly(acts)
1146    asse.name = "Goniometer"
1147    return asse

Build a graphical goniometer to measure the angle formed by 3 points in space.

Arguments:
  • p1 : (list) first point 3D coordinates.
  • p2 : (list) the vertex point.
  • p3 : (list) the last point defining the angle.
  • font : (str) Font face. Check available fonts here.
  • arc_size : (float) dimension of the arc wrt the smallest axis.
  • s : (float) size of the text.
  • italic : (float, bool) italic text.
  • rotation : (float) rotation of text in degrees.
  • prefix : (str) append this string to the numeric value of the angle.
  • lc : (list) color of the goniometer lines.
  • c : (str) color of the goniometer angle filling. Set alpha=0 to remove it.
  • alpha : (float) transparency level.
  • lw : (float) line width.
  • precision : (int) number of significant digits.
Examples:
class Button(vedo.shapes.Text2D):
574class Button(vedo.shapes.Text2D):
575    """
576    Build a Button object.
577    """
578
579    def __init__(
580        self,
581        fnc=None,
582        states=("Button"),
583        c=("white"),
584        bc=("green4"),
585        pos=(0.7, 0.1),
586        size=24,
587        font="Courier",
588        bold=True,
589        italic=False,
590        alpha=1,
591        angle=0,
592    ):
593        """
594        Build a Button object to be shown in the rendering window.
595
596        Arguments:
597            fnc : (function)
598                external function to be called by the widget
599            states : (list)
600                the list of possible states, eg. ['On', 'Off']
601            c : (list)
602                the list of colors for each state eg. ['red3', 'green5']
603            bc : (list)
604                the list of background colors for each state
605            pos : (list, str)
606                2D position in pixels from left-bottom corner
607            size : (int)
608                size of button font
609            font : (str)
610                font type
611            bold : (bool)
612                set bold font face
613            italic : (bool)
614                italic font face
615            alpha : (float)
616                opacity level
617            angle : (float)
618                anticlockwise rotation in degrees
619
620        Examples:
621            - [buttons1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons1.py)
622            - [buttons2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons2.py)
623
624                ![](https://user-images.githubusercontent.com/32848391/50738870-c0fe2500-11d8-11e9-9b78-92754f5c5968.jpg)
625
626            - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py)
627
628                ![](https://vedo.embl.es/images/advanced/timer_callback1.jpg)
629        """
630        super().__init__()
631
632        self.status_idx = 0
633
634        self.spacer = " "
635
636        self.states = states
637
638        if not utils.is_sequence(c):
639            c = [c]
640        self.colors = c
641
642        if not utils.is_sequence(bc):
643            bc = [bc]
644        self.bcolors = bc
645
646        assert len(c) == len(bc), "in Button color number mismatch!"
647
648        self.function = fnc
649        self.function_id = None
650
651        self.status(0)
652
653        if font == "courier":
654            font = font.capitalize()
655        self.font(font).bold(bold).italic(italic)
656
657        self.alpha(alpha).angle(angle)
658        self.size(size / 20)
659        self.pos(pos, "center")
660        self.PickableOn()
661
662    def status(self, s=None) -> "Button":
663        """
664        Set/Get the status of the button.
665        """
666        if s is None:
667            return self.states[self.status_idx]
668
669        if isinstance(s, str):
670            s = self.states.index(s)
671        self.status_idx = s
672        self.text(self.spacer + self.states[s] + self.spacer)
673        s = s % len(self.bcolors)
674        self.color(self.colors[s])
675        self.background(self.bcolors[s])
676        return self
677
678    def switch(self) -> "Button":
679        """
680        Change/cycle button status to the next defined status in states list.
681        """
682        self.status_idx = (self.status_idx + 1) % len(self.states)
683        self.status(self.status_idx)
684        return self

Build a Button object.

Button( fnc=None, states='Button', c='white', bc='green4', pos=(0.7, 0.1), size=24, font='Courier', bold=True, italic=False, alpha=1, angle=0)
579    def __init__(
580        self,
581        fnc=None,
582        states=("Button"),
583        c=("white"),
584        bc=("green4"),
585        pos=(0.7, 0.1),
586        size=24,
587        font="Courier",
588        bold=True,
589        italic=False,
590        alpha=1,
591        angle=0,
592    ):
593        """
594        Build a Button object to be shown in the rendering window.
595
596        Arguments:
597            fnc : (function)
598                external function to be called by the widget
599            states : (list)
600                the list of possible states, eg. ['On', 'Off']
601            c : (list)
602                the list of colors for each state eg. ['red3', 'green5']
603            bc : (list)
604                the list of background colors for each state
605            pos : (list, str)
606                2D position in pixels from left-bottom corner
607            size : (int)
608                size of button font
609            font : (str)
610                font type
611            bold : (bool)
612                set bold font face
613            italic : (bool)
614                italic font face
615            alpha : (float)
616                opacity level
617            angle : (float)
618                anticlockwise rotation in degrees
619
620        Examples:
621            - [buttons1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons1.py)
622            - [buttons2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons2.py)
623
624                ![](https://user-images.githubusercontent.com/32848391/50738870-c0fe2500-11d8-11e9-9b78-92754f5c5968.jpg)
625
626            - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py)
627
628                ![](https://vedo.embl.es/images/advanced/timer_callback1.jpg)
629        """
630        super().__init__()
631
632        self.status_idx = 0
633
634        self.spacer = " "
635
636        self.states = states
637
638        if not utils.is_sequence(c):
639            c = [c]
640        self.colors = c
641
642        if not utils.is_sequence(bc):
643            bc = [bc]
644        self.bcolors = bc
645
646        assert len(c) == len(bc), "in Button color number mismatch!"
647
648        self.function = fnc
649        self.function_id = None
650
651        self.status(0)
652
653        if font == "courier":
654            font = font.capitalize()
655        self.font(font).bold(bold).italic(italic)
656
657        self.alpha(alpha).angle(angle)
658        self.size(size / 20)
659        self.pos(pos, "center")
660        self.PickableOn()

Build a Button object to be shown in the rendering window.

Arguments:
  • fnc : (function) external function to be called by the widget
  • states : (list) the list of possible states, eg. ['On', 'Off']
  • c : (list) the list of colors for each state eg. ['red3', 'green5']
  • bc : (list) the list of background colors for each state
  • pos : (list, str) 2D position in pixels from left-bottom corner
  • size : (int) size of button font
  • font : (str) font type
  • bold : (bool) set bold font face
  • italic : (bool) italic font face
  • alpha : (float) opacity level
  • angle : (float) anticlockwise rotation in degrees
Examples:
def status(self, s=None) -> Button:
662    def status(self, s=None) -> "Button":
663        """
664        Set/Get the status of the button.
665        """
666        if s is None:
667            return self.states[self.status_idx]
668
669        if isinstance(s, str):
670            s = self.states.index(s)
671        self.status_idx = s
672        self.text(self.spacer + self.states[s] + self.spacer)
673        s = s % len(self.bcolors)
674        self.color(self.colors[s])
675        self.background(self.bcolors[s])
676        return self

Set/Get the status of the button.

def switch(self) -> Button:
678    def switch(self) -> "Button":
679        """
680        Change/cycle button status to the next defined status in states list.
681        """
682        self.status_idx = (self.status_idx + 1) % len(self.states)
683        self.status(self.status_idx)
684        return self

Change/cycle button status to the next defined status in states list.

class ButtonWidget:
378class ButtonWidget:
379    """
380    Create a button widget.
381    """
382
383    def __init__(
384        self,
385        function,
386        states=(),
387        c=("white"),
388        bc=("green4"),
389        alpha=1.0,
390        font="Calco",
391        size=100,
392        plotter=None,
393    ):
394        """
395        Create a button widget.
396
397        States can be either text strings or images.
398
399        Arguments:
400            function : (function)
401                external function to be called by the widget
402            states : (list)
403                the list of possible states, eg. ['On', 'Off']
404            c : (list)
405                the list of colors for each state eg. ['red3', 'green5']
406            bc : (list)
407                the list of background colors for each state
408            alpha : (float)
409                opacity level
410            font : (str)
411                font type
412            size : (int)
413                size of button font
414            plotter : (Plotter)
415                the plotter object to which the widget is added
416        
417        Example:
418            ```py
419            from vedo import *
420
421            def button_func(widget, evtname):
422                print("button_func called")
423                cone.color(button.state)
424                
425            def on_mouse_click(event):
426                if event.object:
427                    print("on_mouse_click", event)
428                    cone.color(button.state)
429
430            # Create a cone
431            cone = Cone().color(0)
432
433            # Create a plotter
434            plt = Plotter(bg='bb', axes=1)
435            plt.add_callback('mouse click', on_mouse_click)
436
437            plt.add(cone)
438
439            # Create a button widget
440            img0 = Image("play-button.png")
441            img1 = Image("power-on.png")
442
443            button = ButtonWidget(
444                button_func, 
445                # states=["State 0", "State 1"], 
446                states=[img0, img1],
447                c=["red4", "blue4"],
448                bc=("k9", "k5"),
449                size=100,
450                plotter=plt,
451            )
452            button.pos([0,0]).enable()
453
454            plt.show()
455            ```
456        """
457
458        self.widget = vtki.new("ButtonWidget")
459
460        self.function = function
461        self.states = states
462        self.colors = c
463        self.background_colors = bc
464        self.plotter = plotter
465        self.size = size
466
467        assert len(states) == len(c),  "states and colors must have the same length"
468        assert len(states) == len(bc), "states and background colors must have the same length"
469
470        self.interactor = None
471        if plotter is not None:
472            self.interactor = plotter.interactor
473            self.widget.SetInteractor(plotter.interactor)
474        else:
475            if vedo.plotter_instance:
476                self.interactor = vedo.plotter_instance.interactor
477                self.widget.SetInteractor(self.interactor)
478
479        self.representation = vtki.new("TexturedButtonRepresentation2D")
480        self.representation.SetNumberOfStates(len(states))
481        for i, state in enumerate(states):
482
483            if isinstance(state, vedo.Image):
484                state = state.dataset
485
486            elif isinstance(state, str):
487                txt = state
488                tp = vtki.vtkTextProperty()
489                tp.BoldOff()
490                tp.FrameOff()
491                col = c[i]
492                tp.SetColor(vedo.get_color(col))
493                tp.ShadowOff()
494                tp.ItalicOff()
495                col = bc[i]
496                tp.SetBackgroundColor(vedo.get_color(col))
497                tp.SetBackgroundOpacity(alpha)
498                tp.UseTightBoundingBoxOff()
499
500                # tp.SetJustificationToLeft()
501                # tp.SetVerticalJustificationToCentered()
502                # tp.SetJustificationToCentered()
503                width, height = 100 * len(txt), 1000
504
505                fpath = vedo.utils.get_font_path(font)
506                tp.SetFontFamily(vtki.VTK_FONT_FILE)
507                tp.SetFontFile(fpath)
508
509                tr = vtki.new("TextRenderer")
510                fs = tr.GetConstrainedFontSize(txt, tp, width, height, 500)
511                tp.SetFontSize(fs)
512
513                img = vtki.vtkImageData()
514                tr.RenderString(tp, txt, img, [width, height], 500)
515                state = img
516
517            self.representation.SetButtonTexture(i, state)
518
519        self.widget.SetRepresentation(self.representation)
520        self.widget.AddObserver("StateChangedEvent", function)
521
522    def __del__(self):
523        self.widget.Off()
524        self.widget.SetInteractor(None)
525        self.widget.SetRepresentation(None)
526        self.representation = None
527        self.interactor = None
528        self.function = None
529        self.states = ()
530        self.widget = None
531        self.plotter = None
532
533    def pos(self, pos):
534        assert len(pos) == 2, "pos must be a 2D position"
535        if not self.plotter:
536            vedo.logger.warning("ButtonWidget: pos() can only be used if a Plotter is provided") 
537            return self
538        coords = vtki.vtkCoordinate()
539        coords.SetCoordinateSystemToNormalizedDisplay()
540        coords.SetValue(pos[0], pos[1])
541        sz = self.size
542        ren = self.plotter.renderer
543        p = coords.GetComputedDisplayValue(ren)
544        bds = [0, 0, 0, 0, 0, 0]
545        bds[0] = p[0] - sz
546        bds[1] = bds[0] + sz
547        bds[2] = p[1] - sz
548        bds[3] = bds[2] + sz
549        self.representation.SetPlaceFactor(1)
550        self.representation.PlaceWidget(bds)
551        return self
552
553    def enable(self):
554        self.widget.On()
555        return self
556
557    def disable(self):
558        self.widget.Off()
559        return self
560
561    def next_state(self):
562        self.representation.NextState()
563        return self
564
565    @property
566    def state(self):
567        return self.representation.GetState()
568
569    @state.setter
570    def state(self, i):
571        self.representation.SetState(i)

Create a button widget.

ButtonWidget( function, states=(), c='white', bc='green4', alpha=1.0, font='Calco', size=100, plotter=None)
383    def __init__(
384        self,
385        function,
386        states=(),
387        c=("white"),
388        bc=("green4"),
389        alpha=1.0,
390        font="Calco",
391        size=100,
392        plotter=None,
393    ):
394        """
395        Create a button widget.
396
397        States can be either text strings or images.
398
399        Arguments:
400            function : (function)
401                external function to be called by the widget
402            states : (list)
403                the list of possible states, eg. ['On', 'Off']
404            c : (list)
405                the list of colors for each state eg. ['red3', 'green5']
406            bc : (list)
407                the list of background colors for each state
408            alpha : (float)
409                opacity level
410            font : (str)
411                font type
412            size : (int)
413                size of button font
414            plotter : (Plotter)
415                the plotter object to which the widget is added
416        
417        Example:
418            ```py
419            from vedo import *
420
421            def button_func(widget, evtname):
422                print("button_func called")
423                cone.color(button.state)
424                
425            def on_mouse_click(event):
426                if event.object:
427                    print("on_mouse_click", event)
428                    cone.color(button.state)
429
430            # Create a cone
431            cone = Cone().color(0)
432
433            # Create a plotter
434            plt = Plotter(bg='bb', axes=1)
435            plt.add_callback('mouse click', on_mouse_click)
436
437            plt.add(cone)
438
439            # Create a button widget
440            img0 = Image("play-button.png")
441            img1 = Image("power-on.png")
442
443            button = ButtonWidget(
444                button_func, 
445                # states=["State 0", "State 1"], 
446                states=[img0, img1],
447                c=["red4", "blue4"],
448                bc=("k9", "k5"),
449                size=100,
450                plotter=plt,
451            )
452            button.pos([0,0]).enable()
453
454            plt.show()
455            ```
456        """
457
458        self.widget = vtki.new("ButtonWidget")
459
460        self.function = function
461        self.states = states
462        self.colors = c
463        self.background_colors = bc
464        self.plotter = plotter
465        self.size = size
466
467        assert len(states) == len(c),  "states and colors must have the same length"
468        assert len(states) == len(bc), "states and background colors must have the same length"
469
470        self.interactor = None
471        if plotter is not None:
472            self.interactor = plotter.interactor
473            self.widget.SetInteractor(plotter.interactor)
474        else:
475            if vedo.plotter_instance:
476                self.interactor = vedo.plotter_instance.interactor
477                self.widget.SetInteractor(self.interactor)
478
479        self.representation = vtki.new("TexturedButtonRepresentation2D")
480        self.representation.SetNumberOfStates(len(states))
481        for i, state in enumerate(states):
482
483            if isinstance(state, vedo.Image):
484                state = state.dataset
485
486            elif isinstance(state, str):
487                txt = state
488                tp = vtki.vtkTextProperty()
489                tp.BoldOff()
490                tp.FrameOff()
491                col = c[i]
492                tp.SetColor(vedo.get_color(col))
493                tp.ShadowOff()
494                tp.ItalicOff()
495                col = bc[i]
496                tp.SetBackgroundColor(vedo.get_color(col))
497                tp.SetBackgroundOpacity(alpha)
498                tp.UseTightBoundingBoxOff()
499
500                # tp.SetJustificationToLeft()
501                # tp.SetVerticalJustificationToCentered()
502                # tp.SetJustificationToCentered()
503                width, height = 100 * len(txt), 1000
504
505                fpath = vedo.utils.get_font_path(font)
506                tp.SetFontFamily(vtki.VTK_FONT_FILE)
507                tp.SetFontFile(fpath)
508
509                tr = vtki.new("TextRenderer")
510                fs = tr.GetConstrainedFontSize(txt, tp, width, height, 500)
511                tp.SetFontSize(fs)
512
513                img = vtki.vtkImageData()
514                tr.RenderString(tp, txt, img, [width, height], 500)
515                state = img
516
517            self.representation.SetButtonTexture(i, state)
518
519        self.widget.SetRepresentation(self.representation)
520        self.widget.AddObserver("StateChangedEvent", function)

Create a button widget.

States can be either text strings or images.

Arguments:
  • function : (function) external function to be called by the widget
  • states : (list) the list of possible states, eg. ['On', 'Off']
  • c : (list) the list of colors for each state eg. ['red3', 'green5']
  • bc : (list) the list of background colors for each state
  • alpha : (float) opacity level
  • font : (str) font type
  • size : (int) size of button font
  • plotter : (Plotter) the plotter object to which the widget is added
Example:
from vedo import *

def button_func(widget, evtname):
    print("button_func called")
    cone.color(button.state)

def on_mouse_click(event):
    if event.object:
        print("on_mouse_click", event)
        cone.color(button.state)

# Create a cone
cone = Cone().color(0)

# Create a plotter
plt = Plotter(bg='bb', axes=1)
plt.add_callback('mouse click', on_mouse_click)

plt.add(cone)

# Create a button widget
img0 = Image("play-button.png")
img1 = Image("power-on.png")

button = ButtonWidget(
    button_func, 
    # states=["State 0", "State 1"], 
    states=[img0, img1],
    c=["red4", "blue4"],
    bc=("k9", "k5"),
    size=100,
    plotter=plt,
)
button.pos([0,0]).enable()

plt.show()
def pos(self, pos):
533    def pos(self, pos):
534        assert len(pos) == 2, "pos must be a 2D position"
535        if not self.plotter:
536            vedo.logger.warning("ButtonWidget: pos() can only be used if a Plotter is provided") 
537            return self
538        coords = vtki.vtkCoordinate()
539        coords.SetCoordinateSystemToNormalizedDisplay()
540        coords.SetValue(pos[0], pos[1])
541        sz = self.size
542        ren = self.plotter.renderer
543        p = coords.GetComputedDisplayValue(ren)
544        bds = [0, 0, 0, 0, 0, 0]
545        bds[0] = p[0] - sz
546        bds[1] = bds[0] + sz
547        bds[2] = p[1] - sz
548        bds[3] = bds[2] + sz
549        self.representation.SetPlaceFactor(1)
550        self.representation.PlaceWidget(bds)
551        return self
def enable(self):
553    def enable(self):
554        self.widget.On()
555        return self
def disable(self):
557    def disable(self):
558        self.widget.Off()
559        return self
def next_state(self):
561    def next_state(self):
562        self.representation.NextState()
563        return self
class Flagpost(vtkmodules.vtkRenderingCore.vtkFlagpoleLabel):
 57class Flagpost(vtki.vtkFlagpoleLabel):
 58    """
 59    Create a flag post style element to describe an object.
 60    """
 61
 62    def __init__(
 63        self,
 64        txt="",
 65        base=(0, 0, 0),
 66        top=(0, 0, 1),
 67        s=1,
 68        c="k9",
 69        bc="k1",
 70        alpha=1,
 71        lw=0,
 72        font="Calco",
 73        justify="center-left",
 74        vspacing=1,
 75    ):
 76        """
 77        Create a flag post style element to describe an object.
 78
 79        Arguments:
 80            txt : (str)
 81                Text to display. The default is the filename or the object name.
 82            base : (list)
 83                position of the flag anchor point.
 84            top : (list)
 85                a 3D displacement or offset.
 86            s : (float)
 87                size of the text to be shown
 88            c : (list)
 89                color of text and line
 90            bc : (list)
 91                color of the flag background
 92            alpha : (float)
 93                opacity of text and box.
 94            lw : (int)
 95                line with of box frame. The default is 0.
 96            font : (str)
 97                font name. Use a monospace font for better rendering. The default is "Calco".
 98                Type `vedo -r fonts` for a font demo.
 99                Check [available fonts here](https://vedo.embl.es/fonts).
100            justify : (str)
101                internal text justification. The default is "center-left".
102            vspacing : (float)
103                vertical spacing between lines.
104
105        Examples:
106            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py)
107
108            ![](https://vedo.embl.es/images/other/flag_labels2.png)
109        """
110
111        super().__init__()
112
113        base = utils.make3d(base)
114        top = utils.make3d(top)
115
116        self.SetBasePosition(*base)
117        self.SetTopPosition(*top)
118
119        self.SetFlagSize(s)
120        self.SetInput(txt)
121        self.PickableOff()
122
123        self.GetProperty().LightingOff()
124        self.GetProperty().SetLineWidth(lw + 1)
125
126        prop = self.GetTextProperty()
127        if bc is not None:
128            prop.SetBackgroundColor(get_color(bc))
129
130        prop.SetOpacity(alpha)
131        prop.SetBackgroundOpacity(alpha)
132        if bc is not None and len(bc) == 4:
133            prop.SetBackgroundRGBA(alpha)
134
135        c = get_color(c)
136        prop.SetColor(c)
137        self.GetProperty().SetColor(c)
138
139        prop.SetFrame(bool(lw))
140        prop.SetFrameWidth(lw)
141        prop.SetFrameColor(prop.GetColor())
142
143        prop.SetFontFamily(vtki.VTK_FONT_FILE)
144        fl = utils.get_font_path(font)
145        prop.SetFontFile(fl)
146        prop.ShadowOff()
147        prop.BoldOff()
148        prop.SetOpacity(alpha)
149        prop.SetJustificationToLeft()
150        if "top" in justify:
151            prop.SetVerticalJustificationToTop()
152        if "bottom" in justify:
153            prop.SetVerticalJustificationToBottom()
154        if "cent" in justify:
155            prop.SetVerticalJustificationToCentered()
156            prop.SetJustificationToCentered()
157        if "left" in justify:
158            prop.SetJustificationToLeft()
159        if "right" in justify:
160            prop.SetJustificationToRight()
161        prop.SetLineSpacing(vspacing * 1.2)
162        self.SetUseBounds(False)
163
164    def text(self, value: str) -> Self:
165        self.SetInput(value)
166        return self
167
168    def on(self) -> Self:
169        self.VisibilityOn()
170        return self
171
172    def off(self) -> Self:
173        self.VisibilityOff()
174        return self
175
176    def toggle(self) -> Self:
177        self.SetVisibility(not self.GetVisibility())
178        return self
179
180    def use_bounds(self, value=True) -> Self:
181        self.SetUseBounds(value)
182        return self
183
184    def color(self, c) -> Self:
185        c = get_color(c)
186        self.GetTextProperty().SetColor(c)
187        self.GetProperty().SetColor(c)
188        return self
189
190    def pos(self, p) -> Self:
191        p = np.asarray(p)
192        self.top = self.top - self.base + p
193        self.base = p
194        return self
195
196    @property
197    def base(self) -> np.ndarray:
198        return np.array(self.GetBasePosition())
199
200    @base.setter
201    def base(self, value):
202        self.SetBasePosition(*value)
203
204    @property
205    def top(self) -> np.ndarray:
206        return np.array(self.GetTopPosition())
207
208    @top.setter
209    def top(self, value):
210        self.SetTopPosition(*value)

Create a flag post style element to describe an object.

Flagpost( txt='', base=(0, 0, 0), top=(0, 0, 1), s=1, c='k9', bc='k1', alpha=1, lw=0, font='Calco', justify='center-left', vspacing=1)
 62    def __init__(
 63        self,
 64        txt="",
 65        base=(0, 0, 0),
 66        top=(0, 0, 1),
 67        s=1,
 68        c="k9",
 69        bc="k1",
 70        alpha=1,
 71        lw=0,
 72        font="Calco",
 73        justify="center-left",
 74        vspacing=1,
 75    ):
 76        """
 77        Create a flag post style element to describe an object.
 78
 79        Arguments:
 80            txt : (str)
 81                Text to display. The default is the filename or the object name.
 82            base : (list)
 83                position of the flag anchor point.
 84            top : (list)
 85                a 3D displacement or offset.
 86            s : (float)
 87                size of the text to be shown
 88            c : (list)
 89                color of text and line
 90            bc : (list)
 91                color of the flag background
 92            alpha : (float)
 93                opacity of text and box.
 94            lw : (int)
 95                line with of box frame. The default is 0.
 96            font : (str)
 97                font name. Use a monospace font for better rendering. The default is "Calco".
 98                Type `vedo -r fonts` for a font demo.
 99                Check [available fonts here](https://vedo.embl.es/fonts).
100            justify : (str)
101                internal text justification. The default is "center-left".
102            vspacing : (float)
103                vertical spacing between lines.
104
105        Examples:
106            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py)
107
108            ![](https://vedo.embl.es/images/other/flag_labels2.png)
109        """
110
111        super().__init__()
112
113        base = utils.make3d(base)
114        top = utils.make3d(top)
115
116        self.SetBasePosition(*base)
117        self.SetTopPosition(*top)
118
119        self.SetFlagSize(s)
120        self.SetInput(txt)
121        self.PickableOff()
122
123        self.GetProperty().LightingOff()
124        self.GetProperty().SetLineWidth(lw + 1)
125
126        prop = self.GetTextProperty()
127        if bc is not None:
128            prop.SetBackgroundColor(get_color(bc))
129
130        prop.SetOpacity(alpha)
131        prop.SetBackgroundOpacity(alpha)
132        if bc is not None and len(bc) == 4:
133            prop.SetBackgroundRGBA(alpha)
134
135        c = get_color(c)
136        prop.SetColor(c)
137        self.GetProperty().SetColor(c)
138
139        prop.SetFrame(bool(lw))
140        prop.SetFrameWidth(lw)
141        prop.SetFrameColor(prop.GetColor())
142
143        prop.SetFontFamily(vtki.VTK_FONT_FILE)
144        fl = utils.get_font_path(font)
145        prop.SetFontFile(fl)
146        prop.ShadowOff()
147        prop.BoldOff()
148        prop.SetOpacity(alpha)
149        prop.SetJustificationToLeft()
150        if "top" in justify:
151            prop.SetVerticalJustificationToTop()
152        if "bottom" in justify:
153            prop.SetVerticalJustificationToBottom()
154        if "cent" in justify:
155            prop.SetVerticalJustificationToCentered()
156            prop.SetJustificationToCentered()
157        if "left" in justify:
158            prop.SetJustificationToLeft()
159        if "right" in justify:
160            prop.SetJustificationToRight()
161        prop.SetLineSpacing(vspacing * 1.2)
162        self.SetUseBounds(False)

Create a flag post style element to describe an object.

Arguments:
  • txt : (str) Text to display. The default is the filename or the object name.
  • base : (list) position of the flag anchor point.
  • top : (list) a 3D displacement or offset.
  • s : (float) size of the text to be shown
  • c : (list) color of text and line
  • bc : (list) color of the flag background
  • alpha : (float) opacity of text and box.
  • lw : (int) line with of box frame. The default is 0.
  • font : (str) font name. Use a monospace font for better rendering. The default is "Calco". Type vedo -r fonts for a font demo. Check available fonts here.
  • justify : (str) internal text justification. The default is "center-left".
  • vspacing : (float) vertical spacing between lines.
Examples:

def text(self, value: str) -> Self:
164    def text(self, value: str) -> Self:
165        self.SetInput(value)
166        return self
def on(self) -> Self:
168    def on(self) -> Self:
169        self.VisibilityOn()
170        return self
def off(self) -> Self:
172    def off(self) -> Self:
173        self.VisibilityOff()
174        return self
def toggle(self) -> Self:
176    def toggle(self) -> Self:
177        self.SetVisibility(not self.GetVisibility())
178        return self
def use_bounds(self, value=True) -> Self:
180    def use_bounds(self, value=True) -> Self:
181        self.SetUseBounds(value)
182        return self
def color(self, c) -> Self:
184    def color(self, c) -> Self:
185        c = get_color(c)
186        self.GetTextProperty().SetColor(c)
187        self.GetProperty().SetColor(c)
188        return self
def pos(self, p) -> Self:
190    def pos(self, p) -> Self:
191        p = np.asarray(p)
192        self.top = self.top - self.base + p
193        self.base = p
194        return self
class ProgressBarWidget(vtkmodules.vtkRenderingCore.vtkActor2D):
2617class ProgressBarWidget(vtki.vtkActor2D):
2618    """
2619    Add a progress bar in the rendering window.
2620    """
2621
2622    def __init__(self, n=None, c="blue5", alpha=0.8, lw=10, autohide=True):
2623        """
2624        Add a progress bar window.
2625
2626        Arguments:
2627            n : (int)
2628                number of iterations.
2629                If None, you need to call `update(fraction)` manually.
2630            c : (color)
2631                color of the line.
2632            alpha : (float)
2633                opacity of the line.
2634            lw : (int)
2635                line width in pixels.
2636            autohide : (bool)
2637                if True, hide the progress bar when completed.
2638        """
2639        self.n = 0
2640        self.iterations = n
2641        self.autohide = autohide
2642
2643        ppoints = vtki.vtkPoints()  # Generate the line
2644        psqr = [[0, 0, 0], [1, 0, 0]]
2645        for i, pt in enumerate(psqr):
2646            ppoints.InsertPoint(i, *pt)
2647        lines = vtki.vtkCellArray()
2648        lines.InsertNextCell(len(psqr))
2649        for i in range(len(psqr)):
2650            lines.InsertCellPoint(i)
2651        pd = vtki.vtkPolyData()
2652        pd.SetPoints(ppoints)
2653        pd.SetLines(lines)
2654        self.dataset = pd
2655
2656        mapper = vtki.new("PolyDataMapper2D")
2657        mapper.SetInputData(pd)
2658        cs = vtki.vtkCoordinate()
2659        cs.SetCoordinateSystemToNormalizedViewport()
2660        mapper.SetTransformCoordinate(cs)
2661
2662        super().__init__()
2663
2664        self.SetMapper(mapper)
2665        self.GetProperty().SetOpacity(alpha)
2666        self.GetProperty().SetColor(get_color(c))
2667        self.GetProperty().SetLineWidth(lw * 2)
2668
2669    def lw(self, value: int) -> Self:
2670        """Set width."""
2671        self.GetProperty().SetLineWidth(value * 2)
2672        return self
2673
2674    def c(self, color) -> Self:
2675        """Set color."""
2676        c = get_color(color)
2677        self.GetProperty().SetColor(c)
2678        return self
2679
2680    def alpha(self, value) -> Self:
2681        """Set opacity."""
2682        self.GetProperty().SetOpacity(value)
2683        return self
2684
2685    def update(self, fraction=None) -> Self:
2686        """Update progress bar to fraction of the window width."""
2687        if fraction is None:
2688            if self.iterations is None:
2689                vedo.printc("Error in ProgressBarWindow: must specify iterations", c='r')
2690                return self
2691            self.n += 1
2692            fraction = self.n / self.iterations
2693
2694        if fraction >= 1 and self.autohide:
2695            fraction = 0
2696
2697        psqr = [[0, 0, 0], [fraction, 0, 0]]
2698        vpts = utils.numpy2vtk(psqr, dtype=np.float32)
2699        self.dataset.GetPoints().SetData(vpts)
2700        return self
2701
2702    def reset(self):
2703        """Reset progress bar."""
2704        self.n = 0
2705        self.update(0)
2706        return self

Add a progress bar in the rendering window.

ProgressBarWidget(n=None, c='blue5', alpha=0.8, lw=10, autohide=True)
2622    def __init__(self, n=None, c="blue5", alpha=0.8, lw=10, autohide=True):
2623        """
2624        Add a progress bar window.
2625
2626        Arguments:
2627            n : (int)
2628                number of iterations.
2629                If None, you need to call `update(fraction)` manually.
2630            c : (color)
2631                color of the line.
2632            alpha : (float)
2633                opacity of the line.
2634            lw : (int)
2635                line width in pixels.
2636            autohide : (bool)
2637                if True, hide the progress bar when completed.
2638        """
2639        self.n = 0
2640        self.iterations = n
2641        self.autohide = autohide
2642
2643        ppoints = vtki.vtkPoints()  # Generate the line
2644        psqr = [[0, 0, 0], [1, 0, 0]]
2645        for i, pt in enumerate(psqr):
2646            ppoints.InsertPoint(i, *pt)
2647        lines = vtki.vtkCellArray()
2648        lines.InsertNextCell(len(psqr))
2649        for i in range(len(psqr)):
2650            lines.InsertCellPoint(i)
2651        pd = vtki.vtkPolyData()
2652        pd.SetPoints(ppoints)
2653        pd.SetLines(lines)
2654        self.dataset = pd
2655
2656        mapper = vtki.new("PolyDataMapper2D")
2657        mapper.SetInputData(pd)
2658        cs = vtki.vtkCoordinate()
2659        cs.SetCoordinateSystemToNormalizedViewport()
2660        mapper.SetTransformCoordinate(cs)
2661
2662        super().__init__()
2663
2664        self.SetMapper(mapper)
2665        self.GetProperty().SetOpacity(alpha)
2666        self.GetProperty().SetColor(get_color(c))
2667        self.GetProperty().SetLineWidth(lw * 2)

Add a progress bar window.

Arguments:
  • n : (int) number of iterations. If None, you need to call update(fraction) manually.
  • c : (color) color of the line.
  • alpha : (float) opacity of the line.
  • lw : (int) line width in pixels.
  • autohide : (bool) if True, hide the progress bar when completed.
def lw(self, value: int) -> Self:
2669    def lw(self, value: int) -> Self:
2670        """Set width."""
2671        self.GetProperty().SetLineWidth(value * 2)
2672        return self

Set width.

def c(self, color) -> Self:
2674    def c(self, color) -> Self:
2675        """Set color."""
2676        c = get_color(color)
2677        self.GetProperty().SetColor(c)
2678        return self

Set color.

def alpha(self, value) -> Self:
2680    def alpha(self, value) -> Self:
2681        """Set opacity."""
2682        self.GetProperty().SetOpacity(value)
2683        return self

Set opacity.

def update(self, fraction=None) -> Self:
2685    def update(self, fraction=None) -> Self:
2686        """Update progress bar to fraction of the window width."""
2687        if fraction is None:
2688            if self.iterations is None:
2689                vedo.printc("Error in ProgressBarWindow: must specify iterations", c='r')
2690                return self
2691            self.n += 1
2692            fraction = self.n / self.iterations
2693
2694        if fraction >= 1 and self.autohide:
2695            fraction = 0
2696
2697        psqr = [[0, 0, 0], [fraction, 0, 0]]
2698        vpts = utils.numpy2vtk(psqr, dtype=np.float32)
2699        self.dataset.GetPoints().SetData(vpts)
2700        return self

Update progress bar to fraction of the window width.

def reset(self):
2702    def reset(self):
2703        """Reset progress bar."""
2704        self.n = 0
2705        self.update(0)
2706        return self

Reset progress bar.

class BoxCutter(vtkmodules.vtkInteractionWidgets.vtkBoxWidget, BaseCutter):
2289class BoxCutter(vtki.vtkBoxWidget, BaseCutter):
2290    """
2291    Create a box widget to cut away parts of a Mesh.
2292    """
2293
2294    def __init__(
2295        self,
2296        mesh,
2297        invert=False,
2298        can_rotate=True,
2299        can_translate=True,
2300        can_scale=True,
2301        initial_bounds=(),
2302        padding=0.025,
2303        delayed=False,
2304        c=(0.25, 0.25, 0.25),
2305        alpha=0.05,
2306    ):
2307        """
2308        Create a box widget to cut away parts of a Mesh.
2309
2310        Arguments:
2311            mesh : (Mesh)
2312                the input mesh
2313            invert : (bool)
2314                invert the clipping plane
2315            can_rotate : (bool)
2316                enable rotation of the widget
2317            can_translate : (bool)
2318                enable translation of the widget
2319            can_scale : (bool)
2320                enable scaling of the widget
2321            initial_bounds : (list)
2322                initial bounds of the box widget
2323            padding : (float)
2324                padding space around the input mesh
2325            delayed : (bool)
2326                if True the callback is delayed until
2327                when the mouse button is released (useful for large meshes)
2328            c : (color)
2329                color of the box cutter widget
2330            alpha : (float)
2331                transparency of the cut-off part of the input mesh
2332        """
2333        super().__init__()
2334
2335        self.mesh = mesh
2336        self.remnant = Mesh()
2337        self.remnant.name = mesh.name + "Remnant"
2338        self.remnant.pickable(False)
2339
2340        self._alpha = alpha
2341        self._keypress_id = None
2342        self._init_bounds = initial_bounds
2343        if len(self._init_bounds) == 0:
2344            self._init_bounds = mesh.bounds()
2345        else:
2346            self._init_bounds = initial_bounds
2347
2348        self._implicit_func = vtki.new("Planes")
2349        self._implicit_func.SetBounds(self._init_bounds)
2350
2351        poly = mesh.dataset
2352        self.clipper = vtki.new("ClipPolyData")
2353        self.clipper.GenerateClipScalarsOff()
2354        self.clipper.SetInputData(poly)
2355        self.clipper.SetClipFunction(self._implicit_func)
2356        self.clipper.SetInsideOut(not invert)
2357        self.clipper.GenerateClippedOutputOn()
2358        self.clipper.Update()
2359
2360        self.widget = vtki.vtkBoxWidget()
2361
2362        self.widget.SetRotationEnabled(can_rotate)
2363        self.widget.SetTranslationEnabled(can_translate)
2364        self.widget.SetScalingEnabled(can_scale)
2365
2366        self.widget.OutlineCursorWiresOn()
2367        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
2368        self.widget.GetSelectedHandleProperty().SetColor(get_color("red5"))
2369
2370        self.widget.GetOutlineProperty().SetColor(c)
2371        self.widget.GetOutlineProperty().SetOpacity(1)
2372        self.widget.GetOutlineProperty().SetLineWidth(1)
2373        self.widget.GetOutlineProperty().LightingOff()
2374
2375        self.widget.GetSelectedFaceProperty().LightingOff()
2376        self.widget.GetSelectedFaceProperty().SetOpacity(0.1)
2377
2378        self.widget.SetPlaceFactor(1.0 + padding)
2379        self.widget.SetInputData(poly)
2380        self.widget.PlaceWidget()
2381        if delayed:
2382            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2383        else:
2384            self.widget.AddObserver("InteractionEvent", self._select_polygons)
2385
2386    def _select_polygons(self, vobj, event):
2387        vobj.GetPlanes(self._implicit_func)
2388
2389    def _keypress(self, vobj, event):
2390        if vobj.GetKeySym() == "r":  # reset planes
2391            self._implicit_func.SetBounds(self._init_bounds)
2392            self.widget.GetPlanes(self._implicit_func)
2393            self.widget.PlaceWidget()
2394            self.widget.GetInteractor().Render()
2395        elif vobj.GetKeySym() == "u":
2396            self.invert()
2397            self.widget.GetInteractor().Render()
2398        elif vobj.GetKeySym() == "s":  # Ctrl+s to save mesh
2399            if self.widget.GetInteractor():
2400                if self.widget.GetInteractor().GetControlKey():
2401                    self.mesh.write("vedo_clipped.vtk")
2402                    printc(":save: saved mesh to vedo_clipped.vtk")

Create a box widget to cut away parts of a Mesh.

BoxCutter( mesh, invert=False, can_rotate=True, can_translate=True, can_scale=True, initial_bounds=(), padding=0.025, delayed=False, c=(0.25, 0.25, 0.25), alpha=0.05)
2294    def __init__(
2295        self,
2296        mesh,
2297        invert=False,
2298        can_rotate=True,
2299        can_translate=True,
2300        can_scale=True,
2301        initial_bounds=(),
2302        padding=0.025,
2303        delayed=False,
2304        c=(0.25, 0.25, 0.25),
2305        alpha=0.05,
2306    ):
2307        """
2308        Create a box widget to cut away parts of a Mesh.
2309
2310        Arguments:
2311            mesh : (Mesh)
2312                the input mesh
2313            invert : (bool)
2314                invert the clipping plane
2315            can_rotate : (bool)
2316                enable rotation of the widget
2317            can_translate : (bool)
2318                enable translation of the widget
2319            can_scale : (bool)
2320                enable scaling of the widget
2321            initial_bounds : (list)
2322                initial bounds of the box widget
2323            padding : (float)
2324                padding space around the input mesh
2325            delayed : (bool)
2326                if True the callback is delayed until
2327                when the mouse button is released (useful for large meshes)
2328            c : (color)
2329                color of the box cutter widget
2330            alpha : (float)
2331                transparency of the cut-off part of the input mesh
2332        """
2333        super().__init__()
2334
2335        self.mesh = mesh
2336        self.remnant = Mesh()
2337        self.remnant.name = mesh.name + "Remnant"
2338        self.remnant.pickable(False)
2339
2340        self._alpha = alpha
2341        self._keypress_id = None
2342        self._init_bounds = initial_bounds
2343        if len(self._init_bounds) == 0:
2344            self._init_bounds = mesh.bounds()
2345        else:
2346            self._init_bounds = initial_bounds
2347
2348        self._implicit_func = vtki.new("Planes")
2349        self._implicit_func.SetBounds(self._init_bounds)
2350
2351        poly = mesh.dataset
2352        self.clipper = vtki.new("ClipPolyData")
2353        self.clipper.GenerateClipScalarsOff()
2354        self.clipper.SetInputData(poly)
2355        self.clipper.SetClipFunction(self._implicit_func)
2356        self.clipper.SetInsideOut(not invert)
2357        self.clipper.GenerateClippedOutputOn()
2358        self.clipper.Update()
2359
2360        self.widget = vtki.vtkBoxWidget()
2361
2362        self.widget.SetRotationEnabled(can_rotate)
2363        self.widget.SetTranslationEnabled(can_translate)
2364        self.widget.SetScalingEnabled(can_scale)
2365
2366        self.widget.OutlineCursorWiresOn()
2367        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
2368        self.widget.GetSelectedHandleProperty().SetColor(get_color("red5"))
2369
2370        self.widget.GetOutlineProperty().SetColor(c)
2371        self.widget.GetOutlineProperty().SetOpacity(1)
2372        self.widget.GetOutlineProperty().SetLineWidth(1)
2373        self.widget.GetOutlineProperty().LightingOff()
2374
2375        self.widget.GetSelectedFaceProperty().LightingOff()
2376        self.widget.GetSelectedFaceProperty().SetOpacity(0.1)
2377
2378        self.widget.SetPlaceFactor(1.0 + padding)
2379        self.widget.SetInputData(poly)
2380        self.widget.PlaceWidget()
2381        if delayed:
2382            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2383        else:
2384            self.widget.AddObserver("InteractionEvent", self._select_polygons)

Create a box widget to cut away parts of a Mesh.

Arguments:
  • mesh : (Mesh) the input mesh
  • invert : (bool) invert the clipping plane
  • can_rotate : (bool) enable rotation of the widget
  • can_translate : (bool) enable translation of the widget
  • can_scale : (bool) enable scaling of the widget
  • initial_bounds : (list) initial bounds of the box widget
  • padding : (float) padding space around the input mesh
  • delayed : (bool) if True the callback is delayed until when the mouse button is released (useful for large meshes)
  • c : (color) color of the box cutter widget
  • alpha : (float) transparency of the cut-off part of the input mesh
class PlaneCutter(vtkmodules.vtkInteractionWidgets.vtkPlaneWidget, BaseCutter):
2127class PlaneCutter(vtki.vtkPlaneWidget, BaseCutter):
2128    """
2129    Create a box widget to cut away parts of a Mesh.
2130    """
2131
2132    def __init__(
2133        self,
2134        mesh,
2135        invert=False,
2136        can_translate=True,
2137        can_scale=True,
2138        origin=(),
2139        normal=(),
2140        padding=0.05,
2141        delayed=False,
2142        c=(0.25, 0.25, 0.25),
2143        alpha=0.05,
2144    ):
2145        """
2146        Create a box widget to cut away parts of a `Mesh`.
2147
2148        Arguments:
2149            mesh : (Mesh)
2150                the input mesh
2151            invert : (bool)
2152                invert the clipping plane
2153            can_translate : (bool)
2154                enable translation of the widget
2155            can_scale : (bool)
2156                enable scaling of the widget
2157            origin : (list)
2158                origin of the plane
2159            normal : (list)
2160                normal to the plane
2161            padding : (float)
2162                padding around the input mesh
2163            delayed : (bool)
2164                if True the callback is delayed until
2165                when the mouse button is released (useful for large meshes)
2166            c : (color)
2167                color of the box cutter widget
2168            alpha : (float)
2169                transparency of the cut-off part of the input mesh
2170        
2171        Examples:
2172            - [slice_plane3.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slice_plane3.py)
2173        """
2174        super().__init__()
2175
2176        self.mesh = mesh
2177        self.remnant = Mesh()
2178        self.remnant.name = mesh.name + "Remnant"
2179        self.remnant.pickable(False)
2180
2181        self._alpha = alpha
2182        self._keypress_id = None
2183
2184        self._implicit_func = vtki.new("Plane")
2185
2186        poly = mesh.dataset
2187        self.clipper = vtki.new("ClipPolyData")
2188        self.clipper.GenerateClipScalarsOff()
2189        self.clipper.SetInputData(poly)
2190        self.clipper.SetClipFunction(self._implicit_func)
2191        self.clipper.SetInsideOut(invert)
2192        self.clipper.GenerateClippedOutputOn()
2193        self.clipper.Update()
2194
2195        self.widget = vtki.new("ImplicitPlaneWidget")
2196
2197        # self.widget.KeyPressActivationOff()
2198        # self.widget.SetKeyPressActivationValue('i')
2199
2200        self.widget.SetOriginTranslation(can_translate)
2201        self.widget.SetOutlineTranslation(can_translate)
2202        self.widget.SetScaleEnabled(can_scale)
2203
2204        self.widget.GetOutlineProperty().SetColor(get_color(c))
2205        self.widget.GetOutlineProperty().SetOpacity(0.25)
2206        self.widget.GetOutlineProperty().SetLineWidth(1)
2207        self.widget.GetOutlineProperty().LightingOff()
2208
2209        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
2210
2211        self.widget.SetTubing(0)
2212        self.widget.SetDrawPlane(bool(alpha))
2213        self.widget.GetPlaneProperty().LightingOff()
2214        self.widget.GetPlaneProperty().SetOpacity(alpha)
2215        self.widget.GetSelectedPlaneProperty().SetColor(get_color("red5"))
2216        self.widget.GetSelectedPlaneProperty().LightingOff()
2217
2218        self.widget.SetPlaceFactor(1.0 + padding)
2219        self.widget.SetInputData(poly)
2220        self.widget.PlaceWidget()
2221        if delayed:
2222            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2223        else:
2224            self.widget.AddObserver("InteractionEvent", self._select_polygons)
2225
2226        if len(origin) == 3:
2227            self.widget.SetOrigin(origin)
2228        else:
2229            self.widget.SetOrigin(mesh.center_of_mass())
2230
2231        if len(normal) == 3:
2232            self.widget.SetNormal(normal)
2233        else:
2234            self.widget.SetNormal((1, 0, 0))
2235
2236    @property
2237    def origin(self):
2238        """Get the origin of the plane."""
2239        return np.array(self.widget.GetOrigin())
2240
2241    @origin.setter
2242    def origin(self, value):
2243        """Set the origin of the plane."""
2244        self.widget.SetOrigin(value)
2245
2246    @property
2247    def normal(self):
2248        """Get the normal of the plane."""
2249        return np.array(self.widget.GetNormal())
2250
2251    @normal.setter
2252    def normal(self, value):
2253        """Set the normal of the plane."""
2254        self.widget.SetNormal(value)
2255
2256    def _select_polygons(self, vobj, event) -> None:
2257        vobj.GetPlane(self._implicit_func)
2258
2259    def _keypress(self, vobj, event):
2260        if vobj.GetKeySym() == "r":  # reset planes
2261            self.widget.GetPlane(self._implicit_func)
2262            self.widget.PlaceWidget()
2263            self.widget.GetInteractor().Render()
2264        elif vobj.GetKeySym() == "u":  # invert cut
2265            self.invert()
2266            self.widget.GetInteractor().Render()
2267        elif vobj.GetKeySym() == "x":  # set normal along x
2268            self.widget.SetNormal((1, 0, 0))
2269            self.widget.GetPlane(self._implicit_func)
2270            self.widget.PlaceWidget()
2271            self.widget.GetInteractor().Render()
2272        elif vobj.GetKeySym() == "y":  # set normal along y
2273            self.widget.SetNormal((0, 1, 0))
2274            self.widget.GetPlane(self._implicit_func)
2275            self.widget.PlaceWidget()
2276            self.widget.GetInteractor().Render()
2277        elif vobj.GetKeySym() == "z":  # set normal along z
2278            self.widget.SetNormal((0, 0, 1))
2279            self.widget.GetPlane(self._implicit_func)
2280            self.widget.PlaceWidget()
2281            self.widget.GetInteractor().Render()
2282        elif vobj.GetKeySym() == "s":  # Ctrl+s to save mesh
2283            if self.widget.GetInteractor():
2284                if self.widget.GetInteractor().GetControlKey():
2285                    self.mesh.write("vedo_clipped.vtk")
2286                    printc(":save: saved mesh to vedo_clipped.vtk")

Create a box widget to cut away parts of a Mesh.

PlaneCutter( mesh, invert=False, can_translate=True, can_scale=True, origin=(), normal=(), padding=0.05, delayed=False, c=(0.25, 0.25, 0.25), alpha=0.05)
2132    def __init__(
2133        self,
2134        mesh,
2135        invert=False,
2136        can_translate=True,
2137        can_scale=True,
2138        origin=(),
2139        normal=(),
2140        padding=0.05,
2141        delayed=False,
2142        c=(0.25, 0.25, 0.25),
2143        alpha=0.05,
2144    ):
2145        """
2146        Create a box widget to cut away parts of a `Mesh`.
2147
2148        Arguments:
2149            mesh : (Mesh)
2150                the input mesh
2151            invert : (bool)
2152                invert the clipping plane
2153            can_translate : (bool)
2154                enable translation of the widget
2155            can_scale : (bool)
2156                enable scaling of the widget
2157            origin : (list)
2158                origin of the plane
2159            normal : (list)
2160                normal to the plane
2161            padding : (float)
2162                padding around the input mesh
2163            delayed : (bool)
2164                if True the callback is delayed until
2165                when the mouse button is released (useful for large meshes)
2166            c : (color)
2167                color of the box cutter widget
2168            alpha : (float)
2169                transparency of the cut-off part of the input mesh
2170        
2171        Examples:
2172            - [slice_plane3.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slice_plane3.py)
2173        """
2174        super().__init__()
2175
2176        self.mesh = mesh
2177        self.remnant = Mesh()
2178        self.remnant.name = mesh.name + "Remnant"
2179        self.remnant.pickable(False)
2180
2181        self._alpha = alpha
2182        self._keypress_id = None
2183
2184        self._implicit_func = vtki.new("Plane")
2185
2186        poly = mesh.dataset
2187        self.clipper = vtki.new("ClipPolyData")
2188        self.clipper.GenerateClipScalarsOff()
2189        self.clipper.SetInputData(poly)
2190        self.clipper.SetClipFunction(self._implicit_func)
2191        self.clipper.SetInsideOut(invert)
2192        self.clipper.GenerateClippedOutputOn()
2193        self.clipper.Update()
2194
2195        self.widget = vtki.new("ImplicitPlaneWidget")
2196
2197        # self.widget.KeyPressActivationOff()
2198        # self.widget.SetKeyPressActivationValue('i')
2199
2200        self.widget.SetOriginTranslation(can_translate)
2201        self.widget.SetOutlineTranslation(can_translate)
2202        self.widget.SetScaleEnabled(can_scale)
2203
2204        self.widget.GetOutlineProperty().SetColor(get_color(c))
2205        self.widget.GetOutlineProperty().SetOpacity(0.25)
2206        self.widget.GetOutlineProperty().SetLineWidth(1)
2207        self.widget.GetOutlineProperty().LightingOff()
2208
2209        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
2210
2211        self.widget.SetTubing(0)
2212        self.widget.SetDrawPlane(bool(alpha))
2213        self.widget.GetPlaneProperty().LightingOff()
2214        self.widget.GetPlaneProperty().SetOpacity(alpha)
2215        self.widget.GetSelectedPlaneProperty().SetColor(get_color("red5"))
2216        self.widget.GetSelectedPlaneProperty().LightingOff()
2217
2218        self.widget.SetPlaceFactor(1.0 + padding)
2219        self.widget.SetInputData(poly)
2220        self.widget.PlaceWidget()
2221        if delayed:
2222            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2223        else:
2224            self.widget.AddObserver("InteractionEvent", self._select_polygons)
2225
2226        if len(origin) == 3:
2227            self.widget.SetOrigin(origin)
2228        else:
2229            self.widget.SetOrigin(mesh.center_of_mass())
2230
2231        if len(normal) == 3:
2232            self.widget.SetNormal(normal)
2233        else:
2234            self.widget.SetNormal((1, 0, 0))

Create a box widget to cut away parts of a Mesh.

Arguments:
  • mesh : (Mesh) the input mesh
  • invert : (bool) invert the clipping plane
  • can_translate : (bool) enable translation of the widget
  • can_scale : (bool) enable scaling of the widget
  • origin : (list) origin of the plane
  • normal : (list) normal to the plane
  • padding : (float) padding around the input mesh
  • delayed : (bool) if True the callback is delayed until when the mouse button is released (useful for large meshes)
  • c : (color) color of the box cutter widget
  • alpha : (float) transparency of the cut-off part of the input mesh
Examples:
origin
2236    @property
2237    def origin(self):
2238        """Get the origin of the plane."""
2239        return np.array(self.widget.GetOrigin())

Get the origin of the plane.

normal
2246    @property
2247    def normal(self):
2248        """Get the normal of the plane."""
2249        return np.array(self.widget.GetNormal())

Get the normal of the plane.

class SphereCutter(vtkmodules.vtkInteractionWidgets.vtkSphereWidget, BaseCutter):
2405class SphereCutter(vtki.vtkSphereWidget, BaseCutter):
2406    """
2407    Create a box widget to cut away parts of a Mesh.
2408    """
2409
2410    def __init__(
2411        self,
2412        mesh,
2413        invert=False,
2414        can_translate=True,
2415        can_scale=True,
2416        origin=(),
2417        radius=0,
2418        res=60,
2419        delayed=False,
2420        c="white",
2421        alpha=0.05,
2422    ):
2423        """
2424        Create a box widget to cut away parts of a Mesh.
2425
2426        Arguments:
2427            mesh : Mesh
2428                the input mesh
2429            invert : bool
2430                invert the clipping
2431            can_translate : bool
2432                enable translation of the widget
2433            can_scale : bool
2434                enable scaling of the widget
2435            origin : list
2436                initial position of the sphere widget
2437            radius : float
2438                initial radius of the sphere widget
2439            res : int
2440                resolution of the sphere widget
2441            delayed : bool
2442                if True the cutting callback is delayed until
2443                when the mouse button is released (useful for large meshes)
2444            c : color
2445                color of the box cutter widget
2446            alpha : float
2447                transparency of the cut-off part of the input mesh
2448        """
2449        super().__init__()
2450
2451        self.mesh = mesh
2452        self.remnant = Mesh()
2453        self.remnant.name = mesh.name + "Remnant"
2454        self.remnant.pickable(False)
2455
2456        self._alpha = alpha
2457        self._keypress_id = None
2458
2459        self._implicit_func = vtki.new("Sphere")
2460
2461        if len(origin) == 3:
2462            self._implicit_func.SetCenter(origin)
2463        else:
2464            origin = mesh.center_of_mass()
2465            self._implicit_func.SetCenter(origin)
2466
2467        if radius > 0:
2468            self._implicit_func.SetRadius(radius)
2469        else:
2470            radius = mesh.average_size() * 2
2471            self._implicit_func.SetRadius(radius)
2472
2473        poly = mesh.dataset
2474        self.clipper = vtki.new("ClipPolyData")
2475        self.clipper.GenerateClipScalarsOff()
2476        self.clipper.SetInputData(poly)
2477        self.clipper.SetClipFunction(self._implicit_func)
2478        self.clipper.SetInsideOut(not invert)
2479        self.clipper.GenerateClippedOutputOn()
2480        self.clipper.Update()
2481
2482        self.widget = vtki.vtkSphereWidget()
2483
2484        self.widget.SetThetaResolution(res * 2)
2485        self.widget.SetPhiResolution(res)
2486        self.widget.SetRadius(radius)
2487        self.widget.SetCenter(origin)
2488        self.widget.SetRepresentation(2)
2489        self.widget.HandleVisibilityOff()
2490
2491        self.widget.SetTranslation(can_translate)
2492        self.widget.SetScale(can_scale)
2493
2494        self.widget.HandleVisibilityOff()
2495        self.widget.GetSphereProperty().SetColor(get_color(c))
2496        self.widget.GetSphereProperty().SetOpacity(0.2)
2497        self.widget.GetSelectedSphereProperty().SetColor(get_color("red5"))
2498        self.widget.GetSelectedSphereProperty().SetOpacity(0.2)
2499
2500        self.widget.SetPlaceFactor(1.0)
2501        self.widget.SetInputData(poly)
2502        self.widget.PlaceWidget()
2503        if delayed:
2504            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2505        else:
2506            self.widget.AddObserver("InteractionEvent", self._select_polygons)
2507
2508    def _select_polygons(self, vobj, event):
2509        vobj.GetSphere(self._implicit_func)
2510
2511    def _keypress(self, vobj, event):
2512        if vobj.GetKeySym() == "r":  # reset planes
2513            self._implicit_func.SetBounds(self._init_bounds)
2514            self.widget.GetPlanes(self._implicit_func)
2515            self.widget.PlaceWidget()
2516            self.widget.GetInteractor().Render()
2517        elif vobj.GetKeySym() == "u":
2518            self.invert()
2519            self.widget.GetInteractor().Render()
2520        elif vobj.GetKeySym() == "s":  # Ctrl+s to save mesh
2521            if self.widget.GetInteractor():
2522                if self.widget.GetInteractor().GetControlKey():
2523                    self.mesh.write("vedo_clipped.vtk")
2524                    printc(":save: saved mesh to vedo_clipped.vtk")
2525
2526    @property
2527    def center(self):
2528        """Get the center of the sphere."""
2529        return np.array(self.widget.GetCenter())
2530
2531    @center.setter
2532    def center(self, value):
2533        """Set the center of the sphere."""
2534        self.widget.SetCenter(value)
2535
2536    @property
2537    def radius(self):
2538        """Get the radius of the sphere."""
2539        return self.widget.GetRadius()
2540
2541    @radius.setter
2542    def radius(self, value):
2543        """Set the radius of the sphere."""
2544        self.widget.SetRadius(value)

Create a box widget to cut away parts of a Mesh.

SphereCutter( mesh, invert=False, can_translate=True, can_scale=True, origin=(), radius=0, res=60, delayed=False, c='white', alpha=0.05)
2410    def __init__(
2411        self,
2412        mesh,
2413        invert=False,
2414        can_translate=True,
2415        can_scale=True,
2416        origin=(),
2417        radius=0,
2418        res=60,
2419        delayed=False,
2420        c="white",
2421        alpha=0.05,
2422    ):
2423        """
2424        Create a box widget to cut away parts of a Mesh.
2425
2426        Arguments:
2427            mesh : Mesh
2428                the input mesh
2429            invert : bool
2430                invert the clipping
2431            can_translate : bool
2432                enable translation of the widget
2433            can_scale : bool
2434                enable scaling of the widget
2435            origin : list
2436                initial position of the sphere widget
2437            radius : float
2438                initial radius of the sphere widget
2439            res : int
2440                resolution of the sphere widget
2441            delayed : bool
2442                if True the cutting callback is delayed until
2443                when the mouse button is released (useful for large meshes)
2444            c : color
2445                color of the box cutter widget
2446            alpha : float
2447                transparency of the cut-off part of the input mesh
2448        """
2449        super().__init__()
2450
2451        self.mesh = mesh
2452        self.remnant = Mesh()
2453        self.remnant.name = mesh.name + "Remnant"
2454        self.remnant.pickable(False)
2455
2456        self._alpha = alpha
2457        self._keypress_id = None
2458
2459        self._implicit_func = vtki.new("Sphere")
2460
2461        if len(origin) == 3:
2462            self._implicit_func.SetCenter(origin)
2463        else:
2464            origin = mesh.center_of_mass()
2465            self._implicit_func.SetCenter(origin)
2466
2467        if radius > 0:
2468            self._implicit_func.SetRadius(radius)
2469        else:
2470            radius = mesh.average_size() * 2
2471            self._implicit_func.SetRadius(radius)
2472
2473        poly = mesh.dataset
2474        self.clipper = vtki.new("ClipPolyData")
2475        self.clipper.GenerateClipScalarsOff()
2476        self.clipper.SetInputData(poly)
2477        self.clipper.SetClipFunction(self._implicit_func)
2478        self.clipper.SetInsideOut(not invert)
2479        self.clipper.GenerateClippedOutputOn()
2480        self.clipper.Update()
2481
2482        self.widget = vtki.vtkSphereWidget()
2483
2484        self.widget.SetThetaResolution(res * 2)
2485        self.widget.SetPhiResolution(res)
2486        self.widget.SetRadius(radius)
2487        self.widget.SetCenter(origin)
2488        self.widget.SetRepresentation(2)
2489        self.widget.HandleVisibilityOff()
2490
2491        self.widget.SetTranslation(can_translate)
2492        self.widget.SetScale(can_scale)
2493
2494        self.widget.HandleVisibilityOff()
2495        self.widget.GetSphereProperty().SetColor(get_color(c))
2496        self.widget.GetSphereProperty().SetOpacity(0.2)
2497        self.widget.GetSelectedSphereProperty().SetColor(get_color("red5"))
2498        self.widget.GetSelectedSphereProperty().SetOpacity(0.2)
2499
2500        self.widget.SetPlaceFactor(1.0)
2501        self.widget.SetInputData(poly)
2502        self.widget.PlaceWidget()
2503        if delayed:
2504            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2505        else:
2506            self.widget.AddObserver("InteractionEvent", self._select_polygons)

Create a box widget to cut away parts of a Mesh.

Arguments:
  • mesh : Mesh the input mesh
  • invert : bool invert the clipping
  • can_translate : bool enable translation of the widget
  • can_scale : bool enable scaling of the widget
  • origin : list initial position of the sphere widget
  • radius : float initial radius of the sphere widget
  • res : int resolution of the sphere widget
  • delayed : bool if True the cutting callback is delayed until when the mouse button is released (useful for large meshes)
  • c : color color of the box cutter widget
  • alpha : float transparency of the cut-off part of the input mesh
center
2526    @property
2527    def center(self):
2528        """Get the center of the sphere."""
2529        return np.array(self.widget.GetCenter())

Get the center of the sphere.

radius
2536    @property
2537    def radius(self):
2538        """Get the radius of the sphere."""
2539        return self.widget.GetRadius()

Get the radius of the sphere.