mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-18 21:47:39 +02:00
feat(print-options): add "Auto" state to bed levelling, flow & nozzle-offset calibration
Bed levelling, flow calibration, and nozzle-offset calibration were on/off only, so the sole way to run bed levelling was to force a full level before every print. Bambu Studio has always offered a third "Auto" state that lets the printer skip the calibration when it was done recently -- the state most users actually want. Make these three options tri-state (off/on/auto), defaulting to auto, and leave vibration/layer-inspect/timelapse as on/off (Bambu Studio exposes no auto for those). Wire encoding follows Bambu Studio's source exactly: each option sends a JSON bool (true only for "on") plus a companion int -- off=0, on=1, auto=2. The bool fields stay booleans (the #1478 H2S regression); only the companion int widened from {0,1} to {0,1,2}. #1721's observation that stage 8/39 stays queued when sending 2 is the auto contract (queued, skipped at runtime if recent), not a broken "off". - schemas: TriState = Literal[off/on/auto] with a BeforeValidator coercing legacy bool / 0-1 / true-false so old clients and un-migrated rows validate - model + migration: boolean columns -> String; SQLite via column affinity + data backfill, PostgreSQL via ALTER COLUMN TYPE guarded on information_schema (verified on both dialects); settings rows normalised true/false -> on/off - MQTT: start_print takes the tri-state strings and emits the paired bool+int - Virtual Printer: reconstructs the slicer's auto/on/off from the int companion (auto_bed_leveling / extrude_cali_flag) in both capture paths - frontend: CalibrationMode type; off/auto/on segmented controls in the print dialog, queue bulk-edit, and Settings -> Workflow; calibrationMode_* strings in all 11 locales
This commit is contained in:
@@ -1,15 +1,30 @@
|
||||
import { useState } from 'react';
|
||||
import { Settings, ChevronDown, ChevronUp, Flame } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { PrintOptionsProps, PrintOptions as PrintOptionsType, PreheatOverride } from './types';
|
||||
import type {
|
||||
PrintOptionsProps,
|
||||
PrintOptions as PrintOptionsType,
|
||||
PreheatOverride,
|
||||
CalibrationMode,
|
||||
} from './types';
|
||||
import {
|
||||
CALIBRATION_MODES,
|
||||
CALIBRATION_MODE_ACTIVE,
|
||||
CALIBRATION_MODE_INACTIVE,
|
||||
} from '../../utils/calibrationMode';
|
||||
|
||||
type OptionConfig = {
|
||||
key: keyof PrintOptionsType;
|
||||
label: string;
|
||||
desc: string;
|
||||
dualNozzleOnly?: boolean;
|
||||
/** Tri-state (off/on/auto) rather than a plain on/off pair. */
|
||||
tristate?: boolean;
|
||||
};
|
||||
|
||||
// On/off options render as the same button pair, minus the "auto" choice.
|
||||
const BOOLEAN_MODES = ['off', 'on'] as const;
|
||||
|
||||
/**
|
||||
* Print options toggle panel with collapsible UI.
|
||||
* Shows bed levelling, flow/vibration calibration, layer inspection, timelapse,
|
||||
@@ -27,18 +42,22 @@ export function PrintOptionsPanel({
|
||||
// Labels/descriptions reuse the settings.default* namespace — identical strings,
|
||||
// already translated across all locales. Only nozzle_offset_cali is new (#1682).
|
||||
const printOptionsConfig: OptionConfig[] = [
|
||||
{ key: 'bed_levelling', label: t('settings.defaultBedLevelling'), desc: t('settings.defaultBedLevellingDesc') },
|
||||
{ key: 'flow_cali', label: t('settings.defaultFlowCali'), desc: t('settings.defaultFlowCaliDesc') },
|
||||
{ key: 'bed_levelling', label: t('settings.defaultBedLevelling'), desc: t('settings.defaultBedLevellingDesc'), tristate: true },
|
||||
{ key: 'flow_cali', label: t('settings.defaultFlowCali'), desc: t('settings.defaultFlowCaliDesc'), tristate: true },
|
||||
{ key: 'vibration_cali', label: t('settings.defaultVibrationCali'), desc: t('settings.defaultVibrationCaliDesc') },
|
||||
{ key: 'layer_inspect', label: t('settings.defaultLayerInspect'), desc: t('settings.defaultLayerInspectDesc') },
|
||||
{ key: 'timelapse', label: t('settings.defaultTimelapse'), desc: t('settings.defaultTimelapseDesc') },
|
||||
{ key: 'nozzle_offset_cali', label: t('settings.defaultNozzleOffsetCali'), desc: t('settings.defaultNozzleOffsetCaliDesc'), dualNozzleOnly: true },
|
||||
{ key: 'nozzle_offset_cali', label: t('settings.defaultNozzleOffsetCali'), desc: t('settings.defaultNozzleOffsetCaliDesc'), dualNozzleOnly: true, tristate: true },
|
||||
];
|
||||
|
||||
const visibleOptions = printOptionsConfig.filter(o => !o.dualNozzleOnly || showDualNozzleOptions);
|
||||
|
||||
const handleToggle = (key: keyof PrintOptionsType) => {
|
||||
onChange({ ...options, [key]: !options[key] });
|
||||
const handleToggle = (key: keyof PrintOptionsType, value: boolean) => {
|
||||
onChange({ ...options, [key]: value });
|
||||
};
|
||||
|
||||
const handleCalibrationMode = (key: keyof PrintOptionsType, mode: CalibrationMode) => {
|
||||
onChange({ ...options, [key]: mode });
|
||||
};
|
||||
|
||||
const handlePreheatOverride = (next: PreheatOverride) => {
|
||||
@@ -81,26 +100,56 @@ export function PrintOptionsPanel({
|
||||
</button>
|
||||
{isExpanded && (
|
||||
<div className="mt-2 bg-bambu-dark rounded-lg p-3 space-y-2">
|
||||
{visibleOptions.map(({ key, label, desc }) => (
|
||||
<label key={key} className="flex items-center justify-between cursor-pointer group">
|
||||
<div>
|
||||
<span className="text-sm text-white">{label}</span>
|
||||
<p className="text-xs text-bambu-gray">{desc}</p>
|
||||
{visibleOptions.map(({ key, label, desc, tristate }) =>
|
||||
tristate ? (
|
||||
<div key={key} className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<span className="text-sm text-white">{label}</span>
|
||||
<p className="text-xs text-bambu-gray">{desc}</p>
|
||||
</div>
|
||||
<div className="flex gap-1 shrink-0">
|
||||
{CALIBRATION_MODES.map((mode) => (
|
||||
<button
|
||||
key={mode}
|
||||
type="button"
|
||||
onClick={() => handleCalibrationMode(key, mode)}
|
||||
className={`px-2.5 py-1 text-xs rounded transition-colors ${
|
||||
options[key as 'bed_levelling'] === mode
|
||||
? CALIBRATION_MODE_ACTIVE[mode]
|
||||
: CALIBRATION_MODE_INACTIVE
|
||||
}`}
|
||||
>
|
||||
{t(`settings.calibrationMode_${mode}`)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`relative w-10 h-5 rounded-full transition-colors ${
|
||||
options[key as 'bed_levelling'] ? 'bg-bambu-green' : 'bg-bambu-dark-tertiary'
|
||||
}`}
|
||||
onClick={() => handleToggle(key)}
|
||||
>
|
||||
<div
|
||||
className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-transform ${
|
||||
options[key as 'bed_levelling'] ? 'translate-x-5' : 'translate-x-0.5'
|
||||
}`}
|
||||
/>
|
||||
) : (
|
||||
<div key={key} className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<span className="text-sm text-white">{label}</span>
|
||||
<p className="text-xs text-bambu-gray">{desc}</p>
|
||||
</div>
|
||||
<div className="flex gap-1 shrink-0">
|
||||
{BOOLEAN_MODES.map((mode) => {
|
||||
const active = (options[key as 'vibration_cali'] ? 'on' : 'off') === mode;
|
||||
return (
|
||||
<button
|
||||
key={mode}
|
||||
type="button"
|
||||
onClick={() => handleToggle(key, mode === 'on')}
|
||||
className={`px-2.5 py-1 text-xs rounded transition-colors ${
|
||||
active ? CALIBRATION_MODE_ACTIVE[mode] : CALIBRATION_MODE_INACTIVE
|
||||
}`}
|
||||
>
|
||||
{t(`settings.calibrationMode_${mode}`)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
),
|
||||
)}
|
||||
|
||||
{/* Preheat / heat-soak per-item override (#1468). Defaults to
|
||||
'inherit' which means the global Settings → Workflow toggle
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { PrintQueueItem, Printer } from '../../api/client';
|
||||
import type { PrintQueueItem, Printer, CalibrationMode } from '../../api/client';
|
||||
|
||||
export type { CalibrationMode };
|
||||
|
||||
/**
|
||||
* Mode of operation for the PrintModal.
|
||||
@@ -44,12 +46,12 @@ export interface PrintModalProps {
|
||||
export type PreheatOverride = 'inherit' | 'on' | 'off';
|
||||
|
||||
export interface PrintOptions {
|
||||
bed_levelling: boolean;
|
||||
flow_cali: boolean;
|
||||
bed_levelling: CalibrationMode;
|
||||
flow_cali: CalibrationMode;
|
||||
vibration_cali: boolean;
|
||||
layer_inspect: boolean;
|
||||
timelapse: boolean;
|
||||
nozzle_offset_cali: boolean;
|
||||
nozzle_offset_cali: CalibrationMode;
|
||||
// Per-item preheat / heat-soak override (#1468). 'inherit' uses the global
|
||||
// Settings → Workflow toggle; 'on' / 'off' force the per-print decision.
|
||||
// chamber_target_override is non-null to bypass the per-filament-type
|
||||
@@ -62,12 +64,12 @@ export interface PrintOptions {
|
||||
* Default print options values.
|
||||
*/
|
||||
export const DEFAULT_PRINT_OPTIONS: PrintOptions = {
|
||||
bed_levelling: true,
|
||||
flow_cali: false,
|
||||
bed_levelling: 'auto',
|
||||
flow_cali: 'auto',
|
||||
vibration_cali: true,
|
||||
layer_inspect: false,
|
||||
timelapse: false,
|
||||
nozzle_offset_cali: true,
|
||||
nozzle_offset_cali: 'auto',
|
||||
preheat_override: 'inherit',
|
||||
preheat_chamber_target_override: null,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user