Is the first Edgeloop still the outer loop?

Is the first Edgeloop still the outer loop?

pmeigneux
Advocate Advocate
6,118 Views
31 Replies
Message 1 of 32

Is the first Edgeloop still the outer loop?

pmeigneux
Advocate
Advocate

Hello,

 

I try to determine for all types of surfaces what is the outer Edgeloop.

 

Is it always the first one on the face.EdgeLoops?

 

If not how to find it?

 

Philippe.

0 Likes
Accepted solutions (2)
6,119 Views
31 Replies
Replies (31)
Message 2 of 32

jeremytammik
Autodesk
Autodesk
Accepted solution
0 Likes
Message 3 of 32

RPTHOMAS108
Mentor
Mentor

The area approach mentioned in the above link is probably more reliable but I had an alternate idea based on getting all co-ordinates linked to the edge loops they are from and then finding the minimums in a certain direction. Below I've taken min.x but but same would apply for min.y, max.x and max.y

 

I could not think of a situation where if you convert the 3D co-ordinates of each edge curve to a co-ordinate system of the plane you would end up with a min.x for the outer loop greater than the min.x for the inner loop. Similarly the max.x of the outer loop should be greater than the max.x of the inner loop. This appeared to be true for most cases but then there was the issue of circular curves where the two end points could be further back than a point projecting into the centre of the curve. So I decided to treat cyclic curves differently by checking for min.x along the curve.

 

I came up with the two below functions one for planner faces and the other for faces in general. They both seem to give reliable results on the cases in the image below (based on expected edge counts for the outer loops). However there may be a case that fail. The parts related to co-ordinate transformations may need looking at (there may be better approaches). I checked the z of each transformed point to see that it was 0 or virtually 0, so seems to be as expected.

 

For the more generalised method I'm using API functionality which projects a point onto a face, I was expecting some possible intersection errors since I'm checking points on the boundary of the face. I've not checked such things in detail.

 

In summary the area method mentioned in link is likely better and more reliable, not sure which is faster.

 

 

SlabTypes.PNG

 

 

