Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/gallery/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ <h2><a class="stretch" href="color-attribute-wheel/">Color Attribute Wheel</a></
<h2><a class="stretch" href="parent-inverse-orrery/">Parent Inverse Orrery</a></h2>
<p class="slug">parent-inverse-orrery</p>
<p class="teaches">Data-API parenting for a brass orrery — the keep-world idiom (child.parent = pivot; child.matrix_parent_inverse = pivot.matrix_world.inverted()) carrying arms, planets, and a two-level moon through spinning pivots.</p>
<p class="witnesses"><span class="tag">witnesses</span> Bare `.parent =` really does teleport the child; the idiom restores world position exactly; matrix_world stays stale until view_layer.update(); every orbit lands on its closed form.</p>
<p class="witnesses"><span class="tag">witnesses</span> Bare <code>.parent =</code> really does teleport the child; the idiom restores world position exactly; matrix_world stays stale until view_layer.update(); every orbit lands on its closed form.</p>
<span class="card-link" aria-hidden="true">View example &rarr;</span>
</div>
</article>
Expand Down
2 changes: 1 addition & 1 deletion docs/gallery/parent-inverse-orrery/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ <h1>Parent Inverse Orrery</h1>
<img src="../assets/parent-inverse-orrery-hero.webp" alt="A brass orrery on a turned walnut base: a glowing gold sun atop the column, mottled blue, green and orange planets on riser posts, orbit rings beneath them, and a silver moon." width="1280" height="720" fetchpriority="high" />
</button>
<p class="zoom-hint">Rendered headless by the example itself. Select it to enlarge.</p>
<div class="callout"><span class="tag">witnesses</span> Bare `.parent =` really does teleport the child; the idiom restores world position exactly; matrix_world stays stale until view_layer.update(); every orbit lands on its closed form.</div>
<div class="callout"><span class="tag">witnesses</span> Bare <code>.parent =</code> really does teleport the child; the idiom restores world position exactly; matrix_world stays stale until view_layer.update(); every orbit lands on its closed form.</div>
<p class="taglist"><span class="tag">tags</span> <a href="../#tag=objects">objects</a> <a href="../#tag=transforms">transforms</a></p>
<div class="runline">
<pre id="runCmd">blender --background --python examples/parent-inverse-orrery/parent_inverse_orrery.py --</pre>
Expand Down
14 changes: 14 additions & 0 deletions docs/new-example-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,20 @@ is not acceptable evidence. When a new example outclasses a member, update the
pinned set in `CLAUDE.md`, its canonical home. One successful render command is not
proof that the image is good, and neither is the second.

Asset-sheet gate (required when the subject is a game asset: a prop, kit piece or
vehicle the example ships as a model): render the hero asset alone (neutral
three-quarter view, plain studio light, no staging, labels or comparison props)
beside the pinned asset-quality reference set, whose canonical membership is also in
`CLAUDE.md` § Quality Gates for Example Runs, with
`python scripts/asset_sheet.py --blender PATH NAME`. Add the entry's row to that
script's `SELECT` table first (`tests/check_asset_sheet_select.py` enforces it for
showcase pieces). Commit the composite under `docs/gallery/asset-sheets/`, link it in
the PR body, and report a verdict. The asset ships only if it is not identifiable as
the least-designed object in the lineup: a strong scene can carry a weak model, and
this gate removes the scene. Its measurable floors (naming, material variation, edge
treatment) live in `examples/gallery_asset_quality.py`, called on the render path
(exit 11 on violation).

After implementation and local verification:

1. Review the complete diff and remove accidental churn or temporary files. Stage
Expand Down
24 changes: 19 additions & 5 deletions scripts/build_gallery.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,20 @@ def check_alts(entries: list) -> None:
if len(alt) > ALT_MAX:
raise SystemExit(f"gallery entry {e['name']!r} alt is {len(alt)} chars "
f"(max {ALT_MAX}); describe the still in one sentence")
if "`" in alt:
# An attribute cannot carry <code>, so a backtick would be read
# aloud and shown literally; teaches/witnessesFix render it (#368).
raise SystemExit(f"gallery entry {e['name']!r} alt contains a backtick; "
"alt text is plain prose")


def inline_code(text: str) -> str:
"""Escape text and render its `backtick` spans as <code>, as the landing
page does. Unbalanced backticks stay literal."""
parts = html.escape(text).split("`")
if len(parts) % 2 == 0:
return "`".join(parts)
return "".join(f"<code>{p}</code>" if i % 2 else p for i, p in enumerate(parts))


def check_categories(entries: list) -> None:
Expand Down Expand Up @@ -1446,15 +1460,15 @@ def build_detail(ex: dict, entries: list, *, base: str, repo_root_url: str, site
f'<li aria-current="page">{html.escape(name)}</li></ol></nav>')
parts.append(f' <h1>{html.escape(display_title(ex))}</h1>')
parts.append(f' <p class="slug">{html.escape(ex["dir"])}/</p>')
parts.append(f' <p>{html.escape(ex["teaches"])}</p>')
parts.append(f' <p>{inline_code(ex["teaches"])}</p>')
parts.append(" </header>")
parts.append(' <main id="main">')
parts.append(pager_html(prev, nxt, idx + 1, len(peers), noun))
parts.append(f' <button class="detail-hero" id="heroZoom" type="button" aria-label="View full size: {html.escape(ex["alt"], quote=True)}">')
parts.append(f' <img src="../assets/{html.escape(hero_file)}" alt="{html.escape(ex["alt"], quote=True)}" width="1280" height="720" fetchpriority="high" />')
parts.append(" </button>")
parts.append(f' <p class="zoom-hint">Rendered headless by the {noun} itself. Select it to enlarge.</p>')
parts.append(f' <div class="callout"><span class="tag">witnesses</span> {html.escape(ex["witnessesFix"])}</div>')
parts.append(f' <div class="callout"><span class="tag">witnesses</span> {inline_code(ex["witnessesFix"])}</div>')
cat = ex.get("category")
if cat:
parts.append(f' <p class="taglist"><span class="tag">category</span> '
Expand Down Expand Up @@ -1523,7 +1537,7 @@ def build_detail(ex: dict, entries: list, *, base: str, repo_root_url: str, site
.replace("__SIZES__", MINI_SIZES)
.replace("__ALT__", html.escape(r["alt"], quote=True))
.replace("__TITLE__", html.escape(display_title(r)))
.replace("__TEACHES__", html.escape(r["teaches"])))
.replace("__TEACHES__", inline_code(r["teaches"])))
parts.append(" </div>")
parts.append(" </section>")

Expand Down Expand Up @@ -1673,8 +1687,8 @@ def build_index(data: dict, *, base: str, repo_root_url: str, site: str, css_v:
.replace("__CATATTR__", f' data-category="{html.escape(ex["category"], quote=True)}"'
if ex.get("category") else "")
.replace("__KIND__", kind_noun(ex))
.replace("__TEACHES__", html.escape(ex["teaches"]))
.replace("__WITNESSES__", html.escape(ex["witnessesFix"]))
.replace("__TEACHES__", inline_code(ex["teaches"]))
.replace("__WITNESSES__", inline_code(ex["witnessesFix"]))
)

content = (
Expand Down
Loading