1
$\begingroup$

Below is my current code. I get no errors, but when I run the code nothing happens. I'm not even sure everything is right, or when it works will it work how I want. Any feedback or suggestions are helpful. Overall the end result should be a nice looking 3D Mandelbulb with decently high detail, hundreds of thousands of faces are expected if not more.

Code:


import bpy
import numpy as np
import math

def mandelbulb(x, y, z, iterations):
    c = x + y*1j + z*1j
    z = c
    r = 0
    for i in range(iterations):
        r2 = x*x + y*y + z*z
        if r2 > 2:
            return i/iterations
        theta = math.atan2(math.sqrt(x*x + y*y), z)
        phi = math.atan2(y, x)
        r = r2**(1/8)
        x = r * math.sin(theta/8) * math.cos(phi/8) + c.real
        y = r * math.sin(theta/8) * math.sin(phi/8) + c.imag
        z = r * math.cos(theta/8)
    return 0

def generate_mesh(size, iterations):
    vertices = []
    faces = []
    for x in np.linspace(-2, 2, size):
        for y in np.linspace(-2, 2, size):
            for z in np.linspace(-2, 2, size):
                value = mandelbulb(x, y, z, iterations)
                if value > 0:
                    vertices.append((x, y, z))
    return vertices, faces

def create_mesh_object(vertices, faces, name):
    mesh = bpy.data.meshes.new(name)
    mesh.from_pydata(vertices, [], faces)
    mesh.update()
    object = bpy.data.objects.new(name, mesh)
    bpy.context.collection.objects.link(object)

def execute(size, iterations):
    vertices, faces = generate_mesh(size, iterations)
    create_mesh_object(vertices, faces, "Mandelbulb")

class MandelbulbOperator(bpy.types.Operator):
    bl_idname = "object.mandelbulb_operator"
    bl_label = "Mandelbulb Operator"
    bl_options = {'REGISTER', 'UNDO'}

    size = bpy.props.IntProperty(
        name="Size",
        default=32,
        min=1,
        max=256,
        step=1,
        precision=0
    )
    iterations = bpy.props.IntProperty(
        name="Iterations",
        default=64,
        min=1,
        max=512,
        step=1,
        precision=0
    )

    def execute(self, context):
        execute(self.size, self.iterations)
        return {'FINISHED'}

    def draw(self, context):
        layout = self.layout
        layout.label(text="Create a 3D Mandelbulb")
        layout.prop(self, "size")
        layout.prop(self, "iterations")
        
def register():
    bpy.utils.register_class(MandelbulbOperator)

def unregister():
    bpy.utils.unregister_class(MandelbulbOperator)

if __name__ == "__main__":
    register()

