Skip to content

vedo.addons

addons

core

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)

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

Parameters:

Name Type Description Default
p1 list

First point 3D coordinates.

required
p2 list

The vertex point.

required
p3 list

The last point defining the angle.

required
font str

Font face. Check available fonts here.

''
arc_size float

Dimension of the arc relative to the smallest axis.

0.4
s float

Size of the text.

1
italic float | bool

Whether to render italic text.

0
rotation float

Rotation of the text in degrees.

0
prefix str

String appended to the numeric value of the angle.

''
lc list

Color of the goniometer lines.

'k2'
c str

Color of the goniometer angle filling. Set alpha=0 to remove it.

'white'
alpha float

Transparency level.

1
lw float

Line width.

2
precision int

Number of significant digits.

3

Examples:

Source code in vedo/addons/core.py
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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,
):
    """
    Build a graphical goniometer to measure the angle formed by 3 points in space.

    Args:
        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](https://vedo.embl.es/fonts).
        arc_size (float): Dimension of the arc relative to the smallest axis.
        s (float): Size of the text.
        italic (float | bool): Whether to render italic text.
        rotation (float): Rotation of the text in degrees.
        prefix (str): String appended 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:
        - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)

            ![](https://vedo.embl.es/images/pyplot/goniometer.png)
    """
    if isinstance(p1, Points):
        p1 = p1.pos()
    if isinstance(p2, Points):
        p2 = p2.pos()
    if isinstance(p3, Points):
        p3 = p3.pos()
    if len(p1) == 2:
        p1 = [p1[0], p1[1], 0.0]
    if len(p2) == 2:
        p2 = [p2[0], p2[1], 0.0]
    if len(p3) == 2:
        p3 = [p3[0], p3[1], 0.0]
    p1, p2, p3 = np.array(p1), np.array(p2), np.array(p3)

    acts = []
    ln = shapes.Line([p1, p2, p3], lw=lw, c=lc)
    acts.append(ln)

    va = utils.versor(p1 - p2)
    vb = utils.versor(p3 - p2)
    r = min(utils.mag(p3 - p2), utils.mag(p1 - p2)) * arc_size
    ptsarc = []
    res = 120
    imed = int(res / 2)
    for i in range(res + 1):
        vi = utils.versor(vb * i / res + va * (res - i) / res)
        if i == imed:
            vc = np.array(vi)
        ptsarc.append(p2 + vi * r)
    arc = shapes.Line(ptsarc).lw(lw).c(lc)
    acts.append(arc)

    angle = np.arccos(np.dot(va, vb)) * 180 / np.pi

    lb = shapes.Text3D(
        prefix + utils.precision(angle, precision) + "º",
        s=r / 12 * s,
        font=font,
        italic=italic,
        justify="center",
    )
    cr = np.cross(va, vb)
    lb.reorient([0, 0, 1], cr * np.sign(cr[2]), rotation=rotation, xyplane=False)
    lb.pos(p2 + vc * r / 1.75)
    lb.c(c).bc("tomato").lighting("off")
    acts.append(lb)

    if alpha > 0:
        pts = [p2] + arc.coordinates.tolist() + [p2]
        msh = Mesh([pts, [list(range(arc.npoints + 2))]], c=lc, alpha=alpha)
        msh.lighting("off")
        msh.triangulate()
        msh.shift(0, 0, -r / 10000)  # to resolve 2d conflicts..
        acts.append(msh)

    asse = Assembly(acts)
    asse.name = "Goniometer"
    return asse

Light(pos, focal_point=(0, 0, 0), angle=180, c=None, intensity=1)

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

Parameters:

Name Type Description Default
pos list | vedo object

Light position, or a vedo object whose position will be used.

required
focal_point list

Focal point. If a vedo object is passed its position is used.

(0, 0, 0)
angle float

Aperture angle of the light source in degrees.

180
c color

Light color.

None
intensity float

Intensity value between 0 and 1.

1
Check also

plotter.Plotter.remove_lights()

Examples:

Source code in vedo/addons/core.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
def Light(pos, focal_point=(0, 0, 0), angle=180, c=None, intensity=1):
    """
    Generate a source of light placed at `pos` and directed to `focal point`.
    Returns a `vtkLight` object.

    Args:
        pos (list | vedo object): Light position, or a `vedo` object whose position
            will be used.
        focal_point (list): Focal point. If a `vedo` object is passed its position is used.
        angle (float): Aperture angle of the light source in degrees.
        c (color): Light color.
        intensity (float): Intensity value between 0 and 1.

    Check also:
        `plotter.Plotter.remove_lights()`

    Examples:
        - [light_sources.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/light_sources.py)

            ![](https://vedo.embl.es/images/basic/lights.png)
    """
    if c is None:
        try:
            c = pos.color()
        except AttributeError:
            c = "white"

    try:
        pos = pos.pos()
    except AttributeError:
        pass

    try:
        focal_point = focal_point.pos()
    except AttributeError:
        pass

    light = vtki.vtkLight()
    light.SetLightTypeToSceneLight()
    light.SetPosition(pos)
    light.SetConeAngle(angle)
    light.SetFocalPoint(focal_point)
    light.SetIntensity(intensity)
    light.SetColor(get_color(c))
    return light

ScalarBar(obj, title='', pos=(), size=(80, 400), font_size=14, title_yoffset=20, nlabels=None, c='k', horizontal=False, use_alpha=True, label_format=':6.3g')

A 2D scalar bar for the specified object.

Parameters:

Name Type Description Default
obj

Input object or scalar range used to derive the lookup table.

required
title str

Scalar bar title.

''
pos list

Bottom-left position. It can also be a pair of normalized (x, y) values in the range [0, 1] to indicate the bar corners.

()
size tuple[float, float]

Scalar bar size in pixels as (width, height).

(80, 400)
font_size float

Font size for title and numeric labels.

14
title_yoffset float

Vertical space offset between title and color bar.

20
nlabels int

Number of numeric labels.

None
c list

Text color.

'k'
horizontal bool

Lay the scalar bar out horizontally.

False
use_alpha bool

Render transparency in the color bar itself.

True
label_format str

C-style format string for numeric labels.

':6.3g'

Examples:

Source code in vedo/addons/core.py
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
def ScalarBar(
    obj,
    title="",
    pos=(),
    size=(80, 400),
    font_size=14,
    title_yoffset=20,
    nlabels=None,
    c="k",
    horizontal=False,
    use_alpha=True,
    label_format=":6.3g",
) -> vtki.vtkScalarBarActor | None:
    """
    A 2D scalar bar for the specified object.

    Args:
        obj: Input object or scalar range used to derive the lookup table.
        title (str): Scalar bar title.
        pos (list): Bottom-left position. It can also be a pair of normalized `(x, y)`
            values in the range `[0, 1]` to indicate the bar corners.
        size (tuple[float, float]): Scalar bar size in pixels as `(width, height)`.
        font_size (float): Font size for title and numeric labels.
        title_yoffset (float): Vertical space offset between title and color bar.
        nlabels (int): Number of numeric labels.
        c (list): Text color.
        horizontal (bool): Lay the scalar bar out horizontally.
        use_alpha (bool): Render transparency in the color bar itself.
        label_format (str): C-style format string for numeric labels.

    Examples:
        - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py)

        ![](https://user-images.githubusercontent.com/32848391/62940174-4bdc7900-bdd3-11e9-9713-e4f3e2fdab63.png)
    """

    if isinstance(obj, (Points, TetMesh, vedo.UnstructuredGrid)):
        vtkscalars = obj.dataset.GetPointData().GetScalars()
        if vtkscalars is None:
            vtkscalars = obj.dataset.GetCellData().GetScalars()
        if not vtkscalars:
            return None
        lut = vtkscalars.GetLookupTable()
        if not lut:
            lut = obj.mapper.GetLookupTable()
            if not lut:
                return None

    elif isinstance(obj, Volume):
        lut = utils.ctf2lut(obj)

    elif utils.is_sequence(obj) and len(obj) == 2:
        x = np.linspace(obj[0], obj[1], 256)
        data = []
        for i in range(256):
            rgb = color_map(i, c, 0, 256)
            data.append([x[i], rgb])
        lut = build_lut(data)

    elif not hasattr(obj, "mapper"):
        vedo.logger.error(f"in add_scalarbar(): input is invalid {type(obj)}. Skip.")
        return None

    else:
        return None

    c = get_color(c)
    sb = vtki.vtkScalarBarActor()

    # print("GetLabelFormat", sb.GetLabelFormat())
    label_format = label_format.replace(":", "%-#")
    sb.SetLabelFormat(label_format)

    sb.SetLookupTable(lut)
    sb.SetUseOpacity(use_alpha)
    sb.SetDrawFrame(0)
    sb.SetDrawBackground(0)
    if lut.GetUseBelowRangeColor():
        sb.DrawBelowRangeSwatchOn()
        sb.SetBelowRangeAnnotation("")
    if lut.GetUseAboveRangeColor():
        sb.DrawAboveRangeSwatchOn()
        sb.SetAboveRangeAnnotation("")
    if lut.GetNanColor() != (0.5, 0.0, 0.0, 1.0):
        sb.DrawNanAnnotationOn()
        sb.SetNanAnnotation("nan")

    if title:
        if "\\" in repr(title):
            for r in shapes._reps:
                title = title.replace(r[0], r[1])
        titprop = sb.GetTitleTextProperty()
        titprop.BoldOn()
        titprop.ItalicOff()
        titprop.ShadowOff()
        titprop.SetColor(c)
        titprop.SetVerticalJustificationToTop()
        titprop.SetFontSize(font_size)
        titprop.SetFontFamily(vtki.VTK_FONT_FILE)
        titprop.SetFontFile(utils.get_font_path(vedo.settings.default_font))
        sb.SetTitle(title)
        sb.SetVerticalTitleSeparation(title_yoffset)
        sb.SetTitleTextProperty(titprop)

    sb.SetTextPad(0)
    sb.UnconstrainedFontSizeOn()
    sb.DrawAnnotationsOn()
    sb.DrawTickLabelsOn()
    sb.SetMaximumNumberOfColors(256)
    if nlabels is not None:
        sb.SetNumberOfLabels(nlabels)

    if len(pos) == 0 or utils.is_sequence(pos[0]):
        if len(pos) == 0:
            pos = ((0.87, 0.05), (0.97, 0.5))
            if horizontal:
                pos = ((0.5, 0.05), (0.97, 0.15))
        sb.SetTextPositionToPrecedeScalarBar()
        if horizontal:
            if not nlabels:
                sb.SetNumberOfLabels(3)
            sb.SetOrientationToHorizontal()
            sb.SetTextPositionToSucceedScalarBar()
    else:
        if horizontal:
            size = (size[1], size[0])  # swap size
            sb.SetPosition(pos[0] - 0.7, pos[1])
            if not nlabels:
                sb.SetNumberOfLabels(3)
            sb.SetOrientationToHorizontal()
            sb.SetTextPositionToSucceedScalarBar()
        else:
            sb.SetPosition(pos[0], pos[1])
            if not nlabels:
                sb.SetNumberOfLabels(7)
            sb.SetTextPositionToPrecedeScalarBar()
        sb.SetHeight(1)
        sb.SetWidth(1)
        if size[0] is not None:
            sb.SetMaximumWidthInPixels(size[0])
        if size[1] is not None:
            sb.SetMaximumHeightInPixels(size[1])

    sb.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport()
    sb.GetPosition2Coordinate().SetCoordinateSystemToNormalizedViewport()

    s = np.array(pos[1]) - np.array(pos[0])
    sb.GetPositionCoordinate().SetValue(pos[0][0], pos[0][1])
    sb.GetPosition2Coordinate().SetValue(s[0], s[1])  # size !!??

    sctxt = sb.GetLabelTextProperty()
    sctxt.SetFontFamily(vtki.VTK_FONT_FILE)
    sctxt.SetFontFile(utils.get_font_path(vedo.settings.default_font))
    sctxt.SetColor(c)
    sctxt.SetShadow(0)
    sctxt.SetFontSize(font_size)
    sb.SetAnnotationTextProperty(sctxt)
    sb.PickableOff()
    return sb

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)

Create a 3D scalar bar for the specified object.

Input obj input can be:

- a look-up-table,
- a Mesh already containing a set of scalars associated to vertices or cells,
- if None the last object in the list of actors will be used.

Parameters:

Name Type Description Default
size list

(thickness, length) of scalarbar

(0, 0)
title str

scalar bar title

''
title_xoffset float

horizontal space btw title and color scalarbar

-1.2
title_yoffset float

vertical space offset

0.0
title_size float

size of title wrt numeric labels

1.5
title_rotation float

title rotation in degrees

0.0
nlabels int

number of numeric labels

8
label_font str

font type for labels

''
label_size float

label scale factor

1
label_offset float

space btw numeric labels and scale

0.375
label_rotation float

label rotation in degrees

0
draw_box bool

draw a box around the colorbar

True
categories list

make a categorical scalarbar, the input list will have the format [value, color, alpha, textlabel]

None

Examples:

Source code in vedo/addons/core.py
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
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,
) -> Assembly | None:
    """
    Create a 3D scalar bar for the specified object.

    Input `obj` input can be:

        - a look-up-table,
        - a Mesh already containing a set of scalars associated to vertices or cells,
        - if None the last object in the list of actors will be used.

    Args:
        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:
        - [scalarbars.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/scalarbars.py)
        - [plot_fxy2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy2.py)
    """

    if isinstance(obj, (Points, TetMesh, vedo.UnstructuredGrid)):
        lut = obj.mapper.GetLookupTable()
        if not lut or lut.GetTable().GetNumberOfTuples() == 0:
            # create the most similar to the default
            obj.cmap("jet_r")
            lut = obj.mapper.GetLookupTable()
        vmin, vmax = lut.GetRange()

    elif isinstance(obj, Volume):
        lut = utils.ctf2lut(obj)
        vmin, vmax = lut.GetRange()

    elif isinstance(obj, vtki.vtkLookupTable):
        lut = obj
        vmin, vmax = lut.GetRange()

    else:
        vedo.logger.error("in ScalarBar3D(): input must be a vedo object with bounds.")
        return None

    bns = obj.bounds()
    sx, sy = size
    if sy == 0 or sy is None:
        sy = bns[3] - bns[2]
    if sx == 0 or sx is None:
        sx = sy / 18

    if categories is not None:  ################################
        ncats = len(categories)
        scale = shapes.Grid(
            [-float(sx) * label_offset, 0, 0], c=c, alpha=1, s=(sx, sy), res=(1, ncats)
        )
        cols, alphas = [], []
        ticks_pos, ticks_txt = [0.0], [""]
        for i, cat in enumerate(categories):
            cl = get_color(cat[1])
            cols.append(cl)
            if len(cat) > 2:
                alphas.append(cat[2])
            else:
                alphas.append(1)
            if len(cat) > 3:
                ticks_txt.append(cat[3])
            else:
                ticks_txt.append("")
            ticks_pos.append((i + 0.5) / ncats)
        ticks_pos.append(1.0)
        ticks_txt.append("")
        rgba = np.c_[np.array(cols) * 255, np.array(alphas) * 255]
        scale.cellcolors = rgba

    else:  ########################################################
        # build the color scale part
        scale = shapes.Grid(
            [-float(sx) * label_offset, 0, 0],
            c=c,
            s=(sx, sy),
            res=(1, lut.GetTable().GetNumberOfTuples()),
        )
        cscals = np.linspace(
            vmin, vmax, lut.GetTable().GetNumberOfTuples(), endpoint=True
        )

        if lut.GetScale():  # logarithmic scale
            lut10 = vtki.vtkLookupTable()
            lut10.DeepCopy(lut)
            lut10.SetScaleToLinear()
            lut10.Build()
            scale.cmap(lut10, cscals, on="cells")
            tk = utils.make_ticks(
                vmin, vmax, nlabels, logscale=True, useformat=label_format
            )
        else:
            # for i in range(lut.GetTable().GetNumberOfTuples()):
            #     print("LUT i=", i, lut.GetTableValue(i))
            scale.cmap(lut, cscals, on="cells")
            tk = utils.make_ticks(
                vmin, vmax, nlabels, logscale=False, useformat=label_format
            )
        ticks_pos, ticks_txt = tk

    scale.lw(0).wireframe(False).lighting("off")

    scales = [scale]

    xbns = scale.xbounds()

    lsize = sy / 60 * label_size

    tacts = []
    for i, p in enumerate(ticks_pos):
        tx = ticks_txt[i]
        if i and tx:
            # build numeric text
            y = (p - 0.5) * sy
            if label_rotation:
                a = shapes.Text3D(
                    tx,
                    s=lsize,
                    justify="center-top",
                    c=c,
                    italic=italic,
                    font=label_font,
                )
                a.rotate_z(label_rotation)
                a.pos(sx * label_offset, y, 0)
            else:
                a = shapes.Text3D(
                    tx,
                    pos=[sx * label_offset, y, 0],
                    s=lsize,
                    justify="center-left",
                    c=c,
                    italic=italic,
                    font=label_font,
                )

            tacts.append(a)

            # build ticks
            tic = shapes.Line(
                [xbns[1], y, 0], [xbns[1] + sx * label_offset / 4, y, 0], lw=2, c=c
            )
            tacts.append(tic)

    # build title
    if title:
        t = shapes.Text3D(
            title,
            pos=(0, 0, 0),
            s=sy / 50 * title_size,
            c=c,
            justify="centered-bottom",
            italic=italic,
            font=title_font,
        )
        t.rotate_z(90 + title_rotation)
        t.pos(sx * title_xoffset, title_yoffset, 0)
        tacts.append(t)

    if pos is None:
        tsize = 0
        if title:
            bbt = t.bounds()
            tsize = bbt[1] - bbt[0]
        pos = (bns[1] + tsize + sx * 1.5, (bns[2] + bns[3]) / 2, bns[4])

    # build below scale
    if lut.GetUseBelowRangeColor():
        r, g, b, alfa = lut.GetBelowRangeColor()
        sx = float(sx)
        sy = float(sy)
        brect = shapes.Rectangle(
            [-sx * label_offset - sx / 2, -sy / 2 - sx - sx * 0.1, 0],
            [-sx * label_offset + sx / 2, -sy / 2 - sx * 0.1, 0],
            c=(r, g, b),
            alpha=alfa,
        )
        brect.lw(1).lc(c).lighting("off")
        scales += [brect]
        if below_text is None:
            below_text = " <" + str(vmin)
        if below_text:
            if label_rotation:
                btx = shapes.Text3D(
                    below_text,
                    pos=(0, 0, 0),
                    s=lsize,
                    c=c,
                    justify="center-top",
                    italic=italic,
                    font=label_font,
                )
                btx.rotate_z(label_rotation)
            else:
                btx = shapes.Text3D(
                    below_text,
                    pos=(0, 0, 0),
                    s=lsize,
                    c=c,
                    justify="center-left",
                    italic=italic,
                    font=label_font,
                )

            btx.pos(sx * label_offset, -sy / 2 - sx * 0.66, 0)
            tacts.append(btx)

    # build above scale
    if lut.GetUseAboveRangeColor():
        r, g, b, alfa = lut.GetAboveRangeColor()
        arect = shapes.Rectangle(
            [-sx * label_offset - sx / 2, sy / 2 + sx * 0.1, 0],
            [-sx * label_offset + sx / 2, sy / 2 + sx + sx * 0.1, 0],
            c=(r, g, b),
            alpha=alfa,
        )
        arect.lw(1).lc(c).lighting("off")
        scales += [arect]
        if above_text is None:
            above_text = " >" + str(vmax)
        if above_text:
            if label_rotation:
                atx = shapes.Text3D(
                    above_text,
                    pos=(0, 0, 0),
                    s=lsize,
                    c=c,
                    justify="center-top",
                    italic=italic,
                    font=label_font,
                )
                atx.rotate_z(label_rotation)
            else:
                atx = shapes.Text3D(
                    above_text,
                    pos=(0, 0, 0),
                    s=lsize,
                    c=c,
                    justify="center-left",
                    italic=italic,
                    font=label_font,
                )

            atx.pos(sx * label_offset, sy / 2 + sx * 0.66, 0)
            tacts.append(atx)

    # build NaN scale
    if lut.GetNanColor() != (0.5, 0.0, 0.0, 1.0):
        nanshift = sx * 0.1
        if brect:
            nanshift += sx
        r, g, b, alfa = lut.GetNanColor()
        nanrect = shapes.Rectangle(
            [-sx * label_offset - sx / 2, -sy / 2 - sx - sx * 0.1 - nanshift, 0],
            [-sx * label_offset + sx / 2, -sy / 2 - sx * 0.1 - nanshift, 0],
            c=(r, g, b),
            alpha=alfa,
        )
        nanrect.lw(1).lc(c).lighting("off")
        scales += [nanrect]
        if label_rotation:
            nantx = shapes.Text3D(
                nan_text,
                pos=(0, 0, 0),
                s=lsize,
                c=c,
                justify="center-left",
                italic=italic,
                font=label_font,
            )
            nantx.rotate_z(label_rotation)
        else:
            nantx = shapes.Text3D(
                nan_text,
                pos=(0, 0, 0),
                s=lsize,
                c=c,
                justify="center-left",
                italic=italic,
                font=label_font,
            )
        nantx.pos(sx * label_offset, -sy / 2 - sx * 0.66 - nanshift, 0)
        tacts.append(nantx)

    if draw_box:
        tacts.append(scale.box().lw(1).c(c))

    for m in tacts + scales:
        m.shift(pos)
        m.actor.PickableOff()
        m.properties.LightingOff()

    asse = Assembly(scales + tacts)

    # asse.transform = LinearTransform().shift(pos)

    bb = asse.actor.GetBounds()
    # print("ScalarBar3D pos",pos, bb)
    # asse.SetOrigin(pos)

    asse.actor.SetOrigin(bb[0], bb[2], bb[4])
    # asse.SetOrigin(bb[0],0,0) #in pyplot line 1312

    asse.actor.PickableOff()
    asse.actor.UseBoundsOff()
    asse.name = "ScalarBar3D"
    return asse

axes

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)

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

Examples:

from vedo import Axes, Box, show
box = Box(pos=(1,2,3), size=(8,9,7)).alpha(0.1)
axs = Axes(box, c='k')  # returns an Assembly object
for a in axs.unpack():
    print(a.name)
show(box, axs).close()

Examples:

