mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-22 03:49:18 +02:00
fix(print): expose nozzle_offset_cali toggle for dual-nozzle printers (#1682)
Bambuddy's project_file MQTT payload hardcoded "nozzle_offset_cali": 2 (skip), giving users on H2D / H2D Pro / H2C / X2D no way to control the same toggle BambuStudio exposes. Critical for diamond-nozzle setups that must keep the calibration off. start_print() now takes a nozzle_offset_cali kwarg; the value is encoded as 1 (run) or 2 (skip) and gated on is_dual_nozzle so single-nozzle machines always send 2 even if a stale flag arrives. The kwarg threads through printer_manager, both background_dispatch sites, and print_scheduler so every dispatch path respects the per-item setting. print_queue gains a nozzle_offset_cali column (DEFAULT TRUE, is_sqlite() branch for Postgres BOOLEAN). Settings default key default_nozzle_offset_cali defaults to TRUE to match BambuStudio. Schemas updated across print_queue, library FilePrintRequest, archive ReprintRequest, settings. PrintModal renders the new toggle only when the selected printer is dual- nozzle (printer-mode: nozzle_count===2; model-mode: DUAL_NOZZLE_MODELS). SettingsPage default-print-options row + QueuePage bulk-edit tri-state both hide unless any registered printer is dual-nozzle. Labels reuse the existing settings.default* keys so the only new i18n strings are settings.defaultNozzleOffsetCali / Desc and queue.bulkEdit.nozzleOffsetCali - real translations in all 11 locales.
This commit is contained in:
@@ -1,26 +1,42 @@
|
||||
import { useState } from 'react';
|
||||
import { Settings, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { PrintOptionsProps, PrintOptions as PrintOptionsType } from './types';
|
||||
|
||||
const PRINT_OPTIONS_CONFIG = [
|
||||
{ key: 'bed_levelling', label: 'Bed Levelling', desc: 'Auto-level bed before print' },
|
||||
{ key: 'flow_cali', label: 'Flow Calibration', desc: 'Calibrate extrusion flow' },
|
||||
{ key: 'vibration_cali', label: 'Vibration Calibration', desc: 'Reduce ringing artifacts' },
|
||||
{ key: 'layer_inspect', label: 'First Layer Inspection', desc: 'AI inspection of first layer' },
|
||||
{ key: 'timelapse', label: 'Timelapse', desc: 'Record timelapse video' },
|
||||
] as const;
|
||||
type OptionConfig = {
|
||||
key: keyof PrintOptionsType;
|
||||
label: string;
|
||||
desc: string;
|
||||
dualNozzleOnly?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Print options toggle panel with collapsible UI.
|
||||
* Shows bed levelling, flow/vibration calibration, layer inspection, and timelapse options.
|
||||
* Shows bed levelling, flow/vibration calibration, layer inspection, timelapse,
|
||||
* and (for dual-nozzle printers only) nozzle offset calibration.
|
||||
*/
|
||||
export function PrintOptionsPanel({
|
||||
options,
|
||||
onChange,
|
||||
defaultExpanded = false,
|
||||
showDualNozzleOptions = false,
|
||||
}: PrintOptionsProps) {
|
||||
const { t } = useTranslation();
|
||||
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
|
||||
|
||||
// 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: '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 },
|
||||
];
|
||||
|
||||
const visibleOptions = printOptionsConfig.filter(o => !o.dualNozzleOnly || showDualNozzleOptions);
|
||||
|
||||
const handleToggle = (key: keyof PrintOptionsType) => {
|
||||
onChange({ ...options, [key]: !options[key] });
|
||||
};
|
||||
@@ -33,7 +49,7 @@ export function PrintOptionsPanel({
|
||||
className="flex items-center gap-2 text-sm text-bambu-gray hover:text-white transition-colors w-full"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
<span>Print Options</span>
|
||||
<span>{t('queue.bulkEdit.printOptions')}</span>
|
||||
{isExpanded ? (
|
||||
<ChevronUp className="w-4 h-4 ml-auto" />
|
||||
) : (
|
||||
@@ -42,7 +58,7 @@ export function PrintOptionsPanel({
|
||||
</button>
|
||||
{isExpanded && (
|
||||
<div className="mt-2 bg-bambu-dark rounded-lg p-3 space-y-2">
|
||||
{PRINT_OPTIONS_CONFIG.map(({ key, label, desc }) => (
|
||||
{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>
|
||||
|
||||
@@ -100,6 +100,7 @@ export function PrintModal({
|
||||
vibration_cali: queueItem.vibration_cali ?? DEFAULT_PRINT_OPTIONS.vibration_cali,
|
||||
layer_inspect: queueItem.layer_inspect ?? DEFAULT_PRINT_OPTIONS.layer_inspect,
|
||||
timelapse: queueItem.timelapse ?? DEFAULT_PRINT_OPTIONS.timelapse,
|
||||
nozzle_offset_cali: queueItem.nozzle_offset_cali ?? DEFAULT_PRINT_OPTIONS.nozzle_offset_cali,
|
||||
};
|
||||
}
|
||||
return DEFAULT_PRINT_OPTIONS;
|
||||
@@ -237,6 +238,7 @@ export function PrintModal({
|
||||
vibration_cali: settings.default_vibration_cali ?? DEFAULT_PRINT_OPTIONS.vibration_cali,
|
||||
layer_inspect: settings.default_layer_inspect ?? DEFAULT_PRINT_OPTIONS.layer_inspect,
|
||||
timelapse: settings.default_timelapse ?? DEFAULT_PRINT_OPTIONS.timelapse,
|
||||
nozzle_offset_cali: settings.default_nozzle_offset_cali ?? DEFAULT_PRINT_OPTIONS.nozzle_offset_cali,
|
||||
});
|
||||
}, [settings, mode]);
|
||||
|
||||
@@ -917,6 +919,23 @@ export function PrintModal({
|
||||
isLibraryFile || (isMultiPlate ? selectedPlate !== null : true)
|
||||
);
|
||||
|
||||
// Dual-nozzle gate for the Nozzle Offset Calibration toggle (#1682).
|
||||
// Mirrors backend `DUAL_NOZZLE_MODELS` so model-based assignment can show
|
||||
// the toggle without a specific printer selected. For printer-mode we rely
|
||||
// on the canonical `nozzle_count` field auto-detected from MQTT.
|
||||
const DUAL_NOZZLE_MODELS = useMemo(
|
||||
() => new Set(['H2D', 'H2DPRO', 'H2C', 'X2D']),
|
||||
[],
|
||||
);
|
||||
const showDualNozzleOptions = useMemo(() => {
|
||||
if (assignmentMode === 'model') {
|
||||
if (!targetModel) return false;
|
||||
return DUAL_NOZZLE_MODELS.has(targetModel.toUpperCase().replace(/[\s-]/g, ''));
|
||||
}
|
||||
if (!printers || selectedPrinters.length === 0) return false;
|
||||
return selectedPrinters.some(id => printers.find(p => p.id === id)?.nozzle_count === 2);
|
||||
}, [assignmentMode, targetModel, printers, selectedPrinters, DUAL_NOZZLE_MODELS]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4"
|
||||
@@ -1067,7 +1086,12 @@ export function PrintModal({
|
||||
|
||||
{/* Print options */}
|
||||
{(mode === 'reprint' || effectivePrinterCount > 0 || (assignmentMode === 'model' && targetModel)) && (
|
||||
<PrintOptionsPanel options={printOptions} onChange={setPrintOptions} defaultExpanded={!!initialSelectedPrinterIds?.length} />
|
||||
<PrintOptionsPanel
|
||||
options={printOptions}
|
||||
onChange={setPrintOptions}
|
||||
defaultExpanded={!!initialSelectedPrinterIds?.length}
|
||||
showDualNozzleOptions={showDualNozzleOptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Quantity — create multiple copies (batch). Hidden for multi-printer selection. */}
|
||||
|
||||
@@ -48,6 +48,7 @@ export interface PrintOptions {
|
||||
vibration_cali: boolean;
|
||||
layer_inspect: boolean;
|
||||
timelapse: boolean;
|
||||
nozzle_offset_cali: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,6 +60,7 @@ export const DEFAULT_PRINT_OPTIONS: PrintOptions = {
|
||||
vibration_cali: true,
|
||||
layer_inspect: false,
|
||||
timelapse: false,
|
||||
nozzle_offset_cali: true,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -203,6 +205,9 @@ export interface PrintOptionsProps {
|
||||
options: PrintOptions;
|
||||
onChange: (options: PrintOptions) => void;
|
||||
defaultExpanded?: boolean;
|
||||
/** Show the dual-nozzle-only options (nozzle offset calibration). Default false.
|
||||
* Pass true when at least one selected printer is dual-nozzle. */
|
||||
showDualNozzleOptions?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user