{"slug":"scenario-blender-uv-baking","title":"scenario-blender-uv-baking","summary":"Use 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 w","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-10-17T09:11:01.763581Z","repo":{"url":"https://github.com/scenario-labs/skills","stars":1035,"forks":129,"license":"MIT","updatedAt":"2026-10-17T05:01:44Z"},"bodyHtml":"<hr>\n<h2>name: scenario-blender-uv-baking\ndescription: \"Use 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 or AO ghosts, bpy.ops.object.bake returns CANCELED, or uv.unwrap silently fails.\"\nlicense: MIT</h2>\n<h1>UV unwrapping and baking</h1>\n<p>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 <a href=\"scripts/bx_uvbake.py\"><code>scripts/bx_uvbake.py</code></a> computes. If a sibling skill named here is missing from your available skills, ask the user to install it (<code>npx skills add scenario-labs/skills --skill &lt;name&gt;</code>); unattended, proceed from tool schemas and flag the gap.</p>\n<p><strong>REQUIRED BACKGROUND:</strong> scenario-blender-expert (execution channel, review loop, 5.2 API changes).</p>\n<h2>Stance (the expert delta)</h2>\n<ul>\n<li><strong>Seams follow the texturing method.</strong> Kaspar (2021, 2D painting): as few as possible, hidden under hair, clothes and inner limbs. Blender Studio guide (2024, same author): more seams rather than fewer when you paint in 3D or bake, because stretch costs more. Game bakes add a hard rule: every hard edge is a seam, or the normal map cracks (Lampel, Gambrell, On Mars 3D, Polycount).</li>\n<li><strong>Only closed shells and long tubes need extra cuts.</strong> Morren: anything closed on itself needs one cut. Gambrell: cut every taped-paper ring once, hidden. But an island with holes (a face with eye holes, a washer) flattens fine. <code>cut_to_disks</code> test-unwraps it and cuts only if it stretches [added; cutting everything folded the sheep's big islands].</li>\n<li><strong>Unwrap method is a measurement, and 5.2 defaults to CONFORMAL.</strong> Always pass <code>method=</code>. 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. <code>unwrap(method=\"BEST\")</code> picks per island and rejects islands that fold over themselves.</li>\n<li><strong>Density first, texture size second</strong> (Huge Menace). Targets: FPS 10.24 px/cm, weapon 20.48, third person 5.12, top-down 2.56. Priority follows the camera: face, hands and front up; soles, inner mouth and back down (SpeedChar, Kaspar). Match texture sets by scaling the denser set down, never the other up (Gambrell).</li>\n<li><strong>Straighten, then equalize, then pack without tilting</strong> (Lampel): Follow Active Quads from a truly rectangular quad, pin, re-unwrap the rest, pack with CARDINAL. Padding is a pixel number, res/128 (16 px at 2K), passed as FRACTION margin = gap/(2*res).</li>\n<li><strong>Measure the projection, never guess extrusion, and do not trust one global value.</strong> Ryan King's 0.01 then 0.02 was scene-specific. SpeedChar swept six values by hand, and the value that fixed the face broke the armpit (\"when we fix something somewhere, some other problems occur\", E6QMhGuhyoc [00:26:23]). Rays start on the low, and <code>max_ray_distance</code> 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].</li>\n<li><strong>Unique UVs wherever baked lighting differs</strong> (SpeedChar: an AO ghost means shared texels). Stacked or mirrored copies move exactly one tile out of 0-1 before the bake (Polycount), because inside 0-1 the last island baked wins.</li>\n<li><strong>A bake is judged in pixels and under light.</strong> Blue &lt; 0.5 means rays hit the far side. Exactly flat means rays missed. All-white AO means the distance is too small. Inverted pixels that do not move when extrusion changes point to a geometry problem, not a setting [verified on the sheep]. Multires bakes cast no rays: on the same surface they gave 0.09% inverted texels.</li>\n</ul>\n<h2>Establish first</h2>\n<table>\n<thead>\n<tr>\n<th>Input</th>\n<th>Changes</th>\n<th>Default when the brief is silent</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Target</td>\n<td>seams, symmetry, UDIM vs sets, triangulation</td>\n<td>game engine, one 0-1 set per material</td>\n</tr>\n<tr>\n<td>Texturing method</td>\n<td>seam count and visibility</td>\n<td>3D painting/Substance: seams may show at the back</td>\n</tr>\n<tr>\n<td>Camera</td>\n<td>density and priorities</td>\n<td>third person 512 px/m, closest view defines tiles</td>\n</tr>\n<tr>\n<td>Texture budget</td>\n<td>resolution, sets, UDIM count</td>\n<td>2048 per set; <code>texture_size_for</code> decides</td>\n</tr>\n<tr>\n<td>Deformation</td>\n<td>seams off joints, tangent-space normals</td>\n<td>character: tangent space, unique UVs</td>\n</tr>\n<tr>\n<td>High source</td>\n<td>Selected to Active vs Multires</td>\n<td>separate sculpt: Selected to Active</td>\n</tr>\n<tr>\n<td>Engine convention</td>\n<td>normal green channel</td>\n<td>OpenGL (Blender, Unity, glTF); <code>directx=True</code> for Unreal</td>\n</tr>\n</tbody>\n</table>\n<h2>Workflow</h2>\n<ol>\n<li><strong>Pre-flight.</strong> Apply scale (On Mars 3D: it \"fixes the vast majority\" of problems). Clear non-manifold geometry, doubles and back faces (SpeedChar). Decide on Mirror and Solidify modifiers. If UVs exist, compare <code>island_topology</code> with <code>uv_qa</code> islands: seam flags in files are often leftover retopology marks, so run <code>seams_from_uv_islands</code> (Lampel) before re-unwrapping. GATE: scale 1, <code>bx_audit</code> clean, topology table read.</li>\n<li><strong>Seams.</strong> Hard surface: <code>seams_from_sharp(obj, 30, mark_sharp=True)</code>. Organic: build part labels (head, ears, limbs, torso front and back, soles, inner mouth and eyelids per the Kaspar and Morren seam maps in <a href=\"references/expert-notes.md\"><code>references/expert-notes.md</code></a>), then <code>seams_from_groups(obj, labels)</code>. Specific cuts: <code>seam_path(obj, v_a, v_b, view_dirs)</code>. Then <code>cut_to_disks(obj, view_dirs=[camera])</code>. GATE: <code>must_cut</code> empty; a checker render shows no seams across hero areas that the brief cannot afford.</li>\n<li><strong>Unwrap.</strong> <code>unwrap(obj, method=\"BEST\")</code>, or the method the island type calls for. GATE: <code>unsolved_faces</code> 0 (the operator returns FINISHED even when it fails), <code>folding_islands</code> empty (else <code>seams_from_groups(..., faces=folding_faces)</code> and unwrap again), angle p95 at most 15 to 20 deg.</li>\n<li><strong>Straighten</strong> grid-like islands (hard surface, garments, strands): <code>straighten(obj)</code> before any scaling. GATE: straight stripes in the checker.</li>\n<li><strong>Texel density.</strong> <code>normalize_texel_density(obj, target, res, weights)</code>, then <code>texture_size_for(obj, target)</code>. GATE: island spread within 10-20%, or the intended priority ratios.</li>\n<li><strong>Pack.</strong> <code>pack(obj, tex_res=res)</code>, with <code>scale=False</code> for a strict density and <code>allow_any=True</code> for organic islands. UDIM: pack each region, then <code>move_to_tile</code>. GATE: <code>uv_qa</code>: 0 overlaps, gap at least res/128, border at least gap/2, coverage at least 0.70 or an explained exception.</li>\n<li><strong>Look.</strong> <code>uv_layout_image</code>, <code>checker_review</code>, <code>distortion_review(metric=\"angle\")</code>. Judge with <a href=\"references/critique.md\"><code>references/critique.md</code></a>.</li>\n<li><strong>Bake prep.</strong> <code>prepare_low(low, triangulate=game, sharp_from_uv_islands=hard_surface)</code>. Check the silhouette against the high (<code>bx_audit.audit(low, high=...)</code>). Explode interpenetrating or moving parts (<code>explode</code>, one axis). Run <code>measure_projection</code>: <code>miss_pct</code> under 2 and <code>wrong_part_pct</code> under 1, else use a cage, explode, or bake per pair.</li>\n<li><strong>Bake.</strong> <code>bake_high_to_low(low, highs, out, res=512)</code> first, then the final resolution. It uses adaptive extrusion by default and hides every other object from render. Multires: <code>bake_multires</code>. Extras: <code>bake_id</code>, <code>curvature_from_normal</code>. GATE: <code>sanity[...][\"problems\"]</code> empty (inverted texels under 2%, notes from 0.2%), 16-bit PNG. If inverted texels remain, run <code>map_problem_review</code> to see where. If <code>sweep_projection</code> shows they do not move with extrusion, fix the geometry (fit, holes, thin parts), not the numbers.</li>\n<li><strong>Review and hook up.</strong> <code>bake_review(low, highs, paths, out)</code>: the low with maps must read like the high under the raking light. Then <code>hookup_maps</code>. Report measured numbers and what was not verified.</li>\n</ol>\n<h2>Numbers</h2>\n<table>\n<thead>\n<tr>\n<th>Value</th>\n<th>Relative to</th>\n<th>Source</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>10.24 / 20.48 / 5.12 / 2.56 px/cm</td>\n<td>FPS world / FPS weapon / third person / top-down</td>\n<td>Huge Menace [00:12:28]</td>\n</tr>\n<tr>\n<td>10-20% density mismatch invisible</td>\n<td>between islands or sets</td>\n<td>Gambrell [01:36:16]</td>\n</tr>\n<tr>\n<td>coverage above 70%, 75% studio minimum</td>\n<td>one 0-1 tile</td>\n<td>Gambrell [01:31:56], Huge Menace [00:16:27]</td>\n</tr>\n<tr>\n<td>island gap res/128 (8/16/32 px at 1K/2K/4K)</td>\n<td>texture resolution</td>\n<td>Lampel [00:38:01], On Mars 3D [00:11:18]</td>\n</tr>\n<tr>\n<td>FRACTION margin m: gap 2m, border m</td>\n<td>Blender pack</td>\n<td>verified</td>\n</tr>\n<tr>\n<td>bake margin res/128, 16 px at 2K</td>\n<td>texture resolution</td>\n<td>On Mars 3D, digest</td>\n</tr>\n<tr>\n<td>cylinder sides: 6 min, 8 recommended</td>\n<td>low poly silhouette</td>\n<td>On Mars 3D [00:03:48]</td>\n</tr>\n<tr>\n<td>samples: 16 for normals, 128 for AO, 1 only while iterating</td>\n<td>antialiasing and noise</td>\n<td>Gambrell/Marmoset 16, SpeedChar 4x4, verified</td>\n</tr>\n<tr>\n<td>test bake 512, final 2048</td>\n<td>iteration loop</td>\n<td>SpeedChar [00:59:09]</td>\n</tr>\n<tr>\n<td>AO distance about 0.2 x bbox diagonal (default 10 units)</td>\n<td>asset size</td>\n<td>[added], verified effect</td>\n</tr>\n<tr>\n<td>angle error p95 at most 15 deg organic, 3 deg flat</td>\n<td>per corner</td>\n<td>[added] from 5.2.1 measurements</td>\n</tr>\n<tr>\n<td>Blender packer on an organic character: 55-62%</td>\n<td>16 px gap at 2K</td>\n<td>measured on the sheep (several runs)</td>\n</tr>\n<tr>\n<td>inverted texels: single pass 7.3-7.8%, adaptive 2.2% (sculpt) / 1.2% (consistent high)</td>\n<td>selected to active, sheep</td>\n<td>measured</td>\n</tr>\n<tr>\n<td>2048 bake, 186k-face high: one pass normal + AO 4.4 s, adaptive 25 s</td>\n<td>Metal GPU, headless</td>\n<td>measured</td>\n</tr>\n</tbody>\n</table>\n<h2>Quality gates</h2>\n<p>Code: <code>q = uv_qa(obj, res)</code>, then <code>uv_verdict(q, \"game\" or \"film\", priorities=...)</code>. <code>unwrap()[\"unsolved_faces\"]</code> and <code>[\"folding_islands\"]</code>. <code>measure_projection()</code>. <code>bake_high_to_low()[\"sanity\"]</code> (inverted, exact-flat, non-unit, AO black and white). Cross-check overlaps with <code>bpy.ops.uv.select_overlap()</code> when in doubt; it agreed with the raster on the tests.</p>\n<p>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, <code>bake_review</code> sheet (high, low, low + maps), <code>map_problem_review</code> when sanity flags pixels.</p>\n<h2>Common mistakes</h2>\n<table>\n<thead>\n<tr>\n<th>Mistake</th>\n<th>Looks like</th>\n<th>Fix</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>uv.unwrap()</code> without <code>method</code></td>\n<td>Conformal area distortion</td>\n<td>pass <code>method=</code> or use <code>unwrap(method=\"BEST\")</code></td>\n</tr>\n<tr>\n<td>Trusting FINISHED</td>\n<td>closed island keeps its old UVs, repacked</td>\n<td>zero the UVs first, check for zero-area faces (<code>unwrap()</code> does it)</td>\n</tr>\n<tr>\n<td>Re-unwrapping without Seams from Islands</td>\n<td>layout lost; retopology marks used as seams</td>\n<td><code>seams_from_uv_islands</code> first</td>\n</tr>\n<tr>\n<td>Cutting every island to a disk</td>\n<td>slits, folded islands</td>\n<td>measured <code>cut_to_disks</code> (default)</td>\n</tr>\n<tr>\n<td>Pack rotation ANY after straightening</td>\n<td>tilted grids, jaggy lines</td>\n<td>CARDINAL</td>\n</tr>\n<tr>\n<td>Blender margin 0.005</td>\n<td>padding depends on island scale</td>\n<td>FRACTION, gap/(2*res)</td>\n</tr>\n<tr>\n<td>Target node active but not selected</td>\n<td>bake returns CANCELED, no error</td>\n<td>select and activate; assert FINISHED</td>\n</tr>\n<tr>\n<td>Low carries the sculpt as Multires</td>\n<td>almost flat normal map</td>\n<td>render levels 0 for the bake (bake_high_to_low does it)</td>\n</tr>\n<tr>\n<td>max_ray shorter than extrusion</td>\n<td>90% exactly flat map</td>\n<td>ray = extrusion + inward depth</td>\n</tr>\n<tr>\n<td>One global extrusion on thin, close parts (ears on a head)</td>\n<td>inverted patches, neighbors baked in</td>\n<td>adaptive bake (default), explode, or per-pair bakes</td>\n</tr>\n<tr>\n<td>Other objects render-visible during AO</td>\n<td>black AO</td>\n<td>hide them from render; the active low never occludes</td>\n</tr>\n<tr>\n<td>Default AO distance 10</td>\n<td>AO all dark or flat</td>\n<td>about 0.2 x asset diagonal</td>\n</tr>\n<tr>\n<td>Stacked UVs baked in 0-1</td>\n<td>one side's detail on both</td>\n<td>unique UVs or <code>offset_stacked</code></td>\n</tr>\n<tr>\n<td>Hard edge inside an island</td>\n<td>crack lines</td>\n<td>seam it, or <code>prepare_low(sharp_from_uv_islands=True)</code></td>\n</tr>\n<tr>\n<td>Editing UVs after the bake</td>\n<td>seams, tangent mismatch</td>\n<td>never; re-bake</td>\n</tr>\n</tbody>\n</table>\n<h2>Blender 5.2 notes</h2>\n<ul>\n<li><code>uv.unwrap</code> defaults to CONFORMAL. On failure it keeps the island's old UVs and repacks them. It has <code>use_weights</code>/<code>weight_group</code> (default group \"uv_importance\"): importance weights with Minimum Stretch. A weighted top half went from 31% to 74% of UV area [verified].</li>\n<li>Sync select is on by default (5.0). <code>BMLoopUV</code> only has <code>uv</code> and <code>pin_uv</code>. A <code>me.uv_layers[...]</code> reference goes stale after an Edit Mode toggle, so keep the name.</li>\n<li><code>pack_islands(udim_source=\"CLOSEST_UDIM\")</code> headless packed a region sitting in 1002 back into 1001: pack first, then move. <code>uv.move_on_axis</code> needs a UV editor.</li>\n<li>Bake: the target must be selected AND active (5.0). Operator defaults differ from scene defaults (<code>use_clear</code> 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 <code>scene.render.bake.use_multires</code> with <code>object.bake_image()</code>. A float image saved as PNG is 16-bit. Normal Map node <code>convention</code> OPENGL/DIRECTX (5.1). UDIM tiles need <code>image.tile_add(fill=True)</code>.</li>\n<li>Auto Smooth is gone (4.1): smooth shading plus sharp edges; Triangulate \"Keep Normals\" is <code>keep_custom_normals</code>.</li>\n</ul>\n<h2>References</h2>\n<ul>\n<li><code>references/expert-notes.md</code>: judgment by expert with timestamps, disagreements and deciding conditions, my 5.2.1 measurements. Load when choosing seams, density or a bake strategy.</li>\n<li><a href=\"references/procedures.md\"><code>references/procedures.md</code></a>: tested pipelines (hard-surface, character, film/UDIM, Multires, bake-fix loop) and raw bpy snippets executed by the tests. Load before writing code.</li>\n<li><code>references/critique.md</code>: rubric for seams, stretch, density, packing, bake setup and baked maps, with proxies and severity. Load at every gate.</li>\n<li><a href=\"references/sources.md\"><code>references/sources.md</code></a>: videos, credentials, URLs, best timestamps, web references.</li>\n<li><code>scripts/bx_uvbake.py</code>: the module (docstrings list every function).</li>\n</ul>\n","files":[{"path":"references/critique.md","sizeBytes":17474,"isText":true},{"path":"references/expert-notes.md","sizeBytes":23242,"isText":true},{"path":"references/procedures.md","sizeBytes":17979,"isText":true},{"path":"references/sources.md","sizeBytes":12550,"isText":true},{"path":"scripts/bx_uvbake.py","sizeBytes":107561,"isText":true},{"path":"SKILL.md","sizeBytes":17085,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-10-17T09:13:17.689446Z","sha256":"A882A9037931DF714DF819B7EF225D7580D6A1145E996AD8BA360F8A170A5971","sizeBytes":63637},"review":null,"source":{"repositoryUrl":"https://github.com/scenario-labs/skills","path":"skills/dcc/blender/scenario-blender-uv-baking","license":"MIT","commit":"dc592af7139ffacfe8fdae7dc7efa80f4dd09be3","subtreeSha":"F6A1465086F8D8706CE0DD0D35EC260E7373900DF33F1770DB64E7E6E3596CBF","lastSyncedAt":"2026-10-17T09:10:46.286688Z"},"reviewedAt":"2026-10-17T09:17:23.373916Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/scenario-labs/skills/tree/main/skills/dcc/blender/scenario-blender-uv-baking"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install scenario-labs-skills@llmmart"},{"target":"git","command":"git clone https://github.com/scenario-labs/skills.git"}]}