executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
scenario-blender-uv-bakingUse when UV unwrapping, placing seams, straightening or packing UVs, setting texel density, laying out UDIMs or mirrored UVs, or baking normal, AO, curvature, ID or displacement maps from a sculpt or high poly onto a low poly or retopo in Blender, including Multires bakes. Also when a bake has black or inverted patches, flat areas, cracks, seams, wavy lines…
Independent GitHub snapshot · Automated classification · Original documentation governs
Expert-level UVs put seams where the texturing method can afford them, set texel density before texture size, and pack with pixel-true padding. Expert bakes measure their projection distances instead of guessing, and are judged in pixels and under a raking light, never eyeballed once. Every stage below has a measurable gate that scripts/bx_uvbake.py computes. If a sibling skill named here is missing from your available skills, ask the user to install it (npx skills add scenario-labs/skills --skill <name>); unattended, proceed from tool schemas and flag the gap.
REQUIRED BACKGROUND: scenario-blender-expert (execution channel, review loop, 5.2 API changes).
cut_to_disks test-unwraps it and cuts only if it stretches [added; cutting everything folded the sheep's big islands].method=. On Suzanne, Minimum Stretch gives the most even density (spread 1.38), Angle Based the best angles, and Conformal fails by area (5.89). On flat hard-surface islands all three converge. unwrap(method="BEST") picks per island and rejects islands that fold over themselves.max_ray_distance counts from the extruded start. The adaptive bake keeps, per texel, the smallest extrusion that hits a front face. On the sheep it cut inverted texels from 7.5% to 1.2% [verified].| Input | Changes | Default when the brief is silent |
|---|---|---|
| Target | seams, symmetry, UDIM vs sets, triangulation | game engine, one 0-1 set per material |
| Texturing method | seam count and visibility | 3D painting/Substance: seams may show at the back |
| Camera | density and priorities | third person 512 px/m, closest view defines tiles |
| Texture budget | resolution, sets, UDIM count | 2048 per set; texture_size_for decides |
| Deformation | seams off joints, tangent-space normals | character: tangent space, unique UVs |
| High source | Selected to Active vs Multires | separate sculpt: Selected to Active |
| Engine convention | normal green channel | OpenGL (Blender, Unity, glTF); directx=True for Unreal |
island_topology with uv_qa islands: seam flags in files are often leftover retopology marks, so run seams_from_uv_islands (Lampel) before re-unwrapping. GATE: scale 1, bx_audit clean, topology table read.seams_from_sharp(obj, 30, mark_sharp=True). Organic: build part labels (head, ears, limbs, torso front and back, soles, inner mouth and eyelids per the Kaspar and Morren seam maps in references/expert-notes.md), then seams_from_groups(obj, labels). Specific cuts: seam_path(obj, v_a, v_b, view_dirs). Then cut_to_disks(obj, view_dirs=[camera]). GATE: must_cut empty; a checker render shows no seams across hero areas that the brief cannot afford.unwrap(obj, method="BEST"), or the method the island type calls for. GATE: unsolved_faces 0 (the operator returns FINISHED even when it fails), folding_islands empty (else seams_from_groups(..., faces=folding_faces) and unwrap again), angle p95 at most 15 to 20 deg.straighten(obj) before any scaling. GATE: straight stripes in the checker.normalize_texel_density(obj, target, res, weights), then texture_size_for(obj, target). GATE: island spread within 10-20%, or the intended priority ratios.pack(obj, tex_res=res), with scale=False for a strict density and allow_any=True for organic islands. UDIM: pack each region, then move_to_tile. GATE: uv_qa: 0 overlaps, gap at least res/128, border at least gap/2, coverage at least 0.70 or an explained exception.uv_layout_image, checker_review, distortion_review(metric="angle"). Judge with references/critique.md.prepare_low(low, triangulate=game, sharp_from_uv_islands=hard_surface). Check the silhouette against the high (bx_audit.audit(low, high=...)). Explode interpenetrating or moving parts (explode, one axis). Run measure_projection: miss_pct under 2 and wrong_part_pct under 1, else use a cage, explode, or bake per pair.bake_high_to_low(low, highs, out, res=512) first, then the final resolution. It uses adaptive extrusion by default and hides every other object from render. Multires: bake_multires. Extras: bake_id, curvature_from_normal. GATE: sanity[...]["problems"] empty (inverted texels under 2%, notes from 0.2%), 16-bit PNG. If inverted texels remain, run map_problem_review to see where. If sweep_projection shows they do not move with extrusion, fix the geometry (fit, holes, thin parts), not the numbers.bake_review(low, highs, paths, out): the low with maps must read like the high under the raking light. Then hookup_maps. Report measured numbers and what was not verified.| Value | Relative to | Source |
|---|---|---|
| 10.24 / 20.48 / 5.12 / 2.56 px/cm | FPS world / FPS weapon / third person / top-down | Huge Menace [00:12:28] |
| 10-20% density mismatch invisible | between islands or sets | Gambrell [01:36:16] |
| coverage above 70%, 75% studio minimum | one 0-1 tile | Gambrell [01:31:56], Huge Menace [00:16:27] |
| island gap res/128 (8/16/32 px at 1K/2K/4K) | texture resolution | Lampel [00:38:01], On Mars 3D [00:11:18] |
| FRACTION margin m: gap 2m, border m | Blender pack | verified |
| bake margin res/128, 16 px at 2K | texture resolution | On Mars 3D, digest |
| cylinder sides: 6 min, 8 recommended | low poly silhouette | On Mars 3D [00:03:48] |
| samples: 16 for normals, 128 for AO, 1 only while iterating | antialiasing and noise | Gambrell/Marmoset 16, SpeedChar 4x4, verified |
| test bake 512, final 2048 | iteration loop | SpeedChar [00:59:09] |
| AO distance about 0.2 x bbox diagonal (default 10 units) | asset size | [added], verified effect |
| angle error p95 at most 15 deg organic, 3 deg flat | per corner | [added] from 5.2.1 measurements |
| Blender packer on an organic character: 55-62% | 16 px gap at 2K | measured on the sheep (several runs) |
| inverted texels: single pass 7.3-7.8%, adaptive 2.2% (sculpt) / 1.2% (consistent high) | selected to active, sheep | measured |
| 2048 bake, 186k-face high: one pass normal + AO 4.4 s, adaptive 25 s | Metal GPU, headless | measured |
Code: q = uv_qa(obj, res), then uv_verdict(q, "game" or "film", priorities=...). unwrap()["unsolved_faces"] and ["folding_islands"]. measure_projection(). bake_high_to_low()["sanity"] (inverted, exact-flat, non-unit, AO black and white). Cross-check overlaps with bpy.ops.uv.select_overlap() when in doubt; it agreed with the raster on the tests.
Visual: layout PNG (islands, magenta overlaps), color-grid checker from front, side, three-quarter and the production camera (squares square, letters upright, seams hidden), angle and density heat maps, bake_review sheet (high, low, low + maps), map_problem_review when sanity flags pixels.
| Mistake | Looks like | Fix |
|---|---|---|
uv.unwrap() without method | Conformal area distortion | pass method= or use unwrap(method="BEST") |
| Trusting FINISHED | closed island keeps its old UVs, repacked | zero the UVs first, check for zero-area faces (unwrap() does it) |
| Re-unwrapping without Seams from Islands | layout lost; retopology marks used as seams | seams_from_uv_islands first |
| Cutting every island to a disk | slits, folded islands | measured cut_to_disks (default) |
| Pack rotation ANY after straightening | tilted grids, jaggy lines | CARDINAL |
| Blender margin 0.005 | padding depends on island scale | FRACTION, gap/(2*res) |
| Target node active but not selected | bake returns CANCELED, no error | select and activate; assert FINISHED |
| Low carries the sculpt as Multires | almost flat normal map | render levels 0 for the bake (bake_high_to_low does it) |
| max_ray shorter than extrusion | 90% exactly flat map | ray = extrusion + inward depth |
| One global extrusion on thin, close parts (ears on a head) | inverted patches, neighbors baked in | adaptive bake (default), explode, or per-pair bakes |
| Other objects render-visible during AO | black AO | hide them from render; the active low never occludes |
| Default AO distance 10 | AO all dark or flat | about 0.2 x asset diagonal |
| Stacked UVs baked in 0-1 | one side's detail on both | unique UVs or offset_stacked |
| Hard edge inside an island | crack lines | seam it, or prepare_low(sharp_from_uv_islands=True) |
| Editing UVs after the bake | seams, tangent mismatch | never; re-bake |
uv.unwrap defaults to CONFORMAL. On failure it keeps the island's old UVs and repacks them. It has use_weights/weight_group (default group "uv_importance"): importance weights with Minimum Stretch. A weighted top half went from 31% to 74% of UV area [verified].BMLoopUV only has uv and pin_uv. A me.uv_layers[...] reference goes stale after an Edit Mode toggle, so keep the name.pack_islands(udim_source="CLOSEST_UDIM") headless packed a region sitting in 1002 back into 1001: pack first, then move. uv.move_on_axis needs a UV editor.use_clear False vs True, EXTEND vs ADJACENT_FACES): pass everything. Selected to Active uses the low's render evaluation (Multires render_levels, shape keys). Multires settings live in scene.render.bake.use_multires with object.bake_image(). A float image saved as PNG is 16-bit. Normal Map node convention OPENGL/DIRECTX (5.1). UDIM tiles need image.tile_add(fill=True).keep_custom_normals.references/expert-notes.md: judgment by expert with timestamps, disagreements and deciding conditions, my 5.2.1 measurements. Load when choosing seams, density or a bake strategy.references/procedures.md: tested pipelines (hard-surface, character, film/UDIM, Multires, bake-fix loop) and raw bpy snippets executed by the tests. Load before writing code.references/critique.md: rubric for seams, stretch, density, packing, bake setup and baked maps, with proxies and severity. Load at every gate.references/sources.md: videos, credentials, URLs, best timestamps, web references.scripts/bx_uvbake.py: the module (docstrings list every function).