feat: AI生成工程大样图(整合autocad项目大样图提示词资产, blueprint模式2D图纸转黑白CAD大样图, 修复loadTemplate不回写templateId)

This commit is contained in:
HHY
2026-10-06 07:18:19 -07:00
parent a00c6c1e7b
commit 94f9a26cdc
7 changed files with 91 additions and 16 deletions
+37 -5
View File
@@ -21,13 +21,14 @@ router = APIRouter(prefix="/ai-render", tags=["AI 炫图"])
class AiRenderBody(BaseModel):
template_id: int | None = None
drawing_data: str | dict | None = None
mode: str = Field(default="quick", pattern="^(quick|photo)$")
mode: str = Field(default="quick", pattern="^(quick|photo|blueprint)$")
style: str = Field(default="modern", pattern="^(modern|chinese|european|nordic)$")
time: str = Field(default="day", pattern="^(day|dusk|night)$")
# quick: indoor/outdoor/studio; photo: render/install/erase/indoor/villa/fence/blank/sunroom-*
scene: str = "indoor"
photo_b64: str | None = Field(default=None, description="照片模式参考图(base64, 不带 dataURL 前缀)")
rect: dict | None = Field(default=None, description="照片模式框选窗洞 {x,y,w,h}(原始照片像素坐标)")
sketch_b64: str | None = Field(default=None, description="大样图模式: 2D 图纸截图(base64, 不带 dataURL 前缀)")
async def _resolve_drawing(db, body: AiRenderBody) -> str | dict | None:
@@ -50,16 +51,34 @@ async def preview_prompt(body: AiRenderBody, user: CurrentUser, db: DbSession):
return fail("未找到图纸数据")
try:
info = describe_window(drawing)
prompt = build_prompt(drawing, body.style, body.time, body.scene)
prompt = (build_blueprint_prompt(drawing) if body.mode == "blueprint"
else build_prompt(drawing, body.style, body.time, body.scene))
except Exception:
return fail("图纸数据解析失败")
return ok(data={"features": info, "prompt": prompt,
"size": pick_size(info["width_mm"], info["height_mm"])})
def build_blueprint_prompt(drawing: str | dict) -> str:
"""工程大样图提示词:2D 图纸 → 黑白 CAD 大样图风格(移植自 autocad 项目草图标准化资产,
叠加 drawing_data 结构化特征增强分格保真)。"""
try:
info = describe_window(drawing)
feat = f"窗型实际分格: {info['summary']}; 外框尺寸 {info['width_mm']}×{info['height_mm']} mm。"
except Exception:
feat = ""
return f"""Use case: style-transfer
Asset type: 门窗工程审图参考图
Primary request: 将输入的门窗 2D 图纸重绘为清晰、整洁的工程 CAD 大样图风格预览。
Input images: 第一张是原始 2D 图纸(彩色渲染稿),仅作为几何与分格的几何来源。
Style/medium: 黑白二维门窗工程大样图、正投影立面、细黑线框、玻璃淡灰填充、外部尺寸线与分格尺寸标注、中文工程标注、右下角标题栏(图名/比例/日期)。
Constraints: {feat} 严格保留原图的可辨识总体构图、窗扇/玻璃分格数量与比例、开启扇方向线与把手位置;不要增删或合并分格;不要编造缺失尺寸,标注尺寸须与原图几何一致;输出仅供人工审图与报价附件,不作为 CAD 几何或生产数据来源;无水印、无装饰性场景、无彩色。
Avoid: 三维渲染、透视效果、写实照片、彩色渲染、虚构品牌、模糊文字。"""
@router.post("/generate")
async def generate(body: AiRenderBody, user: CurrentUser, db: DbSession):
"""云端 AI 生图(gpt-image):quick=生成 / photo=参考图编辑。"""
"""云端 AI 生图(gpt-image):quick=生成 / photo=参考图编辑 / blueprint=2D图纸转工程大样图。"""
if not settings.ai_api_key:
return fail("AI 生图服务未配置(HMC_AI_API_KEY)")
drawing = await _resolve_drawing(db, body)
@@ -67,6 +86,8 @@ async def generate(body: AiRenderBody, user: CurrentUser, db: DbSession):
return fail("未找到图纸数据")
if body.mode == "photo" and not body.photo_b64:
return fail("照片模式需要上传参考照片")
if body.mode == "blueprint" and not body.sketch_b64:
return fail("大样图模式需要 2D 图纸截图")
try:
info = describe_window(drawing)
# 照片模式: 把框选洞口位置转成提示词方位描述, 引导 AI 装窗位置
@@ -88,7 +109,10 @@ async def generate(body: AiRenderBody, user: CurrentUser, db: DbSession):
f"replacing whatever old window or opening is there.")
except Exception:
rect_hint = ""
prompt = build_prompt(drawing, body.style, body.time, body.scene, rect_hint=rect_hint)
if body.mode == "blueprint":
prompt = build_blueprint_prompt(drawing)
else:
prompt = build_prompt(drawing, body.style, body.time, body.scene, rect_hint=rect_hint)
size = pick_size(info["width_mm"], info["height_mm"])
except Exception:
return fail("图纸数据解析失败")
@@ -98,7 +122,15 @@ async def generate(body: AiRenderBody, user: CurrentUser, db: DbSession):
timeout = httpx.Timeout(settings.ai_timeout, connect=15)
try:
async with httpx.AsyncClient(timeout=timeout) as client:
if body.mode == "photo":
if body.mode == "blueprint":
img_bytes = base64.b64decode(body.sketch_b64.split(",")[-1])
mime = "image/jpeg" if img_bytes[:2] == b"\xff\xd8" else "image/png"
ext = "jpg" if mime == "image/jpeg" else "png"
resp = await client.post(
f"{base}/v1/images/edits", headers=headers,
data={"model": settings.ai_model, "prompt": prompt, "size": size, "n": "1"},
files={"image[]": (f"drawing.{ext}", img_bytes, mime)})
elif body.mode == "photo":
img_bytes = base64.b64decode(body.photo_b64.split(",")[-1])
mime = "image/jpeg" if img_bytes[:2] == b"\xff\xd8" else "image/png"
ext = "jpg" if mime == "image/jpeg" else "png"