[Addon:]Cave Generator

Ever wish for an easy way to make massive caves? Well, here’s my version of a cave generation script.
At the moment caves are generated randomly, and there is a problem with the “Add Lights” option (ie, doesn’t work), but it does generate good caves for games or low-quality renderings.

Save it in a text document and install it the normal way:

'''
bl_info = {
    'name': 'CaveGen',
    'author': 'sdfgeoff',
    'version': (0, 0),
    "blender": (2, 6, 3),
    'location': 'View3D > Add > Mesh',
    'description': 'Makes Caves using metaballs converted to mesh',
    'warning': 'Murrently WIP',  # used for warning icon and text in addons panel
    'category': 'Add Mesh'}

'''

import bpy
import random

from bpy.props import IntProperty, FloatProperty, BoolProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add

def addCave(self, context):
    print ("regen Cave")


    oldLoc = [0.0, 0.0, 0.0]
    oldScale = [self.chaos, self.chaos, self.chaos]

    print ("generating initial metaball")

    bpy.ops.object.metaball_add(type='BALL', view_align=False, enter_editmode=True, location=(0.0, 0.0, 0.0))
    def randLoc():
        rand = (random.random()-0.5) * 5
        if rand > 1:
            rand = 1
        if rand < -1:
            rand = -1
        return rand

    def randScale():
        rand = (random.random()*2)+0.2
        return rand

    def randType():
        types = ['BALL', 'ELLIPSOID', 'CAPSULE', 'CUBE']
        rand = random.choice(types)
        return rand

    def addRandLights(Prob, oldLoc):
        if random.random() < Prob:
            bpy.ops.object.editmode_toggle()
            bpy.ops.object.lamp_add(type='POINT', view_align=False, location=oldLoc)
            #cave = bpy.data.objects['Mball.001']
            print (bpy.data.objects)
            #bpy.context.scene.objects.active = cave
            #bpy.ops.object.editmode_toggle()
            

    def generateNew(oldLoc, oldScale, run):
        newLoc = randLoc()*oldScale[0]+oldLoc[0], randLoc()*oldScale[1]+oldLoc[1], randLoc()*oldScale[2]+oldLoc[2]
        
        ball = bpy.ops.object.metaball_add(type=randType(), location=(newLoc))
        if self.lights == True:
            addRandLights(self.lightProb, oldLoc)
        #if random.random() > 0.9:
        #    createLamp(newLoc, run)
        #mball = bpy.context.visible_objects[run]
        #metaball = mball.data

        #newScale = [randScale(), randScale(), randScale()]
        #mball.scale[0] = newScale[0]
        #mball.scale[1] = newScale[1]
        #mball.scale[2] = newScale[2]
        return newLoc, oldScale

    mball = bpy.context.selected_objects[0]
    metaball = mball.data
    metaball.resolution = self.res
    metaball.render_resolution = self.res
    metaball.update_method = 'NEVER' 

    run = 0
    while run < self.iterations+1:
        print ("adding mball "+str(run))
        oldLoc, oldScale= generateNew(oldLoc, oldScale, run)
        run += 1

    
    metaball.update_method = 'FAST' 


    bpy.ops.object.editmode_toggle()

    if self.mesh == True:   
        bpy.ops.object.convert(target='MESH')
        bpy.ops.object.editmode_toggle()
        bpy.ops.mesh.vertices_smooth(repeat=2)
        bpy.ops.mesh.subdivide(number_cuts=1, fractal=5, seed=0)
        bpy.ops.mesh.vertices_smooth(repeat=1)
        bpy.ops.mesh.flip_normals()
        bpy.ops.object.editmode_toggle()




