1 // Copyright (c) 2014 OPEN CASCADE SAS
3 // This file is part of Open CASCADE Technology software library.
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.
11 // Alternatively, this file may be used under the terms of Open CASCADE
12 // commercial license or contractual agreement.
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>
37 IMPLEMENT_STANDARD_RTTIEXT(ShapeFix_FixSmallSolid,ShapeFix_Root)
39 //=======================================================================
40 //function : ShapeFix_FixSmallSolid
42 //=======================================================================
43 ShapeFix_FixSmallSolid::ShapeFix_FixSmallSolid()
45 , myVolumeThreshold (Precision::Infinite())
46 , myWidthFactorThreshold (Precision::Infinite()) {}
48 //=======================================================================
49 //function : SetFixMode
50 //purpose : Set the mode for applying fixes of small solids.
51 //=======================================================================
52 void ShapeFix_FixSmallSolid::SetFixMode (
53 const Standard_Integer theMode)
55 myFixMode = (theMode < 0 || theMode > 2) ? 0 : theMode;
58 //=======================================================================
59 //function : SetVolumeThreshold
60 //purpose : Set or clear volume threshold for small solids
61 //=======================================================================
62 void ShapeFix_FixSmallSolid::SetVolumeThreshold (
63 const Standard_Real theThreshold)
66 theThreshold >= 0.0 ? theThreshold : Precision::Infinite();
69 //=======================================================================
70 //function : SetWidthFactorThreshold
71 //purpose : Set or clear width factor threshold for small solids
72 //=======================================================================
73 void ShapeFix_FixSmallSolid::SetWidthFactorThreshold (
74 const Standard_Real theThreshold)
76 myWidthFactorThreshold =
77 theThreshold >= 0.0 ? theThreshold : Precision::Infinite();
80 //=======================================================================
81 //function : IsValidInput
83 //=======================================================================
84 // Check if an input shape is valid
85 static Standard_Boolean IsValidInput (const TopoDS_Shape& theShape)
87 if (theShape.IsNull())
88 return Standard_False;
90 switch (theShape.ShapeType())
93 case TopAbs_COMPSOLID:
97 return Standard_False;
101 //=======================================================================
103 //purpose : Remove small solids from the given shape
104 //=======================================================================
105 TopoDS_Shape ShapeFix_FixSmallSolid::Remove (
106 const TopoDS_Shape& theShape,
107 const Handle(ShapeBuild_ReShape)& theContext) const
109 // Check if at least one smallness criterion is set and the shape is valid
110 if (!IsThresholdsSet() || !IsValidInput (theShape)) return theShape;
112 // Find and remove all small solids
113 TopExp_Explorer aSolidIter (theShape, TopAbs_SOLID);
114 for (; aSolidIter.More(); aSolidIter.Next())
116 const TopoDS_Shape& aSolid = aSolidIter.Current();
117 if (IsSmall (aSolid))
119 theContext->Remove (aSolid);
120 SendWarning ( aSolid, Message_Msg( "ShapeFix.FixSmallSolid.MSG0" ));
124 // Return updated shape
125 return theContext->Apply (theShape);
128 //=======================================================================
129 //function : ShapeArea
130 //purpose : auxiliary
131 //=======================================================================
132 // Calculate surface area of a shape
133 static Standard_Real ShapeArea (const TopoDS_Shape& theShape)
136 BRepGProp::SurfaceProperties (theShape, aProps);
137 return aProps.Mass();
140 //=======================================================================
141 //function : ShapeVolume
142 //purpose : auxiliary
143 //=======================================================================
144 // Calculate volume of a shape
145 static Standard_Real ShapeVolume (const TopoDS_Shape& theShape)
148 BRepGProp::VolumeProperties (theShape, aProps);
149 return aProps.Mass();
152 //=======================================================================
153 //function : AddToMap
154 //purpose : auxiliary
155 //=======================================================================
156 // Append an item to a list of shapes mapped to a shape
157 static void AddToMap (TopTools_DataMapOfShapeListOfShape& theMap,
158 const TopoDS_Shape& theKey,
159 const TopoDS_Shape& theItem)
161 TopTools_ListOfShape* aListPtr = theMap.ChangeSeek (theKey);
162 if (aListPtr == NULL)
164 TopTools_ListOfShape aList;
165 aList.Append (theItem);
166 theMap.Bind (theKey, aList);
169 aListPtr->Append (theItem);
172 //=======================================================================
173 //function : AddToMap
174 //purpose : auxiliary
175 //=======================================================================
176 // Append items to a list of shapes mapped to a shape
177 static void AddToMap (TopTools_DataMapOfShapeListOfShape& theMap,
178 const TopoDS_Shape& theKey,
179 TopTools_ListOfShape& theItems)
181 if (theItems.IsEmpty()) return;
183 TopTools_ListOfShape* aListPtr = theMap.ChangeSeek (theKey);
184 if (aListPtr == NULL)
185 theMap.Bind (theKey, theItems);
187 aListPtr->Append (theItems);
190 //=======================================================================
191 //function : MapFacesToShells
192 //purpose : auxiliary
193 //=======================================================================
194 // Map faces from a solid with their shells;
195 // unmap faces shared between two shells
196 static void MapFacesToShells (const TopoDS_Shape& theSolid,
197 TopTools_DataMapOfShapeShape& theMap)
199 TopoDS_Iterator aShellIter (theSolid);
200 for (; aShellIter.More(); aShellIter.Next())
202 const TopoDS_Shape& aShell = aShellIter.Value();
203 if (aShell.ShapeType() != TopAbs_SHELL) continue;
205 TopoDS_Iterator aFaceIter (aShell);
206 for (; aFaceIter.More(); aFaceIter.Next())
208 const TopoDS_Shape& aFace = aFaceIter.Value();
209 if (aFace.ShapeType() != TopAbs_FACE) continue;
211 if (!theMap.Bind (aFace, aShell))
212 theMap.UnBind (aFace);
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
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)
230 TopTools_DataMapOfShapeReal aSharedAreas;
231 Standard_Real aMaxSharedArea = 0.0;
232 const TopoDS_Shape* aMostSharedOuterShellPtr = NULL;
233 const TopoDS_Shape* aMostSharedSolidShellPtr = NULL;
235 // check every shell in the solid for faces shared with outer shells
236 TopoDS_Iterator aShellIter (theSolid);
237 for (; aShellIter.More(); aShellIter.Next())
239 const TopoDS_Shape& aSolidShell = aShellIter.Value();
240 if (aSolidShell.ShapeType() != TopAbs_SHELL) continue;
242 theOtherSolidShells.Append (aSolidShell);
244 TopoDS_Iterator aFaceIter (aSolidShell);
245 for (; aFaceIter.More(); aFaceIter.Next())
247 const TopoDS_Shape& aFace = aFaceIter.Value();
248 if (aFace.ShapeType() != TopAbs_FACE) continue;
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;
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);
261 anArea = (*aSharedAreaPtr) += anArea;
263 // if this outer shell currently has maximum shared area,
264 // remember it and the current solid's shell
265 if (aMaxSharedArea < anArea)
267 aMaxSharedArea = anArea;
268 aMostSharedOuterShellPtr = &anOuterShell;
269 aMostSharedSolidShellPtr = &aSolidShell;
274 // return nothing if no adjanced outer shells were found
275 if (aMostSharedSolidShellPtr == NULL)
276 return Standard_False;
278 // compose return values
279 theMostSharedOuterShell = *aMostSharedOuterShellPtr;
280 theMostSharedSolidShell = *aMostSharedSolidShellPtr;
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);
288 return Standard_True;
291 //=======================================================================
292 //function : MergeShells
293 //purpose : auxiliary
294 //=======================================================================
295 // Merge some shells to a base shell
296 static TopoDS_Shape MergeShells (
297 const TopoDS_Shape& theBaseShell,
298 TopTools_ListOfShape& theShellsToMerge,
299 const TopTools_DataMapOfShapeShape& theMapFacesToOuterShells,
300 TopTools_DataMapOfShapeShape& theMapNewFreeFacesToShells)
302 // Create a new shell
303 BRep_Builder aBuilder;
304 TopoDS_Shape aNewShell = theBaseShell.EmptyCopied();
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;
316 TopTools_ListIteratorOfListOfShape aShellIter (theShellsToMerge);
317 for (; aShellIter.More(); aShellIter.Next())
319 TopoDS_Iterator aFaceIter (aShellIter.Value());
320 for (; aFaceIter.More(); aFaceIter.Next())
322 const TopoDS_Shape& aFace = aFaceIter.Value();
324 // non-face element in a shell - just add it to the new shell
325 if (aFace.ShapeType() != TopAbs_FACE)
327 aBuilder.Add (aNewShell, aFace);
332 const TopoDS_Shape* anOuterShellPtr = theMapFacesToOuterShells.Seek (aFace);
333 if (anOuterShellPtr != NULL)
335 if (anOuterShellPtr->IsSame (theBaseShell))
336 aRemoveFaces.Add (aFace); // face shared with the base shell
338 aBuilder.Add (aNewShell, aFace); // face shared with another outer shell
342 if (aNewFreeFaces.Contains (aFace))
343 aNewFreeFaces.Remove (aFace); // face shared with another merged shell
345 aNewFreeFaces.Add (aFace); // face not shared
349 theShellsToMerge.Clear();
351 // Add the kept faces from the merged shells to the new shell
352 TopTools_MapIteratorOfMapOfShape aNewFaceIter (aNewFreeFaces);
353 for (; aNewFaceIter.More(); aNewFaceIter.Next())
355 const TopoDS_Shape& aFace = aNewFaceIter.Key();
356 aBuilder.Add (aNewShell, aFace);
357 theMapNewFreeFacesToShells.Bind (aFace, aNewShell);
359 aNewFreeFaces.Clear();
361 // Add needed faces from the base shell to the new shell
362 TopoDS_Iterator aBaseFaceIter (theBaseShell);
363 for (; aBaseFaceIter.More(); aBaseFaceIter.Next())
365 const TopoDS_Shape& aFace = aBaseFaceIter.Value();
366 if (!aRemoveFaces.Contains (aFace))
367 aBuilder.Add (aNewShell, aFace);
370 // If there are no elements in the new shell, return null shape
371 if (!TopoDS_Iterator (aNewShell).More())
372 return TopoDS_Shape();
377 //=======================================================================
378 //function : AddShells
379 //purpose : auxiliary
380 //=======================================================================
381 // Add some shells to a base shell
382 static TopoDS_Compound AddShells (
383 const TopoDS_Shape& theBaseShell,
384 TopTools_ListOfShape& theShellsToAdd)
387 BRep_Builder aBuilder;
388 TopoDS_Compound aCompound;
389 aBuilder.MakeCompound (aCompound);
391 // Add the base shell to the compound
392 if (!theBaseShell.IsNull())
393 aBuilder.Add (aCompound, theBaseShell);
395 // Add other shells to the compound
396 TopTools_ListIteratorOfListOfShape aShellIter (theShellsToAdd);
397 for (; aShellIter.More(); aShellIter.Next())
398 aBuilder.Add (aCompound, aShellIter.Value());
400 theShellsToAdd.Clear();
405 TopoDS_Shape ShapeFix_FixSmallSolid::Merge (
406 const TopoDS_Shape& theShape,
407 const Handle(ShapeBuild_ReShape)& theContext) const
409 // Check if at least one smallness criterion is set and the shape is valid
410 if (!IsThresholdsSet() || !IsValidInput (theShape)) return theShape;
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;
418 TopExp_Explorer aSolidIter (theShape, TopAbs_SOLID);
419 for (; aSolidIter.More(); aSolidIter.Next())
421 const TopoDS_Shape& aSolid = aSolidIter.Current();
422 if (IsSmall (aSolid))
423 aSmallSolids.Append (aSolid);
425 MapFacesToShells (aSolid, aMapFacesToShells);
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())
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())
442 const TopoDS_Shape& aSmallSolid = aSmallIter.Value();
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))
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);
457 // remove the small solid
458 theContext->Remove (aSmallSolid);
459 SendWarning ( aSmallSolid, Message_Msg( "ShapeFix.FixSmallSolid.MSG1" ));
461 aSmallSolids.Remove (aSmallIter);
467 // stop if no solids can be merged
468 if (aShellsToMerge.IsEmpty()) break;
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())
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);
484 // merge needed shells
485 TopoDS_Shape aNewShell = MergeShells (aBaseShell, aShellsToBeMerged,
486 *aMapFacesToShellsPtr, *aNewMapFacesToShellsPtr);
488 // add new shells if needed
489 if (aShellsToBeAddedPtr != NULL)
490 aNewShell = AddShells (aNewShell, *aShellsToBeAddedPtr);
492 // replace the current non-small solid's shell with the new one(s)
493 theContext->Replace (aBaseShell, aNewShell);
496 // clear the old faces map and start using the new one
497 aMapFacesToShellsPtr->Clear();
498 std::swap (aMapFacesToShellsPtr, aNewMapFacesToShellsPtr);
501 // Return updated shape
502 return theContext->Apply (theShape);
505 //=======================================================================
506 //function : IsThresholdsSet
507 //purpose : Check if at least one smallness criterion is set
508 //=======================================================================
509 Standard_Boolean ShapeFix_FixSmallSolid::IsThresholdsSet() const
511 return (IsUsedVolumeThreshold() && myVolumeThreshold < Precision::Infinite()) ||
512 (IsUsedWidthFactorThreshold() && myWidthFactorThreshold < Precision::Infinite());
515 //=======================================================================
517 //purpose : Check if a solid meets the smallness criteria
518 //=======================================================================
519 Standard_Boolean ShapeFix_FixSmallSolid::IsSmall (const TopoDS_Shape& theSolid)
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;
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())
533 Standard_Real anArea = ShapeArea (theSolid);
534 if (aVolume > myWidthFactorThreshold * anArea * 0.5)
535 return Standard_False;
538 // Both thresholds are met - consider the solid as small
539 return Standard_True;
541 //=======================================================================
542 //function : IsUsedWidthFactorThreshold
543 //purpose : Check if width factor threshold criterion is used
544 //=======================================================================
545 Standard_Boolean ShapeFix_FixSmallSolid::IsUsedWidthFactorThreshold() const
547 return myFixMode == 0 || myFixMode == 1;
549 //=======================================================================
550 //function : IsUsedVolumeThreshold
551 //purpose : Check if volume threshold criterion is used
552 //=======================================================================
553 Standard_Boolean ShapeFix_FixSmallSolid::IsUsedVolumeThreshold() const
555 return myFixMode == 0 || myFixMode == 2;