7bd071ed |
1 | // Created on: 2016-06-23 |
2 | // Copyright (c) 2016 OPEN CASCADE SAS |
3 | // Created by: Oleg AGASHIN |
4 | // |
5 | // This file is part of Open CASCADE Technology software library. |
6 | // |
7 | // This library is free software; you can redistribute it and/or modify it under |
8 | // the terms of the GNU Lesser General Public License version 2.1 as published |
9 | // by the Free Software Foundation, with special exception defined in the file |
10 | // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT |
11 | // distribution for complete text of the license and disclaimer of any warranty. |
12 | // |
13 | // Alternatively, this file may be used under the terms of Open CASCADE |
14 | // commercial license or contractual agreement. |
15 | |
16 | #include <BRepMesh_ModelHealer.hxx> |
17 | #include <BRepMesh_Deflection.hxx> |
18 | #include <BRepMesh_ShapeTool.hxx> |
19 | #include <BRepMesh_FaceChecker.hxx> |
20 | #include <BRepMesh_EdgeDiscret.hxx> |
21 | #include <IMeshData_Face.hxx> |
22 | #include <IMeshData_Wire.hxx> |
23 | #include <IMeshData_Edge.hxx> |
24 | #include <IMeshData_PCurve.hxx> |
25 | #include <OSD_Parallel.hxx> |
26 | #include <TopExp.hxx> |
27 | #include <TopoDS_Vertex.hxx> |
28 | |
29 | #ifdef DEBUG_HEALER |
30 | #include <BRepBuilderAPI_MakePolygon.hxx> |
31 | #include <BRepTools.hxx> |
32 | #include <BRep_Builder.hxx> |
33 | #include <TopoDS_Compound.hxx> |
34 | #endif |
35 | |
36 | namespace |
37 | { |
38 | //! Decreases deflection of the given edge and tries to update discretization. |
39 | class EdgeAmplifier |
40 | { |
41 | public: |
42 | //! Constructor. |
43 | EdgeAmplifier(const IMeshTools_Parameters& theParameters) |
44 | : myParameters(theParameters) |
45 | { |
46 | } |
47 | |
48 | //! Main operator. |
49 | void operator()(const IMeshData::IEdgePtr& theDEdge) const |
50 | { |
51 | const IMeshData::IEdgeHandle aDEdge = theDEdge; |
52 | aDEdge->Clear(Standard_True); |
53 | aDEdge->SetDeflection(Max(aDEdge->GetDeflection() / 3., Precision::Confusion())); |
54 | |
55 | const IMeshData::IPCurveHandle& aPCurve = aDEdge->GetPCurve(0); |
56 | const IMeshData::IFaceHandle aDFace = aPCurve->GetFace(); |
57 | Handle(IMeshTools_CurveTessellator) aTessellator = |
58 | BRepMesh_EdgeDiscret::CreateEdgeTessellator( |
59 | aDEdge, aPCurve->GetOrientation(), aDFace, myParameters); |
60 | |
61 | BRepMesh_EdgeDiscret::Tessellate3d(aDEdge, aTessellator, Standard_False); |
62 | BRepMesh_EdgeDiscret::Tessellate2d(aDEdge, Standard_False); |
63 | } |
64 | |
65 | private: |
66 | |
67 | EdgeAmplifier (const EdgeAmplifier& theOther); |
68 | |
69 | void operator=(const EdgeAmplifier& theOther); |
70 | |
71 | private: |
72 | const IMeshTools_Parameters& myParameters; |
73 | }; |
74 | |
75 | //! Returns True if some of two vertcies is same with reference one. |
76 | inline Standard_Boolean isSameWithSomeOf( |
77 | const TopoDS_Vertex& theRefVertex, |
78 | const TopoDS_Vertex& theVertex1, |
79 | const TopoDS_Vertex& theVertex2) |
80 | { |
81 | return (theRefVertex.IsSame(theVertex1) || |
82 | theRefVertex.IsSame(theVertex2)); |
83 | } |
84 | |
85 | //! Returns True if some of two vertcies is within tolerance of reference one. |
86 | inline Standard_Boolean isInToleranceWithSomeOf( |
87 | const gp_Pnt& theRefPoint, |
88 | const gp_Pnt& thePoint1, |
89 | const gp_Pnt& thePoint2, |
90 | const Standard_Real theTol) |
91 | { |
92 | const Standard_Real aSqTol = theTol * theTol; |
93 | return (theRefPoint.SquareDistance(thePoint1) < aSqTol || |
94 | theRefPoint.SquareDistance(thePoint2) < aSqTol); |
95 | } |
96 | } |
97 | |
98 | //======================================================================= |
99 | // Function: Constructor |
100 | // Purpose : |
101 | //======================================================================= |
102 | BRepMesh_ModelHealer::BRepMesh_ModelHealer() |
103 | { |
104 | } |
105 | |
106 | //======================================================================= |
107 | // Function: Destructor |
108 | // Purpose : |
109 | //======================================================================= |
110 | BRepMesh_ModelHealer::~BRepMesh_ModelHealer() |
111 | { |
112 | } |
113 | |
114 | //======================================================================= |
115 | // Function: Perform |
116 | // Purpose : |
117 | //======================================================================= |
c2a25d52 |
118 | Standard_Boolean BRepMesh_ModelHealer::performInternal( |
7bd071ed |
119 | const Handle(IMeshData_Model)& theModel, |
ce97cd97 |
120 | const IMeshTools_Parameters& theParameters, |
121 | const Message_ProgressRange& theRange) |
7bd071ed |
122 | { |
ce97cd97 |
123 | (void )theRange; |
7bd071ed |
124 | myModel = theModel; |
125 | myParameters = theParameters; |
126 | if (myModel.IsNull()) |
127 | { |
128 | return Standard_False; |
129 | } |
130 | |
131 | // MinSize is made as a constant. It is connected with |
132 | // the fact that too rude discretisation can lead to |
133 | // self-intersecting polygon, which cannot be fixed. |
134 | // As result the face will not be triangulated at all. |
135 | // E.g. see "Test mesh standard_mesh C7", the face #17. |
136 | myParameters.MinSize = Precision::Confusion(); |
137 | |
138 | myFaceIntersectingEdges = new IMeshData::DMapOfIFacePtrsMapOfIEdgePtrs; |
139 | for (Standard_Integer aFaceIt = 0; aFaceIt < myModel->FacesNb(); ++aFaceIt) |
140 | { |
141 | myFaceIntersectingEdges->Bind(myModel->GetFace(aFaceIt).get(), Handle(IMeshData::MapOfIEdgePtr)()); |
142 | } |
143 | |
144 | // TODO: Here we can process edges in order to remove close discrete points. |
145 | OSD_Parallel::For(0, myModel->FacesNb(), *this, !isParallel()); |
146 | amplifyEdges(); |
147 | |
148 | IMeshData::DMapOfIFacePtrsMapOfIEdgePtrs::Iterator aFaceIt(*myFaceIntersectingEdges); |
149 | for (; aFaceIt.More(); aFaceIt.Next()) |
150 | { |
151 | if (!aFaceIt.Value().IsNull()) |
152 | { |
153 | const IMeshData::IFaceHandle aDFace = aFaceIt.Key(); |
154 | aDFace->SetStatus(IMeshData_SelfIntersectingWire); |
155 | aDFace->SetStatus(IMeshData_Failure); |
156 | } |
157 | } |
158 | |
159 | myFaceIntersectingEdges.Nullify(); |
160 | myModel.Nullify(); // Do not hold link to model. |
161 | return Standard_True; |
162 | } |
163 | |
164 | //======================================================================= |
165 | // Function: amplifyEdges |
166 | // Purpose : |
167 | //======================================================================= |
168 | void BRepMesh_ModelHealer::amplifyEdges() |
169 | { |
170 | Handle(NCollection_IncAllocator) aTmpAlloc = |
171 | new NCollection_IncAllocator(IMeshData::MEMORY_BLOCK_SIZE_HUGE); |
172 | |
173 | Standard_Integer aAmpIt = 0; |
174 | const Standard_Real aIterNb = 5; |
175 | IMeshData::MapOfIEdgePtr aEdgesToUpdate(1, aTmpAlloc); |
176 | while (aAmpIt++ < aIterNb && popEdgesToUpdate(aEdgesToUpdate)) |
177 | { |
178 | // Try to update discretization by decreasing deflection of problematic edges. |
179 | OSD_Parallel::ForEach(aEdgesToUpdate.cbegin(), aEdgesToUpdate.cend(), |
180 | EdgeAmplifier(myParameters), |
181 | !(myParameters.InParallel && aEdgesToUpdate.Size() > 1), |
182 | aEdgesToUpdate.Size()); |
183 | |
184 | IMeshData::MapOfIFacePtr aFacesToCheck(1, aTmpAlloc); |
185 | IMeshData::MapOfIEdgePtr::Iterator aEdgeIt(aEdgesToUpdate); |
186 | for (; aEdgeIt.More(); aEdgeIt.Next()) |
187 | { |
188 | const IMeshData::IEdgeHandle aDEdge = aEdgeIt.Value(); |
189 | for (Standard_Integer aPCurveIt = 0; aPCurveIt < aDEdge->PCurvesNb(); ++aPCurveIt) |
190 | { |
191 | aFacesToCheck.Add(aDEdge->GetPCurve(aPCurveIt)->GetFace()); |
192 | } |
193 | } |
194 | |
195 | OSD_Parallel::ForEach(aFacesToCheck.cbegin(), aFacesToCheck.cend(), |
196 | *this, !(myParameters.InParallel && aFacesToCheck.Size() > 1), |
197 | aFacesToCheck.Size()); |
198 | |
199 | aEdgesToUpdate.Clear(); |
200 | aTmpAlloc->Reset(Standard_False); |
201 | } |
202 | } |
203 | |
204 | //======================================================================= |
205 | // Function: popEdgesToUpdate |
206 | // Purpose : |
207 | //======================================================================= |
208 | Standard_Boolean BRepMesh_ModelHealer::popEdgesToUpdate( |
209 | IMeshData::MapOfIEdgePtr& theEdgesToUpdate) |
210 | { |
211 | IMeshData::DMapOfIFacePtrsMapOfIEdgePtrs::Iterator aFaceIt(*myFaceIntersectingEdges); |
212 | for (; aFaceIt.More(); aFaceIt.Next()) |
213 | { |
214 | Handle(IMeshData::MapOfIEdgePtr)& aIntersections = aFaceIt.ChangeValue(); |
215 | if (!aIntersections.IsNull()) |
216 | { |
217 | theEdgesToUpdate.Unite(*aIntersections); |
218 | aIntersections.Nullify(); |
219 | } |
220 | } |
221 | |
222 | return !theEdgesToUpdate.IsEmpty(); |
223 | } |
224 | |
225 | //======================================================================= |
226 | // Function: process |
227 | // Purpose : |
228 | //======================================================================= |
229 | void BRepMesh_ModelHealer::process(const IMeshData::IFaceHandle& theDFace) const |
230 | { |
c2a25d52 |
231 | try |
7bd071ed |
232 | { |
c2a25d52 |
233 | OCC_CATCH_SIGNALS |
234 | |
235 | Handle(IMeshData::MapOfIEdgePtr)& aIntersections = myFaceIntersectingEdges->ChangeFind(theDFace.get()); |
236 | aIntersections.Nullify(); |
237 | |
238 | fixFaceBoundaries(theDFace); |
239 | |
240 | if (!theDFace->IsSet(IMeshData_Failure)) |
7bd071ed |
241 | { |
c2a25d52 |
242 | BRepMesh_FaceChecker aChecker(theDFace, myParameters); |
243 | if (!aChecker.Perform()) |
244 | { |
7bd071ed |
245 | #ifdef DEBUG_HEALER |
c2a25d52 |
246 | std::cout << "Failed : #" << aChecker.GetIntersectingEdges()->Size() << std::endl; |
7bd071ed |
247 | #endif |
c2a25d52 |
248 | aIntersections = aChecker.GetIntersectingEdges(); |
249 | } |
8c186dad |
250 | else |
251 | { |
252 | if (theDFace->WiresNb () == 1) |
253 | { |
254 | const IMeshData::IWireHandle& aDWire = theDFace->GetWire (0); |
255 | |
256 | if (aDWire->EdgesNb () == 2) |
257 | { |
258 | const IMeshData::IEdgePtr& aDEdge0 = aDWire->GetEdge (0); |
259 | const IMeshData::IEdgePtr& aDEdge1 = aDWire->GetEdge (1); |
260 | |
261 | const IMeshData::IPCurveHandle& aPCurve0 = aDEdge0->GetPCurve (theDFace.get (), aDWire->GetEdgeOrientation (0)); |
262 | const IMeshData::IPCurveHandle& aPCurve1 = aDEdge1->GetPCurve (theDFace.get (), aDWire->GetEdgeOrientation (1)); |
263 | |
264 | if (aPCurve0->ParametersNb () == 2 && aPCurve1->ParametersNb () == 2) |
265 | { |
266 | aIntersections = new IMeshData::MapOfIEdgePtr; |
267 | // a kind of degenerated face - 1 wire, 2 edges and both edges are very small |
268 | aIntersections->Add (aDEdge0); |
269 | aIntersections->Add (aDEdge1); |
270 | } |
271 | } |
272 | } |
273 | } |
7bd071ed |
274 | } |
275 | } |
c2a25d52 |
276 | catch (Standard_Failure const&) |
277 | { |
278 | theDFace->SetStatus (IMeshData_Failure); |
279 | } |
7bd071ed |
280 | } |
281 | |
282 | //======================================================================= |
283 | // Function: fixFaceBoundaries |
284 | // Purpose : |
285 | //======================================================================= |
286 | void BRepMesh_ModelHealer::fixFaceBoundaries(const IMeshData::IFaceHandle& theDFace) const |
287 | { |
288 | #ifdef DEBUG_HEALER |
289 | TopoDS_Compound aComp; |
290 | BRep_Builder aBuilder; |
291 | aBuilder.MakeCompound(aComp); |
292 | #endif |
293 | |
294 | for (int aWireIt = 0; aWireIt < theDFace->WiresNb(); ++aWireIt) |
295 | { |
296 | const IMeshData::IWireHandle& aDWire = theDFace->GetWire(aWireIt); |
297 | BRepMesh_Deflection::ComputeDeflection(aDWire, myParameters); |
298 | for (int aEdgeIt = 0; aEdgeIt < aDWire->EdgesNb(); ++aEdgeIt) |
299 | { |
300 | const int aPrevEdgeIt = (aEdgeIt + aDWire->EdgesNb() - 1) % aDWire->EdgesNb(); |
301 | const int aNextEdgeIt = (aEdgeIt + 1) % aDWire->EdgesNb(); |
302 | |
303 | const IMeshData::IEdgeHandle aPrevEdge = aDWire->GetEdge(aPrevEdgeIt); |
304 | const IMeshData::IEdgeHandle aCurrEdge = aDWire->GetEdge(aEdgeIt); |
305 | const IMeshData::IEdgeHandle aNextEdge = aDWire->GetEdge(aNextEdgeIt); |
306 | |
307 | Standard_Boolean isConnected = !getCommonVertex(aCurrEdge, aNextEdge).IsNull() && |
308 | !getCommonVertex(aPrevEdge, aCurrEdge).IsNull(); |
309 | |
310 | if (isConnected) |
311 | { |
312 | const IMeshData::IPCurveHandle& aPrevPCurve = |
313 | aPrevEdge->GetPCurve(theDFace.get(), aDWire->GetEdgeOrientation(aPrevEdgeIt)); |
314 | |
315 | const IMeshData::IPCurveHandle& aCurrPCurve = |
316 | aCurrEdge->GetPCurve(theDFace.get(), aDWire->GetEdgeOrientation(aEdgeIt)); |
317 | |
318 | const IMeshData::IPCurveHandle& aNextPCurve = |
319 | aNextEdge->GetPCurve(theDFace.get(), aDWire->GetEdgeOrientation(aNextEdgeIt)); |
320 | |
321 | isConnected = connectClosestPoints(aPrevPCurve, aCurrPCurve, aNextPCurve); |
322 | |
323 | #ifdef DEBUG_HEALER |
324 | BRepBuilderAPI_MakePolygon aPoly; |
325 | for (int i = 0; i < aCurrPCurve->ParametersNb(); ++i) |
326 | { |
327 | const gp_Pnt2d& aPnt = aCurrPCurve->GetPoint(i); |
328 | aPoly.Add(gp_Pnt(aPnt.X(), aPnt.Y(), 0.)); |
329 | } |
330 | |
331 | if (aPoly.IsDone()) |
332 | { |
333 | aBuilder.Add(aComp, aPoly.Shape()); |
334 | } |
335 | TCollection_AsciiString aName("face_discr.brep"); |
336 | BRepTools::Write(aComp, aName.ToCString()); |
337 | #endif |
338 | } |
339 | |
340 | if (!isConnected || aCurrEdge->IsSet(IMeshData_Outdated)) |
341 | { |
342 | // We have to clean face from triangulation. |
343 | theDFace->SetStatus(IMeshData_Outdated); |
344 | |
345 | if (!isConnected) |
346 | { |
347 | // Just mark wire as open, but continue fixing other inconsistencies |
348 | // in hope that this data could be suitable to build mesh somehow. |
349 | aDWire->SetStatus(IMeshData_OpenWire); |
350 | } |
351 | } |
352 | } |
353 | } |
354 | |
355 | #ifdef DEBUG_HEALER |
356 | TCollection_AsciiString aName ("face_discr.brep"); |
357 | TCollection_AsciiString aFaceName("face_geom.brep"); |
358 | BRepTools::Write(aComp, aName.ToCString()); |
359 | BRepTools::Write(theDFace->GetFace(), aFaceName.ToCString()); |
360 | #endif |
361 | |
362 | BRepMesh_Deflection::ComputeDeflection(theDFace, myParameters); |
363 | } |
364 | |
365 | //======================================================================= |
366 | // Function: hasCommonVertex |
367 | // Purpose : |
368 | //======================================================================= |
369 | TopoDS_Vertex BRepMesh_ModelHealer::getCommonVertex( |
370 | const IMeshData::IEdgeHandle& theEdge1, |
371 | const IMeshData::IEdgeHandle& theEdge2) const |
372 | { |
373 | TopoDS_Vertex aVertex1_1, aVertex1_2; |
374 | TopExp::Vertices(theEdge1->GetEdge(), aVertex1_1, aVertex1_2); |
375 | |
376 | //Test bugs moddata_2 bug428. |
377 | // restore [locate_data_file OCC428.brep] rr |
378 | // explode rr f |
379 | // explode rr_91 w |
380 | // explode rr_91_2 e |
381 | // nbshapes rr_91_2_2 |
382 | // # 0 vertices; 1 edge |
383 | |
384 | //This shape is invalid and can lead to exception in this code. |
385 | |
386 | if (aVertex1_1.IsNull() || aVertex1_2.IsNull()) |
387 | return TopoDS_Vertex(); |
388 | |
389 | if (theEdge1->GetEdge().IsSame(theEdge2->GetEdge())) |
390 | { |
391 | return aVertex1_1.IsSame(aVertex1_2) ? aVertex1_1 : TopoDS_Vertex(); |
392 | } |
393 | |
394 | TopoDS_Vertex aVertex2_1, aVertex2_2; |
395 | TopExp::Vertices(theEdge2->GetEdge(), aVertex2_1, aVertex2_2); |
396 | |
397 | if (aVertex2_1.IsNull() || aVertex2_2.IsNull()) |
398 | return TopoDS_Vertex(); |
399 | |
400 | if (isSameWithSomeOf(aVertex1_1, aVertex2_1, aVertex2_2)) |
401 | { |
402 | return aVertex1_1; |
403 | } |
404 | else if (isSameWithSomeOf(aVertex1_2, aVertex2_1, aVertex2_2)) |
405 | { |
406 | return aVertex1_2; |
407 | } |
408 | |
409 | const gp_Pnt aPnt1_1 = BRep_Tool::Pnt(aVertex1_1); |
410 | const gp_Pnt aPnt1_2 = BRep_Tool::Pnt(aVertex1_2); |
411 | const Standard_Real aTol1_1 = BRep_Tool::Tolerance(aVertex1_1); |
412 | const Standard_Real aTol1_2 = BRep_Tool::Tolerance(aVertex1_2); |
413 | |
414 | const gp_Pnt aPnt2_1 = BRep_Tool::Pnt(aVertex2_1); |
415 | const gp_Pnt aPnt2_2 = BRep_Tool::Pnt(aVertex2_2); |
416 | const Standard_Real aTol2_1 = BRep_Tool::Tolerance(aVertex2_1); |
417 | const Standard_Real aTol2_2 = BRep_Tool::Tolerance(aVertex2_2); |
418 | |
419 | if (isInToleranceWithSomeOf(aPnt1_1, aPnt2_1, aPnt2_2, aTol1_1 + Max(aTol2_1, aTol2_2))) |
420 | { |
421 | return aVertex1_1; |
422 | } |
423 | else if (isInToleranceWithSomeOf(aPnt1_2, aPnt2_1, aPnt2_2, aTol1_2 + Max(aTol2_1, aTol2_2))) |
424 | { |
425 | return aVertex1_2; |
426 | } |
427 | |
428 | return TopoDS_Vertex(); |
429 | } |
430 | |
431 | //======================================================================= |
432 | // Function: connectClosestPoints |
433 | // Purpose : |
434 | //======================================================================= |
435 | Standard_Boolean BRepMesh_ModelHealer::connectClosestPoints( |
436 | const IMeshData::IPCurveHandle& thePrevDEdge, |
437 | const IMeshData::IPCurveHandle& theCurrDEdge, |
438 | const IMeshData::IPCurveHandle& theNextDEdge) const |
439 | { |
440 | if (thePrevDEdge->IsInternal() || |
441 | theCurrDEdge->IsInternal() || |
442 | theNextDEdge->IsInternal()) |
443 | { |
444 | return Standard_True; |
445 | } |
446 | |
447 | gp_Pnt2d& aPrevFirstUV = thePrevDEdge->GetPoint(0); |
448 | gp_Pnt2d& aPrevLastUV = thePrevDEdge->GetPoint(thePrevDEdge->ParametersNb() - 1); |
449 | |
450 | if (thePrevDEdge == theCurrDEdge) |
451 | { |
452 | // Wire consists of a single edge. |
453 | aPrevFirstUV = aPrevLastUV; |
454 | return Standard_True; |
455 | } |
456 | |
457 | gp_Pnt2d& aCurrFirstUV = theCurrDEdge->GetPoint(0); |
458 | gp_Pnt2d& aCurrLastUV = theCurrDEdge->GetPoint(theCurrDEdge->ParametersNb() - 1); |
459 | |
460 | gp_Pnt2d *aPrevUV = NULL, *aCurrPrevUV = NULL; |
461 | const Standard_Real aPrevSqDist = closestPoints(aPrevFirstUV, aPrevLastUV, |
462 | aCurrFirstUV, aCurrLastUV, |
463 | aPrevUV, aCurrPrevUV); |
464 | |
465 | gp_Pnt2d *aNextUV = NULL, *aCurrNextUV = NULL; |
466 | if (thePrevDEdge == theNextDEdge) |
467 | { |
468 | // Wire consists of two edges. Connect both ends. |
469 | aNextUV = (aPrevUV == &aPrevFirstUV) ? &aPrevLastUV : &aPrevFirstUV; |
470 | aCurrNextUV = (aCurrPrevUV == &aCurrFirstUV) ? &aCurrLastUV : &aCurrFirstUV; |
471 | |
472 | *aNextUV = *aCurrNextUV; |
473 | *aPrevUV = *aCurrPrevUV; |
474 | return Standard_True; |
475 | } |
476 | |
477 | gp_Pnt2d& aNextFirstUV = theNextDEdge->GetPoint(0); |
478 | gp_Pnt2d& aNextLastUV = theNextDEdge->GetPoint(theNextDEdge->ParametersNb() - 1); |
479 | |
480 | const Standard_Real aNextSqDist = closestPoints(aNextFirstUV, aNextLastUV, |
481 | aCurrFirstUV, aCurrLastUV, |
482 | aNextUV, aCurrNextUV); |
483 | |
484 | #ifdef DEBUG_HEALER |
485 | std::cout << "PrevSqDist = " << aPrevSqDist << std::endl; |
486 | std::cout << "NextSqDist = " << aNextSqDist << std::endl; |
487 | #endif |
488 | |
489 | // Connect closest points first. This can help to identify |
490 | // which ends should be connected in case of gap. |
491 | if (aPrevSqDist - aNextSqDist > gp::Resolution()) |
492 | { |
493 | adjustSamePoints(aCurrNextUV, aNextUV, aCurrPrevUV, aPrevUV, aCurrFirstUV, aCurrLastUV, aPrevFirstUV, aPrevLastUV); |
494 | } |
495 | else |
496 | { |
497 | adjustSamePoints(aCurrPrevUV, aPrevUV, aCurrNextUV, aNextUV, aCurrFirstUV, aCurrLastUV, aNextFirstUV, aNextLastUV); |
498 | } |
499 | |
500 | return Standard_True; |
501 | } |