1 // Copyright (c) 2014 OPEN CASCADE SAS
2 //
3 // This file is part of Open CASCADE Technology software library.
4 //
5 // This library is free software; you can redistribute it and/or modify it under
6 // the terms of the GNU Lesser General Public License version 2.1 as published
7 // by the Free Software Foundation, with special exception defined in the file
8 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
9 // distribution for complete text of the license and disclaimer of any warranty.
10 //
11 // Alternatively, this file may be used under the terms of Open CASCADE
12 // commercial license or contractual agreement.
13
14
15 #include <BRep_Builder.hxx>
16 #include <BRepGProp.hxx>
17 #include <GProp_GProps.hxx>
18 #include <Message_Msg.hxx>
19 #include <Precision.hxx>
20 #include <ShapeBuild_ReShape.hxx>
21 #include <ShapeFix_FixSmallSolid.hxx>
22 #include <Standard_Type.hxx>
23 #include <TopExp_Explorer.hxx>
24 #include <TopoDS_Builder.hxx>
25 #include <TopoDS_Compound.hxx>
26 #include <TopoDS_Iterator.hxx>
27 #include <TopoDS_Shape.hxx>
28 #include <TopTools_DataMapIteratorOfDataMapOfShapeListOfShape.hxx>
29 #include <TopTools_DataMapOfShapeListOfShape.hxx>
30 #include <TopTools_DataMapOfShapeReal.hxx>
31 #include <TopTools_DataMapOfShapeShape.hxx>
32 #include <TopTools_ListIteratorOfListOfShape.hxx>
33 #include <TopTools_ListOfShape.hxx>
34 #include <TopTools_MapIteratorOfMapOfShape.hxx>
35 #include <TopTools_MapOfShape.hxx>
36
IMPLEMENT_STANDARD_RTTIEXT(ShapeFix_FixSmallSolid,ShapeFix_Root)37 IMPLEMENT_STANDARD_RTTIEXT(ShapeFix_FixSmallSolid,ShapeFix_Root)
38
39 //=======================================================================
40 //function : ShapeFix_FixSmallSolid
41 //purpose : Construct
42 //=======================================================================
43 ShapeFix_FixSmallSolid::ShapeFix_FixSmallSolid()
44 : myFixMode (0)
45 , myVolumeThreshold (Precision::Infinite())
46 , myWidthFactorThreshold (Precision::Infinite()) {}
47
48 //=======================================================================
49 //function : SetFixMode
50 //purpose : Set the mode for applying fixes of small solids.
51 //=======================================================================
SetFixMode(const Standard_Integer theMode)52 void ShapeFix_FixSmallSolid::SetFixMode (
53 const Standard_Integer theMode)
54 {
55 myFixMode = (theMode < 0 || theMode > 2) ? 0 : theMode;
56 }
57
58 //=======================================================================
59 //function : SetVolumeThreshold
60 //purpose : Set or clear volume threshold for small solids
61 //=======================================================================
SetVolumeThreshold(const Standard_Real theThreshold)62 void ShapeFix_FixSmallSolid::SetVolumeThreshold (
63 const Standard_Real theThreshold)
64 {
65 myVolumeThreshold =
66 theThreshold >= 0.0 ? theThreshold : Precision::Infinite();
67 }
68
69 //=======================================================================
70 //function : SetWidthFactorThreshold
71 //purpose : Set or clear width factor threshold for small solids
72 //=======================================================================
SetWidthFactorThreshold(const Standard_Real theThreshold)73 void ShapeFix_FixSmallSolid::SetWidthFactorThreshold (
74 const Standard_Real theThreshold)
75 {
76 myWidthFactorThreshold =
77 theThreshold >= 0.0 ? theThreshold : Precision::Infinite();
78 }
79
80 //=======================================================================
81 //function : IsValidInput
82 //purpose : auxiliary
83 //=======================================================================
84 // Check if an input shape is valid
IsValidInput(const TopoDS_Shape & theShape)85 static Standard_Boolean IsValidInput (const TopoDS_Shape& theShape)
86 {
87 if (theShape.IsNull())
88 return Standard_False;
89
90 switch (theShape.ShapeType())
91 {
92 case TopAbs_COMPOUND:
93 case TopAbs_COMPSOLID:
94 case TopAbs_SOLID:
95 return Standard_True;
96 default:
97 return Standard_False;
98 }
99 }
100
101 //=======================================================================
102 //function : Remove
103 //purpose : Remove small solids from the given shape
104 //=======================================================================
Remove(const TopoDS_Shape & theShape,const Handle (ShapeBuild_ReShape)& theContext) const105 TopoDS_Shape ShapeFix_FixSmallSolid::Remove (
106 const TopoDS_Shape& theShape,
107 const Handle(ShapeBuild_ReShape)& theContext) const
108 {
109 // Check if at least one smallness criterion is set and the shape is valid
110 if (!IsThresholdsSet() || !IsValidInput (theShape)) return theShape;
111
112 // Find and remove all small solids
113 TopExp_Explorer aSolidIter (theShape, TopAbs_SOLID);
114 for (; aSolidIter.More(); aSolidIter.Next())
115 {
116 const TopoDS_Shape& aSolid = aSolidIter.Current();
117 if (IsSmall (aSolid))
118 {
119 theContext->Remove (aSolid);
120 SendWarning ( aSolid, Message_Msg( "ShapeFix.FixSmallSolid.MSG0" ));
121 }
122 }
123
124 // Return updated shape
125 return theContext->Apply (theShape);
126 }
127
128 //=======================================================================
129 //function : ShapeArea
130 //purpose : auxiliary
131 //=======================================================================
132 // Calculate surface area of a shape
ShapeArea(const TopoDS_Shape & theShape)133 static Standard_Real ShapeArea (const TopoDS_Shape& theShape)
134 {
135 GProp_GProps aProps;
136 BRepGProp::SurfaceProperties (theShape, aProps);
137 return aProps.Mass();
138 }
139
140 //=======================================================================
141 //function : ShapeVolume
142 //purpose : auxiliary
143 //=======================================================================
144 // Calculate volume of a shape
ShapeVolume(const TopoDS_Shape & theShape)145 static Standard_Real ShapeVolume (const TopoDS_Shape& theShape)
146 {
147 GProp_GProps aProps;
148 BRepGProp::VolumeProperties (theShape, aProps);
149 return aProps.Mass();
150 }
151
152 //=======================================================================
153 //function : AddToMap
154 //purpose : auxiliary
155 //=======================================================================
156 // Append an item to a list of shapes mapped to a shape
AddToMap(TopTools_DataMapOfShapeListOfShape & theMap,const TopoDS_Shape & theKey,const TopoDS_Shape & theItem)157 static void AddToMap (TopTools_DataMapOfShapeListOfShape& theMap,
158 const TopoDS_Shape& theKey,
159 const TopoDS_Shape& theItem)
160 {
161 TopTools_ListOfShape* aListPtr = theMap.ChangeSeek (theKey);
162 if (aListPtr == NULL)
163 {
164 TopTools_ListOfShape aList;
165 aList.Append (theItem);
166 theMap.Bind (theKey, aList);
167 }
168 else
169 aListPtr->Append (theItem);
170 }
171
172 //=======================================================================
173 //function : AddToMap
174 //purpose : auxiliary
175 //=======================================================================
176 // Append items to a list of shapes mapped to a shape
AddToMap(TopTools_DataMapOfShapeListOfShape & theMap,const TopoDS_Shape & theKey,TopTools_ListOfShape & theItems)177 static void AddToMap (TopTools_DataMapOfShapeListOfShape& theMap,
178 const TopoDS_Shape& theKey,
179 TopTools_ListOfShape& theItems)
180 {
181 if (theItems.IsEmpty()) return;
182
183 TopTools_ListOfShape* aListPtr = theMap.ChangeSeek (theKey);
184 if (aListPtr == NULL)
185 theMap.Bind (theKey, theItems);
186 else
187 aListPtr->Append (theItems);
188 }
189
190 //=======================================================================
191 //function : MapFacesToShells
192 //purpose : auxiliary
193 //=======================================================================
194 // Map faces from a solid with their shells;
195 // unmap faces shared between two shells
MapFacesToShells(const TopoDS_Shape & theSolid,TopTools_DataMapOfShapeShape & theMap)196 static void MapFacesToShells (const TopoDS_Shape& theSolid,
197 TopTools_DataMapOfShapeShape& theMap)
198 {
199 TopoDS_Iterator aShellIter (theSolid);
200 for (; aShellIter.More(); aShellIter.Next())
201 {
202 const TopoDS_Shape& aShell = aShellIter.Value();
203 if (aShell.ShapeType() != TopAbs_SHELL) continue;
204
205 TopoDS_Iterator aFaceIter (aShell);
206 for (; aFaceIter.More(); aFaceIter.Next())
207 {
208 const TopoDS_Shape& aFace = aFaceIter.Value();
209 if (aFace.ShapeType() != TopAbs_FACE) continue;
210
211 if (!theMap.Bind (aFace, aShell))
212 theMap.UnBind (aFace);
213 }
214 }
215 }
216
217 //=======================================================================
218 //function : FindMostSharedShell
219 //purpose : auxiliary
220 //=======================================================================
221 // Find an outer shell having greatest sum area of
222 // all faces shared with the solid
FindMostSharedShell(const TopoDS_Shape & theSolid,const TopTools_DataMapOfShapeShape & theMapFacesToOuterShells,TopoDS_Shape & theMostSharedOuterShell,TopoDS_Shape & theMostSharedSolidShell,TopTools_ListOfShape & theOtherSolidShells)223 static Standard_Boolean FindMostSharedShell (
224 const TopoDS_Shape& theSolid,
225 const TopTools_DataMapOfShapeShape& theMapFacesToOuterShells,
226 TopoDS_Shape& theMostSharedOuterShell,
227 TopoDS_Shape& theMostSharedSolidShell,
228 TopTools_ListOfShape& theOtherSolidShells)
229 {
230 TopTools_DataMapOfShapeReal aSharedAreas;
231 Standard_Real aMaxSharedArea = 0.0;
232 const TopoDS_Shape* aMostSharedOuterShellPtr = NULL;
233 const TopoDS_Shape* aMostSharedSolidShellPtr = NULL;
234
235 // check every shell in the solid for faces shared with outer shells
236 TopoDS_Iterator aShellIter (theSolid);
237 for (; aShellIter.More(); aShellIter.Next())
238 {
239 const TopoDS_Shape& aSolidShell = aShellIter.Value();
240 if (aSolidShell.ShapeType() != TopAbs_SHELL) continue;
241
242 theOtherSolidShells.Append (aSolidShell);
243
244 TopoDS_Iterator aFaceIter (aSolidShell);
245 for (; aFaceIter.More(); aFaceIter.Next())
246 {
247 const TopoDS_Shape& aFace = aFaceIter.Value();
248 if (aFace.ShapeType() != TopAbs_FACE) continue;
249
250 // find an outer shell that shares the current face
251 const TopoDS_Shape* anOuterShellPtr = theMapFacesToOuterShells.Seek (aFace);
252 if (anOuterShellPtr == NULL) continue;
253 const TopoDS_Shape& anOuterShell = *anOuterShellPtr;
254
255 // add the face area to the sum shared area for the outer shell
256 Standard_Real anArea = ShapeArea (aFace);
257 Standard_Real* aSharedAreaPtr = aSharedAreas.ChangeSeek (anOuterShell);
258 if (aSharedAreaPtr == NULL)
259 aSharedAreas.Bind (anOuterShell, anArea);
260 else
261 anArea = (*aSharedAreaPtr) += anArea;
262
263 // if this outer shell currently has maximum shared area,
264 // remember it and the current solid's shell
265 if (aMaxSharedArea < anArea)
266 {
267 aMaxSharedArea = anArea;
268 aMostSharedOuterShellPtr = &anOuterShell;
269 aMostSharedSolidShellPtr = &aSolidShell;
270 }
271 }
272 }
273
274 // return nothing if no adjanced outer shells were found
275 if (aMostSharedSolidShellPtr == NULL)
276 return Standard_False;
277
278 // compose return values
279 theMostSharedOuterShell = *aMostSharedOuterShellPtr;
280 theMostSharedSolidShell = *aMostSharedSolidShellPtr;
281
282 // remove the most shared solid's shell from the returned list of its other shells
283 TopTools_ListIteratorOfListOfShape anOtherShellIter (theOtherSolidShells);
284 while (!anOtherShellIter.Value().IsSame (theMostSharedSolidShell))
285 anOtherShellIter.Next();
286 theOtherSolidShells.Remove (anOtherShellIter);
287
288 return Standard_True;
289 }
290
291 //=======================================================================
292 //function : MergeShells
293 //purpose : auxiliary
294 //=======================================================================
295 // Merge some shells to a base shell
MergeShells(const TopoDS_Shape & theBaseShell,TopTools_ListOfShape & theShellsToMerge,const TopTools_DataMapOfShapeShape & theMapFacesToOuterShells,TopTools_DataMapOfShapeShape & theMapNewFreeFacesToShells)296 static TopoDS_Shape MergeShells (
297 const TopoDS_Shape& theBaseShell,
298 TopTools_ListOfShape& theShellsToMerge,
299 const TopTools_DataMapOfShapeShape& theMapFacesToOuterShells,
300 TopTools_DataMapOfShapeShape& theMapNewFreeFacesToShells)
301 {
302 // Create a new shell
303 BRep_Builder aBuilder;
304 TopoDS_Shape aNewShell = theBaseShell.EmptyCopied();
305
306 // Sort the faces belogning to the merged shells:
307 // - faces shared with the base shell:
308 // keep to remove from the base shell;
309 // - faces shared with other outer shells, non-face elements:
310 // add to the new shell;
311 // - faces not shared with any outer or any merged shell:
312 // keep to add to the new shell and to the new map.
313 TopTools_MapOfShape aRemoveFaces;
314 TopTools_MapOfShape aNewFreeFaces;
315
316 TopTools_ListIteratorOfListOfShape aShellIter (theShellsToMerge);
317 for (; aShellIter.More(); aShellIter.Next())
318 {
319 TopoDS_Iterator aFaceIter (aShellIter.Value());
320 for (; aFaceIter.More(); aFaceIter.Next())
321 {
322 const TopoDS_Shape& aFace = aFaceIter.Value();
323
324 // non-face element in a shell - just add it to the new shell
325 if (aFace.ShapeType() != TopAbs_FACE)
326 {
327 aBuilder.Add (aNewShell, aFace);
328 continue;
329 }
330
331 // classify the face
332 const TopoDS_Shape* anOuterShellPtr = theMapFacesToOuterShells.Seek (aFace);
333 if (anOuterShellPtr != NULL)
334 {
335 if (anOuterShellPtr->IsSame (theBaseShell))
336 aRemoveFaces.Add (aFace); // face shared with the base shell
337 else
338 aBuilder.Add (aNewShell, aFace); // face shared with another outer shell
339 }
340 else
341 {
342 if (aNewFreeFaces.Contains (aFace))
343 aNewFreeFaces.Remove (aFace); // face shared with another merged shell
344 else
345 aNewFreeFaces.Add (aFace); // face not shared
346 }
347 }
348 }
349 theShellsToMerge.Clear();
350
351 // Add the kept faces from the merged shells to the new shell
352 TopTools_MapIteratorOfMapOfShape aNewFaceIter (aNewFreeFaces);
353 for (; aNewFaceIter.More(); aNewFaceIter.Next())
354 {
355 const TopoDS_Shape& aFace = aNewFaceIter.Key();
356 aBuilder.Add (aNewShell, aFace);
357 theMapNewFreeFacesToShells.Bind (aFace, aNewShell);
358 }
359 aNewFreeFaces.Clear();
360
361 // Add needed faces from the base shell to the new shell
362 TopoDS_Iterator aBaseFaceIter (theBaseShell);
363 for (; aBaseFaceIter.More(); aBaseFaceIter.Next())
364 {
365 const TopoDS_Shape& aFace = aBaseFaceIter.Value();
366 if (!aRemoveFaces.Contains (aFace))
367 aBuilder.Add (aNewShell, aFace);
368 }
369
370 // If there are no elements in the new shell, return null shape
371 if (aNewShell.NbChildren() == 0)
372 return TopoDS_Shape();
373
374 return aNewShell;
375 }
376
377 //=======================================================================
378 //function : AddShells
379 //purpose : auxiliary
380 //=======================================================================
381 // Add some shells to a base shell
AddShells(const TopoDS_Shape & theBaseShell,TopTools_ListOfShape & theShellsToAdd)382 static TopoDS_Compound AddShells (
383 const TopoDS_Shape& theBaseShell,
384 TopTools_ListOfShape& theShellsToAdd)
385 {
386 // Create a compound
387 BRep_Builder aBuilder;
388 TopoDS_Compound aCompound;
389 aBuilder.MakeCompound (aCompound);
390
391 // Add the base shell to the compound
392 if (!theBaseShell.IsNull())
393 aBuilder.Add (aCompound, theBaseShell);
394
395 // Add other shells to the compound
396 TopTools_ListIteratorOfListOfShape aShellIter (theShellsToAdd);
397 for (; aShellIter.More(); aShellIter.Next())
398 aBuilder.Add (aCompound, aShellIter.Value());
399
400 theShellsToAdd.Clear();
401
402 return aCompound;
403 }
404
Merge(const TopoDS_Shape & theShape,const Handle (ShapeBuild_ReShape)& theContext) const405 TopoDS_Shape ShapeFix_FixSmallSolid::Merge (
406 const TopoDS_Shape& theShape,
407 const Handle(ShapeBuild_ReShape)& theContext) const
408 {
409 // Check if at least one smallness criterion is set and the shape is valid
410 if (!IsThresholdsSet() || !IsValidInput (theShape)) return theShape;
411
412 // Find all small solids and put them in a list;
413 // Build a map of faces belonging to non-small solids
414 // but not shared between two non-small solids
415 TopTools_ListOfShape aSmallSolids;
416 TopTools_DataMapOfShapeShape aMapFacesToShells;
417
418 TopExp_Explorer aSolidIter (theShape, TopAbs_SOLID);
419 for (; aSolidIter.More(); aSolidIter.Next())
420 {
421 const TopoDS_Shape& aSolid = aSolidIter.Current();
422 if (IsSmall (aSolid))
423 aSmallSolids.Append (aSolid);
424 else
425 MapFacesToShells (aSolid, aMapFacesToShells);
426 }
427
428 // Merge all small solids adjacent to at least one non-small one;
429 // repeat this until no small solids remain or no new solids can be merged
430 TopTools_DataMapOfShapeShape aNewMapFacesToShells;
431 TopTools_DataMapOfShapeShape* aMapFacesToShellsPtr = &aMapFacesToShells;
432 TopTools_DataMapOfShapeShape* aNewMapFacesToShellsPtr = &aNewMapFacesToShells;
433 while (!aSmallSolids.IsEmpty())
434 {
435 // find small solids that may be merged on the current iteration;
436 // compose their shells in lists associated with non-small solids' shells
437 // which they should be merged to
438 TopTools_DataMapOfShapeListOfShape aShellsToMerge, aShellsToAdd;
439 TopTools_ListIteratorOfListOfShape aSmallIter(aSmallSolids);
440 while (aSmallIter.More())
441 {
442 const TopoDS_Shape& aSmallSolid = aSmallIter.Value();
443
444 // find a non-small solid's shell having greatest sum area of
445 // all faces shared with the current small solid
446 TopoDS_Shape aNonSmallSolidShell;
447 TopoDS_Shape anAdjacentShell;
448 TopTools_ListOfShape aNotAdjacentShells;
449 if (FindMostSharedShell (aSmallSolid, *aMapFacesToShellsPtr,
450 aNonSmallSolidShell, anAdjacentShell, aNotAdjacentShells))
451 {
452 // add the small solid's shells to appropriate lists
453 // associated with the selected non-small solid's shell
454 AddToMap (aShellsToMerge, aNonSmallSolidShell, anAdjacentShell);
455 AddToMap (aShellsToAdd , aNonSmallSolidShell, aNotAdjacentShells);
456
457 // remove the small solid
458 theContext->Remove (aSmallSolid);
459 SendWarning ( aSmallSolid, Message_Msg( "ShapeFix.FixSmallSolid.MSG1" ));
460
461 aSmallSolids.Remove (aSmallIter);
462 }
463 else
464 aSmallIter.Next();
465 }
466
467 // stop if no solids can be merged
468 if (aShellsToMerge.IsEmpty()) break;
469
470 // update needed non-small solids' shells by
471 // merging and adding the listed small solids' shells to them
472 TopTools_DataMapIteratorOfDataMapOfShapeListOfShape
473 aShellIter (aShellsToMerge);
474 for (; aShellIter.More(); aShellIter.Next())
475 {
476 // get the current non-small solid's shell
477 // and corresponding small solids' shells
478 const TopoDS_Shape& aBaseShell = aShellIter.Key();
479 TopTools_ListOfShape& aShellsToBeMerged =
480 (TopTools_ListOfShape&)aShellIter.Value();
481 TopTools_ListOfShape* aShellsToBeAddedPtr =
482 aShellsToAdd.ChangeSeek (aBaseShell);
483
484 // merge needed shells
485 TopoDS_Shape aNewShell = MergeShells (aBaseShell, aShellsToBeMerged,
486 *aMapFacesToShellsPtr, *aNewMapFacesToShellsPtr);
487
488 // add new shells if needed
489 if (aShellsToBeAddedPtr != NULL)
490 aNewShell = AddShells (aNewShell, *aShellsToBeAddedPtr);
491
492 // replace the current non-small solid's shell with the new one(s)
493 theContext->Replace (aBaseShell, aNewShell);
494 }
495
496 // clear the old faces map and start using the new one
497 aMapFacesToShellsPtr->Clear();
498 std::swap (aMapFacesToShellsPtr, aNewMapFacesToShellsPtr);
499 }
500
501 // Return updated shape
502 return theContext->Apply (theShape);
503 }
504
505 //=======================================================================
506 //function : IsThresholdsSet
507 //purpose : Check if at least one smallness criterion is set
508 //=======================================================================
IsThresholdsSet() const509 Standard_Boolean ShapeFix_FixSmallSolid::IsThresholdsSet() const
510 {
511 return (IsUsedVolumeThreshold() && myVolumeThreshold < Precision::Infinite()) ||
512 (IsUsedWidthFactorThreshold() && myWidthFactorThreshold < Precision::Infinite());
513 }
514
515 //=======================================================================
516 //function : IsSmall
517 //purpose : Check if a solid meets the smallness criteria
518 //=======================================================================
IsSmall(const TopoDS_Shape & theSolid) const519 Standard_Boolean ShapeFix_FixSmallSolid::IsSmall (const TopoDS_Shape& theSolid)
520 const
521 {
522 // If the volume threshold is used and set, and the solid's volume exceeds
523 // threshold value, consider the solid as not small
524 Standard_Real aVolume = ShapeVolume (theSolid);
525 if (IsUsedVolumeThreshold() && aVolume > myVolumeThreshold)
526 return Standard_False;
527
528 // If the width factor threshold is used and set,
529 // and the solid's width factor exceeds threshold value,
530 // consider the solid as not small
531 if (IsUsedWidthFactorThreshold() && myWidthFactorThreshold < Precision::Infinite())
532 {
533 Standard_Real anArea = ShapeArea (theSolid);
534 if (aVolume > myWidthFactorThreshold * anArea * 0.5)
535 return Standard_False;
536 }
537
538 // Both thresholds are met - consider the solid as small
539 return Standard_True;
540 }
541 //=======================================================================
542 //function : IsUsedWidthFactorThreshold
543 //purpose : Check if width factor threshold criterion is used
544 //=======================================================================
IsUsedWidthFactorThreshold() const545 Standard_Boolean ShapeFix_FixSmallSolid::IsUsedWidthFactorThreshold() const
546 {
547 return myFixMode == 0 || myFixMode == 1;
548 }
549 //=======================================================================
550 //function : IsUsedVolumeThreshold
551 //purpose : Check if volume threshold criterion is used
552 //=======================================================================
IsUsedVolumeThreshold() const553 Standard_Boolean ShapeFix_FixSmallSolid::IsUsedVolumeThreshold() const
554 {
555 return myFixMode == 0 || myFixMode == 2;
556 }
557