deepseek-v4.1-flash · round 8 of 8 · seed 3$0.02MP4GLBscene.py
scene.py · 216 lines
"""Café table scene built only from primitive meshes.

espresso cup + saucer, spoon, sugar cubes, croissant.
"""
import bpy
import math
import mathutils

# ----------------------------------------------------------------------------
# helpers
# ----------------------------------------------------------------------------
for obj in list(bpy.data.objects):
    bpy.data.objects.remove(obj, do_unlink=True)


def mat(name, color, rough=0.5, metal=0.0, spec=0.5):
    m = bpy.data.materials.new(name)
    m.use_nodes = True
    b = m.node_tree.nodes["Principled BSDF"]
    b.inputs["Base Color"].default_value = (*color, 1.0)
    b.inputs["Roughness"].default_value = rough
    b.inputs["Metallic"].default_value = metal
    if "Specular" in b.inputs:
        b.inputs["Specular"].default_value = spec
    return m


def setup(obj, material, name, smooth=True, angle=math.radians(40)):
    obj.name = name
    if material:
        obj.data.materials.append(material)
    if smooth:
        for p in obj.data.polygons:
            p.use_smooth = True
        if hasattr(obj.data, "use_auto_smooth"):
            obj.data.use_auto_smooth = True
            obj.data.auto_smooth_angle = angle
    return obj


def cube(name, loc, scale, material, rot=(0, 0, 0)):
    bpy.ops.mesh.primitive_cube_add(size=1, location=loc, rotation=rot)
    o = bpy.context.object
    o.scale = scale
    return setup(o, material, name, smooth=False)


def cyl(name, loc, radius, depth, material, rot=(0, 0, 0), verts=64):
    bpy.ops.mesh.primitive_cylinder_add(
        vertices=verts, radius=radius, depth=depth, location=loc, rotation=rot
    )
    return setup(bpy.context.object, material, name)


def cone(name, loc, r1, r2, depth, material, rot=(0, 0, 0), verts=64,
         caps='NGON'):
    bpy.ops.mesh.primitive_cone_add(
        vertices=verts, radius1=r1, radius2=r2, depth=depth,
        location=loc, rotation=rot, end_fill_type=caps,
    )
    return setup(bpy.context.object, material, name)


def sphere(name, loc, scale, material, rot=(0, 0, 0), segs=32, rings=16):
    bpy.ops.mesh.primitive_uv_sphere_add(
        segments=segs, ring_count=rings, radius=1.0, location=loc, rotation=rot
    )
    o = bpy.context.object
    o.scale = scale
    return setup(o, material, name)


def torus(name, loc, major, minor, material, rot=(0, 0, 0), mseg=48, miseg=16):
    bpy.ops.mesh.primitive_torus_add(
        major_radius=major, minor_radius=minor, location=loc, rotation=rot,
        major_segments=mseg, minor_segments=miseg,
    )
    return setup(bpy.context.object, material, name)


# ----------------------------------------------------------------------------
# materials
# ----------------------------------------------------------------------------
M_TABLE = mat("Table", (0.30, 0.17, 0.09), rough=0.45)
M_PORCELAIN = mat("Porcelain", (0.95, 0.94, 0.90), rough=0.18, spec=0.7)
M_COFFEE = mat("Coffee", (0.045, 0.018, 0.008), rough=0.12, spec=0.9)
M_CREMA = mat("Crema", (0.30, 0.165, 0.075), rough=0.55)
M_METAL = mat("Metal", (0.85, 0.86, 0.88), rough=0.30, metal=1.0)
M_SUGAR = mat("Sugar", (0.97, 0.96, 0.93), rough=0.55)
M_PASTRY = mat("Pastry", (0.52, 0.27, 0.085), rough=0.65)

# ----------------------------------------------------------------------------
# table
# ----------------------------------------------------------------------------
cube("Table", (0, 0, -0.02), (0.9, 0.9, 0.04), M_TABLE)

