@helix3/helix-cli 0.1.18-helix3.299 → 0.1.18-helix3.300

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.
@@ -0,0 +1,25 @@
1
+ import { type SplitSharedLodSkeletonsResult } from './stages/splitSharedLodSkeletons';
2
+ /**
3
+ * File-level entry point for `helix character repair-lod-skeletons`: repairs a packaged multi-LOD add-on
4
+ * whose LOD scenes share one skeleton (helix-creator-cli #219's output) — see `stages/splitSharedLodSkeletons.ts`.
5
+ *
6
+ * Output contract, so a batch driver never has to guess:
7
+ * - outcome 'repaired': `output` holds the repaired GLB. Its accessors are proven byte-identical to the
8
+ * input's (compared as a multiset of content hashes after re-reading the written file) — only the node and
9
+ * skin graph changed.
10
+ * - outcome 'nothing-to-repair': every scene already had its own skeleton; `output` is a BYTE-IDENTICAL copy.
11
+ * - anything the repair cannot do safely throws (see the stage's refusals) and writes nothing.
12
+ */
13
+ export type RepairOutcome = 'repaired' | 'nothing-to-repair';
14
+ export interface RepairLodSkeletonsFileResult extends SplitSharedLodSkeletonsResult {
15
+ outcome: RepairOutcome;
16
+ input: string;
17
+ output: string;
18
+ skinsBefore: number;
19
+ skinsAfter: number;
20
+ nodesBefore: number;
21
+ nodesAfter: number;
22
+ /** Accessors compared by content hash between input and written output (all must match). */
23
+ accessorsVerified: number;
24
+ }
25
+ export declare function repairLodSkeletonsFile(inputPath: string, outputPath: string): Promise<RepairLodSkeletonsFileResult>;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.repairLodSkeletonsFile = repairLodSkeletonsFile;
4
+ const core_1 = require("@gltf-transform/core");
5
+ const extensions_1 = require("@gltf-transform/extensions");
6
+ const node_crypto_1 = require("node:crypto");
7
+ const node_fs_1 = require("node:fs");
8
+ const node_path_1 = require("node:path");
9
+ const meshoptimizer_1 = require("meshoptimizer");
10
+ const splitSharedLodSkeletons_1 = require("./stages/splitSharedLodSkeletons");
11
+ function accessorFingerprints(document) {
12
+ return document.getRoot().listAccessors().map((accessor) => {
13
+ const array = accessor.getArray();
14
+ const bytes = array ? Buffer.from(array.buffer, array.byteOffset, array.byteLength) : Buffer.alloc(0);
15
+ return `${accessor.getType()}:${accessor.getComponentType()}:${accessor.getCount()}:${accessor.getNormalized()}:${(0, node_crypto_1.createHash)('sha256').update(bytes).digest('hex')}`;
16
+ }).sort();
17
+ }
18
+ async function repairLodSkeletonsFile(inputPath, outputPath) {
19
+ await Promise.all([meshoptimizer_1.MeshoptDecoder.ready, meshoptimizer_1.MeshoptEncoder.ready]);
20
+ const io = new core_1.NodeIO()
21
+ .registerExtensions(extensions_1.ALL_EXTENSIONS)
22
+ .registerDependencies({ 'meshopt.decoder': meshoptimizer_1.MeshoptDecoder, 'meshopt.encoder': meshoptimizer_1.MeshoptEncoder });
23
+ const input = (0, node_path_1.resolve)(inputPath), output = (0, node_path_1.resolve)(outputPath);
24
+ const inputBytes = (0, node_fs_1.readFileSync)(input);
25
+ const document = await io.readBinary(inputBytes);
26
+ const before = accessorFingerprints(document);
27
+ const skinsBefore = document.getRoot().listSkins().length, nodesBefore = document.getRoot().listNodes().length;
28
+ const split = (0, splitSharedLodSkeletons_1.splitSharedLodSkeletons)(document);
29
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(output), { recursive: true });
30
+ const base = { input, output, skinsBefore, nodesBefore, ...split };
31
+ if (split.scenesRepaired === 0) {
32
+ if (input !== output)
33
+ (0, node_fs_1.copyFileSync)(input, output);
34
+ return { ...base, outcome: 'nothing-to-repair', skinsAfter: skinsBefore, nodesAfter: nodesBefore, accessorsVerified: before.length };
35
+ }
36
+ const bytes = await io.writeBinary(document);
37
+ const written = await io.readBinary(bytes);
38
+ const after = accessorFingerprints(written);
39
+ if (after.length !== before.length || after.some((fingerprint, i) => fingerprint !== before[i])) {
40
+ throw new Error(`repair: accessor data changed during the repair (${before.length} → ${after.length} accessors) — refusing to write ${output}`);
41
+ }
42
+ (0, node_fs_1.writeFileSync)(output, bytes);
43
+ return {
44
+ ...base, outcome: 'repaired',
45
+ skinsAfter: written.getRoot().listSkins().length, nodesAfter: written.getRoot().listNodes().length,
46
+ accessorsVerified: after.length,
47
+ };
48
+ }
49
+ //# sourceMappingURL=repairLodSkeletonsFile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repairLodSkeletonsFile.js","sourceRoot":"","sources":["../../src/character/repairLodSkeletonsFile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { Document } from '@gltf-transform/core';
2
+ /**
3
+ * REPAIR IN PLACE: gives every LOD scene of an already-packaged character GLB its own copy of a skeleton that
4
+ * several scenes share — undoing helix-creator-cli #219's fold without the original source.
5
+ *
6
+ * #219 grouped a multi-LOD add-on's per-LOD skins by their ordered joint names, repointed every LOD's skinned
7
+ * mesh at the first skin, rooted that one skeleton in every scene, and disposed the other copies. Those copies
8
+ * were byte-for-byte duplicates — every LOD level was parsed from the same conformed bytes, so joint names,
9
+ * order, local transforms and inverse bind matrices were identical — so nothing was lost that cannot be
10
+ * rebuilt: for each scene after the first that roots a shared subtree, clone that subtree (names, TRS,
11
+ * extras, the same mesh/skin references), give the scene's skinned meshes a new Skin over the cloned joints
12
+ * with the SAME inverse-bind accessor, and swap the scene's root reference to the clone. The result is the
13
+ * pre-#219 structure, which three.js loads (see `mergeLods`' header for why the shared form does not).
14
+ *
15
+ * Only the node/skin graph changes: every accessor (positions, weights, joints, IBMs), mesh, material and
16
+ * texture is untouched and keeps its identity. The outcome is checked with `assertSelfContainedLodScenes`.
17
+ *
18
+ * Refuses (throws) rather than guesses when:
19
+ * - a LOD scene has no skinned mesh left — #219's dispose would have deleted a mesh node parented UNDER a
20
+ * duplicate bone, which no repair can recover (re-extract that item);
21
+ * - a skin mixes joints from a shared subtree with joints owned by its own scene;
22
+ * - a node to be cloned carries glTF extensions (never seen on a character skeleton; not silently dropped).
23
+ */
24
+ export interface SplitSharedLodSkeletonsResult {
25
+ scenes: number;
26
+ /** Scenes that received their own copy of a shared subtree. */
27
+ scenesRepaired: number;
28
+ nodesCloned: number;
29
+ skinsCreated: number;
30
+ }
31
+ export declare function splitSharedLodSkeletons(document: Document): SplitSharedLodSkeletonsResult;
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.splitSharedLodSkeletons = splitSharedLodSkeletons;
4
+ const mergeLods_1 = require("./mergeLods");
5
+ function subtree(roots) {
6
+ const out = [];
7
+ const seen = new Set();
8
+ const stack = [...roots];
9
+ while (stack.length > 0) {
10
+ const node = stack.pop();
11
+ if (seen.has(node))
12
+ continue;
13
+ seen.add(node);
14
+ out.push(node);
15
+ stack.push(...node.listChildren());
16
+ }
17
+ return out;
18
+ }
19
+ function cloneTree(document, root, map) {
20
+ if (root.listExtensions().length > 0) {
21
+ throw new Error(`repair: node '${root.getName()}' carries glTF extensions (${root.listExtensions().map((e) => e.extensionName).join(', ')}) — refusing to clone it lossily`);
22
+ }
23
+ const copy = document.createNode(root.getName())
24
+ .setTranslation([...root.getTranslation()])
25
+ .setRotation([...root.getRotation()])
26
+ .setScale([...root.getScale()])
27
+ .setWeights([...root.getWeights()])
28
+ .setExtras(structuredClone(root.getExtras()));
29
+ const mesh = root.getMesh();
30
+ if (mesh)
31
+ copy.setMesh(mesh);
32
+ const skin = root.getSkin();
33
+ if (skin)
34
+ copy.setSkin(skin); // remapped once every joint copy exists
35
+ const camera = root.getCamera();
36
+ if (camera)
37
+ copy.setCamera(camera);
38
+ map.set(root, copy);
39
+ for (const child of root.listChildren())
40
+ copy.addChild(cloneTree(document, child, map));
41
+ return copy;
42
+ }
43
+ function splitSharedLodSkeletons(document) {
44
+ const scenes = document.getRoot().listScenes();
45
+ const result = { scenes: scenes.length, scenesRepaired: 0, nodesCloned: 0, skinsCreated: 0 };
46
+ const owner = new Map();
47
+ for (const scene of scenes) {
48
+ for (const node of subtree(scene.listChildren()))
49
+ if (!owner.has(node))
50
+ owner.set(node, scene);
51
+ }
52
+ for (const scene of scenes) {
53
+ const shared = scene.listChildren().filter((child) => owner.get(child) !== scene);
54
+ if (shared.length === 0)
55
+ continue;
56
+ const map = new Map();
57
+ for (const root of shared) {
58
+ const clone = cloneTree(document, root, map);
59
+ scene.removeChild(root);
60
+ scene.addChild(clone);
61
+ }
62
+ result.nodesCloned += map.size;
63
+ result.scenesRepaired++;
64
+ // Every skinned mesh in this scene that is bound to cloned joints gets a skin over the clones.
65
+ const rebuilt = new Map();
66
+ for (const node of subtree(scene.listChildren())) {
67
+ const skin = node.getSkin();
68
+ if (!skin)
69
+ continue;
70
+ const joints = skin.listJoints();
71
+ const mapped = joints.map((joint) => map.get(joint));
72
+ if (mapped.every((joint) => joint === undefined))
73
+ continue; // bound to this scene's own joints already
74
+ if (mapped.some((joint) => joint === undefined)) {
75
+ throw new Error(`repair: skin '${skin.getName()}' in scene '${scene.getName()}' mixes shared and scene-owned joints — cannot split it safely`);
76
+ }
77
+ let replacement = rebuilt.get(skin);
78
+ if (!replacement) {
79
+ replacement = document.createSkin(skin.getName());
80
+ for (const joint of mapped)
81
+ replacement.addJoint(joint);
82
+ const ibm = skin.getInverseBindMatrices();
83
+ if (ibm)
84
+ replacement.setInverseBindMatrices(ibm); // same bytes, same accessor: identical binding
85
+ const skeleton = skin.getSkeleton();
86
+ if (skeleton)
87
+ replacement.setSkeleton(map.get(skeleton) ?? skeleton);
88
+ replacement.setExtras(structuredClone(skin.getExtras()));
89
+ rebuilt.set(skin, replacement);
90
+ result.skinsCreated++;
91
+ }
92
+ node.setSkin(replacement);
93
+ }
94
+ }
95
+ scenes.forEach((scene, index) => {
96
+ const skinned = subtree(scene.listChildren()).filter((node) => node.getMesh() && node.getSkin());
97
+ if (skinned.length === 0) {
98
+ throw new Error(`repair: LOD scene ${index + 1} ('${scene.getName()}') has no skinned mesh — it cannot be rebuilt from this file; re-extract the item`);
99
+ }
100
+ });
101
+ (0, mergeLods_1.assertSelfContainedLodScenes)(document);
102
+ return result;
103
+ }
104
+ //# sourceMappingURL=splitSharedLodSkeletons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"splitSharedLodSkeletons.js","sourceRoot":"","sources":["../../../src/character/stages/splitSharedLodSkeletons.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -3723,6 +3723,32 @@ character
3723
3723
  fail(`✖ ${err instanceof Error ? err.message : String(err)}`);
