mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-14 17:50:47 +01:00
Closes the remaining bake-off cells. The phase's real result was not a route
score but a MISSING GATE.
All twelve SDXL icons measured 100% opaque coverage -- square tiles with no
sprite silhouette -- and passed every gate in the harness. Gate-pass was
measuring pipeline hygiene, not usability. MARGIN_TOO_SMALL inverts the table
from a meaningless 12/12/12 tie to:
diffusion 0/12 (every output full-bleed)
vector 10/12
procedural 11/12
The two non-diffusion failures were true positives in MY shapes, not gate
error: the procedural sword pommel ran off the bottom edge and the vector meat
bone clipped the top. Both fixed; both routes back to 12/12.
Diffusion recovers to 12/12 with flood-fill background removal, but visual
inspection shows the fill eats into the art (bone reduced to a fragment, sword
nearly erased). Reported as inadequate rather than shipped -- proper matting is
needed before route (b) is a usable pipeline.
Blender exits 0 even when its embedded script raises: BLENDER_EEVEE_NEXT does
not exist in 5.2, and the failure produced a clean exit and no file. render()
now asserts the output exists, or the route would report phantom successes.
Model gating verified at execution time, never recalled: SDXL base and
nerijs/pixel-art-xl are ungated, FLUX.1-dev is gated=auto, and
runwayml/stable-diffusion-v1-5 now 307-redirects.
The finishing stage now generalises across three continuous-tone sources
(SVG raster, Blender render, SDXL): each is 0/N raw and N/N finished.
Verified: 114 tests, 100% branch coverage on 13 modules, zero suppressions,
all hooks pass. Note: a local pytest run masked a missing branch that the
pre-commit hook caught -- the hook's run is authoritative.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9rQ12yZ7sJxiPQTCS5dUF
147 lines
4.5 KiB
Python
147 lines
4.5 KiB
Python
"""Route (d) 3D: a Blender scene script, run headless.
|
|
|
|
This module is *emitted* and executed by ``blender --background --python``, so
|
|
it must not import anything from this package -- the Blender interpreter has
|
|
no path to it. It is kept here as a data file rather than a library so the
|
|
repo's linters still see it as Python.
|
|
|
|
The 3D cell exists to answer one question: does a rendered mesh, put through
|
|
the same finishing stage as the vector route, clear the pixel gates? The
|
|
render is continuous-tone by nature, so the expectation is the same as every
|
|
other continuous route -- it needs the finisher.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Final
|
|
|
|
# Rendered square edge length before downscaling. Kept small: the finisher
|
|
# reduces to 32px anyway, and Cycles time scales with pixel count.
|
|
RENDER_SIZE: Final = 256
|
|
|
|
# Executed inside Blender's own Python. Written as a template rather than
|
|
# imported, because Blender cannot see this package.
|
|
SCENE_TEMPLATE: Final = """
|
|
import sys
|
|
import bpy
|
|
import mathutils
|
|
|
|
SUBJECT = sys.argv[-2]
|
|
OUT = sys.argv[-1]
|
|
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
scene = bpy.context.scene
|
|
scene.render.engine = "BLENDER_EEVEE"
|
|
scene.render.resolution_x = {size}
|
|
scene.render.resolution_y = {size}
|
|
scene.render.film_transparent = True
|
|
scene.render.image_settings.file_format = "PNG"
|
|
scene.render.image_settings.color_mode = "RGBA"
|
|
scene.render.filepath = OUT
|
|
|
|
|
|
def material(name, rgba):
|
|
mat = bpy.data.materials.new(name)
|
|
mat.use_nodes = True
|
|
bsdf = mat.node_tree.nodes["Principled BSDF"]
|
|
bsdf.inputs["Base Color"].default_value = rgba
|
|
bsdf.inputs["Roughness"].default_value = 0.45
|
|
return mat
|
|
|
|
|
|
GOLD = (0.84, 0.64, 0.24, 1.0)
|
|
CYAN = (0.36, 0.69, 0.75, 1.0)
|
|
RED = (0.69, 0.24, 0.24, 1.0)
|
|
BONE = (0.78, 0.73, 0.67, 1.0)
|
|
|
|
|
|
def add(obj, rgba):
|
|
obj.data.materials.append(material(obj.name, rgba))
|
|
return obj
|
|
|
|
|
|
def build_coin():
|
|
bpy.ops.mesh.primitive_cylinder_add(radius=1.1, depth=0.16, vertices=48)
|
|
add(bpy.context.object, GOLD)
|
|
bpy.ops.mesh.primitive_torus_add(major_radius=1.0, minor_radius=0.08)
|
|
add(bpy.context.object, GOLD)
|
|
|
|
|
|
def build_gem():
|
|
bpy.ops.mesh.primitive_cone_add(radius1=1.0, depth=1.2, vertices=8)
|
|
add(bpy.context.object, CYAN)
|
|
bpy.ops.mesh.primitive_cone_add(radius1=1.0, depth=0.7, vertices=8)
|
|
bpy.context.object.rotation_euler = (3.14159, 0, 0)
|
|
bpy.context.object.location = (0, 0, 0.95)
|
|
add(bpy.context.object, CYAN)
|
|
|
|
|
|
def build_ring():
|
|
bpy.ops.mesh.primitive_torus_add(major_radius=0.85, minor_radius=0.16)
|
|
add(bpy.context.object, GOLD)
|
|
bpy.ops.mesh.primitive_ico_sphere_add(radius=0.34, subdivisions=1)
|
|
bpy.context.object.location = (0, 0, 0.95)
|
|
add(bpy.context.object, CYAN)
|
|
|
|
|
|
def build_bone():
|
|
bpy.ops.mesh.primitive_cylinder_add(radius=0.22, depth=1.8, vertices=16)
|
|
add(bpy.context.object, BONE)
|
|
for z in (0.9, -0.9):
|
|
for x in (-0.22, 0.22):
|
|
bpy.ops.mesh.primitive_uv_sphere_add(radius=0.34, segments=16)
|
|
bpy.context.object.location = (x, 0, z)
|
|
add(bpy.context.object, BONE)
|
|
|
|
|
|
def build_bomb():
|
|
bpy.ops.mesh.primitive_uv_sphere_add(radius=1.0, segments=32)
|
|
add(bpy.context.object, (0.16, 0.15, 0.17, 1.0))
|
|
bpy.ops.mesh.primitive_cylinder_add(radius=0.16, depth=0.6, vertices=12)
|
|
bpy.context.object.location = (0.25, 0, 1.15)
|
|
add(bpy.context.object, RED)
|
|
|
|
|
|
BUILDERS = {{
|
|
"coin": build_coin,
|
|
"gem": build_gem,
|
|
"ring": build_ring,
|
|
"bone": build_bone,
|
|
"bomb": build_bomb,
|
|
}}
|
|
|
|
BUILDERS[SUBJECT]()
|
|
|
|
bpy.ops.object.camera_add(location=(0, -4.2, 2.6))
|
|
camera = bpy.context.object
|
|
camera.rotation_euler = mathutils.Euler((1.02, 0, 0))
|
|
camera.data.type = "ORTHO"
|
|
camera.data.ortho_scale = 3.0
|
|
scene.camera = camera
|
|
|
|
bpy.ops.object.light_add(type="SUN", location=(2.5, -3.0, 5.0))
|
|
bpy.context.object.data.energy = 4.0
|
|
bpy.ops.object.light_add(type="AREA", location=(-3.0, -2.0, 2.0))
|
|
bpy.context.object.data.energy = 90.0
|
|
|
|
bpy.ops.render.render(write_still=True)
|
|
"""
|
|
|
|
|
|
def scene_script(size: int = RENDER_SIZE) -> str:
|
|
"""Return the Blender scene script for a given render size.
|
|
|
|
Args:
|
|
size: Square render edge length in pixels.
|
|
|
|
Returns:
|
|
Python source to hand to ``blender --python``.
|
|
"""
|
|
return SCENE_TEMPLATE.format(size=size)
|
|
|
|
|
|
# Subjects with a 3D build. Deliberately a subset: a mesh for "scroll" or
|
|
# "meat" would be modelling work, not a route comparison, and the cell's
|
|
# purpose is to test whether render->finish clears the gates at all.
|
|
SUBJECTS_3D: Final = ("coin", "gem", "ring", "bone", "bomb")
|