Character ages and Phase 8 checkpoint (interiors)
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
{
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"stems": {
|
||||
"baby-female": {
|
||||
"age": "baby",
|
||||
"animationSource": "/media/slapin/library/ogre3/ogre-projects/world2/build-vscode/assets/blender/vrm-vroid-normal-female.blend",
|
||||
"animationSourceHash": "6b258336b456f84f42536bef39e1902bca50cc2cb45aa7dbaa0550c83365f92d",
|
||||
"outputHashes": {
|
||||
"clothes-baby-female-bottom.blend": "25b6b8b5230a5e9d91e76b733ee96856ecea873a9bb118ca5905f8d21c6fe16f",
|
||||
"clothes-baby-female-feet.blend": "4477d51b38b4383849bc3871c2ce83056f01874b7f951e89f082c0a287b457d9",
|
||||
"clothes-baby-female-hair.blend": "77397a68bb1d1aef4d92a1a218390cc8ad1e453bf82fc7e3ba2f1d0d0adc6dc2",
|
||||
"clothes-baby-female-top.blend": "a47bc879db6791056b4646b4428d2017d531b4368b4c245898310e2997c717e7",
|
||||
"edited-normal-baby-female.blend": "0afd20804330a1179a7447c7d4f0bbeed90d5c669bfe82c771a1f6bc737e3dd7"
|
||||
},
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"report": "conversion-report-baby-female.md",
|
||||
"reportJson": "conversion-report-baby-female.json",
|
||||
"sex": "female",
|
||||
"sourceHashes": {
|
||||
"clothes-female-bottom.blend": "c7a5cff31917b176c53aab88d9b22b816ad684ff906d1af856e146a4e3167beb",
|
||||
"clothes-female-feet.blend": "f07ea8aa5145137297b3b556a63e93ba8effa87fb123abb99754925553e0ace1",
|
||||
"clothes-female-hair.blend": "d0aa11db762f9f6974949e4cc579f66c211d7fc913def2c77b71a0ad9199f7bc",
|
||||
"clothes-female-top.blend": "0115190c186d740ac54527dbba944e7b2f0f448f4fa6bff89f3aaba2a02d1439",
|
||||
"edited-normal-female.blend": "b62d4160656b57752cb97fc81c517007c689ad93c510ef2ebc869ac410d13b51"
|
||||
},
|
||||
"state": "auto-seed",
|
||||
"updated": 1793419320
|
||||
},
|
||||
"baby-male": {
|
||||
"age": "baby",
|
||||
"animationSource": "/media/slapin/library/ogre3/ogre-projects/world2/build-vscode/assets/blender/vrm-vroid-normal-male.blend",
|
||||
"animationSourceHash": "a495ad4c4f73bf9971c7f180facbca29d117e002b258380ac946274c5e7b9fee",
|
||||
"outputHashes": {
|
||||
"clothes-baby-male-bottom.blend": "648803be203afe6e1a01553c8f9373a1e136881eb8a0edd1bfe2f837e9bced1c",
|
||||
"clothes-baby-male-feet.blend": "8d3eb2c8c9053c953895508f2de03b6e3abbd23ceb53ebb2da2025b0be3a0a97",
|
||||
"clothes-baby-male-hair.blend": "3e74c20b81b9cb19b4d79a862a5793e7d06726ee9ea8de6d0daeb269b3d4bd94",
|
||||
"clothes-baby-male-top.blend": "124fd1ad4f22f9efc1c3061914a8a74ea1e328af76d064d9080dc171bf3e0ed3",
|
||||
"edited-normal-baby-male.blend": "68537022ec4087743d636336b00bf33be3ed2028d0006e4b0ce2847350ba8737"
|
||||
},
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"report": "conversion-report-baby-male.md",
|
||||
"reportJson": "conversion-report-baby-male.json",
|
||||
"sex": "male",
|
||||
"sourceHashes": {
|
||||
"clothes-male-bottom.blend": "950298780654bd908c15045d8afe572de755ddb1aa38e548c1dfb912b1cc059c",
|
||||
"clothes-male-feet.blend": "f720ce5cd154ae967673dbc2c6a93a25640dad56760586d205a18ec7b550e39c",
|
||||
"clothes-male-hair.blend": "2b267783d2f0dc991c7edd972b6c21b344abf3b5a46737c664912feae0ca9549",
|
||||
"clothes-male-top.blend": "4ff80187a33acc2a4f6228dd6492b84aee342b22a18c68d2f4c9be9fb15ec9cf",
|
||||
"edited-normal-male.blend": "4cba5e5da1e22eeedd6962d24edb1ef6ecf56c9b719c2f623ee3983c235560ff"
|
||||
},
|
||||
"state": "auto-seed",
|
||||
"updated": 1793419311
|
||||
},
|
||||
"child-female": {
|
||||
"age": "child",
|
||||
"animationSource": "/media/slapin/library/ogre3/ogre-projects/world2/build-vscode/assets/blender/vrm-vroid-normal-female.blend",
|
||||
"animationSourceHash": "6b258336b456f84f42536bef39e1902bca50cc2cb45aa7dbaa0550c83365f92d",
|
||||
"outputHashes": {
|
||||
"clothes-child-female-bottom.blend": "924f20a2edbb56e8e41376fcd8ad58e6c3533707fc8cf7750451ed35e811d853",
|
||||
"clothes-child-female-feet.blend": "900a21c055e9464030e0248941099acb6838dce72c9b4bef72df679427589167",
|
||||
"clothes-child-female-hair.blend": "3f1766cdcf6102fafe030b99daa2ae5a78023bc9c2f6146d8a4924faa2a3cd57",
|
||||
"clothes-child-female-top.blend": "3950729a7079297aff7aa2d4be255813eaf69348dee4688a61c470a2493a6f05",
|
||||
"edited-normal-child-female.blend": "f8f22f31a68b40088fefc1a037b4a9622aafad2b952d07f1ab85d45876bf9882"
|
||||
},
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"report": "conversion-report-child-female.md",
|
||||
"reportJson": "conversion-report-child-female.json",
|
||||
"sex": "female",
|
||||
"sourceHashes": {
|
||||
"clothes-female-bottom.blend": "c7a5cff31917b176c53aab88d9b22b816ad684ff906d1af856e146a4e3167beb",
|
||||
"clothes-female-feet.blend": "f07ea8aa5145137297b3b556a63e93ba8effa87fb123abb99754925553e0ace1",
|
||||
"clothes-female-hair.blend": "d0aa11db762f9f6974949e4cc579f66c211d7fc913def2c77b71a0ad9199f7bc",
|
||||
"clothes-female-top.blend": "0115190c186d740ac54527dbba944e7b2f0f448f4fa6bff89f3aaba2a02d1439",
|
||||
"edited-normal-female.blend": "b62d4160656b57752cb97fc81c517007c689ad93c510ef2ebc869ac410d13b51"
|
||||
},
|
||||
"state": "auto-seed",
|
||||
"updated": 1793419306
|
||||
},
|
||||
"child-male": {
|
||||
"age": "child",
|
||||
"animationSource": "/media/slapin/library/ogre3/ogre-projects/world2/build-vscode/assets/blender/vrm-vroid-normal-male.blend",
|
||||
"animationSourceHash": "a495ad4c4f73bf9971c7f180facbca29d117e002b258380ac946274c5e7b9fee",
|
||||
"outputHashes": {
|
||||
"clothes-child-male-bottom.blend": "631f0ae0796b29797716be04c0cc8940693a40f673a97d5f6fd219ea541302c3",
|
||||
"clothes-child-male-feet.blend": "2bed6d24379ec2b322884324d8faea6cd0b1ddc6f62f4bbc4f6396f7adff623b",
|
||||
"clothes-child-male-hair.blend": "6e02f2bbf3f06365d6cde9acbe178e869b417dcb1b163db0dad9e2999395e6bc",
|
||||
"clothes-child-male-top.blend": "8aef24cab77cb209af0393b5bf059e61d411d77236c27bbbd0b126bf63c9211c",
|
||||
"edited-normal-child-male.blend": "6d889491af59a9551f4237cd50fba43bdb1b0aeffbf2e6cc0d5e572da9ea6c53"
|
||||
},
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"report": "conversion-report-child-male.md",
|
||||
"reportJson": "conversion-report-child-male.json",
|
||||
"sex": "male",
|
||||
"sourceHashes": {
|
||||
"clothes-male-bottom.blend": "950298780654bd908c15045d8afe572de755ddb1aa38e548c1dfb912b1cc059c",
|
||||
"clothes-male-feet.blend": "f720ce5cd154ae967673dbc2c6a93a25640dad56760586d205a18ec7b550e39c",
|
||||
"clothes-male-hair.blend": "2b267783d2f0dc991c7edd972b6c21b344abf3b5a46737c664912feae0ca9549",
|
||||
"clothes-male-top.blend": "4ff80187a33acc2a4f6228dd6492b84aee342b22a18c68d2f4c9be9fb15ec9cf",
|
||||
"edited-normal-male.blend": "4cba5e5da1e22eeedd6962d24edb1ef6ecf56c9b719c2f623ee3983c235560ff"
|
||||
},
|
||||
"state": "auto-seed",
|
||||
"updated": 1793419297
|
||||
},
|
||||
"elder-female": {
|
||||
"age": "elder",
|
||||
"animationSource": "/media/slapin/library/ogre3/ogre-projects/world2/build-vscode/assets/blender/vrm-vroid-normal-female.blend",
|
||||
"animationSourceHash": "6b258336b456f84f42536bef39e1902bca50cc2cb45aa7dbaa0550c83365f92d",
|
||||
"outputHashes": {
|
||||
"clothes-elder-female-bottom.blend": "8b9c1be20d83b46ee33397283625e03b2e147c3e35eea5b9eb3b47696f4bea66",
|
||||
"clothes-elder-female-feet.blend": "69c1e995860e5678560cfe76e1349828e49d230d7ee3b4167767bb6708325da1",
|
||||
"clothes-elder-female-hair.blend": "fced301a4709adf9e8a05a17583b8879b0a9f154bd532ed567df9ce924f2859b",
|
||||
"clothes-elder-female-top.blend": "f783777345dfeb83811adc349a4b4f7c21bd5f3085f13c2b218637298f184bad",
|
||||
"edited-normal-elder-female.blend": "570ff96ebfa9005a3c21755603b9a4942ab8336461c7af74bdd970ca1871e9c2"
|
||||
},
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"report": "conversion-report-elder-female.md",
|
||||
"reportJson": "conversion-report-elder-female.json",
|
||||
"sex": "female",
|
||||
"sourceHashes": {
|
||||
"clothes-female-bottom.blend": "c7a5cff31917b176c53aab88d9b22b816ad684ff906d1af856e146a4e3167beb",
|
||||
"clothes-female-feet.blend": "f07ea8aa5145137297b3b556a63e93ba8effa87fb123abb99754925553e0ace1",
|
||||
"clothes-female-hair.blend": "d0aa11db762f9f6974949e4cc579f66c211d7fc913def2c77b71a0ad9199f7bc",
|
||||
"clothes-female-top.blend": "0115190c186d740ac54527dbba944e7b2f0f448f4fa6bff89f3aaba2a02d1439",
|
||||
"edited-normal-female.blend": "b62d4160656b57752cb97fc81c517007c689ad93c510ef2ebc869ac410d13b51"
|
||||
},
|
||||
"state": "auto-seed",
|
||||
"updated": 1793419277
|
||||
},
|
||||
"elder-male": {
|
||||
"age": "elder",
|
||||
"animationSource": "/media/slapin/library/ogre3/ogre-projects/world2/build-vscode/assets/blender/vrm-vroid-normal-male.blend",
|
||||
"animationSourceHash": "a495ad4c4f73bf9971c7f180facbca29d117e002b258380ac946274c5e7b9fee",
|
||||
"outputHashes": {
|
||||
"clothes-elder-male-bottom.blend": "1e5dcf7d41f8f3fe5c3716342a2f9d7163558492f8e4ecab4e37e7aaa0b6d6aa",
|
||||
"clothes-elder-male-feet.blend": "60af548ee9845a29df7021f92378032d961c28108503f39f9a97640bf80f1024",
|
||||
"clothes-elder-male-hair.blend": "f62f93f4464b41d1b27ed89a39d660e3aa1cc6ee1abfbe52c9e5620ffd838616",
|
||||
"clothes-elder-male-top.blend": "a8c422ae1dbcad1f4672578f68c7577b5dff198009968f0689e036090d485e67",
|
||||
"edited-normal-elder-male.blend": "93554b09a4653357b4b91682e27bdda988421ded9eca60902a530537c18c4d5f"
|
||||
},
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"report": "conversion-report-elder-male.md",
|
||||
"reportJson": "conversion-report-elder-male.json",
|
||||
"sex": "male",
|
||||
"sourceHashes": {
|
||||
"clothes-male-bottom.blend": "950298780654bd908c15045d8afe572de755ddb1aa38e548c1dfb912b1cc059c",
|
||||
"clothes-male-feet.blend": "f720ce5cd154ae967673dbc2c6a93a25640dad56760586d205a18ec7b550e39c",
|
||||
"clothes-male-hair.blend": "2b267783d2f0dc991c7edd972b6c21b344abf3b5a46737c664912feae0ca9549",
|
||||
"clothes-male-top.blend": "4ff80187a33acc2a4f6228dd6492b84aee342b22a18c68d2f4c9be9fb15ec9cf",
|
||||
"edited-normal-male.blend": "4cba5e5da1e22eeedd6962d24edb1ef6ecf56c9b719c2f623ee3983c235560ff"
|
||||
},
|
||||
"state": "auto-seed",
|
||||
"updated": 1793419267
|
||||
},
|
||||
"teenager-female": {
|
||||
"age": "teenager",
|
||||
"animationSource": "/media/slapin/library/ogre3/ogre-projects/world2/build-vscode/assets/blender/vrm-vroid-normal-female.blend",
|
||||
"animationSourceHash": "6b258336b456f84f42536bef39e1902bca50cc2cb45aa7dbaa0550c83365f92d",
|
||||
"outputHashes": {
|
||||
"clothes-teenager-female-bottom.blend": "77ff890f2edaa894014c88bf1e1047335182d166bc39ac48746c1dac3d374f1a",
|
||||
"clothes-teenager-female-feet.blend": "b2fed95f17586630e331642f1afa33a6d63ba1ed58758be68a5526082d520809",
|
||||
"clothes-teenager-female-hair.blend": "0c5a762e74f95bd7b10679fa986133247125e46cc33f9a08dfeeed47f3f7719b",
|
||||
"clothes-teenager-female-top.blend": "ce617dbb60927fa81a530019bfd0b822743ac57b7dd96d894c5144a2803e3335",
|
||||
"edited-normal-teenager-female.blend": "1cfb42a0086b0704a1eb9f17ee027ae01ccf4be5664f126aa7d776398b8e3f91"
|
||||
},
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"report": "conversion-report-teenager-female.md",
|
||||
"reportJson": "conversion-report-teenager-female.json",
|
||||
"sex": "female",
|
||||
"sourceHashes": {
|
||||
"clothes-female-bottom.blend": "c7a5cff31917b176c53aab88d9b22b816ad684ff906d1af856e146a4e3167beb",
|
||||
"clothes-female-feet.blend": "f07ea8aa5145137297b3b556a63e93ba8effa87fb123abb99754925553e0ace1",
|
||||
"clothes-female-hair.blend": "d0aa11db762f9f6974949e4cc579f66c211d7fc913def2c77b71a0ad9199f7bc",
|
||||
"clothes-female-top.blend": "0115190c186d740ac54527dbba944e7b2f0f448f4fa6bff89f3aaba2a02d1439",
|
||||
"edited-normal-female.blend": "b62d4160656b57752cb97fc81c517007c689ad93c510ef2ebc869ac410d13b51"
|
||||
},
|
||||
"state": "auto-seed",
|
||||
"updated": 1793419291
|
||||
},
|
||||
"teenager-male": {
|
||||
"age": "teenager",
|
||||
"animationSource": "/media/slapin/library/ogre3/ogre-projects/world2/build-vscode/assets/blender/vrm-vroid-normal-male.blend",
|
||||
"animationSourceHash": "a495ad4c4f73bf9971c7f180facbca29d117e002b258380ac946274c5e7b9fee",
|
||||
"outputHashes": {
|
||||
"clothes-teenager-male-bottom.blend": "9547a808429d71da27fdb11d58f69a2c18e6ad3f4fe78b5348dd25050133ad37",
|
||||
"clothes-teenager-male-feet.blend": "33d32f57c79f5a7b8e9c701c5e0aeffdd052a75b92ea7de234128c418e8be332",
|
||||
"clothes-teenager-male-hair.blend": "fc9c767afe4291c8a4634c2c1fc886c42d0136b905cc2288d3ca363a45957e99",
|
||||
"clothes-teenager-male-top.blend": "7b14973503c70681acbcaf66d7712a5e0ed9336875e4780d84ec749525624161",
|
||||
"edited-normal-teenager-male.blend": "d476061b1ee14df740bb052b82a2be5938246c05fcaf2df1dcded73ca1196daa"
|
||||
},
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"report": "conversion-report-teenager-male.md",
|
||||
"reportJson": "conversion-report-teenager-male.json",
|
||||
"sex": "male",
|
||||
"sourceHashes": {
|
||||
"clothes-male-bottom.blend": "950298780654bd908c15045d8afe572de755ddb1aa38e548c1dfb912b1cc059c",
|
||||
"clothes-male-feet.blend": "f720ce5cd154ae967673dbc2c6a93a25640dad56760586d205a18ec7b550e39c",
|
||||
"clothes-male-hair.blend": "2b267783d2f0dc991c7edd972b6c21b344abf3b5a46737c664912feae0ca9549",
|
||||
"clothes-male-top.blend": "4ff80187a33acc2a4f6228dd6492b84aee342b22a18c68d2f4c9be9fb15ec9cf",
|
||||
"edited-normal-male.blend": "4cba5e5da1e22eeedd6962d24edb1ef6ecf56c9b719c2f623ee3983c235560ff"
|
||||
},
|
||||
"state": "auto-seed",
|
||||
"updated": 1793419282
|
||||
}
|
||||
},
|
||||
"updated": 1793419320,
|
||||
"version": 1
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,37 +20,90 @@ cmake --build build-vscode --target edited-blends # base bodies + animations
|
||||
cmake --build build-vscode # full pipeline incl. clothes
|
||||
```
|
||||
|
||||
Pipeline per sex (`male` / `female`):
|
||||
All Blender steps are wrapped in `${BLENDER_LOCKED}` (`flock -x
|
||||
${BLENDER_LOCK} <blender>`). This makes `-j` builds safe: Blender jobs
|
||||
are serialized because Blender 3.6 shares user config/addon state and the
|
||||
scripts touch common build files, while CMake copies/compiles in
|
||||
parallel. Configure fails if `flock` is missing. The lock defaults to
|
||||
`<build-dir>/blender-assets.lock`; pass the same `-DBLENDER_LOCK=...` to
|
||||
concurrent build trees. Never call `${BLENDER}` directly in a custom
|
||||
command; use `${BLENDER_LOCKED}`.
|
||||
|
||||
1. `edited-normal-<sex>.blend` is the base body. `copy_animations.py`
|
||||
copies the Mixamo animation library (`assets/blender/mixamo/`) into
|
||||
it — idle, walking, running, swimming, treading_water, sitting,
|
||||
sitting-ground, sitting-vehicle, ...
|
||||
2. Each `clothes-<sex>-<slot>.blend` holds garment meshes for one slot.
|
||||
Canonical age bands are `baby`, `child`, `teenager`, `adult` and `elder`.
|
||||
Adult keeps the legacy stem `male`/`female`; every new band uses
|
||||
`<age>-<sex>` (for example `child-male`, `elder-female`). The new base
|
||||
bodies and their clothes are generated once from the adult assets by
|
||||
`generate_age_blends.py` + the `agegen/` package (rest-pose rig scaling,
|
||||
weighted mesh/shape-key deformation, garment/hair conversion and animation
|
||||
retargeting). The generated `.blend` files are committed seeds; the normal
|
||||
build consumes them as-is and never re-runs the generator or retargeting.
|
||||
See `AGES-PLAN.md` for the conversion/hand-off contract and
|
||||
`generate_age_blends.py --check` for seed verification.
|
||||
|
||||
Pipeline per stem (`male`, `female`, `baby-male`, `child-female`, ...):
|
||||
|
||||
1. `edited-normal-<stem>.blend` is the base body. For **adult**,
|
||||
`copy_animations.py` copies the Mixamo animation library
|
||||
(`assets/blender/mixamo/`) into it. New age seeds already contain
|
||||
retargeted actions and NLA tracks.
|
||||
2. Each `clothes-<age>-<sex>-<slot>.blend` (adult:
|
||||
`clothes-<sex>-<slot>.blend`) holds garment meshes for one slot.
|
||||
`process_clothes.py` fits them to the body (weight transfer from the
|
||||
reference body parts, seam fixes), `combine_clothes.py` bakes the
|
||||
combined meshes for garment combinations (see §3).
|
||||
3. `consolidate.py` appends everything into
|
||||
`edited-normal-<sex>-consolidated.blend`.
|
||||
`edited-normal-<stem>-consolidated.blend` and leaves exactly one body
|
||||
armature.
|
||||
4. `export_models2.py` exports the consolidated blend through
|
||||
blender2ogre: one `.mesh` per catalog mesh object, the shared
|
||||
`.skeleton`, `.material` files, the texture atlases
|
||||
(`clothes-atlas.png`, `clothes-atlas-normal.png`, `face-atlas.png`),
|
||||
`.skeleton`, `.material` files, the prefixed texture atlases,
|
||||
a `.glb` preview, and one **`body_part_<mesh data name>.json`
|
||||
catalog entry per mesh object** carrying the required custom
|
||||
properties (see §2).
|
||||
|
||||
Outputs land in `build-vscode/characters/<sex>/` and are staged into
|
||||
Outputs land in `build-vscode/characters/<stem>/` and are staged into
|
||||
the editScene runtime `characters/` directory (OGRE resource group
|
||||
`Characters`). The engine discovers catalog entries by scanning every
|
||||
`Characters`; every stem directory is registered as a flat resource
|
||||
location). The engine discovers catalog entries by scanning every
|
||||
resource group for `body_part_*.json` — no manual registration step.
|
||||
All resource basenames in the tree must be unique; textures are prefixed
|
||||
with `<stem>-` during export.
|
||||
|
||||
**blender2ogre names `.mesh` files after the mesh DATA name** (not the
|
||||
object name) — set both, and never reuse another blend's mesh data
|
||||
names: everything exports into the same directory and silently
|
||||
overwrites.
|
||||
|
||||
## 2. Catalog entry contract (custom properties on the MESH OBJECT)
|
||||
## 1.1 Age seeds (baby / child / teenager / elder)
|
||||
|
||||
`generate_age_blends.py` (with the `agegen/` package) creates first-pass
|
||||
seeds from the same-sex adult base and the adult wardrobe:
|
||||
|
||||
- the age rig keeps every adult bone name, hierarchy, roll and rest
|
||||
orientation; joints/segment lengths and radial thickness are scaled by
|
||||
`age_profiles.py`;
|
||||
- body, face, hair, clothes, helpers and shape keys are deformed by the
|
||||
weighted rest-pose matrices `D_i = F_age · S_i · F_adult⁻¹`;
|
||||
- animations are copied from the Mixamo library and their location/root
|
||||
tracks are scaled by the bone rest-offset ratios; rotations are untouched;
|
||||
- the result is written as `edited-normal-<age>-<sex>.blend` plus
|
||||
`clothes-<age>-<sex>-{top,bottom,feet,hair}.blend`, a conversion report
|
||||
and `AGE-GENERATION.json`.
|
||||
|
||||
Generated seeds are committed and marked `agegen_state = auto-seed`. The
|
||||
normal build must not run the generator or the retarget step again; the
|
||||
retarget step would double-scale root motion. Seeds with
|
||||
`agegen_state = hand-edited` (or a changed file hash) are protected from
|
||||
regeneration; use `--force` only when intentionally replacing them.
|
||||
`generate_age_blends.py --check` verifies the seed hashes and
|
||||
`validate_age_assets.py` verifies the built catalog/skeleton/resource tree.
|
||||
|
||||
The automatic seed is a **complete editable base**, not final art. Baby and
|
||||
child faces/silhouettes are expected to need a manual detail pass; the
|
||||
conversion report lists the first objects to inspect (stretch, seam,
|
||||
penetration, weight and animation issues).
|
||||
|
||||
|
||||
|
||||
`export_models2.py` writes a catalog JSON for every mesh object that
|
||||
carries all three required custom properties (Properties panel ->
|
||||
@@ -58,7 +111,7 @@ Object -> Custom Properties):
|
||||
|
||||
| Property | Type | Meaning |
|
||||
|----------|------|---------|
|
||||
| `age` | string | age band, currently only `adult` exists |
|
||||
| `age` | string | canonical band: `baby` / `child` / `teenager` / `adult` / `elder` (aliases `infant`, `teen`, `senior` normalize on load) |
|
||||
| `sex` | string | `male` / `female` |
|
||||
| `slot` | string | `top` / `bottom` / `feet` / `hair` / `face` |
|
||||
| `layer` | int | 0 = nude base, 1 = underwear (and hair), 2 = clothes (default 0) |
|
||||
@@ -231,12 +284,12 @@ hysteresis on every boundary):
|
||||
- **LOD0 (< 10 m)**: modular, full detail — the normal catalog parts.
|
||||
- **LOD1 (10-30 m)**: per-part `<mesh base>_lod1.mesh` variants — same
|
||||
geometry, shape keys removed, UVs remapped into one shared 2048x2048
|
||||
atlas (`charlod_<sex>-atlas.png`) with a single `charlod_<sex>`
|
||||
material per sex. Hair slots are skipped (hidden/impostor-baked at
|
||||
atlas (`charlod_<stem>-atlas.png`) with a single `charlod_<stem>`
|
||||
material per stem. Hair slots are skipped (hidden/impostor-baked at
|
||||
distance anyway). Parts without a `_lod1` variant keep their base
|
||||
mesh, so the scheme degrades gracefully.
|
||||
- **LOD2 (30-70 m)**: whole-character impostors
|
||||
`charlod_<sex>_thin_lod2.mesh` / `charlod_<sex>_fat_lod2.mesh` — the
|
||||
`charlod_<stem>_thin_lod2.mesh` / `charlod_<stem>_fat_lod2.mesh` — the
|
||||
deterministic default outfit (per slot: highest layer, first entry by
|
||||
name) joined into ONE mesh on the shared armature, `fat` shape key
|
||||
baked at 0/1, hair rigid-bound to `mixamorig:Head`, atlas material.
|
||||
@@ -250,16 +303,16 @@ hysteresis on every boundary):
|
||||
All part entities are hidden; the master entity keeps animating the
|
||||
shared skeleton and the impostor follows it via
|
||||
`shareSkeletonInstanceWith`. Hair physics (ragdoll) is OFF.
|
||||
- **LOD3 (70-100 m, till despawn)**: `charlod_<sex>_<variant>_lod3.mesh` —
|
||||
- **LOD3 (70-100 m, till despawn)**: `charlod_<stem>_<variant>_lod3.mesh` —
|
||||
collapse-decimated LOD2, ~400 triangles (hard limit 400).
|
||||
- Interiors need only LOD0/LOD1.
|
||||
|
||||
Generation is automated in the export pipeline:
|
||||
`assets/blender/characters/generate_lods.py` runs per sex on the
|
||||
consolidated blend (CMake step `edited-normal-<sex>-lods.blend`),
|
||||
`assets/blender/characters/generate_lods.py` runs per stem on the
|
||||
consolidated blend (CMake step `edited-normal-<stem>-lods.blend`),
|
||||
`export_models2.py` appends the result via its 6th argument and exports
|
||||
the LOD meshes with the SAME shared armature. A manifest
|
||||
(`charlod_<sex>.json`: atlas file + impostor mesh names) is written for
|
||||
(`charlod_<stem>.json`: atlas file + impostor mesh names) is written for
|
||||
reference; the runtime uses name conventions + `resourceExists`, not
|
||||
the manifest. `CharacterSlotSystem::prewarmShaders` also warms the
|
||||
`*_lod1.mesh` / `charlod_*.mesh` shaders.
|
||||
|
||||
@@ -1,30 +1,76 @@
|
||||
project(characters)
|
||||
|
||||
set(EDITED_BLENDS_LIST
|
||||
male
|
||||
female
|
||||
)
|
||||
set(CHARACTER_AGES adult baby child teenager elder)
|
||||
set(CHARACTER_SEXES male female)
|
||||
set(CHARACTER_BUILD_AGES ${CHARACTER_AGES} CACHE STRING
|
||||
"Age bands to build (semicolon list; e.g. adult or adult;elder)")
|
||||
|
||||
# Adult keeps the legacy stem (male/female) and all existing resource names.
|
||||
function(character_stem AGE SEX OUT)
|
||||
if(AGE STREQUAL "adult")
|
||||
set(${OUT} ${SEX} PARENT_SCOPE)
|
||||
else()
|
||||
set(${OUT} "${AGE}-${SEX}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(character_build_blend AGE SEX OUT)
|
||||
character_stem(${AGE} ${SEX} STEM)
|
||||
set(${OUT}
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${STEM}.blend"
|
||||
PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(character_source_blend AGE SEX OUT)
|
||||
character_stem(${AGE} ${SEX} STEM)
|
||||
set(${OUT}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-${STEM}.blend"
|
||||
PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(character_glb AGE SEX OUT)
|
||||
character_stem(${AGE} ${SEX} STEM)
|
||||
set(${OUT} "${CMAKE_BINARY_DIR}/characters/${STEM}/normal-${STEM}.glb"
|
||||
PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
set(EDITED_BLEND_TARGETS)
|
||||
set(CHARACTER_GLBS)
|
||||
foreach(EDITED_BLEND ${EDITED_BLENDS_LIST})
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${EDITED_BLEND}.blend
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-${EDITED_BLEND}.blend
|
||||
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-${EDITED_BLEND}.blend
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/copy_animations.py
|
||||
${CMAKE_BINARY_DIR}/assets/blender/mixamo
|
||||
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-E copy ${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-${EDITED_BLEND}.blend
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${EDITED_BLEND}.blend
|
||||
COMMAND ${BLENDER} -b -Y
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${EDITED_BLEND}.blend
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/copy_animations.py --
|
||||
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-${EDITED_BLEND}.blend ${EDITED_BLEND}
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
)
|
||||
list(APPEND EDITED_BLEND_TARGETS ${CMAKE_BINARY_DIR}/assets/blender/characters/edited-normal-${EDITED_BLEND}.blend)
|
||||
list(APPEND CHARACTER_GLBS ${CMAKE_BINARY_DIR}/characters/${EDITED_BLEND}/normal-${EDITED_BLEND}.glb)
|
||||
|
||||
foreach(AGE ${CHARACTER_BUILD_AGES})
|
||||
foreach(SEX ${CHARACTER_SEXES})
|
||||
character_stem(${AGE} ${SEX} STEM)
|
||||
character_source_blend(${AGE} ${SEX} BASE_SOURCE)
|
||||
character_build_blend(${AGE} ${SEX} BASE_BUILD)
|
||||
set(BASE_DEPS ${BASE_SOURCE} ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed)
|
||||
set(BASE_COMMANDS
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${BASE_SOURCE} ${BASE_BUILD})
|
||||
if(AGE STREQUAL "adult")
|
||||
# Adult keeps the historical animation-copy step.
|
||||
list(APPEND BASE_DEPS
|
||||
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-${SEX}.blend
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/copy_animations.py
|
||||
${CMAKE_BINARY_DIR}/assets/blender/mixamo)
|
||||
list(APPEND BASE_COMMANDS
|
||||
COMMAND ${BLENDER_LOCKED} -b -Y ${BASE_BUILD}
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/copy_animations.py --
|
||||
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-${SEX}.blend ${SEX})
|
||||
else()
|
||||
# Generated seeds already contain retargeted actions + NLA
|
||||
# tracks. Never retarget here (double scaling).
|
||||
list(APPEND BASE_DEPS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/generate_age_blends.py)
|
||||
endif()
|
||||
add_custom_command(
|
||||
OUTPUT ${BASE_BUILD}
|
||||
DEPENDS ${BASE_DEPS}
|
||||
${BASE_COMMANDS}
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
)
|
||||
list(APPEND EDITED_BLEND_TARGETS ${BASE_BUILD})
|
||||
character_glb(${AGE} ${SEX} GLB)
|
||||
list(APPEND CHARACTER_GLBS ${GLB})
|
||||
endforeach()
|
||||
endforeach()
|
||||
list(APPEND CHARACTER_GLBS ${CMAKE_BINARY_DIR}/characters/male/male-clothes-toprobe.glb)
|
||||
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/assets/blender/mixamo
|
||||
@@ -41,61 +87,19 @@ set(VRM_IMPORTED_BLENDS
|
||||
#add_custom_command(
|
||||
# OUTPUT ${CHARACTER_GLBS}
|
||||
# COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
|
||||
# COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py
|
||||
# COMMAND ${BLENDER_LOCKED} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py
|
||||
# COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/male/normal-male.glb -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
|
||||
# COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/female/normal-female.glb -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
|
||||
# COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${CHARACTER_GLBS}
|
||||
# DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py ${VRM_IMPORTED_BLENDS} ${EDITED_BLEND_TARGETS}
|
||||
# WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/characters/male/normal-male.glb
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
|
||||
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models2.py --
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-consolidated.blend
|
||||
${CMAKE_BINARY_DIR}/characters/male/normal-male.glb
|
||||
"AUTO"
|
||||
"male"
|
||||
tmp-edited-male.blend
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-lods.blend
|
||||
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/male/normal-male.glb
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
|
||||
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/male/normal-male.scene
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models2.py
|
||||
${VRM_IMPORTED_BLENDS}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-consolidated.blend
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-lods.blend
|
||||
# ${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-consolidated-hair.stamp
|
||||
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
VERBATIM
|
||||
)
|
||||
# Age/sex export commands are generated after the clothes/LOD functions are
|
||||
# defined (see the bottom of this file). The top-level export loop covers
|
||||
# adult and all generated age seeds.
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/characters/female/normal-female.glb
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
|
||||
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models2.py --
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female-consolidated.blend
|
||||
${CMAKE_BINARY_DIR}/characters/female/normal-female.glb
|
||||
"AUTO"
|
||||
"female"
|
||||
tmp-edited-female.blend
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female-lods.blend
|
||||
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/female/normal-female.glb
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
|
||||
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/female/normal-female.scene
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models2.py
|
||||
${VRM_IMPORTED_BLENDS}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female-consolidated.blend
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female-lods.blend
|
||||
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/characters/male/male-clothes-toprobe.glb
|
||||
COMMAND ${BLENDER} -b -Y ${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-male.blend
|
||||
COMMAND ${BLENDER_LOCKED} -b -Y ${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-male.blend
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/export_clothes.py
|
||||
-- ${CMAKE_BINARY_DIR}/characters/male/male-clothes-toprobe.glb
|
||||
BodyTopRobe
|
||||
@@ -126,7 +130,7 @@ foreach(MIXAMO_FILE ${MIXAMO_FILES})
|
||||
list(APPEND VRM_SOURCE "${OUTPUT_FILE}")
|
||||
endforeach()
|
||||
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
|
||||
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/install_addons.py
|
||||
COMMAND ${BLENDER_LOCKED} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/install_addons.py
|
||||
COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
|
||||
DEPENDS ${CMAKE_BINARY_DIR}/assets/blender/scripts/addons/3.6/VRM_Addon_for_Blender-release.zip
|
||||
${CMAKE_BINARY_DIR}/assets/blender/scripts/addons/3.6/io_ogre.zip
|
||||
@@ -152,7 +156,7 @@ add_custom_target(install_addons ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/blender
|
||||
|
||||
#add_custom_command(OUTPUT ${VRM_IMPORTED_BLENDS}
|
||||
# COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
|
||||
# COMMAND ${BLENDER} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/import_vrm.py
|
||||
# COMMAND ${BLENDER_LOCKED} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/import_vrm.py
|
||||
# COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${VRM_IMPORTED_BLENDS}
|
||||
# DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/import_vrm.py
|
||||
# ${CMAKE_CURRENT_BINARY_DIR}/blender-assons-installed
|
||||
@@ -164,7 +168,7 @@ function(blender_import_vrm BLEND VRM EDITABLE RIG)
|
||||
get_filename_component(VRM_NAME ${VRM} NAME_WE)
|
||||
add_custom_command(OUTPUT ${BLEND}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
|
||||
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/import_vrm2.py -- ${VRM_NAME}.vrm ${BLEND} ${EDITABLE} ${RIG}
|
||||
COMMAND ${BLENDER_LOCKED} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/import_vrm2.py -- ${VRM_NAME}.vrm ${BLEND} ${EDITABLE} ${RIG}
|
||||
COMMAND ${CMAKE_COMMAND} -D FILE=${BLEND} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
|
||||
COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${BLEND}
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/import_vrm2.py
|
||||
@@ -186,9 +190,9 @@ blender_import_vrm(${CMAKE_BINARY_DIR}/assets/blender/shapes/male/vrm-vroid-norm
|
||||
|
||||
#add_custom_command(OUTPUT ${VRM_IMPORTED_BLENDS}
|
||||
# COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
|
||||
# COMMAND ${BLENDER} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/import_vrm2.py jane2-dress.vrm vrm-vroid-normal-female.blend modelling-vroid-normal-female.blend female
|
||||
# COMMAND ${BLENDER} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/import_vrm2.py buch1.vrm vrm-vroid-normal-male.blend modelling-vroid-normal-male.blend male
|
||||
# COMMAND ${BLENDER} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/import_vrm2.py buch1-chibi.vrm shapes/male/vrm-vroid-normal-male-chibi.blend modelling-shape-male-chibi.blend male
|
||||
# COMMAND ${BLENDER_LOCKED} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/import_vrm2.py jane2-dress.vrm vrm-vroid-normal-female.blend modelling-vroid-normal-female.blend female
|
||||
# COMMAND ${BLENDER_LOCKED} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/import_vrm2.py buch1.vrm vrm-vroid-normal-male.blend modelling-vroid-normal-male.blend male
|
||||
# COMMAND ${BLENDER_LOCKED} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/import_vrm2.py buch1-chibi.vrm shapes/male/vrm-vroid-normal-male-chibi.blend modelling-shape-male-chibi.blend male
|
||||
# COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${VRM_IMPORTED_BLENDS}
|
||||
# DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/import_vrm.py
|
||||
# ${CMAKE_CURRENT_BINARY_DIR}/blender-assons-installed
|
||||
@@ -211,31 +215,28 @@ add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/characters/shapes/male/chibi/vroid
|
||||
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/shapes/male/chibi/vroid-normal-male-chibi.glb
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/shapes/male/chibi/vroid-normal-male-chibi.glb
|
||||
${CMAKE_BINARY_DIR}/characters/shapes/male/chibi/vroid-normal-male-chibi.glb)
|
||||
add_custom_target(morph ALL DEPENDS MorphTargetsResearch ${CHARACTER_GLBS})
|
||||
|
||||
# Targets (morph/edited-blends/import_vrm and per-age characters-*) are
|
||||
# defined after the age/sex export commands at the bottom of this file.
|
||||
|
||||
add_custom_target(edited-blends ALL DEPENDS ${EDITED_BLEND_TARGETS})
|
||||
|
||||
add_custom_target(import_vrm DEPENDS ${CHARACTER_GLBS})
|
||||
|
||||
function(weight_clothes SRC)
|
||||
function(weight_clothes SRC BODY)
|
||||
get_filename_component(TARGET_NAME ${SRC} NAME_WE)
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/clothes/${TARGET_NAME}_weighted.stamp
|
||||
${CMAKE_CURRENT_BINARY_DIR}/clothes/${TARGET_NAME}_weighted.blend
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/clothes
|
||||
COMMAND ${BLENDER} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/process_clothes.py -- ${CMAKE_CURRENT_SOURCE_DIR}/${TARGET_NAME}.blend ./ ${CMAKE_CURRENT_BINARY_DIR}/clothes
|
||||
COMMAND ${BLENDER_LOCKED} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/process_clothes.py -- ${SRC} ./ ${CMAKE_CURRENT_BINARY_DIR}/clothes ${BODY}
|
||||
COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/clothes/${TARGET_NAME}_weighted.stamp
|
||||
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_CURRENT_BINARY_DIR}/clothes/${TARGET_NAME}_weighted.blend
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
|
||||
DEPENDS ${SRC}
|
||||
DEPENDS ${SRC} ${BODY}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
|
||||
)
|
||||
endfunction()
|
||||
function(transfer_shape_keys SRC CLOTH DST)
|
||||
add_custom_command(
|
||||
OUTPUT ${DST}
|
||||
COMMAND ${BLENDER} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/transfer_shape_keys.py -- ${SRC} ${CLOTH} ${DST}
|
||||
COMMAND ${BLENDER_LOCKED} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/transfer_shape_keys.py -- ${SRC} ${CLOTH} ${DST}
|
||||
COMMAND ${CMAKE_COMMAND} -D FILE=${DST}
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
|
||||
DEPENDS ${SRC} ${CLOTH}
|
||||
@@ -291,7 +292,7 @@ function(add_clothes_combination INPUT_BLEND WEIGHTED_BLEND COMBINED_BLEND)
|
||||
add_custom_command(
|
||||
OUTPUT ${STAMP_FILE} ${COMBINED_BLEND}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${COMBINED_DIR}
|
||||
COMMAND ${BLENDER} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/combine_clothes.py --
|
||||
COMMAND ${BLENDER_LOCKED} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/combine_clothes.py --
|
||||
${INPUT_BLEND}
|
||||
${WEIGHTED_BLEND}
|
||||
${COMBINED_BLEND}
|
||||
@@ -348,7 +349,7 @@ function(add_blend_consolidation INPUT_BLEND COMBINED_BLEND OUTPUT_BLEND)
|
||||
${COMBINE_STAMP}
|
||||
${INPUT_BLEND}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${OUTPUT_DIR}
|
||||
COMMAND ${BLENDER} -b -Y ${INPUT_BLEND}
|
||||
COMMAND ${BLENDER_LOCKED} -b -Y ${INPUT_BLEND}
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/consolidate.py --
|
||||
${COMBINED_BLEND}
|
||||
${OUTPUT_BLEND}
|
||||
@@ -425,28 +426,26 @@ function(add_clothes_pipeline INPUT_BLEND WEIGHTED_BLEND FINAL_OUTPUT_BLEND)
|
||||
)
|
||||
endfunction()
|
||||
|
||||
set(SEX_LIST "male" "female")
|
||||
foreach(SEX ${SEX_LIST})
|
||||
set(HAIR_WEIGHTED_STAMP "${CMAKE_CURRENT_BINARY_DIR}/clothes/clothes-${SEX}-hair_weighted.stamp")
|
||||
add_custom_command(
|
||||
OUTPUT ${HAIR_WEIGHTED_STAMP}
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/clothes-${SEX}-hair.blend
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/process_clothes.py
|
||||
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/clothes
|
||||
COMMAND ${BLENDER} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/process_clothes.py --
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/clothes-${SEX}-hair.blend
|
||||
./ ${CMAKE_CURRENT_BINARY_DIR}/clothes
|
||||
COMMAND ${CMAKE_COMMAND} -E touch ${HAIR_WEIGHTED_STAMP}
|
||||
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_CURRENT_BINARY_DIR}/clothes/clothes-${SEX}-hair_weighted.blend
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
|
||||
COMMENT "Processing hair meshes (weight transfer)"
|
||||
)
|
||||
set(SLOT_LIST "bottom" "top" "feet" "hair")
|
||||
list(GET SLOT_LIST -1 LAST_SLOT)
|
||||
|
||||
weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-${SEX}-top.blend)
|
||||
weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-${SEX}-bottom.blend)
|
||||
weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-${SEX}-feet.blend)
|
||||
endforeach()
|
||||
function(character_clothes_source AGE SEX SLOT OUT)
|
||||
if(AGE STREQUAL "adult")
|
||||
set(${OUT} ${CMAKE_CURRENT_SOURCE_DIR}/clothes-${SEX}-${SLOT}.blend
|
||||
PARENT_SCOPE)
|
||||
else()
|
||||
set(${OUT} ${CMAKE_CURRENT_SOURCE_DIR}/clothes-${AGE}-${SEX}-${SLOT}.blend
|
||||
PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(character_clothes_name AGE SEX SLOT OUT)
|
||||
if(AGE STREQUAL "adult")
|
||||
set(${OUT} "clothes-${SEX}-${SLOT}" PARENT_SCOPE)
|
||||
else()
|
||||
set(${OUT} "clothes-${AGE}-${SEX}-${SLOT}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Propagate missing shape keys (like "fat") from Body_shapes to base body parts.
|
||||
# This ensures all body part meshes have consistent shape keys, preventing seams.
|
||||
@@ -458,57 +457,115 @@ function(add_shape_key_propagation INPUT_BLEND OUTPUT_BLEND)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/transfer_shape_keys.py
|
||||
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${INPUT_BLEND} ${OUTPUT_BLEND}
|
||||
COMMAND ${BLENDER} -b -Y ${OUTPUT_BLEND}
|
||||
COMMAND ${BLENDER_LOCKED} -b -Y ${OUTPUT_BLEND}
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/add_missing_shape_keys.py --
|
||||
${OUTPUT_BLEND} ${OUTPUT_BLEND}
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
set(SLOT_LIST "bottom" "top" "feet" "hair")
|
||||
list(GET SLOT_LIST -1 LAST_SLOT)
|
||||
# male and female clothes pipeline
|
||||
foreach (SEX ${SEX_LIST})
|
||||
add_shape_key_propagation(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-${SEX}.blend
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${SEX}-shapes.blend
|
||||
)
|
||||
set(SLOT_INPUT "edited-normal-${SEX}-shapes.blend")
|
||||
foreach (SLOT ${SLOT_LIST})
|
||||
set(SLOT_OUTPUT "edited-normal-${SEX}-consolidated-${SLOT}.blend")
|
||||
if (SLOT STREQUAL LAST_SLOT)
|
||||
set(SLOT_OUTPUT "edited-normal-${SEX}-consolidated.blend")
|
||||
endif()
|
||||
add_clothes_pipeline("${CMAKE_CURRENT_BINARY_DIR}/${SLOT_INPUT}"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/clothes/clothes-${SEX}-${SLOT}_weighted.blend"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${SLOT_OUTPUT}"
|
||||
# Age/sex clothes, consolidation, LOD and export pipeline.
|
||||
set(ALL_CHARACTER_TARGETS)
|
||||
foreach(AGE ${CHARACTER_BUILD_AGES})
|
||||
set(AGE_TARGETS)
|
||||
foreach(SEX ${CHARACTER_SEXES})
|
||||
character_stem(${AGE} ${SEX} STEM)
|
||||
character_build_blend(${AGE} ${SEX} BASE_BUILD)
|
||||
set(SHAPES_BLEND
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${STEM}-shapes.blend")
|
||||
set(CONSOLIDATED
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${STEM}-consolidated.blend")
|
||||
set(LODS "${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${STEM}-lods.blend")
|
||||
set(OUTDIR "${CMAKE_BINARY_DIR}/characters/${STEM}")
|
||||
|
||||
add_shape_key_propagation(${BASE_BUILD} ${SHAPES_BLEND})
|
||||
|
||||
set(SLOT_INPUT "edited-normal-${STEM}-shapes.blend")
|
||||
foreach(SLOT ${SLOT_LIST})
|
||||
character_clothes_source(${AGE} ${SEX} ${SLOT} CLOTHES_SRC)
|
||||
character_clothes_name(${AGE} ${SEX} ${SLOT} CLOTHES_NAME)
|
||||
weight_clothes(${CLOTHES_SRC} ${BASE_BUILD})
|
||||
set(WEIGHTED
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/clothes/${CLOTHES_NAME}_weighted.blend")
|
||||
set(SLOT_OUTPUT
|
||||
"edited-normal-${STEM}-consolidated-${SLOT}.blend")
|
||||
if(SLOT STREQUAL LAST_SLOT)
|
||||
set(SLOT_OUTPUT "edited-normal-${STEM}-consolidated.blend")
|
||||
endif()
|
||||
add_clothes_pipeline("${CMAKE_CURRENT_BINARY_DIR}/${SLOT_INPUT}"
|
||||
"${WEIGHTED}"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${SLOT_OUTPUT}")
|
||||
set(SLOT_INPUT ${SLOT_OUTPUT})
|
||||
endforeach()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${LODS}
|
||||
COMMAND ${CMAKE_COMMAND} -E remove_directory ${OUTDIR}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${OUTDIR}
|
||||
COMMAND ${BLENDER_LOCKED} -b -Y ${CONSOLIDATED}
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_lods.py --
|
||||
${AGE} ${SEX} ${OUTDIR} ${LODS}
|
||||
COMMAND ${CMAKE_COMMAND} -D FILE=${LODS}
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
|
||||
DEPENDS ${CONSOLIDATED}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/generate_lods.py
|
||||
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
|
||||
COMMENT "Generating character LOD meshes for ${STEM}"
|
||||
)
|
||||
set(SLOT_INPUT ${SLOT_OUTPUT})
|
||||
|
||||
character_glb(${AGE} ${SEX} GLB)
|
||||
add_custom_command(
|
||||
OUTPUT ${GLB}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${OUTDIR}
|
||||
COMMAND ${BLENDER_LOCKED} -b -Y
|
||||
-P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models2.py --
|
||||
${CONSOLIDATED}
|
||||
${GLB}
|
||||
"AUTO"
|
||||
${STEM}
|
||||
tmp-edited-${STEM}.blend
|
||||
${LODS}
|
||||
COMMAND ${CMAKE_COMMAND} -D FILE=${GLB}
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
|
||||
COMMAND ${CMAKE_COMMAND} -D FILE=${OUTDIR}/normal-${STEM}.scene
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models2.py
|
||||
${CONSOLIDATED} ${LODS}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(character-${STEM}
|
||||
DEPENDS ${GLB} ${CONSOLIDATED} ${LODS})
|
||||
list(APPEND AGE_TARGETS character-${STEM})
|
||||
list(APPEND ALL_CHARACTER_TARGETS character-${STEM})
|
||||
endforeach()
|
||||
add_custom_target(characters-${AGE} DEPENDS ${AGE_TARGETS})
|
||||
endforeach()
|
||||
|
||||
# Character LOD generation (demo-taxi phase 6.C.3/6.C.4): from each
|
||||
# consolidated blend generate per-part "_lod1" atlas variants and
|
||||
# whole-character charlod impostors (LOD2 thin/fat, LOD3 <= 400 tris),
|
||||
# the shared 2048 atlas and the charlod_<sex>.json manifest. The
|
||||
# export_models2 commands above append the lods blend so the impostors
|
||||
# link the same skeleton as the body parts.
|
||||
foreach (SEX ${SEX_LIST})
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${SEX}-lods.blend
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||
${CMAKE_BINARY_DIR}/characters/${SEX}
|
||||
COMMAND ${BLENDER} -b -Y
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${SEX}-consolidated.blend
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_lods.py --
|
||||
${SEX}
|
||||
${CMAKE_BINARY_DIR}/characters/${SEX}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${SEX}-lods.blend
|
||||
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${SEX}-lods.blend
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
|
||||
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${SEX}-consolidated.blend
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/generate_lods.py
|
||||
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
|
||||
COMMENT "Generating character LOD meshes for ${SEX}"
|
||||
)
|
||||
endforeach()
|
||||
add_custom_target(characters-all DEPENDS ${ALL_CHARACTER_TARGETS})
|
||||
|
||||
add_custom_target(character-assets-check
|
||||
COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/validate_age_assets.py
|
||||
--characters-dir ${CMAKE_BINARY_DIR}/characters
|
||||
--ages "${CHARACTER_BUILD_AGES}"
|
||||
DEPENDS characters-all
|
||||
COMMENT "Validating generated character age assets")
|
||||
|
||||
add_custom_target(generate-age-seeds
|
||||
COMMAND ${BLENDER_LOCKED} -b -Y
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_age_blends.py --
|
||||
--all --write --only-unedited
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/generate_age_blends.py
|
||||
COMMENT "Generating unedited age seed blends (never touches hand-edited seeds)")
|
||||
|
||||
add_custom_target(check-age-seeds
|
||||
COMMAND ${BLENDER_LOCKED} -b -Y
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_age_blends.py --
|
||||
--all --check
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/generate_age_blends.py
|
||||
COMMENT "Checking age seed presence/hashes")
|
||||
add_custom_target(morph ALL DEPENDS MorphTargetsResearch ${CHARACTER_GLBS})
|
||||
add_custom_target(edited-blends ALL DEPENDS ${EDITED_BLEND_TARGETS})
|
||||
add_custom_target(import_vrm DEPENDS ${CHARACTER_GLBS})
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
"""Age-band profiles and naming helpers for the character pipeline.
|
||||
|
||||
This module is the single asset-side source of truth for the automatic
|
||||
adult -> age conversion. It is imported by the Blender scripts and by
|
||||
``generate_age_blends.py``; the C++ runtime mirrors the small amount of data
|
||||
it needs (names, age thresholds, fallback chains and body metrics).
|
||||
|
||||
Keep this file dependency-free (stdlib only) so it can be imported outside
|
||||
Blender for validation/tests.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
CANONICAL_AGES = ["baby", "child", "teenager", "adult", "elder"]
|
||||
|
||||
AGE_ALIASES = {
|
||||
"infant": "baby",
|
||||
"teen": "teenager",
|
||||
"adolescent": "teenager",
|
||||
"senior": "elder",
|
||||
"old": "elder",
|
||||
"old-age": "elder",
|
||||
}
|
||||
|
||||
# Canonical band boundaries used by the population reference rules.
|
||||
BABY_MAX_YEARS = 2
|
||||
CHILD_MAX_YEARS = 12
|
||||
TEEN_MAX_YEARS = 17
|
||||
ADULT_MAX_YEARS = 64
|
||||
|
||||
# Population rules' default ranges (mirrors TownPopulation::PopulationRules).
|
||||
DEFAULT_AGE_RULES = {
|
||||
"childMinYears": 1,
|
||||
"childMaxYears": 17,
|
||||
"adultMinYears": 18,
|
||||
"adultMaxYears": 64,
|
||||
"elderMinYears": 65,
|
||||
"elderMaxYears": 90,
|
||||
}
|
||||
|
||||
# Animation names every age skeleton must expose. The underlying clips may
|
||||
# differ per band (baby crawl etc.); the names are the animation-tree contract.
|
||||
REQUIRED_ANIMATIONS = [
|
||||
"default",
|
||||
"idle",
|
||||
"walking",
|
||||
"running",
|
||||
"sitting",
|
||||
"sitting-ground",
|
||||
"sitting-vehicle",
|
||||
"swimming",
|
||||
"treading_water",
|
||||
"swimming-fast",
|
||||
"jump",
|
||||
"left_turn",
|
||||
"right_turn",
|
||||
]
|
||||
|
||||
# Per-band conversion profile.
|
||||
#
|
||||
# Length/radial values are multipliers over the same-sex adult bone segments /
|
||||
# thickness. They are conversion targets, not medical proportions.
|
||||
# ``targetHeight`` is filled by sex below.
|
||||
PROFILES = {
|
||||
"baby": {
|
||||
"hipHeight": 0.28,
|
||||
"torso": 0.30,
|
||||
"head": 0.85,
|
||||
"shoulderWidth": 0.50,
|
||||
"arm": 0.30,
|
||||
"leg": 0.26,
|
||||
"foot": 0.55,
|
||||
"hipWidth": 0.55,
|
||||
"bodyRadial": 0.55,
|
||||
"limbRadial": 0.50,
|
||||
"headBulge": 1.08,
|
||||
"walkSpeedScale": 0.0, # home-only in v1
|
||||
},
|
||||
"child": {
|
||||
"hipHeight": 0.55,
|
||||
"torso": 0.62,
|
||||
"head": 0.92,
|
||||
"shoulderWidth": 0.70,
|
||||
"arm": 0.60,
|
||||
"leg": 0.55,
|
||||
"foot": 0.75,
|
||||
"hipWidth": 0.72,
|
||||
"bodyRadial": 0.78,
|
||||
"limbRadial": 0.72,
|
||||
"headBulge": 1.03,
|
||||
"walkSpeedScale": 0.75,
|
||||
},
|
||||
"teenager": {
|
||||
"hipHeight": 0.94,
|
||||
"torso": 0.92,
|
||||
"head": 0.99,
|
||||
"shoulderWidth": 0.92,
|
||||
"arm": 0.92,
|
||||
"leg": 0.94,
|
||||
"foot": 0.97,
|
||||
"hipWidth": 0.92,
|
||||
"bodyRadial": 0.94,
|
||||
"limbRadial": 0.93,
|
||||
"headBulge": 1.0,
|
||||
"walkSpeedScale": 0.95,
|
||||
},
|
||||
"adult": {
|
||||
"hipHeight": 1.0,
|
||||
"torso": 1.0,
|
||||
"head": 1.0,
|
||||
"shoulderWidth": 1.0,
|
||||
"arm": 1.0,
|
||||
"leg": 1.0,
|
||||
"foot": 1.0,
|
||||
"hipWidth": 1.0,
|
||||
"bodyRadial": 1.0,
|
||||
"limbRadial": 1.0,
|
||||
"headBulge": 1.0,
|
||||
"walkSpeedScale": 1.0,
|
||||
},
|
||||
# Elder v1 stays bind-identical to adult: aging is geometry/texture only.
|
||||
# The manual detail pass can scale the rig or posture later.
|
||||
"elder": {
|
||||
"hipHeight": 1.0,
|
||||
"torso": 1.0,
|
||||
"head": 1.0,
|
||||
"shoulderWidth": 1.0,
|
||||
"arm": 1.0,
|
||||
"leg": 1.0,
|
||||
"foot": 1.0,
|
||||
"hipWidth": 1.0,
|
||||
"bodyRadial": 1.0,
|
||||
"limbRadial": 1.0,
|
||||
"headBulge": 1.0,
|
||||
"walkSpeedScale": 1.0,
|
||||
},
|
||||
}
|
||||
|
||||
# Target mesh/skeleton heights in metres. Elder is adult height for the
|
||||
# bind-identical v1 seed; the detail pass can shorten it.
|
||||
TARGET_HEIGHT = {
|
||||
("baby", "male"): 0.72,
|
||||
("baby", "female"): 0.70,
|
||||
("child", "male"): 1.20,
|
||||
("child", "female"): 1.18,
|
||||
("teenager", "male"): 1.70,
|
||||
("teenager", "female"): 1.55,
|
||||
("adult", "male"): 1.85,
|
||||
("adult", "female"): 1.58,
|
||||
("elder", "male"): 1.85,
|
||||
("elder", "female"): 1.58,
|
||||
}
|
||||
|
||||
ADULT_HEIGHT = {"male": 1.85, "female": 1.58}
|
||||
|
||||
FALLBACK_CHAINS = {
|
||||
"baby": ["baby", "child", "adult"],
|
||||
"child": ["child", "baby", "teenager", "adult"],
|
||||
"teenager": ["teenager", "child", "adult"],
|
||||
"adult": ["adult", "teenager", "elder"],
|
||||
"elder": ["elder", "adult"],
|
||||
}
|
||||
|
||||
# Garment/body fit reporting thresholds.
|
||||
GARMENT_OFFSET = 0.003 # metres pushed out from the body surface
|
||||
QUALITY = {
|
||||
"seam_warn": 0.001,
|
||||
"seam_fail": 0.01,
|
||||
"penetration_warn": 0.01,
|
||||
"penetration_fail": 0.05,
|
||||
"height_tolerance": 0.05,
|
||||
"edge_residual_warn": 0.35, # |log(expected/actual)|-ish residual
|
||||
"edge_residual_fail": 0.70,
|
||||
}
|
||||
|
||||
|
||||
def normalize_age(age: str | None) -> str:
|
||||
if not age:
|
||||
return "adult"
|
||||
key = str(age).strip().lower()
|
||||
key = AGE_ALIASES.get(key, key)
|
||||
return key if key in CANONICAL_AGES else "adult"
|
||||
|
||||
|
||||
def age_band_for_years(years: int) -> str:
|
||||
if years <= BABY_MAX_YEARS:
|
||||
return "baby"
|
||||
if years <= CHILD_MAX_YEARS:
|
||||
return "child"
|
||||
if years <= TEEN_MAX_YEARS:
|
||||
return "teenager"
|
||||
if years <= ADULT_MAX_YEARS:
|
||||
return "adult"
|
||||
return "elder"
|
||||
|
||||
|
||||
def is_working_age(years: int) -> bool:
|
||||
return age_band_for_years(years) == "adult"
|
||||
|
||||
|
||||
def fallback_chain(age: str) -> list[str]:
|
||||
return list(FALLBACK_CHAINS[normalize_age(age)])
|
||||
|
||||
|
||||
def stem(age: str, sex: str) -> str:
|
||||
age = normalize_age(age)
|
||||
return sex if age == "adult" else f"{age}-{sex}"
|
||||
|
||||
|
||||
def armature_name(age: str, sex: str) -> str:
|
||||
return stem(age, sex)
|
||||
|
||||
|
||||
def mesh_prefix(age: str, sex: str) -> str:
|
||||
return stem(age, sex) + "_"
|
||||
|
||||
|
||||
def target_height(age: str, sex: str) -> float:
|
||||
age = normalize_age(age)
|
||||
return TARGET_HEIGHT[(age, sex)]
|
||||
|
||||
|
||||
def body_metrics(age: str, sex: str) -> dict:
|
||||
"""Capsule/head metrics scaled from the current adult defaults."""
|
||||
age = normalize_age(age)
|
||||
scale = target_height(age, sex) / ADULT_HEIGHT[sex]
|
||||
radius = 0.3 * scale
|
||||
height = 1.8 * scale
|
||||
return {
|
||||
"radius": radius,
|
||||
"height": height,
|
||||
"totalHeight": height + 2.0 * radius,
|
||||
"headRadius": 0.13 * scale,
|
||||
"headOffsetY": 0.0,
|
||||
"walkSpeedScale": PROFILES[age]["walkSpeedScale"],
|
||||
}
|
||||
|
||||
|
||||
def profile(age: str) -> dict:
|
||||
return dict(PROFILES[normalize_age(age)])
|
||||
|
||||
|
||||
def profile_hash() -> str:
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
payload = {
|
||||
"ages": CANONICAL_AGES,
|
||||
"profiles": PROFILES,
|
||||
"heights": {f"{a}|{s}": h for (a, s), h in TARGET_HEIGHT.items()},
|
||||
"animations": REQUIRED_ANIMATIONS,
|
||||
"quality": QUALITY,
|
||||
}
|
||||
return hashlib.sha256(
|
||||
json.dumps(payload, sort_keys=True).encode("utf-8")
|
||||
).hexdigest()[:16]
|
||||
@@ -0,0 +1,9 @@
|
||||
"""Age-seed conversion helpers (Blender 3.6)."""
|
||||
|
||||
__all__ = [
|
||||
"profiles",
|
||||
"rig",
|
||||
"deform",
|
||||
"report",
|
||||
"anim",
|
||||
]
|
||||
@@ -0,0 +1,140 @@
|
||||
"""Animation copy/retargeting for age seeds.
|
||||
|
||||
The adult Mixamo clips animate rotations in bone-local space and locations for
|
||||
the hips/root. Because the age rig keeps every bone's rest orientation, the
|
||||
rotation curves transfer 1:1; only location curves need the bone's rest-offset
|
||||
ratio. This module is authoring-time only and must not run during the normal
|
||||
build (that would scale the location tracks twice).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
import bpy
|
||||
|
||||
from . import profiles
|
||||
from .rig import bone_length_ratios
|
||||
|
||||
_LOC_RE = re.compile(r'pose\.bones\["([^"]+)"\]\.(location|delta_location)')
|
||||
|
||||
|
||||
def _nla_track_names(arm):
|
||||
if not arm.animation_data:
|
||||
return set()
|
||||
return {t.name for t in arm.animation_data.nla_tracks}
|
||||
|
||||
|
||||
def _ensure_track(arm, action):
|
||||
ad = arm.animation_data
|
||||
if ad is None:
|
||||
arm.animation_data_create()
|
||||
ad = arm.animation_data
|
||||
existing = {t.name for t in ad.nla_tracks}
|
||||
if action.name in existing:
|
||||
return
|
||||
track = ad.nla_tracks.new()
|
||||
track.name = action.name
|
||||
# Match copy_animations.py: strips start at frame 1 and remain active;
|
||||
# export_models2.py bakes each strip explicitly.
|
||||
track.strips.new(action.name, 1, action)
|
||||
|
||||
|
||||
def retarget_actions(arm, ratios: dict, hip_ratio: float,
|
||||
actions) -> dict:
|
||||
"""Scale location F-curves in *actions* and return a small summary."""
|
||||
scaled_curves = 0
|
||||
for action in actions:
|
||||
for fc in action.fcurves:
|
||||
m = _LOC_RE.match(fc.data_path)
|
||||
if m:
|
||||
bone = m.group(1)
|
||||
ratio = ratios.get(bone, 1.0)
|
||||
for kp in fc.keyframe_points:
|
||||
kp.co.y *= ratio
|
||||
kp.handle_left.y *= ratio
|
||||
kp.handle_right.y *= ratio
|
||||
scaled_curves += 1
|
||||
elif fc.data_path in ("location", "delta_location"):
|
||||
for kp in fc.keyframe_points:
|
||||
kp.co.y *= hip_ratio
|
||||
kp.handle_left.y *= hip_ratio
|
||||
kp.handle_right.y *= hip_ratio
|
||||
scaled_curves += 1
|
||||
return {"scaledLocationCurves": scaled_curves}
|
||||
|
||||
|
||||
def _ensure_alias(missing: str, present: set) -> str | None:
|
||||
candidates = {
|
||||
"swimming-fast": ["swimming", "walking", "idle"],
|
||||
"sitting-ground": ["sitting", "default"],
|
||||
"sitting-vehicle": ["sitting", "default"],
|
||||
"sitting": ["default", "idle"],
|
||||
"treading_water": ["swimming", "idle"],
|
||||
"default": ["idle", "walking", "T-Pose"],
|
||||
}.get(missing, [])
|
||||
for name in candidates:
|
||||
if name in present:
|
||||
return name
|
||||
return None
|
||||
|
||||
|
||||
def append_and_retarget(arm, adult_arm, source_blend: str, age: str,
|
||||
sex: str, hips_bone="mixamorig:Hips"):
|
||||
"""Append the sex's Mixamo actions, retarget them and build NLA tracks."""
|
||||
age = profiles.normalize_age(age)
|
||||
stem = profiles.stem(age, sex)
|
||||
|
||||
existing_names = {a.name for a in bpy.data.actions}
|
||||
with bpy.data.libraries.load(source_blend, link=False) as (src, dst):
|
||||
wanted = [n for n in src.actions if n not in existing_names]
|
||||
dst.actions = wanted
|
||||
appended = [a for a in dst.actions if a is not None]
|
||||
|
||||
# Fill required-but-missing action names with the closest available clip.
|
||||
# These are explicit placeholders for the manual animation pass; keeping
|
||||
# the names present prevents silent T-poses / missing-state errors.
|
||||
aliased = {}
|
||||
for name in profiles.REQUIRED_ANIMATIONS:
|
||||
if bpy.data.actions.get(name) is not None:
|
||||
continue
|
||||
src_name = _ensure_alias(name, {a.name for a in bpy.data.actions})
|
||||
if not src_name:
|
||||
continue
|
||||
src = bpy.data.actions[src_name]
|
||||
new = src.copy()
|
||||
new.name = name
|
||||
new.use_fake_user = True
|
||||
aliased[name] = src_name
|
||||
|
||||
# Ensure every action (including custom baby clips and aliases) has an
|
||||
# NLA track.
|
||||
for action in bpy.data.actions:
|
||||
if action.name.startswith("bake_"):
|
||||
continue
|
||||
if action.name in ("T-Pose",):
|
||||
continue
|
||||
_ensure_track(arm, action)
|
||||
|
||||
ratios = bone_length_ratios(adult_arm, arm)
|
||||
hip_ratio = ratios.get(hips_bone, 1.0)
|
||||
|
||||
# Retarget ALL actions, including clips already present in the source
|
||||
# base blend (sitting-ground/sitting-vehicle/etc. are authored there).
|
||||
summary = retarget_actions(arm, ratios, hip_ratio,
|
||||
list(bpy.data.actions))
|
||||
summary["appended"] = [a.name for a in appended]
|
||||
summary["aliased"] = aliased
|
||||
|
||||
# Ensure the default action is active (export_models2 expects it).
|
||||
default = bpy.data.actions.get("default")
|
||||
if default is not None:
|
||||
if arm.animation_data is None:
|
||||
arm.animation_data_create()
|
||||
arm.animation_data.action = default
|
||||
|
||||
present = {a.name for a in bpy.data.actions}
|
||||
missing = [n for n in profiles.REQUIRED_ANIMATIONS if n not in present]
|
||||
summary["missing"] = missing
|
||||
summary["stem"] = stem
|
||||
return summary
|
||||
@@ -0,0 +1,240 @@
|
||||
"""Weighted rest-pose deformation for body parts, clothes and shape keys."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
import bpy
|
||||
from mathutils import Matrix, Vector
|
||||
|
||||
from . import profiles
|
||||
|
||||
|
||||
def _nearest_bone_distance(point: Vector, bone) -> float:
|
||||
a = bone.head_local
|
||||
b = bone.tail_local
|
||||
ab = b - a
|
||||
denom = ab.length_squared
|
||||
if denom <= 1e-12:
|
||||
return (point - a).length
|
||||
t = max(0.0, min(1.0, (point - a).dot(ab) / denom))
|
||||
return (point - (a + ab * t)).length
|
||||
|
||||
|
||||
def _nearest_bone_name(point: Vector, adult_arm) -> str:
|
||||
best = None
|
||||
best_d = 1e30
|
||||
for b in adult_arm.data.bones:
|
||||
if not b.use_deform:
|
||||
continue
|
||||
d = _nearest_bone_distance(point, b)
|
||||
if d < best_d:
|
||||
best_d = d
|
||||
best = b.name
|
||||
return best or "Root"
|
||||
|
||||
|
||||
def _group_name_map(obj) -> dict:
|
||||
return {i: g.name for i, g in enumerate(obj.vertex_groups)}
|
||||
|
||||
|
||||
def _vertex_matrix(v, group_names, dmatrices, adult_arm, obj_to_arm):
|
||||
"""Return (matrix, influence_count) for one mesh vertex."""
|
||||
acc = 0.0
|
||||
m = Matrix(((0.0, 0.0, 0.0, 0.0),
|
||||
(0.0, 0.0, 0.0, 0.0),
|
||||
(0.0, 0.0, 0.0, 0.0),
|
||||
(0.0, 0.0, 0.0, 0.0)))
|
||||
influences = 0
|
||||
for g in v.groups:
|
||||
w = float(g.weight)
|
||||
if w <= 0.0:
|
||||
continue
|
||||
name = group_names.get(g.group)
|
||||
dm = dmatrices.get(name)
|
||||
if dm is None:
|
||||
continue
|
||||
acc += w
|
||||
m = m + dm * w
|
||||
influences += 1
|
||||
if acc > 1e-9:
|
||||
return m * (1.0 / acc), influences
|
||||
# No usable weight: bind to the nearest deform bone.
|
||||
p_arm = obj_to_arm @ v.co
|
||||
name = _nearest_bone_name(p_arm, adult_arm)
|
||||
dm = dmatrices.get(name)
|
||||
return (dm.copy() if dm else Matrix.Identity(4)), 0
|
||||
|
||||
|
||||
def deform_mesh_object(obj, adult_arm, age_arm, dmatrices,
|
||||
profile: dict, retarget_parent=True):
|
||||
"""Deform one mesh and its shape keys in place.
|
||||
|
||||
Returns a metrics dict. ``retarget_parent`` re-parents the mesh to the
|
||||
age armature and points the armature modifier at it (used for the base
|
||||
body seed). Garment seeds are saved without a body armature link, so
|
||||
they pass ``False``.
|
||||
"""
|
||||
if obj.type != 'MESH':
|
||||
return None
|
||||
mesh = obj.data
|
||||
group_names = _group_name_map(obj)
|
||||
|
||||
obj_to_arm = adult_arm.matrix_world.inverted() @ obj.matrix_world
|
||||
arm_to_obj = obj.matrix_world.inverted() @ age_arm.matrix_world
|
||||
|
||||
before = [v.co.copy() for v in mesh.vertices]
|
||||
matrices = []
|
||||
unweighted = 0
|
||||
max_inf = 0
|
||||
for v in mesh.vertices:
|
||||
m, inf = _vertex_matrix(v, group_names, dmatrices, adult_arm,
|
||||
obj_to_arm)
|
||||
matrices.append(m)
|
||||
if inf == 0:
|
||||
unweighted += 1
|
||||
max_inf = max(max_inf, inf)
|
||||
|
||||
# Basis vertices.
|
||||
for i, v in enumerate(mesh.vertices):
|
||||
p = obj_to_arm @ v.co
|
||||
v.co = arm_to_obj @ (matrices[i] @ p)
|
||||
|
||||
# Non-Basis shape keys: absolute positions, same weights.
|
||||
key_count = 0
|
||||
if mesh.shape_keys:
|
||||
for kb in mesh.shape_keys.key_blocks:
|
||||
if kb.name == 'Basis':
|
||||
continue
|
||||
key_count += 1
|
||||
for i, kp in enumerate(kb.data):
|
||||
p = obj_to_arm @ kp.co
|
||||
kp.co = arm_to_obj @ (matrices[i] @ p)
|
||||
|
||||
mesh.update()
|
||||
try:
|
||||
mesh.calc_normals()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if retarget_parent:
|
||||
obj.parent = age_arm
|
||||
mod = next((m for m in obj.modifiers
|
||||
if m.type == 'ARMATURE'), None)
|
||||
if mod is None:
|
||||
mod = obj.modifiers.new(name="Armature", type='ARMATURE')
|
||||
mod.object = age_arm
|
||||
obj.matrix_parent_inverse = age_arm.matrix_world.inverted()
|
||||
|
||||
# Metrics.
|
||||
edge_ratios = []
|
||||
for e in mesh.edges:
|
||||
a, b = e.vertices
|
||||
lb = (before[a] - before[b]).length
|
||||
la = (mesh.vertices[a].co - mesh.vertices[b].co).length
|
||||
if lb > 1e-8:
|
||||
edge_ratios.append(la / lb)
|
||||
mesh.calc_loop_triangles()
|
||||
degenerate = 0
|
||||
for tri in mesh.loop_triangles:
|
||||
a, b, c = tri.vertices
|
||||
area = ((mesh.vertices[b].co - mesh.vertices[a].co).cross(
|
||||
mesh.vertices[c].co - mesh.vertices[a].co)).length * 0.5
|
||||
if area < 1e-10:
|
||||
degenerate += 1
|
||||
|
||||
return {
|
||||
"vertices": len(mesh.vertices),
|
||||
"polygons": len(mesh.polygons),
|
||||
"shapeKeys": key_count,
|
||||
"unweighted": unweighted,
|
||||
"maxInfluences": max_inf,
|
||||
"edgeRatioMin": min(edge_ratios) if edge_ratios else 1.0,
|
||||
"edgeRatioMax": max(edge_ratios) if edge_ratios else 1.0,
|
||||
"degenerateTris": degenerate,
|
||||
"before": before,
|
||||
}
|
||||
|
||||
|
||||
def deform_shape_keys_only(obj, adult_arm, age_arm, dmatrices):
|
||||
"""Transform shape keys without touching the basis (utility)."""
|
||||
return None
|
||||
|
||||
|
||||
def snap_seams(objects_with_metrics, max_report=10):
|
||||
"""Snap coincident boundary vertices across parts to their average.
|
||||
|
||||
``objects_with_metrics`` is a list of ``(obj, metrics)`` where metrics has
|
||||
the original vertex positions. Returns a report dict.
|
||||
"""
|
||||
buckets = {}
|
||||
for obj, metrics in objects_with_metrics:
|
||||
before = metrics.get("before")
|
||||
if not before:
|
||||
continue
|
||||
for i, p in enumerate(before):
|
||||
key = (round(p.x, 5), round(p.y, 5), round(p.z, 5))
|
||||
buckets.setdefault(key, []).append((obj, i, p))
|
||||
max_gap = 0.0
|
||||
snapped = 0
|
||||
examples = []
|
||||
for key, items in buckets.items():
|
||||
if len(items) < 2:
|
||||
continue
|
||||
positions = [obj.matrix_world @ obj.data.vertices[i].co
|
||||
for obj, i, _ in items]
|
||||
avg = Vector((0, 0, 0))
|
||||
for p in positions:
|
||||
avg += p
|
||||
avg /= len(positions)
|
||||
gap = max((p - avg).length for p in positions)
|
||||
if gap < 1e-6:
|
||||
continue
|
||||
max_gap = max(max_gap, gap)
|
||||
for obj, i, _ in items:
|
||||
delta = avg - (obj.matrix_world @ obj.data.vertices[i].co)
|
||||
if delta.length <= 1e-9:
|
||||
continue
|
||||
v = obj.data.vertices[i]
|
||||
v.co += obj.matrix_world.inverted().to_3x3() @ delta
|
||||
if obj.data.shape_keys:
|
||||
for kb in obj.data.shape_keys.key_blocks:
|
||||
if kb.name == 'Basis':
|
||||
continue
|
||||
kb.data[i].co += (obj.matrix_world.inverted().to_3x3()
|
||||
@ delta)
|
||||
snapped += 1
|
||||
if len(examples) < max_report:
|
||||
examples.append({
|
||||
"position": [round(v, 4) for v in key],
|
||||
"gap": round(gap, 6),
|
||||
"objects": sorted({obj.name for obj, _, _ in items}),
|
||||
})
|
||||
return {
|
||||
"coincidentGroups": sum(1 for v in buckets.values() if len(v) > 1),
|
||||
"snappedVertices": snapped,
|
||||
"maxGap": max_gap,
|
||||
"examples": examples,
|
||||
}
|
||||
|
||||
|
||||
def transform_hair_uniform(obj, adult_pivot: Vector, age_pivot: Vector,
|
||||
scale: float):
|
||||
"""Uniformly scale a hair mesh and move it from adult to age scalp."""
|
||||
obj_to_arm = obj.matrix_world
|
||||
inv = obj.matrix_world.inverted()
|
||||
for v in obj.data.vertices:
|
||||
p = obj_to_arm @ v.co
|
||||
v.co = inv @ (age_pivot + (p - adult_pivot) * scale)
|
||||
if obj.data.shape_keys:
|
||||
for kb in obj.data.shape_keys.key_blocks:
|
||||
if kb.name == 'Basis':
|
||||
continue
|
||||
for kp in kb.data:
|
||||
p = obj_to_arm @ kp.co
|
||||
kp.co = inv @ (age_pivot + (p - adult_pivot) * scale)
|
||||
obj.data.update()
|
||||
try:
|
||||
obj.data.calc_normals()
|
||||
except Exception:
|
||||
pass
|
||||
@@ -0,0 +1,25 @@
|
||||
"""Re-export the asset-side age profile data for the converter package."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
_CHARACTERS_DIR = os.path.dirname(_THIS_DIR)
|
||||
if _CHARACTERS_DIR not in sys.path:
|
||||
sys.path.insert(0, _CHARACTERS_DIR)
|
||||
|
||||
import age_profiles as ap # noqa: E402,F401
|
||||
|
||||
CANONICAL_AGES = ap.CANONICAL_AGES
|
||||
PROFILES = ap.PROFILES
|
||||
REQUIRED_ANIMATIONS = ap.REQUIRED_ANIMATIONS
|
||||
normalize_age = ap.normalize_age
|
||||
age_band_for_years = ap.age_band_for_years
|
||||
fallback_chain = ap.fallback_chain
|
||||
stem = ap.stem
|
||||
armature_name = ap.armature_name
|
||||
mesh_prefix = ap.mesh_prefix
|
||||
target_height = ap.target_height
|
||||
body_metrics = ap.body_metrics
|
||||
profile = ap.profile
|
||||
profile_hash = ap.profile_hash
|
||||
@@ -0,0 +1,134 @@
|
||||
"""Conversion quality reporting for generated age seeds."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
from . import profiles
|
||||
|
||||
|
||||
class ConversionReport:
|
||||
def __init__(self, age: str, sex: str):
|
||||
self.age = profiles.normalize_age(age)
|
||||
self.sex = sex
|
||||
self.stem = profiles.stem(self.age, sex)
|
||||
self.started = time.time()
|
||||
self.objects = {}
|
||||
self.warnings = []
|
||||
self.fails = []
|
||||
self.info = {}
|
||||
self.seams = {}
|
||||
self.rig = {}
|
||||
|
||||
def add_object(self, name: str, metrics: dict, quality: str = "ok",
|
||||
note: str = ""):
|
||||
entry = dict(metrics or {})
|
||||
entry.pop("before", None) # large, not serializable/useful
|
||||
entry["quality"] = quality
|
||||
entry["note"] = note
|
||||
self.objects[name] = entry
|
||||
if quality == "fail":
|
||||
self.fails.append(name)
|
||||
elif quality in ("warn", "placeholder"):
|
||||
self.warnings.append(name)
|
||||
|
||||
def status(self):
|
||||
if self.fails:
|
||||
return "fail"
|
||||
if self.warnings:
|
||||
return "warn"
|
||||
return "ok"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"age": self.age,
|
||||
"sex": self.sex,
|
||||
"stem": self.stem,
|
||||
"profileHash": profiles.profile_hash(),
|
||||
"timestamp": int(self.started),
|
||||
"status": self.status(),
|
||||
"info": self.info,
|
||||
"rig": self.rig,
|
||||
"seams": self.seams,
|
||||
"objects": self.objects,
|
||||
"warnings": self.warnings,
|
||||
"fails": self.fails,
|
||||
}
|
||||
|
||||
def write(self, out_dir: str):
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
base = os.path.join(out_dir,
|
||||
f"conversion-report-{self.age}-{self.sex}")
|
||||
data = self.to_dict()
|
||||
with open(base + ".json", "w") as f:
|
||||
json.dump(data, f, indent=2, sort_keys=True)
|
||||
|
||||
lines = [
|
||||
f"# Conversion Report — {self.age} / {self.sex}",
|
||||
"",
|
||||
f"- Stem: `{self.stem}`",
|
||||
f"- Status: **{data['status']}**",
|
||||
f"- Profile hash: `{data['profileHash']}`",
|
||||
f"- Objects: {len(self.objects)}",
|
||||
f"- Warnings: {len(self.warnings)}",
|
||||
f"- Fails: {len(self.fails)}",
|
||||
"",
|
||||
]
|
||||
if self.info:
|
||||
lines.append("## Info")
|
||||
for k in sorted(self.info):
|
||||
lines.append(f"- {k}: `{self.info[k]}`")
|
||||
lines.append("")
|
||||
if self.seams:
|
||||
lines.append("## Seams")
|
||||
for k, v in self.seams.items():
|
||||
lines.append(f"- {k}: `{v}`")
|
||||
lines.append("")
|
||||
if self.fails:
|
||||
lines += ["## Failures", ""]
|
||||
lines += [f"- `{name}`" for name in self.fails]
|
||||
lines.append("")
|
||||
if self.warnings:
|
||||
lines += ["## Warnings (first 20)", ""]
|
||||
lines += [f"- `{name}`" for name in self.warnings[:20]]
|
||||
lines.append("")
|
||||
|
||||
lines += ["## Objects", ""]
|
||||
for name in sorted(self.objects):
|
||||
e = self.objects[name]
|
||||
lines.append(
|
||||
f"- `{name}` — {e.get('quality', 'ok')}; "
|
||||
f"verts={e.get('vertices', '?')}, "
|
||||
f"tris={e.get('degenerateTris', '?')} degenerate, "
|
||||
f"influences={e.get('maxInfluences', '?')}, "
|
||||
f"unweighted={e.get('unweighted', '?')}, "
|
||||
f"edge={e.get('edgeRatioMin', 1):.2f}.."
|
||||
f"{e.get('edgeRatioMax', 1):.2f}"
|
||||
+ (f"; {e.get('note')}" if e.get("note") else "")
|
||||
)
|
||||
path = base + ".md"
|
||||
with open(path, "w") as f:
|
||||
f.write("\n".join(lines) + "\n")
|
||||
return path, base + ".json"
|
||||
|
||||
|
||||
def evaluate_object_quality(metrics: dict, is_catalog: bool) -> str:
|
||||
"""Cheap per-object quality classification for the seed report."""
|
||||
if not metrics:
|
||||
return "warn"
|
||||
if metrics.get("unweighted", 0) > 0 and is_catalog:
|
||||
return "warn"
|
||||
if metrics.get("maxInfluences", 0) > 4:
|
||||
return "fail"
|
||||
if metrics.get("degenerateTris", 0) > 0:
|
||||
return "warn"
|
||||
emin = metrics.get("edgeRatioMin", 1.0)
|
||||
emax = metrics.get("edgeRatioMax", 1.0)
|
||||
# Raw ratios are expected to compress for baby/child; flag only extreme
|
||||
# local distortion. The per-bone expected ratio is evaluated in the
|
||||
# report writer's caller when available.
|
||||
if emax > 2.5 or (emin > 0.0 and emin < 0.35):
|
||||
return "warn"
|
||||
return "ok"
|
||||
@@ -0,0 +1,257 @@
|
||||
"""Age-rig construction and rest-pose transform data.
|
||||
|
||||
All matrices are in armature space. The age rig keeps the adult bone names,
|
||||
hierarchy, roll and orientation; only joint positions and segment lengths
|
||||
change. This is what makes rotation-only animation retargeting valid.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
import bpy
|
||||
from mathutils import Matrix, Vector
|
||||
|
||||
from . import profiles
|
||||
|
||||
|
||||
def bone_region(name: str) -> dict:
|
||||
"""Return length/radial/offset profile keys for a Mixamo bone name."""
|
||||
n = name
|
||||
low = n.lower()
|
||||
|
||||
if n in ("Root", "mixamorig:Hips"):
|
||||
return {"length": "hipHeight", "radial": "bodyRadial",
|
||||
"offset": "hipHeight"}
|
||||
if "spine" in low:
|
||||
return {"length": "torso", "radial": "bodyRadial",
|
||||
"offset": "torso"}
|
||||
if "neck" in low:
|
||||
return {"length": "torso", "radial": "bodyRadial",
|
||||
"offset": "torso"}
|
||||
if ("head" in low or "jaw" in low or "eye" in low or
|
||||
"teeth" in low or "tongue" in low):
|
||||
return {"length": "head", "radial": "head", "offset": "head"}
|
||||
if "bust" in low:
|
||||
return {"length": "torso", "radial": "bodyRadial",
|
||||
"offset": "torso"}
|
||||
if "hair" in low or "j_sec_hair" in low:
|
||||
return {"length": "head", "radial": "head", "offset": "head"}
|
||||
if "sleeve" in low:
|
||||
return {"length": "arm", "radial": "limbRadial",
|
||||
"offset": "arm"}
|
||||
if "skirt" in low:
|
||||
return {"length": "leg", "radial": "bodyRadial",
|
||||
"offset": "hipWidth"}
|
||||
if "shoulder" in low:
|
||||
return {"length": "shoulderWidth", "radial": "limbRadial",
|
||||
"offset": "shoulderWidth"}
|
||||
if "upleg" in low or "upperleg" in low:
|
||||
return {"length": "leg", "radial": "limbRadial",
|
||||
"offset": "hipWidth"}
|
||||
if "leg" in low:
|
||||
return {"length": "leg", "radial": "limbRadial",
|
||||
"offset": "hipWidth"}
|
||||
if "foot" in low:
|
||||
return {"length": "foot", "radial": "limbRadial",
|
||||
"offset": "leg"}
|
||||
if "toe" in low:
|
||||
return {"length": "foot", "radial": "limbRadial",
|
||||
"offset": "foot"}
|
||||
if "arm" in low or "hand" in low or "thumb" in low or \
|
||||
"index" in low or "middle" in low or "ring" in low or \
|
||||
"pinky" in low:
|
||||
return {"length": "arm", "radial": "limbRadial",
|
||||
"offset": "arm"}
|
||||
return {"length": "torso", "radial": "bodyRadial", "offset": "torso"}
|
||||
|
||||
|
||||
def _depth(bone) -> int:
|
||||
d = 0
|
||||
p = bone.parent
|
||||
while p is not None:
|
||||
d += 1
|
||||
p = p.parent
|
||||
return d
|
||||
|
||||
|
||||
def _unique_name(existing, base):
|
||||
if base not in existing:
|
||||
return base
|
||||
i = 1
|
||||
while f"{base}.{i:03d}" in existing:
|
||||
i += 1
|
||||
return f"{base}.{i:03d}"
|
||||
|
||||
|
||||
def build_age_armature(adult_arm, age: str, sex: str,
|
||||
profile: dict | None = None,
|
||||
collection=None):
|
||||
"""Duplicate *adult_arm* and scale its rest pose into the age rig.
|
||||
|
||||
Returns the new armature object. The new object/data are named
|
||||
``<age>-<sex>`` (adult would keep the legacy sex name; this function is
|
||||
only used for non-adult seeds).
|
||||
"""
|
||||
age = profiles.normalize_age(age)
|
||||
profile = profile or profiles.profile(age)
|
||||
stem = profiles.stem(age, sex)
|
||||
|
||||
age_arm = adult_arm.copy()
|
||||
age_arm.data = adult_arm.data.copy()
|
||||
age_arm.name = stem
|
||||
age_arm.data.name = stem
|
||||
(collection or bpy.context.collection).objects.link(age_arm)
|
||||
|
||||
# Snapshot adult rest positions before entering edit mode.
|
||||
adult_head = {b.name: b.head_local.copy() for b in adult_arm.data.bones}
|
||||
adult_tail = {b.name: b.tail_local.copy() for b in adult_arm.data.bones}
|
||||
|
||||
bpy.context.view_layer.objects.active = age_arm
|
||||
age_arm.select_set(True)
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
ebones = age_arm.data.edit_bones
|
||||
|
||||
new_head = {}
|
||||
new_tail = {}
|
||||
order = sorted(ebones, key=_depth)
|
||||
for eb in order:
|
||||
old_h = adult_head[eb.name]
|
||||
old_t = adult_tail[eb.name]
|
||||
direction = old_t - old_h
|
||||
old_len = direction.length
|
||||
if old_len <= 1e-6:
|
||||
new_h = old_h.copy()
|
||||
new_t = old_t.copy()
|
||||
new_head[eb.name] = new_h
|
||||
new_tail[eb.name] = new_t
|
||||
continue
|
||||
direction.normalize()
|
||||
|
||||
region = bone_region(eb.name)
|
||||
lscale = float(profile[region["length"]])
|
||||
rscale = float(profile[region["radial"]])
|
||||
oscale = float(profile[region["offset"]])
|
||||
|
||||
if eb.parent is not None and eb.parent.name in new_head:
|
||||
pname = eb.parent.name
|
||||
if eb.use_connect:
|
||||
new_h = new_tail[pname].copy()
|
||||
else:
|
||||
parent_old_h = adult_head[pname]
|
||||
offset = old_h - parent_old_h
|
||||
new_h = new_head[pname] + offset * oscale
|
||||
else:
|
||||
# Root or a parent-less helper: keep the origin, scale nothing
|
||||
# laterally here (the profile handles the hips length).
|
||||
new_h = old_h.copy()
|
||||
|
||||
new_len = old_len * lscale
|
||||
new_t = new_h + direction * new_len
|
||||
|
||||
eb.head = new_h
|
||||
eb.tail = new_t
|
||||
# roll is intentionally untouched
|
||||
new_head[eb.name] = new_h.copy()
|
||||
new_tail[eb.name] = new_t.copy()
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
age_arm.select_set(False)
|
||||
return age_arm
|
||||
|
||||
|
||||
def _bone_scale_matrix(lscale: float, rscale: float) -> Matrix:
|
||||
return Matrix.Diagonal((rscale, lscale, rscale, 1.0))
|
||||
|
||||
|
||||
def rest_transform_data(adult_arm, age_arm, age: str,
|
||||
profile: dict | None = None) -> dict:
|
||||
"""Build serializable rest-pose deformation data for every bone."""
|
||||
age = profiles.normalize_age(age)
|
||||
profile = profile or profiles.profile(age)
|
||||
out = {}
|
||||
for ab in adult_arm.data.bones:
|
||||
bb = age_arm.data.bones.get(ab.name)
|
||||
if bb is None:
|
||||
continue
|
||||
region = bone_region(ab.name)
|
||||
lscale = float(profile[region["length"]])
|
||||
rscale = float(profile[region["radial"]])
|
||||
f_adult = ab.matrix_local.copy()
|
||||
f_age = bb.matrix_local.copy()
|
||||
s = _bone_scale_matrix(lscale, rscale)
|
||||
d = f_age @ s @ f_adult.inverted()
|
||||
out[ab.name] = {
|
||||
"lengthScale": lscale,
|
||||
"radialScale": rscale,
|
||||
"fAdult": [list(row) for row in f_adult],
|
||||
"fAge": [list(row) for row in f_age],
|
||||
"dMatrix": [list(row) for row in d],
|
||||
}
|
||||
return out
|
||||
|
||||
|
||||
def dmatrices_from_data(data: dict) -> dict:
|
||||
return {name: Matrix(entry["dMatrix"]) for name, entry in data.items()}
|
||||
|
||||
|
||||
def bone_length_ratios(adult_arm, age_arm) -> dict:
|
||||
out = {}
|
||||
for ab in adult_arm.data.bones:
|
||||
bb = age_arm.data.bones.get(ab.name)
|
||||
if bb is None:
|
||||
continue
|
||||
al = (ab.tail_local - ab.head_local).length
|
||||
bl = (bb.tail_local - bb.head_local).length
|
||||
out[ab.name] = (bl / al) if al > 1e-9 else 1.0
|
||||
return out
|
||||
|
||||
|
||||
def validate_rest_orientation(adult_arm, age_arm, tol=1e-4) -> list:
|
||||
"""Return a list of bones whose normalized rest direction changed."""
|
||||
bad = []
|
||||
for ab in adult_arm.data.bones:
|
||||
bb = age_arm.data.bones.get(ab.name)
|
||||
if bb is None:
|
||||
continue
|
||||
ad = (ab.tail_local - ab.head_local)
|
||||
bd = (bb.tail_local - bb.head_local)
|
||||
if ad.length < 1e-3 or bd.length < 1e-3:
|
||||
continue
|
||||
ad.normalize()
|
||||
bd.normalize()
|
||||
if (ad - bd).length > tol:
|
||||
bad.append(ab.name)
|
||||
return bad
|
||||
|
||||
|
||||
def head_pivots(adult_arm, age_arm, head_bone="mixamorig:Head"):
|
||||
ab = adult_arm.data.bones.get(head_bone)
|
||||
bb = age_arm.data.bones.get(head_bone)
|
||||
if not ab or not bb:
|
||||
return Vector((0, 0, 0)), Vector((0, 0, 0))
|
||||
return ab.head_local.copy(), bb.head_local.copy()
|
||||
|
||||
|
||||
def scale_hair_armature(hair_arm, adult_head_pivot: Vector,
|
||||
age_head_pivot: Vector, scale: float):
|
||||
"""Scale a hair armature uniformly from the adult head to the age head.
|
||||
|
||||
Hair armatures are rigidly attached to the body head at runtime; a uniform
|
||||
scale around the scalp preserves their own bone chain and animations.
|
||||
"""
|
||||
bpy.context.view_layer.objects.active = hair_arm
|
||||
hair_arm.select_set(True)
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
for eb in hair_arm.data.edit_bones:
|
||||
old_h = eb.head.copy()
|
||||
old_t = eb.tail.copy()
|
||||
eb.head = age_head_pivot + (old_h - adult_head_pivot) * scale
|
||||
eb.tail = age_head_pivot + (old_t - adult_head_pivot) * scale
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
hair_arm.select_set(False)
|
||||
|
||||
|
||||
def transform_point_uniform(p: Vector, adult_pivot: Vector,
|
||||
age_pivot: Vector, scale: float) -> Vector:
|
||||
return age_pivot + (p - adult_pivot) * scale
|
||||
@@ -0,0 +1,40 @@
|
||||
"""Vertex-weight utilities for the age converter."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def limit_vertex_influences(obj, max_influences=4):
|
||||
"""Keep the strongest N weights per vertex and normalize them to 1."""
|
||||
if obj.type != 'MESH' or not obj.vertex_groups:
|
||||
return 0
|
||||
groups = {g.index: g for g in obj.vertex_groups}
|
||||
mesh = obj.data
|
||||
changed = 0
|
||||
for v in mesh.vertices:
|
||||
ws = [(g.group, float(g.weight)) for g in v.groups
|
||||
if g.weight > 0.0 and g.group in groups]
|
||||
if not ws:
|
||||
continue
|
||||
ws.sort(key=lambda p: p[1], reverse=True)
|
||||
keep = ws[:max_influences]
|
||||
drop = ws[max_influences:]
|
||||
total = sum(w for _, w in keep)
|
||||
if total <= 1e-9:
|
||||
continue
|
||||
for gi, _ in drop:
|
||||
groups[gi].remove([v.index])
|
||||
for gi, w in keep:
|
||||
groups[gi].add([v.index], w / total, 'REPLACE')
|
||||
changed += 1
|
||||
return changed
|
||||
|
||||
|
||||
def count_max_influences(obj):
|
||||
if obj.type != 'MESH':
|
||||
return 0
|
||||
best = 0
|
||||
for v in obj.data.vertices:
|
||||
best = max(best, sum(1 for g in v.groups if g.weight > 0.0))
|
||||
return best
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,6 +6,11 @@ import json
|
||||
import mathutils
|
||||
from mathutils.bvhtree import BVHTree
|
||||
|
||||
_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
if _THIS_DIR not in sys.path:
|
||||
sys.path.insert(0, _THIS_DIR)
|
||||
import age_profiles
|
||||
|
||||
def load_blend_files(clothes_blend_path, body_blend_path):
|
||||
"""Load objects from blend files and return all loaded objects
|
||||
|
||||
@@ -364,6 +369,28 @@ def run_batch_combine():
|
||||
print(f"Found {len(clothing_layer1)} layer 1 clothing objects")
|
||||
print(f"Found {len(clothing_layer2)} layer 2 clothing objects")
|
||||
|
||||
# Guard against mixing ages/sexes in one pipeline run. The CMake inputs
|
||||
# are per age/sex; this catches a misnamed file before it bakes nonsense.
|
||||
ages = {age_profiles.normalize_age(o.get("age", "adult"))
|
||||
for o in body_objects}
|
||||
sexes = {str(o.get("sex", "")) for o in body_objects}
|
||||
if len(ages) != 1 or len(sexes) != 1:
|
||||
raise RuntimeError(
|
||||
f"combine_clothes: mixed body ages/sexes: "
|
||||
f"ages={sorted(ages)} sexes={sorted(sexes)}")
|
||||
target_age = next(iter(ages))
|
||||
target_sex = next(iter(sexes))
|
||||
for o in clothing_layer1 + clothing_layer2:
|
||||
obj_age = age_profiles.normalize_age(o.get("ref_age", "adult"))
|
||||
obj_sex = str(o.get("ref_sex", ""))
|
||||
if obj_age != target_age or obj_sex != target_sex:
|
||||
raise RuntimeError(
|
||||
f"combine_clothes: {o.name} is {obj_age}/{obj_sex}, "
|
||||
f"body is {target_age}/{target_sex}")
|
||||
# Keep the authoritative band on the source garment.
|
||||
o["ref_age"] = target_age
|
||||
o["ref_sex"] = target_sex
|
||||
|
||||
body_objects_dict = {obj.name: obj for obj in body_objects}
|
||||
whitelist = set()
|
||||
combined_objects = []
|
||||
@@ -474,7 +501,11 @@ def run_batch_combine():
|
||||
else:
|
||||
obj["clothing_tags"] = ""
|
||||
|
||||
print(f"Finalized metadata for {obj.name}: layer={obj['layer']}, garments={obj['garments']}, tags={obj['clothing_tags']}")
|
||||
obj["age"] = target_age
|
||||
obj["sex"] = target_sex
|
||||
print(f"Finalized metadata for {obj.name}: age={target_age}, "
|
||||
f"sex={target_sex}, layer={obj['layer']}, "
|
||||
f"garments={obj['garments']}, tags={obj['clothing_tags']}")
|
||||
|
||||
del obj["_combined_meta"]
|
||||
|
||||
|
||||
@@ -7,6 +7,89 @@ _script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
if _script_dir not in sys.path:
|
||||
sys.path.insert(0, _script_dir)
|
||||
from transfer_shape_keys import fix_seams_across_objects, fix_normals_across_objects
|
||||
import age_profiles
|
||||
|
||||
|
||||
def _catalog_objects():
|
||||
required = ("age", "sex", "slot")
|
||||
return [o for o in bpy.data.objects
|
||||
if o.type == 'MESH' and all(p in o for p in required)]
|
||||
|
||||
|
||||
def _own_armature_names():
|
||||
names = set()
|
||||
for obj in _catalog_objects():
|
||||
if obj.get("has_own_armature", False):
|
||||
name = obj.get("own_armature_name", "")
|
||||
if name and bpy.data.objects.get(name):
|
||||
names.add(name)
|
||||
return names
|
||||
|
||||
|
||||
def consolidate_armatures():
|
||||
"""Leave exactly one body armature per consolidated blend.
|
||||
|
||||
The clothing stages append their own copy of the body rig; Blender
|
||||
renames those copies to .001/.002/... They are unused and previously
|
||||
leaked into the export as extra skeletons.
|
||||
"""
|
||||
cats = _catalog_objects()
|
||||
if not cats:
|
||||
print("WARNING: no catalog objects; cannot consolidate armatures")
|
||||
return
|
||||
|
||||
ages = {age_profiles.normalize_age(o.get("age", "adult"))
|
||||
for o in cats}
|
||||
sexes = {str(o.get("sex", "male")) for o in cats}
|
||||
if len(ages) != 1 or len(sexes) != 1:
|
||||
raise RuntimeError(
|
||||
f"mixed ages/sexes in one consolidated blend: "
|
||||
f"ages={sorted(ages)} sexes={sorted(sexes)}")
|
||||
age = next(iter(ages))
|
||||
sex = next(iter(sexes))
|
||||
expected = age_profiles.armature_name(age, sex)
|
||||
|
||||
body = bpy.data.objects.get(expected)
|
||||
if body is None or body.type != 'ARMATURE':
|
||||
# Choose the armature with the most catalog children / deform bones.
|
||||
best = None
|
||||
best_score = -1
|
||||
for arm in [o for o in bpy.data.objects if o.type == 'ARMATURE']:
|
||||
children = sum(1 for o in cats if o.parent == arm)
|
||||
score = children * 10000 + len(arm.data.bones)
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best = arm
|
||||
body = best
|
||||
if body is None:
|
||||
raise RuntimeError("no body armature found in consolidated blend")
|
||||
|
||||
body.name = expected
|
||||
body.data.name = expected
|
||||
own = _own_armature_names()
|
||||
|
||||
for obj in cats:
|
||||
if obj.get("has_own_armature", False):
|
||||
continue
|
||||
obj.parent = body
|
||||
arm_mod = next((m for m in obj.modifiers
|
||||
if m.type == 'ARMATURE'), None)
|
||||
if arm_mod is None:
|
||||
arm_mod = obj.modifiers.new(name="Armature", type='ARMATURE')
|
||||
arm_mod.object = body
|
||||
|
||||
removed = []
|
||||
for arm in [o for o in bpy.data.objects if o.type == 'ARMATURE']:
|
||||
if arm.name == body.name or arm.name in own:
|
||||
continue
|
||||
removed.append(arm.name)
|
||||
bpy.data.objects.remove(arm, do_unlink=True)
|
||||
if removed:
|
||||
print(f"Removed duplicate body armatures: {removed}")
|
||||
print(f"Consolidated body armature: '{body.name}' "
|
||||
f"({len(body.data.bones)} bones), "
|
||||
f"hair armatures kept: {sorted(own)}")
|
||||
|
||||
|
||||
def process_append(source_files, output_path):
|
||||
required_props = {"age", "sex", "slot"}
|
||||
@@ -92,7 +175,8 @@ def process_append(source_files, output_path):
|
||||
continue
|
||||
|
||||
# Normal body part: parent to body armature
|
||||
arm_name = obj.get("sex")
|
||||
arm_name = age_profiles.armature_name(
|
||||
obj.get("age", "adult"), obj.get("sex", "male"))
|
||||
arm_obj = bpy.data.objects.get(arm_name)
|
||||
|
||||
if arm_obj and arm_obj.type == 'ARMATURE':
|
||||
@@ -121,10 +205,14 @@ def process_append(source_files, output_path):
|
||||
else:
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
|
||||
# 4. Recursive Purge of all unlinked data (Materials, Textures, Meshes)
|
||||
# 4. Hardening: one body armature, no duplicate clothing-stage copies.
|
||||
print("\nConsolidating body armatures...")
|
||||
consolidate_armatures()
|
||||
|
||||
# 5. Recursive Purge of all unlinked data (Materials, Textures, Meshes)
|
||||
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
|
||||
|
||||
# 5. Fix seams across ALL body parts in the consolidated scene.
|
||||
# 6. Fix seams across ALL body parts in the consolidated scene.
|
||||
print("\nFixing seams across all consolidated body parts...")
|
||||
fix_seams_across_objects()
|
||||
fix_normals_across_objects()
|
||||
|
||||
@@ -0,0 +1,638 @@
|
||||
{
|
||||
"age": "baby",
|
||||
"fails": [],
|
||||
"info": {
|
||||
"adultArmature": "female",
|
||||
"ageArmature": "baby-female",
|
||||
"animations": {
|
||||
"aliased": {
|
||||
"swimming-fast": "swimming"
|
||||
},
|
||||
"appended": [
|
||||
"Armature|mixamo.com|Layer0.020",
|
||||
"Armature|mixamo.com|Layer0.018",
|
||||
"Armature|mixamo.com|Layer0.016",
|
||||
"Armature|mixamo.com|Layer0.014"
|
||||
],
|
||||
"missing": [],
|
||||
"scaledLocationCurves": 7028,
|
||||
"stem": "baby-female"
|
||||
},
|
||||
"outputBase": "edited-normal-baby-female.blend",
|
||||
"slots": "bottom,face,feet,hair,top",
|
||||
"sourceBase": "edited-normal-female.blend"
|
||||
},
|
||||
"objects": {
|
||||
"Accessory": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.7372296166046396,
|
||||
"edgeRatioMin": 0.3150672796769405,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 40,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 69
|
||||
},
|
||||
"Body": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 2.153788134245814,
|
||||
"edgeRatioMin": 0.0842179761594005,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 6227,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 6578
|
||||
},
|
||||
"Body-merged": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 2.153788134245814,
|
||||
"edgeRatioMin": 0.07910523007157484,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 8423,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 9178
|
||||
},
|
||||
"BodyBottom": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.5943739947353248,
|
||||
"edgeRatioMin": 0.24871503022916608,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1242,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1194
|
||||
},
|
||||
"BodyFeet": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.6102128302187958,
|
||||
"edgeRatioMin": 0.23948013201029564,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 1280,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1050
|
||||
},
|
||||
"BodyTop": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 2.153788134245814,
|
||||
"edgeRatioMin": 0.0842179761594005,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 3701,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 3354
|
||||
},
|
||||
"BodyTopRobe": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 2.153788134245814,
|
||||
"edgeRatioMin": 0.07910523007157484,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 4727,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 4383
|
||||
},
|
||||
"Body_shapes": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 2.153788134245814,
|
||||
"edgeRatioMin": 0.0842179761594005,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 6223,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 2,
|
||||
"unweighted": 0,
|
||||
"vertices": 5550
|
||||
},
|
||||
"Cylinder": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 102.90996675755198,
|
||||
"edgeRatioMin": 0.08829058454162445,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 595,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 840,
|
||||
"vertices": 840
|
||||
},
|
||||
"Face": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8507524157231209,
|
||||
"edgeRatioMin": 0.8497737875813667,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 3887,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 57,
|
||||
"unweighted": 0,
|
||||
"vertices": 4109
|
||||
},
|
||||
"Hair": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8500001569254199,
|
||||
"edgeRatioMin": 0.8499997162406219,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 2,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 8
|
||||
},
|
||||
"Hair001": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8653125183616986,
|
||||
"edgeRatioMin": 0.8197160422662443,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 5658,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 8673
|
||||
},
|
||||
"Hair2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8653125183616986,
|
||||
"edgeRatioMin": 0.8197160422662443,
|
||||
"maxInfluences": 2,
|
||||
"note": "",
|
||||
"polygons": 5658,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 8673
|
||||
},
|
||||
"HairBack": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.4644834678498582,
|
||||
"edgeRatioMin": 0.6099711906323935,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 726,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 760
|
||||
},
|
||||
"OnepieceCloth": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.5492324553724546,
|
||||
"edgeRatioMin": 0.5290424031153619,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 2,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 6
|
||||
},
|
||||
"Shoes": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.5911305932486909,
|
||||
"edgeRatioMin": 0.2363519411318045,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 402,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 608
|
||||
},
|
||||
"Tops": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.7959426635498152,
|
||||
"edgeRatioMin": 0.07910523007157484,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1026,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1029
|
||||
},
|
||||
"bra1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.850017759730551,
|
||||
"edgeRatioMin": 0.8499810981151088,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 430,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 419
|
||||
},
|
||||
"bra2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8500176808030321,
|
||||
"edgeRatioMin": 0.8499719662092197,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 506,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 524
|
||||
},
|
||||
"bra3": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8500183886242741,
|
||||
"edgeRatioMin": 0.8499869218274825,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 410,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 450
|
||||
},
|
||||
"bra4": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8500183886242741,
|
||||
"edgeRatioMin": 0.8499869218274825,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 410,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 450
|
||||
},
|
||||
"panties": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 389.90044750135564,
|
||||
"edgeRatioMin": 0.1344951283772418,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 674,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 797,
|
||||
"vertices": 797
|
||||
},
|
||||
"panties-full": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 9.037905091399436,
|
||||
"edgeRatioMin": 0.09492642747770344,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 337,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 425,
|
||||
"vertices": 425
|
||||
},
|
||||
"panties2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 73.44866646636162,
|
||||
"edgeRatioMin": 0.039480502681632265,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 1266,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 1328,
|
||||
"vertices": 1328
|
||||
},
|
||||
"panties2-full": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 69.03029060930521,
|
||||
"edgeRatioMin": 0.12109997869211511,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 507,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 558,
|
||||
"vertices": 558
|
||||
},
|
||||
"panties3": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 56.096226750526704,
|
||||
"edgeRatioMin": 0.07317455801311457,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 758,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 820,
|
||||
"vertices": 820
|
||||
},
|
||||
"panties4": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 59.134504340321854,
|
||||
"edgeRatioMin": 0.07317455801311457,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 690,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 752,
|
||||
"vertices": 752
|
||||
},
|
||||
"panties5": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8500240796533016,
|
||||
"edgeRatioMin": 0.8499814844536061,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 698,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 758
|
||||
},
|
||||
"panties6": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8500154180994606,
|
||||
"edgeRatioMin": 0.8499882205989492,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 698,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 758
|
||||
},
|
||||
"panties7": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8500126347595567,
|
||||
"edgeRatioMin": 0.849978243664894,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 698,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 758
|
||||
},
|
||||
"shoes1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.5538312559806804,
|
||||
"edgeRatioMin": 0.26002334716847597,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 414,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 414
|
||||
},
|
||||
"skirt1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.5322821714343262,
|
||||
"edgeRatioMin": 0.27078238670490873,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 132,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 154
|
||||
},
|
||||
"skirt2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.5392819893864378,
|
||||
"edgeRatioMin": 0.2767377972392573,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 154,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 176
|
||||
},
|
||||
"skirt3": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.5319295543088763,
|
||||
"edgeRatioMin": 0.2734056437018691,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 132,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 154
|
||||
},
|
||||
"top1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8500186043293043,
|
||||
"edgeRatioMin": 0.8499912680582199,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 860,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 852
|
||||
},
|
||||
"top2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8500086443486258,
|
||||
"edgeRatioMin": 0.8499937873756263,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 876,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 854
|
||||
},
|
||||
"top3": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8500086443486258,
|
||||
"edgeRatioMin": 0.8499937873756263,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 876,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 854
|
||||
}
|
||||
},
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"rig": {
|
||||
"adultBones": 252,
|
||||
"ageBones": 252,
|
||||
"profile": {
|
||||
"arm": 0.3,
|
||||
"bodyRadial": 0.55,
|
||||
"foot": 0.55,
|
||||
"head": 0.85,
|
||||
"headBulge": 1.08,
|
||||
"hipHeight": 0.28,
|
||||
"hipWidth": 0.55,
|
||||
"leg": 0.26,
|
||||
"limbRadial": 0.5,
|
||||
"shoulderWidth": 0.5,
|
||||
"torso": 0.3,
|
||||
"walkSpeedScale": 0.0
|
||||
},
|
||||
"restOrientationErrors": []
|
||||
},
|
||||
"seams": {
|
||||
"coincidentGroups": 3697,
|
||||
"examples": [
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0649,
|
||||
0.0281,
|
||||
1.3797
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.066,
|
||||
0.0231,
|
||||
1.3767
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0694,
|
||||
0.0172,
|
||||
1.3783
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0649,
|
||||
0.0281,
|
||||
1.3797
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.066,
|
||||
0.0231,
|
||||
1.3767
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0694,
|
||||
0.0172,
|
||||
1.3783
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
0.0001,
|
||||
-0.0385,
|
||||
1.5529
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
-0.0001,
|
||||
-0.0385,
|
||||
1.5529
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 3e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
-0.0012,
|
||||
-0.0342,
|
||||
1.5629
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 3e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
-0.001,
|
||||
-0.0343,
|
||||
1.5628
|
||||
]
|
||||
}
|
||||
],
|
||||
"maxGap": 5.9825688860243066e-06,
|
||||
"snappedVertices": 95
|
||||
},
|
||||
"sex": "female",
|
||||
"status": "warn",
|
||||
"stem": "baby-female",
|
||||
"timestamp": 1793419311,
|
||||
"warnings": [
|
||||
"Accessory",
|
||||
"Body",
|
||||
"Body-merged",
|
||||
"Body_shapes",
|
||||
"BodyBottom",
|
||||
"BodyFeet",
|
||||
"BodyTop",
|
||||
"BodyTopRobe",
|
||||
"Shoes",
|
||||
"Tops",
|
||||
"BodyTop",
|
||||
"BodyBottom",
|
||||
"Cylinder",
|
||||
"panties",
|
||||
"panties-full",
|
||||
"panties2",
|
||||
"panties2-full",
|
||||
"panties3",
|
||||
"panties4",
|
||||
"skirt1",
|
||||
"skirt2",
|
||||
"skirt3",
|
||||
"BodyFeet",
|
||||
"shoes1"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
# Conversion Report — baby / female
|
||||
|
||||
- Stem: `baby-female`
|
||||
- Status: **warn**
|
||||
- Profile hash: `dd6901d4220ae704`
|
||||
- Objects: 37
|
||||
- Warnings: 24
|
||||
- Fails: 0
|
||||
|
||||
## Info
|
||||
- adultArmature: `female`
|
||||
- ageArmature: `baby-female`
|
||||
- animations: `{'scaledLocationCurves': 7028, 'appended': ['Armature|mixamo.com|Layer0.020', 'Armature|mixamo.com|Layer0.018', 'Armature|mixamo.com|Layer0.016', 'Armature|mixamo.com|Layer0.014'], 'aliased': {'swimming-fast': 'swimming'}, 'missing': [], 'stem': 'baby-female'}`
|
||||
- outputBase: `edited-normal-baby-female.blend`
|
||||
- slots: `bottom,face,feet,hair,top`
|
||||
- sourceBase: `edited-normal-female.blend`
|
||||
|
||||
## Seams
|
||||
- coincidentGroups: `3697`
|
||||
- snappedVertices: `95`
|
||||
- maxGap: `5.9825688860243066e-06`
|
||||
- examples: `[{'position': [0.0649, 0.0281, 1.3797], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.066, 0.0231, 1.3767], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0694, 0.0172, 1.3783], 'gap': 1e-06, 'objects': ['Face']}, {'position': [-0.0649, 0.0281, 1.3797], 'gap': 1e-06, 'objects': ['Face']}, {'position': [-0.066, 0.0231, 1.3767], 'gap': 1e-06, 'objects': ['Face']}, {'position': [-0.0694, 0.0172, 1.3783], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0001, -0.0385, 1.5529], 'gap': 2e-06, 'objects': ['Hair2']}, {'position': [-0.0001, -0.0385, 1.5529], 'gap': 2e-06, 'objects': ['Hair2']}, {'position': [-0.0012, -0.0342, 1.5629], 'gap': 3e-06, 'objects': ['Hair2']}, {'position': [-0.001, -0.0343, 1.5628], 'gap': 3e-06, 'objects': ['Hair2']}]`
|
||||
|
||||
## Warnings (first 20)
|
||||
|
||||
- `Accessory`
|
||||
- `Body`
|
||||
- `Body-merged`
|
||||
- `Body_shapes`
|
||||
- `BodyBottom`
|
||||
- `BodyFeet`
|
||||
- `BodyTop`
|
||||
- `BodyTopRobe`
|
||||
- `Shoes`
|
||||
- `Tops`
|
||||
- `BodyTop`
|
||||
- `BodyBottom`
|
||||
- `Cylinder`
|
||||
- `panties`
|
||||
- `panties-full`
|
||||
- `panties2`
|
||||
- `panties2-full`
|
||||
- `panties3`
|
||||
- `panties4`
|
||||
- `skirt1`
|
||||
|
||||
## Objects
|
||||
|
||||
- `Accessory` — warn; verts=69, tris=0 degenerate, influences=4, unweighted=0, edge=0.32..0.74
|
||||
- `Body` — warn; verts=6578, tris=0 degenerate, influences=4, unweighted=0, edge=0.08..2.15
|
||||
- `Body-merged` — warn; verts=9178, tris=0 degenerate, influences=4, unweighted=0, edge=0.08..2.15
|
||||
- `BodyBottom` — warn; verts=1194, tris=0 degenerate, influences=4, unweighted=0, edge=0.25..0.59
|
||||
- `BodyFeet` — warn; verts=1050, tris=0 degenerate, influences=3, unweighted=0, edge=0.24..0.61
|
||||
- `BodyTop` — warn; verts=3354, tris=0 degenerate, influences=4, unweighted=0, edge=0.08..2.15
|
||||
- `BodyTopRobe` — warn; verts=4383, tris=0 degenerate, influences=4, unweighted=0, edge=0.08..2.15
|
||||
- `Body_shapes` — warn; verts=5550, tris=0 degenerate, influences=4, unweighted=0, edge=0.08..2.15
|
||||
- `Cylinder` — warn; verts=840, tris=0 degenerate, influences=0, unweighted=840, edge=0.09..102.91
|
||||
- `Face` — ok; verts=4109, tris=0 degenerate, influences=1, unweighted=0, edge=0.85..0.85
|
||||
- `Hair` — ok; verts=8, tris=0 degenerate, influences=1, unweighted=0, edge=0.85..0.85
|
||||
- `Hair001` — ok; verts=8673, tris=0 degenerate, influences=1, unweighted=0, edge=0.82..0.87
|
||||
- `Hair2` — ok; verts=8673, tris=0 degenerate, influences=2, unweighted=0, edge=0.82..0.87
|
||||
- `HairBack` — ok; verts=760, tris=0 degenerate, influences=4, unweighted=0, edge=0.61..1.46
|
||||
- `OnepieceCloth` — ok; verts=6, tris=0 degenerate, influences=3, unweighted=0, edge=0.53..0.55
|
||||
- `Shoes` — warn; verts=608, tris=0 degenerate, influences=3, unweighted=0, edge=0.24..0.59
|
||||
- `Tops` — warn; verts=1029, tris=0 degenerate, influences=4, unweighted=0, edge=0.08..0.80
|
||||
- `bra1` — ok; verts=419, tris=0 degenerate, influences=1, unweighted=0, edge=0.85..0.85
|
||||
- `bra2` — ok; verts=524, tris=0 degenerate, influences=1, unweighted=0, edge=0.85..0.85
|
||||
- `bra3` — ok; verts=450, tris=0 degenerate, influences=1, unweighted=0, edge=0.85..0.85
|
||||
- `bra4` — ok; verts=450, tris=0 degenerate, influences=1, unweighted=0, edge=0.85..0.85
|
||||
- `panties` — warn; verts=797, tris=0 degenerate, influences=0, unweighted=797, edge=0.13..389.90
|
||||
- `panties-full` — warn; verts=425, tris=0 degenerate, influences=0, unweighted=425, edge=0.09..9.04
|
||||
- `panties2` — warn; verts=1328, tris=0 degenerate, influences=0, unweighted=1328, edge=0.04..73.45
|
||||
- `panties2-full` — warn; verts=558, tris=0 degenerate, influences=0, unweighted=558, edge=0.12..69.03
|
||||
- `panties3` — warn; verts=820, tris=0 degenerate, influences=0, unweighted=820, edge=0.07..56.10
|
||||
- `panties4` — warn; verts=752, tris=0 degenerate, influences=0, unweighted=752, edge=0.07..59.13
|
||||
- `panties5` — ok; verts=758, tris=0 degenerate, influences=1, unweighted=0, edge=0.85..0.85
|
||||
- `panties6` — ok; verts=758, tris=0 degenerate, influences=1, unweighted=0, edge=0.85..0.85
|
||||
- `panties7` — ok; verts=758, tris=0 degenerate, influences=1, unweighted=0, edge=0.85..0.85
|
||||
- `shoes1` — warn; verts=414, tris=0 degenerate, influences=3, unweighted=0, edge=0.26..1.55
|
||||
- `skirt1` — warn; verts=154, tris=0 degenerate, influences=3, unweighted=0, edge=0.27..0.53
|
||||
- `skirt2` — warn; verts=176, tris=0 degenerate, influences=3, unweighted=0, edge=0.28..0.54
|
||||
- `skirt3` — warn; verts=154, tris=0 degenerate, influences=3, unweighted=0, edge=0.27..0.53
|
||||
- `top1` — ok; verts=852, tris=0 degenerate, influences=1, unweighted=0, edge=0.85..0.85
|
||||
- `top2` — ok; verts=854, tris=0 degenerate, influences=1, unweighted=0, edge=0.85..0.85
|
||||
- `top3` — ok; verts=854, tris=0 degenerate, influences=1, unweighted=0, edge=0.85..0.85
|
||||
@@ -0,0 +1,491 @@
|
||||
{
|
||||
"age": "baby",
|
||||
"fails": [],
|
||||
"info": {
|
||||
"adultArmature": "male",
|
||||
"ageArmature": "baby-male",
|
||||
"animations": {
|
||||
"aliased": {
|
||||
"swimming-fast": "swimming"
|
||||
},
|
||||
"appended": [
|
||||
"Armature|mixamo.com|Layer0.020",
|
||||
"Armature|mixamo.com|Layer0.018",
|
||||
"Armature|mixamo.com|Layer0.016",
|
||||
"Armature|mixamo.com|Layer0.014",
|
||||
"Armature|mixamo.com|Layer0.012"
|
||||
],
|
||||
"missing": [],
|
||||
"scaledLocationCurves": 7601,
|
||||
"stem": "baby-male"
|
||||
},
|
||||
"outputBase": "edited-normal-baby-male.blend",
|
||||
"slots": "bottom,face,feet,hair,top",
|
||||
"sourceBase": "edited-normal-male.blend"
|
||||
},
|
||||
"objects": {
|
||||
"Accessory": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.6591322275489071,
|
||||
"edgeRatioMin": 0.2294774279830071,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 111,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 145
|
||||
},
|
||||
"Body": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 2.7944548668408027,
|
||||
"edgeRatioMin": 0.09792195397903704,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 6232,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 6584
|
||||
},
|
||||
"BodyBottom": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.696915448832056,
|
||||
"edgeRatioMin": 0.2475168552675534,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1228,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 1181
|
||||
},
|
||||
"BodyBottomPants": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8123872063883257,
|
||||
"edgeRatioMin": 0.2475168552675534,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 972,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 1133
|
||||
},
|
||||
"BodyFeet": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.6102393851343675,
|
||||
"edgeRatioMin": 0.22574066520587047,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 1274,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 1392
|
||||
},
|
||||
"BodyFeetPants": {
|
||||
"degenerateTris": 48,
|
||||
"edgeRatioMax": 0.6102393851343675,
|
||||
"edgeRatioMin": 0.2095881183386803,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 1466,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1602
|
||||
},
|
||||
"BodyFeetPantsShoes": {
|
||||
"degenerateTris": 48,
|
||||
"edgeRatioMax": 0.6017141327128214,
|
||||
"edgeRatioMin": 0.2095881183386803,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 830,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1048
|
||||
},
|
||||
"BodyTop": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 2.7944548668408027,
|
||||
"edgeRatioMin": 0.09792195397903704,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 3726,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 3373
|
||||
},
|
||||
"BodyTopRobe": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 2.694739629684512,
|
||||
"edgeRatioMin": 0.04884643632625867,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 5384,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 5046
|
||||
},
|
||||
"Body_shapes": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 2.7944548668408027,
|
||||
"edgeRatioMin": 0.09792195397903704,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 6228,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 2,
|
||||
"unweighted": 0,
|
||||
"vertices": 5556
|
||||
},
|
||||
"Bottoms": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8123872063883257,
|
||||
"edgeRatioMin": 0.2095881183386803,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 826,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 942
|
||||
},
|
||||
"Face": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8501110244008585,
|
||||
"edgeRatioMin": 0.8499080602231018,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 3839,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 57,
|
||||
"unweighted": 0,
|
||||
"vertices": 4045
|
||||
},
|
||||
"Hair": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8500001087034722,
|
||||
"edgeRatioMin": 0.8499998996166309,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 4,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 9
|
||||
},
|
||||
"Hair2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8932439134046963,
|
||||
"edgeRatioMin": 0.8193953530826958,
|
||||
"maxInfluences": 2,
|
||||
"note": "",
|
||||
"polygons": 4483,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 7022
|
||||
},
|
||||
"HairBack": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.4800221295791642,
|
||||
"edgeRatioMin": 0.6036557832095256,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 724,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 760
|
||||
},
|
||||
"Panties001": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.6962243553417347,
|
||||
"edgeRatioMin": 0.2658767663648385,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 604,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 582
|
||||
},
|
||||
"Pants0001": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.651774029445214,
|
||||
"edgeRatioMin": 0.2253732629144712,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 898,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 915
|
||||
},
|
||||
"Shoes": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.6017141327128214,
|
||||
"edgeRatioMin": 0.39686482033435044,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 334,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 478
|
||||
},
|
||||
"Tops": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.6309442432828267,
|
||||
"edgeRatioMin": 0.04884643632625867,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1658,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1896
|
||||
},
|
||||
"TopsEdited": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.6309442432828267,
|
||||
"edgeRatioMin": 0.04884643632625867,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1658,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1896
|
||||
},
|
||||
"coat": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.5376462038278786,
|
||||
"edgeRatioMin": 0.14071060392477913,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1658,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1896
|
||||
},
|
||||
"hair0001": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 2.322546280419375,
|
||||
"edgeRatioMin": 0.6481636259388702,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 4483,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 7022
|
||||
},
|
||||
"hair0002": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 2.322546280419375,
|
||||
"edgeRatioMin": 0.6481636259388702,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 4483,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 7022
|
||||
},
|
||||
"hair0003": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0,
|
||||
"edgeRatioMin": 1.0,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 4483,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 7022
|
||||
},
|
||||
"shoes1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.5819192631554228,
|
||||
"edgeRatioMin": 0.36626946181073133,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 334,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 478
|
||||
}
|
||||
},
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"rig": {
|
||||
"adultBones": 242,
|
||||
"ageBones": 242,
|
||||
"profile": {
|
||||
"arm": 0.3,
|
||||
"bodyRadial": 0.55,
|
||||
"foot": 0.55,
|
||||
"head": 0.85,
|
||||
"headBulge": 1.08,
|
||||
"hipHeight": 0.28,
|
||||
"hipWidth": 0.55,
|
||||
"leg": 0.26,
|
||||
"limbRadial": 0.5,
|
||||
"shoulderWidth": 0.5,
|
||||
"torso": 0.3,
|
||||
"walkSpeedScale": 0.0
|
||||
},
|
||||
"restOrientationErrors": []
|
||||
},
|
||||
"seams": {
|
||||
"coincidentGroups": 5119,
|
||||
"examples": [
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"BodyTopRobe"
|
||||
],
|
||||
"position": [
|
||||
0.0,
|
||||
-0.0184,
|
||||
1.5008
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0633,
|
||||
0.0369,
|
||||
1.6496
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0633,
|
||||
0.0369,
|
||||
1.6496
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0636,
|
||||
0.0319,
|
||||
1.6457
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0698,
|
||||
0.021,
|
||||
1.649
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0716,
|
||||
0.0206,
|
||||
1.6529
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0636,
|
||||
0.0319,
|
||||
1.6457
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0698,
|
||||
0.021,
|
||||
1.649
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0716,
|
||||
0.0206,
|
||||
1.6529
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
0.0002,
|
||||
-0.0352,
|
||||
1.8214
|
||||
]
|
||||
}
|
||||
],
|
||||
"maxGap": 6.834857277971055e-06,
|
||||
"snappedVertices": 204
|
||||
},
|
||||
"sex": "male",
|
||||
"status": "warn",
|
||||
"stem": "baby-male",
|
||||
"timestamp": 1793419306,
|
||||
"warnings": [
|
||||
"Accessory",
|
||||
"Body",
|
||||
"Body_shapes",
|
||||
"BodyBottom",
|
||||
"BodyBottomPants",
|
||||
"BodyFeet",
|
||||
"BodyFeetPants",
|
||||
"BodyFeetPantsShoes",
|
||||
"BodyTop",
|
||||
"BodyTopRobe",
|
||||
"Bottoms",
|
||||
"Tops",
|
||||
"TopsEdited",
|
||||
"coat",
|
||||
"Panties001",
|
||||
"Pants0001",
|
||||
"BodyFeet"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
# Conversion Report — baby / male
|
||||
|
||||
- Stem: `baby-male`
|
||||
- Status: **warn**
|
||||
- Profile hash: `dd6901d4220ae704`
|
||||
- Objects: 25
|
||||
- Warnings: 17
|
||||
- Fails: 0
|
||||
|
||||
## Info
|
||||
- adultArmature: `male`
|
||||
- ageArmature: `baby-male`
|
||||
- animations: `{'scaledLocationCurves': 7601, 'appended': ['Armature|mixamo.com|Layer0.020', 'Armature|mixamo.com|Layer0.018', 'Armature|mixamo.com|Layer0.016', 'Armature|mixamo.com|Layer0.014', 'Armature|mixamo.com|Layer0.012'], 'aliased': {'swimming-fast': 'swimming'}, 'missing': [], 'stem': 'baby-male'}`
|
||||
- outputBase: `edited-normal-baby-male.blend`
|
||||
- slots: `bottom,face,feet,hair,top`
|
||||
- sourceBase: `edited-normal-male.blend`
|
||||
|
||||
## Seams
|
||||
- coincidentGroups: `5119`
|
||||
- snappedVertices: `204`
|
||||
- maxGap: `6.834857277971055e-06`
|
||||
- examples: `[{'position': [0.0, -0.0184, 1.5008], 'gap': 1e-06, 'objects': ['BodyTop', 'BodyTopRobe']}, {'position': [0.0633, 0.0369, 1.6496], 'gap': 1e-06, 'objects': ['BodyTop', 'Face']}, {'position': [-0.0633, 0.0369, 1.6496], 'gap': 1e-06, 'objects': ['BodyTop', 'Face']}, {'position': [0.0636, 0.0319, 1.6457], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0698, 0.021, 1.649], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0716, 0.0206, 1.6529], 'gap': 1e-06, 'objects': ['Face']}, {'position': [-0.0636, 0.0319, 1.6457], 'gap': 1e-06, 'objects': ['Face']}, {'position': [-0.0698, 0.021, 1.649], 'gap': 1e-06, 'objects': ['Face']}, {'position': [-0.0716, 0.0206, 1.6529], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0002, -0.0352, 1.8214], 'gap': 2e-06, 'objects': ['Hair2']}]`
|
||||
|
||||
## Warnings (first 20)
|
||||
|
||||
- `Accessory`
|
||||
- `Body`
|
||||
- `Body_shapes`
|
||||
- `BodyBottom`
|
||||
- `BodyBottomPants`
|
||||
- `BodyFeet`
|
||||
- `BodyFeetPants`
|
||||
- `BodyFeetPantsShoes`
|
||||
- `BodyTop`
|
||||
- `BodyTopRobe`
|
||||
- `Bottoms`
|
||||
- `Tops`
|
||||
- `TopsEdited`
|
||||
- `coat`
|
||||
- `Panties001`
|
||||
- `Pants0001`
|
||||
- `BodyFeet`
|
||||
|
||||
## Objects
|
||||
|
||||
- `Accessory` — warn; verts=145, tris=0 degenerate, influences=4, unweighted=0, edge=0.23..0.66
|
||||
- `Body` — warn; verts=6584, tris=0 degenerate, influences=4, unweighted=0, edge=0.10..2.79
|
||||
- `BodyBottom` — warn; verts=1181, tris=0 degenerate, influences=4, unweighted=0, edge=0.25..0.70
|
||||
- `BodyBottomPants` — warn; verts=1133, tris=0 degenerate, influences=4, unweighted=0, edge=0.25..0.81
|
||||
- `BodyFeet` — warn; verts=1392, tris=0 degenerate, influences=3, unweighted=0, edge=0.23..0.61
|
||||
- `BodyFeetPants` — warn; verts=1602, tris=48 degenerate, influences=3, unweighted=0, edge=0.21..0.61
|
||||
- `BodyFeetPantsShoes` — warn; verts=1048, tris=48 degenerate, influences=3, unweighted=0, edge=0.21..0.60
|
||||
- `BodyTop` — warn; verts=3373, tris=0 degenerate, influences=4, unweighted=0, edge=0.10..2.79
|
||||
- `BodyTopRobe` — warn; verts=5046, tris=0 degenerate, influences=4, unweighted=0, edge=0.05..2.69
|
||||
- `Body_shapes` — warn; verts=5556, tris=0 degenerate, influences=4, unweighted=0, edge=0.10..2.79
|
||||
- `Bottoms` — warn; verts=942, tris=0 degenerate, influences=4, unweighted=0, edge=0.21..0.81
|
||||
- `Face` — ok; verts=4045, tris=0 degenerate, influences=1, unweighted=0, edge=0.85..0.85
|
||||
- `Hair` — ok; verts=9, tris=0 degenerate, influences=1, unweighted=0, edge=0.85..0.85
|
||||
- `Hair2` — ok; verts=7022, tris=0 degenerate, influences=2, unweighted=0, edge=0.82..0.89
|
||||
- `HairBack` — ok; verts=760, tris=0 degenerate, influences=4, unweighted=0, edge=0.60..1.48
|
||||
- `Panties001` — warn; verts=582, tris=0 degenerate, influences=4, unweighted=0, edge=0.27..0.70
|
||||
- `Pants0001` — warn; verts=915, tris=0 degenerate, influences=4, unweighted=0, edge=0.23..1.65
|
||||
- `Shoes` — ok; verts=478, tris=0 degenerate, influences=3, unweighted=0, edge=0.40..0.60
|
||||
- `Tops` — warn; verts=1896, tris=0 degenerate, influences=4, unweighted=0, edge=0.05..1.63
|
||||
- `TopsEdited` — warn; verts=1896, tris=0 degenerate, influences=4, unweighted=0, edge=0.05..1.63
|
||||
- `coat` — warn; verts=1896, tris=0 degenerate, influences=4, unweighted=0, edge=0.14..1.54
|
||||
- `hair0001` — ok; verts=7022, tris=0 degenerate, influences=4, unweighted=0, edge=0.65..2.32
|
||||
- `hair0002` — ok; verts=7022, tris=0 degenerate, influences=4, unweighted=0, edge=0.65..2.32
|
||||
- `hair0003` — ok; verts=7022, tris=0 degenerate, influences=0, unweighted=0, edge=1.00..1.00
|
||||
- `shoes1` — ok; verts=478, tris=0 degenerate, influences=3, unweighted=0, edge=0.37..0.58
|
||||
@@ -0,0 +1,627 @@
|
||||
{
|
||||
"age": "child",
|
||||
"fails": [],
|
||||
"info": {
|
||||
"adultArmature": "female",
|
||||
"ageArmature": "child-female",
|
||||
"animations": {
|
||||
"aliased": {
|
||||
"swimming-fast": "swimming"
|
||||
},
|
||||
"appended": [
|
||||
"Armature|mixamo.com|Layer0.020",
|
||||
"Armature|mixamo.com|Layer0.018",
|
||||
"Armature|mixamo.com|Layer0.016",
|
||||
"Armature|mixamo.com|Layer0.014"
|
||||
],
|
||||
"missing": [],
|
||||
"scaledLocationCurves": 7028,
|
||||
"stem": "child-female"
|
||||
},
|
||||
"outputBase": "edited-normal-child-female.blend",
|
||||
"slots": "bottom,face,feet,hair,top",
|
||||
"sourceBase": "edited-normal-female.blend"
|
||||
},
|
||||
"objects": {
|
||||
"Accessory": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.7898433968486418,
|
||||
"edgeRatioMin": 0.49563877021449726,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 40,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 69
|
||||
},
|
||||
"Body": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.5936254360808924,
|
||||
"edgeRatioMin": 0.35974842621764863,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 6227,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 6578
|
||||
},
|
||||
"Body-merged": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.5936254360808924,
|
||||
"edgeRatioMin": 0.35974842621764863,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 8423,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 9178
|
||||
},
|
||||
"BodyBottom": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.7850192237493911,
|
||||
"edgeRatioMin": 0.5419409362099876,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1242,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1194
|
||||
},
|
||||
"BodyFeet": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.7839441718995033,
|
||||
"edgeRatioMin": 0.5345308894982719,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 1280,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1050
|
||||
},
|
||||
"BodyTop": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.5936254360808924,
|
||||
"edgeRatioMin": 0.35974842621764863,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 3701,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 3354
|
||||
},
|
||||
"BodyTopRobe": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.5936254360808924,
|
||||
"edgeRatioMin": 0.35974842621764863,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 4727,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 4383
|
||||
},
|
||||
"Body_shapes": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.5936254360808924,
|
||||
"edgeRatioMin": 0.35974842621764863,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 6223,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 2,
|
||||
"unweighted": 0,
|
||||
"vertices": 5550
|
||||
},
|
||||
"Cylinder": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 55.52669888099699,
|
||||
"edgeRatioMin": 0.3728675302592677,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 595,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 840,
|
||||
"vertices": 840
|
||||
},
|
||||
"Face": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9209191562868269,
|
||||
"edgeRatioMin": 0.9194440137740598,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 3887,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 57,
|
||||
"unweighted": 0,
|
||||
"vertices": 4109
|
||||
},
|
||||
"Hair": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9200007036314336,
|
||||
"edgeRatioMin": 0.9199996884063618,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 2,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 8
|
||||
},
|
||||
"Hair001": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9580249853514248,
|
||||
"edgeRatioMin": 0.8921870820834138,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 5658,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 8673
|
||||
},
|
||||
"Hair2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9580249853514248,
|
||||
"edgeRatioMin": 0.8921870820834138,
|
||||
"maxInfluences": 2,
|
||||
"note": "",
|
||||
"polygons": 5658,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 8673
|
||||
},
|
||||
"HairBack": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.243301152005224,
|
||||
"edgeRatioMin": 0.7996069035600766,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 726,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 760
|
||||
},
|
||||
"OnepieceCloth": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.7795104096985646,
|
||||
"edgeRatioMin": 0.7657916805350437,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 2,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 6
|
||||
},
|
||||
"Shoes": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.7710505397361299,
|
||||
"edgeRatioMin": 0.5274789367897975,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 402,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 608
|
||||
},
|
||||
"Tops": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8665602233865596,
|
||||
"edgeRatioMin": 0.44064144963083524,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1026,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1029
|
||||
},
|
||||
"bra1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.920044969946938,
|
||||
"edgeRatioMin": 0.9199654645832204,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 430,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 419
|
||||
},
|
||||
"bra2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9200462644396492,
|
||||
"edgeRatioMin": 0.9199754607547745,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 506,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 524
|
||||
},
|
||||
"bra3": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9200246096786325,
|
||||
"edgeRatioMin": 0.9199754607547745,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 410,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 450
|
||||
},
|
||||
"bra4": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9200246096786325,
|
||||
"edgeRatioMin": 0.9199754607547745,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 410,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 450
|
||||
},
|
||||
"panties": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 211.10052027794262,
|
||||
"edgeRatioMin": 0.19951643125297738,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 674,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 797,
|
||||
"vertices": 797
|
||||
},
|
||||
"panties-full": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 7.322137784611612,
|
||||
"edgeRatioMin": 0.21340035981963004,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 337,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 425,
|
||||
"vertices": 425
|
||||
},
|
||||
"panties2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 39.470945630173105,
|
||||
"edgeRatioMin": 0.21985285672118582,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 1266,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 1328,
|
||||
"vertices": 1328
|
||||
},
|
||||
"panties2-full": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 36.99110437234406,
|
||||
"edgeRatioMin": 0.10284348688443892,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 507,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 558,
|
||||
"vertices": 558
|
||||
},
|
||||
"panties3": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 30.204732645010235,
|
||||
"edgeRatioMin": 0.20627087124391974,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 758,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 820,
|
||||
"vertices": 820
|
||||
},
|
||||
"panties4": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 31.683176789997283,
|
||||
"edgeRatioMin": 0.1989474077553321,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 690,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 752,
|
||||
"vertices": 752
|
||||
},
|
||||
"panties5": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 5.895118610080954,
|
||||
"edgeRatioMin": 0.6048716072518844,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 698,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 758
|
||||
},
|
||||
"panties6": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 7.880647390006613,
|
||||
"edgeRatioMin": 0.6250818099712813,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 698,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 758
|
||||
},
|
||||
"panties7": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 7.880647390006613,
|
||||
"edgeRatioMin": 0.6400929469228386,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 698,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 758
|
||||
},
|
||||
"shoes1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9595848242017094,
|
||||
"edgeRatioMin": 0.5492882605967488,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 414,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 414
|
||||
},
|
||||
"skirt1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.3290984484841972,
|
||||
"edgeRatioMin": 0.3753802859501542,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 132,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 154
|
||||
},
|
||||
"skirt2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.4244777332130143,
|
||||
"edgeRatioMin": 0.3753802859501542,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 154,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 176
|
||||
},
|
||||
"skirt3": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.268563086876801,
|
||||
"edgeRatioMin": 0.37917544169158424,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 132,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 154
|
||||
},
|
||||
"top1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 7.0781828943354546,
|
||||
"edgeRatioMin": 0.7089442731001407,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 860,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 852
|
||||
},
|
||||
"top2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 7.0781828943354546,
|
||||
"edgeRatioMin": 0.7089442731001407,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 876,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 854
|
||||
},
|
||||
"top3": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 7.0781828943354546,
|
||||
"edgeRatioMin": 0.7089442731001407,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 876,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 854
|
||||
}
|
||||
},
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"rig": {
|
||||
"adultBones": 252,
|
||||
"ageBones": 252,
|
||||
"profile": {
|
||||
"arm": 0.6,
|
||||
"bodyRadial": 0.78,
|
||||
"foot": 0.75,
|
||||
"head": 0.92,
|
||||
"headBulge": 1.03,
|
||||
"hipHeight": 0.55,
|
||||
"hipWidth": 0.72,
|
||||
"leg": 0.55,
|
||||
"limbRadial": 0.72,
|
||||
"shoulderWidth": 0.7,
|
||||
"torso": 0.62,
|
||||
"walkSpeedScale": 0.75
|
||||
},
|
||||
"restOrientationErrors": []
|
||||
},
|
||||
"seams": {
|
||||
"coincidentGroups": 3697,
|
||||
"examples": [
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0649,
|
||||
0.0281,
|
||||
1.3797
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.066,
|
||||
0.0231,
|
||||
1.3767
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0694,
|
||||
0.0172,
|
||||
1.3783
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0649,
|
||||
0.0281,
|
||||
1.3797
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.066,
|
||||
0.0231,
|
||||
1.3767
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0694,
|
||||
0.0172,
|
||||
1.3783
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
0.0001,
|
||||
-0.0385,
|
||||
1.5529
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
-0.0001,
|
||||
-0.0385,
|
||||
1.5529
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 3e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
-0.0012,
|
||||
-0.0342,
|
||||
1.5629
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 3e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
-0.001,
|
||||
-0.0343,
|
||||
1.5628
|
||||
]
|
||||
}
|
||||
],
|
||||
"maxGap": 6.470887154236068e-06,
|
||||
"snappedVertices": 95
|
||||
},
|
||||
"sex": "female",
|
||||
"status": "warn",
|
||||
"stem": "child-female",
|
||||
"timestamp": 1793419297,
|
||||
"warnings": [
|
||||
"top1",
|
||||
"top2",
|
||||
"top3",
|
||||
"Cylinder",
|
||||
"panties",
|
||||
"panties-full",
|
||||
"panties2",
|
||||
"panties2-full",
|
||||
"panties3",
|
||||
"panties4",
|
||||
"panties5",
|
||||
"panties6",
|
||||
"panties7"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
# Conversion Report — child / female
|
||||
|
||||
- Stem: `child-female`
|
||||
- Status: **warn**
|
||||
- Profile hash: `dd6901d4220ae704`
|
||||
- Objects: 37
|
||||
- Warnings: 13
|
||||
- Fails: 0
|
||||
|
||||
## Info
|
||||
- adultArmature: `female`
|
||||
- ageArmature: `child-female`
|
||||
- animations: `{'scaledLocationCurves': 7028, 'appended': ['Armature|mixamo.com|Layer0.020', 'Armature|mixamo.com|Layer0.018', 'Armature|mixamo.com|Layer0.016', 'Armature|mixamo.com|Layer0.014'], 'aliased': {'swimming-fast': 'swimming'}, 'missing': [], 'stem': 'child-female'}`
|
||||
- outputBase: `edited-normal-child-female.blend`
|
||||
- slots: `bottom,face,feet,hair,top`
|
||||
- sourceBase: `edited-normal-female.blend`
|
||||
|
||||
## Seams
|
||||
- coincidentGroups: `3697`
|
||||
- snappedVertices: `95`
|
||||
- maxGap: `6.470887154236068e-06`
|
||||
- examples: `[{'position': [0.0649, 0.0281, 1.3797], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.066, 0.0231, 1.3767], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0694, 0.0172, 1.3783], 'gap': 1e-06, 'objects': ['Face']}, {'position': [-0.0649, 0.0281, 1.3797], 'gap': 1e-06, 'objects': ['Face']}, {'position': [-0.066, 0.0231, 1.3767], 'gap': 1e-06, 'objects': ['Face']}, {'position': [-0.0694, 0.0172, 1.3783], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0001, -0.0385, 1.5529], 'gap': 2e-06, 'objects': ['Hair2']}, {'position': [-0.0001, -0.0385, 1.5529], 'gap': 2e-06, 'objects': ['Hair2']}, {'position': [-0.0012, -0.0342, 1.5629], 'gap': 3e-06, 'objects': ['Hair2']}, {'position': [-0.001, -0.0343, 1.5628], 'gap': 3e-06, 'objects': ['Hair2']}]`
|
||||
|
||||
## Warnings (first 20)
|
||||
|
||||
- `top1`
|
||||
- `top2`
|
||||
- `top3`
|
||||
- `Cylinder`
|
||||
- `panties`
|
||||
- `panties-full`
|
||||
- `panties2`
|
||||
- `panties2-full`
|
||||
- `panties3`
|
||||
- `panties4`
|
||||
- `panties5`
|
||||
- `panties6`
|
||||
- `panties7`
|
||||
|
||||
## Objects
|
||||
|
||||
- `Accessory` — ok; verts=69, tris=0 degenerate, influences=4, unweighted=0, edge=0.50..0.79
|
||||
- `Body` — ok; verts=6578, tris=0 degenerate, influences=4, unweighted=0, edge=0.36..1.59
|
||||
- `Body-merged` — ok; verts=9178, tris=0 degenerate, influences=4, unweighted=0, edge=0.36..1.59
|
||||
- `BodyBottom` — ok; verts=1194, tris=0 degenerate, influences=4, unweighted=0, edge=0.54..0.79
|
||||
- `BodyFeet` — ok; verts=1050, tris=0 degenerate, influences=3, unweighted=0, edge=0.53..0.78
|
||||
- `BodyTop` — ok; verts=3354, tris=0 degenerate, influences=4, unweighted=0, edge=0.36..1.59
|
||||
- `BodyTopRobe` — ok; verts=4383, tris=0 degenerate, influences=4, unweighted=0, edge=0.36..1.59
|
||||
- `Body_shapes` — ok; verts=5550, tris=0 degenerate, influences=4, unweighted=0, edge=0.36..1.59
|
||||
- `Cylinder` — warn; verts=840, tris=0 degenerate, influences=0, unweighted=840, edge=0.37..55.53
|
||||
- `Face` — ok; verts=4109, tris=0 degenerate, influences=1, unweighted=0, edge=0.92..0.92
|
||||
- `Hair` — ok; verts=8, tris=0 degenerate, influences=1, unweighted=0, edge=0.92..0.92
|
||||
- `Hair001` — ok; verts=8673, tris=0 degenerate, influences=1, unweighted=0, edge=0.89..0.96
|
||||
- `Hair2` — ok; verts=8673, tris=0 degenerate, influences=2, unweighted=0, edge=0.89..0.96
|
||||
- `HairBack` — ok; verts=760, tris=0 degenerate, influences=4, unweighted=0, edge=0.80..1.24
|
||||
- `OnepieceCloth` — ok; verts=6, tris=0 degenerate, influences=3, unweighted=0, edge=0.77..0.78
|
||||
- `Shoes` — ok; verts=608, tris=0 degenerate, influences=3, unweighted=0, edge=0.53..0.77
|
||||
- `Tops` — ok; verts=1029, tris=0 degenerate, influences=4, unweighted=0, edge=0.44..0.87
|
||||
- `bra1` — ok; verts=419, tris=0 degenerate, influences=1, unweighted=0, edge=0.92..0.92
|
||||
- `bra2` — ok; verts=524, tris=0 degenerate, influences=1, unweighted=0, edge=0.92..0.92
|
||||
- `bra3` — ok; verts=450, tris=0 degenerate, influences=1, unweighted=0, edge=0.92..0.92
|
||||
- `bra4` — ok; verts=450, tris=0 degenerate, influences=1, unweighted=0, edge=0.92..0.92
|
||||
- `panties` — warn; verts=797, tris=0 degenerate, influences=0, unweighted=797, edge=0.20..211.10
|
||||
- `panties-full` — warn; verts=425, tris=0 degenerate, influences=0, unweighted=425, edge=0.21..7.32
|
||||
- `panties2` — warn; verts=1328, tris=0 degenerate, influences=0, unweighted=1328, edge=0.22..39.47
|
||||
- `panties2-full` — warn; verts=558, tris=0 degenerate, influences=0, unweighted=558, edge=0.10..36.99
|
||||
- `panties3` — warn; verts=820, tris=0 degenerate, influences=0, unweighted=820, edge=0.21..30.20
|
||||
- `panties4` — warn; verts=752, tris=0 degenerate, influences=0, unweighted=752, edge=0.20..31.68
|
||||
- `panties5` — warn; verts=758, tris=0 degenerate, influences=3, unweighted=0, edge=0.60..5.90
|
||||
- `panties6` — warn; verts=758, tris=0 degenerate, influences=3, unweighted=0, edge=0.63..7.88
|
||||
- `panties7` — warn; verts=758, tris=0 degenerate, influences=3, unweighted=0, edge=0.64..7.88
|
||||
- `shoes1` — ok; verts=414, tris=0 degenerate, influences=3, unweighted=0, edge=0.55..0.96
|
||||
- `skirt1` — ok; verts=154, tris=0 degenerate, influences=4, unweighted=0, edge=0.38..1.33
|
||||
- `skirt2` — ok; verts=176, tris=0 degenerate, influences=4, unweighted=0, edge=0.38..1.42
|
||||
- `skirt3` — ok; verts=154, tris=0 degenerate, influences=4, unweighted=0, edge=0.38..1.27
|
||||
- `top1` — warn; verts=852, tris=0 degenerate, influences=1, unweighted=0, edge=0.71..7.08
|
||||
- `top2` — warn; verts=854, tris=0 degenerate, influences=1, unweighted=0, edge=0.71..7.08
|
||||
- `top3` — warn; verts=854, tris=0 degenerate, influences=1, unweighted=0, edge=0.71..7.08
|
||||
@@ -0,0 +1,485 @@
|
||||
{
|
||||
"age": "child",
|
||||
"fails": [],
|
||||
"info": {
|
||||
"adultArmature": "male",
|
||||
"ageArmature": "child-male",
|
||||
"animations": {
|
||||
"aliased": {
|
||||
"swimming-fast": "swimming"
|
||||
},
|
||||
"appended": [
|
||||
"Armature|mixamo.com|Layer0.020",
|
||||
"Armature|mixamo.com|Layer0.018",
|
||||
"Armature|mixamo.com|Layer0.016",
|
||||
"Armature|mixamo.com|Layer0.014",
|
||||
"Armature|mixamo.com|Layer0.012"
|
||||
],
|
||||
"missing": [],
|
||||
"scaledLocationCurves": 7601,
|
||||
"stem": "child-male"
|
||||
},
|
||||
"outputBase": "edited-normal-child-male.blend",
|
||||
"slots": "bottom,face,feet,hair,top",
|
||||
"sourceBase": "edited-normal-male.blend"
|
||||
},
|
||||
"objects": {
|
||||
"Accessory": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.7935740735522747,
|
||||
"edgeRatioMin": 0.478987783805106,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 111,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 145
|
||||
},
|
||||
"Body": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.9536679047270835,
|
||||
"edgeRatioMin": 0.33440855736119546,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 6232,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 6584
|
||||
},
|
||||
"BodyBottom": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8216992678030084,
|
||||
"edgeRatioMin": 0.5410301282339429,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1228,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 1181
|
||||
},
|
||||
"BodyBottomPants": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.86670117423969,
|
||||
"edgeRatioMin": 0.5410301282339429,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 972,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 1133
|
||||
},
|
||||
"BodyFeet": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.7839642922675355,
|
||||
"edgeRatioMin": 0.5255821222248701,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 1274,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 1392
|
||||
},
|
||||
"BodyFeetPants": {
|
||||
"degenerateTris": 48,
|
||||
"edgeRatioMax": 0.7839642922675355,
|
||||
"edgeRatioMin": 0.513176300659891,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 1466,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1602
|
||||
},
|
||||
"BodyFeetPantsShoes": {
|
||||
"degenerateTris": 48,
|
||||
"edgeRatioMax": 0.7799605112083939,
|
||||
"edgeRatioMin": 0.513176300659891,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 830,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1048
|
||||
},
|
||||
"BodyTop": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.9536679047270835,
|
||||
"edgeRatioMin": 0.33440855736119546,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 3726,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 3373
|
||||
},
|
||||
"BodyTopRobe": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.8102305567029533,
|
||||
"edgeRatioMin": 0.26171116386651677,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 5384,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 5046
|
||||
},
|
||||
"Body_shapes": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.9536679047270835,
|
||||
"edgeRatioMin": 0.33440855736119546,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 6228,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 2,
|
||||
"unweighted": 0,
|
||||
"vertices": 5556
|
||||
},
|
||||
"Bottoms": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.86670117423969,
|
||||
"edgeRatioMin": 0.513176300659891,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 826,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 942
|
||||
},
|
||||
"Face": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9201631088272872,
|
||||
"edgeRatioMin": 0.9197675922571078,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 3839,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 57,
|
||||
"unweighted": 0,
|
||||
"vertices": 4045
|
||||
},
|
||||
"Hair": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9200000519776095,
|
||||
"edgeRatioMin": 0.9199996919846277,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 4,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 9
|
||||
},
|
||||
"Hair2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9421731351522475,
|
||||
"edgeRatioMin": 0.8873110924578558,
|
||||
"maxInfluences": 2,
|
||||
"note": "",
|
||||
"polygons": 4483,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 7022
|
||||
},
|
||||
"HairBack": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.2562402728849387,
|
||||
"edgeRatioMin": 0.7903516292481316,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 724,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 760
|
||||
},
|
||||
"Panties001": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.8213261947723751,
|
||||
"edgeRatioMin": 0.5459879769698383,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 604,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 582
|
||||
},
|
||||
"Pants0001": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0359715012588415,
|
||||
"edgeRatioMin": 0.5209577970985301,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 898,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 915
|
||||
},
|
||||
"Shoes": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.7799605112083939,
|
||||
"edgeRatioMin": 0.6458808184096261,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 334,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 478
|
||||
},
|
||||
"Tops": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.3836635660461891,
|
||||
"edgeRatioMin": 0.26171116386651677,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1658,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1896
|
||||
},
|
||||
"TopsEdited": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.3836635660461891,
|
||||
"edgeRatioMin": 0.26171116386651677,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1658,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1896
|
||||
},
|
||||
"coat": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 5.7431588938626055,
|
||||
"edgeRatioMin": 0.06297046287549611,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1658,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1896
|
||||
},
|
||||
"hair0001": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.6992910918861586,
|
||||
"edgeRatioMin": 0.8209691490768963,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 4483,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 7022
|
||||
},
|
||||
"hair0002": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.6992910918861586,
|
||||
"edgeRatioMin": 0.8209691490768963,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 4483,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 7022
|
||||
},
|
||||
"hair0003": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0,
|
||||
"edgeRatioMin": 1.0,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 4483,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 7022
|
||||
},
|
||||
"shoes1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.76872570692281,
|
||||
"edgeRatioMin": 0.6246053643865501,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 334,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 478
|
||||
}
|
||||
},
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"rig": {
|
||||
"adultBones": 242,
|
||||
"ageBones": 242,
|
||||
"profile": {
|
||||
"arm": 0.6,
|
||||
"bodyRadial": 0.78,
|
||||
"foot": 0.75,
|
||||
"head": 0.92,
|
||||
"headBulge": 1.03,
|
||||
"hipHeight": 0.55,
|
||||
"hipWidth": 0.72,
|
||||
"leg": 0.55,
|
||||
"limbRadial": 0.72,
|
||||
"shoulderWidth": 0.7,
|
||||
"torso": 0.62,
|
||||
"walkSpeedScale": 0.75
|
||||
},
|
||||
"restOrientationErrors": []
|
||||
},
|
||||
"seams": {
|
||||
"coincidentGroups": 5119,
|
||||
"examples": [
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"BodyTopRobe"
|
||||
],
|
||||
"position": [
|
||||
0.0,
|
||||
0.09,
|
||||
1.4972
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"BodyTopRobe"
|
||||
],
|
||||
"position": [
|
||||
0.0,
|
||||
0.0858,
|
||||
1.6039
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"BodyTopRobe"
|
||||
],
|
||||
"position": [
|
||||
0.0,
|
||||
-0.0184,
|
||||
1.5008
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0633,
|
||||
0.0369,
|
||||
1.6496
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0633,
|
||||
0.0369,
|
||||
1.6496
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0633,
|
||||
0.0344,
|
||||
1.6475
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0636,
|
||||
0.0319,
|
||||
1.6457
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0645,
|
||||
0.0294,
|
||||
1.6445
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0698,
|
||||
0.021,
|
||||
1.649
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0716,
|
||||
0.0206,
|
||||
1.6529
|
||||
]
|
||||
}
|
||||
],
|
||||
"maxGap": 7.33739356344359e-06,
|
||||
"snappedVertices": 216
|
||||
},
|
||||
"sex": "male",
|
||||
"status": "warn",
|
||||
"stem": "child-male",
|
||||
"timestamp": 1793419291,
|
||||
"warnings": [
|
||||
"Body",
|
||||
"Body_shapes",
|
||||
"BodyFeetPants",
|
||||
"BodyFeetPantsShoes",
|
||||
"BodyTop",
|
||||
"BodyTopRobe",
|
||||
"Tops",
|
||||
"TopsEdited",
|
||||
"coat"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
# Conversion Report — child / male
|
||||
|
||||
- Stem: `child-male`
|
||||
- Status: **warn**
|
||||
- Profile hash: `dd6901d4220ae704`
|
||||
- Objects: 25
|
||||
- Warnings: 9
|
||||
- Fails: 0
|
||||
|
||||
## Info
|
||||
- adultArmature: `male`
|
||||
- ageArmature: `child-male`
|
||||
- animations: `{'scaledLocationCurves': 7601, 'appended': ['Armature|mixamo.com|Layer0.020', 'Armature|mixamo.com|Layer0.018', 'Armature|mixamo.com|Layer0.016', 'Armature|mixamo.com|Layer0.014', 'Armature|mixamo.com|Layer0.012'], 'aliased': {'swimming-fast': 'swimming'}, 'missing': [], 'stem': 'child-male'}`
|
||||
- outputBase: `edited-normal-child-male.blend`
|
||||
- slots: `bottom,face,feet,hair,top`
|
||||
- sourceBase: `edited-normal-male.blend`
|
||||
|
||||
## Seams
|
||||
- coincidentGroups: `5119`
|
||||
- snappedVertices: `216`
|
||||
- maxGap: `7.33739356344359e-06`
|
||||
- examples: `[{'position': [0.0, 0.09, 1.4972], 'gap': 1e-06, 'objects': ['BodyTop', 'BodyTopRobe']}, {'position': [0.0, 0.0858, 1.6039], 'gap': 1e-06, 'objects': ['BodyTop', 'BodyTopRobe']}, {'position': [0.0, -0.0184, 1.5008], 'gap': 2e-06, 'objects': ['BodyTop', 'BodyTopRobe']}, {'position': [0.0633, 0.0369, 1.6496], 'gap': 2e-06, 'objects': ['BodyTop', 'Face']}, {'position': [-0.0633, 0.0369, 1.6496], 'gap': 2e-06, 'objects': ['BodyTop', 'Face']}, {'position': [0.0633, 0.0344, 1.6475], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0636, 0.0319, 1.6457], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0645, 0.0294, 1.6445], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0698, 0.021, 1.649], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0716, 0.0206, 1.6529], 'gap': 1e-06, 'objects': ['Face']}]`
|
||||
|
||||
## Warnings (first 20)
|
||||
|
||||
- `Body`
|
||||
- `Body_shapes`
|
||||
- `BodyFeetPants`
|
||||
- `BodyFeetPantsShoes`
|
||||
- `BodyTop`
|
||||
- `BodyTopRobe`
|
||||
- `Tops`
|
||||
- `TopsEdited`
|
||||
- `coat`
|
||||
|
||||
## Objects
|
||||
|
||||
- `Accessory` — ok; verts=145, tris=0 degenerate, influences=4, unweighted=0, edge=0.48..0.79
|
||||
- `Body` — warn; verts=6584, tris=0 degenerate, influences=4, unweighted=0, edge=0.33..1.95
|
||||
- `BodyBottom` — ok; verts=1181, tris=0 degenerate, influences=4, unweighted=0, edge=0.54..0.82
|
||||
- `BodyBottomPants` — ok; verts=1133, tris=0 degenerate, influences=4, unweighted=0, edge=0.54..0.87
|
||||
- `BodyFeet` — ok; verts=1392, tris=0 degenerate, influences=3, unweighted=0, edge=0.53..0.78
|
||||
- `BodyFeetPants` — warn; verts=1602, tris=48 degenerate, influences=3, unweighted=0, edge=0.51..0.78
|
||||
- `BodyFeetPantsShoes` — warn; verts=1048, tris=48 degenerate, influences=3, unweighted=0, edge=0.51..0.78
|
||||
- `BodyTop` — warn; verts=3373, tris=0 degenerate, influences=4, unweighted=0, edge=0.33..1.95
|
||||
- `BodyTopRobe` — warn; verts=5046, tris=0 degenerate, influences=4, unweighted=0, edge=0.26..1.81
|
||||
- `Body_shapes` — warn; verts=5556, tris=0 degenerate, influences=4, unweighted=0, edge=0.33..1.95
|
||||
- `Bottoms` — ok; verts=942, tris=0 degenerate, influences=4, unweighted=0, edge=0.51..0.87
|
||||
- `Face` — ok; verts=4045, tris=0 degenerate, influences=1, unweighted=0, edge=0.92..0.92
|
||||
- `Hair` — ok; verts=9, tris=0 degenerate, influences=1, unweighted=0, edge=0.92..0.92
|
||||
- `Hair2` — ok; verts=7022, tris=0 degenerate, influences=2, unweighted=0, edge=0.89..0.94
|
||||
- `HairBack` — ok; verts=760, tris=0 degenerate, influences=4, unweighted=0, edge=0.79..1.26
|
||||
- `Panties001` — ok; verts=582, tris=0 degenerate, influences=4, unweighted=0, edge=0.55..0.82
|
||||
- `Pants0001` — ok; verts=915, tris=0 degenerate, influences=4, unweighted=0, edge=0.52..1.04
|
||||
- `Shoes` — ok; verts=478, tris=0 degenerate, influences=3, unweighted=0, edge=0.65..0.78
|
||||
- `Tops` — warn; verts=1896, tris=0 degenerate, influences=4, unweighted=0, edge=0.26..1.38
|
||||
- `TopsEdited` — warn; verts=1896, tris=0 degenerate, influences=4, unweighted=0, edge=0.26..1.38
|
||||
- `coat` — warn; verts=1896, tris=0 degenerate, influences=4, unweighted=0, edge=0.06..5.74
|
||||
- `hair0001` — ok; verts=7022, tris=0 degenerate, influences=4, unweighted=0, edge=0.82..1.70
|
||||
- `hair0002` — ok; verts=7022, tris=0 degenerate, influences=4, unweighted=0, edge=0.82..1.70
|
||||
- `hair0003` — ok; verts=7022, tris=0 degenerate, influences=0, unweighted=0, edge=1.00..1.00
|
||||
- `shoes1` — ok; verts=478, tris=0 degenerate, influences=3, unweighted=0, edge=0.62..0.77
|
||||
@@ -0,0 +1,621 @@
|
||||
{
|
||||
"age": "elder",
|
||||
"fails": [],
|
||||
"info": {
|
||||
"adultArmature": "female",
|
||||
"ageArmature": "elder-female",
|
||||
"animations": {
|
||||
"aliased": {
|
||||
"swimming-fast": "swimming"
|
||||
},
|
||||
"appended": [
|
||||
"Armature|mixamo.com|Layer0.020",
|
||||
"Armature|mixamo.com|Layer0.018",
|
||||
"Armature|mixamo.com|Layer0.016",
|
||||
"Armature|mixamo.com|Layer0.014"
|
||||
],
|
||||
"missing": [],
|
||||
"scaledLocationCurves": 7028,
|
||||
"stem": "elder-female"
|
||||
},
|
||||
"outputBase": "edited-normal-elder-female.blend",
|
||||
"slots": "bottom,face,feet,hair,top",
|
||||
"sourceBase": "edited-normal-female.blend"
|
||||
},
|
||||
"objects": {
|
||||
"Accessory": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.000008309035069,
|
||||
"edgeRatioMin": 0.9999861617983601,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 40,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 69
|
||||
},
|
||||
"Body": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000602153963685,
|
||||
"edgeRatioMin": 0.9999297885300813,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 6227,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 6578
|
||||
},
|
||||
"Body-merged": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000602153963685,
|
||||
"edgeRatioMin": 0.9999297885300813,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 8423,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 9178
|
||||
},
|
||||
"BodyBottom": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000091614229163,
|
||||
"edgeRatioMin": 0.9999833439579573,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1242,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1194
|
||||
},
|
||||
"BodyFeet": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.000004257443259,
|
||||
"edgeRatioMin": 0.9999898988835334,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 1280,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1050
|
||||
},
|
||||
"BodyTop": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000602153963685,
|
||||
"edgeRatioMin": 0.9999297885300813,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 3701,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 3354
|
||||
},
|
||||
"BodyTopRobe": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000602153963685,
|
||||
"edgeRatioMin": 0.9999297885300813,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 4727,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 4383
|
||||
},
|
||||
"Body_shapes": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000602153963685,
|
||||
"edgeRatioMin": 0.9999297885300813,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 6223,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 2,
|
||||
"unweighted": 0,
|
||||
"vertices": 5550
|
||||
},
|
||||
"Cylinder": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000211541526272,
|
||||
"edgeRatioMin": 0.9999790604099301,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 595,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 840,
|
||||
"vertices": 840
|
||||
},
|
||||
"Face": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000190299901157,
|
||||
"edgeRatioMin": 0.9999818716880085,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 3887,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 57,
|
||||
"unweighted": 0,
|
||||
"vertices": 4109
|
||||
},
|
||||
"Hair": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0,
|
||||
"edgeRatioMin": 1.0,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 2,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 8
|
||||
},
|
||||
"Hair001": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.000108986292174,
|
||||
"edgeRatioMin": 0.999757672900018,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 5658,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 8673
|
||||
},
|
||||
"Hair2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000886276531582,
|
||||
"edgeRatioMin": 0.9982467837381087,
|
||||
"maxInfluences": 2,
|
||||
"note": "",
|
||||
"polygons": 5658,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 8673
|
||||
},
|
||||
"HairBack": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.000033289511109,
|
||||
"edgeRatioMin": 0.9999827306906097,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 726,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 760
|
||||
},
|
||||
"OnepieceCloth": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.00000327729584,
|
||||
"edgeRatioMin": 0.9999867384343708,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 2,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 6
|
||||
},
|
||||
"Shoes": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.000000954873743,
|
||||
"edgeRatioMin": 0.9999976654518632,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 402,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 608
|
||||
},
|
||||
"Tops": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.000009505554167,
|
||||
"edgeRatioMin": 0.9999842864583346,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1026,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1029
|
||||
},
|
||||
"bra1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000653723155617,
|
||||
"edgeRatioMin": 0.9999349455725334,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 430,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 419
|
||||
},
|
||||
"bra2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000515467022928,
|
||||
"edgeRatioMin": 0.9999485610532655,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 506,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 524
|
||||
},
|
||||
"bra3": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.000045760529744,
|
||||
"edgeRatioMin": 0.9999485610532655,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 410,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 450
|
||||
},
|
||||
"bra4": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.000045760529744,
|
||||
"edgeRatioMin": 0.9999485610532655,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 410,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 450
|
||||
},
|
||||
"panties": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000105452390367,
|
||||
"edgeRatioMin": 0.9999622354677439,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 674,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 797,
|
||||
"vertices": 797
|
||||
},
|
||||
"panties-full": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000048680725775,
|
||||
"edgeRatioMin": 0.9999761306308599,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 337,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 425,
|
||||
"vertices": 425
|
||||
},
|
||||
"panties2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000202864294472,
|
||||
"edgeRatioMin": 0.9999826178387231,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 1266,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 1328,
|
||||
"vertices": 1328
|
||||
},
|
||||
"panties2-full": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000121089841207,
|
||||
"edgeRatioMin": 0.9999863856203022,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 507,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 558,
|
||||
"vertices": 558
|
||||
},
|
||||
"panties3": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000139098043757,
|
||||
"edgeRatioMin": 0.9999866822085812,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 758,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 820,
|
||||
"vertices": 820
|
||||
},
|
||||
"panties4": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000139098043757,
|
||||
"edgeRatioMin": 0.9999866822085812,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 690,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 752,
|
||||
"vertices": 752
|
||||
},
|
||||
"panties5": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000461926176676,
|
||||
"edgeRatioMin": 0.9999441034118597,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 698,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 758
|
||||
},
|
||||
"panties6": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000277280519727,
|
||||
"edgeRatioMin": 0.9999767160991722,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 698,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 758
|
||||
},
|
||||
"panties7": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000385836476469,
|
||||
"edgeRatioMin": 0.9999691785976204,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 698,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 758
|
||||
},
|
||||
"shoes1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.000001217005389,
|
||||
"edgeRatioMin": 0.9999980533858904,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 414,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 414
|
||||
},
|
||||
"skirt1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.000003778236853,
|
||||
"edgeRatioMin": 0.9999955122967341,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 132,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 154
|
||||
},
|
||||
"skirt2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.000005985850434,
|
||||
"edgeRatioMin": 0.999995191044834,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 154,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 176
|
||||
},
|
||||
"skirt3": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000032530300955,
|
||||
"edgeRatioMin": 0.9999941815040422,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 132,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 154
|
||||
},
|
||||
"top1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000349169435994,
|
||||
"edgeRatioMin": 0.9999356995377789,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 860,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 852
|
||||
},
|
||||
"top2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000115228837376,
|
||||
"edgeRatioMin": 0.9999867173552025,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 876,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 854
|
||||
},
|
||||
"top3": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000115228837376,
|
||||
"edgeRatioMin": 0.9999867173552025,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 876,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 854
|
||||
}
|
||||
},
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"rig": {
|
||||
"adultBones": 252,
|
||||
"ageBones": 252,
|
||||
"profile": {
|
||||
"arm": 1.0,
|
||||
"bodyRadial": 1.0,
|
||||
"foot": 1.0,
|
||||
"head": 1.0,
|
||||
"headBulge": 1.0,
|
||||
"hipHeight": 1.0,
|
||||
"hipWidth": 1.0,
|
||||
"leg": 1.0,
|
||||
"limbRadial": 1.0,
|
||||
"shoulderWidth": 1.0,
|
||||
"torso": 1.0,
|
||||
"walkSpeedScale": 1.0
|
||||
},
|
||||
"restOrientationErrors": []
|
||||
},
|
||||
"seams": {
|
||||
"coincidentGroups": 3697,
|
||||
"examples": [
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0649,
|
||||
0.0281,
|
||||
1.3797
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.066,
|
||||
0.0231,
|
||||
1.3767
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0694,
|
||||
0.0172,
|
||||
1.3783
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0649,
|
||||
0.0281,
|
||||
1.3797
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.066,
|
||||
0.0231,
|
||||
1.3767
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0694,
|
||||
0.0172,
|
||||
1.3783
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
0.0001,
|
||||
-0.0385,
|
||||
1.5529
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
-0.0001,
|
||||
-0.0385,
|
||||
1.5529
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 3e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
-0.0012,
|
||||
-0.0342,
|
||||
1.5629
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 3e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
-0.001,
|
||||
-0.0343,
|
||||
1.5628
|
||||
]
|
||||
}
|
||||
],
|
||||
"maxGap": 7.0412131889888606e-06,
|
||||
"snappedVertices": 95
|
||||
},
|
||||
"sex": "female",
|
||||
"status": "warn",
|
||||
"stem": "elder-female",
|
||||
"timestamp": 1793419268,
|
||||
"warnings": [
|
||||
"Cylinder",
|
||||
"panties",
|
||||
"panties-full",
|
||||
"panties2",
|
||||
"panties2-full",
|
||||
"panties3",
|
||||
"panties4"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
# Conversion Report — elder / female
|
||||
|
||||
- Stem: `elder-female`
|
||||
- Status: **warn**
|
||||
- Profile hash: `dd6901d4220ae704`
|
||||
- Objects: 37
|
||||
- Warnings: 7
|
||||
- Fails: 0
|
||||
|
||||
## Info
|
||||
- adultArmature: `female`
|
||||
- ageArmature: `elder-female`
|
||||
- animations: `{'scaledLocationCurves': 7028, 'appended': ['Armature|mixamo.com|Layer0.020', 'Armature|mixamo.com|Layer0.018', 'Armature|mixamo.com|Layer0.016', 'Armature|mixamo.com|Layer0.014'], 'aliased': {'swimming-fast': 'swimming'}, 'missing': [], 'stem': 'elder-female'}`
|
||||
- outputBase: `edited-normal-elder-female.blend`
|
||||
- slots: `bottom,face,feet,hair,top`
|
||||
- sourceBase: `edited-normal-female.blend`
|
||||
|
||||
## Seams
|
||||
- coincidentGroups: `3697`
|
||||
- snappedVertices: `95`
|
||||
- maxGap: `7.0412131889888606e-06`
|
||||
- examples: `[{'position': [0.0649, 0.0281, 1.3797], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.066, 0.0231, 1.3767], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0694, 0.0172, 1.3783], 'gap': 1e-06, 'objects': ['Face']}, {'position': [-0.0649, 0.0281, 1.3797], 'gap': 1e-06, 'objects': ['Face']}, {'position': [-0.066, 0.0231, 1.3767], 'gap': 1e-06, 'objects': ['Face']}, {'position': [-0.0694, 0.0172, 1.3783], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0001, -0.0385, 1.5529], 'gap': 2e-06, 'objects': ['Hair2']}, {'position': [-0.0001, -0.0385, 1.5529], 'gap': 2e-06, 'objects': ['Hair2']}, {'position': [-0.0012, -0.0342, 1.5629], 'gap': 3e-06, 'objects': ['Hair2']}, {'position': [-0.001, -0.0343, 1.5628], 'gap': 3e-06, 'objects': ['Hair2']}]`
|
||||
|
||||
## Warnings (first 20)
|
||||
|
||||
- `Cylinder`
|
||||
- `panties`
|
||||
- `panties-full`
|
||||
- `panties2`
|
||||
- `panties2-full`
|
||||
- `panties3`
|
||||
- `panties4`
|
||||
|
||||
## Objects
|
||||
|
||||
- `Accessory` — ok; verts=69, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `Body` — ok; verts=6578, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `Body-merged` — ok; verts=9178, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `BodyBottom` — ok; verts=1194, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `BodyFeet` — ok; verts=1050, tris=0 degenerate, influences=3, unweighted=0, edge=1.00..1.00
|
||||
- `BodyTop` — ok; verts=3354, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `BodyTopRobe` — ok; verts=4383, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `Body_shapes` — ok; verts=5550, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `Cylinder` — warn; verts=840, tris=0 degenerate, influences=0, unweighted=840, edge=1.00..1.00
|
||||
- `Face` — ok; verts=4109, tris=0 degenerate, influences=1, unweighted=0, edge=1.00..1.00
|
||||
- `Hair` — ok; verts=8, tris=0 degenerate, influences=1, unweighted=0, edge=1.00..1.00
|
||||
- `Hair001` — ok; verts=8673, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `Hair2` — ok; verts=8673, tris=0 degenerate, influences=2, unweighted=0, edge=1.00..1.00
|
||||
- `HairBack` — ok; verts=760, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `OnepieceCloth` — ok; verts=6, tris=0 degenerate, influences=3, unweighted=0, edge=1.00..1.00
|
||||
- `Shoes` — ok; verts=608, tris=0 degenerate, influences=3, unweighted=0, edge=1.00..1.00
|
||||
- `Tops` — ok; verts=1029, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `bra1` — ok; verts=419, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `bra2` — ok; verts=524, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `bra3` — ok; verts=450, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `bra4` — ok; verts=450, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `panties` — warn; verts=797, tris=0 degenerate, influences=0, unweighted=797, edge=1.00..1.00
|
||||
- `panties-full` — warn; verts=425, tris=0 degenerate, influences=0, unweighted=425, edge=1.00..1.00
|
||||
- `panties2` — warn; verts=1328, tris=0 degenerate, influences=0, unweighted=1328, edge=1.00..1.00
|
||||
- `panties2-full` — warn; verts=558, tris=0 degenerate, influences=0, unweighted=558, edge=1.00..1.00
|
||||
- `panties3` — warn; verts=820, tris=0 degenerate, influences=0, unweighted=820, edge=1.00..1.00
|
||||
- `panties4` — warn; verts=752, tris=0 degenerate, influences=0, unweighted=752, edge=1.00..1.00
|
||||
- `panties5` — ok; verts=758, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `panties6` — ok; verts=758, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `panties7` — ok; verts=758, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `shoes1` — ok; verts=414, tris=0 degenerate, influences=3, unweighted=0, edge=1.00..1.00
|
||||
- `skirt1` — ok; verts=154, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `skirt2` — ok; verts=176, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `skirt3` — ok; verts=154, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `top1` — ok; verts=852, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `top2` — ok; verts=854, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `top3` — ok; verts=854, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
@@ -0,0 +1,482 @@
|
||||
{
|
||||
"age": "elder",
|
||||
"fails": [],
|
||||
"info": {
|
||||
"adultArmature": "male",
|
||||
"ageArmature": "elder-male",
|
||||
"animations": {
|
||||
"aliased": {
|
||||
"swimming-fast": "swimming"
|
||||
},
|
||||
"appended": [
|
||||
"Armature|mixamo.com|Layer0.020",
|
||||
"Armature|mixamo.com|Layer0.018",
|
||||
"Armature|mixamo.com|Layer0.016",
|
||||
"Armature|mixamo.com|Layer0.014",
|
||||
"Armature|mixamo.com|Layer0.012"
|
||||
],
|
||||
"missing": [],
|
||||
"scaledLocationCurves": 7601,
|
||||
"stem": "elder-male"
|
||||
},
|
||||
"outputBase": "edited-normal-elder-male.blend",
|
||||
"slots": "bottom,face,feet,hair,top",
|
||||
"sourceBase": "edited-normal-male.blend"
|
||||
},
|
||||
"objects": {
|
||||
"Accessory": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.000019766352531,
|
||||
"edgeRatioMin": 0.9999805255229,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 111,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 145
|
||||
},
|
||||
"Body": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000897554304082,
|
||||
"edgeRatioMin": 0.9999611641687194,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 6232,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 6584
|
||||
},
|
||||
"BodyBottom": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000079995934277,
|
||||
"edgeRatioMin": 0.999989253410586,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1228,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 1181
|
||||
},
|
||||
"BodyBottomPants": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000258231043282,
|
||||
"edgeRatioMin": 0.9999859573234091,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 972,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 1133
|
||||
},
|
||||
"BodyFeet": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000046342516928,
|
||||
"edgeRatioMin": 0.9999981174339364,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 1274,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 1392
|
||||
},
|
||||
"BodyFeetPants": {
|
||||
"degenerateTris": 48,
|
||||
"edgeRatioMax": 1.0000046342516928,
|
||||
"edgeRatioMin": 0.9999981174339364,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 1466,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1602
|
||||
},
|
||||
"BodyFeetPantsShoes": {
|
||||
"degenerateTris": 48,
|
||||
"edgeRatioMax": 1.0000013400794516,
|
||||
"edgeRatioMin": 0.9999981174339364,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 830,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1048
|
||||
},
|
||||
"BodyTop": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000897554304082,
|
||||
"edgeRatioMin": 0.9999611641687194,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 3726,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 3373
|
||||
},
|
||||
"BodyTopRobe": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000897554304082,
|
||||
"edgeRatioMin": 0.9999611641687194,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 5384,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 5046
|
||||
},
|
||||
"Body_shapes": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000897554304082,
|
||||
"edgeRatioMin": 0.9999611641687194,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 6228,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 2,
|
||||
"unweighted": 0,
|
||||
"vertices": 5556
|
||||
},
|
||||
"Bottoms": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000258231043282,
|
||||
"edgeRatioMin": 0.9999859573234091,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 826,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 942
|
||||
},
|
||||
"Face": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000018813696014,
|
||||
"edgeRatioMin": 0.9999982328372087,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 3839,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 57,
|
||||
"unweighted": 0,
|
||||
"vertices": 4045
|
||||
},
|
||||
"Hair": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0,
|
||||
"edgeRatioMin": 0.9999997540155123,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 4,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 9
|
||||
},
|
||||
"Hair2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.000542889561435,
|
||||
"edgeRatioMin": 0.999194851963821,
|
||||
"maxInfluences": 2,
|
||||
"note": "",
|
||||
"polygons": 4483,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 7022
|
||||
},
|
||||
"HairBack": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000295096813758,
|
||||
"edgeRatioMin": 0.9999786138678867,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 724,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 760
|
||||
},
|
||||
"Panties001": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0010504381569287,
|
||||
"edgeRatioMin": 0.9991726553447599,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 604,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 582
|
||||
},
|
||||
"Pants0001": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000229696457892,
|
||||
"edgeRatioMin": 0.9999874704829604,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 898,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 915
|
||||
},
|
||||
"Shoes": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000013174267397,
|
||||
"edgeRatioMin": 0.9999985866462575,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 334,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 478
|
||||
},
|
||||
"Tops": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.00000962524978,
|
||||
"edgeRatioMin": 0.9999900806166673,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1658,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1896
|
||||
},
|
||||
"TopsEdited": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.00000962524978,
|
||||
"edgeRatioMin": 0.9999900806166673,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1658,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1896
|
||||
},
|
||||
"coat": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.000022626000937,
|
||||
"edgeRatioMin": 0.9999831391448877,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1658,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1896
|
||||
},
|
||||
"hair0001": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000358941381666,
|
||||
"edgeRatioMin": 0.99983153357466,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 4483,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 7022
|
||||
},
|
||||
"hair0002": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000358941381666,
|
||||
"edgeRatioMin": 0.99983153357466,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 4483,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 7022
|
||||
},
|
||||
"hair0003": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0,
|
||||
"edgeRatioMin": 1.0,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 4483,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 7022
|
||||
},
|
||||
"shoes1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0000013174267397,
|
||||
"edgeRatioMin": 0.9999985251538689,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 334,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 478
|
||||
}
|
||||
},
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"rig": {
|
||||
"adultBones": 242,
|
||||
"ageBones": 242,
|
||||
"profile": {
|
||||
"arm": 1.0,
|
||||
"bodyRadial": 1.0,
|
||||
"foot": 1.0,
|
||||
"head": 1.0,
|
||||
"headBulge": 1.0,
|
||||
"hipHeight": 1.0,
|
||||
"hipWidth": 1.0,
|
||||
"leg": 1.0,
|
||||
"limbRadial": 1.0,
|
||||
"shoulderWidth": 1.0,
|
||||
"torso": 1.0,
|
||||
"walkSpeedScale": 1.0
|
||||
},
|
||||
"restOrientationErrors": []
|
||||
},
|
||||
"seams": {
|
||||
"coincidentGroups": 5119,
|
||||
"examples": [
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"BodyTopRobe"
|
||||
],
|
||||
"position": [
|
||||
0.0,
|
||||
0.09,
|
||||
1.4972
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"BodyTopRobe"
|
||||
],
|
||||
"position": [
|
||||
0.0,
|
||||
0.0858,
|
||||
1.6039
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"BodyTopRobe"
|
||||
],
|
||||
"position": [
|
||||
0.0,
|
||||
-0.0184,
|
||||
1.5008
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0633,
|
||||
0.0369,
|
||||
1.6496
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0633,
|
||||
0.0369,
|
||||
1.6496
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"BodyTopRobe",
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0842,
|
||||
0.0277,
|
||||
1.6955
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"BodyTopRobe",
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0842,
|
||||
0.0277,
|
||||
1.6955
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0633,
|
||||
0.0344,
|
||||
1.6475
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0636,
|
||||
0.0319,
|
||||
1.6457
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0645,
|
||||
0.0294,
|
||||
1.6445
|
||||
]
|
||||
}
|
||||
],
|
||||
"maxGap": 8.047489926985063e-06,
|
||||
"snappedVertices": 226
|
||||
},
|
||||
"sex": "male",
|
||||
"status": "warn",
|
||||
"stem": "elder-male",
|
||||
"timestamp": 1793419262,
|
||||
"warnings": [
|
||||
"BodyFeetPants",
|
||||
"BodyFeetPantsShoes"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
# Conversion Report — elder / male
|
||||
|
||||
- Stem: `elder-male`
|
||||
- Status: **warn**
|
||||
- Profile hash: `dd6901d4220ae704`
|
||||
- Objects: 25
|
||||
- Warnings: 2
|
||||
- Fails: 0
|
||||
|
||||
## Info
|
||||
- adultArmature: `male`
|
||||
- ageArmature: `elder-male`
|
||||
- animations: `{'scaledLocationCurves': 7601, 'appended': ['Armature|mixamo.com|Layer0.020', 'Armature|mixamo.com|Layer0.018', 'Armature|mixamo.com|Layer0.016', 'Armature|mixamo.com|Layer0.014', 'Armature|mixamo.com|Layer0.012'], 'aliased': {'swimming-fast': 'swimming'}, 'missing': [], 'stem': 'elder-male'}`
|
||||
- outputBase: `edited-normal-elder-male.blend`
|
||||
- slots: `bottom,face,feet,hair,top`
|
||||
- sourceBase: `edited-normal-male.blend`
|
||||
|
||||
## Seams
|
||||
- coincidentGroups: `5119`
|
||||
- snappedVertices: `226`
|
||||
- maxGap: `8.047489926985063e-06`
|
||||
- examples: `[{'position': [0.0, 0.09, 1.4972], 'gap': 2e-06, 'objects': ['BodyTop', 'BodyTopRobe']}, {'position': [0.0, 0.0858, 1.6039], 'gap': 1e-06, 'objects': ['BodyTop', 'BodyTopRobe']}, {'position': [0.0, -0.0184, 1.5008], 'gap': 2e-06, 'objects': ['BodyTop', 'BodyTopRobe']}, {'position': [0.0633, 0.0369, 1.6496], 'gap': 2e-06, 'objects': ['BodyTop', 'Face']}, {'position': [-0.0633, 0.0369, 1.6496], 'gap': 2e-06, 'objects': ['BodyTop', 'Face']}, {'position': [0.0842, 0.0277, 1.6955], 'gap': 1e-06, 'objects': ['BodyTop', 'BodyTopRobe', 'Face']}, {'position': [-0.0842, 0.0277, 1.6955], 'gap': 1e-06, 'objects': ['BodyTop', 'BodyTopRobe', 'Face']}, {'position': [0.0633, 0.0344, 1.6475], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0636, 0.0319, 1.6457], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0645, 0.0294, 1.6445], 'gap': 1e-06, 'objects': ['Face']}]`
|
||||
|
||||
## Warnings (first 20)
|
||||
|
||||
- `BodyFeetPants`
|
||||
- `BodyFeetPantsShoes`
|
||||
|
||||
## Objects
|
||||
|
||||
- `Accessory` — ok; verts=145, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `Body` — ok; verts=6584, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `BodyBottom` — ok; verts=1181, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `BodyBottomPants` — ok; verts=1133, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `BodyFeet` — ok; verts=1392, tris=0 degenerate, influences=3, unweighted=0, edge=1.00..1.00
|
||||
- `BodyFeetPants` — warn; verts=1602, tris=48 degenerate, influences=3, unweighted=0, edge=1.00..1.00
|
||||
- `BodyFeetPantsShoes` — warn; verts=1048, tris=48 degenerate, influences=3, unweighted=0, edge=1.00..1.00
|
||||
- `BodyTop` — ok; verts=3373, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `BodyTopRobe` — ok; verts=5046, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `Body_shapes` — ok; verts=5556, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `Bottoms` — ok; verts=942, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `Face` — ok; verts=4045, tris=0 degenerate, influences=1, unweighted=0, edge=1.00..1.00
|
||||
- `Hair` — ok; verts=9, tris=0 degenerate, influences=1, unweighted=0, edge=1.00..1.00
|
||||
- `Hair2` — ok; verts=7022, tris=0 degenerate, influences=2, unweighted=0, edge=1.00..1.00
|
||||
- `HairBack` — ok; verts=760, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `Panties001` — ok; verts=582, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `Pants0001` — ok; verts=915, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `Shoes` — ok; verts=478, tris=0 degenerate, influences=3, unweighted=0, edge=1.00..1.00
|
||||
- `Tops` — ok; verts=1896, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `TopsEdited` — ok; verts=1896, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `coat` — ok; verts=1896, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `hair0001` — ok; verts=7022, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `hair0002` — ok; verts=7022, tris=0 degenerate, influences=4, unweighted=0, edge=1.00..1.00
|
||||
- `hair0003` — ok; verts=7022, tris=0 degenerate, influences=0, unweighted=0, edge=1.00..1.00
|
||||
- `shoes1` — ok; verts=478, tris=0 degenerate, influences=3, unweighted=0, edge=1.00..1.00
|
||||
@@ -0,0 +1,622 @@
|
||||
{
|
||||
"age": "teenager",
|
||||
"fails": [],
|
||||
"info": {
|
||||
"adultArmature": "female",
|
||||
"ageArmature": "teenager-female",
|
||||
"animations": {
|
||||
"aliased": {
|
||||
"swimming-fast": "swimming"
|
||||
},
|
||||
"appended": [
|
||||
"Armature|mixamo.com|Layer0.020",
|
||||
"Armature|mixamo.com|Layer0.018",
|
||||
"Armature|mixamo.com|Layer0.016",
|
||||
"Armature|mixamo.com|Layer0.014"
|
||||
],
|
||||
"missing": [],
|
||||
"scaledLocationCurves": 7028,
|
||||
"stem": "teenager-female"
|
||||
},
|
||||
"outputBase": "edited-normal-teenager-female.blend",
|
||||
"slots": "bottom,face,feet,hair,top",
|
||||
"sourceBase": "edited-normal-female.blend"
|
||||
},
|
||||
"objects": {
|
||||
"Accessory": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9428835642440181,
|
||||
"edgeRatioMin": 0.8937664900167279,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 40,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 69
|
||||
},
|
||||
"Body": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.153864673534735,
|
||||
"edgeRatioMin": 0.8788689898808826,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 6227,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 6578
|
||||
},
|
||||
"Body-merged": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.153864673534735,
|
||||
"edgeRatioMin": 0.875585733388662,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 8423,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 9178
|
||||
},
|
||||
"BodyBottom": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9413767566501527,
|
||||
"edgeRatioMin": 0.894824475491785,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1242,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1194
|
||||
},
|
||||
"BodyFeet": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.01533051589042,
|
||||
"edgeRatioMin": 0.8905319834891143,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 1280,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1050
|
||||
},
|
||||
"BodyTop": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.153864673534735,
|
||||
"edgeRatioMin": 0.8788684699247973,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 3701,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 3354
|
||||
},
|
||||
"BodyTopRobe": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.153864673534735,
|
||||
"edgeRatioMin": 0.875585733388662,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 4727,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 4383
|
||||
},
|
||||
"Body_shapes": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.153864673534735,
|
||||
"edgeRatioMin": 0.8788689898808826,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 6223,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 2,
|
||||
"unweighted": 0,
|
||||
"vertices": 5550
|
||||
},
|
||||
"Cylinder": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 13.149501164154342,
|
||||
"edgeRatioMin": 0.2067502446063823,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 595,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 840,
|
||||
"vertices": 840
|
||||
},
|
||||
"Face": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9900810993796347,
|
||||
"edgeRatioMin": 0.9896786488081534,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 3887,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 57,
|
||||
"unweighted": 0,
|
||||
"vertices": 4109
|
||||
},
|
||||
"Hair": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9900007559563859,
|
||||
"edgeRatioMin": 0.9899997124609613,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 2,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 8
|
||||
},
|
||||
"Hair001": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.3437205849900313,
|
||||
"edgeRatioMin": 0.39954414397197524,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 5658,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 8673
|
||||
},
|
||||
"Hair2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9961437951038233,
|
||||
"edgeRatioMin": 0.9739232308631328,
|
||||
"maxInfluences": 2,
|
||||
"note": "",
|
||||
"polygons": 5658,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 8673
|
||||
},
|
||||
"HairBack": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0706959837595011,
|
||||
"edgeRatioMin": 0.9495467764182186,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 726,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 760
|
||||
},
|
||||
"OnepieceCloth": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9399357283296659,
|
||||
"edgeRatioMin": 0.9381366728208823,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 2,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 6
|
||||
},
|
||||
"Shoes": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9981897700090412,
|
||||
"edgeRatioMin": 0.9254076761648661,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 402,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 608
|
||||
},
|
||||
"Tops": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9511826460744913,
|
||||
"edgeRatioMin": 0.875585733388662,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1026,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1029
|
||||
},
|
||||
"bra1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9900000946583577,
|
||||
"edgeRatioMin": 0.2887434501951809,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 430,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 419
|
||||
},
|
||||
"bra2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9984894682233404,
|
||||
"edgeRatioMin": 0.4006806605346238,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 506,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 524
|
||||
},
|
||||
"bra3": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9984894682233404,
|
||||
"edgeRatioMin": 0.4889485839438301,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 410,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 450
|
||||
},
|
||||
"bra4": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9984894682233404,
|
||||
"edgeRatioMin": 0.4889485839438301,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 410,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 450
|
||||
},
|
||||
"panties": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 50.21989080149669,
|
||||
"edgeRatioMin": 0.19000501701728584,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 674,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 797,
|
||||
"vertices": 797
|
||||
},
|
||||
"panties-full": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.3300942912650158,
|
||||
"edgeRatioMin": 0.12847562281583486,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 337,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 425,
|
||||
"vertices": 425
|
||||
},
|
||||
"panties2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 10.548253137802863,
|
||||
"edgeRatioMin": 0.3920225273569097,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 1266,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 1328,
|
||||
"vertices": 1328
|
||||
},
|
||||
"panties2-full": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 9.15854640358302,
|
||||
"edgeRatioMin": 0.6364910348952761,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 507,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 558,
|
||||
"vertices": 558
|
||||
},
|
||||
"panties3": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 7.299860127337894,
|
||||
"edgeRatioMin": 0.13403860939637366,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 758,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 820,
|
||||
"vertices": 820
|
||||
},
|
||||
"panties4": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 7.740842499445837,
|
||||
"edgeRatioMin": 0.1158857342919668,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 690,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 752,
|
||||
"vertices": 752
|
||||
},
|
||||
"panties5": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9443143846802673,
|
||||
"edgeRatioMin": 0.8965532928515044,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 698,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 758
|
||||
},
|
||||
"panties6": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.95104115229466,
|
||||
"edgeRatioMin": 0.8819173360530367,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 698,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 758
|
||||
},
|
||||
"panties7": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9455920513906987,
|
||||
"edgeRatioMin": 0.8823391488934409,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 698,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 758
|
||||
},
|
||||
"shoes1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9807207937208285,
|
||||
"edgeRatioMin": 0.9278301779320385,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 414,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 414
|
||||
},
|
||||
"skirt1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9422412937744205,
|
||||
"edgeRatioMin": 0.8944357351478923,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 132,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 154
|
||||
},
|
||||
"skirt2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9422412937744205,
|
||||
"edgeRatioMin": 0.8944357351478923,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 154,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 176
|
||||
},
|
||||
"skirt3": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9444972809207006,
|
||||
"edgeRatioMin": 0.9023806027952785,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 132,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 154
|
||||
},
|
||||
"top1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0484285122626043,
|
||||
"edgeRatioMin": 0.5756344323442318,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 860,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 852
|
||||
},
|
||||
"top2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0484285122626043,
|
||||
"edgeRatioMin": 0.5756344323442318,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 876,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 854
|
||||
},
|
||||
"top3": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0484285122626043,
|
||||
"edgeRatioMin": 0.5756344323442318,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 876,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 854
|
||||
}
|
||||
},
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"rig": {
|
||||
"adultBones": 252,
|
||||
"ageBones": 252,
|
||||
"profile": {
|
||||
"arm": 0.92,
|
||||
"bodyRadial": 0.94,
|
||||
"foot": 0.97,
|
||||
"head": 0.99,
|
||||
"headBulge": 1.0,
|
||||
"hipHeight": 0.94,
|
||||
"hipWidth": 0.92,
|
||||
"leg": 0.94,
|
||||
"limbRadial": 0.93,
|
||||
"shoulderWidth": 0.92,
|
||||
"torso": 0.92,
|
||||
"walkSpeedScale": 0.95
|
||||
},
|
||||
"restOrientationErrors": []
|
||||
},
|
||||
"seams": {
|
||||
"coincidentGroups": 3697,
|
||||
"examples": [
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0649,
|
||||
0.0281,
|
||||
1.3797
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.066,
|
||||
0.0231,
|
||||
1.3767
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0694,
|
||||
0.0172,
|
||||
1.3783
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0649,
|
||||
0.0281,
|
||||
1.3797
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.066,
|
||||
0.0231,
|
||||
1.3767
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0694,
|
||||
0.0172,
|
||||
1.3783
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
0.0001,
|
||||
-0.0385,
|
||||
1.5529
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
-0.0001,
|
||||
-0.0385,
|
||||
1.5529
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 3e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
-0.0012,
|
||||
-0.0342,
|
||||
1.5629
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 3e-06,
|
||||
"objects": [
|
||||
"Hair2"
|
||||
],
|
||||
"position": [
|
||||
-0.001,
|
||||
-0.0343,
|
||||
1.5628
|
||||
]
|
||||
}
|
||||
],
|
||||
"maxGap": 6.944388526471647e-06,
|
||||
"snappedVertices": 95
|
||||
},
|
||||
"sex": "female",
|
||||
"status": "warn",
|
||||
"stem": "teenager-female",
|
||||
"timestamp": 1793419282,
|
||||
"warnings": [
|
||||
"bra1",
|
||||
"Cylinder",
|
||||
"panties",
|
||||
"panties-full",
|
||||
"panties2",
|
||||
"panties2-full",
|
||||
"panties3",
|
||||
"panties4"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
# Conversion Report — teenager / female
|
||||
|
||||
- Stem: `teenager-female`
|
||||
- Status: **warn**
|
||||
- Profile hash: `dd6901d4220ae704`
|
||||
- Objects: 37
|
||||
- Warnings: 8
|
||||
- Fails: 0
|
||||
|
||||
## Info
|
||||
- adultArmature: `female`
|
||||
- ageArmature: `teenager-female`
|
||||
- animations: `{'scaledLocationCurves': 7028, 'appended': ['Armature|mixamo.com|Layer0.020', 'Armature|mixamo.com|Layer0.018', 'Armature|mixamo.com|Layer0.016', 'Armature|mixamo.com|Layer0.014'], 'aliased': {'swimming-fast': 'swimming'}, 'missing': [], 'stem': 'teenager-female'}`
|
||||
- outputBase: `edited-normal-teenager-female.blend`
|
||||
- slots: `bottom,face,feet,hair,top`
|
||||
- sourceBase: `edited-normal-female.blend`
|
||||
|
||||
## Seams
|
||||
- coincidentGroups: `3697`
|
||||
- snappedVertices: `95`
|
||||
- maxGap: `6.944388526471647e-06`
|
||||
- examples: `[{'position': [0.0649, 0.0281, 1.3797], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.066, 0.0231, 1.3767], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0694, 0.0172, 1.3783], 'gap': 1e-06, 'objects': ['Face']}, {'position': [-0.0649, 0.0281, 1.3797], 'gap': 1e-06, 'objects': ['Face']}, {'position': [-0.066, 0.0231, 1.3767], 'gap': 1e-06, 'objects': ['Face']}, {'position': [-0.0694, 0.0172, 1.3783], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0001, -0.0385, 1.5529], 'gap': 2e-06, 'objects': ['Hair2']}, {'position': [-0.0001, -0.0385, 1.5529], 'gap': 2e-06, 'objects': ['Hair2']}, {'position': [-0.0012, -0.0342, 1.5629], 'gap': 3e-06, 'objects': ['Hair2']}, {'position': [-0.001, -0.0343, 1.5628], 'gap': 3e-06, 'objects': ['Hair2']}]`
|
||||
|
||||
## Warnings (first 20)
|
||||
|
||||
- `bra1`
|
||||
- `Cylinder`
|
||||
- `panties`
|
||||
- `panties-full`
|
||||
- `panties2`
|
||||
- `panties2-full`
|
||||
- `panties3`
|
||||
- `panties4`
|
||||
|
||||
## Objects
|
||||
|
||||
- `Accessory` — ok; verts=69, tris=0 degenerate, influences=4, unweighted=0, edge=0.89..0.94
|
||||
- `Body` — ok; verts=6578, tris=0 degenerate, influences=4, unweighted=0, edge=0.88..1.15
|
||||
- `Body-merged` — ok; verts=9178, tris=0 degenerate, influences=4, unweighted=0, edge=0.88..1.15
|
||||
- `BodyBottom` — ok; verts=1194, tris=0 degenerate, influences=4, unweighted=0, edge=0.89..0.94
|
||||
- `BodyFeet` — ok; verts=1050, tris=0 degenerate, influences=3, unweighted=0, edge=0.89..1.02
|
||||
- `BodyTop` — ok; verts=3354, tris=0 degenerate, influences=4, unweighted=0, edge=0.88..1.15
|
||||
- `BodyTopRobe` — ok; verts=4383, tris=0 degenerate, influences=4, unweighted=0, edge=0.88..1.15
|
||||
- `Body_shapes` — ok; verts=5550, tris=0 degenerate, influences=4, unweighted=0, edge=0.88..1.15
|
||||
- `Cylinder` — warn; verts=840, tris=0 degenerate, influences=0, unweighted=840, edge=0.21..13.15
|
||||
- `Face` — ok; verts=4109, tris=0 degenerate, influences=1, unweighted=0, edge=0.99..0.99
|
||||
- `Hair` — ok; verts=8, tris=0 degenerate, influences=1, unweighted=0, edge=0.99..0.99
|
||||
- `Hair001` — ok; verts=8673, tris=0 degenerate, influences=4, unweighted=0, edge=0.40..1.34
|
||||
- `Hair2` — ok; verts=8673, tris=0 degenerate, influences=2, unweighted=0, edge=0.97..1.00
|
||||
- `HairBack` — ok; verts=760, tris=0 degenerate, influences=4, unweighted=0, edge=0.95..1.07
|
||||
- `OnepieceCloth` — ok; verts=6, tris=0 degenerate, influences=3, unweighted=0, edge=0.94..0.94
|
||||
- `Shoes` — ok; verts=608, tris=0 degenerate, influences=3, unweighted=0, edge=0.93..1.00
|
||||
- `Tops` — ok; verts=1029, tris=0 degenerate, influences=4, unweighted=0, edge=0.88..0.95
|
||||
- `bra1` — warn; verts=419, tris=0 degenerate, influences=4, unweighted=0, edge=0.29..0.99
|
||||
- `bra2` — ok; verts=524, tris=0 degenerate, influences=4, unweighted=0, edge=0.40..1.00
|
||||
- `bra3` — ok; verts=450, tris=0 degenerate, influences=4, unweighted=0, edge=0.49..1.00
|
||||
- `bra4` — ok; verts=450, tris=0 degenerate, influences=4, unweighted=0, edge=0.49..1.00
|
||||
- `panties` — warn; verts=797, tris=0 degenerate, influences=0, unweighted=797, edge=0.19..50.22
|
||||
- `panties-full` — warn; verts=425, tris=0 degenerate, influences=0, unweighted=425, edge=0.13..1.33
|
||||
- `panties2` — warn; verts=1328, tris=0 degenerate, influences=0, unweighted=1328, edge=0.39..10.55
|
||||
- `panties2-full` — warn; verts=558, tris=0 degenerate, influences=0, unweighted=558, edge=0.64..9.16
|
||||
- `panties3` — warn; verts=820, tris=0 degenerate, influences=0, unweighted=820, edge=0.13..7.30
|
||||
- `panties4` — warn; verts=752, tris=0 degenerate, influences=0, unweighted=752, edge=0.12..7.74
|
||||
- `panties5` — ok; verts=758, tris=0 degenerate, influences=4, unweighted=0, edge=0.90..0.94
|
||||
- `panties6` — ok; verts=758, tris=0 degenerate, influences=4, unweighted=0, edge=0.88..0.95
|
||||
- `panties7` — ok; verts=758, tris=0 degenerate, influences=4, unweighted=0, edge=0.88..0.95
|
||||
- `shoes1` — ok; verts=414, tris=0 degenerate, influences=3, unweighted=0, edge=0.93..0.98
|
||||
- `skirt1` — ok; verts=154, tris=0 degenerate, influences=4, unweighted=0, edge=0.89..0.94
|
||||
- `skirt2` — ok; verts=176, tris=0 degenerate, influences=4, unweighted=0, edge=0.89..0.94
|
||||
- `skirt3` — ok; verts=154, tris=0 degenerate, influences=4, unweighted=0, edge=0.90..0.94
|
||||
- `top1` — ok; verts=852, tris=0 degenerate, influences=4, unweighted=0, edge=0.58..1.05
|
||||
- `top2` — ok; verts=854, tris=0 degenerate, influences=4, unweighted=0, edge=0.58..1.05
|
||||
- `top3` — ok; verts=854, tris=0 degenerate, influences=4, unweighted=0, edge=0.58..1.05
|
||||
@@ -0,0 +1,482 @@
|
||||
{
|
||||
"age": "teenager",
|
||||
"fails": [],
|
||||
"info": {
|
||||
"adultArmature": "male",
|
||||
"ageArmature": "teenager-male",
|
||||
"animations": {
|
||||
"aliased": {
|
||||
"swimming-fast": "swimming"
|
||||
},
|
||||
"appended": [
|
||||
"Armature|mixamo.com|Layer0.020",
|
||||
"Armature|mixamo.com|Layer0.018",
|
||||
"Armature|mixamo.com|Layer0.016",
|
||||
"Armature|mixamo.com|Layer0.014",
|
||||
"Armature|mixamo.com|Layer0.012"
|
||||
],
|
||||
"missing": [],
|
||||
"scaledLocationCurves": 7601,
|
||||
"stem": "teenager-male"
|
||||
},
|
||||
"outputBase": "edited-normal-teenager-male.blend",
|
||||
"slots": "bottom,face,feet,hair,top",
|
||||
"sourceBase": "edited-normal-male.blend"
|
||||
},
|
||||
"objects": {
|
||||
"Accessory": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9408520391417841,
|
||||
"edgeRatioMin": 0.9129875147754315,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 111,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 145
|
||||
},
|
||||
"Body": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.218153566216659,
|
||||
"edgeRatioMin": 0.7349989979664033,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 6232,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 6584
|
||||
},
|
||||
"BodyBottom": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9415051179583105,
|
||||
"edgeRatioMin": 0.8926428739692449,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1228,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 1181
|
||||
},
|
||||
"BodyBottomPants": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9409140829633826,
|
||||
"edgeRatioMin": 0.8827862876547917,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 972,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 1133
|
||||
},
|
||||
"BodyFeet": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0153534321863444,
|
||||
"edgeRatioMin": 0.8887109284611726,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 1274,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 1392
|
||||
},
|
||||
"BodyFeetPants": {
|
||||
"degenerateTris": 48,
|
||||
"edgeRatioMax": 1.0153534321863444,
|
||||
"edgeRatioMin": 0.8887109284611726,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 1466,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1602
|
||||
},
|
||||
"BodyFeetPantsShoes": {
|
||||
"degenerateTris": 48,
|
||||
"edgeRatioMax": 1.0099758747299858,
|
||||
"edgeRatioMin": 0.9253361353429929,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 830,
|
||||
"quality": "warn",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1048
|
||||
},
|
||||
"BodyTop": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.218153566216659,
|
||||
"edgeRatioMin": 0.7349989979664033,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 3726,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 3373
|
||||
},
|
||||
"BodyTopRobe": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.1954519257878777,
|
||||
"edgeRatioMin": 0.7349989979664033,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 5384,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 1,
|
||||
"unweighted": 0,
|
||||
"vertices": 5046
|
||||
},
|
||||
"Body_shapes": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.218153566216659,
|
||||
"edgeRatioMin": 0.7349989979664033,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 6228,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 2,
|
||||
"unweighted": 0,
|
||||
"vertices": 5556
|
||||
},
|
||||
"Bottoms": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9635867444531326,
|
||||
"edgeRatioMin": 0.8827862876547917,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 826,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 942
|
||||
},
|
||||
"Face": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9902318510318413,
|
||||
"edgeRatioMin": 0.9899010785060705,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 3839,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 57,
|
||||
"unweighted": 0,
|
||||
"vertices": 4045
|
||||
},
|
||||
"Hair": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9900002385614128,
|
||||
"edgeRatioMin": 0.9899999851477491,
|
||||
"maxInfluences": 1,
|
||||
"note": "",
|
||||
"polygons": 4,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 9
|
||||
},
|
||||
"Hair2": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9990771032056067,
|
||||
"edgeRatioMin": 0.949542700174737,
|
||||
"maxInfluences": 2,
|
||||
"note": "",
|
||||
"polygons": 4483,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 7022
|
||||
},
|
||||
"HairBack": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0723475348632554,
|
||||
"edgeRatioMin": 0.9495563211922972,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 724,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 760
|
||||
},
|
||||
"Panties001": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9414287020897463,
|
||||
"edgeRatioMin": 0.8949860611150358,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 604,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 582
|
||||
},
|
||||
"Pants0001": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9741146771468555,
|
||||
"edgeRatioMin": 0.7167993480571109,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 898,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 915
|
||||
},
|
||||
"Shoes": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0099758747299858,
|
||||
"edgeRatioMin": 0.9253361353429929,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 334,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 478
|
||||
},
|
||||
"Tops": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.058850186591336,
|
||||
"edgeRatioMin": 0.8334681396211425,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1658,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1896
|
||||
},
|
||||
"TopsEdited": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.058850186591336,
|
||||
"edgeRatioMin": 0.8334681396211425,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1658,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1896
|
||||
},
|
||||
"coat": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9465083443563386,
|
||||
"edgeRatioMin": 0.8345250576121954,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 1658,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 1896
|
||||
},
|
||||
"hair0001": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.1823451224746337,
|
||||
"edgeRatioMin": 0.949542700174737,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 4483,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 7022
|
||||
},
|
||||
"hair0002": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.1823451224746337,
|
||||
"edgeRatioMin": 0.949542700174737,
|
||||
"maxInfluences": 4,
|
||||
"note": "",
|
||||
"polygons": 4483,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 7022
|
||||
},
|
||||
"hair0003": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 1.0,
|
||||
"edgeRatioMin": 1.0,
|
||||
"maxInfluences": 0,
|
||||
"note": "",
|
||||
"polygons": 4483,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 7022
|
||||
},
|
||||
"shoes1": {
|
||||
"degenerateTris": 0,
|
||||
"edgeRatioMax": 0.9949808923340873,
|
||||
"edgeRatioMin": 0.9268173377136528,
|
||||
"maxInfluences": 3,
|
||||
"note": "",
|
||||
"polygons": 334,
|
||||
"quality": "ok",
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"vertices": 478
|
||||
}
|
||||
},
|
||||
"profileHash": "dd6901d4220ae704",
|
||||
"rig": {
|
||||
"adultBones": 242,
|
||||
"ageBones": 242,
|
||||
"profile": {
|
||||
"arm": 0.92,
|
||||
"bodyRadial": 0.94,
|
||||
"foot": 0.97,
|
||||
"head": 0.99,
|
||||
"headBulge": 1.0,
|
||||
"hipHeight": 0.94,
|
||||
"hipWidth": 0.92,
|
||||
"leg": 0.94,
|
||||
"limbRadial": 0.93,
|
||||
"shoulderWidth": 0.92,
|
||||
"torso": 0.92,
|
||||
"walkSpeedScale": 0.95
|
||||
},
|
||||
"restOrientationErrors": []
|
||||
},
|
||||
"seams": {
|
||||
"coincidentGroups": 5119,
|
||||
"examples": [
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"BodyTopRobe"
|
||||
],
|
||||
"position": [
|
||||
0.0,
|
||||
0.09,
|
||||
1.4972
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"BodyTopRobe"
|
||||
],
|
||||
"position": [
|
||||
0.0,
|
||||
0.0858,
|
||||
1.6039
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"BodyTopRobe"
|
||||
],
|
||||
"position": [
|
||||
0.0,
|
||||
-0.0184,
|
||||
1.5008
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0633,
|
||||
0.0369,
|
||||
1.6496
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 2e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0633,
|
||||
0.0369,
|
||||
1.6496
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"BodyTopRobe",
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0842,
|
||||
0.0277,
|
||||
1.6955
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"BodyTop",
|
||||
"BodyTopRobe",
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
-0.0842,
|
||||
0.0277,
|
||||
1.6955
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0633,
|
||||
0.0344,
|
||||
1.6475
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0636,
|
||||
0.0319,
|
||||
1.6457
|
||||
]
|
||||
},
|
||||
{
|
||||
"gap": 1e-06,
|
||||
"objects": [
|
||||
"Face"
|
||||
],
|
||||
"position": [
|
||||
0.0645,
|
||||
0.0294,
|
||||
1.6445
|
||||
]
|
||||
}
|
||||
],
|
||||
"maxGap": 7.936056721462239e-06,
|
||||
"snappedVertices": 226
|
||||
},
|
||||
"sex": "male",
|
||||
"status": "warn",
|
||||
"stem": "teenager-male",
|
||||
"timestamp": 1793419277,
|
||||
"warnings": [
|
||||
"BodyFeetPants",
|
||||
"BodyFeetPantsShoes"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
# Conversion Report — teenager / male
|
||||
|
||||
- Stem: `teenager-male`
|
||||
- Status: **warn**
|
||||
- Profile hash: `dd6901d4220ae704`
|
||||
- Objects: 25
|
||||
- Warnings: 2
|
||||
- Fails: 0
|
||||
|
||||
## Info
|
||||
- adultArmature: `male`
|
||||
- ageArmature: `teenager-male`
|
||||
- animations: `{'scaledLocationCurves': 7601, 'appended': ['Armature|mixamo.com|Layer0.020', 'Armature|mixamo.com|Layer0.018', 'Armature|mixamo.com|Layer0.016', 'Armature|mixamo.com|Layer0.014', 'Armature|mixamo.com|Layer0.012'], 'aliased': {'swimming-fast': 'swimming'}, 'missing': [], 'stem': 'teenager-male'}`
|
||||
- outputBase: `edited-normal-teenager-male.blend`
|
||||
- slots: `bottom,face,feet,hair,top`
|
||||
- sourceBase: `edited-normal-male.blend`
|
||||
|
||||
## Seams
|
||||
- coincidentGroups: `5119`
|
||||
- snappedVertices: `226`
|
||||
- maxGap: `7.936056721462239e-06`
|
||||
- examples: `[{'position': [0.0, 0.09, 1.4972], 'gap': 2e-06, 'objects': ['BodyTop', 'BodyTopRobe']}, {'position': [0.0, 0.0858, 1.6039], 'gap': 1e-06, 'objects': ['BodyTop', 'BodyTopRobe']}, {'position': [0.0, -0.0184, 1.5008], 'gap': 2e-06, 'objects': ['BodyTop', 'BodyTopRobe']}, {'position': [0.0633, 0.0369, 1.6496], 'gap': 2e-06, 'objects': ['BodyTop', 'Face']}, {'position': [-0.0633, 0.0369, 1.6496], 'gap': 2e-06, 'objects': ['BodyTop', 'Face']}, {'position': [0.0842, 0.0277, 1.6955], 'gap': 1e-06, 'objects': ['BodyTop', 'BodyTopRobe', 'Face']}, {'position': [-0.0842, 0.0277, 1.6955], 'gap': 1e-06, 'objects': ['BodyTop', 'BodyTopRobe', 'Face']}, {'position': [0.0633, 0.0344, 1.6475], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0636, 0.0319, 1.6457], 'gap': 1e-06, 'objects': ['Face']}, {'position': [0.0645, 0.0294, 1.6445], 'gap': 1e-06, 'objects': ['Face']}]`
|
||||
|
||||
## Warnings (first 20)
|
||||
|
||||
- `BodyFeetPants`
|
||||
- `BodyFeetPantsShoes`
|
||||
|
||||
## Objects
|
||||
|
||||
- `Accessory` — ok; verts=145, tris=0 degenerate, influences=4, unweighted=0, edge=0.91..0.94
|
||||
- `Body` — ok; verts=6584, tris=0 degenerate, influences=4, unweighted=0, edge=0.73..1.22
|
||||
- `BodyBottom` — ok; verts=1181, tris=0 degenerate, influences=4, unweighted=0, edge=0.89..0.94
|
||||
- `BodyBottomPants` — ok; verts=1133, tris=0 degenerate, influences=4, unweighted=0, edge=0.88..0.94
|
||||
- `BodyFeet` — ok; verts=1392, tris=0 degenerate, influences=3, unweighted=0, edge=0.89..1.02
|
||||
- `BodyFeetPants` — warn; verts=1602, tris=48 degenerate, influences=3, unweighted=0, edge=0.89..1.02
|
||||
- `BodyFeetPantsShoes` — warn; verts=1048, tris=48 degenerate, influences=3, unweighted=0, edge=0.93..1.01
|
||||
- `BodyTop` — ok; verts=3373, tris=0 degenerate, influences=4, unweighted=0, edge=0.73..1.22
|
||||
- `BodyTopRobe` — ok; verts=5046, tris=0 degenerate, influences=4, unweighted=0, edge=0.73..1.20
|
||||
- `Body_shapes` — ok; verts=5556, tris=0 degenerate, influences=4, unweighted=0, edge=0.73..1.22
|
||||
- `Bottoms` — ok; verts=942, tris=0 degenerate, influences=4, unweighted=0, edge=0.88..0.96
|
||||
- `Face` — ok; verts=4045, tris=0 degenerate, influences=1, unweighted=0, edge=0.99..0.99
|
||||
- `Hair` — ok; verts=9, tris=0 degenerate, influences=1, unweighted=0, edge=0.99..0.99
|
||||
- `Hair2` — ok; verts=7022, tris=0 degenerate, influences=2, unweighted=0, edge=0.95..1.00
|
||||
- `HairBack` — ok; verts=760, tris=0 degenerate, influences=4, unweighted=0, edge=0.95..1.07
|
||||
- `Panties001` — ok; verts=582, tris=0 degenerate, influences=4, unweighted=0, edge=0.89..0.94
|
||||
- `Pants0001` — ok; verts=915, tris=0 degenerate, influences=4, unweighted=0, edge=0.72..0.97
|
||||
- `Shoes` — ok; verts=478, tris=0 degenerate, influences=3, unweighted=0, edge=0.93..1.01
|
||||
- `Tops` — ok; verts=1896, tris=0 degenerate, influences=4, unweighted=0, edge=0.83..1.06
|
||||
- `TopsEdited` — ok; verts=1896, tris=0 degenerate, influences=4, unweighted=0, edge=0.83..1.06
|
||||
- `coat` — ok; verts=1896, tris=0 degenerate, influences=4, unweighted=0, edge=0.83..0.95
|
||||
- `hair0001` — ok; verts=7022, tris=0 degenerate, influences=4, unweighted=0, edge=0.95..1.18
|
||||
- `hair0002` — ok; verts=7022, tris=0 degenerate, influences=4, unweighted=0, edge=0.95..1.18
|
||||
- `hair0003` — ok; verts=7022, tris=0 degenerate, influences=0, unweighted=0, edge=1.00..1.00
|
||||
- `shoes1` — ok; verts=478, tris=0 degenerate, influences=3, unweighted=0, edge=0.93..0.99
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,660 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate first-pass age-band character seeds from the adult assets.
|
||||
|
||||
Run with Blender 3.6 batch mode, for example::
|
||||
|
||||
blender -b -P assets/blender/characters/generate_age_blends.py -- \
|
||||
--age elder --sex male --report-only
|
||||
|
||||
The script is an authoring tool: it writes committed seed ``.blend`` files and
|
||||
conversion reports. It is deliberately NOT part of the CMake build, and it
|
||||
will not overwrite a file that changed since it was generated unless
|
||||
``--force`` is given. See AGES-PLAN.md for the design.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import bpy
|
||||
|
||||
_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
if _THIS_DIR not in sys.path:
|
||||
sys.path.insert(0, _THIS_DIR)
|
||||
|
||||
from agegen import anim, deform, profiles, report, rig, weights # noqa: E402
|
||||
|
||||
|
||||
def log(msg):
|
||||
print("[agegen] " + msg, flush=True)
|
||||
|
||||
|
||||
def sha256(path):
|
||||
h = hashlib.sha256()
|
||||
with open(path, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def manifest_path(out_dir):
|
||||
return os.path.join(out_dir, "AGE-GENERATION.json")
|
||||
|
||||
|
||||
def load_manifest(out_dir):
|
||||
path = manifest_path(out_dir)
|
||||
if not os.path.exists(path):
|
||||
return {"version": 1, "stems": {}}
|
||||
try:
|
||||
with open(path) as f:
|
||||
data = json.load(f)
|
||||
if "stems" not in data:
|
||||
data["stems"] = {}
|
||||
return data
|
||||
except Exception:
|
||||
return {"version": 1, "stems": {}}
|
||||
|
||||
|
||||
def save_manifest(out_dir, data):
|
||||
data["profileHash"] = profiles.profile_hash()
|
||||
data["updated"] = int(time.time())
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
with open(manifest_path(out_dir), "w") as f:
|
||||
json.dump(data, f, indent=2, sort_keys=True)
|
||||
|
||||
|
||||
def find_anim_source(sex, explicit):
|
||||
if explicit:
|
||||
return explicit if os.path.exists(explicit) else None
|
||||
name = f"vrm-vroid-normal-{sex}.blend"
|
||||
roots = [
|
||||
os.getcwd(),
|
||||
os.path.dirname(_THIS_DIR),
|
||||
os.path.dirname(os.path.dirname(_THIS_DIR)),
|
||||
"/media/slapin/library/ogre3/ogre-projects/world2",
|
||||
]
|
||||
patterns = []
|
||||
for root in roots:
|
||||
patterns += [
|
||||
os.path.join(root, "build-vscode", "assets", "blender", name),
|
||||
os.path.join(root, "build", "assets", "blender", name),
|
||||
os.path.join(root, "assets", "blender", name),
|
||||
]
|
||||
patterns += glob.glob(
|
||||
os.path.join(os.getcwd(), "build*", "assets", "blender", name))
|
||||
for path in patterns:
|
||||
if path and os.path.exists(path):
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
def source_paths(age, sex, source_dir):
|
||||
stem = profiles.stem(age, sex)
|
||||
base = os.path.join(source_dir,
|
||||
f"edited-normal-{sex}.blend")
|
||||
clothes = {
|
||||
slot: os.path.join(source_dir, f"clothes-{sex}-{slot}.blend")
|
||||
for slot in ("top", "bottom", "feet", "hair")
|
||||
}
|
||||
out_base = os.path.join(source_dir,
|
||||
f"edited-normal-{age}-{sex}.blend")
|
||||
out_clothes = {
|
||||
slot: os.path.join(source_dir,
|
||||
f"clothes-{age}-{sex}-{slot}.blend")
|
||||
for slot in ("top", "bottom", "feet", "hair")
|
||||
}
|
||||
return stem, base, clothes, out_base, out_clothes
|
||||
|
||||
|
||||
def all_outputs(out_base, out_clothes):
|
||||
return [out_base] + [out_clothes[s] for s in
|
||||
("top", "bottom", "feet", "hair")]
|
||||
|
||||
|
||||
def stem_protected(stem, outputs, manifest, force):
|
||||
if force:
|
||||
return False
|
||||
entry = manifest.get("stems", {}).get(stem)
|
||||
if not entry:
|
||||
# Existing files without a manifest are assumed to be hand-authored.
|
||||
return any(os.path.exists(p) for p in outputs)
|
||||
if entry.get("state") == "hand-edited":
|
||||
return True
|
||||
hashes = entry.get("outputHashes", {})
|
||||
for path in outputs:
|
||||
if not os.path.exists(path):
|
||||
continue
|
||||
want = hashes.get(os.path.basename(path))
|
||||
if want and sha256(path) != want:
|
||||
log(f" {os.path.basename(path)} changed since generation; "
|
||||
f"treating as hand-edited")
|
||||
return True
|
||||
if not want:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def append_objects(blend_path, names):
|
||||
before = set(bpy.data.objects)
|
||||
with bpy.data.libraries.load(blend_path, link=False) as (src, dst):
|
||||
dst.objects = [n for n in names if n in src.objects]
|
||||
loaded = []
|
||||
for obj in dst.objects:
|
||||
if obj is None:
|
||||
continue
|
||||
try:
|
||||
bpy.context.collection.objects.link(obj)
|
||||
except RuntimeError:
|
||||
pass
|
||||
loaded.append(obj)
|
||||
# Blender may pull in child objects of an appended armature. Keep only
|
||||
# the explicitly requested objects; the conversion math does not need
|
||||
# their children, and clothes seed files must not contain body meshes.
|
||||
keep = set(loaded)
|
||||
for obj in list(bpy.data.objects):
|
||||
if obj not in before and obj not in keep:
|
||||
try:
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
except Exception:
|
||||
pass
|
||||
return loaded
|
||||
|
||||
|
||||
def delete_object(obj):
|
||||
if obj is None:
|
||||
return
|
||||
try:
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def purge_orphans():
|
||||
for _ in range(3):
|
||||
try:
|
||||
bpy.ops.outliner.orphans_purge(
|
||||
do_local_ids=True, do_linked_ids=True,
|
||||
do_recursive=True)
|
||||
except Exception:
|
||||
break
|
||||
|
||||
|
||||
def cleanup_seed_objects(adult_arm):
|
||||
"""Remove Blender control junk that must not live in a committed seed."""
|
||||
for obj in list(bpy.data.objects):
|
||||
if obj == adult_arm:
|
||||
continue
|
||||
low = obj.name.lower()
|
||||
if obj.name.startswith("cs_") or "collider" in low:
|
||||
delete_object(obj)
|
||||
continue
|
||||
if obj.type == 'ARMATURE':
|
||||
# Keep only the age armature (and any hair armature handled by
|
||||
# the clothes converter). The adult armature is deleted by the
|
||||
# caller after animation retargeting.
|
||||
continue
|
||||
for obj in list(bpy.data.objects):
|
||||
if obj.type == 'EMPTY' and obj.name.startswith("cs_"):
|
||||
delete_object(obj)
|
||||
|
||||
|
||||
def set_catalog_props(obj, age, sex):
|
||||
if not obj or obj.type != 'MESH':
|
||||
return
|
||||
if all(k in obj for k in ("age", "sex", "slot")):
|
||||
obj["age"] = profiles.normalize_age(age)
|
||||
obj["sex"] = sex
|
||||
obj["agegen_state"] = "auto-seed"
|
||||
obj["agegen_profile"] = profiles.profile_hash()
|
||||
|
||||
|
||||
def convert_body(age, sex, base_path, out_path, anim_source,
|
||||
write, report_only):
|
||||
log(f"loading adult base {base_path}")
|
||||
bpy.ops.wm.open_mainfile(filepath=base_path)
|
||||
bpy.context.scene.frame_set(1)
|
||||
|
||||
adult_arm = bpy.data.objects.get(sex)
|
||||
if adult_arm is None or adult_arm.type != 'ARMATURE':
|
||||
raise RuntimeError(f"adult armature '{sex}' not found in {base_path}")
|
||||
|
||||
prof = profiles.profile(age)
|
||||
stem = profiles.stem(age, sex)
|
||||
age_arm = rig.build_age_armature(adult_arm, age, sex, prof)
|
||||
log(f"built armature {age_arm.name}")
|
||||
|
||||
rest_data = rig.rest_transform_data(adult_arm, age_arm, age, prof)
|
||||
dmat = rig.dmatrices_from_data(rest_data)
|
||||
bad = rig.validate_rest_orientation(adult_arm, age_arm)
|
||||
if bad:
|
||||
raise RuntimeError("rest orientation changed for bones: " +
|
||||
", ".join(bad[:10]))
|
||||
|
||||
rep = report.ConversionReport(age, sex)
|
||||
rep.info["sourceBase"] = os.path.basename(base_path)
|
||||
rep.info["adultArmature"] = adult_arm.name
|
||||
rep.info["ageArmature"] = age_arm.name
|
||||
rep.rig = {
|
||||
"adultBones": len(adult_arm.data.bones),
|
||||
"ageBones": len(age_arm.data.bones),
|
||||
"restOrientationErrors": bad,
|
||||
"profile": prof,
|
||||
}
|
||||
|
||||
converted = []
|
||||
for obj in list(bpy.data.objects):
|
||||
if obj.type != 'MESH':
|
||||
continue
|
||||
if obj.name.startswith("cs_"):
|
||||
continue
|
||||
if obj == adult_arm:
|
||||
continue
|
||||
if obj.data is None:
|
||||
continue
|
||||
if obj.vertex_groups:
|
||||
weights.limit_vertex_influences(obj, 4)
|
||||
metrics = deform.deform_mesh_object(
|
||||
obj, adult_arm, age_arm, dmat, prof, retarget_parent=True)
|
||||
if metrics is None:
|
||||
continue
|
||||
set_catalog_props(obj, age, sex)
|
||||
is_catalog = all(k in obj for k in ("age", "sex", "slot"))
|
||||
quality = report.evaluate_object_quality(metrics, is_catalog)
|
||||
obj["agegen_quality"] = quality
|
||||
rep.add_object(obj.name, metrics, quality=quality)
|
||||
if is_catalog:
|
||||
converted.append((obj, metrics))
|
||||
|
||||
seam = deform.snap_seams(converted)
|
||||
rep.seams = seam
|
||||
log(f"seam groups={seam['coincidentGroups']} "
|
||||
f"snapped={seam['snappedVertices']} maxGap={seam['maxGap']:.6f}")
|
||||
|
||||
# Required catalog slots per sex.
|
||||
slots = sorted({obj.get("slot") for obj, _ in converted
|
||||
if obj.get("slot")})
|
||||
rep.info["slots"] = ",".join(slots)
|
||||
for required in ("face", "top", "bottom", "feet", "hair"):
|
||||
if required not in slots:
|
||||
rep.info.setdefault("missingSlots", [])
|
||||
rep.info["missingSlots"].append(required)
|
||||
|
||||
if anim_source and os.path.exists(anim_source):
|
||||
try:
|
||||
summary = anim.append_and_retarget(
|
||||
age_arm, adult_arm, anim_source, age, sex)
|
||||
rep.info["animations"] = summary
|
||||
if summary.get("missing"):
|
||||
rep.info["missingAnimations"] = summary["missing"]
|
||||
except Exception as exc:
|
||||
rep.info["animationError"] = str(exc)
|
||||
log(f"animation retarget failed: {exc}")
|
||||
else:
|
||||
rep.info["animationError"] = "animation source not found"
|
||||
log("animation source not found; seed will have no clips")
|
||||
|
||||
# Remove the adult armature and control junk; re-parented meshes remain.
|
||||
cleanup_seed_objects(adult_arm)
|
||||
delete_object(adult_arm)
|
||||
|
||||
# Name and validate the single body armature.
|
||||
age_arm.name = stem
|
||||
age_arm.data.name = stem
|
||||
set_catalog_props(age_arm, age, sex)
|
||||
for obj in bpy.data.objects:
|
||||
set_catalog_props(obj, age, sex)
|
||||
|
||||
rep.info["outputBase"] = os.path.basename(out_path)
|
||||
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
||||
purge_orphans()
|
||||
if write:
|
||||
bpy.ops.wm.save_as_mainfile(filepath=out_path)
|
||||
log(f"saved body seed {out_path}")
|
||||
return rep, rest_data, age_arm
|
||||
|
||||
|
||||
def find_own_hair_armature(obj, adult_arm, age_arm):
|
||||
own_name = obj.get("own_armature_name", "")
|
||||
if own_name:
|
||||
arm = bpy.data.objects.get(own_name)
|
||||
if arm and arm.type == 'ARMATURE':
|
||||
return arm
|
||||
for mod in obj.modifiers:
|
||||
if mod.type != 'ARMATURE' or mod.object is None:
|
||||
continue
|
||||
if mod.object != adult_arm and mod.object != age_arm:
|
||||
return mod.object
|
||||
if obj.parent and obj.parent.type == 'ARMATURE' and \
|
||||
obj.parent not in (adult_arm, age_arm):
|
||||
return obj.parent
|
||||
return None
|
||||
|
||||
|
||||
def rename_hair_armature(arm, stem, original_name):
|
||||
safe = "".join(c if c.isalnum() or c in "-_" else "_"
|
||||
for c in original_name)
|
||||
new_name = f"{stem}-hair-{safe}"
|
||||
arm.name = new_name
|
||||
arm.data.name = new_name
|
||||
return new_name
|
||||
|
||||
|
||||
def transfer_weights_from_body(obj, body_obj):
|
||||
"""Copy body weight groups onto an unskinned garment/helper.
|
||||
|
||||
The source clothes files intentionally carry no rig; the normal build
|
||||
transfers weights later. Doing it before conversion gives the geometry a
|
||||
smooth deformation instead of nearest-bone rigid pieces, and the build
|
||||
repeats it from the converted body as usual.
|
||||
"""
|
||||
if body_obj is None or body_obj.type != 'MESH':
|
||||
return False
|
||||
if not body_obj.vertex_groups:
|
||||
return False
|
||||
obj.vertex_groups.clear()
|
||||
dt = obj.modifiers.new(name="WT", type='DATA_TRANSFER')
|
||||
dt.object = body_obj
|
||||
dt.use_vert_data = True
|
||||
dt.data_types_verts = {'VGROUP_WEIGHTS'}
|
||||
dt.layers_vgroup_select_src = 'ALL'
|
||||
dt.vert_mapping = 'POLYINTERP_NEAREST'
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
try:
|
||||
bpy.ops.object.datalayout_transfer(modifier=dt.name)
|
||||
bpy.ops.object.modifier_apply(modifier=dt.name)
|
||||
except Exception as exc:
|
||||
log(f" weight transfer failed for {obj.name}: {exc}")
|
||||
try:
|
||||
obj.modifiers.remove(dt)
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
weights.limit_vertex_influences(obj, 4)
|
||||
return True
|
||||
|
||||
|
||||
def convert_clothes(age, sex, slot, clothes_path, out_path,
|
||||
adult_base, body_seed, rest_data, write, rep,
|
||||
profile):
|
||||
log(f"converting {slot}: {clothes_path}")
|
||||
bpy.ops.wm.open_mainfile(filepath=clothes_path)
|
||||
stem = profiles.stem(age, sex)
|
||||
|
||||
adult_arms = append_objects(adult_base, [sex])
|
||||
age_arms = append_objects(body_seed, [stem])
|
||||
if not adult_arms or not age_arms:
|
||||
raise RuntimeError("failed to append adult/age armatures")
|
||||
adult_arm = adult_arms[0]
|
||||
age_arm = age_arms[0]
|
||||
dmat = rig.dmatrices_from_data(rest_data)
|
||||
prof = profile
|
||||
adult_head, age_head = rig.head_pivots(adult_arm, age_arm)
|
||||
head_scale = float(prof["head"])
|
||||
|
||||
ref_cache = {}
|
||||
|
||||
def get_ref(name):
|
||||
if not name:
|
||||
return None
|
||||
if name not in ref_cache:
|
||||
objs = append_objects(body_seed, [name])
|
||||
ref_cache[name] = objs[0] if objs else None
|
||||
return ref_cache[name]
|
||||
|
||||
hair_arm_map = {}
|
||||
meshes = [o for o in bpy.data.objects
|
||||
if o.type == 'MESH' and o.data is not None]
|
||||
for obj in meshes:
|
||||
own_arm = find_own_hair_armature(obj, adult_arm, age_arm)
|
||||
if own_arm is not None:
|
||||
if own_arm.name not in hair_arm_map:
|
||||
old = own_arm.name
|
||||
new = rename_hair_armature(own_arm, stem, old)
|
||||
hair_arm_map[own_arm.name] = new
|
||||
rig.scale_hair_armature(own_arm, adult_head, age_head,
|
||||
head_scale)
|
||||
log(f" scaled hair armature {old} -> {new}")
|
||||
deform.transform_hair_uniform(obj, adult_head, age_head,
|
||||
head_scale)
|
||||
obj["has_own_armature"] = True
|
||||
obj["own_armature_name"] = own_arm.name
|
||||
obj.parent = own_arm
|
||||
mod = next((m for m in obj.modifiers
|
||||
if m.type == 'ARMATURE'), None)
|
||||
if mod:
|
||||
mod.object = own_arm
|
||||
metrics = {
|
||||
"vertices": len(obj.data.vertices),
|
||||
"polygons": len(obj.data.polygons),
|
||||
"shapeKeys": 0,
|
||||
"unweighted": 0,
|
||||
"maxInfluences": 0,
|
||||
"edgeRatioMin": 1.0,
|
||||
"edgeRatioMax": 1.0,
|
||||
"degenerateTris": 0,
|
||||
}
|
||||
quality = "ok"
|
||||
else:
|
||||
ref = get_ref(obj.get("ref_clothing", ""))
|
||||
transfer_weights_from_body(obj, ref)
|
||||
metrics = deform.deform_mesh_object(
|
||||
obj, adult_arm, age_arm, dmat, prof,
|
||||
retarget_parent=False)
|
||||
quality = report.evaluate_object_quality(metrics, True) \
|
||||
if metrics else "warn"
|
||||
obj["agegen_quality"] = quality
|
||||
# Garment seed files carry no body armature.
|
||||
obj.parent = None
|
||||
for mod in list(obj.modifiers):
|
||||
if mod.type == 'ARMATURE':
|
||||
obj.modifiers.remove(mod)
|
||||
if "ref_layer" in obj:
|
||||
obj["ref_age"] = profiles.normalize_age(age)
|
||||
obj["ref_sex"] = sex
|
||||
obj["agegen_state"] = "auto-seed"
|
||||
rep.add_object(obj.name, metrics, quality=quality)
|
||||
|
||||
# Remove the appended body armatures and reference helpers, keep hair
|
||||
# armatures.
|
||||
for ref in ref_cache.values():
|
||||
delete_object(ref)
|
||||
for arm in age_arms + adult_arms:
|
||||
delete_object(arm)
|
||||
|
||||
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
||||
purge_orphans()
|
||||
if write:
|
||||
bpy.ops.wm.save_as_mainfile(filepath=out_path)
|
||||
log(f"saved clothes seed {out_path}")
|
||||
|
||||
|
||||
def check_seed(age, sex, source_dir):
|
||||
stem, base, clothes, out_base, out_clothes = source_paths(
|
||||
age, sex, source_dir)
|
||||
outputs = all_outputs(out_base, out_clothes)
|
||||
manifest = load_manifest(source_dir)
|
||||
entry = manifest.get("stems", {}).get(stem)
|
||||
missing = [p for p in outputs if not os.path.exists(p)]
|
||||
problems = []
|
||||
if missing:
|
||||
problems.append("missing: " + ", ".join(os.path.basename(p)
|
||||
for p in missing))
|
||||
if not entry:
|
||||
problems.append("no manifest entry")
|
||||
else:
|
||||
if entry.get("profileHash") not in (None,
|
||||
profiles.profile_hash()):
|
||||
problems.append("profile hash mismatch")
|
||||
hashes = entry.get("outputHashes", {})
|
||||
for p in outputs:
|
||||
if not os.path.exists(p):
|
||||
continue
|
||||
want = hashes.get(os.path.basename(p))
|
||||
if not want:
|
||||
problems.append("no hash for " + os.path.basename(p))
|
||||
elif sha256(p) != want:
|
||||
problems.append("changed: " + os.path.basename(p))
|
||||
if problems:
|
||||
log(f"CHECK FAIL {stem}: " + "; ".join(problems))
|
||||
return False
|
||||
log(f"CHECK OK {stem}")
|
||||
return True
|
||||
|
||||
|
||||
def process_pair(age, sex, args):
|
||||
stem, base, clothes, out_base, out_clothes = source_paths(
|
||||
age, sex, args.source_dir)
|
||||
outputs = all_outputs(out_base, out_clothes)
|
||||
manifest = load_manifest(args.source_dir)
|
||||
|
||||
if args.check:
|
||||
return check_seed(age, sex, args.source_dir)
|
||||
|
||||
if not os.path.exists(base):
|
||||
raise RuntimeError(f"adult base not found: {base}")
|
||||
if stem_protected(stem, outputs, manifest, args.force):
|
||||
log(f"SKIP {stem}: output exists and is hand-edited/changed "
|
||||
f"(use --force to overwrite)")
|
||||
return True
|
||||
|
||||
anim_source = find_anim_source(sex, args.anim_source)
|
||||
if anim_source:
|
||||
log(f"animation source: {anim_source}")
|
||||
else:
|
||||
log("WARNING: animation source not found; no clips will be added")
|
||||
|
||||
# Body conversion needs a file for the clothes pass even in report-only
|
||||
# mode; use a temp file that is removed afterwards.
|
||||
tmp_dir = tempfile.mkdtemp(prefix="agegen-")
|
||||
body_seed = out_base if (args.write and not args.report_only) else \
|
||||
os.path.join(tmp_dir, os.path.basename(out_base))
|
||||
|
||||
rep, rest_data, _age_arm = convert_body(
|
||||
age, sex, base, body_seed, anim_source,
|
||||
write=True, report_only=args.report_only)
|
||||
if args.report_only:
|
||||
log(f"temporary body seed: {body_seed}")
|
||||
|
||||
if not args.no_clothes:
|
||||
for slot in ("top", "bottom", "feet", "hair"):
|
||||
src = clothes[slot]
|
||||
if not os.path.exists(src):
|
||||
log(f"WARNING: missing clothes source {src}; skipping")
|
||||
continue
|
||||
convert_clothes(
|
||||
age, sex, slot, src, out_clothes[slot], base, body_seed,
|
||||
rest_data, write=(not args.report_only), rep=rep,
|
||||
profile=profiles.profile(age))
|
||||
|
||||
# Write report (always useful, even in report-only mode).
|
||||
report_path, report_json = rep.write(args.source_dir)
|
||||
log(f"report: {report_path}")
|
||||
|
||||
if args.report_only:
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
return True
|
||||
|
||||
entry = {
|
||||
"state": "auto-seed",
|
||||
"age": profiles.normalize_age(age),
|
||||
"sex": sex,
|
||||
"profileHash": profiles.profile_hash(),
|
||||
"sourceHashes": {
|
||||
os.path.basename(base): sha256(base),
|
||||
**{os.path.basename(clothes[s]): sha256(clothes[s])
|
||||
for s in ("top", "bottom", "feet", "hair")
|
||||
if os.path.exists(clothes[s])},
|
||||
},
|
||||
"outputHashes": {
|
||||
os.path.basename(p): sha256(p)
|
||||
for p in outputs if os.path.exists(p)
|
||||
},
|
||||
"report": os.path.basename(report_path),
|
||||
"reportJson": os.path.basename(report_json),
|
||||
"updated": int(time.time()),
|
||||
}
|
||||
if anim_source:
|
||||
entry["animationSource"] = anim_source
|
||||
entry["animationSourceHash"] = sha256(anim_source)
|
||||
manifest.setdefault("stems", {})[stem] = entry
|
||||
save_manifest(args.source_dir, manifest)
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
|
||||
if rep.status() == "fail" and not args.allow_fail:
|
||||
log(f"WARNING: {stem} has fail-level objects; see report")
|
||||
return True
|
||||
|
||||
|
||||
def parse_args():
|
||||
argv = sys.argv
|
||||
if "--" in argv:
|
||||
argv = argv[argv.index("--") + 1:]
|
||||
else:
|
||||
argv = []
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument("--age", choices=profiles.CANONICAL_AGES)
|
||||
p.add_argument("--sex", choices=["male", "female"])
|
||||
p.add_argument("--all", action="store_true")
|
||||
p.add_argument("--source-dir", default=_THIS_DIR)
|
||||
p.add_argument("--anim-source", default="")
|
||||
p.add_argument("--report-only", action="store_true")
|
||||
p.add_argument("--write", action="store_true")
|
||||
p.add_argument("--check", action="store_true")
|
||||
p.add_argument("--force", action="store_true")
|
||||
p.add_argument("--only-unedited", action="store_true")
|
||||
p.add_argument("--allow-fail", action="store_true")
|
||||
p.add_argument("--no-clothes", action="store_true")
|
||||
p.add_argument("--no-anim", action="store_true")
|
||||
args = p.parse_args(argv)
|
||||
if not args.check and not args.report_only and not args.write:
|
||||
args.write = True
|
||||
if args.only_unedited:
|
||||
args.write = True
|
||||
if args.all:
|
||||
if args.age or args.sex:
|
||||
p.error("--all cannot be combined with --age/--sex")
|
||||
else:
|
||||
if not args.age or not args.sex:
|
||||
p.error("specify --age and --sex, or --all")
|
||||
if args.age == "adult":
|
||||
p.error("adult is the source, not a generated age")
|
||||
if args.no_anim:
|
||||
args.anim_source = "__none__"
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
args.source_dir = os.path.abspath(args.source_dir)
|
||||
pairs = []
|
||||
if args.all:
|
||||
for age in ("elder", "teenager", "child", "baby"):
|
||||
for sex in ("male", "female"):
|
||||
pairs.append((age, sex))
|
||||
else:
|
||||
pairs.append((profiles.normalize_age(args.age), args.sex))
|
||||
ok = True
|
||||
for age, sex in pairs:
|
||||
log(f"=== {age} / {sex} ===")
|
||||
try:
|
||||
if not process_pair(age, sex, args):
|
||||
ok = False
|
||||
except Exception as exc:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
log(f"FAILED {age}/{sex}: {exc}")
|
||||
ok = False
|
||||
if not ok:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -2,18 +2,18 @@
|
||||
#
|
||||
# Run in Blender batch mode with the consolidated blend already loaded:
|
||||
#
|
||||
# blender -b -Y edited-normal-<sex>-consolidated.blend \
|
||||
# -P generate_lods.py -- <sex> <out_dir> <lods_blend_out>
|
||||
# blender -b -Y <consolidated.blend> \
|
||||
# -P generate_lods.py -- <age> <sex> <out_dir> <lods_blend_out>
|
||||
#
|
||||
# Produces, per sex:
|
||||
# * <out_dir>/charlod_<sex>-atlas.png - shared 2048x2048 LOD atlas
|
||||
# * <out_dir>/charlod_<sex>.json - manifest
|
||||
# Produces, per (age, sex) stem (adult keeps the legacy sex stem):
|
||||
# * <out_dir>/charlod_<stem>-atlas.png - shared 2048x2048 LOD atlas
|
||||
# * <out_dir>/charlod_<stem>.json - manifest
|
||||
# * LOD1 object variants `<part>_lod1` - same geometry, no shape keys,
|
||||
# UVs remapped into the atlas, one shared material `charlod_<sex>`
|
||||
# UVs remapped into the atlas, one shared material `charlod_<stem>`
|
||||
# (hair slot is skipped - hair is hidden/impostor-baked at distance)
|
||||
# * LOD2 impostors `charlod_<sex>_{thin,fat}_lod2` - the deterministic
|
||||
# * LOD2 impostors `charlod_<stem>_{thin,fat}_lod2` - the deterministic
|
||||
# default outfit joined into one mesh skinned to the body armature
|
||||
# * LOD3 impostors `charlod_<sex>_{thin,fat}_lod3` - decimate of LOD2,
|
||||
# * LOD3 impostors `charlod_<stem>_{thin,fat}_lod3` - decimate of LOD2,
|
||||
# <= 400 triangles (hard limit)
|
||||
# * saves everything into <lods_blend_out>
|
||||
#
|
||||
@@ -52,6 +52,11 @@ import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
if _THIS_DIR not in sys.path:
|
||||
sys.path.insert(0, _THIS_DIR)
|
||||
import age_profiles
|
||||
|
||||
ATLAS_SIZE = 2048
|
||||
LOD3_MAX_TRIS = 400
|
||||
REQUIRED_PROPS = {"age", "sex", "slot"}
|
||||
@@ -89,11 +94,14 @@ def log(msg):
|
||||
def parse_args():
|
||||
argv = sys.argv
|
||||
argv = argv[argv.index("--") + 1:]
|
||||
if len(argv) < 3:
|
||||
if len(argv) < 4:
|
||||
raise RuntimeError(
|
||||
"usage: blender -b -Y <consolidated.blend> -P generate_lods.py"
|
||||
" -- <sex> <out_dir> <lods_blend_out>")
|
||||
return argv[0], argv[1], argv[2]
|
||||
" -- <age> <sex> <out_dir> <lods_blend_out>")
|
||||
age = age_profiles.normalize_age(argv[0])
|
||||
sex = argv[1]
|
||||
stem = age_profiles.stem(age, sex)
|
||||
return stem, argv[2], argv[3]
|
||||
|
||||
|
||||
def strip_suffix(name):
|
||||
@@ -617,12 +625,13 @@ def write_manifest(sex, out_dir):
|
||||
|
||||
def main():
|
||||
sex, out_dir, lods_blend_out = parse_args()
|
||||
log("sex=%s out_dir=%s lods_blend=%s" % (sex, out_dir,
|
||||
lods_blend_out))
|
||||
stem = sex
|
||||
log("stem=%s out_dir=%s lods_blend=%s" % (stem, out_dir,
|
||||
lods_blend_out))
|
||||
|
||||
cleanup_previous(sex)
|
||||
cleanup_previous(stem)
|
||||
|
||||
armature = find_armature(sex)
|
||||
armature = find_armature(stem)
|
||||
head_bone = find_head_bone(armature)
|
||||
log("shared armature '%s', head bone '%s'"
|
||||
% (armature.name, head_bone))
|
||||
@@ -632,22 +641,22 @@ def main():
|
||||
% (len(catalog), [ob.name for ob in catalog]))
|
||||
|
||||
log("=== step 1: atlas ===")
|
||||
atlas, rects = build_atlas(catalog, sex, out_dir)
|
||||
atlas_mat = make_atlas_material(sex, atlas)
|
||||
atlas, rects = build_atlas(catalog, stem, out_dir)
|
||||
atlas_mat = make_atlas_material(stem, atlas)
|
||||
|
||||
log("=== step 2: LOD1 variants ===")
|
||||
lod1 = generate_lod1(catalog, rects, atlas_mat, sex)
|
||||
lod1 = generate_lod1(catalog, rects, atlas_mat, stem)
|
||||
log("generated %d LOD1 objects" % len(lod1))
|
||||
|
||||
log("=== step 3: LOD2 impostors ===")
|
||||
lod2 = generate_lod2(catalog, rects, atlas_mat, armature,
|
||||
head_bone, sex)
|
||||
head_bone, stem)
|
||||
|
||||
log("=== step 4: LOD3 impostors ===")
|
||||
generate_lod3(lod2, armature, sex)
|
||||
generate_lod3(lod2, armature, stem)
|
||||
|
||||
log("=== step 5: manifest + save ===")
|
||||
write_manifest(sex, out_dir)
|
||||
write_manifest(stem, out_dir)
|
||||
bpy.ops.wm.save_as_mainfile(filepath=lods_blend_out)
|
||||
log("saved lods blend to %s" % lods_blend_out)
|
||||
log("DONE")
|
||||
|
||||
@@ -2,6 +2,11 @@ import bpy
|
||||
import os
|
||||
import sys
|
||||
|
||||
_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
if _THIS_DIR not in sys.path:
|
||||
sys.path.insert(0, _THIS_DIR)
|
||||
import age_profiles
|
||||
|
||||
def clean_scene():
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
bpy.ops.object.delete()
|
||||
@@ -36,8 +41,10 @@ def process_batch():
|
||||
try:
|
||||
args = sys.argv[sys.argv.index("--") + 1:]
|
||||
clothing_path, lib_directory, out_dir = args[0], args[1], args[2]
|
||||
explicit_body = args[3] if len(args) >= 4 else ""
|
||||
except (IndexError, ValueError):
|
||||
print("Usage: blender -b -P script.py -- <source_blend> <lib_dir> <out_dir>")
|
||||
print("Usage: blender -b -P script.py -- "
|
||||
"<source_blend> <lib_dir> <out_dir> [body_blend]")
|
||||
return
|
||||
|
||||
if not os.path.exists(out_dir): os.makedirs(out_dir)
|
||||
@@ -62,7 +69,7 @@ def process_batch():
|
||||
|
||||
# Get properties
|
||||
sex = clothing.get("ref_sex")
|
||||
age = clothing.get("ref_age")
|
||||
age = age_profiles.normalize_age(clothing.get("ref_age"))
|
||||
ref_mesh_name = clothing.get("ref_clothing")
|
||||
|
||||
if not all([sex, age, ref_mesh_name]):
|
||||
@@ -70,19 +77,22 @@ def process_batch():
|
||||
continue
|
||||
|
||||
# 3. Locate Reference Library
|
||||
target_lib_name = f"normal_{age}_{sex}.blend"
|
||||
target_lib_path = os.path.join(lib_directory, target_lib_name)
|
||||
rig_name = str(sex)
|
||||
|
||||
if not os.path.exists(target_lib_path):
|
||||
if target_lib_name == "normal_adult_male.blend":
|
||||
target_lib_name = "edited-normal-male.blend"
|
||||
elif target_lib_name == "normal_adult_female.blend":
|
||||
target_lib_name = "edited-normal-female.blend"
|
||||
rig_name = age_profiles.armature_name(age, sex)
|
||||
if explicit_body and os.path.exists(explicit_body):
|
||||
target_lib_path = explicit_body
|
||||
else:
|
||||
target_lib_name = f"normal_{age}_{sex}.blend"
|
||||
target_lib_path = os.path.join(lib_directory, target_lib_name)
|
||||
if not os.path.exists(target_lib_path):
|
||||
print(f"Error: Library {target_lib_path} not found")
|
||||
continue
|
||||
if age == "adult":
|
||||
fallback = {
|
||||
"male": "edited-normal-male.blend",
|
||||
"female": "edited-normal-female.blend",
|
||||
}.get(sex, "")
|
||||
target_lib_path = os.path.join(lib_directory, fallback)
|
||||
if not os.path.exists(target_lib_path):
|
||||
print(f"Error: Library {target_lib_path} not found")
|
||||
continue
|
||||
|
||||
# 4. Append Weights Source and Rig
|
||||
with bpy.data.libraries.load(target_lib_path) as (data_from, data_to):
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Structural validator for generated character age assets.
|
||||
|
||||
Run after the character build (the CMake target ``character-assets-check``
|
||||
does this automatically)::
|
||||
|
||||
python3 assets/blender/characters/validate_age_assets.py \
|
||||
--characters-dir build-vscode/characters
|
||||
|
||||
Checks:
|
||||
* every age/sex stem has the expected body skeleton and catalog slots;
|
||||
* catalog ages are canonical and clothing layers are present;
|
||||
* no duplicate resource basenames across the flat Characters namespace;
|
||||
* LOD atlas/material files exist;
|
||||
* material texture references resolve to a staged texture (warning only).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
if THIS_DIR not in sys.path:
|
||||
sys.path.insert(0, THIS_DIR)
|
||||
import age_profiles
|
||||
|
||||
|
||||
def fail(msg, errors):
|
||||
errors.append(msg)
|
||||
print("FAIL: " + msg)
|
||||
|
||||
|
||||
def warn(msg, warnings):
|
||||
warnings.append(msg)
|
||||
print("WARN: " + msg)
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument("--characters-dir", required=True)
|
||||
p.add_argument("--ages", default="",
|
||||
help="semicolon/comma separated age bands to check "
|
||||
"(default: all canonical bands)")
|
||||
args = p.parse_args()
|
||||
root = os.path.abspath(args.characters_dir)
|
||||
errors = []
|
||||
warnings = []
|
||||
if not os.path.isdir(root):
|
||||
print("FAIL: characters dir not found: " + root)
|
||||
return 1
|
||||
|
||||
dirs = sorted(d for d in os.listdir(root)
|
||||
if os.path.isdir(os.path.join(root, d)))
|
||||
by_stem = {}
|
||||
for d in dirs:
|
||||
by_stem[d] = os.path.join(root, d)
|
||||
|
||||
ages = age_profiles.CANONICAL_AGES
|
||||
if args.ages:
|
||||
ages = [a.strip() for a in re.split(r"[;,]", args.ages)
|
||||
if a.strip()]
|
||||
expected_stems = []
|
||||
for age in ages:
|
||||
for sex in ("male", "female"):
|
||||
expected_stems.append(age_profiles.stem(age, sex))
|
||||
|
||||
for stem in expected_stems:
|
||||
d = by_stem.get(stem)
|
||||
if d is None:
|
||||
fail(f"missing output directory: {stem}", errors)
|
||||
continue
|
||||
cat_files = sorted(glob.glob(os.path.join(d, "body_part_*.json")))
|
||||
if not cat_files:
|
||||
fail(f"no body_part_*.json in {stem}", errors)
|
||||
continue
|
||||
entries = []
|
||||
for f in cat_files:
|
||||
try:
|
||||
with open(f) as fh:
|
||||
e = json.load(fh)
|
||||
except Exception as exc:
|
||||
fail(f"unreadable catalog {f}: {exc}", errors)
|
||||
continue
|
||||
entries.append(e)
|
||||
ages = {age_profiles.normalize_age(e.get("age", ""))
|
||||
for e in entries}
|
||||
if ages != {stem.split("-")[0] if "-" in stem else "adult"}:
|
||||
fail(f"{stem}: catalog ages {sorted(ages)} do not match stem",
|
||||
errors)
|
||||
slots = {}
|
||||
for e in entries:
|
||||
slots.setdefault(e.get("slot", ""), []).append(e)
|
||||
for req in ("face", "top", "bottom", "feet"):
|
||||
if req not in slots:
|
||||
fail(f"{stem}: missing required slot '{req}'", errors)
|
||||
for req in ("top", "bottom", "feet"):
|
||||
if not any(int(e.get("layer", 0)) == 2
|
||||
for e in slots.get(req, [])):
|
||||
fail(f"{stem}: no layer-2 {req} garment", errors)
|
||||
if "hair" not in slots:
|
||||
warn(f"{stem}: no hair slot (bald band)", warnings)
|
||||
|
||||
skel = os.path.join(d, stem + ".skeleton")
|
||||
if not os.path.exists(skel):
|
||||
fail(f"{stem}: missing body skeleton {stem}.skeleton", errors)
|
||||
|
||||
atlas = os.path.join(d, f"charlod_{stem}-atlas.png")
|
||||
if not os.path.exists(atlas):
|
||||
fail(f"{stem}: missing LOD atlas charlod_{stem}-atlas.png",
|
||||
errors)
|
||||
mat = os.path.join(d, f"charlod_{stem}.material")
|
||||
if not os.path.exists(mat):
|
||||
warn(f"{stem}: missing LOD material charlod_{stem}.material",
|
||||
warnings)
|
||||
|
||||
# Resource basename uniqueness across the flat Characters namespace.
|
||||
seen = {}
|
||||
exts = (".mesh", ".skeleton", ".material", ".png", ".json")
|
||||
for d in dirs:
|
||||
for f in os.listdir(os.path.join(root, d)):
|
||||
if not f.endswith(exts):
|
||||
continue
|
||||
if f.startswith("OgreXMLConverter") or f.startswith(
|
||||
"OgreMeshUpgrader"):
|
||||
continue
|
||||
seen.setdefault(f, []).append(d)
|
||||
for name, dirs_with in sorted(seen.items()):
|
||||
if len(dirs_with) > 1:
|
||||
fail("duplicate resource basename '%s' in: %s" %
|
||||
(name, ", ".join(dirs_with)), errors)
|
||||
|
||||
# Material texture references: collect staged texture names.
|
||||
textures = set()
|
||||
for d in dirs:
|
||||
for f in os.listdir(os.path.join(root, d)):
|
||||
if f.lower().endswith((".png", ".jpg", ".jpeg", ".tga",
|
||||
".dds")):
|
||||
textures.add(f)
|
||||
for d in dirs:
|
||||
for f in glob.glob(os.path.join(root, d, "*.material")):
|
||||
with open(f, errors="ignore") as fh:
|
||||
for line in fh:
|
||||
m = re.match(r"\s*texture\s+(\S+)", line)
|
||||
if not m:
|
||||
continue
|
||||
tex = m.group(1)
|
||||
if tex not in textures:
|
||||
warn(f"material {os.path.basename(f)} references "
|
||||
f"missing texture {tex}", warnings)
|
||||
|
||||
print()
|
||||
print(f"checked {len(expected_stems)} stems in {root}")
|
||||
print(f"errors: {len(errors)}, warnings: {len(warnings)}")
|
||||
if errors:
|
||||
print("RESULT: FAIL")
|
||||
return 1
|
||||
print("RESULT: PASS")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user