Public Shared Function Run(ByVal commandData As Autodesk.Revit.UI.ExternalCommandData) As Autodesk.Revit.UI.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 Face = TryCast(F_El.GetGeometryObjectFromReference(R), Face)
        If F Is Nothing Then Return Result.Failed Else 

        Dim EA1 As EdgeArray = PlannerFaceOuterLoop(F)
        Dim EA2 As EdgeArray = OuterLoop(F)

        Return Result.Succeeded
    End Function

    Public Shared Function OuterLoop(F As Face) As EdgeArray

        Dim MinU = Function(C As Curve) As Double

                       If C.IsCyclic Then
                           Dim Min As Double = Double.NaN
                           For I = 0 To 20
                               Dim Param As Double = I / 20
                               Dim CuvPt As XYZ = C.Evaluate(Param, True)
                               Dim IR As IntersectionResult = F.Project(CuvPt)
                               If IR Is Nothing = False Then
                                   If Min = Double.NaN Then
                                       Min = IR.UVPoint.U
                                   Else
                                       If IR.UVPoint.U < Min Then
                                           Min = IR.UVPoint.U
                                       End If
                                   End If
                               End If
                           Next
                           Return Min
                       Else
                           Dim Pt1 As XYZ = C.GetEndPoint(0)
                           Dim Pt2 As XYZ = C.GetEndPoint(1)

                           Dim IR1 As IntersectionResult = F.Project(Pt1)
                           Dim IR2 As IntersectionResult = F.Project(Pt2)

                           If IR1 Is Nothing OrElse IR2 Is Nothing Then Return Double.NaN

                           If IR1.UVPoint.U < IR2.UVPoint.U Then
                               Return IR1.UVPoint.U
                           Else
                               Return IR2.UVPoint.U
                           End If
                       End If
                   End Function

        Dim Loops As IEnumerable(Of EdgeArray)
        Loops = From L As EdgeArray In F.EdgeLoops
                Select L

        Dim Cv As IEnumerable(Of Tuple(Of EdgeArray, Double))
        Cv = From L2 As EdgeArray In Loops
                From L3 As Edge In L2
                Let Mu As Double = MinU(L3.AsCurve)
                Where Mu <> Double.NaN
                Select New Tuple(Of EdgeArray, Double)(L2, Mu)


        Dim Out As Tuple(Of EdgeArray, Double) = Cv.ToList.Find(Function(Jx) Jx.Item2 = Cv.Min(Function(Jv) Jv.Item2))
        If Out Is Nothing Then
            Return Nothing
        Else
            Return Out.Item1
        End If


    End Function

    Public Shared Function PlannerFaceOuterLoop(F As Face) As EdgeArray
        Dim PF As PlanarFace = TryCast(F, PlanarFace)
        If PF Is Nothing Then Return Nothing Else 

        Dim FN As XYZ = PF.FaceNormal
        Dim T As Transform = Transform.Identity
        T.BasisZ = FN
        T.BasisX = PF.XVector
        T.BasisY = PF.YVector
        T.Origin = PF.Origin

        'Dim Zeds As New List(Of Double)

        Dim MinU = Function(C As Curve) As Double
                       If C.IsCyclic Then
                           Dim Min As Double = Nothing
                           For I = 0 To 20
                               Dim Param As Double = I / 20
                               Dim CuvPt As XYZ = C.Evaluate(Param, True)
                               Dim XYZt As XYZ = T.Inverse.OfPoint(CuvPt)
                               If I = 0 Then
                                   Min = XYZt.X
                               Else
                                   If XYZt.X < Min Then
                                       Min = XYZt.X
                                   End If
                               End If
                           Next
                           Return Min
                       Else
                           Dim Pt1 As XYZ = C.GetEndPoint(0)
                           Dim Pt2 As XYZ = C.GetEndPoint(1)
                           Dim XYZp As XYZ() = New XYZ(1) {}

                           XYZp(0) = T.Inverse.OfPoint(Pt1)
                           XYZp(1) = T.Inverse.OfPoint(Pt2)

                           ' Zeds.Add(XYZp(0).Z)
                           ' Zeds.Add(XYZp(1).Z)

                           If XYZp(0).X < XYZp(1).X Then
                               Return XYZp(0).X
                           Else
                               Return XYZp(1).X
                           End If
                       End If


                   End Function

        Dim Loops As IEnumerable(Of EdgeArray)
        Loops = From L As EdgeArray In F.EdgeLoops
                Select L

        Dim Cv As IEnumerable(Of Tuple(Of EdgeArray, Double))
        Cv = From L2 As EdgeArray In Loops
                From L3 As Edge In L2
                Let Mu As Double = MinU(L3.AsCurve)
                Select New Tuple(Of EdgeArray, Double)(L2, Mu)


        Dim Out As Tuple(Of EdgeArray, Double) = Cv.ToList.Find(Function(Jx) Jx.Item2 = Cv.Min(Function(Jv) Jv.Item2))
        If Out Is Nothing Then
            Return Nothing
        Else
            Return Out.Item1
        End If


    End Function

 

 

 

0 Likes
Message 4 of 32

jeremytammik
Autodesk
Autodesk

Dear Rpthomas108,

 

Thank you very much for sharing this nice idea and implementation!

 

It makes sense to me. Serious testing will be in order 🙂

 

I hope Philippe found it useful. How sad that he does not say.

 

I have a couple of suggestions:

 

  • When you say 'planner' face, you presumably mean 'planar'.
  • Why just handle cyclic curves the way you do? Don't arcs deserver the same treatment, even if they are not cyclic? And other non-linear curves?
  • Instead of using `C.Evaluate` twenty times over, you could call the curve Tessellate method. That would do it for you. It also handles the case of a linear curve gracefully, returning just the two end points, so your MinU function could be shortened and simplified.

 

Cheers,

 

Jeremy



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

0 Likes
Message 5 of 32

jeremytammik
Autodesk
Autodesk

