From 78c852cfb7ace1e3d5873519c54187a5c619f23e Mon Sep 17 00:00:00 2001 From: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Date: Fri, 25 Sep 2026 14:53:47 -0400 Subject: [PATCH] feat(showcase): add grain-sacks Grain sacks were the next unclaimed showcase candidate. What an envelope check cannot see on a sack is whether it has settled: an egg-bottomed sack still touches the floor at its lowest vertex, so zmin and the per-sack support gate both pass it. The piece sums the flat-on-floor face area under each sack, and --round-bottom exits 19 with every patch at 0 while zmin and the supports still pass. Also asserts each twine tie sits in its neck's waist, bites the cloth by a banded depth, and that the sacks press into one another and carry their belly low, each with a falsifier proven on 4.5.11, 5.1.2 and 5.2.1. Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> --- .cursor-plugin/plugin.json | 3 +- CLAUDE.md | 2 +- README.md | 9 +- ROADMAP.md | 2 +- docs/gallery/asset-sheets/grain-sacks.webp | Bin 0 -> 36394 bytes docs/gallery/assets/grain-sacks-hero.webp | Bin 0 -> 33932 bytes .../grain-sacks-contact-sheet.webp | Bin 0 -> 57954 bytes docs/gallery/grain-sacks/index.html | 1783 +++++++++++++++++ docs/gallery/index.html | 15 +- showcase/gallery.json | 13 + showcase/grain-sacks/README.md | 214 ++ showcase/grain-sacks/grain_sacks.py | 1417 +++++++++++++ showcase/grain-sacks/preview.webp | Bin 0 -> 30960 bytes tests/smoke/catalog.json | 3 +- 14 files changed, 3452 insertions(+), 9 deletions(-) create mode 100644 docs/gallery/asset-sheets/grain-sacks.webp create mode 100644 docs/gallery/assets/grain-sacks-hero.webp create mode 100644 docs/gallery/contact-sheets/grain-sacks-contact-sheet.webp create mode 100644 docs/gallery/grain-sacks/index.html create mode 100644 showcase/grain-sacks/README.md create mode 100644 showcase/grain-sacks/grain_sacks.py create mode 100644 showcase/grain-sacks/preview.webp diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 7dd03805..8a3b4435 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -166,6 +166,7 @@ "showcase/hay-bale", "showcase/crate-stack", "showcase/stone-archway", - "showcase/rope-bridge" + "showcase/rope-bridge", + "showcase/grain-sacks" ] } diff --git a/CLAUDE.md b/CLAUDE.md index 1b812e5a..5b94ab13 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,7 +22,7 @@ rules/.mdc - Anti-pattern rules, 9 total templates// - Starter projects, 3 total snippets/.py - Standalone code patterns, 27 total examples// - Runnable smoke-gated examples, 59 total (+ gallery.json) -showcase// - Budget-conformance props, 29 pieces (sibling of examples/; see showcase/README.md) +showcase// - Budget-conformance props, 30 pieces (sibling of examples/; see showcase/README.md) scripts/build_gallery.py - Regenerates docs/gallery/ from examples/gallery.json + showcase/gallery.json scripts/site/ - Vendored landing-page build (Jinja2) docs/gallery/ - Committed generated gallery pages + hero renders diff --git a/README.md b/README.md index fa932531..80bb0dcf 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@

- 16 skills  •  9 rules  •  3 templates  •  27 snippets  •  59 examples  •  29 showcase pieces + 16 skills  •  9 rules  •  3 templates  •  27 snippets  •  59 examples  •  30 showcase pieces

@@ -37,7 +37,7 @@ ## Overview -This repository ships **16 skills, 9 rules, 3 templates, 27 snippets, 59 examples, and 29 showcase pieces** for Blender Python development targeting Blender 5.2 LTS (current stable) with Blender 4.5 LTS fallback support. Blender 5.1 is prior stable. +This repository ships **16 skills, 9 rules, 3 templates, 27 snippets, 59 examples, and 30 showcase pieces** for Blender Python development targeting Blender 5.2 LTS (current stable) with Blender 4.5 LTS fallback support. Blender 5.1 is prior stable. The content is consumed by AI coding agents reading these files directly from a checkout — **there is no MCP server in this repository, and none is required**. Cursor applies `rules/*.mdc` automatically wherever their scope globs match and takes skills by name in chat; Claude Code reads `skills/` and `rules/` from the project workspace, or from this repo kept as a referenced checkout. Any agent that can read files in a workspace can use it the same way. There is no build step for the content — edit the Markdown and Python files directly. @@ -106,7 +106,7 @@ per-script exit-code model, are in Budget-conformance props. Not examples. Conventions: [`showcase/README.md`](showcase/README.md).

-29 showcase pieces — click to expand the preview grid +30 showcase pieces — click to expand the preview grid

