vedo.addons

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

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

A 2D scalar bar for the specified obj.

Arguments:
  • title : (str) scalar bar title
  • pos : (float,float) position coordinates of the bottom left corner
  • title_yoffset : (float) vertical space offset between title and color scalarbar
  • font_size : (float) size of font for title and numeric labels
  • size : (float,float) size of the scalarbar in number of pixels (width, height)
  • 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]:
1193def ScalarBar3D(
1194    obj,
1195    title="",
1196    pos=None,
1197    size=(0, 0),
1198    title_font="",
1199    title_xoffset=-1.2,
1200    title_yoffset=0.0,
1201    title_size=1.5,
1202    title_rotation=0.0,
1203    nlabels=8,
1204    label_font="",
1205    label_size=1,
1206    label_offset=0.375,
1207    label_rotation=0,
1208    label_format="",
1209    italic=0,
1210    c='k',
1211    draw_box=True,
1212    above_text=None,
1213    below_text=None,
1214    nan_text="NaN",
1215    categories=None,
1216) -> Union[Assembly, None]:
1217    """
1218    Create a 3D scalar bar for the specified object.
1219
1220    Input `obj` input can be:
1221
1222        - a list of numbers,
1223        - a list of two numbers in the form (min, max),
1224        - a Mesh already containing a set of scalars associated to vertices or cells,
1225        - if None the last object in the list of actors will be used.
1226
1227    Arguments:
1228        size : (list)
1229            (thickness, length) of scalarbar
1230        title : (str)
1231            scalar bar title
1232        title_xoffset : (float)
1233            horizontal space btw title and color scalarbar
1234        title_yoffset : (float)
1235            vertical space offset
1236        title_size : (float)
1237            size of title wrt numeric labels
1238        title_rotation : (float)
1239            title rotation in degrees
1240        nlabels : (int)
1241            number of numeric labels
1242        label_font : (str)
1243            font type for labels
1244        label_size : (float)
1245            label scale factor
1246        label_offset : (float)
1247            space btw numeric labels and scale
1248        label_rotation : (float)
1249            label rotation in degrees
1250        draw_box : (bool)
1251            draw a box around the colorbar
1252        categories : (list)
1253            make a categorical scalarbar,
1254            the input list will have the format [value, color, alpha, textlabel]
1255
1256    Examples:
1257        - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py)
1258        - [plot_fxy2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy2.py)
1259    """
1260
1261    if isinstance(obj, (Points, TetMesh, vedo.UnstructuredGrid)):
1262        lut = obj.mapper.GetLookupTable()
1263        if not lut or lut.GetTable().GetNumberOfTuples() == 0:
1264            # create the most similar to the default
1265            obj.cmap("jet_r")
1266            lut = obj.mapper.GetLookupTable()
1267        vmin, vmax = lut.GetRange()
1268
1269    elif isinstance(obj, Volume):
1270        lut = utils.ctf2lut(obj)
1271        vmin, vmax = lut.GetRange()
1272
1273    else:
1274        vedo.logger.error("in ScalarBar3D(): input must be a vedo object with bounds.")
1275        return None
1276
1277    bns = obj.bounds()
1278    sx, sy = size
1279    if sy == 0 or sy is None:
1280        sy = bns[3] - bns[2]
1281    if sx == 0 or sx is None:
1282        sx = sy / 18
1283
1284    if categories is not None:  ################################
1285        ncats = len(categories)
1286        scale = shapes.Grid([-float(sx) * label_offset, 0, 0],
1287                            c=c, alpha=1, s=(sx, sy), res=(1, ncats))
1288        cols, alphas = [], []
1289        ticks_pos, ticks_txt = [0.0], [""]
1290        for i, cat in enumerate(categories):
1291            cl = get_color(cat[1])
1292            cols.append(cl)
1293            if len(cat) > 2:
1294                alphas.append(cat[2])
1295            else:
1296                alphas.append(1)
1297            if len(cat) > 3:
1298                ticks_txt.append(cat[3])
1299            else:
1300                ticks_txt.append("")
1301            ticks_pos.append((i + 0.5) / ncats)
1302        ticks_pos.append(1.0)
1303        ticks_txt.append("")
1304        rgba = np.c_[np.array(cols) * 255, np.array(alphas) * 255]
1305        scale.cellcolors = rgba
1306
1307    else:  ########################################################
1308
1309        # build the color scale part
1310        scale = shapes.Grid(
1311            [-float(sx) * label_offset, 0, 0],
1312            c=c,
1313            s=(sx, sy),
1314            res=(1, lut.GetTable().GetNumberOfTuples()),
1315        )
1316        cscals = np.linspace(vmin, vmax, lut.GetTable().GetNumberOfTuples(), endpoint=True)
1317
1318        if lut.GetScale():  # logarithmic scale
1319            lut10 = vtki.vtkLookupTable()
1320            lut10.DeepCopy(lut)
1321            lut10.SetScaleToLinear()
1322            lut10.Build()
1323            scale.cmap(lut10, cscals, on="cells")
1324            tk = utils.make_ticks(vmin, vmax, nlabels, logscale=True, useformat=label_format)
1325        else:
1326            # for i in range(lut.GetTable().GetNumberOfTuples()):
1327            #     print("LUT i=", i, lut.GetTableValue(i))
1328            scale.cmap(lut, cscals, on="cells")
1329            tk = utils.make_ticks(vmin, vmax, nlabels, logscale=False, useformat=label_format)
1330        ticks_pos, ticks_txt = tk
1331    
1332    scale.lw(0).wireframe(False).lighting("off")
1333
1334    scales = [scale]
1335
1336    xbns = scale.xbounds()
1337
1338    lsize = sy / 60 * label_size
1339
1340    tacts = []
1341    for i, p in enumerate(ticks_pos):
1342        tx = ticks_txt[i]
1343        if i and tx:
1344            # build numeric text
1345            y = (p - 0.5) * sy
1346            if label_rotation:
1347                a = shapes.Text3D(
1348                    tx,
1349                    s=lsize,
1350                    justify="center-top",
1351                    c=c,
1352                    italic=italic,
1353                    font=label_font,
1354                )
1355                a.rotate_z(label_rotation)
1356                a.pos(sx * label_offset, y, 0)
1357            else:
1358                a = shapes.Text3D(
1359                    tx,
1360                    pos=[sx * label_offset, y, 0],
1361                    s=lsize,
1362                    justify="center-left",
1363                    c=c,
1364                    italic=italic,
1365                    font=label_font,
1366                )
1367
1368            tacts.append(a)
1369
1370            # build ticks
1371            tic = shapes.Line([xbns[1], y, 0], [xbns[1] + sx * label_offset / 4, y, 0], lw=2, c=c)
1372            tacts.append(tic)
1373
1374    # build title
1375    if title:
1376        t = shapes.Text3D(
1377            title,
1378            pos=(0, 0, 0),
1379            s=sy / 50 * title_size,
1380            c=c,
1381            justify="centered-bottom",
1382            italic=italic,
1383            font=title_font,
1384        )
1385        t.rotate_z(90 + title_rotation)
1386        t.pos(sx * title_xoffset, title_yoffset, 0)
1387        tacts.append(t)
1388
1389    if pos is None:
1390        tsize = 0
1391        if title:
1392            bbt = t.bounds()
1393            tsize = bbt[1] - bbt[0]
1394        pos = (bns[1] + tsize + sx*1.5, (bns[2]+bns[3])/2, bns[4])
1395
1396    # build below scale
1397    if lut.GetUseBelowRangeColor():
1398        r, g, b, alfa = lut.GetBelowRangeColor()
1399        sx = float(sx)
1400        sy = float(sy)
1401        brect = shapes.Rectangle(
1402            [-sx * label_offset - sx / 2, -sy / 2 - sx - sx * 0.1, 0],
1403            [-sx * label_offset + sx / 2, -sy / 2 - sx * 0.1, 0],
1404            c=(r, g, b),
1405            alpha=alfa,
1406        )
1407        brect.lw(1).lc(c).lighting("off")
1408        scales += [brect]
1409        if below_text is None:
1410            below_text = " <" + str(vmin)
1411        if below_text:
1412            if label_rotation:
1413                btx = shapes.Text3D(
1414                    below_text,
1415                    pos=(0, 0, 0),
1416                    s=lsize,
1417                    c=c,
1418                    justify="center-top",
1419                    italic=italic,
1420                    font=label_font,
1421                )
1422                btx.rotate_z(label_rotation)
1423            else:
1424                btx = shapes.Text3D(
1425                    below_text,
1426                    pos=(0, 0, 0),
1427                    s=lsize,
1428                    c=c,
1429                    justify="center-left",
1430                    italic=italic,
1431                    font=label_font,
1432                )
1433
1434            btx.pos(sx * label_offset, -sy / 2 - sx * 0.66, 0)
1435            tacts.append(btx)
1436
1437    # build above scale
1438    if lut.GetUseAboveRangeColor():
1439        r, g, b, alfa = lut.GetAboveRangeColor()
1440        arect = shapes.Rectangle(
1441            [-sx * label_offset - sx / 2, sy / 2 + sx * 0.1, 0],
1442            [-sx * label_offset + sx / 2, sy / 2 + sx + sx * 0.1, 0],
1443            c=(r, g, b),
1444            alpha=alfa,
1445        )
1446        arect.lw(1).lc(c).lighting("off")
1447        scales += [arect]
1448        if above_text is None:
1449            above_text = " >" + str(vmax)
1450        if above_text:
1451            if label_rotation:
1452                atx = shapes.Text3D(
1453                    above_text,
1454                    pos=(0, 0, 0),
1455                    s=lsize,
1456                    c=c,
1457                    justify="center-top",
1458                    italic=italic,
1459                    font=label_font,
1460                )
1461                atx.rotate_z(label_rotation)
1462            else:
1463                atx = shapes.Text3D(
1464                    above_text,
1465                    pos=(0, 0, 0),
1466                    s=lsize,
1467                    c=c,
1468                    justify="center-left",
1469                    italic=italic,
1470                    font=label_font,
1471                )
1472
1473            atx.pos(sx * label_offset, sy / 2 + sx * 0.66, 0)
1474            tacts.append(atx)
1475
1476    # build NaN scale
1477    if lut.GetNanColor() != (0.5, 0.0, 0.0, 1.0):
1478        nanshift = sx * 0.1
1479        if brect:
1480            nanshift += sx
1481        r, g, b, alfa = lut.GetNanColor()
1482        nanrect = shapes.Rectangle(
1483            [-sx * label_offset - sx / 2, -sy / 2 - sx - sx * 0.1 - nanshift, 0],
1484            [-sx * label_offset + sx / 2, -sy / 2 - sx * 0.1 - nanshift, 0],
1485            c=(r, g, b),
1486            alpha=alfa,
1487        )
1488        nanrect.lw(1).lc(c).lighting("off")
1489        scales += [nanrect]
1490        if label_rotation:
1491            nantx = shapes.Text3D(
1492                nan_text,
1493                pos=(0, 0, 0),
1494                s=lsize,
1495                c=c,
1496                justify="center-left",
1497                italic=italic,
1498                font=label_font,
1499            )
1500            nantx.rotate_z(label_rotation)
1501        else:
1502            nantx = shapes.Text3D(
1503                nan_text,
1504                pos=(0, 0, 0),
1505                s=lsize,
1506                c=c,
1507                justify="center-left",
1508                italic=italic,
1509                font=label_font,
1510            )
1511        nantx.pos(sx * label_offset, -sy / 2 - sx * 0.66 - nanshift, 0)
1512        tacts.append(nantx)
1513
1514    if draw_box:
1515        tacts.append(scale.box().lw(1).c(c))
1516
1517    for m in tacts + scales:
1518        m.shift(pos)
1519        m.actor.PickableOff()
1520        m.properties.LightingOff()
1521
1522    asse = Assembly(scales + tacts)
1523
1524    # asse.transform = LinearTransform().shift(pos)
1525
1526    bb = asse.GetBounds()
1527    # print("ScalarBar3D pos",pos, bb)
1528    # asse.SetOrigin(pos)
1529
1530    asse.SetOrigin(bb[0], bb[2], bb[4])
1531    # asse.SetOrigin(bb[0],0,0) #in pyplot line 1312
1532
1533    asse.PickableOff()
1534    asse.UseBoundsOff()
1535    asse.name = "ScalarBar3D"
1536    return asse

Create a 3D scalar bar for the specified object.

Input obj input can be:

- a list of numbers,
- a list of two numbers in the form (min, max),
- a Mesh already containing a set of scalars associated to vertices or cells,
- if None the last object in the list of actors will be used.
Arguments:
  • size : (list) (thickness, length) of scalarbar
  • title : (str) scalar bar title
  • title_xoffset : (float) horizontal space btw title and color scalarbar
  • title_yoffset : (float) vertical space offset
  • title_size : (float) size of title wrt numeric labels
  • title_rotation : (float) title rotation in degrees
  • nlabels : (int) number of numeric labels
  • label_font : (str) font type for labels
  • label_size : (float) label scale factor
  • label_offset : (float) space btw numeric labels and scale
  • label_rotation : (float) label rotation in degrees
  • draw_box : (bool) draw a box around the colorbar
  • categories : (list) make a categorical scalarbar, the input list will have the format [value, color, alpha, textlabel]
