vedo.addons

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

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

A 2D scalar bar for the specified object.

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

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

Create a 3D scalar bar for the specified object.

Input obj input can be:

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

Add a slider which can call an external custom function.

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

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

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

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

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

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

Slider3D( sliderfunc, pos1, pos2, xmin, xmax, value=None, s=0.03, t=1, title='', rotation=0, c=None, show_value=True)
1978    def __init__(
1979        self,
1980        sliderfunc,
1981        pos1,
1982        pos2,
1983        xmin,
1984        xmax,
1985        value=None,
1986        s=0.03,
1987        t=1,
1988        title="",
1989        rotation=0,
1990        c=None,
1991        show_value=True,
1992    ):
1993        """
1994        Add a 3D slider which can call an external custom function.
1995
1996        Arguments:
1997            sliderfunc : (function)
1998                external function to be called by the widget
1999            pos1 : (list)
2000                first position 3D coordinates
2001            pos2 : (list)
2002                second position 3D coordinates
2003            xmin : (float)
2004                lower value
2005            xmax : (float)
2006                upper value
2007            value : (float)
2008                initial value
2009            s : (float)
2010                label scaling factor
2011            t : (float)
2012                tube scaling factor
2013            title : (str)
2014                title text
2015            c : (color)
2016                slider color
2017            rotation : (float)
2018                title rotation around slider axis
2019            show_value : (bool)
2020                if True current value is shown on top of the slider
2021
2022        Examples:
2023            - [sliders3d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders3d.py)
2024        """
2025        c = get_color(c)
2026
2027        if value is None or value < xmin:
2028            value = xmin
2029
2030        slider_rep = vtki.new("SliderRepresentation3D")
2031        slider_rep.SetMinimumValue(xmin)
2032        slider_rep.SetMaximumValue(xmax)
2033        slider_rep.SetValue(value)
2034
2035        slider_rep.GetPoint1Coordinate().SetCoordinateSystemToWorld()
2036        slider_rep.GetPoint2Coordinate().SetCoordinateSystemToWorld()
2037        slider_rep.GetPoint1Coordinate().SetValue(pos2)
2038        slider_rep.GetPoint2Coordinate().SetValue(pos1)
2039
2040        # slider_rep.SetPoint1InWorldCoordinates(pos2[0], pos2[1], pos2[2])
2041        # slider_rep.SetPoint2InWorldCoordinates(pos1[0], pos1[1], pos1[2])
2042
2043        slider_rep.SetSliderWidth(0.03 * t)
2044        slider_rep.SetTubeWidth(0.01 * t)
2045        slider_rep.SetSliderLength(0.04 * t)
2046        slider_rep.SetSliderShapeToCylinder()
2047        slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c)))
2048        slider_rep.GetSliderProperty().SetColor(np.array(c) / 1.5)
2049        slider_rep.GetCapProperty().SetOpacity(0)
2050        slider_rep.SetRotation(rotation)
2051
2052        if not show_value:
2053            slider_rep.ShowSliderLabelOff()
2054
2055        slider_rep.SetTitleText(title)
2056        slider_rep.SetTitleHeight(s * t)
2057        slider_rep.SetLabelHeight(s * t * 0.85)
2058
2059        slider_rep.GetTubeProperty().SetColor(c)
2060
2061        super().__init__()
2062        self.name = "Slider3D"
2063
2064        self.SetRepresentation(slider_rep)
2065        self.SetAnimationModeToJump()
2066        self.AddObserver("InteractionEvent", sliderfunc)

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

Arguments:
  • sliderfunc : (function) external function to be called by the widget
  • pos1 : (list) first position 3D coordinates
  • pos2 : (list) second position 3D coordinates
  • xmin : (float) lower value
  • xmax : (float) upper value
  • value : (float) initial value
  • s : (float) label scaling factor
  • t : (float) tube scaling factor
  • title : (str) title text
  • c : (color) slider color
  • rotation : (float) title rotation around slider axis
  • show_value : (bool) if True current value is shown on top of the slider
Examples:
class Icon(vtkmodules.vtkInteractionWidgets.vtkOrientationMarkerWidget):
2773class Icon(vtki.vtkOrientationMarkerWidget):
2774    """
2775    Add an inset icon mesh into the renderer.
2776    """
2777
2778    def __init__(self, mesh, pos=3, size=0.08):
2779        """
2780        Add an inset icon mesh into the renderer.
2781
2782        Arguments:
2783            pos : (list, int)
2784                icon position in the range [1-4] indicating one of the 4 corners,
2785                or it can be a tuple (x,y) as a fraction of the renderer size.
2786            size : (float)
2787                size of the icon space as fraction of the window size.
2788
2789        Examples:
2790            - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py)
2791        """
2792        super().__init__()
2793        self.name = "Icon"
2794
2795        try:
2796            self.SetOrientationMarker(mesh.actor)
2797        except AttributeError:
2798            self.SetOrientationMarker(mesh)
2799
2800        if utils.is_sequence(pos):
2801            self.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size)
2802        else:
2803            if pos < 2:
2804                self.SetViewport(0, 1 - 2 * size, size * 2, 1)
2805            elif pos == 2:
2806                self.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1)
2807            elif pos == 3:
2808                self.SetViewport(0, 0, size * 2, size * 2)
2809            elif pos == 4:
2810                self.SetViewport(1 - 2 * size, 0, 1, size * 2)

Add an inset icon mesh into the renderer.

Icon(mesh, pos=3, size=0.08)
2778    def __init__(self, mesh, pos=3, size=0.08):
2779        """
2780        Add an inset icon mesh into the renderer.
2781
2782        Arguments:
2783            pos : (list, int)
2784                icon position in the range [1-4] indicating one of the 4 corners,
2785                or it can be a tuple (x,y) as a fraction of the renderer size.
2786            size : (float)
2787                size of the icon space as fraction of the window size.
2788
2789        Examples:
2790            - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py)
2791        """
2792        super().__init__()
2793        self.name = "Icon"
2794
2795        try:
2796            self.SetOrientationMarker(mesh.actor)
2797        except AttributeError:
2798            self.SetOrientationMarker(mesh)
2799
2800        if utils.is_sequence(pos):
2801            self.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size)
2802        else:
2803            if pos < 2:
2804                self.SetViewport(0, 1 - 2 * size, size * 2, 1)
2805            elif pos == 2:
2806                self.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1)
2807            elif pos == 3:
2808                self.SetViewport(0, 0, size * 2, size * 2)
2809            elif pos == 4:
2810                self.SetViewport(1 - 2 * size, 0, 1, size * 2)

Add an inset icon mesh into the renderer.

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

Create a 2D legend box.

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

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

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

Return the width of the legend box.

height
361    @property
362    def height(self):
363        """Return the height of the legend box."""
364        return self.GetHeight()

Return the height of the legend box.

def pos(self, pos):
366    def pos(self, pos):
367        """Set the position of the legend box."""
368        sx, sy = 1 - self.GetWidth(), 1 - self.GetHeight()
369        if pos == 1 or ("top" in pos and "left" in pos):
370            self.GetPositionCoordinate().SetValue(0, sy)
371        elif pos == 2 or ("top" in pos and "right" in pos):
372            self.GetPositionCoordinate().SetValue(sx, sy)
373        elif pos == 3 or ("bottom" in pos and "left" in pos):
374            self.GetPositionCoordinate().SetValue(0, 0)
375        elif pos == 4 or ("bottom" in pos and "right" in pos):
376            self.GetPositionCoordinate().SetValue(sx, 0)
377        elif "cent" in pos and "right" in pos:
378            self.GetPositionCoordinate().SetValue(sx, sy - 0.25)
379        elif "cent" in pos and "left" in pos:
380            self.GetPositionCoordinate().SetValue(0, sy - 0.25)
381        elif "cent" in pos and "bottom" in pos:
382            self.GetPositionCoordinate().SetValue(sx - 0.25, 0)
383        elif "cent" in pos and "top" in pos:
384            self.GetPositionCoordinate().SetValue(sx - 0.25, sy)
385        elif utils.is_sequence(pos):
386            self.GetPositionCoordinate().SetValue(pos[0], pos[1])
387        else:
388            vedo.logger.error("LegendBox: pos must be in range [1-4] or a [x,y] list")
389
390        return self

Set the position of the legend box.

def Light(pos, focal_point=(0, 0, 0), angle=180, c=None, intensity=1):
1174def Light(pos, focal_point=(0, 0, 0), angle=180, c=None, intensity=1):
1175    """
1176    Generate a source of light placed at `pos` and directed to `focal point`.
1177    Returns a `vtkLight` object.
1178
1179    Arguments:
1180        focal_point : (list)
1181            focal point, if a `vedo` object is passed then will grab its position.
1182        angle : (float)
1183            aperture angle of the light source, in degrees
1184        c : (color)
1185            set the light color
1186        intensity : (float)
1187            intensity value between 0 and 1.
1188
1189    Check also:
1190        `plotter.Plotter.remove_lights()`
1191
1192    Examples:
1193        - [light_sources.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/light_sources.py)
1194
1195            ![](https://vedo.embl.es/images/basic/lights.png)
1196    """
1197    if c is None:
1198        try:
1199            c = pos.color()
1200        except AttributeError:
1201            c = "white"
1202
1203    try:
1204        pos = pos.pos()
1205    except AttributeError:
1206        pass
1207
1208    try:
1209        focal_point = focal_point.pos()
1210    except AttributeError:
1211        pass
1212
1213    light = vtki.vtkLight()
1214    light.SetLightTypeToSceneLight()
1215    light.SetPosition(pos)
1216    light.SetConeAngle(angle)
1217    light.SetFocalPoint(focal_point)
1218    light.SetIntensity(intensity)
1219    light.SetColor(get_color(c))
1220    return light

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

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

plotter.Plotter.remove_lights()

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

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

Returns an vedo.Assembly object.

Parameters

  • xtitle, ['x'], x-axis title 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), size=(8,9,7)).alpha(0.1)
axs = Axes(box, c='k')  # returns an Assembly object
for a in axs.unpack():
    print(a.name)
show(box, axs).close()

Examples:

class RendererFrame(vedo.visual.Actor2D):
2586class RendererFrame(Actor2D):
2587    """
2588    Add a line around the renderer subwindow.
2589    """
2590
2591    def __init__(self, c="k", alpha=None, lw=None, padding=None, pattern="brtl"):
2592        """
2593        Add a line around the renderer subwindow.
2594
2595        Arguments:
2596            c : (color)
2597                color of the line.
2598            alpha : (float)
2599                opacity.
2600            lw : (int)
2601                line width in pixels.
2602            padding : (int)
2603                padding in pixel units.
2604            pattern : (str)
2605                combination of characters `b` for bottom, `r` for right,
2606                `t` for top, `l` for left.
2607        """
2608        if lw is None:
2609            lw = settings.renderer_frame_width
2610
2611        if alpha is None:
2612            alpha = settings.renderer_frame_alpha
2613
2614        if padding is None:
2615            padding = settings.renderer_frame_padding
2616
2617        if lw == 0 or alpha == 0:
2618            return
2619        c = get_color(c)
2620
2621        a = padding
2622        b = 1 - padding
2623        p0 = [a, a]
2624        p1 = [b, a]
2625        p2 = [b, b]
2626        p3 = [a, b]
2627        disconnected = False
2628        if "b" in pattern and "r" in pattern and "t" in pattern and "l" in pattern:
2629            psqr = [p0, p1, p2, p3, p0]
2630        elif "b" in pattern and "r" in pattern and "t" in pattern:
2631            psqr = [p0, p1, p2, p3]
2632        elif "b" in pattern and "r" in pattern and "l" in pattern:
2633            psqr = [p3, p0, p1, p2]
2634        elif "b" in pattern and "t" in pattern and "l" in pattern:
2635            psqr = [p2, p3, p0, p1]
2636        elif "b" in pattern and "r" in pattern:
2637            psqr = [p0, p1, p2]
2638        elif "b" in pattern and "l" in pattern:
2639            psqr = [p3, p0, p1]
2640        elif "r" in pattern and "t" in pattern:
2641            psqr = [p1, p2, p3]
2642        elif "t" in pattern and "l" in pattern:
2643            psqr = [p3, p2, p1]
2644        elif "b" in pattern and "t" in pattern:
2645            psqr = [p0, p1, p3, p2]
2646            disconnected = True
2647        elif "r" in pattern and "l" in pattern:
2648            psqr = [p0, p3, p1, p2]
2649            disconnected = True
2650        elif "b" in pattern:
2651            psqr = [p0, p1]
2652        elif "r" in pattern:
2653            psqr = [p1, p2]
2654        elif "t" in pattern:
2655            psqr = [p3, p2]
2656        elif "l" in pattern:
2657            psqr = [p0, p3]
2658        else:
2659            vedo.printc("Error in RendererFrame: pattern not recognized", pattern, c='r')
2660       
2661        ppoints = vtki.vtkPoints()  # Generate the polyline
2662        for i, pt in enumerate(psqr):
2663            ppoints.InsertPoint(i, pt[0], pt[1], 0)
2664
2665        lines = vtki.vtkCellArray()
2666        if disconnected:
2667            lines.InsertNextCell(2)
2668            lines.InsertCellPoint(0)
2669            lines.InsertCellPoint(1)
2670            lines.InsertNextCell(2)
2671            lines.InsertCellPoint(2)
2672            lines.InsertCellPoint(3)
2673        else:
2674            n = len(psqr)
2675            lines.InsertNextCell(n)
2676            for i in range(n):
2677                lines.InsertCellPoint(i)
2678
2679        polydata = vtki.vtkPolyData()
2680        polydata.SetPoints(ppoints)
2681        polydata.SetLines(lines)
2682
2683        super().__init__(polydata)
2684        self.name = "RendererFrame"
2685        
2686        self.coordinate = vtki.vtkCoordinate()
2687        self.coordinate.SetCoordinateSystemToNormalizedViewport()
2688        self.mapper.SetTransformCoordinate(self.coordinate)
2689
2690        self.set_position_coordinates([0, 1], [1, 1])
2691        self.color(c)
2692        self.alpha(alpha)
2693        self.lw(lw)

Add a line around the renderer subwindow.

RendererFrame(c='k', alpha=None, lw=None, padding=None, pattern='brtl')
2591    def __init__(self, c="k", alpha=None, lw=None, padding=None, pattern="brtl"):
2592        """
2593        Add a line around the renderer subwindow.
2594
2595        Arguments:
2596            c : (color)
2597                color of the line.
2598            alpha : (float)
2599                opacity.
2600            lw : (int)
2601                line width in pixels.
2602            padding : (int)
2603                padding in pixel units.
2604            pattern : (str)
2605                combination of characters `b` for bottom, `r` for right,
2606                `t` for top, `l` for left.
2607        """
2608        if lw is None:
2609            lw = settings.renderer_frame_width
2610
2611        if alpha is None:
2612            alpha = settings.renderer_frame_alpha
2613
2614        if padding is None:
2615            padding = settings.renderer_frame_padding
2616
2617        if lw == 0 or alpha == 0:
2618            return
2619        c = get_color(c)
2620
2621        a = padding
2622        b = 1 - padding
2623        p0 = [a, a]
2624        p1 = [b, a]
2625        p2 = [b, b]
2626        p3 = [a, b]
2627        disconnected = False
2628        if "b" in pattern and "r" in pattern and "t" in pattern and "l" in pattern:
2629            psqr = [p0, p1, p2, p3, p0]
2630        elif "b" in pattern and "r" in pattern and "t" in pattern:
2631            psqr = [p0, p1, p2, p3]
2632        elif "b" in pattern and "r" in pattern and "l" in pattern:
2633            psqr = [p3, p0, p1, p2]
2634        elif "b" in pattern and "t" in pattern and "l" in pattern:
2635            psqr = [p2, p3, p0, p1]
2636        elif "b" in pattern and "r" in pattern:
2637            psqr = [p0, p1, p2]
2638        elif "b" in pattern and "l" in pattern:
2639            psqr = [p3, p0, p1]
2640        elif "r" in pattern and "t" in pattern:
2641            psqr = [p1, p2, p3]
2642        elif "t" in pattern and "l" in pattern:
2643            psqr = [p3, p2, p1]
2644        elif "b" in pattern and "t" in pattern:
2645            psqr = [p0, p1, p3, p2]
2646            disconnected = True
2647        elif "r" in pattern and "l" in pattern:
2648            psqr = [p0, p3, p1, p2]
2649            disconnected = True
2650        elif "b" in pattern:
2651            psqr = [p0, p1]
2652        elif "r" in pattern:
2653            psqr = [p1, p2]
2654        elif "t" in pattern:
2655            psqr = [p3, p2]
2656        elif "l" in pattern:
2657            psqr = [p0, p3]
2658        else:
2659            vedo.printc("Error in RendererFrame: pattern not recognized", pattern, c='r')
2660       
2661        ppoints = vtki.vtkPoints()  # Generate the polyline
2662        for i, pt in enumerate(psqr):
2663            ppoints.InsertPoint(i, pt[0], pt[1], 0)
2664
2665        lines = vtki.vtkCellArray()
2666        if disconnected:
2667            lines.InsertNextCell(2)
2668            lines.InsertCellPoint(0)
2669            lines.InsertCellPoint(1)
2670            lines.InsertNextCell(2)
2671            lines.InsertCellPoint(2)
2672            lines.InsertCellPoint(3)
2673        else:
2674            n = len(psqr)
2675            lines.InsertNextCell(n)
2676            for i in range(n):
2677                lines.InsertCellPoint(i)
2678
2679        polydata = vtki.vtkPolyData()
2680        polydata.SetPoints(ppoints)
2681        polydata.SetLines(lines)
2682
2683        super().__init__(polydata)
2684        self.name = "RendererFrame"
2685        
2686        self.coordinate = vtki.vtkCoordinate()
2687        self.coordinate.SetCoordinateSystemToNormalizedViewport()
2688        self.mapper.SetTransformCoordinate(self.coordinate)
2689
2690        self.set_position_coordinates([0, 1], [1, 1])
2691        self.color(c)
2692        self.alpha(alpha)
2693        self.lw(lw)

Add a line around the renderer subwindow.

Arguments:
  • c : (color) color of the line.
  • alpha : (float) opacity.
  • lw : (int) line width in pixels.
  • padding : (int) padding in pixel units.
  • pattern : (str) combination of characters b for bottom, r for right, t for top, l for left.
class Ruler2D(vtkmodules.vtkRenderingAnnotation.vtkAxisActor2D):
3116class Ruler2D(vtki.vtkAxisActor2D):
3117    """
3118    Create a ruler with tick marks, labels and a title.
3119    """
3120
3121    def __init__(
3122        self,
3123        lw=2,
3124        ticks=True,
3125        labels=False,
3126        c="k",
3127        alpha=1,
3128        title="",
3129        font="Calco",
3130        font_size=24,
3131        bc=None,
3132    ):
3133        """
3134        Create a ruler with tick marks, labels and a title.
3135
3136        Ruler2D is a 2D actor; that is, it is drawn on the overlay
3137        plane and is not occluded by 3D geometry.
3138        To use this class, specify two points defining the start and end
3139        with update_points() as 3D points.
3140
3141        This class decides decides how to create reasonable tick
3142        marks and labels.
3143
3144        Labels are drawn on the "right" side of the axis.
3145        The "right" side is the side of the axis on the right.
3146        The way the labels and title line up with the axis and tick marks
3147        depends on whether the line is considered horizontal or vertical.
3148
3149        Arguments:
3150            lw : (int)
3151                width of the line in pixel units
3152            ticks : (bool)
3153                control if drawing the tick marks
3154            labels : (bool)
3155                control if drawing the numeric labels
3156            c : (color)
3157                color of the object
3158            alpha : (float)
3159                opacity of the object
3160            title : (str)
3161                title of the ruler
3162            font : (str)
3163                font face name. Check [available fonts here](https://vedo.embl.es/fonts).
3164            font_size : (int)
3165                font size
3166            bc : (color)
3167                background color of the title
3168
3169        Example:
3170            ```python
3171            from vedo  import *
3172            plt = Plotter(axes=1, interactive=False)
3173            plt.show(Cube())
3174            rul = Ruler2D()
3175            rul.set_points([0,0,0], [0.5,0.5,0.5])
3176            plt.add(rul)
3177            plt.interactive().close()
3178            ```
3179            ![](https://vedo.embl.es/images/feats/dist_tool.png)
3180        """
3181        super().__init__()
3182        self.name = "Ruler2D"
3183
3184        plt = vedo.plotter_instance
3185        if not plt:
3186            vedo.logger.error("Ruler2D need to initialize Plotter first.")
3187            raise RuntimeError()
3188
3189        self.p0 = [0, 0, 0]
3190        self.p1 = [0, 0, 0]
3191        self.distance = 0
3192        self.title = title
3193
3194        prop = self.GetProperty()
3195        tprop = self.GetTitleTextProperty()
3196
3197        self.SetTitle(title)
3198        self.SetNumberOfLabels(9)
3199
3200        if not font:
3201            font = settings.default_font
3202        if font.lower() == "courier":
3203            tprop.SetFontFamilyToCourier()
3204        elif font.lower() == "times":
3205            tprop.SetFontFamilyToTimes()
3206        elif font.lower() == "arial":
3207            tprop.SetFontFamilyToArial()
3208        else:
3209            tprop.SetFontFamily(vtki.VTK_FONT_FILE)
3210            tprop.SetFontFile(utils.get_font_path(font))
3211        tprop.SetFontSize(font_size)
3212        tprop.BoldOff()
3213        tprop.ItalicOff()
3214        tprop.ShadowOff()
3215        tprop.SetColor(get_color(c))
3216        tprop.SetOpacity(alpha)
3217        if bc is not None:
3218            bc = get_color(bc)
3219            tprop.SetBackgroundColor(bc)
3220            tprop.SetBackgroundOpacity(alpha)
3221
3222        lprop = vtki.vtkTextProperty()
3223        lprop.ShallowCopy(tprop)
3224        self.SetLabelTextProperty(lprop)
3225
3226        self.SetLabelFormat("%0.3g")
3227        self.SetTickVisibility(ticks)
3228        self.SetLabelVisibility(labels)
3229        prop.SetLineWidth(lw)
3230        prop.SetColor(get_color(c))
3231
3232        self.renderer = plt.renderer
3233        self.cid = plt.interactor.AddObserver("RenderEvent", self._update_viz, 1.0)
3234
3235    def color(self, c) -> Self:
3236        """Assign a new color."""
3237        c = get_color(c)
3238        self.GetTitleTextProperty().SetColor(c)
3239        self.GetLabelTextProperty().SetColor(c)
3240        self.GetProperty().SetColor(c)
3241        return self
3242
3243    def off(self) -> None:
3244        """Switch off the ruler completely."""
3245        self.renderer.RemoveObserver(self.cid)
3246        self.renderer.RemoveActor(self)
3247
3248    def set_points(self, p0, p1) -> Self:
3249        """Set new values for the ruler start and end points."""
3250        self.p0 = np.asarray(p0)
3251        self.p1 = np.asarray(p1)
3252        self._update_viz(0, 0)
3253        return self
3254
3255    def _update_viz(self, _evt, _name) -> None:
3256        ren = self.renderer
3257        view_size = np.array(ren.GetSize())
3258
3259        ren.SetWorldPoint(*self.p0, 1)
3260        ren.WorldToDisplay()
3261        disp_point1 = ren.GetDisplayPoint()[:2]
3262        disp_point1 = np.array(disp_point1) / view_size
3263
3264        ren.SetWorldPoint(*self.p1, 1)
3265        ren.WorldToDisplay()
3266        disp_point2 = ren.GetDisplayPoint()[:2]
3267        disp_point2 = np.array(disp_point2) / view_size
3268
3269        self.SetPoint1(*disp_point1)
3270        self.SetPoint2(*disp_point2)
3271        self.distance = np.linalg.norm(self.p1 - self.p0)
3272        self.SetRange(0.0, float(self.distance))
3273        if not self.title:
3274            self.SetTitle(utils.precision(self.distance, 3))

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

