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

Add an inset icon mesh into the renderer.

Icon(mesh, pos=3, size=0.08)
2326    def __init__(self, mesh, pos=3, size=0.08):
2327        """
2328        Arguments:
2329            pos : (list, int)
2330                icon position in the range [1-4] indicating one of the 4 corners,
2331                or it can be a tuple (x,y) as a fraction of the renderer size.
2332            size : (float)
2333                size of the icon space as fraction of the window size.
2334
2335        Examples:
2336            - [icon.py](https://github.com/marcomusy/vedo/tree/master/examples/other/icon.py)
2337        """
2338        super().__init__()
2339
2340        try:
2341            self.SetOrientationMarker(mesh.actor)
2342        except AttributeError:
2343            self.SetOrientationMarker(mesh)
2344
2345        if utils.is_sequence(pos):
2346            self.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size)
2347        else:
2348            if pos < 2:
2349                self.SetViewport(0, 1 - 2 * size, size * 2, 1)
2350            elif pos == 2:
2351                self.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1)
2352            elif pos == 3:
2353                self.SetViewport(0, 0, size * 2, size * 2)
2354            elif pos == 4:
2355                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):
212class LegendBox(shapes.TextBase, vtki.vtkLegendBoxActor):
213    """
214    Create a 2D legend box.
215    """
216    def __init__(
217        self,
218        entries=(),
219        nmax=12,
220        c=None,
221        font="",
222        width=0.18,
223        height=None,
224        padding=2,
225        bg="k8",
226        alpha=0.25,
227        pos="top-right",
228        markers=None,
229    ):
230        """
231        Create a 2D legend box for the list of specified objects.
232
233        Arguments:
234            nmax : (int)
235                max number of legend entries
236            c : (color)
237                text color, leave as None to pick the mesh color automatically
238            font : (str)
239                Check [available fonts here](https://vedo.embl.es/fonts)
240            width : (float)
241                width of the box as fraction of the window width
242            height : (float)
243                height of the box as fraction of the window height
244            padding : (int)
245                padding space in units of pixels
246            bg : (color)
247                background color of the box
248            alpha: (float)
249                opacity of the box
250            pos : (str, list)
251                position of the box, can be either a string or a (x,y) screen position in range [0,1]
252
253        Examples:
254            - [legendbox.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/legendbox.py)
255            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
256            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
257
258                ![](https://vedo.embl.es/images/other/flag_labels.png)
259        """
260        super().__init__()
261
262        self.name = "LegendBox"
263        self.entries = entries[:nmax]
264        self.properties = self.GetEntryTextProperty()
265
266        n = 0
267        texts = []
268        for e in self.entries:
269            ename = e.name
270            if "legend" in e.info.keys():
271                if not e.info["legend"]:
272                    ename = ""
273                else:
274                    ename = str(e.info["legend"])
275            if ename:
276                n += 1
277            texts.append(ename)
278        self.SetNumberOfEntries(n)
279
280        if not n:
281            return
282
283        self.ScalarVisibilityOff()
284        self.PickableOff()
285        self.SetPadding(padding)
286
287        self.properties.ShadowOff()
288        self.properties.BoldOff()
289
290        # self.properties.SetJustificationToLeft() # no effect
291        # self.properties.SetVerticalJustificationToTop()
292
293        if not font:
294            font = settings.default_font
295
296        self.font(font)
297
298        n = 0
299        for i in range(len(self.entries)):
300            ti = texts[i]
301            if not ti:
302                continue
303            e = entries[i]
304            if c is None:
305                col = e.properties.GetColor()
306                if col == (1, 1, 1):
307                    col = (0.2, 0.2, 0.2)
308            else:
309                col = get_color(c)
310            if markers is None:  # default
311                poly = e.dataset
312            else:
313                marker = markers[i] if utils.is_sequence(markers) else markers
314                if isinstance(marker, Points):
315                    poly = marker.clone(deep=False).normalize().shift(0, 1, 0).dataset
316                else:  # assume string marker
317                    poly = vedo.shapes.Marker(marker, s=1).shift(0, 1, 0).dataset
318
319            self.SetEntry(n, poly, ti, col)
320            n += 1
321
322        self.SetWidth(width)
323        if height is None:
324            self.SetHeight(width / 3.0 * n)
325        else:
326            self.SetHeight(height)
327
328        sx, sy = 1 - self.GetWidth(), 1 - self.GetHeight()
329        if pos == 1 or ("top" in pos and "left" in pos):
330            self.GetPositionCoordinate().SetValue(0, sy)
331        elif pos == 2 or ("top" in pos and "right" in pos):
332            self.GetPositionCoordinate().SetValue(sx, sy)
333        elif pos == 3 or ("bottom" in pos and "left" in pos):
334            self.GetPositionCoordinate().SetValue(0, 0)
335        elif pos == 4 or ("bottom" in pos and "right" in pos):
336            self.GetPositionCoordinate().SetValue(sx, 0)
337        if alpha:
338            self.UseBackgroundOn()
339            self.SetBackgroundColor(get_color(bg))
340            self.SetBackgroundOpacity(alpha)
341        else:
342            self.UseBackgroundOff()
343        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)
216    def __init__(
217        self,
218        entries=(),
219        nmax=12,
220        c=None,
221        font="",
222        width=0.18,
223        height=None,
224        padding=2,
225        bg="k8",
226        alpha=0.25,
227        pos="top-right",
228        markers=None,
229    ):
230        """
231        Create a 2D legend box for the list of specified objects.
232
233        Arguments:
234            nmax : (int)
235                max number of legend entries
236            c : (color)
237                text color, leave as None to pick the mesh color automatically
238            font : (str)
239                Check [available fonts here](https://vedo.embl.es/fonts)
240            width : (float)
241                width of the box as fraction of the window width
242            height : (float)
243                height of the box as fraction of the window height
244            padding : (int)
245                padding space in units of pixels
246            bg : (color)
247                background color of the box
248            alpha: (float)
249                opacity of the box
250            pos : (str, list)
251                position of the box, can be either a string or a (x,y) screen position in range [0,1]
252
253        Examples:
254            - [legendbox.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/legendbox.py)
255            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py)
256            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py)
257
258                ![](https://vedo.embl.es/images/other/flag_labels.png)
259        """
260        super().__init__()
261
262        self.name = "LegendBox"
263        self.entries = entries[:nmax]
264        self.properties = self.GetEntryTextProperty()
265
266        n = 0
267        texts = []
268        for e in self.entries:
269            ename = e.name
270            if "legend" in e.info.keys():
271                if not e.info["legend"]:
272                    ename = ""
273                else:
274                    ename = str(e.info["legend"])
275            if ename:
276                n += 1
277            texts.append(ename)
278        self.SetNumberOfEntries(n)
279
280        if not n:
281            return
282
283        self.ScalarVisibilityOff()
284        self.PickableOff()
285        self.SetPadding(padding)
286
287        self.properties.ShadowOff()
288        self.properties.BoldOff()
289
290        # self.properties.SetJustificationToLeft() # no effect
291        # self.properties.SetVerticalJustificationToTop()
292
293        if not font:
294            font = settings.default_font
295
296        self.font(font)
297
298        n = 0
299        for i in range(len(self.entries)):
300            ti = texts[i]
301            if not ti:
302                continue
303            e = entries[i]
304            if c is None:
305                col = e.properties.GetColor()
306                if col == (1, 1, 1):
307                    col = (0.2, 0.2, 0.2)
308            else:
309                col = get_color(c)
310            if markers is None:  # default
311                poly = e.dataset
312            else:
313                marker = markers[i] if utils.is_sequence(markers) else markers
314                if isinstance(marker, Points):
315                    poly = marker.clone(deep=False).normalize().shift(0, 1, 0).dataset
316                else:  # assume string marker
317                    poly = vedo.shapes.Marker(marker, s=1).shift(0, 1, 0).dataset
318
319            self.SetEntry(n, poly, ti, col)
320            n += 1
321
322        self.SetWidth(width)
323        if height is None:
324            self.SetHeight(width / 3.0 * n)
325        else:
326            self.SetHeight(height)
327
328        sx, sy = 1 - self.GetWidth(), 1 - self.GetHeight()
329        if pos == 1 or ("top" in pos and "left" in pos):
330            self.GetPositionCoordinate().SetValue(0, sy)
331        elif pos == 2 or ("top" in pos and "right" in pos):
332            self.GetPositionCoordinate().SetValue(sx, sy)
333        elif pos == 3 or ("bottom" in pos and "left" in pos):
334            self.GetPositionCoordinate().SetValue(0, 0)
335        elif pos == 4 or ("bottom" in pos and "right" in pos):
336            self.GetPositionCoordinate().SetValue(sx, 0)
337        if alpha:
338            self.UseBackgroundOn()
339            self.SetBackgroundColor(get_color(bg))
340            self.SetBackgroundOpacity(alpha)
341        else:
342            self.UseBackgroundOff()
343        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):
787def Light(pos, focal_point=(0, 0, 0), angle=180, c=None, intensity=1):
788    """
789    Generate a source of light placed at `pos` and directed to `focal point`.
790    Returns a `vtkLight` object.
791
792    Arguments:
793        focal_point : (list)
794            focal point, if a `vedo` object is passed then will grab its position.
795        angle : (float)
796            aperture angle of the light source, in degrees
797        c : (color)
798            set the light color
799        intensity : (float)
800            intensity value between 0 and 1.
801
802    Check also:
803        `plotter.Plotter.remove_lights()`
804
805    Examples:
806        - [light_sources.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/light_sources.py)
807
808            ![](https://vedo.embl.es/images/basic/lights.png)
809    """
810    if c is None:
811        try:
812            c = pos.color()
813        except AttributeError:
814            c = "white"
815
816    try:
817        pos = pos.pos()
818    except AttributeError:
819        pass
820    
821    try:
822        focal_point = focal_point.pos()
823    except AttributeError:
824        pass
825
826    light = vtki.vtkLight()
827    light.SetLightTypeToSceneLight()
828    light.SetPosition(pos)
829    light.SetConeAngle(angle)
830    light.SetFocalPoint(focal_point)
831    light.SetIntensity(intensity)
832    light.SetColor(get_color(c))
833    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]:
2900def Axes(
2901        obj=None,
2902        xtitle='x', ytitle='y', ztitle='z',
2903        xrange=None, yrange=None, zrange=None,
2904        c=None,
2905        number_of_divisions=None,
2906        digits=None,
2907        limit_ratio=0.04,
2908        title_depth=0,
2909        title_font="", # grab settings.default_font
2910        text_scale=1.0,
2911        x_values_and_labels=None, y_values_and_labels=None, z_values_and_labels=None,
2912        htitle="",
2913        htitle_size=0.03,
2914        htitle_font=None,
2915        htitle_italic=False,
2916        htitle_color=None, htitle_backface_color=None,
2917        htitle_justify='bottom-left',
2918        htitle_rotation=0,
2919        htitle_offset=(0, 0.01, 0),
2920        xtitle_position=0.95, ytitle_position=0.95, ztitle_position=0.95,
2921        # xtitle_offset can be a list (dx,dy,dz)
2922        xtitle_offset=0.025,  ytitle_offset=0.0275, ztitle_offset=0.02,
2923        xtitle_justify=None,  ytitle_justify=None,  ztitle_justify=None,
2924        # xtitle_rotation can be a list (rx,ry,rz)
2925        xtitle_rotation=0, ytitle_rotation=0, ztitle_rotation=0,
2926        xtitle_box=False,  ytitle_box=False,
2927        xtitle_size=0.025, ytitle_size=0.025, ztitle_size=0.025,
2928        xtitle_color=None, ytitle_color=None, ztitle_color=None,
2929        xtitle_backface_color=None, ytitle_backface_color=None, ztitle_backface_color=None,
2930        xtitle_italic=0, ytitle_italic=0, ztitle_italic=0,
2931        grid_linewidth=1,
2932        xygrid=True,   yzgrid=False,  zxgrid=False,
2933        xygrid2=False, yzgrid2=False, zxgrid2=False,
2934        xygrid_transparent=False,  yzgrid_transparent=False,  zxgrid_transparent=False,
2935        xygrid2_transparent=False, yzgrid2_transparent=False, zxgrid2_transparent=False,
2936        xyplane_color=None, yzplane_color=None, zxplane_color=None,
2937        xygrid_color=None, yzgrid_color=None, zxgrid_color=None,
2938        xyalpha=0.075, yzalpha=0.075, zxalpha=0.075,
2939        xyframe_line=None, yzframe_line=None, zxframe_line=None,
2940        xyframe_color=None, yzframe_color=None, zxframe_color=None,
2941        axes_linewidth=1,
2942        xline_color=None, yline_color=None, zline_color=None,
2943        xhighlight_zero=False, yhighlight_zero=False, zhighlight_zero=False,
2944        xhighlight_zero_color='red4', yhighlight_zero_color='green4', zhighlight_zero_color='blue4',
2945        show_ticks=True,
2946        xtick_length=0.015, ytick_length=0.015, ztick_length=0.015,
2947        xtick_thickness=0.0025, ytick_thickness=0.0025, ztick_thickness=0.0025,
2948        xminor_ticks=1, yminor_ticks=1, zminor_ticks=1,
2949        tip_size=None,
2950        label_font="", # grab settings.default_font
2951        xlabel_color=None, ylabel_color=None, zlabel_color=None,
2952        xlabel_backface_color=None, ylabel_backface_color=None, zlabel_backface_color=None,
2953        xlabel_size=0.016, ylabel_size=0.016, zlabel_size=0.016,
2954        xlabel_offset=0.8, ylabel_offset=0.8, zlabel_offset=0.8, # each can be a list (dx,dy,dz)
2955        xlabel_justify=None, ylabel_justify=None, zlabel_justify=None,
2956        xlabel_rotation=0, ylabel_rotation=0, zlabel_rotation=0, # each can be a list (rx,ry,rz)
2957        xaxis_rotation=0, yaxis_rotation=0, zaxis_rotation=0,    # rotate all elements around axis
2958        xyshift=0, yzshift=0, zxshift=0,
2959        xshift_along_y=0, xshift_along_z=0,
2960        yshift_along_x=0, yshift_along_z=0,
2961        zshift_along_x=0, zshift_along_y=0,
2962        x_use_bounds=True, y_use_bounds=True, z_use_bounds=False,
2963        x_inverted=False, y_inverted=False, z_inverted=False,
2964        use_global=False,
2965        tol=0.001,
2966    ) -> Union[Assembly, None]:
2967    """
2968    Draw axes for the input object.
2969    Check [available fonts here](https://vedo.embl.es/fonts).
2970
2971    Returns an `vedo.Assembly` object.
2972
2973    Parameters
2974    ----------
2975
2976    - `xtitle`,                 ['x'], x-axis title text
2977    - `xrange`,                [None], x-axis range in format (xmin, ymin), default is automatic.
2978    - `number_of_divisions`,   [None], approximate number of divisions on the longest axis
2979    - `axes_linewidth`,           [1], width of the axes lines
2980    - `grid_linewidth`,           [1], width of the grid lines
2981    - `title_depth`,              [0], extrusion fractional depth of title text
2982    - `x_values_and_labels`        [], assign custom tick positions and labels [(pos1, label1), ...]
2983    - `xygrid`,                [True], show a gridded wall on plane xy
2984    - `yzgrid`,                [True], show a gridded wall on plane yz
2985    - `zxgrid`,                [True], show a gridded wall on plane zx
2986    - `yzgrid2`,              [False], show yz plane on opposite side of the bounding box
2987    - `zxgrid2`,              [False], show zx plane on opposite side of the bounding box
2988    - `xygrid_transparent`    [False], make grid plane completely transparent
2989    - `xygrid2_transparent`   [False], make grid plane completely transparent on opposite side box
2990    - `xyplane_color`,       ['None'], color of the plane
2991    - `xygrid_color`,        ['None'], grid line color
2992    - `xyalpha`,               [0.15], grid plane opacity
2993    - `xyframe_line`,             [0], add a frame for the plane, use value as the thickness
2994    - `xyframe_color`,         [None], color for the frame of the plane
2995    - `show_ticks`,            [True], show major ticks
2996    - `digits`,                [None], use this number of significant digits in scientific notation
2997    - `title_font`,              [''], font for axes titles
2998    - `label_font`,              [''], font for numeric labels
2999    - `text_scale`,             [1.0], global scaling factor for all text elements (titles, labels)
3000    - `htitle`,                  [''], header title
3001    - `htitle_size`,           [0.03], header title size
3002    - `htitle_font`,           [None], header font (defaults to `title_font`)
3003    - `htitle_italic`,         [True], header font is italic
3004    - `htitle_color`,          [None], header title color (defaults to `xtitle_color`)
3005    - `htitle_backface_color`, [None], header title color on its backface
3006    - `htitle_justify`, ['bottom-center'], origin of the title justification
3007    - `htitle_offset`,   [(0,0.01,0)], control offsets of header title in x, y and z
3008    - `xtitle_position`,       [0.32], title fractional positions along axis
3009    - `xtitle_offset`,         [0.05], title fractional offset distance from axis line, can be a list
3010    - `xtitle_justify`,        [None], choose the origin of the bounding box of title
3011    - `xtitle_rotation`,          [0], add a rotation of the axis title, can be a list (rx,ry,rz)
3012    - `xtitle_box`,           [False], add a box around title text
3013    - `xline_color`,      [automatic], color of the x-axis
3014    - `xtitle_color`,     [automatic], color of the axis title
3015    - `xtitle_backface_color`, [None], color of axis title on its backface
3016    - `xtitle_size`,          [0.025], size of the axis title
3017    - `xtitle_italic`,            [0], a bool or float to make the font italic
3018    - `xhighlight_zero`,       [True], draw a line highlighting zero position if in range
3019    - `xhighlight_zero_color`, [auto], color of the line highlighting the zero position
3020    - `xtick_length`,         [0.005], radius of the major ticks
3021    - `xtick_thickness`,     [0.0025], thickness of the major ticks along their axis
3022    - `xminor_ticks`,             [1], number of minor ticks between two major ticks
3023    - `xlabel_color`,     [automatic], color of numeric labels and ticks
3024    - `xlabel_backface_color`, [auto], back face color of numeric labels and ticks
3025    - `xlabel_size`,          [0.015], size of the numeric labels along axis
3026    - `xlabel_rotation`,     [0,list], numeric labels rotation (can be a list of 3 rotations)
3027    - `xlabel_offset`,     [0.8,list], offset of the numeric labels (can be a list of 3 offsets)
3028    - `xlabel_justify`,        [None], choose the origin of the bounding box of labels
3029    - `xaxis_rotation`,           [0], rotate the X axis elements (ticks and labels) around this same axis
3030    - `xyshift`                 [0.0], slide the xy-plane along z (the range is [0,1])
3031    - `xshift_along_y`          [0.0], slide x-axis along the y-axis (the range is [0,1])
3032    - `tip_size`,              [0.01], size of the arrow tip as a fraction of the bounding box diagonal
3033    - `limit_ratio`,           [0.04], below this ratio don't plot smaller axis
3034    - `x_use_bounds`,          [True], keep into account space occupied by labels when setting camera
3035    - `x_inverted`,           [False], invert labels order and direction (only visually!)
3036    - `use_global`,           [False], try to compute the global bounding box of visible actors
3037
3038    Example:
3039        ```python
3040        from vedo import Axes, Box, show
3041        box = Box(pos=(1,2,3), length=8, width=9, height=7).alpha(0.1)
3042        axs = Axes(box, c='k')  # returns an Assembly object
3043        for a in axs.unpack():
3044            print(a.name)
3045        show(box, axs).close()
3046        ```
3047        ![](https://vedo.embl.es/images/feats/axes1.png)
3048
3049    Examples:
3050        - [custom_axes1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes1.py)
3051        - [custom_axes2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes2.py)
3052        - [custom_axes3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes3.py)
3053        - [custom_axes4.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes4.py)
3054
3055        ![](https://vedo.embl.es/images/pyplot/customAxes3.png)
3056    """
3057    if not title_font:
3058        title_font = vedo.settings.default_font
3059    if not label_font:
3060        label_font = vedo.settings.default_font
3061
3062    if c is None:  # automatic black or white
3063        c = (0.1, 0.1, 0.1)
3064        plt = vedo.plotter_instance
3065        if plt and plt.renderer:
3066            bgcol = plt.renderer.GetBackground()
3067        else:
3068            bgcol = (1, 1, 1)
3069        if np.sum(bgcol) < 1.5:
3070            c = (0.9, 0.9, 0.9)
3071    else:
3072        c = get_color(c)
3073
3074    # Check if obj has bounds, if so use those
3075    if obj is not None:
3076        try:
3077            bb = obj.bounds()
3078        except AttributeError:
3079            try:
3080                bb = obj.GetBounds()
3081                if xrange is None: xrange = (bb[0], bb[1])
3082                if yrange is None: yrange = (bb[2], bb[3])
3083                if zrange is None: zrange = (bb[4], bb[5])
3084                obj = None # dont need it anymore
3085            except AttributeError:
3086                pass
3087        if utils.is_sequence(obj) and len(obj)==6 and utils.is_number(obj[0]):
3088            # passing a list of numeric bounds
3089            if xrange is None: xrange = (obj[0], obj[1])
3090            if yrange is None: yrange = (obj[2], obj[3])
3091            if zrange is None: zrange = (obj[4], obj[5])
3092
3093    if use_global:
3094        vbb, drange, min_bns, max_bns = compute_visible_bounds()
3095    else:
3096        if obj is not None:
3097            vbb, drange, min_bns, max_bns = compute_visible_bounds(obj)
3098        else:
3099            vbb = np.zeros(6)
3100            drange = np.zeros(3)
3101            if zrange is None:
3102                zrange = (0, 0)
3103            if xrange is None or yrange is None:
3104                vedo.logger.error("in Axes() must specify axes ranges!")
3105                return None  ###########################################
3106
3107    if xrange is not None:
3108        if xrange[1] < xrange[0]:
3109            x_inverted = True
3110            xrange = [xrange[1], xrange[0]]
3111        vbb[0], vbb[1] = xrange
3112        drange[0] = vbb[1] - vbb[0]
3113        min_bns = vbb
3114        max_bns = vbb
3115    if yrange is not None:
3116        if yrange[1] < yrange[0]:
3117            y_inverted = True
3118            yrange = [yrange[1], yrange[0]]
3119        vbb[2], vbb[3] = yrange
3120        drange[1] = vbb[3] - vbb[2]
3121        min_bns = vbb
3122        max_bns = vbb
3123    if zrange is not None:
3124        if zrange[1] < zrange[0]:
3125            z_inverted = True
3126            zrange = [zrange[1], zrange[0]]
3127        vbb[4], vbb[5] = zrange
3128        drange[2] = vbb[5] - vbb[4]
3129        min_bns = vbb
3130        max_bns = vbb
3131
3132    drangemax = max(drange)
3133    if not drangemax:
3134        return None
3135
3136    if drange[0] / drangemax < limit_ratio:
3137        drange[0] = 0
3138        xtitle = ""
3139    if drange[1] / drangemax < limit_ratio:
3140        drange[1] = 0
3141        ytitle = ""
3142    if drange[2] / drangemax < limit_ratio:
3143        drange[2] = 0
3144        ztitle = ""
3145
3146    x0, x1, y0, y1, z0, z1 = vbb
3147    dx, dy, dz = drange
3148
3149    gscale = np.sqrt(dx * dx + dy * dy + dz * dz) * 0.75
3150
3151    if not xyplane_color: xyplane_color = c
3152    if not yzplane_color: yzplane_color = c
3153    if not zxplane_color: zxplane_color = c
3154    if not xygrid_color:  xygrid_color = c
3155    if not yzgrid_color:  yzgrid_color = c
3156    if not zxgrid_color:  zxgrid_color = c
3157    if not xtitle_color:  xtitle_color = c
3158    if not ytitle_color:  ytitle_color = c
3159    if not ztitle_color:  ztitle_color = c
3160    if not xline_color:   xline_color = c
3161    if not yline_color:   yline_color = c
3162    if not zline_color:   zline_color = c
3163    if not xlabel_color:  xlabel_color = xline_color
3164    if not ylabel_color:  ylabel_color = yline_color
3165    if not zlabel_color:  zlabel_color = zline_color
3166
3167    if tip_size is None:
3168        tip_size = 0.005 * gscale
3169        if not ztitle:
3170            tip_size = 0  # switch off in xy 2d
3171
3172    ndiv = 4
3173    if not ztitle or not ytitle or not xtitle:  # make more default ticks if 2D
3174        ndiv = 6
3175        if not ztitle:
3176            if xyframe_line is None:
3177                xyframe_line = True
3178            if tip_size is None:
3179                tip_size = False
3180
3181    if utils.is_sequence(number_of_divisions):
3182        rx, ry, rz = number_of_divisions
3183    else:
3184        if not number_of_divisions:
3185            number_of_divisions = ndiv
3186
3187    rx, ry, rz = np.ceil(drange / drangemax * number_of_divisions).astype(int)
3188
3189    if xtitle:
3190        xticks_float, xticks_str = utils.make_ticks(x0, x1, rx, x_values_and_labels, digits)
3191        xticks_float = xticks_float * dx
3192        if x_inverted:
3193            xticks_float = np.flip(-(xticks_float - xticks_float[-1]))
3194            xticks_str = list(reversed(xticks_str))
3195            xticks_str[-1] = ""
3196            xhighlight_zero = False
3197    if ytitle:
3198        yticks_float, yticks_str = utils.make_ticks(y0, y1, ry, y_values_and_labels, digits)
3199        yticks_float = yticks_float * dy
3200        if y_inverted:
3201            yticks_float = np.flip(-(yticks_float - yticks_float[-1]))
3202            yticks_str = list(reversed(yticks_str))
3203            yticks_str[-1] = ""
3204            yhighlight_zero = False
3205    if ztitle:
3206        zticks_float, zticks_str = utils.make_ticks(z0, z1, rz, z_values_and_labels, digits)
3207        zticks_float = zticks_float * dz
3208        if z_inverted:
3209            zticks_float = np.flip(-(zticks_float - zticks_float[-1]))
3210            zticks_str = list(reversed(zticks_str))
3211            zticks_str[-1] = ""
3212            zhighlight_zero = False
3213
3214    ################################################ axes lines
3215    lines = []
3216    if xtitle:
3217        axlinex = shapes.Line([0,0,0], [dx,0,0], c=xline_color, lw=axes_linewidth)
3218        axlinex.shift([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3219        axlinex.name = 'xAxis'
3220        lines.append(axlinex)
3221    if ytitle:
3222        axliney = shapes.Line([0,0,0], [0,dy,0], c=yline_color, lw=axes_linewidth)
3223        axliney.shift([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3224        axliney.name = 'yAxis'
3225        lines.append(axliney)
3226    if ztitle:
3227        axlinez = shapes.Line([0,0,0], [0,0,dz], c=zline_color, lw=axes_linewidth)
3228        axlinez.shift([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3229        axlinez.name = 'zAxis'
3230        lines.append(axlinez)
3231
3232    ################################################ grid planes
3233    # all shapes have a name to keep track of them in the Assembly
3234    # if user wants to unpack it
3235    grids = []
3236    if xygrid and xtitle and ytitle:
3237        if not xygrid_transparent:
3238            gxy = shapes.Grid(s=(xticks_float, yticks_float))
3239            gxy.alpha(xyalpha).c(xyplane_color).lw(0)
3240            if xyshift: gxy.shift([0,0,xyshift*dz])
3241            elif tol:   gxy.shift([0,0,-tol*gscale])
3242            gxy.name = "xyGrid"
3243            grids.append(gxy)
3244        if grid_linewidth:
3245            gxy_lines = shapes.Grid(s=(xticks_float, yticks_float))
3246            gxy_lines.c(xyplane_color).lw(grid_linewidth).alpha(xyalpha)
3247            if xyshift: gxy_lines.shift([0,0,xyshift*dz])
3248            elif tol:   gxy_lines.shift([0,0,-tol*gscale])
3249            gxy_lines.name = "xyGridLines"
3250            grids.append(gxy_lines)
3251
3252    if yzgrid and ytitle and ztitle:
3253        if not yzgrid_transparent:
3254            gyz = shapes.Grid(s=(zticks_float, yticks_float))
3255            gyz.alpha(yzalpha).c(yzplane_color).lw(0).rotate_y(-90)
3256            if yzshift: gyz.shift([yzshift*dx,0,0])
3257            elif tol:   gyz.shift([-tol*gscale,0,0])
3258            gyz.name = "yzGrid"
3259            grids.append(gyz)
3260        if grid_linewidth:
3261            gyz_lines = shapes.Grid(s=(zticks_float, yticks_float))
3262            gyz_lines.c(yzplane_color).lw(grid_linewidth).alpha(yzalpha).rotate_y(-90)
3263            if yzshift: gyz_lines.shift([yzshift*dx,0,0])
3264            elif tol:   gyz_lines.shift([-tol*gscale,0,0])
3265            gyz_lines.name = "yzGridLines"
3266            grids.append(gyz_lines)
3267
3268    if zxgrid and ztitle and xtitle:
3269        if not zxgrid_transparent:
3270            gzx = shapes.Grid(s=(xticks_float, zticks_float))
3271            gzx.alpha(zxalpha).c(zxplane_color).lw(0).rotate_x(90)
3272            if zxshift: gzx.shift([0,zxshift*dy,0])
3273            elif tol:   gzx.shift([0,-tol*gscale,0])
3274            gzx.name = "zxGrid"
3275            grids.append(gzx)
3276        if grid_linewidth:
3277            gzx_lines = shapes.Grid(s=(xticks_float, zticks_float))
3278            gzx_lines.c(zxplane_color).lw(grid_linewidth).alpha(zxalpha).rotate_x(90)
3279            if zxshift: gzx_lines.shift([0,zxshift*dy,0])
3280            elif tol:   gzx_lines.shift([0,-tol*gscale,0])
3281            gzx_lines.name = "zxGridLines"
3282            grids.append(gzx_lines)
3283
3284    # Grid2
3285    if xygrid2 and xtitle and ytitle:
3286        if not xygrid2_transparent:
3287            gxy2 = shapes.Grid(s=(xticks_float, yticks_float)).z(dz)
3288            gxy2.alpha(xyalpha).c(xyplane_color).lw(0)
3289            gxy2.shift([0,tol*gscale,0])
3290            gxy2.name = "xyGrid2"
3291            grids.append(gxy2)
3292        if grid_linewidth:
3293            gxy2_lines = shapes.Grid(s=(xticks_float, yticks_float)).z(dz)
3294            gxy2_lines.c(xyplane_color).lw(grid_linewidth).alpha(xyalpha)
3295            gxy2_lines.shift([0,tol*gscale,0])
3296            gxy2_lines.name = "xygrid2Lines"
3297            grids.append(gxy2_lines)
3298
3299    if yzgrid2 and ytitle and ztitle:
3300        if not yzgrid2_transparent:
3301            gyz2 = shapes.Grid(s=(zticks_float, yticks_float))
3302            gyz2.alpha(yzalpha).c(yzplane_color).lw(0)
3303            gyz2.rotate_y(-90).x(dx).shift([tol*gscale,0,0])
3304            gyz2.name = "yzGrid2"
3305            grids.append(gyz2)
3306        if grid_linewidth:
3307            gyz2_lines = shapes.Grid(s=(zticks_float, yticks_float))
3308            gyz2_lines.c(yzplane_color).lw(grid_linewidth).alpha(yzalpha)
3309            gyz2_lines.rotate_y(-90).x(dx).shift([tol*gscale,0,0])
3310            gyz2_lines.name = "yzGrid2Lines"
3311            grids.append(gyz2_lines)
3312
3313    if zxgrid2 and ztitle and xtitle:
3314        if not zxgrid2_transparent:
3315            gzx2 = shapes.Grid(s=(xticks_float, zticks_float))
3316            gzx2.alpha(zxalpha).c(zxplane_color).lw(0)
3317            gzx2.rotate_x(90).y(dy).shift([0,tol*gscale,0])
3318            gzx2.name = "zxGrid2"
3319            grids.append(gzx2)
3320        if grid_linewidth:
3321            gzx2_lines = shapes.Grid(s=(xticks_float, zticks_float))
3322            gzx2_lines.c(zxplane_color).lw(grid_linewidth).alpha(zxalpha)
3323            gzx2_lines.rotate_x(90).y(dy).shift([0,tol*gscale,0])
3324            gzx2_lines.name = "zxGrid2Lines"
3325            grids.append(gzx2_lines)
3326
3327    ################################################ frame lines
3328    framelines = []
3329    if xyframe_line and xtitle and ytitle:
3330        if not xyframe_color:
3331            xyframe_color = xygrid_color
3332        frxy = shapes.Line([[0,dy,0],[dx,dy,0],[dx,0,0],[0,0,0],[0,dy,0]],
3333                           c=xyframe_color, lw=xyframe_line)
3334        frxy.shift([0,0,xyshift*dz])
3335        frxy.name = 'xyFrameLine'
3336        framelines.append(frxy)
3337    if yzframe_line and ytitle and ztitle:
3338        if not yzframe_color:
3339            yzframe_color = yzgrid_color
3340        fryz = shapes.Line([[0,0,dz],[0,dy,dz],[0,dy,0],[0,0,0],[0,0,dz]],
3341                           c=yzframe_color, lw=yzframe_line)
3342        fryz.shift([yzshift*dx,0,0])
3343        fryz.name = 'yzFrameLine'
3344        framelines.append(fryz)
3345    if zxframe_line and ztitle and xtitle:
3346        if not zxframe_color:
3347            zxframe_color = zxgrid_color
3348        frzx = shapes.Line([[0,0,dz],[dx,0,dz],[dx,0,0],[0,0,0],[0,0,dz]],
3349                           c=zxframe_color, lw=zxframe_line)
3350        frzx.shift([0,zxshift*dy,0])
3351        frzx.name = 'zxFrameLine'
3352        framelines.append(frzx)
3353
3354    ################################################ zero lines highlights
3355    highlights = []
3356    if xygrid and xtitle and ytitle:
3357        if xhighlight_zero and min_bns[0] <= 0 and max_bns[1] > 0:
3358            xhl = -min_bns[0]
3359            hxy = shapes.Line([xhl,0,0], [xhl,dy,0], c=xhighlight_zero_color)
3360            hxy.alpha(np.sqrt(xyalpha)).lw(grid_linewidth*2)
3361            hxy.shift([0,0,xyshift*dz])
3362            hxy.name = "xyHighlightZero"
3363            highlights.append(hxy)
3364        if yhighlight_zero and min_bns[2] <= 0 and max_bns[3] > 0:
3365            yhl = -min_bns[2]
3366            hyx = shapes.Line([0,yhl,0], [dx,yhl,0], c=yhighlight_zero_color)
3367            hyx.alpha(np.sqrt(yzalpha)).lw(grid_linewidth*2)
3368            hyx.shift([0,0,xyshift*dz])
3369            hyx.name = "yxHighlightZero"
3370            highlights.append(hyx)
3371
3372    if yzgrid and ytitle and ztitle:
3373        if yhighlight_zero and min_bns[2] <= 0 and max_bns[3] > 0:
3374            yhl = -min_bns[2]
3375            hyz = shapes.Line([0,yhl,0], [0,yhl,dz], c=yhighlight_zero_color)
3376            hyz.alpha(np.sqrt(yzalpha)).lw(grid_linewidth*2)
3377            hyz.shift([yzshift*dx,0,0])
3378            hyz.name = "yzHighlightZero"
3379            highlights.append(hyz)
3380        if zhighlight_zero and min_bns[4] <= 0 and max_bns[5] > 0:
3381            zhl = -min_bns[4]
3382            hzy = shapes.Line([0,0,zhl], [0,dy,zhl], c=zhighlight_zero_color)
3383            hzy.alpha(np.sqrt(yzalpha)).lw(grid_linewidth*2)
3384            hzy.shift([yzshift*dx,0,0])
3385            hzy.name = "zyHighlightZero"
3386            highlights.append(hzy)
3387
3388    if zxgrid and ztitle and xtitle:
3389        if zhighlight_zero and min_bns[4] <= 0 and max_bns[5] > 0:
3390            zhl = -min_bns[4]
3391            hzx = shapes.Line([0,0,zhl], [dx,0,zhl], c=zhighlight_zero_color)
3392            hzx.alpha(np.sqrt(zxalpha)).lw(grid_linewidth*2)
3393            hzx.shift([0,zxshift*dy,0])
3394            hzx.name = "zxHighlightZero"
3395            highlights.append(hzx)
3396        if xhighlight_zero and min_bns[0] <= 0 and max_bns[1] > 0:
3397            xhl = -min_bns[0]
3398            hxz = shapes.Line([xhl,0,0], [xhl,0,dz], c=xhighlight_zero_color)
3399            hxz.alpha(np.sqrt(zxalpha)).lw(grid_linewidth*2)
3400            hxz.shift([0,zxshift*dy,0])
3401            hxz.name = "xzHighlightZero"
3402            highlights.append(hxz)
3403
3404    ################################################ arrow cone
3405    cones = []
3406
3407    if tip_size:
3408
3409        if xtitle:
3410            if x_inverted:
3411                cx = shapes.Cone(
3412                    r=tip_size, height=tip_size * 2, axis=(-1, 0, 0), c=xline_color, res=12
3413                )
3414            else:
3415                cx = shapes.Cone((dx,0,0), r=tip_size, height=tip_size*2,
3416                                 axis=(1,0,0), c=xline_color, res=12)
3417            T = LinearTransform()
3418            T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3419            cx.apply_transform(T)
3420            cx.name = "xTipCone"
3421            cones.append(cx)
3422
3423        if ytitle:
3424            if y_inverted:
3425                cy = shapes.Cone(r=tip_size, height=tip_size*2,
3426                                 axis=(0,-1,0), c=yline_color, res=12)
3427            else:
3428                cy = shapes.Cone((0,dy,0), r=tip_size, height=tip_size*2,
3429                                 axis=(0,1,0), c=yline_color, res=12)
3430            T = LinearTransform()
3431            T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3432            cy.apply_transform(T)
3433            cy.name = "yTipCone"
3434            cones.append(cy)
3435
3436        if ztitle:
3437            if z_inverted:
3438                cz = shapes.Cone(r=tip_size, height=tip_size*2,
3439                                 axis=(0,0,-1), c=zline_color, res=12)
3440            else:
3441                cz = shapes.Cone((0,0,dz), r=tip_size, height=tip_size*2,
3442                                 axis=(0,0,1), c=zline_color, res=12)
3443            T = LinearTransform()
3444            T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3445            cz.apply_transform(T)
3446            cz.name = "zTipCone"
3447            cones.append(cz)
3448
3449    ################################################################# MAJOR ticks
3450    majorticks, minorticks = [], []
3451    xticks, yticks, zticks = [], [], []
3452    if show_ticks:
3453        if xtitle:
3454            tick_thickness = xtick_thickness * gscale / 2
3455            tick_length = xtick_length * gscale / 2
3456            for i in range(1, len(xticks_float) - 1):
3457                v1 = (xticks_float[i] - tick_thickness, -tick_length, 0)
3458                v2 = (xticks_float[i] + tick_thickness, tick_length, 0)
3459                xticks.append(shapes.Rectangle(v1, v2))
3460            if len(xticks) > 1:
3461                xmajticks = merge(xticks).c(xlabel_color)
3462                T = LinearTransform()
3463                T.rotate_x(xaxis_rotation)
3464                T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3465                xmajticks.apply_transform(T)
3466                xmajticks.name = "xMajorTicks"
3467                majorticks.append(xmajticks)
3468        if ytitle:
3469            tick_thickness = ytick_thickness * gscale / 2
3470            tick_length = ytick_length * gscale / 2
3471            for i in range(1, len(yticks_float) - 1):
3472                v1 = (-tick_length, yticks_float[i] - tick_thickness, 0)
3473                v2 = ( tick_length, yticks_float[i] + tick_thickness, 0)
3474                yticks.append(shapes.Rectangle(v1, v2))
3475            if len(yticks) > 1:
3476                ymajticks = merge(yticks).c(ylabel_color)
3477                T = LinearTransform()
3478                T.rotate_y(yaxis_rotation)
3479                T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3480                ymajticks.apply_transform(T)
3481                ymajticks.name = "yMajorTicks"
3482                majorticks.append(ymajticks)
3483        if ztitle:
3484            tick_thickness = ztick_thickness * gscale / 2
3485            tick_length = ztick_length * gscale / 2.85
3486            for i in range(1, len(zticks_float) - 1):
3487                v1 = (zticks_float[i] - tick_thickness, -tick_length, 0)
3488                v2 = (zticks_float[i] + tick_thickness,  tick_length, 0)
3489                zticks.append(shapes.Rectangle(v1, v2))
3490            if len(zticks) > 1:
3491                zmajticks = merge(zticks).c(zlabel_color)
3492                T = LinearTransform()
3493                T.rotate_y(-90).rotate_z(-45 + zaxis_rotation)
3494                T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3495                zmajticks.apply_transform(T)
3496                zmajticks.name = "zMajorTicks"
3497                majorticks.append(zmajticks)
3498
3499        ############################################################# MINOR ticks
3500        if xtitle and xminor_ticks and len(xticks) > 1:
3501            tick_thickness = xtick_thickness * gscale / 4
3502            tick_length = xtick_length * gscale / 4
3503            xminor_ticks += 1
3504            ticks = []
3505            for i in range(1, len(xticks)):
3506                t0, t1 = xticks[i - 1].pos(), xticks[i].pos()
3507                dt = t1 - t0
3508                for j in range(1, xminor_ticks):
3509                    mt = dt * (j / xminor_ticks) + t0
3510                    v1 = (mt[0] - tick_thickness, -tick_length, 0)
3511                    v2 = (mt[0] + tick_thickness, tick_length, 0)
3512                    ticks.append(shapes.Rectangle(v1, v2))
3513
3514            # finish off the fist lower range from start to first tick
3515            t0, t1 = xticks[0].pos(), xticks[1].pos()
3516            dt = t1 - t0
3517            for j in range(1, xminor_ticks):
3518                mt = t0 - dt * (j / xminor_ticks)
3519                if mt[0] < 0:
3520                    break
3521                v1 = (mt[0] - tick_thickness, -tick_length, 0)
3522                v2 = (mt[0] + tick_thickness,  tick_length, 0)
3523                ticks.append(shapes.Rectangle(v1, v2))
3524
3525            # finish off the last upper range from last tick to end
3526            t0, t1 = xticks[-2].pos(), xticks[-1].pos()
3527            dt = t1 - t0
3528            for j in range(1, xminor_ticks):
3529                mt = t1 + dt * (j / xminor_ticks)
3530                if mt[0] > dx:
3531                    break
3532                v1 = (mt[0] - tick_thickness, -tick_length, 0)
3533                v2 = (mt[0] + tick_thickness,  tick_length, 0)
3534                ticks.append(shapes.Rectangle(v1, v2))
3535
3536            if ticks:
3537                xminticks = merge(ticks).c(xlabel_color)
3538                T = LinearTransform()
3539                T.rotate_x(xaxis_rotation)
3540                T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3541                xminticks.apply_transform(T)
3542                xminticks.name = "xMinorTicks"
3543                minorticks.append(xminticks)
3544
3545        if ytitle and yminor_ticks and len(yticks) > 1:  ##### y
3546            tick_thickness = ytick_thickness * gscale / 4
3547            tick_length = ytick_length * gscale / 4
3548            yminor_ticks += 1
3549            ticks = []
3550            for i in range(1, len(yticks)):
3551                t0, t1 = yticks[i - 1].pos(), yticks[i].pos()
3552                dt = t1 - t0
3553                for j in range(1, yminor_ticks):
3554                    mt = dt * (j / yminor_ticks) + t0
3555                    v1 = (-tick_length, mt[1] - tick_thickness, 0)
3556                    v2 = ( tick_length, mt[1] + tick_thickness, 0)
3557                    ticks.append(shapes.Rectangle(v1, v2))
3558
3559            # finish off the fist lower range from start to first tick
3560            t0, t1 = yticks[0].pos(), yticks[1].pos()
3561            dt = t1 - t0
3562            for j in range(1, yminor_ticks):
3563                mt = t0 - dt * (j / yminor_ticks)
3564                if mt[1] < 0:
3565                    break
3566                v1 = (-tick_length, mt[1] - tick_thickness, 0)
3567                v2 = ( tick_length, mt[1] + tick_thickness, 0)
3568                ticks.append(shapes.Rectangle(v1, v2))
3569
3570            # finish off the last upper range from last tick to end
3571            t0, t1 = yticks[-2].pos(), yticks[-1].pos()
3572            dt = t1 - t0
3573            for j in range(1, yminor_ticks):
3574                mt = t1 + dt * (j / yminor_ticks)
3575                if mt[1] > dy:
3576                    break
3577                v1 = (-tick_length, mt[1] - tick_thickness, 0)
3578                v2 = ( tick_length, mt[1] + tick_thickness, 0)
3579                ticks.append(shapes.Rectangle(v1, v2))
3580
3581            if ticks:
3582                yminticks = merge(ticks).c(ylabel_color)
3583                T = LinearTransform()
3584                T.rotate_y(yaxis_rotation)
3585                T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3586                yminticks.apply_transform(T)
3587                yminticks.name = "yMinorTicks"
3588                minorticks.append(yminticks)
3589
3590        if ztitle and zminor_ticks and len(zticks) > 1:  ##### z
3591            tick_thickness = ztick_thickness * gscale / 4
3592            tick_length = ztick_length * gscale / 5
3593            zminor_ticks += 1
3594            ticks = []
3595            for i in range(1, len(zticks)):
3596                t0, t1 = zticks[i - 1].pos(), zticks[i].pos()
3597                dt = t1 - t0
3598                for j in range(1, zminor_ticks):
3599                    mt = dt * (j / zminor_ticks) + t0
3600                    v1 = (mt[0] - tick_thickness, -tick_length, 0)
3601                    v2 = (mt[0] + tick_thickness,  tick_length, 0)
3602                    ticks.append(shapes.Rectangle(v1, v2))
3603
3604            # finish off the fist lower range from start to first tick
3605            t0, t1 = zticks[0].pos(), zticks[1].pos()
3606            dt = t1 - t0
3607            for j in range(1, zminor_ticks):
3608                mt = t0 - dt * (j / zminor_ticks)
3609                if mt[0] < 0:
3610                    break
3611                v1 = (mt[0] - tick_thickness, -tick_length, 0)
3612                v2 = (mt[0] + tick_thickness,  tick_length, 0)
3613                ticks.append(shapes.Rectangle(v1, v2))
3614
3615            # finish off the last upper range from last tick to end
3616            t0, t1 = zticks[-2].pos(), zticks[-1].pos()
3617            dt = t1 - t0
3618            for j in range(1, zminor_ticks):
3619                mt = t1 + dt * (j / zminor_ticks)
3620                if mt[0] > dz:
3621                    break
3622                v1 = (mt[0] - tick_thickness, -tick_length, 0)
3623                v2 = (mt[0] + tick_thickness,  tick_length, 0)
3624                ticks.append(shapes.Rectangle(v1, v2))
3625
3626            if ticks:
3627                zminticks = merge(ticks).c(zlabel_color)
3628                T = LinearTransform()
3629                T.rotate_y(-90).rotate_z(-45 + zaxis_rotation)
3630                T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3631                zminticks.apply_transform(T)
3632                zminticks.name = "zMinorTicks"
3633                minorticks.append(zminticks)
3634
3635    ################################################ axes NUMERIC text labels
3636    labels = []
3637    xlab, ylab, zlab = None, None, None
3638
3639    if xlabel_size and xtitle:
3640
3641        xRot, yRot, zRot = 0, 0, 0
3642        if utils.is_sequence(xlabel_rotation):  # unpck 3 rotations
3643            zRot, xRot, yRot = xlabel_rotation
3644        else:
3645            zRot = xlabel_rotation
3646        if zRot < 0:  # deal with negative angles
3647            zRot += 360
3648
3649        jus = "center-top"
3650        if zRot:
3651            if zRot >  24: jus = "top-right"
3652            if zRot >  67: jus = "center-right"
3653            if zRot > 112: jus = "right-bottom"
3654            if zRot > 157: jus = "center-bottom"
3655            if zRot > 202: jus = "bottom-left"
3656            if zRot > 247: jus = "center-left"
3657            if zRot > 292: jus = "top-left"
3658            if zRot > 337: jus = "top-center"
3659        if xlabel_justify is not None:
3660            jus = xlabel_justify
3661
3662        for i in range(1, len(xticks_str)):
3663            t = xticks_str[i]
3664            if not t:
3665                continue
3666            if utils.is_sequence(xlabel_offset):
3667                xoffs, yoffs, zoffs = xlabel_offset
3668            else:
3669                xoffs, yoffs, zoffs = 0, xlabel_offset, 0
3670
3671            xlab = shapes.Text3D(
3672                t, s=xlabel_size * text_scale * gscale, font=label_font, justify=jus,
3673            )
3674            tb = xlab.ybounds()  # must be ybounds: height of char
3675
3676            v = (xticks_float[i], 0, 0)
3677            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0])
3678
3679            T = LinearTransform()
3680            T.rotate_x(xaxis_rotation).rotate_y(yRot).rotate_x(xRot).rotate_z(zRot)
3681            T.translate(v + offs)
3682            T.translate([0, zxshift*dy + xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3683            xlab.apply_transform(T)
3684
3685            xlab.use_bounds(x_use_bounds)
3686
3687            xlab.c(xlabel_color)
3688            if xlabel_backface_color is None:
3689                bfc = 1 - np.array(get_color(xlabel_color))
3690                xlab.backcolor(bfc)
3691            xlab.name = f"xNumericLabel {i}"
3692            labels.append(xlab)
3693
3694    if ylabel_size and ytitle:
3695
3696        xRot, yRot, zRot = 0, 0, 0
3697        if utils.is_sequence(ylabel_rotation):  # unpck 3 rotations
3698            zRot, yRot, xRot = ylabel_rotation
3699        else:
3700            zRot = ylabel_rotation
3701        if zRot < 0:
3702            zRot += 360  # deal with negative angles
3703
3704        jus = "center-right"
3705        if zRot:
3706            if zRot >  24: jus = "bottom-right"
3707            if zRot >  67: jus = "center-bottom"
3708            if zRot > 112: jus = "left-bottom"
3709            if zRot > 157: jus = "center-left"
3710            if zRot > 202: jus = "top-left"
3711            if zRot > 247: jus = "center-top"
3712            if zRot > 292: jus = "top-right"
3713            if zRot > 337: jus = "right-center"
3714        if ylabel_justify is not None:
3715            jus = ylabel_justify
3716
3717        for i in range(1, len(yticks_str)):
3718            t = yticks_str[i]
3719            if not t:
3720                continue
3721            if utils.is_sequence(ylabel_offset):
3722                xoffs, yoffs, zoffs = ylabel_offset
3723            else:
3724                xoffs, yoffs, zoffs = ylabel_offset, 0, 0
3725            ylab = shapes.Text3D(
3726                t, s=ylabel_size * text_scale * gscale, font=label_font, justify=jus
3727            )
3728            tb = ylab.ybounds()  # must be ybounds: height of char
3729            v = (0, yticks_float[i], 0)
3730            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0])
3731
3732            T = LinearTransform()
3733            T.rotate_y(yaxis_rotation).rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
3734            T.translate(v + offs)
3735            T.translate([yzshift*dx + yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3736            ylab.apply_transform(T)
3737
3738            ylab.use_bounds(y_use_bounds)
3739
3740            ylab.c(ylabel_color)
3741            if ylabel_backface_color is None:
3742                bfc = 1 - np.array(get_color(ylabel_color))
3743                ylab.backcolor(bfc)
3744            ylab.name = f"yNumericLabel {i}"
3745            labels.append(ylab)
3746
3747    if zlabel_size and ztitle:
3748
3749        xRot, yRot, zRot = 0, 0, 0
3750        if utils.is_sequence(zlabel_rotation):  # unpck 3 rotations
3751            xRot, yRot, zRot = zlabel_rotation
3752        else:
3753            xRot = zlabel_rotation
3754        if xRot < 0: xRot += 360 # deal with negative angles
3755
3756        jus = "center-right"
3757        if xRot:
3758            if xRot >  24: jus = "bottom-right"
3759            if xRot >  67: jus = "center-bottom"
3760            if xRot > 112: jus = "left-bottom"
3761            if xRot > 157: jus = "center-left"
3762            if xRot > 202: jus = "top-left"
3763            if xRot > 247: jus = "center-top"
3764            if xRot > 292: jus = "top-right"
3765            if xRot > 337: jus = "right-center"
3766        if zlabel_justify is not None:
3767            jus = zlabel_justify
3768
3769        for i in range(1, len(zticks_str)):
3770            t = zticks_str[i]
3771            if not t:
3772                continue
3773            if utils.is_sequence(zlabel_offset):
3774                xoffs, yoffs, zoffs = zlabel_offset
3775            else:
3776                xoffs, yoffs, zoffs = zlabel_offset, zlabel_offset, 0
3777            zlab = shapes.Text3D(t, s=zlabel_size*text_scale*gscale, font=label_font, justify=jus)
3778            tb = zlab.ybounds()  # must be ybounds: height of char
3779
3780            v = (0, 0, zticks_float[i])
3781            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0]) / 1.5
3782            angle = np.arctan2(dy, dx) * 57.3
3783
3784            T = LinearTransform()
3785            T.rotate_x(90 + zRot).rotate_y(-xRot).rotate_z(angle + yRot + zaxis_rotation)
3786            T.translate(v + offs)
3787            T.translate([yzshift*dx + zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3788            zlab.apply_transform(T)
3789
3790            zlab.use_bounds(z_use_bounds)
3791
3792            zlab.c(zlabel_color)
3793            if zlabel_backface_color is None:
3794                bfc = 1 - np.array(get_color(zlabel_color))
3795                zlab.backcolor(bfc)
3796            zlab.name = f"zNumericLabel {i}"
3797            labels.append(zlab)
3798
3799    ################################################ axes titles
3800    titles = []
3801
3802    if xtitle:
3803        xRot, yRot, zRot = 0, 0, 0
3804        if utils.is_sequence(xtitle_rotation):  # unpack 3 rotations
3805            zRot, xRot, yRot = xtitle_rotation
3806        else:
3807            zRot = xtitle_rotation
3808        if zRot < 0:  # deal with negative angles
3809            zRot += 360
3810
3811        if utils.is_sequence(xtitle_offset):
3812            xoffs, yoffs, zoffs = xtitle_offset
3813        else:
3814            xoffs, yoffs, zoffs = 0, xtitle_offset, 0
3815
3816        if xtitle_justify is not None:
3817            jus = xtitle_justify
3818        else:
3819            # find best justfication for given rotation(s)
3820            jus = "right-top"
3821            if zRot:
3822                if zRot >  24: jus = "center-right"
3823                if zRot >  67: jus = "right-bottom"
3824                if zRot > 157: jus = "bottom-left"
3825                if zRot > 202: jus = "center-left"
3826                if zRot > 247: jus = "top-left"
3827                if zRot > 337: jus = "top-right"
3828
3829        xt = shapes.Text3D(
3830            xtitle,
3831            s=xtitle_size * text_scale * gscale,
3832            font=title_font,
3833            c=xtitle_color,
3834            justify=jus,
3835            depth=title_depth,
3836            italic=xtitle_italic,
3837        )
3838        if xtitle_backface_color is None:
3839            xtitle_backface_color = 1 - np.array(get_color(xtitle_color))
3840            xt.backcolor(xtitle_backface_color)
3841
3842        shift = 0
3843        if xlab:  # xlab is the last created numeric text label..
3844            lt0, lt1 = xlab.bounds()[2:4]
3845            shift = lt1 - lt0
3846
3847        T = LinearTransform()
3848        T.rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
3849        T.set_position(
3850            [(xoffs + xtitle_position) * dx,
3851            -(yoffs + xtick_length / 2) * dy - shift,
3852            zoffs * dz]
3853        )
3854        T.rotate_x(xaxis_rotation)
3855        T.translate([0, xshift_along_y*dy, xyshift*dz + xshift_along_z*dz])
3856        xt.apply_transform(T)
3857
3858        xt.use_bounds(x_use_bounds)
3859        if xtitle == " ":
3860            xt.use_bounds(False)
3861        xt.name = "xtitle"
3862        titles.append(xt)
3863        if xtitle_box:
3864            titles.append(xt.box(scale=1.1).use_bounds(x_use_bounds))
3865
3866    if ytitle:
3867        xRot, yRot, zRot = 0, 0, 0
3868        if utils.is_sequence(ytitle_rotation):  # unpck 3 rotations
3869            zRot, yRot, xRot = ytitle_rotation
3870        else:
3871            zRot = ytitle_rotation
3872            if len(ytitle) > 3:
3873                zRot += 90
3874                ytitle_position *= 0.975
3875        if zRot < 0:
3876            zRot += 360  # deal with negative angles
3877
3878        if utils.is_sequence(ytitle_offset):
3879            xoffs, yoffs, zoffs = ytitle_offset
3880        else:
3881            xoffs, yoffs, zoffs = ytitle_offset, 0, 0
3882
3883        if ytitle_justify is not None:
3884            jus = ytitle_justify
3885        else:
3886            jus = "center-right"
3887            if zRot:
3888                if zRot >  24: jus = "bottom-right"
3889                if zRot > 112: jus = "left-bottom"
3890                if zRot > 157: jus = "center-left"
3891                if zRot > 202: jus = "top-left"
3892                if zRot > 292: jus = "top-right"
3893                if zRot > 337: jus = "right-center"
3894
3895        yt = shapes.Text3D(
3896            ytitle,
3897            s=ytitle_size * text_scale * gscale,
3898            font=title_font,
3899            c=ytitle_color,
3900            justify=jus,
3901            depth=title_depth,
3902            italic=ytitle_italic,
3903        )
3904        if ytitle_backface_color is None:
3905            ytitle_backface_color = 1 - np.array(get_color(ytitle_color))
3906            yt.backcolor(ytitle_backface_color)
3907
3908        shift = 0
3909        if ylab:  # this is the last created num label..
3910            lt0, lt1 = ylab.bounds()[0:2]
3911            shift = lt1 - lt0
3912
3913        T = LinearTransform()
3914        T.rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
3915        T.set_position(
3916            [-(xoffs + ytick_length / 2) * dx - shift,
3917            (yoffs + ytitle_position) * dy,
3918            zoffs * dz]
3919        )
3920        T.rotate_y(yaxis_rotation)
3921        T.translate([yshift_along_x*dx, 0, xyshift*dz + yshift_along_z*dz])
3922        yt.apply_transform(T)
3923
3924        yt.use_bounds(y_use_bounds)
3925        if ytitle == " ":
3926            yt.use_bounds(False)
3927        yt.name = "ytitle"
3928        titles.append(yt)
3929        if ytitle_box:
3930            titles.append(yt.box(scale=1.1).use_bounds(y_use_bounds))
3931
3932    if ztitle:
3933        xRot, yRot, zRot = 0, 0, 0
3934        if utils.is_sequence(ztitle_rotation):  # unpck 3 rotations
3935            xRot, yRot, zRot = ztitle_rotation
3936        else:
3937            xRot = ztitle_rotation
3938            if len(ztitle) > 3:
3939                xRot += 90
3940                ztitle_position *= 0.975
3941        if xRot < 0:
3942            xRot += 360  # deal with negative angles
3943
3944        if ztitle_justify is not None:
3945            jus = ztitle_justify
3946        else:
3947            jus = "center-right"
3948            if xRot:
3949                if xRot >  24: jus = "bottom-right"
3950                if xRot > 112: jus = "left-bottom"
3951                if xRot > 157: jus = "center-left"
3952                if xRot > 202: jus = "top-left"
3953                if xRot > 292: jus = "top-right"
3954                if xRot > 337: jus = "right-center"
3955
3956        zt = shapes.Text3D(
3957            ztitle,
3958            s=ztitle_size * text_scale * gscale,
3959            font=title_font,
3960            c=ztitle_color,
3961            justify=jus,
3962            depth=title_depth,
3963            italic=ztitle_italic,
3964        )
3965        if ztitle_backface_color is None:
3966            ztitle_backface_color = 1 - np.array(get_color(ztitle_color))
3967            zt.backcolor(ztitle_backface_color)
3968
3969        angle = np.arctan2(dy, dx) * 57.3
3970        shift = 0
3971        if zlab:  # this is the last created one..
3972            lt0, lt1 = zlab.bounds()[0:2]
3973            shift = lt1 - lt0
3974
3975        T = LinearTransform()
3976        T.rotate_x(90 + zRot).rotate_y(-xRot).rotate_z(angle + yRot)
3977        T.set_position([
3978            -(ztitle_offset + ztick_length / 5) * dx - shift,
3979            -(ztitle_offset + ztick_length / 5) * dy - shift,
3980            ztitle_position * dz]
3981        )
3982        T.rotate_z(zaxis_rotation)
3983        T.translate([zshift_along_x*dx, zxshift*dy + zshift_along_y*dy, 0])
3984        zt.apply_transform(T)
3985
3986        zt.use_bounds(z_use_bounds)
3987        if ztitle == " ":
3988            zt.use_bounds(False)
3989        zt.name = "ztitle"
3990        titles.append(zt)
3991
3992    ################################################### header title
3993    if htitle:
3994        if htitle_font is None:
3995            htitle_font = title_font
3996        if htitle_color is None:
3997            htitle_color = xtitle_color
3998        htit = shapes.Text3D(
3999            htitle,
4000            s=htitle_size * gscale * text_scale,
4001            font=htitle_font,
4002            c=htitle_color,
4003            justify=htitle_justify,
4004            depth=title_depth,
4005            italic=htitle_italic,
4006        )
4007        if htitle_backface_color is None:
4008            htitle_backface_color = 1 - np.array(get_color(htitle_color))
4009            htit.backcolor(htitle_backface_color)
4010        htit.rotate_x(htitle_rotation)
4011        wpos = [htitle_offset[0]*dx, (1 + htitle_offset[1])*dy, htitle_offset[2]*dz]
4012        htit.shift(np.array(wpos) + [0, 0, xyshift*dz])
4013        htit.name = "htitle"
4014        titles.append(htit)
4015
4016    ######
4017    acts = titles + lines + labels + grids + framelines
4018    acts += highlights + majorticks + minorticks + cones
4019    orig = (min_bns[0], min_bns[2], min_bns[4])
4020    for a in acts:
4021        a.shift(orig)
4022        a.actor.PickableOff()
4023        a.properties.LightingOff()
4024    asse = Assembly(acts)
4025    asse.PickableOff()
4026    asse.name = "Axes"
4027    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):
2166class RendererFrame(vtki.vtkActor2D):
2167    """
2168    Add a line around the renderer subwindow.
2169    """
2170
2171    def __init__(self, c="k", alpha=None, lw=None, padding=None):
2172        """
2173        Add a line around the renderer subwindow.
2174
2175        Arguments:
2176            c : (color)
2177                color of the line.
2178            alpha : (float)
2179                opacity.
2180            lw : (int)
2181                line width in pixels.
2182            padding : (int)
2183                padding in pixel units.
2184        """
2185
2186        if lw is None:
2187            lw = settings.renderer_frame_width
2188        if lw == 0:
2189            return None
2190
2191        if alpha is None:
2192            alpha = settings.renderer_frame_alpha
2193
2194        if padding is None:
2195            padding = settings.renderer_frame_padding
2196
2197        c = get_color(c)
2198
2199        ppoints = vtki.vtkPoints()  # Generate the polyline
2200        xy = 1 - padding
2201        psqr = [[padding, padding], [padding, xy], [xy, xy], [xy, padding], [padding, padding]]
2202        for i, pt in enumerate(psqr):
2203            ppoints.InsertPoint(i, pt[0], pt[1], 0)
2204        lines = vtki.vtkCellArray()
2205        lines.InsertNextCell(len(psqr))
2206        for i in range(len(psqr)):
2207            lines.InsertCellPoint(i)
2208        pd = vtki.vtkPolyData()
2209        pd.SetPoints(ppoints)
2210        pd.SetLines(lines)
2211
2212        mapper = vtki.new("PolyDataMapper2D")
2213        mapper.SetInputData(pd)
2214        cs = vtki.new("Coordinate")
2215        cs.SetCoordinateSystemToNormalizedViewport()
2216        mapper.SetTransformCoordinate(cs)
2217
2218        super().__init__()
2219
2220        self.GetPositionCoordinate().SetValue(0, 0)
2221        self.GetPosition2Coordinate().SetValue(1, 1)
2222        self.SetMapper(mapper)
2223        self.GetProperty().SetColor(c)
2224        self.GetProperty().SetOpacity(alpha)
2225        self.GetProperty().SetLineWidth(lw)

Add a line around the renderer subwindow.

RendererFrame(c='k', alpha=None, lw=None, padding=None)
2171    def __init__(self, c="k", alpha=None, lw=None, padding=None):
2172        """
2173        Add a line around the renderer subwindow.
2174
2175        Arguments:
2176            c : (color)
2177                color of the line.
2178            alpha : (float)
2179                opacity.
2180            lw : (int)
2181                line width in pixels.
2182            padding : (int)
2183                padding in pixel units.
2184        """
2185
2186        if lw is None:
2187            lw = settings.renderer_frame_width
2188        if lw == 0:
2189            return None
2190
2191        if alpha is None:
2192            alpha = settings.renderer_frame_alpha
2193
2194        if padding is None:
2195            padding = settings.renderer_frame_padding
2196
2197        c = get_color(c)
2198
2199        ppoints = vtki.vtkPoints()  # Generate the polyline
2200        xy = 1 - padding
2201        psqr = [[padding, padding], [padding, xy], [xy, xy], [xy, padding], [padding, padding]]
2202        for i, pt in enumerate(psqr):
2203            ppoints.InsertPoint(i, pt[0], pt[1], 0)
2204        lines = vtki.vtkCellArray()
2205        lines.InsertNextCell(len(psqr))
2206        for i in range(len(psqr)):
2207            lines.InsertCellPoint(i)
2208        pd = vtki.vtkPolyData()
2209        pd.SetPoints(ppoints)
2210        pd.SetLines(lines)
2211
2212        mapper = vtki.new("PolyDataMapper2D")
2213        mapper.SetInputData(pd)
2214        cs = vtki.new("Coordinate")
2215        cs.SetCoordinateSystemToNormalizedViewport()
2216        mapper.SetTransformCoordinate(cs)
2217
2218        super().__init__()
2219
2220        self.GetPositionCoordinate().SetValue(0, 0)
2221        self.GetPosition2Coordinate().SetValue(1, 1)
2222        self.SetMapper(mapper)
2223        self.GetProperty().SetColor(c)
2224        self.GetProperty().SetOpacity(alpha)
2225        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):
2662class Ruler2D(vtki.vtkAxisActor2D):
2663    """
2664    Create a ruler with tick marks, labels and a title.
2665    """
2666    def __init__(
2667        self,
2668        lw=2,
2669        ticks=True,
2670        labels=False,
2671        c="k",
2672        alpha=1,
2673        title="",
2674        font="Calco",
2675        font_size=24,
2676        bc=None,
2677    ):
2678        """
2679        Create a ruler with tick marks, labels and a title.
2680
2681        Ruler2D is a 2D actor; that is, it is drawn on the overlay
2682        plane and is not occluded by 3D geometry.
2683        To use this class, specify two points defining the start and end
2684        with update_points() as 3D points.
2685
2686        This class decides decides how to create reasonable tick
2687        marks and labels.
2688
2689        Labels are drawn on the "right" side of the axis.
2690        The "right" side is the side of the axis on the right.
2691        The way the labels and title line up with the axis and tick marks
2692        depends on whether the line is considered horizontal or vertical.
2693
2694        Arguments:
2695            lw : (int)
2696                width of the line in pixel units
2697            ticks : (bool)
2698                control if drawing the tick marks
2699            labels : (bool)
2700                control if drawing the numeric labels
2701            c : (color)
2702                color of the object
2703            alpha : (float)
2704                opacity of the object
2705            title : (str)
2706                title of the ruler
2707            font : (str)
2708                font face name. Check [available fonts here](https://vedo.embl.es/fonts).
2709            font_size : (int)
2710                font size
2711            bc : (color)
2712                background color of the title
2713
2714        Example:
2715            ```python
2716            from vedo  import *
2717            plt = Plotter(axes=1, interactive=False)
2718            plt.show(Cube())
2719            rul = Ruler2D()
2720            rul.set_points([0,0,0], [0.5,0.5,0.5])
2721            plt.add(rul)
2722            plt.interactive().close()
2723            ```
2724            ![](https://vedo.embl.es/images/feats/dist_tool.png)
2725        """
2726        super().__init__()
2727
2728        plt = vedo.plotter_instance
2729        if not plt:
2730            vedo.logger.error("Ruler2D need to initialize Plotter first.")
2731            raise RuntimeError()
2732
2733        self.p0 = [0, 0, 0]
2734        self.p1 = [0, 0, 0]
2735        self.distance = 0
2736        self.title = title
2737
2738        prop = self.GetProperty()
2739        tprop = self.GetTitleTextProperty()
2740
2741        self.SetTitle(title)
2742        self.SetNumberOfLabels(9)
2743
2744        if not font:
2745            font = settings.default_font
2746        if font.lower() == "courier":
2747            tprop.SetFontFamilyToCourier()
2748        elif font.lower() == "times":
2749            tprop.SetFontFamilyToTimes()
2750        elif font.lower() == "arial":
2751            tprop.SetFontFamilyToArial()
2752        else:
2753            tprop.SetFontFamily(vtki.VTK_FONT_FILE)
2754            tprop.SetFontFile(utils.get_font_path(font))
2755        tprop.SetFontSize(font_size)
2756        tprop.BoldOff()
2757        tprop.ItalicOff()
2758        tprop.ShadowOff()
2759        tprop.SetColor(get_color(c))
2760        tprop.SetOpacity(alpha)
2761        if bc is not None:
2762            bc = get_color(bc)
2763            tprop.SetBackgroundColor(bc)
2764            tprop.SetBackgroundOpacity(alpha)
2765
2766        lprop = vtki.vtkTextProperty()
2767        lprop.ShallowCopy(tprop)
2768        self.SetLabelTextProperty(lprop)
2769
2770        self.SetLabelFormat("%0.3g")
2771        self.SetTickVisibility(ticks)
2772        self.SetLabelVisibility(labels)
2773        prop.SetLineWidth(lw)
2774        prop.SetColor(get_color(c))
2775
2776        self.renderer = plt.renderer
2777        self.cid = plt.interactor.AddObserver("RenderEvent", self._update_viz, 1.0)
2778
2779    def color(self, c) -> Self:
2780        """Assign a new color."""
2781        c = get_color(c)
2782        self.GetTitleTextProperty().SetColor(c)
2783        self.GetLabelTextProperty().SetColor(c)
2784        self.GetProperty().SetColor(c)
2785        return self
2786
2787    def off(self) -> None:
2788        """Switch off the ruler completely."""
2789        self.renderer.RemoveObserver(self.cid)
2790        self.renderer.RemoveActor(self)
2791
2792    def set_points(self, p0, p1) -> Self:
2793        """Set new values for the ruler start and end points."""
2794        self.p0 = np.asarray(p0)
2795        self.p1 = np.asarray(p1)
2796        self._update_viz(0, 0)
2797        return self
2798
2799    def _update_viz(self, evt, name) -> None:
2800        ren = self.renderer
2801        view_size = np.array(ren.GetSize())
2802
2803        ren.SetWorldPoint(*self.p0, 1)
2804        ren.WorldToDisplay()
2805        disp_point1 = ren.GetDisplayPoint()[:2]
2806        disp_point1 = np.array(disp_point1) / view_size
2807
2808        ren.SetWorldPoint(*self.p1, 1)
2809        ren.WorldToDisplay()
2810        disp_point2 = ren.GetDisplayPoint()[:2]
2811        disp_point2 = np.array(disp_point2) / view_size
2812
2813        self.SetPoint1(*disp_point1)
2814        self.SetPoint2(*disp_point2)
2815        self.distance = np.linalg.norm(self.p1 - self.p0)
2816        self.SetRange(0.0, float(self.distance))
2817        if not self.title:
2818            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)
2666    def __init__(
2667        self,
2668        lw=2,
2669        ticks=True,
2670        labels=False,
2671        c="k",
2672        alpha=1,
2673        title="",
2674        font="Calco",
2675        font_size=24,
2676        bc=None,
2677    ):
2678        """
2679        Create a ruler with tick marks, labels and a title.
2680
2681        Ruler2D is a 2D actor; that is, it is drawn on the overlay
2682        plane and is not occluded by 3D geometry.
2683        To use this class, specify two points defining the start and end
2684        with update_points() as 3D points.
2685
2686        This class decides decides how to create reasonable tick
2687        marks and labels.
2688
2689        Labels are drawn on the "right" side of the axis.
2690        The "right" side is the side of the axis on the right.
2691        The way the labels and title line up with the axis and tick marks
2692        depends on whether the line is considered horizontal or vertical.
2693
2694        Arguments:
2695            lw : (int)
2696                width of the line in pixel units
2697            ticks : (bool)
2698                control if drawing the tick marks
2699            labels : (bool)
2700                control if drawing the numeric labels
2701            c : (color)
2702                color of the object
2703            alpha : (float)
2704                opacity of the object
2705            title : (str)
2706                title of the ruler
2707            font : (str)
2708                font face name. Check [available fonts here](https://vedo.embl.es/fonts).
2709            font_size : (int)
2710                font size
2711            bc : (color)
2712                background color of the title
2713
2714        Example:
2715            ```python
2716            from vedo  import *
2717            plt = Plotter(axes=1, interactive=False)
2718            plt.show(Cube())
2719            rul = Ruler2D()
2720            rul.set_points([0,0,0], [0.5,0.5,0.5])
2721            plt.add(rul)
2722            plt.interactive().close()
2723            ```
2724            ![](https://vedo.embl.es/images/feats/dist_tool.png)
2725        """
2726        super().__init__()
2727
2728        plt = vedo.plotter_instance
2729        if not plt:
2730            vedo.logger.error("Ruler2D need to initialize Plotter first.")
2731            raise RuntimeError()
2732
2733        self.p0 = [0, 0, 0]
2734        self.p1 = [0, 0, 0]
2735        self.distance = 0
2736        self.title = title
2737
2738        prop = self.GetProperty()
2739        tprop = self.GetTitleTextProperty()
2740
2741        self.SetTitle(title)
2742        self.SetNumberOfLabels(9)
2743
2744        if not font:
2745            font = settings.default_font
2746        if font.lower() == "courier":
2747            tprop.SetFontFamilyToCourier()
2748        elif font.lower() == "times":
2749            tprop.SetFontFamilyToTimes()
2750        elif font.lower() == "arial":
2751            tprop.SetFontFamilyToArial()
2752        else:
2753            tprop.SetFontFamily(vtki.VTK_FONT_FILE)
2754            tprop.SetFontFile(utils.get_font_path(font))
2755        tprop.SetFontSize(font_size)
2756        tprop.BoldOff()
2757        tprop.ItalicOff()
2758        tprop.ShadowOff()
2759        tprop.SetColor(get_color(c))
2760        tprop.SetOpacity(alpha)
2761        if bc is not None:
2762            bc = get_color(bc)
2763            tprop.SetBackgroundColor(bc)
2764            tprop.SetBackgroundOpacity(alpha)
2765
2766        lprop = vtki.vtkTextProperty()
2767        lprop.ShallowCopy(tprop)
2768        self.SetLabelTextProperty(lprop)
2769
2770        self.SetLabelFormat("%0.3g")
2771        self.SetTickVisibility(ticks)
2772        self.SetLabelVisibility(labels)
2773        prop.SetLineWidth(lw)
2774        prop.SetColor(get_color(c))
2775
2776        self.renderer = plt.renderer
2777        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:
2779    def color(self, c) -> Self:
2780        """Assign a new color."""
2781        c = get_color(c)
2782        self.GetTitleTextProperty().SetColor(c)
2783        self.GetLabelTextProperty().SetColor(c)
2784        self.GetProperty().SetColor(c)
2785        return self

Assign a new color.

def off(self) -> None:
2787    def off(self) -> None:
2788        """Switch off the ruler completely."""
2789        self.renderer.RemoveObserver(self.cid)
2790        self.renderer.RemoveActor(self)

Switch off the ruler completely.

def set_points(self, p0, p1) -> Self:
2792    def set_points(self, p0, p1) -> Self:
2793        """Set new values for the ruler start and end points."""
2794        self.p0 = np.asarray(p0)
2795        self.p1 = np.asarray(p1)
2796        self._update_viz(0, 0)
2797        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:
2396def Ruler3D(
2397    p1,
2398    p2,
2399    units_scale=1,
2400    label="",
2401    s=None,
2402    font=None,
2403    italic=0,
2404    prefix="",
2405    units="",  # eg.'μm'
2406    c=(0.2, 0.1, 0.1),
2407    alpha=1,
2408    lw=1,
2409    precision=3,
2410    label_rotation=0,
2411    axis_rotation=0,
2412    tick_angle=90,
2413) -> Mesh:
2414    """
2415    Build a 3D ruler to indicate the distance of two points p1 and p2.
2416
2417    Arguments:
2418        label : (str)
2419            alternative fixed label to be shown
2420        units_scale : (float)
2421            factor to scale units (e.g. μm to mm)
2422        s : (float)
2423            size of the label
2424        font : (str)
2425            font face.  Check [available fonts here](https://vedo.embl.es/fonts).
2426        italic : (float)
2427            italicness of the font in the range [0,1]
2428        units : (str)
2429            string to be appended to the numeric value
2430        lw : (int)
2431            line width in pixel units
2432        precision : (int)
2433            nr of significant digits to be shown
2434        label_rotation : (float)
2435            initial rotation of the label around the z-axis
2436        axis_rotation : (float)
2437            initial rotation of the line around the main axis
2438        tick_angle : (float)
2439            initial rotation of the line around the main axis
2440
2441    Examples:
2442        - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2443
2444        ![](https://vedo.embl.es/images/pyplot/goniometer.png)
2445    """
2446
2447    if units_scale != 1.0 and units == "":
2448        raise ValueError(
2449            "When setting 'units_scale' to a value other than 1, "
2450            + "a 'units' arguments must be specified."
2451        )
2452
2453    try:
2454        p1 = p1.pos()
2455    except AttributeError:
2456        pass
2457
2458    try:
2459        p2 = p2.pos()
2460    except AttributeError:
2461        pass
2462
2463    if len(p1) == 2:
2464        p1 = [p1[0], p1[1], 0.0]
2465    if len(p2) == 2:
2466        p2 = [p2[0], p2[1], 0.0]
2467    
2468
2469    p1, p2 = np.asarray(p1), np.asarray(p2)
2470    q1, q2 = [0, 0, 0], [utils.mag(p2 - p1), 0, 0]
2471    q1, q2 = np.array(q1), np.array(q2)
2472    v = q2 - q1
2473    d = utils.mag(v) * units_scale
2474
2475    pos = np.array(p1)
2476    p1 = p1 - pos
2477    p2 = p2 - pos
2478
2479    if s is None:
2480        s = d * 0.02 * (1 / units_scale)
2481
2482    if not label:
2483        label = str(d)
2484        if precision:
2485            label = utils.precision(d, precision)
2486    if prefix:
2487        label = prefix + "~" + label
2488    if units:
2489        label += "~" + units
2490
2491    lb = shapes.Text3D(label, s=s, font=font, italic=italic, justify="center")
2492    if label_rotation:
2493        lb.rotate_z(label_rotation)
2494    lb.pos((q1 + q2) / 2)
2495
2496    x0, x1 = lb.xbounds()
2497    gap = [(x1 - x0) / 2, 0, 0]
2498    pc1 = (v / 2 - gap) * 0.9 + q1
2499    pc2 = q2 - (v / 2 - gap) * 0.9
2500
2501    lc1 = shapes.Line(q1 - v / 50, pc1).lw(lw)
2502    lc2 = shapes.Line(q2 + v / 50, pc2).lw(lw)
2503
2504    zs = np.array([0, d / 50 * (1 / units_scale), 0])
2505    ml1 = shapes.Line(-zs, zs).lw(lw)
2506    ml2 = shapes.Line(-zs, zs).lw(lw)
2507    ml1.rotate_z(tick_angle - 90).pos(q1)
2508    ml2.rotate_z(tick_angle - 90).pos(q2)
2509
2510    c1 = shapes.Circle(q1, r=d / 180 * (1 / units_scale), res=24)
2511    c2 = shapes.Circle(q2, r=d / 180 * (1 / units_scale), res=24)
2512
2513    macts = merge(lb, lc1, lc2, c1, c2, ml1, ml2)
2514    macts.c(c).alpha(alpha)
2515    macts.properties.SetLineWidth(lw)
2516    macts.properties.LightingOff()
2517    macts.actor.UseBoundsOff()
2518    macts.rotate_x(axis_rotation)
2519    macts.reorient(q2 - q1, p2 - p1)
2520    macts.pos(pos)
2521    macts.bc("tomato").pickable(False)
2522    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]:
2525def RulerAxes(
2526    inputobj,
2527    xtitle="",
2528    ytitle="",
2529    ztitle="",
2530    xlabel="",
2531    ylabel="",
2532    zlabel="",
2533    xpadding=0.05,
2534    ypadding=0.04,
2535    zpadding=0,
2536    font="Normografo",
2537    s=None,
2538    italic=0,
2539    units="",
2540    c=(0.2, 0, 0),
2541    alpha=1,
2542    lw=1,
2543    precision=3,
2544    label_rotation=0,
2545    xaxis_rotation=0,
2546    yaxis_rotation=0,
2547    zaxis_rotation=0,
2548    xycross=True,
2549) -> Union[Mesh, None]:
2550    """
2551    A 3D ruler axes to indicate the sizes of the input scene or object.
2552
2553    Arguments:
2554        xtitle : (str)
2555            name of the axis or title
2556        xlabel : (str)
2557            alternative fixed label to be shown instead of the distance
2558        s : (float)
2559            size of the label
2560        font : (str)
2561            font face. Check [available fonts here](https://vedo.embl.es/fonts).
2562        italic : (float)
2563            italicness of the font in the range [0,1]
2564        units : (str)
2565            string to be appended to the numeric value
2566        lw : (int)
2567            line width in pixel units
2568        precision : (int)
2569            nr of significant digits to be shown
2570        label_rotation : (float)
2571            initial rotation of the label around the z-axis
2572        [x,y,z]axis_rotation : (float)
2573            initial rotation of the line around the main axis in degrees
2574        xycross : (bool)
2575            show two back crossing lines in the xy plane
2576
2577    Examples:
2578        - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
2579    """
2580    if utils.is_sequence(inputobj):
2581        x0, x1, y0, y1, z0, z1 = inputobj
2582    else:
2583        x0, x1, y0, y1, z0, z1 = inputobj.bounds()
2584    dx, dy, dz = (y1 - y0) * xpadding, (x1 - x0) * ypadding, (y1 - y0) * zpadding
2585    d = np.sqrt((y1 - y0) ** 2 + (x1 - x0) ** 2 + (z1 - z0) ** 2)
2586
2587    if not d:
2588        return None
2589
2590    if s is None:
2591        s = d / 75
2592
2593    acts, rx, ry = [], None, None
2594    if xtitle is not None and (x1 - x0) / d > 0.1:
2595        rx = Ruler3D(
2596            [x0, y0 - dx, z0],
2597            [x1, y0 - dx, z0],
2598            s=s,
2599            font=font,
2600            precision=precision,
2601            label_rotation=label_rotation,
2602            axis_rotation=xaxis_rotation,
2603            lw=lw,
2604            italic=italic,
2605            prefix=xtitle,
2606            label=xlabel,
2607            units=units,
2608        )
2609        acts.append(rx)
2610
2611    if ytitle is not None and (y1 - y0) / d > 0.1:
2612        ry = Ruler3D(
2613            [x1 + dy, y0, z0],
2614            [x1 + dy, y1, z0],
2615            s=s,
2616            font=font,
2617            precision=precision,
2618            label_rotation=label_rotation,
2619            axis_rotation=yaxis_rotation,
2620            lw=lw,
2621            italic=italic,
2622            prefix=ytitle,
2623            label=ylabel,
2624            units=units,
2625        )
2626        acts.append(ry)
2627
2628    if ztitle is not None and (z1 - z0) / d > 0.1:
2629        rz = Ruler3D(
2630            [x0 - dy, y0 + dz, z0],
2631            [x0 - dy, y0 + dz, z1],
2632            s=s,
2633            font=font,
2634            precision=precision,
2635            label_rotation=label_rotation,
2636            axis_rotation=zaxis_rotation + 90,
2637            lw=lw,
2638            italic=italic,
2639            prefix=ztitle,
2640            label=zlabel,
2641            units=units,
2642        )
2643        acts.append(rz)
2644
2645    if xycross and rx and ry:
2646        lx = shapes.Line([x0, y0, z0], [x0, y1 + dx, z0])
2647        ly = shapes.Line([x0 - dy, y1, z0], [x1, y1, z0])
2648        d = min((x1 - x0), (y1 - y0)) / 200
2649        cxy = shapes.Circle([x0, y1, z0], r=d, res=15)
2650        acts.extend([lx, ly, cxy])
2651
2652    macts = merge(acts)
2653    if not macts:
2654        return None
2655    macts.c(c).alpha(alpha).bc("t")
2656    macts.actor.UseBoundsOff()
2657    macts.actor.PickableOff()
2658    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):
2822class DistanceTool(Group):
2823    """
2824    Create a tool to measure the distance between two clicked points.
2825    """
2826
2827    def __init__(self, plotter=None, c="k", lw=2):
2828        """
2829        Create a tool to measure the distance between two clicked points.
2830
2831        Example:
2832            ```python
2833            from vedo import *
2834            mesh = ParametricShape("RandomHills").c("red5")
2835            plt = Plotter(axes=1)
2836            dtool = DistanceTool()
2837            dtool.on()
2838            plt.show(mesh, dtool)
2839            dtool.off()
2840            ```
2841            ![](https://vedo.embl.es/images/feats/dist_tool.png)
2842        """
2843        super().__init__()
2844
2845        self.p0 = [0, 0, 0]
2846        self.p1 = [0, 0, 0]
2847        self.distance = 0
2848        if plotter is None:
2849            plotter = vedo.plotter_instance
2850        self.plotter = plotter
2851        self.callback = None
2852        self.cid = None
2853        self.color = c
2854        self.linewidth = lw
2855        self.toggle = True
2856        self.ruler = None
2857        self.title = ""
2858
2859    def on(self) -> Self:
2860        """Switch tool on."""
2861        self.cid = self.plotter.add_callback("click", self._onclick)
2862        self.VisibilityOn()
2863        self.plotter.render()
2864        return self
2865
2866    def off(self) -> None:
2867        """Switch tool off."""
2868        self.plotter.remove_callback(self.cid)
2869        self.VisibilityOff()
2870        self.ruler.off()
2871        self.plotter.render()
2872
2873    def _onclick(self, event):
2874        if not event.actor:
2875            return
2876
2877        self.clear()
2878
2879        acts = []
2880        if self.toggle:
2881            self.p0 = event.picked3d
2882            acts.append(Point(self.p0, c=self.color))
2883        else:
2884            self.p1 = event.picked3d
2885            self.distance = np.linalg.norm(self.p1 - self.p0)
2886            acts.append(Point(self.p0, c=self.color))
2887            acts.append(Point(self.p1, c=self.color))
2888            self.ruler = Ruler2D(c=self.color)
2889            self.ruler.set_points(self.p0, self.p1)
2890            acts.append(self.ruler)
2891
2892            if self.callback is not None:
2893                self.callback(event)
2894
2895        self += acts
2896        self.toggle = not self.toggle

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

DistanceTool(plotter=None, c='k', lw=2)
2827    def __init__(self, plotter=None, c="k", lw=2):
2828        """
2829        Create a tool to measure the distance between two clicked points.
2830
2831        Example:
2832            ```python
2833            from vedo import *
2834            mesh = ParametricShape("RandomHills").c("red5")
2835            plt = Plotter(axes=1)
2836            dtool = DistanceTool()
2837            dtool.on()
2838            plt.show(mesh, dtool)
2839            dtool.off()
2840            ```
2841            ![](https://vedo.embl.es/images/feats/dist_tool.png)
2842        """
2843        super().__init__()
2844
2845        self.p0 = [0, 0, 0]
2846        self.p1 = [0, 0, 0]
2847        self.distance = 0
2848        if plotter is None:
2849            plotter = vedo.plotter_instance
2850        self.plotter = plotter
2851        self.callback = None
2852        self.cid = None
2853        self.color = c
2854        self.linewidth = lw
2855        self.toggle = True
2856        self.ruler = None
2857        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:
2859    def on(self) -> Self:
2860        """Switch tool on."""
2861        self.cid = self.plotter.add_callback("click", self._onclick)
2862        self.VisibilityOn()
2863        self.plotter.render()
2864        return self

Switch tool on.

def off(self) -> None:
2866    def off(self) -> None:
2867        """Switch tool off."""
2868        self.plotter.remove_callback(self.cid)
2869        self.VisibilityOff()
2870        self.ruler.off()
2871        self.plotter.render()

Switch tool off.

class SplineTool(vtkmodules.vtkInteractionWidgets.vtkContourWidget):
460class SplineTool(vtki.vtkContourWidget):
461    """
462    Spline tool, draw a spline through a set of points interactively.
463    """
464
465    def __init__(self, points, pc="k", ps=8, lc="r4", ac="g5",
466                 lw=2, alpha=1, closed=False, ontop=True, can_add_nodes=True):
467        """
468        Spline tool, draw a spline through a set of points interactively.
469
470        Arguments:
471            points : (list), Points
472                initial set of points.
473            pc : (str)
474                point color.
475            ps : (int)
476                point size.
477            lc : (str)
478                line color.
479            ac : (str)
480                active point color.
481            lw : (int)
482                line width.
483            alpha : (float)
484                line transparency level.
485            closed : (bool)
486                spline is closed or open.
487            ontop : (bool)
488                show it always on top of other objects.
489            can_add_nodes : (bool)
490                allow to add (or remove) new nodes interactively.
491
492        Examples:
493            - [spline_tool.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/spline_tool.py)
494
495                ![](https://vedo.embl.es/images/basic/spline_tool.png)
496        """
497        super().__init__()
498
499        self.representation = self.GetRepresentation()
500        self.representation.SetAlwaysOnTop(ontop)
501        self.SetAllowNodePicking(can_add_nodes)
502
503
504        self.representation.GetLinesProperty().SetColor(get_color(lc))
505        self.representation.GetLinesProperty().SetLineWidth(lw)
506        self.representation.GetLinesProperty().SetOpacity(alpha)
507        if lw == 0 or alpha == 0:
508            self.representation.GetLinesProperty().SetOpacity(0)
509
510        self.representation.GetActiveProperty().SetLineWidth(lw + 1)
511        self.representation.GetActiveProperty().SetColor(get_color(ac))
512
513        self.representation.GetProperty().SetColor(get_color(pc))
514        self.representation.GetProperty().SetPointSize(ps)
515        self.representation.GetProperty().RenderPointsAsSpheresOn()
516
517        # self.representation.BuildRepresentation() # crashes
518
519        self.SetRepresentation(self.representation)
520
521        if utils.is_sequence(points):
522            self.points = Points(points)
523        else:
524            self.points = points
525
526        self.closed = closed
527
528    @property
529    def interactor(self):
530        """Return the current interactor."""
531        return self.GetInteractor()
532    
533    @interactor.setter
534    def interactor(self, iren):
535        """Set the current interactor."""
536        self.SetInteractor(iren)
537
538    def add(self, pt) -> "SplineTool":
539        """
540        Add one point at a specified position in space if 3D,
541        or 2D screen-display position if 2D.
542        """
543        if len(pt) == 2:
544            self.representation.AddNodeAtDisplayPosition(int(pt[0]), int(pt[1]))
545        else:
546            self.representation.AddNodeAtWorldPosition(pt)
547        return self
548    
549    def add_observer(self, event, func, priority=1) -> int:
550        """Add an observer to the widget."""
551        event = utils.get_vtk_name_event(event)
552        cid = self.AddObserver(event, func, priority)
553        return cid
554
555    def remove(self, i: int) -> "SplineTool":
556        """Remove specific node by its index"""
557        self.representation.DeleteNthNode(i)
558        return self
559
560    def on(self) -> "SplineTool":
561        """Activate/Enable the tool"""
562        self.On()
563        self.Render()
564        return self
565
566    def off(self) -> "SplineTool":
567        """Disactivate/Disable the tool"""
568        self.Off()
569        self.Render()
570        return self
571
572    def render(self) -> "SplineTool":
573        """Render the spline"""
574        self.Render()
575        return self
576
577    # def bounds(self) -> np.ndarray:
578    #     """Retrieve the bounding box of the spline as [x0,x1, y0,y1, z0,z1]"""
579    #     return np.array(self.GetBounds())
580
581    def spline(self) -> vedo.Line:
582        """Return the vedo.Spline object."""
583        self.representation.SetClosedLoop(self.closed)
584        self.representation.BuildRepresentation()
585        pd = self.representation.GetContourRepresentationAsPolyData()
586        ln = vedo.Line(pd, lw=2, c="k")
587        return ln
588
589    def nodes(self, onscreen=False) -> np.ndarray:
590        """Return the current position in space (or on 2D screen-display) of the spline nodes."""
591        n = self.representation.GetNumberOfNodes()
592        pts = []
593        for i in range(n):
594            p = [0.0, 0.0, 0.0]
595            if onscreen:
596                self.representation.GetNthNodeDisplayPosition(i, p)
597            else:
598                self.representation.GetNthNodeWorldPosition(i, p)
599            pts.append(p)
600        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)
465    def __init__(self, points, pc="k", ps=8, lc="r4", ac="g5",
466                 lw=2, alpha=1, closed=False, ontop=True, can_add_nodes=True):
467        """
468        Spline tool, draw a spline through a set of points interactively.
469
470        Arguments:
471            points : (list), Points
472                initial set of points.
473            pc : (str)
474                point color.
475            ps : (int)
476                point size.
477            lc : (str)
478                line color.
479            ac : (str)
480                active point color.
481            lw : (int)
482                line width.
483            alpha : (float)
484                line transparency level.
485            closed : (bool)
486                spline is closed or open.
487            ontop : (bool)
488                show it always on top of other objects.
489            can_add_nodes : (bool)
490                allow to add (or remove) new nodes interactively.
491
492        Examples:
493            - [spline_tool.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/spline_tool.py)
494
495                ![](https://vedo.embl.es/images/basic/spline_tool.png)
496        """
497        super().__init__()
498
499        self.representation = self.GetRepresentation()
500        self.representation.SetAlwaysOnTop(ontop)
501        self.SetAllowNodePicking(can_add_nodes)
502
503
504        self.representation.GetLinesProperty().SetColor(get_color(lc))
505        self.representation.GetLinesProperty().SetLineWidth(lw)
506        self.representation.GetLinesProperty().SetOpacity(alpha)
507        if lw == 0 or alpha == 0:
508            self.representation.GetLinesProperty().SetOpacity(0)
509
510        self.representation.GetActiveProperty().SetLineWidth(lw + 1)
511        self.representation.GetActiveProperty().SetColor(get_color(ac))
512
513        self.representation.GetProperty().SetColor(get_color(pc))
514        self.representation.GetProperty().SetPointSize(ps)
515        self.representation.GetProperty().RenderPointsAsSpheresOn()
516
517        # self.representation.BuildRepresentation() # crashes
518
519        self.SetRepresentation(self.representation)
520
521        if utils.is_sequence(points):
522            self.points = Points(points)
523        else:
524            self.points = points
525
526        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
528    @property
529    def interactor(self):
530        """Return the current interactor."""
531        return self.GetInteractor()

