]> SALOME platform Git repositories - modules/shaper.git/blob - src/SketchPlugin/SketchPlugin_Offset.cpp
Salome HOME
Issue #3231: Convert Offset curves to b-splines. Order wires.
[modules/shaper.git] / src / SketchPlugin / SketchPlugin_Offset.cpp
1 // Copyright (C) 2020  CEA/DEN, EDF R&D
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 //
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19
20 #include <SketchPlugin_Offset.h>
21
22 #include <SketchPlugin_Sketch.h>
23 #include <SketchPlugin_Line.h>
24 #include <SketchPlugin_Point.h>
25 #include <SketchPlugin_Arc.h>
26 #include <SketchPlugin_Circle.h>
27 #include <SketchPlugin_Ellipse.h>
28 #include <SketchPlugin_EllipticArc.h>
29 #include <SketchPlugin_BSpline.h>
30 #include <SketchPlugin_BSplinePeriodic.h>
31 #include <SketchPlugin_Tools.h>
32
33 #include <SketcherPrs_Factory.h>
34
35 #include <Events_InfoMessage.h>
36
37 #include <ModelAPI_AttributeBoolean.h>
38 #include <ModelAPI_AttributeDouble.h>
39 #include <ModelAPI_AttributeDoubleArray.h>
40 #include <ModelAPI_AttributeInteger.h>
41 #include <ModelAPI_AttributeRefList.h>
42 #include <ModelAPI_ResultConstruction.h>
43 #include <ModelAPI_Tools.h>
44
45 #include <GeomAlgoAPI_Offset.h>
46 #include <GeomAlgoAPI_ShapeTools.h>
47 #include <GeomAlgoAPI_WireBuilder.h>
48
49 #include <GeomAPI_Edge.h>
50 #include <GeomAPI_Circ.h>
51 #include <GeomAPI_Ellipse.h>
52 #include <GeomAPI_BSpline.h>
53
54 #include <GeomDataAPI_Point2D.h>
55 #include <GeomDataAPI_Point2DArray.h>
56
57 #include <iostream>
58
59 SketchPlugin_Offset::SketchPlugin_Offset()
60   : SketchPlugin_SketchEntity()
61 {
62 }
63
64 void SketchPlugin_Offset::initDerivedClassAttributes()
65 {
66   data()->addAttribute(EDGES_ID(), ModelAPI_AttributeRefList::typeId());
67   data()->addAttribute(VALUE_ID(), ModelAPI_AttributeDouble::typeId());
68   data()->addAttribute(REVERSED_ID(), ModelAPI_AttributeBoolean::typeId());
69 }
70
71 void SketchPlugin_Offset::execute()
72 {
73   ModelAPI_Tools::removeFeaturesAndReferences(myCreatedFeatures);
74   myCreatedFeatures.clear();
75
76   SketchPlugin_Sketch* aSketch = sketch();
77   if (!aSketch) return;
78
79   // 1. Sketch plane
80   std::shared_ptr<GeomAPI_Pln> aPlane = aSketch->plane();
81
82   // 2. Offset value
83   AttributeDoublePtr aValueAttr = real(VALUE_ID());
84   if (!aValueAttr->isInitialized()) return;
85   double aValue = aValueAttr->value();
86   const double tolerance = 1.e-7;
87   if (aValue < tolerance) return;
88
89   // 2.a. Reversed?
90   AttributeBooleanPtr aReversedAttr = boolean(REVERSED_ID());
91   if (!aReversedAttr->isInitialized()) return;
92   if (aReversedAttr->value()) aValue = -aValue; // reverse offset direction
93
94   // 3. List of all selected edges
95   AttributeRefListPtr aSelectedEdges = reflist(EDGES_ID());
96   std::list<ObjectPtr> anEdgesList = aSelectedEdges->list();
97
98   // 4. Put all selected edges in a set to pass them into findWireOneWay() below
99   std::set<FeaturePtr> anEdgesSet;
100   std::list<ObjectPtr>::const_iterator anEdgesIt = anEdgesList.begin();
101   for (; anEdgesIt != anEdgesList.end(); anEdgesIt++) {
102     FeaturePtr aFeature = ModelAPI_Feature::feature(*anEdgesIt);
103     if (aFeature) {
104       anEdgesSet.insert(aFeature);
105     }
106   }
107
108   // 5. Gather wires and make offset for each wire
109   for (anEdgesIt = anEdgesList.begin(); anEdgesIt != anEdgesList.end(); anEdgesIt++) {
110     FeaturePtr aFeature = ModelAPI_Feature::feature(*anEdgesIt);
111     if (aFeature.get()) {
112       if (anEdgesSet.find(aFeature) == anEdgesSet.end())
113         continue;
114
115       // 5.a. End points (if any)
116       std::shared_ptr<GeomDataAPI_Point2D> aStartPoint, anEndPoint;
117       SketchPlugin_SegmentationTools::getFeaturePoints(aFeature, aStartPoint, anEndPoint);
118
119       // 5.b. Find a chain of edges
120       std::list<FeaturePtr> aChain;
121       aChain.push_back(aFeature);
122       if (aStartPoint && anEndPoint) { // not closed edge
123         bool isClosed = findWireOneWay(aFeature, aFeature, aStartPoint, anEdgesSet, aChain, true);
124         if (!isClosed)
125           findWireOneWay(aFeature, aFeature, anEndPoint, anEdgesSet, aChain, false);
126       }
127       std::set<FeaturePtr>::iterator aPos = anEdgesSet.find(aFeature);
128       if (aPos != anEdgesSet.end())
129         anEdgesSet.erase(aPos);
130
131       // 5.c. Make wire
132       ListOfShape aTopoChain;
133       std::list<FeaturePtr>::iterator aChainIt = aChain.begin();
134       for (; aChainIt != aChain.end(); ++aChainIt) {
135         FeaturePtr aChainFeature = (*aChainIt);
136         GeomShapePtr aTopoEdge = aChainFeature->lastResult()->shape();
137         if (aTopoEdge->shapeType() == GeomAPI_Shape::EDGE) {
138           aTopoChain.push_back(aTopoEdge);
139         }
140       }
141       GeomShapePtr anEdgeOrWire = GeomAlgoAPI_WireBuilder::wire(aTopoChain);
142
143       // 5.d. Make offset for each wire
144       std::shared_ptr<GeomAPI_Shape> anOffsetShape =
145         GeomAlgoAPI_Offset::OffsetInPlane(aPlane, anEdgeOrWire, aValue);
146
147       // 5.e. Store offset results.
148       //      Create sketch feature for each edge of anOffsetShape, and also store
149       //      created features in myCreatedFeatures to remove them on next execute()
150       addToSketch(anOffsetShape);
151     }
152   }
153 }
154
155 bool SketchPlugin_Offset::findWireOneWay (const FeaturePtr& theFirstEdge,
156                                           const FeaturePtr& theEdge,
157                                           const std::shared_ptr<GeomDataAPI_Point2D>& theEndPoint,
158                                           std::set<FeaturePtr>& theEdgesSet,
159                                           std::list<FeaturePtr>& theChain,
160                                           const bool isPrepend)
161 {
162   // 1. Find a single edge, coincident to theEndPoint by one of its ends
163   if (!theEndPoint) return false;
164
165   std::shared_ptr<GeomAPI_Pnt2d> aP2d = theEndPoint->pnt();
166
167   FeaturePtr aNextEdgeFeature;
168   int nbFound = 0;
169
170   std::set<AttributePoint2DPtr> aCoincPoints;
171   std::map<AttributePoint2DArrayPtr, int> aCoincPointsInArray;
172   SketchPlugin_Tools::findPointsCoincidentToPoint(theEndPoint, aCoincPoints, aCoincPointsInArray);
173
174   // store all found attributes to a single array
175   std::set<AttributePtr> anAllCoincPoints;
176   anAllCoincPoints.insert(aCoincPoints.begin(), aCoincPoints.end());
177   for (auto it = aCoincPointsInArray.begin(); it != aCoincPointsInArray.end(); ++it)
178     anAllCoincPoints.insert(it->first);
179
180   std::set<AttributePtr>::iterator aPointsIt = anAllCoincPoints.begin();
181   for (; aPointsIt != anAllCoincPoints.end(); aPointsIt++) {
182     AttributePtr aP = (*aPointsIt);
183     FeaturePtr aCoincFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(aP->owner());
184
185     // Condition 0: not auxiliary
186     if (aCoincFeature->boolean(SketchPlugin_SketchEntity::AUXILIARY_ID())->value()) continue;
187
188     // Condition 1: not a point feature
189     if (aCoincFeature->getKind() != SketchPlugin_Point::ID()) {
190       // Condition 2: it is not the current edge
191       if (aCoincFeature != theEdge) {
192         // Condition 3: it is in the set of interest.
193         //              Empty set means all sketch edges.
194         bool isInSet = true;
195         if (theEdgesSet.size()) {
196           isInSet = (theEdgesSet.find(aCoincFeature) != theEdgesSet.end());
197         }
198         if (isInSet) {
199           // Condition 4: consider only features with two end points
200           std::shared_ptr<GeomDataAPI_Point2D> aP1, aP2;
201           SketchPlugin_SegmentationTools::getFeaturePoints(aCoincFeature, aP1, aP2);
202           if (aP1 && aP2) {
203             // Condition 5: consider only features, that have aP as one of they ends.
204             //              For example, we do not need an arc, coincident to aP by its center.
205             if (theEndPoint->pnt()->isEqual(aP1->pnt()) ||
206                 theEndPoint->pnt()->isEqual(aP2->pnt())) {
207               // Condition 6: only one edge can prolongate the chain. If several, we stop here.
208               nbFound++;
209               if (nbFound > 1)
210                 return false;
211
212               // One found
213               aNextEdgeFeature = aCoincFeature;
214             }
215           }
216         }
217       }
218     }
219   }
220
221   // Only one edge can prolongate the chain. If several or none, we stop here.
222   if (nbFound != 1)
223     return false;
224
225   // 2. So, we have the single edge, that prolongate the chain
226
227   // Condition 7: if we reached the very first edge of the chain
228   if (aNextEdgeFeature == theFirstEdge)
229     // Closed chain found
230     return true;
231
232   // 3. Add the found edge to the chain
233   if (isPrepend)
234     theChain.push_front(aNextEdgeFeature);
235   else
236     theChain.push_back(aNextEdgeFeature);
237   // remove from the set, if the set is used
238   if (theEdgesSet.size()) {
239     std::set<FeaturePtr>::iterator aPos = theEdgesSet.find(aNextEdgeFeature);
240     if (aPos != theEdgesSet.end())
241       theEdgesSet.erase(aPos);
242   }
243
244   // 4. Which end of aNextEdgeFeature we need to proceed
245   std::shared_ptr<GeomDataAPI_Point2D> aP1, aP2;
246   SketchPlugin_SegmentationTools::getFeaturePoints(aNextEdgeFeature, aP1, aP2);
247   if (aP2->pnt()->isEqual(theEndPoint->pnt())) {
248     // reversed
249     aP2 = aP1;
250   }
251
252   // 5. Continue gathering the chain (recursive)
253   return findWireOneWay (theFirstEdge, aNextEdgeFeature, aP2, theEdgesSet, theChain, isPrepend);
254 }
255
256 void SketchPlugin_Offset::addToSketch(const std::shared_ptr<GeomAPI_Shape>& anOffsetShape)
257 {
258   //GeomAPI_ShapeExplorer::GeomAPI_ShapeExplorer
259   ListOfShape aResEdges = GeomAlgoAPI_ShapeTools::getLowLevelSubShapes(anOffsetShape);
260   std::list<GeomShapePtr>::const_iterator aResEdgesIt = aResEdges.begin();
261   for (; aResEdgesIt != aResEdges.end(); aResEdgesIt++) {
262     GeomShapePtr aResShape = (*aResEdgesIt);
263     if (aResShape->shapeType() == GeomAPI_Shape::EDGE) {
264       // Add new feature
265       FeaturePtr aResFeature;
266       std::shared_ptr<GeomAPI_Edge> aResEdge (new GeomAPI_Edge(aResShape));
267
268       std::shared_ptr<GeomAPI_Pnt2d> aFP, aLP;
269       std::shared_ptr<GeomAPI_Pnt> aFP3d = aResEdge->firstPoint();
270       std::shared_ptr<GeomAPI_Pnt> aLP3d = aResEdge->lastPoint();
271       //if (aFP3d.get() && aLP3d.get()) {
272       if (aFP3d && aLP3d) {
273         aFP = sketch()->to2D(aFP3d);
274         aLP = sketch()->to2D(aLP3d);
275       }
276
277       if (aResEdge->isLine()) {
278         aResFeature = sketch()->addFeature(SketchPlugin_Line::ID());
279
280         std::dynamic_pointer_cast<GeomDataAPI_Point2D>
281           (aResFeature->attribute(SketchPlugin_Line::START_ID()))->setValue(aFP);
282         std::dynamic_pointer_cast<GeomDataAPI_Point2D>
283           (aResFeature->attribute(SketchPlugin_Line::END_ID()))->setValue(aLP);
284       }
285       else if (aResEdge->isArc()) {
286         std::shared_ptr<GeomAPI_Circ> aCircEdge = aResEdge->circle();
287         std::shared_ptr<GeomAPI_Pnt> aCP3d = aCircEdge->center();
288         std::shared_ptr<GeomAPI_Pnt2d> aCP = sketch()->to2D(aCP3d);
289
290         aResFeature = sketch()->addFeature(SketchPlugin_Arc::ID());
291
292         bool aWasBlocked = aResFeature->data()->blockSendAttributeUpdated(true);
293         std::dynamic_pointer_cast<GeomDataAPI_Point2D>
294           (aResFeature->attribute(SketchPlugin_Arc::CENTER_ID()))->setValue(aCP);
295         std::dynamic_pointer_cast<GeomDataAPI_Point2D>
296           (aResFeature->attribute(SketchPlugin_Arc::START_ID()))->setValue(aFP);
297         std::dynamic_pointer_cast<GeomDataAPI_Point2D>
298           (aResFeature->attribute(SketchPlugin_Arc::END_ID()))->setValue(aLP);
299         aResFeature->data()->blockSendAttributeUpdated(aWasBlocked);
300       }
301       else if (aResEdge->isCircle()) {
302         std::shared_ptr<GeomAPI_Circ> aCircEdge = aResEdge->circle();
303         std::shared_ptr<GeomAPI_Pnt> aCP3d = aCircEdge->center();
304         std::shared_ptr<GeomAPI_Pnt2d> aCP = sketch()->to2D(aCP3d);
305
306         aResFeature = sketch()->addFeature(SketchPlugin_Circle::ID());
307         std::dynamic_pointer_cast<GeomDataAPI_Point2D>
308           (aResFeature->attribute(SketchPlugin_Circle::CENTER_ID()))->setValue(aCP);
309         aResFeature->real(SketchPlugin_Circle::RADIUS_ID())->setValue(aCircEdge->radius());
310       }
311       else if (aResEdge->isEllipse()) {
312         std::shared_ptr<GeomAPI_Ellipse> anEllipseEdge = aResEdge->ellipse();
313
314         GeomPointPtr aCP3d = anEllipseEdge->center();
315         GeomPnt2dPtr aCP = sketch()->to2D(aCP3d);
316
317         GeomPointPtr aFocus3d = anEllipseEdge->firstFocus();
318         GeomPnt2dPtr aFocus = sketch()->to2D(aFocus3d);
319
320         if (aFP3d && aLP3d) {
321           // Elliptic arc
322           aResFeature = sketch()->addFeature(SketchPlugin_EllipticArc::ID());
323
324           bool aWasBlocked = aResFeature->data()->blockSendAttributeUpdated(true);
325           std::dynamic_pointer_cast<GeomDataAPI_Point2D>
326             (aResFeature->attribute(SketchPlugin_EllipticArc::CENTER_ID()))->setValue(aCP);
327           std::dynamic_pointer_cast<GeomDataAPI_Point2D>
328             (aResFeature->attribute(SketchPlugin_EllipticArc::FIRST_FOCUS_ID()))->setValue(aFocus);
329           std::dynamic_pointer_cast<GeomDataAPI_Point2D>
330             (aResFeature->attribute(SketchPlugin_EllipticArc::START_POINT_ID()))->setValue(aFP);
331           std::dynamic_pointer_cast<GeomDataAPI_Point2D>
332             (aResFeature->attribute(SketchPlugin_EllipticArc::END_POINT_ID()))->setValue(aLP);
333           aResFeature->data()->blockSendAttributeUpdated(aWasBlocked);
334         }
335         else {
336           // Ellipse
337           aResFeature = sketch()->addFeature(SketchPlugin_Ellipse::ID());
338
339           std::dynamic_pointer_cast<GeomDataAPI_Point2D>
340             (aResFeature->attribute(SketchPlugin_Ellipse::CENTER_ID()))->setValue(aCP);
341           std::dynamic_pointer_cast<GeomDataAPI_Point2D>
342             (aResFeature->attribute(SketchPlugin_Ellipse::FIRST_FOCUS_ID()))->setValue(aFocus);
343           aResFeature->real(SketchPlugin_Ellipse::MINOR_RADIUS_ID())->setValue(anEllipseEdge->minorRadius());
344         }
345       }
346       else if (aResEdge->isBSpline()) {
347         mkBSpline(aResFeature, aResEdge);
348       }
349       else {
350         // convert to b-spline
351         mkBSpline(aResFeature, aResEdge);
352       }
353
354       if (aResFeature.get()) {
355         myCreatedFeatures.insert(aResFeature);
356
357         aResFeature->boolean(SketchPlugin_SketchEntity::AUXILIARY_ID())->setValue
358           (boolean(SketchPlugin_SketchEntity::AUXILIARY_ID())->value());
359         aResFeature->execute();
360       }
361     }
362   }
363 }
364
365 void SketchPlugin_Offset::mkBSpline (FeaturePtr& theResult,
366                                      const GeomEdgePtr& theEdge)
367 {
368   GeomCurvePtr aCurve (new GeomAPI_Curve (theEdge));
369   // Forced conversion to b-spline, if aCurve is not b-spline
370   GeomAPI_BSpline aBSpline (aCurve, /*isForced*/true);
371
372   if (aBSpline.isPeriodic())
373     theResult = sketch()->addFeature(SketchPlugin_BSplinePeriodic::ID());
374   else
375     theResult = sketch()->addFeature(SketchPlugin_BSpline::ID());
376
377   theResult->integer(SketchPlugin_BSpline::DEGREE_ID())->setValue(aBSpline.degree());
378
379   AttributePoint2DArrayPtr aPolesAttr = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>
380     (theResult->attribute(SketchPlugin_BSpline::POLES_ID()));
381   std::list<GeomPointPtr> aPoles = aBSpline.poles();
382   aPolesAttr->setSize((int)aPoles.size());
383   std::list<GeomPointPtr>::iterator anIt = aPoles.begin();
384   for (int anIndex = 0; anIt != aPoles.end(); ++anIt, ++anIndex) {
385     GeomPnt2dPtr aPoleInSketch = sketch()->to2D(*anIt);
386     aPolesAttr->setPnt(anIndex, aPoleInSketch);
387   }
388
389   AttributeDoubleArrayPtr aWeightsAttr =
390       theResult->data()->realArray(SketchPlugin_BSpline::WEIGHTS_ID());
391   std::list<double> aWeights = aBSpline.weights();
392   if (aWeights.empty()) { // rational B-spline
393     int aSize = (int)aPoles.size();
394     aWeightsAttr->setSize(aSize);
395     for (int anIndex = 0; anIndex < aSize; ++anIndex)
396       aWeightsAttr->setValue(anIndex, 1.0);
397   }
398   else { // non-rational B-spline
399     aWeightsAttr->setSize((int)aWeights.size());
400     std::list<double>::iterator anIt = aWeights.begin();
401     for (int anIndex = 0; anIt != aWeights.end(); ++anIt, ++anIndex)
402       aWeightsAttr->setValue(anIndex, *anIt);
403   }
404
405   AttributeDoubleArrayPtr aKnotsAttr =
406       theResult->data()->realArray(SketchPlugin_BSpline::KNOTS_ID());
407   std::list<double> aKnots = aBSpline.knots();
408   int aSize = (int)aKnots.size();
409   aKnotsAttr->setSize(aSize);
410   std::list<double>::iterator aKIt = aKnots.begin();
411   for (int index = 0; index < aSize; ++index, ++aKIt)
412     aKnotsAttr->setValue(index, *aKIt);
413
414   AttributeIntArrayPtr aMultsAttr =
415       theResult->data()->intArray(SketchPlugin_BSpline::MULTS_ID());
416   std::list<int> aMultiplicities = aBSpline.mults();
417   aSize = (int)aMultiplicities.size();
418   aMultsAttr->setSize(aSize);
419   std::list<int>::iterator aMIt = aMultiplicities.begin();
420   for (int index = 0; index < aSize; ++index, ++aMIt)
421     aMultsAttr->setValue(index, *aMIt);
422 }
423
424 void SketchPlugin_Offset::attributeChanged(const std::string& theID)
425 {
426   ModelAPI_Tools::removeFeaturesAndReferences(myCreatedFeatures);
427   myCreatedFeatures.clear();
428 }
429
430 bool SketchPlugin_Offset::customAction(const std::string& theActionId)
431 {
432   bool isOk = false;
433   if (theActionId == ADD_WIRE_ACTION_ID()) {
434     isOk = findWires();
435   }
436   else {
437     std::string aMsg = "Error: Feature \"%1\" does not support action \"%2\".";
438     Events_InfoMessage("SketchPlugin_Offset", aMsg).arg(getKind()).arg(theActionId).send();
439   }
440   return isOk;
441 }
442
443 bool SketchPlugin_Offset::findWires()
444 {
445   AttributeRefListPtr aSelectedEdges = reflist(EDGES_ID());
446   std::list<ObjectPtr> anEdgesList = aSelectedEdges->list();
447
448   // Empty set
449   std::set<FeaturePtr> anEdgesSet;
450
451   // Processed set
452   std::set<FeaturePtr> aProcessedSet;
453
454   // Put all selected edges in a set to avoid adding them in reflist(EDGES_ID())
455   std::set<FeaturePtr> aSelectedSet;
456   std::list<ObjectPtr>::const_iterator anEdgesIt = anEdgesList.begin();
457   for (; anEdgesIt != anEdgesList.end(); anEdgesIt++) {
458     FeaturePtr aFeature = ModelAPI_Feature::feature(*anEdgesIt);
459     if (aFeature) {
460       aSelectedSet.insert(aFeature);
461     }
462   }
463
464   // Gather chains of edges
465   for (anEdgesIt = anEdgesList.begin(); anEdgesIt != anEdgesList.end(); anEdgesIt++) {
466     FeaturePtr aFeature = ModelAPI_Feature::feature(*anEdgesIt);
467     if (aFeature.get()) {
468       if (aProcessedSet.find(aFeature) != aProcessedSet.end())
469         continue;
470       aProcessedSet.insert(aFeature);
471
472       // End points (if any)
473       std::shared_ptr<GeomDataAPI_Point2D> aStartPoint, anEndPoint;
474       SketchPlugin_SegmentationTools::getFeaturePoints(aFeature, aStartPoint, anEndPoint);
475
476       std::list<FeaturePtr> aChain;
477       aChain.push_back(aFeature);
478       bool isClosed = findWireOneWay(aFeature, aFeature, aStartPoint, anEdgesSet, aChain, true);
479       if (!isClosed)
480         findWireOneWay(aFeature, aFeature, anEndPoint, anEdgesSet, aChain, false);
481
482       std::list<FeaturePtr>::iterator aChainIt = aChain.begin();
483       for (; aChainIt != aChain.end(); ++aChainIt) {
484         FeaturePtr aChainFeature = (*aChainIt);
485         aProcessedSet.insert(aChainFeature);
486         if (aSelectedSet.find(aChainFeature) == aSelectedSet.end()) {
487           aSelectedEdges->append(aChainFeature->lastResult());
488         }
489       }
490     }
491   }
492   // TODO: hilight selection in the viewer
493
494   return true;
495 }
496
497
498 AISObjectPtr SketchPlugin_Offset::getAISObject(AISObjectPtr thePrevious)
499 {
500   if (!sketch())
501     return thePrevious;
502
503   AISObjectPtr anAIS = SketcherPrs_Factory::offsetObject(this, sketch(),
504     thePrevious);
505   return anAIS;
506 }