Ruler2D( lw=2, ticks=True, labels=False, c='k', alpha=1, title='', font='Calco', font_size=24, bc=None)
3121    def __init__(
3122        self,
3123        lw=2,
3124        ticks=True,
3125        labels=False,
3126        c="k",
3127        alpha=1,
3128        title="",
3129        font="Calco",
3130        font_size=24,
3131        bc=None,
3132    ):
3133        """
3134        Create a ruler with tick marks, labels and a title.
3135
3136        Ruler2D is a 2D actor; that is, it is drawn on the overlay
3137        plane and is not occluded by 3D geometry.
3138        To use this class, specify two points defining the start and end
3139        with update_points() as 3D points.
3140
3141        This class decides decides how to create reasonable tick
3142        marks and labels.
3143
3144        Labels are drawn on the "right" side of the axis.
3145        The "right" side is the side of the axis on the right.
3146        The way the labels and title line up with the axis and tick marks
3147        depends on whether the line is considered horizontal or vertical.
3148
3149        Arguments:
3150            lw : (int)
3151                width of the line in pixel units
3152            ticks : (bool)
3153                control if drawing the tick marks
3154            labels : (bool)
3155                control if drawing the numeric labels
3156            c : (color)
3157                color of the object
3158            alpha : (float)
3159                opacity of the object
3160            title : (str)
3161                title of the ruler
3162            font : (str)
3163                font face name. Check [available fonts here](https://vedo.embl.es/fonts).
3164            font_size : (int)
3165                font size
3166            bc : (color)
3167                background color of the title
3168
3169        Example:
3170            ```python
3171            from vedo  import *
3172            plt = Plotter(axes=1, interactive=False)
3173            plt.show(Cube())
3174            rul = Ruler2D()
3175            rul.set_points([0,0,0], [0.5,0.5,0.5])
3176            plt.add(rul)
3177            plt.interactive().close()
3178            ```
3179            ![](https://vedo.embl.es/images/feats/dist_tool.png)
3180        """
3181        super().__init__()
3182        self.name = "Ruler2D"
3183
3184        plt = vedo.plotter_instance
3185        if not plt:
3186            vedo.logger.error("Ruler2D need to initialize Plotter first.")
3187            raise RuntimeError()
3188
3189        self.p0 = [0, 0, 0]
3190        self.p1 = [0, 0, 0]
3191        self.distance = 0
3192        self.title = title
3193
3194        prop = self.GetProperty()
3195        tprop = self.GetTitleTextProperty()
3196
3197        self.SetTitle(title)
3198        self.SetNumberOfLabels(9)
3199
3200        if not font:
3201            font = settings.default_font
3202        if font.lower() == "courier":
3203            tprop.SetFontFamilyToCourier()
3204        elif font.lower() == "times":
3205            tprop.SetFontFamilyToTimes()
3206        elif font.lower() == "arial":
3207            tprop.SetFontFamilyToArial()
3208        else:
3209            tprop.SetFontFamily(vtki.VTK_FONT_FILE)
3210            tprop.SetFontFile(utils.get_font_path(font))
3211        tprop.SetFontSize(font_size)
3212        tprop.BoldOff()
3213        tprop.ItalicOff()
3214        tprop.ShadowOff()
3215        tprop.SetColor(get_color(c))
3216        tprop.SetOpacity(alpha)
3217        if bc is not None:
3218            bc = get_color(bc)
3219            tprop.SetBackgroundColor(bc)
3220            tprop.SetBackgroundOpacity(alpha)
3221
3222        lprop = vtki.vtkTextProperty()
3223        lprop.ShallowCopy(tprop)
3224        self.SetLabelTextProperty(lprop)
3225
3226        self.SetLabelFormat("%0.3g")
3227        self.SetTickVisibility(ticks)
3228        self.SetLabelVisibility(labels)
3229        prop.SetLineWidth(lw)
3230        prop.SetColor(get_color(c))
3231
3232        self.renderer = plt.renderer
3233        self.cid = plt.interactor.AddObserver("RenderEvent", self._update_viz, 1.0)

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

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

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

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

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

def color(self, c) -> Self:
3235    def color(self, c) -> Self:
3236        """Assign a new color."""
3237        c = get_color(c)
3238        self.GetTitleTextProperty().SetColor(c)
3239        self.GetLabelTextProperty().SetColor(c)
3240        self.GetProperty().SetColor(c)
3241        return self

Assign a new color.

def off(self) -> None:
3243    def off(self) -> None:
3244        """Switch off the ruler completely."""
3245        self.renderer.RemoveObserver(self.cid)
3246        self.renderer.RemoveActor(self)

Switch off the ruler completely.

def set_points(self, p0, p1) -> Self:
3248    def set_points(self, p0, p1) -> Self:
3249        """Set new values for the ruler start and end points."""
3250        self.p0 = np.asarray(p0)
3251        self.p1 = np.asarray(p1)
3252        self._update_viz(0, 0)
3253        return self

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:
2851def Ruler3D(
2852    p1,
2853    p2,
2854    units_scale=1,
2855    label="",
2856    s=None,
2857    font=None,
2858    italic=0,
2859    prefix="",
2860    units="",  # eg.'μm'
2861    c=(0.2, 0.1, 0.1),
2862    alpha=1,
2863    lw=1,
2864    precision=3,
2865    label_rotation=0,
2866    axis_rotation=0,
2867    tick_angle=90,
2868) -> Mesh:
2869    """
2870    Build a 3D ruler to indicate the distance of two points p1 and p2.
2871
2872    Arguments:
2873        label : (str)
2874            alternative fixed label to be shown
2875        units_scale : (float)
2876            factor to scale units (e.g. μm to mm)
2877        s : (float)
2878            size of the label
2879        font : (str)
2880            font face.  Check [available fonts here](https://vedo.embl.es/fonts).
2881        italic : (float)
2882            italicness of the font in the range [0,1]
2883        units : (str)
2884            string to be appended to the numeric value
2885        lw : (int)
2886            line width in pixel units
2887        precision : (int)
2888            nr of significant digits to be shown
2889        label_rotation : (float)
2890            initial rotation of the label around the z-axis
2891        axis_rotation : (float)
2892            initial rotation of the line around the main axis
2893        tick_angle : (float)
2894            initial rotation of the line around the main axis
2895
2896    Examples:
2897        - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2898
2899        ![](https://vedo.embl.es/images/pyplot/goniometer.png)
2900    """
2901
2902    if units_scale != 1.0 and units == "":
2903        raise ValueError(
2904            "When setting 'units_scale' to a value other than 1, "
2905            + "a 'units' arguments must be specified."
2906        )
2907
2908    try:
2909        p1 = p1.pos()
2910    except AttributeError:
2911        pass
2912
2913    try:
2914        p2 = p2.pos()
2915    except AttributeError:
2916        pass
2917
2918    if len(p1) == 2:
2919        p1 = [p1[0], p1[1], 0.0]
2920    if len(p2) == 2:
2921        p2 = [p2[0], p2[1], 0.0]
2922
2923    p1, p2 = np.asarray(p1), np.asarray(p2)
2924    q1, q2 = [0, 0, 0], [utils.mag(p2 - p1), 0, 0]
2925    q1, q2 = np.array(q1), np.array(q2)
2926    v = q2 - q1
2927    d = utils.mag(v) * units_scale
2928
2929    pos = np.array(p1)
2930    p1 = p1 - pos
2931    p2 = p2 - pos
2932
2933    if s is None:
2934        s = d * 0.02 * (1 / units_scale)
2935
2936    if not label:
2937        label = str(d)
2938        if precision:
2939            label = utils.precision(d, precision)
2940    if prefix:
2941        label = prefix + "~" + label
2942    if units:
2943        label += "~" + units
2944
2945    lb = shapes.Text3D(label, s=s, font=font, italic=italic, justify="center")
2946    if label_rotation:
2947        lb.rotate_z(label_rotation)
2948    lb.pos((q1 + q2) / 2)
2949
2950    x0, x1 = lb.xbounds()
2951    gap = [(x1 - x0) / 2, 0, 0]
2952    pc1 = (v / 2 - gap) * 0.9 + q1
2953    pc2 = q2 - (v / 2 - gap) * 0.9
2954
2955    lc1 = shapes.Line(q1 - v / 50, pc1).lw(lw)
2956    lc2 = shapes.Line(q2 + v / 50, pc2).lw(lw)
2957
2958    zs = np.array([0, d / 50 * (1 / units_scale), 0])
2959    ml1 = shapes.Line(-zs, zs).lw(lw)
2960    ml2 = shapes.Line(-zs, zs).lw(lw)
2961    ml1.rotate_z(tick_angle - 90).pos(q1)
2962    ml2.rotate_z(tick_angle - 90).pos(q2)
2963
2964    c1 = shapes.Circle(q1, r=d / 180 * (1 / units_scale), res=24)
2965    c2 = shapes.Circle(q2, r=d / 180 * (1 / units_scale), res=24)
2966
2967    macts = merge(lb, lc1, lc2, c1, c2, ml1, ml2)
2968    macts.c(c).alpha(alpha)
2969    macts.properties.SetLineWidth(lw)
2970    macts.properties.LightingOff()
2971    macts.actor.UseBoundsOff()
2972    macts.rotate_x(axis_rotation)
2973    macts.reorient(q2 - q1, p2 - p1)
2974    macts.pos(pos)
2975    macts.bc("tomato").pickable(False)
2976    return macts

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

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

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]:
2979def RulerAxes(
2980    inputobj,
2981    xtitle="",
2982    ytitle="",
2983    ztitle="",
2984    xlabel="",
2985    ylabel="",
2986    zlabel="",
2987    xpadding=0.05,
2988    ypadding=0.04,
2989    zpadding=0,
2990    font="Normografo",
2991    s=None,
2992    italic=0,
2993    units="",
2994    c=(0.2, 0, 0),
2995    alpha=1,
2996    lw=1,
2997    precision=3,
2998    label_rotation=0,
2999    xaxis_rotation=0,
3000    yaxis_rotation=0,
3001    zaxis_rotation=0,
3002    xycross=True,
3003) -> Union[Mesh, None]:
3004    """
3005    A 3D ruler axes to indicate the sizes of the input scene or object.
3006
3007    Arguments:
3008        xtitle : (str)
3009            name of the axis or title
3010        xlabel : (str)
3011            alternative fixed label to be shown instead of the distance
3012        s : (float)
3013            size of the label
3014        font : (str)
3015            font face. Check [available fonts here](https://vedo.embl.es/fonts).
3016        italic : (float)
3017            italicness of the font in the range [0,1]
3018        units : (str)
3019            string to be appended to the numeric value
3020        lw : (int)
3021            line width in pixel units
3022        precision : (int)
3023            nr of significant digits to be shown
3024        label_rotation : (float)
3025            initial rotation of the label around the z-axis
3026        [x,y,z]axis_rotation : (float)
3027            initial rotation of the line around the main axis in degrees
3028        xycross : (bool)
3029            show two back crossing lines in the xy plane
3030
3031    Examples:
3032        - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
3033    """
3034    if utils.is_sequence(inputobj):
3035        x0, x1, y0, y1, z0, z1 = inputobj
3036    else:
3037        x0, x1, y0, y1, z0, z1 = inputobj.bounds()
3038    dx, dy, dz = (y1 - y0) * xpadding, (x1 - x0) * ypadding, (y1 - y0) * zpadding
3039    d = np.sqrt((y1 - y0) ** 2 + (x1 - x0) ** 2 + (z1 - z0) ** 2)
3040
3041    if not d:
3042        return None
3043
3044    if s is None:
3045        s = d / 75
3046
3047    acts, rx, ry = [], None, None
3048    if xtitle is not None and (x1 - x0) / d > 0.1:
3049        rx = Ruler3D(
3050            [x0, y0 - dx, z0],
3051            [x1, y0 - dx, z0],
3052            s=s,
3053            font=font,
3054            precision=precision,
3055            label_rotation=label_rotation,
3056            axis_rotation=xaxis_rotation,
3057            lw=lw,
3058            italic=italic,
3059            prefix=xtitle,
3060            label=xlabel,
3061            units=units,
3062        )
3063        acts.append(rx)
3064
3065    if ytitle is not None and (y1 - y0) / d > 0.1:
3066        ry = Ruler3D(
3067            [x1 + dy, y0, z0],
3068            [x1 + dy, y1, z0],
3069            s=s,
3070            font=font,
3071            precision=precision,
3072            label_rotation=label_rotation,
3073            axis_rotation=yaxis_rotation,
3074            lw=lw,
3075            italic=italic,
3076            prefix=ytitle,
3077            label=ylabel,
3078            units=units,
3079        )
3080        acts.append(ry)
3081
3082    if ztitle is not None and (z1 - z0) / d > 0.1:
3083        rz = Ruler3D(
3084            [x0 - dy, y0 + dz, z0],
3085            [x0 - dy, y0 + dz, z1],
3086            s=s,
3087            font=font,
3088            precision=precision,
3089            label_rotation=label_rotation,
3090            axis_rotation=zaxis_rotation + 90,
3091            lw=lw,
3092            italic=italic,
3093            prefix=ztitle,
3094            label=zlabel,
3095            units=units,
3096        )
3097        acts.append(rz)
3098
3099    if xycross and rx and ry:
3100        lx = shapes.Line([x0, y0, z0], [x0, y1 + dx, z0])
3101        ly = shapes.Line([x0 - dy, y1, z0], [x1, y1, z0])
3102        d = min((x1 - x0), (y1 - y0)) / 200
3103        cxy = shapes.Circle([x0, y1, z0], r=d, res=15)
3104        acts.extend([lx, ly, cxy])
3105
3106    macts = merge(acts)
3107    if not macts:
3108        return None
3109    macts.c(c).alpha(alpha).bc("t")
3110    macts.actor.UseBoundsOff()
3111    macts.actor.PickableOff()
3112    return macts

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

Arguments:
  • xtitle : (str) name of the axis or title
  • xlabel : (str) alternative fixed label to be shown instead of the distance
  • s : (float) size of the label
  • font : (str) font face. Check available fonts here.
  • italic : (float) italicness of the font in the range [0,1]
  • units : (str) string to be appended to the numeric value
  • lw : (int) line width in pixel units
  • precision : (int) nr of significant digits to be shown
  • label_rotation : (float) initial rotation of the label around the z-axis
  • [x,y,z]axis_rotation : (float) initial rotation of the line around the main axis in degrees
  • xycross : (bool) show two back crossing lines in the xy plane
Examples:
class DistanceTool(vedo.assembly.Group):
3278class DistanceTool(Group):
3279    """
3280    Create a tool to measure the distance between two clicked points.
3281    """
3282
3283    def __init__(self, plotter=None, c="k", lw=2):
3284        """
3285        Create a tool to measure the distance between two clicked points.
3286
3287        Example:
3288            ```python
3289            from vedo import *
3290            mesh = ParametricShape("RandomHills").c("red5")
3291            plt = Plotter(axes=1)
3292            dtool = DistanceTool()
3293            dtool.on()
3294            plt.show(mesh, dtool)
3295            dtool.off()
3296            ```
3297            ![](https://vedo.embl.es/images/feats/dist_tool.png)
3298        """
3299        super().__init__()
3300        self.name = "DistanceTool"
3301
3302        self.p0 = [0, 0, 0]
3303        self.p1 = [0, 0, 0]
3304        self.distance = 0
3305        if plotter is None:
3306            plotter = vedo.plotter_instance
3307        self.plotter = plotter
3308        # define self.callback as callable function:
3309        self.callback = lambda x: None
3310        self.cid = None
3311        self.color = c
3312        self.linewidth = lw
3313        self.toggle = True
3314        self.ruler = None
3315        self.title = ""
3316
3317    def on(self) -> Self:
3318        """Switch tool on."""
3319        self.cid = self.plotter.add_callback("click", self._onclick)
3320        self.VisibilityOn()
3321        self.plotter.render()
3322        return self
3323
3324    def off(self) -> None:
3325        """Switch tool off."""
3326        self.plotter.remove_callback(self.cid)
3327        self.VisibilityOff()
3328        self.ruler.off()
3329        self.plotter.render()
3330
3331    def _onclick(self, event):
3332        if not event.actor:
3333            return
3334
3335        self.clear()
3336
3337        acts = []
3338        if self.toggle:
3339            self.p0 = event.picked3d
3340            acts.append(Point(self.p0, c=self.color))
3341        else:
3342            self.p1 = event.picked3d
3343            self.distance = np.linalg.norm(self.p1 - self.p0)
3344            acts.append(Point(self.p0, c=self.color))
3345            acts.append(Point(self.p1, c=self.color))
3346            self.ruler = Ruler2D(c=self.color)
3347            self.ruler.set_points(self.p0, self.p1)
3348            acts.append(self.ruler)
3349
3350            if self.callback is not None:
3351                self.callback(event)
3352
3353        for a in acts:
3354            try:
3355                self += a.actor
3356            except AttributeError:
3357                self += a
3358        self.toggle = not self.toggle

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

DistanceTool(plotter=None, c='k', lw=2)
3283    def __init__(self, plotter=None, c="k", lw=2):
3284        """
3285        Create a tool to measure the distance between two clicked points.
3286
3287        Example:
3288            ```python
3289            from vedo import *
3290            mesh = ParametricShape("RandomHills").c("red5")
3291            plt = Plotter(axes=1)
3292            dtool = DistanceTool()
3293            dtool.on()
3294            plt.show(mesh, dtool)
3295            dtool.off()
3296            ```
3297            ![](https://vedo.embl.es/images/feats/dist_tool.png)
3298        """
3299        super().__init__()
3300        self.name = "DistanceTool"
3301
3302        self.p0 = [0, 0, 0]
3303        self.p1 = [0, 0, 0]
3304        self.distance = 0
3305        if plotter is None:
3306            plotter = vedo.plotter_instance
3307        self.plotter = plotter
3308        # define self.callback as callable function:
3309        self.callback = lambda x: None
3310        self.cid = None
3311        self.color = c
3312        self.linewidth = lw
3313        self.toggle = True
3314        self.ruler = None
3315        self.title = ""

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

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

def on(self) -> Self:
3317    def on(self) -> Self:
3318        """Switch tool on."""
3319        self.cid = self.plotter.add_callback("click", self._onclick)
3320        self.VisibilityOn()
3321        self.plotter.render()
3322        return self

Switch tool on.

def off(self) -> None:
3324    def off(self) -> None:
3325        """Switch tool off."""
3326        self.plotter.remove_callback(self.cid)
3327        self.VisibilityOff()
3328        self.ruler.off()
3329        self.plotter.render()

Switch tool off.

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

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

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

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

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

Return the current interactor.

def add(self, pt) -> SplineTool:
763    def add(self, pt) -> "SplineTool":
764        """
765        Add one point at a specified position in space if 3D,
766        or 2D screen-display position if 2D.
767        """
768        if len(pt) == 2:
769            self.representation.AddNodeAtDisplayPosition(int(pt[0]), int(pt[1]))
770        else:
771            self.representation.AddNodeAtWorldPosition(pt)
772        return self

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

def add_observer(self, event, func, priority=1) -> int:
774    def add_observer(self, event, func, priority=1) -> int:
775        """Add an observer to the widget."""
776        event = utils.get_vtk_name_event(event)
777        cid = self.AddObserver(event, func, priority)
778        return cid

Add an observer to the widget.

def remove(self, i: int) -> SplineTool:
780    def remove(self, i: int) -> "SplineTool":
781        """Remove specific node by its index"""
782        self.representation.DeleteNthNode(i)
783        return self

Remove specific node by its index

def on(self) -> SplineTool:
785    def on(self) -> "SplineTool":
786        """Activate/Enable the tool"""
787        self.On()
788        self.Render()
789        return self

Activate/Enable the tool

def off(self) -> SplineTool:
791    def off(self) -> "SplineTool":
792        """Disactivate/Disable the tool"""
793        self.Off()
794        self.Render()
795        return self

Disactivate/Disable the tool

def render(self) -> SplineTool:
797    def render(self) -> "SplineTool":
798        """Render the spline"""
799        self.Render()
800        return self

Render the spline

def spline(self) -> vedo.shapes.Line:
806    def spline(self) -> vedo.Line:
807        """Return the vedo.Spline object."""
808        self.representation.SetClosedLoop(self.closed)
809        self.representation.BuildRepresentation()
810        pd = self.representation.GetContourRepresentationAsPolyData()
811        ln = vedo.Line(pd, lw=2, c="k")
812        return ln

Return the vedo.Spline object.

def nodes(self, onscreen=False) -> numpy.ndarray:
814    def nodes(self, onscreen=False) -> np.ndarray:
815        """Return the current position in space (or on 2D screen-display) of the spline nodes."""
816        n = self.representation.GetNumberOfNodes()
817        pts = []
818        for i in range(n):
819            p = [0.0, 0.0, 0.0]
820            if onscreen:
821                self.representation.GetNthNodeDisplayPosition(i, p)
822            else:
823                self.representation.GetNthNodeWorldPosition(i, p)
824            pts.append(p)
825        return np.array(pts)

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

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

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

  • Any object can be input rather than just 2D images
  • The widget fires pick events at the input prop to decide where to move its handles
  • The widget has 2D glyphs for handles instead of 3D spheres.
The button actions and key modifiers are as follows for controlling the widget:

1) left button click over the image, hold and drag draws a free hand line.

2) left button click and release erases the widget line, if it exists, and repositions the first handle.

3) middle button click starts a snap drawn line. The line is terminated by clicking the middle button while ressing the ctrl key.

4) when tracing a continuous or snap drawn line, if the last cursor position is within a specified tolerance to the first handle, the widget line will form a closed loop.

5) right button clicking and holding on any handle that is part of a snap drawn line allows handle dragging: existing line segments are updated accordingly. If the path is open and closing_radius is set, the path can be closed by repositioning the first and last points over one another.

6) Ctrl + right button down on any handle will erase it: existing snap drawn line segments are updated accordingly. If the line was formed by continuous tracing, the line is deleted leaving one handle.

7) Shift + right button down on any snap drawn line segment will insert a handle at the cursor position. The line segment is split accordingly.

Arguments:
  • obj : vtkProp The prop to trace on.
  • c : str, optional The color of the line. The default is "green5".
  • lw : int, optional The line width. The default is 4.
  • closed : bool, optional Whether to close the line. The default is False.
  • snap_to_image : bool, optional Whether to snap to the image. The default is False.
Example:
DrawingWidget(obj, c='green5', lw=4, closed=False, snap_to_image=False)
875    def __init__(self, obj, c="green5", lw=4, closed=False, snap_to_image=False):
876
877        self.widget = vtki.new("ImageTracerWidget")
878        self.name = "DrawingWidget"
879
880        self.line = None
881        self.line_properties = self.widget.GetLineProperty()
882        self.line_properties.SetColor(vedo.get_color(c))
883        self.line_properties.SetLineWidth(lw)
884        self.callback_id = None
885        self.event_name = "EndInteractionEvent"
886
887        if vedo.plotter_instance:
888            self.widget.SetInteractor(vedo.plotter_instance.interactor)
889        if vedo.plotter_instance.renderer:
890            self.widget.SetDefaultRenderer(vedo.plotter_instance.renderer)
891
892        try:
893            self.widget.SetViewProp(obj.actor)
894        except AttributeError:
895            self.widget.SetViewProp(obj)
896
897        if closed:
898            closing_radius = 1e10
899            self.widget.SetAutoClose(1)
900            self.widget.SetCaptureRadius(closing_radius)
901
902        self.widget.SetProjectToPlane(0)
903        self.widget.SetProjectionNormal(2)  # XY plane
904        self.widget.SetProjectionPosition(0)
905        self.widget.SetSnapToImage(snap_to_image)
def callback(self, widget, _event_name) -> None:
907    def callback(self, widget, _event_name) -> None:
908        """Callback function for the widget."""
909        path = vtki.vtkPolyData()
910        widget.GetPath(path)
911        self.line = vedo.shapes.Line(path, c=self.line_properties.GetColor())
912        # print(f"There are {path.GetNumberOfPoints()} points in the line.")

