generate-slicer-schema.mjs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. // Regenerates the vendored OrcaSlicer process-settings metadata under
  2. // src/data/slicer/ from the `three-slicer` npm package.
  3. //
  4. // Why vendored and not a runtime dependency: we need three of the package's
  5. // four data files, trimmed to the *process* tab only, and none of its engine,
  6. // viewer or React code.
  7. // Pulling `three-slicer` as a dependency would drag in an 8 MB WASM kernel and
  8. // a `three@^0.160` peer pin that conflicts with our three@^0.181.
  9. //
  10. // Usage: node scripts/generate-slicer-schema.mjs <path-to-three-slicer-package>
  11. //
  12. // The upstream data is AGPL-3.0-or-later, extracted from OrcaSlicer's C++
  13. // sources — same licence as Bambuddy, so vendoring is clean. Re-run this when
  14. // bumping to a newer three-slicer release and commit the regenerated output.
  15. import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
  16. import { join, resolve } from 'node:path';
  17. const src = process.argv[2];
  18. if (!src) {
  19. console.error('usage: node scripts/generate-slicer-schema.mjs <path-to-three-slicer-package>');
  20. process.exit(1);
  21. }
  22. const OUT_DIR = resolve(import.meta.dirname, '..', 'src', 'data', 'slicer');
  23. const readJson = (p) => JSON.parse(readFileSync(join(src, p), 'utf8'));
  24. const schema = readJson('data/config-schema.json');
  25. const uiTree = readJson('data/ui-tree.json');
  26. const toggles = readJson('data/toggle-rules.json');
  27. // --- 1. UI tree, process tab only -----------------------------------------
  28. // TabPrint::build is the process/print preset — the one whose JSON our slice
  29. // route patches. Filament and printer presets are separate objects on the
  30. // sidecar and out of scope for this panel.
  31. const pages = uiTree['TabPrint::build'];
  32. if (!Array.isArray(pages)) throw new Error('ui-tree.json has no TabPrint::build array');
  33. // Tab.cpp references that PrintConfig.cpp no longer defines, collected while
  34. // walking the tree so the run can report them.
  35. const dropped = [];
  36. // Drop the C++ source line numbers: useful for the extractor, noise for us.
  37. const trimmedPages = pages.map((page) => ({
  38. page: page.page,
  39. icon: page.icon,
  40. groups: (page.groups ?? []).map((g) => ({
  41. group: g.group,
  42. options: (g.options ?? []).filter((key) => {
  43. if (!schema[key]) {
  44. // A handful of Tab.cpp references point at options that no longer
  45. // exist in PrintConfig.cpp. Silently dropping them keeps the panel
  46. // from rendering a control with no type, label or default.
  47. dropped.push(key);
  48. return false;
  49. }
  50. return true;
  51. }),
  52. })).filter((g) => g.options.length > 0),
  53. })).filter((p) => p.groups.length > 0);
  54. // --- 2. Schema, trimmed to the options the tree actually references --------
  55. const referenced = new Set(trimmedPages.flatMap((p) => p.groups.flatMap((g) => g.options)));
  56. // Toggle rules reference options for their *conditions* too (e.g. wall_loops
  57. // gates have_perimeters). Those must survive the trim or the evaluator reads a
  58. // default of `undefined` and fails open on a rule it could have decided.
  59. const CONDITION_KEYS = [
  60. 'wall_loops', 'sparse_infill_density', 'top_shell_layers', 'bottom_shell_layers',
  61. 'spiral_mode', 'skirt_loops', 'enable_support', 'raft_layers', 'enable_prime_tower',
  62. 'support_interface_top_layers', 'support_interface_bottom_layers', 'sparse_infill_pattern',
  63. 'support_type', 'support_style', 'wall_generator', 'timelapse_type', 'infill_combination',
  64. 'detect_thin_wall', 'ironing_type', 'default_acceleration', 'adaptive_layer_height',
  65. ];
  66. for (const k of CONDITION_KEYS) if (schema[k]) referenced.add(k);
  67. // Only the fields the panel renders or the evaluator reads. This is what keeps
  68. // the vendored payload proportionate: the upstream schema is 384 KB across 907
  69. // options, most of it source-location bookkeeping we have no use for.
  70. const KEEP = ['type', 'mode', 'label', 'tooltip', 'sidetext', 'min', 'max', 'enum_values', 'enum_labels', 'default'];
  71. const trimmedSchema = {};
  72. for (const key of [...referenced].sort()) {
  73. const opt = schema[key];
  74. const out = {};
  75. for (const f of KEEP) if (opt[f] !== undefined) out[f] = opt[f];
  76. trimmedSchema[key] = out;
  77. }
  78. // --- 3. Toggle rules, FFF print options only ------------------------------
  79. // The other rule groups drive the filament and printer tabs, which this panel
  80. // does not render.
  81. const fff = toggles['toggle_print_fff_options'] ?? {};
  82. const trimmedToggles = {
  83. locals: fff.locals ?? {},
  84. rules: (fff.rules ?? [])
  85. .filter((r) => r.enable_if && Array.isArray(r.fields))
  86. // A rule whose fields are all outside our trimmed set can never change
  87. // anything the panel shows.
  88. .map((r) => ({ fields: r.fields.filter((f) => referenced.has(f)), enable_if: r.enable_if }))
  89. .filter((r) => r.fields.length > 0),
  90. };
  91. mkdirSync(OUT_DIR, { recursive: true });
  92. const write = (name, data) => {
  93. const path = join(OUT_DIR, name);
  94. writeFileSync(path, JSON.stringify(data, null, 0) + '\n');
  95. return `${name}: ${(readFileSync(path).length / 1024).toFixed(1)} KB`;
  96. };
  97. console.log(write('process-ui-tree.json', trimmedPages));
  98. console.log(write('process-schema.json', trimmedSchema));
  99. console.log(write('process-toggle-rules.json', trimmedToggles));
  100. console.log(`options: ${Object.keys(trimmedSchema).length}, pages: ${trimmedPages.length}, rules: ${trimmedToggles.rules.length}`);
  101. if (dropped.length) console.log(`dropped (no schema entry): ${dropped.join(', ')}`);