1 // Copyright (C) 2020-2023 CEA, EDF
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.
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.
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
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 #include <SketchPlugin_Offset.h>
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>
33 #include <SketcherPrs_Factory.h>
35 #include <Events_InfoMessage.h>
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>
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>
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>
65 #include <GeomDataAPI_Point2D.h>
66 #include <GeomDataAPI_Point2DArray.h>
70 static const double tolerance = 1.e-7;
72 SketchPlugin_Offset::SketchPlugin_Offset()
76 void SketchPlugin_Offset::initAttributes()
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
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
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
95 AttributeBooleanPtr approxAttr;
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(),
106 entaAttr = std::dynamic_pointer_cast<ModelAPI_AttributeRefList>
107 (data()->addAttribute(SketchPlugin_Constraint::ENTITY_A(),
108 ModelAPI_AttributeRefList::typeId(),
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());
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());
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());
130 // Good place for approxAttr is here
131 approxAttr = std::dynamic_pointer_cast<ModelAPI_AttributeBoolean>
132 (data()->addAttribute(APPROX_ID(), ModelAPI_AttributeBoolean::typeId()));
135 // Initialize approximation to false by default for backward compatibility
136 if (!approxAttr->isInitialized())
137 approxAttr->setValue(false);
140 void SketchPlugin_Offset::execute()
142 SketchPlugin_Sketch* aSketch = sketch();
143 if (!aSketch) return;
146 AttributeStringPtr aJointAttr = string(JOINT_ID());
147 std::string aType = JOINT_KEEP_DISTANCE();
148 if (aJointAttr->isInitialized())
149 aType = aJointAttr->value();
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;
157 aJoint = GeomAlgoAPI_OffsetJoint::KeepDistance;
160 std::shared_ptr<GeomAPI_Pln> aPlane = aSketch->plane();
163 AttributeDoublePtr aValueAttr = real(VALUE_ID());
164 if (!aValueAttr->isInitialized()) return;
165 double aValue = aValueAttr->value();
166 if (aValue < tolerance) return;
169 AttributeBooleanPtr aReversedAttr = boolean(REVERSED_ID());
170 if (!aReversedAttr->isInitialized()) return;
171 if (aReversedAttr->value()) aValue = -aValue; // reverse offset direction
173 // 3. List of all selected edges
174 AttributeRefListPtr aSelectedEdges = reflist(EDGES_ID());
175 std::list<ObjectPtr> anEdgesList = aSelectedEdges->list();
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);
183 anEdgesSet.insert(aFeature);
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);
191 Events_Loop::loop()->setFlushed(anUpdateEvent, false);
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();
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())
207 // 5.a. End points (if any)
208 std::shared_ptr<GeomDataAPI_Point2D> aStartPoint, anEndPoint;
209 SketchPlugin_SegmentationTools::getFeaturePoints(aFeature, aStartPoint, anEndPoint);
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
216 isClosed = findWireOneWay(aFeature, aFeature, aStartPoint, anEdgesSet,
217 aProcessedEdgesSet, aChain, true);
219 isClosed = findWireOneWay(aFeature, aFeature, anEndPoint, anEdgesSet,
220 aProcessedEdgesSet, aChain, false);
223 aProcessedEdgesSet.insert(aFeature);
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);
235 std::shared_ptr<GeomAlgoAPI_WireBuilder> aWireBuilder(
236 new GeomAlgoAPI_WireBuilder(aTopoChain, !isClosed));
238 GeomShapePtr aWireShape = aWireBuilder->shape();
239 GeomWirePtr aWire (new GeomAPI_Wire (aWireShape));
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.
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())
254 // 5.d. Make offset for the wire
255 AttributeBooleanPtr anApproxAttr = boolean(APPROX_ID());
256 if (!anApproxAttr->isInitialized())
258 // It must be initialized at least by SketchPlugin_Offset::initAttributes()
262 std::shared_ptr<GeomAlgoAPI_Offset> anOffsetShape
263 (new GeomAlgoAPI_Offset(aPlane, aWireShape, aValue*aSign, aJoint, anApproxAttr->value()));
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);
272 std::shared_ptr<GeomAlgoAPI_MakeShapeList> aMakeList (new GeomAlgoAPI_MakeShapeList);
273 aMakeList->appendAlgo(aWireBuilder);
274 aMakeList->appendAlgo(anOffsetShape);
275 anOffsetAlgos.push_back(aMakeList);
279 setError("Offset algorithm failed");
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);
289 restoreCurrentFeature();
291 // send events to update the sub-features by the solver
293 Events_Loop::loop()->setFlushed(anUpdateEvent, true);
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)
304 // 1. Find a single edge, coincident to theEndPoint by one of its ends
305 if (!theEndPoint) return false;
307 FeaturePtr aNextEdgeFeature;
310 std::set<AttributePoint2DPtr> aCoincPoints =
311 SketchPlugin_Tools::findPointsCoincidentToPoint(theEndPoint);
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());
319 // Condition 0: not auxiliary
320 if (!isInSet && aCoincFeature->boolean(SketchPlugin_SketchEntity::AUXILIARY_ID())->value())
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);
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.
344 aNextEdgeFeature = aCoincFeature;
352 // Only one edge can prolongate the chain. If several or none, we stop here.
356 // 2. So, we have the single edge, that prolongate the chain
358 // Condition 7: if we reached the very first edge of the chain
359 if (aNextEdgeFeature == theFirstEdge)
360 // Closed chain found
363 // 3. Add the found edge to the chain
365 theChain.push_front(aNextEdgeFeature);
367 theChain.push_back(aNextEdgeFeature);
368 theProcessedEdgesSet.insert(aNextEdgeFeature);
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())) {
378 // 5. Continue gathering the chain (recursive)
379 return findWireOneWay (theFirstEdge, aNextEdgeFeature, aP2, theEdgesSet,
380 theProcessedEdgesSet, theChain, isPrepend);
383 static void setRefListValue(AttributeRefListPtr theList, int theListSize,
384 ObjectPtr theValue, int theIndex)
386 if (theIndex < theListSize) {
387 ObjectPtr aCur = theList->object(theIndex);
388 if (aCur != theValue)
389 theList->substitute(aCur, theValue);
392 theList->append(theValue);
395 // Reorder shapes according to the wire's order
396 static void reorderShapes(ListOfShape& theShapes, GeomShapePtr theWire)
398 std::set<GeomShapePtr, GeomAPI_Shape::Comparator> aShapes;
399 aShapes.insert(theShapes.begin(), theShapes.end());
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);
414 static void removeLastFromIndex(AttributeRefListPtr theList, int theListSize, int& theLastIndex)
416 if (theLastIndex < theListSize) {
417 std::set<int> anIndicesToRemove;
418 for (; theLastIndex < theListSize; ++theLastIndex)
419 anIndicesToRemove.insert(theLastIndex);
420 theList->remove(anIndicesToRemove);
424 void SketchPlugin_Offset::addToSketch(const ListOfMakeShape& theOffsetAlgos)
426 AttributeRefListPtr aSelectedRefList = reflist(EDGES_ID());
427 AttributeRefListPtr aBaseRefList = reflist(ENTITY_A());
428 AttributeRefListPtr anOffsetRefList = reflist(ENTITY_B());
429 AttributeIntArrayPtr anOffsetToBaseMap = intArray(ENTITY_C());
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>();
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);
449 anObjectsToRemove.push_back(aCurrent);
452 anObjectsToRemove.push_back(aCurrent);
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());
476 // store base feature
477 setRefListValue(aBaseRefList, aBaseListSize, *aSIt, aBaseListIndex);
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);
489 // store an offseted feature
490 setRefListValue(anOffsetRefList, anOffsetListSize, aNewFeature, anOffsetListIndex);
492 anOffsetBaseBackRefs.push_back(aBaseListIndex);
496 anObjectsToRemove.insert(anObjectsToRemove.end(), anImgIt, anImages.end());
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);
510 // store an offseted feature
511 setRefListValue(anOffsetRefList, anOffsetListSize, aNewFeature, anOffsetListIndex);
513 anOffsetBaseBackRefs.push_back(-1);
519 removeLastFromIndex(aBaseRefList, aBaseListSize, aBaseListIndex);
520 removeLastFromIndex(anOffsetRefList, anOffsetListSize, anOffsetListIndex);
522 anOffsetToBaseMap->setSize((int)anOffsetBaseBackRefs.size(), false);
524 for (std::list<int>::iterator anIt = anOffsetBaseBackRefs.begin();
525 anIt != anOffsetBaseBackRefs.end(); ++anIt) {
526 anOffsetToBaseMap->setValue(anIndex++, *anIt, false);
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);
535 aSet.insert(aFeature);
537 ModelAPI_Tools::removeFeaturesAndReferences(aSet);
540 static void findOrCreateFeatureByKind(SketchPlugin_Sketch* theSketch,
541 const std::string& theFeatureKind,
542 FeaturePtr& theFeature,
543 std::list<ObjectPtr>& thePoolOfFeatures)
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();
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);
564 // feature not found, create new
566 theFeature = theSketch->addFeature(theFeatureKind);
570 void SketchPlugin_Offset::updateExistentOrCreateNew(const GeomShapePtr& theShape,
571 FeaturePtr& theFeature,
572 std::list<ObjectPtr>& thePoolOfFeatures)
574 if (theShape->shapeType() != GeomAPI_Shape::EDGE)
577 std::shared_ptr<GeomAPI_Edge> aResEdge(new GeomAPI_Edge(theShape));
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);
587 if (aResEdge->isLine()) {
588 findOrCreateFeatureByKind(sketch(), SketchPlugin_Line::ID(), theFeature, thePoolOfFeatures);
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);
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);
600 findOrCreateFeatureByKind(sketch(), SketchPlugin_Arc::ID(), theFeature, thePoolOfFeatures);
602 GeomDirPtr aCircNormal = aCircEdge->normal();
603 GeomDirPtr aSketchNormal = sketch()->coordinatePlane()->normal();
604 if (aSketchNormal->dot(aCircNormal) < -tolerance)
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);
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);
621 findOrCreateFeatureByKind(sketch(), SketchPlugin_Circle::ID(), theFeature, thePoolOfFeatures);
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());
627 else if (aResEdge->isEllipse()) {
628 std::shared_ptr<GeomAPI_Ellipse> anEllipseEdge = aResEdge->ellipse();
630 GeomPointPtr aCP3d = anEllipseEdge->center();
631 GeomPnt2dPtr aCP = sketch()->to2D(aCP3d);
633 GeomPointPtr aFocus3d = anEllipseEdge->firstFocus();
634 GeomPnt2dPtr aFocus = sketch()->to2D(aFocus3d);
636 if (aFP3d && aLP3d) {
638 findOrCreateFeatureByKind(sketch(), SketchPlugin_EllipticArc::ID(),
639 theFeature, thePoolOfFeatures);
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);
654 findOrCreateFeatureByKind(sketch(), SketchPlugin_Ellipse::ID(),
655 theFeature, thePoolOfFeatures);
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());
666 // convert to b-spline
667 mkBSpline(theFeature, aResEdge, thePoolOfFeatures);
670 if (theFeature.get()) {
671 theFeature->boolean(SketchPlugin_SketchEntity::COPY_ID())->setValue(true);
672 theFeature->execute();
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);
683 void SketchPlugin_Offset::mkBSpline (FeaturePtr& theResult,
684 const GeomEdgePtr& theEdge,
685 std::list<ObjectPtr>& thePoolOfFeatures)
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);
691 const std::string& aBSplineKind = aBSpline->isPeriodic() ? SketchPlugin_BSplinePeriodic::ID()
692 : SketchPlugin_BSpline::ID();
693 findOrCreateFeatureByKind(sketch(), aBSplineKind, theResult, thePoolOfFeatures);
695 theResult->integer(SketchPlugin_BSpline::DEGREE_ID())->setValue(aBSpline->degree());
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);
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);
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);
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);
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);
742 void SketchPlugin_Offset::attributeChanged(const std::string& theID)
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);
755 aFeaturesToBeRemoved.insert(aFeature);
758 reflist(SketchPlugin_Constraint::ENTITY_A())->clear();
759 anOffsetAttr->clear();
760 intArray(SketchPlugin_Constraint::ENTITY_C())->setSize(0);
762 ModelAPI_Tools::removeFeaturesAndReferences(aFeaturesToBeRemoved);
767 bool SketchPlugin_Offset::customAction(const std::string& theActionId)
770 if (theActionId == ADD_WIRE_ACTION_ID()) {
774 std::string aMsg = "Error: Feature \"%1\" does not support action \"%2\".";
775 Events_InfoMessage("SketchPlugin_Offset", aMsg).arg(getKind()).arg(theActionId).send();
780 bool SketchPlugin_Offset::findWires()
782 AttributeRefListPtr aSelectedEdges = reflist(EDGES_ID());
783 std::list<ObjectPtr> anEdgesList = aSelectedEdges->list();
786 std::set<FeaturePtr> anEdgesSet;
789 std::set<FeaturePtr> aProcessedEdgesSet;
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);
797 aSelectedSet.insert(aFeature);
801 bool aWasBlocked = data()->blockSendAttributeUpdated(true);
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())
809 aProcessedEdgesSet.insert(aFeature);
811 // End points (if any)
812 std::shared_ptr<GeomDataAPI_Point2D> aStartPoint, anEndPoint;
813 SketchPlugin_SegmentationTools::getFeaturePoints(aFeature, aStartPoint, anEndPoint);
815 std::list<FeaturePtr> aChain;
816 aChain.push_back(aFeature);
817 bool isClosed = findWireOneWay(aFeature, aFeature, aStartPoint, anEdgesSet,
818 aProcessedEdgesSet, aChain, true);
820 findWireOneWay(aFeature, aFeature, anEndPoint, anEdgesSet,
821 aProcessedEdgesSet, aChain, false);
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());
834 data()->blockSendAttributeUpdated(aWasBlocked);
839 AISObjectPtr SketchPlugin_Offset::getAISObject(AISObjectPtr thePrevious)
844 AISObjectPtr anAIS = SketcherPrs_Factory::offsetObject(this, sketch(),
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)
856 std::shared_ptr<GeomAlgoAPI_MakeShapeList> aMakeList (new GeomAlgoAPI_MakeShapeList);
857 aMakeList->appendAlgo(theWireBuilder);
858 aMakeList->appendAlgo(theOffsetShape);
862 GeomShapePtr aResWire = theOffsetShape->shape();
863 GeomAlgoAPI_MapShapesAndAncestors aMapVE
864 (aResWire, GeomAPI_Shape::VERTEX, GeomAPI_Shape::EDGE);
865 const MapShapeToShapes& aSubshapes = aMapVE.map();
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)
875 // both edges should be linear
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())
883 // skip vertices, which smoothly connect adjacent edges
884 GeomVertexPtr aSharedVertex(new GeomAPI_Vertex(anIt->first));
885 if (GeomAlgoAPI_ShapeTools::isTangent(anEdge1, anEdge2, aSharedVertex))
888 aFilletVertices.insert(anIt->first);
891 if (!aFilletVertices.empty()) {
892 isOK = false; // the wire needs correction
893 ListOfShape aVerticesList (aFilletVertices.begin(), aFilletVertices.end());
895 // Fillet1D on all linear edges intersections
896 std::shared_ptr<GeomAlgoAPI_Fillet1D> aFilletBuilder
897 (new GeomAlgoAPI_Fillet1D(aResWire, aVerticesList, theValue));
900 if (!GeomAlgoAPI_Tools::AlgoError::isAlgorithmFailed
901 (aFilletBuilder, getKind(), anError)) {
902 aMakeList->appendAlgo(aFilletBuilder);
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
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);
930 if (aFilletVertices.size() == 0) {
931 // there are no suitable vertices for fillet
935 // Fillet1D one more try
936 ListOfShape aVerticesList1 (aFilletVertices.begin(), aFilletVertices.end());
938 std::shared_ptr<GeomAlgoAPI_Fillet1D> aFilletBuilder1
939 (new GeomAlgoAPI_Fillet1D(aResWire, aVerticesList1, theValue));
941 if (!GeomAlgoAPI_Tools::AlgoError::isAlgorithmFailed
942 (aFilletBuilder1, getKind(), anError)) {
943 aMakeList->appendAlgo(aFilletBuilder1);
952 theOffsetAlgos.push_back(aMakeList);
954 setError("Offset algorithm failed");