From 7ae382159ed080487566e2404afcf30b0ec6190f Mon Sep 17 00:00:00 2001
From: Andrew Maclean <andrew.amaclean@gmail.com>
Date: Tue, 31 Dec 2024 16:12:00 +1100
Subject: [PATCH] Adding ImplicitAnnulusWidget

---
 src/Cxx.md                                    |   1 +
 src/Cxx/GeometricObjects/ConesOnSphere.cxx    |   2 +-
 src/Cxx/GeometricObjects/GoldenBallSource.cxx |   2 +-
 src/Cxx/GeometricObjects/SphereSource.cxx     |   2 +-
 src/Cxx/Widgets/ImplicitAnnulusWidget.cxx     | 214 +++++++++++++++++
 src/PythonicAPI.md                            |   1 +
 .../GeometricObjects/ConesOnSphere.py         |   2 +-
 .../GeometricObjects/GoldenBallSource.py      |   2 +-
 .../GeometricObjects/SphereSource.py          |   2 +-
 .../Widgets/ImplicitAnnulusWidget.py          | 217 ++++++++++++++++++
 .../Widgets/ImplicitPlaneWidget2.py           |  46 ++--
 .../Cxx/Widgets/TestImplicitAnnulusWidget.png |   3 +
 .../Widgets/TestImplicitAnnulusWidget.png     |   3 +
 13 files changed, 467 insertions(+), 30 deletions(-)
 create mode 100644 src/Cxx/Widgets/ImplicitAnnulusWidget.cxx
 create mode 100644 src/PythonicAPI/Widgets/ImplicitAnnulusWidget.py
 create mode 100644 src/Testing/Baseline/Cxx/Widgets/TestImplicitAnnulusWidget.png
 create mode 100644 src/Testing/Baseline/PythonicAPI/Widgets/TestImplicitAnnulusWidget.png