I found your code really hard to understand, so I refactored it and rewrote some parts like this:

 

<Transaction(TransactionMode.ReadOnly)>
Public Class AdskCommand
  Implements IExternalCommand

  Shared Function MinU1(C As Curve, F As Face) As Double

    If C.IsCyclic Then
      Dim Min As Double = Double.NaN
      For I = 0 To 20
        Dim Param As Double = I / 20
        Dim CuvPt As XYZ = C.Evaluate(Param, True)
        Dim IR As IntersectionResult = F.Project(CuvPt)
        If IR Is Nothing = False Then
          If Min = Double.NaN Then
            Min = IR.UVPoint.U
          Else
            If IR.UVPoint.U < Min Then
              Min = IR.UVPoint.U
            End If
          End If
        End If
      Next
      Return Min
    Else
      Dim Pt1 As XYZ = C.GetEndPoint(0)
      Dim Pt2 As XYZ = C.GetEndPoint(1)

      Dim IR1 As IntersectionResult = F.Project(Pt1)
      Dim IR2 As IntersectionResult = F.Project(Pt2)

      If IR1 Is Nothing OrElse IR2 Is Nothing Then Return Double.NaN

      If IR1.UVPoint.U < IR2.UVPoint.U Then
        Return IR1.UVPoint.U
      Else
        Return IR2.UVPoint.U
      End If
    End If
  End Function
  Public Shared Function OuterLoop(F As Face) As EdgeArray

    Dim Loops As EdgeArrayArray = F.EdgeLoops

    Dim Cv As List(Of Tuple(Of EdgeArray, Double)) =
      New List(Of Tuple(Of EdgeArray, Double))(Loops.Size)

    For Each L2 As EdgeArray In Loops
      For Each L3 As Edge In L2
        Dim Mu As Double = MinU1(L3.AsCurve, F)
        Cv.Add(New Tuple(Of EdgeArray, Double)(L2, Mu))
      Next
    Next

    Dim Out As Tuple(Of EdgeArray, Double) =
      Cv.Find(Function(Jx) Jx.Item2 = Cv.Min(Function(Jv) Jv.Item2))

    If Out Is Nothing Then
      Return Nothing
    Else
      Return Out.Item1
    End If
  End Function

  Shared Function MinU2(C As Curve, T As Transform) As Double
    If C.IsCyclic Then
      Dim Min As Double = Nothing
      For I = 0 To 20
        Dim Param As Double = I / 20
        Dim CuvPt As XYZ = C.Evaluate(Param, True)
        Dim XYZt As XYZ = T.Inverse.OfPoint(CuvPt)
        If I = 0 Then
          Min = XYZt.X
        Else
          If XYZt.X < Min Then
            Min = XYZt.X
          End If
        End If
      Next
      Return Min
    Else
      Dim Pt1 As XYZ = C.GetEndPoint(0)
      Dim Pt2 As XYZ = C.GetEndPoint(1)
      Dim XYZp As XYZ() = New XYZ(1) {}

      XYZp(0) = T.Inverse.OfPoint(Pt1)
      XYZp(1) = T.Inverse.OfPoint(Pt2)

      ' Zeds.Add(XYZp(0).Z)
      ' Zeds.Add(XYZp(1).Z)

      If XYZp(0).X < XYZp(1).X Then
        Return XYZp(0).X
      Else
        Return XYZp(1).X
      End If
    End If
  End Function

  Public Shared Function PlanarFaceOuterLoop(F As Face) As EdgeArray
    Dim PF As PlanarFace = TryCast(F, PlanarFace)
    If PF Is Nothing Then Return Nothing Else

    Dim FN As XYZ = PF.FaceNormal
    Dim T As Transform = Transform.Identity
    T.BasisZ = FN
    T.BasisX = PF.XVector
    T.BasisY = PF.YVector
    T.Origin = PF.Origin

    'Dim Zeds As New List(Of Double)

    Dim Loops As EdgeArrayArray = F.EdgeLoops

    Dim Cv As List(Of Tuple(Of EdgeArray, Double)) =
      New List(Of Tuple(Of EdgeArray, Double))(Loops.Size)

    For Each L2 As EdgeArray In Loops
      For Each L3 As Edge In L2
        Dim Mu As Double = MinU2(L3.AsCurve, T)
        Cv.Add(New Tuple(Of EdgeArray, Double)(L2, Mu))
      Next
    Next

    Dim Out As Tuple(Of EdgeArray, Double) =
      Cv.ToList.Find(Function(Jx) Jx.Item2 = Cv.Min(Function(Jv) Jv.Item2))

    If Out Is Nothing Then
      Return Nothing
    Else
      Return Out.Item1
    End If
  End Function

  Public Function Execute(
    ByVal commandData As ExternalCommandData,
    ByRef message As String,
    ByVal elements As ElementSet) _
  As Result Implements IExternalCommand.Execute

    Dim uiapp As UIApplication = commandData.Application
    Dim uidoc As UIDocument = uiapp.ActiveUIDocument
    Dim app As Application = uiapp.Application
    Dim doc As Document = uidoc.Document

    Dim sel As Selection = uidoc.Selection

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

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

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

    Dim EA1 As EdgeArray = PlanarFaceOuterLoop(F)
    Dim EA2 As EdgeArray = OuterLoop(F)

    Return Result.Succeeded
  End Function

End Class


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

0 Likes
Message 6 of 32

jeremytammik
Autodesk
Autodesk

Here are your methods simplified and rewritten in C#:

 

    #region Rpthomas108 searches for minimum point
    // In Revit API discussion forum thread
    // https://forums.autodesk.com/t5/revit-api-forum/is-the-first-edgeloop-still-the-outer-loop/m-p/7225379

    public static double MinU( Curve C, Face F )
    {
      return C.Tessellate()
        .Select<XYZ, IntersectionResult>( p => F.Project( p ) )
        .Min<IntersectionResult>( ir => ir.UVPoint.U );
    }

    public static double MinX( Curve C, Transform Tinv )
    {
      return C.Tessellate()
        .Select<XYZ, XYZ>( p => Tinv.OfPoint( p ) )
        .Min<XYZ>( p => p.X );
    }

    public static EdgeArray OuterLoop( Face F )
    {
      EdgeArray eaMin = null;
      EdgeArrayArray loops = F.EdgeLoops;
      double uMin = double.MaxValue;
      foreach( EdgeArray a in loops )
      {
        double uMin2 = double.MaxValue;
        foreach( Edge e in a )
        {
          double min = MinU( e.AsCurve(), F );
          if( min < uMin2 ) { uMin2 = min; }
        }
        if( uMin2 < uMin )
        {
          uMin = uMin2;
          eaMin = a;
        }
      }
      return eaMin;
    }

    public static EdgeArray PlanarFaceOuterLoop( Face F )
    {
      PlanarFace face = F as PlanarFace;
      if( face == null )
      {
        return null;
      }
      Transform T = Transform.Identity;
      T.BasisZ = face.FaceNormal;
      T.BasisX = face.XVector;
      T.BasisY = face.YVector;
      T.Origin = face.Origin;
      Transform Tinv = T.Inverse;

      EdgeArray eaMin = null;
      EdgeArrayArray loops = F.EdgeLoops;
      double uMin = double.MaxValue;
      foreach( EdgeArray a in loops )
      {
        double uMin2 = double.MaxValue;
        foreach( Edge e in a )
        {
          double min = MinX( e.AsCurve(), Tinv );
          if( min < uMin2 ) { uMin2 = min; }
        }
        if( uMin2 < uMin )
        {
          uMin = uMin2;
          eaMin = a;
        }
      }
      return eaMin;
    }
    #endregion // Rpthomas108 searches for minimum point

Note how short and sweet MinU and MinX became?

 

I added them to The Building Coder samples release:

 

https://github.com/jeremytammik/the_building_coder_samples/releases/tag/2018.0.134.3

 

