1 // Copyright (C) 2007-2024 CEA, EDF, OPEN CASCADE
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 <Standard_Stream.hxx>
22 #include <GEOMImpl_Fillet1dDriver.hxx>
24 #include <GEOMImpl_Fillet1d.hxx>
25 #include <GEOMImpl_IFillet1d.hxx>
26 #include <GEOMImpl_Types.hxx>
27 #include <GEOMImpl_HealingDriver.hxx>
29 #include <GEOM_Function.hxx>
31 #include <GEOMUtils.hxx>
33 #include <ShapeFix_Wire.hxx>
34 #include <StdFail_NotDone.hxx>
35 #include <Standard_ConstructionError.hxx>
39 #include <TopoDS_Edge.hxx>
40 #include <TopoDS_Wire.hxx>
41 #include <TopoDS_Shape.hxx>
43 #include <TopExp_Explorer.hxx>
44 #include <TopTools_DataMapOfShapeShape.hxx>
45 #include <TopTools_ListOfShape.hxx>
46 #include <TopTools_ListIteratorOfListOfShape.hxx>
47 #include <TopTools_IndexedDataMapOfShapeListOfShape.hxx>
48 #include <TopTools_IndexedMapOfShape.hxx>
50 #include <BRep_Tool.hxx>
51 #include <BRep_Builder.hxx>
52 #include <BRepTools.hxx>
53 #include <BRepBuilderAPI_MakeWire.hxx>
54 #include <BRepCheck_Analyzer.hxx>
60 #include <Precision.hxx>
62 //=======================================================================
65 //=======================================================================
66 const Standard_GUID& GEOMImpl_Fillet1dDriver::GetID()
68 static Standard_GUID aFillet1dDriver("FF60908B-AB2E-4b71-B098-5C256C37D961");
69 return aFillet1dDriver;
72 //=======================================================================
73 //function : GEOMImpl_Fillet1dDriver
75 //=======================================================================
76 GEOMImpl_Fillet1dDriver::GEOMImpl_Fillet1dDriver()
80 //=======================================================================
81 //function : anotherVertex
82 //purpose : local function to get vertex from edge
83 //=======================================================================
84 static TopoDS_Vertex anotherVertex( const TopoDS_Edge& theE,
85 const TopoDS_Vertex& theV )
87 // here is an assumption that edge has different vertices
89 TopExp_Explorer anExp( theE, TopAbs_VERTEX );
90 for ( ; anExp.More(); anExp.Next() )
92 if ( BRepTools::Compare(theV,TopoDS::Vertex(anExp.Current())) /*theV.IsSame(anExp.Current())*/ )
94 aV = TopoDS::Vertex( anExp.Current() );
100 //=======================================================================
101 //function : takePlane
102 //purpose : local function returns plane of given edges
103 //=======================================================================
104 static Standard_Boolean takePlane( const TopoDS_Edge& theE1,
105 const TopoDS_Edge& theE2,
106 const TopoDS_Vertex& theV,
109 TopoDS_Vertex aV12 = anotherVertex( theE1, theV );
110 TopoDS_Vertex aV22 = anotherVertex( theE2, theV );
111 // check can closed wire be created by two initial edges
112 if ( aV12.IsNull() || aV22.IsNull() || aV12.IsSame( aV22 ) )
115 // create plane by 3 points
116 gp_XYZ aXYZ = BRep_Tool::Pnt( theV ).XYZ();
117 gp_XYZ aXYZ1 = BRep_Tool::Pnt( aV12 ).XYZ();
118 gp_XYZ aXYZ2 = BRep_Tool::Pnt( aV22 ).XYZ();
120 gp_Dir aDir1( aXYZ - aXYZ1 );
121 gp_Dir aDir2( aXYZ2 - aXYZ );
122 Standard_Real anAngle = aDir1.Angle(aDir2);
123 if ( fabs(anAngle) <= gp::Resolution() ||
124 fabs(anAngle - M_PI) <= gp::Resolution() )
126 thePlane = gp_Pln( gp_Pnt(aXYZ), aDir1^ aDir2);
128 catch (Standard_Failure&) {
134 //=======================================================================
135 //function : addEdgeRelation
136 //purpose : local function to remember relation between initial and modified edge
137 //=======================================================================
138 static void addEdgeRelation(TopTools_DataMapOfShapeShape& theMap,
139 const TopoDS_Edge& theInitE,
140 const TopoDS_Edge& theResE)
142 if ( theMap.IsBound( theInitE ) )
143 theMap.ChangeFind( theInitE ) = theResE;
145 theMap.Bind( theInitE, theResE );
148 //=======================================================================
151 //=======================================================================
152 Standard_Integer GEOMImpl_Fillet1dDriver::Execute(Handle(TFunction_Logbook)& log) const
154 if (Label().IsNull()) return 0;
155 Handle(GEOM_Function) aFunction = GEOM_Function::GetFunction(Label());
157 GEOMImpl_IFillet1d aCI (aFunction);
159 Handle(GEOM_Function) aRefShape = aCI.GetShape();
160 TopoDS_Shape aShape = aRefShape->GetValue();
163 if (aShape.ShapeType() != TopAbs_WIRE)
164 Standard_ConstructionError::Raise("Wrong arguments: polyline as wire must be given");
166 TopoDS_Wire aWire = TopoDS::Wire(aShape);
168 bool doIgnoreSecantPoints = aCI.GetFlag();
170 double rad = aCI.GetR();
171 if (rad < Precision::Confusion())
174 // collect vertices for make fillet
175 TopTools_ListOfShape aVertexList;
176 TopTools_IndexedMapOfShape anIndices;
177 TopExp::MapShapes(aWire, anIndices);
178 int aLen = aCI.GetLength();
180 for (int ii = 1; ii <= aLen; ii++) {
181 int ind = aCI.GetVertex(ii);
182 if (1 <= ind && ind <= anIndices.Extent()) {
183 TopoDS_Shape aShapeVertex = anIndices.FindKey(ind);
184 if (aShapeVertex.ShapeType() == TopAbs_VERTEX)
185 aVertexList.Append(aShapeVertex);
189 else { // get all vertices from wire
190 TopTools_MapOfShape mapShape;
191 TopExp_Explorer anExp (aWire, TopAbs_VERTEX);
192 for (; anExp.More(); anExp.Next()) {
193 if (mapShape.Add(anExp.Current()))
194 aVertexList.Append(anExp.Current());
197 if (aVertexList.IsEmpty())
198 Standard_ConstructionError::Raise("Invalid input: no vertices to make fillet");
200 // at first we try to make fillet on the initial wire (without edges fusing)
201 bool isFinalPass = !doIgnoreSecantPoints;
203 bool isAllStepsOk = MakeFillet(aWire, aVertexList, rad, isFinalPass, aResult);
205 // try to fuse collinear edges to allow bigger radius
206 if (!isFinalPass && !isAllStepsOk) {
208 TopoDS_Shape aShapeNew;
209 Handle(TColStd_HSequenceOfTransient) aVerts;
210 GEOMImpl_HealingDriver::FuseCollinearEdges(aWire, aVerts, aShapeNew);
211 TopoDS_Wire aWireNew = TopoDS::Wire(aShapeNew);
213 // 2. Rebuild the list of vertices (by coincidence)
214 Standard_Real tol, tolMax = Precision::Confusion();
215 for (TopExp_Explorer ExV (aWireNew, TopAbs_VERTEX); ExV.More(); ExV.Next()) {
216 TopoDS_Vertex Vertex = TopoDS::Vertex(ExV.Current());
217 tol = BRep_Tool::Tolerance(Vertex);
222 TopTools_ListOfShape aVertexListNew;
223 TopTools_IndexedMapOfShape anIndicesNew;
224 TopExp::MapShapes(aWireNew, anIndicesNew);
225 TopTools_ListIteratorOfListOfShape anIt (aVertexList);
226 for (; anIt.More(); anIt.Next()) {
227 TopoDS_Vertex aV = TopoDS::Vertex(anIt.Value());
228 if (anIndicesNew.Contains(aV))
229 aVertexListNew.Append(aV);
231 // try to find by coords in the new wire
232 gp_Pnt aP = BRep_Tool::Pnt(aV);
234 bool isFound = false;
235 TopTools_MapOfShape mapShape;
236 TopExp_Explorer exp (aWireNew, TopAbs_VERTEX);
237 for (; exp.More() && !isFound; exp.Next()) {
238 if (mapShape.Add(exp.Current())) {
239 TopoDS_Vertex aVi = TopoDS::Vertex(exp.Current());
240 gp_Pnt aPi = BRep_Tool::Pnt(aVi);
241 if (aPi.Distance(aP) < tolMax) {
242 aVertexListNew.Append(aVi);
250 // 3. Repeat the fillet algorithm
252 MakeFillet(aWireNew, aVertexListNew, rad, isFinalPass, aResult);
255 if (!GEOMUtils::CheckShape(aResult, true) &&
256 !GEOMUtils::FixShapeTolerance(aResult)) {
257 Standard_ConstructionError::Raise("Non valid shape result");
260 aFunction->SetValue(aResult);
261 log->SetTouched(Label());
266 //=======================================================================
267 //function : MakeFillet
269 //=======================================================================
270 bool GEOMImpl_Fillet1dDriver::MakeFillet(const TopoDS_Wire& theWire,
271 const TopTools_ListOfShape& theVertexList,
272 const Standard_Real theRadius,
274 TopoDS_Wire& theResult) const
276 // this variable is needed to break execution
277 // in case of fillet failure and try to fuse edges
278 bool isAllStepsOk = true;
280 //INFO: this algorithm implemented in assumption that user can select both
281 // vertices of some edges to make fillet. In this case we should remember
282 // already modified initial edges to take care in next fillet step
283 TopTools_DataMapOfShapeShape anEdgeToEdgeMap;
285 //iterates on vertices, and make fillet on each couple of edges
286 //collect result fillet edges in list
287 TopTools_ListOfShape aListOfNewEdge;
288 // remember relation between initial and modified map
289 TopTools_IndexedDataMapOfShapeListOfShape aMapVToEdges;
290 TopExp::MapShapesAndAncestors( theWire, TopAbs_VERTEX, TopAbs_EDGE, aMapVToEdges );
291 TopTools_ListIteratorOfListOfShape anIt( theVertexList );
292 for ( ; anIt.More(); anIt.Next() ) {
293 TopoDS_Vertex aV = TopoDS::Vertex( anIt.Value() );
294 if ( aV.IsNull() || !aMapVToEdges.Contains( aV ) )
296 const TopTools_ListOfShape& aVertexEdges = aMapVToEdges.FindFromKey( aV );
297 if ( aVertexEdges.Extent() != 2 )
298 continue; // no input data to make fillet
299 TopoDS_Edge anEdge1 = TopoDS::Edge( aVertexEdges.First() );
300 TopoDS_Edge anEdge2 = TopoDS::Edge( aVertexEdges.Last() );
301 // check if initial edges already modified in previous fillet operation
302 if ( anEdgeToEdgeMap.IsBound( anEdge1 ) ) anEdge1 = TopoDS::Edge(anEdgeToEdgeMap.Find( anEdge1 ));
303 if ( anEdgeToEdgeMap.IsBound( anEdge2 ) ) anEdge2 = TopoDS::Edge(anEdgeToEdgeMap.Find( anEdge2 ));
304 if ( anEdge1.IsNull() || anEdge2.IsNull() || anEdge1.IsSame( anEdge2 ) )
305 continue; //no input data to make fillet
307 // create plane on 2 edges
309 if ( !takePlane(anEdge1, anEdge2, aV, aPlane) )
310 continue; // seems edges does not belong to same plane or parallel (fillet can not be build)
312 GEOMImpl_Fillet1d aFilletAlgo (anEdge1, anEdge2, aPlane);
313 if (!aFilletAlgo.Perform(theRadius)) {
315 continue; // can not create fillet with given radius
317 isAllStepsOk = false;
318 break; // can not create fillet with given radius
322 // take fillet result in given vertex
323 TopoDS_Edge aModifE1, aModifE2;
324 TopoDS_Edge aNewE = aFilletAlgo.Result(BRep_Tool::Pnt(aV), aModifE1, aModifE2);
325 if (aNewE.IsNull()) {
327 continue; // no result found
329 isAllStepsOk = false;
330 break; // no result found
334 // add new created edges and take modified edges
335 aListOfNewEdge.Append(aNewE);
337 // check if wire edges modified,
338 // if yes, then map to original edges (from vertex-edges list), because edges can be modified before
339 if (aModifE1.IsNull() || !anEdge1.IsSame( aModifE1 ))
340 addEdgeRelation( anEdgeToEdgeMap, TopoDS::Edge(aVertexEdges.First()), aModifE1 );
341 if (aModifE2.IsNull() || !anEdge2.IsSame( aModifE2 ))
342 addEdgeRelation( anEdgeToEdgeMap, TopoDS::Edge(aVertexEdges.Last()), aModifE2 );
345 if (anEdgeToEdgeMap.IsEmpty() && aListOfNewEdge.IsEmpty()) {
347 StdFail_NotDone::Raise("1D Fillet can't be computed on the given shape with the given radius");
349 isAllStepsOk = false;
355 // create new wire instead of original
357 Standard_Real aVertMaxTol = -RealLast();
358 for (TopExp_Explorer anExp (theWire, TopAbs_EDGE); anExp.More(); anExp.Next()) {
359 TopoDS_Shape anEdge = anExp.Current();
360 if (!anEdgeToEdgeMap.IsBound(anEdge))
361 aListOfNewEdge.Append(anEdge);
362 else if (!anEdgeToEdgeMap.Find(anEdge).IsNull())
363 aListOfNewEdge.Append(anEdgeToEdgeMap.Find(anEdge));
365 // calculate maximum vertex tolerance of the initial wire
366 // to be used for resulting wire fixing (some gaps are possible)
367 for (TopExp_Explorer anExV (anEdge, TopAbs_VERTEX); anExV.More(); anExV.Next()) {
368 TopoDS_Vertex aVert = TopoDS::Vertex(anExV.Current());
369 aTol = BRep_Tool::Tolerance(aVert);
370 if (aTol > aVertMaxTol)
375 // Fix for Mantis issue 0023411: BEGIN
377 TopoDS_Wire aResWire;
378 B.MakeWire(aResWire);
379 TopTools_ListIteratorOfListOfShape anItNewEdge (aListOfNewEdge);
380 for (; anItNewEdge.More(); anItNewEdge.Next()) {
381 B.Add(aResWire, TopoDS::Edge(anItNewEdge.Value()));
383 Handle(ShapeFix_Wire) aFW = new ShapeFix_Wire;
386 aFW->ClosedWireMode() = theWire.Closed();
387 // Fix for Mantis issue 0023411
388 // We forced to do this because of fillet 1d algorithm feature
389 // (see function DivideEdge in file GEOMImpl_Fillet1d.cxx):
390 // a distance (gap) from new arc end to a vertex of original wire
391 // can reach (aVertexTolerance + Precision::Confusion()),
392 // so a distance between two adjacent arcs will be covered by value below
393 aFW->FixConnected(aVertMaxTol*2.0 + Precision::Confusion()*3.0);
394 theResult = aFW->WireAPIMake();
395 GEOMUtils::FixShapeTolerance(theResult, TopAbs_VERTEX, Precision::Confusion());
397 // In OCCT 7.0.0 and earlier this gap was successfully covered by
398 // implementation of BRepBuilderAPI_MakeWire::Add(TopTools_ListOfShape)
399 // (in the case described in Mantis issue 0023411)
401 //GEOMUtils::SortShapes(aListOfNewEdge);
402 //BRepBuilderAPI_MakeWire aWireTool;
403 //aWireTool.Add(aListOfNewEdge);
405 //if (!aWireTool.IsDone())
407 //theResult = aWireTool.Wire();
408 // Fix for Mantis issue 0023411: END
412 //================================================================================
414 * \brief Returns a name of creation operation and names and values of creation parameters
416 //================================================================================
418 bool GEOMImpl_Fillet1dDriver::
419 GetCreationInformation(std::string& theOperationName,
420 std::vector<GEOM_Param>& theParams)
422 if (Label().IsNull()) return 0;
423 Handle(GEOM_Function) function = GEOM_Function::GetFunction(Label());
425 GEOMImpl_IFillet1d aCI( function );
426 Standard_Integer aType = function->GetType();
428 theOperationName = "FILLET_1D";
432 AddParam( theParams, "Wire", aCI.GetShape() );
433 AddParam( theParams, "Vertexes");
434 if ( aCI.GetLength() > 1 )
435 theParams[1] << aCI.GetLength() << " vertexes: ";
436 for (int i = 1; i <= aCI.GetLength(); ++i )
437 theParams[1] << aCI.GetVertex( i ) << " ";
438 AddParam( theParams, "Radius", aCI.GetR() );
439 AddParam( theParams, "Fuse collinear edges", aCI.GetFlag() );
448 IMPLEMENT_STANDARD_RTTIEXT (GEOMImpl_Fillet1dDriver,GEOM_BaseDriver)