design: trimmed atlas frames (Tim t1030u)
@@ -583,3 +583,13 @@ Per sex, every equipment bone can be linked to one character bone (at least one
583
583
Automatic filling links equipment bones to character bones by name: (1) exact name, case-insensitive; (2) equal after normalising: lowercase, strip namespace/armature prefixes (`mixamorig:`, `Armature|`, everything up to the last `:` or `|`, `bip01`, `def-`), strip numeric suffixes (`.001`, `_01`, `1`), and fold side markers (`L`/`R`/`left`/`right` as prefix or suffix, `LeftArm`) into one side tag; (3) partial: same side, and one normalised name contains the other (at least 3 characters). The best score wins; ties go to the first character bone. Auto-fill only fills unlinked bones; manual links are kept.
584
584
585
585
Storage: equipment bindings already live in the `.ppe` preset, which the `.ppd` lists; the `.ppd` only stores the preset paths, so the `.ppd` schema is unchanged. The `.ppe` container goes from version 1 to 2 (new optional fields: the part skeleton, the bone of each mesh, and per-sex bone links). Version 1 files load as rigid parts (migration) and are written as version 2 on the next save. Renders and the live preview use the same layer function, so both show the mapping.
586
587
## Decision 2026-11-08 (Tim t1030u): trimmed atlas frames
588
589
When the atlases are built, each view (one frame at one pitch/yaw) can be cropped to the bounding box of its content plus 2 px padding, clamped to the frame. The crop is the same for all four maps, so diffuse, normal, depth and specular stay aligned. "Content" means any pixel that is not all-zero bytes in any of the four maps (the renderer's empty background is all zeros in every map). Outside the crop every map is therefore exactly zero, and rebuilding the full frame by pasting the crop into a zero frame is lossless. A view with no content is *empty*: it takes no atlas space and is marked `empty: true`.
590
591
Packing is deterministic and keeps row streaming: each atlas row is one (clip, frame) row as before. Within it the 60 views are placed left to right in column order (pitch −60→+60, yaw 0→330), with their trimmed widths and no gaps. The row height is the tallest crop in that row; the atlas width is the widest row (at least 1 × 1). Untrimmed atlases keep the fixed grid.
592
593
Two project settings in the `.ppd` `render` block, both on by default: `trim_body` (body sets) and `trim_equipment` (equipment sets). The `.ppd` goes from version 3 to 4; older files load with both settings on. Loose raw PNGs stay full size; trimming changes only the atlases. It is not part of the render contract (`render.json`), so resuming a run with a changed setting just builds the atlases with the current setting.
594
595
`.ppda` version 5 adds `trimmed` (bool) and, per view, `offset` `[x, y]` within the full frame, `frame` `[w, h]` (= the cell/target size) and `empty`. `x, y, w, h` are the rect in the atlas (`0, 0, 0, 0` for empty views). Untrimmed v5 files use the grid with offset `[0, 0]` and the full frame. The reader checks v5 rects by recomputing the packing from the stored sizes, so overlapping or out-of-bounds rects are refused. Versions 2–4 still load. The reader (`LoadedAtlas::cell`, used by the 2D preview, compositor and checks) always returns the full frame: the stored rect pasted at its offset into a zero frame. Composition at character position + offset is therefore pixel-identical to an untrimmed export.
@@ -583,3 +583,13 @@ Per sex, every equipment bone can be linked to one character bone (at least one583
Automatic filling links equipment bones to character bones by name: (1) exact name, case-insensitive; (2) equal after normalising: lowercase, strip namespace/armature prefixes (`mixamorig:`, `Armature|`, everything up to the last `:` or `|`, `bip01`, `def-`), strip numeric suffixes (`.001`, `_01`, `1`), and fold side markers (`L`/`R`/`left`/`right` as prefix or suffix, `LeftArm`) into one side tag; (3) partial: same side, and one normalised name contains the other (at least 3 characters). The best score wins; ties go to the first character bone. Auto-fill only fills unlinked bones; manual links are kept.583
Automatic filling links equipment bones to character bones by name: (1) exact name, case-insensitive; (2) equal after normalising: lowercase, strip namespace/armature prefixes (`mixamorig:`, `Armature|`, everything up to the last `:` or `|`, `bip01`, `def-`), strip numeric suffixes (`.001`, `_01`, `1`), and fold side markers (`L`/`R`/`left`/`right` as prefix or suffix, `LeftArm`) into one side tag; (3) partial: same side, and one normalised name contains the other (at least 3 characters). The best score wins; ties go to the first character bone. Auto-fill only fills unlinked bones; manual links are kept.584
584
585
Storage: equipment bindings already live in the `.ppe` preset, which the `.ppd` lists; the `.ppd` only stores the preset paths, so the `.ppd` schema is unchanged. The `.ppe` container goes from version 1 to 2 (new optional fields: the part skeleton, the bone of each mesh, and per-sex bone links). Version 1 files load as rigid parts (migration) and are written as version 2 on the next save. Renders and the live preview use the same layer function, so both show the mapping.585
Storage: equipment bindings already live in the `.ppe` preset, which the `.ppd` lists; the `.ppd` only stores the preset paths, so the `.ppd` schema is unchanged. The `.ppe` container goes from version 1 to 2 (new optional fields: the part skeleton, the bone of each mesh, and per-sex bone links). Version 1 files load as rigid parts (migration) and are written as version 2 on the next save. Renders and the live preview use the same layer function, so both show the mapping.586
587
## Decision 2026-11-08 (Tim t1030u): trimmed atlas frames588
589
When the atlases are built, each view (one frame at one pitch/yaw) can be cropped to the bounding box of its content plus 2 px padding, clamped to the frame. The crop is the same for all four maps, so diffuse, normal, depth and specular stay aligned. "Content" means any pixel that is not all-zero bytes in any of the four maps (the renderer's empty background is all zeros in every map). Outside the crop every map is therefore exactly zero, and rebuilding the full frame by pasting the crop into a zero frame is lossless. A view with no content is *empty*: it takes no atlas space and is marked `empty: true`.590
591
Packing is deterministic and keeps row streaming: each atlas row is one (clip, frame) row as before. Within it the 60 views are placed left to right in column order (pitch −60→+60, yaw 0→330), with their trimmed widths and no gaps. The row height is the tallest crop in that row; the atlas width is the widest row (at least 1 × 1). Untrimmed atlases keep the fixed grid.592
593
Two project settings in the `.ppd` `render` block, both on by default: `trim_body` (body sets) and `trim_equipment` (equipment sets). The `.ppd` goes from version 3 to 4; older files load with both settings on. Loose raw PNGs stay full size; trimming changes only the atlases. It is not part of the render contract (`render.json`), so resuming a run with a changed setting just builds the atlases with the current setting.594
595
`.ppda` version 5 adds `trimmed` (bool) and, per view, `offset` `[x, y]` within the full frame, `frame` `[w, h]` (= the cell/target size) and `empty`. `x, y, w, h` are the rect in the atlas (`0, 0, 0, 0` for empty views). Untrimmed v5 files use the grid with offset `[0, 0]` and the full frame. The reader checks v5 rects by recomputing the packing from the stored sizes, so overlapping or out-of-bounds rects are refused. Versions 2–4 still load. The reader (`LoadedAtlas::cell`, used by the 2D preview, compositor and checks) always returns the full frame: the stored rect pasted at its offset into a zero frame. Composition at character position + offset is therefore pixel-identical to an untrimmed export.