Add auto-print G-code injection for queue items (#422)

Per-model start/end G-code snippets configurable in Settings (Workflow
  tab). Queue items get "Inject G-code" toggle — scheduler injects
  snippets into a temp 3MF copy before FTP upload. Supports Farmloop,
  SwapMod, AutoClear, Printflow 3D and similar bed-clearing systems.
  Original files are never modified.
This commit is contained in:
maziggy
2026-04-05 11:14:26 +02:00
parent aa9b60a265
commit f006472f79
29 changed files with 778 additions and 182 deletions
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Calendar, Clock, Hand, Power, Layers } from 'lucide-react';
import { Calendar, Clock, Hand, Power, Layers, Code } from 'lucide-react';
import type { ScheduleOptionsProps, ScheduleType } from './types';
import {
formatDateInput,
@@ -27,6 +27,7 @@ export function ScheduleOptionsPanel({
canControlPrinter = true,
showStagger = false,
printerCount = 0,
hasGcodeSnippets = false,
}: ScheduleOptionsProps) {
const { t } = useTranslation();
const [dateValue, setDateValue] = useState('');
@@ -251,6 +252,23 @@ export function ScheduleOptionsPanel({
</label>
</div>
{/* G-code injection */}
{hasGcodeSnippets && (
<div className="flex items-center gap-2">
<input
type="checkbox"
id="gcodeInjection"
checked={options.gcodeInjection}
onChange={(e) => onChange({ ...options, gcodeInjection: e.target.checked })}
className="rounded border-bambu-dark-tertiary bg-bambu-dark text-bambu-green focus:ring-bambu-green"
/>
<label htmlFor="gcodeInjection" className="text-sm flex items-center gap-1 text-bambu-gray">
<Code className="w-3.5 h-3.5" />
{t('printModal.gcodeInjection', 'Inject auto-print G-code')}
</label>
</div>
)}
{/* Stagger start */}
{showStagger && options.scheduleType !== 'manual' && (
<div className="space-y-3">
+23 -1
View File
@@ -1,5 +1,5 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { AlertCircle, AlertTriangle, Calendar, Layers, Loader2, Pencil, Printer, X } from 'lucide-react';
import { AlertCircle, AlertTriangle, Calendar, Code, Layers, Loader2, Pencil, Printer, X } from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { PrintQueueItemCreate, PrintQueueItemUpdate, SpoolAssignment } from '../../api/client';
@@ -124,6 +124,7 @@ export function PrintModal({
scheduledTime,
requirePreviousSuccess: queueItem.require_previous_success,
autoOffAfter: queueItem.auto_off_after,
gcodeInjection: queueItem.gcode_injection ?? false,
staggerEnabled: false,
staggerGroupSize: DEFAULT_SCHEDULE_OPTIONS.staggerGroupSize,
staggerIntervalMinutes: DEFAULT_SCHEDULE_OPTIONS.staggerIntervalMinutes,
@@ -645,6 +646,7 @@ export function PrintModal({
library_file_id: isLibraryFile ? libraryFileId : undefined,
require_previous_success: scheduleOptions.requirePreviousSuccess,
auto_off_after: scheduleOptions.autoOffAfter,
gcode_injection: scheduleOptions.gcodeInjection,
manual_start: scheduleOptions.scheduleType === 'manual',
ams_mapping: printerId ? getMappingForPrinter(printerId) : undefined,
plate_id: plateOverride !== undefined ? plateOverride : selectedPlate,
@@ -678,6 +680,7 @@ export function PrintModal({
filament_overrides: filamentOverridesArray || null,
require_previous_success: scheduleOptions.requirePreviousSuccess,
auto_off_after: scheduleOptions.autoOffAfter,
gcode_injection: scheduleOptions.gcodeInjection,
manual_start: scheduleOptions.scheduleType === 'manual',
ams_mapping: undefined,
plate_id: plateId,
@@ -755,6 +758,7 @@ export function PrintModal({
target_location: null,
require_previous_success: scheduleOptions.requirePreviousSuccess,
auto_off_after: scheduleOptions.autoOffAfter,
gcode_injection: scheduleOptions.gcodeInjection,
manual_start: scheduleOptions.scheduleType === 'manual',
ams_mapping: printerMapping,
plate_id: plateId,
@@ -1130,9 +1134,27 @@ export function PrintModal({
canControlPrinter={hasPermission('printers:control')}
showStagger={mode === 'add-to-queue' && assignmentMode === 'printer' && selectedPrinters.length > 1}
printerCount={selectedPrinters.length}
hasGcodeSnippets={!!settings?.gcode_snippets}
/>
)}
{/* G-code injection for reprint mode (only shown when quantity > 1 — applies to queued copies) */}
{mode === 'reprint' && !!settings?.gcode_snippets && effectiveQuantity > 1 && (
<div className="flex items-center gap-2">
<input
type="checkbox"
id="gcodeInjectionReprint"
checked={scheduleOptions.gcodeInjection}
onChange={(e) => setScheduleOptions({ ...scheduleOptions, gcodeInjection: e.target.checked })}
className="rounded border-bambu-dark-tertiary bg-bambu-dark text-bambu-green focus:ring-bambu-green"
/>
<label htmlFor="gcodeInjectionReprint" className="text-sm flex items-center gap-1 text-bambu-gray">
<Code className="w-3.5 h-3.5" />
{t('printModal.gcodeInjection', 'Inject auto-print G-code')}
</label>
</div>
)}
{/* Error message */}
{updateQueueMutation.isError && (
<div className="mb-4 p-3 bg-red-500/20 border border-red-500/50 rounded-lg text-sm text-red-400">
@@ -69,6 +69,7 @@ export interface ScheduleOptions {
scheduledTime: string;
requirePreviousSuccess: boolean;
autoOffAfter: boolean;
gcodeInjection: boolean;
staggerEnabled: boolean;
staggerGroupSize: number;
staggerIntervalMinutes: number;
@@ -82,6 +83,7 @@ export const DEFAULT_SCHEDULE_OPTIONS: ScheduleOptions = {
scheduledTime: '',
requirePreviousSuccess: false,
autoOffAfter: false,
gcodeInjection: false,
staggerEnabled: false,
staggerGroupSize: 2,
staggerIntervalMinutes: 5,
@@ -214,4 +216,6 @@ export interface ScheduleOptionsProps {
showStagger?: boolean;
/** Number of selected printers (for stagger preview) */
printerCount?: number;
/** Whether G-code snippets are configured in settings */
hasGcodeSnippets?: boolean;
}