Salome HOME
Task #3231: Sketcher Offset of a curve. Fix a bug with several edges, coincident...
[modules/shaper.git] / src / SketchPlugin / SketchPlugin_Offset.cpp
1 // Copyright (C) 2020  CEA/DEN, EDF R&D
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 //
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19
20 #include <SketchPlugin_Offset.h>
21
22 #include <SketchPlugin_Sketch.h>
23 #include <SketchPlugin_Line.h>
24 #include <SketchPlugin_Point.h>
25 #include <SketchPlugin_Arc.h>
26 #include <SketchPlugin_Circle.h>
27 #include <SketchPlugin_Ellipse.h>
28 #include <SketchPlugin_EllipticArc.h>
29 #include <SketchPlugin_BSpline.h>
30 #include <SketchPlugin_BSplinePeriodic.h>
31 #include <SketchPlugin_Tools.h>
32
33 #include <SketcherPrs_Factory.h>
34
35 #include <Events_InfoMessage.h>
36
37 #include <ModelAPI_AttributeBoolean.h>
38 #include <ModelAPI_AttributeDouble.h>
39 #include <ModelAPI_AttributeDoubleArray.h>
40 #include <ModelAPI_AttributeInteger.h>
41 #include <ModelAPI_AttributeIntArray.h>
42 #include <ModelAPI_AttributeRefList.h>
43 #include <ModelAPI_Events.h>
44 #include <ModelAPI_ResultConstruction.h>
45 #include <ModelAPI_Tools.h>
46 #include <ModelAPI_Validator.h>
47
48 #include <GeomAlgoAPI_MakeShapeList.h>
49 #include <GeomAlgoAPI_Offset.h>
50 #include <GeomAlgoAPI_ShapeTools.h>
51 #include <GeomAlgoAPI_WireBuilder.h>
52
53 #include <GeomAPI_BSpline.h>
54 #include <GeomAPI_Circ.h>
55 #include <GeomAPI_Edge.h>
56 #include <GeomAPI_Ellipse.h>
57 #include <GeomAPI_ShapeExplorer.h>
58 #include <GeomAPI_Wire.h>
59 #include <GeomAPI_WireExplorer.h>
60
61 #include <GeomDataAPI_Point2D.h>
62 #include <GeomDataAPI_Point2DArray.h>
63
64 static const double tolerance = 1.e-7;
65
66 SketchPlugin_Offset::SketchPlugin_Offset()
67 {
68 }
69
70 void SketchPlugin_Offset::initAttributes()
71 {
72   data()->addAttribute(EDGES_ID(), ModelAPI_AttributeRefList::typeId());
73   data()->addAttribute(VALUE_ID(), ModelAPI_AttributeDouble::typeId());
74   data()->addAttribute(REVERSED_ID(), ModelAPI_AttributeBoolean::typeId());
75
76   // store original entities
77   data()->addAttribute(SketchPlugin_Constraint::ENTITY_A(), ModelAPI_AttributeRefList::typeId());
78   // store offset entities
79   data()->addAttribute(SketchPlugin_Constraint::ENTITY_B(), ModelAPI_AttributeRefList::typeId());
80   // store mapping between original entity and index of the corresponding offset entity
81   data()->addAttribute(SketchPlugin_Constraint::ENTITY_C(), ModelAPI_AttributeIntArray::typeId());
82
83   ModelAPI_Session::get()->validators()->
84       registerNotObligatory(getKind(), SketchPlugin_Constraint::ENTITY_A());
85   ModelAPI_Session::get()->validators()->
86       registerNotObligatory(getKind(), SketchPlugin_Constraint::ENTITY_B());
87   ModelAPI_Session::get()->validators()->
88       registerNotObligatory(getKind(), SketchPlugin_Constraint::ENTITY_C());
89 }
90
91 void SketchPlugin_Offset::execute()
92 {
93   SketchPlugin_Sketch* aSketch = sketch();
94   if (!aSketch) return;
95
96   // 1. Sketch plane
97   std::shared_ptr<GeomAPI_Pln> aPlane = aSketch->plane();
98
99   // 2. Offset value
100   AttributeDoublePtr aValueAttr = real(VALUE_ID());
101   if (!aValueAttr->isInitialized()) return;
102   double aValue = aValueAttr->value();
103   if (aValue < tolerance) return;
104
105   // 2.a. Reversed?
106   AttributeBooleanPtr aReversedAttr = boolean(REVERSED_ID());
107   if (!aReversedAttr->isInitialized()) return;
108   if (aReversedAttr->value()) aValue = -aValue; // reverse offset direction
109
110   // 3. List of all selected edges
111   AttributeRefListPtr aSelectedEdges = reflist(EDGES_ID());
112   std::list<ObjectPtr> anEdgesList = aSelectedEdges->list();
113
114   // 4. Put all selected edges in a set to pass them into findWireOneWay() below
115   std::set<FeaturePtr> anEdgesSet;
116   std::list<ObjectPtr>::const_iterator anEdgesIt = anEdgesList.begin();
117   for (; anEdgesIt != anEdgesList.end(); anEdgesIt++) {
118     FeaturePtr aFeature = ModelAPI_Feature::feature(*anEdgesIt);
119     if (aFeature) {
120       anEdgesSet.insert(aFeature);
121     }
122   }
123
124   // Wait all objects being created, then send update events
125   static Events_ID anUpdateEvent = Events_Loop::eventByName(EVENT_OBJECT_UPDATED);
126   bool isUpdateFlushed = Events_Loop::loop()->isFlushed(anUpdateEvent);
127   if (isUpdateFlushed)
128     Events_Loop::loop()->setFlushed(anUpdateEvent, false);
129
130   // 5. Gather wires and make offset for each wire
131   ListOfMakeShape anOffsetAlgos;
132   std::set<FeaturePtr> aProcessedEdgesSet;
133   for (anEdgesIt = anEdgesList.begin(); anEdgesIt != anEdgesList.end(); anEdgesIt++) {
134     FeaturePtr aFeature = ModelAPI_Feature::feature(*anEdgesIt);
135     if (aFeature.get()) {
136       if (aProcessedEdgesSet.find(aFeature) != aProcessedEdgesSet.end())
137         continue;
138
139       // 5.a. End points (if any)
140       std::shared_ptr<GeomDataAPI_Point2D> aStartPoint, anEndPoint;
141       SketchPlugin_SegmentationTools::getFeaturePoints(aFeature, aStartPoint, anEndPoint);
142
143       // 5.b. Find a chain of edges
144       std::list<FeaturePtr> aChain;
145       aChain.push_back(aFeature);
146       if (aStartPoint && anEndPoint) { // not closed edge
147         bool isClosed = findWireOneWay(aFeature, aFeature, aStartPoint, anEdgesSet, aProcessedEdgesSet, aChain, true);
148         if (!isClosed)
149           findWireOneWay(aFeature, aFeature, anEndPoint, anEdgesSet, aProcessedEdgesSet, aChain, false);
150       }
151       aProcessedEdgesSet.insert(aFeature);
152
153       // 5.c. Make wire
154       ListOfShape aTopoChain;
155       std::list<FeaturePtr>::iterator aChainIt = aChain.begin();
156       for (; aChainIt != aChain.end(); ++aChainIt) {
157         FeaturePtr aChainFeature = (*aChainIt);
158         GeomShapePtr aTopoEdge = aChainFeature->lastResult()->shape();
159         if (aTopoEdge->shapeType() == GeomAPI_Shape::EDGE) {
160           aTopoChain.push_back(aTopoEdge);
161         }
162       }
163       std::shared_ptr<GeomAlgoAPI_WireBuilder> aWireBuilder(
164           new GeomAlgoAPI_WireBuilder(aTopoChain));
165
166       GeomShapePtr aWireShape = aWireBuilder->shape();
167       GeomWirePtr aWire (new GeomAPI_Wire (aWireShape));
168
169       // Fix for a problem of offset side change with selection change.
170       // Wire direction is defined by the first selected edge of this wire.
171       double aSign = 1.;
172       if (!aWire->isClosed()) {
173         ListOfShape aModified;
174         // First selected edge of current chain
175         GeomShapePtr aFirstSel = aFeature->lastResult()->shape();
176         aWireBuilder->modified(aFirstSel, aModified);
177         GeomShapePtr aModFS = aModified.front();
178         if (aModFS->orientation() != aFirstSel->orientation())
179           aSign = -1.;
180       }
181
182       // 5.d. Make offset for the wire
183       std::shared_ptr<GeomAlgoAPI_Offset> anOffsetShape(
184           new GeomAlgoAPI_Offset(aPlane, aWireShape, aValue*aSign));
185
186       std::shared_ptr<GeomAlgoAPI_MakeShapeList> aMakeList(new GeomAlgoAPI_MakeShapeList);
187       aMakeList->appendAlgo(aWireBuilder);
188       aMakeList->appendAlgo(anOffsetShape);
189       anOffsetAlgos.push_back(aMakeList);
190     }
191   }
192
193   // 6. Store offset results.
194   //    Create sketch feature for each edge of anOffsetShape, and also store
195   //    created features in CREATED_ID() to remove them on next execute()
196   addToSketch(anOffsetAlgos);
197
198   // send events to update the sub-features by the solver
199   if (isUpdateFlushed)
200     Events_Loop::loop()->setFlushed(anUpdateEvent, true);
201 }
202
203 bool SketchPlugin_Offset::findWireOneWay (const FeaturePtr& theFirstEdge,
204                                           const FeaturePtr& theEdge,
205                                           const std::shared_ptr<GeomDataAPI_Point2D>& theEndPoint,
206                                           std::set<FeaturePtr>& theEdgesSet,
207                                           std::set<FeaturePtr>& theProcessedEdgesSet,
208                                           std::list<FeaturePtr>& theChain,
209                                           const bool isPrepend)
210 {
211   // 1. Find a single edge, coincident to theEndPoint by one of its ends
212   if (!theEndPoint) return false;
213
214   FeaturePtr aNextEdgeFeature;
215   int nbFound = 0;
216
217   std::set<AttributePoint2DPtr> aCoincPoints =
218       SketchPlugin_Tools::findPointsCoincidentToPoint(theEndPoint);
219
220   std::set<AttributePoint2DPtr>::iterator aPointsIt = aCoincPoints.begin();
221   for (; aPointsIt != aCoincPoints.end(); aPointsIt++) {
222     AttributePoint2DPtr aP = (*aPointsIt);
223     FeaturePtr aCoincFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(aP->owner());
224     bool isInSet = (theEdgesSet.find(aCoincFeature) != theEdgesSet.end());
225
226     // Condition 0: not auxiliary
227     if (!isInSet && aCoincFeature->boolean(SketchPlugin_SketchEntity::AUXILIARY_ID())->value())
228       continue;
229
230     // Condition 1: not a point feature
231     if (aCoincFeature->getKind() != SketchPlugin_Point::ID()) {
232       // Condition 2: it is not the current edge
233       if (aCoincFeature != theEdge) {
234         // Condition 3: it is in the set of interest.
235         //              Empty set means all sketch edges.
236         if (isInSet || theEdgesSet.empty()) {
237           // Condition 4: consider only features with two end points
238           std::shared_ptr<GeomDataAPI_Point2D> aP1, aP2;
239           SketchPlugin_SegmentationTools::getFeaturePoints(aCoincFeature, aP1, aP2);
240           if (aP1 && aP2) {
241             // Condition 5: consider only features, that have aP as one of they ends.
242             //              For example, we do not need an arc, coincident to aP by its center.
243             if (theEndPoint->pnt()->isEqual(aP1->pnt()) ||
244                 theEndPoint->pnt()->isEqual(aP2->pnt())) {
245               // Condition 6: only one edge can prolongate the chain. If several, we stop here.
246               nbFound++;
247               if (nbFound > 1)
248                 return false;
249
250               // One found
251               aNextEdgeFeature = aCoincFeature;
252             }
253           }
254         }
255       }
256     }
257   }
258
259   // Only one edge can prolongate the chain. If several or none, we stop here.
260   if (nbFound != 1)
261     return false;
262
263   // 2. So, we have the single edge, that prolongate the chain
264
265   // Condition 7: if we reached the very first edge of the chain
266   if (aNextEdgeFeature == theFirstEdge)
267     // Closed chain found
268     return true;
269
270   // 3. Add the found edge to the chain
271   if (isPrepend)
272     theChain.push_front(aNextEdgeFeature);
273   else
274     theChain.push_back(aNextEdgeFeature);
275   theProcessedEdgesSet.insert(aNextEdgeFeature);
276
277   // 4. Which end of aNextEdgeFeature we need to proceed
278   std::shared_ptr<GeomDataAPI_Point2D> aP1, aP2;
279   SketchPlugin_SegmentationTools::getFeaturePoints(aNextEdgeFeature, aP1, aP2);
280   if (aP2->pnt()->isEqual(theEndPoint->pnt())) {
281     // reversed
282     aP2 = aP1;
283   }
284
285   // 5. Continue gathering the chain (recursive)
286   return findWireOneWay (theFirstEdge, aNextEdgeFeature, aP2, theEdgesSet, theProcessedEdgesSet, theChain, isPrepend);
287 }
288
289 static void setRefListValue(AttributeRefListPtr theList, int theListSize,
290                             ObjectPtr theValue, int theIndex)
291 {
292   if (theIndex < theListSize) {
293     ObjectPtr aCur = theList->object(theIndex);
294     if (aCur != theValue)
295       theList->substitute(aCur, theValue);
296   }
297   else
298     theList->append(theValue);
299 }
300
301 // Reorder shapes according to the wire's order
302 static void reorderShapes(ListOfShape& theShapes, GeomShapePtr theWire)
303 {
304   std::set<GeomShapePtr, GeomAPI_Shape::Comparator> aShapes;
305   aShapes.insert(theShapes.begin(), theShapes.end());
306   theShapes.clear();
307
308   GeomWirePtr aWire(new GeomAPI_Wire(theWire));
309   GeomAPI_WireExplorer anExp(aWire);
310   for (; anExp.more(); anExp.next()) {
311     GeomShapePtr aCurEdge = anExp.current();
312     auto aFound = aShapes.find(aCurEdge);
313     if (aFound != aShapes.end()) {
314       theShapes.push_back(aCurEdge);
315       aShapes.erase(aFound);
316     }
317   }
318 }
319
320 static void removeLastFromIndex(AttributeRefListPtr theList, int theListSize, int& theLastIndex)
321 {
322   if (theLastIndex < theListSize) {
323     std::set<int> anIndicesToRemove;
324     for (; theLastIndex < theListSize; ++theLastIndex)
325       anIndicesToRemove.insert(theLastIndex);
326     theList->remove(anIndicesToRemove);
327   }
328 }
329
330 void SketchPlugin_Offset::addToSketch(const ListOfMakeShape& theOffsetAlgos)
331 {
332   AttributeRefListPtr aSelectedRefList = reflist(EDGES_ID());
333   AttributeRefListPtr aBaseRefList = reflist(ENTITY_A());
334   AttributeRefListPtr anOffsetRefList = reflist(ENTITY_B());
335   AttributeIntArrayPtr anOffsetToBaseMap = intArray(ENTITY_C());
336
337   // compare the list of selected edges and the previously stored,
338   // and store maping between them
339   std::map<ObjectPtr, std::list<ObjectPtr> > aMapExistent;
340   std::list<ObjectPtr> anObjectsToRemove;
341   std::list<ObjectPtr> aSelectedList = aSelectedRefList->list();
342   for (std::list<ObjectPtr>::iterator aSIt = aSelectedList.begin();
343        aSIt != aSelectedList.end(); ++aSIt) {
344     aMapExistent[*aSIt] = std::list<ObjectPtr>();
345   }
346   for (int anIndex = 0, aSize = anOffsetRefList->size(); anIndex < aSize; ++anIndex) {
347     ObjectPtr aCurrent = anOffsetRefList->object(anIndex);
348     int aBaseIndex = anOffsetToBaseMap->value(anIndex);
349     if (aBaseIndex >= 0) {
350       ObjectPtr aBaseObj = aBaseRefList->object(aBaseIndex);
351       std::map<ObjectPtr, std::list<ObjectPtr> >::iterator aFound = aMapExistent.find(aBaseObj);
352       if (aFound != aMapExistent.end())
353         aFound->second.push_back(aCurrent);
354       else
355         anObjectsToRemove.push_back(aCurrent);
356     }
357     else
358       anObjectsToRemove.push_back(aCurrent);
359   }
360
361   // update lists of base shapes and of offset shapes
362   int aBaseListSize = aBaseRefList->size();
363   int anOffsetListSize = anOffsetRefList->size();
364   int aBaseListIndex = 0, anOffsetListIndex = 0;
365   std::list<int> anOffsetBaseBackRefs;
366   std::set<GeomShapePtr, GeomAPI_Shape::ComparatorWithOri> aProcessedOffsets;
367   for (std::list<ObjectPtr>::iterator aSIt = aSelectedList.begin();
368        aSIt != aSelectedList.end(); ++aSIt) {
369     // find an offseted edge
370     FeaturePtr aBaseFeature = ModelAPI_Feature::feature(*aSIt);
371     GeomShapePtr aBaseShape = aBaseFeature->lastResult()->shape();
372     ListOfShape aNewShapes;
373     for (ListOfMakeShape::const_iterator anAlgoIt = theOffsetAlgos.begin();
374          anAlgoIt != theOffsetAlgos.end(); ++anAlgoIt) {
375       (*anAlgoIt)->generated(aBaseShape, aNewShapes);
376       if (!aNewShapes.empty()) {
377         reorderShapes(aNewShapes, (*anAlgoIt)->shape());
378         break;
379       }
380     }
381
382     // store base feature
383     setRefListValue(aBaseRefList, aBaseListSize, *aSIt, aBaseListIndex);
384
385     // create or update an offseted feature
386     const std::list<ObjectPtr>& anImages = aMapExistent[*aSIt];
387     std::list<ObjectPtr>::const_iterator anImgIt = anImages.begin();
388     for (ListOfShape::iterator aNewIt = aNewShapes.begin(); aNewIt != aNewShapes.end(); ++aNewIt) {
389       FeaturePtr aNewFeature;
390       if (anImgIt != anImages.end())
391         aNewFeature = ModelAPI_Feature::feature(*anImgIt++);
392       updateExistentOrCreateNew(*aNewIt, aNewFeature, anObjectsToRemove);
393       aProcessedOffsets.insert(*aNewIt);
394
395       // store an offseted feature
396       setRefListValue(anOffsetRefList, anOffsetListSize, aNewFeature, anOffsetListIndex);
397
398       anOffsetBaseBackRefs.push_back(aBaseListIndex);
399       ++anOffsetListIndex;
400     }
401     ++aBaseListIndex;
402     anObjectsToRemove.insert(anObjectsToRemove.end(), anImgIt, anImages.end());
403   }
404   // create arcs generated from vertices
405   for (ListOfMakeShape::const_iterator anAlgoIt = theOffsetAlgos.begin();
406        anAlgoIt != theOffsetAlgos.end(); ++anAlgoIt) {
407     GeomShapePtr aCurWire = (*anAlgoIt)->shape();
408     GeomAPI_ShapeExplorer anExp(aCurWire, GeomAPI_Shape::EDGE);
409     for (; anExp.more(); anExp.next()) {
410       GeomShapePtr aCurEdge = anExp.current();
411       if (aProcessedOffsets.find(aCurEdge) == aProcessedOffsets.end()) {
412         FeaturePtr aNewFeature;
413         updateExistentOrCreateNew(aCurEdge, aNewFeature, anObjectsToRemove);
414         aProcessedOffsets.insert(aCurEdge);
415
416         // store an offseted feature
417         setRefListValue(anOffsetRefList, anOffsetListSize, aNewFeature, anOffsetListIndex);
418
419         anOffsetBaseBackRefs.push_back(-1);
420         ++anOffsetListIndex;
421       }
422     }
423   }
424
425   removeLastFromIndex(aBaseRefList, aBaseListSize, aBaseListIndex);
426   removeLastFromIndex(anOffsetRefList, anOffsetListSize, anOffsetListIndex);
427
428   anOffsetToBaseMap->setSize((int)anOffsetBaseBackRefs.size(), false);
429   int anIndex = 0;
430   for (std::list<int>::iterator anIt = anOffsetBaseBackRefs.begin();
431        anIt != anOffsetBaseBackRefs.end(); ++anIt) {
432     anOffsetToBaseMap->setValue(anIndex++, *anIt, false);
433   }
434
435   // remove unused objects
436   std::set<FeaturePtr> aSet;
437   for (std::list<ObjectPtr>::iterator anIt = anObjectsToRemove.begin();
438        anIt != anObjectsToRemove.end(); ++anIt) {
439     FeaturePtr aFeature = ModelAPI_Feature::feature(*anIt);
440     if (aFeature)
441       aSet.insert(aFeature);
442   }
443   ModelAPI_Tools::removeFeaturesAndReferences(aSet);
444 }
445
446 static void findOrCreateFeatureByKind(SketchPlugin_Sketch* theSketch,
447                                       const std::string& theFeatureKind,
448                                       FeaturePtr& theFeature,
449                                       std::list<ObjectPtr>& thePoolOfFeatures)
450 {
451   if (theFeature) {
452     // check the feature type is the same as required
453     if (theFeature->getKind() != theFeatureKind) {
454       // return feature to the pool and try to find the most appropriate
455       thePoolOfFeatures.push_back(theFeature);
456       theFeature = FeaturePtr();
457     }
458   }
459   if (!theFeature) {
460     // try to find appropriate feature in the pool
461     for (std::list<ObjectPtr>::iterator it = thePoolOfFeatures.begin();
462          it != thePoolOfFeatures.end(); ++it) {
463       FeaturePtr aCurFeature = ModelAPI_Feature::feature(*it);
464       if (aCurFeature->getKind() == theFeatureKind) {
465         theFeature = aCurFeature;
466         thePoolOfFeatures.erase(it);
467         break;
468       }
469     }
470     // feature not found, create new
471     if (!theFeature)
472       theFeature = theSketch->addFeature(theFeatureKind);
473   }
474 }
475
476 void SketchPlugin_Offset::updateExistentOrCreateNew(const GeomShapePtr& theShape,
477                                                     FeaturePtr& theFeature,
478                                                     std::list<ObjectPtr>& thePoolOfFeatures)
479 {
480   if (theShape->shapeType() != GeomAPI_Shape::EDGE)
481     return;
482
483   std::shared_ptr<GeomAPI_Edge> aResEdge(new GeomAPI_Edge(theShape));
484
485   std::shared_ptr<GeomAPI_Pnt2d> aFP, aLP;
486   std::shared_ptr<GeomAPI_Pnt> aFP3d = aResEdge->firstPoint();
487   std::shared_ptr<GeomAPI_Pnt> aLP3d = aResEdge->lastPoint();
488   if (aFP3d && aLP3d) {
489     aFP = sketch()->to2D(aFP3d);
490     aLP = sketch()->to2D(aLP3d);
491   }
492
493   if (aResEdge->isLine()) {
494     findOrCreateFeatureByKind(sketch(), SketchPlugin_Line::ID(), theFeature, thePoolOfFeatures);
495
496     std::dynamic_pointer_cast<GeomDataAPI_Point2D>
497       (theFeature->attribute(SketchPlugin_Line::START_ID()))->setValue(aFP);
498     std::dynamic_pointer_cast<GeomDataAPI_Point2D>
499       (theFeature->attribute(SketchPlugin_Line::END_ID()))->setValue(aLP);
500   }
501   else if (aResEdge->isArc()) {
502     std::shared_ptr<GeomAPI_Circ> aCircEdge = aResEdge->circle();
503     std::shared_ptr<GeomAPI_Pnt> aCP3d = aCircEdge->center();
504     std::shared_ptr<GeomAPI_Pnt2d> aCP = sketch()->to2D(aCP3d);
505
506     findOrCreateFeatureByKind(sketch(), SketchPlugin_Arc::ID(), theFeature, thePoolOfFeatures);
507
508     GeomDirPtr aCircNormal = aCircEdge->normal();
509     GeomDirPtr aSketchNormal = sketch()->coordinatePlane()->normal();
510     if (aSketchNormal->dot(aCircNormal) < -tolerance)
511       std::swap(aFP, aLP);
512
513     bool aWasBlocked = theFeature->data()->blockSendAttributeUpdated(true);
514     std::dynamic_pointer_cast<GeomDataAPI_Point2D>
515       (theFeature->attribute(SketchPlugin_Arc::END_ID()))->setValue(aLP);
516     std::dynamic_pointer_cast<GeomDataAPI_Point2D>
517       (theFeature->attribute(SketchPlugin_Arc::START_ID()))->setValue(aFP);
518     std::dynamic_pointer_cast<GeomDataAPI_Point2D>
519       (theFeature->attribute(SketchPlugin_Arc::CENTER_ID()))->setValue(aCP);
520     theFeature->data()->blockSendAttributeUpdated(aWasBlocked);
521   }
522   else if (aResEdge->isCircle()) {
523     std::shared_ptr<GeomAPI_Circ> aCircEdge = aResEdge->circle();
524     std::shared_ptr<GeomAPI_Pnt> aCP3d = aCircEdge->center();
525     std::shared_ptr<GeomAPI_Pnt2d> aCP = sketch()->to2D(aCP3d);
526
527     findOrCreateFeatureByKind(sketch(), SketchPlugin_Circle::ID(), theFeature, thePoolOfFeatures);
528
529     std::dynamic_pointer_cast<GeomDataAPI_Point2D>
530       (theFeature->attribute(SketchPlugin_Circle::CENTER_ID()))->setValue(aCP);
531     theFeature->real(SketchPlugin_Circle::RADIUS_ID())->setValue(aCircEdge->radius());
532   }
533   else if (aResEdge->isEllipse()) {
534     std::shared_ptr<GeomAPI_Ellipse> anEllipseEdge = aResEdge->ellipse();
535
536     GeomPointPtr aCP3d = anEllipseEdge->center();
537     GeomPnt2dPtr aCP = sketch()->to2D(aCP3d);
538
539     GeomPointPtr aFocus3d = anEllipseEdge->firstFocus();
540     GeomPnt2dPtr aFocus = sketch()->to2D(aFocus3d);
541
542     if (aFP3d && aLP3d) {
543       // Elliptic arc
544       findOrCreateFeatureByKind(sketch(), SketchPlugin_EllipticArc::ID(),
545                                 theFeature, thePoolOfFeatures);
546
547       bool aWasBlocked = theFeature->data()->blockSendAttributeUpdated(true);
548       std::dynamic_pointer_cast<GeomDataAPI_Point2D>
549         (theFeature->attribute(SketchPlugin_EllipticArc::CENTER_ID()))->setValue(aCP);
550       std::dynamic_pointer_cast<GeomDataAPI_Point2D>
551         (theFeature->attribute(SketchPlugin_EllipticArc::FIRST_FOCUS_ID()))->setValue(aFocus);
552       std::dynamic_pointer_cast<GeomDataAPI_Point2D>
553         (theFeature->attribute(SketchPlugin_EllipticArc::START_POINT_ID()))->setValue(aFP);
554       std::dynamic_pointer_cast<GeomDataAPI_Point2D>
555         (theFeature->attribute(SketchPlugin_EllipticArc::END_POINT_ID()))->setValue(aLP);
556       theFeature->data()->blockSendAttributeUpdated(aWasBlocked);
557     }
558     else {
559       // Ellipse
560       findOrCreateFeatureByKind(sketch(), SketchPlugin_Ellipse::ID(),
561                                 theFeature, thePoolOfFeatures);
562
563       std::dynamic_pointer_cast<GeomDataAPI_Point2D>
564         (theFeature->attribute(SketchPlugin_Ellipse::CENTER_ID()))->setValue(aCP);
565       std::dynamic_pointer_cast<GeomDataAPI_Point2D>
566         (theFeature->attribute(SketchPlugin_Ellipse::FIRST_FOCUS_ID()))->setValue(aFocus);
567       theFeature->real(SketchPlugin_Ellipse::MINOR_RADIUS_ID())->setValue(
568         anEllipseEdge->minorRadius());
569     }
570   }
571   else {
572     // convert to b-spline
573     mkBSpline(theFeature, aResEdge, thePoolOfFeatures);
574   }
575
576   if (theFeature.get()) {
577     theFeature->boolean(SketchPlugin_SketchEntity::COPY_ID())->setValue(true);
578     theFeature->execute();
579
580     static Events_ID aRedisplayEvent = Events_Loop::eventByName(EVENT_OBJECT_TO_REDISPLAY);
581     ModelAPI_EventCreator::get()->sendUpdated(theFeature, aRedisplayEvent);
582     const std::list<ResultPtr>& aResults = theFeature->results();
583     for (std::list<ResultPtr>::const_iterator anIt = aResults.begin();
584          anIt != aResults.end(); ++anIt)
585       ModelAPI_EventCreator::get()->sendUpdated(*anIt, aRedisplayEvent);
586   }
587 }
588
589 void SketchPlugin_Offset::mkBSpline (FeaturePtr& theResult,
590                                      const GeomEdgePtr& theEdge,
591                                      std::list<ObjectPtr>& thePoolOfFeatures)
592 {
593   GeomCurvePtr aCurve (new GeomAPI_Curve (theEdge));
594   // Forced conversion to b-spline, if aCurve is not b-spline
595   GeomBSplinePtr aBSpline = GeomAPI_BSpline::convertToBSpline(aCurve);
596
597   const std::string& aBSplineKind = aBSpline->isPeriodic() ? SketchPlugin_BSplinePeriodic::ID()
598                                                            : SketchPlugin_BSpline::ID();
599   findOrCreateFeatureByKind(sketch(), aBSplineKind, theResult, thePoolOfFeatures);
600
601   theResult->integer(SketchPlugin_BSpline::DEGREE_ID())->setValue(aBSpline->degree());
602
603   AttributePoint2DArrayPtr aPolesAttr = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>
604     (theResult->attribute(SketchPlugin_BSpline::POLES_ID()));
605   std::list<GeomPointPtr> aPoles = aBSpline->poles();
606   aPolesAttr->setSize((int)aPoles.size());
607   std::list<GeomPointPtr>::iterator anIt = aPoles.begin();
608   for (int anIndex = 0; anIt != aPoles.end(); ++anIt, ++anIndex) {
609     GeomPnt2dPtr aPoleInSketch = sketch()->to2D(*anIt);
610     aPolesAttr->setPnt(anIndex, aPoleInSketch);
611   }
612
613   AttributeDoubleArrayPtr aWeightsAttr =
614       theResult->data()->realArray(SketchPlugin_BSpline::WEIGHTS_ID());
615   std::list<double> aWeights = aBSpline->weights();
616   if (aWeights.empty()) { // rational B-spline
617     int aSize = (int)aPoles.size();
618     aWeightsAttr->setSize(aSize);
619     for (int anIndex = 0; anIndex < aSize; ++anIndex)
620       aWeightsAttr->setValue(anIndex, 1.0);
621   }
622   else { // non-rational B-spline
623     aWeightsAttr->setSize((int)aWeights.size());
624     std::list<double>::iterator anIt = aWeights.begin();
625     for (int anIndex = 0; anIt != aWeights.end(); ++anIt, ++anIndex)
626       aWeightsAttr->setValue(anIndex, *anIt);
627   }
628
629   AttributeDoubleArrayPtr aKnotsAttr =
630       theResult->data()->realArray(SketchPlugin_BSpline::KNOTS_ID());
631   std::list<double> aKnots = aBSpline->knots();
632   int aSize = (int)aKnots.size();
633   aKnotsAttr->setSize(aSize);
634   std::list<double>::iterator aKIt = aKnots.begin();
635   for (int index = 0; index < aSize; ++index, ++aKIt)
636     aKnotsAttr->setValue(index, *aKIt);
637
638   AttributeIntArrayPtr aMultsAttr =
639       theResult->data()->intArray(SketchPlugin_BSpline::MULTS_ID());
640   std::list<int> aMultiplicities = aBSpline->mults();
641   aSize = (int)aMultiplicities.size();
642   aMultsAttr->setSize(aSize);
643   std::list<int>::iterator aMIt = aMultiplicities.begin();
644   for (int index = 0; index < aSize; ++index, ++aMIt)
645     aMultsAttr->setValue(index, *aMIt);
646 }
647
648 void SketchPlugin_Offset::attributeChanged(const std::string& theID)
649 {
650 ////  if (theID == EDGES_ID())
651 ////    removeCreated();
652 }
653
654 bool SketchPlugin_Offset::customAction(const std::string& theActionId)
655 {
656   bool isOk = false;
657   if (theActionId == ADD_WIRE_ACTION_ID()) {
658     isOk = findWires();
659   }
660   else {
661     std::string aMsg = "Error: Feature \"%1\" does not support action \"%2\".";
662     Events_InfoMessage("SketchPlugin_Offset", aMsg).arg(getKind()).arg(theActionId).send();
663   }
664   return isOk;
665 }
666
667 bool SketchPlugin_Offset::findWires()
668 {
669   AttributeRefListPtr aSelectedEdges = reflist(EDGES_ID());
670   std::list<ObjectPtr> anEdgesList = aSelectedEdges->list();
671
672   // Empty set
673   std::set<FeaturePtr> anEdgesSet;
674
675   // Processed set
676   std::set<FeaturePtr> aProcessedEdgesSet;
677
678   // Put all selected edges in a set to avoid adding them in reflist(EDGES_ID())
679   std::set<FeaturePtr> aSelectedSet;
680   std::list<ObjectPtr>::const_iterator anEdgesIt = anEdgesList.begin();
681   for (; anEdgesIt != anEdgesList.end(); anEdgesIt++) {
682     FeaturePtr aFeature = ModelAPI_Feature::feature(*anEdgesIt);
683     if (aFeature) {
684       aSelectedSet.insert(aFeature);
685     }
686   }
687
688   bool aWasBlocked = data()->blockSendAttributeUpdated(true);
689
690   // Gather chains of edges
691   for (anEdgesIt = anEdgesList.begin(); anEdgesIt != anEdgesList.end(); anEdgesIt++) {
692     FeaturePtr aFeature = ModelAPI_Feature::feature(*anEdgesIt);
693     if (aFeature.get()) {
694       if (aProcessedEdgesSet.find(aFeature) != aProcessedEdgesSet.end())
695         continue;
696       aProcessedEdgesSet.insert(aFeature);
697
698       // End points (if any)
699       std::shared_ptr<GeomDataAPI_Point2D> aStartPoint, anEndPoint;
700       SketchPlugin_SegmentationTools::getFeaturePoints(aFeature, aStartPoint, anEndPoint);
701
702       std::list<FeaturePtr> aChain;
703       aChain.push_back(aFeature);
704       bool isClosed = findWireOneWay(aFeature, aFeature, aStartPoint, anEdgesSet, aProcessedEdgesSet, aChain, true);
705       if (!isClosed)
706         findWireOneWay(aFeature, aFeature, anEndPoint, anEdgesSet, aProcessedEdgesSet, aChain, false);
707
708       std::list<FeaturePtr>::iterator aChainIt = aChain.begin();
709       for (; aChainIt != aChain.end(); ++aChainIt) {
710         FeaturePtr aChainFeature = (*aChainIt);
711         if (aSelectedSet.find(aChainFeature) == aSelectedSet.end()) {
712           aSelectedEdges->append(aChainFeature->lastResult());
713         }
714       }
715     }
716   }
717   // TODO: hilight selection in the viewer
718
719   data()->blockSendAttributeUpdated(aWasBlocked);
720   return true;
721 }
722
723
724 AISObjectPtr SketchPlugin_Offset::getAISObject(AISObjectPtr thePrevious)
725 {
726   if (!sketch())
727     return thePrevious;
728
729   AISObjectPtr anAIS = SketcherPrs_Factory::offsetObject(this, sketch(),
730     thePrevious);
731   return anAIS;
732 }