How to randomly place family instance without clustering

I’m trying to populate a ceiling with cubes of different sizes. Their positions are fixed so i just placed the cube-families by hand and am now trying to replace them with different families and family types (different attachment menthods and different sizes). I managed to to do this but id like to get a more even distribution. Most importantly the big 400mm cubes should not be next to each other.

Maybe i can get the x/y coordinates and somehow set a minimum distance between cubes of the same size somehow?

In the end i will have hundreds of cubes across multiple office spaces and floors so the solution should be able to account for that.


Here is my current dynamo script

An iterative approach might be best. It gives you more control over each size, location, and any other conditions you might have. You can start by checking out these other topics, they use the same code for producing “random” values but might give you some context for how you want to build your logic.
Material color randomizer - DesignScript - Dynamo
Random.list and adjacent families - Revit - Dynamo

Ooooooh a fixed field study… Maybe a chance to implement WFC… :thinking:

Nah I don’t have time for all that.

Here’s a ‘random sampling with separation distance’ algorithm that should help intead.

Random Sampling With Separation Distance
########################################
############## Properties ##############
########################################
__author__ = 'Jacob Small'
__version__ = '0.1.0'
__description__ = "Randomly shuffles a list of points in an attempt to get sets of random samplings with sufficent space between each point so 'objects are not adjacent' in each set."
__DynamoBuilds__ = "3.4 - should work back to 2.13"
__ReleaseNotes__ = "POC only - test thuroughly before implementing in production. Not tested for group counts exceeding point count, and other edge cases. Recommend adding error handling accordingly."
__Dependancies__ = "None"
__Copyright__ = "2025, Autodesk Inc."
__License__ = "Apache 2"



########################################
### Configure the Python environment ###
########################################
### standard imports ###
import sys, clr, random
### basic Dynamo imports ###
clr.AddReference('ProtoGeometry') #add the Dynamo geometry library to the CLR
from Autodesk.DesignScript import Geometry as DG #add the Dynamo geometry class using the alias DG, to ensure no overlap between class calls in Revit or Dynamo



#########################################
###### Global variables and inputs ######
#########################################
# The inputs to this node will be stored as a list in the IN variables.
reRun = IN[0] #a boolean input to allow running again if you don't like the outcome for some reason
allPnts = IN[1] #the points on the surface at which you'll host an object
mixCounts = IN[2] #the list of object counts
minDist = IN[3] #the minimum distance between matching objects
### #note that minimum distance isn't certain, but it will do a better job on earlier sets in the list, so keep the most 'certain' apart early in the mixCount ###
results = [] #an empty list to hold the results


#########################################
######## Real code starts here  #########
#########################################
for count in mixCounts: #for each count in the list of mixes
    attempt = 0 #the current attempt
    result = [] #an empty list to hold the result
    while attempt < 10 and len(result)< count: #while you ahve made less than 10 attempts and the count is less than the number of objects in result
        needed = count - len(result) #get the number of points still needed
        attempt+=1 #increment the attempt
        random.shuffle(allPnts) #shuffle the points
        if needed > len(allPnts): #if the number of all points is less than or equal to the count
            [result.append(i) for i in allPnts] #append all items in all points to the result
            allPnts == []
        else: #otherwise
            sample = [allPnts.pop() for i in range(needed)] #pull a point out of the shuffled all points for each item in the count
            [result.append(i) for i in sample] #append each item in the sampel to the result list
            result = [i for i in DG.Point.PruneDuplicates(result,minDist)] #generate a new result list by pruning any duplicate points based no the Dynamo geometry engine
            sample = [i for i in sample if not i in result] #remove any poitns in the result from the sample
            [allPnts.append(i) for i in sample] #append the sample back into the all points list - they were too close to another point and are excluded as a result
            if len(result) > count: #if the length of result is greater than the desired count
                    overage = len(result) - count #get the number of excess items
                    reinsert = [result.pop() for i in range(overage)] #remove the excess items 
                    [allPnts.append(i) for i in reinsert] #append each of the excess items to the all points list
    if len(result)<count: #if the count of items in result is less than the desired count after the 10 attempts
        missing = count-len(result) #find out how many short we are
        random.shuffle(allPnts) #shuffl the all points list once more
        sample = [allPnts.pop() for i in range(missing)] #grab a sample from the all points list
        [result.append(i) for i in sample] #append each of the samples to the result
    results.append(result) #append the result list to the results list


#########################################
#### Send the desired bit to Dynamo #####
#########################################
OUT = results #return the list of result lists to the Dynamo environment

Note that this isn’t a certainty - in fact without some degree of flexibility in the mix you can’t ensure certainty. It will try each sampling 10 times, keeping those points which meet the separation requirement on each attempt. If after 10 attempts it has too many or too few then it will drop the excess and return the to the pool or grab a random set from the pool regardless of how close they might be. You can get good results from this if you manage the UI well.

Once you have the groups built you can randomize the height with List.Count > Math.RandomList > Geometry.Translate nodes.

In this case I’m attempting to use it to generate random groups from a random set of points with each member of each group a decent distance away from the others in the group, and generating a sphere at each location:

I’ve got to admit, i don’t get it. I’m not that familiar with dynamo nor python. I plugged my list of existing cubes and my distribution list of cubes into your script and got 25 empty lists as a result.

Does allPnts use the families in the project as points or do i need to extract an X/Y coordinate from the family instances first?
What does mixCounts take as an input? Currently my mix of Cubetypes just throws all cube types in a list and multiplies the entries that i want to have more of. Then i just apply this distribution to the existing family instances.

The forum doesn’t let me attach the revit and dynamo file because i’m a new user so i’ll share it as a download link.

This would be the Dynamo points - perhaps Element.Location would feed into this in your case.

The number of points in each group, so if you want five groups you provide five numbers corresponding to the number of items in the group. As an example [3,4,6] would provide a group of 3, a group of four, and a group of 6 as the output.

In your use case it is likely that you will want to delete the existing cubes and replace them with new ones or use a lookup method to re-associate each point with a family instance.

I’ll try to have a look at this later, but give it a shot and see if you can make it work with these instructions. I am also bumping your trust level so file sharing shouldn’t be an issue in the future.

Revit 2021!?!?!?!?!?!

I don’t have that installed and can’t get it. Hopefully you’re not working in something that old, but you certainly shouldn’t be doing something so schematic in a model in that build.

I updated the model to 2025 and built this sample graph that uses modern techniques instead of stuff like cycling lists. Give it a shot at rebuilding on your own. Note that you may want to adjust the family type sort as well as filter some (to allow for manual placement).

1 Like

i have to work with what i got. I’d love to work with a newer version but i’m not the one to decide that.

I tried using your inital script but ran into some issues and was just about to upload my dynamo file but as you were quicker with your attempt i’ll try to replicate that and see if i can get it to work in 2021

1 Like

I get that - but be sure to raise the security implications of not using supported software. Everyone on that project is increasing their likelihood of a infosec incident by a significant factor. In my experience asking “would you like to be responsible for getting our entire company and all consultants hit with a ransomware attack” will usually get things upgraded the same week (or reasonably quickly anyway).

Let me know where you get - it’s a great concept and a lot of fun. :slight_smile:

I got it working well enough. There are still a few cases of the same cube-types besides each other but its good enough.

I just tried to load it into the actual model and ran into an issue. One of the cubes has no position and thus bricks the whole script. How do i get rid of this ghostcube?

Best to identify where that cube is in the project as that will identify why it is location-less which will inform you as to how the issue can be resolved. The fast way to do this is to click the green highlighted element id in a data preview or watch node.

Tried that, it can’t find it.

Ok. Try selecting it via the element ID and look at the properties pallet for some info. Specifically stuff like workset, design options, and the like.

1 Like

Got it selected with the element ID and just deleted it. Script works now :slight_smile:
Now this is optional but how would i combine cubes of the same size into one group? Currently the cubes are sorted by size and attachment method (corner or edge) but ideally i’d like to have the minimum distance to all cubes of the same size

EDIT:
My hack-solution would be to generate the whole thing using only one attachment method and then replacing 50% of cubes with the other attachment method.

I don’t understand the question - can you sketch something?


I have this situation quite often. These cubes are from different families because one is attached on the corner and the other on the edge of the cube. Both ar 400mm cubes. Currently these are “different” cubes so they are allowed to be next to each other. Ideally id like to not have two cubes of the same size next to each other regardless of which family they belong to.

  1. Set all the family types to a single type.
  2. Combine the instances into 3 groups using the method so far.
  3. Split the groups in half using a List.Chop and List.Transpose (watch your list levels!), adn then flatten the list at level two so you get 6 groups of family instances.
  4. Set the family types using a reordered list of matching sizes - i.e. [type 1 40, type 2 40, type 1 30, type 2 30, type 1 20, type 2 20] instead of [type 1 40, type 1 30, type 1 20, type 2 40, type 2 30, type 2 20] or whatever you have now.
1 Like

Yes, will do. Many thanks for your help!

1 Like