vedo.addons

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

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

A 2D scalar bar for the specified object.

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

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

Create a 3D scalar bar for the specified object.

Input obj input can be:

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

Add a slider which can call an external custom function.

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

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

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

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

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

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

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

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

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

Add an inset icon mesh into the renderer.

Icon(mesh, pos=3, size=0.08)
2709    def __init__(self, mesh, pos=3, size=0.08):
2710        """
2711        Arguments:
2712            pos : (list, int)
2713                icon position in the range [1-4] indicating one of the 4 corners,
2714                or it can be a tuple (x,y) as a fraction of the renderer size.
2715            size : (float)
2716                size of the icon space as fraction of the window size.
2717
2718        Examples:
2719            - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py)
2720        """
2721        super().__init__()
2722
2723        try:
2724            self.SetOrientationMarker(mesh.actor)
2725        except AttributeError:
2726            self.SetOrientationMarker(mesh)
2727
2728        if utils.is_sequence(pos):
2729            self.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size)
2730        else:
2731            if pos < 2:
2732                self.SetViewport(0, 1 - 2 * size, size * 2, 1)
2733            elif pos == 2:
2734                self.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1)
2735            elif pos == 3:
2736                self.SetViewport(0, 0, size * 2, size * 2)
2737            elif pos == 4:
2738                self.SetViewport(1 - 2 * size, 0, 1, size * 2)
Arguments:
  • pos : (list, int) icon position in the range [1-4] indicating one of the 4 corners, or it can be a tuple (x,y) as a fraction of the renderer size.
  • size : (float) size of the icon space as fraction of the window size.
Examples:
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):
1147def Light(pos, focal_point=(0, 0, 0), angle=180, c=None, intensity=1):
1148    """
1149    Generate a source of light placed at `pos` and directed to `focal point`.
1150    Returns a `vtkLight` object.
1151
1152    Arguments:
1153        focal_point : (list)
1154            focal point, if a `vedo` object is passed then will grab its position.
1155        angle : (float)
1156            aperture angle of the light source, in degrees
1157        c : (color)
1158            set the light color
1159        intensity : (float)
1160            intensity value between 0 and 1.
1161
1162    Check also:
1163        `plotter.Plotter.remove_lights()`
1164
1165    Examples:
1166        - [light_sources.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/light_sources.py)
1167
1168            ![](https://vedo.embl.es/images/basic/lights.png)
1169    """
1170    if c is None:
1171        try:
1172            c = pos.color()
1173        except AttributeError:
1174            c = "white"
1175
1176    try:
1177        pos = pos.pos()
1178    except AttributeError:
1179        pass
1180
1181    try:
1182        focal_point = focal_point.pos()
1183    except AttributeError:
1184        pass
1185
1186    light = vtki.vtkLight()
1187    light.SetLightTypeToSceneLight()
1188    light.SetPosition(pos)
1189    light.SetConeAngle(angle)
1190    light.SetFocalPoint(focal_point)
1191    light.SetIntensity(intensity)
1192    light.SetColor(get_color(c))
1193    return light

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

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

plotter.Plotter.remove_lights()

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

Add a line around the renderer subwindow.

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

Add a line around the renderer subwindow.

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

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

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

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

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

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

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

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

title

read-write, Calls GetTitle/SetTitle

def color(self, c) -> Self:
3162    def color(self, c) -> Self:
3163        """Assign a new color."""
3164        c = get_color(c)
3165        self.GetTitleTextProperty().SetColor(c)
3166        self.GetLabelTextProperty().SetColor(c)
3167        self.GetProperty().SetColor(c)
3168        return self

Assign a new color.

def off(self) -> None:
3170    def off(self) -> None:
3171        """Switch off the ruler completely."""
3172        self.renderer.RemoveObserver(self.cid)
3173        self.renderer.RemoveActor(self)

Switch off the ruler completely.

def set_points(self, p0, p1) -> Self:
3175    def set_points(self, p0, p1) -> Self:
3176        """Set new values for the ruler start and end points."""
3177        self.p0 = np.asarray(p0)
3178        self.p1 = np.asarray(p1)
3179        self._update_viz(0, 0)
3180        return self

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

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

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

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

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

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

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

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

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

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

def on(self) -> Self:
3242    def on(self) -> Self:
3243        """Switch tool on."""
3244        self.cid = self.plotter.add_callback("click", self._onclick)
3245        self.VisibilityOn()
3246        self.plotter.render()
3247        return self

Switch tool on.

def off(self) -> None:
3249    def off(self) -> None:
3250        """Switch tool off."""
3251        self.plotter.remove_callback(self.cid)
3252        self.VisibilityOff()
3253        self.ruler.off()
3254        self.plotter.render()

Switch tool off.

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:
representation

write-only, Calls SetRepresentation

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

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

