Skip to content

Commit 7ca0fdc

Browse files
TMHSDigitalclaude
andauthored
fix(examples): make the examples follow the rules they teach, and lint it (#431)
Agents copy these scripts (bmesh-gear is the documented anatomy), but nothing checked them against the repo's own rules, and they broke them: 53 per-face `use_smooth` loops (three in bmesh-gear), 74 per-vertex `v.co.z += ...` loops, more per-element material/co writes, and seven `active_object` reads with no None guard. - 127 single-statement loops became foreach_set / foreach_get by a scripted AST rewrite (inline comments kept); seven more hand-converted (constant material indices, uniform co scale/offset, the moka-pot lean). - Eight loops with genuinely per-element logic (ring/station material bands, n-gon marking, ball-surface tests) carry a `# foreach-exempt: <why>` marker. - Seven `X = bpy.context.active_object` reads get `if X is None: raise`. - tests/check_example_rules.py (Validate) rejects an unmarked per-element write in a loop over .polygons/.vertices (Mesh-only names, so bmesh code is never hit) and an unguarded active_object read. Old and new versions of six scripts, run on 5.2.1 across ten paths including the touched falsifiers, print identical output with identical exit codes. Closes #400 Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 5177e24 commit 7ca0fdc

108 files changed

Lines changed: 503 additions & 274 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/validate.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ jobs:
7979
- name: Showcase helper drift (warn-only report)
8080
run: python3 tests/check_helper_drift.py
8181

82+
- name: Examples and showcase follow the repo's own rules
83+
run: python3 tests/check_example_rules.py
84+
8285
- name: Check asset_sheet.py can render every showcase piece and reference
8386
run: python3 tests/check_asset_sheet_select.py
8487

‎docs/new-example-prompt.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ intentional, important geometry is not clipped, highlights are not blown out,
136136
lights or helper objects are not visible unintentionally, and materials, lighting,
137137
background, and camera feel designed rather than left at Blender defaults.
138138

139+
The example must follow the repo's own rules, not just teach them:
140+
`tests/check_example_rules.py` (run by Validate) rejects a per-element write
141+
in a loop over `mesh.polygons` / `mesh.vertices` (use `foreach_set`) and an
142+
`active_object` read with no `None` guard. A loop that really needs per-element
143+
logic carries `# foreach-exempt: <why>` on or just above its `for` line.
144+
139145
Contact-sheet gate (required before shipping the still): build a composite placing
140146
the new hero beside the pinned calibration set—canonical membership is in
141147
`CLAUDE.md` § Quality Gates for Example Runs—and compare stage darkness, wedge

‎examples/armature-bend/armature_bend.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,7 @@ def build_tube():
120120
bm.to_mesh(me)
121121
finally:
122122
bm.free()
123-
for poly in me.polygons:
124-
poly.use_smooth = True
123+
me.polygons.foreach_set("use_smooth", [True] * len(me.polygons))
125124
# collars keep crisp machined steps; the rubber ribs stay smooth
126125
me.set_sharp_from_angle(angle=math.radians(50.0))
127126
obj = bpy.data.objects.new("Tube", me)

‎examples/bake-normal-high-to-low/bake_normal_high_to_low.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,7 @@ def decimate_apply(obj, target_tris):
140140
object=obj, active_object=obj, selected_objects=[obj]
141141
):
142142
bpy.ops.object.modifier_apply(modifier=mod.name)
143-
for poly in obj.data.polygons:
144-
poly.use_smooth = True
143+
obj.data.polygons.foreach_set("use_smooth", [True] * len(obj.data.polygons))
145144
obj.data.update()
146145
return current, evaluated_triangle_count(obj)
147146

‎examples/bmesh-gear/bmesh_gear.py‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -290,8 +290,7 @@ def companion_pitch_r(teeth):
290290

291291
def render_still(obj, path, engine):
292292
scene = bpy.context.scene
293-
for poly in obj.data.polygons:
294-
poly.use_smooth = False # crisp machined facets
293+
obj.data.polygons.foreach_set("use_smooth", [False] * len(obj.data.polygons)) # crisp machined facets
295294
brass = machined_brass()
296295
obj.data.materials.append(brass)
297296
blued = principled("BluedSteel", (0.07, 0.11, 0.22), 0.26, metal=1.0, noise=3.0)
@@ -329,8 +328,7 @@ def partner(name, teeth, direction, mat, depth, bore=None):
329328
cy = HERO_C[1] + dist * math.sin(direction)
330329
me = ring_gear_mesh(name, teeth, pr - ADDENDUM, pr + ADDENDUM, depth, bore=bore)
331330
me.materials.append(mat)
332-
for p in me.polygons:
333-
p.use_smooth = False
331+
me.polygons.foreach_set("use_smooth", [False] * len(me.polygons))
334332
ob = bpy.data.objects.new(name, me)
335333
scene.collection.objects.link(ob)
336334
# a gap centred on the line of centres, facing the checked gear's tooth
@@ -368,8 +366,7 @@ def web(bm):
368366
def add(name, build, mat, cx, cy, smooth=False, bevel=0.01):
369367
me = solid_mesh(name, build)
370368
me.materials.append(mat)
371-
for p in me.polygons:
372-
p.use_smooth = smooth
369+
me.polygons.foreach_set("use_smooth", [smooth] * len(me.polygons))
373370
ob = bpy.data.objects.new(name, me)
374371
scene.collection.objects.link(ob)
375372
place(ob, cx, cy)