Shipping crate: a wooden slat crate with iron corner brackets on a dark studio floor, warm wedge on the back wall @@ -138,6 +138,7 @@ Budget-conformance props. Not examples. Conventions: [`showcase/README.md`](show Crate stack: three slatted shipping crates stacked and yawed, each with iron corner straps and runners, on a dark studio floor Stone archway: a semicircular masonry arch with coursed piers, projecting imposts, nine voussoirs and a proud keystone, on a dark studio floor Rope bridge: a sagging plank deck on foot ropes between two pairs of log posts, with lashed hemp hand ropes, suspenders, and ropes staked to the ground, on a dark studio floor + Grain sacks: three burlap sacks gathered and tied with twine, two standing and one lying slumped in front, one blue-striped and one red-striped, on a dark studio floor

[`shipping-crate`](showcase/shipping-crate/) — procedural crate through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets. Falsifier `--skip-decimate` exits 9. @@ -194,6 +195,8 @@ Budget-conformance props. Not examples. Conventions: [`showcase/README.md`](show [`rope-bridge`](showcase/rope-bridge/) — procedural rope footbridge whose plank deck is fitted to a parabola recomputed from the plank tops, with lashed hand ropes and staked foot ropes, through the same pipeline. Falsifiers `--vee-deck` exits 19, `--float-suspenders` exits 18. +[`grain-sacks`](showcase/grain-sacks/) — procedural pile of burlap grain sacks settled onto flat bases and tied with twine, through the same pipeline. Falsifiers `--round-bottom` exits 19, `--slip-tie` exits 17. +
## Examples diff --git a/ROADMAP.md b/ROADMAP.md index f3c51400..ea29741d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -134,7 +134,7 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo - ~~Stack of shipping crates as a game-prop showcase piece~~ **SHIPPED** as `showcase/crate-stack/` — one generator called three times with a per-instance seed driving yaw and plank widths, crates seated on the lid below; `--same-seed` exits 20 on the per-instance variation budget, `--float-stack` exits 18 on the seat - ~~Keystone archway with voussoirs as a game-prop showcase piece~~ **SHIPPED** as `showcase/stone-archway/` — coursed piers, projecting imposts, nine voussoirs, proud keystone; the intrados circle fit is recomputed from vertex positions and `--off-circle` exits 19 on it while leaving every other budget passing - ~~Rope bridge (planks, rope rails, sag between anchors) as a game-prop showcase piece~~ **SHIPPED** as `showcase/rope-bridge/` — log posts, through-tenoned sills, twenty planks seated on laid foot ropes, lashed hand ropes, suspenders, staked tie-offs; a least-squares parabola is fitted to the plank tops and `--vee-deck` exits 19 on it with the sag still in band -- Grain sacks (cloth sacks, some slumped, tied necks) as a game-prop showcase piece +- ~~Grain sacks (cloth sacks, some slumped, tied necks) as a game-prop showcase piece~~ **SHIPPED** as `showcase/grain-sacks/` — three settled, pleated burlap sacks, one lying, twine ties seated in the neck waist, sacks pressed into one another; flat contact patch summed from the mesh, `--round-bottom` exits 19 on it while zmin still passes - Brazier (iron bowl on legs, coals, ash) as a game-prop showcase piece - Apothecary shelf (shelf unit, bottles and jars of varied form) as a game-prop showcase piece - Wooden yoke (oxbow, iron rings) as a game-prop showcase piece diff --git a/docs/gallery/asset-sheets/grain-sacks.webp b/docs/gallery/asset-sheets/grain-sacks.webp new file mode 100644 index 0000000000000000000000000000000000000000..20e559db26dc90b9ad88689b4950abeee85fd085 GIT binary patch literal 36394 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 literal 0 HcmV?d00001 diff --git a/docs/gallery/assets/grain-sacks-hero.webp b/docs/gallery/assets/grain-sacks-hero.webp new file mode 100644 index 0000000000000000000000000000000000000000..5f409ab61f4780b94ef110d4cea2b1c99bf7af43 GIT binary patch literal 33932 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 g000000000000000000000000000000000000Mi)ap#T5? literal 0 HcmV?d00001 diff --git a/docs/gallery/grain-sacks/index.html b/docs/gallery/grain-sacks/index.html new file mode 100644 index 00000000..ae4e03ea --- /dev/null +++ b/docs/gallery/grain-sacks/index.html @@ -0,0 +1,1783 @@ + + + + + + grain-sacks — Showcase — Blender Developer Tools + + + + + + + + + + + + + + + + + + + + + + + + +
+

grain-sacks

+

A procedural pile of burlap grain sacks — settled onto flat bases, pressed into one another, gathered under twine ties with pleated crowns — carried through UVs, bake, LOD, compound collider, and Unity glTF, asserting recomputed budgets rather than an API contract.

+
+
+ +

Rendered headless by the showcase piece itself — click to zoom.

+
witnesses Recomputed: 8640 tris, two materials with 3264 burlap and 1152 twine faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.914×0.905×0.636 m, zmin 0, hygiene 0 including zero coplanar cross-shell pairs, all three sacks grounded, every tie in a neck waist 13–15 mm narrower than the cloth 40 mm either side, six twine turns seated 3.6–5.2 mm into the cloth, sacks pressing 12 mm into one another, bellies settled low (0.25–0.32 of the body), and flat contact patches of 0.035, 0.099 and 0.117 m² under the three sacks. --round-bottom exits 19 with every patch at 0 while zmin and the supports still pass; --slip-tie exits 17 on the waist.
+
+
blender --background --python showcase/grain-sacks/grain_sacks.py --
+ +
+
+

A pile of three burlap grain sacks. Two stand, one lies slumped in front of them, and each is gathered at the neck under a two-turn twine tie with a pleated crown above it. The sacks have settled onto flat bases and press into each other where they lean. A showcase piece, not an example — it witnesses no API contract. It asserts that generated geometry meets declared asset budgets, recomputed from the finished mesh.

+

What it composes

+
Shipped contentUsed for
skills/mesh-editing-and-bmeshlofted, pleated sack bodies and laid twine in one bmesh
skills/procedural-materials-and-shadershessian weave and stripes on the cloth's own coordinates (a second UV layer), per-sack tone
skills/bake-high-to-lowCycles tangent-space normal bake, high onto low
skills/engine-export-presetsUnity glTF (export_yup=True)
skills/depsgraph-and-evaluated-dataevaluated triangle counts for the LOD ratios
snippets/decimate_to_budget.pyLOD1 / LOD2 COLLAPSE chain
snippets/convex_hull_collider.pyone hull per sack, merged into a compound
snippets/lod_chain.pyLOD naming and ratio pattern
examples/mesh-hygiene-audithygiene combinatorics (copied, not imported)
+

The budget that matters

+

A sack full of grain settles. Its contents slump into the bottom and it rests on a flat patch of cloth pressed against the floor. An egg-bottomed sack still touches the floor at its lowest vertex, so the grounded-zmin gate and the per-sack support gate both pass it, but it reads as a balloon rocking on a point. The piece sums, per sack, the area of the faces that lie flat on the floor (every vertex at z = 0) and asserts each is at least 0.012 m².

+

--round-bottom is the falsifier built for exactly this. It sinks each sack by the same amount and folds the same height, but squashes the base linearly instead of flattening it, so every sack keeps its height, its footprint and its single lowest point on the floor. Every other budget passes. Only the patch sees it: 0 m² on all three.

+

Settling and pressing

+
  • Settle. Each sack is built standing (or laid on its flat side and squashed 16%), sunk SINK = 60 mm into the floor, and everything under SETTLE_C = 30 mm is folded back up by g(u) = ((u + 1) / 2)², clamped to 0 below u = −1. The fold is C1 at its top and exactly flat where the grain presses the floor. The squashed height goes outward from the sack's own axis (SPREAD), so the base bulges the way a filled sack does.
  • Press. Sacks are placed, then slid along the line between centres until the moving sack's deepest vertex inside the sack it leans on reaches PRESS = 12 mm. This uses bisection on the real meshes, so the press is exact whatever the lumps and pleats do where the two meet.
  • Tie. Each twine turn's centreline is the host's own section at its station, pushed out along each vertex's radial by the twine radius less TIE_BITE. The turn follows the pleats instead of standing proud of the valleys and sinking into the crests.
+

Budgets

+

Declared in the script as named constants, recomputed from the generated mesh. Measured values are from Blender 5.2.1; every one is byte-identical on 4.5.11 and 5.1.2.

+
BudgetBandMeasured
Base triangles8000–93008640
LOD1 ratio0.32–0.620.5000
LOD2 ratio0.10–0.350.2199
Material slotsexactly 2, distinct2
Cloth faces≥ 26003264
Twine faces≥ 9001152
UV boundsinside 0..1(0.0133, 0.0133)–(0.9867, 0.9867)
UV AABB overlap≤ 1e-50.000000
Outer AABB0.914 × 0.905 × 0.636 m ± 0.0200.9139 × 0.9054 × 0.6360
Collider triangles≤ 450376 (three hulls)
Normal bake{'FINISHED'} with image data{'FINISHED'}, has_data=True
glTF exportfile written, non-empty~280 kB
Hygieneall zeroloose 0/0, non-manifold 0, zero-area 0, doubles 0, n-gons 0, coplanar cross-shell pairs 0
Grounded AABB\zmin\≤ 1e-40.00000
Named supports3 sacks, each zmin ≤ 1e-33 at 0.00000
Tie in the waisthost under the tie ≥ 8 mm narrower than 40 mm either side, per sack14.25 / 15.04 / 13.11 mm
Tie seat6 turns, deepest twine vertex inside the cloth 1.0–6.0 mm3.57–5.21 mm
Press between sackseach sack's deepest vertex inside another 6–20 mm11.96 / 12.00 / 12.00 mm
Contact patcheach sack ≥ 0.012 m² flat on the floor0.0352 / 0.0988 / 0.1168 m²
Settled bellywidest station ≤ 0.42 of the body from its base0.251 / 0.280 / 0.316
+

Real-world size: the tall sack stands 0.64 m and is 0.47 m across the belly — a 50 kg grain sack. The outer AABB is the three sacks themselves, with no appendage above or beside them, so the AABB gate already holds the bodies to their stated size.

+

Surface

+
  • Weave. Hessian is two perpendicular sine gratings on a second UV layer, ClothCo: metres round the sack from its front, and metres up it. The weave follows the cloth whichever way up the sack lies. A 6 mm period aliased into moiré at hero distance, so the period is 12 mm with a soft bump.
  • Stripes. Stripes are the old feed-sack marking, centred on each sack's front face (the broad face a viewer sees): one broad blue band on the tall sack, three thin red ones on the lying sack, none on the third. Each sack also has its own tone.
  • Twine. Twine is paler and yellower than the hessian, so the tie reads against the neck.
+

Conventions walked

+

Every convention in showcase/README.md, and whether it applies here.

+
ConventionAppliesHow
Deterministic, budgets declared, assertions recomputeyesno RNG at all; every value above is read off the mesh
Falsifier fails the budget it targetsyestable below, proven on all three binaries
Hygiene incl. cross-shell coplanaryesexit 15. A twine face parallel to the cloth it hugs, one column over, first shared its plane (2 pairs); the turns now sit a quarter step off the body's columns
Named supportsyesevery sack grounded (--float-sack)
Wrappers follow the host's profileyesthe tie is built on the host's own pleated section
Band hooped, never flush / seat conformanceyestie bite banded (--loose-tie)
One connected assembly / carried parts biteyessacks press a banded depth into one another (--part-sacks)
A vessel has a base and a bellyyessettled belly low (--high-belly) and the contact patch (--round-bottom)
Plumb and real-world sizepartlya slumped sack is not plumb by design; size is held by the AABB, which is the bodies themselves
Shading is part of the modelyescloth and twine smooth-shaded: nothing on a sack is faceted in life
One substance, one slotyesburlap, twine
Rope is laid, not pipedyesthree-lobed 6-vertex twine section, turned per ring
Two identical sections stacked share planesyesthe two tie turns are half a step apart
Identical parts read as CGyeseach sack has its own height, width, depth, pleat phase, tone and stripe
Sort bmesh operator inputsn/ano bevel or other set-fed operator is run
Edge treatment: no right anglesn/ano boxes; every surface is lofted cloth or twine
Bake texels per UV celln/anine UV islands, one per shell, each a ninth of the sheet
Level on the stage; stage 60 myesturned about Z only; 60 m floor and wall
Keep a falsifier's envelope stillyesworst deltas: --high-belly −16.2 mm Y, --part-sacks +15.5 mm Y, against 20 mm
Timber, iron, masonry, scatter, fixtures, roofs, ringsnothe piece has none of these
+

Falsifiers

+

Each breaks one pipeline stage so a named budget fails. All nine were run on 4.5.11, 5.1.2 and 5.2.1 and exited the same declared code on all three.

+
FlagTarget budgetBreaksExit
--skip-decimateLOD1 ratiodrops the DECIMATE modifiers, LOD1 ratio goes to 1.00009
--stray-vertmesh hygieneadds one loose vertex inside the pile15
--lift-zgrounded zminlifts the whole mesh 50 mm16
--float-sacknamed supportsfloats the lying sack 12 mm; the standing ones still ground the AABB16
--slip-tietie in the waistslides every tie 50 mm up onto the crown, still seated on it; margin −13.4 to −44.6 mm17
--loose-tietie seatsizes each turn 6 mm wider; shallowest turn −1.67 mm18
--part-sackspress between sacksstops the lying sack 4 mm short of the one it leans on18
--round-bottomcontact patchsquashes the bases round instead of flat; 0 m² on all three19
--high-bellysettled bellymoves the widest station up to 0.52 of the body, same widths; 0.535–0.58619
+

Exit codes

+

File-local and sequential. 9 is a valid check code. 1 is the FATAL wrapper — a crash, never a named check.

+
CodeMeaning
0Success
1Uncaught exception (FATAL wrapper)
2argparse / usage
3Mesh did not build, or has no UV layer
4Base triangle count outside band
5Material slots, or a material's face floor
6UVs outside 0..1
7UV AABB overlap above tolerance
8Outer AABB off declared size
9LOD1 or LOD2 ratio outside band (--skip-decimate)
10Framing gate (examples/gallery_framing.py, render path only)
11Collider triangles above ceiling
12Normal bake failed or produced no image data
13glTF export missing or empty
14--output produced no file
15Mesh hygiene (--stray-vert)
16Grounded zmin, or a sack floating (--lift-z, --float-sack)
17A tie not in its neck's waist (--slip-tie)
18Tie seat, or press between sacks (--loose-tie, --part-sacks)
19Contact patch or settled belly (--round-bottom, --high-belly)
+

Run it

+
# Budget check, no render. ~1.9 s on 4.5, ~1.7 s on 5.1, ~2.1 s on 5.2.
+blender --background --python grain_sacks.py --
+
+# Falsifier: the sacks stop settling onto flat bases. Must exit 19.
+blender --background --python grain_sacks.py -- --round-bottom
+
+# Falsifier: every tie rides up onto its crown. Must exit 17.
+blender --background --python grain_sacks.py -- --slip-tie
+
+# Render the gallery still (EEVEE; --engine cycles on a GPU-less host).
+blender --background --python grain_sacks.py -- --output sacks.webp
+

Smoke runs the check-only path. It does not pass --output or any falsifier.

+

Cross-version measurements

+
Value4.5.115.1.25.2.1
Base triangles864086408640
LOD1 / LOD2 tris4320 / 1900samesame
Face counts (cloth / twine)3264 / 1152samesame
Outer AABB0.9139 × 0.9054 × 0.6360samesame
Collider tris376376376
Contact patches (m²)0.0352 / 0.0988 / 0.1168samesame
glTF bytesidenticalidenticalidentical
Check wall-clock~1.9 s~1.7 s~2.1 s
+

DECIMATE COLLAPSE is the usual cross-version suspect. Here it produces identical LOD counts on all three binaries; the gate is still a ratio band, not an exact count.

+
+
+

Source

+
+ showcase/grain-sacks/grain_sacks.py + View on GitHub → +
+
"""Game-ready grain sacks — a showcase piece, not an example.
+
+Asserts budget conformance of a procedural pile of three burlap grain
+sacks: two standing, one lying slumped against them, each gathered at the
+neck under a two-turn twine tie. Carried through UVs, two materials
+(burlap, twine), a high-to-low normal bake, an LOD chain, a compound
+convex collider, and a Unity glTF export.
+
+The budget that matters here is the one a sack can fail invisibly: a
+sack full of grain settles, so it rests on a flat contact patch, not on
+a point. An egg-bottomed sack still touches the floor, so the grounded
+zmin gate passes it; only the patch area, summed from the faces that lie
+flat on the floor, knows the difference.
+
+Budgets are declared below and recomputed from the generated result.
+They are not API-contract witnesses. Each falsifier violates one named
+budget: ``--skip-decimate`` the LOD-ratio band, ``--stray-vert`` mesh
+hygiene, ``--lift-z`` grounded zmin, ``--float-sack`` the named
+supports, ``--slip-tie`` the tie-at-waist fit, ``--loose-tie`` the tie
+seat, ``--part-sacks`` the press between sacks, ``--round-bottom`` the
+contact patch, ``--high-belly`` the settled belly.
+
+No randomness: every lump, pleat and tone is a closed-form term of the
+sack's own parameters. DECIMATE COLLAPSE triangle counts are not
+byte-identical across Blender versions — the LOD gate is a ratio band.
+
+    blender --background --python grain_sacks.py --
+    blender --background --python grain_sacks.py -- --round-bottom
+    blender --background --python grain_sacks.py -- --output sacks.png
+"""
+import argparse
+import math
+import os
+import sys
+import tempfile
+import traceback
+
+import bmesh
+import bpy
+from mathutils import Matrix, Vector
+from mathutils.bvhtree import BVHTree
+from mathutils.kdtree import KDTree
+
+_REPO = os.path.abspath(
+    os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir)
+)
+sys.path.insert(0, os.path.join(_REPO, "examples"))
+sys.dont_write_bytecode = True
+import gallery_framing  # noqa: E402
+
+# Three sacks: two stood up, one laid down in front. Heights are the
+# unsettled body length; W is the belly half-width, D the depth ratio of
+# the flattened sack section (two sewn panels, not a tube).
+SACKS = (
+    {"name": "A", "at": (-0.24, 0.10), "yaw": 16.0, "H": 0.80, "W": 0.235,
+     "D": 0.70, "lying": False, "stripe": 1, "front": 0.75, "phase": 0.0, "tone": 0.56},
+    {"name": "B", "at": (0.30, 0.16), "yaw": -28.0, "H": 0.68, "W": 0.250,
+     "D": 0.74, "lying": False, "stripe": 0, "front": 0.75, "phase": 0.9, "tone": 0.44},
+    {"name": "C", "at": (0.04, -0.52), "yaw": 72.0, "H": 0.82, "W": 0.235,
+     "D": 0.66, "lying": True, "stripe": 2, "front": 0.25, "phase": 2.1, "tone": 0.50},
+)
+# Order in which sacks are slid into contact: each one presses the sack
+# named here, along the line between their starting centres.
+PRESS_INTO = {"B": "A", "C": "A"}
+
+SEG = 32
+# Body profile: (height fraction, radius fraction). Round bottom, belly
+# low where the grain settles, a shoulder, the gathered neck under the
+# tie, and the pleated crown above it closing to a pole.
+PROFILE = (
+    (0.000, 0.00), (0.020, 0.52), (0.060, 0.80), (0.140, 0.96), (0.260, 1.00),
+    (0.400, 0.97), (0.520, 0.88), (0.610, 0.68), (0.680, 0.36), (0.720, 0.19),
+    (0.750, 0.21), (0.800, 0.33), (0.840, 0.30), (0.870, 0.00),
+)
+# --high-belly: same widths, the widest ring moved up to 0.52 of the body.
+HIGH_BELLY_PROFILE = (
+    (0.000, 0.00), (0.020, 0.50), (0.060, 0.72), (0.140, 0.82), (0.260, 0.90),
+    (0.400, 0.97), (0.520, 1.00), (0.610, 0.78), (0.680, 0.36), (0.720, 0.19),
+    (0.750, 0.21), (0.800, 0.33), (0.840, 0.30), (0.870, 0.00),
+)
+BODY_RINGS = 30
+NECK_T = 0.720
+# Pleats gather where the tie cinches: none on the belly, deep on the
+# crown, shallow under the tie itself so the tie has a round seat.
+PLEATS = 11
+PLEAT_SHOULDER = 0.09
+PLEAT_TIE = 0.025
+PLEAT_CROWN = 0.20
+LUMP = 0.035
+
+# Twine tie: two turns hooped onto the neck, the second half a vertex
+# step round from the first so the two are not one section stacked.
+TIE_R = 0.0065
+TIE_PIPE = 6
+TIE_LOBE = 0.12
+TIE_BITE = 0.003
+TIE_PITCH = 0.012
+LOOSE_TIE_BITE = -0.003
+SLIP_TIE = 0.050
+
+# Settling: the body is sunk SINK below the floor and everything under
+# SETTLE_C is folded up onto it, flat where the grain presses, and pushed
+# outward so the squashed base bulges.
+SINK = 0.060
+SETTLE_C = 0.030
+SPREAD = 0.55
+LYING_SQUASH = 0.84
+# Sacks press into each other by this much, found by sliding each one in
+# along the line between centres until its deepest vertex inside the
+# other reaches it.
+PRESS = 0.012
+PART_GAP = -0.004
+
+BBOX_TOL = 0.020
+OUTER_SIZE = (0.914, 0.905, 0.636)
+
+BASE_TRIS_MIN = 8000
+BASE_TRIS_MAX = 9300
+LOD1_RATIO_MIN = 0.32
+LOD1_RATIO_MAX = 0.62
+LOD2_RATIO_MIN = 0.10
+LOD2_RATIO_MAX = 0.35
+LOD1_TARGET = 0.50
+LOD2_TARGET = 0.22
+MATERIAL_COUNT = 2
+CLOTH_FACES_MIN = 2600
+TWINE_FACES_MIN = 900
+UV_EPS = 1e-4
+UV_OVERLAP_MAX = 1e-5
+COLLIDER_TRIS_MAX = 450
+COLLIDER_STRIDE = 3
+BAKE_RES = 256
+CAGE_EXTRUSION = 0.01
+ZMIN_EPS = 1e-4
+DOUBLES_EPS = 1e-5
+AREA_EPS = 1e-10
+COPLANAR_NORMAL_EPS = 1e-4
+COPLANAR_PLANE_EPS = 1e-4
+COPLANAR_CENTRE_MAX = 0.05
+LIFT_Z = 0.05
+FLOAT_SACK = 0.012
+SUPPORT_Z_MAX = 1e-3
+# Tie at the waist: the host under the tie is narrower than the host
+# WAIST_PROBE above and below it, by at least WAIST_MARGIN. A tie that is
+# not in a waist slides off.
+WAIST_PROBE = 0.040
+WAIST_BAND = 0.012
+WAIST_MARGIN = 0.008
+# Tie seat: deepest twine vertex inside the cloth, per turn.
+TIE_SEAT_MIN = 0.0010
+TIE_SEAT_MAX = 0.0060
+# Press: each slid sack's deepest vertex inside the sack it leans on.
+PRESS_MIN = 0.006
+PRESS_MAX = 0.020
+# Contact patch: flat-on-floor area under each sack.
+PATCH_MIN = 0.012
+# Settled belly: the widest station in the lower part of the body.
+BELLY_MAX = 0.42
+
+CLOTH_IDX = 0
+# Weave period on the cloth. 6 mm aliased into moire at hero distance.
+WEAVE = 0.012
+TWINE_IDX = 1
+
+
+def eevee_engine_id():
+    """EEVEE id: 'BLENDER_EEVEE' on 5.0+, 'BLENDER_EEVEE_NEXT' on 4.2-4.5."""
+    return "BLENDER_EEVEE" if bpy.app.version >= (5, 0, 0) else "BLENDER_EEVEE_NEXT"
+
+
+def fail(msg, code):
+    print(f"FAIL[{code}]: {msg}", file=sys.stderr)
+    return code
+
+
+def triangle_count(mesh):
+    mesh.calc_loop_triangles()
+    return len(mesh.loop_triangles)
+
+
+def evaluated_triangle_count(obj):
+    deps = bpy.context.evaluated_depsgraph_get()
+    ev = obj.evaluated_get(deps)
+    mesh = ev.to_mesh()
+    try:
+        mesh.calc_loop_triangles()
+        return len(mesh.loop_triangles)
+    finally:
+        ev.to_mesh_clear()
+
+
+# --- profile ----------------------------------------------------------------
+
+
+def pchip(knots, t):
+    """Monotone cubic (Fritsch-Carlson) through ``knots`` at ``t``.
+
+    Smooth through the knots without the overshoot a Catmull-Rom spline
+    puts into the neck, which would pinch the cloth below the tie.
+    """
+    xs = [k[0] for k in knots]
+    ys = [k[1] for k in knots]
+    n = len(xs)
+    h = [xs[i + 1] - xs[i] for i in range(n - 1)]
+    d = [(ys[i + 1] - ys[i]) / h[i] for i in range(n - 1)]
+    m = [0.0] * n
+    m[0], m[-1] = d[0], d[-1]
+    for i in range(1, n - 1):
+        if d[i - 1] * d[i] <= 0.0:
+            m[i] = 0.0
+        else:
+            w1 = 2.0 * h[i] + h[i - 1]
+            w2 = h[i] + 2.0 * h[i - 1]
+            m[i] = (w1 + w2) / (w1 / d[i - 1] + w2 / d[i])
+    t = min(max(t, xs[0]), xs[-1])
+    i = min(n - 2, max(0, next((j for j in range(n - 1) if t <= xs[j + 1]), n - 2)))
+    s = (t - xs[i]) / h[i]
+    h00 = (1 + 2 * s) * (1 - s) ** 2
+    h10 = s * (1 - s) ** 2
+    h01 = s * s * (3 - 2 * s)
+    h11 = s * s * (s - 1)
+    return h00 * ys[i] + h10 * h[i] * m[i] + h01 * ys[i + 1] + h11 * h[i] * m[i + 1]
+
+
+def pleat_amp(t):
+    """Pleat depth along the body: nothing on the belly, gathered at the neck."""
+    if t < 0.52:
+        return 0.0
+    if t < 0.68:
+        return PLEAT_SHOULDER * (t - 0.52) / 0.16
+    if t < NECK_T:
+        return PLEAT_SHOULDER + (PLEAT_TIE - PLEAT_SHOULDER) * (t - 0.68) / (NECK_T - 0.68)
+    if t < 0.78:
+        return PLEAT_TIE + (PLEAT_CROWN - PLEAT_TIE) * (t - NECK_T) / (0.78 - NECK_T)
+    return PLEAT_CROWN
+
+
+def section_point(sack, prof, t, a):
+    """Body surface at height fraction ``t`` and section angle ``a``, local frame."""
+    f = pchip(prof, t)
+    ratio = sack["D"] + (1.0 - sack["D"]) * (1.0 - min(1.0, f))
+    m = 1.0 + pleat_amp(t) * math.cos(PLEATS * a + sack["phase"])
+    # Grain lumps: low-order, closed-form, fading out toward the neck.
+    m += LUMP * max(0.0, 1.0 - t / 0.6) * math.cos(2.0 * a + 3.0 * t + sack["phase"])
+    r = sack["W"] * f * m
+    return Vector((r * math.cos(a), r * ratio * math.sin(a), t * sack["H"]))
+
+
+# --- parts ------------------------------------------------------------------
+
+
+class Part:
+    """Loose geometry for one shell: verts, faces, per-corner UVs, metadata."""
+
+    def __init__(self, kind, sack, mat):
+        self.kind = kind
+        self.sack = sack
+        self.mat = mat
+        self.verts = []
+        self.faces = []
+        self.uvs = []
+        self.cloth = []
+
+    def add(self, co):
+        self.verts.append(Vector(co))
+        return len(self.verts) - 1
+
+
+def grid_faces(part, rings, s_vals, n, poles=None, closed=False):
+    """Quads between consecutive rings (and pole fans), with strip UVs.
+
+    ``poles`` maps 'start'/'end' to (vertex index, s) for a fan to a pole;
+    the pole's UV sits half a column over, so every fan triangle has its
+    own UV rectangle and nothing overlaps.
+    """
+    m = len(rings)
+    spans = m if closed else m - 1
+    for k in range(spans):
+        a, b = rings[k], rings[(k + 1) % m]
+        s0, s1 = s_vals[k], s_vals[k + 1]
+        for i in range(n):
+            j = (i + 1) % n
+            part.faces.append((a[i], a[j], b[j], b[i]))
+            part.uvs.append(((s0, i / n), (s0, (i + 1) / n), (s1, (i + 1) / n), (s1, i / n)))
+    for key, ring, s_ring in (("start", rings[0], s_vals[0]), ("end", rings[-1], s_vals[m - 1])):
+        if not poles or key not in poles:
+            continue
+        pole, s_pole = poles[key]
+        for i in range(n):
+            j = (i + 1) % n
+            # Wound outward: the base fan runs the other way round from the crown's.
+            if key == "start":
+                part.faces.append((pole, ring[j], ring[i]))
+                part.uvs.append(((s_pole, (i + 0.5) / n), (s_ring, (i + 1) / n), (s_ring, i / n)))
+            else:
+                part.faces.append((pole, ring[i], ring[j]))
+                part.uvs.append(((s_pole, (i + 0.5) / n), (s_ring, i / n), (s_ring, (i + 1) / n)))
+
+
+def body_part(sack, prof):
+    """The sack body: a lofted, pleated, lumpy bag closed at both poles."""
+    part = Part("body", sack["name"], CLOTH_IDX)
+    top = prof[-1][0]
+    ts = [0.012 + (top - 0.024) * k / (BODY_RINGS - 1) for k in range(BODY_RINGS)]
+    ts += [NECK_T + d * TIE_PITCH / sack["H"] for d in (-0.5, 0.0, 0.5)]
+    ts = sorted(set(round(t, 6) for t in ts))
+    rings = []
+    for t in ts:
+        rings.append([part.add(section_point(sack, prof, t, 2.0 * math.pi * i / SEG))
+                      for i in range(SEG)])
+    bottom = part.add((0.0, 0.0, 0.0))
+    crown = part.add((0.0, 0.0, top * sack["H"]))
+    s = [t * sack["H"] for t in ts]
+    grid_faces(part, rings, s, SEG, poles={"start": (bottom, 0.0),
+                                           "end": (crown, top * sack["H"])})
+    # Cloth coordinates for the weave and stripes: metres round the sack
+    # and metres up it, read by the shader so the weave follows the cloth
+    # whatever way up the sack lies.
+    # Round the sack is measured from its front (the broad face a viewer
+    # sees: -Y stood up, +Y laid down), so stripes centre on it.
+    circ = 2.0 * math.pi * sack["W"] * (1.0 + sack["D"]) / 2.0
+
+    def around(v):
+        return ((v - sack["front"] + 0.5) % 1.0 - 0.5) * circ
+
+    part.cloth = []
+    for corner_uvs in part.uvs:
+        vs = [v for _u, v in corner_uvs]
+        # A face straddling the back seam keeps its corners on one side.
+        ref = around(vs[0])
+        row = []
+        for u, v in corner_uvs:
+            a = around(v)
+            if abs(a - ref) > 0.5 * circ:
+                a += circ if a < ref else -circ
+            row.append((a, u))
+        part.cloth.append(tuple(row))
+    return part
+
+
+def ring_offsets(t, side, r, n, lobe, idx, angle0=0.0):
+    """Section offsets for ring ``idx``: a lobed circle, turned per ring."""
+    s = side - t * side.dot(t)
+    s.normalize()
+    up = t.cross(s)
+    out = []
+    for k in range(n):
+        a = 2.0 * math.pi * k / n + angle0
+        rr = r * (1.0 + lobe * math.cos(3.0 * a - 2.0 * math.pi * 3.0 * idx / n))
+        out.append(s * (rr * math.cos(a)) + up * (rr * math.sin(a)))
+    return out
+
+
+def tie_part(sack, prof, t_station, bite, angle0):
+    """One twine turn hooped onto the neck at ``t_station``.
+
+    The centreline follows the host's own section at that station — every
+    body vertex of that ring, pushed out along its radial by the twine
+    radius less the bite — so the turn hugs the pleats rather than a
+    circle that stands proud of the valleys and sinks into the crests.
+    """
+    part = Part("tie", sack["name"], TWINE_IDX)
+    pts = []
+    for i in range(SEG):
+        p = section_point(sack, prof, t_station, 2.0 * math.pi * i / SEG)
+        radial = Vector((p.x, p.y, 0.0))
+        pts.append(p + radial.normalized() * (TIE_R - bite))
+    m = len(pts)
+    tans = [(pts[(i + 1) % m] - pts[(i - 1) % m]).normalized() for i in range(m)]
+    side = Vector((0.0, 0.0, 1.0))
+    rings = []
+    for i, (p, t) in enumerate(zip(pts, tans)):
+        rings.append([part.add(p + o) for o in ring_offsets(
+            t, side, TIE_R, TIE_PIPE, TIE_LOBE, i, angle0)])
+    s = [0.0]
+    for i in range(1, m + 1):
+        s.append(s[-1] + (pts[i % m] - pts[i - 1]).length)
+    grid_faces(part, rings, s, TIE_PIPE, closed=True)
+    part.cloth = [tuple((0.0, 0.0) for _ in uv) for uv in part.uvs]
+    return part
+
+
+def orient(parts, sack):
+    """Stand or lay the sack, turn it by its yaw; returns its axis in world."""
+    rot = Matrix.Identity(3)
+    if sack["lying"]:
+        # Lay it on its flat side: the section's narrow axis becomes Z.
+        rot = Matrix.Rotation(math.radians(90.0), 3, "X") @ rot
+        squash = Matrix.Diagonal((1.0, 1.0, LYING_SQUASH))
+        rot = squash @ rot
+    rot = Matrix.Rotation(math.radians(sack["yaw"]), 3, "Z") @ rot
+    for part in parts:
+        part.verts = [rot @ v for v in part.verts]
+    axis = (rot @ Vector((0.0, 0.0, 1.0))).normalized()
+    return axis
+
+
+def settle(parts, axis, round_bottom=False):
+    """Sink the sack SINK into the floor and fold everything under SETTLE_C up.
+
+    Below SETTLE_C the height is remapped by g(u) = ((u + 1) / 2)^2 on
+    u = z / SETTLE_C, clamped to 0 below u = -1: C1 at the fold, and
+    exactly flat where the grain presses the floor. The squashed height
+    goes outward, away from the sack's axis, so the base bulges.
+
+    ``round_bottom`` is the falsifier: the same sink and the same fold
+    height, but a linear squash that keeps the base round, touching the
+    floor at one point.
+    """
+    zmin = min(v.z for p in parts for v in p.verts)
+    shift = -zmin - SINK
+    centre = sum((v for v in parts[0].verts), Vector()) / len(parts[0].verts)
+    for part in parts:
+        out = []
+        for v in part.verts:
+            z = v.z + shift
+            if z >= SETTLE_C:
+                out.append(Vector((v.x, v.y, z)))
+                continue
+            if round_bottom:
+                z2 = (z + SINK) * SETTLE_C / (SETTLE_C + SINK)
+                out.append(Vector((v.x, v.y, z2)))
+                continue
+            u = z / SETTLE_C
+            z2 = 0.0 if u <= -1.0 else SETTLE_C * ((u + 1.0) / 2.0) ** 2
+            w = Vector((v.x, v.y, z)) - centre
+            w = w - axis * w.dot(axis)
+            w.z = 0.0
+            if w.length > 1e-9:
+                w.normalize()
+            out.append(Vector((v.x, v.y, z2)) + w * (SPREAD * (z2 - z)))
+        part.verts = out
+
+
+def translate(parts, d):
+    for part in parts:
+        part.verts = [v + d for v in part.verts]
+
+
+def body_tree(part):
+    return BVHTree.FromPolygons(part.verts, part.faces)
+
+
+def inside_depth(tree, pts):
+    """Deepest point of ``pts`` inside ``tree``'s surface; negative is a gap."""
+    best = -99.0
+    for p in pts:
+        loc, nrm, _i, dist = tree.find_nearest(p)
+        if loc is None:
+            continue
+        d = dist if (p - loc).dot(nrm) < 0.0 else -dist
+        best = max(best, d)
+    return best
+
+
+def slide_into(moving, fixed, target):
+    """Slide ``moving`` along the line to ``fixed`` until it presses ``target``.
+
+    Bisection on the offset, measuring the deepest vertex of the moving body
+    inside the fixed one after each move. The press is then exact whatever
+    the lumps and pleats do where the two meet.
+    """
+    body_m, body_f = moving[0], fixed[0]
+    cm = sum(body_m.verts, Vector()) / len(body_m.verts)
+    cf = sum(body_f.verts, Vector()) / len(body_f.verts)
+    d = Vector((cf.x - cm.x, cf.y - cm.y, 0.0)).normalized()
+    tree = body_tree(body_f)
+    base = [v.copy() for v in body_m.verts]
+
+    def depth(off):
+        pts = [v + d * off for v in base]
+        return inside_depth(tree, pts)
+
+    lo, hi = -0.40, 0.60
+    for _ in range(40):
+        mid = 0.5 * (lo + hi)
+        if depth(mid) > target:
+            hi = mid
+        else:
+            lo = mid
+    translate(moving, d * (0.5 * (lo + hi)))
+
+
+def build_parts(
+    slip_tie=False,
+    loose_tie=False,
+    part_sacks=False,
+    round_bottom=False,
+    high_belly=False,
+    float_sack=False,
+):
+    """Every shell of the pile, placed, settled and pressed together."""
+    bite = LOOSE_TIE_BITE if loose_tie else TIE_BITE
+    prof = HIGH_BELLY_PROFILE if high_belly else PROFILE
+    groups = {}
+    axes = {}
+    for sack in SACKS:
+        parts = [body_part(sack, prof)]
+        t0 = NECK_T + (SLIP_TIE / sack["H"] if slip_tie else 0.0)
+        for k, dt in enumerate((-0.5, 0.5)):
+            # A quarter step off the body's columns, and each turn half a
+            # step off the other: a twine face parallel to the cloth face it
+            # hugs, one column over, shared its plane.
+            parts.append(tie_part(sack, prof, t0 + dt * TIE_PITCH / sack["H"], bite,
+                                  (0.5 + k) * math.pi / TIE_PIPE))
+        axis = orient(parts, sack)
+        settle(parts, axis, round_bottom=round_bottom)
+        body = parts[0]
+        c = sum(body.verts, Vector()) / len(body.verts)
+        translate(parts, Vector((sack["at"][0] - c.x, sack["at"][1] - c.y, 0.0)))
+        groups[sack["name"]] = parts
+        axes[sack["name"]] = axis
+    for name, onto in PRESS_INTO.items():
+        target = PART_GAP if (part_sacks and name == "C") else PRESS
+        slide_into(groups[name], groups[onto], target)
+    if float_sack:
+        translate(groups["C"], Vector((0.0, 0.0, FLOAT_SACK)))
+    return groups, axes
+
+
+def pack_uvs(bm, islands, margin=0.08):
+    """One grid cell per UV island; each island normalised into its cell."""
+    uv = bm.loops.layers.uv.active
+    cols = max(1, math.ceil(math.sqrt(len(islands))))
+    rows = max(1, math.ceil(len(islands) / cols))
+    cell_w, cell_h = 1.0 / cols, 1.0 / rows
+    pad_u, pad_v = margin * cell_w * 0.5, margin * cell_h * 0.5
+    for idx, faces in enumerate(islands):
+        coords = [c for f in faces for c in (loop[uv].uv.copy() for loop in f.loops)]
+        minx = min(c.x for c in coords)
+        maxx = max(c.x for c in coords)
+        miny = min(c.y for c in coords)
+        maxy = max(c.y for c in coords)
+        dx, dy = max(maxx - minx, 1e-8), max(maxy - miny, 1e-8)
+        ou = (idx % cols) * cell_w + pad_u
+        ov = (idx // cols) * cell_h + pad_v
+        for f in faces:
+            for loop in f.loops:
+                c = loop[uv].uv
+                loop[uv].uv = (ou + (c.x - minx) / dx * (cell_w - 2 * pad_u),
+                               ov + (c.y - miny) / dy * (cell_h - 2 * pad_v))
+
+
+def build_sacks_mesh(name, stray_vert=False, **flags):
+    groups, _axes = build_parts(**flags)
+    bm = bmesh.new()
+    try:
+        uv = bm.loops.layers.uv.new("UVMap")
+        cloth = bm.loops.layers.uv.new("ClothCo")
+        islands = []
+        for sack in SACKS:
+            for part in groups[sack["name"]]:
+                vs = [bm.verts.new(v) for v in part.verts]
+                faces = []
+                for f, uvs, cls in zip(part.faces, part.uvs, part.cloth):
+                    face = bm.faces.new([vs[i] for i in f])
+                    face.material_index = part.mat
+                    face.smooth = True
+                    for loop, u, c in zip(face.loops, uvs, cls):
+                        loop[uv].uv = u
+                        loop[cloth].uv = c
+                    faces.append(face)
+                islands.append(faces)
+        bmesh.ops.recalc_face_normals(bm, faces=list(bm.faces))
+        pack_uvs(bm, islands)
+        if stray_vert:
+            bm.verts.new((0.0, 0.0, 0.3))
+        me = bpy.data.meshes.new(name)
+        bm.to_mesh(me)
+        me.update()
+    finally:
+        bm.free()
+    uvl = me.uv_layers.get("UVMap")
+    if uvl is not None:
+        me.uv_layers.active = uvl
+    paint_sacks(me)
+    obj = bpy.data.objects.new(name, me)
+    bpy.context.collection.objects.link(obj)
+    return obj
+
+
+def paint_sacks(me):
+    """``SackTone`` and ``Stripe`` face attributes from each sack's parameters."""
+    tone = [0.5] * len(me.polygons)
+    stripe = [0.0] * len(me.polygons)
+    owner = {}
+    # Each body takes the parameters of the sack placed nearest its centre;
+    # a tie keeps the neutral values, which the twine shader ignores.
+    for g in shells(me):
+        if len(g) <= 400:
+            continue
+        c = sum((me.vertices[i].co for i in g), Vector()) / len(g)
+        sack = min(SACKS, key=lambda s: (s["at"][0] - c.x) ** 2 + (s["at"][1] - c.y) ** 2)
+        for i in g:
+            owner[i] = (sack["tone"], float(sack["stripe"]))
+    for p in me.polygons:
+        t, s = owner.get(p.vertices[0], (0.5, 0.0))
+        tone[p.index] = t
+        stripe[p.index] = s
+    a = me.attributes.new("SackTone", "FLOAT", "FACE")
+    a.data.foreach_set("value", tone)
+    b = me.attributes.new("Stripe", "FLOAT", "FACE")
+    b.data.foreach_set("value", stripe)
+
+
+# --- surface ----------------------------------------------------------------
+
+
+def _sock(sockets, identifier):
+    return next(sk for sk in sockets if sk.identifier == identifier)
+
+
+def burlap_material(name):
+    """Hessian: a weave on the cloth's own coordinates, stripes, dust at the base."""
+    mat = bpy.data.materials.new(name)
+    mat.use_nodes = True
+    nt = mat.node_tree
+    bsdf = nt.nodes["Principled BSDF"]
+    co = nt.nodes.new("ShaderNodeUVMap")
+    co.uv_map = "ClothCo"
+    sep = nt.nodes.new("ShaderNodeSeparateXYZ")
+    nt.links.new(co.outputs["UV"], sep.inputs["Vector"])
+    # Weave: two perpendicular sine gratings on the cloth coordinates.
+    weave = []
+    for axis in ("X", "Y"):
+        mul = nt.nodes.new("ShaderNodeMath")
+        mul.operation = "MULTIPLY"
+        mul.inputs[1].default_value = 2.0 * math.pi / WEAVE
+        nt.links.new(sep.outputs[axis], mul.inputs[0])
+        sn = nt.nodes.new("ShaderNodeMath")
+        sn.operation = "SINE"
+        nt.links.new(mul.outputs["Value"], sn.inputs[0])
+        weave.append(sn)
+    prod = nt.nodes.new("ShaderNodeMath")
+    prod.operation = "MULTIPLY"
+    nt.links.new(weave[0].outputs["Value"], prod.inputs[0])
+    nt.links.new(weave[1].outputs["Value"], prod.inputs[1])
+    wv = nt.nodes.new("ShaderNodeMapRange")
+    wv.inputs["From Min"].default_value = -1.0
+    wv.inputs["To Min"].default_value = 0.90
+    wv.inputs["To Max"].default_value = 1.04
+    nt.links.new(prod.outputs["Value"], wv.inputs["Value"])
+    # Fibre mottling in object space.
+    tc = nt.nodes.new("ShaderNodeTexCoord")
+    noise = nt.nodes.new("ShaderNodeTexNoise")
+    noise.inputs["Scale"].default_value = 9.0
+    noise.inputs["Detail"].default_value = 6.0
+    nt.links.new(tc.outputs["Object"], noise.inputs["Vector"])
+    ramp = nt.nodes.new("ShaderNodeValToRGB")
+    ramp.color_ramp.elements[0].position = 0.30
+    ramp.color_ramp.elements[0].color = (0.30, 0.22, 0.13, 1.0)
+    ramp.color_ramp.elements[1].position = 0.75
+    ramp.color_ramp.elements[1].color = (0.52, 0.41, 0.26, 1.0)
+    nt.links.new(noise.outputs["Fac"], ramp.inputs["Fac"])
+    # Stripes down the front: Stripe 1 is one broad blue band, 2 is three
+    # thin red ones, 0 is plain. Centred on the cloth's front (u = 1/4 turn).
+    stripe = nt.nodes.new("ShaderNodeAttribute")
+    stripe.attribute_type = "GEOMETRY"
+    stripe.attribute_name = "Stripe"
+    circ_u = nt.nodes.new("ShaderNodeMath")
+    circ_u.operation = "PINGPONG"
+    circ_u.inputs[1].default_value = 0.09
+    shifted = nt.nodes.new("ShaderNodeMath")
+    shifted.operation = "SUBTRACT"
+    shifted.inputs[1].default_value = 0.0
+    nt.links.new(sep.outputs["X"], shifted.inputs[0])
+    absu = nt.nodes.new("ShaderNodeMath")
+    absu.operation = "ABSOLUTE"
+    nt.links.new(shifted.outputs["Value"], absu.inputs[0])
+    nt.links.new(absu.outputs["Value"], circ_u.inputs[0])
+    broad = nt.nodes.new("ShaderNodeMath")
+    broad.operation = "LESS_THAN"
+    broad.inputs[1].default_value = 0.028
+    nt.links.new(absu.outputs["Value"], broad.inputs[0])
+    thin = nt.nodes.new("ShaderNodeMath")
+    thin.operation = "LESS_THAN"
+    thin.inputs[1].default_value = 0.010
+    rep = nt.nodes.new("ShaderNodeMath")
+    rep.operation = "PINGPONG"
+    rep.inputs[1].default_value = 0.025
+    nt.links.new(absu.outputs["Value"], rep.inputs[0])
+    nt.links.new(rep.outputs["Value"], thin.inputs[0])
+    within = nt.nodes.new("ShaderNodeMath")
+    within.operation = "LESS_THAN"
+    within.inputs[1].default_value = 0.07
+    nt.links.new(absu.outputs["Value"], within.inputs[0])
+    thin_m = nt.nodes.new("ShaderNodeMath")
+    thin_m.operation = "MULTIPLY"
+    nt.links.new(thin.outputs["Value"], thin_m.inputs[0])
+    nt.links.new(within.outputs["Value"], thin_m.inputs[1])
+    is1 = nt.nodes.new("ShaderNodeMath")
+    is1.operation = "COMPARE"
+    is1.inputs[1].default_value = 1.0
+    is1.inputs[2].default_value = 0.1
+    nt.links.new(stripe.outputs["Fac"], is1.inputs[0])
+    is2 = nt.nodes.new("ShaderNodeMath")
+    is2.operation = "COMPARE"
+    is2.inputs[1].default_value = 2.0
+    is2.inputs[2].default_value = 0.1
+    nt.links.new(stripe.outputs["Fac"], is2.inputs[0])
+    m1 = nt.nodes.new("ShaderNodeMath")
+    m1.operation = "MULTIPLY"
+    nt.links.new(is1.outputs["Value"], m1.inputs[0])
+    nt.links.new(broad.outputs["Value"], m1.inputs[1])
+    m2 = nt.nodes.new("ShaderNodeMath")
+    m2.operation = "MULTIPLY"
+    nt.links.new(is2.outputs["Value"], m2.inputs[0])
+    nt.links.new(thin_m.outputs["Value"], m2.inputs[1])
+    # Stripes stop short of the neck and of the settled base.
+    band_lo = nt.nodes.new("ShaderNodeMath")
+    band_lo.operation = "GREATER_THAN"
+    band_lo.inputs[1].default_value = 0.10
+    nt.links.new(sep.outputs["Y"], band_lo.inputs[0])
+    band_hi = nt.nodes.new("ShaderNodeMath")
+    band_hi.operation = "LESS_THAN"
+    band_hi.inputs[1].default_value = 0.44
+    nt.links.new(sep.outputs["Y"], band_hi.inputs[0])
+    band = nt.nodes.new("ShaderNodeMath")
+    band.operation = "MULTIPLY"
+    nt.links.new(band_lo.outputs["Value"], band.inputs[0])
+    nt.links.new(band_hi.outputs["Value"], band.inputs[1])
+    col1 = nt.nodes.new("ShaderNodeMix")
+    col1.data_type = "RGBA"
+    nt.links.new(ramp.outputs["Color"], _sock(col1.inputs, "A_Color"))
+    _sock(col1.inputs, "B_Color").default_value = (0.07, 0.12, 0.26, 1.0)
+    f1 = nt.nodes.new("ShaderNodeMath")
+    f1.operation = "MULTIPLY"
+    nt.links.new(m1.outputs["Value"], f1.inputs[0])
+    nt.links.new(band.outputs["Value"], f1.inputs[1])
+    nt.links.new(f1.outputs["Value"], _sock(col1.inputs, "Factor_Float"))
+    col2 = nt.nodes.new("ShaderNodeMix")
+    col2.data_type = "RGBA"
+    nt.links.new(_sock(col1.outputs, "Result_Color"), _sock(col2.inputs, "A_Color"))
+    _sock(col2.inputs, "B_Color").default_value = (0.30, 0.06, 0.04, 1.0)
+    f2 = nt.nodes.new("ShaderNodeMath")
+    f2.operation = "MULTIPLY"
+    nt.links.new(m2.outputs["Value"], f2.inputs[0])
+    nt.links.new(band.outputs["Value"], f2.inputs[1])
+    nt.links.new(f2.outputs["Value"], _sock(col2.inputs, "Factor_Float"))
+    # Per-sack tone and the weave.
+    tone = nt.nodes.new("ShaderNodeAttribute")
+    tone.attribute_type = "GEOMETRY"
+    tone.attribute_name = "SackTone"
+    gain = nt.nodes.new("ShaderNodeMath")
+    gain.operation = "MULTIPLY_ADD"
+    gain.inputs[1].default_value = 0.8
+    gain.inputs[2].default_value = 0.6
+    nt.links.new(tone.outputs["Fac"], gain.inputs[0])
+    g2 = nt.nodes.new("ShaderNodeMath")
+    g2.operation = "MULTIPLY"
+    nt.links.new(gain.outputs["Value"], g2.inputs[0])
+    nt.links.new(wv.outputs["Result"], g2.inputs[1])
+    mix = nt.nodes.new("ShaderNodeMix")
+    mix.data_type = "RGBA"
+    mix.blend_type = "MULTIPLY"
+    _sock(mix.inputs, "Factor_Float").default_value = 1.0
+    nt.links.new(_sock(col2.outputs, "Result_Color"), _sock(mix.inputs, "A_Color"))
+    nt.links.new(g2.outputs["Value"], _sock(mix.inputs, "B_Color"))
+    nt.links.new(_sock(mix.outputs, "Result_Color"), bsdf.inputs["Base Color"])
+    bsdf.inputs["Roughness"].default_value = 0.92
+    bmp = nt.nodes.new("ShaderNodeBump")
+    bmp.inputs["Strength"].default_value = 0.15
+    bmp.inputs["Distance"].default_value = 0.0015
+    nt.links.new(prod.outputs["Value"], bmp.inputs["Height"])
+    nt.links.new(bmp.outputs["Normal"], bsdf.inputs["Normal"])
+    return mat
+
+
+def twine_material(name):
+    """Sisal twine: paler and yellower than the hessian, matte."""
+    mat = bpy.data.materials.new(name)
+    mat.use_nodes = True
+    nt = mat.node_tree
+    bsdf = nt.nodes["Principled BSDF"]
+    tc = nt.nodes.new("ShaderNodeTexCoord")
+    noise = nt.nodes.new("ShaderNodeTexNoise")
+    noise.inputs["Scale"].default_value = 120.0
+    noise.inputs["Detail"].default_value = 6.0
+    nt.links.new(tc.outputs["Object"], noise.inputs["Vector"])
+    ramp = nt.nodes.new("ShaderNodeValToRGB")
+    ramp.color_ramp.elements[0].color = (0.40, 0.33, 0.18, 1.0)
+    ramp.color_ramp.elements[1].color = (0.70, 0.60, 0.38, 1.0)
+    nt.links.new(noise.outputs["Fac"], ramp.inputs["Fac"])
+    nt.links.new(ramp.outputs["Color"], bsdf.inputs["Base Color"])
+    bsdf.inputs["Roughness"].default_value = 0.85
+    return mat
+
+
+def sack_materials():
+    return burlap_material("SackBurlap"), twine_material("SackTwine")
+
+
+def assign_slots(obj, cloth, twine):
+    mats = obj.data.materials
+    for i, mat in enumerate((cloth, twine)):
+        if i < len(mats):
+            mats[i] = mat
+        else:
+            mats.append(mat)
+
+
+# --- measurement ------------------------------------------------------------
+
+
+def world_bbox(obj):
+    corners = [obj.matrix_world @ Vector(c) for c in obj.bound_box]
+    xs = [c.x for c in corners]
+    ys = [c.y for c in corners]
+    zs = [c.z for c in corners]
+    return (min(xs), min(ys), min(zs), max(xs), max(ys), max(zs))
+
+
+def uv_stats(mesh):
+    uv = mesh.uv_layers.get("UVMap")
+    if uv is None:
+        return 0.0, 0.0, 1.0, 1.0, 0.0, 0
+    data = uv.data
+    us = [loop.uv[0] for loop in data]
+    vs = [loop.uv[1] for loop in data]
+    aabbs = []
+    for poly in mesh.polygons:
+        pu = [data[i].uv[0] for i in poly.loop_indices]
+        pv = [data[i].uv[1] for i in poly.loop_indices]
+        aabbs.append((min(pu), min(pv), max(pu), max(pv)))
+    span = max(1e-6, max(a[2] - a[0] for a in aabbs), max(a[3] - a[1] for a in aabbs))
+    buckets = {}
+    for i, a in enumerate(aabbs):
+        for c in range(int(a[0] // span), int(a[2] // span) + 1):
+            for r in range(int(a[1] // span), int(a[3] // span) + 1):
+                buckets.setdefault((c, r), []).append(i)
+    overlap = 0.0
+    seen = set()
+    for members in buckets.values():
+        for ii in range(len(members)):
+            for jj in range(ii + 1, len(members)):
+                i, j = members[ii], members[jj]
+                key = (i, j) if i < j else (j, i)
+                if key in seen:
+                    continue
+                seen.add(key)
+                a, b = aabbs[i], aabbs[j]
+                overlap += max(0.0, min(a[2], b[2]) - max(a[0], b[0])) * max(
+                    0.0, min(a[3], b[3]) - max(a[1], b[1]))
+    return min(us), min(vs), max(us), max(vs), overlap, len(aabbs)
+
+
+def face_area(me, poly):
+    idxs = poly.vertices
+    v0 = me.vertices[idxs[0]].co
+    area = 0.0
+    for i in range(1, len(idxs) - 1):
+        area += (me.vertices[idxs[i]].co - v0).cross(me.vertices[idxs[i + 1]].co - v0).length * 0.5
+    return area
+
+
+def hygiene_audit(me):
+    ngons = sum(1 for p in me.polygons if len(p.vertices) > 4)
+    zero_area = sum(1 for p in me.polygons if face_area(me, p) <= AREA_EPS)
+    bm = bmesh.new()
+    try:
+        bm.from_mesh(me)
+        loose_v = sum(1 for v in bm.verts if len(v.link_edges) == 0)
+        loose_e = sum(1 for e in bm.edges if len(e.link_faces) == 0)
+        nonman = sum(1 for e in bm.edges if not e.is_manifold)
+        ret = bmesh.ops.find_doubles(bm, verts=list(bm.verts), dist=DOUBLES_EPS)
+        doubles = len(ret.get("targetmap") or {})
+    finally:
+        bm.free()
+    return {"ngons": ngons, "loose_v": loose_v, "loose_e": loose_e,
+            "nonman": nonman, "zero_area": zero_area, "doubles": doubles}
+
+
+def shells(me):
+    neighbors = [[] for _ in range(len(me.vertices))]
+    for edge in me.edges:
+        a, b = edge.vertices
+        neighbors[a].append(b)
+        neighbors[b].append(a)
+    seen = [False] * len(me.vertices)
+    groups = []
+    for start in range(len(me.vertices)):
+        if seen[start]:
+            continue
+        seen[start] = True
+        stack = [start]
+        group = []
+        while stack:
+            cur = stack.pop()
+            group.append(cur)
+            for nxt in neighbors[cur]:
+                if not seen[nxt]:
+                    seen[nxt] = True
+                    stack.append(nxt)
+        groups.append(group)
+    return groups
+
+
+def zfight_pairs(me):
+    """Coplanar face pairs from *different shells* (copied from showcase/grindstone).
+
+    Every sack lays a flat patch on the floor, so the budget is what keeps
+    two sacks' patches from meeting on one plane side by side.
+    """
+    owner = {}
+    for si, g in enumerate(shells(me)):
+        for vi in g:
+            owner[vi] = si
+    faces = [(p.normal.copy(), p.center.copy(), owner.get(p.vertices[0], -1))
+             for p in me.polygons]
+    kd = KDTree(len(faces))
+    for i, (_n, c, _s) in enumerate(faces):
+        kd.insert(c, i)
+    kd.balance()
+    hits = 0
+    for i, (ni, ci, si) in enumerate(faces):
+        for _co, j, _d in kd.find_range(ci, COPLANAR_CENTRE_MAX):
+            if j <= i:
+                continue
+            nj, cj, sj = faces[j]
+            if si == sj:
+                continue
+            if abs(abs(ni.dot(nj)) - 1.0) > COPLANAR_NORMAL_EPS:
+                continue
+            if abs(ni.dot(cj - ci)) > COPLANAR_PLANE_EPS:
+                continue
+            hits += 1
+    return hits
+
+
+def shell_tree(me, group):
+    member = set(group)
+    polys = [list(p.vertices) for p in me.polygons if p.vertices[0] in member]
+    remap = {v: k for k, v in enumerate(group)}
+    return BVHTree.FromPolygons(
+        [me.vertices[i].co.copy() for i in group],
+        [[remap[i] for i in p] for p in polys],
+    )
+
+
+def _long_axis(pts):
+    c = sum(pts, Vector()) / len(pts)
+    cov = [[0.0] * 3 for _ in range(3)]
+    for p in pts:
+        d = p - c
+        for i in range(3):
+            for j in range(3):
+                cov[i][j] += d[i] * d[j]
+    v = Vector((0.3, 0.2, 1.0))
+    for _ in range(60):
+        w = Vector([sum(cov[i][j] * v[j] for j in range(3)) for i in range(3)])
+        if w.length < 1e-12:
+            break
+        v = w.normalized()
+    return c, v
+
+
+def classify(me):
+    """Bodies are the big cloth shells; each tie turn joins its nearest body."""
+    mats = {}
+    for p in me.polygons:
+        for i in p.vertices:
+            mats.setdefault(i, p.material_index)
+    bodies, ties, other = [], [], []
+    for g in shells(me):
+        pts = [me.vertices[i].co.copy() for i in g]
+        rec = {"g": g, "pts": pts, "c": sum(pts, Vector()) / len(pts)}
+        m = mats.get(g[0], -1)
+        if m == CLOTH_IDX and len(g) > 400:
+            bodies.append(rec)
+        elif m == TWINE_IDX:
+            ties.append(rec)
+        else:
+            other.append(rec)
+    for t in ties:
+        t["body"] = min(range(len(bodies)),
+                        key=lambda k: min((p - t["c"]).length for p in bodies[k]["pts"]))
+    return bodies, ties, other
+
+
+def sack_audit(me):
+    """Supports, tie waist and seat, press, contact patch and belly, per sack."""
+    bodies, ties, other = classify(me)
+    trees = [shell_tree(me, b["g"]) for b in bodies]
+    out = {"n_body": len(bodies), "n_tie": len(ties), "n_other": len(other)}
+    out["support_worst"] = max((min(p.z for p in b["pts"]) for b in bodies), default=99.0)
+
+    # Contact patch: faces lying flat on the floor under each body.
+    patch = []
+    for b in bodies:
+        member = set(b["g"])
+        area = 0.0
+        for p in me.polygons:
+            if p.vertices[0] in member and all(me.vertices[i].co.z <= 1e-6 for i in p.vertices):
+                area += face_area(me, p)
+        patch.append(area)
+    out["patch"] = [round(a, 5) for a in patch]
+    out["patch_min"] = min(patch, default=0.0)
+
+    # Each body's axis, oriented from its base to its tie.
+    waist, belly = [], []
+    for k, b in enumerate(bodies):
+        c, axis = _long_axis(b["pts"])
+        own = [t for t in ties if t["body"] == k]
+        if not own:
+            waist.append(-99.0)
+            continue
+        tc = sum((t["c"] for t in own), Vector()) / len(own)
+        if (tc - c).dot(axis) < 0.0:
+            axis = -axis
+        proj = [(p - c).dot(axis) for p in b["pts"]]
+        lo, hi = min(proj), max(proj)
+        bins = {}
+        for p, s in zip(b["pts"], proj):
+            r = ((p - c) - axis * s).length
+            bins.setdefault(round(s / 0.004), []).append(r)
+        stations = sorted((key * 0.004, sum(v) / len(v)) for key, v in bins.items() if len(v) >= 8)
+        widest = max(stations, key=lambda x: x[1])
+        belly.append((widest[0] - lo) / (hi - lo))
+        # Waist: host radius at the tie's station against the host radius
+        # WAIST_PROBE along the axis either side of it.
+        st = (tc - c).dot(axis)
+
+        def radius_at(s0):
+            rs = [((p - c) - axis * s).length for p, s in zip(b["pts"], proj)
+                  if abs(s - s0) <= WAIST_BAND]
+            return sum(rs) / len(rs) if rs else 0.0
+
+        waist.append(min(radius_at(st - WAIST_PROBE), radius_at(st + WAIST_PROBE))
+                     - radius_at(st))
+    out["waist"] = [round(x, 5) for x in waist]
+    out["waist_min"] = min(waist, default=-99.0)
+    out["belly_max"] = max(belly, default=99.0)
+    out["belly"] = [round(x, 3) for x in belly]
+
+    # Tie seat: deepest twine vertex inside its body, per turn.
+    seats = [inside_depth(trees[t["body"]], t["pts"]) for t in ties]
+    out["tie_min"] = min(seats, default=-99.0)
+    out["tie_max"] = max(seats, default=99.0)
+
+    # Press: each body's deepest vertex inside any other body.
+    press = []
+    for k, b in enumerate(bodies):
+        best = max((inside_depth(trees[j], b["pts"]) for j in range(len(bodies)) if j != k),
+                   default=-99.0)
+        press.append(best)
+    out["press"] = [round(x, 5) for x in press]
+    out["press_min"] = min(press, default=-99.0)
+    out["press_max"] = max(press, default=99.0)
+    return out
+
+
+def make_lod(obj, name, ratio, skip_decimate):
+    mesh = obj.data.copy()
+    lod = bpy.data.objects.new(name, mesh)
+    lod.matrix_world = obj.matrix_world.copy()
+    bpy.context.scene.collection.objects.link(lod)
+    if not skip_decimate and 0.0 < ratio < 1.0:
+        mod = lod.modifiers.new("DecimateBudget", "DECIMATE")
+        mod.decimate_type = "COLLAPSE"
+        mod.ratio = ratio
+    return lod
+
+
+def hull_collider(obj, name):
+    """Compound collider: one convex hull per sack body, every third ring, fourth column.
+
+    One hull over the pile would fill the wedge between the lying sack and
+    the standing ones; a hull per sack keeps it. Striding the rings keeps
+    each hull's triangle count down without leaving the silhouette.
+    """
+    me = obj.data
+    bodies, _ties, _other = classify(me)
+    mesh = bpy.data.meshes.new(name)
+    bm = bmesh.new()
+    try:
+        for b in bodies:
+            # Body vertices in build order are ring after ring of SEG, so
+            # stride rings and columns alike.
+            order = sorted(b["g"])
+            pts = [me.vertices[v].co.copy() for k, v in enumerate(order)
+                   if (k // SEG) % COLLIDER_STRIDE == 0 and (k % SEG) % 4 == 0]
+            tmp = bmesh.new()
+            try:
+                vs = [tmp.verts.new(p) for p in pts]
+                bmesh.ops.convex_hull(tmp, input=vs)
+                # Only the hull's own vertices: interior points have no face.
+                remap = {}
+                for f in tmp.faces:
+                    for v in f.verts:
+                        if v not in remap:
+                            remap[v] = bm.verts.new(v.co)
+                    bm.faces.new([remap[v] for v in f.verts])
+            finally:
+                tmp.free()
+        bm.to_mesh(mesh)
+        mesh.update()
+    finally:
+        bm.free()
+    col = bpy.data.objects.new(name, mesh)
+    bpy.context.collection.objects.link(col)
+    return col
+
+
+def setup_bake_image(obj, target_mat, size):
+    img = bpy.data.images.new("SackNrm", size, size, alpha=True, float_buffer=False)
+    img.colorspace_settings.name = "Non-Color"
+    nodes = target_mat.node_tree.nodes
+    tex = nodes.new("ShaderNodeTexImage")
+    tex.image = img
+    nodes.active = tex
+    tex.select = True
+    obj.active_material_index = CLOTH_IDX
+    return img, tex
+
+
+def bake_normal(high, low):
+    scene = bpy.context.scene
+    scene.render.engine = "CYCLES"
+    scene.cycles.device = "CPU"
+    scene.cycles.samples = 1
+    scene.cycles.use_denoising = False
+    for ob in bpy.context.view_layer.objects:
+        ob.select_set(False)
+    high.select_set(True)
+    low.select_set(True)
+    bpy.context.view_layer.objects.active = low
+    return bpy.ops.object.bake(
+        type="NORMAL", use_selected_to_active=True, cage_extrusion=CAGE_EXTRUSION,
+        use_cage=False, normal_space="TANGENT", margin=4,
+        margin_type="ADJACENT_FACES", use_clear=True, target="IMAGE_TEXTURES",
+        uv_layer="UVMap",
+    )
+
+
+def export_unity(path, objects):
+    for ob in bpy.context.view_layer.objects:
+        ob.select_set(False)
+    for ob in objects:
+        ob.select_set(True)
+    bpy.context.view_layer.objects.active = objects[0]
+    bpy.ops.export_scene.gltf(
+        filepath=path, use_selection=True, export_yup=True, export_apply=True,
+        export_draco_mesh_compression_enable=False, export_animations=False,
+    )
+
+
+def check(skip_decimate, lift_z=False, stray_vert=False, **flags):
+    bpy.ops.wm.read_factory_settings(use_empty=True)
+    nothing = (None,) * 5
+    low = build_sacks_mesh("SacksLow", stray_vert=stray_vert, **flags)
+    high = build_sacks_mesh("SacksHigh", **flags)
+    cloth, twine = sack_materials()
+    assign_slots(low, cloth, twine)
+    assign_slots(high, cloth, twine)
+    if lift_z:
+        for v in low.data.vertices:
+            v.co.z += LIFT_Z
+        low.data.update()
+        bpy.context.view_layer.update()
+    if len(low.data.polygons) < 6 or low.data.uv_layers.get("UVMap") is None:
+        return (fail("sack mesh did not build, or has no UV layer", 3),) + nothing
+
+    base_tris = triangle_count(low.data)
+    mats = [s for s in low.data.materials if s is not None]
+    nmat, distinct = len(mats), len({id(s) for s in mats})
+    idx_counts = {}
+    for poly in low.data.polygons:
+        idx_counts[poly.material_index] = idx_counts.get(poly.material_index, 0) + 1
+    u0, v0, u1, v1, overlap, nfaces = uv_stats(low.data)
+    bb = world_bbox(low)
+    size_x, size_y, size_z = bb[3] - bb[0], bb[4] - bb[1], bb[5] - bb[2]
+
+    img, tex = setup_bake_image(low, cloth, BAKE_RES)
+    bake_result = bake_normal(high, low)
+
+    lod1 = make_lod(low, "SacksLOD1", LOD1_TARGET, skip_decimate)
+    lod2 = make_lod(low, "SacksLOD2", LOD2_TARGET, skip_decimate)
+    bpy.context.view_layer.update()
+    lod1_tris = evaluated_triangle_count(lod1)
+    lod2_tris = evaluated_triangle_count(lod2)
+    r1 = lod1_tris / base_tris if base_tris else 0.0
+    r2 = lod2_tris / base_tris if base_tris else 0.0
+
+    collider = hull_collider(high, "SacksCollider")
+    col_tris = triangle_count(collider.data)
+
+    export_path = os.path.join(tempfile.gettempdir(), f"bdt_grain_sacks_{os.getpid()}.glb")
+    if os.path.exists(export_path):
+        os.remove(export_path)
+    export_unity(export_path, [low, collider])
+    export_size = os.path.getsize(export_path) if os.path.isfile(export_path) else 0
+    # Blender points TMPDIR at its own temp preference, which on a portable
+    # build is the working directory, so the export must not outlive this.
+    if os.path.isfile(export_path):
+        os.remove(export_path)
+
+    hyg = hygiene_audit(low.data)
+    zf = zfight_pairs(low.data)
+    sa = sack_audit(low.data)
+
+    print(f"blender={tuple(bpy.app.version)} skip_decimate={skip_decimate}")
+    print(f"measured mat_index_counts={dict(sorted(idx_counts.items()))}")
+    print(f"measured base_tris={base_tris} lod1_tris={lod1_tris} "
+          f"lod2_tris={lod2_tris} r1={r1:.4f} r2={r2:.4f}")
+    print(f"measured nmat={nmat} uv=({u0:.4f},{v0:.4f})-({u1:.4f},{v1:.4f}) "
+          f"overlap={overlap:.6f} nfaces={nfaces}")
+    print(f"measured bbox=({size_x:.4f},{size_y:.4f},{size_z:.4f}) "
+          f"outer={OUTER_SIZE} zmin={bb[2]:.5f}")
+    print(f"measured collider_tris={col_tris} bake={bake_result} "
+          f"bake_has_data={img.has_data} export_bytes={export_size}")
+    print(f"measured hygiene loose_v={hyg['loose_v']} loose_e={hyg['loose_e']} "
+          f"nonman={hyg['nonman']} zero_area={hyg['zero_area']} "
+          f"doubles={hyg['doubles']} ngons={hyg['ngons']} zfight={zf}")
+    print(f"measured sacks bodies={sa['n_body']} ties={sa['n_tie']} other={sa['n_other']} "
+          f"support_worst={sa['support_worst']:.5f} waist={sa['waist']} "
+          f"tie=({sa['tie_min']:.5f},{sa['tie_max']:.5f}) press={sa['press']} "
+          f"patch={sa['patch']} belly={sa['belly']}")
+
+    if not (BASE_TRIS_MIN <= base_tris <= BASE_TRIS_MAX):
+        return (fail(f"base tris {base_tris} not in [{BASE_TRIS_MIN}, {BASE_TRIS_MAX}]", 4),) + nothing
+    if nmat != MATERIAL_COUNT or distinct != MATERIAL_COUNT:
+        return (fail(f"material slots {nmat} distinct {distinct} != {MATERIAL_COUNT}", 5),) + nothing
+    if idx_counts.get(CLOTH_IDX, 0) < CLOTH_FACES_MIN:
+        return (fail(f"cloth faces {idx_counts.get(CLOTH_IDX, 0)} < {CLOTH_FACES_MIN}", 5),) + nothing
+    if idx_counts.get(TWINE_IDX, 0) < TWINE_FACES_MIN:
+        return (fail(f"twine faces {idx_counts.get(TWINE_IDX, 0)} < {TWINE_FACES_MIN}", 5),) + nothing
+    if u0 < -UV_EPS or v0 < -UV_EPS or u1 > 1.0 + UV_EPS or v1 > 1.0 + UV_EPS:
+        return (fail(f"UVs outside 0..1: ({u0:.4f},{v0:.4f})-({u1:.4f},{v1:.4f})", 6),) + nothing
+    if overlap > UV_OVERLAP_MAX:
+        return (fail(f"UV AABB overlap {overlap:.6f} > {UV_OVERLAP_MAX}", 7),) + nothing
+    if (abs(size_x - OUTER_SIZE[0]) > BBOX_TOL or abs(size_y - OUTER_SIZE[1]) > BBOX_TOL
+            or abs(size_z - OUTER_SIZE[2]) > BBOX_TOL):
+        return (fail(f"bbox ({size_x:.4f},{size_y:.4f},{size_z:.4f}) off outer {OUTER_SIZE}", 8),) + nothing
+    if not (LOD1_RATIO_MIN <= r1 <= LOD1_RATIO_MAX):
+        return (fail(f"LOD1 ratio {r1:.4f} not in [{LOD1_RATIO_MIN}, {LOD1_RATIO_MAX}] "
+                     "(--skip-decimate is the designed fail)", 9),) + nothing
+    if not (LOD2_RATIO_MIN <= r2 <= LOD2_RATIO_MAX):
+        return (fail(f"LOD2 ratio {r2:.4f} not in [{LOD2_RATIO_MIN}, {LOD2_RATIO_MAX}]", 9),) + nothing
+    if col_tris > COLLIDER_TRIS_MAX:
+        return (fail(f"collider tris {col_tris} > {COLLIDER_TRIS_MAX}", 11),) + nothing
+    if bake_result != {"FINISHED"} or not img.has_data:
+        return (fail(f"bake failed result={bake_result} has_data={img.has_data}", 12),) + nothing
+    if export_size <= 0:
+        return (fail("export file missing or empty", 13),) + nothing
+    if (hyg["loose_v"] or hyg["loose_e"] or hyg["nonman"] or hyg["zero_area"]
+            or hyg["doubles"] or hyg["ngons"] or zf):
+        return (fail(f"hygiene {hyg} zfight={zf} (--stray-vert is the designed fail)", 15),) + nothing
+    if abs(bb[2]) > ZMIN_EPS:
+        return (fail(f"zmin {bb[2]:.6f} not within {ZMIN_EPS} of 0 "
+                     "(--lift-z is the designed fail)", 16),) + nothing
+    if sa["n_body"] != len(SACKS) or sa["support_worst"] > SUPPORT_Z_MAX:
+        return (fail(f"supports: {sa['n_body']} of {len(SACKS)} sacks, worst base z="
+                     f"{sa['support_worst']:.5f} > {SUPPORT_Z_MAX} "
+                     "(--float-sack is the designed fail)", 16),) + nothing
+    if sa["n_tie"] != 2 * len(SACKS) or sa["waist_min"] < WAIST_MARGIN:
+        return (fail(f"ties: {sa['n_tie']} turns; host under the tie narrower than "
+                     f"{WAIST_PROBE} m either side by {sa['waist']}, need >= {WAIST_MARGIN} "
+                     "(--slip-tie is the designed fail)", 17),) + nothing
+    if sa["tie_min"] < TIE_SEAT_MIN or sa["tie_max"] > TIE_SEAT_MAX:
+        return (fail(f"tie seat ({sa['tie_min']:.5f}, {sa['tie_max']:.5f}) outside "
+                     f"[{TIE_SEAT_MIN}, {TIE_SEAT_MAX}] (--loose-tie is the designed fail)", 18),) + nothing
+    if sa["press_min"] < PRESS_MIN or sa["press_max"] > PRESS_MAX:
+        return (fail(f"press between sacks {sa['press']} outside [{PRESS_MIN}, {PRESS_MAX}] "
+                     "(--part-sacks is the designed fail)", 18),) + nothing
+    if sa["patch_min"] < PATCH_MIN:
+        return (fail(f"contact patch {sa['patch']} m^2, smallest < {PATCH_MIN} "
+                     "(--round-bottom is the designed fail)", 19),) + nothing
+    if sa["belly_max"] > BELLY_MAX:
+        return (fail(f"widest station at {sa['belly']} of the body > {BELLY_MAX} "
+                     "(--high-belly is the designed fail)", 19),) + nothing
+    return 0, low, high, cloth, tex, collider
+
+
+def wire_normal(mat, tex):
+    """Baked normal map under the weave bump."""
+    nt = mat.node_tree
+    nrm = nt.nodes.new("ShaderNodeNormalMap")
+    nt.links.new(tex.outputs["Color"], nrm.inputs["Color"])
+    bump = next(n for n in nt.nodes if n.bl_idname == "ShaderNodeBump")
+    nt.links.new(nrm.outputs["Normal"], bump.inputs["Normal"])
+
+
+def render_still(low, cloth, tex, path, engine):
+    scene = bpy.context.scene
+    wire_normal(cloth, tex)
+    for ob in list(scene.objects):
+        if ob.type == "MESH" and ob != low:
+            ob.hide_render = True
+            ob.hide_viewport = True
+    # Level on the floor: turned about Z only.
+    low.rotation_euler.z = math.radians(-10.0)
+
+    floor_me = bpy.data.meshes.new("Floor")
+    bm = bmesh.new()
+    try:
+        bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=60.0)
+        bm.to_mesh(floor_me)
+    finally:
+        bm.free()
+    fmat = bpy.data.materials.new("Floor")
+    fmat.use_nodes = True
+    fb = fmat.node_tree.nodes["Principled BSDF"]
+    fb.inputs["Base Color"].default_value = (0.03, 0.032, 0.037, 1.0)
+    fb.inputs["Roughness"].default_value = 0.7
+    floor_me.materials.append(fmat)
+    floor = bpy.data.objects.new("Floor", floor_me)
+    scene.collection.objects.link(floor)
+    wall = bpy.data.objects.new("Wall", floor_me.copy())
+    wall.location = (0.0, 8.5, 0.0)
+    wall.rotation_euler = (math.radians(90), 0.0, 0.0)
+    scene.collection.objects.link(wall)
+
+    world = bpy.data.worlds.new("World")
+    world.use_nodes = True
+    world.node_tree.nodes["Background"].inputs["Color"].default_value = (0.02, 0.021, 0.025, 1.0)
+    scene.world = world
+
+    def light(name, loc, energy, size, col, rot):
+        ld = bpy.data.lights.new(name, "AREA")
+        ld.energy = energy
+        ld.size = size
+        ld.color = col
+        ob = bpy.data.objects.new(name, ld)
+        ob.location = loc
+        ob.rotation_euler = tuple(math.radians(a) for a in rot)
+        scene.collection.objects.link(ob)
+
+    light("Key", (-3.2, -4.2, 4.8), 480.0, 4.0, (1.0, 0.95, 0.88), (46, 0, -38))
+    light("Fill", (4.2, -3.2, 2.2), 60.0, 8.0, (0.74, 0.84, 1.0), (66, 0, 52))
+    light("Rim", (-2.0, 3.4, 2.8), 260.0, 3.0, (0.62, 0.78, 1.0), (-60, 0, 200))
+    light("Wedge", (1.4, 3.8, 2.0), 520.0, 5.0, (1.0, 0.70, 0.38), (-94, 0, 194))
+
+    cam_data = bpy.data.cameras.new("Cam")
+    cam_data.lens = 50.0
+    cam = bpy.data.objects.new("Cam", cam_data)
+    cam.location = (1.22, -2.30, 1.18)
+    scene.collection.objects.link(cam)
+    aim = bpy.data.objects.new("Aim", None)
+    aim.location = (0.02, -0.08, 0.30)
+    scene.collection.objects.link(aim)
+    con = cam.constraints.new("TRACK_TO")
+    con.target = aim
+    con.track_axis = "TRACK_NEGATIVE_Z"
+    con.up_axis = "UP_Y"
+    scene.camera = cam
+
+    scene.render.engine = "CYCLES" if engine == "cycles" else eevee_engine_id()
+    if engine == "cycles":
+        scene.cycles.samples = 32
+        scene.cycles.device = "CPU"
+    else:
+        try:
+            scene.eevee.taa_render_samples = 64
+        except AttributeError:
+            pass
+    scene.render.resolution_x = 1280
+    scene.render.resolution_y = 720
+    scene.render.image_settings.file_format = "WEBP" if path.lower().endswith(".webp") else "PNG"
+    if path.lower().endswith(".webp"):
+        scene.render.image_settings.quality = 90
+    scene.render.filepath = path
+    scene.view_settings.view_transform = "Standard"
+
+    fcode = gallery_framing.check_framing(scene, cam, hero=[low], elements=[low], stage=[floor, wall])
+    if fcode:
+        return fcode
+    bpy.ops.render.render(write_still=True)
+    if not (os.path.exists(path) and os.path.getsize(path) > 0):
+        return fail("render produced no file", 14)
+    return 0
+
+
+def main():
+    argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
+    p = argparse.ArgumentParser()
+    p.add_argument("--output", default=None)
+    p.add_argument("--engine", default="eevee", choices=("eevee", "cycles"))
+    p.add_argument("--skip-decimate", action="store_true")
+    p.add_argument("--stray-vert", action="store_true")
+    p.add_argument("--lift-z", action="store_true")
+    p.add_argument("--float-sack", action="store_true")
+    p.add_argument("--slip-tie", action="store_true")
+    p.add_argument("--loose-tie", action="store_true")
+    p.add_argument("--part-sacks", action="store_true")
+    p.add_argument("--round-bottom", action="store_true")
+    p.add_argument("--high-belly", action="store_true")
+    args = p.parse_args(argv)
+
+    code, low, _high, cloth, tex, _col = check(
+        args.skip_decimate,
+        lift_z=args.lift_z,
+        stray_vert=args.stray_vert,
+        float_sack=args.float_sack,
+        slip_tie=args.slip_tie,
+        loose_tie=args.loose_tie,
+        part_sacks=args.part_sacks,
+        round_bottom=args.round_bottom,
+        high_belly=args.high_belly,
+    )
+    if code:
+        return code
+    if args.output:
+        rcode = render_still(low, cloth, tex, os.path.abspath(args.output), args.engine)
+        if rcode:
+            return rcode
+        print(f"rendered still {args.output}")
+    print("grain-sacks OK")
+    return 0
+
+
+if __name__ == "__main__":
+    try:
+        sys.exit(main())
+    except Exception as e:
+        traceback.print_exc()
+        print(f"FATAL: {e}", file=sys.stderr)
+        sys.exit(1)
+
+
+
+ + + Three burlap grain sacks tied at the neck with twine, two standing and one lying in front, with a blue stripe on one and red stripes on the other. + +
+
+ generated from showcase/gallery.json + CC-BY-NC-ND-4.0 + exit 0 +
+
+ + + diff --git a/docs/gallery/index.html b/docs/gallery/index.html index 923fb2be..b570981d 100644 --- a/docs/gallery/index.html +++ b/docs/gallery/index.html @@ -291,7 +291,7 @@

Examples and Showcase

autocomplete="off" spellcheck="false" aria-label="Search examples and showcase pieces" /> - 51 examples, 29 showcase pieces + 51 examples, 30 showcase pieces
@@ -1221,6 +1221,17 @@

rope-bridge

View showcase piece rope-bridge
+
+ +
+

grain-sacks

+

A procedural pile of burlap grain sacks — settled onto flat bases, pressed into one another, gathered under twine ties with pleated crowns — carried through UVs, bake, LOD, compound collider, and Unity glTF, asserting recomputed budgets rather than an API contract.

+

witnesses Recomputed: 8640 tris, two materials with 3264 burlap and 1152 twine faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.914×0.905×0.636 m, zmin 0, hygiene 0 including zero coplanar cross-shell pairs, all three sacks grounded, every tie in a neck waist 13–15 mm narrower than the cloth 40 mm either side, six twine turns seated 3.6–5.2 mm into the cloth, sacks pressing 12 mm into one another, bellies settled low (0.25–0.32 of the body), and flat contact patches of 0.035, 0.099 and 0.117 m² under the three sacks. --round-bottom exits 19 with every patch at 0 while zmin and the supports still pass; --slip-tie exits 17 on the waist.

+ View showcase piece grain-sacks +
+
@@ -1250,7 +1261,7 @@

rope-bridge

var tagsToggle = document.getElementById('tagsToggle'); var toTop = document.getElementById('toTop'); var total = cards.length; - var COUNT_LABEL = '51 examples, 29 showcase pieces'; + var COUNT_LABEL = '51 examples, 30 showcase pieces'; var LS_KEY = 'bdt-gallery-density'; var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; diff --git a/showcase/gallery.json b/showcase/gallery.json index 43e2b49e..a6e1449c 100644 --- a/showcase/gallery.json +++ b/showcase/gallery.json @@ -387,6 +387,19 @@ "mesh", "export" ] + }, + { + "name": "grain-sacks", + "dir": "showcase/grain-sacks", + "teaches": "A procedural pile of burlap grain sacks — settled onto flat bases, pressed into one another, gathered under twine ties with pleated crowns — carried through UVs, bake, LOD, compound collider, and Unity glTF, asserting recomputed budgets rather than an API contract.", + "alt": "Three burlap grain sacks tied at the neck with twine, two standing and one lying in front, with a blue stripe on one and red stripes on the other.", + "witnessesFix": "Recomputed: 8640 tris, two materials with 3264 burlap and 1152 twine faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.914×0.905×0.636 m, zmin 0, hygiene 0 including zero coplanar cross-shell pairs, all three sacks grounded, every tie in a neck waist 13–15 mm narrower than the cloth 40 mm either side, six twine turns seated 3.6–5.2 mm into the cloth, sacks pressing 12 mm into one another, bellies settled low (0.25–0.32 of the body), and flat contact patches of 0.035, 0.099 and 0.117 m² under the three sacks. --round-bottom exits 19 with every patch at 0 while zmin and the supports still pass; --slip-tie exits 17 on the waist.", + "hero": "docs/gallery/assets/grain-sacks-hero.webp", + "preview": "showcase/grain-sacks/preview.webp", + "tags": [ + "mesh", + "export" + ] } ] } diff --git a/showcase/grain-sacks/README.md b/showcase/grain-sacks/README.md new file mode 100644 index 00000000..305d3432 --- /dev/null +++ b/showcase/grain-sacks/README.md @@ -0,0 +1,214 @@ +# grain-sacks + +![Three burlap grain sacks gathered and tied at the neck with twine: two standing, one striped in blue, and one red-striped sack lying slumped in front of them](preview.webp) + +A pile of three burlap grain sacks. Two stand, one lies slumped in front +of them, and each is gathered at the neck under a two-turn twine tie with +a pleated crown above it. The sacks have settled onto flat bases and +press into each other where they lean. +**A showcase piece, not an example** — it witnesses no API contract. It +asserts that generated geometry meets declared asset budgets, recomputed +from the finished mesh. + +## What it composes + +| Shipped content | Used for | +| --- | --- | +| `skills/mesh-editing-and-bmesh` | lofted, pleated sack bodies and laid twine in one `bmesh` | +| `skills/procedural-materials-and-shaders` | hessian weave and stripes on the cloth's own coordinates (a second UV layer), per-sack tone | +| `skills/bake-high-to-low` | Cycles tangent-space normal bake, high onto low | +| `skills/engine-export-presets` | Unity glTF (`export_yup=True`) | +| `skills/depsgraph-and-evaluated-data` | evaluated triangle counts for the LOD ratios | +| `snippets/decimate_to_budget.py` | LOD1 / LOD2 COLLAPSE chain | +| `snippets/convex_hull_collider.py` | one hull per sack, merged into a compound | +| `snippets/lod_chain.py` | LOD naming and ratio pattern | +| `examples/mesh-hygiene-audit` | hygiene combinatorics (copied, not imported) | + +## The budget that matters + +A sack full of grain settles. Its contents slump into the bottom and it +rests on a flat patch of cloth pressed against the floor. An egg-bottomed +sack still touches the floor at its lowest vertex, so the grounded-zmin +gate and the per-sack support gate both pass it, but it reads as a +balloon rocking on a point. The piece sums, per sack, the area of the +faces that lie flat on the floor (every vertex at z = 0) and asserts each +is at least 0.012 m². + +`--round-bottom` is the falsifier built for exactly this. It sinks each +sack by the same amount and folds the same height, but squashes the base +linearly instead of flattening it, so every sack keeps its height, its +footprint and its single lowest point on the floor. Every other budget +passes. Only the patch sees it: 0 m² on all three. + +## Settling and pressing + +- **Settle.** Each sack is built standing (or laid on its flat side and + squashed 16%), sunk `SINK` = 60 mm into the floor, and everything under + `SETTLE_C` = 30 mm is folded back up by g(u) = ((u + 1) / 2)², clamped to 0 + below u = −1. The fold is C1 at its top and exactly flat where the grain + presses the floor. The squashed height goes outward from the sack's own + axis (`SPREAD`), so the base bulges the way a filled sack does. +- **Press.** Sacks are placed, then slid along the line between centres + until the moving sack's deepest vertex inside the sack it leans on + reaches `PRESS` = 12 mm. This uses bisection on the real meshes, so the + press is exact whatever the lumps and pleats do where the two meet. +- **Tie.** Each twine turn's centreline is the host's own section at its + station, pushed out along each vertex's radial by the twine radius less + `TIE_BITE`. The turn follows the pleats instead of standing proud of the + valleys and sinking into the crests. + +## Budgets + +Declared in the script as named constants, recomputed from the generated +mesh. Measured values are from Blender 5.2.1; every one is byte-identical +on 4.5.11 and 5.1.2. + +| Budget | Band | Measured | +| --- | --- | --- | +| Base triangles | 8000–9300 | 8640 | +| LOD1 ratio | 0.32–0.62 | 0.5000 | +| LOD2 ratio | 0.10–0.35 | 0.2199 | +| Material slots | exactly 2, distinct | 2 | +| Cloth faces | ≥ 2600 | 3264 | +| Twine faces | ≥ 900 | 1152 | +| UV bounds | inside 0..1 | (0.0133, 0.0133)–(0.9867, 0.9867) | +| UV AABB overlap | ≤ 1e-5 | 0.000000 | +| Outer AABB | 0.914 × 0.905 × 0.636 m ± 0.020 | 0.9139 × 0.9054 × 0.6360 | +| Collider triangles | ≤ 450 | 376 (three hulls) | +| Normal bake | `{'FINISHED'}` with image data | `{'FINISHED'}`, `has_data=True` | +| glTF export | file written, non-empty | ~280 kB | +| Hygiene | all zero | loose 0/0, non-manifold 0, zero-area 0, doubles 0, n-gons 0, coplanar cross-shell pairs 0 | +| Grounded AABB | \|zmin\| ≤ 1e-4 | 0.00000 | +| Named supports | 3 sacks, each zmin ≤ 1e-3 | 3 at 0.00000 | +| Tie in the waist | host under the tie ≥ 8 mm narrower than 40 mm either side, per sack | 14.25 / 15.04 / 13.11 mm | +| Tie seat | 6 turns, deepest twine vertex inside the cloth 1.0–6.0 mm | 3.57–5.21 mm | +| Press between sacks | each sack's deepest vertex inside another 6–20 mm | 11.96 / 12.00 / 12.00 mm | +| Contact patch | each sack ≥ 0.012 m² flat on the floor | 0.0352 / 0.0988 / 0.1168 m² | +| Settled belly | widest station ≤ 0.42 of the body from its base | 0.251 / 0.280 / 0.316 | + +Real-world size: the tall sack stands 0.64 m and is 0.47 m across the +belly — a 50 kg grain sack. The outer AABB is the three sacks themselves, +with no appendage above or beside them, so the AABB gate already holds +the bodies to their stated size. + +## Surface + +- **Weave.** Hessian is two perpendicular sine gratings on a second UV + layer, `ClothCo`: metres round the sack from its front, and metres up it. + The weave follows the cloth whichever way up the sack lies. A 6 mm period + aliased into moiré at hero distance, so the period is 12 mm with a soft + bump. +- **Stripes.** Stripes are the old feed-sack marking, centred on each + sack's front face (the broad face a viewer sees): one broad blue band on + the tall sack, three thin red ones on the lying sack, none on the third. + Each sack also has its own tone. +- **Twine.** Twine is paler and yellower than the hessian, so the tie reads + against the neck. + +## Conventions walked + +Every convention in `showcase/README.md`, and whether it applies here. + +| Convention | Applies | How | +| --- | --- | --- | +| Deterministic, budgets declared, assertions recompute | yes | no RNG at all; every value above is read off the mesh | +| Falsifier fails the budget it targets | yes | table below, proven on all three binaries | +| Hygiene incl. cross-shell coplanar | yes | exit 15. A twine face parallel to the cloth it hugs, one column over, first shared its plane (2 pairs); the turns now sit a quarter step off the body's columns | +| Named supports | yes | every sack grounded (`--float-sack`) | +| Wrappers follow the host's profile | yes | the tie is built on the host's own pleated section | +| Band hooped, never flush / seat conformance | yes | tie bite banded (`--loose-tie`) | +| One connected assembly / carried parts bite | yes | sacks press a banded depth into one another (`--part-sacks`) | +| A vessel has a base and a belly | yes | settled belly low (`--high-belly`) and the contact patch (`--round-bottom`) | +| Plumb and real-world size | partly | a slumped sack is not plumb by design; size is held by the AABB, which is the bodies themselves | +| Shading is part of the model | yes | cloth and twine smooth-shaded: nothing on a sack is faceted in life | +| One substance, one slot | yes | burlap, twine | +| Rope is laid, not piped | yes | three-lobed 6-vertex twine section, turned per ring | +| Two identical sections stacked share planes | yes | the two tie turns are half a step apart | +| Identical parts read as CG | yes | each sack has its own height, width, depth, pleat phase, tone and stripe | +| Sort bmesh operator inputs | n/a | no bevel or other set-fed operator is run | +| Edge treatment: no right angles | n/a | no boxes; every surface is lofted cloth or twine | +| Bake texels per UV cell | n/a | nine UV islands, one per shell, each a ninth of the sheet | +| Level on the stage; stage 60 m | yes | turned about Z only; 60 m floor and wall | +| Keep a falsifier's envelope still | yes | worst deltas: `--high-belly` −16.2 mm Y, `--part-sacks` +15.5 mm Y, against 20 mm | +| Timber, iron, masonry, scatter, fixtures, roofs, rings | no | the piece has none of these | + +## Falsifiers + +Each breaks one pipeline stage so a **named** budget fails. All nine were +run on 4.5.11, 5.1.2 and 5.2.1 and exited the same declared code on all +three. + +| Flag | Target budget | Breaks | Exit | +| --- | --- | --- | --- | +| `--skip-decimate` | LOD1 ratio | drops the DECIMATE modifiers, LOD1 ratio goes to 1.0000 | 9 | +| `--stray-vert` | mesh hygiene | adds one loose vertex inside the pile | 15 | +| `--lift-z` | grounded zmin | lifts the whole mesh 50 mm | 16 | +| `--float-sack` | named supports | floats the lying sack 12 mm; the standing ones still ground the AABB | 16 | +| `--slip-tie` | tie in the waist | slides every tie 50 mm up onto the crown, still seated on it; margin −13.4 to −44.6 mm | 17 | +| `--loose-tie` | tie seat | sizes each turn 6 mm wider; shallowest turn −1.67 mm | 18 | +| `--part-sacks` | press between sacks | stops the lying sack 4 mm short of the one it leans on | 18 | +| `--round-bottom` | contact patch | squashes the bases round instead of flat; 0 m² on all three | 19 | +| `--high-belly` | settled belly | moves the widest station up to 0.52 of the body, same widths; 0.535–0.586 | 19 | + +## Exit codes + +File-local and sequential. `9` is a valid check code. `1` is the FATAL +wrapper — a crash, never a named check. + +| Code | Meaning | +| --- | --- | +| 0 | Success | +| 1 | Uncaught exception (FATAL wrapper) | +| 2 | argparse / usage | +| 3 | Mesh did not build, or has no UV layer | +| 4 | Base triangle count outside band | +| 5 | Material slots, or a material's face floor | +| 6 | UVs outside 0..1 | +| 7 | UV AABB overlap above tolerance | +| 8 | Outer AABB off declared size | +| 9 | LOD1 or LOD2 ratio outside band (`--skip-decimate`) | +| 10 | Framing gate (`examples/gallery_framing.py`, render path only) | +| 11 | Collider triangles above ceiling | +| 12 | Normal bake failed or produced no image data | +| 13 | glTF export missing or empty | +| 14 | `--output` produced no file | +| 15 | Mesh hygiene (`--stray-vert`) | +| 16 | Grounded zmin, or a sack floating (`--lift-z`, `--float-sack`) | +| 17 | A tie not in its neck's waist (`--slip-tie`) | +| 18 | Tie seat, or press between sacks (`--loose-tie`, `--part-sacks`) | +| 19 | Contact patch or settled belly (`--round-bottom`, `--high-belly`) | + +## Run it + +```bash +# Budget check, no render. ~1.9 s on 4.5, ~1.7 s on 5.1, ~2.1 s on 5.2. +blender --background --python grain_sacks.py -- + +# Falsifier: the sacks stop settling onto flat bases. Must exit 19. +blender --background --python grain_sacks.py -- --round-bottom + +# Falsifier: every tie rides up onto its crown. Must exit 17. +blender --background --python grain_sacks.py -- --slip-tie + +# Render the gallery still (EEVEE; --engine cycles on a GPU-less host). +blender --background --python grain_sacks.py -- --output sacks.webp +``` + +Smoke runs the check-only path. It does not pass `--output` or any falsifier. + +## Cross-version measurements + +| Value | 4.5.11 | 5.1.2 | 5.2.1 | +| --- | --- | --- | --- | +| Base triangles | 8640 | 8640 | 8640 | +| LOD1 / LOD2 tris | 4320 / 1900 | same | same | +| Face counts (cloth / twine) | 3264 / 1152 | same | same | +| Outer AABB | 0.9139 × 0.9054 × 0.6360 | same | same | +| Collider tris | 376 | 376 | 376 | +| Contact patches (m²) | 0.0352 / 0.0988 / 0.1168 | same | same | +| glTF bytes | identical | identical | identical | +| Check wall-clock | ~1.9 s | ~1.7 s | ~2.1 s | + +`DECIMATE COLLAPSE` is the usual cross-version suspect. Here it produces +identical LOD counts on all three binaries; the gate is still a ratio +band, not an exact count. diff --git a/showcase/grain-sacks/grain_sacks.py b/showcase/grain-sacks/grain_sacks.py new file mode 100644 index 00000000..f5cc25f9 --- /dev/null +++ b/showcase/grain-sacks/grain_sacks.py @@ -0,0 +1,1417 @@ +"""Game-ready grain sacks — a showcase piece, not an example. + +Asserts budget conformance of a procedural pile of three burlap grain +sacks: two standing, one lying slumped against them, each gathered at the +neck under a two-turn twine tie. Carried through UVs, two materials +(burlap, twine), a high-to-low normal bake, an LOD chain, a compound +convex collider, and a Unity glTF export. + +The budget that matters here is the one a sack can fail invisibly: a +sack full of grain settles, so it rests on a flat contact patch, not on +a point. An egg-bottomed sack still touches the floor, so the grounded +zmin gate passes it; only the patch area, summed from the faces that lie +flat on the floor, knows the difference. + +Budgets are declared below and recomputed from the generated result. +They are not API-contract witnesses. Each falsifier violates one named +budget: ``--skip-decimate`` the LOD-ratio band, ``--stray-vert`` mesh +hygiene, ``--lift-z`` grounded zmin, ``--float-sack`` the named +supports, ``--slip-tie`` the tie-at-waist fit, ``--loose-tie`` the tie +seat, ``--part-sacks`` the press between sacks, ``--round-bottom`` the +contact patch, ``--high-belly`` the settled belly. + +No randomness: every lump, pleat and tone is a closed-form term of the +sack's own parameters. DECIMATE COLLAPSE triangle counts are not +byte-identical across Blender versions — the LOD gate is a ratio band. + + blender --background --python grain_sacks.py -- + blender --background --python grain_sacks.py -- --round-bottom + blender --background --python grain_sacks.py -- --output sacks.png +""" +import argparse +import math +import os +import sys +import tempfile +import traceback + +import bmesh +import bpy +from mathutils import Matrix, Vector +from mathutils.bvhtree import BVHTree +from mathutils.kdtree import KDTree + +_REPO = os.path.abspath( + os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir) +) +sys.path.insert(0, os.path.join(_REPO, "examples")) +sys.dont_write_bytecode = True +import gallery_framing # noqa: E402 + +# Three sacks: two stood up, one laid down in front. Heights are the +# unsettled body length; W is the belly half-width, D the depth ratio of +# the flattened sack section (two sewn panels, not a tube). +SACKS = ( + {"name": "A", "at": (-0.24, 0.10), "yaw": 16.0, "H": 0.80, "W": 0.235, + "D": 0.70, "lying": False, "stripe": 1, "front": 0.75, "phase": 0.0, "tone": 0.56}, + {"name": "B", "at": (0.30, 0.16), "yaw": -28.0, "H": 0.68, "W": 0.250, + "D": 0.74, "lying": False, "stripe": 0, "front": 0.75, "phase": 0.9, "tone": 0.44}, + {"name": "C", "at": (0.04, -0.52), "yaw": 72.0, "H": 0.82, "W": 0.235, + "D": 0.66, "lying": True, "stripe": 2, "front": 0.25, "phase": 2.1, "tone": 0.50}, +) +# Order in which sacks are slid into contact: each one presses the sack +# named here, along the line between their starting centres. +PRESS_INTO = {"B": "A", "C": "A"} + +SEG = 32 +# Body profile: (height fraction, radius fraction). Round bottom, belly +# low where the grain settles, a shoulder, the gathered neck under the +# tie, and the pleated crown above it closing to a pole. +PROFILE = ( + (0.000, 0.00), (0.020, 0.52), (0.060, 0.80), (0.140, 0.96), (0.260, 1.00), + (0.400, 0.97), (0.520, 0.88), (0.610, 0.68), (0.680, 0.36), (0.720, 0.19), + (0.750, 0.21), (0.800, 0.33), (0.840, 0.30), (0.870, 0.00), +) +# --high-belly: same widths, the widest ring moved up to 0.52 of the body. +HIGH_BELLY_PROFILE = ( + (0.000, 0.00), (0.020, 0.50), (0.060, 0.72), (0.140, 0.82), (0.260, 0.90), + (0.400, 0.97), (0.520, 1.00), (0.610, 0.78), (0.680, 0.36), (0.720, 0.19), + (0.750, 0.21), (0.800, 0.33), (0.840, 0.30), (0.870, 0.00), +) +BODY_RINGS = 30 +NECK_T = 0.720 +# Pleats gather where the tie cinches: none on the belly, deep on the +# crown, shallow under the tie itself so the tie has a round seat. +PLEATS = 11 +PLEAT_SHOULDER = 0.09 +PLEAT_TIE = 0.025 +PLEAT_CROWN = 0.20 +LUMP = 0.035 + +# Twine tie: two turns hooped onto the neck, the second half a vertex +# step round from the first so the two are not one section stacked. +TIE_R = 0.0065 +TIE_PIPE = 6 +TIE_LOBE = 0.12 +TIE_BITE = 0.003 +TIE_PITCH = 0.012 +LOOSE_TIE_BITE = -0.003 +SLIP_TIE = 0.050 + +# Settling: the body is sunk SINK below the floor and everything under +# SETTLE_C is folded up onto it, flat where the grain presses, and pushed +# outward so the squashed base bulges. +SINK = 0.060 +SETTLE_C = 0.030 +SPREAD = 0.55 +LYING_SQUASH = 0.84 +# Sacks press into each other by this much, found by sliding each one in +# along the line between centres until its deepest vertex inside the +# other reaches it. +PRESS = 0.012 +PART_GAP = -0.004 + +BBOX_TOL = 0.020 +OUTER_SIZE = (0.914, 0.905, 0.636) + +BASE_TRIS_MIN = 8000 +BASE_TRIS_MAX = 9300 +LOD1_RATIO_MIN = 0.32 +LOD1_RATIO_MAX = 0.62 +LOD2_RATIO_MIN = 0.10 +LOD2_RATIO_MAX = 0.35 +LOD1_TARGET = 0.50 +LOD2_TARGET = 0.22 +MATERIAL_COUNT = 2 +CLOTH_FACES_MIN = 2600 +TWINE_FACES_MIN = 900 +UV_EPS = 1e-4 +UV_OVERLAP_MAX = 1e-5 +COLLIDER_TRIS_MAX = 450 +COLLIDER_STRIDE = 3 +BAKE_RES = 256 +CAGE_EXTRUSION = 0.01 +ZMIN_EPS = 1e-4 +DOUBLES_EPS = 1e-5 +AREA_EPS = 1e-10 +COPLANAR_NORMAL_EPS = 1e-4 +COPLANAR_PLANE_EPS = 1e-4 +COPLANAR_CENTRE_MAX = 0.05 +LIFT_Z = 0.05 +FLOAT_SACK = 0.012 +SUPPORT_Z_MAX = 1e-3 +# Tie at the waist: the host under the tie is narrower than the host +# WAIST_PROBE above and below it, by at least WAIST_MARGIN. A tie that is +# not in a waist slides off. +WAIST_PROBE = 0.040 +WAIST_BAND = 0.012 +WAIST_MARGIN = 0.008 +# Tie seat: deepest twine vertex inside the cloth, per turn. +TIE_SEAT_MIN = 0.0010 +TIE_SEAT_MAX = 0.0060 +# Press: each slid sack's deepest vertex inside the sack it leans on. +PRESS_MIN = 0.006 +PRESS_MAX = 0.020 +# Contact patch: flat-on-floor area under each sack. +PATCH_MIN = 0.012 +# Settled belly: the widest station in the lower part of the body. +BELLY_MAX = 0.42 + +CLOTH_IDX = 0 +# Weave period on the cloth. 6 mm aliased into moire at hero distance. +WEAVE = 0.012 +TWINE_IDX = 1 + + +def eevee_engine_id(): + """EEVEE id: 'BLENDER_EEVEE' on 5.0+, 'BLENDER_EEVEE_NEXT' on 4.2-4.5.""" + return "BLENDER_EEVEE" if bpy.app.version >= (5, 0, 0) else "BLENDER_EEVEE_NEXT" + + +def fail(msg, code): + print(f"FAIL[{code}]: {msg}", file=sys.stderr) + return code + + +def triangle_count(mesh): + mesh.calc_loop_triangles() + return len(mesh.loop_triangles) + + +def evaluated_triangle_count(obj): + deps = bpy.context.evaluated_depsgraph_get() + ev = obj.evaluated_get(deps) + mesh = ev.to_mesh() + try: + mesh.calc_loop_triangles() + return len(mesh.loop_triangles) + finally: + ev.to_mesh_clear() + + +# --- profile ---------------------------------------------------------------- + + +def pchip(knots, t): + """Monotone cubic (Fritsch-Carlson) through ``knots`` at ``t``. + + Smooth through the knots without the overshoot a Catmull-Rom spline + puts into the neck, which would pinch the cloth below the tie. + """ + xs = [k[0] for k in knots] + ys = [k[1] for k in knots] + n = len(xs) + h = [xs[i + 1] - xs[i] for i in range(n - 1)] + d = [(ys[i + 1] - ys[i]) / h[i] for i in range(n - 1)] + m = [0.0] * n + m[0], m[-1] = d[0], d[-1] + for i in range(1, n - 1): + if d[i - 1] * d[i] <= 0.0: + m[i] = 0.0 + else: + w1 = 2.0 * h[i] + h[i - 1] + w2 = h[i] + 2.0 * h[i - 1] + m[i] = (w1 + w2) / (w1 / d[i - 1] + w2 / d[i]) + t = min(max(t, xs[0]), xs[-1]) + i = min(n - 2, max(0, next((j for j in range(n - 1) if t <= xs[j + 1]), n - 2))) + s = (t - xs[i]) / h[i] + h00 = (1 + 2 * s) * (1 - s) ** 2 + h10 = s * (1 - s) ** 2 + h01 = s * s * (3 - 2 * s) + h11 = s * s * (s - 1) + return h00 * ys[i] + h10 * h[i] * m[i] + h01 * ys[i + 1] + h11 * h[i] * m[i + 1] + + +def pleat_amp(t): + """Pleat depth along the body: nothing on the belly, gathered at the neck.""" + if t < 0.52: + return 0.0 + if t < 0.68: + return PLEAT_SHOULDER * (t - 0.52) / 0.16 + if t < NECK_T: + return PLEAT_SHOULDER + (PLEAT_TIE - PLEAT_SHOULDER) * (t - 0.68) / (NECK_T - 0.68) + if t < 0.78: + return PLEAT_TIE + (PLEAT_CROWN - PLEAT_TIE) * (t - NECK_T) / (0.78 - NECK_T) + return PLEAT_CROWN + + +def section_point(sack, prof, t, a): + """Body surface at height fraction ``t`` and section angle ``a``, local frame.""" + f = pchip(prof, t) + ratio = sack["D"] + (1.0 - sack["D"]) * (1.0 - min(1.0, f)) + m = 1.0 + pleat_amp(t) * math.cos(PLEATS * a + sack["phase"]) + # Grain lumps: low-order, closed-form, fading out toward the neck. + m += LUMP * max(0.0, 1.0 - t / 0.6) * math.cos(2.0 * a + 3.0 * t + sack["phase"]) + r = sack["W"] * f * m + return Vector((r * math.cos(a), r * ratio * math.sin(a), t * sack["H"])) + + +# --- parts ------------------------------------------------------------------ + + +class Part: + """Loose geometry for one shell: verts, faces, per-corner UVs, metadata.""" + + def __init__(self, kind, sack, mat): + self.kind = kind + self.sack = sack + self.mat = mat + self.verts = [] + self.faces = [] + self.uvs = [] + self.cloth = [] + + def add(self, co): + self.verts.append(Vector(co)) + return len(self.verts) - 1 + + +def grid_faces(part, rings, s_vals, n, poles=None, closed=False): + """Quads between consecutive rings (and pole fans), with strip UVs. + + ``poles`` maps 'start'/'end' to (vertex index, s) for a fan to a pole; + the pole's UV sits half a column over, so every fan triangle has its + own UV rectangle and nothing overlaps. + """ + m = len(rings) + spans = m if closed else m - 1 + for k in range(spans): + a, b = rings[k], rings[(k + 1) % m] + s0, s1 = s_vals[k], s_vals[k + 1] + for i in range(n): + j = (i + 1) % n + part.faces.append((a[i], a[j], b[j], b[i])) + part.uvs.append(((s0, i / n), (s0, (i + 1) / n), (s1, (i + 1) / n), (s1, i / n))) + for key, ring, s_ring in (("start", rings[0], s_vals[0]), ("end", rings[-1], s_vals[m - 1])): + if not poles or key not in poles: + continue + pole, s_pole = poles[key] + for i in range(n): + j = (i + 1) % n + # Wound outward: the base fan runs the other way round from the crown's. + if key == "start": + part.faces.append((pole, ring[j], ring[i])) + part.uvs.append(((s_pole, (i + 0.5) / n), (s_ring, (i + 1) / n), (s_ring, i / n))) + else: + part.faces.append((pole, ring[i], ring[j])) + part.uvs.append(((s_pole, (i + 0.5) / n), (s_ring, i / n), (s_ring, (i + 1) / n))) + + +def body_part(sack, prof): + """The sack body: a lofted, pleated, lumpy bag closed at both poles.""" + part = Part("body", sack["name"], CLOTH_IDX) + top = prof[-1][0] + ts = [0.012 + (top - 0.024) * k / (BODY_RINGS - 1) for k in range(BODY_RINGS)] + ts += [NECK_T + d * TIE_PITCH / sack["H"] for d in (-0.5, 0.0, 0.5)] + ts = sorted(set(round(t, 6) for t in ts)) + rings = [] + for t in ts: + rings.append([part.add(section_point(sack, prof, t, 2.0 * math.pi * i / SEG)) + for i in range(SEG)]) + bottom = part.add((0.0, 0.0, 0.0)) + crown = part.add((0.0, 0.0, top * sack["H"])) + s = [t * sack["H"] for t in ts] + grid_faces(part, rings, s, SEG, poles={"start": (bottom, 0.0), + "end": (crown, top * sack["H"])}) + # Cloth coordinates for the weave and stripes: metres round the sack + # and metres up it, read by the shader so the weave follows the cloth + # whatever way up the sack lies. + # Round the sack is measured from its front (the broad face a viewer + # sees: -Y stood up, +Y laid down), so stripes centre on it. + circ = 2.0 * math.pi * sack["W"] * (1.0 + sack["D"]) / 2.0 + + def around(v): + return ((v - sack["front"] + 0.5) % 1.0 - 0.5) * circ + + part.cloth = [] + for corner_uvs in part.uvs: + vs = [v for _u, v in corner_uvs] + # A face straddling the back seam keeps its corners on one side. + ref = around(vs[0]) + row = [] + for u, v in corner_uvs: + a = around(v) + if abs(a - ref) > 0.5 * circ: + a += circ if a < ref else -circ + row.append((a, u)) + part.cloth.append(tuple(row)) + return part + + +def ring_offsets(t, side, r, n, lobe, idx, angle0=0.0): + """Section offsets for ring ``idx``: a lobed circle, turned per ring.""" + s = side - t * side.dot(t) + s.normalize() + up = t.cross(s) + out = [] + for k in range(n): + a = 2.0 * math.pi * k / n + angle0 + rr = r * (1.0 + lobe * math.cos(3.0 * a - 2.0 * math.pi * 3.0 * idx / n)) + out.append(s * (rr * math.cos(a)) + up * (rr * math.sin(a))) + return out + + +def tie_part(sack, prof, t_station, bite, angle0): + """One twine turn hooped onto the neck at ``t_station``. + + The centreline follows the host's own section at that station — every + body vertex of that ring, pushed out along its radial by the twine + radius less the bite — so the turn hugs the pleats rather than a + circle that stands proud of the valleys and sinks into the crests. + """ + part = Part("tie", sack["name"], TWINE_IDX) + pts = [] + for i in range(SEG): + p = section_point(sack, prof, t_station, 2.0 * math.pi * i / SEG) + radial = Vector((p.x, p.y, 0.0)) + pts.append(p + radial.normalized() * (TIE_R - bite)) + m = len(pts) + tans = [(pts[(i + 1) % m] - pts[(i - 1) % m]).normalized() for i in range(m)] + side = Vector((0.0, 0.0, 1.0)) + rings = [] + for i, (p, t) in enumerate(zip(pts, tans)): + rings.append([part.add(p + o) for o in ring_offsets( + t, side, TIE_R, TIE_PIPE, TIE_LOBE, i, angle0)]) + s = [0.0] + for i in range(1, m + 1): + s.append(s[-1] + (pts[i % m] - pts[i - 1]).length) + grid_faces(part, rings, s, TIE_PIPE, closed=True) + part.cloth = [tuple((0.0, 0.0) for _ in uv) for uv in part.uvs] + return part + + +def orient(parts, sack): + """Stand or lay the sack, turn it by its yaw; returns its axis in world.""" + rot = Matrix.Identity(3) + if sack["lying"]: + # Lay it on its flat side: the section's narrow axis becomes Z. + rot = Matrix.Rotation(math.radians(90.0), 3, "X") @ rot + squash = Matrix.Diagonal((1.0, 1.0, LYING_SQUASH)) + rot = squash @ rot + rot = Matrix.Rotation(math.radians(sack["yaw"]), 3, "Z") @ rot + for part in parts: + part.verts = [rot @ v for v in part.verts] + axis = (rot @ Vector((0.0, 0.0, 1.0))).normalized() + return axis + + +def settle(parts, axis, round_bottom=False): + """Sink the sack SINK into the floor and fold everything under SETTLE_C up. + + Below SETTLE_C the height is remapped by g(u) = ((u + 1) / 2)^2 on + u = z / SETTLE_C, clamped to 0 below u = -1: C1 at the fold, and + exactly flat where the grain presses the floor. The squashed height + goes outward, away from the sack's axis, so the base bulges. + + ``round_bottom`` is the falsifier: the same sink and the same fold + height, but a linear squash that keeps the base round, touching the + floor at one point. + """ + zmin = min(v.z for p in parts for v in p.verts) + shift = -zmin - SINK + centre = sum((v for v in parts[0].verts), Vector()) / len(parts[0].verts) + for part in parts: + out = [] + for v in part.verts: + z = v.z + shift + if z >= SETTLE_C: + out.append(Vector((v.x, v.y, z))) + continue + if round_bottom: + z2 = (z + SINK) * SETTLE_C / (SETTLE_C + SINK) + out.append(Vector((v.x, v.y, z2))) + continue + u = z / SETTLE_C + z2 = 0.0 if u <= -1.0 else SETTLE_C * ((u + 1.0) / 2.0) ** 2 + w = Vector((v.x, v.y, z)) - centre + w = w - axis * w.dot(axis) + w.z = 0.0 + if w.length > 1e-9: + w.normalize() + out.append(Vector((v.x, v.y, z2)) + w * (SPREAD * (z2 - z))) + part.verts = out + + +def translate(parts, d): + for part in parts: + part.verts = [v + d for v in part.verts] + + +def body_tree(part): + return BVHTree.FromPolygons(part.verts, part.faces) + + +def inside_depth(tree, pts): + """Deepest point of ``pts`` inside ``tree``'s surface; negative is a gap.""" + best = -99.0 + for p in pts: + loc, nrm, _i, dist = tree.find_nearest(p) + if loc is None: + continue + d = dist if (p - loc).dot(nrm) < 0.0 else -dist + best = max(best, d) + return best + + +def slide_into(moving, fixed, target): + """Slide ``moving`` along the line to ``fixed`` until it presses ``target``. + + Bisection on the offset, measuring the deepest vertex of the moving body + inside the fixed one after each move. The press is then exact whatever + the lumps and pleats do where the two meet. + """ + body_m, body_f = moving[0], fixed[0] + cm = sum(body_m.verts, Vector()) / len(body_m.verts) + cf = sum(body_f.verts, Vector()) / len(body_f.verts) + d = Vector((cf.x - cm.x, cf.y - cm.y, 0.0)).normalized() + tree = body_tree(body_f) + base = [v.copy() for v in body_m.verts] + + def depth(off): + pts = [v + d * off for v in base] + return inside_depth(tree, pts) + + lo, hi = -0.40, 0.60 + for _ in range(40): + mid = 0.5 * (lo + hi) + if depth(mid) > target: + hi = mid + else: + lo = mid + translate(moving, d * (0.5 * (lo + hi))) + + +def build_parts( + slip_tie=False, + loose_tie=False, + part_sacks=False, + round_bottom=False, + high_belly=False, + float_sack=False, +): + """Every shell of the pile, placed, settled and pressed together.""" + bite = LOOSE_TIE_BITE if loose_tie else TIE_BITE + prof = HIGH_BELLY_PROFILE if high_belly else PROFILE + groups = {} + axes = {} + for sack in SACKS: + parts = [body_part(sack, prof)] + t0 = NECK_T + (SLIP_TIE / sack["H"] if slip_tie else 0.0) + for k, dt in enumerate((-0.5, 0.5)): + # A quarter step off the body's columns, and each turn half a + # step off the other: a twine face parallel to the cloth face it + # hugs, one column over, shared its plane. + parts.append(tie_part(sack, prof, t0 + dt * TIE_PITCH / sack["H"], bite, + (0.5 + k) * math.pi / TIE_PIPE)) + axis = orient(parts, sack) + settle(parts, axis, round_bottom=round_bottom) + body = parts[0] + c = sum(body.verts, Vector()) / len(body.verts) + translate(parts, Vector((sack["at"][0] - c.x, sack["at"][1] - c.y, 0.0))) + groups[sack["name"]] = parts + axes[sack["name"]] = axis + for name, onto in PRESS_INTO.items(): + target = PART_GAP if (part_sacks and name == "C") else PRESS + slide_into(groups[name], groups[onto], target) + if float_sack: + translate(groups["C"], Vector((0.0, 0.0, FLOAT_SACK))) + return groups, axes + + +def pack_uvs(bm, islands, margin=0.08): + """One grid cell per UV island; each island normalised into its cell.""" + uv = bm.loops.layers.uv.active + cols = max(1, math.ceil(math.sqrt(len(islands)))) + rows = max(1, math.ceil(len(islands) / cols)) + cell_w, cell_h = 1.0 / cols, 1.0 / rows + pad_u, pad_v = margin * cell_w * 0.5, margin * cell_h * 0.5 + for idx, faces in enumerate(islands): + coords = [c for f in faces for c in (loop[uv].uv.copy() for loop in f.loops)] + minx = min(c.x for c in coords) + maxx = max(c.x for c in coords) + miny = min(c.y for c in coords) + maxy = max(c.y for c in coords) + dx, dy = max(maxx - minx, 1e-8), max(maxy - miny, 1e-8) + ou = (idx % cols) * cell_w + pad_u + ov = (idx // cols) * cell_h + pad_v + for f in faces: + for loop in f.loops: + c = loop[uv].uv + loop[uv].uv = (ou + (c.x - minx) / dx * (cell_w - 2 * pad_u), + ov + (c.y - miny) / dy * (cell_h - 2 * pad_v)) + + +def build_sacks_mesh(name, stray_vert=False, **flags): + groups, _axes = build_parts(**flags) + bm = bmesh.new() + try: + uv = bm.loops.layers.uv.new("UVMap") + cloth = bm.loops.layers.uv.new("ClothCo") + islands = [] + for sack in SACKS: + for part in groups[sack["name"]]: + vs = [bm.verts.new(v) for v in part.verts] + faces = [] + for f, uvs, cls in zip(part.faces, part.uvs, part.cloth): + face = bm.faces.new([vs[i] for i in f]) + face.material_index = part.mat + face.smooth = True + for loop, u, c in zip(face.loops, uvs, cls): + loop[uv].uv = u + loop[cloth].uv = c + faces.append(face) + islands.append(faces) + bmesh.ops.recalc_face_normals(bm, faces=list(bm.faces)) + pack_uvs(bm, islands) + if stray_vert: + bm.verts.new((0.0, 0.0, 0.3)) + me = bpy.data.meshes.new(name) + bm.to_mesh(me) + me.update() + finally: + bm.free() + uvl = me.uv_layers.get("UVMap") + if uvl is not None: + me.uv_layers.active = uvl + paint_sacks(me) + obj = bpy.data.objects.new(name, me) + bpy.context.collection.objects.link(obj) + return obj + + +def paint_sacks(me): + """``SackTone`` and ``Stripe`` face attributes from each sack's parameters.""" + tone = [0.5] * len(me.polygons) + stripe = [0.0] * len(me.polygons) + owner = {} + # Each body takes the parameters of the sack placed nearest its centre; + # a tie keeps the neutral values, which the twine shader ignores. + for g in shells(me): + if len(g) <= 400: + continue + c = sum((me.vertices[i].co for i in g), Vector()) / len(g) + sack = min(SACKS, key=lambda s: (s["at"][0] - c.x) ** 2 + (s["at"][1] - c.y) ** 2) + for i in g: + owner[i] = (sack["tone"], float(sack["stripe"])) + for p in me.polygons: + t, s = owner.get(p.vertices[0], (0.5, 0.0)) + tone[p.index] = t + stripe[p.index] = s + a = me.attributes.new("SackTone", "FLOAT", "FACE") + a.data.foreach_set("value", tone) + b = me.attributes.new("Stripe", "FLOAT", "FACE") + b.data.foreach_set("value", stripe) + + +# --- surface ---------------------------------------------------------------- + + +def _sock(sockets, identifier): + return next(sk for sk in sockets if sk.identifier == identifier) + + +def burlap_material(name): + """Hessian: a weave on the cloth's own coordinates, stripes, dust at the base.""" + mat = bpy.data.materials.new(name) + mat.use_nodes = True + nt = mat.node_tree + bsdf = nt.nodes["Principled BSDF"] + co = nt.nodes.new("ShaderNodeUVMap") + co.uv_map = "ClothCo" + sep = nt.nodes.new("ShaderNodeSeparateXYZ") + nt.links.new(co.outputs["UV"], sep.inputs["Vector"]) + # Weave: two perpendicular sine gratings on the cloth coordinates. + weave = [] + for axis in ("X", "Y"): + mul = nt.nodes.new("ShaderNodeMath") + mul.operation = "MULTIPLY" + mul.inputs[1].default_value = 2.0 * math.pi / WEAVE + nt.links.new(sep.outputs[axis], mul.inputs[0]) + sn = nt.nodes.new("ShaderNodeMath") + sn.operation = "SINE" + nt.links.new(mul.outputs["Value"], sn.inputs[0]) + weave.append(sn) + prod = nt.nodes.new("ShaderNodeMath") + prod.operation = "MULTIPLY" + nt.links.new(weave[0].outputs["Value"], prod.inputs[0]) + nt.links.new(weave[1].outputs["Value"], prod.inputs[1]) + wv = nt.nodes.new("ShaderNodeMapRange") + wv.inputs["From Min"].default_value = -1.0 + wv.inputs["To Min"].default_value = 0.90 + wv.inputs["To Max"].default_value = 1.04 + nt.links.new(prod.outputs["Value"], wv.inputs["Value"]) + # Fibre mottling in object space. + tc = nt.nodes.new("ShaderNodeTexCoord") + noise = nt.nodes.new("ShaderNodeTexNoise") + noise.inputs["Scale"].default_value = 9.0 + noise.inputs["Detail"].default_value = 6.0 + nt.links.new(tc.outputs["Object"], noise.inputs["Vector"]) + ramp = nt.nodes.new("ShaderNodeValToRGB") + ramp.color_ramp.elements[0].position = 0.30 + ramp.color_ramp.elements[0].color = (0.30, 0.22, 0.13, 1.0) + ramp.color_ramp.elements[1].position = 0.75 + ramp.color_ramp.elements[1].color = (0.52, 0.41, 0.26, 1.0) + nt.links.new(noise.outputs["Fac"], ramp.inputs["Fac"]) + # Stripes down the front: Stripe 1 is one broad blue band, 2 is three + # thin red ones, 0 is plain. Centred on the cloth's front (u = 1/4 turn). + stripe = nt.nodes.new("ShaderNodeAttribute") + stripe.attribute_type = "GEOMETRY" + stripe.attribute_name = "Stripe" + circ_u = nt.nodes.new("ShaderNodeMath") + circ_u.operation = "PINGPONG" + circ_u.inputs[1].default_value = 0.09 + shifted = nt.nodes.new("ShaderNodeMath") + shifted.operation = "SUBTRACT" + shifted.inputs[1].default_value = 0.0 + nt.links.new(sep.outputs["X"], shifted.inputs[0]) + absu = nt.nodes.new("ShaderNodeMath") + absu.operation = "ABSOLUTE" + nt.links.new(shifted.outputs["Value"], absu.inputs[0]) + nt.links.new(absu.outputs["Value"], circ_u.inputs[0]) + broad = nt.nodes.new("ShaderNodeMath") + broad.operation = "LESS_THAN" + broad.inputs[1].default_value = 0.028 + nt.links.new(absu.outputs["Value"], broad.inputs[0]) + thin = nt.nodes.new("ShaderNodeMath") + thin.operation = "LESS_THAN" + thin.inputs[1].default_value = 0.010 + rep = nt.nodes.new("ShaderNodeMath") + rep.operation = "PINGPONG" + rep.inputs[1].default_value = 0.025 + nt.links.new(absu.outputs["Value"], rep.inputs[0]) + nt.links.new(rep.outputs["Value"], thin.inputs[0]) + within = nt.nodes.new("ShaderNodeMath") + within.operation = "LESS_THAN" + within.inputs[1].default_value = 0.07 + nt.links.new(absu.outputs["Value"], within.inputs[0]) + thin_m = nt.nodes.new("ShaderNodeMath") + thin_m.operation = "MULTIPLY" + nt.links.new(thin.outputs["Value"], thin_m.inputs[0]) + nt.links.new(within.outputs["Value"], thin_m.inputs[1]) + is1 = nt.nodes.new("ShaderNodeMath") + is1.operation = "COMPARE" + is1.inputs[1].default_value = 1.0 + is1.inputs[2].default_value = 0.1 + nt.links.new(stripe.outputs["Fac"], is1.inputs[0]) + is2 = nt.nodes.new("ShaderNodeMath") + is2.operation = "COMPARE" + is2.inputs[1].default_value = 2.0 + is2.inputs[2].default_value = 0.1 + nt.links.new(stripe.outputs["Fac"], is2.inputs[0]) + m1 = nt.nodes.new("ShaderNodeMath") + m1.operation = "MULTIPLY" + nt.links.new(is1.outputs["Value"], m1.inputs[0]) + nt.links.new(broad.outputs["Value"], m1.inputs[1]) + m2 = nt.nodes.new("ShaderNodeMath") + m2.operation = "MULTIPLY" + nt.links.new(is2.outputs["Value"], m2.inputs[0]) + nt.links.new(thin_m.outputs["Value"], m2.inputs[1]) + # Stripes stop short of the neck and of the settled base. + band_lo = nt.nodes.new("ShaderNodeMath") + band_lo.operation = "GREATER_THAN" + band_lo.inputs[1].default_value = 0.10 + nt.links.new(sep.outputs["Y"], band_lo.inputs[0]) + band_hi = nt.nodes.new("ShaderNodeMath") + band_hi.operation = "LESS_THAN" + band_hi.inputs[1].default_value = 0.44 + nt.links.new(sep.outputs["Y"], band_hi.inputs[0]) + band = nt.nodes.new("ShaderNodeMath") + band.operation = "MULTIPLY" + nt.links.new(band_lo.outputs["Value"], band.inputs[0]) + nt.links.new(band_hi.outputs["Value"], band.inputs[1]) + col1 = nt.nodes.new("ShaderNodeMix") + col1.data_type = "RGBA" + nt.links.new(ramp.outputs["Color"], _sock(col1.inputs, "A_Color")) + _sock(col1.inputs, "B_Color").default_value = (0.07, 0.12, 0.26, 1.0) + f1 = nt.nodes.new("ShaderNodeMath") + f1.operation = "MULTIPLY" + nt.links.new(m1.outputs["Value"], f1.inputs[0]) + nt.links.new(band.outputs["Value"], f1.inputs[1]) + nt.links.new(f1.outputs["Value"], _sock(col1.inputs, "Factor_Float")) + col2 = nt.nodes.new("ShaderNodeMix") + col2.data_type = "RGBA" + nt.links.new(_sock(col1.outputs, "Result_Color"), _sock(col2.inputs, "A_Color")) + _sock(col2.inputs, "B_Color").default_value = (0.30, 0.06, 0.04, 1.0) + f2 = nt.nodes.new("ShaderNodeMath") + f2.operation = "MULTIPLY" + nt.links.new(m2.outputs["Value"], f2.inputs[0]) + nt.links.new(band.outputs["Value"], f2.inputs[1]) + nt.links.new(f2.outputs["Value"], _sock(col2.inputs, "Factor_Float")) + # Per-sack tone and the weave. + tone = nt.nodes.new("ShaderNodeAttribute") + tone.attribute_type = "GEOMETRY" + tone.attribute_name = "SackTone" + gain = nt.nodes.new("ShaderNodeMath") + gain.operation = "MULTIPLY_ADD" + gain.inputs[1].default_value = 0.8 + gain.inputs[2].default_value = 0.6 + nt.links.new(tone.outputs["Fac"], gain.inputs[0]) + g2 = nt.nodes.new("ShaderNodeMath") + g2.operation = "MULTIPLY" + nt.links.new(gain.outputs["Value"], g2.inputs[0]) + nt.links.new(wv.outputs["Result"], g2.inputs[1]) + mix = nt.nodes.new("ShaderNodeMix") + mix.data_type = "RGBA" + mix.blend_type = "MULTIPLY" + _sock(mix.inputs, "Factor_Float").default_value = 1.0 + nt.links.new(_sock(col2.outputs, "Result_Color"), _sock(mix.inputs, "A_Color")) + nt.links.new(g2.outputs["Value"], _sock(mix.inputs, "B_Color")) + nt.links.new(_sock(mix.outputs, "Result_Color"), bsdf.inputs["Base Color"]) + bsdf.inputs["Roughness"].default_value = 0.92 + bmp = nt.nodes.new("ShaderNodeBump") + bmp.inputs["Strength"].default_value = 0.15 + bmp.inputs["Distance"].default_value = 0.0015 + nt.links.new(prod.outputs["Value"], bmp.inputs["Height"]) + nt.links.new(bmp.outputs["Normal"], bsdf.inputs["Normal"]) + return mat + + +def twine_material(name): + """Sisal twine: paler and yellower than the hessian, matte.""" + mat = bpy.data.materials.new(name) + mat.use_nodes = True + nt = mat.node_tree + bsdf = nt.nodes["Principled BSDF"] + tc = nt.nodes.new("ShaderNodeTexCoord") + noise = nt.nodes.new("ShaderNodeTexNoise") + noise.inputs["Scale"].default_value = 120.0 + noise.inputs["Detail"].default_value = 6.0 + nt.links.new(tc.outputs["Object"], noise.inputs["Vector"]) + ramp = nt.nodes.new("ShaderNodeValToRGB") + ramp.color_ramp.elements[0].color = (0.40, 0.33, 0.18, 1.0) + ramp.color_ramp.elements[1].color = (0.70, 0.60, 0.38, 1.0) + nt.links.new(noise.outputs["Fac"], ramp.inputs["Fac"]) + nt.links.new(ramp.outputs["Color"], bsdf.inputs["Base Color"]) + bsdf.inputs["Roughness"].default_value = 0.85 + return mat + + +def sack_materials(): + return burlap_material("SackBurlap"), twine_material("SackTwine") + + +def assign_slots(obj, cloth, twine): + mats = obj.data.materials + for i, mat in enumerate((cloth, twine)): + if i < len(mats): + mats[i] = mat + else: + mats.append(mat) + + +# --- measurement ------------------------------------------------------------ + + +def world_bbox(obj): + corners = [obj.matrix_world @ Vector(c) for c in obj.bound_box] + xs = [c.x for c in corners] + ys = [c.y for c in corners] + zs = [c.z for c in corners] + return (min(xs), min(ys), min(zs), max(xs), max(ys), max(zs)) + + +def uv_stats(mesh): + uv = mesh.uv_layers.get("UVMap") + if uv is None: + return 0.0, 0.0, 1.0, 1.0, 0.0, 0 + data = uv.data + us = [loop.uv[0] for loop in data] + vs = [loop.uv[1] for loop in data] + aabbs = [] + for poly in mesh.polygons: + pu = [data[i].uv[0] for i in poly.loop_indices] + pv = [data[i].uv[1] for i in poly.loop_indices] + aabbs.append((min(pu), min(pv), max(pu), max(pv))) + span = max(1e-6, max(a[2] - a[0] for a in aabbs), max(a[3] - a[1] for a in aabbs)) + buckets = {} + for i, a in enumerate(aabbs): + for c in range(int(a[0] // span), int(a[2] // span) + 1): + for r in range(int(a[1] // span), int(a[3] // span) + 1): + buckets.setdefault((c, r), []).append(i) + overlap = 0.0 + seen = set() + for members in buckets.values(): + for ii in range(len(members)): + for jj in range(ii + 1, len(members)): + i, j = members[ii], members[jj] + key = (i, j) if i < j else (j, i) + if key in seen: + continue + seen.add(key) + a, b = aabbs[i], aabbs[j] + overlap += max(0.0, min(a[2], b[2]) - max(a[0], b[0])) * max( + 0.0, min(a[3], b[3]) - max(a[1], b[1])) + return min(us), min(vs), max(us), max(vs), overlap, len(aabbs) + + +def face_area(me, poly): + idxs = poly.vertices + v0 = me.vertices[idxs[0]].co + area = 0.0 + for i in range(1, len(idxs) - 1): + area += (me.vertices[idxs[i]].co - v0).cross(me.vertices[idxs[i + 1]].co - v0).length * 0.5 + return area + + +def hygiene_audit(me): + ngons = sum(1 for p in me.polygons if len(p.vertices) > 4) + zero_area = sum(1 for p in me.polygons if face_area(me, p) <= AREA_EPS) + bm = bmesh.new() + try: + bm.from_mesh(me) + loose_v = sum(1 for v in bm.verts if len(v.link_edges) == 0) + loose_e = sum(1 for e in bm.edges if len(e.link_faces) == 0) + nonman = sum(1 for e in bm.edges if not e.is_manifold) + ret = bmesh.ops.find_doubles(bm, verts=list(bm.verts), dist=DOUBLES_EPS) + doubles = len(ret.get("targetmap") or {}) + finally: + bm.free() + return {"ngons": ngons, "loose_v": loose_v, "loose_e": loose_e, + "nonman": nonman, "zero_area": zero_area, "doubles": doubles} + + +def shells(me): + neighbors = [[] for _ in range(len(me.vertices))] + for edge in me.edges: + a, b = edge.vertices + neighbors[a].append(b) + neighbors[b].append(a) + seen = [False] * len(me.vertices) + groups = [] + for start in range(len(me.vertices)): + if seen[start]: + continue + seen[start] = True + stack = [start] + group = [] + while stack: + cur = stack.pop() + group.append(cur) + for nxt in neighbors[cur]: + if not seen[nxt]: + seen[nxt] = True + stack.append(nxt) + groups.append(group) + return groups + + +def zfight_pairs(me): + """Coplanar face pairs from *different shells* (copied from showcase/grindstone). + + Every sack lays a flat patch on the floor, so the budget is what keeps + two sacks' patches from meeting on one plane side by side. + """ + owner = {} + for si, g in enumerate(shells(me)): + for vi in g: + owner[vi] = si + faces = [(p.normal.copy(), p.center.copy(), owner.get(p.vertices[0], -1)) + for p in me.polygons] + kd = KDTree(len(faces)) + for i, (_n, c, _s) in enumerate(faces): + kd.insert(c, i) + kd.balance() + hits = 0 + for i, (ni, ci, si) in enumerate(faces): + for _co, j, _d in kd.find_range(ci, COPLANAR_CENTRE_MAX): + if j <= i: + continue + nj, cj, sj = faces[j] + if si == sj: + continue + if abs(abs(ni.dot(nj)) - 1.0) > COPLANAR_NORMAL_EPS: + continue + if abs(ni.dot(cj - ci)) > COPLANAR_PLANE_EPS: + continue + hits += 1 + return hits + + +def shell_tree(me, group): + member = set(group) + polys = [list(p.vertices) for p in me.polygons if p.vertices[0] in member] + remap = {v: k for k, v in enumerate(group)} + return BVHTree.FromPolygons( + [me.vertices[i].co.copy() for i in group], + [[remap[i] for i in p] for p in polys], + ) + + +def _long_axis(pts): + c = sum(pts, Vector()) / len(pts) + cov = [[0.0] * 3 for _ in range(3)] + for p in pts: + d = p - c + for i in range(3): + for j in range(3): + cov[i][j] += d[i] * d[j] + v = Vector((0.3, 0.2, 1.0)) + for _ in range(60): + w = Vector([sum(cov[i][j] * v[j] for j in range(3)) for i in range(3)]) + if w.length < 1e-12: + break + v = w.normalized() + return c, v + + +def classify(me): + """Bodies are the big cloth shells; each tie turn joins its nearest body.""" + mats = {} + for p in me.polygons: + for i in p.vertices: + mats.setdefault(i, p.material_index) + bodies, ties, other = [], [], [] + for g in shells(me): + pts = [me.vertices[i].co.copy() for i in g] + rec = {"g": g, "pts": pts, "c": sum(pts, Vector()) / len(pts)} + m = mats.get(g[0], -1) + if m == CLOTH_IDX and len(g) > 400: + bodies.append(rec) + elif m == TWINE_IDX: + ties.append(rec) + else: + other.append(rec) + for t in ties: + t["body"] = min(range(len(bodies)), + key=lambda k: min((p - t["c"]).length for p in bodies[k]["pts"])) + return bodies, ties, other + + +def sack_audit(me): + """Supports, tie waist and seat, press, contact patch and belly, per sack.""" + bodies, ties, other = classify(me) + trees = [shell_tree(me, b["g"]) for b in bodies] + out = {"n_body": len(bodies), "n_tie": len(ties), "n_other": len(other)} + out["support_worst"] = max((min(p.z for p in b["pts"]) for b in bodies), default=99.0) + + # Contact patch: faces lying flat on the floor under each body. + patch = [] + for b in bodies: + member = set(b["g"]) + area = 0.0 + for p in me.polygons: + if p.vertices[0] in member and all(me.vertices[i].co.z <= 1e-6 for i in p.vertices): + area += face_area(me, p) + patch.append(area) + out["patch"] = [round(a, 5) for a in patch] + out["patch_min"] = min(patch, default=0.0) + + # Each body's axis, oriented from its base to its tie. + waist, belly = [], [] + for k, b in enumerate(bodies): + c, axis = _long_axis(b["pts"]) + own = [t for t in ties if t["body"] == k] + if not own: + waist.append(-99.0) + continue + tc = sum((t["c"] for t in own), Vector()) / len(own) + if (tc - c).dot(axis) < 0.0: + axis = -axis + proj = [(p - c).dot(axis) for p in b["pts"]] + lo, hi = min(proj), max(proj) + bins = {} + for p, s in zip(b["pts"], proj): + r = ((p - c) - axis * s).length + bins.setdefault(round(s / 0.004), []).append(r) + stations = sorted((key * 0.004, sum(v) / len(v)) for key, v in bins.items() if len(v) >= 8) + widest = max(stations, key=lambda x: x[1]) + belly.append((widest[0] - lo) / (hi - lo)) + # Waist: host radius at the tie's station against the host radius + # WAIST_PROBE along the axis either side of it. + st = (tc - c).dot(axis) + + def radius_at(s0): + rs = [((p - c) - axis * s).length for p, s in zip(b["pts"], proj) + if abs(s - s0) <= WAIST_BAND] + return sum(rs) / len(rs) if rs else 0.0 + + waist.append(min(radius_at(st - WAIST_PROBE), radius_at(st + WAIST_PROBE)) + - radius_at(st)) + out["waist"] = [round(x, 5) for x in waist] + out["waist_min"] = min(waist, default=-99.0) + out["belly_max"] = max(belly, default=99.0) + out["belly"] = [round(x, 3) for x in belly] + + # Tie seat: deepest twine vertex inside its body, per turn. + seats = [inside_depth(trees[t["body"]], t["pts"]) for t in ties] + out["tie_min"] = min(seats, default=-99.0) + out["tie_max"] = max(seats, default=99.0) + + # Press: each body's deepest vertex inside any other body. + press = [] + for k, b in enumerate(bodies): + best = max((inside_depth(trees[j], b["pts"]) for j in range(len(bodies)) if j != k), + default=-99.0) + press.append(best) + out["press"] = [round(x, 5) for x in press] + out["press_min"] = min(press, default=-99.0) + out["press_max"] = max(press, default=99.0) + return out + + +def make_lod(obj, name, ratio, skip_decimate): + mesh = obj.data.copy() + lod = bpy.data.objects.new(name, mesh) + lod.matrix_world = obj.matrix_world.copy() + bpy.context.scene.collection.objects.link(lod) + if not skip_decimate and 0.0 < ratio < 1.0: + mod = lod.modifiers.new("DecimateBudget", "DECIMATE") + mod.decimate_type = "COLLAPSE" + mod.ratio = ratio + return lod + + +def hull_collider(obj, name): + """Compound collider: one convex hull per sack body, every third ring, fourth column. + + One hull over the pile would fill the wedge between the lying sack and + the standing ones; a hull per sack keeps it. Striding the rings keeps + each hull's triangle count down without leaving the silhouette. + """ + me = obj.data + bodies, _ties, _other = classify(me) + mesh = bpy.data.meshes.new(name) + bm = bmesh.new() + try: + for b in bodies: + # Body vertices in build order are ring after ring of SEG, so + # stride rings and columns alike. + order = sorted(b["g"]) + pts = [me.vertices[v].co.copy() for k, v in enumerate(order) + if (k // SEG) % COLLIDER_STRIDE == 0 and (k % SEG) % 4 == 0] + tmp = bmesh.new() + try: + vs = [tmp.verts.new(p) for p in pts] + bmesh.ops.convex_hull(tmp, input=vs) + # Only the hull's own vertices: interior points have no face. + remap = {} + for f in tmp.faces: + for v in f.verts: + if v not in remap: + remap[v] = bm.verts.new(v.co) + bm.faces.new([remap[v] for v in f.verts]) + finally: + tmp.free() + bm.to_mesh(mesh) + mesh.update() + finally: + bm.free() + col = bpy.data.objects.new(name, mesh) + bpy.context.collection.objects.link(col) + return col + + +def setup_bake_image(obj, target_mat, size): + img = bpy.data.images.new("SackNrm", size, size, alpha=True, float_buffer=False) + img.colorspace_settings.name = "Non-Color" + nodes = target_mat.node_tree.nodes + tex = nodes.new("ShaderNodeTexImage") + tex.image = img + nodes.active = tex + tex.select = True + obj.active_material_index = CLOTH_IDX + return img, tex + + +def bake_normal(high, low): + scene = bpy.context.scene + scene.render.engine = "CYCLES" + scene.cycles.device = "CPU" + scene.cycles.samples = 1 + scene.cycles.use_denoising = False + for ob in bpy.context.view_layer.objects: + ob.select_set(False) + high.select_set(True) + low.select_set(True) + bpy.context.view_layer.objects.active = low + return bpy.ops.object.bake( + type="NORMAL", use_selected_to_active=True, cage_extrusion=CAGE_EXTRUSION, + use_cage=False, normal_space="TANGENT", margin=4, + margin_type="ADJACENT_FACES", use_clear=True, target="IMAGE_TEXTURES", + uv_layer="UVMap", + ) + + +def export_unity(path, objects): + for ob in bpy.context.view_layer.objects: + ob.select_set(False) + for ob in objects: + ob.select_set(True) + bpy.context.view_layer.objects.active = objects[0] + bpy.ops.export_scene.gltf( + filepath=path, use_selection=True, export_yup=True, export_apply=True, + export_draco_mesh_compression_enable=False, export_animations=False, + ) + + +def check(skip_decimate, lift_z=False, stray_vert=False, **flags): + bpy.ops.wm.read_factory_settings(use_empty=True) + nothing = (None,) * 5 + low = build_sacks_mesh("SacksLow", stray_vert=stray_vert, **flags) + high = build_sacks_mesh("SacksHigh", **flags) + cloth, twine = sack_materials() + assign_slots(low, cloth, twine) + assign_slots(high, cloth, twine) + if lift_z: + for v in low.data.vertices: + v.co.z += LIFT_Z + low.data.update() + bpy.context.view_layer.update() + if len(low.data.polygons) < 6 or low.data.uv_layers.get("UVMap") is None: + return (fail("sack mesh did not build, or has no UV layer", 3),) + nothing + + base_tris = triangle_count(low.data) + mats = [s for s in low.data.materials if s is not None] + nmat, distinct = len(mats), len({id(s) for s in mats}) + idx_counts = {} + for poly in low.data.polygons: + idx_counts[poly.material_index] = idx_counts.get(poly.material_index, 0) + 1 + u0, v0, u1, v1, overlap, nfaces = uv_stats(low.data) + bb = world_bbox(low) + size_x, size_y, size_z = bb[3] - bb[0], bb[4] - bb[1], bb[5] - bb[2] + + img, tex = setup_bake_image(low, cloth, BAKE_RES) + bake_result = bake_normal(high, low) + + lod1 = make_lod(low, "SacksLOD1", LOD1_TARGET, skip_decimate) + lod2 = make_lod(low, "SacksLOD2", LOD2_TARGET, skip_decimate) + bpy.context.view_layer.update() + lod1_tris = evaluated_triangle_count(lod1) + lod2_tris = evaluated_triangle_count(lod2) + r1 = lod1_tris / base_tris if base_tris else 0.0 + r2 = lod2_tris / base_tris if base_tris else 0.0 + + collider = hull_collider(high, "SacksCollider") + col_tris = triangle_count(collider.data) + + export_path = os.path.join(tempfile.gettempdir(), f"bdt_grain_sacks_{os.getpid()}.glb") + if os.path.exists(export_path): + os.remove(export_path) + export_unity(export_path, [low, collider]) + export_size = os.path.getsize(export_path) if os.path.isfile(export_path) else 0 + # Blender points TMPDIR at its own temp preference, which on a portable + # build is the working directory, so the export must not outlive this. + if os.path.isfile(export_path): + os.remove(export_path) + + hyg = hygiene_audit(low.data) + zf = zfight_pairs(low.data) + sa = sack_audit(low.data) + + print(f"blender={tuple(bpy.app.version)} skip_decimate={skip_decimate}") + print(f"measured mat_index_counts={dict(sorted(idx_counts.items()))}") + print(f"measured base_tris={base_tris} lod1_tris={lod1_tris} " + f"lod2_tris={lod2_tris} r1={r1:.4f} r2={r2:.4f}") + print(f"measured nmat={nmat} uv=({u0:.4f},{v0:.4f})-({u1:.4f},{v1:.4f}) " + f"overlap={overlap:.6f} nfaces={nfaces}") + print(f"measured bbox=({size_x:.4f},{size_y:.4f},{size_z:.4f}) " + f"outer={OUTER_SIZE} zmin={bb[2]:.5f}") + print(f"measured collider_tris={col_tris} bake={bake_result} " + f"bake_has_data={img.has_data} export_bytes={export_size}") + print(f"measured hygiene loose_v={hyg['loose_v']} loose_e={hyg['loose_e']} " + f"nonman={hyg['nonman']} zero_area={hyg['zero_area']} " + f"doubles={hyg['doubles']} ngons={hyg['ngons']} zfight={zf}") + print(f"measured sacks bodies={sa['n_body']} ties={sa['n_tie']} other={sa['n_other']} " + f"support_worst={sa['support_worst']:.5f} waist={sa['waist']} " + f"tie=({sa['tie_min']:.5f},{sa['tie_max']:.5f}) press={sa['press']} " + f"patch={sa['patch']} belly={sa['belly']}") + + if not (BASE_TRIS_MIN <= base_tris <= BASE_TRIS_MAX): + return (fail(f"base tris {base_tris} not in [{BASE_TRIS_MIN}, {BASE_TRIS_MAX}]", 4),) + nothing + if nmat != MATERIAL_COUNT or distinct != MATERIAL_COUNT: + return (fail(f"material slots {nmat} distinct {distinct} != {MATERIAL_COUNT}", 5),) + nothing + if idx_counts.get(CLOTH_IDX, 0) < CLOTH_FACES_MIN: + return (fail(f"cloth faces {idx_counts.get(CLOTH_IDX, 0)} < {CLOTH_FACES_MIN}", 5),) + nothing + if idx_counts.get(TWINE_IDX, 0) < TWINE_FACES_MIN: + return (fail(f"twine faces {idx_counts.get(TWINE_IDX, 0)} < {TWINE_FACES_MIN}", 5),) + nothing + if u0 < -UV_EPS or v0 < -UV_EPS or u1 > 1.0 + UV_EPS or v1 > 1.0 + UV_EPS: + return (fail(f"UVs outside 0..1: ({u0:.4f},{v0:.4f})-({u1:.4f},{v1:.4f})", 6),) + nothing + if overlap > UV_OVERLAP_MAX: + return (fail(f"UV AABB overlap {overlap:.6f} > {UV_OVERLAP_MAX}", 7),) + nothing + if (abs(size_x - OUTER_SIZE[0]) > BBOX_TOL or abs(size_y - OUTER_SIZE[1]) > BBOX_TOL + or abs(size_z - OUTER_SIZE[2]) > BBOX_TOL): + return (fail(f"bbox ({size_x:.4f},{size_y:.4f},{size_z:.4f}) off outer {OUTER_SIZE}", 8),) + nothing + if not (LOD1_RATIO_MIN <= r1 <= LOD1_RATIO_MAX): + return (fail(f"LOD1 ratio {r1:.4f} not in [{LOD1_RATIO_MIN}, {LOD1_RATIO_MAX}] " + "(--skip-decimate is the designed fail)", 9),) + nothing + if not (LOD2_RATIO_MIN <= r2 <= LOD2_RATIO_MAX): + return (fail(f"LOD2 ratio {r2:.4f} not in [{LOD2_RATIO_MIN}, {LOD2_RATIO_MAX}]", 9),) + nothing + if col_tris > COLLIDER_TRIS_MAX: + return (fail(f"collider tris {col_tris} > {COLLIDER_TRIS_MAX}", 11),) + nothing + if bake_result != {"FINISHED"} or not img.has_data: + return (fail(f"bake failed result={bake_result} has_data={img.has_data}", 12),) + nothing + if export_size <= 0: + return (fail("export file missing or empty", 13),) + nothing + if (hyg["loose_v"] or hyg["loose_e"] or hyg["nonman"] or hyg["zero_area"] + or hyg["doubles"] or hyg["ngons"] or zf): + return (fail(f"hygiene {hyg} zfight={zf} (--stray-vert is the designed fail)", 15),) + nothing + if abs(bb[2]) > ZMIN_EPS: + return (fail(f"zmin {bb[2]:.6f} not within {ZMIN_EPS} of 0 " + "(--lift-z is the designed fail)", 16),) + nothing + if sa["n_body"] != len(SACKS) or sa["support_worst"] > SUPPORT_Z_MAX: + return (fail(f"supports: {sa['n_body']} of {len(SACKS)} sacks, worst base z=" + f"{sa['support_worst']:.5f} > {SUPPORT_Z_MAX} " + "(--float-sack is the designed fail)", 16),) + nothing + if sa["n_tie"] != 2 * len(SACKS) or sa["waist_min"] < WAIST_MARGIN: + return (fail(f"ties: {sa['n_tie']} turns; host under the tie narrower than " + f"{WAIST_PROBE} m either side by {sa['waist']}, need >= {WAIST_MARGIN} " + "(--slip-tie is the designed fail)", 17),) + nothing + if sa["tie_min"] < TIE_SEAT_MIN or sa["tie_max"] > TIE_SEAT_MAX: + return (fail(f"tie seat ({sa['tie_min']:.5f}, {sa['tie_max']:.5f}) outside " + f"[{TIE_SEAT_MIN}, {TIE_SEAT_MAX}] (--loose-tie is the designed fail)", 18),) + nothing + if sa["press_min"] < PRESS_MIN or sa["press_max"] > PRESS_MAX: + return (fail(f"press between sacks {sa['press']} outside [{PRESS_MIN}, {PRESS_MAX}] " + "(--part-sacks is the designed fail)", 18),) + nothing + if sa["patch_min"] < PATCH_MIN: + return (fail(f"contact patch {sa['patch']} m^2, smallest < {PATCH_MIN} " + "(--round-bottom is the designed fail)", 19),) + nothing + if sa["belly_max"] > BELLY_MAX: + return (fail(f"widest station at {sa['belly']} of the body > {BELLY_MAX} " + "(--high-belly is the designed fail)", 19),) + nothing + return 0, low, high, cloth, tex, collider + + +def wire_normal(mat, tex): + """Baked normal map under the weave bump.""" + nt = mat.node_tree + nrm = nt.nodes.new("ShaderNodeNormalMap") + nt.links.new(tex.outputs["Color"], nrm.inputs["Color"]) + bump = next(n for n in nt.nodes if n.bl_idname == "ShaderNodeBump") + nt.links.new(nrm.outputs["Normal"], bump.inputs["Normal"]) + + +def render_still(low, cloth, tex, path, engine): + scene = bpy.context.scene + wire_normal(cloth, tex) + for ob in list(scene.objects): + if ob.type == "MESH" and ob != low: + ob.hide_render = True + ob.hide_viewport = True + # Level on the floor: turned about Z only. + low.rotation_euler.z = math.radians(-10.0) + + floor_me = bpy.data.meshes.new("Floor") + bm = bmesh.new() + try: + bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=60.0) + bm.to_mesh(floor_me) + finally: + bm.free() + fmat = bpy.data.materials.new("Floor") + fmat.use_nodes = True + fb = fmat.node_tree.nodes["Principled BSDF"] + fb.inputs["Base Color"].default_value = (0.03, 0.032, 0.037, 1.0) + fb.inputs["Roughness"].default_value = 0.7 + floor_me.materials.append(fmat) + floor = bpy.data.objects.new("Floor", floor_me) + scene.collection.objects.link(floor) + wall = bpy.data.objects.new("Wall", floor_me.copy()) + wall.location = (0.0, 8.5, 0.0) + wall.rotation_euler = (math.radians(90), 0.0, 0.0) + scene.collection.objects.link(wall) + + world = bpy.data.worlds.new("World") + world.use_nodes = True + world.node_tree.nodes["Background"].inputs["Color"].default_value = (0.02, 0.021, 0.025, 1.0) + scene.world = world + + def light(name, loc, energy, size, col, rot): + ld = bpy.data.lights.new(name, "AREA") + ld.energy = energy + ld.size = size + ld.color = col + ob = bpy.data.objects.new(name, ld) + ob.location = loc + ob.rotation_euler = tuple(math.radians(a) for a in rot) + scene.collection.objects.link(ob) + + light("Key", (-3.2, -4.2, 4.8), 480.0, 4.0, (1.0, 0.95, 0.88), (46, 0, -38)) + light("Fill", (4.2, -3.2, 2.2), 60.0, 8.0, (0.74, 0.84, 1.0), (66, 0, 52)) + light("Rim", (-2.0, 3.4, 2.8), 260.0, 3.0, (0.62, 0.78, 1.0), (-60, 0, 200)) + light("Wedge", (1.4, 3.8, 2.0), 520.0, 5.0, (1.0, 0.70, 0.38), (-94, 0, 194)) + + cam_data = bpy.data.cameras.new("Cam") + cam_data.lens = 50.0 + cam = bpy.data.objects.new("Cam", cam_data) + cam.location = (1.22, -2.30, 1.18) + scene.collection.objects.link(cam) + aim = bpy.data.objects.new("Aim", None) + aim.location = (0.02, -0.08, 0.30) + scene.collection.objects.link(aim) + con = cam.constraints.new("TRACK_TO") + con.target = aim + con.track_axis = "TRACK_NEGATIVE_Z" + con.up_axis = "UP_Y" + scene.camera = cam + + scene.render.engine = "CYCLES" if engine == "cycles" else eevee_engine_id() + if engine == "cycles": + scene.cycles.samples = 32 + scene.cycles.device = "CPU" + else: + try: + scene.eevee.taa_render_samples = 64 + except AttributeError: + pass + scene.render.resolution_x = 1280 + scene.render.resolution_y = 720 + scene.render.image_settings.file_format = "WEBP" if path.lower().endswith(".webp") else "PNG" + if path.lower().endswith(".webp"): + scene.render.image_settings.quality = 90 + scene.render.filepath = path + scene.view_settings.view_transform = "Standard" + + fcode = gallery_framing.check_framing(scene, cam, hero=[low], elements=[low], stage=[floor, wall]) + if fcode: + return fcode + bpy.ops.render.render(write_still=True) + if not (os.path.exists(path) and os.path.getsize(path) > 0): + return fail("render produced no file", 14) + return 0 + + +def main(): + argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [] + p = argparse.ArgumentParser() + p.add_argument("--output", default=None) + p.add_argument("--engine", default="eevee", choices=("eevee", "cycles")) + p.add_argument("--skip-decimate", action="store_true") + p.add_argument("--stray-vert", action="store_true") + p.add_argument("--lift-z", action="store_true") + p.add_argument("--float-sack", action="store_true") + p.add_argument("--slip-tie", action="store_true") + p.add_argument("--loose-tie", action="store_true") + p.add_argument("--part-sacks", action="store_true") + p.add_argument("--round-bottom", action="store_true") + p.add_argument("--high-belly", action="store_true") + args = p.parse_args(argv) + + code, low, _high, cloth, tex, _col = check( + args.skip_decimate, + lift_z=args.lift_z, + stray_vert=args.stray_vert, + float_sack=args.float_sack, + slip_tie=args.slip_tie, + loose_tie=args.loose_tie, + part_sacks=args.part_sacks, + round_bottom=args.round_bottom, + high_belly=args.high_belly, + ) + if code: + return code + if args.output: + rcode = render_still(low, cloth, tex, os.path.abspath(args.output), args.engine) + if rcode: + return rcode + print(f"rendered still {args.output}") + print("grain-sacks OK") + return 0 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except Exception as e: + traceback.print_exc() + print(f"FATAL: {e}", file=sys.stderr) + sys.exit(1) diff --git a/showcase/grain-sacks/preview.webp b/showcase/grain-sacks/preview.webp new file mode 100644 index 0000000000000000000000000000000000000000..aac56e9e135878d81112d947e2c7cbb4a3a4f21d GIT binary patch literal 30960 zcmV(>K-j-hNk&HKcmM!bMM6+kP&gpmcmMzp76Y9DDzF5j0zNSqibNtIp(LWz2wob5F#7&_ zb@?&q>*xJ&{-5u|{fGQtcWb}=SAqZ7zn*8;wg1|G*z_s!U$D$WJ@+wQSyZO_v;3aL z{&?!|Y%47QaG~c?SbcD;1*0J?cM8dRTJVr=I&Vu;g=0%y!m?am0)vtxiT5uS0)ut8 zg=0f*W3CzE=6IWd_bjr;F-<;hl4$r|5e>&p>1uCl(;Zx{pIIwm`N%yR9}jS?z7l^L z-~mTYlHvOh!@^^Q&ZH~9BX_tB)|2=38%SDB1$P^d+v|z>?GtU`ao?Ry4KL z3QfNtwpD;`E4;5<$^6>RJ+Yvbb7o2PiQ2oy&q^o|D%h>bOZbp)Kwi(KN35kT5YACr zGg}Q#+)+#j^7>`tDnV?i=kUhUyaRUYUq=7Rd|Cel(RGObXW&nP4LOvNL6Af%o-O!a(`B7RRA?t1}WwRBdUB6lNtl7V%<;IwlyE;V-$V zD4(SikBwdOx@dnxt7ebKU@jU{lzM*R1dRitXXf(u`FCdeoD#PEp-8&Vx&qh_ZCLpp zKL(&uy#+0%&g29UQKtWTRlXK2$onK=4Thx8>lpvX@tiPxc z*A-QQD{wl!--dCay?@(p!mH46erCpxd}84i+doWyJ#N6e#3sm!dJl2qPkHv|f0HbF z!1PEr?RMo2t=zwIZ6F{Yno{|6nBP!Sga0c2V2JrNmkHJ9hf&KhBBHp;9u-o^G$qdU&jhJ@Or4^aSlb0OBc^VqRUN$P&In?*3KAgk|e58g( zh_^PE3C+yc_#b|}bJPV0?|HttTNrCXt)#R31YSSWlbL+G??5E^WkqRA+seF?a+9i} zEdFxQZ~(83oS(5ilSer7%@SGE#0)q7%u4h{q6Es3V26{t5Gd7_eTU4BjRj(hc|BXE z{68lri3}qrtxLQPhQkFbrH{qjRei+7(kbdVK#&FL-*(c91o!?`rL<@GF=fK2O2m_(MMYdQ{M*520i9U}?K{_Pw$yi;kNAOviMfU+{-*WwjfbK5x* zf9?_G;yOEJ)iXd;I^`MEJa%Iss!-6F9SPC>>zNKO2P2o3CHr1exgZ5r#rOL<_`-Jn zLDjV1aRfXja-hQw0vUbcxsWCp6Y8_X1<{>e*N{E zSc)A5V9Ja1^O_9~c5Frj^6{hBX+pEThD%V3ij@?qv>f zu$KZ6Pt@E*VJ(ygU>f8Hd-%R$m;XU|me?-B9eZYd&PI!zqN{ z)Qbv`B|bsZ=1Gjyu(`icVpNZA2asHTgBU>CZCF?XVA5T4_NUTyaE7;w`p?Nw~7%P zJ5}vVX1&_I>zjfm-iY$sDWuh&!=@aP-(fkH^6+}`w>?dsNUWwU;UZd!2bCu%)niS6Cc!u1 z^3Bfsr&X+0LX=QTM~$bN_SsfIK{oa3UQI_cbAqEJPpN`^`;`Ff%N}xPN8~GP4*(@ZXZ{{I9bPSP%Ch2Ht(UMH{Y4^wOsrf-q)1wDQp4f`3_~`R<0Q zF9z1QXEED~E2<8pEap5zcV!}*hemXMS5`}s_HlvqKN7lHOp8Tj{#O*ZxO#|v*wS=8 z|8p?wCbV}c1^}{iI@sr}W}!Kb`dDcjAcE8qbx6#69F~;mC(U}Kd#q|CEq|lY`XTLeeihBFxQ_5KB2z-i^o3 z4F6VZ$}XVE=8?Lvk_8Qx$}bVx;+~`891djGF?@J?`^M}d>uo%aNd?+Ax~cThJR~<) z5%#HE6yhDp2W}>0sOj`bnnE`rWLmNzdacU?&N{6jtI|jXD+KgnQwW=Q?4R2T!!zaU zJQJZpunrBnGIX$`1ekP{%|&<|4m7| z^~4GQpecv_AmsoJ<89~O$0dRiqb;wms3ZVT?hfN!Ze_+h#DUF z7e8uk>jwwa93Yf%i1IVJ6k5>rgay!Mjt})fZhqqy_O2}aEB!fQGVB)X25NRs zEN5FoBp+~m;3g9{e&E-<{TK7wqsF@D!VGLnagK23NGE99x6F$li7X{Zq_h%= z*rwAxB_R>F*d5Z9eJNRR?xr!hhb@=PTkcv6hfE*Uz3lj%R3ODz%HfIv5J8JwAb{d) z3X-mj7`x~+Yg=%yW~v3j`{ducbtNFG8q42eCZzpbWcfRnIVFkXC+r-4!svy8?TI65 zj8x1t$B*zgsZX&s4`4()N%Bf=a!W=BFH|OAt^_7xF=lW6;D^-m1!XX()!eQxX?QfA zZ@JPZgC>$jVdd<&lmP&ofzDfS0$jyID69E{)>T8qEFhMSaOKzFo%NVc27+qCwGm>t zMc@9?(f|G1+S7OXK|~fqq5KGwyK)umQj;~`y$b-mv0!#Vu=bhCPg_2$Mo1J3wZL@P z_I^Q^htRsb0-iX>5+N&uv24|O_URb9yNIa*GDl-~+NBSsh-8jp5FXLE!Zv8fe4h(; zp)Lh~^n$OuOxcX{Z@*ulxiN!)Gg0zYkBVCr;Dtqe6J@|oI{P4|bZnG3*7%xF1tu}h z;2Vp=u9c#oO5EJ^j{VgrAn9sfZhh;vIG7)jlQ&i@mIM=EYm5Mo#b?BpK~85Bok-9aUuDd(5h2-hl?pdNI(1`CRQ_6m;90ii8GYl^U6AG%fMzhbyF?WqU(MGTI zW7>+V8R|7je@`rR)UVuU**m?AS%g|7Jz%GhrGVlAOATax9Xo4n)E~Q*b?~3)t)UrF zJ{umd`?kIKMm1c5#A+HSo0{7H4!yBPAi!+|Nu8^=`e6R6sAv)VqHcqtSzF%BLhK&X zBKqy}>Gi0Eo}$P*2U7<*>z@w(htjxMyx4m}fUsz^ z2z341E0MxPbpJKLh~3095fv+3I{(ghC%=Hs-PFutuJ$RydEwf@T8q*Oj;{E}CN&YkR64-*oa-Bs>RF>ItC zp43n~sR>?NS~JP&G6bIEp2Zldva0v`l4UvN8B0c8T#5Z~-ReHQJ5@~cG*P6q*h&w0 z#QE9!iLE5kOlJk=r)22bLGp||ATC550o92C@_lYO7w~cJ4&C_cW`CM<(ymc@ryV`P*g2UJG`$ zM85e`l#q9tZdEqA?rxoPzwME8evjiQkv&n|G_K1pP-HF0peR1#aSWC`p7&*Hz%M|7 zov5aQbY?;PO7>|WGE5`KgWy6GKc+Rmab#0#(VVqoZ43C7)_;~w5snlae}oh-zw;H1 zE^jATB9KbCn^`x$N5(pB!@JcRd>*dJh3`aw)g}B5ehPYybna7wwfro>9O@7-aE}M& z3$25l)?%_3xH)_8`iM(Cs15)qn7SBOaneRAk2N7?@f0qITg+>J|3S$is0w=Sg4o;G(U0!H^ zJW@7hK)WF{Kao{b0ofaC zF5>iZE4_s!It#Jr*W)Pz3b<#{%GDa3g0#K7d z`&YB`exNmZhiP<{>ub*-l&k4jM4g&?r58RQ@} zn~Z`9@mtY&ya;PQ+_2QTlyeHrIB~eNTgMj_+>bL)*l+CF9ePt4WH zt!yO1AjpDGC}7q0)b%9&ghU~KDXW9aRRv+1mOV#J@TejeRlqQrg6+~d#DB55N4CFI zhAHi$vP*;VTS=O>;gq7lR#W6H>3MCf@pE`AX#fBEy6UBzW}af`0Ow%2OJGX`=npV+qO-40r!+PEN!df4;Jk8EoHsVloQW*uvOHvl`+$ zA1Z=_;T!o$ZnkV#b{<+fsj;bC-zayzrGKF*i>>>hsYK;J1h%bCO*Si^b8>zANML;z zDp!F#k%Fyh-n?cPnn5nXXY`vHd z7%?a-0N7bPB zAb-iH6*NNTok~67mWqrBb_Om7bNL(6Y(YtnKg>e(ti#EI32O`jDAlRSoTpoU;gbSM zbyKUC6ZJ&ckz%=uJ%@KXDf(BOVw$>l4LLoT<;r5CxsNY|UE;w+jo|mdE?UodqIU5G zNT2_xSwcU6=Q`?Blrau?X{|s!m)nh)a$MFpH!DvCr=ED$687$sg|UO$yPHX;Fm?=c zW&Pv<$Of5{6oG~&h6t?vBjv@fS%H%l={BtuSpKJvv=>9nvL`PXnr6$fi3%g)E7B4dR{|l+UvHhXe$PnTz*Il zlI!ql--2<@Z`pHY@y=$1mkMngR-CrIXg_M1sD52f*O=E4XB-kC6KXTPCw*#>a7w9O^{zz@8r;S|#@Y2cmkIHUjxu`I;%o_DJh-4lhr=z`&bXJ5 z8q=sTDoTw?Fde#R2Gc77$VNBk6uWWMr~1}hLJP9An zWM#C`s*fTG+aFe^u^D$B5Y%T1AnH!nzS(x^>%P=#O3l&y8%;Cf&D?+WI%c}$v9_=n zskdDgr6BPln9giKtFWwS%E8l>oFT^NnQ#xPd#v@upv~>+ytL~RQQoAerzS4dkGxhI zujHeT7EQZPlrLhLTb6u9m~yGj0*tYr{h98NV6@heOS+Z@-N#vx`)#QdxR^x{22T|4 z#|oQQ6PZpKWoud*5A$)&W5Axfg#0td8gM-ZzFK&i#8QQ8>{cXkX>qF$4-ggB-#UGh z+_BT=YI!eeVU~+bX%8ih-t;^QFQrnQQWjL!Zo##08=8h5O81}hwKKHV!N{quL!-LNWGASNF`r8#2fxM9U@@_)K12Qw!vB)c(HMfASy9r+f5L&UX;i;yEi z21pP=ruZP%3w%ED=WJIONritWdXcvU|);dAC(Z^j#gD`zpr3s zPPft|BPl`5uAvc+3*Ls;4+1Tp4xi<(B(M1arp+@7UNNQ*U^e*EI4dX><}l)S>9&r8 z`k%UyR@p3h_N;(|hc0h-Ta`f`JD_9gTHLA&83Z^+}PB-oxWZ!UAE^wFT9jhm7E~yMnfHKpj#tjscQ*2tARwLFi^(XM>-~8TaV@ z4+s|;QorhmwJAMJky_mTm1DGKykRNrH2G8n$!Oe$mBevCd&a){rX(qbne4nL9^Qe| zt(LC~_JUpUwXoOgn3n;U@3wGjO7z+T-4YAooz*X#_tYj zQ#!(TT+`zq2bD`at=e}Ai(&d&-~9ftsNx@v&`UXJ&xaGZMi(BywRoFOg|o%BjGClV z+(nlg0;mFk_?W$dTAAbPUBr0xb=63^X0ZzW%o(w3Mg~2zm4m$)uisc+F9uLS-FSH{ zT4f8-{UV+FxD%)lK&T2A+P%^q2Wj+i+PD*581l3L@Hh2gd29Akh z#+S@w~K2pd!d5=U6afr_Vkos3c?g;sG7ki z;}*Qvp5}Ay|5ya;bo^Y$c(1=hJ!k<1oOby7K&`3~ByXWl@R(8*KX3SxzJ2M{0dS&} zCK3a)x5`>BXwbwNiSgBN$)IWhRPJg94geOpc3c~l5L(Jfw18zp751Y>Mm0{Bs0Kc*?JeQjcZ029a{q4mCoJSafh z(Z?)79QM6z$+FQAolZeCEaM*l^3x*B`3`MQu_t&i7mOJd5blE>vZ`p}o#l|G^@Jfq z^->oPFth$fNU$3sSAmI5bpwcdUApW0rk7`bPl^}cgfGb;$gjy@h=>|5(s^w{GDQKZ zx)_1be{Ej{4Nr=hDQCRkiPPP(wvw6Ck+)Yl6=k${Gk5TF6sxr_)-D`IB=C_4fzV|TR+>~Z3Am!zz?YD<;Ksw4!VP#6_seHrSie(Sj&RNQ;|hAT1rE_$Q`2h z%jH2VK>+ivU;b4IC6z_|rPf$hwFHRpRU|wMN0o&C-KG<+;NO=wcX5l;W`JKzlc^=! zTdsvVHjP;q-P4LCP+MNZA}D=J$guIv_2mBmP$q*B35IPLhr@*7KH)Z?A;rFK{8>{p7T{H^_1m2t?u9B$SzNEnR_{)KMkzA;H;B2g;OSD7^ ztaf-_jp!gaGke`nlYHb1ZXoq4l=q@D_VC$7e9M{Bb2--s*rD(?=a3w_oV+UM^+=i1 zzSB|){}g6ucQ+Ykqepp$PT$tnM|qe5^N`5rKn(Fdm&3vQ@Z=cW*tBg?V@QFS@g^V; zaj^*c&<*4{7+Z=mfjD^oa%HL{T2mD2Sk!@53S14MJy@G=^c%%W4O{Jo2v%wx~nra6bWdksf7G5f^ zR?Uv%HH4mVOO>y_0yl@61^r4Yfe+wkIZbnH61-z#QOQ6Qd-x6?O_=Ti5Ahl9Oc%5% zwlVvmnX#kBqP7kHqOiDC{;pmAWf%g|IdU^MEKh22{~J6}d*;##CJX}+J@?C9HIvnl z@oT|X;w;q+LB z6*h0=E%m^fwa&uSJ?TingfjJ_5bU(ppw{$pCY6ag+(T{w0PvG;00n2NeMs`Syk$Ys zI$Cdp$`*+cS;Ge`7y0DqOe^uoPco`g6JuvwP%{&c%!fDneBEQN9mCIr1aHa^itKW? zN*N9T9Z0VPX&;RpsM5Yx0w~mA&b-3)gv|a&^bNYqQ#F!yv5QFu{RHSfhOc`M7YCdT z4UdAbu!AdO=Xd{Gj|(KJ+t$op7LyY^N$-KJUH!Wo4T*SxpzKCkszKT~oDxgBHyBtw z%mjG0)U|AL@ONRetCzQ6EXJQ1`wo1m>epyB7M=u1CuE&OcT$NAgUCIU<&ibc--btFn12g?vA)Acsr&0A9^z;8e)0A>HL z{~v7HG_4=cLqv9Pi7HU>^?zk9+U#M)VTqJIiEhh2Q?hfVm9UU9V=ewD%cZbhgy=Y_# zw3Wm_H^4Bs8!O0jqN}ntnVBCOXT;Y<5rMcL0iT`EO?dQmU?c518V&x+pSmqXKEX;2 zn-qK)?kOSU7TaKmKkMSh_KG_wWz{!OK-MVKI)2LLQeNOaJ?^4EiVK{?Umv|aNK05b z2MhJ^px#)1EFg);n|cIYQhjV6k+N}b%*Oh-Vd_}VjWRzEpHiPEyqVa>RHlehwWG@{ zNdEJe(3Gb`u<9huCT%>|D(`Z-tLPu@XFP4PN3hOlFvWD2Zqj(?77E6AetHVD!ik*; zUs~)O+C37$wc2He$#aHn0O=VeQxE$a7$gR4gZaC5F$;xd6}n+Uzk@(~2GR+}9FpD% zV=?l72{S*;Xtbkqg<$~z9FQCz@UH7|cI6W>+7;?QOM=IafUGVk3)v=J#R1X0_DQLm zvnwz^YTz&!4fPzpNA*nVPyfQ}`L4W@ObZFgY0o4w0_HD`duL1tL>l-qsj(#zaO=L$ zUG*j==uH)5`a(YzLFyiPP?X`E@qQCZmZk7zh&O0NhJxnzVTy^Kzjg7e>#RVLeSq6J zU+vWRt&yyNO~ZMhQyp&!h5Yr*mi? zCx}bLzPdU{1>vjztNn;r}H5{61q9L?|F`Nvs&JF@LrSMmqnOC3y)vAuL zgj#)dV66^vkG^q3A7AWi`*~#=jjC z_bc|x;)~YQqr>43imajiFqn70SrnJi@PMy~w#A`PD+wMR^!Q&tJl!6?J3_gEyWjjL zto2ip=RGkZde?DJfh`F(lhdt-Q_2^0yuZ_lEE7R;7nT`0k7uI4haKy|>ZwR37O7sc zUssowdxQ*b^yKbQaxo~yx-(7d%!KAr*py_u%_b^M1Ys;I^;@P-%>fYIoh!86PVW~t zMz|;EdaH?-eZ0TnKB*#JIN73BWSb6H8j*ll;#R{*yF+H>q3?gXn)QG|GJkDOH`lrf z9@N*)xcpSh%+bLp6vlHs3|6ZK<7x5zaHv$fTmw}g4a2eIAnay?YgO~`ZOYW~*dL$~ zvr{kBn)cWOTrT!xr{KvG9&mF~IKc$mGF4;q`-ZcWn7fFpgf5OZTV*1zOS=R3KMLeN z1ak)B>B{$zkcJ+l?|86)zwj@3>FK-|#Typmr(MahjHequ!fX-n)v|}}6dM+*RGJD; zW7?m2U8AMLw{S0x@753v)_=F)W?y#fxggVnf$v*FBB4HmiUx=WTVTcu}O&k+w zy6Mk#5|Fp_XlxA)vqcWJe+gSw(a~$&;F61T>*!*n#V2;cKHsdg2MK^Ntpte5X~@L| zPh_%X9ICg$V3S8_p}HVcylyAQtznC^ri8Go^&!0uvZ(>-Y_&DWcDS%;%SpUsPjgt^ zQ;nyN-q$V9dI6V{w%tKI?NH#aKPpq;%x!E95t&%crgrmUa{rP_4ti*j-NH&o@?KR& zI1D+BSUS!oQjJzaF96P!jIVunhyt$eoGi9>0@U<|E}p02Mm57Io1Kf$5)J|dhL;>Guq`;O!uogykHrgwN6*wTnzW&`)`pgxD<8_V{9{cK zX-#FicA9i+9u!ig!Cqrm0$7Ri-c$n_l6j6w;k>|1FMA@SQPfyU!d@Dj_A{S5d=`R~ zOdmY-Bglxlgd&xs$!|OSc{#$(-+Kix@)IOq3!d#+K@AqxFgohYj|(qLIa2fe!Zb5i1>QIZ!tumWxIHvM?OxKVh!m9&jZIz z?M}!Zdf}P+r+IQME}}A5REf3io>G7GgNAcgh2(HQIUB*kRb~ zQXuCO=EX0rEz7EfL2;a9*wMr`ryi(dHJo};$Db8w6_jl%0J=>B`0|_IetQbQNeLTD z>SNQF(lp;02nG4AoE|1)@LiXS-nUMYOeAU-ZrkY53d{M#%}619SIe|#t@@5sU32*^ z+jb?HX#=`TS`{`9h_#}C%poC)dR#Aa^w&%{p#^skDD6xhDuyDz&$~H;O%)FaHBysg z4`A?v2O3{j1rackX~Rt8B-WM@#rO(*>_jMsvk-7$tGPNx_fSRHdtqlu_KZ4ncS>RBmm1k=2%}UEQ3y^1)IPBUC zusaLv+*U~IZW4s9qT~Y5ZA?xIB=rk%9UbCJ12u5gd7o1vFB!3*#mXMoAF`)w%BvaA zxmjy*-nbR&hM33QsscD_^drE#xNcWMQ)h>kfC3Ar23ix1ViI95BC#*3@%?Wx{uElLTz-_mUg{aC|e3OREqaB#uK2LsijWu~#x+oE0odr{7!HokdAe zFJHT_%c=_6cQtnwU_C~Rx65(D)}1rD9Z1tYZvT0J)0 zt~l@R-IjTA1*MIH5qJ2z%s9Fs9nzbhaU0s7Li55Z&HImKeZGc;0T3xguFAu?!l;xR z#Kr1y)az}GW{feK-=$_;0%ktajkvtVkW%was+>c%hz^56%07^mFN&IMN+GYO*Wk<^zdc zp&&S!Qj%xt)=M~SxEa>^tUtEc&6cT=lA=DFyX6Pr;daAafD`8mx>1rAzc`8iOGozU zxf6>_lwt-VJLq*6RXbHvS&`fIL)YE4AXZ86nshMRtppsl!<)=TDaBAtwuHu^0xV*a(nLgjw^|!^QAvgNIc*2CkXDDyU?z5cxZmg>Z z(d-S77=cIOOF|9)Kt|v*d}!0MF#PXKUc5fC5%us-`%l? ziY|&PSz%nN@$SF+>b(snJ$WcTTT%yGZ3BrMmeOu(?%&2-ZCZ@G*j=I;j{TAj{yqI7 z+i;ve-8>FS?`B`mhKW6}N5c7MfBzk)i~Ve0Os-DD}~e0H*Wi`#GV(5oG#vaq{GE43Ny77J-84 z3DnP|M*uW7M!kA8(YLyqT`V9pKm12mZ9FHnnRP8quh=Vk*oM2GtFe!0y zXbU<}x~AIbDODEf)CYmq7CJ?q;;71(gjsw7{>uJ)$ZrBXh6^m#I-6}kn~A%DMdLXv zdx}aeJD%L^%VB;cXm7roE&wO9rzn-KAfq-lo!fK?d@|bfh`sq!^=Tl0{+v@;KY5wG zymej&HA`E>wrKjnhSEAS?M*3RKf%}}18T08PD|?-0LPP`4=>e_mh`Syrg5+6>dq*S z5V}~J8_5!+(7X+Q{YzOdl0%!Q`w@4j*(^#GQoPvRO8+fdevl-`kFymgZ7Mi82>RTi zgM!BuBhk*0B|&E62B){UUua)z@p-#>!?OXPboy@y0-@aoTxc2Qj`m%8$Gm8kz}p;* zJn^n+86!q*dXDykH00A?SVS}&EbjY|G4(apGMALKN$Hlm4kUpA99s?%xWgozft@mN z1DDq6zxiYqdA|mz!_~hlE>2LZ3*BZvmB0bAJ^2ZHT%H9|Q_Ww)sDL01v}Za{QHpfW zarzB`d5FhzGIiMsRzcLsmtGM0 z%0?58=D`d39C(9_qB0j#Z<^ON9R!zrn*qi@>A<2Goqni>0oK9$TzR_?eFxgp0ABR7 zXKq32f%Zsg-uaNoM*Hz4>+nk-E*Nwq;6wN_>pQw*=hnpbGU7VSKS%VwTThCrPdK*Y0YAK9H^3=E-_UIR4C7m#C>Umkien?7rdLR-hQ_@w91LSc z>H#z^U?{TQnX;$YX?qd*RVANNNR+-m-uSlSEnGL%iHz??Ge^50RL&MEG z4uoMo09D}=CWXm3rO_)sg7;G~9B&rbKB6nR`FFBEi8Z2q%LcDEtd0GzRiV!x&h`Q< zPjkkD&>5XUA70EKw<(Y~Vu12u&z=DzS>{#tyl2d2}KiDYFqp)@faES zb(p|i!EF@2WN71{YpNWe0BQvqwdW=L;^Uyl;QF%%l@8?q%Yxl5sY)m2@@Lrz@ek~a zPY)+L5v-1Ycs)~nhWj*bxI7(nYQiAInGS+bg^SMFz;*CnIj48_m*ishZu0GgZ-k|J zBnVmu*vZF{_VlYtIZuqme7%zyzgBNf(f}dl9s~qz*RGOicR|2Y+uSz)LRVi)>d&k4 z*8xJqK*|6BY)v#5>buMOJ};^<;>ZqCIvn(jO|_3liIL||3c3o&vL!F9&C9Es+w>QNq}-MUNrS^vq5l&rZtlPm;#_y`$i z^>t4zTa7HD0-k$qG8I?oS|e=WrL@AcAZ63wQ3G#LYbIl0hU1hiN#{4gQO%FMv#a4# z)gvu4se&!wIN1->3hTu!6AA_QhkZQHn=f2$6;JY^KMe2YA)7G5W~=ROUOSMBIr0Bm z*H0PFm9BE5Cg~b}&D#LYFb~;tB*Z|wcbYp-(y(}+g7K}slECa(iRnrJJhYdAcGYke zJWpoKMU!ff?f4dH9$8tLbz6$n(@gD%1tj~y$x&37A6zn*hroHM(wl2_i!ssm z$VWEUD^`%;{!GP@W7;fQP?PZ&_?XIMsMoGz*93^7`vDS@-_cPWBGd^6eq_@#ZboyT zPBsHI?F^G}4iJk|P=pgMi8pbcG^vm^nwSha=8P%785CsRuQUj?M_e$;66OHS`l^#Z zaKi%mFkvH-ZqfV{_!V>wXC*-4ROyymcdjw}T{8$gY|();_@w$ z0#m)5IuGkJ6OpR_@j)`%A`uWK6~(o#=E>VEZNL1-)o$o-{!C-v36Fr>-k&xZFV#r+(>(m_` zn@^8vB9x0h-=N3FB}Wi{1p2?V)aal@76(V2;p1dv!`Q58^U_->b0)a058rx5Rgp8o zbr@ILn!<3ezBQ|Mh?-!;9y^D!m$G8jr<{u930(5mxPPjcIrwumB&D@~2Dz+U~%YG3Y_K;Jj zIV~+uA=5zdKLq)NIFEKdj>~g9%}285#4G1S ziXOi*fITi=1E#tb0P-v?bfpH&t900lDM^63i-YjtN9UMp;&q?5n*kkz7Fp+CjSUV_;U8Zn1r{in3%=k3S{vYjH!xGCzp9lQFlY0HGw@X(vDY z_?16;`xIKqJ${~VzN(^DAqZ6#L4L-rVmOajYZ}bI9%k5+oYOI+ED?+d=Sdye-ubD} z#Ipkt(%IX}4WfakqqHh-NSB01EWLB5>JRzolI`t6UmUuO`Xy)}r^x&0U#X>IuxW92 z5A8_b=IQ~av5BC0(2O>$%{oaAD?lqp>QR2tHJ+3Gyg0B#JY0sHzc4=m6WYyyTz-Tv0l-cBqm^0QNBUNfr$N z36033h4d@J87SOi;UVkYaxPR=q=cjq@ocqe6oOg*KreR;j{X@<(EA0Qroe+{I0 zK6LdK{cHT2vQW1@$|`A&9-^%(8S!L17=@%sc}@TS)AxQY`CYsF!l-OmV=4VscRu0g zGt7$2RmAn0_;|3Mpf(?SS;(ka#~{o>71xQ4o7!XIEz%Q~$)mNs8e7ddjL{pTU>2%~ zI{2f!ple+II^V{x0KlMvqY4#G4 z*k&O_^c|T`&&J9rTiX@zG8uXQ39rnn0u}Q7h}L_JLX^SyV>IaTveD~*&#RO4NQo!A zZ(jYlS)WeAt57b`6Hjw8ix{VZA*#+=2<{lm?+cXYlY*5)yP4jotMbNZ`SBBf3aX*d z0{J5}WOK2qhdg^lL1qsi*;=wuI@g$(=VH=f_B56P2=oO(%9zo>^(EEW`h)1GVZz+h z)+!ffwU@B1dVusfgwphY?tUQXl&f_h2-ayQ-s^9tC;?|g9ca&&oN7KCa_QU7TSK@X7c47S7dqI}%_aQ_9w1m(Sru9j3M>qRfy#km>9?lT;#O~*>>1Wa;D^_Xd{h{e z=2K$RIX_RcxwDwNXI=2&O|sa3Y`HO-jcWRkVPU0G#*_{j6*f<#Z>A|Ngz$d zKrj==MK)nJT-ChN|6bL&(f!!+m44kbPhEWu3Z`jueF*e;UwkuvSpF!FmJ7MEm%G~n zHgcAWxvtmZfX#!CP7!IUx~A~z@%7KqL-?fp=~+OmXK?!~M03j(SwN2`fX7X561+%4 zw4?D87#4`jAgw{k3pR&hF(nqcSD6p_O6_J*x++C(xcKDahtJv1cT=lJX&LMO(~Q4G z31LWC%3gA`K0S|y_&wSp2!6KyqTz^w{GC?70|A2W=yS#h?t~(N;zL24!IA=Kq=G^;ycP3#qWBN z$ezDzoS0>}-J2`q80QRi=Y>&u&UG9NF!){Y{uPsl^JMcu`8YZ<6d$$#is>_mP$uQc z+b)Z@3aF6BT5XG)20{fG}kYk)FJiR1rxp!>x1lMfV6VU^e=h+ zcEY*|3r3Pr4W@y?^Q14YM9Fopy;Qo%ZUBwZai1`vAd`!|uJK398l*M9e1U_{TIk;S zbs1=KP@O~6Sn-3i|G!7P?L<2nqfMt5r&c1nO^fdGOmuUI_2B2xj$-US^vOE+evaH& zpLh$}r}X*0`3KUDBjfRBKX1MX61g|S!X*A%S{@m+i&XxI3B6@bNy8Y4tw%(G6*60H zpS~J~%wY+3)*NhomeSTmHtGJj`sK==Bg@wn)7TvaQ-&71r0oZ>gTSUrRkkcbvXgiJ z%X8U*H?B;^D5{Gv2j)$0db&pvM=6X~gCY4FY$Co|9;BsIPQi|$?g&Nw=Y7`)TkARt z!6>E^|8Y^HuS9Sw4_P)wbplnTrcwOu1HOz-T3H>0rpY8nJ^$Xo)~R;uzjhHlYy+2J z>q`*J(mc^GV+y-@`uJXdv0bS|Aouz|DH#j+)9lJUP3F)waVt^wDn3Oc8;Rpr_tpk81%>Ktn9`d(t zCfRh;$A~jF`McX#0e7uSGC-~31`s$6s^Nk4TSDv!=WT&MmX2ud1nx@4oR8*49dBbJK;^aZ|N4BQuEjjE(buur@ZV2U z+cRS5)heveJUJ~XklK7Tg2*H?o>iqcPs(3>@@7bR?nEJ7rrU|^qi39P-+t8NIO7w@ z5B&>M?2U&cFXXEq&#wJ-8s&CM(V5H`Rt>9dgPYnNwg=FuAEbQNmH|4a{6=t`|a# z<>~vHURAXrY>d&5lrq21I-H?np(`-2ylgIK_=52a6W}lZ=Ug)>)rM8SGoa9q>f?_N zSM&JFs?xjCt7raEY4bCq;%H|XDSJaVLr%RJ0pBb92Vnb^{N>!YpDV^HoUOgvC4$qu zvK2%Z`C=5ZwB;y$;~tvG=qn1S5I-C52m~(0OPGaM6bPBb zJ{=POeCv?}L`z%<5TzY&@;OWf-x5TU>a?fqP4FYtelh~`y=D}~)SxuN_YSC2_E~>; zIFI`fn;Hf+qU+Ki=G|50cIRl4)Nph@yT$-wvChw5p{#rs>b5#9(ixX9CJ_Vv8W+ab ziDpIK9pxuBQDMF%@ppt~$ReZ=wfFID`GKmG>FezEX|CZ-^)Q6?Q`)!nBv#i4WoxY9 z@(A-`OjndYY#bk!XNM}5QTNl!%qjK@$?(9p>*9x zKuS-zi7VR>5KrGXIb_JAQeih=fQk@(3t~nxw=9LB8E0Y)L)8-WEb@N#`b57#pSWR; zCfj50WS@DN^wogFCkBdlE~!!!zjh=`%vzF@amb6hs>lqhBZ-ZEm7pkZYN~irev@P< z>UR67BE~G!yz-09uh@WYrG<3S8e$YM3vJ(C%x{RQvAG|k=IkvS^&cZFCsijuLHB>@ zj(ly%jVMq*2jz7twV%O^mOYf?obvz2PKv&{Mb`$rsI=NoJ_{m}CvWyE3mxwgE;ER? z!w;gGS_=f6I+o)nQMEy3@YW zl0f;?;@3)f%sgP(7>C!@F2#UWB@#7W)Y%QwL-!&EIu4do5 zl&FsX8izsJ0Z8D}GQ88p$hqsjI>)X(!NghuFl;nW9pR}ue4cqUojd`HX1G5geWzf~ z<+Azjf&Bn>>~1zkKeOoob(>U+-)?sE6MTo9P$yK8^9DirdeE?K#2kQW_Tp$K)JkC! z%yJ}kx#jD#5&6W&7v46~6+Fg;035hgmOr~X{g0N2tNov@^?33-xG|9}G5qfnM|3x2 zle7>XL4k67sPBGBx9`bnHoqhh$CiR zwA5^2gcWBB|H-U8(g!w~=xr%4n=nWtP!(lhXdzZStT#m-<|@0Ra~uJGgTpabvU|&8 zc_1SmH|6yqlm_V1V4YoJJ}v|LCDNAI1NvTx1g1d>_f}|f&Z0oqPEAMkn1>h!b)bna zG#qh6qPw^hNjMPotsCAseG$p|wkwC##E#Hyux;nazQ=c=b8@cRt?~QFEH)s)7FvDz zGDn!yh`FZm(Ab$~S*MuBqDK^~0MaU{ur(BRuxwq$DrovI{zDKzEUK zz^VkSjR~(~%U{aJMaOkoVFyG?h~RTDQ%In`f9xlo#K#J{0Xf9Lahe_@HTXQ+d)I44 zNHP_7>gED*EET*RE25`$cm1Sd4btVITNQy%d%Js^d;PDmWSf|!RsGFuiI4jhUn->y zR}q8*o-NrY0hc6l{D^nhLH-PY1j)~7m}T@M8q@Gj+Yv@B>au!~`#FUQrkB^D)28VL zszES)dcVUgL0(-FD{j)5898n@!7`AgbWQF>Nl5YJsB zPCCl$Nw;Wtg#hCPbG4)%INvTRY=8>TZZmB|lK9oP=r^zVK4b84X>W}+Yt{m6F(>vK7)p&;!)8#yi z7@1sWj(3Bhym6#yN_wrQ-l1{6dKFpGwQBBFs!Y}pJ)oO|W_KeAvR;3ejd=X&Jw+$sK^-`Fe-wb$A0p9 zHk8ety9QRSdN@F!VDHIgl(cNOapFOoee^lW-#*lTNB_amU3V7LJ)Ml|ZK#|fmt1Zb zn`L;v^~VIYO#2=Yq;)p869qdDk>6r)23$A}uasL~&l!6TOiddL30x*iC0>5ckTfv5 zKQhdVHCkF3N&vg5cuxPSX`zG}4qs)nE=cUL{+|_Dg`EmP(%5F!4*2plK;oalQL)SG z{2bLA&Z75y>LeRNp`z&TQW&@ZkTlFS*Cs5b0@WZ|5y`jaus0!v#K}2CCNi#v6VY+5 zWuwB@ST)fCcCTTY-S>goUQgs0Zt&+mP_CkFk-T!n9TS0UH&y!$cA+LQA75NANYIg+ zN5JKHKA=*s|IA(I%#`<7yZfEd$Q!e*-kR;o-bZ1dCvG9hbtH5Kxr*L>6<}V|@Mz?E0W;j9KUZI%F43KZ6R4`NUz7dh8Njr?Trl%tPEex9 zV^rxMusmV9RJIehj(INn5RQ~Ye?QbM&*x<`w9P~FbYmW)-9$JD0^NBX#Q7Zp$4-X; zXp@xUB_vQ$yg*cMZ4XxGKgutzRp0>TLMO+!-Q1KGc<;~I`!VLwiU~@gHg}D^uMwMsk0ln5S#==YwA^5rxvv-luH#5pbUey^^RL7K{V%Qi64f=%8B^HJe?SEeN45aTaunI*1 zh3=iifkunpo+=?|hKTGZA{-cJonF|r1$Jh_X|=s4G$;;(Be21(b1(84#l0VMtJH!i z$xo#6>-MM2=8kjll^*j^m(`ENdGt`S%<<>}qCY8Nul%QF=T5K~+0)gw5okzO$8#4or2a$rR0&_D0?PmBugXG zhrHwJh3ob*6DKS4BU%v)z#gxMPEGUhonh1+h}kyYg@zwx4wx#krDSJw_JzHbraa1Y zA1@VMUctQ2RAyEX@^v*5c80 z9dMVE-??AtYlrgfRDoQIPB*}9kyvka=fA4&q!j1&4)Uf{qRxe;AI}?^RAEV+Z2dWQ4sn=hwHRJ5V9=MlS?DP6+tKJjk&WOPfsFm0L!F~W`^n%> zR;^uh9Y-Oc02 z1HUW4RPH7So|Q{vsk&}gKbNR%|F|(mBjfL*nF-J1S30tfC@Wz%L{;+WF9z^6+$pJt zEwK_MMelOAJ*UGg^MU_YzcF7^Bp!@dsz!s_N>~Mi!3OR} zsm+r-ZH^k6&PRX~36@p5*=OT~_nwGs&;dsBb;sIKO#M^@nVzqj9dKT#Pk?buJ9}Ae5)l`EiDsqR;jDx*_nT5 z7v}HpS~Jsmeq?OP?8q__xpNXsj|65N-4JFrb?uLS5(o33O@|=b@*U~`IK*N5hDlW6 z#_r@f42gig&SUISG(aw6-12HApC3Sg&HQT=!jg=PlZ8A$y+yI4L>_qo4d9E058&-2 z;k&9_`E_(>p5fStDj-@l5;i$WlXZISOVgZDZfXO-k$dOzaB67>ZNPh4_2&QuXkmKW z1g#W@rb``isxqc>SMUYNkh#@+ObDp~h8S?zzUpg{YNTpd1QKZ;r{QFU|67$?e3`^B z8N)COWkPV>6#TQIx$U`@!tQ2h?r>1q{#P+Hev*$*=5~(8XIxTnvd&oOf)ih5R9QmP z8CoI%;wk;Sp{R+Rr!-&@U|qZR?xvy`&yDc}{}6IFs~2eEbK8V!rLZ3Tq%?6wzA~Q< zq#q`TjOx_G3I zuk>m&N;%ALRnf3re$*6;?`54TdT&g@;lU8DX()4q^_XeZEK8&EKXpKD{)52HGEV&D z!yjo~JQdv#_oQ1(FM!~2I8C)6yUT$)#;+_sfg~yzQThl6+=x0%k;ab704rJ+uCMw^`D|oewie2NE5~-+B1X)OXPyAM3=es! z6W1?9@ecrqu?6?iN*JPQWkjgYT8T_2%|y?rx6}I$=i>f6ZTQmvMn;CB!$v4`;DcVQSjr~Fz;lo0 z*n7}UV^^*)b|0KQhin)XlDISXBATr;Wxcf#CSpZSmi^oYeGP~!nPCM3alBd|@Yc6j zsbIbqlCaS|ID(X&?-Xet)A~@OO!hCaChIvn*>kDT8?ayvQ{-f%uGEY@D({00brtgr z4E%bWZ+@&8cW~F~dm7wc;cqAZX3@$62NwCo0>C!OXjvW+dGUxAkx5^xQknDPcj+D@ zip~YXYJe0IbJXX9v#MMvlv5oyw;oK2_E25<3^8U;g`59^)kDwL_~|ZIeR{m1x2;;x z|F5aocapaE`^di25+c!Iq%rLG>OHlo&eA`%0Yc#*<^3tlfbP%q%JxYn20K-LHK-tt zK}IXj)NDuHRV9L4uicU`|IKg3k%(DvUqSNhIRpkVeykimUXt((nUg)0AuezwVKA9S zpG+2+W;n~cJTS+k-&&pOoP)D?-}yU110>L3-ZG@JHd#wm{Y=izINa7=0Qf~O7~#Ve z5YgSATE+H=MtxnPqv9(R^o;DPtK|bRNKG_{^ZoTiH%}?+p8J;HQK3yp@E+F5JpuFr>9pOnP3!HTt0L38^9wIk z*=C>XL*$pOJ@J-XPtg$oUhW5qVk9ek&G}?Ao`pv;-%>{rT05Mufl?4+aVYmZ^q?^L zs~LOxEtarsNW;;yep5l|l+h@77vc$Q*o~w6H6P0Ihz;+?>2C%#e$IsWhhQ-KEY=tQ z-4}A$JmU~)m_gp+nO`!?cZnn}oHi5cnnAL!?327HX4N_~PfP*fQnlA&5GTn4Mh>lF zKhSU+A;qyFltG6RStLOzjtRfk(^}9=c``jLf=nnopfvd=)r5yH6@)0xYSTA+QhTKB z@lgtX3$bT}?gN@bYU3%j*(}!2(j~MW;f1ovhBPEVTO8!$ukBtPIwJG)0H-56*!J)L zr#=U7yo%j$~1rp4C3k4*L{%m>~>=D+MZlAaK0L`n}Q0#DR3VU!nKJ zhIL!p0go^QvwJzPESFyVN>B9xxaa?x9Y^NCquSb^h8O^u*ewOT6$4-yWlo|p(`rX@ ztSzWVajkqmvw;7j;nq=EJGCkIi@Hprvh-drXqar~=yBq9(_iK6I~@at5L6uJ-#Cr8 zKhbu2mi^4=z7fLfOPa#rfO=v6H%mW((mT{T%rw3v@8Z;27rTx7Bw~0pbZh<_5w{w_Z8yzj&RtO#lgIVbjxMky0lG?5;gnOVuviFa%RcG)+iDPwvID# z+UN5?T0S9kf^Qrik1tzfnhemFB6>1d2>z!{&;!h(?4TX49Fxu~n;q%UhmN<-kz4}_ zD3jSfq){cqCqsmeL8eHCsq0Gb+4)fqWd;-ai!{()_u3&;fndnFNM2T0)FKBFMfBn( ziwS$Ia>FM+fq5hwLQJYez0cf(e8dMaMgwu)to-@%U~#q+XuDM<*PCv>SsXJHeA znB={mAfI^YlGD#KEhe~j!hNC+{=#r@dah05R9zsQlx|9{*pv|OJq7SgzIVAwD=Wc7 zyOhQJc*nkG8CfOa*C+1C&)bim?+oJKL%W(7G0oNHY!<{1nGVc$i4a1f@hE+gs(UU( z|1|C5Em$`W+5b@@UrUk_8Sbf?3wFf7+MpNLkjEy;qs(qvd^oN+N9f`msGh9SX@m>N zPKO)G$EWzxIYQ`PP|LAriIp}$jicd`q_1hSiP-D9n!<@gjTBk0psu)%#-Q93SEzKH zPeFqYH4{)8J%~3wu?NxCZpsXhP-gA6(lLiaYHrbmmAJ~>=+o3j&HqrN?UQ3$Z5LJ9 z)GjOUBnh7Vhv{myWQ5(j8cNNk-st;iDG;vfM=uoGo|v} z2|Kg=8Fea3V}InI!*1uU%@*3=T#>M4m8b_jw{MVQN7 zL=n5YQEPo3QI6*!b9oeWb4?1R2WBJgxCIWq9rC%}%S$`}tyuGz#HxPZcN=zf#}YwW}X6ABejG!uekF+hBb?)Vw86A%Tgn7_60c)55B)P$GwIX+H@ z9YpQnu4;o~3KD^8p2`i6O;DE3V?4Af$(#UweJhoa>Dh%-x`eQGN(~BD{c`7jVxLbx z;KRN$2@3Z)=t!g^3GX0G%_l$YPuyacC)zZz_7TZX)&0@@u#{a(cQ3>ny9h5#B+<}` zawA?KAUP&^S0JbNJ_$lj^vT#~m*2?&W!mi*>||d~rVL-7|8% z-t@*W+ge;>JP}OQi8$^dELO@<_n6AUxQ%L&n~tjCft1{9-83bE)*-Hu%hDF)8Tj%* zBAPecA_id?Jr{0m4@P=0iI*?tk?uZ$qT#Bv-@?aW6<`z!3ic$>kq2jfH_eo+~g#Ke>tXh?;Yu_gmT#m z0{2%v%p^>{b-%-iq4a=z1+;f<{gY?~N|YRFVwKCP=TBJGneY~0xNAYE`==0H+Y#z3 z0wTxwgiXjmP(UEK6dI2%!ns!9*OM4t4j0*Dz{}!9S>ZToV^zG8M;%nf8-UfCh`5l9 z#1Gd%7eT8uKu0(iO`($BNl9`l=ZkkbPYoC-4f(Nz#&ODneI++%<2$mDPp$w4bZF(j zu~PqkPt5=^X@tQ2YTxt0Bb~-E#{8FpeX}Hu)S?f>I)|59Yd=!H#5CWoq;cfcY%{Z{ z_&IQO5CXs4nDa66(M9ZI?sJxKiimp?vdKNpnssX@C#tu(THP z9qWWIhebvfDHBRGk>#5UmK%r6fs#xnHU`2|My^AzX582lBw~cS~a5P zwuKh0zSF_A{(Ab6!mye+Bq}mslwo0)v*SE>58R9(?%T86NqGBgYYmFBL0$Bb+lGAg zHZHRHoWs!qHrbVk;Hq{Uszkk&bg>37J;}jJKa5Yh;YT_T+Csg}i?2e^?r58~ zcfNtGAUxVOPH#Z8n|*XIJ8)APwCzT2#4!lmdvn$4(B{X%7Dat2#YY!l_H%bloEqh{ z!K9PTZRfWA@=YZO!sCwMH^7IN_@j!$TsGAQo0(o$^!2Xa|DEADOeN? zR{zdmG0~yADDG_bjrDp&p1e--%sXrMX5n&bmr>%oV@==d&I+MqmS#b@=B<|c^xB}T zFU8-S7up{Xj~!0DS*Hwg6E6sXnqN zm|y({YfQ#j7`bG^gl$o*#XPVc*cj1APB245#dealf6H~eW65+nOjD9sjt6jPa97fI zX2Bh5ULiR%>`zh27Wl?(c2ZKf#tr`QW@tB~ak@bH!VmI*Ombl%)l#oX`<)fk;6MKD zIyERvh>~273yeprosO9--lY1-9kjZhgN$z^-oy@c$6*Ld(S!zE1TNv*C>&L{PD04B zN5ZtYtVpc^+Zf#Vc=2-+Q0+KyhOTp&Or+lum4iXEb;bA+v<}w+7`rw+7MRmbYTE5T zv_mB=@ki;SCu%J2DSSC@E2Q=W#}u#WhyU%7eNP@t?J#3X;b8{qsM{_To+L7w85G!P ztxR5N(H3JnWU%8J%sf55*?h65@M8+k`!)@^zL7?VnO2QGzCHA>_U?Gx^Nq`QLoIp3==f01yf| z4_<~VwlXV3<>%3wQ6tN~0jywX!B|5+VAu-q5!_y&JB41r8YvaHxW!2jF@h2D8%^h= z0(AHO?0E-=tcXWgXJr6D*44VF>Uh@ijgwBO;Fvu=DP}N((`_mgN5y6nnSjVj1sSO zAM9h3g2R1n*r((afL~axW)RGfev{wdjdeL`TS9QAcPVtD$x`^DbiNP?xMU}W>nMN~ z20pI%CH{ZiM&3plF0=s6ASSO?kED6a|>q#{0bZ>(*un`pF5{svkfc_l~m;swI*N&BrW-(ur$?Tgx&yu-JZAOxZWd9B8A zaE+ka^%+mgPJ_``YnvP3O0g~tsi_JZL~!aYwsp1zmk{K&ZNMPsL8Fx!UBnY@WmBX9 zybmbJ%!y6UYI``kEcy+)==S;^M0xAJ}q9X8haP45Ls? z*XQpmd8@x^Q_Fkvm?=A#x(&KQf#4=QYsTkj9`yg7T(3b+EBMi-;u5%@8KW)S1c*Sg zc+*TN7_5z{(@_Ru@LGMhRgr&a(XN*C5~w<3I=$(DaBLafGSyB>5UhG$@4R+#gzVCV zvIvob)p=%S+XaQLaCnJ(UP9HMVY9v2mXmAVC(L(Pt<5?vM5yCV25#^!Y~*mHdBf#6 zIVXHZ`zG*1EeWrF-`0l8DBJnvYT>IV^0DG_=2*hDM&6S4YgWR6Od^;Na>mL< zs-{d#im3}rmrQ$xWdlnh<04|Q(4mll<9Wc+gAfG{(XCe>;T5e(iUJWQoZp>=+J7oQ zC1?CHzaBH)>LOxetN-o;)OEomnY#6KNW-JUe>biN?9EZW19Su zd-22;WovnSS3yoR_0qdBO)6JJXLz7R#X;hzhDqsh_S*6{)^!?DN-tX#)VQ`|UKuI5 zI7X+D%H_wcPrXvs|?xpJXQK&K9pY?T^RSN%NCKO%T zENa$3G9vLIYml^=(ClbF?RCdB%IX??<{kBJ1FZzi>NiPBND*|mxCBl zThTG?Q(jeGUE0f)8)`plv_=j>MQp;bVkW5NUcIz?*4`Qmn5Gq9R7~eJY zra=W4oN^YWNl*88Kr;6%}@JBh_RW$B6bcsd^EjGqupJ&3(@VO||ULCw&|_(Ye+ z70;=DFAM&sy4gWQ%okx_9YOLgA(gx+cq=_-O@1ZIPc6QZF_`^Yv`*T;0WQgtkdS^_)Go0 zIMg4lYcVdh4@J3-vdzTe2F1np0)>J#PpdQwp61OV|1gR6J-|@1W@Q@=izBZgbFMo* z`%~5a7SJmPhl%TRgWe!6NGuIXVY032R>WBU%Ll*JUKF|=jsp=dfTHN=61$%`b$N4! zu?LRls8S8bou5jWN32HsKMk?Z|49P-9={&GVrepE63)vu@&t8R7dmPPdZUg>5zi4s z)zMl;b0BdJs^W7<=Nh9R2;lmK2{@!wyEd|OP!;dcRZRc@n`6Il{B$zX1HUK^f8V3| z*YkSy9`6lXfvYD_!`}L4wERs_#7Ju`d16(V`cqIn;tKo=gBv+X!FP2Pjz@hb+^CMH zV4K!Y=%)z1HFLbb^-0dCzBY3d>#`A@@-^m$CyE>_;MNbyUl^2N-)U_l(15WXiZ(Ki z`tQ1!*K!V}dNy2^MvXUarsrnFcX?#oV_$QtYVuFm0Y2x#;20m0=1N{?L8Z_E(8>_d zr!GnE&LZ^cv(qKAnD>sqj7^a85i`h5_?+X_l1r}?K3XE@E{$yzZ?1Vkf{7m>zR$yu zm|?#EOB*9bd|QzR|9cYoM%2+}^IoRz5RG@=F7_ktC4D!exKZn@oQHu*=`MIR2@Ze* z6{VW{Nv55z=`-7UsK0}lz-?$X*gOc3#2-|q&T`;s#tg&Nd5KUzf<0pj%k+r%CIe<} zdow6$>}CdG?Og>n26+{|<#!yn2?i5}=U|Y&FFEWdIEINV!AnZsn@} zWt{q)cv@l|(tlJYkzq)R!exCUnXM2%P^8vhNhct=AB(?GU%6<$+mD0z#7z={w8ghL zE}`G!puowoIuMH}EUVuD_gI^S_@Q#$u=q0^%MPzzmo5?$xc0m9Am3K@e;*2`!xDt( zh{}rHc<~hnU+|6PIP)!n{koP#bLE}J-mk3m+ySJPA%kl-`?9S|xk`^cDsu4h|7G3Q3R;u(Jg=#W*$j2b1cQ#lQY z_vsPN5z{*bY`llc89YS!ybaL-KSR7ID=e1u+EA6@>af^*8hyGwvgAu}pKjJ<62+jZ zd~F6esBADpzW@(g)nQERx2*WFnDotp{4~r$hkz6|bSd#GItnkSrR0$tEb1NbVd2LU zjE>;^j9h8`{T;y-%{(jjwmYLRR?|WOy9?@ZOWB3u$``=V*FLxos?!Hv3`r0Q)cIK_%9oa@8Zi z{0NON1)Nb)Q1!|j7@S!9p6Mx{*|w>ETfSQk1dRl9#83focilX!_iVOt`^l2DLY=E7 zxYKhu$h3$9s+Ij)SJAWs5r6D}FaKfHp6w^VAlCvq3aXq4$9!>I1E3W1z?l@3X5O*$ zhmA5N(}#U%mYFkKQ6B=<7W^xr6D((=oO$dAM6&juF&AK z{}|!1P0xFGWoKgY)^1iU$x8tznl;Q?c%3=BRhLdm{Dk4Etr^5&_1w3Bd4s-8bsYP{ zxMY2iGk>^cafcF1%ua<+Gbf`eK~LUt7Q@?sqa+BAFgAT;s0?gX}$qe0yM5qMOSa2xQ0!2*y9Jd-kp<$$5 zF$bEBdH0X92O^E{ij>ky-Rh&huJ3(~b|f$@+dJLe7F1_Cn1F6tI!LD95R2^92YNNN z=BDRraX(cR{@W`9F^6ck2{1=EEwdIQkna=U5~}iK2$Ik$$}fJ%*B`Hvm-irW#{))_ z1ZIGYbE!8;$T*(|5gCVAqF1WFm0^d=G!^$;HloospY*#d5!s2}i;NQ8*v(WryY|KC zIafpms*g0IHPw$iMLR56k9oP%tF|$%Fbyn=r|W%teyh;m`1j9S5ZLoO^yPYsiyUi` zJ_+(|>$2vRRFA-w|F&{9qvLZZM4?!Dcz0joeQWpKzV>yFH*oQiP}S^^#g1u+vz%d! z&_5^){%7%z8SHz=0H_T=GXOer*@lbCfW0u_-kcNm51@r9oJFd%Kg5=-0DaMlyax`K zgT~lRCOObGapqFwqlM#7chVs4zBvSLh}r=u-B16?%QT==@>>-`u}%56L;@dw<)xgF zyb#om*B|RAT6GHUy$)zqL^C?*E1z%jK{cG{)?9G3gK}2Tjlz){ecoG67Ru}O5tkfY z?Lli%Z!S|k2NON&;$b!_q??uC(ECkK$8u4&RQT~{iX+1~k*tPrqoAty6m}^trfLoQ zwYLxyUyQzAad+j8JZM@q^M@{lmE~JF<;k4KKET_fCCuWAx)-+(ZQD>m63150N$GI) zx0jAVGU0$3pqyl>p)o{kY4KkS@+3WC2+h+PWE=$Itb*XEw7`|comNK10x!ietR-mO zhxb(ipBfSboa7~f{>WkAO~8bKdT&|sZaCdYtg$6sB_7bFH}k!3#UKGP)#LBw0(nG@ zX{1;nr`aL)3c%bSe&MWc0W4@?3N^qq%dipO)*m_|)1b11S}6);aT+Ti8fs9YDtu}@ zwtlW;a{t7V^!>t~kQ?vz(@11qA3KWbGSseMFoI#^%L4I)P|l z`g|Tj2_v@~)nD|)?K1B<#DmfR{}H|K6hjZ~cb?gZsv|~Efg$ApDFlTQW!{tdu)L99 z!g2zP?XTF2Ar7o&`RbI>zHGj^u5~D3)r9*=SHjp_3hkOe$eZV!|2v09;Pn-5@bBpIu;WT*l|%H57u_(N-+20%FgoYz}^oOcAx zA`m@uYFbhWm2s3*S^t6}di4uI%HN}fTZp5Xa^T4b6Ue8bgrf190G2=qmq3;)ZW!hd zdo>{?n$2-<=+|keaElk;OM*CuDD?dP??h9)D_DX0P*?a6mp5H>WUIK&Z_5W^hij?8xT%oCbnm+#x}yS%Kxj>TP=L zs*J6MAASjq1r$rVDBAc$mcC~5nE{%*p`I11W6V1%IaA?8)H?wmFAQOztxOrzo2!>c<8u(wp}N}$-mBOtqPjk zC!CcnzQX)>1STz}tk+OG9Zp{kGwC0A181G_;1ncLD~yB%e6Y_wyDq%XBmMb*@y+5?I@*)s6& zEG?3GL+r#U1p+mR7(*(~!S>w5f}0)@jqif1eNU1JX>+jvE8rCifXRiBR-Ad%rntac z^h<8#w`Unkaf8CFtN+Q!0@NqZM7{LA%mQg9^>B@j01TZ!LP(=`99|)#KN3K)z48>V ze5gzmvQQ--1$SO_l7T)>`c|}wRM5yZx79}7L2H|d03v=B1+}8!D@XvF0-CaL)-LEw zdUziIRv4lZzq}48Dw5}P{^^Chm!@M;O`m=c+dr`WKFm_x_wnZZQr(#3={oO+;h}U) z*D@yj4Ml;SvEeN~D&{C=1CCgQJ~F?kK)=^E-Yuj7?Yy8Wvqq2Pm2rg#_Ph+9k<3c_ zH`IJsganZ>_-@dWjWFp%6{k}l)=(3m)PIzgN3U;ZhHKb`ffEv%kAu94I$-%c1^PV) z3dS$mQJ%d=-IG@47a;l5b=R5q{0G$~N%5)86i-ZSokyE?ixWilnq|ub!S@Q!_^{nRWREPuevi`$?4x;doZXK28e;;29HE_N1SWq*u266; z5-%OMjSmA10Qe6VSeSNQ2w@lz0H_VcGP>qVs{X<^)6V3tk?Jz(;D3Mmo{;97&0N3m z5t;rT&(Qt=_ex3j<(qU3nNtp)1#?H?6h4IBA5b#9ze)0~2Q=!@Z)Jix^Ke^MAd7d& zS+G8858EPIP9P7n>}zaTdisb&*u=qJtX&V%v-vEgE14Cw^-*8`wp)1gUnq1Ukzjc`@kKZobppl zyJt>^xF;;-0#)F-i@&~WFJk#q18yV{=xdS4ay@3Y0O|-Zk1Lz6s9FF4+#J=97Sxv; z`0T}8Z@fXs6-k3jdSMSUj6mJ$%5ntE)Ok98vr3I=^V_kLsR5njCIa^N$iW!ylzJ0q zFF#7Bmus`LDXS;A;TNh1_$R;e2eE9R%6A= jwUKb{Vi9tG0t#UddgjY7yX6iUu?>)GVRy%fB