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