# company-builds
**Repository Path**: aowind/company-builds
## Basic Information
- **Project Name**: company-builds
- **Description**: 依据 CAD 总平面图 DWG 的 three.js 园区三维重建:11 栋楼、158 层可逐层剖切、楼层定位点与设备互联走线(MIT)
- **Primary Language**: Unknown
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-10-21
- **Last Updated**: 2026-10-21
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 信息产业园 · 三维重建
依据 CAD 规划总平面图(DWG,AutoCAD 2004 / AC1018)重建园区 11 栋建筑的 three.js 网页三维模型,**并支持逐层剖切、楼层选择与可拖拽的楼层定位点**。

## 快速开始
```bash
npm install
npm run dev # http://127.0.0.1:5180
npm run build # 产物在 dist/
npm run preview
```
技术栈:three.js `0.186.1`(当前 npm 最新)+ Vite 8 + `@mlightcad/libredwg-web`(DWG 解析,仅用于离线出数据)。
> **仓库不含原始图纸,也不含任何可定位到具体地块的信息。** 原始 `.dwg` 属于设计成果,未随代码发布;`public/` 下是已经提取并做过坐标归一化的几何 JSON(原点在园区中心,单位为米),所以 clone 后直接 `npm install && npm run dev` 就能跑,不需要图纸。
> 如果你想用自己的图纸重跑提取流程,见文末「数据从哪来」。
## 两种模式
交互分工很明确:**3D 视图只负责「看」和「拖定位点」,楼栋/楼层一律在右侧面板选。**
### ① 总览
| 操作 | 效果 |
| --- | --- |
| 左键拖拽 / 滚轮 / 右键拖拽 | 旋转 / 缩放 / 平移 |
| 右侧 `⌂ 恢复全览视角` | 回到总览 |
| 楼栋标注 / 自动旋转 / 线框模式 | 面板开关 |
点 3D 里的楼体不会有任何反应——这是刻意设计,避免误触把视图带飞。
### ② 楼层聚焦
在面板里点楼栋 → 进入该栋的楼层视图;再点楼层网格(B2、B1、1F…21F)切换楼层:
- 只渲染当前层楼板 + **核心筒(楼梯 / 电梯 / 卫生间,三色分区)** + 走廊带
- 上下相邻层以半透明 ghost 保留
- **自动开启线框模式**:从当前层起画出整栋的层环、角部竖线与顶底轮廓,一眼看出"上面还剩多少层"
- 相机自动飞到该层斜上方,面包屑显示 `← 总览 | 5# · 10F | 标准层`

### ③ 楼层定位点
楼层视图里按 F(或面板底部按钮)在视图中心放置一个定位点;落在核心筒或楼板外时会自动吸附到楼板内,与同层已有点冲突时按 60° 步进自动错开。
面板里先选**设备类型**,放点时按所选形态生成 3D 模型:
| 设备 | 模型特征 |
| --- | --- |
| 架式服务器 | 1U 机箱、前面板 LED 与硬盘托架、机架耳、顶部散热槽 |
| 台式电脑 | 显示器 + 支架 + 右侧主机箱,屏幕发光 |
| 网络交换机 | 扁盒 + 8 个网口 + 8 颗状态灯 |
| 监控摄像头 | 底座 + 立杆 + 横臂 + 带遮阳罩的机身与镜头 |
- 默认名字是「设备类型 + 编号」(如 `电脑 1`、`交换机 2`),按同层同类顺序编号
- **列表每行右侧的 ✎ 按钮可重命名**,弹浮层输入,Enter 或「确定」提交、Esc 取消
- **在 3D 里点它即可选中**,出现红/绿/蓝三轴箭头,拖动任意轴移动
- 选中后底部有 **移动 / 旋转** 两个手柄切换(快捷键 G / R):
旋转手柄只保留竖直轴圆环,用来调整设备朝向(摄像头朝哪边、机柜正面朝哪边),下方实时显示当前角度
- 位置和朝向都自动写回 `localStorage`,面板列表同步
- 点空白处或按 Esc 取消选中



