I am working on creating a variable number N of cylinders randomly placed on a surface. The cylinders should meet the following 3 requirements:

Their entire volume should be within the surface. I’ve managed that by offsetting the target surface inward by the cylinder radius when generating the start point of the cylinders (see “Requirement 1” block in my screenshot).

All cylinders should be on the surface. Currently, I have the “DoesIntersect?” filter to remove any start points that are created outside of the surface, since my surface is an L-shape. However, that means I do not end up with the same number of cylinders originally specified (in this example, I specified 11 but end up with only 7).
I am wondering what the best way would be to ensure I end up with the same number as originally specified, while keeping things as random as possible. I’ve been thinking about translating the outside points in a given direction, or re-generating them in a more constrained surface, but, even if it worked, it would take away some of the randomness. Another option I have considered would be the LoopWhile node but I have never used it and am struggling to find the correct parameters to get the outputs I am after.

The cylinders should not intersect with each other. I have nothing in place to meet this requirement for now. I was going to introduce another “DoesIntersect?” filter to remove overlapping cylinders but that might leave me with the same problem as described in 2), with a different number of cylinders to what was originally specified. Ideally I’d want all start points randomly generated a radius distance away from each other.

I am looking for help/ideas/tips on how to meet requirement 2 and 3. As an additional piece of info, the target surface is also randomly generated and will be either a rectangle or an L-shape of random dimension.

So, it’s kind of random, but there are constraints…

Personally, having accepted that this isn’t actually random (because there are these intersect constraints). I would divide the surface into 0.5 squares and randomly pick 11 of them… Then use the centre point to place the cylinder.

Otherwise, we’ll need to do lots of looping, which isn’t Dynamo’s strong point, I personally write those in Python, but I don’t know if that is an option for you?

Another option to look into might be the Optimo package? This example seems to be doing some similar things to what you’re after…

As @Mark.Ackerley mentioned, this should be relatively straight forward in python with a few conditions and a while loop.

The other option is to create a lot of cylinders by default and then filter down to N number of cylinders requested. This gives you the opportunity to filter out cylinders that don’t meet your constraints and presumably still end up with enough options to retain your initial specified count.

Thanks for the input! I am actually not constrained to Dynamo nodes and could definitely introduce Python script into this if it’s the best alternative. Would you be able to point me in the direction of resources that could help me write a script for this specific task? I have some prior Python experience from a class I took in grad school but have not used it within a Dynamo environment.

Otherwise, I think the square method, Optimo and creating more cylinders than needed approach could do the job.

Landform package from Lauren Schmidt of LandArchBIM.com has a node which does this by duplicating the random population of points on the surface twice, pruning the duplicates, and taking the first N items. It does a decent job for many surfaces, but may not always work.

For a Python based method, you can utilize this:

import clr #add the common language runtime so we can use the Dynamo geometry library
import random #import random so we can generate random numbers
clr.AddReference('ProtoGeometry') #add the dynamo geomgry library to the CLR
from Autodesk.DesignScript.Geometry import Geometry, Point, Surface #import the relevant parts of the Dynamo geometry library
srf = IN[0] #the surface to populate
cnt = IN[1] #the number of points you want
clearance = IN[2] #the clearance between points
maxAttempts = 10 #the maximum number of attempts, to prevent an infinate loop
pnts = [] #an empty list of points to start the loop
attempt = 0 #a counter to use for the infinate loop prevention
while len(pnts)<cnt and attempt < maxAttempts: #while the length of points is less than the count and the attempt is the less than the maximum number of attempts
attempt +=1 #increase the attempt counter
uSet = [ random.random() for i in range(cnt) ] #a random list of U parameters
vSet = [ random.random() for i in range(cnt) ] #a random list of V parameters
testPnts = [Surface.PointAtParameter(srf, u, v) for u,v in zip(uSet,vSet) ] #an initial collection of points
fitPnts = [ i for i in testPnts if Geometry.DoesIntersect(i,srf) ] #removing any points which dont' fall on the surface
[ pnts.append(i) for i in fitPnts ] #apprend each point which was in the fit points list into the points list
spacedPnts = Point.PruneDuplicates(pnts, clearance) #prune any duplicate points
pnts = [i for i in spacedPnts] #convert the spaced points into a clean list of points
pnts = pnts[0:cnt] #remvoe any extra points
OUT = pnts, attempt #return the points to the Dynamo environment, and the number of attempts (to let you know when the design was over constrained causing the near infinate loop)