// Created on: 1998-11-12
// Created by: Robert COUBLANC
// Copyright (c) 1998-1999 Matra Datavision
// Copyright (c) 1999-2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

#include <ViewerTest.hxx>

#include <AIS_PlaneTrihedron.hxx>

#include <Quantity_NameOfColor.hxx>
#include <Draw_Interpretor.hxx>
#include <Draw.hxx>
#include <Draw_Appli.hxx>
#include <DBRep.hxx>
#include <DBRep_DrawableShape.hxx>

#include <Font_BRepFont.hxx>
#include <Font_BRepTextBuilder.hxx>
#include <Font_FontMgr.hxx>
#include <Message.hxx>
#include <NCollection_List.hxx>

#include <OSD_Chronometer.hxx>
#include <TCollection_AsciiString.hxx>
#include <V3d_Viewer.hxx>
#include <V3d_View.hxx>
#include <V3d.hxx>

#include <AIS_Shape.hxx>
#include <AIS_DisplayMode.hxx>
#include <AIS_PointCloud.hxx>
#include <BRepLib_PointCloudShape.hxx>
#include <Standard_Integer.hxx>
#include <NCollection_Map.hxx>
#include <ViewerTest_AutoUpdater.hxx>
#include <ViewerTest_DoubleMapOfInteractiveAndName.hxx>
#include <ViewerTest_EventManager.hxx>

#include <TopoDS_Solid.hxx>
#include <BRepTools.hxx>
#include <BRep_Builder.hxx>
#include <TopAbs_ShapeEnum.hxx>

#include <TopoDS.hxx>
#include <BRep_Tool.hxx>
#include <TopExp_Explorer.hxx>

#include <BRepAdaptor_Curve.hxx>
#include <BRepAdaptor_Surface.hxx>

#include <TopAbs.hxx>
#include <TopExp.hxx>
#include <TopoDS_Vertex.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Face.hxx>

#include <Draw_Window.hxx>
#include <AIS_InteractiveObject.hxx>
#include <AIS_ColoredShape.hxx>

#include <AIS_InteractiveContext.hxx>
#include <Geom_Plane.hxx>
#include <gp_Pln.hxx>
#include <TCollection_ExtendedString.hxx>
#include <TCollection_HAsciiString.hxx>
#include <GC_MakePlane.hxx>
#include <gp_Circ.hxx>
#include <AIS_Axis.hxx>
#include <Geom_Axis2Placement.hxx>
#include <Geom_Axis1Placement.hxx>
#include <AIS_Trihedron.hxx>
#include <gp_Trsf.hxx>
#include <gp_Quaternion.hxx>
#include <TopLoc_Location.hxx>

#include <HLRAlgo_Projector.hxx>
#include <HLRBRep_Algo.hxx>
#include <HLRBRep_PolyAlgo.hxx>
#include <HLRBRep_PolyHLRToShape.hxx>
#include <HLRBRep_HLRToShape.hxx>
#include <Aspect_Window.hxx>

#include <Graphic3d_ArrayOfPoints.hxx>
#include <Graphic3d_ArrayOfSegments.hxx>
#include <Graphic3d_ArrayOfPolylines.hxx>
#include <Graphic3d_ArrayOfTriangles.hxx>
#include <Graphic3d_ArrayOfTriangleFans.hxx>
#include <Graphic3d_ArrayOfTriangleStrips.hxx>
#include <Graphic3d_ArrayOfQuadrangles.hxx>
#include <Graphic3d_ArrayOfQuadrangleStrips.hxx>
#include <Graphic3d_ArrayOfPolygons.hxx>
#include <Graphic3d_AttribBuffer.hxx>
#include <Graphic3d_AspectMarker3d.hxx>
#include <Graphic3d_Group.hxx>
#include <Standard_Real.hxx>

#include <AIS_Circle.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <Geom_Circle.hxx>
#include <GC_MakeCircle.hxx>
#include <Select3D_SensitiveCircle.hxx>
#include <SelectMgr_EntityOwner.hxx>
#include <SelectMgr_Selection.hxx>
#include <StdFail_NotDone.hxx>
#include <StdPrs_ShadedShape.hxx>
#include <TopoDS_Wire.hxx>

#include <AIS_MultipleConnectedInteractive.hxx>
#include <AIS_ConnectedInteractive.hxx>
#include <AIS_TextLabel.hxx>

#include <Select3D_SensitiveSegment.hxx>
#include <Select3D_SensitivePrimitiveArray.hxx>
#include <Select3D_SensitivePoint.hxx>
#include <Select3D_SensitivePoly.hxx>
#include <StdPrs_Curve.hxx>

#include <BRepExtrema_ExtPC.hxx>
#include <BRepExtrema_ExtPF.hxx>

#include <Prs3d_Arrow.hxx>
#include <Prs3d_ArrowAspect.hxx>
#include <Prs3d_DatumAttribute.hxx>
#include <Prs3d_DatumAspect.hxx>
#include <Prs3d_Drawer.hxx>
#include <Prs3d_VertexDrawMode.hxx>
#include <Prs3d_LineAspect.hxx>
#include <Prs3d_PointAspect.hxx>
#include <Prs3d_Presentation.hxx>
#include <Prs3d_TextAspect.hxx>
#include <Prs3d_ToolCylinder.hxx>
#include <Prs3d_ToolSphere.hxx>
#include <Prs3d_ToolTorus.hxx>

#include <Image_AlienPixMap.hxx>
#include <NCollection_Array1.hxx>
#include <NCollection_HArray1.hxx>
#include <NCollection_Sequence.hxx>
#include <NCollection_HSequence.hxx>

extern NCollection_DoubleMap<occ::handle<AIS_InteractiveObject>, TCollection_AsciiString>&
            GetMapOfAIS();
extern bool VDisplayAISObject(const TCollection_AsciiString&            theName,
                              const occ::handle<AIS_InteractiveObject>& theAISObj,
                              bool                                      theReplaceIfExists = true);
extern occ::handle<AIS_InteractiveContext>& TheAISContext();

namespace
{
static bool convertToColor(
  const occ::handle<NCollection_HSequence<TCollection_AsciiString>>& theColorValues,
  Quantity_Color&                                                    theColor)
{
  const char* anArgs[3] = {theColorValues->Size() >= 1 ? theColorValues->Value(1).ToCString() : "",
                           theColorValues->Size() >= 2 ? theColorValues->Value(2).ToCString() : "",
                           theColorValues->Size() >= 3 ? theColorValues->Value(3).ToCString() : ""};
  return Draw::ParseColor(theColorValues->Size(), anArgs, theColor) != 0;
}

static bool convertToDatumPart(const TCollection_AsciiString& theValue,
                               Prs3d_DatumParts&              theDatumPart)
{
  TCollection_AsciiString aValue = theValue;
  aValue.LowerCase();
  if (aValue == "origin")
    theDatumPart = Prs3d_DatumParts_Origin;
  else if (aValue == "xaxis")
    theDatumPart = Prs3d_DatumParts_XAxis;
  else if (aValue == "yaxis")
    theDatumPart = Prs3d_DatumParts_YAxis;
  else if (aValue == "zaxis")
    theDatumPart = Prs3d_DatumParts_ZAxis;
  else if (aValue == "xarrow")
    theDatumPart = Prs3d_DatumParts_XArrow;
  else if (aValue == "yarrow")
    theDatumPart = Prs3d_DatumParts_YArrow;
  else if (aValue == "zarrow")
    theDatumPart = Prs3d_DatumParts_ZArrow;
  else if (aValue == "xoyaxis")
    theDatumPart = Prs3d_DatumParts_XOYAxis;
  else if (aValue == "yozaxis")
    theDatumPart = Prs3d_DatumParts_YOZAxis;
  else if (aValue == "xozaxis")
    theDatumPart = Prs3d_DatumParts_XOZAxis;
  else if (aValue == "whole")
    theDatumPart = Prs3d_DatumParts_None;
  else
  {
    return false;
  }
  return true;
}

static void convertToDatumParts(const TCollection_AsciiString&      theValue,
                                NCollection_List<Prs3d_DatumParts>& theParts)
{
  TCollection_AsciiString aValue    = theValue;
  const int               aSplitPos = theValue.Search("|");
  Prs3d_DatumParts        aPart     = Prs3d_DatumParts_None;
  if (aSplitPos > 0)
  {
    convertToDatumParts(theValue.SubString(aSplitPos + 1, theValue.Length()), theParts);
    if (aSplitPos == 1) // first symbol
    {
      return;
    }
    aValue = theValue.SubString(1, aSplitPos - 1);
  }
  if (convertToDatumPart(aValue, aPart))
  {
    theParts.Append(aPart);
  }
}

static bool convertToDatumAttribute(const TCollection_AsciiString& theValue,
                                    Prs3d_DatumAttribute&          theAttribute)
{
  TCollection_AsciiString aValue = theValue;
  aValue.LowerCase();
  if (aValue == "xaxislength")
    theAttribute = Prs3d_DatumAttribute_XAxisLength;
  else if (aValue == "yaxislength")
    theAttribute = Prs3d_DatumAttribute_YAxisLength;
  else if (aValue == "zaxislength")
    theAttribute = Prs3d_DatumAttribute_ZAxisLength;
  else if (aValue == "tuberadiuspercent")
    theAttribute = Prs3d_DatumAttribute_ShadingTubeRadiusPercent;
  else if (aValue == "coneradiuspercent")
    theAttribute = Prs3d_DatumAttribute_ShadingConeRadiusPercent;
  else if (aValue == "conelengthpercent")
    theAttribute = Prs3d_DatumAttribute_ShadingConeLengthPercent;
  else if (aValue == "originradiuspercent")
    theAttribute = Prs3d_DatumAttribute_ShadingOriginRadiusPercent;
  else if (aValue == "shadingnumberoffacettes")
    theAttribute = Prs3d_DatumAttribute_ShadingNumberOfFacettes;
  else
    return false;
  return true;
}

static void convertToDatumAttributes(const TCollection_AsciiString&          theValue,
                                     NCollection_List<Prs3d_DatumAttribute>& theAttributes)
{
  TCollection_AsciiString aValue      = theValue;
  const int               aSplitPos   = theValue.Search("|");
  Prs3d_DatumAttribute    anAttribute = Prs3d_DatumAttribute_XAxisLength;
  if (aSplitPos > 0)
  {
    convertToDatumAttributes(theValue.SubString(aSplitPos + 1, theValue.Length()), theAttributes);
    if (aSplitPos == 1) // first symbol
    {
      return;
    }
    aValue = theValue.SubString(1, aSplitPos - 1);
  }
  if (convertToDatumAttribute(aValue, anAttribute))
  {
    theAttributes.Append(anAttribute);
  }
}

static bool convertToDatumAxes(const TCollection_AsciiString& theValue,
                               Prs3d_DatumAxes&               theDatumAxes)
{
  TCollection_AsciiString aValue = theValue;
  aValue.LowerCase();
  if (aValue == "x")
    theDatumAxes = Prs3d_DatumAxes_XAxis;
  else if (aValue == "y")
    theDatumAxes = Prs3d_DatumAxes_YAxis;
  else if (aValue == "z")
    theDatumAxes = Prs3d_DatumAxes_ZAxis;
  else if (aValue == "xy")
    theDatumAxes = Prs3d_DatumAxes_XYAxes;
  else if (aValue == "zy")
    theDatumAxes = Prs3d_DatumAxes_YZAxes;
  else if (aValue == "xz")
    theDatumAxes = Prs3d_DatumAxes_XZAxes;
  else if (aValue == "xyz")
    theDatumAxes = Prs3d_DatumAxes_XYZAxes;
  else
  {
    return false;
  }
  return true;
}

static bool setTrihedronParams(int                               theArgsNb,
                               const char**                      theArgVec,
                               const occ::handle<AIS_Trihedron>& theTrihedron)
{
  NCollection_DataMap<TCollection_AsciiString,
                      occ::handle<NCollection_HSequence<TCollection_AsciiString>>>
                          aMapOfArgs;
  TCollection_AsciiString aParseKey;
  for (int anArgIt = 1; anArgIt < theArgsNb; ++anArgIt)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIt]);
    if (anArg.Value(1) == '-' && !anArg.IsRealValue(true))
    {
      aParseKey = anArg;
      aParseKey.Remove(1);
      aParseKey.LowerCase();
      std::string aKey = aParseKey.ToCString();
      aMapOfArgs.Bind(aParseKey, new NCollection_HSequence<TCollection_AsciiString>());
      continue;
    }

    if (aParseKey.IsEmpty())
    {
      continue;
    }

    aMapOfArgs(aParseKey)->Append(anArg);
  }

  // Check parameters
  if ((aMapOfArgs.IsBound("xaxis") && !aMapOfArgs.IsBound("zaxis"))
      || (!aMapOfArgs.IsBound("xaxis") && aMapOfArgs.IsBound("zaxis")))
  {
    Message::SendFail("Syntax error: -xaxis and -zaxis parameters are to set together");
    return false;
  }

  occ::handle<NCollection_HSequence<TCollection_AsciiString>> aValues;
  occ::handle<Geom_Axis2Placement> aComponent = theTrihedron->Component();
  if (aMapOfArgs.Find("origin", aValues))
  {
    aComponent->SetLocation(gp_Pnt(aValues->Value(1).RealValue(),
                                   aValues->Value(2).RealValue(),
                                   aValues->Value(3).RealValue()));
  }
  occ::handle<NCollection_HSequence<TCollection_AsciiString>> aXValues, aZValues;
  if (aMapOfArgs.Find("xaxis", aXValues) && aMapOfArgs.Find("zaxis", aZValues))
  {
    gp_Dir aXDir(aXValues->Value(1).RealValue(),
                 aXValues->Value(2).RealValue(),
                 aXValues->Value(3).RealValue());

    gp_Dir aZDir(aZValues->Value(1).RealValue(),
                 aZValues->Value(2).RealValue(),
                 aZValues->Value(3).RealValue());

    if (!aZDir.IsNormal(aXDir, M_PI / 180.0))
    {
      Message::SendFail("Syntax error - parameters 'xaxis' and 'zaxis' are not applied as VectorX "
                        "is not normal to VectorZ");
      return false;
    }

    aComponent->SetAx2(gp_Ax2(aComponent->Location(), aZDir, aXDir));
  }

  if (aMapOfArgs.Find("dispmode", aValues))
  {
    TCollection_AsciiString aValue(aValues->Value(1));
    bool                    isWireframe = true;
    if (aValue.IsEqual("sh") || aValue.IsEqual("shading"))
      isWireframe = false;
    theTrihedron->SetDatumDisplayMode(isWireframe ? Prs3d_DM_WireFrame : Prs3d_DM_Shaded);
  }

  if (aMapOfArgs.Find("hidelabels", aValues))
  {
    bool toHideLabels = true;
    if (aValues->Size() == 1)
    {
      Draw::ParseOnOff(aValues->First().ToCString(), toHideLabels);
    }
    else if (aValues->Size() != 0)
    {
      Message::SendFail("Syntax error: -hidelabels expects parameter 'on' or 'off' after");
      return false;
    }

    if (!theTrihedron->Attributes()->HasOwnDatumAspect())
    {
      theTrihedron->Attributes()->SetDatumAspect(new Prs3d_DatumAspect());
    }
    theTrihedron->Attributes()->DatumAspect()->SetDrawLabels(!toHideLabels);
  }

  if (aMapOfArgs.Find("hidearrows", aValues))
  {
    bool toHideArrows = true;
    if (aValues->Size() == 1)
    {
      Draw::ParseOnOff(aValues->First().ToCString(), toHideArrows);
    }
    else if (aValues->Size() != 0)
    {
      Message::SendFail("Syntax error: -hidearrows expects parameter 'on' or 'off' after");
      return false;
    }

    if (!theTrihedron->Attributes()->HasOwnDatumAspect())
    {
      theTrihedron->Attributes()->SetDatumAspect(new Prs3d_DatumAspect());
    }
    theTrihedron->Attributes()->DatumAspect()->SetDrawArrows(!toHideArrows);
  }

  if (aMapOfArgs.Find("color", aValues))
  {
    NCollection_List<Prs3d_DatumParts> aParts;
    if (aValues->Size() < 2)
    {
      Message::SendFail("Syntax error: -color wrong parameters");
      return false;
    }

    convertToDatumParts(aValues->Value(1), aParts);
    aValues->Remove(1); // datum part is processed
    Quantity_Color aColor;
    if (!convertToColor(aValues, aColor))
    {
      Message::SendFail("Syntax error: -color wrong parameters");
      return false;
    }

    for (NCollection_List<Prs3d_DatumParts>::Iterator anIterator(aParts); anIterator.More();
         anIterator.Next())
    {
      Prs3d_DatumParts aDatumPart = anIterator.Value();
      if (aDatumPart == Prs3d_DatumParts_None)
      {
        theTrihedron->SetColor(aColor);
      }
      else
      {
        theTrihedron->SetDatumPartColor(aDatumPart, aColor);
      }
    }
  }

  if (aMapOfArgs.Find("textcolor", aValues))
  {
    Prs3d_DatumParts aDatumPart = Prs3d_DatumParts_None;
    if (!aValues->IsEmpty() && convertToDatumPart(aValues->First(), aDatumPart)
        && aDatumPart >= Prs3d_DatumParts_XAxis && aDatumPart <= Prs3d_DatumParts_ZAxis)
    {
      aValues->Remove(1); // datum part is processed
    }

    Quantity_Color aColor;
    if (!convertToColor(aValues, aColor))
    {
      Message::SendFail("Syntax error: -textcolor wrong parameters");
      return false;
    }

    if (aDatumPart != Prs3d_DatumParts_None)
    {
      theTrihedron->SetTextColor(aDatumPart, aColor);
    }
    else
    {
      theTrihedron->SetTextColor(aColor);
    }
  }

  if (aMapOfArgs.Find("arrowcolor", aValues))
  {
    Prs3d_DatumParts aDatumPart = Prs3d_DatumParts_None;
    if (!aValues->IsEmpty() && convertToDatumPart(aValues->First(), aDatumPart)
        && ((aDatumPart >= Prs3d_DatumParts_XArrow && aDatumPart <= Prs3d_DatumParts_ZArrow)
            || (aDatumPart >= Prs3d_DatumParts_XAxis && aDatumPart <= Prs3d_DatumParts_ZAxis)))
    {
      aValues->Remove(1); // datum part is processed
    }

    Quantity_Color aColor;
    if (!convertToColor(aValues, aColor))
    {
      Message::SendFail("Syntax error: -arrowcolor wrong parameters");
      return false;
    }

    if (aDatumPart != Prs3d_DatumParts_None)
    {
      Prs3d_DatumParts anArrowPart = Prs3d_DatumAspect::ArrowPartForAxis(aDatumPart);
      theTrihedron->SetArrowColor(anArrowPart, aColor);
    }
    else
    {
      theTrihedron->SetArrowColor(aColor);
    }
  }

  if (aMapOfArgs.Find("attribute", aValues))
  {
    NCollection_List<Prs3d_DatumAttribute> anAttributes;
    if (aValues->Size() != 2)
    {
      Message::SendFail("Syntax error: -attribute wrong parameters");
      return false;
    }

    convertToDatumAttributes(aValues->Value(1), anAttributes);
    if (!theTrihedron->Attributes()->HasOwnDatumAspect())
      theTrihedron->Attributes()->SetDatumAspect(new Prs3d_DatumAspect());
    for (NCollection_List<Prs3d_DatumAttribute>::Iterator anIterator(anAttributes);
         anIterator.More();
         anIterator.Next())
    {
      theTrihedron->Attributes()->DatumAspect()->SetAttribute(anIterator.Value(),
                                                              aValues->Value(2).RealValue());
    }
  }

  if (aMapOfArgs.Find("priority", aValues))
  {
    Prs3d_DatumParts aDatumPart;
    if (aValues->Size() < 2 || !convertToDatumPart(aValues->Value(1), aDatumPart))
    {
      Message::SendFail("Syntax error: -priority wrong parameters");
      return false;
    }
    theTrihedron->SetSelectionPriority(aDatumPart, aValues->Value(2).IntegerValue());
  }

  if (aMapOfArgs.Find("labels", aValues) || aMapOfArgs.Find("label", aValues))
  {
    Prs3d_DatumParts aDatumPart = Prs3d_DatumParts_None;
    if (aValues->Size() >= 2 && convertToDatumPart(aValues->Value(1), aDatumPart)
        && aDatumPart >= Prs3d_DatumParts_XAxis
        && aDatumPart <= Prs3d_DatumParts_ZAxis) // labels are set to axes only
    {
      theTrihedron->SetLabel(aDatumPart, aValues->Value(2));
    }
    else
    {
      Message::SendFail("Syntax error: -labels wrong parameters");
      return false;
    }
  }

  if (aMapOfArgs.Find("drawaxes", aValues))
  {
    Prs3d_DatumAxes aDatumAxes = Prs3d_DatumAxes_XAxis;
    if (aValues->Size() < 1 || !convertToDatumAxes(aValues->Value(1), aDatumAxes))
    {
      Message::SendFail("Syntax error: -drawaxes wrong parameters");
      return false;
    }
    if (!theTrihedron->Attributes()->HasOwnDatumAspect())
      theTrihedron->Attributes()->SetDatumAspect(new Prs3d_DatumAspect());
    theTrihedron->Attributes()->DatumAspect()->SetDrawDatumAxes(aDatumAxes);
  }
  return true;
}

//! Auxiliary function to parse font aspect style argument
static bool parseFontStyle(const TCollection_AsciiString& theArg, Font_FontAspect& theAspect)
{
  if (theArg == "regular" || *theArg.ToCString() == 'r')
  {
    theAspect = Font_FA_Regular;
    return true;
  }
  else if (theArg == "bolditalic" || theArg == "bold-italic" || theArg == "italic-bold"
           || theArg == "italicbold")
  {
    theAspect = Font_FA_BoldItalic;
    return true;
  }
  else if (theArg == "bold" || *theArg.ToCString() == 'b')
  {
    theAspect = Font_FA_Bold;
    return true;
  }
  else if (theArg == "italic" || *theArg.ToCString() == 'i')
  {
    theAspect = Font_FA_Italic;
    return true;
  }
  return false;
}

//! Auxiliary function to parse font strict level argument
static int parseFontStrictLevel(const int         theArgNb,
                                const char**      theArgVec,
                                Font_StrictLevel& theLevel)
{
  if (theArgNb >= 1)
  {
    TCollection_AsciiString anArg(theArgVec[0]);
    anArg.LowerCase();
    if (anArg == "any")
    {
      theLevel = Font_StrictLevel_Any;
      return 1;
    }
    else if (anArg == "aliases")
    {
      theLevel = Font_StrictLevel_Aliases;
      return 1;
    }
    else if (anArg == "strict")
    {
      theLevel = Font_StrictLevel_Strict;
      return 1;
    }
  }
  theLevel = Font_StrictLevel_Strict;
  return 0;
}
} // namespace

//==============================================================================
// function : Vtrihedron 2d
// purpose  : Create a plane with a 2D  trihedron from a faceselection
// Draw arg : vtri2d  name
//==============================================================================
static int VTrihedron2D(Draw_Interpretor& /*theDI*/, int theArgsNum, const char** theArgVec)
{
  if (ViewerTest::CurrentView().IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }
  if (theArgsNum != 2)
  {
    Message::SendFail() << "Syntax error: wrong number of arguments.";
    return 1;
  }

  NCollection_List<TopoDS_Shape> aShapes;
  ViewerTest::GetSelectedShapes(aShapes);

  if (aShapes.Extent() != 1)
  {
    Message::SendFail("Error: wrong number of selected shapes.");
    return 1;
  }

  const TopoDS_Shape& aShape = aShapes.First();

  TopoDS_Face     aFace = TopoDS::Face(aShape);
  TopExp_Explorer aFaceExp(aFace, TopAbs_EDGE);
  TopoDS_Edge     anEdge0 = TopoDS::Edge(aFaceExp.Current());

  gp_Pnt A, B, C;
  if (aFaceExp.More())
  {
    aFaceExp.Next();
    TopoDS_Edge       anEdge1 = TopoDS::Edge(aFaceExp.Current());
    BRepAdaptor_Curve aCurve0(anEdge0);
    BRepAdaptor_Curve aCurve1(anEdge1);
    A = aCurve1.Value(0.1);
    B = aCurve1.Value(0.9);
    C = aCurve0.Value(0.5);
  }
  else
  {
    BRepAdaptor_Curve aCurve0(anEdge0);
    A = aCurve0.Value(0.1);
    B = aCurve0.Value(0.9);
    C = aCurve0.Value(0.5);
  }

  GC_MakePlane aMkPlane(A, B, C);

  occ::handle<AIS_PlaneTrihedron> anAISPlaneTri = new AIS_PlaneTrihedron(aMkPlane.Value());
  TCollection_AsciiString         aName(theArgVec[1]);

  VDisplayAISObject(aName, anAISPlaneTri);

  return 0;
}

//=================================================================================================

static int VTrihedron(Draw_Interpretor&, int theArgsNb, const char** theArgVec)
{
  if (ViewerTest::CurrentView().IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }
  if (theArgsNb < 2)
  {
    Message::SendFail("Syntax error: the wrong number of input parameters");
    return 1;
  }

  TCollection_AsciiString aName(theArgVec[1]);
  bool                    toUpdate = true;

  NCollection_DataMap<TCollection_AsciiString, double>                  aRealParams;
  NCollection_DataMap<TCollection_AsciiString, TCollection_AsciiString> aStringParams;

  occ::handle<AIS_Trihedron>         aTrihedron;
  occ::handle<AIS_InteractiveObject> anObject;
  if (GetMapOfAIS().Find2(aName, anObject))
  {
    aTrihedron = occ::down_cast<AIS_Trihedron>(anObject);
    if (aTrihedron.IsNull())
    {
      Message::SendFail("Syntax error: no trihedron with this name");
      return 1;
    }
  }
  else
  {
    occ::handle<Geom_Axis2Placement> aPlacement =
      new Geom_Axis2Placement(gp_Pnt(0.0, 0.0, 0.0), gp::DZ(), gp::DX());
    aTrihedron = new AIS_Trihedron(aPlacement);
  }

  if (!setTrihedronParams(theArgsNb, theArgVec, aTrihedron))
  {
    return 1;
  }

  // Redisplay a dimension after parameter changing.
  if (ViewerTest::GetAISContext()->IsDisplayed(aTrihedron))
  {
    ViewerTest::GetAISContext()->Redisplay(aTrihedron, toUpdate);
  }
  else
  {
    VDisplayAISObject(theArgVec[1], aTrihedron);
  }

  return 0;
}

