Salome HOME
Task #3079 3.5 Build/Vertex on a whole Sketch
authorjfa <jfa@opencascade.com>
Fri, 15 Nov 2019 12:43:33 +0000 (15:43 +0300)
committerjfa <jfa@opencascade.com>
Fri, 15 Nov 2019 12:43:33 +0000 (15:43 +0300)
src/BuildAPI/BuildAPI_Vertex.cpp
src/BuildAPI/BuildAPI_Vertex.h
src/BuildPlugin/BuildPlugin_Plugin.cpp
src/BuildPlugin/BuildPlugin_Validators.cpp
src/BuildPlugin/BuildPlugin_Validators.h
src/BuildPlugin/BuildPlugin_Vertex.cpp
src/BuildPlugin/BuildPlugin_Vertex.h
src/BuildPlugin/Test/TestVertex.py
src/BuildPlugin/vertex_widget.xml

index 327a2d2905d2b856c9dd346db41f294612eb17e0..f94b230f18cf841a6f2161690c27852faae9d2f5 100644 (file)
@@ -35,6 +35,19 @@ BuildAPI_Vertex::BuildAPI_Vertex(const std::shared_ptr<ModelAPI_Feature>& theFea
 : ModelHighAPI_Interface(theFeature)
 {
   if(initialize()) {
+    fillAttribute(false, mydoIntersect);
+    setBase(theBaseObjects);
+  }
+}
+
+//==================================================================================================
+BuildAPI_Vertex::BuildAPI_Vertex(const std::shared_ptr<ModelAPI_Feature>& theFeature,
+                                 const std::list<ModelHighAPI_Selection>& theBaseObjects,
+                                 const bool theDoIntersect)
+: ModelHighAPI_Interface(theFeature)
+{
+  if(initialize()) {
+    fillAttribute(theDoIntersect, mydoIntersect);
     setBase(theBaseObjects);
   }
 }
@@ -61,6 +74,15 @@ VertexPtr addVertex(const std::shared_ptr<ModelAPI_Document>& thePart,
   return VertexPtr(new BuildAPI_Vertex(aFeature, theBaseObjects));
 }
 
+//==================================================================================================
+VertexPtr addVertex(const std::shared_ptr<ModelAPI_Document>& thePart,
+                    const std::list<ModelHighAPI_Selection>& theBaseObjects,
+                    const bool theDoIntersect)
+{
+  std::shared_ptr<ModelAPI_Feature> aFeature = thePart->addFeature(BuildAPI_Vertex::ID());
+  return VertexPtr(new BuildAPI_Vertex(aFeature, theBaseObjects, theDoIntersect));
+}
+
 //==================================================================================================
 void BuildAPI_Vertex::dump(ModelHighAPI_Dumper& theDumper) const
 {
@@ -68,5 +90,6 @@ void BuildAPI_Vertex::dump(ModelHighAPI_Dumper& theDumper) const
   std::string aPartName = theDumper.name(aBase->document());
 
   theDumper << aBase << " = model.addVertex(" << aPartName << ", "
-            << aBase->selectionList(BuildPlugin_Vertex::BASE_OBJECTS_ID()) << ")" << std::endl;
+            << aBase->selectionList(BuildPlugin_Vertex::BASE_OBJECTS_ID()) << ", "
+            << aBase->boolean(BuildPlugin_Vertex::INTERSECT_ID()) << ")" << std::endl;
 }
index 6a96640a488d964b798e75a09b581abe1f85bb23..77ba77e957db28fb06ba7797a56e69737d51fbff 100644 (file)
@@ -44,13 +44,21 @@ public:
   explicit BuildAPI_Vertex(const std::shared_ptr<ModelAPI_Feature>& theFeature,
                            const std::list<ModelHighAPI_Selection>& theBaseObjects);
 
+  /// Constructor with values.
+  BUILDAPI_EXPORT
+  explicit BuildAPI_Vertex(const std::shared_ptr<ModelAPI_Feature>& theFeature,
+                           const std::list<ModelHighAPI_Selection>& theBaseObjects,
+                           const bool theDoIntersect);
+
   /// Destructor.
   BUILDAPI_EXPORT
   virtual ~BuildAPI_Vertex();
 
-  INTERFACE_1(BuildPlugin_Vertex::ID(),
+  INTERFACE_2(BuildPlugin_Vertex::ID(),
               baseObjects, BuildPlugin_Vertex::BASE_OBJECTS_ID(),
-              ModelAPI_AttributeSelectionList, /** Base objects */)
+              ModelAPI_AttributeSelectionList, /** Base objects */,
+              doIntersect, BuildPlugin_Vertex::INTERSECT_ID(),
+              ModelAPI_AttributeBoolean, /** Compute intersections */)
 
   /// Modify base attribute of the feature.
   BUILDAPI_EXPORT
@@ -70,4 +78,11 @@ BUILDAPI_EXPORT
 VertexPtr addVertex(const std::shared_ptr<ModelAPI_Document>& thePart,
                     const std::list<ModelHighAPI_Selection>& theBaseObjects);
 
+/// \ingroup CPPHighAPI
+/// \brief Create Vertex feature.
+BUILDAPI_EXPORT
+VertexPtr addVertex(const std::shared_ptr<ModelAPI_Document>& thePart,
+                    const std::list<ModelHighAPI_Selection>& theBaseObjects,
+                    const bool theDoIntersect);
+
 #endif // BuildAPI_Vertex_H_
index 5d37e3299e8602c5efddd19b4c8561d6b14de795..4c20e54ddcc2f84b1dcbdb071414530c62f66130 100644 (file)
@@ -57,6 +57,8 @@ BuildPlugin_Plugin::BuildPlugin_Plugin()
                               new BuildPlugin_ValidatorSubShapesSelection());
   aFactory->registerValidator("BuildPlugin_ValidatorFillingSelection",
                               new BuildPlugin_ValidatorFillingSelection());
