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