Return the current interactor.

def add(self, pt) -> SplineTool:
538    def add(self, pt) -> "SplineTool":
539        """
540        Add one point at a specified position in space if 3D,
541        or 2D screen-display position if 2D.
542        """
543        if len(pt) == 2:
544            self.representation.AddNodeAtDisplayPosition(int(pt[0]), int(pt[1]))
545        else:
546            self.representation.AddNodeAtWorldPosition(pt)
547        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:
549    def add_observer(self, event, func, priority=1) -> int:
550        """Add an observer to the widget."""
551        event = utils.get_vtk_name_event(event)
552        cid = self.AddObserver(event, func, priority)
553        return cid

Add an observer to the widget.

def remove(self, i: int) -> SplineTool:
555    def remove(self, i: int) -> "SplineTool":
556        """Remove specific node by its index"""
557        self.representation.DeleteNthNode(i)
558        return self

Remove specific node by its index

def on(self) -> SplineTool:
560    def on(self) -> "SplineTool":
561        """Activate/Enable the tool"""
562        self.On()
563        self.Render()
564        return self

Activate/Enable the tool

def off(self) -> SplineTool:
566    def off(self) -> "SplineTool":
567        """Disactivate/Disable the tool"""
568        self.Off()
569        self.Render()
570        return self

Disactivate/Disable the tool

def render(self) -> SplineTool:
572    def render(self) -> "SplineTool":
573        """Render the spline"""
574        self.Render()
575        return self

Render the spline

def spline(self) -> vedo.shapes.Line:
581    def spline(self) -> vedo.Line:
582        """Return the vedo.Spline object."""
583        self.representation.SetClosedLoop(self.closed)
584        self.representation.BuildRepresentation()
585        pd = self.representation.GetContourRepresentationAsPolyData()
586        ln = vedo.Line(pd, lw=2, c="k")
587        return ln

Return the vedo.Spline object.

def nodes(self, onscreen=False) -> numpy.ndarray:
589    def nodes(self, onscreen=False) -> np.ndarray:
590        """Return the current position in space (or on 2D screen-display) of the spline nodes."""
591        n = self.representation.GetNumberOfNodes()
592        pts = []
593        for i in range(n):
594            p = [0.0, 0.0, 0.0]
595            if onscreen:
596                self.representation.GetNthNodeDisplayPosition(i, p)
597            else:
598                self.representation.GetNthNodeWorldPosition(i, p)
599            pts.append(p)
600        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):
679def Goniometer(
680    p1,
681    p2,
682    p3,
683    font="",
684    arc_size=0.4,
685    s=1,
686    italic=0,
687    rotation=0,
688    prefix="",
689    lc="k2",
690    c="white",
691    alpha=1,
692    lw=2,
693    precision=3,
694):
695    """
696    Build a graphical goniometer to measure the angle formed by 3 points in space.
697
698    Arguments:
699        p1 : (list)
700            first point 3D coordinates.
701        p2 : (list)
702            the vertex point.
703        p3 : (list)
704            the last point defining the angle.
705        font : (str)
706            Font face. Check [available fonts here](https://vedo.embl.es/fonts).
707        arc_size : (float)
708            dimension of the arc wrt the smallest axis.
709        s : (float)
710            size of the text.
711        italic : (float, bool)
712            italic text.
713        rotation : (float)
714            rotation of text in degrees.
715        prefix : (str)
716            append this string to the numeric value of the angle.
717        lc : (list)
718            color of the goniometer lines.
719        c : (str)
720            color of the goniometer angle filling. Set alpha=0 to remove it.
721        alpha : (float)
722            transparency level.
723        lw : (float)
724            line width.
725        precision : (int)
726            number of significant digits.
727
728    Examples:
729        - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
730
731            ![](https://vedo.embl.es/images/pyplot/goniometer.png)
732    """
733    if isinstance(p1, Points): p1 = p1.pos()
734    if isinstance(p2, Points): p2 = p2.pos()
735    if isinstance(p3, Points): p3 = p3.pos()
736    if len(p1)==2: p1=[p1[0], p1[1], 0.0]
737    if len(p2)==2: p2=[p2[0], p2[1], 0.0]
738    if len(p3)==2: p3=[p3[0], p3[1], 0.0]
739    p1, p2, p3 = np.array(p1), np.array(p2), np.array(p3)
740
741    acts = []
742    ln = shapes.Line([p1, p2, p3], lw=lw, c=lc)
743    acts.append(ln)
744
745    va = utils.versor(p1 - p2)
746    vb = utils.versor(p3 - p2)
747    r = min(utils.mag(p3 - p2), utils.mag(p1 - p2)) * arc_size
748    ptsarc = []
749    res = 120
750    imed = int(res / 2)
751    for i in range(res + 1):
752        vi = utils.versor(vb * i / res + va * (res - i) / res)
753        if i == imed:
754            vc = np.array(vi)
755        ptsarc.append(p2 + vi * r)
756    arc = shapes.Line(ptsarc).lw(lw).c(lc)
757    acts.append(arc)
758
759    angle = np.arccos(np.dot(va, vb)) * 180 / np.pi
760
761    lb = shapes.Text3D(
762        prefix + utils.precision(angle, precision) + "º",
763        s=r/12 * s,
764        font=font,
765        italic=italic,
766        justify="center",
767    )
768    cr = np.cross(va, vb)
769    lb.reorient([0,0,1], cr * np.sign(cr[2]), rotation=rotation, xyplane=False)
770    lb.pos(p2 + vc * r / 1.75)
771    lb.c(c).bc("tomato").lighting("off")
772    acts.append(lb)
773
774    if alpha > 0:
775        pts = [p2] + arc.vertices.tolist() + [p2]
776        msh = Mesh([pts, [list(range(arc.npoints + 2))]], c=lc, alpha=alpha)
777        msh.lighting("off")
778        msh.triangulate()
779        msh.shift(0, 0, -r / 10000)  # to resolve 2d conflicts..
780        acts.append(msh)
781
782    asse = Assembly(acts)
783    asse.name = "Goniometer"
784    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):
346class Button(vedo.shapes.Text2D):
347    """
348    Build a Button object.
349    """
350    def __init__(
351            self,
352            fnc=None,
353            states=("Button"),
354            c=("white"),
355            bc=("green4"),
356            pos=(0.7, 0.1),
357            size=24,
358            font="Courier",
359            bold=True,
360            italic=False,
361            alpha=1,
362            angle=0,
363        ):
364        """
365        Build a Button object to be shown in the rendering window.
366
367        Arguments:
368            fnc : (function)
369                external function to be called by the widget
370            states : (list)
371                the list of possible states, eg. ['On', 'Off']
372            c : (list)
373                the list of colors for each state eg. ['red3', 'green5']
374            bc : (list)
375                the list of background colors for each state
376            pos : (list, str)
377                2D position in pixels from left-bottom corner
378            size : (int)
379                size of button font
380            font : (str)
381                font type
382            bold : (bool)
383                set bold font face
384            italic : (bool)
385                italic font face
386            alpha : (float)
387                opacity level
388            angle : (float)
389                anticlockwise rotation in degrees
390
391        Examples:
392            - [buttons1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons1.py)
393            - [buttons2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons2.py)
394
395                ![](https://user-images.githubusercontent.com/32848391/50738870-c0fe2500-11d8-11e9-9b78-92754f5c5968.jpg)
396
397            - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py)
398
399                ![](https://vedo.embl.es/images/advanced/timer_callback1.jpg)
400        """
401        super().__init__()
402
403        self.status_idx = 0
404
405        self.spacer = " "
406
407        self.states = states
408
409        if not utils.is_sequence(c):
410            c = [c]
411        self.colors = c
412
413        if not utils.is_sequence(bc):
414            bc = [bc]
415        self.bcolors = bc
416
417        assert len(c) == len(bc), "in Button color number mismatch!"
418
419        self.function = fnc
420        self.function_id = None
421
422        self.status(0)
423
424        if font == "courier":
425            font = font.capitalize()
426        self.font(font).bold(bold).italic(italic)
427
428        self.alpha(alpha).angle(angle)
429        self.size(size/20)
430        self.pos(pos, "center")
431        self.PickableOn()
432
433
434    def status(self, s=None) -> "Button":
435        """
436        Set/Get the status of the button.
437        """
438        if s is None:
439            return self.states[self.status_idx]
440
441        if isinstance(s, str):
442            s = self.states.index(s)
443        self.status_idx = s
444        self.text(self.spacer + self.states[s] + self.spacer)
445        s = s % len(self.bcolors)
446        self.color(self.colors[s])
447        self.background(self.bcolors[s])
448        return self
449
450    def switch(self) -> "Button":
451        """
452        Change/cycle button status to the next defined status in states list.
453        """
454        self.status_idx = (self.status_idx + 1) % len(self.states)
455        self.status(self.status_idx)
456        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)
350    def __init__(
351            self,
352            fnc=None,
353            states=("Button"),
354            c=("white"),
355            bc=("green4"),
356            pos=(0.7, 0.1),
357            size=24,
358            font="Courier",
359            bold=True,
360            italic=False,
361            alpha=1,
362            angle=0,
363        ):
364        """
365        Build a Button object to be shown in the rendering window.
366
367        Arguments:
368            fnc : (function)
369                external function to be called by the widget
370            states : (list)
371                the list of possible states, eg. ['On', 'Off']
372            c : (list)
373                the list of colors for each state eg. ['red3', 'green5']
374            bc : (list)
375                the list of background colors for each state
376            pos : (list, str)
377                2D position in pixels from left-bottom corner
378            size : (int)
379                size of button font
380            font : (str)
381                font type
382            bold : (bool)
383                set bold font face
384            italic : (bool)
385                italic font face
386            alpha : (float)
387                opacity level
388            angle : (float)
389                anticlockwise rotation in degrees
390
391        Examples:
392            - [buttons1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons1.py)
393            - [buttons2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/buttons2.py)
394
395                ![](https://user-images.githubusercontent.com/32848391/50738870-c0fe2500-11d8-11e9-9b78-92754f5c5968.jpg)
396
397            - [timer_callback2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/timer_callback2.py)
398
399                ![](https://vedo.embl.es/images/advanced/timer_callback1.jpg)
400        """
401        super().__init__()
402
403        self.status_idx = 0
404
405        self.spacer = " "
406
407        self.states = states
408
409        if not utils.is_sequence(c):
410            c = [c]
411        self.colors = c
412
413        if not utils.is_sequence(bc):
414            bc = [bc]
415        self.bcolors = bc
416
417        assert len(c) == len(bc), "in Button color number mismatch!"
418
419        self.function = fnc
420        self.function_id = None
421
422        self.status(0)
423
424        if font == "courier":
425            font = font.capitalize()
426        self.font(font).bold(bold).italic(italic)
427
428        self.alpha(alpha).angle(angle)
429        self.size(size/20)
430        self.pos(pos, "center")
431        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:
434    def status(self, s=None) -> "Button":
435        """
436        Set/Get the status of the button.
437        """
438        if s is None:
439            return self.states[self.status_idx]
440
441        if isinstance(s, str):
442            s = self.states.index(s)
443        self.status_idx = s
444        self.text(self.spacer + self.states[s] + self.spacer)
445        s = s % len(self.bcolors)
446        self.color(self.colors[s])
447        self.background(self.bcolors[s])
448        return self

