建筑插件
建筑插件
建筑插件(architecture-plugin)为 WebCAD 提供完整的建筑平面图绘制工具集,包括轴网、柱子、墙体、门窗、楼梯、阳台、房间标注、建筑符号等,符合 GB/T 50001-2017 和 GB/T 50104-2010 国家标准。
功能概览
| 分类 | 命令 | 说明 |
|---|---|---|
| 轴网 | BINDRAWAXISGRID | 绘制直线轴网(输入横纵间距) |
| 轴网 | BINLABELAXIS | 轴号标注(自动编号 1,2,3... / A,B,C...) |
| 柱子 | BINDRAWCOLUMN | 绘制柱子(矩形/圆形,支持沿轴网批量布置) |
| 柱子 | BINCOLUMNTOAXIS | 柱齐轴网(在所有轴线交点放置柱子) |
| 墙体 | BINDRAWWALL | 绘制墙体(连续绘制,支持撤销/闭合) |
| 墙体 | BINWALLFROMAXIS | 轴线生墙(从轴网自动生成墙体) |
| 墙体 | BINEDITWALL | 编辑墙体 |
| 门 | BININSERTDOOR | 插入门(单开/双开/推拉,自动关联墙体) |
| 窗 | BININSERTWINDOW | 插入窗(标准窗/飘窗,自动关联墙体) |
| 楼梯 | BINDRAWSTAIR | 绘制楼梯(直跑/双跑) |
| 阳台 | BINDRAWBALCONY | 绘制阳台 |
| 房间 | BINSEARCHROOM | 房间搜索(点击放置房间标注) |
| 房间 | BINLABELROOM | 房间标注(名称+面积) |
| 符号 | BINELEVMARK | 标高符号 |
| 符号 | BINSECTIONMARK | 剖切符号 |
| 符号 | BINNORTHARROW | 指北针 |
| 符号 | BININDEXMARK | 索引符号 |
| 标注 | BINARCHDIM | 建筑尺寸标注 |
| 标注 | BINAXISDIM | 轴网标注 |
| 工具 | BINARCHTOOLBAR | 打开建筑浮动工具栏 |
| 工具 | BINDEMOFLOORPLAN | 一键生成示例户型图 |
安装与加载
方式一:loadByName(推荐)
const pm = PluginManager.getInstance();
// 按包名加载,已加载则跳过,正在加载则等待
await pm.loadByName('vcad-plugin-architecture');loadByName 会自动从 ./plugins/vcad-plugin-architecture.js 加载,也可自定义路径:
await pm.loadByName('vcad-plugin-architecture', {
baseUrl: '/static/plugins/'
});方式二:loadFromUrl
const pm = PluginManager.getInstance();
await pm.loadFromUrl('./plugins/vcad-plugin-architecture.js');方式三:MainView 配置
const cadView = new MainView({
plugins: [
{ url: './plugins/vcad-plugin-architecture.js' }
]
});SDK API 创建建筑实体
插件加载后,可通过 window.vjcadArchEntities 获取建筑实体类,用 SDK API 直接创建建筑实体。
获取实体类
await pm.loadByName('vcad-plugin-architecture');
const {
AxisGridEnt, ColumnEnt, WallEnt,
DoorEnt, WindowEnt, StairEnt, BalconyEnt,
ElevationMarkEnt, SectionMarkEnt,
} = window.vjcadArchEntities;创建轴网
// 横向间距 3600, 3900, 5700mm,纵向间距 4200, 2100, 4200mm
const grid = AxisGridEnt.create(
new Point2D(0, 0), // 原点
[3600, 3900, 5700], // 横向间距数组
[4200, 2100, 4200] // 纵向间距数组
);
Engine.addEntities([grid]);创建墙体
// 从 (0,0) 到 (13200,0),墙厚 240mm,中线对齐
const wall = WallEnt.create(
new Point2D(0, 0), // 起点
new Point2D(13200, 0), // 终点
240 // 墙厚(mm)
);
Engine.addEntities([wall]);创建门窗并关联墙体
门窗与墙体的关联是建筑插件的核心功能。关联后:
- 墙体在门窗位置自动开洞
- 门窗只能沿墙体中线移动
- 墙体移动时门窗跟随
- 门窗删除时墙体自动恢复
// 创建门
const door = DoorEnt.create(
new Point2D(5000, 0), // 位置(在墙体上)
'single', // 类型:single/double/sliding
900 // 门宽(mm)
);
door.rotation = 0; // 与墙体方向一致
door.wallThickness = 240; // 墙厚
// 创建窗
const window = WindowEnt.create(
new Point2D(2000, 0),
'standard', // 类型:standard/bay
1500 // 窗宽(mm)
);
window.rotation = 0;
window.wallThickness = 240;
// 先添加到画布(获得实体 ID)
Engine.addEntities([wall, door, window]);
// 建立双向关联
door.wallHandle = String(wall.id);
wall.doorWindowHandles = [String(door.id), String(window.id)];
window.wallHandle = String(wall.id);
// 触发墙体重绘(显示开洞效果)
wall.setModified();完整示例
在线示例
建筑插件 SDK API 示例{target="_blank"}
技术设计
自定义实体体系
建筑插件的所有建筑构件都继承自 CustomEntityBase,通过 buildNestEnts() 方法将复杂的建筑图形分解为基础图元(LineEnt、ArcEnt、PolylineEnt 等):
CustomEntityBase
├── AxisGridEnt → LineEnt[] + CircleEnt + TextEnt (轴线+轴号)
├── ColumnEnt → PolylineEnt + HatchEnt (矩形/圆形填充)
├── WallEnt → LineEnt[] (双线墙,支持开洞)
├── DoorEnt → LineEnt + ArcEnt (门扇+弧线)
├── WindowEnt → PolylineEnt + LineEnt[] (矩形框+玻璃线)
├── StairEnt → LineEnt[] + PolylineEnt (踏步+轮廓+箭头)
├── BalconyEnt → PolylineEnt + LineEnt (U形轮廓+栏杆)
├── ElevationMarkEnt → LineEnt[] + TextEnt (三角形+标高值)
└── SectionMarkEnt → LineEnt[] + TextEnt (剖切线+箭头+编号)每个实体都实现了:
getSnapPoints()— 对象捕捉点getGripPoints()— 夹点编辑点gripEdit()— 夹点移动处理getPropertyInfo()— 属性面板显示clone()/move()/rotate()/scale()/mirror()— 几何变换getEntityData()/setEntityData()/fromDb()— 序列化
墙体与门窗的关联关系
这是建筑插件最核心的技术设计。
数据模型
WallEnt:
doorWindowHandles: string[] ← 关联的门窗实体 ID 列表
DoorEnt / WindowEnt:
wallHandle: string ← 关联的墙体实体 ID通过实体 ID(entity.id)建立双向引用。
关联反应流程
墙体自动开洞算法
WallEnt.buildNestEnts() 中的核心逻辑:
- 遍历
doorWindowHandles,在Engine.currentSpace.items中查找每个门窗实体 - 将门窗位置投影到墙体中线,计算沿墙方向的参数
t(0~1) - 根据门窗宽度计算开洞范围
[tStart, tEnd] - 合并所有开洞范围和墙体交叉缺口
- 将墙线分段绘制,跳过开洞段
// 简化的开洞算法
const openings = collectOpeningRanges(); // [{tStart, tEnd}, ...]
openings.sort((a, b) => a.tStart - b.tStart);
const segments = [];
let cursor = 0;
for (const op of openings) {
if (op.tStart > cursor) segments.push({ tStart: cursor, tEnd: op.tStart });
cursor = Math.max(cursor, op.tEnd);
}
if (cursor < 1) segments.push({ tStart: cursor, tEnd: 1 });
// 只在 segments 范围内绘制墙线
for (const seg of segments) {
drawWallLine(seg.tStart, seg.tEnd);
}门窗沿墙约束移动
DoorEnt.gripEdit() 和 WindowEnt.gripEdit() 中:
// 中心夹点移动时,约束在墙体中线上
if (this._wallHandle) {
const wall = findWallById(this._wallHandle);
if (wall) {
const proj = projectPointOnSegment(newPos, wall.startPoint, wall.endPoint);
const dToStart = dist(proj, wall.startPoint);
const dToEnd = dist(proj, wall.endPoint);
// 确保门窗不超出墙体范围
if (dToStart >= halfWidth && dToEnd >= halfWidth) {
this._position = proj;
}
}
}事件监听机制(WallService)
WallService 在插件激活时启动事件监听:
| 事件 | 处理 |
|---|---|
EntityAdded | 新增墙体时处理拓扑;新增门窗时自动关联最近墙体 |
EntityModified | 门窗修改时重新关联;墙体修改时更新关联门窗位置 |
EntitiesErasing | 门窗删除前从墙体移除关联;墙体删除前清除门窗的 wallHandle |
实体生命周期与 isAlive 防护
引擎删除实体的流程为:EntitiesErasing → 设置 isAlive=false → setModified() → EntitiesErased。
其中第 3 步会触发 EntityModified 事件排入队列,resumeEvents 时 modifiedHandler 可能对已死实体执行 associateDoorWindow,把僵尸引用重新关联到墙体。因此,WallService 在以下关键路径中检查 isAlive:
// modifiedHandler:跳过已删除实体,防止僵尸重关联
if ((entity as any).isAlive === false) return;
// associateDoorWindow:不关联已删除的门窗
if ((entity as any).isAlive === false) return;
// collectOpeningRanges:不为已删除门窗生成洞口
if ((ent as any).isAlive === false) break;
// updateDoorWindowsOnWallChange:不更新已删除门窗
if ((ent as any).isAlive === false) continue;删除门窗后墙体自动补洞的完整事件链路:
1. 用户删除门窗 → 引擎触发 EntitiesErasing
2. erasingHandler → dissociateDoorWindow:
- 从 wall.doorWindowHandles 中移除门窗 ID
- wall.setModified() 标记墙体需重绘
3. 引擎设置 entity.isAlive = false
4. 引擎调 entity.setModified() → 排入 EntityModified 事件
5. modifiedHandler 检测到 isAlive===false → 跳过(不重新关联)
6. 墙体 buildNestEnts → collectOpeningRanges:
- doorWindowHandles 已清空(或残留 ID 对应的实体 isAlive=false 被跳过)
- 返回空的开洞范围 → 墙线完整绘制墙体拓扑处理
当多面墙体交叉时,WallService.processWallTopology() 自动处理交叉处的墙线清理:
| 交叉类型 | 处理方式 |
|---|---|
| L 型(两墙端点相交) | 移除端头封口线,墙线自然相交 |
| T 型(一墙端点接另一墙中部) | 连续墙在交叉处开缺口,终止墙移除端头 |
| 十字型(两墙中部相交) | 两面墙都在交叉处开缺口 |
图层自动管理
所有建筑实体自动创建并使用国标图层,颜色通过 ByLayer(color=256)跟随图层:
| 图层 | 颜色 | 用途 |
|---|---|---|
| A_AXIS | 红色(1) | 轴线 |
| A_COLU | 黄色(2) | 柱子 |
| A_WALL | 白色(7) | 墙体 |
| A_DOOR | 青色(4) | 门 |
| A_WIND | 青色(4) | 窗 |
| A_STRS | 绿色(3) | 楼梯 |
| A_ROOM | 品红(6) | 房间 |
| A_BALC | 绿色(3) | 阳台 |
| A_DIMS | 白色(7) | 标注 |
| A_SYMB | 红色(1) | 符号 |
插件结构
architecture-plugin/
├── src/
│ ├── index.ts # 插件入口(注册实体/命令/UI)
│ ├── icons.ts # SVG 图标
│ ├── constants.ts # 建筑规范常量
│ ├── utils/
│ │ ├── ArchLayerUtils.ts # 图层自动管理
│ │ └── ArchGeomUtils.ts # 建筑几何计算
│ ├── entities/
│ │ ├── AxisGridEnt.ts # 轴网
│ │ ├── WallEnt.ts # 墙体(含开洞逻辑)
│ │ ├── DoorEnt.ts # 门(含沿墙约束)
│ │ ├── WindowEnt.ts # 窗(含沿墙约束)
│ │ └── ... # 其他实体
│ ├── commands/ # 交互式绘制命令
│ │ ├── axis/ # 轴网命令
│ │ ├── wall/ # 墙体命令
│ │ ├── door/ # 门命令
│ │ ├── window/ # 窗命令
│ │ └── ...
│ └── services/
│ ├── WallService.ts # 墙体关联管理+拓扑处理+事件监听
│ └── AxisService.ts # 轴网查询服务Ribbon 与菜单
插件在 Ribbon 中创建 "建筑" 标签页,包含以下分组:
| 分组 | 显示模式 | 按钮 |
|---|---|---|
| 轴网柱子 | large | 绘制轴网、绘制柱子 + 更多(轴号标注、柱齐轴网) |
| 墙体 | large | 绘制墙体、轴线生墙 + 更多(编辑墙体) |
| 门窗 | large | 插入门、插入窗 |
| 楼梯其他 | compact | 绘制楼梯、阳台 |
| 房间 | compact | 房间搜索、房间标注 |
| 符号标注 | small-icons | 标高、剖切、指北针、索引、建筑标注、轴网标注 |
| 工具 | large | 示例户型图、建筑工具栏 |
同时创建 "建筑" 顶级菜单和 浮动工具栏(5x3 布局,涵盖最常用操作)。