class caveGen(bpy.types.Operator, AddObjectHelper):
    """Add a Mesh Object"""
    bl_idname = "mesh.primitive_steppyramid_add"
    bl_label = "Pyramid"
    bl_options = {'REGISTER', 'UNDO'}

    iterations = IntProperty(name="Iterations", default=15,
                                min=2, max=10000,
                                description="Sets how many metaballs to use in the cave")

    chaos = FloatProperty(name="Chaos", default=1.0,
                                min=0.1, max=2,
                                description="Sets the scaling of distance between metaballs")

    res = FloatProperty(name="Resolution", default=0.8,
                                min=0.1, max=2.0,
                                description="Changes the resolution of the cave")

    mesh = BoolProperty(name="Convert to mesh", default=True, description="Converts to mesh and does some subdivide/fractal fucntions")
    lights = BoolProperty(name="Lights", default=False, description="Adds Lights in the passage")
    lightProb = FloatProperty(name="Light Probability", default=0.05, min=0.001, max=0.2, description="Chance of a light being placed at any given point")


    def execute(self, context):
        addCave(self, context)
        return {'FINISHED'}


def menu_func(self, context):
    self.layout.operator(caveGen.bl_idname,
                        text="Meta Cave", icon="PLUGIN")


def register():
    bpy.utils.register_class(caveGen)
    bpy.types.INFO_MT_mesh_add.append(menu_func)


def unregister():
    bpy.utils.unregister_class(caveGen)
    bpy.types.INFO_MT_mesh_add.remove(menu_func)

if __name__ == "__main__":
    register()

A few questions:

  1. How to fix the light buisness. Pretty much, how do I re-select the meta-ball I started with?
  2. How do I change the name “Pyramid” on the settings when you use it. (You can tell what script I based it off can’t you)

Planned Features:

  1. Choose between generate a random cave and using an existing mesh as a base (follows edges with the meta-ball structure)
  2. Use a group for lights rather than a lamp object.

Interesting. Gave it a try and it was fun.

hi, cool fun script, thanks :slight_smile:

Here are a few tweaks to your script.
I added a random seed. The first thing that bugged me about the script was that when I got a cave that looked nice, it randomly changed to another cave when I tried to tweak another value. By adding a random seed, the cave generation remains constant for every generation unless you change the random seed.
I added lights to the generation by creating them directly, without using bpy.ops. This is accomplished by forwarding the current scene, provided via the context when addCave is invoked. This works as a one-shot deal but if you keep generating more and more caves you get more and more lights. So get your cave where you like it then turn on the lights toggle.


'''
bl_info = {
    'name': 'CaveGen',
    'author': 'sdfgeoff',
    'version': (0, 0),
    "blender": (2, 6, 3),
    'location': 'View3D > Add > Mesh',
    'description': 'Makes Caves using metaballs converted to mesh',
    'warning': 'Murrently WIP',  # used for warning icon and text in addons panel
    'category': 'Add Mesh'}

'''

import bpy
import random

from bpy.props import IntProperty, FloatProperty, BoolProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add

