# Generate random ellipsoids

I have a problem to generate random ellipsoids inside a cubic space.

This is the script I wrote:

# How many Sphere you want to add
count = 25

# Sphere properties
size = 1

# The sphere will be created between -domain <--> domain
domain = 1


My problem in this script is that the spheres are away from each other and I cant get ellipsoid.

• So, My first question how can I change these spheres into ellipsoidal, starting with the ellipsoid dimension statement: size_x =, = size_y, size_z =

• the second question, How to make them close to each others without getting them overlapped.

• Sorry, I'm new here I posted a new topic, Dec 6, 2015 at 12:03
• ok, I click on the small triangle that is left of batFINGER Dec 9, 2015 at 17:29

## 1 Answer

Cobbled together a test script, that has parts that answer both of your q's. Scale a sphere on x, y, z to make ellipsoids. (set sphere_only=False in script)

The 'within_touch' method looks at two spheres and if they are closer than the sum of their two radii, they are overlapping. The distance between the spheres is the length of the vector created by subtracting ones location from the other.

If a pair of spheres are found to be overlapping the method returns the required vector to move to make them touch (with a bit of tol)

While testing added an object color, the bluer the harder to place.

import bpy
context = bpy.context
from random import uniform
from mathutils import Vector

count = 200

# random scale
min_scale = 0.001
max_scale = 1
# scale_factor .. scale down to help fit
scale_factor = 0.1
domain = 1
only_spheres = True

tries_per_sphere = 256

use_context_object = False
obj = context.object

def random_vector(a, b):
return Vector([uniform(a, b) for c in "xyz"])

def checkbounds(sphere, fix):
#return True
loc = sphere.location + fix
#dm = max(sphere.dimensions) / 2
dm = max(sphere.scale)
_min = -domain + dm
_max =  domain - dm
for i, v in enumerate(loc):
if v < _min:
loc[i] = _min
if v > _max:
loc[i] = _max
return loc

def inbounds(sphere, loc):
#return True
dm = max(sphere.scale)
return -domain + dm < min(loc) and  max(loc) < domain - dm

def random_sphere(sphere):
# random scale and rotation for sphere based on settings
if only_spheres:
scale = uniform(min_scale , max_scale) * Vector((1, 1, 1))
else:
scale = random_vector(min_scale, max_scale)
scale = Vector([min(s, domain) for s in scale])
dom = domain - max(scale)
sphere.scale = scale

sphere.location = random_vector(-dom, dom)

scene = context.scene

mat = bpy.data.materials.get("randobjcol")
if not mat:
mat = bpy.data.materials.new("randobjcol")
mat.use_object_color = True

boundbox = bpy.data.objects.get("BoundBOX")
if not boundbox:
bpy.ops.mesh.primitive_cube_add()
boundbox = context.scene.objects.active
boundbox.name = "BoundBOX"
boundbox.draw_type = 'WIRE'
boundbox.hide_select = True
boundbox.location = (0, 0, 0)

if not scene.objects.get("BoundBOX"):
scene.objects.link(boundbox)

boundbox.scale = domain * Vector((1, 1, 1))

spheres = []
if use_context_object and obj:
sphere = obj
else:
bpy.ops.mesh.primitive_uv_sphere_add()
sphere = context.scene.objects.active
bpy.ops.object.shade_smooth()
scene.objects.unlink(sphere)

sphere.active_material = mat

for i in range(count):
spheres.append(sphere)
random_sphere(sphere)
sphere = sphere.copy()
#context.scene.objects.link(sphere)

# update the spheres dimensions
#context.scene.update()

tries = 0
t_count = 0
r_count = 0
moves = 0
resets = 0
pfix = Vector()

def within_touch(s1, s2):
def r(s):
return max(s.scale)
#return max(s.dimensions) / 2
#return sum(s.dimensions) / 3 #  average.

d = (s2.location - s1.location)

r1, r2 = r(s1), r(s2)

radsum = r(s1) + r(s2)
if d.length <= 0.0001:
print("SAME")
return Vector((r1, r1, r1))
# same position
elif d.length < radsum:
# return a vector to move away
d.length = radsum - d.length
return  -d
return Vector()

sphere = spheres.pop()
arranged_spheres = [sphere]
sphere = spheres.pop()

while sphere and tries < tries_per_sphere * count:
if t_count > 5 and abs(min_scale - max_scale) > 0.00001:
v = scale_factor * sphere.scale
sphere.scale = Vector([max(s, min_scale) for s in v])
#scene.update()
touchers = [s for s in [within_touch(sphere, s) for s in arranged_spheres] if s.length > 0]
if len(touchers):
fix = Vector()
for v in touchers:
if sphere.color[0]:
sphere.color[0] -= 0.1
elif sphere.color[1]:
sphere.color[1] -= 0.01
else:
sphere.color[2] -= 0.001
fix += v

#if fix.length < 0.0001 or not inbounds(sphere, loc) or r_count > tries_per_sphere:
if (fix < 0.0001) or r_count > tries_per_sphere / 2:
r_count = 0
random_sphere(sphere)
resets += 1
#sphere.color[1] = 0
else:
loc = checkbounds(sphere, fix)
sphere.location = loc
moves += 1
t_count += 1
r_count += 1
tries += 1
else:
print("Arranged ", sphere.name, "moves:", moves, "rand moves", resets, "fixes:", t_count)
moves = 0
t_count = 0
r_count = 0
resets = 0
arranged_spheres.append(sphere)
sphere =  spheres.pop() if len(spheres) else None

for s in arranged_spheres:
if not scene.objects.get(s.name):
scene.objects.link(s)

print("Arranged % d of %d" % (len(arranged_spheres), count))
print("TRIES", tries)


Edit

Following shows size 0.142 spheres placed in a domain 1 cube. Placed 197 of 200 on this run, with settings shown.

Update for 2.8

import bpy
context = bpy.context
from random import uniform
from mathutils import Vector

count = 200

# random scale
min_scale = 0.1
max_scale = 0.1
# scale_factor .. scale down to help fit
scale_factor = 0.1
domain = 1
only_spheres = True

tries_per_sphere = 256

use_context_object = False
obj = context.object

def random_vector(a, b):
return Vector([uniform(a, b) for c in "xyz"])

def checkbounds(sphere, fix):
#return True
loc = sphere.location + fix
#dm = max(sphere.dimensions) / 2
dm = max(sphere.scale)
_min = -domain + dm
_max =  domain - dm
for i, v in enumerate(loc):
if v < _min:
loc[i] = _min
if v > _max:
loc[i] = _max
return loc

def inbounds(sphere, loc):
#return True
dm = max(sphere.scale)
return -domain + dm < min(loc) and  max(loc) < domain - dm

def random_sphere(sphere):
# random scale and rotation for sphere based on settings
if only_spheres:
scale = uniform(min_scale , max_scale) * Vector((1, 1, 1))
else:
scale = random_vector(min_scale, max_scale)
scale = Vector([min(s, domain) for s in scale])
dom = domain - max(scale)
sphere.scale = scale

sphere.location = random_vector(-dom, dom)

scene = context.scene
view_layer = context.view_layer
mat = bpy.data.materials.get("randobjcol")
if not mat:
mat = bpy.data.materials.new("randobjcol")
#    mat.use_object_color = True

boundbox = bpy.data.objects.get("BoundBOX")
if not boundbox:
bpy.ops.mesh.primitive_cube_add()
boundbox = context.object
boundbox.name = "BoundBOX"
boundbox.display_type = 'WIRE'
boundbox.hide_select = True
boundbox.location = (0, 0, 0)

if not scene.collection.objects.get("BoundBOX"):
scene.collection.objects.link(boundbox)

boundbox.scale = domain * Vector((1, 1, 1))

spheres = []
if use_context_object and obj:
sphere = obj
else:
bpy.ops.mesh.primitive_uv_sphere_add()
sphere = context.object
#bpy.ops.object.shade_smooth()
context.collection.objects.unlink(sphere)

sphere.active_material = mat

for i in range(count):
spheres.append(sphere)
random_sphere(sphere)
sphere = sphere.copy()
#context.scene.objects.link(sphere)

# update the spheres dimensions
#context.scene.update()

tries = 0
t_count = 0
r_count = 0
moves = 0
resets = 0
pfix = Vector()

def within_touch(s1, s2):
def r(s):
return max(s.scale)
#return max(s.dimensions) / 2
#return sum(s.dimensions) / 3 #  average.

d = (s2.location - s1.location)

r1, r2 = r(s1), r(s2)

radsum = r(s1) + r(s2)
if d.length <= 0.0001:
print("SAME")
return Vector((r1, r1, r1))
# same position
elif d.length < radsum:
# return a vector to move away
d.length = radsum - d.length
return  -d
return Vector()

sphere = spheres.pop()
arranged_spheres = [sphere]
sphere = spheres.pop()

while sphere and tries < tries_per_sphere * count:
if t_count > 5 and abs(min_scale - max_scale) > 0.00001:
v = scale_factor * sphere.scale
sphere.scale = Vector([max(s, min_scale) for s in v])
#scene.update()
touchers = [s for s in [within_touch(sphere, s) for s in arranged_spheres] if s.length > 0]
if len(touchers):
fix = Vector()
for v in touchers:
if sphere.color[0]:
sphere.color[0] -= 0.1
elif sphere.color[1]:
sphere.color[1] -= 0.01
else:
sphere.color[2] -= 0.001
fix += v

#if fix.length < 0.0001 or not inbounds(sphere, loc) or r_count > tries_per_sphere:
if (fix < 0.0001) or r_count > tries_per_sphere / 2:
r_count = 0
random_sphere(sphere)
resets += 1
#sphere.color[1] = 0
else:
loc = checkbounds(sphere, fix)
sphere.location = loc
moves += 1
t_count += 1
r_count += 1
tries += 1
else:
print("Arranged ", sphere.name, "moves:", moves, "rand moves", resets, "fixes:", t_count)
moves = 0
t_count = 0
r_count = 0
resets = 0
arranged_spheres.append(sphere)
sphere =  spheres.pop() if len(spheres) else None

for s in arranged_spheres:
if not context.collection.objects.get(s.name):
context.collection.objects.link(s)

print("Arranged % d of %d" % (len(arranged_spheres), count))
print("TRIES", tries)

• Thank for your help, for this algorithm what's her name? - For the case where the spheres (or ellipsoids) are identical , how to make it - The case of random orientation of the ellipsoidal (random.orientation), how to make it - When I reduce the volume (domain) field that is proportional with the volume sphere (size) and in my script, domain is relation with size (rayon of sphere) I us relation of volume fraction : frac=(NVolume(sphere) / Volume(cube) or N: number of sphere, Volume cube = domainedomain * domain Does the algorithm works like this. Nov 28, 2015 at 20:38
• When i use this values: [IMG]i64.tinypic.com/w0kms6.png[/IMG], i get 1 sphere, in my script i use count:200 , size =0.142 in domain = 2. The proportion of spheres within the cube : frac=(N*Vsphere) / Volume(cube). in your algorithm, when the size is small does not work? Nov 29, 2015 at 16:13
• Sigh, I don't use the size attribute to create spheres in the script. This way I can get their dimensions directly from the scale when the mesh is created with a radius of 1. To make a size 0.142 sphere use min_scale = max_scale = 0.142. The image you show makes sense because only ONE scale 1 sphere will fit in a domain 1 cube & at only one location. ( Amended the code to only place one sphere in this instance) The code is my take on how I'd approach fitting a number of sphere's into a cube,. Feel free to use or not use, edit, amend, tweak, add volume testing... etc to suit your needs. Nov 29, 2015 at 16:47
• yes it's good :), but there is some overlap between the spheres when i use size 0.142. Nov 29, 2015 at 17:23
• Make sure you have the latest version of script from post, as prior version wasn't removing non placed matches, and wasn't dealing with last sphere properly. Not getting overlaps in my tests with those parameters. Nov 29, 2015 at 17:30