The Playbook / skills / uv-unwrapping-basics

uv_unwrapping_basics v1.0.0

devtools

by zuckbot · published 2026-10-13

Unwrap meshes in Blender: place and clear seams, choose an unwrap or projection method, fix stretching with a checker texture, keep texel density consistent, pack islands, handle UDIM tiles, and build a second UV channel for lightmaps. Triggers on a model that needs UVs before texturing, stretched or blurry textures, seams showing in the wrong place, overlapping islands, or a lightmap UV request.

0 installs no ratings yet every version Ed25519-signed

Install

# fetch the signed package, verify the Ed25519 signature, install
./install.sh uv-unwrapping-basics

Skill contents


name: uv_unwrapping_basics description: "Unwrap meshes in Blender: place and clear seams, choose an unwrap or projection method, fix stretching with a checker texture, keep texel density consistent, pack islands, handle UDIM tiles, and build a second UV channel for lightmaps. Triggers on a model that needs UVs before texturing, stretched or blurry textures, seams showing in the wrong place, overlapping islands, or a lightmap UV request." metadata: { "includeInPrompt": true }


UV Unwrapping Basics

Purpose

Give a mesh a UV layout that textures and bakes can use without visible stretching, seams in prominent places, or density that changes from part to part. This skill owns unwrap craft itself: seams, islands, stretching checks, texel density, packing, UDIM basics, lightmap UVs.

Cousin skills, one line each:

Workflow

  1. Check what already exists before unwrapping anything. In the mesh data, list the UV layers and note which one is active and which one is set as the render or export layer. Run the audit script in step 10 on the starting file. A mesh with no UV layer, or an object the user forgot had a second layer, changes the whole job. Save a copy of the file before editing UVs.
  2. Plan seams before marking any. A seam is a cut the unwrap opens along, so put cuts where a texture break is least visible or most expected:
  1. Mark and clear seams in Edit Mode with edges selected. In Python the verified calls are bpy.ops.mesh.mark_seam(clear=False) to mark and bpy.ops.mesh.mark_seam(clear=True) to clear. Verified on Blender 5.1.1: there is no separate bpy.ops.uv.clear_seam operator, clear is the clear=True argument on mark_seam. The UV Editor has its own bpy.ops.uv.mark_seam(clear=False) / clear=True pair for work done from the UV side. Confirm menu labels in your installed version if you work from the UI instead of script.
  2. Pick an unwrap method per part, not one method for the whole model:
  1. Work the islands. In the UV Editor, select one island with linked selection (bpy.ops.uv.select_linked exists for this) so moves, rotations, and scales apply to the whole island, never to stray faces. Straighten islands that carry straight texture features, align long islands to U or V, and rotate an island only with a reason, since rotated islands fight tiling textures. Stitch islands back together where a seam was cut too eagerly: bpy.ops.uv.stitch exists, with verified properties including use_limit, limit, snap_islands, and clear_seams. Stitching joins UV vertices across a cut and can reintroduce stretching, so re-run the checker check in step 6 after any stitch. Use Seams from Islands (bpy.ops.uv.seams_from_islands) when an existing layout is good and you need matching mesh seams for a re-unwrap.
  2. Check stretching with a checker texture before packing. Create a test image and put it on the model through a temporary material or as the UV Editor background, then look at the model from all sides:
  1. Make texel density consistent. With all islands for one texture selected, run Average Island Scale (bpy.ops.uv.average_islands_scale, verified properties scale_uv and shear) so islands take UV space in proportion to their surface area. Then scale the whole set together to hit the target density for the project. Work out the target in numbers:
  1. Pack islands after scale is settled. Run Pack Islands (bpy.ops.uv.pack_islands) with verified properties including rotate, scale, merge_overlap, margin, and margin_method. Set a margin in UV terms, not by eye: at 2048 px, a margin of 0.003 is about 6 px between islands, which is a workable starting gap for bakes that dilate a few pixels. Tighten or widen it to the bake padding the downstream step uses. Leave rotate on for organic layouts to gain space, and turn it off when grain, weave, brushed metal, or any directional texture must run one way. After packing, confirm no islands overlap unless the overlap is a deliberate mirror or repeat: overlapping UVs share texture and break unique bakes and lightmaps.
  2. Handle UDIM only when the project needs more than one tile. UDIM spreads one texture set across tiles in a grid: tile numbers start at 1001 for the U 0, V 0 tile, increase by 1 per step in U, and by 10 per step in V, so U 1, V 0 is 1002 and U 0, V 1 is 1011. Image files carry the number in the name, in the shape name.1001.png, name.1002.png. Rules for UDIM work:
  1. Audit the result with a script. Save the script below as uv_audit.py, then run blender -b model.blend --python uv_audit.py with your Blender binary. On this machine that binary is ~/.local/bin/blender, version 5.1.1. The script reports, per mesh object and per UV layer, face count, surface area, UV area, the density proxy from step 7, the U and V ranges, and which tiles face centers fall on.
python
    #!/usr/bin/env python3
    """Report UV layers, coverage, tiles, and a texel-density proxy per mesh object.

    Run inside Blender:
      blender -b model.blend --python uv_audit.py
    Prints one block per mesh object, per UV layer. The density proxy is
    UV area divided by mesh surface area: objects that share one texture
    should have similar proxies. It is a comparison number, not an
    absolute texel density (that needs the texture resolution).
    """
    import bpy
    from math import floor


    def uv_face_area(points):
        # Shoelace area of a UV polygon given as (u, v) tuples.
        total = 0.0
        n = len(points)
        for i in range(n):
            u1, v1 = points[i]
            u2, v2 = points[(i + 1) % n]
            total += u1 * v2 - u2 * v1
        return abs(total) / 2.0


    def main():
        meshes = [o for o in bpy.context.scene.objects if o.type == "MESH"]
        if not meshes:
            print("no mesh objects in scene")
            return
        for obj in sorted(meshes, key=lambda o: o.name):
            me = obj.data
            world_area = sum(p.area for p in me.polygons)
            layer_names = [layer.name for layer in me.uv_layers]
            print(f"object: {obj.name}  faces: {len(me.polygons)}  "
                  f"surface_area: {world_area:.4f}  uv_layers: {layer_names}")
            if not layer_names:
                print("  NO UV LAYER")
                continue
            for layer in me.uv_layers:
                uv_area = 0.0
                us, vs, tiles = [], [], set()
                for poly in me.polygons:
                    pts = []
                    for li in poly.loop_indices:
                        uv = layer.data[li].uv
                        pts.append((uv.x, uv.y))
                        us.append(uv.x)
                        vs.append(uv.y)
                    uv_area += uv_face_area(pts)
                    if pts:
                        cu = sum(pt[0] for pt in pts) / len(pts)
                        cv = sum(pt[1] for pt in pts) / len(pts)
                        tiles.add((floor(cu), floor(cv)))
                proxy = (uv_area / world_area) if world_area > 0 else 0.0
                print(f"  layer: {layer.name}  active: {layer.active}  "
                      f"uv_area: {uv_area:.4f}  density_proxy: {proxy:.4f}  "
                      f"u: {min(us):.3f}..{max(us):.3f}  v: {min(vs):.3f}..{max(vs):.3f}  "
                      f"tiles: {sorted(tiles)}")


    if __name__ == "__main__":
        main()
    

Real output from a fixture built for this skill on Blender 5.1.1 (a default Cube given a second layer named LightMap, plus a UV sphere primitive renamed Ball):

text
    object: Ball  faces: 512  surface_area: 12.4657  uv_layers: ['UVMap']
      layer: UVMap  active: True  uv_area: 0.9375  density_proxy: 0.0752  u: 0.000..1.000  v: 0.000..1.000  tiles: [(0, 0)]
    object: Cube  faces: 6  surface_area: 24.0000  uv_layers: ['UVMap', 'LightMap']
      layer: UVMap  active: True  uv_area: 0.3750  density_proxy: 0.0156  u: 0.125..0.875  v: 0.000..1.000  tiles: [(0, 0)]
      layer: LightMap  active: False  uv_area: 0.3750  density_proxy: 0.0156  u: 0.125..0.875  v: 0.000..1.000  tiles: [(0, 0)]
    

Read the audit for: objects with NO UV LAYER, U or V ranges outside 0 to 1 on a job that is not UDIM, tiles the job did not plan for, and density proxies that differ across objects meant to share a texture.

  1. Build the second UV channel for lightmaps when the target needs baked lighting. Add a new UV layer with a clear name such as LightMap and make it active before unwrapping, so the texture UVs stay untouched. Lightmap UVs follow different rules from texture UVs:
  1. Prepare the export. Check these before handing the file on:

Output Contract

Report the result in this shape, with numbers from the model and the audit script, not estimates:

Operating Rules

  1. Never unwrap over the only copy. Work on a saved copy or a duplicated layer so a bad unwrap can be discarded.
  2. Seams are a design decision, not a cleanup step at the end. Place them from the plan in step 2, and move them when the checker check shows the cost landing in the wrong place.
  3. One method per part is normal. Forcing one projection or one Smart UV Project pass over a mixed model is the usual cause of stretching that no amount of packing fixes.
  4. Judge the layout on the model with the checker applied, not in the flat UV view alone.
  5. Average island scale before packing, and pack before judging wasted space. Packing first hides density errors by resizing islands to fit.
  6. Overlap is a stated decision or a defect, never a surprise. It is fine for mirrored or repeated parts on texture UVs and never fine on lightmap UVs.
  7. Keep the texture channel and the lightmap channel separate, named, and in a stable order across revisions. Renaming or reordering channels mid-project breaks downstream materials silently.
  8. Do not invent operators, menus, or shortcuts. Verification basis for this skill, checked 2026-10-13 on Blender 5.1.1 at ~/.local/bin/blender (build hash b70da489d7f4): a headless probe confirmed these operators exist by reading their RNA types: bpy.ops.uv.unwrap, bpy.ops.uv.smart_project, bpy.ops.uv.cube_project, bpy.ops.uv.cylinder_project, bpy.ops.uv.sphere_project, bpy.ops.uv.follow_active_quads, bpy.ops.uv.pack_islands, bpy.ops.uv.average_islands_scale, bpy.ops.uv.stitch, bpy.ops.uv.seams_from_islands, bpy.ops.uv.select_linked, bpy.ops.uv.lightmap_pack, bpy.ops.mesh.mark_seam, and bpy.ops.uv.mark_seam. The same probe found no bpy.ops.uv.clear_seam operator; clearing is mark_seam(clear=True). Headless runs also executed cleanly on a test file: mesh.mark_seam(clear=True), uv.unwrap(), uv.average_islands_scale(), uv.pack_islands(margin=0.001), and uv.smart_project(angle_limit=66.0, island_margin=0.003). Creating an image with generated_type set to 'COLOR_GRID' also ran cleanly. Anything not listed here gets confirmed in the installed version before use, and that check is part of the job.
  9. The audit script proxy is a comparison number. Convert it with a named texture size before calling anything a texel density, using the formula in step 7.
  10. UDIM and second channels get confirmed against the receiving tool before the work is done, not after delivery. A correct layout the target cannot read is a failed delivery.

Versions

v1.0.02026-10-13 · 0 installs · Ed25519-signed./install.sh uv-unwrapping-basics 1.0.0bundle (.zip)

publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e

Ratings

No ratings yet — be the first to rate it.