//==============================================================================
// function : VSize
// author   : ege
// purpose  : Change the size of a named or selected trihedron
//           if no name : it affects the trihedrons which are selected otherwise nothing is donne
//           if no value, the value is set at 100 by default
// Draw arg : vsize [name] [size]
//==============================================================================
static int VSize(Draw_Interpretor& di, int argc, const char** argv)
{
  if (TheAISContext().IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  TCollection_AsciiString aName;
  double                  aSize = 0.0;
  switch (argc)
  {
    case 1: {
      aSize = 100;
      break;
    }
    case 2: {
      aSize = Draw::Atof(argv[1]);
      break;
    }
    case 3: {
      aName = argv[1];
      aSize = Draw::Atof(argv[2]);
      break;
    }
    default: {
      di << "Syntax error";
      return 1;
    }
  }

  NCollection_Sequence<occ::handle<AIS_Trihedron>> aTrihedrons;
  if (!aName.IsEmpty())
  {
    occ::handle<AIS_InteractiveObject> aShape;
    if (GetMapOfAIS().Find2(aName, aShape))
    {
      if (occ::handle<AIS_Trihedron> aTrihedron = occ::down_cast<AIS_Trihedron>(aShape))
      {
        aTrihedrons.Append(aTrihedron);
      }
      else
      {
        di << "Syntax error: " << aName << " is not a trihedron";
        return 1;
      }
    }
  }
  else if (TheAISContext()->NbSelected() > 0)
  {
    for (NCollection_DoubleMap<occ::handle<AIS_InteractiveObject>,
                               TCollection_AsciiString>::Iterator it(GetMapOfAIS());
         it.More();
         it.Next())
    {
      const occ::handle<AIS_InteractiveObject>& aShape = it.Key1();
      if (!aShape.IsNull() && TheAISContext()->IsSelected(aShape))
      {
        if (occ::handle<AIS_Trihedron> aTrihedron = occ::down_cast<AIS_Trihedron>(aShape))
        {
          aTrihedrons.Append(aTrihedron);
        }
      }
    }
  }

  for (NCollection_Sequence<occ::handle<AIS_Trihedron>>::Iterator anObjIter(aTrihedrons);
       anObjIter.More();
       anObjIter.Next())
  {
    const occ::handle<AIS_Trihedron>& aTrihedron = anObjIter.Value();
    Quantity_Color                    aColor     = Quantity_NOC_BLACK;
    const bool                        hasColor   = aTrihedron->HasColor();
    if (hasColor)
    {
      aTrihedron->Color(aColor);
    }

    aTrihedron->SetSize(aSize);
    if (hasColor)
    {
      aTrihedron->SetColor(aColor);
    }
    else
    {
      aTrihedron->UnsetColor();
    }

    TheAISContext()->Redisplay(aTrihedron, false);
  }
  if (!aTrihedrons.IsEmpty())
  {
    TheAISContext()->UpdateCurrentViewer();
  }

  return 0;
}

//==============================================================================

//==============================================================================
// function : VPlaneTrihedron
// purpose  : Create a plane from a trihedron selection. If no arguments are set, the default
// Draw arg : vplanetri  name
//==============================================================================
#include <AIS_Plane.hxx>

static int VPlaneTrihedron(Draw_Interpretor& di, int argc, const char** argv)

{
  // Verification des arguments
  if (argc != 2)
  {
    di << argv[0] << " error\n";
    return 1;
  }

  if (TheAISContext().IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  if (TheAISContext()->NbSelected() != 1)
  {
    Message::SendFail("Error: Wrong number of selected shapes.");
    return 1;
  }

  TheAISContext()->InitSelected();
  occ::handle<AIS_InteractiveObject> aTest  = TheAISContext()->SelectedInteractive();
  occ::handle<AIS_Plane>             aPlane = occ::down_cast<AIS_Plane>(aTest);
  if (aPlane.IsNull())
  {
    Message::SendFail("Error: Selected shape is not a plane.");
    return 1;
  }

  VDisplayAISObject(argv[1], aPlane);

  return 0;
}

//==============================================================================
// Fonction        First click      2de click
//
// vaxis           vertex           vertex
//                 edge             None
// vaxispara       edge             vertex
// vaxisortho      edge             Vertex
// vaxisinter      Face             Face
//==============================================================================

//==============================================================================
// function : VAxisBuilder
// purpose  :
// Draw arg : vaxis AxisName Xa Ya Za Xb Yb Zb
//==============================================================================
#include <TopoDS_Edge.hxx>
#include <Geom_Line.hxx>

static int VAxisBuilder(Draw_Interpretor& di, int argc, const char** argv)
{
  // Declarations
  bool                    HasArg;
  TCollection_AsciiString name;

  // Verification
  if (argc < 2 || argc > 8)
  {
    di << " Syntaxe error\n";
    return 1;
  }
  if (argc == 8)
    HasArg = true;
  else
    HasArg = false;

  name = argv[1];

  NCollection_List<TopoDS_Shape> aShapes;
  ViewerTest::GetSelectedShapes(aShapes);

  // Cas ou il y a des arguments
  // Purpose: Teste le constructeur AIS_Axis::AIS_Axis(x: Line from Geom)
  if (HasArg)
  {
    double coord[6];
    for (int i = 0; i <= 5; i++)
    {
      coord[i] = Draw::Atof(argv[2 + i]);
    }
    gp_Pnt p1(coord[0], coord[1], coord[2]), p2(coord[3], coord[4], coord[5]);

    gp_Vec                 myVect(p1, p2);
    occ::handle<Geom_Line> myLine  = new Geom_Line(p1, myVect);
    occ::handle<AIS_Axis>  TheAxis = new AIS_Axis(myLine);
    GetMapOfAIS().Bind(TheAxis, name);
    TheAISContext()->Display(TheAxis, true);
  }

  // Pas d'arguments
  else
  {
    // fonction vaxis
    // Purpose: Teste le constructeur AIS_Axis::AIS_Axis (x:Axis1Placement from Geom)
    if (!strcasecmp(argv[0], "vaxis"))
    {
      if (aShapes.Extent() != 2 && aShapes.Extent() != 1)
      {
        Message::SendFail("Error: Wrong number of selected shapes.");
        return 1;
      }

      const TopoDS_Shape& aShapeA = aShapes.First();
      if (aShapeA.ShapeType() == TopAbs_VERTEX)
      {
        if (aShapes.Extent() != 2)
        {
          Message::SendFail("Error: Wrong number of selected shapes.");
          return 1;
        }

        const TopoDS_Shape& aShapeB = aShapes.Last();
        if (aShapeB.ShapeType() != TopAbs_VERTEX)
        {
          Message::SendFail("Syntax error: You should select two vertices or one edge.");
          return 1;
        }

        // Construction de l'axe
        gp_Pnt                           A = BRep_Tool::Pnt(TopoDS::Vertex(aShapeA));
        gp_Pnt                           B = BRep_Tool::Pnt(TopoDS::Vertex(aShapeB));
        gp_Vec                           V(A, B);
        gp_Dir                           D(V);
        occ::handle<Geom_Axis1Placement> OrigineAndVect = new Geom_Axis1Placement(A, D);
        occ::handle<AIS_Axis>            TheAxis        = new AIS_Axis(OrigineAndVect);
        GetMapOfAIS().Bind(TheAxis, name);
        TheAISContext()->Display(TheAxis, true);
      }
      else
      {
        TopoDS_Edge   ed = TopoDS::Edge(aShapeA);
        TopoDS_Vertex Va, Vb;
        TopExp::Vertices(ed, Va, Vb);
        gp_Pnt                           A = BRep_Tool::Pnt(Va);
        gp_Pnt                           B = BRep_Tool::Pnt(Vb);
        gp_Vec                           V(A, B);
        gp_Dir                           D(V);
        occ::handle<Geom_Axis1Placement> OrigineAndVect = new Geom_Axis1Placement(A, D);
        occ::handle<AIS_Axis>            TheAxis        = new AIS_Axis(OrigineAndVect);
        GetMapOfAIS().Bind(TheAxis, name);
        TheAISContext()->Display(TheAxis, true);
      }
    }

    // Fonction axispara
    // Purpose: Teste le constructeur AIS_Axis::AIS_Axis(x: Axis2Placement from Geom, y: TypeOfAxis
    // from AIS)
    else if (!strcasecmp(argv[0], "vaxispara"))
    {
      if (aShapes.Extent() != 2)
      {
        Message::SendFail("Error: Wrong number of selected shapes.");
        return 1;
      }

      const TopoDS_Shape& aShapeA = aShapes.First();
      const TopoDS_Shape& aShapeB = aShapes.Last();
      if (!(aShapeA.ShapeType() == TopAbs_EDGE && aShapeB.ShapeType() == TopAbs_VERTEX))
      {
        Message::SendFail("Syntax error: You should select face and then vertex.");
        return 1;
      }

      TopoDS_Edge   ed = TopoDS::Edge(aShapeA);
      gp_Pnt        B  = BRep_Tool::Pnt(TopoDS::Vertex(aShapeB));
      TopoDS_Vertex Va, Vc;
      TopExp::Vertices(ed, Va, Vc);
      gp_Pnt                           A = BRep_Tool::Pnt(Va);
      gp_Pnt                           C = BRep_Tool::Pnt(Vc);
      gp_Vec                           V(A, C);
      gp_Dir                           D(V);
      occ::handle<Geom_Axis1Placement> OrigineAndVect = new Geom_Axis1Placement(B, D);
      occ::handle<AIS_Axis>            TheAxis        = new AIS_Axis(OrigineAndVect);
      GetMapOfAIS().Bind(TheAxis, name);
      TheAISContext()->Display(TheAxis, true);
    }

    // Fonction axisortho
    else
    {
      if (aShapes.Extent() != 2)
      {
        Message::SendFail("Error: Wrong number of selected shapes.");
        return 1;
      }

      const TopoDS_Shape& aShapeA = aShapes.First();
      const TopoDS_Shape& aShapeB = aShapes.Last();
      if (!(aShapeA.ShapeType() == TopAbs_EDGE && aShapeB.ShapeType() == TopAbs_VERTEX))
      {
        Message::SendFail("Syntax error: You should select face and then vertex.");
        return 1;
      }

      // Construction de l'axe
      TopoDS_Edge   ed = TopoDS::Edge(aShapeA);
      gp_Pnt        B  = BRep_Tool::Pnt(TopoDS::Vertex(aShapeB));
      TopoDS_Vertex Va, Vc;
      TopExp::Vertices(ed, Va, Vc);
      gp_Pnt A = BRep_Tool::Pnt(Va);
      gp_Pnt C = BRep_Tool::Pnt(Vc);
      gp_Pnt E(A.Y() + A.Z() - C.Y() - C.Z(), C.X() - A.X(), C.X() - A.X());
      gp_Vec V(A, E);
      gp_Dir D(V);
      occ::handle<Geom_Axis1Placement> OrigineAndVect = new Geom_Axis1Placement(B, D);
      occ::handle<AIS_Axis>            TheAxis        = new AIS_Axis(OrigineAndVect);
      GetMapOfAIS().Bind(TheAxis, name);
      TheAISContext()->Display(TheAxis, true);
    }
  }
  return 0;
}

//==============================================================================
// Fonction        First click      Result
//
// vpoint          vertex           AIS_Point=Vertex
//                 edge             AIS_Point=Middle of the edge
//==============================================================================

//=================================================================================================

#include <AIS_Point.hxx>
#include <Geom_CartesianPoint.hxx>

static int VPointBuilder(Draw_Interpretor&, int theArgNb, const char** theArgVec)
{
  TCollection_AsciiString aName;
  gp_Pnt                  aPnt(RealLast(), 0.0, 0.0);
  bool                    is2d = false, isNoSel = false;
  for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anArg == "-2d")
    {
      is2d = true;
    }
    else if (anArg == "-nosel" || anArg == "-noselection")
    {
      isNoSel = true;
    }
    else if (aName.IsEmpty())
    {
      aName = theArgVec[anArgIter];
    }
    else if (aPnt.X() == RealLast() && anArgIter + 1 < theArgNb
             && Draw::ParseReal(theArgVec[anArgIter + 0], aPnt.ChangeCoord().ChangeCoord(1))
             && Draw::ParseReal(theArgVec[anArgIter + 1], aPnt.ChangeCoord().ChangeCoord(2)))
    {
      if (anArgIter + 2 < theArgNb && TCollection_AsciiString(theArgVec[anArgIter + 2]) != "-2d"
          && Draw::ParseReal(theArgVec[anArgIter + 2], aPnt.ChangeCoord().ChangeCoord(3)))
      {
        anArgIter += 2;
      }
      else
      {
        anArgIter += 1;
      }
    }
    else
    {
      Message::SendFail() << "Syntax error at argument '" << anArg << "'\n";
      return 1;
    }
  }

  if (aPnt.X() == RealLast())
  {
    NCollection_List<TopoDS_Shape> aShapes;
    ViewerTest::GetSelectedShapes(aShapes);
    TopoDS_Shape aShapeA;
    if (aShapes.Extent() == 1)
    {
      aShapeA = aShapes.First();
    }
    switch (!aShapeA.IsNull() ? aShapeA.ShapeType() : TopAbs_SHAPE)
    {
      case TopAbs_VERTEX: {
        aPnt = BRep_Tool::Pnt(TopoDS::Vertex(aShapeA));
        break;
      }
      case TopAbs_EDGE: // edge middle point
      {
        const TopoDS_Edge& anEdge = TopoDS::Edge(aShapeA);
        TopoDS_Vertex      aVertPair[2];
        TopExp::Vertices(anEdge, aVertPair[0], aVertPair[1]);
        const gp_Pnt A = BRep_Tool::Pnt(aVertPair[0]);
        const gp_Pnt B = BRep_Tool::Pnt(aVertPair[1]);
        aPnt           = (A.XYZ() + B.XYZ()) / 2;
        break;
      }
      default: {
        Message::SendFail() << "Error: Wrong number of selected shapes.\n"
                            << "\tYou should select one edge or vertex.";
        return 1;
      }
    }
  }

  if (is2d)
  {
    aPnt.SetY(-aPnt.Y());
  }
  occ::handle<Geom_CartesianPoint> aGeomPoint = new Geom_CartesianPoint(aPnt);
  occ::handle<AIS_Point>           aPointPrs  = new AIS_Point(aGeomPoint);
  if (is2d)
  {
    aPointPrs->SetTransformPersistence(
      new Graphic3d_TransformPers(Graphic3d_TMF_2d, Aspect_TOTP_LEFT_UPPER));
    aPointPrs->SetZLayer(Graphic3d_ZLayerId_TopOSD);
  }
  ViewerTest::Display(aName, aPointPrs);
  if (isNoSel)
  {
    ViewerTest::GetAISContext()->Deactivate(aPointPrs);
  }
  return 0;
}

//==============================================================================
// Function        1st click   2de click  3de click
// vplane          Vertex      Vertex     Vertex
//                 Vertex      Edge
//                 Edge        Vertex
//                 Face
// vplanepara      Face        Vertex
//                 Vertex      Face
// vplaneortho     Face        Edge
//                 Edge        Face
//==============================================================================

//==============================================================================
// function : VPlaneBuilder
// purpose  : Build an AIS_Plane from selected entities or Named AIS components
// Draw arg : vplane PlaneName [AxisName]  [PointName] [TypeOfSensitivity]
//                            [PointName] [PointName] [PointName] [TypeOfSensitivity]
//                            [PlaneName] [PointName] [TypeOfSensitivity]
//==============================================================================

static int VPlaneBuilder(Draw_Interpretor& /*di*/, int argc, const char** argv)
{
  // Declarations
  bool                    hasArg;
  TCollection_AsciiString aName;

  // Verification
  if (argc < 2 || argc > 6)
  {
    Message::SendFail("Syntax error: wrong number of arguments");
    return 1;
  }
  if (argc == 6 || argc == 5 || argc == 4)
    hasArg = true;
  else
    hasArg = false;

  aName = argv[1];

  // There are some arguments
  if (hasArg)
  {
    occ::handle<AIS_InteractiveObject> aShapeA;
    if (!GetMapOfAIS().Find2(argv[2], aShapeA))
    {
      Message::SendFail("Syntax error: 1st name is not displayed");
      return 1;
    }

    // The first argument is an AIS_Point
    if (!aShapeA.IsNull() && aShapeA->Type() == AIS_KindOfInteractive_Datum
        && aShapeA->Signature() == 1)
    {
      // The second argument must also be an AIS_Point
      occ::handle<AIS_InteractiveObject> aShapeB;
      if (argc < 5 || !GetMapOfAIS().Find2(argv[3], aShapeB))
      {
        Message::SendFail("Syntax error: 2nd name is not displayed");
        return 1;
      }

      // If B is not an AIS_Point
      if (aShapeB.IsNull()
          || !(aShapeB->Type() == AIS_KindOfInteractive_Datum && aShapeB->Signature() == 1))
      {
        Message::SendFail("Syntax error: 2nd object is expected to be an AIS_Point");
        return 1;
      }

      // The third object is an AIS_Point
      occ::handle<AIS_InteractiveObject> aShapeC;
      if (!GetMapOfAIS().Find2(argv[4], aShapeC))
      {
        Message::SendFail("Syntax error: 3d name is not displayed");
        return 1;
      }

      // If C is not an AIS_Point
      if (aShapeC.IsNull()
          || !(aShapeC->Type() == AIS_KindOfInteractive_Datum && aShapeC->Signature() == 1))
      {
        Message::SendFail("Syntax error: 3d object is expected to be an AIS_Point");
        return 1;
      }

      // Treatment of objects A, B, C
      // Downcast an AIS_IO to AIS_Point
      occ::handle<AIS_Point> anAISPointA = occ::down_cast<AIS_Point>(aShapeA);
      occ::handle<AIS_Point> anAISPointB = occ::down_cast<AIS_Point>(aShapeB);
      occ::handle<AIS_Point> anAISPointC = occ::down_cast<AIS_Point>(aShapeC);

      occ::handle<Geom_CartesianPoint> aCartPointA =
        occ::down_cast<Geom_CartesianPoint>(anAISPointA->Component());
      occ::handle<Geom_CartesianPoint> aCartPointB =
        occ::down_cast<Geom_CartesianPoint>(anAISPointB->Component());
      occ::handle<Geom_CartesianPoint> aCartPointC =
        occ::down_cast<Geom_CartesianPoint>(anAISPointC->Component());

      // Verification that the three points are different
      if (std::abs(aCartPointB->X() - aCartPointA->X()) <= Precision::Confusion()
          && std::abs(aCartPointB->Y() - aCartPointA->Y()) <= Precision::Confusion()
          && std::abs(aCartPointB->Z() - aCartPointA->Z()) <= Precision::Confusion())
      {
        // B=A
        Message::SendFail("Error: same points");
        return 1;
      }
      if (std::abs(aCartPointC->X() - aCartPointA->X()) <= Precision::Confusion()
          && std::abs(aCartPointC->Y() - aCartPointA->Y()) <= Precision::Confusion()
          && std::abs(aCartPointC->Z() - aCartPointA->Z()) <= Precision::Confusion())
      {
        // C=A
        Message::SendFail("Error: same points");
        return 1;
      }
      if (std::abs(aCartPointC->X() - aCartPointB->X()) <= Precision::Confusion()
          && std::abs(aCartPointC->Y() - aCartPointB->Y()) <= Precision::Confusion()
          && std::abs(aCartPointC->Z() - aCartPointB->Z()) <= Precision::Confusion())
      {
        // C=B
        Message::SendFail("Error: same points");
        return 1;
      }

      gp_Pnt A = aCartPointA->Pnt();
      gp_Pnt B = aCartPointB->Pnt();
      gp_Pnt C = aCartPointC->Pnt();

      // Construction of AIS_Plane
      GC_MakePlane                   MkPlane(A, B, C);
      const occ::handle<Geom_Plane>& aGeomPlane = MkPlane.Value();
      occ::handle<AIS_Plane>         anAISPlane = new AIS_Plane(aGeomPlane);
      GetMapOfAIS().Bind(anAISPlane, aName);
      if (argc == 6)
      {
        int aType = Draw::Atoi(argv[5]);
        if (aType != 0 && aType != 1)
        {
          Message::SendFail("Syntax error: wrong type of sensitivity.\n"
                            "Should be one of the following values:\n"
                            "0 - Interior\n"
                            "1 - Boundary");
          return 1;
        }
        else
        {
          anAISPlane->SetTypeOfSensitivity(Select3D_TypeOfSensitivity(aType));
        }
      }
      TheAISContext()->Display(anAISPlane, true);
    }
    // The first argument is an AIS_Axis
    // Creation of a plane orthogonal to the axis through a point
    else if (aShapeA->Type() == AIS_KindOfInteractive_Datum && aShapeA->Signature() == 2)
    {
      // The second argument should be an AIS_Point
      occ::handle<AIS_InteractiveObject> aShapeB;
      if (argc != 4 || !GetMapOfAIS().Find2(argv[3], aShapeB))
      {
        Message::SendFail("Syntax error: 2d name is not displayed");
        return 1;
      }
      // If B is not an AIS_Point
      if (aShapeB.IsNull()
          || !(aShapeB->Type() == AIS_KindOfInteractive_Datum && aShapeB->Signature() == 1))
      {
        Message::SendFail("Syntax error: 2d object is expected to be an AIS_Point");
        return 1;
      }

      // Treatment of objects A and B
      occ::handle<AIS_Axis>  anAISAxisA  = occ::down_cast<AIS_Axis>(aShapeA);
      occ::handle<AIS_Point> anAISPointB = occ::down_cast<AIS_Point>(aShapeB);

      occ::handle<Geom_Line>  aGeomLineA  = anAISAxisA->Component();
      occ::handle<Geom_Point> aGeomPointB = anAISPointB->Component();

      gp_Ax1                           anAxis = aGeomLineA->Position();
      occ::handle<Geom_CartesianPoint> aCartPointB =
        occ::down_cast<Geom_CartesianPoint>(aGeomPointB);

      gp_Dir D = anAxis.Direction();
      gp_Pnt B = aCartPointB->Pnt();

      // Construction of AIS_Plane
      occ::handle<Geom_Plane> aGeomPlane = new Geom_Plane(B, D);
      occ::handle<AIS_Plane>  anAISPlane = new AIS_Plane(aGeomPlane, B);
      GetMapOfAIS().Bind(anAISPlane, aName);
      if (argc == 5)
      {
        int aType = Draw::Atoi(argv[4]);
        if (aType != 0 && aType != 1)
        {
          Message::SendFail("Syntax error: wrong type of sensitivity!\n"
                            "Should be one of the following values:\n"
                            "0 - Interior\n"
                            "1 - Boundary");
          return 1;
        }
        else
        {
          anAISPlane->SetTypeOfSensitivity(Select3D_TypeOfSensitivity(aType));
        }
      }
      TheAISContext()->Display(anAISPlane, true);
    }
    // The first argument is an AIS_Plane
    // Creation of a plane parallel to the plane passing through the point
    else if (aShapeA->Type() == AIS_KindOfInteractive_Datum && aShapeA->Signature() == 7)
    {
      // The second argument should be an AIS_Point
      occ::handle<AIS_InteractiveObject> aShapeB;
      if (argc != 4 || !GetMapOfAIS().Find2(argv[3], aShapeB))
      {
        Message::SendFail("Syntax error: 2d name is not displayed");
        return 1;
      }
      // B should be an AIS_Point
      if (aShapeB.IsNull()
          || !(aShapeB->Type() == AIS_KindOfInteractive_Datum && aShapeB->Signature() == 1))
      {
        Message::SendFail("Syntax error: 2d object is expected to be an AIS_Point");
        return 1;
      }

      // Treatment of objects A and B
      occ::handle<AIS_Plane> anAISPlaneA = occ::down_cast<AIS_Plane>(aShapeA);
      occ::handle<AIS_Point> anAISPointB = occ::down_cast<AIS_Point>(aShapeB);

      occ::handle<Geom_Plane> aNewGeomPlane = anAISPlaneA->Component();
      occ::handle<Geom_Point> aGeomPointB   = anAISPointB->Component();

      occ::handle<Geom_CartesianPoint> aCartPointB =
        occ::down_cast<Geom_CartesianPoint>(aGeomPointB);
      gp_Pnt B = aCartPointB->Pnt();

      // Construction of an AIS_Plane
      occ::handle<AIS_Plane> anAISPlane = new AIS_Plane(aNewGeomPlane, B);
      GetMapOfAIS().Bind(anAISPlane, aName);
      if (argc == 5)
      {
        int aType = Draw::Atoi(argv[4]);
        if (aType != 0 && aType != 1)
        {
          Message::SendFail("Syntax error: wrong type of sensitivity!\n"
                            "Should be one of the following values:\n"
                            "0 - Interior\n"
                            "1 - Boundary");
          return 1;
        }
        else
        {
          anAISPlane->SetTypeOfSensitivity(Select3D_TypeOfSensitivity(aType));
        }
      }
      TheAISContext()->Display(anAISPlane, true);
    }
    // Error
    else
    {
      Message::SendFail("Syntax error: 1st object is not an AIS");
      return 1;
    }
  }
  // There are no arguments
  else
  {
    NCollection_List<TopoDS_Shape> aShapes;
    ViewerTest::GetSelectedShapes(aShapes);

    // Function vplane
    // Test the constructor AIS_Plane::AIS_Plane(Geom_Plane, bool )
    if (!strcasecmp(argv[0], "vplane"))
    {
      if (aShapes.Extent() < 1 || aShapes.Extent() > 3)
      {
        Message::SendFail()
          << "Error: Wrong number of selected shapes.\n"
          << "\tYou should one of variant: face, edge and vertex or three vertices.";
        return 1;
      }

      const TopoDS_Shape& aShapeA = aShapes.First();
      if (aShapeA.ShapeType() == TopAbs_VERTEX)
      {
        if (aShapes.Extent() == 2)
        {
          const TopoDS_Shape& aShapeB = aShapes.Last();
          if (aShapeB.ShapeType() != TopAbs_EDGE)
          {
            Message::SendFail("Syntax error: Together with vertex should be edge.");
            return 1;
          }

          // Verify that the vertex is not on the edge ShapeB
          TopoDS_Edge   anEdgeB = TopoDS::Edge(aShapeB);
          TopoDS_Vertex aVertA  = TopoDS::Vertex(aShapeA);

          BRepExtrema_ExtPC OrthoProj(aVertA, anEdgeB);
          if (OrthoProj.SquareDistance(1) < Precision::Approximation())
          {
            // The vertex is on the edge
            Message::SendFail("Error: point is on the edge");
            return 1;
          }
          else
          {
            gp_Pnt        A = BRep_Tool::Pnt(aVertA);
            TopoDS_Vertex aVBa, aVBb;
            TopExp::Vertices(anEdgeB, aVBa, aVBb);
            gp_Pnt                         aBa = BRep_Tool::Pnt(aVBa);
            gp_Pnt                         aBb = BRep_Tool::Pnt(aVBb);
            GC_MakePlane                   MkPlane(A, aBa, aBb);
            const occ::handle<Geom_Plane>& aGeomPlane = MkPlane.Value();
            occ::handle<AIS_Plane>         anAISPlane = new AIS_Plane(aGeomPlane);
            GetMapOfAIS().Bind(anAISPlane, aName);
            TheAISContext()->Display(anAISPlane, true);
          }
        }
        else if (aShapes.Extent() == 3)
        {
          NCollection_List<TopoDS_Shape>::Iterator anIter(aShapes);

          anIter.Next();
          const TopoDS_Shape& aShapeB = anIter.Value();

          anIter.Next();
          const TopoDS_Shape& aShapeC = anIter.Value();

          if (!(aShapeB.ShapeType() == TopAbs_VERTEX && aShapeC.ShapeType() == TopAbs_VERTEX))
          {
            Message::SendFail(
              "Syntax error: You should one of variant: face, edge and vertex or three vertices.");
            return 1;
          }

          gp_Pnt                         A = BRep_Tool::Pnt(TopoDS::Vertex(aShapeA));
          gp_Pnt                         B = BRep_Tool::Pnt(TopoDS::Vertex(aShapeB));
          gp_Pnt                         C = BRep_Tool::Pnt(TopoDS::Vertex(aShapeC));
          GC_MakePlane                   MkPlane(A, B, C);
          const occ::handle<Geom_Plane>& aGeomPlane = MkPlane.Value();
          occ::handle<AIS_Plane>         anAISPlane = new AIS_Plane(aGeomPlane);
          GetMapOfAIS().Bind(anAISPlane, aName);
          TheAISContext()->Display(anAISPlane, true);
        }
        else
        {
          Message::SendFail(
            "Syntax error: You should one of variant: face, edge and vertex or three vertices.");
          return 1;
        }
      }
      else if (aShapeA.ShapeType() == TopAbs_EDGE)
      {
        if (aShapes.Extent() != 2)
        {
          Message::SendFail("Error: wrong number of selected shapes.");
          return 1;
        }

        const TopoDS_Shape& aShapeB = aShapes.Last();
        if (aShapeB.ShapeType() != TopAbs_VERTEX)
        {
          Message::SendFail("Syntax error: Together with edge should be vertex.");
          return 1;
        }

        // Check that the vertex aShapeB is not on the edge
        TopoDS_Edge   anEdgeA = TopoDS::Edge(aShapeA);
        TopoDS_Vertex aVertB  = TopoDS::Vertex(aShapeB);

        BRepExtrema_ExtPC OrthoProj(aVertB, anEdgeA);
        if (OrthoProj.SquareDistance(1) < Precision::Approximation())
        {
          // The vertex is on the edge
          Message::SendFail("Error point is on the edge");
          return 1;
        }

        gp_Pnt        B = BRep_Tool::Pnt(aVertB);
        TopoDS_Vertex aVAa, aVAb;
        TopExp::Vertices(anEdgeA, aVAa, aVAb);
        gp_Pnt                         Aa = BRep_Tool::Pnt(aVAa);
        gp_Pnt                         Ab = BRep_Tool::Pnt(aVAb);
        GC_MakePlane                   MkPlane(B, Aa, Ab);
        const occ::handle<Geom_Plane>& aGeomPlane = MkPlane.Value();
        occ::handle<AIS_Plane>         anAISPlane = new AIS_Plane(aGeomPlane);
        GetMapOfAIS().Bind(anAISPlane, aName);
        TheAISContext()->Display(anAISPlane, true);
      }
      else if (aShapeA.ShapeType() == TopAbs_FACE)
      {
        TopoDS_Face         aFace = TopoDS::Face(aShapeA);
        BRepAdaptor_Surface aSurface(aFace, false);
        if (aSurface.GetType() == GeomAbs_Plane)
        {
          gp_Pln                  aPlane     = aSurface.Plane();
          occ::handle<Geom_Plane> aGeomPlane = new Geom_Plane(aPlane);
          occ::handle<AIS_Plane>  anAISPlane = new AIS_Plane(aGeomPlane);
          GetMapOfAIS().Bind(anAISPlane, aName);
          TheAISContext()->Display(anAISPlane, true);
        }
        else
        {
          Message::SendFail("Error: surface is not Plane");
          return 1;
        }
      }
      else
      {
        Message::SendFail(
          "Syntax error: You should one of variant: face, edge and vertex or three vertices");
        return 1;
      }
    }

    // Function vPlanePara
    // ===================
    // test the constructor AIS_Plane::AIS_Plane(Geom_Plane,gp_Pnt)
    else if (!strcasecmp(argv[0], "vplanepara"))
    {
      if (aShapes.Extent() != 2)
      {
        Message::SendFail("Error: Wrong number of selected shapes.");
        return 1;
      }

      const TopoDS_Shape* aShapeA = &aShapes.First();
      const TopoDS_Shape* aShapeB = &aShapes.Last();
      if (aShapeA->ShapeType() != TopAbs_VERTEX)
      {
        std::swap(aShapeA, aShapeB);
      }

      if (!(aShapeA->ShapeType() == TopAbs_VERTEX && aShapeB->ShapeType() == TopAbs_FACE))
      {
        Message::SendFail("Syntax error: you should select face and vertex.");
        return 1;
      }

      gp_Pnt A = BRep_Tool::Pnt(TopoDS::Vertex(*aShapeA));

      TopoDS_Face         aFace = TopoDS::Face(*aShapeB);
      BRepAdaptor_Surface aSurface(aFace, false);
      if (aSurface.GetType() == GeomAbs_Plane)
      {
        gp_Pln aPlane = aSurface.Plane();
        // Construct a plane parallel to aGeomPlane through A
        aPlane.SetLocation(A);
        occ::handle<Geom_Plane> aGeomPlane = new Geom_Plane(aPlane);
        occ::handle<AIS_Plane>  aAISPlane  = new AIS_Plane(aGeomPlane, A);
        GetMapOfAIS().Bind(aAISPlane, aName);
        TheAISContext()->Display(aAISPlane, true);
      }
      else
      {
        Message::SendFail("Error: Built surface is not a plane.");
        return 1;
      }
    }

    // Function vplaneortho
    // ====================
    // test the constructor AIS_Plane::AIS_Plane(Geom_Plane,gp_Pnt,gp_Pnt,gp_Pnt)
    else
    {
      if (aShapes.Extent() != 2)
      {
        Message::SendFail("Error: wrong number of selected shapes.");
        return 1;
      }

      const TopoDS_Shape* aShapeA = &aShapes.First();
      const TopoDS_Shape* aShapeB = &aShapes.Last();

      if (aShapeA->ShapeType() != TopAbs_EDGE)
      {
        std::swap(aShapeA, aShapeB);
      }

      if (!(aShapeA->ShapeType() == TopAbs_EDGE && aShapeB->ShapeType() == TopAbs_FACE))
      {
        Message::SendFail("Error: you should select edge and face.");
        return 1;
      }

      // Construction of plane
      TopoDS_Edge   anEdgeA = TopoDS::Edge(*aShapeA);
      TopoDS_Vertex aVAa, aVAb;
      TopExp::Vertices(anEdgeA, aVAa, aVAb);
      gp_Pnt Aa = BRep_Tool::Pnt(aVAa);
      gp_Pnt Ab = BRep_Tool::Pnt(aVAb);
      gp_Vec ab(Aa, Ab);

      gp_Dir Dab(ab);
      // Creation of rotation axis
      gp_Ax1 aRotAxis(Aa, Dab);

      TopoDS_Face aFace = TopoDS::Face(*aShapeB);
      // The edge must be parallel to the face
      BRepExtrema_ExtPF aHeightA(aVAa, aFace);
      BRepExtrema_ExtPF aHeightB(aVAb, aFace);
      // Compare to heights
      if (fabs(sqrt(aHeightA.SquareDistance(1)) - sqrt(aHeightB.SquareDistance(1)))
          > Precision::Confusion())
      {
        // the edge is not parallel to the face
        Message::SendFail("Error: the edge is not parallel to the face");
        return 1;
      }
      // the edge is OK
      BRepAdaptor_Surface aSurface(aFace, false);
      if (aSurface.GetType() == GeomAbs_Plane)
      {
        gp_Pln aPlane = aSurface.Plane();
        // It rotates a half turn round the axis of rotation
        aPlane.Rotate(aRotAxis, M_PI / 2);

        occ::handle<Geom_Plane> aGeomPlane = new Geom_Plane(aPlane);
        // constructed aGeomPlane parallel to a plane containing the edge (center mid-edge)
        gp_Pnt aMiddle((Aa.X() + Ab.X()) / 2, (Aa.Y() + Ab.Y()) / 2, (Aa.Z() + Ab.Z()) / 2);
        occ::handle<AIS_Plane> anAISPlane = new AIS_Plane(aGeomPlane, aMiddle);
        GetMapOfAIS().Bind(anAISPlane, aName);
        TheAISContext()->Display(anAISPlane, true);
      }
      else
      {
        Message::SendFail("Error: surface is not Plane");
        return 1;
      }
    }
  }
  return 0;
}

//=================================================================================================