3724
3724
  }
3725
3725
  });
3726
+ character
3727
+ .command('repair-lod-skeletons')
3728
+ .description("Repair a packaged multi-LOD add-on whose LOD scenes share one skeleton (helix-creator-cli #219's output, which three.js loads with undefined bones): give every LOD scene its own copy, changing only the node/skin graph")
3729
+ .argument('<glb>', 'packaged add-on GLB')
3730
+ .requiredOption('-o, --output <glb>', 'output GLB: the repaired file, or a byte-identical copy when nothing is shared')
3731
+ .option('--json', 'print the result as JSON')
3732
+ .addHelpText('after', '\nExit codes: 0 = repaired · 3 = nothing to repair (output is a byte-identical copy) · 1 = refused/error (nothing written).\nCheck the result with `node scripts/engine-bind-check.mjs <out.glb> --host <base.glb>` before publishing.')
3733
+ .action(async (glb, opts) => {
3734
+ try {
3735
+ const { repairLodSkeletonsFile } = await Promise.resolve().then(() => __importStar(require('./character/repairLodSkeletonsFile')));
3736
+ const result = await repairLodSkeletonsFile((0, node_path_1.resolve)(glb), (0, node_path_1.resolve)(opts.output));
3737
+ if (opts.json)
3738
+ console.log(JSON.stringify(result, null, 2));
3739
+ else if (result.outcome === 'repaired') {
3740
+ console.log(`✔ repaired: ${result.scenesRepaired}/${result.scenes} LOD scene(s) given their own skeleton — skins ${result.skinsBefore} → ${result.skinsAfter}, nodes ${result.nodesBefore} → ${result.nodesAfter}; ${result.accessorsVerified} accessor(s) verified byte-identical`);
3741
+ console.log(` output: ${result.output}`);
3742
+ }
3743
+ else {
3744
+ console.log(`• nothing to repair: every LOD scene already has its own skeleton — output is a byte-identical copy (${result.output})`);
3745
+ }
3746
+ process.exitCode = result.outcome === 'repaired' ? 0 : 3;
3747
+ }
3748
+ catch (err) {
3749
+ fail(`✖ ${err instanceof Error ? err.message : String(err)}`);
3750
+ }
3751
+ });
3726
3752
  character
3727
3753
  .command('retarget-animation')
3728
3754
  .description('Retarget one raw Meshy/Mixamo FBX or GLB directly to a helix-humanoid@1 animation clip')