diff --git a/src/Cxx.md b/src/Cxx.md
index badd640e660..11095d51ed8 100644
--- a/src/Cxx.md
+++ b/src/Cxx.md
@@ -1349,6 +1349,7 @@ See [this tutorial](http://www.vtk.org/Wiki/VTK/Tutorials/3DDataTypes) for a bri
 [ImageTracerWidget](/Cxx/Widgets/ImageTracerWidget) | Scribble on an image.
 [ImageTracerWidgetInsideContour](/Cxx/Widgets/ImageTracerWidgetInsideContour) | Highlight pixels inside a non-regular region scribbled on an image.
 [ImageTracerWidgetNonPlanar](/Cxx/Widgets/ImageTracerWidgetNonPlanar) | Draw on a non-planar surface.
+[ImplicitAnnulusWidget](/Cxx/Widgets/ImplicitAnnulusWidget) | Clip polydata with an implicit annulus.
 [ImplicitConeWidget](/Cxx/Widgets/ImplicitConeWidget) | An interactive implicit cone widget.
 [ImplicitPlaneWidget2](/Cxx/Widgets/ImplicitPlaneWidget2) | Clip polydata with an implicit plane.
 [LineWidget2](/Cxx/Widgets/LineWidget2) |
diff --git a/src/Cxx/GeometricObjects/ConesOnSphere.cxx b/src/Cxx/GeometricObjects/ConesOnSphere.cxx
index c38c9bae100..40789169b13 100644
--- a/src/Cxx/GeometricObjects/ConesOnSphere.cxx
+++ b/src/Cxx/GeometricObjects/ConesOnSphere.cxx
@@ -63,7 +63,7 @@ int main(int, char*[])
   vtkNew<vtkRenderer> renderer;
   vtkNew<vtkRenderWindow> renderWindow;
   renderWindow->SetSize(600, 600);
-  renderWindow->SetWindowName("Cones on Sphere");
+  renderWindow->SetWindowName("ConesOnSphere");
   renderWindow->AddRenderer(renderer);
   vtkNew<vtkRenderWindowInteractor> renderWindowInteractor;
   renderWindowInteractor->SetRenderWindow(renderWindow);
diff --git a/src/Cxx/GeometricObjects/GoldenBallSource.cxx b/src/Cxx/GeometricObjects/GoldenBallSource.cxx
index b5f6e308048..e58a9a736db 100644
--- a/src/Cxx/GeometricObjects/GoldenBallSource.cxx
+++ b/src/Cxx/GeometricObjects/GoldenBallSource.cxx
@@ -43,7 +43,7 @@ int main(int, char*[])
   renderer->SetBackground(colors->GetColor3d("ParaViewBkg").GetData());
   vtkNew<vtkRenderWindow> renderWindow;
   renderWindow->SetSize(600, 600);
-  renderWindow->SetWindowName("Golden Ball Source");
+  renderWindow->SetWindowName("GoldenBallSource");
   renderWindow->AddRenderer(renderer);
   vtkNew<vtkRenderWindowInteractor> renderWindowInteractor;
   renderWindowInteractor->SetRenderWindow(renderWindow);
diff --git a/src/Cxx/GeometricObjects/SphereSource.cxx b/src/Cxx/GeometricObjects/SphereSource.cxx
index 8af6cda05dd..4e147928969 100644
--- a/src/Cxx/GeometricObjects/SphereSource.cxx
+++ b/src/Cxx/GeometricObjects/SphereSource.cxx
@@ -42,7 +42,7 @@ int main(int, char*[])
   vtkNew<vtkRenderer> renderer;
   vtkNew<vtkRenderWindow> renderWindow;
   renderWindow->SetSize(600, 600);
-  renderWindow->SetWindowName("Sphere Source");
+  renderWindow->SetWindowName("SphereSource");
   renderWindow->AddRenderer(renderer);
   vtkNew<vtkRenderWindowInteractor> renderWindowInteractor;
   renderWindowInteractor->SetRenderWindow(renderWindow);
diff --git a/src/Cxx/Widgets/ImplicitAnnulusWidget.cxx b/src/Cxx/Widgets/ImplicitAnnulusWidget.cxx
new file mode 100644
index 00000000000..2f6da27cda8
--- /dev/null
+++ b/src/Cxx/Widgets/ImplicitAnnulusWidget.cxx
@@ -0,0 +1,214 @@
+#include <vtkActor.h>
+#include <vtkAnnulus.h>
+#include <vtkAppendPolyData.h>
+#include <vtkCamera.h>
+#include <vtkCameraOrientationWidget.h>
+#include <vtkClipPolyData.h>
+#include <vtkCommand.h>
+#include <vtkConeSource.h>
+#include <vtkGlyph3D.h>
+#include <vtkImplicitAnnulusRepresentation.h>
+#include <vtkImplicitAnnulusWidget.h>
+#include <vtkInteractorStyleSwitch.h>
+#include <vtkNamedColors.h>
+#include <vtkNew.h>
+#include <vtkPolyDataMapper.h>
+#include <vtkProperty.h>
+#include <vtkRenderWindow.h>
+#include <vtkRenderWindowInteractor.h>
+#include <vtkRenderer.h>
+#include <vtkSmartPointer.h>
+#include <vtkSphereSource.h>
+#include <vtkXMLPolyDataReader.h>
+
+#include <algorithm>
+#include <vector>
+
+namespace {
+// This does the actual work: updates the vtkAnnulus implicit function.
+// This in turn causes the pipeline to update and clip the object.
+// Callback for the interaction
+class vtkTICWCallback : public vtkCommand
+{
+public:
+  static vtkTICWCallback* New()
+  {
+    return new vtkTICWCallback;
+  }
+
+  void Execute(vtkObject* caller, unsigned long, void*) override
+  {
+    vtkImplicitAnnulusWidget* annulusWidget =
+        reinterpret_cast<vtkImplicitAnnulusWidget*>(caller);
+    vtkImplicitAnnulusRepresentation* rep =
+        reinterpret_cast<vtkImplicitAnnulusRepresentation*>(
+            annulusWidget->GetRepresentation());
+    rep->GetAnnulus(this->Annulus);
+    this->Actor->VisibilityOn();
+  }
+
+  vtkAnnulus* Annulus{nullptr};
+  vtkActor* Actor{nullptr};
+};
+
+vtkNew<vtkAppendPolyData> CreateMace();
+
+} // namespace
+
+int main(int argc, char* argv[])
+{
+
+  vtkNew<vtkNamedColors> colors;
+
+  vtkNew<vtkXMLPolyDataReader> reader;
+
+  // This portion of the code clips the mace or the input from the reader with
+  //  the vtkAnnulus implicit function. The clipped region is colored green.
+  vtkNew<vtkAnnulus> annulus;
+  vtkNew<vtkClipPolyData> clipper;
+  clipper->SetClipFunction(annulus);
+  clipper->InsideOutOn();
+
+  vtkSmartPointer<vtkPolyData> source;
+  if (argc < 2)
+  {
+    auto mace = CreateMace();
+    source = mace->GetOutput();
+  }
+  else
+  {
+    // For example: cow.vtp
+    reader->SetFileName(argv[1]);
+    reader->Update();
+    source = reader->GetOutput();
+  }
+  clipper->SetInputData(source);
+  auto bounds = source->GetBounds();
+
+  std::cout << "bounds: (";
+  for (auto i = 0; i < 6; ++i)
+  {
+    if (i == 0)
+    {
+      std::cout << bounds[i];
+    }
+    else
+    {
+      std::cout << " " << bounds[i];
+    }
+  }
+  std::cout << ")" << std::endl;
+
+  vtkNew<vtkPolyDataMapper> mapper;
+  mapper->SetInputData(source);
+
+  vtkNew<vtkProperty> backFaces;
+  backFaces->SetDiffuseColor(colors->GetColor3d("Gold").GetData());
+
+  vtkNew<vtkActor> actor;
+  actor->SetBackfaceProperty(backFaces);
+  actor->SetMapper(mapper);
+  actor->VisibilityOn();
+
+  vtkNew<vtkPolyDataMapper> selectMapper;
+  selectMapper->SetInputConnection(clipper->GetOutputPort());
+
+  vtkNew<vtkActor> selectActor;
+  selectActor->SetMapper(selectMapper);
+  selectActor->GetProperty()->SetColor(colors->GetColor3d("Lime").GetData());
+  selectActor->VisibilityOff();
+  selectActor->SetScale(1.01, 1.01, 1.01);
+
+  // Create the RenderWindow, Renderer and both Actors
+  vtkNew<vtkRenderer> renderer;
+  renderer->AddActor(actor);
+  renderer->AddActor(selectActor);
+  renderer->SetBackground(colors->GetColor3d("MidnightBlue").GetData());
+
+  vtkNew<vtkRenderWindow> renWin;
+  renWin->SetMultiSamples(0);
+  renWin->SetSize(600, 600);
+  renWin->SetWindowName("ImplicitAnnulusWidget");
+  renWin->AddRenderer(renderer);
+
+  vtkNew<vtkRenderWindowInteractor> iren;
+  renWin->SetInteractor(iren);
+  auto is = vtkInteractorStyleSwitch::SafeDownCast(iren->GetInteractorStyle());
+  if (is)
+  {
+    is->SetCurrentStyleToTrackballCamera();
+  }
+
+  // The SetInteractor method is how 3D widgets are associated with the render
+  // window interactor. Internally, SetInteractor sets up a bunch of callbacks
+  // using the Command/Observer mechanism (AddObserver()).
+  vtkNew<vtkTICWCallback> myCallback;
+  myCallback->Annulus = annulus;
+  myCallback->Actor = selectActor;
+
+  std::vector<double> radii;
+  for (auto i = 1; i < 6; i += 2)
+  {
+    radii.push_back((bounds[i] - bounds[i - 1]) / 2.0);
+  }
+  auto radius = *std::min_element(radii.cbegin(), radii.cend());
+
+  vtkNew<vtkImplicitAnnulusRepresentation> rep;
+  rep->SetPlaceFactor(1.25);
+  rep->ScaleEnabledOff();
+  rep->PlaceWidget(bounds);
+  rep->SetInnerRadius(radius * 0.5);
+  rep->SetOuterRadius(radius);
+  std::cout << "Inner radius: " << rep->GetInnerRadius() << std::endl;
+  std::cout << "Outer radius: " << rep->GetOuterRadius() << std::endl;
+
+  vtkNew<vtkImplicitAnnulusWidget> annulusWidget;
+  annulusWidget->SetInteractor(iren);
+  annulusWidget->SetRepresentation(rep);
+  annulusWidget->AddObserver(vtkCommand::InteractionEvent, myCallback);
+  annulusWidget->EnabledOn();
+
+  renderer->GetActiveCamera()->Azimuth(60);
+  renderer->GetActiveCamera()->Elevation(30);
+  renderer->ResetCamera();
+  renderer->GetActiveCamera()->Zoom(1.0);
+
+  vtkNew<vtkCameraOrientationWidget> cow;
+  cow->SetParentRenderer(renderer);
+  // Enable the widget.
+  cow->On();
+
+  // Render and interact.
+  iren->Initialize();
+  renWin->Render();
+  annulusWidget->On();
+
+  // Begin mouse interaction.
+  iren->Start();
+
+  return EXIT_SUCCESS;
+}
+
+namespace {
+vtkNew<vtkAppendPolyData> CreateMace()
+{
+  // Create a mace out of filters.
+  vtkNew<vtkSphereSource> sphere;
+  vtkNew<vtkConeSource> coneSource;
+  vtkNew<vtkGlyph3D> glyph;
+  glyph->SetInputConnection(sphere->GetOutputPort());
+  glyph->SetSourceConnection(coneSource->GetOutputPort());
+  glyph->SetVectorModeToUseNormal();
+  glyph->SetScaleModeToScaleByVector();
+  glyph->SetScaleFactor(0.25);
+
+  // The sphere and spikes are appended into a single polydata.
+  // This just makes things simpler to manage.
+  vtkNew<vtkAppendPolyData> apd;
+  apd->AddInputConnection(glyph->GetOutputPort());
+  apd->AddInputConnection(sphere->GetOutputPort());
+  apd->Update();
+
+  return apd;
+}
+} // namespace
diff --git a/src/PythonicAPI.md b/src/PythonicAPI.md
index 47f3abe1602..e279853df00 100644
--- a/src/PythonicAPI.md
+++ b/src/PythonicAPI.md
@@ -702,6 +702,7 @@ See [this tutorial](http://www.vtk.org/Wiki/VTK/Tutorials/3DDataTypes) for a bri
 [ImagePlaneWidget](/PythonicAPI/Widgets/ImagePlaneWidget) |
 [ImageTracerWidgetInsideContour](/PythonicAPI/Widgets/ImageTracerWidgetInsideContour) | Highlight pixels inside a non-regular region scribbled on an image.
 [ImageTracerWidgetNonPlanar](/PythonicAPI/Widgets/ImageTracerWidgetNonPlanar) | Draw on a non-planar surface.
+[ImplicitAnnulusWidget](/PythonicAPI/Widgets/ImplicitAnnulusWidget) | Clip polydata with an implicit annulus.
 [ImplicitConeWidget](/PythonicAPI/Widgets/ImplicitConeWidget) | An interactive implicit cone widget.
 [ImplicitPlaneWidget2](/PythonicAPI/Widgets/ImplicitPlaneWidget2) | Clip polydata with an implicit plane.
 [PolygonalSurfacePointPlacer](/PythonicAPI/PolyData/PolygonalSurfacePointPlacer) | Used in conjunction with vtkContourWidget to draw curves on a surface.
diff --git a/src/PythonicAPI/GeometricObjects/ConesOnSphere.py b/src/PythonicAPI/GeometricObjects/ConesOnSphere.py
index e94cbe88dd5..3914226f5d3 100755
--- a/src/PythonicAPI/GeometricObjects/ConesOnSphere.py
+++ b/src/PythonicAPI/GeometricObjects/ConesOnSphere.py
@@ -66,7 +66,7 @@ def main():
     actor = vtkActor(property=actor_prop, mapper=mapper)
 
     renderer = vtkRenderer(background=colors.GetColor3d('ParaViewBkg'))
-    render_window = vtkRenderWindow(size=(600, 600), window_name='Cones on Sphere')
+    render_window = vtkRenderWindow(size=(600, 600), window_name='ConesOnSphere')
     render_window.AddRenderer(renderer)
     render_window_interactor = vtkRenderWindowInteractor()
     render_window_interactor.SetRenderWindow(render_window)
diff --git a/src/PythonicAPI/GeometricObjects/GoldenBallSource.py b/src/PythonicAPI/GeometricObjects/GoldenBallSource.py
index 67516e6f9c9..d583d81a15a 100755
--- a/src/PythonicAPI/GeometricObjects/GoldenBallSource.py
+++ b/src/PythonicAPI/GeometricObjects/GoldenBallSource.py
@@ -35,7 +35,7 @@ def main():
     actor = vtkActor(property=actor_prop, mapper=mapper)
 
     renderer = vtkRenderer(background=colors.GetColor3d('ParaViewBkg'))
-    render_window = vtkRenderWindow(size=(600, 600), window_name='Golden Ball Source')
+    render_window = vtkRenderWindow(size=(600, 600), window_name='GoldenBallSource')
     render_window.AddRenderer(renderer)
     render_window_interactor = vtkRenderWindowInteractor()
     render_window_interactor.SetRenderWindow(render_window)
diff --git a/src/PythonicAPI/GeometricObjects/SphereSource.py b/src/PythonicAPI/GeometricObjects/SphereSource.py
index 01681a46d3f..72eeacfa5ab 100755
--- a/src/PythonicAPI/GeometricObjects/SphereSource.py
+++ b/src/PythonicAPI/GeometricObjects/SphereSource.py
@@ -33,7 +33,7 @@ def main():
     actor = vtkActor(property=actor_prop, mapper=mapper)
 
     renderer = vtkRenderer(background=colors.GetColor3d('ParaViewBkg'))
-    render_window = vtkRenderWindow(size=(600, 600), window_name='Sphere Source')
+    render_window = vtkRenderWindow(size=(600, 600), window_name='SphereSource')
     render_window.AddRenderer(renderer)
     render_window_interactor = vtkRenderWindowInteractor()
     render_window_interactor.SetRenderWindow(render_window)
diff --git a/src/PythonicAPI/Widgets/ImplicitAnnulusWidget.py b/src/PythonicAPI/Widgets/ImplicitAnnulusWidget.py
new file mode 100644
index 00000000000..e0789fb6ce0
--- /dev/null
+++ b/src/PythonicAPI/Widgets/ImplicitAnnulusWidget.py
@@ -0,0 +1,217 @@
+#!/usr/bin/env python3
+
+from dataclasses import dataclass
+from pathlib import Path
+
+# noinspection PyUnresolvedReferences
+import vtkmodules.vtkInteractionStyle
+# noinspection PyUnresolvedReferences
+import vtkmodules.vtkRenderingOpenGL2
+# noinspection PyUnresolvedReferences
+import vtkmodules.vtkRenderingUI
+from vtkmodules.vtkCommonColor import vtkNamedColors
+from vtkmodules.vtkCommonDataModel import vtkAnnulus
+from vtkmodules.vtkFiltersCore import (
+    vtkAppendPolyData,
+    vtkClipPolyData,
+    vtkGlyph3D
+)
+from vtkmodules.vtkFiltersSources import (
+    vtkConeSource,
+    vtkSphereSource)
+from vtkmodules.vtkIOXML import vtkXMLPolyDataReader
+from vtkmodules.vtkInteractionWidgets import (
+    vtkCameraOrientationWidget,
+    vtkImplicitAnnulusRepresentation,
+    vtkImplicitAnnulusWidget,
+)
+from vtkmodules.vtkRenderingCore import (
+    vtkActor,
+    vtkPolyDataMapper,
+    vtkProperty,
+    vtkRenderWindow,
+    vtkRenderWindowInteractor,
+    vtkRenderer
+)
+
+
+def get_program_parameters():
+    import argparse
+    description = 'Clip polydata with an implicit annulus.'
+    epilogue = '''
+    By specifying a .vtp file, the example can operate on arbitrary polydata.
+'''
+    parser = argparse.ArgumentParser(description=description, epilog=epilogue,
+                                     formatter_class=argparse.RawTextHelpFormatter)
+    parser.add_argument('file_name', nargs='?', default=None, help='A VTK Poly Data file e.g. cow.vtp')
+
+    args = parser.parse_args()
+    return args.file_name
+
+
+def main(fn):
+    colors = vtkNamedColors()
+
+    fn = get_program_parameters()
+
+    # This portion of the code clips the mace or the input from the reader with
+    #  the vtkAnnulus implicit function. The clipped region is colored green.
+    annulus = vtkAnnulus()
+    clipper = vtkClipPolyData(clip_function=annulus, inside_out=True)
+
+    if fn:
+        fp = Path(fn)
+        if not (fp.is_file() and fp.suffix == '.vtp'):
+            print(f'Expected an existing file name with extension .vtp\n  got: {fp}')
+            return
+
+        reader = vtkXMLPolyDataReader(file_name=fp)
+        reader >> clipper
+        source = reader.update().output
+    else:
+        mace = create_mace()
+        source = mace.output
+
+    clipper.input_data = source
+    bounds = source.bounds
+    print(f'bounds: ({fmt_floats(bounds)})')
+
+    # Create a mapper and actor.
+    mapper = vtkPolyDataMapper(input_data=source)
+
+    back_faces = vtkProperty(diffuse_color=colors.GetColor3d('Gold'))
+
+    actor = vtkActor(backface_property=back_faces, mapper=mapper, visibility=True)
+
+    select_mapper = vtkPolyDataMapper()
+    clipper >> select_mapper
+
+    select_actor = vtkActor(mapper=select_mapper, scale=(1.01, 1.01, 1.01), visibility=False)
+    select_actor.property.color = colors.GetColor3d('Lime')
+
+    # Create the RenderWindow, Renderer and both Actors
+    renderer = vtkRenderer(background=colors.GetColor3d('MidnightBlue'))
+    renderer.AddActor(actor)
+    renderer.AddActor(select_actor)
+    ren_win = vtkRenderWindow(multi_samples=0, size=(600, 600), window_name='ImplicitAnnulusWidget')
+    ren_win.AddRenderer(renderer)
+    iren = vtkRenderWindowInteractor()
+    ren_win.interactor = iren
+    # Since we import vtkmodules.vtkInteractionStyle we can do this
+    # because vtkInteractorStyleSwitch is automatically imported:
+    iren.interactor_style.SetCurrentStyleToTrackballCamera()
+
+    # The SetInteractor method is how 3D widgets are associated with the render
+    # window interactor. Internally, SetInteractor sets up a bunch of callbacks
+    # using the Command/Observer mechanism (AddObserver()).
+    my_callback = TICWCallback(annulus, select_actor)
+
+    radii = list()
+    for i in range(1, 6, 2):
+        radii.append((bounds[i] - bounds[i - 1]) / 2.0)
+    radius = min(radii)
+
+    rep = vtkImplicitAnnulusRepresentation(place_factor=1.25, scale_enabled=False,
+                                           inner_radius=radius * 0.5, outer_radius=radius)
+    rep.PlaceWidget(bounds)
+    print(f'Inner radius: {rep.inner_radius:g}')
+    print(f'Outer radius: {rep.outer_radius:g}')
+
+    annulus_widget = vtkImplicitAnnulusWidget(interactor=iren, representation=rep, enabled=True)
+    annulus_widget.AddObserver('InteractionEvent', my_callback)
+
+    renderer.active_camera.Azimuth(60)
+    renderer.active_camera.Elevation(30)
+    renderer.ResetCamera()
+    renderer.active_camera.Zoom(1.0)
+
+    cow = vtkCameraOrientationWidget(parent_renderer=renderer)
+    # Enable the widget.
+    cow.On()
+
+    # Render and interact.
+    iren.Initialize()
+    ren_win.Render()
+    annulus_widget.On()
+
+    # Begin mouse interaction.
+    iren.Start()
+
+
+class TICWCallback:
+    def __init__(self, annulus, select_actor):
+        self.annulus = annulus
+        self.select_actor = select_actor
+
+    def __call__(self, caller, ev):
+        rep = caller.representation
+        rep.GetAnnulus(self.annulus)
+        self.select_actor.visibility = True
+
+
+def create_mace():
+    # Create a mace out of filters.
+    sphere = vtkSphereSource()
+    cone_source = vtkConeSource()
+    glyph = vtkGlyph3D(source_connection=cone_source.output_port,
+                       vector_mode=Glyph3D.VectorMode.VTK_USE_NORMAL,
+                       scale_mode=Glyph3D.ScaleMode.VTK_SCALE_BY_VECTOR,
+                       scale_factor=0.25)
+    sphere >> glyph
+
+    # The sphere and spikes are appended into a single polydata.
+    # This just makes things simpler to manage.
+    apd = vtkAppendPolyData()
+    (glyph, sphere) >> apd
+    apd.update()
+    return apd
+
+
+def fmt_floats(v, w=0, d=6, pt='g'):
+    """
+    Pretty print a list or tuple of floats.
+
+    :param v: The list or tuple of floats.
+    :param w: Total width of the field.
+    :param d: The number of decimal places.
+    :param pt: The presentation type, 'f', 'g' or 'e'.
+    :return: A string.
+    """
+    pt = pt.lower()
+    if pt not in ['f', 'g', 'e']:
+        pt = 'f'
+    return ', '.join([f'{element:{w}.{d}{pt}}' for element in v])
+
+
+@dataclass(frozen=True)
+class Glyph3D:
+    @dataclass(frozen=True)
+    class ColorMode:
+        VTK_COLOR_BY_SCALE: int = 0
+        VTK_COLOR_BY_SCALAR: int = 1
+        VTK_COLOR_BY_VECTOR: int = 2
+
+    @dataclass(frozen=True)
+    class IndexMode:
+        VTK_INDEXING_OFF: int = 0
+        VTK_INDEXING_BY_SCALAR: int = 1
+        VTK_INDEXING_BY_VECTOR: int = 2
+
+    @dataclass(frozen=True)
+    class ScaleMode:
+        VTK_SCALE_BY_SCALAR: int = 0
+        VTK_SCALE_BY_VECTOR: int = 1
+        VTK_SCALE_BY_VECTORCOMPONENTS: int = 2
+        VTK_DATA_SCALING_OFF: int = 3
+
+    @dataclass(frozen=True)
+    class VectorMode:
+        VTK_USE_VECTOR: int = 0
+        VTK_USE_NORMAL: int = 1
+        VTK_VECTOR_ROTATION_OFF: int = 2
+        VTK_FOLLOW_CAMERA_DIRECTION: int = 3
+
+
+if __name__ == '__main__':
+    file_name = get_program_parameters()
+    main(file_name)
diff --git a/src/PythonicAPI/Widgets/ImplicitPlaneWidget2.py b/src/PythonicAPI/Widgets/ImplicitPlaneWidget2.py
index 43d65404b97..10dccc72e68 100755
--- a/src/PythonicAPI/Widgets/ImplicitPlaneWidget2.py
+++ b/src/PythonicAPI/Widgets/ImplicitPlaneWidget2.py
@@ -11,9 +11,7 @@ import vtkmodules.vtkRenderingUI
 from vtkmodules.vtkCommonColor import vtkNamedColors
 from vtkmodules.vtkCommonCore import vtkCommand
 from vtkmodules.vtkCommonDataModel import vtkPlane
-from vtkmodules.vtkFiltersCore import (
-    vtkClipPolyData
-)
+from vtkmodules.vtkFiltersCore import vtkClipPolyData
 from vtkmodules.vtkFiltersSources import vtkSphereSource
 from vtkmodules.vtkIOXML import vtkXMLPolyDataReader
 from vtkmodules.vtkInteractionWidgets import (
@@ -30,21 +28,37 @@ from vtkmodules.vtkRenderingCore import (
 )
 
 
+def get_program_parameters():
+    import argparse
+    description = 'How to use the second generation ImplicitPlaneWidget2 to interactively' \
+                  ' define the clipping plane for a polydata.'
+    epilogue = '''
+    If no arguments are specified, a vtkSphereSource generates the polydata.
+    By specifying a .vtp file, the example can operate on arbitrary polydata.
+'''
+    parser = argparse.ArgumentParser(description=description, epilog=epilogue,
+                                     formatter_class=argparse.RawTextHelpFormatter)
+    parser.add_argument('file_name', nargs='?', default=None, help='A VTK Poly Data file e.g. cow.vtp')
+
+    args = parser.parse_args()
+    return args.file_name
+
+
 def main(fn):
     colors = vtkNamedColors()
     sphere_source = vtkSphereSource(radius=10.0)
 
-    fp = None
+    fn = get_program_parameters()
+
+    # Setup a visualization pipeline.
+    plane = vtkPlane()
+    clipper = vtkClipPolyData(clip_function=plane, inside_out=True)
     if fn:
         fp = Path(fn)
         if not (fp.is_file() and fp.suffix == '.vtp'):
             print(f'Expected an existing file name with extension .vtp\n  got: {fp}')
             return
 
-    # Setup a visualization pipeline.
-    plane = vtkPlane()
-    clipper = vtkClipPolyData(clip_function=plane, inside_out=True)
-    if fp:
         reader = vtkXMLPolyDataReader(file_name=fp)
         reader >> clipper
     else:
@@ -102,22 +116,6 @@ class IPWCallback:
         rep.GetPlane(self.plane)
 
 
-def get_program_parameters():
-    import argparse
-    description = 'How to use the second generation ImplicitPlaneWidget2 to interactively' \
-                  ' define the clipping plane for a polydata.'
-    epilogue = '''
-    If no arguments are specified, a vtkSphereSource generates the polydata.
-    By specifying a .vtp file, the example can operate on arbitrary polydata.
-'''
-    parser = argparse.ArgumentParser(description=description, epilog=epilogue,
-                                     formatter_class=argparse.RawTextHelpFormatter)
-    parser.add_argument('file_name', nargs='?', default=None, help='A VTK Poly Data file e.g. cow.vtp')
-
-    args = parser.parse_args()
-    return args.file_name
-
-
 if __name__ == '__main__':
     file_name = get_program_parameters()
     main(file_name)
diff --git a/src/Testing/Baseline/Cxx/Widgets/TestImplicitAnnulusWidget.png b/src/Testing/Baseline/Cxx/Widgets/TestImplicitAnnulusWidget.png
new file mode 100644
index 00000000000..92e01a4196f
--- /dev/null
+++ b/src/Testing/Baseline/Cxx/Widgets/TestImplicitAnnulusWidget.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:241f0fc9174b4780c03951da0b5f6adc685b2a701c77180bd94e094155c66a3d
+size 22475
diff --git a/src/Testing/Baseline/PythonicAPI/Widgets/TestImplicitAnnulusWidget.png b/src/Testing/Baseline/PythonicAPI/Widgets/TestImplicitAnnulusWidget.png
new file mode 100644
index 00000000000..92e01a4196f
--- /dev/null
+++ b/src/Testing/Baseline/PythonicAPI/Widgets/TestImplicitAnnulusWidget.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:241f0fc9174b4780c03951da0b5f6adc685b2a701c77180bd94e094155c66a3d
+size 22475
-- 
GitLab