static int VChangePlane(Draw_Interpretor& /*theDi*/, int theArgsNb, const char** theArgVec)
{
  occ::handle<AIS_InteractiveContext> aContextAIS = ViewerTest::GetAISContext();
  if (aContextAIS.IsNull())
  {
    Message::SendFail("Error: no active viewer.");
    return 1;
  }

  if (theArgsNb < 3 || theArgsNb > 11)
  {
    Message::SendFail("Syntax error: wrong number of arguments.");
    return 1;
  }

  TCollection_AsciiString aName(theArgVec[1]);

  occ::handle<AIS_Plane> aPlane =
    GetMapOfAIS().IsBound2(aName) ? occ::down_cast<AIS_Plane>(GetMapOfAIS().Find2(aName)) : nullptr;

  if (aPlane.IsNull())
  {
    Message::SendFail() << "Syntax error: there is no interactive plane with the given name '"
                        << aName << "'.";
    return 1;
  }

  double aCenterX = aPlane->Center().X();
  double aCenterY = aPlane->Center().Y();
  double aCenterZ = aPlane->Center().Z();

  double aDirX = aPlane->Component()->Axis().Direction().X();
  double aDirY = aPlane->Component()->Axis().Direction().Y();
  double aDirZ = aPlane->Component()->Axis().Direction().Z();

  double aSizeX      = 0.0;
  double aSizeY      = 0.0;
  bool   aHasMinSize = aPlane->HasMinimumSize();
  double aMinSizeY   = 0.0;
  aPlane->Size(aSizeX, aSizeY);
  bool isUpdate = true;

  TCollection_AsciiString aPName, aPValue;
  for (int anArgIt = 1; anArgIt < theArgsNb; ++anArgIt)
  {
    const TCollection_AsciiString anArg     = theArgVec[anArgIt];
    TCollection_AsciiString       anArgCase = anArg;
    anArgCase.UpperCase();
    if (ViewerTest::SplitParameter(anArg, aPName, aPValue))
    {
      aPName.UpperCase();
      if (aPName.IsEqual("X"))
      {
        aCenterX = aPValue.RealValue();
      }
      else if (aPName.IsEqual("Y"))
      {
        aCenterY = aPValue.RealValue();
      }
      else if (aPName.IsEqual("Z"))
      {
        aCenterZ = aPValue.RealValue();
      }
      else if (aPName.IsEqual("DX"))
      {
        aDirX = aPValue.RealValue();
      }
      else if (aPName.IsEqual("DY"))
      {
        aDirY = aPValue.RealValue();
      }
      else if (aPName.IsEqual("DZ"))
      {
        aDirZ = aPValue.RealValue();
      }
      else if (aPName.IsEqual("SX"))
      {
        aSizeX = aPValue.RealValue();
      }
      else if (aPName.IsEqual("SY"))
      {
        aSizeY = aPValue.RealValue();
      }
      else if (aPName.IsEqual("MINSIZE"))
      {
        aHasMinSize = true;
        aMinSizeY   = aPValue.RealValue();
      }
    }
    else if (anArg.IsEqual("NOUPDATE"))
    {
      isUpdate = false;
    }
  }

  gp_Dir aDirection(aDirX, aDirY, aDirZ);
  gp_Pnt aCenterPnt(aCenterX, aCenterY, aCenterZ);
  aPlane->SetCenter(aCenterPnt);
  aPlane->SetComponent(new Geom_Plane(aCenterPnt, aDirection));
  aPlane->SetSize(aSizeX, aSizeY);

  if (aHasMinSize)
  {
    aPlane->SetMinimumSize(aMinSizeY);
  }
  else if (aPlane->HasMinimumSize())
  {
    aPlane->UnsetMinimumSize();
  }

  aContextAIS->Update(aPlane, isUpdate);

  return 0;
}

//==============================================================================
// Fonction  vline
// ---------------  Uniquement par parametre. Pas de selection dans le viewer.
//==============================================================================

//==============================================================================
// function : VLineBuilder
// purpose  : Build an AIS_Line
// Draw arg : vline LineName  [AIS_PointName] [AIS_PointName]
//                           [Xa] [Ya] [Za]   [Xb] [Yb] [Zb]
//==============================================================================
#include <AIS_Line.hxx>

static int VLineBuilder(Draw_Interpretor& di, int argc, const char** argv)
{
  if (argc == 4) // parameters: AIS_Point AIS_Point
  {
    occ::handle<AIS_InteractiveObject> aShapeA, aShapeB;
    GetMapOfAIS().Find2(argv[2], aShapeA);
    GetMapOfAIS().Find2(argv[3], aShapeB);
    occ::handle<AIS_Point> anAISPointA = occ::down_cast<AIS_Point>(aShapeA);
    occ::handle<AIS_Point> anAISPointB = occ::down_cast<AIS_Point>(aShapeB);
    if (anAISPointA.IsNull() || anAISPointB.IsNull())
    {
      di << "vline error: wrong type of arguments\n";
      return 1;
    }

    occ::handle<Geom_Point>          aGeomPointBA = anAISPointA->Component();
    occ::handle<Geom_CartesianPoint> aCartPointA =
      occ::down_cast<Geom_CartesianPoint>(aGeomPointBA);

    occ::handle<Geom_Point>          aGeomPointB = anAISPointB->Component();
    occ::handle<Geom_CartesianPoint> aCartPointB = occ::down_cast<Geom_CartesianPoint>(aGeomPointB);

    if (aCartPointB->X() == aCartPointA->X() && aCartPointB->Y() == aCartPointA->Y()
        && aCartPointB->Z() == aCartPointA->Z())
    {
      // B=A
      di << "vline error: same points\n";
      return 1;
    }

    occ::handle<AIS_Line> anAISLine = new AIS_Line(aCartPointA, aCartPointB);
    GetMapOfAIS().Bind(anAISLine, argv[1]);
    TheAISContext()->Display(anAISLine, true);
  }
  else if (argc == 8) // parametres 6 reals
  {
    double aCoord[6] = {};
    for (int i = 0; i <= 2; ++i)
    {
      aCoord[i]     = Draw::Atof(argv[2 + i]);
      aCoord[i + 3] = Draw::Atof(argv[5 + i]);
    }

    occ::handle<Geom_CartesianPoint> aCartPointA =
      new Geom_CartesianPoint(aCoord[0], aCoord[1], aCoord[2]);
    occ::handle<Geom_CartesianPoint> aCartPointB =
      new Geom_CartesianPoint(aCoord[3], aCoord[4], aCoord[5]);

    occ::handle<AIS_Line> anAISLine = new AIS_Line(aCartPointA, aCartPointB);
    GetMapOfAIS().Bind(anAISLine, argv[1]);
    TheAISContext()->Display(anAISLine, true);
  }
  else if (argc == 2) // selection in 3D viewer
  {
    NCollection_List<TopoDS_Shape> aShapes;
    ViewerTest::GetSelectedShapes(aShapes);
    if (aShapes.Extent() != 2)
    {
      Message::SendFail("Error: wrong number of selected shapes.");
      return 1;
    }

    const TopoDS_Shape& aShapeA = aShapes.First();
    const TopoDS_Shape& aShapeB = aShapes.Last();
    if (aShapeA.ShapeType() != TopAbs_VERTEX || aShapeB.ShapeType() != TopAbs_VERTEX)
    {
      Message::SendFail("Error: you should select two different vertex.");
      return 1;
    }

    // Construction de la line
    gp_Pnt A = BRep_Tool::Pnt(TopoDS::Vertex(aShapeA));
    gp_Pnt B = BRep_Tool::Pnt(TopoDS::Vertex(aShapeB));

    occ::handle<Geom_CartesianPoint> aCartPointA = new Geom_CartesianPoint(A);
    occ::handle<Geom_CartesianPoint> aCartPointB = new Geom_CartesianPoint(B);

    occ::handle<AIS_Line> anAISLine = new AIS_Line(aCartPointA, aCartPointB);
    GetMapOfAIS().Bind(anAISLine, argv[1]);
    TheAISContext()->Display(anAISLine, true);
  }
  else
  {
    di << "Syntax error: wrong number of arguments";
    return 1;
  }

  return 0;
}

//==============================================================================
// class   : FilledCircle
// purpose : creates filled circle based on AIS_InteractiveObject
//           and Geom_Circle.
//           This class is used to check method Matches() of class
//           Select3D_SensitiveCircle with member myFillStatus = true,
//           because none of AIS classes provides creation of
//           Select3D_SensitiveCircle with member myFillStatus = true
//           (look method ComputeSelection() )
//==============================================================================

occ::handle<Geom_Circle> CreateCircle(gp_Pnt theCenter, double theRadius)
{
  gp_Ax2                   anAxes(theCenter, gp_Dir(gp_Vec(0., 0., 1.)));
  gp_Circ                  aCirc(anAxes, theRadius);
  occ::handle<Geom_Circle> aCircle = new Geom_Circle(aCirc);
  return aCircle;
}

class FilledCircle : public AIS_InteractiveObject
{
public:
  // CASCADE RTTI
  DEFINE_STANDARD_RTTI_INLINE(FilledCircle, AIS_InteractiveObject);

  FilledCircle(const occ::handle<Geom_Circle>& theCircle,
               const double                    theUStart,
               const double                    theUEnd);

  FilledCircle(const gp_Pnt& theCenter,
               const double  theRadius,
               const double  theUStart,
               const double  theUEnd);

private:
  TopoDS_Face ComputeFace();

  // Virtual methods implementation
  void Compute(const occ::handle<PrsMgr_PresentationManager>& thePrsMgr,
               const occ::handle<Prs3d_Presentation>&         thePrs,
               const int                                      theMode) override;

  void ComputeSelection(const occ::handle<SelectMgr_Selection>& theSel, const int theMode) override;

protected:
  occ::handle<Geom_Circle> myCircle;
  double                   myUStart;
  double                   myUEnd;
  bool                     myFilledStatus;
};

FilledCircle::FilledCircle(const occ::handle<Geom_Circle>& theCircle,
                           const double                    theUStart,
                           const double                    theUEnd)
    : myCircle(theCircle),
      myUStart(theUStart),
      myUEnd(theUEnd),
      myFilledStatus(true)
{
}

FilledCircle::FilledCircle(const gp_Pnt& theCenter,
                           const double  theRadius,
                           const double  theUStart,
                           const double  theUEnd)
    : FilledCircle(CreateCircle(theCenter, theRadius), theUStart, theUEnd)
{
}

TopoDS_Face FilledCircle::ComputeFace()
{
  // Create edge from myCircle
  BRepBuilderAPI_MakeEdge anEdgeMaker(myCircle->Circ(), myUStart, myUEnd);
  TopoDS_Edge             anEdge = anEdgeMaker.Edge();

  // Create wire from anEdge
  BRepBuilderAPI_MakeWire aWireMaker;
  if (std::abs(std::abs(myUEnd - myUStart) - 2.0 * M_PI) > gp::Resolution())
  {
    TopoDS_Edge anEndCenterEdge =
      BRepBuilderAPI_MakeEdge(myCircle->Value(myUEnd), myCircle->Location()).Edge();
    TopoDS_Edge aStartCenterEdge =
      BRepBuilderAPI_MakeEdge(myCircle->Location(), myCircle->Value(myUStart)).Edge();
    aWireMaker = BRepBuilderAPI_MakeWire(anEdge, anEndCenterEdge, aStartCenterEdge);
  }
  else
  {
    aWireMaker = BRepBuilderAPI_MakeWire(anEdge);
  }
  TopoDS_Wire aWire = aWireMaker.Wire();

  // Create face from aWire
  BRepBuilderAPI_MakeFace aFaceMaker(aWire);
  TopoDS_Face             aFace = aFaceMaker.Face();

  return aFace;
}

void FilledCircle::Compute(const occ::handle<PrsMgr_PresentationManager>&,
                           const occ::handle<Prs3d_Presentation>& thePrs,
                           const int                              theMode)
{
  thePrs->Clear();

  TopoDS_Face aFace = ComputeFace();

  if (aFace.IsNull())
    return;
  if (theMode != 0)
    return;

  StdPrs_ShadedShape::Add(thePrs, aFace, myDrawer);
}

void FilledCircle::ComputeSelection(const occ::handle<SelectMgr_Selection>& theSelection,
                                    const int /*theMode*/)
{
  occ::handle<SelectMgr_EntityOwner>    anEntityOwner = new SelectMgr_EntityOwner(this);
  occ::handle<Select3D_SensitiveEntity> aSensitiveCircle;

  if (std::abs(std::abs(myUEnd - myUStart) - 2.0 * M_PI) > gp::Resolution())
  {
    aSensitiveCircle =
      new Select3D_SensitivePoly(anEntityOwner, myCircle->Circ(), myUStart, myUEnd, myFilledStatus);
  }
  else
  {
    aSensitiveCircle =
      new Select3D_SensitiveCircle(anEntityOwner, myCircle->Circ(), myFilledStatus);
  }

  theSelection->Add(aSensitiveCircle);
}

//==============================================================================
// Fonction  vcircle
// -----------------  Uniquement par parametre. Pas de selection dans le viewer.
//==============================================================================

//==============================================================================
// function : VCircleBuilder
// purpose  : Build an AIS_Circle
// Draw arg : vcircle CircleName PlaneName PointName Radius IsFilled UStart UEnd
//                              PointName PointName PointName IsFilled UStart UEnd
//==============================================================================

void DisplayCircle(const occ::handle<Geom_Circle>& theGeomCircle,
                   const TCollection_AsciiString&  theName,
                   const bool                      isFilled,
                   const double                    theUStart,
                   const double                    theUEnd)
{
  occ::handle<AIS_InteractiveObject> aCircle;
  if (isFilled)
  {
    aCircle = new FilledCircle(theGeomCircle, theUStart, theUEnd);
  }
  else
  {
    aCircle = new AIS_Circle(theGeomCircle, theUStart, theUEnd, false);
  }

  // Check if there is an object with given name
  // and remove it from context
  if (GetMapOfAIS().IsBound2(theName))
  {
    occ::handle<AIS_InteractiveObject> anInterObj = GetMapOfAIS().Find2(theName);
    TheAISContext()->Remove(anInterObj, false);
    GetMapOfAIS().UnBind2(theName);
  }

  // Bind the circle to its name
  GetMapOfAIS().Bind(aCircle, theName);

  // Display the circle
  TheAISContext()->Display(aCircle, true);
}

static int VCircleBuilder(Draw_Interpretor& /*di*/, int argc, const char** argv)
{
  if (argc > 8 || argc < 2)
  {
    Message::SendFail("Syntax error: wrong number of arguments");
    return 1;
  }

  double anUStart = 0, anUEnd = M_PI * 2.0;
  if (argc == 8)
  {
    anUStart = Draw::Atof(argv[6]) * M_PI / 180.0;
    anUEnd   = Draw::Atof(argv[7]) * M_PI / 180.0;
  }
  else if (argc == 4)
  {
    anUStart = Draw::Atof(argv[2]) * M_PI / 180.0;
    anUEnd   = Draw::Atof(argv[3]) * M_PI / 180.0;
  }

  if (argc == 6 || argc == 8)
  {
    TCollection_AsciiString aName(argv[1]);
    bool                    isFilled = Draw::Atoi(argv[5]) != 0;

    occ::handle<AIS_InteractiveObject> aShapeA, aShapeB;
    GetMapOfAIS().Find2(argv[2], aShapeA);
    GetMapOfAIS().Find2(argv[3], aShapeB);

    // Arguments: AIS_Point AIS_Point AIS_Point
    if (!aShapeA.IsNull() && !aShapeB.IsNull() && aShapeA->Type() == AIS_KindOfInteractive_Datum
        && aShapeA->Signature() == 1)
    {
      occ::handle<AIS_InteractiveObject> aShapeC;
      GetMapOfAIS().Find2(argv[4], aShapeC);
      occ::handle<AIS_Point> anAISPointA = occ::down_cast<AIS_Point>(aShapeA);
      occ::handle<AIS_Point> anAISPointB = occ::down_cast<AIS_Point>(aShapeB);
      occ::handle<AIS_Point> anAISPointC = occ::down_cast<AIS_Point>(aShapeC);
      if (anAISPointA.IsNull() || anAISPointB.IsNull() || anAISPointC.IsNull())
      {
        Message::SendFail("Error: arguments are expected to be points");
        return 1;
      }

      // Verify that the three points are different
      occ::handle<Geom_CartesianPoint> aCartPointA =
        occ::down_cast<Geom_CartesianPoint>(anAISPointA->Component());
      occ::handle<Geom_CartesianPoint> aCartPointB =
        occ::down_cast<Geom_CartesianPoint>(anAISPointB->Component());
      occ::handle<Geom_CartesianPoint> aCartPointC =
        occ::down_cast<Geom_CartesianPoint>(anAISPointC->Component());
      // Test A=B
      if (std::abs(aCartPointA->X() - aCartPointB->X()) <= Precision::Confusion()
          && std::abs(aCartPointA->Y() - aCartPointB->Y()) <= Precision::Confusion()
          && std::abs(aCartPointA->Z() - aCartPointB->Z()) <= Precision::Confusion())
      {
        Message::SendFail("Error: Same points");
        return 1;
      }
      // Test A=C
      if (std::abs(aCartPointA->X() - aCartPointC->X()) <= Precision::Confusion()
          && std::abs(aCartPointA->Y() - aCartPointC->Y()) <= Precision::Confusion()
          && std::abs(aCartPointA->Z() - aCartPointC->Z()) <= Precision::Confusion())
      {
        Message::SendFail("Error: Same points");
        return 1;
      }
      // Test B=C
      if (std::abs(aCartPointB->X() - aCartPointC->X()) <= Precision::Confusion()
          && std::abs(aCartPointB->Y() - aCartPointC->Y()) <= Precision::Confusion()
          && std::abs(aCartPointB->Z() - aCartPointC->Z()) <= Precision::Confusion())
      {
        Message::SendFail("Error: Same points");
        return 1;
      }
      // Construction of the circle
      GC_MakeCircle aCir =
        GC_MakeCircle(aCartPointA->Pnt(), aCartPointB->Pnt(), aCartPointC->Pnt());
      occ::handle<Geom_Circle> aGeomCircle;
      try
      {
        aGeomCircle = aCir.Value();
      }
      catch (StdFail_NotDone const&)
      {
        Message::SendFail("Error: can't create circle");
        return 1;
      }

      DisplayCircle(aGeomCircle, aName, isFilled, anUStart, anUEnd);
    }

    // Arguments: AIS_Plane AIS_Point Real
    else if (aShapeA->Type() == AIS_KindOfInteractive_Datum && aShapeA->Signature() == 7)
    {
      occ::handle<AIS_Plane> anAISPlane  = occ::down_cast<AIS_Plane>(aShapeA);
      occ::handle<AIS_Point> anAISPointB = occ::down_cast<AIS_Point>(aShapeB);
      if (anAISPointB.IsNull())
      {
        Message::SendFail("Error: 2d element is a expected to be a point");
        return 1;
      }

      // Check that the radius is >= 0
      const double anR = Draw::Atof(argv[4]);
      if (anR <= 0)
      {
        Message::SendFail("Syntax error: the radius must be >=0");
        return 1;
      }

      // Recover the normal to the plane
      occ::handle<Geom_Plane>          aGeomPlane  = anAISPlane->Component();
      occ::handle<Geom_Point>          aGeomPointB = anAISPointB->Component();
      occ::handle<Geom_CartesianPoint> aCartPointB =
        occ::down_cast<Geom_CartesianPoint>(aGeomPointB);

      gp_Pln                   aGpPlane = aGeomPlane->Pln();
      gp_Ax1                   aGpAxe   = aGpPlane.Axis();
      gp_Dir                   aDir     = aGpAxe.Direction();
      gp_Pnt                   aCenter  = aCartPointB->Pnt();
      GC_MakeCircle            aCir     = GC_MakeCircle(aCenter, aDir, anR);
      occ::handle<Geom_Circle> aGeomCircle;
      try
      {
        aGeomCircle = aCir.Value();
      }
      catch (StdFail_NotDone const&)
      {
        Message::SendFail("Error: can't create circle");
        return 1;
      }

      DisplayCircle(aGeomCircle, aName, isFilled, anUStart, anUEnd);
    }
    else
    {
      Message::SendFail("Error: 1st argument has an unexpected type");
      return 1;
    }
  }
  else // No arguments: selection in the viewer
  {
    // Get the name of the circle
    TCollection_AsciiString aName(argv[1]);

    NCollection_List<TopoDS_Shape> aShapes;
    ViewerTest::GetSelectedShapes(aShapes);
    if (aShapes.Extent() != 3 && aShapes.Extent() != 2)
    {
      Message::SendFail("Error: Wrong number of selected shapes.");
      return 1;
    }

    const TopoDS_Shape& aShapeA = aShapes.First();
    if (aShapeA.ShapeType() == TopAbs_VERTEX)
    {
      if (aShapes.Extent() != 3)
      {
        Message::SendFail("Error: wrong number of selected shapes.");
        return 1;
      }

      NCollection_List<TopoDS_Shape>::Iterator anIter(aShapes);

      anIter.Next();
      const TopoDS_Shape& aShapeB = anIter.Value();

      anIter.Next();
      const TopoDS_Shape& aShapeC = anIter.Value();

      // Get isFilled
      bool isFilled;
      std::cout << "Enter filled status (0 or 1)\n";
      std::cin >> isFilled;

      // Construction of the circle
      gp_Pnt A = BRep_Tool::Pnt(TopoDS::Vertex(aShapeA));
      gp_Pnt B = BRep_Tool::Pnt(TopoDS::Vertex(aShapeB));
      gp_Pnt C = BRep_Tool::Pnt(TopoDS::Vertex(aShapeC));

      GC_MakeCircle            aCir = GC_MakeCircle(A, B, C);
      occ::handle<Geom_Circle> aGeomCircle;
      try
      {
        aGeomCircle = aCir.Value();
      }
      catch (StdFail_NotDone const&)
      {
        Message::SendFail("Error: can't create circle");
        return 1;
      }

      DisplayCircle(aGeomCircle, aName, isFilled, anUStart, anUEnd);
    }
    else if (aShapeA.ShapeType() == TopAbs_FACE)
    {
      const TopoDS_Shape& aShapeB = aShapes.Last();

      // Recover the radius
      double aRad = 0.0;
      do
      {
        std::cout << " Enter the value of the radius:\n";
        std::cin >> aRad;
      } while (aRad <= 0);

      // Get filled status
      bool isFilled;
      std::cout << "Enter filled status (0 or 1)\n";
      std::cin >> isFilled;

      // Recover the normal to the plane. tag
      TopoDS_Face             aFace = TopoDS::Face(aShapeA);
      BRepAdaptor_Surface     aSurface(aFace, false);
      gp_Pln                  aPlane     = aSurface.Plane();
      occ::handle<Geom_Plane> aGeomPlane = new Geom_Plane(aPlane);
      gp_Pln                  aGpPlane   = aGeomPlane->Pln();
      gp_Ax1                  aGpAxe     = aGpPlane.Axis();
      gp_Dir                  aDir       = aGpAxe.Direction();

      // Recover the center
      gp_Pnt aCenter = BRep_Tool::Pnt(TopoDS::Vertex(aShapeB));

      // Construct the circle
      GC_MakeCircle            aCir = GC_MakeCircle(aCenter, aDir, aRad);
      occ::handle<Geom_Circle> aGeomCircle;
      try
      {
        aGeomCircle = aCir.Value();
      }
      catch (StdFail_NotDone const&)
      {
        Message::SendFail("Error: can't create circle");
        return 1;
      }

      DisplayCircle(aGeomCircle, aName, isFilled, anUStart, anUEnd);
    }
    else
    {
      Message::SendFail("Error: You should select face and vertex or three vertices.");
      return 1;
    }
  }

  return 0;
}

//=================================================================================================

static int VDrawText(Draw_Interpretor& theDI, int theArgsNb, const char** theArgVec)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (theArgsNb < 3)
  {
    Message::SendFail("Syntax error: wrong number of arguments. See usage:");
    theDI.PrintHelp(theArgVec[0]);
    return 1;
  }
  else if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  int                        anArgIt = 1;
  TCollection_ExtendedString aName(theArgVec[anArgIt++], true);
  TCollection_ExtendedString aText(theArgVec[anArgIt++], true);
  occ::handle<AIS_TextLabel> aTextPrs;
  ViewerTest_AutoUpdater     anAutoUpdater(aContext, ViewerTest::CurrentView());

  bool isNewPrs = false;
  if (GetMapOfAIS().IsBound2(aName))
  {
    aTextPrs = occ::down_cast<AIS_TextLabel>(GetMapOfAIS().Find2(aName));
  }

  if (aTextPrs.IsNull())
  {
    isNewPrs = true;
    aTextPrs = new AIS_TextLabel();
  }

  aTextPrs->SetText(aText);

  occ::handle<Graphic3d_TransformPers> aTrsfPers;
  Aspect_TypeOfDisplayText             aDisplayType = Aspect_TODT_NORMAL;

  bool   aHasPlane = false;
  gp_Dir aNormal;
  gp_Dir aDirection;
  gp_Pnt aPos;

  occ::handle<Font_TextFormatter> aTextFormatter;
  for (; anArgIt < theArgsNb; ++anArgIt)
  {
    TCollection_AsciiString aParam(theArgVec[anArgIt]);
    aParam.LowerCase();

    if (anAutoUpdater.parseRedrawMode(aParam))
    {
      continue;
    }
    else if ((aParam == "-pos" || aParam == "-position") && anArgIt + 3 < theArgsNb)
    {
      aPos.SetX(Draw::Atof(theArgVec[++anArgIt]));
      aPos.SetY(Draw::Atof(theArgVec[++anArgIt]));
      aPos.SetZ(Draw::Atof(theArgVec[++anArgIt]));
      aTextPrs->SetPosition(aPos);
    }
    else if (aParam == "-color")
    {
      Quantity_Color aColor;
      int aNbParsed = Draw::ParseColor(theArgsNb - anArgIt - 1, theArgVec + anArgIt + 1, aColor);
      if (aNbParsed == 0)
      {
        Message::SendFail() << "Syntax error at '" << aParam << "'";
        return 1;
      }
      anArgIt += aNbParsed;
      aTextPrs->SetColor(aColor);
    }
    else if ((aParam == "-halign" || aParam == "-valign" || aParam == "-align")
             && anArgIt + 1 < theArgsNb)
    {
      TCollection_AsciiString aType(theArgVec[++anArgIt]);
      aType.LowerCase();
      if (aType == "left")
      {
        aTextPrs->SetHJustification(Graphic3d_HTA_LEFT);
        if (aParam == "-valign")
        {
          Message::SendFail() << "Syntax error at '" << aParam << "'";
          return 1;
        }
      }
      else if (aType == "center")
      {
        if (aParam == "-halign" || aParam == "-align")
        {
          aTextPrs->SetHJustification(Graphic3d_HTA_CENTER);
        }
        if (aParam == "-valign" || aParam == "-align")
        {
          aTextPrs->SetVJustification(Graphic3d_VTA_CENTER);
        }
      }
      else if (aType == "right")
      {
        aTextPrs->SetHJustification(Graphic3d_HTA_RIGHT);
        if (aParam == "-valign")
        {
          Message::SendFail() << "Syntax error at '" << aParam << "'";
          return 1;
        }
      }
      else if (aType == "top")
      {
        aTextPrs->SetVJustification(Graphic3d_VTA_TOP);
        if (aParam == "-halign")
        {
          Message::SendFail() << "Syntax error at '" << aParam << "'";
          return 1;
        }
      }
      else if (aType == "bottom")
      {
        aTextPrs->SetVJustification(Graphic3d_VTA_BOTTOM);
        if (aParam == "-halign")
        {
          Message::SendFail() << "Syntax error at '" << aParam << "'";
          return 1;
        }
      }
      else if (aType == "topfirstline")
      {
        aTextPrs->SetVJustification(Graphic3d_VTA_TOPFIRSTLINE);
        if (aParam == "-halign")
        {
          Message::SendFail() << "Syntax error at '" << aParam << "'";
          return 1;
        }
      }
      else
      {
        Message::SendFail() << "Syntax error at '" << aParam << "'";
        return 1;
      }
    }
    else if (aParam == "-angle" && anArgIt + 1 < theArgsNb)
    {
      aTextPrs->SetAngle(Draw::Atof(theArgVec[++anArgIt]) * (M_PI / 180.0));
    }
    else if (aParam == "-zoom" || aParam == "-nozoom" || aParam == "-zoomable"
             || aParam == "-nonzoomable")
    {
      const bool isZoomable = Draw::ParseOnOffNoIterator(theArgsNb, theArgVec, anArgIt);
      aTextPrs->SetZoomable(isZoomable);
    }
    else if (aParam == "-height" && anArgIt + 1 < theArgsNb)
    {
      aTextPrs->SetHeight(Draw::Atof(theArgVec[++anArgIt]));
    }
    else if (aParam == "-wrapping" && anArgIt + 1 < theArgsNb)
    {
      if (aTextFormatter.IsNull())
      {
        aTextFormatter = new Font_TextFormatter();
      }
      aTextFormatter->SetWrapping((float)Draw::Atof(theArgVec[++anArgIt]));
    }
    else if (aParam == "-wordwrapping")
    {
      const bool isWordWrapping = Draw::ParseOnOffNoIterator(theArgsNb, theArgVec, anArgIt);
      aTextFormatter->SetWordWrapping(isWordWrapping);
    }
    else if (aParam == "-aspect" && anArgIt + 1 < theArgsNb)
    {
      TCollection_AsciiString anOption(theArgVec[++anArgIt]);
      anOption.LowerCase();
      Font_FontAspect aFontAspect = Font_FA_Undefined;
      if (!parseFontStyle(anOption, aFontAspect))
      {
        Message::SendFail() << "Syntax error: unknown font aspect '" << anOption << "'";
        return 1;
      }
      aTextPrs->SetFontAspect(aFontAspect);
    }
    else if (aParam == "-font" && anArgIt + 1 < theArgsNb)
    {
      aTextPrs->SetFont(theArgVec[++anArgIt]);
    }
    else if (aParam == "-plane" && anArgIt + 6 < theArgsNb)
    {
      aNormal.SetCoord(Draw::Atof(theArgVec[anArgIt + 1]),
                       Draw::Atof(theArgVec[anArgIt + 2]),
                       Draw::Atof(theArgVec[anArgIt + 3]));
      aDirection.SetCoord(Draw::Atof(theArgVec[anArgIt + 4]),
                          Draw::Atof(theArgVec[anArgIt + 5]),
                          Draw::Atof(theArgVec[anArgIt + 6]));
      aHasPlane = true;
      anArgIt += 6;
    }
    else if (aParam == "-flipping" || aParam == "-noflipping" || aParam == "-flip"
             || aParam == "-noflip")
    {
      const bool toFlip = Draw::ParseOnOffNoIterator(theArgsNb, theArgVec, anArgIt);
      aTextPrs->SetFlipping(toFlip);
    }
    else if (aParam == "-ownanchor" || aParam == "-noownanchor")
    {
      const bool isOwnAnchor = Draw::ParseOnOffNoIterator(theArgsNb, theArgVec, anArgIt);
      aTextPrs->SetOwnAnchorPoint(isOwnAnchor);
    }
    else if ((aParam == "-disptype" || aParam == "-displaytype") && anArgIt + 1 < theArgsNb)
    {
      TCollection_AsciiString aType(theArgVec[++anArgIt]);
      aType.LowerCase();
      if (aType == "subtitle")
      {
        aDisplayType = Aspect_TODT_SUBTITLE;
      }
      else if (aType == "decal")
      {
        aDisplayType = Aspect_TODT_DEKALE;
      }
      else if (aType == "blend")
      {
        aDisplayType = Aspect_TODT_BLEND;
      }
      else if (aType == "dimension")
      {
        aDisplayType = Aspect_TODT_DIMENSION;
      }
      else if (aType == "normal")
      {
        aDisplayType = Aspect_TODT_NORMAL;
      }
      else if (aType == "shadow")
      {
        aDisplayType = Aspect_TODT_SHADOW;
      }
      else
      {
        Message::SendFail() << "Syntax error: wrong display type '" << aType << "'";
        return 1;
      }
    }
    else if (aParam == "-subcolor" || aParam == "-subtitlecolor")
    {
      Quantity_Color aColor;
      int aNbParsed = Draw::ParseColor(theArgsNb - anArgIt - 1, theArgVec + anArgIt + 1, aColor);
      if (aNbParsed == 0)
      {
        Message::SendFail() << "Syntax error at '" << aParam << "'";
        return 1;
      }
      anArgIt += aNbParsed;
      aTextPrs->SetColorSubTitle(aColor);
    }
    else if (aParam == "-2d")
    {
      aTrsfPers = new Graphic3d_TransformPers(Graphic3d_TMF_2d);
    }
    else if (aParam == "-trsfperspos" || aParam == "-perspos")
    {
      if (anArgIt + 2 >= theArgsNb)
      {
        Message::SendFail() << "Error: wrong number of values for parameter '" << aParam << "'.";
        return 1;
      }

      TCollection_AsciiString aX(theArgVec[++anArgIt]);
      TCollection_AsciiString aY(theArgVec[++anArgIt]);
      TCollection_AsciiString aZ = "0";
      if (!aX.IsIntegerValue() || !aY.IsIntegerValue())
      {
        Message::SendFail() << "Error: wrong syntax at '" << aParam << "'.";
        return 1;
      }
      if (anArgIt + 1 < theArgsNb)
      {
        TCollection_AsciiString aTemp = theArgVec[anArgIt + 1];
        if (aTemp.IsIntegerValue())
        {
          aZ = aTemp;
          ++anArgIt;
        }
      }

      int aCorner = Aspect_TOTP_CENTER;
      if (aX.IntegerValue() > 0.0)
      {
        aCorner |= Aspect_TOTP_RIGHT;
      }
      else if (aX.IntegerValue() < 0.0)
      {
        aCorner |= Aspect_TOTP_LEFT;
      }
      if (aY.IntegerValue() > 0.0)
      {
        aCorner |= Aspect_TOTP_TOP;
      }
      else if (aY.IntegerValue() < 0.0)
      {
        aCorner |= Aspect_TOTP_BOTTOM;
      }
      aTrsfPers = new Graphic3d_TransformPers(aTrsfPers->Mode(),
                                              Aspect_TypeOfTriedronPosition(aCorner),
                                              NCollection_Vec2<int>(aZ.IntegerValue()));
    }
    else
    {
      Message::SendFail() << "Syntax error: unknown argument '" << aParam << "'";
      return 1;
    }
  }

  aTextPrs->SetTextFormatter(aTextFormatter);

  if (aHasPlane)
  {
    aTextPrs->SetOrientation3D(gp_Ax2(aPos, aNormal, aDirection));
  }

  aTextPrs->SetDisplayType(aDisplayType);

  if (!aTrsfPers.IsNull())
  {
    aContext->SetTransformPersistence(aTextPrs, aTrsfPers);
    aTextPrs->SetZLayer(Graphic3d_ZLayerId_TopOSD);
    if (aTextPrs->Position().Z() != 0)
    {
      aTextPrs->SetPosition(gp_Pnt(aTextPrs->Position().X(), aTextPrs->Position().Y(), 0));
    }
  }
  else if (!aTextPrs->TransformPersistence().IsNull())
  {
    aContext->SetTransformPersistence(aTextPrs, occ::handle<Graphic3d_TransformPers>());
  }

  if (isNewPrs)
  {
    ViewerTest::Display(aName, aTextPrs, false);
  }
  else
  {
    aContext->Redisplay(aTextPrs, false, true);
  }
  return 0;
}

