Salome HOME
be0cb6cdacc6296441aa758e2585da9a627f31ea
[modules/shaper.git] / src / SketchPlugin / SketchPlugin_Offset.cpp
1 // Copyright (C) 2020-2023  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_AttributeString.h>
44 #include <ModelAPI_Events.h>
45 #include <ModelAPI_ResultConstruction.h>
46 #include <ModelAPI_Tools.h>
47 #include <ModelAPI_Validator.h>
48
49 #include <GeomAlgoAPI_MakeShapeList.h>
50 #include <GeomAlgoAPI_MapShapesAndAncestors.h>
51 #include <GeomAlgoAPI_Offset.h>
52 #include <GeomAlgoAPI_ShapeTools.h>
53 #include <GeomAlgoAPI_WireBuilder.h>
54 #include <GeomAlgoAPI_Fillet1D.h>
55 #include <GeomAlgoAPI_Tools.h>
56
57 #include <GeomAPI_BSpline.h>
58 #include <GeomAPI_Circ.h>
59 #include <GeomAPI_Edge.h>
60 #include <GeomAPI_Ellipse.h>
61 #include <GeomAPI_ShapeExplorer.h>
62 #include <GeomAPI_Wire.h>
63 #include <GeomAPI_WireExplorer.h>
64
65 #include <GeomDataAPI_Point2D.h>
66 #include <GeomDataAPI_Point2DArray.h>
67
68 #include <math.h>
69
70 static const double tolerance = 1.e-7;
71
72 SketchPlugin_Offset::SketchPlugin_Offset()
73 {
74 }
75
76 void SketchPlugin_Offset::initAttributes()
77 {
78   data()->addAttribute(EDGES_ID(), ModelAPI_AttributeRefList::typeId());
79   data()->addAttribute(VALUE_ID(), ModelAPI_AttributeDouble::typeId());
80   data()->addAttribute(REVERSED_ID(), ModelAPI_AttributeBoolean::typeId());
81
82   // store original entities
83   data()->addAttribute(SketchPlugin_Constraint::ENTITY_A(), ModelAPI_AttributeRefList::typeId());
84   // store offset entities
85   data()->addAttribute(SketchPlugin_Constraint::ENTITY_B(), ModelAPI_AttributeRefList::typeId());
86   // store mapping between original entity and index of the corresponding offset entity
87   data()->addAttribute(SketchPlugin_Constraint::ENTITY_C(), ModelAPI_AttributeIntArray::typeId());
88
89   ModelAPI_Session::get()->validators()->
90       registerNotObligatory(getKind(), SketchPlugin_Constraint::ENTITY_A());
91   ModelAPI_Session::get()->validators()->
92       registerNotObligatory(getKind(), SketchPlugin_Constraint::ENTITY_B());
93   ModelAPI_Session::get()->validators()->
94       registerNotObligatory(getKind(), SketchPlugin_Constraint::ENTITY_C());
95
96   AttributeStringPtr aJointAttr = std::dynamic_pointer_cast<ModelAPI_AttributeString>
97     (data()->addAttribute(JOINT_ID(), ModelAPI_AttributeString::typeId()));
98   if (!aJointAttr->isInitialized())
99     aJointAttr->setValue(JOINT_KEEP_DISTANCE());
100 }
101
102 void SketchPlugin_Offset::execute()
103 {
104   SketchPlugin_Sketch* aSketch = sketch();
105   if (!aSketch) return;
106
107   // 0. Joint type
108   AttributeStringPtr aJointAttr = string(JOINT_ID());
109   std::string aType = JOINT_KEEP_DISTANCE();
110   if (aJointAttr->isInitialized())
111     aType = aJointAttr->value();
112
113   GeomAlgoAPI_OffsetJoint aJoint;
114   if (aType == JOINT_ARCS())
115     aJoint = GeomAlgoAPI_OffsetJoint::Arcs;
116   else if (aType == JOINT_LINES())
117     aJoint = GeomAlgoAPI_OffsetJoint::Lines;
118   else // Default mode
119     aJoint = GeomAlgoAPI_OffsetJoint::KeepDistance;
120
121   // 1. Sketch plane
122   std::shared_ptr<GeomAPI_Pln> aPlane = aSketch->plane();
123
124   // 2. Offset value
125   AttributeDoublePtr aValueAttr = real(VALUE_ID());
126   if (!aValueAttr->isInitialized()) return;
127   double aValue = aValueAttr->value();
128   if (aValue < tolerance) return;
129
130   // 2.a. Reversed?
131   AttributeBooleanPtr aReversedAttr = boolean(REVERSED_ID());
132   if (!aReversedAttr->isInitialized()) return;
133   if (aReversedAttr->value()) aValue = -aValue; // reverse offset direction
134
135   // 3. List of all selected edges
136   AttributeRefListPtr aSelectedEdges = reflist(EDGES_ID());
137   std::list<ObjectPtr> anEdgesList = aSelectedEdges->list();
138
139   // 4. Put all selected edges in a set to pass them into findWireOneWay() below
140   std::set<FeaturePtr> anEdgesSet;
141   std::list<ObjectPtr>::const_iterator anEdgesIt = anEdgesList.begin();
142   for (; anEdgesIt != anEdgesList.end(); anEdgesIt++) {
143     FeaturePtr aFeature = ModelAPI_Feature::feature(*anEdgesIt);
144     if (aFeature) {
145       anEdgesSet.insert(aFeature);
146     }
147   }
148
149   // Wait all objects being created, then send update events
150   static Events_ID anUpdateEvent = Events_Loop::eventByName(EVENT_OBJECT_UPDATED);
151   bool isUpdateFlushed = Events_Loop::loop()->isFlushed(anUpdateEvent);
152   if (isUpdateFlushed)
153     Events_Loop::loop()->setFlushed(anUpdateEvent, false);
154
155   // Save the current feature of the document, because new features may appear while executing.
156   // In this case, they will become current. But if the number of copies is updated from outside
157   // of sketch (e.g. by parameter change), the history line should not hold in sketch.
158   keepCurrentFeature();
159
160   // 5. Gather wires and make offset for each wire
161   ListOfMakeShape anOffsetAlgos;
162   std::set<FeaturePtr> aProcessedEdgesSet;
163   for (anEdgesIt = anEdgesList.begin(); anEdgesIt != anEdgesList.end(); anEdgesIt++) {
164     FeaturePtr aFeature = ModelAPI_Feature::feature(*anEdgesIt);
165     if (aFeature.get()) {
166       if (aProcessedEdgesSet.find(aFeature) != aProcessedEdgesSet.end())
167         continue;
168
169       // 5.a. End points (if any)
170       std::shared_ptr<GeomDataAPI_Point2D> aStartPoint, anEndPoint;
171       SketchPlugin_SegmentationTools::getFeaturePoints(aFeature, aStartPoint, anEndPoint);
172
173       // 5.b. Find a chain of edges
174       std::list<FeaturePtr> aChain;
175       aChain.push_back(aFeature);
176       bool isClosed = !(aStartPoint && anEndPoint);  // not closed edge
177       if (!isClosed) {
178         isClosed = findWireOneWay(aFeature, aFeature, aStartPoint, anEdgesSet,
179                                   aProcessedEdgesSet, aChain, true);
180         if (!isClosed) {
181           isClosed = findWireOneWay(aFeature, aFeature, anEndPoint, anEdgesSet,
182                                     aProcessedEdgesSet, aChain, false);
183         }
184       }
185       aProcessedEdgesSet.insert(aFeature);
186
187       // 5.c. Make wire
188       ListOfShape aTopoChain;
189       std::list<FeaturePtr>::iterator aChainIt = aChain.begin();
190       for (; aChainIt != aChain.end(); ++aChainIt) {
191         FeaturePtr aChainFeature = (*aChainIt);
192         GeomShapePtr aTopoEdge = aChainFeature->lastResult()->shape();
193         if (aTopoEdge->shapeType() == GeomAPI_Shape::EDGE) {
194           aTopoChain.push_back(aTopoEdge);
195         }
196       }
197       std::shared_ptr<GeomAlgoAPI_WireBuilder> aWireBuilder(
198           new GeomAlgoAPI_WireBuilder(aTopoChain, !isClosed));
199
200       GeomShapePtr aWireShape = aWireBuilder->shape();
201       GeomWirePtr aWire (new GeomAPI_Wire (aWireShape));
202
203       // Fix for a problem of offset side change with selection change.
204       // Wire direction is defined by the first selected edge of this wire.
205       double aSign = 1.;
206       if (!aWire->isClosed()) {
207         ListOfShape aModified;
208         // First selected edge of current chain
209         GeomShapePtr aFirstSel = aFeature->lastResult()->shape();
210         aWireBuilder->modified(aFirstSel, aModified);
211         GeomShapePtr aModFS = aModified.front();
212         if (aModFS->orientation() != aFirstSel->orientation())
213           aSign = -1.;
214       }
215
216       // 5.d. Make offset for the wire
217       std::shared_ptr<GeomAlgoAPI_Offset> anOffsetShape
218         (new GeomAlgoAPI_Offset(aPlane, aWireShape, aValue*aSign, aJoint));
219
220       if (anOffsetShape->isDone()) {
221         if (aJoint == GeomAlgoAPI_OffsetJoint::Arcs) {
222           // For Arcs joint make fillet at all straight edges intersections
223           // of the wire, resulting from GeomAlgoAPI_Offset algorithm
224           makeFillet(fabs(aValue), aWireBuilder, anOffsetShape, anOffsetAlgos);
225         }
226         else {
227           std::shared_ptr<GeomAlgoAPI_MakeShapeList> aMakeList (new GeomAlgoAPI_MakeShapeList);
228           aMakeList->appendAlgo(aWireBuilder);
229           aMakeList->appendAlgo(anOffsetShape);
230           anOffsetAlgos.push_back(aMakeList);
231         }
232       }
233       else {
234         setError("Offset algorithm failed");
235       }
236     }
237   }
238
239   // 6. Store offset results.
240   //    Create sketch feature for each edge of anOffsetShape, and also store
241   //    created features in CREATED_ID() to remove them on next execute()
242   addToSketch(anOffsetAlgos);
243
244   restoreCurrentFeature();
245
246   // send events to update the sub-features by the solver
247   if (isUpdateFlushed)
248     Events_Loop::loop()->setFlushed(anUpdateEvent, true);
249 }
250
251 bool SketchPlugin_Offset::findWireOneWay (const FeaturePtr& theFirstEdge,
252                                           const FeaturePtr& theEdge,
253                                           const std::shared_ptr<GeomDataAPI_Point2D>& theEndPoint,
254                                           std::set<FeaturePtr>& theEdgesSet,
255                                           std::set<FeaturePtr>& theProcessedEdgesSet,
256                                           std::list<FeaturePtr>& theChain,
257                                           const bool isPrepend)
258 {
259   // 1. Find a single edge, coincident to theEndPoint by one of its ends
260   if (!theEndPoint) return false;
261
262   FeaturePtr aNextEdgeFeature;
263   int nbFound = 0;
264
265   std::set<AttributePoint2DPtr> aCoincPoints =
266       SketchPlugin_Tools::findPointsCoincidentToPoint(theEndPoint);
267
268   std::set<AttributePoint2DPtr>::iterator aPointsIt = aCoincPoints.begin();
269   for (; aPointsIt != aCoincPoints.end(); aPointsIt++) {
270     AttributePoint2DPtr aP = (*aPointsIt);
271     FeaturePtr aCoincFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(aP->owner());
272     bool isInSet = (theEdgesSet.find(aCoincFeature) != theEdgesSet.end());
273
274     // Condition 0: not auxiliary
275     if (!isInSet && aCoincFeature->boolean(SketchPlugin_SketchEntity::AUXILIARY_ID())->value())
276       continue;
277
278     // Condition 1: not a point feature
279     if (aCoincFeature->getKind() != SketchPlugin_Point::ID()) {
280       // Condition 2: it is not the current edge
281       if (aCoincFeature != theEdge) {
282         // Condition 3: it is in the set of interest.
283         //              Empty set means all sketch edges.
284         if (isInSet || theEdgesSet.empty()) {
285           // Condition 4: consider only features with two end points
286           std::shared_ptr<GeomDataAPI_Point2D> aP1, aP2;
287           SketchPlugin_SegmentationTools::getFeaturePoints(aCoincFeature, aP1, aP2);
288           if (aP1 && aP2) {
289             // Condition 5: consider only features, that have aP as one of they ends.
290             //              For example, we do not need an arc, coincident to aP by its center.
291             if (theEndPoint->pnt()->isEqual(aP1->pnt()) ||
292                 theEndPoint->pnt()->isEqual(aP2->pnt())) {
293               // Condition 6: only one edge can prolongate the chain. If several, we stop here.
294               nbFound++;
295               if (nbFound > 1)
296                 return false;
297
298               // One found
299               aNextEdgeFeature = aCoincFeature;
300             }
301           }
302         }
303       }
304     }
305   }
306
307   // Only one edge can prolongate the chain. If several or none, we stop here.
308   if (nbFound != 1)
309     return false;
310
311   // 2. So, we have the single edge, that prolongate the chain
312
313   // Condition 7: if we reached the very first edge of the chain
314   if (aNextEdgeFeature == theFirstEdge)
315     // Closed chain found
316     return true;
317
318   // 3. Add the found edge to the chain
319   if (isPrepend)
320     theChain.push_front(aNextEdgeFeature);
321   else
322     theChain.push_back(aNextEdgeFeature);
323   theProcessedEdgesSet.insert(aNextEdgeFeature);
324
325   // 4. Which end of aNextEdgeFeature we need to proceed
326   std::shared_ptr<GeomDataAPI_Point2D> aP1, aP2;
327   SketchPlugin_SegmentationTools::getFeaturePoints(aNextEdgeFeature, aP1, aP2);
328   if (aP2->pnt()->isEqual(theEndPoint->pnt())) {
329     // reversed
330     aP2 = aP1;
331   }
332
333   // 5. Continue gathering the chain (recursive)
334   return findWireOneWay (theFirstEdge, aNextEdgeFeature, aP2, theEdgesSet,
335                          theProcessedEdgesSet, theChain, isPrepend);
336 }
337
338 static void setRefListValue(AttributeRefListPtr theList, int theListSize,
339                             ObjectPtr theValue, int theIndex)
340 {
341   if (theIndex < theListSize) {
342     ObjectPtr aCur = theList->object(theIndex);
343     if (aCur != theValue)
344       theList->substitute(aCur, theValue);
345   }
346   else
347     theList->append(theValue);
348 }
349
350 // Reorder shapes according to the wire's order
351 static void reorderShapes(ListOfShape& theShapes, GeomShapePtr theWire)
352 {
353   std::set<GeomShapePtr, GeomAPI_Shape::Comparator> aShapes;
354   aShapes.insert(theShapes.begin(), theShapes.end());
355   theShapes.clear();
356
357   GeomWirePtr aWire(new GeomAPI_Wire(theWire));
358   GeomAPI_WireExplorer anExp(aWire);
359   for (; anExp.more(); anExp.next()) {
360     GeomShapePtr aCurEdge = anExp.current();
361     auto aFound = aShapes.find(aCurEdge);
362     if (aFound != aShapes.end()) {
363       theShapes.push_back(aCurEdge);
364       aShapes.erase(aFound);
365     }
366   }
367 }
368
369 static void removeLastFromIndex(AttributeRefListPtr theList, int theListSize, int& theLastIndex)
370 {
371   if (theLastIndex < theListSize) {
372     std::set<int> anIndicesToRemove;
373     for (; theLastIndex < theListSize; ++theLastIndex)
374       anIndicesToRemove.insert(theLastIndex);
375     theList->remove(anIndicesToRemove);
376   }
377 }
378
379 void SketchPlugin_Offset::addToSketch(const ListOfMakeShape& theOffsetAlgos)
380 {
381   AttributeRefListPtr aSelectedRefList = reflist(EDGES_ID());
382   AttributeRefListPtr aBaseRefList = reflist(ENTITY_A());
383   AttributeRefListPtr anOffsetRefList = reflist(ENTITY_B());
384   AttributeIntArrayPtr anOffsetToBaseMap = intArray(ENTITY_C());
385
386   // compare the list of selected edges and the previously stored,
387   // and store maping between them
388   std::map<ObjectPtr, std::list<ObjectPtr> > aMapExistent;
389   std::list<ObjectPtr> anObjectsToRemove;
390   std::list<ObjectPtr> aSelectedList = aSelectedRefList->list();
391   for (std::list<ObjectPtr>::iterator aSIt = aSelectedList.begin();
392        aSIt != aSelectedList.end(); ++aSIt) {
393     aMapExistent[*aSIt] = std::list<ObjectPtr>();
394   }
395   for (int anIndex = 0, aSize = anOffsetRefList->size(); anIndex < aSize; ++anIndex) {
396     ObjectPtr aCurrent = anOffsetRefList->object(anIndex);
397     int aBaseIndex = anOffsetToBaseMap->value(anIndex);
398     if (aBaseIndex >= 0) {
399       ObjectPtr aBaseObj = aBaseRefList->object(aBaseIndex);
400       std::map<ObjectPtr, std::list<ObjectPtr> >::iterator aFound = aMapExistent.find(aBaseObj);
401       if (aFound != aMapExistent.end())
402         aFound->second.push_back(aCurrent);
403       else
404         anObjectsToRemove.push_back(aCurrent);
405     }
406     else
407       anObjectsToRemove.push_back(aCurrent);
408   }
409
410   // update lists of base shapes and of offset shapes
411   int aBaseListSize = aBaseRefList->size();
412   int anOffsetListSize = anOffsetRefList->size();
413   int aBaseListIndex = 0, anOffsetListIndex = 0;
414   std::list<int> anOffsetBaseBackRefs;
415   std::set<GeomShapePtr, GeomAPI_Shape::ComparatorWithOri> aProcessedOffsets;
416   for (std::list<ObjectPtr>::iterator aSIt = aSelectedList.begin();
417        aSIt != aSelectedList.end(); ++aSIt) {
418     // find an offseted edge
419     FeaturePtr aBaseFeature = ModelAPI_Feature::feature(*aSIt);
420     GeomShapePtr aBaseShape = aBaseFeature->lastResult()->shape();
421     ListOfShape aNewShapes;
422     for (ListOfMakeShape::const_iterator anAlgoIt = theOffsetAlgos.begin();
423          anAlgoIt != theOffsetAlgos.end(); ++anAlgoIt) {
424       (*anAlgoIt)->generated(aBaseShape, aNewShapes);
425       if (!aNewShapes.empty()) {
426         reorderShapes(aNewShapes, (*anAlgoIt)->shape());
427         break;
428       }
429     }
430
431     // store base feature
432     setRefListValue(aBaseRefList, aBaseListSize, *aSIt, aBaseListIndex);
433
434     // create or update an offseted feature
435     const std::list<ObjectPtr>& anImages = aMapExistent[*aSIt];
436     std::list<ObjectPtr>::const_iterator anImgIt = anImages.begin();
437     for (ListOfShape::iterator aNewIt = aNewShapes.begin(); aNewIt != aNewShapes.end(); ++aNewIt) {
438       FeaturePtr aNewFeature;
439       if (anImgIt != anImages.end())
440         aNewFeature = ModelAPI_Feature::feature(*anImgIt++);
441       updateExistentOrCreateNew(*aNewIt, aNewFeature, anObjectsToRemove);
442       aProcessedOffsets.insert(*aNewIt);
443
444       // store an offseted feature
445       setRefListValue(anOffsetRefList, anOffsetListSize, aNewFeature, anOffsetListIndex);
446
447       anOffsetBaseBackRefs.push_back(aBaseListIndex);
448       ++anOffsetListIndex;
449     }
450     ++aBaseListIndex;
451     anObjectsToRemove.insert(anObjectsToRemove.end(), anImgIt, anImages.end());
452   }
453   // create arcs generated from vertices
454   for (ListOfMakeShape::const_iterator anAlgoIt = theOffsetAlgos.begin();
455        anAlgoIt != theOffsetAlgos.end(); ++anAlgoIt) {
456     GeomShapePtr aCurWire = (*anAlgoIt)->shape();
457     GeomAPI_ShapeExplorer anExp(aCurWire, GeomAPI_Shape::EDGE);
458     for (; anExp.more(); anExp.next()) {
459       GeomShapePtr aCurEdge = anExp.current();
460       if (aProcessedOffsets.find(aCurEdge) == aProcessedOffsets.end()) {
461         FeaturePtr aNewFeature;
462         updateExistentOrCreateNew(aCurEdge, aNewFeature, anObjectsToRemove);
463         aProcessedOffsets.insert(aCurEdge);
464
465         // store an offseted feature
466         setRefListValue(anOffsetRefList, anOffsetListSize, aNewFeature, anOffsetListIndex);
467
468         anOffsetBaseBackRefs.push_back(-1);
469         ++anOffsetListIndex;
470       }
471     }
472   }
473
474   removeLastFromIndex(aBaseRefList, aBaseListSize, aBaseListIndex);
475   removeLastFromIndex(anOffsetRefList, anOffsetListSize, anOffsetListIndex);
476
477   anOffsetToBaseMap->setSize((int)anOffsetBaseBackRefs.size(), false);
478   int anIndex = 0;
479   for (std::list<int>::iterator anIt = anOffsetBaseBackRefs.begin();
480        anIt != anOffsetBaseBackRefs.end(); ++anIt) {
481     anOffsetToBaseMap->setValue(anIndex++, *anIt, false);
482   }
483
484   // remove unused objects
485   std::set<FeaturePtr> aSet;
486   for (std::list<ObjectPtr>::iterator anIt = anObjectsToRemove.begin();
487        anIt != anObjectsToRemove.end(); ++anIt) {
488     FeaturePtr aFeature = ModelAPI_Feature::feature(*anIt);
489     if (aFeature)
490       aSet.insert(aFeature);
491   }
492   ModelAPI_Tools::removeFeaturesAndReferences(aSet);
493 }
494
495 static void findOrCreateFeatureByKind(SketchPlugin_Sketch* theSketch,
496                                       const std::string& theFeatureKind,
497                                       FeaturePtr& theFeature,
498                                       std::list<ObjectPtr>& thePoolOfFeatures)
499 {
500   if (theFeature) {
501     // check the feature type is the same as required
502     if (theFeature->getKind() != theFeatureKind) {
503       // return feature to the pool and try to find the most appropriate
504       thePoolOfFeatures.push_back(theFeature);
505       theFeature = FeaturePtr();
506     }
507   }
508   if (!theFeature) {
509     // try to find appropriate feature in the pool
510     for (std::list<ObjectPtr>::iterator it = thePoolOfFeatures.begin();
511          it != thePoolOfFeatures.end(); ++it) {
512       FeaturePtr aCurFeature = ModelAPI_Feature::feature(*it);
513       if (aCurFeature->getKind() == theFeatureKind) {
514         theFeature = aCurFeature;
515         thePoolOfFeatures.erase(it);
516         break;
517       }
518     }
519     // feature not found, create new
520     if (!theFeature)
521       theFeature = theSketch->addFeature(theFeatureKind);
522   }
523 }
524
525 void SketchPlugin_Offset::updateExistentOrCreateNew(const GeomShapePtr& theShape,
526                                                     FeaturePtr& theFeature,
527                                                     std::list<ObjectPtr>& thePoolOfFeatures)
528 {
529   if (theShape->shapeType() != GeomAPI_Shape::EDGE)
530     return;
531
532   std::shared_ptr<GeomAPI_Edge> aResEdge(new GeomAPI_Edge(theShape));
533
534   std::shared_ptr<GeomAPI_Pnt2d> aFP, aLP;
535   std::shared_ptr<GeomAPI_Pnt> aFP3d = aResEdge->firstPoint();
536   std::shared_ptr<GeomAPI_Pnt> aLP3d = aResEdge->lastPoint();
537   if (aFP3d && aLP3d) {
538     aFP = sketch()->to2D(aFP3d);
539     aLP = sketch()->to2D(aLP3d);
540   }
541
542   if (aResEdge->isLine()) {
543     findOrCreateFeatureByKind(sketch(), SketchPlugin_Line::ID(), theFeature, thePoolOfFeatures);
544
545     std::dynamic_pointer_cast<GeomDataAPI_Point2D>
546       (theFeature->attribute(SketchPlugin_Line::START_ID()))->setValue(aFP);
547     std::dynamic_pointer_cast<GeomDataAPI_Point2D>
548       (theFeature->attribute(SketchPlugin_Line::END_ID()))->setValue(aLP);
549   }
550   else if (aResEdge->isArc()) {
551     std::shared_ptr<GeomAPI_Circ> aCircEdge = aResEdge->circle();
552     std::shared_ptr<GeomAPI_Pnt> aCP3d = aCircEdge->center();
553     std::shared_ptr<GeomAPI_Pnt2d> aCP = sketch()->to2D(aCP3d);
554
555     findOrCreateFeatureByKind(sketch(), SketchPlugin_Arc::ID(), theFeature, thePoolOfFeatures);
556
557     GeomDirPtr aCircNormal = aCircEdge->normal();
558     GeomDirPtr aSketchNormal = sketch()->coordinatePlane()->normal();
559     if (aSketchNormal->dot(aCircNormal) < -tolerance)
560       std::swap(aFP, aLP);
561
562     bool aWasBlocked = theFeature->data()->blockSendAttributeUpdated(true);
563     std::dynamic_pointer_cast<GeomDataAPI_Point2D>
564       (theFeature->attribute(SketchPlugin_Arc::END_ID()))->setValue(aLP);
565     std::dynamic_pointer_cast<GeomDataAPI_Point2D>
566       (theFeature->attribute(SketchPlugin_Arc::START_ID()))->setValue(aFP);
567     std::dynamic_pointer_cast<GeomDataAPI_Point2D>
568       (theFeature->attribute(SketchPlugin_Arc::CENTER_ID()))->setValue(aCP);
569     theFeature->data()->blockSendAttributeUpdated(aWasBlocked);
570   }
571   else if (aResEdge->isCircle()) {
572     std::shared_ptr<GeomAPI_Circ> aCircEdge = aResEdge->circle();
573     std::shared_ptr<GeomAPI_Pnt> aCP3d = aCircEdge->center();
574     std::shared_ptr<GeomAPI_Pnt2d> aCP = sketch()->to2D(aCP3d);
575
576     findOrCreateFeatureByKind(sketch(), SketchPlugin_Circle::ID(), theFeature, thePoolOfFeatures);
577
578     std::dynamic_pointer_cast<GeomDataAPI_Point2D>
579       (theFeature->attribute(SketchPlugin_Circle::CENTER_ID()))->setValue(aCP);
580     theFeature->real(SketchPlugin_Circle::RADIUS_ID())->setValue(aCircEdge->radius());
581   }
582   else if (aResEdge->isEllipse()) {
583     std::shared_ptr<GeomAPI_Ellipse> anEllipseEdge = aResEdge->ellipse();
584
585     GeomPointPtr aCP3d = anEllipseEdge->center();
586     GeomPnt2dPtr aCP = sketch()->to2D(aCP3d);
587
588     GeomPointPtr aFocus3d = anEllipseEdge->firstFocus();
589     GeomPnt2dPtr aFocus = sketch()->to2D(aFocus3d);
590
591     if (aFP3d && aLP3d) {
592       // Elliptic arc
593       findOrCreateFeatureByKind(sketch(), SketchPlugin_EllipticArc::ID(),
594                                 theFeature, thePoolOfFeatures);
595
596       bool aWasBlocked = theFeature->data()->blockSendAttributeUpdated(true);
597       std::dynamic_pointer_cast<GeomDataAPI_Point2D>
598         (theFeature->attribute(SketchPlugin_EllipticArc::CENTER_ID()))->setValue(aCP);
599       std::dynamic_pointer_cast<GeomDataAPI_Point2D>
600         (theFeature->attribute(SketchPlugin_EllipticArc::FIRST_FOCUS_ID()))->setValue(aFocus);
601       std::dynamic_pointer_cast<GeomDataAPI_Point2D>
602         (theFeature->attribute(SketchPlugin_EllipticArc::START_POINT_ID()))->setValue(aFP);
603       std::dynamic_pointer_cast<GeomDataAPI_Point2D>
604         (theFeature->attribute(SketchPlugin_EllipticArc::END_POINT_ID()))->setValue(aLP);
605       theFeature->data()->blockSendAttributeUpdated(aWasBlocked);
606     }
607     else {
608       // Ellipse
609       findOrCreateFeatureByKind(sketch(), SketchPlugin_Ellipse::ID(),
610                                 theFeature, thePoolOfFeatures);
611
612       std::dynamic_pointer_cast<GeomDataAPI_Point2D>
613         (theFeature->attribute(SketchPlugin_Ellipse::CENTER_ID()))->setValue(aCP);
614       std::dynamic_pointer_cast<GeomDataAPI_Point2D>
615         (theFeature->attribute(SketchPlugin_Ellipse::FIRST_FOCUS_ID()))->setValue(aFocus);
616       theFeature->real(SketchPlugin_Ellipse::MINOR_RADIUS_ID())->setValue(
617         anEllipseEdge->minorRadius());
618     }
619   }
620   else {
621     // convert to b-spline
622     mkBSpline(theFeature, aResEdge, thePoolOfFeatures);
623   }
624
625   if (theFeature.get()) {
626     theFeature->boolean(SketchPlugin_SketchEntity::COPY_ID())->setValue(true);
627     theFeature->execute();
628
629     static Events_ID aRedisplayEvent = Events_Loop::eventByName(EVENT_OBJECT_TO_REDISPLAY);
630     ModelAPI_EventCreator::get()->sendUpdated(theFeature, aRedisplayEvent);
631     const std::list<ResultPtr>& aResults = theFeature->results();
632     for (std::list<ResultPtr>::const_iterator anIt = aResults.begin();
633          anIt != aResults.end(); ++anIt)
634       ModelAPI_EventCreator::get()->sendUpdated(*anIt, aRedisplayEvent);
635   }
636 }
637
638 void SketchPlugin_Offset::mkBSpline (FeaturePtr& theResult,
639                                      const GeomEdgePtr& theEdge,
640                                      std::list<ObjectPtr>& thePoolOfFeatures)
641 {
642   GeomCurvePtr aCurve (new GeomAPI_Curve (theEdge));
643   // Forced conversion to b-spline, if aCurve is not b-spline
644   GeomBSplinePtr aBSpline = GeomAPI_BSpline::convertToBSpline(aCurve);
645
646   const std::string& aBSplineKind = aBSpline->isPeriodic() ? SketchPlugin_BSplinePeriodic::ID()
647                                                            : SketchPlugin_BSpline::ID();
648   findOrCreateFeatureByKind(sketch(), aBSplineKind, theResult, thePoolOfFeatures);
649
650   theResult->integer(SketchPlugin_BSpline::DEGREE_ID())->setValue(aBSpline->degree());
651
652   AttributePoint2DArrayPtr aPolesAttr = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>
653     (theResult->attribute(SketchPlugin_BSpline::POLES_ID()));
654   std::list<GeomPointPtr> aPoles = aBSpline->poles();
655   aPolesAttr->setSize((int)aPoles.size());
656   std::list<GeomPointPtr>::iterator anIt = aPoles.begin();
657   for (int anIndex = 0; anIt != aPoles.end(); ++anIt, ++anIndex) {
658     GeomPnt2dPtr aPoleInSketch = sketch()->to2D(*anIt);
659     aPolesAttr->setPnt(anIndex, aPoleInSketch);
660   }
661
662   AttributeDoubleArrayPtr aWeightsAttr =
663       theResult->data()->realArray(SketchPlugin_BSpline::WEIGHTS_ID());
664   std::list<double> aWeights = aBSpline->weights();
665   if (aWeights.empty()) { // rational B-spline
666     int aSize = (int)aPoles.size();
667     aWeightsAttr->setSize(aSize);
668     for (int anIndex = 0; anIndex < aSize; ++anIndex)
669       aWeightsAttr->setValue(anIndex, 1.0);
670   }
671   else { // non-rational B-spline
672     aWeightsAttr->setSize((int)aWeights.size());
673     std::list<double>::iterator aWIt = aWeights.begin();
674     for (int anIndex = 0; aWIt != aWeights.end(); ++aWIt, ++anIndex)
675       aWeightsAttr->setValue(anIndex, *aWIt);
676   }
677
678   AttributeDoubleArrayPtr aKnotsAttr =
679       theResult->data()->realArray(SketchPlugin_BSpline::KNOTS_ID());
680   std::list<double> aKnots = aBSpline->knots();
681   int aSize = (int)aKnots.size();
682   aKnotsAttr->setSize(aSize);
683   std::list<double>::iterator aKIt = aKnots.begin();
684   for (int index = 0; index < aSize; ++index, ++aKIt)
685     aKnotsAttr->setValue(index, *aKIt);
686
687   AttributeIntArrayPtr aMultsAttr =
688       theResult->data()->intArray(SketchPlugin_BSpline::MULTS_ID());
689   std::list<int> aMultiplicities = aBSpline->mults();
690   aSize = (int)aMultiplicities.size();
691   aMultsAttr->setSize(aSize);
692   std::list<int>::iterator aMIt = aMultiplicities.begin();
693   for (int index = 0; index < aSize; ++index, ++aMIt)
694     aMultsAttr->setValue(index, *aMIt);
695 }
696
697 void SketchPlugin_Offset::attributeChanged(const std::string& theID)
698 {
699   if (theID == EDGES_ID()) {
700     AttributeRefListPtr aSelected = reflist(EDGES_ID());
701     if (aSelected->size() == 0) {
702       // Clear list of objects
703       AttributeRefListPtr anOffsetAttr = reflist(SketchPlugin_Constraint::ENTITY_B());
704       std::list<ObjectPtr> anOffsetList = anOffsetAttr->list();
705       std::set<FeaturePtr> aFeaturesToBeRemoved;
706       for (std::list<ObjectPtr>::iterator anIt = anOffsetList.begin();
707            anIt != anOffsetList.end(); ++anIt) {
708         FeaturePtr aFeature = ModelAPI_Feature::feature(*anIt);
709         if (aFeature)
710           aFeaturesToBeRemoved.insert(aFeature);
711       }
712
713       reflist(SketchPlugin_Constraint::ENTITY_A())->clear();
714       anOffsetAttr->clear();
715       intArray(SketchPlugin_Constraint::ENTITY_C())->setSize(0);
716
717       ModelAPI_Tools::removeFeaturesAndReferences(aFeaturesToBeRemoved);
718     }
719   }
720 }
721
722 bool SketchPlugin_Offset::customAction(const std::string& theActionId)
723 {
724   bool isOk = false;
725   if (theActionId == ADD_WIRE_ACTION_ID()) {
726     isOk = findWires();
727   }
728   else {
729     std::string aMsg = "Error: Feature \"%1\" does not support action \"%2\".";
730     Events_InfoMessage("SketchPlugin_Offset", aMsg).arg(getKind()).arg(theActionId).send();
731   }
732   return isOk;
733 }
734
735 bool SketchPlugin_Offset::findWires()
736 {
737   AttributeRefListPtr aSelectedEdges = reflist(EDGES_ID());
738   std::list<ObjectPtr> anEdgesList = aSelectedEdges->list();
739
740   // Empty set
741   std::set<FeaturePtr> anEdgesSet;
742
743   // Processed set
744   std::set<FeaturePtr> aProcessedEdgesSet;
745
746   // Put all selected edges in a set to avoid adding them in reflist(EDGES_ID())
747   std::set<FeaturePtr> aSelectedSet;
748   std::list<ObjectPtr>::const_iterator anEdgesIt = anEdgesList.begin();
749   for (; anEdgesIt != anEdgesList.end(); anEdgesIt++) {
750     FeaturePtr aFeature = ModelAPI_Feature::feature(*anEdgesIt);
751     if (aFeature) {
752       aSelectedSet.insert(aFeature);
753     }
754   }
755
756   bool aWasBlocked = data()->blockSendAttributeUpdated(true);
757
758   // Gather chains of edges
759   for (anEdgesIt = anEdgesList.begin(); anEdgesIt != anEdgesList.end(); anEdgesIt++) {
760     FeaturePtr aFeature = ModelAPI_Feature::feature(*anEdgesIt);
761     if (aFeature.get()) {
762       if (aProcessedEdgesSet.find(aFeature) != aProcessedEdgesSet.end())
763         continue;
764       aProcessedEdgesSet.insert(aFeature);
765
766       // End points (if any)
767       std::shared_ptr<GeomDataAPI_Point2D> aStartPoint, anEndPoint;
768       SketchPlugin_SegmentationTools::getFeaturePoints(aFeature, aStartPoint, anEndPoint);
769
770       std::list<FeaturePtr> aChain;
771       aChain.push_back(aFeature);
772       bool isClosed = findWireOneWay(aFeature, aFeature, aStartPoint, anEdgesSet,
773                                      aProcessedEdgesSet, aChain, true);
774       if (!isClosed) {
775         findWireOneWay(aFeature, aFeature, anEndPoint, anEdgesSet,
776                        aProcessedEdgesSet, aChain, false);
777       }
778
779       std::list<FeaturePtr>::iterator aChainIt = aChain.begin();
780       for (; aChainIt != aChain.end(); ++aChainIt) {
781         FeaturePtr aChainFeature = (*aChainIt);
782         if (aSelectedSet.find(aChainFeature) == aSelectedSet.end()) {
783           aSelectedEdges->append(aChainFeature->lastResult());
784         }
785       }
786     }
787   }
788
789   data()->blockSendAttributeUpdated(aWasBlocked);
790   return true;
791 }
792
793
794 AISObjectPtr SketchPlugin_Offset::getAISObject(AISObjectPtr thePrevious)
795 {
796   if (!sketch())
797     return thePrevious;
798
799   AISObjectPtr anAIS = SketcherPrs_Factory::offsetObject(this, sketch(),
800     thePrevious);
801   return anAIS;
802 }
803
804
805 void SketchPlugin_Offset::makeFillet
806      (const double theValue,
807       const std::shared_ptr<GeomAlgoAPI_WireBuilder>& theWireBuilder,
808       const std::shared_ptr<GeomAlgoAPI_Offset>& theOffsetShape,
809       ListOfMakeShape& theOffsetAlgos)
810 {
811   std::shared_ptr<GeomAlgoAPI_MakeShapeList> aMakeList (new GeomAlgoAPI_MakeShapeList);
812   aMakeList->appendAlgo(theWireBuilder);
813   aMakeList->appendAlgo(theOffsetShape);
814
815   bool isOK = true;
816
817   GeomShapePtr aResWire = theOffsetShape->shape();
818   GeomAlgoAPI_MapShapesAndAncestors aMapVE
819     (aResWire, GeomAPI_Shape::VERTEX, GeomAPI_Shape::EDGE);
820   const MapShapeToShapes& aSubshapes = aMapVE.map();
821
822   // find vertices for fillet
823   std::set<GeomShapePtr, GeomAPI_Shape::Comparator> aFilletVertices;
824   for (MapShapeToShapes::const_iterator anIt = aSubshapes.begin();
825        anIt != aSubshapes.end(); ++anIt) {
826     // vertex should have 2 adjacent edges
827     if (anIt->second.size() != 2)
828       continue;
829
830     // both edges should be linear
831     ListOfShape anEdges;
832     anEdges.insert(anEdges.end(), anIt->second.begin(), anIt->second.end());
833     GeomEdgePtr anEdge1 (new GeomAPI_Edge(anEdges.front()));
834     GeomEdgePtr anEdge2 (new GeomAPI_Edge(anEdges.back()));
835     if (!anEdge1->isLine() || !anEdge2->isLine())
836       continue;
837
838     // skip vertices, which smoothly connect adjacent edges
839     GeomVertexPtr aSharedVertex(new GeomAPI_Vertex(anIt->first));
840     if (GeomAlgoAPI_ShapeTools::isTangent(anEdge1, anEdge2, aSharedVertex))
841       continue;
842
843     aFilletVertices.insert(anIt->first);
844   }
845
846   if (!aFilletVertices.empty()) {
847     isOK = false; // the wire needs correction
848     ListOfShape aVerticesList (aFilletVertices.begin(), aFilletVertices.end());
849
850     // Fillet1D on all linear edges intersections
851     std::shared_ptr<GeomAlgoAPI_Fillet1D> aFilletBuilder
852       (new GeomAlgoAPI_Fillet1D(aResWire, aVerticesList, theValue));
853
854     std::string anError;
855     if (!GeomAlgoAPI_Tools::AlgoError::isAlgorithmFailed
856         (aFilletBuilder, getKind(), anError)) {
857       aMakeList->appendAlgo(aFilletBuilder);
858       isOK = true;
859     }
860     else {
861       ListOfShape aFailedVertices = aFilletBuilder->failedVertices();
862       if (aFailedVertices.size() != 0) {
863         // Exclude failed vertices and also vertices, joined
864         // with failed by one edge, and run algorithm once again
865         ListOfShape::iterator itVertices = aFailedVertices.begin();
866         for (; itVertices != aFailedVertices.end(); itVertices++) {
867           GeomShapePtr aFailedVertex = *itVertices;
868           aFilletVertices.erase(aFailedVertex);
869           // remove also neighbour vertices
870           MapShapeToShapes::const_iterator anIt = aSubshapes.find(aFailedVertex);
871           if (anIt != aSubshapes.end()) { // should be always true
872             ListOfShape anEdges;
873             anEdges.insert(anEdges.end(), anIt->second.begin(), anIt->second.end());
874             GeomEdgePtr anEdge1 (new GeomAPI_Edge(anEdges.front()));
875             GeomEdgePtr anEdge2 (new GeomAPI_Edge(anEdges.back()));
876             GeomVertexPtr V1, V2;
877             anEdge1->vertices(V1, V2);
878             if (V1->isEqual(aFailedVertex)) V1 = V2;
879             aFilletVertices.erase(V1);
880             anEdge2->vertices(V1, V2);
881             if (V1->isEqual(aFailedVertex)) V1 = V2;
882             aFilletVertices.erase(V1);
883           }
884         }
885         if (aFilletVertices.size() == 0) {
886           // there are no suitable vertices for fillet
887           isOK = true;
888         }
889         else {
890           // Fillet1D one more try
891           ListOfShape aVerticesList1 (aFilletVertices.begin(), aFilletVertices.end());
892
893           std::shared_ptr<GeomAlgoAPI_Fillet1D> aFilletBuilder1
894             (new GeomAlgoAPI_Fillet1D(aResWire, aVerticesList1, theValue));
895
896           if (!GeomAlgoAPI_Tools::AlgoError::isAlgorithmFailed
897               (aFilletBuilder1, getKind(), anError)) {
898             aMakeList->appendAlgo(aFilletBuilder1);
899             isOK = true;
900           }
901         }
902       }
903     }
904   }
905
906   if (isOK)
907     theOffsetAlgos.push_back(aMakeList);
908   else
909     setError("Offset algorithm failed");
910 }