Source code in vedo/addons/axes.py
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
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="", # grab settings.default_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 can be a list (dx,dy,dz)
        xtitle_offset=0.025,  ytitle_offset=0.0275, ztitle_offset=0.02,
        xtitle_justify=None,  ytitle_justify=None,  ztitle_justify=None,
        # xtitle_rotation can be a list (rx,ry,rz)
        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="", # grab settings.default_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, # each can be a list (dx,dy,dz)
        xlabel_justify=None, ylabel_justify=None, zlabel_justify=None,
        xlabel_rotation=0, ylabel_rotation=0, zlabel_rotation=0, # each can be a list (rx,ry,rz)
        xaxis_rotation=0, yaxis_rotation=0, zaxis_rotation=0,    # rotate all elements around axis
        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,
    ) -> Assembly | None:
    """
    Draw axes for the input object.
    Check [available fonts here](https://vedo.embl.es/fonts).

    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

    Examples:
        ```python
        from vedo import Axes, Box, show
        box = Box(pos=(1,2,3), size=(8,9,7)).alpha(0.1)
        axs = Axes(box, c='k')  # returns an Assembly object
        for a in axs.unpack():
            print(a.name)
        show(box, axs).close()
        ```
        ![](https://vedo.embl.es/images/feats/axes1.png)

    Examples:
        - [custom_axes1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes1.py)
        - [custom_axes2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes2.py)
        - [custom_axes3.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes3.py)
        - [custom_axes4.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/custom_axes4.py)

        ![](https://vedo.embl.es/images/pyplot/customAxes3.png)
    """
    if not title_font:
        title_font = vedo.settings.default_font
    if not label_font:
        label_font = vedo.settings.default_font

    if c is None:  # automatic black or white
        c = (0.1, 0.1, 0.1)
        plt = vedo.current_plotter()
        if plt and plt.renderer:
            bgcol = plt.renderer.GetBackground()
        else:
            bgcol = (1, 1, 1)
        if np.sum(bgcol) < 1.5:
            c = (0.9, 0.9, 0.9)
    else:
        c = get_color(c)

    # Check if obj has bounds, if so use those
    if obj is not None:
        try:
            bb = obj.bounds()
        except AttributeError:
            try:
                bb = obj.GetBounds()
                if xrange is None:
                    xrange = (bb[0], bb[1])
                if yrange is None:
                    yrange = (bb[2], bb[3])
                if zrange is None:
                    zrange = (bb[4], bb[5])
                obj = None  # dont need it anymore
            except AttributeError:
                pass
        if utils.is_sequence(obj) and len(obj) == 6 and utils.is_number(obj[0]):
            # passing a list of numeric bounds
            if xrange is None:
                xrange = (obj[0], obj[1])
            if yrange is None:
                yrange = (obj[2], obj[3])
            if zrange is None:
                zrange = (obj[4], obj[5])

    if use_global:
        vbb, drange, min_bns, max_bns = compute_visible_bounds()
    else:
        if obj is not None:
            vbb, drange, min_bns, max_bns = compute_visible_bounds(obj)
        else:
            vbb = np.zeros(6)
            drange = np.zeros(3)
            if zrange is None:
                zrange = (0, 0)
            if xrange is None or yrange is None:
                vedo.logger.error("in Axes() must specify axes ranges!")
                return None  ###########################################

    if xrange is not None:
        if xrange[1] < xrange[0]:
            x_inverted = True
            xrange = [xrange[1], xrange[0]]
        vbb[0], vbb[1] = xrange
        drange[0] = vbb[1] - vbb[0]
        min_bns = vbb
        max_bns = vbb
    if yrange is not None:
        if yrange[1] < yrange[0]:
            y_inverted = True
            yrange = [yrange[1], yrange[0]]
        vbb[2], vbb[3] = yrange
        drange[1] = vbb[3] - vbb[2]
        min_bns = vbb
        max_bns = vbb
    if zrange is not None:
        if zrange[1] < zrange[0]:
            z_inverted = True
            zrange = [zrange[1], zrange[0]]
        vbb[4], vbb[5] = zrange
        drange[2] = vbb[5] - vbb[4]
        min_bns = vbb
        max_bns = vbb

    drangemax = max(drange)
    if not drangemax:
        return None

    if drange[0] / drangemax < limit_ratio:
        drange[0] = 0
        xtitle = ""
    if drange[1] / drangemax < limit_ratio:
        drange[1] = 0
        ytitle = ""
    if drange[2] / drangemax < limit_ratio:
        drange[2] = 0
        ztitle = ""

    x0, x1, y0, y1, z0, z1 = vbb
    dx, dy, dz = drange

    gscale = np.sqrt(dx * dx + dy * dy + dz * dz) * 0.75

    if not xyplane_color: xyplane_color = c
    if not yzplane_color: yzplane_color = c
    if not zxplane_color: zxplane_color = c
    if not xygrid_color:  xygrid_color = c
    if not yzgrid_color:  yzgrid_color = c
    if not zxgrid_color:  zxgrid_color = c
    if not xtitle_color:  xtitle_color = c
    if not ytitle_color:  ytitle_color = c
    if not ztitle_color:  ztitle_color = c
    if not xline_color:   xline_color = c
    if not yline_color:   yline_color = c
    if not zline_color:   zline_color = c
    if not xlabel_color:  xlabel_color = xline_color
    if not ylabel_color:  ylabel_color = yline_color
    if not zlabel_color:  zlabel_color = zline_color

    if tip_size is None:
        tip_size = 0.005 * gscale
        if not ztitle:
            tip_size = 0  # switch off in xy 2d

    ndiv = 4
    if not ztitle or not ytitle or not xtitle:  # make more default ticks if 2D
        ndiv = 6
        if not ztitle:
            if xyframe_line is None:
                xyframe_line = True
            if tip_size is None:
                tip_size = False

    if utils.is_sequence(number_of_divisions):
        rx, ry, rz = number_of_divisions
    else:
        if not number_of_divisions:
            number_of_divisions = ndiv
        if not drangemax or np.any(np.isnan(drange)):
            rx, ry, rz = 1, 1, 1
        else:
            rx, ry, rz = np.ceil(drange / drangemax * number_of_divisions).astype(int)

    if xtitle:
        xticks_float, xticks_str = utils.make_ticks(
            x0, x1, rx, x_values_and_labels, digits
        )
        xticks_float = xticks_float * dx
        if x_inverted:
            xticks_float = np.flip(-(xticks_float - xticks_float[-1]))
            xticks_str = list(reversed(xticks_str))
            xticks_str[-1] = ""
            xhighlight_zero = False
    if ytitle:
        yticks_float, yticks_str = utils.make_ticks(
            y0, y1, ry, y_values_and_labels, digits
        )
        yticks_float = yticks_float * dy
        if y_inverted:
            yticks_float = np.flip(-(yticks_float - yticks_float[-1]))
            yticks_str = list(reversed(yticks_str))
            yticks_str[-1] = ""
            yhighlight_zero = False
    if ztitle:
        zticks_float, zticks_str = utils.make_ticks(
            z0, z1, rz, z_values_and_labels, digits
        )
        zticks_float = zticks_float * dz
        if z_inverted:
            zticks_float = np.flip(-(zticks_float - zticks_float[-1]))
            zticks_str = list(reversed(zticks_str))
            zticks_str[-1] = ""
            zhighlight_zero = False

    ################################################ axes lines
    lines = []
    if xtitle:
        axlinex = shapes.Line([0, 0, 0], [dx, 0, 0], c=xline_color, lw=axes_linewidth)
        axlinex.shift(
            [0, zxshift * dy + xshift_along_y * dy, xyshift * dz + xshift_along_z * dz]
        )
        axlinex.name = "xAxis"
        lines.append(axlinex)
    if ytitle:
        axliney = shapes.Line([0, 0, 0], [0, dy, 0], c=yline_color, lw=axes_linewidth)
        axliney.shift(
            [yzshift * dx + yshift_along_x * dx, 0, xyshift * dz + yshift_along_z * dz]
        )
        axliney.name = "yAxis"
        lines.append(axliney)
    if ztitle:
        axlinez = shapes.Line([0, 0, 0], [0, 0, dz], c=zline_color, lw=axes_linewidth)
        axlinez.shift(
            [yzshift * dx + zshift_along_x * dx, zxshift * dy + zshift_along_y * dy, 0]
        )
        axlinez.name = "zAxis"
        lines.append(axlinez)

    ################################################ grid planes
    # all shapes have a name to keep track of them in the Assembly
    # if user wants to unpack it
    grids = []
    if xygrid and xtitle and ytitle:
        if not xygrid_transparent:
            gxy = shapes.Grid(s=(xticks_float, yticks_float))
            gxy.alpha(xyalpha).c(xyplane_color).lw(0)
            if xyshift:
                gxy.shift([0, 0, xyshift * dz])
            elif tol:
                gxy.shift([0, 0, -tol * gscale])
            gxy.name = "xyGrid"
            grids.append(gxy)
        if grid_linewidth:
            gxy_lines = shapes.Grid(s=(xticks_float, yticks_float))
            gxy_lines.c(xyplane_color).lw(grid_linewidth).alpha(xyalpha)
            if xyshift:
                gxy_lines.shift([0, 0, xyshift * dz])
            elif tol:
                gxy_lines.shift([0, 0, -tol * gscale])
            gxy_lines.name = "xyGridLines"
            grids.append(gxy_lines)

    if yzgrid and ytitle and ztitle:
        if not yzgrid_transparent:
            gyz = shapes.Grid(s=(zticks_float, yticks_float))
            gyz.alpha(yzalpha).c(yzplane_color).lw(0).rotate_y(-90)
            if yzshift:
                gyz.shift([yzshift * dx, 0, 0])
            elif tol:
                gyz.shift([-tol * gscale, 0, 0])
            gyz.name = "yzGrid"
            grids.append(gyz)
        if grid_linewidth:
            gyz_lines = shapes.Grid(s=(zticks_float, yticks_float))
            gyz_lines.c(yzplane_color).lw(grid_linewidth).alpha(yzalpha).rotate_y(-90)
            if yzshift:
                gyz_lines.shift([yzshift * dx, 0, 0])
            elif tol:
                gyz_lines.shift([-tol * gscale, 0, 0])
            gyz_lines.name = "yzGridLines"
            grids.append(gyz_lines)

    if zxgrid and ztitle and xtitle:
        if not zxgrid_transparent:
            gzx = shapes.Grid(s=(xticks_float, zticks_float))
            gzx.alpha(zxalpha).c(zxplane_color).lw(0).rotate_x(90)
            if zxshift:
                gzx.shift([0, zxshift * dy, 0])
            elif tol:
                gzx.shift([0, -tol * gscale, 0])
            gzx.name = "zxGrid"
            grids.append(gzx)
        if grid_linewidth:
            gzx_lines = shapes.Grid(s=(xticks_float, zticks_float))
            gzx_lines.c(zxplane_color).lw(grid_linewidth).alpha(zxalpha).rotate_x(90)
            if zxshift:
                gzx_lines.shift([0, zxshift * dy, 0])
            elif tol:
                gzx_lines.shift([0, -tol * gscale, 0])
            gzx_lines.name = "zxGridLines"
            grids.append(gzx_lines)

    # Grid2
    if xygrid2 and xtitle and ytitle:
        if not xygrid2_transparent:
            gxy2 = shapes.Grid(s=(xticks_float, yticks_float)).z(dz)
            gxy2.alpha(xyalpha).c(xyplane_color).lw(0)
            gxy2.shift([0, tol * gscale, 0])
            gxy2.name = "xyGrid2"
            grids.append(gxy2)
        if grid_linewidth:
            gxy2_lines = shapes.Grid(s=(xticks_float, yticks_float)).z(dz)
            gxy2_lines.c(xyplane_color).lw(grid_linewidth).alpha(xyalpha)
            gxy2_lines.shift([0, tol * gscale, 0])
            gxy2_lines.name = "xygrid2Lines"
            grids.append(gxy2_lines)

    if yzgrid2 and ytitle and ztitle:
        if not yzgrid2_transparent:
            gyz2 = shapes.Grid(s=(zticks_float, yticks_float))
            gyz2.alpha(yzalpha).c(yzplane_color).lw(0)
            gyz2.rotate_y(-90).x(dx).shift([tol * gscale, 0, 0])
            gyz2.name = "yzGrid2"
            grids.append(gyz2)
        if grid_linewidth:
            gyz2_lines = shapes.Grid(s=(zticks_float, yticks_float))
            gyz2_lines.c(yzplane_color).lw(grid_linewidth).alpha(yzalpha)
            gyz2_lines.rotate_y(-90).x(dx).shift([tol * gscale, 0, 0])
            gyz2_lines.name = "yzGrid2Lines"
            grids.append(gyz2_lines)

    if zxgrid2 and ztitle and xtitle:
        if not zxgrid2_transparent:
            gzx2 = shapes.Grid(s=(xticks_float, zticks_float))
            gzx2.alpha(zxalpha).c(zxplane_color).lw(0)
            gzx2.rotate_x(90).y(dy).shift([0, tol * gscale, 0])
            gzx2.name = "zxGrid2"
            grids.append(gzx2)
        if grid_linewidth:
            gzx2_lines = shapes.Grid(s=(xticks_float, zticks_float))
            gzx2_lines.c(zxplane_color).lw(grid_linewidth).alpha(zxalpha)
            gzx2_lines.rotate_x(90).y(dy).shift([0, tol * gscale, 0])
            gzx2_lines.name = "zxGrid2Lines"
            grids.append(gzx2_lines)

    ################################################ frame lines
    framelines = []
    if xyframe_line and xtitle and ytitle:
        if not xyframe_color:
            xyframe_color = xygrid_color
        frxy = shapes.Line(
            [[0, dy, 0], [dx, dy, 0], [dx, 0, 0], [0, 0, 0], [0, dy, 0]],
            c=xyframe_color,
            lw=xyframe_line,
        )
        frxy.shift([0, 0, xyshift * dz])
        frxy.name = "xyFrameLine"
        framelines.append(frxy)
    if yzframe_line and ytitle and ztitle:
        if not yzframe_color:
            yzframe_color = yzgrid_color
        fryz = shapes.Line(
            [[0, 0, dz], [0, dy, dz], [0, dy, 0], [0, 0, 0], [0, 0, dz]],
            c=yzframe_color,
            lw=yzframe_line,
        )
        fryz.shift([yzshift * dx, 0, 0])
        fryz.name = "yzFrameLine"
        framelines.append(fryz)
    if zxframe_line and ztitle and xtitle:
        if not zxframe_color:
            zxframe_color = zxgrid_color
        frzx = shapes.Line(
            [[0, 0, dz], [dx, 0, dz], [dx, 0, 0], [0, 0, 0], [0, 0, dz]],
            c=zxframe_color,
            lw=zxframe_line,
        )
        frzx.shift([0, zxshift * dy, 0])
        frzx.name = "zxFrameLine"
        framelines.append(frzx)

    ################################################ zero lines highlights
    highlights = []
    if xygrid and xtitle and ytitle:
        if xhighlight_zero and min_bns[0] <= 0 and max_bns[1] > 0:
            xhl = -min_bns[0]
            hxy = shapes.Line([xhl, 0, 0], [xhl, dy, 0], c=xhighlight_zero_color)
            hxy.alpha(np.sqrt(xyalpha)).lw(grid_linewidth * 2)
            hxy.shift([0, 0, xyshift * dz])
            hxy.name = "xyHighlightZero"
            highlights.append(hxy)
        if yhighlight_zero and min_bns[2] <= 0 and max_bns[3] > 0:
            yhl = -min_bns[2]
            hyx = shapes.Line([0, yhl, 0], [dx, yhl, 0], c=yhighlight_zero_color)
            hyx.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2)
            hyx.shift([0, 0, xyshift * dz])
            hyx.name = "yxHighlightZero"
            highlights.append(hyx)

    if yzgrid and ytitle and ztitle:
        if yhighlight_zero and min_bns[2] <= 0 and max_bns[3] > 0:
            yhl = -min_bns[2]
            hyz = shapes.Line([0, yhl, 0], [0, yhl, dz], c=yhighlight_zero_color)
            hyz.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2)
            hyz.shift([yzshift * dx, 0, 0])
            hyz.name = "yzHighlightZero"
            highlights.append(hyz)
        if zhighlight_zero and min_bns[4] <= 0 and max_bns[5] > 0:
            zhl = -min_bns[4]
            hzy = shapes.Line([0, 0, zhl], [0, dy, zhl], c=zhighlight_zero_color)
            hzy.alpha(np.sqrt(yzalpha)).lw(grid_linewidth * 2)
            hzy.shift([yzshift * dx, 0, 0])
            hzy.name = "zyHighlightZero"
            highlights.append(hzy)

    if zxgrid and ztitle and xtitle:
        if zhighlight_zero and min_bns[4] <= 0 and max_bns[5] > 0:
            zhl = -min_bns[4]
            hzx = shapes.Line([0, 0, zhl], [dx, 0, zhl], c=zhighlight_zero_color)
            hzx.alpha(np.sqrt(zxalpha)).lw(grid_linewidth * 2)
            hzx.shift([0, zxshift * dy, 0])
            hzx.name = "zxHighlightZero"
            highlights.append(hzx)
        if xhighlight_zero and min_bns[0] <= 0 and max_bns[1] > 0:
            xhl = -min_bns[0]
            hxz = shapes.Line([xhl, 0, 0], [xhl, 0, dz], c=xhighlight_zero_color)
            hxz.alpha(np.sqrt(zxalpha)).lw(grid_linewidth * 2)
            hxz.shift([0, zxshift * dy, 0])
            hxz.name = "xzHighlightZero"
            highlights.append(hxz)

    ################################################ arrow cone
    cones = []

    if tip_size:
        if xtitle:
            if x_inverted:
                cx = shapes.Cone(
                    r=tip_size,
                    height=tip_size * 2,
                    axis=(-1, 0, 0),
                    c=xline_color,
                    res=12,
                )
            else:
                cx = shapes.Cone(
                    (dx, 0, 0),
                    r=tip_size,
                    height=tip_size * 2,
                    axis=(1, 0, 0),
                    c=xline_color,
                    res=12,
                )
            T = LinearTransform()
            T.translate(
                [
                    0,
                    zxshift * dy + xshift_along_y * dy,
                    xyshift * dz + xshift_along_z * dz,
                ]
            )
            cx.apply_transform(T)
            cx.name = "xTipCone"
            cones.append(cx)

        if ytitle:
            if y_inverted:
                cy = shapes.Cone(
                    r=tip_size,
                    height=tip_size * 2,
                    axis=(0, -1, 0),
                    c=yline_color,
                    res=12,
                )
            else:
                cy = shapes.Cone(
                    (0, dy, 0),
                    r=tip_size,
                    height=tip_size * 2,
                    axis=(0, 1, 0),
                    c=yline_color,
                    res=12,
                )
            T = LinearTransform()
            T.translate(
                [
                    yzshift * dx + yshift_along_x * dx,
                    0,
                    xyshift * dz + yshift_along_z * dz,
                ]
            )
            cy.apply_transform(T)
            cy.name = "yTipCone"
            cones.append(cy)

        if ztitle:
            if z_inverted:
                cz = shapes.Cone(
                    r=tip_size,
                    height=tip_size * 2,
                    axis=(0, 0, -1),
                    c=zline_color,
                    res=12,
                )
            else:
                cz = shapes.Cone(
                    (0, 0, dz),
                    r=tip_size,
                    height=tip_size * 2,
                    axis=(0, 0, 1),
                    c=zline_color,
                    res=12,
                )
            T = LinearTransform()
            T.translate(
                [
                    yzshift * dx + zshift_along_x * dx,
                    zxshift * dy + zshift_along_y * dy,
                    0,
                ]
            )
            cz.apply_transform(T)
            cz.name = "zTipCone"
            cones.append(cz)

    ################################################################# MAJOR ticks
    majorticks, minorticks = [], []
    xticks, yticks, zticks = [], [], []
    if show_ticks:
        if xtitle:
            tick_thickness = xtick_thickness * gscale / 2
            tick_length = xtick_length * gscale / 2
            for i in range(1, len(xticks_float) - 1):
                v1 = (xticks_float[i] - tick_thickness, -tick_length, 0)
                v2 = (xticks_float[i] + tick_thickness, tick_length, 0)
                xticks.append(shapes.Rectangle(v1, v2))
            if len(xticks) > 1:
                xmajticks = merge(xticks).c(xlabel_color)
                T = LinearTransform()
                T.rotate_x(xaxis_rotation)
                T.translate(
                    [
                        0,
                        zxshift * dy + xshift_along_y * dy,
                        xyshift * dz + xshift_along_z * dz,
                    ]
                )
                xmajticks.apply_transform(T)
                xmajticks.name = "xMajorTicks"
                majorticks.append(xmajticks)
        if ytitle:
            tick_thickness = ytick_thickness * gscale / 2
            tick_length = ytick_length * gscale / 2
            for i in range(1, len(yticks_float) - 1):
                v1 = (-tick_length, yticks_float[i] - tick_thickness, 0)
                v2 = (tick_length, yticks_float[i] + tick_thickness, 0)
                yticks.append(shapes.Rectangle(v1, v2))
            if len(yticks) > 1:
                ymajticks = merge(yticks).c(ylabel_color)
                T = LinearTransform()
                T.rotate_y(yaxis_rotation)
                T.translate(
                    [
                        yzshift * dx + yshift_along_x * dx,
                        0,
                        xyshift * dz + yshift_along_z * dz,
                    ]
                )
                ymajticks.apply_transform(T)
                ymajticks.name = "yMajorTicks"
                majorticks.append(ymajticks)
        if ztitle:
            tick_thickness = ztick_thickness * gscale / 2
            tick_length = ztick_length * gscale / 2.85
            for i in range(1, len(zticks_float) - 1):
                v1 = (zticks_float[i] - tick_thickness, -tick_length, 0)
                v2 = (zticks_float[i] + tick_thickness, tick_length, 0)
                zticks.append(shapes.Rectangle(v1, v2))
            if len(zticks) > 1:
                zmajticks = merge(zticks).c(zlabel_color)
                T = LinearTransform()
                T.rotate_y(-90).rotate_z(-45 + zaxis_rotation)
                T.translate(
                    [
                        yzshift * dx + zshift_along_x * dx,
                        zxshift * dy + zshift_along_y * dy,
                        0,
                    ]
                )
                zmajticks.apply_transform(T)
                zmajticks.name = "zMajorTicks"
                majorticks.append(zmajticks)

        ############################################################# MINOR ticks
        if xtitle and xminor_ticks and len(xticks) > 1:
            tick_thickness = xtick_thickness * gscale / 4
            tick_length = xtick_length * gscale / 4
            xminor_ticks += 1
            ticks = []
            for i in range(1, len(xticks)):
                t0, t1 = xticks[i - 1].pos(), xticks[i].pos()
                dt = t1 - t0
                for j in range(1, xminor_ticks):
                    mt = dt * (j / xminor_ticks) + t0
                    v1 = (mt[0] - tick_thickness, -tick_length, 0)
                    v2 = (mt[0] + tick_thickness, tick_length, 0)
                    ticks.append(shapes.Rectangle(v1, v2))

            # finish off the fist lower range from start to first tick
            t0, t1 = xticks[0].pos(), xticks[1].pos()
            dt = t1 - t0
            for j in range(1, xminor_ticks):
                mt = t0 - dt * (j / xminor_ticks)
                if mt[0] < 0:
                    break
                v1 = (mt[0] - tick_thickness, -tick_length, 0)
                v2 = (mt[0] + tick_thickness, tick_length, 0)
                ticks.append(shapes.Rectangle(v1, v2))

            # finish off the last upper range from last tick to end
            t0, t1 = xticks[-2].pos(), xticks[-1].pos()
            dt = t1 - t0
            for j in range(1, xminor_ticks):
                mt = t1 + dt * (j / xminor_ticks)
                if mt[0] > dx:
                    break
                v1 = (mt[0] - tick_thickness, -tick_length, 0)
                v2 = (mt[0] + tick_thickness, tick_length, 0)
                ticks.append(shapes.Rectangle(v1, v2))

            if ticks:
                xminticks = merge(ticks).c(xlabel_color)
                T = LinearTransform()
                T.rotate_x(xaxis_rotation)
                T.translate(
                    [
                        0,
                        zxshift * dy + xshift_along_y * dy,
                        xyshift * dz + xshift_along_z * dz,
                    ]
                )
                xminticks.apply_transform(T)
                xminticks.name = "xMinorTicks"
                minorticks.append(xminticks)

        if ytitle and yminor_ticks and len(yticks) > 1:  ##### y
            tick_thickness = ytick_thickness * gscale / 4
            tick_length = ytick_length * gscale / 4
            yminor_ticks += 1
            ticks = []
            for i in range(1, len(yticks)):
                t0, t1 = yticks[i - 1].pos(), yticks[i].pos()
                dt = t1 - t0
                for j in range(1, yminor_ticks):
                    mt = dt * (j / yminor_ticks) + t0
                    v1 = (-tick_length, mt[1] - tick_thickness, 0)
                    v2 = (tick_length, mt[1] + tick_thickness, 0)
                    ticks.append(shapes.Rectangle(v1, v2))

            # finish off the fist lower range from start to first tick
            t0, t1 = yticks[0].pos(), yticks[1].pos()
            dt = t1 - t0
            for j in range(1, yminor_ticks):
                mt = t0 - dt * (j / yminor_ticks)
                if mt[1] < 0:
                    break
                v1 = (-tick_length, mt[1] - tick_thickness, 0)
                v2 = (tick_length, mt[1] + tick_thickness, 0)
                ticks.append(shapes.Rectangle(v1, v2))

            # finish off the last upper range from last tick to end
            t0, t1 = yticks[-2].pos(), yticks[-1].pos()
            dt = t1 - t0
            for j in range(1, yminor_ticks):
                mt = t1 + dt * (j / yminor_ticks)
                if mt[1] > dy:
                    break
                v1 = (-tick_length, mt[1] - tick_thickness, 0)
                v2 = (tick_length, mt[1] + tick_thickness, 0)
                ticks.append(shapes.Rectangle(v1, v2))

            if ticks:
                yminticks = merge(ticks).c(ylabel_color)
                T = LinearTransform()
                T.rotate_y(yaxis_rotation)
                T.translate(
                    [
                        yzshift * dx + yshift_along_x * dx,
                        0,
                        xyshift * dz + yshift_along_z * dz,
                    ]
                )
                yminticks.apply_transform(T)
                yminticks.name = "yMinorTicks"
                minorticks.append(yminticks)

        if ztitle and zminor_ticks and len(zticks) > 1:  ##### z
            tick_thickness = ztick_thickness * gscale / 4
            tick_length = ztick_length * gscale / 5
            zminor_ticks += 1
            ticks = []
            for i in range(1, len(zticks)):
                t0, t1 = zticks[i - 1].pos(), zticks[i].pos()
                dt = t1 - t0
                for j in range(1, zminor_ticks):
                    mt = dt * (j / zminor_ticks) + t0
                    v1 = (mt[0] - tick_thickness, -tick_length, 0)
                    v2 = (mt[0] + tick_thickness, tick_length, 0)
                    ticks.append(shapes.Rectangle(v1, v2))

            # finish off the fist lower range from start to first tick
            t0, t1 = zticks[0].pos(), zticks[1].pos()
            dt = t1 - t0
            for j in range(1, zminor_ticks):
                mt = t0 - dt * (j / zminor_ticks)
                if mt[0] < 0:
                    break
                v1 = (mt[0] - tick_thickness, -tick_length, 0)
                v2 = (mt[0] + tick_thickness, tick_length, 0)
                ticks.append(shapes.Rectangle(v1, v2))

            # finish off the last upper range from last tick to end
            t0, t1 = zticks[-2].pos(), zticks[-1].pos()
            dt = t1 - t0
            for j in range(1, zminor_ticks):
                mt = t1 + dt * (j / zminor_ticks)
                if mt[0] > dz:
                    break
                v1 = (mt[0] - tick_thickness, -tick_length, 0)
                v2 = (mt[0] + tick_thickness, tick_length, 0)
                ticks.append(shapes.Rectangle(v1, v2))

            if ticks:
                zminticks = merge(ticks).c(zlabel_color)
                T = LinearTransform()
                T.rotate_y(-90).rotate_z(-45 + zaxis_rotation)
                T.translate(
                    [
                        yzshift * dx + zshift_along_x * dx,
                        zxshift * dy + zshift_along_y * dy,
                        0,
                    ]
                )
                zminticks.apply_transform(T)
                zminticks.name = "zMinorTicks"
                minorticks.append(zminticks)

    ################################################ axes NUMERIC text labels
    labels = []
    xlab, ylab, zlab = None, None, None

    if xlabel_size and xtitle:
        xRot, yRot, zRot = 0, 0, 0
        if utils.is_sequence(xlabel_rotation):  # unpck 3 rotations
            zRot, xRot, yRot = xlabel_rotation
        else:
            zRot = xlabel_rotation
        if zRot < 0:  # deal with negative angles
            zRot += 360

        jus = "center-top"
        if zRot:
            if zRot > 24:
                jus = "top-right"
            if zRot > 67:
                jus = "center-right"
            if zRot > 112:
                jus = "right-bottom"
            if zRot > 157:
                jus = "center-bottom"
            if zRot > 202:
                jus = "bottom-left"
            if zRot > 247:
                jus = "center-left"
            if zRot > 292:
                jus = "top-left"
            if zRot > 337:
                jus = "top-center"
        if xlabel_justify is not None:
            jus = xlabel_justify

        for i in range(1, len(xticks_str)):
            t = xticks_str[i]
            if not t:
                continue
            if utils.is_sequence(xlabel_offset):
                xoffs, yoffs, zoffs = xlabel_offset
            else:
                xoffs, yoffs, zoffs = 0, xlabel_offset, 0

            xlab = shapes.Text3D(
                t, s=xlabel_size * text_scale * gscale, font=label_font, justify=jus
            )
            tb = xlab.ybounds()  # must be ybounds: height of char

            v = (xticks_float[i], 0, 0)
            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0])

            T = LinearTransform()
            T.rotate_x(xaxis_rotation).rotate_y(yRot).rotate_x(xRot).rotate_z(zRot)
            T.translate(v + offs)
            T.translate(
                [
                    0,
                    zxshift * dy + xshift_along_y * dy,
                    xyshift * dz + xshift_along_z * dz,
                ]
            )
            xlab.apply_transform(T)

            xlab.use_bounds(x_use_bounds)

            xlab.c(xlabel_color)
            if xlabel_backface_color is None:
                bfc = 1 - np.array(get_color(xlabel_color))
                xlab.backcolor(bfc)
            xlab.name = f"xNumericLabel {i}"
            labels.append(xlab)

    if ylabel_size and ytitle:
        xRot, yRot, zRot = 0, 0, 0
        if utils.is_sequence(ylabel_rotation):  # unpck 3 rotations
            zRot, yRot, xRot = ylabel_rotation
        else:
            zRot = ylabel_rotation
        if zRot < 0:
            zRot += 360  # deal with negative angles

        jus = "center-right"
        if zRot:
            if zRot > 24:
                jus = "bottom-right"
            if zRot > 67:
                jus = "center-bottom"
            if zRot > 112:
                jus = "left-bottom"
            if zRot > 157:
                jus = "center-left"
            if zRot > 202:
                jus = "top-left"
            if zRot > 247:
                jus = "center-top"
            if zRot > 292:
                jus = "top-right"
            if zRot > 337:
                jus = "right-center"
        if ylabel_justify is not None:
            jus = ylabel_justify

        for i in range(1, len(yticks_str)):
            t = yticks_str[i]
            if not t:
                continue
            if utils.is_sequence(ylabel_offset):
                xoffs, yoffs, zoffs = ylabel_offset
            else:
                xoffs, yoffs, zoffs = ylabel_offset, 0, 0
            ylab = shapes.Text3D(
                t, s=ylabel_size * text_scale * gscale, font=label_font, justify=jus
            )
            tb = ylab.ybounds()  # must be ybounds: height of char
            v = (0, yticks_float[i], 0)
            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0])

            T = LinearTransform()
            T.rotate_y(yaxis_rotation).rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
            T.translate(v + offs)
            T.translate(
                [
                    yzshift * dx + yshift_along_x * dx,
                    0,
                    xyshift * dz + yshift_along_z * dz,
                ]
            )
            ylab.apply_transform(T)

            ylab.use_bounds(y_use_bounds)

            ylab.c(ylabel_color)
            if ylabel_backface_color is None:
                bfc = 1 - np.array(get_color(ylabel_color))
                ylab.backcolor(bfc)
            ylab.name = f"yNumericLabel {i}"
            labels.append(ylab)

    if zlabel_size and ztitle:
        xRot, yRot, zRot = 0, 0, 0
        if utils.is_sequence(zlabel_rotation):  # unpck 3 rotations
            xRot, yRot, zRot = zlabel_rotation
        else:
            xRot = zlabel_rotation
        if xRot < 0:
            xRot += 360  # deal with negative angles

        jus = "center-right"
        if xRot:
            if xRot > 24:
                jus = "bottom-right"
            if xRot > 67:
                jus = "center-bottom"
            if xRot > 112:
                jus = "left-bottom"
            if xRot > 157:
                jus = "center-left"
            if xRot > 202:
                jus = "top-left"
            if xRot > 247:
                jus = "center-top"
            if xRot > 292:
                jus = "top-right"
            if xRot > 337:
                jus = "right-center"
        if zlabel_justify is not None:
            jus = zlabel_justify

        for i in range(1, len(zticks_str)):
            t = zticks_str[i]
            if not t:
                continue
            if utils.is_sequence(zlabel_offset):
                xoffs, yoffs, zoffs = zlabel_offset
            else:
                xoffs, yoffs, zoffs = zlabel_offset, zlabel_offset, 0
            zlab = shapes.Text3D(
                t, s=zlabel_size * text_scale * gscale, font=label_font, justify=jus
            )
            tb = zlab.ybounds()  # must be ybounds: height of char

            v = (0, 0, zticks_float[i])
            offs = -np.array([xoffs, yoffs, zoffs]) * (tb[1] - tb[0]) / 1.5
            angle = np.arctan2(dy, dx) * 57.3

            T = LinearTransform()
            T.rotate_x(90 + zRot).rotate_y(-xRot).rotate_z(
                angle + yRot + zaxis_rotation
            )
            T.translate(v + offs)
            T.translate(
                [
                    yzshift * dx + zshift_along_x * dx,
                    zxshift * dy + zshift_along_y * dy,
                    0,
                ]
            )
            zlab.apply_transform(T)

            zlab.use_bounds(z_use_bounds)

            zlab.c(zlabel_color)
            if zlabel_backface_color is None:
                bfc = 1 - np.array(get_color(zlabel_color))
                zlab.backcolor(bfc)
            zlab.name = f"zNumericLabel {i}"
            labels.append(zlab)

    ################################################ axes titles
    titles = []

    if xtitle:
        xRot, yRot, zRot = 0, 0, 0
        if utils.is_sequence(xtitle_rotation):  # unpack 3 rotations
            zRot, xRot, yRot = xtitle_rotation
        else:
            zRot = xtitle_rotation
        if zRot < 0:  # deal with negative angles
            zRot += 360

        if utils.is_sequence(xtitle_offset):
            xoffs, yoffs, zoffs = xtitle_offset
        else:
            xoffs, yoffs, zoffs = 0, xtitle_offset, 0

        if xtitle_justify is not None:
            jus = xtitle_justify
        else:
            # find best justfication for given rotation(s)
            jus = "right-top"
            if zRot:
                if zRot > 24:
                    jus = "center-right"
                if zRot > 67:
                    jus = "right-bottom"
                if zRot > 157:
                    jus = "bottom-left"
                if zRot > 202:
                    jus = "center-left"
                if zRot > 247:
                    jus = "top-left"
                if zRot > 337:
                    jus = "top-right"

        xt = shapes.Text3D(
            xtitle,
            s=xtitle_size * text_scale * gscale,
            font=title_font,
            c=xtitle_color,
            justify=jus,
            depth=title_depth,
            italic=xtitle_italic,
        )
        if xtitle_backface_color is None:
            xtitle_backface_color = 1 - np.array(get_color(xtitle_color))
        xt.backcolor(xtitle_backface_color)

        shift = 0
        if xlab:  # xlab is the last created numeric text label..
            lt0, lt1 = xlab.bounds()[2:4]
            shift = lt1 - lt0

        T = LinearTransform()
        T.rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
        T.set_position(
            [
                (xoffs + xtitle_position) * dx,
                -(yoffs + xtick_length / 2) * dy - shift,
                zoffs * dz,
            ]
        )
        T.rotate_x(xaxis_rotation)
        T.translate([0, xshift_along_y * dy, xyshift * dz + xshift_along_z * dz])
        xt.apply_transform(T)

        xt.use_bounds(x_use_bounds)
        if xtitle == " ":
            xt.use_bounds(False)
        xt.name = "xtitle"
        titles.append(xt)
        if xtitle_box:
            titles.append(xt.box(scale=1.1).use_bounds(x_use_bounds))

    if ytitle:
        xRot, yRot, zRot = 0, 0, 0
        if utils.is_sequence(ytitle_rotation):  # unpck 3 rotations
            zRot, yRot, xRot = ytitle_rotation
        else:
            zRot = ytitle_rotation
            if len(ytitle) > 3:
                zRot += 90
                ytitle_position *= 0.975
        if zRot < 0:
            zRot += 360  # deal with negative angles

        if utils.is_sequence(ytitle_offset):
            xoffs, yoffs, zoffs = ytitle_offset
        else:
            xoffs, yoffs, zoffs = ytitle_offset, 0, 0

        if ytitle_justify is not None:
            jus = ytitle_justify
        else:
            jus = "center-right"
            if zRot:
                if zRot > 24:
                    jus = "bottom-right"
                if zRot > 112:
                    jus = "left-bottom"
                if zRot > 157:
                    jus = "center-left"
                if zRot > 202:
                    jus = "top-left"
                if zRot > 292:
                    jus = "top-right"
                if zRot > 337:
                    jus = "right-center"

        yt = shapes.Text3D(
            ytitle,
            s=ytitle_size * text_scale * gscale,
            font=title_font,
            c=ytitle_color,
            justify=jus,
            depth=title_depth,
            italic=ytitle_italic,
        )
        if ytitle_backface_color is None:
            ytitle_backface_color = 1 - np.array(get_color(ytitle_color))
        yt.backcolor(ytitle_backface_color)

        shift = 0
        if ylab:  # this is the last created num label..
            lt0, lt1 = ylab.bounds()[0:2]
            shift = lt1 - lt0

        T = LinearTransform()
        T.rotate_x(xRot).rotate_y(yRot).rotate_z(zRot)
        T.set_position(
            [
                -(xoffs + ytick_length / 2) * dx - shift,
                (yoffs + ytitle_position) * dy,
                zoffs * dz,
            ]
        )
        T.rotate_y(yaxis_rotation)
        T.translate([yshift_along_x * dx, 0, xyshift * dz + yshift_along_z * dz])
        yt.apply_transform(T)

        yt.use_bounds(y_use_bounds)
        if ytitle == " ":
            yt.use_bounds(False)
        yt.name = "ytitle"
        titles.append(yt)
        if ytitle_box:
            titles.append(yt.box(scale=1.1).use_bounds(y_use_bounds))

    if ztitle:
        xRot, yRot, zRot = 0, 0, 0
        if utils.is_sequence(ztitle_rotation):  # unpck 3 rotations
            xRot, yRot, zRot = ztitle_rotation
        else:
            xRot = ztitle_rotation
            if len(ztitle) > 3:
                xRot += 90
                ztitle_position *= 0.975
        if xRot < 0:
            xRot += 360  # deal with negative angles

        if ztitle_justify is not None:
            jus = ztitle_justify
        else:
            jus = "center-right"
            if xRot:
                if xRot > 24:
                    jus = "bottom-right"
                if xRot > 112:
                    jus = "left-bottom"
                if xRot > 157:
                    jus = "center-left"
                if xRot > 202:
                    jus = "top-left"
                if xRot > 292:
                    jus = "top-right"
                if xRot > 337:
                    jus = "right-center"

        zt = shapes.Text3D(
            ztitle,
            s=ztitle_size * text_scale * gscale,
            font=title_font,
            c=ztitle_color,
            justify=jus,
            depth=title_depth,
            italic=ztitle_italic,
        )

        if ztitle_backface_color is None:
            ztitle_backface_color = 1 - np.array(get_color(ztitle_color))
        zt.backcolor(ztitle_backface_color)

        angle = np.arctan2(dy, dx) * 57.3
        shift = 0
        if zlab:  # this is the last created one..
            lt0, lt1 = zlab.bounds()[0:2]
            shift = lt1 - lt0

        T = LinearTransform()
        T.rotate_x(90 + zRot).rotate_y(-xRot).rotate_z(angle + yRot)
        T.set_position(
            [
                -(ztitle_offset + ztick_length / 5) * dx - shift,
                -(ztitle_offset + ztick_length / 5) * dy - shift,
                ztitle_position * dz,
            ]
        )
        T.rotate_z(zaxis_rotation)
        T.translate([zshift_along_x * dx, zxshift * dy + zshift_along_y * dy, 0])
        zt.apply_transform(T)

        zt.use_bounds(z_use_bounds)
        if ztitle == " ":
            zt.use_bounds(False)
        zt.name = "ztitle"
        titles.append(zt)

    ################################################### header title
    if htitle:
        if htitle_font is None:
            htitle_font = title_font
        if htitle_color is None:
            htitle_color = xtitle_color
        htit = shapes.Text3D(
            htitle,
            s=htitle_size * gscale * text_scale,
            font=htitle_font,
            c=htitle_color,
            justify=htitle_justify,
            depth=title_depth,
            italic=htitle_italic,
        )
        if htitle_backface_color is None:
            htitle_backface_color = 1 - np.array(get_color(htitle_color))
            htit.backcolor(htitle_backface_color)
        htit.rotate_x(htitle_rotation)
        wpos = [
            htitle_offset[0] * dx,
            (1 + htitle_offset[1]) * dy,
            htitle_offset[2] * dz,
        ]
        htit.shift(np.array(wpos) + [0, 0, xyshift * dz])
        htit.name = "htitle"
        titles.append(htit)

    ######
    acts = titles + lines + labels + grids + framelines
    acts += highlights + majorticks + minorticks + cones
    orig = (min_bns[0], min_bns[2], min_bns[4])
    for a in acts:
        a.shift(orig)
        a.actor.PickableOff()
        a.properties.LightingOff()
    asse = Assembly(acts)
    asse.actor.PickableOff()
    asse.name = "Axes"
    return asse