#include <cmath>
#include <gp_Pnt.hxx>
#include <Graphic3d_ArrayOfPrimitives.hxx>
#include <Poly_Triangle.hxx>
#include <Poly_Triangulation.hxx>
#include <Standard_ShortReal.hxx>

#include <AIS_Triangulation.hxx>
#include <StdPrs_ToolTriangulatedShape.hxx>
#include <Poly_Connect.hxx>
#include <gp_Dir.hxx>
#include <Graphic3d_GraphicDriver.hxx>

#include <Prs3d_ShadingAspect.hxx>
#include <Graphic3d_MaterialAspect.hxx>
#include <Graphic3d_AspectFillArea3d.hxx>

#include <BRepPrimAPI_MakeCylinder.hxx>
#include <utility>

//===============================================================================================
// function : CalculationOfSphere
// author   : psn
// purpose  : Create a Sphere
//===============================================================================================

occ::handle<Poly_Triangulation> CalculationOfSphere(double X,
                                                    double Y,
                                                    double Z,
                                                    int    res,
                                                    double Radius)
{
  double mRadius    = Radius;
  double mCenter[3] = {X, Y, Z};
  int    mThetaResolution;
  int    mPhiResolution;
  double mStartTheta = 0;   // StartTheta;
  double mEndTheta   = 360; // EndTheta;
  double mStartPhi   = 0;   // StartPhi;
  double mEndPhi     = 180; // EndPhi;
  res                = res < 4 ? 4 : res;

  mThetaResolution = res;
  mPhiResolution   = res;

  int    i, j;
  int    jStart, jEnd, numOffset;
  double x[3], n[3], deltaPhi, deltaTheta, phi, theta, radius;
  double startTheta, endTheta, startPhi, endPhi;
  int    base, numPoles = 0, thetaResolution, phiResolution;

  int pts[3];
  int piece     = -1;
  int numPieces = 1;
  if (numPieces > mThetaResolution)
  {
    numPieces = mThetaResolution;
  }

  int    localThetaResolution = mThetaResolution;
  double localStartTheta      = mStartTheta;
  double localEndTheta        = mEndTheta;

  while (localEndTheta < localStartTheta)
  {
    localEndTheta += 360.0;
  }

  deltaTheta = (localEndTheta - localStartTheta) / localThetaResolution;

  // Change the ivars based on pieces.
  int start, end;
  start                = piece * localThetaResolution / numPieces;
  end                  = (piece + 1) * localThetaResolution / numPieces;
  localEndTheta        = localStartTheta + (double)(end)*deltaTheta;
  localStartTheta      = localStartTheta + (double)(start)*deltaTheta;
  localThetaResolution = end - start;

  // Create north pole if needed
  int number_point      = 0;
  int number_pointArray = 0;

  if (mStartPhi <= 0.0)
  {
    number_pointArray++;
    numPoles++;
  }
  if (mEndPhi >= 180.0)
  {
    number_pointArray++;
    numPoles++;
  }

  // Check data, determine increments, and convert to radians
  startTheta = (localStartTheta < localEndTheta ? localStartTheta : localEndTheta);
  startTheta *= M_PI / 180.0;
  endTheta = (localEndTheta > localStartTheta ? localEndTheta : localStartTheta);
  endTheta *= M_PI / 180.0;

  startPhi = (mStartPhi < mEndPhi ? mStartPhi : mEndPhi);
  startPhi *= M_PI / 180.0;
  endPhi = (mEndPhi > mStartPhi ? mEndPhi : mStartPhi);
  endPhi *= M_PI / 180.0;

  phiResolution   = mPhiResolution - numPoles;
  deltaPhi        = (endPhi - startPhi) / (mPhiResolution - 1);
  thetaResolution = localThetaResolution;
  if (fabs(localStartTheta - localEndTheta) < 360.0)
  {
    ++localThetaResolution;
  }
  deltaTheta = (endTheta - startTheta) / thetaResolution;

  jStart = (mStartPhi <= 0.0 ? 1 : 0);
  jEnd   = (mEndPhi >= 180.0 ? mPhiResolution - 1 : mPhiResolution);

  // Create intermediate points
  for (i = 0; i < localThetaResolution; i++)
  {
    for (j = jStart; j < jEnd; j++)
    {
      number_pointArray++;
    }
  }

  // Generate mesh connectivity
  base = phiResolution * localThetaResolution;

  int number_triangle = 0;
  if (mStartPhi <= 0.0)
  { // around north pole
    number_triangle += localThetaResolution;
  }

  if (mEndPhi >= 180.0)
  { // around south pole
    number_triangle += localThetaResolution;
  }

  // bands in-between poles
  for (i = 0; i < localThetaResolution; i++)
  {
    for (j = 0; j < (phiResolution - 1); j++)
    {
      number_triangle += 2;
    }
  }

  occ::handle<Poly_Triangulation> polyTriangulation =
    new Poly_Triangulation(number_pointArray, number_triangle, false, true);

  if (mStartPhi <= 0.0)
  {
    x[0] = mCenter[0];
    x[1] = mCenter[1];
    x[2] = mCenter[2] + mRadius;
    polyTriangulation->SetNode(1, gp_Pnt(x[0], x[1], x[2]));
  }

  // Create south pole if needed
  if (mEndPhi >= 180.0)
  {
    x[0] = mCenter[0];
    x[1] = mCenter[1];
    x[2] = mCenter[2] - mRadius;
    polyTriangulation->SetNode(2, gp_Pnt(x[0], x[1], x[2]));
  }

  number_point = 3;
  for (i = 0; i < localThetaResolution; i++)
  {
    theta = localStartTheta * M_PI / 180.0 + i * deltaTheta;
    for (j = jStart; j < jEnd; j++)
    {
      phi    = startPhi + j * deltaPhi;
      radius = mRadius * sin((double)phi);
      n[0]   = radius * cos((double)theta);
      n[1]   = radius * sin((double)theta);
      n[2]   = mRadius * cos((double)phi);
      x[0]   = n[0] + mCenter[0];
      x[1]   = n[1] + mCenter[1];
      x[2]   = n[2] + mCenter[2];
      polyTriangulation->SetNode(number_point, gp_Pnt(x[0], x[1], x[2]));
      number_point++;
    }
  }

  numPoles        = 3;
  number_triangle = 1;
  if (mStartPhi <= 0.0)
  { // around north pole
    for (i = 0; i < localThetaResolution; i++)
    {
      pts[0] = phiResolution * i + numPoles;
      pts[1] = (phiResolution * (i + 1) % base) + numPoles;
      pts[2] = 1;
      polyTriangulation->SetTriangle(number_triangle, Poly_Triangle(pts[0], pts[1], pts[2]));
      number_triangle++;
    }
  }

  if (mEndPhi >= 180.0)
  { // around south pole
    numOffset = phiResolution - 1 + numPoles;
    for (i = 0; i < localThetaResolution; i++)
    {
      pts[0] = phiResolution * i + numOffset;
      pts[2] = ((phiResolution * (i + 1)) % base) + numOffset;
      pts[1] = numPoles - 1;
      polyTriangulation->SetTriangle(number_triangle, Poly_Triangle(pts[0], pts[1], pts[2]));
      number_triangle++;
    }
  }

  // bands in-between poles

  for (i = 0; i < localThetaResolution; i++)
  {
    for (j = 0; j < (phiResolution - 1); j++)
    {
      pts[0] = phiResolution * i + j + numPoles;
      pts[1] = pts[0] + 1;
      pts[2] = ((phiResolution * (i + 1) + j) % base) + numPoles + 1;
      polyTriangulation->SetTriangle(number_triangle, Poly_Triangle(pts[0], pts[1], pts[2]));
      number_triangle++;
      pts[1] = pts[2];
      pts[2] = pts[1] - 1;
      polyTriangulation->SetTriangle(number_triangle, Poly_Triangle(pts[0], pts[1], pts[2]));
      number_triangle++;
    }
  }

  Poly_Connect pc(polyTriangulation);

  int              index[3];
  constexpr double Tol = Precision::Confusion();

  gp_Dir Nor;
  for (i = 1; i <= polyTriangulation->NbNodes(); i++)
  {
    gp_XYZ eqPlan(0, 0, 0);
    for (pc.Initialize(i); pc.More(); pc.Next())
    {
      polyTriangulation->Triangle(pc.Value()).Get(index[0], index[1], index[2]);
      gp_XYZ v1(polyTriangulation->Node(index[1]).Coord()
                - polyTriangulation->Node(index[0]).Coord());
      gp_XYZ v2(polyTriangulation->Node(index[2]).Coord()
                - polyTriangulation->Node(index[1]).Coord());
      gp_XYZ vv  = v1 ^ v2;
      double mod = vv.Modulus();
      if (mod < Tol)
        continue;
      eqPlan += vv / mod;
    }

    double modmax = eqPlan.Modulus();
    if (modmax > Tol)
      Nor = gp_Dir(eqPlan);
    else
      Nor = gp_Dir(gp_Dir::D::Z);

    polyTriangulation->SetNormal(i, Nor.XYZ());
  }

  return polyTriangulation;
}

//===============================================================================================
// function : VDrawSphere
// author   : psn
// purpose  : Create an AIS shape.
//===============================================================================================
static int VDrawSphere(Draw_Interpretor& /*di*/, int argc, const char** argv)
{
  // check for errors
  occ::handle<AIS_InteractiveContext> aContextAIS = ViewerTest::GetAISContext();
  if (aContextAIS.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }
  else if (argc < 3)
  {
    Message::SendFail() << "Syntax error: wrong number of arguments.\n"
                        << "Use: " << argv[0]
                        << " shapeName Fineness [X=0.0 Y=0.0 Z=0.0] [Radius=100.0] [ToShowEdges=0]";
    return 1;
  }

  // read the arguments
  TCollection_AsciiString aShapeName(argv[1]);
  int                     aResolution = Draw::Atoi(argv[2]);
  double                  aCenterX    = (argc > 5) ? Draw::Atof(argv[3]) : 0.0;
  double                  aCenterY    = (argc > 5) ? Draw::Atof(argv[4]) : 0.0;
  double                  aCenterZ    = (argc > 5) ? Draw::Atof(argv[5]) : 0.0;
  double                  aRadius     = (argc > 6) ? Draw::Atof(argv[6]) : 100.0;
  bool                    toShowEdges = (argc > 7) ? Draw::Atoi(argv[7]) == 1 : false;
  bool                    toPrintInfo = (argc > 8) ? Draw::Atoi(argv[8]) == 1 : true;

  // remove AIS object with given name from map
  VDisplayAISObject(aShapeName, occ::handle<AIS_InteractiveObject>());

  if (toPrintInfo)
    std::cout << "Compute Triangulation...\n";
  occ::handle<AIS_Triangulation> aShape =
    new AIS_Triangulation(CalculationOfSphere(aCenterX, aCenterY, aCenterZ, aResolution, aRadius));
  const int aNumberPoints    = aShape->GetTriangulation()->NbNodes();
  const int aNumberTriangles = aShape->GetTriangulation()->NbTriangles();

  // stupid initialization of Green color in RGBA space as integer
  // probably wrong for big-endian CPUs
  const NCollection_Vec4<uint8_t> aColor(0, 255, 0, 0);

  // setup colors array per vertex
  occ::handle<NCollection_HArray1<int>> aColorArray =
    new NCollection_HArray1<int>(1, aNumberPoints);
  for (int aNodeId = 1; aNodeId <= aNumberPoints; ++aNodeId)
  {
    aColorArray->SetValue(aNodeId, *reinterpret_cast<const int*>(aColor.GetData()));
  }
  aShape->SetColors(aColorArray);

  // show statistics
  int aPointsSize  = aNumberPoints * 3 * sizeof(float);      // 3x GLfloat
  int aNormalsSize = aNumberPoints * 3 * sizeof(float);      // 3x GLfloat
                                                             // clang-format off
  int aColorsSize      = aNumberPoints * 3 * sizeof(float);  // 3x GLfloat without alpha
                                                             // clang-format on
  int aTrianglesSize   = aNumberTriangles * 3 * sizeof(int); // 3x GLint
  int aPolyConnectSize = aNumberPoints * 4 + aNumberTriangles * 6 * 4;
  int aTotalSize       = aPointsSize + aNormalsSize + aColorsSize + aTrianglesSize;
  aTotalSize >>= 20; // MB
  aNormalsSize >>= 20;
  aColorsSize >>= 20;
  aTrianglesSize >>= 20;
  aPolyConnectSize >>= 20;
  if (toPrintInfo)
  {
    std::cout << "NumberOfPoints:    " << aNumberPoints << "\n"
              << "NumberOfTriangles: " << aNumberTriangles << "\n"
              << "Amount of memory required for PolyTriangulation without Normals: "
              << (aTotalSize - aNormalsSize) << " Mb\n"
              << "Amount of memory for colors: " << aColorsSize << " Mb\n"
              << "Amount of memory for PolyConnect: " << aPolyConnectSize << " Mb\n"
              << "Amount of graphic card memory required: " << aTotalSize << " Mb\n";
  }

  // Setting material properties, very important for desirable visual result!
  Graphic3d_MaterialAspect aMat(Graphic3d_NameOfMaterial_Plastified);
  aMat.SetAmbientColor(Quantity_Color(NCollection_Vec3<float>(0.04f)));
  aMat.SetSpecularColor(Quantity_Color(NCollection_Vec3<float>(0.50f)));
  occ::handle<Graphic3d_AspectFillArea3d> anAspect =
    new Graphic3d_AspectFillArea3d(Aspect_IS_SOLID,
                                   Quantity_NOC_WHITE,
                                   Quantity_NOC_YELLOW,
                                   Aspect_TOL_SOLID,
                                   1.0,
                                   aMat,
                                   aMat);
  occ::handle<Prs3d_ShadingAspect> aShAsp = new Prs3d_ShadingAspect();
  anAspect->SetDrawEdges(toShowEdges);
  aShAsp->SetAspect(anAspect);
  aShape->Attributes()->SetShadingAspect(aShAsp);

  VDisplayAISObject(aShapeName, aShape);
  return 0;
}

//=================================================================================================

static int VComputeHLR(Draw_Interpretor&, int theArgNb, const char** theArgVec)
{
  TCollection_AsciiString aShapeName, aHlrName;
  TopoDS_Shape            aSh;
  gp_Pnt                  anEye;
  gp_Dir                  aDir;
  gp_Ax2                  aProjAx;
  bool                    hasViewDirArg = false;
  Prs3d_TypeOfHLR         anAlgoType    = Prs3d_TOH_PolyAlgo;
  bool                    toShowCNEdges = false, toShowHiddenEdges = false;
  int                     aNbIsolines = 0;
  if (occ::handle<V3d_Viewer> aViewer = ViewerTest::GetViewerFromContext())
  {
    gp_Dir                aRight;
    occ::handle<V3d_View> aView = ViewerTest::CurrentView();
    int                   aWidth, aHeight;
    double                aCentX, aCentY, aCentZ, aDirX, aDirY, aDirZ;
    double                aRightX, aRightY, aRightZ;
    aView->Window()->Size(aWidth, aHeight);

    aView->ConvertWithProj(aWidth, aHeight / 2, aRightX, aRightY, aRightZ, aDirX, aDirY, aDirZ);
    aView->ConvertWithProj(aWidth / 2, aHeight / 2, aCentX, aCentY, aCentZ, aDirX, aDirY, aDirZ);

    anEye.SetCoord(-aCentX, -aCentY, -aCentZ);
    aDir.SetCoord(-aDirX, -aDirY, -aDirZ);
    aRight.SetCoord(aRightX - aCentX, aRightY - aCentY, aRightZ - aCentZ);
    aProjAx.SetLocation(anEye);
    aProjAx.SetDirection(aDir);
    aProjAx.SetXDirection(aRight);
  }
  for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
  {
    TCollection_AsciiString anArgCase(theArgVec[anArgIter]);
    anArgCase.LowerCase();
    if (anArgIter + 1 < theArgNb
        && (anArgCase == "-algotype" || anArgCase == "-algo" || anArgCase == "-type"))
    {
      TCollection_AsciiString anArgNext(theArgVec[++anArgIter]);
      anArgNext.LowerCase();
      if (anArgNext == "polyalgo")
      {
        anAlgoType = Prs3d_TOH_PolyAlgo;
      }
      else if (anArgNext == "algo")
      {
        anAlgoType = Prs3d_TOH_Algo;
      }
      else
      {
        Message::SendFail() << "Syntax error: unknown algo type '" << anArgNext << "'";
        return 1;
      }
    }
    else if (anArgCase == "-showhiddenedges" || anArgCase == "-hiddenedges"
             || anArgCase == "-hidden")
    {
      toShowHiddenEdges = true;
      if (anArgIter + 1 < theArgNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], toShowHiddenEdges))
      {
        ++anArgIter;
      }
    }
    else if (anArgCase == "-showtangentedges" || anArgCase == "-tangentedges"
             || anArgCase == "-tangent")
    {
      toShowCNEdges = true;
      if (anArgIter + 1 < theArgNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], toShowCNEdges))
      {
        ++anArgIter;
      }
    }
    else if (anArgIter + 1 < theArgNb && (anArgCase == "-nbiso" || anArgCase == "-nbisolines"))
    {
      aNbIsolines = Draw::Atoi(theArgVec[++anArgIter]);
    }
    else if (aSh.IsNull())
    {
      aSh        = DBRep::Get(theArgVec[anArgIter]);
      aShapeName = theArgVec[anArgIter];
      if (aSh.IsNull())
      {
        BRep_Builder aBrepBuilder;
        BRepTools::Read(aSh, theArgVec[anArgIter], aBrepBuilder);
        if (aSh.IsNull())
        {
          Message::SendFail() << "Syntax error: no shape with name " << theArgVec[anArgIter]
                              << " found";
          return 1;
        }
      }
    }
    else if (aHlrName.IsEmpty())
    {
      aHlrName = theArgVec[anArgIter];
    }
    else if (!hasViewDirArg && anArgIter + 8 < theArgNb)
    {
      hasViewDirArg = true;
      gp_Dir anUp;
      anEye.SetCoord(Draw::Atof(theArgVec[anArgIter + 0]),
                     Draw::Atof(theArgVec[anArgIter + 1]),
                     Draw::Atof(theArgVec[anArgIter + 2]));
      aDir.SetCoord(Draw::Atof(theArgVec[anArgIter + 3]),
                    Draw::Atof(theArgVec[anArgIter + 4]),
                    Draw::Atof(theArgVec[anArgIter + 5]));
      anUp.SetCoord(Draw::Atof(theArgVec[anArgIter + 6]),
                    Draw::Atof(theArgVec[anArgIter + 7]),
                    Draw::Atof(theArgVec[anArgIter + 8]));
      aProjAx.SetLocation(anEye);
      aProjAx.SetDirection(aDir);
      aProjAx.SetYDirection(anUp);
      anArgIter += 8;
    }
    else
    {
      Message::SendFail() << "Syntax error: unknown argument '" << theArgVec[anArgIter] << "'";
      return 1;
    }
  }

  if (aHlrName.IsEmpty() || aSh.IsNull()
      || (ViewerTest::GetAISContext().IsNull() && !hasViewDirArg))
  {
    Message::SendFail("Syntax error: wrong number of arguments");
    return 1;
  }

  HLRAlgo_Projector aProjector(aProjAx);
  TopoDS_Shape      aVisible[6];
  TopoDS_Shape      aHidden[6];
  if (anAlgoType == Prs3d_TOH_PolyAlgo)
  {
    occ::handle<HLRBRep_PolyAlgo> aPolyAlgo = new HLRBRep_PolyAlgo();
    aPolyAlgo->Projector(aProjector);
    aPolyAlgo->Load(aSh);
    aPolyAlgo->Update();

    HLRBRep_PolyHLRToShape aHLRToShape;
    aHLRToShape.Update(aPolyAlgo);

    aVisible[HLRBRep_Sharp]   = aHLRToShape.VCompound();
    aVisible[HLRBRep_OutLine] = aHLRToShape.OutLineVCompound(); // extract visible outlines
    aVisible[HLRBRep_Rg1Line] = aHLRToShape.Rg1LineVCompound();
    if (toShowCNEdges)
    {
      aVisible[HLRBRep_RgNLine] = aHLRToShape.RgNLineVCompound();
    }
    if (toShowHiddenEdges)
    {
      aHidden[HLRBRep_Sharp]   = aHLRToShape.HCompound();
      aHidden[HLRBRep_OutLine] = aHLRToShape.OutLineHCompound();
      aHidden[HLRBRep_Rg1Line] = aHLRToShape.Rg1LineHCompound();
      if (toShowCNEdges)
      {
        aHidden[HLRBRep_RgNLine] = aHLRToShape.RgNLineHCompound();
      }
    }
  }
  else
  {
    occ::handle<HLRBRep_Algo> aHlrAlgo = new HLRBRep_Algo();
    aHlrAlgo->Add(aSh, aNbIsolines);
    aHlrAlgo->Projector(aProjector);
    aHlrAlgo->Update();
    aHlrAlgo->Hide();

    HLRBRep_HLRToShape aHLRToShape(aHlrAlgo);
    aVisible[HLRBRep_Sharp]   = aHLRToShape.VCompound();
    aVisible[HLRBRep_OutLine] = aHLRToShape.OutLineVCompound();
    aVisible[HLRBRep_Rg1Line] = aHLRToShape.Rg1LineVCompound();
    if (toShowCNEdges)
    {
      aVisible[HLRBRep_RgNLine] = aHLRToShape.RgNLineVCompound();
    }
    aVisible[HLRBRep_IsoLine] = aHLRToShape.IsoLineVCompound();

    if (toShowHiddenEdges)
    {
      aHidden[HLRBRep_Sharp]   = aHLRToShape.HCompound();
      aHidden[HLRBRep_OutLine] = aHLRToShape.OutLineHCompound();
      aHidden[HLRBRep_Rg1Line] = aHLRToShape.Rg1LineHCompound();
      if (toShowCNEdges)
      {
        aHidden[HLRBRep_RgNLine] = aHLRToShape.RgNLineHCompound();
      }
      aHidden[HLRBRep_IsoLine] = aHLRToShape.IsoLineHCompound();
    }
    // extract 3d
    // aVisible[HLRBRep_Sharp]   = aHLRToShape.CompoundOfEdges (HLRBRep_Sharp, true,
    // true); aVisible[HLRBRep_OutLine] = aHLRToShape.OutLineVCompound3d();
  }

  TopoDS_Compound aCompRes, aCompVis, aCompHid;
  BRep_Builder    aBuilder;
  aBuilder.MakeCompound(aCompVis);
  aBuilder.MakeCompound(aCompHid);
  aBuilder.MakeCompound(aCompRes);
  for (int aTypeIter = 0; aTypeIter < 6; ++aTypeIter)
  {
    if (!aVisible[aTypeIter].IsNull())
    {
      aBuilder.Add(aCompVis, aVisible[aTypeIter]);
    }
    if (!aHidden[aTypeIter].IsNull())
    {
      aBuilder.Add(aCompHid, aHidden[aTypeIter]);
    }
  }
  aBuilder.Add(aCompRes, aCompVis);
  aBuilder.Add(aCompRes, aCompHid);

  // create an AIS shape and display it
  if (!ViewerTest::GetAISContext().IsNull())
  {
    occ::handle<AIS_ColoredShape> anObject = new AIS_ColoredShape(aCompRes);
    if (toShowHiddenEdges)
    {
      occ::handle<Prs3d_LineAspect> aLineAspect =
        new Prs3d_LineAspect(Quantity_Color(Quantity_NOC_RED), Aspect_TOL_DASH, 1.0f);
      for (int aTypeIter = 0; aTypeIter < 6; ++aTypeIter)
      {
        if (!aHidden[aTypeIter].IsNull())
        {
          occ::handle<AIS_ColoredDrawer> aDrawer = anObject->CustomAspects(aHidden[aTypeIter]);
          aDrawer->SetLineAspect(aLineAspect);
          aDrawer->SetWireAspect(aLineAspect);
          aDrawer->SetFreeBoundaryAspect(aLineAspect);
          aDrawer->SetUnFreeBoundaryAspect(aLineAspect);
        }
      }
    }
    ViewerTest::Display(aHlrName, anObject, true);
  }

  DBRep::Set(aHlrName.ToCString(), aCompRes);
  return 0;
}

// This class is a wrap for Graphic3d_ArrayOfPrimitives; it is used for
// manipulating and displaying such an array with AIS context

class MyPArrayObject : public AIS_InteractiveObject
{

public:
  MyPArrayObject(const occ::handle<Graphic3d_ArrayOfPrimitives>& thePArray)
      : myPArray(thePArray)
  {
  }

  MyPArrayObject(Graphic3d_TypeOfPrimitiveArray                                   thePrimType,
                 const occ::handle<NCollection_HArray1<TCollection_AsciiString>>& theDesc,
                 const occ::handle<Graphic3d_AspectMarker3d>&                     theMarkerAspect)
  {
    Init(thePrimType, theDesc, theMarkerAspect, false);
  }

  //! Initialize the array from specified description.
  bool Init(Graphic3d_TypeOfPrimitiveArray                                   thePrimType,
            const occ::handle<NCollection_HArray1<TCollection_AsciiString>>& theDesc,
            const occ::handle<Graphic3d_AspectMarker3d>&                     theMarkerAspect,
            bool                                                             theToPatch);

  DEFINE_STANDARD_RTTI_INLINE(MyPArrayObject, AIS_InteractiveObject);

  bool AcceptDisplayMode(const int theMode) const override { return theMode == 0; }

  //! Sets color to this interactive object
  //! @param theColor the color to be set
  void SetColor(const Quantity_Color& theColor) override;

private:
  void Compute(const occ::handle<PrsMgr_PresentationManager>& thePrsMgr,
               const occ::handle<Prs3d_Presentation>&         thePrs,
               const int                                      theMode) override;

  void ComputeSelection(const occ::handle<SelectMgr_Selection>& theSel, const int theMode) override;

  bool CheckInputCommand(
    const TCollection_AsciiString&                                   theCommand,
    const occ::handle<NCollection_HArray1<TCollection_AsciiString>>& theArgsArray,
    int&                                                             theArgIndex,
    int                                                              theArgCount,
    int                                                              theMaxArgs);

  //! Sets color for the shading aspect of the drawer used in this interactive object
  //! @param theColor the color to be set
  void setColorForShadingAspect(const Quantity_Color& theColor);

  //! Replaces shading aspect from myDrawer->Link() with the own shading aspect of myDrawer for this
  //! interactive object
  void replaceShadingAspect();

protected:
  occ::handle<Graphic3d_AspectMarker3d>    myMarkerAspect;
  occ::handle<Graphic3d_ArrayOfPrimitives> myPArray;
};

void MyPArrayObject::Compute(const occ::handle<PrsMgr_PresentationManager>&,
                             const occ::handle<Prs3d_Presentation>& thePrs,
                             const int                              theMode)
{
  if (myPArray.IsNull() || theMode != 0)
  {
    return;
  }

  occ::handle<Graphic3d_Group> aGroup = thePrs->NewGroup();
  if (!myMarkerAspect.IsNull())
  {
    aGroup->SetGroupPrimitivesAspect(myMarkerAspect);
  }
  else
  {
    aGroup->SetGroupPrimitivesAspect(myDrawer->ShadingAspect()->Aspect());
  }
  aGroup->AddPrimitiveArray(myPArray);
}

