Skip to content

Commit e178446

Browse files
TMHSDigitalclaude
andcommitted
fix(examples): redesign attribute-domain-shear staging as a paver patio with slate placards
The CORNER/POINT labels were extruded letters lying on the studio floor. They are now slate placards on brass posts in front of each parasol base, the domain name inlaid in cream enamel. Both parasols stand on a render-only terracotta paver patio: a 10 x 5 deck of beveled pavers with grout joints, two tones alternated by a fixed index hash. The patio is passed to the framing gate as stage, like the floor. The parasols, the authoring functions and the checks are unchanged. Evidence (live runs, local): - E:/Blender-Developer-Tools/.scratch/blender-5.2.1-windows-x64/blender.exe (Blender 5.2.1 LTS): check exit 0, point_shear=0.751031 (= closed form), corner_exact. --no-overwrite exits 5 (hub reads palette[0], not the last write). --output exit 0 with framing_ok and aq_ok (materials n=5, edge90 0.065). - E:/Blender-Developer-Tools/.scratch/blender-4.5.11-windows-x64/blender.exe (Blender 4.5.11 LTS): check and --output exit 0, same numbers. - Framing: fill x=0.800 y=0.639; margins l=0.100 r=0.100 b=0.033 t=0.194. - Contact sheet: candidate luma 0.285 / stage 0.219 / warmth +0.074 / sat 0.237, against armature-bend 0.243/0.192/-0.144/0.237, damped-track-aim 0.237/0.218/+0.105/0.178, bmesh-gear 0.237/0.252/+0.092/0.153. - Asset sheet not re-rendered: the parasol model is unchanged, only its staging. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 6cc85f8 commit e178446

7 files changed

Lines changed: 111 additions & 20 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1098,7 +1098,7 @@ evaluation). Break the mirror and the render is literally half a car.
10981098
</tr>
10991099
<tr>
11001100
<td width="46%" valign="middle">
1101-
<a href="examples/attribute-domain-shear/"><img src="examples/attribute-domain-shear/preview.webp" alt="Attribute domain shear: two striped patio parasols side by side — left CORNER-domain with crisp crimson and cream stripes, right naive POINT-domain with the stripes smeared pink along the seams and the front red panel gone white, proving last-write-wins shear at shared vertices" /></a>
1101+
<a href="examples/attribute-domain-shear/"><img src="examples/attribute-domain-shear/preview.webp" alt="Attribute domain shear: two striped patio parasols on a terracotta paver deck behind slate placards — left CORNER-domain with crisp crimson and cream stripes, right naive POINT-domain with the stripes smeared pink along the seams and the front red panel gone white, proving last-write-wins shear at shared vertices" /></a>
11021102
</td>
11031103
<td valign="middle">
11041104

9.47 KB
Loading
19.1 KB
Loading

‎examples/attribute-domain-shear/README.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@ The staging is render-only and does not touch the checked mesh. Each parasol
6565
is eight named parts with five materials: canopy, aluminium ribs and
6666
stretchers, brass runner, finial and tilt knuckle, teak upper and lower pole,
6767
and a cast-iron base. The canopy tilts 24 degrees toward the camera about the
68-
knuckle so the whole top reads. One bold label stands in front of each base.
68+
knuckle so the whole top reads. The pair stands on a terracotta paver patio
69+
(a render-only 10 x 5 deck of beveled pavers with grout joints, two tones
70+
alternated by a fixed index hash, passed to the framing gate as stage like
71+
the floor), and each base has a slate placard on a brass post in front of
72+
it, the domain name inlaid in cream enamel.
6973
The render path runs the framing gate (exit 10) and the asset-quality floors
7074
on one parasol (exit 11).
7175

‎examples/attribute-domain-shear/attribute_domain_shear.py‎

Lines changed: 104 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -432,20 +432,104 @@ def link(ob_name, me, mat, location, rot):
432432
return parts
433433

434434

435-
def floor_letters(sc, text, x, mat):
436-
"""One bold label per parasol: extruded block letters standing on the
437-
floor in front of the base, readable at card size."""
438-
cu = bpy.data.curves.new(f"Label{text.title()}", "FONT")
435+
def _box_mesh(name, dims, bevel=0.0):
436+
me = bpy.data.meshes.new(name)
437+
bm = bmesh.new()
438+
try:
439+
bmesh.ops.create_cube(bm, size=1.0)
440+
for v in bm.verts:
441+
v.co.x *= dims[0]
442+
v.co.y *= dims[1]
443+
v.co.z *= dims[2]
444+
if bevel > 0.0:
445+
bmesh.ops.bevel(bm, geom=list(bm.edges), offset=bevel, segments=2,
446+
profile=0.5, affect="EDGES", clamp_overlap=True)
447+
bm.to_mesh(me)
448+
finally:
449+
bm.free()
450+
return me
451+
452+
453+
def placard(sc, text, x, mats):
454+
"""One sign per parasol: a slate plaque on a brass post in front of the
455+
base, tilted back toward the raised camera, the domain name inlaid in
456+
cream enamel. Returns every part (they all count for the margin gate)."""
457+
parts = []
458+
post = bpy.data.objects.new(f"Sign{text.title()}.Post",
459+
_box_mesh(f"Sign{text.title()}.Post", (0.035, 0.035, 0.24), 0.006))
460+
post.data.materials.append(mats["brass"])
461+
post.location = (x, -1.40, 0.12)
462+
sc.collection.objects.link(post)
463+
parts.append(post)
464+
foot = bpy.data.objects.new(f"Sign{text.title()}.Foot",
465+
_box_mesh(f"Sign{text.title()}.Foot", (0.26, 0.20, 0.03), 0.008))
466+
foot.data.materials.append(mats["iron"])
467+
foot.location = (x, -1.40, 0.015)
468+
sc.collection.objects.link(foot)
469+
parts.append(foot)
470+
root = bpy.data.objects.new(f"Sign{text.title()}", None)
471+
root.location = (x, -1.42, 0.30)
472+
root.rotation_euler = (math.radians(62), 0.0, 0.0)
473+
sc.collection.objects.link(root)
474+
plaque = bpy.data.objects.new(f"Sign{text.title()}.Plaque",
475+
_box_mesh(f"Sign{text.title()}.Plaque", (0.98, 0.30, 0.035), 0.012))
476+
plaque.data.materials.append(mats["slate"])
477+
plaque.parent = root
478+
sc.collection.objects.link(plaque)
479+
parts.append(plaque)
480+
cu = bpy.data.curves.new(f"Sign{text.title()}.Text", "FONT")
439481
cu.body = text
440-
cu.size = 0.29
441-
cu.extrude = 0.03
442-
cu.bevel_depth = 0.004
443-
cu.offset = 0.012 # thicken the stroke: the stock font is thin
482+
cu.size = 0.19
483+
cu.extrude = 0.003
484+
cu.offset = 0.006 # thicken the stroke: the stock font is thin
444485
cu.align_x = "CENTER"
445-
ob = bpy.data.objects.new(f"Label{text.title()}", cu)
446-
ob.location = (x, -1.0, 0.0)
447-
ob.rotation_euler = (math.radians(90), 0.0, 0.0)
448-
ob.data.materials.append(mat)
486+
cu.align_y = "CENTER"
487+
ob = bpy.data.objects.new(f"Sign{text.title()}.Text", cu)
488+
ob.location = (0.0, 0.0, 0.0185)
489+
ob.parent = root
490+
ob.data.materials.append(mats["enamel"])
491+
sc.collection.objects.link(ob)
492+
parts.append(ob)
493+
return parts
494+
495+
496+
PAVER = 0.6 # terracotta paver pitch (m)
497+
PAVER_GAP = 0.018 # grout joint
498+
DECK_DEPTH = 0.06 # pavers sit flush at z=0; the studio floor drops below
499+
500+
501+
def build_patio(sc):
502+
"""Render-only terracotta paver deck under both parasols: a 10 x 5 grid
503+
of beveled pavers with grout joints, two tones alternated by a fixed
504+
index hash so the deck is identical every run. Passed to the framing
505+
gate as stage, like the floor."""
506+
tones = [make_material("PaverA", (0.19, 0.075, 0.04), rough=0.72, metallic=0.0),
507+
make_material("PaverB", (0.15, 0.062, 0.034), rough=0.78, metallic=0.0)]
508+
me = bpy.data.meshes.new("Patio")
509+
bm = bmesh.new()
510+
try:
511+
s = (PAVER - PAVER_GAP) / 2
512+
for i in range(10):
513+
for j in range(5):
514+
cx = (i - 4.5) * PAVER
515+
cy = (j - 2.0) * PAVER
516+
res = bmesh.ops.create_cube(bm, size=1.0)
517+
vs = res["verts"]
518+
for v in vs:
519+
v.co.x = cx + v.co.x * 2 * s
520+
v.co.y = cy + v.co.y * 2 * s
521+
v.co.z = -DECK_DEPTH / 2 + v.co.z * DECK_DEPTH
522+
idx = 1 if (i * 7 + j * 3) % 5 < 2 else 0
523+
for f in {f for v in vs for f in v.link_faces}:
524+
f.material_index = idx
525+
bmesh.ops.bevel(bm, geom=list(bm.edges), offset=0.012, segments=2,
526+
profile=0.5, affect="EDGES", clamp_overlap=True)
527+
bm.to_mesh(me)
528+
finally:
529+
bm.free()
530+
for m in tones:
531+
me.materials.append(m)
532+
ob = bpy.data.objects.new("Patio", me)
449533
sc.collection.objects.link(ob)
450534
return ob
451535

@@ -512,11 +596,14 @@ def render_still(path, engine):
512596
spin = math.radians(-90 - 22.5)
513597
left = build_parasol(sc, "Corner", assign_corner, ATTR_C, (-PAIR_X, 0.0), spin, mats)
514598
right = build_parasol(sc, "Point", assign_point_naive, ATTR_P, (PAIR_X, 0.0), spin, mats)
515-
label_mat = make_material("LabelEnamel", (0.36, 0.345, 0.32), rough=0.6, metallic=0.0)
516-
labels = [floor_letters(sc, "CORNER", -PAIR_X, label_mat),
517-
floor_letters(sc, "POINT", PAIR_X, label_mat)]
599+
mats["slate"] = make_material("Slate", (0.045, 0.048, 0.055), rough=0.55, metallic=0.0)
600+
mats["enamel"] = make_material("SignEnamel", (0.78, 0.72, 0.58), rough=0.5, metallic=0.0)
601+
labels = (placard(sc, "CORNER", -PAIR_X, mats)
602+
+ placard(sc, "POINT", PAIR_X, mats))
518603

519604
floor, wall = build_studio(sc)
605+
floor.location.z = -DECK_DEPTH
606+
patio = build_patio(sc)
520607

521608
cam_data = bpy.data.cameras.new("Cam")
522609
cam_data.lens = 50.0
@@ -554,14 +641,14 @@ def render_still(path, engine):
554641
sc, cam,
555642
hero=hero,
556643
elements=hero + labels,
557-
stage=[floor, wall],
644+
stage=[floor, wall, patio],
558645
)
559646
if fcode:
560647
return fcode
561648
# asset-quality floors (naming, material variation, edge treatment),
562649
# measured on one parasol — exit 11 on violation
563650
aqcode = gallery_asset_quality.check_asset_quality(
564-
sc, cam, hero=left, stage=[floor, wall])
651+
sc, cam, hero=left, stage=[floor, wall, patio])
565652
if aqcode:
566653
return aqcode
567654
bpy.ops.render.render(write_still=True)
8.11 KB
Loading

‎examples/gallery.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@
544544
"name": "attribute-domain-shear",
545545
"dir": "examples/attribute-domain-shear",
546546
"teaches": "POINT vs CORNER color-attribute domains on a shared-vertex fan: CORNER stays exact per face while a naive per-wedge POINT loop shears to the last write at the hub. A striped parasol keeps crisp stripes under CORNER and smears, with one panel turned the wrong color, under POINT; the measured shear equals the palette closed form.",
547-
"alt": "Two striped patio parasols labelled CORNER and POINT: the left has crisp crimson and cream stripes, the right has stripes smeared pink along the seams and a front panel turned cream.",
547+
"alt": "Two patio parasols on a terracotta paver deck behind CORNER and POINT slate placards: the left has crisp crimson and cream stripes, the right has its stripes smeared pink.",
548548
"witnessesFix": "Authoring per-face colors into a POINT-domain attribute overwrites shared vertices once per neighbor; the last write wins and intended colors shear across every shared vertex.",
549549
"hero": "docs/gallery/assets/attribute-domain-shear-hero.webp",
550550
"preview": "examples/attribute-domain-shear/preview.webp",

0 commit comments

Comments
 (0)