# ----------------------------------------------------------------------------
# saucer: wide dish + rim
# ----------------------------------------------------------------------------
SAUCER_TOP = 0.011
cyl("SaucerBase", (0, 0, 0.0055), 0.092, 0.011, M_PORCELAIN)
torus("SaucerRim", (0, 0, SAUCER_TOP - 0.001), 0.085, 0.0055, M_PORCELAIN)
torus("SaucerWellRing", (0, 0, SAUCER_TOP - 0.0005), 0.048, 0.0035, M_PORCELAIN)

# ----------------------------------------------------------------------------
# espresso cup
# ----------------------------------------------------------------------------
CUP_BOT = SAUCER_TOP - 0.001
CUP_H = 0.052
CUP_TOP = CUP_BOT + CUP_H
CUP_CZ = CUP_BOT + CUP_H / 2
cone("CupWall", (0, 0, CUP_CZ), 0.026, 0.038, CUP_H, M_PORCELAIN, caps='NOTHING')
torus("CupRim", (0, 0, CUP_TOP), 0.038, 0.0032, M_PORCELAIN)
cyl("CupFoot", (0, 0, CUP_BOT + 0.002), 0.021, 0.006, M_PORCELAIN)
cyl("CupBase", (0, 0, CUP_BOT + 0.006), 0.026, 0.004, M_PORCELAIN)

# coffee surface just below the rim, with a lighter crema disc
cyl("Coffee", (0, 0, CUP_TOP - 0.012), 0.0335, 0.008, M_COFFEE)
cyl("Crema", (0, 0, CUP_TOP - 0.0085), 0.0255, 0.0025, M_CREMA)

# handle
torus("CupHandle", (0.053, 0.0, CUP_CZ + 0.002), 0.021, 0.0055,
      M_PORCELAIN, rot=(math.pi / 2, 0, 0))

# ----------------------------------------------------------------------------
# spoon on the table to the right of the saucer (bowl + handle)
# ----------------------------------------------------------------------------
SP_Y = -0.052
sphere("SpoonBowl", (0.132, SP_Y, 0.0075), (0.024, 0.015, 0.0048), M_METAL,
       rot=(0, 0, math.radians(4)))
cube("SpoonHandle", (0.196, SP_Y - 0.003, 0.0085),
     (0.082, 0.009, 0.0038), M_METAL, rot=(0, 0, math.radians(-4)))

# ----------------------------------------------------------------------------
# sugar cubes
# ----------------------------------------------------------------------------
for i, (x, y, z) in enumerate([
    (-0.128, 0.050, 0.010),
    (-0.150, 0.050, 0.010),
    (-0.128, 0.028, 0.010),
    (-0.150, 0.028, 0.010),
    (-0.139, 0.039, 0.030),
]):
    cube("Sugar%d" % i, (x, y, z), (0.020, 0.020, 0.020), M_SUGAR,
         rot=(0, 0, math.radians((i % 3 - 1) * 4)))

# ----------------------------------------------------------------------------
# croissant: spheres swept along a tapering crescent arc
# ----------------------------------------------------------------------------
CROT_C = (-0.030, -0.135)
AMAX = 1.15
N = 11
R = 0.052
rotz = math.radians(25)


def rot2(x, y):
    c, s = math.cos(rotz), math.sin(rotz)
    return (x * c - y * s + CROT_C[0], x * s + y * c + CROT_C[1])


for i in range(N):
    a = -AMAX + 2 * AMAX * i / (N - 1)
    t = abs(a) / AMAX
    px = R * math.sin(a)
    py = R * math.cos(a) - R
    # taper towards the horns, bulge in the middle
    r = 0.024 * (1.0 - 0.72 * t ** 1.6) + 0.002
    x, y = rot2(px, py)
    sphere("Croissant%d" % i, (x, y, r * 0.72),
           (r, r, r * 0.72), M_PASTRY, segs=24, rings=12)