bool MyPArrayObject::Init(Graphic3d_TypeOfPrimitiveArray thePrimType,
                          const occ::handle<NCollection_HArray1<TCollection_AsciiString>>& theDesc,
                          const occ::handle<Graphic3d_AspectMarker3d>& theMarkerAspect,
                          bool                                         theToPatch)
{
  myMarkerAspect = theMarkerAspect;
  if (!theToPatch)
  {
    myPArray.Nullify();
  }

  // Parsing array description
  int                  aVertexNum = 0, aBoundNum = 0, aEdgeNum = 0;
  Graphic3d_ArrayFlags anArrayFlags = Graphic3d_ArrayFlags_None;

  const int               anArgsCount = theDesc->Length();
  TCollection_AsciiString aCommand;
  for (int anArgIndex = theDesc->Lower(); anArgIndex <= theDesc->Upper();)
  {
    aCommand = theDesc->Value(anArgIndex);
    aCommand.LowerCase();

    if (CheckInputCommand("-deinterleaved", theDesc, anArgIndex, 0, anArgsCount))
    {
      anArrayFlags |= Graphic3d_ArrayFlags_AttribsDeinterleaved;
    }
    else if (CheckInputCommand("-mutable", theDesc, anArgIndex, 0, anArgsCount))
    {
      anArrayFlags |= Graphic3d_ArrayFlags_AttribsMutable;
      anArrayFlags |= Graphic3d_ArrayFlags_IndexesMutable;
    }
    // vertex command
    else if (CheckInputCommand("v", theDesc, anArgIndex, 3, anArgsCount))
    {
      // vertex has a normal or normal with color or texel
      if (CheckInputCommand("n", theDesc, anArgIndex, 3, anArgsCount))
      {
        anArrayFlags = anArrayFlags | Graphic3d_ArrayFlags_VertexNormal;
      }

      // vertex has a color
      if (CheckInputCommand("c", theDesc, anArgIndex, 3, anArgsCount))
      {
        anArrayFlags = anArrayFlags | Graphic3d_ArrayFlags_VertexColor;
      }

      // vertex has a texel
      if (CheckInputCommand("t", theDesc, anArgIndex, 2, anArgsCount))
      {
        anArrayFlags = anArrayFlags | Graphic3d_ArrayFlags_VertexTexel;
      }

      aVertexNum++;
    }
    // bound command
    else if (CheckInputCommand("b", theDesc, anArgIndex, 1, anArgsCount))
    {
      // bound has color
      if (CheckInputCommand("c", theDesc, anArgIndex, 3, anArgsCount))
      {
        anArrayFlags = anArrayFlags | Graphic3d_ArrayFlags_BoundColor;
      }

      aBoundNum++;
    }
    // edge command
    else if (CheckInputCommand("e", theDesc, anArgIndex, 1, anArgsCount))
    {
      aEdgeNum++;
    }
    // unknown command
    else
      anArgIndex++;
  }

  if (myPArray.IsNull())
  {
    myPArray = Graphic3d_ArrayOfPrimitives::CreateArray(thePrimType,
                                                        aVertexNum,
                                                        aBoundNum,
                                                        aEdgeNum,
                                                        anArrayFlags);
    if (myPArray->HasVertexColors())
    {
      myDrawer->SetupOwnShadingAspect();
      Graphic3d_MaterialAspect aMat(Graphic3d_NameOfMaterial_Plastified);
      aMat.SetSpecularColor(Quantity_NOC_BLACK);
      aMat.SetEmissiveColor(Quantity_NOC_BLACK);
      aMat.SetAmbientColor(Quantity_Color(NCollection_Vec3<float>(0.5f)));
      aMat.SetDiffuseColor(Quantity_Color(NCollection_Vec3<float>(0.5f)));
      myDrawer->ShadingAspect()->SetMaterial(aMat);
      myDrawer->ShadingAspect()->SetColor(Quantity_NOC_WHITE);
    }
  }
  else
  {
    if (myPArray->Type() != thePrimType || aVertexNum > myPArray->VertexNumberAllocated()
        || aEdgeNum > myPArray->EdgeNumberAllocated()
        || aBoundNum > myPArray->BoundNumberAllocated() || !myPArray->Attributes()->IsMutable()
        || (!myPArray->Indices().IsNull() && !myPArray->Indices()->IsMutable()))
    {
      Message::SendFail("Syntax error: array cannot be patched");
      return false;
    }

    myPArray->Attributes()->NbElements = aVertexNum;
    if (occ::handle<Graphic3d_AttribBuffer> anAttribs =
          occ::down_cast<Graphic3d_AttribBuffer>(myPArray->Attributes()))
    {
      anAttribs->Invalidate(0, aVertexNum - 1);
    }
    if (!myPArray->Indices().IsNull())
    {
      myPArray->Indices()->NbElements = aEdgeNum;
    }
    if (!myPArray->Bounds().IsNull())
    {
      myPArray->Bounds()->NbBounds = aBoundNum;
    }
  }

  int aVertIndex = 0;
  for (int anArgIndex = theDesc->Lower(); anArgIndex <= theDesc->Upper();)
  {
    aCommand = theDesc->Value(anArgIndex);
    aCommand.LowerCase();
    if (!aCommand.IsAscii() || aCommand.IsEmpty())
    {
      break;
    }

    // skip beautifiers (syntax is not actually validated)
    if (aCommand == "-deinterleaved" || aCommand == "-mutable" || aCommand.Value(1) == '('
        || aCommand.Value(1) == ')' || aCommand.Value(1) == ',')
    {
      ++anArgIndex;
    }
    // vertex command
    else if (CheckInputCommand("v", theDesc, anArgIndex, 3, anArgsCount))
    {
      const NCollection_Vec3<float> aVert((float)theDesc->Value(anArgIndex - 3).RealValue(),
                                          (float)theDesc->Value(anArgIndex - 2).RealValue(),
                                          (float)theDesc->Value(anArgIndex - 1).RealValue());
      if ((anArrayFlags & Graphic3d_ArrayFlags_AttribsDeinterleaved) != 0
          || (anArrayFlags & Graphic3d_ArrayFlags_AttribsMutable) != 0)
      {
        ++aVertIndex;
        myPArray->SetVertice(aVertIndex, aVert.x(), aVert.y(), aVert.z());
      }
      else
      {
        aVertIndex = myPArray->AddVertex(aVert);
      }

      // vertex has a normal or normal with color or texel
      if (CheckInputCommand("n", theDesc, anArgIndex, 3, anArgsCount))
      {
        const NCollection_Vec3<float> aNorm((float)theDesc->Value(anArgIndex - 3).RealValue(),
                                            (float)theDesc->Value(anArgIndex - 2).RealValue(),
                                            (float)theDesc->Value(anArgIndex - 1).RealValue());
        myPArray->SetVertexNormal(aVertIndex, aNorm.x(), aNorm.y(), aNorm.z());
      }

      if (CheckInputCommand("c", theDesc, anArgIndex, 3, anArgsCount))
      {
        const NCollection_Vec3<double> aCol(theDesc->Value(anArgIndex - 3).RealValue(),
                                            theDesc->Value(anArgIndex - 2).RealValue(),
                                            theDesc->Value(anArgIndex - 1).RealValue());
        myPArray->SetVertexColor(aVertIndex, aCol.r(), aCol.g(), aCol.b());
      }
      if (CheckInputCommand("t", theDesc, anArgIndex, 2, anArgsCount))
      {
        const NCollection_Vec2<float> aTex((float)theDesc->Value(anArgIndex - 2).RealValue(),
                                           (float)theDesc->Value(anArgIndex - 1).RealValue());
        myPArray->SetVertexTexel(aVertIndex, aTex.x(), aTex.y());
      }
    }
    // bounds command
    else if (CheckInputCommand("b", theDesc, anArgIndex, 1, anArgsCount))
    {
      int aVertCount = theDesc->Value(anArgIndex - 1).IntegerValue();

      if (CheckInputCommand("c", theDesc, anArgIndex, 3, anArgsCount))
        myPArray->AddBound(aVertCount,
                           theDesc->Value(anArgIndex - 3).RealValue(),
                           theDesc->Value(anArgIndex - 2).RealValue(),
                           theDesc->Value(anArgIndex - 1).RealValue());

      else
        myPArray->AddBound(aVertCount);
    }
    // edge command
    else if (CheckInputCommand("e", theDesc, anArgIndex, 1, anArgsCount))
    {
      const int anEdge = theDesc->Value(anArgIndex - 1).IntegerValue();
      myPArray->AddEdge(anEdge);
    }
    // unknown command
    else
    {
      Message::SendFail() << "Syntax error: unknown argument '" << theDesc->Value(anArgIndex)
                          << "'";
      return false;
    }
  }
  return true;
}

//=================================================================================================

void MyPArrayObject::SetColor(const Quantity_Color& theColor)
{
  AIS_InteractiveObject::SetColor(theColor);
  setColorForShadingAspect(theColor);
  if (myMarkerAspect)
  {
    myMarkerAspect->SetColor(theColor);
  }
  SynchronizeAspects();
}

void MyPArrayObject::ComputeSelection(const occ::handle<SelectMgr_Selection>& theSelection,
                                      const int                               theMode)
{
  if (theMode != 0 || myPArray.IsNull())
  {
    return;
  }

  occ::handle<SelectMgr_EntityOwner> anOwner = new SelectMgr_EntityOwner(this);
  if (occ::handle<Graphic3d_ArrayOfTriangles> aTris =
        occ::down_cast<Graphic3d_ArrayOfTriangles>(myPArray))
  {
    occ::handle<Select3D_SensitivePrimitiveArray> aSensitive =
      new Select3D_SensitivePrimitiveArray(anOwner);
    aSensitive->InitTriangulation(myPArray->Attributes(),
                                  myPArray->Indices(),
                                  TopLoc_Location(),
                                  true);
    theSelection->Add(aSensitive);
  }
  else if (occ::handle<Graphic3d_ArrayOfSegments> aSegs =
             occ::down_cast<Graphic3d_ArrayOfSegments>(myPArray))
  {
    if (aSegs->EdgeNumber() > 0)
    {
      for (int aPntIter = 1; aPntIter <= aSegs->EdgeNumber(); aPntIter += 2)
      {
        occ::handle<Select3D_SensitiveSegment> aSeg =
          new Select3D_SensitiveSegment(anOwner,
                                        aSegs->Vertice(aSegs->Edge(aPntIter)),
                                        aSegs->Vertice(aSegs->Edge(aPntIter + 1)));
        aSeg->SetSensitivityFactor(4);
        theSelection->Add(aSeg);
      }
    }
    else
    {
      for (int aPntIter = 1; aPntIter <= aSegs->VertexNumber(); aPntIter += 2)
      {
        occ::handle<Select3D_SensitiveSegment> aSeg =
          new Select3D_SensitiveSegment(anOwner,
                                        aSegs->Vertice(aPntIter),
                                        aSegs->Vertice(aPntIter + 1));
        aSeg->SetSensitivityFactor(4);
        theSelection->Add(aSeg);
      }
    }
  }
  else
  {
    occ::handle<Select3D_SensitivePrimitiveArray> aSensitive =
      new Select3D_SensitivePrimitiveArray(anOwner);
    aSensitive->SetSensitivityFactor(8);
    aSensitive->InitPoints(myPArray->Attributes(), myPArray->Indices(), TopLoc_Location(), true);
    theSelection->Add(aSensitive);
  }
}

bool MyPArrayObject::CheckInputCommand(
  const TCollection_AsciiString&                                   theCommand,
  const occ::handle<NCollection_HArray1<TCollection_AsciiString>>& theArgsArray,
  int&                                                             theArgIndex,
  int                                                              theArgCount,
  int                                                              theMaxArgs)
{
  // check if there is more elements than expected
  if (theArgIndex >= theMaxArgs)
    return false;

  TCollection_AsciiString aStrCommand = theArgsArray->Value(theArgIndex);
  aStrCommand.LowerCase();
  if (aStrCommand.Search(theCommand) != 1 || theArgIndex + (theArgCount - 1) >= theMaxArgs)
    return false;

  // go to the first data element
  theArgIndex++;

  // check data if it can be converted to numeric
  for (int aElement = 0; aElement < theArgCount; aElement++, theArgIndex++)
  {
    aStrCommand = theArgsArray->Value(theArgIndex);
    if (!aStrCommand.IsRealValue(true))
      return false;
  }

  return true;
}

//=================================================================================================

void MyPArrayObject::setColorForShadingAspect(const Quantity_Color& theColor)
{
  if (myDrawer->SetupOwnShadingAspect())
  {
    replaceShadingAspect();
  }
  myDrawer->ShadingAspect()->SetColor(theColor);
}

//=================================================================================================

void MyPArrayObject::replaceShadingAspect()
{
  if (!myDrawer->Link())
  {
    return;
  }
  NCollection_DataMap<occ::handle<Graphic3d_Aspects>, occ::handle<Graphic3d_Aspects>>
    anAspectReplacementMap;
  anAspectReplacementMap.Bind(myDrawer->Link()->ShadingAspect()->Aspect(),
                              myDrawer->ShadingAspect()->Aspect());
  replaceAspects(anAspectReplacementMap);
}

//=============================================================================
// function : VDrawPArray
// purpose  : Draws primitives array from list of vertexes, bounds, edges
//=============================================================================

static int VDrawPArray(Draw_Interpretor& di, int argc, const char** argv)
{
  occ::handle<AIS_InteractiveContext> aContextAIS = ViewerTest::GetAISContext();
  if (aContextAIS.IsNull())
  {
    Message::SendFail("Error: no active Viewer");
    return 1;
  }
  else if (argc < 3)
  {
    Message::SendFail("Syntax error: wrong number of arguments");
    return 1;
  }

  // read the arguments
  int                     anArgIndex = 1;
  TCollection_AsciiString aName(argv[anArgIndex++]);
  TCollection_AsciiString anArrayType(argv[anArgIndex++]);
  anArrayType.LowerCase();
  occ::handle<MyPArrayObject> aPObject;
  if (anArrayType == "-shape")
  {
    const char*                              aShapeName = argv[anArgIndex++];
    TopoDS_Shape                             aShape     = DBRep::Get(aShapeName);
    occ::handle<Graphic3d_ArrayOfPrimitives> aTris      = StdPrs_ShadedShape::FillTriangles(aShape);
    if (aShape.IsNull())
    {
      Message::SendFail() << "Syntax error: shape '" << aShapeName << "' is not found";
      return 1;
    }
    else if (aTris.IsNull())
    {
      Message::SendFail() << "Syntax error: shape '" << aShapeName << "' is not triangulated";
      return 1;
    }

    aPObject = new MyPArrayObject(aTris);
    ViewerTest::Display(aName, aPObject);
    return 0;
  }
  else if (anArrayType == "-patch" || anArrayType == "-modify" || anArrayType == "-edit")
  {
    if (argc >= 3)
    {
      anArrayType = argv[anArgIndex++];
      anArrayType.LowerCase();
    }

    if (GetMapOfAIS().IsBound2(aName))
    {
      aPObject = occ::down_cast<MyPArrayObject>(GetMapOfAIS().Find2(aName));
    }
    if (aPObject.IsNull())
    {
      Message::SendFail() << "Syntax error: object '" << aName << "' cannot be found";
      return 1;
    }
  }

  bool hasVertex = false;

  Graphic3d_TypeOfPrimitiveArray aPrimType = Graphic3d_TOPA_UNDEFINED;
  if (anArrayType == "points")
  {
    aPrimType = Graphic3d_TOPA_POINTS;
  }
  else if (anArrayType == "segments")
  {
    aPrimType = Graphic3d_TOPA_SEGMENTS;
  }
  else if (anArrayType == "polylines")
  {
    aPrimType = Graphic3d_TOPA_POLYLINES;
  }
  else if (anArrayType == "triangles")
  {
    aPrimType = Graphic3d_TOPA_TRIANGLES;
  }
  else if (anArrayType == "trianglefans")
  {
    aPrimType = Graphic3d_TOPA_TRIANGLEFANS;
  }
  else if (anArrayType == "trianglestrips")
  {
    aPrimType = Graphic3d_TOPA_TRIANGLESTRIPS;
  }
  else if (anArrayType == "quads")
  {
    aPrimType = Graphic3d_TOPA_QUADRANGLES;
  }
  else if (anArrayType == "quadstrips")
  {
    aPrimType = Graphic3d_TOPA_QUADRANGLESTRIPS;
  }
  else if (anArrayType == "polygons")
  {
    aPrimType = Graphic3d_TOPA_POLYGONS;
  }
  if (aPrimType == Graphic3d_TOPA_UNDEFINED)
  {
    Message::SendFail("Syntax error: unexpected type of primitives array");
    return 1;
  }

  int                                                       aLowerArg = anArgIndex;
  occ::handle<NCollection_HArray1<TCollection_AsciiString>> anArgsArray =
    new NCollection_HArray1<TCollection_AsciiString>(0, argc - 3);
  for (; anArgIndex < argc; ++anArgIndex)
  {
    TCollection_AsciiString aCommand(argv[anArgIndex]);
    aCommand.LowerCase();
    if (!aCommand.IsAscii())
    {
      di << "Unexpected argument: #" << anArgIndex - 1 << " , "
         << "should be an array element: 'v', 'b', 'e' \n";
      break;
    }

    if (aCommand == "v")
    {
      hasVertex = true;
    }

    anArgsArray->SetValue(anArgIndex - aLowerArg, aCommand);
  }

  if (!hasVertex)
  {
    di << "You should pass any vertices in the list of array elements\n";
    return 1;
  }

  occ::handle<Graphic3d_AspectMarker3d> anAspPoints;
  if (aPrimType == Graphic3d_TOPA_POINTS)
  {
    anAspPoints = new Graphic3d_AspectMarker3d(Aspect_TOM_POINT, Quantity_NOC_YELLOW, 1.0f);
  }

  // create primitives array object
  if (aPObject.IsNull())
  {
    // register the object in map
    aPObject = new MyPArrayObject(aPrimType, anArgsArray, anAspPoints);
    VDisplayAISObject(aName, aPObject);
  }
  else
  {
    aPObject->Init(aPrimType, anArgsArray, anAspPoints, true);
    ViewerTest::CurrentView()->Redraw();
  }
  return 0;
}

namespace
{
//! Auxiliary function for parsing translation vector - either 2D or 3D.
static int parseTranslationVec(int theArgNb, const char** theArgVec, gp_Vec& theVec)
{
  if (theArgNb < 2)
  {
    return 0;
  }

  TCollection_AsciiString anX(theArgVec[0]);
  TCollection_AsciiString anY(theArgVec[1]);
  if (!anX.IsRealValue(true) || !anY.IsRealValue(true))
  {
    return 0;
  }

  theVec.SetX(anX.RealValue());
  theVec.SetY(anY.RealValue());
  if (theArgNb >= 3)
  {
    TCollection_AsciiString anZ(theArgVec[2]);
    if (anZ.IsRealValue(true))
    {
      theVec.SetZ(anZ.RealValue());
      return 3;
    }
  }
  return 2;
}
} // namespace

//=======================================================================
// function : VSetLocation
// purpose  : Change location of AIS interactive object
//=======================================================================

static int VSetLocation(Draw_Interpretor& theDI, int theArgNb, const char** theArgVec)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  ViewerTest_AutoUpdater              anUpdateTool(aContext, ViewerTest::CurrentView());
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  bool                               toPrintInfo = true;
  occ::handle<AIS_InteractiveObject> anObj;
  TCollection_AsciiString            aCmdName(theArgVec[0]);
  aCmdName.LowerCase();
  for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
  {
    TCollection_AsciiString anArg = theArgVec[anArgIter];
    anArg.LowerCase();
    if (anUpdateTool.parseRedrawMode(anArg))
    {
      continue;
    }
    else if (anObj.IsNull())
    {
      const TCollection_AsciiString aName(theArgVec[anArgIter]);
      GetMapOfAIS().Find2(aName, anObj);
      if (anObj.IsNull())
      {
        Message::SendFail() << "Error: object '" << aName << "' is not displayed";
        return 1;
      }
    }
    else if (anArg == "-reset")
    {
      toPrintInfo = false;
      aContext->SetLocation(anObj, gp_Trsf());
    }
    else if (anArg == "-copyfrom" || anArg == "-copy")
    {
      if (anArgIter + 1 >= theArgNb)
      {
        Message::SendFail() << "Syntax error at '" << anArg << "'";
        return 1;
      }

      const TCollection_AsciiString      aName2(theArgVec[anArgIter + 1]);
      occ::handle<AIS_InteractiveObject> anObj2;
      GetMapOfAIS().Find2(aName2, anObj2);
      if (anObj2.IsNull())
      {
        Message::SendFail() << "Error: object '" << aName2 << "' is not displayed";
        return 1;
      }

      ++anArgIter;
      aContext->SetLocation(anObj, anObj2->LocalTransformation());
    }
    else if (anArg == "-rotate" || anArg == "-prerotate")
    {
      toPrintInfo = false;
      if (anArgIter + 7 >= theArgNb)
      {
        Message::SendFail() << "Syntax error at '" << anArg << "'";
        return 1;
      }

      gp_Trsf aTrsf;
      aTrsf.SetRotation(gp_Ax1(gp_Pnt(Draw::Atof(theArgVec[anArgIter + 1]),
                                      Draw::Atof(theArgVec[anArgIter + 2]),
                                      Draw::Atof(theArgVec[anArgIter + 3])),
                               gp_Vec(Draw::Atof(theArgVec[anArgIter + 4]),
                                      Draw::Atof(theArgVec[anArgIter + 5]),
                                      Draw::Atof(theArgVec[anArgIter + 6]))),
                        Draw::Atof(theArgVec[anArgIter + 7]) * (M_PI / 180.0));
      anArgIter += 7;

      if (anArg == "-prerotate")
      {
        aTrsf = anObj->LocalTransformation() * aTrsf;
      }
      else
      {
        aTrsf = aTrsf * anObj->LocalTransformation();
      }
      aContext->SetLocation(anObj, aTrsf);
    }
    else if (anArg == "-translate" || anArg == "-pretranslate")
    {
      toPrintInfo = false;
      gp_Vec aLocVec;
      int    aNbParsed =
        parseTranslationVec(theArgNb - anArgIter - 1, theArgVec + anArgIter + 1, aLocVec);
      anArgIter += aNbParsed;
      if (aNbParsed == 0)
      {
        Message::SendFail() << "Syntax error at '" << anArg << "'";
        return 1;
      }

      gp_Trsf aTrsf;
      aTrsf.SetTranslationPart(aLocVec);
      if (anArg == "-pretranslate")
      {
        aTrsf = anObj->LocalTransformation() * aTrsf;
      }
      else
      {
        aTrsf = aTrsf * anObj->LocalTransformation();
      }
      aContext->SetLocation(anObj, aTrsf);
    }
    else if (anArg == "-scale" || anArg == "-prescale" || anArg == "-setscale")
    {
      toPrintInfo = false;
      gp_XYZ aScaleLoc;
      double aScale       = 1.0;
      bool   toPrintScale = true;
      bool   hasScaleLoc  = false;
      if (anArgIter + 4 < theArgNb)
      {
        TCollection_AsciiString aScaleArgs[4] = {TCollection_AsciiString(theArgVec[anArgIter + 1]),
                                                 TCollection_AsciiString(theArgVec[anArgIter + 2]),
                                                 TCollection_AsciiString(theArgVec[anArgIter + 3]),
                                                 TCollection_AsciiString(theArgVec[anArgIter + 4])};
        int                     aScaleArgIter = 0;
        for (; aScaleArgIter < 4; ++aScaleArgIter)
        {
          if (!aScaleArgs[aScaleArgIter].IsRealValue(true))
          {
            break;
          }
        }
        if (aScaleArgIter == 4)
        {
          aScaleLoc.SetCoord(aScaleArgs[0].RealValue(),
                             aScaleArgs[1].RealValue(),
                             aScaleArgs[2].RealValue());
          aScale = aScaleArgs[3].RealValue();
          anArgIter += 4;
          toPrintScale = false;
          hasScaleLoc  = true;
        }
        else if (aScaleArgIter >= 1)
        {
          aScale = aScaleArgs[0].RealValue();
          ++anArgIter;
          toPrintScale = false;
        }
      }
      else if (anArgIter + 1 < theArgNb)
      {
        TCollection_AsciiString aScaleArg(theArgVec[anArgIter + 1]);
        if (aScaleArg.IsRealValue(true))
        {
          aScale = aScaleArg.RealValue();
          ++anArgIter;
          toPrintScale = false;
        }
      }

      if (toPrintScale)
      {
        if (anArg == "-setscale" || anArg == "-prescale")
        {
          Message::SendFail() << "Syntax error at '" << anArg << "'";
          return 1;
        }

        char aText[1024];
        Sprintf(aText, "%g ", anObj->LocalTransformation().ScaleFactor());
        theDI << aText;
        continue;
      }

      if (anArg == "-setscale")
      {
        gp_Trsf aTrsf = anObj->LocalTransformation();
        if (hasScaleLoc)
        {
          aTrsf.SetScale(aScaleLoc, aScale);
        }
        else
        {
          aTrsf.SetScaleFactor(aScale);
        }
        aContext->SetLocation(anObj, aTrsf);
      }
      else
      {
        gp_Trsf aTrsf;
        if (hasScaleLoc)
        {
          aTrsf.SetScale(aScaleLoc, aScale);
        }
        else
        {
          aTrsf.SetScaleFactor(aScale);
        }

        if (anArg == "-prescale")
        {
          aTrsf = anObj->LocalTransformation() * aTrsf;
        }
        else
        {
          aTrsf = aTrsf * anObj->LocalTransformation();
        }
        aContext->SetLocation(anObj, aTrsf);
      }
    }
    else if (anArg == "-mirror" || anArg == "-premirror")
    {
      toPrintInfo = false;
      if (anArgIter + 6 >= theArgNb)
      {
        Message::SendFail() << "Syntax error at '" << anArg << "'";
        return 1;
      }

      gp_Trsf aTrsf;
      aTrsf.SetMirror(gp_Ax2(gp_Pnt(Draw::Atof(theArgVec[theArgNb - 6]),
                                    Draw::Atof(theArgVec[theArgNb - 5]),
                                    Draw::Atof(theArgVec[theArgNb - 4])),
                             gp_Vec(Draw::Atof(theArgVec[theArgNb - 3]),
                                    Draw::Atof(theArgVec[theArgNb - 2]),
                                    Draw::Atof(theArgVec[theArgNb - 1]))));
      anArgIter += 6;
      if (anArg == "-premirror")
      {
        aTrsf = anObj->LocalTransformation() * aTrsf;
      }
      else
      {
        aTrsf = aTrsf * anObj->LocalTransformation();
      }
      aContext->SetLocation(anObj, aTrsf);
    }
    else if (anArg == "-setrotation" || anArg == "-rotation")
    {
      toPrintInfo = false;
      if (anArgIter + 4 < theArgNb)
      {
        TCollection_AsciiString aQuatArgs[4] = {TCollection_AsciiString(theArgVec[anArgIter + 1]),
                                                TCollection_AsciiString(theArgVec[anArgIter + 2]),
                                                TCollection_AsciiString(theArgVec[anArgIter + 3]),
                                                TCollection_AsciiString(theArgVec[anArgIter + 4])};
        int                     aQuatArgIter = 0;
        for (; aQuatArgIter < 4; ++aQuatArgIter)
        {
          if (!aQuatArgs[aQuatArgIter].IsRealValue(true))
          {
            break;
          }
        }

        if (aQuatArgIter == 4)
        {
          anArgIter += 4;
          const gp_Quaternion aQuat(aQuatArgs[0].RealValue(),
                                    aQuatArgs[1].RealValue(),
                                    aQuatArgs[2].RealValue(),
                                    aQuatArgs[3].RealValue());
          gp_Trsf             aTrsf = anObj->LocalTransformation();
          aTrsf.SetRotationPart(aQuat);
          aContext->SetLocation(anObj, aTrsf);
          continue;
        }
        else if (anArg == "-setrotation")
        {
          Message::SendFail() << "Syntax error at '" << anArg << "'";
          return 1;
        }
      }

      char                aText[1024];
      const gp_Quaternion aQuat = anObj->LocalTransformation().GetRotation();
      Sprintf(aText, "%g %g %g %g ", aQuat.X(), aQuat.Y(), aQuat.Z(), aQuat.W());
      theDI << aText;
    }
    else if (anArg == "-setlocation" || anArg == "-location")
    {
      toPrintInfo = false;
      gp_Vec aLocVec;
      int    aNbParsed =
        parseTranslationVec(theArgNb - anArgIter - 1, theArgVec + anArgIter + 1, aLocVec);
      anArgIter += aNbParsed;
      if (aNbParsed != 0)
      {
        gp_Trsf aTrsf = anObj->LocalTransformation();
        aTrsf.SetTranslationPart(aLocVec);
        aContext->SetLocation(anObj, aTrsf);
      }
      else if (anArg == "-setlocation")
      {
        Message::SendFail() << "Syntax error at '" << anArg << "'";
        return 1;
      }

      char         aText[1024];
      const gp_XYZ aLoc = anObj->LocalTransformation().TranslationPart();
      Sprintf(aText, "%g %g %g ", aLoc.X(), aLoc.Y(), aLoc.Z());
      theDI << aText;
    }
    else if (aCmdName == "vsetlocation")
    {
      // compatibility with old syntax
      gp_Vec aLocVec;
      int    aNbParsed = parseTranslationVec(theArgNb - anArgIter, theArgVec + anArgIter, aLocVec);
      if (aNbParsed == 0)
      {
        Message::SendFail() << "Syntax error at '" << anArg << "'";
        return 1;
      }
      anArgIter = anArgIter + aNbParsed - 1;

      gp_Trsf aTrsf;
      aTrsf.SetTranslationPart(aLocVec);
      aContext->SetLocation(anObj, aTrsf);
      toPrintInfo = false;
    }
    else
    {
      Message::SendFail() << "Error: unknown argument '" << anArg << "'";
      return 1;
    }
  }

  if (anObj.IsNull())
  {
    Message::SendFail("Syntax error - wrong number of arguments");
    return 1;
  }
  else if (!toPrintInfo)
  {
    return 0;
  }

  const gp_Trsf       aTrsf = anObj->LocalTransformation();
  const gp_XYZ        aLoc  = aTrsf.TranslationPart();
  const gp_Quaternion aRot  = aTrsf.GetRotation();
  char                aText[4096];
  Sprintf(aText,
          "Location: %g %g %g\n"
          "Rotation: %g %g %g %g\n"
          "Scale:    %g\n",
          aLoc.X(),
          aLoc.Y(),
          aLoc.Z(),
          aRot.X(),
          aRot.Y(),
          aRot.Z(),
          aRot.W(),
          aTrsf.ScaleFactor());
  theDI << aText;
  return 0;
}

//! Find displayed object.
static occ::handle<AIS_InteractiveObject> findConnectedObject(
  const TCollection_AsciiString& theName)
{
  occ::handle<AIS_InteractiveObject> aPrs;
  if (!GetMapOfAIS().Find2(theName, aPrs))
  {
    return occ::handle<AIS_InteractiveObject>();
  }
  if (occ::handle<AIS_ConnectedInteractive> aConnected =
        occ::down_cast<AIS_ConnectedInteractive>(aPrs))
  {
    return aConnected;
  }
  else if (occ::handle<AIS_MultipleConnectedInteractive> aMultiCon =
             occ::down_cast<AIS_MultipleConnectedInteractive>(aPrs))
  {
    return aMultiCon;
  }

  // replace already displayed object with connected one
  TheAISContext()->Remove(aPrs, false);
  occ::handle<AIS_ConnectedInteractive> aConnected = new AIS_ConnectedInteractive();
  if (aPrs->HasDisplayMode())
  {
    aConnected->SetDisplayMode(aPrs->DisplayMode());
  }
  aConnected->Connect(aPrs, aPrs->LocalTransformationGeom());
  if (!aPrs->TransformPersistence().IsNull())
  {
    aConnected->SetTransformPersistence(aPrs->TransformPersistence());
  }
  ViewerTest::Display(theName, aConnected, false);
  return aConnected;
}

//===============================================================================================
// function : VConnect
// purpose  : Creates and displays AIS_ConnectedInteractive object from input object and location
//===============================================================================================
static int VConnect(Draw_Interpretor& /*di*/, int argc, const char** argv)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }
  if (argc < 6)
  {
    Message::SendFail("Syntax error: expect at least 5 arguments");
    return 1;
  }

  // Get values
  int                                           anArgIter = 1;
  const TCollection_AsciiString                 aName(argv[anArgIter++]);
  occ::handle<AIS_MultipleConnectedInteractive> aMultiConObject;
  TCollection_AsciiString                       aColorString(argv[argc - 1]);
  Quantity_Color                                aColor;
  bool                                          hasColor = false;
  if (aColorString.Search("color=") != -1)
  {
    hasColor = true;
    aColorString.Remove(1, 6);
    if (!Quantity_Color::ColorFromName(aColorString.ToCString(), aColor))
    {
      Message::SendFail() << "Syntax error at " << aColorString;
      return 1;
    }
  }

  const int aNbShapes = hasColor ? (argc - 1) : argc;
  for (int i = 5; i < aNbShapes; ++i)
  {
    TCollection_AsciiString            anOriginObjectName(argv[i]);
    occ::handle<AIS_InteractiveObject> anObject;
    if (aName.IsEqual(anOriginObjectName))
    {
      Message::SendFail("Syntax error: equal names for connected objects");
      continue;
    }

    anObject = findConnectedObject(anOriginObjectName);
    if (anObject.IsNull())
    {
      TopoDS_Shape aTDShape = DBRep::Get(anOriginObjectName);
      if (aTDShape.IsNull())
      {
        Message::SendFail() << "Syntax error: object " << anOriginObjectName << " doesn't exist";
        return 1;
      }
      occ::handle<AIS_Shape>                aShapePrs      = new AIS_Shape(aTDShape);
      occ::handle<AIS_ConnectedInteractive> aConnectedOrig = new AIS_ConnectedInteractive();
      aConnectedOrig->Connect(aShapePrs);
      anObject = aConnectedOrig;

      aContext->Load(anObject);
      anObject->SetColor(aColor);
    }

    if (aMultiConObject.IsNull())
    {
      aMultiConObject = new AIS_MultipleConnectedInteractive();
    }

    aMultiConObject->Connect(anObject);
  }
  if (aMultiConObject.IsNull())
  {
    Message::SendFail("Syntax error: can't connect input objects");
    return 1;
  }

  // Create transformation
  gp_Trsf aTrsf;
  aTrsf.SetTranslationPart(gp_Vec(Draw::Atof(argv[anArgIter + 0]),
                                  Draw::Atof(argv[anArgIter + 1]),
                                  Draw::Atof(argv[anArgIter + 2])));
  TopLoc_Location aLocation(aTrsf);
  anArgIter += 3;

  aMultiConObject->SetLocalTransformation(aTrsf);

  ViewerTest::Display(aName, aMultiConObject, true);
  return 0;
}

