]> OCCT Git - occt.git/commitdiff
0025209: Draw Harness - add command 'vnormals' and extend command 'normals' to show...
authoroan <oan@opencascade.com>
Sun, 31 Jul 2016 10:09:37 +0000 (13:09 +0300)
committerbugmaster <bugmaster@opencascade.com>
Thu, 20 Apr 2017 11:54:47 +0000 (14:54 +0300)
src/DBRep/DBRep.cxx
src/DBRep/DBRep_DrawableShape.cxx
src/DBRep/DBRep_DrawableShape.hxx
src/ViewerTest/ViewerTest_ObjectCommands.cxx

index b37942f4c07a556faa1a270446b7b6e6f9b0ad8c..dfe37e36484f28498981070c16e866672f5947b4 100644 (file)
@@ -31,6 +31,7 @@
 #include <gp_Ax2.hxx>
 #include <GProp.hxx>
 #include <GProp_GProps.hxx>
+#include <NCollection_Vector.hxx>
 #include <Poly_Triangulation.hxx>
 #include <Precision.hxx>
 #include <Standard.hxx>
@@ -1118,73 +1119,124 @@ static Standard_Integer check(Draw_Interpretor& ,
 //=======================================================================
 // normals
 //=======================================================================
-
-static Standard_Integer normals(Draw_Interpretor& di,
-                               Standard_Integer n, const char** a)
+static Standard_Integer normals (Draw_Interpretor& theDI,
+                                 Standard_Integer  theArgNum,
+                                 const char**      theArgs)
 {
-  if (n <= 1) return 1;
-  Standard_Real l = 1.;
-  if (n > 2) 
-    l = Draw::Atof(a[2]);
-
-  TopoDS_Shape S = DBRep::Get(a[1]);
-  if (S.IsNull()) return 1;
-
-  DBRep_WriteColorOrientation();
-
-  gp_Pnt P1,P2;
-  gp_Vec V,V1,V2;
-  Draw_Color col;
+  if (theArgNum < 2)
+  {
+    std::cout << "Syntax error: wrong number of arguments!\n";
+    theDI.PrintHelp (theArgs[0]);
+    return 1;
+  }
 
-  TopExp_Explorer ex(S,TopAbs_FACE);
-  while (ex.More()) {
+  TopoDS_Shape aShape = DBRep::Get (theArgs[1]);
+  if (aShape.IsNull())
+  {
+    std::cout << "Error: shape with name " << theArgs[1] << " is not found\n";
+    return 1;
+  }
 
-    const TopoDS_Face& F = TopoDS::Face(ex.Current());
-    
-    // find the center of the minmax
-    BRepAdaptor_Surface SF(F);
-
-    Standard_Real u, v, x;
-
-    u = SF.FirstUParameter();
-    x = SF.LastUParameter();
-    if (Precision::IsInfinite(u))
-      u =  (Precision::IsInfinite(x)) ? 0. : x;
-    else if (!Precision::IsInfinite(x))
-      u = (u+x) / 2.;
-
-    v = SF.FirstVParameter();
-    x = SF.LastVParameter();
-    if (Precision::IsInfinite(v))
-      v =  (Precision::IsInfinite(x)) ? 0. : x;
-    else if (!Precision::IsInfinite(x))
-      v = (v+x) / 2.;
-
-    SF.D1(u,v,P1,V1,V2);
-    V = V1.Crossed(V2);
-    x = V.Magnitude();
-    if (x > 1.e-10) 
-      V.Multiply(l/x);
-    else {
-      V.SetCoord(l/2.,0,0);
-      di << "Null normal\n";
+  Standard_Boolean toUseMesh = Standard_False;
+  Standard_Real    aLength   = 10.0;
+  Standard_Integer aNbAlongU = 1, aNbAlongV = 1;
+  for (Standard_Integer anArgIter = 2; anArgIter< theArgNum; ++anArgIter)
+  {
+    TCollection_AsciiString aParam (theArgs[anArgIter]);
+    aParam.LowerCase();
+    if (anArgIter == 2
+     && aParam.IsRealValue())
+    {
+      aLength = aParam.RealValue();
+      if (Abs (aLength) <= gp::Resolution())
+      {
+        std::cout << "Syntax error: length should not be zero\n";
+        return 1;
+      }
+    }
+    else if (aParam == "-usemesh"
+          || aParam == "-mesh")
+    {
+      toUseMesh = Standard_True;
     }
+    else if (aParam == "-length"
+          || aParam == "-len")
+    {
+      ++anArgIter;
+      aLength = anArgIter < theArgNum ? Draw::Atof(theArgs[anArgIter]) : 0.0;
+      if (Abs(aLength) <= gp::Resolution())
+      {
+        std::cout << "Syntax error: length should not be zero\n";
+        return 1;
+      }
+    }
+    else if (aParam == "-nbalongu"
+          || aParam == "-nbu")
+    {
+      ++anArgIter;
+      aNbAlongU = anArgIter< theArgNum ? Draw::Atoi (theArgs[anArgIter]) : 0;
+      if (aNbAlongU < 1)
+      {
+        std::cout << "Syntax error: NbAlongU should be >=1\n";
+        return 1;
+      }
+    }
+    else if (aParam == "-nbalongv"
+          || aParam == "-nbv")
+    {
+      ++anArgIter;
+      aNbAlongV = anArgIter< theArgNum ? Draw::Atoi (theArgs[anArgIter]) : 0;
+      if (aNbAlongV < 1)
+      {
+        std::cout << "Syntax error: NbAlongV should be >=1\n";
+        return 1;
+      }
+    }
+    else if (aParam == "-nbalong"
+          || aParam == "-nbuv")
+    {
+      ++anArgIter;
+      aNbAlongU = anArgIter< theArgNum ? Draw::Atoi (theArgs[anArgIter]) : 0;
+      aNbAlongV = aNbAlongU;
+      if (aNbAlongU < 1)
+      {
+        std::cout << "Syntax error: NbAlong should be >=1\n";
+        return 1;
+      }
+    }
+    else
+    {
+      std::cout << "Syntax error: unknwon argument '" << aParam << "'\n";
+      return 1;
+    }
+  }
 
-    P2 = P1;
-    P2.Translate(V);
+  DBRep_WriteColorOrientation();
 
-    col = DBRep_ColorOrientation(F.Orientation());
+  NCollection_DataMap<TopoDS_Face, NCollection_Vector<std::pair<gp_Pnt, gp_Pnt> > > aNormals;
+  if (toUseMesh)
+  {
+    DBRep_DrawableShape::addMeshNormals (aNormals, aShape, aLength);
+  }
+  else
+  {
+    DBRep_DrawableShape::addSurfaceNormals (aNormals, aShape, aLength, aNbAlongU, aNbAlongV);
+  }
 
-    Handle(Draw_Segment3D) seg = new Draw_Segment3D(P1,P2,col);
-    dout << seg;
-    
-    
-    ex.Next();
+  for (NCollection_DataMap<TopoDS_Face, NCollection_Vector<std::pair<gp_Pnt, gp_Pnt> > >::Iterator aFaceIt (aNormals); aFaceIt.More(); aFaceIt.Next())
+  {
+    const Draw_Color aColor = DBRep_ColorOrientation (aFaceIt.Key().Orientation());
+    for (NCollection_Vector<std::pair<gp_Pnt, gp_Pnt> >::Iterator aNormalsIt (aFaceIt.Value()); aNormalsIt.More(); aNormalsIt.Next())
+    {
+      const std::pair<gp_Pnt, gp_Pnt>& aVec = aNormalsIt.Value();
+      Handle(Draw_Segment3D) aSeg = new Draw_Segment3D(aVec.first, aVec.second, aColor);
+      dout << aSeg;
+    }
   }
+
   return 0;
 }
 
-
 //=======================================================================
 //function : Set
 //purpose  : 
@@ -1354,7 +1406,7 @@ void  DBRep::BasicCommands(Draw_Interpretor& theCommands)
   theCommands.Add("treverse","treverse name1 name2 ...",__FILE__,orientation,g);
   theCommands.Add("complement","complement name1 name2 ...",__FILE__,orientation,g);
   theCommands.Add("invert","invert name, reverse subshapes",__FILE__,invert,g);
-  theCommands.Add("normals","normals s (length = 10), disp normals",__FILE__,normals,g);
+  theCommands.Add("normals","normals shape [Length {10}] [-NbAlongU {1}] [-NbAlongV {1}] [-UseMesh], display normals",__FILE__,normals,g);
   theCommands.Add("nbshapes",
                   "\n nbshapes s - shows the number of sub-shapes in <s>;\n nbshapes s -t - shows the number of sub-shapes in <s> counting the same sub-shapes with different location as different sub-shapes.",
                   __FILE__,nbshapes,g);
index 05581002c991d5552d7057856ff30adab89f6af1..9165396c327a257871c28911469089fa8a85de0e 100644 (file)
@@ -1174,3 +1174,161 @@ void  DBRep_DrawableShape::display(const Handle(Poly_Triangulation)& T,
   }
 }
 
+//=======================================================================
+//function : addMeshNormals
+//purpose  :
+//=======================================================================
+Standard_Boolean DBRep_DrawableShape::addMeshNormals (NCollection_Vector<std::pair<gp_Pnt, gp_Pnt> >& theNormals,
+                                                      const TopoDS_Face& theFace,
+                                                      const Standard_Real theLength)
+{
+  TopLoc_Location aLoc;
+  const Handle(Poly_Triangulation)& aTriangulation = BRep_Tool::Triangulation (theFace, aLoc);
+  const Standard_Boolean hasNormals = aTriangulation->HasNormals();
+  if (aTriangulation.IsNull()
+  || (!hasNormals && !aTriangulation->HasUVNodes()))
+  {
+    return Standard_False;
+  }
+
+  const TColgp_Array1OfPnt& aNodes = aTriangulation->Nodes();
+  BRepAdaptor_Surface aSurface (theFace);
+  for (Standard_Integer aNodeIter = aNodes.Lower(); aNodeIter <= aNodes.Upper(); ++aNodeIter)
+  {
+    gp_Pnt aP1 = aNodes (aNodeIter);
+    if (!aLoc.IsIdentity())
+    {
+      aP1.Transform (aLoc.Transformation());
+    }
+
+    gp_Vec aNormal;
+    if (hasNormals)
+    {
+      aNormal = aTriangulation->Normal (aNodeIter);
+    }
+    else
+    {
+      const gp_Pnt2d& aUVNode = aTriangulation->UVNode (aNodeIter);
+      gp_Pnt aDummyPnt;
+      gp_Vec aV1, aV2;
+      aSurface.D1 (aUVNode.X(), aUVNode.Y(), aDummyPnt, aV1, aV2);
+      aNormal = aV1.Crossed (aV2);
+    }
+
+    const Standard_Real aNormalLen = aNormal.Magnitude();
+    if (aNormalLen > 1.e-10)
+    {
+      aNormal.Multiply (theLength / aNormalLen);
+    }
+    else
+    {
+      aNormal.SetCoord (aNormalLen / 2.0, 0.0, 0.0);
+      std::cout << "Null normal at node X = " << aP1.X() << ", Y = " << aP1.Y() << ", Z = " << aP1.Z() << "\n";
+    }
+
+    const gp_Pnt aP2 = aP1.Translated (aNormal);
+    theNormals.Append (std::pair<gp_Pnt, gp_Pnt> (aP1, aP2));
+  }
+  return Standard_True;
+}
+
+//=======================================================================
+//function : addMeshNormals
+//purpose  :
+//=======================================================================
+void DBRep_DrawableShape::addMeshNormals (NCollection_DataMap<TopoDS_Face, NCollection_Vector<std::pair<gp_Pnt, gp_Pnt> > > & theNormals,
+                                          const TopoDS_Shape& theShape,
+                                          const Standard_Real theLength)
+{
+  TopLoc_Location aLoc;
+  for (TopExp_Explorer aFaceIt(theShape, TopAbs_FACE); aFaceIt.More(); aFaceIt.Next())
+  {
+    const TopoDS_Face& aFace = TopoDS::Face(aFaceIt.Current());
+    NCollection_Vector<std::pair<gp_Pnt, gp_Pnt> >* aFaceNormals = theNormals.ChangeSeek(aFace);
+    if (aFaceNormals == NULL)
+    {
+      aFaceNormals = theNormals.Bound(aFace, NCollection_Vector<std::pair<gp_Pnt, gp_Pnt> >());
+    }
+
+    addMeshNormals (*aFaceNormals, aFace, theLength);
+  }
+}
+
+//=======================================================================
+//function : addSurfaceNormals
+//purpose  :
+//=======================================================================
+Standard_Boolean DBRep_DrawableShape::addSurfaceNormals (NCollection_Vector<std::pair<gp_Pnt, gp_Pnt> >& theNormals,
+                                                         const TopoDS_Face&     theFace,
+                                                         const Standard_Real    theLength,
+                                                         const Standard_Integer theNbAlongU,
+                                                         const Standard_Integer theNbAlongV)
+{
+  {
+    TopLoc_Location aLoc;
+    const Handle(Geom_Surface)& aSurface = BRep_Tool::Surface (theFace, aLoc);
+    if (aSurface.IsNull())
+    {
+      return Standard_False;
+    }
+  }
+
+  Standard_Real aUmin = 0.0, aVmin = 0.0, aUmax = 0.0, aVmax = 0.0;
+  BRepTools::UVBounds (theFace, aUmin, aUmax, aVmin, aVmax);
+  const Standard_Boolean isUseMidU = (theNbAlongU == 1);
+  const Standard_Boolean isUseMidV = (theNbAlongV == 1);
+  const Standard_Real aDU = (aUmax - aUmin) / (isUseMidU ? 2 : (theNbAlongU - 1));
+  const Standard_Real aDV = (aVmax - aVmin) / (isUseMidV ? 2 : (theNbAlongV - 1));
+
+  BRepAdaptor_Surface aSurface (theFace);
+  for (Standard_Integer aUIter = 0; aUIter < theNbAlongU; ++aUIter)
+  {
+    const Standard_Real aU = aUmin + (isUseMidU ? 1 : aUIter) * aDU;
+    for (Standard_Integer aVIter = 0; aVIter < theNbAlongV; ++aVIter)
+    {
+      const Standard_Real aV = aVmin + (isUseMidV ? 1 : aVIter) * aDV;
+
+      gp_Pnt aP1;
+      gp_Vec aV1, aV2;
+      aSurface.D1 (aU, aV, aP1, aV1, aV2);
+
+      gp_Vec aVec = aV1.Crossed (aV2);
+      Standard_Real aNormalLen = aVec.Magnitude();
+      if (aNormalLen > 1.e-10)
+      {
+        aVec.Multiply (theLength / aNormalLen);
+      }
+      else
+      {
+        aVec.SetCoord (aNormalLen / 2.0, 0.0, 0.0);
+        std::cout << "Null normal at U = " << aU << ", V = " << aV << "\n";
+      }
+
+      const gp_Pnt aP2 = aP1.Translated(aVec);
+      theNormals.Append (std::pair<gp_Pnt, gp_Pnt> (aP1, aP2));
+    }
+  }
+  return Standard_True;
+}
+
+//=======================================================================
+//function : addSurfaceNormals
+//purpose  :
+//=======================================================================
+void DBRep_DrawableShape::addSurfaceNormals (NCollection_DataMap<TopoDS_Face, NCollection_Vector<std::pair<gp_Pnt, gp_Pnt> > >& theNormals,
+                                             const TopoDS_Shape&    theShape,
+                                             const Standard_Real    theLength,
+                                             const Standard_Integer theNbAlongU,
+                                             const Standard_Integer theNbAlongV)
+{
+  for (TopExp_Explorer aFaceIt (theShape, TopAbs_FACE); aFaceIt.More(); aFaceIt.Next())
+  {
+    const TopoDS_Face& aFace = TopoDS::Face (aFaceIt.Current());
+    NCollection_Vector<std::pair<gp_Pnt, gp_Pnt> >* aFaceNormals = theNormals.ChangeSeek (aFace);
+    if (aFaceNormals == NULL)
+    {
+      aFaceNormals = theNormals.Bound (aFace, NCollection_Vector<std::pair<gp_Pnt, gp_Pnt> >());
+    }
+    addSurfaceNormals (*aFaceNormals, aFace, theLength, theNbAlongU, theNbAlongV);
+  }
+}
index cc7b48741000aeecef2eb0f6cfc9b1b97594c066..1176bca039ed91aa2a7e227986ffe372aca9173c 100644 (file)
 #ifndef _DBRep_DrawableShape_HeaderFile
 #define _DBRep_DrawableShape_HeaderFile
 
-#include <Standard.hxx>
-#include <Standard_Type.hxx>
-
-#include <TopoDS_Shape.hxx>
 #include <DBRep_ListOfEdge.hxx>
 #include <DBRep_ListOfFace.hxx>
 #include <DBRep_ListOfHideData.hxx>
-#include <Standard_Real.hxx>
-#include <Standard_Integer.hxx>
 #include <Draw_Color.hxx>
-#include <Standard_Boolean.hxx>
 #include <Draw_Drawable3D.hxx>
-#include <Standard_OStream.hxx>
 #include <Draw_Interpretor.hxx>
-class Standard_DomainError;
-class TopoDS_Shape;
-class Draw_Color;
+#include <NCollection_DataMap.hxx>
+#include <NCollection_Vector.hxx>
+#include <Standard_OStream.hxx>
+#include <TopoDS_Shape.hxx>
+
 class Draw_Display;
 class Poly_Triangulation;
 class gp_Trsf;
-class Draw_Drawable3D;
-
-
-class DBRep_DrawableShape;
-DEFINE_STANDARD_HANDLE(DBRep_DrawableShape, Draw_Drawable3D)
 
 //! Drawable structure to display a  shape. Contains a
 //! list of edges and a list of faces.
 class DBRep_DrawableShape : public Draw_Drawable3D
 {
-
+  DEFINE_STANDARD_RTTIEXT(DBRep_DrawableShape, Draw_Drawable3D)
 public:
-
   
   Standard_EXPORT DBRep_DrawableShape(const TopoDS_Shape& C, const Draw_Color& FreeCol, const Draw_Color& ConnCol, const Draw_Color& EdgeCol, const Draw_Color& IsosCol, const Standard_Real size, const Standard_Integer nbisos, const Standard_Integer discret);
   
@@ -106,10 +94,50 @@ public:
   //! u,v are the parameters of the closest point.
   Standard_EXPORT static void LastPick (TopoDS_Shape& S, Standard_Real& u, Standard_Real& v);
 
+public:
 
-
-
-  DEFINE_STANDARD_RTTIEXT(DBRep_DrawableShape,Draw_Drawable3D)
+  //! Auxiliary method computing nodal normals for presentation purposes.
+  //! @param theNormals [out] vector of computed normals (pair of points [from, to])
+  //! @param theFace    [in]  input face
+  //! @param theLength  [in]  normal length
+  //! @return FALSE if normals can not be computed
+  Standard_EXPORT static Standard_Boolean addMeshNormals (NCollection_Vector<std::pair<gp_Pnt, gp_Pnt> >& theNormals,
+                                                          const TopoDS_Face&  theFace,
+                                                          const Standard_Real theLength);
+
+  //! Auxiliary method computing nodal normals for presentation purposes.
+  //! @param theNormals [out] map of computed normals (grouped per Face)
+  //! @param theShape   [in]  input shape which will be exploded into Faces
+  //! @param theLength  [in]  normal length
+  Standard_EXPORT static void addMeshNormals (NCollection_DataMap<TopoDS_Face, NCollection_Vector<std::pair<gp_Pnt, gp_Pnt> > > & theNormals,
+                                              const TopoDS_Shape& theShape,
+                                              const Standard_Real theLength);
+
+  //! Auxiliary method computing surface normals distributed within the Face for presentation purposes.
+  //! @param theNormals  [out] vector of computed normals (pair of points [from, to])
+  //! @param theFace     [in]  input face
+  //! @param theLength   [in]  normal length
+  //! @param theNbAlongU [in]  number along U
+  //! @param theNbAlongV [in]  number along V
+  //! @return FALSE if normals can not be computed
+  Standard_EXPORT static Standard_Boolean addSurfaceNormals (NCollection_Vector<std::pair<gp_Pnt, gp_Pnt> >& theNormals,
+                                                             const TopoDS_Face&     theFace,
+                                                             const Standard_Real    theLength,
+                                                             const Standard_Integer theNbAlongU,
+                                                             const Standard_Integer theNbAlongV);
+
+  //! Auxiliary method computing surface normals distributed within the Face for presentation purposes.
+  //! @param theNormals  [out] map of computed normals (grouped per Face)
+  //! @param theShape    [in]  input shape which will be exploded into Faces
+  //! @param theLength   [in]  normal length
+  //! @param theNbAlongU [in]  number along U
+  //! @param theNbAlongV [in]  number along V
+  //! @return FALSE if normals can not be computed
+  Standard_EXPORT static void addSurfaceNormals (NCollection_DataMap<TopoDS_Face, NCollection_Vector<std::pair<gp_Pnt, gp_Pnt> > >& theNormals,
+                                                 const TopoDS_Shape&    theShape,
+                                                 const Standard_Real    theLength,
+                                                 const Standard_Integer theNbAlongU,
+                                                 const Standard_Integer theNbAlongV);
 
 private:
 
@@ -142,13 +170,8 @@ private:
   Standard_Boolean myHid;
   Standard_Real myAng;
 
-
 };
 
-
-
-
-
-
+DEFINE_STANDARD_HANDLE(DBRep_DrawableShape, Draw_Drawable3D)
 
 #endif // _DBRep_DrawableShape_HeaderFile
index 24092370e028a6f819cde0b2fec5a862921eb17b..2795ce9b61ba4101f7dd65aec9622f6aa25b10ef 100644 (file)
@@ -21,6 +21,7 @@
 #include <Draw.hxx>
 #include <Draw_Appli.hxx>
 #include <DBRep.hxx>
+#include <DBRep_DrawableShape.hxx>
 
 #include <Font_BRepFont.hxx>
 #include <Font_BRepTextBuilder.hxx>
 #include <BRepExtrema_ExtPC.hxx>
 #include <BRepExtrema_ExtPF.hxx>
 
+#include <Prs3d_Arrow.hxx>
+#include <Prs3d_ArrowAspect.hxx>
 #include <Prs3d_DatumAspect.hxx>
 #include <Prs3d_Drawer.hxx>
 #include <Prs3d_VertexDrawMode.hxx>
@@ -5934,6 +5937,217 @@ static int VPriority (Draw_Interpretor& theDI,
   return 0;
 }
 
+//! Auxiliary class for command vnormals.
+class MyShapeWithNormals : public AIS_Shape
+{
+  DEFINE_STANDARD_RTTI_INLINE(MyShapeWithNormals, AIS_Shape);
+public:
+
+  Standard_Real    NormalLength;
+  Standard_Integer NbAlongU;
+  Standard_Integer NbAlongV;
+  Standard_Boolean ToUseMesh;
+  Standard_Boolean ToOrient;
+
+public:
+
+  //! Main constructor.
+  MyShapeWithNormals (const TopoDS_Shape& theShape)
+  : AIS_Shape   (theShape),
+    NormalLength(10),
+    NbAlongU  (1),
+    NbAlongV  (1),
+    ToUseMesh (Standard_False),
+    ToOrient  (Standard_False) {}
+
+protected:
+
+  //! Comnpute presentation.
+  virtual void Compute (const Handle(PrsMgr_PresentationManager3d)& thePrsMgr,
+                        const Handle(Prs3d_Presentation)&           thePrs,
+                        const Standard_Integer                      theMode) Standard_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);
+    }
+
+    Handle(Graphic3d_Group) aPrsGroup = Prs3d_Root::NewGroup (thePrs);
+    aPrsGroup->SetGroupPrimitivesAspect (myDrawer->ArrowAspect()->Aspect());
+
+    const Standard_Real aArrowAngle  = myDrawer->ArrowAspect()->Angle();
+    const Standard_Real 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 Standard_Boolean toReverse = ToOrient && aFaceIt.Key().Orientation() == TopAbs_REVERSED;
+      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,
+                     Standard_Integer  theArgNum,
+                     const char**      theArgs)
+{
+  Handle(AIS_InteractiveContext) aContext = ViewerTest::GetAISContext();
+  if (aContext.IsNull())
+  {
+    std::cout << "Error: no view available, call 'vinit' before!\n";
+    return 1;
+  }
+  else if (theArgNum < 2)
+  {
+    std::cout << "Error: wrong number of arguments! See usage:\n";
+    theDI.PrintHelp (theArgs[0]);
+    return 1;
+  }
+
+  Standard_Integer anArgIter = 1;
+  Standard_CString aShapeName = theArgs[anArgIter++];
+  TopoDS_Shape     aShape     = DBRep::Get (aShapeName);
+  Standard_Boolean isOn = Standard_True;
+  if (aShape.IsNull())
+  {
+    std::cout << "Error: shape with name '" << aShapeName << "' is not found\n";
+    return 1;
+  }
+
+  ViewerTest_DoubleMapOfInteractiveAndName& aMap = GetMapOfAIS();
+  Handle(MyShapeWithNormals) aShapePrs;
+  if (aMap.IsBound2 (aShapeName))
+  {
+    aShapePrs = Handle(MyShapeWithNormals)::DownCast (aMap.Find2 (aShapeName));
+  }
+
+  Standard_Boolean isUseMesh = Standard_False;
+  Standard_Real    aLength = 10.0;
+  Standard_Integer aNbAlongU = 1, aNbAlongV = 1;
+  Standard_Boolean isOriented = Standard_False;
+  for (; anArgIter < theArgNum; ++anArgIter)
+  {
+    TCollection_AsciiString aParam (theArgs[anArgIter]);
+    aParam.LowerCase();
+    if (anArgIter == 2
+     && ViewerTest::ParseOnOff (aParam.ToCString(), isOn))
+    {
+      continue;
+    }
+    else if (aParam == "-usemesh"
+          || aParam == "-mesh")
+    {
+      isUseMesh = Standard_True;
+    }
+    else if (aParam == "-length"
+          || aParam == "-len")
+    {
+      ++anArgIter;
+      aLength = anArgIter < theArgNum ? Draw::Atof (theArgs[anArgIter]) : 0.0;
+      if (Abs (aLength) <= gp::Resolution())
+      {
+        std::cout << "Syntax error: length should not be zero\n";
+        return 1;
+      }
+    }
+    else if (aParam == "-orient"
+          || aParam == "-oriented")
+    {
+      isOriented = Standard_True;
+      if (anArgIter + 1 < theArgNum
+        && ViewerTest::ParseOnOff (theArgs[anArgIter + 1], isOriented))
+      {
+        ++anArgIter;
+      }
+    }
+    else if (aParam == "-nbalongu"
+          || aParam == "-nbu")
+    {
+      ++anArgIter;
+      aNbAlongU = anArgIter < theArgNum ? Draw::Atoi (theArgs[anArgIter]) : 0;
+      if (aNbAlongU < 1)
+      {
+        std::cout << "Syntax error: NbAlongU should be >=1\n";
+        return 1;
+      }
+    }
+    else if (aParam == "-nbalongv"
+          || aParam == "-nbv")
+    {
+      ++anArgIter;
+      aNbAlongV = anArgIter < theArgNum ? Draw::Atoi (theArgs[anArgIter]) : 0;
+      if (aNbAlongV < 1)
+      {
+        std::cout << "Syntax error: NbAlongV should be >=1\n";
+        return 1;
+      }
+    }
+    else if (aParam == "-nbalong"
+          || aParam == "-nbuv")
+    {
+      ++anArgIter;
+      aNbAlongU = anArgIter < theArgNum ? Draw::Atoi (theArgs[anArgIter]) : 0;
+      aNbAlongV = aNbAlongU;
+      if (aNbAlongU < 1)
+      {
+        std::cout << "Syntax error: NbAlong should be >=1\n";
+        return 1;
+      }
+    }
+    else
+    {
+      std::cout << "Syntax error: unknwon argument '" << aParam << "'\n";
+      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;
+}
+
 //=======================================================================
 //function : ObjectsCommands
 //purpose  :
@@ -6232,4 +6446,11 @@ void ViewerTest::ObjectCommands(Draw_Interpretor& theCommands)
     "vpriority [-noupdate|-update] name [value]\n\t\t  prints or sets the display priority for an object",
     __FILE__,
     VPriority, group);
+
+  theCommands.Add ("vnormals",
+                   "vnormals usage:\n"
+                   "vnormals Shape [{on|off}=on] [-length {10}] [-nbAlongU {1}] [-nbAlongV {1}] [-nbAlong {1}]"
+                   "\n\t\t:        [-useMesh] [-oriented {0}1}=0]"
+                   "\n\t\t:  Displays/Hides normals calculated on shape geometry or retrieved from triangulation",
+                   __FILE__, VNormals, group);
 }