Arguments:
  • p1 : (list) first point 3D coordinates.
  • p2 : (list) the vertex point.
  • p3 : (list) the last point defining the angle.
  • font : (str) Font face. Check available fonts here.
  • arc_size : (float) dimension of the arc wrt the smallest axis.
  • s : (float) size of the text.
  • italic : (float, bool) italic text.
  • rotation : (float) rotation of text in degrees.
  • prefix : (str) append this string to the numeric value of the angle.
  • lc : (list) color of the goniometer lines.
  • c : (str) color of the goniometer angle filling. Set alpha=0 to remove it.
  • alpha : (float) transparency level.
  • lw : (float) line width.
  • precision : (int) number of significant digits.
Examples:
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

read-write, Calls GetUseBounds/SetUseBounds

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):
2611class ProgressBarWidget(vtki.vtkActor2D):
2612    """
2613    Add a progress bar in the rendering window.
2614    """
2615
2616    def __init__(self, n=None, c="blue5", alpha=0.8, lw=10, autohide=True):
2617        """
2618        Add a progress bar window.
2619
2620        Arguments:
2621            n : (int)
2622                number of iterations.
2623                If None, you need to call `update(fraction)` manually.
2624            c : (color)
2625                color of the line.
2626            alpha : (float)
2627                opacity of the line.
2628            lw : (int)
2629                line width in pixels.
2630            autohide : (bool)
2631                if True, hide the progress bar when completed.
2632        """
2633        self.n = 0
2634        self.iterations = n
2635        self.autohide = autohide
2636
2637        ppoints = vtki.vtkPoints()  # Generate the line
2638        psqr = [[0, 0, 0], [1, 0, 0]]
2639        for i, pt in enumerate(psqr):
2640            ppoints.InsertPoint(i, *pt)
2641        lines = vtki.vtkCellArray()
2642        lines.InsertNextCell(len(psqr))
2643        for i in range(len(psqr)):
2644            lines.InsertCellPoint(i)
2645        pd = vtki.vtkPolyData()
2646        pd.SetPoints(ppoints)
2647        pd.SetLines(lines)
2648        self.dataset = pd
2649
2650        mapper = vtki.new("PolyDataMapper2D")
2651        mapper.SetInputData(pd)
2652        cs = vtki.vtkCoordinate()
2653        cs.SetCoordinateSystemToNormalizedViewport()
2654        mapper.SetTransformCoordinate(cs)
2655
2656        super().__init__()
2657
2658        self.SetMapper(mapper)
2659        self.GetProperty().SetOpacity(alpha)
2660        self.GetProperty().SetColor(get_color(c))
2661        self.GetProperty().SetLineWidth(lw * 2)
2662
2663    def lw(self, value: int) -> Self:
2664        """Set width."""
2665        self.GetProperty().SetLineWidth(value * 2)
2666        return self
2667
2668    def c(self, color) -> Self:
2669        """Set color."""
2670        c = get_color(color)
2671        self.GetProperty().SetColor(c)
2672        return self
2673
2674    def alpha(self, value) -> Self:
2675        """Set opacity."""
2676        self.GetProperty().SetOpacity(value)
2677        return self
2678
2679    def update(self, fraction=None) -> Self:
2680        """Update progress bar to fraction of the window width."""
2681        if fraction is None:
2682            if self.iterations is None:
2683                vedo.printc("Error in ProgressBarWindow: must specify iterations", c='r')
2684                return self
2685            self.n += 1
2686            fraction = self.n / self.iterations
2687
2688        if fraction >= 1 and self.autohide:
2689            fraction = 0
2690
2691        psqr = [[0, 0, 0], [fraction, 0, 0]]
2692        vpts = utils.numpy2vtk(psqr, dtype=np.float32)
2693        self.dataset.GetPoints().SetData(vpts)
2694        return self
2695
2696    def reset(self):
2697        """Reset progress bar."""
2698        self.n = 0
2699        self.update(0)
2700        return self

Add a progress bar in the rendering window.

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

Add a progress bar window.

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

Set width.

def c(self, color) -> Self:
2668    def c(self, color) -> Self:
2669        """Set color."""
2670        c = get_color(color)
2671        self.GetProperty().SetColor(c)
2672        return self

Set color.

def alpha(self, value) -> Self:
2674    def alpha(self, value) -> Self:
2675        """Set opacity."""
2676        self.GetProperty().SetOpacity(value)
2677        return self

Set opacity.

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

Update progress bar to fraction of the window width.

def reset(self):
2696    def reset(self):
2697        """Reset progress bar."""
2698        self.n = 0
2699        self.update(0)
2700        return self

Reset progress bar.

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

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

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

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

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

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

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

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

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

Get the origin of the plane.

normal
2240    @property
2241    def normal(self):
2242        """Get the normal of the plane."""
2243        return np.array(self.widget.GetNormal())

Get the normal of the plane.

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

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

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

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

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

Get the center of the sphere.

radius
2530    @property
2531    def radius(self):
2532        """Get the radius of the sphere."""
2533        return self.widget.GetRadius()

Get the radius of the sphere.