只有一张总平面图:11 栋楼的体量是怎么从 DWG 里挖出来的
先说结论:这张图纸里没有楼层平面。所有 11 栋楼的体量、158 个楼层的标高,全部来自这一张规划总平面图上的图层语义、文字标注和多段线几何。
这篇文章讲三件事:工具链怎么选错的、数据是怎么挖出来的、以及三个让我返工的细节。数字都来自仓库里的实测脚本。
一、起点:一个 2.9 MB 的 DWG
项目起点很朴素:一个目录,里面只有一个文件——某产业园的《规划总平面图》,2.9 MB,文件头 AC1018(AutoCAD 2004 格式)。
本机没有任何 CAD 软件(AutoCAD、中望、浩辰、天正都没有),也没有 ODA File Converter。所以第一件事是找一条能读到实体级数据的路。
第一轮走的是最常见的那条:先把 DWG 转成 DXF,再用 ezdxf 读。为此装了 Aspose.CAD 的 Python 绑定。
这条路是死路,而且失败方式很骗人。 img.save(out.dxf, DxfOptions()) 确实产出了一个 212 MB 的 DXF,看起来"成功了"。一量就露馅:
| 检查项 | 实测结果 |
|---|---|
| DXF 版本 | AC1009(R12),encoding=cp1251 |
| 实体总数 | 82,425 个,全部是 POLYLINE |
| 文字 | 全部被矢量化成笔画轮廓(仅"文字说明"层就有 11,096 条) |
| 块 | 全部被炸开 |
| 闭合标志 | is_closed 全为 0 |
| 坐标 | 被整体重算,与图纸原始坐标不一致 |
拿到的是"这张图看起来像什么"的轮廓集合,不是"这张图里有什么"的语义数据。碎片面积只有 20~180 平方米,用 shapely 做 polygonize 只能拼出一地碎渣,楼栋轮廓根本还原不出来。
顺带记一个坑:cad.Image.load() 返回的是通用 aspose.cad.Image,不是 CadImage,拿不到 .entities / .layers。渲染 PNG 倒能用,但整幅带评估水印。
教训:格式转换和语义保真是两件事。评估版导出器为了"能出文件"会大量降级——TEXT 变轮廓、INSERT 炸开、闭合标志丢掉。这种输出肉眼看不出问题,用代码一量全是坑。
二、换成 WASM 版 LibreDWG
第二条路是直接解析原始 DWG,不经过转格式。用的是 @mlightcad/libredwg-web(0.8.1,GPL-3.0),GNU LibreDWG 的 WASM 编译版:
const libredwg = await LibreDwg.create('node_modules/@mlightcad/libredwg-web/wasm/');
const dwg = libredwg.dwg_read_data(new Uint8Array(fs.readFileSync(SRC)), Dwg_File_Type.DWG);
const db = libredwg.convert(dwg);
有个吓人但无害的现象:它打印 Open dwg file with error code: 68,但数据完整可读。要是因为这句报错就放弃,那就又走错一次。
实测规模:
| 项目 | 数量 |
|---|---|
| 模型空间实体 | 6,047 |
| LWPOLYLINE / INSERT / LINE / TEXT | 649 / 2,011 / 2,156 / 986 |
| 图层 | 116(名字含中文,解码正常) |
| 块定义 | 986 |
| 有效文字 | 1,025 条(layer / text / x / y / height / rotation) |
块要递归展开才能拿到真实几何:按 T·R·S 二维仿射逐层套用(块基点全是 (0,0),最大嵌套 8 层),展开后从 6,047 个模型空间实体变成 3,711 个带世界坐标的实体。
三、从实体到楼栋:三条线索
有了语义数据,接下来是把"一堆几何"变成"11 栋楼"。靠三条线索互相校验。
线索一:图层语义。 这张图的图层名做得很规范,用途写在名字里:
建筑层 → 已建建筑,合并后是一个 L 形连通块,3,077.5 平方米规划建筑层 → 新建楼栋,合并后 21 个连通块
线索二:文字标注。 1,025 条文字里藏着每栋楼的层数与高度,格式是 #-地上/地下层数-用途-高度,例如 -2+1F+18F、H=64.65M。文字带真实坐标,可以按坐标归属到楼栋轮廓上。
这里必须做一个判断:图面标注优先于指标表。 同一栋楼在右侧楼栋表和主图标注不一致时(我们遇到 18F 与 21F 的冲突),取主图标注——那是设计人直接在图上落的。
线索三:连通块合并。 shapely 把同层多边形按 0.6 m buffer 融合后取连通块,得到楼栋并集轮廓;再取最小外接矩形做旋转对齐,作为三维建模的底面。
四、三个让我返工的细节
1. 剖面图会混进主图。 图纸右下角有剖面块(块名就叫 poumian),展开后完全没被过滤掉,面积 2,283.8 / 1,502.0 / 312.5 的那三个"大楼"其实是剖面墙体。教训:展开块之后必须按地块 bbox 做一次空间过滤,不能只看图层。
2. "顶点乱序"是我误判的。 LWPOLYLINE 的闭合标志位大多是 0,一开始以为数据坏了。实测一个 4 点多段线:包围盒 16.8 × 56.7,shoelace 面积 952.6,两者自洽——它是个斜置矩形,包围盒面积本来就远大于实际面积。教训:先用能自洽的小样本验证"数据坏了"这个假设,再动手修数据。
3. 不能用"包含点取最大面积"归属楼栋。 L 形、U 形轮廓的凹口会让标注点落在多边形外,导致 1# 误配到 196.4 平方米的碎块、7# 误配到 113.8 平方米。最后改成按"包围盒左下角 + 面积"双重精确匹配。
五、结果与口径
| 指标 | 数值 |
|---|---|
| 楼栋 | 11 栋 |
| 楼层 | 158 层 |
| 最高 | 21 层塔楼 / 73.95 m |
| 最低 | 3 层研发楼 / 13.0 m |
| 主图地块 | 约 310 × 198 m |
一个必须写清楚的口径:这张图纸没有楼层平面。 所以下一篇里所有"每层长什么样"的东西,都是按总平面轮廓 + 层数生成的推演示意,不是施工图。
代码:gitee.com/aowind/company-builds(MIT)