Boolean Operations for 2D Polygons

by

in

Friday the thirteenth! What has gone wrong so far today? Keeping my fingers crossed …

We recently discussed the issue of determining the

wall area adjacent to a room

and mentioned the fact that the simplest way to calculate such an overlapping area might be the use of a free-standing library for performing Boolean set operations on two-dimensional polygons.
As said, there are many such libraries available, as you can see by googling for “polygon boolean” or by looking at the Wikipedia article on

Boolean operations on polygons
.
I suggested possibly using the

Generic Polygon Clipper (gpc)

and its C# wrapper GpcWrapper.
Now my colleague Adam Nagy
– the

pronunciation

is ‘nadj’, according to Adam, and an interesting topic in itself –
went ahead and wrote a sample application demonstrating the use of this tool in a Revit add-in.

Adam’s sample application is named GpcNET.
It defines an external command within the namespace GpcNET.
It also includes the GpcWrapper code in the same namespace, so that all its classes are immediately available to the command class.

You need to select two floors before running the command.
If not, it will prompt you to do so and abort.
The two floors are used to define a two-dimensional polygon each.
The intersection of the two polygons is computed.
If the result is a valid polygon, a new third floor is created.
It uses the intersection result to define its profile CurveArray, and the same floor type and level as the first selected floor.

Here is the code that extracts the profile from a selected floor and creates a GPC polygon object from it:


Polygon getPolygon( Floor floor )
{
  Options geomOptions
    = app.Create.NewGeometryOptions();
 
  GeoElement elem
    = floor.get_Geometry( geomOptions );
 
  List<Vertex> vertices = new List<Vertex>();
 
  foreach( object obj in elem.Objects )
  {
    Solid solid = obj as Solid;
    if( null != solid )
    {
      Face face = solid.Faces.get_Item( 0 );
      EdgeArray loop = face.EdgeLoops.get_Item( 0 );
      foreach( Edge edge in loop )
      {
        XYZArray edgePts = edge.Tessellate();
        int n = edgePts.Size;
        for( int i = 0; i < n - 1; ++i )
        {
          XYZ p = edgePts.get_Item( i );
          vertices.Add( new Vertex( p.X, p.Y ) );
        }
      }
      break;
    }
  }
  VertexList vertexList = new VertexList();
  vertexList.NofVertices = vertices.Count;
  vertexList.Vertex = vertices.ToArray();
  Polygon poly = new Polygon();
  poly.AddContour( vertexList, false );
  return poly;
}

Here is the code for the Execute method of the command, i.e. the command mainline:


CmdResult rc = CmdResult.Failed;
app = commandData.Application;
doc = app.ActiveDocument;
 
Floor[] floors = new Floor[2] { null, null };
 
// get the first 2 floors of the selection
foreach( RvtElement e in doc.Selection.Elements )
{
  if( null == floors[0] )
  {
    floors[0] = e as Floor;
  }
  else if( null == floors[1] )
  {
    floors[1] = e as Floor;
  }
  else
  {
    break;
  }
}
 
// if the selction did not contain two floors, return
if( null == floors[0] || null == floors[1] )
{
  MessageBox.Show(
    "Please select two floors before"
    + " running this command.",
    "GpcNET" );
}
else
{
  // get the intersection
  Polygon poly1 = getPolygon( floors[0] );
 
  Polygon poly2 = getPolygon( floors[1] );
 
  Polygon poly3 = poly1.Clip(
    GpcOperation.Intersection,
    poly2 );
 
  // if it looks like a valid polygon, create a new floor
  if( 0 < poly3.NofContours )
  {
    CurveArray curves = app.Create.NewCurveArray();
    VertexList v = poly3.Contour[0];
    int i, j, n = v.NofVertices;
    for( i = 0; i < n; ++i )
    {
      j = ( i + 1 ) % n;
      Vertex p = v.Vertex[i];
      Vertex q = v.Vertex[j];
 
      curves.Append( app.Create.NewLineBound(
        app.Create.NewXYZ( p.X, p.Y, 0 ),
        app.Create.NewXYZ( q.X, q.Y, 0 ) ) );
    }
    doc.Create.NewFloor( curves,
      floors[0].FloorType,
      floors[0].Level, false );
 
    rc = CmdResult.Succeeded;
  }
}
return rc;