Callback function for the widget.

def add_observer(self, event, func, priority=1) -> int:
914    def add_observer(self, event, func, priority=1) -> int:
915        """Add an observer to the widget."""
916        event = utils.get_vtk_name_event(event)
917        cid = self.widget.AddObserver(event, func, priority)
918        return cid

Add an observer to the widget.

interactor
920    @property
921    def interactor(self):
922        """Return the interactor for the widget."""
923        return self.widget.GetInteractor()

Return the interactor for the widget.

renderer
930    @property
931    def renderer(self):
932        """Return the renderer for the widget."""
933        return self.widget.GetDefaultRenderer()

Return the renderer for the widget.

def on(self) -> Self:
940    def on(self) -> Self:
941        """Activate/Enable the widget"""
942        self.widget.On()
943        ev_name = vedo.utils.get_vtk_name_event(self.event_name)
944        self.callback_id = self.widget.AddObserver(ev_name, self.callback, 1000)
945        return self

Activate/Enable the widget

def off(self) -> None:
947    def off(self) -> None:
948        """Disactivate/Disable the widget"""
949        self.widget.Off()
950        self.widget.RemoveObserver(self.callback_id)

Disactivate/Disable the widget

def freeze(self, value=True) -> Self:
952    def freeze(self, value=True) -> Self:
953        """Freeze the widget by disabling interaction."""
954        self.widget.SetInteraction(not value)
955        return self

Freeze the widget by disabling interaction.

def remove(self) -> None:
957    def remove(self) -> None:
958        """Remove the widget."""
959        self.widget.Off()
960        self.widget.RemoveObserver(self.callback_id)
961        self.widget.SetInteractor(None)
962        self.line = None
963        self.line_properties = None
964        self.callback_id = None
965        self.widget = None

Remove the widget.

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

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

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

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

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

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

Arguments:
  • fnc : (function) external function to be called by the widget
  • states : (list) the list of possible states, eg. ['On', 'Off']
  • c : (list) the list of colors for each state eg. ['red3', 'green5']
  • bc : (list) the list of background colors for each state
  • pos : (list, str) 2D position in pixels from left-bottom corner
  • size : (int) size of button font
  • font : (str) font type
  • bold : (bool) set bold font face
  • italic : (bool) italic font face
  • alpha : (float) opacity level
  • angle : (float) anticlockwise rotation in degrees
Examples:
def status(self, s=None) -> Button:
649    def status(self, s=None) -> "Button":
650        """Set/Get the status of the button."""
651        if s is None:
652            return self.states[self.status_idx]
653
654        if isinstance(s, str):
655            s = self.states.index(s)
656        self.status_idx = s
657        self.text(self.spacer + self.states[s] + self.spacer)
658        s = s % len(self.bcolors)
659        self.color(self.colors[s])
660        self.background(self.bcolors[s])
661        return self

Set/Get the status of the button.

def switch(self) -> Button:
663    def switch(self) -> "Button":
664        """
665        Change/cycle button status to the next defined status in states list.
666        """
667        self.status_idx = (self.status_idx + 1) % len(self.states)
668        self.status(self.status_idx)
669        return self

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

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

Create a button widget.

ButtonWidget( function, states=(), c='white', bc='green4', alpha=1.0, font='Calco', size=100, plotter=None)
398    def __init__(
399        self,
400        function,
401        states=(),
402        c=("white"),
403        bc=("green4"),
404        alpha=1.0,
405        font="Calco",
406        size=100,
407        plotter=None,
408    ):
409        """
410        Create a button widget.
411
412        States can be either text strings or images.
413
414        Arguments:
415            function : (function)
416                external function to be called by the widget
417            states : (list)
418                the list of possible states, eg. ['On', 'Off']
419            c : (list)
420                the list of colors for each state eg. ['red3', 'green5']
421            bc : (list)
422                the list of background colors for each state
423            alpha : (float)
424                opacity level
425            font : (str)
426                font type
427            size : (int)
428                size of button font
429            plotter : (Plotter)
430                the plotter object to which the widget is added
431
432        Example:
433
434        [buttons3.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons3.py)
435        """
436
437        self.widget = vtki.new("ButtonWidget")
438        self.name = "ButtonWidget"
439
440        self.function = function
441        self.states = states
442        self.colors = c
443        self.background_colors = bc
444        self.plotter = plotter
445        self.size = size
446
447        assert len(states) == len(c),  "states and colors must have the same length"
448        assert len(states) == len(bc), "states and background colors must have the same length"
449
450        self.interactor = None
451        if plotter is not None:
452            self.interactor = plotter.interactor
453            self.widget.SetInteractor(plotter.interactor)
454        else:
455            if vedo.plotter_instance:
456                self.interactor = vedo.plotter_instance.interactor
457                self.widget.SetInteractor(self.interactor)
458
459        self.representation = vtki.new("TexturedButtonRepresentation2D")
460        self.representation.SetNumberOfStates(len(states))
461        for i, state in enumerate(states):
462
463            if isinstance(state, vedo.Image):
464                state = state.dataset
465
466            elif isinstance(state, str):
467                txt = state
468                tp = vtki.vtkTextProperty()
469                tp.BoldOff()
470                tp.FrameOff()
471                col = c[i]
472                tp.SetColor(vedo.get_color(col))
473                tp.ShadowOff()
474                tp.ItalicOff()
475                col = bc[i]
476                tp.SetBackgroundColor(vedo.get_color(col))
477                tp.SetBackgroundOpacity(alpha)
478                tp.UseTightBoundingBoxOff()
479
480                # tp.SetJustificationToLeft()
481                # tp.SetVerticalJustificationToCentered()
482                # tp.SetJustificationToCentered()
483                width, height = 100 * len(txt), 1000
484
485                fpath = vedo.utils.get_font_path(font)
486                tp.SetFontFamily(vtki.VTK_FONT_FILE)
487                tp.SetFontFile(fpath)
488
489                tr = vtki.new("TextRenderer")
490                fs = tr.GetConstrainedFontSize(txt, tp, width, height, 500)
491                tp.SetFontSize(fs)
492
493                img = vtki.vtkImageData()
494                tr.RenderString(tp, txt, img, [width, height], 500)
495                state = img
496
497            self.representation.SetButtonTexture(i, state)
498
499        self.widget.SetRepresentation(self.representation)
500        self.widget.AddObserver("StateChangedEvent", function)

Create a button widget.

States can be either text strings or images.

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

Example:

buttons3.py

def pos(self, pos):
513    def pos(self, pos):
514        """Set the position of the button widget."""
515        assert len(pos) == 2, "pos must be a 2D position"
516        if not self.plotter:
517            vedo.logger.warning("ButtonWidget: pos() can only be used if a Plotter is provided")
518            return self
519        coords = vtki.vtkCoordinate()
520        coords.SetCoordinateSystemToNormalizedDisplay()
521        coords.SetValue(pos[0], pos[1])
522        sz = self.size
523        ren = self.plotter.renderer
524        p = coords.GetComputedDisplayValue(ren)
525        bds = [0, 0, 0, 0, 0, 0]
526        bds[0] = p[0] - sz
527        bds[1] = bds[0] + sz
528        bds[2] = p[1] - sz
529        bds[3] = bds[2] + sz
530        self.representation.SetPlaceFactor(1)
531        self.representation.PlaceWidget(bds)
532        return self

Set the position of the button widget.

def enable(self):
534    def enable(self):
535        """Enable the button widget."""
536        self.widget.On()
537        return self

Enable the button widget.

def disable(self):
539    def disable(self):
540        """Disable the button widget."""
541        self.widget.Off()
542        return self

Disable the button widget.

def next_state(self):
544    def next_state(self):
545        """Change to the next state."""
546        self.representation.NextState()
547        return self

Change to the next state.

state
549    @property
550    def state(self):
551        """Return the current state."""
552        return self.representation.GetState()

Return the current state.

class Flagpost(vtkmodules.vtkRenderingCore.vtkFlagpoleLabel):
 59class Flagpost(vtki.vtkFlagpoleLabel):
 60    """
 61    Create a flag post style element to describe an object.
 62    """
 63
 64    def __init__(
 65        self,
 66        txt="",
 67        base=(0, 0, 0),
 68        top=(0, 0, 1),
 69        s=1,
 70        c="k9",
 71        bc="k1",
 72        alpha=1,
 73        lw=0,
 74        font="Calco",
 75        justify="center-left",
 76        vspacing=1,
 77    ):
 78        """
 79        Create a flag post style element to describe an object.
 80
 81        Arguments:
 82            txt : (str)
 83                Text to display. The default is the filename or the object name.
 84            base : (list)
 85                position of the flag anchor point.
 86            top : (list)
 87                a 3D displacement or offset.
 88            s : (float)
 89                size of the text to be shown
 90            c : (list)
 91                color of text and line
 92            bc : (list)
 93                color of the flag background
 94            alpha : (float)
 95                opacity of text and box.
 96            lw : (int)
 97                line with of box frame. The default is 0.
 98            font : (str)
 99                font name. Use a monospace font for better rendering. The default is "Calco".
100                Type `vedo -r fonts` for a font demo.
101                Check [available fonts here](https://vedo.embl.es/fonts).
102            justify : (str)
103                internal text justification. The default is "center-left".
104            vspacing : (float)
105                vertical spacing between lines.
106
107        Examples:
108            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
109
110            ![](https://vedo.embl.es/images/other/flag_labels2.png)
111        """
112
113        super().__init__()
114
115        self.name = "Flagpost"
116
117        base = utils.make3d(base)
118        top = utils.make3d(top)
119
120        self.SetBasePosition(*base)
121        self.SetTopPosition(*top)
122
123        self.SetFlagSize(s)
124        self.SetInput(txt)
125        self.PickableOff()
126
127        self.GetProperty().LightingOff()
128        self.GetProperty().SetLineWidth(lw + 1)
129
130        prop = self.GetTextProperty()
131        if bc is not None:
132            prop.SetBackgroundColor(get_color(bc))
133
134        prop.SetOpacity(alpha)
135        prop.SetBackgroundOpacity(alpha)
136        if bc is not None and len(bc) == 4:
137            prop.SetBackgroundRGBA(alpha)
138
139        c = get_color(c)
140        prop.SetColor(c)
141        self.GetProperty().SetColor(c)
142
143        prop.SetFrame(bool(lw))
144        prop.SetFrameWidth(lw)
145        prop.SetFrameColor(prop.GetColor())
146
147        prop.SetFontFamily(vtki.VTK_FONT_FILE)
148        fl = utils.get_font_path(font)
149        prop.SetFontFile(fl)
150        prop.ShadowOff()
151        prop.BoldOff()
152        prop.SetOpacity(alpha)
153        prop.SetJustificationToLeft()
154        if "top" in justify:
155            prop.SetVerticalJustificationToTop()
156        if "bottom" in justify:
157            prop.SetVerticalJustificationToBottom()
158        if "cent" in justify:
159            prop.SetVerticalJustificationToCentered()
160            prop.SetJustificationToCentered()
161        if "left" in justify:
162            prop.SetJustificationToLeft()
163        if "right" in justify:
164            prop.SetJustificationToRight()
165        prop.SetLineSpacing(vspacing * 1.2)
166        self.SetUseBounds(False)
167
168    def text(self, value: str) -> Self:
169        """Set the text of the flagpost."""
170        self.SetInput(value)
171        return self
172
173    def on(self) -> Self:
174        """Show the flagpost."""
175        self.VisibilityOn()
176        return self
177
178    def off(self) -> Self:
179        """Hide the flagpost."""
180        self.VisibilityOff()
181        return self
182
183    def toggle(self) -> Self:
184        """Toggle the visibility of the flagpost."""
185        self.SetVisibility(not self.GetVisibility())
186        return self
187
188    def use_bounds(self, value=True) -> Self:
189        """Set the flagpost to keep bounds into account."""
190        self.SetUseBounds(value)
191        return self
192
193    def color(self, c) -> Self:
194        """Set the color of the flagpost."""
195        c = get_color(c)
196        self.GetTextProperty().SetColor(c)
197        self.GetProperty().SetColor(c)
198        return self
199
200    def pos(self, p) -> Self:
201        """Set the position of the flagpost."""
202        p = np.asarray(p)
203        self.top = self.top - self.base + p
204        self.base = p
205        return self
206
207    @property
208    def base(self) -> np.ndarray:
209        """Return the base position of the flagpost."""
210        return np.array(self.GetBasePosition())
211
212    @base.setter
213    def base(self, value):
214        """Set the base position of the flagpost."""
215        self.SetBasePosition(*value)
216
217    @property
218    def top(self) -> np.ndarray:
219        """Return the top position of the flagpost."""
220        return np.array(self.GetTopPosition())
221
222    @top.setter
223    def top(self, value):
224        """Set the top position of the flagpost."""
225        self.SetTopPosition(*value)