# ----------------------------------------------------------------------------
# camera + lights
# ----------------------------------------------------------------------------
cam_data = bpy.data.cameras.new("Camera")
cam_data.lens = 60
cam = bpy.data.objects.new("Camera", cam_data)
bpy.context.scene.collection.objects.link(cam)
cam.location = mathutils.Vector((0.20, -0.52, 0.40))
look = mathutils.Vector((0.0, -0.03, 0.015))
cam.rotation_euler = (look - cam.location).to_track_quat('-Z', 'Y').to_euler()
bpy.context.scene.camera = cam


def area(name, loc, energy, size, target=(0, 0, 0)):
    ld = bpy.data.lights.new(name, 'AREA')
    ld.energy = energy
    ld.size = size
    o = bpy.data.objects.new(name, ld)
    bpy.context.scene.collection.objects.link(o)
    o.location = loc
    o.rotation_euler = (mathutils.Vector(target) - mathutils.Vector(loc)) \
        .to_track_quat('-Z', 'Y').to_euler()
    return o


area("Key", (-0.32, -0.38, 0.55), 8, 0.5, (0, 0, 0.03))
area("Fill", (0.45, -0.12, 0.35), 2.5, 0.6, (0, 0, 0.03))
area("Rim", (0.10, 0.45, 0.40), 5, 0.6, (0, 0, 0.03))

world = bpy.context.scene.world
if world is None:
    world = bpy.data.worlds.new("World")
    bpy.context.scene.world = world
world.use_nodes = True
world.node_tree.nodes["Background"].inputs[0].default_value = (0.07, 0.07, 0.09, 1)
world.node_tree.nodes["Background"].inputs[1].default_value = 0.6
scn = bpy.context.scene
scn.view_settings.exposure = -0.7

# render settings (harness overrides engine/res but keep sensible values)
scn = bpy.context.scene
scn.render.film_transparent = False
scn.view_settings.look = 'Medium High Contrast' if 'Medium High Contrast' in \
    [l.name for l in bpy.types.ColorManagedViewSettings.bl_rna.properties['look'].enum_items] else 'None'
round 1 of 8
Café table from primitives, by DeepSeek 4.1 Flash, the render after round 1
12345678What the model saw after each round. The picture above is the round that was scored, round 8.
gallery/Café table from primitives/DeepSeek 4.1 Flash, run 3

Eight rounds, stopped by the round cap rather than the clock. The best croissant on the bench, next to a spoon that does not read as one.

modeldeepseek/deepseek-v4.1-flashtaskblender-cafe-table@v2date2026-09-21rounds8 of 8 allowedcost$0.0194thinkingmediumtokens470,115 in, 20,483 out, of which 11,594 reasoningtime11 min 9 sfailurephysical-plausibility
All five objectsdecided by looking · Pass if all five can be pointed at in the render as separate objects. One missing, or hidden behind another, fails it.pass
Primitives onlydecided by a number, from the filepass
Spoon reads as a spoondecided by looking · Pass if the spoon is one connected piece with a bowl at one end, wider than the handle, and the handle running from it in a line. Loose parts, or no handle, fail it.fail
Croissant reads as a croissantdecided by looking · Pass if it is a crescent that is thickest in the middle and tapers to both tips, with segments or scoring across it. A smooth curved tube fails it, and so does a segmented one with no taper.pass
Consistent scaledecided by a number, from the filepass

There is no post about this one, since only some results need one, so the picture, the source and the scoring are here instead.

its other runs
the prompt it was given
Write a Blender Python script that builds a café table scene: an espresso cup on a saucer, a spoon, sugar cubes and a croissant. Use only primitive meshes (cube, cylinder, cone, sphere, torus).
Frozen at v2. This model got this and nothing else.
ground truth

The render, and the script that made it: the objects a person would name unprompted, at sizes a person would accept. A 5 cm spoon and an 11 cm croissant are checkable in the source. How it is scored, and this run's files on GitHub.