+  aFactory->registerValidator("BuildPlugin_ValidatorBaseForVertex",
+                              new BuildPlugin_ValidatorBaseForVertex());
 
   // Register this plugin.
   ModelAPI_Session::get()->registerPlugin(this);
index 65be3411933356f25d4724b941c2659b693436a4..6b44423f877549b50f43992147414e87f3b68c6e 100644 (file)
@@ -464,7 +464,7 @@ bool BuildPlugin_ValidatorFillingSelection::isValid(const AttributePtr& theAttri
     return false;
   }
 
-  FeaturePtr anOwner = ModelAPI_Feature::feature(theAttribute->owner());
+  //FeaturePtr anOwner = ModelAPI_Feature::feature(theAttribute->owner());
 
   // Check selected shapes.
   for (int anIndex = 0; anIndex < aSelectionList->size(); ++anIndex) {
@@ -491,3 +491,61 @@ bool BuildPlugin_ValidatorFillingSelection::isValid(const AttributePtr& theAttri
 
   return true;
 }
+
+
+//=================================================================================================
+bool BuildPlugin_ValidatorBaseForVertex::isValid(const AttributePtr& theAttribute,
+                                                 const std::list<std::string>& /*theArguments*/,
+                                                 Events_InfoMessage& theError) const
+{
+  if (!theAttribute.get()) {
+    theError = "Error: empty selection.";
+    return false;
+  }
+
+  AttributeSelectionListPtr aSelectionList =
+    std::dynamic_pointer_cast<ModelAPI_AttributeSelectionList>(theAttribute);
+  if (!aSelectionList.get()) {
+    theError = "Could not get selection list.";
+    return false;
+  }
+
+  for (int anIndex = 0; anIndex < aSelectionList->size(); ++anIndex) {
+    AttributeSelectionPtr aSelectionAttr = aSelectionList->value(anIndex);
+    if (!aSelectionAttr.get()) {
+      theError = "Empty attribute in list.";
+      return false;
+    }
+
+    // Vertex?
+    bool isVertex = false;
+    GeomShapePtr aShapeInList = aSelectionAttr->value();
+    if (aShapeInList.get()) {
+      isVertex = (aShapeInList->shapeType() == GeomAPI_Shape::VERTEX);
+    }
+
+    if (!isVertex) {
+      // Sketch?
+      FeaturePtr aFeature = aSelectionAttr->contextFeature();
+      if (!aFeature.get()) {
+        ResultPtr aContext = aSelectionAttr->context();
+        if (aContext.get()) {
+          aFeature = ModelAPI_Feature::feature(aContext);
+        }
+      }
+
+      if (aFeature.get()) {
+        std::string aFeatureKind = aFeature->getKind();
+        if (aFeatureKind != "Sketch" &&
+            aFeatureKind != "Point" &&
+            aFeatureKind != "Vertex") {
+          theError = "Error: %1 shape is not allowed for selection.";
+          theError.arg(aFeatureKind);
+          return false;
+        }
+      }
+    }
+  }
+
+  return true;
+}
index 4c54b6b7b8ef5b2c7d1ab3d65b91032f8298465f..116d44afcfefc596cde350b124f9998c093a11d5 100644 (file)
@@ -118,4 +118,19 @@ public:
                         Events_InfoMessage& theError) const;
 };
 
+/// \class BuildPlugin_ValidatorBaseForVertex
+/// \ingroup Validators
+/// \brief A validator for selection of Vertex feature.
+class BuildPlugin_ValidatorBaseForVertex: public ModelAPI_AttributeValidator
+{
+public:
+  //! Returns true if attribute is ok.
+  //! \param[in] theAttribute the checked attribute.
+  //! \param[in] theArguments arguments of the attribute.
+  //! \param[out] theError error message.
+   virtual bool isValid(const AttributePtr& theAttribute,
+                        const std::list<std::string>& theArguments,
+                        Events_InfoMessage& theError) const;
+};
+
 #endif
index 82f3521ed81ac72dd66859b97c9a7e5a19e83bef..cd14766ed902b40105e2d42c9db29d47c36a09ae 100644 (file)
 #include "BuildPlugin_Vertex.h"
 
 #include <ModelAPI_AttributeSelectionList.h>
+#include <ModelAPI_AttributeBoolean.h>
+#include <ModelAPI_CompositeFeature.h>
 #include <ModelAPI_ResultBody.h>
+#include <ModelAPI_Validator.h>
+#include <ModelAPI_Session.h>
 
 #include <GeomAlgoAPI_Copy.h>
 #include <GeomAlgoAPI_Tools.h>
+#include <GeomAlgoAPI_Partition.h>
+#include <GeomAlgoAPI_ShapeTools.h>
+
+#include <GeomAPI_ShapeExplorer.h>
 
 //=================================================================================================
 BuildPlugin_Vertex::BuildPlugin_Vertex()
@@ -34,62 +42,242 @@ BuildPlugin_Vertex::BuildPlugin_Vertex()
 void BuildPlugin_Vertex::initAttributes()
 {
   data()->addAttribute(BASE_OBJECTS_ID(), ModelAPI_AttributeSelectionList::typeId());
+
+  data()->addAttribute(INTERSECT_ID(), ModelAPI_AttributeBoolean::typeId());
+  ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), INTERSECT_ID());
+}
+
+//=================================================================================================
+bool BuildPlugin_Vertex::buildVertices(GeomShapePtr theShape,
+                                       bool isIntersect,
+                                       int& theResultIndex)
+{
+  if (!theShape.get()) {
+    setError("Error: Empty shape selected.");
+    return false;
+  }
+
+  if (theShape->shapeType() == GeomAPI_Shape::VERTEX) {
+    // Copy shape.
+    std::shared_ptr<GeomAlgoAPI_Copy> aCopyAlgo(new GeomAlgoAPI_Copy(theShape));
+
+    std::string anError;
+    if (GeomAlgoAPI_Tools::AlgoError::isAlgorithmFailed(aCopyAlgo, getKind(), anError)) {
+      setError(anError);
+      return false;
+    }
+
+    // Store result.
+    ResultBodyPtr aResultBody = document()->createBody(data(), theResultIndex);
+    aResultBody->storeModified(theShape, aCopyAlgo->shape());
+    setResult(aResultBody, theResultIndex);
+    ++theResultIndex;
+  } else {
+    // Sketch
+    GeomAPI_DataMapOfShapeShape alreadyProcessed;
+
+    // 1. Explode on Vertices
+    for (GeomAPI_ShapeExplorer anExp (theShape, GeomAPI_Shape::VERTEX); anExp.more(); anExp.next()) {
+      GeomShapePtr aSubShape = anExp.current();
+
+      if (alreadyProcessed.bind(aSubShape, aSubShape)) {
+        // Store result.
+        ResultBodyPtr aResultBody = document()->createBody(data(), theResultIndex);
+        aResultBody->storeModified(theShape, aSubShape);
+        setResult(aResultBody, theResultIndex);
+        ++theResultIndex;
+      }
+    }
+
+    // 2. If need intersection points, perform Partition
+    if (isIntersect) {
+      // Partition
+      ListOfShape anObjList, aTools;
+      anObjList.push_back(theShape);
+      std::shared_ptr<GeomAlgoAPI_Partition> aPartitionAlgo (new GeomAlgoAPI_Partition(anObjList, aTools));
+
+      std::string anError;
+      if (GeomAlgoAPI_Tools::AlgoError::isAlgorithmFailed(aPartitionAlgo, getKind(), anError)) {
+        setError(anError);
+        return false;
+      }
+      GeomShapePtr aSplittedSketch = aPartitionAlgo->shape();
+
+      // Explode on Vertices, skip vertices of initial sketch
+      for (GeomAPI_ShapeExplorer anExp (aSplittedSketch, GeomAPI_Shape::VERTEX); anExp.more(); anExp.next()) {
+        GeomShapePtr aSubShape = anExp.current();
+
+        //if (!theShape->isSubShape(aSubShape)) { // skip vertices of initial sketch
+        if (alreadyProcessed.bind(aSubShape, aSubShape)) {
+          // Store result.
+          ResultBodyPtr aResultBody = document()->createBody(data(), theResultIndex);
+          aResultBody->storeGenerated(anObjList, aSubShape, aPartitionAlgo);
+          setResult(aResultBody, theResultIndex);
+          ++theResultIndex;
+        }
+      }
+    }
+  }
+
+  return true;
 }
 
 //=================================================================================================