add_global_axes(axtype=None, c=None, bounds=())

Draw axes on scene. Available axes types are

Parameters

axtype (int): - 0, no axes, - 1, draw three gray grid walls - 2, show cartesian axes from (0,0,0) - 3, show positive range of cartesian axes from (0,0,0) - 4, show a triad at bottom left - 5, show a cube at bottom left - 6, mark the corners of the bounding box - 7, draw a 3D ruler at each side of the cartesian axes - 8, show the vtkCubeAxesActor object - 9, show the bounding box outLine - 10, show three circles representing the maximum bounding box - 11, show a large grid on the x-y plane (use with zoom=8) - 12, show polar axes - 13, draw a simple ruler at the bottom of the window - 14, show the vtk default vtkCameraOrientationWidget object

Axis type-1 can be fully customized by passing a dictionary axes=dict(), see vedo.Axes for the complete list of options.

Examples:

from vedo import Box, show

b = Box(pos=(0, 0, 0), size=(80, 90, 70)).alpha(0.1)
show(
    b,
    axes={
        "xtitle": "Some long variable [a.u.]",
        "number_of_divisions": 4,
        # ...
    },
)
Source code in vedo/addons/axes.py
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
def add_global_axes(axtype=None, c=None, bounds=()) -> None:
    """
    Draw axes on scene. Available axes types are

    Parameters
    ----------
    axtype (int):
        - 0,  no axes,
        - 1,  draw three gray grid walls
        - 2,  show cartesian axes from (0,0,0)
        - 3,  show positive range of cartesian axes from (0,0,0)
        - 4,  show a triad at bottom left
        - 5,  show a cube at bottom left
        - 6,  mark the corners of the bounding box
        - 7,  draw a 3D ruler at each side of the cartesian axes
        - 8,  show the `vtkCubeAxesActor` object
        - 9,  show the bounding box outLine
        - 10, show three circles representing the maximum bounding box
        - 11, show a large grid on the x-y plane (use with zoom=8)
        - 12, show polar axes
        - 13, draw a simple ruler at the bottom of the window
        - 14, show the vtk default `vtkCameraOrientationWidget` object

    Axis type-1 can be fully customized by passing a dictionary `axes=dict()`,
    see `vedo.Axes` for the complete list of options.

    Examples:
        ```python
        from vedo import Box, show

        b = Box(pos=(0, 0, 0), size=(80, 90, 70)).alpha(0.1)
        show(
            b,
            axes={
                "xtitle": "Some long variable [a.u.]",
                "number_of_divisions": 4,
                # ...
            },
        )
        ```
    """
    plt = vedo.current_plotter()
    if plt is None:
        return

    if axtype is not None:
        plt.axes = axtype  # override

    r = plt.renderers.index(plt.renderer)

    if not plt.axes:
        return

    if c is None:  # automatic black or white
        c = (0.9, 0.9, 0.9)
        if np.sum(plt.renderer.GetBackground()) > 1.5:
            c = (0.1, 0.1, 0.1)
    else:
        c = get_color(c)  # for speed

    if not plt.renderer:
        return

    if plt.axes_instances[r]:
        return

    ############################################################
    # custom grid walls
    if plt.axes == 1 or plt.axes is True or isinstance(plt.axes, dict):
        if len(bounds) == 6:
            bnds = bounds
            xrange = (bnds[0], bnds[1])
            yrange = (bnds[2], bnds[3])
            zrange = (bnds[4], bnds[5])
        else:
            xrange = None
            yrange = None
            zrange = None

        if isinstance(plt.axes, dict):
            plt.axes.update({"use_global": True})
            # protect from invalid camelCase options from vedo<=2.3
            for k in plt.axes:
                if k.lower() != k:
                    return
            if "xrange" in plt.axes:
                xrange = plt.axes.pop("xrange")
            if "yrange" in plt.axes:
                yrange = plt.axes.pop("yrange")
            if "zrange" in plt.axes:
                zrange = plt.axes.pop("zrange")
            asse = Axes(**plt.axes, xrange=xrange, yrange=yrange, zrange=zrange)
        else:
            asse = Axes(xrange=xrange, yrange=yrange, zrange=zrange)

        plt.add(asse)
        plt.axes_instances[r] = asse

    elif plt.axes in (2, 3):
        x0, x1, y0, y1, z0, z1 = plt.renderer.ComputeVisiblePropBounds()
        xcol, ycol, zcol = "dr", "dg", "db"
        s = 1
        alpha = 1
        centered = False
        dx, dy, dz = x1 - x0, y1 - y0, z1 - z0
        aves = np.sqrt(dx * dx + dy * dy + dz * dz) / 2
        x0, x1 = min(x0, 0), max(x1, 0)
        y0, y1 = min(y0, 0), max(y1, 0)
        z0, z1 = min(z0, 0), max(z1, 0)

        if plt.axes == 3:
            if x1 > 0:
                x0 = 0
            if y1 > 0:
                y0 = 0
            if z1 > 0:
                z0 = 0

        dx, dy, dz = x1 - x0, y1 - y0, z1 - z0
        acts = []
        if x0 * x1 <= 0 or y0 * z1 <= 0 or z0 * z1 <= 0:  # some ranges contain origin
            zero = shapes.Sphere(r=aves / 120 * s, c="k", alpha=alpha, res=10)
            acts += [zero]

        if dx > aves / 100:
            xl = shapes.Cylinder(
                [[x0, 0, 0], [x1, 0, 0]], r=aves / 250 * s, c=xcol, alpha=alpha
            )
            xc = shapes.Cone(
                pos=[x1, 0, 0],
                c=xcol,
                alpha=alpha,
                r=aves / 100 * s,
                height=aves / 25 * s,
                axis=[1, 0, 0],
                res=10,
            )
            wpos = [x1, -aves / 25 * s, 0]  # aligned to arrow tip
            if centered:
                wpos = [(x0 + x1) / 2, -aves / 25 * s, 0]
            xt = shapes.Text3D("x", pos=wpos, s=aves / 40 * s, c=xcol)
            acts += [xl, xc, xt]

        if dy > aves / 100:
            yl = shapes.Cylinder(
                [[0, y0, 0], [0, y1, 0]], r=aves / 250 * s, c=ycol, alpha=alpha
            )
            yc = shapes.Cone(
                pos=[0, y1, 0],
                c=ycol,
                alpha=alpha,
                r=aves / 100 * s,
                height=aves / 25 * s,
                axis=[0, 1, 0],
                res=10,
            )
            wpos = [-aves / 40 * s, y1, 0]
            if centered:
                wpos = [-aves / 40 * s, (y0 + y1) / 2, 0]
            yt = shapes.Text3D("y", pos=(0, 0, 0), s=aves / 40 * s, c=ycol)
            yt.rotate_z(90)
            yt.pos(wpos)
            acts += [yl, yc, yt]

        if dz > aves / 100:
            zl = shapes.Cylinder(
                [[0, 0, z0], [0, 0, z1]], r=aves / 250 * s, c=zcol, alpha=alpha
            )
            zc = shapes.Cone(
                pos=[0, 0, z1],
                c=zcol,
                alpha=alpha,
                r=aves / 100 * s,
                height=aves / 25 * s,
                axis=[0, 0, 1],
                res=10,
            )
            wpos = [-aves / 50 * s, -aves / 50 * s, z1]
            if centered:
                wpos = [-aves / 50 * s, -aves / 50 * s, (z0 + z1) / 2]
            zt = shapes.Text3D("z", pos=(0, 0, 0), s=aves / 40 * s, c=zcol)
            zt.rotate_z(45)
            zt.rotate_x(90)
            zt.pos(wpos)
            acts += [zl, zc, zt]
        for a in acts:
            a.actor.PickableOff()
        asse = Assembly(acts)
        asse.actor.PickableOff()
        plt.add(asse)
        plt.axes_instances[r] = asse

    elif plt.axes == 4:
        axact = vtki.vtkAxesActor()
        axact.SetShaftTypeToCylinder()
        axact.SetCylinderRadius(0.03)
        axact.SetXAxisLabelText("x")
        axact.SetYAxisLabelText("y")
        axact.SetZAxisLabelText("z")
        axact.GetXAxisShaftProperty().SetColor(1, 0, 0)
        axact.GetYAxisShaftProperty().SetColor(0, 1, 0)
        axact.GetZAxisShaftProperty().SetColor(0, 0, 1)
        axact.GetXAxisTipProperty().SetColor(1, 0, 0)
        axact.GetYAxisTipProperty().SetColor(0, 1, 0)
        axact.GetZAxisTipProperty().SetColor(0, 0, 1)
        bc = np.array(plt.renderer.GetBackground())
        if np.sum(bc) < 1.5:
            lc = (1, 1, 1)
        else:
            lc = (0, 0, 0)
        axact.GetXAxisCaptionActor2D().GetCaptionTextProperty().BoldOff()
        axact.GetYAxisCaptionActor2D().GetCaptionTextProperty().BoldOff()
        axact.GetZAxisCaptionActor2D().GetCaptionTextProperty().BoldOff()
        axact.GetXAxisCaptionActor2D().GetCaptionTextProperty().ItalicOff()
        axact.GetYAxisCaptionActor2D().GetCaptionTextProperty().ItalicOff()
        axact.GetZAxisCaptionActor2D().GetCaptionTextProperty().ItalicOff()
        axact.GetXAxisCaptionActor2D().GetCaptionTextProperty().ShadowOff()
        axact.GetYAxisCaptionActor2D().GetCaptionTextProperty().ShadowOff()
        axact.GetZAxisCaptionActor2D().GetCaptionTextProperty().ShadowOff()
        axact.GetXAxisCaptionActor2D().GetCaptionTextProperty().SetColor(lc)
        axact.GetYAxisCaptionActor2D().GetCaptionTextProperty().SetColor(lc)
        axact.GetZAxisCaptionActor2D().GetCaptionTextProperty().SetColor(lc)
        axact.PickableOff()
        icn = Icon(axact, size=0.1)
        plt.axes_instances[r] = icn
        icn.SetInteractor(plt.interactor)
        icn.EnabledOn()
        icn.InteractiveOff()
        plt.widgets.append(icn)

    elif plt.axes == 5:
        axact = vtki.new("AnnotatedCubeActor")
        axact.GetCubeProperty().SetColor(get_color(settings.annotated_cube_color))
        axact.SetTextEdgesVisibility(0)
        axact.SetFaceTextScale(settings.annotated_cube_text_scale)
        axact.SetXPlusFaceText(settings.annotated_cube_texts[0])  # XPlus
        axact.SetXMinusFaceText(settings.annotated_cube_texts[1])  # XMinus
        axact.SetYPlusFaceText(settings.annotated_cube_texts[2])  # YPlus
        axact.SetYMinusFaceText(settings.annotated_cube_texts[3])  # YMinus
        axact.SetZPlusFaceText(settings.annotated_cube_texts[4])  # ZPlus
        axact.SetZMinusFaceText(settings.annotated_cube_texts[5])  # ZMinus
        axact.SetXFaceTextRotation(settings.annotated_cube_text_rotations[0])
        axact.SetYFaceTextRotation(settings.annotated_cube_text_rotations[1])
        axact.SetZFaceTextRotation(settings.annotated_cube_text_rotations[2])

        if settings.annotated_cube_text_color is None:  # use default
            axact.GetXPlusFaceProperty().SetColor(get_color("r"))
            axact.GetXMinusFaceProperty().SetColor(get_color("dr"))
            axact.GetYPlusFaceProperty().SetColor(get_color("g"))
            axact.GetYMinusFaceProperty().SetColor(get_color("dg"))
            axact.GetZPlusFaceProperty().SetColor(get_color("b"))
            axact.GetZMinusFaceProperty().SetColor(get_color("db"))
        else:  # use single user color
            ac = get_color(settings.annotated_cube_text_color)
            axact.GetXPlusFaceProperty().SetColor(ac)
            axact.GetXMinusFaceProperty().SetColor(ac)
            axact.GetYPlusFaceProperty().SetColor(ac)
            axact.GetYMinusFaceProperty().SetColor(ac)
            axact.GetZPlusFaceProperty().SetColor(ac)
            axact.GetZMinusFaceProperty().SetColor(ac)

        axact.PickableOff()
        icn = Icon(axact, size=0.06)
        plt.axes_instances[r] = icn
        icn.SetInteractor(plt.interactor)
        icn.EnabledOn()
        icn.InteractiveOff()
        plt.widgets.append(icn)

    elif plt.axes == 6:
        ocf = vtki.new("OutlineCornerFilter")
        ocf.SetCornerFactor(0.1)
        largestact, sz = None, -1
        for a in plt.objects:
            try:
                if a.pickable():
                    b = a.bounds()
                    if b is None:
                        return
                    d = max(b[1] - b[0], b[3] - b[2], b[5] - b[4])
                    if sz < d:
                        largestact = a
                        sz = d
            except AttributeError:
                pass

        try:
            ocf.SetInputData(largestact)
        except TypeError:
            try:
                ocf.SetInputData(largestact.dataset)
            except (TypeError, AttributeError):
                return
        ocf.Update()

        oc_mapper = vtki.new("HierarchicalPolyDataMapper")
        oc_mapper.SetInputConnection(0, ocf.GetOutputPort(0))
        oc_actor = vtki.vtkActor()
        oc_actor.SetMapper(oc_mapper)
        bc = np.array(plt.renderer.GetBackground())
        if np.sum(bc) < 1.5:
            lc = (1, 1, 1)
        else:
            lc = (0, 0, 0)
        oc_actor.GetProperty().SetColor(lc)
        oc_actor.PickableOff()
        oc_actor.UseBoundsOn()
        plt.axes_instances[r] = oc_actor
        plt.add(oc_actor)

    elif plt.axes == 7:
        vbb = compute_visible_bounds()[0]
        rulax = RulerAxes(vbb, c=c, xtitle="x - ", ytitle="y - ", ztitle="z - ")
        plt.axes_instances[r] = rulax
        if not rulax:
            return
        rulax.actor.UseBoundsOn()
        rulax.actor.PickableOff()
        plt.add(rulax)

    elif plt.axes == 8:
        vbb = compute_visible_bounds()[0]
        ca = vtki.new("CubeAxesActor")
        ca.SetBounds(vbb)
        ca.SetCamera(plt.renderer.GetActiveCamera())
        ca.GetXAxesLinesProperty().SetColor(c)
        ca.GetYAxesLinesProperty().SetColor(c)
        ca.GetZAxesLinesProperty().SetColor(c)
        for i in range(3):
            ca.GetLabelTextProperty(i).SetColor(c)
            ca.GetTitleTextProperty(i).SetColor(c)
        # ca.SetTitleOffset(5)
        ca.SetFlyMode(3)
        ca.SetXTitle("x")
        ca.SetYTitle("y")
        ca.SetZTitle("z")
        ca.PickableOff()
        ca.UseBoundsOff()
        plt.axes_instances[r] = ca
        plt.renderer.AddActor(ca)

    elif plt.axes == 9:
        vbb = compute_visible_bounds()[0]
        src = vtki.new("CubeSource")
        src.SetXLength(vbb[1] - vbb[0])
        src.SetYLength(vbb[3] - vbb[2])
        src.SetZLength(vbb[5] - vbb[4])
        src.Update()
        ca = Mesh(src.GetOutput(), c, 0.5).wireframe(True)
        ca.pos((vbb[0] + vbb[1]) / 2, (vbb[3] + vbb[2]) / 2, (vbb[5] + vbb[4]) / 2)
        ca.actor.PickableOff()
        ca.actor.UseBoundsOff()
        plt.axes_instances[r] = ca
        plt.add(ca)

    elif plt.axes == 10:
        vbb = compute_visible_bounds()[0]
        x0 = (vbb[0] + vbb[1]) / 2, (vbb[3] + vbb[2]) / 2, (vbb[5] + vbb[4]) / 2
        rx, ry, rz = (vbb[1] - vbb[0]) / 2, (vbb[3] - vbb[2]) / 2, (vbb[5] - vbb[4]) / 2
        # compute diagonal length of the bounding box
        rm = np.sqrt(rx**2 + ry**2 + rz**2)
        d = 0.005 * rm
        xc = shapes.Disc(x0, r1=rm, r2=rm + d, c="lr", res=(1, 120))
        yc = shapes.Disc(x0, r1=rm, r2=rm + d, c="lg", res=(1, 120)).rotate_x(90)
        zc = shapes.Disc(x0, r1=rm, r2=rm + d, c="lb", res=(1, 120)).rotate_y(90)
        xc.pickable(0).lighting("off")
        yc.pickable(0).lighting("off")
        zc.pickable(0).lighting("off")
        ca = xc + yc + zc
        ca.pickable(False)
        ca.actor.UseBoundsOff()
        plt.axes_instances[r] = ca
        plt.add(ca)

    elif plt.axes == 11:
        vbb, ss = compute_visible_bounds()[0:2]
        xpos, ypos = (vbb[1] + vbb[0]) / 2, (vbb[3] + vbb[2]) / 2
        gs = sum(ss) * 3
        gr = shapes.Grid((xpos, ypos, vbb[4]), s=(gs, gs), res=(11, 11), c=c, alpha=0.1)
        gr.lighting("off").actor.PickableOff()
        gr.actor.UseBoundsOff()
        plt.axes_instances[r] = gr
        plt.add(gr)

    elif plt.axes == 12:
        polaxes = vtki.new("PolarAxesActor")
        vbb = compute_visible_bounds()[0]

        polaxes.SetPolarAxisTitle("radial distance")
        polaxes.SetPole(0, 0, vbb[4])
        rd = max(abs(vbb[0]), abs(vbb[2]), abs(vbb[1]), abs(vbb[3]))
        polaxes.SetMaximumRadius(rd)
        try:  # fails in vtk 9.5
            polaxes.AutoSubdividePolarAxisOff()
            polaxes.SetNumberOfPolarAxisTicks(10)
            polaxes.SetNumberOfPolarAxisTicks(5)
        except Exception:
            pass
            # vedo.logger.warning("Failed to set polar axis properties")
        polaxes.SetCamera(plt.renderer.GetActiveCamera())
        polaxes.SetPolarLabelFormat("%6.1f")
        polaxes.PolarLabelVisibilityOff()  # due to bad overlap of labels

        polaxes.GetPolarArcsProperty().SetColor(c)
        polaxes.GetPolarAxisProperty().SetColor(c)
        polaxes.GetPolarAxisTitleTextProperty().SetColor(c)
        polaxes.GetPolarAxisLabelTextProperty().SetColor(c)
        polaxes.GetLastRadialAxisTextProperty().SetColor(c)
        polaxes.GetSecondaryRadialAxesTextProperty().SetColor(c)
        polaxes.GetSecondaryRadialAxesProperty().SetColor(c)
        polaxes.GetSecondaryPolarArcsProperty().SetColor(c)

        polaxes.SetMinimumAngle(0.0)
        polaxes.SetMaximumAngle(315.0)
        polaxes.UseBoundsOn()
        polaxes.PickableOff()
        plt.axes_instances[r] = polaxes
        plt.renderer.AddActor(polaxes)

    elif plt.axes == 13:
        # draws a simple ruler at the bottom of the window
        ls = vtki.new("LegendScaleActor")
        ls.RightAxisVisibilityOff()
        ls.TopAxisVisibilityOff()
        ls.LeftAxisVisibilityOff()
        ls.LegendVisibilityOff()
        ls.SetBottomBorderOffset(50)
        ls.GetBottomAxis().SetNumberOfMinorTicks(1)
        ls.GetBottomAxis().SetFontFactor(1.1)
        ls.GetBottomAxis().GetProperty().SetColor(c)
        ls.GetBottomAxis().GetProperty().SetOpacity(1.0)
        ls.GetBottomAxis().GetProperty().SetLineWidth(2)
        ls.GetBottomAxis().GetLabelTextProperty().SetColor(c)
        ls.GetBottomAxis().GetLabelTextProperty().BoldOff()
        ls.GetBottomAxis().GetLabelTextProperty().ItalicOff()
        pr = ls.GetBottomAxis().GetLabelTextProperty()
        pr.SetFontFamily(vtki.VTK_FONT_FILE)
        pr.SetFontFile(utils.get_font_path(settings.default_font))
        ls.PickableOff()
        # if not plt.renderer.GetActiveCamera().GetParallelProjection():
        #     vedo.logger.warning("Axes type 13 should be used with parallel projection")
        plt.axes_instances[r] = ls
        plt.renderer.AddActor(ls)

    elif plt.axes == 14:
        try:
            cow = vtki.new("CameraOrientationWidget")
            cow.SetParentRenderer(plt.renderer)
            cow.On()
            plt.axes_instances[r] = cow
        except ImportError:
            vedo.logger.warning("axes mode 14 is unavailable in this vtk version")

    else:
        e = "Keyword axes type must be in range [0-13]."
        e += "Available axes types are:\n\n"
        e += "0 = no axes\n"
        e += "1 = draw three customizable gray grid walls\n"
        e += "2 = show cartesian axes from (0,0,0)\n"
        e += "3 = show positive range of cartesian axes from (0,0,0)\n"
        e += "4 = show a triad at bottom left\n"
        e += "5 = show a cube at bottom left\n"
        e += "6 = mark the corners of the bounding box\n"
        e += "7 = draw a 3D ruler at each side of the cartesian axes\n"
        e += "8 = show the vtkCubeAxesActor object\n"
        e += "9 = show the bounding box outline\n"
        e += "10 = show three circles representing the maximum bounding box\n"
        e += "11 = show a large grid on the x-y plane (use with zoom=8)\n"
        e += "12 = show polar axes\n"
        e += "13 = draw a simple ruler at the bottom of the window\n"
        e += "14 = show the CameraOrientationWidget object"
        vedo.logger.warning(e)

    if not plt.axes_instances[r]:
        plt.axes_instances[r] = True