A short readme file and sample Revit.ini snippet are included with the project.
Here are the steps to rebuild and install it:

  1. In the Visual Studio project, adjust the Properties > Build > Output Path to point to the Revit.exe folder, e.g. “C:Program FilesRevit Architecture 2009Program”.
  2. Compile the project.
  3. Copy the gpc.dll file from the project folder to the Revit.exe folder.
  4. Adjust your Revit.ini file, for instance using the text in the sample Revit.ini file provided.

Please note that the GPC library is only free to use for non-commercial use. Please contact the vendor if you are thinking of using it in a commercial product.

The zip file includes a sample project named floors.rvt containing two floors.
Here is what they look like before running the command:

Two floors before calculating intersection

Here is the third floor created using the profile resulting from the intersection of the two, which have been temporarily half-toned:

New floor created using intersection result

To apply these results to the problem we started the discussion with, the wall area adjacent to a given room, we would need to transform the vertical wall and room faces into 2D, as described in the discussion on

polygon transformation
.

Here is the complete Visual Studio
GpcNET solution.


Comments

6 responses to “Boolean Operations for 2D Polygons”

  1. Hi Jeremy,
    I want to get the intersection of circle drawn with the floor face.
    I am using the below code that i got from the .chm help file.
    List resultSetList = new List();
    foreach (Floor temFloor in floors)
    {
    if (temFloor.Level.Id.Value == currentLevelElemId)
    {
    GeometryObjectArray objects = temFloor.get_Geometry(opt).Objects;
    foreach (GeometryObject obj in objects)
    {
    Solid solid = obj as Solid;
    if (solid != null)
    {
    PlanarFace f = GetTopFace(solid);
    if (null == f)
    {
    //do nothing
    //Debug.WriteLine(Util.ElementDescription(floor) + ” has no top face.”); ++nNullFaces;
    }
    topFaces.Add(f);
    }
    }
    }
    }
    //checking the resultset with the topface
    foreach (Face f in topFaces)
    {
    Autodesk.Revit.Enums.SetComparisonResult objResultSet = f.Intersect(detailArcCircle.GeometryCurve);
    //adding the resulset to the global list
    resultSetList.Add(objResultSet.ToString());
    }
    When circular arc cover the whole area of the floor and did not intersect with floor top face the result of the impression ‘ Autodesk.Revit.Enums.SetComparisonResult objResultSet = f.Intersect(detailArcCircle.GeometryCurve);’ came out to be ‘disjoint’ and if it intersect the result came out to be ‘subset’ which is ok in some curves. But for some curve if circle does not intersect with the floor face even then the ‘subset’ is coming as result
    Will you please elaborate how can i get the intersection of the floor face with the circular arc to get the absolutre result of ‘subset’,’disjoint’ etc.?
    Thanks in advance,
    Ritish

  2. Dear Ritish,
    Interesting question. I cannot really suggest anything to improve on the behaviour of the built-in Revit API intersection method. If you need more information than it returns, such as the fact that the floor face is completely contained within the circular area, you could create some additional geometrical elements and intersect the floor face with those as well, for instance with a line or two crossing the circle radially.
    What it really sounds like to me is that you are searching for the intersection of the floor area with a circular area. For that you could use the 2D polygon intersection discussed above, approximating the circle by a regular polygon of its own, instead of intersecting with individual curve elements which will never cover the entire area of interest to you.
    Cheers, Jeremy.

  3. Hi Jeremy,
    Using your above approach I am able to make one polygon for the floor face and one polygon from the points of detailarcs (The arc points i am getting using arc.tessellate() method)
    I am using below code to get the intersection of the two polygons in the third polygon.
    Polygon floorPoly = getFloorPolygon(revitApp, temFloor);
    Polygon arcPoly = MakePoly(detailArcCircle.GeometryCurve.Tessellate());
    Polygon resultPoly = arcPoly.Clip(GpcOperation.Intersection,floorPoly );
    but in ‘resultPoly’ I am getting the points of vertices and contour even these points are not common in the both the polygons.
    Please suggest what to do to get absolute result?
    Thanks in advance,
    Ritish

  4. Dear Ritish,
    Your code looks good to me. I have no idea what might be going wrong.
    Cheers, Jeremy.

  5. Hi Jeremy, is the above approach using a 3rd paty library still the best way of determining overlapping areas in Revit 2012?
    I need to determin floor finish areas within rooms, where the floor may have been divided with the split face command.
    Thanks,
    Neil.

  6. Dear Neil,
    I would say basically yes. There were a number of additions to the Revit API since I wrote the above, including some support for (3D) Boolean operations, but probably the easiest way to achieve what you are looking for is still the use of an external library.
    Cheers, Jeremy.

Leave a Reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading