Files
workinf_Blender_Wasm/my-tools/diagnose_local_object_editor.mjs
mes123456 f3e21c6b98
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
chore: checkpoint web Blender migration source, tools and docs
Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-10 11:18:01 +08:00

92 lines
5.0 KiB
JavaScript

import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import createEngine from '../build_web_blender6/bin/web_engine.js';
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const stages = [];
const begin = performance.now();
const engine = await createEngine({ wasmBinary: fs.readFileSync(path.join(root, 'build_web_blender6/bin/web_engine.wasm')) });
stages.push({ stage: 'instantiate', seconds: (performance.now() - begin) / 1000 });
const handle = engine._web_engine_create();
const pointers = engine._malloc(8);
const call = (stage, data) => {
const started = performance.now();
const pointer = data ? engine._malloc(data.length) : 0;
try {
if (data) engine.HEAPU8.set(data, pointer);
const code = stage === 'open'
? engine._web_engine_open_blend(handle, pointer, data.length)
: stage === 'snapshot'
? engine._web_engine_get_scene_snapshot(handle, pointers, pointers + 4)
: engine._web_engine_call_json(handle, pointer, data.length, pointers, pointers + 4);
if (code !== 0) throw new Error(engine.UTF8ToString(engine._web_engine_last_error_message(handle)));
if (stage === 'open') return null;
const output = engine.HEAPU32[pointers / 4];
const length = engine.HEAPU32[pointers / 4 + 1];
return JSON.parse(new TextDecoder().decode(engine.HEAPU8.slice(output, output + length)));
} finally {
if (pointer) engine._free(pointer);
stages.push({ stage, seconds: (performance.now() - started) / 1000 });
}
};
const edit = (command, expected = true) => {
const result = call(command.type, Buffer.from(JSON.stringify({ cmd: 'applyCommand', params: { command } })));
assert.equal(result.ok, expected, result.error ?? command.type);
};
try {
const hierarchy = process.argv.includes('--hierarchy');
call('open', fs.readFileSync(path.join(root, 'tests/files/web',
hierarchy ? 'object_editor_visibility_scene.blend' : 'object_editor_scene.blend')));
const initial = call('snapshot');
const cube = initial.nodes.find(node => node.name === 'BasicCube');
const collection = initial.collections.find(collection => collection.name === 'Collection');
assert(cube && collection);
edit({ type: 'clearObjectTransform', objectId: cube.id, channels: ['LOCATION', 'ROTATION', 'SCALE'] });
const cleared = call('snapshot').nodes.find(node => node.id === cube.id);
if (process.argv.includes('--inspect')) console.log(JSON.stringify({ transform: cleared.transform }));
assert.deepEqual(cleared.transform.translation, [0, 0, 0]);
assert.deepEqual(cleared.transform.rotationEuler, [0, 0, 0]);
assert.deepEqual(cleared.transform.scale, [1, 1, 1]);
for (const objectType of ['CAMERA', 'LIGHT', 'EMPTY', 'ARMATURE']) {
const name = `Object Editor ${objectType}`;
edit({ type: 'createObjectData', objectType, name });
assert.equal(call('snapshot').nodes.find(node => node.name === name)?.type, objectType);
}
edit({ type: 'setObjectSelection', objectId: cube.id, selected: true, active: true });
assert.equal(call('snapshot').activeObjectId, cube.id);
edit({ type: 'setCollectionVisibility', collectionId: collection.id, viewportVisible: false, renderVisible: false });
const hidden = call('snapshot');
assert.equal(hidden.nodes.find(node => node.id === cube.id)?.visible, false,
'hidden collection must hide its objects in the Web snapshot');
if (hierarchy) {
assert.equal(hidden.nodes.find(node => node.name === 'BasicCamera')?.visible, false);
assert.equal(hidden.nodes.find(node => node.name === 'BasicArea')?.visible, true);
assert.equal(hidden.nodes.find(node => node.name === 'ObjectEditor Hidden')?.visible, false);
}
edit({ type: 'setCollectionVisibility', collectionId: collection.id, viewportVisible: true, renderVisible: true });
const shown = call('snapshot');
assert.equal(shown.nodes.find(node => node.id === cube.id)?.visible, true);
if (hierarchy) {
assert.equal(shown.nodes.find(node => node.name === 'BasicCamera')?.visible, true);
assert.equal(shown.nodes.find(node => node.name === 'BasicArea')?.visible, true);
assert.equal(shown.nodes.find(node => node.name === 'ObjectEditor Hidden')?.visible, false);
}
for (const command of [
{ type: 'clearObjectTransform', objectId: 'object:Missing', channels: ['LOCATION'] },
{ type: 'createObjectData', objectType: 'UNSUPPORTED', name: 'Invalid' },
{ type: 'setObjectSelection', objectId: 'object:Missing', selected: true, active: true },
{ type: 'setCollectionVisibility', collectionId: 'collection:Missing', viewportVisible: false },
]) edit(command, false);
assert.equal(call('snapshot').nodes.length, initial.nodes.length + 4);
console.log(JSON.stringify({ status: 'PASS', stages }));
} catch (error) {
console.log(JSON.stringify({ status: 'FAIL', error: error.message,
frames: String(error.stack).split('\n').filter(line => /wasm-function|wasm:/.test(line)).slice(0, 10), stages }));
process.exitCode = 1;
} finally {
engine._free(pointers);
engine._web_engine_destroy(handle);
}