Set/Get the status of the button.

def switch(self) -> Button:
450    def switch(self) -> "Button":
451        """
452        Change/cycle button status to the next defined status in states list.
453        """
454        self.status_idx = (self.status_idx + 1) % len(self.states)
455        self.status(self.status_idx)
456        return self

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

class Flagpost(vtkmodules.vtkRenderingCore.vtkFlagpoleLabel):
 54class Flagpost(vtki.vtkFlagpoleLabel):
 55    """
 56    Create a flag post style element to describe an object.
 57    """
 58
 59    def __init__(
 60        self,
 61        txt="",
 62        base=(0, 0, 0),
 63        top=(0, 0, 1),
 64        s=1,
 65        c="k9",
 66        bc="k1",
 67        alpha=1,
 68        lw=0,
 69        font="Calco",
 70        justify="center-left",
 71        vspacing=1,
 72    ):
 73        """
 74        Create a flag post style element to describe an object.
 75
 76        Arguments:
 77            txt : (str)
 78                Text to display. The default is the filename or the object name.
 79            base : (list)
 80                position of the flag anchor point.
 81            top : (list)
 82                a 3D displacement or offset.
 83            s : (float)
 84                size of the text to be shown
 85            c : (list)
 86                color of text and line
 87            bc : (list)
 88                color of the flag background
 89            alpha : (float)
 90                opacity of text and box.
 91            lw : (int)
 92                line with of box frame. The default is 0.
 93            font : (str)
 94                font name. Use a monospace font for better rendering. The default is "Calco".
 95                Type `vedo -r fonts` for a font demo.
 96                Check [available fonts here](https://vedo.embl.es/fonts).
 97            justify : (str)
 98                internal text justification. The default is "center-left".
 99            vspacing : (float)
100                vertical spacing between lines.
101
102        Examples:
103            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py)
104
105            ![](https://vedo.embl.es/images/other/flag_labels2.png)
106        """
107
108        super().__init__()
109
110        base = utils.make3d(base)
111        top = utils.make3d(top)
112
113        self.SetBasePosition(*base)
114        self.SetTopPosition(*top)
115
116        self.SetFlagSize(s)
117        self.SetInput(txt)
118        self.PickableOff()
119
120        self.GetProperty().LightingOff()
121        self.GetProperty().SetLineWidth(lw + 1)
122
123        prop = self.GetTextProperty()
124        if bc is not None:
125            prop.SetBackgroundColor(get_color(bc))
126
127        prop.SetOpacity(alpha)
128        prop.SetBackgroundOpacity(alpha)
129        if bc is not None and len(bc) == 4:
130            prop.SetBackgroundRGBA(alpha)
131
132        c = get_color(c)
133        prop.SetColor(c)
134        self.GetProperty().SetColor(c)
135
136        prop.SetFrame(bool(lw))
137        prop.SetFrameWidth(lw)
138        prop.SetFrameColor(prop.GetColor())
139
140        prop.SetFontFamily(vtki.VTK_FONT_FILE)
141        fl = utils.get_font_path(font)
142        prop.SetFontFile(fl)
143        prop.ShadowOff()
144        prop.BoldOff()
145        prop.SetOpacity(alpha)
146        prop.SetJustificationToLeft()
147        if "top" in justify:
148            prop.SetVerticalJustificationToTop()
149        if "bottom" in justify:
150            prop.SetVerticalJustificationToBottom()
151        if "cent" in justify:
152            prop.SetVerticalJustificationToCentered()
153            prop.SetJustificationToCentered()
154        if "left" in justify:
155            prop.SetJustificationToLeft()
156        if "right" in justify:
157            prop.SetJustificationToRight()
158        prop.SetLineSpacing(vspacing * 1.2)
159        self.SetUseBounds(False)
160
161    def text(self, value: str) -> Self:
162        self.SetInput(value)
163        return self
164
165    def on(self) -> Self:
166        self.VisibilityOn()
167        return self
168
169    def off(self) -> Self:
170        self.VisibilityOff()
171        return self
172
173    def toggle(self) -> Self:
174        self.SetVisibility(not self.GetVisibility())
175        return self
176
177    def use_bounds(self, value=True) -> Self:
178        self.SetUseBounds(value)
179        return self
180
181    def color(self, c) -> Self:
182        c = get_color(c)
183        self.GetTextProperty().SetColor(c)
184        self.GetProperty().SetColor(c)
185        return self
186
187    def pos(self, p) -> Self:
188        p = np.asarray(p)
189        self.top = self.top - self.base + p
190        self.base = p
191        return self
192
193    @property
194    def base(self) -> np.ndarray:
195        return np.array(self.GetBasePosition())
196
197    @base.setter
198    def base(self, value):
199        self.SetBasePosition(*value)
200
201    @property
202    def top(self) -> np.ndarray:
203        return np.array(self.GetTopPosition())
204
205    @top.setter
206    def top(self, value):
207        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)
 59    def __init__(
 60        self,
 61        txt="",
 62        base=(0, 0, 0),
 63        top=(0, 0, 1),
 64        s=1,
 65        c="k9",
 66        bc="k1",
 67        alpha=1,
 68        lw=0,
 69        font="Calco",
 70        justify="center-left",
 71        vspacing=1,
 72    ):
 73        """
 74        Create a flag post style element to describe an object.
 75
 76        Arguments:
 77            txt : (str)
 78                Text to display. The default is the filename or the object name.
 79            base : (list)
 80                position of the flag anchor point.
 81            top : (list)
 82                a 3D displacement or offset.
 83            s : (float)
 84                size of the text to be shown
 85            c : (list)
 86                color of text and line
 87            bc : (list)
 88                color of the flag background
 89            alpha : (float)
 90                opacity of text and box.
 91            lw : (int)
 92                line with of box frame. The default is 0.
 93            font : (str)
 94                font name. Use a monospace font for better rendering. The default is "Calco".
 95                Type `vedo -r fonts` for a font demo.
 96                Check [available fonts here](https://vedo.embl.es/fonts).
 97            justify : (str)
 98                internal text justification. The default is "center-left".
 99            vspacing : (float)
100                vertical spacing between lines.
101
102        Examples:
103            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py)
104
105            ![](https://vedo.embl.es/images/other/flag_labels2.png)
106        """
107
108        super().__init__()
109
110        base = utils.make3d(base)
111        top = utils.make3d(top)
112
113        self.SetBasePosition(*base)
114        self.SetTopPosition(*top)
115
116        self.SetFlagSize(s)
117        self.SetInput(txt)
118        self.PickableOff()
119
120        self.GetProperty().LightingOff()
121        self.GetProperty().SetLineWidth(lw + 1)
122
123        prop = self.GetTextProperty()
124        if bc is not None:
125            prop.SetBackgroundColor(get_color(bc))
126
127        prop.SetOpacity(alpha)
128        prop.SetBackgroundOpacity(alpha)
129        if bc is not None and len(bc) == 4:
130            prop.SetBackgroundRGBA(alpha)
131
132        c = get_color(c)
133        prop.SetColor(c)
134        self.GetProperty().SetColor(c)
135
136        prop.SetFrame(bool(lw))
137        prop.SetFrameWidth(lw)
138        prop.SetFrameColor(prop.GetColor())
139
140        prop.SetFontFamily(vtki.VTK_FONT_FILE)
141        fl = utils.get_font_path(font)
142        prop.SetFontFile(fl)
143        prop.ShadowOff()
144        prop.BoldOff()
145        prop.SetOpacity(alpha)
146        prop.SetJustificationToLeft()
147        if "top" in justify:
148            prop.SetVerticalJustificationToTop()
149        if "bottom" in justify:
150            prop.SetVerticalJustificationToBottom()
151        if "cent" in justify:
152            prop.SetVerticalJustificationToCentered()
153            prop.SetJustificationToCentered()
154        if "left" in justify:
155            prop.SetJustificationToLeft()
156        if "right" in justify:
157            prop.SetJustificationToRight()
158        prop.SetLineSpacing(vspacing * 1.2)
159        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:
161    def text(self, value: str) -> Self:
162        self.SetInput(value)
163        return self
def on(self) -> Self:
165    def on(self) -> Self:
166        self.VisibilityOn()
167        return self
def off(self) -> Self:
169    def off(self) -> Self:
170        self.VisibilityOff()
171        return self
def toggle(self) -> Self:
173    def toggle(self) -> Self:
174        self.SetVisibility(not self.GetVisibility())
175        return self
def use_bounds(self, value=True) -> Self:
177    def use_bounds(self, value=True) -> Self:
178        self.SetUseBounds(value)
179        return self
def color(self, c) -> Self:
181    def color(self, c) -> Self:
182        c = get_color(c)
183        self.GetTextProperty().SetColor(c)
184        self.GetProperty().SetColor(c)
185        return self
def pos(self, p) -> Self:
187    def pos(self, p) -> Self:
188        p = np.asarray(p)
189        self.top = self.top - self.base + p
190        self.base = p
191        return self
class ProgressBarWidget(vtkmodules.vtkRenderingCore.vtkActor2D):
2228class ProgressBarWidget(vtki.vtkActor2D):
2229    """
2230    Add a progress bar in the rendering window.
2231    """
2232    def __init__(self, n=None, c="blue5", alpha=0.8, lw=10, autohide=True):
2233        """
2234        Add a progress bar window.
2235
2236        Arguments:
2237            n : (int)
2238                number of iterations.
2239                If None, you need to call `update(fraction)` manually.
2240            c : (color)
2241                color of the line.
2242            alpha : (float)
2243                opacity of the line.
2244            lw : (int)
2245                line width in pixels.
2246            autohide : (bool)
2247                if True, hide the progress bar when completed.
2248        """
2249        self.n = 0
2250        self.iterations = n
2251        self.autohide = autohide
2252
2253        ppoints = vtki.vtkPoints()  # Generate the line
2254        psqr = [[0, 0, 0], [1, 0, 0]]
2255        for i, pt in enumerate(psqr):
2256            ppoints.InsertPoint(i, *pt)
2257        lines = vtki.vtkCellArray()
2258        lines.InsertNextCell(len(psqr))
2259        for i in range(len(psqr)):
2260            lines.InsertCellPoint(i)
2261        pd = vtki.vtkPolyData()
2262        pd.SetPoints(ppoints)
2263        pd.SetLines(lines)
2264        self.dataset = pd
2265
2266        mapper = vtki.new("PolyDataMapper2D")
2267        mapper.SetInputData(pd)
2268        cs = vtki.vtkCoordinate()
2269        cs.SetCoordinateSystemToNormalizedViewport()
2270        mapper.SetTransformCoordinate(cs)
2271
2272        super().__init__()
2273
2274        self.SetMapper(mapper)
2275        self.GetProperty().SetOpacity(alpha)
2276        self.GetProperty().SetColor(get_color(c))
2277        self.GetProperty().SetLineWidth(lw*2)
2278
2279
2280    def lw(self, value: int) -> Self:
2281        """Set width."""
2282        self.GetProperty().SetLineWidth(value*2)
2283        return self
2284
2285    def c(self, color) -> Self:
2286        """Set color."""
2287        c = get_color(color)
2288        self.GetProperty().SetColor(c)
2289        return self
2290
2291    def alpha(self, value) -> Self:
2292        """Set opacity."""
2293        self.GetProperty().SetOpacity(value)
2294        return self
2295
2296    def update(self, fraction=None) -> Self:
2297        """Update progress bar to fraction of the window width."""
2298        if fraction is None:
2299            if self.iterations is None:
2300                vedo.printc("Error in ProgressBarWindow: must specify iterations", c='r')
2301                return self
2302            self.n += 1
2303            fraction = self.n / self.iterations
2304
2305        if fraction >= 1 and self.autohide:
2306            fraction = 0
2307
2308        psqr = [[0, 0, 0], [fraction, 0, 0]]
2309        vpts = utils.numpy2vtk(psqr, dtype=np.float32)
2310        self.dataset.GetPoints().SetData(vpts)
2311        return self
2312
2313    def reset(self):
2314        """Reset progress bar."""
2315        self.n = 0
2316        self.update(0)
2317        return self

Add a progress bar in the rendering window.

ProgressBarWidget(n=None, c='blue5', alpha=0.8, lw=10, autohide=True)
2232    def __init__(self, n=None, c="blue5", alpha=0.8, lw=10, autohide=True):
2233        """
2234        Add a progress bar window.
2235
2236        Arguments:
2237            n : (int)
2238                number of iterations.
2239                If None, you need to call `update(fraction)` manually.
2240            c : (color)
2241                color of the line.
2242            alpha : (float)
2243                opacity of the line.
2244            lw : (int)
2245                line width in pixels.
2246            autohide : (bool)
2247                if True, hide the progress bar when completed.
2248        """
2249        self.n = 0
2250        self.iterations = n
2251        self.autohide = autohide
2252
2253        ppoints = vtki.vtkPoints()  # Generate the line
2254        psqr = [[0, 0, 0], [1, 0, 0]]
2255        for i, pt in enumerate(psqr):
2256            ppoints.InsertPoint(i, *pt)
2257        lines = vtki.vtkCellArray()
2258        lines.InsertNextCell(len(psqr))
2259        for i in range(len(psqr)):
2260            lines.InsertCellPoint(i)
2261        pd = vtki.vtkPolyData()
2262        pd.SetPoints(ppoints)
2263        pd.SetLines(lines)
2264        self.dataset = pd
2265
2266        mapper = vtki.new("PolyDataMapper2D")
2267        mapper.SetInputData(pd)
2268        cs = vtki.vtkCoordinate()
2269        cs.SetCoordinateSystemToNormalizedViewport()
2270        mapper.SetTransformCoordinate(cs)
2271
2272        super().__init__()
2273
2274        self.SetMapper(mapper)
2275        self.GetProperty().SetOpacity(alpha)
2276        self.GetProperty().SetColor(get_color(c))
2277        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:
2280    def lw(self, value: int) -> Self:
2281        """Set width."""
2282        self.GetProperty().SetLineWidth(value*2)
2283        return self

