// Regenerates the vendored OrcaSlicer process-settings metadata under // src/data/slicer/ from the `three-slicer` npm package. // // Why vendored and not a runtime dependency: we need three of the package's // four data files, trimmed to the *process* tab only, and none of its engine, // viewer or React code. // Pulling `three-slicer` as a dependency would drag in an 8 MB WASM kernel and // a `three@^0.160` peer pin that conflicts with our three@^0.181. // // Usage: node scripts/generate-slicer-schema.mjs // // The upstream data is AGPL-3.0-or-later, extracted from OrcaSlicer's C++ // sources — same licence as Bambuddy, so vendoring is clean. Re-run this when // bumping to a newer three-slicer release and commit the regenerated output. import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'; import { join, resolve } from 'node:path'; const src = process.argv[2]; if (!src) { console.error('usage: node scripts/generate-slicer-schema.mjs '); process.exit(1); } const OUT_DIR = resolve(import.meta.dirname, '..', 'src', 'data', 'slicer'); const readJson = (p) => JSON.parse(readFileSync(join(src, p), 'utf8')); const schema = readJson('data/config-schema.json'); const uiTree = readJson('data/ui-tree.json'); const toggles = readJson('data/toggle-rules.json'); // --- 1. UI tree, process tab only ----------------------------------------- // TabPrint::build is the process/print preset — the one whose JSON our slice // route patches. Filament and printer presets are separate objects on the // sidecar and out of scope for this panel. const pages = uiTree['TabPrint::build']; if (!Array.isArray(pages)) throw new Error('ui-tree.json has no TabPrint::build array'); // Tab.cpp references that PrintConfig.cpp no longer defines, collected while // walking the tree so the run can report them. const dropped = []; // Drop the C++ source line numbers: useful for the extractor, noise for us. const trimmedPages = pages.map((page) => ({ page: page.page, icon: page.icon, groups: (page.groups ?? []).map((g) => ({ group: g.group, options: (g.options ?? []).filter((key) => { if (!schema[key]) { // A handful of Tab.cpp references point at options that no longer // exist in PrintConfig.cpp. Silently dropping them keeps the panel // from rendering a control with no type, label or default. dropped.push(key); return false; } return true; }), })).filter((g) => g.options.length > 0), })).filter((p) => p.groups.length > 0); // --- 2. Schema, trimmed to the options the tree actually references -------- const referenced = new Set(trimmedPages.flatMap((p) => p.groups.flatMap((g) => g.options))); // Toggle rules reference options for their *conditions* too (e.g. wall_loops // gates have_perimeters). Those must survive the trim or the evaluator reads a // default of `undefined` and fails open on a rule it could have decided. const CONDITION_KEYS = [ 'wall_loops', 'sparse_infill_density', 'top_shell_layers', 'bottom_shell_layers', 'spiral_mode', 'skirt_loops', 'enable_support', 'raft_layers', 'enable_prime_tower', 'support_interface_top_layers', 'support_interface_bottom_layers', 'sparse_infill_pattern', 'support_type', 'support_style', 'wall_generator', 'timelapse_type', 'infill_combination', 'detect_thin_wall', 'ironing_type', 'default_acceleration', 'adaptive_layer_height', ]; for (const k of CONDITION_KEYS) if (schema[k]) referenced.add(k); // Only the fields the panel renders or the evaluator reads. This is what keeps // the vendored payload proportionate: the upstream schema is 384 KB across 907 // options, most of it source-location bookkeeping we have no use for. const KEEP = ['type', 'mode', 'label', 'tooltip', 'sidetext', 'min', 'max', 'enum_values', 'enum_labels', 'default']; const trimmedSchema = {}; for (const key of [...referenced].sort()) { const opt = schema[key]; const out = {}; for (const f of KEEP) if (opt[f] !== undefined) out[f] = opt[f]; trimmedSchema[key] = out; } // --- 3. Toggle rules, FFF print options only ------------------------------ // The other rule groups drive the filament and printer tabs, which this panel // does not render. const fff = toggles['toggle_print_fff_options'] ?? {}; const trimmedToggles = { locals: fff.locals ?? {}, rules: (fff.rules ?? []) .filter((r) => r.enable_if && Array.isArray(r.fields)) // A rule whose fields are all outside our trimmed set can never change // anything the panel shows. .map((r) => ({ fields: r.fields.filter((f) => referenced.has(f)), enable_if: r.enable_if })) .filter((r) => r.fields.length > 0), }; mkdirSync(OUT_DIR, { recursive: true }); const write = (name, data) => { const path = join(OUT_DIR, name); writeFileSync(path, JSON.stringify(data, null, 0) + '\n'); return `${name}: ${(readFileSync(path).length / 1024).toFixed(1)} KB`; }; console.log(write('process-ui-tree.json', trimmedPages)); console.log(write('process-schema.json', trimmedSchema)); console.log(write('process-toggle-rules.json', trimmedToggles)); console.log(`options: ${Object.keys(trimmedSchema).length}, pages: ${trimmedPages.length}, rules: ${trimmedToggles.rules.length}`); if (dropped.length) console.log(`dropped (no schema entry): ${dropped.join(', ')}`);