综合实战 — 工程剖面图(钻孔数据自动出图)
文档:
vjcad/doc/comprehensive/index.md示例:vjcad/example/comprehensive/01section.js
这是 vjcad 在 真实业务(工程地质 / 勘察) 中最完整的端到端示例:根据钻孔数据,自动绘制带图框 / 标尺 / 图例 / 标题栏的剖面图。它把 图层 / 实体 / Hatch / Block / Insert / 文字 / 标尺 / 表格 等组合在一起,是评估 vjcad 能力上限的好样板。
1. 业务流程
flowchart TB
A[钻孔数据 mockBoreholeData] --> B[注册岩性 PAT 图案<br/>patternManager.registerPattern]
B --> C[计算图框尺寸 / 标尺范围]
C --> D[创建孔口标签 Block<br/>doc.blocks.add + addEntity]
D --> E[绘制柱状图<br/>PolylineEnt + HatchEnt + InsertEnt]
E --> F[绘制连线 / 距离标注]
F --> G[绘制图框 + 标题 + 图例]
G --> H[绘制标题栏表格]
H --> I[Engine.addEntities + zoomExtents]
2. 关键技术点 1:注册 PAT 图案
function parsePatternString(patString) {
const lines = patString.trim().split('\n');
const header = lines[0].match(/^\*([^,]+),(.*)$/);
const name = header[1].trim();
const desc = header[2].trim();
const patternLines = lines.slice(1).map(l => {
const p = l.split(',').map(parseFloat);
return {
angle: p[0], xOrigin: p[1], yOrigin: p[2],
deltaX: p[3], deltaY: p[4], dashes: p.slice(5)
};
});
return { name, description: desc, lines: patternLines };
}
for (const [name, patString] of Object.entries(ROCK_PATTERNS)) {
Engine.patternManager.registerPattern(parsePatternString(patString), 'rock');
}
要点:
- PAT 文本格式:
*Name,Desc行 + 若干"角度,xOrigin,yOrigin,dx,dy,dashes…"行 - 第二参数
'rock'是 分类(用于PatternPickerDialog分组) - 注册后
HatchEnt.patternName = 'XX'即可使用
3. 关键技术点 2:用 Block 做"重复模板"
function createBoreholeBlock() {
const doc = Engine.currentDoc;
let blockDef = doc.blocks.itemByName('BoreholeLabel');
if (blockDef) return blockDef;
blockDef = doc.blocks.add('BoreholeLabel');
blockDef.basePoint = [0, 0];
const attrName = new TextEnt([0, 5], '孔', 30, 0, TextAlignmentEnum.Center);
attrName.tag = 'NAME'; attrName.prompt = '钻孔编号'; attrName.setDefaults();
const sep = new LineEnt([-30, 0], [30, 0]); sep.setDefaults();
const attrElev = new TextEnt([0, -5], '0', 25, 0, TextAlignmentEnum.TopCenter);
attrElev.tag = 'ELEV'; attrElev.prompt = '孔口高程'; attrElev.setDefaults();
blockDef.addEntity(attrName);
blockDef.addEntity(sep);
blockDef.addEntity(attrElev);
return blockDef;
}
要点:
doc.blocks.itemByName / .add维护 BlockTable- 块里包含 属性文字(带
tag),后续InsertEnt时用addAttribute填值 BlockEntities的setDefaults()不可省
const label = new InsertEnt();
label.blockId = boreholeBlock.blockId;
label.insertionPoint = [x + 15, y + 50];
label.scaleFactor = 1;
label.rotation = 0;
label.setDefaults();
label.addAttribute({ tag: 'NAME', textString: bh.name });
label.addAttribute({ tag: 'ELEV', textString: bh.y.toString() });
entities.push(label);
4. 关键技术点 3:HatchEnt 的边界与图案
function createRectBoundary(x, y, w, h) {
const b = new PolylineEnt();
b.setPoints([[x, y], [x + w, y], [x + w, y + h], [x, y + h]]);
b.isClosed = true; b.setDefaults();
return b;
}
function createEdgesFromPolyline(polyline) {
const edges = new Edges();
const e = new Edge();
e.edgeType = EdgeType.Polyline;
e.bulgePoints = polyline.bulgePoints.clone();
edges.add(e);
return edges;
}
const hatch = new HatchEnt();
hatch.patternName = stratum.name;
hatch.patternScale = Math.max(1, stratum.thickness / 5);
hatch.setLoops(createEdgesFromPolyline(boundary));
hatch.setDefaults();
hatch.color = ROCK_COLORS[stratum.name] || 7;
entities.push(hatch);
要点:
- 边界要先建(PolylineEnt + setDefaults)才能 clone 它的 bulgePoints
HatchEnt.setLoops(edges)接受Edges(多边界用多个Edge)patternScale与patternName决定纹理疏密;过小会变成黑块Edges/Edge/EdgeType必须从 vjcad 导入
5. 关键技术点 4:标尺 / 图框 / 标题栏
// 左标尺(垂直)
const leftScale = new LineEnt([sx, sy], [sx, sy + drawHeight]);
leftScale.setDefaults(); leftScale.color = 7;
entities.push(leftScale);
for (let elev = scaleMinY; elev <= scaleMaxY; elev += interval) {
const drawY = sy + scaleMaxY - elev;
entities.push(makeLine([sx, drawY], [sx - tickLen, drawY]));
entities.push(makeText([sx - tickLen - 10, drawY], elev.toString(),
textHeight, TextAlignmentEnum.MidRight));
}
// 图框
const frame = new PolylineEnt();
frame.setPoints([[fxMin, fyMin], [fxMax, fyMin], [fxMax, fyMax], [fxMin, fyMax]]);
frame.isClosed = true; frame.setDefaults();
entities.push(frame);
// 标题栏单元格(双层 for 循环 + createRectBoundary + 文字)
for (let row = 0; row < titleRows.length; row++) {
for (const cell of titleRows[row]) {
const cellX = titleX + cell.col * tbCellW;
const w = tbCellW * (cell.colSpan ?? 1);
entities.push(createRectBoundary(cellX, rowY - tbCellH, w, tbCellH));
if (cell.text) {
const t = cell.text.includes('\n') ? new MTextEnt() : new TextEnt();
t.insertionPoint = [cellX + w / 2, rowY - tbCellH / 2];
t.text = cell.text;
t.height = cell.fontSize ?? 16;
if (t instanceof MTextEnt) t.textAttachment = 5;
else t.textAlignment = TextAlignmentEnum.MidCenter;
t.setDefaults();
entities.push(t);
}
}
}
要点:
- TextEnt vs MTextEnt 选择:含换行用 MText(
textAttachment = 5中心对齐) - 标题栏用
colSpan实现合并单元格 - 整张图最后一次性
Engine.addEntities(entities):收益是合并为一条撤销记录(一次 Ctrl+Z 撤掉整张图);EntityAdding/EntityAdded仍按实体逐个触发
6. 心智模型
flowchart TB
Data[钻孔 / 岩性数据] --> Setup[注册图案 / 创建块]
Setup --> Layout[计算图框 / 标尺]
Layout --> Draw[批量构造 entities[]]
Draw --> Final[Engine.addEntities + zoomExtents]
把"准备 → 布局 → 构造数组 → 一次性入库"分四步走,是 vjcad 批量出图的标准模板。
7. 适合改造的方向
| 改造方向 | 提示 |
|---|---|
| 数据来源换成后端 / Excel | 替换 mockBoreholeData(),保留布局逻辑 |
| 多剖面分页 | 把 drawSectionDiagram() 包进 for 循环,每页 entities 独立、再统一加 |
| 增加自定义岩性 | 新增 PAT 字符串 + ROCK_COLORS 映射 |
| 变成可交互编辑 | 把 entities.push(...) 后跟 setReactor / 注册命令对柱状图段拖动 |
| 出图打印 | 出图后调用命令 EXPORTPNG / EXPORTDWG(Engine.editor.executerWithOp;无 Engine.exportPng 函数,也没有 PDF 导出命令) |
红旗
- 不调用
setDefaults()的实体仍会正常加入,只是停留在构造默认值(0 层 /color=256ByLayer /lineType='ByLayer'),不吸当前图层与CECOLOR/CELTYPE HatchEnt回路由setLoops()自动闭合(Edge没有isClosed,按首尾相接处理),只需把边界点clone()进Edge;边界多段线的isClosed只影响轮廓线本身TextAlignmentEnum.MidCenter与MTextEnt.textAttachment = 5是两套独立枚举(TextAlignmentEnum/MTextAttachmentEnum),但编号按同一 3×3 小键盘布局(7=左上、5=正中,不是 DXF 的 1=左上)- 一次
addEntities([])巨大数组(上万实体)时会卡 UI;可以分批Engine.addEntities(batch)+await new Promise(r => setTimeout(r, 0)) - 图框 + 图例 + 标题栏的位置全部依赖最终图纸尺寸,必须先算
frameMinX/Max、drawHeight再绘 patternScale不当时 hatch 会变成"全黑"或"空白";调试时把patternScale调到 100 看下边界是否对就能定位
参考(官方文档 MCP)
- 文档:
vjcad/doc/comprehensive/index.md - 示例:
vjcad/example/comprehensive/01section.js