Set width.

def c(self, color) -> Self:
2285    def c(self, color) -> Self:
2286        """Set color."""
2287        c = get_color(color)
2288        self.GetProperty().SetColor(c)
2289        return self

Set color.

def alpha(self, value) -> Self:
2291    def alpha(self, value) -> Self:
2292        """Set opacity."""
2293        self.GetProperty().SetOpacity(value)
2294        return self

Set opacity.

def update(self, fraction=None) -> Self:
2296    def update(self, fraction=None) -> Self:
2297        """Update progress bar to fraction of the window width."""
2298        if fraction is None:
2299            if self.iterations is None:
2300                vedo.printc("Error in ProgressBarWindow: must specify iterations", c='r')
2301                return self
2302            self.n += 1
2303            fraction = self.n / self.iterations
2304
2305        if fraction >= 1 and self.autohide:
2306            fraction = 0
2307
2308        psqr = [[0, 0, 0], [fraction, 0, 0]]
2309        vpts = utils.numpy2vtk(psqr, dtype=np.float32)
2310        self.dataset.GetPoints().SetData(vpts)
2311        return self

Update progress bar to fraction of the window width.

def reset(self):
2313    def reset(self):
2314        """Reset progress bar."""
2315        self.n = 0
2316        self.update(0)
2317        return self

