192 lines
9.3 KiB
Python
192 lines
9.3 KiB
Python
"""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"
|