## 数据是怎么来的
总平面图是 DWG 二进制,且本机没有任何 CAD 软件,整条链路全部离线自建:
1. **DWG → 数据库**:`@mlightcad/libredwg-web`(WASM 版 LibreDWG)直接解析原始 DWG,保住 TEXT / MTEXT / INSERT / ATTRIB 等实体与原始图纸坐标。`scripts/probe.mjs`、`dump_texts.mjs`、`dump_blocks.mjs` 打印模型空间、图层、块定义。
2. **文字提取**:`scripts/filter_texts.py` 过滤出 1025 条有效文字(图层 / 内容 / 坐标 / 字高),`work/texts.json`。楼号、层数、高度、用途全部来自图面标注。
3. **几何展开**:`scripts/expand_geo.mjs` 把 `INSERT` 递归展开(2D 仿射 T·R·S,最大深度 8),得到块内实体的世界坐标几何 `work/geo_all.json`(3711 个实体)。
4. **图层语义**:
- `建筑` 层 = 已建 L 形大楼(3#/4#,含楼梯、核心筒);
- `规划建筑` 层 = 新建楼栋(1#/2#/5#/6#/7# + 研发中心 8#~11#);
- `建筑-建筑面积` 层 = 面积计算轮廓;
- `用地界线`、`道路`、`绿化`、`地下室`、`车库轮廓线` 等各自成层。
5. **轮廓归属**:`scripts/build_buildings.py` 用「bbox 左下角 + 面积」双重精确匹配把标注绑到轮廓上(L/U 形轮廓的凹口会让「包含点」判据失效,这是一个踩过的坑)。
6. **场景导出**:`scripts/export_scene.py` 用 shapely `unary_union(buffer(tol)).buffer(-tol)` 融合、简化 0.15 m,输出 `work/scene.json` → `public/scene.json`。
7. **楼层数据**:`scripts/build_floors.py` 按层高规则切片(首层 4.5 m、标准层 3.34 m、地下室 3.6 m、裙房 4.5 m),并用 shapely 求每层的核心筒落位与内部分区,输出 `work/floors.json` → `public/floors.json`(11 栋 / 158 层)。
坐标约定:图纸原始坐标系为 UTM(单位米),出数据时做了两步处理——① **先平移到园区中心为原点**(偏移量不写进仓库,需要复现时由 `SITE_ORIGIN` 环境变量传入,见 `scripts/localize_scripts.py`),② 再把图纸的 y(北向)映射到 three 的 `-Z`。所以 `public/*.json` 里 `[x, y]` 已是园区局部米制坐标,前端 `three.x = x`、`three.z = -y`。
## 楼栋参数
| 楼栋 | 用途 | 地上 | 地下 | 高度 | 首层轮廓 | 楼层数 |
| --- | --- | --- | --- | --- | --- | --- |
| 1# | 一类高层办公 | 18F | 2F | 64.65 m | 471.0 ㎡ | 21 |
| 2# | 一类高层办公(装配式) | 18F | 2F | 64.65 m | 415.2 ㎡ | 21 |
| 3# | 办公 | 13F + 3F 裙房 | 1F | 65.40 m | 952.6 ㎡ | 17 |
| 4# | 办公 | 13F + 3F 裙房 | 1F | 66.60 m | 952.6 ㎡ | 17 |
| 5# | 高层办公(装配式) | 21F | 2F | 73.95 m | 448.9 ㎡ | 25 |
| 6# | 高层办公(装配式) | 21F | 2F | 73.95 m | 448.8 ㎡ | 25 |
| 7# | 一类高层办公(装配式) | 21F + 1F 裙房 | 2F | 73.95 m | 406.7 ㎡ | 25 |
| 8# | 研发中心 | 3F | — | 13.00 m | 164.5 ㎡ | 3 |
| 9# | 研发中心 | 3F | — | 13.00 m | 164.5 ㎡ | 3 |
| 10# | 研发中心 | 3F | — | 13.00 m | 164.5 ㎡ | 3 |
| 11# | 研发中心 | 3F | — | 13.00 m | 164.5 ㎡ | 3 |
## 目录
```
index.html 页面骨架(HUD / 面板 / 面包屑 / 加载层)
src/main.js 场景装配、模式状态机、楼层网格、定位点 UI
src/floors.js 楼层几何:楼板、核心筒墙、分区色块、走廊、可站立判定
src/pins.js 定位点数据 + 四种设备建模(localStorage)
src/links.js 设备互联:折线路径生成、双层管线、流动光点
src/pathfind.js 楼板平面避障:可见性图 + Dijkstra、轮廓内缩
src/names.js 楼栋 / 楼层自定义命名(localStorage)
scripts/e2e.mjs 端到端交互自检(puppeteer)
scripts/e2e_devices.mjs 设备选择 / 重命名 / 持久化自检
scripts/e2e_rotate.mjs 旋转手柄 / 角度持久化自检
scripts/e2e_links.mjs 设备互联:建链 / 折点 / 楼层过滤 / 流速自检
scripts/diag_ghost.mjs 半透明楼栋与可见对象诊断
scripts/e2e_detour.mjs 绕核心筒 / 不出楼板的几何校验
scripts/diag_labels.mjs 定位点标签残留诊断
src/style.css 浅色玻璃 UI
public/scene.json 总图几何(buildings / podium / site / roads / green)
public/floors.json 逐层数据(158 层:ring、elev、floorH、core、corridorRing)
scripts/ 数据提取与处理(python + node)
docs/ README 配图
work/ 本机中间产物与截图(**不入库**,见 .gitignore)
```
## 重命名
设备、楼栋、楼层都能改成业务上的叫法。每行右侧都有一个 ✎ 按钮,点开是浮层输入框,`Enter` 或「确定」提交,`Esc` 或点遮罩取消。
| 位置 | ✎ 在哪 | 例子 |
| --- | --- | --- |
| 定位点列表 | 每行「删除 ×」左边 | `服务器 1` → `机房主服务器` |
| 楼栋列表 | 每行最右侧 | `一类高层办公` → `研发楼 A` |
| 楼层网格 | 每个楼层按钮内,层号右边 | `4F` → `会议室层` |


改名后同步刷新:3D 楼栋标签、面包屑、楼层标题、定位点列表、设备互联列表。名字存在 `localStorage` 的 `cb_names_v1`(`{ buildings: {id: 名字}, levels: {"5#|4": 名字} }`),**只覆盖显示名**,数据里仍用 `5#` / `4F` 这种稳定 id,所以改名字不会破坏定位点和链路的关联。输入框留空即恢复默认。
## 设备互联
在定位点列表里点选一个设备,面板下方会出现「设备互联」区块,勾选其它设备即可建立链路(一个设备可连多个)。两端都在场景里时,连线自动生成。
每条链路带**数据方向**:勾选后行尾会变成 `发 →` 或 `← 收`,点一下即可翻转。方向记在 `pin.links` 里(`{ peerId: 'out' | 'in' }`,两端互为反向),刷新后保留。



路径不是两点直连,而是按弱电走线逻辑折线布置(`src/links.js`):
走线全部**贴楼板、贴墙根,绕开核心筒**,不再两点直连:
| 情形 | 折点顺序 |
| --- | --- |
| 同栋同层 | 设备 → 落到楼面(+0.08 m)→ 绕核心筒贴墙走到目标下方 → 上升接目标 |
| 同栋跨层 | 楼面 → 绕核心筒到本层竖井口 → 竖直井道 → 目标层竖井口 → 绕到目标设备 |
| 跨楼栋 | 各自绕到核心筒 → 下到 1F → 贴墙根走到门厅 → 室外贴地(0.14 m)→ 目标楼门厅 → 1F 内走到核心筒 → 上到目标层 → 目标设备 |
平面避障用可见性图 + Dijkstra(`src/pathfind.js`):候选点取起终点、楼板轮廓顶点(沿内法线内缩 0.4 m)与核心筒角点,连线必须整段在楼板内且不进入核心筒,再求最短路。单次求解约 10 ms,只在建立/调整链路时算一次。校验脚本 `scripts/e2e_detour.mjs` 会沿曲线采样 200 点,确认贴楼板段 `outOfSlab = 0`、`inCore = 0`。
面板底部有「链接线」三态开关,默认**全显**:
| 模式 | 行为 |
| --- | --- |
| 全显 | 所有链路都画出来 |
| 关闭 | 全部隐藏,只看设备和楼栋 |
| 仅选中 | 只显示与当前选中设备相连的链路(没选设备时全部隐藏) |

管线是双层管(0.11 m 外管 + 0.055 m 亮芯),沿管线分布若干光点,`updateFlow()` 每帧按弧长从数据源推进到目标,形成流动效果;管上还等距嵌了朝向目标的小箭头,静止时也能看出方向。速度按 `11 m/s ÷ 管线长度` 归一,所以长链路不会跑得飞起。
总览时,有设备的楼栋自动变成半透明(墙 0.26 / 屋顶 0.18),这样不用切到楼层模式也能看见内部的设备和走线。
| 脚本 | 用途 |
| --- | --- |
| `scripts/e2e_links.mjs` | 建链路、校折点数、校楼层过滤、校光点位移 |
| `scripts/diag_ghost.mjs` | 半透明楼栋与可见对象诊断 |
## 已知限制
- **楼层平面为推演示意,不是施工图**:规划总平面图里没有任何楼层平面。每层楼板轮廓取自塔楼/裙房外轮廓,核心筒(楼梯 / 电梯 / 卫生间)位置与内部分区由算法推导,隔墙未建模。
- **地下室未建模**:`地下室` 层的实体全部是 INSERT,展开后轮廓未闭合或面积过小,暂未生成地下车库体量。楼层网格里的 B1/B2 仍可选中,用的是塔楼投影轮廓。
- 楼栋按标注层数做均质挤出,未做逐层退台、坡屋顶、机房层等细节。
- 周边城市肌理(约 240 个灰盒子)是程序生成的示意体量,不是真实建筑;树木同样为绿地内随机撒点。
- 3#/4# 在图纸 `建筑` 层标注为已建,此处按规划方案统一建成体量呈现。
## 数据从哪来
`public/` 下两个文件是离线提取的产物:
| 文件 | 内容 | 生成脚本 |
| --- | --- | --- |
| `scene.json` | 11 栋楼轮廓、裙房、用地红线、道路、绿地(园区局部米制) | `scripts/filter_texts.py` → `scripts/build_buildings.py` → `scripts/export_scene.py` |
| `floors.json` | 158 层楼板标高、层高、核心筒/走廊示意几何 | `scripts/build_floors.py` |
DWG 读取走 WASM 版 LibreDWG(`@mlightcad/libredwg-web`),脚本在 `scripts/` 里,都是 node ESM:
```bash
node scripts/probe.mjs # 摸清 DWG 结构
node scripts/dump_texts.mjs # 导出图面文字(楼号 / 层数 / 高度)
node scripts/expand_geo.mjs # 递归展开块,导出世界坐标几何
python scripts/build_buildings.py # 文字标注 → 楼栋轮廓归属
python scripts/export_scene.py # 生成 public/scene.json
python scripts/build_floors.py # 生成 public/floors.json
```
`scripts/e2e*.mjs` 是 puppeteer 端到端自检(需要本机 Chrome,默认路径已写在文件常量 `CHROME` 里,可自行改)。
### 关于坐标与版权
- 仓库里**没有任何原始图纸**(`*.dwg` / `*.dxf` 已被 `.gitignore` 排除),也没有图纸派生的中间数据(`work/`)。
- `public/*.json` 中的所有坐标已经**平移到园区中心为原点**,仓库内不出现任何绝对坐标;`scripts/` 里的常量同样做过平移。要复现提取流程,用 `SITE_ORIGIN=","` 环境变量把偏移量传给 `scripts/localize_scripts.py`。
- 图纸中的企业名称、经济技术指标、投资额等经营信息,以及竣工图还原的真实楼层几何,均未包含在本仓库中。
## 许可证
[MIT](LICENSE)