Examples:
class Slider2D(SliderWidget):
1540class Slider2D(SliderWidget):
1541    """
1542    Add a slider which can call an external custom function.
1543    """
1544    def __init__(
1545        self,
1546        sliderfunc,
1547        xmin,
1548        xmax,
1549        value=None,
1550        pos=4,
1551        title="",
1552        font="Calco",
1553        title_size=1,
1554        c="k",
1555        alpha=1,
1556        show_value=True,
1557        delayed=False,
1558        **options,
1559    ):
1560        """
1561        Add a slider which can call an external custom function.
1562        Set any value as float to increase the number of significant digits above the slider.
1563
1564        Use `play()` to start an animation between the current slider value and the last value.
1565
1566        Arguments:
1567            sliderfunc : (function)
1568                external function to be called by the widget
1569            xmin : (float)
1570                lower value of the slider
1571            xmax : (float)
1572                upper value
1573            value : (float)
1574                current value
1575            pos : (list, str)
1576                position corner number: horizontal [1-5] or vertical [11-15]
1577                it can also be specified by corners coordinates [(x1,y1), (x2,y2)]
1578                and also by a string descriptor (eg. "bottom-left")
1579            title : (str)
1580                title text
1581            font : (str)
1582                title font face. Check [available fonts here](https://vedo.embl.es/fonts).
1583            title_size : (float)
1584                title text scale [1.0]
1585            show_value : (bool)
1586                if True current value is shown
1587            delayed : (bool)
1588                if True the callback is delayed until when the mouse button is released
1589            alpha : (float)
1590                opacity of the scalar bar texts
1591            slider_length : (float)
1592                slider length
1593            slider_width : (float)
1594                slider width
1595            end_cap_length : (float)
1596                length of the end cap
1597            end_cap_width : (float)
1598                width of the end cap
1599            tube_width : (float)
1600                width of the tube
1601            title_height : (float)
1602                height of the title
1603            tformat : (str)
1604                format of the title
1605
1606        Examples:
1607            - [sliders1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders1.py)
1608            - [sliders2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders2.py)
1609
1610            ![](https://user-images.githubusercontent.com/32848391/50738848-be033480-11d8-11e9-9b1a-c13105423a79.jpg)
1611        """
1612        slider_length = options.pop("slider_length",  0.015)
1613        slider_width  = options.pop("slider_width",   0.025)
1614        end_cap_length= options.pop("end_cap_length", 0.0015)
1615        end_cap_width = options.pop("end_cap_width",  0.0125)
1616        tube_width    = options.pop("tube_width",     0.0075)
1617        title_height  = options.pop("title_height",   0.025)
1618        tformat       = options.pop("tformat",        None)
1619
1620        if options:
1621            vedo.logger.warning(f"in Slider2D unknown option(s): {options}")
1622
1623        c = get_color(c)
1624
1625        if value is None or value < xmin:
1626            value = xmin
1627
1628        slider_rep = vtki.new("SliderRepresentation2D")
1629        slider_rep.SetMinimumValue(xmin)
1630        slider_rep.SetMaximumValue(xmax)
1631        slider_rep.SetValue(value)
1632        slider_rep.SetSliderLength(slider_length)
1633        slider_rep.SetSliderWidth(slider_width)
1634        slider_rep.SetEndCapLength(end_cap_length)
1635        slider_rep.SetEndCapWidth(end_cap_width)
1636        slider_rep.SetTubeWidth(tube_width)
1637        slider_rep.GetPoint1Coordinate().SetCoordinateSystemToNormalizedDisplay()
1638        slider_rep.GetPoint2Coordinate().SetCoordinateSystemToNormalizedDisplay()
1639
1640        if isinstance(pos, str):
1641            if "top" in pos:
1642                if "left" in pos:
1643                    if "vert" in pos:
1644                        pos = 11
1645                    else:
1646                        pos = 1
1647                elif "right" in pos:
1648                    if "vert" in pos:
1649                        pos = 12
1650                    else:
1651                        pos = 2
1652            elif "bott" in pos:
1653                if "left" in pos:
1654                    if "vert" in pos:
1655                        pos = 13
1656                    else:
1657                        pos = 3
1658                elif "right" in pos:
1659                    if "vert" in pos:
1660                        if "span" in pos:
1661                            pos = 15
1662                        else:
1663                            pos = 14
1664                    else:
1665                        pos = 4
1666                elif "span" in pos:
1667                    pos = 5
1668
1669        if utils.is_sequence(pos):
1670            slider_rep.GetPoint1Coordinate().SetValue(pos[0][0], pos[0][1])
1671            slider_rep.GetPoint2Coordinate().SetValue(pos[1][0], pos[1][1])
1672        elif pos == 1:  # top-left horizontal
1673            slider_rep.GetPoint1Coordinate().SetValue(0.04, 0.93)
1674            slider_rep.GetPoint2Coordinate().SetValue(0.45, 0.93)
1675        elif pos == 2:
1676            slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.93)
1677            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.93)
1678        elif pos == 3:
1679            slider_rep.GetPoint1Coordinate().SetValue(0.05, 0.06)
1680            slider_rep.GetPoint2Coordinate().SetValue(0.45, 0.06)
1681        elif pos == 4:  # bottom-right
1682            slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.06)
1683            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06)
1684        elif pos == 5:  # bottom span horizontal
1685            slider_rep.GetPoint1Coordinate().SetValue(0.04, 0.06)
1686            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06)
1687        elif pos == 11:  # top-left vertical
1688            slider_rep.GetPoint1Coordinate().SetValue(0.065, 0.54)
1689            slider_rep.GetPoint2Coordinate().SetValue(0.065, 0.9)
1690        elif pos == 12:
1691            slider_rep.GetPoint1Coordinate().SetValue(0.94, 0.54)
1692            slider_rep.GetPoint2Coordinate().SetValue(0.94, 0.9)
1693        elif pos == 13:
1694            slider_rep.GetPoint1Coordinate().SetValue(0.065, 0.1)
1695            slider_rep.GetPoint2Coordinate().SetValue(0.065, 0.54)
1696        elif pos == 14:  # bottom-right vertical
1697            slider_rep.GetPoint1Coordinate().SetValue(0.94, 0.1)
1698            slider_rep.GetPoint2Coordinate().SetValue(0.94, 0.54)
1699        elif pos == 15:  # right margin vertical
1700            slider_rep.GetPoint1Coordinate().SetValue(0.95, 0.1)
1701            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.9)
1702        else:  # bottom-right
1703            slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.06)
1704            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06)
1705
1706        if show_value:
1707            if tformat is None:
1708                if isinstance(xmin, int) and isinstance(xmax, int) and isinstance(value, int):
1709                    tformat = "%0.0f"
1710                else:
1711                    tformat = "%0.2f"
1712
1713            slider_rep.SetLabelFormat(tformat)  # default is '%0.3g'
1714            slider_rep.GetLabelProperty().SetShadow(0)
1715            slider_rep.GetLabelProperty().SetBold(0)
1716            slider_rep.GetLabelProperty().SetOpacity(alpha)
1717            slider_rep.GetLabelProperty().SetColor(c)
1718            if isinstance(pos, int) and pos > 10:
1719                slider_rep.GetLabelProperty().SetOrientation(90)
1720        else:
1721            slider_rep.ShowSliderLabelOff()
1722        slider_rep.GetTubeProperty().SetColor(c)
1723        slider_rep.GetTubeProperty().SetOpacity(0.75)
1724        slider_rep.GetSliderProperty().SetColor(c)
1725        slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c)))
1726        slider_rep.GetCapProperty().SetColor(c)
1727
1728        slider_rep.SetTitleHeight(title_height * title_size)
1729        slider_rep.GetTitleProperty().SetShadow(0)
1730        slider_rep.GetTitleProperty().SetColor(c)
1731        slider_rep.GetTitleProperty().SetOpacity(alpha)
1732        slider_rep.GetTitleProperty().SetBold(0)
1733        if font.lower() == "courier":
1734            slider_rep.GetTitleProperty().SetFontFamilyToCourier()
1735        elif font.lower() == "times":
1736            slider_rep.GetTitleProperty().SetFontFamilyToTimes()
1737        elif font.lower() == "arial":
1738            slider_rep.GetTitleProperty().SetFontFamilyToArial()
1739        else:
1740            if font == "":
1741                font = utils.get_font_path(settings.default_font)
1742            else:
1743                font = utils.get_font_path(font)
1744            slider_rep.GetTitleProperty().SetFontFamily(vtki.VTK_FONT_FILE)
1745            slider_rep.GetLabelProperty().SetFontFamily(vtki.VTK_FONT_FILE)
1746            slider_rep.GetTitleProperty().SetFontFile(font)
1747            slider_rep.GetLabelProperty().SetFontFile(font)
1748
1749        if title:
1750            slider_rep.SetTitleText(title)
1751            if not utils.is_sequence(pos):
1752                if isinstance(pos, int) and pos > 10:
1753                    slider_rep.GetTitleProperty().SetOrientation(90)
1754            else:
1755                if abs(pos[0][0] - pos[1][0]) < 0.1:
1756                    slider_rep.GetTitleProperty().SetOrientation(90)
1757
1758        super().__init__()
1759
1760        self.SetAnimationModeToJump()
1761        self.SetRepresentation(slider_rep)
1762        if delayed:
1763            self.AddObserver("EndInteractionEvent", sliderfunc)
1764        else:
1765            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)
1544    def __init__(
1545        self,
1546        sliderfunc,
1547        xmin,
1548        xmax,
1549        value=None,
1550        pos=4,
1551        title="",
1552        font="Calco",
1553        title_size=1,
1554        c="k",
1555        alpha=1,
1556        show_value=True,
1557        delayed=False,
1558        **options,
1559    ):
1560        """
1561        Add a slider which can call an external custom function.
1562        Set any value as float to increase the number of significant digits above the slider.
1563
1564        Use `play()` to start an animation between the current slider value and the last value.
1565
1566        Arguments:
1567            sliderfunc : (function)
1568                external function to be called by the widget
1569            xmin : (float)
1570                lower value of the slider
1571            xmax : (float)
1572                upper value
1573            value : (float)
1574                current value
1575            pos : (list, str)
1576                position corner number: horizontal [1-5] or vertical [11-15]
1577                it can also be specified by corners coordinates [(x1,y1), (x2,y2)]
1578                and also by a string descriptor (eg. "bottom-left")
1579            title : (str)
1580                title text
1581            font : (str)
1582                title font face. Check [available fonts here](https://vedo.embl.es/fonts).
1583            title_size : (float)
1584                title text scale [1.0]
1585            show_value : (bool)
1586                if True current value is shown
1587            delayed : (bool)
1588                if True the callback is delayed until when the mouse button is released
1589            alpha : (float)
1590                opacity of the scalar bar texts
1591            slider_length : (float)
1592                slider length
1593            slider_width : (float)
1594                slider width
1595            end_cap_length : (float)
1596                length of the end cap
1597            end_cap_width : (float)
1598                width of the end cap
1599            tube_width : (float)
1600                width of the tube
1601            title_height : (float)
1602                height of the title
1603            tformat : (str)
1604                format of the title
1605
1606        Examples:
1607            - [sliders1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders1.py)
1608            - [sliders2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders2.py)
1609
1610            ![](https://user-images.githubusercontent.com/32848391/50738848-be033480-11d8-11e9-9b1a-c13105423a79.jpg)
1611        """
1612        slider_length = options.pop("slider_length",  0.015)
1613        slider_width  = options.pop("slider_width",   0.025)
1614        end_cap_length= options.pop("end_cap_length", 0.0015)
1615        end_cap_width = options.pop("end_cap_width",  0.0125)
1616        tube_width    = options.pop("tube_width",     0.0075)
1617        title_height  = options.pop("title_height",   0.025)
1618        tformat       = options.pop("tformat",        None)
1619
1620        if options:
1621            vedo.logger.warning(f"in Slider2D unknown option(s): {options}")
1622
1623        c = get_color(c)
1624
1625        if value is None or value < xmin:
1626            value = xmin
1627
1628        slider_rep = vtki.new("SliderRepresentation2D")
1629        slider_rep.SetMinimumValue(xmin)
1630        slider_rep.SetMaximumValue(xmax)
1631        slider_rep.SetValue(value)
1632        slider_rep.SetSliderLength(slider_length)
1633        slider_rep.SetSliderWidth(slider_width)
1634        slider_rep.SetEndCapLength(end_cap_length)
1635        slider_rep.SetEndCapWidth(end_cap_width)
1636        slider_rep.SetTubeWidth(tube_width)
1637        slider_rep.GetPoint1Coordinate().SetCoordinateSystemToNormalizedDisplay()
1638        slider_rep.GetPoint2Coordinate().SetCoordinateSystemToNormalizedDisplay()
1639
1640        if isinstance(pos, str):
1641            if "top" in pos:
1642                if "left" in pos:
1643                    if "vert" in pos:
1644                        pos = 11
1645                    else:
1646                        pos = 1
1647                elif "right" in pos:
1648                    if "vert" in pos:
1649                        pos = 12
1650                    else:
1651                        pos = 2
1652            elif "bott" in pos:
1653                if "left" in pos:
1654                    if "vert" in pos:
1655                        pos = 13
1656                    else:
1657                        pos = 3
1658                elif "right" in pos:
1659                    if "vert" in pos:
1660                        if "span" in pos:
1661                            pos = 15
1662                        else:
1663                            pos = 14
1664                    else:
1665                        pos = 4
1666                elif "span" in pos:
1667                    pos = 5
1668
1669        if utils.is_sequence(pos):
1670            slider_rep.GetPoint1Coordinate().SetValue(pos[0][0], pos[0][1])
1671            slider_rep.GetPoint2Coordinate().SetValue(pos[1][0], pos[1][1])
1672        elif pos == 1:  # top-left horizontal
1673            slider_rep.GetPoint1Coordinate().SetValue(0.04, 0.93)
1674            slider_rep.GetPoint2Coordinate().SetValue(0.45, 0.93)
1675        elif pos == 2:
1676            slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.93)
1677            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.93)
1678        elif pos == 3:
1679            slider_rep.GetPoint1Coordinate().SetValue(0.05, 0.06)
1680            slider_rep.GetPoint2Coordinate().SetValue(0.45, 0.06)
1681        elif pos == 4:  # bottom-right
1682            slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.06)
1683            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06)
1684        elif pos == 5:  # bottom span horizontal
1685            slider_rep.GetPoint1Coordinate().SetValue(0.04, 0.06)
1686            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06)
1687        elif pos == 11:  # top-left vertical
1688            slider_rep.GetPoint1Coordinate().SetValue(0.065, 0.54)
1689            slider_rep.GetPoint2Coordinate().SetValue(0.065, 0.9)
1690        elif pos == 12:
1691            slider_rep.GetPoint1Coordinate().SetValue(0.94, 0.54)
1692            slider_rep.GetPoint2Coordinate().SetValue(0.94, 0.9)
1693        elif pos == 13:
1694            slider_rep.GetPoint1Coordinate().SetValue(0.065, 0.1)
1695            slider_rep.GetPoint2Coordinate().SetValue(0.065, 0.54)
1696        elif pos == 14:  # bottom-right vertical
1697            slider_rep.GetPoint1Coordinate().SetValue(0.94, 0.1)
1698            slider_rep.GetPoint2Coordinate().SetValue(0.94, 0.54)
1699        elif pos == 15:  # right margin vertical
1700            slider_rep.GetPoint1Coordinate().SetValue(0.95, 0.1)
1701            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.9)
1702        else:  # bottom-right
1703            slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.06)
1704            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06)
1705
1706        if show_value:
1707            if tformat is None:
1708                if isinstance(xmin, int) and isinstance(xmax, int) and isinstance(value, int):
1709                    tformat = "%0.0f"
1710                else:
1711                    tformat = "%0.2f"
1712
1713            slider_rep.SetLabelFormat(tformat)  # default is '%0.3g'
1714            slider_rep.GetLabelProperty().SetShadow(0)
1715            slider_rep.GetLabelProperty().SetBold(0)
1716            slider_rep.GetLabelProperty().SetOpacity(alpha)
1717            slider_rep.GetLabelProperty().SetColor(c)
1718            if isinstance(pos, int) and pos > 10:
1719                slider_rep.GetLabelProperty().SetOrientation(90)
1720        else:
1721            slider_rep.ShowSliderLabelOff()
1722        slider_rep.GetTubeProperty().SetColor(c)
1723        slider_rep.GetTubeProperty().SetOpacity(0.75)
1724        slider_rep.GetSliderProperty().SetColor(c)
1725        slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c)))
1726        slider_rep.GetCapProperty().SetColor(c)
1727
1728        slider_rep.SetTitleHeight(title_height * title_size)
1729        slider_rep.GetTitleProperty().SetShadow(0)
1730        slider_rep.GetTitleProperty().SetColor(c)
1731        slider_rep.GetTitleProperty().SetOpacity(alpha)
1732        slider_rep.GetTitleProperty().SetBold(0)
1733        if font.lower() == "courier":
1734            slider_rep.GetTitleProperty().SetFontFamilyToCourier()
1735        elif font.lower() == "times":
1736            slider_rep.GetTitleProperty().SetFontFamilyToTimes()
1737        elif font.lower() == "arial":
1738            slider_rep.GetTitleProperty().SetFontFamilyToArial()
1739        else:
1740            if font == "":
1741                font = utils.get_font_path(settings.default_font)
1742            else:
1743                font = utils.get_font_path(font)
1744            slider_rep.GetTitleProperty().SetFontFamily(vtki.VTK_FONT_FILE)
1745            slider_rep.GetLabelProperty().SetFontFamily(vtki.VTK_FONT_FILE)
1746            slider_rep.GetTitleProperty().SetFontFile(font)
1747            slider_rep.GetLabelProperty().SetFontFile(font)
1748
1749        if title:
1750            slider_rep.SetTitleText(title)
1751            if not utils.is_sequence(pos):
1752                if isinstance(pos, int) and pos > 10:
1753                    slider_rep.GetTitleProperty().SetOrientation(90)
1754            else:
1755                if abs(pos[0][0] - pos[1][0]) < 0.1:
1756                    slider_rep.GetTitleProperty().SetOrientation(90)
1757
1758        super().__init__()
1759
1760        self.SetAnimationModeToJump()
1761        self.SetRepresentation(slider_rep)
1762        if delayed:
1763            self.AddObserver("EndInteractionEvent", sliderfunc)
1764        else:
1765            self.AddObserver("InteractionEvent", sliderfunc)

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

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

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

Inherited Members
SliderWidget
on
off
toggle
add_observer
class Slider3D(SliderWidget):
1769class Slider3D(SliderWidget):
1770    """
1771    Add a 3D slider which can call an external custom function.
1772    """
1773
1774    def __init__(
1775        self,
1776        sliderfunc,
1777        pos1,
1778        pos2,
1779        xmin,
1780        xmax,
1781        value=None,
1782        s=0.03,
1783        t=1,
1784        title="",
1785        rotation=0,
1786        c=None,
1787        show_value=True,
1788    ):
1789        """
1790        Add a 3D slider which can call an external custom function.
1791
1792        Arguments:
1793            sliderfunc : (function)
1794                external function to be called by the widget
1795            pos1 : (list)
1796                first position 3D coordinates
1797            pos2 : (list)
1798                second position 3D coordinates
1799            xmin : (float)
1800                lower value
1801            xmax : (float)
1802                upper value
1803            value : (float)
1804                initial value
1805            s : (float)
1806                label scaling factor
1807            t : (float)
1808                tube scaling factor
1809            title : (str)
1810                title text
1811            c : (color)
1812                slider color
1813            rotation : (float)
1814                title rotation around slider axis
1815            show_value : (bool)
1816                if True current value is shown on top of the slider
1817
1818        Examples:
1819            - [sliders3d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders3d.py)
1820        """
1821        c = get_color(c)
1822
1823        if value is None or value < xmin:
1824            value = xmin
1825
1826        slider_rep = vtki.new("SliderRepresentation3D")
1827        slider_rep.SetMinimumValue(xmin)
1828        slider_rep.SetMaximumValue(xmax)
1829        slider_rep.SetValue(value)
1830
1831        slider_rep.GetPoint1Coordinate().SetCoordinateSystemToWorld()
1832        slider_rep.GetPoint2Coordinate().SetCoordinateSystemToWorld()
1833        slider_rep.GetPoint1Coordinate().SetValue(pos2)
1834        slider_rep.GetPoint2Coordinate().SetValue(pos1)
1835
1836        # slider_rep.SetPoint1InWorldCoordinates(pos2[0], pos2[1], pos2[2])
1837        # slider_rep.SetPoint2InWorldCoordinates(pos1[0], pos1[1], pos1[2])
1838
1839        slider_rep.SetSliderWidth(0.03 * t)
1840        slider_rep.SetTubeWidth(0.01 * t)
1841        slider_rep.SetSliderLength(0.04 * t)
1842        slider_rep.SetSliderShapeToCylinder()
1843        slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c)))
1844        slider_rep.GetSliderProperty().SetColor(np.array(c) / 1.5)
1845        slider_rep.GetCapProperty().SetOpacity(0)
1846        slider_rep.SetRotation(rotation)
1847
1848        if not show_value:
1849            slider_rep.ShowSliderLabelOff()
1850
1851        slider_rep.SetTitleText(title)
1852        slider_rep.SetTitleHeight(s * t)
1853        slider_rep.SetLabelHeight(s * t * 0.85)
1854
1855        slider_rep.GetTubeProperty().SetColor(c)
1856
1857        super().__init__()
1858
1859        self.SetRepresentation(slider_rep)
1860        self.SetAnimationModeToJump()
1861        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)
1774    def __init__(
1775        self,
1776        sliderfunc,
1777        pos1,
1778        pos2,
1779        xmin,
1780        xmax,
1781        value=None,
1782        s=0.03,
1783        t=1,
1784        title="",
1785        rotation=0,
1786        c=None,
1787        show_value=True,
1788    ):
1789        """
1790        Add a 3D slider which can call an external custom function.
1791
1792        Arguments:
1793            sliderfunc : (function)
1794                external function to be called by the widget
1795            pos1 : (list)
1796                first position 3D coordinates
1797            pos2 : (list)
1798                second position 3D coordinates
1799            xmin : (float)
1800                lower value
1801            xmax : (float)
1802                upper value
1803            value : (float)
1804                initial value
1805            s : (float)
1806                label scaling factor
1807            t : (float)
1808                tube scaling factor
1809            title : (str)
1810                title text
1811            c : (color)
1812                slider color
1813            rotation : (float)
1814                title rotation around slider axis
1815            show_value : (bool)
1816                if True current value is shown on top of the slider
1817
1818        Examples:
1819            - [sliders3d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders3d.py)
1820        """
1821        c = get_color(c)
1822
1823        if value is None or value < xmin:
1824            value = xmin
1825
1826        slider_rep = vtki.new("SliderRepresentation3D")
1827        slider_rep.SetMinimumValue(xmin)
1828        slider_rep.SetMaximumValue(xmax)
1829        slider_rep.SetValue(value)
1830
1831        slider_rep.GetPoint1Coordinate().SetCoordinateSystemToWorld()
1832        slider_rep.GetPoint2Coordinate().SetCoordinateSystemToWorld()
1833        slider_rep.GetPoint1Coordinate().SetValue(pos2)
1834        slider_rep.GetPoint2Coordinate().SetValue(pos1)
1835
1836        # slider_rep.SetPoint1InWorldCoordinates(pos2[0], pos2[1], pos2[2])
1837        # slider_rep.SetPoint2InWorldCoordinates(pos1[0], pos1[1], pos1[2])
1838
1839        slider_rep.SetSliderWidth(0.03 * t)
1840        slider_rep.SetTubeWidth(0.01 * t)
1841        slider_rep.SetSliderLength(0.04 * t)
1842        slider_rep.SetSliderShapeToCylinder()
1843        slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c)))
1844        slider_rep.GetSliderProperty().SetColor(np.array(c) / 1.5)
1845        slider_rep.GetCapProperty().SetOpacity(0)
1846        slider_rep.SetRotation(rotation)
1847
1848        if not show_value:
1849            slider_rep.ShowSliderLabelOff()
1850
1851        slider_rep.SetTitleText(title)
1852        slider_rep.SetTitleHeight(s * t)
1853        slider_rep.SetLabelHeight(s * t * 0.85)
1854
1855        slider_rep.GetTubeProperty().SetColor(c)
1856
1857        super().__init__()
1858
1859        self.SetRepresentation(slider_rep)
1860        self.SetAnimationModeToJump()
1861        self.AddObserver("InteractionEvent", sliderfunc)

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

Arguments:
  • sliderfunc : (function) external function to be called by the widget
  • pos1 : (list) first position 3D coordinates
  • pos2 : (list) second position 3D coordinates
  • xmin : (float) lower value
  • xmax : (float) upper value
  • value : (float) initial value
  • s : (float) label scaling factor
  • t : (float) tube scaling factor
  • title : (str) title text
  • c : (color) slider color
  • rotation : (float) title rotation around slider axis
  • show_value : (bool) if True current value is shown on top of the slider
Examples:
Inherited Members
SliderWidget
on
off
toggle
add_observer
class Icon(vtkmodules.vtkInteractionWidgets.vtkOrientationMarkerWidget):
2519class Icon(vtki.vtkOrientationMarkerWidget):
2520    """
2521    Add an inset icon mesh into the renderer.
2522    """
2523
2524    def __init__(self, mesh, pos=3, size=0.08):
2525        """
2526        Arguments:
2527            pos : (list, int)
2528                icon position in the range [1-4] indicating one of the 4 corners,
2529                or it can be a tuple (x,y) as a fraction of the renderer size.
2530            size : (float)
2531                size of the icon space as fraction of the window size.
2532
2533        Examples:
2534            - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py)
2535        """
2536        super().__init__()
2537
2538        try:
2539            self.SetOrientationMarker(mesh.actor)
2540        except AttributeError:
2541            self.SetOrientationMarker(mesh)
2542
2543        if utils.is_sequence(pos):
2544            self.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size)
2545        else:
2546            if pos < 2:
2547                self.SetViewport(0, 1 - 2 * size, size * 2, 1)
2548            elif pos == 2:
2549                self.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1)
2550            elif pos == 3:
2551                self.SetViewport(0, 0, size * 2, size * 2)
2552            elif pos == 4:
2553                self.SetViewport(1 - 2 * size, 0, 1, size * 2)

Add an inset icon mesh into the renderer.

Icon(mesh, pos=3, size=0.08)
2524    def __init__(self, mesh, pos=3, size=0.08):
2525        """
2526        Arguments:
2527            pos : (list, int)
2528                icon position in the range [1-4] indicating one of the 4 corners,
2529                or it can be a tuple (x,y) as a fraction of the renderer size.
2530            size : (float)
2531                size of the icon space as fraction of the window size.
2532
2533        Examples:
2534            - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py)
2535        """
2536        super().__init__()
2537
2538        try:
2539            self.SetOrientationMarker(mesh.actor)
2540        except AttributeError:
2541            self.SetOrientationMarker(mesh)
2542
2543        if utils.is_sequence(pos):
2544            self.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size)
2545        else:
2546            if pos < 2:
2547                self.SetViewport(0, 1 - 2 * size, size * 2, 1)
2548            elif pos == 2:
2549                self.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1)
2550            elif pos == 3:
2551                self.SetViewport(0, 0, size * 2, size * 2)
2552            elif pos == 4:
2553                self.SetViewport(1 - 2 * size, 0, 1, size * 2)
Arguments:
  • pos : (list, int) icon position in the range [1-4] indicating one of the 4 corners, or it can be a tuple (x,y) as a fraction of the renderer size.
  • size : (float) size of the icon space as fraction of the window size.
