唯杰地图 logo唯杰地图
VJMAP
EN

overview

vjcad/references/autodraw/overview.md 23.7 KB501 行
原文

本文回答:自动成图 = 把"任意一个绘图需求"按 拆解 → 图层 → 按层建实体 → 标注 → 撤销组 + 批量提交 五步走的工程范式。本页只讲通用思路——具体实体 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();

详见 entity/line-arc-circle。

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 一起平移,标注就不再量原几何。

详见 entity/dimension-mleader。

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();

详见 entity/hatch-solid。

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();

详见 entity/dimension-mleader。

4.9 样条 SplineEnt(光滑曲线)

const sp = new SplineEnt();
sp.setControlPoints([[0, 0], [20, 30], [60, 30], [80, 0]]);
sp.setDefaults();

详见 entity/spline-ellipse。

五、阵列 / 对称 / 等分(通用算法)

只是数学公式,无新 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。

七、收尾 & 验证

收尾固定四步:

  1. Engine.regen()——重新渲染一次(图层 / 线型变了用 Engine.regen(true) 强制刷新)。
  2. Engine.zoomExtents()——或 Engine.zoomToEntities(entities) / Engine.zoomToFit(entities) 聚焦到本次绘制的范围(无 zoomToBounds;缩放到矩形用 zoomExtents(undefined,{wcsBounds:[...]}))。
  3. 可选 message.success('已生成 ' + entities.length + ' 个实体')(message 是 vjcad 顶层导出的提示通道)反馈用户,详见 ui/dialogs。
  4. 可选返回 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 标准色 / 256 ByLayer);要 RGB 真彩色时用 ColorConverter.createRgbColorIndex / createRgbColorIndexFromHexStr / createRgbColorIndexFromComponents,详见 property/color-linetype。
  • 字段命名歧义(如 lineType vs linetypeName、viewMode vs viewerMode、move(dx,dy) vs move(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)