"""AI 炫图提示词构建器:从 drawing_data 提取窗型结构特征,生成专业英文生图提示词。 设计原则(生图模型工程实践): - 主体先行: 窗户的几何/材质/分格描述置前,场景氛围居后 - 结构量化: 宽高 mm、分格数、扇数、开向全部显式给出,抑制模型自由发挥 - 负面约束: 明确"保持窗型几何不变"(edits 模式关键) - 光影专项: 时间(日/黄昏/夜)与室内外视角分别给出摄影级光照词 """ from huamenchuang.core.drawing import parse_drawing, replay_shape # 风格 → 型材材质/颜色描述 STYLE_FINISH = { "modern": ("matte anthracite grey aluminum frames, minimalist slim profiles", "modern minimalist"), "chinese": ("dark bronze aluminum frames with wood-grain inner finish and subtle Chinese lattice grille", "new-Chinese style"), "european": ("cream white aluminum frames with classic decorative architrave", "classic European"), "nordic": ("white aluminum frames with light oak interior foil", "Scandinavian Nordic"), } # 时间 → 摄影光照 TIME_LIGHT = { "day": "bright midday daylight, soft natural shadows, clear sky", "dusk": "warm golden-hour sunset light, long soft shadows, amber sky", "night": "night scene, warm interior glow shining through the glass, cool blue ambience", } # 场景 → 场景描述(照片式 7 场景对齐源站 renderV2) SCENE_DESC = { "render": "the window installed on a clean rendered exterior wall, straight-on elevation view", "install": "the brand-new window just mounted into the wall opening, installation foam and fixing screws still visible at the edges, home renovation context", "erase": "renovation scene: the old worn window has been removed from the masonry opening and this new window is being fitted in its place, dusty sill", "indoor": "interior view, the window as the focal point of a tastefully furnished {style_word} living room with sofa, wooden floor, curtains and green plants", "villa": "exterior view of a {style_word} villa facade featuring this large window, landscaped garden with lawn below, blue sky", "fence": "view from inside looking out through the window, clear unobstructed garden and distant hills beyond the glass, no railings or bars outside", "blank": "unfurnished rough-condition room with bare grey cement walls, the window as the only fitted element, pre-renovation state", } # 快速模式场景(无照片) QUICK_SCENE = { "indoor": "interior view in a tastefully furnished {style_word} room, the window as the light source", "outdoor": "exterior facade view on a residential building, mild urban greenery around", "studio": "studio product shot on a seamless neutral background, softbox lighting, slight floor reflection", } # 阳光房场景(对齐目标站 sunnyhouse 平顶/人字顶) SUNROOM_DESC = { "sunroom-flat": "a flat-roof glass sunroom attached to the house, slim {frame_word} aluminum posts and glass beams, clear tempered glass roof panels, furnished with rattan chairs and plants, garden view", "sunroom-gable": "a gable (A-frame pitched) glass sunroom attached to the house, ridge beam with fan-shaped front glass, {frame_word} aluminum structure, clear glass roof, garden view", } def _sash_kind(sub: dict) -> str | None: t = str(sub.get("type", "")) is_door = bool(sub.get("isDoor")) if "Screen" in t: return None # 纱扇不参与文字描述 if "Kfc" in t: return "tilt-and-turn " + ("door leaf" if is_door else "sash") if "DoubleSash" in t: return "pair of French " + ("door leaves" if is_door else "casement sashes opening from the center") if "Slide" in t: return "sliding " + ("door panel" if is_door else "sash") if t in ("Sash",): return "side-hung casement " + ("door leaf" if is_door else "sash") return None def _opening_words(sub: dict) -> str: toward = str(sub.get("openToward") or "") od = sub.get("od") if "up" in str(sub.get("openDirection") or "").lower() or (isinstance(od, (int, float)) and 60 <= od <= 180 and "up" in str(sub.get("openDirection", "")).lower()): return "top-hung" return "inward-opening" if toward == "inward" else "outward-opening" def describe_window(drawing: str | dict) -> dict: """从 drawing_data 提取窗型结构特征(供提示词与前端展示)。""" dd = parse_drawing(drawing) shapes = [s for s in dd.get("sm", []) if isinstance(s, dict)] if not shapes: return {"count": 0, "summary": "", "width_mm": 0, "height_mm": 0} xs, ys = [], [] parts: list[str] = [] sash_total = 0 has_arc = has_screen = has_deco = False for shape in shapes: regions = replay_shape(shape) pts = [[(seg["ps"]["x"], seg["ps"]["y"]) for seg in r] for r in regions] if not pts: continue flat = [p for r in pts for p in r] x0, y0 = min(p[0] for p in flat), min(p[1] for p in flat) x1, y1 = max(p[0] for p in flat), max(p[1] for p in flat) xs += [x0, x1]; ys += [y0, y1] w, h = round(x1 - x0), round(y1 - y0) n_region = len(regions) # 扇描述 sashes = [sub for sub in (shape.get("sm") or []) if isinstance(sub, dict)] kind_words: list[str] = [] for sub in sashes: k = _sash_kind(sub) if k is None: if "Screen" in str(sub.get("type", "")): has_screen = True continue sash_total += 1 kind_words.append(f"{_opening_words(sub)} {k}") if shape.get("rt"): has_arc = True if shape.get("decs"): has_deco = True part = f"{w}×{h}mm unit with {n_region} glass panes" if kind_words: part += ", " + " and ".join(dict.fromkeys(kind_words)) parts.append(part) if not xs: return {"count": 0, "summary": "", "width_mm": 0, "height_mm": 0} width_mm, height_mm = round(max(xs) - min(xs)), round(max(ys) - min(ys)) multi = len(parts) > 1 summary = "; ".join(parts) if multi: summary = f"a combination of {len(parts)} window units joined together: " + summary else: summary = parts[0] if has_arc: summary += ", arched/curved top (heritage style)" if has_screen: summary += ", with retractable fly screen" if has_deco: summary += ", with decorative gliding bars (grilles) between the glass" return {"count": len(parts), "summary": summary, "width_mm": width_mm, "height_mm": height_mm, "sashes": sash_total} def build_prompt(drawing: str | dict, style: str = "modern", time: str = "day", scene: str = "render", ratio: str = "landscape", rect_hint: str = "") -> str: """构建优化后的完整英文提示词。 ratio: portrait/landscape/square → 依据窗型宽高比的构图引导。 rect_hint: 照片模式下洞口位置描述(如 "centered slightly left of middle")。 """ info = describe_window(drawing) dd = parse_drawing(drawing) finish, style_word = STYLE_FINISH.get(style, STYLE_FINISH["modern"]) light = TIME_LIGHT.get(time, TIME_LIGHT["day"]) # 阳光房场景: 窗型描述让位于结构描述 frame_word = "anthracite" if style == "modern" else ("bronze" if style == "chinese" else "white") if scene in SUNROOM_DESC: scene_text = SUNROOM_DESC[scene].replace("{frame_word}", frame_word) sr = dd.get("_sunroom") or {} fw = sr.get("width_mm") or info["width_mm"] or 4000 fd = sr.get("depth_mm") or 3000 fh = sr.get("height_mm") or 2800 bays = max(2, round(fw / (sr.get("bay_mm") or 1000))) window_desc = (f"Structure: {scene_text}; footprint {fw}×{fd}mm, eave height {fh}mm, " f"{bays} bays with slim aluminum posts") else: scene_tpl = SCENE_DESC.get(scene) or QUICK_SCENE.get(scene) or SCENE_DESC["render"] scene_text = scene_tpl.replace("{style_word}", style_word) window_desc = info["summary"] or "a modern aluminum window" composition = { "portrait": "vertical composition, window fills most of the frame height", "landscape": "horizontal composition, generous wall context around the window", "square": "balanced square composition, window centered", }.get(ratio, "") prompt = ( f"Photorealistic architectural photograph of {scene_text}. " f"The window: {window_desc}; {finish}. " f"Lighting: {light}. {composition} " + (f"{rect_hint} " if rect_hint else "") + "Keep the window geometry, pane division and frame profiles exactly as described — do not add or remove sashes. " "Realistic glass with soft sky reflection, physically correct shadows and contact shadows, " "true-to-material aluminum texture, high dynamic range, ultra-detailed, 8k, professional architectural photography, no people, no text, no watermark." ) return prompt def pick_size(width_mm: int, height_mm: int) -> str: """按窗型宽高比选择生成尺寸(gpt-image 支持 1024x1024 / 1536x1024 / 1024x1536)。""" if not width_mm or not height_mm: return "1024x1024" r = width_mm / height_mm if r > 1.2: return "1536x1024" if r < 0.83: return "1024x1536" return "1024x1024"