www.xbdev.net
xbdev - software development
Wednesday July 16, 2025
Home | Contact | Support | Blender (.py) Scripts... Automating Blender ..
     
 

Blender (.py) Scripts...

Automating Blender ..

 

Fractal Fun - Creating Menger Sponge


We'll use Blender scripts to construct a simple 'Menger Sponge' fractal - a simple but effective algorithm - we can recursively subdivide a cube into smaller cubes.


A render of the menger sponge fractal of depth 2 using a glass type material.
A render of the menger sponge fractal of depth 2 using a glass type material.


The first version below is a bit slow - as it uses the boolean modifier to 'cut' holes in the cube. Gradually building up the fractal effect.

import bpy
from mathutils import Vector

# === CONFIGURATION ===
MAX_DEPTH 2
SIZE 
2.0

# === CLEAN THE SCENE ===
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()

# === HELPER FUNCTION: Carve Menger Sponge ===
def create_menger(locationsizedepth):
    
# Create the solid cube to start from
    
bpy.ops.mesh.primitive_cube_add(size=sizelocation=location)
    
sponge bpy.context.active_object

    def recursive_carve
(objlocsizedepth):
        if 
depth == 0:
            return

        
step size 3
        holes 
= []

        for 
x in range(3):
            for 
y in range(3):
                for 
z in range(3):
                    
# Skip the central cube and centers of each face
                    
if (== and == 1) or (== and == 1) or (== and == 1):
                        
cube_loc Vector((
                            
loc[0] + (1) * step,
                            
loc[1] + (1) * step,
                            
loc[2] + (1) * step
                        
))
                        
bpy.ops.mesh.primitive_cube_add(size=steplocation=cube_loc)
                        
hole bpy.context.active_object
                        holes
.append(hole)

        
# Apply Boolean modifiers
        
for hole in holes:
            
bool_mod obj.modifiers.new(name='Bool'type='BOOLEAN')
            
bool_mod.operation 'DIFFERENCE'
            
bool_mod.object hole
            bpy
.context.view_layer.objects.active obj
            bpy
.ops.object.modifier_apply(modifier=bool_mod.name)
            
bpy.data.objects.remove(holedo_unlink=True)

        
# Now recurse into each sub-cube
        
for x in range(3):
            for 
y in range(3):
                for 
z in range(3):
                    if (
xyz) == (111): continue
                    if 
sum([axis == for axis in (xyz)]) >= 2: continue  # skip face/center holes
                    
sub_loc Vector((
                        
loc[0] + (1) * step,
                        
loc[1] + (1) * step,
                        
loc[2] + (1) * step
                    
))
                    
recursive_carve(objsub_locstepdepth 1)

    
recursive_carve(spongelocationsizedepth)
    return 
sponge

# === BUILD THE SPONGE ===
sponge create_menger(Vector((000)), SIZEMAX_DEPTH)

# === ADD LIGHT ===
bpy.ops.object.light_add(type='AREA'location=(4, -46))
light bpy.context.active_object
light
.data.energy 1500

# === ADD CAMERA ===
bpy.ops.object.camera_add(location=(6, -64), rotation=(1.200.8))
bpy.context.scene.camera bpy.context.active_object


Meshes


The boolean operation is simple - and it can produce a single mesh effect - however, it's slow! Very slow! If you go past a depth of 3 you'll probably crash your machine - or get it stuck in a loop for a long while - as it does the work.

A cheep and cheerful alternative is to construct the mesh from lots of small cubes.


The menger sponge fractal looks the same as the boolen operation version - but it
The menger sponge fractal looks the same as the boolen operation version - but it's made up of lots of small cubes instead of a single mesh.


This version is a lot faster - and lets you do a lot more with your menger fractal.

import bpy
from mathutils import Vector

# === CONFIG ===
MAX_DEPTH 2
SIZE 
2.0

# === CLEAN SCENE ===
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()

# === MAKE BASE MESH ONCE ===
mesh bpy.data.meshes.new("CubeMesh")
bm bpy.data.objects.new("BaseCube"mesh)
bpy.context.collection.objects.link(bm)
bpy.ops.object.select_all(action='DESELECT')
bm.select_set(True)
bpy.context.view_layer.objects.active bm
bpy
.ops.object.delete()  # remove for instancing use only

bpy.ops.mesh.primitive_cube_add(size=1)
base_cube bpy.context.active_object
mesh_data 
base_cube.data.copy()
bpy.data.objects.remove(base_cube)

# === FUNCTION TO CHECK REMOVAL ===
def is_filled(xyz):
    
checks = [== 1== 1== 1]
    return 
checks.count(True) < 2

def add_cube
(locationsize):
    
obj bpy.data.objects.new("Cube"mesh_data)
    
obj.location location
    obj
.scale = (size 1,) * 3
    bpy
.context.collection.objects.link(obj)

# === BUILD SPONGE ===
def menger(locationsizedepth):
    
step size 3
    
for x in range(3):
        for 
y in range(3):
            for 
z in range(3):
                if 
not is_filled(xyz):
                    continue
                
offset Vector(((1) * step, (1) * step, (1) * step))
                
pos location offset
                
if depth 1:
                    
menger(posstepdepth 1)
                else:
                    
add_cube(posstep)

menger(Vector((000)), SIZEMAX_DEPTH)

# === ADD LIGHT ===
bpy.ops.object.light_add(type='SUN'location=(10, -1010))
bpy.context.active_object.data.energy 3

# === ADD CAMERA ===
bpy.ops.object.camera_add(location=(6, -64), rotation=(1.200.8))
bpy.context.scene.camera bpy.context.active_object




View the boolean version and the mesh version side by side - you
View the boolean version and the mesh version side by side - you'll notice the boolean version is a single mesh while the other version is lots of little cubes.









 
Advert (Support Website)

 
 Visitor:
Copyright (c) 2002-2025 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.