The easiest way to freeze GH (populate 3D)

I think I have found the quickest way to crash/freeze GH. Just try to populate 3D any box with a “large” number of points.

image

It freezes GH and does not recover. I am not sure if it will eventually finish, but I gave up after like 10 minutes.

Also since when is pressing Esc to stop something in GH not a thing any more. From my experience at least the last 50 times I can remember GH being stalled, 49 of those times Esc did nothing.

Why does it struggle with creating 300.000 points?

For context I am using Cockroach’s Populate Mesh to create pointclouds of meshes. You can create those with something like 1 million points and it will do it in 27ms. But it only populates the surface of the mesh and I would like to populate the volume. So I thought randomly creating points in a volume and then discarding those outside the mesh or original polysurface might work.

Well seems like GH really doesn’t like doing things in large numbers.

Is this a straight up bug or just something GH is not good at?

Should ESC do something in this instance?

So many question around such a trivial seeming task.

ps: sorry for the clickbaity title, but I have crashed/frozen GH probably thousands of times over the last 13 years, but never have I done it with just 3 components.

P.S. They’re planning to tweak both the populate2D algorithms and the multi-threading stuff in Grasshopper2.

1 Like

I had also the same problem, for example for using Dendro so I made 2 little components in Nautilus. One like Cockroach plugin (so on a mesh) and the other for the Box (it is simple random use from .Net)
10 million points in 5 s.

2 Likes

I’m not sure how it works underneath, but my guess is it’s not a completely random Points creation, but it looks at already created Points and try to place other Point somewhere not near the existing ones, which makes Populate 3D more regularly populated, but requires much more computing time with each new Point created to decide where to place another one. But it’s just my guess, whoever knows more, correct me if I’m wrong :slight_smile: .

GH1 runs on the UI thread, the same thread where key events are handled. As such, there’s no ‘nice’ way to handle Escape presses. Instead, the code has to specifically check whether Escape has been pressed since the last time it checked. This will therefore not work if (a) the code just doesn’t check or (b) some other code handles key presses in the meantime. There’s no great solution while all the hard work is being done on the UI thread I’m afraid.

Because each new point must be placed away from all previous points. The populate components try hard to create point distributions with roughly equal densities everywhere. This means that to insert point #259543 it has to find a coordinate as far away as possible from the existing 259542 points. This becomes more and more work the more points there are. I’m not exactly sure what the runtime is but there’s definitely a square somewhere in that big O.

3 Likes

That looks pretty good! Do you mind sharing it?

In the meantime ChatGPT and me created a C# script to create points inside Breps. I need the points to fill the volume of a given Brep, not the surface like Cockroach does.

It’s reasonably quick and creates the 300k points inside the Brep in just under 6s. I am than using Cockroach’s CloudCreate component to turn it into a pointcloud.

Here is the C# code:

using System;
using System.Collections;
using System.Collections.Generic;

using Rhino;
using Rhino.Geometry;

using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;

using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Linq;

/// <summary>
/// This class will be instantiated on demand by the Script component.
/// </summary>
public class Script_Instance : GH_ScriptInstance
{
#region Utility functions
  /// <summary>Print a String to the [Out] Parameter of the Script component.</summary>
  /// <param name="text">String to print.</param>
  private void Print(string text) { /* Implementation hidden. */ }
  /// <summary>Print a formatted String to the [Out] Parameter of the Script component.</summary>
  /// <param name="format">String format.</param>
  /// <param name="args">Formatting parameters.</param>
  private void Print(string format, params object[] args) { /* Implementation hidden. */ }
  /// <summary>Print useful information about an object instance to the [Out] Parameter of the Script component. </summary>
  /// <param name="obj">Object instance to parse.</param>
  private void Reflect(object obj) { /* Implementation hidden. */ }
  /// <summary>Print the signatures of all the overloads of a specific method to the [Out] Parameter of the Script component. </summary>
  /// <param name="obj">Object instance to parse.</param>
  private void Reflect(object obj, string method_name) { /* Implementation hidden. */ }
#endregion

#region Members
  /// <summary>Gets the current Rhino document.</summary>
  private readonly RhinoDoc RhinoDocument;
  /// <summary>Gets the Grasshopper document that owns this script.</summary>
  private readonly GH_Document GrasshopperDocument;
  /// <summary>Gets the Grasshopper script component that owns this script.</summary>
  private readonly IGH_Component Component;
  /// <summary>
  /// Gets the current iteration count. The first call to RunScript() is associated with Iteration==0.
  /// Any subsequent call within the same solution will increment the Iteration count.
  /// </summary>
  private readonly int Iteration;
#endregion

  /// <summary>
  /// This procedure contains the user code. Input parameters are provided as regular arguments,
  /// Output parameters as ref arguments. You don't have to assign output parameters,
  /// they will have a default value.
  /// </summary>
  private void RunScript(Brep brep, int pointCount, ref object A)
  {
    if (brep == null || pointCount <= 0)
    {
      A = null;
      return;
    }

    var bbox = brep.GetBoundingBox(false);
    var points = new ConcurrentBag<Point3d>();
    int generatedPoints = 0; // Track the number of points generated

    while (generatedPoints < pointCount)
    {
      Parallel.For(0, Environment.ProcessorCount, () => new Random(), (i, loopState, random) =>
        {
        var batchPoints = new List<Point3d>();
        while (batchPoints.Count < 100) // Use a smaller batch size to avoid over-generation
        {
          var x = bbox.Min.X + (bbox.Max.X - bbox.Min.X) * random.NextDouble();
          var y = bbox.Min.Y + (bbox.Max.Y - bbox.Min.Y) * random.NextDouble();
          var z = bbox.Min.Z + (bbox.Max.Z - bbox.Min.Z) * random.NextDouble();

          var point = new Point3d(x, y, z);

          if (brep.IsPointInside(point, RhinoMath.ZeroTolerance, false))
          {
            batchPoints.Add(point);
          }
        }

        foreach (var point in batchPoints)
        {
          if (points.Count < pointCount)
          {
            points.Add(point);
          }
          else
          {
            loopState.Stop(); // Stop the loop if enough points have been generated
          }
        }

        return random;
        }, random => { });

      generatedPoints = points.Count; // Update the count after each parallel batch
    }

    A = points.ToArray(); // Output all generated points

  }

  // <Custom additional code> 
  ///// WE CAN EDIT HERE
  // </Custom additional code> 
}

I am sure there are ways to make it even faster. Any ideas on how to improve it or why it is still quite slow are welcome :slight_smile:

231214_Populate Brep.gh (9.6 KB)

ps: had to edit the post, since it was not creating the required number of points.

Hello
it is not smarter than your code.

  public static Point3d[] FastPopulateBox(Box box, int numberOfPoints)
        {
            numberOfPoints = Math.Max(1, numberOfPoints);
            Point3d[] pts = box.GetCorners();
            Vector3d vx = pts[1] - pts[0];
            Vector3d vy = pts[3] - pts[0];
            Vector3d vz = pts[4] - pts[0];
            Random rnd = new Random();
            Point3d[] tab_pts = new Point3d[numberOfPoints];

            for (int i = 0; i < numberOfPoints; i++)
            {
                tab_pts[i] = pts[0] + vx * rnd.NextDouble() + vy * rnd.NextDouble() + vz * rnd.NextDouble();
            }

            return tab_pts;
        }

:thinking:That is why the INPUT pre-existing population takes force.

Hello @DavidRutten while I’m familiar using the explicit PopulateGeometry component with Rhino.NodeInCode in C#.
I’m curious if there’s an alternative way to access to that logic or a basic example illustrating the algorithm in C#?
pre-existing Points >>>> Insert new Points to populate the geometry

Any guidance or insights into this matter would be greatly appreciated.
Bests!

Got it, thanks. So as I thought, its the brep.IsPointInside part that is ultra slow (relatively speaking).

Is there a faster way to do this part? I could also convert the Brep to Mesh first in case there is a faster Mesh.IsPointInside command!?

I don’t think the algorithm was publicly exposed, but it uses an OcTree lookup which is available. For every additional point it has to add it generates N random points within the boundingbox (N being some factor of the existing points, hard limited to some constants) and for each finds the nearest point in the OcTree. The furthest point is inserted into the list and the OcTree. There’s probably better ways of doing it, for example a volumetric delaunay tetrahedron mesh with tets sorted by volume would allow the code to more quickly generate points in a region which is already more empty than the average. But I didn’t have a volumetric delaunay tet-mesher ready to be used so I opted for the OcTree solution instead.

2 Likes