```
$\endgroup$
3
  • 1
    $\begingroup$ Just a hint: The script registers an operator. You need to run the operator so that the script actually does something. Run the script, then press F3, and search for "Mandelbulb Operator". The execute() method will then run. But there is a problem with the Size property and there will be an error :-( Need to have a closer look to solve it. $\endgroup$
    – Blunder
    Jan 30, 2023 at 4:12
  • $\begingroup$ @Blunder I just tried the F3 and it seems like it can't find "Mandelbulb Operator" or any similar searches like "MandelbulbOperator" $\endgroup$
    – MinerDy
    Jan 30, 2023 at 4:18
  • 1
    $\begingroup$ Replace = with : in the operator properties definition (not those beiginng with bl_ + precision is an invalid argument for IntProperty, remove it in both size and iterations $\endgroup$
    – Gorgious
    Jan 30, 2023 at 7:32

1 Answer 1

1
$\begingroup$

There are two little mistakes in your script as Gorgious wrote in the comments:

  1. the operator properties "size" and "iterations" must be defined by ":" not by a "="
  2. bpy.props.IntProperty has no precision parameter.
import bpy
import numpy as np
import math

def mandelbulb(x, y, z, iterations):
    c = x + y*1j + z*1j
    z = c
    r = 0
    for i in range(iterations):
        r2 = x*x + y*y + z*z
        if r2 > 2:
            return i/iterations
        theta = math.atan2(math.sqrt(x*x + y*y), z)
        phi = math.atan2(y, x)
        r = r2**(1/8)
        x = r * math.sin(theta/8) * math.cos(phi/8) + c.real
        y = r * math.sin(theta/8) * math.sin(phi/8) + c.imag
        z = r * math.cos(theta/8)
    return 0

def generate_mesh(size, iterations):
    vertices = []
    faces = []
    for x in np.linspace(-2, 2, size):
        for y in np.linspace(-2, 2, size):
            for z in np.linspace(-2, 2, size):
                value = mandelbulb(x, y, z, iterations)
                if value > 0:
                    vertices.append((x, y, z))
    return vertices, faces

def create_mesh_object(vertices, faces, name):
    mesh = bpy.data.meshes.new(name)
    mesh.from_pydata(vertices, [], faces)
    mesh.update()
    object = bpy.data.objects.new(name, mesh)
    bpy.context.collection.objects.link(object)

def execute(size, iterations):
    vertices, faces = generate_mesh(size, iterations)
    create_mesh_object(vertices, faces, "Mandelbulb")

class MandelbulbOperator(bpy.types.Operator):
    bl_idname = "object.mandelbulb_operator"
    bl_label = "Mandelbulb Operator"
    bl_options = {'REGISTER', 'UNDO'}

    size: bpy.props.IntProperty(
        name="Size",
        default=32,
        min=1,
        max=256,
        step=1
    )
    iterations: bpy.props.IntProperty(
        name="Iterations",
        default=64,
        min=1,
        max=512,
        step=1
    )

    def execute(self, context):
        execute(self.size, self.iterations)
        return {'FINISHED'}

    def draw(self, context):
        layout = self.layout
        layout.label(text="Create a 3D Mandelbulb")
        layout.prop(self, "size")
        layout.prop(self, "iterations")
        
def register():
    bpy.utils.register_class(MandelbulbOperator)

def unregister():
    bpy.utils.unregister_class(MandelbulbOperator)

if __name__ == "__main__":
    register()

After you run the script, no error should be displayed in the System Console window and nothing will happen because you only have registered the operator Mandelbulb Operator.

Use the main menu Edit > Menu Search... F3 or Edit > Operator Search... to find your operator. Both should work.

search operator

Menu search:
menu search

Operator search:
operator search

Select it and hit Enter to execute it.

With the Adjust Last Operator popup at the bottom left of the screen you can change the settings Size and Iterations of your Mandelbulb Operator.

screenshot of the Mandelbulb

If for some reason you cannot see the popup make sure the option View > [X] Adjust Last Operation is ticked in the viewport's menu, or press F9 to bring it back.

$\endgroup$
3
  • $\begingroup$ This is exactly the kind of response I was looking for, thank you. It now runs without errors and i'm able to repeat your result. The problem now is of course the result looks nothing like the true Mandelbulb I'm going for. It's possible what appears is a certain stage of Mandelbulb, but it's not as detailed or cool as I was hoping. Maybe a miscalculation. I'd love to hear if you caught anything or have any suggestions for how to continue. P.S. The "Iterations" operation that you can change doesn't seem to actually do anything. By googling "Mandelbulb" you can see the desired result. $\endgroup$
    – MinerDy
    Jan 31, 2023 at 5:18
  • $\begingroup$ Yes, it looks a bit poor. Have you tried Geometry Nodes instead of Python? There is a nice tutorial. And in the YT comments, there is an (improved version)[youtu.be/qir9LcmYywg] including a blend file (link in one of the YT comment). I recommend decreasing the render resolution in the GN modifier because the value of 1024 requires up to 20 GB RAM. Also, decrease the 200% upscale in the render output settings for the first test and check the missing HDRI in the world settings. $\endgroup$
    – Blunder
    Feb 3, 2023 at 11:16
  • $\begingroup$ Here is an example of the output: i.stack.imgur.com/pU7eS.jpg $\endgroup$
    – Blunder
    Feb 3, 2023 at 11:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .