b0b99668ab6da0069e915be4e1150040ebbb0a42
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
53f175ed6d |
feat(characters): say what every surface IS, and light it accordingly
A character has arrived as separate body, garment and hair meshes since the
pipeline stopped joining them — but nothing recorded which was which, so every
system downstream re-guessed from the material. That guess ("untextured and
nearly black means ink") had already rendered the mannequin's flat yellow body
as a black silhouette once.
The question is answerable once, at build time, where the mesh, the weights and
the skeleton are all in hand. tools/surface_map.py answers it three ways, in
order of how much it trusts them: the material name, which on VRoid exports is
formal and on hand-authored models is still explicit; the weights, which are
decisive when the name says nothing — a surface pulled by the skirt chain is a
skirt whatever it is called; and the material flags, which catch the model's own
line-work. The answer goes in the rig sidecar next to the roles and the chains,
and SkinSurfaces reads it.
All eighteen of Taila's surfaces, and every surface of the other five skins,
now resolve from the table with nothing falling through to the heuristic
(debug/surface_class_check.gd). The heuristic stays as the fallback, which is
the one job it was ever right for.
What that buys immediately is per-class art direction, which was impossible
while every surface had to take numbers calibrated on skin. Hair takes a much
thinner line — at the body's 5 mm each strand's hull swallows its neighbour and
the head reads as a solid dark cap. Cloth takes a heavier line and a crisper
terminator, because a garment's silhouette is most of what separates a character
from the background at range. Accessories take the heaviest. `body` is unchanged
on purpose, so the look this was all calibrated against does not move.
That required moving the outline from the instance to the surface: Miku's body,
face and hair are three surfaces of ONE mesh, so an instance-wide overlay could
only ever give all three the same weight.
Two things found on the way, fixed here because they are one line each: the
surface classifier skips meshes with no vertex groups, which drops the stray
42-vertex Icosphere that rides inside every shipped skin — two older tools
already skipped it by spelling its name — and load_model now clears _rig_info,
which a model with no skeleton used to inherit from the last character loaded.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
682597a0d2 |
feat(weapons): scale the gun to the arm, and close the hands around it
The hold was wrong on every character in three independent ways, all of them a constant standing where a measurement belonged. MOUNT. set_weapon seated the weapon with rotation_degrees = (0, 90, -90). A bone attachment is expressed in the BONE's axes and no two rigs agree on those, so one constant mounted the gun differently on every model. It never needed to be right — the pose layer aims by rotating the wrist until the weapon's forward lies on the aim line, so the identity means "forward is the hand bone's -Z", true on any rig, and the wrist absorbs the roll. SIZE. The set is modelled at real-world scale; an M4 is 0.84 m butt to muzzle and these characters have 0.47 m arms against an adult 0.52. That put the handguard 0.66 m from the support shoulder, 0.2 m past reach, so the support hand was slid back down the weapon until it fitted — on Taila from an authored 0.35 m to 0.083 m, which puts both fists together at the grip. Two hands on a pistol, not a rifle. Fixed by solving the support arm's triangle rather than picking a factor: its hand must reach stock+fore ahead of the pocket from a shoulder half a shoulder-width off the axis, so scale the gun to the largest that keeps the handguard inside that reach. Taila and Kiyoko now come out at their own scales (0.217 and 0.213 m of hand separation) with the support hand at its FULL authored handguard distance and the slide-back loop never firing. The loop also has a floor now: a straight support arm beats no handguard hold. FINGERS. Nothing posed them — every hand was flat and open, which is the loudest possible tell that a character is not holding anything. They close now, about an axis derived from each hand's own anatomy in the rest pose: along = wrist to middle knuckle, palm = middle knuckle to thumb tip (the thumb opposes the fingers, so it marks the palm side by construction), curl = along x palm. The trigger finger gets a much shallower curl than the rest, because it lies along the trigger. Finger bones resolve by ROLE across all three naming families met so far — Rigify DEF-f_index.01.L, VRoid J_Bip_L_Index1, Blender IndexFinger1_L — ordered by depth below the hand rather than by the number in the name, which is not consistent between them. Verified by render on Taila and by measurement on Kiyoko: stock at the shoulder, trigger hand on the grip, support hand out on the handguard, fingers wrapped, arms not crossing. Smoke 0 failures. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
c8b0337aa7 |
fix(characters): rig-independent weapon mount, measured facing, revert Miku
WEAPON MOUNT, all models. set_weapon() seated the gun with a fixed rotation_degrees = (0, 90, -90). A bone attachment is expressed in the BONE's axes and no two rigs agree on those, so one constant mounted the weapon differently on every character. It never needed to be right: the pose layer aims the gun by rotating the wrist until the weapon's forward lies on the aim line, so the identity means "forward is the hand bone's -Z", which is true on any rig, and the wrist absorbs the roll. The grip now sits at the bone origin, so the gun is in the hand rather than at an offset from a differently-oriented bone. Verified by render on kiyoko: rifle shouldered, both hands on it. FACING. flatten_and_scale() now measures toes-versus-ankles and snaps the character to face Blender -Y, the convention the runtime's blanket flip is built around. Kiyoko was 180 degrees off. Snapped to the nearest quarter turn so splayed feet in a rest pose are not read as a turned character. MIKU. Re-imported without --grow-cloth. Her grown hair chains were the cause of the stretching: cloth_bones.py clears a vertex's body weights and re-assigns it to the fitted polyline, so a poor fit does not degrade to "stiff", it degrades to "torn". She now has no hair simulation — stiff but correct — until that tool blends against the weights it replaces instead of destroying them. ARIA'S SKIRT IS NOT SIMULATED. Worth recording plainly, because it looks better than Taila's and the obvious conclusion is the wrong one: aria has ZERO cloth chains. Her skirt never clips because it is rigidly skinned and follows the legs it is weighted to. There is nothing to port to Taila except switching her simulation off. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
27c4117c25 |
fix(pipeline): stand the character up before scaling; reach bones by role
Fixes the two root causes behind four of the seven reported breakages, and rejects the source that cannot be fixed. STAND UP FIRST. flatten_and_scale() now derives the up axis from the skeleton and rotates the model upright before measuring anything. Aria and Momo are correct: 1.75 m tall, 1.43 x 0.41 and 1.52 x 0.39 across, verified by render. The up vector is measured from the FEET to the HIPS, not from the hips to the head. The head is not a reliable landmark — the spine walk ends on the last non-cosmetic bone in the chain, which on a rig with a facial skeleton can sit BELOW the hips. Momo's did, so the first cut of this fix stood her neatly on her head: right size, right proportions, upside down. Feet cannot be mistaken. REACH BONES BY ROLE. SkinnedPlayerModel gained _role_bone(), and set_weapon uses it. Four characters could not hold a gun because one hardcoded lookup knew three spellings and their hands are called "Right wrist" and "J_Bip_R_Hand" — both resolved perfectly in the sidecar the whole time. HIKARI IS REJECTED. She now fails the gate: her feet and spine disagree about which way is up, so the stand-up correction cannot resolve her either, on top of zero-length cosmetic bones and a second armature that was smuggling its own clips into the export. That is not a tuning problem, it is a file that has been through two toolchains. De-registered and removed rather than shipped broken — which is what the gate is for. Six GLB skins remain, all passing. Smoke 0 failures, 11/11 movement tests. Still open, recorded in the skill: kiyoko faces backwards, miku's grown hair stretches under animation, the mannequin's rifle hold does not convince, and taila's front skirt clipping. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
fc9e6f4275 |
feat(characters): four VRoid characters imported and selectable
Kiyoko, Hikari, Aria and Momo, all CC-BY from Sketchfab, licences recorded beside each skin. Seven GLB characters selectable now. kiyoko 13 meshes 20 cloth chains (62 bones) 0.0% cross-leg bleed aria 15 meshes 15 cloth chains (37 bones) 0.0% momo 5 meshes 9 cloth chains (35 bones) 0.0% hikari 13 meshes 10 cloth chains (37 bones) 0.2%, 12 twist bones These are the first characters imported that were NOT authored against the library's own bone spelling, and every one of them broke something that had been quietly wrong all along. All four failures were in code that guesses anatomy from names, which is exactly what tools/rig_map.py exists to stop doing: - LIMB ROLES went to the first role in LIMB_ORDER that matched at all, so shin's catch-all "leg" claimed UpperLeg before thigh's exact "upperleg" was ever consulted, and the thigh went unassigned. The result depended on the order bones arrived in. Claims are now granted longest-stem first. This also broke Mixamo (LeftUpLeg/LeftLeg) and had simply never been hit, because every character so far used Rigify DEF- names. - Names cannot settle thigh-vs-shin at all. A bare "leg" is the SHIN on Mixamo and the THIGH on a rig whose shin is "knee" — both common, same token, opposite bones. RigRoles now walks the leg from the foot upward and fills in whatever the names could not, stepping over twist bones. - verify_character.py looked for legs by the substrings "thigh"/"shin", which VRoid spells UpperLeg/LowerLeg. It declared every locomotion clip static while the legs animated perfectly, and the cross-leg bleed check found no leg vertex groups at all and passed vacuously. Two green- looking lies from one missing lookup; both now read the sidecar's resolved roles. - The cosmetic/spring classifier matched whole tokens only, so Momo's HairFL / HairFR / HairF_Top tokenised to "hairfl" and matched nothing. She imported with six chains, all bust, and no hair. Both classifiers now share one rule that also accepts a two-character positional suffix, which is short enough that "forearm" and "earring" are still untouched. Two more pipeline fixes: - A source model's own clips leaked into the export. Clearing bpy.data .actions before the library import is not enough — hikari carried two on a second armature's NLA tracks, and NLA_TRACKS export mode ships anything in a track anywhere in the file. They export as rest-pose statues. Now everything not retargeted is stripped from every object. - verify_character.py gained a check for cloth chains with no measurable extent. A chain whose bones are zero-length is dropped by the runtime and simulates nothing, while the sidecar still cheerfully reports it. Known: hikari's ten cloth chains are all zero-length and her collider fit found nothing, so her costume does not simulate — her rig has been through two toolchains and its cosmetic bones are empty terminators. She animates correctly otherwise. The new check now reports this instead of hiding it. tools/retarget.py also carries local working-tree changes that predate this session. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
cd0d1b2d99 |
feat(characters): import the Quaternius mannequin as a selectable skin
A third playable character, built with the pipeline skill from a source that was already in the repo: the animation library ships a rigged Mannequin mesh on the exact 53-joint reference skeleton, CC0, so it needed no download and retargets perfectly. 18 clips, 0.3% cross-leg bleed, 7% of verts at four influences — a clean authored-weight import. Licence recorded in mannequin.license.json as the other skins do. It has no cloth chains, correctly: it is a mannequin and has neither hair nor clothes. Importing it turned up two real bugs, both of which would have hit any flat-coloured or single-piece model: - LevelMaterials.apply_character_look treated ANY untextured surface on a character as the model's own outline shell and hid it, so the mannequin rendered as a solid black silhouette — its body and joint materials are untextured flat colours, not ink. _is_line_work() now asks whether the surface is named eyes*, is drawn front-face-culled (the inverted-hull setup), or is near-black. Taila and Miku are unaffected: their materials are textured and never reach that branch. Verified by render. - verify_character.py failed the build for having one mesh. That check cannot tell "the pipeline joined them" from "the artist authored one mesh" — Quaternius' mannequin is one piece on purpose. It is advisory now; the join path's two unambiguous signatures, cross-leg bleed and the 4-influences-everywhere spread, are still hard checks. Also restored Miku's description, which the re-import had blanked. 3 GLB skins selectable (6 with the built-in colour skins). Smoke 0 failures, 11/11 movement tests, cloth idle 0.024-0.078 deg/frame. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
374d9f9822 | feat: implement automated 3D character pipeline with retargeting and rig management tools |