feat: auto-preview, timestamped/grouped output, folder memory; fix batch format

- Auto-preview the first image after a folder loads (both panels), so the
  canvas isn't empty (§17.8).
- Group exports as <output_dir>/<timestamp>/<print_stem>/<garment_stem>.<ext>;
  one timestamp per export run, shared across the batch (§17.9).
- Remember the last garment/print folders and reopen the picker there;
  persisted via config, wired through main_window (§17.10).
- Shrink the 3 box templates further and lower them (chest placement).
- Fix: batch export ignored the chosen format/quality/output dir because the
  queue never received ExportOptions. Export panel now emits
  export_options_changed → queue.set_export_options (+ initial sync) (§17.11).

Docs: PRD 6.1/6.8/9, architecture 4.12, UI design 5.1, tasks.md 17.8–17.11.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 15:13:01 +08:00
co-authored by Claude Opus 4.8
parent 0cb1544654
commit edcd5d1e18
11 changed files with 208 additions and 40 deletions
+28 -11
View File
@@ -2,7 +2,7 @@ import logging
from pathlib import Path
from typing import Optional
from PySide6.QtCore import Qt
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import (
QComboBox,
QFileDialog,
@@ -17,7 +17,11 @@ from PySide6.QtWidgets import (
from core.composer import compose
from core.models import ExportOptions, TransformState
from services.file_service import get_output_dir, make_safe_output_path
from services.file_service import (
get_output_dir,
make_safe_output_path,
timestamped_run_dir,
)
logger = logging.getLogger(__name__)
@@ -25,6 +29,8 @@ logger = logging.getLogger(__name__)
class ExportPanel(QWidget):
"""Right-panel section: output settings and single-image export."""
export_options_changed = Signal(object) # ExportOptions when dir/format/quality changes
def __init__(self, parent=None):
super().__init__(parent)
self._garment_path: Optional[str] = None
@@ -66,6 +72,7 @@ class ExportPanel(QWidget):
self._dir_input.setReadOnly(True)
self._dir_input.setPlaceholderText("输出目录")
self._dir_input.setText(str(get_output_dir()))
self._dir_input.textChanged.connect(self._emit_options)
browse_btn = QPushButton("浏览…")
browse_btn.setObjectName("exportBrowseBtn")
@@ -92,6 +99,7 @@ class ExportPanel(QWidget):
self._quality_combo.addItem(label, val)
self._quality_combo.setCurrentIndex(1) # default: 高 (90)
self._quality_combo.setEnabled(False) # enabled only for JPG
self._quality_combo.currentIndexChanged.connect(self._emit_options)
# 格式 and 质量 share one row (quality lights up only for JPG)
row.addWidget(QLabel("格式"))
@@ -133,11 +141,24 @@ class ExportPanel(QWidget):
"""Update the operation-scope hint label."""
self._hint_lbl.setText(text)
def current_options(self) -> ExportOptions:
"""Build ExportOptions from the current output settings."""
fmt = self._format_combo.currentData()
return ExportOptions(
output_dir=self._dir_input.text().strip(),
output_format=fmt,
quality=self._quality_combo.currentData() if fmt == "JPG" else 95,
)
# ── slot handlers ────────────────────────────────────────────────────────
def _emit_options(self, *_):
self.export_options_changed.emit(self.current_options())
def _on_format_changed(self, _index: int):
fmt = self._format_combo.currentData()
self._quality_combo.setEnabled(fmt == "JPG")
self._emit_options()
def _browse_output_dir(self):
folder = QFileDialog.getExistingDirectory(
@@ -158,9 +179,8 @@ class ExportPanel(QWidget):
QMessageBox.information(self, "提示", "当前没有可导出的印花参数。")
return
out_dir = self._dir_input.text().strip() or str(get_output_dir())
fmt = self._format_combo.currentData()
quality = self._quality_combo.currentData() if fmt == "JPG" else 95
options = self.current_options()
out_dir = options.output_dir or str(get_output_dir())
out_dir_path = Path(out_dir)
try:
@@ -173,13 +193,10 @@ class ExportPanel(QWidget):
)
return
options = ExportOptions(
output_dir=out_dir,
output_format=fmt,
quality=quality,
)
# Group this export under a timestamped run folder (compose creates it)
run_dir = timestamped_run_dir(out_dir)
output_path = make_safe_output_path(
out_dir, self._garment_path, self._print_path, fmt
run_dir, self._garment_path, self._print_path, options.output_format
)
result = compose(