Skip to content

Instantly share code, notes, and snippets.

@timseverien
Last active January 3, 2018 18:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timseverien/d11d7174e4a3ab4c4dfb41276bb30e40 to your computer and use it in GitHub Desktop.
Save timseverien/d11d7174e4a3ab4c4dfb41276bb30e40 to your computer and use it in GitHub Desktop.
Blender k-means fractions

K-means Fractions Blender Script

This script splits a mesh in pow(CLUSTER_COUNT, ITERATION_COUNT) fractions in pseudo-random shapes.

  1. Select an object
  2. Distribute an amount of random points (SEED_POINT_COUNT) in the mesh of that object
  3. K-means the points into k clusters (CLUSTER_COUNT)
  4. Find the convex hull of the clusters, creating meshes
  5. Remove original object
  6. Go to step 1

Inspired by Matt DesLauriers' tweet: https://twitter.com/mattdesl/status/945728639496310786

Installing dependencies

The commends below work if you have pip (a Python package manager) installed. If not, see installing pip for Blender's Python.

  • python -m pip install sklearn

How to use

  • Select any object in the 3D View
  • Run the script

Possible improvements

  • Instead of picking the first selected object, iterate through all selected objects
import bpy
import itertools
import mathutils
from mathutils import Vector
from sklearn.cluster import KMeans
CLUSTER_COUNT = 4
SEED_POINT_COUNT = 1024 * 4
ITERATION_COUNT = 3
def lerp(a, b, t):
return t * (b - a) + a
def is_point_in_mesh(point, object):
axes = [Vector((1, 0, 0)), Vector((0, 1, 0)), Vector((0, 0, 1))]
is_outside = False
for axis in axes:
count = 0
orig = point
while True:
result, location, normal, index = object.ray_cast(orig, orig + axis * 10000.0)
if index == -1: break
count += 1
orig = location + axis * 0.00001
if count % 2 == 0:
is_outside = True
break
return not is_outside
def get_bounding_box_component(bounding_box, component_index):
return list(map(lambda v: v[component_index], bounding_box))
def unselect_all():
for obj in bpy.context.selected_objects:
obj.select = False
def get_bounding_box_min_max(bounding_box):
x = get_bounding_box_component(bounding_box, 0)
y = get_bounding_box_component(bounding_box, 1)
z = get_bounding_box_component(bounding_box, 2)
vmin = Vector((min(x), min(y), min(z)))
vmax = Vector((max(x), max(y), max(z)))
return (vmin, vmax)
def generate_points(point_count, object):
min, max = get_bounding_box_min_max(object.bound_box)
points = []
for x in range(0, point_count):
points.append(Vector((
lerp(min.x, max.x, mathutils.noise.random()),
lerp(min.y, max.y, mathutils.noise.random()),
lerp(min.z, max.z, mathutils.noise.random())
)))
return list(filter(lambda v: is_point_in_mesh(v, object), points))
def get_clusters(points, n_clusters=2):
clusters = list(map(lambda c: [], range(0, n_clusters)))
cluster_indexes = KMeans(n_clusters=n_clusters).fit(points).predict(points)
for i in range(0, len(points)):
point = points[i]
cluster_index = cluster_indexes[i]
clusters[cluster_index].append(point)
return clusters
def replace_object(object):
objects = []
points = generate_points(SEED_POINT_COUNT, object)
clusters = get_clusters(points, CLUSTER_COUNT)
for cluster in clusters:
unselect_all()
mesh = bpy.data.meshes.new('fractal')
mesh.from_pydata(cluster, [], [])
fractal = bpy.data.objects.new('fractal', mesh)
fractal.location = bpy.context.scene.cursor_location
fractal.active_material = object.active_material
bpy.context.scene.objects.link(fractal)
bpy.context.scene.update()
bpy.context.scene.objects.active = fractal
fractal.select = True
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.convex_hull()
bpy.ops.object.mode_set(mode='OBJECT')
objects.append(fractal)
bpy.context.scene.objects.unlink(object)
return objects
def main():
if len(bpy.context.selected_objects) == 0:
console.error('No object selected')
return
objects = [bpy.context.selected_objects[0]]
for i in range(0, ITERATION_COUNT):
newObjects = list(map(lambda o: replace_object(o), objects))
objects = list(itertools.chain(*newObjects))
main()

Installing pip for Blender's Python

  • Download get-pip.py
  • Find the python in your Blender installation (Blender Foudation/Blender/2.x/python/bin)
  • Open a terminal and run python get-pip.py

Note: in case you run into errors, you might have to change the directory access settings to Blender\2.79\python\lib\site-packages, or run above script as administrator.

Now you can run pip via path/to/Blender/2.x/python/bin/python -m pip.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment