Feat/outline thickness and tp weapon hold #22
@@ -52,8 +52,16 @@ That is exactly what makes the bug invisible: **the number you normalised always
|
||||
comes out right, whether or not it was the right number.** A check on "is the
|
||||
height 1.75" passes on all seven of these.
|
||||
|
||||
**Rule:** derive the up axis from the SKELETON (hips → head), not from a
|
||||
convention, and rotate the model upright before scaling anything.
|
||||
**Rule:** derive the up axis from the SKELETON, and rotate the model upright
|
||||
before scaling anything. `flatten_and_scale()` now does this.
|
||||
|
||||
**Measure it from the FEET to the HIPS, not from the hips to the head.** The head
|
||||
is not a reliable landmark: the spine walk ends on whatever the last non-cosmetic
|
||||
bone in the chain is, and on a rig with a facial skeleton that can be a bone
|
||||
sitting BELOW the hips. Momo's did, so the first version of this fix stood her
|
||||
neatly on her head — correct size, correct proportions, upside down. Feet cannot
|
||||
be mistaken; they are the bottom of a standing character on every rig, and
|
||||
`foot.L/R` have resolved on every source met so far.
|
||||
|
||||
**And compare it to WORLD up, not to the model's own proportions.** The obvious
|
||||
test — "is the spine the longest axis of the bounding box?" — catches nothing
|
||||
@@ -172,7 +180,25 @@ than judging by eye.
|
||||
|
||||
---
|
||||
|
||||
## 6. Known-unsolved, and honestly so
|
||||
## 6. Some sources are not salvageable, and the gate should say so
|
||||
|
||||
**Hit:** hikari. She failed every way at once — stretched and warped, tiny, far
|
||||
away, gun backwards. Her rig has been through at least two toolchains: her
|
||||
cosmetic bones are zero-length terminators, she carried a second armature with
|
||||
its own clips, and her feet and her spine disagree about which way is up, so the
|
||||
stand-up correction cannot resolve her either. She is now REJECTED by the gate
|
||||
and removed from the roster rather than shipped broken.
|
||||
|
||||
**Rule:** a source that fails several unrelated checks is not a tuning problem,
|
||||
it is a bad file. Spend the effort on finding a cleaner source, not on repairing
|
||||
this one — and make sure the gate blocks it, because the failure mode before this
|
||||
work was that everything passed and the breakage was only visible in game.
|
||||
|
||||
The vetting snippet in `separation.md` catches most of these before download:
|
||||
several meshes, 50+ joints, cosmetic bones present. Add a look for duplicate
|
||||
armatures and for bone chains whose bones are all at the same position.
|
||||
|
||||
## 7. Known-unsolved, and honestly so
|
||||
|
||||
Taila's legs still clip through the front of her skirt in a run, slide and dash
|
||||
(~95 mm). See `cloth-and-hair.md`. The solver sees the contact and pushes on it
|
||||
|
||||
Binary file not shown.
+1184
-1184
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "school anime girl (rigged)",
|
||||
"uid": "6eaf44ff6ef7461583a5eccc301b88fc",
|
||||
"author": "hikari",
|
||||
"author_url": "https://sketchfab.com/hikari0135",
|
||||
"license": "CC Attribution",
|
||||
"license_slug": "by",
|
||||
"source_url": "https://sketchfab.com/3d-models/school-anime-girl-rigged-6eaf44ff6ef7461583a5eccc301b88fc"
|
||||
}
|
||||
@@ -1,731 +0,0 @@
|
||||
{
|
||||
"roles": {
|
||||
"hips": "Hips",
|
||||
"head": "Bone.003_end",
|
||||
"neck": "Neck",
|
||||
"spine": [
|
||||
"Spine",
|
||||
"Chest",
|
||||
"Upper Chest",
|
||||
"Neck",
|
||||
"Head",
|
||||
"Bone.003",
|
||||
"Bone.003_end"
|
||||
],
|
||||
"shoulder.R": "Right shoulder",
|
||||
"shoulder.L": "Left shoulder",
|
||||
"foot.R": "Right ankle",
|
||||
"foot.L": "Left ankle",
|
||||
"hand.R": "Right wrist",
|
||||
"hand.L": "Left wrist",
|
||||
"forearm.R": "Right elbow",
|
||||
"forearm.L": "Left elbow",
|
||||
"shin.R": "Right knee",
|
||||
"shin.L": "Left knee",
|
||||
"toe.R": "Right toe",
|
||||
"toe.L": "Left toe_end",
|
||||
"upper_arm.R": "Right arm",
|
||||
"upper_arm.L": "Left arm",
|
||||
"thigh.L": "Left leg",
|
||||
"thigh.R": "Right leg"
|
||||
},
|
||||
"chains": [
|
||||
{
|
||||
"class": "breast",
|
||||
"root_parent": "Upper Chest",
|
||||
"bones": [
|
||||
"Breast_L",
|
||||
"J_Sec_L_Bust2",
|
||||
"J_Sec_L_Bust2_end"
|
||||
],
|
||||
"tips": [
|
||||
[
|
||||
-0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
2e-05,
|
||||
5e-05,
|
||||
-0.0
|
||||
],
|
||||
[
|
||||
2e-05,
|
||||
5e-05,
|
||||
-0.0
|
||||
]
|
||||
],
|
||||
"hulls": [
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
],
|
||||
"neighbours": [
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "breast",
|
||||
"root_parent": "Upper Chest",
|
||||
"bones": [
|
||||
"Breast_R",
|
||||
"J_Sec_R_Bust2",
|
||||
"J_Sec_R_Bust2_end"
|
||||
],
|
||||
"tips": [
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
-0.0
|
||||
],
|
||||
[
|
||||
-3e-05,
|
||||
5e-05,
|
||||
-1e-05
|
||||
],
|
||||
[
|
||||
-3e-05,
|
||||
5e-05,
|
||||
-1e-05
|
||||
]
|
||||
],
|
||||
"hulls": [
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
],
|
||||
"neighbours": [
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "hair",
|
||||
"root_parent": "Head",
|
||||
"bones": [
|
||||
"J_Sec_Hair1_01",
|
||||
"J_Sec_Hair2_01",
|
||||
"J_Sec_Hair2_01_end"
|
||||
],
|
||||
"tips": [
|
||||
[
|
||||
-0.0,
|
||||
-0.0,
|
||||
-0.0
|
||||
],
|
||||
[
|
||||
3e-05,
|
||||
3e-05,
|
||||
-4e-05
|
||||
],
|
||||
[
|
||||
3e-05,
|
||||
3e-05,
|
||||
-4e-05
|
||||
]
|
||||
],
|
||||
"hulls": [
|
||||
[
|
||||
[
|
||||
-0.0,
|
||||
-0.0,
|
||||
-0.0
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
-0.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
[]
|
||||
],
|
||||
"neighbours": [
|
||||
{
|
||||
"J_Sec_Hair2_01": 14.335576471341188
|
||||
},
|
||||
{
|
||||
"J_Sec_Hair1_01": 14.335576471341188
|
||||
},
|
||||
{}
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "hair",
|
||||
"root_parent": "Head",
|
||||
"bones": [
|
||||
"J_Sec_Hair1_02",
|
||||
"J_Sec_Hair2_02",
|
||||
"J_Sec_Hair2_02_end"
|
||||
],
|
||||
"tips": [
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
-0.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
2e-05,
|
||||
-5e-05
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
2e-05,
|
||||
-5e-05
|
||||
]
|
||||
],
|
||||
"hulls": [
|
||||
[
|
||||
[
|
||||
-0.0,
|
||||
0.0,
|
||||
-0.0
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
-0.0,
|
||||
0.0,
|
||||
-0.0
|
||||
]
|
||||
],
|
||||
[]
|
||||
],
|
||||
"neighbours": [
|
||||
{
|
||||
"J_Sec_Hair2_02": 13.644314260335037
|
||||
},
|
||||
{
|
||||
"J_Sec_Hair1_02": 13.644314260335037
|
||||
},
|
||||
{}
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "hair",
|
||||
"root_parent": "Head",
|
||||
"bones": [
|
||||
"J_Sec_Hair1_03",
|
||||
"J_Sec_Hair2_03",
|
||||
"J_Sec_Hair2_03_end"
|
||||
],
|
||||
"tips": [
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
-0.0
|
||||
],
|
||||
[
|
||||
1e-05,
|
||||
-0.0,
|
||||
-6e-05
|
||||
],
|
||||
[
|
||||
1e-05,
|
||||
-0.0,
|
||||
-6e-05
|
||||
]
|
||||
],
|
||||
"hulls": [
|
||||
[
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
-0.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
[]
|
||||
],
|
||||
"neighbours": [
|
||||
{
|
||||
"J_Sec_Hair2_03": 29.697010893706615
|
||||
},
|
||||
{
|
||||
"J_Sec_Hair1_03": 29.697010893706615
|
||||
},
|
||||
{}
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "hair",
|
||||
"root_parent": "Head",
|
||||
"bones": [
|
||||
"J_Sec_Hair1_04",
|
||||
"J_Sec_Hair2_04",
|
||||
"J_Sec_Hair2_04_end"
|
||||
],
|
||||
"tips": [
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
2e-05,
|
||||
1e-05,
|
||||
-6e-05
|
||||
],
|
||||
[
|
||||
2e-05,
|
||||
1e-05,
|
||||
-6e-05
|
||||
]
|
||||
],
|
||||
"hulls": [
|
||||
[
|
||||
[
|
||||
-0.0,
|
||||
-0.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
-0.0
|
||||
]
|
||||
],
|
||||
[]
|
||||
],
|
||||
"neighbours": [
|
||||
{
|
||||
"J_Sec_Hair2_04": 11.592366459380674
|
||||
},
|
||||
{
|
||||
"J_Sec_Hair1_04": 11.592366459380674
|
||||
},
|
||||
{}
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "hair",
|
||||
"root_parent": "Head",
|
||||
"bones": [
|
||||
"J_Sec_Hair1_05",
|
||||
"J_Sec_Hair2_05",
|
||||
"J_Sec_Hair2_05_end"
|
||||
],
|
||||
"tips": [
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
-0.0
|
||||
],
|
||||
[
|
||||
1e-05,
|
||||
2e-05,
|
||||
-6e-05
|
||||
],
|
||||
[
|
||||
1e-05,
|
||||
2e-05,
|
||||
-6e-05
|
||||
]
|
||||
],
|
||||
"hulls": [
|
||||
[
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
-0.0
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
-0.0,
|
||||
0.0,
|
||||
-0.0
|
||||
]
|
||||
],
|
||||
[]
|
||||
],
|
||||
"neighbours": [
|
||||
{
|
||||
"J_Sec_Hair2_05": 11.876289952662745
|
||||
},
|
||||
{
|
||||
"J_Sec_Hair1_05": 11.876289952662745
|
||||
},
|
||||
{}
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "hair",
|
||||
"root_parent": "Head",
|
||||
"bones": [
|
||||
"J_Sec_Hair1_06",
|
||||
"J_Sec_Hair2_06",
|
||||
"J_Sec_Hair3_06",
|
||||
"J_Sec_Hair4_06",
|
||||
"J_Sec_Hair5_06",
|
||||
"J_Sec_Hair5_06_end"
|
||||
],
|
||||
"tips": [
|
||||
[
|
||||
-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,
|
||||
3e-05,
|
||||
-5e-05
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
3e-05,
|
||||
-5e-05
|
||||
]
|
||||
],
|
||||
"hulls": [
|
||||
[
|
||||
[
|
||||
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
|
||||
]
|
||||
],
|
||||
[]
|
||||
],
|
||||
"neighbours": [
|
||||
{
|
||||
"J_Sec_Hair2_06": 35.917357232526754
|
||||
},
|
||||
{
|
||||
"J_Sec_Hair3_06": 35.52913589468082,
|
||||
"J_Sec_Hair1_06": 35.917357232526754
|
||||
},
|
||||
{
|
||||
"J_Sec_Hair4_06": 40.302343821289796,
|
||||
"J_Sec_Hair2_06": 35.52913589468082
|
||||
},
|
||||
{
|
||||
"J_Sec_Hair3_06": 40.302343821289796,
|
||||
"J_Sec_Hair5_06": 40.950213036078026
|
||||
},
|
||||
{
|
||||
"J_Sec_Hair4_06": 40.950213036078026
|
||||
},
|
||||
{}
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "hair",
|
||||
"root_parent": "Head",
|
||||
"bones": [
|
||||
"J_Sec_Hair1_07",
|
||||
"J_Sec_Hair2_07",
|
||||
"J_Sec_Hair3_07",
|
||||
"J_Sec_Hair4_07",
|
||||
"J_Sec_Hair5_07",
|
||||
"J_Sec_Hair5_07_end"
|
||||
],
|
||||
"tips": [
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
-0.0,
|
||||
0.0,
|
||||
-0.0
|
||||
],
|
||||
[
|
||||
-0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
-0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
3e-05,
|
||||
3e-05,
|
||||
-3e-05
|
||||
],
|
||||
[
|
||||
3e-05,
|
||||
3e-05,
|
||||
-3e-05
|
||||
]
|
||||
],
|
||||
"hulls": [
|
||||
[
|
||||
[
|
||||
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
|
||||
]
|
||||
],
|
||||
[]
|
||||
],
|
||||
"neighbours": [
|
||||
{
|
||||
"J_Sec_Hair2_07": 35.51566362438205
|
||||
},
|
||||
{
|
||||
"J_Sec_Hair3_07": 35.31215403867316,
|
||||
"J_Sec_Hair1_07": 35.51566362438205
|
||||
},
|
||||
{
|
||||
"J_Sec_Hair4_07": 44.27519591590416,
|
||||
"J_Sec_Hair2_07": 35.31215403867316
|
||||
},
|
||||
{
|
||||
"J_Sec_Hair3_07": 44.27519591590416,
|
||||
"J_Sec_Hair5_07": 40.6256217681383
|
||||
},
|
||||
{
|
||||
"J_Sec_Hair4_07": 40.6256217681383
|
||||
},
|
||||
{}
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "hair",
|
||||
"root_parent": "Head",
|
||||
"bones": [
|
||||
"J_Sec_Hair1_08",
|
||||
"J_Sec_Hair2_08",
|
||||
"J_Sec_Hair3_08",
|
||||
"J_Sec_Hair3_08_end"
|
||||
],
|
||||
"tips": [
|
||||
[
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
-0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
4e-05,
|
||||
-4e-05,
|
||||
-1e-05
|
||||
],
|
||||
[
|
||||
4e-05,
|
||||
-4e-05,
|
||||
-1e-05
|
||||
]
|
||||
],
|
||||
"hulls": [
|
||||
[
|
||||
[
|
||||
-0.0,
|
||||
-0.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
-0.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
-0.0,
|
||||
-0.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
[]
|
||||
],
|
||||
"neighbours": [
|
||||
{
|
||||
"J_Sec_Hair2_08": 2.5078807040660998
|
||||
},
|
||||
{
|
||||
"J_Sec_Hair1_08": 2.5078807040660998,
|
||||
"J_Sec_Hair3_08": 2.222614413427433
|
||||
},
|
||||
{
|
||||
"J_Sec_Hair2_08": 2.222614413427433
|
||||
},
|
||||
{}
|
||||
]
|
||||
}
|
||||
],
|
||||
"twist": [
|
||||
{
|
||||
"bone": "Bone.004",
|
||||
"parent": "Head",
|
||||
"child": "Bone.004_end"
|
||||
},
|
||||
{
|
||||
"bone": "Bone.005",
|
||||
"parent": "Head",
|
||||
"child": "Bone.005_end"
|
||||
},
|
||||
{
|
||||
"bone": "Bone.006",
|
||||
"parent": "Head",
|
||||
"child": "Bone.006_end"
|
||||
},
|
||||
{
|
||||
"bone": "Bone.007",
|
||||
"parent": "Head",
|
||||
"child": "Bone.007_end"
|
||||
},
|
||||
{
|
||||
"bone": "Bone.008",
|
||||
"parent": "Head",
|
||||
"child": "Bone.008_end"
|
||||
},
|
||||
{
|
||||
"bone": "Bone.009",
|
||||
"parent": "Head",
|
||||
"child": "Bone.009_end"
|
||||
},
|
||||
{
|
||||
"bone": "Bone.010",
|
||||
"parent": "Head",
|
||||
"child": "Bone.010_end"
|
||||
},
|
||||
{
|
||||
"bone": "Bone.011",
|
||||
"parent": "Head",
|
||||
"child": "Bone.011_end"
|
||||
},
|
||||
{
|
||||
"bone": "Bone.012",
|
||||
"parent": "Head",
|
||||
"child": "Bone.012_end"
|
||||
},
|
||||
{
|
||||
"bone": "Bone.013",
|
||||
"parent": "Head",
|
||||
"child": "Bone.013_end"
|
||||
},
|
||||
{
|
||||
"bone": "Bone.001",
|
||||
"parent": "Upper Chest",
|
||||
"child": "Bone.001_end"
|
||||
},
|
||||
{
|
||||
"bone": "Bone.002",
|
||||
"parent": "Upper Chest",
|
||||
"child": "Bone.002_end"
|
||||
}
|
||||
],
|
||||
"colliders": [],
|
||||
"weights_authored": true,
|
||||
"driven_bones": [
|
||||
"Bone.003",
|
||||
"Bone.003_end",
|
||||
"Chest",
|
||||
"Head",
|
||||
"Hips",
|
||||
"Left ankle",
|
||||
"Left arm",
|
||||
"Left elbow",
|
||||
"Left knee",
|
||||
"Left leg",
|
||||
"Left shoulder",
|
||||
"Left toe_end",
|
||||
"Left wrist",
|
||||
"Neck",
|
||||
"Right ankle",
|
||||
"Right arm",
|
||||
"Right elbow",
|
||||
"Right knee",
|
||||
"Right leg",
|
||||
"Right shoulder",
|
||||
"Right toe",
|
||||
"Right wrist",
|
||||
"Spine",
|
||||
"Upper Chest"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 238 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 345 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 337 KiB |
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -41,13 +41,6 @@
|
||||
"description": "Momo Ayase, DANDADAN (CC-BY, HiGuys920)",
|
||||
"model": "res://assets/characters/skins/momo.glb",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"id": "hikari",
|
||||
"name": "Hikari",
|
||||
"description": "School anime girl \u2014 VRoid (CC-BY, hikari)",
|
||||
"model": "res://assets/characters/skins/hikari.glb",
|
||||
"unlocked": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -922,7 +922,7 @@ func set_weapon(script_path: String) -> void:
|
||||
_measure_weapon(w)
|
||||
)
|
||||
|
||||
var hand_idx := _find_bone(["RightHand", "Hand_R", "hand.R"])
|
||||
var hand_idx := _role_bone("hand.R", ["RightHand", "Hand_R", "hand.R"])
|
||||
if hand_idx >= 0:
|
||||
_weapon_attachment = BoneAttachment3D.new()
|
||||
_weapon_attachment.name = "WeaponAttachment"
|
||||
@@ -995,6 +995,27 @@ func _measure_weapon(w: Node3D) -> void:
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
## A bone by its resolved ROLE, falling back to name matching.
|
||||
##
|
||||
## tools/rig_map.py resolves every rig to roles and writes them to the sidecar so
|
||||
## that nothing downstream has to guess a bone name — but a hardcoded lookup here
|
||||
## meant four characters could not hold a gun. Their hands resolve perfectly as
|
||||
## "Right wrist" and "J_Bip_R_Hand"; none of them matches a spelling this file
|
||||
## knew, so set_weapon fell through to parenting the weapon to the model root at
|
||||
## a fixed chest offset, where it is not attached to the character at all.
|
||||
##
|
||||
## The fallback stays for a model with no sidecar. It must never be the first
|
||||
## thing tried.
|
||||
func _role_bone(role: String, fallbacks: Array) -> int:
|
||||
var roles: Dictionary = _rig_info.get("roles", {})
|
||||
var actual := String(roles.get(role, ""))
|
||||
if actual != "" and skeleton:
|
||||
var b := skeleton.find_bone(actual)
|
||||
if b >= 0:
|
||||
return b
|
||||
return _find_bone(fallbacks)
|
||||
|
||||
|
||||
func _find_bone(name_parts: Array) -> int:
|
||||
if not skeleton:
|
||||
return -1
|
||||
|
||||
+52
-2
@@ -335,7 +335,7 @@ def rebuild_hierarchy(arm, roles):
|
||||
# ----------------------------------------------------------------- normalize
|
||||
|
||||
|
||||
def flatten_and_scale(arm, meshes, target_height):
|
||||
def flatten_and_scale(arm, meshes, target_height, roles=None):
|
||||
"""Bake the import hierarchy away and set the character's real-world size.
|
||||
|
||||
Sketchfab wraps everything in scaled/rotated empties. Left in place they
|
||||
@@ -363,6 +363,56 @@ def flatten_and_scale(arm, meshes, target_height):
|
||||
|
||||
apply_all()
|
||||
|
||||
# STAND THE CHARACTER UP before measuring anything.
|
||||
#
|
||||
# The scale below normalises the bounding box along Z because Z is up in
|
||||
# Blender. For a model that arrives lying along another axis that measures
|
||||
# the character's THICKNESS — about 0.25 m — so it gets scaled by ~7 and left
|
||||
# on its back. One assumption, both symptoms, and invisible afterwards
|
||||
# because the exporter maps Blender Z to glTF Y: "is the height 1.75" comes
|
||||
# out true on a character who is 7.5 m tall lying down.
|
||||
#
|
||||
# Which way is up is not a convention to assume, it is a property of the
|
||||
# skeleton: the head is above the hips. Snapped to the nearest axis rather
|
||||
# than aligned exactly, so a character with a slight lean in their rest pose
|
||||
# is stood up, not straightened.
|
||||
# Measured from the FEET to the HIPS, not from the hips to the head.
|
||||
#
|
||||
# The head is not a reliable landmark. The spine walk ends on whatever the
|
||||
# last non-cosmetic bone in the chain is, and on a rig with a facial skeleton
|
||||
# that can be a bone sitting BELOW the hips — which points this vector
|
||||
# downwards and stands the character neatly on her head. Momo did exactly
|
||||
# that. Feet cannot be mistaken: they are the bottom of a standing character
|
||||
# on every rig, and foot.L/R resolve on every source met so far.
|
||||
if roles is not None and roles.hips:
|
||||
hips_b = arm.data.bones.get(roles.hips)
|
||||
feet = [arm.data.bones.get(roles.limb.get(("foot", s), ""))
|
||||
for s in ("L", "R")]
|
||||
feet = [f for f in feet if f]
|
||||
ref = None
|
||||
if feet:
|
||||
ref = sum((f.head_local for f in feet), Vector()) / len(feet)
|
||||
elif roles.head and arm.data.bones.get(roles.head):
|
||||
# No feet — fall back to the head, and accept the risk above.
|
||||
ref = hips_b.head_local - (
|
||||
arm.data.bones[roles.head].head_local - hips_b.head_local)
|
||||
if hips_b and ref is not None:
|
||||
d = hips_b.head_local - ref
|
||||
k = max(range(3), key=lambda i: abs(d[i]))
|
||||
src = Vector((0.0, 0.0, 0.0))
|
||||
src[k] = 1.0 if d[k] > 0 else -1.0
|
||||
up = Vector((0.0, 0.0, 1.0))
|
||||
if src.dot(up) < 0.999:
|
||||
axis = src.cross(up)
|
||||
if axis.length < 1e-6:
|
||||
axis = Vector((1.0, 0.0, 0.0)) # upside down
|
||||
R = Matrix.Rotation(src.angle(up), 4, axis.normalized())
|
||||
for obj in [arm] + meshes:
|
||||
obj.matrix_world = R @ obj.matrix_world
|
||||
apply_all()
|
||||
print(f"Stood the character up: feet->hips ran along "
|
||||
f"{'XYZ'[k]}{'+' if d[k] > 0 else '-'}, rotated to Z+")
|
||||
|
||||
lo = Vector((1e9, 1e9, 1e9))
|
||||
hi = -lo.copy()
|
||||
for m in meshes:
|
||||
@@ -1047,7 +1097,7 @@ def main():
|
||||
rebuild_hierarchy(arm, roles)
|
||||
subdivide_cloth_panels(arm, meshes, roles)
|
||||
unbind_cloth_from_legs(arm, meshes, roles)
|
||||
flatten_and_scale(arm, meshes, TARGET_HEIGHT)
|
||||
flatten_and_scale(arm, meshes, TARGET_HEIGHT, roles)
|
||||
fix_unlit_materials(meshes)
|
||||
roles = RigRoles(arm) # rest positions moved; re-read
|
||||
|
||||
|
||||
Reference in New Issue
Block a user