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