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