@hypersoniclabs/helix-cli 0.1.7 → 0.1.24-helix3.438
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +631 -8
- package/assets/bridge/metahuman-eye-baked.png +0 -0
- package/assets/item-def-sounds/Common/A_DryShot.ogg +0 -0
- package/assets/item-def-sounds/Common/A_LMG_CloseCoverA.ogg +0 -0
- package/assets/item-def-sounds/Common/A_LMG_CloseCoverB.ogg +0 -0
- package/assets/item-def-sounds/Common/A_LMG_InsertMag.ogg +0 -0
- package/assets/item-def-sounds/Common/A_LMG_RemoveMag.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Pistol_InsertMag_001.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Pistol_InsertMag_002.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Pistol_InsertMag_003.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Pistol_RemoveMag_001.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Pistol_RemoveMag_002.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Pistol_RemoveMag_003.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_ChargingHandle_001.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_ChargingHandle_002.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_ChargingHandle_003.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_InsertMag_001.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_InsertMag_002.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_InsertMag_003.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_RemoveMag_001.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_RemoveMag_002.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Rifle_RemoveMag_003.ogg +0 -0
- package/assets/item-def-sounds/Common/A_SMG_ChargingHandle_001.ogg +0 -0
- package/assets/item-def-sounds/Common/A_SMG_ChargingHandle_002.ogg +0 -0
- package/assets/item-def-sounds/Common/A_SMG_ChargingHandle_003.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Sniper_ChargingHandle.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Sniper_RemoveMag_001.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Sniper_RemoveMag_002.ogg +0 -0
- package/assets/item-def-sounds/Common/A_Sniper_RemoveMag_003.ogg +0 -0
- package/assets/item-def-sounds/Explosions/Explosions_Grenade_Punch-Close_01.ogg +0 -0
- package/assets/item-def-sounds/Explosions/Explosions_Grenade_Punch-Close_02.ogg +0 -0
- package/assets/item-def-sounds/Explosions/Explosions_Grenade_Punch-Close_03.ogg +0 -0
- package/assets/item-def-sounds/LMG/A_LWS-32_Shot_001.ogg +0 -0
- package/assets/item-def-sounds/LMG/A_LWS-32_Shot_002.ogg +0 -0
- package/assets/item-def-sounds/LMG/A_LWS-32_Shot_003.ogg +0 -0
- package/assets/item-def-sounds/Pistol/A_Gaston_Shot_001.ogg +0 -0
- package/assets/item-def-sounds/Pistol/A_Gaston_Shot_002.ogg +0 -0
- package/assets/item-def-sounds/Pistol/A_Gaston_Shot_003.ogg +0 -0
- package/assets/item-def-sounds/Rifle/A_KAL_Shot_001.ogg +0 -0
- package/assets/item-def-sounds/Rifle/A_KAL_Shot_002.ogg +0 -0
- package/assets/item-def-sounds/Rifle/A_KAL_Shot_003.ogg +0 -0
- package/assets/item-def-sounds/SMG/A_Bison_InsertMag.ogg +0 -0
- package/assets/item-def-sounds/SMG/A_Bison_RemoveMag.ogg +0 -0
- package/assets/item-def-sounds/SMG/A_Bison_Shot_001.ogg +0 -0
- package/assets/item-def-sounds/SMG/A_Bison_Shot_002.ogg +0 -0
- package/assets/item-def-sounds/SMG/A_Bison_Shot_003.ogg +0 -0
- package/assets/item-def-sounds/Shotgun/A_DB-12_Shot_001.ogg +0 -0
- package/assets/item-def-sounds/Shotgun/A_DB-12_Shot_002.ogg +0 -0
- package/assets/item-def-sounds/Shotgun/A_DB-12_Shot_003.ogg +0 -0
- package/assets/item-def-sounds/Sniper/A_CS-446_Shot_001.ogg +0 -0
- package/assets/item-def-sounds/Sniper/A_CS-446_Shot_002.ogg +0 -0
- package/assets/item-def-sounds/Sniper/A_CS-446_Shot_003.ogg +0 -0
- package/bridge-plugins/helix.bridge.unreal/bin/unreal-skeletal-mesh-extract.js +812 -0
- package/bridge-plugins/helix.bridge.unreal/bridge-plugin.json +40 -0
- package/bridge-plugins/helix.bridge.unreal/test/exporter.test.js +418 -0
- package/bridge-plugins/helix.bridge.unreal/test/handler.test.js +417 -0
- package/bridge-plugins/helix.bridge.unreal/unreal/export_skeletal_mesh.py +384 -0
- package/bridge-recipes/cyberpunk/cyberpunk-female-bone-map.json +476 -0
- package/bridge-recipes/sims4/README.md +43 -0
- package/bridge-recipes/sims4/sims4-female-bone-map.json +65 -0
- package/bridge-recipes/unreal/character-customizer/README.md +58 -0
- package/bridge-recipes/unreal/character-customizer/all.json +13 -0
- package/bridge-recipes/unreal/character-customizer/base-male-body.json +6 -0
- package/bridge-recipes/unreal/character-customizer/base-male-head.json +6 -0
- package/bridge-recipes/unreal/character-customizer/beard.addon.json +1 -0
- package/bridge-recipes/unreal/character-customizer/beard.json +6 -0
- package/bridge-recipes/unreal/character-customizer/bottom.addon.json +1 -0
- package/bridge-recipes/unreal/character-customizer/bottom.json +6 -0
- package/bridge-recipes/unreal/character-customizer/eyewear.addon.json +1 -0
- package/bridge-recipes/unreal/character-customizer/eyewear.json +6 -0
- package/bridge-recipes/unreal/character-customizer/footwear.addon.json +1 -0
- package/bridge-recipes/unreal/character-customizer/footwear.json +6 -0
- package/bridge-recipes/unreal/character-customizer/hair.addon.json +1 -0
- package/bridge-recipes/unreal/character-customizer/hair.json +6 -0
- package/bridge-recipes/unreal/character-customizer/test/recipes.test.js +74 -0
- package/bridge-recipes/unreal/character-customizer/top.addon.json +1 -0
- package/bridge-recipes/unreal/character-customizer/top.json +6 -0
- package/bridge-recipes/unreal/helix-character-creator-female/README.md +57 -0
- package/bridge-recipes/unreal/helix-character-creator-female/all.json +18 -0
- package/bridge-recipes/unreal/helix-character-creator-female/back.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/back.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/base-female-face.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/base-female.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/bottom.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/bottom.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/eyewear.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/eyewear.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-basic-t-shirt.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-basic-t-shirt.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-batch-hair.json +23 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-batch-nebula-urbanghost.json +7 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-batch-singles.json +19 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-batch5.json +19 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-batch6.json +19 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-batch7.json +11 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-cap-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-cap-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-cap.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-cap.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-cargo-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-cargo-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-carnival-mask-3.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-carnival-mask-3.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-chain.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-chain.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-clare-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-clare-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-clare-shoes.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-clare-shoes.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-clare-top.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-clare-top.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-compression-bodysuit-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-compression-bodysuit-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-compression-bodysuit.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-compression-bodysuit.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-eye-hole-mask-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-eye-hole-mask-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-eye-patch.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-eye-patch.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-firefight-outfit.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-firefight-outfit.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-fruity-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-fruity-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-fruity-sneakers.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-fruity-sneakers.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-fruity-top.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-fruity-top.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-gamer-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-gamer-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-guy-fawkes-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-guy-fawkes-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a1.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a1.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a10.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a10.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a11.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a11.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a12.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a12.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a13.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a13.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a14.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a14.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a15.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a15.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a16.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a16.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a17.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a17.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a18.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a18.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a19.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a19.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a20.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a20.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a21.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a21.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a3.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a3.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a4.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a4.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a5.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a5.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a6.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a6.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a7.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a7.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a8.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a8.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a9.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hair-a9.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-high-waist-tailored-trousers.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-high-waist-tailored-trousers.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hogwarts-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hogwarts-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hoody.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-hoody.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-jacket-6.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-jacket-6.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-joker-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-joker-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-kombes-brown.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-kombes-brown.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-kombes.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-kombes.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-marshmallow-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-marshmallow-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-maska.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-maska.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-nebula-executive-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-nebula-executive-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-nebula-executive-shoes.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-nebula-executive-shoes.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-ninza-costume.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-ninza-costume.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-orange-kombes.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-orange-kombes.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-panama.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-panama.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-panda-bag-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-panda-bag-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-pantslow.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-pantslow.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-paper-bag-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-paper-bag-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-pig-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-pig-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-police-uniform.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-police-uniform.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-prison-outfit.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-prison-outfit.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-short-b4low.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-short-b4low.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-short-swimming.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-short-swimming.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-shortlow.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-shortlow.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-shorts-b3.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-shorts-b3.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-sterile-gloves.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-sterile-gloves.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-surgeon-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-surgeon-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-swat.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-swat.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-top.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-top.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-topx1.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-topx1.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-urban-ghost-hoodie.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-urban-ghost-hoodie.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-urban-ghost-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-urban-ghost-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-urban-ghost-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-urban-ghost-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-warehouse-uniform.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-warehouse-uniform.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-wide-top.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-wide-top.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-wolf-set.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-wolf-set.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-woman-mask-3.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-woman-mask-3.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-women-out-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/female-women-out-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/footwear.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/footwear.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/gloves.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/gloves.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/hair.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/hair.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/headwear.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/headwear.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/neck.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/neck.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/outfit.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/outfit.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/top.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-female/top.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/README.md +15 -0
- package/bridge-recipes/unreal/helix-character-creator-male/all.json +18 -0
- package/bridge-recipes/unreal/helix-character-creator-male/back.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/back.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/base-male-face.json +6 -0
- package/bridge-recipes/unreal/helix-character-creator-male/base-male.json +6 -0
- package/bridge-recipes/unreal/helix-character-creator-male/bottom.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/bottom.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/eyewear.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/eyewear.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/footwear.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/footwear.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/gloves.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/gloves.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/hair.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/hair.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/headwear.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/headwear.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-batch-hair.json +23 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-batch-nebula-urbanghost.json +7 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-batch-uniforms.json +10 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-batch6-slot2.json +7 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-batch7-slot3.json +6 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-batch8-combined.json +11 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-boots-25.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-cargo-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-cargo-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-carnival-mask-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-carnival-mask-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-carnival-mask-3.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-carnival-mask-3.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-clare-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-clare-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-clare-shoes.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-clare-shoes.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-clare-top.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-clare-top.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-diamond-hands.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-diamond-hands.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-doctor-outfit.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-doctor-outfit.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-eye-hole-mask-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-eye-hole-mask-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-eye-hole-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-eye-hole-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-eye-patch.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-eye-patch.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-fb-cargo-pants-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-fb-cargo-pants-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-fb-cargo-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-fb-puffy.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-fb-puffy.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-firefight-outfit.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-firefight-outfit.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-gamer-mask-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-gamer-mask-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a1.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a1.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a10.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a10.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a11.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a11.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a12.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a12.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a13.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a13.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a14.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a14.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a15.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a15.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a16.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a16.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a17.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a17.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a18.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a18.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a19.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a19.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a20.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a20.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a21.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a21.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a3.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a3.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a4.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a4.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a5.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a5.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a6.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a6.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a7.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a7.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a8.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a8.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a9.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-hair-a9.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-kombes-brown-v2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-kombes-brown-v2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-kombes-brown.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-man-mask-3.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-marshmallow-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-marshmallow-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-mikejack-jacket.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-nebula-executive-coat.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-nebula-executive-coat.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-nebula-executive-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-nebula-executive-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-nebula-executive-shoes.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-nebula-executive-shoes.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-gloves.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-gloves.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-mantle.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-mantle.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-tunic.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-tunic.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-orangekombes.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-orangekombes.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-out2-boots.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-out2-boots.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-panda-bag-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-panda-bag-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-pants-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-pants-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-paper-bag-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-paper-bag-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-pig-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-pig-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-police-uniform.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-police-uniform.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-prison-outfit.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-prison-outfit.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-puffer-jacket.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-puffer-jacket.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-pullover-hoodie.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-pullover-hoodie.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-robber-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-robber-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-short-swimming.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-short-swimming.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-sinth-ascetic-jacket.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-sinth-ascetic-jacket.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-sip-cap.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-sip-cap.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-socks.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-socks.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-surgeon-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-surgeon-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-tech-hoodie.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-tech-hoodie.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-urban-ghost-hoodie.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-urban-ghost-hoodie.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-urban-ghost-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-urban-ghost-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-urban-ghost-pants.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-urban-ghost-pants.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-warehouse-uniform.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-warehouse-uniform.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/neck.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/neck.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/outfit.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/outfit.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/palette-pilot.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/test/recipes.test.js +122 -0
- package/bridge-recipes/unreal/helix-character-creator-male/top.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/top.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/variants/bottom-nopalette.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/variants/headwear-nopalette.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/variants/neck-nopalette.addon.json +1 -0
- package/dist/ability.d.ts +2 -2
- package/dist/ability.js +6 -5
- package/dist/ability.js.map +1 -1
- package/dist/achievement.d.ts +123 -0
- package/dist/achievement.js +400 -0
- package/dist/achievement.js.map +1 -0
- package/dist/api.d.ts +19 -1
- package/dist/api.js +80 -6
- package/dist/api.js.map +1 -1
- package/dist/assets.d.ts +595 -0
- package/dist/assets.js +1121 -0
- package/dist/assets.js.map +1 -0
- package/dist/audioCommand.d.ts +65 -0
- package/dist/audioCommand.js +181 -0
- package/dist/audioCommand.js.map +1 -0
- package/dist/bakeBodyRegionsCli.d.ts +19 -0
- package/dist/bakeBodyRegionsCli.js +25 -0
- package/dist/bakeBodyRegionsCli.js.map +1 -0
- package/dist/bakeLighting.d.ts +173 -0
- package/dist/bakeLighting.js +452 -0
- package/dist/bakeLighting.js.map +1 -0
- package/dist/bridge/bridge.testutil.d.ts +105 -0
- package/dist/bridge/bridge.testutil.js +259 -0
- package/dist/bridge/bridge.testutil.js.map +1 -0
- package/dist/bridge/characterAddon.d.ts +47 -0
- package/dist/bridge/characterAddon.js +146 -0
- package/dist/bridge/characterAddon.js.map +1 -0
- package/dist/bridge/detect.d.ts +4 -0
- package/dist/bridge/detect.js +154 -0
- package/dist/bridge/detect.js.map +1 -0
- package/dist/bridge/execute.d.ts +3 -0
- package/dist/bridge/execute.js +206 -0
- package/dist/bridge/execute.js.map +1 -0
- package/dist/bridge/glb.d.ts +60 -0
- package/dist/bridge/glb.js +81 -0
- package/dist/bridge/glb.js.map +1 -0
- package/dist/bridge/hash.d.ts +6 -0
- package/dist/bridge/hash.js +40 -0
- package/dist/bridge/hash.js.map +1 -0
- package/dist/bridge/index.d.ts +11 -0
- package/dist/bridge/index.js +28 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/bridge/internal/artifacts.d.ts +5 -0
- package/dist/bridge/internal/artifacts.js +31 -0
- package/dist/bridge/internal/artifacts.js.map +1 -0
- package/dist/bridge/internal/builtInExecution.d.ts +4 -0
- package/dist/bridge/internal/builtInExecution.js +514 -0
- package/dist/bridge/internal/builtInExecution.js.map +1 -0
- package/dist/bridge/internal/externalExecution.d.ts +12 -0
- package/dist/bridge/internal/externalExecution.js +261 -0
- package/dist/bridge/internal/externalExecution.js.map +1 -0
- package/dist/bridge/internal/outputReservation.d.ts +13 -0
- package/dist/bridge/internal/outputReservation.js +151 -0
- package/dist/bridge/internal/outputReservation.js.map +1 -0
- package/dist/bridge/internal/snapshots.d.ts +32 -0
- package/dist/bridge/internal/snapshots.js +562 -0
- package/dist/bridge/internal/snapshots.js.map +1 -0
- package/dist/bridge/management.d.ts +40 -0
- package/dist/bridge/management.js +333 -0
- package/dist/bridge/management.js.map +1 -0
- package/dist/bridge/manifest.d.ts +8 -0
- package/dist/bridge/manifest.js +192 -0
- package/dist/bridge/manifest.js.map +1 -0
- package/dist/bridge/metahuman.d.ts +26 -0
- package/dist/bridge/metahuman.js +332 -0
- package/dist/bridge/metahuman.js.map +1 -0
- package/dist/bridge/planner.d.ts +9 -0
- package/dist/bridge/planner.js +246 -0
- package/dist/bridge/planner.js.map +1 -0
- package/dist/bridge/plugins.d.ts +17 -0
- package/dist/bridge/plugins.js +307 -0
- package/dist/bridge/plugins.js.map +1 -0
- package/dist/bridge/resolution.d.ts +8 -0
- package/dist/bridge/resolution.js +55 -0
- package/dist/bridge/resolution.js.map +1 -0
- package/dist/bridge/types.d.ts +247 -0
- package/dist/bridge/types.js +60 -0
- package/dist/bridge/types.js.map +1 -0
- package/dist/bridge/validate.d.ts +12 -0
- package/dist/bridge/validate.js +370 -0
- package/dist/bridge/validate.js.map +1 -0
- package/dist/bridge/zipSource.d.ts +27 -0
- package/dist/bridge/zipSource.js +90 -0
- package/dist/bridge/zipSource.js.map +1 -0
- package/dist/browser/hostSlots.d.ts +57 -0
- package/dist/browser/hostSlots.js +371 -0
- package/dist/browser/hostSlots.js.map +1 -0
- package/dist/browser/launch.d.ts +25 -0
- package/dist/browser/launch.js +74 -0
- package/dist/browser/launch.js.map +1 -0
- package/dist/browser/nonSystemChrome.d.ts +15 -0
- package/dist/browser/nonSystemChrome.js +91 -0
- package/dist/browser/nonSystemChrome.js.map +1 -0
- package/dist/builds/index.d.ts +54 -0
- package/dist/builds/index.js +215 -0
- package/dist/builds/index.js.map +1 -0
- package/dist/bundle.d.ts +35 -2
- package/dist/bundle.js +437 -20
- package/dist/bundle.js.map +1 -1
- package/dist/canary/cli.d.ts +17 -0
- package/dist/canary/cli.js +95 -0
- package/dist/canary/cli.js.map +1 -0
- package/dist/canary/index.d.ts +4 -0
- package/dist/canary/index.js +21 -0
- package/dist/canary/index.js.map +1 -0
- package/dist/canary/inventory.d.ts +46 -0
- package/dist/canary/inventory.js +499 -0
- package/dist/canary/inventory.js.map +1 -0
- package/dist/canary/pacifica.d.ts +51 -0
- package/dist/canary/pacifica.js +247 -0
- package/dist/canary/pacifica.js.map +1 -0
- package/dist/canary/plan.d.ts +115 -0
- package/dist/canary/plan.js +479 -0
- package/dist/canary/plan.js.map +1 -0
- package/dist/character/addonPushOut.d.ts +66 -0
- package/dist/character/addonPushOut.js +211 -0
- package/dist/character/addonPushOut.js.map +1 -0
- package/dist/character/avatarPreflight.d.ts +92 -0
- package/dist/character/avatarPreflight.js +261 -0
- package/dist/character/avatarPreflight.js.map +1 -0
- package/dist/character/bakeBodyRegions.d.ts +80 -0
- package/dist/character/bakeBodyRegions.js +190 -0
- package/dist/character/bakeBodyRegions.js.map +1 -0
- package/dist/character/baseColorGuard.d.ts +62 -0
- package/dist/character/baseColorGuard.js +141 -0
- package/dist/character/baseColorGuard.js.map +1 -0
- package/dist/character/bodyGeometryGuard.d.ts +243 -0
- package/dist/character/bodyGeometryGuard.js +417 -0
- package/dist/character/bodyGeometryGuard.js.map +1 -0
- package/dist/character/bodyRegionHide.d.ts +57 -0
- package/dist/character/bodyRegionHide.js +97 -0
- package/dist/character/bodyRegionHide.js.map +1 -0
- package/dist/character/composeBaseMale.d.ts +25 -0
- package/dist/character/composeBaseMale.js +712 -0
- package/dist/character/composeBaseMale.js.map +1 -0
- package/dist/character/esm.js +3 -1
- package/dist/character/esm.js.map +1 -1
- package/dist/character/faceManifest.d.ts +53 -0
- package/dist/character/faceManifest.js +256 -0
- package/dist/character/faceManifest.js.map +1 -0
- package/dist/character/fbx.d.ts +70 -0
- package/dist/character/fbx.js +396 -0
- package/dist/character/fbx.js.map +1 -0
- package/dist/character/fitGarmentFile.d.ts +39 -0
- package/dist/character/fitGarmentFile.js +53 -0
- package/dist/character/fitGarmentFile.js.map +1 -0
- package/dist/character/helixPalette.d.ts +68 -0
- package/dist/character/helixPalette.js +183 -0
- package/dist/character/helixPalette.js.map +1 -0
- package/dist/character/hideCoverage.d.ts +135 -0
- package/dist/character/hideCoverage.js +450 -0
- package/dist/character/hideCoverage.js.map +1 -0
- package/dist/character/imageLimits.d.ts +3 -0
- package/dist/character/imageLimits.js +7 -0
- package/dist/character/imageLimits.js.map +1 -0
- package/dist/character/importCharacter.d.ts +188 -10
- package/dist/character/importCharacter.js +854 -76
- package/dist/character/importCharacter.js.map +1 -1
- package/dist/character/importTrust.d.ts +11 -0
- package/dist/character/importTrust.js +56 -0
- package/dist/character/importTrust.js.map +1 -0
- package/dist/character/ktx2Decode.d.ts +11 -0
- package/dist/character/ktx2Decode.js +87 -0
- package/dist/character/ktx2Decode.js.map +1 -0
- package/dist/character/mat4.d.ts +7 -0
- package/dist/character/mat4.js +25 -2
- package/dist/character/mat4.js.map +1 -1
- package/dist/character/meshyToHelixMap.d.ts +14 -0
- package/dist/character/meshyToHelixMap.js +155 -3
- package/dist/character/meshyToHelixMap.js.map +1 -1
- package/dist/character/metahumanToHelixMap.d.ts +2 -0
- package/dist/character/metahumanToHelixMap.js +34 -0
- package/dist/character/metahumanToHelixMap.js.map +1 -0
- package/dist/character/normalMapGuard.d.ts +59 -0
- package/dist/character/normalMapGuard.js +139 -0
- package/dist/character/normalMapGuard.js.map +1 -0
- package/dist/character/quat.js +2 -2
- package/dist/character/quat.js.map +1 -1
- package/dist/character/reframeSkeletonFile.d.ts +19 -0
- package/dist/character/reframeSkeletonFile.js +45 -0
- package/dist/character/reframeSkeletonFile.js.map +1 -0
- package/dist/character/repairLodSkeletonsFile.d.ts +25 -0
- package/dist/character/repairLodSkeletonsFile.js +49 -0
- package/dist/character/repairLodSkeletonsFile.js.map +1 -0
- package/dist/character/restFrameGuard.d.ts +52 -0
- package/dist/character/restFrameGuard.js +90 -0
- package/dist/character/restFrameGuard.js.map +1 -0
- package/dist/character/slotBudgets.d.ts +37 -0
- package/dist/character/slotBudgets.generated.d.ts +15 -0
- package/dist/character/slotBudgets.generated.js +110 -0
- package/dist/character/slotBudgets.generated.js.map +1 -0
- package/dist/character/slotBudgets.js +45 -0
- package/dist/character/slotBudgets.js.map +1 -0
- package/dist/character/stages/atlasMaterials.d.ts +53 -0
- package/dist/character/stages/atlasMaterials.js +246 -0
- package/dist/character/stages/atlasMaterials.js.map +1 -0
- package/dist/character/stages/bakeTexture.d.ts +28 -7
- package/dist/character/stages/bakeTexture.js +586 -108
- package/dist/character/stages/bakeTexture.js.map +1 -1
- package/dist/character/stages/conform.d.ts +54 -9
- package/dist/character/stages/conform.js +686 -113
- package/dist/character/stages/conform.js.map +1 -1
- package/dist/character/stages/earClearance.d.ts +105 -0
- package/dist/character/stages/earClearance.js +480 -0
- package/dist/character/stages/earClearance.js.map +1 -0
- package/dist/character/stages/faceRegion.d.ts +55 -0
- package/dist/character/stages/faceRegion.js +183 -0
- package/dist/character/stages/faceRegion.js.map +1 -0
- package/dist/character/stages/firstPersonHide.d.ts +42 -0
- package/dist/character/stages/firstPersonHide.js +140 -0
- package/dist/character/stages/firstPersonHide.js.map +1 -0
- package/dist/character/stages/joinPerMesh.d.ts +18 -0
- package/dist/character/stages/joinPerMesh.js +46 -0
- package/dist/character/stages/joinPerMesh.js.map +1 -0
- package/dist/character/stages/ktx2.d.ts +6 -4
- package/dist/character/stages/ktx2.js +81 -29
- package/dist/character/stages/ktx2.js.map +1 -1
- package/dist/character/stages/limbBridge.d.ts +114 -0
- package/dist/character/stages/limbBridge.js +1286 -0
- package/dist/character/stages/limbBridge.js.map +1 -0
- package/dist/character/stages/mergeLods.d.ts +19 -0
- package/dist/character/stages/mergeLods.js +58 -2
- package/dist/character/stages/mergeLods.js.map +1 -1
- package/dist/character/stages/poseAwareFit.d.ts +192 -0
- package/dist/character/stages/poseAwareFit.js +417 -0
- package/dist/character/stages/poseAwareFit.js.map +1 -0
- package/dist/character/stages/poseFit/math.d.ts +141 -0
- package/dist/character/stages/poseFit/math.js +514 -0
- package/dist/character/stages/poseFit/math.js.map +1 -0
- package/dist/character/stages/poseFit/measure.d.ts +88 -0
- package/dist/character/stages/poseFit/measure.js +148 -0
- package/dist/character/stages/poseFit/measure.js.map +1 -0
- package/dist/character/stages/poseFit/model.d.ts +46 -0
- package/dist/character/stages/poseFit/model.js +147 -0
- package/dist/character/stages/poseFit/model.js.map +1 -0
- package/dist/character/stages/poseFit/offsetField.d.ts +76 -0
- package/dist/character/stages/poseFit/offsetField.js +249 -0
- package/dist/character/stages/poseFit/offsetField.js.map +1 -0
- package/dist/character/stages/poseFit/posing.d.ts +52 -0
- package/dist/character/stages/poseFit/posing.js +330 -0
- package/dist/character/stages/poseFit/posing.js.map +1 -0
- package/dist/character/stages/poseFit/testRig.d.ts +31 -0
- package/dist/character/stages/poseFit/testRig.js +85 -0
- package/dist/character/stages/poseFit/testRig.js.map +1 -0
- package/dist/character/stages/reframeSkeletonToHost.d.ts +35 -0
- package/dist/character/stages/reframeSkeletonToHost.js +111 -0
- package/dist/character/stages/reframeSkeletonToHost.js.map +1 -0
- package/dist/character/stages/report.js +0 -2
- package/dist/character/stages/report.js.map +1 -1
- package/dist/character/stages/resizeTextures.js +4 -4
- package/dist/character/stages/resizeTextures.js.map +1 -1
- package/dist/character/stages/retarget.d.ts +26 -2
- package/dist/character/stages/retarget.js +52 -4
- package/dist/character/stages/retarget.js.map +1 -1
- package/dist/character/stages/retargetClip.d.ts +6 -2
- package/dist/character/stages/retargetClip.js +109 -17
- package/dist/character/stages/retargetClip.js.map +1 -1
- package/dist/character/stages/sanitizeMaterials.d.ts +39 -0
- package/dist/character/stages/sanitizeMaterials.js +136 -0
- package/dist/character/stages/sanitizeMaterials.js.map +1 -0
- package/dist/character/stages/simplifyMesh.js +6 -3
- package/dist/character/stages/simplifyMesh.js.map +1 -1
- package/dist/character/stages/splitFacePrimitive.js +0 -2
- package/dist/character/stages/splitFacePrimitive.js.map +1 -1
- package/dist/character/stages/splitSharedLodSkeletons.d.ts +31 -0
- package/dist/character/stages/splitSharedLodSkeletons.js +104 -0
- package/dist/character/stages/splitSharedLodSkeletons.js.map +1 -0
- package/dist/character/stages/unifyMaterial.d.ts +66 -0
- package/dist/character/stages/unifyMaterial.js +136 -0
- package/dist/character/stages/unifyMaterial.js.map +1 -0
- package/dist/character/stages/vrmDynamics.d.ts +144 -0
- package/dist/character/stages/vrmDynamics.js +421 -0
- package/dist/character/stages/vrmDynamics.js.map +1 -0
- package/dist/character/stages/vrmFace.d.ts +82 -0
- package/dist/character/stages/vrmFace.js +280 -0
- package/dist/character/stages/vrmFace.js.map +1 -0
- package/dist/character/stages/weldedSimplify.d.ts +15 -1
- package/dist/character/stages/weldedSimplify.js +90 -9
- package/dist/character/stages/weldedSimplify.js.map +1 -1
- package/dist/character/testutil/meshyBridgeFixture.d.ts +33 -0
- package/dist/character/testutil/meshyBridgeFixture.js +219 -0
- package/dist/character/testutil/meshyBridgeFixture.js.map +1 -0
- package/dist/character/testutil/meshyHead.d.ts +19 -0
- package/dist/character/testutil/meshyHead.js +99 -0
- package/dist/character/testutil/meshyHead.js.map +1 -0
- package/dist/character/testutil/meshyRig.d.ts +17 -0
- package/dist/character/testutil/meshyRig.js +117 -0
- package/dist/character/testutil/meshyRig.js.map +1 -0
- package/dist/character/testutil/slotBudgetExpectations.d.ts +11 -0
- package/dist/character/testutil/slotBudgetExpectations.js +21 -0
- package/dist/character/testutil/slotBudgetExpectations.js.map +1 -0
- package/dist/character/testutil/syntheticRig.d.ts +12 -1
- package/dist/character/testutil/syntheticRig.js +88 -3
- package/dist/character/testutil/syntheticRig.js.map +1 -1
- package/dist/character/unrealMaterialSidecar.d.ts +94 -0
- package/dist/character/unrealMaterialSidecar.js +879 -0
- package/dist/character/unrealMaterialSidecar.js.map +1 -0
- package/dist/character/untrustedSource.d.ts +54 -0
- package/dist/character/untrustedSource.js +181 -0
- package/dist/character/untrustedSource.js.map +1 -0
- package/dist/character/vrmCharacter.d.ts +156 -0
- package/dist/character/vrmCharacter.js +678 -0
- package/dist/character/vrmCharacter.js.map +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +72 -24
- package/dist/config.js.map +1 -1
- package/dist/continuum/adminPackagePinCommand.d.ts +14 -0
- package/dist/continuum/adminPackagePinCommand.js +46 -0
- package/dist/continuum/adminPackagePinCommand.js.map +1 -0
- package/dist/continuum/adminPackagePublishCommand.d.ts +13 -0
- package/dist/continuum/adminPackagePublishCommand.js +22 -0
- package/dist/continuum/adminPackagePublishCommand.js.map +1 -0
- package/dist/continuum/authoring.d.ts +52 -0
- package/dist/continuum/authoring.js +143 -0
- package/dist/continuum/authoring.js.map +1 -0
- package/dist/continuum/characterMigrationGenerator.d.ts +17 -0
- package/dist/continuum/characterMigrationGenerator.js +242 -0
- package/dist/continuum/characterMigrationGenerator.js.map +1 -0
- package/dist/continuum/cli.d.ts +69 -0
- package/dist/continuum/cli.js +145 -0
- package/dist/continuum/cli.js.map +1 -0
- package/dist/continuum/cliAuthority.d.ts +17 -0
- package/dist/continuum/cliAuthority.js +28 -0
- package/dist/continuum/cliAuthority.js.map +1 -0
- package/dist/continuum/client.d.ts +103 -0
- package/dist/continuum/client.js +190 -0
- package/dist/continuum/client.js.map +1 -0
- package/dist/continuum/homeScenePackage.d.ts +159 -0
- package/dist/continuum/homeScenePackage.js +267 -0
- package/dist/continuum/homeScenePackage.js.map +1 -0
- package/dist/continuum/homeScenePackageCommand.d.ts +44 -0
- package/dist/continuum/homeScenePackageCommand.js +107 -0
- package/dist/continuum/homeScenePackageCommand.js.map +1 -0
- package/dist/continuum/identity.d.ts +4 -0
- package/dist/continuum/identity.js +20 -0
- package/dist/continuum/identity.js.map +1 -0
- package/dist/continuum/index.d.ts +11 -0
- package/dist/continuum/index.js +28 -0
- package/dist/continuum/index.js.map +1 -0
- package/dist/continuum/missingNormals.d.ts +48 -0
- package/dist/continuum/missingNormals.js +99 -0
- package/dist/continuum/missingNormals.js.map +1 -0
- package/dist/continuum/objectUpload.d.ts +64 -0
- package/dist/continuum/objectUpload.js +375 -0
- package/dist/continuum/objectUpload.js.map +1 -0
- package/dist/continuum/pull.d.ts +22 -0
- package/dist/continuum/pull.js +47 -0
- package/dist/continuum/pull.js.map +1 -0
- package/dist/continuum/sourceBundle.d.ts +18 -0
- package/dist/continuum/sourceBundle.js +160 -0
- package/dist/continuum/sourceBundle.js.map +1 -0
- package/dist/continuum/state.d.ts +41 -0
- package/dist/continuum/state.js +198 -0
- package/dist/continuum/state.js.map +1 -0
- package/dist/continuum/types.d.ts +156 -0
- package/dist/continuum/types.js +6 -0
- package/dist/continuum/types.js.map +1 -0
- package/dist/continuum/workflow.d.ts +62 -0
- package/dist/continuum/workflow.js +592 -0
- package/dist/continuum/workflow.js.map +1 -0
- package/dist/cs2/areas.d.ts +41 -0
- package/dist/cs2/areas.js +82 -0
- package/dist/cs2/areas.js.map +1 -0
- package/dist/cs2/binary.d.ts +10 -0
- package/dist/cs2/binary.js +59 -0
- package/dist/cs2/binary.js.map +1 -0
- package/dist/cs2/cityLegacy.d.ts +29 -0
- package/dist/cs2/cityLegacy.js +48 -0
- package/dist/cs2/cityLegacy.js.map +1 -0
- package/dist/cs2/convert/areas.d.ts +32 -0
- package/dist/cs2/convert/areas.js +96 -0
- package/dist/cs2/convert/areas.js.map +1 -0
- package/dist/cs2/convert/glb.d.ts +48 -0
- package/dist/cs2/convert/glb.js +178 -0
- package/dist/cs2/convert/glb.js.map +1 -0
- package/dist/cs2/convert/markings.d.ts +96 -0
- package/dist/cs2/convert/markings.js +329 -0
- package/dist/cs2/convert/markings.js.map +1 -0
- package/dist/cs2/convert/nodeSeams.d.ts +20 -0
- package/dist/cs2/convert/nodeSeams.js +164 -0
- package/dist/cs2/convert/nodeSeams.js.map +1 -0
- package/dist/cs2/convert/roadBake.d.ts +112 -0
- package/dist/cs2/convert/roadBake.js +290 -0
- package/dist/cs2/convert/roadBake.js.map +1 -0
- package/dist/cs2/convert/roads.d.ts +110 -0
- package/dist/cs2/convert/roads.js +419 -0
- package/dist/cs2/convert/roads.js.map +1 -0
- package/dist/cs2/convert/terrain.d.ts +67 -0
- package/dist/cs2/convert/terrain.js +141 -0
- package/dist/cs2/convert/terrain.js.map +1 -0
- package/dist/cs2/convert/terrainConform.d.ts +58 -0
- package/dist/cs2/convert/terrainConform.js +284 -0
- package/dist/cs2/convert/terrainConform.js.map +1 -0
- package/dist/cs2/convert/treeRoadClearance.d.ts +17 -0
- package/dist/cs2/convert/treeRoadClearance.js +78 -0
- package/dist/cs2/convert/treeRoadClearance.js.map +1 -0
- package/dist/cs2/convert/trees.d.ts +33 -0
- package/dist/cs2/convert/trees.js +68 -0
- package/dist/cs2/convert/trees.js.map +1 -0
- package/dist/cs2/csm2.d.ts +58 -0
- package/dist/cs2/csm2.js +188 -0
- package/dist/cs2/csm2.js.map +1 -0
- package/dist/cs2/fixtures.d.ts +5 -0
- package/dist/cs2/fixtures.js +15 -0
- package/dist/cs2/fixtures.js.map +1 -0
- package/dist/cs2/impostors.d.ts +31 -0
- package/dist/cs2/impostors.js +42 -0
- package/dist/cs2/impostors.js.map +1 -0
- package/dist/cs2/index.d.ts +22 -0
- package/dist/cs2/index.js +191 -0
- package/dist/cs2/index.js.map +1 -0
- package/dist/cs2/inspect.d.ts +81 -0
- package/dist/cs2/inspect.js +181 -0
- package/dist/cs2/inspect.js.map +1 -0
- package/dist/cs2/lods.d.ts +22 -0
- package/dist/cs2/lods.js +136 -0
- package/dist/cs2/lods.js.map +1 -0
- package/dist/cs2/materials.d.ts +43 -0
- package/dist/cs2/materials.js +137 -0
- package/dist/cs2/materials.js.map +1 -0
- package/dist/cs2/placements.d.ts +57 -0
- package/dist/cs2/placements.js +116 -0
- package/dist/cs2/placements.js.map +1 -0
- package/dist/cs2/roadPieces.d.ts +79 -0
- package/dist/cs2/roadPieces.js +263 -0
- package/dist/cs2/roadPieces.js.map +1 -0
- package/dist/cs2/roads.d.ts +92 -0
- package/dist/cs2/roads.js +329 -0
- package/dist/cs2/roads.js.map +1 -0
- package/dist/cs2/terrain.d.ts +37 -0
- package/dist/cs2/terrain.js +91 -0
- package/dist/cs2/terrain.js.map +1 -0
- package/dist/cs2/types.d.ts +444 -0
- package/dist/cs2/types.js +22 -0
- package/dist/cs2/types.js.map +1 -0
- package/dist/cs2/water.d.ts +26 -0
- package/dist/cs2/water.js +58 -0
- package/dist/cs2/water.js.map +1 -0
- package/dist/currency.d.ts +94 -0
- package/dist/currency.js +425 -0
- package/dist/currency.js.map +1 -0
- package/dist/dev/devServer.d.ts +53 -0
- package/dist/dev/devServer.js +228 -0
- package/dist/dev/devServer.js.map +1 -0
- package/dist/dev/shellPage.d.ts +3 -0
- package/dist/dev/shellPage.js +297 -0
- package/dist/dev/shellPage.js.map +1 -0
- package/dist/dev/state.d.ts +84 -0
- package/dist/dev/state.js +265 -0
- package/dist/dev/state.js.map +1 -0
- package/dist/device.d.ts +104 -0
- package/dist/device.js +2744 -0
- package/dist/device.js.map +1 -0
- package/dist/deviceActionValidation.d.ts +21 -0
- package/dist/deviceActionValidation.js +460 -0
- package/dist/deviceActionValidation.js.map +1 -0
- package/dist/distribution.d.ts +69 -0
- package/dist/distribution.js +138 -0
- package/dist/distribution.js.map +1 -0
- package/dist/enginePins.d.ts +45 -0
- package/dist/enginePins.js +134 -0
- package/dist/enginePins.js.map +1 -0
- package/dist/gesture/axisReference.d.ts +22 -0
- package/dist/gesture/axisReference.js +32 -0
- package/dist/gesture/axisReference.js.map +1 -0
- package/dist/gesture/capture.d.ts +84 -0
- package/dist/gesture/capture.js +303 -0
- package/dist/gesture/capture.js.map +1 -0
- package/dist/gesture/harnessPage.d.ts +23 -0
- package/dist/gesture/harnessPage.js +153 -0
- package/dist/gesture/harnessPage.js.map +1 -0
- package/dist/gesture/inspect.d.ts +189 -0
- package/dist/gesture/inspect.js +678 -0
- package/dist/gesture/inspect.js.map +1 -0
- package/dist/gesture/rig.d.ts +12 -0
- package/dist/gesture/rig.js +111 -0
- package/dist/gesture/rig.js.map +1 -0
- package/dist/gesture/worldRuntime.d.ts +65 -0
- package/dist/gesture/worldRuntime.js +110 -0
- package/dist/gesture/worldRuntime.js.map +1 -0
- package/dist/glb/draw-calls.d.ts +60 -0
- package/dist/glb/draw-calls.js +160 -0
- package/dist/glb/draw-calls.js.map +1 -0
- package/dist/glb/glb-summary.d.ts +68 -0
- package/dist/glb/glb-summary.js +450 -0
- package/dist/glb/glb-summary.js.map +1 -0
- package/dist/glb/index.d.ts +12 -0
- package/dist/glb/index.js +24 -0
- package/dist/glb/index.js.map +1 -0
- package/dist/glb/smooth-normals.d.ts +98 -0
- package/dist/glb/smooth-normals.js +395 -0
- package/dist/glb/smooth-normals.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6786 -128
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +35 -1
- package/dist/init.js +1115 -47
- package/dist/init.js.map +1 -1
- package/dist/install.d.ts +16 -5
- package/dist/install.js +123 -23
- package/dist/install.js.map +1 -1
- package/dist/interactable.d.ts +154 -0
- package/dist/interactable.js +1219 -0
- package/dist/interactable.js.map +1 -0
- package/dist/item.d.ts +217 -0
- package/dist/item.js +892 -0
- package/dist/item.js.map +1 -0
- package/dist/itemDef/distribute.d.ts +17 -0
- package/dist/itemDef/distribute.js +29 -0
- package/dist/itemDef/distribute.js.map +1 -0
- package/dist/itemDef/emoteRules.d.ts +85 -0
- package/dist/itemDef/emoteRules.js +368 -0
- package/dist/itemDef/emoteRules.js.map +1 -0
- package/dist/itemDef/gunConfigRules.d.ts +24 -0
- package/dist/itemDef/gunConfigRules.js +300 -0
- package/dist/itemDef/gunConfigRules.js.map +1 -0
- package/dist/itemDef/inspect.d.ts +19 -0
- package/dist/itemDef/inspect.js +144 -0
- package/dist/itemDef/inspect.js.map +1 -0
- package/dist/itemDef/scaffold.d.ts +41 -0
- package/dist/itemDef/scaffold.js +303 -0
- package/dist/itemDef/scaffold.js.map +1 -0
- package/dist/itemDef/tables.d.ts +81 -0
- package/dist/itemDef/tables.js +226 -0
- package/dist/itemDef/tables.js.map +1 -0
- package/dist/itemDef/validate.d.ts +30 -0
- package/dist/itemDef/validate.js +175 -0
- package/dist/itemDef/validate.js.map +1 -0
- package/dist/itemPublishVersion.d.ts +46 -0
- package/dist/itemPublishVersion.js +242 -0
- package/dist/itemPublishVersion.js.map +1 -0
- package/dist/itemSearch.d.ts +128 -0
- package/dist/itemSearch.js +140 -0
- package/dist/itemSearch.js.map +1 -0
- package/dist/itemSetVersion.d.ts +77 -0
- package/dist/itemSetVersion.js +170 -0
- package/dist/itemSetVersion.js.map +1 -0
- package/dist/itemThumbnail.d.ts +40 -0
- package/dist/itemThumbnail.js +116 -0
- package/dist/itemThumbnail.js.map +1 -0
- package/dist/lib.d.ts +571 -7
- package/dist/lib.js +1178 -15
- package/dist/lib.js.map +1 -1
- package/dist/looks.d.ts +103 -0
- package/dist/looks.js +102 -0
- package/dist/looks.js.map +1 -0
- package/dist/materials/availability.d.ts +8 -0
- package/dist/materials/availability.js +22 -0
- package/dist/materials/availability.js.map +1 -0
- package/dist/materials/identity.d.ts +30 -0
- package/dist/materials/identity.js +68 -0
- package/dist/materials/identity.js.map +1 -0
- package/dist/materials/pins.d.ts +23 -0
- package/dist/materials/pins.js +93 -0
- package/dist/materials/pins.js.map +1 -0
- package/dist/mesh/surfaceBvh.d.ts +13 -0
- package/dist/mesh/surfaceBvh.js +197 -0
- package/dist/mesh/surfaceBvh.js.map +1 -0
- package/dist/module-validation.d.ts +2 -0
- package/dist/module-validation.js +24 -0
- package/dist/module-validation.js.map +1 -0
- package/dist/money.d.ts +183 -0
- package/dist/money.js +299 -0
- package/dist/money.js.map +1 -0
- package/dist/npc/capture.d.ts +41 -0
- package/dist/npc/capture.js +99 -0
- package/dist/npc/capture.js.map +1 -0
- package/dist/npc/format.d.ts +2 -0
- package/dist/npc/format.js +102 -0
- package/dist/npc/format.js.map +1 -0
- package/dist/npc/inspect.d.ts +23 -0
- package/dist/npc/inspect.js +54 -0
- package/dist/npc/inspect.js.map +1 -0
- package/dist/npc/metrics.d.ts +132 -0
- package/dist/npc/metrics.js +214 -0
- package/dist/npc/metrics.js.map +1 -0
- package/dist/npc/session.d.ts +35 -0
- package/dist/npc/session.js +172 -0
- package/dist/npc/session.js.map +1 -0
- package/dist/pack/locomotionReference.d.ts +40 -0
- package/dist/pack/locomotionReference.js +132 -0
- package/dist/pack/locomotionReference.js.map +1 -0
- package/dist/pack/scaffold.d.ts +15 -0
- package/dist/pack/scaffold.js +144 -0
- package/dist/pack/scaffold.js.map +1 -0
- package/dist/pack/validate.d.ts +67 -0
- package/dist/pack/validate.js +317 -0
- package/dist/pack/validate.js.map +1 -0
- package/dist/package.d.ts +14 -1
- package/dist/package.js +106 -7
- package/dist/package.js.map +1 -1
- package/dist/paletteShowcase.d.ts +183 -0
- package/dist/paletteShowcase.js +454 -0
- package/dist/paletteShowcase.js.map +1 -0
- package/dist/paletteShowcaseProfiles.d.ts +112 -0
- package/dist/paletteShowcaseProfiles.js +121 -0
- package/dist/paletteShowcaseProfiles.js.map +1 -0
- package/dist/paths.d.ts +4 -0
- package/dist/paths.js +11 -0
- package/dist/paths.js.map +1 -0
- package/dist/placementRequest.d.ts +37 -0
- package/dist/placementRequest.js +220 -0
- package/dist/placementRequest.js.map +1 -0
- package/dist/placements/assets.d.ts +7 -0
- package/dist/placements/assets.js +114 -0
- package/dist/placements/assets.js.map +1 -0
- package/dist/placements/attachFoliageLods.d.ts +4 -0
- package/dist/placements/attachFoliageLods.js +70 -0
- package/dist/placements/attachFoliageLods.js.map +1 -0
- package/dist/placements/attachImpostors.d.ts +3 -0
- package/dist/placements/attachImpostors.js +51 -0
- package/dist/placements/attachImpostors.js.map +1 -0
- package/dist/placements/authored.d.ts +11 -0
- package/dist/placements/authored.js +65 -0
- package/dist/placements/authored.js.map +1 -0
- package/dist/placements/bake.d.ts +2 -0
- package/dist/placements/bake.js +235 -0
- package/dist/placements/bake.js.map +1 -0
- package/dist/placements/captureHost.d.ts +4 -0
- package/dist/placements/captureHost.js +125 -0
- package/dist/placements/captureHost.js.map +1 -0
- package/dist/placements/foliageCapture.d.ts +2 -0
- package/dist/placements/foliageCapture.js +98 -0
- package/dist/placements/foliageCapture.js.map +1 -0
- package/dist/placements/foliageCards.d.ts +17 -0
- package/dist/placements/foliageCards.js +120 -0
- package/dist/placements/foliageCards.js.map +1 -0
- package/dist/placements/foliageLods.d.ts +8 -0
- package/dist/placements/foliageLods.js +240 -0
- package/dist/placements/foliageLods.js.map +1 -0
- package/dist/placements/foliageTexture.d.ts +4 -0
- package/dist/placements/foliageTexture.js +91 -0
- package/dist/placements/foliageTexture.js.map +1 -0
- package/dist/placements/impostorCapture.d.ts +2 -0
- package/dist/placements/impostorCapture.js +111 -0
- package/dist/placements/impostorCapture.js.map +1 -0
- package/dist/placements/impostors.d.ts +18 -0
- package/dist/placements/impostors.js +279 -0
- package/dist/placements/impostors.js.map +1 -0
- package/dist/placements/source.d.ts +53 -0
- package/dist/placements/source.js +111 -0
- package/dist/placements/source.js.map +1 -0
- package/dist/product.d.ts +84 -0
- package/dist/product.js +285 -0
- package/dist/product.js.map +1 -0
- package/dist/publications.d.ts +116 -0
- package/dist/publications.js +180 -0
- package/dist/publications.js.map +1 -0
- package/dist/publishDuplicateGuard.d.ts +92 -0
- package/dist/publishDuplicateGuard.js +128 -0
- package/dist/publishDuplicateGuard.js.map +1 -0
- package/dist/realizability.d.ts +36 -0
- package/dist/realizability.js +269 -0
- package/dist/realizability.js.map +1 -0
- package/dist/resolve.d.ts +7 -1
- package/dist/resolve.js +77 -11
- package/dist/resolve.js.map +1 -1
- package/dist/sceneElements/compiler.d.ts +21 -0
- package/dist/sceneElements/compiler.js +976 -0
- package/dist/sceneElements/compiler.js.map +1 -0
- package/dist/sceneElements/continuum.d.ts +54 -0
- package/dist/sceneElements/continuum.js +451 -0
- package/dist/sceneElements/continuum.js.map +1 -0
- package/dist/sceneElements/devices.d.ts +12 -0
- package/dist/sceneElements/devices.js +212 -0
- package/dist/sceneElements/devices.js.map +1 -0
- package/dist/sceneElements/meshCells.d.ts +49 -0
- package/dist/sceneElements/meshCells.js +174 -0
- package/dist/sceneElements/meshCells.js.map +1 -0
- package/dist/sceneElements/types.d.ts +261 -0
- package/dist/sceneElements/types.js +3 -0
- package/dist/sceneElements/types.js.map +1 -0
- package/dist/sceneHlod/approximate.d.ts +23 -0
- package/dist/sceneHlod/approximate.js +171 -0
- package/dist/sceneHlod/approximate.js.map +1 -0
- package/dist/sceneHlod/artifact.d.ts +3 -0
- package/dist/sceneHlod/artifact.js +29 -0
- package/dist/sceneHlod/artifact.js.map +1 -0
- package/dist/sceneHlod/bakeSurface.d.ts +2 -0
- package/dist/sceneHlod/bakeSurface.js +59 -0
- package/dist/sceneHlod/bakeSurface.js.map +1 -0
- package/dist/sceneHlod/basisEncoder.d.ts +3 -0
- package/dist/sceneHlod/basisEncoder.js +68 -0
- package/dist/sceneHlod/basisEncoder.js.map +1 -0
- package/dist/sceneHlod/cellArtifact.d.ts +17 -0
- package/dist/sceneHlod/cellArtifact.js +78 -0
- package/dist/sceneHlod/cellArtifact.js.map +1 -0
- package/dist/sceneHlod/commands.d.ts +2 -0
- package/dist/sceneHlod/commands.js +114 -0
- package/dist/sceneHlod/commands.js.map +1 -0
- package/dist/sceneHlod/complete.d.ts +78 -0
- package/dist/sceneHlod/complete.js +261 -0
- package/dist/sceneHlod/complete.js.map +1 -0
- package/dist/sceneHlod/completePrototype.d.ts +39 -0
- package/dist/sceneHlod/completePrototype.js +121 -0
- package/dist/sceneHlod/completePrototype.js.map +1 -0
- package/dist/sceneHlod/geometry.d.ts +6 -0
- package/dist/sceneHlod/geometry.js +101 -0
- package/dist/sceneHlod/geometry.js.map +1 -0
- package/dist/sceneHlod/imageSampling.d.ts +11 -0
- package/dist/sceneHlod/imageSampling.js +100 -0
- package/dist/sceneHlod/imageSampling.js.map +1 -0
- package/dist/sceneHlod/refinement.d.ts +46 -0
- package/dist/sceneHlod/refinement.js +204 -0
- package/dist/sceneHlod/refinement.js.map +1 -0
- package/dist/sceneHlod/scene.d.ts +80 -0
- package/dist/sceneHlod/scene.js +264 -0
- package/dist/sceneHlod/scene.js.map +1 -0
- package/dist/sceneHlod/sharedAtlas.d.ts +37 -0
- package/dist/sceneHlod/sharedAtlas.js +84 -0
- package/dist/sceneHlod/sharedAtlas.js.map +1 -0
- package/dist/sceneHlod/source.d.ts +17 -0
- package/dist/sceneHlod/source.js +241 -0
- package/dist/sceneHlod/source.js.map +1 -0
- package/dist/sceneHlod/textureCodec.d.ts +4 -0
- package/dist/sceneHlod/textureCodec.js +66 -0
- package/dist/sceneHlod/textureCodec.js.map +1 -0
- package/dist/sceneHlod/transfer.d.ts +8 -0
- package/dist/sceneHlod/transfer.js +147 -0
- package/dist/sceneHlod/transfer.js.map +1 -0
- package/dist/sceneHlod/types.d.ts +69 -0
- package/dist/sceneHlod/types.js +3 -0
- package/dist/sceneHlod/types.js.map +1 -0
- package/dist/sceneImport/archive.d.ts +30 -0
- package/dist/sceneImport/archive.js +336 -0
- package/dist/sceneImport/archive.js.map +1 -0
- package/dist/sceneImport/bistroMaterialBake.d.ts +20 -0
- package/dist/sceneImport/bistroMaterialBake.js +169 -0
- package/dist/sceneImport/bistroMaterialBake.js.map +1 -0
- package/dist/sceneImport/bistroTexturePlan.d.ts +6 -0
- package/dist/sceneImport/bistroTexturePlan.js +23 -0
- package/dist/sceneImport/bistroTexturePlan.js.map +1 -0
- package/dist/sceneImport/compiler.d.ts +36 -0
- package/dist/sceneImport/compiler.js +251 -0
- package/dist/sceneImport/compiler.js.map +1 -0
- package/dist/sceneImport/composeMounts.d.ts +159 -0
- package/dist/sceneImport/composeMounts.js +687 -0
- package/dist/sceneImport/composeMounts.js.map +1 -0
- package/dist/sceneImport/defaultMaterials.d.ts +45 -0
- package/dist/sceneImport/defaultMaterials.js +121 -0
- package/dist/sceneImport/defaultMaterials.js.map +1 -0
- package/dist/sceneImport/externalTextures.d.ts +44 -0
- package/dist/sceneImport/externalTextures.js +146 -0
- package/dist/sceneImport/externalTextures.js.map +1 -0
- package/dist/sceneImport/fbx.d.ts +101 -0
- package/dist/sceneImport/fbx.js +307 -0
- package/dist/sceneImport/fbx.js.map +1 -0
- package/dist/sceneImport/glbSplit.d.ts +24 -0
- package/dist/sceneImport/glbSplit.js +95 -0
- package/dist/sceneImport/glbSplit.js.map +1 -0
- package/dist/sceneImport/gltfIo.d.ts +32 -0
- package/dist/sceneImport/gltfIo.js +39 -0
- package/dist/sceneImport/gltfIo.js.map +1 -0
- package/dist/sceneImport/helsinkiTiles.d.ts +31 -0
- package/dist/sceneImport/helsinkiTiles.js +52 -0
- package/dist/sceneImport/helsinkiTiles.js.map +1 -0
- package/dist/sceneImport/materials/binding.d.ts +16 -0
- package/dist/sceneImport/materials/binding.js +42 -0
- package/dist/sceneImport/materials/binding.js.map +1 -0
- package/dist/sceneImport/materials/catalog.d.ts +36 -0
- package/dist/sceneImport/materials/catalog.js +233 -0
- package/dist/sceneImport/materials/catalog.js.map +1 -0
- package/dist/sceneImport/materials/index.d.ts +4 -0
- package/dist/sceneImport/materials/index.js +21 -0
- package/dist/sceneImport/materials/index.js.map +1 -0
- package/dist/sceneImport/materials/matcher.d.ts +33 -0
- package/dist/sceneImport/materials/matcher.js +127 -0
- package/dist/sceneImport/materials/matcher.js.map +1 -0
- package/dist/sceneImport/materials/recipe.d.ts +30 -0
- package/dist/sceneImport/materials/recipe.js +134 -0
- package/dist/sceneImport/materials/recipe.js.map +1 -0
- package/dist/sceneImport/multiAsset.d.ts +79 -0
- package/dist/sceneImport/multiAsset.js +535 -0
- package/dist/sceneImport/multiAsset.js.map +1 -0
- package/dist/sceneImport/multiAssetSpawn.d.ts +40 -0
- package/dist/sceneImport/multiAssetSpawn.js +98 -0
- package/dist/sceneImport/multiAssetSpawn.js.map +1 -0
- package/dist/sceneImport/obj.d.ts +41 -0
- package/dist/sceneImport/obj.js +276 -0
- package/dist/sceneImport/obj.js.map +1 -0
- package/dist/sceneImport/package.d.ts +104 -0
- package/dist/sceneImport/package.js +1004 -0
- package/dist/sceneImport/package.js.map +1 -0
- package/dist/sceneImport/packageReader.d.ts +10 -0
- package/dist/sceneImport/packageReader.js +250 -0
- package/dist/sceneImport/packageReader.js.map +1 -0
- package/dist/sceneImport/proxy.d.ts +100 -0
- package/dist/sceneImport/proxy.js +334 -0
- package/dist/sceneImport/proxy.js.map +1 -0
- package/dist/sceneImport/publish.d.ts +47 -0
- package/dist/sceneImport/publish.js +98 -0
- package/dist/sceneImport/publish.js.map +1 -0
- package/dist/sceneImport/semantic.d.ts +75 -0
- package/dist/sceneImport/semantic.js +296 -0
- package/dist/sceneImport/semantic.js.map +1 -0
- package/dist/sceneImport/spawnProbe.d.ts +55 -0
- package/dist/sceneImport/spawnProbe.js +159 -0
- package/dist/sceneImport/spawnProbe.js.map +1 -0
- package/dist/sceneImport/streamLayout.d.ts +96 -0
- package/dist/sceneImport/streamLayout.js +203 -0
- package/dist/sceneImport/streamLayout.js.map +1 -0
- package/dist/sceneImport/textureDecode.d.ts +21 -0
- package/dist/sceneImport/textureDecode.js +292 -0
- package/dist/sceneImport/textureDecode.js.map +1 -0
- package/dist/sceneImport/types.d.ts +84 -0
- package/dist/sceneImport/types.js +58 -0
- package/dist/sceneImport/types.js.map +1 -0
- package/dist/scenePerformance.d.ts +162 -0
- package/dist/scenePerformance.js +905 -0
- package/dist/scenePerformance.js.map +1 -0
- package/dist/screenshot/captureScreenshots.d.ts +36 -0
- package/dist/screenshot/captureScreenshots.js +229 -0
- package/dist/screenshot/captureScreenshots.js.map +1 -0
- package/dist/screenshot/encodeImagePreview.d.ts +5 -0
- package/dist/screenshot/encodeImagePreview.js +42 -0
- package/dist/screenshot/encodeImagePreview.js.map +1 -0
- package/dist/screenshot/publishThumbnail.d.ts +4 -0
- package/dist/screenshot/publishThumbnail.js +56 -0
- package/dist/screenshot/publishThumbnail.js.map +1 -0
- package/dist/screenshot/staticServe.d.ts +14 -0
- package/dist/screenshot/staticServe.js +120 -0
- package/dist/screenshot/staticServe.js.map +1 -0
- package/dist/source-archive.d.ts +101 -0
- package/dist/source-archive.js +309 -0
- package/dist/source-archive.js.map +1 -0
- package/dist/stdioBlocking.d.ts +1 -0
- package/dist/stdioBlocking.js +9 -0
- package/dist/stdioBlocking.js.map +1 -0
- package/dist/terrain/bake.d.ts +40 -0
- package/dist/terrain/bake.js +318 -0
- package/dist/terrain/bake.js.map +1 -0
- package/dist/terrain/generate.d.ts +21 -0
- package/dist/terrain/generate.js +239 -0
- package/dist/terrain/generate.js.map +1 -0
- package/dist/terrain/grid.d.ts +48 -0
- package/dist/terrain/grid.js +133 -0
- package/dist/terrain/grid.js.map +1 -0
- package/dist/terrain/import.d.ts +40 -0
- package/dist/terrain/import.js +252 -0
- package/dist/terrain/import.js.map +1 -0
- package/dist/terrain/index.d.ts +11 -0
- package/dist/terrain/index.js +88 -0
- package/dist/terrain/index.js.map +1 -0
- package/dist/terrain/inspect.d.ts +35 -0
- package/dist/terrain/inspect.js +115 -0
- package/dist/terrain/inspect.js.map +1 -0
- package/dist/terrain/island.d.ts +31 -0
- package/dist/terrain/island.js +128 -0
- package/dist/terrain/island.js.map +1 -0
- package/dist/terrain/noise.d.ts +79 -0
- package/dist/terrain/noise.js +162 -0
- package/dist/terrain/noise.js.map +1 -0
- package/dist/terrain/normals.d.ts +21 -0
- package/dist/terrain/normals.js +55 -0
- package/dist/terrain/normals.js.map +1 -0
- package/dist/terrain/presets/alpine.json +20 -0
- package/dist/terrain/presets/coast.json +20 -0
- package/dist/terrain/presets/desert.json +22 -0
- package/dist/terrain/presets/forest.json +20 -0
- package/dist/terrain/presets/meadow.json +20 -0
- package/dist/terrain/presets/volcanic.json +22 -0
- package/dist/terrain/presets.d.ts +29 -0
- package/dist/terrain/presets.js +128 -0
- package/dist/terrain/presets.js.map +1 -0
- package/dist/terrain/source.d.ts +42 -0
- package/dist/terrain/source.js +241 -0
- package/dist/terrain/source.js.map +1 -0
- package/dist/terrain/tile.d.ts +92 -0
- package/dist/terrain/tile.js +268 -0
- package/dist/terrain/tile.js.map +1 -0
- package/dist/textureAudit.d.ts +23 -0
- package/dist/textureAudit.js +192 -0
- package/dist/textureAudit.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/unreal/avatarMode.d.ts +21 -0
- package/dist/unreal/avatarMode.js +33 -0
- package/dist/unreal/avatarMode.js.map +1 -0
- package/dist/unreal/delivery.d.ts +46 -0
- package/dist/unreal/delivery.js +89 -0
- package/dist/unreal/delivery.js.map +1 -0
- package/dist/unreal/doctor.d.ts +50 -0
- package/dist/unreal/doctor.js +102 -0
- package/dist/unreal/doctor.js.map +1 -0
- package/dist/unreal/package.d.ts +80 -0
- package/dist/unreal/package.js +273 -0
- package/dist/unreal/package.js.map +1 -0
- package/dist/unreal/release.d.ts +41 -0
- package/dist/unreal/release.js +122 -0
- package/dist/unreal/release.js.map +1 -0
- package/dist/unreal.d.ts +30 -0
- package/dist/unreal.js +362 -0
- package/dist/unreal.js.map +1 -0
- package/dist/unrealCandidate.d.ts +213 -0
- package/dist/unrealCandidate.js +765 -0
- package/dist/unrealCandidate.js.map +1 -0
- package/dist/unrealManifest.d.ts +74 -0
- package/dist/unrealManifest.js +354 -0
- package/dist/unrealManifest.js.map +1 -0
- package/dist/unrealPublish.d.ts +111 -0
- package/dist/unrealPublish.js +331 -0
- package/dist/unrealPublish.js.map +1 -0
- package/dist/vault.d.ts +121 -0
- package/dist/vault.js +136 -0
- package/dist/vault.js.map +1 -0
- package/dist/vehicle/addOnDefinition.d.ts +19 -0
- package/dist/vehicle/addOnDefinition.js +21 -0
- package/dist/vehicle/addOnDefinition.js.map +1 -0
- package/dist/vehicle/addon.d.ts +158 -0
- package/dist/vehicle/addon.js +441 -0
- package/dist/vehicle/addon.js.map +1 -0
- package/dist/vehicle/addonDefinitionGuard.d.ts +53 -0
- package/dist/vehicle/addonDefinitionGuard.js +206 -0
- package/dist/vehicle/addonDefinitionGuard.js.map +1 -0
- package/dist/vehicle/addonPublished.d.ts +80 -0
- package/dist/vehicle/addonPublished.js +221 -0
- package/dist/vehicle/addonPublished.js.map +1 -0
- package/dist/vehicle/audio.d.ts +259 -0
- package/dist/vehicle/audio.js +1069 -0
- package/dist/vehicle/audio.js.map +1 -0
- package/dist/vehicle/audioDefects.d.ts +125 -0
- package/dist/vehicle/audioDefects.js +591 -0
- package/dist/vehicle/audioDefects.js.map +1 -0
- package/dist/vehicle/audioDistinct.d.ts +62 -0
- package/dist/vehicle/audioDistinct.js +479 -0
- package/dist/vehicle/audioDistinct.js.map +1 -0
- package/dist/vehicle/audioImport.d.ts +217 -0
- package/dist/vehicle/audioImport.js +610 -0
- package/dist/vehicle/audioImport.js.map +1 -0
- package/dist/vehicle/audioProvenance.d.ts +47 -0
- package/dist/vehicle/audioProvenance.js +364 -0
- package/dist/vehicle/audioProvenance.js.map +1 -0
- package/dist/vehicle/audioReference.d.ts +36 -0
- package/dist/vehicle/audioReference.js +176 -0
- package/dist/vehicle/audioReference.js.map +1 -0
- package/dist/vehicle/audioSlots.d.ts +27 -0
- package/dist/vehicle/audioSlots.js +43 -0
- package/dist/vehicle/audioSlots.js.map +1 -0
- package/dist/vehicle/audioSpectral.d.ts +71 -0
- package/dist/vehicle/audioSpectral.js +534 -0
- package/dist/vehicle/audioSpectral.js.map +1 -0
- package/dist/vehicle/cabinCheck.d.ts +209 -0
- package/dist/vehicle/cabinCheck.js +799 -0
- package/dist/vehicle/cabinCheck.js.map +1 -0
- package/dist/vehicle/drive.d.ts +66 -0
- package/dist/vehicle/drive.js +228 -0
- package/dist/vehicle/drive.js.map +1 -0
- package/dist/vehicle/enginePolicy.d.ts +68 -0
- package/dist/vehicle/enginePolicy.js +209 -0
- package/dist/vehicle/enginePolicy.js.map +1 -0
- package/dist/vehicle/hostFrame.d.ts +78 -0
- package/dist/vehicle/hostFrame.js +136 -0
- package/dist/vehicle/hostFrame.js.map +1 -0
- package/dist/vehicle/inspectionSheets.d.ts +42 -0
- package/dist/vehicle/inspectionSheets.js +173 -0
- package/dist/vehicle/inspectionSheets.js.map +1 -0
- package/dist/vehicle/keyboardLaunch.d.ts +189 -0
- package/dist/vehicle/keyboardLaunch.js +340 -0
- package/dist/vehicle/keyboardLaunch.js.map +1 -0
- package/dist/vehicle/lampCoverage.d.ts +49 -0
- package/dist/vehicle/lampCoverage.js +252 -0
- package/dist/vehicle/lampCoverage.js.map +1 -0
- package/dist/vehicle/mintAddOn.d.ts +110 -0
- package/dist/vehicle/mintAddOn.js +321 -0
- package/dist/vehicle/mintAddOn.js.map +1 -0
- package/dist/vehicle/mintVehicle.d.ts +189 -0
- package/dist/vehicle/mintVehicle.js +417 -0
- package/dist/vehicle/mintVehicle.js.map +1 -0
- package/dist/vehicle/paintHue.d.ts +89 -0
- package/dist/vehicle/paintHue.js +248 -0
- package/dist/vehicle/paintHue.js.map +1 -0
- package/dist/vehicle/panelCoverage.d.ts +37 -0
- package/dist/vehicle/panelCoverage.js +93 -0
- package/dist/vehicle/panelCoverage.js.map +1 -0
- package/dist/vehicle/platform.d.ts +71 -0
- package/dist/vehicle/platform.js +144 -0
- package/dist/vehicle/platform.js.map +1 -0
- package/dist/vehicle/preview.d.ts +339 -0
- package/dist/vehicle/preview.js +854 -0
- package/dist/vehicle/preview.js.map +1 -0
- package/dist/vehicle/previewAddon.d.ts +197 -0
- package/dist/vehicle/previewAddon.js +520 -0
- package/dist/vehicle/previewAddon.js.map +1 -0
- package/dist/vehicle/previewSeats.d.ts +102 -0
- package/dist/vehicle/previewSeats.js +190 -0
- package/dist/vehicle/previewSeats.js.map +1 -0
- package/dist/vehicle/publish.d.ts +78 -0
- package/dist/vehicle/publish.js +111 -0
- package/dist/vehicle/publish.js.map +1 -0
- package/dist/vehicle/recheckListed.d.ts +87 -0
- package/dist/vehicle/recheckListed.js +200 -0
- package/dist/vehicle/recheckListed.js.map +1 -0
- package/dist/vehicle/renditionManifest.d.ts +10 -0
- package/dist/vehicle/renditionManifest.js +56 -0
- package/dist/vehicle/renditionManifest.js.map +1 -0
- package/dist/vehicle/sealCheck.d.ts +30 -0
- package/dist/vehicle/sealCheck.js +59 -0
- package/dist/vehicle/sealCheck.js.map +1 -0
- package/dist/vehicle/simulate.d.ts +219 -0
- package/dist/vehicle/simulate.js +536 -0
- package/dist/vehicle/simulate.js.map +1 -0
- package/dist/vehicle/simulateRuntime.d.ts +76 -0
- package/dist/vehicle/simulateRuntime.js +252 -0
- package/dist/vehicle/simulateRuntime.js.map +1 -0
- package/dist/vehicle/testutil/sourcedAudio.d.ts +13 -0
- package/dist/vehicle/testutil/sourcedAudio.js +96 -0
- package/dist/vehicle/testutil/sourcedAudio.js.map +1 -0
- package/dist/vehicle/validate.d.ts +225 -0
- package/dist/vehicle/validate.js +956 -0
- package/dist/vehicle/validate.js.map +1 -0
- package/dist/vehicle/wheelDesign.d.ts +98 -0
- package/dist/vehicle/wheelDesign.js +276 -0
- package/dist/vehicle/wheelDesign.js.map +1 -0
- package/dist/verifySubPath.d.ts +69 -0
- package/dist/verifySubPath.js +458 -0
- package/dist/verifySubPath.js.map +1 -0
- package/dist/visualQa/animation.d.ts +57 -0
- package/dist/visualQa/animation.js +503 -0
- package/dist/visualQa/animation.js.map +1 -0
- package/dist/visualQa/avatar.d.ts +3 -0
- package/dist/visualQa/avatar.js +443 -0
- package/dist/visualQa/avatar.js.map +1 -0
- package/dist/visualQa/avatarMath.d.ts +180 -0
- package/dist/visualQa/avatarMath.js +318 -0
- package/dist/visualQa/avatarMath.js.map +1 -0
- package/dist/visualQa/avatarPreview.d.ts +26 -0
- package/dist/visualQa/avatarPreview.js +167 -0
- package/dist/visualQa/avatarPreview.js.map +1 -0
- package/dist/visualQa/avatarPreviewMath.d.ts +189 -0
- package/dist/visualQa/avatarPreviewMath.js +696 -0
- package/dist/visualQa/avatarPreviewMath.js.map +1 -0
- package/dist/visualQa/avatarPreviewProbe.d.ts +7 -0
- package/dist/visualQa/avatarPreviewProbe.js +181 -0
- package/dist/visualQa/avatarPreviewProbe.js.map +1 -0
- package/dist/visualQa/avatarProbe.d.ts +1 -0
- package/dist/visualQa/avatarProbe.js +264 -0
- package/dist/visualQa/avatarProbe.js.map +1 -0
- package/dist/visualQa/browser.d.ts +116 -0
- package/dist/visualQa/browser.js +817 -0
- package/dist/visualQa/browser.js.map +1 -0
- package/dist/visualQa/candidate.d.ts +116 -0
- package/dist/visualQa/candidate.js +265 -0
- package/dist/visualQa/candidate.js.map +1 -0
- package/dist/visualQa/checklists.d.ts +31 -0
- package/dist/visualQa/checklists.js +160 -0
- package/dist/visualQa/checklists.js.map +1 -0
- package/dist/visualQa/command.d.ts +110 -0
- package/dist/visualQa/command.js +521 -0
- package/dist/visualQa/command.js.map +1 -0
- package/dist/visualQa/commonAsserts.d.ts +68 -0
- package/dist/visualQa/commonAsserts.js +239 -0
- package/dist/visualQa/commonAsserts.js.map +1 -0
- package/dist/visualQa/contactSheet.d.ts +32 -0
- package/dist/visualQa/contactSheet.js +153 -0
- package/dist/visualQa/contactSheet.js.map +1 -0
- package/dist/visualQa/critic.d.ts +18 -0
- package/dist/visualQa/critic.js +179 -0
- package/dist/visualQa/critic.js.map +1 -0
- package/dist/visualQa/emotePage.d.ts +10 -0
- package/dist/visualQa/emotePage.js +249 -0
- package/dist/visualQa/emotePage.js.map +1 -0
- package/dist/visualQa/evidenceOverlay.d.ts +74 -0
- package/dist/visualQa/evidenceOverlay.js +268 -0
- package/dist/visualQa/evidenceOverlay.js.map +1 -0
- package/dist/visualQa/harness.d.ts +57 -0
- package/dist/visualQa/harness.js +3 -0
- package/dist/visualQa/harness.js.map +1 -0
- package/dist/visualQa/hosted.d.ts +152 -0
- package/dist/visualQa/hosted.js +367 -0
- package/dist/visualQa/hosted.js.map +1 -0
- package/dist/visualQa/labelLimit.d.ts +12 -0
- package/dist/visualQa/labelLimit.js +31 -0
- package/dist/visualQa/labelLimit.js.map +1 -0
- package/dist/visualQa/mirror.d.ts +36 -0
- package/dist/visualQa/mirror.js +98 -0
- package/dist/visualQa/mirror.js.map +1 -0
- package/dist/visualQa/moveUx.d.ts +44 -0
- package/dist/visualQa/moveUx.js +103 -0
- package/dist/visualQa/moveUx.js.map +1 -0
- package/dist/visualQa/pinnedChrome.d.ts +63 -0
- package/dist/visualQa/pinnedChrome.js +214 -0
- package/dist/visualQa/pinnedChrome.js.map +1 -0
- package/dist/visualQa/resolve.d.ts +88 -0
- package/dist/visualQa/resolve.js +248 -0
- package/dist/visualQa/resolve.js.map +1 -0
- package/dist/visualQa/scene.d.ts +14 -0
- package/dist/visualQa/scene.js +688 -0
- package/dist/visualQa/scene.js.map +1 -0
- package/dist/visualQa/shot.d.ts +2 -0
- package/dist/visualQa/shot.js +27 -0
- package/dist/visualQa/shot.js.map +1 -0
- package/dist/visualQa/signIn.d.ts +35 -0
- package/dist/visualQa/signIn.js +144 -0
- package/dist/visualQa/signIn.js.map +1 -0
- package/dist/visualQa/submitBudget.d.ts +25 -0
- package/dist/visualQa/submitBudget.js +82 -0
- package/dist/visualQa/submitBudget.js.map +1 -0
- package/dist/visualQa/types.d.ts +195 -0
- package/dist/visualQa/types.js +12 -0
- package/dist/visualQa/types.js.map +1 -0
- package/dist/visualQa/vehicle.d.ts +130 -0
- package/dist/visualQa/vehicle.js +2002 -0
- package/dist/visualQa/vehicle.js.map +1 -0
- package/dist/visualQa/vehicleAddonProbe.d.ts +1 -0
- package/dist/visualQa/vehicleAddonProbe.js +115 -0
- package/dist/visualQa/vehicleAddonProbe.js.map +1 -0
- package/dist/visualQa/vehicleCabinFit.d.ts +78 -0
- package/dist/visualQa/vehicleCabinFit.js +55 -0
- package/dist/visualQa/vehicleCabinFit.js.map +1 -0
- package/dist/visualQa/vehicleClearance.d.ts +135 -0
- package/dist/visualQa/vehicleClearance.js +299 -0
- package/dist/visualQa/vehicleClearance.js.map +1 -0
- package/dist/visualQa/vehicleDriveAlive.d.ts +55 -0
- package/dist/visualQa/vehicleDriveAlive.js +103 -0
- package/dist/visualQa/vehicleDriveAlive.js.map +1 -0
- package/dist/visualQa/vehicleGrip.d.ts +156 -0
- package/dist/visualQa/vehicleGrip.js +439 -0
- package/dist/visualQa/vehicleGrip.js.map +1 -0
- package/dist/visualQa/vehicleInspection.d.ts +70 -0
- package/dist/visualQa/vehicleInspection.js +318 -0
- package/dist/visualQa/vehicleInspection.js.map +1 -0
- package/dist/visualQa/vehicleLamps.d.ts +120 -0
- package/dist/visualQa/vehicleLamps.js +377 -0
- package/dist/visualQa/vehicleLamps.js.map +1 -0
- package/dist/visualQa/vehicleMath.d.ts +440 -0
- package/dist/visualQa/vehicleMath.js +967 -0
- package/dist/visualQa/vehicleMath.js.map +1 -0
- package/dist/visualQa/vehicleOccupants.d.ts +66 -0
- package/dist/visualQa/vehicleOccupants.js +448 -0
- package/dist/visualQa/vehicleOccupants.js.map +1 -0
- package/dist/visualQa/vehiclePenetration.d.ts +172 -0
- package/dist/visualQa/vehiclePenetration.js +1157 -0
- package/dist/visualQa/vehiclePenetration.js.map +1 -0
- package/dist/visualQa/vehiclePoses.d.ts +25 -0
- package/dist/visualQa/vehiclePoses.js +56 -0
- package/dist/visualQa/vehiclePoses.js.map +1 -0
- package/dist/visualQa/vehiclePosture.d.ts +78 -0
- package/dist/visualQa/vehiclePosture.js +263 -0
- package/dist/visualQa/vehiclePosture.js.map +1 -0
- package/dist/visualQa/vehiclePreview.d.ts +103 -0
- package/dist/visualQa/vehiclePreview.js +312 -0
- package/dist/visualQa/vehiclePreview.js.map +1 -0
- package/dist/visualQa/vehicleProbe.d.ts +3 -0
- package/dist/visualQa/vehicleProbe.js +670 -0
- package/dist/visualQa/vehicleProbe.js.map +1 -0
- package/dist/visualQa/vehicleStance.d.ts +108 -0
- package/dist/visualQa/vehicleStance.js +200 -0
- package/dist/visualQa/vehicleStance.js.map +1 -0
- package/dist/visualQa/viewer.d.ts +6 -0
- package/dist/visualQa/viewer.js +750 -0
- package/dist/visualQa/viewer.js.map +1 -0
- package/dist/visualQa/viewerGeometry.d.ts +116 -0
- package/dist/visualQa/viewerGeometry.js +476 -0
- package/dist/visualQa/viewerGeometry.js.map +1 -0
- package/dist/visualQa/viewerPage.d.ts +11 -0
- package/dist/visualQa/viewerPage.js +414 -0
- package/dist/visualQa/viewerPage.js.map +1 -0
- package/dist/visualQa/webgpuSelfTest.d.ts +52 -0
- package/dist/visualQa/webgpuSelfTest.js +113 -0
- package/dist/visualQa/webgpuSelfTest.js.map +1 -0
- package/dist/visualQa/winChrome.d.ts +92 -0
- package/dist/visualQa/winChrome.js +445 -0
- package/dist/visualQa/winChrome.js.map +1 -0
- package/dist/visualQa/worldWalk.d.ts +59 -0
- package/dist/visualQa/worldWalk.js +523 -0
- package/dist/visualQa/worldWalk.js.map +1 -0
- package/dist/visualQa/worldWalkMath.d.ts +263 -0
- package/dist/visualQa/worldWalkMath.js +873 -0
- package/dist/visualQa/worldWalkMath.js.map +1 -0
- package/dist/visualQa/worldWalkProbe.d.ts +1 -0
- package/dist/visualQa/worldWalkProbe.js +206 -0
- package/dist/visualQa/worldWalkProbe.js.map +1 -0
- package/dist/visualsConfig.d.ts +65 -0
- package/dist/visualsConfig.js +1333 -0
- package/dist/visualsConfig.js.map +1 -0
- package/dist/voiceCommand.d.ts +11 -0
- package/dist/voiceCommand.js +101 -0
- package/dist/voiceCommand.js.map +1 -0
- package/dist/world/analyze.d.ts +51 -0
- package/dist/world/analyze.js +767 -0
- package/dist/world/analyze.js.map +1 -0
- package/dist/world/baseline.d.ts +33 -0
- package/dist/world/baseline.js +111 -0
- package/dist/world/baseline.js.map +1 -0
- package/dist/world/canonicalize.d.ts +7 -0
- package/dist/world/canonicalize.js +110 -0
- package/dist/world/canonicalize.js.map +1 -0
- package/dist/world/diff.d.ts +18 -0
- package/dist/world/diff.js +58 -0
- package/dist/world/diff.js.map +1 -0
- package/dist/world/focus.d.ts +11 -0
- package/dist/world/focus.js +161 -0
- package/dist/world/focus.js.map +1 -0
- package/dist/world/format.d.ts +6 -0
- package/dist/world/format.js +125 -0
- package/dist/world/format.js.map +1 -0
- package/dist/world/readWorldMetrics.d.ts +10 -0
- package/dist/world/readWorldMetrics.js +31 -0
- package/dist/world/readWorldMetrics.js.map +1 -0
- package/dist/world/snapshotTypes.d.ts +59 -0
- package/dist/world/snapshotTypes.js +8 -0
- package/dist/world/snapshotTypes.js.map +1 -0
- package/dist/world/snapshotWorld.d.ts +16 -0
- package/dist/world/snapshotWorld.js +142 -0
- package/dist/world/snapshotWorld.js.map +1 -0
- package/dist/world/wiring.d.ts +21 -0
- package/dist/world/wiring.js +73 -0
- package/dist/world/wiring.js.map +1 -0
- package/dist/world/zones.d.ts +23 -0
- package/dist/world/zones.js +134 -0
- package/dist/world/zones.js.map +1 -0
- package/docs/bridge.md +304 -0
- package/docs/device-hand-reach.md +44 -0
- package/docs/scene-hlod.md +139 -0
- package/docs/vehicle-audio-import.md +299 -0
- package/docs/vehicle-simulate.md +165 -0
- package/docs/visual-qa-candidate-versions.md +71 -0
- package/docs/visual-qa.md +462 -0
- package/package.json +69 -4
- package/plugins/beamng-vehicle/README.md +129 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_abs.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_audio.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_engine.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_expression.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_jbeam.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_materials.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_stock_events.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/fmod_studio.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/fsb5.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/sim_audio.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_ac.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_beamng.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_fixtures.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/test_beamng_engine.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/test_sim_audio.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/beamng-vehicle.mjs +1295 -0
- package/plugins/beamng-vehicle/bin/beamng_abs.py +408 -0
- package/plugins/beamng-vehicle/bin/beamng_audio.py +394 -0
- package/plugins/beamng-vehicle/bin/beamng_engine.py +184 -0
- package/plugins/beamng-vehicle/bin/beamng_expression.py +373 -0
- package/plugins/beamng-vehicle/bin/beamng_jbeam.py +858 -0
- package/plugins/beamng-vehicle/bin/beamng_materials.py +261 -0
- package/plugins/beamng-vehicle/bin/beamng_stock_events.py +171 -0
- package/plugins/beamng-vehicle/bin/convert-beamng.py +1563 -0
- package/plugins/beamng-vehicle/bin/fmod_studio.py +524 -0
- package/plugins/beamng-vehicle/bin/fsb5.py +304 -0
- package/plugins/beamng-vehicle/bin/index-beamng-common.mjs +16 -0
- package/plugins/beamng-vehicle/bin/index-beamng.py +69 -0
- package/plugins/beamng-vehicle/bin/lower-beamng.py +2399 -0
- package/plugins/beamng-vehicle/bin/sim_audio.py +250 -0
- package/plugins/beamng-vehicle/bin/sim_audio_ac.py +378 -0
- package/plugins/beamng-vehicle/bin/sim_audio_beamng.py +878 -0
- package/plugins/beamng-vehicle/bin/sim_audio_fixtures.py +287 -0
- package/plugins/beamng-vehicle/bin/sim_audio_import.py +135 -0
- package/plugins/beamng-vehicle/bin/test_beamng_engine.py +124 -0
- package/plugins/beamng-vehicle/bin/test_beamng_jbeam.py +383 -0
- package/plugins/beamng-vehicle/bin/test_beamng_precedence.py +134 -0
- package/plugins/beamng-vehicle/bin/test_sim_audio.py +249 -0
- package/plugins/beamng-vehicle/bridge-plugin.json +55 -0
- package/plugins/beamng-vehicle/controller-adapters/beamng-0.39.4-abs.json +52 -0
- package/plugins/beamng-vehicle/lib/common-dependency.mjs +357 -0
- package/plugins/beamng-vehicle/lib/dae-scan.mjs +40 -0
- package/plugins/beamng-vehicle/lib/file-identity.mjs +46 -0
- package/plugins/beamng-vehicle/lib/material-semantics.mjs +312 -0
- package/plugins/beamng-vehicle/lib/palette-contract.mjs +6 -0
- package/plugins/beamng-vehicle/lib/vehicle-qa.mjs +567 -0
- package/plugins/beamng-vehicle/lib/vehicle-source.mjs +247 -0
- package/plugins/beamng-vehicle/profiles/source-compatibility.json +14 -0
- package/plugins/beamng-vehicle/vendor/vehicle-package-v2.mjs +6451 -0
- package/vendor/README.md +63 -0
- package/vendor/ability-manifest.mjs +527 -6
- package/vendor/audits/asset-credits.mjs +96 -0
- package/vendor/audits/check-loaders.mjs +80 -0
- package/vendor/audits/perf-gate.mjs +759 -0
- package/vendor/audits/prove-live.mjs +307 -0
- package/vendor/audits/source-audit.mjs +530 -0
- package/vendor/audits/world-audit.mjs +439 -0
- package/vendor/capture-body.glb +0 -0
- package/vendor/capture-face.glb +0 -0
- package/vendor/installed-index.mjs +1 -1
- package/vendor/vehicle-package-v2.mjs +6451 -0
|
@@ -0,0 +1,1563 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Blender-side BeamNG DAE to canonical HELIX GLB conversion.
|
|
3
|
+
|
|
4
|
+
The converter never fabricates cabin or wheel meshes. It measures authored source
|
|
5
|
+
geometry, restores source material maps, emits canonical sockets, and leaves final
|
|
6
|
+
acceptance to vehicle-qa.mjs.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import json
|
|
12
|
+
import math
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
import xml.etree.ElementTree as ET
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
# Blender runs `--python <script>` without the script's directory on sys.path (4.0 and 4.5 on
|
|
20
|
+
# Linux), so sibling modules must be made importable explicitly before they are imported.
|
|
21
|
+
_SCRIPT_DIR = str(Path(__file__).resolve().parent)
|
|
22
|
+
if _SCRIPT_DIR not in sys.path:
|
|
23
|
+
sys.path.insert(0, _SCRIPT_DIR)
|
|
24
|
+
|
|
25
|
+
import bpy
|
|
26
|
+
import numpy as np
|
|
27
|
+
from mathutils import Matrix, Vector
|
|
28
|
+
from beamng_materials import TextureChain, parse_material_tree
|
|
29
|
+
from beamng_jbeam import (
|
|
30
|
+
JBeamDocumentCache,
|
|
31
|
+
MAX_PC_FILE_BYTES,
|
|
32
|
+
MAX_PC_SOURCES,
|
|
33
|
+
MAX_PC_TOTAL_BYTES,
|
|
34
|
+
bounded_asset_paths,
|
|
35
|
+
build_part_catalog,
|
|
36
|
+
build_selected_part_index,
|
|
37
|
+
preselect_configuration,
|
|
38
|
+
resolve_part_closure,
|
|
39
|
+
selected_part_summary,
|
|
40
|
+
write_bound_selected_part_index,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
INTERIOR = re.compile(r"seat|dash|interior|cabin|console|steer|gauge|door.?(?:card|panel)|pedal", re.I)
|
|
45
|
+
# `trim`, `wheelwell`, and `steeringwheel` are common vehicle mesh names but are
|
|
46
|
+
# not road-wheel custody. Keep this predicate exact because it controls both
|
|
47
|
+
# configuration retention and destructive wheel-source normalization.
|
|
48
|
+
WHEEL = re.compile(r"tyre|tire|(?<!steering)(?<!steering_)(?<!steer_)wheel(?!well)|(?<!t)rim", re.I)
|
|
49
|
+
TYRE = re.compile(r"tyre|tire", re.I)
|
|
50
|
+
BRAKE_HARDWARE = re.compile(r"brake.?disc|brake.?rotor|brake.?drum|(?:^|_)drum(?:_|$)|"
|
|
51
|
+
r"wheel.?hub|(?:^|_)hub(?:_|$)", re.I)
|
|
52
|
+
REAR = re.compile(r"exhaust|muffler|tail.?pipe|taillight|tailgate|trunk|boot|bumper.?r", re.I)
|
|
53
|
+
COLLADA_NS = {"c": "http://www.collada.org/2005/11/COLLADASchema"}
|
|
54
|
+
MATERIAL_SCAN_LIMIT = (4_096, 256 * 1024 * 1024, 32 * 1024 * 1024)
|
|
55
|
+
TEXTURE_SCAN_LIMIT = (200_000, 128 * 1024 * 1024 * 1024, 2 * 1024 * 1024 * 1024)
|
|
56
|
+
AUDIO_SCAN_LIMIT = (20_000, 16 * 1024 * 1024 * 1024, 1024 * 1024 * 1024)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def local_tag(element) -> str:
|
|
60
|
+
return element.tag.split("}")[-1]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def primitive_inputs(element) -> list[dict]:
|
|
64
|
+
return [{"semantic": row.get("semantic"), "source": row.get("source", "").lstrip("#"),
|
|
65
|
+
"offset": int(row.get("offset", "0")), "set": int(row.get("set") or 0)}
|
|
66
|
+
for row in element.findall("c:input", COLLADA_NS) if row.get("source")]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def fan_triangulate(rows: np.ndarray, counts: np.ndarray) -> np.ndarray:
|
|
70
|
+
output = []
|
|
71
|
+
cursor = 0
|
|
72
|
+
for count in counts:
|
|
73
|
+
face = rows[cursor:cursor + int(count)]
|
|
74
|
+
cursor += int(count)
|
|
75
|
+
for index in range(1, len(face) - 1):
|
|
76
|
+
output.extend((face[0], face[index], face[index + 1]))
|
|
77
|
+
return np.asarray(output, dtype=np.int64)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def read_geometry(element) -> tuple[str, dict] | None:
|
|
81
|
+
mesh = element.find("c:mesh", COLLADA_NS)
|
|
82
|
+
if mesh is None:
|
|
83
|
+
return None
|
|
84
|
+
sources = {}
|
|
85
|
+
for source in mesh.findall("c:source", COLLADA_NS):
|
|
86
|
+
values = source.find("c:float_array", COLLADA_NS)
|
|
87
|
+
accessor = source.find("c:technique_common/c:accessor", COLLADA_NS)
|
|
88
|
+
if values is None:
|
|
89
|
+
continue
|
|
90
|
+
stride = int(accessor.get("stride", "1")) if accessor is not None else 1
|
|
91
|
+
numbers = np.fromstring(values.text or "", dtype=np.float64, sep=" ")
|
|
92
|
+
numbers = numbers[:numbers.size - numbers.size % stride] if stride else numbers
|
|
93
|
+
sources[source.get("id")] = numbers.reshape(-1, stride)
|
|
94
|
+
vertices = {}
|
|
95
|
+
for vertex in mesh.findall("c:vertices", COLLADA_NS):
|
|
96
|
+
position = vertex.find('c:input[@semantic="POSITION"]', COLLADA_NS)
|
|
97
|
+
if position is not None:
|
|
98
|
+
vertices[vertex.get("id")] = position.get("source", "").lstrip("#")
|
|
99
|
+
primitives = []
|
|
100
|
+
for primitive in list(mesh.findall("c:triangles", COLLADA_NS)) + list(mesh.findall("c:polylist", COLLADA_NS)):
|
|
101
|
+
inputs = primitive_inputs(primitive)
|
|
102
|
+
payload = primitive.find("c:p", COLLADA_NS)
|
|
103
|
+
if not inputs or payload is None:
|
|
104
|
+
continue
|
|
105
|
+
stride = max(row["offset"] for row in inputs) + 1
|
|
106
|
+
values = np.fromstring(payload.text or "", dtype=np.int64, sep=" ")
|
|
107
|
+
values = values[:values.size - values.size % stride].reshape(-1, stride)
|
|
108
|
+
if local_tag(primitive) == "polylist":
|
|
109
|
+
counts_element = primitive.find("c:vcount", COLLADA_NS)
|
|
110
|
+
counts = np.fromstring(counts_element.text or "", dtype=np.int64, sep=" ") if counts_element is not None else np.zeros(0)
|
|
111
|
+
values = fan_triangulate(values, counts)
|
|
112
|
+
else:
|
|
113
|
+
values = values[:len(values) - len(values) % 3]
|
|
114
|
+
if len(values):
|
|
115
|
+
primitives.append({"material": primitive.get("material") or "default", "inputs": inputs, "indices": values})
|
|
116
|
+
identifier = element.get("id")
|
|
117
|
+
return (identifier, {"name": element.get("name") or identifier, "sources": sources,
|
|
118
|
+
"vertices": vertices, "primitives": primitives}) if identifier else None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def read_scene(element) -> list[dict]:
|
|
122
|
+
nodes = []
|
|
123
|
+
|
|
124
|
+
def visit(parent, parent_matrix):
|
|
125
|
+
for node in parent.findall("c:node", COLLADA_NS):
|
|
126
|
+
local = np.eye(4)
|
|
127
|
+
for child in node:
|
|
128
|
+
if local_tag(child) == "matrix" and (child.text or "").strip():
|
|
129
|
+
local = local @ np.fromstring(child.text, dtype=np.float64, sep=" ").reshape(4, 4)
|
|
130
|
+
world = parent_matrix @ local
|
|
131
|
+
instance = node.find("c:instance_geometry", COLLADA_NS)
|
|
132
|
+
bindings = {}
|
|
133
|
+
if instance is not None:
|
|
134
|
+
for binding in instance.findall(".//c:instance_material", COLLADA_NS):
|
|
135
|
+
bindings[binding.get("symbol")] = binding.get("target", "").lstrip("#")
|
|
136
|
+
nodes.append({"name": node.get("name") or node.get("id") or "mesh",
|
|
137
|
+
"geometry": instance.get("url", "").lstrip("#") if instance is not None else None,
|
|
138
|
+
"matrix": world, "materials": bindings})
|
|
139
|
+
visit(node, world)
|
|
140
|
+
for scene in element.findall("c:visual_scene", COLLADA_NS):
|
|
141
|
+
visit(scene, np.eye(4))
|
|
142
|
+
return nodes
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def parse_collada(path: Path) -> dict:
|
|
146
|
+
geometries, materials, nodes = {}, {}, []
|
|
147
|
+
up_axis, metre = "Z_UP", 1.0
|
|
148
|
+
for _, element in ET.iterparse(path, events=("end",)):
|
|
149
|
+
tag = local_tag(element)
|
|
150
|
+
if tag == "asset":
|
|
151
|
+
up = element.find("c:up_axis", COLLADA_NS)
|
|
152
|
+
unit = element.find("c:unit", COLLADA_NS)
|
|
153
|
+
up_axis = (up.text or "Z_UP").strip() if up is not None else "Z_UP"
|
|
154
|
+
metre = float(unit.get("meter", "1")) if unit is not None else 1.0
|
|
155
|
+
element.clear()
|
|
156
|
+
elif tag == "library_materials":
|
|
157
|
+
for material in element.findall("c:material", COLLADA_NS):
|
|
158
|
+
materials[material.get("id")] = material.get("name") or material.get("id") or "default"
|
|
159
|
+
element.clear()
|
|
160
|
+
elif tag == "geometry":
|
|
161
|
+
result = read_geometry(element)
|
|
162
|
+
if result:
|
|
163
|
+
geometries[result[0]] = result[1]
|
|
164
|
+
element.clear()
|
|
165
|
+
elif tag == "library_visual_scenes":
|
|
166
|
+
nodes.extend(read_scene(element))
|
|
167
|
+
element.clear()
|
|
168
|
+
return {"geometries": geometries, "materials": materials, "nodes": nodes,
|
|
169
|
+
"upAxis": up_axis, "metre": metre}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def build_collada(path: Path) -> list[bpy.types.Object]:
|
|
173
|
+
document = parse_collada(path)
|
|
174
|
+
built = []
|
|
175
|
+
path_parts = [part.lower() for part in path.parts]
|
|
176
|
+
wheel_axle = "front" if "wheels" in path_parts and "f" in path_parts else \
|
|
177
|
+
"rear" if "wheels" in path_parts and "r" in path_parts else None
|
|
178
|
+
for node in document["nodes"]:
|
|
179
|
+
geometry = document["geometries"].get(node["geometry"])
|
|
180
|
+
if not geometry or not geometry["primitives"]:
|
|
181
|
+
continue
|
|
182
|
+
position_source = None
|
|
183
|
+
for primitive in geometry["primitives"]:
|
|
184
|
+
vertex = next((row for row in primitive["inputs"] if row["semantic"] == "VERTEX"), None)
|
|
185
|
+
if vertex:
|
|
186
|
+
position_source = geometry["vertices"].get(vertex["source"], vertex["source"])
|
|
187
|
+
break
|
|
188
|
+
positions = geometry["sources"].get(position_source)
|
|
189
|
+
if positions is None or len(positions) == 0:
|
|
190
|
+
continue
|
|
191
|
+
faces, face_materials, loop_uvs, slots = [], [], [], []
|
|
192
|
+
for primitive in geometry["primitives"]:
|
|
193
|
+
input_by_semantic = {}
|
|
194
|
+
for row in primitive["inputs"]:
|
|
195
|
+
if row["semantic"] != "TEXCOORD" or row["semantic"] not in input_by_semantic or row["set"] == 0:
|
|
196
|
+
input_by_semantic[row["semantic"]] = row
|
|
197
|
+
vertex = input_by_semantic.get("VERTEX")
|
|
198
|
+
if not vertex:
|
|
199
|
+
continue
|
|
200
|
+
material_id = node["materials"].get(primitive["material"], primitive["material"])
|
|
201
|
+
material_name = document["materials"].get(material_id, material_id or "default")
|
|
202
|
+
if material_name not in slots:
|
|
203
|
+
slots.append(material_name)
|
|
204
|
+
material_index = slots.index(material_name)
|
|
205
|
+
texcoord = input_by_semantic.get("TEXCOORD")
|
|
206
|
+
tex_values = geometry["sources"].get(texcoord["source"]) if texcoord else None
|
|
207
|
+
rows = primitive["indices"]
|
|
208
|
+
for start in range(0, len(rows), 3):
|
|
209
|
+
triangle = rows[start:start + 3]
|
|
210
|
+
if len(triangle) < 3:
|
|
211
|
+
continue
|
|
212
|
+
faces.append(tuple(int(row[vertex["offset"]]) for row in triangle))
|
|
213
|
+
face_materials.append(material_index)
|
|
214
|
+
loop_uvs.extend(tuple(tex_values[int(row[texcoord["offset"]])][:2]) if tex_values is not None else (0.0, 0.0)
|
|
215
|
+
for row in triangle)
|
|
216
|
+
if not faces:
|
|
217
|
+
continue
|
|
218
|
+
object_name = f"{node['name']}_{wheel_axle}" if wheel_axle and WHEEL.search(node["name"]) else node["name"]
|
|
219
|
+
mesh = bpy.data.meshes.new(object_name)
|
|
220
|
+
mesh.from_pydata([tuple(row[:3] * document["metre"]) for row in positions], [], faces)
|
|
221
|
+
mesh.update()
|
|
222
|
+
obj = bpy.data.objects.new(object_name, mesh)
|
|
223
|
+
bpy.context.scene.collection.objects.link(obj)
|
|
224
|
+
obj["helix_source_dae"] = str(path)
|
|
225
|
+
obj["helix_dedicated_wheel"] = bool(wheel_axle)
|
|
226
|
+
for name in slots:
|
|
227
|
+
material = bpy.data.materials.get(name) or bpy.data.materials.new(name)
|
|
228
|
+
mesh.materials.append(material)
|
|
229
|
+
for polygon, index in zip(mesh.polygons, face_materials):
|
|
230
|
+
polygon.material_index = index
|
|
231
|
+
uv_layer = mesh.uv_layers.new(name="UVMap")
|
|
232
|
+
for index, value in enumerate(loop_uvs[:len(uv_layer.data)]):
|
|
233
|
+
uv_layer.data[index].uv = value
|
|
234
|
+
obj.matrix_world = Matrix(node["matrix"].tolist())
|
|
235
|
+
if document["upAxis"] == "Y_UP":
|
|
236
|
+
obj.matrix_world = Matrix.Rotation(math.radians(90), 4, "X") @ obj.matrix_world
|
|
237
|
+
built.append(obj)
|
|
238
|
+
return built
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def config_overrides(path: Path, cache: JBeamDocumentCache) -> dict:
|
|
242
|
+
try:
|
|
243
|
+
document = cache.load(path)
|
|
244
|
+
return document.get("parts", {}) if isinstance(document, dict) and isinstance(document.get("parts"), dict) else {}
|
|
245
|
+
except Exception:
|
|
246
|
+
return {}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def choose_config(root: Path, daes: list[str], parts: dict[str, dict],
|
|
250
|
+
cache: JBeamDocumentCache,
|
|
251
|
+
configuration_hint: str | None = None,
|
|
252
|
+
present_meshes: set[str] | None = None) -> Path | None:
|
|
253
|
+
variants = [root.name.lower(), *(Path(value).parent.name.lower() for value in daes)]
|
|
254
|
+
configs = bounded_asset_paths(
|
|
255
|
+
[root], {".pc": (MAX_PC_SOURCES, MAX_PC_TOTAL_BYTES, MAX_PC_FILE_BYTES)},
|
|
256
|
+
recursive=False)
|
|
257
|
+
preselected = preselect_configuration(root, variants, configuration_hint)
|
|
258
|
+
if preselected is not None:
|
|
259
|
+
return preselected
|
|
260
|
+
if not configs:
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
def score(path: Path) -> tuple:
|
|
264
|
+
overrides = config_overrides(path, cache)
|
|
265
|
+
chosen = resolve_part_closure(parts, overrides)["parts"]
|
|
266
|
+
meshes = {mesh for part in chosen for mesh in part["meshes"] if isinstance(mesh, str)}
|
|
267
|
+
semantic = " ".join([path.stem, *overrides.keys(), *map(str, overrides.values()),
|
|
268
|
+
*(part["name"] for part in chosen), *meshes]).lower()
|
|
269
|
+
present = meshes & present_meshes if present_meshes is not None else meshes
|
|
270
|
+
present_semantic = " ".join(present).lower()
|
|
271
|
+
present_coverage = sum(bool(re.search(pattern, present_semantic)) for pattern in (
|
|
272
|
+
r"body|shell|frame|chassis", r"seat|interior|cabin", r"steer", r"wheel|rim|tire|tyre",
|
|
273
|
+
r"engine|motor", r"suspension|hub", r"door", r"glass|windshield|windscreen"))
|
|
274
|
+
coverage = sum(bool(re.search(pattern, semantic)) for pattern in (
|
|
275
|
+
r"body|shell|frame|chassis", r"seat|interior|cabin", r"steer", r"wheel|rim|tire|tyre",
|
|
276
|
+
r"engine|motor", r"suspension|hub", r"door", r"glass|windshield|windscreen"))
|
|
277
|
+
stem = re.sub(r"[^a-z0-9]", "", path.stem.lower())
|
|
278
|
+
candidate = re.sub(r"[^a-z0-9]", "", root.name.lower())
|
|
279
|
+
canonical = int(stem == candidate)
|
|
280
|
+
configured = " ".join(str(value) for value in overrides.values() if isinstance(value, str)).lower()
|
|
281
|
+
active_seats = {value for value in overrides.values()
|
|
282
|
+
if isinstance(value, str) and value != "none" and re.search(r"(?:^|_)seat(?:s)?(?:_|$)", value, re.I)}
|
|
283
|
+
front_sides = sum(bool(re.search(pattern, configured)) for pattern in (
|
|
284
|
+
r"seat[^ ]*(?:_fl|_l(?:_|$)|driver)", r"seat[^ ]*(?:_fr|_r(?:_|$)|passenger)"))
|
|
285
|
+
rear_seats = int(bool(re.search(r"seat(?:s)?[^ ]*(?:_r(?:_|$)|rear)", configured)))
|
|
286
|
+
visual_steering = int(any(isinstance(value, str) and value != "none"
|
|
287
|
+
and re.search(r"(?:^|_)steer(?:_|$)", value, re.I)
|
|
288
|
+
for value in overrides.values()))
|
|
289
|
+
# A complete road cabin outranks optional mesh volume. This rejects a
|
|
290
|
+
# one-seat hillclimb shell in favour of the authored two/four-seat trim
|
|
291
|
+
# while leaving requested compatibility-profile configurations intact.
|
|
292
|
+
cabin = min(len(active_seats), 4) + front_sides + rear_seats + visual_steering * 2
|
|
293
|
+
stock = int(bool(re.search(r"(?:^|_)(?:base|stock|factory|default|vanilla)(?:_|$)", path.stem, re.I)))
|
|
294
|
+
info_path = root / f"info_{path.stem}.json"
|
|
295
|
+
if info_path.is_file():
|
|
296
|
+
try:
|
|
297
|
+
info = cache.load(info_path)
|
|
298
|
+
description = f"{info.get('Configuration', '')} {info.get('Description', '')}".lower()
|
|
299
|
+
stock += int(bool(re.search(r"base model|stock|factory|standard model|entry.?level", description))) * 2
|
|
300
|
+
except Exception:
|
|
301
|
+
pass
|
|
302
|
+
stock += int(bool(re.fullmatch(r"sa_1000", path.stem, re.I))) * 2
|
|
303
|
+
modified = int(bool(re.search(r"race|drift|drag|hillclimb|maxspeed|stanced|stripped|offroad|jato|godmode|"
|
|
304
|
+
r"derby|custom|tuned|owner|experience|camo|colour|special|rhd|police|pursuit",
|
|
305
|
+
path.stem, re.I)))
|
|
306
|
+
if modified:
|
|
307
|
+
stock = 0
|
|
308
|
+
# Completeness comes first. Among complete configurations, canonical or
|
|
309
|
+
# explicitly stock/base spawns win before raw optional-part volume.
|
|
310
|
+
return (present_coverage, coverage, canonical, cabin, stock, -modified,
|
|
311
|
+
len(present), len(meshes), len(chosen), len(overrides), path.name)
|
|
312
|
+
|
|
313
|
+
scored = [(score(path), path) for path in configs]
|
|
314
|
+
selected = max(scored, key=lambda row: row[0])[1]
|
|
315
|
+
top = sorted(scored, key=lambda row: row[0], reverse=True)[:5]
|
|
316
|
+
print(f"[beamng] configuration candidates top={[(path.name, values[:-1]) for values, path in top]}")
|
|
317
|
+
return selected
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def configured_meshes(root: Path, source_root: Path, daes: list[str],
|
|
321
|
+
configuration_hint: str | None = None,
|
|
322
|
+
present_meshes: set[str] | None = None,
|
|
323
|
+
dependency_roots: list[Path] | None = None,
|
|
324
|
+
part_index_path: Path | None = None,
|
|
325
|
+
bridge_plan_key: str | None = None,
|
|
326
|
+
source_sha256: str | None = None,
|
|
327
|
+
common_closure_sha256: str | None = None) -> tuple[set[str], dict]:
|
|
328
|
+
dependency_roots = dependency_roots or []
|
|
329
|
+
cache = JBeamDocumentCache(source_root, dependency_roots)
|
|
330
|
+
common = source_root / "vehicles" / "common"
|
|
331
|
+
dependency_common = [path / "vehicles" / "common" for path in dependency_roots]
|
|
332
|
+
ranked_roots = [(root, 0), (common, 1), *((source, 2) for source in dependency_common)]
|
|
333
|
+
admitted = [(source, rank) for source, rank in ranked_roots if source.is_dir()]
|
|
334
|
+
parts, errors = build_part_catalog(
|
|
335
|
+
[source for source, _ in admitted], cache, [rank for _, rank in admitted])
|
|
336
|
+
config = choose_config(root, daes, parts, cache, configuration_hint, present_meshes)
|
|
337
|
+
overrides = {}
|
|
338
|
+
config_document = {}
|
|
339
|
+
if config:
|
|
340
|
+
try:
|
|
341
|
+
config_document = cache.load(config)
|
|
342
|
+
overrides = config_overrides(config, cache)
|
|
343
|
+
except Exception as error:
|
|
344
|
+
errors.append(f"{config.name}: {error}")
|
|
345
|
+
requested_main = config_document.get("mainPartName") if isinstance(config_document, dict) else None
|
|
346
|
+
graph = resolve_part_closure(parts, overrides, requested_main)
|
|
347
|
+
chosen = graph["parts"]
|
|
348
|
+
wanted = {mesh for part in chosen for mesh in part["meshes"] if isinstance(mesh, str)}
|
|
349
|
+
slot_closure = graph["slotClosure"]
|
|
350
|
+
semantic = " ".join([*(part["name"] for part in chosen), *wanted]).lower()
|
|
351
|
+
if len(wanted) < 4 or not re.search(r"body|shell|frame|chassis", semantic):
|
|
352
|
+
raise RuntimeError(f"configuration {config.name if config else '<default>'} has no complete vehicle shell "
|
|
353
|
+
f"({len(wanted)} render meshes; JBeam parse failures={len(errors)})")
|
|
354
|
+
paint_base = None
|
|
355
|
+
if config_document:
|
|
356
|
+
try:
|
|
357
|
+
paints = config_document.get("paints", [])
|
|
358
|
+
colors = config_document.get("colors", [])
|
|
359
|
+
raw_color = paints[0].get("baseColor") if (
|
|
360
|
+
isinstance(paints, list) and paints and isinstance(paints[0], dict)
|
|
361
|
+
) else colors[0] if (
|
|
362
|
+
isinstance(colors, list) and colors and isinstance(colors[0], list)
|
|
363
|
+
) else None
|
|
364
|
+
if isinstance(raw_color, list) and len(raw_color) >= 3:
|
|
365
|
+
paint_base = [round(max(0.0, min(1.0, float(value))), 4) for value in raw_color[:3]] + [1.0]
|
|
366
|
+
except Exception:
|
|
367
|
+
pass
|
|
368
|
+
selected_part_index = build_selected_part_index(root.name, config, graph, cache, parts) if config else None
|
|
369
|
+
selected_summary = None
|
|
370
|
+
if selected_part_index is not None:
|
|
371
|
+
if part_index_path is None or bridge_plan_key is None or source_sha256 is None:
|
|
372
|
+
raise RuntimeError("selected-part index output and Bridge identity are required")
|
|
373
|
+
selected_part_index["parseFailures"] = errors[:20]
|
|
374
|
+
selected_part_index["parseCounts"] = {
|
|
375
|
+
f"{cache.receipt(path)['root']}:{cache.receipt(path)['path']}": count
|
|
376
|
+
for path, count in sorted(cache.parse_counts.items(), key=lambda row: str(row[0]))
|
|
377
|
+
}
|
|
378
|
+
index_receipt = write_bound_selected_part_index(selected_part_index, part_index_path,
|
|
379
|
+
bridge_plan_key, source_sha256,
|
|
380
|
+
common_closure_sha256)
|
|
381
|
+
selected_summary = selected_part_summary(selected_part_index, index_receipt)
|
|
382
|
+
configuration_receipt = cache.receipt(config) if config else None
|
|
383
|
+
return wanted, {"file": config.name if config else None,
|
|
384
|
+
"path": configuration_receipt["path"] if configuration_receipt else None,
|
|
385
|
+
"sha256": configuration_receipt["sha256"] if configuration_receipt else None,
|
|
386
|
+
"sizeBytes": configuration_receipt["sizeBytes"] if configuration_receipt else None,
|
|
387
|
+
"parts": [part["name"] for part in chosen],
|
|
388
|
+
"slotClosure": slot_closure, "selectedMeshes": sorted(wanted),
|
|
389
|
+
"meshes": len(wanted), "explicitOverrides": len(overrides),
|
|
390
|
+
"selectedPartSummary": selected_summary,
|
|
391
|
+
"paintBaseColor": paint_base,
|
|
392
|
+
"semanticCoverage": {key: bool(re.search(pattern, semantic)) for key, pattern in {
|
|
393
|
+
"body": r"body|shell|frame|chassis", "cabin": r"seat|interior|cabin",
|
|
394
|
+
"steering": r"steer", "wheels": r"wheel|rim|tire|tyre", "engine": r"engine|motor"}.items()},
|
|
395
|
+
"jbeamParseFailures": errors[:20]}
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def filter_configuration(objects: list[bpy.types.Object], wanted: set[str],
|
|
399
|
+
has_dedicated_wheels: bool,
|
|
400
|
+
configuration_hint: str | None = None) -> tuple[list[bpy.types.Object], list[str]]:
|
|
401
|
+
present = {obj.name for obj in objects}
|
|
402
|
+
# Wheel geometry is often authored directly in the main DAE and never
|
|
403
|
+
# repeated as a JBeam flexbody, so an exact config-only filter can delete
|
|
404
|
+
# every wheel (RX-7). When the selected trim supplies dedicated wheel DAEs
|
|
405
|
+
# (992 GT3 RS), those are authoritative and the unrelated base wheels must
|
|
406
|
+
# be excluded. Otherwise retain the main DAE's authored wheel geometry.
|
|
407
|
+
def configured(obj: bpy.types.Object) -> bool:
|
|
408
|
+
# Blender may disambiguate equal source names with a numeric suffix.
|
|
409
|
+
# That must not disconnect a JBeam-selected flexbody from its authored
|
|
410
|
+
# DAE mesh.
|
|
411
|
+
return obj.name in wanted or re.sub(r"\.\d+$", "", obj.name) in wanted
|
|
412
|
+
|
|
413
|
+
for obj in objects:
|
|
414
|
+
obj["helix_configured_mesh"] = configured(obj)
|
|
415
|
+
|
|
416
|
+
normalized_hint = re.sub(r"[^a-z0-9]", "", (configuration_hint or "").lower())
|
|
417
|
+
|
|
418
|
+
def cross_variant_source(obj: bpy.types.Object) -> bool:
|
|
419
|
+
# Some trims inherit their seats/interior from a sibling body-kit DAE.
|
|
420
|
+
# Importing that whole sibling stacks two exterior shells, while
|
|
421
|
+
# dropping it removes the authored H-points. Retain only semantic cabin
|
|
422
|
+
# meshes when an explicitly requested trim consumes a different
|
|
423
|
+
# body-kit variant.
|
|
424
|
+
dae = Path(str(obj.get("helix_source_dae", "")))
|
|
425
|
+
lowered_parts = [part.lower() for part in dae.parts]
|
|
426
|
+
if "body kits" not in lowered_parts:
|
|
427
|
+
return False
|
|
428
|
+
variant_tail = dae.parts[lowered_parts.index("body kits") + 1:]
|
|
429
|
+
normalized_variant = re.sub(r"[^a-z0-9]", "", "/".join(variant_tail).lower())
|
|
430
|
+
return bool(normalized_hint and normalized_hint not in normalized_variant)
|
|
431
|
+
|
|
432
|
+
def cross_variant_interior(obj: bpy.types.Object) -> bool:
|
|
433
|
+
return cross_variant_source(obj) and bool(INTERIOR.search(obj.name))
|
|
434
|
+
|
|
435
|
+
def inactive_damage_variant(obj: bpy.types.Object) -> bool:
|
|
436
|
+
# JBeam flexbody lists can include the replacement mesh used only after
|
|
437
|
+
# breakage. Exporting it alongside the intact part produces cracked
|
|
438
|
+
# glass and doubled/deformed panels on a freshly imported vehicle.
|
|
439
|
+
return bool(re.search(r"(?:^|[_-])(?:dmg|damage|broken|deformed)(?:[_-]|$)", obj.name, re.I))
|
|
440
|
+
|
|
441
|
+
configured_wheels = {obj.name for obj in objects if road_wheel_signal(obj) and (
|
|
442
|
+
not has_dedicated_wheels
|
|
443
|
+
or TYRE.search(obj.name)
|
|
444
|
+
# A dedicated axle DAE is an authored source library, not a placed road
|
|
445
|
+
# wheel. Retain its candidates until axle expansion can choose the
|
|
446
|
+
# configured trim, instantiate four corners, and prune the rest.
|
|
447
|
+
or "wheels" in [part.lower() for part in Path(str(obj.get("helix_source_dae", ""))).parts]
|
|
448
|
+
)}
|
|
449
|
+
keep = [obj for obj in objects if not inactive_damage_variant(obj) and (cross_variant_interior(obj) or (
|
|
450
|
+
not cross_variant_source(obj) and (
|
|
451
|
+
configured(obj) or obj.name in configured_wheels or BRAKE_HARDWARE.search(obj.name)
|
|
452
|
+
)
|
|
453
|
+
))]
|
|
454
|
+
missing = sorted(wanted - present)
|
|
455
|
+
for obj in list(objects):
|
|
456
|
+
if obj not in keep:
|
|
457
|
+
bpy.data.objects.remove(obj, do_unlink=True)
|
|
458
|
+
if len(keep) < 5:
|
|
459
|
+
raise RuntimeError(f"default configuration retained only {len(keep)} authored mesh nodes")
|
|
460
|
+
return keep, missing
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def arguments() -> argparse.Namespace:
|
|
464
|
+
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
|
|
465
|
+
parser = argparse.ArgumentParser()
|
|
466
|
+
parser.add_argument("--source-root", required=True)
|
|
467
|
+
parser.add_argument("--vehicle-root", required=True)
|
|
468
|
+
parser.add_argument("--dae", action="append", required=True)
|
|
469
|
+
parser.add_argument("--output", required=True)
|
|
470
|
+
parser.add_argument("--receipt", required=True)
|
|
471
|
+
parser.add_argument("--palette", required=True)
|
|
472
|
+
parser.add_argument("--material-root", action="append", default=[])
|
|
473
|
+
parser.add_argument("--configuration-hint")
|
|
474
|
+
parser.add_argument("--part-index", required=True)
|
|
475
|
+
parser.add_argument("--bridge-plan-key", required=True)
|
|
476
|
+
parser.add_argument("--source-sha", required=True)
|
|
477
|
+
parser.add_argument("--common-closure")
|
|
478
|
+
parser.add_argument("--mirror-z", action="store_true")
|
|
479
|
+
return parser.parse_args(argv)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def parse_materials(root: Path, default_tier: str, vehicle_root: Path | None = None) -> dict:
|
|
483
|
+
parsed = parse_material_tree(root, default_tier, vehicle_root)
|
|
484
|
+
rows = parsed["materials"]
|
|
485
|
+
aliases = parsed["aliases"]
|
|
486
|
+
block_re = re.compile(r"(?:singleton\s+)?Material\s*\([^)]*\)\s*\{(.*?)\};", re.I | re.S)
|
|
487
|
+
for path in bounded_asset_paths(
|
|
488
|
+
[root], {"materials.cs": MATERIAL_SCAN_LIMIT},
|
|
489
|
+
exact_names={"materials.cs": "materials.cs"}):
|
|
490
|
+
text = path.read_text(errors="ignore")
|
|
491
|
+
for block in block_re.findall(text):
|
|
492
|
+
mapped = re.search(r'mapTo\s*=\s*"([^"]+)"', block, re.I)
|
|
493
|
+
if not mapped:
|
|
494
|
+
continue
|
|
495
|
+
|
|
496
|
+
def indexed(field: str) -> str | None:
|
|
497
|
+
values = [(int(index), value) for index, value in re.findall(
|
|
498
|
+
rf'{field}\s*\[(\d+)\]\s*=\s*"([^"]+)"', block, re.I)]
|
|
499
|
+
return max(values, default=(0, None))[1]
|
|
500
|
+
|
|
501
|
+
color_match = re.search(r'diffuseColor\s*\[\d+\]\s*=\s*"([\d.\s-]+)"', block, re.I)
|
|
502
|
+
power_match = re.search(r'specularPower\s*\[\d+\]\s*=\s*"?([\d.]+)', block, re.I)
|
|
503
|
+
power = float(power_match.group(1)) if power_match else 32.0
|
|
504
|
+
identity = mapped.group(1).lower()
|
|
505
|
+
base = indexed("diffuseMap")
|
|
506
|
+
normal = indexed("normalMap")
|
|
507
|
+
roughness_map = indexed("roughnessMap")
|
|
508
|
+
metallic_map = indexed("metallicMap")
|
|
509
|
+
rows[identity] = {
|
|
510
|
+
"key": mapped.group(1), "mapTo": mapped.group(1), "stageCount": 0, "stages": [],
|
|
511
|
+
"maps": {key: value for key, value in {"baseColor": base, "normal": normal,
|
|
512
|
+
"roughness": roughness_map, "metallic": metallic_map}.items() if value},
|
|
513
|
+
"orderedMaps": [], "factors": {},
|
|
514
|
+
"base": base,
|
|
515
|
+
"paletteMask": None,
|
|
516
|
+
"normal": normal,
|
|
517
|
+
"roughnessMap": roughness_map,
|
|
518
|
+
"metallicMap": metallic_map,
|
|
519
|
+
"color": [float(value) for value in color_match.group(1).split()] if color_match else None,
|
|
520
|
+
"metallic": 0.75 if re.search(r'dynamicCubemap\s*=\s*(?:true|"1")', block, re.I) else 0.0,
|
|
521
|
+
"roughness": max(0.08, min(0.9, 1.0 - power / 160.0)),
|
|
522
|
+
"alpha": {"mode": "BLEND" if re.search(r'translucent\s*=\s*(?:true|"1")', block, re.I) else "OPAQUE",
|
|
523
|
+
"source": "materials.cs"},
|
|
524
|
+
"provenance": {"declarationTier": default_tier,
|
|
525
|
+
"declarationFile": str(path.relative_to(root)).replace(os.sep, "/"),
|
|
526
|
+
"declarationKey": mapped.group(1), "mapTo": mapped.group(1)},
|
|
527
|
+
}
|
|
528
|
+
aliases[identity] = identity
|
|
529
|
+
return {"materials": rows, "aliases": aliases, "diagnostics": parsed["diagnostics"]}
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def texture_index(root: Path) -> dict[str, list[Path]]:
|
|
533
|
+
result: dict[str, list[Path]] = {}
|
|
534
|
+
extensions = {extension: TEXTURE_SCAN_LIMIT for extension in
|
|
535
|
+
(".dds", ".png", ".jpg", ".jpeg", ".tga", ".bmp")}
|
|
536
|
+
for path in bounded_asset_paths([root], extensions):
|
|
537
|
+
result.setdefault(path.name.lower(), []).append(path)
|
|
538
|
+
return result
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def material_row(definitions: dict[str, tuple[dict, Path]], name: str) -> tuple[dict, Path] | None:
|
|
542
|
+
key = name.lower()
|
|
543
|
+
candidates = [key, key.removeprefix("m_"), re.sub(r"\.\d{3}$", "", key)]
|
|
544
|
+
return next((definitions[value] for value in candidates if value in definitions), None)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def connect_image(nodes, links, shader, socket: str, path: Path, non_color: bool = False,
|
|
548
|
+
link_alpha: bool = False) -> bool:
|
|
549
|
+
try:
|
|
550
|
+
image = bpy.data.images.load(str(path), check_existing=True)
|
|
551
|
+
except Exception as error:
|
|
552
|
+
print(f"[beamng] texture refused {path}: {error}")
|
|
553
|
+
return False
|
|
554
|
+
if non_color:
|
|
555
|
+
image.colorspace_settings.name = "Non-Color"
|
|
556
|
+
texture = nodes.new("ShaderNodeTexImage")
|
|
557
|
+
texture.image = image
|
|
558
|
+
if socket == "Normal":
|
|
559
|
+
normal = nodes.new("ShaderNodeNormalMap")
|
|
560
|
+
links.new(texture.outputs["Color"], normal.inputs["Color"])
|
|
561
|
+
links.new(normal.outputs["Normal"], shader.inputs["Normal"])
|
|
562
|
+
else:
|
|
563
|
+
links.new(texture.outputs["Color"], shader.inputs[socket])
|
|
564
|
+
# BeamNG commonly stores masks/palette data in the alpha channel of an
|
|
565
|
+
# otherwise opaque surface. Treating every image alpha as transparency
|
|
566
|
+
# made paint, dashboards and upholstery ghost through the car in HELIX.
|
|
567
|
+
# Only real glazing is allowed to drive the Principled alpha socket.
|
|
568
|
+
if link_alpha and "Alpha" in texture.outputs and "Alpha" in shader.inputs:
|
|
569
|
+
links.new(texture.outputs["Alpha"], shader.inputs["Alpha"])
|
|
570
|
+
return True
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
VEHICLE_MATERIAL_ROLES = (
|
|
574
|
+
("tyre", re.compile(r"tyre|tire|tread", re.I), "tire-rubber", (0.012, 0.012, 0.014, 1.0)),
|
|
575
|
+
("glass", re.compile(r"glass|window|windscreen|windshield", re.I), "vehicle-glass", (0.025, 0.035, 0.045, 1.0)),
|
|
576
|
+
("brake-caliper", re.compile(r"caliper", re.I), "brake-caliper-painted", (0.55, 0.025, 0.015, 1.0)),
|
|
577
|
+
("brake-rotor", re.compile(r"brake|rotor|disc", re.I), "brake-rotor", (0.18, 0.19, 0.20, 1.0)),
|
|
578
|
+
("headlamp", re.compile(r"head.?l(?:amp|ight)|light[_-]?f", re.I), "headlamp-lens", (0.78, 0.86, 1.0, 1.0)),
|
|
579
|
+
("taillamp", re.compile(r"tail.?l(?:amp|ight)|brake.?light|light[_-]?r", re.I), "taillamp-lens", (0.52, 0.01, 0.008, 1.0)),
|
|
580
|
+
("carbon", re.compile(r"carbon|cfiber|(?:^|[_-])cf(?:[_-]|$)", re.I), "carbon-fiber", (0.012, 0.014, 0.016, 1.0)),
|
|
581
|
+
("carpet", re.compile(r"carpet|floor.?mat", re.I), "vehicle-carpet", (0.012, 0.013, 0.015, 1.0)),
|
|
582
|
+
("leather", re.compile(r"leather|upholstery|seat", re.I), "vehicle-leather", (0.024, 0.027, 0.032, 1.0)),
|
|
583
|
+
("dashboard", re.compile(r"dashboard|dash|gauges?|instrument|cluster|soft.?touch", re.I),
|
|
584
|
+
"dashboard-soft-touch", (0.018, 0.020, 0.024, 1.0)),
|
|
585
|
+
("interior", re.compile(r"interior|plastic|console|door.?card|trim|fabric|speaker|symbol", re.I),
|
|
586
|
+
"vehicle-interior-plastic", (0.018, 0.020, 0.024, 1.0)),
|
|
587
|
+
("underbody", re.compile(r"underbody|under.?tray|chassis|subframe|suspension", re.I),
|
|
588
|
+
"underbody-coating", (0.014, 0.015, 0.017, 1.0)),
|
|
589
|
+
("exhaust", re.compile(r"exhaust|muffler|tail.?pipe", re.I), "stainless-steel", (0.24, 0.25, 0.27, 1.0)),
|
|
590
|
+
("chrome", re.compile(r"chrome", re.I), "chrome", (0.62, 0.65, 0.68, 1.0)),
|
|
591
|
+
("wheel", re.compile(r"wheel|rim|alloy|aluminium|aluminum", re.I), "aluminum-brushed", (0.28, 0.30, 0.32, 1.0)),
|
|
592
|
+
("paint", re.compile(r"paint|body|bodyshell|main|exterior", re.I), "vehicle-paint-metallic", (0.42, 0.025, 0.035, 1.0)),
|
|
593
|
+
("metal", re.compile(r"metal|steel|iron", re.I), "stainless-steel", (0.22, 0.24, 0.26, 1.0)),
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
# Contracts whose `families[]` carry the fields this converter reads (id, definitionVersion,
|
|
598
|
+
# physical.sizeMeters, shader.defaults). /4 is the current asset pack; /3 stays accepted.
|
|
599
|
+
SUPPORTED_PALETTE_CONTRACTS = ("helix-palette/4", "helix-palette/3")
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def load_vehicle_palette(path: Path) -> tuple[str, dict[str, dict]]:
|
|
603
|
+
try:
|
|
604
|
+
document = json.loads(path.read_text())
|
|
605
|
+
except Exception as error:
|
|
606
|
+
raise RuntimeError(f"Palette input is not valid JSON: {error}")
|
|
607
|
+
contract = document.get("contract")
|
|
608
|
+
if contract not in SUPPORTED_PALETTE_CONTRACTS or not isinstance(document.get("families"), list):
|
|
609
|
+
raise RuntimeError(f"Palette input must use contract {' or '.join(SUPPORTED_PALETTE_CONTRACTS)} "
|
|
610
|
+
"with a families array")
|
|
611
|
+
families = {row.get("id"): row for row in document["families"] if isinstance(row, dict)}
|
|
612
|
+
required = {row[2] for row in VEHICLE_MATERIAL_ROLES} | {"vehicle-interior-plastic"}
|
|
613
|
+
for family_id in required:
|
|
614
|
+
family = families.get(family_id)
|
|
615
|
+
defaults = family.get("shader", {}).get("defaults", {}) if family else {}
|
|
616
|
+
if not family or not isinstance(family.get("definitionVersion"), str):
|
|
617
|
+
raise RuntimeError(f"Palette has no versioned reusable family {family_id}")
|
|
618
|
+
for field in ("roughness", "metallic"):
|
|
619
|
+
value = defaults.get(field)
|
|
620
|
+
if not isinstance(value, (int, float)) or not 0 <= value <= 1:
|
|
621
|
+
raise RuntimeError(f"Palette family {family_id} has invalid {field}")
|
|
622
|
+
return contract, families
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def vehicle_material_role(name: str) -> tuple[str, str, tuple]:
|
|
626
|
+
return next(((role, family, tint) for role, pattern, family, tint in VEHICLE_MATERIAL_ROLES
|
|
627
|
+
if pattern.search(name)),
|
|
628
|
+
("generic", "vehicle-interior-plastic", (0.12, 0.13, 0.15, 1.0)))
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def helix_material_binding(family: dict, source_id: str, tint: tuple | None,
|
|
632
|
+
authored_maps: bool, alpha_mode: str) -> dict:
|
|
633
|
+
physical = family.get("physical", {})
|
|
634
|
+
size = physical.get("sizeMeters")
|
|
635
|
+
physical_size = size if isinstance(size, list) and len(size) == 2 else None
|
|
636
|
+
overrides = {}
|
|
637
|
+
if tint is not None:
|
|
638
|
+
overrides["tint"] = "#" + "".join(f"{max(0, min(255, round(channel*255))):02X}" for channel in tint[:3])
|
|
639
|
+
return {
|
|
640
|
+
"contract": "helix-material-binding/1",
|
|
641
|
+
"familyId": family["id"],
|
|
642
|
+
"familyVersion": family["definitionVersion"],
|
|
643
|
+
"presetId": None,
|
|
644
|
+
"overrides": overrides,
|
|
645
|
+
"uv": {"uvSet": 0, "offset": [0.0, 0.0], "scale": [1.0, 1.0], "rotationRadians": 0.0,
|
|
646
|
+
"physicalSizeMeters": physical_size},
|
|
647
|
+
"preserved": {"identityBearingDetails": authored_maps, "sourceMaterialId": source_id,
|
|
648
|
+
"maskBindings": [], "decals": []},
|
|
649
|
+
"renderState": {"alphaMode": alpha_mode, "alphaCutoff": 0.5, "doubleSided": False,
|
|
650
|
+
"depthWrite": True, "depthTest": True, "blendMode": "normal"},
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def restore_materials(roots: list[Path], palette_path: Path, vehicle_root: Path,
|
|
655
|
+
configured_paint: list[float] | None = None) -> dict:
|
|
656
|
+
palette_contract, palette = load_vehicle_palette(palette_path)
|
|
657
|
+
definitions: dict[str, tuple[dict, Path]] = {}
|
|
658
|
+
source_diagnostics = {key: [] for key in ("parseFailures", "shadowedDeclarations", "stageConflicts",
|
|
659
|
+
"dynamicTextures", "sentinels", "unsupportedMaps")}
|
|
660
|
+
# Shared/base-game dependencies first; the mod's own definitions win when
|
|
661
|
+
# both bind the same mapTo name.
|
|
662
|
+
for index in reversed(range(len(roots))):
|
|
663
|
+
root = roots[index]
|
|
664
|
+
tier = "mod" if index == 0 else "basegame"
|
|
665
|
+
parsed = parse_materials(root, tier, vehicle_root if index == 0 else None)
|
|
666
|
+
for alias, identity in parsed["aliases"].items():
|
|
667
|
+
row = parsed["materials"].get(identity)
|
|
668
|
+
if row:
|
|
669
|
+
definitions[alias] = (row, root)
|
|
670
|
+
for key in source_diagnostics:
|
|
671
|
+
source_diagnostics[key].extend(parsed["diagnostics"].get(key, []))
|
|
672
|
+
texture_chain = TextureChain([("mod", roots[0]), *[("basegame", root) for root in roots[1:]]])
|
|
673
|
+
resolved = mapped = factor_defaults = 0
|
|
674
|
+
role_counts: dict[str, int] = {}
|
|
675
|
+
bindings = []
|
|
676
|
+
texture_resolutions, unresolved_textures, ambiguous_textures = [], [], []
|
|
677
|
+
|
|
678
|
+
def material_texture(row: dict, role: str, material_name: str) -> Path | None:
|
|
679
|
+
raw = row.get("maps", {}).get(role)
|
|
680
|
+
resolution = texture_chain.resolve(raw)
|
|
681
|
+
if raw is None:
|
|
682
|
+
return None
|
|
683
|
+
portable = {key: value for key, value in resolution.items() if key != "path"}
|
|
684
|
+
portable.update({"material": material_name, "role": role})
|
|
685
|
+
if resolution.get("resolved"):
|
|
686
|
+
hit = Path(resolution["path"])
|
|
687
|
+
root = next((candidate for _tier, candidate in texture_chain.roots
|
|
688
|
+
if candidate == hit or candidate in hit.parents), roots[0])
|
|
689
|
+
portable["path"] = str(hit.relative_to(root)).replace(os.sep, "/")
|
|
690
|
+
texture_resolutions.append(portable)
|
|
691
|
+
return hit
|
|
692
|
+
if resolution.get("reason") == "ambiguous":
|
|
693
|
+
portable["candidates"] = [Path(value).name for value in resolution.get("candidates", [])]
|
|
694
|
+
ambiguous_textures.append(portable)
|
|
695
|
+
elif not resolution.get("dynamic") and not resolution.get("sentinel"):
|
|
696
|
+
unresolved_textures.append(portable)
|
|
697
|
+
return None
|
|
698
|
+
|
|
699
|
+
for material in bpy.data.materials:
|
|
700
|
+
material.use_nodes = True
|
|
701
|
+
shader = next((node for node in material.node_tree.nodes if node.type == "BSDF_PRINCIPLED"), None)
|
|
702
|
+
if not shader:
|
|
703
|
+
continue
|
|
704
|
+
material_binding = material_row(definitions, material.name)
|
|
705
|
+
row, owner_root = material_binding if material_binding is not None else (None, roots[0])
|
|
706
|
+
semantic = material.name.lower()
|
|
707
|
+
is_glazing = bool(re.search(r"glass|window|windscreen|windshield", semantic))
|
|
708
|
+
role, family_id, tint = vehicle_material_role(semantic)
|
|
709
|
+
configured_paint_active = role == "paint" and bool(configured_paint and len(configured_paint) >= 3)
|
|
710
|
+
if configured_paint_active:
|
|
711
|
+
tint = tuple(configured_paint[:3]) + (1.0,)
|
|
712
|
+
is_transmissive = is_glazing or role in {"headlamp", "taillamp"}
|
|
713
|
+
family = palette[family_id]
|
|
714
|
+
source_color = False
|
|
715
|
+
incomplete_paint = False
|
|
716
|
+
if row:
|
|
717
|
+
resolved += 1
|
|
718
|
+
color = row.get("color")
|
|
719
|
+
if isinstance(color, list) and len(color) >= 3 and not configured_paint_active:
|
|
720
|
+
rgba = tuple(float(value) for value in color[:4])
|
|
721
|
+
if len(rgba) == 3:
|
|
722
|
+
rgba += (1.0,)
|
|
723
|
+
shader.inputs["Base Color"].default_value = rgba
|
|
724
|
+
source_color = True
|
|
725
|
+
if isinstance(row.get("metallic"), (int, float)):
|
|
726
|
+
shader.inputs["Metallic IOR Level" if "Metallic IOR Level" in shader.inputs else "Metallic"].default_value = float(row["metallic"])
|
|
727
|
+
if isinstance(row.get("roughness"), (int, float)):
|
|
728
|
+
shader.inputs["Roughness"].default_value = float(row["roughness"])
|
|
729
|
+
base = material_texture(row, "baseColor", material.name)
|
|
730
|
+
palette_mask = material_texture(row, "colorPalette", material.name)
|
|
731
|
+
normal = material_texture(row, "normal", material.name)
|
|
732
|
+
roughness_map = material_texture(row, "roughness", material.name)
|
|
733
|
+
metallic_map = material_texture(row, "metallic", material.name)
|
|
734
|
+
attached = False
|
|
735
|
+
# A configured BeamNG paint is assembled from multi-channel palette
|
|
736
|
+
# masks in-engine. A portable glTF cannot reproduce that shader by
|
|
737
|
+
# wiring the mask itself as RGB; doing so yielded disconnected
|
|
738
|
+
# white/red panels. Keep normal/roughness detail, but use the
|
|
739
|
+
# selected paint as the portable base-color factor.
|
|
740
|
+
if base and not configured_paint_active:
|
|
741
|
+
attached |= connect_image(material.node_tree.nodes, material.node_tree.links, shader, "Base Color", base,
|
|
742
|
+
link_alpha=is_transmissive)
|
|
743
|
+
elif palette_mask and role == "paint" and not configured_paint_active:
|
|
744
|
+
# glTF multiplies baseColorTexture by baseColorFactor, so a
|
|
745
|
+
# BeamNG palette mask can stay authored while the Palette tint
|
|
746
|
+
# supplies the missing in-engine color factor.
|
|
747
|
+
attached |= connect_image(material.node_tree.nodes, material.node_tree.links, shader, "Base Color", palette_mask)
|
|
748
|
+
incomplete_paint = True
|
|
749
|
+
if normal:
|
|
750
|
+
attached |= connect_image(material.node_tree.nodes, material.node_tree.links, shader, "Normal", normal, True)
|
|
751
|
+
if roughness_map:
|
|
752
|
+
attached |= connect_image(material.node_tree.nodes, material.node_tree.links, shader, "Roughness", roughness_map, True)
|
|
753
|
+
if metallic_map:
|
|
754
|
+
attached |= connect_image(material.node_tree.nodes, material.node_tree.links, shader,
|
|
755
|
+
"Metallic IOR Level" if "Metallic IOR Level" in shader.inputs else "Metallic",
|
|
756
|
+
metallic_map, True)
|
|
757
|
+
mapped += int(attached)
|
|
758
|
+
# `translucent` in BeamNG is also used for palette masks, decals,
|
|
759
|
+
# illuminated gauges and chrome. Those surfaces are not transparent
|
|
760
|
+
# in the final vehicle. Restrict blend materials to actual glazing.
|
|
761
|
+
if is_transmissive:
|
|
762
|
+
shader.inputs["Alpha"].default_value = min(shader.inputs["Alpha"].default_value, 0.42)
|
|
763
|
+
if hasattr(material, "surface_render_method"):
|
|
764
|
+
material.surface_render_method = "DITHERED"
|
|
765
|
+
else:
|
|
766
|
+
shader.inputs["Alpha"].default_value = 1.0
|
|
767
|
+
elif re.search(r"glass|window|windscreen|windshield", semantic):
|
|
768
|
+
shader.inputs["Base Color"].default_value = (0.025, 0.035, 0.045, 1.0)
|
|
769
|
+
shader.inputs["Roughness"].default_value = 0.08
|
|
770
|
+
shader.inputs["Alpha"].default_value = 0.26
|
|
771
|
+
if hasattr(material, "surface_render_method"):
|
|
772
|
+
material.surface_render_method = "DITHERED"
|
|
773
|
+
elif re.search(r"tyre|tire|rubber", semantic):
|
|
774
|
+
shader.inputs["Base Color"].default_value = (0.012, 0.012, 0.014, 1.0)
|
|
775
|
+
shader.inputs["Roughness"].default_value = 0.82
|
|
776
|
+
elif re.search(r"chrome|wheel|rim|metal|exhaust", semantic):
|
|
777
|
+
shader.inputs["Metallic IOR Level" if "Metallic IOR Level" in shader.inputs else "Metallic"].default_value = 0.82
|
|
778
|
+
shader.inputs["Roughness"].default_value = 0.25
|
|
779
|
+
elif re.search(r"paint|body|main", semantic):
|
|
780
|
+
shader.inputs["Base Color"].default_value = (0.42, 0.025, 0.018, 1.0)
|
|
781
|
+
shader.inputs["Metallic IOR Level" if "Metallic IOR Level" in shader.inputs else "Metallic"].default_value = 0.18
|
|
782
|
+
shader.inputs["Roughness"].default_value = 0.24
|
|
783
|
+
if "Coat Weight" in shader.inputs:
|
|
784
|
+
shader.inputs["Coat Weight"].default_value = 1.0
|
|
785
|
+
shader.inputs["Coat Roughness"].default_value = 0.06
|
|
786
|
+
else:
|
|
787
|
+
shader.inputs["Roughness"].default_value = 0.55
|
|
788
|
+
# A large class of older BeamNG interiors declares neutral white
|
|
789
|
+
# factors and relies on an in-engine skin tint. HELIX has no such tint
|
|
790
|
+
# target, so preserve embedded maps but close the untextured factors to
|
|
791
|
+
# the selected black cabin instead of rendering white plastic/leather.
|
|
792
|
+
if not shader.inputs["Base Color"].is_linked:
|
|
793
|
+
if re.search(r"leather|upper_leather|interior_d", semantic):
|
|
794
|
+
shader.inputs["Base Color"].default_value = (0.024, 0.027, 0.032, 1.0)
|
|
795
|
+
shader.inputs["Roughness"].default_value = 0.42
|
|
796
|
+
elif re.search(r"plastic|rubber|carpet|fabric|speakers|symbols", semantic):
|
|
797
|
+
shader.inputs["Base Color"].default_value = (0.018, 0.020, 0.024, 1.0)
|
|
798
|
+
shader.inputs["Roughness"].default_value = 0.58
|
|
799
|
+
if re.search(r"(?:int|gauges?)_chrome", semantic):
|
|
800
|
+
shader.inputs["Metallic IOR Level" if "Metallic IOR Level" in shader.inputs else "Metallic"].default_value = 0.88
|
|
801
|
+
shader.inputs["Roughness"].default_value = 0.2
|
|
802
|
+
authored_maps = any(shader.inputs[name].is_linked for name in (
|
|
803
|
+
"Base Color", "Normal", "Roughness",
|
|
804
|
+
"Metallic IOR Level" if "Metallic IOR Level" in shader.inputs else "Metallic"))
|
|
805
|
+
applied_tint = None
|
|
806
|
+
# BeamNG paint textures are frequently grayscale/palette masks whose
|
|
807
|
+
# actual color comes from the selected `.pc` configuration. Preserve
|
|
808
|
+
# the authored image/UV and supply that missing multiplicative factor.
|
|
809
|
+
palette_authoritative = role in {"tyre", "glass", "brake-rotor", "carbon", "carpet",
|
|
810
|
+
"dashboard", "underbody", "exhaust", "wheel", "interior", "leather"}
|
|
811
|
+
if role != "generic" and (configured_paint_active or not source_color or palette_authoritative):
|
|
812
|
+
shader.inputs["Base Color"].default_value = tint
|
|
813
|
+
applied_tint = tint
|
|
814
|
+
if not authored_maps or incomplete_paint or configured_paint_active:
|
|
815
|
+
defaults = family["shader"]["defaults"]
|
|
816
|
+
if configured_paint_active or not source_color or incomplete_paint:
|
|
817
|
+
shader.inputs["Base Color"].default_value = tint
|
|
818
|
+
applied_tint = tint
|
|
819
|
+
shader.inputs["Metallic IOR Level" if "Metallic IOR Level" in shader.inputs else "Metallic"].default_value = float(defaults["metallic"])
|
|
820
|
+
shader.inputs["Roughness"].default_value = float(defaults["roughness"])
|
|
821
|
+
factor_defaults += 1
|
|
822
|
+
if role == "paint" and "Coat Weight" in shader.inputs:
|
|
823
|
+
shader.inputs["Coat Weight"].default_value = 1.0
|
|
824
|
+
shader.inputs["Coat Roughness"].default_value = 0.06
|
|
825
|
+
if role == "glass":
|
|
826
|
+
shader.inputs["Base Color"].default_value = (0.025, 0.04, 0.055, 1.0)
|
|
827
|
+
shader.inputs["Alpha"].default_value = 0.28
|
|
828
|
+
shader.inputs["Roughness"].default_value = min(0.12, shader.inputs["Roughness"].default_value)
|
|
829
|
+
transmission = shader.inputs.get("Transmission Weight") or shader.inputs.get("Transmission")
|
|
830
|
+
if transmission is not None:
|
|
831
|
+
transmission.default_value = 0.72
|
|
832
|
+
ior = shader.inputs.get("IOR")
|
|
833
|
+
if ior is not None:
|
|
834
|
+
ior.default_value = 1.45
|
|
835
|
+
if hasattr(material, "surface_render_method"):
|
|
836
|
+
material.surface_render_method = "DITHERED"
|
|
837
|
+
declared_alpha = row.get("alpha", {}) if row else {}
|
|
838
|
+
alpha_mode = str(declared_alpha.get("mode", "blend" if is_transmissive else "opaque")).lower()
|
|
839
|
+
if alpha_mode == "blend" and not is_transmissive:
|
|
840
|
+
# Preserve the declaration in provenance but retain the existing
|
|
841
|
+
# portable opaque fallback for palette/decal materials until the
|
|
842
|
+
# runtime has the matching staged compositor.
|
|
843
|
+
alpha_mode = "opaque"
|
|
844
|
+
bindings.append({"name": material.name, "role": role, "binding": helix_material_binding(
|
|
845
|
+
family, material.name, applied_tint, authored_maps, alpha_mode),
|
|
846
|
+
"source": {"provenance": row.get("provenance") if row else None,
|
|
847
|
+
"stages": row.get("stages", []) if row else [],
|
|
848
|
+
"factors": row.get("factors", {}) if row else {},
|
|
849
|
+
"alpha": declared_alpha}})
|
|
850
|
+
material["helix_vehicle_role"] = role
|
|
851
|
+
role_counts[role] = role_counts.get(role, 0) + 1
|
|
852
|
+
total = len(bpy.data.materials)
|
|
853
|
+
declaration_provenance = sorted({json.dumps(row[0].get("provenance"), sort_keys=True)
|
|
854
|
+
for row in definitions.values() if row[0].get("provenance")})
|
|
855
|
+
return {"definitions": len({id(row[0]) for row in definitions.values()}), "resolved": resolved, "mapped": mapped, "materials": total,
|
|
856
|
+
"paletteContract": palette_contract, "bound": total, "factorDefaults": factor_defaults,
|
|
857
|
+
"familyVersions": {family_id: family["definitionVersion"] for family_id, family in palette.items()},
|
|
858
|
+
"renderReady": total, "renderReadyCoverage": 1.0 if total else 0.0,
|
|
859
|
+
"roles": role_counts, "bindings": bindings,
|
|
860
|
+
"declarationProvenance": [json.loads(value) for value in declaration_provenance],
|
|
861
|
+
**source_diagnostics, "textureResolutions": texture_resolutions,
|
|
862
|
+
"unresolvedTextures": unresolved_textures, "ambiguousTextures": ambiguous_textures}
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
def meshes() -> list[bpy.types.Object]:
|
|
866
|
+
return [obj for obj in bpy.context.scene.objects if obj.type == "MESH" and len(obj.data.polygons) > 0]
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
def world_bounds(obj: bpy.types.Object) -> tuple[Vector, Vector]:
|
|
870
|
+
points = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
|
|
871
|
+
return Vector((min(point.x for point in points), min(point.y for point in points), min(point.z for point in points))), \
|
|
872
|
+
Vector((max(point.x for point in points), max(point.y for point in points), max(point.z for point in points)))
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
def bounds_of(objects: list[bpy.types.Object]) -> tuple[Vector, Vector]:
|
|
876
|
+
values = [world_bounds(obj) for obj in objects]
|
|
877
|
+
return Vector(tuple(min(row[0][axis] for row in values) for axis in range(3))), \
|
|
878
|
+
Vector(tuple(max(row[1][axis] for row in values) for axis in range(3)))
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
def centre(obj: bpy.types.Object) -> Vector:
|
|
882
|
+
lo, hi = world_bounds(obj)
|
|
883
|
+
return (lo + hi) * 0.5
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
def dimensions(obj: bpy.types.Object) -> Vector:
|
|
887
|
+
lo, hi = world_bounds(obj)
|
|
888
|
+
return hi - lo
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def road_wheel_signal(obj: bpy.types.Object) -> bool:
|
|
892
|
+
"""Recognize road-wheel meshes by semantic name or authored source library.
|
|
893
|
+
|
|
894
|
+
BeamNG flexbodies often call a rim by a style name (for example
|
|
895
|
+
`gavril_VSpoke_17x7_5H_F`) that contains neither `wheel` nor `rim`. The DAE
|
|
896
|
+
path remains explicit. Steering-wheel libraries are deliberately excluded.
|
|
897
|
+
"""
|
|
898
|
+
if WHEEL.search(obj.name):
|
|
899
|
+
return True
|
|
900
|
+
source = str(obj.get("helix_source_dae", "")).lower()
|
|
901
|
+
return bool(re.search(r"wheel|rim", source) and not re.search(r"steer|wheelwell", source))
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
def canonical(point: Vector) -> list[float]:
|
|
905
|
+
# Blender/BeamNG +X-left, +Z-up, -Y-forward -> glTF/HELIX +X-left, +Y-up, +Z-forward.
|
|
906
|
+
return [round(float(point.x), 4), round(float(point.z), 4), round(float(-point.y), 4)]
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
def tag_for(point: Vector, split_y: float) -> str:
|
|
910
|
+
return ("l" if point.x >= 0 else "r") + ("f" if point.y <= split_y else "r")
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
def select_wheels(objects: list[bpy.types.Object]) -> tuple[dict[str, bpy.types.Object], dict[str, bpy.types.Object]]:
|
|
914
|
+
candidates = []
|
|
915
|
+
for obj in objects:
|
|
916
|
+
if not road_wheel_signal(obj):
|
|
917
|
+
continue
|
|
918
|
+
size = dimensions(obj)
|
|
919
|
+
c = centre(obj)
|
|
920
|
+
if max(size) < 0.12 or max(size) > 1.5 or abs(c.x) < 0.25:
|
|
921
|
+
continue
|
|
922
|
+
candidates.append(obj)
|
|
923
|
+
if not candidates:
|
|
924
|
+
return {}, {}
|
|
925
|
+
ys = sorted(centre(obj).y for obj in candidates)
|
|
926
|
+
split_y = (ys[0] + ys[-1]) / 2
|
|
927
|
+
rims: dict[str, bpy.types.Object] = {}
|
|
928
|
+
tyres: dict[str, bpy.types.Object] = {}
|
|
929
|
+
for obj in candidates:
|
|
930
|
+
tag = tag_for(centre(obj), split_y)
|
|
931
|
+
target = tyres if TYRE.search(obj.name) else rims
|
|
932
|
+
if tag not in target or len(obj.data.polygons) > len(target[tag].data.polygons):
|
|
933
|
+
target[tag] = obj
|
|
934
|
+
# Some sources call the complete wheel a tyre; allow that authored mesh to be the rim visual too.
|
|
935
|
+
for tag in ("lf", "rf", "lr", "rr"):
|
|
936
|
+
if tag not in rims and tag in tyres:
|
|
937
|
+
rims[tag] = tyres[tag]
|
|
938
|
+
return rims, tyres
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
def normalize_authored_tyres(objects: list[bpy.types.Object]) -> tuple[list[str], dict, dict]:
|
|
942
|
+
"""Export exactly four instances of one rim-compatible authored tyre source."""
|
|
943
|
+
rims, _ = select_wheels(objects)
|
|
944
|
+
actual_rims = {tag: obj for tag, obj in rims.items() if not TYRE.search(obj.name)}
|
|
945
|
+
if len(actual_rims) != 4:
|
|
946
|
+
raise RuntimeError("cannot normalize authored tyres without four measured authored rim corners")
|
|
947
|
+
candidates = [obj for obj in objects if TYRE.search(obj.name)]
|
|
948
|
+
if not candidates:
|
|
949
|
+
raise RuntimeError("default configuration contains no authored tyre mesh")
|
|
950
|
+
rim_radii = {tag: max(dimensions(obj).y, dimensions(obj).z) * 0.5 for tag, obj in actual_rims.items()}
|
|
951
|
+
reference_radius = sum(rim_radii.values()) / len(rim_radii)
|
|
952
|
+
|
|
953
|
+
def compatibility(obj: bpy.types.Object) -> tuple[float, int, str] | None:
|
|
954
|
+
size = dimensions(obj)
|
|
955
|
+
radius = max(size.y, size.z) * 0.5
|
|
956
|
+
radial_roundness = abs(size.y-size.z) / max(size.y, size.z, 0.001)
|
|
957
|
+
ratio = radius / max(reference_radius, 0.001)
|
|
958
|
+
if not (0.04 <= size.x <= 0.65 and 0.18 <= radius <= 0.70
|
|
959
|
+
and radial_roundness <= 0.25 and 0.90 <= ratio <= 1.65):
|
|
960
|
+
return None
|
|
961
|
+
return abs(ratio-1.18) + radial_roundness * 0.5, -len(obj.data.polygons), obj.name
|
|
962
|
+
|
|
963
|
+
compatible = [(score, obj) for obj in candidates if (score := compatibility(obj)) is not None]
|
|
964
|
+
if not compatible:
|
|
965
|
+
measured = [(obj.name, tuple(round(value, 3) for value in dimensions(obj))) for obj in candidates]
|
|
966
|
+
raise RuntimeError(f"no authored tyre variant is compatible with the selected rims: {measured}")
|
|
967
|
+
_, source = min(compatible, key=lambda row: row[0])
|
|
968
|
+
source_identity = f"{Path(str(source.get('helix_source_dae', 'unknown'))).name}:{source.name}"
|
|
969
|
+
source_centre = centre(source)
|
|
970
|
+
source_radius = max(dimensions(source).y, dimensions(source).z) * 0.5
|
|
971
|
+
instances = []
|
|
972
|
+
for tag in ("lf", "rf", "lr", "rr"):
|
|
973
|
+
target = centre(actual_rims[tag])
|
|
974
|
+
desired_radius = max(0.22, min(0.55, rim_radii[tag] * 1.18))
|
|
975
|
+
copy = source.copy()
|
|
976
|
+
copy.data = source.data
|
|
977
|
+
copy.name = f"tyre_{tag}"
|
|
978
|
+
copy["helix_tyre_source"] = source_identity
|
|
979
|
+
bpy.context.scene.collection.objects.link(copy)
|
|
980
|
+
copy.matrix_world = (Matrix.Translation(target) @ Matrix.Scale(desired_radius / source_radius, 4)
|
|
981
|
+
@ Matrix.Translation(-source_centre)) @ copy.matrix_world
|
|
982
|
+
source_side = "l" if source_centre.x >= 0.25 else "r" if source_centre.x <= -0.25 else None
|
|
983
|
+
if ((source_side is not None and source_side != tag[0])
|
|
984
|
+
or (source_side is None and tag[0] == "r")):
|
|
985
|
+
copy.matrix_world = (Matrix.Translation(target) @ Matrix.Rotation(math.pi, 4, "Z")
|
|
986
|
+
@ Matrix.Translation(-target)) @ copy.matrix_world
|
|
987
|
+
instances.append(copy)
|
|
988
|
+
chosen_rims = set(actual_rims.values())
|
|
989
|
+
rim_sources = []
|
|
990
|
+
for tag, rim in actual_rims.items():
|
|
991
|
+
identity = f"{Path(str(rim.get('helix_source_dae', 'unknown'))).name}:{rim.name}"
|
|
992
|
+
rim["helix_rim_source"] = identity
|
|
993
|
+
rim_sources.append(identity)
|
|
994
|
+
# Configuration libraries contain centered templates and color variants.
|
|
995
|
+
# Once four measured rims have won custody, every other true road-wheel
|
|
996
|
+
# object must be removed so it cannot render beneath the vehicle.
|
|
997
|
+
for candidate in list(objects):
|
|
998
|
+
if candidate not in chosen_rims and road_wheel_signal(candidate):
|
|
999
|
+
bpy.data.objects.remove(candidate, do_unlink=True)
|
|
1000
|
+
objects[:] = meshes()
|
|
1001
|
+
receipt = {"source": source_identity, "instances": [obj.name for obj in instances]}
|
|
1002
|
+
rim_receipt = {"sources": sorted(set(rim_sources)),
|
|
1003
|
+
"instances": [f"rim_{tag}" for tag in ("lf", "rf", "lr", "rr")]}
|
|
1004
|
+
print(f"[beamng] normalized tyre custody={receipt}")
|
|
1005
|
+
print(f"[beamng] normalized rim custody={rim_receipt}")
|
|
1006
|
+
return [f"instanced one compatible authored tyre source {source_identity} to four measured rim centres",
|
|
1007
|
+
"pruned every unselected authored road-wheel template after four-corner rim custody"], receipt, rim_receipt
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
def expand_axle_wheels(objects: list[bpy.types.Object]) -> list[str]:
|
|
1011
|
+
rims, tyres = select_wheels(objects)
|
|
1012
|
+
# `select_wheels` intentionally lets a complete tyre stand in for a rim
|
|
1013
|
+
# when a source only authors complete wheels. Do not let that fallback hide
|
|
1014
|
+
# centred trim-specific rim geometry: the 992 then rendered four bare rims
|
|
1015
|
+
# and silently discarded its four correctly placed base tyres.
|
|
1016
|
+
actual_rims = {tag: obj for tag, obj in rims.items() if not TYRE.search(obj.name)}
|
|
1017
|
+
if len(actual_rims) == 4 and len(tyres) == 4:
|
|
1018
|
+
return []
|
|
1019
|
+
sources = [obj for obj in objects if road_wheel_signal(obj)]
|
|
1020
|
+
body = vehicle_shell(objects)
|
|
1021
|
+
print(f"[beamng] axle wheel expansion sources={[obj.name for obj in sources]} body={body.name if body else None}")
|
|
1022
|
+
if not sources or not body:
|
|
1023
|
+
return []
|
|
1024
|
+
lo, hi = world_bounds(body)
|
|
1025
|
+
width, length = hi.x-lo.x, hi.y-lo.y
|
|
1026
|
+
# Measured road-car wheel centres sit at roughly 41.5% of body width. The
|
|
1027
|
+
# older 38.5% envelope rule put dependency wheels too far inboard; in the
|
|
1028
|
+
# 992 the driver's line of sight went over the inner fender and straight
|
|
1029
|
+
# onto the left-front tyre.
|
|
1030
|
+
half_track = width * 0.415
|
|
1031
|
+
# Axle-pair legacy assets carry no per-corner transforms. A measured 64%
|
|
1032
|
+
# shell-length wheelbase is conservative across compact and sports-car
|
|
1033
|
+
# bodies; the older 55% rule collapsed short legacy cars below a plausible
|
|
1034
|
+
# road-car wheelbase even though their authored body proportions were sound.
|
|
1035
|
+
front_y, rear_y = lo.y + length * 0.18, hi.y - length * 0.18
|
|
1036
|
+
target_radius = max(0.28, min(0.40, length * 0.075))
|
|
1037
|
+
hub_z = lo.z + target_radius
|
|
1038
|
+
expanded, messages = [], []
|
|
1039
|
+
for tyre_role, pattern in ((False, re.compile(r"(?<!steering)(?<!steering_)(?<!steer_)wheel(?!well)|(?<!t)rim", re.I)),
|
|
1040
|
+
(True, TYRE)):
|
|
1041
|
+
matching = [obj for obj in sources if pattern.search(obj.name)]
|
|
1042
|
+
def source_axle(obj: bpy.types.Object) -> str | None:
|
|
1043
|
+
dae = Path(str(obj.get("helix_source_dae", "")))
|
|
1044
|
+
parent = dae.parent.name.lower()
|
|
1045
|
+
stem = dae.stem.lower()
|
|
1046
|
+
if parent in {"f", "front"} or re.search(r"(?:^|_)f$|front", stem):
|
|
1047
|
+
return "f"
|
|
1048
|
+
if parent in {"r", "rear"} or re.search(r"(?:^|_)r$|rear", stem):
|
|
1049
|
+
return "r"
|
|
1050
|
+
return None
|
|
1051
|
+
|
|
1052
|
+
def source_rank(obj: bpy.types.Object) -> tuple[int, int, str]:
|
|
1053
|
+
return (int(bool(obj.get("helix_configured_mesh"))), len(obj.data.polygons), obj.name)
|
|
1054
|
+
|
|
1055
|
+
shared_tyre_source = max(matching, key=source_rank, default=None) if tyre_role else None
|
|
1056
|
+
axle_named = [obj for obj in matching if source_axle(obj) is not None
|
|
1057
|
+
or re.search(r"(?:^|_)[fr](?:\d|_|$)|front|rear", obj.name, re.I)]
|
|
1058
|
+
shared_rim_source = (max(matching, key=source_rank, default=None)
|
|
1059
|
+
if not tyre_role and len({source_axle(obj) for obj in axle_named if source_axle(obj)}) < 2
|
|
1060
|
+
else None)
|
|
1061
|
+
for axle, axle_pattern, target_y in (("f", re.compile(r"(?:^|_)f(?:\d|_|$)|front", re.I), front_y),
|
|
1062
|
+
("r", re.compile(r"(?:^|_)r(?:\d|_|$)|rear", re.I), rear_y)):
|
|
1063
|
+
source = shared_tyre_source if tyre_role else shared_rim_source or max(
|
|
1064
|
+
(obj for obj in matching if source_axle(obj) == axle or axle_pattern.search(obj.name)),
|
|
1065
|
+
key=source_rank, default=None)
|
|
1066
|
+
if source is None:
|
|
1067
|
+
continue
|
|
1068
|
+
for side, x in (("l", half_track), ("r", -half_track)):
|
|
1069
|
+
copy = source.copy()
|
|
1070
|
+
copy.data = source.data.copy()
|
|
1071
|
+
copy.name = f"{'tyre' if tyre_role else 'wheel'}_source_{side}{axle}"
|
|
1072
|
+
copy["helix_expanded_axle"] = True
|
|
1073
|
+
bpy.context.scene.collection.objects.link(copy)
|
|
1074
|
+
target = Vector((x, target_y, hub_z))
|
|
1075
|
+
copy.matrix_world = Matrix.Translation(target - centre(copy)) @ copy.matrix_world
|
|
1076
|
+
# Legacy common libraries often store spare/axle-pair wheels
|
|
1077
|
+
# flat on Z. Canonical road wheels rotate around local X, so
|
|
1078
|
+
# orient the measured thinnest axis before any corner/radius
|
|
1079
|
+
# checks rather than rejecting an otherwise authored tyre.
|
|
1080
|
+
source_size = dimensions(copy)
|
|
1081
|
+
thin_axis = min(range(3), key=lambda index: source_size[index])
|
|
1082
|
+
if thin_axis == 2:
|
|
1083
|
+
copy.matrix_world = (Matrix.Translation(target) @ Matrix.Rotation(math.pi / 2, 4, "Y")
|
|
1084
|
+
@ Matrix.Translation(-target)) @ copy.matrix_world
|
|
1085
|
+
elif thin_axis == 1:
|
|
1086
|
+
copy.matrix_world = (Matrix.Translation(target) @ Matrix.Rotation(math.pi / 2, 4, "Z")
|
|
1087
|
+
@ Matrix.Translation(-target)) @ copy.matrix_world
|
|
1088
|
+
if side == "r":
|
|
1089
|
+
copy.matrix_world = (Matrix.Translation(target) @ Matrix.Rotation(math.pi, 4, "Z")
|
|
1090
|
+
@ Matrix.Translation(-target)) @ copy.matrix_world
|
|
1091
|
+
size = dimensions(copy)
|
|
1092
|
+
radius = max(size.y, size.z) * 0.5
|
|
1093
|
+
if radius > 0 and not 0.22 <= radius <= 0.55:
|
|
1094
|
+
current = centre(copy)
|
|
1095
|
+
copy.matrix_world = (Matrix.Translation(current) @ Matrix.Scale(target_radius / radius, 4)
|
|
1096
|
+
@ Matrix.Translation(-current)) @ copy.matrix_world
|
|
1097
|
+
expanded.append(copy)
|
|
1098
|
+
if expanded:
|
|
1099
|
+
for source in sources:
|
|
1100
|
+
if source.name in bpy.data.objects:
|
|
1101
|
+
bpy.data.objects.remove(source, do_unlink=True)
|
|
1102
|
+
objects[:] = meshes()
|
|
1103
|
+
print(f"[beamng] expanded wheel nodes={[(obj.name, tuple(round(v, 3) for v in centre(obj)), tuple(round(v, 3) for v in dimensions(obj))) for obj in expanded]}")
|
|
1104
|
+
messages.append(f"expanded authored axle wheel meshes to four measured body-envelope hub placements")
|
|
1105
|
+
return messages
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
def fit_authored_wheels(objects: list[bpy.types.Object], rims: dict[str, bpy.types.Object],
|
|
1109
|
+
tyres: dict[str, bpy.types.Object], specified_tyre_radius: float | None = None) -> list[str]:
|
|
1110
|
+
candidates = [obj for obj in objects if BRAKE_HARDWARE.search(obj.name)
|
|
1111
|
+
and not road_wheel_signal(obj) and abs(centre(obj).x) > 0.3 and max(dimensions(obj)) < 1.0]
|
|
1112
|
+
repairs = []
|
|
1113
|
+
if candidates:
|
|
1114
|
+
ys = sorted(centre(obj).y for obj in candidates)
|
|
1115
|
+
split = (ys[0] + ys[-1]) / 2
|
|
1116
|
+
hubs = {}
|
|
1117
|
+
for obj in candidates:
|
|
1118
|
+
tag = tag_for(centre(obj), split)
|
|
1119
|
+
if tag not in hubs or len(obj.data.polygons) > len(hubs[tag].data.polygons):
|
|
1120
|
+
hubs[tag] = obj
|
|
1121
|
+
else:
|
|
1122
|
+
hubs = {}
|
|
1123
|
+
body = semantic_object(objects, re.compile(r"(?:^|_)(?:body|bodyshell|shell|chassis)(?:$|_)", re.I))
|
|
1124
|
+
target_radius = max(0.24, min(0.46, dimensions(body).y * 0.075)) if body else 0.32
|
|
1125
|
+
for tag in ("lf", "rf", "lr", "rr"):
|
|
1126
|
+
authored = {obj for obj in (rims.get(tag), tyres.get(tag)) if obj is not None}
|
|
1127
|
+
measured_tyre = tyres.get(tag)
|
|
1128
|
+
measured_tyre_radius = (max(dimensions(measured_tyre).y, dimensions(measured_tyre).z) * 0.5
|
|
1129
|
+
if measured_tyre is not None else None)
|
|
1130
|
+
configured_scale = (specified_tyre_radius / measured_tyre_radius
|
|
1131
|
+
if specified_tyre_radius and measured_tyre_radius else 1.0)
|
|
1132
|
+
# Four authored per-corner discs/drums/hubs are stronger evidence than
|
|
1133
|
+
# a body-envelope estimate. A partial/ambiguous legacy set is not.
|
|
1134
|
+
expanded_corner = rims.get(tag) if rims.get(tag) is not None and rims[tag].get("helix_expanded_axle") else None
|
|
1135
|
+
measured_hub = hubs.get(tag) if len(hubs) == 4 else None
|
|
1136
|
+
hub = centre(measured_hub) if measured_hub else (centre(expanded_corner) if expanded_corner else (
|
|
1137
|
+
centre(rims[tag]) if tag in rims else None))
|
|
1138
|
+
for obj in authored:
|
|
1139
|
+
lo, hi = world_bounds(obj)
|
|
1140
|
+
current_centre = (lo + hi) * 0.5
|
|
1141
|
+
radius = max(hi.y-lo.y, hi.z-lo.z) * 0.5
|
|
1142
|
+
fitted_radius = target_radius
|
|
1143
|
+
needs_radius_fit = not 0.22 <= radius <= 0.55 or abs(configured_scale - 1.0) > 0.04
|
|
1144
|
+
if abs(configured_scale - 1.0) > 0.04:
|
|
1145
|
+
fitted_radius = radius * configured_scale
|
|
1146
|
+
rim = rims.get(tag)
|
|
1147
|
+
if TYRE.search(obj.name) and rim is not None and rim is not obj:
|
|
1148
|
+
rim_size = dimensions(rim)
|
|
1149
|
+
rim_radius = max(rim_size.y, rim_size.z) * 0.5
|
|
1150
|
+
ratio = radius / max(0.001, rim_radius)
|
|
1151
|
+
# A shared dependency tyre must wrap the selected trim rim,
|
|
1152
|
+
# not merely fall inside a broad road-wheel range. The first
|
|
1153
|
+
# common-asset Porsche pass produced a 0.362 m tyre around a
|
|
1154
|
+
# 0.274 m rim and visibly pushed through the GT3 RS arch.
|
|
1155
|
+
if ratio < 1.08 or ratio > 1.28:
|
|
1156
|
+
fitted_radius = rim_radius * 1.22
|
|
1157
|
+
needs_radius_fit = True
|
|
1158
|
+
scale = fitted_radius / radius if radius > 0 and needs_radius_fit else 1.0
|
|
1159
|
+
target = hub or current_centre
|
|
1160
|
+
obj.matrix_world = (Matrix.Translation(target) @ Matrix.Scale(scale, 4)
|
|
1161
|
+
@ Matrix.Translation(-current_centre)) @ obj.matrix_world
|
|
1162
|
+
if abs(scale - 1.0) > 0.01 or (target-current_centre).length > 0.02:
|
|
1163
|
+
repairs.append(f"fitted authored {obj.name} to measured {tag} hub at {fitted_radius:.3f} m radius")
|
|
1164
|
+
return repairs
|
|
1165
|
+
|
|
1166
|
+
|
|
1167
|
+
def configured_tyre_radius(parts: list[str]) -> float | None:
|
|
1168
|
+
"""Return the physical outside radius encoded by common BeamNG tyre part IDs."""
|
|
1169
|
+
sizes = []
|
|
1170
|
+
for part in parts:
|
|
1171
|
+
match = re.search(r"(?:tire|tyre)[^\d]*(\d{3})_(\d{2})_(\d{2})(?:_|$)", part, re.I)
|
|
1172
|
+
if not match:
|
|
1173
|
+
continue
|
|
1174
|
+
width_mm, aspect, rim_inches = map(float, match.groups())
|
|
1175
|
+
radius = rim_inches * 0.0127 + width_mm * (aspect / 100.0) / 1000.0
|
|
1176
|
+
if 0.22 <= radius <= 0.55:
|
|
1177
|
+
sizes.append(radius)
|
|
1178
|
+
return sum(sizes) / len(sizes) if sizes else None
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
def normalize_wheel_hardware(objects: list[bpy.types.Object],
|
|
1182
|
+
rims: dict[str, bpy.types.Object]) -> tuple[list[str], dict | None]:
|
|
1183
|
+
"""Keep or instance selected authored brake/hub hardware at exactly four wheel corners."""
|
|
1184
|
+
candidates = [obj for obj in objects if BRAKE_HARDWARE.search(obj.name) and not road_wheel_signal(obj)]
|
|
1185
|
+
if not candidates or len(rims) != 4:
|
|
1186
|
+
return [], None
|
|
1187
|
+
def rank(obj: bpy.types.Object) -> tuple:
|
|
1188
|
+
strong_brake = int(bool(re.search(r"disc|rotor|drum", obj.name, re.I)))
|
|
1189
|
+
size = max(dimensions(obj))
|
|
1190
|
+
return strong_brake, -abs(size-0.38), len(obj.data.polygons)
|
|
1191
|
+
measured = [obj for obj in candidates if abs(centre(obj).x) > 0.3]
|
|
1192
|
+
selected: dict[str, bpy.types.Object] = {}
|
|
1193
|
+
if measured:
|
|
1194
|
+
ys = sorted(centre(obj).y for obj in measured)
|
|
1195
|
+
split = (ys[0] + ys[-1]) / 2
|
|
1196
|
+
for obj in measured:
|
|
1197
|
+
tag = tag_for(centre(obj), split)
|
|
1198
|
+
if tag not in selected or rank(obj) > rank(selected[tag]):
|
|
1199
|
+
selected[tag] = obj
|
|
1200
|
+
instances: dict[str, bpy.types.Object] = {}
|
|
1201
|
+
if len(selected) == 4:
|
|
1202
|
+
instances = selected
|
|
1203
|
+
else:
|
|
1204
|
+
front_pattern = re.compile(r"(?:^|_)f(?:_|$)|front", re.I)
|
|
1205
|
+
rear_pattern = re.compile(r"(?:^|_)r(?:_|$)|rear", re.I)
|
|
1206
|
+
axle_sources = {
|
|
1207
|
+
"f": max((obj for obj in candidates if front_pattern.search(obj.name)), key=rank, default=None),
|
|
1208
|
+
"r": max((obj for obj in candidates if rear_pattern.search(obj.name)), key=rank, default=None),
|
|
1209
|
+
}
|
|
1210
|
+
shared = max(candidates, key=rank)
|
|
1211
|
+
for tag in ("lf", "rf", "lr", "rr"):
|
|
1212
|
+
source = axle_sources[tag[1]] or shared
|
|
1213
|
+
target = centre(rims[tag])
|
|
1214
|
+
copy = source.copy()
|
|
1215
|
+
copy.data = source.data.copy()
|
|
1216
|
+
copy["helix_brake_source_identity"] = f"{Path(str(source.get('helix_source_dae', 'unknown'))).name}:{source.name}"
|
|
1217
|
+
bpy.context.scene.collection.objects.link(copy)
|
|
1218
|
+
copy.matrix_world = Matrix.Translation(target-centre(copy)) @ copy.matrix_world
|
|
1219
|
+
if tag[0] == "r":
|
|
1220
|
+
copy.matrix_world = (Matrix.Translation(target) @ Matrix.Rotation(math.pi, 4, "Z")
|
|
1221
|
+
@ Matrix.Translation(-target)) @ copy.matrix_world
|
|
1222
|
+
instances[tag] = copy
|
|
1223
|
+
chosen = set(instances.values())
|
|
1224
|
+
sources = []
|
|
1225
|
+
for tag, obj in instances.items():
|
|
1226
|
+
identity = str(obj.get("helix_brake_source_identity",
|
|
1227
|
+
f"{Path(str(obj.get('helix_source_dae', 'unknown'))).name}:{obj.name}"))
|
|
1228
|
+
obj["helix_brake_source"] = identity
|
|
1229
|
+
obj.name = f"brake_{tag}"
|
|
1230
|
+
sources.append(identity)
|
|
1231
|
+
for candidate in candidates:
|
|
1232
|
+
if candidate not in chosen and candidate.name in bpy.data.objects:
|
|
1233
|
+
bpy.data.objects.remove(candidate, do_unlink=True)
|
|
1234
|
+
objects[:] = meshes()
|
|
1235
|
+
receipt = {"sources": sorted(set(sources)), "instances": [f"brake_{tag}" for tag in ("lf", "rf", "lr", "rr")]}
|
|
1236
|
+
print(f"[beamng] normalized wheel hardware custody={receipt}")
|
|
1237
|
+
action = "retained four authored per-corner brake/hub meshes" if len(selected) == 4 else \
|
|
1238
|
+
"instanced selected authored axle brake/hub templates to four measured wheel centres"
|
|
1239
|
+
return [action], receipt
|
|
1240
|
+
|
|
1241
|
+
|
|
1242
|
+
def rename_wheels(rims: dict[str, bpy.types.Object], tyres: dict[str, bpy.types.Object]) -> None:
|
|
1243
|
+
chosen = set(rims.values()) | set(tyres.values())
|
|
1244
|
+
for obj in chosen:
|
|
1245
|
+
obj.name = f"source_{obj.name}"
|
|
1246
|
+
for tag, obj in rims.items():
|
|
1247
|
+
obj.name = f"rim_{tag}"
|
|
1248
|
+
for tag, obj in tyres.items():
|
|
1249
|
+
if obj not in rims.values():
|
|
1250
|
+
obj.name = f"tyre_{tag}"
|
|
1251
|
+
|
|
1252
|
+
|
|
1253
|
+
def seat_points(objects: list[bpy.types.Object]) -> tuple[dict[str, Vector], list[str]]:
|
|
1254
|
+
candidates = [obj for obj in objects if re.search(r"seat", obj.name, re.I)
|
|
1255
|
+
and not re.search(r"seat.?belt|belt.*seat|seat.?rail|seat.?bracket|seat.?mount", obj.name, re.I)
|
|
1256
|
+
and max(dimensions(obj)) >= 0.25]
|
|
1257
|
+
if not candidates:
|
|
1258
|
+
return {}, []
|
|
1259
|
+
# Collapse multi-piece seats by side/row, keeping the most substantial authored mesh.
|
|
1260
|
+
ys = sorted(centre(obj).y for obj in candidates)
|
|
1261
|
+
split = (ys[0] + ys[-1]) / 2 if len(ys) > 2 and ys[-1] - ys[0] > 0.45 else math.inf
|
|
1262
|
+
selected: dict[str, bpy.types.Object] = {}
|
|
1263
|
+
for obj in candidates:
|
|
1264
|
+
c = centre(obj)
|
|
1265
|
+
# A singular `seat_R` is normally the right-front seat (CCF); plural
|
|
1266
|
+
# `seats_R` and `bench_R` are the common rear-bench conventions.
|
|
1267
|
+
explicitly_rear = bool(re.search(r"(?:seats|bench)_R(?:$|_)|rear", obj.name, re.I))
|
|
1268
|
+
front = not explicitly_rear and c.y <= split
|
|
1269
|
+
if abs(c.x) < 0.08 and dimensions(obj).x >= 0.5:
|
|
1270
|
+
# One authored rear bench represents two real seating positions.
|
|
1271
|
+
selected["dr" if not front else "df"] = obj
|
|
1272
|
+
selected["pr" if not front else "pf"] = obj
|
|
1273
|
+
continue
|
|
1274
|
+
key = ("d" if c.x >= 0 else "p") + ("f" if front else "r")
|
|
1275
|
+
if key not in selected or len(obj.data.polygons) > len(selected[key].data.polygons):
|
|
1276
|
+
selected[key] = obj
|
|
1277
|
+
sockets: dict[str, Vector] = {}
|
|
1278
|
+
names = {"df": "seat_dside_f", "pf": "seat_pside_f", "dr": "seat_dside_r1", "pr": "seat_pside_r1"}
|
|
1279
|
+
for key, obj in selected.items():
|
|
1280
|
+
lo, hi = world_bounds(obj)
|
|
1281
|
+
lateral = centre(obj).x
|
|
1282
|
+
if abs(lateral) < 0.08 and hi.x-lo.x >= 0.5:
|
|
1283
|
+
lateral = (hi.x-lo.x) * (0.25 if key.startswith("d") else -0.25)
|
|
1284
|
+
# A source seat mesh centre is usually the backrest; the H-point is over the cushion.
|
|
1285
|
+
sockets[names[key]] = Vector((lateral, lo.y * 0.35 + hi.y * 0.65, lo.z + min(0.42, (hi.z-lo.z) * 0.38)))
|
|
1286
|
+
return sockets, [obj.name for obj in selected.values()]
|
|
1287
|
+
|
|
1288
|
+
|
|
1289
|
+
def semantic_object(objects: list[bpy.types.Object], pattern: re.Pattern) -> bpy.types.Object | None:
|
|
1290
|
+
candidates = [obj for obj in objects if pattern.search(obj.name)]
|
|
1291
|
+
return max(candidates, key=lambda obj: len(obj.data.polygons), default=None)
|
|
1292
|
+
|
|
1293
|
+
|
|
1294
|
+
def vehicle_shell(objects: list[bpy.types.Object]) -> bpy.types.Object | None:
|
|
1295
|
+
named = semantic_object(objects, re.compile(r"(?:^|_)(?:body|bodyshell|shell|chassis)(?:$|_)", re.I))
|
|
1296
|
+
if named:
|
|
1297
|
+
return named
|
|
1298
|
+
candidates = [obj for obj in objects if dimensions(obj).x >= 1.2 and dimensions(obj).y >= 3.0
|
|
1299
|
+
and dimensions(obj).z >= 0.55 and not road_wheel_signal(obj)]
|
|
1300
|
+
return max(candidates, key=lambda obj: len(obj.data.polygons), default=None)
|
|
1301
|
+
|
|
1302
|
+
|
|
1303
|
+
def decimate(objects: list[bpy.types.Object], limit: int = 285_000) -> tuple[int, int]:
|
|
1304
|
+
before = sum(len(obj.data.polygons) for obj in objects)
|
|
1305
|
+
if before <= limit:
|
|
1306
|
+
return before, before
|
|
1307
|
+
# Preserve first-person cabin controls and wheels. Exterior panels may receive a shallow
|
|
1308
|
+
# source-mesh decimation so a high-detail body cannot make the whole vehicle miss budget.
|
|
1309
|
+
protected = [obj for obj in objects if INTERIOR.search(obj.name) or road_wheel_signal(obj)]
|
|
1310
|
+
protected_count = sum(len(obj.data.polygons) for obj in protected)
|
|
1311
|
+
reducible = [obj for obj in objects if obj not in protected and len(obj.data.polygons) > 200]
|
|
1312
|
+
reducible_count = max(1, before - protected_count)
|
|
1313
|
+
target_reducible = max(5_000, limit - protected_count)
|
|
1314
|
+
ratio = min(1.0, target_reducible / reducible_count)
|
|
1315
|
+
for obj in reducible:
|
|
1316
|
+
bpy.context.view_layer.objects.active = obj
|
|
1317
|
+
obj.select_set(True)
|
|
1318
|
+
modifier = obj.modifiers.new("HELIX_web_budget", "DECIMATE")
|
|
1319
|
+
modifier.ratio = max(0.04, ratio)
|
|
1320
|
+
try:
|
|
1321
|
+
bpy.ops.object.modifier_apply(modifier=modifier.name)
|
|
1322
|
+
except Exception as error:
|
|
1323
|
+
print(f"[beamng] decimation skipped for {obj.name}: {error}")
|
|
1324
|
+
obj.select_set(False)
|
|
1325
|
+
return before, sum(len(obj.data.polygons) for obj in objects)
|
|
1326
|
+
|
|
1327
|
+
|
|
1328
|
+
def main() -> None:
|
|
1329
|
+
args = arguments()
|
|
1330
|
+
source_root = Path(args.source_root).resolve()
|
|
1331
|
+
vehicle_root = Path(args.vehicle_root).resolve()
|
|
1332
|
+
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
1333
|
+
for dae in args.dae:
|
|
1334
|
+
print(f"[beamng] importing {dae}")
|
|
1335
|
+
build_collada(Path(dae).resolve())
|
|
1336
|
+
objects = meshes()
|
|
1337
|
+
if not objects:
|
|
1338
|
+
raise RuntimeError("selected BeamNG DAE contains no renderable meshes")
|
|
1339
|
+
wanted, config = configured_meshes(vehicle_root, source_root, args.dae,
|
|
1340
|
+
args.configuration_hint, {obj.name for obj in objects},
|
|
1341
|
+
[Path(value).resolve() for value in args.material_root],
|
|
1342
|
+
Path(args.part_index).resolve(), args.bridge_plan_key,
|
|
1343
|
+
args.source_sha, args.common_closure)
|
|
1344
|
+
has_dedicated_wheels = any("wheels" in [part.lower() for part in Path(dae).parts] for dae in args.dae)
|
|
1345
|
+
objects, missing_meshes = filter_configuration(objects, wanted, has_dedicated_wheels,
|
|
1346
|
+
args.configuration_hint)
|
|
1347
|
+
config["missingMeshes"] = missing_meshes
|
|
1348
|
+
print(f"[beamng] default config {config['file']}: {len(config['parts'])} parts, "
|
|
1349
|
+
f"{len(objects)} present meshes, {len(missing_meshes)} missing")
|
|
1350
|
+
|
|
1351
|
+
if args.mirror_z:
|
|
1352
|
+
reflection = Matrix.Scale(-1.0, 4, Vector((0.0, 1.0, 0.0)))
|
|
1353
|
+
for obj in objects:
|
|
1354
|
+
obj.matrix_world = reflection @ obj.matrix_world
|
|
1355
|
+
|
|
1356
|
+
# Put the measured tyre contact patch at zero. Underbody/optional meshes can extend below
|
|
1357
|
+
# the tyres and must not lift the whole cabin (the original RX-7 roof-seat defect).
|
|
1358
|
+
axle_repairs = expand_axle_wheels(objects)
|
|
1359
|
+
initial_rims, initial_tyres = select_wheels(objects)
|
|
1360
|
+
wheel_repairs = fit_authored_wheels(objects, initial_rims, initial_tyres,
|
|
1361
|
+
configured_tyre_radius(config.get("parts", [])))
|
|
1362
|
+
brake_repairs, brake_custody = normalize_wheel_hardware(objects, initial_rims)
|
|
1363
|
+
grounding = list(initial_tyres.values()) or list(initial_rims.values())
|
|
1364
|
+
if len(initial_rims) != 4:
|
|
1365
|
+
raise RuntimeError(f"default configuration exposes {len(initial_rims)}/4 measurable wheel corners")
|
|
1366
|
+
ground = min(world_bounds(obj)[0].z for obj in grounding)
|
|
1367
|
+
lowest = min(objects, key=lambda obj: world_bounds(obj)[0].z)
|
|
1368
|
+
highest = max(objects, key=lambda obj: world_bounds(obj)[1].z)
|
|
1369
|
+
print(f"[beamng] ground={ground:.4f} from tyres; vertical extremes "
|
|
1370
|
+
f"{lowest.name}={world_bounds(lowest)[0].z:.4f}, {highest.name}={world_bounds(highest)[1].z:.4f}")
|
|
1371
|
+
shift = Matrix.Translation(Vector((0.0, 0.0, -ground)))
|
|
1372
|
+
for obj in objects:
|
|
1373
|
+
obj.matrix_world = shift @ obj.matrix_world
|
|
1374
|
+
obj.select_set(True)
|
|
1375
|
+
bpy.context.view_layer.objects.active = objects[0]
|
|
1376
|
+
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
|
1377
|
+
|
|
1378
|
+
tyre_repairs, tyre_custody, rim_custody = normalize_authored_tyres(objects)
|
|
1379
|
+
|
|
1380
|
+
material_roots = [source_root, *(Path(value).resolve() for value in args.material_root)]
|
|
1381
|
+
materials = restore_materials(material_roots, Path(args.palette).resolve(), vehicle_root,
|
|
1382
|
+
config.get("paintBaseColor"))
|
|
1383
|
+
rims, tyres = select_wheels(objects)
|
|
1384
|
+
rename_wheels(rims, tyres)
|
|
1385
|
+
before, after = decimate(objects)
|
|
1386
|
+
lo, hi = bounds_of(objects)
|
|
1387
|
+
sockets_raw, seat_sources = seat_points(objects)
|
|
1388
|
+
steering_obj = semantic_object(objects, re.compile(r"steer(?:ing)?(?:wheel|rim)?", re.I))
|
|
1389
|
+
engine_obj = semantic_object(objects, re.compile(r"(?:^|[_-])engine(?:[_-]|\d|$)", re.I))
|
|
1390
|
+
exhaust_obj = semantic_object(objects, re.compile(r"exhaust|muffler|tail.?pipe", re.I))
|
|
1391
|
+
steering = centre(steering_obj) if steering_obj else None
|
|
1392
|
+
seat_side_repaired = False
|
|
1393
|
+
if steering is not None and "seat_dside_f" in sockets_raw and "seat_pside_f" in sockets_raw:
|
|
1394
|
+
driver_distance = abs(sockets_raw["seat_dside_f"].x-steering.x)
|
|
1395
|
+
passenger_distance = abs(sockets_raw["seat_pside_f"].x-steering.x)
|
|
1396
|
+
if passenger_distance + 0.05 < driver_distance:
|
|
1397
|
+
sockets_raw["seat_dside_f"], sockets_raw["seat_pside_f"] = (
|
|
1398
|
+
sockets_raw["seat_pside_f"], sockets_raw["seat_dside_f"])
|
|
1399
|
+
if "seat_dside_r1" in sockets_raw and "seat_pside_r1" in sockets_raw:
|
|
1400
|
+
sockets_raw["seat_dside_r1"], sockets_raw["seat_pside_r1"] = (
|
|
1401
|
+
sockets_raw["seat_pside_r1"], sockets_raw["seat_dside_r1"])
|
|
1402
|
+
seat_side_repaired = True
|
|
1403
|
+
|
|
1404
|
+
# Preserve what was genuinely authored before any ergonomic repair. Runtime
|
|
1405
|
+
# sockets may be repaired, but those repairs must never make an incomplete
|
|
1406
|
+
# source look eligible to the Bridge acceptance gate.
|
|
1407
|
+
authored_seat_count = len(sockets_raw)
|
|
1408
|
+
authored_steering_mesh = steering_obj is not None
|
|
1409
|
+
|
|
1410
|
+
repairs: list[str] = list(axle_repairs) + list(wheel_repairs) + list(brake_repairs) + list(tyre_repairs)
|
|
1411
|
+
if seat_side_repaired:
|
|
1412
|
+
repairs.append("assigned driver/passenger sockets from the authored steering-wheel side")
|
|
1413
|
+
driver = sockets_raw.get("seat_dside_f")
|
|
1414
|
+
cabin_shell = vehicle_shell(objects)
|
|
1415
|
+
roof = world_bounds(cabin_shell)[1].z if cabin_shell else hi.z
|
|
1416
|
+
interior_objects = [obj for obj in objects if INTERIOR.search(obj.name)]
|
|
1417
|
+
if driver is None and len(interior_objects) >= 8 and cabin_shell and len(rims) == 4:
|
|
1418
|
+
shell_lo, shell_hi = world_bounds(cabin_shell)
|
|
1419
|
+
front = (canonical(centre(rims["lf"]))[2] + canonical(centre(rims["rf"]))[2]) / 2
|
|
1420
|
+
rear = (canonical(centre(rims["lr"]))[2] + canonical(centre(rims["rr"]))[2]) / 2
|
|
1421
|
+
if steering is not None:
|
|
1422
|
+
# Combined legacy cabins may not expose separate seat nodes. Anchor
|
|
1423
|
+
# their H-point behind the actual authored steering rim instead of
|
|
1424
|
+
# guessing from the body midpoint; this supports both LHD and RHD
|
|
1425
|
+
# layouts and keeps the cockpit lens behind the controls.
|
|
1426
|
+
seat_z = canonical(steering)[2] - 0.45
|
|
1427
|
+
seat_x = steering.x
|
|
1428
|
+
else:
|
|
1429
|
+
seat_z = (front + rear) / 2 + 0.16
|
|
1430
|
+
seat_x = (shell_hi.x-shell_lo.x) * 0.178
|
|
1431
|
+
seat_height = roof - 0.86
|
|
1432
|
+
sockets_raw = {"seat_dside_f": Vector((seat_x, -seat_z, seat_height)),
|
|
1433
|
+
"seat_pside_f": Vector((-seat_x, -seat_z, seat_height))}
|
|
1434
|
+
driver = sockets_raw["seat_dside_f"]
|
|
1435
|
+
if steering is None:
|
|
1436
|
+
steering = Vector((driver.x, driver.y - 0.45, driver.z + 0.30))
|
|
1437
|
+
seat_sources = [obj.name for obj in interior_objects]
|
|
1438
|
+
repairs.append("inferred two H-points and control anchor inside source-authored combined cabin geometry")
|
|
1439
|
+
if driver is not None and roof - driver.z < 0.86:
|
|
1440
|
+
delta = 0.86 - (roof - driver.z)
|
|
1441
|
+
for key in list(sockets_raw):
|
|
1442
|
+
sockets_raw[key].z -= delta
|
|
1443
|
+
driver = sockets_raw["seat_dside_f"]
|
|
1444
|
+
repairs.append(f"lowered measured H-points {delta:.3f} m for occupant headroom")
|
|
1445
|
+
if driver is not None and steering is not None:
|
|
1446
|
+
driver_gltf, steering_gltf = canonical(driver), canonical(steering)
|
|
1447
|
+
reach = math.dist(driver_gltf, steering_gltf)
|
|
1448
|
+
if not (0.30 <= reach <= 0.80 and steering.z-driver.z >= 0.14 and -steering.y-(-driver.y) >= 0.20):
|
|
1449
|
+
# Keep source lateral placement; refit only the interaction point from the measured H-point.
|
|
1450
|
+
steering = Vector((steering.x if abs(steering.x-driver.x) <= 0.18 else driver.x,
|
|
1451
|
+
driver.y - 0.45, driver.z + 0.30))
|
|
1452
|
+
repairs.append("re-fitted steering interaction point from measured driver H-point")
|
|
1453
|
+
|
|
1454
|
+
wheel_sockets = {f"wheel_{tag}": canonical(centre(obj)) for tag, obj in rims.items()}
|
|
1455
|
+
sockets = {**wheel_sockets, **{key: canonical(value) for key, value in sockets_raw.items()}}
|
|
1456
|
+
if steering is not None:
|
|
1457
|
+
sockets["steering"] = canonical(steering)
|
|
1458
|
+
if engine_obj is not None:
|
|
1459
|
+
sockets["engine"] = canonical(centre(engine_obj))
|
|
1460
|
+
rear_axle = min(sockets["wheel_lr"][2], sockets["wheel_rr"][2])
|
|
1461
|
+
body_length = dimensions(cabin_shell).y if cabin_shell else hi.y-lo.y
|
|
1462
|
+
if exhaust_obj:
|
|
1463
|
+
exhaust_lo, exhaust_hi = world_bounds(exhaust_obj)
|
|
1464
|
+
authored_exhaust = [round(float(centre(exhaust_obj).x), 4),
|
|
1465
|
+
round(float(exhaust_lo.z + min(0.12, (exhaust_hi.z-exhaust_lo.z) * 0.25)), 4),
|
|
1466
|
+
round(float(-exhaust_hi.y), 4)]
|
|
1467
|
+
if authored_exhaust[2] < rear_axle - 0.3:
|
|
1468
|
+
sockets["exhaust"] = authored_exhaust
|
|
1469
|
+
else:
|
|
1470
|
+
sockets["exhaust"] = [authored_exhaust[0], authored_exhaust[1],
|
|
1471
|
+
round(float(rear_axle - max(0.45, body_length * 0.20)), 4)]
|
|
1472
|
+
repairs.append("extended exhaust interaction point from source assembly to measured rear overhang")
|
|
1473
|
+
else:
|
|
1474
|
+
sockets["exhaust"] = [0.0, round(float(lo.z + 0.35), 4),
|
|
1475
|
+
round(float(rear_axle - max(0.45, body_length * 0.20)), 4)]
|
|
1476
|
+
repairs.append("derived exhaust interaction point from measured rear axle and source body length")
|
|
1477
|
+
if driver is not None:
|
|
1478
|
+
driver_canonical = canonical(driver)
|
|
1479
|
+
pedal_y = max(float(lo.z + 0.12), driver_canonical[1] - 0.40)
|
|
1480
|
+
pedal_z = min(float(-lo.y - 0.18), driver_canonical[2] + 0.72)
|
|
1481
|
+
sockets["pedal_throttle"] = [round(driver_canonical[0] - 0.07, 4), round(pedal_y, 4), round(pedal_z, 4)]
|
|
1482
|
+
sockets["pedal_brake"] = [round(driver_canonical[0] + 0.02, 4), round(pedal_y, 4), round(pedal_z, 4)]
|
|
1483
|
+
sockets["pedal_clutch"] = [round(driver_canonical[0] + 0.12, 4), round(pedal_y, 4), round(pedal_z, 4)]
|
|
1484
|
+
# Put the lens at the eye plane and FORWARD of the head centre. The old +2 cm anchor remained inside
|
|
1485
|
+
# the avatar's skull/upper torso on low sports seats, producing a first-person frame filled by skin.
|
|
1486
|
+
cockpit_forward = driver_canonical[2] + 0.04
|
|
1487
|
+
if steering_obj is not None:
|
|
1488
|
+
# The lens must remain behind the actual authored rim, including
|
|
1489
|
+
# legacy cabins whose cushion-derived H-point sits unusually close
|
|
1490
|
+
# to it. Otherwise the QA camera can land beyond the wheel and show
|
|
1491
|
+
# only the hood despite a valid interior.
|
|
1492
|
+
cockpit_forward = min(cockpit_forward, canonical(centre(steering_obj))[2] - 0.32)
|
|
1493
|
+
sockets["cam_cockpit"] = [driver_canonical[0], round(min(float(hi.z - 0.14), driver.z + 0.65), 4),
|
|
1494
|
+
round(cockpit_forward, 4)]
|
|
1495
|
+
sockets["cam_chase"] = [0.0, round(float(lo.z + 1.25), 4), round(float(-hi.y - 4.8), 4)]
|
|
1496
|
+
sockets["cam_hood"] = [0.0, round(float(lo.z + (hi.z-lo.z)*0.72), 4), round(float(-lo.y - 0.25), 4)]
|
|
1497
|
+
|
|
1498
|
+
wheel_radii = []
|
|
1499
|
+
for obj in rims.values():
|
|
1500
|
+
size = dimensions(obj)
|
|
1501
|
+
wheel_radii.append(max(size.y, size.z) * 0.5)
|
|
1502
|
+
audio_limits = {extension: AUDIO_SCAN_LIMIT for extension in (".wav", ".ogg", ".flac", ".mp3")}
|
|
1503
|
+
audio_files = len(bounded_asset_paths([vehicle_root], audio_limits))
|
|
1504
|
+
audio_refs = int(bool(config.get("selectedPartSummary", {}).get("semanticEvidence", {}).get("audio")))
|
|
1505
|
+
def geometry_receipt(value: str) -> str:
|
|
1506
|
+
path = Path(value).resolve()
|
|
1507
|
+
try:
|
|
1508
|
+
return str(path.relative_to(source_root))
|
|
1509
|
+
except ValueError:
|
|
1510
|
+
return f"common-dependency:{path.name}"
|
|
1511
|
+
|
|
1512
|
+
receipt = {
|
|
1513
|
+
"HELIX_frame": "+X left, +Y up, +Z forward; metres" + (" (source rear-at-+Z mirrored during import)" if args.mirror_z else ""),
|
|
1514
|
+
"HELIX_bounds": {"min": canonical(Vector((lo.x, hi.y, lo.z))), "max": canonical(Vector((hi.x, lo.y, hi.z)))},
|
|
1515
|
+
"HELIX_vehicle": {
|
|
1516
|
+
"version": 1,
|
|
1517
|
+
"class": "sports-car" if len(sockets_raw) <= 2 else "car",
|
|
1518
|
+
"wheelRadius": round(float(sum(wheel_radii) / len(wheel_radii)), 4) if wheel_radii else None,
|
|
1519
|
+
"sockets": sockets,
|
|
1520
|
+
},
|
|
1521
|
+
"HELIX_import_quality": {
|
|
1522
|
+
"geometry": [geometry_receipt(value) for value in args.dae],
|
|
1523
|
+
"configuration": config,
|
|
1524
|
+
"socketSources": {"wheels": [obj.name for obj in rims.values()], "seats": seat_sources,
|
|
1525
|
+
"steering": steering_obj.name if steering_obj else None,
|
|
1526
|
+
"engine": engine_obj.name if engine_obj else None,
|
|
1527
|
+
"exhaust": exhaust_obj.name if exhaust_obj else "rear-bound fallback"},
|
|
1528
|
+
"tyreCustody": tyre_custody,
|
|
1529
|
+
"rimCustody": rim_custody,
|
|
1530
|
+
"brakeCustody": brake_custody,
|
|
1531
|
+
"materials": materials,
|
|
1532
|
+
"triangles": {"source": before, "export": after},
|
|
1533
|
+
"audio": {"recordedFiles": audio_files, "jbeamReferences": audio_refs},
|
|
1534
|
+
"sourceMeasurements": {
|
|
1535
|
+
"seatHPoints": authored_seat_count,
|
|
1536
|
+
"steeringMesh": authored_steering_mesh,
|
|
1537
|
+
"interiorMeshes": len(interior_objects),
|
|
1538
|
+
"engineMesh": engine_obj is not None,
|
|
1539
|
+
"exhaustMesh": exhaust_obj is not None,
|
|
1540
|
+
"massKg": None,
|
|
1541
|
+
},
|
|
1542
|
+
"repairs": repairs,
|
|
1543
|
+
},
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
for obj in objects:
|
|
1547
|
+
bpy.context.view_layer.objects.active = obj
|
|
1548
|
+
try:
|
|
1549
|
+
bpy.ops.object.shade_auto_smooth(angle=math.radians(35))
|
|
1550
|
+
except Exception:
|
|
1551
|
+
pass
|
|
1552
|
+
obj.select_set(True)
|
|
1553
|
+
bpy.context.view_layer.objects.active = objects[0]
|
|
1554
|
+
bpy.ops.export_scene.gltf(filepath=str(Path(args.output).resolve()), export_format="GLB", use_selection=True,
|
|
1555
|
+
export_yup=True, export_apply=True, export_image_format="AUTO",
|
|
1556
|
+
export_materials="EXPORT", export_texcoords=True, export_normals=True,
|
|
1557
|
+
export_cameras=False, export_lights=False, export_extras=True)
|
|
1558
|
+
Path(args.receipt).write_text(json.dumps(receipt, indent=2) + "\n")
|
|
1559
|
+
print(f"[beamng] exported {after} triangles, {materials['mapped']}/{materials['materials']} mapped materials")
|
|
1560
|
+
|
|
1561
|
+
|
|
1562
|
+
if __name__ == "__main__":
|
|
1563
|
+
main()
|