Reset progress bar.

class BoxCutter(vtkmodules.vtkInteractionWidgets.vtkBoxWidget, BaseCutter):
1909class BoxCutter(vtki.vtkBoxWidget, BaseCutter):
1910    """
1911    Create a box widget to cut away parts of a Mesh.
1912    """
1913    def __init__(
1914            self,
1915            mesh,
1916            invert=False,
1917            can_rotate=True,
1918            can_translate=True,
1919            can_scale=True,
1920            initial_bounds=(),
1921            padding=0.025,
1922            delayed=False,
1923            c=(0.25, 0.25, 0.25),
1924            alpha=0.05,
1925    ):
1926        """
1927        Create a box widget to cut away parts of a Mesh.
1928
1929        Arguments:
1930            mesh : (Mesh)
1931                the input mesh
1932            invert : (bool)
1933                invert the clipping plane
1934            can_rotate : (bool)
1935                enable rotation of the widget
1936            can_translate : (bool)
1937                enable translation of the widget
1938            can_scale : (bool)
1939                enable scaling of the widget
1940            initial_bounds : (list)
1941                initial bounds of the box widget
1942            padding : (float)
1943                padding space around the input mesh
1944            delayed : (bool)
1945                if True the callback is delayed until
1946                when the mouse button is released (useful for large meshes)
1947            c : (color)
1948                color of the box cutter widget
1949            alpha : (float)
1950                transparency of the cut-off part of the input mesh
1951        """
1952        super().__init__()
1953
1954        self.mesh = mesh
1955        self.remnant = Mesh()
1956        self.remnant.name = mesh.name + "Remnant"
1957        self.remnant.pickable(False)
1958
1959        self._alpha = alpha
1960        self._keypress_id = None
1961        self._init_bounds = initial_bounds
1962        if len(self._init_bounds) == 0:
1963            self._init_bounds = mesh.bounds()
1964        else:
1965            self._init_bounds = initial_bounds
1966
1967        self._implicit_func = vtki.new("Planes")
1968        self._implicit_func.SetBounds(self._init_bounds)
1969
1970        poly = mesh.dataset
1971        self.clipper = vtki.new("ClipPolyData")
1972        self.clipper.GenerateClipScalarsOff()
1973        self.clipper.SetInputData(poly)
1974        self.clipper.SetClipFunction(self._implicit_func)
1975        self.clipper.SetInsideOut(not invert)
1976        self.clipper.GenerateClippedOutputOn()
1977        self.clipper.Update()
1978
1979        self.widget = vtki.vtkBoxWidget()
1980
1981        self.widget.SetRotationEnabled(can_rotate)
1982        self.widget.SetTranslationEnabled(can_translate)
1983        self.widget.SetScalingEnabled(can_scale)
1984
1985        self.widget.OutlineCursorWiresOn()
1986        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
1987        self.widget.GetSelectedHandleProperty().SetColor(get_color("red5"))
1988
1989        self.widget.GetOutlineProperty().SetColor(c)
1990        self.widget.GetOutlineProperty().SetOpacity(1)
1991        self.widget.GetOutlineProperty().SetLineWidth(1)
1992        self.widget.GetOutlineProperty().LightingOff()
1993
1994        self.widget.GetSelectedFaceProperty().LightingOff()
1995        self.widget.GetSelectedFaceProperty().SetOpacity(0.1)
1996
1997        self.widget.SetPlaceFactor(1.0 + padding)
1998        self.widget.SetInputData(poly)
1999        self.widget.PlaceWidget()
2000        if delayed:
2001            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2002        else:
2003            self.widget.AddObserver("InteractionEvent", self._select_polygons)
2004
2005    def _select_polygons(self, vobj, event):
2006        vobj.GetPlanes(self._implicit_func)
2007
2008    def _keypress(self, vobj, event):
2009        if vobj.GetKeySym() == "r":  # reset planes
2010            self._implicit_func.SetBounds(self._init_bounds)
2011            self.widget.GetPlanes(self._implicit_func)
2012            self.widget.PlaceWidget()
2013            self.widget.GetInteractor().Render()
2014        elif vobj.GetKeySym() == "u":
2015            self.invert()
2016            self.widget.GetInteractor().Render()
2017        elif vobj.GetKeySym() == "s": # Ctrl+s to save mesh
2018            if self.widget.GetInteractor():
2019                if self.widget.GetInteractor().GetControlKey():
2020                    self.mesh.write("vedo_clipped.vtk")
2021                    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)
1913    def __init__(
1914            self,
1915            mesh,
1916            invert=False,
1917            can_rotate=True,
1918            can_translate=True,
1919            can_scale=True,
1920            initial_bounds=(),
1921            padding=0.025,
1922            delayed=False,
1923            c=(0.25, 0.25, 0.25),
1924            alpha=0.05,
1925    ):
1926        """
1927        Create a box widget to cut away parts of a Mesh.
1928
1929        Arguments:
1930            mesh : (Mesh)
1931                the input mesh
1932            invert : (bool)
1933                invert the clipping plane
1934            can_rotate : (bool)
1935                enable rotation of the widget
1936            can_translate : (bool)
1937                enable translation of the widget
1938            can_scale : (bool)
1939                enable scaling of the widget
1940            initial_bounds : (list)
1941                initial bounds of the box widget
1942            padding : (float)
1943                padding space around the input mesh
1944            delayed : (bool)
1945                if True the callback is delayed until
1946                when the mouse button is released (useful for large meshes)
1947            c : (color)
1948                color of the box cutter widget
1949            alpha : (float)
1950                transparency of the cut-off part of the input mesh
1951        """
1952        super().__init__()
1953
1954        self.mesh = mesh
1955        self.remnant = Mesh()
1956        self.remnant.name = mesh.name + "Remnant"
1957        self.remnant.pickable(False)
1958
1959        self._alpha = alpha
1960        self._keypress_id = None
1961        self._init_bounds = initial_bounds
1962        if len(self._init_bounds) == 0:
1963            self._init_bounds = mesh.bounds()
1964        else:
1965            self._init_bounds = initial_bounds
1966
1967        self._implicit_func = vtki.new("Planes")
1968        self._implicit_func.SetBounds(self._init_bounds)
1969
1970        poly = mesh.dataset
1971        self.clipper = vtki.new("ClipPolyData")
1972        self.clipper.GenerateClipScalarsOff()
1973        self.clipper.SetInputData(poly)
1974        self.clipper.SetClipFunction(self._implicit_func)
1975        self.clipper.SetInsideOut(not invert)
1976        self.clipper.GenerateClippedOutputOn()
1977        self.clipper.Update()
1978
1979        self.widget = vtki.vtkBoxWidget()
1980
1981        self.widget.SetRotationEnabled(can_rotate)
1982        self.widget.SetTranslationEnabled(can_translate)
1983        self.widget.SetScalingEnabled(can_scale)
1984
1985        self.widget.OutlineCursorWiresOn()
1986        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
1987        self.widget.GetSelectedHandleProperty().SetColor(get_color("red5"))
1988
1989        self.widget.GetOutlineProperty().SetColor(c)
1990        self.widget.GetOutlineProperty().SetOpacity(1)
1991        self.widget.GetOutlineProperty().SetLineWidth(1)
1992        self.widget.GetOutlineProperty().LightingOff()
1993
1994        self.widget.GetSelectedFaceProperty().LightingOff()
1995        self.widget.GetSelectedFaceProperty().SetOpacity(0.1)
1996
1997        self.widget.SetPlaceFactor(1.0 + padding)
1998        self.widget.SetInputData(poly)
1999        self.widget.PlaceWidget()
2000        if delayed:
2001            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2002        else:
2003            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):
1748class PlaneCutter(vtki.vtkPlaneWidget, BaseCutter):
1749    """
1750    Create a box widget to cut away parts of a Mesh.
1751    """
1752    def __init__(
1753            self,
1754            mesh,
1755            invert=False,
1756            can_translate=True,
1757            can_scale=True,
1758            origin=(),
1759            normal=(),
1760            padding=0.05,
1761            delayed=False,
1762            c=(0.25, 0.25, 0.25),
1763            alpha=0.05,
1764    ):
1765        """
1766        Create a box widget to cut away parts of a Mesh.
1767
1768        Arguments:
1769            mesh : (Mesh)
1770                the input mesh
1771            invert : (bool)
1772                invert the clipping plane
1773            can_translate : (bool)
1774                enable translation of the widget
1775            can_scale : (bool)
1776                enable scaling of the widget
1777            origin : (list)
1778                origin of the plane
1779            normal : (list)
1780                normal to the plane
1781            padding : (float)
1782                padding around the input mesh
1783            delayed : (bool)
1784                if True the callback is delayed until
1785                when the mouse button is released (useful for large meshes)
1786            c : (color)
1787                color of the box cutter widget
1788            alpha : (float)
1789                transparency of the cut-off part of the input mesh
1790        
1791        Examples:
1792            - [slice_plane3.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slice_plane3.py)
1793        """
1794        super().__init__()
1795
1796        self.mesh = mesh
1797        self.remnant = Mesh()
1798        self.remnant.name = mesh.name + "Remnant"
1799        self.remnant.pickable(False)
1800
1801        self._alpha = alpha
1802        self._keypress_id = None
1803
1804        self._implicit_func = vtki.new("Plane")
1805
1806        poly = mesh.dataset
1807        self.clipper = vtki.new("ClipPolyData")
1808        self.clipper.GenerateClipScalarsOff()
1809        self.clipper.SetInputData(poly)
1810        self.clipper.SetClipFunction(self._implicit_func)
1811        self.clipper.SetInsideOut(invert)
1812        self.clipper.GenerateClippedOutputOn()
1813        self.clipper.Update()
1814
1815        self.widget = vtki.new("ImplicitPlaneWidget")
1816
1817        # self.widget.KeyPressActivationOff()
1818        # self.widget.SetKeyPressActivationValue('i')
1819
1820        self.widget.SetOriginTranslation(can_translate)
1821        self.widget.SetOutlineTranslation(can_translate)
1822        self.widget.SetScaleEnabled(can_scale)
1823
1824        self.widget.GetOutlineProperty().SetColor(get_color(c))
1825        self.widget.GetOutlineProperty().SetOpacity(0.25)
1826        self.widget.GetOutlineProperty().SetLineWidth(1)
1827        self.widget.GetOutlineProperty().LightingOff()
1828
1829        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
1830
1831        self.widget.SetTubing(0)
1832        self.widget.SetDrawPlane(bool(alpha))
1833        self.widget.GetPlaneProperty().LightingOff()
1834        self.widget.GetPlaneProperty().SetOpacity(alpha)
1835        self.widget.GetSelectedPlaneProperty().SetColor(get_color("red5"))
1836        self.widget.GetSelectedPlaneProperty().LightingOff()
1837
1838        self.widget.SetPlaceFactor(1.0 + padding)
1839        self.widget.SetInputData(poly)
1840        self.widget.PlaceWidget()
1841        if delayed:
1842            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
1843        else:
1844            self.widget.AddObserver("InteractionEvent", self._select_polygons)
1845
1846        if len(origin) == 3:
1847            self.widget.SetOrigin(origin)
1848        else:
1849            self.widget.SetOrigin(mesh.center_of_mass())
1850
1851        if len(normal) == 3:
1852            self.widget.SetNormal(normal)
1853        else:
1854            self.widget.SetNormal((1, 0, 0))
1855    
1856    @property
1857    def origin(self):
1858        """Get the origin of the plane."""
1859        return np.array(self.widget.GetOrigin())
1860    
1861    @origin.setter
1862    def origin(self, value):
1863        """Set the origin of the plane."""
1864        self.widget.SetOrigin(value)
1865
1866    @property
1867    def normal(self):
1868        """Get the normal of the plane."""
1869        return np.array(self.widget.GetNormal())
1870    
1871    @normal.setter
1872    def normal(self, value):
1873        """Set the normal of the plane."""
1874        self.widget.SetNormal(value)
1875
1876    def _select_polygons(self, vobj, event) -> None:
1877        vobj.GetPlane(self._implicit_func)
1878
1879    def _keypress(self, vobj, event):
1880        if vobj.GetKeySym() == "r": # reset planes
1881            self.widget.GetPlane(self._implicit_func)
1882            self.widget.PlaceWidget()
1883            self.widget.GetInteractor().Render()
1884        elif vobj.GetKeySym() == "u": # invert cut
1885            self.invert()
1886            self.widget.GetInteractor().Render()
1887        elif vobj.GetKeySym() == "x": # set normal along x
1888            self.widget.SetNormal((1, 0, 0))
1889            self.widget.GetPlane(self._implicit_func)
1890            self.widget.PlaceWidget()
1891            self.widget.GetInteractor().Render()
1892        elif vobj.GetKeySym() == "y": # set normal along y
1893            self.widget.SetNormal((0, 1, 0))
1894            self.widget.GetPlane(self._implicit_func)
1895            self.widget.PlaceWidget()
1896            self.widget.GetInteractor().Render()
1897        elif vobj.GetKeySym() == "z": # set normal along z
1898            self.widget.SetNormal((0, 0, 1))
1899            self.widget.GetPlane(self._implicit_func)
1900            self.widget.PlaceWidget()
1901            self.widget.GetInteractor().Render()
1902        elif vobj.GetKeySym() == "s": # Ctrl+s to save mesh
1903            if self.widget.GetInteractor():
1904                if self.widget.GetInteractor().GetControlKey():
1905                    self.mesh.write("vedo_clipped.vtk")
1906                    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)
1752    def __init__(
1753            self,
1754            mesh,
1755            invert=False,
1756            can_translate=True,
1757            can_scale=True,
1758            origin=(),
1759            normal=(),
1760            padding=0.05,
1761            delayed=False,
1762            c=(0.25, 0.25, 0.25),
1763            alpha=0.05,
1764    ):
1765        """
1766        Create a box widget to cut away parts of a Mesh.
1767
1768        Arguments:
1769            mesh : (Mesh)
1770                the input mesh
1771            invert : (bool)
1772                invert the clipping plane
1773            can_translate : (bool)
1774                enable translation of the widget
1775            can_scale : (bool)
1776                enable scaling of the widget
1777            origin : (list)
1778                origin of the plane
1779            normal : (list)
1780                normal to the plane
1781            padding : (float)
1782                padding around the input mesh
1783            delayed : (bool)
1784                if True the callback is delayed until
1785                when the mouse button is released (useful for large meshes)
1786            c : (color)
1787                color of the box cutter widget
1788            alpha : (float)
1789                transparency of the cut-off part of the input mesh
1790        
1791        Examples:
1792            - [slice_plane3.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slice_plane3.py)
1793        """
1794        super().__init__()
1795
1796        self.mesh = mesh
1797        self.remnant = Mesh()
1798        self.remnant.name = mesh.name + "Remnant"
1799        self.remnant.pickable(False)
1800
1801        self._alpha = alpha
1802        self._keypress_id = None
1803
1804        self._implicit_func = vtki.new("Plane")
1805
1806        poly = mesh.dataset
1807        self.clipper = vtki.new("ClipPolyData")
1808        self.clipper.GenerateClipScalarsOff()
1809        self.clipper.SetInputData(poly)
1810        self.clipper.SetClipFunction(self._implicit_func)
1811        self.clipper.SetInsideOut(invert)
1812        self.clipper.GenerateClippedOutputOn()
1813        self.clipper.Update()
1814
1815        self.widget = vtki.new("ImplicitPlaneWidget")
1816
1817        # self.widget.KeyPressActivationOff()
1818        # self.widget.SetKeyPressActivationValue('i')
1819
1820        self.widget.SetOriginTranslation(can_translate)
1821        self.widget.SetOutlineTranslation(can_translate)
1822        self.widget.SetScaleEnabled(can_scale)
1823
1824        self.widget.GetOutlineProperty().SetColor(get_color(c))
1825        self.widget.GetOutlineProperty().SetOpacity(0.25)
1826        self.widget.GetOutlineProperty().SetLineWidth(1)
1827        self.widget.GetOutlineProperty().LightingOff()
1828
1829        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
1830
1831        self.widget.SetTubing(0)
1832        self.widget.SetDrawPlane(bool(alpha))
1833        self.widget.GetPlaneProperty().LightingOff()
1834        self.widget.GetPlaneProperty().SetOpacity(alpha)
1835        self.widget.GetSelectedPlaneProperty().SetColor(get_color("red5"))
1836        self.widget.GetSelectedPlaneProperty().LightingOff()
1837
1838        self.widget.SetPlaceFactor(1.0 + padding)
1839        self.widget.SetInputData(poly)
1840        self.widget.PlaceWidget()
1841        if delayed:
1842            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
1843        else:
1844            self.widget.AddObserver("InteractionEvent", self._select_polygons)
1845
1846        if len(origin) == 3:
1847            self.widget.SetOrigin(origin)
1848        else:
1849            self.widget.SetOrigin(mesh.center_of_mass())
1850
1851        if len(normal) == 3:
1852            self.widget.SetNormal(normal)
1853        else:
1854            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
1856    @property
1857    def origin(self):
1858        """Get the origin of the plane."""
1859        return np.array(self.widget.GetOrigin())

