feat: 工程大样图支持上传手绘量尺登记表(逐窗转换保留分格/开启扇/手写尺寸, 纯草图模式无样板, 横版1536x1024)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# CHANGELOG
|
||||
|
||||
- [2026-10-07 06:35] FEAT: v2.45 工程大样图支持上传手绘量尺登记表(用户核心场景)。blueprint 底图扩展: 上传手绘草图照片(现场量尺登记表)/2D 图纸截图二选一, 纯草图模式(无样板)不要求图纸数据; 提示词升级: 手绘登记表逐窗转换(保留各窗相对布局/分格/开启扇对角三角/手写尺寸规范誊写, 不保留表格框线, 模糊尺寸按可辨识程度誊写不编造); 前端蓝图 Tab 加草图上传(预览+移除, el-upload http-request 的 file 兼容 raw 包裹), 上传草图时脱离样板上下文(避免无关窗型特征误导); blueprint 尺寸固定 1536x1024 横版(量尺登记表整页比例, 对齐 autocad 口径); 真实实测: 用户手绘登记表(第52页 5 窗)78s 转规范大样图——分格/开启扇/尺寸(2780x2035/2810x2040/1850x2440/650/1500/700/1600/300)全保留规范誊写, 手写房间位置(27#南阳)识别保留, 表格框线去除, 标题栏/横版布局完整; 回归: 冒烟 60/60+24 页巡检 0 异常 (Files: src/huamenchuang/api/ai_render.py, frontend/src/views/AIRenderView.vue, doc/API.md)
|
||||
- [2026-10-06 07:17] FEAT: v2.44 AI 生成工程大样图(整合自 D:\Codeutocad 项目草图标准化能力)。后端 /api/ai-render 新增 blueprint 模式: 2D 图纸截图(sketch_b64, WindoorCanvas 新增 exportImage() 画布导出)为底图 images/edits 重绘黑白 CAD 工程大样图; 大样图提示词移植 autocad 项目风格迁移资产(正投影立面/细黑线框/玻璃淡灰填充/尺寸线/中文工程标注/标题栏+不编造尺寸/仅供审图与报价附件约束)并叠加 describe_window 窗型分格特征增强保真; /prompt 端点支持 blueprint 预览; 前端 AI 炫图页新增「工程大样图」Tab(样板选择+2D 图纸底图+一键生成+结果下载); 真实生成实测 59~68s: 样板704 → 黑白大样图(分格 F1/A1/F2 与原图一致, 开启扇对角线+把手保留, 尺寸标注 600/1200/400/1200 与几何一致, 中文引出线标注+说明+标题栏图名/规格/比例/日期); 修复 AIRenderView.loadTemplate 不回写 templateId(程序化自动选样板后 template_id=undefined 报未找到图纸数据, 手动下拉路径 v-model 自动更新故从未暴露); 回归: 冒烟 60/60+24 页巡检 0 异常 (Files: src/huamenchuang/api/ai_render.py, frontend/src/views/AIRenderView.vue, frontend/src/components/WindoorCanvas.vue, doc/API.md)
|
||||
- [2026-10-06 05:57] FEAT: v2.43 部位级材料选择——对应部位使用对应材料。估算器 estimate() 增加 material_overrides(部位级覆盖: profile 按 _profile_role 名称归位 frame/mullion/sash/pressline, 命中行替换名称/单价/米重; glass/hardware 整类替换)+overrides 回显; 新增 GET /api/costbook/series-materials(聚合全部系列用料按部位分组去重, 供跨系列混搭: frame 3 种/mullion 3/sash 2/pressline 6/glass 5/hardware 1); offer-price 新增 materials 参数透传; QuoteDialog 部位选材面板(外框型材/中梃型材/扇框型材/玻璃配置/五金套件 五下拉, 选项带价格与系列来源, 改选即重算 offer-price 实时刷新系列成本); 端到端实测: 样板704+系列75, 外框改选 95 系列 33框(25.88元/kg) → 系列成本 1251.10→1303.57 实时重算(手算吻合), 报价总额独立对比; 回归: 冒烟 60/60+24 页巡检 0 异常 (Files: src/huamenchuang/services/costbook_service.py, src/huamenchuang/api/quote.py, src/huamenchuang/api/costbook.py, frontend/src/components/QuoteDialog.vue, doc/API.md)
|
||||
- [2026-10-06 08:05] FEAT: v2.42 样板库整合材料系列选择——商家选材闭环。样板库预览对话框(编辑/非编辑态)新增「材料系列」选择器(GET /api/costbook/series: 75框扇齐平1.4/85框扇齐平1.8/95窗纱一体1.8, 来自鲁铭成本核算 Excel); 选择后算料报价携带 series_id → /api/offer-price 响应附加 series(系列名/壁厚)+series_estimate(按图纸实际宽高的系列八分区成本估算: 总成本/每平/型材/玻璃/五金/人工/margin_ladder 毛利阶梯); QuoteDialog 新增「材料系列成本参考」面板(统计卡+分区明细+建议售价50%毛利), 与报价总额并列对比辅助商家决策; 端到端实测: 样板704(1800x1600)+系列75 → 报价761.08元, 系列成本参考1251.1元/樘(每平434.41, 50%毛利建议1876.65); 回归: 冒烟 60/60+24 页巡检 0 异常 (Files: src/huamenchuang/api/quote.py, frontend/src/components/QuoteDialog.vue, frontend/src/views/TemplatesView.vue, doc/API.md)
|
||||
|
||||
+1
-1
@@ -459,7 +459,7 @@ uv run uvicorn huamenchuang.main:app --reload # 启动,http://localhost:8000
|
||||
|
||||
### 12.22 照片式 AI 炫图(v2.20 升级,P0-照片炫图)
|
||||
|
||||
> **v2.44 新增 `blueprint` 模式(工程大样图,整合自 autocad 项目草图标准化资产)**:`{mode:"blueprint", template_id|drawing_data, sketch_b64(2D 图纸截图)}` → AI 重绘为黑白 CAD 工程大样图(正投影立面/细黑线框/玻璃淡灰填充/尺寸线/中文工程标注/标题栏;严格保留分格与开启扇位置,不编造尺寸),可作报价与合同附件。提示词预览 `/api/ai-render/prompt` 同样支持 `mode:"blueprint"`。
|
||||
> **v2.44 新增 `blueprint` 模式(工程大样图,整合自 autocad 项目草图标准化资产);v2.45 支持手绘草图**:`{mode:"blueprint", sketch_b64(手绘量尺登记表照片或 2D 图纸截图), template_id|drawing_data?(可选, 仅作窗型特征参考; 纯草图模式可不传)}` → AI 重绘为黑白 CAD 工程大样图(正投影立面/细黑线框/玻璃淡灰填充/尺寸线/中文工程标注/标题栏;一页多窗逐窗转换,保留分格/开启扇/手写尺寸并规范誊写,不编造尺寸),可作报价与合同附件。提示词预览 `/api/ai-render/prompt` 同样支持 `mode:"blueprint"`。
|
||||
|
||||
对齐目标站 `renderV2` 炫图 7 分类:**渲图 / 安装合成 / 门窗擦除 / 室内装修 / 别墅 / 去围栏 / 变毛坯**。
|
||||
|
||||
|
||||
@@ -87,20 +87,26 @@
|
||||
<!-- ============ 工程大样图(2D 图纸 → 黑白 CAD 大样图, 整合自 autocad 项目草图标准化能力) ============ -->
|
||||
<template v-else-if="tab === 'blueprint'">
|
||||
<div class="controls">
|
||||
<el-select v-model="templateId" filterable placeholder="选择样板" style="width:220px" @change="loadTemplate">
|
||||
<el-option v-for="t in templates" :key="t.id" :label="t.name || `样板#${t.id}`" :value="t.id" />
|
||||
</el-select>
|
||||
<span class="tip">以 2D 图纸为底图,AI 重绘为黑白 CAD 工程大样图(含尺寸线/标题栏,可作报价与合同附件)</span>
|
||||
<el-upload :show-file-list="false" accept="image/*" :http-request="onSketchUpload">
|
||||
<el-button type="primary" plain>{{ sketchPhoto ? '更换手绘草图' : '上传手绘草图' }}</el-button>
|
||||
</el-upload>
|
||||
<el-button v-if="sketchPhoto" size="small" text type="danger" @click="sketchPhoto = ''">移除草图</el-button>
|
||||
<span class="tip">{{ sketchPhoto ? '已上传手绘草图,AI 将逐窗转为规范大样图' : '上传现场手绘量尺登记表照片;也可不传,用所选样板的 2D 图纸生成' }}</span>
|
||||
<div style="flex:1" />
|
||||
<el-button type="primary" :disabled="!drawing || generating" :loading="generating" @click="generateBlueprint">
|
||||
<el-button type="primary" :disabled="(!sketchPhoto && !drawing) || generating" :loading="generating"
|
||||
@click="generateBlueprint">
|
||||
{{ generating ? 'AI 绘制中…' : '✦ AI 生成大样图' }}</el-button>
|
||||
</div>
|
||||
<el-progress v-if="generating" :percentage="progress" :stroke-width="10" style="margin: 10px 0"
|
||||
:format="() => stageText" />
|
||||
<div v-if="drawing" class="bp-stage">
|
||||
<!-- 底图: 优先上传的手绘草图, 否则 2D 图纸 -->
|
||||
<div v-if="sketchPhoto" class="bp-stage bp-photo">
|
||||
<img :src="sketchPhoto" class="sketch-img" />
|
||||
</div>
|
||||
<div v-else-if="drawing" class="bp-stage">
|
||||
<WindoorCanvas ref="bpCanvasRef" :drawing-data="drawing" :editable="false" />
|
||||
</div>
|
||||
<el-empty v-else description="请先选择样板" />
|
||||
<el-empty v-else description="上传手绘草图照片,或先选择样板" />
|
||||
</template>
|
||||
|
||||
<div v-if="variants.length" class="gallery-foot">
|
||||
@@ -252,14 +258,14 @@ const aiImage = ref('')
|
||||
const aiPrompt = ref('')
|
||||
const aiFeatures = ref('')
|
||||
|
||||
async function generateCloud(mode: 'quick' | 'photo' | 'blueprint', sketch_b64?: string) {
|
||||
if (!drawing.value) return
|
||||
async function generateCloud(mode: 'quick' | 'photo' | 'blueprint', sketch_b64?: string, withoutTemplate = false) {
|
||||
if (!drawing.value && !(mode === 'blueprint' && sketch_b64)) return
|
||||
if (mode === 'photo' && (!photo.value || !rect.value)) return
|
||||
generating.value = true
|
||||
aiImage.value = ''
|
||||
variants.value = []
|
||||
progress.value = 8
|
||||
stageText.value = mode === 'photo' ? '上传照片与窗型特征' : mode === 'blueprint' ? '上传 2D 图纸与分格特征' : '解析窗型结构'
|
||||
stageText.value = mode === 'photo' ? '上传照片与窗型特征' : mode === 'blueprint' ? '上传手绘草图' : '解析窗型结构'
|
||||
const timer = window.setInterval(() => {
|
||||
progress.value = Math.min(92, progress.value + 2)
|
||||
if (progress.value > 40) stageText.value = 'AI 绘制中(约 30~90 秒, 请勿关闭页面)'
|
||||
@@ -275,7 +281,8 @@ async function generateCloud(mode: 'quick' | 'photo' | 'blueprint', sketch_b64?:
|
||||
}
|
||||
}
|
||||
const d = await api<any>('post', '/api/ai-render/generate', {
|
||||
template_id: templateId.value, mode,
|
||||
// 上传手绘草图时脱离样板上下文(登记表窗型与样板无关, 避免特征误导)
|
||||
template_id: withoutTemplate ? undefined : templateId.value, mode,
|
||||
style: form.ai_style, time: form.ai_time,
|
||||
scene: mode === 'photo' ? scene.value : form.scene, photo_b64, rect: rectPayload, sketch_b64,
|
||||
}, { timeout: 200000 })
|
||||
@@ -291,8 +298,28 @@ async function generateCloud(mode: 'quick' | 'photo' | 'blueprint', sketch_b64?:
|
||||
}
|
||||
}
|
||||
|
||||
/** 工程大样图: 以 2D 图纸画布截图为底图, AI 重绘为黑白 CAD 大样图 */
|
||||
/** 工程大样图: 底图优先上传的手绘草图照片, 否则 2D 图纸画布截图; AI 重绘为黑白 CAD 大样图 */
|
||||
const sketchPhoto = ref('')
|
||||
|
||||
function onSketchUpload(e: { file: unknown }) {
|
||||
// el-upload http-request: options.file 即原始 File(部分版本包 raw)
|
||||
const opt = e.file as { raw?: File } | File
|
||||
const file = (opt as { raw?: File }).raw ?? (opt as File)
|
||||
if (!(file instanceof File)) return
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => {
|
||||
sketchPhoto.value = String(reader.result)
|
||||
ElMessage.success('草图已上传')
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
|
||||
async function generateBlueprint() {
|
||||
// 上传的手绘草图优先(脱离样板上下文); 无上传时用 2D 图纸画布截图
|
||||
if (sketchPhoto.value) {
|
||||
await generateCloud('blueprint', sketchPhoto.value.split(',')[1], true)
|
||||
return
|
||||
}
|
||||
await nextTick()
|
||||
const dataUrl = bpCanvasRef.value?.exportImage?.()
|
||||
if (!dataUrl) { ElMessage.warning('图纸截图失败,请稍后重试'); return }
|
||||
@@ -396,6 +423,8 @@ onMounted(async () => {
|
||||
background: rgba(0,0,0,0.5); padding: 4px 10px; border-radius: 10px; pointer-events: none; }
|
||||
.preview { height: 58vh; }
|
||||
.bp-stage { height: 58vh; border: 1px solid #e4e7ed; border-radius: 6px; overflow: hidden; }
|
||||
.bp-photo { display: flex; align-items: center; justify-content: center; background: #f5f6f8; }
|
||||
.sketch-img { max-width: 100%; max-height: 100%; object-fit: contain; }
|
||||
.gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; margin-top: 14px; }
|
||||
.gallery-foot { margin-top: 8px; }
|
||||
.ai-result { margin-top: 14px; text-align: center; }
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 472 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 346 KiB |
@@ -60,36 +60,39 @@ async def preview_prompt(body: AiRenderBody, user: CurrentUser, db: DbSession):
|
||||
|
||||
|
||||
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 = ""
|
||||
"""工程大样图提示词:手绘草图/量尺登记表照片或 2D 图纸 → 黑白 CAD 大样图
|
||||
(移植自 autocad 项目草图标准化资产,叠加 drawing_data 结构化特征增强分格保真)。"""
|
||||
feat = ""
|
||||
if drawing:
|
||||
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 图纸(彩色渲染稿),仅作为几何与分格的几何来源。
|
||||
Primary request: 将输入的门窗手绘草图(现场量尺登记表照片)或 2D 图纸重绘为清晰、整洁的工程 CAD 大样图风格预览。
|
||||
Input images: 第一张是原始手绘草图照片或 2D 图纸,仅作为几何、分格与标注位置的几何来源。输入可能是一页包含多个窗的手绘量尺登记表——逐窗转换,保持各窗的相对布局与各自分格。
|
||||
Style/medium: 黑白二维门窗工程大样图、正投影立面、细黑线框、玻璃淡灰填充、外部尺寸线与分格尺寸标注、中文工程标注、右下角标题栏(图名/比例/日期)。
|
||||
Constraints: {feat} 严格保留原图的可辨识总体构图、窗扇/玻璃分格数量与比例、开启扇方向线与把手位置;不要增删或合并分格;不要编造缺失尺寸,标注尺寸须与原图几何一致;输出仅供人工审图与报价附件,不作为 CAD 几何或生产数据来源;无水印、无装饰性场景、无彩色。
|
||||
Constraints: {feat} 严格保留手稿中可辨识的总体构图、每个窗的窗扇/玻璃分格数量与比例、开启扇方向符号(对角三角线)位置、手写尺寸数字;不要增删或合并分格;手写字迹模糊的尺寸按可辨识程度规范誊写,不要编造缺失尺寸;输出仅供人工审图与报价附件,不作为 CAD 几何或生产数据来源;无水印、无装饰性场景、无彩色、不保留登记表表格框线。
|
||||
Avoid: 三维渲染、透视效果、写实照片、彩色渲染、虚构品牌、模糊文字。"""
|
||||
|
||||
|
||||
@router.post("/generate")
|
||||
async def generate(body: AiRenderBody, user: CurrentUser, db: DbSession):
|
||||
"""云端 AI 生图(gpt-image):quick=生成 / photo=参考图编辑 / blueprint=2D图纸转工程大样图。"""
|
||||
"""云端 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)
|
||||
if drawing is None:
|
||||
# blueprint 纯草图模式(上传手绘登记表, 无样板)不要求图纸数据
|
||||
if drawing is None and body.mode != "blueprint":
|
||||
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 图纸截图")
|
||||
return fail("大样图模式需要草图照片或 2D 图纸截图")
|
||||
try:
|
||||
info = describe_window(drawing)
|
||||
info = describe_window(drawing) if drawing else {"width_mm": 0, "height_mm": 0, "summary": ""}
|
||||
# 照片模式: 把框选洞口位置转成提示词方位描述, 引导 AI 装窗位置
|
||||
rect_hint = ""
|
||||
if body.mode == "photo" and body.rect:
|
||||
@@ -111,9 +114,10 @@ async def generate(body: AiRenderBody, user: CurrentUser, db: DbSession):
|
||||
rect_hint = ""
|
||||
if body.mode == "blueprint":
|
||||
prompt = build_blueprint_prompt(drawing)
|
||||
size = "1536x1024" # 大样图横版(量尺登记表整页比例), 对齐 autocad 项目口径
|
||||
else:
|
||||
prompt = build_prompt(drawing, body.style, body.time, body.scene, rect_hint=rect_hint)
|
||||
size = pick_size(info["width_mm"], info["height_mm"])
|
||||
size = pick_size(info["width_mm"], info["height_mm"])
|
||||
except Exception:
|
||||
return fail("图纸数据解析失败")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user