chore(assets): Synty intake spike — scripts, evidence logs, verdict data (T-1089)
Bridge-C evidence spike: unitypackage extraction, rest-pose recon, garment transplant onto the Quaternius rig (Data Transfer POLYINTERP_NEAREST), 11-body batch fit, Godot QA scenes. Technical PASS; route nonetheless REJECTED by product call (cost, baked body-segment modularity, style) — full trail on T-1089. Salvage: source-agnostic transplant/batch-fit scripts (the G1-family pipeline for any donor mesh) and the T-1090 discovery (5 body types misrender bare). Extracted vendor payload (spikes/**/raw/) now gitignored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -55,6 +55,9 @@ spikes/**/*.blend
|
||||
spikes/**/*.exr
|
||||
spikes/**/*.hdr
|
||||
spikes/**/*.zip
|
||||
# Extracted vendor payload (reproducible from docs/assets/downloads/ archives
|
||||
# via the spike's own extraction script) — includes Unity .asset/.sk metadata.
|
||||
spikes/**/raw/
|
||||
|
||||
# Planet generator intermediates
|
||||
*.tmp.npz
|
||||
|
||||
@@ -0,0 +1,793 @@
|
||||
{
|
||||
"garment_glb": "/var/mnt/data/projects/settled-reach/spikes/synty-intake/out/SK_SCFI_CIVL_09_10TORS_HU01_quaternius.glb",
|
||||
"bodies": {
|
||||
"average_m": {
|
||||
"garment_verts": 2155,
|
||||
"segments_used": [
|
||||
"seg_torso",
|
||||
"seg_hips",
|
||||
"seg_neck",
|
||||
"seg_arm_upper_l",
|
||||
"seg_arm_upper_r",
|
||||
"seg_leg_upper_l",
|
||||
"seg_leg_upper_r"
|
||||
],
|
||||
"affine": {
|
||||
"scale_xy": 1.0,
|
||||
"scale_z": 1.0,
|
||||
"translate": [
|
||||
-0.0,
|
||||
-0.0,
|
||||
-0.0
|
||||
]
|
||||
},
|
||||
"aabb_post_affine": {
|
||||
"min": [
|
||||
-0.2082,
|
||||
-0.1677,
|
||||
0.8251
|
||||
],
|
||||
"max": [
|
||||
0.2305,
|
||||
0.1739,
|
||||
1.59
|
||||
]
|
||||
},
|
||||
"warp": {
|
||||
"max_displacement_mm": 13.2,
|
||||
"smooth_iterations": 20
|
||||
},
|
||||
"zero_weight_verts": 0,
|
||||
"vgroups": 65,
|
||||
"edge_distortion": {
|
||||
"max_stretch": 1.058,
|
||||
"max_shrink": 0.919,
|
||||
"edges_gt_2x": 0,
|
||||
"edges_lt_0.5x": 0,
|
||||
"edges_total": 3799
|
||||
},
|
||||
"aabb_post_fit": {
|
||||
"min": [
|
||||
-0.2083,
|
||||
-0.1707,
|
||||
0.8236
|
||||
],
|
||||
"max": [
|
||||
0.2337,
|
||||
0.1739,
|
||||
1.59
|
||||
]
|
||||
},
|
||||
"signed_distance": {
|
||||
"verts_sampled": 2155,
|
||||
"min_signed_mm": -74.86,
|
||||
"p05_signed_mm": -6.44,
|
||||
"median_signed_mm": 36.03,
|
||||
"max_signed_mm": 96.8,
|
||||
"verts_inside_gt_3mm": 135,
|
||||
"pct_inside": 6.26
|
||||
},
|
||||
"export": {
|
||||
"path": "/var/mnt/data/projects/settled-reach/spikes/synty-intake/out/bodies/SK_SCFI_CIVL_09_10TORS_HU01_average_m.glb",
|
||||
"size_kb": 653
|
||||
},
|
||||
"result": "OK"
|
||||
},
|
||||
"average_f": {
|
||||
"garment_verts": 2155,
|
||||
"segments_used": [
|
||||
"seg_torso",
|
||||
"seg_hips",
|
||||
"seg_neck",
|
||||
"seg_arm_upper_l",
|
||||
"seg_arm_upper_r",
|
||||
"seg_leg_upper_l",
|
||||
"seg_leg_upper_r"
|
||||
],
|
||||
"affine": {
|
||||
"scale_xy": 0.79,
|
||||
"scale_z": 0.96031,
|
||||
"translate": [
|
||||
0.0,
|
||||
0.01173,
|
||||
0.02037
|
||||
]
|
||||
},
|
||||
"aabb_post_affine": {
|
||||
"min": [
|
||||
-0.1645,
|
||||
-0.1207,
|
||||
0.8127
|
||||
],
|
||||
"max": [
|
||||
0.1821,
|
||||
0.1491,
|
||||
1.5473
|
||||
]
|
||||
},
|
||||
"warp": {
|
||||
"max_displacement_mm": 65.3,
|
||||
"smooth_iterations": 20
|
||||
},
|
||||
"zero_weight_verts": 0,
|
||||
"vgroups": 65,
|
||||
"edge_distortion": {
|
||||
"max_stretch": 1.712,
|
||||
"max_shrink": 0.762,
|
||||
"edges_gt_2x": 0,
|
||||
"edges_lt_0.5x": 0,
|
||||
"edges_total": 3799
|
||||
},
|
||||
"aabb_post_fit": {
|
||||
"min": [
|
||||
-0.169,
|
||||
-0.1566,
|
||||
0.8021
|
||||
],
|
||||
"max": [
|
||||
0.2341,
|
||||
0.1404,
|
||||
1.5467
|
||||
]
|
||||
},
|
||||
"signed_distance": {
|
||||
"verts_sampled": 2155,
|
||||
"min_signed_mm": -67.38,
|
||||
"p05_signed_mm": -12.82,
|
||||
"median_signed_mm": 26.3,
|
||||
"max_signed_mm": 85.13,
|
||||
"verts_inside_gt_3mm": 206,
|
||||
"pct_inside": 9.56
|
||||
},
|
||||
"export": {
|
||||
"path": "/var/mnt/data/projects/settled-reach/spikes/synty-intake/out/bodies/SK_SCFI_CIVL_09_10TORS_HU01_average_f.glb",
|
||||
"size_kb": 653
|
||||
},
|
||||
"result": "OK"
|
||||
},
|
||||
"muscular_m": {
|
||||
"garment_verts": 2155,
|
||||
"segments_used": [
|
||||
"seg_torso",
|
||||
"seg_hips",
|
||||
"seg_neck",
|
||||
"seg_arm_upper_l",
|
||||
"seg_arm_upper_r",
|
||||
"seg_leg_upper_l",
|
||||
"seg_leg_upper_r"
|
||||
],
|
||||
"affine": {
|
||||
"scale_xy": 1.1048,
|
||||
"scale_z": 1.0,
|
||||
"translate": [
|
||||
0.0,
|
||||
-0.00451,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"aabb_post_affine": {
|
||||
"min": [
|
||||
-0.23,
|
||||
-0.1898,
|
||||
0.8251
|
||||
],
|
||||
"max": [
|
||||
0.2546,
|
||||
0.1876,
|
||||
1.59
|
||||
]
|
||||
},
|
||||
"warp": {
|
||||
"max_displacement_mm": 54.7,
|
||||
"smooth_iterations": 20
|
||||
},
|
||||
"zero_weight_verts": 0,
|
||||
"vgroups": 65,
|
||||
"edge_distortion": {
|
||||
"max_stretch": 1.489,
|
||||
"max_shrink": 0.638,
|
||||
"edges_gt_2x": 0,
|
||||
"edges_lt_0.5x": 0,
|
||||
"edges_total": 3799
|
||||
},
|
||||
"aabb_post_fit": {
|
||||
"min": [
|
||||
-0.239,
|
||||
-0.2016,
|
||||
0.8217
|
||||
],
|
||||
"max": [
|
||||
0.2662,
|
||||
0.2002,
|
||||
1.5928
|
||||
]
|
||||
},
|
||||
"signed_distance": {
|
||||
"verts_sampled": 2155,
|
||||
"min_signed_mm": -67.99,
|
||||
"p05_signed_mm": -9.31,
|
||||
"median_signed_mm": 38.97,
|
||||
"max_signed_mm": 110.81,
|
||||
"verts_inside_gt_3mm": 177,
|
||||
"pct_inside": 8.21
|
||||
},
|
||||
"export": {
|
||||
"path": "/var/mnt/data/projects/settled-reach/spikes/synty-intake/out/bodies/SK_SCFI_CIVL_09_10TORS_HU01_muscular_m.glb",
|
||||
"size_kb": 653
|
||||
},
|
||||
"result": "OK"
|
||||
},
|
||||
"muscular_f": {
|
||||
"garment_verts": 2155,
|
||||
"segments_used": [
|
||||
"seg_torso",
|
||||
"seg_hips",
|
||||
"seg_neck",
|
||||
"seg_arm_upper_l",
|
||||
"seg_arm_upper_r",
|
||||
"seg_leg_upper_l",
|
||||
"seg_leg_upper_r"
|
||||
],
|
||||
"affine": {
|
||||
"scale_xy": 0.79,
|
||||
"scale_z": 0.96031,
|
||||
"translate": [
|
||||
0.0,
|
||||
0.01173,
|
||||
0.02037
|
||||
]
|
||||
},
|
||||
"aabb_post_affine": {
|
||||
"min": [
|
||||
-0.1645,
|
||||
-0.1207,
|
||||
0.8127
|
||||
],
|
||||
"max": [
|
||||
0.1821,
|
||||
0.1491,
|
||||
1.5473
|
||||
]
|
||||
},
|
||||
"warp": {
|
||||
"max_displacement_mm": 89.1,
|
||||
"smooth_iterations": 20
|
||||
},
|
||||
"zero_weight_verts": 0,
|
||||
"vgroups": 65,
|
||||
"edge_distortion": {
|
||||
"max_stretch": 1.815,
|
||||
"max_shrink": 0.744,
|
||||
"edges_gt_2x": 0,
|
||||
"edges_lt_0.5x": 0,
|
||||
"edges_total": 3799
|
||||
},
|
||||
"aabb_post_fit": {
|
||||
"min": [
|
||||
-0.1747,
|
||||
-0.1679,
|
||||
0.8022
|
||||
],
|
||||
"max": [
|
||||
0.237,
|
||||
0.1415,
|
||||
1.5473
|
||||
]
|
||||
},
|
||||
"signed_distance": {
|
||||
"verts_sampled": 2155,
|
||||
"min_signed_mm": -72.04,
|
||||
"p05_signed_mm": -12.99,
|
||||
"median_signed_mm": 26.65,
|
||||
"max_signed_mm": 85.26,
|
||||
"verts_inside_gt_3mm": 189,
|
||||
"pct_inside": 8.77
|
||||
},
|
||||
"export": {
|
||||
"path": "/var/mnt/data/projects/settled-reach/spikes/synty-intake/out/bodies/SK_SCFI_CIVL_09_10TORS_HU01_muscular_f.glb",
|
||||
"size_kb": 653
|
||||
},
|
||||
"result": "OK"
|
||||
},
|
||||
"thin_m": {
|
||||
"garment_verts": 2155,
|
||||
"segments_used": [
|
||||
"seg_torso",
|
||||
"seg_neck",
|
||||
"seg_arm_upper_l",
|
||||
"seg_arm_upper_r",
|
||||
"seg_leg_upper_l",
|
||||
"seg_leg_upper_r"
|
||||
],
|
||||
"affine": {
|
||||
"scale_xy": 1.0,
|
||||
"scale_z": 1.0,
|
||||
"translate": [
|
||||
-0.0,
|
||||
-0.0,
|
||||
-0.0
|
||||
]
|
||||
},
|
||||
"aabb_post_affine": {
|
||||
"min": [
|
||||
-0.2082,
|
||||
-0.1677,
|
||||
0.8251
|
||||
],
|
||||
"max": [
|
||||
0.2305,
|
||||
0.1739,
|
||||
1.59
|
||||
]
|
||||
},
|
||||
"warp": {
|
||||
"max_displacement_mm": 148.8,
|
||||
"smooth_iterations": 20
|
||||
},
|
||||
"zero_weight_verts": 0,
|
||||
"vgroups": 65,
|
||||
"edge_distortion": {
|
||||
"max_stretch": 2.091,
|
||||
"max_shrink": 0.232,
|
||||
"edges_gt_2x": 1,
|
||||
"edges_lt_0.5x": 14,
|
||||
"edges_total": 3799
|
||||
},
|
||||
"aabb_post_fit": {
|
||||
"min": [
|
||||
-0.1968,
|
||||
-0.1693,
|
||||
0.8205
|
||||
],
|
||||
"max": [
|
||||
0.212,
|
||||
0.1959,
|
||||
1.5056
|
||||
]
|
||||
},
|
||||
"signed_distance": {
|
||||
"verts_sampled": 2155,
|
||||
"min_signed_mm": -63.51,
|
||||
"p05_signed_mm": -8.58,
|
||||
"median_signed_mm": 40.36,
|
||||
"max_signed_mm": 124.52,
|
||||
"verts_inside_gt_3mm": 126,
|
||||
"pct_inside": 5.85
|
||||
},
|
||||
"export": {
|
||||
"path": "/var/mnt/data/projects/settled-reach/spikes/synty-intake/out/bodies/SK_SCFI_CIVL_09_10TORS_HU01_thin_m.glb",
|
||||
"size_kb": 653
|
||||
},
|
||||
"result": "OK"
|
||||
},
|
||||
"thin_f": {
|
||||
"garment_verts": 2155,
|
||||
"segments_used": [
|
||||
"seg_torso",
|
||||
"seg_neck",
|
||||
"seg_arm_upper_l",
|
||||
"seg_arm_upper_r",
|
||||
"seg_leg_upper_l",
|
||||
"seg_leg_upper_r"
|
||||
],
|
||||
"affine": {
|
||||
"scale_xy": 0.79,
|
||||
"scale_z": 0.96031,
|
||||
"translate": [
|
||||
0.0,
|
||||
0.01173,
|
||||
0.02037
|
||||
]
|
||||
},
|
||||
"aabb_post_affine": {
|
||||
"min": [
|
||||
-0.1645,
|
||||
-0.1207,
|
||||
0.8127
|
||||
],
|
||||
"max": [
|
||||
0.1821,
|
||||
0.1491,
|
||||
1.5473
|
||||
]
|
||||
},
|
||||
"warp": {
|
||||
"max_displacement_mm": 105.2,
|
||||
"smooth_iterations": 20
|
||||
},
|
||||
"zero_weight_verts": 0,
|
||||
"vgroups": 65,
|
||||
"edge_distortion": {
|
||||
"max_stretch": 2.589,
|
||||
"max_shrink": 0.409,
|
||||
"edges_gt_2x": 3,
|
||||
"edges_lt_0.5x": 7,
|
||||
"edges_total": 3799
|
||||
},
|
||||
"aabb_post_fit": {
|
||||
"min": [
|
||||
-0.1598,
|
||||
-0.1652,
|
||||
0.824
|
||||
],
|
||||
"max": [
|
||||
0.223,
|
||||
0.1782,
|
||||
1.4946
|
||||
]
|
||||
},
|
||||
"signed_distance": {
|
||||
"verts_sampled": 2155,
|
||||
"min_signed_mm": -50.52,
|
||||
"p05_signed_mm": -18.1,
|
||||
"median_signed_mm": 25.69,
|
||||
"max_signed_mm": 85.58,
|
||||
"verts_inside_gt_3mm": 286,
|
||||
"pct_inside": 13.27
|
||||
},
|
||||
"export": {
|
||||
"path": "/var/mnt/data/projects/settled-reach/spikes/synty-intake/out/bodies/SK_SCFI_CIVL_09_10TORS_HU01_thin_f.glb",
|
||||
"size_kb": 653
|
||||
},
|
||||
"result": "OK"
|
||||
},
|
||||
"heavy_m": {
|
||||
"garment_verts": 2155,
|
||||
"segments_used": [
|
||||
"seg_torso",
|
||||
"seg_neck",
|
||||
"seg_arm_upper_l",
|
||||
"seg_arm_upper_r",
|
||||
"seg_leg_upper_l",
|
||||
"seg_leg_upper_r"
|
||||
],
|
||||
"affine": {
|
||||
"scale_xy": 1.0,
|
||||
"scale_z": 1.0,
|
||||
"translate": [
|
||||
-0.0,
|
||||
-0.0,
|
||||
-0.0
|
||||
]
|
||||
},
|
||||
"aabb_post_affine": {
|
||||
"min": [
|
||||
-0.2082,
|
||||
-0.1677,
|
||||
0.8251
|
||||
],
|
||||
"max": [
|
||||
0.2305,
|
||||
0.1739,
|
||||
1.59
|
||||
]
|
||||
},
|
||||
"warp": {
|
||||
"max_displacement_mm": 34.6,
|
||||
"smooth_iterations": 20
|
||||
},
|
||||
"zero_weight_verts": 0,
|
||||
"vgroups": 65,
|
||||
"edge_distortion": {
|
||||
"max_stretch": 1.236,
|
||||
"max_shrink": 0.839,
|
||||
"edges_gt_2x": 0,
|
||||
"edges_lt_0.5x": 0,
|
||||
"edges_total": 3799
|
||||
},
|
||||
"aabb_post_fit": {
|
||||
"min": [
|
||||
-0.22,
|
||||
-0.1776,
|
||||
0.8241
|
||||
],
|
||||
"max": [
|
||||
0.253,
|
||||
0.1837,
|
||||
1.5938
|
||||
]
|
||||
},
|
||||
"signed_distance": {
|
||||
"verts_sampled": 2155,
|
||||
"min_signed_mm": -88.67,
|
||||
"p05_signed_mm": -6.24,
|
||||
"median_signed_mm": 31.44,
|
||||
"max_signed_mm": 103.76,
|
||||
"verts_inside_gt_3mm": 148,
|
||||
"pct_inside": 6.87
|
||||
},
|
||||
"export": {
|
||||
"path": "/var/mnt/data/projects/settled-reach/spikes/synty-intake/out/bodies/SK_SCFI_CIVL_09_10TORS_HU01_heavy_m.glb",
|
||||
"size_kb": 653
|
||||
},
|
||||
"result": "OK"
|
||||
},
|
||||
"heavy_f": {
|
||||
"garment_verts": 2155,
|
||||
"segments_used": [
|
||||
"seg_torso",
|
||||
"seg_neck",
|
||||
"seg_arm_upper_l",
|
||||
"seg_arm_upper_r",
|
||||
"seg_leg_upper_l",
|
||||
"seg_leg_upper_r"
|
||||
],
|
||||
"affine": {
|
||||
"scale_xy": 0.79,
|
||||
"scale_z": 0.96031,
|
||||
"translate": [
|
||||
0.0,
|
||||
0.01173,
|
||||
0.02037
|
||||
]
|
||||
},
|
||||
"aabb_post_affine": {
|
||||
"min": [
|
||||
-0.1645,
|
||||
-0.1207,
|
||||
0.8127
|
||||
],
|
||||
"max": [
|
||||
0.1821,
|
||||
0.1491,
|
||||
1.5473
|
||||
]
|
||||
},
|
||||
"warp": {
|
||||
"max_displacement_mm": 104.0,
|
||||
"smooth_iterations": 20
|
||||
},
|
||||
"zero_weight_verts": 0,
|
||||
"vgroups": 65,
|
||||
"edge_distortion": {
|
||||
"max_stretch": 1.839,
|
||||
"max_shrink": 0.678,
|
||||
"edges_gt_2x": 0,
|
||||
"edges_lt_0.5x": 0,
|
||||
"edges_total": 3799
|
||||
},
|
||||
"aabb_post_fit": {
|
||||
"min": [
|
||||
-0.1891,
|
||||
-0.1631,
|
||||
0.8005
|
||||
],
|
||||
"max": [
|
||||
0.1965,
|
||||
0.1545,
|
||||
1.549
|
||||
]
|
||||
},
|
||||
"signed_distance": {
|
||||
"verts_sampled": 2155,
|
||||
"min_signed_mm": -69.85,
|
||||
"p05_signed_mm": -15.38,
|
||||
"median_signed_mm": 21.36,
|
||||
"max_signed_mm": 87.94,
|
||||
"verts_inside_gt_3mm": 275,
|
||||
"pct_inside": 12.76
|
||||
},
|
||||
"export": {
|
||||
"path": "/var/mnt/data/projects/settled-reach/spikes/synty-intake/out/bodies/SK_SCFI_CIVL_09_10TORS_HU01_heavy_f.glb",
|
||||
"size_kb": 653
|
||||
},
|
||||
"result": "OK"
|
||||
},
|
||||
"teen_m": {
|
||||
"garment_verts": 2155,
|
||||
"segments_used": [
|
||||
"seg_torso",
|
||||
"seg_hips",
|
||||
"seg_neck",
|
||||
"seg_arm_upper_l",
|
||||
"seg_arm_upper_r",
|
||||
"seg_leg_upper_l",
|
||||
"seg_leg_upper_r"
|
||||
],
|
||||
"affine": {
|
||||
"scale_xy": 0.73705,
|
||||
"scale_z": 0.84515,
|
||||
"translate": [
|
||||
0.0,
|
||||
0.00491,
|
||||
0.00582
|
||||
]
|
||||
},
|
||||
"aabb_post_affine": {
|
||||
"min": [
|
||||
-0.1534,
|
||||
-0.1187,
|
||||
0.7031
|
||||
],
|
||||
"max": [
|
||||
0.1699,
|
||||
0.1331,
|
||||
1.3496
|
||||
]
|
||||
},
|
||||
"warp": {
|
||||
"max_displacement_mm": 42.4,
|
||||
"smooth_iterations": 20
|
||||
},
|
||||
"zero_weight_verts": 0,
|
||||
"vgroups": 65,
|
||||
"edge_distortion": {
|
||||
"max_stretch": 1.747,
|
||||
"max_shrink": 0.819,
|
||||
"edges_gt_2x": 0,
|
||||
"edges_lt_0.5x": 0,
|
||||
"edges_total": 3799
|
||||
},
|
||||
"aabb_post_fit": {
|
||||
"min": [
|
||||
-0.1578,
|
||||
-0.1319,
|
||||
0.7008
|
||||
],
|
||||
"max": [
|
||||
0.1859,
|
||||
0.1363,
|
||||
1.3515
|
||||
]
|
||||
},
|
||||
"signed_distance": {
|
||||
"verts_sampled": 2155,
|
||||
"min_signed_mm": -63.32,
|
||||
"p05_signed_mm": -7.51,
|
||||
"median_signed_mm": 25.93,
|
||||
"max_signed_mm": 74.03,
|
||||
"verts_inside_gt_3mm": 170,
|
||||
"pct_inside": 7.89
|
||||
},
|
||||
"export": {
|
||||
"path": "/var/mnt/data/projects/settled-reach/spikes/synty-intake/out/bodies/SK_SCFI_CIVL_09_10TORS_HU01_teen_m.glb",
|
||||
"size_kb": 653
|
||||
},
|
||||
"result": "OK"
|
||||
},
|
||||
"teen_f": {
|
||||
"garment_verts": 2155,
|
||||
"segments_used": [
|
||||
"seg_torso",
|
||||
"seg_hips",
|
||||
"seg_neck",
|
||||
"seg_arm_upper_l",
|
||||
"seg_arm_upper_r",
|
||||
"seg_leg_upper_l",
|
||||
"seg_leg_upper_r"
|
||||
],
|
||||
"affine": {
|
||||
"scale_xy": 0.68165,
|
||||
"scale_z": 0.82861,
|
||||
"translate": [
|
||||
0.0,
|
||||
0.01012,
|
||||
0.01758
|
||||
]
|
||||
},
|
||||
"aabb_post_affine": {
|
||||
"min": [
|
||||
-0.1419,
|
||||
-0.1042,
|
||||
0.7013
|
||||
],
|
||||
"max": [
|
||||
0.1571,
|
||||
0.1286,
|
||||
1.3351
|
||||
]
|
||||
},
|
||||
"warp": {
|
||||
"max_displacement_mm": 46.5,
|
||||
"smooth_iterations": 20
|
||||
},
|
||||
"zero_weight_verts": 0,
|
||||
"vgroups": 65,
|
||||
"edge_distortion": {
|
||||
"max_stretch": 1.72,
|
||||
"max_shrink": 0.759,
|
||||
"edges_gt_2x": 0,
|
||||
"edges_lt_0.5x": 0,
|
||||
"edges_total": 3799
|
||||
},
|
||||
"aabb_post_fit": {
|
||||
"min": [
|
||||
-0.1403,
|
||||
-0.1322,
|
||||
0.684
|
||||
],
|
||||
"max": [
|
||||
0.1987,
|
||||
0.1183,
|
||||
1.3355
|
||||
]
|
||||
},
|
||||
"signed_distance": {
|
||||
"verts_sampled": 2155,
|
||||
"min_signed_mm": -41.05,
|
||||
"p05_signed_mm": -6.08,
|
||||
"median_signed_mm": 24.43,
|
||||
"max_signed_mm": 70.84,
|
||||
"verts_inside_gt_3mm": 120,
|
||||
"pct_inside": 5.57
|
||||
},
|
||||
"export": {
|
||||
"path": "/var/mnt/data/projects/settled-reach/spikes/synty-intake/out/bodies/SK_SCFI_CIVL_09_10TORS_HU01_teen_f.glb",
|
||||
"size_kb": 653
|
||||
},
|
||||
"result": "OK"
|
||||
},
|
||||
"child": {
|
||||
"garment_verts": 2155,
|
||||
"segments_used": [
|
||||
"seg_torso",
|
||||
"seg_hips",
|
||||
"seg_neck",
|
||||
"seg_arm_upper_l",
|
||||
"seg_arm_upper_r",
|
||||
"seg_leg_upper_l",
|
||||
"seg_leg_upper_r"
|
||||
],
|
||||
"affine": {
|
||||
"scale_xy": 0.73705,
|
||||
"scale_z": 0.84515,
|
||||
"translate": [
|
||||
0.0,
|
||||
0.00491,
|
||||
0.00582
|
||||
]
|
||||
},
|
||||
"aabb_post_affine": {
|
||||
"min": [
|
||||
-0.1534,
|
||||
-0.1187,
|
||||
0.7031
|
||||
],
|
||||
"max": [
|
||||
0.1699,
|
||||
0.1331,
|
||||
1.3496
|
||||
]
|
||||
},
|
||||
"warp": {
|
||||
"max_displacement_mm": 302.7,
|
||||
"smooth_iterations": 20
|
||||
},
|
||||
"zero_weight_verts": 0,
|
||||
"vgroups": 65,
|
||||
"edge_distortion": {
|
||||
"max_stretch": 2.619,
|
||||
"max_shrink": 0.055,
|
||||
"edges_gt_2x": 3,
|
||||
"edges_lt_0.5x": 84,
|
||||
"edges_total": 3799
|
||||
},
|
||||
"aabb_post_fit": {
|
||||
"min": [
|
||||
-0.1403,
|
||||
-0.1072,
|
||||
0.6974
|
||||
],
|
||||
"max": [
|
||||
0.1622,
|
||||
0.1086,
|
||||
1.1344
|
||||
]
|
||||
},
|
||||
"signed_distance": {
|
||||
"verts_sampled": 2155,
|
||||
"min_signed_mm": -54.44,
|
||||
"p05_signed_mm": -13.32,
|
||||
"median_signed_mm": 26.92,
|
||||
"max_signed_mm": 79.3,
|
||||
"verts_inside_gt_3mm": 187,
|
||||
"pct_inside": 8.68
|
||||
},
|
||||
"export": {
|
||||
"path": "/var/mnt/data/projects/settled-reach/spikes/synty-intake/out/bodies/SK_SCFI_CIVL_09_10TORS_HU01_child.glb",
|
||||
"size_kb": 653
|
||||
},
|
||||
"result": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,771 @@
|
||||
{
|
||||
"garment_objects": [
|
||||
{
|
||||
"name": "Armature",
|
||||
"type": "ARMATURE",
|
||||
"scale": [
|
||||
0.01,
|
||||
0.01,
|
||||
0.01
|
||||
],
|
||||
"location": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SK_SCFI_CIVL_09_10TORS_HU01",
|
||||
"type": "MESH",
|
||||
"scale": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"location": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
}
|
||||
],
|
||||
"sidekick_bones": {
|
||||
"pelvis": {
|
||||
"head": [
|
||||
0.0,
|
||||
0.02817,
|
||||
0.91021
|
||||
],
|
||||
"tail": [
|
||||
0.0,
|
||||
-0.0946,
|
||||
0.91021
|
||||
]
|
||||
},
|
||||
"thigh_l": {
|
||||
"head": [
|
||||
0.09613,
|
||||
0.03111,
|
||||
0.883
|
||||
],
|
||||
"tail": [
|
||||
0.09622,
|
||||
0.36502,
|
||||
0.87676
|
||||
]
|
||||
},
|
||||
"thigh_r": {
|
||||
"head": [
|
||||
-0.09613,
|
||||
0.03111,
|
||||
0.883
|
||||
],
|
||||
"tail": [
|
||||
-0.09604,
|
||||
-0.30279,
|
||||
0.88923
|
||||
]
|
||||
},
|
||||
"spine_01": {
|
||||
"head": [
|
||||
0.0,
|
||||
0.02533,
|
||||
0.9497
|
||||
],
|
||||
"tail": [
|
||||
0.0,
|
||||
0.09286,
|
||||
0.96049
|
||||
]
|
||||
},
|
||||
"spine_02": {
|
||||
"head": [
|
||||
0.0,
|
||||
0.01595,
|
||||
1.01744
|
||||
],
|
||||
"tail": [
|
||||
0.0,
|
||||
0.1409,
|
||||
1.03122
|
||||
]
|
||||
},
|
||||
"spine_03": {
|
||||
"head": [
|
||||
0.0,
|
||||
0.0085,
|
||||
1.14293
|
||||
],
|
||||
"tail": [
|
||||
1e-05,
|
||||
0.26263,
|
||||
1.14473
|
||||
]
|
||||
},
|
||||
"neck_01": {
|
||||
"head": [
|
||||
0.0,
|
||||
0.04285,
|
||||
1.46301
|
||||
],
|
||||
"tail": [
|
||||
0.0,
|
||||
0.13851,
|
||||
1.49219
|
||||
]
|
||||
},
|
||||
"head": {
|
||||
"head": [
|
||||
0.0,
|
||||
0.01367,
|
||||
1.55867
|
||||
],
|
||||
"tail": [
|
||||
0.0,
|
||||
0.13291,
|
||||
1.55867
|
||||
]
|
||||
},
|
||||
"clavicle_l": {
|
||||
"head": [
|
||||
0.0198,
|
||||
0.0427,
|
||||
1.36663
|
||||
],
|
||||
"tail": [
|
||||
0.01704,
|
||||
0.22638,
|
||||
1.37059
|
||||
]
|
||||
},
|
||||
"upperarm_l": {
|
||||
"head": [
|
||||
0.19434,
|
||||
0.04519,
|
||||
1.37329
|
||||
],
|
||||
"tail": [
|
||||
0.18605,
|
||||
0.25779,
|
||||
1.37358
|
||||
]
|
||||
},
|
||||
"clavicle_r": {
|
||||
"head": [
|
||||
-0.0198,
|
||||
0.0427,
|
||||
1.36663
|
||||
],
|
||||
"tail": [
|
||||
-0.02256,
|
||||
-0.14098,
|
||||
1.36267
|
||||
]
|
||||
},
|
||||
"upperarm_r": {
|
||||
"head": [
|
||||
-0.19434,
|
||||
0.04519,
|
||||
1.37329
|
||||
],
|
||||
"tail": [
|
||||
-0.18605,
|
||||
0.25779,
|
||||
1.37358
|
||||
]
|
||||
}
|
||||
},
|
||||
"garment_meshes": {
|
||||
"SK_SCFI_CIVL_09_10TORS_HU01": {
|
||||
"verts": 919,
|
||||
"bbox": {
|
||||
"min": [
|
||||
-0.21081,
|
||||
-0.18519,
|
||||
0.79682
|
||||
],
|
||||
"max": [
|
||||
0.23342,
|
||||
0.1607,
|
||||
1.49631
|
||||
],
|
||||
"dims": [
|
||||
0.44423,
|
||||
0.34589,
|
||||
0.69949
|
||||
]
|
||||
},
|
||||
"vgroups": [
|
||||
"pelvis",
|
||||
"thigh_l",
|
||||
"thigh_r",
|
||||
"spine_01",
|
||||
"spine_02",
|
||||
"spine_03",
|
||||
"neck_01",
|
||||
"head",
|
||||
"clavicle_l",
|
||||
"upperarm_l",
|
||||
"clavicle_r",
|
||||
"upperarm_r"
|
||||
],
|
||||
"shape_keys": [
|
||||
"Basis",
|
||||
"defaultBuff",
|
||||
"masculineFeminine",
|
||||
"defaultHeavy",
|
||||
"defaultSkinny"
|
||||
]
|
||||
}
|
||||
},
|
||||
"our_armature_bone_count": 65,
|
||||
"our_bones": {
|
||||
"pelvis": {
|
||||
"head": [
|
||||
-0.0,
|
||||
0.043,
|
||||
0.9491
|
||||
],
|
||||
"tail": [
|
||||
-0.0,
|
||||
0.01664,
|
||||
1.03883
|
||||
]
|
||||
},
|
||||
"thigh_l": {
|
||||
"head": [
|
||||
0.0906,
|
||||
0.036,
|
||||
0.9712
|
||||
],
|
||||
"tail": [
|
||||
0.0906,
|
||||
0.0361,
|
||||
0.5424
|
||||
]
|
||||
},
|
||||
"thigh_r": {
|
||||
"head": [
|
||||
-0.0906,
|
||||
0.036,
|
||||
0.9712
|
||||
],
|
||||
"tail": [
|
||||
-0.0906,
|
||||
0.0361,
|
||||
0.5424
|
||||
]
|
||||
},
|
||||
"spine_01": {
|
||||
"head": [
|
||||
-0.0,
|
||||
0.0069,
|
||||
1.072
|
||||
],
|
||||
"tail": [
|
||||
-0.0,
|
||||
-0.0039,
|
||||
1.1778
|
||||
]
|
||||
},
|
||||
"spine_02": {
|
||||
"head": [
|
||||
-0.0,
|
||||
-0.0039,
|
||||
1.1778
|
||||
],
|
||||
"tail": [
|
||||
-0.0,
|
||||
-0.0069,
|
||||
1.3109
|
||||
]
|
||||
},
|
||||
"spine_03": {
|
||||
"head": [
|
||||
-0.0,
|
||||
-0.0069,
|
||||
1.3109
|
||||
],
|
||||
"tail": [
|
||||
-0.0,
|
||||
0.03553,
|
||||
1.49501
|
||||
]
|
||||
},
|
||||
"neck_01": {
|
||||
"head": [
|
||||
-0.0,
|
||||
0.0414,
|
||||
1.5205
|
||||
],
|
||||
"tail": [
|
||||
-0.0,
|
||||
0.0174,
|
||||
1.5998
|
||||
]
|
||||
},
|
||||
"Head": {
|
||||
"head": [
|
||||
-0.0,
|
||||
0.0174,
|
||||
1.5998
|
||||
],
|
||||
"tail": [
|
||||
-0.0,
|
||||
0.015,
|
||||
1.68262
|
||||
]
|
||||
},
|
||||
"clavicle_l": {
|
||||
"head": [
|
||||
0.0314,
|
||||
-0.0335,
|
||||
1.4953
|
||||
],
|
||||
"tail": [
|
||||
0.20039,
|
||||
0.05906,
|
||||
1.49517
|
||||
]
|
||||
},
|
||||
"upperarm_l": {
|
||||
"head": [
|
||||
0.1919,
|
||||
0.0654,
|
||||
1.4555
|
||||
],
|
||||
"tail": [
|
||||
0.4429,
|
||||
0.073,
|
||||
1.4555
|
||||
]
|
||||
},
|
||||
"clavicle_r": {
|
||||
"head": [
|
||||
-0.0314,
|
||||
-0.0335,
|
||||
1.4953
|
||||
],
|
||||
"tail": [
|
||||
-0.20039,
|
||||
0.05906,
|
||||
1.49517
|
||||
]
|
||||
},
|
||||
"upperarm_r": {
|
||||
"head": [
|
||||
-0.1919,
|
||||
0.0654,
|
||||
1.4555
|
||||
],
|
||||
"tail": [
|
||||
-0.4429,
|
||||
0.073,
|
||||
1.4555
|
||||
]
|
||||
}
|
||||
},
|
||||
"bone_head_deltas": {
|
||||
"pelvis": {
|
||||
"delta": [
|
||||
0.0,
|
||||
-0.01483,
|
||||
-0.03889
|
||||
],
|
||||
"magnitude_m": 0.04162
|
||||
},
|
||||
"thigh_l": {
|
||||
"delta": [
|
||||
0.00553,
|
||||
-0.00489,
|
||||
-0.0882
|
||||
],
|
||||
"magnitude_m": 0.08851
|
||||
},
|
||||
"thigh_r": {
|
||||
"delta": [
|
||||
-0.00553,
|
||||
-0.00489,
|
||||
-0.0882
|
||||
],
|
||||
"magnitude_m": 0.08851
|
||||
},
|
||||
"spine_01": {
|
||||
"delta": [
|
||||
0.0,
|
||||
0.01843,
|
||||
-0.1223
|
||||
],
|
||||
"magnitude_m": 0.12368
|
||||
},
|
||||
"spine_02": {
|
||||
"delta": [
|
||||
0.0,
|
||||
0.01985,
|
||||
-0.16036
|
||||
],
|
||||
"magnitude_m": 0.16158
|
||||
},
|
||||
"spine_03": {
|
||||
"delta": [
|
||||
0.0,
|
||||
0.0154,
|
||||
-0.16797
|
||||
],
|
||||
"magnitude_m": 0.16867
|
||||
},
|
||||
"neck_01": {
|
||||
"delta": [
|
||||
0.0,
|
||||
0.00145,
|
||||
-0.05749
|
||||
],
|
||||
"magnitude_m": 0.05751
|
||||
},
|
||||
"head": {
|
||||
"delta": [
|
||||
0.0,
|
||||
-0.00373,
|
||||
-0.04113
|
||||
],
|
||||
"magnitude_m": 0.0413
|
||||
},
|
||||
"clavicle_l": {
|
||||
"delta": [
|
||||
-0.0116,
|
||||
0.0762,
|
||||
-0.12867
|
||||
],
|
||||
"magnitude_m": 0.14999
|
||||
},
|
||||
"upperarm_l": {
|
||||
"delta": [
|
||||
0.00244,
|
||||
-0.02021,
|
||||
-0.08221
|
||||
],
|
||||
"magnitude_m": 0.08469
|
||||
},
|
||||
"clavicle_r": {
|
||||
"delta": [
|
||||
0.0116,
|
||||
0.0762,
|
||||
-0.12867
|
||||
],
|
||||
"magnitude_m": 0.14999
|
||||
},
|
||||
"upperarm_r": {
|
||||
"delta": [
|
||||
-0.00244,
|
||||
-0.02021,
|
||||
-0.08221
|
||||
],
|
||||
"magnitude_m": 0.08469
|
||||
}
|
||||
},
|
||||
"our_body_segments": {
|
||||
"seg_torso": {
|
||||
"verts": 375,
|
||||
"bbox": {
|
||||
"min": [
|
||||
-0.15826,
|
||||
-0.10161,
|
||||
1.03118
|
||||
],
|
||||
"max": [
|
||||
0.15826,
|
||||
0.12067,
|
||||
1.35323
|
||||
],
|
||||
"dims": [
|
||||
0.31652,
|
||||
0.22228,
|
||||
0.32205
|
||||
]
|
||||
},
|
||||
"vgroups": [
|
||||
"root",
|
||||
"pelvis",
|
||||
"spine_01",
|
||||
"spine_02",
|
||||
"spine_03",
|
||||
"neck_01",
|
||||
"Head",
|
||||
"clavicle_l",
|
||||
"upperarm_l",
|
||||
"lowerarm_l",
|
||||
"hand_l",
|
||||
"index_01_l",
|
||||
"index_02_l",
|
||||
"index_03_l",
|
||||
"index_04_leaf_l",
|
||||
"middle_01_l",
|
||||
"middle_02_l",
|
||||
"middle_03_l",
|
||||
"middle_04_leaf_l",
|
||||
"pinky_01_l",
|
||||
"pinky_02_l",
|
||||
"pinky_03_l",
|
||||
"pinky_04_leaf_l",
|
||||
"ring_01_l",
|
||||
"ring_02_l",
|
||||
"ring_03_l",
|
||||
"ring_04_leaf_l",
|
||||
"thumb_01_l",
|
||||
"thumb_02_l",
|
||||
"thumb_03_l",
|
||||
"thumb_04_leaf_l",
|
||||
"clavicle_r",
|
||||
"upperarm_r",
|
||||
"lowerarm_r",
|
||||
"hand_r",
|
||||
"index_01_r",
|
||||
"index_02_r",
|
||||
"index_03_r",
|
||||
"index_04_leaf_r",
|
||||
"middle_01_r",
|
||||
"middle_02_r",
|
||||
"middle_03_r",
|
||||
"middle_04_leaf_r",
|
||||
"pinky_01_r",
|
||||
"pinky_02_r",
|
||||
"pinky_03_r",
|
||||
"pinky_04_leaf_r",
|
||||
"ring_01_r",
|
||||
"ring_02_r",
|
||||
"ring_03_r",
|
||||
"ring_04_leaf_r",
|
||||
"thumb_01_r",
|
||||
"thumb_02_r",
|
||||
"thumb_03_r",
|
||||
"thumb_04_leaf_r",
|
||||
"thigh_l",
|
||||
"calf_l",
|
||||
"foot_l",
|
||||
"ball_l",
|
||||
"ball_leaf_l",
|
||||
"thigh_r",
|
||||
"calf_r",
|
||||
"foot_r",
|
||||
"ball_r",
|
||||
"ball_leaf_r"
|
||||
]
|
||||
},
|
||||
"seg_hips": {
|
||||
"verts": 42,
|
||||
"bbox": {
|
||||
"min": [
|
||||
-0.95106,
|
||||
-1.0,
|
||||
-1.0
|
||||
],
|
||||
"max": [
|
||||
0.95106,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"dims": [
|
||||
1.90212,
|
||||
2.0,
|
||||
2.0
|
||||
]
|
||||
},
|
||||
"vgroups": []
|
||||
},
|
||||
"seg_neck": {
|
||||
"verts": 144,
|
||||
"bbox": {
|
||||
"min": [
|
||||
-0.07635,
|
||||
-0.02606,
|
||||
1.53878
|
||||
],
|
||||
"max": [
|
||||
0.07635,
|
||||
0.10504,
|
||||
1.61028
|
||||
],
|
||||
"dims": [
|
||||
0.1527,
|
||||
0.1311,
|
||||
0.0715
|
||||
]
|
||||
},
|
||||
"vgroups": [
|
||||
"root",
|
||||
"pelvis",
|
||||
"spine_01",
|
||||
"spine_02",
|
||||
"spine_03",
|
||||
"neck_01",
|
||||
"Head",
|
||||
"clavicle_l",
|
||||
"upperarm_l",
|
||||
"lowerarm_l",
|
||||
"hand_l",
|
||||
"index_01_l",
|
||||
"index_02_l",
|
||||
"index_03_l",
|
||||
"index_04_leaf_l",
|
||||
"middle_01_l",
|
||||
"middle_02_l",
|
||||
"middle_03_l",
|
||||
"middle_04_leaf_l",
|
||||
"pinky_01_l",
|
||||
"pinky_02_l",
|
||||
"pinky_03_l",
|
||||
"pinky_04_leaf_l",
|
||||
"ring_01_l",
|
||||
"ring_02_l",
|
||||
"ring_03_l",
|
||||
"ring_04_leaf_l",
|
||||
"thumb_01_l",
|
||||
"thumb_02_l",
|
||||
"thumb_03_l",
|
||||
"thumb_04_leaf_l",
|
||||
"clavicle_r",
|
||||
"upperarm_r",
|
||||
"lowerarm_r",
|
||||
"hand_r",
|
||||
"index_01_r",
|
||||
"index_02_r",
|
||||
"index_03_r",
|
||||
"index_04_leaf_r",
|
||||
"middle_01_r",
|
||||
"middle_02_r",
|
||||
"middle_03_r",
|
||||
"middle_04_leaf_r",
|
||||
"pinky_01_r",
|
||||
"pinky_02_r",
|
||||
"pinky_03_r",
|
||||
"pinky_04_leaf_r",
|
||||
"ring_01_r",
|
||||
"ring_02_r",
|
||||
"ring_03_r",
|
||||
"ring_04_leaf_r",
|
||||
"thumb_01_r",
|
||||
"thumb_02_r",
|
||||
"thumb_03_r",
|
||||
"thumb_04_leaf_r",
|
||||
"thigh_l",
|
||||
"calf_l",
|
||||
"foot_l",
|
||||
"ball_l",
|
||||
"ball_leaf_l",
|
||||
"thigh_r",
|
||||
"calf_r",
|
||||
"foot_r",
|
||||
"ball_r",
|
||||
"ball_leaf_r"
|
||||
]
|
||||
},
|
||||
"seg_arm_upper_l": {
|
||||
"verts": 183,
|
||||
"bbox": {
|
||||
"min": [
|
||||
0.15183,
|
||||
-0.00827,
|
||||
1.38075
|
||||
],
|
||||
"max": [
|
||||
0.44693,
|
||||
0.13217,
|
||||
1.51998
|
||||
],
|
||||
"dims": [
|
||||
0.2951,
|
||||
0.14044,
|
||||
0.13923
|
||||
]
|
||||
},
|
||||
"vgroups": [
|
||||
"root",
|
||||
"pelvis",
|
||||
"spine_01",
|
||||
"spine_02",
|
||||
"spine_03",
|
||||
"neck_01",
|
||||
"Head",
|
||||
"clavicle_l",
|
||||
"upperarm_l",
|
||||
"lowerarm_l",
|
||||
"hand_l",
|
||||
"index_01_l",
|
||||
"index_02_l",
|
||||
"index_03_l",
|
||||
"index_04_leaf_l",
|
||||
"middle_01_l",
|
||||
"middle_02_l",
|
||||
"middle_03_l",
|
||||
"middle_04_leaf_l",
|
||||
"pinky_01_l",
|
||||
"pinky_02_l",
|
||||
"pinky_03_l",
|
||||
"pinky_04_leaf_l",
|
||||
"ring_01_l",
|
||||
"ring_02_l",
|
||||
"ring_03_l",
|
||||
"ring_04_leaf_l",
|
||||
"thumb_01_l",
|
||||
"thumb_02_l",
|
||||
"thumb_03_l",
|
||||
"thumb_04_leaf_l",
|
||||
"clavicle_r",
|
||||
"upperarm_r",
|
||||
"lowerarm_r",
|
||||
"hand_r",
|
||||
"index_01_r",
|
||||
"index_02_r",
|
||||
"index_03_r",
|
||||
"index_04_leaf_r",
|
||||
"middle_01_r",
|
||||
"middle_02_r",
|
||||
"middle_03_r",
|
||||
"middle_04_leaf_r",
|
||||
"pinky_01_r",
|
||||
"pinky_02_r",
|
||||
"pinky_03_r",
|
||||
"pinky_04_leaf_r",
|
||||
"ring_01_r",
|
||||
"ring_02_r",
|
||||
"ring_03_r",
|
||||
"ring_04_leaf_r",
|
||||
"thumb_01_r",
|
||||
"thumb_02_r",
|
||||
"thumb_03_r",
|
||||
"thumb_04_leaf_r",
|
||||
"thigh_l",
|
||||
"calf_l",
|
||||
"foot_l",
|
||||
"ball_l",
|
||||
"ball_leaf_l",
|
||||
"thigh_r",
|
||||
"calf_r",
|
||||
"foot_r",
|
||||
"ball_r",
|
||||
"ball_leaf_r"
|
||||
]
|
||||
},
|
||||
"seg_arm_upper_r": {
|
||||
"verts": 42,
|
||||
"bbox": {
|
||||
"min": [
|
||||
-0.95106,
|
||||
-1.0,
|
||||
-1.0
|
||||
],
|
||||
"max": [
|
||||
0.95106,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"dims": [
|
||||
1.90212,
|
||||
2.0,
|
||||
2.0
|
||||
]
|
||||
},
|
||||
"vgroups": []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"garment": {
|
||||
"verts": 919,
|
||||
"sidekick_vgroups": [
|
||||
"pelvis",
|
||||
"thigh_l",
|
||||
"thigh_r",
|
||||
"spine_01",
|
||||
"spine_02",
|
||||
"spine_03",
|
||||
"neck_01",
|
||||
"head",
|
||||
"clavicle_l",
|
||||
"upperarm_l",
|
||||
"clavicle_r",
|
||||
"upperarm_r"
|
||||
]
|
||||
},
|
||||
"alignment": {
|
||||
"scale_xy": 0.98744,
|
||||
"scale_z": 1.09356,
|
||||
"translate": [
|
||||
-0.0,
|
||||
0.01518,
|
||||
-0.04628
|
||||
],
|
||||
"sk_pelvis": [
|
||||
0.0,
|
||||
0.02817,
|
||||
0.91021
|
||||
],
|
||||
"our_pelvis": [
|
||||
-0.0,
|
||||
0.043,
|
||||
0.9491
|
||||
],
|
||||
"sk_upperarm_l": [
|
||||
0.19434,
|
||||
0.04519,
|
||||
1.37329
|
||||
],
|
||||
"our_upperarm_l": [
|
||||
0.1919,
|
||||
0.0654,
|
||||
1.4555
|
||||
]
|
||||
},
|
||||
"post_affine_bone_residuals": {
|
||||
"pelvis": {
|
||||
"residual_m": 0.0,
|
||||
"delta": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"thigh_l": {
|
||||
"residual_m": 0.05298,
|
||||
"delta": [
|
||||
0.00432,
|
||||
0.00991,
|
||||
-0.05186
|
||||
]
|
||||
},
|
||||
"thigh_r": {
|
||||
"residual_m": 0.05298,
|
||||
"delta": [
|
||||
-0.00433,
|
||||
0.00991,
|
||||
-0.05186
|
||||
]
|
||||
},
|
||||
"spine_01": {
|
||||
"residual_m": 0.08639,
|
||||
"delta": [
|
||||
-0.0,
|
||||
0.03329,
|
||||
-0.07972
|
||||
]
|
||||
},
|
||||
"spine_02": {
|
||||
"residual_m": 0.11676,
|
||||
"delta": [
|
||||
-0.0,
|
||||
0.03483,
|
||||
-0.11144
|
||||
]
|
||||
},
|
||||
"spine_03": {
|
||||
"residual_m": 0.11156,
|
||||
"delta": [
|
||||
-0.0,
|
||||
0.03047,
|
||||
-0.10732
|
||||
]
|
||||
},
|
||||
"neck_01": {
|
||||
"residual_m": 0.03682,
|
||||
"delta": [
|
||||
-0.0,
|
||||
0.0161,
|
||||
0.03311
|
||||
]
|
||||
},
|
||||
"head": {
|
||||
"residual_m": 0.0595,
|
||||
"delta": [
|
||||
-0.0,
|
||||
0.01128,
|
||||
0.05843
|
||||
]
|
||||
},
|
||||
"clavicle_l": {
|
||||
"residual_m": 0.10301,
|
||||
"delta": [
|
||||
-0.01185,
|
||||
0.09085,
|
||||
-0.04708
|
||||
]
|
||||
},
|
||||
"upperarm_l": {
|
||||
"residual_m": 0.00559,
|
||||
"delta": [
|
||||
-0.0,
|
||||
-0.00559,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"clavicle_r": {
|
||||
"residual_m": 0.10301,
|
||||
"delta": [
|
||||
0.01185,
|
||||
0.09085,
|
||||
-0.04708
|
||||
]
|
||||
},
|
||||
"upperarm_r": {
|
||||
"residual_m": 0.00559,
|
||||
"delta": [
|
||||
-0.0,
|
||||
-0.00559,
|
||||
0.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"weight_source_segments": [
|
||||
{
|
||||
"name": "RegularMale.001",
|
||||
"verts": 375
|
||||
},
|
||||
{
|
||||
"name": "RegularMale.002",
|
||||
"verts": 255
|
||||
},
|
||||
{
|
||||
"name": "RegularMale.003",
|
||||
"verts": 144
|
||||
},
|
||||
{
|
||||
"name": "RegularMale.004",
|
||||
"verts": 183
|
||||
},
|
||||
{
|
||||
"name": "RegularMale.005",
|
||||
"verts": 183
|
||||
},
|
||||
{
|
||||
"name": "RegularMale.006",
|
||||
"verts": 240
|
||||
},
|
||||
{
|
||||
"name": "RegularMale.007",
|
||||
"verts": 241
|
||||
}
|
||||
],
|
||||
"transferred_vgroups": [
|
||||
"root",
|
||||
"pelvis",
|
||||
"spine_01",
|
||||
"spine_02",
|
||||
"spine_03",
|
||||
"neck_01",
|
||||
"Head",
|
||||
"clavicle_l",
|
||||
"upperarm_l",
|
||||
"lowerarm_l",
|
||||
"hand_l",
|
||||
"index_01_l",
|
||||
"index_02_l",
|
||||
"index_03_l",
|
||||
"index_04_leaf_l",
|
||||
"middle_01_l",
|
||||
"middle_02_l",
|
||||
"middle_03_l",
|
||||
"middle_04_leaf_l",
|
||||
"pinky_01_l",
|
||||
"pinky_02_l",
|
||||
"pinky_03_l",
|
||||
"pinky_04_leaf_l",
|
||||
"ring_01_l",
|
||||
"ring_02_l",
|
||||
"ring_03_l",
|
||||
"ring_04_leaf_l",
|
||||
"thumb_01_l",
|
||||
"thumb_02_l",
|
||||
"thumb_03_l",
|
||||
"thumb_04_leaf_l",
|
||||
"clavicle_r",
|
||||
"upperarm_r",
|
||||
"lowerarm_r",
|
||||
"hand_r",
|
||||
"index_01_r",
|
||||
"index_02_r",
|
||||
"index_03_r",
|
||||
"index_04_leaf_r",
|
||||
"middle_01_r",
|
||||
"middle_02_r",
|
||||
"middle_03_r",
|
||||
"middle_04_leaf_r",
|
||||
"pinky_01_r",
|
||||
"pinky_02_r",
|
||||
"pinky_03_r",
|
||||
"pinky_04_leaf_r",
|
||||
"ring_01_r",
|
||||
"ring_02_r",
|
||||
"ring_03_r",
|
||||
"ring_04_leaf_r",
|
||||
"thumb_01_r",
|
||||
"thumb_02_r",
|
||||
"thumb_03_r",
|
||||
"thumb_04_leaf_r",
|
||||
"thigh_l",
|
||||
"calf_l",
|
||||
"foot_l",
|
||||
"ball_l",
|
||||
"ball_leaf_l",
|
||||
"thigh_r",
|
||||
"calf_r",
|
||||
"foot_r",
|
||||
"ball_r",
|
||||
"ball_leaf_r"
|
||||
],
|
||||
"zero_weight_verts": 0,
|
||||
"export": {
|
||||
"path": "/var/mnt/data/projects/settled-reach/spikes/synty-intake/out/SK_SCFI_CIVL_09_10TORS_HU01_quaternius.glb",
|
||||
"size_kb": 653
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
"""
|
||||
Spike T-1089 / Synty Sidekick intake — step 1: rest-pose recon.
|
||||
|
||||
Imports the Sidekick garment FBX (with its armature) and our Quaternius
|
||||
armature.glb + average_m body segments, then prints:
|
||||
- world-space bone head/tail positions for the 12 vertex-group bones
|
||||
in BOTH armatures + per-bone delta
|
||||
- bounding boxes of the garment vs our torso-region body segments
|
||||
- object transforms as-imported (scale factors etc.)
|
||||
|
||||
Run:
|
||||
tooling/blender --background --python \
|
||||
spikes/synty-intake/scripts/01_recon_rest_pose.py
|
||||
"""
|
||||
|
||||
import bpy
|
||||
import json
|
||||
import os
|
||||
|
||||
REPO = "/var/mnt/data/projects/settled-reach"
|
||||
GARMENT_FBX = os.path.join(
|
||||
REPO, "spikes/synty-intake/raw/Assets/Synty/SidekickCharacters/Resources",
|
||||
"Meshes/Outfits/Starter/SK_SCFI_CIVL_09_10TORS_HU01.fbx")
|
||||
OUR_ARMATURE_GLB = os.path.join(REPO, "client/assets/characters/skeleton/armature.glb")
|
||||
BODY_DIR = os.path.join(REPO, "client/assets/characters/bodies/average_m")
|
||||
BODY_SEGS = ["seg_torso", "seg_hips", "seg_neck", "seg_arm_upper_l", "seg_arm_upper_r"]
|
||||
|
||||
BOUND_BONES = ["pelvis", "thigh_l", "thigh_r", "spine_01", "spine_02", "spine_03",
|
||||
"neck_01", "head", "clavicle_l", "upperarm_l", "clavicle_r",
|
||||
"upperarm_r"]
|
||||
|
||||
OUT_JSON = os.path.join(REPO, "spikes/synty-intake/out/recon_rest_pose.json")
|
||||
|
||||
|
||||
def clear_scene():
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
|
||||
def import_new(op, path, **kw):
|
||||
before = set(bpy.data.objects)
|
||||
op(filepath=path, **kw)
|
||||
return list(set(bpy.data.objects) - before)
|
||||
|
||||
|
||||
def find_armature(objs):
|
||||
for o in objs:
|
||||
if o.type == 'ARMATURE':
|
||||
return o
|
||||
return None
|
||||
|
||||
|
||||
def bone_world(arm_obj, name):
|
||||
b = arm_obj.data.bones.get(name)
|
||||
if b is None:
|
||||
return None
|
||||
mw = arm_obj.matrix_world
|
||||
h = mw @ b.head_local
|
||||
t = mw @ b.tail_local
|
||||
return {"head": [round(v, 5) for v in h], "tail": [round(v, 5) for v in t]}
|
||||
|
||||
|
||||
def mesh_world_bbox(obj):
|
||||
import mathutils
|
||||
pts = [obj.matrix_world @ mathutils.Vector(c) for c in obj.bound_box]
|
||||
lo = [round(min(p[i] for p in pts), 5) for i in range(3)]
|
||||
hi = [round(max(p[i] for p in pts), 5) for i in range(3)]
|
||||
return {"min": lo, "max": hi,
|
||||
"dims": [round(hi[i] - lo[i], 5) for i in range(3)]}
|
||||
|
||||
|
||||
clear_scene()
|
||||
|
||||
report = {}
|
||||
|
||||
# --- garment FBX ---
|
||||
g_objs = import_new(bpy.ops.import_scene.fbx, GARMENT_FBX)
|
||||
g_arm = find_armature(g_objs)
|
||||
g_meshes = [o for o in g_objs if o.type == 'MESH']
|
||||
report["garment_objects"] = [
|
||||
{"name": o.name, "type": o.type,
|
||||
"scale": [round(s, 6) for s in o.scale],
|
||||
"location": [round(v, 6) for v in o.location]} for o in g_objs]
|
||||
report["sidekick_bones"] = {}
|
||||
for bn in BOUND_BONES:
|
||||
report["sidekick_bones"][bn] = bone_world(g_arm, bn)
|
||||
report["garment_meshes"] = {}
|
||||
for m in g_meshes:
|
||||
report["garment_meshes"][m.name] = {
|
||||
"verts": len(m.data.vertices),
|
||||
"bbox": mesh_world_bbox(m),
|
||||
"vgroups": [vg.name for vg in m.vertex_groups],
|
||||
"shape_keys": ([kb.name for kb in m.data.shape_keys.key_blocks]
|
||||
if m.data.shape_keys else []),
|
||||
}
|
||||
|
||||
# --- our armature ---
|
||||
o_objs = import_new(bpy.ops.import_scene.gltf, OUR_ARMATURE_GLB)
|
||||
o_arm = find_armature(o_objs)
|
||||
report["our_armature_bone_count"] = len(o_arm.data.bones)
|
||||
report["our_bones"] = {}
|
||||
deltas = {}
|
||||
for bn in BOUND_BONES:
|
||||
ours = bn if bn != "head" else "Head"
|
||||
info = bone_world(o_arm, ours)
|
||||
report["our_bones"][ours] = info
|
||||
sk = report["sidekick_bones"][bn]
|
||||
if info and sk:
|
||||
d = [round(sk["head"][i] - info["head"][i], 5) for i in range(3)]
|
||||
mag = round(sum(x * x for x in d) ** 0.5, 5)
|
||||
deltas[bn] = {"delta": d, "magnitude_m": mag}
|
||||
report["bone_head_deltas"] = deltas
|
||||
|
||||
# --- our body segments (proportion reference) ---
|
||||
report["our_body_segments"] = {}
|
||||
for seg in BODY_SEGS:
|
||||
path = os.path.join(BODY_DIR, seg + ".glb")
|
||||
objs = import_new(bpy.ops.import_scene.gltf, path)
|
||||
for m in objs:
|
||||
if m.type == 'MESH':
|
||||
report["our_body_segments"][seg] = {
|
||||
"verts": len(m.data.vertices),
|
||||
"bbox": mesh_world_bbox(m),
|
||||
"vgroups": [vg.name for vg in m.vertex_groups],
|
||||
}
|
||||
|
||||
os.makedirs(os.path.dirname(OUT_JSON), exist_ok=True)
|
||||
with open(OUT_JSON, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
|
||||
print("\n=== RECON SUMMARY ===")
|
||||
print(json.dumps(deltas, indent=2))
|
||||
print("Garment bbox:", json.dumps(
|
||||
{k: v["bbox"]["dims"] for k, v in report["garment_meshes"].items()}))
|
||||
print("Body seg bbox:", json.dumps(
|
||||
{k: v["bbox"]["dims"] for k, v in report["our_body_segments"].items()}))
|
||||
print("Report written:", OUT_JSON)
|
||||
@@ -0,0 +1,263 @@
|
||||
"""
|
||||
Spike T-1089 / Synty Sidekick intake — step 2: transplant.
|
||||
|
||||
Route decision (from 01_recon_rest_pose.py, out/recon_rest_pose.json):
|
||||
Route 1 (rename-and-rebind, keep Sidekick weights verbatim) REJECTED on
|
||||
measured evidence: spine_02/spine_03 joint heads sit 0.160/0.168 m lower in
|
||||
the Sidekick rig than ours, shoulders 0.082 m lower, clavicles 0.150 m off.
|
||||
Rest pose would render fine (inverse binds absorb it) but every spine/shoulder
|
||||
rotation would pivot the garment around joints up to 17 cm away from where the
|
||||
body underneath pivots -> swim/clip under animation.
|
||||
Route 2 (proven pipeline): affine-align garment onto our average_m body,
|
||||
then Data Transfer POLYINTERP_NEAREST weights from OUR body mesh, normalize,
|
||||
bind to OUR armature.
|
||||
|
||||
Alignment transform (documented, measured from bone landmarks):
|
||||
scale XY by ours_shoulder_x / sk_shoulder_x
|
||||
scale Z by (ours_upperarm_z - ours_pelvis_z) / (sk_upperarm_z - sk_pelvis_z)
|
||||
then translate so the pelvis landmarks coincide.
|
||||
|
||||
Run:
|
||||
tooling/blender --background --python \
|
||||
spikes/synty-intake/scripts/02_transplant_garment.py
|
||||
"""
|
||||
|
||||
import bpy
|
||||
import json
|
||||
import os
|
||||
from mathutils import Matrix, Vector
|
||||
|
||||
REPO = "/var/mnt/data/projects/settled-reach"
|
||||
GARMENT_FBX = os.path.join(
|
||||
REPO, "spikes/synty-intake/raw/Assets/Synty/SidekickCharacters/Resources",
|
||||
"Meshes/Outfits/Starter/SK_SCFI_CIVL_09_10TORS_HU01.fbx")
|
||||
OUR_ARMATURE_GLB = os.path.join(REPO, "client/assets/characters/skeleton/armature.glb")
|
||||
BODY_DIR = os.path.join(REPO, "client/assets/characters/bodies/average_m")
|
||||
# Segments covered by / adjacent to the torso garment — the weight-transfer source.
|
||||
BODY_SEGS = ["seg_torso", "seg_hips", "seg_neck",
|
||||
"seg_arm_upper_l", "seg_arm_upper_r",
|
||||
"seg_leg_upper_l", "seg_leg_upper_r"]
|
||||
|
||||
OUT_GLB = os.path.join(REPO, "spikes/synty-intake/out",
|
||||
"SK_SCFI_CIVL_09_10TORS_HU01_quaternius.glb")
|
||||
OUT_LOG = os.path.join(REPO, "spikes/synty-intake/out", "transplant_log.json")
|
||||
|
||||
log = {}
|
||||
|
||||
|
||||
def import_new(op, path, **kw):
|
||||
before = set(bpy.data.objects)
|
||||
op(filepath=path, **kw)
|
||||
return list(set(bpy.data.objects) - before)
|
||||
|
||||
|
||||
def find_armature(objs):
|
||||
for o in objs:
|
||||
if o.type == 'ARMATURE':
|
||||
return o
|
||||
return None
|
||||
|
||||
|
||||
def bone_head_world(arm, name):
|
||||
b = arm.data.bones.get(name)
|
||||
return (arm.matrix_world @ b.head_local) if b else None
|
||||
|
||||
|
||||
def deselect_all():
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
|
||||
|
||||
def set_active(obj):
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
|
||||
|
||||
# --- fresh scene ---
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
# --- 1. import garment ---
|
||||
g_objs = import_new(bpy.ops.import_scene.fbx, GARMENT_FBX)
|
||||
g_arm = find_armature(g_objs)
|
||||
garment = next(o for o in g_objs if o.type == 'MESH')
|
||||
log["garment"] = {"verts": len(garment.data.vertices),
|
||||
"sidekick_vgroups": [vg.name for vg in garment.vertex_groups]}
|
||||
|
||||
# landmarks in Sidekick space (world)
|
||||
sk_pelvis = bone_head_world(g_arm, "pelvis").copy()
|
||||
sk_upperarm = bone_head_world(g_arm, "upperarm_l").copy()
|
||||
|
||||
# --- 2. import our armature ---
|
||||
o_objs = import_new(bpy.ops.import_scene.gltf, OUR_ARMATURE_GLB)
|
||||
our_arm = find_armature(o_objs)
|
||||
our_pelvis = bone_head_world(our_arm, "pelvis").copy()
|
||||
our_upperarm = bone_head_world(our_arm, "upperarm_l").copy()
|
||||
|
||||
# --- 3. detach garment from Sidekick armature, freeze world coords ---
|
||||
deselect_all()
|
||||
set_active(garment)
|
||||
garment.select_set(True)
|
||||
mw = garment.matrix_world.copy()
|
||||
garment.parent = None
|
||||
garment.matrix_world = mw
|
||||
for mod in list(garment.modifiers):
|
||||
garment.modifiers.remove(mod)
|
||||
# drop Sidekick body-blend shape keys (not part of our body-type system)
|
||||
if garment.data.shape_keys:
|
||||
set_active(garment)
|
||||
bpy.ops.object.shape_key_remove(all=True)
|
||||
# bake object transform into mesh data so object space == world space
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
|
||||
# --- 4. affine alignment garment -> our proportions ---
|
||||
sxy = our_upperarm.x / sk_upperarm.x
|
||||
sz = (our_upperarm.z - our_pelvis.z) / (sk_upperarm.z - sk_pelvis.z)
|
||||
S = Matrix.Diagonal(Vector((sxy, sxy, sz, 1.0)))
|
||||
scaled_pelvis = Vector((sk_pelvis.x * sxy, sk_pelvis.y * sxy, sk_pelvis.z * sz))
|
||||
t = our_pelvis - scaled_pelvis
|
||||
M = Matrix.Translation(t) @ S
|
||||
garment.data.transform(M)
|
||||
garment.data.update()
|
||||
|
||||
log["alignment"] = {
|
||||
"scale_xy": round(sxy, 5), "scale_z": round(sz, 5),
|
||||
"translate": [round(v, 5) for v in t],
|
||||
"sk_pelvis": [round(v, 5) for v in sk_pelvis],
|
||||
"our_pelvis": [round(v, 5) for v in our_pelvis],
|
||||
"sk_upperarm_l": [round(v, 5) for v in sk_upperarm],
|
||||
"our_upperarm_l": [round(v, 5) for v in our_upperarm],
|
||||
}
|
||||
|
||||
# residual landmark error after affine, for all 12 bound bones
|
||||
residuals = {}
|
||||
for bn in ["pelvis", "thigh_l", "thigh_r", "spine_01", "spine_02", "spine_03",
|
||||
"neck_01", "head", "clavicle_l", "upperarm_l", "clavicle_r",
|
||||
"upperarm_r"]:
|
||||
sk = bone_head_world(g_arm, bn)
|
||||
ours = bone_head_world(our_arm, bn if bn != "head" else "Head")
|
||||
sk_t = M @ sk
|
||||
d = sk_t - ours
|
||||
residuals[bn] = {"residual_m": round(d.length, 5),
|
||||
"delta": [round(v, 5) for v in d]}
|
||||
log["post_affine_bone_residuals"] = residuals
|
||||
|
||||
# --- 5. delete Sidekick armature ---
|
||||
deselect_all()
|
||||
g_arm.select_set(True)
|
||||
set_active(g_arm)
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
# --- 6. import + join our body segments as the weight-transfer source ---
|
||||
seg_meshes = []
|
||||
for seg in BODY_SEGS:
|
||||
objs = import_new(bpy.ops.import_scene.gltf,
|
||||
os.path.join(BODY_DIR, seg + ".glb"))
|
||||
picked = None
|
||||
for o in objs:
|
||||
if o.type == 'MESH' and len(o.vertex_groups) > 0:
|
||||
picked = o
|
||||
elif o.type == 'MESH':
|
||||
# debris (bounds icosphere etc.) — delete
|
||||
deselect_all()
|
||||
o.select_set(True)
|
||||
set_active(o)
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
# delete the segment's own armature copy
|
||||
arm = find_armature(objs)
|
||||
if arm and picked is not None:
|
||||
# unparent mesh first, keep transform
|
||||
pmw = picked.matrix_world.copy()
|
||||
picked.parent = None
|
||||
picked.matrix_world = pmw
|
||||
if arm:
|
||||
deselect_all()
|
||||
arm.select_set(True)
|
||||
set_active(arm)
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
if picked is None:
|
||||
print(f"WARNING: no weighted mesh in {seg}")
|
||||
continue
|
||||
for mod in list(picked.modifiers):
|
||||
picked.modifiers.remove(mod)
|
||||
seg_meshes.append(picked)
|
||||
|
||||
log["weight_source_segments"] = [
|
||||
{"name": m.name, "verts": len(m.data.vertices)} for m in seg_meshes]
|
||||
|
||||
deselect_all()
|
||||
for m in seg_meshes:
|
||||
m.select_set(True)
|
||||
set_active(seg_meshes[0])
|
||||
if len(seg_meshes) > 1:
|
||||
bpy.ops.object.join()
|
||||
body = bpy.context.view_layer.objects.active
|
||||
body.name = "weight_source_body"
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
|
||||
# --- 7. weight transfer: our body -> garment ---
|
||||
# wipe Sidekick vertex groups first so only OUR weights remain
|
||||
for vg in list(garment.vertex_groups):
|
||||
garment.vertex_groups.remove(vg)
|
||||
|
||||
deselect_all()
|
||||
set_active(garment)
|
||||
garment.select_set(True)
|
||||
|
||||
dt = garment.modifiers.new(name="WeightTransfer", type='DATA_TRANSFER')
|
||||
dt.object = body
|
||||
dt.use_vert_data = True
|
||||
dt.data_types_verts = {'VGROUP_WEIGHTS'}
|
||||
dt.vert_mapping = 'POLYINTERP_NEAREST'
|
||||
dt.layers_vgroup_select_src = 'ALL'
|
||||
dt.layers_vgroup_select_dst = 'NAME'
|
||||
bpy.ops.object.datalayout_transfer(modifier=dt.name)
|
||||
bpy.ops.object.modifier_apply(modifier=dt.name)
|
||||
|
||||
# normalize
|
||||
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
||||
bpy.ops.object.vertex_group_normalize_all(lock_active=False)
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
log["transferred_vgroups"] = [vg.name for vg in garment.vertex_groups]
|
||||
|
||||
# sanity: count verts with (near-)zero total weight
|
||||
zero = 0
|
||||
for v in garment.data.vertices:
|
||||
tot = sum(g.weight for g in v.groups)
|
||||
if tot < 1e-4:
|
||||
zero += 1
|
||||
log["zero_weight_verts"] = zero
|
||||
|
||||
# --- 8. bind garment to OUR armature ---
|
||||
garment.parent = our_arm
|
||||
garment.matrix_parent_inverse = our_arm.matrix_world.inverted()
|
||||
am = garment.modifiers.new(name="Armature", type='ARMATURE')
|
||||
am.object = our_arm
|
||||
|
||||
# --- 9. delete the body source, export armature + garment ---
|
||||
deselect_all()
|
||||
body.select_set(True)
|
||||
set_active(body)
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
os.makedirs(os.path.dirname(OUT_GLB), exist_ok=True)
|
||||
deselect_all()
|
||||
our_arm.select_set(True)
|
||||
garment.select_set(True)
|
||||
set_active(our_arm)
|
||||
bpy.ops.export_scene.gltf(
|
||||
filepath=OUT_GLB,
|
||||
export_format='GLB',
|
||||
use_selection=True,
|
||||
export_apply=False,
|
||||
export_animations=False,
|
||||
export_skins=True,
|
||||
export_morph=False,
|
||||
export_extras=False,
|
||||
)
|
||||
log["export"] = {"path": OUT_GLB, "size_kb": os.path.getsize(OUT_GLB) // 1024}
|
||||
|
||||
with open(OUT_LOG, "w") as f:
|
||||
json.dump(log, f, indent=2)
|
||||
|
||||
print("\n=== TRANSPLANT SUMMARY ===")
|
||||
print(json.dumps(log, indent=2))
|
||||
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
Spike T-1089 / Synty Sidekick intake — step 3 recon: the 11 body types.
|
||||
|
||||
Questions this answers (feeds 04_batch_fit_bodies.py):
|
||||
1. Do the garment-adjacent segments (torso/hips/neck/upper arms/upper legs)
|
||||
share topology (vert/poly counts) across all 11 body types? If yes, the
|
||||
Surface-Deform refit can be driven by direct vertex correspondence
|
||||
(bind SD on average_m, write target coords into the reference mesh).
|
||||
2. Do the per-segment embedded armatures differ across bodies (rest-pose
|
||||
bake), or is it the shared skeleton everywhere?
|
||||
|
||||
Run:
|
||||
tooling/blender --background --python \
|
||||
spikes/synty-intake/scripts/03_recon_bodies.py
|
||||
"""
|
||||
|
||||
import bpy
|
||||
import json
|
||||
import os
|
||||
|
||||
REPO = "/var/mnt/data/projects/settled-reach"
|
||||
BODIES_DIR = os.path.join(REPO, "client/assets/characters/bodies")
|
||||
OUT_JSON = os.path.join(REPO, "spikes/synty-intake/out/bodies_recon.json")
|
||||
|
||||
BODY_TYPES = ["average_f", "average_m", "child", "heavy_f", "heavy_m",
|
||||
"muscular_f", "muscular_m", "teen_f", "teen_m",
|
||||
"thin_f", "thin_m"]
|
||||
|
||||
SEGS = ["seg_torso", "seg_hips", "seg_neck",
|
||||
"seg_arm_upper_l", "seg_arm_upper_r",
|
||||
"seg_leg_upper_l", "seg_leg_upper_r"]
|
||||
|
||||
LANDMARK_BONES = ["pelvis", "spine_01", "spine_02", "spine_03",
|
||||
"clavicle_l", "upperarm_l", "thigh_l", "neck_01", "Head"]
|
||||
|
||||
|
||||
def clear_scene():
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
for block_list in (bpy.data.meshes, bpy.data.armatures, bpy.data.images,
|
||||
bpy.data.materials):
|
||||
for block in list(block_list):
|
||||
if block.users == 0:
|
||||
block_list.remove(block)
|
||||
|
||||
|
||||
def import_new(path):
|
||||
before = set(bpy.data.objects)
|
||||
bpy.ops.import_scene.gltf(filepath=path)
|
||||
return list(set(bpy.data.objects) - before)
|
||||
|
||||
|
||||
report = {}
|
||||
|
||||
for body in BODY_TYPES:
|
||||
body_dir = os.path.join(BODIES_DIR, body)
|
||||
entry = {"segments": {}, "armature": None}
|
||||
for seg in SEGS:
|
||||
path = os.path.join(body_dir, seg + ".glb")
|
||||
if not os.path.exists(path):
|
||||
entry["segments"][seg] = None
|
||||
continue
|
||||
clear_scene()
|
||||
objs = import_new(path)
|
||||
mesh = None
|
||||
for o in objs:
|
||||
if o.type == 'MESH' and len(o.vertex_groups) > 0:
|
||||
mesh = o
|
||||
arm = None
|
||||
for o in objs:
|
||||
if o.type == 'ARMATURE':
|
||||
arm = o
|
||||
seg_info = {}
|
||||
if mesh is not None:
|
||||
seg_info["verts"] = len(mesh.data.vertices)
|
||||
seg_info["polys"] = len(mesh.data.polygons)
|
||||
seg_info["vgroups"] = len(mesh.vertex_groups)
|
||||
entry["segments"][seg] = seg_info
|
||||
# record armature landmarks once per body (from seg_torso)
|
||||
if seg == "seg_torso" and arm is not None:
|
||||
lm = {}
|
||||
for bn in LANDMARK_BONES:
|
||||
b = arm.data.bones.get(bn)
|
||||
if b is None:
|
||||
b = arm.data.bones.get(bn.lower())
|
||||
if b is not None:
|
||||
w = arm.matrix_world @ b.head_local
|
||||
lm[bn] = [round(v, 5) for v in w]
|
||||
entry["armature"] = {
|
||||
"bones": len(arm.data.bones),
|
||||
"landmarks": lm,
|
||||
}
|
||||
report[body] = entry
|
||||
|
||||
# cross-body topology comparison vs average_m
|
||||
ref = report["average_m"]["segments"]
|
||||
topo = {}
|
||||
for body in BODY_TYPES:
|
||||
diffs = []
|
||||
for seg in SEGS:
|
||||
a = ref.get(seg)
|
||||
b = report[body]["segments"].get(seg)
|
||||
if not a or not b:
|
||||
diffs.append(f"{seg}: missing")
|
||||
elif (a["verts"], a["polys"]) != (b["verts"], b["polys"]):
|
||||
diffs.append(f"{seg}: {b['verts']}v/{b['polys']}p vs "
|
||||
f"ref {a['verts']}v/{a['polys']}p")
|
||||
topo[body] = diffs if diffs else "MATCH"
|
||||
report["_topology_vs_average_m"] = topo
|
||||
|
||||
os.makedirs(os.path.dirname(OUT_JSON), exist_ok=True)
|
||||
with open(OUT_JSON, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
|
||||
print("\n=== BODIES RECON ===")
|
||||
print(json.dumps(report, indent=2))
|
||||
@@ -0,0 +1,420 @@
|
||||
"""
|
||||
Spike T-1089 / Synty Sidekick intake — step 4: 11-body batch fit.
|
||||
|
||||
Takes the transplanted torso garment (fitted to average_m, bound to our shared
|
||||
armature = the average_m bind skeleton) through a multi-body refit, following
|
||||
the fit_outfits_to_bodies.py Surface-Deform approach, adapted for the measured
|
||||
reality that the 11 bodies do NOT share topology (03_recon_bodies.py:
|
||||
every body is independently authored; thin_*/heavy_* even lack seg_hips).
|
||||
|
||||
Per target body:
|
||||
1. Import the transplanted garment GLB; capture reference (average_m)
|
||||
landmarks from its armature; detach + freeze the garment mesh.
|
||||
2. Import the average_m reference segments, join -> ref_body.
|
||||
3. Import the target body segments (skipping missing ones), keep the
|
||||
seg_torso armature as the target bind skeleton, join -> tgt_body.
|
||||
4. Landmark affine (same machinery as 02): scale XY by shoulder ratio,
|
||||
scale Z by pelvis->upperarm span ratio, translate pelvis->pelvis.
|
||||
Applied to BOTH the garment and ref_body.
|
||||
5. BVH nearest-surface warp (replaces Surface Deform + Shrinkwrap —
|
||||
first attempt showed SD driven by a shrinkwrapped driver mesh
|
||||
spikes 14-28x on fold discontinuities, and SD bind itself is
|
||||
unreliable against the multi-shell joined segment mesh):
|
||||
for each garment vertex, find the nearest point on ref_body,
|
||||
decompose the offset into (height along surface normal +
|
||||
tangential residual), re-evaluate the same surface point on
|
||||
tgt_body via its own nearest-surface lookup, and rebuild the
|
||||
vertex at the target surface with the offset preserved. The
|
||||
per-vertex displacement field is then Laplacian-smoothed over
|
||||
the garment mesh connectivity to kill nearest-point-map
|
||||
discontinuities while keeping rigid details (back device,
|
||||
plating) coherent.
|
||||
6. Wipe vgroups, Data Transfer POLYINTERP_NEAREST weights from tgt_body,
|
||||
normalize, bind to the target body's embedded armature (same bind pose
|
||||
as the body segments -> garment deforms identically to the body under
|
||||
any runtime skeleton pose).
|
||||
7. Export armature + garment GLB (export_skins=True) to
|
||||
out/bodies/SK_SCFI_CIVL_09_10TORS_HU01_<body>.glb
|
||||
8. Metrics (out/bodies_fit_log.json): zero-weight verts, AABB,
|
||||
edge-length distortion vs post-affine baseline (collapse/fold
|
||||
detector), signed-distance stats garment vs body surface
|
||||
(penetration detector).
|
||||
|
||||
Run:
|
||||
tooling/blender --background --python \
|
||||
spikes/synty-intake/scripts/04_batch_fit_bodies.py
|
||||
"""
|
||||
|
||||
import bpy
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from mathutils import Matrix, Vector
|
||||
from mathutils.bvhtree import BVHTree
|
||||
|
||||
REPO = "/var/mnt/data/projects/settled-reach"
|
||||
GARMENT_GLB = os.path.join(REPO, "spikes/synty-intake/out",
|
||||
"SK_SCFI_CIVL_09_10TORS_HU01_quaternius.glb")
|
||||
BODIES_DIR = os.path.join(REPO, "client/assets/characters/bodies")
|
||||
OUT_DIR = os.path.join(REPO, "spikes/synty-intake/out/bodies")
|
||||
OUT_LOG = os.path.join(REPO, "spikes/synty-intake/out/bodies_fit_log.json")
|
||||
|
||||
BODY_TYPES = ["average_m", "average_f", "muscular_m", "muscular_f",
|
||||
"thin_m", "thin_f", "heavy_m", "heavy_f",
|
||||
"teen_m", "teen_f", "child"]
|
||||
|
||||
SEGS = ["seg_torso", "seg_hips", "seg_neck",
|
||||
"seg_arm_upper_l", "seg_arm_upper_r",
|
||||
"seg_leg_upper_l", "seg_leg_upper_r"]
|
||||
|
||||
PENETRATION_MM = 3.0 # garment vertex deeper than this inside the body -> flag
|
||||
|
||||
|
||||
def deselect_all():
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
|
||||
|
||||
def set_active(obj):
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
|
||||
|
||||
def clear_scene():
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
for block_list in (bpy.data.meshes, bpy.data.armatures, bpy.data.images,
|
||||
bpy.data.materials):
|
||||
for block in list(block_list):
|
||||
if block.users == 0:
|
||||
block_list.remove(block)
|
||||
|
||||
|
||||
def import_new(op, path, **kw):
|
||||
before = set(bpy.data.objects)
|
||||
op(filepath=path, **kw)
|
||||
return list(set(bpy.data.objects) - before)
|
||||
|
||||
|
||||
def find_armature(objs):
|
||||
for o in objs:
|
||||
if o.type == 'ARMATURE':
|
||||
return o
|
||||
return None
|
||||
|
||||
|
||||
def bone_head_world(arm, name):
|
||||
b = arm.data.bones.get(name)
|
||||
return (arm.matrix_world @ b.head_local).copy() if b else None
|
||||
|
||||
|
||||
def delete_obj(obj):
|
||||
deselect_all()
|
||||
obj.select_set(True)
|
||||
set_active(obj)
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
|
||||
def freeze(obj):
|
||||
"""Unparent keeping transform, strip modifiers, bake transform to data."""
|
||||
deselect_all()
|
||||
set_active(obj)
|
||||
obj.select_set(True)
|
||||
mw = obj.matrix_world.copy()
|
||||
obj.parent = None
|
||||
obj.matrix_world = mw
|
||||
for mod in list(obj.modifiers):
|
||||
obj.modifiers.remove(mod)
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
|
||||
|
||||
def import_body(body, keep_armature):
|
||||
"""Import + join a body's garment-adjacent segments.
|
||||
|
||||
Returns (joined_mesh, armature_or_None, segments_used).
|
||||
"""
|
||||
seg_meshes = []
|
||||
kept_arm = None
|
||||
used = []
|
||||
for seg in SEGS:
|
||||
path = os.path.join(BODIES_DIR, body, seg + ".glb")
|
||||
if not os.path.exists(path):
|
||||
continue
|
||||
objs = import_new(bpy.ops.import_scene.gltf, path)
|
||||
picked = None
|
||||
for o in objs:
|
||||
if o.type == 'MESH' and len(o.vertex_groups) > 0:
|
||||
picked = o
|
||||
elif o.type == 'MESH':
|
||||
delete_obj(o) # debris (bounds helpers etc.)
|
||||
arm = find_armature(objs)
|
||||
if picked is not None:
|
||||
pmw = picked.matrix_world.copy()
|
||||
picked.parent = None
|
||||
picked.matrix_world = pmw
|
||||
if arm is not None:
|
||||
if keep_armature and kept_arm is None:
|
||||
kept_arm = arm
|
||||
else:
|
||||
delete_obj(arm)
|
||||
if picked is None:
|
||||
print(f" WARNING: no weighted mesh in {body}/{seg}")
|
||||
continue
|
||||
for mod in list(picked.modifiers):
|
||||
picked.modifiers.remove(mod)
|
||||
seg_meshes.append(picked)
|
||||
used.append(seg)
|
||||
deselect_all()
|
||||
for m in seg_meshes:
|
||||
m.select_set(True)
|
||||
set_active(seg_meshes[0])
|
||||
if len(seg_meshes) > 1:
|
||||
bpy.ops.object.join()
|
||||
joined = bpy.context.view_layer.objects.active
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
return joined, kept_arm, used
|
||||
|
||||
|
||||
def edge_lengths(mesh_obj):
|
||||
verts = mesh_obj.data.vertices
|
||||
return [(verts[e.vertices[0]].co - verts[e.vertices[1]].co).length
|
||||
for e in mesh_obj.data.edges]
|
||||
|
||||
|
||||
def aabb(mesh_obj):
|
||||
xs = [v.co for v in mesh_obj.data.vertices]
|
||||
lo = Vector((min(v.x for v in xs), min(v.y for v in xs),
|
||||
min(v.z for v in xs)))
|
||||
hi = Vector((max(v.x for v in xs), max(v.y for v in xs),
|
||||
max(v.z for v in xs)))
|
||||
return lo, hi
|
||||
|
||||
|
||||
def warp_garment(garment, ref_body, tgt_body, depsgraph,
|
||||
smooth_iterations=20, smooth_lambda=0.5):
|
||||
"""Nearest-surface warp: move each garment vertex from its offset
|
||||
relative to ref_body onto the equivalent offset relative to tgt_body,
|
||||
then Laplacian-smooth the displacement field over the garment mesh."""
|
||||
bvh_ref = BVHTree.FromObject(ref_body, depsgraph)
|
||||
bvh_tgt = BVHTree.FromObject(tgt_body, depsgraph)
|
||||
|
||||
verts = garment.data.vertices
|
||||
n = len(verts)
|
||||
disp = [None] * n
|
||||
for i, v in enumerate(verts):
|
||||
co_r, n_r, _idx, _d = bvh_ref.find_nearest(v.co)
|
||||
if co_r is None:
|
||||
disp[i] = Vector((0, 0, 0))
|
||||
continue
|
||||
delta = v.co - co_r
|
||||
h = delta.dot(n_r)
|
||||
tang = delta - h * n_r
|
||||
co_t, n_t, _idx2, _d2 = bvh_tgt.find_nearest(co_r)
|
||||
if co_t is None:
|
||||
disp[i] = Vector((0, 0, 0))
|
||||
continue
|
||||
new_co = co_t + h * n_t + tang
|
||||
disp[i] = new_co - v.co
|
||||
|
||||
# adjacency from edges
|
||||
adj = [[] for _ in range(n)]
|
||||
for e in garment.data.edges:
|
||||
a, b = e.vertices
|
||||
adj[a].append(b)
|
||||
adj[b].append(a)
|
||||
|
||||
# Laplacian smoothing of the displacement field (not the geometry)
|
||||
for _ in range(smooth_iterations):
|
||||
new_disp = [None] * n
|
||||
for i in range(n):
|
||||
if not adj[i]:
|
||||
new_disp[i] = disp[i]
|
||||
continue
|
||||
avg = Vector((0, 0, 0))
|
||||
for j in adj[i]:
|
||||
avg += disp[j]
|
||||
avg /= len(adj[i])
|
||||
new_disp[i] = disp[i].lerp(avg, smooth_lambda)
|
||||
disp = new_disp
|
||||
|
||||
max_disp = 0.0
|
||||
for i, v in enumerate(verts):
|
||||
v.co = v.co + disp[i]
|
||||
if disp[i].length > max_disp:
|
||||
max_disp = disp[i].length
|
||||
garment.data.update()
|
||||
return {"max_displacement_mm": round(max_disp * 1000, 1),
|
||||
"smooth_iterations": smooth_iterations}
|
||||
|
||||
|
||||
def signed_distance_stats(garment, body_obj, depsgraph):
|
||||
"""Signed distance of each garment vertex to the body surface.
|
||||
|
||||
Positive = outside the body (along surface normal), negative = inside.
|
||||
"""
|
||||
bvh = BVHTree.FromObject(body_obj, depsgraph)
|
||||
dists = []
|
||||
for v in garment.data.vertices:
|
||||
co, normal, _idx, _d = bvh.find_nearest(v.co)
|
||||
if co is None:
|
||||
continue
|
||||
dists.append((v.co - co).dot(normal))
|
||||
dists.sort()
|
||||
n = len(dists)
|
||||
inside = [d for d in dists if d < -PENETRATION_MM / 1000.0]
|
||||
return {
|
||||
"verts_sampled": n,
|
||||
"min_signed_mm": round(dists[0] * 1000, 2),
|
||||
"p05_signed_mm": round(dists[max(0, int(n * 0.05) - 1)] * 1000, 2),
|
||||
"median_signed_mm": round(dists[n // 2] * 1000, 2),
|
||||
"max_signed_mm": round(dists[-1] * 1000, 2),
|
||||
f"verts_inside_gt_{PENETRATION_MM:g}mm": len(inside),
|
||||
"pct_inside": round(100.0 * len(inside) / n, 2),
|
||||
}
|
||||
|
||||
|
||||
log = {"garment_glb": GARMENT_GLB, "bodies": {}}
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
|
||||
for body in BODY_TYPES:
|
||||
print(f"\n{'='*60}\n BODY: {body}\n{'='*60}")
|
||||
entry = {}
|
||||
clear_scene()
|
||||
|
||||
# --- 1. garment + reference landmarks ---
|
||||
g_objs = import_new(bpy.ops.import_scene.gltf, GARMENT_GLB)
|
||||
ref_arm = find_armature(g_objs)
|
||||
# the GLB also carries a bounds icosphere — the garment is the skinned
|
||||
# mesh (has vertex groups); the set-diff order is nondeterministic, so
|
||||
# pick explicitly and delete the rest
|
||||
g_meshes = [o for o in g_objs if o.type == 'MESH']
|
||||
garment = max((m for m in g_meshes if len(m.vertex_groups) > 0),
|
||||
key=lambda m: len(m.data.vertices))
|
||||
ref_pelvis = bone_head_world(ref_arm, "pelvis")
|
||||
ref_upperarm = bone_head_world(ref_arm, "upperarm_l")
|
||||
freeze(garment)
|
||||
for m in g_meshes:
|
||||
if m is not garment:
|
||||
delete_obj(m)
|
||||
delete_obj(ref_arm)
|
||||
entry["garment_verts"] = len(garment.data.vertices)
|
||||
|
||||
# --- 2. reference body (average_m) ---
|
||||
ref_body, _, _ = import_body("average_m", keep_armature=False)
|
||||
ref_body.name = "ref_body"
|
||||
|
||||
# --- 3. target body ---
|
||||
tgt_body, tgt_arm, used = import_body(body, keep_armature=True)
|
||||
tgt_body.name = "tgt_body"
|
||||
entry["segments_used"] = used
|
||||
if tgt_arm is None:
|
||||
entry["result"] = "FAIL: no target armature"
|
||||
log["bodies"][body] = entry
|
||||
continue
|
||||
tgt_pelvis = bone_head_world(tgt_arm, "pelvis")
|
||||
tgt_upperarm = bone_head_world(tgt_arm, "upperarm_l")
|
||||
|
||||
# --- 4. landmark affine ref -> tgt ---
|
||||
sxy = tgt_upperarm.x / ref_upperarm.x
|
||||
sz = (tgt_upperarm.z - tgt_pelvis.z) / (ref_upperarm.z - ref_pelvis.z)
|
||||
S = Matrix.Diagonal(Vector((sxy, sxy, sz, 1.0)))
|
||||
scaled_pelvis = Vector((ref_pelvis.x * sxy, ref_pelvis.y * sxy,
|
||||
ref_pelvis.z * sz))
|
||||
t = tgt_pelvis - scaled_pelvis
|
||||
M = Matrix.Translation(t) @ S
|
||||
garment.data.transform(M)
|
||||
garment.data.update()
|
||||
ref_body.data.transform(M)
|
||||
ref_body.data.update()
|
||||
entry["affine"] = {"scale_xy": round(sxy, 5), "scale_z": round(sz, 5),
|
||||
"translate": [round(v, 5) for v in t]}
|
||||
|
||||
# post-affine baseline for distortion metrics
|
||||
base_edges = edge_lengths(garment)
|
||||
lo, hi = aabb(garment)
|
||||
entry["aabb_post_affine"] = {"min": [round(v, 4) for v in lo],
|
||||
"max": [round(v, 4) for v in hi]}
|
||||
|
||||
# --- 5. BVH nearest-surface warp ref_body -> tgt_body ---
|
||||
depsgraph = bpy.context.evaluated_depsgraph_get()
|
||||
entry["warp"] = warp_garment(garment, ref_body, tgt_body, depsgraph)
|
||||
|
||||
delete_obj(ref_body)
|
||||
|
||||
# --- 7. weights from the target body ---
|
||||
for vg in list(garment.vertex_groups):
|
||||
garment.vertex_groups.remove(vg)
|
||||
deselect_all()
|
||||
set_active(garment)
|
||||
garment.select_set(True)
|
||||
dt = garment.modifiers.new(name="WeightTransfer", type='DATA_TRANSFER')
|
||||
dt.object = tgt_body
|
||||
dt.use_vert_data = True
|
||||
dt.data_types_verts = {'VGROUP_WEIGHTS'}
|
||||
dt.vert_mapping = 'POLYINTERP_NEAREST'
|
||||
dt.layers_vgroup_select_src = 'ALL'
|
||||
dt.layers_vgroup_select_dst = 'NAME'
|
||||
bpy.ops.object.datalayout_transfer(modifier=dt.name)
|
||||
bpy.ops.object.modifier_apply(modifier=dt.name)
|
||||
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
||||
bpy.ops.object.vertex_group_normalize_all(lock_active=False)
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
zero = sum(1 for v in garment.data.vertices
|
||||
if sum(g.weight for g in v.groups) < 1e-4)
|
||||
entry["zero_weight_verts"] = zero
|
||||
entry["vgroups"] = len(garment.vertex_groups)
|
||||
|
||||
# --- 8. metrics ---
|
||||
post_edges = edge_lengths(garment)
|
||||
ratios = [p / b for p, b in zip(post_edges, base_edges) if b > 1e-9]
|
||||
entry["edge_distortion"] = {
|
||||
"max_stretch": round(max(ratios), 3),
|
||||
"max_shrink": round(min(ratios), 3),
|
||||
"edges_gt_2x": sum(1 for r in ratios if r > 2.0),
|
||||
"edges_lt_0.5x": sum(1 for r in ratios if r < 0.5),
|
||||
"edges_total": len(ratios),
|
||||
}
|
||||
lo, hi = aabb(garment)
|
||||
entry["aabb_post_fit"] = {"min": [round(v, 4) for v in lo],
|
||||
"max": [round(v, 4) for v in hi]}
|
||||
depsgraph = bpy.context.evaluated_depsgraph_get()
|
||||
entry["signed_distance"] = signed_distance_stats(garment, tgt_body,
|
||||
depsgraph)
|
||||
|
||||
# --- 9. bind + export ---
|
||||
garment.parent = tgt_arm
|
||||
garment.matrix_parent_inverse = tgt_arm.matrix_world.inverted()
|
||||
am = garment.modifiers.new(name="Armature", type='ARMATURE')
|
||||
am.object = tgt_arm
|
||||
delete_obj(tgt_body)
|
||||
|
||||
out_glb = os.path.join(OUT_DIR,
|
||||
f"SK_SCFI_CIVL_09_10TORS_HU01_{body}.glb")
|
||||
deselect_all()
|
||||
tgt_arm.select_set(True)
|
||||
garment.select_set(True)
|
||||
set_active(tgt_arm)
|
||||
bpy.ops.export_scene.gltf(
|
||||
filepath=out_glb,
|
||||
export_format='GLB',
|
||||
use_selection=True,
|
||||
export_apply=False,
|
||||
export_animations=False,
|
||||
export_skins=True,
|
||||
export_morph=False,
|
||||
export_extras=False,
|
||||
)
|
||||
entry["export"] = {"path": out_glb,
|
||||
"size_kb": os.path.getsize(out_glb) // 1024}
|
||||
entry["result"] = "OK"
|
||||
log["bodies"][body] = entry
|
||||
|
||||
with open(OUT_LOG, "w") as f:
|
||||
json.dump(log, f, indent=2)
|
||||
|
||||
print("\n=== BATCH FIT SUMMARY ===")
|
||||
for body, e in log["bodies"].items():
|
||||
sd_stats = e.get("signed_distance", {})
|
||||
print(f" {body:12s} {e.get('result','?'):14s} "
|
||||
f"zero_w={e.get('zero_weight_verts','-')} "
|
||||
f"inside%={sd_stats.get('pct_inside','-')} "
|
||||
f"stretch={e.get('edge_distortion',{}).get('max_stretch','-')}")
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract a .unitypackage (gzipped tar of GUID dirs) into real paths.
|
||||
|
||||
Copies the raw `asset` bytes of every FBX/texture/metadata entry to
|
||||
raw/<original pathname>. Prints a summary by extension.
|
||||
"""
|
||||
import sys
|
||||
import tarfile
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from collections import Counter
|
||||
|
||||
KEEP = {".fbx", ".png", ".tga", ".json", ".txt", ".sk", ".asset"}
|
||||
|
||||
|
||||
def main(pkg: str, out_root: str) -> None:
|
||||
out = Path(out_root)
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
counts: Counter = Counter()
|
||||
with tarfile.open(pkg, "r:gz") as tar:
|
||||
members = {m.name: m for m in tar.getmembers()}
|
||||
guids = sorted({name.split("/")[0] for name in members if "/" in name})
|
||||
for guid in guids:
|
||||
pn = members.get(f"{guid}/pathname")
|
||||
asset = members.get(f"{guid}/asset")
|
||||
if pn is None or asset is None:
|
||||
continue
|
||||
pathname = tar.extractfile(pn).read().decode("utf-8").splitlines()[0].strip()
|
||||
ext = Path(pathname).suffix.lower()
|
||||
if ext not in KEEP:
|
||||
counts[f"(skipped {ext or 'dir'})"] += 1
|
||||
continue
|
||||
dest = out / pathname
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
with tar.extractfile(asset) as src, open(dest, "wb") as dst:
|
||||
shutil.copyfileobj(src, dst)
|
||||
counts[ext] += 1
|
||||
for ext, n in sorted(counts.items()):
|
||||
print(f"{ext}\t{n}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1], sys.argv[2])
|
||||
@@ -0,0 +1,193 @@
|
||||
extends Node3D
|
||||
## Spike T-1089 Synty intake — 11-body batch QA scene (THROWAWAY, lives in
|
||||
## client/spike_synty_qa/ only for the res:// path; source of truth is
|
||||
## spikes/synty-intake/scripts/qa_bodies.gd).
|
||||
##
|
||||
## For each body type on disk: builds a CharacterVisual, reparents that
|
||||
## body's refitted Synty torso garment onto its skeleton (mirrors
|
||||
## character_visual.gd:517-535), screenshots idle front, then plays Walk
|
||||
## and screenshots two stride phases (front + side). Peasant pants/shoes
|
||||
## are added only for bodies that have them on disk. Saves to
|
||||
## spikes/synty-intake/out/qa_bodies/, then quits.
|
||||
|
||||
const BODIES_GLB_DIR := "res://spike_synty_qa/bodies/"
|
||||
const SHOT_DIR := "/var/mnt/data/projects/settled-reach/spikes/synty-intake/out/qa_bodies/"
|
||||
const CLOTHING_DIR := "res://assets/characters/clothing/"
|
||||
|
||||
const BODY_KEYS: Array[String] = [
|
||||
"average_m", "average_f", "muscular_m", "muscular_f",
|
||||
"thin_m", "thin_f", "heavy_m", "heavy_f",
|
||||
"teen_m", "teen_f", "child",
|
||||
]
|
||||
|
||||
const KEY_TO_ENUM := {
|
||||
"average_m": CharacterVisualDescriptor.BodyType.AVERAGE_M,
|
||||
"average_f": CharacterVisualDescriptor.BodyType.AVERAGE_F,
|
||||
"muscular_m": CharacterVisualDescriptor.BodyType.MUSCULAR_M,
|
||||
"muscular_f": CharacterVisualDescriptor.BodyType.MUSCULAR_F,
|
||||
"thin_m": CharacterVisualDescriptor.BodyType.THIN_M,
|
||||
"thin_f": CharacterVisualDescriptor.BodyType.THIN_F,
|
||||
"heavy_m": CharacterVisualDescriptor.BodyType.HEAVY_M,
|
||||
"heavy_f": CharacterVisualDescriptor.BodyType.HEAVY_F,
|
||||
"teen_m": CharacterVisualDescriptor.BodyType.TEEN_M,
|
||||
"teen_f": CharacterVisualDescriptor.BodyType.TEEN_F,
|
||||
"child": CharacterVisualDescriptor.BodyType.CHILD,
|
||||
}
|
||||
|
||||
var _cam: Camera3D = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_setup_stage()
|
||||
_run_qa.call_deferred()
|
||||
|
||||
|
||||
func _setup_stage() -> void:
|
||||
var light := DirectionalLight3D.new()
|
||||
light.rotation_degrees = Vector3(-45, 30, 0)
|
||||
light.light_energy = 1.2
|
||||
add_child(light)
|
||||
|
||||
var env := Environment.new()
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.8, 0.8, 0.85)
|
||||
env.ambient_light_energy = 0.9
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.18, 0.19, 0.22)
|
||||
var we := WorldEnvironment.new()
|
||||
we.environment = env
|
||||
add_child(we)
|
||||
|
||||
_cam = Camera3D.new()
|
||||
add_child(_cam)
|
||||
|
||||
|
||||
func _cam_front(h: float) -> void:
|
||||
_cam.position = Vector3(0, h * 0.72, h * 0.85)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0, h * 0.62, 0), Vector3.UP)
|
||||
|
||||
|
||||
func _cam_side(h: float) -> void:
|
||||
_cam.position = Vector3(h * 0.85, h * 0.72, 0)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0, h * 0.62, 0), Vector3.UP)
|
||||
|
||||
|
||||
func _attach_garment(skel: Skeleton3D, glb_path: String) -> Array[MeshInstance3D]:
|
||||
var out: Array[MeshInstance3D] = []
|
||||
var scene := load(glb_path) as PackedScene
|
||||
if scene == null:
|
||||
push_error("QA: garment GLB failed to load: " + glb_path)
|
||||
return out
|
||||
var inst := scene.instantiate()
|
||||
var stack: Array[Node] = [inst]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
if n is MeshInstance3D and (n as MeshInstance3D).skin != null:
|
||||
var mi := n as MeshInstance3D
|
||||
mi.get_parent().remove_child(mi)
|
||||
mi.owner = null
|
||||
skel.add_child(mi)
|
||||
out.append(mi)
|
||||
inst.queue_free()
|
||||
return out
|
||||
|
||||
|
||||
func _shot(fname: String) -> void:
|
||||
await RenderingServer.frame_post_draw
|
||||
var img := get_viewport().get_texture().get_image()
|
||||
img.save_png(SHOT_DIR + fname)
|
||||
print("QA: saved ", fname)
|
||||
|
||||
|
||||
func _wait_frames(n: int) -> void:
|
||||
for i in n:
|
||||
await get_tree().process_frame
|
||||
|
||||
|
||||
func _run_qa() -> void:
|
||||
DirAccess.make_dir_recursive_absolute(SHOT_DIR)
|
||||
for body_key: String in BODY_KEYS:
|
||||
var glb := BODIES_GLB_DIR + "SK_SCFI_CIVL_09_10TORS_HU01_%s.glb" % body_key
|
||||
if not ResourceLoader.exists(glb):
|
||||
print("QA: MISSING garment GLB for ", body_key, " — skipped")
|
||||
continue
|
||||
|
||||
var cv := CharacterVisual.new()
|
||||
add_child(cv)
|
||||
|
||||
var desc := CharacterVisualDescriptor.new()
|
||||
desc.body_type = KEY_TO_ENUM[body_key]
|
||||
desc.head_id = "head_001"
|
||||
desc.hair_id = "buzzed"
|
||||
desc.eyebrow_id = "regular"
|
||||
desc.skin_tone = 3
|
||||
var slots := {}
|
||||
if ResourceLoader.exists(CLOTHING_DIR + "peasant_pants/%s.glb" % body_key):
|
||||
slots["legs"] = "peasant_pants"
|
||||
if ResourceLoader.exists(CLOTHING_DIR + "peasant_shoes/%s.glb" % body_key):
|
||||
slots["feet"] = "peasant_shoes"
|
||||
desc.clothing_slots = slots
|
||||
cv.load_descriptor(desc)
|
||||
|
||||
var skel := cv.get_skeleton()
|
||||
if skel == null:
|
||||
push_error("QA: no skeleton for " + body_key)
|
||||
cv.queue_free()
|
||||
continue
|
||||
print("QA[", body_key, "]: skeleton bones = ", skel.get_bone_count(),
|
||||
" pants = ", slots.has("legs"))
|
||||
|
||||
# rough height for camera framing (child/teen are shorter)
|
||||
var h0 := 1.75
|
||||
if body_key == "child" or body_key.begins_with("teen"):
|
||||
h0 = 1.45
|
||||
|
||||
# body-only control shot BEFORE the garment — separates body-asset
|
||||
# rendering defects from garment fit defects
|
||||
await _wait_frames(5)
|
||||
_cam_front(h0)
|
||||
await _shot("%s_0_body_only.png" % body_key)
|
||||
|
||||
var meshes := _attach_garment(skel, glb)
|
||||
for mi in meshes:
|
||||
print("QA[", body_key, "]: garment '", mi.name, "' skin binds = ",
|
||||
mi.skin.get_bind_count(), " aabb = ", mi.get_aabb())
|
||||
|
||||
var h := h0
|
||||
|
||||
await _wait_frames(5)
|
||||
_cam_front(h)
|
||||
await _shot("%s_1_idle_front.png" % body_key)
|
||||
|
||||
var ap := cv.get_animation_player()
|
||||
var walk := ""
|
||||
if ap:
|
||||
for lib_name in ap.get_animation_library_list():
|
||||
var lib := ap.get_animation_library(lib_name)
|
||||
for a in lib.get_animation_list():
|
||||
var full := str(lib_name) + "/" + str(a)
|
||||
if full.to_lower().contains("walk"):
|
||||
walk = str(a)
|
||||
if full.to_lower().ends_with("/walk"):
|
||||
break
|
||||
if walk != "":
|
||||
cv.play_animation(walk)
|
||||
await _wait_frames(24)
|
||||
_cam_front(h)
|
||||
await _shot("%s_2_walk_front.png" % body_key)
|
||||
_cam_side(h)
|
||||
await _wait_frames(8)
|
||||
await _shot("%s_3_walk_side.png" % body_key)
|
||||
else:
|
||||
push_warning("QA: no walk animation for " + body_key)
|
||||
|
||||
for mi in meshes:
|
||||
print("QA[", body_key, "]: post-walk garment aabb = ", mi.get_aabb())
|
||||
|
||||
cv.queue_free()
|
||||
await _wait_frames(2)
|
||||
|
||||
print("QA: DONE")
|
||||
get_tree().quit(0)
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://synty_qa_bodies1"]
|
||||
|
||||
[ext_resource type="Script" path="res://spike_synty_qa/qa_bodies.gd" id="1"]
|
||||
|
||||
[node name="QABodies" type="Node3D"]
|
||||
script = ExtResource("1")
|
||||
@@ -0,0 +1,167 @@
|
||||
extends Node3D
|
||||
## Spike T-1089 Synty intake — Godot QA scene (THROWAWAY, lives in
|
||||
## client/spike_synty_qa/ only for the res:// path; source of truth is
|
||||
## spikes/synty-intake/scripts/qa_scene.gd).
|
||||
##
|
||||
## Builds a CharacterVisual (average_m + peasant pants/shoes), reparents the
|
||||
## transplanted Synty torso garment onto its skeleton (mirrors
|
||||
## character_visual.gd:517-535), plays Walk, saves screenshots to
|
||||
## spikes/synty-intake/out/qa/, then quits.
|
||||
|
||||
const GARMENT_GLB := "res://spike_synty_qa/SK_SCFI_CIVL_09_10TORS_HU01_quaternius.glb"
|
||||
const SHOT_DIR := "/var/mnt/data/projects/settled-reach/spikes/synty-intake/out/qa/"
|
||||
|
||||
var _cv: CharacterVisual = null
|
||||
var _cam: Camera3D = null
|
||||
var _garment_meshes: Array[MeshInstance3D] = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_setup_stage()
|
||||
_cv = CharacterVisual.new()
|
||||
add_child(_cv)
|
||||
|
||||
var desc := CharacterVisualDescriptor.new()
|
||||
desc.body_type = CharacterVisualDescriptor.BodyType.AVERAGE_M
|
||||
desc.head_id = "head_001"
|
||||
desc.hair_id = "buzzed"
|
||||
desc.eyebrow_id = "regular"
|
||||
desc.skin_tone = 3
|
||||
desc.clothing_slots = {"legs": "peasant_pants", "feet": "peasant_shoes"}
|
||||
_cv.load_descriptor(desc)
|
||||
|
||||
var skel := _cv.get_skeleton()
|
||||
print("QA: skeleton bones = ", skel.get_bone_count() if skel else -1)
|
||||
|
||||
_attach_garment(skel)
|
||||
_run_qa.call_deferred()
|
||||
|
||||
|
||||
func _setup_stage() -> void:
|
||||
var light := DirectionalLight3D.new()
|
||||
light.rotation_degrees = Vector3(-45, 30, 0)
|
||||
light.light_energy = 1.2
|
||||
add_child(light)
|
||||
|
||||
var env := Environment.new()
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.8, 0.8, 0.85)
|
||||
env.ambient_light_energy = 0.9
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.18, 0.19, 0.22)
|
||||
var we := WorldEnvironment.new()
|
||||
we.environment = env
|
||||
add_child(we)
|
||||
|
||||
_cam = Camera3D.new()
|
||||
add_child(_cam)
|
||||
_set_cam_front()
|
||||
|
||||
|
||||
func _set_cam_front() -> void:
|
||||
_cam.position = Vector3(0, 1.25, 1.4)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0, 1.15, 0), Vector3.UP)
|
||||
|
||||
|
||||
func _set_cam_side() -> void:
|
||||
_cam.position = Vector3(1.4, 1.25, 0)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0, 1.15, 0), Vector3.UP)
|
||||
|
||||
|
||||
func _set_cam_back() -> void:
|
||||
_cam.position = Vector3(0, 1.25, -1.4)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0, 1.15, 0), Vector3.UP)
|
||||
|
||||
|
||||
func _attach_garment(skel: Skeleton3D) -> void:
|
||||
# Mirror character_visual.gd:517-535 — reparent skinned meshes onto OUR
|
||||
# skeleton; matching bone names in the Skin resource drive them.
|
||||
var scene := load(GARMENT_GLB) as PackedScene
|
||||
if scene == null:
|
||||
push_error("QA: garment GLB failed to load: " + GARMENT_GLB)
|
||||
return
|
||||
var inst := scene.instantiate()
|
||||
var stack: Array[Node] = [inst]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
if n is MeshInstance3D and (n as MeshInstance3D).skin != null:
|
||||
var mi := n as MeshInstance3D
|
||||
mi.get_parent().remove_child(mi)
|
||||
mi.owner = null
|
||||
skel.add_child(mi)
|
||||
_garment_meshes.append(mi)
|
||||
inst.queue_free()
|
||||
print("QA: garment skinned meshes attached = ", _garment_meshes.size())
|
||||
for mi in _garment_meshes:
|
||||
print("QA: garment mesh '", mi.name, "' skin binds = ", mi.skin.get_bind_count())
|
||||
|
||||
|
||||
func _shot(fname: String) -> void:
|
||||
await RenderingServer.frame_post_draw
|
||||
var img := get_viewport().get_texture().get_image()
|
||||
img.save_png(SHOT_DIR + fname)
|
||||
print("QA: saved ", fname)
|
||||
|
||||
|
||||
func _wait_frames(n: int) -> void:
|
||||
for i in n:
|
||||
await get_tree().process_frame
|
||||
|
||||
|
||||
func _run_qa() -> void:
|
||||
await _wait_frames(5)
|
||||
|
||||
# report garment AABB sanity (explosion check)
|
||||
for mi in _garment_meshes:
|
||||
print("QA: garment local AABB = ", mi.get_aabb())
|
||||
|
||||
# --- idle ---
|
||||
await _shot("01_idle_front.png")
|
||||
_set_cam_side()
|
||||
await _shot("02_idle_side.png")
|
||||
_set_cam_back()
|
||||
await _shot("02b_idle_back.png")
|
||||
|
||||
# --- walk ---
|
||||
var ap := _cv.get_animation_player()
|
||||
var names: Array[String] = []
|
||||
if ap:
|
||||
for lib_name in ap.get_animation_library_list():
|
||||
var lib := ap.get_animation_library(lib_name)
|
||||
for a in lib.get_animation_list():
|
||||
names.append(str(lib_name) + "/" + str(a))
|
||||
print("QA: animations = ", names)
|
||||
|
||||
var walk := ""
|
||||
for n in names:
|
||||
if n.to_lower().contains("walk"):
|
||||
walk = n.get_slice("/", 1)
|
||||
if n.to_lower().ends_with("/walk"):
|
||||
break
|
||||
if walk == "":
|
||||
push_warning("QA: no walk animation found — staying on idle")
|
||||
else:
|
||||
print("QA: playing '", walk, "'")
|
||||
_cv.play_animation(walk)
|
||||
|
||||
# mid-stride frames at two phases, front + side
|
||||
await _wait_frames(24)
|
||||
_set_cam_front()
|
||||
await _shot("03_walk_front_a.png")
|
||||
_set_cam_side()
|
||||
await _shot("04_walk_side_a.png")
|
||||
await _wait_frames(14)
|
||||
await _shot("05_walk_side_b.png")
|
||||
_set_cam_front()
|
||||
await _shot("06_walk_front_b.png")
|
||||
_set_cam_back()
|
||||
await _wait_frames(10)
|
||||
await _shot("07_walk_back.png")
|
||||
|
||||
for mi in _garment_meshes:
|
||||
print("QA: post-walk garment local AABB = ", mi.get_aabb())
|
||||
|
||||
print("QA: DONE")
|
||||
get_tree().quit(0)
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://spike_synty_qa1"]
|
||||
|
||||
[ext_resource type="Script" path="res://spike_synty_qa/qa_scene.gd" id="1"]
|
||||
|
||||
[node name="QARoot" type="Node3D"]
|
||||
script = ExtResource("1")
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Quick recon: joined garment-adjacent body mesh AABB per body type."""
|
||||
import bpy
|
||||
import os
|
||||
|
||||
REPO = "/var/mnt/data/projects/settled-reach"
|
||||
BODIES_DIR = os.path.join(REPO, "client/assets/characters/bodies")
|
||||
BODY_TYPES = ["average_m", "average_f", "muscular_m", "muscular_f",
|
||||
"thin_m", "thin_f", "heavy_m", "heavy_f",
|
||||
"teen_m", "teen_f", "child"]
|
||||
SEGS = ["seg_torso", "seg_hips", "seg_neck",
|
||||
"seg_arm_upper_l", "seg_arm_upper_r",
|
||||
"seg_leg_upper_l", "seg_leg_upper_r"]
|
||||
|
||||
for body in BODY_TYPES:
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
xs, ys, zs = [], [], []
|
||||
t_xs, t_ys = [], []
|
||||
for seg in SEGS:
|
||||
path = os.path.join(BODIES_DIR, body, seg + ".glb")
|
||||
if not os.path.exists(path):
|
||||
continue
|
||||
before = set(bpy.data.objects)
|
||||
bpy.ops.import_scene.gltf(filepath=path)
|
||||
for o in set(bpy.data.objects) - before:
|
||||
if o.type == 'MESH' and len(o.vertex_groups) > 0:
|
||||
for v in o.data.vertices:
|
||||
w = o.matrix_world @ v.co
|
||||
xs.append(w.x)
|
||||
ys.append(w.y)
|
||||
zs.append(w.z)
|
||||
if seg == "seg_torso":
|
||||
t_xs.append(w.x)
|
||||
t_ys.append(w.y)
|
||||
print(f"AABB {body}: full x={max(xs)-min(xs):.3f} "
|
||||
f"y={max(ys)-min(ys):.3f} z_top={max(zs):.3f} "
|
||||
f"| torso x={max(t_xs)-min(t_xs):.3f} depth y={max(t_ys)-min(t_ys):.3f}")
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Quick recon: which bind skeleton do production per-body clothing GLBs embed?"""
|
||||
import bpy
|
||||
import os
|
||||
|
||||
REPO = "/var/mnt/data/projects/settled-reach"
|
||||
FILES = [
|
||||
"client/assets/characters/clothing/peasant_pants/teen_m.glb",
|
||||
"client/assets/characters/clothing/peasant_pants/average_f.glb",
|
||||
"client/assets/characters/clothing/peasant_pants/average_m.glb",
|
||||
]
|
||||
|
||||
for rel in FILES:
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
bpy.ops.import_scene.gltf(filepath=os.path.join(REPO, rel))
|
||||
for o in bpy.data.objects:
|
||||
if o.type == 'ARMATURE':
|
||||
for bn in ("pelvis", "upperarm_l"):
|
||||
b = o.data.bones.get(bn)
|
||||
if b:
|
||||
w = o.matrix_world @ b.head_local
|
||||
print(f"RECON {rel} {bn} "
|
||||
f"({w.x:.5f}, {w.y:.5f}, {w.z:.5f})")
|
||||
@@ -0,0 +1,42 @@
|
||||
"""Blender recon: dump skeleton/mesh facts for a Sidekick FBX and our armature.
|
||||
|
||||
Usage: tooling/blender --background --python recon_dump.py -- <fbx> <our_armature_glb>
|
||||
"""
|
||||
import sys
|
||||
|
||||
import bpy
|
||||
|
||||
argv = sys.argv[sys.argv.index("--") + 1:]
|
||||
fbx_path, glb_path = argv[0], argv[1]
|
||||
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
bpy.ops.import_scene.fbx(filepath=fbx_path)
|
||||
print("=== SIDEKICK FBX:", fbx_path.split("/")[-1])
|
||||
sk_bones = []
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == "ARMATURE":
|
||||
sk_bones = sorted(b.name for b in obj.data.bones)
|
||||
print("ARMATURE:", obj.name, "bones:", len(sk_bones))
|
||||
elif obj.type == "MESH":
|
||||
keys = obj.data.shape_keys
|
||||
print(
|
||||
"MESH:", obj.name,
|
||||
"verts:", len(obj.data.vertices),
|
||||
"dim:", [round(d, 3) for d in obj.dimensions],
|
||||
"shapekeys:", [k.name for k in keys.key_blocks] if keys else [],
|
||||
"vgroups:", len(obj.vertex_groups),
|
||||
)
|
||||
print("SK_BONES:", ",".join(sk_bones))
|
||||
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
bpy.ops.import_scene.gltf(filepath=glb_path)
|
||||
print("=== OURS:", glb_path.split("/")[-1])
|
||||
our_bones = []
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == "ARMATURE":
|
||||
our_bones = sorted(b.name for b in obj.data.bones)
|
||||
print("ARMATURE:", obj.name, "bones:", len(our_bones))
|
||||
print("OUR_BONES:", ",".join(our_bones))
|
||||
|
||||
overlap = set(sk_bones) & set(our_bones)
|
||||
print("OVERLAP:", len(overlap), "of", len(sk_bones), "sidekick /", len(our_bones), "ours")
|
||||
@@ -0,0 +1,11 @@
|
||||
"""Dump vertex-group names of every mesh in an FBX."""
|
||||
import sys
|
||||
|
||||
import bpy
|
||||
|
||||
fbx_path = sys.argv[sys.argv.index("--") + 1]
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
bpy.ops.import_scene.fbx(filepath=fbx_path)
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == "MESH":
|
||||
print("MESH:", obj.name, "VGROUPS:", ",".join(g.name for g in obj.vertex_groups))
|
||||
Reference in New Issue
Block a user