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