def addCave(self, context):
    print ("regen Cave")


    oldLoc = [0.0, 0.0, 0.0]
    oldScale = [self.chaos, self.chaos, self.chaos]
    random.seed(self.random_seed)

    print ("generating initial metaball")

    bpy.ops.object.metaball_add(type='BALL', view_align=False, enter_editmode=True, location=(0.0, 0.0, 0.0))
    def randLoc():
        rand = (random.random()-0.5) * 5
        if rand > 1:
            rand = 1
        if rand < -1:
            rand = -1
        return rand

    def randScale():
        rand = (random.random()*2)+0.2
        return rand

    def randType():
        types = ['BALL', 'ELLIPSOID', 'CAPSULE', 'CUBE']
        rand = random.choice(types)
        return rand

    def addRandLights(Prob, oldLoc,passedName,passedScene):
        print("user wants lights")
        if random.random() < Prob:
            print("create a light")
            la_lamp = bpy.data.lamps.new("la_" + passedName,'POINT')
            la_lamp.energy = 0.1
            la_lamp.distance = 15
            ob_light = bpy.data.objects.new(passedName,la_lamp)
            ob_light.location = oldLoc
            passedScene.objects.link(ob_light)
            '''
            bpy.ops.object.editmode_toggle()
            bpy.ops.object.lamp_add(type='POINT', view_align=False, location=oldLoc)
            #cave = bpy.data.objects['Mball.001']
            print (bpy.data.objects)
            #bpy.context.scene.objects.active = cave
            #bpy.ops.object.editmode_toggle()
            '''            

    def generateNew(oldLoc, oldScale, run,passedScene):
        newLoc = randLoc()*oldScale[0]+oldLoc[0], randLoc()*oldScale[1]+oldLoc[1], randLoc()*oldScale[2]+oldLoc[2]
        
        ball = bpy.ops.object.metaball_add(type=randType(), location=(newLoc))
        if self.lights == True:
            light_name = "cave_lamp_" + str(run)
            addRandLights(self.lightProb, oldLoc,light_name,passedScene)
        #if random.random() > 0.9:
        #    createLamp(newLoc, run)
        #mball = bpy.context.visible_objects[run]
        #metaball = mball.data

        #newScale = [randScale(), randScale(), randScale()]
        #mball.scale[0] = newScale[0]
        #mball.scale[1] = newScale[1]
        #mball.scale[2] = newScale[2]
        return newLoc, oldScale

    mball = bpy.context.selected_objects[0]
    metaball = mball.data
    metaball.resolution = self.res
    metaball.render_resolution = self.res
    metaball.update_method = 'NEVER' 

    run = 0
    while run < self.iterations+1:
        print ("adding mball "+str(run))
        oldLoc, oldScale= generateNew(oldLoc, oldScale, run,context.scene)
        run += 1

    
    metaball.update_method = 'FAST' 

    bpy.ops.object.editmode_toggle()

    if self.mesh == True:   
        bpy.ops.object.convert(target='MESH')
        bpy.ops.object.editmode_toggle()
        bpy.ops.mesh.vertices_smooth(repeat=2)
        bpy.ops.mesh.subdivide(number_cuts=1, fractal=5, seed=0)
        bpy.ops.mesh.vertices_smooth(repeat=1)
        bpy.ops.mesh.flip_normals()
        bpy.ops.object.editmode_toggle()


class caveGen(bpy.types.Operator, AddObjectHelper):
    """Add a Mesh Object"""
    bl_idname = "mesh.primitive_cave_gen"
    bl_label = "Cave"
    bl_options = {'REGISTER', 'UNDO'}

    iterations = IntProperty(name="Iterations", default=15,
                                min=2, max=10000,
                                description="Sets how many metaballs to use in the cave")

    chaos = FloatProperty(name="Chaos", default=1.0,
                                min=0.1, max=2,
                                description="Sets the scaling of distance between metaballs")

    res = FloatProperty(name="Resolution", default=0.8,
                                min=0.1, max=2.0,
                                description="Changes the resolution of the cave")

    mesh = BoolProperty(name="Convert to mesh", default=True, description="Converts to mesh and does some subdivide/fractal functions")
    lights = BoolProperty(name="Lights", default=False, description="Adds Lights in the passage")
    lightProb = FloatProperty(name="Light Probability", default=0.1, min=0.001, max=1.0, description="Chance of a light being placed at any given point")
    random_seed = IntProperty(name="Random Seed", description="Set the random seed for this cave object", default = 101, min = -420, max = 420)


    def execute(self, context):
        addCave(self, context)
        return {'FINISHED'}


def menu_func(self, context):
    self.layout.operator(caveGen.bl_idname, text="Metaball Cave", icon="PLUGIN")


def register():
    bpy.utils.register_class(caveGen)
    bpy.types.INFO_MT_mesh_add.append(menu_func)


def unregister():
    bpy.utils.unregister_class(caveGen)
    bpy.types.INFO_MT_mesh_add.remove(menu_func)

if __name__ == "__main__":
    register()

Attachments


can someone show a render?

@Atom
Awesome, I shall be dissecting the changes in there.
Much better idea of using the seeds.

@blend_B
Here’s a cave for you, made in 2 minutes, 1 minute was baking AO (it was for a game engine thing).



(Sorry for the mouse pointer and horrible UV unwrapping)

Hi, thanks for this script.

I made a change based on Atom’s update. I wanted to make flatter caves for game usage so I split the ‘Chaos’ into 3 parts - this let me set the Z to 0.1 and get something like I wanted. I’m not 100% happy with it because I couldn’t get FloatVectorProperty to work, so I had to duplicate it 3 times.

'''
bl_info = {
    'name': 'CaveGen',
    'author': 'sdfgeoff',
    'version': (0, 0),
    "blender": (2, 6, 3),
    'location': 'View3D > Add > Mesh',
    'description': 'Makes Caves using metaballs converted to mesh',
    'warning': 'Murrently WIP',  # used for warning icon and text in addons panel
    'category': 'Add Mesh'}

'''

import bpy
import random

from bpy.props import IntProperty, FloatProperty, BoolProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add

def addCave(self, context):
    print ("regen Cave")


    oldLoc = [0.0, 0.0, 0.0]
    oldScale = [self.chaosx, self.chaosy, self.chaosz]
    random.seed(self.random_seed)

    print ("generating initial metaball")

    bpy.ops.object.metaball_add(type='BALL', view_align=False, enter_editmode=True, location=(0.0, 0.0, 0.0))
    def randLoc():
        rand = (random.random()-0.5) * 5
        if rand > 1:
            rand = 1
        if rand < -1:
            rand = -1
        return rand

    def randScale():
        rand = (random.random()*2)+0.2
        return rand

    def randType():
        types = ['BALL', 'ELLIPSOID', 'CAPSULE', 'CUBE']
        rand = random.choice(types)
        return rand

    def addRandLights(Prob, oldLoc,passedName,passedScene):
        print("user wants lights")
        if random.random() < Prob:
            print("create a light")
            la_lamp = bpy.data.lamps.new("la_" + passedName,'POINT')
            la_lamp.energy = 0.1
            la_lamp.distance = 15
            ob_light = bpy.data.objects.new(passedName,la_lamp)
            ob_light.location = oldLoc
            passedScene.objects.link(ob_light)
            '''
            bpy.ops.object.editmode_toggle()
            bpy.ops.object.lamp_add(type='POINT', view_align=False, location=oldLoc)
            #cave = bpy.data.objects['Mball.001']
            print (bpy.data.objects)
            #bpy.context.scene.objects.active = cave
            #bpy.ops.object.editmode_toggle()
            '''            

    def generateNew(oldLoc, oldScale, run,passedScene):
        newLoc = randLoc()*oldScale[0]+oldLoc[0], randLoc()*oldScale[1]+oldLoc[1], randLoc()*oldScale[2]+oldLoc[2]
        
        ball = bpy.ops.object.metaball_add(type=randType(), location=(newLoc))
        if self.lights == True:
            light_name = "cave_lamp_" + str(run)
            addRandLights(self.lightProb, oldLoc,light_name,passedScene)
        #if random.random() > 0.9:
        #    createLamp(newLoc, run)
        #mball = bpy.context.visible_objects[run]
        #metaball = mball.data

        #newScale = [randScale(), randScale(), randScale()]
        #mball.scale[0] = newScale[0]
        #mball.scale[1] = newScale[1]
        #mball.scale[2] = newScale[2]
        return newLoc, oldScale

    mball = bpy.context.selected_objects[0]
    metaball = mball.data
    metaball.resolution = self.res
    metaball.render_resolution = self.res
    metaball.update_method = 'NEVER' 

    run = 0
    while run < self.iterations+1:
        print ("adding mball "+str(run))
        oldLoc, oldScale= generateNew(oldLoc, oldScale, run,context.scene)
        run += 1

    
    metaball.update_method = 'FAST' 

    bpy.ops.object.editmode_toggle()

    if self.mesh == True:   
        bpy.ops.object.convert(target='MESH')
        bpy.ops.object.editmode_toggle()
        bpy.ops.mesh.vertices_smooth(repeat=2)
        bpy.ops.mesh.subdivide(number_cuts=1, fractal=5, seed=0)
        bpy.ops.mesh.vertices_smooth(repeat=1)
        bpy.ops.mesh.flip_normals()
        bpy.ops.object.editmode_toggle()