widgets

Button

Bases: Text2D

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

Source code in vedo/addons/widgets.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
class Button(vedo.shapes.Text2D):
    """
    Build a Button object to be shown in the rendering window.
    """

    def __init__(
        self,
        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,
    ):
        super().__init__()

        self.name = "Button"
        self.status_idx = 0
        self.spacer = " "
        self.states = states

        if not utils.is_sequence(c):
            c = [c]
        self.colors = c

        if not utils.is_sequence(bc):
            bc = [bc]
        self.bcolors = bc

        assert len(c) == len(bc), "in Button color number mismatch!"

        self.function = fnc
        self.function_id = None

        self.status(0)

        if font == "courier":
            font = font.capitalize()
        self.font(font).bold(bold).italic(italic)

        self.alpha(alpha).angle(angle)
        self.size(size / 20)
        self.pos(pos, "center")
        self.pickable(1)

    def status(self, s=None) -> Button:
        """Set/Get the status of the button."""
        if s is None:
            return self.states[self.status_idx]

        if isinstance(s, str):
            s = self.states.index(s)
        self.status_idx = s
        self.text(self.spacer + self.states[s] + self.spacer)
        s = s % len(self.bcolors)
        self.color(self.colors[s])
        self.background(self.bcolors[s])
        return self

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

status(s=None)

Set/Get the status of the button.

Source code in vedo/addons/widgets.py
203
204
205
206
207
208
209
210
211
212
213
214
215
def status(self, s=None) -> Button:
    """Set/Get the status of the button."""
    if s is None:
        return self.states[self.status_idx]

    if isinstance(s, str):
        s = self.states.index(s)
    self.status_idx = s
    self.text(self.spacer + self.states[s] + self.spacer)
    s = s % len(self.bcolors)
    self.color(self.colors[s])
    self.background(self.bcolors[s])
    return self

switch()

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

Source code in vedo/addons/widgets.py
217
218
219
220
221
def switch(self) -> Button:
    """Change/cycle button status to the next defined status in states list."""
    self.status_idx = (self.status_idx + 1) % len(self.states)
    self.status(self.status_idx)
    return self

ButtonWidget

Create a button widget.

Source code in vedo/addons/widgets.py
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
class ButtonWidget:
    """
    Create a button widget.
    """

    def __init__(
        self,
        function,
        states=(),
        c=("white"),
        bc=("green4"),
        alpha=1.0,
        font="Calco",
        size=100,
        plotter=None,
    ):
        self.widget = vtki.new("ButtonWidget")
        self.name = "ButtonWidget"

        self.function = function
        self.states = states
        self.colors = c
        self.background_colors = bc
        self.plotter = plotter
        self.size = size

        assert len(states) == len(c), "states and colors must have the same length"
        assert len(states) == len(bc), (
            "states and background colors must have the same length"
        )

        self.interactor = None
        if plotter is not None:
            self.interactor = plotter.interactor
            self.widget.SetInteractor(plotter.interactor)
        else:
            plt = vedo.current_plotter()
            if plt:
                self.interactor = plt.interactor
                self.widget.SetInteractor(self.interactor)

        self.representation = vtki.new("TexturedButtonRepresentation2D")
        self.representation.SetNumberOfStates(len(states))
        for i, state in enumerate(states):
            if isinstance(state, vedo.Image):
                state = state.dataset

            elif isinstance(state, str):
                txt = state
                tp = vtki.vtkTextProperty()
                tp.BoldOff()
                tp.FrameOff()
                col = c[i]
                tp.SetColor(vedo.get_color(col))
                tp.ShadowOff()
                tp.ItalicOff()
                col = bc[i]
                tp.SetBackgroundColor(vedo.get_color(col))
                tp.SetBackgroundOpacity(alpha)
                tp.UseTightBoundingBoxOff()
                width, height = 100 * len(txt), 1000

                fpath = vedo.utils.get_font_path(font)
                tp.SetFontFamily(vtki.VTK_FONT_FILE)
                tp.SetFontFile(fpath)

                tr = vtki.new("TextRenderer")
                fs = tr.GetConstrainedFontSize(txt, tp, width, height, 500)
                tp.SetFontSize(fs)

                img = vtki.vtkImageData()
                tr.RenderString(tp, txt, img, [width, height], 500)
                state = img

            self.representation.SetButtonTexture(i, state)

        self.widget.SetRepresentation(self.representation)
        self.widget.AddObserver("StateChangedEvent", function)

    def __del__(self):
        self.widget.Off()
        self.widget.SetInteractor(None)
        self.widget.SetRepresentation(None)
        self.representation = None
        self.interactor = None
        self.function = None
        self.states = ()
        self.widget = None
        self.plotter = None

    def pos(self, pos):
        """Set the position of the button widget."""
        assert len(pos) == 2, "pos must be a 2D position"
        if not self.plotter:
            vedo.logger.warning(
                "ButtonWidget: pos() can only be used if a Plotter is provided"
            )
            return self
        coords = vtki.vtkCoordinate()
        coords.SetCoordinateSystemToNormalizedDisplay()
        coords.SetValue(pos[0], pos[1])
        sz = self.size
        ren = self.plotter.renderer
        p = coords.GetComputedDisplayValue(ren)
        bds = [0, 0, 0, 0, 0, 0]
        bds[0] = p[0] - sz
        bds[1] = bds[0] + sz
        bds[2] = p[1] - sz
        bds[3] = bds[2] + sz
        self.representation.SetPlaceFactor(1)
        self.representation.PlaceWidget(bds)
        return self

    def enable(self):
        """Enable the button widget."""
        self.widget.On()
        return self

    def disable(self):
        """Disable the button widget."""
        self.widget.Off()
        return self

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

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

    @state.setter
    def state(self, i):
        """Set the current state."""
        self.representation.SetState(i)

state property writable

Return the current state.

disable()

Disable the button widget.

Source code in vedo/addons/widgets.py
132
133
134
135
def disable(self):
    """Disable the button widget."""
    self.widget.Off()
    return self

enable()

Enable the button widget.

Source code in vedo/addons/widgets.py
127
128
129
130
def enable(self):
    """Enable the button widget."""
    self.widget.On()
    return self

next_state()

Change to the next state.

Source code in vedo/addons/widgets.py
137
138
139
140
def next_state(self):
    """Change to the next state."""
    self.representation.NextState()
    return self

pos(pos)

Set the position of the button widget.

Source code in vedo/addons/widgets.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
def pos(self, pos):
    """Set the position of the button widget."""
    assert len(pos) == 2, "pos must be a 2D position"
    if not self.plotter:
        vedo.logger.warning(
            "ButtonWidget: pos() can only be used if a Plotter is provided"
        )
        return self
    coords = vtki.vtkCoordinate()
    coords.SetCoordinateSystemToNormalizedDisplay()
    coords.SetValue(pos[0], pos[1])
    sz = self.size
    ren = self.plotter.renderer
    p = coords.GetComputedDisplayValue(ren)
    bds = [0, 0, 0, 0, 0, 0]
    bds[0] = p[0] - sz
    bds[1] = bds[0] + sz
    bds[2] = p[1] - sz
    bds[3] = bds[2] + sz
    self.representation.SetPlaceFactor(1)
    self.representation.PlaceWidget(bds)
    return self

DrawingWidget

3D widget for tracing on planar props.

Source code in vedo/addons/widgets.py
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
class DrawingWidget:
    """
    3D widget for tracing on planar props.
    """

    def __init__(self, obj, c="green5", lw=4, closed=False, snap_to_image=False):
        self.widget = vtki.new("ImageTracerWidget")
        self.name = "DrawingWidget"

        self.line = None
        self.line_properties = self.widget.GetLineProperty()
        self.line_properties.SetColor(vedo.get_color(c))
        self.line_properties.SetLineWidth(lw)
        self.callback_id = None
        self.event_name = "EndInteractionEvent"

        plt = vedo.current_plotter()
        if plt:
            self.widget.SetInteractor(plt.interactor)
            if plt.renderer:
                self.widget.SetDefaultRenderer(plt.renderer)

        try:
            self.widget.SetViewProp(obj.actor)
        except AttributeError:
            self.widget.SetViewProp(obj)

        if closed:
            closing_radius = 1e10
            self.widget.SetAutoClose(1)
            self.widget.SetCaptureRadius(closing_radius)

        self.widget.SetProjectToPlane(0)
        self.widget.SetProjectionNormal(2)
        self.widget.SetProjectionPosition(0)
        self.widget.SetSnapToImage(snap_to_image)

    def callback(self, widget, _event_name) -> None:
        """Callback function for the widget."""
        path = vtki.vtkPolyData()
        widget.GetPath(path)
        self.line = vedo.shapes.Line(path, c=self.line_properties.GetColor())

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

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

    @interactor.setter
    def interactor(self, value):
        """Set the interactor for the widget."""
        self.widget.SetInteractor(value)

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

    @renderer.setter
    def renderer(self, value):
        """Set the renderer for the widget."""
        self.widget.SetDefaultRenderer(value)

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

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

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

    def remove(self) -> None:
        """Remove the widget."""
        self.widget.Off()
        self.widget.RemoveObserver(self.callback_id)
        self.widget.SetInteractor(None)
        self.line = None
        self.line_properties = None
        self.callback_id = None
        self.widget = None

interactor property writable

Return the interactor for the widget.

renderer property writable

Return the renderer for the widget.

add_observer(event, func, priority=1)

Add an observer to the widget.

Source code in vedo/addons/widgets.py
267
268
269
270
271
def add_observer(self, event, func, priority=1) -> int:
    """Add an observer to the widget."""
    event = utils.get_vtk_name_event(event)
    cid = self.widget.AddObserver(event, func, priority)
    return cid

callback(widget, _event_name)

Callback function for the widget.

Source code in vedo/addons/widgets.py
261
262
263
264
265
def callback(self, widget, _event_name) -> None:
    """Callback function for the widget."""
    path = vtki.vtkPolyData()
    widget.GetPath(path)
    self.line = vedo.shapes.Line(path, c=self.line_properties.GetColor())

freeze(value=True)

Freeze the widget by disabling interaction.

Source code in vedo/addons/widgets.py
305
306
307
308
def freeze(self, value=True) -> Self:
    """Freeze the widget by disabling interaction."""
    self.widget.SetInteraction(not value)
    return self

off()

Disactivate/Disable the widget.

Source code in vedo/addons/widgets.py
300
301
302
303
def off(self) -> None:
    """Disactivate/Disable the widget."""
    self.widget.Off()
    self.widget.RemoveObserver(self.callback_id)

on()

Activate/Enable the widget.

Source code in vedo/addons/widgets.py
293
294
295
296
297
298
def on(self) -> Self:
    """Activate/Enable the widget."""
    self.widget.On()
    ev_name = vedo.utils.get_vtk_name_event(self.event_name)
    self.callback_id = self.widget.AddObserver(ev_name, self.callback, 1000)
    return self

remove()

Remove the widget.

Source code in vedo/addons/widgets.py
310
311
312
313
314
315
316
317
318
def remove(self) -> None:
    """Remove the widget."""
    self.widget.Off()
    self.widget.RemoveObserver(self.callback_id)
    self.widget.SetInteractor(None)
    self.line = None
    self.line_properties = None
    self.callback_id = None
    self.widget = None

interaction

CylinderWidget

An interactive widget to place and resize a 3D cylinder.

The cylinder axis, center, and radius can be manipulated interactively. Use add_to(plotter) to activate the widget in a scene. Read back center, radius, axis, and points in an observer callback.

Example
from vedo import Plotter, UnstructuredGrid, dataurl
from vedo.addons import CylinderWidget

def on_move(widget, event):
    print(cw.center, cw.radius, cw.axis)

domain = UnstructuredGrid(dataurl + "comb_domain.vtu")
plt = Plotter()
cw = CylinderWidget((5, 0, 30), domain, r=2, axis=(0, 0, 1))
cw.add_to(plt)
cw.add_observer("interaction", on_move)
plt.show().close()
Source code in vedo/addons/interaction.py
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
class CylinderWidget:
    """
    An interactive widget to place and resize a 3D cylinder.

    The cylinder axis, center, and radius can be manipulated interactively.
    Use `add_to(plotter)` to activate the widget in a scene.
    Read back `center`, `radius`, `axis`, and `points` in an observer callback.

    Example:
        ```python
        from vedo import Plotter, UnstructuredGrid, dataurl
        from vedo.addons import CylinderWidget

        def on_move(widget, event):
            print(cw.center, cw.radius, cw.axis)

        domain = UnstructuredGrid(dataurl + "comb_domain.vtu")
        plt = Plotter()
        cw = CylinderWidget((5, 0, 30), domain, r=2, axis=(0, 0, 1))
        cw.add_to(plt)
        cw.add_observer("interaction", on_move)
        plt.show().close()
        ```
    """

    def __init__(
        self,
        center,
        bounds,
        r=1.0,
        axis=(1, 0, 0),
        c="green5",
        alpha=0.3,
        res=12,
    ):
        """
        Create an interactive cylinder widget.

        Args:
            center (list): world coordinates of the cylinder center.
            bounds (list, vedo object): placement bounds as `[xmin,xmax, ymin,ymax, zmin,zmax]`
                or any vedo object whose `.bounds()` is used.
            r (float): cylinder radius.
            axis (list): unit vector along the cylinder axis.
            c (color): color of the cylinder surface.
            alpha (float): opacity of the cylinder surface.
            res (int, tuple): resolution as `(radial, axial)` or a single int for both.
        """
        self.name = "CylinderWidget"
        self.widget = vtki.new("ImplicitCylinderWidget")
        self.representation = vtki.new("ImplicitCylinderRepresentation")
        if utils.is_sequence(res):
            self._res_r, self._res_a = int(res[0]), int(res[1])
        else:
            self._res_r = self._res_a = int(res)

        self.widget.SetRepresentation(self.representation)
        self.representation.DrawCylinderOn()
        self.representation.GetOutlineProperty().SetOpacity(0)
        self.representation.GetSelectedOutlineProperty().SetOpacity(0)
        self.representation.GetEdgesProperty().SetOpacity(0)
        self.representation.OutlineTranslationOff()

        c_ = get_color(c)
        cp = self.representation.GetCylinderProperty()
        cp.SetColor(c_)
        cp.SetOpacity(alpha)

        sp = self.representation.GetSelectedCylinderProperty() if hasattr(
            self.representation, "GetSelectedCylinderProperty") else None
        if sp:
            sp.SetColor(get_color("red3"))
            sp.SetOpacity(min(alpha + 0.2, 1.0))

        ap = self.representation.GetAxisProperty()
        ap.SetColor(c_)
        ap.SetLineWidth(2)

        if hasattr(bounds, "bounds"):
            bounds = bounds.bounds()
        bounds = np.asarray(bounds, dtype=float)
        # Use tight bounds around the cylinder so the visual is not clipped.
        # Half-length is derived from the domain diagonal so the cylinder
        # reaches across the scene naturally.
        c = np.asarray(center, dtype=float)
        half_len = np.linalg.norm(bounds[1::2] - bounds[::2]) / 2
        pad = max(float(r), half_len)
        tight = [c[0]-pad, c[0]+pad, c[1]-pad, c[1]+pad, c[2]-pad, c[2]+pad]
        self.representation.PlaceWidget(tight)
        self.representation.SetCenter(list(c))
        self.representation.SetAxis(list(axis))
        self.representation.SetRadius(float(r))

    # ── geometry properties ───────────────────────────────────────────────

    @property
    def center(self) -> np.ndarray:
        """World position of the cylinder center."""
        return np.array(self.representation.GetCenter())

    @center.setter
    def center(self, value) -> None:
        self.representation.SetCenter(list(value))

    @property
    def radius(self) -> float:
        """Radius of the cylinder."""
        return float(self.representation.GetRadius())

    @radius.setter
    def radius(self, value) -> None:
        self.representation.SetRadius(float(value))

    @property
    def axis(self) -> np.ndarray:
        """Unit vector along the cylinder axis."""
        a = np.array(self.representation.GetAxis())
        n = np.linalg.norm(a)
        return a / n if n > 0 else a

    @axis.setter
    def axis(self, value) -> None:
        self.representation.SetAxis(list(value))

    @property
    def length(self) -> float:
        """Cylinder length derived from the widget placement bounds."""
        b = np.array(self.representation.GetWidgetBounds())
        ax = self.axis
        corners = np.array([[b[i], b[j], b[k]]
            for i in [0, 1] for j in [2, 3] for k in [4, 5]])
        projs = corners @ ax
        return float(projs.max() - projs.min())

    @property
    def points(self) -> np.ndarray:
        """
        Return surface points of the cylinder as a (N, 3) array.
        Points are sampled with `res` circumferential divisions and 2 rows
        (top/bottom caps), derived analytically from center, axis, and radius.
        """
        ax = self.axis
        half = self.length / 2
        perp = np.array([1, 0, 0]) if abs(ax[0]) < 0.9 else np.array([0, 1, 0])
        e1 = np.cross(ax, perp); e1 /= np.linalg.norm(e1)
        e2 = np.cross(ax, e1)

        u = np.linspace(0, 2 * np.pi, self._res_r, endpoint=False)
        v = np.linspace(-half, half, self._res_a)
        uu, vv = np.meshgrid(u, v)

        pts = (
            self.center
            + self.radius * (np.cos(uu)[..., None] * e1 + np.sin(uu)[..., None] * e2)
            + vv[..., None] * ax
        )
        return pts.reshape(-1, 3)

    # ── visual styling ────────────────────────────────────────────────────

    def color(self, c) -> "CylinderWidget":
        """Set the cylinder surface color."""
        self.representation.GetCylinderProperty().SetColor(get_color(c))
        return self

    def alpha(self, value: float) -> "CylinderWidget":
        """Set the cylinder surface opacity."""
        self.representation.GetCylinderProperty().SetOpacity(value)
        return self

    # ── lifecycle ─────────────────────────────────────────────────────────

    def add_to(self, plt) -> "CylinderWidget":
        """Add the widget to a `Plotter` instance and enable it."""
        self.widget.SetInteractor(plt.interactor)
        self.widget.SetCurrentRenderer(plt.renderer)
        self.widget.On()
        if self.widget not in plt.widgets:
            plt.widgets.append(self.widget)
        return self

    def remove_from(self, plt) -> "CylinderWidget":
        """Remove the widget from a `Plotter` instance."""
        self.widget.Off()
        if self.widget in plt.widgets:
            plt.widgets.remove(self.widget)
        return self

    def on(self) -> "CylinderWidget":
        """Enable the widget."""
        self.widget.On()
        return self

    def off(self) -> "CylinderWidget":
        """Disable the widget."""
        self.widget.Off()
        return self

    def toggle(self) -> "CylinderWidget":
        """Toggle the widget on/off."""
        if self.widget.GetEnabled():
            self.widget.Off()
        else:
            self.widget.On()
        return self

    def is_enabled(self) -> bool:
        """Return True if the widget is currently enabled."""
        return bool(self.widget.GetEnabled())

    # ── observers ─────────────────────────────────────────────────────────

    def add_observer(self, event, func, priority=1) -> int:
        """Add an observer callback for a widget event."""
        event = utils.get_vtk_name_event(event)
        return self.widget.AddObserver(event, func, priority)

    def remove_observer(self, cid: int) -> "CylinderWidget":
        """Remove an observer by its callback id."""
        self.widget.RemoveObserver(cid)
        return self

    def remove_observers(self, event="") -> "CylinderWidget":
        """Remove all observers, or those for a specific event."""
        if event:
            self.widget.RemoveObservers(utils.get_vtk_name_event(event))
        else:
            self.widget.RemoveAllObservers()
        return self

    def __repr__(self) -> str:
        return (
            f"CylinderWidget(center={self.center.tolist()}, "
            f"radius={self.radius:.4g}, axis={self.axis.tolist()})"
        )

axis property writable

Unit vector along the cylinder axis.

center property writable

World position of the cylinder center.

length property

Cylinder length derived from the widget placement bounds.

points property

Return surface points of the cylinder as a (N, 3) array. Points are sampled with res circumferential divisions and 2 rows (top/bottom caps), derived analytically from center, axis, and radius.

radius property writable

Radius of the cylinder.

add_observer(event, func, priority=1)

Add an observer callback for a widget event.

Source code in vedo/addons/interaction.py
934
935
936
937
def add_observer(self, event, func, priority=1) -> int:
    """Add an observer callback for a widget event."""
    event = utils.get_vtk_name_event(event)
    return self.widget.AddObserver(event, func, priority)

add_to(plt)

Add the widget to a Plotter instance and enable it.

Source code in vedo/addons/interaction.py
894
895
896
897
898
899
900
901
def add_to(self, plt) -> "CylinderWidget":
    """Add the widget to a `Plotter` instance and enable it."""
    self.widget.SetInteractor(plt.interactor)
    self.widget.SetCurrentRenderer(plt.renderer)
    self.widget.On()
    if self.widget not in plt.widgets:
        plt.widgets.append(self.widget)
    return self

alpha(value)

Set the cylinder surface opacity.

Source code in vedo/addons/interaction.py
887
888
889
890
def alpha(self, value: float) -> "CylinderWidget":
    """Set the cylinder surface opacity."""
    self.representation.GetCylinderProperty().SetOpacity(value)
    return self

color(c)

Set the cylinder surface color.

Source code in vedo/addons/interaction.py
882
883
884
885
def color(self, c) -> "CylinderWidget":
    """Set the cylinder surface color."""
    self.representation.GetCylinderProperty().SetColor(get_color(c))
    return self

is_enabled()

Return True if the widget is currently enabled.

Source code in vedo/addons/interaction.py
928
929
930
def is_enabled(self) -> bool:
    """Return True if the widget is currently enabled."""
    return bool(self.widget.GetEnabled())

off()

Disable the widget.

Source code in vedo/addons/interaction.py
915
916
917
918
def off(self) -> "CylinderWidget":
    """Disable the widget."""
    self.widget.Off()
    return self

on()

Enable the widget.

Source code in vedo/addons/interaction.py
910
911
912
913
def on(self) -> "CylinderWidget":
    """Enable the widget."""
    self.widget.On()
    return self

remove_from(plt)

Remove the widget from a Plotter instance.

Source code in vedo/addons/interaction.py
903
904
905
906
907
908
def remove_from(self, plt) -> "CylinderWidget":
    """Remove the widget from a `Plotter` instance."""
    self.widget.Off()
    if self.widget in plt.widgets:
        plt.widgets.remove(self.widget)
    return self

remove_observer(cid)

Remove an observer by its callback id.

Source code in vedo/addons/interaction.py
939
940
941
942
def remove_observer(self, cid: int) -> "CylinderWidget":
    """Remove an observer by its callback id."""
    self.widget.RemoveObserver(cid)
    return self

remove_observers(event='')

Remove all observers, or those for a specific event.

Source code in vedo/addons/interaction.py
944
945
946
947
948
949
950
def remove_observers(self, event="") -> "CylinderWidget":
    """Remove all observers, or those for a specific event."""
    if event:
        self.widget.RemoveObservers(utils.get_vtk_name_event(event))
    else:
        self.widget.RemoveAllObservers()
    return self

toggle()

Toggle the widget on/off.

Source code in vedo/addons/interaction.py
920
921
922
923
924
925
926
def toggle(self) -> "CylinderWidget":
    """Toggle the widget on/off."""
    if self.widget.GetEnabled():
        self.widget.Off()
    else:
        self.widget.On()
    return self

LineWidget

An interactive widget to place and manipulate a 3D line segment.

Two draggable sphere handles mark the endpoints. The line can be translated, scaled, and its endpoints repositioned interactively.

Use add_to(plotter) to activate the widget in a scene. Read back p1/p2 inside an observer callback to get updated positions.

Example
from vedo import Plotter, Sphere
from vedo.addons import LineWidget

def on_move(widget, event):
    print(widget.p1, "->", widget.p2, "  length:", round(widget.length, 3))

plt = Plotter()
plt += Sphere().alpha(0.3)
lw = LineWidget((-0.5, 0, 0), (0.5, 0, 0))
lw.add_to(plt)
lw.add_observer("interaction", on_move)
plt.show().close()
Source code in vedo/addons/interaction.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
class LineWidget:
    """
    An interactive widget to place and manipulate a 3D line segment.

    Two draggable sphere handles mark the endpoints. The line can be
    translated, scaled, and its endpoints repositioned interactively.

    Use `add_to(plotter)` to activate the widget in a scene.
    Read back `p1`/`p2` inside an observer callback to get updated positions.

    Example:
        ```python
        from vedo import Plotter, Sphere
        from vedo.addons import LineWidget

        def on_move(widget, event):
            print(widget.p1, "->", widget.p2, "  length:", round(widget.length, 3))

        plt = Plotter()
        plt += Sphere().alpha(0.3)
        lw = LineWidget((-0.5, 0, 0), (0.5, 0, 0))
        lw.add_to(plt)
        lw.add_observer("interaction", on_move)
        plt.show().close()
        ```
    """

    def __init__(
        self,
        p1=(-0.5, 0, 0),
        p2=(0.5, 0, 0),
        lc="k3",
        pc="k4",
        lw=2,
        ps=10,
        alpha=1.0,
        res=2,
    ):
        """
        Create an interactive line-segment widget.

        Args:
            p1 (list): world coordinates of the first endpoint.
            p2 (list): world coordinates of the second endpoint.
            lc (color): color of the line.
            pc (color): color of the endpoint handles.
            lw (int): line width in pixels.
            ps (int): handle sphere size (pixels).
            alpha (float): opacity of the line and handles.
            res (int): number of points along the line (including endpoints).
                `points` returns exactly `res` equally-spaced points.

        Examples:
            - [line_widget.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/line_widget.py)
        """
        self.name = "LineWidget"
        self.widget = vtki.new("LineWidget2")
        self.representation = vtki.new("LineRepresentation")
        self._callback_id = None

        self.representation.SetResolution(max(1, int(res) - 1))  # VTK: npts = res+1
        self.representation.SetPoint1WorldPosition(list(p1))
        self.representation.SetPoint2WorldPosition(list(p2))

        lc_ = get_color(lc)
        pc_ = get_color(pc)

        lp = self.representation.GetLineProperty()
        lp.SetColor(lc_)
        lp.SetLineWidth(lw)
        lp.SetOpacity(alpha)
        lp.LightingOff()

        for ep_prop in (
            self.representation.GetEndPointProperty(),
            self.representation.GetEndPoint2Property(),
        ):
            ep_prop.SetColor(pc_)
            ep_prop.SetOpacity(alpha)
            ep_prop.RenderPointsAsSpheresOn()

        for sel_prop in (
            self.representation.GetSelectedEndPointProperty(),
            self.representation.GetSelectedEndPoint2Property(),
        ):
            sel_prop.SetColor(get_color("red3"))
            sel_prop.SetOpacity(alpha)
            sel_prop.RenderPointsAsSpheresOn()

        self.representation.SetHandleSize(ps)
        self.widget.SetRepresentation(self.representation)

    # ── geometry properties ───────────────────────────────────────────────

    @property
    def p1(self) -> np.ndarray:
        """World position of the first endpoint."""
        return np.array(self.representation.GetPoint1WorldPosition())

    @p1.setter
    def p1(self, value) -> None:
        self.representation.SetPoint1WorldPosition(list(value))

    @property
    def p2(self) -> np.ndarray:
        """World position of the second endpoint."""
        return np.array(self.representation.GetPoint2WorldPosition())

    @p2.setter
    def p2(self, value) -> None:
        self.representation.SetPoint2WorldPosition(list(value))

    @property
    def midpoint(self) -> np.ndarray:
        """Midpoint of the line segment."""
        return (self.p1 + self.p2) / 2

    @property
    def length(self) -> float:
        """Length of the line segment."""
        return float(np.linalg.norm(self.p2 - self.p1))

    @property
    def direction(self) -> np.ndarray:
        """Unit vector from p1 to p2. Returns zeros if the two points coincide."""
        d = self.p2 - self.p1
        n = np.linalg.norm(d)
        return d / n if n > 0 else d

    @property
    def points(self) -> np.ndarray:
        """
        Return `res` equally-spaced points along the line segment as a (res, 3)
        numpy array. `res` is the value set at construction time.
        """
        self.representation.BuildRepresentation()
        pd = vtki.new("PolyData")
        self.representation.GetPolyData(pd)
        return utils.vtk2numpy(pd.GetPoints().GetData())

    # ── visual styling ────────────────────────────────────────────────────

    def line_color(self, c) -> "LineWidget":
        """Set the color of the line."""
        self.representation.GetLineProperty().SetColor(get_color(c))
        return self

    def handle_color(self, c) -> "LineWidget":
        """Set the color of both endpoint handles."""
        c_ = get_color(c)
        self.representation.GetEndPointProperty().SetColor(c_)
        self.representation.GetEndPoint2Property().SetColor(c_)
        return self

    def lw(self, value: int) -> "LineWidget":
        """Set the line width in pixels."""
        self.representation.GetLineProperty().SetLineWidth(value)
        return self

    def ps(self, value: int) -> "LineWidget":
        """Set the handle sphere size in pixels."""
        self.representation.SetHandleSize(value)
        return self

    def show_distance(self, value=True, fmt="{:.3g}") -> "LineWidget":
        """
        Show or hide the distance annotation along the line.

        Args:
            value (bool): whether to show the annotation.
            fmt (str): Python format string used for the numeric label.
        """
        self.representation.SetDistanceAnnotationVisibility(value)
        if value:
            self.representation.SetDistanceAnnotationFormat(
                fmt.replace("{:.3g}", "%-#6.3g")
                   .replace("{:.2f}", "%-#6.2f")
                   .replace("{:.1f}", "%-#6.1f")
            )
        return self

    # ── lifecycle ─────────────────────────────────────────────────────────

    def add_to(self, plt) -> "LineWidget":
        """
        Add the widget to a `Plotter` instance and enable it.

        Args:
            plt (Plotter): the target plotter.
        """
        _p1 = self.p1.copy()
        _p2 = self.p2.copy()
        self.widget.SetInteractor(plt.interactor)
        self.widget.SetCurrentRenderer(plt.renderer)
        self.representation.PlaceWidget(plt.renderer.ComputeVisiblePropBounds())
        # PlaceWidget resets endpoint positions — restore the user-specified ones
        self.representation.SetPoint1WorldPosition(list(_p1))
        self.representation.SetPoint2WorldPosition(list(_p2))
        self.widget.On()
        if self.widget not in plt.widgets:
            plt.widgets.append(self.widget)
        return self

    def remove_from(self, plt) -> "LineWidget":
        """Remove the widget from a `Plotter` instance."""
        self.widget.Off()
        if self.widget in plt.widgets:
            plt.widgets.remove(self.widget)
        return self

    def on(self) -> "LineWidget":
        """Enable the widget."""
        self.widget.On()
        return self

    def off(self) -> "LineWidget":
        """Disable the widget."""
        self.widget.Off()
        return self

    def toggle(self) -> "LineWidget":
        """Toggle the widget on/off."""
        if self.widget.GetEnabled():
            self.widget.Off()
        else:
            self.widget.On()
        return self

    def is_enabled(self) -> bool:
        """Return True if the widget is currently enabled."""
        return bool(self.widget.GetEnabled())

    # ── observers ─────────────────────────────────────────────────────────

    def add_observer(self, event, func, priority=1) -> int:
        """
        Add an observer callback to the widget.

        The callback receives `(widget, event_name)` and can read
        `widget.p1`, `widget.p2`, `widget.length`, etc.

        Args:
            event (str): event name, e.g. `"interaction"`, `"start"`, `"end"`.
            func (callable): callback function.
            priority (int): observer priority.

        Returns:
            int: observer id (use with `remove_observer`).
        """
        event = utils.get_vtk_name_event(event)
        return self.widget.AddObserver(event, func, priority)

    def remove_observer(self, cid: int) -> "LineWidget":
        """Remove a specific observer by its id."""
        self.widget.RemoveObserver(cid)
        return self

    def remove_observers(self, event="") -> "LineWidget":
        """Remove all observers, or only those for a specific event."""
        if not event:
            self.widget.RemoveAllObservers()
        else:
            self.widget.RemoveObservers(utils.get_vtk_name_event(event))
        return self

    # ── output ────────────────────────────────────────────────────────────

    def get_line(self) -> "vedo.Line":
        """
        Return the current widget state as a `vedo.Line` object.

        Useful for extracting geometry after interaction.
        """
        pd = vtki.vtkPolyData()
        self.representation.GetPolyData(pd)
        return vedo.Line(self.p1, self.p2)

    def __repr__(self) -> str:
        return (
            f"LineWidget(p1={self.p1.tolist()}, p2={self.p2.tolist()}, "
            f"length={self.length:.4g})"
        )

direction property

Unit vector from p1 to p2. Returns zeros if the two points coincide.

length property

Length of the line segment.

midpoint property

Midpoint of the line segment.

p1 property writable

World position of the first endpoint.

p2 property writable

World position of the second endpoint.

points property

Return res equally-spaced points along the line segment as a (res, 3) numpy array. res is the value set at construction time.

add_observer(event, func, priority=1)

Add an observer callback to the widget.

The callback receives (widget, event_name) and can read widget.p1, widget.p2, widget.length, etc.

Parameters:

Name Type Description Default
event str

event name, e.g. "interaction", "start", "end".

required
func callable

callback function.

required
priority int

observer priority.

1

Returns:

Name Type Description
int int

observer id (use with remove_observer).

Source code in vedo/addons/interaction.py
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def add_observer(self, event, func, priority=1) -> int:
    """
    Add an observer callback to the widget.

    The callback receives `(widget, event_name)` and can read
    `widget.p1`, `widget.p2`, `widget.length`, etc.

    Args:
        event (str): event name, e.g. `"interaction"`, `"start"`, `"end"`.
        func (callable): callback function.
        priority (int): observer priority.

    Returns:
        int: observer id (use with `remove_observer`).
    """
    event = utils.get_vtk_name_event(event)
    return self.widget.AddObserver(event, func, priority)

add_to(plt)

Add the widget to a Plotter instance and enable it.

Parameters:

Name Type Description Default
plt Plotter

the target plotter.

required
Source code in vedo/addons/interaction.py
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
def add_to(self, plt) -> "LineWidget":
    """
    Add the widget to a `Plotter` instance and enable it.

    Args:
        plt (Plotter): the target plotter.
    """
    _p1 = self.p1.copy()
    _p2 = self.p2.copy()
    self.widget.SetInteractor(plt.interactor)
    self.widget.SetCurrentRenderer(plt.renderer)
    self.representation.PlaceWidget(plt.renderer.ComputeVisiblePropBounds())
    # PlaceWidget resets endpoint positions — restore the user-specified ones
    self.representation.SetPoint1WorldPosition(list(_p1))
    self.representation.SetPoint2WorldPosition(list(_p2))
    self.widget.On()
    if self.widget not in plt.widgets:
        plt.widgets.append(self.widget)
    return self

get_line()

Return the current widget state as a vedo.Line object.

Useful for extracting geometry after interaction.

Source code in vedo/addons/interaction.py
359
360
361
362
363
364
365
366
367
def get_line(self) -> "vedo.Line":
    """
    Return the current widget state as a `vedo.Line` object.

    Useful for extracting geometry after interaction.
    """
    pd = vtki.vtkPolyData()
    self.representation.GetPolyData(pd)
    return vedo.Line(self.p1, self.p2)

handle_color(c)

Set the color of both endpoint handles.

Source code in vedo/addons/interaction.py
239
240
241
242
243
244
def handle_color(self, c) -> "LineWidget":
    """Set the color of both endpoint handles."""
    c_ = get_color(c)
    self.representation.GetEndPointProperty().SetColor(c_)
    self.representation.GetEndPoint2Property().SetColor(c_)
    return self

is_enabled()

Return True if the widget is currently enabled.

Source code in vedo/addons/interaction.py
320
321
322
def is_enabled(self) -> bool:
    """Return True if the widget is currently enabled."""
    return bool(self.widget.GetEnabled())

line_color(c)

Set the color of the line.

Source code in vedo/addons/interaction.py
234
235
236
237
def line_color(self, c) -> "LineWidget":
    """Set the color of the line."""
    self.representation.GetLineProperty().SetColor(get_color(c))
    return self

lw(value)

Set the line width in pixels.

Source code in vedo/addons/interaction.py
246
247
248
249
def lw(self, value: int) -> "LineWidget":
    """Set the line width in pixels."""
    self.representation.GetLineProperty().SetLineWidth(value)
    return self

off()

Disable the widget.

Source code in vedo/addons/interaction.py
307
308
309
310
def off(self) -> "LineWidget":
    """Disable the widget."""
    self.widget.Off()
    return self

on()

Enable the widget.

Source code in vedo/addons/interaction.py
302
303
304
305
def on(self) -> "LineWidget":
    """Enable the widget."""
    self.widget.On()
    return self

ps(value)

Set the handle sphere size in pixels.

Source code in vedo/addons/interaction.py
251
252
253
254
def ps(self, value: int) -> "LineWidget":
    """Set the handle sphere size in pixels."""
    self.representation.SetHandleSize(value)
    return self

remove_from(plt)

Remove the widget from a Plotter instance.

Source code in vedo/addons/interaction.py
295
296
297
298
299
300
def remove_from(self, plt) -> "LineWidget":
    """Remove the widget from a `Plotter` instance."""
    self.widget.Off()
    if self.widget in plt.widgets:
        plt.widgets.remove(self.widget)
    return self

remove_observer(cid)

Remove a specific observer by its id.

Source code in vedo/addons/interaction.py
344
345
346
347
def remove_observer(self, cid: int) -> "LineWidget":
    """Remove a specific observer by its id."""
    self.widget.RemoveObserver(cid)
    return self

remove_observers(event='')

Remove all observers, or only those for a specific event.

Source code in vedo/addons/interaction.py
349
350
351
352
353
354
355
def remove_observers(self, event="") -> "LineWidget":
    """Remove all observers, or only those for a specific event."""
    if not event:
        self.widget.RemoveAllObservers()
    else:
        self.widget.RemoveObservers(utils.get_vtk_name_event(event))
    return self

show_distance(value=True, fmt='{:.3g}')

Show or hide the distance annotation along the line.

Parameters:

Name Type Description Default
value bool

whether to show the annotation.

True
fmt str

Python format string used for the numeric label.

'{:.3g}'
Source code in vedo/addons/interaction.py
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
def show_distance(self, value=True, fmt="{:.3g}") -> "LineWidget":
    """
    Show or hide the distance annotation along the line.

    Args:
        value (bool): whether to show the annotation.
        fmt (str): Python format string used for the numeric label.
    """
    self.representation.SetDistanceAnnotationVisibility(value)
    if value:
        self.representation.SetDistanceAnnotationFormat(
            fmt.replace("{:.3g}", "%-#6.3g")
               .replace("{:.2f}", "%-#6.2f")
               .replace("{:.1f}", "%-#6.1f")
        )
    return self

toggle()

Toggle the widget on/off.

Source code in vedo/addons/interaction.py
312
313
314
315
316
317
318
def toggle(self) -> "LineWidget":
    """Toggle the widget on/off."""
    if self.widget.GetEnabled():
        self.widget.Off()
    else:
        self.widget.On()
    return self

PointCloudWidget

Point cloud widget to interactively add or remove points in a point cloud.

Source code in vedo/addons/interaction.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
class PointCloudWidget:
    """
    Point cloud widget to interactively add or remove points in a point cloud.
    """

    def __init__(self, points):
        self.widget = vtki.new("PointCloudWidget")
        self.name = "PointCloudWidget"
        self.representation = self.widget.GetRepresentation()

        if utils.is_sequence(points):
            points = Points(points)
        self.points = points
        self.representation.PlacePointCloud(self.points.dataset)
        self.representation.BuildRepresentation()

    def add_to(self, plotter) -> PointCloudWidget:
        """Add the point cloud widget to a Plotter instance."""
        if not isinstance(plotter, vedo.Plotter):
            vedo.logger.error("PointCloudWidget: add_to() requires a Plotter instance")
            return self
        self.widget.SetInteractor(plotter.interactor)
        self.representation.SetRenderer(plotter.renderer)
        self.widget.On()
        return self

    def on(self) -> PointCloudWidget:
        """Activate/Enable the point cloud widget."""
        self.widget.On()
        self.widget.Render()
        return self

    def off(self) -> PointCloudWidget:
        """Disactivate/Disable the point cloud widget."""
        self.widget.Off()
        self.widget.Render()
        return self

    @property
    def interactor(self):
        """Return the current interactor."""
        return self.GetInteractor()

    @interactor.setter
    def interactor(self, iren):
        """Set the current interactor."""
        self.widget.SetInteractor(iren)
        self.representation.SetInteractor(iren)

    @property
    def renderer(self):
        """Return the current renderer."""
        return self.representation.GetRenderer()

    @renderer.setter
    def renderer(self, ren):
        """Set the current renderer."""
        self.widget.SetRenderer(ren)
        self.widget.Render()

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

    def get_point_coordinates(self) -> np.ndarray:
        """Get the coordinates of a point by its index."""
        v = [0.0, 0.0, 0.0]
        self.representation.GetPointCoordinates(v)
        return np.array(v)

interactor property writable

Return the current interactor.

renderer property writable

Return the current renderer.

add_observer(event, func, priority=1)

Add an observer to the widget.

Source code in vedo/addons/interaction.py
79
80
81
82
83
def add_observer(self, event, func, priority=1) -> int:
    """Add an observer to the widget."""
    event = utils.get_vtk_name_event(event)
    cid = self.widget.AddObserver(event, func, priority)
    return cid

add_to(plotter)

Add the point cloud widget to a Plotter instance.

Source code in vedo/addons/interaction.py
35
36
37
38
39
40
41
42
43
def add_to(self, plotter) -> PointCloudWidget:
    """Add the point cloud widget to a Plotter instance."""
    if not isinstance(plotter, vedo.Plotter):
        vedo.logger.error("PointCloudWidget: add_to() requires a Plotter instance")
        return self
    self.widget.SetInteractor(plotter.interactor)
    self.representation.SetRenderer(plotter.renderer)
    self.widget.On()
    return self

get_point_coordinates()

Get the coordinates of a point by its index.

Source code in vedo/addons/interaction.py
85
86
87
88
89
def get_point_coordinates(self) -> np.ndarray:
    """Get the coordinates of a point by its index."""
    v = [0.0, 0.0, 0.0]
    self.representation.GetPointCoordinates(v)
    return np.array(v)

off()

Disactivate/Disable the point cloud widget.

Source code in vedo/addons/interaction.py
51
52
53
54
55
def off(self) -> PointCloudWidget:
    """Disactivate/Disable the point cloud widget."""
    self.widget.Off()
    self.widget.Render()
    return self

on()

Activate/Enable the point cloud widget.

Source code in vedo/addons/interaction.py
45
46
47
48
49
def on(self) -> PointCloudWidget:
    """Activate/Enable the point cloud widget."""
    self.widget.On()
    self.widget.Render()
    return self

PointWidget

An interactive widget to place a single draggable 3D point.

Use add_to(plotter) to activate the widget in a scene. Read back pos inside an observer callback to get the current position.

Example
from vedo import Plotter, Sphere
from vedo.addons import PointWidget

def on_move(widget, event):
    print(widget.pos)

plt = Plotter()
plt += Sphere().alpha(0.3)
pw = PointWidget((0.5, 0, 0))
pw.add_to(plt)
pw.add_observer("interaction", on_move)
plt.show().close()
Source code in vedo/addons/interaction.py
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
class PointWidget:
    """
    An interactive widget to place a single draggable 3D point.

    Use `add_to(plotter)` to activate the widget in a scene.
    Read back `pos` inside an observer callback to get the current position.

    Example:
        ```python
        from vedo import Plotter, Sphere
        from vedo.addons import PointWidget

        def on_move(widget, event):
            print(widget.pos)

        plt = Plotter()
        plt += Sphere().alpha(0.3)
        pw = PointWidget((0.5, 0, 0))
        pw.add_to(plt)
        pw.add_observer("interaction", on_move)
        plt.show().close()
        ```
    """

    def __init__(
        self,
        pos=(0, 0, 0),
        c="red5",
        alpha=1.0,
        ps=0.1,
    ):
        """
        Create an interactive single-point widget.

        Args:
            pos (list): initial world coordinates of the point.
            c (color): color of the point handle.
            alpha (float): opacity of the point handle.
            ps (float): sphere radius of the point handle in world units.
        """
        self.name = "PointWidget"
        self.widget = vtki.new("HandleWidget")
        self.representation = vtki.new("SphereHandleRepresentation")
        self._pos = list(pos)

        self.representation.SetWorldPosition(list(pos))
        self.representation.SetSphereRadius(ps * 0.05)
        self.representation.GetProperty().SetColor(get_color(c))
        self.representation.GetProperty().SetOpacity(alpha)
        self.representation.GetSelectedProperty().SetColor(get_color("red3"))
        self.representation.GetSelectedProperty().SetOpacity(alpha)
        self.widget.SetRepresentation(self.representation)

    # ── geometry ──────────────────────────────────────────────────────────

    @property
    def pos(self) -> np.ndarray:
        """World position of the point."""
        return np.array(self.representation.GetWorldPosition())

    @pos.setter
    def pos(self, value) -> None:
        self._pos = list(value)
        self.representation.SetWorldPosition(list(value))

    # ── visual styling ────────────────────────────────────────────────────

    def color(self, c) -> "PointWidget":
        """Set the point color."""
        self.representation.GetProperty().SetColor(get_color(c))
        return self

    def alpha(self, value: float) -> "PointWidget":
        """Set the point opacity."""
        self.representation.GetProperty().SetOpacity(value)
        return self

    def ps(self, value: float) -> "PointWidget":
        """Set the sphere radius of the point handle."""
        self.representation.SetSphereRadius(value)
        return self

    # ── lifecycle ─────────────────────────────────────────────────────────

    def add_to(self, plt) -> "PointWidget":
        """Add the widget to a `Plotter` instance and enable it."""
        self.widget.SetInteractor(plt.interactor)
        self.widget.SetCurrentRenderer(plt.renderer)
        self.representation.SetWorldPosition(self._pos)
        self.widget.On()
        if self.widget not in plt.widgets:
            plt.widgets.append(self.widget)
        return self

    def remove_from(self, plt) -> "PointWidget":
        """Remove the widget from a `Plotter` instance."""
        self.widget.Off()
        if self.widget in plt.widgets:
            plt.widgets.remove(self.widget)
        return self

    def on(self) -> "PointWidget":
        """Enable the widget."""
        self.widget.On()
        return self

    def off(self) -> "PointWidget":
        """Disable the widget."""
        self.widget.Off()
        return self

    def toggle(self) -> "PointWidget":
        """Toggle the widget on/off."""
        if self.widget.GetEnabled():
            self.widget.Off()
        else:
            self.widget.On()
        return self

    def is_enabled(self) -> bool:
        """Return True if the widget is currently enabled."""
        return bool(self.widget.GetEnabled())

    # ── observers ─────────────────────────────────────────────────────────

    def add_observer(self, event, func, priority=1) -> int:
        """Add an observer callback for a widget event."""
        event = utils.get_vtk_name_event(event)
        return self.widget.AddObserver(event, func, priority)

    def remove_observer(self, cid: int) -> "PointWidget":
        """Remove an observer by its callback id."""
        self.widget.RemoveObserver(cid)
        return self

    def remove_observers(self, event="") -> "PointWidget":
        """Remove all observers, or those for a specific event."""
        if event:
            self.widget.RemoveObservers(utils.get_vtk_name_event(event))
        else:
            self.widget.RemoveAllObservers()
        return self

    def __repr__(self) -> str:
        return f"PointWidget(pos={self.pos.tolist()})"

pos property writable

World position of the point.

add_observer(event, func, priority=1)

Add an observer callback for a widget event.

Source code in vedo/addons/interaction.py
501
502
503
504
def add_observer(self, event, func, priority=1) -> int:
    """Add an observer callback for a widget event."""
    event = utils.get_vtk_name_event(event)
    return self.widget.AddObserver(event, func, priority)

add_to(plt)

Add the widget to a Plotter instance and enable it.

Source code in vedo/addons/interaction.py
460
461
462
463
464
465
466
467
468
def add_to(self, plt) -> "PointWidget":
    """Add the widget to a `Plotter` instance and enable it."""
    self.widget.SetInteractor(plt.interactor)
    self.widget.SetCurrentRenderer(plt.renderer)
    self.representation.SetWorldPosition(self._pos)
    self.widget.On()
    if self.widget not in plt.widgets:
        plt.widgets.append(self.widget)
    return self

alpha(value)

Set the point opacity.

Source code in vedo/addons/interaction.py
448
449
450
451
def alpha(self, value: float) -> "PointWidget":
    """Set the point opacity."""
    self.representation.GetProperty().SetOpacity(value)
    return self

color(c)

Set the point color.

Source code in vedo/addons/interaction.py
443
444
445
446
def color(self, c) -> "PointWidget":
    """Set the point color."""
    self.representation.GetProperty().SetColor(get_color(c))
    return self

is_enabled()

Return True if the widget is currently enabled.

Source code in vedo/addons/interaction.py
495
496
497
def is_enabled(self) -> bool:
    """Return True if the widget is currently enabled."""
    return bool(self.widget.GetEnabled())

off()

Disable the widget.

Source code in vedo/addons/interaction.py
482
483
484
485
def off(self) -> "PointWidget":
    """Disable the widget."""
    self.widget.Off()
    return self

on()

Enable the widget.

Source code in vedo/addons/interaction.py
477
478
479
480
def on(self) -> "PointWidget":
    """Enable the widget."""
    self.widget.On()
    return self

ps(value)

Set the sphere radius of the point handle.

Source code in vedo/addons/interaction.py
453
454
455
456
def ps(self, value: float) -> "PointWidget":
    """Set the sphere radius of the point handle."""
    self.representation.SetSphereRadius(value)
    return self

remove_from(plt)

Remove the widget from a Plotter instance.

Source code in vedo/addons/interaction.py
470
471
472
473
474
475
def remove_from(self, plt) -> "PointWidget":
    """Remove the widget from a `Plotter` instance."""
    self.widget.Off()
    if self.widget in plt.widgets:
        plt.widgets.remove(self.widget)
    return self

remove_observer(cid)

Remove an observer by its callback id.

Source code in vedo/addons/interaction.py
506
507
508
509
def remove_observer(self, cid: int) -> "PointWidget":
    """Remove an observer by its callback id."""
    self.widget.RemoveObserver(cid)
    return self

remove_observers(event='')

Remove all observers, or those for a specific event.

Source code in vedo/addons/interaction.py
511
512
513
514
515
516
517
def remove_observers(self, event="") -> "PointWidget":
    """Remove all observers, or those for a specific event."""
    if event:
        self.widget.RemoveObservers(utils.get_vtk_name_event(event))
    else:
        self.widget.RemoveAllObservers()
    return self

toggle()

Toggle the widget on/off.

Source code in vedo/addons/interaction.py
487
488
489
490
491
492
493
def toggle(self) -> "PointWidget":
    """Toggle the widget on/off."""
    if self.widget.GetEnabled():
        self.widget.Off()
    else:
        self.widget.On()
    return self

SphereWidget

An interactive widget to place and resize a 3D sphere.

A draggable handle on the sphere surface lets you resize interactively. The sphere body can be translated by clicking and dragging.

Use add_to(plotter) to activate the widget in a scene. Read back center and radius inside an observer callback.

Example
from vedo import Plotter
from vedo.addons import SphereWidget

def on_move(widget, event):
    print(widget.center, widget.radius)

plt = Plotter()
sw = SphereWidget(center=(0, 0, 0), r=1)
sw.add_to(plt)
sw.add_observer("interaction", on_move)
plt.show().close()
Source code in vedo/addons/interaction.py
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
class SphereWidget:
    """
    An interactive widget to place and resize a 3D sphere.

    A draggable handle on the sphere surface lets you resize interactively.
    The sphere body can be translated by clicking and dragging.

    Use `add_to(plotter)` to activate the widget in a scene.
    Read back `center` and `radius` inside an observer callback.

    Example:
        ```python
        from vedo import Plotter
        from vedo.addons import SphereWidget

        def on_move(widget, event):
            print(widget.center, widget.radius)

        plt = Plotter()
        sw = SphereWidget(center=(0, 0, 0), r=1)
        sw.add_to(plt)
        sw.add_observer("interaction", on_move)
        plt.show().close()
        ```
    """

    def __init__(
        self,
        center=(0, 0, 0),
        r=1.0,
        c="green5",
        alpha=0.5,
        res=24,
    ):
        """
        Create an interactive sphere widget.

        Args:
            center (list): world coordinates of the sphere center.
            r (float): radius of the sphere.
            c (color): color of the sphere surface.
            alpha (float): opacity of the sphere surface.
            res (int): sphere surface resolution (phi and theta subdivisions).
        """
        self.name = "SphereWidget"
        self.widget = vtki.new("SphereWidget2")
        self.representation = vtki.new("SphereRepresentation")

        self.representation.SetCenter(list(center))
        self.representation.SetRadius(r)
        self.representation.SetPhiResolution(res)
        self.representation.SetThetaResolution(res*2)
        self.representation.SetRepresentationToSurface()

        c_ = get_color(c)
        sp = self.representation.GetSphereProperty()
        sp.SetColor(c_)
        sp.SetOpacity(alpha)

        sp_sel = self.representation.GetSelectedSphereProperty()
        sp_sel.SetColor(get_color("red3"))
        sp_sel.SetOpacity(min(alpha + 0.2, 1.0))

        hp = self.representation.GetHandleProperty()
        hp.SetColor(get_color("white"))
        hp.RenderPointsAsSpheresOn()

        hp_sel = self.representation.GetSelectedHandleProperty()
        hp_sel.SetColor(get_color("red3"))
        hp_sel.RenderPointsAsSpheresOn()

        self.widget.SetRepresentation(self.representation)

    # ── geometry properties ───────────────────────────────────────────────

    @property
    def center(self) -> np.ndarray:
        """World position of the sphere center."""
        return np.array(self.representation.GetCenter())

    @center.setter
    def center(self, value) -> None:
        self.representation.SetCenter(list(value))

    @property
    def radius(self) -> float:
        """Radius of the sphere."""
        return float(self.representation.GetRadius())

    @radius.setter
    def radius(self, value) -> None:
        self.representation.SetRadius(float(value))

    @property
    def points(self) -> np.ndarray:
        """
        Return the surface point coordinates of the sphere as a (N, 3) array.
        N is determined by the `res` value set at construction time.
        Useful as seed points for streamline integration.
        """
        self.representation.BuildRepresentation()
        pd = vtki.new("PolyData")
        self.representation.GetPolyData(pd)
        return utils.vtk2numpy(pd.GetPoints().GetData())

    # ── visual styling ────────────────────────────────────────────────────

    def color(self, c) -> "SphereWidget":
        """Set the sphere surface color."""
        self.representation.GetSphereProperty().SetColor(get_color(c))
        return self

    def alpha(self, value: float) -> "SphereWidget":
        """Set the sphere surface opacity."""
        self.representation.GetSphereProperty().SetOpacity(value)
        return self

    def lw(self, value: int) -> "SphereWidget":
        """Set the wireframe line width (when in wireframe mode)."""
        self.representation.GetSphereProperty().SetLineWidth(value)
        return self

    # ── lifecycle ─────────────────────────────────────────────────────────

    def add_to(self, plt) -> "SphereWidget":
        """Add the widget to a `Plotter` instance and enable it."""
        c, r = self.center.copy(), self.radius
        self.widget.SetInteractor(plt.interactor)
        self.widget.SetCurrentRenderer(plt.renderer)
        self.representation.PlaceWidget(plt.renderer.ComputeVisiblePropBounds())
        # Restore center/radius — PlaceWidget resets them
        self.representation.SetCenter(list(c))
        self.representation.SetRadius(r)
        self.widget.On()
        if self.widget not in plt.widgets:
            plt.widgets.append(self.widget)
        return self

    def remove_from(self, plt) -> "SphereWidget":
        """Remove the widget from a `Plotter` instance."""
        self.widget.Off()
        if self.widget in plt.widgets:
            plt.widgets.remove(self.widget)
        return self

    def on(self) -> "SphereWidget":
        """Enable the widget."""
        self.widget.On()
        return self

    def off(self) -> "SphereWidget":
        """Disable the widget."""
        self.widget.Off()
        return self

    def toggle(self) -> "SphereWidget":
        """Toggle the widget on/off."""
        if self.widget.GetEnabled():
            self.widget.Off()
        else:
            self.widget.On()
        return self

    def is_enabled(self) -> bool:
        """Return True if the widget is currently enabled."""
        return bool(self.widget.GetEnabled())

    # ── observers ─────────────────────────────────────────────────────────

    def add_observer(self, event, func, priority=1) -> int:
        """Add an observer callback for a widget event."""
        event = utils.get_vtk_name_event(event)
        return self.widget.AddObserver(event, func, priority)

    def remove_observer(self, cid: int) -> "SphereWidget":
        """Remove an observer by its callback id."""
        self.widget.RemoveObserver(cid)
        return self

    def remove_observers(self, event="") -> "SphereWidget":
        """Remove all observers, or those for a specific event."""
        if event:
            self.widget.RemoveObservers(utils.get_vtk_name_event(event))
        else:
            self.widget.RemoveAllObservers()
        return self

    # ── output ────────────────────────────────────────────────────────────

    def get_sphere(self) -> "vedo.Sphere":
        """Return the current sphere as a `vedo.Sphere` object."""
        return vedo.shapes.Sphere(self.center, r=self.radius)

    def __repr__(self) -> str:
        return (
            f"SphereWidget(center={self.center.tolist()}, radius={self.radius:.4g})"
        )

