增加款式设计的部分标签
Showing
4 changed files
with
972 additions
and
5 deletions
| ... | @@ -3,8 +3,9 @@ | ... | @@ -3,8 +3,9 @@ |
| 3 | <component name="NewModuleRootManager"> | 3 | <component name="NewModuleRootManager"> |
| 4 | <content url="file://$MODULE_DIR$"> | 4 | <content url="file://$MODULE_DIR$"> |
| 5 | <excludeFolder url="file://$MODULE_DIR$/.venv" /> | 5 | <excludeFolder url="file://$MODULE_DIR$/.venv" /> |
| 6 | <excludeFolder url="file://$MODULE_DIR$/venv" /> | ||
| 6 | </content> | 7 | </content> |
| 7 | <orderEntry type="jdk" jdkName="Python 3.11 (GoogleNanoBananaApp)" jdkType="Python SDK" /> | 8 | <orderEntry type="inheritedJdk" /> |
| 8 | <orderEntry type="sourceFolder" forTests="false" /> | 9 | <orderEntry type="sourceFolder" forTests="false" /> |
| 9 | </component> | 10 | </component> |
| 10 | <component name="PyDocumentationSettings"> | 11 | <component name="PyDocumentationSettings"> | ... | ... |
| ... | @@ -10,7 +10,7 @@ from PySide6.QtWidgets import ( | ... | @@ -10,7 +10,7 @@ from PySide6.QtWidgets import ( |
| 10 | QLabel, QLineEdit, QPushButton, QCheckBox, QTextEdit, | 10 | QLabel, QLineEdit, QPushButton, QCheckBox, QTextEdit, |
| 11 | QComboBox, QScrollArea, QGroupBox, QFileDialog, QMessageBox, | 11 | QComboBox, QScrollArea, QGroupBox, QFileDialog, QMessageBox, |
| 12 | QListWidget, QListWidgetItem, QTabWidget, QSplitter, | 12 | QListWidget, QListWidgetItem, QTabWidget, QSplitter, |
| 13 | QMenu, QProgressBar | 13 | QMenu, QProgressBar, QInputDialog |
| 14 | ) | 14 | ) |
| 15 | from PySide6.QtCore import Qt, QThread, Signal, QSize, QTimer, QMimeData | 15 | from PySide6.QtCore import Qt, QThread, Signal, QSize, QTimer, QMimeData |
| 16 | from PySide6.QtGui import QPixmap, QFont, QIcon, QDesktopServices, QAction, QImage, QDragEnterEvent, QDropEvent | 16 | from PySide6.QtGui import QPixmap, QFont, QIcon, QDesktopServices, QAction, QImage, QDragEnterEvent, QDropEvent |
| ... | @@ -25,6 +25,7 @@ import shutil | ... | @@ -25,6 +25,7 @@ import shutil |
| 25 | import tempfile | 25 | import tempfile |
| 26 | import platform | 26 | import platform |
| 27 | import logging | 27 | import logging |
| 28 | import random | ||
| 28 | from pathlib import Path | 29 | from pathlib import Path |
| 29 | from google import genai | 30 | from google import genai |
| 30 | from google.genai import types | 31 | from google.genai import types |
| ... | @@ -1264,6 +1265,16 @@ class ImageGeneratorWindow(QMainWindow): | ... | @@ -1264,6 +1265,16 @@ class ImageGeneratorWindow(QMainWindow): |
| 1264 | generation_tab = self.setup_generation_tab() | 1265 | generation_tab = self.setup_generation_tab() |
| 1265 | self.tab_widget.addTab(generation_tab, "图片生成") | 1266 | self.tab_widget.addTab(generation_tab, "图片生成") |
| 1266 | 1267 | ||
| 1268 | # Create style designer tab | ||
| 1269 | try: | ||
| 1270 | jewelry_lib_manager = JewelryLibraryManager(self.get_config_dir()) | ||
| 1271 | style_designer_tab = StyleDesignerTab(jewelry_lib_manager, parent=self) | ||
| 1272 | self.tab_widget.addTab(style_designer_tab, "款式设计") | ||
| 1273 | self.logger.info("款式设计 Tab 创建成功") | ||
| 1274 | except Exception as e: | ||
| 1275 | self.logger.error(f"款式设计 Tab 创建失败: {e}") | ||
| 1276 | # 即使失败也继续运行,不阻止应用启动 | ||
| 1277 | |||
| 1267 | # Create history tab | 1278 | # Create history tab |
| 1268 | history_tab = self.setup_history_tab() | 1279 | history_tab = self.setup_history_tab() |
| 1269 | self.tab_widget.addTab(history_tab, "历史记录") | 1280 | self.tab_widget.addTab(history_tab, "历史记录") |
| ... | @@ -1286,7 +1297,7 @@ class ImageGeneratorWindow(QMainWindow): | ... | @@ -1286,7 +1297,7 @@ class ImageGeneratorWindow(QMainWindow): |
| 1286 | 1297 | ||
| 1287 | # Upload button and count | 1298 | # Upload button and count |
| 1288 | upload_header = QHBoxLayout() | 1299 | upload_header = QHBoxLayout() |
| 1289 | upload_btn = QPushButton("+ 添加图片") | 1300 | upload_btn = QPushButton("添加图片") |
| 1290 | upload_btn.clicked.connect(self.upload_images) | 1301 | upload_btn.clicked.connect(self.upload_images) |
| 1291 | upload_header.addWidget(upload_btn) | 1302 | upload_header.addWidget(upload_btn) |
| 1292 | 1303 | ||
| ... | @@ -1357,7 +1368,7 @@ class ImageGeneratorWindow(QMainWindow): | ... | @@ -1357,7 +1368,7 @@ class ImageGeneratorWindow(QMainWindow): |
| 1357 | self.saved_prompts_combo = QComboBox() | 1368 | self.saved_prompts_combo = QComboBox() |
| 1358 | prompt_toolbar.addWidget(self.saved_prompts_combo) | 1369 | prompt_toolbar.addWidget(self.saved_prompts_combo) |
| 1359 | 1370 | ||
| 1360 | delete_prompt_btn = QPushButton("🗑️ 删除") | 1371 | delete_prompt_btn = QPushButton("删除") |
| 1361 | delete_prompt_btn.clicked.connect(self.delete_saved_prompt) | 1372 | delete_prompt_btn.clicked.connect(self.delete_saved_prompt) |
| 1362 | prompt_toolbar.addWidget(delete_prompt_btn) | 1373 | prompt_toolbar.addWidget(delete_prompt_btn) |
| 1363 | prompt_toolbar.addStretch() | 1374 | prompt_toolbar.addStretch() |
| ... | @@ -2090,7 +2101,7 @@ class ImageGeneratorWindow(QMainWindow): | ... | @@ -2090,7 +2101,7 @@ class ImageGeneratorWindow(QMainWindow): |
| 2090 | processing_method = "格式转换" if processed_bytes != self.generated_image_bytes else "原始数据" | 2101 | processing_method = "格式转换" if processed_bytes != self.generated_image_bytes else "原始数据" |
| 2091 | self.logger.info(f"图片下载完成 - 方法: {processing_method}, 文件: {file_path}, 大小: {file_size} 字节") | 2102 | self.logger.info(f"图片下载完成 - 方法: {processing_method}, 文件: {file_path}, 大小: {file_size} 字节") |
| 2092 | 2103 | ||
| 2093 | QMessageBox.information(self, "成功", f"图片已保存到:\n{file_path}\n\n文件大小: {file_size:,} 字节") | 2104 | # 只使用状态栏提示,不显示弹窗 |
| 2094 | self.status_label.setText("● 图片已保存") | 2105 | self.status_label.setText("● 图片已保存") |
| 2095 | self.status_label.setStyleSheet("QLabel { color: #34C759; }") | 2106 | self.status_label.setStyleSheet("QLabel { color: #34C759; }") |
| 2096 | except Exception as e: | 2107 | except Exception as e: |
| ... | @@ -2527,6 +2538,874 @@ class ImageGenerationWorker(QThread): | ... | @@ -2527,6 +2538,874 @@ class ImageGenerationWorker(QThread): |
| 2527 | self.error.emit(error_msg) | 2538 | self.error.emit(error_msg) |
| 2528 | 2539 | ||
| 2529 | 2540 | ||
| 2541 | # ============================================================================ | ||
| 2542 | # 珠宝设计功能模块 | ||
| 2543 | # ============================================================================ | ||
| 2544 | |||
| 2545 | # 默认珠宝词库(纯中文) | ||
| 2546 | DEFAULT_JEWELRY_LIBRARY = { | ||
| 2547 | "主石形状": [ | ||
| 2548 | "椭圆形", | ||
| 2549 | "圆形", | ||
| 2550 | "祖母绿形", | ||
| 2551 | "梨形", | ||
| 2552 | "垫形", | ||
| 2553 | "公主方形", | ||
| 2554 | "心形" | ||
| 2555 | ], | ||
| 2556 | "主石材质": [ | ||
| 2557 | "黑发晶", | ||
| 2558 | "莫桑石", | ||
| 2559 | "红宝石", | ||
| 2560 | "蓝宝石", | ||
| 2561 | "绿碧玺", | ||
| 2562 | "黄水晶带天然包裹体", | ||
| 2563 | "钻石", | ||
| 2564 | "粉红蓝宝石" | ||
| 2565 | ], | ||
| 2566 | "金属": [ | ||
| 2567 | "14K黄金", | ||
| 2568 | "18K玫瑰金", | ||
| 2569 | "14K白金", | ||
| 2570 | "铂金(PT950)", | ||
| 2571 | "双色14K黄金+白金", | ||
| 2572 | "950银镀铑", | ||
| 2573 | "钛金属" | ||
| 2574 | ], | ||
| 2575 | "花头形式": [ | ||
| 2576 | "花卉风格光环", | ||
| 2577 | "经典圆形光环", | ||
| 2578 | "复古风格米粒边光环", | ||
| 2579 | "双层光环", | ||
| 2580 | "几何六边形光环", | ||
| 2581 | "非对称光环", | ||
| 2582 | "简约爪镶无光环" | ||
| 2583 | ], | ||
| 2584 | "戒臂结构": [ | ||
| 2585 | "扭转戒臂", | ||
| 2586 | "分裂戒臂", | ||
| 2587 | "刀刃戒臂", | ||
| 2588 | "大教堂戒臂", | ||
| 2589 | "交叉戒臂", | ||
| 2590 | "直线平滑戒臂", | ||
| 2591 | "三股编织戒臂" | ||
| 2592 | ], | ||
| 2593 | "戒臂处理": [ | ||
| 2594 | "雕刻镂空花丝工艺", | ||
| 2595 | "密钉镶戒臂", | ||
| 2596 | "抛光平滑戒臂", | ||
| 2597 | "浮雕雕刻", | ||
| 2598 | "编织纹理", | ||
| 2599 | "极简干净戒臂", | ||
| 2600 | "穿孔镂空细节", | ||
| 2601 | "锤纹处理" | ||
| 2602 | ], | ||
| 2603 | "特殊元素": [ | ||
| 2604 | "凯尔特结图案", | ||
| 2605 | "装饰艺术几何元素", | ||
| 2606 | "自然风格叶子图案", | ||
| 2607 | "复古米粒边细节", | ||
| 2608 | "哥特式图案元素", | ||
| 2609 | "天体图案(星星、月亮)", | ||
| 2610 | "藤蔓装饰曲线", | ||
| 2611 | "蝴蝶结装饰" | ||
| 2612 | ], | ||
| 2613 | "辅石镶嵌": [ | ||
| 2614 | "密钉镶", | ||
| 2615 | "微密钉镶", | ||
| 2616 | "槽镶", | ||
| 2617 | "珠粒镶", | ||
| 2618 | "共爪镶", | ||
| 2619 | "包镶", | ||
| 2620 | "轨道镶" | ||
| 2621 | ] | ||
| 2622 | } | ||
| 2623 | |||
| 2624 | |||
| 2625 | class JewelryLibraryManager: | ||
| 2626 | """珠宝词库管理器""" | ||
| 2627 | |||
| 2628 | def __init__(self, config_dir: Path): | ||
| 2629 | """初始化词库管理器 | ||
| 2630 | |||
| 2631 | Args: | ||
| 2632 | config_dir: 配置文件目录 | ||
| 2633 | """ | ||
| 2634 | self.logger = logging.getLogger(__name__) | ||
| 2635 | self.config_dir = config_dir | ||
| 2636 | self.config_path = config_dir / "jewelry_library.json" | ||
| 2637 | self.library = self.load_library() | ||
| 2638 | |||
| 2639 | def load_library(self) -> Dict[str, List[str]]: | ||
| 2640 | """加载词库,不存在则创建默认词库""" | ||
| 2641 | if self.config_path.exists(): | ||
| 2642 | try: | ||
| 2643 | with open(self.config_path, 'r', encoding='utf-8') as f: | ||
| 2644 | library = json.load(f) | ||
| 2645 | |||
| 2646 | # 检查是否需要进行数据迁移 | ||
| 2647 | library = self._migrate_library_if_needed(library) | ||
| 2648 | |||
| 2649 | self.logger.info(f"珠宝词库加载成功: {self.config_path}") | ||
| 2650 | return library | ||
| 2651 | except Exception as e: | ||
| 2652 | self.logger.error(f"珠宝词库加载失败: {e},使用默认词库") | ||
| 2653 | return DEFAULT_JEWELRY_LIBRARY.copy() | ||
| 2654 | else: | ||
| 2655 | # 首次使用,创建默认词库 | ||
| 2656 | self.logger.info("未找到珠宝词库文件,创建默认词库") | ||
| 2657 | library = DEFAULT_JEWELRY_LIBRARY.copy() | ||
| 2658 | self.save_library(library) | ||
| 2659 | return library | ||
| 2660 | |||
| 2661 | def _migrate_library_if_needed(self, library: Dict[str, List[str]]) -> Dict[str, List[str]]: | ||
| 2662 | """检查并执行数据迁移""" | ||
| 2663 | # 如果存在旧的"主石"字段,需要拆分 | ||
| 2664 | if "主石" in library: | ||
| 2665 | self.logger.info("检测到旧版主石字段,执行数据迁移...") | ||
| 2666 | |||
| 2667 | # 创建新字段 | ||
| 2668 | shapes = [] | ||
| 2669 | materials = [] | ||
| 2670 | |||
| 2671 | # 分析现有数据 | ||
| 2672 | for item in library["主石"]: | ||
| 2673 | # 提取形状信息 | ||
| 2674 | shape = self._extract_shape(item) | ||
| 2675 | if shape and shape not in shapes: | ||
| 2676 | shapes.append(shape) | ||
| 2677 | |||
| 2678 | # 提取材质信息 | ||
| 2679 | material = self._extract_material(item) | ||
| 2680 | if material and material not in materials: | ||
| 2681 | materials.append(material) | ||
| 2682 | |||
| 2683 | # 更新词库 | ||
| 2684 | library["主石形状"] = shapes if shapes else DEFAULT_JEWELRY_LIBRARY["主石形状"] | ||
| 2685 | library["主石材质"] = materials if materials else DEFAULT_JEWELRY_LIBRARY["主石材质"] | ||
| 2686 | |||
| 2687 | # 保留旧字段作为备份 | ||
| 2688 | # library.pop("主石", None) # 可以选择是否移除 | ||
| 2689 | |||
| 2690 | # 保存迁移后的数据 | ||
| 2691 | self.save_library(library) | ||
| 2692 | self.logger.info(f"数据迁移完成:形状 {len(shapes)} 个,材质 {len(materials)} 个") | ||
| 2693 | |||
| 2694 | return library | ||
| 2695 | |||
| 2696 | def _extract_shape(self, item: str) -> Optional[str]: | ||
| 2697 | """从主石描述中提取形状""" | ||
| 2698 | shapes = ["椭圆形", "圆形", "祖母绿形", "梨形", "垫形", "公主方形", "心形"] | ||
| 2699 | for shape in shapes: | ||
| 2700 | if item.startswith(shape): | ||
| 2701 | return shape | ||
| 2702 | return None | ||
| 2703 | |||
| 2704 | def _extract_material(self, item: str) -> Optional[str]: | ||
| 2705 | """从主石描述中提取材质""" | ||
| 2706 | shapes = ["椭圆形", "圆形", "祖母绿形", "梨形", "垫形", "公主方形", "心形"] | ||
| 2707 | for shape in shapes: | ||
| 2708 | if item.startswith(shape): | ||
| 2709 | material = item[len(shape):] | ||
| 2710 | # 去除可能的连接词 | ||
| 2711 | if material.startswith("的"): | ||
| 2712 | material = material[1:] | ||
| 2713 | return material if material else None | ||
| 2714 | return item # 如果没有形状前缀,返回整个字符串 | ||
| 2715 | |||
| 2716 | def save_library(self, library: Dict[str, List[str]] = None): | ||
| 2717 | """保存词库到配置文件 | ||
| 2718 | |||
| 2719 | Args: | ||
| 2720 | library: 要保存的词库,如果为None则保存当前词库 | ||
| 2721 | """ | ||
| 2722 | if library is None: | ||
| 2723 | library = self.library | ||
| 2724 | |||
| 2725 | try: | ||
| 2726 | self.config_dir.mkdir(parents=True, exist_ok=True) | ||
| 2727 | with open(self.config_path, 'w', encoding='utf-8') as f: | ||
| 2728 | json.dump(library, f, ensure_ascii=False, indent=2) | ||
| 2729 | self.logger.info(f"珠宝词库保存成功: {self.config_path}") | ||
| 2730 | except Exception as e: | ||
| 2731 | self.logger.error(f"珠宝词库保存失败: {e}") | ||
| 2732 | raise | ||
| 2733 | |||
| 2734 | def add_item(self, category: str, value: str): | ||
| 2735 | """添加词库项 | ||
| 2736 | |||
| 2737 | Args: | ||
| 2738 | category: 类别名称(如"主石") | ||
| 2739 | value: 词条内容(纯中文) | ||
| 2740 | """ | ||
| 2741 | if category not in self.library: | ||
| 2742 | self.library[category] = [] | ||
| 2743 | |||
| 2744 | if value not in self.library[category]: | ||
| 2745 | self.library[category].append(value) | ||
| 2746 | self.save_library() | ||
| 2747 | self.logger.info(f"添加词库项: {category} -> {value}") | ||
| 2748 | else: | ||
| 2749 | self.logger.warning(f"词库项已存在: {category} -> {value}") | ||
| 2750 | |||
| 2751 | def remove_item(self, category: str, value: str): | ||
| 2752 | """删除词库项 | ||
| 2753 | |||
| 2754 | Args: | ||
| 2755 | category: 类别名称 | ||
| 2756 | value: 词条内容 | ||
| 2757 | """ | ||
| 2758 | if category in self.library and value in self.library[category]: | ||
| 2759 | self.library[category].remove(value) | ||
| 2760 | self.save_library() | ||
| 2761 | self.logger.info(f"删除词库项: {category} -> {value}") | ||
| 2762 | else: | ||
| 2763 | self.logger.warning(f"词库项不存在: {category} -> {value}") | ||
| 2764 | |||
| 2765 | def reset_category(self, category: str): | ||
| 2766 | """恢复单个类别的默认词库 | ||
| 2767 | |||
| 2768 | Args: | ||
| 2769 | category: 类别名称 | ||
| 2770 | """ | ||
| 2771 | if category in DEFAULT_JEWELRY_LIBRARY: | ||
| 2772 | self.library[category] = DEFAULT_JEWELRY_LIBRARY[category].copy() | ||
| 2773 | self.save_library() | ||
| 2774 | self.logger.info(f"恢复默认词库: {category}") | ||
| 2775 | else: | ||
| 2776 | self.logger.warning(f"未知的类别: {category}") | ||
| 2777 | |||
| 2778 | def reset_all(self): | ||
| 2779 | """恢复所有类别的默认词库""" | ||
| 2780 | self.library = DEFAULT_JEWELRY_LIBRARY.copy() | ||
| 2781 | self.save_library() | ||
| 2782 | self.logger.info("恢复所有默认词库") | ||
| 2783 | |||
| 2784 | |||
| 2785 | class PromptAssembler: | ||
| 2786 | """Prompt 组装器(纯中文)""" | ||
| 2787 | |||
| 2788 | BASE_TEMPLATE = """一款高端精品珠宝戒指设计,主石为{主石},镶嵌于{金属}中。戒头采用{花头形式}围绕主石。戒臂采用{戒臂结构}设计,表面处理为{戒臂处理}。风格元素包括{特殊元素}。辅石采用{辅石镶嵌}增加光彩。高端珠宝渲染,干净的摄影棚光线,精准的金属抛光,强调宝石清晰度,不要出现手部。""" | ||
| 2789 | |||
| 2790 | @staticmethod | ||
| 2791 | def assemble(form_data: Dict[str, str]) -> str: | ||
| 2792 | """智能组装 prompt,处理空值 | ||
| 2793 | |||
| 2794 | Args: | ||
| 2795 | form_data: 表单数据,key为字段名,value为选中的值 | ||
| 2796 | |||
| 2797 | Returns: | ||
| 2798 | 组装后的 prompt 字符串 | ||
| 2799 | """ | ||
| 2800 | # 提取字段值(支持新旧两种格式) | ||
| 2801 | center_stone = form_data.get("主石", "").strip() # 向后兼容 | ||
| 2802 | |||
| 2803 | # 新格式:拆分形状和材质 | ||
| 2804 | center_stone_shape = form_data.get("主石形状", "").strip() | ||
| 2805 | center_stone_material = form_data.get("主石材质", "").strip() | ||
| 2806 | metal = form_data.get("金属", "").strip() | ||
| 2807 | halo_style = form_data.get("花头形式", "").strip() | ||
| 2808 | shank_structure = form_data.get("戒臂结构", "").strip() | ||
| 2809 | shank_treatment = form_data.get("戒臂处理", "").strip() | ||
| 2810 | special_motifs = form_data.get("特殊元素", "").strip() | ||
| 2811 | accent_setting = form_data.get("辅石镶嵌", "").strip() | ||
| 2812 | |||
| 2813 | # 构建 prompt 片段 | ||
| 2814 | parts = [] | ||
| 2815 | |||
| 2816 | # 构建主石描述(优先使用新格式) | ||
| 2817 | if center_stone_shape and center_stone_material: | ||
| 2818 | # 新格式:形状+材质 | ||
| 2819 | main_stone_desc = f"{center_stone_shape}{center_stone_material}" | ||
| 2820 | elif center_stone: | ||
| 2821 | # 旧格式:直接使用 | ||
| 2822 | main_stone_desc = center_stone | ||
| 2823 | elif center_stone_shape: | ||
| 2824 | # 只有形状 | ||
| 2825 | main_stone_desc = center_stone_shape | ||
| 2826 | elif center_stone_material: | ||
| 2827 | # 只有材质 | ||
| 2828 | main_stone_desc = center_stone_material | ||
| 2829 | else: | ||
| 2830 | main_stone_desc = "" | ||
| 2831 | |||
| 2832 | # 主体部分(主石和金属) | ||
| 2833 | if main_stone_desc and metal: | ||
| 2834 | parts.append(f"一款高端精品珠宝戒指设计,主石为{main_stone_desc},镶嵌于{metal}中。") | ||
| 2835 | elif metal: | ||
| 2836 | parts.append(f"一款高端精品珠宝戒指设计,镶嵌于{metal}中。") | ||
| 2837 | elif main_stone_desc: | ||
| 2838 | parts.append(f"一款高端精品珠宝戒指设计,主石为{main_stone_desc}。") | ||
| 2839 | else: | ||
| 2840 | parts.append("一款高端精品珠宝戒指设计。") | ||
| 2841 | |||
| 2842 | # 戒头部分 | ||
| 2843 | if halo_style: | ||
| 2844 | if main_stone_desc: | ||
| 2845 | parts.append(f"戒头采用{halo_style}围绕主石。") | ||
| 2846 | else: | ||
| 2847 | parts.append(f"戒头采用{halo_style}设计。") | ||
| 2848 | |||
| 2849 | # 戒臂部分 | ||
| 2850 | if shank_structure and shank_treatment: | ||
| 2851 | parts.append(f"戒臂采用{shank_structure}设计,表面处理为{shank_treatment}。") | ||
| 2852 | elif shank_structure: | ||
| 2853 | parts.append(f"戒臂采用{shank_structure}设计。") | ||
| 2854 | elif shank_treatment: | ||
| 2855 | parts.append(f"戒臂表面处理为{shank_treatment}。") | ||
| 2856 | |||
| 2857 | # 特殊元素 | ||
| 2858 | if special_motifs: | ||
| 2859 | parts.append(f"风格元素包括{special_motifs}。") | ||
| 2860 | |||
| 2861 | # 辅石镶嵌 | ||
| 2862 | if accent_setting: | ||
| 2863 | parts.append(f"辅石采用{accent_setting}增加光彩。") | ||
| 2864 | |||
| 2865 | # 固定的渲染要求 | ||
| 2866 | parts.append("高端珠宝渲染,干净的摄影棚光线,精准的金属抛光,强调宝石清晰度,不要出现手部。") | ||
| 2867 | |||
| 2868 | return "".join(parts) | ||
| 2869 | |||
| 2870 | |||
| 2871 | class StyleDesignerTab(QWidget): | ||
| 2872 | """款式设计 Tab(纯中文)""" | ||
| 2873 | |||
| 2874 | def __init__(self, library_manager: JewelryLibraryManager, parent=None): | ||
| 2875 | super().__init__(parent) | ||
| 2876 | self.logger = logging.getLogger(__name__) | ||
| 2877 | self.library_manager = library_manager | ||
| 2878 | self.parent_window = parent | ||
| 2879 | |||
| 2880 | # 字段顺序 | ||
| 2881 | self.categories = ["主石形状", "主石材质", "金属", "花头形式", "戒臂结构", "戒臂处理", "特殊元素", "辅石镶嵌"] | ||
| 2882 | |||
| 2883 | # 存储每个类别的 ComboBox | ||
| 2884 | self.combo_boxes = {} | ||
| 2885 | |||
| 2886 | # 生成相关 | ||
| 2887 | self.generated_image_bytes = None | ||
| 2888 | |||
| 2889 | self.setup_ui() | ||
| 2890 | |||
| 2891 | def setup_ui(self): | ||
| 2892 | """创建 UI 布局""" | ||
| 2893 | main_layout = QVBoxLayout() | ||
| 2894 | main_layout.setContentsMargins(10, 10, 10, 10) | ||
| 2895 | main_layout.setSpacing(15) | ||
| 2896 | |||
| 2897 | # 表单区域 - 每行两列布局 | ||
| 2898 | form_group = QGroupBox("珠宝元素选择") | ||
| 2899 | form_layout = QVBoxLayout() | ||
| 2900 | |||
| 2901 | # 使用网格布局实现每行两列 | ||
| 2902 | grid_layout = QGridLayout() | ||
| 2903 | grid_layout.setHorizontalSpacing(20) # 列间距 | ||
| 2904 | grid_layout.setVerticalSpacing(10) # 行间距 | ||
| 2905 | |||
| 2906 | for i, category in enumerate(self.categories): | ||
| 2907 | field_widget = self.create_field_widget(category) | ||
| 2908 | row = i // 2 | ||
| 2909 | col = i % 2 | ||
| 2910 | grid_layout.addWidget(field_widget, row, col) | ||
| 2911 | |||
| 2912 | form_layout.addLayout(grid_layout) | ||
| 2913 | |||
| 2914 | # 全局按钮区域 | ||
| 2915 | buttons_layout = QHBoxLayout() | ||
| 2916 | |||
| 2917 | # 随机生成按钮 | ||
| 2918 | random_btn = QPushButton("🎲 随机生成参数") | ||
| 2919 | random_btn.clicked.connect(self.randomize_parameters) | ||
| 2920 | random_btn.setStyleSheet(""" | ||
| 2921 | QPushButton { | ||
| 2922 | background-color: #4CAF50; | ||
| 2923 | color: white; | ||
| 2924 | padding: 8px; | ||
| 2925 | border-radius: 4px; | ||
| 2926 | font-weight: bold; | ||
| 2927 | } | ||
| 2928 | QPushButton:hover { | ||
| 2929 | background-color: #45a049; | ||
| 2930 | } | ||
| 2931 | """) | ||
| 2932 | buttons_layout.addWidget(random_btn) | ||
| 2933 | |||
| 2934 | # 全局恢复按钮 | ||
| 2935 | reset_all_btn = QPushButton("🔄 恢复所有默认词库") | ||
| 2936 | reset_all_btn.clicked.connect(self.reset_all_library) | ||
| 2937 | reset_all_btn.setStyleSheet(""" | ||
| 2938 | QPushButton { | ||
| 2939 | background-color: #ff9800; | ||
| 2940 | color: white; | ||
| 2941 | padding: 8px; | ||
| 2942 | border-radius: 4px; | ||
| 2943 | font-weight: bold; | ||
| 2944 | } | ||
| 2945 | QPushButton:hover { | ||
| 2946 | background-color: #f57c00; | ||
| 2947 | } | ||
| 2948 | """) | ||
| 2949 | buttons_layout.addWidget(reset_all_btn) | ||
| 2950 | |||
| 2951 | buttons_layout.addStretch() | ||
| 2952 | form_layout.addLayout(buttons_layout) | ||
| 2953 | |||
| 2954 | form_group.setLayout(form_layout) | ||
| 2955 | main_layout.addWidget(form_group) | ||
| 2956 | |||
| 2957 | # Content row: Prompt (left) + Settings (right) - 与图片生成页面保持一致 | ||
| 2958 | content_row = QHBoxLayout() | ||
| 2959 | |||
| 2960 | # Prompt section | ||
| 2961 | prompt_group = QGroupBox("Prompt 预览") | ||
| 2962 | prompt_layout = QVBoxLayout() | ||
| 2963 | self.prompt_preview = QTextEdit() | ||
| 2964 | self.prompt_preview.setReadOnly(True) | ||
| 2965 | prompt_layout.addWidget(self.prompt_preview) | ||
| 2966 | prompt_group.setLayout(prompt_layout) | ||
| 2967 | content_row.addWidget(prompt_group, 2) | ||
| 2968 | |||
| 2969 | # Settings section | ||
| 2970 | settings_group = QGroupBox("生成设置") | ||
| 2971 | settings_layout = QVBoxLayout() | ||
| 2972 | |||
| 2973 | aspect_label = QLabel("宽高比") | ||
| 2974 | aspect_label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }") | ||
| 2975 | settings_layout.addWidget(aspect_label) | ||
| 2976 | self.aspect_ratio = QComboBox() | ||
| 2977 | self.aspect_ratio.addItems(["1:1", "2:3", "3:2", "3:4", "4:3", "16:9"]) | ||
| 2978 | settings_layout.addWidget(self.aspect_ratio) | ||
| 2979 | |||
| 2980 | settings_layout.addSpacing(10) | ||
| 2981 | |||
| 2982 | size_label = QLabel("图片尺寸") | ||
| 2983 | size_label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }") | ||
| 2984 | settings_layout.addWidget(size_label) | ||
| 2985 | self.image_size = QComboBox() | ||
| 2986 | self.image_size.addItems(["1K", "2K", "4K"]) | ||
| 2987 | self.image_size.setCurrentIndex(1) | ||
| 2988 | settings_layout.addWidget(self.image_size) | ||
| 2989 | |||
| 2990 | settings_layout.addSpacing(10) | ||
| 2991 | |||
| 2992 | settings_layout.addStretch() | ||
| 2993 | settings_group.setLayout(settings_layout) | ||
| 2994 | content_row.addWidget(settings_group, 1) | ||
| 2995 | |||
| 2996 | main_layout.addLayout(content_row) | ||
| 2997 | |||
| 2998 | # Action buttons - 与图片生成页面保持一致 | ||
| 2999 | action_layout = QHBoxLayout() | ||
| 3000 | self.generate_btn = QPushButton("🎨 生成珠宝图片") | ||
| 3001 | self.generate_btn.clicked.connect(self.generate_image) | ||
| 3002 | self.generate_btn.setStyleSheet(""" | ||
| 3003 | QPushButton { | ||
| 3004 | background-color: #007AFF; | ||
| 3005 | color: white; | ||
| 3006 | padding: 12px; | ||
| 3007 | border-radius: 4px; | ||
| 3008 | font-size: 14px; | ||
| 3009 | font-weight: bold; | ||
| 3010 | } | ||
| 3011 | QPushButton:hover { | ||
| 3012 | background-color: #0051D5; | ||
| 3013 | } | ||
| 3014 | """) | ||
| 3015 | action_layout.addWidget(self.generate_btn) | ||
| 3016 | |||
| 3017 | self.download_btn = QPushButton("💾 下载图片") | ||
| 3018 | self.download_btn.clicked.connect(self.download_image) | ||
| 3019 | self.download_btn.setEnabled(False) | ||
| 3020 | action_layout.addWidget(self.download_btn) | ||
| 3021 | |||
| 3022 | self.status_label = QLabel("● 就绪") | ||
| 3023 | self.status_label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }") | ||
| 3024 | action_layout.addWidget(self.status_label) | ||
| 3025 | action_layout.addStretch() | ||
| 3026 | |||
| 3027 | main_layout.addLayout(action_layout) | ||
| 3028 | |||
| 3029 | # Preview section - 与图片生成页面保持一致 | ||
| 3030 | preview_group = QGroupBox("预览") | ||
| 3031 | preview_layout = QVBoxLayout() | ||
| 3032 | |||
| 3033 | self.result_label = QLabel("生成的图片将在这里显示\n双击用系统查看器打开") | ||
| 3034 | self.result_label.setAlignment(Qt.AlignCenter) | ||
| 3035 | self.result_label.setMinimumHeight(300) | ||
| 3036 | self.result_label.setMinimumWidth(400) | ||
| 3037 | self.result_label.setStyleSheet(""" | ||
| 3038 | QLabel { | ||
| 3039 | color: #999999; | ||
| 3040 | font-size: 14px; | ||
| 3041 | line-height: 18px; | ||
| 3042 | border: 1px solid #ddd; | ||
| 3043 | border-radius: 4px; | ||
| 3044 | background-color: #f9f9f9; | ||
| 3045 | } | ||
| 3046 | """) | ||
| 3047 | self.result_label.setScaledContents(False) | ||
| 3048 | # 保存原始的双击处理方法引用 | ||
| 3049 | self.original_mouseDoubleClickEvent = self.result_label.mouseDoubleClickEvent | ||
| 3050 | # 重写双击事件 | ||
| 3051 | self.result_label.mouseDoubleClickEvent = self.open_fullsize_view | ||
| 3052 | preview_layout.addWidget(self.result_label) | ||
| 3053 | |||
| 3054 | preview_group.setLayout(preview_layout) | ||
| 3055 | main_layout.addWidget(preview_group, 1) | ||
| 3056 | |||
| 3057 | main_layout.addStretch() | ||
| 3058 | self.setLayout(main_layout) | ||
| 3059 | |||
| 3060 | # 初始化 prompt 预览 | ||
| 3061 | self.update_prompt_preview() | ||
| 3062 | |||
| 3063 | def randomize_parameters(self): | ||
| 3064 | """随机生成一套参数""" | ||
| 3065 | for category, combo in self.combo_boxes.items(): | ||
| 3066 | if combo.count() > 0: | ||
| 3067 | random_index = random.randint(0, combo.count() - 1) | ||
| 3068 | combo.setCurrentIndex(random_index) | ||
| 3069 | # 触发prompt预览更新 | ||
| 3070 | self.update_prompt_preview() | ||
| 3071 | |||
| 3072 | def create_field_widget(self, category: str) -> QWidget: | ||
| 3073 | """创建单个字段的 UI 组件""" | ||
| 3074 | widget = QWidget() | ||
| 3075 | layout = QHBoxLayout() | ||
| 3076 | layout.setContentsMargins(0, 0, 0, 0) | ||
| 3077 | |||
| 3078 | # 标签 | ||
| 3079 | label = QLabel(f"{category}:") | ||
| 3080 | label.setMinimumWidth(70) # 减小标签宽度以适应两列布局 | ||
| 3081 | label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }") | ||
| 3082 | layout.addWidget(label) | ||
| 3083 | |||
| 3084 | # 下拉框 | ||
| 3085 | combo = QComboBox() | ||
| 3086 | combo.addItem("") # 空选项 | ||
| 3087 | items = self.library_manager.library.get(category, []) | ||
| 3088 | combo.addItems(items) | ||
| 3089 | combo.currentTextChanged.connect(self.update_prompt_preview) | ||
| 3090 | self.combo_boxes[category] = combo | ||
| 3091 | layout.addWidget(combo, 3) | ||
| 3092 | |||
| 3093 | # 添加按钮 | ||
| 3094 | add_btn = QPushButton("添加") | ||
| 3095 | add_btn.clicked.connect(lambda: self.add_library_item(category)) | ||
| 3096 | add_btn.setFixedWidth(60) | ||
| 3097 | layout.addWidget(add_btn) | ||
| 3098 | |||
| 3099 | # 删除按钮 | ||
| 3100 | del_btn = QPushButton("删除") | ||
| 3101 | del_btn.clicked.connect(lambda: self.remove_library_item(category)) | ||
| 3102 | del_btn.setFixedWidth(60) | ||
| 3103 | layout.addWidget(del_btn) | ||
| 3104 | |||
| 3105 | widget.setLayout(layout) | ||
| 3106 | return widget | ||
| 3107 | |||
| 3108 | def update_prompt_preview(self): | ||
| 3109 | """实时更新 prompt 预览""" | ||
| 3110 | form_data = {} | ||
| 3111 | for category, combo in self.combo_boxes.items(): | ||
| 3112 | form_data[category] = combo.currentText() | ||
| 3113 | |||
| 3114 | prompt = PromptAssembler.assemble(form_data) | ||
| 3115 | self.prompt_preview.setPlainText(prompt) | ||
| 3116 | |||
| 3117 | def add_library_item(self, category: str): | ||
| 3118 | """添加词库项 UI""" | ||
| 3119 | value, ok = QInputDialog.getText( | ||
| 3120 | self, | ||
| 3121 | f"添加{category}词库项", | ||
| 3122 | f"请输入新的{category}词条(纯中文):", | ||
| 3123 | QLineEdit.Normal | ||
| 3124 | ) | ||
| 3125 | |||
| 3126 | if ok and value.strip(): | ||
| 3127 | value = value.strip() | ||
| 3128 | try: | ||
| 3129 | self.library_manager.add_item(category, value) | ||
| 3130 | # 刷新下拉框 | ||
| 3131 | self.refresh_combo_box(category) | ||
| 3132 | QMessageBox.information(self, "成功", f"已添加词库项: {value}") | ||
| 3133 | except Exception as e: | ||
| 3134 | self.logger.error(f"添加词库项失败: {e}") | ||
| 3135 | QMessageBox.warning(self, "错误", f"添加失败: {e}") | ||
| 3136 | else: | ||
| 3137 | self.logger.info(f"用户取消了添加{category}词库项的操作") | ||
| 3138 | |||
| 3139 | def remove_library_item(self, category: str): | ||
| 3140 | """删除词库项 UI""" | ||
| 3141 | combo = self.combo_boxes.get(category) | ||
| 3142 | if not combo: | ||
| 3143 | return | ||
| 3144 | |||
| 3145 | current_value = combo.currentText() | ||
| 3146 | if not current_value: | ||
| 3147 | QMessageBox.warning(self, "提示", "请先选择要删除的词库项") | ||
| 3148 | return | ||
| 3149 | |||
| 3150 | reply = QMessageBox.question( | ||
| 3151 | self, | ||
| 3152 | "确认删除", | ||
| 3153 | f"确定要删除词库项 \"{current_value}\" 吗?", | ||
| 3154 | QMessageBox.Yes | QMessageBox.No | ||
| 3155 | ) | ||
| 3156 | |||
| 3157 | if reply == QMessageBox.Yes: | ||
| 3158 | try: | ||
| 3159 | self.library_manager.remove_item(category, current_value) | ||
| 3160 | # 刷新下拉框 | ||
| 3161 | self.refresh_combo_box(category) | ||
| 3162 | QMessageBox.information(self, "成功", f"已删除词库项: {current_value}") | ||
| 3163 | except Exception as e: | ||
| 3164 | QMessageBox.warning(self, "错误", f"删除失败: {e}") | ||
| 3165 | |||
| 3166 | def reset_category_library(self, category: str): | ||
| 3167 | """恢复单个类别的默认词库 UI""" | ||
| 3168 | reply = QMessageBox.question( | ||
| 3169 | self, | ||
| 3170 | "确认恢复", | ||
| 3171 | f"确定要恢复 \"{category}\" 的默认词库吗?\n这将删除所有自定义词条。", | ||
| 3172 | QMessageBox.Yes | QMessageBox.No | ||
| 3173 | ) | ||
| 3174 | |||
| 3175 | if reply == QMessageBox.Yes: | ||
| 3176 | try: | ||
| 3177 | self.library_manager.reset_category(category) | ||
| 3178 | # 刷新下拉框 | ||
| 3179 | self.refresh_combo_box(category) | ||
| 3180 | QMessageBox.information(self, "成功", f"已恢复 {category} 的默认词库") | ||
| 3181 | except Exception as e: | ||
| 3182 | QMessageBox.warning(self, "错误", f"恢复失败: {e}") | ||
| 3183 | |||
| 3184 | def reset_all_library(self): | ||
| 3185 | """恢复所有字段的默认词库 UI""" | ||
| 3186 | reply = QMessageBox.question( | ||
| 3187 | self, | ||
| 3188 | "确认恢复所有默认词库", | ||
| 3189 | "确定要恢复所有字段的默认词库吗?\n这将删除所有自定义词条。", | ||
| 3190 | QMessageBox.Yes | QMessageBox.No | ||
| 3191 | ) | ||
| 3192 | |||
| 3193 | if reply == QMessageBox.Yes: | ||
| 3194 | try: | ||
| 3195 | self.library_manager.reset_all() | ||
| 3196 | # 刷新所有下拉框 | ||
| 3197 | for category in self.categories: | ||
| 3198 | self.refresh_combo_box(category) | ||
| 3199 | QMessageBox.information(self, "成功", "已恢复所有默认词库") | ||
| 3200 | except Exception as e: | ||
| 3201 | QMessageBox.warning(self, "错误", f"恢复失败: {e}") | ||
| 3202 | |||
| 3203 | def open_fullsize_view(self, event): | ||
| 3204 | """双击打开完整尺寸查看器""" | ||
| 3205 | if not self.generated_image_bytes: | ||
| 3206 | QMessageBox.warning(self, "提示", "没有可预览的图片") | ||
| 3207 | return | ||
| 3208 | |||
| 3209 | # 保存为临时文件并使用系统查看器打开 | ||
| 3210 | import tempfile | ||
| 3211 | with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp_file: | ||
| 3212 | tmp_file.write(self.generated_image_bytes) | ||
| 3213 | temp_path = Path(tmp_file.name) | ||
| 3214 | |||
| 3215 | # 使用系统默认程序打开 | ||
| 3216 | QDesktopServices.openUrl(QUrl.fromLocalFile(str(temp_path))) | ||
| 3217 | |||
| 3218 | def refresh_combo_box(self, category: str): | ||
| 3219 | """刷新指定类别的下拉框""" | ||
| 3220 | combo = self.combo_boxes.get(category) | ||
| 3221 | if not combo: | ||
| 3222 | return | ||
| 3223 | |||
| 3224 | # 保存当前选中值 | ||
| 3225 | current_value = combo.currentText() | ||
| 3226 | |||
| 3227 | # 清空并重新加载 | ||
| 3228 | combo.clear() | ||
| 3229 | combo.addItem("") # 空选项 | ||
| 3230 | items = self.library_manager.library.get(category, []) | ||
| 3231 | combo.addItems(items) | ||
| 3232 | |||
| 3233 | # 尝试恢复选中值 | ||
| 3234 | index = combo.findText(current_value) | ||
| 3235 | if index >= 0: | ||
| 3236 | combo.setCurrentIndex(index) | ||
| 3237 | else: | ||
| 3238 | combo.setCurrentIndex(0) | ||
| 3239 | |||
| 3240 | def generate_image(self): | ||
| 3241 | """调用文生图 API""" | ||
| 3242 | # 获取 prompt | ||
| 3243 | prompt = self.prompt_preview.toPlainText() | ||
| 3244 | if not prompt.strip(): | ||
| 3245 | QMessageBox.warning(self, "提示", "Prompt 不能为空") | ||
| 3246 | return | ||
| 3247 | |||
| 3248 | # 获取设置 | ||
| 3249 | aspect_ratio = self.aspect_ratio.currentText() | ||
| 3250 | image_size = self.image_size.currentText() | ||
| 3251 | model = "imagen-3.0-generate-002" # 硬编码使用默认模型 | ||
| 3252 | |||
| 3253 | # 获取父窗口的 API key | ||
| 3254 | if not hasattr(self.parent_window, 'api_key') or not self.parent_window.api_key: | ||
| 3255 | QMessageBox.warning(self, "错误", "未找到 API 密钥,请在主窗口配置") | ||
| 3256 | return | ||
| 3257 | |||
| 3258 | # 非阻塞状态提示 | ||
| 3259 | self.generate_btn.setEnabled(False) | ||
| 3260 | self.status_label.setText("● 生成中...") | ||
| 3261 | QApplication.processEvents() | ||
| 3262 | |||
| 3263 | try: | ||
| 3264 | # 创建生成线程 | ||
| 3265 | self.gen_thread = ImageGenerationWorker( | ||
| 3266 | api_key=self.parent_window.api_key, | ||
| 3267 | prompt=prompt, | ||
| 3268 | images=[], | ||
| 3269 | aspect_ratio=aspect_ratio, | ||
| 3270 | image_size=image_size, | ||
| 3271 | model=model | ||
| 3272 | ) | ||
| 3273 | self.gen_thread.finished.connect(self.on_generation_success) | ||
| 3274 | self.gen_thread.error.connect(self.on_generation_error) | ||
| 3275 | self.gen_thread.start() | ||
| 3276 | |||
| 3277 | except Exception as e: | ||
| 3278 | self.generate_btn.setEnabled(True) | ||
| 3279 | self.status_label.setText("● 就绪") | ||
| 3280 | QMessageBox.critical(self, "错误", f"生成失败: {e}") | ||
| 3281 | |||
| 3282 | def on_generation_success(self, image_bytes: bytes): | ||
| 3283 | """生成成功回调""" | ||
| 3284 | # 恢复按钮状态 | ||
| 3285 | self.generate_btn.setEnabled(True) | ||
| 3286 | self.status_label.setText("● 就绪") | ||
| 3287 | |||
| 3288 | self.generated_image_bytes = image_bytes | ||
| 3289 | |||
| 3290 | # 启用下载按钮 | ||
| 3291 | self.download_btn.setEnabled(True) | ||
| 3292 | |||
| 3293 | # 显示图片 | ||
| 3294 | pixmap = QPixmap() | ||
| 3295 | pixmap.loadFromData(image_bytes) | ||
| 3296 | |||
| 3297 | # 清除之前的文本 | ||
| 3298 | self.result_label.setText("") | ||
| 3299 | |||
| 3300 | # 获取标签可用空间 | ||
| 3301 | label_size = self.result_label.size() | ||
| 3302 | if label_size.width() < 100 or label_size.height() < 100: | ||
| 3303 | # 如果标签还没有正确的尺寸,使用默认尺寸 | ||
| 3304 | label_size = QSize(400, 300) | ||
| 3305 | |||
| 3306 | # 缩放图片以适应显示区域,保持宽高比且完整显示 | ||
| 3307 | scaled_pixmap = pixmap.scaled( | ||
| 3308 | label_size, | ||
| 3309 | Qt.KeepAspectRatio, | ||
| 3310 | Qt.SmoothTransformation | ||
| 3311 | ) | ||
| 3312 | |||
| 3313 | # 设置缩放后的图片,并确保居中对齐 | ||
| 3314 | self.result_label.setPixmap(scaled_pixmap) | ||
| 3315 | self.result_label.setAlignment(Qt.AlignCenter) | ||
| 3316 | |||
| 3317 | # 保存到历史记录 | ||
| 3318 | try: | ||
| 3319 | # 添加到历史记录管理器 | ||
| 3320 | if hasattr(self.parent_window, 'history_manager'): | ||
| 3321 | timestamp = self.parent_window.history_manager.save_generation( | ||
| 3322 | image_bytes=image_bytes, | ||
| 3323 | prompt=self.prompt_preview.toPlainText(), | ||
| 3324 | reference_images=[], # 款式设计无参考图 | ||
| 3325 | aspect_ratio=self.aspect_ratio.currentText(), | ||
| 3326 | image_size=self.image_size.currentText(), | ||
| 3327 | model="imagen-3.0-generate-002" | ||
| 3328 | ) | ||
| 3329 | self.logger.info(f"款式设计已添加到历史记录: {timestamp}") | ||
| 3330 | |||
| 3331 | # 刷新历史记录列表 | ||
| 3332 | if hasattr(self.parent_window, 'refresh_history'): | ||
| 3333 | self.parent_window.refresh_history() | ||
| 3334 | else: | ||
| 3335 | self.logger.warning("未找到历史记录管理器") | ||
| 3336 | |||
| 3337 | except Exception as e: | ||
| 3338 | self.logger.error(f"保存历史记录失败: {e}") | ||
| 3339 | |||
| 3340 | # 更新状态提示 | ||
| 3341 | self.status_label.setText("● 图片生成成功") | ||
| 3342 | self.status_label.setStyleSheet("QLabel { color: #34C759; }") | ||
| 3343 | |||
| 3344 | def on_generation_error(self, error_msg: str): | ||
| 3345 | """生成失败回调""" | ||
| 3346 | # 恢复按钮状态 | ||
| 3347 | self.generate_btn.setEnabled(True) | ||
| 3348 | self.status_label.setText("● 就绪") | ||
| 3349 | QMessageBox.critical(self, "生成失败", error_msg) | ||
| 3350 | |||
| 3351 | def download_image(self): | ||
| 3352 | """下载图片""" | ||
| 3353 | if not self.generated_image_bytes: | ||
| 3354 | QMessageBox.warning(self, "提示", "没有可下载的图片") | ||
| 3355 | return | ||
| 3356 | |||
| 3357 | file_path, _ = QFileDialog.getSaveFileName( | ||
| 3358 | self, | ||
| 3359 | "保存图片", | ||
| 3360 | f"jewelry_design_{datetime.now().strftime('%Y%m%d%H%M%S')}.png", | ||
| 3361 | "PNG Files (*.png)" | ||
| 3362 | ) | ||
| 3363 | |||
| 3364 | if file_path: | ||
| 3365 | try: | ||
| 3366 | with open(file_path, 'wb') as f: | ||
| 3367 | f.write(self.generated_image_bytes) | ||
| 3368 | # 使用状态栏提示而不是弹窗 | ||
| 3369 | self.status_label.setText("● 图片已保存") | ||
| 3370 | self.status_label.setStyleSheet("QLabel { color: #34C759; }") | ||
| 3371 | except Exception as e: | ||
| 3372 | QMessageBox.critical(self, "错误", f"保存失败: {e}") | ||
| 3373 | |||
| 3374 | def preview_image(self): | ||
| 3375 | """预览大图""" | ||
| 3376 | if not self.generated_image_bytes: | ||
| 3377 | QMessageBox.warning(self, "提示", "没有可预览的图片") | ||
| 3378 | return | ||
| 3379 | |||
| 3380 | # 创建预览对话框 | ||
| 3381 | dialog = QDialog(self) | ||
| 3382 | dialog.setWindowTitle("预览大图") | ||
| 3383 | dialog.resize(800, 600) | ||
| 3384 | |||
| 3385 | layout = QVBoxLayout() | ||
| 3386 | |||
| 3387 | # 图片标签 | ||
| 3388 | label = QLabel() | ||
| 3389 | pixmap = QPixmap() | ||
| 3390 | pixmap.loadFromData(self.generated_image_bytes) | ||
| 3391 | label.setPixmap(pixmap) | ||
| 3392 | label.setScaledContents(True) | ||
| 3393 | |||
| 3394 | # 滚动区域 | ||
| 3395 | scroll = QScrollArea() | ||
| 3396 | scroll.setWidget(label) | ||
| 3397 | scroll.setWidgetResizable(True) | ||
| 3398 | layout.addWidget(scroll) | ||
| 3399 | |||
| 3400 | # 关闭按钮 | ||
| 3401 | close_btn = QPushButton("关闭") | ||
| 3402 | close_btn.clicked.connect(dialog.close) | ||
| 3403 | layout.addWidget(close_btn) | ||
| 3404 | |||
| 3405 | dialog.setLayout(layout) | ||
| 3406 | dialog.exec() | ||
| 3407 | |||
| 3408 | |||
| 2530 | def main(): | 3409 | def main(): |
| 2531 | """Main application entry point""" | 3410 | """Main application entry point""" |
| 2532 | # 初始化日志系统 | 3411 | # 初始化日志系统 | ... | ... |
jewelry_library.json
0 → 100644
| 1 | { | ||
| 2 | "主石": [ | ||
| 3 | "椭圆形黑发晶", | ||
| 4 | "圆形莫桑石", | ||
| 5 | "祖母绿形红宝石", | ||
| 6 | "梨形蓝宝石", | ||
| 7 | "垫形绿碧玺", | ||
| 8 | "椭圆形黄水晶带天然包裹体", | ||
| 9 | "公主方形钻石", | ||
| 10 | "心形粉红蓝宝石" | ||
| 11 | ], | ||
| 12 | "金属": [ | ||
| 13 | "14K黄金", | ||
| 14 | "18K玫瑰金", | ||
| 15 | "14K白金", | ||
| 16 | "铂金(PT950)", | ||
| 17 | "双色14K黄金+白金", | ||
| 18 | "950银镀铑", | ||
| 19 | "钛金属" | ||
| 20 | ], | ||
| 21 | "花头形式": [ | ||
| 22 | "花卉风格光环", | ||
| 23 | "经典圆形光环", | ||
| 24 | "复古风格米粒边光环", | ||
| 25 | "双层光环", | ||
| 26 | "几何六边形光环", | ||
| 27 | "非对称光环", | ||
| 28 | "简约爪镶无光环" | ||
| 29 | ], | ||
| 30 | "戒臂结构": [ | ||
| 31 | "扭转戒臂", | ||
| 32 | "分裂戒臂", | ||
| 33 | "刀刃戒臂", | ||
| 34 | "大教堂戒臂", | ||
| 35 | "交叉戒臂", | ||
| 36 | "直线平滑戒臂", | ||
| 37 | "三股编织戒臂" | ||
| 38 | ], | ||
| 39 | "戒臂处理": [ | ||
| 40 | "雕刻镂空花丝工艺", | ||
| 41 | "密钉镶戒臂", | ||
| 42 | "抛光平滑戒臂", | ||
| 43 | "浮雕雕刻", | ||
| 44 | "编织纹理", | ||
| 45 | "极简干净戒臂", | ||
| 46 | "穿孔镂空细节", | ||
| 47 | "锤纹处理" | ||
| 48 | ], | ||
| 49 | "特殊元素": [ | ||
| 50 | "凯尔特结图案", | ||
| 51 | "装饰艺术几何元素", | ||
| 52 | "自然风格叶子图案", | ||
| 53 | "复古米粒边细节", | ||
| 54 | "哥特式图案元素", | ||
| 55 | "天体图案(星星、月亮)", | ||
| 56 | "藤蔓装饰曲线", | ||
| 57 | "蝴蝶结装饰" | ||
| 58 | ], | ||
| 59 | "辅石镶嵌": [ | ||
| 60 | "密钉镶", | ||
| 61 | "微密钉镶", | ||
| 62 | "槽镶", | ||
| 63 | "珠粒镶", | ||
| 64 | "共爪镶", | ||
| 65 | "包镶", | ||
| 66 | "轨道镶" | ||
| 67 | ], | ||
| 68 | "主石形状": [ | ||
| 69 | "椭圆形", | ||
| 70 | "圆形", | ||
| 71 | "祖母绿形", | ||
| 72 | "梨形", | ||
| 73 | "垫形", | ||
| 74 | "公主方形", | ||
| 75 | "心形" | ||
| 76 | ], | ||
| 77 | "主石材质": [ | ||
| 78 | "黑发晶", | ||
| 79 | "莫桑石", | ||
| 80 | "红宝石", | ||
| 81 | "蓝宝石", | ||
| 82 | "绿碧玺", | ||
| 83 | "黄水晶带天然包裹体", | ||
| 84 | "钻石", | ||
| 85 | "粉红蓝宝石" | ||
| 86 | ] | ||
| 87 | } | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
zb100_icon.png
0 → 100644
2.2 MB
-
Please register or sign in to post a comment