Revit API Forum
Welcome to Autodesk’s Revit API Forums. Share your knowledge, ask questions, and explore popular Revit API topics.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Outer loops of planar face with separate parts

3 REPLIES 3
Reply
Message 1 of 4
RPTHOMAS108
1390 Views, 3 Replies

Outer loops of planar face with separate parts

Just wanted to report that I've found a more straightforward and likely reliable way of getting outer loops of planar faces (than previous discussed). This method also allows for faces made up of disjointed parts.

 

The approach is to create some undocumented solid extrusions using GeometryCreationUtilities based on curve loop parts of original face. Then extract the parallel top face from these (now separated out) and use powerful built in functionality of the PlanarFace class (PlanarFace.IsInside) to check for loops with points not inside other faces. We only have to check one point because curveloops can't be self intersecting.

 

It would be nice if we could create PlanarFace elements directly with the GeometryCreationUtilities (to cut out some of the above steps) but that does not seem possible yet. I've created an idea entry here for this.

 

I've tested this on some interesting slab shapes below and results seem reliable.

 

OuterLoop.PNG

 

 

VB:

 

Public Function GetPlanarFaceOuterLoops(ByVal commandData As Autodesk.Revit.UI.ExternalCommandData, _
                           ByRef message As String, ByVal elements As Autodesk.Revit.DB.ElementSet) As Result

        Dim IntApp As UIApplication = commandData.Application
        Dim IntUIDoc As UIDocument = IntApp.ActiveUIDocument
        If IntUIDoc Is Nothing Then Return Result.Failed Else 
        Dim IntDoc As Document = IntUIDoc.Document

        Dim R As Reference = Nothing
        Try
            R = IntUIDoc.Selection.PickObject(Selection.ObjectType.Face)
        Catch ex As Exception
        End Try
        If R Is Nothing Then Return Result.Cancelled Else 

        Dim F_El As Element = IntDoc.GetElement(R.ElementId)
        If F_El Is Nothing Then Return Result.Failed Else 

        Dim F As PlanarFace = TryCast(F_El.GetGeometryObjectFromReference(R), PlanarFace)
        If F Is Nothing Then Return Result.Failed Else 

        'Create individual CurveLoops to compare from the orginal CurveLoopArray
        'If floor has separate parts these will now be separated out into individual faces rather than one face with multiple loops.
        Dim CLoop As New List(Of Tuple(Of PlanarFace, CurveLoop, Integer))
        Dim Ix As Integer = 0
        For Each item As CurveLoop In F.GetEdgesAsCurveLoops
            Dim CLL As New List(Of CurveLoop)
            CLL.Add(item)
            'Create a solid extrusion for each CurveLoop (we want to get the planarFace from this to use built in functionality (.PlanarFace.IsInside).
            'Would be nice if you could skip this step and create PlanarFaces directly from CuveLoops? Does no appear to be possible, I only looked in GeometryCreationUtilities.
            'Below creates geometry in memory rather than actual geometry in the document, therefore no transaction required.
            Dim S As Solid = GeometryCreationUtilities.CreateExtrusionGeometry(CLL, F.FaceNormal, 1)
            For Each Fx As Face In S.Faces
                Dim PFx As PlanarFace = TryCast(Fx, PlanarFace)
                If PFx Is Nothing Then Continue For Else 
                If PFx.FaceNormal.IsAlmostEqualTo(F.FaceNormal) Then
                    Ix += 1
                    CLoop.Add(New Tuple(Of PlanarFace, CurveLoop, Integer)(PFx, item, Ix))
                End If
            Next
        Next

        Dim FirstPointIsInsideFace = Function(CL As CurveLoop, PFace As PlanarFace) As Boolean
                                         Dim Trans As Transform = PFace.ComputeDerivatives(New UV(0, 0))
                                         If CL.Count = 0 Then Return False Else 
                                         Dim Pt As XYZ = Trans.Inverse.OfPoint(CL(0).GetEndPoint(0))
                                         Dim Res As IntersectionResult = Nothing
                                         Dim out As Boolean = PFace.IsInside(New UV(Pt.X, Pt.Y), Res)
                                         Return out
                                     End Function

        Dim OuterLoops As New List(Of CurveLoop)
        'If there is more than one outerloop we know the original face has separate parts.
        'We could therefore stop the creation of floors with separate parts via posting failures etc. or more passively create a geometry checking utility to identify them.
        Dim InnerLoops As New List(Of CurveLoop)
        For Each item As Tuple(Of PlanarFace, CurveLoop, Integer) In CLoop
            'To identify an inner loop we just need to see if any of it's points are inside another face.
            'The exception to this is a loop compared to the face it was taken from. This will also be considered inside as the points are on the boundary.
            'Therefore give each item an integer ID to ensure it isn't self comparing. An alternative would be to look for J=1 instead of J=0 below (perhaps).
            Dim J As Integer = CLoop.ToList.FindAll(Function(z) FirstPointIsInsideFace(item.Item2, z.Item1) = True AndAlso z.Item3 <> item.Item3).Count
            If J = 0 Then
                OuterLoops.Add(item.Item2)
            Else
                InnerLoops.Add(item.Item2)
            End If
        Next

        Using Tx As New Transaction(IntDoc, "Outer loops")
            If Tx.Start = TransactionStatus.Started Then
                Dim SKP As SketchPlane = SketchPlane.Create(IntDoc, Plane.CreateByThreePoints(F.Origin, F.Origin + F.XVector, F.Origin + F.YVector))
                For Each Crv As CurveLoop In OuterLoops
                    For Each C As Curve In Crv
                        IntDoc.Create.NewModelCurve(C, SKP)
                    Next
                Next
                Tx.Commit()
            End If
        End Using
        Return Result.Succeeded
    End Function

 