‎examples/boolean-exact-volume/boolean_exact_volume.py‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -288,8 +288,7 @@ def tube_mesh(name, segments, radius, sides=10):
288288
bm.to_mesh(me)
289289
finally:
290290
bm.free()
291-
for p in me.polygons:
292-
p.use_smooth = True
291+
me.polygons.foreach_set("use_smooth", [True] * len(me.polygons))
293292
return me
294293

295294

@@ -357,8 +356,7 @@ def add(name, me, mat, mw=None):
357356
# the boolean output carries the operands' (empty) material slots;
358357
# start clean so every face takes the finish below
359358
shown.materials.clear()
360-
for poly in shown.polygons:
361-
poly.material_index = 0
359+
shown.polygons.foreach_set("material_index", [0] * len(shown.polygons))
362360
ob = add(f"{op.title()}Shown", shown, finishes[op], res.matrix_world.copy())
363361
bev = ob.modifiers.new("MachinedChamfer", 'BEVEL')
364362
bev.width = 0.012

‎examples/car-mirror-symmetry/car_mirror_symmetry.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ def _assign_body_materials(body):
396396
are glass exactly where the cabin factor ramps (windshield, hatch)."""
397397
PAINT, GLASS, TRIM, CHROME = (MATERIALS.index(m)
398398
for m in ("Paint", "Glass", "Trim", "Chrome"))
399+
# foreach-exempt: per-face material from the face's ring/station index
399400
for poly in body.data.polygons:
400401
i, k = divmod(poly.index, RING - 1)
401402
if i >= N_ST - 1: # cap ngons (front/rear fascia)

‎examples/collision-hull-proxy/collision_hull_proxy.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ def build_hydrant():
223223
body = lathe_object("Body", BODY_PROFILE, SEGMENTS)
224224
body.data.materials.append(red)
225225
body.data.materials.append(yellow)
226+
# foreach-exempt: conditional per-face material by face height
226227
for p in body.data.polygons: # the bonnet dome is the yellow profile tail
227228
if p.center.z > 1.09:
228229
p.material_index = 1
@@ -384,8 +385,7 @@ def render_still(groups, pieces, path, engine):
384385
scene = bpy.context.scene
385386
for g in groups:
386387
for o in g["render"]:
387-
for p in o.data.polygons:
388-
p.use_smooth = True
388+
o.data.polygons.foreach_set("use_smooth", [True] * len(o.data.polygons))
389389
for g in groups: # cages exist only to build hulls; never rendered
390390
for o in g["cage"]:
391391
o.hide_render = True
@@ -415,8 +415,7 @@ def render_still(groups, pieces, path, engine):
415415

416416
for name, hull, _ in pieces:
417417
hull.data.materials.append(shell)
418-
for p in hull.data.polygons:
419-
p.use_smooth = False # facets must read as facets
418+
hull.data.polygons.foreach_set("use_smooth", [False] * len(hull.data.polygons)) # facets must read as facets
420419
hull.data.materials.append(wire_mat)
421420
wire = bpy.data.objects.new(f"{name}Wire", hull.data)
422421
mod = wire.modifiers.new("Wire", 'WIREFRAME')
@@ -521,8 +520,9 @@ def main():
521520
for g in groups:
522521
hull = build_hull(f"{g['name']}Hull", collect_points(g["cage"]))
523522
if args.shrink_hull:
524-
for v in hull.data.vertices:
525-
v.co *= 0.5
523+
co = [0.0] * (len(hull.data.vertices) * 3)
524+
hull.data.vertices.foreach_get("co", co)
525+
hull.data.vertices.foreach_set("co", [c * 0.5 for c in co])
526526
hull.data.update()
527527
pieces.append((g["name"], hull, collect_points(g["render"])))
528528
code = check(pieces)

‎examples/color-attribute-wheel/color_attribute_wheel.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,7 @@ def _mat(name, base, rough, metal=0.0, noise=None, coat=0.0):
259259
def render_still(obj, path, engine):
260260
scene = bpy.context.scene
261261
me = obj.data
262-
for poly in me.polygons:
263-
poly.use_smooth = True
262+
me.polygons.foreach_set("use_smooth", [True] * len(me.polygons))
264263

265264
mat = build_material()
266265
if mat is None:

‎examples/compositor-glare/compositor_glare.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,11 @@ def build_scene():
127127
major_segments=64, minor_segments=16,
128128
location=(x, y, RING_Z), rotation=(math.radians(90), 0.0, 0.0))
129129
ring = bpy.context.active_object
130+
if ring is None:
131+
raise RuntimeError("the add operator left no active object")
130132
ring.name = name
131133
ring.data.materials.append(make_emissive(f"Neon{name[4:]}", color, strength))
132-
for poly in ring.data.polygons:
133-
poly.use_smooth = True
134+
ring.data.polygons.foreach_set("use_smooth", [True] * len(ring.data.polygons))
134135

135136
world = bpy.data.worlds.new("World")
136137
world.use_nodes = True

0 commit comments

Comments
 (0)