Create a flag post style element to describe an object.

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

Create a flag post style element to describe an object.

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

def text(self, value: str) -> Self:
168    def text(self, value: str) -> Self:
169        """Set the text of the flagpost."""
170        self.SetInput(value)
171        return self

Set the text of the flagpost.

def on(self) -> Self:
173    def on(self) -> Self:
174        """Show the flagpost."""
175        self.VisibilityOn()
176        return self

Show the flagpost.

def off(self) -> Self:
178    def off(self) -> Self:
179        """Hide the flagpost."""
180        self.VisibilityOff()
181        return self

Hide the flagpost.

def toggle(self) -> Self:
183    def toggle(self) -> Self:
184        """Toggle the visibility of the flagpost."""
185        self.SetVisibility(not self.GetVisibility())
186        return self

Toggle the visibility of the flagpost.

def use_bounds(self, value=True) -> Self:
188    def use_bounds(self, value=True) -> Self:
189        """Set the flagpost to keep bounds into account."""
190        self.SetUseBounds(value)
191        return self

Set the flagpost to keep bounds into account.

def color(self, c) -> Self:
193    def color(self, c) -> Self:
194        """Set the color of the flagpost."""
195        c = get_color(c)
196        self.GetTextProperty().SetColor(c)
197        self.GetProperty().SetColor(c)
198        return self

Set the color of the flagpost.

def pos(self, p) -> Self:
200    def pos(self, p) -> Self:
201        """Set the position of the flagpost."""
202        p = np.asarray(p)
203        self.top = self.top - self.base + p
204        self.base = p
205        return self

Set the position of the flagpost.

base: numpy.ndarray
207    @property
208    def base(self) -> np.ndarray:
209        """Return the base position of the flagpost."""
210        return np.array(self.GetBasePosition())

Return the base position of the flagpost.

top: numpy.ndarray
217    @property
218    def top(self) -> np.ndarray:
219        """Return the top position of the flagpost."""
220        return np.array(self.GetTopPosition())

Return the top position of the flagpost.

class ProgressBarWidget(vedo.visual.Actor2D):
2698class ProgressBarWidget(Actor2D):
2699    """
2700    Add a progress bar in the rendering window.
2701    """
2702
2703    def __init__(self, n=None, c="blue5", alpha=0.8, lw=10, autohide=True):
2704        """
2705        Add a progress bar window.
2706
2707        Arguments:
2708            n : (int)
2709                number of iterations.
2710                If None, you need to call `update(fraction)` manually.
2711            c : (color)
2712                color of the line.
2713            alpha : (float)
2714                opacity of the line.
2715            lw : (int)
2716                line width in pixels.
2717            autohide : (bool)
2718                if True, hide the progress bar when completed.
2719        """
2720        self.n = 0
2721        self.iterations = n
2722        self.autohide = autohide
2723
2724        ppoints = vtki.vtkPoints()  # Generate the line
2725        psqr = [[0, 0, 0], [1, 0, 0]]
2726        for i, pt in enumerate(psqr):
2727            ppoints.InsertPoint(i, *pt)
2728        lines = vtki.vtkCellArray()
2729        lines.InsertNextCell(len(psqr))
2730        for i in range(len(psqr)):
2731            lines.InsertCellPoint(i)
2732
2733        pd = vtki.vtkPolyData()
2734        pd.SetPoints(ppoints)
2735        pd.SetLines(lines)
2736
2737        super().__init__(pd)
2738        self.name = "ProgressBarWidget"
2739
2740        self.coordinate = vtki.vtkCoordinate()
2741        self.coordinate.SetCoordinateSystemToNormalizedViewport()
2742        self.mapper.SetTransformCoordinate(self.coordinate)
2743
2744        self.alpha(alpha)
2745        self.color(get_color(c))
2746        self.lw(lw * 2)
2747
2748    def update(self, fraction=None) -> Self:
2749        """Update progress bar to fraction of the window width."""
2750        if fraction is None:
2751            if self.iterations is None:
2752                vedo.printc("Error in ProgressBarWindow: must specify iterations", c='r')
2753                return self
2754            self.n += 1
2755            fraction = self.n / self.iterations
2756
2757        if fraction >= 1 and self.autohide:
2758            fraction = 0
2759
2760        psqr = [[0, 0, 0], [fraction, 0, 0]]
2761        vpts = utils.numpy2vtk(psqr, dtype=np.float32)
2762        self.dataset.GetPoints().SetData(vpts)
2763        return self
2764
2765    def reset(self):
2766        """Reset progress bar."""
2767        self.n = 0
2768        self.update(0)
2769        return self

Add a progress bar in the rendering window.

ProgressBarWidget(n=None, c='blue5', alpha=0.8, lw=10, autohide=True)
2703    def __init__(self, n=None, c="blue5", alpha=0.8, lw=10, autohide=True):
2704        """
2705        Add a progress bar window.
2706
2707        Arguments:
2708            n : (int)
2709                number of iterations.
2710                If None, you need to call `update(fraction)` manually.
2711            c : (color)
2712                color of the line.
2713            alpha : (float)
2714                opacity of the line.
2715            lw : (int)
2716                line width in pixels.
2717            autohide : (bool)
2718                if True, hide the progress bar when completed.
2719        """
2720        self.n = 0
2721        self.iterations = n
2722        self.autohide = autohide
2723
2724        ppoints = vtki.vtkPoints()  # Generate the line
2725        psqr = [[0, 0, 0], [1, 0, 0]]
2726        for i, pt in enumerate(psqr):
2727            ppoints.InsertPoint(i, *pt)
2728        lines = vtki.vtkCellArray()
2729        lines.InsertNextCell(len(psqr))
2730        for i in range(len(psqr)):
2731            lines.InsertCellPoint(i)
2732
2733        pd = vtki.vtkPolyData()
2734        pd.SetPoints(ppoints)
2735        pd.SetLines(lines)
2736
2737        super().__init__(pd)
2738        self.name = "ProgressBarWidget"
2739
2740        self.coordinate = vtki.vtkCoordinate()
2741        self.coordinate.SetCoordinateSystemToNormalizedViewport()
2742        self.mapper.SetTransformCoordinate(self.coordinate)
2743
2744        self.alpha(alpha)
2745        self.color(get_color(c))
2746        self.lw(lw * 2)

Add a progress bar window.

Arguments:
  • n : (int) number of iterations. If None, you need to call update(fraction) manually.
  • c : (color) color of the line.
  • alpha : (float) opacity of the line.
  • lw : (int) line width in pixels.
  • autohide : (bool) if True, hide the progress bar when completed.
def update(self, fraction=None) -> Self:
2748    def update(self, fraction=None) -> Self:
2749        """Update progress bar to fraction of the window width."""
2750        if fraction is None:
2751            if self.iterations is None:
2752                vedo.printc("Error in ProgressBarWindow: must specify iterations", c='r')
2753                return self
2754            self.n += 1
2755            fraction = self.n / self.iterations
2756
2757        if fraction >= 1 and self.autohide:
2758            fraction = 0
2759
2760        psqr = [[0, 0, 0], [fraction, 0, 0]]
2761        vpts = utils.numpy2vtk(psqr, dtype=np.float32)
2762        self.dataset.GetPoints().SetData(vpts)
2763        return self

Update progress bar to fraction of the window width.

def reset(self):
2765    def reset(self):
2766        """Reset progress bar."""
2767        self.n = 0
2768        self.update(0)
2769        return self

Reset progress bar.

class BoxCutter(BaseCutter, vtkmodules.vtkInteractionWidgets.vtkBoxWidget):
2337class BoxCutter(BaseCutter, vtki.vtkBoxWidget):
2338    """
2339    Create a box widget to cut away parts of a Mesh.
2340    """
2341
2342    def __init__(
2343        self,
2344        mesh,
2345        invert=False,
2346        initial_bounds=(),
2347        padding=0.025,
2348        delayed=False,
2349        c=(0.25, 0.25, 0.25),
2350        alpha=0.05,
2351    ):
2352        """
2353        Create a box widget to cut away parts of a Mesh.
2354
2355        Arguments:
2356            mesh : (Mesh)
2357                the input mesh
2358            invert : (bool)
2359                invert the clipping plane
2360            initial_bounds : (list)
2361                initial bounds of the box widget
2362            padding : (float)
2363                padding space around the input mesh
2364            delayed : (bool)
2365                if True the callback is delayed until
2366                when the mouse button is released (useful for large meshes)
2367            c : (color)
2368                color of the box cutter widget
2369            alpha : (float)
2370                transparency of the cut-off part of the input mesh
2371        """
2372        super().__init__()
2373        self.name = "BoxCutter"
2374
2375        self.mesh = mesh
2376        self.remnant = Mesh()
2377        self.remnant.name = mesh.name + "Remnant"
2378        self.remnant.pickable(False)
2379
2380        self._alpha = alpha
2381
2382        self._init_bounds = initial_bounds
2383        if len(self._init_bounds) == 0:
2384            self._init_bounds = mesh.bounds()
2385        else:
2386            self._init_bounds = initial_bounds
2387
2388        self.__implicit_func = vtki.new("Planes")
2389        self.__implicit_func.SetBounds(self._init_bounds)
2390
2391        poly = mesh.dataset
2392        self.clipper = vtki.new("ClipPolyData")
2393        self.clipper.GenerateClipScalarsOff()
2394        self.clipper.SetInputData(poly)
2395        self.clipper.SetClipFunction(self.__implicit_func)
2396        self.clipper.SetInsideOut(not invert)
2397        self.clipper.GenerateClippedOutputOn()
2398        self.clipper.Update()
2399
2400        self.widget = vtki.vtkBoxWidget()
2401
2402        self.widget.OutlineCursorWiresOn()
2403        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
2404        self.widget.GetSelectedHandleProperty().SetColor(get_color("red5"))
2405
2406        self.widget.GetOutlineProperty().SetColor(c)
2407        self.widget.GetOutlineProperty().SetOpacity(1)
2408        self.widget.GetOutlineProperty().SetLineWidth(1)
2409        self.widget.GetOutlineProperty().LightingOff()
2410
2411        self.widget.GetSelectedFaceProperty().LightingOff()
2412        self.widget.GetSelectedFaceProperty().SetOpacity(0.1)
2413
2414        self.widget.SetPlaceFactor(1.0 + padding)
2415        self.widget.SetInputData(poly)
2416        self.widget.PlaceWidget()
2417        if delayed:
2418            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2419        else:
2420            self.widget.AddObserver("InteractionEvent", self._select_polygons)
2421
2422    def _select_polygons(self, vobj, _event):
2423        vobj.GetPlanes(self.__implicit_func)
2424
2425    def set_bounds(self, bb) -> Self:
2426        """Set the bounding box as a list of 6 values."""
2427        self.__implicit_func.SetBounds(bb)
2428        return self
2429
2430    def enable_translation(self, value=True) -> Self:
2431        """Enable or disable translation of the widget."""
2432        self.widget.SetTranslationEnabled(value)
2433        self.can_translate = bool(value)
2434        return self
2435    
2436    def enable_scaling(self, value=True) -> Self:
2437        """Enable or disable scaling of the widget."""
2438        self.widget.SetScalingEnabled(value)
2439        self.can_scale = bool(value)
2440        return self
2441    
2442    def enable_rotation(self, value=True) -> Self:
2443        """Enable or disable rotation of the widget."""
2444        self.widget.SetRotationEnabled(value)
2445        self.can_rotate = bool(value)
2446        return self

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

