overview
本文回答:自动成图 = 把"任意一个绘图需求"按 拆解 → 图层 → 按层建实体 → 标注 → 撤销组 + 批量提交 五步走的工程范式。本页只讲通用思路——具体实体 API、几何工具、视图缩放等只链接不复述。
一、自动成图心智模型
flowchart LR
Req["需求<br/>(自然语言/规格)"] --> Decompose["拆解<br/>外形/孔/标注/文字/填充/装饰"]
Decompose --> LayerPlan["图层规划<br/>OUTLINE/HOLE/CENTER/DIM/HATCH/TEXT"]
LayerPlan --> Build["按层逐组建实体<br/>setCurrentLayer + setDefaults"]
Build --> Anno["标注/中心线/引线/文字"]
Anno --> Commit["addEntities + regen + zoomExtents<br/>包在一组撤销里"]
五步骨架(可直接套用):
async function autoDraw(spec) {
Engine.startUndoRecord('AUTO_DRAW'); // 整张图作为一组撤销
try {
ensureLayers(); // ① 图层
const entities = [];
entities.push(...buildOutline(spec)); // ② 外形
entities.push(...buildFeatures(spec)); // ③ 孔/特征/阵列
entities.push(...buildHatches(spec)); // ④ 填充
entities.push(...buildAnnotations(spec)); // ⑤ 标注/文字/引线
Engine.addEntities(entities); // 一次性提交
Engine.regen();
Engine.zoomExtents();
} finally {
Engine.endUndoRecord(); // 异常也保证关组
}
}
API 来源:undo/group、entity/create-entities、view/zoom。
二、需求拆解决策树(关键词 → 实体)
任意需求都先按下表把"自然语言"映射到"图形要素 / 实体类":
| 需求中出现 | 图形要素 | 用什么实体 | 链接 |
|---|---|---|---|
| 矩形 / 板 / 框 | 外形 | PolylineEnt(闭合,可带 bulge 圆角) |
entity/polyline |
| 圆 / 通孔 / Φ X | 孔 / 特征 | CircleEnt |
entity/line-arc-circle |
| 弧 / 圆角 R / 倒角 | 外形 | PolylineEnt 带 bulge / ArcEnt |
entity/polyline、entity/line-arc-circle |
| 中心 / 周围阵列 / N 个 | 阵列 | 循环 + Point2D 计算 |
见本页第五节 |
| 标尺寸 / 标注 / 公差 | 标注 | LinearDimensionEnt 等 7 类 |
entity/dimension-mleader |
| 文字 / 标签 / 注释 | 文字 | TextEnt / MTextEnt |
entity/text-mtext |
| 填充 / 阴影 / 图案 | 填充 | HatchEnt + setLoops |
entity/hatch-solid |
| 实色填充(箭头 / 三角 / 四边) | 填充 | SolidEnt |
entity/hatch-solid |
| 重复部件 / 符号 / 出现 ≥ 2 次 | 块 | doc.blocks.add + InsertEnt |
entity/block-insert |
| 引线 / 注释指向 | 引线 | MLeaderEnt |
entity/dimension-mleader |
| 曲线 / 函数图 / 等高线 | 路径 | PolylineEnt 折线 / SplineEnt |
entity/polyline、entity/spline-ellipse |
| 网格 / 刻度 | 装饰 | LineEnt 循环(GRID 图层) |
entity/line-arc-circle |
| 中心线 / 虚线 / 隐藏线 | 装饰 | LineEnt + lineType='CENTER'/'HIDDEN' |
property/color-linetype |
| 椭圆 / 椭圆弧 | 外形 | EllipseEnt(可设 startAngle/endAngle) |
entity/spline-ellipse |
| 图像 / 位图 | 引用 | ImageRefEnt + ImageSource |
entity/image |
| 已有 SVG | 导入 | IMPORTSVG 命令 |
insert/svg-import |
把映射结果写进注释或
writeMessage(...)(vjcad 顶层调试通道,详见 trouble/overview),让用户能复核 AI 的拆解假设是否正确(例如"周围 4 个孔" = 四角 vs 四边中点)。
三、图层规划(通用方案)
不论画什么,先按用途分图层。颜色优先用 ACI 索引(1~7、8 灰、256 ByLayer),保持深 / 浅主题兼容。
| 用途 | 建议名 | ACI | 线型 |
|---|---|---|---|
| 外轮廓 | OUTLINE |
7(白/黑随主题) | CONTINUOUS |
| 孔 / 切口 | HOLE |
1(红) | CONTINUOUS |
| 中心线 | CENTER |
4(青) | CENTER |
| 隐藏线 | HIDDEN |
2(黄) | HIDDEN |
| 标注 | DIM |
3(绿) | CONTINUOUS |
| 文字 / 标签 | TEXT |
7 | CONTINUOUS |
| 填充 | HATCH |
8(深灰) | CONTINUOUS |
| 网格 / 辅助 | GRID |
8 | CONTINUOUS |
| 装饰 / 不打印 | AUX |
8 | CONTINUOUS |
确保图层存在(幂等):
function ensureLayers() {
const defs = [
{ name: 'OUTLINE', color: 7, lineType: 'CONTINUOUS' },
{ name: 'HOLE', color: 1, lineType: 'CONTINUOUS' },
{ name: 'CENTER', color: 4, lineType: 'CENTER' },
{ name: 'HIDDEN', color: 2, lineType: 'HIDDEN' },
{ name: 'DIM', color: 3, lineType: 'CONTINUOUS' },
{ name: 'TEXT', color: 7, lineType: 'CONTINUOUS' },
{ name: 'HATCH', color: 8, lineType: 'CONTINUOUS' },
{ name: 'GRID', color: 8, lineType: 'CONTINUOUS' }
];
for (const d of defs) {
if (!Engine.currentDoc.layers.itemByName(d.name)) {
Engine.createLayer(d.name, d);
}
}
}
切到当前图层 + 用 setDefaults 让新建实体落到这一层:
Engine.setCurrentLayer('OUTLINE'); // 切 CLAYER
Engine.CECOLOR = 256; // ByLayer:颜色随层
Engine.CELTYPE = 'ByLayer'; // 线型随层
const outline = new PolylineEnt();
outline.setPoints([[0, 0], [80, 0], [80, 60], [0, 60]]);
outline.isClosed = true;
outline.setDefaults(); // 应用 CECOLOR / CELTYPE / CLAYER
API 来源:layer/create-switch、layer/layer-states、property/set-defaults、property/color-linetype。
想让"图层颜色 / 线型"自动覆盖所有实体:把
Engine.CECOLOR = 256(ByLayer)、Engine.CELTYPE = 'ByLayer',再setDefaults()。后续只改图层颜色 / 线型,所有该层实体一起变。
四、实体创建套路(自动成图最常用范式)
每条范式都是 new XxxEnt(...) → 设置参数 → setDefaults() → 推入 entities。详细字段见各 entity 子页。
4.1 直线 LineEnt(网格 / 刻度 / 引线)
const ln = new LineEnt([x1, y1], [x2, y2]);
ln.setDefaults();
entities.push(ln);
批量画线(如网格):循环 push,一次性 Engine.addEntities(arr),不要在循环里逐个 add 也不要循环里 regen。详见 entity/line-arc-circle。
4.2 圆 / 弧(孔 / 圆角)
const c = new CircleEnt([cx, cy], r);
c.setDefaults();
const arc = new ArcEnt([cx, cy], r, startAngRad, endAngRad); // 弧度,不是度
arc.setDefaults();
// 三点定弧(已知端点 + 中间点)——参数必须是 Point2D,传 [x,y] 数组会得 NaN
const arc2 = new ArcEnt();
arc2.initBy3Pt(new Point2D(0, 0), new Point2D(50, 50), new Point2D(100, 0));
arc2.setDefaults();
4.3 多段线 PolylineEnt(外形 / 折线 / 曲线拟合)
简单矩形 / 多边形(无圆角):
const pl = new PolylineEnt();
pl.setPoints([[0, 0], [80, 0], [80, 60], [0, 60]]);
pl.isClosed = true;
pl.setDefaults();
圆角矩形(用 bulge 表示 90° 弧)——bulge = tan(π/8) ≈ 0.4142 表 90° 弧;每个直角拐点替换为 2 个切点:
const r = 5, w = 80, h = 60;
const minX = -w/2, maxX = w/2, minY = -h/2, maxY = h/2;
const bulge = Math.tan(Math.PI / 8);
const rect = new PolylineEnt();
rect.bulgePoints.add(new BulgePoint(new Point2D(minX + r, minY), 0));
rect.bulgePoints.add(new BulgePoint(new Point2D(maxX - r, minY), bulge));
rect.bulgePoints.add(new BulgePoint(new Point2D(maxX, minY + r), 0));
rect.bulgePoints.add(new BulgePoint(new Point2D(maxX, maxY - r), bulge));
rect.bulgePoints.add(new BulgePoint(new Point2D(maxX - r, maxY), 0));
rect.bulgePoints.add(new BulgePoint(new Point2D(minX + r, maxY), bulge));
rect.bulgePoints.add(new BulgePoint(new Point2D(minX, maxY - r), 0));
rect.bulgePoints.add(new BulgePoint(new Point2D(minX, minY + r), bulge));
rect.isClosed = true;
rect.setDefaults();
正多边形 / 函数曲线——逐点 addVertex:
const pl = new PolylineEnt();
const N = 200;
for (let i = 0; i <= N; i++) {
const x = -10 + (20 * i) / N;
pl.addVertex([x, Math.sin(x)]);
}
pl.isClosed = false;
pl.setDefaults();
详见 entity/polyline。
4.4 文字 TextEnt / MTextEnt(标签 / 注释)
// 单行
const t = new TextEnt();
t.insertionPoint = [x, y];
t.text = '说明文字';
t.height = 8;
t.styleName = 'simkai'; // 见 setup/fonts;默认字体可省
t.setDefaults();
// 多行
const mt = new MTextEnt();
mt.insertionPoint = [x, y];
mt.text = '第一行\n第二行';
mt.height = 10;
mt.setDefaults();
字体 / 样式预先配在 MainView 构造配置的 fonts 数组,详见 setup/fonts、entity/text-mtext。
4.5 标注(按需求选 7 类)
| 需求里说的 | 用 | 关键参数 |
|---|---|---|
| 长 / 宽 / 距离 | LinearDimensionEnt |
(p1, p2, textPos, angleRad) |
| 倾斜的尺寸 | AlignedDimensionEnt |
(p1, p2, textPos) |
| 半径 R | RadialDimensionEnt |
(center, ptOnCircle, leaderLen) |
| 直径 Φ | DiametricDimensionEnt |
(p1, p2) 直径两端 |
| 角度 ° | AngleDimensionEnt |
(vertex, p1, p2, arcRadius) |
| 弧长 | ArcDimensionEnt |
(center, p1, p2, textPos) |
| X / Y 坐标 | OrdinateDimensionEnt |
(origin, pt, textPos, 'x'/'y'/'xy') |
import { Point2D } from 'vjcad';
const dim = new LinearDimensionEnt(
new Point2D(0, 0),
new Point2D(80, 0),
new Point2D(40, -15), // 文字位置(决定标注线偏移方向 / 距离)
0 // 弧度,0 = 水平测量
);
dim.setDefaults();
层叠避让:多条平行标注在构造时按递增 step 给不同的第三参(尺寸线位置点),建议 step ≥ 字高 × 2:
const step = 15;
const spans = [[0, 80], [0, 200]]; // 每条标注的测量起止 x
const dims = spans.map(([x1, x2], i) => new LinearDimensionEnt(
new Point2D(x1, 0),
new Point2D(x2, 0),
new Point2D((x1 + x2) / 2, -15 - i * step), // 尺寸线逐条下移
0
));
不要事后
move标注来错开:标注族的move只收Point2D(传[x,y]得 NaN),而且会把测量点startPoint/endPoint一起平移,标注就不再量原几何。
4.6 填充 HatchEnt(图案)/ SolidEnt(实色)
图案填充——边界用 Edge / Edges + EdgeType.Polyline + bulgePoints:
import { HatchEnt, PolylineEnt, Edge, Edges, EdgeType } from 'vjcad';
const boundary = new PolylineEnt();
boundary.setPoints([[0, 0], [80, 0], [80, 60], [0, 60]]);
boundary.isClosed = true;
boundary.setDefaults();
const hatch = new HatchEnt();
hatch.patternName = 'ANSI31';
const edge = new Edge();
edge.edgeType = EdgeType.Polyline;
edge.bulgePoints = boundary.bulgePoints.clone(); // 必须 clone
const edges = new Edges();
edges.add(edge);
hatch.setLoops(edges);
hatch.setDefaults();
entities.push(boundary, hatch);
实色填充(三角 / 四边 / 箭头)——SolidEnt(p1, p2, p3, p4) 顺序按四边形(三角让 p3 === p4):
const solid = new SolidEnt([0, 0], [10, 0], [10, 8], [0, 8]);
solid.setDefaults();
4.7 块 InsertEnt(同形状出现 ≥ 2 次时)
判定:同一形状要出现 ≥ 2 次就用块;只出现 1 次直接画,不必搞块。
const doc = Engine.currentDoc;
const blockName = 'PIN';
let blockDef = doc.blocks.itemByName(blockName);
if (!blockDef) {
blockDef = doc.blocks.add(blockName);
blockDef.basePoint = [0, 0];
const c = new CircleEnt([0, 0], 4);
c.setDefaults();
blockDef.addEntity(c); // ⚠️ 块内实体的 setDefaults 不能省
}
// 复用:N 个孔 → N 个 InsertEnt
const ins = new InsertEnt();
ins.blockId = blockDef.blockId; // 不是 blockName 字符串
ins.insertionPoint = [10, 10];
ins.scaleFactor = 1; // 只有统一缩放;无 xScaleFactor / yScaleFactor
ins.rotation = 0; // 弧度
ins.setDefaults();
entities.push(ins);
属性文字(tag 占位 → 插入时填值)、计数命令 COUNTBLOCK、替换命令 REPLACEBLOCK 见 entity/block-insert。
4.8 引线 MLeaderEnt(指向标注)
import { MLeaderEnt, MLeaderContentType, Point2D } from 'vjcad';
const ml = new MLeaderEnt();
ml.contentType = MLeaderContentType.MText;
ml.textContent = '说明';
ml.textPosition = new Point2D(textX, textY);
ml.textHeight = 6;
ml.addLeaderLine(new Point2D(arrowX, arrowY)); // 箭头端
ml.setDefaults();
4.9 样条 SplineEnt(光滑曲线)
const sp = new SplineEnt();
sp.setControlPoints([[0, 0], [20, 30], [60, 30], [80, 0]]);
sp.setDefaults();
五、阵列 / 对称 / 等分(通用算法)
只是数学公式,无新 API;用前面的实体范式 + 循环。
矩形阵列:
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
entities.push(makeOne([ox + i * dx, oy + j * dy]));
}
}
圆形阵列(绕中心 N 等分):
for (let i = 0; i < n; i++) {
const a = (i * 2 * Math.PI) / n;
entities.push(makeOne([cx + r * Math.cos(a), cy + r * Math.sin(a)]));
}
矩形四角孔(最常见):
const pad = 8;
[[pad, pad], [w-pad, pad], [w-pad, h-pad], [pad, h-pad]]
.forEach(([cx, cy]) => entities.push(makeHole([cx, cy])));
镜像复制(先建一半,再 mirror 出另一半):
const half = buildHalf();
const mirrored = half.map(e => {
const c = e.clone();
c.mirror(new Point2D(0, -1), new Point2D(0, 1)); // 关于 X 轴
return c;
});
entities.push(...half, ...mirrored);
等距等分(坐标轴刻度 / 折线段):
const N = 20;
for (let i = 0; i <= N; i++) {
const t = i / N;
const x = x0 + (x1 - x0) * t;
// 在 x 处画刻度...
}
API 来源:edit/move-copy、edit/mirror、geometry/point-bounds。
六、撤销组 + 批量提交(必做)
自动成图必须把整张图作为一组撤销——否则用户按一次 Ctrl+Z 只能退掉最后一个实体。
Engine.startUndoRecord('AUTO_DRAW_PLATE'); // 描述会显示在历史 / tooltip
try {
// …构造 entities…
Engine.addEntities(entities); // 一次性提交
} finally {
Engine.endUndoRecord(); // 异常也要关组
}
Engine.regen(); // 渲染一次(不要在循环里调)
Engine.zoomExtents(); // 视图收尾一次
嵌套安全:
Engine.startUndoRecord是引用计数,子函数里再调一次也只算一组。详见 undo/group。
实体数量极大(千级):再加 eventManager.suspendEvents 提速:
Engine.startUndoRecord('BIG');
Engine.eventManager.suspendEvents();
try {
for (let i = 0; i < 5000; i++) entities.push(makeOne(i));
Engine.addEntities(entities);
} finally {
Engine.eventManager.resumeEvents();
Engine.endUndoRecord();
}
Engine.regen();
Engine.zoomExtents();
API 来源:undo/group、event/listen。
七、收尾 & 验证
收尾固定四步:
Engine.regen()——重新渲染一次(图层 / 线型变了用Engine.regen(true)强制刷新)。Engine.zoomExtents()——或Engine.zoomToEntities(entities)/Engine.zoomToFit(entities)聚焦到本次绘制的范围(无zoomToBounds;缩放到矩形用zoomExtents(undefined,{wcsBounds:[...]}))。- 可选
message.success('已生成 ' + entities.length + ' 个实体')(message是 vjcad 顶层导出的提示通道)反馈用户,详见 ui/dialogs。 - 可选返回
entities/ 一些"句柄实体",便于调用方继续操作(如再次 mirror、再标注、做选中高亮)。
API 来源:view/zoom、view/pan-redraw、ui/dialogs。
八、综合红旗
| # | 红旗 | 描述 / 修复 |
|---|---|---|
| 1 | 在 cadView.onLoad() 之前调 autoDraw |
Engine 还没装好;先 await cadView.onLoad() 再画 |
| 2 | 没有 setDefaults() |
实体不吸 Engine.CECOLOR / CELTYPE / CLAYER,颜色 / 图层错乱 |
| 3 | setDefaults 之后才 setCurrentLayer |
顺序倒了;先切层再 setDefaults |
| 4 | 没用 startUndoRecord / endUndoRecord |
用户撤销体验割裂,一次只退一个 |
| 5 | 没用 try/finally 关组 |
任意异常 → 撤销组永远没关 → 后续操作全归到这个组 |
| 6 | 循环里 regen / zoomExtents |
卡顿 / 视图来回跳;只在批量结束后调一次 |
| 7 | addEntities 在循环里逐个调 |
应收集到数组一次性 Engine.addEntities(arr) |
| 8 | 用块但忘 doc.blocks.add 直接 new InsertEnt() |
块定义不存在;先 add 或 itemByName 拿到 blockDef |
| 9 | ins.blockId = blockName(字符串) |
错;要 ins.blockId = blockDef.blockId |
| 10 | Hatch 边界点直接引用不 clone |
bulgePoints.clone() 不能省;回路由 hatch.setLoops() 自动闭合(Edge 无 isClosed),边界多段线的 isClosed 只影响轮廓线本身 |
| 11 | lineType: 'CENTER' 但项目没加载 CENTER 线型 |
显示成实线;自定义线型时先用 LineType 管理器加载,详见 property/color-linetype |
| 12 | 标注 LinearDimensionEnt(...) 第四参传度数 |
实际是弧度,用 Math.PI/2 表示 90° |
| 13 | MLeaderEnt 没设 textPosition 或 addLeaderLine |
引线渲染不出 |
| 14 | 自然语言"周围 4 个孔"理解为四角 vs 四边中点 | 没有上下文时默认四角,并把假设用 writeMessage(...) 输出让用户复核 |
| 15 | 多条平行标注重叠 | 构造时按 step 递增第三参(尺寸线位置,≥ 字高 × 2),别事后 move 标注 |
| 16 | 颜色想要 RGB 直接 ent.color = 0xFF8000 |
应加标记位 0x1000000 + 0xFF8000,或用 ColorConverter.createRgbColorIndexFromHexStr('#FF8000') |
| 17 | 把 mirror([0, 0], [1, 0]) 当作"沿 X 轴" |
是"过点 (0,0) 和 (1,0) 的镜像线",就是 X 轴;OK,但理解为"两点定线"更准确 |
| 18 | 调 clone() 后忘了 Engine.addEntities |
副本只在内存里,没进图纸 |
九、源约束(反编造)
- 所有实体类、构造签名、字段都必须能在 vjcad skill 实体页、edit/、property/、layer/ 或对应
vjcad/example/*文件里找到。不存在的方法不要捏造。 - 自然语言"四角孔" → "四角"是经验法则,但写进代码前先用
writeMessage(...)把假设说出来,让用户能复核。 - 颜色优先 ACI 索引(
1~7标准色 /256ByLayer);要 RGB 真彩色时用ColorConverter.createRgbColorIndex / createRgbColorIndexFromHexStr / createRgbColorIndexFromComponents,详见 property/color-linetype。 - 字段命名歧义(如
lineTypevslinetypeName、viewModevsviewerMode、move(dx,dy)vsmove(from,to))按 trouble/source-conflicts 的策略:示例代码(runnable)优先。
十、综合示例索引
下面 4 个示例是把本页"五步骨架"贴到不同复杂度的真实需求上,按复杂度递增——读完本页再看示例,能直接对照"哪一步对应哪几行代码":
| # | 示例 | 复杂度 | 主要难点 | 文件 |
|---|---|---|---|---|
| 1 | 80×60 圆角板 + 5 孔 + 标注 | 简单 | 圆角矩形 bulge、角孔与圆角间隙 | examples/plate-and-cat(示例 A) |
| 2 | 可爱小猫(细节丰富) | 中 | 多图层艺术构图、SolidEnt 三角、HatchEnt 蝴蝶结 |
examples/plate-and-cat(示例 B) |
| 3 | DN15 内螺纹黄铜截止阀(剖视 + 侧视) | 中-复杂 | 剖切区 Hatch、螺纹循环、双视图布局 | examples/valve |
| 4 | 通风过滤装置三视图 | 复杂 | 三视图绝对坐标、四线型混排、链式标注、中文 MText | examples/ventilation-three-views |
每个示例文件都按以下八段写:① 摘要 → ② 需求拆解(NL → 图层 + 实体清单)→ ③ mermaid 步骤图 → ④ 完整可运行代码(套用本页五步骨架)→ ⑤ 关键技术点 → ⑥ 标注布局规则 → ⑦ 红旗 / 反例 → ⑧ 来源。
推荐顺序:先看 examples/plate-and-cat 体会"工程图与艺术图共用同一骨架",再看 examples/valve 体会"剖视 + 多视图布局",最后看 examples/ventilation-three-views 体会"复杂三视图 + 标注链"。
参考(官方文档 MCP)
- 文档:
vjcad/doc/guide/quick-start.md、vjcad/doc/entity/index.md、vjcad/doc/layer/layers.md、vjcad/doc/property/index.md、vjcad/doc/undo/index.md、vjcad/doc/undo/undo-redo.md、vjcad/doc/event/event-usage.md、vjcad/doc/view/index.md - 示例:
vjcad/example/entity/all/01entities.js、vjcad/example/entity/polyline/02rectangle.js、vjcad/example/entity/hatch/01create.js、vjcad/example/entity/block/01create.js、vjcad/example/entity/block/02insert.js、vjcad/example/entity/dimension/01linear.js、vjcad/example/entity/mleader/01create.js、vjcad/example/edit/01move.js、vjcad/example/edit/05mirror.js、vjcad/example/layer/01create.js、vjcad/example/layer/02switch.js、vjcad/example/property/01color.js、vjcad/example/property/03linetype.js、vjcad/example/property/06setdefaults.js、vjcad/example/undo/03group.js、vjcad/example/undo/04description.js、vjcad/example/view/01zoomextents.js、vjcad/example/comprehensive/01section.js - 内部交叉:entity/create-entities、entity/polyline、entity/hatch-solid、entity/block-insert、entity/dimension-mleader、entity/text-mtext、entity/line-arc-circle、entity/spline-ellipse、edit/move-copy、edit/mirror、layer/create-switch、layer/layer-states、property/set-defaults、property/color-linetype、undo/group、event/listen、view/zoom、trouble/source-conflicts
- 综合示例(推荐实现前先看下相关示例):examples/plate-and-cat、examples/valve、examples/ventilation-three-views