center property writable

World position of the sphere center.

points property

Return the surface point coordinates of the sphere as a (N, 3) array. N is determined by the res value set at construction time. Useful as seed points for streamline integration.

radius property writable

Radius of the sphere.

add_observer(event, func, priority=1)

Add an observer callback for a widget event.

Source code in vedo/addons/interaction.py
692
693
694
695
def add_observer(self, event, func, priority=1) -> int:
    """Add an observer callback for a widget event."""
    event = utils.get_vtk_name_event(event)
    return self.widget.AddObserver(event, func, priority)

add_to(plt)

Add the widget to a Plotter instance and enable it.

Source code in vedo/addons/interaction.py
647
648
649
650
651
652
653
654
655
656
657
658
659
def add_to(self, plt) -> "SphereWidget":
    """Add the widget to a `Plotter` instance and enable it."""
    c, r = self.center.copy(), self.radius
    self.widget.SetInteractor(plt.interactor)
    self.widget.SetCurrentRenderer(plt.renderer)
    self.representation.PlaceWidget(plt.renderer.ComputeVisiblePropBounds())
    # Restore center/radius — PlaceWidget resets them
    self.representation.SetCenter(list(c))
    self.representation.SetRadius(r)
    self.widget.On()
    if self.widget not in plt.widgets:
        plt.widgets.append(self.widget)
    return self

alpha(value)

Set the sphere surface opacity.

Source code in vedo/addons/interaction.py
635
636
637
638
def alpha(self, value: float) -> "SphereWidget":
    """Set the sphere surface opacity."""
    self.representation.GetSphereProperty().SetOpacity(value)
    return self

color(c)

Set the sphere surface color.

Source code in vedo/addons/interaction.py
630
631
632
633
def color(self, c) -> "SphereWidget":
    """Set the sphere surface color."""
    self.representation.GetSphereProperty().SetColor(get_color(c))
    return self

get_sphere()

Return the current sphere as a vedo.Sphere object.

Source code in vedo/addons/interaction.py
712
713
714
def get_sphere(self) -> "vedo.Sphere":
    """Return the current sphere as a `vedo.Sphere` object."""
    return vedo.shapes.Sphere(self.center, r=self.radius)

is_enabled()

Return True if the widget is currently enabled.

Source code in vedo/addons/interaction.py
686
687
688
def is_enabled(self) -> bool:
    """Return True if the widget is currently enabled."""
    return bool(self.widget.GetEnabled())

lw(value)

Set the wireframe line width (when in wireframe mode).

Source code in vedo/addons/interaction.py
640
641
642
643
def lw(self, value: int) -> "SphereWidget":
    """Set the wireframe line width (when in wireframe mode)."""
    self.representation.GetSphereProperty().SetLineWidth(value)
    return self

off()

Disable the widget.

Source code in vedo/addons/interaction.py
673
674
675
676
def off(self) -> "SphereWidget":
    """Disable the widget."""
    self.widget.Off()
    return self

on()

Enable the widget.

Source code in vedo/addons/interaction.py
668
669
670
671
def on(self) -> "SphereWidget":
    """Enable the widget."""
    self.widget.On()
    return self

remove_from(plt)

Remove the widget from a Plotter instance.

Source code in vedo/addons/interaction.py
661
662
663
664
665
666
def remove_from(self, plt) -> "SphereWidget":
    """Remove the widget from a `Plotter` instance."""
    self.widget.Off()
    if self.widget in plt.widgets:
        plt.widgets.remove(self.widget)
    return self

remove_observer(cid)

Remove an observer by its callback id.

Source code in vedo/addons/interaction.py
697
698
699
700
def remove_observer(self, cid: int) -> "SphereWidget":
    """Remove an observer by its callback id."""
    self.widget.RemoveObserver(cid)
    return self

remove_observers(event='')

Remove all observers, or those for a specific event.

Source code in vedo/addons/interaction.py
702
703
704
705
706
707
708
def remove_observers(self, event="") -> "SphereWidget":
    """Remove all observers, or those for a specific event."""
    if event:
        self.widget.RemoveObservers(utils.get_vtk_name_event(event))
    else:
        self.widget.RemoveAllObservers()
    return self

toggle()

Toggle the widget on/off.

Source code in vedo/addons/interaction.py
678
679
680
681
682
683
684
def toggle(self) -> "SphereWidget":
    """Toggle the widget on/off."""
    if self.widget.GetEnabled():
        self.widget.Off()
    else:
        self.widget.On()
    return self

SplineTool

Bases: vtkContourWidget

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

Source code in vedo/addons/interaction.py
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
class SplineTool(vtki.vtkContourWidget):
    """
    Spline tool, draw a spline through a set of points interactively.
    """

    def __init__(
        self,
        points,
        pc="k",
        ps=8,
        lc="r4",
        ac="g5",
        lw=2,
        alpha=1,
        closed=False,
        ontop=True,
        can_add_nodes=True,
    ):
        super().__init__()

        self.name = "SplineTool"

        self.representation = self.GetRepresentation()
        self.representation.SetAlwaysOnTop(ontop)
        self.SetAllowNodePicking(can_add_nodes)

        self.representation.GetLinesProperty().SetColor(get_color(lc))
        self.representation.GetLinesProperty().SetLineWidth(lw)
        self.representation.GetLinesProperty().SetOpacity(alpha)
        if lw == 0 or alpha == 0:
            self.representation.GetLinesProperty().SetOpacity(0)

        self.representation.GetActiveProperty().SetLineWidth(lw + 1)
        self.representation.GetActiveProperty().SetColor(get_color(ac))

        self.representation.GetProperty().SetColor(get_color(pc))
        self.representation.GetProperty().SetPointSize(ps)
        self.representation.GetProperty().RenderPointsAsSpheresOn()
        self.SetRepresentation(self.representation)

        if utils.is_sequence(points):
            self.points = Points(points)
        else:
            self.points = points

        self.closed = closed

    @property
    def interactor(self):
        """Return the current interactor."""
        return self.GetInteractor()

    @interactor.setter
    def interactor(self, iren):
        """Set the current interactor."""
        self.SetInteractor(iren)

    def add_node(self, pt) -> SplineTool:
        """Add one point at a specified position in world/display coordinates."""
        if len(pt) == 2:
            self.representation.AddNodeAtDisplayPosition(int(pt[0]), int(pt[1]))
        else:
            self.representation.AddNodeAtWorldPosition(pt)
        return self

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

    def remove_node(self, i: int) -> SplineTool:
        """Remove specific node by its index."""
        self.representation.DeleteNthNode(i)
        return self

    def on(self) -> SplineTool:
        """Activate/Enable the tool."""
        self.On()
        self.Render()
        return self

    def off(self) -> SplineTool:
        """Disactivate/Disable the tool."""
        self.Off()
        self.Render()
        return self

    def toggle(self) -> SplineTool:
        """Toggle the visibility of the tool."""
        self.SetEnabled(not self.GetEnabled())
        return self

    def render(self) -> SplineTool:
        """Render the spline."""
        self.Render()
        return self

    def lw(self, lw: int) -> SplineTool:
        """Set the line width of the spline."""
        self.representation.GetLinesProperty().SetLineWidth(lw)
        self.representation.GetActiveProperty().SetLineWidth(lw)
        return self

    def ps(self, ps: int) -> SplineTool:
        """Set the point size of the spline."""
        self.representation.GetProperty().SetPointSize(ps)
        return self

    def point_color(self, c: str | tuple) -> SplineTool:
        """Set the color of the spline points."""
        c = get_color(c)
        self.representation.GetProperty().SetColor(c)
        return self

    def color(self, c: str | tuple) -> SplineTool:
        """Set the color of the spline."""
        c = get_color(c)
        self.representation.GetProperty().SetColor(c)
        self.representation.GetLinesProperty().SetColor(c)
        self.representation.GetActiveProperty().SetColor(c)
        return self

    def closed_loop(self, value: bool) -> SplineTool:
        """Set whether the spline is a closed loop."""
        self.closed = value
        self.representation.SetClosedLoop(value)
        return self

    def spline(self) -> vedo.Line:
        """Return the vedo.Line object."""
        self.representation.SetClosedLoop(self.closed)
        self.representation.BuildRepresentation()
        pd = self.representation.GetContourRepresentationAsPolyData()
        ln = vedo.Line(pd, lw=2, c="k")
        return ln

    def nodes(self, onscreen=False) -> np.ndarray:
        """Return the current spline nodes in world or screen coordinates."""
        n = self.representation.GetNumberOfNodes()
        pts = []
        for i in range(n):
            p = [0.0, 0.0, 0.0]
            if onscreen:
                self.representation.GetNthNodeDisplayPosition(i, p)
            else:
                self.representation.GetNthNodeWorldPosition(i, p)
            pts.append(p)
        return np.array(pts)

    def node_position(self, i, pt, onscreen=False) -> SplineTool:
        """Set the position of a specific node by index."""
        n = self.representation.GetNumberOfNodes()
        if i < 0 or i >= n:
            vedo.logger.error(f"SplineTool: index {i} out of range [0-{n - 1}]")
            return self
        if onscreen:
            self.representation.SetNthNodeDisplayPosition(i, pt[0], pt[1])
        else:
            self.representation.SetNthNodeWorldPosition(i, pt)
        return self

    def set_nodes(self, pts: np.ndarray | list) -> SplineTool:
        """Set all spline nodes from an array/list of points."""
        if isinstance(pts, list):
            pts = np.array(pts)
        if pts.ndim == 1:
            pts = pts.reshape(-1, 3)
        for i in range(len(pts)):
            self.representation.SetNthNodeWorldPosition(i, pts[i])
        return self

interactor property writable

Return the current interactor.

add_node(pt)

Add one point at a specified position in world/display coordinates.

Source code in vedo/addons/interaction.py
1016
1017
1018
1019
1020
1021
1022
def add_node(self, pt) -> SplineTool:
    """Add one point at a specified position in world/display coordinates."""
    if len(pt) == 2:
        self.representation.AddNodeAtDisplayPosition(int(pt[0]), int(pt[1]))
    else:
        self.representation.AddNodeAtWorldPosition(pt)
    return self

add_observer(event, func, priority=1)

Add an observer to the widget.

Source code in vedo/addons/interaction.py
1024
1025
1026
1027
1028
def add_observer(self, event, func, priority=1) -> int:
    """Add an observer to the widget."""
    event = utils.get_vtk_name_event(event)
    cid = self.AddObserver(event, func, priority)
    return cid

closed_loop(value)

Set whether the spline is a closed loop.

Source code in vedo/addons/interaction.py
1082
1083
1084
1085
1086
def closed_loop(self, value: bool) -> SplineTool:
    """Set whether the spline is a closed loop."""
    self.closed = value
    self.representation.SetClosedLoop(value)
    return self

color(c)

Set the color of the spline.

Source code in vedo/addons/interaction.py
1074
1075
1076
1077
1078
1079
1080
def color(self, c: str | tuple) -> SplineTool:
    """Set the color of the spline."""
    c = get_color(c)
    self.representation.GetProperty().SetColor(c)
    self.representation.GetLinesProperty().SetColor(c)
    self.representation.GetActiveProperty().SetColor(c)
    return self

lw(lw)

Set the line width of the spline.

Source code in vedo/addons/interaction.py
1057
1058
1059
1060
1061
def lw(self, lw: int) -> SplineTool:
    """Set the line width of the spline."""
    self.representation.GetLinesProperty().SetLineWidth(lw)
    self.representation.GetActiveProperty().SetLineWidth(lw)
    return self

node_position(i, pt, onscreen=False)

Set the position of a specific node by index.

Source code in vedo/addons/interaction.py
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
def node_position(self, i, pt, onscreen=False) -> SplineTool:
    """Set the position of a specific node by index."""
    n = self.representation.GetNumberOfNodes()
    if i < 0 or i >= n:
        vedo.logger.error(f"SplineTool: index {i} out of range [0-{n - 1}]")
        return self
    if onscreen:
        self.representation.SetNthNodeDisplayPosition(i, pt[0], pt[1])
    else:
        self.representation.SetNthNodeWorldPosition(i, pt)
    return self

nodes(onscreen=False)

Return the current spline nodes in world or screen coordinates.

Source code in vedo/addons/interaction.py
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
def nodes(self, onscreen=False) -> np.ndarray:
    """Return the current spline nodes in world or screen coordinates."""
    n = self.representation.GetNumberOfNodes()
    pts = []
    for i in range(n):
        p = [0.0, 0.0, 0.0]
        if onscreen:
            self.representation.GetNthNodeDisplayPosition(i, p)
        else:
            self.representation.GetNthNodeWorldPosition(i, p)
        pts.append(p)
    return np.array(pts)

off()

Disactivate/Disable the tool.

Source code in vedo/addons/interaction.py
1041
1042
1043
1044
1045
def off(self) -> SplineTool:
    """Disactivate/Disable the tool."""
    self.Off()
    self.Render()
    return self

on()

Activate/Enable the tool.

Source code in vedo/addons/interaction.py
1035
1036
1037
1038
1039
def on(self) -> SplineTool:
    """Activate/Enable the tool."""
    self.On()
    self.Render()
    return self

point_color(c)

Set the color of the spline points.

Source code in vedo/addons/interaction.py
1068
1069
1070
1071
1072
def point_color(self, c: str | tuple) -> SplineTool:
    """Set the color of the spline points."""
    c = get_color(c)
    self.representation.GetProperty().SetColor(c)
    return self

ps(ps)

Set the point size of the spline.

Source code in vedo/addons/interaction.py
1063
1064
1065
1066
def ps(self, ps: int) -> SplineTool:
    """Set the point size of the spline."""
    self.representation.GetProperty().SetPointSize(ps)
    return self

remove_node(i)

Remove specific node by its index.

Source code in vedo/addons/interaction.py
1030
1031
1032
1033
def remove_node(self, i: int) -> SplineTool:
    """Remove specific node by its index."""
    self.representation.DeleteNthNode(i)
    return self

render()

Render the spline.

Source code in vedo/addons/interaction.py
1052
1053
1054
1055
def render(self) -> SplineTool:
    """Render the spline."""
    self.Render()
    return self

set_nodes(pts)

Set all spline nodes from an array/list of points.

Source code in vedo/addons/interaction.py
1121
1122
1123
1124
1125
1126
1127
1128
1129
def set_nodes(self, pts: np.ndarray | list) -> SplineTool:
    """Set all spline nodes from an array/list of points."""
    if isinstance(pts, list):
        pts = np.array(pts)
    if pts.ndim == 1:
        pts = pts.reshape(-1, 3)
    for i in range(len(pts)):
        self.representation.SetNthNodeWorldPosition(i, pts[i])
    return self

spline()

Return the vedo.Line object.

Source code in vedo/addons/interaction.py
1088
1089
1090
1091
1092
1093
1094
def spline(self) -> vedo.Line:
    """Return the vedo.Line object."""
    self.representation.SetClosedLoop(self.closed)
    self.representation.BuildRepresentation()
    pd = self.representation.GetContourRepresentationAsPolyData()
    ln = vedo.Line(pd, lw=2, c="k")
    return ln

toggle()

Toggle the visibility of the tool.

Source code in vedo/addons/interaction.py
1047
1048
1049
1050
def toggle(self) -> SplineTool:
    """Toggle the visibility of the tool."""
    self.SetEnabled(not self.GetEnabled())
    return self

ui

Flagpost

Bases: vtkFlagpoleLabel

Create a flag post style element to describe an object.

Source code in vedo/addons/ui.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
class Flagpost(vtki.vtkFlagpoleLabel):
    """
    Create a flag post style element to describe an object.
    """

    def __init__(
        self,
        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,
    ):
        """
        Create a flag post style element to describe an object.

        Args:
            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](https://vedo.embl.es/fonts).
            justify (str):
                internal text justification. The default is "center-left".
            vspacing (float):
                vertical spacing between lines.

        Examples:
            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/extras/flag_labels2.py)

            ![](https://vedo.embl.es/images/extras/flag_labels2.png)
        """

        super().__init__()

        self.name = "Flagpost"

        base = utils.make3d(base)
        top = utils.make3d(top)

        self.SetBasePosition(*base)
        self.SetTopPosition(*top)

        self.SetFlagSize(s)
        self.SetInput(txt)
        self.PickableOff()

        self.GetProperty().LightingOff()
        self.GetProperty().SetLineWidth(lw + 1)

        prop = self.GetTextProperty()
        if bc is not None:
            prop.SetBackgroundColor(get_color(bc))

        prop.SetOpacity(alpha)
        prop.SetBackgroundOpacity(alpha)
        if bc is not None and len(bc) == 4:
            prop.SetBackgroundRGBA(alpha)

        c = get_color(c)
        prop.SetColor(c)
        self.GetProperty().SetColor(c)

        prop.SetFrame(bool(lw))
        prop.SetFrameWidth(lw)
        prop.SetFrameColor(prop.GetColor())

        prop.SetFontFamily(vtki.VTK_FONT_FILE)
        fl = utils.get_font_path(font)
        prop.SetFontFile(fl)
        prop.ShadowOff()
        prop.BoldOff()
        prop.SetOpacity(alpha)
        prop.SetJustificationToLeft()
        if "top" in justify:
            prop.SetVerticalJustificationToTop()
        if "bottom" in justify:
            prop.SetVerticalJustificationToBottom()
        if "cent" in justify:
            prop.SetVerticalJustificationToCentered()
            prop.SetJustificationToCentered()
        if "left" in justify:
            prop.SetJustificationToLeft()
        if "right" in justify:
            prop.SetJustificationToRight()
        prop.SetLineSpacing(vspacing * 1.2)
        self.SetUseBounds(False)

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

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

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

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

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

    def color(self, c) -> Self:
        """Set the color of the flagpost."""
        c = get_color(c)
        self.GetTextProperty().SetColor(c)
        self.GetProperty().SetColor(c)
        return self

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

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

    @base.setter
    def base(self, value):
        """Set the base position of the flagpost."""
        self.SetBasePosition(*value)

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

    @top.setter
    def top(self, value):
        """Set the top position of the flagpost."""
        self.SetTopPosition(*value)

base property writable

Return the base position of the flagpost.

top property writable

Return the top position of the flagpost.

color(c)

Set the color of the flagpost.

Source code in vedo/addons/ui.py
154
155
156
157
158
159
def color(self, c) -> Self:
    """Set the color of the flagpost."""
    c = get_color(c)
    self.GetTextProperty().SetColor(c)
    self.GetProperty().SetColor(c)
    return self

off()

Hide the flagpost.

Source code in vedo/addons/ui.py
139
140
141
142
def off(self) -> Self:
    """Hide the flagpost."""
    self.VisibilityOff()
    return self

on()

Show the flagpost.

Source code in vedo/addons/ui.py
134
135
136
137
def on(self) -> Self:
    """Show the flagpost."""
    self.VisibilityOn()
    return self

pos(p)

Set the position of the flagpost.

Source code in vedo/addons/ui.py
161
162
163
164
165
166
def pos(self, p) -> Self:
    """Set the position of the flagpost."""
    p = np.asarray(p)
    self.top = self.top - self.base + p
    self.base = p
    return self

text(value)

Set the text of the flagpost.

Source code in vedo/addons/ui.py
129
130
131
132
def text(self, value: str) -> Self:
    """Set the text of the flagpost."""
    self.SetInput(value)
    return self

toggle()

Toggle the visibility of the flagpost.

Source code in vedo/addons/ui.py
144
145
146
147
def toggle(self) -> Self:
    """Toggle the visibility of the flagpost."""
    self.SetVisibility(not self.GetVisibility())
    return self

use_bounds(value=True)

Set the flagpost to keep bounds into account.

Source code in vedo/addons/ui.py
149
150
151
152
def use_bounds(self, value=True) -> Self:
    """Set the flagpost to keep bounds into account."""
    self.SetUseBounds(value)
    return self

Icon

Bases: vtkOrientationMarkerWidget

Add an inset icon mesh into the renderer.

Source code in vedo/addons/ui.py
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
class Icon(vtki.vtkOrientationMarkerWidget):
    """
    Add an inset icon mesh into the renderer.
    """

    def __init__(self, mesh, pos=3, size=0.08):
        """
        Add an inset icon mesh into the renderer.

        Args:
            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.
        """
        super().__init__()
        self.name = "Icon"

        try:
            self.SetOrientationMarker(mesh.actor)
        except AttributeError:
            self.SetOrientationMarker(mesh)

        if utils.is_sequence(pos):
            self.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size)
        else:
            if pos < 2:
                self.SetViewport(0, 1 - 2 * size, size * 2, 1)
            elif pos == 2:
                self.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1)
            elif pos == 3:
                self.SetViewport(0, 0, size * 2, size * 2)
            elif pos == 4:
                self.SetViewport(1 - 2 * size, 0, 1, size * 2)

LegendBox

Bases: vtkLegendBoxActor

Create a 2D legend box.

Source code in vedo/addons/ui.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
class LegendBox(vtki.vtkLegendBoxActor):
    """
    Create a 2D legend box.
    """

    def __init__(
        self,
        entries=(),
        nmax=12,
        c=None,
        font="",
        width=0.18,
        height=None,
        padding=2,
        bg="k8",
        alpha=0.25,
        pos="top-right",
        markers=None,
    ):
        """
        Create a 2D legend box for the list of specified objects.

        Args:
            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](https://vedo.embl.es/fonts)
            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:
            - [legendbox.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/legendbox.py)
            - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/extras/flag_labels1.py)
            - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/extras/flag_labels2.py)

                ![](https://vedo.embl.es/images/extras/flag_labels.png)
        """
        super().__init__()

        self.name = "LegendBox"
        self.entries = entries[:nmax]
        self.properties = self.GetEntryTextProperty()

        n = 0
        texts = []
        for e in self.entries:
            ename = e.name
            if "legend" in e.info.keys():
                if not e.info["legend"]:
                    ename = ""
                else:
                    ename = str(e.info["legend"])
            if ename:
                n += 1
            texts.append(ename)
        self.SetNumberOfEntries(n)

        if not n:
            return

        self.ScalarVisibilityOff()
        self.PickableOff()
        self.SetPadding(padding)

        self.properties.ShadowOff()
        self.properties.BoldOff()

        # self.properties.SetJustificationToLeft() # no effect
        # self.properties.SetVerticalJustificationToTop()

        if not font:
            font = settings.default_font
        self.font(font)

        n = 0
        for i in range(len(self.entries)):
            ti = texts[i]
            if not ti:
                continue
            e = entries[i]
            if c is None:
                col = e.properties.GetColor()
                if col == (1, 1, 1):
                    col = (0.2, 0.2, 0.2)
            else:
                col = get_color(c)
            if markers is None:  # default
                poly = e.dataset
            else:
                marker = markers[i] if utils.is_sequence(markers) else markers
                if isinstance(marker, Points):
                    poly = marker.clone(deep=False).normalize().shift(0, 1, 0).dataset
                else:  # assume string marker
                    poly = vedo.shapes.Marker(marker, s=1).shift(0, 1, 0).dataset

            self.SetEntry(n, poly, ti, col)
            n += 1

        self.SetWidth(width)
        if height is None:
            self.SetHeight(width / 3.0 * n)
        else:
            self.SetHeight(height)

        self.pos(pos)

        if alpha:
            self.UseBackgroundOn()
            self.SetBackgroundColor(get_color(bg))
            self.SetBackgroundOpacity(alpha)
        else:
            self.UseBackgroundOff()
        self.LockBorderOn()

    @property
    def width(self):
        """Return the width of the legend box."""
        return self.GetWidth()

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

    def font(self, font: str):
        """Text font face"""
        if isinstance(font, int):
            lfonts = list(settings.font_parameters.keys())
            n = font % len(lfonts)
            font = lfonts[n]
            self.fontname = font

        if not font:  # use default font
            font = self.fontname
            fpath = os.path.join(vedo.fonts_path, font + ".ttf")
        elif font.startswith("https"):  # user passed URL link, make it a path
            fpath = vedo.file_io.download(font, verbose=False, force=False)
        elif font.endswith(".ttf"):  # user passing a local path to font file
            fpath = font
        else:  # user passing name of preset font
            fpath = os.path.join(vedo.fonts_path, font + ".ttf")

        if font == "Courier":
            self.properties.SetFontFamilyToCourier()
        elif font == "Times":
            self.properties.SetFontFamilyToTimes()
        elif font == "Arial":
            self.properties.SetFontFamilyToArial()
        else:
            fpath = utils.get_font_path(font)
            self.properties.SetFontFamily(vtki.VTK_FONT_FILE)
            self.properties.SetFontFile(fpath)
        self.fontname = font  # io.tonumpy() uses it

        return self

    def pos(self, pos):
        """Set the position of the legend box."""
        sx, sy = 1 - self.GetWidth(), 1 - self.GetHeight()
        if pos == 1 or ("top" in pos and "left" in pos):
            self.GetPositionCoordinate().SetValue(0, sy)
        elif pos == 2 or ("top" in pos and "right" in pos):
            self.GetPositionCoordinate().SetValue(sx, sy)
        elif pos == 3 or ("bottom" in pos and "left" in pos):
            self.GetPositionCoordinate().SetValue(0, 0)
        elif pos == 4 or ("bottom" in pos and "right" in pos):
            self.GetPositionCoordinate().SetValue(sx, 0)
        elif "cent" in pos and "right" in pos:
            self.GetPositionCoordinate().SetValue(sx, sy - 0.25)
        elif "cent" in pos and "left" in pos:
            self.GetPositionCoordinate().SetValue(0, sy - 0.25)
        elif "cent" in pos and "bottom" in pos:
            self.GetPositionCoordinate().SetValue(sx - 0.25, 0)
        elif "cent" in pos and "top" in pos:
            self.GetPositionCoordinate().SetValue(sx - 0.25, sy)
        elif utils.is_sequence(pos):
            self.GetPositionCoordinate().SetValue(pos[0], pos[1])
        else:
            vedo.logger.error("LegendBox: pos must be in range [1-4] or a [x,y] list")

        return self

height property

Return the height of the legend box.

width property

Return the width of the legend box.

font(font)

Text font face

Source code in vedo/addons/ui.py
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
def font(self, font: str):
    """Text font face"""
    if isinstance(font, int):
        lfonts = list(settings.font_parameters.keys())
        n = font % len(lfonts)
        font = lfonts[n]
        self.fontname = font

    if not font:  # use default font
        font = self.fontname
        fpath = os.path.join(vedo.fonts_path, font + ".ttf")
    elif font.startswith("https"):  # user passed URL link, make it a path
        fpath = vedo.file_io.download(font, verbose=False, force=False)
    elif font.endswith(".ttf"):  # user passing a local path to font file
        fpath = font
    else:  # user passing name of preset font
        fpath = os.path.join(vedo.fonts_path, font + ".ttf")

    if font == "Courier":
        self.properties.SetFontFamilyToCourier()
    elif font == "Times":
        self.properties.SetFontFamilyToTimes()
    elif font == "Arial":
        self.properties.SetFontFamilyToArial()
    else:
        fpath = utils.get_font_path(font)
        self.properties.SetFontFamily(vtki.VTK_FONT_FILE)
        self.properties.SetFontFile(fpath)
    self.fontname = font  # io.tonumpy() uses it

    return self

pos(pos)

Set the position of the legend box.

Source code in vedo/addons/ui.py
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
def pos(self, pos):
    """Set the position of the legend box."""
    sx, sy = 1 - self.GetWidth(), 1 - self.GetHeight()
    if pos == 1 or ("top" in pos and "left" in pos):
        self.GetPositionCoordinate().SetValue(0, sy)
    elif pos == 2 or ("top" in pos and "right" in pos):
        self.GetPositionCoordinate().SetValue(sx, sy)
    elif pos == 3 or ("bottom" in pos and "left" in pos):
        self.GetPositionCoordinate().SetValue(0, 0)
    elif pos == 4 or ("bottom" in pos and "right" in pos):
        self.GetPositionCoordinate().SetValue(sx, 0)
    elif "cent" in pos and "right" in pos:
        self.GetPositionCoordinate().SetValue(sx, sy - 0.25)
    elif "cent" in pos and "left" in pos:
        self.GetPositionCoordinate().SetValue(0, sy - 0.25)
    elif "cent" in pos and "bottom" in pos:
        self.GetPositionCoordinate().SetValue(sx - 0.25, 0)
    elif "cent" in pos and "top" in pos:
        self.GetPositionCoordinate().SetValue(sx - 0.25, sy)
    elif utils.is_sequence(pos):
        self.GetPositionCoordinate().SetValue(pos[0], pos[1])
    else:
        vedo.logger.error("LegendBox: pos must be in range [1-4] or a [x,y] list")

    return self