https://github.com/jeremytammik/the_building_coder_samples/blob/master/BuildingCoder/BuildingCoder/C...

 

Cheers,

 

Jeremy



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

0 Likes
Message 7 of 32

RPTHOMAS108
Mentor
Mentor

Hello Jeremy

 

Thanks for the reply, sorry for the delay in response.

 

In truth I was detecting straight line curves by looking at the cyclic property as it was convenient for the example. A more generalised solution should take into account the various types of curve (perhaps with casting attempts or gettype comparisons). The important aspect is that the straight curves give certainty that one of two points is the minimum whilst other types of curve in theory have to be evaluated/tessellated along them otherwise the min may be missed. So the first step for performance is to establish if you need to evaluate the curve at all.

 

I didn't consider the Tessellate function but that does seem the better option. I picked 20 because it seemed a good resolution for the example cases. Probably I'm being a bit pessimistic about how long it takes a modern computer to iterate these things whilst ignoring possible errors in long winding curves.

 

Yes planar. The other type of non-planar function was looking at the face uv co-ordinates in a similar arbitrary way.

 

I'm glad you see the basis of the approach as useful. They tell me lambda expressions are great but I don't know where to draw the line sometimes.

 

 

Regards

 

Richard

0 Likes
Message 8 of 32

jeremytammik
Autodesk
Autodesk

Dear Richard,

 

Yes, I like your approach a lot.

 

Kiss!  -- https://en.wikipedia.org/wiki/KISS_principle

 

Yes, Tessellate is very handy indeed.

 

I love lambda expressions too. I am just not fluent in VB, and never saw them there before 🙂

 

Cheers,

 

Jeremy



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

0 Likes
Message 9 of 32

Anonymous
Not applicable

Hi I would like to toss this in for discussion, 
It should be able to seperate all curve loops of a faceinto interior / exterior curves...
It needs some adaptation for non planar faces...


