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
3573    rx, ry, rz = np.ceil(drange / drangemax * number_of_divisions).astype(int)
3574
3575    if xtitle:
3576        xticks_float, xticks_str = utils.make_ticks(x0, x1, rx, x_values_and_labels, digits)
3577        xticks_float = xticks_float * dx
3578        if x_inverted:
3579            xticks_float = np.flip(-(xticks_float - xticks_float[-1]))
3580            xticks_str = list(reversed(xticks_str))
3581            xticks_str[-1] = ""
3582            xhighlight_zero = False
3583    if ytitle:
3584        yticks_float, yticks_str = utils.make_ticks(y0, y1, ry, y_values_and_labels, digits)
3585        yticks_float = yticks_float * dy
3586        if y_inverted:
3587            yticks_float = np.flip(-(yticks_float - yticks_float[-1]))
3588            yticks_str = list(reversed(yticks_str))
3589            yticks_str[-1] = ""
3590            yhighlight_zero = False
3591    if ztitle:
3592        zticks_float, zticks_str = utils.make_ticks(z0, z1, rz, z_values_and_labels, digits)
3593        zticks_float = zticks_float * dz
3594        if z_inverted:
3595            zticks_float = np.flip(-(zticks_float - zticks_float[-1]))
3596            zticks_str = list(reversed(zticks_str))
3597            zticks_str[-1] = ""
3598            zhighlight_zero = False
3599
3600    ################################################ axes lines
3601    lines = []
3602    if xtitle:
3603        axlinex = shapes.Line([0,0,0], [dx,0,0], c=xline_color, lw=axes_linewidth)
3604        axlinex.shift([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3605        axlinex.name = 'xAxis'
3606        lines.append(axlinex)
3607    if ytitle:
3608        axliney = shapes.Line([0,0,0], [0,dy,0], c=yline_color, lw=axes_linewidth)
3609        axliney.shift([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3610        axliney.name = 'yAxis'
3611        lines.append(axliney)
3612    if ztitle:
3613        axlinez = shapes.Line([0,0,0], [0,0,dz], c=zline_color, lw=axes_linewidth)
3614        axlinez.shift([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3615        axlinez.name = 'zAxis'
3616        lines.append(axlinez)
3617
3618    ################################################ grid planes
3619    # all shapes have a name to keep track of them in the Assembly
3620    # if user wants to unpack it
3621    grids = []
3622    if xygrid and xtitle and ytitle:
3623        if not xygrid_transparent:
3624            gxy = shapes.Grid(s=(xticks_float, yticks_float))
3625            gxy.alpha(xyalpha).c(xyplane_color).lw(0)
3626            if xyshift: gxy.shift([0,0,xyshift*dz])
3627            elif tol:   gxy.shift([0,0,-tol*gscale])
3628            gxy.name = "xyGrid"
3629            grids.append(gxy)
3630        if grid_linewidth:
3631            gxy_lines = shapes.Grid(s=(xticks_float, yticks_float))
3632            gxy_lines.c(xyplane_color).lw(grid_linewidth).alpha(xyalpha)
3633            if xyshift: gxy_lines.shift([0,0,xyshift*dz])
3634            elif tol:   gxy_lines.shift([0,0,-tol*gscale])
3635            gxy_lines.name = "xyGridLines"
3636            grids.append(gxy_lines)
3637
3638    if yzgrid and ytitle and ztitle:
3639        if not yzgrid_transparent:
3640            gyz = shapes.Grid(s=(zticks_float, yticks_float))
3641            gyz.alpha(yzalpha).c(yzplane_color).lw(0).rotate_y(-90)
3642            if yzshift: gyz.shift([yzshift*dx,0,0])
3643            elif tol:   gyz.shift([-tol*gscale,0,0])
3644            gyz.name = "yzGrid"
3645            grids.append(gyz)
3646        if grid_linewidth:
3647            gyz_lines = shapes.Grid(s=(zticks_float, yticks_float))
3648            gyz_lines.c(yzplane_color).lw(grid_linewidth).alpha(yzalpha).rotate_y(-90)
3649            if yzshift: gyz_lines.shift([yzshift*dx,0,0])
3650            elif tol:   gyz_lines.shift([-tol*gscale,0,0])
3651            gyz_lines.name = "yzGridLines"
3652            grids.append(gyz_lines)
3653
3654    if zxgrid and ztitle and xtitle:
3655        if not zxgrid_transparent:
3656            gzx = shapes.Grid(s=(xticks_float, zticks_float))
3657            gzx.alpha(zxalpha).c(zxplane_color).lw(0).rotate_x(90)
3658            if zxshift: gzx.shift([0,zxshift*dy,0])
3659            elif tol:   gzx.shift([0,-tol*gscale,0])
3660            gzx.name = "zxGrid"
3661            grids.append(gzx)
3662        if grid_linewidth:
3663            gzx_lines = shapes.Grid(s=(xticks_float, zticks_float))
3664            gzx_lines.c(zxplane_color).lw(grid_linewidth).alpha(zxalpha).rotate_x(90)
3665            if zxshift: gzx_lines.shift([0,zxshift*dy,0])
3666            elif tol:   gzx_lines.shift([0,-tol*gscale,0])
3667            gzx_lines.name = "zxGridLines"
3668            grids.append(gzx_lines)
3669
3670    # Grid2
3671    if xygrid2 and xtitle and ytitle:
3672        if not xygrid2_transparent:
3673            gxy2 = shapes.Grid(s=(xticks_float, yticks_float)).z(dz)
3674            gxy2.alpha(xyalpha).c(xyplane_color).lw(0)
3675            gxy2.shift([0, tol * gscale, 0])
3676            gxy2.name = "xyGrid2"
3677            grids.append(gxy2)
3678        if grid_linewidth:
3679            gxy2_lines = shapes.Grid(s=(xticks_float, yticks_float)).z(dz)
3680            gxy2_lines.c(xyplane_color).lw(grid_linewidth).alpha(xyalpha)
3681            gxy2_lines.shift([0, tol * gscale, 0])
3682            gxy2_lines.name = "xygrid2Lines"
3683            grids.append(gxy2_lines)
3684
3685    if yzgrid2 and ytitle and ztitle:
3686        if not yzgrid2_transparent:
3687            gyz2 = shapes.Grid(s=(zticks_float, yticks_float))
3688            gyz2.alpha(yzalpha).c(yzplane_color).lw(0)
3689            gyz2.rotate_y(-90).x(dx).shift([tol * gscale, 0, 0])
3690            gyz2.name = "yzGrid2"
3691            grids.append(gyz2)
3692        if grid_linewidth:
3693            gyz2_lines = shapes.Grid(s=(zticks_float, yticks_float))
3694            gyz2_lines.c(yzplane_color).lw(grid_linewidth).alpha(yzalpha)
3695            gyz2_lines.rotate_y(-90).x(dx).shift([tol * gscale, 0, 0])
3696            gyz2_lines.name = "yzGrid2Lines"
3697            grids.append(gyz2_lines)
3698
3699    if zxgrid2 and ztitle and xtitle:
3700        if not zxgrid2_transparent:
3701            gzx2 = shapes.Grid(s=(xticks_float, zticks_float))
3702            gzx2.alpha(zxalpha).c(zxplane_color).lw(0)
3703            gzx2.rotate_x(90).y(dy).shift([0, tol * gscale, 0])
3704            gzx2.name = "zxGrid2"
3705            grids.append(gzx2)
3706        if grid_linewidth:
3707            gzx2_lines = shapes.Grid(s=(xticks_float, zticks_float))
3708            gzx2_lines.c(zxplane_color).lw(grid_linewidth).alpha(zxalpha)
3709            gzx2_lines.rotate_x(90).y(dy).shift([0, tol * gscale, 0])
3710            gzx2_lines.name = "zxGrid2Lines"
3711            grids.append(gzx2_lines)
3712
3713    ################################################ frame lines
3714    framelines = []
3715    if xyframe_line and xtitle and ytitle:
3716        if not xyframe_color:
3717            xyframe_color = xygrid_color
3718        frxy = shapes.Line(
3719            [[0, dy, 0], [dx, dy, 0], [dx, 0, 0], [0, 0, 0], [0, dy, 0]],
3720            c=xyframe_color,
3721            lw=xyframe_line,
3722        )
3723        frxy.shift([0, 0, xyshift * dz])
3724        frxy.name = "xyFrameLine"
3725        framelines.append(frxy)
3726    if yzframe_line and ytitle and ztitle:
3727        if not yzframe_color:
3728            yzframe_color = yzgrid_color
3729        fryz = shapes.Line(
3730            [[0, 0, dz], [0, dy, dz], [0, dy, 0], [0, 0, 0], [0, 0, dz]],
3731            c=yzframe_color,
3732            lw=yzframe_line,
3733        )
3734        fryz.shift([yzshift * dx, 0, 0])
3735        fryz.name = "yzFrameLine"
3736        framelines.append(fryz)
3737    if zxframe_line and ztitle and xtitle:
3738        if not zxframe_color:
3739            zxframe_color = zxgrid_color
3740        frzx = shapes.Line(
3741            [[0, 0, dz], [dx, 0, dz], [dx, 0, 0], [0, 0, 0], [0, 0, dz]],
3742            c=zxframe_color,
3743            lw=zxframe_line,
3744        )
3745        frzx.shift([0, zxshift * dy, 0])
3746        frzx.name = "zxFrameLine"
3747        framelines.append(frzx)
3748
3749    ################################################ zero lines highlights
3750    highlights = []
3751    if xygrid and xtitle and ytitle:
3752        if xhighlight_zero and min_bns[0] <= 0 and max_bns[1] > 0:
3753            xhl = -min_bns[0]
3754            hxy = shapes.Line([xhl, 0, 0], [xhl, dy, 0], c=xhighlight_zero_color)
3755            hxy.alpha(np.sqrt(xyalpha)).lw(grid_linewidth * 2)
3756            hxy.shift([0, 0, xyshift * dz])
3757            hxy.name = "xyHighlightZero"
3758            highlights.append(hxy)
3759        if yhighlight_zero and min_bns[2] <= 0 and max_bns[3] > 0:
3760            yhl = -min_bns[2]
3761            hyx = shapes.Line([0, yhl, 0], [dx, yhl, 0], c=yhighlight_zero_color)
3762            hyx.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2)
3763            hyx.shift([0, 0, xyshift * dz])
3764            hyx.name = "yxHighlightZero"
3765            highlights.append(hyx)
3766
3767    if yzgrid and ytitle and ztitle:
3768        if yhighlight_zero and min_bns[2] <= 0 and max_bns[3] > 0:
3769            yhl = -min_bns[2]
3770            hyz = shapes.Line([0, yhl, 0], [0, yhl, dz], c=yhighlight_zero_color)
3771            hyz.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2)
3772            hyz.shift([yzshift * dx, 0, 0])
3773            hyz.name = "yzHighlightZero"
3774            highlights.append(hyz)
3775        if zhighlight_zero and min_bns[4] <= 0 and max_bns[5] > 0:
3776            zhl = -min_bns[4]
3777            hzy = shapes.Line([0, 0, zhl], [0, dy, zhl], c=zhighlight_zero_color)
3778            hzy.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2)
3779            hzy.shift([yzshift * dx, 0, 0])
3780            hzy.name = "zyHighlightZero"
3781            highlights.append(hzy)
3782
3783    if zxgrid and ztitle and xtitle:
3784        if zhighlight_zero and min_bns[4] <= 0 and max_bns[5] > 0:
3785            zhl = -min_bns[4]
3786            hzx = shapes.Line([0, 0, zhl], [dx, 0, zhl], c=zhighlight_zero_color)
3787            hzx.alpha(np.sqrt(zxalpha)).lw(grid_linewidth * 2)
3788            hzx.shift([0, zxshift * dy, 0])
3789            hzx.name = "zxHighlightZero"
3790            highlights.append(hzx)
3791        if xhighlight_zero and min_bns[0] <= 0 and max_bns[1] > 0:
3792            xhl = -min_bns[0]
3793            hxz = shapes.Line([xhl, 0, 0], [xhl, 0, dz], c=xhighlight_zero_color)
3794            hxz.alpha(np.sqrt(zxalpha)).lw(grid_linewidth * 2)
3795            hxz.shift([0, zxshift * dy, 0])
3796            hxz.name = "xzHighlightZero"
3797            highlights.append(hxz)
3798
3799    ################################################ arrow cone
3800    cones = []
3801
3802    if tip_size:
3803
3804        if xtitle:
3805            if x_inverted:
3806                cx = shapes.Cone(
3807                    r=tip_size,
3808                    height=tip_size * 2,
3809                    axis=(-1, 0, 0),
3810                    c=xline_color,
3811                    res=12,
3812                )
3813            else:
3814                cx = shapes.Cone(
3815                    (dx, 0, 0),
3816                    r=tip_size,
3817                    height=tip_size * 2,
3818                    axis=(1, 0, 0),
3819                    c=xline_color,
3820                    res=12,
3821                )
3822            T = LinearTransform()
3823            T.translate(
3824                [
3825                    0,
3826                    zxshift * dy + xshift_along_y * dy,
3827                    xyshift * dz + xshift_along_z * dz,
3828                ]
3829            )
3830            cx.apply_transform(T)
3831            cx.name = "xTipCone"
3832            cones.append(cx)
3833
3834        if ytitle:
3835            if y_inverted:
3836                cy = shapes.Cone(
3837                    r=tip_size,
3838                    height=tip_size * 2,
3839                    axis=(0, -1, 0),
3840                    c=yline_color,
3841                    res=12,
3842                )
3843            else:
3844                cy = shapes.Cone(
3845                    (0, dy, 0),
3846                    r=tip_size,
3847                    height=tip_size * 2,
3848                    axis=(0, 1, 0),
3849                    c=yline_color,
3850                    res=12,
3851                )
3852            T = LinearTransform()
3853            T.translate(
3854                [
3855                    yzshift * dx + yshift_along_x * dx,
3856                    0,
3857                    xyshift * dz + yshift_along_z * dz,
3858                ]
3859            )
3860            cy.apply_transform(T)
3861            cy.name = "yTipCone"
3862            cones.append(cy)
3863
3864        if ztitle:
3865            if z_inverted:
3866                cz = shapes.Cone(
3867                    r=tip_size,
3868                    height=tip_size * 2,
3869                    axis=(0, 0, -1),
3870                    c=zline_color,
3871                    res=12,
3872                )
3873            else:
3874                cz = shapes.Cone(
3875                    (0, 0, dz),
3876                    r=tip_size,
3877                    height=tip_size * 2,
3878                    axis=(0, 0, 1),
3879                    c=zline_color,
3880                    res=12,
3881                )
3882            T = LinearTransform()
3883            T.translate(
3884                [
3885                    yzshift * dx + zshift_along_x * dx,
3886                    zxshift * dy + zshift_along_y * dy,
3887                    0,
3888                ]
3889            )
3890            cz.apply_transform(T)
3891            cz.name = "zTipCone"
3892            cones.append(cz)
3893
3894    ################################################################# MAJOR ticks
3895    majorticks, minorticks = [], []
3896    xticks, yticks, zticks = [], [], []
3897    if show_ticks:
3898        if xtitle:
3899            tick_thickness = xtick_thickness * gscale / 2
3900            tick_length = xtick_length * gscale / 2
3901            for i in range(1, len(xticks_float) - 1):
3902                v1 = (xticks_float[i] - tick_thickness, -tick_length, 0)
3903                v2 = (xticks_float[i] + tick_thickness, tick_length, 0)
3904                xticks.append(shapes.Rectangle(v1, v2))
3905            if len(xticks) > 1:
3906                xmajticks = merge(xticks).c(xlabel_color)
3907                T = LinearTransform()
3908                T.rotate_x(xaxis_rotation)
3909                T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3910                xmajticks.apply_transform(T)
3911                xmajticks.name = "xMajorTicks"
3912                majorticks.append(xmajticks)
3913        if ytitle:
3914            tick_thickness = ytick_thickness * gscale / 2
3915            tick_length = ytick_length * gscale / 2
3916            for i in range(1, len(yticks_float) - 1):
3917                v1 = (-tick_length, yticks_float[i] - tick_thickness, 0)
3918                v2 = (tick_length, yticks_float[i] + tick_thickness, 0)
3919                yticks.append(shapes.Rectangle(v1, v2))
3920            if len(yticks) > 1:
3921                ymajticks = merge(yticks).c(ylabel_color)
3922                T = LinearTransform()
3923                T.rotate_y(yaxis_rotation)
3924                T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3925                ymajticks.apply_transform(T)
3926                ymajticks.name = "yMajorTicks"
3927                majorticks.append(ymajticks)
3928        if ztitle:
3929            tick_thickness = ztick_thickness * gscale / 2
3930            tick_length = ztick_length * gscale / 2.85
3931            for i in range(1, len(zticks_float) - 1):
3932                v1 = (zticks_float[i] - tick_thickness, -tick_length, 0)
3933                v2 = (zticks_float[i] + tick_thickness, tick_length, 0)
3934                zticks.append(shapes.Rectangle(v1, v2))
3935            if len(zticks) > 1:
3936                zmajticks = merge(zticks).c(zlabel_color)
3937                T = LinearTransform()
3938                T.rotate_y(-90).rotate_z(-45 + zaxis_rotation)
3939                T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3940                zmajticks.apply_transform(T)
3941                zmajticks.name = "zMajorTicks"
3942                majorticks.append(zmajticks)
3943
3944        ############################################################# MINOR ticks
3945        if xtitle and xminor_ticks and len(xticks) > 1:
3946            tick_thickness = xtick_thickness * gscale / 4
3947            tick_length = xtick_length * gscale / 4
3948            xminor_ticks += 1
3949            ticks = []
3950            for i in range(1, len(xticks)):
3951                t0, t1 = xticks[i - 1].pos(), xticks[i].pos()
3952                dt = t1 - t0
3953                for j in range(1, xminor_ticks):
3954                    mt = dt * (j / xminor_ticks) + t0
3955                    v1 = (mt[0] - tick_thickness, -tick_length, 0)
3956                    v2 = (mt[0] + tick_thickness, tick_length, 0)
3957                    ticks.append(shapes.Rectangle(v1, v2))
3958
3959            # finish off the fist lower range from start to first tick
3960            t0, t1 = xticks[0].pos(), xticks[1].pos()
3961            dt = t1 - t0
3962            for j in range(1, xminor_ticks):
3963                mt = t0 - dt * (j / xminor_ticks)
3964                if mt[0] < 0:
3965                    break
3966                v1 = (mt[0] - tick_thickness, -tick_length, 0)
3967                v2 = (mt[0] + tick_thickness, tick_length, 0)
3968                ticks.append(shapes.Rectangle(v1, v2))
3969
3970            # finish off the last upper range from last tick to end
3971            t0, t1 = xticks[-2].pos(), xticks[-1].pos()
3972            dt = t1 - t0
3973            for j in range(1, xminor_ticks):
3974                mt = t1 + dt * (j / xminor_ticks)
3975                if mt[0] > dx:
3976                    break
3977                v1 = (mt[0] - tick_thickness, -tick_length, 0)
3978                v2 = (mt[0] + tick_thickness, tick_length, 0)
3979                ticks.append(shapes.Rectangle(v1, v2))
3980
3981            if ticks:
3982                xminticks = merge(ticks).c(xlabel_color)
3983                T = LinearTransform()
3984                T.rotate_x(xaxis_rotation)
3985                T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3986                xminticks.apply_transform(T)
3987                xminticks.name = "xMinorTicks"
3988                minorticks.append(xminticks)
3989
3990        if ytitle and yminor_ticks and len(yticks) > 1:  ##### y
3991            tick_thickness = ytick_thickness * gscale / 4
3992            tick_length = ytick_length * gscale / 4
3993            yminor_ticks += 1
3994            ticks = []
3995            for i in range(1, len(yticks)):
3996                t0, t1 = yticks[i - 1].pos(), yticks[i].pos()
3997                dt = t1 - t0
3998                for j in range(1, yminor_ticks):
3999                    mt = dt * (j / yminor_ticks) + t0
4000                    v1 = (-tick_length, mt[1] - tick_thickness, 0)
4001                    v2 = (tick_length, mt[1] + tick_thickness, 0)
4002                    ticks.append(shapes.Rectangle(v1, v2))
4003
4004            # finish off the fist lower range from start to first tick
4005            t0, t1 = yticks[0].pos(), yticks[1].pos()
4006            dt = t1 - t0
4007            for j in range(1, yminor_ticks):
4008                mt = t0 - dt * (j / yminor_ticks)
4009                if mt[1] < 0:
4010                    break
4011                v1 = (-tick_length, mt[1] - tick_thickness, 0)
4012                v2 = (tick_length, mt[1] + tick_thickness, 0)
4013                ticks.append(shapes.Rectangle(v1, v2))
4014
4015            # finish off the last upper range from last tick to end
4016            t0, t1 = yticks[-2].pos(), yticks[-1].pos()
4017            dt = t1 - t0
4018            for j in range(1, yminor_ticks):
4019                mt = t1 + dt * (j / yminor_ticks)
4020                if mt[1] > dy:
4021                    break
4022                v1 = (-tick_length, mt[1] - tick_thickness, 0)
4023                v2 = (tick_length, mt[1] + tick_thickness, 0)
4024                ticks.append(shapes.Rectangle(v1, v2))
4025
4026            if ticks:
4027                yminticks = merge(ticks).c(ylabel_color)
4028                T = LinearTransform()
4029                T.rotate_y(yaxis_rotation)
4030                T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
4031                yminticks.apply_transform(T)
4032                yminticks.name = "yMinorTicks"
4033                minorticks.append(yminticks)
4034
4035        if ztitle and zminor_ticks and len(zticks) > 1:  ##### z
4036            tick_thickness = ztick_thickness * gscale / 4
4037            tick_length = ztick_length * gscale / 5
4038            zminor_ticks += 1
4039            ticks = []
4040            for i in range(1, len(zticks)):
4041                t0, t1 = zticks[i - 1].pos(), zticks[i].pos()
4042                dt = t1 - t0
4043                for j in range(1, zminor_ticks):
4044                    mt = dt * (j / zminor_ticks) + t0
4045                    v1 = (mt[0] - tick_thickness, -tick_length, 0)
4046                    v2 = (mt[0] + tick_thickness, tick_length, 0)
4047                    ticks.append(shapes.Rectangle(v1, v2))
4048
4049            # finish off the fist lower range from start to first tick
4050            t0, t1 = zticks[0].pos(), zticks[1].pos()
4051            dt = t1 - t0
4052            for j in range(1, zminor_ticks):
4053                mt = t0 - dt * (j / zminor_ticks)
4054                if mt[0] < 0:
4055                    break
4056                v1 = (mt[0] - tick_thickness, -tick_length, 0)
4057                v2 = (mt[0] + tick_thickness, tick_length, 0)
4058                ticks.append(shapes.Rectangle(v1, v2))
4059
4060            # finish off the last upper range from last tick to end
4061            t0, t1 = zticks[-2].pos(), zticks[-1].pos()
4062            dt = t1 - t0
4063            for j in range(1, zminor_ticks):
4064                mt = t1 + dt * (j / zminor_ticks)
4065                if mt[0] > dz:
4066                    break
4067                v1 = (mt[0] - tick_thickness, -tick_length, 0)
4068                v2 = (mt[0] + tick_thickness, tick_length, 0)
4069                ticks.append(shapes.Rectangle(v1, v2))
4070
4071            if ticks:
4072                zminticks = merge(ticks).c(zlabel_color)
4073                T = LinearTransform()
4074                T.rotate_y(-90).rotate_z(-45 + zaxis_rotation)
4075                T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
4076                zminticks.apply_transform(T)
4077                zminticks.name = "zMinorTicks"
4078                minorticks.append(zminticks)
4079
4080    ################################################ axes NUMERIC text labels
4081    labels = []
4082    xlab, ylab, zlab = None, None, None
4083
4084    if xlabel_size and xtitle:
4085
4086        xRot, yRot, zRot = 0, 0, 0
4087        if utils.is_sequence(xlabel_rotation):  # unpck 3 rotations
4088            zRot, xRot, yRot = xlabel_rotation
4089        else:
4090            zRot = xlabel_rotation
4091        if zRot < 0:  # deal with negative angles
4092            zRot += 360
4093
4094        jus = "center-top"
4095        if zRot:
4096            if zRot >  24: jus = "top-right"
4097            if zRot >  67: jus = "center-right"
4098            if zRot > 112: jus = "right-bottom"
4099            if zRot > 157: jus = "center-bottom"
4100            if zRot > 202: jus = "bottom-left"
4101            if zRot > 247: jus = "center-left"
4102            if zRot > 292: jus = "top-left"
4103            if zRot > 337: jus = "top-center"
4104        if xlabel_justify is not None:
4105            jus = xlabel_justify
4106
4107        for i in range(1, len(xticks_str)):
4108            t = xticks_str[i]
4109            if not t:
4110                continue
4111            if utils.is_sequence(xlabel_offset):
4112                xoffs, yoffs, zoffs = xlabel_offset
4113            else:
4114                xoffs, yoffs, zoffs = 0, xlabel_offset, 0
4115
4116            xlab = shapes.Text3D(
4117                t, s=xlabel_size * text_scale * gscale, font=label_font, justify=jus
4118            )
4119            tb = xlab.ybounds()  # must be ybounds: height of char
4120
4121            v = (xticks_float[i], 0, 0)
4122            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0])
4123
4124            T = LinearTransform()
4125            T.rotate_x(xaxis_rotation).rotate_y(yRot).rotate_x(xRot).rotate_z(zRot)
4126            T.translate(v + offs)
4127            T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
4128            xlab.apply_transform(T)
4129
4130            xlab.use_bounds(x_use_bounds)
4131
4132            xlab.c(xlabel_color)
4133            if xlabel_backface_color is None:
4134                bfc = 1 - np.array(get_color(xlabel_color))
4135                xlab.backcolor(bfc)
4136            xlab.name = f"xNumericLabel {i}"
4137            labels.append(xlab)
4138
4139    if ylabel_size and ytitle:
4140
4141        xRot, yRot, zRot = 0, 0, 0
4142        if utils.is_sequence(ylabel_rotation):  # unpck 3 rotations
4143            zRot, yRot, xRot = ylabel_rotation
4144        else:
4145            zRot = ylabel_rotation
4146        if zRot < 0:
4147            zRot += 360  # deal with negative angles
4148
4149        jus = "center-right"
4150        if zRot:
4151            if zRot >  24: jus = "bottom-right"
4152            if zRot >  67: jus = "center-bottom"
4153            if zRot > 112: jus = "left-bottom"
4154            if zRot > 157: jus = "center-left"
4155            if zRot > 202: jus = "top-left"
4156            if zRot > 247: jus = "center-top"
4157            if zRot > 292: jus = "top-right"
4158            if zRot > 337: jus = "right-center"
4159        if ylabel_justify is not None:
4160            jus = ylabel_justify
4161
4162        for i in range(1, len(yticks_str)):
4163            t = yticks_str[i]
4164            if not t:
4165                continue
4166            if utils.is_sequence(ylabel_offset):
4167                xoffs, yoffs, zoffs = ylabel_offset
4168            else:
4169                xoffs, yoffs, zoffs = ylabel_offset, 0, 0
4170            ylab = shapes.Text3D(
4171                t, s=ylabel_size * text_scale * gscale, font=label_font, justify=jus
4172            )
4173            tb = ylab.ybounds()  # must be ybounds: height of char
4174            v = (0, yticks_float[i], 0)
4175            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0])
4176
4177            T = LinearTransform()
4178            T.rotate_y(yaxis_rotation).rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
4179            T.translate(v + offs)
4180            T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
4181            ylab.apply_transform(T)
4182
4183            ylab.use_bounds(y_use_bounds)
4184
4185            ylab.c(ylabel_color)
4186            if ylabel_backface_color is None:
4187                bfc = 1 - np.array(get_color(ylabel_color))
4188                ylab.backcolor(bfc)
4189            ylab.name = f"yNumericLabel {i}"
4190            labels.append(ylab)
4191
4192    if zlabel_size and ztitle:
4193
4194        xRot, yRot, zRot = 0, 0, 0
4195        if utils.is_sequence(zlabel_rotation):  # unpck 3 rotations
4196            xRot, yRot, zRot = zlabel_rotation
4197        else:
4198            xRot = zlabel_rotation
4199        if xRot < 0: xRot += 360 # deal with negative angles
4200
4201        jus = "center-right"
4202        if xRot:
4203            if xRot >  24: jus = "bottom-right"
4204            if xRot >  67: jus = "center-bottom"
4205            if xRot > 112: jus = "left-bottom"
4206            if xRot > 157: jus = "center-left"
4207            if xRot > 202: jus = "top-left"
4208            if xRot > 247: jus = "center-top"
4209            if xRot > 292: jus = "top-right"
4210            if xRot > 337: jus = "right-center"
4211        if zlabel_justify is not None:
4212            jus = zlabel_justify
4213
4214        for i in range(1, len(zticks_str)):
4215            t = zticks_str[i]
4216            if not t:
4217                continue
4218            if utils.is_sequence(zlabel_offset):
4219                xoffs, yoffs, zoffs = zlabel_offset
4220            else:
4221                xoffs, yoffs, zoffs = zlabel_offset, zlabel_offset, 0
4222            zlab = shapes.Text3D(t, s=zlabel_size*text_scale*gscale, font=label_font, justify=jus)
4223            tb = zlab.ybounds()  # must be ybounds: height of char
4224
4225            v = (0, 0, zticks_float[i])
4226            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0]) / 1.5
4227            angle = np.arctan2(dy, dx) * 57.3
4228
4229            T = LinearTransform()
4230            T.rotate_x(90 + zRot).rotate_y(-xRot).rotate_z(angle + yRot + zaxis_rotation)
4231            T.translate(v + offs)
4232            T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
4233            zlab.apply_transform(T)
4234
4235            zlab.use_bounds(z_use_bounds)
4236
4237            zlab.c(zlabel_color)
4238            if zlabel_backface_color is None:
4239                bfc = 1 - np.array(get_color(zlabel_color))
4240                zlab.backcolor(bfc)
4241            zlab.name = f"zNumericLabel {i}"
4242            labels.append(zlab)
4243
4244    ################################################ axes titles
4245    titles = []
4246
4247    if xtitle:
4248        xRot, yRot, zRot = 0, 0, 0
4249        if utils.is_sequence(xtitle_rotation):  # unpack 3 rotations
4250            zRot, xRot, yRot = xtitle_rotation
4251        else:
4252            zRot = xtitle_rotation
4253        if zRot < 0:  # deal with negative angles
4254            zRot += 360
4255
4256        if utils.is_sequence(xtitle_offset):
4257            xoffs, yoffs, zoffs = xtitle_offset
4258        else:
4259            xoffs, yoffs, zoffs = 0, xtitle_offset, 0
4260
4261        if xtitle_justify is not None:
4262            jus = xtitle_justify
4263        else:
4264            # find best justfication for given rotation(s)
4265            jus = "right-top"
4266            if zRot:
4267                if zRot >  24: jus = "center-right"
4268                if zRot >  67: jus = "right-bottom"
4269                if zRot > 157: jus = "bottom-left"
4270                if zRot > 202: jus = "center-left"
4271                if zRot > 247: jus = "top-left"
4272                if zRot > 337: jus = "top-right"
4273
4274        xt = shapes.Text3D(
4275            xtitle,
4276            s=xtitle_size * text_scale * gscale,
4277            font=title_font,
4278            c=xtitle_color,
4279            justify=jus,
4280            depth=title_depth,
4281            italic=xtitle_italic,
4282        )
4283        if xtitle_backface_color is None:
4284            xtitle_backface_color = 1 - np.array(get_color(xtitle_color))
4285            xt.backcolor(xtitle_backface_color)
4286
4287        shift = 0
4288        if xlab:  # xlab is the last created numeric text label..
4289            lt0, lt1 = xlab.bounds()[2:4]
4290            shift = lt1 - lt0
4291
4292        T = LinearTransform()
4293        T.rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
4294        T.set_position(
4295            [(xoffs + xtitle_position) * dx,
4296            -(yoffs + xtick_length / 2) * dy - shift,
4297            zoffs * dz]
4298        )
4299        T.rotate_x(xaxis_rotation)
4300        T.translate([0, xshift_along_y * dy, xyshift * dz + xshift_along_z * dz])
4301        xt.apply_transform(T)
4302
4303        xt.use_bounds(x_use_bounds)
4304        if xtitle == " ":
4305            xt.use_bounds(False)
4306        xt.name = "xtitle"
4307        titles.append(xt)
4308        if xtitle_box:
4309            titles.append(xt.box(scale=1.1).use_bounds(x_use_bounds))
4310
4311    if ytitle:
4312        xRot, yRot, zRot = 0, 0, 0
4313        if utils.is_sequence(ytitle_rotation):  # unpck 3 rotations
4314            zRot, yRot, xRot = ytitle_rotation
4315        else:
4316            zRot = ytitle_rotation
4317            if len(ytitle) > 3:
4318                zRot += 90
4319                ytitle_position *= 0.975
4320        if zRot < 0:
4321            zRot += 360  # deal with negative angles
4322
4323        if utils.is_sequence(ytitle_offset):
4324            xoffs, yoffs, zoffs = ytitle_offset
4325        else:
4326            xoffs, yoffs, zoffs = ytitle_offset, 0, 0
4327
4328        if ytitle_justify is not None:
4329            jus = ytitle_justify
4330        else:
4331            jus = "center-right"
4332            if zRot:
4333                if zRot >  24: jus = "bottom-right"
4334                if zRot > 112: jus = "left-bottom"
4335                if zRot > 157: jus = "center-left"
4336                if zRot > 202: jus = "top-left"
4337                if zRot > 292: jus = "top-right"
4338                if zRot > 337: jus = "right-center"
4339
4340        yt = shapes.Text3D(
4341            ytitle,
4342            s=ytitle_size * text_scale * gscale,
4343            font=title_font,
4344            c=ytitle_color,
4345            justify=jus,
4346            depth=title_depth,
4347            italic=ytitle_italic,
4348        )
4349        if ytitle_backface_color is None:
4350            ytitle_backface_color = 1 - np.array(get_color(ytitle_color))
4351            yt.backcolor(ytitle_backface_color)
4352
4353        shift = 0
4354        if ylab:  # this is the last created num label..
4355            lt0, lt1 = ylab.bounds()[0:2]
4356            shift = lt1 - lt0
4357
4358        T = LinearTransform()
4359        T.rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
4360        T.set_position(
4361            [-(xoffs + ytick_length / 2) * dx - shift,
4362            (yoffs + ytitle_position) * dy,
4363            zoffs * dz]
4364        )
4365        T.rotate_y(yaxis_rotation)
4366        T.translate([yshift_along_x * dx, 0, xyshift * dz + yshift_along_z * dz])
4367        yt.apply_transform(T)
4368
4369        yt.use_bounds(y_use_bounds)
4370        if ytitle == " ":
4371            yt.use_bounds(False)
4372        yt.name = "ytitle"
4373        titles.append(yt)
4374        if ytitle_box:
4375            titles.append(yt.box(scale=1.1).use_bounds(y_use_bounds))
4376
4377    if ztitle:
4378        xRot, yRot, zRot = 0, 0, 0
4379        if utils.is_sequence(ztitle_rotation):  # unpck 3 rotations
4380            xRot, yRot, zRot = ztitle_rotation
4381        else:
4382            xRot = ztitle_rotation
4383            if len(ztitle) > 3:
4384                xRot += 90
4385                ztitle_position *= 0.975
4386        if xRot < 0:
4387            xRot += 360  # deal with negative angles
4388
4389        if ztitle_justify is not None:
4390            jus = ztitle_justify
4391        else:
4392            jus = "center-right"
4393            if xRot:
4394                if xRot >  24: jus = "bottom-right"
4395                if xRot > 112: jus = "left-bottom"
4396                if xRot > 157: jus = "center-left"
4397                if xRot > 202: jus = "top-left"
4398                if xRot > 292: jus = "top-right"
4399                if xRot > 337: jus = "right-center"
4400
4401        zt = shapes.Text3D(
4402            ztitle,
4403            s=ztitle_size * text_scale * gscale,
4404            font=title_font,
4405            c=ztitle_color,
4406            justify=jus,
4407            depth=title_depth,
4408            italic=ztitle_italic,
4409        )
4410        if ztitle_backface_color is None:
4411            ztitle_backface_color = 1 - np.array(get_color(ztitle_color))
4412            zt.backcolor(ztitle_backface_color)
4413
4414        angle = np.arctan2(dy, dx) * 57.3
4415        shift = 0
4416        if zlab:  # this is the last created one..
4417            lt0, lt1 = zlab.bounds()[0:2]
4418            shift = lt1 - lt0
4419
4420        T = LinearTransform()
4421        T.rotate_x(90 + zRot).rotate_y(-xRot).rotate_z(angle + yRot)
4422        T.set_position([
4423            -(ztitle_offset + ztick_length / 5) * dx - shift,
4424            -(ztitle_offset + ztick_length / 5) * dy - shift,
4425            ztitle_position * dz]
4426        )
4427        T.rotate_z(zaxis_rotation)
4428        T.translate([zshift_along_x * dx, zxshift * dy + zshift_along_y * dy, 0])
4429        zt.apply_transform(T)
4430
4431        zt.use_bounds(z_use_bounds)
4432        if ztitle == " ":
4433            zt.use_bounds(False)
4434        zt.name = "ztitle"
4435        titles.append(zt)
4436
4437    ################################################### header title
4438    if htitle:
4439        if htitle_font is None:
4440            htitle_font = title_font
4441        if htitle_color is None:
4442            htitle_color = xtitle_color
4443        htit = shapes.Text3D(
4444            htitle,
4445            s=htitle_size * gscale * text_scale,
4446            font=htitle_font,
4447            c=htitle_color,
4448            justify=htitle_justify,
4449            depth=title_depth,
4450            italic=htitle_italic,
4451        )
4452        if htitle_backface_color is None:
4453            htitle_backface_color = 1 - np.array(get_color(htitle_color))
4454            htit.backcolor(htitle_backface_color)
4455        htit.rotate_x(htitle_rotation)
4456        wpos = [htitle_offset[0]*dx, (1 + htitle_offset[1])*dy, htitle_offset[2]*dz]
4457        htit.shift(np.array(wpos) + [0, 0, xyshift*dz])
4458        htit.name = "htitle"
4459        titles.append(htit)
4460
4461    ######
4462    acts = titles + lines + labels + grids + framelines
4463    acts += highlights + majorticks + minorticks + cones
4464    orig = (min_bns[0], min_bns[2], min_bns[4])
4465    for a in acts:
4466        a.shift(orig)
4467        a.actor.PickableOff()
4468        a.properties.LightingOff()
4469    asse = Assembly(acts)
4470    asse.PickableOff()
4471    asse.name = "Axes"
4472    return asse
4473
4474
4475def add_global_axes(axtype=None, c=None, bounds=()) -> None:
4476    """
4477    Draw axes on scene. Available axes types are
4478
4479    Parameters
4480    ----------
4481    axtype : (int)
4482        - 0,  no axes,
4483        - 1,  draw three gray grid walls
4484        - 2,  show cartesian axes from (0,0,0)
4485        - 3,  show positive range of cartesian axes from (0,0,0)
4486        - 4,  show a triad at bottom left
4487        - 5,  show a cube at bottom left
4488        - 6,  mark the corners of the bounding box
4489        - 7,  draw a 3D ruler at each side of the cartesian axes
4490        - 8,  show the `vtkCubeAxesActor` object
4491        - 9,  show the bounding box outLine
4492        - 10, show three circles representing the maximum bounding box
4493        - 11, show a large grid on the x-y plane (use with zoom=8)
4494        - 12, show polar axes
4495        - 13, draw a simple ruler at the bottom of the window
4496        - 14, show the vtk default `vtkCameraOrientationWidget` object
4497
4498    Axis type-1 can be fully customized by passing a dictionary `axes=dict()`,
4499    see `vedo.Axes` for the complete list of options.
4500
4501    Example
4502    -------
4503        .. code-block:: python
4504
4505            from vedo import Box, show
4506            b = Box(pos=(0, 0, 0), length=80, width=90, height=70).alpha(0.1)
4507            show(
4508                b,
4509                axes={
4510                    "xtitle": "Some long variable [a.u.]",
4511                    "number_of_divisions": 4,
4512                    # ...
4513                },
4514            )
4515    """
4516    plt = vedo.plotter_instance
4517    if plt is None:
4518        return
4519
4520    if axtype is not None:
4521        plt.axes = axtype  # override
4522
4523    r = plt.renderers.index(plt.renderer)
4524
4525    if not plt.axes:
4526        return
4527
4528    if c is None:  # automatic black or white
4529        c = (0.9, 0.9, 0.9)
4530        if np.sum(plt.renderer.GetBackground()) > 1.5:
4531            c = (0.1, 0.1, 0.1)
4532    else:
4533        c = get_color(c)  # for speed
4534
4535    if not plt.renderer:
4536        return
4537
4538    if plt.axes_instances[r]:
4539        return
4540
4541    ############################################################
4542    # custom grid walls
4543    if plt.axes == 1 or plt.axes is True or isinstance(plt.axes, dict):
4544
4545        if len(bounds) == 6:
4546            bnds = bounds
4547            xrange = (bnds[0], bnds[1])
4548            yrange = (bnds[2], bnds[3])
4549            zrange = (bnds[4], bnds[5])
4550        else:
4551            xrange = None
4552            yrange = None
4553            zrange = None
4554
4555        if isinstance(plt.axes, dict):
4556            plt.axes.update({"use_global": True})
4557            # protect from invalid camelCase options from vedo<=2.3
4558            for k in plt.axes:
4559                if k.lower() != k:
4560                    return
4561            if "xrange" in plt.axes:
4562                xrange = plt.axes.pop("xrange")
4563            if "yrange" in plt.axes:
4564                yrange = plt.axes.pop("yrange")
4565            if "zrange" in plt.axes:
4566                zrange = plt.axes.pop("zrange")
4567            asse = Axes(**plt.axes, xrange=xrange, yrange=yrange, zrange=zrange)
4568        else:
4569            asse = Axes(xrange=xrange, yrange=yrange, zrange=zrange)
4570
4571        plt.add(asse)
4572        plt.axes_instances[r] = asse
4573
4574    elif plt.axes in (2, 3):
4575        x0, x1, y0, y1, z0, z1 = plt.renderer.ComputeVisiblePropBounds()
4576        xcol, ycol, zcol = "dr", "dg", "db"
4577        s = 1
4578        alpha = 1
4579        centered = False
4580        dx, dy, dz = x1 - x0, y1 - y0, z1 - z0
4581        aves = np.sqrt(dx * dx + dy * dy + dz * dz) / 2
4582        x0, x1 = min(x0, 0), max(x1, 0)
4583        y0, y1 = min(y0, 0), max(y1, 0)
4584        z0, z1 = min(z0, 0), max(z1, 0)
4585
4586        if plt.axes == 3:
4587            if x1 > 0:
4588                x0 = 0
4589            if y1 > 0:
4590                y0 = 0
4591            if z1 > 0:
4592                z0 = 0
4593
4594        dx, dy, dz = x1 - x0, y1 - y0, z1 - z0
4595        acts = []
4596        if x0 * x1 <= 0 or y0 * z1 <= 0 or z0 * z1 <= 0:  # some ranges contain origin
4597            zero = shapes.Sphere(r=aves / 120 * s, c="k", alpha=alpha, res=10)
4598            acts += [zero]
4599
4600        if dx > aves / 100:
4601            xl = shapes.Cylinder([[x0, 0, 0], [x1, 0, 0]], r=aves / 250 * s, c=xcol, alpha=alpha)
4602            xc = shapes.Cone(
4603                pos=[x1, 0, 0],
4604                c=xcol,
4605                alpha=alpha,
4606                r=aves / 100 * s,
4607                height=aves / 25 * s,
4608                axis=[1, 0, 0],
4609                res=10,
4610            )
4611            wpos = [x1, -aves / 25 * s, 0]  # aligned to arrow tip
4612            if centered:
4613                wpos = [(x0 + x1) / 2, -aves / 25 * s, 0]
4614            xt = shapes.Text3D("x", pos=wpos, s=aves / 40 * s, c=xcol)
4615            acts += [xl, xc, xt]
4616
4617        if dy > aves / 100:
4618            yl = shapes.Cylinder([[0, y0, 0], [0, y1, 0]], r=aves / 250 * s, c=ycol, alpha=alpha)
4619            yc = shapes.Cone(
4620                pos=[0, y1, 0],
4621                c=ycol,
4622                alpha=alpha,
4623                r=aves / 100 * s,
4624                height=aves / 25 * s,
4625                axis=[0, 1, 0],
4626                res=10,
4627            )
4628            wpos = [-aves / 40 * s, y1, 0]
4629            if centered:
4630                wpos = [-aves / 40 * s, (y0 + y1) / 2, 0]
4631            yt = shapes.Text3D("y", pos=(0, 0, 0), s=aves / 40 * s, c=ycol)
4632            yt.rotate_z(90)
4633            yt.pos(wpos)
4634            acts += [yl, yc, yt]
4635
4636        if dz > aves / 100:
4637            zl = shapes.Cylinder([[0, 0, z0], [0, 0, z1]], r=aves / 250 * s, c=zcol, alpha=alpha)
4638            zc = shapes.Cone(
4639                pos=[0, 0, z1],
4640                c=zcol,
4641                alpha=alpha,
4642                r=aves / 100 * s,
4643                height=aves / 25 * s,
4644                axis=[0, 0, 1],
4645                res=10,
4646            )
4647            wpos = [-aves / 50 * s, -aves / 50 * s, z1]
4648            if centered:
4649                wpos = [-aves / 50 * s, -aves / 50 * s, (z0 + z1) / 2]
4650            zt = shapes.Text3D("z", pos=(0, 0, 0), s=aves / 40 * s, c=zcol)
4651            zt.rotate_z(45)
4652            zt.rotate_x(90)
4653            zt.pos(wpos)
4654            acts += [zl, zc, zt]
4655        for a in acts:
4656            a.actor.PickableOff()
4657        asse = Assembly(acts)
4658        asse.actor.PickableOff()
4659        plt.add(asse)
4660        plt.axes_instances[r] = asse
4661
4662    elif plt.axes == 4:
4663        axact = vtki.vtkAxesActor()
4664        axact.SetShaftTypeToCylinder()
4665        axact.SetCylinderRadius(0.03)
4666        axact.SetXAxisLabelText("x")
4667        axact.SetYAxisLabelText("y")
4668        axact.SetZAxisLabelText("z")
4669        axact.GetXAxisShaftProperty().SetColor(1, 0, 0)
4670        axact.GetYAxisShaftProperty().SetColor(0, 1, 0)
4671        axact.GetZAxisShaftProperty().SetColor(0, 0, 1)
4672        axact.GetXAxisTipProperty().SetColor(1, 0, 0)
4673        axact.GetYAxisTipProperty().SetColor(0, 1, 0)
4674        axact.GetZAxisTipProperty().SetColor(0, 0, 1)
4675        bc = np.array(plt.renderer.GetBackground())
4676        if np.sum(bc) < 1.5:
4677            lc = (1, 1, 1)
4678        else:
4679            lc = (0, 0, 0)
4680        axact.GetXAxisCaptionActor2D().GetCaptionTextProperty().BoldOff()
4681        axact.GetYAxisCaptionActor2D().GetCaptionTextProperty().BoldOff()
4682        axact.GetZAxisCaptionActor2D().GetCaptionTextProperty().BoldOff()
4683        axact.GetXAxisCaptionActor2D().GetCaptionTextProperty().ItalicOff()
4684        axact.GetYAxisCaptionActor2D().GetCaptionTextProperty().ItalicOff()
4685        axact.GetZAxisCaptionActor2D().GetCaptionTextProperty().ItalicOff()
4686        axact.GetXAxisCaptionActor2D().GetCaptionTextProperty().ShadowOff()
4687        axact.GetYAxisCaptionActor2D().GetCaptionTextProperty().ShadowOff()
4688        axact.GetZAxisCaptionActor2D().GetCaptionTextProperty().ShadowOff()
4689        axact.GetXAxisCaptionActor2D().GetCaptionTextProperty().SetColor(lc)
4690        axact.GetYAxisCaptionActor2D().GetCaptionTextProperty().SetColor(lc)
4691        axact.GetZAxisCaptionActor2D().GetCaptionTextProperty().SetColor(lc)
4692        axact.PickableOff()
4693        icn = Icon(axact, size=0.1)
4694        plt.axes_instances[r] = icn
4695        icn.SetInteractor(plt.interactor)
4696        icn.EnabledOn()
4697        icn.InteractiveOff()
4698        plt.widgets.append(icn)
4699
4700    elif plt.axes == 5:
4701        axact = vtki.new("AnnotatedCubeActor")
4702        axact.GetCubeProperty().SetColor(get_color(settings.annotated_cube_color))
4703        axact.SetTextEdgesVisibility(0)
4704        axact.SetFaceTextScale(settings.annotated_cube_text_scale)
4705        axact.SetXPlusFaceText(settings.annotated_cube_texts[0])  # XPlus
4706        axact.SetXMinusFaceText(settings.annotated_cube_texts[1])  # XMinus
4707        axact.SetYPlusFaceText(settings.annotated_cube_texts[2])  # YPlus
4708        axact.SetYMinusFaceText(settings.annotated_cube_texts[3])  # YMinus
4709        axact.SetZPlusFaceText(settings.annotated_cube_texts[4])  # ZPlus
4710        axact.SetZMinusFaceText(settings.annotated_cube_texts[5])  # ZMinus
4711        axact.SetZFaceTextRotation(90)
4712
4713        if settings.annotated_cube_text_color is None:  # use default
4714            axact.GetXPlusFaceProperty().SetColor(get_color("r"))
4715            axact.GetXMinusFaceProperty().SetColor(get_color("dr"))
4716            axact.GetYPlusFaceProperty().SetColor(get_color("g"))
4717            axact.GetYMinusFaceProperty().SetColor(get_color("dg"))
4718            axact.GetZPlusFaceProperty().SetColor(get_color("b"))
4719            axact.GetZMinusFaceProperty().SetColor(get_color("db"))
4720        else:  # use single user color
4721            ac = get_color(settings.annotated_cube_text_color)
4722            axact.GetXPlusFaceProperty().SetColor(ac)
4723            axact.GetXMinusFaceProperty().SetColor(ac)
4724            axact.GetYPlusFaceProperty().SetColor(ac)
4725            axact.GetYMinusFaceProperty().SetColor(ac)
4726            axact.GetZPlusFaceProperty().SetColor(ac)
4727            axact.GetZMinusFaceProperty().SetColor(ac)
4728
4729        axact.PickableOff()
4730        icn = Icon(axact, size=0.06)
4731        plt.axes_instances[r] = icn
4732        icn.SetInteractor(plt.interactor)
4733        icn.EnabledOn()
4734        icn.InteractiveOff()
4735        plt.widgets.append(icn)
4736
4737    elif plt.axes == 6:
4738        ocf = vtki.new("OutlineCornerFilter")
4739        ocf.SetCornerFactor(0.1)
4740        largestact, sz = None, -1
4741        for a in plt.objects:
4742            try:
4743                if a.pickable():
4744                    b = a.bounds()
4745                    if b is None:
4746                        return
4747                    d = max(b[1] - b[0], b[3] - b[2], b[5] - b[4])
4748                    if sz < d:
4749                        largestact = a
4750                        sz = d
4751            except AttributeError:
4752                pass
4753
4754        try:
4755            ocf.SetInputData(largestact)
4756        except TypeError:
4757            try:
4758                ocf.SetInputData(largestact.dataset)
4759            except (TypeError, AttributeError):
4760                return
4761        ocf.Update()
4762
4763        oc_mapper = vtki.new("HierarchicalPolyDataMapper")
4764        oc_mapper.SetInputConnection(0, ocf.GetOutputPort(0))
4765        oc_actor = vtki.vtkActor()
4766        oc_actor.SetMapper(oc_mapper)
4767        bc = np.array(plt.renderer.GetBackground())
4768        if np.sum(bc) < 1.5:
4769            lc = (1, 1, 1)
4770        else:
4771            lc = (0, 0, 0)
4772        oc_actor.GetProperty().SetColor(lc)
4773        oc_actor.PickableOff()
4774        oc_actor.UseBoundsOn()
4775        plt.axes_instances[r] = oc_actor
4776        plt.add(oc_actor)
4777
4778    elif plt.axes == 7:
4779        vbb = compute_visible_bounds()[0]
4780        rulax = RulerAxes(vbb, c=c, xtitle="x - ", ytitle="y - ", ztitle="z - ")
4781        plt.axes_instances[r] = rulax
4782        if not rulax:
4783            return
4784        rulax.actor.UseBoundsOn()
4785        rulax.actor.PickableOff()
4786        plt.add(rulax)
4787
4788    elif plt.axes == 8:
4789        vbb = compute_visible_bounds()[0]
4790        ca = vtki.new("CubeAxesActor")
4791        ca.SetBounds(vbb)
4792        ca.SetCamera(plt.renderer.GetActiveCamera())
4793        ca.GetXAxesLinesProperty().SetColor(c)
4794        ca.GetYAxesLinesProperty().SetColor(c)
4795        ca.GetZAxesLinesProperty().SetColor(c)
4796        for i in range(3):
4797            ca.GetLabelTextProperty(i).SetColor(c)
4798            ca.GetTitleTextProperty(i).SetColor(c)
4799        ca.SetTitleOffset(5)
4800        ca.SetFlyMode(3)
4801        ca.SetXTitle("x")
4802        ca.SetYTitle("y")
4803        ca.SetZTitle("z")
4804        ca.PickableOff()
4805        ca.UseBoundsOff()
4806        plt.axes_instances[r] = ca
4807        plt.renderer.AddActor(ca)
4808
4809    elif plt.axes == 9:
4810        vbb = compute_visible_bounds()[0]
4811        src = vtki.new("CubeSource")
4812        src.SetXLength(vbb[1] - vbb[0])
4813        src.SetYLength(vbb[3] - vbb[2])
4814        src.SetZLength(vbb[5] - vbb[4])
4815        src.Update()
4816        ca = Mesh(src.GetOutput(), c, 0.5).wireframe(True)
4817        ca.pos((vbb[0] + vbb[1]) / 2, (vbb[3] + vbb[2]) / 2, (vbb[5] + vbb[4]) / 2)
4818        ca.actor.PickableOff()
4819        ca.actor.UseBoundsOff()
4820        plt.axes_instances[r] = ca
4821        plt.add(ca)
4822
4823    elif plt.axes == 10:
4824        vbb = compute_visible_bounds()[0]
4825        x0 = (vbb[0] + vbb[1]) / 2, (vbb[3] + vbb[2]) / 2, (vbb[5] + vbb[4]) / 2
4826        rx, ry, rz = (vbb[1] - vbb[0]) / 2, (vbb[3] - vbb[2]) / 2, (vbb[5] - vbb[4]) / 2
4827        rm = max(rx, ry, rz)
4828        xc = shapes.Disc(x0, r1=rm, r2=rm, c="lr", res=(1, 72))
4829        yc = shapes.Disc(x0, r1=rm, r2=rm, c="lg", res=(1, 72))
4830        yc.rotate_x(90)
4831        zc = shapes.Disc(x0, r1=rm, r2=rm, c="lb", res=(1, 72))
4832        yc.rotate_y(90)
4833        xc.clean().alpha(0.5).wireframe().linewidth(2).actor.PickableOff()
4834        yc.clean().alpha(0.5).wireframe().linewidth(2).actor.PickableOff()
4835        zc.clean().alpha(0.5).wireframe().linewidth(2).actor.PickableOff()
4836        ca = xc + yc + zc
4837        ca.PickableOff()
4838        ca.UseBoundsOn()
4839        plt.renderer.AddActor(ca)
4840        plt.axes_instances[r] = ca
4841
4842    elif plt.axes == 11:
4843        vbb, ss = compute_visible_bounds()[0:2]
4844        xpos, ypos = (vbb[1] + vbb[0]) / 2, (vbb[3] + vbb[2]) / 2
4845        gs = sum(ss) * 3
4846        gr = shapes.Grid((xpos, ypos, vbb[4]), s=(gs, gs), res=(11, 11), c=c, alpha=0.1)
4847        gr.lighting("off").actor.PickableOff()
4848        gr.actor.UseBoundsOff()
4849        plt.axes_instances[r] = gr
4850        plt.add(gr)
4851
4852    elif plt.axes == 12:
4853        polaxes = vtki.new("PolarAxesActor")
4854        vbb = compute_visible_bounds()[0]
4855
4856        polaxes.SetPolarAxisTitle("radial distance")
4857        polaxes.SetPole(0, 0, vbb[4])
4858        rd = max(abs(vbb[0]), abs(vbb[2]), abs(vbb[1]), abs(vbb[3]))
4859        polaxes.SetMaximumRadius(rd)
4860        polaxes.AutoSubdividePolarAxisOff()
4861        polaxes.SetNumberOfPolarAxisTicks(10)
4862        polaxes.SetCamera(plt.renderer.GetActiveCamera())
4863        polaxes.SetPolarLabelFormat("%6.1f")
4864        polaxes.PolarLabelVisibilityOff()  # due to bad overlap of labels
4865
4866        polaxes.GetPolarArcsProperty().SetColor(c)
4867        polaxes.GetPolarAxisProperty().SetColor(c)
4868        polaxes.GetPolarAxisTitleTextProperty().SetColor(c)
4869        polaxes.GetPolarAxisLabelTextProperty().SetColor(c)
4870        polaxes.GetLastRadialAxisTextProperty().SetColor(c)
4871        polaxes.GetSecondaryRadialAxesTextProperty().SetColor(c)
4872        polaxes.GetSecondaryRadialAxesProperty().SetColor(c)
4873        polaxes.GetSecondaryPolarArcsProperty().SetColor(c)
4874
4875        polaxes.SetMinimumAngle(0.0)
4876        polaxes.SetMaximumAngle(315.0)
4877        polaxes.SetNumberOfPolarAxisTicks(5)
4878        polaxes.UseBoundsOn()
4879        polaxes.PickableOff()
4880        plt.axes_instances[r] = polaxes
4881        plt.renderer.AddActor(polaxes)
4882
4883    elif plt.axes == 13:
4884        # draws a simple ruler at the bottom of the window
4885        ls = vtki.new("LegendScaleActor")
4886        ls.RightAxisVisibilityOff()
4887        ls.TopAxisVisibilityOff()
4888        ls.LeftAxisVisibilityOff()
4889        ls.LegendVisibilityOff()
4890        ls.SetBottomBorderOffset(50)
4891        ls.GetBottomAxis().SetNumberOfMinorTicks(1)
4892        ls.GetBottomAxis().SetFontFactor(1.1)
4893        ls.GetBottomAxis().GetProperty().SetColor(c)
4894        ls.GetBottomAxis().GetProperty().SetOpacity(1.0)
4895        ls.GetBottomAxis().GetProperty().SetLineWidth(2)
4896        ls.GetBottomAxis().GetLabelTextProperty().SetColor(c)
4897        ls.GetBottomAxis().GetLabelTextProperty().BoldOff()
4898        ls.GetBottomAxis().GetLabelTextProperty().ItalicOff()
4899        pr = ls.GetBottomAxis().GetLabelTextProperty()
4900        pr.SetFontFamily(vtki.VTK_FONT_FILE)
4901        pr.SetFontFile(utils.get_font_path(settings.default_font))
4902        ls.PickableOff()
4903        # if not plt.renderer.GetActiveCamera().GetParallelProjection():
4904        #     vedo.logger.warning("Axes type 13 should be used with parallel projection")
4905        plt.axes_instances[r] = ls
4906        plt.renderer.AddActor(ls)
4907
4908    elif plt.axes == 14:
4909        try:
4910            cow = vtki.new("CameraOrientationWidget")
4911            cow.SetParentRenderer(plt.renderer)
4912            cow.On()
4913            plt.axes_instances[r] = cow
4914        except ImportError:
4915            vedo.logger.warning("axes mode 14 is unavailable in this vtk version")
4916
4917    else:
4918        e = "Keyword axes type must be in range [0-13]."
4919        e += "Available axes types are:\n\n"
4920        e += "0 = no axes\n"
4921        e += "1 = draw three customizable gray grid walls\n"
4922        e += "2 = show cartesian axes from (0,0,0)\n"
4923        e += "3 = show positive range of cartesian axes from (0,0,0)\n"
4924        e += "4 = show a triad at bottom left\n"
4925        e += "5 = show a cube at bottom left\n"
4926        e += "6 = mark the corners of the bounding box\n"
4927        e += "7 = draw a 3D ruler at each side of the cartesian axes\n"
4928        e += "8 = show the vtkCubeAxesActor object\n"
4929        e += "9 = show the bounding box outline\n"
4930        e += "10 = show three circles representing the maximum bounding box\n"
4931        e += "11 = show a large grid on the x-y plane (use with zoom=8)\n"
4932        e += "12 = show polar axes\n"
4933        e += "13 = draw a simple ruler at the bottom of the window\n"
4934        e += "14 = show the CameraOrientationWidget object"
4935        vedo.logger.warning(e)
4936
4937    if not plt.axes_instances[r]:
4938        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:

Inherited Members
SliderWidget
on
off
toggle
add_observer
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:
Inherited Members
SliderWidget
on
off
toggle
add_observer
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
3574    rx, ry, rz = np.ceil(drange / drangemax * number_of_divisions).astype(int)
3575
3576    if xtitle:
3577        xticks_float, xticks_str = utils.make_ticks(x0, x1, rx, x_values_and_labels, digits)
3578        xticks_float = xticks_float * dx
3579        if x_inverted:
3580            xticks_float = np.flip(-(xticks_float - xticks_float[-1]))
3581            xticks_str = list(reversed(xticks_str))
3582            xticks_str[-1] = ""
3583            xhighlight_zero = False
3584    if ytitle:
3585        yticks_float, yticks_str = utils.make_ticks(y0, y1, ry, y_values_and_labels, digits)
3586        yticks_float = yticks_float * dy
3587        if y_inverted:
3588            yticks_float = np.flip(-(yticks_float - yticks_float[-1]))
3589            yticks_str = list(reversed(yticks_str))
3590            yticks_str[-1] = ""
3591            yhighlight_zero = False
3592    if ztitle:
3593        zticks_float, zticks_str = utils.make_ticks(z0, z1, rz, z_values_and_labels, digits)
3594        zticks_float = zticks_float * dz
3595        if z_inverted:
3596            zticks_float = np.flip(-(zticks_float - zticks_float[-1]))
3597            zticks_str = list(reversed(zticks_str))
3598            zticks_str[-1] = ""
3599            zhighlight_zero = False
3600
3601    ################################################ axes lines
3602    lines = []
3603    if xtitle:
3604        axlinex = shapes.Line([0,0,0], [dx,0,0], c=xline_color, lw=axes_linewidth)
3605        axlinex.shift([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3606        axlinex.name = 'xAxis'
3607        lines.append(axlinex)
3608    if ytitle:
3609        axliney = shapes.Line([0,0,0], [0,dy,0], c=yline_color, lw=axes_linewidth)
3610        axliney.shift([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3611        axliney.name = 'yAxis'
3612        lines.append(axliney)
3613    if ztitle:
3614        axlinez = shapes.Line([0,0,0], [0,0,dz], c=zline_color, lw=axes_linewidth)
3615        axlinez.shift([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3616        axlinez.name = 'zAxis'
3617        lines.append(axlinez)
3618
3619    ################################################ grid planes
3620    # all shapes have a name to keep track of them in the Assembly
3621    # if user wants to unpack it
3622    grids = []
3623    if xygrid and xtitle and ytitle:
3624        if not xygrid_transparent:
3625            gxy = shapes.Grid(s=(xticks_float, yticks_float))
3626            gxy.alpha(xyalpha).c(xyplane_color).lw(0)
3627            if xyshift: gxy.shift([0,0,xyshift*dz])
3628            elif tol:   gxy.shift([0,0,-tol*gscale])
3629            gxy.name = "xyGrid"
3630            grids.append(gxy)
3631        if grid_linewidth:
3632            gxy_lines = shapes.Grid(s=(xticks_float, yticks_float))
3633            gxy_lines.c(xyplane_color).lw(grid_linewidth).alpha(xyalpha)
3634            if xyshift: gxy_lines.shift([0,0,xyshift*dz])
3635            elif tol:   gxy_lines.shift([0,0,-tol*gscale])
3636            gxy_lines.name = "xyGridLines"
3637            grids.append(gxy_lines)
3638
3639    if yzgrid and ytitle and ztitle:
3640        if not yzgrid_transparent:
3641            gyz = shapes.Grid(s=(zticks_float, yticks_float))
3642            gyz.alpha(yzalpha).c(yzplane_color).lw(0).rotate_y(-90)
3643            if yzshift: gyz.shift([yzshift*dx,0,0])
3644            elif tol:   gyz.shift([-tol*gscale,0,0])
3645            gyz.name = "yzGrid"
3646            grids.append(gyz)
3647        if grid_linewidth:
3648            gyz_lines = shapes.Grid(s=(zticks_float, yticks_float))
3649            gyz_lines.c(yzplane_color).lw(grid_linewidth).alpha(yzalpha).rotate_y(-90)
3650            if yzshift: gyz_lines.shift([yzshift*dx,0,0])
3651            elif tol:   gyz_lines.shift([-tol*gscale,0,0])
3652            gyz_lines.name = "yzGridLines"
3653            grids.append(gyz_lines)
3654
3655    if zxgrid and ztitle and xtitle:
3656        if not zxgrid_transparent:
3657            gzx = shapes.Grid(s=(xticks_float, zticks_float))
3658            gzx.alpha(zxalpha).c(zxplane_color).lw(0).rotate_x(90)
3659            if zxshift: gzx.shift([0,zxshift*dy,0])
3660            elif tol:   gzx.shift([0,-tol*gscale,0])
3661            gzx.name = "zxGrid"
3662            grids.append(gzx)
3663        if grid_linewidth:
3664            gzx_lines = shapes.Grid(s=(xticks_float, zticks_float))
3665            gzx_lines.c(zxplane_color).lw(grid_linewidth).alpha(zxalpha).rotate_x(90)
3666            if zxshift: gzx_lines.shift([0,zxshift*dy,0])
3667            elif tol:   gzx_lines.shift([0,-tol*gscale,0])
3668            gzx_lines.name = "zxGridLines"
3669            grids.append(gzx_lines)
3670
3671    # Grid2
3672    if xygrid2 and xtitle and ytitle:
3673        if not xygrid2_transparent:
3674            gxy2 = shapes.Grid(s=(xticks_float, yticks_float)).z(dz)
3675            gxy2.alpha(xyalpha).c(xyplane_color).lw(0)
3676            gxy2.shift([0, tol * gscale, 0])
3677            gxy2.name = "xyGrid2"
3678            grids.append(gxy2)
3679        if grid_linewidth:
3680            gxy2_lines = shapes.Grid(s=(xticks_float, yticks_float)).z(dz)
3681            gxy2_lines.c(xyplane_color).lw(grid_linewidth).alpha(xyalpha)
3682            gxy2_lines.shift([0, tol * gscale, 0])
3683            gxy2_lines.name = "xygrid2Lines"
3684            grids.append(gxy2_lines)
3685
3686    if yzgrid2 and ytitle and ztitle:
3687        if not yzgrid2_transparent:
3688            gyz2 = shapes.Grid(s=(zticks_float, yticks_float))
3689            gyz2.alpha(yzalpha).c(yzplane_color).lw(0)
3690            gyz2.rotate_y(-90).x(dx).shift([tol * gscale, 0, 0])
3691            gyz2.name = "yzGrid2"
3692            grids.append(gyz2)
3693        if grid_linewidth:
3694            gyz2_lines = shapes.Grid(s=(zticks_float, yticks_float))
3695            gyz2_lines.c(yzplane_color).lw(grid_linewidth).alpha(yzalpha)
3696            gyz2_lines.rotate_y(-90).x(dx).shift([tol * gscale, 0, 0])
3697            gyz2_lines.name = "yzGrid2Lines"
3698            grids.append(gyz2_lines)
3699
3700    if zxgrid2 and ztitle and xtitle:
3701        if not zxgrid2_transparent:
3702            gzx2 = shapes.Grid(s=(xticks_float, zticks_float))
3703            gzx2.alpha(zxalpha).c(zxplane_color).lw(0)
3704            gzx2.rotate_x(90).y(dy).shift([0, tol * gscale, 0])
3705            gzx2.name = "zxGrid2"
3706            grids.append(gzx2)
3707        if grid_linewidth:
3708            gzx2_lines = shapes.Grid(s=(xticks_float, zticks_float))
3709            gzx2_lines.c(zxplane_color).lw(grid_linewidth).alpha(zxalpha)
3710            gzx2_lines.rotate_x(90).y(dy).shift([0, tol * gscale, 0])
3711            gzx2_lines.name = "zxGrid2Lines"
3712            grids.append(gzx2_lines)
3713
3714    ################################################ frame lines
3715    framelines = []
3716    if xyframe_line and xtitle and ytitle:
3717        if not xyframe_color:
3718            xyframe_color = xygrid_color
3719        frxy = shapes.Line(
3720            [[0, dy, 0], [dx, dy, 0], [dx, 0, 0], [0, 0, 0], [0, dy, 0]],
3721            c=xyframe_color,
3722            lw=xyframe_line,
3723        )
3724        frxy.shift([0, 0, xyshift * dz])
3725        frxy.name = "xyFrameLine"
3726        framelines.append(frxy)
3727    if yzframe_line and ytitle and ztitle:
3728        if not yzframe_color:
3729            yzframe_color = yzgrid_color
3730        fryz = shapes.Line(
3731            [[0, 0, dz], [0, dy, dz], [0, dy, 0], [0, 0, 0], [0, 0, dz]],
3732            c=yzframe_color,
3733            lw=yzframe_line,
3734        )
3735        fryz.shift([yzshift * dx, 0, 0])
3736        fryz.name = "yzFrameLine"
3737        framelines.append(fryz)
3738    if zxframe_line and ztitle and xtitle:
3739        if not zxframe_color:
3740            zxframe_color = zxgrid_color
3741        frzx = shapes.Line(
3742            [[0, 0, dz], [dx, 0, dz], [dx, 0, 0], [0, 0, 0], [0, 0, dz]],
3743            c=zxframe_color,
3744            lw=zxframe_line,
3745        )
3746        frzx.shift([0, zxshift * dy, 0])
3747        frzx.name = "zxFrameLine"
3748        framelines.append(frzx)
3749
3750    ################################################ zero lines highlights
3751    highlights = []
3752    if xygrid and xtitle and ytitle:
3753        if xhighlight_zero and min_bns[0] <= 0 and max_bns[1] > 0:
3754            xhl = -min_bns[0]
3755            hxy = shapes.Line([xhl, 0, 0], [xhl, dy, 0], c=xhighlight_zero_color)
3756            hxy.alpha(np.sqrt(xyalpha)).lw(grid_linewidth * 2)
3757            hxy.shift([0, 0, xyshift * dz])
3758            hxy.name = "xyHighlightZero"
3759            highlights.append(hxy)
3760        if yhighlight_zero and min_bns[2] <= 0 and max_bns[3] > 0:
3761            yhl = -min_bns[2]
3762            hyx = shapes.Line([0, yhl, 0], [dx, yhl, 0], c=yhighlight_zero_color)
3763            hyx.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2)
3764            hyx.shift([0, 0, xyshift * dz])
3765            hyx.name = "yxHighlightZero"
3766            highlights.append(hyx)
3767
3768    if yzgrid and ytitle and ztitle:
3769        if yhighlight_zero and min_bns[2] <= 0 and max_bns[3] > 0:
3770            yhl = -min_bns[2]
3771            hyz = shapes.Line([0, yhl, 0], [0, yhl, dz], c=yhighlight_zero_color)
3772            hyz.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2)
3773            hyz.shift([yzshift * dx, 0, 0])
3774            hyz.name = "yzHighlightZero"
3775            highlights.append(hyz)
3776        if zhighlight_zero and min_bns[4] <= 0 and max_bns[5] > 0:
3777            zhl = -min_bns[4]
3778            hzy = shapes.Line([0, 0, zhl], [0, dy, zhl], c=zhighlight_zero_color)
3779            hzy.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2)
3780            hzy.shift([yzshift * dx, 0, 0])
3781            hzy.name = "zyHighlightZero"
3782            highlights.append(hzy)
3783
3784    if zxgrid and ztitle and xtitle:
3785        if zhighlight_zero and min_bns[4] <= 0 and max_bns[5] > 0:
3786            zhl = -min_bns[4]
3787            hzx = shapes.Line([0, 0, zhl], [dx, 0, zhl], c=zhighlight_zero_color)
3788            hzx.alpha(np.sqrt(zxalpha)).lw(grid_linewidth * 2)
3789            hzx.shift([0, zxshift * dy, 0])
3790            hzx.name = "zxHighlightZero"
3791            highlights.append(hzx)
3792        if xhighlight_zero and min_bns[0] <= 0 and max_bns[1] > 0:
3793            xhl = -min_bns[0]
3794            hxz = shapes.Line([xhl, 0, 0], [xhl, 0, dz], c=xhighlight_zero_color)
3795            hxz.alpha(np.sqrt(zxalpha)).lw(grid_linewidth * 2)
3796            hxz.shift([0, zxshift * dy, 0])
3797            hxz.name = "xzHighlightZero"
3798            highlights.append(hxz)
3799
3800    ################################################ arrow cone
3801    cones = []
3802
3803    if tip_size:
3804
3805        if xtitle:
3806            if x_inverted:
3807                cx = shapes.Cone(
3808                    r=tip_size,
3809                    height=tip_size * 2,
3810                    axis=(-1, 0, 0),
3811                    c=xline_color,
3812                    res=12,
3813                )
3814            else:
3815                cx = shapes.Cone(
3816                    (dx, 0, 0),
3817                    r=tip_size,
3818                    height=tip_size * 2,
3819                    axis=(1, 0, 0),
3820                    c=xline_color,
3821                    res=12,
3822                )
3823            T = LinearTransform()
3824            T.translate(
3825                [
3826                    0,
3827                    zxshift * dy + xshift_along_y * dy,
3828                    xyshift * dz + xshift_along_z * dz,
3829                ]
3830            )
3831            cx.apply_transform(T)
3832            cx.name = "xTipCone"
3833            cones.append(cx)
3834
3835        if ytitle:
3836            if y_inverted:
3837                cy = shapes.Cone(
3838                    r=tip_size,
3839                    height=tip_size * 2,
3840                    axis=(0, -1, 0),
3841                    c=yline_color,
3842                    res=12,
3843                )
3844            else:
3845                cy = shapes.Cone(
3846                    (0, dy, 0),
3847                    r=tip_size,
3848                    height=tip_size * 2,
3849                    axis=(0, 1, 0),
3850                    c=yline_color,
3851                    res=12,
3852                )
3853            T = LinearTransform()
3854            T.translate(
3855                [
3856                    yzshift * dx + yshift_along_x * dx,
3857                    0,
3858                    xyshift * dz + yshift_along_z * dz,
3859                ]
3860            )
3861            cy.apply_transform(T)
3862            cy.name = "yTipCone"
3863            cones.append(cy)
3864
3865        if ztitle:
3866            if z_inverted:
3867                cz = shapes.Cone(
3868                    r=tip_size,
3869                    height=tip_size * 2,
3870                    axis=(0, 0, -1),
3871                    c=zline_color,
3872                    res=12,
3873                )
3874            else:
3875                cz = shapes.Cone(
3876                    (0, 0, dz),
3877                    r=tip_size,
3878                    height=tip_size * 2,
3879                    axis=(0, 0, 1),
3880                    c=zline_color,
3881                    res=12,
3882                )
3883            T = LinearTransform()
3884            T.translate(
3885                [
3886                    yzshift * dx + zshift_along_x * dx,
3887                    zxshift * dy + zshift_along_y * dy,
3888                    0,
3889                ]
3890            )
3891            cz.apply_transform(T)
3892            cz.name = "zTipCone"
3893            cones.append(cz)
3894
3895    ################################################################# MAJOR ticks
3896    majorticks, minorticks = [], []
3897    xticks, yticks, zticks = [], [], []
3898    if show_ticks:
3899        if xtitle:
3900            tick_thickness = xtick_thickness * gscale / 2
3901            tick_length = xtick_length * gscale / 2
3902            for i in range(1, len(xticks_float) - 1):
3903                v1 = (xticks_float[i] - tick_thickness, -tick_length, 0)
3904                v2 = (xticks_float[i] + tick_thickness, tick_length, 0)
3905                xticks.append(shapes.Rectangle(v1, v2))
3906            if len(xticks) > 1:
3907                xmajticks = merge(xticks).c(xlabel_color)
3908                T = LinearTransform()
3909                T.rotate_x(xaxis_rotation)
3910                T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3911                xmajticks.apply_transform(T)
3912                xmajticks.name = "xMajorTicks"
3913                majorticks.append(xmajticks)
3914        if ytitle:
3915            tick_thickness = ytick_thickness * gscale / 2
3916            tick_length = ytick_length * gscale / 2
3917            for i in range(1, len(yticks_float) - 1):
3918                v1 = (-tick_length, yticks_float[i] - tick_thickness, 0)
3919                v2 = (tick_length, yticks_float[i] + tick_thickness, 0)
3920                yticks.append(shapes.Rectangle(v1, v2))
3921            if len(yticks) > 1:
3922                ymajticks = merge(yticks).c(ylabel_color)
3923                T = LinearTransform()
3924                T.rotate_y(yaxis_rotation)
3925                T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3926                ymajticks.apply_transform(T)
3927                ymajticks.name = "yMajorTicks"
3928                majorticks.append(ymajticks)
3929        if ztitle:
3930            tick_thickness = ztick_thickness * gscale / 2
3931            tick_length = ztick_length * gscale / 2.85
3932            for i in range(1, len(zticks_float) - 1):
3933                v1 = (zticks_float[i] - tick_thickness, -tick_length, 0)
3934                v2 = (zticks_float[i] + tick_thickness, tick_length, 0)
3935                zticks.append(shapes.Rectangle(v1, v2))
3936            if len(zticks) > 1:
3937                zmajticks = merge(zticks).c(zlabel_color)
3938                T = LinearTransform()
3939                T.rotate_y(-90).rotate_z(-45 + zaxis_rotation)
3940                T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3941                zmajticks.apply_transform(T)
3942                zmajticks.name = "zMajorTicks"
3943                majorticks.append(zmajticks)
3944
3945        ############################################################# MINOR ticks
3946        if xtitle and xminor_ticks and len(xticks) > 1:
3947            tick_thickness = xtick_thickness * gscale / 4
3948            tick_length = xtick_length * gscale / 4
3949            xminor_ticks += 1
3950            ticks = []
3951            for i in range(1, len(xticks)):
3952                t0, t1 = xticks[i - 1].pos(), xticks[i].pos()
3953                dt = t1 - t0
3954                for j in range(1, xminor_ticks):
3955                    mt = dt * (j / xminor_ticks) + t0
3956                    v1 = (mt[0] - tick_thickness, -tick_length, 0)
3957                    v2 = (mt[0] + tick_thickness, tick_length, 0)
3958                    ticks.append(shapes.Rectangle(v1, v2))
3959
3960            # finish off the fist lower range from start to first tick
3961            t0, t1 = xticks[0].pos(), xticks[1].pos()
3962            dt = t1 - t0
3963            for j in range(1, xminor_ticks):
3964                mt = t0 - dt * (j / xminor_ticks)
3965                if mt[0] < 0:
3966                    break
3967                v1 = (mt[0] - tick_thickness, -tick_length, 0)
3968                v2 = (mt[0] + tick_thickness, tick_length, 0)
3969                ticks.append(shapes.Rectangle(v1, v2))
3970
3971            # finish off the last upper range from last tick to end
3972            t0, t1 = xticks[-2].pos(), xticks[-1].pos()
3973            dt = t1 - t0
3974            for j in range(1, xminor_ticks):
3975                mt = t1 + dt * (j / xminor_ticks)
3976                if mt[0] > dx:
3977                    break
3978                v1 = (mt[0] - tick_thickness, -tick_length, 0)
3979                v2 = (mt[0] + tick_thickness, tick_length, 0)
3980                ticks.append(shapes.Rectangle(v1, v2))
3981
3982            if ticks:
3983                xminticks = merge(ticks).c(xlabel_color)
3984                T = LinearTransform()
3985                T.rotate_x(xaxis_rotation)
3986                T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3987                xminticks.apply_transform(T)
3988                xminticks.name = "xMinorTicks"
3989                minorticks.append(xminticks)
3990
3991        if ytitle and yminor_ticks and len(yticks) > 1:  ##### y
3992            tick_thickness = ytick_thickness * gscale / 4
3993            tick_length = ytick_length * gscale / 4
3994            yminor_ticks += 1
3995            ticks = []
3996            for i in range(1, len(yticks)):
3997                t0, t1 = yticks[i - 1].pos(), yticks[i].pos()
3998                dt = t1 - t0
3999                for j in range(1, yminor_ticks):
4000                    mt = dt * (j / yminor_ticks) + t0
4001                    v1 = (-tick_length, mt[1] - tick_thickness, 0)
4002                    v2 = (tick_length, mt[1] + tick_thickness, 0)
4003                    ticks.append(shapes.Rectangle(v1, v2))
4004
4005            # finish off the fist lower range from start to first tick
4006            t0, t1 = yticks[0].pos(), yticks[1].pos()
4007            dt = t1 - t0
4008            for j in range(1, yminor_ticks):
4009                mt = t0 - dt * (j / yminor_ticks)
4010                if mt[1] < 0:
4011                    break
4012                v1 = (-tick_length, mt[1] - tick_thickness, 0)
4013                v2 = (tick_length, mt[1] + tick_thickness, 0)
4014                ticks.append(shapes.Rectangle(v1, v2))
4015
4016            # finish off the last upper range from last tick to end
4017            t0, t1 = yticks[-2].pos(), yticks[-1].pos()
4018            dt = t1 - t0
4019            for j in range(1, yminor_ticks):
4020                mt = t1 + dt * (j / yminor_ticks)
4021                if mt[1] > dy:
4022                    break
4023                v1 = (-tick_length, mt[1] - tick_thickness, 0)
4024                v2 = (tick_length, mt[1] + tick_thickness, 0)
4025                ticks.append(shapes.Rectangle(v1, v2))
4026
4027            if ticks:
4028                yminticks = merge(ticks).c(ylabel_color)
4029                T = LinearTransform()
4030                T.rotate_y(yaxis_rotation)
4031                T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
4032                yminticks.apply_transform(T)
4033                yminticks.name = "yMinorTicks"
4034                minorticks.append(yminticks)
4035
4036        if ztitle and zminor_ticks and len(zticks) > 1:  ##### z
4037            tick_thickness = ztick_thickness * gscale / 4
4038            tick_length = ztick_length * gscale / 5
4039            zminor_ticks += 1
4040            ticks = []
4041            for i in range(1, len(zticks)):
4042                t0, t1 = zticks[i - 1].pos(), zticks[i].pos()
4043                dt = t1 - t0
4044                for j in range(1, zminor_ticks):
4045                    mt = dt * (j / zminor_ticks) + t0
4046                    v1 = (mt[0] - tick_thickness, -tick_length, 0)
4047                    v2 = (mt[0] + tick_thickness, tick_length, 0)
4048                    ticks.append(shapes.Rectangle(v1, v2))
4049
4050            # finish off the fist lower range from start to first tick
4051            t0, t1 = zticks[0].pos(), zticks[1].pos()
4052            dt = t1 - t0
4053            for j in range(1, zminor_ticks):
4054                mt = t0 - dt * (j / zminor_ticks)
4055                if mt[0] < 0:
4056                    break
4057                v1 = (mt[0] - tick_thickness, -tick_length, 0)
4058                v2 = (mt[0] + tick_thickness, tick_length, 0)
4059                ticks.append(shapes.Rectangle(v1, v2))
4060
4061            # finish off the last upper range from last tick to end
4062            t0, t1 = zticks[-2].pos(), zticks[-1].pos()
4063            dt = t1 - t0
4064            for j in range(1, zminor_ticks):
4065                mt = t1 + dt * (j / zminor_ticks)
4066                if mt[0] > dz:
4067                    break
4068                v1 = (mt[0] - tick_thickness, -tick_length, 0)
4069                v2 = (mt[0] + tick_thickness, tick_length, 0)
4070                ticks.append(shapes.Rectangle(v1, v2))
4071
4072            if ticks:
4073                zminticks = merge(ticks).c(zlabel_color)
4074                T = LinearTransform()
4075                T.rotate_y(-90).rotate_z(-45 + zaxis_rotation)
4076                T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
4077                zminticks.apply_transform(T)
4078                zminticks.name = "zMinorTicks"
4079                minorticks.append(zminticks)
4080
4081    ################################################ axes NUMERIC text labels
4082    labels = []
4083    xlab, ylab, zlab = None, None, None
4084
4085    if xlabel_size and xtitle:
4086
4087        xRot, yRot, zRot = 0, 0, 0
4088        if utils.is_sequence(xlabel_rotation):  # unpck 3 rotations
4089            zRot, xRot, yRot = xlabel_rotation
4090        else:
4091            zRot = xlabel_rotation
4092        if zRot < 0:  # deal with negative angles
4093            zRot += 360
4094
4095        jus = "center-top"
4096        if zRot:
4097            if zRot >  24: jus = "top-right"
4098            if zRot >  67: jus = "center-right"
4099            if zRot > 112: jus = "right-bottom"
4100            if zRot > 157: jus = "center-bottom"
4101            if zRot > 202: jus = "bottom-left"
4102            if zRot > 247: jus = "center-left"
4103            if zRot > 292: jus = "top-left"
4104            if zRot > 337: jus = "top-center"
4105        if xlabel_justify is not None:
4106            jus = xlabel_justify
4107
4108        for i in range(1, len(xticks_str)):
4109            t = xticks_str[i]
4110            if not t:
4111                continue
4112            if utils.is_sequence(xlabel_offset):
4113                xoffs, yoffs, zoffs = xlabel_offset
4114            else:
4115                xoffs, yoffs, zoffs = 0, xlabel_offset, 0
4116
4117            xlab = shapes.Text3D(
4118                t, s=xlabel_size * text_scale * gscale, font=label_font, justify=jus
4119            )
4120            tb = xlab.ybounds()  # must be ybounds: height of char
4121
4122            v = (xticks_float[i], 0, 0)
4123            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0])
4124
4125            T = LinearTransform()
4126            T.rotate_x(xaxis_rotation).rotate_y(yRot).rotate_x(xRot).rotate_z(zRot)
4127            T.translate(v + offs)
4128            T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
4129            xlab.apply_transform(T)
4130
4131            xlab.use_bounds(x_use_bounds)
4132
4133            xlab.c(xlabel_color)
4134            if xlabel_backface_color is None:
4135                bfc = 1 - np.array(get_color(xlabel_color))
4136                xlab.backcolor(bfc)
4137            xlab.name = f"xNumericLabel {i}"
4138            labels.append(xlab)
4139
4140    if ylabel_size and ytitle:
4141
4142        xRot, yRot, zRot = 0, 0, 0
4143        if utils.is_sequence(ylabel_rotation):  # unpck 3 rotations
4144            zRot, yRot, xRot = ylabel_rotation
4145        else:
4146            zRot = ylabel_rotation
4147        if zRot < 0:
4148            zRot += 360  # deal with negative angles
4149
4150        jus = "center-right"
4151        if zRot:
4152            if zRot >  24: jus = "bottom-right"
4153            if zRot >  67: jus = "center-bottom"
4154            if zRot > 112: jus = "left-bottom"
4155            if zRot > 157: jus = "center-left"
4156            if zRot > 202: jus = "top-left"
4157            if zRot > 247: jus = "center-top"
4158            if zRot > 292: jus = "top-right"
4159            if zRot > 337: jus = "right-center"
4160        if ylabel_justify is not None:
4161            jus = ylabel_justify
4162
4163        for i in range(1, len(yticks_str)):
4164            t = yticks_str[i]
4165            if not t:
4166                continue
4167            if utils.is_sequence(ylabel_offset):
4168                xoffs, yoffs, zoffs = ylabel_offset
4169            else:
4170                xoffs, yoffs, zoffs = ylabel_offset, 0, 0
4171            ylab = shapes.Text3D(
4172                t, s=ylabel_size * text_scale * gscale, font=label_font, justify=jus
4173            )
4174            tb = ylab.ybounds()  # must be ybounds: height of char
4175            v = (0, yticks_float[i], 0)
4176            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0])
4177
4178            T = LinearTransform()
4179            T.rotate_y(yaxis_rotation).rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
4180            T.translate(v + offs)
4181            T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
4182            ylab.apply_transform(T)
4183
4184            ylab.use_bounds(y_use_bounds)
4185
4186            ylab.c(ylabel_color)
4187            if ylabel_backface_color is None:
4188                bfc = 1 - np.array(get_color(ylabel_color))
4189                ylab.backcolor(bfc)
4190            ylab.name = f"yNumericLabel {i}"
4191            labels.append(ylab)
4192
4193    if zlabel_size and ztitle:
4194
4195        xRot, yRot, zRot = 0, 0, 0
4196        if utils.is_sequence(zlabel_rotation):  # unpck 3 rotations
4197            xRot, yRot, zRot = zlabel_rotation
4198        else:
4199            xRot = zlabel_rotation
4200        if xRot < 0: xRot += 360 # deal with negative angles
4201
4202        jus = "center-right"
4203        if xRot:
4204            if xRot >  24: jus = "bottom-right"
4205            if xRot >  67: jus = "center-bottom"
4206            if xRot > 112: jus = "left-bottom"
4207            if xRot > 157: jus = "center-left"
4208            if xRot > 202: jus = "top-left"
4209            if xRot > 247: jus = "center-top"
4210            if xRot > 292: jus = "top-right"
4211            if xRot > 337: jus = "right-center"
4212        if zlabel_justify is not None:
4213            jus = zlabel_justify
4214
4215        for i in range(1, len(zticks_str)):
4216            t = zticks_str[i]
4217            if not t:
4218                continue
4219            if utils.is_sequence(zlabel_offset):
4220                xoffs, yoffs, zoffs = zlabel_offset
4221            else:
4222                xoffs, yoffs, zoffs = zlabel_offset, zlabel_offset, 0
4223            zlab = shapes.Text3D(t, s=zlabel_size*text_scale*gscale, font=label_font, justify=jus)
4224            tb = zlab.ybounds()  # must be ybounds: height of char
4225
4226            v = (0, 0, zticks_float[i])
4227            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0]) / 1.5
4228            angle = np.arctan2(dy, dx) * 57.3
4229
4230            T = LinearTransform()
4231            T.rotate_x(90 + zRot).rotate_y(-xRot).rotate_z(angle + yRot + zaxis_rotation)
4232            T.translate(v + offs)
4233            T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
4234            zlab.apply_transform(T)
4235
4236            zlab.use_bounds(z_use_bounds)
4237
4238            zlab.c(zlabel_color)
4239            if zlabel_backface_color is None:
4240                bfc = 1 - np.array(get_color(zlabel_color))
4241                zlab.backcolor(bfc)
4242            zlab.name = f"zNumericLabel {i}"
4243            labels.append(zlab)
4244
4245    ################################################ axes titles
4246    titles = []
4247
4248    if xtitle:
4249        xRot, yRot, zRot = 0, 0, 0
4250        if utils.is_sequence(xtitle_rotation):  # unpack 3 rotations
4251            zRot, xRot, yRot = xtitle_rotation
4252        else:
4253            zRot = xtitle_rotation
4254        if zRot < 0:  # deal with negative angles
4255            zRot += 360
4256
4257        if utils.is_sequence(xtitle_offset):
4258            xoffs, yoffs, zoffs = xtitle_offset
4259        else:
4260            xoffs, yoffs, zoffs = 0, xtitle_offset, 0
4261
4262        if xtitle_justify is not None:
4263            jus = xtitle_justify
4264        else:
4265            # find best justfication for given rotation(s)
4266            jus = "right-top"
4267            if zRot:
4268                if zRot >  24: jus = "center-right"
4269                if zRot >  67: jus = "right-bottom"
4270                if zRot > 157: jus = "bottom-left"
4271                if zRot > 202: jus = "center-left"
4272                if zRot > 247: jus = "top-left"
4273                if zRot > 337: jus = "top-right"
4274
4275        xt = shapes.Text3D(
4276            xtitle,
4277            s=xtitle_size * text_scale * gscale,
4278            font=title_font,
4279            c=xtitle_color,
4280            justify=jus,
4281            depth=title_depth,
4282            italic=xtitle_italic,
4283        )
4284        if xtitle_backface_color is None:
4285            xtitle_backface_color = 1 - np.array(get_color(xtitle_color))
4286            xt.backcolor(xtitle_backface_color)
4287
4288        shift = 0
4289        if xlab:  # xlab is the last created numeric text label..
4290            lt0, lt1 = xlab.bounds()[2:4]
4291            shift = lt1 - lt0
4292
4293        T = LinearTransform()
4294        T.rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
4295        T.set_position(
4296            [(xoffs + xtitle_position) * dx,
4297            -(yoffs + xtick_length / 2) * dy - shift,
4298            zoffs * dz]
4299        )
4300        T.rotate_x(xaxis_rotation)
4301        T.translate([0, xshift_along_y * dy, xyshift * dz + xshift_along_z * dz])
4302        xt.apply_transform(T)
4303
4304        xt.use_bounds(x_use_bounds)
4305        if xtitle == " ":
4306            xt.use_bounds(False)
4307        xt.name = "xtitle"
4308        titles.append(xt)
4309        if xtitle_box:
4310            titles.append(xt.box(scale=1.1).use_bounds(x_use_bounds))
4311
4312    if ytitle:
4313        xRot, yRot, zRot = 0, 0, 0
4314        if utils.is_sequence(ytitle_rotation):  # unpck 3 rotations
4315            zRot, yRot, xRot = ytitle_rotation
4316        else:
4317            zRot = ytitle_rotation
4318            if len(ytitle) > 3:
4319                zRot += 90
4320                ytitle_position *= 0.975
4321        if zRot < 0:
4322            zRot += 360  # deal with negative angles
4323
4324        if utils.is_sequence(ytitle_offset):
4325            xoffs, yoffs, zoffs = ytitle_offset
4326        else:
4327            xoffs, yoffs, zoffs = ytitle_offset, 0, 0
4328
4329        if ytitle_justify is not None:
4330            jus = ytitle_justify
4331        else:
4332            jus = "center-right"
4333            if zRot:
4334                if zRot >  24: jus = "bottom-right"
4335                if zRot > 112: jus = "left-bottom"
4336                if zRot > 157: jus = "center-left"
4337                if zRot > 202: jus = "top-left"
4338                if zRot > 292: jus = "top-right"
4339                if zRot > 337: jus = "right-center"
4340
4341        yt = shapes.Text3D(
4342            ytitle,
4343            s=ytitle_size * text_scale * gscale,
4344            font=title_font,
4345            c=ytitle_color,
4346            justify=jus,
4347            depth=title_depth,
4348            italic=ytitle_italic,
4349        )
4350        if ytitle_backface_color is None:
4351            ytitle_backface_color = 1 - np.array(get_color(ytitle_color))
4352            yt.backcolor(ytitle_backface_color)
4353
4354        shift = 0
4355        if ylab:  # this is the last created num label..
4356            lt0, lt1 = ylab.bounds()[0:2]
4357            shift = lt1 - lt0
4358
4359        T = LinearTransform()
4360        T.rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
4361        T.set_position(
4362            [-(xoffs + ytick_length / 2) * dx - shift,
4363            (yoffs + ytitle_position) * dy,
4364            zoffs * dz]
4365        )
4366        T.rotate_y(yaxis_rotation)
4367        T.translate([yshift_along_x * dx, 0, xyshift * dz + yshift_along_z * dz])
4368        yt.apply_transform(T)
4369
4370        yt.use_bounds(y_use_bounds)
4371        if ytitle == " ":
4372            yt.use_bounds(False)
4373        yt.name = "ytitle"
4374        titles.append(yt)
4375        if ytitle_box:
4376            titles.append(yt.box(scale=1.1).use_bounds(y_use_bounds))
4377
4378    if ztitle:
4379        xRot, yRot, zRot = 0, 0, 0
4380        if utils.is_sequence(ztitle_rotation):  # unpck 3 rotations
4381            xRot, yRot, zRot = ztitle_rotation
4382        else:
4383            xRot = ztitle_rotation
4384            if len(ztitle) > 3:
4385                xRot += 90
4386                ztitle_position *= 0.975
4387        if xRot < 0:
4388            xRot += 360  # deal with negative angles
4389
4390        if ztitle_justify is not None:
4391            jus = ztitle_justify
4392        else:
4393            jus = "center-right"
4394            if xRot:
4395                if xRot >  24: jus = "bottom-right"
4396                if xRot > 112: jus = "left-bottom"
4397                if xRot > 157: jus = "center-left"
4398                if xRot > 202: jus = "top-left"
4399                if xRot > 292: jus = "top-right"
4400                if xRot > 337: jus = "right-center"
4401
4402        zt = shapes.Text3D(
4403            ztitle,
4404            s=ztitle_size * text_scale * gscale,
4405            font=title_font,
4406            c=ztitle_color,
4407            justify=jus,
4408            depth=title_depth,
4409            italic=ztitle_italic,
4410        )
4411        if ztitle_backface_color is None:
4412            ztitle_backface_color = 1 - np.array(get_color(ztitle_color))
4413            zt.backcolor(ztitle_backface_color)
4414
4415        angle = np.arctan2(dy, dx) * 57.3
4416        shift = 0
4417        if zlab:  # this is the last created one..
4418            lt0, lt1 = zlab.bounds()[0:2]
4419            shift = lt1 - lt0
4420
4421        T = LinearTransform()
4422        T.rotate_x(90 + zRot).rotate_y(-xRot).rotate_z(angle + yRot)
4423        T.set_position([
4424            -(ztitle_offset + ztick_length / 5) * dx - shift,
4425            -(ztitle_offset + ztick_length / 5) * dy - shift,
4426            ztitle_position * dz]
4427        )
4428        T.rotate_z(zaxis_rotation)
4429        T.translate([zshift_along_x * dx, zxshift * dy + zshift_along_y * dy, 0])
4430        zt.apply_transform(T)
4431
4432        zt.use_bounds(z_use_bounds)
4433        if ztitle == " ":
4434            zt.use_bounds(False)
4435        zt.name = "ztitle"
4436        titles.append(zt)
4437
4438    ################################################### header title
4439    if htitle:
4440        if htitle_font is None:
4441            htitle_font = title_font
4442        if htitle_color is None:
4443            htitle_color = xtitle_color
4444        htit = shapes.Text3D(
4445            htitle,
4446            s=htitle_size * gscale * text_scale,
4447            font=htitle_font,
4448            c=htitle_color,
4449            justify=htitle_justify,
4450            depth=title_depth,
4451            italic=htitle_italic,
4452        )
4453        if htitle_backface_color is None:
4454            htitle_backface_color = 1 - np.array(get_color(htitle_color))
4455            htit.backcolor(htitle_backface_color)
4456        htit.rotate_x(htitle_rotation)
4457        wpos = [htitle_offset[0]*dx, (1 + htitle_offset[1])*dy, htitle_offset[2]*dz]
4458        htit.shift(np.array(wpos) + [0, 0, xyshift*dz])
4459        htit.name = "htitle"
4460        titles.append(htit)
4461
4462    ######
4463    acts = titles + lines + labels + grids + framelines
4464    acts += highlights + majorticks + minorticks + cones
4465    orig = (min_bns[0], min_bns[2], min_bns[4])
4466    for a in acts:
4467        a.shift(orig)
4468        a.actor.PickableOff()
4469        a.properties.LightingOff()
4470    asse = Assembly(acts)
4471    asse.PickableOff()
4472    asse.name = "Axes"
4473    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()

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