Examples:
class LegendBox(vedo.shapes.TextBase, vtkmodules.vtkRenderingAnnotation.vtkLegendBoxActor):
214class LegendBox(shapes.TextBase, vtki.vtkLegendBoxActor):
215    """
216    Create a 2D legend box.
217    """
218    def __init__(
219        self,
220        entries=(),
221        nmax=12,
222        c=None,
223        font="",
224        width=0.18,
225        height=None,
226        padding=2,
227        bg="k8",
228        alpha=0.25,
229        pos="top-right",
230        markers=None,
231    ):
232        """
233        Create a 2D legend box for the list of specified objects.
234
235        Arguments:
236            nmax : (int)
237                max number of legend entries
238            c : (color)
239                text color, leave as None to pick the mesh color automatically
240            font : (str)
241                Check [available fonts here](https://vedo.embl.es/fonts)
242            width : (float)
243                width of the box as fraction of the window width
244            height : (float)
245                height of the box as fraction of the window height
246            padding : (int)
247                padding space in units of pixels
248            bg : (color)
249                background color of the box
250            alpha: (float)
251                opacity of the box
252            pos : (str, list)
253                position of the box, can be either a string or a (x,y) screen position in range [0,1]
254
255        Examples:
256            - [legendbox.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/legendbox.py)
257            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
258            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
259
260                ![](https://vedo.embl.es/images/other/flag_labels.png)
261        """
262        super().__init__()
263
264        self.name = "LegendBox"
265        self.entries = entries[:nmax]
266        self.properties = self.GetEntryTextProperty()
267
268        n = 0
269        texts = []
270        for e in self.entries:
271            ename = e.name
272            if "legend" in e.info.keys():
273                if not e.info["legend"]:
274                    ename = ""
275                else:
276                    ename = str(e.info["legend"])
277            if ename:
278                n += 1
279            texts.append(ename)
280        self.SetNumberOfEntries(n)
281
282        if not n:
283            return
284
285        self.ScalarVisibilityOff()
286        self.PickableOff()
287        self.SetPadding(padding)
288
289        self.properties.ShadowOff()
290        self.properties.BoldOff()
291
292        # self.properties.SetJustificationToLeft() # no effect
293        # self.properties.SetVerticalJustificationToTop()
294
295        if not font:
296            font = settings.default_font
297
298        self.font(font)
299
300        n = 0
301        for i in range(len(self.entries)):
302            ti = texts[i]
303            if not ti:
304                continue
305            e = entries[i]
306            if c is None:
307                col = e.properties.GetColor()
308                if col == (1, 1, 1):
309                    col = (0.2, 0.2, 0.2)
310            else:
311                col = get_color(c)
312            if markers is None:  # default
313                poly = e.dataset
314            else:
315                marker = markers[i] if utils.is_sequence(markers) else markers
316                if isinstance(marker, Points):
317                    poly = marker.clone(deep=False).normalize().shift(0, 1, 0).dataset
318                else:  # assume string marker
319                    poly = vedo.shapes.Marker(marker, s=1).shift(0, 1, 0).dataset
320
321            self.SetEntry(n, poly, ti, col)
322            n += 1
323
324        self.SetWidth(width)
325        if height is None:
326            self.SetHeight(width / 3.0 * n)
327        else:
328            self.SetHeight(height)
329
330        sx, sy = 1 - self.GetWidth(), 1 - self.GetHeight()
331        if pos == 1 or ("top" in pos and "left" in pos):
332            self.GetPositionCoordinate().SetValue(0, sy)
333        elif pos == 2 or ("top" in pos and "right" in pos):
334            self.GetPositionCoordinate().SetValue(sx, sy)
335        elif pos == 3 or ("bottom" in pos and "left" in pos):
336            self.GetPositionCoordinate().SetValue(0, 0)
337        elif pos == 4 or ("bottom" in pos and "right" in pos):
338            self.GetPositionCoordinate().SetValue(sx, 0)
339        if alpha:
340            self.UseBackgroundOn()
341            self.SetBackgroundColor(get_color(bg))
342            self.SetBackgroundOpacity(alpha)
343        else:
344            self.UseBackgroundOff()
345        self.LockBorderOn()

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)
218    def __init__(
219        self,
220        entries=(),
221        nmax=12,
222        c=None,
223        font="",
224        width=0.18,
225        height=None,
226        padding=2,
227        bg="k8",
228        alpha=0.25,
229        pos="top-right",
230        markers=None,
231    ):
232        """
233        Create a 2D legend box for the list of specified objects.
234
235        Arguments:
236            nmax : (int)
237                max number of legend entries
238            c : (color)
239                text color, leave as None to pick the mesh color automatically
240            font : (str)
241                Check [available fonts here](https://vedo.embl.es/fonts)
242            width : (float)
243                width of the box as fraction of the window width
244            height : (float)
245                height of the box as fraction of the window height
246            padding : (int)
247                padding space in units of pixels
248            bg : (color)
249                background color of the box
250            alpha: (float)
251                opacity of the box
252            pos : (str, list)
253                position of the box, can be either a string or a (x,y) screen position in range [0,1]
254
255        Examples:
256            - [legendbox.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/legendbox.py)
257            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
258            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
259
260                ![](https://vedo.embl.es/images/other/flag_labels.png)
261        """
262        super().__init__()
263
264        self.name = "LegendBox"
265        self.entries = entries[:nmax]
266        self.properties = self.GetEntryTextProperty()
267
268        n = 0
269        texts = []
270        for e in self.entries:
271            ename = e.name
272            if "legend" in e.info.keys():
273                if not e.info["legend"]:
274                    ename = ""
275                else:
276                    ename = str(e.info["legend"])
277            if ename:
278                n += 1
279            texts.append(ename)
280        self.SetNumberOfEntries(n)
281
282        if not n:
283            return
284
285        self.ScalarVisibilityOff()
286        self.PickableOff()
287        self.SetPadding(padding)
288
289        self.properties.ShadowOff()
290        self.properties.BoldOff()
291
292        # self.properties.SetJustificationToLeft() # no effect
293        # self.properties.SetVerticalJustificationToTop()
294
295        if not font:
296            font = settings.default_font
297
298        self.font(font)
299
300        n = 0
301        for i in range(len(self.entries)):
302            ti = texts[i]
303            if not ti:
304                continue
305            e = entries[i]
306            if c is None:
307                col = e.properties.GetColor()
308                if col == (1, 1, 1):
309                    col = (0.2, 0.2, 0.2)
310            else:
311                col = get_color(c)
312            if markers is None:  # default
313                poly = e.dataset
314            else:
315                marker = markers[i] if utils.is_sequence(markers) else markers
316                if isinstance(marker, Points):
317                    poly = marker.clone(deep=False).normalize().shift(0, 1, 0).dataset
318                else:  # assume string marker
319                    poly = vedo.shapes.Marker(marker, s=1).shift(0, 1, 0).dataset
320
321            self.SetEntry(n, poly, ti, col)
322            n += 1
323
324        self.SetWidth(width)
325        if height is None:
326            self.SetHeight(width / 3.0 * n)
327        else:
328            self.SetHeight(height)
329
330        sx, sy = 1 - self.GetWidth(), 1 - self.GetHeight()
331        if pos == 1 or ("top" in pos and "left" in pos):
332            self.GetPositionCoordinate().SetValue(0, sy)
333        elif pos == 2 or ("top" in pos and "right" in pos):
334            self.GetPositionCoordinate().SetValue(sx, sy)
335        elif pos == 3 or ("bottom" in pos and "left" in pos):
336            self.GetPositionCoordinate().SetValue(0, 0)
337        elif pos == 4 or ("bottom" in pos and "right" in pos):
338            self.GetPositionCoordinate().SetValue(sx, 0)
339        if alpha:
340            self.UseBackgroundOn()
341            self.SetBackgroundColor(get_color(bg))
342            self.SetBackgroundOpacity(alpha)
343        else:
344            self.UseBackgroundOff()
345        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:
def Light(pos, focal_point=(0, 0, 0), angle=180, c=None, intensity=1):
 985def Light(pos, focal_point=(0, 0, 0), angle=180, c=None, intensity=1):
 986    """
 987    Generate a source of light placed at `pos` and directed to `focal point`.
 988    Returns a `vtkLight` object.
 989
 990    Arguments:
 991        focal_point : (list)
 992            focal point, if a `vedo` object is passed then will grab its position.
 993        angle : (float)
 994            aperture angle of the light source, in degrees
 995        c : (color)
 996            set the light color
 997        intensity : (float)
 998            intensity value between 0 and 1.
 999
1000    Check also:
1001        `plotter.Plotter.remove_lights()`
1002
1003    Examples:
1004        - [light_sources.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/light_sources.py)
1005
1006            ![](https://vedo.embl.es/images/basic/lights.png)
1007    """
1008    if c is None:
1009        try:
1010            c = pos.color()
1011        except AttributeError:
1012            c = "white"
1013
1014    try:
1015        pos = pos.pos()
1016    except AttributeError:
1017        pass
1018    
1019    try:
1020        focal_point = focal_point.pos()
1021    except AttributeError:
1022        pass
1023
1024    light = vtki.vtkLight()
1025    light.SetLightTypeToSceneLight()
1026    light.SetPosition(pos)
1027    light.SetConeAngle(angle)
1028    light.SetFocalPoint(focal_point)
1029    light.SetIntensity(intensity)
1030    light.SetColor(get_color(c))
1031    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]:
3098def Axes(
3099        obj=None,
3100        xtitle='x', ytitle='y', ztitle='z',
3101        xrange=None, yrange=None, zrange=None,
3102        c=None,
3103        number_of_divisions=None,
3104        digits=None,
3105        limit_ratio=0.04,
3106        title_depth=0,
3107        title_font="", # grab settings.default_font
3108        text_scale=1.0,
3109        x_values_and_labels=None, y_values_and_labels=None, z_values_and_labels=None,
3110        htitle="",
3111        htitle_size=0.03,
3112        htitle_font=None,
3113        htitle_italic=False,
3114        htitle_color=None, htitle_backface_color=None,
3115        htitle_justify='bottom-left',
3116        htitle_rotation=0,
3117        htitle_offset=(0, 0.01, 0),
3118        xtitle_position=0.95, ytitle_position=0.95, ztitle_position=0.95,
3119        # xtitle_offset can be a list (dx,dy,dz)
3120        xtitle_offset=0.025,  ytitle_offset=0.0275, ztitle_offset=0.02,
3121        xtitle_justify=None,  ytitle_justify=None,  ztitle_justify=None,
3122        # xtitle_rotation can be a list (rx,ry,rz)
3123        xtitle_rotation=0, ytitle_rotation=0, ztitle_rotation=0,
3124        xtitle_box=False,  ytitle_box=False,
3125        xtitle_size=0.025, ytitle_size=0.025, ztitle_size=0.025,
3126        xtitle_color=None, ytitle_color=None, ztitle_color=None,
3127        xtitle_backface_color=None, ytitle_backface_color=None, ztitle_backface_color=None,
3128        xtitle_italic=0, ytitle_italic=0, ztitle_italic=0,
3129        grid_linewidth=1,
3130        xygrid=True,   yzgrid=False,  zxgrid=False,
3131        xygrid2=False, yzgrid2=False, zxgrid2=False,
3132        xygrid_transparent=False,  yzgrid_transparent=False,  zxgrid_transparent=False,
3133        xygrid2_transparent=False, yzgrid2_transparent=False, zxgrid2_transparent=False,
3134        xyplane_color=None, yzplane_color=None, zxplane_color=None,
3135        xygrid_color=None, yzgrid_color=None, zxgrid_color=None,
3136        xyalpha=0.075, yzalpha=0.075, zxalpha=0.075,
3137        xyframe_line=None, yzframe_line=None, zxframe_line=None,
3138        xyframe_color=None, yzframe_color=None, zxframe_color=None,
3139        axes_linewidth=1,
3140        xline_color=None, yline_color=None, zline_color=None,
3141        xhighlight_zero=False, yhighlight_zero=False, zhighlight_zero=False,
3142        xhighlight_zero_color='red4', yhighlight_zero_color='green4', zhighlight_zero_color='blue4',
3143        show_ticks=True,
3144        xtick_length=0.015, ytick_length=0.015, ztick_length=0.015,
3145        xtick_thickness=0.0025, ytick_thickness=0.0025, ztick_thickness=0.0025,
3146        xminor_ticks=1, yminor_ticks=1, zminor_ticks=1,
3147        tip_size=None,
3148        label_font="", # grab settings.default_font
3149        xlabel_color=None, ylabel_color=None, zlabel_color=None,
3150        xlabel_backface_color=None, ylabel_backface_color=None, zlabel_backface_color=None,
3151        xlabel_size=0.016, ylabel_size=0.016, zlabel_size=0.016,
3152        xlabel_offset=0.8, ylabel_offset=0.8, zlabel_offset=0.8, # each can be a list (dx,dy,dz)
3153        xlabel_justify=None, ylabel_justify=None, zlabel_justify=None,
3154        xlabel_rotation=0, ylabel_rotation=0, zlabel_rotation=0, # each can be a list (rx,ry,rz)
3155        xaxis_rotation=0, yaxis_rotation=0, zaxis_rotation=0,    # rotate all elements around axis
3156        xyshift=0, yzshift=0, zxshift=0,
3157        xshift_along_y=0, xshift_along_z=0,
3158        yshift_along_x=0, yshift_along_z=0,
3159        zshift_along_x=0, zshift_along_y=0,
3160        x_use_bounds=True, y_use_bounds=True, z_use_bounds=False,
3161        x_inverted=False, y_inverted=False, z_inverted=False,
3162        use_global=False,
3163        tol=0.001,
3164    ) -> Union[Assembly, None]:
3165    """
3166    Draw axes for the input object.
3167    Check [available fonts here](https://vedo.embl.es/fonts).
3168
3169    Returns an `vedo.Assembly` object.
3170
3171    Parameters
3172    ----------
3173
3174    - `xtitle`,                 ['x'], x-axis title text
3175    - `xrange`,                [None], x-axis range in format (xmin, ymin), default is automatic.
3176    - `number_of_divisions`,   [None], approximate number of divisions on the longest axis
3177    - `axes_linewidth`,           [1], width of the axes lines
3178    - `grid_linewidth`,           [1], width of the grid lines
3179    - `title_depth`,              [0], extrusion fractional depth of title text
3180    - `x_values_and_labels`        [], assign custom tick positions and labels [(pos1, label1), ...]
3181    - `xygrid`,                [True], show a gridded wall on plane xy
3182    - `yzgrid`,                [True], show a gridded wall on plane yz
3183    - `zxgrid`,                [True], show a gridded wall on plane zx
3184    - `yzgrid2`,              [False], show yz plane on opposite side of the bounding box
3185    - `zxgrid2`,              [False], show zx plane on opposite side of the bounding box
3186    - `xygrid_transparent`    [False], make grid plane completely transparent
3187    - `xygrid2_transparent`   [False], make grid plane completely transparent on opposite side box
3188    - `xyplane_color`,       ['None'], color of the plane
3189    - `xygrid_color`,        ['None'], grid line color
3190    - `xyalpha`,               [0.15], grid plane opacity
3191    - `xyframe_line`,             [0], add a frame for the plane, use value as the thickness
3192    - `xyframe_color`,         [None], color for the frame of the plane
3193    - `show_ticks`,            [True], show major ticks
3194    - `digits`,                [None], use this number of significant digits in scientific notation
3195    - `title_font`,              [''], font for axes titles
3196    - `label_font`,              [''], font for numeric labels
3197    - `text_scale`,             [1.0], global scaling factor for all text elements (titles, labels)
3198    - `htitle`,                  [''], header title
3199    - `htitle_size`,           [0.03], header title size
3200    - `htitle_font`,           [None], header font (defaults to `title_font`)
3201    - `htitle_italic`,         [True], header font is italic
3202    - `htitle_color`,          [None], header title color (defaults to `xtitle_color`)
3203    - `htitle_backface_color`, [None], header title color on its backface
3204    - `htitle_justify`, ['bottom-center'], origin of the title justification
3205    - `htitle_offset`,   [(0,0.01,0)], control offsets of header title in x, y and z
3206    - `xtitle_position`,       [0.32], title fractional positions along axis
3207    - `xtitle_offset`,         [0.05], title fractional offset distance from axis line, can be a list
3208    - `xtitle_justify`,        [None], choose the origin of the bounding box of title
3209    - `xtitle_rotation`,          [0], add a rotation of the axis title, can be a list (rx,ry,rz)
3210    - `xtitle_box`,           [False], add a box around title text
3211    - `xline_color`,      [automatic], color of the x-axis
3212    - `xtitle_color`,     [automatic], color of the axis title
3213    - `xtitle_backface_color`, [None], color of axis title on its backface
3214    - `xtitle_size`,          [0.025], size of the axis title
3215    - `xtitle_italic`,            [0], a bool or float to make the font italic
3216    - `xhighlight_zero`,       [True], draw a line highlighting zero position if in range
3217    - `xhighlight_zero_color`, [auto], color of the line highlighting the zero position
3218    - `xtick_length`,         [0.005], radius of the major ticks
3219    - `xtick_thickness`,     [0.0025], thickness of the major ticks along their axis
3220    - `xminor_ticks`,             [1], number of minor ticks between two major ticks
3221    - `xlabel_color`,     [automatic], color of numeric labels and ticks
3222    - `xlabel_backface_color`, [auto], back face color of numeric labels and ticks
3223    - `xlabel_size`,          [0.015], size of the numeric labels along axis
3224    - `xlabel_rotation`,     [0,list], numeric labels rotation (can be a list of 3 rotations)
3225    - `xlabel_offset`,     [0.8,list], offset of the numeric labels (can be a list of 3 offsets)
3226    - `xlabel_justify`,        [None], choose the origin of the bounding box of labels
3227    - `xaxis_rotation`,           [0], rotate the X axis elements (ticks and labels) around this same axis
3228    - `xyshift`                 [0.0], slide the xy-plane along z (the range is [0,1])
3229    - `xshift_along_y`          [0.0], slide x-axis along the y-axis (the range is [0,1])
3230    - `tip_size`,              [0.01], size of the arrow tip as a fraction of the bounding box diagonal
3231    - `limit_ratio`,           [0.04], below this ratio don't plot smaller axis
3232    - `x_use_bounds`,          [True], keep into account space occupied by labels when setting camera
3233    - `x_inverted`,           [False], invert labels order and direction (only visually!)
3234    - `use_global`,           [False], try to compute the global bounding box of visible actors
3235
3236    Example:
3237        ```python
3238        from vedo import Axes, Box, show
3239        box = Box(pos=(1,2,3), length=8, width=9, height=7).alpha(0.1)
3240        axs = Axes(box, c='k')  # returns an Assembly object
3241        for a in axs.unpack():
3242            print(a.name)
3243        show(box, axs).close()
3244        ```
3245        ![](https://vedo.embl.es/images/feats/axes1.png)
3246
3247    Examples:
3248        - [custom_axes1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes1.py)
3249        - [custom_axes2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes2.py)
3250        - [custom_axes3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes3.py)
3251        - [custom_axes4.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes4.py)
3252
3253        ![](https://vedo.embl.es/images/pyplot/customAxes3.png)
3254    """
3255    if not title_font:
3256        title_font = vedo.settings.default_font
3257    if not label_font:
3258        label_font = vedo.settings.default_font
3259
3260    if c is None:  # automatic black or white
3261        c = (0.1, 0.1, 0.1)
3262        plt = vedo.plotter_instance
3263        if plt and plt.renderer:
3264            bgcol = plt.renderer.GetBackground()
3265        else:
3266            bgcol = (1, 1, 1)
3267        if np.sum(bgcol) < 1.5:
3268            c = (0.9, 0.9, 0.9)
3269    else:
3270        c = get_color(c)
3271
3272    # Check if obj has bounds, if so use those
3273    if obj is not None:
3274        try:
3275            bb = obj.bounds()
3276        except AttributeError:
3277            try:
3278                bb = obj.GetBounds()
3279                if xrange is None: xrange = (bb[0], bb[1])
3280                if yrange is None: yrange = (bb[2], bb[3])
3281                if zrange is None: zrange = (bb[4], bb[5])
3282                obj = None # dont need it anymore
3283            except AttributeError:
3284                pass
3285        if utils.is_sequence(obj) and len(obj)==6 and utils.is_number(obj[0]):
3286            # passing a list of numeric bounds
3287            if xrange is None: xrange = (obj[0], obj[1])
3288            if yrange is None: yrange = (obj[2], obj[3])
3289            if zrange is None: zrange = (obj[4], obj[5])
3290
3291    if use_global:
3292        vbb, drange, min_bns, max_bns = compute_visible_bounds()
3293    else:
3294        if obj is not None:
3295            vbb, drange, min_bns, max_bns = compute_visible_bounds(obj)
3296        else:
3297            vbb = np.zeros(6)
3298            drange = np.zeros(3)
3299            if zrange is None:
3300                zrange = (0, 0)
3301            if xrange is None or yrange is None:
3302                vedo.logger.error("in Axes() must specify axes ranges!")
3303                return None  ###########################################
3304
3305    if xrange is not None:
3306        if xrange[1] < xrange[0]:
3307            x_inverted = True
3308            xrange = [xrange[1], xrange[0]]
3309        vbb[0], vbb[1] = xrange
3310        drange[0] = vbb[1] - vbb[0]
3311        min_bns = vbb
3312        max_bns = vbb
3313    if yrange is not None:
3314        if yrange[1] < yrange[0]:
3315            y_inverted = True
3316            yrange = [yrange[1], yrange[0]]
3317        vbb[2], vbb[3] = yrange
3318        drange[1] = vbb[3] - vbb[2]
3319        min_bns = vbb
3320        max_bns = vbb
3321    if zrange is not None:
3322        if zrange[1] < zrange[0]:
3323            z_inverted = True
3324            zrange = [zrange[1], zrange[0]]
3325        vbb[4], vbb[5] = zrange
3326        drange[2] = vbb[5] - vbb[4]
3327        min_bns = vbb
3328        max_bns = vbb
3329
3330    drangemax = max(drange)
3331    if not drangemax:
3332        return None
3333
3334    if drange[0] / drangemax < limit_ratio:
3335        drange[0] = 0
3336        xtitle = ""
3337    if drange[1] / drangemax < limit_ratio:
3338        drange[1] = 0
3339        ytitle = ""
3340    if drange[2] / drangemax < limit_ratio:
3341        drange[2] = 0
3342        ztitle = ""
3343
3344    x0, x1, y0, y1, z0, z1 = vbb
3345    dx, dy, dz = drange
3346
3347    gscale = np.sqrt(dx * dx + dy * dy + dz * dz) * 0.75
3348
3349    if not xyplane_color: xyplane_color = c
3350    if not yzplane_color: yzplane_color = c
3351    if not zxplane_color: zxplane_color = c
3352    if not xygrid_color:  xygrid_color = c
3353    if not yzgrid_color:  yzgrid_color = c
3354    if not zxgrid_color:  zxgrid_color = c
3355    if not xtitle_color:  xtitle_color = c
3356    if not ytitle_color:  ytitle_color = c
3357    if not ztitle_color:  ztitle_color = c
3358    if not xline_color:   xline_color = c
3359    if not yline_color:   yline_color = c
3360    if not zline_color:   zline_color = c
3361    if not xlabel_color:  xlabel_color = xline_color
3362    if not ylabel_color:  ylabel_color = yline_color
3363    if not zlabel_color:  zlabel_color = zline_color
3364
3365    if tip_size is None:
3366        tip_size = 0.005 * gscale
3367        if not ztitle:
3368            tip_size = 0  # switch off in xy 2d
3369
3370    ndiv = 4
3371    if not ztitle or not ytitle or not xtitle:  # make more default ticks if 2D
3372        ndiv = 6
3373        if not ztitle:
3374            if xyframe_line is None:
3375                xyframe_line = True
3376            if tip_size is None:
3377                tip_size = False
3378
3379    if utils.is_sequence(number_of_divisions):
3380        rx, ry, rz = number_of_divisions
3381    else:
3382        if not number_of_divisions:
3383            number_of_divisions = ndiv
3384
3385    rx, ry, rz = np.ceil(drange / drangemax * number_of_divisions).astype(int)
3386
3387    if xtitle:
3388        xticks_float, xticks_str = utils.make_ticks(x0, x1, rx, x_values_and_labels, digits)
3389        xticks_float = xticks_float * dx
3390        if x_inverted:
3391            xticks_float = np.flip(-(xticks_float - xticks_float[-1]))
3392            xticks_str = list(reversed(xticks_str))
3393            xticks_str[-1] = ""
3394            xhighlight_zero = False
3395    if ytitle:
3396        yticks_float, yticks_str = utils.make_ticks(y0, y1, ry, y_values_and_labels, digits)
3397        yticks_float = yticks_float * dy
3398        if y_inverted:
3399            yticks_float = np.flip(-(yticks_float - yticks_float[-1]))
3400            yticks_str = list(reversed(yticks_str))
3401            yticks_str[-1] = ""
3402            yhighlight_zero = False
3403    if ztitle:
3404        zticks_float, zticks_str = utils.make_ticks(z0, z1, rz, z_values_and_labels, digits)
3405        zticks_float = zticks_float * dz
3406        if z_inverted:
3407            zticks_float = np.flip(-(zticks_float - zticks_float[-1]))
3408            zticks_str = list(reversed(zticks_str))
3409            zticks_str[-1] = ""
3410            zhighlight_zero = False
3411
3412    ################################################ axes lines
3413    lines = []
3414    if xtitle:
3415        axlinex = shapes.Line([0,0,0], [dx,0,0], c=xline_color, lw=axes_linewidth)
3416        axlinex.shift([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3417        axlinex.name = 'xAxis'
3418        lines.append(axlinex)
3419    if ytitle:
3420        axliney = shapes.Line([0,0,0], [0,dy,0], c=yline_color, lw=axes_linewidth)
3421        axliney.shift([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3422        axliney.name = 'yAxis'
3423        lines.append(axliney)
3424    if ztitle:
3425        axlinez = shapes.Line([0,0,0], [0,0,dz], c=zline_color, lw=axes_linewidth)
3426        axlinez.shift([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3427        axlinez.name = 'zAxis'
3428        lines.append(axlinez)
3429
3430    ################################################ grid planes
3431    # all shapes have a name to keep track of them in the Assembly
3432    # if user wants to unpack it
3433    grids = []
3434    if xygrid and xtitle and ytitle:
3435        if not xygrid_transparent:
3436            gxy = shapes.Grid(s=(xticks_float, yticks_float))
3437            gxy.alpha(xyalpha).c(xyplane_color).lw(0)
3438            if xyshift: gxy.shift([0,0,xyshift*dz])
3439            elif tol:   gxy.shift([0,0,-tol*gscale])
3440            gxy.name = "xyGrid"
3441            grids.append(gxy)
3442        if grid_linewidth:
3443            gxy_lines = shapes.Grid(s=(xticks_float, yticks_float))
3444            gxy_lines.c(xyplane_color).lw(grid_linewidth).alpha(xyalpha)
3445            if xyshift: gxy_lines.shift([0,0,xyshift*dz])
3446            elif tol:   gxy_lines.shift([0,0,-tol*gscale])
3447            gxy_lines.name = "xyGridLines"
3448            grids.append(gxy_lines)
3449
3450    if yzgrid and ytitle and ztitle:
3451        if not yzgrid_transparent:
3452            gyz = shapes.Grid(s=(zticks_float, yticks_float))
3453            gyz.alpha(yzalpha).c(yzplane_color).lw(0).rotate_y(-90)
3454            if yzshift: gyz.shift([yzshift*dx,0,0])
3455            elif tol:   gyz.shift([-tol*gscale,0,0])
3456            gyz.name = "yzGrid"
3457            grids.append(gyz)
3458        if grid_linewidth:
3459            gyz_lines = shapes.Grid(s=(zticks_float, yticks_float))
3460            gyz_lines.c(yzplane_color).lw(grid_linewidth).alpha(yzalpha).rotate_y(-90)
3461            if yzshift: gyz_lines.shift([yzshift*dx,0,0])
3462            elif tol:   gyz_lines.shift([-tol*gscale,0,0])
3463            gyz_lines.name = "yzGridLines"
3464            grids.append(gyz_lines)
3465
3466    if zxgrid and ztitle and xtitle:
3467        if not zxgrid_transparent:
3468            gzx = shapes.Grid(s=(xticks_float, zticks_float))
3469            gzx.alpha(zxalpha).c(zxplane_color).lw(0).rotate_x(90)
3470            if zxshift: gzx.shift([0,zxshift*dy,0])
3471            elif tol:   gzx.shift([0,-tol*gscale,0])
3472            gzx.name = "zxGrid"
3473            grids.append(gzx)
3474        if grid_linewidth:
3475            gzx_lines = shapes.Grid(s=(xticks_float, zticks_float))
3476            gzx_lines.c(zxplane_color).lw(grid_linewidth).alpha(zxalpha).rotate_x(90)
3477            if zxshift: gzx_lines.shift([0,zxshift*dy,0])
3478            elif tol:   gzx_lines.shift([0,-tol*gscale,0])
3479            gzx_lines.name = "zxGridLines"
3480            grids.append(gzx_lines)
3481
3482    # Grid2
3483    if xygrid2 and xtitle and ytitle:
3484        if not xygrid2_transparent:
3485            gxy2 = shapes.Grid(s=(xticks_float, yticks_float)).z(dz)
3486            gxy2.alpha(xyalpha).c(xyplane_color).lw(0)
3487            gxy2.shift([0,tol*gscale,0])
3488            gxy2.name = "xyGrid2"
3489            grids.append(gxy2)
3490        if grid_linewidth:
3491            gxy2_lines = shapes.Grid(s=(xticks_float, yticks_float)).z(dz)
3492            gxy2_lines.c(xyplane_color).lw(grid_linewidth).alpha(xyalpha)
3493            gxy2_lines.shift([0,tol*gscale,0])
3494            gxy2_lines.name = "xygrid2Lines"
3495            grids.append(gxy2_lines)
3496
3497    if yzgrid2 and ytitle and ztitle:
3498        if not yzgrid2_transparent:
3499            gyz2 = shapes.Grid(s=(zticks_float, yticks_float))
3500            gyz2.alpha(yzalpha).c(yzplane_color).lw(0)
3501            gyz2.rotate_y(-90).x(dx).shift([tol*gscale,0,0])
3502            gyz2.name = "yzGrid2"
3503            grids.append(gyz2)
3504        if grid_linewidth:
3505            gyz2_lines = shapes.Grid(s=(zticks_float, yticks_float))
3506            gyz2_lines.c(yzplane_color).lw(grid_linewidth).alpha(yzalpha)
3507            gyz2_lines.rotate_y(-90).x(dx).shift([tol*gscale,0,0])
3508            gyz2_lines.name = "yzGrid2Lines"
3509            grids.append(gyz2_lines)
3510
3511    if zxgrid2 and ztitle and xtitle:
3512        if not zxgrid2_transparent:
3513            gzx2 = shapes.Grid(s=(xticks_float, zticks_float))
3514            gzx2.alpha(zxalpha).c(zxplane_color).lw(0)
3515            gzx2.rotate_x(90).y(dy).shift([0,tol*gscale,0])
3516            gzx2.name = "zxGrid2"
3517            grids.append(gzx2)
3518        if grid_linewidth:
3519            gzx2_lines = shapes.Grid(s=(xticks_float, zticks_float))
3520            gzx2_lines.c(zxplane_color).lw(grid_linewidth).alpha(zxalpha)
3521            gzx2_lines.rotate_x(90).y(dy).shift([0,tol*gscale,0])
3522            gzx2_lines.name = "zxGrid2Lines"
3523            grids.append(gzx2_lines)
3524
3525    ################################################ frame lines
3526    framelines = []
3527    if xyframe_line and xtitle and ytitle:
3528        if not xyframe_color:
3529            xyframe_color = xygrid_color
3530        frxy = shapes.Line([[0,dy,0],[dx,dy,0],[dx,0,0],[0,0,0],[0,dy,0]],
3531                           c=xyframe_color, lw=xyframe_line)
3532        frxy.shift([0,0,xyshift*dz])
3533        frxy.name = 'xyFrameLine'
3534        framelines.append(frxy)
3535    if yzframe_line and ytitle and ztitle:
3536        if not yzframe_color:
3537            yzframe_color = yzgrid_color
3538        fryz = shapes.Line([[0,0,dz],[0,dy,dz],[0,dy,0],[0,0,0],[0,0,dz]],
3539                           c=yzframe_color, lw=yzframe_line)
3540        fryz.shift([yzshift*dx,0,0])
3541        fryz.name = 'yzFrameLine'
3542        framelines.append(fryz)
3543    if zxframe_line and ztitle and xtitle:
3544        if not zxframe_color:
3545            zxframe_color = zxgrid_color
3546        frzx = shapes.Line([[0,0,dz],[dx,0,dz],[dx,0,0],[0,0,0],[0,0,dz]],
3547                           c=zxframe_color, lw=zxframe_line)
3548        frzx.shift([0,zxshift*dy,0])
3549        frzx.name = 'zxFrameLine'
3550        framelines.append(frzx)
3551
3552    ################################################ zero lines highlights
3553    highlights = []
3554    if xygrid and xtitle and ytitle:
3555        if xhighlight_zero and min_bns[0] <= 0 and max_bns[1] > 0:
3556            xhl = -min_bns[0]
3557            hxy = shapes.Line([xhl,0,0], [xhl,dy,0], c=xhighlight_zero_color)
3558            hxy.alpha(np.sqrt(xyalpha)).lw(grid_linewidth*2)
3559            hxy.shift([0,0,xyshift*dz])
3560            hxy.name = "xyHighlightZero"
3561            highlights.append(hxy)
3562        if yhighlight_zero and min_bns[2] <= 0 and max_bns[3] > 0:
3563            yhl = -min_bns[2]
3564            hyx = shapes.Line([0,yhl,0], [dx,yhl,0], c=yhighlight_zero_color)
3565            hyx.alpha(np.sqrt(yzalpha)).lw(grid_linewidth*2)
3566            hyx.shift([0,0,xyshift*dz])
3567            hyx.name = "yxHighlightZero"
3568            highlights.append(hyx)
3569
3570    if yzgrid and ytitle and ztitle:
3571        if yhighlight_zero and min_bns[2] <= 0 and max_bns[3] > 0:
3572            yhl = -min_bns[2]
3573            hyz = shapes.Line([0,yhl,0], [0,yhl,dz], c=yhighlight_zero_color)
3574            hyz.alpha(np.sqrt(yzalpha)).lw(grid_linewidth*2)
3575            hyz.shift([yzshift*dx,0,0])
3576            hyz.name = "yzHighlightZero"
3577            highlights.append(hyz)
3578        if zhighlight_zero and min_bns[4] <= 0 and max_bns[5] > 0:
3579            zhl = -min_bns[4]
3580            hzy = shapes.Line([0,0,zhl], [0,dy,zhl], c=zhighlight_zero_color)
3581            hzy.alpha(np.sqrt(yzalpha)).lw(grid_linewidth*2)
3582            hzy.shift([yzshift*dx,0,0])
3583            hzy.name = "zyHighlightZero"
3584            highlights.append(hzy)
3585
3586    if zxgrid and ztitle and xtitle:
3587        if zhighlight_zero and min_bns[4] <= 0 and max_bns[5] > 0:
3588            zhl = -min_bns[4]
3589            hzx = shapes.Line([0,0,zhl], [dx,0,zhl], c=zhighlight_zero_color)
3590            hzx.alpha(np.sqrt(zxalpha)).lw(grid_linewidth*2)
3591            hzx.shift([0,zxshift*dy,0])
3592            hzx.name = "zxHighlightZero"
3593            highlights.append(hzx)
3594        if xhighlight_zero and min_bns[0] <= 0 and max_bns[1] > 0:
3595            xhl = -min_bns[0]
3596            hxz = shapes.Line([xhl,0,0], [xhl,0,dz], c=xhighlight_zero_color)
3597            hxz.alpha(np.sqrt(zxalpha)).lw(grid_linewidth*2)
3598            hxz.shift([0,zxshift*dy,0])
3599            hxz.name = "xzHighlightZero"
3600            highlights.append(hxz)
3601
3602    ################################################ arrow cone
3603    cones = []
3604
3605    if tip_size:
3606
3607        if xtitle:
3608            if x_inverted:
3609                cx = shapes.Cone(
3610                    r=tip_size, height=tip_size * 2, axis=(-1, 0, 0), c=xline_color, res=12
3611                )
3612            else:
3613                cx = shapes.Cone((dx,0,0), r=tip_size, height=tip_size*2,
3614                                 axis=(1,0,0), c=xline_color, res=12)
3615            T = LinearTransform()
3616            T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3617            cx.apply_transform(T)
3618            cx.name = "xTipCone"
3619            cones.append(cx)
3620
3621        if ytitle:
3622            if y_inverted:
3623                cy = shapes.Cone(r=tip_size, height=tip_size*2,
3624                                 axis=(0,-1,0), c=yline_color, res=12)
3625            else:
3626                cy = shapes.Cone((0,dy,0), r=tip_size, height=tip_size*2,
3627                                 axis=(0,1,0), c=yline_color, res=12)
3628            T = LinearTransform()
3629            T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3630            cy.apply_transform(T)
3631            cy.name = "yTipCone"
3632            cones.append(cy)
3633
3634        if ztitle:
3635            if z_inverted:
3636                cz = shapes.Cone(r=tip_size, height=tip_size*2,
3637                                 axis=(0,0,-1), c=zline_color, res=12)
3638            else:
3639                cz = shapes.Cone((0,0,dz), r=tip_size, height=tip_size*2,
3640                                 axis=(0,0,1), c=zline_color, res=12)
3641            T = LinearTransform()
3642            T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3643            cz.apply_transform(T)
3644            cz.name = "zTipCone"
3645            cones.append(cz)
3646
3647    ################################################################# MAJOR ticks
3648    majorticks, minorticks = [], []
3649    xticks, yticks, zticks = [], [], []
3650    if show_ticks:
3651        if xtitle:
3652            tick_thickness = xtick_thickness * gscale / 2
3653            tick_length = xtick_length * gscale / 2
3654            for i in range(1, len(xticks_float) - 1):
3655                v1 = (xticks_float[i] - tick_thickness, -tick_length, 0)
3656                v2 = (xticks_float[i] + tick_thickness, tick_length, 0)
3657                xticks.append(shapes.Rectangle(v1, v2))
3658            if len(xticks) > 1:
3659                xmajticks = merge(xticks).c(xlabel_color)
3660                T = LinearTransform()
3661                T.rotate_x(xaxis_rotation)
3662                T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3663                xmajticks.apply_transform(T)
3664                xmajticks.name = "xMajorTicks"
3665                majorticks.append(xmajticks)
3666        if ytitle:
3667            tick_thickness = ytick_thickness * gscale / 2
3668            tick_length = ytick_length * gscale / 2
3669            for i in range(1, len(yticks_float) - 1):
3670                v1 = (-tick_length, yticks_float[i] - tick_thickness, 0)
3671                v2 = ( tick_length, yticks_float[i] + tick_thickness, 0)
3672                yticks.append(shapes.Rectangle(v1, v2))
3673            if len(yticks) > 1:
3674                ymajticks = merge(yticks).c(ylabel_color)
3675                T = LinearTransform()
3676                T.rotate_y(yaxis_rotation)
3677                T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3678                ymajticks.apply_transform(T)
3679                ymajticks.name = "yMajorTicks"
3680                majorticks.append(ymajticks)
3681        if ztitle:
3682            tick_thickness = ztick_thickness * gscale / 2
3683            tick_length = ztick_length * gscale / 2.85
3684            for i in range(1, len(zticks_float) - 1):
3685                v1 = (zticks_float[i] - tick_thickness, -tick_length, 0)
3686                v2 = (zticks_float[i] + tick_thickness,  tick_length, 0)
3687                zticks.append(shapes.Rectangle(v1, v2))
3688            if len(zticks) > 1:
3689                zmajticks = merge(zticks).c(zlabel_color)
3690                T = LinearTransform()
3691                T.rotate_y(-90).rotate_z(-45 + zaxis_rotation)
3692                T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3693                zmajticks.apply_transform(T)
3694                zmajticks.name = "zMajorTicks"
3695                majorticks.append(zmajticks)
3696
3697        ############################################################# MINOR ticks
3698        if xtitle and xminor_ticks and len(xticks) > 1:
3699            tick_thickness = xtick_thickness * gscale / 4
3700            tick_length = xtick_length * gscale / 4
3701            xminor_ticks += 1
3702            ticks = []
3703            for i in range(1, len(xticks)):
3704                t0, t1 = xticks[i - 1].pos(), xticks[i].pos()
3705                dt = t1 - t0
3706                for j in range(1, xminor_ticks):
3707                    mt = dt * (j / xminor_ticks) + t0
3708                    v1 = (mt[0] - tick_thickness, -tick_length, 0)
3709                    v2 = (mt[0] + tick_thickness, tick_length, 0)
3710                    ticks.append(shapes.Rectangle(v1, v2))
3711
3712            # finish off the fist lower range from start to first tick
3713            t0, t1 = xticks[0].pos(), xticks[1].pos()
3714            dt = t1 - t0
3715            for j in range(1, xminor_ticks):
3716                mt = t0 - dt * (j / xminor_ticks)
3717                if mt[0] < 0:
3718                    break
3719                v1 = (mt[0] - tick_thickness, -tick_length, 0)
3720                v2 = (mt[0] + tick_thickness,  tick_length, 0)
3721                ticks.append(shapes.Rectangle(v1, v2))
3722
3723            # finish off the last upper range from last tick to end
3724            t0, t1 = xticks[-2].pos(), xticks[-1].pos()
3725            dt = t1 - t0
3726            for j in range(1, xminor_ticks):
3727                mt = t1 + dt * (j / xminor_ticks)
3728                if mt[0] > dx:
3729                    break
3730                v1 = (mt[0] - tick_thickness, -tick_length, 0)
3731                v2 = (mt[0] + tick_thickness,  tick_length, 0)
3732                ticks.append(shapes.Rectangle(v1, v2))
3733
3734            if ticks:
3735                xminticks = merge(ticks).c(xlabel_color)
3736                T = LinearTransform()
3737                T.rotate_x(xaxis_rotation)
3738                T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3739                xminticks.apply_transform(T)
3740                xminticks.name = "xMinorTicks"
3741                minorticks.append(xminticks)
3742
3743        if ytitle and yminor_ticks and len(yticks) > 1:  ##### y
3744            tick_thickness = ytick_thickness * gscale / 4
3745            tick_length = ytick_length * gscale / 4
3746            yminor_ticks += 1
3747            ticks = []
3748            for i in range(1, len(yticks)):
3749                t0, t1 = yticks[i - 1].pos(), yticks[i].pos()
3750                dt = t1 - t0
3751                for j in range(1, yminor_ticks):
3752                    mt = dt * (j / yminor_ticks) + t0
3753                    v1 = (-tick_length, mt[1] - tick_thickness, 0)
3754                    v2 = ( tick_length, mt[1] + tick_thickness, 0)
3755                    ticks.append(shapes.Rectangle(v1, v2))
3756
3757            # finish off the fist lower range from start to first tick
3758            t0, t1 = yticks[0].pos(), yticks[1].pos()
3759            dt = t1 - t0
3760            for j in range(1, yminor_ticks):
3761                mt = t0 - dt * (j / yminor_ticks)
3762                if mt[1] < 0:
3763                    break
3764                v1 = (-tick_length, mt[1] - tick_thickness, 0)
3765                v2 = ( tick_length, mt[1] + tick_thickness, 0)
3766                ticks.append(shapes.Rectangle(v1, v2))
3767
3768            # finish off the last upper range from last tick to end
3769            t0, t1 = yticks[-2].pos(), yticks[-1].pos()
3770            dt = t1 - t0
3771            for j in range(1, yminor_ticks):
3772                mt = t1 + dt * (j / yminor_ticks)
3773                if mt[1] > dy:
3774                    break
3775                v1 = (-tick_length, mt[1] - tick_thickness, 0)
3776                v2 = ( tick_length, mt[1] + tick_thickness, 0)
3777                ticks.append(shapes.Rectangle(v1, v2))
3778
3779            if ticks:
3780                yminticks = merge(ticks).c(ylabel_color)
3781                T = LinearTransform()
3782                T.rotate_y(yaxis_rotation)
3783                T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3784                yminticks.apply_transform(T)
3785                yminticks.name = "yMinorTicks"
3786                minorticks.append(yminticks)
3787
3788        if ztitle and zminor_ticks and len(zticks) > 1:  ##### z
3789            tick_thickness = ztick_thickness * gscale / 4
3790            tick_length = ztick_length * gscale / 5
3791            zminor_ticks += 1
3792            ticks = []
3793            for i in range(1, len(zticks)):
3794                t0, t1 = zticks[i - 1].pos(), zticks[i].pos()
3795                dt = t1 - t0
3796                for j in range(1, zminor_ticks):
3797                    mt = dt * (j / zminor_ticks) + t0
3798                    v1 = (mt[0] - tick_thickness, -tick_length, 0)
3799                    v2 = (mt[0] + tick_thickness,  tick_length, 0)
3800                    ticks.append(shapes.Rectangle(v1, v2))
3801
3802            # finish off the fist lower range from start to first tick
3803            t0, t1 = zticks[0].pos(), zticks[1].pos()
3804            dt = t1 - t0
3805            for j in range(1, zminor_ticks):
3806                mt = t0 - dt * (j / zminor_ticks)
3807                if mt[0] < 0:
3808                    break
3809                v1 = (mt[0] - tick_thickness, -tick_length, 0)
3810                v2 = (mt[0] + tick_thickness,  tick_length, 0)
3811                ticks.append(shapes.Rectangle(v1, v2))
3812
3813            # finish off the last upper range from last tick to end
3814            t0, t1 = zticks[-2].pos(), zticks[-1].pos()
3815            dt = t1 - t0
3816            for j in range(1, zminor_ticks):
3817                mt = t1 + dt * (j / zminor_ticks)
3818                if mt[0] > dz:
3819                    break
3820                v1 = (mt[0] - tick_thickness, -tick_length, 0)
3821                v2 = (mt[0] + tick_thickness,  tick_length, 0)
3822                ticks.append(shapes.Rectangle(v1, v2))
3823
3824            if ticks:
3825                zminticks = merge(ticks).c(zlabel_color)
3826                T = LinearTransform()
3827                T.rotate_y(-90).rotate_z(-45 + zaxis_rotation)
3828                T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3829                zminticks.apply_transform(T)
3830                zminticks.name = "zMinorTicks"
3831                minorticks.append(zminticks)
3832
3833    ################################################ axes NUMERIC text labels
3834    labels = []
3835    xlab, ylab, zlab = None, None, None
3836
3837    if xlabel_size and xtitle:
3838
3839        xRot, yRot, zRot = 0, 0, 0
3840        if utils.is_sequence(xlabel_rotation):  # unpck 3 rotations
3841            zRot, xRot, yRot = xlabel_rotation
3842        else:
3843            zRot = xlabel_rotation
3844        if zRot < 0:  # deal with negative angles
3845            zRot += 360
3846
3847        jus = "center-top"
3848        if zRot:
3849            if zRot >  24: jus = "top-right"
3850            if zRot >  67: jus = "center-right"
3851            if zRot > 112: jus = "right-bottom"
3852            if zRot > 157: jus = "center-bottom"
3853            if zRot > 202: jus = "bottom-left"
3854            if zRot > 247: jus = "center-left"
3855            if zRot > 292: jus = "top-left"
3856            if zRot > 337: jus = "top-center"
3857        if xlabel_justify is not None:
3858            jus = xlabel_justify
3859
3860        for i in range(1, len(xticks_str)):
3861            t = xticks_str[i]
3862            if not t:
3863                continue
3864            if utils.is_sequence(xlabel_offset):
3865                xoffs, yoffs, zoffs = xlabel_offset
3866            else:
3867                xoffs, yoffs, zoffs = 0, xlabel_offset, 0
3868
3869            xlab = shapes.Text3D(
3870                t, s=xlabel_size * text_scale * gscale, font=label_font, justify=jus,
3871            )
3872            tb = xlab.ybounds()  # must be ybounds: height of char
3873
3874            v = (xticks_float[i], 0, 0)
3875            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0])
3876
3877            T = LinearTransform()
3878            T.rotate_x(xaxis_rotation).rotate_y(yRot).rotate_x(xRot).rotate_z(zRot)
3879            T.translate(v + offs)
3880            T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3881            xlab.apply_transform(T)
3882
3883            xlab.use_bounds(x_use_bounds)
3884
3885            xlab.c(xlabel_color)
3886            if xlabel_backface_color is None:
3887                bfc = 1 - np.array(get_color(xlabel_color))
3888                xlab.backcolor(bfc)
3889            xlab.name = f"xNumericLabel {i}"
3890            labels.append(xlab)
3891
3892    if ylabel_size and ytitle:
3893
3894        xRot, yRot, zRot = 0, 0, 0
3895        if utils.is_sequence(ylabel_rotation):  # unpck 3 rotations
3896            zRot, yRot, xRot = ylabel_rotation
3897        else:
3898            zRot = ylabel_rotation
3899        if zRot < 0:
3900            zRot += 360  # deal with negative angles
3901
3902        jus = "center-right"
3903        if zRot:
3904            if zRot >  24: jus = "bottom-right"
3905            if zRot >  67: jus = "center-bottom"
3906            if zRot > 112: jus = "left-bottom"
3907            if zRot > 157: jus = "center-left"
3908            if zRot > 202: jus = "top-left"
3909            if zRot > 247: jus = "center-top"
3910            if zRot > 292: jus = "top-right"
3911            if zRot > 337: jus = "right-center"
3912        if ylabel_justify is not None:
3913            jus = ylabel_justify
3914
3915        for i in range(1, len(yticks_str)):
3916            t = yticks_str[i]
3917            if not t:
3918                continue
3919            if utils.is_sequence(ylabel_offset):
3920                xoffs, yoffs, zoffs = ylabel_offset
3921            else:
3922                xoffs, yoffs, zoffs = ylabel_offset, 0, 0
3923            ylab = shapes.Text3D(
3924                t, s=ylabel_size * text_scale * gscale, font=label_font, justify=jus
3925            )
3926            tb = ylab.ybounds()  # must be ybounds: height of char
3927            v = (0, yticks_float[i], 0)
3928            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0])
3929
3930            T = LinearTransform()
3931            T.rotate_y(yaxis_rotation).rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
3932            T.translate(v + offs)
3933            T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3934            ylab.apply_transform(T)
3935
3936            ylab.use_bounds(y_use_bounds)
3937
3938            ylab.c(ylabel_color)
3939            if ylabel_backface_color is None:
3940                bfc = 1 - np.array(get_color(ylabel_color))
3941                ylab.backcolor(bfc)
3942            ylab.name = f"yNumericLabel {i}"
3943            labels.append(ylab)
3944
3945    if zlabel_size and ztitle:
3946
3947        xRot, yRot, zRot = 0, 0, 0
3948        if utils.is_sequence(zlabel_rotation):  # unpck 3 rotations
3949            xRot, yRot, zRot = zlabel_rotation
3950        else:
3951            xRot = zlabel_rotation
3952        if xRot < 0: xRot += 360 # deal with negative angles
3953
3954        jus = "center-right"
3955        if xRot:
3956            if xRot >  24: jus = "bottom-right"
3957            if xRot >  67: jus = "center-bottom"
3958            if xRot > 112: jus = "left-bottom"
3959            if xRot > 157: jus = "center-left"
3960            if xRot > 202: jus = "top-left"
3961            if xRot > 247: jus = "center-top"
3962            if xRot > 292: jus = "top-right"
3963            if xRot > 337: jus = "right-center"
3964        if zlabel_justify is not None:
3965            jus = zlabel_justify
3966
3967        for i in range(1, len(zticks_str)):
3968            t = zticks_str[i]
3969            if not t:
3970                continue
3971            if utils.is_sequence(zlabel_offset):
3972                xoffs, yoffs, zoffs = zlabel_offset
3973            else:
3974                xoffs, yoffs, zoffs = zlabel_offset, zlabel_offset, 0
3975            zlab = shapes.Text3D(t, s=zlabel_size*text_scale*gscale, font=label_font, justify=jus)
3976            tb = zlab.ybounds()  # must be ybounds: height of char
3977
3978            v = (0, 0, zticks_float[i])
3979            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0]) / 1.5
3980            angle = np.arctan2(dy, dx) * 57.3
3981
3982            T = LinearTransform()
3983            T.rotate_x(90 + zRot).rotate_y(-xRot).rotate_z(angle + yRot + zaxis_rotation)
3984            T.translate(v + offs)
3985            T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3986            zlab.apply_transform(T)
3987
3988            zlab.use_bounds(z_use_bounds)
3989
3990            zlab.c(zlabel_color)
3991            if zlabel_backface_color is None:
3992                bfc = 1 - np.array(get_color(zlabel_color))
3993                zlab.backcolor(bfc)
3994            zlab.name = f"zNumericLabel {i}"
3995            labels.append(zlab)
3996
3997    ################################################ axes titles
3998    titles = []
3999
4000    if xtitle:
4001        xRot, yRot, zRot = 0, 0, 0
4002        if utils.is_sequence(xtitle_rotation):  # unpack 3 rotations
4003            zRot, xRot, yRot = xtitle_rotation
4004        else:
4005            zRot = xtitle_rotation
4006        if zRot < 0:  # deal with negative angles
4007            zRot += 360
4008
4009        if utils.is_sequence(xtitle_offset):
4010            xoffs, yoffs, zoffs = xtitle_offset
4011        else:
4012            xoffs, yoffs, zoffs = 0, xtitle_offset, 0
4013
4014        if xtitle_justify is not None:
4015            jus = xtitle_justify
4016        else:
4017            # find best justfication for given rotation(s)
4018            jus = "right-top"
4019            if zRot:
4020                if zRot >  24: jus = "center-right"
4021                if zRot >  67: jus = "right-bottom"
4022                if zRot > 157: jus = "bottom-left"
4023                if zRot > 202: jus = "center-left"
4024                if zRot > 247: jus = "top-left"
4025                if zRot > 337: jus = "top-right"
4026
4027        xt = shapes.Text3D(
4028            xtitle,
4029            s=xtitle_size * text_scale * gscale,
4030            font=title_font,
4031            c=xtitle_color,
4032            justify=jus,
4033            depth=title_depth,
4034            italic=xtitle_italic,
4035        )
4036        if xtitle_backface_color is None:
4037            xtitle_backface_color = 1 - np.array(get_color(xtitle_color))
4038            xt.backcolor(xtitle_backface_color)
4039
4040        shift = 0
4041        if xlab:  # xlab is the last created numeric text label..
4042            lt0, lt1 = xlab.bounds()[2:4]
4043            shift = lt1 - lt0
4044
4045        T = LinearTransform()
4046        T.rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
4047        T.set_position(
4048            [(xoffs + xtitle_position) * dx,
4049            -(yoffs + xtick_length / 2) * dy - shift,
4050            zoffs * dz]
4051        )
4052        T.rotate_x(xaxis_rotation)
4053        T.translate([0, xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
4054        xt.apply_transform(T)
4055
4056        xt.use_bounds(x_use_bounds)
4057        if xtitle == " ":
4058            xt.use_bounds(False)
4059        xt.name = "xtitle"
4060        titles.append(xt)
4061        if xtitle_box:
4062            titles.append(xt.box(scale=1.1).use_bounds(x_use_bounds))
4063
4064    if ytitle:
4065        xRot, yRot, zRot = 0, 0, 0
4066        if utils.is_sequence(ytitle_rotation):  # unpck 3 rotations
4067            zRot, yRot, xRot = ytitle_rotation
4068        else:
4069            zRot = ytitle_rotation
4070            if len(ytitle) > 3:
4071                zRot += 90
4072                ytitle_position *= 0.975
4073        if zRot < 0:
4074            zRot += 360  # deal with negative angles
4075
4076        if utils.is_sequence(ytitle_offset):
4077            xoffs, yoffs, zoffs = ytitle_offset
4078        else:
4079            xoffs, yoffs, zoffs = ytitle_offset, 0, 0
4080
4081        if ytitle_justify is not None:
4082            jus = ytitle_justify
4083        else:
4084            jus = "center-right"
4085            if zRot:
4086                if zRot >  24: jus = "bottom-right"
4087                if zRot > 112: jus = "left-bottom"
4088                if zRot > 157: jus = "center-left"
4089                if zRot > 202: jus = "top-left"
4090                if zRot > 292: jus = "top-right"
4091                if zRot > 337: jus = "right-center"
4092
4093        yt = shapes.Text3D(
4094            ytitle,
4095            s=ytitle_size * text_scale * gscale,
4096            font=title_font,
4097            c=ytitle_color,
4098            justify=jus,
4099            depth=title_depth,
4100            italic=ytitle_italic,
4101        )
4102        if ytitle_backface_color is None:
4103            ytitle_backface_color = 1 - np.array(get_color(ytitle_color))
4104            yt.backcolor(ytitle_backface_color)
4105
4106        shift = 0
4107        if ylab:  # this is the last created num label..
4108            lt0, lt1 = ylab.bounds()[0:2]
4109            shift = lt1 - lt0
4110
4111        T = LinearTransform()
4112        T.rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
4113        T.set_position(
4114            [-(xoffs + ytick_length / 2) * dx - shift,
4115            (yoffs + ytitle_position) * dy,
4116            zoffs * dz]
4117        )
4118        T.rotate_y(yaxis_rotation)
4119        T.translate([yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
4120        yt.apply_transform(T)
4121
4122        yt.use_bounds(y_use_bounds)
4123        if ytitle == " ":
4124            yt.use_bounds(False)
4125        yt.name = "ytitle"
4126        titles.append(yt)
4127        if ytitle_box:
4128            titles.append(yt.box(scale=1.1).use_bounds(y_use_bounds))
4129
4130    if ztitle:
4131        xRot, yRot, zRot = 0, 0, 0
4132        if utils.is_sequence(ztitle_rotation):  # unpck 3 rotations
4133            xRot, yRot, zRot = ztitle_rotation
4134        else:
4135            xRot = ztitle_rotation
4136            if len(ztitle) > 3:
4137                xRot += 90
4138                ztitle_position *= 0.975
4139        if xRot < 0:
4140            xRot += 360  # deal with negative angles
4141
4142        if ztitle_justify is not None:
4143            jus = ztitle_justify
4144        else:
4145            jus = "center-right"
4146            if xRot:
4147                if xRot >  24: jus = "bottom-right"
4148                if xRot > 112: jus = "left-bottom"
4149                if xRot > 157: jus = "center-left"
4150                if xRot > 202: jus = "top-left"
4151                if xRot > 292: jus = "top-right"
4152                if xRot > 337: jus = "right-center"
4153
4154        zt = shapes.Text3D(
4155            ztitle,
4156            s=ztitle_size * text_scale * gscale,
4157            font=title_font,
4158            c=ztitle_color,
4159            justify=jus,
4160            depth=title_depth,
4161            italic=ztitle_italic,
4162        )
4163        if ztitle_backface_color is None:
4164            ztitle_backface_color = 1 - np.array(get_color(ztitle_color))
4165            zt.backcolor(ztitle_backface_color)
4166
4167        angle = np.arctan2(dy, dx) * 57.3
4168        shift = 0
4169        if zlab:  # this is the last created one..
4170            lt0, lt1 = zlab.bounds()[0:2]
4171            shift = lt1 - lt0
4172
4173        T = LinearTransform()
4174        T.rotate_x(90 + zRot).rotate_y(-xRot).rotate_z(angle + yRot)
4175        T.set_position([
4176            -(ztitle_offset + ztick_length / 5) * dx - shift,
4177            -(ztitle_offset + ztick_length / 5) * dy - shift,
4178            ztitle_position * dz]
4179        )
4180        T.rotate_z(zaxis_rotation)
4181        T.translate([zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
4182        zt.apply_transform(T)
4183
4184        zt.use_bounds(z_use_bounds)
4185        if ztitle == " ":
4186            zt.use_bounds(False)
4187        zt.name = "ztitle"
4188        titles.append(zt)
4189
4190    ################################################### header title
4191    if htitle:
4192        if htitle_font is None:
4193            htitle_font = title_font
4194        if htitle_color is None:
4195            htitle_color = xtitle_color
4196        htit = shapes.Text3D(
4197            htitle,
4198            s=htitle_size * gscale * text_scale,
4199            font=htitle_font,
4200            c=htitle_color,
4201            justify=htitle_justify,
4202            depth=title_depth,
4203            italic=htitle_italic,
4204        )
4205        if htitle_backface_color is None:
4206            htitle_backface_color = 1 - np.array(get_color(htitle_color))
4207            htit.backcolor(htitle_backface_color)
4208        htit.rotate_x(htitle_rotation)
4209        wpos = [htitle_offset[0]*dx, (1 + htitle_offset[1])*dy, htitle_offset[2]*dz]
4210        htit.shift(np.array(wpos) + [0, 0, xyshift*dz])
4211        htit.name = "htitle"
4212        titles.append(htit)
4213
4214    ######
4215    acts = titles + lines + labels + grids + framelines
4216    acts += highlights + majorticks + minorticks + cones
4217    orig = (min_bns[0], min_bns[2], min_bns[4])
4218    for a in acts:
4219        a.shift(orig)
4220        a.actor.PickableOff()
4221        a.properties.LightingOff()
4222    asse = Assembly(acts)
4223    asse.PickableOff()
4224    asse.name = "Axes"
4225    return asse

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

Returns an vedo.Assembly object.

Parameters

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

Examples:

class RendererFrame(vtkmodules.vtkRenderingCore.vtkActor2D):
2364class RendererFrame(vtki.vtkActor2D):
2365    """
2366    Add a line around the renderer subwindow.
2367    """
2368
2369    def __init__(self, c="k", alpha=None, lw=None, padding=None):
2370        """
2371        Add a line around the renderer subwindow.
2372
2373        Arguments:
2374            c : (color)
2375                color of the line.
2376            alpha : (float)
2377                opacity.
2378            lw : (int)
2379                line width in pixels.
2380            padding : (int)
2381                padding in pixel units.
2382        """
2383
2384        if lw is None:
2385            lw = settings.renderer_frame_width
2386        if lw == 0:
2387            return None
2388
2389        if alpha is None:
2390            alpha = settings.renderer_frame_alpha
2391
2392        if padding is None:
2393            padding = settings.renderer_frame_padding
2394
2395        c = get_color(c)
2396
2397        ppoints = vtki.vtkPoints()  # Generate the polyline
2398        xy = 1 - padding
2399        psqr = [[padding, padding], [padding, xy], [xy, xy], [xy, padding], [padding, padding]]
2400        for i, pt in enumerate(psqr):
2401            ppoints.InsertPoint(i, pt[0], pt[1], 0)
2402        lines = vtki.vtkCellArray()
2403        lines.InsertNextCell(len(psqr))
2404        for i in range(len(psqr)):
2405            lines.InsertCellPoint(i)
2406        pd = vtki.vtkPolyData()
2407        pd.SetPoints(ppoints)
2408        pd.SetLines(lines)
2409
2410        mapper = vtki.new("PolyDataMapper2D")
2411        mapper.SetInputData(pd)
2412        cs = vtki.new("Coordinate")
2413        cs.SetCoordinateSystemToNormalizedViewport()
2414        mapper.SetTransformCoordinate(cs)
2415
2416        super().__init__()
2417
2418        self.GetPositionCoordinate().SetValue(0, 0)
2419        self.GetPosition2Coordinate().SetValue(1, 1)
2420        self.SetMapper(mapper)
2421        self.GetProperty().SetColor(c)
2422        self.GetProperty().SetOpacity(alpha)
2423        self.GetProperty().SetLineWidth(lw)

Add a line around the renderer subwindow.

RendererFrame(c='k', alpha=None, lw=None, padding=None)
2369    def __init__(self, c="k", alpha=None, lw=None, padding=None):
2370        """
2371        Add a line around the renderer subwindow.
2372
2373        Arguments:
2374            c : (color)
2375                color of the line.
2376            alpha : (float)
2377                opacity.
2378            lw : (int)
2379                line width in pixels.
2380            padding : (int)
2381                padding in pixel units.
2382        """
2383
2384        if lw is None:
2385            lw = settings.renderer_frame_width
2386        if lw == 0:
2387            return None
2388
2389        if alpha is None:
2390            alpha = settings.renderer_frame_alpha
2391
2392        if padding is None:
2393            padding = settings.renderer_frame_padding
2394
2395        c = get_color(c)
2396
2397        ppoints = vtki.vtkPoints()  # Generate the polyline
2398        xy = 1 - padding
2399        psqr = [[padding, padding], [padding, xy], [xy, xy], [xy, padding], [padding, padding]]
2400        for i, pt in enumerate(psqr):
2401            ppoints.InsertPoint(i, pt[0], pt[1], 0)
2402        lines = vtki.vtkCellArray()
2403        lines.InsertNextCell(len(psqr))
2404        for i in range(len(psqr)):
2405            lines.InsertCellPoint(i)
2406        pd = vtki.vtkPolyData()
2407        pd.SetPoints(ppoints)
2408        pd.SetLines(lines)
2409
2410        mapper = vtki.new("PolyDataMapper2D")
2411        mapper.SetInputData(pd)
2412        cs = vtki.new("Coordinate")
2413        cs.SetCoordinateSystemToNormalizedViewport()
2414        mapper.SetTransformCoordinate(cs)
2415
2416        super().__init__()
2417
2418        self.GetPositionCoordinate().SetValue(0, 0)
2419        self.GetPosition2Coordinate().SetValue(1, 1)
2420        self.SetMapper(mapper)
2421        self.GetProperty().SetColor(c)
2422        self.GetProperty().SetOpacity(alpha)
2423        self.GetProperty().SetLineWidth(lw)

Add a line around the renderer subwindow.

Arguments:
  • c : (color) color of the line.
  • alpha : (float) opacity.
  • lw : (int) line width in pixels.
  • padding : (int) padding in pixel units.
class Ruler2D(vtkmodules.vtkRenderingAnnotation.vtkAxisActor2D):
2860class Ruler2D(vtki.vtkAxisActor2D):
2861    """
2862    Create a ruler with tick marks, labels and a title.
2863    """
2864    def __init__(
2865        self,
2866        lw=2,
2867        ticks=True,
2868        labels=False,
2869        c="k",
2870        alpha=1,
2871        title="",
2872        font="Calco",
2873        font_size=24,
2874        bc=None,
2875    ):
2876        """
2877        Create a ruler with tick marks, labels and a title.
2878
2879        Ruler2D is a 2D actor; that is, it is drawn on the overlay
2880        plane and is not occluded by 3D geometry.
2881        To use this class, specify two points defining the start and end
2882        with update_points() as 3D points.
2883
2884        This class decides decides how to create reasonable tick
2885        marks and labels.
2886
2887        Labels are drawn on the "right" side of the axis.
2888        The "right" side is the side of the axis on the right.
2889        The way the labels and title line up with the axis and tick marks
2890        depends on whether the line is considered horizontal or vertical.
2891
2892        Arguments:
2893            lw : (int)
2894                width of the line in pixel units
2895            ticks : (bool)
2896                control if drawing the tick marks
2897            labels : (bool)
2898                control if drawing the numeric labels
2899            c : (color)
2900                color of the object
2901            alpha : (float)
2902                opacity of the object
2903            title : (str)
2904                title of the ruler
2905            font : (str)
2906                font face name. Check [available fonts here](https://vedo.embl.es/fonts).
2907            font_size : (int)
2908                font size
2909            bc : (color)
2910                background color of the title
2911
2912        Example:
2913            ```python
2914            from vedo  import *
2915            plt = Plotter(axes=1, interactive=False)
2916            plt.show(Cube())
2917            rul = Ruler2D()
2918            rul.set_points([0,0,0], [0.5,0.5,0.5])
2919            plt.add(rul)
2920            plt.interactive().close()
2921            ```
2922            ![](https://vedo.embl.es/images/feats/dist_tool.png)
2923        """
2924        super().__init__()
2925
2926        plt = vedo.plotter_instance
2927        if not plt:
2928            vedo.logger.error("Ruler2D need to initialize Plotter first.")
2929            raise RuntimeError()
2930
2931        self.p0 = [0, 0, 0]
2932        self.p1 = [0, 0, 0]
2933        self.distance = 0
2934        self.title = title
2935
2936        prop = self.GetProperty()
2937        tprop = self.GetTitleTextProperty()
2938
2939        self.SetTitle(title)
2940        self.SetNumberOfLabels(9)
2941
2942        if not font:
2943            font = settings.default_font
2944        if font.lower() == "courier":
2945            tprop.SetFontFamilyToCourier()
2946        elif font.lower() == "times":
2947            tprop.SetFontFamilyToTimes()
2948        elif font.lower() == "arial":
2949            tprop.SetFontFamilyToArial()
2950        else:
2951            tprop.SetFontFamily(vtki.VTK_FONT_FILE)
2952            tprop.SetFontFile(utils.get_font_path(font))
2953        tprop.SetFontSize(font_size)
2954        tprop.BoldOff()
2955        tprop.ItalicOff()
2956        tprop.ShadowOff()
2957        tprop.SetColor(get_color(c))
2958        tprop.SetOpacity(alpha)
2959        if bc is not None:
2960            bc = get_color(bc)
2961            tprop.SetBackgroundColor(bc)
2962            tprop.SetBackgroundOpacity(alpha)
2963
2964        lprop = vtki.vtkTextProperty()
2965        lprop.ShallowCopy(tprop)
2966        self.SetLabelTextProperty(lprop)
2967
2968        self.SetLabelFormat("%0.3g")
2969        self.SetTickVisibility(ticks)
2970        self.SetLabelVisibility(labels)
2971        prop.SetLineWidth(lw)
2972        prop.SetColor(get_color(c))
2973
2974        self.renderer = plt.renderer
2975        self.cid = plt.interactor.AddObserver("RenderEvent", self._update_viz, 1.0)
2976
2977    def color(self, c) -> Self:
2978        """Assign a new color."""
2979        c = get_color(c)
2980        self.GetTitleTextProperty().SetColor(c)
2981        self.GetLabelTextProperty().SetColor(c)
2982        self.GetProperty().SetColor(c)
2983        return self
2984
2985    def off(self) -> None:
2986        """Switch off the ruler completely."""
2987        self.renderer.RemoveObserver(self.cid)
2988        self.renderer.RemoveActor(self)
2989
2990    def set_points(self, p0, p1) -> Self:
2991        """Set new values for the ruler start and end points."""
2992        self.p0 = np.asarray(p0)
2993        self.p1 = np.asarray(p1)
2994        self._update_viz(0, 0)
2995        return self
2996
2997    def _update_viz(self, evt, name) -> None:
2998        ren = self.renderer
2999        view_size = np.array(ren.GetSize())
3000
3001        ren.SetWorldPoint(*self.p0, 1)
3002        ren.WorldToDisplay()
3003        disp_point1 = ren.GetDisplayPoint()[:2]
3004        disp_point1 = np.array(disp_point1) / view_size
3005
3006        ren.SetWorldPoint(*self.p1, 1)
3007        ren.WorldToDisplay()
3008        disp_point2 = ren.GetDisplayPoint()[:2]
3009        disp_point2 = np.array(disp_point2) / view_size
3010
3011        self.SetPoint1(*disp_point1)
3012        self.SetPoint2(*disp_point2)
3013        self.distance = np.linalg.norm(self.p1 - self.p0)
3014        self.SetRange(0.0, float(self.distance))
3015        if not self.title:
3016            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)
2864    def __init__(
2865        self,
2866        lw=2,
2867        ticks=True,
2868        labels=False,
2869        c="k",
2870        alpha=1,
2871        title="",
2872        font="Calco",
2873        font_size=24,
2874        bc=None,
2875    ):
2876        """
2877        Create a ruler with tick marks, labels and a title.
2878
2879        Ruler2D is a 2D actor; that is, it is drawn on the overlay
2880        plane and is not occluded by 3D geometry.
2881        To use this class, specify two points defining the start and end
2882        with update_points() as 3D points.
2883
2884        This class decides decides how to create reasonable tick
2885        marks and labels.
2886
2887        Labels are drawn on the "right" side of the axis.
2888        The "right" side is the side of the axis on the right.
2889        The way the labels and title line up with the axis and tick marks
2890        depends on whether the line is considered horizontal or vertical.
2891
2892        Arguments:
2893            lw : (int)
2894                width of the line in pixel units
2895            ticks : (bool)
2896                control if drawing the tick marks
2897            labels : (bool)
2898                control if drawing the numeric labels
2899            c : (color)
2900                color of the object
2901            alpha : (float)
2902                opacity of the object
2903            title : (str)
2904                title of the ruler
2905            font : (str)
2906                font face name. Check [available fonts here](https://vedo.embl.es/fonts).
2907            font_size : (int)
2908                font size
2909            bc : (color)
2910                background color of the title
2911
2912        Example:
2913            ```python
2914            from vedo  import *
2915            plt = Plotter(axes=1, interactive=False)
2916            plt.show(Cube())
2917            rul = Ruler2D()
2918            rul.set_points([0,0,0], [0.5,0.5,0.5])
2919            plt.add(rul)
2920            plt.interactive().close()
2921            ```
2922            ![](https://vedo.embl.es/images/feats/dist_tool.png)
2923        """
2924        super().__init__()
2925
2926        plt = vedo.plotter_instance
2927        if not plt:
2928            vedo.logger.error("Ruler2D need to initialize Plotter first.")
2929            raise RuntimeError()
2930
2931        self.p0 = [0, 0, 0]
2932        self.p1 = [0, 0, 0]
2933        self.distance = 0
2934        self.title = title
2935
2936        prop = self.GetProperty()
2937        tprop = self.GetTitleTextProperty()
2938
2939        self.SetTitle(title)
2940        self.SetNumberOfLabels(9)
2941
2942        if not font:
2943            font = settings.default_font
2944        if font.lower() == "courier":
2945            tprop.SetFontFamilyToCourier()
2946        elif font.lower() == "times":
2947            tprop.SetFontFamilyToTimes()
2948        elif font.lower() == "arial":
2949            tprop.SetFontFamilyToArial()
2950        else:
2951            tprop.SetFontFamily(vtki.VTK_FONT_FILE)
2952            tprop.SetFontFile(utils.get_font_path(font))
2953        tprop.SetFontSize(font_size)
2954        tprop.BoldOff()
2955        tprop.ItalicOff()
2956        tprop.ShadowOff()
2957        tprop.SetColor(get_color(c))
2958        tprop.SetOpacity(alpha)
2959        if bc is not None:
2960            bc = get_color(bc)
2961            tprop.SetBackgroundColor(bc)
2962            tprop.SetBackgroundOpacity(alpha)
2963
2964        lprop = vtki.vtkTextProperty()
2965        lprop.ShallowCopy(tprop)
2966        self.SetLabelTextProperty(lprop)
2967
2968        self.SetLabelFormat("%0.3g")
2969        self.SetTickVisibility(ticks)
2970        self.SetLabelVisibility(labels)
2971        prop.SetLineWidth(lw)
2972        prop.SetColor(get_color(c))
2973
2974        self.renderer = plt.renderer
2975        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:
2977    def color(self, c) -> Self:
2978        """Assign a new color."""
2979        c = get_color(c)
2980        self.GetTitleTextProperty().SetColor(c)
2981        self.GetLabelTextProperty().SetColor(c)
2982        self.GetProperty().SetColor(c)
2983        return self

Assign a new color.

def off(self) -> None:
2985    def off(self) -> None:
2986        """Switch off the ruler completely."""
2987        self.renderer.RemoveObserver(self.cid)
2988        self.renderer.RemoveActor(self)

Switch off the ruler completely.

def set_points(self, p0, p1) -> Self:
2990    def set_points(self, p0, p1) -> Self:
2991        """Set new values for the ruler start and end points."""
2992        self.p0 = np.asarray(p0)
2993        self.p1 = np.asarray(p1)
2994        self._update_viz(0, 0)
2995        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:
2594def Ruler3D(
2595    p1,
2596    p2,
2597    units_scale=1,
2598    label="",
2599    s=None,
2600    font=None,
2601    italic=0,
2602    prefix="",
2603    units="",  # eg.'μm'
2604    c=(0.2, 0.1, 0.1),
2605    alpha=1,
2606    lw=1,
2607    precision=3,
2608    label_rotation=0,
2609    axis_rotation=0,
2610    tick_angle=90,
2611) -> Mesh:
2612    """
2613    Build a 3D ruler to indicate the distance of two points p1 and p2.
2614
2615    Arguments:
2616        label : (str)
2617            alternative fixed label to be shown
2618        units_scale : (float)
2619            factor to scale units (e.g. μm to mm)
2620        s : (float)
2621            size of the label
2622        font : (str)
2623            font face.  Check [available fonts here](https://vedo.embl.es/fonts).
2624        italic : (float)
2625            italicness of the font in the range [0,1]
2626        units : (str)
2627            string to be appended to the numeric value
2628        lw : (int)
2629            line width in pixel units
2630        precision : (int)
2631            nr of significant digits to be shown
2632        label_rotation : (float)
2633            initial rotation of the label around the z-axis
2634        axis_rotation : (float)
2635            initial rotation of the line around the main axis
2636        tick_angle : (float)
2637            initial rotation of the line around the main axis
2638
2639    Examples:
2640        - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2641
2642        ![](https://vedo.embl.es/images/pyplot/goniometer.png)
2643    """
2644
2645    if units_scale != 1.0 and units == "":
2646        raise ValueError(
2647            "When setting 'units_scale' to a value other than 1, "
2648            + "a 'units' arguments must be specified."
2649        )
2650
2651    try:
2652        p1 = p1.pos()
2653    except AttributeError:
2654        pass
2655
2656    try:
2657        p2 = p2.pos()
2658    except AttributeError:
2659        pass
2660
2661    if len(p1) == 2:
2662        p1 = [p1[0], p1[1], 0.0]
2663    if len(p2) == 2:
2664        p2 = [p2[0], p2[1], 0.0]
2665    
2666
2667    p1, p2 = np.asarray(p1), np.asarray(p2)
2668    q1, q2 = [0, 0, 0], [utils.mag(p2 - p1), 0, 0]
2669    q1, q2 = np.array(q1), np.array(q2)
2670    v = q2 - q1
2671    d = utils.mag(v) * units_scale
2672
2673    pos = np.array(p1)
2674    p1 = p1 - pos
2675    p2 = p2 - pos
2676
2677    if s is None:
2678        s = d * 0.02 * (1 / units_scale)
2679
2680    if not label:
2681        label = str(d)
2682        if precision:
2683            label = utils.precision(d, precision)
2684    if prefix:
2685        label = prefix + "~" + label
2686    if units:
2687        label += "~" + units
2688
2689    lb = shapes.Text3D(label, s=s, font=font, italic=italic, justify="center")
2690    if label_rotation:
2691        lb.rotate_z(label_rotation)
2692    lb.pos((q1 + q2) / 2)
2693
2694    x0, x1 = lb.xbounds()
2695    gap = [(x1 - x0) / 2, 0, 0]
2696    pc1 = (v / 2 - gap) * 0.9 + q1
2697    pc2 = q2 - (v / 2 - gap) * 0.9
2698
2699    lc1 = shapes.Line(q1 - v / 50, pc1).lw(lw)
2700    lc2 = shapes.Line(q2 + v / 50, pc2).lw(lw)
2701
2702    zs = np.array([0, d / 50 * (1 / units_scale), 0])
2703    ml1 = shapes.Line(-zs, zs).lw(lw)
2704    ml2 = shapes.Line(-zs, zs).lw(lw)
2705    ml1.rotate_z(tick_angle - 90).pos(q1)
2706    ml2.rotate_z(tick_angle - 90).pos(q2)
2707
2708    c1 = shapes.Circle(q1, r=d / 180 * (1 / units_scale), res=24)
2709    c2 = shapes.Circle(q2, r=d / 180 * (1 / units_scale), res=24)
2710
2711    macts = merge(lb, lc1, lc2, c1, c2, ml1, ml2)
2712    macts.c(c).alpha(alpha)
2713    macts.properties.SetLineWidth(lw)
2714    macts.properties.LightingOff()
2715    macts.actor.UseBoundsOff()
2716    macts.rotate_x(axis_rotation)
2717    macts.reorient(q2 - q1, p2 - p1)
2718    macts.pos(pos)
2719    macts.bc("tomato").pickable(False)
2720    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]:
2723def RulerAxes(
2724    inputobj,
2725    xtitle="",
2726    ytitle="",
2727    ztitle="",
2728    xlabel="",
2729    ylabel="",
2730    zlabel="",
2731    xpadding=0.05,
2732    ypadding=0.04,
2733    zpadding=0,
2734    font="Normografo",
2735    s=None,
2736    italic=0,
2737    units="",
2738    c=(0.2, 0, 0),
2739    alpha=1,
2740    lw=1,
2741    precision=3,
2742    label_rotation=0,
2743    xaxis_rotation=0,
2744    yaxis_rotation=0,
2745    zaxis_rotation=0,
2746    xycross=True,
2747) -> Union[Mesh, None]:
2748    """
2749    A 3D ruler axes to indicate the sizes of the input scene or object.
2750
2751    Arguments:
2752        xtitle : (str)
2753            name of the axis or title
2754        xlabel : (str)
2755            alternative fixed label to be shown instead of the distance
2756        s : (float)
2757            size of the label
2758        font : (str)
2759            font face. Check [available fonts here](https://vedo.embl.es/fonts).
2760        italic : (float)
2761            italicness of the font in the range [0,1]
2762        units : (str)
2763            string to be appended to the numeric value
2764        lw : (int)
2765            line width in pixel units
2766        precision : (int)
2767            nr of significant digits to be shown
2768        label_rotation : (float)
2769            initial rotation of the label around the z-axis
2770        [x,y,z]axis_rotation : (float)
2771            initial rotation of the line around the main axis in degrees
2772        xycross : (bool)
2773            show two back crossing lines in the xy plane
2774
2775    Examples:
2776        - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2777    """
2778    if utils.is_sequence(inputobj):
2779        x0, x1, y0, y1, z0, z1 = inputobj
2780    else:
2781        x0, x1, y0, y1, z0, z1 = inputobj.bounds()
2782    dx, dy, dz = (y1 - y0) * xpadding, (x1 - x0) * ypadding, (y1 - y0) * zpadding
2783    d = np.sqrt((y1 - y0) ** 2 + (x1 - x0) ** 2 + (z1 - z0) ** 2)
2784
2785    if not d:
2786        return None
2787
2788    if s is None:
2789        s = d / 75
2790
2791    acts, rx, ry = [], None, None
2792    if xtitle is not None and (x1 - x0) / d > 0.1:
2793        rx = Ruler3D(
2794            [x0, y0 - dx, z0],
2795            [x1, y0 - dx, z0],
2796            s=s,
2797            font=font,
2798            precision=precision,
2799            label_rotation=label_rotation,
2800            axis_rotation=xaxis_rotation,
2801            lw=lw,
2802            italic=italic,
2803            prefix=xtitle,
2804            label=xlabel,
2805            units=units,
2806        )
2807        acts.append(rx)
2808
2809    if ytitle is not None and (y1 - y0) / d > 0.1:
2810        ry = Ruler3D(
2811            [x1 + dy, y0, z0],
2812            [x1 + dy, y1, z0],
2813            s=s,
2814            font=font,
2815            precision=precision,
2816            label_rotation=label_rotation,
2817            axis_rotation=yaxis_rotation,
2818            lw=lw,
2819            italic=italic,
2820            prefix=ytitle,
2821            label=ylabel,
2822            units=units,
2823        )
2824        acts.append(ry)
2825
2826    if ztitle is not None and (z1 - z0) / d > 0.1:
2827        rz = Ruler3D(
2828            [x0 - dy, y0 + dz, z0],
2829            [x0 - dy, y0 + dz, z1],
2830            s=s,
2831            font=font,
2832            precision=precision,
2833            label_rotation=label_rotation,
2834            axis_rotation=zaxis_rotation + 90,
2835            lw=lw,
2836            italic=italic,
2837            prefix=ztitle,
2838            label=zlabel,
2839            units=units,
2840        )
2841        acts.append(rz)
2842
2843    if xycross and rx and ry:
2844        lx = shapes.Line([x0, y0, z0], [x0, y1 + dx, z0])
2845        ly = shapes.Line([x0 - dy, y1, z0], [x1, y1, z0])
2846        d = min((x1 - x0), (y1 - y0)) / 200
2847        cxy = shapes.Circle([x0, y1, z0], r=d, res=15)
2848        acts.extend([lx, ly, cxy])
2849
2850    macts = merge(acts)
2851    if not macts:
2852        return None
2853    macts.c(c).alpha(alpha).bc("t")
2854    macts.actor.UseBoundsOff()
2855    macts.actor.PickableOff()
2856    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):
3020class DistanceTool(Group):
3021    """
3022    Create a tool to measure the distance between two clicked points.
3023    """
3024
3025    def __init__(self, plotter=None, c="k", lw=2):
3026        """
3027        Create a tool to measure the distance between two clicked points.
3028
3029        Example:
3030            ```python
3031            from vedo import *
3032            mesh = ParametricShape("RandomHills").c("red5")
3033            plt = Plotter(axes=1)
3034            dtool = DistanceTool()
3035            dtool.on()
3036            plt.show(mesh, dtool)
3037            dtool.off()
3038            ```
3039            ![](https://vedo.embl.es/images/feats/dist_tool.png)
3040        """
3041        super().__init__()
3042
3043        self.p0 = [0, 0, 0]
3044        self.p1 = [0, 0, 0]
3045        self.distance = 0
3046        if plotter is None:
3047            plotter = vedo.plotter_instance
3048        self.plotter = plotter
3049        self.callback = None
3050        self.cid = None
3051        self.color = c
3052        self.linewidth = lw
3053        self.toggle = True
3054        self.ruler = None
3055        self.title = ""
3056
3057    def on(self) -> Self:
3058        """Switch tool on."""
3059        self.cid = self.plotter.add_callback("click", self._onclick)
3060        self.VisibilityOn()
3061        self.plotter.render()
3062        return self
3063
3064    def off(self) -> None:
3065        """Switch tool off."""
3066        self.plotter.remove_callback(self.cid)
3067        self.VisibilityOff()
3068        self.ruler.off()
3069        self.plotter.render()
3070
3071    def _onclick(self, event):
3072        if not event.actor:
3073            return
3074
3075        self.clear()
3076
3077        acts = []
3078        if self.toggle:
3079            self.p0 = event.picked3d
3080            acts.append(Point(self.p0, c=self.color))
3081        else:
3082            self.p1 = event.picked3d
3083            self.distance = np.linalg.norm(self.p1 - self.p0)
3084            acts.append(Point(self.p0, c=self.color))
3085            acts.append(Point(self.p1, c=self.color))
3086            self.ruler = Ruler2D(c=self.color)
3087            self.ruler.set_points(self.p0, self.p1)
3088            acts.append(self.ruler)
3089
3090            if self.callback is not None:
3091                self.callback(event)
3092
3093        self += acts
3094        self.toggle = not self.toggle

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

DistanceTool(plotter=None, c='k', lw=2)
3025    def __init__(self, plotter=None, c="k", lw=2):
3026        """
3027        Create a tool to measure the distance between two clicked points.
3028
3029        Example:
3030            ```python
3031            from vedo import *
3032            mesh = ParametricShape("RandomHills").c("red5")
3033            plt = Plotter(axes=1)
3034            dtool = DistanceTool()
3035            dtool.on()
3036            plt.show(mesh, dtool)
3037            dtool.off()
3038            ```
3039            ![](https://vedo.embl.es/images/feats/dist_tool.png)
3040        """
3041        super().__init__()
3042
3043        self.p0 = [0, 0, 0]
3044        self.p1 = [0, 0, 0]
3045        self.distance = 0
3046        if plotter is None:
3047            plotter = vedo.plotter_instance
3048        self.plotter = plotter
3049        self.callback = None
3050        self.cid = None
3051        self.color = c
3052        self.linewidth = lw
3053        self.toggle = True
3054        self.ruler = None
3055        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:
3057    def on(self) -> Self:
3058        """Switch tool on."""
3059        self.cid = self.plotter.add_callback("click", self._onclick)
3060        self.VisibilityOn()
3061        self.plotter.render()
3062        return self

Switch tool on.

def off(self) -> None:
3064    def off(self) -> None:
3065        """Switch tool off."""
3066        self.plotter.remove_callback(self.cid)
3067        self.VisibilityOff()
3068        self.ruler.off()
3069        self.plotter.render()

Switch tool off.

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

Return the current interactor.

def add(self, pt) -> SplineTool:
736    def add(self, pt) -> "SplineTool":
737        """
738        Add one point at a specified position in space if 3D,
739        or 2D screen-display position if 2D.
740        """
741        if len(pt) == 2:
742            self.representation.AddNodeAtDisplayPosition(int(pt[0]), int(pt[1]))
743        else:
744            self.representation.AddNodeAtWorldPosition(pt)
745        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:
747    def add_observer(self, event, func, priority=1) -> int:
748        """Add an observer to the widget."""
749        event = utils.get_vtk_name_event(event)
750        cid = self.AddObserver(event, func, priority)
751        return cid

Add an observer to the widget.

def remove(self, i: int) -> SplineTool:
753    def remove(self, i: int) -> "SplineTool":
754        """Remove specific node by its index"""
755        self.representation.DeleteNthNode(i)
756        return self

Remove specific node by its index

def on(self) -> SplineTool:
758    def on(self) -> "SplineTool":
759        """Activate/Enable the tool"""
760        self.On()
761        self.Render()
762        return self

Activate/Enable the tool

def off(self) -> SplineTool:
764    def off(self) -> "SplineTool":
765        """Disactivate/Disable the tool"""
766        self.Off()
767        self.Render()
768        return self

Disactivate/Disable the tool

def render(self) -> SplineTool:
770    def render(self) -> "SplineTool":
771        """Render the spline"""
772        self.Render()
773        return self

Render the spline

def spline(self) -> vedo.shapes.Line:
779    def spline(self) -> vedo.Line:
780        """Return the vedo.Spline object."""
781        self.representation.SetClosedLoop(self.closed)
782        self.representation.BuildRepresentation()
783        pd = self.representation.GetContourRepresentationAsPolyData()
784        ln = vedo.Line(pd, lw=2, c="k")
785        return ln

Return the vedo.Spline object.

def nodes(self, onscreen=False) -> numpy.ndarray:
787    def nodes(self, onscreen=False) -> np.ndarray:
788        """Return the current position in space (or on 2D screen-display) of the spline nodes."""
789        n = self.representation.GetNumberOfNodes()
790        pts = []
791        for i in range(n):
792            p = [0.0, 0.0, 0.0]
793            if onscreen:
794                self.representation.GetNthNodeDisplayPosition(i, p)
795            else:
796                self.representation.GetNthNodeWorldPosition(i, p)
797            pts.append(p)
798        return np.array(pts)

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

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):
877def Goniometer(
878    p1,
879    p2,
880    p3,
881    font="",
882    arc_size=0.4,
883    s=1,
884    italic=0,
885    rotation=0,
886    prefix="",
887    lc="k2",
888    c="white",
889    alpha=1,
890    lw=2,
891    precision=3,
892):
893    """
894    Build a graphical goniometer to measure the angle formed by 3 points in space.
895
896    Arguments:
897        p1 : (list)
898            first point 3D coordinates.
899        p2 : (list)
900            the vertex point.
901        p3 : (list)
902            the last point defining the angle.
903        font : (str)
904            Font face. Check [available fonts here](https://vedo.embl.es/fonts).
905        arc_size : (float)
906            dimension of the arc wrt the smallest axis.
907        s : (float)
908            size of the text.
909        italic : (float, bool)
910            italic text.
911        rotation : (float)
912            rotation of text in degrees.
913        prefix : (str)
914            append this string to the numeric value of the angle.
915        lc : (list)
916            color of the goniometer lines.
917        c : (str)
918            color of the goniometer angle filling. Set alpha=0 to remove it.
919        alpha : (float)
920            transparency level.
921        lw : (float)
922            line width.
923        precision : (int)
924            number of significant digits.
925
926    Examples:
927        - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
928
929            ![](https://vedo.embl.es/images/pyplot/goniometer.png)
930    """
931    if isinstance(p1, Points): p1 = p1.pos()
932    if isinstance(p2, Points): p2 = p2.pos()
933    if isinstance(p3, Points): p3 = p3.pos()
934    if len(p1)==2: p1=[p1[0], p1[1], 0.0]
935    if len(p2)==2: p2=[p2[0], p2[1], 0.0]
936    if len(p3)==2: p3=[p3[0], p3[1], 0.0]
937    p1, p2, p3 = np.array(p1), np.array(p2), np.array(p3)
938
939    acts = []
940    ln = shapes.Line([p1, p2, p3], lw=lw, c=lc)
941    acts.append(ln)
942
943    va = utils.versor(p1 - p2)
944    vb = utils.versor(p3 - p2)
945    r = min(utils.mag(p3 - p2), utils.mag(p1 - p2)) * arc_size
946    ptsarc = []
947    res = 120
948    imed = int(res / 2)
949    for i in range(res + 1):
950        vi = utils.versor(vb * i / res + va * (res - i) / res)
951        if i == imed:
952            vc = np.array(vi)
953        ptsarc.append(p2 + vi * r)
954    arc = shapes.Line(ptsarc).lw(lw).c(lc)
955    acts.append(arc)
956
957    angle = np.arccos(np.dot(va, vb)) * 180 / np.pi
958
959    lb = shapes.Text3D(
960        prefix + utils.precision(angle, precision) + "º",
961        s=r/12 * s,
962        font=font,
963        italic=italic,
964        justify="center",
965    )
966    cr = np.cross(va, vb)
967    lb.reorient([0,0,1], cr * np.sign(cr[2]), rotation=rotation, xyplane=False)
968    lb.pos(p2 + vc * r / 1.75)
969    lb.c(c).bc("tomato").lighting("off")
970    acts.append(lb)
971
972    if alpha > 0:
973        pts = [p2] + arc.vertices.tolist() + [p2]
974        msh = Mesh([pts, [list(range(arc.npoints + 2))]], c=lc, alpha=alpha)
975        msh.lighting("off")
976        msh.triangulate()
977        msh.shift(0, 0, -r / 10000)  # to resolve 2d conflicts..
978        acts.append(msh)
979
980    asse = Assembly(acts)
981    asse.name = "Goniometer"
982    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):
544class Button(vedo.shapes.Text2D):
545    """
546    Build a Button object.
547    """
548    def __init__(
549            self,
550            fnc=None,
551            states=("Button"),
552            c=("white"),
553            bc=("green4"),
554            pos=(0.7, 0.1),
555            size=24,
556            font="Courier",
557            bold=True,
558            italic=False,
559            alpha=1,
560            angle=0,
561        ):
562        """
563        Build a Button object to be shown in the rendering window.
564
565        Arguments:
566            fnc : (function)
567                external function to be called by the widget
568            states : (list)
569                the list of possible states, eg. ['On', 'Off']
570            c : (list)
571                the list of colors for each state eg. ['red3', 'green5']
572            bc : (list)
573                the list of background colors for each state
574            pos : (list, str)
575                2D position in pixels from left-bottom corner
576            size : (int)
577                size of button font
578            font : (str)
579                font type
580            bold : (bool)
581                set bold font face
582            italic : (bool)
583                italic font face
584            alpha : (float)
585                opacity level
586            angle : (float)
587                anticlockwise rotation in degrees
588
589        Examples:
590            - [buttons1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons1.py)
591            - [buttons2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons2.py)
592
593                ![](https://user-images.githubusercontent.com/32848391/50738870-c0fe2500-11d8-11e9-9b78-92754f5c5968.jpg)
594
595            - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py)
596
597                ![](https://vedo.embl.es/images/advanced/timer_callback1.jpg)
598        """
599        super().__init__()
600
601        self.status_idx = 0
602
603        self.spacer = " "
604
605        self.states = states
606
607        if not utils.is_sequence(c):
608            c = [c]
609        self.colors = c
610
611        if not utils.is_sequence(bc):
612            bc = [bc]
613        self.bcolors = bc
614
615        assert len(c) == len(bc), "in Button color number mismatch!"
616
617        self.function = fnc
618        self.function_id = None
619
620        self.status(0)
621
622        if font == "courier":
623            font = font.capitalize()
624        self.font(font).bold(bold).italic(italic)
625
626        self.alpha(alpha).angle(angle)
627        self.size(size/20)
628        self.pos(pos, "center")
629        self.PickableOn()
630
631
632    def status(self, s=None) -> "Button":
633        """
634        Set/Get the status of the button.
635        """
636        if s is None:
637            return self.states[self.status_idx]
638
639        if isinstance(s, str):
640            s = self.states.index(s)
641        self.status_idx = s
642        self.text(self.spacer + self.states[s] + self.spacer)
643        s = s % len(self.bcolors)
644        self.color(self.colors[s])
645        self.background(self.bcolors[s])
646        return self
647
648    def switch(self) -> "Button":
649        """
650        Change/cycle button status to the next defined status in states list.
651        """
652        self.status_idx = (self.status_idx + 1) % len(self.states)
653        self.status(self.status_idx)
654        return self

Build a Button object.

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

Set/Get the status of the button.

def switch(self) -> Button:
648    def switch(self) -> "Button":
649        """
650        Change/cycle button status to the next defined status in states list.
651        """
652        self.status_idx = (self.status_idx + 1) % len(self.states)
653        self.status(self.status_idx)
654        return self

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

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

Create a button widget.

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

Create a button widget.

States can be either text strings or images.

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

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

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

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

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

plt.add(cone)

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

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

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

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

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:
163    def text(self, value: str) -> Self:
164        self.SetInput(value)
165        return self
def on(self) -> Self:
167    def on(self) -> Self:
168        self.VisibilityOn()
169        return self
def off(self) -> Self:
171    def off(self) -> Self:
172        self.VisibilityOff()
173        return self
def toggle(self) -> Self:
175    def toggle(self) -> Self:
176        self.SetVisibility(not self.GetVisibility())
177        return self
def use_bounds(self, value=True) -> Self:
179    def use_bounds(self, value=True) -> Self:
180        self.SetUseBounds(value)
181        return self
def color(self, c) -> Self:
183    def color(self, c) -> Self:
184        c = get_color(c)
185        self.GetTextProperty().SetColor(c)
186        self.GetProperty().SetColor(c)
187        return self
def pos(self, p) -> Self:
189    def pos(self, p) -> Self:
190        p = np.asarray(p)
191        self.top = self.top - self.base + p
192        self.base = p
193        return self
class ProgressBarWidget(vtkmodules.vtkRenderingCore.vtkActor2D):
2426class ProgressBarWidget(vtki.vtkActor2D):
2427    """
2428    Add a progress bar in the rendering window.
2429    """
2430    def __init__(self, n=None, c="blue5", alpha=0.8, lw=10, autohide=True):
2431        """
2432        Add a progress bar window.
2433
2434        Arguments:
2435            n : (int)
2436                number of iterations.
2437                If None, you need to call `update(fraction)` manually.
2438            c : (color)
2439                color of the line.
2440            alpha : (float)
2441                opacity of the line.
2442            lw : (int)
2443                line width in pixels.
2444            autohide : (bool)
2445                if True, hide the progress bar when completed.
2446        """
2447        self.n = 0
2448        self.iterations = n
2449        self.autohide = autohide
2450
2451        ppoints = vtki.vtkPoints()  # Generate the line
2452        psqr = [[0, 0, 0], [1, 0, 0]]
2453        for i, pt in enumerate(psqr):
2454            ppoints.InsertPoint(i, *pt)
2455        lines = vtki.vtkCellArray()
2456        lines.InsertNextCell(len(psqr))
2457        for i in range(len(psqr)):
2458            lines.InsertCellPoint(i)
2459        pd = vtki.vtkPolyData()
2460        pd.SetPoints(ppoints)
2461        pd.SetLines(lines)
2462        self.dataset = pd
2463
2464        mapper = vtki.new("PolyDataMapper2D")
2465        mapper.SetInputData(pd)
2466        cs = vtki.vtkCoordinate()
2467        cs.SetCoordinateSystemToNormalizedViewport()
2468        mapper.SetTransformCoordinate(cs)
2469
2470        super().__init__()
2471
2472        self.SetMapper(mapper)
2473        self.GetProperty().SetOpacity(alpha)
2474        self.GetProperty().SetColor(get_color(c))
2475        self.GetProperty().SetLineWidth(lw*2)
2476
2477
2478    def lw(self, value: int) -> Self:
2479        """Set width."""
2480        self.GetProperty().SetLineWidth(value*2)
2481        return self
2482
2483    def c(self, color) -> Self:
2484        """Set color."""
2485        c = get_color(color)
2486        self.GetProperty().SetColor(c)
2487        return self
2488
2489    def alpha(self, value) -> Self:
2490        """Set opacity."""
2491        self.GetProperty().SetOpacity(value)
2492        return self
2493
2494    def update(self, fraction=None) -> Self:
2495        """Update progress bar to fraction of the window width."""
2496        if fraction is None:
2497            if self.iterations is None:
2498                vedo.printc("Error in ProgressBarWindow: must specify iterations", c='r')
2499                return self
2500            self.n += 1
2501            fraction = self.n / self.iterations
2502
2503        if fraction >= 1 and self.autohide:
2504            fraction = 0
2505
2506        psqr = [[0, 0, 0], [fraction, 0, 0]]
2507        vpts = utils.numpy2vtk(psqr, dtype=np.float32)
2508        self.dataset.GetPoints().SetData(vpts)
2509        return self
2510
2511    def reset(self):
2512        """Reset progress bar."""
2513        self.n = 0
2514        self.update(0)
2515        return self

Add a progress bar in the rendering window.

ProgressBarWidget(n=None, c='blue5', alpha=0.8, lw=10, autohide=True)
2430    def __init__(self, n=None, c="blue5", alpha=0.8, lw=10, autohide=True):
2431        """
2432        Add a progress bar window.
2433
2434        Arguments:
2435            n : (int)
2436                number of iterations.
2437                If None, you need to call `update(fraction)` manually.
2438            c : (color)
2439                color of the line.
2440            alpha : (float)
2441                opacity of the line.
2442            lw : (int)
2443                line width in pixels.
2444            autohide : (bool)
2445                if True, hide the progress bar when completed.
2446        """
2447        self.n = 0
2448        self.iterations = n
2449        self.autohide = autohide
2450
2451        ppoints = vtki.vtkPoints()  # Generate the line
2452        psqr = [[0, 0, 0], [1, 0, 0]]
2453        for i, pt in enumerate(psqr):
2454            ppoints.InsertPoint(i, *pt)
2455        lines = vtki.vtkCellArray()
2456        lines.InsertNextCell(len(psqr))
2457        for i in range(len(psqr)):
2458            lines.InsertCellPoint(i)
2459        pd = vtki.vtkPolyData()
2460        pd.SetPoints(ppoints)
2461        pd.SetLines(lines)
2462        self.dataset = pd
2463
2464        mapper = vtki.new("PolyDataMapper2D")
2465        mapper.SetInputData(pd)
2466        cs = vtki.vtkCoordinate()
2467        cs.SetCoordinateSystemToNormalizedViewport()
2468        mapper.SetTransformCoordinate(cs)
2469
2470        super().__init__()
2471
2472        self.SetMapper(mapper)
2473        self.GetProperty().SetOpacity(alpha)
2474        self.GetProperty().SetColor(get_color(c))
2475        self.GetProperty().SetLineWidth(lw*2)

Add a progress bar window.

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

Set width.

def c(self, color) -> Self:
2483    def c(self, color) -> Self:
2484        """Set color."""
2485        c = get_color(color)
2486        self.GetProperty().SetColor(c)
2487        return self

Set color.

def alpha(self, value) -> Self:
2489    def alpha(self, value) -> Self:
2490        """Set opacity."""
2491        self.GetProperty().SetOpacity(value)
2492        return self

Set opacity.

def update(self, fraction=None) -> Self:
2494    def update(self, fraction=None) -> Self:
2495        """Update progress bar to fraction of the window width."""
2496        if fraction is None:
2497            if self.iterations is None:
2498                vedo.printc("Error in ProgressBarWindow: must specify iterations", c='r')
2499                return self
2500            self.n += 1
2501            fraction = self.n / self.iterations
2502
2503        if fraction >= 1 and self.autohide:
2504            fraction = 0
2505
2506        psqr = [[0, 0, 0], [fraction, 0, 0]]
2507        vpts = utils.numpy2vtk(psqr, dtype=np.float32)
2508        self.dataset.GetPoints().SetData(vpts)
2509        return self

Update progress bar to fraction of the window width.

def reset(self):
2511    def reset(self):
2512        """Reset progress bar."""
2513        self.n = 0
2514        self.update(0)
2515        return self

Reset progress bar.

class BoxCutter(vtkmodules.vtkInteractionWidgets.vtkBoxWidget, BaseCutter):
2107class BoxCutter(vtki.vtkBoxWidget, BaseCutter):
2108    """
2109    Create a box widget to cut away parts of a Mesh.
2110    """
2111    def __init__(
2112            self,
2113            mesh,
2114            invert=False,
2115            can_rotate=True,
2116            can_translate=True,
2117            can_scale=True,
2118            initial_bounds=(),
2119            padding=0.025,
2120            delayed=False,
2121            c=(0.25, 0.25, 0.25),
2122            alpha=0.05,
2123    ):
2124        """
2125        Create a box widget to cut away parts of a Mesh.
2126
2127        Arguments:
2128            mesh : (Mesh)
2129                the input mesh
2130            invert : (bool)
2131                invert the clipping plane
2132            can_rotate : (bool)
2133                enable rotation of the widget
2134            can_translate : (bool)
2135                enable translation of the widget
2136            can_scale : (bool)
2137                enable scaling of the widget
2138            initial_bounds : (list)
2139                initial bounds of the box widget
2140            padding : (float)
2141                padding space around the input mesh
2142            delayed : (bool)
2143                if True the callback is delayed until
2144                when the mouse button is released (useful for large meshes)
2145            c : (color)
2146                color of the box cutter widget
2147            alpha : (float)
2148                transparency of the cut-off part of the input mesh
2149        """
2150        super().__init__()
2151
2152        self.mesh = mesh
2153        self.remnant = Mesh()
2154        self.remnant.name = mesh.name + "Remnant"
2155        self.remnant.pickable(False)
2156
2157        self._alpha = alpha
2158        self._keypress_id = None
2159        self._init_bounds = initial_bounds
2160        if len(self._init_bounds) == 0:
2161            self._init_bounds = mesh.bounds()
2162        else:
2163            self._init_bounds = initial_bounds
2164
2165        self._implicit_func = vtki.new("Planes")
2166        self._implicit_func.SetBounds(self._init_bounds)
2167
2168        poly = mesh.dataset
2169        self.clipper = vtki.new("ClipPolyData")
2170        self.clipper.GenerateClipScalarsOff()
2171        self.clipper.SetInputData(poly)
2172        self.clipper.SetClipFunction(self._implicit_func)
2173        self.clipper.SetInsideOut(not invert)
2174        self.clipper.GenerateClippedOutputOn()
2175        self.clipper.Update()
2176
2177        self.widget = vtki.vtkBoxWidget()
2178
2179        self.widget.SetRotationEnabled(can_rotate)
2180        self.widget.SetTranslationEnabled(can_translate)
2181        self.widget.SetScalingEnabled(can_scale)
2182
2183        self.widget.OutlineCursorWiresOn()
2184        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
2185        self.widget.GetSelectedHandleProperty().SetColor(get_color("red5"))
2186
2187        self.widget.GetOutlineProperty().SetColor(c)
2188        self.widget.GetOutlineProperty().SetOpacity(1)
2189        self.widget.GetOutlineProperty().SetLineWidth(1)
2190        self.widget.GetOutlineProperty().LightingOff()
2191
2192        self.widget.GetSelectedFaceProperty().LightingOff()
2193        self.widget.GetSelectedFaceProperty().SetOpacity(0.1)
2194
2195        self.widget.SetPlaceFactor(1.0 + padding)
2196        self.widget.SetInputData(poly)
2197        self.widget.PlaceWidget()
2198        if delayed:
2199            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2200        else:
2201            self.widget.AddObserver("InteractionEvent", self._select_polygons)
2202
2203    def _select_polygons(self, vobj, event):
2204        vobj.GetPlanes(self._implicit_func)
2205
2206    def _keypress(self, vobj, event):
2207        if vobj.GetKeySym() == "r":  # reset planes
2208            self._implicit_func.SetBounds(self._init_bounds)
2209            self.widget.GetPlanes(self._implicit_func)
2210            self.widget.PlaceWidget()
2211            self.widget.GetInteractor().Render()
2212        elif vobj.GetKeySym() == "u":
2213            self.invert()
2214            self.widget.GetInteractor().Render()
2215        elif vobj.GetKeySym() == "s": # Ctrl+s to save mesh
2216            if self.widget.GetInteractor():
2217                if self.widget.GetInteractor().GetControlKey():
2218                    self.mesh.write("vedo_clipped.vtk")
2219                    printc(":save: saved mesh to vedo_clipped.vtk")

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

BoxCutter( mesh, invert=False, can_rotate=True, can_translate=True, can_scale=True, initial_bounds=(), padding=0.025, delayed=False, c=(0.25, 0.25, 0.25), alpha=0.05)
2111    def __init__(
2112            self,
2113            mesh,
2114            invert=False,
2115            can_rotate=True,
2116            can_translate=True,
2117            can_scale=True,
2118            initial_bounds=(),
2119            padding=0.025,
2120            delayed=False,
2121            c=(0.25, 0.25, 0.25),
2122            alpha=0.05,
2123    ):
2124        """
2125        Create a box widget to cut away parts of a Mesh.
2126
2127        Arguments:
2128            mesh : (Mesh)
2129                the input mesh
2130            invert : (bool)
2131                invert the clipping plane
2132            can_rotate : (bool)
2133                enable rotation of the widget
2134            can_translate : (bool)
2135                enable translation of the widget
2136            can_scale : (bool)
2137                enable scaling of the widget
2138            initial_bounds : (list)
2139                initial bounds of the box widget
2140            padding : (float)
2141                padding space around the input mesh
2142            delayed : (bool)
2143                if True the callback is delayed until
2144                when the mouse button is released (useful for large meshes)
2145            c : (color)
2146                color of the box cutter widget
2147            alpha : (float)
2148                transparency of the cut-off part of the input mesh
2149        """
2150        super().__init__()
2151
2152        self.mesh = mesh
2153        self.remnant = Mesh()
2154        self.remnant.name = mesh.name + "Remnant"
2155        self.remnant.pickable(False)
2156
2157        self._alpha = alpha
2158        self._keypress_id = None
2159        self._init_bounds = initial_bounds
2160        if len(self._init_bounds) == 0:
2161            self._init_bounds = mesh.bounds()
2162        else:
2163            self._init_bounds = initial_bounds
2164
2165        self._implicit_func = vtki.new("Planes")
2166        self._implicit_func.SetBounds(self._init_bounds)
2167
2168        poly = mesh.dataset
2169        self.clipper = vtki.new("ClipPolyData")
2170        self.clipper.GenerateClipScalarsOff()
2171        self.clipper.SetInputData(poly)
2172        self.clipper.SetClipFunction(self._implicit_func)
2173        self.clipper.SetInsideOut(not invert)
2174        self.clipper.GenerateClippedOutputOn()
2175        self.clipper.Update()
2176
2177        self.widget = vtki.vtkBoxWidget()
2178
2179        self.widget.SetRotationEnabled(can_rotate)
2180        self.widget.SetTranslationEnabled(can_translate)
2181        self.widget.SetScalingEnabled(can_scale)
2182
2183        self.widget.OutlineCursorWiresOn()
2184        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
2185        self.widget.GetSelectedHandleProperty().SetColor(get_color("red5"))
2186
2187        self.widget.GetOutlineProperty().SetColor(c)
2188        self.widget.GetOutlineProperty().SetOpacity(1)
2189        self.widget.GetOutlineProperty().SetLineWidth(1)
2190        self.widget.GetOutlineProperty().LightingOff()
2191
2192        self.widget.GetSelectedFaceProperty().LightingOff()
2193        self.widget.GetSelectedFaceProperty().SetOpacity(0.1)
2194
2195        self.widget.SetPlaceFactor(1.0 + padding)
2196        self.widget.SetInputData(poly)
2197        self.widget.PlaceWidget()
2198        if delayed:
2199            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2200        else:
2201            self.widget.AddObserver("InteractionEvent", self._select_polygons)

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

Arguments:
  • mesh : (Mesh) the input mesh
  • invert : (bool) invert the clipping plane
  • can_rotate : (bool) enable rotation of the widget
  • can_translate : (bool) enable translation of the widget
  • can_scale : (bool) enable scaling of the widget
  • initial_bounds : (list) initial bounds of the box widget
  • padding : (float) padding space around the input mesh
  • delayed : (bool) if True the callback is delayed until when the mouse button is released (useful for large meshes)
  • c : (color) color of the box cutter widget
  • alpha : (float) transparency of the cut-off part of the input mesh
class PlaneCutter(vtkmodules.vtkInteractionWidgets.vtkPlaneWidget, BaseCutter):
1946class PlaneCutter(vtki.vtkPlaneWidget, BaseCutter):
1947    """
1948    Create a box widget to cut away parts of a Mesh.
1949    """
1950    def __init__(
1951            self,
1952            mesh,
1953            invert=False,
1954            can_translate=True,
1955            can_scale=True,
1956            origin=(),
1957            normal=(),
1958            padding=0.05,
1959            delayed=False,
1960            c=(0.25, 0.25, 0.25),
1961            alpha=0.05,
1962    ):
1963        """
1964        Create a box widget to cut away parts of a Mesh.
1965
1966        Arguments:
1967            mesh : (Mesh)
1968                the input mesh
1969            invert : (bool)
1970                invert the clipping plane
1971            can_translate : (bool)
1972                enable translation of the widget
1973            can_scale : (bool)
1974                enable scaling of the widget
1975            origin : (list)
1976                origin of the plane
1977            normal : (list)
1978                normal to the plane
1979            padding : (float)
1980                padding around the input mesh
1981            delayed : (bool)
1982                if True the callback is delayed until
1983                when the mouse button is released (useful for large meshes)
1984            c : (color)
1985                color of the box cutter widget
1986            alpha : (float)
1987                transparency of the cut-off part of the input mesh
1988        
1989        Examples:
1990            - [slice_plane3.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slice_plane3.py)
1991        """
1992        super().__init__()
1993
1994        self.mesh = mesh
1995        self.remnant = Mesh()
1996        self.remnant.name = mesh.name + "Remnant"
1997        self.remnant.pickable(False)
1998
1999        self._alpha = alpha
2000        self._keypress_id = None
2001
2002        self._implicit_func = vtki.new("Plane")
2003
2004        poly = mesh.dataset
2005        self.clipper = vtki.new("ClipPolyData")
2006        self.clipper.GenerateClipScalarsOff()
2007        self.clipper.SetInputData(poly)
2008        self.clipper.SetClipFunction(self._implicit_func)
2009        self.clipper.SetInsideOut(invert)
2010        self.clipper.GenerateClippedOutputOn()
2011        self.clipper.Update()
2012
2013        self.widget = vtki.new("ImplicitPlaneWidget")
2014
2015        # self.widget.KeyPressActivationOff()
2016        # self.widget.SetKeyPressActivationValue('i')
2017
2018        self.widget.SetOriginTranslation(can_translate)
2019        self.widget.SetOutlineTranslation(can_translate)
2020        self.widget.SetScaleEnabled(can_scale)
2021
2022        self.widget.GetOutlineProperty().SetColor(get_color(c))
2023        self.widget.GetOutlineProperty().SetOpacity(0.25)
2024        self.widget.GetOutlineProperty().SetLineWidth(1)
2025        self.widget.GetOutlineProperty().LightingOff()
2026
2027        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
2028
2029        self.widget.SetTubing(0)
2030        self.widget.SetDrawPlane(bool(alpha))
2031        self.widget.GetPlaneProperty().LightingOff()
2032        self.widget.GetPlaneProperty().SetOpacity(alpha)
2033        self.widget.GetSelectedPlaneProperty().SetColor(get_color("red5"))
2034        self.widget.GetSelectedPlaneProperty().LightingOff()
2035
2036        self.widget.SetPlaceFactor(1.0 + padding)
2037        self.widget.SetInputData(poly)
2038        self.widget.PlaceWidget()
2039        if delayed:
2040            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2041        else:
2042            self.widget.AddObserver("InteractionEvent", self._select_polygons)
2043
2044        if len(origin) == 3:
2045            self.widget.SetOrigin(origin)
2046        else:
2047            self.widget.SetOrigin(mesh.center_of_mass())
2048
2049        if len(normal) == 3:
2050            self.widget.SetNormal(normal)
2051        else:
2052            self.widget.SetNormal((1, 0, 0))
2053    
2054    @property
2055    def origin(self):
2056        """Get the origin of the plane."""
2057        return np.array(self.widget.GetOrigin())
2058    
2059    @origin.setter
2060    def origin(self, value):
2061        """Set the origin of the plane."""
2062        self.widget.SetOrigin(value)
2063
2064    @property
2065    def normal(self):
2066        """Get the normal of the plane."""
2067        return np.array(self.widget.GetNormal())
2068    
2069    @normal.setter
2070    def normal(self, value):
2071        """Set the normal of the plane."""
2072        self.widget.SetNormal(value)
2073
2074    def _select_polygons(self, vobj, event) -> None:
2075        vobj.GetPlane(self._implicit_func)
2076
2077    def _keypress(self, vobj, event):
2078        if vobj.GetKeySym() == "r": # reset planes
2079            self.widget.GetPlane(self._implicit_func)
2080            self.widget.PlaceWidget()
2081            self.widget.GetInteractor().Render()
2082        elif vobj.GetKeySym() == "u": # invert cut
2083            self.invert()
2084            self.widget.GetInteractor().Render()
2085        elif vobj.GetKeySym() == "x": # set normal along x
2086            self.widget.SetNormal((1, 0, 0))
2087            self.widget.GetPlane(self._implicit_func)
2088            self.widget.PlaceWidget()
2089            self.widget.GetInteractor().Render()
2090        elif vobj.GetKeySym() == "y": # set normal along y
2091            self.widget.SetNormal((0, 1, 0))
2092            self.widget.GetPlane(self._implicit_func)
2093            self.widget.PlaceWidget()
2094            self.widget.GetInteractor().Render()
2095        elif vobj.GetKeySym() == "z": # set normal along z
2096            self.widget.SetNormal((0, 0, 1))
2097            self.widget.GetPlane(self._implicit_func)
2098            self.widget.PlaceWidget()
2099            self.widget.GetInteractor().Render()
2100        elif vobj.GetKeySym() == "s": # Ctrl+s to save mesh
2101            if self.widget.GetInteractor():
2102                if self.widget.GetInteractor().GetControlKey():
2103                    self.mesh.write("vedo_clipped.vtk")
2104                    printc(":save: saved mesh to vedo_clipped.vtk")

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

PlaneCutter( mesh, invert=False, can_translate=True, can_scale=True, origin=(), normal=(), padding=0.05, delayed=False, c=(0.25, 0.25, 0.25), alpha=0.05)
1950    def __init__(
1951            self,
1952            mesh,
1953            invert=False,
1954            can_translate=True,
1955            can_scale=True,
1956            origin=(),
1957            normal=(),
1958            padding=0.05,
1959            delayed=False,
1960            c=(0.25, 0.25, 0.25),
1961            alpha=0.05,
1962    ):
1963        """
1964        Create a box widget to cut away parts of a Mesh.
1965
1966        Arguments:
1967            mesh : (Mesh)
1968                the input mesh
1969            invert : (bool)
1970                invert the clipping plane
1971            can_translate : (bool)
1972                enable translation of the widget
1973            can_scale : (bool)
1974                enable scaling of the widget
1975            origin : (list)
1976                origin of the plane
1977            normal : (list)
1978                normal to the plane
1979            padding : (float)
1980                padding around the input mesh
1981            delayed : (bool)
1982                if True the callback is delayed until
1983                when the mouse button is released (useful for large meshes)
1984            c : (color)
1985                color of the box cutter widget
1986            alpha : (float)
1987                transparency of the cut-off part of the input mesh
1988        
1989        Examples:
1990            - [slice_plane3.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slice_plane3.py)
1991        """
1992        super().__init__()
1993
1994        self.mesh = mesh
1995        self.remnant = Mesh()
1996        self.remnant.name = mesh.name + "Remnant"
1997        self.remnant.pickable(False)
1998
1999        self._alpha = alpha
2000        self._keypress_id = None
2001
2002        self._implicit_func = vtki.new("Plane")
2003
2004        poly = mesh.dataset
2005        self.clipper = vtki.new("ClipPolyData")
2006        self.clipper.GenerateClipScalarsOff()
2007        self.clipper.SetInputData(poly)
2008        self.clipper.SetClipFunction(self._implicit_func)
2009        self.clipper.SetInsideOut(invert)
2010        self.clipper.GenerateClippedOutputOn()
2011        self.clipper.Update()
2012
2013        self.widget = vtki.new("ImplicitPlaneWidget")
2014
2015        # self.widget.KeyPressActivationOff()
2016        # self.widget.SetKeyPressActivationValue('i')
2017
2018        self.widget.SetOriginTranslation(can_translate)
2019        self.widget.SetOutlineTranslation(can_translate)
2020        self.widget.SetScaleEnabled(can_scale)
2021
2022        self.widget.GetOutlineProperty().SetColor(get_color(c))
2023        self.widget.GetOutlineProperty().SetOpacity(0.25)
2024        self.widget.GetOutlineProperty().SetLineWidth(1)
2025        self.widget.GetOutlineProperty().LightingOff()
2026
2027        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
2028
2029        self.widget.SetTubing(0)
2030        self.widget.SetDrawPlane(bool(alpha))
2031        self.widget.GetPlaneProperty().LightingOff()
2032        self.widget.GetPlaneProperty().SetOpacity(alpha)
2033        self.widget.GetSelectedPlaneProperty().SetColor(get_color("red5"))
2034        self.widget.GetSelectedPlaneProperty().LightingOff()
2035
2036        self.widget.SetPlaceFactor(1.0 + padding)
2037        self.widget.SetInputData(poly)
2038        self.widget.PlaceWidget()
2039        if delayed:
2040            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2041        else:
2042            self.widget.AddObserver("InteractionEvent", self._select_polygons)
2043
2044        if len(origin) == 3:
2045            self.widget.SetOrigin(origin)
2046        else:
2047            self.widget.SetOrigin(mesh.center_of_mass())
2048
2049        if len(normal) == 3:
2050            self.widget.SetNormal(normal)
2051        else:
2052            self.widget.SetNormal((1, 0, 0))

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

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

Get the origin of the plane.

normal
2064    @property
2065    def normal(self):
2066        """Get the normal of the plane."""
2067        return np.array(self.widget.GetNormal())

Get the normal of the plane.

class SphereCutter(vtkmodules.vtkInteractionWidgets.vtkSphereWidget, BaseCutter):
2222class SphereCutter(vtki.vtkSphereWidget, BaseCutter):
2223    """
2224    Create a box widget to cut away parts of a Mesh.
2225    """
2226    def __init__(
2227            self,
2228            mesh,
2229            invert=False,
2230            can_translate=True,
2231            can_scale=True,
2232            origin=(),
2233            radius=0,
2234            res=60,
2235            delayed=False,
2236            c='white',
2237            alpha=0.05,
2238    ):
2239        """
2240        Create a box widget to cut away parts of a Mesh.
2241
2242        Arguments:
2243            mesh : Mesh
2244                the input mesh
2245            invert : bool
2246                invert the clipping
2247            can_translate : bool
2248                enable translation of the widget
2249            can_scale : bool
2250                enable scaling of the widget
2251            origin : list
2252                initial position of the sphere widget
2253            radius : float
2254                initial radius of the sphere widget
2255            res : int
2256                resolution of the sphere widget
2257            delayed : bool
2258                if True the cutting callback is delayed until
2259                when the mouse button is released (useful for large meshes)
2260            c : color
2261                color of the box cutter widget
2262            alpha : float
2263                transparency of the cut-off part of the input mesh
2264        """
2265        super().__init__()
2266
2267        self.mesh = mesh
2268        self.remnant = Mesh()
2269        self.remnant.name = mesh.name + "Remnant"
2270        self.remnant.pickable(False)
2271
2272        self._alpha = alpha
2273        self._keypress_id = None
2274
2275        self._implicit_func = vtki.new("Sphere")
2276
2277        if len(origin) == 3:
2278            self._implicit_func.SetCenter(origin)
2279        else:
2280            origin = mesh.center_of_mass()
2281            self._implicit_func.SetCenter(origin)
2282
2283        if radius > 0:
2284            self._implicit_func.SetRadius(radius)
2285        else:
2286            radius = mesh.average_size() * 2
2287            self._implicit_func.SetRadius(radius)
2288
2289        poly = mesh.dataset
2290        self.clipper = vtki.new("ClipPolyData")
2291        self.clipper.GenerateClipScalarsOff()
2292        self.clipper.SetInputData(poly)
2293        self.clipper.SetClipFunction(self._implicit_func)
2294        self.clipper.SetInsideOut(not invert)
2295        self.clipper.GenerateClippedOutputOn()
2296        self.clipper.Update()
2297
2298        self.widget = vtki.vtkSphereWidget()
2299
2300        self.widget.SetThetaResolution(res*2)
2301        self.widget.SetPhiResolution(res)
2302        self.widget.SetRadius(radius)
2303        self.widget.SetCenter(origin)
2304        self.widget.SetRepresentation(2)
2305        self.widget.HandleVisibilityOff()
2306
2307        self.widget.SetTranslation(can_translate)
2308        self.widget.SetScale(can_scale)
2309
2310        self.widget.HandleVisibilityOff()
2311        self.widget.GetSphereProperty().SetColor(get_color(c))
2312        self.widget.GetSphereProperty().SetOpacity(0.2)
2313        self.widget.GetSelectedSphereProperty().SetColor(get_color("red5"))
2314        self.widget.GetSelectedSphereProperty().SetOpacity(0.2)
2315
2316        self.widget.SetPlaceFactor(1.0)
2317        self.widget.SetInputData(poly)
2318        self.widget.PlaceWidget()
2319        if delayed:
2320            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2321        else:
2322            self.widget.AddObserver("InteractionEvent", self._select_polygons)
2323
2324    def _select_polygons(self, vobj, event):
2325        vobj.GetSphere(self._implicit_func)
2326
2327    def _keypress(self, vobj, event):
2328        if vobj.GetKeySym() == "r":  # reset planes
2329            self._implicit_func.SetBounds(self._init_bounds)
2330            self.widget.GetPlanes(self._implicit_func)
2331            self.widget.PlaceWidget()
2332            self.widget.GetInteractor().Render()
2333        elif vobj.GetKeySym() == "u":
2334            self.invert()
2335            self.widget.GetInteractor().Render()
2336        elif vobj.GetKeySym() == "s": # Ctrl+s to save mesh
2337            if self.widget.GetInteractor():
2338                if self.widget.GetInteractor().GetControlKey():
2339                    self.mesh.write("vedo_clipped.vtk")
2340                    printc(":save: saved mesh to vedo_clipped.vtk")
2341
2342    @property
2343    def center(self):
2344        """Get the center of the sphere."""
2345        return np.array(self.widget.GetCenter())
2346    
2347    @center.setter
2348    def center(self, value):
2349        """Set the center of the sphere."""
2350        self.widget.SetCenter(value)
2351
2352    @property
2353    def radius(self):
2354        """Get the radius of the sphere."""
2355        return self.widget.GetRadius()
2356    
2357    @radius.setter
2358    def radius(self, value):
2359        """Set the radius of the sphere."""
2360        self.widget.SetRadius(value)

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

SphereCutter( mesh, invert=False, can_translate=True, can_scale=True, origin=(), radius=0, res=60, delayed=False, c='white', alpha=0.05)
2226    def __init__(
2227            self,
2228            mesh,
2229            invert=False,
2230            can_translate=True,
2231            can_scale=True,
2232            origin=(),
2233            radius=0,
2234            res=60,
2235            delayed=False,
2236            c='white',
2237            alpha=0.05,
2238    ):
2239        """
2240        Create a box widget to cut away parts of a Mesh.
2241
2242        Arguments:
2243            mesh : Mesh
2244                the input mesh
2245            invert : bool
2246                invert the clipping
2247            can_translate : bool
2248                enable translation of the widget
2249            can_scale : bool
2250                enable scaling of the widget
2251            origin : list
2252                initial position of the sphere widget
2253            radius : float
2254                initial radius of the sphere widget
2255            res : int
2256                resolution of the sphere widget
2257            delayed : bool
2258                if True the cutting callback is delayed until
2259                when the mouse button is released (useful for large meshes)
2260            c : color
2261                color of the box cutter widget
2262            alpha : float
2263                transparency of the cut-off part of the input mesh
2264        """
2265        super().__init__()
2266
2267        self.mesh = mesh
2268        self.remnant = Mesh()
2269        self.remnant.name = mesh.name + "Remnant"
2270        self.remnant.pickable(False)
2271
2272        self._alpha = alpha
2273        self._keypress_id = None
2274
2275        self._implicit_func = vtki.new("Sphere")
2276
2277        if len(origin) == 3:
2278            self._implicit_func.SetCenter(origin)
2279        else:
2280            origin = mesh.center_of_mass()
2281            self._implicit_func.SetCenter(origin)
2282
2283        if radius > 0:
2284            self._implicit_func.SetRadius(radius)
2285        else:
2286            radius = mesh.average_size() * 2
2287            self._implicit_func.SetRadius(radius)
2288
2289        poly = mesh.dataset
2290        self.clipper = vtki.new("ClipPolyData")
2291        self.clipper.GenerateClipScalarsOff()
2292        self.clipper.SetInputData(poly)
2293        self.clipper.SetClipFunction(self._implicit_func)
2294        self.clipper.SetInsideOut(not invert)
2295        self.clipper.GenerateClippedOutputOn()
2296        self.clipper.Update()
2297
2298        self.widget = vtki.vtkSphereWidget()
2299
2300        self.widget.SetThetaResolution(res*2)
2301        self.widget.SetPhiResolution(res)
2302        self.widget.SetRadius(radius)
2303        self.widget.SetCenter(origin)
2304        self.widget.SetRepresentation(2)
2305        self.widget.HandleVisibilityOff()
2306
2307        self.widget.SetTranslation(can_translate)
2308        self.widget.SetScale(can_scale)
2309
2310        self.widget.HandleVisibilityOff()
2311        self.widget.GetSphereProperty().SetColor(get_color(c))
2312        self.widget.GetSphereProperty().SetOpacity(0.2)
2313        self.widget.GetSelectedSphereProperty().SetColor(get_color("red5"))
2314        self.widget.GetSelectedSphereProperty().SetOpacity(0.2)
2315
2316        self.widget.SetPlaceFactor(1.0)
2317        self.widget.SetInputData(poly)
2318        self.widget.PlaceWidget()
2319        if delayed:
2320            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2321        else:
2322            self.widget.AddObserver("InteractionEvent", self._select_polygons)

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

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

Get the center of the sphere.

radius
2352    @property
2353    def radius(self):
2354        """Get the radius of the sphere."""
2355        return self.widget.GetRadius()

Get the radius of the sphere.