Get the origin of the plane.

normal
1866    @property
1867    def normal(self):
1868        """Get the normal of the plane."""
1869        return np.array(self.widget.GetNormal())

Get the normal of the plane.

class SphereCutter(vtkmodules.vtkInteractionWidgets.vtkSphereWidget, BaseCutter):
2024class SphereCutter(vtki.vtkSphereWidget, BaseCutter):
2025    """
2026    Create a box widget to cut away parts of a Mesh.
2027    """
2028    def __init__(
2029            self,
2030            mesh,
2031            invert=False,
2032            can_translate=True,
2033            can_scale=True,
2034            origin=(),
2035            radius=0,
2036            res=60,
2037            delayed=False,
2038            c='white',
2039            alpha=0.05,
2040    ):
2041        """
2042        Create a box widget to cut away parts of a Mesh.
2043
2044        Arguments:
2045            mesh : Mesh
2046                the input mesh
2047            invert : bool
2048                invert the clipping
2049            can_translate : bool
2050                enable translation of the widget
2051            can_scale : bool
2052                enable scaling of the widget
2053            origin : list
2054                initial position of the sphere widget
2055            radius : float
2056                initial radius of the sphere widget
2057            res : int
2058                resolution of the sphere widget
2059            delayed : bool
2060                if True the cutting callback is delayed until
2061                when the mouse button is released (useful for large meshes)
2062            c : color
2063                color of the box cutter widget
2064            alpha : float
2065                transparency of the cut-off part of the input mesh
2066        """
2067        super().__init__()
2068
2069        self.mesh = mesh
2070        self.remnant = Mesh()
2071        self.remnant.name = mesh.name + "Remnant"
2072        self.remnant.pickable(False)
2073
2074        self._alpha = alpha
2075        self._keypress_id = None
2076
2077        self._implicit_func = vtki.new("Sphere")
2078
2079        if len(origin) == 3:
2080            self._implicit_func.SetCenter(origin)
2081        else:
2082            origin = mesh.center_of_mass()
2083            self._implicit_func.SetCenter(origin)
2084
2085        if radius > 0:
2086            self._implicit_func.SetRadius(radius)
2087        else:
2088            radius = mesh.average_size() * 2
2089            self._implicit_func.SetRadius(radius)
2090
2091        poly = mesh.dataset
2092        self.clipper = vtki.new("ClipPolyData")
2093        self.clipper.GenerateClipScalarsOff()
2094        self.clipper.SetInputData(poly)
2095        self.clipper.SetClipFunction(self._implicit_func)
2096        self.clipper.SetInsideOut(not invert)
2097        self.clipper.GenerateClippedOutputOn()
2098        self.clipper.Update()
2099
2100        self.widget = vtki.vtkSphereWidget()
2101
2102        self.widget.SetThetaResolution(res*2)
2103        self.widget.SetPhiResolution(res)
2104        self.widget.SetRadius(radius)
2105        self.widget.SetCenter(origin)
2106        self.widget.SetRepresentation(2)
2107        self.widget.HandleVisibilityOff()
2108
2109        self.widget.SetTranslation(can_translate)
2110        self.widget.SetScale(can_scale)
2111
2112        self.widget.HandleVisibilityOff()
2113        self.widget.GetSphereProperty().SetColor(get_color(c))
2114        self.widget.GetSphereProperty().SetOpacity(0.2)
2115        self.widget.GetSelectedSphereProperty().SetColor(get_color("red5"))
2116        self.widget.GetSelectedSphereProperty().SetOpacity(0.2)
2117
2118        self.widget.SetPlaceFactor(1.0)
2119        self.widget.SetInputData(poly)
2120        self.widget.PlaceWidget()
2121        if delayed:
2122            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2123        else:
2124            self.widget.AddObserver("InteractionEvent", self._select_polygons)
2125
2126    def _select_polygons(self, vobj, event):
2127        vobj.GetSphere(self._implicit_func)
2128
2129    def _keypress(self, vobj, event):
2130        if vobj.GetKeySym() == "r":  # reset planes
2131            self._implicit_func.SetBounds(self._init_bounds)
2132            self.widget.GetPlanes(self._implicit_func)
2133            self.widget.PlaceWidget()
2134            self.widget.GetInteractor().Render()
2135        elif vobj.GetKeySym() == "u":
2136            self.invert()
2137            self.widget.GetInteractor().Render()
2138        elif vobj.GetKeySym() == "s": # Ctrl+s to save mesh
2139            if self.widget.GetInteractor():
2140                if self.widget.GetInteractor().GetControlKey():
2141                    self.mesh.write("vedo_clipped.vtk")
2142                    printc(":save: saved mesh to vedo_clipped.vtk")
2143
2144    @property
2145    def center(self):
2146        """Get the center of the sphere."""
2147        return np.array(self.widget.GetCenter())
2148    
2149    @center.setter
2150    def center(self, value):
2151        """Set the center of the sphere."""
2152        self.widget.SetCenter(value)
2153
2154    @property
2155    def radius(self):
2156        """Get the radius of the sphere."""
2157        return self.widget.GetRadius()
2158    
2159    @radius.setter
2160    def radius(self, value):
2161        """Set the radius of the sphere."""
2162        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)
2028    def __init__(
2029            self,
2030            mesh,
2031            invert=False,
2032            can_translate=True,
2033            can_scale=True,
2034            origin=(),
2035            radius=0,
2036            res=60,
2037            delayed=False,
2038            c='white',
2039            alpha=0.05,
2040    ):
2041        """
2042        Create a box widget to cut away parts of a Mesh.
2043
2044        Arguments:
2045            mesh : Mesh
2046                the input mesh
2047            invert : bool
2048                invert the clipping
2049            can_translate : bool
2050                enable translation of the widget
2051            can_scale : bool
2052                enable scaling of the widget
2053            origin : list
2054                initial position of the sphere widget
2055            radius : float
2056                initial radius of the sphere widget
2057            res : int
2058                resolution of the sphere widget
2059            delayed : bool
2060                if True the cutting callback is delayed until
2061                when the mouse button is released (useful for large meshes)
2062            c : color
2063                color of the box cutter widget
2064            alpha : float
2065                transparency of the cut-off part of the input mesh
2066        """
2067        super().__init__()
2068
2069        self.mesh = mesh
2070        self.remnant = Mesh()
2071        self.remnant.name = mesh.name + "Remnant"
2072        self.remnant.pickable(False)
2073
2074        self._alpha = alpha
2075        self._keypress_id = None
2076
2077        self._implicit_func = vtki.new("Sphere")
2078
2079        if len(origin) == 3:
2080            self._implicit_func.SetCenter(origin)
2081        else:
2082            origin = mesh.center_of_mass()
2083            self._implicit_func.SetCenter(origin)
2084
2085        if radius > 0:
2086            self._implicit_func.SetRadius(radius)
2087        else:
2088            radius = mesh.average_size() * 2
2089            self._implicit_func.SetRadius(radius)
2090
2091        poly = mesh.dataset
2092        self.clipper = vtki.new("ClipPolyData")
2093        self.clipper.GenerateClipScalarsOff()
2094        self.clipper.SetInputData(poly)
2095        self.clipper.SetClipFunction(self._implicit_func)
2096        self.clipper.SetInsideOut(not invert)
2097        self.clipper.GenerateClippedOutputOn()
2098        self.clipper.Update()
2099
2100        self.widget = vtki.vtkSphereWidget()
2101
2102        self.widget.SetThetaResolution(res*2)
2103        self.widget.SetPhiResolution(res)
2104        self.widget.SetRadius(radius)
2105        self.widget.SetCenter(origin)
2106        self.widget.SetRepresentation(2)
2107        self.widget.HandleVisibilityOff()
2108
2109        self.widget.SetTranslation(can_translate)
2110        self.widget.SetScale(can_scale)
2111
2112        self.widget.HandleVisibilityOff()
2113        self.widget.GetSphereProperty().SetColor(get_color(c))
2114        self.widget.GetSphereProperty().SetOpacity(0.2)
2115        self.widget.GetSelectedSphereProperty().SetColor(get_color("red5"))
2116        self.widget.GetSelectedSphereProperty().SetOpacity(0.2)
2117
2118        self.widget.SetPlaceFactor(1.0)
2119        self.widget.SetInputData(poly)
2120        self.widget.PlaceWidget()
2121        if delayed:
2122            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
2123        else:
2124            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
2144    @property
2145    def center(self):
2146        """Get the center of the sphere."""
2147        return np.array(self.widget.GetCenter())

Get the center of the sphere.

radius
2154    @property
2155    def radius(self):
2156        """Get the radius of the sphere."""
2157        return self.widget.GetRadius()

Get the radius of the sphere.