sliders

Slider2D

Bases: SliderWidget

Add a slider which can call an external custom function.

Source code in vedo/addons/sliders.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
class Slider2D(SliderWidget):
    """
    Add a slider which can call an external custom function.
    """

    def __init__(
        self,
        sliderfunc,
        xmin,
        xmax,
        value=None,
        pos=4,
        title="",
        font="Calco",
        title_size=1,
        c="k",
        alpha=1,
        show_value=True,
        delayed=False,
        **options,
    ):
        """
        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.

        Args:
            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](https://vedo.embl.es/fonts).
            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:
            - [sliders1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders1.py)
            - [sliders2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders2.py)

            ![](https://user-images.githubusercontent.com/32848391/50738848-be033480-11d8-11e9-9b1a-c13105423a79.jpg)
        """
        slider_length = options.pop("slider_length", 0.015)
        slider_width = options.pop("slider_width", 0.025)
        end_cap_length = options.pop("end_cap_length", 0.0015)
        end_cap_width = options.pop("end_cap_width", 0.0125)
        tube_width = options.pop("tube_width", 0.0075)
        title_height = options.pop("title_height", 0.025)
        tformat = options.pop("tformat", None)

        if options:
            vedo.logger.warning(f"in Slider2D unknown option(s): {options}")

        c = get_color(c)

        if value is None or value < xmin:
            value = xmin

        slider_rep = vtki.new("SliderRepresentation2D")
        slider_rep.SetMinimumValue(xmin)
        slider_rep.SetMaximumValue(xmax)
        slider_rep.SetValue(value)
        slider_rep.SetSliderLength(slider_length)
        slider_rep.SetSliderWidth(slider_width)
        slider_rep.SetEndCapLength(end_cap_length)
        slider_rep.SetEndCapWidth(end_cap_width)
        slider_rep.SetTubeWidth(tube_width)
        slider_rep.GetPoint1Coordinate().SetCoordinateSystemToNormalizedDisplay()
        slider_rep.GetPoint2Coordinate().SetCoordinateSystemToNormalizedDisplay()

        if isinstance(pos, str):
            if "top" in pos:
                if "left" in pos:
                    if "vert" in pos:
                        pos = 11
                    else:
                        pos = 1
                elif "right" in pos:
                    if "vert" in pos:
                        pos = 12
                    else:
                        pos = 2
            elif "bott" in pos:
                if "left" in pos:
                    if "vert" in pos:
                        pos = 13
                    else:
                        pos = 3
                elif "right" in pos:
                    if "vert" in pos:
                        if "span" in pos:
                            pos = 15
                        else:
                            pos = 14
                    else:
                        pos = 4
                elif "span" in pos:
                    pos = 5

        if utils.is_sequence(pos):
            slider_rep.GetPoint1Coordinate().SetValue(pos[0][0], pos[0][1])
            slider_rep.GetPoint2Coordinate().SetValue(pos[1][0], pos[1][1])
        elif pos == 1:  # top-left horizontal
            slider_rep.GetPoint1Coordinate().SetValue(0.04, 0.93)
            slider_rep.GetPoint2Coordinate().SetValue(0.45, 0.93)
        elif pos == 2:
            slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.93)
            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.93)
        elif pos == 3:
            slider_rep.GetPoint1Coordinate().SetValue(0.05, 0.06)
            slider_rep.GetPoint2Coordinate().SetValue(0.45, 0.06)
        elif pos == 4:  # bottom-right
            slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.06)
            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06)
        elif pos == 5:  # bottom span horizontal
            slider_rep.GetPoint1Coordinate().SetValue(0.04, 0.06)
            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06)
        elif pos == 11:  # top-left vertical
            slider_rep.GetPoint1Coordinate().SetValue(0.065, 0.54)
            slider_rep.GetPoint2Coordinate().SetValue(0.065, 0.9)
        elif pos == 12:
            slider_rep.GetPoint1Coordinate().SetValue(0.94, 0.54)
            slider_rep.GetPoint2Coordinate().SetValue(0.94, 0.9)
        elif pos == 13:
            slider_rep.GetPoint1Coordinate().SetValue(0.065, 0.1)
            slider_rep.GetPoint2Coordinate().SetValue(0.065, 0.54)
        elif pos == 14:  # bottom-right vertical
            slider_rep.GetPoint1Coordinate().SetValue(0.94, 0.1)
            slider_rep.GetPoint2Coordinate().SetValue(0.94, 0.54)
        elif pos == 15:  # right margin vertical
            slider_rep.GetPoint1Coordinate().SetValue(0.95, 0.1)
            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.9)
        else:  # bottom-right
            slider_rep.GetPoint1Coordinate().SetValue(0.55, 0.06)
            slider_rep.GetPoint2Coordinate().SetValue(0.95, 0.06)

        if show_value:
            if tformat is None:
                if (
                    isinstance(xmin, int)
                    and isinstance(xmax, int)
                    and isinstance(value, int)
                ):
                    tformat = "%0.0f"
                else:
                    tformat = "%0.2f"
            if utils.vtk_version_at_least(6, 0):
                # replace the default format of '%0.2g' to '{:0.2f}' to show more significant digits
                tformat = (
                    tformat.replace("%", "{:").replace("f", "f}").replace("g", "g}")
                )

            slider_rep.SetLabelFormat(tformat)
            slider_rep.GetLabelProperty().SetShadow(0)
            slider_rep.GetLabelProperty().SetBold(0)
            slider_rep.GetLabelProperty().SetOpacity(alpha)
            slider_rep.GetLabelProperty().SetColor(c)
            if isinstance(pos, int) and pos > 10:
                slider_rep.GetLabelProperty().SetOrientation(90)
        else:
            slider_rep.ShowSliderLabelOff()
        slider_rep.GetTubeProperty().SetColor(c)
        slider_rep.GetTubeProperty().SetOpacity(0.75)
        slider_rep.GetSliderProperty().SetColor(c)
        slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c)))
        slider_rep.GetCapProperty().SetColor(c)

        slider_rep.SetTitleHeight(title_height * title_size)
        slider_rep.GetTitleProperty().SetShadow(0)
        slider_rep.GetTitleProperty().SetColor(c)
        slider_rep.GetTitleProperty().SetOpacity(alpha)
        slider_rep.GetTitleProperty().SetBold(0)
        if font.lower() == "courier":
            slider_rep.GetTitleProperty().SetFontFamilyToCourier()
        elif font.lower() == "times":
            slider_rep.GetTitleProperty().SetFontFamilyToTimes()
        elif font.lower() == "arial":
            slider_rep.GetTitleProperty().SetFontFamilyToArial()
        else:
            if font == "":
                font = utils.get_font_path(settings.default_font)
            else:
                font = utils.get_font_path(font)
            slider_rep.GetTitleProperty().SetFontFamily(vtki.VTK_FONT_FILE)
            slider_rep.GetLabelProperty().SetFontFamily(vtki.VTK_FONT_FILE)
            slider_rep.GetTitleProperty().SetFontFile(font)
            slider_rep.GetLabelProperty().SetFontFile(font)

        if title:
            slider_rep.SetTitleText(title)
            if not utils.is_sequence(pos):
                if isinstance(pos, int) and pos > 10:
                    slider_rep.GetTitleProperty().SetOrientation(90)
            else:
                if abs(pos[0][0] - pos[1][0]) < 0.1:
                    slider_rep.GetTitleProperty().SetOrientation(90)

        super().__init__()
        self.name = "Slider2D"

        self.SetAnimationModeToJump()
        self.SetRepresentation(slider_rep)
        if delayed:
            self.AddObserver("EndInteractionEvent", sliderfunc)
        else:
            self.AddObserver("InteractionEvent", sliderfunc)

    def color(self, c) -> Self:
        c = get_color(c)
        self.GetRepresentation().GetTubeProperty().SetColor(c)
        self.GetRepresentation().GetSliderProperty().SetColor(c)
        self.GetRepresentation().GetCapProperty().SetColor(c)
        self.GetRepresentation().GetLabelProperty().SetColor(c)
        self.GetRepresentation().GetTitleProperty().SetColor(c)
        return self

Slider3D

Bases: SliderWidget

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

Source code in vedo/addons/sliders.py
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
class Slider3D(SliderWidget):
    """
    Add a 3D slider which can call an external custom function.
    """

    def __init__(
        self,
        sliderfunc,
        pos1,
        pos2,
        xmin,
        xmax,
        value=None,
        s=0.03,
        t=1,
        title="",
        rotation=0,
        c=None,
        show_value=True,
    ):
        """
        Add a 3D slider which can call an external custom function.

        Args:
            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:
            - [sliders3d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/sliders3d.py)
        """
        c = get_color(c)

        if value is None or value < xmin:
            value = xmin

        slider_rep = vtki.new("SliderRepresentation3D")
        slider_rep.SetMinimumValue(xmin)
        slider_rep.SetMaximumValue(xmax)
        slider_rep.SetValue(value)

        slider_rep.GetPoint1Coordinate().SetCoordinateSystemToWorld()
        slider_rep.GetPoint2Coordinate().SetCoordinateSystemToWorld()
        slider_rep.GetPoint1Coordinate().SetValue(pos2)
        slider_rep.GetPoint2Coordinate().SetValue(pos1)

        # slider_rep.SetPoint1InWorldCoordinates(pos2[0], pos2[1], pos2[2])
        # slider_rep.SetPoint2InWorldCoordinates(pos1[0], pos1[1], pos1[2])

        slider_rep.SetSliderWidth(0.03 * t)
        slider_rep.SetTubeWidth(0.01 * t)
        slider_rep.SetSliderLength(0.04 * t)
        slider_rep.SetSliderShapeToCylinder()
        slider_rep.GetSelectedProperty().SetColor(np.sqrt(np.array(c)))
        slider_rep.GetSliderProperty().SetColor(np.array(c) / 1.5)
        slider_rep.GetCapProperty().SetOpacity(0)
        slider_rep.SetRotation(rotation)

        if not show_value:
            slider_rep.ShowSliderLabelOff()

        slider_rep.SetTitleText(title)
        slider_rep.SetTitleHeight(s * t)
        slider_rep.SetLabelHeight(s * t * 0.85)

        slider_rep.GetTubeProperty().SetColor(c)

        super().__init__()
        self.name = "Slider3D"

        self.SetRepresentation(slider_rep)
        self.SetAnimationModeToJump()
        self.AddObserver("InteractionEvent", sliderfunc)

SliderWidget

Bases: vtkSliderWidget

Helper class for vtkSliderWidget

Source code in vedo/addons/sliders.py
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
class SliderWidget(vtki.vtkSliderWidget):
    """Helper class for `vtkSliderWidget`"""

    def __init__(self):
        super().__init__()
        self.previous_value = None
        self.name = "SliderWidget"

    @property
    def interactor(self):
        """Return the interactor for the slider."""
        return self.GetInteractor()

    @interactor.setter
    def interactor(self, iren):
        """Set the interactor for the slider."""
        self.SetInteractor(iren)

    @property
    def representation(self):
        """Return the representation of the slider."""
        return self.GetRepresentation()

    @property
    def value(self):
        """Return the value of the slider."""
        val = self.GetRepresentation().GetValue()
        # self.previous_value = val
        return val

    @value.setter
    def value(self, val):
        """Set the value of the slider."""
        self.GetRepresentation().SetValue(val)

    @property
    def renderer(self):
        """Return the renderer for the slider."""
        return self.GetCurrentRenderer()

    @renderer.setter
    def renderer(self, ren):
        """Set the renderer for the slider."""
        self.SetCurrentRenderer(ren)

    @property
    def title(self):
        """Return the title of the slider."""
        return self.GetRepresentation().GetTitleText()

    @title.setter
    def title(self, txt):
        """Set the title of the slider."""
        self.GetRepresentation().SetTitleText(str(txt))

    @property
    def range(self):
        """Return the range of the slider."""
        xmin = self.GetRepresentation().GetMinimumValue()
        xmax = self.GetRepresentation().GetMaximumValue()
        return [xmin, xmax]

    @range.setter
    def range(self, vals):
        """Set the range of the slider."""
        if vals[0] is not None:
            self.GetRepresentation().SetMinimumValue(vals[0])
        if vals[1] is not None:
            self.GetRepresentation().SetMaximumValue(vals[1])

    def on(self) -> Self:
        """Activate/Enable the widget"""
        self.EnabledOn()
        return self

    def off(self) -> Self:
        """Disactivate/Disable the widget"""
        self.EnabledOff()
        return self

    def toggle(self) -> Self:
        """Toggle the widget"""
        self.SetEnabled(not self.GetEnabled())
        return self

    def is_enabled(self) -> bool:
        """Check if the widget is enabled."""
        return bool(self.GetEnabled())

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

    def render(self):
        """Render the widget."""
        self.Render()
        return self

interactor property writable

Return the interactor for the slider.

range property writable

Return the range of the slider.

renderer property writable

Return the renderer for the slider.

representation property

Return the representation of the slider.

title property writable

Return the title of the slider.

value property writable

Return the value of the slider.

add_observer(event, func, priority=1)

Add an observer to the widget.

Source code in vedo/addons/sliders.py
108
109
110
111
112
def add_observer(self, event, func, priority=1) -> int:
    """Add an observer to the widget."""
    event = utils.get_vtk_name_event(event)
    cid = self.AddObserver(event, func, priority)
    return cid

is_enabled()

Check if the widget is enabled.

Source code in vedo/addons/sliders.py
104
105
106
def is_enabled(self) -> bool:
    """Check if the widget is enabled."""
    return bool(self.GetEnabled())

off()

Disactivate/Disable the widget

Source code in vedo/addons/sliders.py
94
95
96
97
def off(self) -> Self:
    """Disactivate/Disable the widget"""
    self.EnabledOff()
    return self

on()

Activate/Enable the widget

Source code in vedo/addons/sliders.py
89
90
91
92
def on(self) -> Self:
    """Activate/Enable the widget"""
    self.EnabledOn()
    return self

render()

Render the widget.

Source code in vedo/addons/sliders.py
114
115
116
117
def render(self):
    """Render the widget."""
    self.Render()
    return self

toggle()

Toggle the widget

Source code in vedo/addons/sliders.py
 99
100
101
102
def toggle(self) -> Self:
    """Toggle the widget"""
    self.SetEnabled(not self.GetEnabled())
    return self

cutters

BaseCutter

Base class for Cutter widgets.

Source code in vedo/addons/cutters.py
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
class BaseCutter:
    """
    Base class for Cutter widgets.
    """

    def __init__(self):
        self.__implicit_func = None
        self.widget = None
        self.clipper = None
        self.cutter = None
        self.mesh = None
        self.remnant = None
        self._alpha = 0.5

        self.can_translate = True
        self.can_scale = True
        self.can_rotate = True
        self.is_inverted = False

    @property
    def transform(self) -> LinearTransform:
        """Get the transformation matrix."""
        t = vtki.vtkTransform()
        self.widget.GetTransform(t)
        return LinearTransform(t)

    def get_cut_mesh(self, invert=False) -> Mesh:
        """
        Get the mesh resulting from the cut operation.
        Returns the original mesh and the remnant mesh.
        """
        self.clipper.Update()
        if invert:
            poly = self.clipper.GetClippedOutput()
        else:
            poly = self.clipper.GetOutput()
        out = Mesh(poly)
        out.copy_properties_from(self.mesh)
        return out

    def invert(self) -> Self:
        """Invert selection."""
        self.clipper.SetInsideOut(not self.clipper.GetInsideOut())
        self.is_inverted = not self.clipper.GetInsideOut()
        return self

    def on(self) -> Self:
        """Switch the widget on or off."""
        self.widget.On()
        return self

    def off(self) -> Self:
        """Switch the widget on or off."""
        self.widget.Off()
        return self

    def toggle(self) -> Self:
        """Toggle the widget on or off."""
        if self.widget.GetEnabled():
            self.off()
        else:
            self.on()
        return self

    def add_to(self, plt) -> Self:
        """Assign the widget to the provided `Plotter` instance."""
        self.widget.SetInteractor(plt.interactor)
        self.widget.SetCurrentRenderer(plt.renderer)
        if self.widget not in plt.widgets:
            plt.widgets.append(self.widget)

        cpoly = self.clipper.GetOutput()
        self.mesh._update(cpoly)

        out = self.clipper.GetClippedOutputPort()
        if self._alpha:
            self.remnant.mapper.SetInputConnection(out)
            self.remnant.alpha(self._alpha).color((0.5, 0.5, 0.5))
            self.remnant.lighting("off").wireframe()
            plt.add(self.mesh, self.remnant)
        else:
            plt.add(self.mesh)

        if plt.interactor and plt.interactor.GetInitialized():
            self.widget.On()
            self._select_polygons(self.widget, "InteractionEvent")
            plt.interactor.Render()
        return self

    def remove_from(self, plt) -> Self:
        """Remove the widget to the provided `Plotter` instance."""
        self.widget.Off()
        plt.remove(self.remnant)
        if self.widget in plt.widgets:
            plt.widgets.remove(self.widget)
        return self

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

    def remove_observers(self, event="") -> Self:
        """Remove all observers from the widget."""
        if not event:
            self.widget.RemoveAllObservers()
        else:
            event = utils.get_vtk_name_event(event)
            self.widget.RemoveObservers(event)
        return self

    def keypress_activation(self, value=True) -> Self:
        """Enable or disable keypress activation of the widget."""
        self.widget.SetKeyPressActivation(value)
        return self

    def render(self) -> Self:
        """Render the current state of the widget."""
        if self.widget.GetInteractor() and self.widget.GetInteractor().GetInitialized():
            self.widget.GetInteractor().Render()
        return self

transform property

Get the transformation matrix.

add_observer(event, func, priority=1)

Add an observer to the widget.

Source code in vedo/addons/cutters.py
123
124
125
126
127
def add_observer(self, event, func, priority=1) -> int:
    """Add an observer to the widget."""
    event = utils.get_vtk_name_event(event)
    cid = self.widget.AddObserver(event, func, priority)
    return cid

add_to(plt)

Assign the widget to the provided Plotter instance.

Source code in vedo/addons/cutters.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def add_to(self, plt) -> Self:
    """Assign the widget to the provided `Plotter` instance."""
    self.widget.SetInteractor(plt.interactor)
    self.widget.SetCurrentRenderer(plt.renderer)
    if self.widget not in plt.widgets:
        plt.widgets.append(self.widget)

    cpoly = self.clipper.GetOutput()
    self.mesh._update(cpoly)

    out = self.clipper.GetClippedOutputPort()
    if self._alpha:
        self.remnant.mapper.SetInputConnection(out)
        self.remnant.alpha(self._alpha).color((0.5, 0.5, 0.5))
        self.remnant.lighting("off").wireframe()
        plt.add(self.mesh, self.remnant)
    else:
        plt.add(self.mesh)

    if plt.interactor and plt.interactor.GetInitialized():
        self.widget.On()
        self._select_polygons(self.widget, "InteractionEvent")
        plt.interactor.Render()
    return self

get_cut_mesh(invert=False)

Get the mesh resulting from the cut operation. Returns the original mesh and the remnant mesh.

Source code in vedo/addons/cutters.py
52
53
54
55
56
57
58
59
60
61
62
63
64
def get_cut_mesh(self, invert=False) -> Mesh:
    """
    Get the mesh resulting from the cut operation.
    Returns the original mesh and the remnant mesh.
    """
    self.clipper.Update()
    if invert:
        poly = self.clipper.GetClippedOutput()
    else:
        poly = self.clipper.GetOutput()
    out = Mesh(poly)
    out.copy_properties_from(self.mesh)
    return out

invert()

Invert selection.

Source code in vedo/addons/cutters.py
66
67
68
69
70
def invert(self) -> Self:
    """Invert selection."""
    self.clipper.SetInsideOut(not self.clipper.GetInsideOut())
    self.is_inverted = not self.clipper.GetInsideOut()
    return self

keypress_activation(value=True)

Enable or disable keypress activation of the widget.

Source code in vedo/addons/cutters.py
138
139
140
141
def keypress_activation(self, value=True) -> Self:
    """Enable or disable keypress activation of the widget."""
    self.widget.SetKeyPressActivation(value)
    return self

off()

Switch the widget on or off.

Source code in vedo/addons/cutters.py
77
78
79
80
def off(self) -> Self:
    """Switch the widget on or off."""
    self.widget.Off()
    return self

on()

Switch the widget on or off.

Source code in vedo/addons/cutters.py
72
73
74
75
def on(self) -> Self:
    """Switch the widget on or off."""
    self.widget.On()
    return self

remove_from(plt)

Remove the widget to the provided Plotter instance.

Source code in vedo/addons/cutters.py
115
116
117
118
119
120
121
def remove_from(self, plt) -> Self:
    """Remove the widget to the provided `Plotter` instance."""
    self.widget.Off()
    plt.remove(self.remnant)
    if self.widget in plt.widgets:
        plt.widgets.remove(self.widget)
    return self

remove_observers(event='')

Remove all observers from the widget.

Source code in vedo/addons/cutters.py
129
130
131
132
133
134
135
136
def remove_observers(self, event="") -> Self:
    """Remove all observers from the widget."""
    if not event:
        self.widget.RemoveAllObservers()
    else:
        event = utils.get_vtk_name_event(event)
        self.widget.RemoveObservers(event)
    return self

render()

Render the current state of the widget.

Source code in vedo/addons/cutters.py
143
144
145
146
147
def render(self) -> Self:
    """Render the current state of the widget."""
    if self.widget.GetInteractor() and self.widget.GetInteractor().GetInitialized():
        self.widget.GetInteractor().Render()
    return self

toggle()

Toggle the widget on or off.

Source code in vedo/addons/cutters.py
82
83
84
85
86
87
88
def toggle(self) -> Self:
    """Toggle the widget on or off."""
    if self.widget.GetEnabled():
        self.off()
    else:
        self.on()
    return self

BoxCutter

Bases: BaseCutter, vtkBoxWidget

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

Source code in vedo/addons/cutters.py
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
class BoxCutter(BaseCutter, vtki.vtkBoxWidget):
    """
    Create a box widget to cut away parts of a Mesh.
    """

    def __init__(
        self,
        mesh,
        invert=False,
        initial_bounds=(),
        padding=0.025,
        delayed=False,
        c=(0.25, 0.25, 0.25),
        alpha=0.05,
    ):
        """
        Create a box widget to cut away parts of a Mesh.

        Args:
            mesh (Mesh):
                the input mesh
            invert (bool):
                invert the clipping plane
            initial_bounds (list):
                initial bounds of the box widget
            padding (float):
                padding space around the input mesh
            delayed (bool):
                if True the callback is delayed until
                when the mouse button is released (useful for large meshes)
            c (color):
                color of the box cutter widget
            alpha (float):
                transparency of the cut-off part of the input mesh
        """
        super().__init__()
        self.name = "BoxCutter"

        self.mesh = mesh
        self.remnant = Mesh()
        self.remnant.name = mesh.name + "Remnant"
        self.remnant.pickable(False)

        self._alpha = alpha

        self._init_bounds = initial_bounds
        if len(self._init_bounds) == 0:
            self._init_bounds = mesh.bounds()
        else:
            self._init_bounds = initial_bounds

        self.__implicit_func = vtki.new("Planes")
        self.__implicit_func.SetBounds(self._init_bounds)

        poly = mesh.dataset
        self.clipper = vtki.new("ClipPolyData")
        self.clipper.GenerateClipScalarsOff()
        self.clipper.SetInputData(poly)
        self.clipper.SetClipFunction(self.__implicit_func)
        self.clipper.SetInsideOut(not invert)
        self.clipper.GenerateClippedOutputOn()
        self.clipper.Update()

        self.widget = vtki.vtkBoxWidget()

        self.widget.OutlineCursorWiresOn()
        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))
        self.widget.GetSelectedHandleProperty().SetColor(get_color("red5"))

        self.widget.GetOutlineProperty().SetColor(c)
        self.widget.GetOutlineProperty().SetOpacity(1)
        self.widget.GetOutlineProperty().SetLineWidth(1)
        self.widget.GetOutlineProperty().LightingOff()

        self.widget.GetSelectedFaceProperty().LightingOff()
        self.widget.GetSelectedFaceProperty().SetOpacity(0.1)

        self.widget.SetPlaceFactor(1.0 + padding)
        self.widget.SetInputData(poly)
        self.widget.PlaceWidget()
        if delayed:
            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
        else:
            self.widget.AddObserver("InteractionEvent", self._select_polygons)

    def _select_polygons(self, vobj, _event):
        vobj.GetPlanes(self.__implicit_func)

    def set_bounds(self, bb) -> Self:
        """Set the bounding box as a list of 6 values."""
        self.__implicit_func.SetBounds(bb)
        return self

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

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

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

enable_rotation(value=True)

Enable or disable rotation of the widget.

Source code in vedo/addons/cutters.py
398
399
400
401
402
def enable_rotation(self, value=True) -> Self:
    """Enable or disable rotation of the widget."""
    self.widget.SetRotationEnabled(value)
    self.can_rotate = bool(value)
    return self

enable_scaling(value=True)

Enable or disable scaling of the widget.

Source code in vedo/addons/cutters.py
392
393
394
395
396
def enable_scaling(self, value=True) -> Self:
    """Enable or disable scaling of the widget."""
    self.widget.SetScalingEnabled(value)
    self.can_scale = bool(value)
    return self

enable_translation(value=True)

Enable or disable translation of the widget.

Source code in vedo/addons/cutters.py
386
387
388
389
390
def enable_translation(self, value=True) -> Self:
    """Enable or disable translation of the widget."""
    self.widget.SetTranslationEnabled(value)
    self.can_translate = bool(value)
    return self

set_bounds(bb)

Set the bounding box as a list of 6 values.

Source code in vedo/addons/cutters.py
381
382
383
384
def set_bounds(self, bb) -> Self:
    """Set the bounding box as a list of 6 values."""
    self.__implicit_func.SetBounds(bb)
    return self

PlaneCutter

Bases: BaseCutter, vtkPlaneWidget

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

Source code in vedo/addons/cutters.py
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
class PlaneCutter(BaseCutter, vtki.vtkPlaneWidget):
    """
    Create a box widget to cut away parts of a Mesh.
    """

    def __init__(
        self,
        mesh,
        invert=False,
        origin=(),
        normal=(),
        padding=0.05,
        delayed=False,
        c=(0.25, 0.25, 0.25),
        alpha=0.05,
    ):
        """
        Create a box widget to cut away parts of a `Mesh`.

        Args:
            mesh (Mesh):
                the input mesh
            invert (bool):
                invert the clipping plane
            origin (list):
                origin of the plane
            normal (list):
                normal to the plane
            padding (float):
                padding around the input mesh
            delayed (bool):
                if True the callback is delayed until
                when the mouse button is released (useful for large meshes)
            c (color):
                color of the box cutter widget
            alpha (float):
                transparency of the cut-off part of the input mesh

        Examples:
            - [slice_plane3.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slice_plane3.py)
        """
        super().__init__()
        self.name = "PlaneCutter"

        self.mesh = mesh
        self.remnant = Mesh()
        self.remnant.name = mesh.name + "Remnant"
        self.remnant.pickable(False)

        self._alpha = alpha

        self.__implicit_func = vtki.new("Plane")

        poly = mesh.dataset
        self.clipper = vtki.new("ClipPolyData")
        self.clipper.GenerateClipScalarsOff()
        self.clipper.SetInputData(poly)
        self.clipper.SetClipFunction(self.__implicit_func)
        self.clipper.SetInsideOut(invert)
        self.clipper.GenerateClippedOutputOn()
        self.clipper.Update()

        self.widget = vtki.new("ImplicitPlaneWidget")

        self.widget.GetOutlineProperty().SetColor(get_color(c))
        self.widget.GetOutlineProperty().SetOpacity(0.25)
        self.widget.GetOutlineProperty().SetLineWidth(1)
        self.widget.GetOutlineProperty().LightingOff()

        self.widget.GetSelectedOutlineProperty().SetColor(get_color("red3"))

        self.widget.SetTubing(0)
        self.widget.SetDrawPlane(bool(alpha))
        self.widget.GetPlaneProperty().LightingOff()
        self.widget.GetPlaneProperty().SetOpacity(alpha)
        self.widget.GetSelectedPlaneProperty().SetColor(get_color("red5"))
        self.widget.GetSelectedPlaneProperty().LightingOff()

        self.widget.SetPlaceFactor(1.0 + padding)
        self.widget.SetInputData(poly)
        self.widget.PlaceWidget()
        if delayed:
            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
        else:
            self.widget.AddObserver("InteractionEvent", self._select_polygons)

        if len(origin) == 3:
            self.widget.SetOrigin(origin)
        else:
            self.widget.SetOrigin(mesh.center_of_mass())

        if len(normal) == 3:
            self.widget.SetNormal(normal)
        else:
            self.widget.SetNormal((1, 0, 0))

    @property
    def origin(self):
        """Get the origin of the plane."""
        return np.array(self.widget.GetOrigin())

    @origin.setter
    def origin(self, value):
        """Set the origin of the plane."""
        self.widget.SetOrigin(value)

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

    @normal.setter
    def normal(self, value):
        """Set the normal of the plane."""
        self.widget.SetNormal(value)

    def _select_polygons(self, vobj, _event) -> None:
        vobj.GetPlane(self.__implicit_func)

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

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

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

    def enable_rotation(self, value=True) -> Self:
        """Not supported by vtkPlaneWidget — state is stored but has no effect."""
        self.can_rotate = bool(value)
        return self

