Skip to content

Commit 2e6948f

Browse files
TMHSDigitalclaude
andcommitted
fix(examples): redesign text-version-stamp as gold Inter numerals on a stone plinth
The old still read "5.2.I LTS": the built-in Bfont's "1" is a bare stem. The render path now sets the stamp in Inter, which Blender ships at datafiles/fonts/Inter.woff2 on 4.5 LTS and 5.x (looked up through bpy.utils.system_resource('DATAFILES'); exit 13 if missing), so the numeral carries its flag. The check still asserts the built-in Bfont, because the swap happens only after every check passes. The staging is redesigned too: a two-tier plinth of dark honed stone with 45° chamfers, a brushed-steel nameplate on its front with the BLENDER caption inked on it, and polished gold numerals with brushed roughness variation. A card light behind the camera gives the gold faces something to mirror, where they previously reflected the dark studio. Fill, rim and wedge were retuned toward the calibration stage. Evidence (live runs, local): - .scratch/blender-5.2.1-windows-x64/blender.exe (Blender 5.2.1 LTS): check-only exit 0, body='5.2.1 LTS' solid z-extent=0.1600 faces=4401. --wrong-body exits 4 (body='not-a-version'). - .scratch/blender-4.5.11-windows-x64/blender.exe (Blender 4.5.11 LTS): check-only exit 0, body='4.5.11 LTS'. The --output render exits 0 (Inter loads), with framing fill x=0.737 and margins l=0.047 r=0.088 b=0.150 t=0.389. - Framing on 5.2.1: fill x=0.741, margins l=0.047 r=0.088 b=0.150 t=0.356. - Contact sheet (docs/gallery/contact-sheets/text-version-stamp-contact-sheet.webp): candidate luma 0.248 / stage 0.221 / warmth +0.085 / sat 0.142, against armature-bend 0.279/0.213/-0.428/0.144 (the pre-redesign hero in this worktree), damped-track-aim 0.151/0.145/+0.277/0.231, and bmesh-gear 0.236/0.177/+0.303/0.250. Luma, stage and saturation sit inside the set, and the version reads unambiguously at thumbnail size. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 5798259 commit 2e6948f

7 files changed

Lines changed: 139 additions & 51 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ topology 1044 verts / 1028 faces.
430430
</tr>
431431
<tr>
432432
<td width="46%" valign="middle">
433-
<a href="examples/text-version-stamp/"><img src="examples/text-version-stamp/preview.webp" alt="Text version stamp: beveled brass version numerals standing on a dark plinth with a steel BLENDER caption set into its face — the body text is the live bpy.app.version_string" /></a>
433+
<a href="examples/text-version-stamp/"><img src="examples/text-version-stamp/preview.webp" alt="Text version stamp: beveled gold version numerals standing on a chamfered dark stone plinth with a BLENDER caption on a brushed-steel nameplate — the body text is the live bpy.app.version_string" /></a>
434434
</td>
435435
<td valign="middle">
436436

6.44 KB
Loading
-10.9 KB
Loading

‎examples/gallery.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@
259259
"name": "text-version-stamp",
260260
"dir": "examples/text-version-stamp",
261261
"teaches": "The TextCurve data API — curves.new(type='FONT'), live body text from bpy.app.version_string, extrude and bevel_depth solids, and evaluated-mesh conversion — so every render self-documents which Blender produced it.",
262-
"alt": "The Blender version number in large gold 3D characters standing on a dark plinth, the word BLENDER set into its face.",
262+
"alt": "The Blender version number in polished gold 3D characters on a chamfered two-tier dark stone plinth, with BLENDER inked on a brushed-steel nameplate across its face.",
263263
"witnessesFix": "TextCurve solids are exactly predictable: evaluated z-extent = 2 × (extrude + bevel_depth), bevel widens the outline by 2 × bevel_depth, editing body regenerates geometry — and version_string is not bare semver on LTS (\"4.5.11 LTS\"), so parse bpy.app.version instead.",
264264
"hero": "docs/gallery/assets/text-version-stamp-hero.webp",
265265
"preview": "examples/text-version-stamp/preview.webp",

‎examples/text-version-stamp/README.md‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,19 @@ on every LTS build; branch on the `bpy.app.version` tuple instead.
2424

2525
## Staging
2626

27-
The numerals stand on a dark plinth, with the steel caption set into the
28-
plinth's front face. The caption used to float in the air above the
29-
stamp, and an emissive bar lay along the bottom edge of the frame. The
30-
still renders under the Standard view transform on the house stage.
31-
Render path only; the checked text body and its extents are unchanged.
27+
The polished gold numerals stand on a two-tier chamfered plinth of dark
28+
honed stone. A brushed-steel nameplate on its front face carries the
29+
BLENDER caption, so gold is reserved for the version and steel for the
30+
plate. A card light behind the camera gives the gold faces something to
31+
mirror. The still renders under the Standard view transform on the house
32+
stage.
33+
34+
The render sets the stamp in **Inter**, which Blender ships at
35+
`datafiles/fonts/Inter.woff2` on 4.5 LTS and 5.x alike (found through
36+
`bpy.utils.system_resource('DATAFILES')`). The built-in Bfont's "1" is a
37+
bare stem, so the old still read "5.2.I LTS". The check still asserts the
38+
built-in Bfont: the font swap happens on the render path only, after every
39+
check has passed, and the checked text body and its extents are unchanged.
3240

3341
## Run
3442

@@ -65,6 +73,7 @@ against it. `10` is the shared framing helper.
6573
| 8 | Mesh survived `to_mesh_clear()` |
6674
| 9 | `--output` produced no file |
6775
| 10 | Gallery framing violation |
76+
| 13 | `--output`: bundled `datafiles/fonts/Inter.woff2` not found |
6877

6978
The `blender-smoke` workflow runs the check on Blender 5.2 LTS and 4.5 LTS
7079
(5.1 on the weekly cron, the `needs-5.1` PR label, or manual dispatch).
5.51 KB
Loading

‎examples/text-version-stamp/text_version_stamp.py‎

Lines changed: 123 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -132,19 +132,46 @@ def eevee_engine_id():
132132
return 'BLENDER_EEVEE' if bpy.app.version >= (5, 0, 0) else 'BLENDER_EEVEE_NEXT'
133133

134134

135+
def bundled_font(name):
136+
"""Load a font Blender ships in datafiles/fonts (both 4.5 LTS and 5.x ship
137+
Inter.woff2 and DejaVuSansMono.woff2). None if the install lacks it."""
138+
base = bpy.utils.system_resource('DATAFILES')
139+
path = os.path.join(base, "fonts", name) if base else ""
140+
if not os.path.isfile(path):
141+
return None
142+
return bpy.data.fonts.load(path, check_existing=True)
143+
144+
135145
def render_still(obj, path, engine):
136146
scene = bpy.context.scene
137147
txt = obj.data
138148
txt.size = 1.0
139149
txt.space_character = 1.05
140-
141-
# brass stamp
142-
mat = bpy.data.materials.new("Brass")
150+
# Render only (the check above asserts the built-in Bfont): Bfont's "1"
151+
# is a bare stem that reads as a capital I ("5.2.I LTS"). Inter, shipped
152+
# with Blender, gives the numeral its flag.
153+
inter = bundled_font("Inter.woff2")
154+
if inter is None:
155+
print("ERROR: bundled datafiles/fonts/Inter.woff2 not found", file=sys.stderr)
156+
return 13
157+
txt.font = inter
158+
159+
# polished gold stamp: warm base, low roughness with a faint brushed
160+
# variation so the long faces carry a gradient instead of a flat slab
161+
mat = bpy.data.materials.new("Gold")
143162
mat.use_nodes = True
144-
bsdf = mat.node_tree.nodes["Principled BSDF"]
145-
bsdf.inputs["Base Color"].default_value = (0.85, 0.62, 0.28, 1.0)
163+
nt = mat.node_tree
164+
bsdf = nt.nodes["Principled BSDF"]
165+
bsdf.inputs["Base Color"].default_value = (0.95, 0.66, 0.26, 1.0)
146166
bsdf.inputs["Metallic"].default_value = 1.0
147-
bsdf.inputs["Roughness"].default_value = 0.32
167+
noise = nt.nodes.new("ShaderNodeTexNoise")
168+
noise.inputs["Scale"].default_value = 60.0
169+
noise.inputs["Detail"].default_value = 4.0
170+
rmap = nt.nodes.new("ShaderNodeMapRange")
171+
rmap.inputs["To Min"].default_value = 0.16
172+
rmap.inputs["To Max"].default_value = 0.30
173+
nt.links.new(noise.outputs["Fac"], rmap.inputs["Value"])
174+
nt.links.new(rmap.outputs["Result"], bsdf.inputs["Roughness"])
148175
txt.materials.append(mat)
149176

150177
# stand the text upright, feet on the floor, scaled to a constant width
@@ -156,53 +183,100 @@ def render_still(obj, path, engine):
156183
width = max(v.co.x for v in me.vertices) - min(v.co.x for v in me.vertices)
157184
height = max(v.co.y for v in me.vertices) - min(v.co.y for v in me.vertices)
158185
ev.to_mesh_clear()
159-
s = 3.1 / width
186+
s = 3.2 / width
160187
obj.scale = (s, s, s)
161188
obj.rotation_euler = (math.radians(90), 0.0, 0.0)
162189
# The stamp stands on a dark plinth, and the caption is set into the
163190
# plinth's front face. It used to float in the air above the stamp,
164191
# with a glowing bar along the bottom edge of the frame.
165-
plinth_h = 0.30
166-
plinth_d = 0.56
167-
obj.location = (0.0, 0.0, plinth_h + s * height / 2 + 0.02)
192+
# A two-tier plinth with 45-degree chamfers: a wide foot, and an upper
193+
# tier whose front face carries a brushed-steel nameplate with the
194+
# caption standing proud of it. The gold is reserved for the version; the
195+
# plinth is dark stone and the plate and caption are steel.
196+
import bmesh
197+
foot_h, top_h = 0.12, 0.30
198+
plinth_h = foot_h + top_h
199+
top_d = 0.62
200+
obj.location = (0.0, 0.0, plinth_h + s * height / 2 + 0.01)
201+
202+
def chamfer_box(name, size, loc, mat, chamfer):
203+
me = bpy.data.meshes.new(name)
204+
bm = bmesh.new()
205+
try:
206+
bmesh.ops.create_cube(bm, size=1.0)
207+
for v in bm.verts:
208+
v.co = (v.co.x * size[0], v.co.y * size[1], v.co.z * size[2])
209+
bmesh.ops.bevel(bm, geom=list(bm.edges), offset=chamfer, segments=1,
210+
profile=0.5, affect="EDGES", clamp_overlap=True)
211+
bm.to_mesh(me)
212+
finally:
213+
bm.free()
214+
me.materials.append(mat)
215+
ob = bpy.data.objects.new(name, me)
216+
ob.location = loc
217+
scene.collection.objects.link(ob)
218+
return ob
168219

169-
# small steel caption above, also a TextCurve
220+
pmat = bpy.data.materials.new("Stone")
221+
pmat.use_nodes = True
222+
pnt = pmat.node_tree
223+
pb = pnt.nodes["Principled BSDF"]
224+
pb.inputs["Roughness"].default_value = 0.62
225+
# honed dark stone: faint mottling, never a flat slot
226+
pn = pnt.nodes.new("ShaderNodeTexNoise")
227+
pn.inputs["Scale"].default_value = 9.0
228+
pn.inputs["Detail"].default_value = 8.0
229+
pr = pnt.nodes.new("ShaderNodeValToRGB")
230+
pr.color_ramp.elements[0].color = (0.011, 0.011, 0.013, 1.0)
231+
pr.color_ramp.elements[1].color = (0.028, 0.027, 0.029, 1.0)
232+
pnt.links.new(pn.outputs["Fac"], pr.inputs["Fac"])
233+
pnt.links.new(pr.outputs["Color"], pb.inputs["Base Color"])
234+
235+
smat = bpy.data.materials.new("Steel")
236+
smat.use_nodes = True
237+
snt = smat.node_tree
238+
sb = snt.nodes["Principled BSDF"]
239+
sb.inputs["Base Color"].default_value = (0.70, 0.72, 0.76, 1.0)
240+
sb.inputs["Metallic"].default_value = 1.0
241+
# brushed along X: noise stretched hard in X drives the roughness
242+
stc = snt.nodes.new("ShaderNodeTexCoord")
243+
smap = snt.nodes.new("ShaderNodeMapping")
244+
smap.inputs["Scale"].default_value = (2.0, 120.0, 120.0)
245+
sn = snt.nodes.new("ShaderNodeTexNoise")
246+
sn.inputs["Detail"].default_value = 6.0
247+
sr = snt.nodes.new("ShaderNodeMapRange")
248+
sr.inputs["To Min"].default_value = 0.22
249+
sr.inputs["To Max"].default_value = 0.42
250+
snt.links.new(stc.outputs["Object"], smap.inputs["Vector"])
251+
snt.links.new(smap.outputs["Vector"], sn.inputs["Vector"])
252+
snt.links.new(sn.outputs["Fac"], sr.inputs["Value"])
253+
snt.links.new(sr.outputs["Result"], sb.inputs["Roughness"])
254+
255+
foot = chamfer_box("PlinthFoot", (3.62, top_d + 0.20, foot_h), (0.0, 0.0, foot_h / 2),
256+
pmat, 0.035)
257+
top = chamfer_box("PlinthTop", (3.42, top_d, top_h), (0.0, 0.0, foot_h + top_h / 2),
258+
pmat, 0.045)
259+
plate = chamfer_box("Nameplate", (2.2, 0.03, 0.19),
260+
(0.0, -top_d / 2 - 0.008, foot_h + top_h / 2), smat, 0.008)
261+
262+
# dark inked caption on the steel plate, also a TextCurve
170263
cap = bpy.data.curves.new("Caption", type='FONT')
171264
cap.body = "B L E N D E R"
172265
cap.align_x = 'CENTER'; cap.align_y = 'CENTER'
173-
cap.size = 0.34; cap.extrude = 0.012; cap.bevel_depth = 0.004
174-
cmat = bpy.data.materials.new("Steel")
266+
cap.size = 0.15; cap.extrude = 0.008; cap.bevel_depth = 0.003
267+
cap.font = inter
268+
cmat = bpy.data.materials.new("CaptionInk")
175269
cmat.use_nodes = True
176270
cb = cmat.node_tree.nodes["Principled BSDF"]
177-
cb.inputs["Base Color"].default_value = (0.62, 0.66, 0.72, 1.0)
178-
cb.inputs["Metallic"].default_value = 1.0
179-
cb.inputs["Roughness"].default_value = 0.4
271+
cb.inputs["Base Color"].default_value = (0.03, 0.03, 0.035, 1.0)
272+
cb.inputs["Metallic"].default_value = 0.2
273+
cb.inputs["Roughness"].default_value = 0.35
180274
cap.materials.append(cmat)
181275
cap_obj = bpy.data.objects.new("Caption", cap)
182276
cap_obj.rotation_euler = (math.radians(90), 0.0, 0.0)
183-
cap_obj.location = (0.0, -plinth_d / 2 - cap.extrude, plinth_h / 2)
277+
cap_obj.location = (0.0, -top_d / 2 - 0.023 - cap.extrude + 0.002, foot_h + top_h / 2)
184278
scene.collection.objects.link(cap_obj)
185-
186-
import bmesh
187-
plinth_me = bpy.data.meshes.new("Plinth")
188-
bm = bmesh.new()
189-
try:
190-
bmesh.ops.create_cube(bm, size=1.0)
191-
bmesh.ops.bevel(bm, geom=list(bm.edges), offset=0.012, segments=2,
192-
profile=0.5, affect="EDGES", clamp_overlap=True)
193-
bm.to_mesh(plinth_me)
194-
finally:
195-
bm.free()
196-
pmat = bpy.data.materials.new("Plinth")
197-
pmat.use_nodes = True
198-
pb = pmat.node_tree.nodes["Principled BSDF"]
199-
pb.inputs["Base Color"].default_value = (0.045, 0.045, 0.05, 1.0)
200-
pb.inputs["Roughness"].default_value = 0.45
201-
plinth_me.materials.append(pmat)
202-
plinth = bpy.data.objects.new("Plinth", plinth_me)
203-
plinth.scale = (3.5, plinth_d, plinth_h)
204-
plinth.location = (0.0, 0.0, plinth_h / 2)
205-
scene.collection.objects.link(plinth)
279+
plinth = [foot, top, plate]
206280