+bool BuildPlugin_Vertex::buildVertices(FeaturePtr theFeature,
+                                       bool isIntersect,
+                                       int& theResultIndex)
+{
+  if (theFeature->getKind() != "Sketch") return false;
+
+  // Sub-features
+  CompositeFeaturePtr aComposite =
+    std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(theFeature);
+  if (!aComposite) return false;
+  int nbSubs = aComposite->numberOfSubs();
+  if (nbSubs < 1) return false;
+
+  // The whole sketch shape
+  ResultPtr aContext = theFeature->firstResult();
+  GeomShapePtr theShape = aContext->shape();
+
+  GeomAPI_DataMapOfShapeShape alreadyProcessed;
+
+  // 1. Explode on Vertices
+  for (GeomAPI_ShapeExplorer anExp (theShape, GeomAPI_Shape::VERTEX); anExp.more(); anExp.next()) {
+    GeomShapePtr aSubShape = anExp.current();
+
+    if (alreadyProcessed.bind(aSubShape, aSubShape)) {
+      // Store result.
+      ResultBodyPtr aResultBody = document()->createBody(data(), theResultIndex);
+      aResultBody->storeModified(theShape, aSubShape);
+      setResult(aResultBody, theResultIndex);
+      ++theResultIndex;
+    }
+  }
+
+  // 2. If need intersection points, perform Partition
+  if (isIntersect) {
+    // Partition
+    ListOfShape anObjList, aTools;
+    anObjList.push_back(theShape);
+    std::shared_ptr<GeomAlgoAPI_Partition> aPartitionAlgo (new GeomAlgoAPI_Partition(anObjList, aTools));
+
+    std::string anError;
+    if (GeomAlgoAPI_Tools::AlgoError::isAlgorithmFailed(aPartitionAlgo, getKind(), anError)) {
+      setError(anError);
+      return false;
+    }
+    GeomShapePtr aSplittedSketch = aPartitionAlgo->shape();
+
+    // Explode on Vertices, skip vertices of initial sketch
+    for (GeomAPI_ShapeExplorer anExp (aSplittedSketch, GeomAPI_Shape::VERTEX); anExp.more(); anExp.next()) {
+      GeomShapePtr aSubShape = anExp.current();
+
+      //if (!theShape->isSubShape(aSubShape)) { // skip vertices of initial sketch
+      if (alreadyProcessed.bind(aSubShape, aSubShape)) {
+        // Store result.
+        ResultBodyPtr aResultBody = document()->createBody(data(), theResultIndex);
+        aResultBody->storeGenerated(anObjList, aSubShape, aPartitionAlgo);
+        setResult(aResultBody, theResultIndex);
+        ++theResultIndex;
+      }
+    }
+  }
+
+  // 3. Add construction points (centers of circles, etc.)
+  for (int i = 0; i < nbSubs; i++) {
+    FeaturePtr aSubFeature = aComposite->subFeature(i);
+    const std::list<ResultPtr>& aSubResults = aSubFeature->results();
+    std::list<ResultPtr>::const_iterator anItRes = aSubResults.cbegin();
+    // Iterate on all sub-results
+    for (; anItRes != aSubResults.cend(); anItRes++) {
+      ResultPtr aRes = *anItRes;
+      if (aRes.get()) {
+        // Sub-result i
+        GeomShapePtr aSubResShape = aRes->shape();
+
+        for (GeomAPI_ShapeExplorer anExp (aSubResShape, GeomAPI_Shape::VERTEX); anExp.more(); anExp.next()) {
+          GeomShapePtr aSubShape = anExp.current();
+
+          if (alreadyProcessed.bind(aSubShape, aSubShape)) {
+            // Store result.
+            ResultBodyPtr aResultBody = document()->createBody(data(), theResultIndex);
+            aResultBody->storeModified(theShape, aSubShape);
+            setResult(aResultBody, theResultIndex);
+            ++theResultIndex;
+          }
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
 void BuildPlugin_Vertex::execute()
 {
   // Get base objects list.
   AttributeSelectionListPtr aSelectionList = selectionList(BASE_OBJECTS_ID());
-  if(!aSelectionList.get()) {
+  if (!aSelectionList.get()) {
     setError("Error: Could not get selection list.");
     return;
   }
-  if(aSelectionList->size() == 0) {
+  if (aSelectionList->size() == 0) {
     setError("Error: Empty selection list.");
     return;
   }
 
-  // Collect base shapes.
-  ListOfShape aListOfShapes;
+  // Get "Compute intersections" flag value
+  bool isIntersect = false;
+  if (boolean(INTERSECT_ID()).get() && boolean(INTERSECT_ID())->isInitialized()) {
+    isIntersect = boolean(INTERSECT_ID())->value();
+  }
+
+  // Iterate arguments and build results
   int aResultIndex = 0;
-  for(int anIndex = 0; anIndex < aSelectionList->size(); ++anIndex) {
+  for (int anIndex = 0; anIndex < aSelectionList->size(); ++anIndex) {
     AttributeSelectionPtr aSelection = aSelectionList->value(anIndex);
     GeomShapePtr aShape = aSelection->value();
-    if(!aShape.get()) {
+    if (aShape.get()) {
+      // A shape selected
+      if (!buildVertices(aShape, isIntersect, aResultIndex)) return;
+    } else {
       ResultPtr aContext = aSelection->context();
-      if(!aContext.get()) {
-        setError("Error: Attribute has empty context.");
-        return;
+      if (aContext.get()) { // Result selected
+        FeaturePtr aFeature = ModelAPI_Feature::feature(aContext);
+        if (aFeature.get()) {
+          if (aFeature->getKind() == "Sketch") {
+            // Special processing for sketch to build center vertices etc.
+            if (!buildVertices(aFeature, isIntersect, aResultIndex)) return;
+          } else {
+            aShape = aContext->shape();
+            if (!buildVertices(aShape, isIntersect, aResultIndex)) return;
+          }
+        }
+      } else {
+        FeaturePtr aFeature = aSelection->contextFeature();
+        if (aFeature.get()) { // Feature selected
+          if (aFeature->getKind() == "Sketch") {
+            // Special processing for sketch to build center vertices etc.
+            if (!buildVertices(aFeature, isIntersect, aResultIndex)) return;
+          } else {
+            const std::list<ResultPtr>& anArgResults = aFeature->results();
+            std::list<ResultPtr>::const_iterator anItRes = anArgResults.cbegin();
+            // Iterate on all its results
+            for (; anItRes != anArgResults.cend(); anItRes++) {
+              ResultPtr aRes = *anItRes;
+              if (aRes.get()) {
+                // Result i
+                aShape = aRes->shape();
+                if (!buildVertices(aShape, isIntersect, aResultIndex)) return;
+              }
+            }
+          }
+        }
       }
-
-      aShape = aContext->shape();
     }
-    if(!aShape.get()) {
-      setError("Error: Empty shape selected.");
-      return;
-    }
-
-    if(aShape->shapeType() != GeomAPI_Shape::VERTEX) {
-      setError("Error: Selected shape has wrong type. Only vertices acceptable.");
-      return;
-    }
-
-    // Copy shape.
-    std::shared_ptr<GeomAlgoAPI_Copy> aCopyAlgo(new GeomAlgoAPI_Copy(aShape));
-
-    std::string anError;
-    if (GeomAlgoAPI_Tools::AlgoError::isAlgorithmFailed(aCopyAlgo, getKind(), anError)) {
-      setError(anError);
-      return;
-    }
-
-    // Store result.
-    ResultBodyPtr aResultBody = document()->createBody(data(), aResultIndex);
-    aResultBody->storeModified(aShape, aCopyAlgo->shape());
-    setResult(aResultBody, aResultIndex);
-    ++aResultIndex;
   }
 
+  // Remove extra results from previous execution
   removeResults(aResultIndex);
 }
index 62852d5e3a087b13fb93b4739741203c6bf3f901..d6dfa9c7814e880a940f2dd39030fe45f74ca721 100644 (file)
@@ -23,6 +23,7 @@
 #include "BuildPlugin.h"
 
 #include <ModelAPI_Feature.h>
+#include <GeomAPI_Shape.h>
 
 /// \class BuildPlugin_Vertex
 /// \ingroup Plugins
@@ -47,6 +48,13 @@ public:
     return MY_BASE_OBJECTS_ID;
   }
 
+  /// Attribute name of "Compute intersections" checkbox.
+  inline static const std::string& INTERSECT_ID()
+  {
+    static const std::string MY_INTERSECT_ID("intersect");
+    return MY_INTERSECT_ID;
+  }
+
   /// \return the kind of a feature.
   BUILDPLUGIN_EXPORT virtual const std::string& getKind()
   {
@@ -59,6 +67,15 @@ public:
 
   /// Creates a new part document if needed.
   BUILDPLUGIN_EXPORT virtual void execute();
+
+ protected:
+  bool buildVertices(GeomShapePtr theShape,
+                     bool isIntersect,
+                     int& theResultIndex);
+
+  bool buildVertices(FeaturePtr theFeature,
+                     bool isIntersect,
+                     int& theResultIndex);
 };
 
 #endif
index c9fcd95441450738029112b7f5c66ffbb70084c3..e2b9369902f8236ba6d9322a1eb392bdf62f8985 100644 (file)
@@ -81,7 +81,7 @@ aVertexFeature2 = aPart.addFeature("Vertex")
 aBaseObjectsList = aVertexFeature2.selectionList("base_objects")
 aBaseObjectsList.append(aSketchResult, None)
 aSession.finishOperation()
-assert (len(aVertexFeature2.results()) == 0)
+assert (len(aVertexFeature2.results()) == 10)
 
 aSession.startOperation()
 aLine = aSketchFeature.addFeature("SketchLine")
@@ -94,11 +94,14 @@ aBaseObjectsList.append(aSketchResult, aLine.lastResult().shape())
 aSession.finishOperation()
 assert (len(aVertexFeature2.results()) == 0)
 
+# Check Vertex feature failed on incorrect input
+# TODO
+
 # remove failed feature
-aSession.startOperation()
-aPart.removeFeature(aVertexFeature2)
-aPart.setCurrentFeature(aVertexFeature, True)
-aSession.finishOperation()
+#aSession.startOperation()
+#aPart.removeFeature(aVertexFeature2)
+#aPart.setCurrentFeature(aVertexFeature, True)
+#aSession.finishOperation()
 
 from salome.shaper import model
 assert(model.checkPythonDump())
index 77af0a18f5aeec634dc2270fd1abfd3fdace7e04..14ef22749b1520dfee0f0a2a2f3aea9b5e0d822f 100644 (file)
@@ -1,8 +1,10 @@
 <source>
   <multi_selector id="base_objects"
-                  label="Vertices:"
-                  tooltip="Select vertices on sketch or vertex objects."
-                  shape_types="vertices"
+                  label="Vertices and sketches:"
+                  tooltip="Select vertices or sketch objects or features."
+                  shape_types="vertices objects"
                   concealment="true">
+    <validator id="BuildPlugin_ValidatorBaseForVertex"/>
   </multi_selector>
+  <boolvalue id="intersect" label="Compute intersections" tooltip="Compute intersections of all sketch edges." default="false"/>
 </source>