Face Boundaries faster

Hello everyone, is there a faster way to extract face boundaries from a mesh in C#? (faster than or as grasashopper component)?


Face Boundaries.gh (10.6 KB)

using System.Linq;
 private void RunScript(Mesh mesh, ref object A)
  {  Component.Name = "Face.Edge Boundaries by unweld Edge";
    var ix = System.Linq.Enumerable.Range(0, mesh.Faces.QuadCount * 4 + mesh.Faces.TriangleCount * 3);
    var edges = new List<Polyline>();
    mesh.UnweldEdge(ix, true);
    var meshf = mesh.ExplodeAtUnweldedEdges();
    foreach(var m in meshf){
      var v = m.Vertices.ToPoint3dArray().ToList();
      edges.Add(new Polyline(v));}
    A = edges; }

If you translate this GHPython code to C#, I bet that’ll get you close to native performance:


230824_GetFaceLoops.gh (5.1 KB)

1 Like

hi @AndersDeleuran Thanks-
i Convert it to c# and use 2 method and using{ System.Parallel cpu Core} and imporoved ;but grasshopper component is faster- Is it really possible to write a code that is faster than that?


230824_GetFaceLoops-ver 2.gh (12.6 KB)

using System.IO;
using System.Linq;
using System.Data;
using System.Drawing;
using System.Reflection;
using System.Windows;
using System.Xml;
using System.Xml.Linq;
using System.Runtime.InteropServices;
 using Rhino.DocObjects;
using Rhino.Collections;
using GH_IO;
using GH_IO.Serialization;
using System.Security.Policy;
using Rhino.Runtime;
using System.Security.Cryptography;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Collections.Concurrent;
 private void RunScript(Mesh mesh, ref object A)
  {
    Component.Name = "Face.Edge by unweld Edge Parallel.";
    var edges = new ConcurrentBag<Polyline>();
    var vts = new List<Point3d>();
    //  foreach(Rhino.Geometry.MeshFace f in mesh.Faces){
    System.Threading.Tasks.Parallel.ForEach(mesh.Faces, f => {
      var pa = mesh.Vertices.Point3dAt(f.A);
      var pb = mesh.Vertices.Point3dAt(f.B);
      var pc = mesh.Vertices.Point3dAt(f.C);
      if( f.IsQuad)
      {   var pd = mesh.Vertices.Point3dAt(f.D);
        vts = new List<Point3d>(){pa,pb,pc,pd};}
      else
        vts = new List<Point3d>(){pa,pb,pc};

      edges.Add(new Polyline(vts));});
    A = edges;
  }

@Mahdiyar

The primary penalty is likely outputting the polylines. If you wrap them in Grasshopper.Kernel.Types.GH_Curve you should see a substantial performance boost. And you need to compile the component to really see the benefit of writing this in C#. Here’s going to 1000 with the GHPython example I posted, it’s slightly faster in SDK mode. I would expect a non-threaded C# scripting component to at least hit this performance:


230824_GetFaceLoops_01.gh (7.3 KB)

2 Likes

Firstly, if you want to use a parallel loop, it’s better to use a simple array instead of a fancy “ConcurrentBag.” This is because we already know exactly how many faces there are, so the ConcurrentBag isn’t needed.

In this situation, using a parallel loop won’t make things much faster because there’s not a lot of work to do in each step.

As @AndersDeleuran mentioned, you can make things quicker by changing the output to the standard Grasshopper type called “GH_Curve.”

var edges = new GH_Curve[mesh.Faces.Count];
for(var i = 0; i < edges.Length; i++)
{
    var f = mesh.Faces[i];
    PolylineCurve pl;
    if (f.IsQuad)
    {
    pl = new PolylineCurve(new []
    {
        mesh.Vertices.Point3dAt(f.A),
        mesh.Vertices.Point3dAt(f.B),
        mesh.Vertices.Point3dAt(f.C),
        mesh.Vertices.Point3dAt(f.D),
        mesh.Vertices.Point3dAt(f.A)
        });
    }
    else
    {
        pl = new PolylineCurve(new []
        {
            mesh.Vertices.Point3dAt(f.A),
            mesh.Vertices.Point3dAt(f.B),
            mesh.Vertices.Point3dAt(f.C),
            mesh.Vertices.Point3dAt(f.A)
            });
        }
        edges[i] = new GH_Curve(pl);
    }
A = edges;

FaceBoundaries.gh (11.9 KB)

3 Likes

@Mahdiyar
Thanks
If we generate this code in visual studio using new GH_Curve(pl) is effective for improvements speed of components or not only in c# grasshopper Component?

Assuming this means compiling the component, then yes that should improve performance. See this post on the old forum for a great example:

And while we’re micro-optimising, I’ve noticed that the method by which we get mesh faces affects performance too. Where in GHPython at least, a for each loop is fastest:


230825_GetFaceMethodsComparison_00.gh (8.1 KB)

1 Like

Hi @AndersDeleuran and @Mahdiyar Again
I tried to create the Inset Mesh C# component but the Weaverbird image frame component is much faster than Comonent I use Gh_Mesh for faster - do you have any suggestions to optimize thisWeaverbird codebetter?
inset mesh faster.gh (8.4 KB)

inset mesh faster.gh (8.4 KB)

  private void RunScript(Mesh mesh, double dist, ref object M)
  {
    Component.Name = "Inset Mesh";
    var pl = Boundary.MeshToPoly(mesh);
    var msh = new Mesh();
    var m = new Mesh();
    // Parallel.ForEach(pl, p =>
    foreach(var p in pl)
    {var ver = new List<Point3d>();
      var cen = p.CenterPoint();
      var t = Transform.Scale(cen, dist);
      var dup = p.Duplicate();
      dup.Transform(t);
      for (int i = 0; i < p.SegmentCount; i++)
      {msh = new Mesh();
        ver = new List<Point3d>
          {p.SegmentAt(i).PointAt(0),
            p.SegmentAt(i).PointAt(1),
            dup.SegmentAt(i).PointAt(1),
            dup.SegmentAt(i).PointAt(0)
            };
        msh.Vertices.AddVertices(ver);
        msh.Faces.AddFace(0, 1, 2, 3);
        m.Append(msh);
      }
    }
    // );
    m.Weld(0);
    var mm = new GH_Mesh(m);
    M = mm;

  }

  // <Custom additional code> 
  public static class Boundary
  {
    public static List<Polyline> MeshToPoly(Mesh mesh)
    {
      // var edges = new ConcurrentBag<Polyline>();
      var edges = new List<Polyline>();
      // Parallel.For(0, mesh.Faces.Count, i =>
      for (int i = 0; i < mesh.Faces.Count; i++)
      {
        var f = mesh.Faces[i];
        Polyline pl;
        if (f.IsQuad)
        {
          pl = new Polyline(new[]
            {
              mesh.Vertices.Point3dAt(f.A),
              mesh.Vertices.Point3dAt(f.B),
              mesh.Vertices.Point3dAt(f.C),
              mesh.Vertices.Point3dAt(f.D),
              mesh.Vertices.Point3dAt(f.A)
              });
        }
        else
        {
          pl = new Polyline(new[]
            {
              mesh.Vertices.Point3dAt(f.A),
              mesh.Vertices.Point3dAt(f.B),
              mesh.Vertices.Point3dAt(f.C),
              mesh.Vertices.Point3dAt(f.A)
              });
        }
        edges.Add(pl);
      }
      //);

      return edges.ToList();
    }
  }

If you are going to replicate/study existing components, you can decompile the relevant .dll using DNSpy or ILSpy to read the source code. This is technically a breach of license, but as far as I gather is not frowned upon too hard around here.

1 Like

Is there any link of this ?(i don’t find any opensource of this plugin in web or github) maybe not opensource?
I need know mechanism it to use a generete complex code for subd and better bases code is fast and optimized

If you are located in the European Union, any form of de-compilation or disassembling binaries is allowed by law, if you follow the purpose of interoperability. No matter what a license says. You also don’t need to ask McNeel for doing so:

https://eur-lex.europa.eu/legal-content/EN/ALL/?uri=celex%3A32009L0024

However, if you plan to copy and paste code, and then republish the code, then this another thing. However, I’m not so sure that the complexity involved satisfies a copyright issue. Because Grasshopper components usually only call the Rhinocommon library in a specific way. So you see its a grey area. I would be very careful with copy and pasting code and republishing it.

Btw, ILSpy is used in Visual Studio as the default decompiler…
But a standalone version you find here:

3 Likes

Thanks for clearing this up Tom, much appreciated.

1 Like