feat: print name in output filename; export panel trim; queue template fix
- Output filename now includes the print: <garment>_<print>.<ext>, e.g. 1_TY030.png (folder grouping unchanged) (§17.12). - Hide the single-export button (export via the queue) and remove the orphaned static export hint label (§17.13). - Fix: selecting a non-fine-tuned queue item now lays the print out per the selected template (apply_current) instead of load_print's centred default; still guarded by _loading_queue_item so it isn't marked 已微调 (§17.14). Docs: PRD 6.6/6.8/9, UI design 7.6/7.7/10/12.4, tasks.md 17.12–17.14. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+3
-3
@@ -164,7 +164,7 @@
|
|||||||
- 模板参数应基于衣服底图尺寸计算,避免只适配单一图片分辨率。
|
- 模板参数应基于衣服底图尺寸计算,避免只适配单一图片分辨率。
|
||||||
- 模板的 `width_ratio` / `height_ratio` 定义衣服上的**目标框**,而非强制拉伸尺寸。已知印花原始尺寸时,印花按自身宽高比缩放后 contain 进目标框并居中,不被拉伸变形;未知印花尺寸时退化为铺满目标框。
|
- 模板的 `width_ratio` / `height_ratio` 定义衣服上的**目标框**,而非强制拉伸尺寸。已知印花原始尺寸时,印花按自身宽高比缩放后 contain 进目标框并居中,不被拉伸变形;未知印花尺寸时退化为铺满目标框。
|
||||||
- 内置模板尺寸以贴近真实印花占比为准(例如正方形模板约为衣服宽度的 1/6,居中偏下),避免印花占据过大面积。
|
- 内置模板尺寸以贴近真实印花占比为准(例如正方形模板约为衣服宽度的 1/6,居中偏下),避免印花占据过大面积。
|
||||||
- 选中的模板是**粘性**的:在预览区切换衣服或切换印花时,印花不应丢失,应按当前选中模板基于新的衣服/印花尺寸重新计算落位,而非回退到画布的写死默认值。已手动微调的项不受影响,仍使用其自身参数。
|
- 选中的模板是**粘性**的:在预览区切换衣服、切换印花,或在合成队列中选中一个**未微调**的任务时,印花不应丢失,应按当前选中模板基于该任务的衣服/印花尺寸重新计算落位,而非回退到画布的写死默认值。已手动微调的项不受影响,仍使用其自身参数。
|
||||||
- **记住上次选择的模板**:程序退出前持久化当前选中的模板,下次启动自动恢复选中该模板。若该模板为自定义模板且已被删除,则回退到第一个内置模板。
|
- **记住上次选择的模板**:程序退出前持久化当前选中的模板,下次启动自动恢复选中该模板。若该模板为自定义模板且已被删除,则回退到第一个内置模板。
|
||||||
- 提供 `位置` / `尺寸` / `旋转` **三项独立的重置**,分别只把对应参数还原为当前选中模板的值(位置→模板居中位置;尺寸→模板目标框内适配的大小;旋转→模板角度,通常为 0),三者互不影响。
|
- 提供 `位置` / `尺寸` / `旋转` **三项独立的重置**,分别只把对应参数还原为当前选中模板的值(位置→模板居中位置;尺寸→模板目标框内适配的大小;旋转→模板角度,通常为 0),三者互不影响。
|
||||||
- 另在参数栏底部提供 `位置尺寸旋转重置为当前模板`,一次性把位置、尺寸、旋转整体还原为模板,作为三项分项重置的「全部」入口。该按钮归属「调整参数」,放在参数栏(紧挨它操作的参数),不放在模板管理区域。
|
- 另在参数栏底部提供 `位置尺寸旋转重置为当前模板`,一次性把位置、尺寸、旋转整体还原为模板,作为三项分项重置的「全部」入口。该按钮归属「调整参数」,放在参数栏(紧挨它操作的参数),不放在模板管理区域。
|
||||||
@@ -193,7 +193,7 @@
|
|||||||
- 支持 JPG、PNG 输出。
|
- 支持 JPG、PNG 输出。
|
||||||
- 支持输出质量设置。
|
- 支持输出质量设置。
|
||||||
- 文件名应避免覆盖已有文件,除非用户确认。
|
- 文件名应避免覆盖已有文件,除非用户确认。
|
||||||
- 输出按「运行时间戳 → 印花」两级分组:每次导出运行先在输出目录下新建一个时间戳文件夹,其内再按印花分子文件夹,即 `输出目录/<时间戳>/<印花文件名>/<衣服文件名>.<扩展名>`(单张导出与批量导出一致)。
|
- 输出按「运行时间戳 → 印花」两级分组:每次导出运行先在输出目录下新建一个时间戳文件夹,其内再按印花分子文件夹,文件名为 `<衣服文件名>_<印花文件名>`,即 `输出目录/<时间戳>/<印花文件名>/<衣服文件名>_<印花文件名>.<扩展名>`(例如印花 `TY030`、衣服 `1` → `.../TY030/1_TY030.png`;单张导出与批量导出一致)。
|
||||||
- 一次导出运行(一次批量,或一次单张)共用同一个时间戳,避免重复生成时互相覆盖或混在一起。
|
- 一次导出运行(一次批量,或一次单张)共用同一个时间戳,避免重复生成时互相覆盖或混在一起。
|
||||||
|
|
||||||
### 6.9 批量合成导出
|
### 6.9 批量合成导出
|
||||||
@@ -297,7 +297,7 @@ CMBot/
|
|||||||
output/
|
output/
|
||||||
<时间戳>/ # 每次导出运行一个时间戳文件夹(如 20260617_143022)
|
<时间戳>/ # 每次导出运行一个时间戳文件夹(如 20260617_143022)
|
||||||
<印花文件名>/ # 其内按印花分组,子文件夹以印花文件名命名
|
<印花文件名>/ # 其内按印花分组,子文件夹以印花文件名命名
|
||||||
<衣服文件名>.png
|
<衣服文件名>_<印花文件名>.png # 文件名 = 衣服名_印花名,如 1_TY030.png
|
||||||
...
|
...
|
||||||
resources/
|
resources/
|
||||||
```
|
```
|
||||||
|
|||||||
+9
-15
@@ -284,32 +284,25 @@ Shift 锁比例
|
|||||||
- 质量设置用于 JPG 或需要压缩的输出格式。
|
- 质量设置用于 JPG 或需要压缩的输出格式。
|
||||||
- `格式` 与 `质量` 是一对相关设置,放在**同一行**;`质量` 仅在选中 JPG 时可用(PNG 时置灰),并排能体现「先选格式 → 质量才可用」的关系。
|
- `格式` 与 `质量` 是一对相关设置,放在**同一行**;`质量` 仅在选中 JPG 时可用(PNG 时置灰),并排能体现「先选格式 → 质量才可用」的关系。
|
||||||
|
|
||||||
### 7.6 提示信息区域
|
### 7.6 提示信息区域(暂缓)
|
||||||
|
|
||||||
用于显示当前操作影响范围。
|
用于显示当前操作影响范围。**当前未实现**:早期导出面板有一条静态提示,因从未接入动态内容(操作范围说明)已移除;待真正需要按「当前单张 / 选中队列项 / 批量模板」给出范围提示时再加。
|
||||||
|
|
||||||
示例:
|
设想示例:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
当前调整将仅作用于队列中选中的「黑T_短袖 × 花卉_001」,并标记为「已微调」,重跑模板时不被覆盖。
|
当前调整将仅作用于队列中选中的「黑T_短袖 × 花卉_001」,并标记为「已微调」,重跑模板时不被覆盖。
|
||||||
```
|
```
|
||||||
|
|
||||||
要求:
|
要求(实现时):
|
||||||
|
|
||||||
- 提示语需要明确说明当前参数影响的是当前单张、选中队列项还是批量模板。
|
- 提示语需要明确说明当前参数影响的是当前单张、选中队列项还是批量模板。
|
||||||
- 警告和错误信息不能只用颜色表达。
|
- 警告和错误信息不能只用颜色表达。
|
||||||
|
|
||||||
### 7.7 操作按钮
|
### 7.7 操作按钮
|
||||||
|
|
||||||
主要按钮:
|
- `导出当前单张` 按钮**当前隐藏**:导出统一通过底部合成队列完成(`开始批量导出` / `导出选中`),导出面板只承担输出设置。代码保留该按钮与单张导出逻辑,后续需要时可再显示。
|
||||||
|
- 导出面板不重复放置模板相关操作(保存 / 另存为 / 删除统一在模板区域 7.1),避免同一动作出现两次。
|
||||||
- `导出当前单张`
|
|
||||||
|
|
||||||
要求:
|
|
||||||
|
|
||||||
- `导出当前单张` 使用主按钮样式。
|
|
||||||
- 当前缺少衣服图或印花图时,导出按钮应禁用。
|
|
||||||
- 导出面板只负责输出与导出;模板相关操作(保存 / 另存为 / 重置为模板)统一放在模板区域(7.1),不在导出面板重复设置,避免同一动作出现两次。
|
|
||||||
|
|
||||||
## 8. 底部合成队列
|
## 8. 底部合成队列
|
||||||
|
|
||||||
@@ -457,7 +450,7 @@ Microsoft YaHei
|
|||||||
|
|
||||||
按钮层级(一套主色,按角色克制使用):
|
按钮层级(一套主色,按角色克制使用):
|
||||||
|
|
||||||
- **主按钮**:主色填充、白字。用于每个区域「推动流程前进」的唯一动作:左侧 `打开文件夹`、右侧 `导出当前单张`、底部 `开始批量导出`。三者分处不同区域,各自是本区域主操作,不互相竞争。
|
- **主按钮**:主色填充、白字。用于每个区域「推动流程前进」的唯一动作:左侧 `打开文件夹`、底部 `开始批量导出`。各自是本区域主操作,不互相竞争。(导出面板的 `导出当前单张` 当前隐藏,见 7.7。)
|
||||||
- **次按钮**:白底、主色描边与文字。用于有意义但非主线的动作,如模板区域的 `另存为`。
|
- **次按钮**:白底、主色描边与文字。用于有意义但非主线的动作,如模板区域的 `另存为`。
|
||||||
- **辅助按钮**:浅灰底、细边框。用于就地微调类动作,如 `浏览…`、排序、`左旋 / 右旋`、模板区域的 `保存`。
|
- **辅助按钮**:浅灰底、细边框。用于就地微调类动作,如 `浏览…`、排序、`左旋 / 右旋`、模板区域的 `保存`。
|
||||||
- **重置类按钮**:最安静的一档,用于「还原为模板」的动作(三个分区的 `↺ 重置` 与底部的 `位置尺寸旋转重置为当前模板`)。它们属于「纠正/撤销」类动作,不占用常驻强调色;强调色只在悬停时短暂出现以表达意图。重置可逆,因此**不使用红色/警告色**。
|
- **重置类按钮**:最安静的一档,用于「还原为模板」的动作(三个分区的 `↺ 重置` 与底部的 `位置尺寸旋转重置为当前模板`)。它们属于「纠正/撤销」类动作,不占用常驻强调色;强调色只在悬停时短暂出现以表达意图。重置可逆,因此**不使用红色/警告色**。
|
||||||
@@ -528,7 +521,8 @@ Microsoft YaHei
|
|||||||
|
|
||||||
- 队列行高亮。
|
- 队列行高亮。
|
||||||
- 预览区显示该队列项的衣服、印花和参数。
|
- 预览区显示该队列项的衣服、印花和参数。
|
||||||
- 若该队列项被手动调整,参数状态显示为 `已微调`。
|
- 未微调的任务:印花按**当前选中模板**落位(与导出时一致),而非画布的写死默认值。
|
||||||
|
- 若该队列项被手动调整,参数状态显示为 `已微调`,预览使用其自身参数。
|
||||||
|
|
||||||
### 12.5 正在批量导出
|
### 12.5 正在批量导出
|
||||||
|
|
||||||
|
|||||||
@@ -325,11 +325,8 @@ class MainWindow(QMainWindow):
|
|||||||
self.image_canvas.load_print(print_path)
|
self.image_canvas.load_print(print_path)
|
||||||
self.export_panel.set_garment(garment_path)
|
self.export_panel.set_garment(garment_path)
|
||||||
self.export_panel.set_print(print_path)
|
self.export_panel.set_print(print_path)
|
||||||
# If the item already has a fine-tuned transform, restore it
|
# Keep template panel garment and print size in sync first, so
|
||||||
if item.transform:
|
# apply_current() below can compute the template layout.
|
||||||
self.image_canvas.set_transform(item.transform)
|
|
||||||
self.transform_panel.set_transform(item.transform)
|
|
||||||
# Keep template panel garment and print size in sync
|
|
||||||
try:
|
try:
|
||||||
from PIL import Image as _PilImage
|
from PIL import Image as _PilImage
|
||||||
with _PilImage.open(garment_path) as img:
|
with _PilImage.open(garment_path) as img:
|
||||||
@@ -338,6 +335,13 @@ class MainWindow(QMainWindow):
|
|||||||
self.template_panel.set_print_size(img.width, img.height)
|
self.template_panel.set_print_size(img.width, img.height)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
# Fine-tuned item: restore its own transform. Otherwise lay the print
|
||||||
|
# out per the selected template instead of load_print's default.
|
||||||
|
if item.transform:
|
||||||
|
self.image_canvas.set_transform(item.transform)
|
||||||
|
self.transform_panel.set_transform(item.transform)
|
||||||
|
else:
|
||||||
|
self.template_panel.apply_current()
|
||||||
finally:
|
finally:
|
||||||
self._loading_queue_item = False
|
self._loading_queue_item = False
|
||||||
self._update_reset_availability()
|
self._update_reset_availability()
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ class ExportPanel(QWidget):
|
|||||||
|
|
||||||
body_col.addLayout(self._make_dir_row())
|
body_col.addLayout(self._make_dir_row())
|
||||||
body_col.addLayout(self._make_format_form())
|
body_col.addLayout(self._make_format_form())
|
||||||
body_col.addWidget(self._make_hint_label())
|
|
||||||
body_col.addWidget(self._make_export_btn())
|
body_col.addWidget(self._make_export_btn())
|
||||||
|
|
||||||
col.addWidget(body)
|
col.addWidget(body)
|
||||||
@@ -108,16 +107,11 @@ class ExportPanel(QWidget):
|
|||||||
row.addWidget(self._quality_combo, 1)
|
row.addWidget(self._quality_combo, 1)
|
||||||
return row
|
return row
|
||||||
|
|
||||||
def _make_hint_label(self) -> QLabel:
|
|
||||||
self._hint_lbl = QLabel("加载衣服和印花图片后即可导出。")
|
|
||||||
self._hint_lbl.setObjectName("exportHint")
|
|
||||||
self._hint_lbl.setWordWrap(True)
|
|
||||||
return self._hint_lbl
|
|
||||||
|
|
||||||
def _make_export_btn(self) -> QPushButton:
|
def _make_export_btn(self) -> QPushButton:
|
||||||
self._export_btn = QPushButton("导出当前单张")
|
self._export_btn = QPushButton("导出当前单张")
|
||||||
self._export_btn.setObjectName("exportPrimaryBtn")
|
self._export_btn.setObjectName("exportPrimaryBtn")
|
||||||
self._export_btn.setEnabled(False)
|
self._export_btn.setEnabled(False)
|
||||||
|
self._export_btn.setVisible(False) # hidden: export via the queue
|
||||||
self._export_btn.clicked.connect(self._do_export)
|
self._export_btn.clicked.connect(self._do_export)
|
||||||
return self._export_btn
|
return self._export_btn
|
||||||
|
|
||||||
@@ -137,10 +131,6 @@ class ExportPanel(QWidget):
|
|||||||
"""Store current print transform (used on export)."""
|
"""Store current print transform (used on export)."""
|
||||||
self._transform = state
|
self._transform = state
|
||||||
|
|
||||||
def set_hint(self, text: str):
|
|
||||||
"""Update the operation-scope hint label."""
|
|
||||||
self._hint_lbl.setText(text)
|
|
||||||
|
|
||||||
def current_options(self) -> ExportOptions:
|
def current_options(self) -> ExportOptions:
|
||||||
"""Build ExportOptions from the current output settings."""
|
"""Build ExportOptions from the current output settings."""
|
||||||
fmt = self._format_combo.currentData()
|
fmt = self._format_combo.currentData()
|
||||||
@@ -256,14 +246,6 @@ class ExportPanel(QWidget):
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
}
|
}
|
||||||
#exportHint {
|
|
||||||
font-size: 11px;
|
|
||||||
color: #777777;
|
|
||||||
background: #fffbe6;
|
|
||||||
border: 1px solid #ffe58f;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 4px 6px;
|
|
||||||
}
|
|
||||||
#exportPrimaryBtn {
|
#exportPrimaryBtn {
|
||||||
font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
|
font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|||||||
@@ -117,13 +117,13 @@ def make_safe_output_path(output_dir, garment_path, print_path, output_format="P
|
|||||||
"""Return an output Path that will not overwrite an existing file.
|
"""Return an output Path that will not overwrite an existing file.
|
||||||
|
|
||||||
Outputs are grouped by print into a subfolder named after the print file:
|
Outputs are grouped by print into a subfolder named after the print file:
|
||||||
<output_dir>/<print_stem>/<garment_stem>.<ext>
|
<output_dir>/<print_stem>/<garment_stem>_<print_stem>.<ext>
|
||||||
If that path exists, appends _1, _2, ... until a free slot is found.
|
If that path exists, appends _1, _2, ... until a free slot is found.
|
||||||
(The composer creates the subfolder when saving.)
|
(The composer creates the subfolder when saving.)
|
||||||
"""
|
"""
|
||||||
ext = ".png" if output_format.upper() == "PNG" else ".jpg"
|
ext = ".png" if output_format.upper() == "PNG" else ".jpg"
|
||||||
target_dir = Path(output_dir) / Path(print_path).stem
|
target_dir = Path(output_dir) / Path(print_path).stem
|
||||||
stem = Path(garment_path).stem
|
stem = "{}_{}".format(Path(garment_path).stem, Path(print_path).stem)
|
||||||
|
|
||||||
candidate = target_dir / (stem + ext)
|
candidate = target_dir / (stem + ext)
|
||||||
counter = 1
|
counter = 1
|
||||||
|
|||||||
@@ -764,6 +764,62 @@
|
|||||||
- [x] 选 JPG 后批量导出的文件为 `.jpg` 且按 JPEG 保存
|
- [x] 选 JPG 后批量导出的文件为 `.jpg` 且按 JPEG 保存
|
||||||
- [x] 批量导出跟随所选输出目录与质量
|
- [x] 批量导出跟随所选输出目录与质量
|
||||||
|
|
||||||
|
### 17.12 合并图文件名加上印花名
|
||||||
|
|
||||||
|
前置阅读:
|
||||||
|
|
||||||
|
- `docs/02-prd.md`(6.8 单张合成导出、9 数据与文件结构)
|
||||||
|
|
||||||
|
任务:
|
||||||
|
|
||||||
|
- [x] `make_safe_output_path` 的文件名由 `<衣服文件名>` 改为 `<衣服文件名>_<印花文件名>`
|
||||||
|
- [x] 目录分组结构不变(仍 `输出目录/<时间戳>/<印花文件名>/`)
|
||||||
|
- [x] 重名仍追加 `_1`/`_2` 避免覆盖
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- [x] 输出文件形如 `输出目录/<时间戳>/TY030/1_TY030.png`
|
||||||
|
- [x] 单张导出与批量导出一致
|
||||||
|
|
||||||
|
### 17.13 隐藏「导出当前单张」按钮
|
||||||
|
|
||||||
|
前置阅读:
|
||||||
|
|
||||||
|
- `docs/07-ui-design.md`(7.7 操作按钮、10 视觉规范)
|
||||||
|
|
||||||
|
任务:
|
||||||
|
|
||||||
|
- [x] 隐藏导出面板的 `导出当前单张` 按钮(`setVisible(False)`)
|
||||||
|
- [x] 保留按钮与 `_do_export` 逻辑,便于后续需要时再显示
|
||||||
|
- [x] 导出统一经底部合成队列完成
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- [x] 导出面板不再显示 `导出当前单张`,只剩输出设置
|
||||||
|
- [x] 队列的批量/选中导出不受影响
|
||||||
|
|
||||||
|
### 17.14 修复:选中未微调队列项时预览未按模板落位
|
||||||
|
|
||||||
|
前置阅读:
|
||||||
|
|
||||||
|
- `docs/02-prd.md`(6.6 模板选择)
|
||||||
|
- `docs/07-ui-design.md`(12.4 选中队列项)
|
||||||
|
|
||||||
|
问题:
|
||||||
|
|
||||||
|
- `_on_queue_item_activated` 仅在 `item.transform` 存在(已微调)时覆盖画布变换;未微调项保留 `load_print` 的写死默认(印花≈衣服宽 40%、居中),预览尺寸与选中模板不一致——「模板粘性」在队列项激活路径的遗漏分支。
|
||||||
|
|
||||||
|
任务:
|
||||||
|
|
||||||
|
- [x] 选中队列项且 `item.transform` 为空时,调用 `template_panel.apply_current()` 按当前模板落位
|
||||||
|
- [x] 仍在 `_loading_queue_item` 保护下,避免被误标为「已微调」
|
||||||
|
- [x] 与预览流程(`_on_print_preview`)行为一致
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- [x] 点击未微调队列项,预览印花为选中模板要求的大小/位置
|
||||||
|
- [x] 已微调项仍显示其自身参数,不被覆盖
|
||||||
|
|
||||||
## 18. 后续暂缓任务
|
## 18. 后续暂缓任务
|
||||||
|
|
||||||
以下任务第一阶段暂不做,后续需要时再新增设计文档:
|
以下任务第一阶段暂不做,后续需要时再新增设计文档:
|
||||||
|
|||||||
Reference in New Issue
Block a user