normal property writable

Get the normal of the plane.

origin property writable

Get the origin of the plane.

enable_origin_translation(value=True)

Enable or disable rotation of the widget.

Source code in vedo/addons/cutters.py
276
277
278
279
def enable_origin_translation(self, value=True) -> Self:
    """Enable or disable rotation of the widget."""
    self.widget.SetOriginTranslation(value)
    return self

enable_rotation(value=True)

Not supported by vtkPlaneWidget — state is stored but has no effect.

Source code in vedo/addons/cutters.py
287
288
289
290
def enable_rotation(self, value=True) -> Self:
    """Not supported by vtkPlaneWidget — state is stored but has no effect."""
    self.can_rotate = bool(value)
    return self

enable_scaling(value=True)

Enable or disable scaling of the widget.

Source code in vedo/addons/cutters.py
281
282
283
284
285
def enable_scaling(self, value=True) -> Self:
    """Enable or disable scaling of the widget."""
    self.widget.SetScaleEnabled(value)
    self.can_scale = bool(value)
    return self

enable_translation(value=True)

Enable or disable translation of the widget.

Source code in vedo/addons/cutters.py
269
270
271
272
273
274
def enable_translation(self, value=True) -> Self:
    """Enable or disable translation of the widget."""
    self.widget.SetOutlineTranslation(value)
    self.widget.SetOriginTranslation(value)
    self.can_translate = bool(value)
    return self

ProgressBarWidget

Bases: Actor2D

Add a progress bar in the rendering window.

Source code in vedo/addons/cutters.py
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
class ProgressBarWidget(Actor2D):
    """
    Add a progress bar in the rendering window.
    """

    def __init__(self, n=None, c="blue5", alpha=0.8, lw=10, autohide=True):
        """
        Add a progress bar window.

        Args:
            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.
        """
        self.n = 0
        self.iterations = n
        self.autohide = autohide

        ppoints = vtki.vtkPoints()  # Generate the line
        psqr = [[0, 0, 0], [1, 0, 0]]
        for i, pt in enumerate(psqr):
            ppoints.InsertPoint(i, *pt)
        lines = vtki.vtkCellArray()
        lines.InsertNextCell(len(psqr))
        for i in range(len(psqr)):
            lines.InsertCellPoint(i)

        pd = vtki.vtkPolyData()
        pd.SetPoints(ppoints)
        pd.SetLines(lines)

        super().__init__(pd)
        self.name = "ProgressBarWidget"

        self.coordinate = vtki.vtkCoordinate()
        self.coordinate.SetCoordinateSystemToNormalizedViewport()
        self.mapper.SetTransformCoordinate(self.coordinate)

        self.alpha(alpha)
        self.color(get_color(c))
        self.lw(lw * 2)

    def update(self, fraction=None) -> Self:
        """Update progress bar to fraction of the window width."""
        if fraction is None:
            if self.iterations is None:
                vedo.logger.error(
                    "Error in ProgressBarWindow: must specify iterations"
                )
                return self
            self.n += 1
            fraction = self.n / self.iterations

        if fraction >= 1 and self.autohide:
            fraction = 0

        psqr = [[0, 0, 0], [fraction, 0, 0]]
        vpts = utils.numpy2vtk(psqr, dtype=np.float32)
        self.dataset.GetPoints().SetData(vpts)
        return self

    def reset(self):
        """Reset progress bar."""
        self.n = 0
        self.update(0)
        return self

reset()

Reset progress bar.

Source code in vedo/addons/cutters.py
723
724
725
726
727
def reset(self):
    """Reset progress bar."""
    self.n = 0
    self.update(0)
    return self

update(fraction=None)

Update progress bar to fraction of the window width.

Source code in vedo/addons/cutters.py
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
def update(self, fraction=None) -> Self:
    """Update progress bar to fraction of the window width."""
    if fraction is None:
        if self.iterations is None:
            vedo.logger.error(
                "Error in ProgressBarWindow: must specify iterations"
            )
            return self
        self.n += 1
        fraction = self.n / self.iterations

    if fraction >= 1 and self.autohide:
        fraction = 0

    psqr = [[0, 0, 0], [fraction, 0, 0]]
    vpts = utils.numpy2vtk(psqr, dtype=np.float32)
    self.dataset.GetPoints().SetData(vpts)
    return self

RendererFrame

Bases: Actor2D

Add a line around the renderer subwindow.

Source code in vedo/addons/cutters.py
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
class RendererFrame(Actor2D):
    """
    Add a line around the renderer subwindow.
    """

    def __init__(self, c="k", alpha=None, lw=None, padding=None, pattern="brtl"):
        """
        Add a line around the renderer subwindow.

        Args:
            c (color):
                color of the line.
            alpha (float):
                opacity.
            lw (int):
                line width in pixels.
            padding (int):
                padding in pixel units.
            pattern (str):
                combination of characters `b` for bottom, `r` for right,
                `t` for top, `l` for left.
        """
        if lw is None:
            lw = settings.renderer_frame_width

        if alpha is None:
            alpha = settings.renderer_frame_alpha

        if padding is None:
            padding = settings.renderer_frame_padding

        if lw == 0 or alpha == 0:
            return
        c = get_color(c)

        a = padding
        b = 1 - padding
        p0 = [a, a]
        p1 = [b, a]
        p2 = [b, b]
        p3 = [a, b]
        disconnected = False
        if "b" in pattern and "r" in pattern and "t" in pattern and "l" in pattern:
            psqr = [p0, p1, p2, p3, p0]
        elif "b" in pattern and "r" in pattern and "t" in pattern:
            psqr = [p0, p1, p2, p3]
        elif "b" in pattern and "r" in pattern and "l" in pattern:
            psqr = [p3, p0, p1, p2]
        elif "b" in pattern and "t" in pattern and "l" in pattern:
            psqr = [p2, p3, p0, p1]
        elif "b" in pattern and "r" in pattern:
            psqr = [p0, p1, p2]
        elif "b" in pattern and "l" in pattern:
            psqr = [p3, p0, p1]
        elif "r" in pattern and "t" in pattern:
            psqr = [p1, p2, p3]
        elif "t" in pattern and "l" in pattern:
            psqr = [p3, p2, p1]
        elif "b" in pattern and "t" in pattern:
            psqr = [p0, p1, p3, p2]
            disconnected = True
        elif "r" in pattern and "l" in pattern:
            psqr = [p0, p3, p1, p2]
            disconnected = True
        elif "b" in pattern:
            psqr = [p0, p1]
        elif "r" in pattern:
            psqr = [p1, p2]
        elif "t" in pattern:
            psqr = [p3, p2]
        elif "l" in pattern:
            psqr = [p0, p3]
        else:
            vedo.logger.error(
                f"Error in RendererFrame: pattern not recognized {pattern}"
            )

        ppoints = vtki.vtkPoints()  # Generate the polyline
        for i, pt in enumerate(psqr):
            ppoints.InsertPoint(i, pt[0], pt[1], 0)

        lines = vtki.vtkCellArray()
        if disconnected:
            lines.InsertNextCell(2)
            lines.InsertCellPoint(0)
            lines.InsertCellPoint(1)
            lines.InsertNextCell(2)
            lines.InsertCellPoint(2)
            lines.InsertCellPoint(3)
        else:
            n = len(psqr)
            lines.InsertNextCell(n)
            for i in range(n):
                lines.InsertCellPoint(i)

        polydata = vtki.vtkPolyData()
        polydata.SetPoints(ppoints)
        polydata.SetLines(lines)

        super().__init__(polydata)
        self.name = "RendererFrame"

        self.coordinate = vtki.vtkCoordinate()
        self.coordinate.SetCoordinateSystemToNormalizedViewport()
        self.mapper.SetTransformCoordinate(self.coordinate)

        self.set_position_coordinates([0, 1], [1, 1])
        self.color(c)
        self.alpha(alpha)
        self.lw(lw)

SphereCutter

Bases: BaseCutter, vtkSphereWidget

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

Source code in vedo/addons/cutters.py
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
class SphereCutter(BaseCutter, vtki.vtkSphereWidget):
    """
    Create a box widget to cut away parts of a Mesh.
    """

    def __init__(
        self,
        mesh,
        invert=False,
        origin=(),
        radius=0,
        res=60,
        delayed=False,
        c="white",
        alpha=0.05,
    ):
        """
        Create a box widget to cut away parts of a Mesh.

        Args:
            mesh : Mesh
                the input mesh
            invert : bool
                invert the clipping
            origin : list
                initial position of the sphere widget
            radius : float
                initial radius of the sphere widget
            res : int
                resolution of the sphere widget
            delayed : bool
                if True the cutting callback is delayed until
                when the mouse button is released (useful for large meshes)
            c : color
                color of the box cutter widget
            alpha : float
                transparency of the cut-off part of the input mesh
        """
        super().__init__()
        self.name = "SphereCutter"

        self.mesh = mesh
        self.remnant = Mesh()
        self.remnant.name = mesh.name + "Remnant"
        self.remnant.pickable(False)

        self._alpha = alpha

        self.__implicit_func = vtki.new("Sphere")

        if len(origin) == 3:
            self.__implicit_func.SetCenter(origin)
        else:
            origin = mesh.center_of_mass()
            self.__implicit_func.SetCenter(origin)

        if radius > 0:
            self.__implicit_func.SetRadius(radius)
        else:
            radius = mesh.average_size() * 2
            self.__implicit_func.SetRadius(radius)

        poly = mesh.dataset
        self.clipper = vtki.new("ClipPolyData")
        self.clipper.GenerateClipScalarsOff()
        self.clipper.SetInputData(poly)
        self.clipper.SetClipFunction(self.__implicit_func)
        self.clipper.SetInsideOut(not invert)
        self.clipper.GenerateClippedOutputOn()
        self.clipper.Update()

        self.widget = vtki.vtkSphereWidget()

        self.widget.SetThetaResolution(res * 2)
        self.widget.SetPhiResolution(res)
        self.widget.SetRadius(radius)
        self.widget.SetCenter(origin)
        self.widget.SetRepresentation(2)
        self.widget.HandleVisibilityOff()

        self.widget.HandleVisibilityOff()
        self.widget.GetSphereProperty().SetColor(get_color(c))
        self.widget.GetSphereProperty().SetOpacity(0.2)
        self.widget.GetSelectedSphereProperty().SetColor(get_color("red5"))
        self.widget.GetSelectedSphereProperty().SetOpacity(0.2)

        self.widget.SetPlaceFactor(1.0)
        self.widget.SetInputData(poly)
        self.widget.PlaceWidget()
        if delayed:
            self.widget.AddObserver("EndInteractionEvent", self._select_polygons)
        else:
            self.widget.AddObserver("InteractionEvent", self._select_polygons)

    def _select_polygons(self, vobj, _event):
        vobj.GetSphere(self.__implicit_func)

    @property
    def center(self):
        """Get the center of the sphere."""
        return np.array(self.widget.GetCenter())

    @center.setter
    def center(self, value):
        """Set the center of the sphere."""
        self.widget.SetCenter(value)

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

    @radius.setter
    def radius(self, value):
        """Set the radius of the sphere."""
        self.widget.SetRadius(value)

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

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

    def enable_rotation(self, value=True) -> Self:
        """Not supported by vtkSphereWidget — state is stored but has no effect."""
        self.can_rotate = bool(value)
        return self

center property writable

Get the center of the sphere.

radius property writable

Get the radius of the sphere.

enable_rotation(value=True)

Not supported by vtkSphereWidget — state is stored but has no effect.

Source code in vedo/addons/cutters.py
534
535
536
537
def enable_rotation(self, value=True) -> Self:
    """Not supported by vtkSphereWidget — state is stored but has no effect."""
    self.can_rotate = bool(value)
    return self

enable_scaling(value=True)

Enable or disable scaling of the widget.

Source code in vedo/addons/cutters.py
528
529
530
531
532
def enable_scaling(self, value=True) -> Self:
    """Enable or disable scaling of the widget."""
    self.widget.SetScale(value)
    self.can_scale = bool(value)
    return self

enable_translation(value=True)

Enable or disable translation of the widget.

Source code in vedo/addons/cutters.py
522
523
524
525
526
def enable_translation(self, value=True) -> Self:
    """Enable or disable translation of the widget."""
    self.widget.SetTranslation(value)
    self.can_translate = bool(value)
    return self

measure

DistanceTool

Bases: Group

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

Source code in vedo/addons/measure.py
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
class DistanceTool(Group):
    """
    Create a tool to measure the distance between two clicked points.
    """

    def __init__(self, plotter=None, c="k", lw=2):
        """
        Create a tool to measure the distance between two clicked points.

        Examples:
            ```python
            from vedo import *
            mesh = ParametricShape("RandomHills").c("red5")
            plt = Plotter(axes=1)
            dtool = DistanceTool()
            dtool.on()
            plt.show(mesh, dtool)
            dtool.off()
            ```
            ![](https://vedo.embl.es/images/feats/dist_tool.png)
        """
        super().__init__()
        self.name = "DistanceTool"

        self.p0 = [0, 0, 0]
        self.p1 = [0, 0, 0]
        self.distance = 0
        if plotter is None:
            plotter = vedo.current_plotter()
        self.plotter = plotter
        # define self.callback as callable function:
        self.callback = lambda x: None
        self.cid = None
        self.color = c
        self.linewidth = lw
        self.toggle = True
        self.ruler = None
        self.title = ""

    def on(self) -> Self:
        """Switch tool on."""
        self.cid = self.plotter.add_callback("click", self._onclick)
        self.VisibilityOn()
        self.plotter.render()
        return self

    def off(self) -> None:
        """Switch tool off."""
        self.plotter.remove_callback(self.cid)
        self.VisibilityOff()
        self.ruler.off()
        self.plotter.render()

    def _onclick(self, event):
        if not event.actor:
            return

        self.clear()

        acts = []
        if self.toggle:
            self.p0 = event.picked3d
            acts.append(Point(self.p0, c=self.color))
        else:
            self.p1 = event.picked3d
            self.distance = np.linalg.norm(self.p1 - self.p0)
            acts.append(Point(self.p0, c=self.color))
            acts.append(Point(self.p1, c=self.color))
            self.ruler = Ruler2D(c=self.color)
            self.ruler.set_points(self.p0, self.p1)
            acts.append(self.ruler)

            if self.callback is not None:
                self.callback(event)

        for a in acts:
            try:
                self += a.actor
            except AttributeError:
                self += a
        self.toggle = not self.toggle

off()

Switch tool off.

Source code in vedo/addons/measure.py
230
231
232
233
234
235
def off(self) -> None:
    """Switch tool off."""
    self.plotter.remove_callback(self.cid)
    self.VisibilityOff()
    self.ruler.off()
    self.plotter.render()

on()

Switch tool on.

Source code in vedo/addons/measure.py
223
224
225
226
227
228
def on(self) -> Self:
    """Switch tool on."""
    self.cid = self.plotter.add_callback("click", self._onclick)
    self.VisibilityOn()
    self.plotter.render()
    return self

Ruler2D

Bases: vtkAxisActor2D

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

Source code in vedo/addons/measure.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
class Ruler2D(vtki.vtkAxisActor2D):
    """
    Create a ruler with tick marks, labels and a title.
    """

    def __init__(
        self,
        lw=2,
        ticks=True,
        labels=False,
        c="k",
        alpha=1,
        title="",
        font="Calco",
        font_size=24,
        bc=None,
    ):
        """
        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.

        Args:
            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](https://vedo.embl.es/fonts).
            font_size (int):
                font size
            bc (color):
                background color of the title

        Examples:
            ```python
            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()
            ```
            ![](https://vedo.embl.es/images/feats/dist_tool.png)
        """
        super().__init__()
        self.name = "Ruler2D"

        plt = vedo.current_plotter()
        if not plt:
            vedo.logger.error("Ruler2D need to initialize Plotter first.")
            raise RuntimeError()

        self.p0 = [0, 0, 0]
        self.p1 = [0, 0, 0]
        self.distance = 0
        self.title = title

        prop = self.GetProperty()
        tprop = self.GetTitleTextProperty()

        self.SetTitle(title)
        self.SetNumberOfLabels(9)

        if not font:
            font = settings.default_font
        if font.lower() == "courier":
            tprop.SetFontFamilyToCourier()
        elif font.lower() == "times":
            tprop.SetFontFamilyToTimes()
        elif font.lower() == "arial":
            tprop.SetFontFamilyToArial()
        else:
            tprop.SetFontFamily(vtki.VTK_FONT_FILE)
            tprop.SetFontFile(utils.get_font_path(font))
        tprop.SetFontSize(font_size)
        tprop.BoldOff()
        tprop.ItalicOff()
        tprop.ShadowOff()
        tprop.SetColor(get_color(c))
        tprop.SetOpacity(alpha)
        if bc is not None:
            bc = get_color(bc)
            tprop.SetBackgroundColor(bc)
            tprop.SetBackgroundOpacity(alpha)

        lprop = vtki.vtkTextProperty()
        lprop.ShallowCopy(tprop)
        self.SetLabelTextProperty(lprop)

        self.SetLabelFormat("%0.3g")
        self.SetTickVisibility(ticks)
        self.SetLabelVisibility(labels)
        prop.SetLineWidth(lw)
        prop.SetColor(get_color(c))

        self.renderer = plt.renderer
        self.cid = plt.interactor.AddObserver("RenderEvent", self._update_viz, 1.0)

    def color(self, c) -> Self:
        """Assign a new color."""
        c = get_color(c)
        self.GetTitleTextProperty().SetColor(c)
        self.GetLabelTextProperty().SetColor(c)
        self.GetProperty().SetColor(c)
        return self

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

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

    def _update_viz(self, _evt, _name) -> None:
        ren = self.renderer
        view_size = np.array(ren.GetSize())

        ren.SetWorldPoint(*self.p0, 1)
        ren.WorldToDisplay()
        disp_point1 = ren.GetDisplayPoint()[:2]
        disp_point1 = np.array(disp_point1) / view_size

        ren.SetWorldPoint(*self.p1, 1)
        ren.WorldToDisplay()
        disp_point2 = ren.GetDisplayPoint()[:2]
        disp_point2 = np.array(disp_point2) / view_size

        self.SetPoint1(*disp_point1)
        self.SetPoint2(*disp_point2)
        self.distance = np.linalg.norm(self.p1 - self.p0)
        self.SetRange(0.0, float(self.distance))
        if not self.title:
            self.SetTitle(utils.precision(self.distance, 3))

color(c)

Assign a new color.

Source code in vedo/addons/measure.py
141
142
143
144
145
146
147
def color(self, c) -> Self:
    """Assign a new color."""
    c = get_color(c)
    self.GetTitleTextProperty().SetColor(c)
    self.GetLabelTextProperty().SetColor(c)
    self.GetProperty().SetColor(c)
    return self

off()

Switch off the ruler completely.

Source code in vedo/addons/measure.py
149
150
151
152
def off(self) -> None:
    """Switch off the ruler completely."""
    self.renderer.RemoveObserver(self.cid)
    self.renderer.RemoveActor(self)

set_points(p0, p1)

Set new values for the ruler start and end points.

Source code in vedo/addons/measure.py
154
155
156
157
158
159
def set_points(self, p0, p1) -> Self:
    """Set new values for the ruler start and end points."""
    self.p0 = np.asarray(p0)
    self.p1 = np.asarray(p1)
    self._update_viz(0, 0)
    return self

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)

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

Parameters:

Name Type Description Default
label str

alternative fixed label to be shown

''
units_scale float

factor to scale units (e.g. μm to mm)

1
s float

size of the label

None
font str

font face. Check available fonts here.

None
italic float

italicness of the font in the range [0,1]

0
units str

string to be appended to the numeric value

''
lw int

line width in pixel units

1
precision int

nr of significant digits to be shown

3
label_rotation float

initial rotation of the label around the z-axis

0
axis_rotation float

initial rotation of the line around the main axis

0
tick_angle float

initial rotation of the line around the main axis

90

Examples:

Source code in vedo/addons/measure.py
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
def Ruler3D(
    p1,
    p2,
    units_scale=1,
    label="",
    s=None,
    font=None,
    italic=0,
    prefix="",
    units="",  # eg.'μm'
    c=(0.2, 0.1, 0.1),
    alpha=1,
    lw=1,
    precision=3,
    label_rotation=0,
    axis_rotation=0,
    tick_angle=90,
) -> Mesh:
    """
    Build a 3D ruler to indicate the distance of two points p1 and p2.

    Args:
        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](https://vedo.embl.es/fonts).
        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:
        - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)

        ![](https://vedo.embl.es/images/pyplot/goniometer.png)
    """

    if units_scale != 1.0 and units == "":
        raise ValueError(
            "When setting 'units_scale' to a value other than 1, "
            + "a 'units' arguments must be specified."
        )

    try:
        p1 = p1.pos()
    except AttributeError:
        pass

    try:
        p2 = p2.pos()
    except AttributeError:
        pass

    if len(p1) == 2:
        p1 = [p1[0], p1[1], 0.0]
    if len(p2) == 2:
        p2 = [p2[0], p2[1], 0.0]

    p1, p2 = np.asarray(p1), np.asarray(p2)
    q1, q2 = [0, 0, 0], [utils.mag(p2 - p1), 0, 0]
    q1, q2 = np.array(q1), np.array(q2)
    v = q2 - q1
    d = utils.mag(v) * units_scale

    pos = np.array(p1)
    p1 = p1 - pos
    p2 = p2 - pos

    if s is None:
        s = d * 0.02 * (1 / units_scale)

    if not label:
        label = str(d)
        if precision:
            label = utils.precision(d, precision)
    if prefix:
        label = prefix + "~" + label
    if units:
        label += "~" + units

    lb = shapes.Text3D(label, s=s, font=font, italic=italic, justify="center")
    if label_rotation:
        lb.rotate_z(label_rotation)
    lb.pos((q1 + q2) / 2)

    x0, x1 = lb.xbounds()
    gap = [(x1 - x0) / 2, 0, 0]
    pc1 = (v / 2 - gap) * 0.9 + q1
    pc2 = q2 - (v / 2 - gap) * 0.9

    lc1 = shapes.Line(q1 - v / 50, pc1).lw(lw)
    lc2 = shapes.Line(q2 + v / 50, pc2).lw(lw)

    zs = np.array([0, d / 50 * (1 / units_scale), 0])
    ml1 = shapes.Line(-zs, zs).lw(lw)
    ml2 = shapes.Line(-zs, zs).lw(lw)
    ml1.rotate_z(tick_angle - 90).pos(q1)
    ml2.rotate_z(tick_angle - 90).pos(q2)

    c1 = shapes.Circle(q1, r=d / 180 * (1 / units_scale), res=24)
    c2 = shapes.Circle(q2, r=d / 180 * (1 / units_scale), res=24)

    macts = merge(lb, lc1, lc2, c1, c2, ml1, ml2)
    macts.c(c).alpha(alpha)
    macts.properties.SetLineWidth(lw)
    macts.properties.LightingOff()
    macts.actor.UseBoundsOff()
    macts.rotate_x(axis_rotation)
    macts.reorient(q2 - q1, p2 - p1)
    macts.pos(pos)
    macts.bc("tomato").pickable(False)
    return macts

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)

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

Parameters:

Name Type Description Default
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

None
font str

font face. Check available fonts here.

'Normografo'
italic float

italicness of the font in the range [0,1]

0
units str

string to be appended to the numeric value

''
lw int

line width in pixel units

1
precision int

nr of significant digits to be shown

3
label_rotation float

initial rotation of the label around the z-axis

0
[x,y,z]axis_rotation

(float) initial rotation of the line around the main axis in degrees

required
xycross bool

show two back crossing lines in the xy plane

True

Examples:

Source code in vedo/addons/measure.py
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
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,
) -> Mesh | None:
    """
    A 3D ruler axes to indicate the sizes of the input scene or object.

    Args:
        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](https://vedo.embl.es/fonts).
        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:
        - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py)
    """
    if utils.is_sequence(inputobj):
        x0, x1, y0, y1, z0, z1 = inputobj
    else:
        x0, x1, y0, y1, z0, z1 = inputobj.bounds()
    dx, dy, dz = (y1 - y0) * xpadding, (x1 - x0) * ypadding, (y1 - y0) * zpadding
    d = np.sqrt((y1 - y0) ** 2 + (x1 - x0) ** 2 + (z1 - z0) ** 2)

    if not d:
        return None

    if s is None:
        s = d / 75

    acts, rx, ry = [], None, None
    if xtitle is not None and (x1 - x0) / d > 0.1:
        rx = Ruler3D(
            [x0, y0 - dx, z0],
            [x1, y0 - dx, z0],
            s=s,
            font=font,
            precision=precision,
            label_rotation=label_rotation,
            axis_rotation=xaxis_rotation,
            lw=lw,
            italic=italic,
            prefix=xtitle,
            label=xlabel,
            units=units,
        )
        acts.append(rx)

    if ytitle is not None and (y1 - y0) / d > 0.1:
        ry = Ruler3D(
            [x1 + dy, y0, z0],
            [x1 + dy, y1, z0],
            s=s,
            font=font,
            precision=precision,
            label_rotation=label_rotation,
            axis_rotation=yaxis_rotation,
            lw=lw,
            italic=italic,
            prefix=ytitle,
            label=ylabel,
            units=units,
        )
        acts.append(ry)

    if ztitle is not None and (z1 - z0) / d > 0.1:
        rz = Ruler3D(
            [x0 - dy, y0 + dz, z0],
            [x0 - dy, y0 + dz, z1],
            s=s,
            font=font,
            precision=precision,
            label_rotation=label_rotation,
            axis_rotation=zaxis_rotation + 90,
            lw=lw,
            italic=italic,
            prefix=ztitle,
            label=zlabel,
            units=units,
        )
        acts.append(rz)

    if xycross and rx and ry:
        lx = shapes.Line([x0, y0, z0], [x0, y1 + dx, z0])
        ly = shapes.Line([x0 - dy, y1, z0], [x1, y1, z0])
        d = min((x1 - x0), (y1 - y0)) / 200
        cxy = shapes.Circle([x0, y1, z0], r=d, res=15)
        acts.extend([lx, ly, cxy])

    macts = merge(acts)
    if not macts:
        return None
    macts.c(c).alpha(alpha).bc("t")
    macts.actor.UseBoundsOff()
    macts.actor.PickableOff()
    return macts

compute_visible_bounds(objs=None)

Calculate max objects bounds and sizes.

Source code in vedo/addons/measure.py
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
def compute_visible_bounds(objs=None) -> list:
    """Calculate max objects bounds and sizes."""
    bns = []

    plt = vedo.current_plotter()
    if objs is None and plt:
        objs = plt.actors
    if callable(objs):
        objs = objs()
    elif not utils.is_sequence(objs):
        objs = [objs]

    actors = [ob.actor for ob in objs if hasattr(ob, "actor") and ob.actor]

    try:
        # this block fails for VolumeSlice as vtkImageSlice.GetBounds() returns a pointer..
        # in any case we dont need axes for that one.
        for a in actors:
            if a and a.GetUseBounds():
                b = a.GetBounds()
                if b:
                    bns.append(b)
        if bns:
            max_bns = np.max(bns, axis=0)
            min_bns = np.min(bns, axis=0)
            vbb = [
                min_bns[0],
                max_bns[1],
                min_bns[2],
                max_bns[3],
                min_bns[4],
                max_bns[5],
            ]
        elif plt and plt.renderer:
            vbb = list(plt.renderer.ComputeVisiblePropBounds())
            max_bns = vbb
            min_bns = vbb
        sizes = np.array(
            [max_bns[1] - min_bns[0], max_bns[3] - min_bns[2], max_bns[5] - min_bns[4]]
        )
        return [vbb, sizes, min_bns, max_bns]

    except Exception:
        return [[0, 0, 0, 0, 0, 0], [0, 0, 0], 0, 0]