//===============================================================================================
// function : VConnectTo
// purpose  : Creates and displays AIS_ConnectedInteractive object from input object and location
//===============================================================================================
static int VConnectTo(Draw_Interpretor& /*di*/, int argc, const char** argv)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  ViewerTest_AutoUpdater              anUpdateTool(aContext, ViewerTest::CurrentView());
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }
  if (argc != 6 && argc != 7)
  {
    Message::SendFail("Syntax error: expect at least 5 arguments");
    return 1;
  }

  int                                anArgIter = 1;
  TCollection_AsciiString            aName(argv[anArgIter++]);
  occ::handle<AIS_InteractiveObject> anOriginObject;

  TCollection_AsciiString anOriginObjectName(argv[5]);
  if (aName.IsEqual(anOriginObjectName))
  {
    Message::SendFail("Syntax error: equal names for connected objects");
    return 1;
  }
  anOriginObject = findConnectedObject(anOriginObjectName);
  if (anOriginObject.IsNull())
  {
    TopoDS_Shape aTDShape = DBRep::Get(anOriginObjectName);
    if (aTDShape.IsNull())
    {
      Message::SendFail() << "Syntax error: object " << anOriginObjectName << " doesn't exist";
      return 1;
    }

    occ::handle<AIS_Shape>                aShapePrs      = new AIS_Shape(aTDShape);
    occ::handle<AIS_ConnectedInteractive> aConnectedOrig = new AIS_ConnectedInteractive();
    aConnectedOrig->Connect(aShapePrs);

    anOriginObject = aConnectedOrig;
    GetMapOfAIS().Bind(aConnectedOrig, anOriginObjectName);
  }

  // Create transformation
  gp_Trsf aTrsf;
  aTrsf.SetTranslationPart(gp_Vec(Draw::Atof(argv[anArgIter + 0]),
                                  Draw::Atof(argv[anArgIter + 1]),
                                  Draw::Atof(argv[anArgIter + 2])));
  anArgIter += 3;

  occ::handle<AIS_ConnectedInteractive> aConnected = new AIS_ConnectedInteractive();
  aConnected->Connect(anOriginObject, aTrsf);
  if (argc == 7)
  {
    TCollection_AsciiString anArg = argv[6];
    anArg.LowerCase();
    if (anArg == "-nodisplay")
    {
      // bind connected object without displaying it
      occ::handle<AIS_InteractiveObject> anObj;
      if (GetMapOfAIS().Find2(aName, anObj))
      {
        TheAISContext()->Remove(anObj, false);
        GetMapOfAIS().UnBind2(aName);
      }
      GetMapOfAIS().Bind(aConnected, aName);
      return 0;
    }

    if (!anUpdateTool.parseRedrawMode(anArg))
    {
      Message::SendFail() << "Syntax error: unknown argument '" << anArg << "'";
      return 1;
    }
  }

  ViewerTest::Display(aName, aConnected, false);
  return 0;
}

//=================================================================================================

static int VDisconnect(Draw_Interpretor& di, int argc, const char** argv)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  if (argc != 3)
  {
    Message::SendFail() << "Syntax error: wrong number of arguments.\nUsage: " << argv[0]
                        << " name object";
    return 1;
  }

  TCollection_AsciiString aName(argv[1]);
  TCollection_AsciiString anObject(argv[2]);
  int                     anObjectNumber = Draw::Atoi(argv[2]);

  // find objects
  NCollection_DoubleMap<occ::handle<AIS_InteractiveObject>, TCollection_AsciiString>& aMap =
    GetMapOfAIS();
  occ::handle<AIS_MultipleConnectedInteractive> anAssembly;
  if (!aMap.IsBound2(aName))
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  anAssembly = occ::down_cast<AIS_MultipleConnectedInteractive>(aMap.Find2(aName));
  if (anAssembly.IsNull())
  {
    di << "Not an assembly\n";
    return 1;
  }

  occ::handle<AIS_InteractiveObject> anIObj;
  if (!aMap.Find2(anObject, anIObj))
  {
    // try to interpret second argument as child number
    if (anObjectNumber > 0 && anObjectNumber <= anAssembly->Children().Size())
    {
      int aCounter = 1;
      for (NCollection_List<occ::handle<PrsMgr_PresentableObject>>::Iterator anIter(
             anAssembly->Children());
           anIter.More();
           anIter.Next())
      {
        if (aCounter == anObjectNumber)
        {
          anIObj = occ::down_cast<AIS_InteractiveObject>(anIter.Value());
          break;
        }
        ++aCounter;
      }
    }
    else
    {
      Message::SendFail("Error: no active viewer");
      return 1;
    }
  }

  aContext->Disconnect(anAssembly, anIObj);
  aContext->UpdateCurrentViewer();
  return 0;
}

//=================================================================================================

static int VAddConnected(Draw_Interpretor&, int argc, const char** argv)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  if (argc != 6)
  {
    Message::SendFail("Syntax error: expect 5 arguments");
    return 1;
  }

  const TCollection_AsciiString aName(argv[1]);
  const double                  aX = Draw::Atof(argv[2]);
  const double                  aY = Draw::Atof(argv[3]);
  const double                  aZ = Draw::Atof(argv[4]);
  const TCollection_AsciiString anObjectName(argv[5]);

  // find object
  NCollection_DoubleMap<occ::handle<AIS_InteractiveObject>, TCollection_AsciiString>& aMap =
    GetMapOfAIS();
  occ::handle<AIS_MultipleConnectedInteractive> anAssembly;
  {
    occ::handle<AIS_InteractiveObject> aPrs;
    aMap.Find2(aName, aPrs);
    anAssembly = occ::down_cast<AIS_MultipleConnectedInteractive>(aPrs);
    if (anAssembly.IsNull())
    {
      Message::SendFail() << "Syntax error: '" << aName << "' is not an assembly";
      return 1;
    }
  }

  occ::handle<AIS_InteractiveObject> anIObj = findConnectedObject(anObjectName);
  if (anIObj.IsNull())
  {
    Message::SendFail() << "Syntax error: '" << anObjectName << "' is not displayed";
    return 1;
  }

  gp_Trsf aTrsf;
  aTrsf.SetTranslation(gp_Vec(aX, aY, aZ));

  anAssembly->Connect(anIObj, aTrsf);
  TheAISContext()->Display(anAssembly, false);
  TheAISContext()->RecomputeSelectionOnly(anAssembly);
  aContext->UpdateCurrentViewer();
  return 0;
}

//=================================================================================================

static int VListConnected(Draw_Interpretor& /*di*/, int argc, const char** argv)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  if (argc != 2)
  {
    Message::SendFail() << "Syntax error: wrong number of arguments.\nUsage: " << argv[0]
                        << " name";
    return 1;
  }

  TCollection_AsciiString aName(argv[1]);

  // find object
  NCollection_DoubleMap<occ::handle<AIS_InteractiveObject>, TCollection_AsciiString>& aMap =
    GetMapOfAIS();
  occ::handle<AIS_MultipleConnectedInteractive> anAssembly;
  if (!aMap.IsBound2(aName))
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  anAssembly = occ::down_cast<AIS_MultipleConnectedInteractive>(aMap.Find2(aName));
  if (anAssembly.IsNull())
  {
    Message::SendFail("Syntax error: Not an assembly");
    return 1;
  }

  std::cout << "Children of " << aName << ":\n";

  int aCounter = 1;
  for (NCollection_List<occ::handle<PrsMgr_PresentableObject>>::Iterator anIter(
         anAssembly->Children());
       anIter.More();
       anIter.Next())
  {
    occ::handle<AIS_InteractiveObject> anObj =
      occ::down_cast<AIS_InteractiveObject>(anIter.Value());
    if (GetMapOfAIS().IsBound1(anObj))
    {
      TCollection_AsciiString aCuurrentName = GetMapOfAIS().Find1(anObj);
      std::cout << aCounter << ")  " << aCuurrentName << "    ("
                << anIter.Value()->DynamicType()->Name() << ")";
    }

    std::cout << aCounter << ")  " << anIter.Value()->DynamicType()->Name();

    occ::handle<AIS_ConnectedInteractive> aConnected =
      occ::down_cast<AIS_ConnectedInteractive>(anIter.Value());
    if (!aConnected.IsNull() && !aConnected->ConnectedTo().IsNull()
        && aMap.IsBound1(aConnected->ConnectedTo()))
    {
      std::cout << " connected to " << aMap.Find1(aConnected->ConnectedTo());
    }
    std::cout << std::endl;

    ++aCounter;
  }

  return 0;
}

//=================================================================================================

static int VChild(Draw_Interpretor&, int theNbArgs, const char** theArgVec)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  int                                toAdd = -1;
  occ::handle<AIS_InteractiveObject> aParent;
  bool                               hasActions    = false;
  int                                toInheritTrsf = -1;
  ViewerTest_AutoUpdater             anUpdateTool(aContext, ViewerTest::CurrentView());
  for (int anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anUpdateTool.parseRedrawMode(anArg))
    {
      continue;
    }
    else if (anArg == "-add")
    {
      toAdd = 1;
      continue;
    }
    else if (anArg == "-remove")
    {
      toAdd = 0;
      continue;
    }
    else if (anArg == "-inheritparenttrsf" || anArg == "-inheritparentloc"
             || anArg == "-inheritparentlocation" || anArg == "-inheritparent"
             || anArg == "-noinheritparenttrsf" || anArg == "-noinheritparentloc"
             || anArg == "-noinheritparentlocation" || anArg == "-noinheritparent"
             || anArg == "-ignoreparenttrsf" || anArg == "-ignoreparentloc"
             || anArg == "-ignoreparentlocation" || anArg == "-ignoreparent")
    {
      bool aVal = true;
      if (anArgIter + 1 < theNbArgs && Draw::ParseOnOff(theArgVec[anArgIter + 1], aVal))
      {
        ++anArgIter;
      }
      if (anArg.StartsWith("-no") || anArg.StartsWith("-ignore"))
      {
        aVal = !aVal;
      }
      toInheritTrsf = aVal ? 1 : 0;
      continue;
    }

    occ::handle<AIS_InteractiveObject> aChild;
    if (!GetMapOfAIS().Find2(theArgVec[anArgIter], aChild))
    {
      Message::SendFail() << "Syntax error: object '" << theArgVec[anArgIter] << "' is not found";
      return 1;
    }

    if (aParent.IsNull())
    {
      aParent = aChild;
    }
    else if (toAdd == -1)
    {
      Message::SendFail("Syntax error: no action specified");
      return 1;
    }
    else
    {
      hasActions = true;
      if (toAdd == 1)
      {
        if (toInheritTrsf == 0)
          aParent->AddChildWithCurrentTransformation(aChild);
        else
          aParent->AddChild(aChild);
      }
      else
      {
        if (toInheritTrsf == 0)
          aParent->RemoveChildWithRestoreTransformation(aChild);
        else
          aParent->RemoveChild(aChild);
      }
    }
  }
  if (!hasActions)
  {
    Message::SendFail("Syntax error: not enough arguments");
    return 1;
  }
  return 0;
}

//=================================================================================================

static int VParent(Draw_Interpretor&, int theNbArgs, const char** theArgVec)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  if (theNbArgs < 2)
  {
    Message::SendFail("Syntax error: wrong number of arguments");
    return 1;
  }

  TCollection_AsciiString            aName(theArgVec[1]);
  occ::handle<AIS_InteractiveObject> aParent;
  if (!GetMapOfAIS().Find2(theArgVec[1], aParent))
  {
    Message::SendFail() << "Syntax error: object '" << theArgVec[1] << "' is not found";
    return 1;
  }

  ViewerTest_AutoUpdater anUpdateTool(aContext, ViewerTest::CurrentView());
  for (int anArgIter = 2; anArgIter < theNbArgs; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anArg == "-ignorevisu")
      aParent->SetPropagateVisualState(false);
  }
  return 0;
}

//=================================================================================================

static int VSetSelectionMode(Draw_Interpretor& /*di*/, int theNbArgs, const char** theArgv)
{
  // Check errors
  occ::handle<AIS_InteractiveContext> anAISContext = ViewerTest::GetAISContext();
  if (anAISContext.IsNull())
  {
    Message::SendFail("Error: no active Viewer");
    return 1;
  }

  NCollection_Sequence<TCollection_AsciiString> anObjNames;
  int                                           aSelectionMode = -1;
  bool                                          toTurnOn       = true;
  AIS_SelectionModesConcurrency aSelModeConcurrency = AIS_SelectionModesConcurrency_GlobalOrLocal;
  for (int anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
  {
    TCollection_AsciiString anArgCase(theArgv[anArgIter]);
    anArgCase.LowerCase();
    if (anArgCase == "-set" || anArgCase == "-replace" || anArgCase == "-single"
        || anArgCase == "-exclusive")
    {
      aSelModeConcurrency = AIS_SelectionModesConcurrency_Single;
    }
    else if (anArgCase == "-add" || anArgCase == "-combine" || anArgCase == "-combination"
             || anArgCase == "-multiple")
    {
      aSelModeConcurrency = AIS_SelectionModesConcurrency_Multiple;
    }
    else if (anArgCase == "-globalorlocal" || anArgCase == "-localorglobal")
    {
      aSelModeConcurrency = AIS_SelectionModesConcurrency_GlobalOrLocal;
    }
    else
    {
      anObjNames.Append(theArgv[anArgIter]);
    }
  }
  if (anObjNames.Size() < 2 || !Draw::ParseOnOff(anObjNames.Last().ToCString(), toTurnOn))
  {
    Message::SendFail("Syntax error: wrong number of arguments");
    return 1;
  }
  anObjNames.Remove(anObjNames.Upper());
  {
    const TCollection_AsciiString aSelModeString = anObjNames.Last();
    anObjNames.Remove(anObjNames.Upper());
    TopAbs_ShapeEnum aShapeType = TopAbs_SHAPE;
    if (aSelModeString.IsIntegerValue())
    {
      aSelectionMode = aSelModeString.IntegerValue();
    }
    else if (TopAbs::ShapeTypeFromString(aSelModeString.ToCString(), aShapeType))
    {
      aSelectionMode = AIS_Shape::SelectionMode(aShapeType);
    }
    else
    {
      Message::SendFail() << "Syntax error: unknown selection mode '" << aSelModeString << "'";
      return 1;
    }
  }

  NCollection_List<occ::handle<AIS_InteractiveObject>> aTargetIOs;
  for (NCollection_Sequence<TCollection_AsciiString>::Iterator anObjIter(anObjNames);
       anObjIter.More();
       anObjIter.Next())
  {
    const TCollection_AsciiString&     aNameIO = anObjIter.Value();
    occ::handle<AIS_InteractiveObject> anIO;
    GetMapOfAIS().Find2(aNameIO, anIO);
    if (anIO.IsNull())
    {
      Message::SendFail() << "Syntax error: undefined presentable object " << aNameIO;
      return 1;
    }
    aTargetIOs.Append(anIO);
  }
  if (aTargetIOs.IsEmpty())
  {
    anAISContext->DisplayedObjects(aTargetIOs);
  }

  for (NCollection_List<occ::handle<AIS_InteractiveObject>>::Iterator aTargetIt(aTargetIOs);
       aTargetIt.More();
       aTargetIt.Next())
  {
    const occ::handle<AIS_InteractiveObject>& anIO = aTargetIt.Value();
    anAISContext->SetSelectionModeActive(anIO, aSelectionMode, toTurnOn, aSelModeConcurrency);
  }
  return 0;
}

//=================================================================================================

static int VSelectionNext(Draw_Interpretor& /*theDI*/,
                          int /*theArgsNb*/,
                          const char** /*theArgVec*/)
{
  // Check errors
  occ::handle<AIS_InteractiveContext> anAISContext = ViewerTest::GetAISContext();
  occ::handle<V3d_View>               aView        = ViewerTest::CurrentView();

  if (anAISContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  anAISContext->HilightNextDetected(aView);
  return 0;
}

//=================================================================================================

static int VSelectionPrevious(Draw_Interpretor& /*theDI*/,
                              int /*theArgsNb*/,
                              const char** /*theArgVec*/)
{
  // Check errors
  occ::handle<AIS_InteractiveContext> anAISContext = ViewerTest::GetAISContext();
  occ::handle<V3d_View>               aView        = ViewerTest::CurrentView();

  if (anAISContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  anAISContext->HilightPreviousDetected(aView);
  return 0;
}

//===========================================================================
// function : VTriangle
// Draw arg : vtriangle Name PointName PointName PointName
// purpose  : creates and displays Triangle
//===========================================================================
static int VTriangle(Draw_Interpretor& /*di*/, int argc, const char** argv)
{
  const bool                               isTri = TCollection_AsciiString(argv[0]) == "vtriangle";
  occ::handle<Graphic3d_ArrayOfPrimitives> aPrims;
  if (isTri)
  {
    aPrims = new Graphic3d_ArrayOfTriangles(3);
  }
  else
  {
    aPrims = new Graphic3d_ArrayOfSegments(2);
  }

  if (argc != (2 + aPrims->VertexNumberAllocated()))
  {
    Message::SendFail("Syntax error: wrong number of arguments");
    return 1;
  }

  gp_Pnt aPnts[3];
  for (int aPntIter = 0; aPntIter < aPrims->VertexNumberAllocated(); ++aPntIter)
  {
    const TCollection_AsciiString aName(argv[2 + aPntIter]);
    if (occ::handle<AIS_Point> aPntPrs = occ::down_cast<AIS_Point>(
          GetMapOfAIS().IsBound2(aName) ? GetMapOfAIS().Find2(aName) : nullptr))
    {
      aPnts[aPntIter] = aPntPrs->Component()->Pnt();
    }
    else
    {
      TopoDS_Shape aShape = DBRep::Get(argv[2 + aPntIter]);
      if (aShape.IsNull() || aShape.ShapeType() != TopAbs_VERTEX)
      {
        Message::SendFail() << "Syntax error: argument " << aName << " must be a point";
        return 1;
      }
      aPnts[aPntIter] = BRep_Tool::Pnt(TopoDS::Vertex(aShape));
    }

    for (int aPnt2Iter = 0; aPnt2Iter < aPntIter; ++aPnt2Iter)
    {
      if (aPnts[aPnt2Iter].IsEqual(aPnts[aPntIter], Precision::Confusion()))
      {
        Message::SendFail("Syntax error: points should not be equal");
        return 1;
      }
    }

    aPrims->AddVertex(aPnts[aPntIter]);
  }

  occ::handle<AIS_InteractiveObject> aPrs = new MyPArrayObject(aPrims);
  if (!isTri)
  {
    aPrs->Attributes()->SetupOwnShadingAspect();
    aPrs->Attributes()->ShadingAspect()->Aspect()->SetColor(Quantity_NOC_YELLOW);
  }
  ViewerTest::Display(argv[1], aPrs);
  return 0;
}

//===========================================================================
// function : VTorus
// purpose  : creates and displays a torus or torus segment
//===========================================================================
static int VTorus(Draw_Interpretor& /*di*/, int theNbArgs, const char** theArgVec)
{
  if (ViewerTest::GetAISContext().IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  TCollection_AsciiString aName;
  int                     aNbSlices = 100, aNbStacks = 100;
  double                  aMajorRad = -1.0, aMinorRad = -1.0;
  double                  aPipeAngle = 360.0, aSegAngle1 = 0.0, aSegAngle2 = 360.0;
  double                  anAngles[3] = {-1.0, -1.0, -1.0};
  ViewerTest_AutoUpdater  anUpdateTool(ViewerTest::GetAISContext(), ViewerTest::CurrentView());
  for (int anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anUpdateTool.parseRedrawMode(anArg))
    {
      continue;
    }
    else if (anArgIter + 1 < theNbArgs && (anArg == "-nbslices" || anArg == "-slices")
             && Draw::ParseInteger(theArgVec[anArgIter + 1], aNbSlices))
    {
      ++anArgIter;
    }
    else if (anArgIter + 1 < theNbArgs && (anArg == "-nbstacks" || anArg == "-stacks")
             && Draw::ParseInteger(theArgVec[anArgIter + 1], aNbStacks))
    {
      ++anArgIter;
    }
    else if (anArgIter + 1 < theNbArgs && (anArg == "-radius" || anArg == "-majorradius")
             && Draw::ParseReal(theArgVec[anArgIter + 1], aMajorRad))
    {
      ++anArgIter;
    }
    else if (anArgIter + 1 < theNbArgs && (anArg == "-piperadius" || anArg == "-minoradius")
             && Draw::ParseReal(theArgVec[anArgIter + 1], aMinorRad))
    {
      ++anArgIter;
    }
    else if (anArgIter + 1 < theNbArgs && (anArg == "-pipeangle" || anArg == "-angle")
             && Draw::ParseReal(theArgVec[anArgIter + 1], aPipeAngle))
    {
      ++anArgIter;
    }
    else if (anArgIter + 1 < theNbArgs
             && (anArg == "-segmentanglefrom" || anArg == "-seganglefrom"
                 || anArg == "-segmentangle1" || anArg == "-segangle1")
             && Draw::ParseReal(theArgVec[anArgIter + 1], aSegAngle1))
    {
      ++anArgIter;
    }
    else if (anArgIter + 1 < theNbArgs
             && (anArg == "-segmentangleto" || anArg == "-segangleto" || anArg == "-segmentangle2"
                 || anArg == "-segangle2")
             && Draw::ParseReal(theArgVec[anArgIter + 1], aSegAngle2))
    {
      ++anArgIter;
    }
    else if (aName.IsEmpty())
    {
      aName = theArgVec[anArgIter];
    }
    else if (aMajorRad < 0.0 && Draw::ParseReal(theArgVec[anArgIter], aMajorRad))
    {
      //
    }
    else if (aMinorRad < 0.0 && Draw::ParseReal(theArgVec[anArgIter], aMinorRad))
    {
      //
    }
    else if (anAngles[0] < 0.0 && Draw::ParseReal(theArgVec[anArgIter], anAngles[0]))
    {
      //
    }
    else if (anAngles[1] < 0.0 && Draw::ParseReal(theArgVec[anArgIter], anAngles[1]))
    {
      //
    }
    else if (anAngles[2] < 0.0 && Draw::ParseReal(theArgVec[anArgIter], anAngles[2]))
    {
      //
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
      return 1;
    }
  }

  if (anAngles[2] > 0.0)
  {
    aSegAngle1 = anAngles[0];
    aSegAngle2 = anAngles[1];
    aPipeAngle = anAngles[2];
  }
  else if (anAngles[1] > 0.0)
  {
    aSegAngle1 = anAngles[0];
    aSegAngle2 = anAngles[1];
  }
  else if (anAngles[0] > 0.0)
  {
    aPipeAngle = anAngles[0];
  }

  aSegAngle1 = aSegAngle1 * (M_PI / 180.0);
  aSegAngle2 = aSegAngle2 * (M_PI / 180.0);
  aPipeAngle = aPipeAngle * (M_PI / 180.0);

  if (aMajorRad <= 0 || aMinorRad <= 0 || aNbSlices <= 0 || aNbStacks <= 0
      || std::abs(aSegAngle2 - aSegAngle1) <= Precision::Angular()
      || std::abs(aPipeAngle) <= Precision::Angular())
  {
    Message::SendFail("Syntax error: wrong parameters");
    return 1;
  }

  occ::handle<Graphic3d_ArrayOfTriangles> aTriangles = Prs3d_ToolTorus::Create(aMajorRad,
                                                                               aMinorRad,
                                                                               aSegAngle1,
                                                                               aSegAngle2,
                                                                               aPipeAngle,
                                                                               aNbSlices,
                                                                               aNbStacks,
                                                                               gp_Trsf());
  occ::handle<AIS_InteractiveObject>      anIO       = new MyPArrayObject(aTriangles);
  ViewerTest::Display(aName, anIO, false);
  return 0;
}

//===========================================================================
// function : VCylinder
// purpose  : creates and displays a cylinder
//===========================================================================
static int VCylinder(Draw_Interpretor& /*di*/, int theNbArgs, const char** theArgVec)
{
  if (ViewerTest::GetAISContext().IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  TCollection_AsciiString aName;
  int                     aNbSlices = 100, aNbStacks = 1;
  double                  aBotRad = -1.0, aTopRad = -1.0, aHeight = -1.0;
  ViewerTest_AutoUpdater  anUpdateTool(ViewerTest::GetAISContext(), ViewerTest::CurrentView());
  for (int anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anUpdateTool.parseRedrawMode(anArg))
    {
      continue;
    }
    else if (anArgIter + 1 < theNbArgs && (anArg == "-nbslices" || anArg == "-slices")
             && Draw::ParseInteger(theArgVec[anArgIter + 1], aNbSlices))
    {
      ++anArgIter;
    }
    else if (anArgIter + 1 < theNbArgs && (anArg == "-nbstacks" || anArg == "-stacks")
             && Draw::ParseInteger(theArgVec[anArgIter + 1], aNbStacks))
    {
      ++anArgIter;
    }
    else if (anArgIter + 1 < theNbArgs && anArg == "-radius"
             && Draw::ParseReal(theArgVec[anArgIter + 1], aBotRad))
    {
      aTopRad = aBotRad;
      ++anArgIter;
    }
    else if (anArgIter + 1 < theNbArgs && anArg == "-bottomradius"
             && Draw::ParseReal(theArgVec[anArgIter + 1], aBotRad))
    {
      ++anArgIter;
    }
    else if (anArgIter + 1 < theNbArgs && anArg == "-topradius"
             && Draw::ParseReal(theArgVec[anArgIter + 1], aTopRad))
    {
      ++anArgIter;
    }
    else if (anArgIter + 1 < theNbArgs && anArg == "-height"
             && Draw::ParseReal(theArgVec[anArgIter + 1], aHeight))
    {
      ++anArgIter;
    }
    else if (aName.IsEmpty())
    {
      aName = theArgVec[anArgIter];
    }
    else if (aBotRad < 0.0 && Draw::ParseReal(theArgVec[anArgIter], aBotRad))
    {
      //
    }
    else if (aTopRad < 0.0 && Draw::ParseReal(theArgVec[anArgIter], aTopRad))
    {
      //
    }
    else if (aHeight < 0.0 && Draw::ParseReal(theArgVec[anArgIter], aHeight))
    {
      //
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
      return 1;
    }
  }

  if (aBotRad < 0 || aTopRad < 0 || aHeight <= 0 || aNbSlices < 3)
  {
    Message::SendFail("Syntax error: wrong parameters");
    return 1;
  }

  occ::handle<Graphic3d_ArrayOfTriangles> aTriangles =
    Prs3d_ToolCylinder::Create(aBotRad, aTopRad, aHeight, aNbSlices, aNbStacks, gp_Trsf());
  occ::handle<AIS_InteractiveObject> anIO = new MyPArrayObject(aTriangles);
  ViewerTest::Display(aName, anIO, false);
  return 0;
}

//===========================================================================
// function : VSphere
// purpose  : creates and displays a sphere
//===========================================================================
static int VSphere(Draw_Interpretor& /*di*/, int theNbArgs, const char** theArgVec)
{
  if (ViewerTest::GetAISContext().IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  TCollection_AsciiString aName;
  int                     aNbSlices = 100, aNbStacks = 100;
  double                  aRad = -1.0;
  ViewerTest_AutoUpdater  anUpdateTool(ViewerTest::GetAISContext(), ViewerTest::CurrentView());
  for (int anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if (anUpdateTool.parseRedrawMode(anArg))
    {
      continue;
    }
    else if (anArgIter + 1 < theNbArgs && (anArg == "-nbslices" || anArg == "-slices")
             && Draw::ParseInteger(theArgVec[anArgIter + 1], aNbSlices))
    {
      ++anArgIter;
    }
    else if (anArgIter + 1 < theNbArgs && (anArg == "-nbstacks" || anArg == "-stacks")
             && Draw::ParseInteger(theArgVec[anArgIter + 1], aNbStacks))
    {
      ++anArgIter;
    }
    else if (anArgIter + 1 < theNbArgs && anArg == "-radius"
             && Draw::ParseReal(theArgVec[anArgIter + 1], aRad))
    {
      ++anArgIter;
    }
    else if (aName.IsEmpty())
    {
      aName = theArgVec[anArgIter];
    }
    else if (aRad < 0.0 && Draw::ParseReal(theArgVec[anArgIter], aRad))
    {
      //
    }
    else
    {
      Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
      return 1;
    }
  }

  if (aRad <= 0 || aNbSlices <= 0 || aNbStacks <= 0)
  {
    Message::SendFail("Syntax error: wrong parameters");
    return 1;
  }

  occ::handle<Graphic3d_ArrayOfTriangles> aTriangles =
    Prs3d_ToolSphere::Create(aRad, aNbSlices, aNbStacks, gp_Trsf());
  occ::handle<AIS_InteractiveObject> anIO = new MyPArrayObject(aTriangles);
  ViewerTest::Display(aName, anIO, false);
  return 0;
}

//=======================================================================
// function : VObjZLayer
// purpose  : Set or get z layer id for presentable object
//=======================================================================

static int VObjZLayer(Draw_Interpretor& di, int argc, const char** argv)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  // get operation
  TCollection_AsciiString aOperation;
  if (argc >= 2)
    aOperation = TCollection_AsciiString(argv[1]);

  // check for correct arguments
  if (!(argc == 4 && aOperation.IsEqual("set")) && !(argc == 3 && aOperation.IsEqual("get")))
  {
    di << "Usage : " << argv[0] << " set/get object [layerid]\n";
    di << " set - set layer id for interactive object, layerid - z layer id\n";
    di << " get - get layer id of interactive object\n";
    di << " argument layerid should be passed for set operation only\n";
    return 1;
  }

  // find object
  TCollection_AsciiString            aName(argv[2]);
  occ::handle<AIS_InteractiveObject> anInterObj;
  GetMapOfAIS().Find2(aName, anInterObj);
  if (anInterObj.IsNull())
  {
    Message::SendFail() << "Syntax error: object '" << aName << "' is not displayed";
    return 1;
  }

  // process operation
  if (aOperation.IsEqual("set"))
  {
    int aLayerId = Draw::Atoi(argv[3]);
    aContext->SetZLayer(anInterObj, aLayerId);
  }
  else if (aOperation.IsEqual("get"))
  {
    di << "Z layer id: " << aContext->GetZLayer(anInterObj);
  }

  return 0;
}

//=======================================================================
// function : VPolygonOffset
// purpose  : Set or get polygon offset parameters
//=======================================================================
static int VPolygonOffset(Draw_Interpretor& /*di*/, int argc, const char** argv)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  if (argc > 2 && argc != 5)
  {
    Message::SendFail()
      << "Syntax error: wrong number of arguments.\n"
         "Usage: "
      << argv[0]
      << " [object [mode factor units]] - sets/gets polygon offset parameters for an object,"
         "without arguments prints the default values";
    return 1;
  }

  // find object
  occ::handle<AIS_InteractiveObject> anInterObj;
  if (argc >= 2)
  {
    TCollection_AsciiString aName(argv[1]);
    if (!GetMapOfAIS().Find2(aName, anInterObj) || anInterObj.IsNull())
    {
      Message::SendFail() << "Syntax error: object '" << aName << "' is not displayed";
      return 1;
    }
  }

  int   aMode;
  float aFactor, aUnits;
  if (argc == 5)
  {
    aMode   = Draw::Atoi(argv[2]);
    aFactor = (float)Draw::Atof(argv[3]);
    aUnits  = (float)Draw::Atof(argv[4]);

    anInterObj->SetPolygonOffsets(aMode, aFactor, aUnits);
    aContext->UpdateCurrentViewer();
    return 0;
  }
  else if (argc == 2)
  {
    if (anInterObj->HasPolygonOffsets())
    {
      anInterObj->PolygonOffsets(aMode, aFactor, aUnits);
      std::cout << "Current polygon offset parameters for " << argv[1] << ":" << std::endl;
      std::cout << "\tMode: " << aMode << std::endl;
      std::cout << "\tFactor: " << aFactor << std::endl;
      std::cout << "\tUnits: " << aUnits << std::endl;
      return 0;
    }
    else
    {
      std::cout << "Specific polygon offset parameters are not set for " << argv[1] << std::endl;
    }
  }

  std::cout << "Default polygon offset parameters:" << std::endl;
  aContext->DefaultDrawer()->ShadingAspect()->Aspect()->PolygonOffsets(aMode, aFactor, aUnits);
  std::cout << "\tMode: " << aMode << std::endl;
  std::cout << "\tFactor: " << aFactor << std::endl;
  std::cout << "\tUnits: " << aUnits << std::endl;

  return 0;
}

// This class is used for testing markers.
class ViewerTest_MarkersArrayObject : public AIS_InteractiveObject
{

public:
  ViewerTest_MarkersArrayObject(const gp_XYZ&                         theStartPoint,
                                const int&                            thePointsOnSide,
                                occ::handle<Graphic3d_AspectMarker3d> theMarkerAspect = nullptr)
  {
    myStartPoint   = theStartPoint;
    myPointsOnSide = thePointsOnSide;
    myMarkerAspect = std::move(theMarkerAspect);
  }

  DEFINE_STANDARD_RTTI_INLINE(ViewerTest_MarkersArrayObject, AIS_InteractiveObject);

private:
  void Compute(const occ::handle<PrsMgr_PresentationManager>& thePrsMgr,
               const occ::handle<Prs3d_Presentation>&         thePrs,
               const int                                      theMode) override;

  void ComputeSelection(const occ::handle<SelectMgr_Selection>& theSel, const int theMode) override;

protected:
  gp_XYZ                                myStartPoint;
  int                                   myPointsOnSide;
  occ::handle<Graphic3d_AspectMarker3d> myMarkerAspect;
};

void ViewerTest_MarkersArrayObject::Compute(const occ::handle<PrsMgr_PresentationManager>&,
                                            const occ::handle<Prs3d_Presentation>& aPresentation,
                                            const int)
{
  occ::handle<Graphic3d_ArrayOfPrimitives> anArray =
    new Graphic3d_ArrayOfPoints((int)std::pow(myPointsOnSide, 3), myPointsOnSide != 1);
  if (myPointsOnSide == 1)
  {
    anArray->AddVertex(myStartPoint);
  }
  else
  {
    for (double i = 1; i <= myPointsOnSide; i++)
    {
      for (double j = 1; j <= myPointsOnSide; j++)
      {
        for (double k = 1; k <= myPointsOnSide; k++)
        {
          anArray->AddVertex(myStartPoint.X() + i, myStartPoint.Y() + j, myStartPoint.Z() + k);
          anArray->SetVertexColor(anArray->VertexNumber(),
                                  i / myPointsOnSide,
                                  j / myPointsOnSide,
                                  k / myPointsOnSide);
        }
      }
    }
  }

  aPresentation->Clear();
  if (!myMarkerAspect.IsNull())
  {
    aPresentation->CurrentGroup()->SetGroupPrimitivesAspect(myMarkerAspect);
  }
  aPresentation->CurrentGroup()->AddPrimitiveArray(anArray);
}

void ViewerTest_MarkersArrayObject::ComputeSelection(
  const occ::handle<SelectMgr_Selection>& theSelection,
  const int /*theMode*/)
{
  occ::handle<SelectMgr_EntityOwner> anEntityOwner = new SelectMgr_EntityOwner(this);

  if (myPointsOnSide == 1)
  {
    gp_Pnt                               aPoint(myStartPoint);
    occ::handle<Select3D_SensitivePoint> aSensetivePoint =
      new Select3D_SensitivePoint(anEntityOwner, aPoint);
    theSelection->Add(aSensetivePoint);
  }
  else
  {
    for (double i = 1; i <= myPointsOnSide; i++)
    {
      for (double j = 1; j <= myPointsOnSide; j++)
      {
        for (double k = 1; k <= myPointsOnSide; k++)
        {
          gp_Pnt aPoint(myStartPoint.X() + i, myStartPoint.Y() + j, myStartPoint.Z() + k);
          occ::handle<Select3D_SensitivePoint> aSensetivePoint =
            new Select3D_SensitivePoint(anEntityOwner, aPoint);
          theSelection->Add(aSensetivePoint);
        }
      }
    }
  }
}

//=======================================================================
// function : VMarkersTest
// purpose  : Draws an array of markers for testing purposes.
//=======================================================================
static int VMarkersTest(Draw_Interpretor&, int theArgNb, const char** theArgVec)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  if (theArgNb < 5)
  {
    Message::SendFail("Syntax error: wrong number of arguments");
    return 1;
  }

  int anArgIter = 1;

  TCollection_AsciiString aName(theArgVec[anArgIter++]);
  TCollection_AsciiString aFileName;
  gp_XYZ                  aPnt(Atof(theArgVec[anArgIter]),
              Atof(theArgVec[anArgIter + 1]),
              Atof(theArgVec[anArgIter + 2]));
  anArgIter += 3;

  int    aPointsOnSide = 10;
  int    aMarkerType   = -1;
  double aScale        = 1.0;
  for (; anArgIter < theArgNb; ++anArgIter)
  {
    const TCollection_AsciiString anArg(theArgVec[anArgIter]);
    if (anArg.Search("PointsOnSide=") > -1)
    {
      aPointsOnSide = anArg.Token("=", 2).IntegerValue();
    }
    else if (anArg.Search("MarkerType=") > -1)
    {
      aMarkerType = anArg.Token("=", 2).IntegerValue();
    }
    else if (anArg.Search("Scale=") > -1)
    {
      aScale = anArg.Token("=", 2).RealValue();
    }
    else if (anArg.Search("FileName=") > -1)
    {
      aFileName = anArg.Token("=", 2);
    }
    else
    {
      Message::SendFail() << "Syntax error: wrong argument '" << anArg << "'";
      return 1;
    }
  }

  occ::handle<Graphic3d_AspectMarker3d> anAspect;
  occ::handle<Image_AlienPixMap>        anImage;
  Quantity_Color                        aColor(Quantity_NOC_GREEN1);
  if ((aMarkerType == Aspect_TOM_USERDEFINED || aMarkerType < 0) && !aFileName.IsEmpty())
  {
    anImage = new Image_AlienPixMap();
    if (!anImage->Load(aFileName))
    {
      Message::SendFail() << "Error: could not load image from file '" << aFileName << "'!";
      return 1;
    }
    if (anImage->Format() == Image_Format_Gray)
    {
      anImage->SetFormat(Image_Format_Alpha);
    }
    else if (anImage->Format() == Image_Format_GrayF)
    {
      anImage->SetFormat(Image_Format_AlphaF);
    }
    anAspect = new Graphic3d_AspectMarker3d(anImage);
  }
  else
  {
    anAspect = new Graphic3d_AspectMarker3d(aMarkerType >= 0 ? (Aspect_TypeOfMarker)aMarkerType
                                                             : Aspect_TOM_POINT,
                                            aColor,
                                            aScale);
  }

  occ::handle<ViewerTest_MarkersArrayObject> aMarkersArray =
    new ViewerTest_MarkersArrayObject(aPnt, aPointsOnSide, anAspect);
  VDisplayAISObject(aName, aMarkersArray);

  return 0;
}

//=======================================================================
// function : TextToBrep
// purpose  : Tool for conversion text to occt-shapes
//=======================================================================
static int TextToBRep(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgVec)
{
  // Check arguments
  if (theArgNb < 3)
  {
    Message::SendFail() << "Error: " << theArgVec[0] << " - invalid syntax";
    return 1;
  }

  int         anArgIt = 1;
  const char* aName   = theArgVec[anArgIt++];
  const char* aText   = theArgVec[anArgIt++];

  Font_BRepFont           aFont;
  TCollection_AsciiString aFontName("Courier");
  double                  aTextHeight        = 16.0;
  Font_FontAspect         aFontAspect        = Font_FA_Regular;
  bool                    anIsCompositeCurve = false;
  gp_Ax3                  aPenAx3(gp::XOY());
  gp_Dir                  aNormal(gp_Dir::D::Z);
  gp_Dir                  aDirection(gp_Dir::D::X);
  gp_Pnt                  aPenLoc;

  Graphic3d_HorizontalTextAlignment aHJustification = Graphic3d_HTA_LEFT;
  Graphic3d_VerticalTextAlignment   aVJustification = Graphic3d_VTA_BOTTOM;
  Font_StrictLevel                  aStrictLevel    = Font_StrictLevel_Any;
  for (; anArgIt < theArgNb; ++anArgIt)
  {
    TCollection_AsciiString aParam(theArgVec[anArgIt]);
    aParam.LowerCase();

    if (aParam == "-pos" || aParam == "-position")
    {
      if (anArgIt + 3 >= theArgNb)
      {
        Message::SendFail() << "Error: wrong number of values for parameter '" << aParam << "'";
        return 1;
      }

      aPenLoc.SetX(Draw::Atof(theArgVec[++anArgIt]));
      aPenLoc.SetY(Draw::Atof(theArgVec[++anArgIt]));
      aPenLoc.SetZ(Draw::Atof(theArgVec[++anArgIt]));
    }
    else if (aParam == "-halign")
    {
      if (++anArgIt >= theArgNb)
      {
        Message::SendFail() << "Error: wrong number of values for parameter '" << aParam << "'";
        return 1;
      }

      TCollection_AsciiString aType(theArgVec[anArgIt]);
      aType.LowerCase();
      if (aType == "left")
      {
        aHJustification = Graphic3d_HTA_LEFT;
      }
      else if (aType == "center")
      {
        aHJustification = Graphic3d_HTA_CENTER;
      }
      else if (aType == "right")
      {
        aHJustification = Graphic3d_HTA_RIGHT;
      }
      else
      {
        Message::SendFail() << "Error: wrong syntax at '" << aParam << "'";
        return 1;
      }
    }
    else if (aParam == "-valign")
    {
      if (++anArgIt >= theArgNb)
      {
        Message::SendFail() << "Error: wrong number of values for parameter '" << aParam << "'";
        return 1;
      }

      TCollection_AsciiString aType(theArgVec[anArgIt]);
      aType.LowerCase();
      if (aType == "top")
      {
        aVJustification = Graphic3d_VTA_TOP;
      }
      else if (aType == "center")
      {
        aVJustification = Graphic3d_VTA_CENTER;
      }
      else if (aType == "bottom")
      {
        aVJustification = Graphic3d_VTA_BOTTOM;
      }
      else if (aType == "topfirstline")
      {
        aVJustification = Graphic3d_VTA_TOPFIRSTLINE;
      }
      else
      {
        Message::SendFail() << "Error: wrong syntax at '" << aParam << "'";
        return 1;
      }
    }
    else if (aParam == "-height")
    {
      if (++anArgIt >= theArgNb)
      {
        Message::SendFail() << "Error: wrong number of values for parameter '" << aParam << "'";
        return 1;
      }

      aTextHeight = Draw::Atof(theArgVec[anArgIt]);
    }
    else if (aParam == "-aspect")
    {
      if (++anArgIt >= theArgNb)
      {
        Message::SendFail() << "Error: wrong number of values for parameter '" << aParam << "'";
        return 1;
      }

      TCollection_AsciiString anOption(theArgVec[anArgIt]);
      anOption.LowerCase();
      if (!parseFontStyle(anOption, aFontAspect))
      {
        Message::SendFail() << "Error: unknown font aspect '" << anOption << "'";
        return 1;
      }
    }
    else if (aParam == "-font")
    {
      if (++anArgIt >= theArgNb)
      {
        Message::SendFail() << "Error: wrong number of values for parameter '" << aParam << "'";
        return 1;
      }

      aFontName = theArgVec[anArgIt];
    }
    else if (aParam == "-strict")
    {
      anArgIt +=
        parseFontStrictLevel(theArgNb - anArgIt - 1, theArgVec + anArgIt + 1, aStrictLevel);
    }
    else if (aParam == "-composite")
    {
      if (++anArgIt >= theArgNb)
      {
        Message::SendFail() << "Error: wrong number of values for parameter '" << aParam << "'";
        return 1;
      }

      Draw::ParseOnOff(theArgVec[anArgIt], anIsCompositeCurve);
    }
    else if (aParam == "-plane")
    {
      if (anArgIt + 6 >= theArgNb)
      {
        Message::SendFail() << "Error: wrong number of values for parameter '" << aParam << "'";
        return 1;
      }

      double aX = Draw::Atof(theArgVec[++anArgIt]);
      double aY = Draw::Atof(theArgVec[++anArgIt]);
      double aZ = Draw::Atof(theArgVec[++anArgIt]);
      aNormal.SetCoord(aX, aY, aZ);

      aX = Draw::Atof(theArgVec[++anArgIt]);
      aY = Draw::Atof(theArgVec[++anArgIt]);
      aZ = Draw::Atof(theArgVec[++anArgIt]);
      aDirection.SetCoord(aX, aY, aZ);
    }
    else
    {
      Message::SendFail() << "Warning! Unknown argument '" << aParam << "'";
    }
  }

  aFont.SetCompositeCurveMode(anIsCompositeCurve);
  if (!aFont.FindAndInit(aFontName.ToCString(), aFontAspect, aTextHeight, aStrictLevel))
  {
    Message::SendFail("Error: unable to load Font");
    return 1;
  }

  aPenAx3 = gp_Ax3(aPenLoc, aNormal, aDirection);

  Font_BRepTextBuilder aBuilder;
  DBRep::Set(aName, aBuilder.Perform(aFont, aText, aPenAx3, aHJustification, aVJustification));
  return 0;
}

//=================================================================================================

struct FontComparator
{
  bool operator()(const occ::handle<Font_SystemFont>& theFontA,
                  const occ::handle<Font_SystemFont>& theFontB)
  {
    return theFontA->FontKey().IsLess(theFontB->FontKey());
  }
};

static int VFont(Draw_Interpretor& theDI, int theArgNb, const char** theArgVec)
{
  occ::handle<Font_FontMgr> aMgr        = Font_FontMgr::GetInstance();
  bool                      toPrintList = theArgNb < 2, toPrintNames = false;
  Font_StrictLevel          aStrictLevel = Font_StrictLevel_Any;
  for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
  {
    const TCollection_AsciiString anArg(theArgVec[anArgIter]);
    TCollection_AsciiString       anArgCase(anArg);
    anArgCase.LowerCase();
    if (anArgCase == "-strict")
    {
      anArgIter +=
        parseFontStrictLevel(theArgNb - anArgIter - 1, theArgVec + anArgIter + 1, aStrictLevel);
    }
    else if (anArgCase == "-clear")
    {
      aMgr->ClearFontDataBase();
    }
    else if (anArgCase == "-init")
    {
      aMgr->InitFontDataBase();
    }
    else if (anArgCase == "-list")
    {
      toPrintList = true;
    }
    else if (anArgCase == "-names")
    {
      toPrintList  = true;
      toPrintNames = true;
    }
    else if (anArgIter + 1 < theArgNb
             && (anArgCase == "-find" || anArgCase == "-findinfo" || anArgCase == "-findall"
                 || anArgCase == "find"))
    {
      const TCollection_AsciiString aFontName(theArgVec[++anArgIter]);
      Font_FontAspect               aFontAspect = Font_FA_Undefined;
      if (++anArgIter < theArgNb)
      {
        anArgCase = theArgVec[anArgIter];
        anArgCase.LowerCase();
        if (!parseFontStyle(anArgCase, aFontAspect))
        {
          --anArgIter;
        }
      }

      const bool              toFindAll   = (anArgCase == "-findall");
      const bool              toPrintInfo = (anArgCase == "-findinfo");
      TCollection_AsciiString aResult;
      if (toFindAll || aFontName.Search("*") != -1)
      {
        const NCollection_List<occ::handle<Font_SystemFont>> aFonts = aMgr->GetAvailableFonts();
        std::vector<occ::handle<Font_SystemFont>>            aFontsSorted;
        aFontsSorted.reserve(aFonts.Size());
        for (NCollection_List<occ::handle<Font_SystemFont>>::Iterator aFontIter(aFonts);
             aFontIter.More();
             aFontIter.Next())
        {
          aFontsSorted.push_back(aFontIter.Value());
        }
        std::stable_sort(aFontsSorted.begin(), aFontsSorted.end(), FontComparator());
        for (std::vector<occ::handle<Font_SystemFont>>::iterator aFontIter = aFontsSorted.begin();
             aFontIter != aFontsSorted.end();
             ++aFontIter)
        {
          const occ::handle<Font_SystemFont>& aFont = *aFontIter;
          const TCollection_AsciiString aCheck = TCollection_AsciiString("string match -nocase \"")
                                                 + aFontName + "\" \"" + aFont->FontName() + "\"";
          if (theDI.Eval(aCheck.ToCString()) == 0 && *theDI.Result() != '1')
          {
            theDI.Reset();
            continue;
          }

          theDI.Reset();
          if (!aResult.IsEmpty())
          {
            aResult += "\n";
          }

          aResult += toPrintInfo ? aFont->ToString() : aFont->FontName();
          if (!toFindAll)
          {
            break;
          }
        }
      }
      else if (occ::handle<Font_SystemFont> aFont =
                 aMgr->FindFont(aFontName, aStrictLevel, aFontAspect))
      {
        aResult = toPrintInfo ? aFont->ToString() : aFont->FontName();
      }

      if (!aResult.IsEmpty())
      {
        theDI << aResult;
      }
      else
      {
        Message::SendFail() << "Error: font '" << aFontName << "' is not found";
      }
    }
    else if (anArgIter + 1 < theArgNb
             && (anArgCase == "-add" || anArgCase == "add" || anArgCase == "-register"
                 || anArgCase == "register"))
    {
      ++anArgIter;
      const char*             aFontPath = theArgVec[anArgIter++];
      TCollection_AsciiString aFontName;
      Font_FontAspect         aFontAspect    = Font_FA_Undefined;
      int                     isSingelStroke = -1;
      for (; anArgIter < theArgNb; ++anArgIter)
      {
        anArgCase = theArgVec[anArgIter];
        anArgCase.LowerCase();
        if (aFontAspect == Font_FontAspect_UNDEFINED && parseFontStyle(anArgCase, aFontAspect))
        {
          continue;
        }
        else if (anArgCase == "singlestroke" || anArgCase == "singleline" || anArgCase == "oneline")
        {
          isSingelStroke = 1;
        }
        else if (aFontName.IsEmpty())
        {
          aFontName = theArgVec[anArgIter];
        }
        else
        {
          --anArgIter;
          break;
        }
      }

      occ::handle<Font_SystemFont> aFont = aMgr->CheckFont(aFontPath);
      if (aFont.IsNull())
      {
        Message::SendFail() << "Error: font '" << aFontPath << "' is not found!";
        continue;
      }

      if (aFontAspect != Font_FontAspect_UNDEFINED || !aFontName.IsEmpty())
      {
        TCollection_AsciiString aName = aFont->FontName();
        if (!aFontName.IsEmpty())
        {
          aName = aFontName;
        }
        occ::handle<Font_SystemFont> aFont2 = new Font_SystemFont(aName);
        if (aFontAspect != Font_FontAspect_UNDEFINED)
        {
          aFont2->SetFontPath(aFontAspect, aFontPath, 0);
        }
        else
        {
          for (int anAspectIter = 0; anAspectIter < Font_FontAspect_NB; ++anAspectIter)
          {
            aFont2->SetFontPath((Font_FontAspect)anAspectIter,
                                aFont->FontPath((Font_FontAspect)anAspectIter),
                                aFont->FontFaceId((Font_FontAspect)anAspectIter));
          }
        }
        aFont = aFont2;
      }
      if (isSingelStroke != -1)
      {
        aFont->SetSingleStrokeFont(isSingelStroke == 1);
      }

      aMgr->RegisterFont(aFont, true);
      theDI << aFont->ToString();
    }
    else if (anArgCase == "-aliases")
    {
      TCollection_AsciiString                                     anAliasName;
      NCollection_Sequence<occ::handle<TCollection_HAsciiString>> aNames;
      if (anArgIter + 1 < theArgNb && *theArgVec[anArgIter + 1] != '-')
      {
        anAliasName = theArgVec[++anArgIter];
      }
      if (!anAliasName.IsEmpty())
      {
        aMgr->GetFontAliases(aNames, anAliasName);
      }
      else
      {
        aMgr->GetAllAliases(aNames);
      }
      for (NCollection_Sequence<occ::handle<TCollection_HAsciiString>>::Iterator aNameIter(aNames);
           aNameIter.More();
           aNameIter.Next())
      {
        theDI << "{" << aNameIter.Value()->String() << "} ";
      }
    }
    else if (anArgIter + 2 < theArgNb && anArgCase == "-addalias")
    {
      TCollection_AsciiString anAliasName(theArgVec[++anArgIter]);
      TCollection_AsciiString aFontName(theArgVec[++anArgIter]);
      aMgr->AddFontAlias(anAliasName, aFontName);
    }
    else if (anArgIter + 2 < theArgNb && anArgCase == "-removealias")
    {
      TCollection_AsciiString anAliasName(theArgVec[++anArgIter]);
      TCollection_AsciiString aFontName(theArgVec[++anArgIter]);
      aMgr->RemoveFontAlias(anAliasName, aFontName);
    }
    else if (anArgIter + 1 < theArgNb && anArgCase == "-clearalias")
    {
      TCollection_AsciiString anAliasName(theArgVec[++anArgIter]);
      aMgr->RemoveFontAlias(anAliasName, "");
    }
    else if (anArgCase == "-clearaliases")
    {
      aMgr->RemoveFontAlias("", "");
    }
    else if (anArgCase == "-verbose" || anArgCase == "-trace")
    {
      bool toEnable = true;
      if (anArgIter + 1 < theArgNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], toEnable))
      {
        ++anArgIter;
      }
      aMgr->SetTraceAliases(toEnable);
    }
    else if (anArgCase == "-unicodefallback" || anArgCase == "-fallback"
             || anArgCase == "-touseunicodesubsetfallback")
    {
      bool toEnable = true;
      if (anArgIter + 1 < theArgNb && Draw::ParseOnOff(theArgVec[anArgIter + 1], toEnable))
      {
        ++anArgIter;
      }
      Font_FontMgr::ToUseUnicodeSubsetFallback() = toEnable;
    }
    else
    {
      Message::SendFail() << "Warning! Unknown argument '" << anArg << "'";
    }
  }

  if (toPrintList)
  {
    // just print the list of available fonts
    bool                                                 isFirst = true;
    const NCollection_List<occ::handle<Font_SystemFont>> aFonts  = aMgr->GetAvailableFonts();
    std::vector<occ::handle<Font_SystemFont>>            aFontsSorted;
    aFontsSorted.reserve(aFonts.Size());
    for (NCollection_List<occ::handle<Font_SystemFont>>::Iterator aFontIter(aFonts);
         aFontIter.More();
         aFontIter.Next())
    {
      aFontsSorted.push_back(aFontIter.Value());
    }
    std::stable_sort(aFontsSorted.begin(), aFontsSorted.end(), FontComparator());
    for (std::vector<occ::handle<Font_SystemFont>>::iterator aFontIter = aFontsSorted.begin();
         aFontIter != aFontsSorted.end();
         ++aFontIter)
    {
      const occ::handle<Font_SystemFont>& aFont = *aFontIter;

      if (toPrintNames)
      {
        if (!isFirst)
        {
          theDI << "\n";
        }
        theDI << "\"" << aFont->FontName() << "\"";
      }
      else
      {
        if (!isFirst)
        {
          theDI << "\n";
        }
        theDI << aFont->ToString();
      }
      isFirst = false;
    }
    return 0;
  }

  return 0;
}

//=======================================================================
// function : VVertexMode
// purpose  : Switches vertex display mode for AIS_Shape or displays the current value
//=======================================================================

static int VVertexMode(Draw_Interpretor& theDI, int theArgNum, const char** theArgs)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  // No arguments --> print the current default vertex draw mode
  if (theArgNum == 1)
  {
    Prs3d_VertexDrawMode aCurrMode = aContext->DefaultDrawer()->VertexDrawMode();
    theDI << "Default vertex draw mode: "
          << (aCurrMode == Prs3d_VDM_Isolated ? "'isolated'" : "'all'") << "\n";
    return 0;
  }

  // -set argument --> change the default vertex draw mode and the mode for all displayed or given
  // object(s)
  TCollection_AsciiString aParam(theArgs[1]);
  if (aParam == "-set")
  {
    if (theArgNum == 2)
    {
      Message::SendFail(
        "Error: '-set' option not followed by the mode and optional object name(s)\n"
        "Type 'help vvertexmode' for usage hints");
      return 1;
    }

    TCollection_AsciiString aModeStr(theArgs[2]);
    Prs3d_VertexDrawMode    aNewMode = aModeStr == "isolated"
                                         ? Prs3d_VDM_Isolated
                                         : (aModeStr == "all" ? Prs3d_VDM_All : Prs3d_VDM_Inherited);

    bool                                                 aRedrawNeeded = false;
    NCollection_List<occ::handle<AIS_InteractiveObject>> anObjs;

    // No object(s) specified -> use all displayed
    if (theArgNum == 3)
    {
      theDI << "Setting the default vertex draw mode and updating all displayed objects...\n";
      aContext->DisplayedObjects(anObjs);
      aContext->DefaultDrawer()->SetVertexDrawMode(aNewMode);
      aRedrawNeeded = true;
    }

    occ::handle<AIS_InteractiveObject> anObject;
    for (int aCount = 3; aCount < theArgNum; aCount++)
    {
      TCollection_AsciiString aName(theArgs[aCount]);
      if (!GetMapOfAIS().Find2(aName, anObject))
      {
        theDI << "Warning: wrong object name ignored - " << theArgs[0] << "\n";
        continue;
      }
      anObjs.Append(anObject);
    }

    for (NCollection_List<occ::handle<AIS_InteractiveObject>>::Iterator anIt(anObjs); anIt.More();
         anIt.Next())
    {
      anObject = anIt.Value();
      anObject->Attributes()->SetVertexDrawMode(aNewMode);
      aContext->Redisplay(anObject, false);
      aRedrawNeeded = true;
    }

    if (aRedrawNeeded)
      ViewerTest::CurrentView()->Redraw();

    return 0;
  }

  occ::handle<AIS_InteractiveObject> anObject;
  if (theArgNum > 2 || !GetMapOfAIS().Find2(aParam, anObject))
  {
    Message::SendFail("Syntax error: invalid number of arguments");
    return 1;
  }

  // One argument (object name) --> print the current vertex draw mode for the object
  Prs3d_VertexDrawMode aCurrMode = anObject->Attributes()->VertexDrawMode();
  theDI << "Object's vertex draw mode: "
        << (aCurrMode == Prs3d_VDM_Isolated ? "'isolated'" : "'all'") << "\n";
  return 0;
}

//=======================================================================
// function : VPointCloud
// purpose  : Create interactive object for arbitrary set of points.
//=======================================================================
static int VPointCloud(Draw_Interpretor& theDI, int theArgNum, const char** theArgs)
{
  if (theArgNum < 2)
  {
    Message::SendFail("Syntax error: wrong number of arguments");
    return 1;
  }

  occ::handle<AIS_InteractiveContext> anAISContext = ViewerTest::GetAISContext();
  if (anAISContext.IsNull())
  {
    Message::SendFail("Error: no active view!");
    return 1;
  }

  TCollection_AsciiString aName;
  TopoDS_Shape            aShape;

  TCollection_AsciiString aDistribution;
  gp_Pnt                  aDistCenter;
  double                  aDistRadius   = 0.0;
  int                     aDistNbPoints = 0;

  // parse options
  bool   toRandColors = false;
  bool   hasNormals = true, hasUV = false;
  bool   isDensityPoints = false;
  double aDensity = 0.0, aDist = 0.0;
  double aTol = Precision::Confusion();
  for (int anArgIter = 1; anArgIter < theArgNum; ++anArgIter)
  {
    TCollection_AsciiString aFlag(theArgs[anArgIter]);
    aFlag.LowerCase();
    if (aFlag == "-randcolors" || aFlag == "-randcolor")
    {
      toRandColors = Draw::ParseOnOffIterator(theArgNum, theArgs, anArgIter);
    }
    else if (aFlag == "-normals" || aFlag == "-normal")
    {
      hasNormals = Draw::ParseOnOffIterator(theArgNum, theArgs, anArgIter);
    }
    else if (aFlag == "-nonormals" || aFlag == "-nonormal")
    {
      hasNormals = !Draw::ParseOnOffIterator(theArgNum, theArgs, anArgIter);
    }
    else if (aFlag == "-uv" || aFlag == "-texels")
    {
      hasUV = Draw::ParseOnOffIterator(theArgNum, theArgs, anArgIter);
    }
    else if ((aFlag == "-dist" || aFlag == "-distance") && anArgIter + 1 < theArgNum
             && Draw::ParseReal(theArgs[anArgIter + 1], aDist))
    {
      ++anArgIter;
      if (aDist < 0.0)
      {
        theDI << "Syntax error: -distance value should be >= 0.0";
        return 1;
      }
      aDist = std::max(aDist, Precision::Confusion());
    }
    else if ((aFlag == "-dens" || aFlag == "-density") && anArgIter + 1 < theArgNum
             && Draw::ParseReal(theArgs[anArgIter + 1], aDensity))
    {
      ++anArgIter;
      isDensityPoints = true;
      if (aDensity <= 0.0)
      {
        theDI << "Syntax error: -density value should be > 0.0";
        return 1;
      }
    }
    else if ((aFlag == "-tol" || aFlag == "-tolerance") && anArgIter + 1 < theArgNum
             && Draw::ParseReal(theArgs[anArgIter + 1], aTol))
    {
      ++anArgIter;
      if (aTol < Precision::Confusion())
      {
        theDI << "Syntax error: -tol value should be >= " << Precision::Confusion();
        return 1;
      }
    }
    else if ((aFlag == "-surface" || aFlag == "-volume") && anArgIter + 5 < theArgNum)
    {
      aDistribution = aFlag;
      aDistCenter.SetCoord(Draw::Atof(theArgs[anArgIter + 1]),
                           Draw::Atof(theArgs[anArgIter + 2]),
                           Draw::Atof(theArgs[anArgIter + 3]));
      aDistRadius   = Draw::Atof(theArgs[anArgIter + 4]);
      aDistNbPoints = Draw::Atoi(theArgs[anArgIter + 5]);
      anArgIter += 5;
    }
    else if (aName.IsEmpty())
    {
      aName = theArgs[anArgIter];
    }
    else if (aShape.IsNull())
    {
      aShape = DBRep::Get(theArgs[anArgIter]);
      if (aShape.IsNull())
      {
        theDI << "Syntax error: invalid shape '" << theArgs[anArgIter] << "'";
        return 1;
      }
    }
    else
    {
      theDI << "Syntax error at '" << theArgs[anArgIter] << "'";
      return 1;
    }
  }

  Graphic3d_ArrayFlags aFlags = Graphic3d_ArrayFlags_None;
  if (hasNormals)
  {
    aFlags |= Graphic3d_ArrayFlags_VertexNormal;
  }
  if (toRandColors)
  {
    aFlags |= Graphic3d_ArrayFlags_VertexColor;
  }
  if (hasUV)
  {
    aFlags |= Graphic3d_ArrayFlags_VertexTexel;
  }

  // generate arbitrary set of points
  occ::handle<Graphic3d_ArrayOfPoints> anArrayPoints;
  if (!aShape.IsNull())
  {
    class PointCloudPntFiller : public BRepLib_PointCloudShape
    {
    public:
      PointCloudPntFiller(double theTol)
          : BRepLib_PointCloudShape(TopoDS_Shape(), theTol)
      {
      }

      void SetPointArray(const occ::handle<Graphic3d_ArrayOfPoints>& thePoints)
      {
        myPoints = thePoints;
      }

    protected:
      void addPoint(const gp_Pnt&   thePoint,
                    const gp_Vec&   theNorm,
                    const gp_Pnt2d& theUV,
                    const TopoDS_Shape&) override
      {
        const int aPntIndex = myPoints->AddVertex(thePoint, theUV);
        if (theNorm.SquareMagnitude() > gp::Resolution())
        {
          myPoints->SetVertexNormal(aPntIndex, theNorm);
        }
        if (myPoints->HasVertexColors())
        {
          Quantity_Color aColor(360.0 * double(aPntIndex)
                                  / double(myPoints->VertexNumberAllocated()),
                                1.0,
                                0.5,
                                Quantity_TOC_HLS);
          myPoints->SetVertexColor(aPntIndex, aColor);
        }
      }

    private:
      occ::handle<Graphic3d_ArrayOfPoints> myPoints;
    };

    PointCloudPntFiller aPoitCloudTool(aTol);
    aPoitCloudTool.SetShape(aShape);
    aPoitCloudTool.SetDistance(aDist);

    int aNbPoints = isDensityPoints ? aPoitCloudTool.NbPointsByDensity(aDensity)
                                    : aPoitCloudTool.NbPointsByTriangulation();
    theDI << "Number of the generated points : " << aNbPoints << "\n";
    anArrayPoints = new Graphic3d_ArrayOfPoints(aNbPoints, aFlags);
    aPoitCloudTool.SetPointArray(anArrayPoints);
    bool isDone = isDensityPoints ? aPoitCloudTool.GeneratePointsByDensity(aDensity)
                                  : aPoitCloudTool.GeneratePointsByTriangulation();
    if (!isDone)
    {
      Message::SendFail() << "Error: Point cloud was not generated";
      return 1;
    }
  }
  else if (!aDistribution.IsEmpty())
  {
    const bool isSurface = aDistribution == "-surface";

    anArrayPoints = new Graphic3d_ArrayOfPoints(aDistNbPoints, aFlags);
    std::mt19937                     aRandomGenerator(0);
    std::uniform_real_distribution<> anAlphaDistrib(0.0, 2.0 * M_PI);
    std::uniform_real_distribution<> aBetaDistrib(0.0, 2.0 * M_PI);
    std::uniform_real_distribution<> aRadiusDistrib(0.0, aDistRadius);
    for (int aPntIt = 0; aPntIt < aDistNbPoints; ++aPntIt)
    {
      double anAlpha   = anAlphaDistrib(aRandomGenerator);
      double aBeta     = aBetaDistrib(aRandomGenerator);
      double aDistance = isSurface ? aDistRadius : aRadiusDistrib(aRandomGenerator);

      gp_Dir aDir(std::cos(anAlpha) * std::sin(aBeta),
                  std::sin(anAlpha),
                  std::cos(anAlpha) * std::cos(aBeta));
      gp_Pnt aPoint = aDistCenter.Translated(aDir.XYZ() * aDistance);

      const int anIndexOfPoint = anArrayPoints->AddVertex(aPoint);
      if (toRandColors)
      {
        Quantity_Color aColor(360.0 * double(anIndexOfPoint) / double(aDistNbPoints),
                              1.0,
                              0.5,
                              Quantity_TOC_HLS);
        anArrayPoints->SetVertexColor(anIndexOfPoint, aColor);
      }

      if (hasNormals)
      {
        anArrayPoints->SetVertexNormal(anIndexOfPoint, aDir);
      }
      if (hasUV)
      {
        anArrayPoints->SetVertexTexel(anIndexOfPoint,
                                      gp_Pnt2d(anAlpha / 2.0 * M_PI, aBeta / 2.0 * M_PI));
      }
    }
  }
  else
  {
    Message::SendFail("Error: wrong number of arguments");
    return 1;
  }

  // set array of points in point cloud object
  occ::handle<AIS_PointCloud> aPointCloud = new AIS_PointCloud();
  aPointCloud->SetPoints(anArrayPoints);
  ViewerTest::Display(aName, aPointCloud);
  return 0;
}

