Hi blenderartists, just registered here to post a script I wrote quite some time ago, when I started fiddling around with blender. I used to fill an objects surface with spheres, and it is probably not the best approach ever done, it worked for me.
This works currently only with spheres, but maybe someone will find it usefull anyways and it might be not too hard to modify the script to create any kind of object.
#!BPY
"""
Name: 'Object Filler'
Blender: 236
Group: 'Object'
Tooltips: 'fill an objects surface with other objects'
"""
# to use this script:
# 1. create a Material named "Particle" in your scene
# 2. select the object which you want to fill with spheres
# 3. open this script in the blender text editor
# 4. optional: tweak the globals which control the particle to your liking
# 5. execute the script
#
# be carefull with very complex objects or very small particle sizes, the script may
# take a very long time to finish! you have been warned
#
# author: [email protected]
import Blender;
from Blender import Mathutils, NMesh, Object, Scene, Window, Material;
from Blender.Mathutils import MidpointVecs;
import math;
from math import *;
import random;
# Define some things, like exceptions we need later.
selection_except = 'selection_except'
type_except = 'type_except'
cantfill_except = 'cantfill_except'
# these globals can be used to control the size and the variation of the spheres that will be created as particles
particle_radius = .3;
particle_radius_diff = 0;
particle_resolution = 12;
# one big dict for every vertex we have already touched (iirc)
# i didn't rc, it is one big dict for every _position_ ever touched, so this grows by one every time
# a particle gets created and certainly contributes alot to the horrible performance of this script...
processed = {};
#
# these tricky_func thingies are little helpers to find the correct neighbouring vertices for a given vertex
# on a quad or a triangle
#
def tricky_func_quad(x):
if x == 0:
return (1,2,3);
elif x == 1:
return (2,3,0);
elif x == 2:
return (3,0,1);
elif x == 3:
return (0,1,2);
else:
raise ValueError;
def tricky_func_triangle(x):
if x == 0:
return (1,2);
elif x == 1:
return (2,0);
elif x == 2:
return (0,1);
else:
raise ValueError;
def dot_product(v,w):
dp = 0;
if len(v) != len(w):
return false;
for i in range(0,len(v)):
dp=dp+round(v[i],4)*round(w[i],4);
return dp;
def edge_length(v,w):
if len(v) != len(w) or v == w:
return 0;
length_v = sqrt(dot_product(v,v));
length_w = sqrt(dot_product(w,w));
if length_v == 0 or length_w == 0:
return 0;
cosinus_vw = dot_product(v,w)/(length_v*length_w);
length_squared = pow(length_v,2) + pow(length_w,2) - 2*length_v*length_w*cosinus_vw;
if length_squared < 0:
return 0;
else:
return sqrt(length_squared);
#
# function to create an UVsphere, stolen from jmsoler.free.fr
#
# takes radius, resolution, and a translation vector
#
def create_sphere(radius,res,tx,ty,tz):
sphere=NMesh.New();
maxpoints=res*res;
n=math.sqrt(maxpoints);
n=int(n);
for i in range(0, n):
for j in range(0, n):
x = (sin(j*pi*2/(n-1))*cos(-pi/2+i*pi/(n-1))*radius)+tx;
y = (cos(j*pi*2/(n-1))*cos(-pi/2+i*pi/(n-1))*radius)+ty;
z = (sin(-pi/2+i*pi/(n-1))*radius)+tz;
v=NMesh.Vert( x , y , z );
sphere.verts.append(v);
n0=len(range(0,n));
for i in range(0,n-1):
for j in range(0,n-1):
f=NMesh.Face();
f.v.append(sphere.verts[i*n0+j]);
f.v.append(sphere.verts[i*n0+j+1]);
f.v.append(sphere.verts[(i+1)*n0+j+1]);
f.v.append(sphere.verts[(i+1)*n0+j]);
sphere.faces.append(f);
blubb = Object.New("Mesh","blubb");
blubb.setMatrix(current_matrix);
blubb.link(sphere);
sphere.update(1);
scene.link(blubb);
blubb2 = Object.Get(blubb.getName());
blubb2.setMaterials(current_materials);
blubb2.colbits = 1;
Window.Redraw();
def create_pseudo_face(face,i,midpoint):
pseudo_face = NMesh.Face();
if len(face.v) == 4:
foo = MidpointVecs(face.v[i].co,face.v[tricky_func_quad(i)[0]].co);
pseudo_face.append(NMesh.Vert(foo[0],foo[1],foo[2]));
foo = midpoint;
pseudo_face.append(NMesh.Vert(foo[0],foo[1],foo[2]));
foo = MidpointVecs(face.v[tricky_func_quad(i)[2]].co,face.v[i].co)
pseudo_face.append(NMesh.Vert(foo[0],foo[1],foo[2]));
pseudo_face.append(face.v[i]);
else:
foo = face.v[i].co;
pseudo_face.append(NMesh.Vert(foo[0],foo[1],foo[2]));
foo = face.v[tricky_func_triangle(i)[0]].co;
pseudo_face.append(NMesh.Vert(foo[0],foo[1],foo[2]));
foo = midpoint;
pseudo_face.append(NMesh.Vert(foo[0],foo[1],foo[2]));
pseudo_face.append(face.v[i]);
return pseudo_face;
def process_vertex(v):
vertex = (v.x,v.y,v.z);
found_long_edge = 0;
if not processed.has_key(vertex):
for i in processed.keys():
if edge_length(vertex, processed[i]) < particle_radius:
found_long_edge = 1;
if found_long_edge == 0:
processed[vertex] = vertex;
diff = random.uniform(-1*particle_radius_diff,particle_radius_diff);
create_sphere(particle_radius+diff,particle_resolution,vertex[0],vertex[1],vertex[2]);
def process_edge(edge):
midpoint = Mathutils.MidpointVecs(edge[0],edge[1]);
process_vertex(midpoint);
def process_face(face):
dont_place_midpoint = 0;
face3 = 0;
if len(face.v) == 4:
edges = [[face.v[0].co, face.v[1].co], [face.v[1].co, face.v[2].co], [face.v[2].co, face.v[3].co], [face.v[3].co, face.v[0].co]];
edges_length = [edge_length(face.v[0], face.v[1]), edge_length(face.v[1], face.v[2]), edge_length(face.v[2], face.v[3]), edge_length(face.v[3], face.v[0])];
midpoint = MidpointVecs(MidpointVecs(face.v[0].co, face.v[1].co),MidpointVecs(face.v[2].co, face.v[3].co));
else:
face3 = 1;
edges = [[face.v[0].co, face.v[1].co], [face.v[1].co, face.v[2].co], [face.v[2].co, face.v[0].co]];
edges_length = [edge_length(face.v[0], face.v[1]), edge_length(face.v[1], face.v[2]), edge_length(face.v[2], face.v[0])];
if edges_length[0] > edges_length[1] and edges_length[2]:
midpoint = MidpointVecs(MidpointVecs(edges[0][0], edges[0][1]), edges[2][0]);
elif edges_length[1] > edges_length[0] and edges_length[2]:
midpoint = MidpointVecs(MidpointVecs(edges[1][0], edges[1][1]), edges[0][0]);
else:
midpoint = MidpointVecs(MidpointVecs(edges[2][0], edges[2][1]), edges[1][0]);
if edges_length[0] >= particle_radius:
process_vertex(edges[0][0]);
if edges_length[0] > 2*particle_radius:
process_edge(edges[0]);
if edges_length[1] >= particle_radius:
process_vertex(edges[1][0]);
if edges_length[1] > 2*particle_radius:
process_edge(edges[1]);
if edges_length[2] >= particle_radius:
process_vertex(edges[2][0]);
if edges_length[2] > 2*particle_radius:
process_edge(edges[2]);
if face3 == 0:
if edges_length[3] >= particle_radius:
process_vertex(edges[3][0]);
if edges_length[3] > 2*particle_radius:
process_edge(edges[3]);
for i in range(0,len(face.v)):
if edges_length[i] >= 3*particle_radius:
process_face(create_pseudo_face(face,i,midpoint));
dont_place_midpoint = 1;
if dont_place_midpoint == 0:
process_vertex(midpoint);
dont_place_midpoint = 1;
def transform_vertex(v,matrix):
new_v = [0, 0, 0, 0];
for i in range(0,2):
for j in range(0,3):
new_v[i] = new_v[i]+v[i]*matrix[j][i];
print new_v;
return new_v;
def transform_face(face,matrix):
new_face = NMesh.Face();
for i in range(0,len(face.v)):
foo = transform_vertex(face.v[i].co, matrix);
new_face.append(NMesh.Vert(foo[0],foo[1],foo[2]));
return new_face;
#
# Get the Scene + Selected Objects, if more then
# one selected -> throw exception(for now)
#
scene = Scene.getCurrent();
objects = Object.GetSelected();
if len(objects) != 1:
print "You must select at least 1 Object, nor should you select more!";
raise selection_except;
if objects[0].getType() != "Mesh":
print "ALARM! Object isn't a Mesh! It is : " + objects[0].getType();
raise type_except;
current_object = objects[0];
current_matrix = current_object.getMatrix();
current_data = current_object.getData();
current_faces = current_data.faces;
current_materials = [];
current_materials.append(Material.Get("Particle"));
print "STARTING";
for i in range(0,len(current_faces)):
process_face(current_faces[i]);
print "FINISHED";
The good thing about this script and why I wrote it in the first place is that it manages to fill any arbitrary object evenly with spheres, because it does not only rely on the vertices.
The bad things are that it can be really slow when used with more complex objects, and that as a whole, it feels a little bit like an ugly hack.
Anyways, hope it might be of some kind of use.
PS: Sorry for not documenting the ‘algorithm’. I am being a bit lazy. But if anyone would like some more information, i’ll explain.