户型图 → 装修设计器网页
产出一个单文件 HTML,双击离线打开:界面和功能就是完整的“户型装修设计器”,只是“示例户型”换成了用户这张图。
设计器模板是 assets/template.html,由项目 D:\桌面文件\混剪skill\房屋设计\designer 打包而来,不要改模板。
你要做的只有一件事:写出准确的 HouseSpec(户型描述 JSON),格式见 reference/spec-format.md(动手前必读)。
依赖(首次)
- Python:
pip install numpy pillow opencv-python pymupdf ezdxf(后两个给 CAD 图纸用) - Node:在
scripts/目录执行npm install(puppeteer-core,用本机 Chrome/Edge 截图)
流程
脚本都在本 skill 的 scripts/ 下。工作目录建议用户图片旁边的 <图名>_design/,最终 HTML 放在图片同目录。
0. 先看是哪种图,选识别路线
| 输入 | 路线 |
|---|---|
CAD 导出的矢量 PDF(放大不糊;fitz 的 get_drawings() 有几千个图元)、DXF | 走 CAD 路线(下面 2C):cadvec.py 直接读图元坐标,墙、门窗洞口精确到 mm,不用像素比例和 axismap |
| 一页上并排好几层平面(别墅、自建房、联排的一层 / 二层……),或描边墙在每个交接处都断开(量房软件导出、cadvec 报“没认出墙厚”或墙认不全) | 走 整栋楼路线(下面 2D):cadsplit.py 按楼层切开、识别描边墙、各层自动对齐,输出整栋楼初稿 |
| DWG | 让用户另存为 DXF,或用看图软件 / 网页导出 SVG(SVG 是真实毫米坐标、按图层分色,最准),再走 CAD / 整栋楼路线;实在不行打印成 PDF |
| 图片、截图(含黑底 CAD 截图)、扫描件、只有位图的 PDF | 走图片路线(下面 2):PDF 先渲染成 PNG(fitz 200dpi) |
CAD 图纸的画法(涂黑 = 钢筋混凝土 / 承重、砖纹填充 = 新砌墙、3~4 线 = 窗、弧线 = 平开门、⊠ = 烟道管井、⊕ = 地漏、45° 斜杠 = 尺寸起止符号、三道尺寸、轴线)
和人工核对清单见 reference/cad-conventions.md,走 CAD 路线前必读。
1. 读图
查看户型图,记下:
- 最外圈总尺寸(总宽、总深 mm),以及分段尺寸
- 每个房间的名字和位置,门(开向、门轴在哪端)、窗、推拉门、入户门
- 门窗的具体类型:子母门、折叠门、隐形门;平开窗、推拉窗、落地窗、高窗、飘窗(判断方法见格式说明“门窗”一节)
- 承重墙(多数户型图用黑色/深色实心表示),柱、烟道、管井
- 指北针方向(写进
north);用户提到城市就写进site,日照模拟会按当地纬度算 - 图上标的面积,用来事后核对
图没有任何尺寸时,问用户要一个已知尺寸(比如某面墙多长),或者按常见尺寸估算,并且在交付时说明比例是估的。
2. 自动识别墙体(初稿 + 比例)
bashpython scripts/analyze.py 户型图.jpg --total-width 12340 --total-height 11940 --out <工作目录>/analysis
- 输出
mmPerPx(比例),scale_check里是按宽、按深分别算的比例。两者差 2% 以内说明比例可信。 差得多通常是因为有凸出部分(飘窗、阳台)超出了总尺寸的范围:改用--dim 像素x0 像素x1 毫米,按某一段标注线两端的像素位置定比例。 analysis.png:原图 + 识别出的墙段(W 编号,红色 = 承重墙,橙色 = 非承重)+ 断口(G 编号,门窗候选)+ 尺寸线刻度(T/B/L/R 编号的紫色圈)+ 像素标尺,用来读坐标。- 承重墙:脚本按墙芯颜色自动分两类(深色 = 承重,
bearing_by: "color")。图上所有墙同一个颜色时只能按厚度猜(bearing_by: "thickness"), 这些墙在 analysis.png 上编号带?、也列在输出的bearing_unsure里——必须对照原图图例逐面确认(图例常写“承重墙 / 非承重墙 / 新建墙”)。 常见画法:承重墙黑色或深灰实心、斜线填充;非承重墙浅灰实心或只画两条边线;外墙一般按承重处理;厨卫的 100120mm 薄墙基本是非承重。 颜色分界不对时用255)。--bearing-max 亮度手动指定(承重墙颜色的最大亮度,0 - 隔墙只画了两条细边线、中间是白的(贝壳等常见):加
--outlined,否则这些墙会漏掉。 analysis.json的dims_px:每条尺寸线上的刻度(尺寸界线)像素位置,下一步精确定位墙要用。draft.json:直接可用的 HouseSpec 初稿(墙 + 门窗候选 + 底图参数)。识别会漏墙,漏掉的通常是:被窗户打断的墙段、很细的隔墙、浅色玻璃隔断;门窗类型也只是猜测。- 墙体颜色偏浅、识别不到时调
--gray-max 200。图例、logo 被误识别成墙时用--crop x0 y0 x1 y1限定范围。
2C. CAD 路线:矢量识别(PDF / DXF)
bashpython scripts/cadvec.py 图纸.pdf --out <工作目录>/cad # 图上有尺寸标注:自动读数字定比例、按标注定坐标 python scripts/cadvec.py 图纸.dxf --out <工作目录>/cad # DXF:自带单位,标注图元(含改写的标注文字)直接用 python scripts/cadvec.py 图纸.pdf --total-width 10540 ... # 读不出标注时才需要:最外道总尺寸(墙体外皮)
- 尺寸以标注数字为准(精度的关键):用户拿图纸上的数字逐个对设计器,墙厚、每段墙、洞口、房间开间进深都必须等于标注,不是“和图形差不多”。cadvec 的做法:
- 找全所有尺寸线(直线 + 落在线上的 45° 起止符号)和每个刻度的尺寸界线(界线另一头 = 被量的那个墙面);
- 读出每段的数字:文字层直接读;没有文字层(SHX / TrueType 字体炸成轮廓,最常见)就用
dimtext.py破译——同一张图同一个数字字形完全一样,按形状聚类,再用量出的长度投票“哪类字形是哪个数字”,挤在一起的两个数字(“390120”)会拆开分给相邻两段;读出的数字和图形差 > 12mm / 0.8% 的不用; - 每个方向把所有标注当成“刻度之间的距离约束”做加权最小二乘:外侧分段最细的那道(门窗洞口细部尺寸)优先级最高,其次轴线尺寸、总尺寸,室内细部尺寸最低;明显读错的数字自动剔除;
- 墙面、洞口边离某个刻度 ~15mm 以内就直接取那个刻度的标注值(图上线和刻度常差几毫米),选哪个刻度先看它的尺寸界线是不是伸到了这面墙;
- 坐标精确到 0.5mm(245、225 这种奇数墙厚,中线在半毫米上,两侧墙面才是整数)。
- 输出里看:
scale_from写着“尺寸数字(n/m 段读出)”;dim_labels读出比例;dim_conflicts是图纸自己前后矛盾的标注(例如分段加起来 12070、总尺寸写 12060;两道尺寸量同一段一个 2770 一个 2775)——这些地方不可能同时满足,按“细部尺寸优先”处理,交付时要逐条告诉用户。cad.json的dims[]:每条尺寸线的labels(读出的数字,?xxx= 没读准)和segs(最终坐标量出来的长度),两者逐段相等才算对上;cad.png上每段标注旁边写着读出的数字(绿 = 读出且一致,红 = 没读出)。 - 读不出数字(扫描件转的 PDF、字体特殊)时会退回整体比例:这时要加
--total-width,并在交付时说明尺寸是按图形量的、不是按标注。 cad.png:毫米网格 + 墙段(红 = 实心承重、紫 = 填充新砌、绿 = 描边隔墙、蓝 = 窗带)+ 墙线 W 编号 + 洞口 O 编号和类型 + 符号 + 尺寸刻度,坐标直接就是 mm。cad.json:openings[].why写着每个洞口的判断依据;walls[].free是悬空端;dims是尺寸线刻度(mm),可以和图上数字逐段对。draft.json:基本可以直接用的 HouseSpec——墙(含flush、status: new、bearing)、门窗(有弧线的门带hinge/into)、管井 / 地漏 / 柱、房间名(有文字层时)、底图。 写生成脚本时读入 draft.json 再补改,不要重新手写墙:补房间名、把hole按房间功能换成门并定开向、窗台高、家具(见reference/cad-conventions.md末尾的核对清单)。- 实测(本 skill 自带回归):装修公司原始结构图 PDF,101 段标注读出 100 段,94 段与设计器尺寸差 ≤1mm,其余 6 段全是图纸自身矛盾;装修公司原始结构图 PDF,墙体与手工逐面量取一致(≤10mm),16 个洞口全部识别、类型正确;DXF(天正图层、门弧、图案填充、标注)往返 16/16、墙体 0 误差;黑底打印的 PDF 墙体全对。
3. 写完整的 HouseSpec
(CAD 路线:读入 cad/draft.json 后只做补改,跳过下面第 1、2 条。)
推荐写一个小的 Python 生成脚本,不要手写 JSON。参考 reference/example-gen.py:用像素写墙,脚本统一换算成 mm。
- 像素 → 毫米的换算(尺寸精度的关键):图上有分段尺寸标注时,一定用
scripts/axismap.py按标注定位,不要只乘一个整体比例—— 户型图本身画得不准,整体按比例换算每面墙会差 20~80mm,标注数字才是准的。做法:- 在
dims_px.top(或 bottom)里挑一条分段尺寸线,按 analysis.png 上的刻度编号,把相邻刻度之间的标注数字读出来; 刻度多出来的(文字、墙角误检)跳过,少的照原图补上像素位置。左 / 右侧同理得到竖直方向。 X = Axis(chain(刻度像素, 各段毫米))、Y = Axis(chain(...)),生成脚本里用X(px)、Y(px)代替(px-OX)*S;X.report()会标出读数可疑的段。- 看清标注量的是墙面还是轴线:量到墙面(净尺寸)时,墙中线 = 墙面 ± 墙厚 / 2。
- 完整示例见
reference/example-gen-precise.py:两条尺寸线合成一个Axis,墙写成(墙面像素1, 墙面像素2)时中线和墙厚都直接取标注;同一条墙线上黑色段、灰色段分开写(承重 / 非承重不同)。 - 墙按标注修正后,靠墙家具会被设计器自动推出墙面(嵌入 ≤120mm 的),不用逐个改坐标。
- 图上没有分段标注、只有总尺寸时,才退回整体比例:从
analysis.json拿mmPerPx和外轮廓左上角像素outline_px[0..1]作为 S、OX、OY。
- 在
- 对照
analysis.png的像素标尺和segments_px(每段墙的 line = 中线像素位置),逐面列出墙:外圈闭合,内墙端点落在别的墙中线上。 墙厚按图上的像素厚度 × 比例取整(常见 240 / 200 / 120 / 100)。 - 门窗:
from/to取洞口两端的像素。门要填into(门开进去的那个房间里的一点)和hinge。 - 房间标签放在原图房间名的位置。
- 承重 / 非承重按原图图例逐面核对(
bearing_unsure里的墙必须看)。 同一直线上厚墙接薄墙(外墙段接隔墙、黑色段接灰色段):设计器会按“没有垂直墙的那一侧平齐”自动处理;两侧都有 / 都没有垂直墙的交接点(外墙中段变薄、十字交叉)看原图哪一侧墙线连续,给薄墙手写flush(格式说明“面齐平”)。交付前在 plan2d 截图里放大每个厚薄墙交接点确认没有错台。 - 墙的位置和厚度按原图量墙面,不要按刻度猜:
scripts/wallfit.py逐面墙量两个墙面(涂黑墙量黑块边、描边墙量两条线),给出中线和厚度。 刻度常是轴线,墙不一定居中在轴线上(差 3~6px 就是几十毫米的错台);外墙常比内墙厚(如 300 vs 200); 描边画的非承重墙往往和同一直线上涂黑的承重墙一样厚、墙面对齐,不要默认给 100。同一直线上厚度不同的两段,看原图哪一侧墙线连续,给薄的那段写flush。 check 会报「墙面错台」(房间里看得到的那一侧墙面没对齐),必须清零。 - 再用标注尺寸核对一遍(下一步自查会把设计器算出来的尺寸列出来,逐个对数)。
2D. 整栋楼 / 描边墙路线:cadsplit.py
bashpython scripts/cadsplit.py 图纸.svg --out <工作目录>/split # SVG / DXF:坐标就是 mm python scripts/cadsplit.py 图纸.pdf --out <工作目录>/split --ref-dim 11720 # PDF:第一张平面最长那道水平尺寸(一般是最外道总尺寸)上标的数 python scripts/cadsplit.py 图纸.pdf --out ... --scale 162.5 # 或直接给 mm / pt # 可选:--names 一层,二层,三层 --order ltr|rtl|ttb|btt(楼层在图上的排列) --wall-color '#000000'
- 怎么识别:图元按空白间隔聚成几块 = 几张平面(图例、指北针这些小块丢掉);墙线颜色 = 能围出最多“墙厚宽度窄条”的颜色; 所有线栅格化后,墙线之间的窄长白条 = 墙(长边是墙线颜色),按行 / 列拆成矩形 → 墙中线 + 墙厚(吸到 100 / 120 / 150 / 200 / 220 / 240… 常用值); 同一直线上的墙段连成墙线,中间的缺口看里面画了什么:墙内部有玻璃线 = 窗,只有两侧面线 / 门框线 = 门(量房图常把门洞画成一块),空的 = 门洞; 墙端和前方的柱 / 垂直墙之间画了门窗线的也接上;外围非墙色的长窄条 = 玻璃墙 / 玻璃栏杆(整段落地窗);涂黑块、比墙厚的方块 = 柱; 各层按墙线重合最多的平移自动对齐到同一坐标系。
- 输出:
building_draft.json(整栋楼 HouseSpec 初稿,每层带底图)、check<i>.png(红墙 / 绿门 / 蓝窗 / 橙门洞 / 紫柱 / 青楼梯候选 / 品红电梯候选 / 黄开洞候选)、floor<i>.dxf(每层干净的 mm DXF,可再交给 cadvec 或别的软件)、floor<i>.png(对齐的底图)、report.json。 _candidates(要人工确认):楼梯 = 等间距平行短线组(rect、踏步数、走向),电梯 = 楼梯区里三面墙围成内宽 0.75~1.45m 的小间,开洞 = 成片 45° 斜线。- 认不出来要人工补:房间名(文字常被炸成线 / 三角面)、门的开向和类型、窗台高、层高、楼层顺序、楼梯上下方向; PDF 里门块和墙同色时认不出门(只有 SVG / DXF 的门窗图层能认),要对照原图补;PDF 的坐标精度受出图比例限制(1:400 左右约 ±20mm),按标注数字修。
- 实测(格林御庭 2-9 栋,5 层并排在一张 A0 上):SVG 输入 5 层墙体、门窗、玻璃栏杆、柱全部识别,三层 8 个房间开间进深与标注 0 误差; PDF 输入(--ref-dim)比例误差 0.03%,房间尺寸与标注差 ≤ 20mm,门要人工补。
- 下一步:写生成脚本读入
building_draft.json补齐(模板reference/example-gen-building.py),再 build.py + check.mjs。
整栋楼 / 多楼层(别墅、自建房、联排、复式)
一张图纸上有几层平面(或几张图各是一层)时,做成一个多楼层设计器,不要拆成几个 HTML:
- spec 写成整栋格式:
{"name": 楼名, "slab": 楼板厚, "floors": [ {"name": "一层", "height": 层高, "walls": ..., ...}, ... ]},floors 从最低一层往上排,每层就是一份普通 HouseSpec(walls / rooms / furniture / autoElec 都照写),每层可以有自己的underlay(原图里这一层那一块),build.py 会逐层打包。格式见reference/spec-format.md“整栋楼”。 - 所有楼层用同一个坐标系(上下层对齐:同一根柱、同一面外墙在各层坐标相同),一般取主体左墙内皮 / 北墙内皮为原点。
- 楼梯用家具
stairU(U 形三跑)/stairRun(直跑,opt.flat为平台),h= 层高 + 楼板厚;上层楼板在楼梯上方开洞写voids(楼梯井、挑空、采光井;depth > 0是下沉区 / 下行楼梯口)。 - 楼梯怎么从平面图读(各层楼梯必须接得上:下层梯段的终点 = 上层楼梯洞口的落地边):
- 国标 / 教材画法(每层平面图都是在本层楼面以上约 1.2~1.5m 处水平剖开往下看):
- 底层:剖切面在第一个休息平台下方,第一跑被剖断,用 30° / 45° 折断线表示(为了和踏步线区分),只画剖切面以下那一截,只标“上”;剖切面以上的部分不画或画虚线。
- 中间层:折断线画在梯段中段,一侧“上 ×× 级”、另一侧“下 ×× 级”,下行梯段被上方梯段挡住的部分也以折断线结束(双折断线)。
- 顶层:剖切面在楼梯以上,踏步完整、没有折断线,只标“下”,楼面临空一侧画水平栏杆。顶层图是唯一能看到整部楼梯的平面图。
- 设计器已按这个画:直跑梯段高过剖切面(
opt.cut,默认 1500)的踏步画虚线、交界处画 45° 折断线、“上”箭头到折断线为止;上一层楼梯井里自动画出下一层的楼梯(实线、标“下”,不再写“楼梯井”)。图上的折断线在别的高度时,用opt.cut对齐到图上的位置。 - 首层:剖切面约 1.2~1.5m。画实线的是着地的低段(起步段 + 剖切线以下),45° 折断线以外是剖切面以上的部分,不画或画虚线。所以首层图上“楼梯看起来到某处就断了”,不代表楼梯在那里到顶;它会继续往上,底下净高 > 2.2m 的地方照样能走人、开门(门开在高段梯段下面很常见)。
- 先分清楼梯两端:哪端着地、哪端连上层(读反了整部楼梯都会错,方向、层高、洞口全跟着错):
- 着地端(起步):在下层图上画实线、带“上”字和起步圆点 / 箭头尾巴的那一端;常常是一小段短跑(2~3 级)或第一跑的开头,旁边是本层地面、走道。
- 连接上层的到达端:在下层图上要么在折断线以外(不画 / 虚线),要么根本看不到;在上层图上是洞口边紧贴上层地面(过厅、走道)的那一端,标“下”的起点就在这里。到达端外侧一定是上层能站人的地面,不是洞口、墙、室外。
- 判断口诀:下层看实线找起点,上层看洞口边找终点;“上”字在起点,“下”字在终点;折断线永远在两端之间、偏向高的那边。
- 两端定下来再顺着走向数踏步把中间接起来(数踏步,踢面 160~180),终点必须正好落在上层到达端。
- 设计器会自动检查两端(自查报告里的“楼梯着地端 / 楼梯到达端 / 楼梯净高”):最低一级离地 ≤ 一级、踩在本层地面上;最高一级 = 楼面到楼面、外侧是上层地面;梯段头顶净空 < 2m 的地方上层必须开洞。有这几条 warn 说明两端弄反了、层高 / 踏步数对不上或洞口没盖住,必须改到没有。
- 顶层:楼梯洞口里能看到下面的梯段(实线);洞口里画斜线 / 交叉线的区域是楼板开洞(往下看得到下层梯段),不是转角踏步。上层图边上量洞口的分段尺寸(如 830 + 120 = 洞口宽 + 栏板)可以用来核对。
- 转弯处从两跑分界线端点向外角画放射线 = 扇形转角踏步(
stairU的winders,或几块stairRun平台按矩形近似);空白矩形 = 休息平台。 - 每层都严格按自己那张图还原,不许为了楼梯挪下层的门、家具、墙。下层楼梯照下层图放(直跑段
opt.steps写数出来的踏步数),上层只在洞口范围开voids。两层对不上时先怀疑自己的读法(起步方向、剖切以上的隐藏段、开洞符号),推通了再动手;实在推不通再问用户。 - 交付前用漫游走一遍:从起步走到上层地面;从高段梯段底下穿过、进出旁边的门;中途往外沿走应被扶手挡住。
- 先判断是哪种楼梯 / 层高(不同户型不同判断,别默认“一跑上到二楼”):
- 两层图各数踏步:下层图实线段 + 上层图里看得到的梯段 + 转角踏步,合计 N 级。楼面到楼面 ≈ N × 165~175。
- N ≈ 16~19 → 普通层高(约 3m),一部双跑 / L / 直跑楼梯就到上层。
- N ≈ 26
34 → **挑高复式 / LOFT(一层挑高 4.55.5m):楼梯是多跑盘旋**的(常见四跑三拐角),上面几跑压在下面几跑正上方(踏面之间净空 ≥ 2m)。 这时下层图只画得到着地的前两跑(画到折断线),后两跑和拐角在剖切面以上,画成虚线或叠在下面梯段上的虚线;上层图洞口里看到的是最后两跑(一排往外、一排往回)和它们之间的转角,和下层图位置对不上是正常的——它们本来就不是同一跑。 推法:从下层的起步段开始,按踏步顺序把每一跑、每个拐角接起来,最后一跑必须落在上层过厅 / 走道边;两排梯段之间的那道线(尺寸里常见 100~120)是中间隔断 / 栏杆,不是楼板栏板。 - 层高定下来后:一层
height= 楼面到楼面 − 楼板厚;整个上层图里画楼梯的那块(看得到的所有梯段 + 转角)都写进上层voids。
- 多跑楼梯的写法:每一跑一个
stairRun(z= 起点高度、h= 这一跑升高、opt.steps= 踏步数),图上拐角画放射斜线的是扇形转角踏步,用opt.winder照斜线写(见格式说明);空白矩形才是休息平台,用opt.flat。扶手自动:外沿临空、或旁边的梯段高差超过一步(上下叠着的两跑)都会装;和相邻梯段 / 平台一步之内接上的地方不装。 - 下层图按剖切画:被切到的那一跑在
opt.cut(剖切高度)处画 45° 折断线、以上虚线;整段在剖切面以上的梯段、平台只画虚线、不画折断线和箭头。
- 国标 / 教材画法(每层平面图都是在本层楼面以上约 1.2~1.5m 处水平剖开往下看):
- 识别:多层并排在一张图上 / 描边墙 → 用上面的 cadsplit.py(自动切层、对齐、出初稿);每层是单独一张图的,各自用 cadvec / analyze 还原后拼成 floors(注意对齐到同一坐标系)。楼层顺序看楼梯能不能接上:下层上行梯段的终点要落在上层楼梯起步处。
- 位图里几层并排(截图、照片,cadsplit 用不了;每层比例常常不一样):每层按自己的分段尺寸刻度定 mm/px 和原点像素,各层完全相同的那条分段尺寸链(如西侧 220/1590/220/1900)起点对到同一毫米坐标 = 同一坐标系;
再用
scripts/gridmm.py 图 OX OY S X0 Y0 X1 Y1 out.png给每层逐块叠毫米网格,直接按毫米读墙、门窗、楼梯(比对着像素换算快、不易错)。每层底图用这一层切出来的图。 完整示例:reference/example-gen-duplex.py(挑高复式、四跑三拐角楼梯)。 - 设计器里:左侧“户型”面板管理楼层(改名、层高、排序、增删、复制本层),画面左侧楼层面板切换当前层(PgUp / PgDn);2D「平铺」把各层平面摆开、点哪层编辑哪层,「单层」只看当前层;3D 顶部楼层条:「整栋」叠成整栋楼、「平铺」各层并排摆开(每层上方有层名牌)、点「一层/二层…」把那一层单独拿出来看(勾「含下层」= 剖切),3D 里点某一层也会单独拿出来;风格(沙盘、彩平图、配色)对单层和平铺都生效。漫游时可以直接走楼梯上下楼(踏步、平台逐级升高,走过半层自动切到那一层;楼梯井、下沉区边上有落差的地方自动装玻璃护栏,人也走不进去;楼梯外沿不靠墙、悬空的那几段自动加扶手,贴墙的段不加;扶手有碰撞,走不出梯段),所以楼梯要按图纸位置放准、
voids要盖住楼梯井。有电梯的(别墅常见:楼梯井正中一个内宽 900 左右、三面墙一面开口的小井道,可能只在某一层画了井道墙)每层放家具elevator(同一位置),井道写voids带"shaft": true(楼板开洞但能站人、不装护栏),漫游时走进轿厢会出现楼层按钮,关门 → 升降 → 到站开门,人跟着换层。所有编辑、水电、报价、导出都作用于当前层;报价可以切“全楼汇总”;撤销 / 重做跨楼层。 - 自查:check.mjs 自动逐层检查,输出
plan2d_<i>.png、overlay_<i>.png、plan2d_all.png、view3d.png(整栋)、view3d_cut<i>.png(剖到每层),report.json 里floors[]每层一份。 - 漫游走查(有楼梯必做):
node scripts/walktest.mjs 设计器.html 路线.json,路线照reference/example-walk-route.json写(起点 + 一段段移动)。 看输出:爬楼梯时脚高逐段升高、过半层楼层号 +1、到顶脚高 0;往扶手外 / 墙外走坐标应停住;穿门、从高段梯段底下走过去坐标应能通过。走不通就回去改楼梯分块、高度或洞口。
独栋别墅(车库、庭院、多层、挑空)
独栋别墅图和公寓图的区别:主楼外面还有车库、庭院围墙、车道口、车棚、泳池,楼里常有挑空客厅、电梯、地下室 / 设备间、露台(车库顶上、顶层)。按下面认:
- 楼层:一般 2
4 层(可能有地下室),一张图里几层并排或每层一张,照“整栋楼 / 多楼层”写成一个 floors;首层常挑高 3.33.6m,楼上 3.0m 左右,楼板 120~150。所有层用同一坐标系,原点取整栋最西北的外墙外皮(车库凸出去就以车库为准)。 - 车库:图上标“车库 / 车位 / 停车”,外墙上有一个 2.4~6m 宽的大洞口(单车位 ~2.7m、双车位 ~5m),常画一条粗线或虚线门板、有时写“卷帘门 / 电动门”→ 门型
garage(电动卷帘门,2D 画门板 + 室内侧卷帘箱虚线,3D 门板卷进门头,中控能开关)。车库和主楼之间那扇门(常通设备间 / 玄关 / 洗衣房)照普通门写。车库地面mat: "epoxy"(环氧地坪)。按图画的车位放car/suv(车头朝里:rot 让车头朝车库内墙),壁挂充电桩evCharger挂在车头正对的墙上;储物架、工具台靠侧墙。 - 庭院:主楼外围一圈细墙线(围墙 / 栏杆)圈起来的地方就是院子,标“庭院 / 花园 / 前院 / 后院 / 天井”。围墙照墙画、厚 150
240,用1800,low(...)那种写法开整段hole(sill= 围墙高 1200head= 层高):设计器只砌到围墙高;车道口、人行门写成sill: 0的通高hole(两边门垛自动只砌到围墙高),再在洞口上放gate(庭院大门,宽度按洞口)。院子地面mat: "paving"(石材)/grass(草坪),草坪块、汀步、泳池、凉亭、车棚、乔木、灌木、草坪灯、烧烤台、叠水景墙、秋千、躺椅、遮阳伞都在“别墅 · 庭院”里。院子必须被围墙 + 主楼外墙完整围住,否则识别成室外(没地面、不布水电)。 - 车棚:院子里画一个虚线矩形 + 对角线 / 四角立柱、标“车棚 / 车位”的 → 家具
carport(单车位)/carport2(双车位),底下再放一辆车;车棚、凉亭、草坪、汀步、乔木不参与家具重叠和通道检查(本来就压在别的东西上面)。 - 泳池:画蓝色或水波线的矩形、标“泳池 / 水池” →
pool(w × d 照图,长边方向用 rot)。 - 挑空:上层图在客厅上方画斜线 / 交叉线、标“挑空 / 上空 / 客厅上空”→ 上层
voids写这块矩形(name: "客厅挑空"),四周用栏板(hole+sill: 1100)或实墙围住;挑空里可以放chandelier(水晶大吊灯,挂在上层顶面垂进客厅)。 - 电梯:楼梯旁一个约 1.5 × 1.5m 的小井道、标“电梯” → 每层同一位置放
elevator,上层井道voids带"shaft": true。 - 常见房间:玄关、客厅、餐厅、中西厨、老人房(一层带卫生间)、主卧套房(主卧 + 衣帽间 + 主卫)、家庭厅 / 起居厅、书房、茶室、影音室、台球室 / 棋牌室、健身房、酒窖、桑拿、保姆房、设备间 / 锅炉房(壁挂炉
boiler、地暖分集水器manifold、中央软水机waterSoftener、洗衣烘干)、储藏间、露台。对应家具都在“别墅 · 休闲 · 设备”和原有分组里。 - 水电(
autoElec自动):车库按面积排吸顶 LED 灯、进屋门和车库门两处双控、每个车位一个充电桩电源(socketEV,32A 独立回路,壁挂充电桩自带的算一个)、卷帘门电机插座(门头上方)、墙上插座 1.3m 高;庭院沿围墙 / 外墙装壁灯、留防水插座和取水点(浇花、洗车),车棚旁留充电桩电源;露台壁灯 + 防水插座;二三层没有入户门时,每层的强电箱、弱电箱放在楼梯口旁的公共空间(分层配电);设备间不预留空调、网口。 - 完整示例:
reference/example-gen-villa.py(三层独栋:双车位车库 + 车库顶露台、庭院围墙 + 车道口、车棚、泳池、凉亭、挑空客厅 + 水晶吊灯、U 形楼梯 + 电梯、主卧套房、茶室、影音室、台球室、桑拿)。
4. 生成并自查(循环直到通过)
bashpython scripts/build.py <工作目录>/spec.json -o <工作目录>/check.html node scripts/check.mjs <工作目录>/check.html <工作目录>/check
report.json 逐项看:
freeWallEnds应为空(悬空的墙端 = 墙没接上,房间会漏成一片)。确实有半截墙(比如玄关隔断)时可以保留。rooms:每个房间都识别出来了,名字对,面积和原图标注接近(这里是净面积,一般比原图标的小一点)。出现“(未命名)”说明漏了标签,或者有墙没连上。wallsDropped必须为空:spec 里写了、方案里却没有的墙(两面平行墙只差十几毫米时会被吸附吞掉——让它们落在同一条线上)。labelsOutsideRooms、openingsDropped应为空或 0(门窗放不下会被丢掉:洞口比墙段还长)。- 尺寸逐个对数:
exteriorDims是设计器按墙算出来的外围三道尺寸(openings门窗分段、axis墙厚 + 净距、total总长),rooms[].size_mm是矩形房间的开间 × 进深。和原图标注逐个比,差 30mm 以上就回去改对应的墙(多半是刻度读错、墙厚取错、或该用墙面却用了中线)。 房间面积现在是按墙面精确算的净面积,和原图标的面积通常差 1% 以内(原图有的按轴线算,会偏大一点)。 errors必须为空。
一定要看截图:overlay.png 是生成的户型叠在原图上(半透明),墙、门窗位置应当和原图重合。plan2d.png 看门的开向,view3d.png 看整体。
发现偏差就改生成脚本,重新 build 和 check。一般 2–3 轮就能对上。
5. 布置家具、家电和水电(默认要做)
原图画了家具 / 布置的,必须一比一还原:每件家具的类型、位置、尺寸、朝向照图放(床多宽、桌子是椭圆还是方的、沙发几段怎么拼、柜子分几段、卫生间玻璃隔断和门在哪),图上画了的不能漏、不能用近似件凑;家具库没有的类型,补进 designer 项目(src/catalog.ts + 2D src/draw/symbols2d.ts + 3D src/view3d/furniture3d.ts,npm run skill:sync)和格式说明。
同时要能用:门扇打得开、每个房间和门都有 ≥ 700 的通道,人能走进去。图上画法真的走不通的(比如岛台和橱柜之间只留了几十毫米),按能走通的最小改动处理,并在交付时逐条告诉用户改了哪里、为什么。
读图要点(容易认错的画法,拿不准就放大 4 倍逐个看,必要时查资料):
- 厨房里被橱柜三面围住的大矩形多半是地面(U 形厨房的站人区),不是岛台——岛台四周一定留着 ≥ 800 的通道;
- 洞口边线上一段加粗的短线 / 两头短竖线 = 推拉门轨道(玻璃推拉门、隐形门),要建墙 +
sliding/pocket,不是空着; - 卫生间里一条窄框 + 三个小圆 + 带斜线的小方框 = 嵌入式淋浴(
showerSet),旁边的小圆是花洒、带十字的圆是地漏; - 靠墙一条细长圆角框、前面没有电视屏幕符号(梯形 / 加粗短线)的,是矮柜 / 边柜,不要当电视柜;沙发背对的那面墙不放电视;
- 单人椅、沙发:加粗的双线 / 厚的那一边是靠背,椅子面朝茶几、餐桌;
- 门扇:门轴在弧线圆心,开向看弧线在哪一侧;门开在哪面墙看门框短线,套房卫生间的门常开在卧室里而不是走廊;
- 沙发是弯的 / 折角 / 蛇形的用
bendSofa按中线描,不要拿直沙发或curvedSofa近似。 - 墙边的细带也要认:贴墙一条带等距刻度 = 木格栅护墙板,床头那面墙的双线 = 床头背景板 / 软包,都用
wallPanel;墙位置上一条分格细长条 = 玻璃隔断glassWall;小灰圆 = 地漏(structs的drain,按图上的位置放);墙垛、柜子端头的小圆角柜这些零碎也照画。 - 斜墙(非 90° 的转角)设计器会按墙面求交斜接,不用手工凑;画完放大看一眼转角。
- 一个地方一个地方地对:furnoverlay 把墙(绿 = 承重、青 = 非承重,按厚度画)、门窗洞口(黄)、家具外框(红 / 蓝)都画回原图,按房间切块逐张看,原图上每一笔都要能在叠图里找到对应的东西。 核对方法:
bashpython scripts/furnoverlay.py <工作目录>/spec.json --out <工作目录>/furncheck # 家具外框画回原图(红 = 落地,蓝 = 挂墙 / 吸顶,箭头 = 正面)
逐块看 furn_r*c*.png:每个框要正好压在原图那件家具的轮廓上,原图上有而没有框的就是漏了。
除非用户只要结构,否则按房间功能放一套完整的家具和家电,让打开就像真的住进去(家具 key 和朝向规则见格式说明):
- 卧室:床、床头柜、衣柜、挂机空调、窗帘;儿童房可换上下床 / 婴儿床,书房放书桌椅、书架
- 客餐厅:沙发(或 L 形沙发)、茶几、电视柜或壁挂电视、地毯、餐桌椅、餐边柜、柜机或挂机空调、落地窗帘;入户放鞋柜(可加换鞋凳)、饮水机
- 厨房:整体橱柜(含水槽灶台烟机)、冰箱;空间够加洗碗机、电器高柜;燃气热水器常挂在厨房或阳台
- 卫生间:马桶、浴室柜、淋浴房或一字隔断、电热水器(挂在马桶/淋浴上方,或用燃气热水器)、浴霸、毛巾架;放
sewer下水口和drain地漏 - 阳台:洗衣机(+ 烘干机)、晾衣架、洗衣池柜,放地漏
- 别墅:车库(车、充电桩、储物架、工具台)、庭院(草坪、汀步、泳池、凉亭、车棚、乔木、灌木、草坪灯、烧烤台、景墙、秋千、躺椅、遮阳伞、大门)、休闲(台球桌、麻将桌、茶台、影院沙发、三角钢琴、吧台、桑拿房、挑空大吊灯)、设备(壁挂炉、分集水器、软水机),见上面“独栋别墅”
- 按现实装修的常理摆(设计器是正式装修前的预看,每样东西都要真能这么装):
- 挂机空调、热水器、壁挂炉、吊柜、电视、镜柜、装饰画只能挂在实墙上,不能挂在窗洞 / 窗帘盒 / 门洞的位置,也不能和窗帘叠在一起;空调优先放靠外墙(好打孔走管)的侧墙,房间里没有合适实墙时放在床头墙上方,别对着门
- 橱柜窗前那段不做吊柜(
kit.wallLegs只写其他段);燃气热水器挂在厨房靠外墙的窗边实墙上 - 每个卫生间都要有热水来源:离厨房燃气热水器近的共用,远的各放一台电热水器(挂在马桶上方或淋浴区旁边的实墙上,不要挂在浴室柜镜子上方)
- check 会报「挂墙位置」「橱柜吊柜挡住窗户」,必须清零
- 窗帘:住人的房间(卧室、客餐厅、书房、茶室……)每扇窗都放落地窗帘,宽度必须盖住整个窗洞,两侧各多出 100~200;转角窗两面墙各放一幅;落地窗 / 窗紧挨着阳台门的,一幅连门一起盖住。check 会报「窗帘没盖住窗户」
- 软装点缀:绿植、地毯、装饰画、落地灯;家具库 160 多件,按户型面积和风格挑(U 形 / 弧形沙发、圆餐桌、卡座、酒柜、L 形橱柜、双盆浴室柜、独立浴缸、猫爬架等都有,见格式说明)
- 地面铺装:默认用
mat预设;用户说了具体的砖(“客厅 800×800 灰色大板”“卫生间 300×300 斜铺”“主卧人字拼木地板”),写进房间的tile(格式见格式说明“自定义铺砖”),设计器会按砖的尺寸、缝宽和铺法一块块铺,报价按排出来的片数算 - 用户点名想要的效果风格(沙盘效果图、彩平图、奶油风、原木、莫兰迪……)写进 spec 的
look - spec 里写
"autoElec": true:设计器按房间、门窗和这些家具自动生成全套水电点位并关联开关(餐桌 / 岛台上方吊灯、各功能区灯、全屋覆盖补筒灯不留暗区、插座按墙长补齐,规则见格式说明),一般不用手写 elec - 管线自动排布(不用写):水电图层上自动排出强电插座回路、照明回路、网线、冷热水、排水的走线,并给出回路表(断路器 / 线径 / 长度)和材料清单,规则与规范依据见
reference/mep-rules.md。所以强电箱、弱电箱、管井、下水口、热水器要放对:缺强电箱排不出电路,缺弱电箱排不出网线,没热水器没有热水管,排水接管井 / 下水口。autoElec 会自动放进水总阀(waterIn),知道实际进户位置就手写。别墅 / 复式:总箱在有入户门那层,其他层是分层配电箱(自动算干线);进水总阀只放在入户层,其他层由给水 / 热水立管接上来(立管放管井,没有就放在上下层对齐的用水点),所以上下层的卫生间、厨房、管井要按图对齐放准;壁挂炉也算热源;挑空、楼梯井上不走地面管线;有庭院 / 车库的首层排水直接出户。全楼材料清单在水电面板导出
放完再跑一次 check,处理 issues 里的 [warn](家具重叠、嵌进墙里、挡住门扇、马桶离下水口太远、点位被柜子挡住等)。
[error] 和 [warn] 必须清零才能交付:用户打开设计器,右侧“检查”页会把它们标成红色 / 橙色,看起来就是“有错误”。不要觉得问题小就留着。
确实改不掉的(原图就是这么画的),交付时逐条说明原因。
[info] 级别的提示可以留着(比如家电附近缺少插座,多半是家电没靠墙)。
6. 交付
bashpython scripts/build.py <工作目录>/spec.json -o <图片同目录>/<户型名>-装修设计器.html
交付前必须跑全功能验收,全部通过才能交付(真显卡、无头浏览器把每个按钮、工具、图层、面板、弹窗、导出都实际操作一遍,约 15~20 分钟):
bashnode scripts/qa.mjs <户型名>-装修设计器.html <工作目录>/qa # 加 --quick 跳过 PDF 和光线追踪(约 8 分钟) python scripts/qa_files.py <工作目录>/qa/downloads # 导出的 PNG / DXF / PDF / CSV / JSON / 分享文件能不能正常打开
qa/report.md每一项 ✅ / ❌ + 原因 + 截图;有 ❌ 先分清是脚本点错位置还是设计器真有问题,真问题修设计器(npm run skill:sync后重新 build),不能带着 ❌ 交付。- 判定规则:任何页面报错 = 失败;界面上出现 NaN / undefined / Infinity = 失败;操作后状态没变(加了家具数量没变、撤销没恢复)= 失败;导出没下载到文件 = 失败;3D 画面空白 = 失败;夜里中控文字对比度不够 = 失败;1280×720 ~ 1920×1080 下按钮超出 / 重叠 = 失败;手机尺寸打开平面超出屏幕 = 失败。
- 自动检查之外一定要人工过一遍
qa/shots/(每一步一张图):重点看文字有没有被截断 / 压住、弹窗内容、3D 风格和配色、中控卡片、手机尺寸。 - 改过家具库或 3D 家具建模时,再跑
node scripts/qa_furniture.mjs <html> <工作目录>/furn:全部家具排成网格分块拍 3D,对照list.txt逐件看模型对不对(注意广角边缘有透视变形,存疑的拉近单独拍)。
交付时告诉用户:
- HTML 的路径,附上
plan2d.png和view3d.png - 识别结果和原图哪里可能不一致、比例是不是估算的;CAD 图纸要说明承重墙是按“涂黑 + 厚度”判断的(原始结构图常把所有原墙都涂黑),以物业结构图为准;原图是门洞、方案里装了门的要说明
- 打开后的用法:左侧“户型”面板可以勾选“显示底图”对照原图;“载入初始户型”随时恢复到这个户型;其余功能(3D 漫游、家具、水电、吊顶、拆改、报价、导出 PDF/分享文件)和原设计器一样;「导出图纸 → CAD 平面布置图(DXF · 国标 / 装修)/ CAD 水电点位图(DXF)」导出 DXF R2007(文字 UTF-8,中文不乱码;模型空间 1:1 mm),按 GB/T 50001 / 50104 出图习惯:分图层并设线宽(墙柱粗线 0.5、门窗 0.25、家具尺寸 0.13)、承重墙涂黑、新砌墙 45° 斜线图例、拆除墙虚线、门开启线细实线、定位轴线 + 轴号(1、2、3… / A、B、C…,不用 I O Z)、尺寸两种导出:国标三道(门窗洞口 / 轴线 / 总尺寸)或装修四道(多一道墙厚 + 净距),间距 10mm、第一道离外轮廓 10mm,短段数字错开 + 引出线;每段尺寸都是真正的 DIMENSION 标注对象(标注样式 GB:45° 建筑斜线、字高 3、DIMSCALE = 比例,数字由 CAD 按定义点量,和图形一致);门窗编号(M / TLM / DK / C / GC / TC + 宽高分米,撞号加 a、b)+ 门窗表(毫米实数、数量、窗台高、入户门)、指北针、图名 + 比例、A3/A2 图框 + 标题栏,比例自动选(优先 1:100),可再用 cadvec.py 读回来;选中橱柜、定制柜、浴室柜、淋浴房、中岛,属性面板有「定制」区:橱柜可改一字 / L / U 形、进深、台面高厚和颜色、水槽灶台在哪一段和位置宽度、吊柜、烟机、高柜,定制柜可改每列宽和类型、L 形转角、顶柜、踢脚、拉手、顶天立地,2D / 3D / 立面 / 报价 / 水电同步(冰箱洗衣机等成品家电保持标准尺寸);3D 里点墙上的开关能开关对应的灯(夜间模式最明显),门、窗帘、电视、台灯也能点;水电面板有“一键布置水电”和「管线排布」(全部 / 电路 / 网线 / 给排水切换,插座回路走地或走顶、给水走顶或走地,回路表、材料清单 CSV,点中一段管线拖动改走向、恢复自动走线,选中插座改所属回路;DXF 水电图按系统分图层带管线);3D「⚡ 管线」把管线按真实高度放进模型、墙半透明;写实风格漫游:GTAO 接触阴影、窗户面光源(窗帘拉上会暗)、家具小倒角、布艺 / 木纹 / 乳胶漆微纹理、窗外城市远景(夜里亮窗)、室内摄影调色,顶面不再漏光;「风格」里有画质档位(流畅 / 均衡 / 高画质,默认按显卡自动选;均衡以上有布艺丝绒光泽、烤漆清漆、木纹布纹凹凸、房间反弹光、抗锯齿);软装是鼓包的填充感垫子、带包边,被子有褶皱;☀ 面板「出图方式」可选光线追踪(three-gpu-pathtracer,首次编译核显约 1
2 分钟,有进度条、可提前结束保存,最后降噪);3D 视图「🎨 风格」可切换写实 / 沙盘效果图(微缩模型感)/ 彩平图(正上方俯视的彩色平面图,标准门窗符号、房间名和面积、指北针、比例尺;尺寸可选不标 / 简洁 / 完整,彩平图模式下「渲染出图」直接出完整彩平图)和 12 套配色,鸟瞰、漫游、出图都保持;「导出图纸 → 彩平图(高清 PNG)」直接出一张长边 4000 像素的彩平图;房间面板里可以「自定义铺砖」(砖的长宽、灰缝、8 种铺法、纹理、砖色缝色、起铺点),2D / 3D / 报价同步,报价按整砖 + 切砖片数算;右侧总览里可以开关三类尺寸标注(外墙门窗分段、每段墙净长、房间开间 × 进深),改墙后实时重算,导出图纸也带上;☀ 面板按城市、月份、时间模拟真实日照,夜里出月亮星空,有“日照演示”;🔒 锁定后 3D 里不能拖动家具(防误触);3D 里的动效都是平滑的:门、窗(平开窗往里转开、推拉窗滑开,3D 里点窗扇就能开关)、窗帘(一褶一褶收拢展开,可停在任意开合度)、开关灯淡入淡出、电视屏亮起、楼层切换时楼层滑开 / 滑回;3D 视图「🏠 智能中控」(参考 HAbridge 智能家居仪表盘的演示,单层套房和多层都能用):把方案当成装好智能家居的房子来用——墙面变成白色半透明、能看到房间里面,开着的灯在地面打出暖色光池、灯具周围有光晕(色温、亮度一调就看得出);左侧问候、日期农历、当地日出日落、灯光 / 空调 / 窗帘开着几台和室内 PM2.5 / 温度 / 湿度曲线(模拟数据),右侧楼层(多层时 ALL = 各层拉开的爆炸视图、缓慢自转),底部灯光 / 环境 / 设备 / 扫地机 / 安防五类,3D 里设备上浮出图标,点图标镜头推到房间、弹出控制卡片:灯(色温 27006500K、亮度、柔和 / 日常 / 明亮)、窗帘(开合百分比、关闭 / 暂停 / 打开)、空调(温度、模式、风速、摆风,3D 里出风气流:制冷蓝、制热橙)、电视等设备开关、扫地机器人(每层一台、在自己那层跑;100mm 栅格避障规划:墙、落地家具、楼梯、楼梯井绕开不穿模,桌椅底下能钻,关着的门进不去,路被挡住会重新找路或跳过;能到达的地面弓字形覆盖,地上留青色清扫轨迹;电量低自动走最短路回基站充电、充满接着扫;卡片显示进度、已扫面积、电量、跳过的区域;方案里没放基站时在客厅连通面积最大的那片地面靠北墙处放一个虚拟基站)、门窗(开关、入户门上锁、一键关闭全部门窗);切换楼层时别的层边滑开边淡出;挑高(层高 > 3.1m)的一层在 2.8m 处剖开,也看得清每个房间;状态只在本次打开有效、不存进方案。想让中控演示更完整,布置家具时记得放上空调、窗帘、电视、灯具(autoElec 会布灯) - 同一个 HTML 第二次打开会恢复用户上次的修改。重新生成的 HTML 会自动用新户型
更新模板
设计器项目改了功能后,在项目目录执行 npm run skill:sync(打包后把产物复制到本 skill 的 assets/template.html)。
项目侧负责读取预设户型的代码在 src/model/spec.ts(planFromSpec、window.__PRESET__)和 src/app.ts(initApp / samplePlan)。


