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
+29 -3
View File
@@ -155,12 +155,14 @@ class _AssetPanel(QWidget):
preview_changed = Signal(object) # ImageAsset | None
assets_changed = Signal(list) # List[ImageAsset]
folder_opened = Signal(str) # chosen folder path (for persistence)
def __init__(self, title: str, open_text: str, parent=None):
super().__init__(parent)
self._assets: List[ImageAsset] = []
self._root_folder: Optional[Path] = None
self._current_preview_row: int = -1
self._start_dir: str = "" # where the folder picker opens
self._setup_ui(title, open_text)
# ── UI construction ────────────────────────────────────────────────
@@ -238,10 +240,16 @@ class _AssetPanel(QWidget):
# ── folder scanning ────────────────────────────────────────────────
def set_start_dir(self, path: str):
"""Set where the folder picker opens (restored last-used folder)."""
self._start_dir = path or ""
def _open_folder(self):
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
folder = QFileDialog.getExistingDirectory(self, "选择文件夹", self._start_dir)
if not folder:
return
self._start_dir = folder
self.folder_opened.emit(folder)
self._root_folder = Path(folder)
try:
self._assets = scan_image_folder(self._root_folder)
@@ -251,6 +259,9 @@ class _AssetPanel(QWidget):
logger.info("Loaded %d asset(s) from %s", len(self._assets), folder)
self._apply_sort()
self.assets_changed.emit(list(self._assets))
# Auto-preview the first image so the canvas isn't empty after loading
if self._assets:
self._select_preview_row(0)
# ── sorting & list rebuild ─────────────────────────────────────────
@@ -284,7 +295,12 @@ class _AssetPanel(QWidget):
# ── interaction ────────────────────────────────────────────────────
def _on_item_clicked(self, item: QListWidgetItem):
row = self._list.row(item)
self._select_preview_row(self._list.row(item))
def _select_preview_row(self, row: int):
"""Highlight the row as the current preview and emit preview_changed."""
if not (0 <= row < self._list.count()):
return
# Clear previous preview highlight
if 0 <= self._current_preview_row < self._list.count():
@@ -295,7 +311,7 @@ class _AssetPanel(QWidget):
w.set_preview_selected(False)
self._current_preview_row = row
widget = self._list.itemWidget(item)
widget = self._list.itemWidget(self._list.item(row))
if widget:
widget.set_preview_selected(True)
self.preview_changed.emit(widget.asset)
@@ -357,6 +373,8 @@ class ImageListPanel(QWidget):
print_preview_changed = Signal(object) # ImageAsset | None
garments_changed = Signal(list) # List[ImageAsset]
prints_changed = Signal(list) # List[ImageAsset]
garment_folder_opened = Signal(str) # last-used garment folder
print_folder_opened = Signal(str) # last-used print folder
def __init__(self, parent=None):
super().__init__(parent)
@@ -378,6 +396,8 @@ class ImageListPanel(QWidget):
self._print_panel.preview_changed.connect(self.print_preview_changed)
self._garment_panel.assets_changed.connect(self.garments_changed)
self._print_panel.assets_changed.connect(self.prints_changed)
self._garment_panel.folder_opened.connect(self.garment_folder_opened)
self._print_panel.folder_opened.connect(self.print_folder_opened)
splitter.addWidget(self._garment_panel)
splitter.addWidget(self._print_panel)
@@ -456,3 +476,9 @@ class ImageListPanel(QWidget):
def get_prints(self) -> List[ImageAsset]:
return self._print_panel.get_assets()
def set_garment_start_dir(self, path: str):
self._garment_panel.set_start_dir(path)
def set_print_start_dir(self, path: str):
self._print_panel.set_start_dir(path)