207281
# dark studio: floor + back wall
208282
floor_me = bpy.data.meshes.new("Floor")
@@ -239,20 +313,25 @@ def light(name, loc, energy, size, col, rot):
239313
scene.collection.objects.link(ob)
240314

241315
# brass reads on reflections: warm key, broad cool fill, hard warm rim
242-
light("Key", (-3.2, -4.8, 4.6), 850.0, 6.0, (1.0, 0.95, 0.86), (52, 0, -32))
243-
light("Fill", (4.6, -3.6, 2.4), 320.0, 8.0, (0.75, 0.84, 1.0), (66, 0, 48))
244-
light("Rim", (0.8, 4.2, 3.4), 900.0, 3.0, (1.0, 0.75, 0.45), (-65, 0, 170))
316+
light("Key", (-3.2, -4.8, 4.6), 700.0, 6.0, (1.0, 0.95, 0.86), (52, 0, -32))
317+
light("Fill", (4.6, -3.6, 2.4), 150.0, 8.0, (0.75, 0.84, 1.0), (66, 0, 48))
318+
light("Rim", (0.8, 4.2, 3.4), 520.0, 3.0, (1.0, 0.75, 0.45), (-65, 0, 170))
319+
# a broad card behind the camera facing the stamp: the gold's front faces
320+
# mirror it as a clean gradient instead of the dark studio
321+
light("Card", (0.9, -8.6, 1.9), 260.0, 4.0, (1.0, 0.9, 0.74), (84, 0, 6))
322+
# warm wedge raking the back wall (docs/VISUAL-STYLE.md)
323+
light("Wedge", (2.6, 5.0, 3.8), 260.0, 6.0, (1.0, 0.72, 0.45), (-70, 0, 195))
245324

246325
cam_data = bpy.data.cameras.new("Cam")
247326
cam_data.lens = 50.0
248327
cam = bpy.data.objects.new("Cam", cam_data)
249328
# Reframed: the old (0,-8.5,1.15) fixed 86.5° pitch left the stamp at
250329
# 0.663 fill with a dead lower third; moved in with an aim on the stamp's
251330
# vertical center so text, caption, and underline bar balance the frame.
252-
cam.location = (0.0, -5.45, 1.55)
331+
cam.location = (1.15, -5.75, 1.75)
253332
scene.collection.objects.link(cam)
254333
aim = bpy.data.objects.new("Aim", None)
255-
aim.location = (0.0, 0.0, 0.62)
334+
aim.location = (0.12, 0.0, 0.66)
256335
scene.collection.objects.link(aim)
257336
con = cam.constraints.new('TRACK_TO')
258337
con.target = aim
@@ -280,7 +359,7 @@ def light(name, loc, energy, size, col, rot):
280359
fcode = gallery_framing.check_framing(
281360
scene, cam,
282361
hero=[obj],
283-
elements=[obj, cap_obj, plinth],
362+
elements=[obj, cap_obj, *plinth],
284363
stage=[floor, wall],
285364
)
286365
if fcode:

0 commit comments

Comments
 (0)