class caveGen(bpy.types.Operator, AddObjectHelper):
    """Add a Mesh Object"""
    bl_idname = "mesh.primitive_cave_gen"
    bl_label = "Cave Generator"
    bl_options = {'REGISTER', 'UNDO'}

    iterations = IntProperty(name="Iterations", default=15,
                                min=2, max=10000,
                                description="Sets how many metaballs to use in the cave")

    chaosx = FloatProperty(name="Chaos X", default=1.0,
                                min=0.1, max=2,
                                description="Sets the scaling of X distance between metaballs")

    chaosy = FloatProperty(name="Chaos Y", default=1.0,
                                min=0.1, max=2,
                                description="Sets the scaling of Y distance between metaballs")

    chaosz = FloatProperty(name="Chaos Z", default=1.0,
                                min=0.1, max=2,
                                description="Sets the scaling of Z distance between metaballs")

    res = FloatProperty(name="Resolution", default=0.8,
                                min=0.1, max=2.0,
                                description="Changes the resolution of the cave")

    mesh = BoolProperty(name="Convert to mesh", default=True, description="Converts to mesh and does some subdivide/fractal functions")
    lights = BoolProperty(name="Lights", default=False, description="Adds Lights in the passage")
    lightProb = FloatProperty(name="Light Probability", default=0.1, min=0.001, max=1.0, description="Chance of a light being placed at any given point")
    random_seed = IntProperty(name="Random Seed", description="Set the random seed for this cave object", default = 101, min = -420, max = 420)


    def execute(self, context):
        addCave(self, context)
        return {'FINISHED'}


def menu_func(self, context):
    self.layout.operator(caveGen.bl_idname, text="Metaball Cave", icon="PLUGIN")


def register():
    bpy.utils.register_class(caveGen)
    bpy.types.INFO_MT_mesh_add.append(menu_func)


def unregister():
    bpy.utils.unregister_class(caveGen)
    bpy.types.INFO_MT_mesh_add.remove(menu_func)

if __name__ == "__main__":
    register()

Gar! This just keeps getting better and better! :slight_smile:

One thing I’ve found works great is using the remesh modifier on the result of this script. It cleans up the metaball mesh a lot, drops the face count significantly and generally makes it much more suitable as a base mesh for game engine usage.

Ive tried installing this script but I am not finding it under 3d>>>add>>>mesh anywhere?

quote
Ive tried installing this script but I am not finding it under 3d>>>add>>>mesh anywhere?
unquote
It’s under ‘add mesh’ 8-]

This has 1000 iterations, I think when you add Uv (cubic) you lose the parameters.

Attachments


@CaverX: Before you install script in a standard way from User Preferences, delete 2 lines having ‘’’ (before and after bl_info block).
If you run script from Text Editor you can use Add - Mesh menu.

Thanks to the author of the script - fun to play and looks like shapes generated could be used not only for the caves.

Hello,
I did what you told us to but I still cant install it. Please help
image

Due to how copy works on this page Python code gets messed up: symbols “greater than” and “less than” get their web representation. I wont even try to write example here. See Google, Wiki on this.

Get the working one on pastebin.

Hey guys any one have a working copy of these. the past bin link has been removed and i keep on getting a error code

  File "\cave.txt", line 34
    if rand > 1:
               ^
SyntaxError: invalid syntax

location: <unknown location>:-1
Python script failed, check the message in the system console




Anywhere you see &gt; replace it with >
Same goes for &lt; replace it with <

Thank you for the feedback. I will try it now


thanks for help now im getting this error

Hi, I have update the code to work on Blender 2.82, here is the addon:

CaveGen.py.zip (2.5 KB)

Testing on 2.82.6 & macOS and it works perfectly.

1 Like

I got it to work on Blender 2.79, I tred using the sliders in the toolbar panel to adjust the global z position but the mball did not move am I doing something wrongly? Also the mesh is perfectly smooth, Why is it not craggy?