├── README.md ├── .gitattributes ├── .gitignore ├── menger_csg2.py ├── menger_csg.py └── menger.py /README.md: -------------------------------------------------------------------------------- 1 | Blender iterated fractals 2 | ========================= 3 | 4 | ## Generates iterated fractal objects in blender -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must ends with two \r. 29 | Icon 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | -------------------------------------------------------------------------------- /menger_csg2.py: -------------------------------------------------------------------------------- 1 | # Thanks to Ethan (@sherbondy) for the awesome idea of using CSG! 2 | # Much slower than the other version, but it uses like 1/3 of the geometry 3 | # Refactored version. slightly slower but more readable. 4 | 5 | import bpy 6 | import mathutils 7 | 8 | bpy.ops.object.select_all(action='DESELECT') 9 | 10 | pos = bpy.context.scene.cursor_location 11 | 12 | bpy.ops.mesh.primitive_cube_add(view_align=False, enter_editmode=False) 13 | iterator = bpy.context.active_object 14 | iterator.name = 'Iterator' 15 | 16 | bpy.ops.mesh.primitive_cube_add(view_align=False, enter_editmode=False) 17 | menger = bpy.context.active_object 18 | menger.name = 'MengerSponge' 19 | 20 | def apply_modifier(): 21 | bpy.ops.object.modifier_add(type='BOOLEAN') 22 | bpy.context.object.modifiers["Boolean"].operation = 'DIFFERENCE' 23 | bpy.context.object.modifiers["Boolean"].object = bpy.data.objects["Iterator"] 24 | bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Boolean") 25 | 26 | max_depth = 3 27 | 28 | def cycle(array): 29 | new_array = [] 30 | for i in range(len(array)): 31 | new_array.append(array[(i+1)%len(array)]) 32 | return new_array 33 | 34 | 35 | 36 | for depth in range (max_depth): 37 | for i in range(3**depth): 38 | for j in range(3**depth): 39 | scale = [1.01, 1/3**(depth+1), 1/3**(depth+1)] 40 | location = [0, -1+1/3**depth+2*i/3**depth, -1+1/3**depth+2*j/3**depth] 41 | for k in range(3): 42 | 43 | iterator.scale = scale 44 | iterator.location = pos + mathutils.Vector(location) 45 | apply_modifier() 46 | scale = cycle(scale) 47 | location = cycle(location) 48 | 49 | 50 | bpy.ops.object.select_all(action='DESELECT') 51 | iterator.select = True 52 | bpy.ops.object.delete() 53 | 54 | menger.select = True -------------------------------------------------------------------------------- /menger_csg.py: -------------------------------------------------------------------------------- 1 | # Thanks to Ethan (@sherbondy) for the awesome idea of using CSG! 2 | # Much slower than the other version, but it uses like 1/3 of the geometry 3 | 4 | import bpy 5 | import mathutils 6 | 7 | bpy.ops.object.select_all(action='DESELECT') 8 | 9 | pos = bpy.context.scene.cursor_location 10 | 11 | bpy.ops.mesh.primitive_cube_add(view_align=False, enter_editmode=False) 12 | iterator = bpy.context.active_object 13 | iterator.name = 'Iterator' 14 | 15 | bpy.ops.mesh.primitive_cube_add(view_align=False, enter_editmode=False) 16 | menger = bpy.context.active_object 17 | menger.name = 'MengerSponge' 18 | 19 | def apply_modifier(): 20 | bpy.ops.object.modifier_add(type='BOOLEAN') 21 | bpy.context.object.modifiers["Boolean"].operation = 'DIFFERENCE' 22 | bpy.context.object.modifiers["Boolean"].object = bpy.data.objects["Iterator"] 23 | bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Boolean") 24 | 25 | max_depth = 3 26 | 27 | for depth in range (max_depth): 28 | for i in range(3**depth): 29 | for j in range(3**depth): 30 | iterator.scale = (1.01, 1/3**(depth+1), 1/3**(depth+1)) 31 | iterator.location = pos + mathutils.Vector((0, -1+1/3**depth+2*i/3**depth, -1+1/3**depth+2*j/3**depth)) 32 | apply_modifier() 33 | 34 | iterator.scale = (1/3**(depth+1), 1.01, 1/3**(depth+1)) 35 | iterator.location = pos + mathutils.Vector((-1+1/3**depth+2*i/3**depth, 0, -1+1/3**depth+2*j/3**depth)) 36 | apply_modifier() 37 | 38 | iterator.scale = (1/3**(depth+1), 1/3**(depth+1), 1.01) 39 | iterator.location = pos + mathutils.Vector((-1+1/3**depth+2*i/3**depth, -1+1/3**depth+2*j/3**depth, 0)) 40 | apply_modifier() 41 | 42 | 43 | bpy.ops.object.select_all(action='DESELECT') 44 | iterator.select = True 45 | bpy.ops.object.delete() 46 | 47 | menger.select = True -------------------------------------------------------------------------------- /menger.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | global_depth = 3 4 | 5 | def gen_mat (x): # x < 8 6 | mat = bpy.data.materials.new('menger_mat_'+str(x)) 7 | mat.diffuse_color = (1-x//4 % 2, 1-x//2 % 2, 1-x % 2) 8 | 9 | return mat 10 | 11 | def new_mat_idx (x, y, z, mat_idx): 12 | new_mat_idx = mat_idx 13 | 14 | if x >= 1: 15 | new_mat_idx[1] += 1 16 | if x <= 1: 17 | new_mat_idx[0] += 1 18 | 19 | if y >= 1: 20 | new_mat_idx[3] += 1 21 | if y <= 1: 22 | new_mat_idx[2] += 1 23 | 24 | if z >= 1: 25 | new_mat_idx[5] += 1 26 | if z <= 1: 27 | new_mat_idx[4] += 1 28 | return new_mat_idx 29 | 30 | class MengerSponge: 31 | def __init__(self): 32 | self.idx = 0; 33 | self.verts = [] 34 | self.faces = [] 35 | self.face_mat_idx = [] 36 | 37 | def recurse(self, pos, size, mat_idx, depth): 38 | 39 | if depth == 0: 40 | for x in range(2): 41 | for y in range(2): 42 | for z in range(2): 43 | self.verts.append((pos[0]+x*size, pos[1]+y*size, pos[2]+z*size)) # make vertices 44 | self.faces.append((self.idx+4, self.idx+5, self.idx+7, self.idx+6)) 45 | self.faces.append((self.idx+0, self.idx+1, self.idx+3, self.idx+2)) 46 | self.faces.append((self.idx+2, self.idx+3, self.idx+7, self.idx+6)) 47 | self.faces.append((self.idx+0, self.idx+1, self.idx+5, self.idx+4)) 48 | self.faces.append((self.idx+1, self.idx+5, self.idx+7, self.idx+3)) 49 | self.faces.append((self.idx+0, self.idx+4, self.idx+6, self.idx+2)) 50 | 51 | self.face_mat_idx.extend(mat_idx) 52 | self.idx += 8 53 | return 54 | # todo: maybe use bpy.ops.mesh instead? 55 | 56 | for x in range(3): 57 | for y in range(3): 58 | for z in range(3): 59 | if x%2 + y%2 + z%2 < 2: # check if valid. ugly but almost twice as fast as x%3==1 60 | new_mat_idx = list(map(lambda x: x, mat_idx)) 61 | 62 | if x >= 1: 63 | new_mat_idx[1] = depth 64 | if x <= 1: 65 | new_mat_idx[0] = depth 66 | 67 | if y >= 1: 68 | new_mat_idx[3] = depth 69 | if y <= 1: 70 | new_mat_idx[2] = depth 71 | 72 | if z >= 1: 73 | new_mat_idx[5] = depth 74 | if z <= 1: 75 | new_mat_idx[4] = depth 76 | self.recurse([pos[0]+x*size/3, pos[1]+y*size/3, pos[2]+z*size/3], size/3, new_mat_idx, depth-1) 77 | 78 | def generate(self, size, depth): 79 | self.recurse([0.0, 0.0, 0.0], size, [0, 0, 0, 0, 0, 0], depth) 80 | mesh = bpy.data.meshes.new(name="MengerSponge") 81 | object = bpy.data.objects.new('MESH', mesh) 82 | bpy.context.scene.objects.link(object) 83 | mesh.from_pydata(self.verts, [], self.faces) 84 | mesh.update(calc_edges = True) 85 | 86 | for i in range(global_depth+1): 87 | object.data.materials.append(gen_mat(i)) 88 | 89 | 90 | for f in mesh.polygons: 91 | f.material_index = self.face_mat_idx[f.index] 92 | 93 | bpy.context.scene.objects.active = object 94 | bpy.ops.object.mode_set(mode = 'EDIT') 95 | bpy.ops.mesh.remove_doubles() 96 | bpy.ops.object.mode_set(mode = 'OBJECT') 97 | 98 | sponge = MengerSponge() 99 | sponge.generate(10,global_depth) --------------------------------------------------------------------------------