0025529: ShapeProcessAPI: introduce DropSmallSolids operator
[occt.git] / src / ShapeFix / ShapeFix_FixSmallSolid.cxx
diff --git a/src/ShapeFix/ShapeFix_FixSmallSolid.cxx b/src/ShapeFix/ShapeFix_FixSmallSolid.cxx
new file mode 100644 (file)
index 0000000..f2b2e25
--- /dev/null
@@ -0,0 +1,522 @@
+// Copyright (c) 2014 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#include <ShapeFix_FixSmallSolid.ixx>
+
+#include <Precision.hxx>
+
+#include <TopoDS_Builder.hxx>
+#include <TopoDS_Compound.hxx>
+#include <TopoDS_Iterator.hxx>
+#include <TopExp_Explorer.hxx>
+
+#include <TopTools_ListOfShape.hxx>
+#include <TopTools_ListIteratorOfListOfShape.hxx>
+#include <TopTools_MapOfShape.hxx>
+#include <TopTools_MapIteratorOfMapOfShape.hxx>
+#include <TopTools_DataMapOfShapeReal.hxx>
+#include <TopTools_DataMapOfShapeShape.hxx>
+#include <TopTools_DataMapOfShapeListOfShape.hxx>
+#include <TopTools_DataMapIteratorOfDataMapOfShapeListOfShape.hxx>
+
+#include <GProp_GProps.hxx>
+#include <BRepGProp.hxx>
+#include <BRep_Builder.hxx>
+#include <ShapeBuild_ReShape.hxx>
+
+//=======================================================================
+//function : ShapeFix_FixSmallSolid
+//purpose  : Construct
+//=======================================================================
+ShapeFix_FixSmallSolid::ShapeFix_FixSmallSolid()
+  : myVolumeThreshold      (Precision::Infinite())
+  , myWidthFactorThreshold (Precision::Infinite()) {}
+
+//=======================================================================
+//function : SetVolumeThreshold
+//purpose  : Set or clear volume threshold for small solids
+//=======================================================================
+void ShapeFix_FixSmallSolid::SetVolumeThreshold (
+  const Standard_Real theThreshold)
+{
+  myVolumeThreshold =
+    theThreshold >= 0.0 ? theThreshold : Precision::Infinite();
+}
+
+//=======================================================================
+//function : SetWidthFactorThreshold
+//purpose  : Set or clear width factor threshold for small solids
+//=======================================================================
+void ShapeFix_FixSmallSolid::SetWidthFactorThreshold (
+  const Standard_Real theThreshold)
+{
+  myWidthFactorThreshold =
+    theThreshold >= 0.0 ? theThreshold : Precision::Infinite();
+}
+
+//=======================================================================
+//function : IsValidInput
+//purpose  : auxiliary
+//=======================================================================
+// Check if an input shape is valid
+static Standard_Boolean IsValidInput (const TopoDS_Shape& theShape)
+{
+  if (theShape.IsNull())
+    return Standard_False;
+
+  switch (theShape.ShapeType())
+  {
+  case TopAbs_COMPOUND:
+  case TopAbs_COMPSOLID:
+  case TopAbs_SOLID:
+    return Standard_True;
+  default:
+    return Standard_False;
+  }
+}
+
+//=======================================================================
+//function : Remove
+//purpose  : Remove small solids from the given shape
+//=======================================================================
+TopoDS_Shape ShapeFix_FixSmallSolid::Remove (
+  const TopoDS_Shape& theShape,
+  const Handle(ShapeBuild_ReShape)& theContext) const
+{
+  // Check if at least one smallness criterion is set and the shape is valid
+  if (!IsThresholdsSet() || !IsValidInput (theShape)) return theShape;
+
+  // Find and remove all small solids
+  TopExp_Explorer aSolidIter (theShape, TopAbs_SOLID);
+  for (; aSolidIter.More(); aSolidIter.Next())
+  {
+    const TopoDS_Shape& aSolid = aSolidIter.Current();
+    if (IsSmall (aSolid))
+      theContext->Remove (aSolid);
+  }
+
+  // Return updated shape
+  return theContext->Apply (theShape);
+}
+
+//=======================================================================
+//function : ShapeArea
+//purpose  : auxiliary
+//=======================================================================
+// Calculate surface area of a shape
+static Standard_Real ShapeArea (const TopoDS_Shape& theShape)
+{
+  GProp_GProps aProps;
+  BRepGProp::SurfaceProperties (theShape, aProps);
+  return aProps.Mass();
+}
+
+//=======================================================================
+//function : ShapeVolume
+//purpose  : auxiliary
+//=======================================================================
+// Calculate volume of a shape
+static Standard_Real ShapeVolume (const TopoDS_Shape& theShape)
+{
+  GProp_GProps aProps;
+  BRepGProp::VolumeProperties (theShape, aProps);
+  return aProps.Mass();
+}
+
+//=======================================================================
+//function : AddToMap
+//purpose  : auxiliary
+//=======================================================================
+// Append an item to a list of shapes mapped to a shape
+static void AddToMap (TopTools_DataMapOfShapeListOfShape& theMap,
+                      const TopoDS_Shape& theKey,
+                      const TopoDS_Shape& theItem)
+{
+  Standard_Address aListPtr = theMap.ChangeFind1 (theKey);
+  if (aListPtr == NULL)
+  {
+    TopTools_ListOfShape aList;
+    aList.Append (theItem);
+    theMap.Bind (theKey, aList);
+  }
+  else
+    ((TopTools_ListOfShape*)aListPtr)->Append (theItem);
+}
+
+//=======================================================================
+//function : AddToMap
+//purpose  : auxiliary
+//=======================================================================
+// Append items to a list of shapes mapped to a shape
+static void AddToMap (TopTools_DataMapOfShapeListOfShape& theMap,
+                      const TopoDS_Shape& theKey,
+                      TopTools_ListOfShape& theItems)
+{
+  if (theItems.IsEmpty()) return;
+
+  Standard_Address aListPtr = theMap.ChangeFind1 (theKey);
+  if (aListPtr == NULL)
+    theMap.Bind (theKey, theItems);
+  else
+    ((TopTools_ListOfShape*)aListPtr)->Append (theItems);
+}
+
+//=======================================================================
+//function : MapFacesToShells
+//purpose  : auxiliary
+//=======================================================================
+// Map faces from a solid with their shells;
+// unmap faces shared between two shells
+static void MapFacesToShells (const TopoDS_Shape& theSolid,
+                              TopTools_DataMapOfShapeShape& theMap)
+{
+  TopoDS_Iterator aShellIter (theSolid);
+  for (; aShellIter.More(); aShellIter.Next())
+  {
+    const TopoDS_Shape& aShell = aShellIter.Value();
+    if (aShell.ShapeType() != TopAbs_SHELL) continue;
+
+    TopoDS_Iterator aFaceIter (aShell);
+    for (; aFaceIter.More(); aFaceIter.Next())
+    {
+      const TopoDS_Shape& aFace = aFaceIter.Value();
+      if (aFace.ShapeType() != TopAbs_FACE) continue;
+
+      if (!theMap.Bind (aFace, aShell))
+        theMap.UnBind (aFace);
+    }
+  }
+}
+
+//=======================================================================
+//function : FindMostSharedShell
+//purpose  : auxiliary
+//=======================================================================
+// Find an outer shell having greatest sum area of
+// all faces shared with the solid
+static Standard_Boolean FindMostSharedShell (
+  const TopoDS_Shape& theSolid,
+  const TopTools_DataMapOfShapeShape& theMapFacesToOuterShells,
+  TopoDS_Shape& theMostSharedOuterShell,
+  TopoDS_Shape& theMostSharedSolidShell,
+  TopTools_ListOfShape& theOtherSolidShells)
+{
+  TopTools_DataMapOfShapeReal aSharedAreas;
+  Standard_Real aMaxSharedArea = 0.0;
+  const TopoDS_Shape* aMostSharedOuterShellPtr = NULL;
+  const TopoDS_Shape* aMostSharedSolidShellPtr = NULL;
+
+  // check every shell in the solid for faces shared with outer shells
+  TopoDS_Iterator aShellIter (theSolid);
+  for (; aShellIter.More(); aShellIter.Next())
+  {
+    const TopoDS_Shape& aSolidShell = aShellIter.Value();
+    if (aSolidShell.ShapeType() != TopAbs_SHELL) continue;
+
+    theOtherSolidShells.Append (aSolidShell);
+
+    TopoDS_Iterator aFaceIter (aSolidShell);
+    for (; aFaceIter.More(); aFaceIter.Next())
+    {
+      const TopoDS_Shape& aFace = aFaceIter.Value();
+      if (aFace.ShapeType() != TopAbs_FACE) continue;
+    
+      // find an outer shell that shares the current face
+      Standard_Address anOuterShellPtr = theMapFacesToOuterShells.Find1 (aFace);
+      if (anOuterShellPtr == NULL) continue;
+      const TopoDS_Shape& anOuterShell = *(TopoDS_Shape*)anOuterShellPtr;
+    
+      // add the face area to the sum shared area for the outer shell
+      Standard_Real anArea = ShapeArea (aFace);
+      Standard_Address aSharedAreaPtr = aSharedAreas.ChangeFind1 (anOuterShell);
+      if (aSharedAreaPtr == NULL)
+        aSharedAreas.Bind (anOuterShell, anArea);
+      else
+        anArea = *(Standard_Real*)aSharedAreaPtr += anArea;
+    
+      // if this outer shell currently has maximum shared area,
+      // remember it and the current solid's shell
+      if (aMaxSharedArea < anArea)
+      {
+        aMaxSharedArea           = anArea;
+        aMostSharedOuterShellPtr = &anOuterShell;
+        aMostSharedSolidShellPtr = &aSolidShell;
+      }
+    }
+  }
+
+  // return nothing if no adjanced outer shells were found
+  if (aMostSharedSolidShellPtr == NULL)
+    return Standard_False;
+
+  // compose return values
+  theMostSharedOuterShell = *aMostSharedOuterShellPtr;
+  theMostSharedSolidShell = *aMostSharedSolidShellPtr;
+
+  // remove the most shared solid's shell from the returned list of its other shells
+  TopTools_ListIteratorOfListOfShape anOtherShellIter (theOtherSolidShells);
+  while (!anOtherShellIter.Value().IsSame (theMostSharedSolidShell))
+    anOtherShellIter.Next();
+  theOtherSolidShells.Remove (anOtherShellIter);
+
+  return Standard_True;
+}
+
+//=======================================================================
+//function : MergeShells
+//purpose  : auxiliary
+//=======================================================================
+// Merge some shells to a base shell
+static TopoDS_Shape MergeShells (
+  const TopoDS_Shape& theBaseShell,
+  TopTools_ListOfShape& theShellsToMerge,
+  const TopTools_DataMapOfShapeShape& theMapFacesToOuterShells,
+  TopTools_DataMapOfShapeShape& theMapNewFreeFacesToShells)
+{
+  // Create a new shell
+  BRep_Builder aBuilder;
+  TopoDS_Shape aNewShell = theBaseShell.EmptyCopied();
+
+  // Sort the faces belogning to the merged shells:
+  // - faces shared with the base shell:
+  //     keep to remove from the base shell;
+  // - faces shared with other outer shells, non-face elements:
+  //     add to the new shell;
+  // - faces not shared with any outer or any merged shell:
+  //     keep to add to the new shell and to the new map.
+  TopTools_MapOfShape aRemoveFaces;
+  TopTools_MapOfShape aNewFreeFaces;
+
+  TopTools_ListIteratorOfListOfShape aShellIter (theShellsToMerge);
+  for (; aShellIter.More(); aShellIter.Next())
+  {
+    TopoDS_Iterator aFaceIter (aShellIter.Value());
+    for (; aFaceIter.More(); aFaceIter.Next())
+    {
+      const TopoDS_Shape& aFace = aFaceIter.Value();
+
+      // non-face element in a shell - just add it to the new shell
+      if (aFace.ShapeType() != TopAbs_FACE)
+      {
+        aBuilder.Add (aNewShell, aFace);
+        continue;
+      }
+
+      // classify the face
+      Standard_Address anOuterShellPtr = theMapFacesToOuterShells.Find1 (aFace);
+      if (anOuterShellPtr != NULL)
+      {
+        if (((TopoDS_Shape*)anOuterShellPtr)->IsSame (theBaseShell))
+          aRemoveFaces.Add (aFace);        // face shared with the base shell
+        else
+          aBuilder.Add (aNewShell, aFace); // face shared with another outer shell
+      }
+      else
+      {
+        if (aNewFreeFaces.Contains (aFace))
+          aNewFreeFaces.Remove (aFace);    // face shared with another merged shell
+        else
+          aNewFreeFaces.Add (aFace);       // face not shared
+      }
+    }
+  }
+  theShellsToMerge.Clear();
+
+  // Add the kept faces from the merged shells to the new shell
+  TopTools_MapIteratorOfMapOfShape aNewFaceIter (aNewFreeFaces);
+  for (; aNewFaceIter.More(); aNewFaceIter.Next())
+  {
+    const TopoDS_Shape& aFace = aNewFaceIter.Key();
+    aBuilder.Add (aNewShell, aFace);
+    theMapNewFreeFacesToShells.Bind (aFace, aNewShell);
+  }
+  aNewFreeFaces.Clear();
+
+  // Add needed faces from the base shell to the new shell
+  TopoDS_Iterator aBaseFaceIter (theBaseShell);
+  for (; aBaseFaceIter.More(); aBaseFaceIter.Next())
+  {
+    const TopoDS_Shape& aFace = aBaseFaceIter.Value();
+    if (!aRemoveFaces.Contains (aFace))
+      aBuilder.Add (aNewShell, aFace);
+  }
+  
+  // If there are no elements in the new shell, return null shape
+  if (!TopoDS_Iterator (aNewShell).More())
+    return TopoDS_Shape();
+
+  return aNewShell;
+}
+
+//=======================================================================
+//function : AddShells
+//purpose  : auxiliary
+//=======================================================================
+// Add some shells to a base shell
+static TopoDS_Compound AddShells (
+  const TopoDS_Shape& theBaseShell,
+  TopTools_ListOfShape& theShellsToAdd)
+{
+  // Create a compound
+  BRep_Builder aBuilder;
+  TopoDS_Compound aCompound;
+  aBuilder.MakeCompound (aCompound);
+
+  // Add the base shell to the compound
+  if (!theBaseShell.IsNull())
+    aBuilder.Add (aCompound, theBaseShell);
+
+  // Add other shells to the compound
+  TopTools_ListIteratorOfListOfShape aShellIter (theShellsToAdd);
+  for (; aShellIter.More(); aShellIter.Next())
+    aBuilder.Add (aCompound, aShellIter.Value());
+
+  theShellsToAdd.Clear();
+
+  return aCompound;
+}
+
+TopoDS_Shape ShapeFix_FixSmallSolid::Merge (
+  const TopoDS_Shape& theShape,
+  const Handle(ShapeBuild_ReShape)& theContext) const
+{
+  // Check if at least one smallness criterion is set and the shape is valid
+  if (!IsThresholdsSet() || !IsValidInput (theShape)) return theShape;
+
+  // Find all small solids and put them in a list;
+  // Build a map of faces belonging to non-small solids
+  // but not shared between two non-small solids
+  TopTools_ListOfShape aSmallSolids;
+  TopTools_DataMapOfShapeShape aMapFacesToShells;
+
+  TopExp_Explorer aSolidIter (theShape, TopAbs_SOLID);
+  for (; aSolidIter.More(); aSolidIter.Next())
+  {
+    const TopoDS_Shape& aSolid = aSolidIter.Current();
+    if (IsSmall (aSolid))
+      aSmallSolids.Append (aSolid);
+    else
+      MapFacesToShells (aSolid, aMapFacesToShells);
+  }
+
+  // Merge all small solids adjacent to at least one non-small one;
+  // repeat this until no small solids remain or no new solids can be merged
+  TopTools_DataMapOfShapeShape aNewMapFacesToShells;
+  TopTools_DataMapOfShapeShape* aMapFacesToShellsPtr    = &aMapFacesToShells;
+  TopTools_DataMapOfShapeShape* aNewMapFacesToShellsPtr = &aNewMapFacesToShells;
+  while (!aSmallSolids.IsEmpty())
+  {
+    // find small solids that may be merged on the current iteration;
+    // compose their shells in lists associated with non-small solids' shells
+    // which they should be merged to
+    TopTools_DataMapOfShapeListOfShape aShellsToMerge, aShellsToAdd;
+    TopTools_ListIteratorOfListOfShape aSolidIter (aSmallSolids);
+    while (aSolidIter.More())
+    {
+      const TopoDS_Shape& aSmallSolid = aSolidIter.Value();
+
+      // find a non-small solid's shell having greatest sum area of
+      // all faces shared with the current small solid
+      TopoDS_Shape         aNonSmallSolidShell;
+      TopoDS_Shape         anAdjacentShell;
+      TopTools_ListOfShape aNotAdjacentShells;
+      if (FindMostSharedShell (aSmallSolid, *aMapFacesToShellsPtr,
+          aNonSmallSolidShell, anAdjacentShell, aNotAdjacentShells))
+      {
+        // add the small solid's shells to appropriate lists
+        // associated with the selected non-small solid's shell
+        AddToMap (aShellsToMerge, aNonSmallSolidShell, anAdjacentShell);
+        AddToMap (aShellsToAdd  , aNonSmallSolidShell, aNotAdjacentShells);
+
+        // remove the small solid
+        theContext->Remove (aSmallSolid);
+        aSmallSolids.Remove (aSolidIter);
+      }
+      else
+        aSolidIter.Next();
+    }
+
+    // stop if no solids can be merged
+    if (aShellsToMerge.IsEmpty()) break;
+
+    // update needed non-small solids' shells by
+    // merging and adding the listed small solids' shells to them
+    TopTools_DataMapIteratorOfDataMapOfShapeListOfShape
+      aShellIter (aShellsToMerge);
+    for (; aShellIter.More(); aShellIter.Next())
+    {
+      // get the current non-small solid's shell
+      // and corresponding small solids' shells
+      const TopoDS_Shape& aBaseShell = aShellIter.Key();
+      TopTools_ListOfShape& aShellsToBeMerged =
+        (TopTools_ListOfShape&)aShellIter.Value();
+      TopTools_ListOfShape* aShellsToBeAddedPtr =
+        (TopTools_ListOfShape*)aShellsToAdd.ChangeFind1 (aBaseShell);
+
+      // merge needed shells
+      TopoDS_Shape aNewShell = MergeShells (aBaseShell, aShellsToBeMerged,
+                               *aMapFacesToShellsPtr, *aNewMapFacesToShellsPtr);
+
+      // add new shells if needed
+      if (aShellsToBeAddedPtr != NULL)
+        aNewShell = AddShells (aNewShell, *aShellsToBeAddedPtr);
+
+      // replace the current non-small solid's shell with the new one(s)
+      theContext->Replace (aBaseShell, aNewShell);
+    }
+
+    // clear the old faces map and start using the new one
+    aMapFacesToShellsPtr->Clear();
+    std::swap (aMapFacesToShellsPtr, aNewMapFacesToShellsPtr);
+  }
+
+  // Return updated shape
+  return theContext->Apply (theShape);
+}
+
+//=======================================================================
+//function : IsThresholdsSet
+//purpose  : Check if at least one smallness criterion is set
+//=======================================================================
+Standard_Boolean ShapeFix_FixSmallSolid::IsThresholdsSet() const
+{
+  return myVolumeThreshold      < Precision::Infinite()
+      || myWidthFactorThreshold < Precision::Infinite();
+}
+
+//=======================================================================
+//function : IsSmall
+//purpose  : Check if a solid meets the smallness criteria
+//=======================================================================
+Standard_Boolean ShapeFix_FixSmallSolid::IsSmall (const TopoDS_Shape& theSolid)
+  const
+{
+  // If the volume threshold is set and the solid's volume exceeds it,
+  // consider the solid as not small
+  Standard_Real aVolume = ShapeVolume (theSolid);
+  if (aVolume > myVolumeThreshold)
+    return Standard_False;
+
+  // If the width factor threshold is set
+  // and the solid's width factor exceeds it
+  // consider the solid as not small
+  if (myWidthFactorThreshold < Precision::Infinite())
+  {
+    Standard_Real anArea = ShapeArea (theSolid);
+    if (aVolume > myWidthFactorThreshold * anArea * 0.5)
+      return Standard_False;
+  }
+
+  // Both thresholds are met - consider the solid as small
+  return Standard_True;
+}