|
53 | 53 | render exactly like a failed contract check fails the witness. The gate |
54 | 54 | runs only on the opt-in render path; check-only semantics (non-zero == |
55 | 55 | API-contract drift) are unchanged. |
| 56 | +
|
| 57 | +Deviations: VISUAL-STYLE Layer 1 allows a documented framing deviation |
| 58 | +when the bleed or the scale is the design — radiating subjects that read |
| 59 | +as extending past the frame, edge-to-edge fields where the fill is the |
| 60 | +point, subjects whose contract is the world or the atmosphere. The |
| 61 | +deviation must be visible in the example's source and render log: pass |
| 62 | +``deviation="reason"`` to `check_framing`, which switches enforcement to |
| 63 | +reporting and prints the reason with the numbers. An empty or whitespace |
| 64 | +reason raises ValueError — a deviation cannot be taken silently. The |
| 65 | +README must carry the same one-line note, exactly as stage deviations do. |
| 66 | +
|
| 67 | +Scope: the helper measures the (scene, camera) pair it is handed — pass |
| 68 | +the scene the still actually renders from. Multi-scene examples (e.g. |
| 69 | +vse-cut-list, whose gallery still renders from a dedicated ``Bay`` scene |
| 70 | +while ``bpy.context.scene`` is the cut-list scene) must pass that scene |
| 71 | +and its camera explicitly. Examples whose subject IS the world or the |
| 72 | +atmosphere (sky-texture-sun-elevation) have no renderable hero for the |
| 73 | +matte to isolate — the number mismeasures a reference prop, so they take |
| 74 | +a documented deviation and report rather than enforce. |
56 | 75 | """ |
57 | 76 | import os |
58 | 77 | import sys |
@@ -325,13 +344,32 @@ def measure_framing(scene, camera, hero, elements, stage=(), strategy=DEFAULT_ST |
325 | 344 | return res |
326 | 345 |
|
327 | 346 |
|
328 | | -def check_framing(scene, camera, hero, elements, stage=(), strategy=DEFAULT_STRATEGY): |
| 347 | +def check_framing(scene, camera, hero, elements, stage=(), strategy=DEFAULT_STRATEGY, |
| 348 | + deviation=None): |
329 | 349 | """Measure, print the numbers, and gate: 0 pass, EXIT_FRAMING (10) on violation. |
330 | 350 |
|
331 | 351 | Render path only — never call this from an example's check-only path. |
| 352 | +
|
| 353 | + deviation — None (default) enforces the band. A non-empty reason string |
| 354 | + documents a legitimate framing deviation (VISUAL-STYLE Layer 1): the |
| 355 | + numbers are still measured and printed with the reason, but the gate |
| 356 | + reports instead of enforcing and always returns 0. An empty or |
| 357 | + whitespace-only reason raises ValueError — no silent opt-outs. |
332 | 358 | """ |
| 359 | + if deviation is not None and not str(deviation).strip(): |
| 360 | + raise ValueError( |
| 361 | + "check_framing deviation requires a non-empty reason string; " |
| 362 | + "a framing deviation cannot be taken silently" |
| 363 | + ) |
333 | 364 | res = measure_framing(scene, camera, hero, elements, stage=stage, strategy=strategy) |
334 | 365 | print(res.report()) |
| 366 | + if deviation is not None: |
| 367 | + print( |
| 368 | + f'framing_deviation reason="{str(deviation).strip()}" ' |
| 369 | + f"enforcement=report fill_ok={res.fill_ok} " |
| 370 | + f"margins_ok={res.margins_ok} (numbers printed, gate not enforced)" |
| 371 | + ) |
| 372 | + return 0 |
335 | 373 | if not res.ok: |
336 | 374 | print( |
337 | 375 | "ERROR: framing violation — Layer 1 requires fill " |
|
0 commit comments