mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-29 18:51:43 +02:00
Edit the full print-parameter set from the slice dialog
Slicing from Bambuddy meant taking a process preset as-is; any change meant a round trip through Bambu Studio. The slice dialog now carries OrcaSlicer's full process tree -- pages, groups, labels, tooltips, ranges and defaults extracted from the slicer's own sources. Enable/disable rules are evaluated from the slicer's own enable_if expressions via a recursive-descent interpreter (no eval, CSP), with enum comparisons validated against each option's declared values. Anything undecidable leaves the field editable rather than greyed. Overrides apply after the source's support config (#1881) and the designer's carried tweaks (#2622), so an explicit choice always wins; an untouched panel sends the same request as before. Adds slice_engine as a separate setting from preferred_slicer -- where slicing runs is a different axis from which binary the sidecar drives. Only the sidecar engine is registered, so no picker renders yet.
This commit is contained in:
@@ -220,6 +220,7 @@ const FR_COGNATES = [
|
||||
'Compact', // cam-wall status overlay mode — same word in French
|
||||
'ntfy, Pushover, Discord, etc.',
|
||||
'{{filament}} @ {{temp}}°C', // drying badge: filament code + universal °C
|
||||
'Simple', 'Expert', // slicer settings visibility tiers — identical words in French
|
||||
];
|
||||
|
||||
// Italian cognates.
|
||||
@@ -365,6 +366,7 @@ const ES_COGNATES = [
|
||||
'Avery L7160 — A4 sheet (38.1 × 63.5 mm × 21)',
|
||||
'Avery 5160 — US Letter sheet (25.4 × 66.7 mm × 30)',
|
||||
'{{filament}} @ {{temp}}°C', // drying badge: filament code + universal °C
|
||||
'Simple', // slicer settings visibility tier — identical word in Spanish
|
||||
];
|
||||
|
||||
// Turkish cognates — technical UI labels that Turkish speakers use verbatim
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
// 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 <path-to-three-slicer-package>
|
||||
//
|
||||
// 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 <path-to-three-slicer-package>');
|
||||
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(', ')}`);
|
||||
Reference in New Issue
Block a user