BoxCutter( mesh, invert=False, initial_bounds=(), padding=0.025, delayed=False, c=(0.25, 0.25, 0.25), alpha=0.05)
2342    def __init__(
2343        self,
2344        mesh,
2345        invert=False,
2346        initial_bounds=(),
2347        padding=0.025,
2348        delayed=False,
2349        c=(0.25, 0.25, 0.25),
2350        alpha=0.05,
2351    ):
2352        """
2353        Create a box widget to cut away parts of a Mesh.
2354
2355        Arguments:
2356            mesh : (Mesh)
2357                the input mesh
2358            invert : (bool)
2359                invert the clipping plane
2360            initial_bounds : (list)
2361                initial bounds of the box widget
2362            padding : (float)
2363                padding space around the input mesh
2364            delayed : (bool)
2365                if True the callback is delayed until
2366                when the mouse button is released (useful for large meshes)
2367            c : (color)
2368                color of the box cutter widget
2369            alpha : (float)
2370                transparency of the cut-off part of the input mesh
2371        """
2372        super().__init__()
2373        self.name = "BoxCutter"
2374
2375        self.mesh = mesh
2376        self.remnant = Mesh()
2377        self.remnant.name = mesh.name + "Remnant"
2378        self.remnant.pickable(False)
2379
2380        self._alpha = alpha
2381
2382        self._init_bounds = initial_bounds
2383        if len(self._init_bounds) == 0:
2384            self._init_bounds = mesh.bounds()
2385        else:
2386            self._init_bounds = initial_bounds
2387
2388        self.__implicit_func = vtki.new("Planes")
2389        self.__implicit_func.SetBounds(self._init_bounds)
2390
2391        poly = mesh.dataset
2392        self.clipper = vtki.new("ClipPolyData")
2393        self.clipper.GenerateClipScalarsOff()
2394        self.clipper.SetInputData(poly)
2395        self.clipper.SetClipFunction(self.__implicit_func)
2396        self.clipper.SetInsideOut(not invert)
2397        self.clipper.GenerateClippedOutputOn()
2398        self.clipper.Update()
2399
2400        self.widget = vtki.vtkBoxWidget()
2401
2402        self.widget.OutlineCursorWiresOn()
2403        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
2404        self.widget.GetSelectedHandleProperty().SetColor(get_color("red5"))
2405
2406        self.widget.GetOutlineProperty().SetColor(c)
2407        self.widget.GetOutlineProperty().SetOpacity(1)
2408        self.widget.GetOutlineProperty().SetLineWidth(1)
2409        self.widget.GetOutlineProperty().LightingOff()
2410
2411        self.widget.GetSelectedFaceProperty().LightingOff()
2412        self.widget.GetSelectedFaceProperty().SetOpacity(0.1)
2413
2414        self.widget.SetPlaceFactor(1.0 + padding)
2415        self.widget.SetInputData(poly)
2416        self.widget.PlaceWidget()
2417        if delayed:
2418            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2419        else:
2420            self.widget.AddObserver("InteractionEvent", self._select_polygons)

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

Arguments:
  • mesh : (Mesh) the input mesh
  • invert : (bool) invert the clipping plane
  • initial_bounds : (list) initial bounds of the box widget
  • padding : (float) padding space around the input mesh
  • delayed : (bool) if True the callback is delayed until when the mouse button is released (useful for large meshes)
  • c : (color) color of the box cutter widget
  • alpha : (float) transparency of the cut-off part of the input mesh
def set_bounds(self, bb) -> Self:
2425    def set_bounds(self, bb) -> Self:
2426        """Set the bounding box as a list of 6 values."""
2427        self.__implicit_func.SetBounds(bb)
2428        return self

Set the bounding box as a list of 6 values.

def enable_translation(self, value=True) -> Self:
2430    def enable_translation(self, value=True) -> Self:
2431        """Enable or disable translation of the widget."""
2432        self.widget.SetTranslationEnabled(value)
2433        self.can_translate = bool(value)
2434        return self

Enable or disable translation of the widget.

def enable_scaling(self, value=True) -> Self:
2436    def enable_scaling(self, value=True) -> Self:
2437        """Enable or disable scaling of the widget."""
2438        self.widget.SetScalingEnabled(value)
2439        self.can_scale = bool(value)
2440        return self

Enable or disable scaling of the widget.

def enable_rotation(self, value=True) -> Self:
2442    def enable_rotation(self, value=True) -> Self:
2443        """Enable or disable rotation of the widget."""
2444        self.widget.SetRotationEnabled(value)
2445        self.can_rotate = bool(value)
2446        return self

Enable or disable rotation of the widget.

class PlaneCutter(BaseCutter, vtkmodules.vtkInteractionWidgets.vtkPlaneWidget):
2194class PlaneCutter(BaseCutter, vtki.vtkPlaneWidget):
2195    """
2196    Create a box widget to cut away parts of a Mesh.
2197    """
2198
2199    def __init__(
2200        self,
2201        mesh,
2202        invert=False,
2203        origin=(),
2204        normal=(),
2205        padding=0.05,
2206        delayed=False,
2207        c=(0.25, 0.25, 0.25),
2208        alpha=0.05,
2209    ):
2210        """
2211        Create a box widget to cut away parts of a `Mesh`.
2212
2213        Arguments:
2214            mesh : (Mesh)
2215                the input mesh
2216            invert : (bool)
2217                invert the clipping plane
2218            origin : (list)
2219                origin of the plane
2220            normal : (list)
2221                normal to the plane
2222            padding : (float)
2223                padding around the input mesh
2224            delayed : (bool)
2225                if True the callback is delayed until
2226                when the mouse button is released (useful for large meshes)
2227            c : (color)
2228                color of the box cutter widget
2229            alpha : (float)
2230                transparency of the cut-off part of the input mesh
2231
2232        Examples:
2233            - [slice_plane3.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slice_plane3.py)
2234        """
2235        super().__init__()
2236        self.name = "PlaneCutter"
2237
2238        self.mesh = mesh
2239        self.remnant = Mesh()
2240        self.remnant.name = mesh.name + "Remnant"
2241        self.remnant.pickable(False)
2242
2243        self._alpha = alpha
2244
2245        self.__implicit_func = vtki.new("Plane")
2246
2247        poly = mesh.dataset
2248        self.clipper = vtki.new("ClipPolyData")
2249        self.clipper.GenerateClipScalarsOff()
2250        self.clipper.SetInputData(poly)
2251        self.clipper.SetClipFunction(self.__implicit_func)
2252        self.clipper.SetInsideOut(invert)
2253        self.clipper.GenerateClippedOutputOn()
2254        self.clipper.Update()
2255
2256        self.widget = vtki.new("ImplicitPlaneWidget")
2257
2258        self.widget.GetOutlineProperty().SetColor(get_color(c))
2259        self.widget.GetOutlineProperty().SetOpacity(0.25)
2260        self.widget.GetOutlineProperty().SetLineWidth(1)
2261        self.widget.GetOutlineProperty().LightingOff()
2262
2263        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
2264
2265        self.widget.SetTubing(0)
2266        self.widget.SetDrawPlane(bool(alpha))
2267        self.widget.GetPlaneProperty().LightingOff()
2268        self.widget.GetPlaneProperty().SetOpacity(alpha)
2269        self.widget.GetSelectedPlaneProperty().SetColor(get_color("red5"))
2270        self.widget.GetSelectedPlaneProperty().LightingOff()
2271
2272        self.widget.SetPlaceFactor(1.0 + padding)
2273        self.widget.SetInputData(poly)
2274        self.widget.PlaceWidget()
2275        if delayed:
2276            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2277        else:
2278            self.widget.AddObserver("InteractionEvent", self._select_polygons)
2279
2280        if len(origin) == 3:
2281            self.widget.SetOrigin(origin)
2282        else:
2283            self.widget.SetOrigin(mesh.center_of_mass())
2284
2285        if len(normal) == 3:
2286            self.widget.SetNormal(normal)
2287        else:
2288            self.widget.SetNormal((1, 0, 0))
2289
2290    @property
2291    def origin(self):
2292        """Get the origin of the plane."""
2293        return np.array(self.widget.GetOrigin())
2294
2295    @origin.setter
2296    def origin(self, value):
2297        """Set the origin of the plane."""
2298        self.widget.SetOrigin(value)
2299
2300    @property
2301    def normal(self):
2302        """Get the normal of the plane."""
2303        return np.array(self.widget.GetNormal())
2304
2305    @normal.setter
2306    def normal(self, value):
2307        """Set the normal of the plane."""
2308        self.widget.SetNormal(value)
2309
2310    def _select_polygons(self, vobj, _event) -> None:
2311        vobj.GetPlane(self.__implicit_func)
2312
2313    def enable_translation(self, value=True) -> Self:
2314        """Enable or disable translation of the widget."""
2315        self.widget.SetOutlineTranslation(value)
2316        self.widget.SetOriginTranslation(value)
2317        self.can_translate = bool(value)
2318        return self
2319    
2320    def enable_origin_translation(self, value=True) -> Self:
2321        """Enable or disable rotation of the widget."""
2322        self.widget.SetOriginTranslation(value)
2323        return self
2324    
2325    def enable_scaling(self, value=True) -> Self:
2326        """Enable or disable scaling of the widget."""
2327        self.widget.SetScaleEnabled(value)
2328        self.can_scale = bool(value)
2329        return self
2330    
2331    def enable_rotation(self, value=True) -> Self:
2332        """Dummy."""
2333        self.can_rotate = bool(value)
2334        return self

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

PlaneCutter( mesh, invert=False, origin=(), normal=(), padding=0.05, delayed=False, c=(0.25, 0.25, 0.25), alpha=0.05)
2199    def __init__(
2200        self,
2201        mesh,
2202        invert=False,
2203        origin=(),
2204        normal=(),
2205        padding=0.05,
2206        delayed=False,
2207        c=(0.25, 0.25, 0.25),
2208        alpha=0.05,
2209    ):
2210        """
2211        Create a box widget to cut away parts of a `Mesh`.
2212
2213        Arguments:
2214            mesh : (Mesh)
2215                the input mesh
2216            invert : (bool)
2217                invert the clipping plane
2218            origin : (list)
2219                origin of the plane
2220            normal : (list)
2221                normal to the plane
2222            padding : (float)
2223                padding around the input mesh
2224            delayed : (bool)
2225                if True the callback is delayed until
2226                when the mouse button is released (useful for large meshes)
2227            c : (color)
2228                color of the box cutter widget
2229            alpha : (float)
2230                transparency of the cut-off part of the input mesh
2231
2232        Examples:
2233            - [slice_plane3.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slice_plane3.py)
2234        """
2235        super().__init__()
2236        self.name = "PlaneCutter"
2237
2238        self.mesh = mesh
2239        self.remnant = Mesh()
2240        self.remnant.name = mesh.name + "Remnant"
2241        self.remnant.pickable(False)
2242
2243        self._alpha = alpha
2244
2245        self.__implicit_func = vtki.new("Plane")
2246
2247        poly = mesh.dataset
2248        self.clipper = vtki.new("ClipPolyData")
2249        self.clipper.GenerateClipScalarsOff()
2250        self.clipper.SetInputData(poly)
2251        self.clipper.SetClipFunction(self.__implicit_func)
2252        self.clipper.SetInsideOut(invert)
2253        self.clipper.GenerateClippedOutputOn()
2254        self.clipper.Update()
2255
2256        self.widget = vtki.new("ImplicitPlaneWidget")
2257
2258        self.widget.GetOutlineProperty().SetColor(get_color(c))
2259        self.widget.GetOutlineProperty().SetOpacity(0.25)
2260        self.widget.GetOutlineProperty().SetLineWidth(1)
2261        self.widget.GetOutlineProperty().LightingOff()
2262
2263        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
2264
2265        self.widget.SetTubing(0)
2266        self.widget.SetDrawPlane(bool(alpha))
2267        self.widget.GetPlaneProperty().LightingOff()
2268        self.widget.GetPlaneProperty().SetOpacity(alpha)
2269        self.widget.GetSelectedPlaneProperty().SetColor(get_color("red5"))
2270        self.widget.GetSelectedPlaneProperty().LightingOff()
2271
2272        self.widget.SetPlaceFactor(1.0 + padding)
2273        self.widget.SetInputData(poly)
2274        self.widget.PlaceWidget()
2275        if delayed:
2276            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2277        else:
2278            self.widget.AddObserver("InteractionEvent", self._select_polygons)
2279
2280        if len(origin) == 3:
2281            self.widget.SetOrigin(origin)
2282        else:
2283            self.widget.SetOrigin(mesh.center_of_mass())
2284
2285        if len(normal) == 3:
2286            self.widget.SetNormal(normal)
2287        else:
2288            self.widget.SetNormal((1, 0, 0))

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

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

Get the origin of the plane.

normal
2300    @property
2301    def normal(self):
2302        """Get the normal of the plane."""
2303        return np.array(self.widget.GetNormal())

Get the normal of the plane.

def enable_translation(self, value=True) -> Self:
2313    def enable_translation(self, value=True) -> Self:
2314        """Enable or disable translation of the widget."""
2315        self.widget.SetOutlineTranslation(value)
2316        self.widget.SetOriginTranslation(value)
2317        self.can_translate = bool(value)
2318        return self

Enable or disable translation of the widget.

def enable_origin_translation(self, value=True) -> Self:
2320    def enable_origin_translation(self, value=True) -> Self:
2321        """Enable or disable rotation of the widget."""
2322        self.widget.SetOriginTranslation(value)
2323        return self

Enable or disable rotation of the widget.

def enable_scaling(self, value=True) -> Self:
2325    def enable_scaling(self, value=True) -> Self:
2326        """Enable or disable scaling of the widget."""
2327        self.widget.SetScaleEnabled(value)
2328        self.can_scale = bool(value)
2329        return self

Enable or disable scaling of the widget.

def enable_rotation(self, value=True) -> Self:
2331    def enable_rotation(self, value=True) -> Self:
2332        """Dummy."""
2333        self.can_rotate = bool(value)
2334        return self

Dummy.

class SphereCutter(BaseCutter, vtkmodules.vtkInteractionWidgets.vtkSphereWidget):
2448class SphereCutter(BaseCutter, vtki.vtkSphereWidget):
2449    """
2450    Create a box widget to cut away parts of a Mesh.
2451    """
2452
2453    def __init__(
2454        self,
2455        mesh,
2456        invert=False,
2457        origin=(),
2458        radius=0,
2459        res=60,
2460        delayed=False,
2461        c="white",
2462        alpha=0.05,
2463    ):
2464        """
2465        Create a box widget to cut away parts of a Mesh.
2466
2467        Arguments:
2468            mesh : Mesh
2469                the input mesh
2470            invert : bool
2471                invert the clipping
2472            origin : list
2473                initial position of the sphere widget
2474            radius : float
2475                initial radius of the sphere widget
2476            res : int
2477                resolution of the sphere widget
2478            delayed : bool
2479                if True the cutting callback is delayed until
2480                when the mouse button is released (useful for large meshes)
2481            c : color
2482                color of the box cutter widget
2483            alpha : float
2484                transparency of the cut-off part of the input mesh
2485        """
2486        super().__init__()
2487        self.name = "SphereCutter"
2488
2489        self.mesh = mesh
2490        self.remnant = Mesh()
2491        self.remnant.name = mesh.name + "Remnant"
2492        self.remnant.pickable(False)
2493
2494        self._alpha = alpha
2495
2496        self.__implicit_func = vtki.new("Sphere")
2497
2498        if len(origin) == 3:
2499            self.__implicit_func.SetCenter(origin)
2500        else:
2501            origin = mesh.center_of_mass()
2502            self.__implicit_func.SetCenter(origin)
2503
2504        if radius > 0:
2505            self.__implicit_func.SetRadius(radius)
2506        else:
2507            radius = mesh.average_size() * 2
2508            self.__implicit_func.SetRadius(radius)
2509
2510        poly = mesh.dataset
2511        self.clipper = vtki.new("ClipPolyData")
2512        self.clipper.GenerateClipScalarsOff()
2513        self.clipper.SetInputData(poly)
2514        self.clipper.SetClipFunction(self.__implicit_func)
2515        self.clipper.SetInsideOut(not invert)
2516        self.clipper.GenerateClippedOutputOn()
2517        self.clipper.Update()
2518
2519        self.widget = vtki.vtkSphereWidget()
2520
2521        self.widget.SetThetaResolution(res * 2)
2522        self.widget.SetPhiResolution(res)
2523        self.widget.SetRadius(radius)
2524        self.widget.SetCenter(origin)
2525        self.widget.SetRepresentation(2)
2526        self.widget.HandleVisibilityOff()
2527
2528        self.widget.HandleVisibilityOff()
2529        self.widget.GetSphereProperty().SetColor(get_color(c))
2530        self.widget.GetSphereProperty().SetOpacity(0.2)
2531        self.widget.GetSelectedSphereProperty().SetColor(get_color("red5"))
2532        self.widget.GetSelectedSphereProperty().SetOpacity(0.2)
2533
2534        self.widget.SetPlaceFactor(1.0)
2535        self.widget.SetInputData(poly)
2536        self.widget.PlaceWidget()
2537        if delayed:
2538            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2539        else:
2540            self.widget.AddObserver("InteractionEvent", self._select_polygons)
2541
2542    def _select_polygons(self, vobj, _event):
2543        vobj.GetSphere(self.__implicit_func)
2544
2545
2546    @property
2547    def center(self):
2548        """Get the center of the sphere."""
2549        return np.array(self.widget.GetCenter())
2550
2551    @center.setter
2552    def center(self, value):
2553        """Set the center of the sphere."""
2554        self.widget.SetCenter(value)
2555
2556    @property
2557    def radius(self):
2558        """Get the radius of the sphere."""
2559        return self.widget.GetRadius()
2560
2561    @radius.setter
2562    def radius(self, value):
2563        """Set the radius of the sphere."""
2564        self.widget.SetRadius(value)
2565
2566    def enable_translation(self, value=True) -> Self:
2567        """Enable or disable translation of the widget."""
2568        self.widget.SetTranslation(value)
2569        self.can_translate = bool(value)
2570        return self
2571    
2572    def enable_scaling(self, value=True) -> Self:
2573        """Enable or disable scaling of the widget."""
2574        self.widget.SetScale(value)
2575        self.can_scale = bool(value)
2576        return self
2577    
2578    def enable_rotation(self, value=True) -> Self:
2579        """Enable or disable rotation of the widget."""
2580        # This is dummy anyway
2581        self.can_rotate = bool(value)
2582        return self

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

SphereCutter( mesh, invert=False, origin=(), radius=0, res=60, delayed=False, c='white', alpha=0.05)
2453    def __init__(
2454        self,
2455        mesh,
2456        invert=False,
2457        origin=(),
2458        radius=0,
2459        res=60,
2460        delayed=False,
2461        c="white",
2462        alpha=0.05,
2463    ):
2464        """
2465        Create a box widget to cut away parts of a Mesh.
2466
2467        Arguments:
2468            mesh : Mesh
2469                the input mesh
2470            invert : bool
2471                invert the clipping
2472            origin : list
2473                initial position of the sphere widget
2474            radius : float
2475                initial radius of the sphere widget
2476            res : int
2477                resolution of the sphere widget
2478            delayed : bool
2479                if True the cutting callback is delayed until
2480                when the mouse button is released (useful for large meshes)
2481            c : color
2482                color of the box cutter widget
2483            alpha : float
2484                transparency of the cut-off part of the input mesh
2485        """
2486        super().__init__()
2487        self.name = "SphereCutter"
2488
2489        self.mesh = mesh
2490        self.remnant = Mesh()
2491        self.remnant.name = mesh.name + "Remnant"
2492        self.remnant.pickable(False)
2493
2494        self._alpha = alpha
2495
2496        self.__implicit_func = vtki.new("Sphere")
2497
2498        if len(origin) == 3:
2499            self.__implicit_func.SetCenter(origin)
2500        else:
2501            origin = mesh.center_of_mass()
2502            self.__implicit_func.SetCenter(origin)
2503
2504        if radius > 0:
2505            self.__implicit_func.SetRadius(radius)
2506        else:
2507            radius = mesh.average_size() * 2
2508            self.__implicit_func.SetRadius(radius)
2509
2510        poly = mesh.dataset
2511        self.clipper = vtki.new("ClipPolyData")
2512        self.clipper.GenerateClipScalarsOff()
2513        self.clipper.SetInputData(poly)
2514        self.clipper.SetClipFunction(self.__implicit_func)
2515        self.clipper.SetInsideOut(not invert)
2516        self.clipper.GenerateClippedOutputOn()
2517        self.clipper.Update()
2518
2519        self.widget = vtki.vtkSphereWidget()
2520
2521        self.widget.SetThetaResolution(res * 2)
2522        self.widget.SetPhiResolution(res)
2523        self.widget.SetRadius(radius)
2524        self.widget.SetCenter(origin)
2525        self.widget.SetRepresentation(2)
2526        self.widget.HandleVisibilityOff()
2527
2528        self.widget.HandleVisibilityOff()
2529        self.widget.GetSphereProperty().SetColor(get_color(c))
2530        self.widget.GetSphereProperty().SetOpacity(0.2)
2531        self.widget.GetSelectedSphereProperty().SetColor(get_color("red5"))
2532        self.widget.GetSelectedSphereProperty().SetOpacity(0.2)
2533
2534        self.widget.SetPlaceFactor(1.0)
2535        self.widget.SetInputData(poly)
2536        self.widget.PlaceWidget()
2537        if delayed:
2538            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2539        else:
2540            self.widget.AddObserver("InteractionEvent", self._select_polygons)

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

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

Get the center of the sphere.

radius
2556    @property
2557    def radius(self):
2558        """Get the radius of the sphere."""
2559        return self.widget.GetRadius()

Get the radius of the sphere.

def enable_translation(self, value=True) -> Self:
2566    def enable_translation(self, value=True) -> Self:
2567        """Enable or disable translation of the widget."""
2568        self.widget.SetTranslation(value)
2569        self.can_translate = bool(value)
2570        return self

Enable or disable translation of the widget.

def enable_scaling(self, value=True) -> Self:
2572    def enable_scaling(self, value=True) -> Self:
2573        """Enable or disable scaling of the widget."""
2574        self.widget.SetScale(value)
2575        self.can_scale = bool(value)
2576        return self

Enable or disable scaling of the widget.

def enable_rotation(self, value=True) -> Self:
2578    def enable_rotation(self, value=True) -> Self:
2579        """Enable or disable rotation of the widget."""
2580        # This is dummy anyway
2581        self.can_rotate = bool(value)
2582        return self

Enable or disable rotation of the widget.