The idea is quite simple:
The face has an area which can be computed, The area of each curve can be computed (https://en.wikipedia.org/wiki/Shoelace_formula)... 

So the only issues is to find a combination of + / - so that the resulting sum equals the area of the initial face...
This will work just fine if the face has a limited number since it uses a branch and bound approch for itterating through the possibilities...

 public static CurveLoop[] GetOuterBoundaries(Autodesk.Revit.DB.Face source)
        {
            List<CurveLoop> allBounds = new List<CurveLoop>();
            int totalArea = (int)(source.Area * 100);

            allBounds.AddRange(GeometryHelper.GetBoundaries(source));
            List<int> boundAreas = new List<int>();
            for (int i = 0; i < allBounds.Count; i++)
            {
                boundAreas.Add( (int)(GeometryHelper.Area(allBounds[i], out XYZ currentCenter) * 100) );
            }

            if (boundAreas.Sum() > totalArea)
            {
                List<int[]> results = SumUpRecursivly(boundAreas, totalArea, new List<int>());
                for (int i = 0; i < results.Count; i++)
                {

                    if (results[i].Length == 0)
                        continue;

                    int[] r = results[i];
                    for (int ii = 0; ii < r.Length; ii++)
                    {
                        int index = boundAreas.IndexOf(r[ii]);
                        boundAreas.RemoveAt(index);
                        allBounds.RemoveAt(index);

                    }
                    return allBounds.ToArray();
                }

            }

            return allBounds.ToArray();
        }

/// adaptiation of: https://stackoverflow.com/questions/4632322/finding-all-possible-combinations-of-numbers-to-reach-a-...
        private static List<int[]> SumUpRecursivly(List<int> numbers, int target, List<int> partial)
        {
            int s = 0;
            foreach (int x in numbers) s += x;
            foreach (int x in partial) s -= x;

            int diff = Math.Abs(s - target);

            if (Math.Abs(s - target) < 10 )
            {
                return new List<int[]> { partial.ToArray() };
            }

            if (numbers.Count == 0 || s < target)
            {
                return new List<int[]> { new int[0] };
            }

            List<int[]> results = new List<int[]>();
            for (int i = 0; i < numbers.Count; i++)
            {
                List<int> remaining = new List<int>();
                int n = numbers[i];
                for (int j = 0; j < numbers.Count; j++)
                {
                    if (j != i) remaining.Add(numbers[j]);
                }

                List<int> partial_rec = new List<int>(partial) { n };
                results.AddRange(SumUpRecursivly(remaining, target, partial_rec));

            }
            return results;
        }
0 Likes
Message 10 of 32

jeremytammik
Autodesk
Autodesk

Thank you for the nice idea.

 

However, I am not immediately convinced...

 

Imagine a situation with many rooms, many of which have holes.

 

Imagine that many of the holes are the exact same size as many of the other rooms.

 

How do you tell them apart, if all you consider is their area?

 



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

0 Likes
Message 11 of 32

Anonymous
Not applicable

 

Okay, since I am still exploring revit Api, I just found this:

https://thebuildingcoder.typepad.com/blog/2015/01/exporterifcutils-curve-loop-sort-and-validate.html

That should do the trick , shouldn't it.

However I can't find the functions in outocomplete, which assemblies do I need?

0 Likes
Message 12 of 32

jeremytammik
Autodesk
Autodesk

Search the Revit API DLLs for something named IFC. I think the namespace is

 

using Autodesk.Revit.DB.IFC;

 



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

0 Likes
Message 13 of 32

Anonymous
Not applicable

Yes, got them. And it does the job quite nicly, for reference once again:

https://thebuildingcoder.typepad.com/blog/2015/01/wall-elevation-profiles-in-the-building-coder-samp...

 

On friday I was a bit made that this functionality is hidden away in a class that is called 

Autodesk.Revit.DB.IFC.ExporterIFCUtils.SortCurveLoops

I mean it is obvious that you have to look into the ExporterIFCUtils namespace in order to make a distinction between inner and outer boundary loops.... 

 

But today I feel like a little kid looking for easter eggs, I wounder what more is hidden within the Revit API.

Maybe there is a line saying:

 

"This apt has super easter bunny powers"

0 Likes
Message 14 of 32

jeremytammik
Autodesk
Autodesk
Accepted solution

Dear Richard,

 

Congratulations on finding this egg, and best of luck discovering all the others!

 

Yes, another developer who is a big fan of the hidden wonders of the Rev it API utility classes is Rudolf Honke:

 

https://thebuildingcoder.typepad.com/blog/2013/04/handy-utility-classes.html

 

Cheers,

 

Jeremy

 



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

0 Likes
Message 15 of 32

stefanome
Collaborator
Collaborator

I tried using SortCurveLoops on the the face shown below, expecting to get one list with the outer loop and one list with the 3 inner loops, but I got one list containing one list containing two loops.

stefanomenci_0-1618433121784.png

 

This article also mentions co-planar loops in the description of ValidateCurveLoops. Perhaps the problem I had is caused by the fact that also SortCurveLoops only works with co-planar loops?

 

Here is the code I use to find the outer loop and all the inner loops. It finds the outer loop by cycling through all the tessellated points of all the loop edges and finding the one with the lowest U.

 

I did a few tests and it seems to work well. I am surprised to see that my short function does the same job as other long functions shown in the previous posts.  I am learning LINQ, so I spent some time to get this to work with LINQ, but my previous version using 3 nested foreach was very simple to do.

Am I doing something wrong?

Am I doing something different from what this post describes?

 

public Face Face;
private CurveLoop _outerLoop;
private List<CurveLoop> _innerLoops;

private void GetInnerAndOuterLoops()
{
    var allLoops = Face.GetEdgesAsCurveLoops();

    _outerLoop = allLoops
        .SelectMany(loop => loop
            .SelectMany(curve => curve.Tessellate()
                .Select(point => (Face.Project(point).UVPoint.V, Loop: loop))))
        .Aggregate((lowestLoop, nextLoop) => lowestLoop.V < nextLoop.V ? lowestLoop : nextLoop).Loop;

    _innerLoops = allLoops.Where(loop => loop != _outerLoop).ToList();
}

 

 

0 Likes
Message 16 of 32

RPTHOMAS108
Mentor
Mentor

If it works it works.

 

One thing I considered at the time was resolution of the points you get from tessellate i.e. if you have a curve does one of the points on that curve (from tessellate) describe the actual minimum location of that curve (for some curves that is unlikely to be the case). Since there is the parametric curve and the actual points are obtained from that. Below is an exaggerated example of what I mean by that. Depends also on rotation of UV axis on face in comparison to those points.

 

210414a.PNG

For the most part I don't think such a thing would cause issues unless you set out to prove it didn't work i.e. in a real world scenario there is no arrangement you would likely have that would be affected by such things. So it's a question of comfort level through testing really.

 

I think it has also since been noted that there are patterns in how the faces are constructed that gives away the actual outer loop of a face.

 

Was also at the time dealing with PlanarFaces only so also should note that for those you can use Face.IsInside with solid creation utils, this makes things far more straightforward than my original above code and perhaps more reliable i.e. extrude each loop and check points from each within faces of one another to find other loop.

 

0 Likes
Message 17 of 32

stefanome
Collaborator
Collaborator

The documentation of Curve.Tessellate says both the tolerance is slightly larger than 1/16” and is defined internally by Revit to be adequate for display purposes.

 

Tessellation in computer graphics is often adjusted to the zoom level or to the desired rendering quality. In other words, if "you can see" that one curve is below the other curve, then the tessellation can see it too. But I don't know if Curve.Tessellation is the same tessellation used for the graphics card. We could also talk about the definition of "you can see", but let's not add speculation to the speculation 🙂

0 Likes
Message 18 of 32

RPTHOMAS108
Mentor
Mentor

Yes it is unlikely but possible.

0 Likes
Message 19 of 32

jeremy_tammik
Alumni
Alumni

You say you got one list with one list with two loops...

 

If you are unsure which loop is which, or, in any case, I would highly recommend implementing some little debugging utility functions to display those loops graphically, or it will be very hard to understand what you are getting.

 

I implemented such stuff in several blog posts using the Creator class:

 

 

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
Message 20 of 32

stefanome
Collaborator
Collaborator

Thank you for the list of articles, it will be very helpful in the near future!

 

This article seems to mention the same problem that I found in SortCurveLoops.

 

I did another test with SortCurveLoops, and indeed it seems to be working reliably only with planar faces. With curved faces it usually does nothing. Only in one curved face I was able to get 2 out of the 4 loops, but it usually gets none.

 

In this snapshot you can see two masses. The first one has only planar faces, the second one is a copy of the first one with a void that creates a curved face. 

 

The texts show the first line of each loop with the loop indexes as returned by SortCurveLoops. I like to create texts at 1/3 of each line, so it visually gives an idea of the direction of the loop. Just looking at the texts you immediately understand which loops are clockwise and which ones are counterclockwise.

 

I have the feeling that SortCurveLoops projects the curves to a plane, then crunches the numbers on the projected curves. If this is the case, then it will never be reliable on curved faces. The correct approach would be to work on the UV coordinates.

stefanomenci_0-1618514863757.png

Here is the code I used:

var loops = face.GetEdgesAsCurveLoops();
var sortedLoops = ExporterIFCUtils.SortCurveLoops(loops);
for (var i = 0; i < sortedLoops.Count; i++)
{
for (var j = 0; j < sortedLoops[i].Count; j++)
{
CreateTextNote($"[{i}][{j}]", sortedLoops[i][j].First().Evaluate(0.33, true), doc);
}
}

TextNote CreateTextNote(string text, XYZ origin, Document doc)
{
var options = new TextNoteOptions
{
HorizontalAlignment = HorizontalTextAlignment.Center,
VerticalAlignment = VerticalTextAlignment.Middle,
TypeId = doc.GetDefaultElementTypeId(ElementTypeGroup.TextNoteType)
};

return TextNote.Create(doc, doc.ActiveView.Id, origin, text, options);
} 

 

0 Likes