C#

 

        public Result GetPlanarFaceOuterLoops(Autodesk.Revit.UI.ExternalCommandData commandData, ref string message, Autodesk.Revit.DB.ElementSet elements)
        {

            UIApplication IntApp = commandData.Application;
            UIDocument IntUIDoc = IntApp.ActiveUIDocument;
            if (IntUIDoc == null)
                return Result.Failed;
            Document IntDoc = IntUIDoc.Document;

            Reference R = null;
            try
            {
                R = IntUIDoc.Selection.PickObject(ObjectType.Face);
            }
            catch 
            {
            }
            if (R == null)
                return Result.Cancelled;

            Element F_El = IntDoc.GetElement(R.ElementId);
            if (F_El == null)
                return Result.Failed;

            PlanarFace F = F_El.GetGeometryObjectFromReference(R) as PlanarFace;
            if (F == null)
                return Result.Failed;

            //Create individual CurveLoops to compare from the orginal CurveLoopArray
            //If floor has separate parts these will now be separated out into individual faces rather than one face with multiple loops.
            List<Tuple<PlanarFace, CurveLoop, int>> CLoop = new List<Tuple<PlanarFace, CurveLoop, int>>();
            int Ix = 0;
            foreach (CurveLoop item in F.GetEdgesAsCurveLoops())
            {
                List<CurveLoop> CLL = new List<CurveLoop>();
                CLL.Add(item);
                //Create a solid extrusion for each CurveLoop (we want to get the planarFace from this to use built in functionality (.PlanarFace.IsInside).
                //Would be nice if you could skip this step and create PlanarFaces directly from CuveLoops? Does no appear to be possible, I only looked in GeometryCreationUtilities.
                //Below creates geometry in memory rather than actual geometry in the document, therefore no transaction required.
                Solid S = GeometryCreationUtilities.CreateExtrusionGeometry(CLL, F.FaceNormal, 1);
                foreach (Face Fx in S.Faces)
                {
                    PlanarFace PFx = Fx as PlanarFace;
                    if (PFx == null)
                        continue;
                    if (PFx.FaceNormal.IsAlmostEqualTo(F.FaceNormal))
                    {
                        Ix += 1;
                        CLoop.Add(new Tuple<PlanarFace, CurveLoop, int>(PFx, item, Ix));
                    }
                }
            }

            List<CurveLoop> OuterLoops = new List<CurveLoop>();
            //If there is more than one outerloop we know the original face has separate parts.
            //We could therefore stop the creation of floors with separate parts via posting failures etc. or more passively create a geometry checking utility to identify them.
            List<CurveLoop> InnerLoops = new List<CurveLoop>();
            foreach (Tuple<PlanarFace, CurveLoop, int> item in CLoop)
            {
                //To identify an inner loop we just need to see if any of it's points are inside another face.
                //The exception to this is a loop compared to the face it was taken from. This will also be considered inside as the points are on the boundary.
                //Therefore give each item an integer ID to ensure it isn't self comparing. An alternative would be to look for J=1 instead of J=0 below (perhaps).
                int J = CLoop.ToList().FindAll(z => FirstPointIsInsideFace(item.Item2, z.Item1) == true && z.Item3 != item.Item3).Count;
                if (J == 0)
                {
                    OuterLoops.Add(item.Item2);
                }
                else
                {
                    InnerLoops.Add(item.Item2);
                }
            }

            using (Transaction Tx = new Transaction(IntDoc, "Outer loops"))
            {
                if (Tx.Start() == TransactionStatus.Started)
                {
                    SketchPlane SKP = SketchPlane.Create(IntDoc, Plane.CreateByThreePoints(F.Origin, F.Origin + F.XVector, F.Origin + F.YVector));
                    foreach (CurveLoop Crv in OuterLoops)
                    {
                        foreach (Curve C in Crv)
                        {
                            IntDoc.Create.NewModelCurve(C, SKP);
                        }
                    }
                    Tx.Commit();
                }
            }
            return Result.Succeeded;
        }

        public bool FirstPointIsInsideFace(CurveLoop CL, PlanarFace PFace)
        {
            Transform Trans = PFace.ComputeDerivatives(new UV(0, 0));
            if (CL.Count() == 0)
                return false;
            XYZ Pt = Trans.Inverse.OfPoint(CL.ToList()[0].GetEndPoint(0));
            IntersectionResult Res = null;
            bool outval = PFace.IsInside(new UV(Pt.X, Pt.Y), out Res);
            return outval;
        }

 

3 REPLIES 3
Message 2 of 4
jeremytammik
in reply to: RPTHOMAS108

Mille grazie!

 

Published and added to The Building Coder samples:

 

http://thebuildingcoder.typepad.com/blog/2017/10/disjunct-outer-loops-from-planar-face-with-separate...

 

Cheers,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 3 of 4
minh_vc
in reply to: jeremytammik

How do I do with extrusion family ( my family is a extrusion with some curveloop)

 Can I do 

R = IntUIDoc.Selection.PickObject(ObjectType.Element);

and replace 

  PlanarFace F = F_El.GetGeometryObjectFromReference(R)
as PlanarFace; 

-> Plane plan = extrusion.Sketch.SketchPlane.GetPlane();

Message 4 of 4
RPTHOMAS108
in reply to: minh_vc

Plane has no extent so you can't test for a point inside it.

 

However Plane has a Normal, PlanarFace also has a FaceNormal. You can inspect the faces in the geometry to get the ones that have the same orientation to SketchPlane and use these as the starting point. You may have to use some rounding to get equality of the face normals.

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Rail Community