]> SALOME platform Git repositories - modules/shaper.git/commitdiff
Salome HOME
[bos #35153][EDF](2023-T1) Construction grid.
authordish <dmitrii.shvydkoi@opencascade.com>
Wed, 22 May 2024 11:49:37 +0000 (11:49 +0000)
committerdish <dmitrii.shvydkoi@opencascade.com>
Wed, 22 May 2024 11:49:37 +0000 (11:49 +0000)
Add rectangular and circular construction grids.

28 files changed:
src/GeomAPI/GeomAPI_Face.cpp
src/GeomAPI/GeomAPI_Face.h
src/GeomAPI/GeomAPI_Pln.h
src/GeomAlgoAPI/GeomAlgoAPI_FaceBuilder.cpp
src/GeomAlgoAPI/GeomAlgoAPI_FaceBuilder.h
src/ModuleBase/ModuleBase_ModelWidget.cpp
src/ModuleBase/ModuleBase_ModelWidget.h
src/PartSet/CMakeLists.txt
src/PartSet/PartSet_Module.cpp
src/PartSet/PartSet_MouseProcessor.cpp [new file with mode: 0644]
src/PartSet/PartSet_MouseProcessor.h
src/PartSet/PartSet_PreviewSketchPlane.cpp
src/PartSet/PartSet_PreviewSketchPlane.h
src/PartSet/PartSet_SketcherMgr.cpp
src/PartSet/PartSet_SketcherMgr.h
src/PartSet/PartSet_Tools.cpp
src/PartSet/PartSet_Tools.h
src/PartSet/PartSet_WidgetBSplinePoints.cpp
src/PartSet/PartSet_WidgetPoint2DFlyout.cpp
src/PartSet/PartSet_WidgetPoint2d.cpp
src/PartSet/PartSet_WidgetPoint2d.h
src/PartSet/PartSet_WidgetSketchCreator.cpp
src/PartSet/PartSet_WidgetSketchLabel.cpp
src/PartSet/PartSet_WidgetSketchLabel.h
src/PartSet/PartSet_msg_fr.ts
src/SketchPlugin/SketchPlugin_Sketch.h
src/XGUI/XGUI_Displayer.cpp
src/XGUI/XGUI_Displayer.h

index f0bfe7e9781bf36eaaf9d6c96098571421ac4d7b..d80a08e805c1ed63b82904699b85edb988f23d57 100644 (file)
@@ -378,7 +378,7 @@ GeomPointPtr GeomAPI_Face::middlePoint() const
     return anInnerPoint;
 
   double aUMin, aUMax, aVMin, aVMax;
-  optimalBounds(aFace, aUMin, aUMax, aVMin, aVMax);
+  ::optimalBounds(aFace, aUMin, aUMax, aVMin, aVMax);
 
   Handle(Geom_Surface) aSurf = BRep_Tool::Surface(aFace);
   if (aSurf.IsNull())
@@ -389,6 +389,16 @@ GeomPointPtr GeomAPI_Face::middlePoint() const
   return anInnerPoint;
 }
 
+bool GeomAPI_Face::optimalBounds(double& theUMin, double& theUMax,
+                                 double& theVMin, double& theVMax) const
+{
+  const TopoDS_Face& aFace = impl<TopoDS_Face>();
+  if (aFace.IsNull())
+    return false;
+
+  ::optimalBounds(aFace, theUMin, theUMax, theVMin, theVMax);
+  return true;
+}
 
 // ==================     Auxiliary functions     ========================
 
@@ -596,4 +606,4 @@ void optimalBounds(const TopoDS_Face& theFace, double& theUMin, double& theUMax,
     optimalBounds(theFace, TopoDS::Edge(anExp.Current()), aBB);
 
   aBB.Get(theUMin, theVMin, theUMax, theVMax);
-}
+}
\ No newline at end of file
index 4fb969047f3016db3b47a3ec0cd6b0d98c16c3e8..81682ae2363785acdf98d810f4f2c1f4aef95d10 100644 (file)
@@ -71,8 +71,14 @@ public:
   /// Returns torus if the face is based on the toroidal surface
   GEOMAPI_EXPORT std::shared_ptr<GeomAPI_Torus> getTorus() const;
 
-  /// Return inner point in the face
+  /// Returns inner point in the face
   GEOMAPI_EXPORT virtual std::shared_ptr<GeomAPI_Pnt> middlePoint() const;
+
+  /// Returns bounding box in UV-space.
+  GEOMAPI_EXPORT virtual bool optimalBounds(
+    double& theUMin, double& theUMax,
+    double& theVMin, double& theVMax
+  ) const;
 };
 
 //! Pointer on attribute object
index eca7cec71f5c2e7eaf9499bb75740fd0a5f1952c..998693c6896945cb4c62df82b08cd9231ebb858f 100644 (file)
@@ -27,10 +27,11 @@ class GeomAPI_Ax3;
 class GeomAPI_Pnt;
 class GeomAPI_Dir;
 class GeomAPI_Lin;
+class gp_Pln;
 
 /**\class GeomAPI_Pln
  * \ingroup DataModel
- * \brief 3D point defined by three coordinates
+ * \Plane in 3D place, defined by normal, center and x-direction.
  */
 
 class GeomAPI_Pln : public GeomAPI_Interface
@@ -38,7 +39,7 @@ class GeomAPI_Pln : public GeomAPI_Interface
  public:
   /// Creation of plane by the axis placement
   GEOMAPI_EXPORT
-  GeomAPI_Pln(const std::shared_ptr<GeomAPI_Ax3>& theAxis);
+  GeomAPI_Pln(const std::shared_ptr<GeomAPI_Ax3>& theAxes);
 
   /// Creation of plane by the point and normal
   GEOMAPI_EXPORT
index 511dae1d4a9f4b3d64abaa62c20d05ae204c091a..60ac0ba128bc1ec4945d483fb392b606ff6a5673 100644 (file)
@@ -98,6 +98,22 @@ std::shared_ptr<GeomAPI_Face> GeomAlgoAPI_FaceBuilder::planarFace(
   return aRes;
 }
 
+//==================================================================================================
+/*static*/ std::shared_ptr<GeomAPI_Face> GeomAlgoAPI_FaceBuilder::planarRectangularFace(
+  const gp_Ax3& theCS, double theWidth, double theHeight, double theOffsetX, double theOffsetY
+) {
+  const gp_Pln plane = gp_Pln(theCS);
+  BRepBuilderAPI_MakeFace faceBuilder(
+    plane,
+    theOffsetX - theWidth/2 , theOffsetX + theWidth/2,
+    theOffsetY - theHeight/2, theOffsetY + theHeight/2
+  );
+
+  std::shared_ptr<GeomAPI_Face> res(new GeomAPI_Face());
+  res->setImpl(new TopoDS_Face(faceBuilder.Face()));
+  return res;
+}
+
 //==================================================================================================
 std::shared_ptr<GeomAPI_Face> GeomAlgoAPI_FaceBuilder::planarFaceByThreeVertices(
     const std::shared_ptr<GeomAPI_Vertex> theVertex1,
index 798e5a59b4196633a43214f524dc77aacb6bc77f..0d8f7a921bc83d931c34e68b2a73dde19da27334 100644 (file)
@@ -29,6 +29,7 @@ class GeomAPI_Face;
 class GeomAPI_Pln;
 class GeomAPI_Pnt;
 class GeomAPI_Vertex;
+class gp_Ax3;
 
 /// \class GeomAlgoAPI_FaceBuilder
 /// \ingroup DataAlgo
@@ -37,12 +38,12 @@ class GEOMALGOAPI_EXPORT GeomAlgoAPI_FaceBuilder
 {
  public:
   /// Creates square planar face by given point of the center,
-  /// normal to the plane and size of square
+  /// normal to the plane and size of square.
   static std::shared_ptr<GeomAPI_Face> squareFace(const std::shared_ptr<GeomAPI_Pnt> theCenter,
                                                   const std::shared_ptr<GeomAPI_Dir> theNormal,
                                                   const double theSize);
   /// Creates square planar face by given point of the center,
-  /// normal to the plane and size of square
+  /// normal to the plane and size of square.
   static std::shared_ptr<GeomAPI_Face> squareFace(const std::shared_ptr<GeomAPI_Pln> thePlane,
                                                   const double theSize);
 
@@ -50,11 +51,18 @@ class GEOMALGOAPI_EXPORT GeomAlgoAPI_FaceBuilder
   static std::shared_ptr<GeomAPI_Face> planarFace(const std::shared_ptr<GeomAPI_Pnt> theCenter,
                                                   const std::shared_ptr<GeomAPI_Dir> theNormal);
 
-  /// Creates a planar face by given plane, left lower point and size.
+  /// Creates a planar face by given plane, left lower point and size. Does not take into account X & Y directions of thePlane!
   static std::shared_ptr<GeomAPI_Face> planarFace(const std::shared_ptr<GeomAPI_Pln> thePlane,
                                                   const double theX, const double theY,
                                                   const double theWidth, const double theHeight);
 
+  /*! \brief Creates rectangular planar face, with normal, X and Y directions as of theCS. */
+  static std::shared_ptr<GeomAPI_Face> planarRectangularFace(
+    const gp_Ax3& theCS,
+    double theWidth, double theHeight,
+    double theOffsetX = 0, double theOffsetY = 0
+  );
+
   /// Creates a planar face by three vertices.
   static std::shared_ptr<GeomAPI_Face> planarFaceByThreeVertices(
                         const std::shared_ptr<GeomAPI_Vertex> theVertex1,
index d70c6572a49203eff321c0f98dcabd31f91d7c89..df43f802b7e800e92c29fca71f2ff1848ab4d469 100644 (file)
@@ -52,7 +52,7 @@
 ModuleBase_ModelWidget::ModuleBase_ModelWidget(QWidget* theParent,
   const Config_WidgetAPI* theData)
   : QWidget(theParent),
-  myWidgetValidator(0),
+  myWidgetValidator(nullptr),
   myState(Stored),
   myIsEditing(false),
   myIsValueStateBlocked(false),
index 3af67f56db81fbb55afdc0b03a581f0f791fd40d..04a69e12c020791184fe73a814f62730ad5c5f6f 100644 (file)
@@ -48,7 +48,7 @@ class QKeyEvent;
 
 /**\class ModuleBase_ModelWidget
  * \ingroup GUI
- * \brief An abstract custom widget class. This class realization is assumed 
+ * \brief An abstract custom widget class. This class realization is assumed
  * to create some controls.
  * The controls values modification should send signal about values change.
  *
@@ -177,7 +177,7 @@ Q_OBJECT
   { return true; }
 
   /// Returns widget validator, by default it is NULL. To be created in a child if necessary
-  ModuleBase_WidgetValidator* widgetValidator() { return myWidgetValidator; }
+  ModuleBase_WidgetValidator* widgetValidator() { return myWidgetValidator.get(); }
 
   /// Restore value from attribute data to the widget's control.
   /// Emits signals before and after store
@@ -430,7 +430,7 @@ protected:
 
  protected:
     /// own validator, by default it is zero
-   ModuleBase_WidgetValidator* myWidgetValidator;
+  std::unique_ptr<ModuleBase_WidgetValidator> myWidgetValidator;
 
   /// The attribute name of the model feature
   std::string myAttributeID;
index a09d270864a0d99619e43977bb0fa39e8fde7901..8a07ba61a2f91fe3d05b5a4c854cad889c0e32bb 100644 (file)
@@ -93,6 +93,7 @@ SET(PROJECT_SOURCES
     PartSet_IconFactory.cpp
     PartSet_MenuMgr.cpp
     PartSet_Module.cpp
+    PartSet_MouseProcessor.cpp
     PartSet_OperationPrs.cpp
     PartSet_OverconstraintListener.cpp
     PartSet_PreviewPlanes.cpp
index efc79517164858128f90545408d964e51ac29d05..684fe5f08378a886191adfd361ee4141cf5004e5 100644 (file)
@@ -1783,9 +1783,9 @@ void PartSet_Module::processEvent(const std::shared_ptr<Events_Message>& theMess
     CompositeFeaturePtr aSketch = mySketchMgr->activeSketch();
     if (aSketch.get()) {
       ModuleBase_Operation* anOperation = myWorkshop->currentOperation();
-      if (PartSet_SketcherMgr::isSketchOperation(anOperation) &&
-        mySketchMgr->previewSketchPlane()->isDisplayed())
-        mySketchMgr->previewSketchPlane()->createSketchPlane(aSketch, myWorkshop);
+      if (PartSet_SketcherMgr::isSketchOperation(anOperation)) {
+        mySketchMgr->previewSketchPlane()->setAllUsingSketch(aSketch);
+      }
     }
   }
   else if (theMessage->eventID() == Events_Loop::loop()->eventByName(EVENT_FEATURE_LICENSE_VALID)) {
diff --git a/src/PartSet/PartSet_MouseProcessor.cpp b/src/PartSet/PartSet_MouseProcessor.cpp
new file mode 100644 (file)
index 0000000..2a68166
--- /dev/null
@@ -0,0 +1,128 @@
+// Copyright (C) 2014-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#include "PartSet_MouseProcessor.h"
+
+#include "ModuleBase_IViewWindow.h"
+#include "ModuleBase_IWorkshop.h"
+#include "ModuleBase_IViewer.h"
+#include "PartSet_Module.h"
+#include "PartSet_PreviewSketchPlane.h"
+#include "ModelAPI_CompositeFeature.h"
+#include <gp_Pnt.hxx>
+#include <Aspect_Grid.hxx>
+#include <QPoint>
+#include <PartSet_Tools.h>
+
+
+/// @brief
+/// During drawing of a line on a sketch, a crash happens if coordinates (theX, theY), returned by \ref PartSet_MouseProcessor::convertPointToLocal,
+/// coincide with on-sketch plane coordinates of the closest grid node. What exactly causes this odd behaviour remains unknown.
+/// The crash is manifested on all supported Linux OSes, except Debian 11 and, maybe, 12.
+/// Even on problematic OSes, the crash does not happen if a debugger (at least LLDB) is attached to Salome with Shaper being compiled in Debug mode.
+double slightlyChangeVal(double theVal)
+{
+  static const double eps = 1e-5;
+
+  if (std::abs(theVal) > 1)
+    return (1 + eps) * theVal;
+  else
+    return theVal + eps;
+}
+
+
+bool PartSet_MouseProcessor::convertPointToLocal(
+  ModuleBase_IWorkshop* theWorkshop,
+  const std::shared_ptr<ModelAPI_CompositeFeature>& theSketch,
+  ModuleBase_IViewWindow* theWindow,
+  const QPoint& theEventPos,
+  double& theX, double& theY,
+  bool theHighlight,
+  bool theAddOffset
+) const {
+  ModuleBase_IViewer* const viewer = theWorkshop->viewer();
+  if (!viewer)
+    return false;
+
+  const auto aV3dViewer = viewer->v3dViewer();
+  const PartSet_Module* module = dynamic_cast<PartSet_Module*>(theWorkshop->module());
+  if (!module)
+    return false;
+
+  const Handle(V3d_View) view = theWindow->v3dView();
+
+  PartSet_PreviewSketchPlane* previewPlane = module->sketchMgr()->previewSketchPlane();
+  if (!aV3dViewer || !aV3dViewer->Grid()->IsActive() || previewPlane->getGridSnappingMode() == PartSet_PreviewSketchPlane::GridSnappingMode::Off) {
+    const gp_Pnt mousePoint = PartSet_Tools::convertClickToPoint(theEventPos, view);
+    PartSet_Tools::convertTo2D(mousePoint, theSketch, view, theX, theY);
+    return true;
+  }
+  else {
+    double closestGridPointX, closestGridPointY, closestGridPointZ;
+    view->ConvertToGrid(theEventPos.x(), theEventPos.y(), closestGridPointX, closestGridPointY, closestGridPointZ);
+    const gp_Pnt gridPoint = gp_Pnt(closestGridPointX, closestGridPointY, closestGridPointZ);
+
+    if (previewPlane->getGridSnappingMode() == PartSet_PreviewSketchPlane::GridSnappingMode::SnapAnyway) {
+      PartSet_Tools::convertTo2D(gridPoint, theSketch, view, theX, theY);
+
+      if (theAddOffset) {
+        theX = slightlyChangeVal(theX);
+        theY = slightlyChangeVal(theY);
+      }
+
+      if (theHighlight) {
+        view->Viewer()->ShowGridEcho(view, Graphic3d_Vertex(closestGridPointX, closestGridPointY, closestGridPointZ));
+        view->Viewer()->SetGridEcho(true);
+        view->RedrawImmediate();
+      }
+      else {
+        view->Viewer()->SetGridEcho(false);
+        view->RedrawImmediate();
+      }
+
+      return true;
+    }
+    else /* aPreviewPlane->getGridSnappingMode() == PartSet_PreviewSketchPlane::GridSnappingMode::SnapInProximity */ {
+      Standard_Integer aClosestPX, aClosestPY; // Unit is pixel.
+      view->Convert(closestGridPointX, closestGridPointY, closestGridPointZ, aClosestPX, aClosestPY);
+      const int squareDistanceP = std::pow(aClosestPX - theEventPos.x(), 2) + std::pow(aClosestPY - theEventPos.y(), 2);
+      static const int THRESHOLD = std::pow(PartSet_PreviewSketchPlane::SNAP_PROXIMITY_P, 2);
+
+      if (squareDistanceP > THRESHOLD) {
+        const gp_Pnt mousePoint = PartSet_Tools::convertClickToPoint(theEventPos, view);
+        PartSet_Tools::convertTo2D(mousePoint, theSketch, view, theX, theY);
+        view->Viewer()->SetGridEcho(false);
+        view->RedrawImmediate();
+      }
+      else {
+        PartSet_Tools::convertTo2D(gridPoint, theSketch, view, theX, theY);
+
+        if (theAddOffset) {
+          theX = slightlyChangeVal(theX);
+          theY = slightlyChangeVal(theY);
+        }
+
+        view->Viewer()->ShowGridEcho(view, Graphic3d_Vertex(closestGridPointX, closestGridPointY, closestGridPointZ));
+        view->Viewer()->SetGridEcho(true);
+        view->RedrawImmediate();
+      }
+      return true;
+    }
+  }
+}
\ No newline at end of file
index 0ad72cae4d281d2a58e89e03bb815c0e374a3b3f..4f759249fc799fe3292a5fe38fca0d9431c87903 100644 (file)
 
 class ModuleBase_IViewWindow;
 class ModuleBase_ViewerPrs;
+class ModuleBase_IWorkshop;
+class ModelAPI_CompositeFeature;
 class QMouseEvent;
+class QPoint;
 
-/**
- * This is an interface to allow processing of mouse events. Implementation of necessary methods
-* should be done in a child.
-*/
+/** Interface for mouse events processing. */
 class PartSet_MouseProcessor
 {
 public:
@@ -64,6 +64,24 @@ public:
   virtual void setPreSelection(const std::shared_ptr<ModuleBase_ViewerPrs>& thePreSelected,
                                ModuleBase_IViewWindow* theWnd,
                                QMouseEvent* theEvent) {}
+
+  protected:
+  /// \brief Converts position of mouse cursor to local coordinates on sketch plane.
+  ///  Snaps on-sketch-plane-coordinates to closest construction grid node.
+  /// \param theEventPos is position of mouse cursor.
+  /// \param theX and \param theY are local coordinates on sketch plane.
+  /// \param theHighlight If point is snapped, hightlight grid point.
+  /// \param theAddOffset If true, serves as a remedy for odd crash during drawing of a line on a sketch.
+  /// \return true on success.
+  virtual bool convertPointToLocal(
+    ModuleBase_IWorkshop* theWorkshop,
+    const std::shared_ptr<ModelAPI_CompositeFeature>& theSketch, // Passing by reference is intentionally.
+    ModuleBase_IViewWindow* theWindow,
+    const QPoint& theEventPos,
+    double& theX, double& theY,
+    bool theHighlight = false,
+    bool theAddOffset = false
+  ) const;
 };
 
 #endif
index 05cf99f7251cce0a41b60eed6b7c4aee04594a8e..a895b0d2d3adf3e0339ae234638b2a7bca475e0e 100644 (file)
 //
 
 #include "PartSet_PreviewSketchPlane.h"
+#include "PartSet_SketcherMgr.h"
 #include "PartSet_Tools.h"
 
 #include <ModuleBase_IWorkshop.h>
 
-#include <ModelAPI_ResultBody.h>
-#include <ModelAPI_ResultConstruction.h>
+#include <ModelAPI_AttributeBoolean.h>
+#include <ModelAPI_AttributeDouble.h>
+#include <ModelAPI_AttributeInteger.h>
 #include <ModelAPI_CompositeFeature.h>
-#include <ModelAPI_Tools.h>
 
 #include <GeomAPI_AISObject.h>
-#include <GeomAPI_Pnt.h>
+#include <GeomAPI_Face.h>
 
 #include <XGUI_Tools.h>
 #include <XGUI_Displayer.h>
 #include <SketchPlugin_Sketch.h>
 #include <SketchPlugin_SketchEntity.h>
 
-#include <BRepBndLib.hxx>
-
-PartSet_PreviewSketchPlane::PartSet_PreviewSketchPlane()
- : myPreviewIsDisplayed(false), mySizeOfView(0), myIsUseSizeOfView(false)
+#include <ModuleBase_IViewer.h>
+#include <Aspect_Grid.hxx>
+#include <Geom_Plane.hxx>
+#include <AIS_PlaneTrihedron.hxx>
+#include <Quantity_Color.hxx>
+
+#include <gp_Pnt.hxx>
+#include <gp_Dir.hxx>
+#include <gp_Trsf.hxx>
+#include <gp_Ax2d.hxx>
+#include <gp_Trsf2d.hxx>
+#include <gp_Pnt2d.hxx>
+#include <gp_Dir2d.hxx>
+
+#include <cmath>
+#include <algorithm>
+#include <limits>
+#include <QString>
+
+#define PI 3.14159265358979323846
+
+#ifdef SKETCH_ACCESSORY_DBG
+#include <iostream>
+#include <sstream>
+#include <string>
+const std::string  PREFIX  =  "PreviewSketchPlane: ";
+const std::wstring WPREFIX = L"PreviewSketchPlane: ";
+#endif
+/*static*/ bool PartSet_PreviewSketchPlane::SketchAccessoryDbg(const QString& theString)
 {
+#ifdef SKETCH_ACCESSORY_DBG
+  std::wcout << WPREFIX << theString.toStdWString() << std::endl;
+  return true;
+#else
+  return false;
+#endif
 }
-
-void PartSet_PreviewSketchPlane::eraseSketchPlane(ModuleBase_IWorkshop* theWorkshop,
-                                                  const bool isClearPlane)
+/*static*/ bool PartSet_PreviewSketchPlane::SketchAccessoryDbg(const char* src)
 {
-  if (myPreviewIsDisplayed) {
-    XGUI_Displayer* aDisp = XGUI_Tools::workshop(theWorkshop)->displayer();
-    aDisp->eraseAIS(myPlane, false);
-    myPreviewIsDisplayed = false;
-  }
-  if (isClearPlane) clearPlanePreview();
+#ifdef SKETCH_ACCESSORY_DBG
+  std::wcout << WPREFIX << std::wstring(src, src + strlen(src)) << std::endl;
+  return true;
+#else
+  return false;
+#endif;
 }
-
-void PartSet_PreviewSketchPlane::displaySketchPlane(ModuleBase_IWorkshop* theWorkshop)
+/*static*/ bool PartSet_PreviewSketchPlane::SketchAccessoryDbg(const char* theCSDescription, const gp_Ax3& theCS)
 {
-  if (myPlane.get() && (!myPreviewIsDisplayed)) {
-    XGUI_Displayer* aDisp = XGUI_Tools::workshop(theWorkshop)->displayer();
-    aDisp->displayAIS(myPlane, false/*load object in selection*/, 1/*shaded*/, false);
-    myPreviewIsDisplayed = true;
-  }
+#ifdef SKETCH_ACCESSORY_DBG
+  std::stringstream s;
+  s << PREFIX << theCSDescription << "\n";
+  theCS.DumpJson(s);
+  s << "\n";
+  PartSet_PreviewSketchPlane::SketchAccessoryDbg(QString::fromStdString(s.str()));
+  return true;
+#else
+  return false;
+#endif
+}
+/*static*/ bool PartSet_PreviewSketchPlane::SketchAccessoryDbg(const char* theCSDescription, const gp_Pnt& thePnt)
+{
+#ifdef SKETCH_ACCESSORY_DBG
+  std::stringstream s;
+  s << PREFIX << theCSDescription;
+  thePnt.DumpJson(s);
+  s << "\n";
+  PartSet_PreviewSketchPlane::SketchAccessoryDbg(QString::fromStdString(s.str()));
+  return true;
+#else
+  return false;
+#endif
+}
+/*static*/ bool PartSet_PreviewSketchPlane::SketchAccessoryDbg(const char* theCSDescription, const gp_Trsf& theMatrix)
+{
+#ifdef SKETCH_ACCESSORY_DBG
+  std::stringstream s;
+  s << PREFIX << theCSDescription << "\n";
+  theMatrix.DumpJson(s);
+  s << "\n";
+  PartSet_PreviewSketchPlane::SketchAccessoryDbg(QString::fromStdString(s.str()));
+  return true;
+#else
+  return false;
+#endif
 }
 
 
-void PartSet_PreviewSketchPlane::clearPlanePreview()
+PartSet_PreviewSketchPlane::PartSet_PreviewSketchPlane(PartSet_SketcherMgr* theManager)
+ : mySketcherMgr(theManager), myValid(false),
+   mySketchDimensions(0, 0), mySketchDefaultSize(0),
+   myShowTrihedron(false), myTrihedron(new AIS_PlaneTrihedron(new Geom_Plane(1, 0, 0, 0))),
+   myShowSubstrate(false), mySubstrate(nullptr),
+   myGridType(PartSet_Tools::SketchPlaneGridType::No), myGridDrawMode(Aspect_GridDrawMode::Aspect_GDM_Lines),
+   mySnappingMode(GridSnappingMode::SnapAnyway),
+   myRectangularGridSteps(1, 1), myCircularGridRadialStep(1)
 {
-  myPlane = std::shared_ptr<GeomAPI_AISObject>();
-  myShape = std::shared_ptr<GeomAPI_Shape>();
+  myTrihedron->SetXLabel("X'");
+  myTrihedron->SetYLabel("Y'");
+  myTrihedron->SetColor(Quantity_Color(Quantity_NameOfColor::Quantity_NOC_MAGENTA)); // The color does not coincide with colors of world axes.
 }
 
-
-void PartSet_PreviewSketchPlane::createSketchPlane(const CompositeFeaturePtr& theSketch,
-                                                   ModuleBase_IWorkshop* theWorkshop)
+void PartSet_PreviewSketchPlane::savePreferencesIntoSketchData(std::shared_ptr<ModelAPI_CompositeFeature> theSketch) const
 {
-  // plane is visualized only if sketch plane is filled
-  if (!PartSet_Tools::sketchPlane(theSketch).get())
+  if (!theSketch || !this->isValid())
     return;
 
-  AttributeSelectionPtr aSelAttr = std::dynamic_pointer_cast<ModelAPI_AttributeSelection>
-    (theSketch->data()->attribute(SketchPlugin_SketchEntity::EXTERNAL_ID()));
-  if (!aSelAttr)
+  PartSet_Tools::sketchPlaneAxesEnabled(theSketch)->setValue(myShowTrihedron);
+  PartSet_Tools::sketchPlaneSubstrateEnabled(theSketch)->setValue(myShowSubstrate);
+  PartSet_Tools::setSketchPlaneGridType(theSketch, myGridType);
+
+  saveRectangularGridPreferencesIntoSketchData(theSketch);
+}
+
+void PartSet_PreviewSketchPlane::saveRectangularGridPreferencesIntoSketchData(std::shared_ptr<ModelAPI_CompositeFeature> theSketch) const
+{
+  if (!theSketch || !this->isValid())
     return;
 
-  if (myShape.get() && myShape->isSame(aSelAttr->value()) && myPlane.get())
+  PartSet_Tools::sketchPlaneRectangularGridStepX(theSketch)->setValue(myRectangularGridSteps.first);
+  PartSet_Tools::sketchPlaneRectangularGridStepY(theSketch)->setValue(myRectangularGridSteps.second);
+
+  PartSet_Tools::sketchPlaneRectangularGridOffsetAngle(theSketch)->setValue(myRectangularGridOffsetAngle);
+  PartSet_Tools::sketchPlaneRectangularGridOffsetX(theSketch)->setValue(myRectangularGridTransOffset.first);
+  PartSet_Tools::sketchPlaneRectangularGridOffsetY(theSketch)->setValue(myRectangularGridTransOffset.second);
+}
+
+void PartSet_PreviewSketchPlane::saveCircularGridPreferencesIntoSketchData(std::shared_ptr<ModelAPI_CompositeFeature> theSketch) const
+{
+  if (!theSketch || !this->isValid())
     return;
 
-  XGUI_Displayer* aDisp = XGUI_Tools::workshop(theWorkshop)->displayer();
-  if (myPreviewIsDisplayed) {
-    aDisp->eraseAIS(myPlane, false);
-  }
-
-  // Create Preview
-  // selected linear face parameters
-  myShape = aSelAttr->value();
-  // this case is needed by constructing sketch on a plane, where result shape is equal
-  // to context result, therefore value() returns NULL and we should use shape of context.
-  if (!myShape.get() && aSelAttr->context().get())
-    myShape = aSelAttr->context()->shape();
-
-  if (!myShape.get()) {
-    // Create Preview for default planes
-    std::shared_ptr<GeomDataAPI_Point> anOrigin = std::dynamic_pointer_cast<GeomDataAPI_Point>(
-        theSketch->data()->attribute(SketchPlugin_Sketch::ORIGIN_ID()));
-    std::shared_ptr<GeomDataAPI_Dir> aNormal = std::dynamic_pointer_cast<GeomDataAPI_Dir>(
-        theSketch->data()->attribute(SketchPlugin_Sketch::NORM_ID()));
-
-    double aFaceSize = myIsUseSizeOfView ? mySizeOfView
-      : Config_PropManager::real(SKETCH_TAB_NAME, "planes_size");
-    if (aFaceSize <= Precision::Confusion())
-      aFaceSize = 200;   // Set default value
-
-    myShape = GeomAlgoAPI_FaceBuilder::squareFace(
-      myViewCentralPoint.get() ? myViewCentralPoint : anOrigin->pnt(), aNormal->dir(), aFaceSize);
-  }
-  else if (myIsUseSizeOfView && (mySizeOfView > 0)) {
-    std::shared_ptr<GeomAPI_Face> aFace(new GeomAPI_Face(myShape));
-    std::shared_ptr<GeomAPI_Pln> aPlane = aFace->getPlane();
-    if (aPlane.get()) {
-      double anA, aB, aC, aD;
-      aPlane->coefficients(anA, aB, aC, aD);
-      std::shared_ptr<GeomAPI_Dir> aNormDir(new GeomAPI_Dir(anA, aB, aC));
-      std::shared_ptr<GeomAPI_XYZ> aCoords = aNormDir->xyz();
-      std::shared_ptr<GeomAPI_XYZ> aZero(new GeomAPI_XYZ(0, 0, 0));
-      aCoords = aCoords->multiplied(-aD * aCoords->distance(aZero));
-      std::shared_ptr<GeomAPI_Pnt> anOrigPnt(new GeomAPI_Pnt(aCoords));
-      myShape = GeomAlgoAPI_FaceBuilder::squareFace(
-        myViewCentralPoint.get() ? myViewCentralPoint : anOrigPnt, aNormDir, mySizeOfView);
-    }
-  }
-  myPlane = createPreviewPlane();
+  PartSet_Tools::sketchPlaneCircularGridStepR(theSketch)->setValue(myCircularGridRadialStep);
+  PartSet_Tools::sketchPlaneCircularGridNumOfAngSegments(theSketch)->setValue(myCircularGridNumOfAngularSections);
 
-  aDisp->displayAIS(myPlane, false/*load object in selection*/, 1/*shaded*/, false);
-  myPreviewIsDisplayed = true;
+  PartSet_Tools::sketchPlaneCircularGridOffsetAngle(theSketch)->setValue(myCircularGridOffsetAngle);
+  PartSet_Tools::sketchPlaneCircularGridOffsetX(theSketch)->setValue(myCircularGridTransOffset.first);
+  PartSet_Tools::sketchPlaneCircularGridOffsetY(theSketch)->setValue(myCircularGridTransOffset.second);
 }
 
-double maximumSize(double theXmin, double theYmin, double theZmin,
-                   double theXmax, double theYmax, double theZmax)
+void PartSet_PreviewSketchPlane::setCSAndSize(const gp_Ax3& theCS, double theSize)
 {
-  double aSize = fabs(theXmax - theXmin);
-  double aSizeToCompare = fabs(theYmax - theYmin);
-  if (aSizeToCompare > aSize)
-    aSize = aSizeToCompare;
-  aSizeToCompare = fabs(theZmax - theZmin);
-  if (aSizeToCompare > aSize)
-    aSize = aSizeToCompare;
+  SketchAccessoryDbg("setCSAndSize(_)");
 
-  return aSize;
+  mySketchCS = theCS;
+  mySketchDefaultSize = std::abs(theSize);
+  mySketchDimensions.first  = mySketchDefaultSize;
+  mySketchDimensions.second = mySketchDefaultSize;
+
+  configureTrihedron();
+
+  if (!mySubstrate)
+    initSubstrate(nullptr);
+  else {
+    const auto face = GeomAlgoAPI_FaceBuilder::planarRectangularFace(mySketchCS, mySketchDimensions.first, mySketchDimensions.second);
+    mySubstrate->createShape(face);
+  }
+
+  reconfigureGrid();
+  myValid = true;
 }
 
-bool PartSet_PreviewSketchPlane::getDefaultSizeOfView(
-  const CompositeFeaturePtr& theSketch, double& theSizeOfView,
-  std::shared_ptr<GeomAPI_Pnt>& theCentralPnt)
+bool PartSet_PreviewSketchPlane::setAllUsingSketch(std::shared_ptr<ModelAPI_CompositeFeature> theSketch)
 {
-  if (!PartSet_Tools::sketchPlane(theSketch).get())
+  SketchAccessoryDbg("setAllUsingSketch(theSketch)");
+
+  if (!theSketch || !PartSet_Tools::sketchPlane(theSketch)) {
+    SketchAccessoryDbg("invalid sketch - nullptr or normal/center is undefined.");
+    setInvalid();
     return false;
+  }
 
-  AttributeSelectionPtr aSelAttr = std::dynamic_pointer_cast<ModelAPI_AttributeSelection>
-    (theSketch->data()->attribute(SketchPlugin_SketchEntity::EXTERNAL_ID()));
+  bool sketchIsBlank = false;
+  const auto aSelAttr = std::dynamic_pointer_cast<ModelAPI_AttributeSelection>(theSketch->data()->attribute(SketchPlugin_SketchEntity::EXTERNAL_ID()));
   if (aSelAttr) {
-    myShape = aSelAttr->value();
-    // this case is needed by constructing sketch on a plane, where result shape is equal
-    // to context result, therefore value() returns NULL and we should use shape of context.
-    if (!myShape.get() && aSelAttr->context().get())
-      myShape = aSelAttr->context()->shape();
+    std::shared_ptr<GeomAPI_Shape> sketchShape;
+    if (aSelAttr->value()) {
+      sketchShape = aSelAttr->value();
+      SketchAccessoryDbg("aSelAttr->value()");
+    }
+    else if (aSelAttr->context()) {
+      sketchShape = aSelAttr->context()->shape();
+      SketchAccessoryDbg("aSelAttr->context()->shape()");
+    }
+
+    if (!sketchShape) {
+      sketchIsBlank = true;
+    }
+    else {
+      if (!sketchShape->isPlanar()) {
+        SketchAccessoryDbg("invalid sketch - shape is not planar.");
+        setInvalid();
+        return false;
+      }
+      const std::shared_ptr<GeomAPI_Face> sketchFace = sketchShape->face();
+      if (!sketchFace) {
+        SketchAccessoryDbg("invalid sketch - shape is not face.");
+        setInvalid();
+        return false;
+      }
+
+      { // Define CS as one at the center (Uc, Vc) of parametric domain.
+        // If the planar surface is non-convex, the center of parametric domain may
+        // lay outside of the surface. If X'(U, V) or Y'(U, V) are non-linear,
+        // X(Uc, Vc), Y(Uc, Vc) or its 1st derivative may not be defined.
+        // But:
+        //    1) The sketch widget only accepts Planes to start sketch.
+        //    2) The set of planar surfaces, being used in the app, does not include those described above.
+        double UMax, UMin, VMax, VMin;
+        const bool success = sketchFace->optimalBounds(UMin, UMax, VMin, VMax);
+        if (!success) {
+          SketchAccessoryDbg("Can't get sketch CS: UV-domain is undefined.");
+          setInvalid();
+          return false;
+        }
+
+        // X' and Y' directions and sketch center in the sketch->data() are not coincide with
+        // ones of the surface the sketch is started on.
+        const auto sketchFaceCS = PartSet_Tools::getWorldCSAt(*sketchFace, (UMin+UMax)/2, (VMin+VMax)/2);
+        if (!sketchFaceCS.first) {
+          SketchAccessoryDbg("Can't get sketch CS: sketch face is not 1-differentiable as function of U and V at the center of UV-domain.");
+          setInvalid();
+          return false;
+        }
+
+        mySketchCS = sketchFaceCS.second;
+      }
+
+      mySketchDefaultSize = PartSet_Tools::sketchPlaneDefaultSize(theSketch)->value();
+
+      { // Calculate sketch dimensions.
+        // The only purpose of converting from UV to X'Y' during calculation of sketch dimensions
+        // is to take into account scale factor or if the a non-orthogonal transformation is applied (if it even possible).
+        const auto sketchFaceBox = PartSet_Tools::getBBoxAtCS(*sketchFace, mySketchCS);
+        if (!sketchFaceBox.first) {
+          SketchAccessoryDbg("Can't find bounding box for sketch face.");
+          setInvalid();
+          mySketchDimensions.first  = getDefaultSize();
+          mySketchDimensions.second = getDefaultSize();
+        }
+        else {
+          // Here X and Y are X' and Y', and Z is sketch normal.
+          double Xmin, Ymin, Zmin, Xmax, Ymax, Zmax;
+          sketchFaceBox.second.Get(Xmin, Ymin, Zmin, Xmax, Ymax, Zmax);
+
+          mySketchDimensions.first  = std::abs(Xmax - Xmin);
+          mySketchDimensions.second = std::abs(Ymax - Ymin);
+        }
+      }
+
+      if (!mySubstrate)
+        initSubstrate(sketchFace);
+      else
+        mySubstrate->createShape(sketchFace);
+
+#ifdef SKETCH_ACCESSORY_DBG
+      auto sketchCS = gp_Ax3();
+      {
+        const auto aData = theSketch->data();
+        const auto aC = std::dynamic_pointer_cast<GeomDataAPI_Point>(aData->attribute(SketchPlugin_Sketch::ORIGIN_ID()));
+        const auto aX = std::dynamic_pointer_cast<GeomDataAPI_Dir>(aData->attribute(SketchPlugin_Sketch::DIRX_ID()));
+        const auto aNorm = std::dynamic_pointer_cast<GeomDataAPI_Dir>(aData->attribute(SketchPlugin_Sketch::NORM_ID()));
+        try {
+          sketchCS = gp_Ax3(aC->pnt()->impl<gp_Pnt>(), aX->dir()->impl<gp_Dir>(), aNorm->dir()->impl<gp_Dir>());
+        }
+        catch(...) {
+          SketchAccessoryDbg("Sketch data contains invalid basis.");
+        }
+      }
+
+      SketchAccessoryDbg("Sketch CS (sketch data):", sketchCS);
+      SketchAccessoryDbg("Sketch face CS:", mySketchCS);
+      SketchAccessoryDbg(QString("Sketch dimensions: ") + QString::number(mySketchDimensions.first) + ", " +  QString::number(mySketchDimensions.second));
+#endif
+    }
+  }
+
+  if (sketchIsBlank) {
+    SketchAccessoryDbg(QString("setAllUsingSketch(theSketch). Empty sketch"));
+    try
+    {
+      const auto sketchOrigin = std::dynamic_pointer_cast<GeomDataAPI_Point>(
+        theSketch->data()->attribute(SketchPlugin_Sketch::ORIGIN_ID())
+      );
+
+      const auto sketchNormal = std::dynamic_pointer_cast<GeomDataAPI_Dir>(
+        theSketch->data()->attribute(SketchPlugin_Sketch::NORM_ID())
+      );
+
+      const auto sketchXDirection = std::dynamic_pointer_cast<GeomDataAPI_Dir>(
+        theSketch->data()->attribute(SketchPlugin_Sketch::DIRX_ID())
+      );
+
+      mySketchCS.SetLocation(sketchOrigin->pnt()->impl<gp_Pnt>());
+
+      const gp_Dir normal = sketchNormal->dir()->impl<gp_Dir>();
+      mySketchCS.SetDirection(normal);
+      // Chi is synonym for X'.
+      const gp_Dir chiDirection = sketchXDirection->dir()->impl<gp_Dir>();
+      mySketchCS.SetXDirection(chiDirection);
+
+      mySketchDefaultSize = PartSet_Tools::sketchPlaneDefaultSize(theSketch)->value();
+      const double defaultSize = getDefaultSize();
+      mySketchDimensions.first  = defaultSize;
+      mySketchDimensions.second = defaultSize;
+    }
+    catch(...) {
+      SketchAccessoryDbg("Invalid basis in sketch data.");
+      setInvalid();
+      return false;
+    }
   }
 
-  if (myShape.get())
+  configureTrihedron();
+
+  if (sketchIsBlank) {
+    if (!mySubstrate)
+      initSubstrate(nullptr);
+    else {
+      const auto face = GeomAlgoAPI_FaceBuilder::planarRectangularFace(mySketchCS, mySketchDimensions.first, mySketchDimensions.second);
+      mySubstrate->createShape(face);
+    }
+  }
+
+  myValid = true;
+  showAxes(PartSet_Tools::sketchPlaneAxesEnabled(theSketch)->value());
+  showSubstrate(PartSet_Tools::sketchPlaneSubstrateEnabled(theSketch)->value());
+  reconfigureGridUsingSketch(theSketch);
+  return true;
+}
+
+bool PartSet_PreviewSketchPlane::reconfigureGrid()
+{
+  return reconfigureGridUsingSketch(nullptr);
+}
+
+bool PartSet_PreviewSketchPlane::reconfigureGridUsingSketch(std::shared_ptr<ModelAPI_CompositeFeature> theSketch)
+{
+  static const auto MARKER_COLOR = Quantity_Color(Quantity_NameOfColor::Quantity_NOC_TOMATO);
+  static const auto MARKER_SIZE = 2.0;
+  static const opencascade::handle<Graphic3d_AspectMarker3d> MARKER = new Graphic3d_AspectMarker3d(Aspect_TypeOfMarker::Aspect_TOM_BALL, MARKER_COLOR, MARKER_SIZE);
+  static const double MARGINS = 1.005; // Makes grid surface bigger to display marginal grid lines.
+
+  if (!isValid())
+    return myGridType == PartSet_Tools::SketchPlaneGridType::No;
+
+  const auto aV3DViewer = getV3DViewer();
+  if (!aV3DViewer) {
+    SketchAccessoryDbg("can't retrieve V3d_Viewer to configure grid.");
     return false;
+  }
 
-  Bnd_Box aBox;
-  int aNumberOfSubs = theSketch->numberOfSubs();
-  for (int aSubFeatureId = 0; aSubFeatureId < aNumberOfSubs; aSubFeatureId++) {
-    FeaturePtr aFeature = theSketch->subFeature(aSubFeatureId);
-    if (!aFeature.get())
-      continue;
-
-    std::list<ResultPtr> aResults = aFeature->results();
-    std::list<ResultPtr>::const_iterator aResultIt;
-    for (aResultIt = aResults.begin(); aResultIt != aResults.end(); ++aResultIt) {
-      ResultPtr aResult = *aResultIt;
-      std::shared_ptr<GeomAPI_Shape> aShapePtr = aResult->shape();
-      if (aShapePtr.get()) {
-        TopoDS_Shape aShape = aShapePtr->impl<TopoDS_Shape>();
-        if (aShape.IsNull())
-          continue;
-        BRepBndLib::Add(aShape, aBox);
-      }
+  const auto dimensions = getDimensions();
+
+  if (theSketch) {
+    { // Rectangular grid.
+      const double stepXAttrVal = PartSet_Tools::sketchPlaneRectangularGridStepX(theSketch)->value();
+      myRectangularGridSteps.first = stepXAttrVal < 0 ? dimensions.first  / PartSet_PreviewSketchPlane::DEFAULT_RELATIVE_STEP_INVERSE : stepXAttrVal;
+
+      const double stepYAttrVal = PartSet_Tools::sketchPlaneRectangularGridStepY(theSketch)->value();
+      myRectangularGridSteps.second = stepYAttrVal < 0 ? dimensions.second / PartSet_PreviewSketchPlane::DEFAULT_RELATIVE_STEP_INVERSE : stepYAttrVal;
+
+      myRectangularGridTransOffset.first  = PartSet_Tools::sketchPlaneRectangularGridOffsetX(theSketch)->value();
+      myRectangularGridTransOffset.second = PartSet_Tools::sketchPlaneRectangularGridOffsetY(theSketch)->value();
+      myRectangularGridOffsetAngle = PartSet_Tools::sketchPlaneRectangularGridOffsetAngle(theSketch)->value();
+    }
+
+    { // Circular grid
+      myCircularGridTransOffset.first  = PartSet_Tools::sketchPlaneCircularGridOffsetX(theSketch)->value();
+      myCircularGridTransOffset.second = PartSet_Tools::sketchPlaneCircularGridOffsetY(theSketch)->value();
+      myCircularGridOffsetAngle = PartSet_Tools::sketchPlaneCircularGridOffsetAngle(theSketch)->value();
+
+      const double stepRAttrVal = PartSet_Tools::sketchPlaneCircularGridStepR(theSketch)->value();
+      myCircularGridRadialStep = stepRAttrVal < 0 ?
+        std::min(dimensions.first, dimensions.second) / PartSet_PreviewSketchPlane::DEFAULT_RELATIVE_STEP_INVERSE * 2 :
+        stepRAttrVal;
+
+      const int aNAS = PartSet_Tools::sketchPlaneCircularGridNumOfAngSegments(theSketch)->value();
+      myCircularGridNumOfAngularSections = aNAS <= 0 ? PartSet_PreviewSketchPlane::DEFAULT_NUM_OF_ANGULAR_SECTIONS : aNAS;
+    }
+
+    myGridType = PartSet_Tools::getSketchPlaneGridType(theSketch);
+  }
+
+  aV3DViewer->SetGridEcho(MARKER);
+
+  if (myGridType == PartSet_Tools::SketchPlaneGridType::Rectangular) {
+    static const double MAX = std::numeric_limits<double>::max();
+
+    auto steps = myRectangularGridSteps;
+    if (steps.first <= Precision::Confusion())
+      steps.first = MAX;
+
+    if (steps.second <= Precision::Confusion())
+      steps.second = MAX;
+
+    // Chi and Upsilon are synonyms for X' and Y'.
+    const double offsetChi     = steps.first  < MAX ? std::remainder(myRectangularGridTransOffset.first , steps.first)  : myRectangularGridTransOffset.first;
+    const double offsetUpsilon = steps.second < MAX ? std::remainder(myRectangularGridTransOffset.second, steps.second) : myRectangularGridTransOffset.second;
+    const double offsetAngleRad = myRectangularGridOffsetAngle * PI / 180;
+
+    aV3DViewer->SetPrivilegedPlane(mySketchCS);
+    aV3DViewer->SetRectangularGridValues(offsetChi, offsetUpsilon, steps.first, steps.second, offsetAngleRad);
+#define V3DVIEWER_RECTANGULAR_GRID_OFFSET_BUGS
+#ifdef V3DVIEWER_RECTANGULAR_GRID_OFFSET_BUGS
+    /*
+    How grid offsets should work? Imagine an invisible rectangular frame - only those elements of the the grid must be visible,
+    which are inside of the frame.
+    If an offset is adjusted, the frame remains intact and stays in place, but elements of the grid are shifted/rotated
+    appropriate to the offset direction and value.
+    The dimensions of the frame are ones of 2D bounding box of the sketch face.
+
+    V3d_Viewer::SetRectangularGridGraphicValues adjusts grid elements' position, but also translates/rotates the frame of the grid!
+    Following equations enlarge grid dimensions, so that the entire surface of the 2D BBox is covered with grid.
+    */
+
+    const auto outerDims = PartSet_PreviewSketchPlane::dimsOfRectangleFittingRectangle(
+      dimensions.first, dimensions.second, -offsetAngleRad, offsetChi, offsetUpsilon
+    );
+
+    aV3DViewer->SetRectangularGridGraphicValues(outerDims.first / 2 * MARGINS, outerDims.second / 2 * MARGINS, 0);
+
+    {
+      const int chiDim     = outerDims.first  / 2 * MARGINS / steps.first  >= 1 ? 1 : 0;
+      const int upsilonDim = outerDims.second / 2 * MARGINS / steps.second >= 1 ? 1 : 0;
+      myRectangularGridDimOfNodeSpace = chiDim + upsilonDim;
+    }
+#else
+    aV3DViewer->SetRectangularGridGraphicValues(dimensions.first / 2 * MARGINS, dimensions.second / 2 * MARGINS, 0);
+
+    {
+      const int chiDim     = dimensions.first  / 2 * MARGINS / steps.first  >= 1 ? 1 : 0;
+      const int upsilonDim = dimensions.second / 2 * MARGINS / steps.second >= 1 ? 1 : 0;
+      myRectangularGridDimOfNodeSpace = chiDim + upsilonDim;
+    }
+#endif
+  }
+  else if (myGridType == PartSet_Tools::SketchPlaneGridType::Circular) {
+    const int& aNAS = myCircularGridNumOfAngularSections;
+    const int divisionNumber = aNAS == 1 ? 0 : aNAS / 2 + aNAS % 2; // V3d_Viewer::SetCircularGridValues is weird.
+
+    aV3DViewer->SetPrivilegedPlane(mySketchCS);
+    aV3DViewer->SetCircularGridValues(
+      myCircularGridTransOffset.first, myCircularGridTransOffset.second,
+      myCircularGridRadialStep > Precision::Confusion() ? myCircularGridRadialStep : std::numeric_limits<double>::max(),
+      divisionNumber,
+      myCircularGridOffsetAngle * PI / 180
+    );
+
+    /* Circles of circular grid are not circles, but regular polygons and cover less surface than circles. */
+    const double R = getCircularGridRaduis(dimensions);
+    int numOfEdges = aNAS < 12 ? 12 : aNAS + aNAS % 2;
+    const double polyR = R / std::cos(PI / numOfEdges) * MARGINS;
+    aV3DViewer->SetCircularGridGraphicValues(polyR, 0);
+
+    {
+      const int angularDim = divisionNumber > 2 ? 2 : 1;
+      const int radialDim = myCircularGridRadialStep > Precision::Confusion() && polyR / myCircularGridRadialStep >=1 ? 1 : 0;
+      myRectangularGridDimOfNodeSpace = angularDim * radialDim;
     }
   }
-  if (aBox.IsVoid())
-    return 0;
 
-  double aXmin, aXmax, anYmin, anYmax, aZmin, aZmax;
-  aBox.Get(aXmin, anYmin, aZmin, aXmax, anYmax, aZmax);
+  if (theSketch) {
+    if (myGridType == PartSet_Tools::SketchPlaneGridType::No)
+      aV3DViewer->DeactivateGrid();
+    else {
+      Aspect_GridType type = myGridType == PartSet_Tools::SketchPlaneGridType::Rectangular ?
+        Aspect_GridType::Aspect_GT_Rectangular : Aspect_GridType::Aspect_GT_Circular;
 
-  theSizeOfView = maximumSize(aXmin, anYmin, aZmin, aXmax, anYmax, aZmax);
-  if (theSizeOfView > 0) {
-    gp_Pnt aCentre(aXmax-fabs(aXmax-aXmin)/2., anYmax-fabs(anYmax-anYmin)/2.,
-                   aZmax - fabs(aZmax-aZmin)/2.);
-    theCentralPnt = std::shared_ptr<GeomAPI_Pnt>(new GeomAPI_Pnt(aCentre.X(), aCentre.Y(),
-                                                                 aCentre.Z()));
+      aV3DViewer->ActivateGrid(type, myGridDrawMode);
+    }
   }
+
+  return true;
+}
+
+bool PartSet_PreviewSketchPlane::showAxes(bool theShow)
+{
+  if (!isValid())
+    return !theShow;
+
+  XGUI_Displayer* const displayer = mySketcherMgr->workshop()->displayer();
+  if (theShow)
+    displayer->displayAIS(myTrihedron, false /*load object in selection*/, 1 /*shaded*/, false /*update viewer*/);
+  else
+    displayer->eraseAIS(myTrihedron, false /*update viewer*/);
+
+  myShowTrihedron = theShow;
   return true;
 }
 
-void PartSet_PreviewSketchPlane::setSizeOfView(double theSizeOfView, bool isUseSizeOfView,
-  const std::shared_ptr<GeomAPI_Pnt>& theCentralPoint)
+bool PartSet_PreviewSketchPlane::showSubstrate(bool theShow)
 {
-  mySizeOfView = theSizeOfView;
-  myIsUseSizeOfView = isUseSizeOfView;
+  if (!isValid())
+    return !theShow;
 
-  myViewCentralPoint = theCentralPoint;
+  XGUI_Displayer* const displayer = mySketcherMgr->workshop()->displayer();
+  if (theShow)
+    displayer->displayAIS(mySubstrate, false /*load object in selection*/, 1 /*shaded*/, false /*update viewer*/);
+  else
+    displayer->eraseAIS(mySubstrate, false /*update viewer*/);
+
+  myShowSubstrate = theShow;
+  return true;
 }
 
-AISObjectPtr PartSet_PreviewSketchPlane::createPreviewPlane()
+bool PartSet_PreviewSketchPlane::setGridType(PartSet_Tools::SketchPlaneGridType::Enum theType)
 {
-  if (myPlane.get()) {
-    myPlane->createShape(myShape);
-    return myPlane;
+  if (!isValid())
+    return theType == PartSet_Tools::SketchPlaneGridType::No;
+
+  const auto aV3DViewer = getV3DViewer();
+  if (!aV3DViewer) {
+    SketchAccessoryDbg("can't retrieve V3d_Viewer to show/hide grid.");
+    return false;
   }
+
+  myGridType = theType;
+  reconfigureGrid();
+
+  if (myGridType == PartSet_Tools::SketchPlaneGridType::No)
+    aV3DViewer->DeactivateGrid();
   else {
-    AISObjectPtr aAIS = AISObjectPtr(new GeomAPI_AISObject());
-    aAIS->createShape(myShape);
-    std::vector<int> aColor = Config_PropManager::color("Visualization", "sketch_preview_plane");
-    if (aColor.size() == 3)
-      aAIS->setColor(aColor[0], aColor[1], aColor[2]);
-    aAIS->setTransparensy(0.8);
-
-    int aDispMode = 1; // shading
-    Handle(AIS_InteractiveObject) anAISIO = aAIS->impl<Handle(AIS_InteractiveObject)>();
-    if (!anAISIO.IsNull()) {
-      //anAISIO->SetInfiniteState(Standard_True);
-      anAISIO->Attributes()->SetFaceBoundaryDraw( Standard_True );
-      anAISIO->SetDisplayMode(aDispMode);
+    Aspect_GridType type = myGridType == PartSet_Tools::SketchPlaneGridType::Rectangular ?
+      Aspect_GridType::Aspect_GT_Rectangular : Aspect_GridType::Aspect_GT_Circular;
+
+    aV3DViewer->ActivateGrid(type, myGridDrawMode);
+  }
+
+  return true;
+}
+
+void PartSet_PreviewSketchPlane::hideAll()
+{
+  showAxes(false);
+  showSubstrate(false);
+  setGridType(PartSet_Tools::SketchPlaneGridType::No);
+}
+
+std::pair<double, double> PartSet_PreviewSketchPlane::getDimensions() const
+{
+  const double defaultSize = getDefaultSize();
+  // Chi and Upsilon are synonyms for X' and Y'.
+  const bool chiSizeOk = mySketchDimensions.first > Precision::Confusion();
+  const bool upsilonSizeOk = mySketchDimensions.second > Precision::Confusion();
+
+  return std::pair<double, double>(
+    chiSizeOk ? mySketchDimensions.first : upsilonSizeOk ? mySketchDimensions.second : defaultSize,
+    upsilonSizeOk ? mySketchDimensions.second : chiSizeOk ? mySketchDimensions.first : defaultSize
+  );
+}
+
+void PartSet_PreviewSketchPlane::resetRectangularGrid()
+{
+  const auto dimensions = getDimensions();
+  myRectangularGridSteps.first  = dimensions.first  / PartSet_PreviewSketchPlane::DEFAULT_RELATIVE_STEP_INVERSE;
+  myRectangularGridSteps.second = dimensions.second / PartSet_PreviewSketchPlane::DEFAULT_RELATIVE_STEP_INVERSE;
+  myRectangularGridOffsetAngle = 0;
+  myRectangularGridTransOffset.first  = 0;
+  myRectangularGridTransOffset.second = 0;
+}
+
+void PartSet_PreviewSketchPlane::setRectangularGridStepX(double theStepX)
+{
+  myRectangularGridSteps.first  = theStepX;
+}
+
+void PartSet_PreviewSketchPlane::setRectangularGridStepY(double theStepY)
+{
+  myRectangularGridSteps.second = theStepY;
+}
+
+std::pair<std::pair<double, double>, double> PartSet_PreviewSketchPlane::getRectangularGridOffsets() const
+{
+  return std::pair<std::pair<double, double>, double>(myRectangularGridTransOffset, myRectangularGridOffsetAngle);
+}
+
+void PartSet_PreviewSketchPlane::setRectangularGridOffsetX(double theOffsetX)
+{
+  myRectangularGridTransOffset.first  = theOffsetX;
+}
+
+void PartSet_PreviewSketchPlane::setRectangularGridOffsetY(double theOffsetY)
+{
+  myRectangularGridTransOffset.second = theOffsetY;
+}
+
+void PartSet_PreviewSketchPlane::setRectangularGridOffsetA(double theOffsetAngle)
+{
+  myRectangularGridOffsetAngle = theOffsetAngle;
+}
+
+void PartSet_PreviewSketchPlane::resetCircularGrid()
+{
+  const auto d = getDimensions();
+  double R = std::sqrt(std::pow(d.first, 2) + std::pow(d.second, 2)) / 2;
+
+  myCircularGridRadialStep = R / PartSet_PreviewSketchPlane::DEFAULT_RELATIVE_STEP_INVERSE;
+  myCircularGridNumOfAngularSections = PartSet_PreviewSketchPlane::DEFAULT_NUM_OF_ANGULAR_SECTIONS;
+  myCircularGridOffsetAngle = 0;
+  myCircularGridTransOffset.first  = 0;
+  myCircularGridTransOffset.second = 0;
+}
+
+void PartSet_PreviewSketchPlane::setCircularGridRadialStep(double theStep)
+{
+  myCircularGridRadialStep = theStep;
+}
+
+void PartSet_PreviewSketchPlane::setCircularGridNumOfAngularSegments(int theNum)
+{
+  myCircularGridNumOfAngularSections = theNum;
+}
+
+std::pair<double, int> PartSet_PreviewSketchPlane::getCircularGrid_dR_and_NAS() const
+{
+  return std::pair<double, int>(myCircularGridRadialStep, myCircularGridNumOfAngularSections);
+}
+
+void PartSet_PreviewSketchPlane::setCircularGridOffsetX(double theOffset)
+{
+  myCircularGridTransOffset.first = theOffset;
+}
+
+void PartSet_PreviewSketchPlane::setCircularGridOffsetY(double theOffset)
+{
+  myCircularGridTransOffset.second = theOffset;
+}
+
+void PartSet_PreviewSketchPlane::setCircularGridOffsetA(double theOffset)
+{
+  myCircularGridOffsetAngle = theOffset;
+}
+
+std::pair<std::pair<double, double>, double> PartSet_PreviewSketchPlane::getCircularGridOffsets() const
+{
+  return std::pair<std::pair<double, double>, double>(myCircularGridTransOffset, myCircularGridOffsetAngle);
+}
+
+void PartSet_PreviewSketchPlane::configureTrihedron()
+{
+  const opencascade::handle<Geom_Plane> aGeom_Plane = new Geom_Plane(mySketchCS);
+  const auto dimensions = getDimensions();
+  myTrihedron->SetComponent(aGeom_Plane);
+  myTrihedron->SetLength(std::min(dimensions.first, dimensions.second) / 2);
+}
+
+void PartSet_PreviewSketchPlane::initSubstrate(std::shared_ptr<GeomAPI_Face> theFace)
+{
+  if (!theFace) {
+    const auto dimensions = getDimensions();
+    theFace = GeomAlgoAPI_FaceBuilder::planarRectangularFace(mySketchCS, dimensions.first, dimensions.second);
+  }
+
+  AISObjectPtr aAIS = AISObjectPtr(new GeomAPI_AISObject());
+  aAIS->createShape(theFace);
+  std::vector<int> aColor = Config_PropManager::color("Visualization", "sketch_preview_plane");
+  if (aColor.size() == 3)
+    aAIS->setColor(aColor[0], aColor[1], aColor[2]);
+
+  aAIS->setTransparensy(0.8);
+
+  int aDispMode = 1; // shading
+  Handle(AIS_InteractiveObject) anAISIO = aAIS->impl<Handle(AIS_InteractiveObject)>();
+  if (!anAISIO.IsNull()) {
+    anAISIO->Attributes()->SetFaceBoundaryDraw( Standard_True );
+    anAISIO->SetDisplayMode(aDispMode);
+  }
+  mySubstrate = std::move(aAIS);
+}
+
+opencascade::handle<V3d_Viewer> PartSet_PreviewSketchPlane::getV3DViewer() const
+{
+  const ModuleBase_IViewer* const viewer = mySketcherMgr->workshop()->salomeViewer();
+  if (!viewer)
+    return nullptr;
+
+  return viewer->v3dViewer();
+}
+
+bool PartSet_PreviewSketchPlane::isValid() const
+{
+  return myValid;
+}
+
+void PartSet_PreviewSketchPlane::setInvalid()
+{
+  myValid = false;
+
+  XGUI_Displayer* const displayer = mySketcherMgr->workshop()->displayer();
+
+  displayer->eraseAIS(myTrihedron, false /*update viewer*/);
+  myShowTrihedron = false;
+
+  if (mySubstrate && myShowSubstrate) {
+    displayer->eraseAIS(mySubstrate, false /*update viewer*/);
+    myShowSubstrate = false;
+  }
+
+  if (myGridType != PartSet_Tools::SketchPlaneGridType::No) {
+    ModuleBase_IViewer* aViewer = mySketcherMgr->workshop()->salomeViewer();
+    const auto aV3DViewer = getV3DViewer();
+    if (aV3DViewer) {
+      aV3DViewer->DeactivateGrid();
+      myGridType = PartSet_Tools::SketchPlaneGridType::No;
     }
-    return aAIS;
+    else {
+      SketchAccessoryDbg("can't retrieve V3d_Viewer to deactivate grid.");
+    }
+  }
+}
+
+double PartSet_PreviewSketchPlane::getDefaultSize() const {
+  const double defaultSize = mySketchDefaultSize;
+  if (defaultSize <= Precision::Confusion())
+    return PartSet_PreviewSketchPlane::defaultSketchSize();
+
+  return defaultSize;
+}
+
+double PartSet_PreviewSketchPlane::getCircularGridRaduis(const std::pair<double, double>& theBBox2Dimensions) const
+{
+  // Find big enough R to cover entire 2D-bounding of sketch face.
+  const double& Cx = myCircularGridTransOffset.first;
+  const double& Cy = myCircularGridTransOffset.second;
+  const double& W = theBBox2Dimensions.first  / 2;
+  const double& H = theBBox2Dimensions.second / 2;
+
+  /* Distances from center of circular grid to vertices of sketch face 2D rectangular box. */
+  double squareDistances[4];
+  squareDistances[0] = std::pow(Cx - W, 2) + std::pow(Cy - H, 2);
+  squareDistances[1] = std::pow(Cx + W, 2) + std::pow(Cy - H, 2);
+  squareDistances[2] = std::pow(Cx - W, 2) + std::pow(Cy + H, 2);
+  squareDistances[3] = std::pow(Cx + W, 2) + std::pow(Cy + H, 2);
+  double R2 = squareDistances[3];
+  for (int i = 0; i < 3; i++) {
+    if (squareDistances[i] > R2)
+      R2 = squareDistances[i];
   }
+
+  return std::sqrt(R2);
 }
+
+/*static*/ double PartSet_PreviewSketchPlane::defaultSketchSize()
+{
+  const double defaultSize  = Config_PropManager::real(SKETCH_TAB_NAME, "planes_size");
+  if (defaultSize <= Precision::Confusion())
+    return PartSet_PreviewSketchPlane::DEFAULT_SKETCH_SIZE;
+
+  return defaultSize;
+}
+
+/*static*/ const double PartSet_PreviewSketchPlane::DEFAULT_SKETCH_SIZE = 200;
+/*static*/ const double PartSet_PreviewSketchPlane::DEFAULT_RELATIVE_STEP_INVERSE = 20;
+/*static*/ const int PartSet_PreviewSketchPlane::DEFAULT_NUM_OF_ANGULAR_SECTIONS = 18;
+/*static*/ const int PartSet_PreviewSketchPlane::SNAP_PROXIMITY_P = 10;
+
+/*static*/ std::pair<double, double> PartSet_PreviewSketchPlane::dimsOfRectangleFittingRectangle(
+  double wInner, double hInner, double a, double cX, double cY
+) {
+  const auto center = gp_Pnt2d(cX, cY);
+  const auto dirX = gp_Dir2d(std::cos(a), std::sin(a));
+  const gp_Ax2d csOuter = gp_Ax2d(center, dirX);
+
+  gp_Pnt2d vertexInner[4] =
+    {gp_Pnt2d(-wInner/2, -hInner/2), gp_Pnt2d(-wInner/2, hInner/2), gp_Pnt2d(wInner/2, -hInner/2), gp_Pnt2d(wInner/2, hInner/2)};
+
+  gp_Trsf2d trsf;
+  trsf.SetTransformation(csOuter);
+
+  double maxXP = 0, minXN = 0, maxYP = 0, minYN = 0;
+  for (int i = 0; i < 4; i++) {
+    vertexInner[i].Transform(trsf);
+    const auto& v = vertexInner[i];
+    if (v.X() > maxXP)
+      maxXP = v.X();
+    else if (v.X() < minXN)
+      minXN = v.X();
+
+    if (v.Y() > maxYP)
+      maxYP = v.Y();
+    else if (v.Y() < minYN)
+      minYN = v.Y();
+  }
+  return std::pair<double, double>(std::max(maxXP, -minXN) * 2, std::max(maxYP, -minYN) * 2);
+}
\ No newline at end of file
index ae0784bae4aa171688e94ab5a1e9edab8d7693ce..ff76ddc9fb688643ee5d00568c3728998d1b1b5b 100644 (file)
 #ifndef PartSet_PreviewSketchPlane_H
 #define PartSet_PreviewSketchPlane_H
 
-#include <GeomAPI_Pnt.h>
+#include <memory>
+#include <utility>
+#include <Standard_Handle.hxx>
+#include <Aspect_GridType.hxx>
+#include <Aspect_GridDrawMode.hxx>
+#include <ModelAPI_CompositeFeature.h>
+#include <gp_Ax3.hxx>
 
-class GeomAPI_AISObject;
-class GeomAPI_Shape;
+#include "PartSet_Tools.h"
 
-class ModuleBase_IWorkshop;
+class PartSet_SketcherMgr;
 class ModelAPI_CompositeFeature;
+class GeomAPI_AISObject;
+class GeomAPI_Face;
+class AIS_PlaneTrihedron;
+class V3d_Viewer;
+class QString;
+class gp_Trsf;
 
-#include <memory>
+// #define SKETCH_ACCESSORY_DBG;
 
 /**
 * \class PartSet_PreviewSketchPlane
 * \ingroup Modules
-* A class to show/hide sketch preview plane
+* Visualization of 2D-bluebrint' accessories: translucent rectangular substrate, basis axes, grid.
+* All methods do not modify sketch data, unless otherwise is stated.
 */
 class PartSet_PreviewSketchPlane
 {
 public:
-  /// Constructor
-  PartSet_PreviewSketchPlane();
-
-  ~PartSet_PreviewSketchPlane() {};
-
-  /// Erase preview planes
-  /// \param theWorkshop the application workshop
-  /// \param isClearPlane flag whether the plane, origin and normal should be nullified
-  void eraseSketchPlane(ModuleBase_IWorkshop* theWorkshop, const bool isClearPlane = true);
-
-  /// Show preview planes
-  /// \param theSketch source sketch to initialize plane
-  /// \param theWorkshop the application workshop
-  void createSketchPlane(const std::shared_ptr<ModelAPI_CompositeFeature>& theSketch,
-                         ModuleBase_IWorkshop* theWorkshop);
-
-  /// Returns bounding box size covered the sketch sub-elements.
-  /// If the sketch uses extenal face, it will not have default size and returns false.
-  /// \param theSketch sources sketch
-  /// \param [out] theSizeOfView maximum value in X, Y or Z direction
-  /// \param theCentralPoint central point of the sketch sub features
-  /// \return boolean value
-  bool getDefaultSizeOfView(const std::shared_ptr<ModelAPI_CompositeFeature>& theSketch,
-                            double& theSizeOfView,
-                            std::shared_ptr<GeomAPI_Pnt>& theCentralPnt);
-
-  /// Returns whether custom size of view is set
-  /// \return boolean value
-  bool isUseSizeOfView() const { return myIsUseSizeOfView; }
-
-  /// Sets the size of default created face
-  /// \param theSizeOfView value
-  /// \param isUseSizeOfView state whether the size should be used
-  void setSizeOfView(double theSizeOfView, bool isUseSizeOfView,
-    const std::shared_ptr<GeomAPI_Pnt>& theCentralPoint = std::shared_ptr<GeomAPI_Pnt>());
-
-  /// Returns True if the plane preview is already created
-  bool isPlaneCreated() const {
-    return myPlane.get();
-  }
-
-  /// Returns current state of the plane preview visibility
-  bool isDisplayed() const { return myPreviewIsDisplayed; }
-
-  /// Displays preview planes
- /// \param theWorkshop the application workshop
-  void displaySketchPlane(ModuleBase_IWorkshop* theWorkshop);
-
-  /// Nullyfies current plane preview object.
-  /// Important: Before call of this function the plane has to be erased from viewer
-  void clearPlanePreview();
+  inline static bool SketchAccessoryDbg()
+  {
+#ifdef SKETCH_ACCESSORY_DBG
+    return true;
+#else
+  return false;
+#endif
+  };
+
+  static bool SketchAccessoryDbg(const QString& theString);
+  static bool SketchAccessoryDbg(const char* theString);
+  static bool SketchAccessoryDbg(const char* theCSDescription, const gp_Ax3& theCS);
+  static bool SketchAccessoryDbg(const char* theCSDescription, const gp_Pnt& thePoint);
+  static bool SketchAccessoryDbg(const char* theCSDescription, const gp_Trsf& theMatrix);
+
+  enum GridSnappingMode {
+    Off,
+    SnapAnyway,
+    SnapInProximity
+  };
+
+  /*! \param theManager must not be nullptr. */
+  PartSet_PreviewSketchPlane(PartSet_SketcherMgr* theManager);
+  PartSet_PreviewSketchPlane(const PartSet_PreviewSketchPlane&) = delete;
+  PartSet_PreviewSketchPlane& operator=(const PartSet_PreviewSketchPlane&) = delete;
+  ~PartSet_PreviewSketchPlane() = default;
+
+  void savePreferencesIntoSketchData(std::shared_ptr<ModelAPI_CompositeFeature> theSketch) const;
+  void saveRectangularGridPreferencesIntoSketchData(std::shared_ptr<ModelAPI_CompositeFeature> theSketch) const;
+  void saveCircularGridPreferencesIntoSketchData(std::shared_ptr<ModelAPI_CompositeFeature> theSketch) const;
+
+  /*! \brief Updates CS (coordinate system), dimensions and accessories' configuration. */
+  void setCSAndSize(const gp_Ax3& theCS, double theSize);
+
+  /*! \brief Updates CS (coordinate system), dimensions and accessories' configuration.
+  Updates visibility of accessories - visibility preferences are also retrieved from theSketch data.
+  If sketch data contains unitialized grid steps, the method assigns default ones.
+  For rectangular grid default step is a fraction of corresponding dimension.
+  \returns true, on success (if theSketch is valid). */
+  bool setAllUsingSketch(std::shared_ptr<ModelAPI_CompositeFeature> theSketch);
+
+  /*! \brief Call it, after any preference of grid, except grid type, is changed using methods of this instance.
+  Updates grid configuration. Does not affect visibility of grid.
+  \returns true, if grid is successfuly configured. */
+  bool reconfigureGrid();
+
+  /*! \brief Call it, after any preference of grid is changed in theSketch data directly.
+  Updates grid configuration. Updates grid visibility - preference is also retrieved from theSketch data.
+  If sketch data contains unitialized grid steps, the method assigns default ones.
+  For rectangular grid default step is a fraction of corresponding dimension.
+  \returns true, if grid is successfuly configured. */
+  bool reconfigureGridUsingSketch(std::shared_ptr<ModelAPI_CompositeFeature> theSketch);
+
+  /*! \returns true on success. */
+  bool showAxes(bool theShow);
+  /*! \returns true on success. */
+  bool showSubstrate(bool theShow);
+  /*! \brief Changes grid type and reconfigures grid. Shows grid, if theType != SketchPlaneGridType::No.
+  \returns true on success. */
+  bool setGridType(PartSet_Tools::SketchPlaneGridType::Enum theType);
+
+  PartSet_Tools::SketchPlaneGridType::Enum getGridType() const { return myGridType; }
+
+  /*! \brief Hides all accessories. */
+  void hideAll();
+
+  /*! \brief Substrate dimensions. Substrate dimension coincides with one of the sketch face, if the latter is non-zero. */
+  std::pair<double, double> getDimensions() const;
+
+  bool isShowAxes() const { return myShowTrihedron; }
+  bool isShowSubstrate() const { return myShowSubstrate; }
+  bool isShowGrid() const { return myGridType != PartSet_Tools::SketchPlaneGridType::No; }
+
+  GridSnappingMode getGridSnappingMode() const { return mySnappingMode; };
+  void setGridSnappingMode(GridSnappingMode theMode) { mySnappingMode = theMode; };
+
+  /*! \brief Sets default steps and zero offsets. */
+  void resetRectangularGrid();
+
+  void setRectangularGridStepX(double theStepX);
+  void setRectangularGridStepY(double theStepY);
+  std::pair<double, double> getRectangularGridSteps() const { return myRectangularGridSteps; }
+
+  void setRectangularGridOffsetX(double theOffsetX);
+  void setRectangularGridOffsetY(double theOffsetY);
+  void setRectangularGridOffsetA(double theOffsetAngle);
+  std::pair<std::pair<double, double>, double> getRectangularGridOffsets() const;
+  int getRectangularGridDimOfNodeSpace() const { return myRectangularGridDimOfNodeSpace; }
+
+  /*! \brief Sets default steps and zero offsets. */
+  void resetCircularGrid();
+
+  void setCircularGridRadialStep(double theStep);
+  void setCircularGridNumOfAngularSegments(int theNum);
+  std::pair<double, int> getCircularGrid_dR_and_NAS() const;
+
+  void setCircularGridOffsetX(double theOffset);
+  void setCircularGridOffsetY(double theOffset);
+  void setCircularGridOffsetA(double theOffset);
+  std::pair<std::pair<double, double>, double> getCircularGridOffsets() const;
+  int getCircularGridDimOfNodeSpace() const { return myCircularGridDimOfNodeSpace; }
 
 private:
-  /// Create a square face by parameters
-  std::shared_ptr<GeomAPI_AISObject> createPreviewPlane();
+  void configureTrihedron();
+  void initSubstrate(std::shared_ptr<GeomAPI_Face> theFace);
+
+  opencascade::handle<V3d_Viewer> getV3DViewer() const;
+  bool isValid() const;
+  void setInvalid();
+
+  /*! \returns Default size from sketch data (if > 0), or the default size from Config_PropManager (if > 0), or DEFAULT_SKETCH_SIZE. */
+  double getDefaultSize() const;
+
+  double getCircularGridRaduis(const std::pair<double, double>& theBBox2Dimensions) const;
+
+  /*! \returns Dimensions of outer rectangle, center of which is shifted from center of inner rectangle by (cX, cY);
+  outer rectangle is rotated by angle (counterclockwise is positive) relative to inner one. */
+  static std::pair<double, double> dimsOfRectangleFittingRectangle(double wInner, double hInner, double angle, double cX, double cY);
+
+public:
+  /*! \returns Default size from Config_PropManager (if > 0), or DEFAULT_SKETCH_SIZE. */
+  static double defaultSketchSize();
+
+  static const double DEFAULT_RELATIVE_STEP_INVERSE;
+  static const int DEFAULT_NUM_OF_ANGULAR_SECTIONS;
+  static const int SNAP_PROXIMITY_P; /// Distance [pixel], at which snapping to grid engages in SnapInProximity mode.
 
 private:
-  bool myPreviewIsDisplayed;
-  std::shared_ptr<GeomAPI_AISObject> myPlane; //! visualized presentation
-  std::shared_ptr<GeomAPI_Shape> myShape; //! current shape to be displayed
-  std::shared_ptr<GeomAPI_Pnt> myViewCentralPoint; //! central point of the default view
-
-  double mySizeOfView; //! size that should be used by creating a default face
-  bool myIsUseSizeOfView; //! state if the size is custom or from preferences
-  std::shared_ptr<GeomAPI_Pnt> myViewOrigin; //! origin point of sketch if default view is used
+  /** Is used to create default substrate, if plane size is 0 both in the sketch data and in Config_PropManager. */
+  static const double DEFAULT_SKETCH_SIZE;
+
+  const PartSet_SketcherMgr* const mySketcherMgr;
+
+  bool myValid;
+  gp_Ax3 mySketchCS;
+  std::pair<double, double> mySketchDimensions; // Width and height of sketch bounding box, aligned with sketch axes.
+  double mySketchDefaultSize;
+
+  bool myShowTrihedron;
+  opencascade::handle<AIS_PlaneTrihedron> myTrihedron; // Visualization of sketch axes and origin.
+
+  bool myShowSubstrate;
+  std::shared_ptr<GeomAPI_AISObject> mySubstrate; // Translucent substrate.
+
+  PartSet_Tools::SketchPlaneGridType::Enum myGridType;
+  Aspect_GridDrawMode myGridDrawMode;
+  GridSnappingMode mySnappingMode;
+
+  std::pair<double, double> myRectangularGridSteps;
+  std::pair<double, double> myRectangularGridTransOffset;
+  double myRectangularGridOffsetAngle;
+  int myRectangularGridDimOfNodeSpace; // Minimal dimension of space which can fit displayed grid nodes.
+
+  double myCircularGridRadialStep;
+  int    myCircularGridNumOfAngularSections;
+  std::pair<double, double> myCircularGridTransOffset;
+  double myCircularGridOffsetAngle;
+  int myCircularGridDimOfNodeSpace; // Minimal dimension of space which can fit displayed grid nodes.
 };
 
 #endif
\ No newline at end of file
index 1a34c5127dabb1b31c31351558176b5f1ca1ffcc..6b95b8a539176e5c303fb5ef93ef0ce03e34f5c3 100644 (file)
@@ -206,7 +206,7 @@ PartSet_SketcherMgr::PartSet_SketcherMgr(PartSet_Module* theModule)
   myIsConstraintsShown[PartSet_Tools::Dimensional] = true;
   myIsConstraintsShown[PartSet_Tools::Expressions] = false;
 
-  mySketchPlane = new PartSet_PreviewSketchPlane();
+  mySketchPlane = new PartSet_PreviewSketchPlane(this);
 
   registerSelectionFilter(SF_SketchCirclePointFilter, new PartSet_CirclePointFilter(anIWorkshop));
   registerSelectionFilter(SF_SketchPlaneFilter, new ModuleBase_ShapeInPlaneFilter());
@@ -535,13 +535,12 @@ void PartSet_SketcherMgr::onMouseReleased(ModuleBase_IViewWindow* theWnd, QMouse
   bool aWasDragging = myIsDragging;
   myIsDragging = false;
 
-  if (myModule->sketchReentranceMgr()->processMouseReleased(theWnd, theEvent)) {
+  if (myModule->sketchReentranceMgr()->processMouseReleased(theWnd, theEvent))
     return;
-  }
+
   // if mouse is pressed when it was over view and at release the mouse is out of view, do nothing
-  if (!myIsMouseOverViewProcessed) {
+  if (!myIsMouseOverViewProcessed)
     return;
-  }
 
   ModuleBase_OperationFeature* aOp =
     dynamic_cast<ModuleBase_OperationFeature*>(getCurrentOperation());
@@ -555,9 +554,45 @@ void PartSet_SketcherMgr::onMouseReleased(ModuleBase_IViewWindow* theWnd, QMouse
         myNoDragMoving = !myNoDragMoving;
       else
         myNoDragMoving = false;
+
       if (myNoDragMoving)
         return;
       else {
+        ModuleBase_OperationFeature* aOp =
+        dynamic_cast<ModuleBase_OperationFeature*>(getCurrentOperation());
+
+        bool isEditing = false;
+        if (aOp) {
+          isEditing = aOp->isEditOperation();
+          bool aStartNoDragOperation = !aViewer->canDragByMouse() && isEditing;
+          if (aStartNoDragOperation || myNoDragMoving) {
+            // Process edit operation without dragging
+            if (myCurrentSelection.size() > 0)
+              myNoDragMoving = !myNoDragMoving;
+            else
+              myNoDragMoving = false;
+            if (myNoDragMoving)
+              return;
+            else {
+              restoreSelection(myCurrentSelection);
+              myCurrentSelection.clear();
+            }
+          }
+          else {
+            if (isNestedSketchOperation(aOp)) {
+              // Only for sketcher operations
+              if (aWasDragging) {
+                if (myDragDone) {
+                  /// the previous selection is lost by mouse release in the viewer(Select method), but
+                  /// it is still stored in myCurrentSelection. So, it is possible to restore selection
+                  /// It is important for drag(edit with mouse) of sketch entities.
+                  restoreSelection(myCurrentSelection);
+                  myCurrentSelection.clear();
+                }
+              }
+            }
+          }
+        }
         restoreSelection(myCurrentSelection);
         myCurrentSelection.clear();
       }
@@ -580,6 +615,7 @@ void PartSet_SketcherMgr::onMouseReleased(ModuleBase_IViewWindow* theWnd, QMouse
 
   ModuleBase_ModelWidget* anActiveWidget = getActiveWidget();
   PartSet_MouseProcessor* aProcessor = dynamic_cast<PartSet_MouseProcessor*>(anActiveWidget);
+
   if (aProcessor) {
     ModuleBase_ISelection* aSelection = aWorkshop->selection();
     QList<ModuleBase_ViewerPrsPtr> aPreSelected = aSelection->getHighlighted();
@@ -593,6 +629,7 @@ void PartSet_SketcherMgr::onMouseReleased(ModuleBase_IViewWindow* theWnd, QMouse
     QString aOpId = aOp->id();
     if (aOpId == "Sketch")
       return;
+
     QPoint aPnt(theEvent->x(), theEvent->y());
     anActiveWidget = getActiveWidget();
     if ((aPnt == myMousePoint) && anActiveWidget) {
@@ -630,6 +667,7 @@ void PartSet_SketcherMgr::onMouseMoved(ModuleBase_IViewWindow* theWnd, QMouseEve
   XGUI_Displayer* aDisplayer = aConnector->workshop()->displayer();
 
   if (isNestedCreateOperation(getCurrentOperation(), activeSketch())) {
+
 #ifdef DRAGGING_DEBUG
     QTime t;
     t.start();
@@ -639,8 +677,9 @@ void PartSet_SketcherMgr::onMouseMoved(ModuleBase_IViewWindow* theWnd, QMouseEve
     // presentation. These widgets correct the feature attribute according to the mouse position
     ModuleBase_ModelWidget* anActiveWidget = myModule->activeWidget();
     PartSet_MouseProcessor* aProcessor = dynamic_cast<PartSet_MouseProcessor*>(anActiveWidget);
-    if (aProcessor)
+    if (aProcessor) {
       aProcessor->mouseMoved(theWnd, theEvent);
+    }
     if (!myIsMouseOverViewProcessed) {
       myIsMouseOverViewProcessed = true;
 
@@ -667,10 +706,12 @@ void PartSet_SketcherMgr::onMouseMoved(ModuleBase_IViewWindow* theWnd, QMouseEve
     // mouse press signal in the viewer(it call Select for AIS context and the dragged objects are
     // deselected). This flag should be restored in the slot, processed the mouse release signal.
     ModuleBase_Operation* aCurrentOperation = getCurrentOperation();
-    if (!aCurrentOperation)
+    if (!aCurrentOperation) {
       return;
-    if (isSketchOperation(aCurrentOperation))
+    }
+    if (isSketchOperation(aCurrentOperation)) {
       return; // No edit operation activated
+    }
 
 #ifdef DRAGGING_DEBUG
     QTime t;
@@ -745,6 +786,7 @@ void PartSet_SketcherMgr::onMouseMoved(ModuleBase_IViewWindow* theWnd, QMouseEve
         }
       }
     }
+
     // the modified state of the current operation should be updated if there are features, which
     // were changed here
     if (isModified) {
@@ -1093,16 +1135,9 @@ void PartSet_SketcherMgr::startSketch(ModuleBase_Operation* theOperation)
 
   // Display all sketcher sub-Objects
   myCurrentSketch = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(aFOperation->feature());
-  double aSizeOfView = 0;
-  std::shared_ptr<GeomAPI_Pnt> aCentralPoint;
-  // Reset size of view from previous launches
-  mySketchPlane->setSizeOfView(aSizeOfView, false, aCentralPoint);
-  if (aFOperation->isEditOperation() &&
-      mySketchPlane->getDefaultSizeOfView(myCurrentSketch, aSizeOfView, aCentralPoint)) {
-    mySketchPlane->setSizeOfView(aSizeOfView, true, aCentralPoint);
-  }
+  if (aFOperation->isEditOperation())
+    mySketchPlane->setAllUsingSketch(myCurrentSketch);
 
-  mySketchPlane->createSketchPlane(myCurrentSketch, myModule->workshop());
   XGUI_ModuleConnector* aConnector = dynamic_cast<XGUI_ModuleConnector*>(myModule->workshop());
 
   // Hide sketcher result
@@ -1225,7 +1260,7 @@ void PartSet_SketcherMgr::stopSketch(ModuleBase_Operation* theOperation)
     XGUI_Displayer* aDisplayer = aConnector->workshop()->displayer();
     // The sketch was aborted
     myCurrentSketch = CompositeFeaturePtr();
-    mySketchPlane->eraseSketchPlane(myModule->workshop());
+    mySketchPlane->hideAll();
 
     // Erase all sketcher objects
     QObjectPtrList aObjects = aDisplayer->displayedObjects();
@@ -1267,7 +1302,7 @@ void PartSet_SketcherMgr::stopSketch(ModuleBase_Operation* theOperation)
       myCurrentSketch->setDisplayed(true);
 
     myCurrentSketch = CompositeFeaturePtr();
-    mySketchPlane->eraseSketchPlane(myModule->workshop());
+    mySketchPlane->hideAll();
 
     Events_Loop::loop()->flush(aDispEvent);
   }
index 12b9f4fce1b1ed8e69b7e931e40e2d64e17c6fc4..312021601fa32382e60ce9edb8a2627cfddc5e6b 100644 (file)
@@ -487,6 +487,8 @@ private:
   std::vector<int> colorOfObject(const ObjectPtr& theObject,
     const FeaturePtr& aFeature, bool isConstruction) const;
 
+  friend class PartSet_PreviewSketchPlane;
+
 private:
   PartSet_Module* myModule;
   PartSet_PreviewSketchPlane* mySketchPlane; // display/erase sketch plane on start/stop sketch
index fd71ff50bbf7480c8eeda1ac1cf2d31f8b43564b..62b743ee49ac9a1d6d8a8efd9f6def4e4036e0b9 100644 (file)
@@ -23,6 +23,9 @@
 
 #include <ModelAPI_Data.h>
 #include <ModelAPI_AttributeDouble.h>
+#include <ModelAPI_AttributeInteger.h>
+#include <ModelAPI_AttributeBoolean.h>
+#include <ModelAPI_AttributeString.h>
 #include <ModelAPI_AttributeRefList.h>
 #include <ModelAPI_Document.h>
 #include <ModelAPI_Session.h>
@@ -81,6 +84,8 @@
 
 #include <V3d_View.hxx>
 #include <gp_Pln.hxx>
+#include <gp_Vec.hxx>
+#include <gp_Dir.hxx>
 #include <gp_Circ.hxx>
 #include <ProjLib.hxx>
 #include <ElSLib.hxx>
@@ -88,6 +93,7 @@
 #include <GeomAPI_ProjectPointOnCurve.hxx>
 #include <BRep_Tool.hxx>
 #include <TopoDS.hxx>
+#include <TopoDS_Face.hxx>
 #include <TopoDS_Edge.hxx>
 #include <TopoDS_Vertex.hxx>
 #include <AIS_InteractiveObject.hxx>
@@ -324,6 +330,404 @@ void PartSet_Tools::nullifySketchPlane(CompositeFeaturePtr theSketch)
   anOrigin->reset();
 }
 
+std::pair<bool, gp_Ax3> PartSet_Tools::getWorldCSAt(const GeomAPI_Face& theFace, double U, double V)
+{
+  auto res = std::pair<bool, gp_Ax3>(false, gp_Ax3());
+  try {
+    const TopoDS_Face& face = theFace.impl<TopoDS_Face>();
+    if (face.IsNull())
+      return res;
+
+    const opencascade::handle<Geom_Surface> surface = BRep_Tool::Surface(face);
+    if (surface.IsNull())
+      return res;
+
+    gp_Pnt origin;
+    gp_Vec dirX;
+    gp_Vec dirY;
+    surface->D1(U, V, origin, dirX, dirY);
+    const gp_Vec normal = dirX.Crossed(dirY);
+
+    res.second = gp_Ax3(origin, gp_Dir(normal), gp_Dir(dirX));
+    res.first = true;
+    return res;
+  }
+  catch (...) {
+    return res;
+  }
+}
+
+std::pair<bool, gp_Pnt> PartSet_Tools::getWorldPointByUV(const GeomAPI_Face& theFace, double U, double V)
+{
+  auto res = std::pair<bool, gp_Pnt>(false, gp_Pnt());
+  try {
+    const TopoDS_Face& face = theFace.impl<TopoDS_Face>();
+    if (face.IsNull())
+      return res;
+
+    const opencascade::handle<Geom_Surface> surface = BRep_Tool::Surface(face);
+    if (surface.IsNull())
+      return res;
+
+    res.second = surface->Value(U, V);
+    res.first = true;
+    return res;
+  }
+  catch (...) {
+    return res;
+  }
+}
+
+std::pair<bool, Bnd_Box> PartSet_Tools::getBBoxAtCS(const GeomAPI_Shape& theShape, const gp_Ax3 theCS)
+{
+  auto res = std::pair<bool, Bnd_Box>(false, Bnd_Box());
+
+  { // Get BBox if theShape is GeomAPI_Pln, which has linear dependency of X and Y on U and V.
+    const auto face = theShape.face();
+    if (face) {
+      const auto plane = face->getPlane();
+      if (plane) {
+        try {
+          double UMax, UMin, VMax, VMin = 0;
+          face->optimalBounds(UMin, UMax, VMin, VMax);
+          std::pair<double, double> UVPairs[4];
+          UVPairs[0] = std::pair<double, double>(UMin, VMin);
+          UVPairs[1] = std::pair<double, double>(UMax, VMin);
+          UVPairs[2] = std::pair<double, double>(UMin, VMax);
+          UVPairs[3] = std::pair<double, double>(UMax, VMax);
+
+          gp_Pnt points[4];
+          bool success = true;
+          for (int idx = 0; idx < 4; idx++) {
+            std::tie(success, points[idx]) = getWorldPointByUV(*face, UVPairs[idx].first, UVPairs[idx].second);
+            if (!success)
+              return res;
+          }
+
+          gp_Trsf trsf;
+          trsf.SetTransformation(theCS);
+          for (int idx = 0; idx < 4; idx++) {
+            res.second.Add(points[idx].Transformed(trsf));
+          }
+          res.first = true;
+          return res;
+        }
+        catch(...) {
+          return res;
+        }
+      } // If Pln.
+    } // If Face.
+  } // If theShape is GeomAPI_Pln.
+
+  { // TODO Fill aligned (oriented) bounding box instead of tranforming unaligned one.
+    double Xmin, Ymin, Zmin, Xmax, Ymax, Zmax = 0;
+    const bool success = theShape.computeSize(Xmin, Ymin, Zmin, Xmax, Ymax, Zmax);
+    if (!success)
+      return res;
+
+    res.second.Update(Xmin, Ymin, Zmin, Xmax, Ymax, Zmax);
+
+    gp_Trsf trsf;
+    trsf.SetTransformation(theCS);
+    res.second = res.second.Transformed(trsf);
+
+    return res;
+  }
+
+  return res;
+}
+
+std::shared_ptr<ModelAPI_AttributeDouble> PartSet_Tools::sketchPlaneDefaultSize(CompositeFeaturePtr theSketch)
+{
+  auto aSize = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(
+    theSketch->data()->attribute(SketchPlugin_Sketch::DEFAULT_SIZE_ID())
+  );
+
+  if (!aSize) {
+    aSize = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(theSketch->data()->addAttribute(
+      SketchPlugin_Sketch::DEFAULT_SIZE_ID(),
+      ModelAPI_AttributeDouble::typeId()
+    ));
+
+    aSize->setIsArgument(false);
+    aSize->setValue(0);
+  }
+
+  return aSize;
+}
+
+std::shared_ptr<ModelAPI_AttributeBoolean> PartSet_Tools::sketchPlaneAxesEnabled(CompositeFeaturePtr theSketch)
+{
+  auto isEnabled = std::dynamic_pointer_cast<ModelAPI_AttributeBoolean>(
+    theSketch->data()->attribute(SketchPlugin_Sketch::AXES_ENABLED_ID())
+  );
+
+  if (!isEnabled) {
+    isEnabled = std::dynamic_pointer_cast<ModelAPI_AttributeBoolean>(theSketch->data()->addAttribute(
+      SketchPlugin_Sketch::AXES_ENABLED_ID(),
+      ModelAPI_AttributeBoolean::typeId()
+    ));
+
+    isEnabled->setIsArgument(false);
+    isEnabled->setValue(true);
+  }
+
+  return isEnabled;
+}
+
+std::shared_ptr<ModelAPI_AttributeBoolean> PartSet_Tools::sketchPlaneSubstrateEnabled(CompositeFeaturePtr theSketch)
+{
+  auto isEnabled = std::dynamic_pointer_cast<ModelAPI_AttributeBoolean>(
+    theSketch->data()->attribute(SketchPlugin_Sketch::SUBSTRATE_ENABLED_ID())
+  );
+
+  if (!isEnabled) {
+    isEnabled = std::dynamic_pointer_cast<ModelAPI_AttributeBoolean>(theSketch->data()->addAttribute(
+      SketchPlugin_Sketch::SUBSTRATE_ENABLED_ID(),
+      ModelAPI_AttributeBoolean::typeId()
+    ));
+
+    isEnabled->setIsArgument(false);
+    isEnabled->setValue(true);
+  }
+
+  return isEnabled;
+}
+
+std::shared_ptr<ModelAPI_AttributeString> PartSet_Tools::sketchPlaneGridType(CompositeFeaturePtr theSketch)
+{
+  auto gridType = std::dynamic_pointer_cast<ModelAPI_AttributeString>(
+    theSketch->data()->attribute(SketchPlugin_Sketch::CONSTRUCTION_GRID_TYPE_ID())
+  );
+
+  if (!gridType) {
+    gridType = std::dynamic_pointer_cast<ModelAPI_AttributeString>(theSketch->data()->addAttribute(
+      SketchPlugin_Sketch::CONSTRUCTION_GRID_TYPE_ID(),
+      ModelAPI_AttributeString::typeId()
+    ));
+
+    gridType->setIsArgument(false);
+    gridType->setValue(PartSet_Tools::SketchPlaneGridType::toString(PartSet_Tools::SketchPlaneGridType::No));
+  }
+
+  return gridType;
+}
+
+std::string PartSet_Tools::SketchPlaneGridType::toString(Enum iType)
+{
+  if (iType >= SketchPlaneGridType::STRINGS.size())
+    return SketchPlaneGridType::STRINGS[0];
+
+  return SketchPlaneGridType::STRINGS[iType];
+}
+
+PartSet_Tools::SketchPlaneGridType::Enum PartSet_Tools::SketchPlaneGridType::fromString(const std::string& iTypeString)
+{
+  for (int i = 0; i < SketchPlaneGridType::STRINGS.size(); i++) {
+    if (SketchPlaneGridType::STRINGS[i] == iTypeString) {
+      return SketchPlaneGridType::Enum(i);
+    }
+  }
+  return SketchPlaneGridType::Enum::No;
+}
+
+const std::array<std::string, 3> PartSet_Tools::SketchPlaneGridType::STRINGS = {"", "Rectangular", "Circular"};
+
+void PartSet_Tools::setSketchPlaneGridType(CompositeFeaturePtr theSketch, PartSet_Tools::SketchPlaneGridType::Enum theType)
+{
+  PartSet_Tools::sketchPlaneGridType(theSketch)->setValue(PartSet_Tools::SketchPlaneGridType::toString(theType));
+}
+
+PartSet_Tools::SketchPlaneGridType::Enum PartSet_Tools::getSketchPlaneGridType(CompositeFeaturePtr theSketch)
+{
+  return PartSet_Tools::SketchPlaneGridType::fromString(PartSet_Tools::sketchPlaneGridType(theSketch)->value());
+}
+
+std::shared_ptr<ModelAPI_AttributeDouble> PartSet_Tools::sketchPlaneRectangularGridStepX(CompositeFeaturePtr theSketch) {
+  auto aStep = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(
+    theSketch->data()->attribute(SketchPlugin_Sketch::RECTANGULAR_CONSTRUCTION_GRID_STEP_X_ID())
+  );
+
+  if (!aStep) {
+    aStep = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(theSketch->data()->addAttribute(
+      SketchPlugin_Sketch::RECTANGULAR_CONSTRUCTION_GRID_STEP_X_ID(),
+      ModelAPI_AttributeDouble::typeId()
+    ));
+
+    aStep->setIsArgument(false);
+    aStep->setValue(-1);
+  }
+
+  return aStep;
+}
+
+std::shared_ptr<ModelAPI_AttributeDouble> PartSet_Tools::sketchPlaneRectangularGridStepY(CompositeFeaturePtr theSketch) {
+  auto aStep = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(
+    theSketch->data()->attribute(SketchPlugin_Sketch::RECTANGULAR_CONSTRUCTION_GRID_STEP_Y_ID())
+  );
+
+  if (!aStep) {
+    aStep = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(theSketch->data()->addAttribute(
+      SketchPlugin_Sketch::RECTANGULAR_CONSTRUCTION_GRID_STEP_Y_ID(),
+      ModelAPI_AttributeDouble::typeId()
+    ));
+
+    aStep->setIsArgument(false);
+    aStep->setValue(-1);
+  }
+
+  return aStep;
+}
+
+std::shared_ptr<ModelAPI_AttributeDouble> PartSet_Tools::sketchPlaneRectangularGridOffsetAngle(CompositeFeaturePtr theSketch)
+{
+  auto aAngle = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(
+    theSketch->data()->attribute(SketchPlugin_Sketch::RECTANGULAR_CONSTRUCTION_GRID_OFFSET_ANGLE_ID())
+  );
+
+  if (!aAngle) {
+    aAngle = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(theSketch->data()->addAttribute(
+      SketchPlugin_Sketch::RECTANGULAR_CONSTRUCTION_GRID_OFFSET_ANGLE_ID(),
+      ModelAPI_AttributeDouble::typeId()
+    ));
+
+    aAngle->setIsArgument(false);
+    aAngle->setValue(0);
+  }
+
+  return aAngle;
+}
+
+std::shared_ptr<ModelAPI_AttributeDouble> PartSet_Tools::sketchPlaneRectangularGridOffsetX(CompositeFeaturePtr theSketch) {
+  auto aShift = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(
+    theSketch->data()->attribute(SketchPlugin_Sketch::RECTANGULAR_CONSTRUCTION_GRID_OFFSET_X_ID())
+  );
+
+  if (!aShift) {
+    aShift = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(theSketch->data()->addAttribute(
+      SketchPlugin_Sketch::RECTANGULAR_CONSTRUCTION_GRID_OFFSET_X_ID(),
+      ModelAPI_AttributeDouble::typeId()
+    ));
+
+    aShift->setIsArgument(false);
+    aShift->setValue(0);
+  }
+
+  return aShift;
+}
+
+std::shared_ptr<ModelAPI_AttributeDouble> PartSet_Tools::sketchPlaneRectangularGridOffsetY(CompositeFeaturePtr theSketch) {
+  auto aShift = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(
+    theSketch->data()->attribute(SketchPlugin_Sketch::RECTANGULAR_CONSTRUCTION_GRID_OFFSET_Y_ID())
+  );
+
+  if (!aShift) {
+    aShift = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(theSketch->data()->addAttribute(
+      SketchPlugin_Sketch::RECTANGULAR_CONSTRUCTION_GRID_OFFSET_Y_ID(),
+      ModelAPI_AttributeDouble::typeId()
+    ));
+
+    aShift->setIsArgument(false);
+    aShift->setValue(0);
+  }
+
+  return aShift;
+}
+
+
+std::shared_ptr<ModelAPI_AttributeDouble> PartSet_Tools::sketchPlaneCircularGridStepR(CompositeFeaturePtr theSketch)
+{
+  auto aStep = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(
+    theSketch->data()->attribute(SketchPlugin_Sketch::CIRCULAR_CONSTRUCTION_GRID_STEP_R_ID())
+  );
+
+  if (!aStep) {
+    aStep = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(theSketch->data()->addAttribute(
+      SketchPlugin_Sketch::CIRCULAR_CONSTRUCTION_GRID_STEP_R_ID(),
+      ModelAPI_AttributeDouble::typeId()
+    ));
+
+    aStep->setIsArgument(false);
+    aStep->setValue(-1);
+  }
+
+  return aStep;
+}
+
+std::shared_ptr<ModelAPI_AttributeInteger> PartSet_Tools::sketchPlaneCircularGridNumOfAngSegments(CompositeFeaturePtr theSketch)
+{
+  auto num = std::dynamic_pointer_cast<ModelAPI_AttributeInteger>(
+    theSketch->data()->attribute(SketchPlugin_Sketch::CIRCULAR_CONSTRUCTION_GRID_NUM_OF_ANG_SEGMENTS_ID())
+  );
+
+  if (!num) {
+    num = std::dynamic_pointer_cast<ModelAPI_AttributeInteger>(theSketch->data()->addAttribute(
+      SketchPlugin_Sketch::CIRCULAR_CONSTRUCTION_GRID_NUM_OF_ANG_SEGMENTS_ID(),
+      ModelAPI_AttributeInteger::typeId()
+    ));
+
+    num->setIsArgument(false);
+    num->setValue(-1);
+  }
+
+  return num;
+}
+
+std::shared_ptr<ModelAPI_AttributeDouble> PartSet_Tools::sketchPlaneCircularGridOffsetAngle(CompositeFeaturePtr theSketch)
+{
+  auto aAngle = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(
+    theSketch->data()->attribute(SketchPlugin_Sketch::CIRCULAR_CONSTRUCTION_GRID_OFFSET_ANGLE_ID())
+  );
+
+  if (!aAngle) {
+    aAngle = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(theSketch->data()->addAttribute(
+      SketchPlugin_Sketch::CIRCULAR_CONSTRUCTION_GRID_OFFSET_ANGLE_ID(),
+      ModelAPI_AttributeDouble::typeId()
+    ));
+
+    aAngle->setIsArgument(false);
+    aAngle->setValue(0);
+  }
+
+  return aAngle;
+}
+
+std::shared_ptr<ModelAPI_AttributeDouble> PartSet_Tools::sketchPlaneCircularGridOffsetX(CompositeFeaturePtr theSketch) {
+  auto aShift = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(
+    theSketch->data()->attribute(SketchPlugin_Sketch::CIRCULAR_CONSTRUCTION_GRID_OFFSET_X_ID())
+  );
+
+  if (!aShift) {
+    aShift = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(theSketch->data()->addAttribute(
+      SketchPlugin_Sketch::CIRCULAR_CONSTRUCTION_GRID_OFFSET_X_ID(),
+      ModelAPI_AttributeDouble::typeId()
+    ));
+
+    aShift->setIsArgument(false);
+    aShift->setValue(0);
+  }
+
+  return aShift;
+}
+
+std::shared_ptr<ModelAPI_AttributeDouble> PartSet_Tools::sketchPlaneCircularGridOffsetY(CompositeFeaturePtr theSketch) {
+  auto aShift = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(
+    theSketch->data()->attribute(SketchPlugin_Sketch::CIRCULAR_CONSTRUCTION_GRID_OFFSET_Y_ID())
+  );
+
+  if (!aShift) {
+    aShift = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(theSketch->data()->addAttribute(
+      SketchPlugin_Sketch::CIRCULAR_CONSTRUCTION_GRID_OFFSET_Y_ID(),
+      ModelAPI_AttributeDouble::typeId()
+    ));
+
+    aShift->setIsArgument(false);
+    aShift->setValue(0);
+  }
+
+  return aShift;
+}
+
 std::shared_ptr<GeomAPI_Pnt> PartSet_Tools::point3D(std::shared_ptr<GeomAPI_Pnt2d> thePoint2D,
                                                       CompositeFeaturePtr theSketch)
 {
index f8ae5dba73f6ea7c68e53e44cb793ae20c6a3ba0..11e2da8c07471e003a5f7e06a4e80cac67338a00 100644 (file)
@@ -23,6 +23,8 @@
 #include "PartSet.h"
 
 #include <gp_Pnt.hxx>
+#include <gp_Ax3.hxx>
+#include <Bnd_Box.hxx>
 
 #include <QPoint>
 #include <QList>
@@ -38,6 +40,9 @@
 #include <TopoDS_Shape.hxx>
 
 #include <memory>
+#include <string>
+#include <array>
+#include <utility>
 
 class V3d_View;
 class ModuleBase_IViewWindow;
@@ -45,6 +50,7 @@ class ModuleBase_ViewerPrs;
 class ModuleBase_IWorkshop;
 class GeomDataAPI_Point2D;
 class GeomAPI_Shape;
+class GeomAPI_Face;
 class GeomAPI_Pln;
 class GeomAPI_Pnt2d;
 class GeomAPI_Pnt;
@@ -149,6 +155,79 @@ public:
   /// \return API object of geom plane
   static void nullifySketchPlane(CompositeFeaturePtr theSketch);
 
+  /*! \returns Basis of theFace' surface, defined in world CS, at point, which corresponds to point (U, V) in parametric space. */
+  static std::pair<bool, gp_Ax3> getWorldCSAt(const GeomAPI_Face& theFace, double U, double V);
+
+  /*! \returns Point, defined in world CS, which corresponds to point (U, V) in parametric space. */
+  static std::pair<bool, gp_Pnt> getWorldPointByUV(const GeomAPI_Face& theFace, double U, double V);
+
+  /*! \brief Fills bounding box, which is aligned according to theCS.
+  Returns the smallest possible box only for GeomAPI_Plns, for other shapes
+  returns unaligned transformed bounding box (so its dimensions are bigger than the smallest)!
+  \returns true on success. */
+  static std::pair<bool, Bnd_Box> getBBoxAtCS(const GeomAPI_Shape& theShape, const gp_Ax3 theCS);
+
+  /// @returns 0, if the plane was initialized on a feature. Result is never nullptr.
+  static std::shared_ptr<ModelAPI_AttributeDouble> sketchPlaneDefaultSize(CompositeFeaturePtr theSketch);
+
+  /// @returns never nullptr.
+  static std::shared_ptr<ModelAPI_AttributeBoolean> sketchPlaneAxesEnabled(CompositeFeaturePtr theSketch);
+
+  /// @returns never nullptr.
+  static std::shared_ptr<ModelAPI_AttributeBoolean> sketchPlaneSubstrateEnabled(CompositeFeaturePtr theSketch);
+
+  /// @returns empty string, if construction grid is disabled. Result is never nullptr.
+  static std::shared_ptr<ModelAPI_AttributeString> sketchPlaneGridType(CompositeFeaturePtr theSketch);
+
+  class SketchPlaneGridType {
+  public:
+    typedef enum {
+      No,
+      Rectangular,
+      Circular
+    } Enum;
+
+    static std::string toString(Enum iType);
+    static Enum fromString(const std::string& iTypeString);
+
+  private:
+    static const std::array<std::string, 3> STRINGS;
+  };
+
+  static void setSketchPlaneGridType(CompositeFeaturePtr theSketch, SketchPlaneGridType::Enum theType);
+  static SketchPlaneGridType::Enum getSketchPlaneGridType(CompositeFeaturePtr theSketch);
+
+  /// @returns never nullptr.
+  static std::shared_ptr<ModelAPI_AttributeDouble> sketchPlaneRectangularGridStepX(CompositeFeaturePtr theSketch);
+
+  /// @returns never nullptr.
+  static std::shared_ptr<ModelAPI_AttributeDouble> sketchPlaneRectangularGridStepY(CompositeFeaturePtr theSketch);
+
+  /// @returns never nullptr.
+  static std::shared_ptr<ModelAPI_AttributeDouble> sketchPlaneRectangularGridOffsetAngle(CompositeFeaturePtr theSketch);
+
+  /// @returns never nullptr.
+  static std::shared_ptr<ModelAPI_AttributeDouble> sketchPlaneRectangularGridOffsetX(CompositeFeaturePtr theSketch);
+
+  /// @returns never nullptr.
+  static std::shared_ptr<ModelAPI_AttributeDouble> sketchPlaneRectangularGridOffsetY(CompositeFeaturePtr theSketch);
+
+  /// @returns never nullptr.
+  static std::shared_ptr<ModelAPI_AttributeDouble> sketchPlaneCircularGridStepR(CompositeFeaturePtr theSketch);
+
+  /// @returns never nullptr.
+  static std::shared_ptr<ModelAPI_AttributeInteger> sketchPlaneCircularGridNumOfAngSegments(CompositeFeaturePtr theSketch);
+
+  /// @returns never nullptr.
+  static std::shared_ptr<ModelAPI_AttributeDouble> sketchPlaneCircularGridOffsetAngle(CompositeFeaturePtr theSketch);
+
+  /// @returns never nullptr.
+  static std::shared_ptr<ModelAPI_AttributeDouble> sketchPlaneCircularGridOffsetX(CompositeFeaturePtr theSketch);
+
+  /// @returns never nullptr.
+  static std::shared_ptr<ModelAPI_AttributeDouble> sketchPlaneCircularGridOffsetY(CompositeFeaturePtr theSketch);
+
+
   /// Create a point 3D on a basis of point 2D and sketch feature
   /// \param thePoint2D a point on a sketch
   /// \param theSketch a sketch feature
index fe01d920f3e367f8e573ca158a407837f4a8c412..192078248bee8140725055456a1fff51a643ed24 100644 (file)
@@ -119,7 +119,7 @@ PartSet_WidgetBSplinePoints::PartSet_WidgetBSplinePoints(QWidget* theParent,
 
   myScrollArea->setWidget(aContainer);
 
-  myWidgetValidator = new ModuleBase_WidgetValidator(this, myWorkshop);
+  myWidgetValidator.reset(new ModuleBase_WidgetValidator(this, myWorkshop));
   myExternalObjectMgr = new PartSet_ExternalObjectsMgr(theData->getProperty("use_external"),
                                          theData->getProperty("can_create_external"), true);
 }
@@ -555,10 +555,19 @@ void PartSet_WidgetBSplinePoints::mouseMoved(ModuleBase_IViewWindow* theWindow,
   if (myFinished || isEditingMode() || aModule->sketchReentranceMgr()->isInternalEditActive())
     return;
 
-  gp_Pnt aPoint = PartSet_Tools::convertClickToPoint(theEvent->pos(), theWindow->v3dView());
+  double aX = 0, aY = 0; // Coords at sketch plane.
+  bool success = convertPointToLocal(
+    myWorkshop,
+    mySketch,
+    theWindow,
+    theEvent->pos(),
+    aX, aY,
+    true
+  );
+
+  if (!success)
+    return;
 
-  double aX = 0, aY = 0;
-  PartSet_Tools::convertTo2D(aPoint, mySketch, theWindow->v3dView(), aX, aY);
   if (myState != ModifiedInViewer)
     storeCurentValue();
   // we need to block the value state change
index bb6ffcfa651ceac3f7aca6fcad5570eab83a4075..2d4622ee6146875eaacbb1ba3da67b38a343dc94 100644 (file)
@@ -37,7 +37,7 @@ PartSet_WidgetPoint2DFlyout::PartSet_WidgetPoint2DFlyout(QWidget* theParent,
                                                          const Config_WidgetAPI* theData)
  : PartSet_WidgetPoint2D(theParent, theWorkshop, theData)
 {
-  myWidgetValidator = new ModuleBase_WidgetValidator(this, myWorkshop);
+  myWidgetValidator.reset(new ModuleBase_WidgetValidator(this, myWorkshop));
 }
 
 bool PartSet_WidgetPoint2DFlyout::setSelection(QList<ModuleBase_ViewerPrsPtr>& theValues,
index e434c3d0f2de57cf0fc9aa2f200992bcad941500..dfaccf2ed32f0379b69a5ea51af50c7136b3af7d 100644 (file)
@@ -73,6 +73,7 @@
 #include <QMouseEvent>
 #include <QApplication>
 
+#include <Aspect_Grid.hxx>
 #include <TopoDS.hxx>
 #include <TopoDS_Vertex.hxx>
 #include <BRep_Tool.hxx>
@@ -126,7 +127,7 @@ PartSet_WidgetPoint2D::PartSet_WidgetPoint2D(QWidget* theParent,
   aLayout->addWidget(myGroupBox);
   setLayout(aLayout);
 
-  myWidgetValidator = new ModuleBase_WidgetValidator(this, myWorkshop);
+  myWidgetValidator.reset(new ModuleBase_WidgetValidator(this, myWorkshop));
   myExternalObjectMgr = new PartSet_ExternalObjectsMgr(theData->getProperty("use_external"),
                                          theData->getProperty("can_create_external"), true);
 }
@@ -548,7 +549,6 @@ void PartSet_WidgetPoint2D::mouseReleased(ModuleBase_IViewWindow* theWindow, QMo
     return;
 
   ModuleBase_ISelection* aSelection = myWorkshop->selection();
-  Handle(V3d_View) aView = theWindow->v3dView();
 
   QList<ModuleBase_ViewerPrsPtr> aList = aSelection->getSelected(ModuleBase_ISelection::Viewer);
   ModuleBase_ViewerPrsPtr aFirstValue =
@@ -563,6 +563,7 @@ void PartSet_WidgetPoint2D::mouseReleased(ModuleBase_IViewWindow* theWindow, QMo
     GeomShapePtr aShape = aFirstValue->shape();
     if (aShape.get() && aShape->shapeType() == GeomAPI_Shape::VERTEX) {
       const TopoDS_Shape& aTDShape = aShape->impl<TopoDS_Shape>();
+      Handle(V3d_View) aView = theWindow->v3dView();
       GeomPnt2dPtr aPnt = PartSet_Tools::getPnt2d(aView, aTDShape, mySketch);
       aX = aPnt->x();
       aY = aPnt->y();
@@ -570,8 +571,9 @@ void PartSet_WidgetPoint2D::mouseReleased(ModuleBase_IViewWindow* theWindow, QMo
     }
   }
   if (!aHasPoint) {
-    gp_Pnt aPoint = PartSet_Tools::convertClickToPoint(theEvent->pos(), aView);
-    PartSet_Tools::convertTo2D(aPoint, mySketch, aView, aX, aY);
+    bool success = convertPointToLocal(myWorkshop, mySketch, theWindow, theEvent->pos(), aX, aY, false);
+    if (!success)
+      return;
   }
   processSelection(aFirstValue, aX, aY);
 }
@@ -643,6 +645,7 @@ void PartSet_WidgetPoint2D::processSelection(const ModuleBase_ViewerPrsPtr& theV
       }
     }
     else {
+
       if (!isFeatureContainsPoint(myFeature, theX, theY)) {
         double aX = 0, aY = 0;
         bool anOrphanPoint = isOrphanPoint(aSelectedFeature, mySketch, aX, aY);
@@ -738,10 +741,11 @@ void PartSet_WidgetPoint2D::mouseMoved(ModuleBase_IViewWindow* theWindow, QMouse
   if (isEditingMode() || aModule->sketchReentranceMgr()->isInternalEditActive())
     return;
 
-  gp_Pnt aPoint = PartSet_Tools::convertClickToPoint(theEvent->pos(), theWindow->v3dView());
+  double aX = 0, aY = 0; // Coords at sketch plane.
+  bool success = convertPointToLocal(myWorkshop, mySketch, theWindow, theEvent->pos(), aX, aY, true, true);
+  if (!success)
+    return;
 
-  double aX = 0, aY = 0;
-  PartSet_Tools::convertTo2D(aPoint, mySketch, theWindow->v3dView(), aX, aY);
   if (myState != ModifiedInViewer)
     storeCurentValue();
   // we need to block the value state change
index 273a29faec25d6be23b68eaf395701e55e364d71..8f664c502709722758b177382356dae27eee7738 100644 (file)
@@ -220,7 +220,7 @@ protected:
 
    /// Create a coincidence constraint between the attribute and the parameter object
    /// \theObject a result object
-   /// \return true if succed
+   /// \return true if succeed
    bool setConstraintToObject(const ObjectPtr& theObject);
 
    /// Process selected value
@@ -229,7 +229,6 @@ protected:
    /// \param theY Y coordinate of clicked point
    void processSelection(const ModuleBase_ViewerPrsPtr& theValue, double theX, double theY);
 
-
 public:
    /// Returns if the feature is an orphan point, circle or an arc. Returns true if it
    /// has no a coincident to other lines. It processes point, circle and arc features
index 2f67e9db686e591018dc55105528755d2d4b68fe..67d156a60a3a399e400e285c398464ae020cc934 100644 (file)
@@ -41,6 +41,7 @@
 #include <ModelAPI_ResultBody.h>
 #include <ModelAPI_AttributeSelection.h>
 #include <ModelAPI_AttributeSelectionList.h>
+#include <ModelAPI_AttributeDouble.h>
 #include <ModelAPI_Validator.h>
 #include <ModelAPI_Events.h>
 #include <ModelAPI_ResultConstruction.h>
@@ -254,10 +255,6 @@ bool PartSet_WidgetSketchCreator::isValidSelectionCustom(const ModuleBase_Viewer
 
 void PartSet_WidgetSketchCreator::activateSelectionControl()
 {
-  // reset previously set size of view needed on restart extrusion after Sketch
-  if (myModule->sketchMgr()->previewSketchPlane()->isUseSizeOfView())
-    myModule->sketchMgr()->previewSketchPlane()->setSizeOfView(0, false);
-
   // we need to call activate here as the widget has no focus accepted controls
   // if these controls are added here, activate will happens automatically after focusIn()
   XGUI_Workshop* aWorkshop = XGUI_Tools::workshop(myModule->workshop());
@@ -431,20 +428,18 @@ bool PartSet_WidgetSketchCreator::startSketchOperation(
   }
   aSketchStarted = true;
   // Set View size if a plane is selected
-  if (myPreviewPlanes->isPreviewDisplayed() &&
-      myPreviewPlanes->isPreviewShape(aValue->shape())) {
+  if (myPreviewPlanes->isPreviewDisplayed() && myPreviewPlanes->isPreviewShape(aValue->shape())) {
     // set default plane size
-    bool isSetSizeOfView = false;
-    double aSizeOfView = 0;
-    QString aSizeOfViewStr = mySizeOfView->text();
-    if (!aSizeOfViewStr.isEmpty()) {
-      aSizeOfView = aSizeOfViewStr.toDouble(&isSetSizeOfView);
-      if (isSetSizeOfView && aSizeOfView <= 0) {
-        isSetSizeOfView = false;
-      }
-    }
-    if (isSetSizeOfView)
-      myModule->sketchMgr()->previewSketchPlane()->setSizeOfView(aSizeOfView, true);
+    bool isValidSizeInput = true;
+    double aSizeOfView = mySizeOfView->text().toDouble(&isValidSizeInput);
+    if (aSizeOfView <= 0 || !isValidSizeInput)
+      aSizeOfView = PartSet_PreviewSketchPlane::defaultSketchSize();
+
+    const auto sketch = myModule->sketchMgr()->activeSketch();
+    if (sketch)
+      PartSet_Tools::sketchPlaneDefaultSize(sketch)->setValue(aSizeOfView);
+
+    myModule->sketchMgr()->previewSketchPlane()->setAllUsingSketch(sketch);
   }
   // manually deactivation because the widget was not activated as has no focus acceptin controls
   deactivate();
index e1f71f19a45e611ae9d80dbc4dcf75531c66cd96..b3806c7d27436f9b7fda586d9a39153d6a49a279 100644 (file)
@@ -37,6 +37,9 @@
 
 #include <ModelAPI_ResultBody.h>
 #include <ModelAPI_Tools.h>
+#include <ModelAPI_AttributeDouble.h>
+#include <ModelAPI_AttributeInteger.h>
+#include <ModelAPI_AttributeBoolean.h>
 #include <ModelAPI_AttributeString.h>
 #include <ModelAPI_Events.h>
 
 #include <QVBoxLayout>
 #include <QHBoxLayout>
 #include <QCheckBox>
+#include <QComboBox>
 #include <QGroupBox>
 #include <QPushButton>
 #include <QLineEdit>
 #include <QDoubleValidator>
+#include <QDoubleSpinBox>
 #include <QDialog>
 #include <QTimer>
+#include <QToolTip>
+#include <QRect>
+
+#include <limits>
+#include <iostream>
+#include <cmath>
+#include <utility>
+
 
 #ifdef WIN32
 #pragma warning(disable : 4456) // for nested foreach
 #define DBL_MAX 1.7976931348623158e+308
 #endif
 
+
 PartSet_WidgetSketchLabel::PartSet_WidgetSketchLabel(QWidget* theParent,
-                        ModuleBase_IWorkshop* theWorkshop,
-                        const Config_WidgetAPI* theData,
-                        const QMap<PartSet_Tools::ConstraintVisibleState, bool>& toShowConstraints)
-: ModuleBase_WidgetValidated(theParent, theWorkshop, theData), myOpenTransaction(false),
-myIsSelection(false)
+  ModuleBase_IWorkshop* theWorkshop,
+  const Config_WidgetAPI* theData,
+  const QMap<PartSet_Tools::ConstraintVisibleState, bool>& toShowConstraints
+) : ModuleBase_WidgetValidated(theParent, theWorkshop, theData),
+  mySketchDataIsModified(false), myOpenTransaction(false), myIsSelection(false)
 {
   QVBoxLayout* aLayout = new QVBoxLayout(this);
   ModuleBase_Tools::zeroMargins(aLayout);
@@ -155,37 +169,104 @@ myIsSelection(false)
 
   myStackWidget->addWidget(aFirstWgt);
 
-  // Define widget for sketch manmagement
+  // Define widget for sketch management
   QWidget* aSecondWgt = new QWidget(this);
   aLayout = new QVBoxLayout(aSecondWgt);
   ModuleBase_Tools::zeroMargins(aLayout);
 
-  QGroupBox* aViewBox = new QGroupBox(tr("Sketcher plane"), this);
-  QGridLayout* aViewLayout = new QGridLayout(aViewBox);
+  { // Sketch view controls.
+    mySketchViewGroupBox = new QGroupBox(tr("Sketcher plane"), this);
+    QGridLayout* aViewLayout = new QGridLayout(mySketchViewGroupBox);
 
-  myViewInverted = new QCheckBox(tr("Reversed"), aViewBox);
-  aViewLayout->addWidget(myViewInverted, 0, 0);
+    PartSet_Module* const aModule = dynamic_cast<PartSet_Module*>(myWorkshop->module());
+    PartSet_PreviewSketchPlane* const aPreviewPlane = aModule->sketchMgr()->previewSketchPlane();
+    const CompositeFeaturePtr sketch = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(myFeature);
 
-  // Sketch plane visibility
-  myViewVisible = new QCheckBox(tr("Visible"), aViewBox);
-  PartSet_Module* aModule = dynamic_cast<PartSet_Module*>(myWorkshop->module());
-  PartSet_PreviewSketchPlane* aPreviewPlane = aModule->sketchMgr()->previewSketchPlane();
-  if (aPreviewPlane->isPlaneCreated())
-    // init with current state
-    myViewVisible->setChecked(aPreviewPlane->isDisplayed());
-  else
-    // true by default (at start of sketch creation)
-    myViewVisible->setChecked(true);
+    { // Reverse flag.
+      myViewInverted = new QCheckBox(tr("Reversed"), mySketchViewGroupBox);
+      aViewLayout->addWidget(myViewInverted, 0, 0);
+    }
+
+    { // Sketch axes visibility.
+      myAxesVisibleCheckBox = new QCheckBox(tr("Axes"), mySketchViewGroupBox);
+      myAxesVisibleCheckBox->setChecked(sketch ? PartSet_Tools::sketchPlaneAxesEnabled(sketch)->value() : false);
+
+      aViewLayout->addWidget(myAxesVisibleCheckBox, 0, 1);
+      connect(myAxesVisibleCheckBox, SIGNAL(toggled(bool)), this, SLOT(onShowAxes(bool)));
+    }
 
-  aViewLayout->addWidget(myViewVisible, 0, 1, Qt::AlignRight);
-  connect(myViewVisible, SIGNAL(toggled(bool)), this, SLOT(onShowViewPlane(bool)));
+    { // Sketch substrate-plane visibility.
+      mySubstrateVisibleCheckBox = new QCheckBox(tr("Substrate"), mySketchViewGroupBox);
+      mySubstrateVisibleCheckBox->setChecked(sketch ? PartSet_Tools::sketchPlaneSubstrateEnabled(sketch)->value() : false);
 
-  QPushButton* aSetViewBtn =
-    new QPushButton(QIcon(":icons/plane_view.png"), tr("Set plane view"), aViewBox);
-  connect(aSetViewBtn, SIGNAL(clicked(bool)), this, SLOT(onSetPlaneView()));
-  aViewLayout->addWidget(aSetViewBtn, 1, 0, 1, 2);
+      aViewLayout->addWidget(mySubstrateVisibleCheckBox, 0, 2);
+      connect(mySubstrateVisibleCheckBox, SIGNAL(toggled(bool)), this, SLOT(onShowSubstrate(bool)));
+    }
+
+    {
+      QPushButton* aSetViewBtn = new QPushButton(QIcon(":icons/plane_view.png"), tr("Set plane view"), mySketchViewGroupBox);
+      connect(aSetViewBtn, SIGNAL(clicked(bool)), this, SLOT(onSetPlaneView()));
+      aViewLayout->addWidget(aSetViewBtn, 1, 0, 1, 3);
+    }
+
+    { // Sketch construction grid.
+      QGroupBox* aCGBox = new QGroupBox(tr("Construction grid"), this);
+      QGridLayout* aCGLayout = new QGridLayout(aCGBox);
+
+      {
+        myGridTypeComboBox = new QComboBox(aCGBox);
+        myGridTypeComboBox->addItem(tr("Disabled"),    PartSet_Tools::SketchPlaneGridType::No);
+        myGridTypeComboBox->addItem(tr("Rectangular"), PartSet_Tools::SketchPlaneGridType::Rectangular);
+        myGridTypeComboBox->addItem(tr("Circular"),    PartSet_Tools::SketchPlaneGridType::Circular);
+        myGridTypeComboBox->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+        myGridTypeComboBox->setCurrentIndex(0);
+        if (sketch) {
+          const int idx = myGridTypeComboBox->findData(PartSet_Tools::getSketchPlaneGridType(sketch));
+          if (idx != -1)
+            myGridTypeComboBox->setCurrentIndex(idx);
+        }
+
+        aCGLayout->addWidget(myGridTypeComboBox, 0, 0);
+        connect(myGridTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onGridTypeChanged(int)));
+      }
+
+      {
+        myGridSnappingModeComboBox = new QComboBox(aCGBox);
+        myGridSnappingModeComboBox->addItem(tr("Don't snap"), PartSet_PreviewSketchPlane::GridSnappingMode::Off);
+        myGridSnappingModeComboBox->addItem(tr("Snap anyway"), PartSet_PreviewSketchPlane::GridSnappingMode::SnapAnyway);
+        myGridSnappingModeComboBox->addItem(tr("Snap in proximity"), PartSet_PreviewSketchPlane::GridSnappingMode::SnapInProximity);
+        myGridSnappingModeComboBox->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+        myGridSnappingModeComboBox->setCurrentIndex(int(aPreviewPlane->getGridSnappingMode()));
+
+        aCGLayout->addWidget(myGridSnappingModeComboBox, 0, 1);
+        connect(myGridSnappingModeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onGridSnappingModeChanged(int)));
+      }
+
+      {
+        const auto widgetGrouper = new QWidget(aCGBox);
+        aCGLayout->addWidget(widgetGrouper, 1, 0, 1, 2);
+        const auto grouperLayout = new QVBoxLayout(widgetGrouper);
+
+        const auto currentGridType = myGridTypeComboBox->currentData();
+
+        myWidgetRectangularGrid = new PartSet_WidgetSketchRectangularGrid(aCGBox, this);
+        grouperLayout->addWidget(myWidgetRectangularGrid);
+        myWidgetRectangularGrid->setVisible(currentGridType == PartSet_Tools::SketchPlaneGridType::Rectangular);
+
+        myWidgetCircularGrid = new PartSet_WidgetSketchCircularGrid(aCGBox, this);
+        grouperLayout->addWidget(myWidgetCircularGrid);
+        myWidgetCircularGrid->setVisible(currentGridType == PartSet_Tools::SketchPlaneGridType::Circular);
+      }
+
+      aViewLayout->addWidget(aCGBox, 2, 0, 1, 3);
+    } // Sketch construction grid.
+
+    aLayout->addWidget(mySketchViewGroupBox);
+
+    aPreviewPlane->setAllUsingSketch(sketch);
+    reconfigureSketchViewWidgets();
+  } // View box.
 
-  aLayout->addWidget(aViewBox);
 
   QMap<PartSet_Tools::ConstraintVisibleState, QString> aStates;
   aStates[PartSet_Tools::Geometrical] = tr("Show geometrical constraints");
@@ -234,8 +315,16 @@ myIsSelection(false)
   myPreviewPlanes = new PartSet_PreviewPlanes();
 }
 
-PartSet_WidgetSketchLabel::~PartSet_WidgetSketchLabel()
-{
+void PartSet_WidgetSketchLabel::setFeature(
+  const FeaturePtr& theFeature,
+  const bool theToStoreValue,
+  const bool isUpdateFlushed
+) {
+  ModuleBase_WidgetValidated::setFeature(theFeature, theToStoreValue, isUpdateFlushed);
+  PartSet_Module* aModule = dynamic_cast<PartSet_Module*>(myWorkshop->module());
+  const CompositeFeaturePtr sketch = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(myFeature);
+  aModule->sketchMgr()->previewSketchPlane()->setAllUsingSketch(sketch);
+  reconfigureSketchViewWidgets();
 }
 
 bool PartSet_WidgetSketchLabel::setSelection(QList<ModuleBase_ViewerPrsPtr>& theValues,
@@ -363,24 +452,19 @@ void PartSet_WidgetSketchLabel::updateByPlaneSelected(const ModuleBase_ViewerPrs
   // 1. hide main planes if they have been displayed and display sketch preview plane
   myPreviewPlanes->erasePreviewPlanes(myWorkshop);
 
-  QString aSizeOfViewStr = mySizeOfView->text();
-  bool isSetSizeOfView = false;
-  double aSizeOfView = 0;
-  if (!aSizeOfViewStr.isEmpty()) {
-    aSizeOfView = aSizeOfViewStr.toDouble(&isSetSizeOfView);
-    if (isSetSizeOfView && aSizeOfView <= 0) {
-      isSetSizeOfView = false;
-    }
-  }
+  bool isValidSizeInput = true;
+  double aSizeOfView = mySizeOfView->text().toDouble(&isValidSizeInput);
+  if (aSizeOfView <= 0 || !isValidSizeInput)
+    aSizeOfView = PartSet_PreviewSketchPlane::defaultSketchSize();
+
   PartSet_Module* aModule = dynamic_cast<PartSet_Module*>(myWorkshop->module());
   if (aModule) {
-    CompositeFeaturePtr aSketch = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(myFeature);
-    aModule->sketchMgr()->previewSketchPlane()->setSizeOfView(aSizeOfView, isSetSizeOfView);
-    if (myViewVisible->isChecked())
-      aModule->sketchMgr()->previewSketchPlane()->createSketchPlane(aSketch, myWorkshop);
-    else
-      aModule->sketchMgr()->previewSketchPlane()->clearPlanePreview();
+    CompositeFeaturePtr sketch = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(myFeature);
+    PartSet_Tools::sketchPlaneDefaultSize(sketch)->setValue(aSizeOfView);
+    aModule->sketchMgr()->previewSketchPlane()->setAllUsingSketch(sketch);
   }
+  reconfigureSketchViewWidgets();
+
   // 2. if the planes were displayed, change the view projection
 
   std::shared_ptr<GeomAPI_Dir> aDir = aPlane->direction();
@@ -393,15 +477,15 @@ void PartSet_WidgetSketchLabel::updateByPlaneSelected(const ModuleBase_ViewerPrs
   if (aRotate) {
     myWorkshop->viewer()->setViewProjection(aXYZ.X(), aXYZ.Y(), aXYZ.Z(), aTwist);
   }
-  if (isSetSizeOfView && aSizeOfView > 0) {
-    Handle(V3d_View) aView3d = myWorkshop->viewer()->activeView();
-    if (!aView3d.IsNull()) {
-      Bnd_Box aBndBox;
-      double aHalfSize = aSizeOfView/2.0;
-      aBndBox.Update(-aHalfSize, -aHalfSize, -aHalfSize, aHalfSize, aHalfSize, aHalfSize);
-      aView3d->FitAll(aBndBox, 0.01, false);
-    }
+
+  Handle(V3d_View) aView3d = myWorkshop->viewer()->activeView();
+  if (!aView3d.IsNull()) {
+    Bnd_Box aBndBox;
+    double aHalfSize = aSizeOfView/2.0;
+    aBndBox.Update(-aHalfSize, -aHalfSize, -aHalfSize, aHalfSize, aHalfSize, aHalfSize);
+    aView3d->FitAll(aBndBox, 0.01, false);
   }
+
   if (myOpenTransaction) {
     SessionPtr aMgr = ModelAPI_Session::get();
     aMgr->finishOperation();
@@ -621,6 +705,23 @@ void PartSet_WidgetSketchLabel::hideEvent(QHideEvent* theEvent)
 }
 
 
+void PartSet_WidgetSketchLabel::onShowDOF()
+{
+  CompositeFeaturePtr aCompFeature =
+    std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(myFeature);
+  if (aCompFeature.get()) {
+    static const Events_ID anEvent = Events_Loop::eventByName(EVENT_GET_DOF_OBJECTS);
+    ModelAPI_EventCreator::get()->sendUpdated(aCompFeature, anEvent);
+    Events_Loop::loop()->flush(anEvent);
+
+    // Transfer focus to the current viewport for correct processing of a key event
+    QWidget* aViewPort = myWorkshop->viewer()->activeViewPort();
+    if (aViewPort)
+      aViewPort->setFocus();
+  }
+}
+
+
 void PartSet_WidgetSketchLabel::onShowPanel()
 {
   //if (mySizeOfViewWidget->isVisible()) {
@@ -639,6 +740,112 @@ void PartSet_WidgetSketchLabel::onShowPanel()
   }
 }
 
+void PartSet_WidgetSketchLabel::onShowAxes(bool toShow)
+{
+  const auto sketch = std::static_pointer_cast<ModelAPI_CompositeFeature>(feature());
+  if (sketch) {
+    PartSet_Tools::sketchPlaneAxesEnabled(sketch)->setValue(toShow);
+    mySketchDataIsModified = true;
+  }
+
+  const auto module = dynamic_cast<PartSet_Module*>(myWorkshop->module());
+  PartSet_PreviewSketchPlane* const previewPlane = module->sketchMgr()->previewSketchPlane();
+  previewPlane->showAxes(toShow);
+  myWorkshop->viewer()->update();
+}
+
+void PartSet_WidgetSketchLabel::onShowSubstrate(bool toShow)
+{
+  const auto sketch = std::static_pointer_cast<ModelAPI_CompositeFeature>(feature());
+  if (sketch) {
+    PartSet_Tools::sketchPlaneSubstrateEnabled(sketch)->setValue(toShow);
+    mySketchDataIsModified = true;
+  }
+
+  const auto module = dynamic_cast<PartSet_Module*>(myWorkshop->module());
+  PartSet_PreviewSketchPlane* const previewPlane = module->sketchMgr()->previewSketchPlane();
+  previewPlane->showSubstrate(toShow);
+  myWorkshop->viewer()->update();
+}
+
+void PartSet_WidgetSketchLabel::onGridTypeChanged(int theComboBoxIdx) {
+  (void)theComboBoxIdx;
+  const auto module = dynamic_cast<PartSet_Module*>(myWorkshop->module());
+  const auto previewPlane = module->sketchMgr()->previewSketchPlane();
+  const auto gridType = PartSet_Tools::SketchPlaneGridType::Enum(myGridTypeComboBox->currentData().toInt());
+
+  const auto sketch = std::static_pointer_cast<ModelAPI_CompositeFeature>(feature());
+  if (sketch) {
+    PartSet_Tools::setSketchPlaneGridType(sketch, gridType);
+    mySketchDataIsModified = true;
+  }
+
+  previewPlane->setGridType(gridType);
+  myGridSnappingModeComboBox->setEnabled(gridType != PartSet_Tools::SketchPlaneGridType::No);
+  myWidgetRectangularGrid->setVisible(gridType == PartSet_Tools::SketchPlaneGridType::Rectangular);
+  myWidgetCircularGrid->setVisible(gridType == PartSet_Tools::SketchPlaneGridType::Circular);
+  myWorkshop->viewer()->update();
+}
+
+void PartSet_WidgetSketchLabel::onGridSnappingModeChanged(int theModeIdx) {
+  const auto module = dynamic_cast<PartSet_Module*>(myWorkshop->module());
+  PartSet_PreviewSketchPlane* const previewPlane = module->sketchMgr()->previewSketchPlane();
+  previewPlane->setGridSnappingMode(static_cast<PartSet_PreviewSketchPlane::GridSnappingMode>(theModeIdx));
+}
+
+void PartSet_WidgetSketchLabel::reconfigureSketchViewWidgets()
+{
+  const auto module = dynamic_cast<PartSet_Module*>(myWorkshop->module());
+  if (module) {
+    const PartSet_PreviewSketchPlane* const previewPlane = module->sketchMgr()->previewSketchPlane();
+
+    myAxesVisibleCheckBox->setChecked(previewPlane->isShowAxes());
+    mySubstrateVisibleCheckBox->setChecked(previewPlane->isShowSubstrate());
+
+    const auto gridType = previewPlane->getGridType();
+    const int gridTypeIdx = myGridTypeComboBox->findData(gridType);
+    myGridTypeComboBox->setCurrentIndex(gridTypeIdx != -1 ? gridTypeIdx : 0);
+    myWidgetRectangularGrid->setVisible(gridType == PartSet_Tools::SketchPlaneGridType::Rectangular);
+    myWidgetRectangularGrid->recongifure();
+    myWidgetCircularGrid->setVisible(gridType == PartSet_Tools::SketchPlaneGridType::Circular);
+    myWidgetCircularGrid->recongifure();
+
+    myGridSnappingModeComboBox->setEnabled(gridType != PartSet_Tools::SketchPlaneGridType::No);
+    myGridSnappingModeComboBox->setCurrentIndex(int(previewPlane->getGridSnappingMode()));
+
+    mySketchViewGroupBox->setEnabled(true);
+  }
+  else {
+    mySketchViewGroupBox->setEnabled(false);
+
+    myAxesVisibleCheckBox->setChecked(false);
+    mySubstrateVisibleCheckBox->setChecked(false);
+
+    myGridTypeComboBox->setCurrentIndex(0);
+    myWidgetRectangularGrid->setVisible(false);
+    myWidgetRectangularGrid->recongifure();
+    myWidgetCircularGrid->setVisible(false);
+    myWidgetCircularGrid->recongifure();
+
+    myGridSnappingModeComboBox->setEnabled(false);
+  }
+
+  mySketchDataIsModified = false;
+  myWorkshop->viewer()->update();
+}
+
+void PartSet_WidgetSketchLabel::saveSketchViewPreferenceToSkethData()
+{
+  const auto module = dynamic_cast<PartSet_Module*>(myWorkshop->module());
+  if (!module)
+    return;
+
+  const PartSet_PreviewSketchPlane* const previewPlane = module->sketchMgr()->previewSketchPlane();
+  const auto sketch = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(myFeature);
+  previewPlane->savePreferencesIntoSketchData(sketch);
+  mySketchDataIsModified = false;
+}
+
 void PartSet_WidgetSketchLabel::deactivate()
 {
   QWidget* aTopWidget = window();
@@ -750,7 +957,6 @@ void PartSet_WidgetSketchLabel::onSetPlaneView()
   }
 }
 
-
 //******************************************************
 QList<ModuleBase_ViewerPrsPtr> PartSet_WidgetSketchLabel::findCircularEdgesInPlane()
 {
@@ -837,6 +1043,14 @@ void PartSet_WidgetSketchLabel::setShowPointsState(bool theState)
   myShowPoints->blockSignals(aBlock);
 }
 
+bool PartSet_WidgetSketchLabel::storeValueCustom()
+{
+  if (mySketchDataIsModified)
+    saveSketchViewPreferenceToSkethData();
+
+  return true;
+}
+
 bool PartSet_WidgetSketchLabel::restoreValueCustom()
 {
   if (myFeature.get()) {
@@ -858,6 +1072,12 @@ bool PartSet_WidgetSketchLabel::restoreValueCustom()
           myShowDOFBtn->setEnabled(true);
         }
       }
+
+      PartSet_Module* aModule = dynamic_cast<PartSet_Module*>(myWorkshop->module());
+      if (aModule)
+        aModule->sketchMgr()->previewSketchPlane()->setAllUsingSketch(aSketch);
+
+      reconfigureSketchViewWidgets();
     }
     else {
       myDoFLabel->setText("");
@@ -867,23 +1087,6 @@ bool PartSet_WidgetSketchLabel::restoreValueCustom()
   return true;
 }
 
-
-void PartSet_WidgetSketchLabel::onShowDOF()
-{
-  CompositeFeaturePtr aCompFeature =
-    std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(myFeature);
-  if (aCompFeature.get()) {
-    static const Events_ID anEvent = Events_Loop::eventByName(EVENT_GET_DOF_OBJECTS);
-    ModelAPI_EventCreator::get()->sendUpdated(aCompFeature, anEvent);
-    Events_Loop::loop()->flush(anEvent);
-
-    // Transfer focus to the current viewport for correct processing of a key event
-    QWidget* aViewPort = myWorkshop->viewer()->activeViewPort();
-    if (aViewPort)
-      aViewPort->setFocus();
-  }
-}
-
 bool PartSet_WidgetSketchLabel::eventFilter(QObject* theObj, QEvent* theEvent)
 {
   if (theObj == window()) {
@@ -900,19 +1103,453 @@ bool PartSet_WidgetSketchLabel::eventFilter(QObject* theObj, QEvent* theEvent)
   return ModuleBase_WidgetValidated::eventFilter(theObj, theEvent);
 }
 
-void PartSet_WidgetSketchLabel::onShowViewPlane(bool toShow)
+
+PitchSpinBox::PitchSpinBox(QWidget* theParent) : QDoubleSpinBox(theParent)
 {
-  PartSet_Module* aModule = dynamic_cast<PartSet_Module*>(myWorkshop->module());
-  PartSet_PreviewSketchPlane* aPreviewPlane = aModule->sketchMgr()->previewSketchPlane();
-  if (toShow) {
-    CompositeFeaturePtr aSketch = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(myFeature);
-    if (aPreviewPlane->isPlaneCreated())
-      aPreviewPlane->displaySketchPlane(myWorkshop);
-    else
-      aPreviewPlane->createSketchPlane(aSketch, myWorkshop);
+  setRange(std::pow(10, -PartSet_WidgetSketchGrid::NUM_OF_DECIMAL_DIGITS_TRANS), std::numeric_limits<double>::max());
+  setDecimals(PartSet_WidgetSketchGrid::NUM_OF_DECIMAL_DIGITS_TRANS);
+  setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+  setMinimumWidth(PartSet_WidgetSketchGrid::SPIN_BOX_MIN_WIDTH);
+  myPrevVal = 1;
+
+  connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(onTextChanged()));
+  connect(this, SIGNAL(editingFinished()), this, SLOT(onEditingFinished()));
+}
+
+void PitchSpinBox::setValue(double theVal)
+{
+  myPrevVal = theVal;
+  QDoubleSpinBox::setValue(theVal);
+}
+
+void PitchSpinBox::onTextChanged()
+{
+  if (value() <= Precision::Confusion())
+    return;
+
+  setSingleStep(PartSet_WidgetSketchGrid::reasonablePitchIncrement(value()));
+  emit valueSet(value());
+}
+
+void PitchSpinBox::onEditingFinished()
+{
+  if (value() <= Precision::Confusion()) {
+    QDoubleSpinBox::setValue(myPrevVal);
   }
   else {
-    aPreviewPlane->eraseSketchPlane(myWorkshop, false);
+    myPrevVal = value();
+    emit valueSet(value());
   }
-  myWorkshop->viewer()->update();
 }
+
+
+PartSet_WidgetSketchGrid::PartSet_WidgetSketchGrid(QWidget* theParent, PartSet_WidgetSketchLabel* theSketchLabel)
+: QWidget(theParent), mySketchLabel(theSketchLabel), myPreviewPlane(nullptr)
+{
+  static const double MAX_DOUBLE = std::numeric_limits<double>::max();
+  static const double MIN_DOUBLE = std::numeric_limits<double>::lowest();
+
+  myResetButton = new QPushButton(tr("Reset"), this);
+  myResetButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+  myResetButton->setToolTip(tr("Set default pitches and zero offsets."));
+
+  const auto offsetAngleLabel = new QLabel(this);
+  offsetAngleLabel->setText(tr("Offset angle,°"));
+
+  myOffsetAngleSpinBox = new QDoubleSpinBox(this);
+  myOffsetAngleSpinBox->setRange(-180, 180);
+  myOffsetAngleSpinBox->setDecimals(PartSet_WidgetSketchGrid::NUM_OF_DECIMAL_DIGITS_ROTAT);
+  myOffsetAngleSpinBox->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+  myOffsetAngleSpinBox->setMinimumWidth(PartSet_WidgetSketchGrid::SPIN_BOX_MIN_WIDTH);
+
+  const auto offsetXLabel = new QLabel(this);
+  offsetXLabel->setText(tr("Offset") + " X'");
+
+  myOffsetXSpinBox = new QDoubleSpinBox(this);
+  myOffsetXSpinBox->setRange(MIN_DOUBLE, MAX_DOUBLE);
+  myOffsetXSpinBox->setDecimals(PartSet_WidgetSketchGrid::NUM_OF_DECIMAL_DIGITS_TRANS);
+  myOffsetXSpinBox->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+  myOffsetXSpinBox->setMinimumWidth(PartSet_WidgetSketchGrid::SPIN_BOX_MIN_WIDTH);
+
+  const auto offsetYLabel = new QLabel(this);
+  offsetYLabel->setText(tr("Offset")  + " Y'");
+
+  myOffsetYSpinBox = new QDoubleSpinBox(this);
+  myOffsetYSpinBox->setRange(MIN_DOUBLE, MAX_DOUBLE);
+  myOffsetYSpinBox->setDecimals(PartSet_WidgetSketchGrid::NUM_OF_DECIMAL_DIGITS_TRANS);
+  myOffsetYSpinBox->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+  myOffsetYSpinBox->setMinimumWidth(PartSet_WidgetSketchGrid::SPIN_BOX_MIN_WIDTH);
+
+  myLayout = new QGridLayout(this);
+  myLayout->addWidget(myResetButton , 2, 0);
+  myLayout->addWidget(offsetAngleLabel, 3, 0);
+  myLayout->addWidget(offsetXLabel    , 3, 1);
+  myLayout->addWidget(offsetYLabel    , 3, 2);
+  myLayout->addWidget(myOffsetAngleSpinBox, 4, 0);
+  myLayout->addWidget(myOffsetXSpinBox    , 4, 1);
+  myLayout->addWidget(myOffsetYSpinBox    , 4, 2);
+
+  connect(myResetButton, SIGNAL(clicked(bool)), this, SLOT(onResetClicked()));
+  connect(myOffsetAngleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(onOffsetAngleChanged(double)));
+  connect(myOffsetXSpinBox, SIGNAL(valueChanged(double)), this, SLOT(onOffsetXChanged(double)));
+  connect(myOffsetYSpinBox, SIGNAL(valueChanged(double)), this, SLOT(onOffsetYChanged(double)));
+
+  setEnabled(false);
+}
+
+/*static*/ double PartSet_WidgetSketchGrid::clampValue(double theValue, double theIntervalWidth)
+{
+  theValue = std::remainder(theValue, theIntervalWidth);
+  if (theValue > theIntervalWidth/2)
+    theValue = theValue - theIntervalWidth;
+  else if (theValue <= -theIntervalWidth/2)
+    theValue = theIntervalWidth + theValue;
+
+  return theValue;
+}
+
+/*static*/ double PartSet_WidgetSketchGrid::reasonableOffsetIncrement(double theStep)
+{
+  const double stepTenth = theStep / 10;
+  if (std::abs(stepTenth) < Precision::Confusion())
+    return 0;
+
+  return std::pow(10, std::floor(std::log10(stepTenth)));
+}
+
+/*static*/ double PartSet_WidgetSketchGrid::reasonablePitchIncrement(double theStep)
+{
+  return PartSet_WidgetSketchGrid::reasonableOffsetIncrement(theStep);
+}
+
+void PartSet_WidgetSketchGrid::retrieveSketchAndPlane()
+{
+  const auto module = dynamic_cast<PartSet_Module*>(mySketchLabel->myWorkshop->module());
+  if (!module)
+    myPreviewPlane = nullptr;
+  else
+    myPreviewPlane = module->sketchMgr()->previewSketchPlane();
+}
+
+/*static*/ const int PartSet_WidgetSketchGrid::NUM_OF_DECIMAL_DIGITS_TRANS = 3;
+/*static*/ const int PartSet_WidgetSketchGrid::NUM_OF_DECIMAL_DIGITS_ROTAT = 4; // Fits angular second.
+/*static*/ const int PartSet_WidgetSketchGrid::SPIN_BOX_MIN_WIDTH = 80;
+
+
+PartSet_WidgetSketchRectangularGrid::PartSet_WidgetSketchRectangularGrid(QWidget* theParent, PartSet_WidgetSketchLabel* theSketchLabel)
+: PartSet_WidgetSketchGrid(theParent, theSketchLabel)
+{
+  static const double MAX_DOUBLE = std::numeric_limits<double>::max();
+  static const double MIN_DOUBLE = std::numeric_limits<double>::min();
+
+  const auto stepXLabel = new QLabel(this);
+  stepXLabel->setText(tr("Pitch") + " X'");
+
+  myStepXSpinBox = new PitchSpinBox(this);
+
+  const auto stepYLabel = new QLabel(this);
+  stepYLabel->setText(tr("Pitch") + " Y'");
+
+  myStepYSpinBox = new PitchSpinBox(this);
+
+  myLayout->addWidget(stepXLabel    , 1, 1);
+  myLayout->addWidget(stepYLabel    , 1, 2);
+  myLayout->addWidget(myStepXSpinBox, 2, 1);
+  myLayout->addWidget(myStepYSpinBox, 2, 2);
+
+  connect(myStepXSpinBox, SIGNAL(valueSet(double)), this, SLOT(onStepXSet(double)));
+  connect(myStepYSpinBox, SIGNAL(valueSet(double)), this, SLOT(onStepYSet(double)));
+}
+
+void PartSet_WidgetSketchRectangularGrid::recongifure()
+{
+  retrieveSketchAndPlane();
+
+  if (myPreviewPlane) {
+    const auto steps = myPreviewPlane->getRectangularGridSteps();
+    myStepXSpinBox->setValue(steps.first);
+    myStepYSpinBox->setValue(steps.second);
+
+    const auto offsets = myPreviewPlane->getRectangularGridOffsets();
+    myOffsetAngleSpinBox->setValue(PartSet_WidgetSketchGrid::clampValue(offsets.second, 360));
+    myOffsetXSpinBox->setValue(offsets.first.first);
+    myOffsetYSpinBox->setValue(offsets.first.second);
+
+    setEnabled(true);
+  }
+  else {
+    setEnabled(false);
+
+    myStepXSpinBox->setValue(0);
+    myStepYSpinBox->setValue(0);
+    myOffsetAngleSpinBox->setValue(0);
+    myOffsetXSpinBox->setValue(0);
+    myOffsetYSpinBox->setValue(0);
+  }
+}
+
+void PartSet_WidgetSketchRectangularGrid::onStepXSet(double theStep)
+{
+  myOffsetXSpinBox->setSingleStep(PartSet_WidgetSketchGrid::reasonableOffsetIncrement(theStep));
+
+  const auto sketch = std::static_pointer_cast<ModelAPI_CompositeFeature>(mySketchLabel->myFeature);
+  if (sketch) {
+    PartSet_Tools::sketchPlaneRectangularGridStepX(sketch)->setValue(theStep);
+    mySketchLabel->mySketchDataIsModified = true;
+  }
+
+  if (myPreviewPlane) {
+    myPreviewPlane->setRectangularGridStepX(theStep);
+    myPreviewPlane->reconfigureGrid();
+    mySketchLabel->myWorkshop->viewer()->update();
+  }
+}
+
+void PartSet_WidgetSketchRectangularGrid::onStepYSet(double theStep)
+{
+  myOffsetYSpinBox->setSingleStep(PartSet_WidgetSketchGrid::reasonableOffsetIncrement(theStep));
+
+  const auto sketch = std::static_pointer_cast<ModelAPI_CompositeFeature>(mySketchLabel->myFeature);
+  if (sketch) {
+    PartSet_Tools::sketchPlaneRectangularGridStepY(sketch)->setValue(theStep);
+    mySketchLabel->mySketchDataIsModified = true;
+  }
+
+  if (myPreviewPlane) {
+    myPreviewPlane->setRectangularGridStepY(theStep);
+    myPreviewPlane->reconfigureGrid();
+    mySketchLabel->myWorkshop->viewer()->update();
+  }
+}
+
+void PartSet_WidgetSketchRectangularGrid::onResetClicked()
+{
+  if (!myPreviewPlane)
+    return;
+
+  myPreviewPlane->resetRectangularGrid();
+  recongifure();
+
+  const auto sketch = std::static_pointer_cast<ModelAPI_CompositeFeature>(mySketchLabel->feature());
+  if (sketch) {
+    myPreviewPlane->saveRectangularGridPreferencesIntoSketchData(sketch);
+    mySketchLabel->mySketchDataIsModified = true;
+  }
+
+  mySketchLabel->myWorkshop->viewer()->update();
+}
+
+void PartSet_WidgetSketchRectangularGrid::onOffsetAngleChanged(double theOffset)
+{
+  const auto sketch = std::static_pointer_cast<ModelAPI_CompositeFeature>(mySketchLabel->feature());
+  if (sketch) {
+    PartSet_Tools::sketchPlaneRectangularGridOffsetAngle(sketch)->setValue(theOffset);
+    mySketchLabel->mySketchDataIsModified = true;
+  }
+
+  if (myPreviewPlane) {
+    myPreviewPlane->setRectangularGridOffsetA(theOffset);
+    myPreviewPlane->reconfigureGrid();
+    mySketchLabel->myWorkshop->viewer()->update();
+  }
+}
+
+void PartSet_WidgetSketchRectangularGrid::onOffsetXChanged(double theOffset)
+{
+  const auto sketch = std::static_pointer_cast<ModelAPI_CompositeFeature>(mySketchLabel->feature());
+  if (sketch) {
+    PartSet_Tools::sketchPlaneRectangularGridOffsetX(sketch)->setValue(theOffset);
+    mySketchLabel->mySketchDataIsModified = true;
+  }
+
+  if (myPreviewPlane) {
+    myPreviewPlane->setRectangularGridOffsetX(theOffset);
+    myPreviewPlane->reconfigureGrid();
+    mySketchLabel->myWorkshop->viewer()->update();
+  }
+}
+
+void PartSet_WidgetSketchRectangularGrid::onOffsetYChanged(double theOffset)
+{
+  const auto sketch = std::static_pointer_cast<ModelAPI_CompositeFeature>(mySketchLabel->feature());
+  if (sketch) {
+    PartSet_Tools::sketchPlaneRectangularGridOffsetY(sketch)->setValue(theOffset);
+    mySketchLabel->mySketchDataIsModified = true;
+  }
+
+  if (myPreviewPlane) {
+    myPreviewPlane->setRectangularGridOffsetY(theOffset);
+    myPreviewPlane->reconfigureGrid();
+    mySketchLabel->myWorkshop->viewer()->update();
+  }
+}
+
+
+PartSet_WidgetSketchCircularGrid::PartSet_WidgetSketchCircularGrid(QWidget* theParent, PartSet_WidgetSketchLabel* theSketchLabel)
+: PartSet_WidgetSketchGrid(theParent, theSketchLabel)
+{
+  static const double MAX_DOUBLE = std::numeric_limits<double>::max();
+  static const double MIN_DOUBLE = std::numeric_limits<double>::min();
+
+  const auto stepRLabel = new QLabel(this);
+  stepRLabel->setText(tr("Pitch") + " R");
+
+  myStepRSpinBox = new PitchSpinBox(this);
+
+  const auto aNASLabel = new QLabel(this);
+  aNASLabel->setText(tr("Num of angular segments"));
+
+  myNASSpinBox = new QSpinBox(this);
+  myNASSpinBox->setRange(2, std::numeric_limits<int>::max());
+  myNASSpinBox->setSingleStep(2);
+  myNASSpinBox->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+  myNASSpinBox->setToolTip(tr("Even numbers only."));
+  myNASSpinBox->setMinimumWidth(PartSet_WidgetSketchGrid::SPIN_BOX_MIN_WIDTH);
+
+  myLayout->addWidget(stepRLabel    , 1, 1);
+  myLayout->addWidget(aNASLabel     , 1, 2);
+  myLayout->addWidget(myStepRSpinBox, 2, 1);
+  myLayout->addWidget(myNASSpinBox  , 2, 2);
+
+  connect(myStepRSpinBox, SIGNAL(valueSet(double)), this, SLOT(onStepRChanged(double)));
+  connect(myNASSpinBox, SIGNAL(valueChanged(int)), this, SLOT(onNumOfAngularSegmentsChanged(int)));
+}
+
+void PartSet_WidgetSketchCircularGrid::recongifure()
+{
+  retrieveSketchAndPlane();
+
+  if (myPreviewPlane) {
+    const auto stepAndNum = myPreviewPlane->getCircularGrid_dR_and_NAS();
+    myStepRSpinBox->setValue(stepAndNum.first);
+    myNASSpinBox->setValue(stepAndNum.second);
+    updateSegmentsToolTip();
+
+    const auto offsets = myPreviewPlane->getCircularGridOffsets();
+    myOffsetAngleSpinBox->setValue(PartSet_WidgetSketchGrid::clampValue(offsets.second, 360));
+    myOffsetXSpinBox->setValue(offsets.first.first);
+    myOffsetYSpinBox->setValue(offsets.first.second);
+
+    setEnabled(true);
+  }
+  else {
+    setEnabled(false);
+
+    myStepRSpinBox->setValue(0);
+    myNASSpinBox->setValue(1);
+    updateSegmentsToolTip();
+    myOffsetAngleSpinBox->setValue(0);
+    myOffsetXSpinBox->setValue(0);
+    myOffsetYSpinBox->setValue(0);
+  }
+}
+
+void PartSet_WidgetSketchCircularGrid::onStepRChanged(double theStep)
+{
+  const double increment = PartSet_WidgetSketchGrid::reasonableOffsetIncrement(theStep);
+  myOffsetXSpinBox->setSingleStep(increment);
+  myOffsetYSpinBox->setSingleStep(increment);
+
+  const auto sketch = std::static_pointer_cast<ModelAPI_CompositeFeature>(mySketchLabel->myFeature);
+  if (sketch) {
+    PartSet_Tools::sketchPlaneCircularGridStepR(sketch)->setValue(theStep);
+    mySketchLabel->mySketchDataIsModified = true;
+  }
+
+  if (myPreviewPlane) {
+    myPreviewPlane->setCircularGridRadialStep(theStep);
+    myPreviewPlane->reconfigureGrid();
+    mySketchLabel->myWorkshop->viewer()->update();
+  }
+}
+
+void PartSet_WidgetSketchCircularGrid::onNumOfAngularSegmentsChanged(int theNum)
+{
+  theNum = theNum + theNum % 2;
+  myNASSpinBox->setValue(theNum);
+  updateSegmentsToolTip();
+
+  const double increment = PartSet_WidgetSketchGrid::reasonableOffsetIncrement(double(360)/theNum);
+  myOffsetAngleSpinBox->setSingleStep(increment);
+
+  const auto sketch = std::static_pointer_cast<ModelAPI_CompositeFeature>(mySketchLabel->myFeature);
+  if (sketch) {
+    PartSet_Tools::sketchPlaneCircularGridNumOfAngSegments(sketch)->setValue(theNum);
+    mySketchLabel->mySketchDataIsModified = true;
+  }
+
+  if (myPreviewPlane) {
+    myPreviewPlane->setCircularGridNumOfAngularSegments(theNum);
+    myPreviewPlane->reconfigureGrid();
+    mySketchLabel->myWorkshop->viewer()->update();
+  }
+}
+
+void PartSet_WidgetSketchCircularGrid::onResetClicked()
+{
+  if (!myPreviewPlane)
+    return;
+
+  myPreviewPlane->resetCircularGrid();
+  recongifure();
+
+  const auto sketch = std::static_pointer_cast<ModelAPI_CompositeFeature>(mySketchLabel->feature());
+  if (sketch) {
+    myPreviewPlane->saveCircularGridPreferencesIntoSketchData(sketch);
+    mySketchLabel->mySketchDataIsModified = true;
+  }
+
+  mySketchLabel->myWorkshop->viewer()->update();
+}
+
+void PartSet_WidgetSketchCircularGrid::onOffsetAngleChanged(double theOffset)
+{
+  const auto sketch = std::static_pointer_cast<ModelAPI_CompositeFeature>(mySketchLabel->feature());
+  if (sketch) {
+    PartSet_Tools::sketchPlaneCircularGridOffsetAngle(sketch)->setValue(theOffset);
+    mySketchLabel->mySketchDataIsModified = true;
+  }
+
+  if (myPreviewPlane) {
+    myPreviewPlane->setCircularGridOffsetA(theOffset);
+    myPreviewPlane->reconfigureGrid();
+    mySketchLabel->myWorkshop->viewer()->update();
+  }
+}
+
+void PartSet_WidgetSketchCircularGrid::onOffsetXChanged(double theOffset)
+{
+  const auto sketch = std::static_pointer_cast<ModelAPI_CompositeFeature>(mySketchLabel->feature());
+  if (sketch) {
+    PartSet_Tools::sketchPlaneCircularGridOffsetX(sketch)->setValue(theOffset);
+    mySketchLabel->mySketchDataIsModified = true;
+  }
+
+  if (myPreviewPlane) {
+    myPreviewPlane->setCircularGridOffsetX(theOffset);
+    myPreviewPlane->reconfigureGrid();
+    mySketchLabel->myWorkshop->viewer()->update();
+  }
+}
+
+void PartSet_WidgetSketchCircularGrid::onOffsetYChanged(double theOffset)
+{
+  const auto sketch = std::static_pointer_cast<ModelAPI_CompositeFeature>(mySketchLabel->feature());
+  if (sketch) {
+    PartSet_Tools::sketchPlaneCircularGridOffsetY(sketch)->setValue(theOffset);
+    mySketchLabel->mySketchDataIsModified = true;
+  }
+
+  if (myPreviewPlane) {
+    myPreviewPlane->setCircularGridOffsetY(theOffset);
+    myPreviewPlane->reconfigureGrid();
+    mySketchLabel->myWorkshop->viewer()->update();
+  }
+}
+
+void PartSet_WidgetSketchCircularGrid::updateSegmentsToolTip()
+{
+   const int N = myNASSpinBox->value();
+   QString toolTip = tr("Even numbers only.") + "\nAngle " + QString::number(180 - double(N-2)/N * 180, 'f', PartSet_WidgetSketchGrid::NUM_OF_DECIMAL_DIGITS_ROTAT) + "°";
+   myNASSpinBox->setToolTip(toolTip);
+}
\ No newline at end of file
index 2d1d1ae3a00e278bfeb481684700b456f856997a..e09c9f4738c90bedc4286729109c2de6c5a93ea1 100644 (file)
@@ -21,7 +21,6 @@
 #define PartSet_WidgetSketchLabel_H
 
 #include "PartSet.h"
-
 #include "PartSet_Tools.h"
 
 #include <ModuleBase_WidgetValidated.h>
 #include <TopoDS_Shape.hxx>
 
 #include <QStackedWidget>
+#include <QDoubleSpinBox>
 #include <QMap>
 
 class PartSet_PreviewPlanes;
+class PartSet_PreviewSketchPlane;
 
-class QLabel;
 class XGUI_OperationMgr;
 class XGUI_Workshop;
 class QCheckBox;
-class QStackedWidget;
+class QComboBox;
+class QDialog;
+class QSpinBox;
+class QGroupBox;
+class QLabel;
 class QLineEdit;
 class QPushButton;
-class QDialog;
+class QStackedWidget;
+class QGridLayout;
+
+class PartSet_WidgetSketchLabel;
+class ModelAPI_CompositeFeature;
+
+
+class PartSet_WidgetSketchRectangularGrid;
+class PartSet_WidgetSketchCircularGrid;
+
 
 /**
 * \ingroup Modules
-* A model widget implementation for a label which provides specific behaviour 
+* A model widget implementation for a label which provides specific behaviour
 * for sketcher starting and launching operations
 */
 class PARTSET_EXPORT PartSet_WidgetSketchLabel : public ModuleBase_WidgetValidated
@@ -65,7 +78,15 @@ public:
                       const Config_WidgetAPI* theData,
                       const QMap<PartSet_Tools::ConstraintVisibleState, bool>& toShowConstraints);
 
-  virtual ~PartSet_WidgetSketchLabel();
+  virtual ~PartSet_WidgetSketchLabel() = default;
+
+  virtual void setFeature(
+    const FeaturePtr& theFeature,
+    const bool theToStoreValue = false,
+    const bool isUpdateFlushed = true
+  );
+
+  virtual bool isModified() const { return mySketchDataIsModified; }
 
   /// Set the given wrapped value to the current widget
   /// This value should be processed in the widget according to the needs
@@ -157,10 +178,7 @@ protected:
 
   /// Saves the internal parameters to the given feature
   /// \return True in success
-  virtual bool storeValueCustom()
-  {
-    return true;
-  }
+  virtual bool storeValueCustom();
 
   virtual bool restoreValueCustom();
 
@@ -213,24 +231,32 @@ protected:
   virtual bool eventFilter(QObject* theObj, QEvent* theEvent);
 
 private slots:
-  /// A slot called on set sketch plane view
+  /// Called on set sketch plane view
   void onSetPlaneView();
 
   /// Emits signal about check box state changed with information about ConstraintVisibleState
   /// \param theOn a flag show constraints or not
   void onShowConstraint(bool theOn);
 
-  /// A a slot called on "Change sketch plane" check box toggele
+  /// Called on "Change sketch plane" button is clicked.
   void onChangePlane();
 
-  /// A a slot called on "Show remaining DOFs" check box toggele
+  /// Called on "Show remaining DOFs" button is clicked.
   void onShowDOF();
 
-  ///  A a slot called on changing the panel visibility
+  ///  Called on changing the panel visibility
   void onShowPanel();
 
-  /// A slot which is called on "Visible" plane checkbox toggle
-  void onShowViewPlane(bool);
+  void onShowAxes(bool);
+  void onShowSubstrate(bool);
+
+  void onGridTypeChanged(int);
+  void onGridSnappingModeChanged(int);
+
+  /*! \brief Must be called after PartSet_PreviewSketchPlane is configured. */
+  void reconfigureSketchViewWidgets();
+
+  void saveSketchViewPreferenceToSkethData();
 
 private:
   /// Set sketch plane by shape
@@ -243,7 +269,7 @@ private:
 
   /**
   * Returns list of presentations which have displayed shapes with circular edges
-  * (circles, arcs) which are in pane of of the given sketch
+  * (circles, arcs) which are in plane of of the given sketch
   * \param theSketch - the sketch
   */
   QList<ModuleBase_ViewerPrsPtr> findCircularEdgesInPlane();
@@ -252,8 +278,22 @@ private:
   /// class to show/hide preview planes
   PartSet_PreviewPlanes* myPreviewPlanes;
 
+  bool mySketchDataIsModified;
+  QGroupBox* mySketchViewGroupBox;
+
   QCheckBox* myViewInverted;
-  QCheckBox* myViewVisible;
+  QCheckBox* myAxesVisibleCheckBox; // Local sketch axes.
+  QCheckBox* mySubstrateVisibleCheckBox;
+
+  QComboBox* myGridTypeComboBox;
+  QComboBox* myGridSnappingModeComboBox;
+
+  friend class PartSet_WidgetSketchGrid;
+  friend class PartSet_WidgetSketchRectangularGrid;
+  friend class PartSet_WidgetSketchCircularGrid;
+  PartSet_WidgetSketchRectangularGrid* myWidgetRectangularGrid;
+  PartSet_WidgetSketchCircularGrid*    myWidgetCircularGrid;
+
   QCheckBox* myRemoveExternal;
   QCheckBox* myShowPoints;
   QCheckBox* myAutoConstraints;
@@ -276,4 +316,134 @@ private:
   GeomPlanePtr myTmpPlane;
 };
 
+
+class PARTSET_EXPORT PitchSpinBox : public QDoubleSpinBox
+{
+    Q_OBJECT
+public:
+  PitchSpinBox(QWidget* theParent);
+  ~PitchSpinBox() = default;
+
+  void setValue(double theVal);
+
+signals:
+  void valueSet(double theVal);
+
+private slots:
+  virtual void onTextChanged();
+  virtual void onEditingFinished();
+
+private:
+  double myPrevVal;
+};
+
+
+class PARTSET_EXPORT PartSet_WidgetSketchGrid : public QWidget
+{
+  Q_OBJECT
+public:
+/*! \param theSketchLabel must not be nullptr. */
+  PartSet_WidgetSketchGrid(QWidget* theParent, PartSet_WidgetSketchLabel* theSketchLabel);
+  virtual ~PartSet_WidgetSketchGrid() = default;
+
+  virtual void recongifure() = 0;
+
+protected slots:
+  virtual void onResetClicked() = 0;
+  virtual void onOffsetAngleChanged(double theOffset) = 0;
+  virtual void onOffsetXChanged(double theOffset) = 0;
+  virtual void onOffsetYChanged(double theOffset) = 0;
+
+protected:
+  /*! \returns Modulo of theValue in (-theIntervalWidth/2; theIntervalWidth/2]. */
+  static double clampValue(double theValue, double theIntervalWidth);
+
+  /*! \returns Reasonable increment for offset spinbox for given grid pitch. */
+  static double reasonableOffsetIncrement(double theStep);
+
+  /*! \returns Reasonable increment for pitch spinbox for given grid pitch. */
+  static double reasonablePitchIncrement(double theStep);
+
+  void retrieveSketchAndPlane();
+
+protected:
+  /** Num of digits in fractional part of translational values. */
+  static const int NUM_OF_DECIMAL_DIGITS_TRANS;
+
+  /** Num of digits in fractional part of rotational values. */
+  static const int NUM_OF_DECIMAL_DIGITS_ROTAT;
+
+  static const int SPIN_BOX_MIN_WIDTH;
+
+protected:
+  PartSet_WidgetSketchLabel* const mySketchLabel;
+  PartSet_PreviewSketchPlane* myPreviewPlane;
+
+  QGridLayout*    myLayout;
+
+  QPushButton*    myResetButton;
+
+  QDoubleSpinBox* myOffsetXSpinBox;
+  QDoubleSpinBox* myOffsetYSpinBox;
+  QDoubleSpinBox* myOffsetAngleSpinBox;
+
+  friend class PitchSpinBox;
+};
+
+
+class PARTSET_EXPORT PartSet_WidgetSketchRectangularGrid : public PartSet_WidgetSketchGrid
+{
+  Q_OBJECT
+public:
+  /*! \param theSketchLabel must not be nullptr. */
+  PartSet_WidgetSketchRectangularGrid(QWidget* theParent, PartSet_WidgetSketchLabel* theSketchLabel);
+  virtual ~PartSet_WidgetSketchRectangularGrid() = default;
+
+  /*! \brief Must be called after PartSet_PreviewSketchPlane is configured. */
+  virtual void recongifure();
+
+private slots:
+  void onStepXSet(double theStep);
+  void onStepYSet(double theStep);
+
+protected slots:
+  virtual void onResetClicked();
+  virtual void onOffsetAngleChanged(double theOffset);
+  virtual void onOffsetXChanged(double theOffset);
+  virtual void onOffsetYChanged(double theOffset);
+
+private:
+  PitchSpinBox* myStepXSpinBox;
+  PitchSpinBox* myStepYSpinBox;
+};
+
+
+class PARTSET_EXPORT PartSet_WidgetSketchCircularGrid : public PartSet_WidgetSketchGrid
+{
+  Q_OBJECT
+public:
+  /*! \param theSketchLabel must not be nullptr. */
+  PartSet_WidgetSketchCircularGrid(QWidget* theParent, PartSet_WidgetSketchLabel* theSketchLabel);
+  virtual ~PartSet_WidgetSketchCircularGrid() = default;
+
+  /*! \brief Must be called after PartSet_PreviewSketchPlane is configured. */
+  virtual void recongifure();
+
+private slots:
+  void onStepRChanged(double theStep);
+  void onNumOfAngularSegmentsChanged(int theNum);
+
+protected slots:
+  virtual void onResetClicked();
+  virtual void onOffsetAngleChanged(double theOffset);
+  virtual void onOffsetXChanged(double theOffset);
+  virtual void onOffsetYChanged(double theOffset);
+
+private:
+  void updateSegmentsToolTip();
+
+  PitchSpinBox* myStepRSpinBox;
+  QSpinBox*       myNASSpinBox;
+};
+
 #endif
index 9e308a2fa2733b295fa314471b5341b02613285f..b9e730521718b00154717d8a6ded28624e1fd65b 100644 (file)
@@ -98,6 +98,47 @@ Error: %1</source>
 Erreur : %1</translation>
     </message>
 </context>
+<context>
+    <name>PartSet_WidgetSketchGrid</name>
+    <message>
+        <source>Reset</source>
+        <translation>Réinitialiser</translation>
+    </message>
+    <message>
+        <source>Set default pitches and zero offsets.</source>
+        <translation>Définissez le pas par défaut et les offsets zéro.</translation>
+    </message>
+    <message>
+        <source>Offset angle,°</source>
+        <translation>Offset angulaire,°</translation>
+    </message>
+    <message>
+        <source>Offset</source>
+        <translation>Offset</translation>
+    </message>
+</context>
+<context>
+    <name>PartSet_WidgetSketchRectangularGrid</name>
+    <message>
+        <source>Pitch</source>
+        <translation>Pas</translation>
+    </message>
+</context>
+<context>
+    <name>PartSet_WidgetSketchCircularGrid</name>
+    <message>
+        <source>Pitch</source>
+        <translation>Pas</translation>
+    </message>
+    <message>
+        <source>Num of angular segments</source>
+        <translation>Nombre de segments angulaires</translation>
+    </message>
+    <message>
+        <source>Even numbers only.</source>
+        <translation>Chiffres pairs uniquement.</translation>
+    </message>
+</context>
 <context>
     <name>PartSet_WidgetSketchLabel</name>
     <message>
@@ -116,10 +157,46 @@ Erreur : %1</translation>
         <source>Reversed</source>
         <translation>Renversé</translation>
     </message>
+    <message>
+        <source>Axes</source>
+        <translation>Axes</translation>
+    </message>
+    <message>
+        <source>Substrate</source>
+        <translation>Substrat</translation>
+    </message>
     <message>
         <source>Set plane view</source>
         <translation>Définir la vue plane</translation>
     </message>
+    <message>
+        <source>Construction grid</source>
+        <translation>Grille de construction</translation>
+    </message>
+    <message>
+        <source>Disabled</source>
+        <translation>Désactivé</translation>
+    </message>
+    <message>
+        <source>Rectangular</source>
+        <translation>Rectangulaire</translation>
+    </message>
+    <message>
+        <source>Circular</source>
+        <translation>Circulaire</translation>
+    </message>
+    <message>
+        <source>Don't snap</source>
+        <translation>Ne t'accroche pas</translation>
+    </message>
+    <message>
+        <source>Snap anyway</source>
+        <translation>Pour s'accrocher quand même</translation>
+    </message>
+    <message>
+        <source>Snap in proximity</source>
+        <translation>Pour s'accrocher à proximité</translation>
+    </message>
     <message>
         <source>Show geometrical constraints</source>
         <translation>Afficher les contraintes géométriques</translation>
index 537ff13033e1cba789850df7392f7758d14414b4..87014f3ab44697aa4deb9bc7457e20fe51d590ae 100644 (file)
@@ -70,6 +70,84 @@ class SketchPlugin_Sketch : public ModelAPI_CompositeFeature//, public GeomAPI_I
     static const std::string MY_NORM_ID("Norm");
     return MY_NORM_ID;
   }
+
+  /// Size of a skectch, if it has just been created on a default plane.
+  inline static const std::string& DEFAULT_SIZE_ID()
+  {
+    static const std::string MY_DEFAULT_SIZE_ID_ID("DefaultSize");
+    return MY_DEFAULT_SIZE_ID_ID;
+  }
+
+  inline static const std::string& AXES_ENABLED_ID()
+  {
+    static const std::string MY_AXES_ENABLED_ID("AxesEnabled");
+    return MY_AXES_ENABLED_ID;
+  }
+
+  inline static const std::string& SUBSTRATE_ENABLED_ID()
+  {
+    static const std::string MY_SUBSTRATE_ENABLED_ID("SubstrateEnabled");
+    return MY_SUBSTRATE_ENABLED_ID;
+  }
+
+  inline static const std::string& CONSTRUCTION_GRID_TYPE_ID()
+  {
+    static const std::string MY_CONSTRUCTION_GRID_TYPE_ID("GridType");
+    return MY_CONSTRUCTION_GRID_TYPE_ID;
+  }
+
+  inline static const std::string& RECTANGULAR_CONSTRUCTION_GRID_STEP_X_ID()
+  {
+    static const std::string MY_RECTANGULAR_CONSTRUCTION_GRID_STEP_X_ID("RectangularGridStepX");
+    return MY_RECTANGULAR_CONSTRUCTION_GRID_STEP_X_ID;
+  }
+  inline static const std::string& RECTANGULAR_CONSTRUCTION_GRID_STEP_Y_ID()
+  {
+    static const std::string MY_RECTANGULAR_CONSTRUCTION_GRID_STEP_Y_ID("RectangularGridStepY");
+    return MY_RECTANGULAR_CONSTRUCTION_GRID_STEP_Y_ID;
+  }
+  inline static const std::string& RECTANGULAR_CONSTRUCTION_GRID_OFFSET_ANGLE_ID()
+  {
+    static const std::string MY_RECTANGULAR_CONSTRUCTION_GRID_OFFSET_ANGLE_ID("RectangularGridOffsetAngle");
+    return MY_RECTANGULAR_CONSTRUCTION_GRID_OFFSET_ANGLE_ID;
+  }
+  inline static const std::string& RECTANGULAR_CONSTRUCTION_GRID_OFFSET_X_ID()
+  {
+    static const std::string MY_RECTANGULAR_CONSTRUCTION_GRID_OFFSET_X_ID("RectangularGridOffsetX");
+    return MY_RECTANGULAR_CONSTRUCTION_GRID_OFFSET_X_ID;
+  }
+  inline static const std::string& RECTANGULAR_CONSTRUCTION_GRID_OFFSET_Y_ID()
+  {
+    static const std::string MY_RECTANGULAR_CONSTRUCTION_GRID_OFFSET_Y_ID("RectangularGridOffsetY");
+    return MY_RECTANGULAR_CONSTRUCTION_GRID_OFFSET_Y_ID;
+  }
+
+  inline static const std::string& CIRCULAR_CONSTRUCTION_GRID_STEP_R_ID()
+  {
+    static const std::string MY_CIRCULAR_CONSTRUCTION_GRID_STEP_R_ID("CircularGridStepR");
+    return MY_CIRCULAR_CONSTRUCTION_GRID_STEP_R_ID;
+  }
+  inline static const std::string& CIRCULAR_CONSTRUCTION_GRID_NUM_OF_ANG_SEGMENTS_ID()
+  {
+    static const std::string MY_CIRCULAR_CONSTRUCTION_GRID_NUM_OF_ANG_SEGMENTS_ID("CircularGridNumOfAnularSegments");
+    return MY_CIRCULAR_CONSTRUCTION_GRID_NUM_OF_ANG_SEGMENTS_ID;
+  }
+  inline static const std::string& CIRCULAR_CONSTRUCTION_GRID_OFFSET_ANGLE_ID()
+  {
+    static const std::string MY_CIRCULAR_CONSTRUCTION_GRID_OFFSET_ANGLE_ID("CircularGridOffsetAngle");
+    return MY_CIRCULAR_CONSTRUCTION_GRID_OFFSET_ANGLE_ID;
+  }
+  inline static const std::string& CIRCULAR_CONSTRUCTION_GRID_OFFSET_X_ID()
+  {
+    static const std::string MY_CIRCULAR_CONSTRUCTION_GRID_OFFSET_X_ID("CircularGridOffsetX");
+    return MY_CIRCULAR_CONSTRUCTION_GRID_OFFSET_X_ID;
+  }
+  inline static const std::string& CIRCULAR_CONSTRUCTION_GRID_OFFSET_Y_ID()
+  {
+    static const std::string MY_CIRCULAR_CONSTRUCTION_GRID_OFFSET_Y_ID("CircularGridOffsetY");
+    return MY_CIRCULAR_CONSTRUCTION_GRID_OFFSET_Y_ID;
+  }
+
   /// All features of this sketch (list of references)
   inline static const std::string& FEATURES_ID()
   {
index 4775444902facde04b55b7dd5a36b7b2d05f59af..cbc346a0564e3c92484aa432fbbc0b85b298d93f 100644 (file)
@@ -771,26 +771,32 @@ Handle(SelectMgr_AndFilter) XGUI_Displayer::GetFilter()
 //**************************************************************
 bool XGUI_Displayer::displayAIS(AISObjectPtr theAIS, const bool toActivateInSelectionModes,
                                 const Standard_Integer theDisplayMode, bool theUpdateViewer)
+{
+  Handle(AIS_InteractiveObject) anAISIO = theAIS->impl<Handle(AIS_InteractiveObject)>();
+  return displayAIS(anAISIO, toActivateInSelectionModes, theDisplayMode, theUpdateViewer);
+}
+
+bool XGUI_Displayer::displayAIS(Handle(AIS_InteractiveObject) theAISIO, const bool toActivateInSelectionModes,
+    const Standard_Integer theDisplayMode, bool theUpdateViewer)
 {
   bool aDisplayed = false;
   Handle(AIS_InteractiveContext) aContext = AISContext();
-  Handle(AIS_InteractiveObject) anAISIO = theAIS->impl<Handle(AIS_InteractiveObject)>();
-  if (!aContext.IsNull() && !anAISIO.IsNull()) {
-    aContext->Display(anAISIO, theDisplayMode, 0, false/*update viewer*/, AIS_DS_Displayed);
+  if (!aContext.IsNull() && !theAISIO.IsNull()) {
+    aContext->Display(theAISIO, theDisplayMode, 0, false/*update viewer*/, AIS_DS_Displayed);
     #ifdef TINSPECTOR
-    if (getCallBack()) getCallBack()->Display(anAISIO);
+    if (getCallBack()) getCallBack()->Display(theAISIO);
     #endif
     aDisplayed = true;
-    aContext->Deactivate(anAISIO);
+    aContext->Deactivate(theAISIO);
     #ifdef TINSPECTOR
-    if (getCallBack()) getCallBack()->Deactivate(anAISIO);
+    if (getCallBack()) getCallBack()->Deactivate(theAISIO);
     #endif
-    aContext->Load(anAISIO);
+    aContext->Load(theAISIO);
     #ifdef TINSPECTOR
-    if (getCallBack()) getCallBack()->Load(anAISIO);
+    if (getCallBack()) getCallBack()->Load(theAISIO);
     #endif
     if (toActivateInSelectionModes)
-      myWorkshop->selectionActivate()->activateOnDisplay(anAISIO, theUpdateViewer);
+      myWorkshop->selectionActivate()->activateOnDisplay(theAISIO, theUpdateViewer);
 
     if (theUpdateViewer)
       updateViewer();
@@ -818,6 +824,25 @@ bool XGUI_Displayer::eraseAIS(AISObjectPtr theAIS, const bool theUpdateViewer)
   return aErased;
 }
 
+//**************************************************************
+bool XGUI_Displayer::eraseAIS(Handle(AIS_InteractiveObject) theAISIO, const bool theUpdateViewer)
+{
+  bool aErased = false;
+  Handle(AIS_InteractiveContext) aContext = AISContext();
+  if (!aContext.IsNull()) {
+    if (!theAISIO.IsNull() && aContext->IsDisplayed(theAISIO)) {
+      aContext->Remove(theAISIO, false/*update viewer*/);
+      #ifdef TINSPECTOR
+      if (getCallBack()) getCallBack()->Remove(theAISIO);
+      #endif
+      aErased = true;
+    }
+  }
+  if (aErased && theUpdateViewer)
+    updateViewer();
+  return aErased;
+}
+
 //**************************************************************
 void XGUI_Displayer::setDisplayMode(ObjectPtr theObject, DisplayMode theMode, bool theUpdateViewer)
 {
index a720d6b586af1e3b31401c705236691e4cba3121..fd31cc2bb3e1584fda4d993195b763aaa9a34836 100644 (file)
@@ -209,6 +209,19 @@ public:
   bool displayAIS(AISObjectPtr theAIS, const bool toActivateInSelectionModes,
     const Standard_Integer theDisplayMode = 0, bool theUpdateViewer = true);
 
+  /// Display the given AISIO.
+  /// This object is not added to the displayer internal map of objects
+  /// So, it can not be obtained from displayer. This is just a wrap method of OCC display in
+  /// order to perform the display with correct flags.
+  /// \param theAISIO is an object to display
+  /// \param toActivateInSelectionModes boolean value whether the presentation should be
+  /// activated in the current selection modes
+  /// \param theDisplayMode mode how the presentation should be displayed
+  /// \param theUpdateViewer the parameter whether the viewer should be update immediatelly
+  /// \return true if the object visibility state is changed
+  bool displayAIS(Handle(AIS_InteractiveObject) theAISIO, const bool toActivateInSelectionModes,
+    const Standard_Integer theDisplayMode = 0, bool theUpdateViewer = true);
+
   /// Redisplay the shape if it was displayed
   /// \param theObject an object instance
   /// \param theUpdateViewer the parameter whether the viewer should be update immediatelly
@@ -243,6 +256,11 @@ public:
   /// \return true if the object visibility state is changed
   bool eraseAIS(AISObjectPtr theAIS, const bool theUpdateViewer = true);
 
+  /// Erase the given AISIO displayed by corresponded display method
+  /// \param theUpdateViewer the parameter whether the viewer should be update immediatelly
+  /// \return true if the object visibility state is changed
+  bool eraseAIS(Handle(AIS_InteractiveObject) theAISIO, const bool theUpdateViewer = true);
+
   /// Erase all presentations
   /// \param theUpdateViewer the parameter whether the viewer should be update immediatelly
   /// \return true if the object visibility state is changed