//=======================================================================
// function : VPriority
// purpose  : Prints or sets the display priority for an object
//=======================================================================

static int VPriority(Draw_Interpretor& theDI, int theArgNum, const char** theArgs)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  ViewerTest_AutoUpdater              anUpdateTool(aContext, ViewerTest::CurrentView());
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }

  TCollection_AsciiString aLastArg(theArgs[theArgNum - 1]);
  int                     aPriority = Graphic3d_DisplayPriority_INVALID;
  int                     aNbArgs   = theArgNum;
  if (aLastArg.IsIntegerValue())
  {
    aPriority = aLastArg.IntegerValue();
    --aNbArgs;
    if (aPriority < Graphic3d_DisplayPriority_Bottom
        || aPriority > Graphic3d_DisplayPriority_Topmost)
    {
      Message::SendFail() << "Syntax error: the specified display priority value '" << aLastArg
                          << "' is outside the valid range [" << Graphic3d_DisplayPriority_Bottom
                          << ".." << Graphic3d_DisplayPriority_Topmost << "]";
      return 1;
    }
  }
  else
  {
    anUpdateTool.Invalidate();
  }

  if (aNbArgs < 2)
  {
    Message::SendFail("Syntax error: wrong number of arguments! See usage:");
    theDI.PrintHelp(theArgs[0]);
    return 1;
  }

  for (int anArgIter = 1; anArgIter < aNbArgs; ++anArgIter)
  {
    if (anUpdateTool.parseRedrawMode(theArgs[anArgIter]))
    {
      continue;
    }

    TCollection_AsciiString            aName(theArgs[anArgIter]);
    occ::handle<AIS_InteractiveObject> anIObj;
    GetMapOfAIS().Find2(aName, anIObj);
    if (anIObj.IsNull())
    {
      Message::SendFail() << "Error: the object '" << theArgs[1] << "' is not displayed";
      return 1;
    }

    if (aPriority == Graphic3d_DisplayPriority_INVALID)
    {
      theDI << aContext->DisplayPriority(anIObj) << " ";
    }
    else
    {
      aContext->SetDisplayPriority(anIObj, (Graphic3d_DisplayPriority)aPriority);
    }
  }
  return 0;
}

//! Auxiliary class for command vnormals.
class MyShapeWithNormals : public AIS_Shape
{
  DEFINE_STANDARD_RTTI_INLINE(MyShapeWithNormals, AIS_Shape);

public:
  double NormalLength;
  int    NbAlongU;
  int    NbAlongV;
  bool   ToUseMesh;
  bool   ToOrient;

public:
  //! Main constructor.
  MyShapeWithNormals(const TopoDS_Shape& theShape)
      : AIS_Shape(theShape),
        NormalLength(10),
        NbAlongU(1),
        NbAlongV(1),
        ToUseMesh(false),
        ToOrient(false)
  {
  }

protected:
  //! Compute presentation.
  void Compute(const occ::handle<PrsMgr_PresentationManager>& thePrsMgr,
               const occ::handle<Prs3d_Presentation>&         thePrs,
               const int                                      theMode) override
  {
    AIS_Shape::Compute(thePrsMgr, thePrs, theMode);

    NCollection_DataMap<TopoDS_Face, NCollection_Vector<std::pair<gp_Pnt, gp_Pnt>>> aNormalMap;
    if (ToUseMesh)
    {
      DBRep_DrawableShape::addMeshNormals(aNormalMap, myshape, NormalLength);
    }
    else
    {
      DBRep_DrawableShape::addSurfaceNormals(aNormalMap, myshape, NormalLength, NbAlongU, NbAlongV);
    }

    occ::handle<Graphic3d_Group> aPrsGroup = thePrs->NewGroup();
    aPrsGroup->SetGroupPrimitivesAspect(myDrawer->ArrowAspect()->Aspect());

    const double aArrowAngle  = myDrawer->ArrowAspect()->Angle();
    const double aArrowLength = myDrawer->ArrowAspect()->Length();
    for (NCollection_DataMap<TopoDS_Face, NCollection_Vector<std::pair<gp_Pnt, gp_Pnt>>>::Iterator
           aFaceIt(aNormalMap);
         aFaceIt.More();
         aFaceIt.Next())
    {
      const bool toReverse = ToOrient && aFaceIt.Key().Orientation() == TopAbs_REVERSED;
      occ::handle<Graphic3d_ArrayOfSegments> aSegments =
        new Graphic3d_ArrayOfSegments(2 * aFaceIt.Value().Size());
      for (NCollection_Vector<std::pair<gp_Pnt, gp_Pnt>>::Iterator aPntIt(aFaceIt.Value());
           aPntIt.More();
           aPntIt.Next())
      {
        std::pair<gp_Pnt, gp_Pnt> aPair = aPntIt.Value();
        if (toReverse)
        {
          const gp_Vec aDir = aPair.first.XYZ() - aPair.second.XYZ();
          aPair.second      = aPair.first.XYZ() + aDir.XYZ();
        }

        aSegments->AddVertex(aPair.first);
        aSegments->AddVertex(aPair.second);
        Prs3d_Arrow::Draw(aPrsGroup,
                          aPair.second,
                          gp_Vec(aPair.first, aPair.second),
                          aArrowAngle,
                          aArrowLength);
      }

      aPrsGroup->AddPrimitiveArray(aSegments);
    }
  }
};

//=======================================================================
// function : VNormals
// purpose  : Displays/Hides normals calculated on shape geometry or retrieved from triangulation
//=======================================================================
static int VNormals(Draw_Interpretor& theDI, int theArgNum, const char** theArgs)
{
  occ::handle<AIS_InteractiveContext> aContext = ViewerTest::GetAISContext();
  if (aContext.IsNull())
  {
    Message::SendFail("Error: no active viewer");
    return 1;
  }
  else if (theArgNum < 2)
  {
    Message::SendFail("Syntax error: wrong number of arguments. See usage:");
    theDI.PrintHelp(theArgs[0]);
    return 1;
  }

  int          anArgIter  = 1;
  const char*  aShapeName = theArgs[anArgIter++];
  TopoDS_Shape aShape     = DBRep::Get(aShapeName);
  bool         isOn       = true;
  if (aShape.IsNull())
  {
    Message::SendFail() << "Error: shape with name '" << aShapeName << "' is not found";
    return 1;
  }

  NCollection_DoubleMap<occ::handle<AIS_InteractiveObject>, TCollection_AsciiString>& aMap =
    GetMapOfAIS();
  occ::handle<MyShapeWithNormals> aShapePrs;
  if (aMap.IsBound2(aShapeName))
  {
    aShapePrs = occ::down_cast<MyShapeWithNormals>(aMap.Find2(aShapeName));
  }

  bool   isUseMesh = false;
  double aLength   = 10.0;
  int    aNbAlongU = 1, aNbAlongV = 1;
  bool   isOriented = false;
  for (; anArgIter < theArgNum; ++anArgIter)
  {
    TCollection_AsciiString aParam(theArgs[anArgIter]);
    aParam.LowerCase();
    if (anArgIter == 2 && Draw::ParseOnOff(aParam.ToCString(), isOn))
    {
      continue;
    }
    else if (aParam == "-usemesh" || aParam == "-mesh")
    {
      isUseMesh = true;
    }
    else if (aParam == "-length" || aParam == "-len")
    {
      ++anArgIter;
      aLength = anArgIter < theArgNum ? Draw::Atof(theArgs[anArgIter]) : 0.0;
      if (std::abs(aLength) <= gp::Resolution())
      {
        Message::SendFail("Syntax error: length should not be zero");
        return 1;
      }
    }
    else if (aParam == "-orient" || aParam == "-oriented")
    {
      isOriented = true;
      if (anArgIter + 1 < theArgNum && Draw::ParseOnOff(theArgs[anArgIter + 1], isOriented))
      {
        ++anArgIter;
      }
    }
    else if (aParam == "-nbalongu" || aParam == "-nbu")
    {
      ++anArgIter;
      aNbAlongU = anArgIter < theArgNum ? Draw::Atoi(theArgs[anArgIter]) : 0;
      if (aNbAlongU < 1)
      {
        Message::SendFail("Syntax error: NbAlongU should be >=1");
        return 1;
      }
    }
    else if (aParam == "-nbalongv" || aParam == "-nbv")
    {
      ++anArgIter;
      aNbAlongV = anArgIter < theArgNum ? Draw::Atoi(theArgs[anArgIter]) : 0;
      if (aNbAlongV < 1)
      {
        Message::SendFail("Syntax error: NbAlongV should be >=1");
        return 1;
      }
    }
    else if (aParam == "-nbalong" || aParam == "-nbuv")
    {
      ++anArgIter;
      aNbAlongU = anArgIter < theArgNum ? Draw::Atoi(theArgs[anArgIter]) : 0;
      aNbAlongV = aNbAlongU;
      if (aNbAlongU < 1)
      {
        Message::SendFail("Syntax error: NbAlong should be >=1");
        return 1;
      }
    }
    else
    {
      Message::SendFail() << "Syntax error: unknown argument '" << aParam << "'";
      return 1;
    }
  }

  if (isOn)
  {
    if (aShapePrs.IsNull())
    {
      aShapePrs = new MyShapeWithNormals(aShape);
    }
    aShapePrs->ToUseMesh    = isUseMesh;
    aShapePrs->ToOrient     = isOriented;
    aShapePrs->NormalLength = aLength;
    aShapePrs->NbAlongU     = aNbAlongU;
    aShapePrs->NbAlongV     = aNbAlongV;
    VDisplayAISObject(aShapeName, aShapePrs);
  }
  else if (!aShapePrs.IsNull())
  {
    VDisplayAISObject(aShapeName, new AIS_Shape(aShape));
  }

  return 0;
}

//=================================================================================================

void ViewerTest::ObjectCommands(Draw_Interpretor& theCommands)
{
  const char* aGroup    = "AIS Viewer";
  const char* aFileName = __FILE__;
  auto        addCmd =
    [&](const char* theName, Draw_Interpretor::CommandFunction theFunc, const char* theHelp) {
      theCommands.Add(theName, theHelp, aFileName, theFunc, aGroup);
    };

  addCmd("vtrihedron", VTrihedron, /* [vtrihedron] */ R"(
vtrihedron name
           [-dispMode {wireframe|shading} ]
           [-origin x y z ]
           [-zaxis u v w -xaxis u v w ]
           [-drawAxes {X|Y|Z|XY|YZ|XZ|XYZ}]
           [-hideLabels {on|off}]
           [-hideArrows {on|off}]
           [-label {XAxis|YAxis|ZAxis} value]
           [-attribute {XAxisLength|YAxisLength|ZAxisLength
                       |TubeRadiusPercent|ConeRadiusPercent
                       |ConeLengthPercent|OriginRadiusPercent
                       |ShadingNumberOfFacettes} value]
           [-color {Origin|XAxis|YAxis|ZAxis|XOYAxis|YOZAxis
                   |XOZAxis|Whole} {r g b | colorName}]
           [-textColor  [XAxis|YAxis|ZAxis] {r g b | colorName}]
           [-arrowColor [XAxis|YAxis|ZAxis] {r g b | colorName}]
           [-priority {Origin|XAxis|YAxis|ZAxis|XArrow
                      |YArrow|ZArrow|XOYAxis|YOZAxis
                      |XOZAxis|Whole} value]

Creates/changes *AIS_Trihedron* object.
 -dispMode   mode of visualization: wf - wireframe,
                                    sh - shading;
             default value is wireframe;
 -origin     allows to set trihedron location;
 -zaxis/-xaxis allows to set trihedron X and Z directions;
             the directions should be orthogonal;
             Y direction is calculated;
 -drawAxes   allows to set what axes are drawn in the
             trihedron, default state is XYZ;
 -hideLabels allows to show/hide trihedron labels;
 -hideArrows allows to show/hide trihedron arrows;
 -label      allows to change default X/Y/Z titles of axes;
 -attribute  sets parameters of trihedron;
 -color      sets color properties of parts of trihedron;
 -textColor  sets color properties of trihedron labels;
 -arrowColor sets color properties of trihedron arrows;
 -priority   allows to change default selection priority
             of trihedron components.
)" /* [vtrihedron] */);

  addCmd("vtri2d", VTrihedron2D, /* [vtri2d] */ R"(
vtri2d Name : Creates a plane with a 2D trihedron from an interactively selected face.
)" /* [vtri2d] */);

  addCmd("vplanetri", VPlaneTrihedron, /* [vplanetri] */ R"(
vplanetri name
Create a plane from a trihedron selection.
If no arguments are set, the default plane is created.
)" /* [vplanetri] */);

  addCmd("vsize", VSize, /* [vsize] */ R"(
vsize [name(Default=Current)] [size(Default=100)]
Changes the size of a named or selected trihedron.
If the name is not defined: it affects the selected trihedrons otherwise nothing is done.
If the value is not defined: it is set to 100 by default.
)" /* [vsize] */);

  addCmd("vaxis", VAxisBuilder, /* [vaxis] */ R"(
vaxis name [Xa] [Ya] [Za] [Xb] [Yb] [Zb]
Creates an axis. If  the values are not defined,
an axis is created by interactive selection of two vertices or one edge.
)" /* [vaxis] */);

  addCmd("vaxispara", VAxisBuilder, /* [vaxispara] */ R"(
vaxispara name
Creates an axis by interactive selection of an edge and a vertex.
)" /* [vaxispara] */);

  addCmd("vaxisortho", VAxisBuilder, /* [vaxisortho] */ R"(
vaxisortho name
Creates an axis by interactive selection of an edge and a vertex.
The axis will be orthogonal to the selected edge.
)" /* [vaxisortho] */);

  addCmd("vpoint", VPointBuilder, /* [vpoint] */ R"(
vpoint name [X Y [Z]] [-2d] [-nosel]
Creates a point from coordinates.
If the values are not defined, a point is created from selected vertex or edge (center).
 -2d    defines on-screen 2D point from top-left window corner;
 -nosel creates non-selectable presentation.
)" /* [vpoint] */);

  addCmd("vplane", VPlaneBuilder, /* [vplane] */ R"(
vplane PlaneName [AxisName/PlaneName/PointName]
       [PointName/PointName/PointName] [Nothing/Nothing/PointName] [TypeOfSensitivity {0|1}]
Creates a plane from named or interactively selected entities. TypeOfSensitivity:
  0 - Interior;
  1 - Boundary.
)" /* [vplane] */);

  addCmd("vchangeplane", VChangePlane, /* [vchangeplane] */ R"(
vchangeplane plane_name
             [x=center_x y=center_y z=center_z]
             [dx=dir_x dy=dir_y dz=dir_z]
             [sx=size_x sy=size_y]
             [minsize=value]
             [noupdate]
Changes parameters of the plane:
 - x y z     - center
 - dx dy dz  - normal
 - sx sy     - plane sizes
 - noupdate  - do not update/redisplay the plane in context
Please enter coordinates in format "param=value" in arbitrary order.
)" /* [vchangeplane] */);

  addCmd("vplanepara", VPlaneBuilder, /* [vplanepara] */ R"(
vplanepara  PlaneName
Creates a plane from interactively selected vertex and face.
)" /* [vplanepara] */);

  addCmd("vplaneortho", VPlaneBuilder, /* [vplaneortho] */ R"(
vplaneortho  PlaneName
Creates a plane from interactive selected face and coplanar edge.
)" /* [vplaneortho] */);

  addCmd("vline", VLineBuilder, /* [vline] */ R"(
vline LineName [Xa/PointName] [Ya/PointName] [Za] [Xb] [Yb] [Zb]
Creates a line from coordinates, named or interactively selected vertices.
)" /* [vline] */);

  addCmd("vcircle", VCircleBuilder, /* [vcircle] */ R"(
vcircle CircleName [PointName PointName PointName IsFilled] [UStart UEnd]
                   [PlaneName PointName Radius IsFilled] [UStart UEnd]
Creates a circle from named or interactively selected entities.
Parameter IsFilled is defined as 0 or 1.
)" /* [vcircle] */);

  addCmd("vdrawtext", VDrawText, /* [vdrawtext] */ R"(
vdrawtext name text
          [-pos X Y Z]={0 0 0}
          [-color {R G B|name}]=yellow
          [-halign {left|center|right}]=left
          [-valign {top|center|bottom|topfirstline}}]=bottom
          [-angle angle]=0
          [-zoom {0|1}]=0
          [-height height]=16
          [-wrapping width]=40
          [-wordwrapping {0|1}]=1
          [-aspect {regular|bold|italic|boldItalic}]=regular
          [-font font]=Times
          [-2d] [-perspos {X Y Z}]={0 0 0}
          [-disptype {blend|decal|shadow|subtitle|dimension|normal}}=normal
          [-subcolor {R G B|name}]=white
          [-noupdate]
          [-plane NormX NormY NormZ DirX DirY DirZ]
          [-flipping] [-ownanchor {0|1}]=1
Display text label at specified position.
Within -perspos, X and Y define the coordinate origin in 2d space relative to the view window.
Example: X=0 Y=0 is center, X=1 Y=1 is upper right corner etc...
Z coordinate defines the gap from border of view window (except center position).
)" /* [vdrawtext] */);

  addCmd("vdrawsphere", VDrawSphere, /* [vdrawsphere] */ R"(
vdrawsphere shapeName Fineness [X=0.0 Y=0.0 Z=0.0] [Radius=100.0] [ToShowEdges=0] [ToPrintInfo=1]
)" /* [vdrawsphere] */);

  addCmd("vlocation", VSetLocation, /* [vlocation] */ R"(
vlocation name
    [-reset] [-copyFrom otherName]
    [-translate    X Y [Z]] [-rotate    x y z dx dy dz angle] [-scale    [X Y Z] scale]
    [-pretranslate X Y [Z]] [-prerotate x y z dx dy dz angle] [-prescale [X Y Z] scale]
    [-mirror x y z dx dy dz] [-premirror x y z dx dy dz]
    [-setLocation X Y [Z]] [-setRotation QX QY QZ QW] [-setScale [X Y Z] scale]
Object local transformation management:
 -reset        resets transformation to identity
 -translate    applies translation vector
 -rotate       applies rotation around axis
 -scale        applies scale factor with optional anchor
 -mirror       applies mirror transformation
 -pretranslate pre-multiplies translation vector
 -prerotate    pre-multiplies rotation around axis
 -prescale     pre-multiplies scale  transformation
 -premirror    pre-multiplies mirror transformation
 -setLocation  overrides translation part
 -setRotation  overrides rotation part with specified quaternion
 -setScale     overrides scale factor
)" /* [vlocation] */);

  addCmd("vsetlocation", VSetLocation, /* [vsetlocation] */ R"(
Alias for vlocation
)" /* [vsetlocation] */);

  addCmd("vchild", VChild, /* [vchild] */ R"(
vchild parent [-add] [-remove] [-ignoreParentTrsf {0|1}] child1 [child2] [...]
Command for testing low-level presentation connections.
vconnect command should be used instead.
)" /* [vchild] */);

  addCmd("vparent", VParent, /* [vparent] */ R"(
vparent parent [-ignoreVisu]
Command for testing object properties as parent in the hierarchy.
 -ignoreVisu do not propagate the visual state (display/erase/color) to children objects
)" /* [vparent] */);

  addCmd("vcomputehlr", VComputeHLR, /* [vcomputehlr] */ R"(
vcomputehlr shapeInput hlrResult [-algoType {algo|polyAlgo}=polyAlgo]
    [eyeX eyeY eyeZ dirX dirY dirZ upX upY upZ]
    [-showTangentEdges {on|off}=off] [-nbIsolines N=0] [-showHiddenEdges {on|off}=off]
Arguments:
  shapeInput - name of the initial shape
  hlrResult  - result HLR object from initial shape
  eye, dir are eye position and look direction
  up is the look up direction vector
 -algoType HLR algorithm to use
 -showTangentEdges include tangent edges
 -nbIsolines include isolines
 -showHiddenEdges include hidden edges
Use vtop to see projected HLR shape.
)" /* [vcomputehlr] */);

  addCmd("vdrawparray", VDrawPArray, /* [vdrawparray] */ R"(
vdrawparray name TypeOfArray={points|segments|polylines|triangles
                   |trianglefans|trianglestrips|quads|quadstrips|polygons}
            [-deinterleaved|-mutable]
            [vertex={'v' x y z [normal={'n' nx ny nz}] [color={'c' r g b}] [texel={'t' tx ty}]]
            [bound= {'b' nbVertices [bound_color={'c' r g b}]]
            [edge=  {'e' vertexId]
            [-shape shapeName] [-patch]
Commands create an Interactive Object for specified Primitive Array definition
with the main purpose is covering various combinations by tests.
)" /* [vdrawparray] */);

  addCmd("vconnect", VConnect, /* [vconnect] */ R"(
vconnect name Xo Yo Zo object1 object2 ... [color=NAME]
Creates and displays AIS_ConnectedInteractive object from input object and location.
)" /* [vconnect] */);

  addCmd("vconnectto", VConnectTo, /* [vconnectto] */ R"(
vconnectto instance_name Xo Yo Zo object [-nodisplay|-noupdate|-update]
Makes an instance 'instance_name' of 'object' with position (Xo Yo Zo).
 -nodisplay - only creates interactive object, but not displays it.
)" /* [vconnectto] */);

  addCmd("vdisconnect", VDisconnect, /* [vdisconnect] */ R"(
vdisconnect assembly_name {object_name|object_number|'all'}
Disconnects all objects from assembly or disconnects object by name or number.
Use vlistconnected to enumerate assembly children.
)" /* [vdisconnect] */);

  addCmd("vaddconnected", VAddConnected, /* [vaddconnected] */ R"(
vaddconnected assembly_name object_name
Adds object to assembly.
)" /* [vaddconnected] */);

  addCmd("vlistconnected", VListConnected, /* [vlistconnected] */ R"(
vlistconnected assembly_name
Lists objects in assembly.
)" /* [vlistconnected] */);

  addCmd("vselmode", VSetSelectionMode, /* [vselmode] */ R"(
vselmode [object] selectionMode {on|off}
         [{-add|-set|-globalOrLocal}=-globalOrLocal]
Switches selection mode for the specified object or for all objects in context.
Selection mode is either an integer number specific to Interactive Object,
or sub-shape type in case of AIS_Shape:
  Shape, Vertex, Edge, Wire, Face, Shell, Solid, CompSolid, Compound
The integer mode 0 (Shape in case of AIS_Shape) is reserved for selecting object as whole.
Additional options:
 -add           already activated selection modes will be left activated
 -set           already activated selection modes will be deactivated
 -globalOrLocal (default) if new mode is Global selection mode,
                then active local selection modes will be deactivated
                and the samthen active local selection modes will be deactivated
)" /* [vselmode] */);

  addCmd("vselnext", VSelectionNext, /* [vselnext] */ R"(
vselnext : hilight next detected
)" /* [vselnext] */);

  addCmd("vselprev", VSelectionPrevious, /* [vselprev] */ R"(
vselnext : hilight previous detected
)" /* [vselprev] */);

  addCmd("vtriangle", VTriangle, /* [vtriangle] */ R"(
vtriangle Name PointName PointName PointName
Creates and displays a filled triangle from named points.
)" /* [vtriangle] */);

  addCmd("vsegment", VTriangle, /* [vsegment] */ R"(
vsegment Name PointName PointName
Creates and displays a segment from named points.
)" /* [vsegment] */);

  addCmd("vtorus", VTorus, /* [vtorus] */ R"(
vtorus name [R1 R2 [Angle1=0 Angle2=360] [Angle=360]]
       [-radius R1] [-pipeRadius R2]
       [-pipeAngle Angle=360] [-segmentAngle1 Angle1=0 -segmentAngle2 Angle2=360]
       [-nbSlices Number=100] [-nbStacks Number=100] [-noupdate]
Creates and displays a torus or torus segment.
Parameters of the torus:
 - R1     distance from the center of the pipe to the center of the torus
 - R2     radius of the pipe
 - Angle1 first angle to create a torus ring segment
 - Angle2 second angle to create a torus ring segment
 - Angle  angle to create a torus pipe segment
)" /* [vtorus] */);

  addCmd("vcylinder", VCylinder, /* [vcylinder] */ R"(
vcylinder name [R1 R2 Height] [-height H] [-radius R] [-bottomRadius R1 -topRadius R2]
               [-nbSlices Number=100] [-noupdate]
Creates and displays a cylinder.
Parameters of the cylinder:
 - R1     cylinder bottom radius
 - R2     cylinder top radius
 - Height cylinder height
)" /* [vcylinder] */);

  addCmd("vsphere", VSphere, /* [vsphere] */ R"(
vsphere name [-radius] R
             [-nbSlices Number=100] [-nbStacks Number=100] [-noupdate]
Creates and displays a sphere.
)" /* [vsphere] */);

  addCmd("vobjzlayer", VObjZLayer, /* [vobjzlayer] */ R"(
vobjzlayer : set/get object [layerid] - set or get z layer id for the interactive object
)" /* [vobjzlayer] */);

  addCmd("vpolygonoffset", VPolygonOffset, /* [vpolygonoffset] */ R"(
vpolygonoffset [object [mode factor units]]
Sets/gets polygon offset parameters for an object; without arguments prints the default values
)" /* [vpolygonoffset] */);

  addCmd("vmarkerstest", VMarkersTest, /* [vmarkerstest] */ R"(
vmarkerstest: name X Y Z [PointsOnSide=10] [MarkerType=0] [Scale=1.0] [FileName=ImageFile]
)" /* [vmarkerstest] */);

  addCmd("text2brep", TextToBRep, /* [text2brep] */ R"(
text2brep name text"
          [-pos X=0 Y=0 Z=0]"
          [-halign {left|center|right}=left]"
          [-valign {top|center|bottom|topfirstline}=bottom}]"
          [-height height=16]"
          [-aspect {regular|bold|italic|boldItalic}=regular]"
          [-font font=Courier] [-strict {strict|aliases|any}=any]"
          [-composite {on|off}=off]"
          [-plane NormX NormY NormZ DirX DirY DirZ]",
)" /* [text2brep] */);

  addCmd("vfont", VFont, /* [vfont] */ R"(
vfont [-add pathToFont [fontName] [regular,bold,italic,boldItalic=undefined] [singleStroke]]
      [-strict {any|aliases|strict}] [-find fontName [regular,bold,italic,boldItalic=undefined]]
      [-verbose {on|off}]
      [-findAll fontNameMask] [-findInfo fontName]
      [-unicodeFallback {on|off}]
      [-clear] [-init] [-list] [-names]
      [-aliases [aliasName]] [-addAlias Alias FontName] [-removeAlias Alias FontName]
      [-clearAlias Alias] [-clearAliases]
Work with font registry - register font, list available fonts, find font.
 -findAll  is same as -find, but can print more than one font when mask is passed.
 -findInfo is same as -find, but prints complete font information instead of family name.
)" /* [vfont] */);

  addCmd("vvertexmode", VVertexMode, /* [vvertexmode] */ R"(
vvertexmode [name | -set {isolated|all|inherited} [name1 name2 ...]]
Sets the vertex draw mode for the specified object(s)
or sets default vertex draw mode and updates the mode for all displayed objects.
Prints the default vertex draw mode without -set parameter.
)" /* [vvertexmode] */);

  addCmd("vpointcloud", VPointCloud, /* [vpointcloud] */ R"(
vpointcloud name shape [-randColor {0|1}]=0 [-normals {0|1}]=1 [-uv {0|1}]=0
            [-distance Value]=0.0 [-density Value] [-tolerance Value]
Create an interactive object for arbitrary set of points from triangulated shape.

vpointcloud name {-surface|-volume} x y z r npts
            [-randColor] [-normals] [-uv]
Create arbitrary set of points (npts) randomly distributed
on spheric surface or within spheric volume (x y z r).

Additional options:
 -normals   generate or not normal per point
 -uv        generate UV (texel) coordinates per point
 -randColor generate random color per point
 -distance  distance from shape into the range [0, Value];
 -density   density of points to generate randomly on surface;
 -tolerance cloud generator's tolerance; default value is Precision::Confusion();

)" /* [vpointcloud] */);

  addCmd("vpriority", VPriority, /* [vpriority] */ R"(
vpriority [-noupdate|-update] name [value]
Prints or sets the display priority for an object.
)" /* [vpriority] */);

  addCmd("vnormals", VNormals, /* [vnormals] */ R"(
vnormals Shape [{on|off}=on] [-length {10}] [-nbAlongU {1}] [-nbAlongV {1}] [-nbAlong {1}]
               [-useMesh] [-oriented {0}1}=0]
Displays/Hides normals calculated on shape geometry or retrieved from triangulation
)" /* [vnormals] */);
}
