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