ventilation-three-views
本文回答:壁挂式通风过滤装置 CAD 三视图(主视、左视、俯视)按用户给定绝对坐标绘制;展示自动成图通用骨架在大型工程图中的伸缩性——同样是 autodraw/overview 五步走,只是图层多 2 个、实体多 130+、标注链长一些。重点示范:① 视图间距用绝对坐标分块;② 隐藏 / 中实 / 中心 / 粗实四种线型混排;③ 标注层叠的 3 列分布避免堆成一墙;④
MTextEnt多行文字 + 视图名 / 风机细节 / dimstyle 三件套让"几何图"升级到"可交付图"(详第八节)。
一、需求拆解
通风过滤装置 CAD 三视图绘制指令(含完整标注与开口)
单位:mm,绘图坐标比例 1:1
线型:外轮廓粗实线,开口/百叶中实线,中心线点划线,隐藏结构虚线
视图排布:三视图间距 ≥1500 mm,完全分离不重叠
| 部件 | 主视 | 左视 | 俯视 | 图层 |
|---|---|---|---|---|
| 箱体外框 | ✓ | ✓ | ✓ | OUTLINE |
| 滤芯口(百叶 + 法兰) | 3 个正面 | 3 个左侧 | 各面投影 | OPENING |
| 顶部出风口 Φ600/Φ700 | ✓ | – | ✓ | OPENING |
| 风机外形 + 内部细节(电机 + 4 辐条 + 4 角加强筋 + "风机"标签) | ✓ | – | – | OUTLINE / TEXT |
| 风机底座投影 | – | – | ✓ | OPENING |
| 背部支架 | – | ✓(虚线) | – | HIDDEN |
| 底面进风口 | – | ✓(虚线) | ✓(虚线) | HIDDEN |
| 中心线 | ✓ | ✓ | ✓ | CENTER |
| 标注 | ~12 | ~6 | ~8 | DIM |
| 视图标签(主视图 / 左视图 / 俯视图 1:100) | ✓ | ✓ | ✓ | TEXT |
| 技术说明(MText) + "比例 1:100" 题块 | – | – | 右下角 | TEXT |
总实体 ≈ 130。
二、视图坐标布局
俯视 [0,4000]──[2000,5200]
│
│ 间距 1500
│
主视 [0,0]────[2000,3450] 左视 [4000,0]──[5200,2500] 技术说明 [6200,2400]→[9200,~1230]
└─────── 间距 2000 ───────────┘ └─ width 3000 / textAttachment=7 ─┘
比例题块 [6200,800]→[9200,1100]
- 主视右边界
X=2000→ 左视左边界X=4000,水平间距 2000 mm(≥1500 ✓) - 主视上边界
Y=3450→ 俯视下边界Y=4000(用风机区算到Y=2500时间距 1500,含风机时仍 ≥ 550,本例选Y=4000即从主视上边距 550 到俯视下边) - 左视右边界
X=5200→ 技术说明 MText 左边界X=6200,水平间距 1000 mm(避免 MText 锚点漂移盖到左视图) - 注:用户给的 "三视图间距 ≥ 1500" 指箱体投影间距——主视箱体顶 2500 → 俯视底 4000 = 1500 ✓
三、生成步骤
flowchart LR
L["1.ensureLayers<br/>OUTLINE/OPENING/HIDDEN<br/>CENTER/DIM/TEXT"]
L --> D["2.setupDimStyle<br/>DIMSCALE 50 → 1:100 可读"]
D --> F["3.主视 buildFrontView<br/>(箱体+3 百叶+顶部出风口<br/>+风机[电机+支架+'风机'标签])"]
F --> S["4.左视 buildLeftView<br/>(箱体+3 百叶+背支架+底进风口虚线)"]
S --> T["5.俯视 buildTopView<br/>(投影外框+各面投影+顶出风口+风机底座)"]
T --> V["6.buildViewLabels<br/>主视图 / 左视图 / 俯视图"]
V --> N["7.技术说明 buildTitleNote<br/>(MText 高 150+'比例 1:100' 题块)"]
N --> C["8.commit<br/>addEntities+regen+zoomExtents"]
四、完整可运行代码
4.1 顶层 + 通用工具
import {
Engine, PolylineEnt, LineEnt, CircleEnt, TextEnt, MTextEnt,
LinearDimensionEnt, DiametricDimensionEnt, Point2D,
writeMessage
} from 'vjcad';
await cadView.onLoad();
drawVentilation();
function drawVentilation() {
Engine.startUndoRecord('VENTILATION_3_VIEWS');
try {
ensureVentLayers();
setupDimStyle(); // 让默认标注在 1:100 出图时仍可读
const ents = [];
ents.push(...buildFrontView());
ents.push(...buildLeftView());
ents.push(...buildTopView());
ents.push(...buildViewLabels()); // 主视图 / 左视图 / 俯视图
ents.push(...buildTitleNote());
Engine.addEntities(ents);
writeMessage(`通风装置三视图共 ${ents.length} 个实体`);
} finally {
Engine.endUndoRecord();
}
Engine.regen();
Engine.zoomExtents();
}
function setupDimStyle() {
// 代码 new 出来的标注默认用全局 DefaultDimensionStyle 的副本,改文档样式对象(dimensionStyles.itemByName)不生效;
// 文档比例走 DIMSCALE,每个标注再调 applyCurrentDocStyle() 吸取(见 hDim / vDim)。
Engine.currentDoc.DIMSCALE = 50; // 整体放大 50× → 1:100 出图时字高 ≈ 2.5 mm/纸面
}
function ensureVentLayers() {
const defs = [
{ name: 'OUTLINE', color: 7, lineType: 'CONTINUOUS' },
{ name: 'OPENING', color: 3, lineType: 'CONTINUOUS' },
{ name: 'HIDDEN', color: 2, lineType: 'HIDDEN' },
{ name: 'CENTER', color: 4, lineType: 'CENTER' },
{ name: 'DIM', color: 1, lineType: 'CONTINUOUS' },
{ name: 'TEXT', color: 7, lineType: 'CONTINUOUS' }
];
for (const d of defs) {
if (!Engine.currentDoc.layers.itemByName(d.name)) {
Engine.createLayer(d.name, d);
}
}
}
function rectClosed(p1, p2) {
const pl = new PolylineEnt();
pl.setPoints([[p1[0], p1[1]], [p2[0], p1[1]], [p2[0], p2[1]], [p1[0], p2[1]]]);
pl.isClosed = true;
pl.setDefaults();
return pl;
}
function crossDiagonals(p1, p2) {
const a = new LineEnt(p1, p2);
const b = new LineEnt([p1[0], p2[1]], [p2[0], p1[1]]);
a.setDefaults(); b.setDefaults();
return [a, b];
}
function centerCross(cx, cy, halfH, halfV) {
const a = new LineEnt([cx - halfH, cy], [cx + halfH, cy]);
const b = new LineEnt([cx, cy - halfV], [cx, cy + halfV]);
a.setDefaults(); b.setDefaults();
return [a, b];
}
function hDim(p1, p2, textPosY) {
const d = new LinearDimensionEnt(
new Point2D(p1[0], p1[1]), new Point2D(p2[0], p2[1]),
new Point2D((p1[0] + p2[0]) / 2, textPosY), 0);
d.setDefaults();
d.applyCurrentDocStyle(); // 吸取文档当前标注样式,scaleFactor 取自 DIMSCALE
return d;
}
function vDim(p1, p2, textPosX) {
const d = new LinearDimensionEnt(
new Point2D(p1[0], p1[1]), new Point2D(p2[0], p2[1]),
new Point2D(textPosX, (p1[1] + p2[1]) / 2), Math.PI / 2);
d.setDefaults();
d.applyCurrentDocStyle();
return d;
}
4.2 主视图
function buildFrontView() {
const ents = [];
Engine.setCurrentLayer('OUTLINE');
Engine.CECOLOR = 256;
Engine.CELTYPE = 'ByLayer';
ents.push(rectClosed([0, 0], [2000, 2500])); // 箱体
ents.push(...buildFan({ x0: 500, y0: 2500, x1: 1500, y1: 3450 }));
Engine.setCurrentLayer('OPENING');
const frontOpens = [
[[664, 192], [1336, 864]],
[[664, 914], [1336, 1586]],
[[664, 1636], [1336, 2308]]
];
for (const [p1, p2] of frontOpens) {
ents.push(rectClosed(p1, p2));
ents.push(...crossDiagonals(p1, p2));
}
const ventOuter = new CircleEnt([1000, 2500], 350);
const ventInner = new CircleEnt([1000, 2500], 300);
ventOuter.setDefaults(); ventInner.setDefaults();
ents.push(ventOuter, ventInner);
Engine.setCurrentLayer('CENTER');
ents.push(...centerCross(1000, 2500, 400, 400));
ents.push(...buildFrontDims());
return ents;
}
function buildFan({ x0, y0, x1, y1 }) {
const out = [];
Engine.setCurrentLayer('OUTLINE');
out.push(rectClosed([x0, y0], [x1, y1]));
const cx = (x0 + x1) / 2, cy = (y0 + y1) / 2;
const motor = new CircleEnt([cx, cy], 200);
const hub = new CircleEnt([cx, cy], 60);
motor.setDefaults(); hub.setDefaults();
out.push(motor, hub);
for (let i = 0; i < 4; i++) {
const a = Math.PI / 4 + i * Math.PI / 2;
const ln = new LineEnt(
[cx + 60 * Math.cos(a), cy + 60 * Math.sin(a)],
[cx + 200 * Math.cos(a), cy + 200 * Math.sin(a)]
);
ln.setDefaults();
out.push(ln);
}
const corners = [
[[x0, y0], [x0 + 80, y0 + 80]],
[[x1, y0], [x1 - 80, y0 + 80]],
[[x0, y1], [x0 + 80, y1 - 80]],
[[x1, y1], [x1 - 80, y1 - 80]]
];
for (const [a, b] of corners) {
const ln = new LineEnt(a, b);
ln.setDefaults();
out.push(ln);
}
Engine.setCurrentLayer('TEXT');
const label = new TextEnt();
label.insertionPoint = [x0 + 220, cy - 75];
label.text = '风机';
label.height = 150;
label.styleName = 'simkai';
label.setDefaults();
out.push(label);
Engine.setCurrentLayer('OUTLINE');
return out;
}
function buildFrontDims() {
Engine.setCurrentLayer('DIM');
const out = [];
out.push(hDim([0, 0], [2000, 0], -120)); // 箱体宽 2000
out.push(hDim([500, 3450], [1500, 3450], 3580)); // 风机宽 1000
out.push(hDim([664, 0], [1336, 0], -240)); // 百叶宽 672
out.push(hDim([0, 0], [664, 0], -360)); // 左侧空 664
out.push(hDim([1336, 0], [2000, 0], -360)); // 右侧空 664
const cInner = 2150, cMid = 2350, cOuter = 2600;
out.push(vDim([2000, 0], [2000, 192], cInner)); // 风口下空 192
out.push(vDim([2000, 864], [2000, 914], cInner)); // 间距 50
out.push(vDim([2000, 1586], [2000, 1636], cInner)); // 间距 50
out.push(vDim([2000, 2308], [2000, 2500], cInner)); // 风口上空 192
out.push(vDim([2000, 192], [2000, 864], cMid)); // 第 1 风口 672
out.push(vDim([2000, 914], [2000, 1586], cMid)); // 第 2 风口 672
out.push(vDim([2000, 1636], [2000, 2308], cMid)); // 第 3 风口 672
out.push(vDim([2000, 0], [2000, 2500], cOuter)); // 箱体高 2500
out.push(vDim([2000, 0], [2000, 3450], cOuter + 200)); // 总高 3450
out.push(vDim([1500, 2500], [1500, 3450], 1700)); // 风机高 950
const dimD600 = new DiametricDimensionEnt(
new Point2D(700, 2500), new Point2D(1300, 2500));
const dimD700 = new DiametricDimensionEnt(
new Point2D(650, 2500), new Point2D(1350, 2500));
dimD600.setDefaults(); dimD700.setDefaults();
dimD600.applyCurrentDocStyle(); dimD700.applyCurrentDocStyle();
out.push(dimD600, dimD700);
return out;
}
4.3 左视图
function buildLeftView() {
const ents = [];
const ox = 4000;
Engine.setCurrentLayer('OUTLINE');
ents.push(rectClosed([ox, 0], [ox + 1200, 2500]));
Engine.setCurrentLayer('OPENING');
const leftOpens = [
[[ox + 80, 192], [ox + 752, 864]],
[[ox + 80, 914], [ox + 752, 1586]],
[[ox + 80, 1636], [ox + 752, 2308]]
];
for (const [p1, p2] of leftOpens) {
ents.push(rectClosed(p1, p2));
ents.push(...crossDiagonals(p1, p2));
}
Engine.setCurrentLayer('HIDDEN');
ents.push(rectClosed([ox + 1200, 1150], [ox + 1700, 1350])); // 背支架 500×200
ents.push(rectClosed([ox + 80, -50], [ox + 752, 0])); // 底面进风口
ents.push(...crossDiagonals([ox + 80, -50], [ox + 752, 0]));
ents.push(...buildLeftDims(ox));
return ents;
}
function buildLeftDims(ox) {
Engine.setCurrentLayer('DIM');
const out = [];
out.push(hDim([ox, 0], [ox + 1200, 0], -120)); // 深 1200
out.push(hDim([ox + 80, 0], [ox + 752, 0], -240)); // 百叶 672
const cx = ox + 1200 + 200;
out.push(vDim([ox + 1200, 192], [ox + 1200, 864], cx));
out.push(vDim([ox + 1200, 914], [ox + 1200, 1586], cx));
out.push(vDim([ox + 1200, 1636], [ox + 1200, 2308], cx));
out.push(vDim([ox + 1200, 0], [ox + 1200, 2500], cx + 200));
return out;
}
4.4 俯视图
function buildTopView() {
const ents = [];
const oy = 4000;
Engine.setCurrentLayer('OUTLINE');
ents.push(rectClosed([0, oy], [2000, oy + 1200]));
const ventOuter = new CircleEnt([1000, oy + 600], 350);
const ventInner = new CircleEnt([1000, oy + 600], 300);
ventOuter.setDefaults(); ventInner.setDefaults();
ents.push(ventOuter, ventInner);
Engine.setCurrentLayer('CENTER');
ents.push(...centerCross(1000, oy + 600, 400, 400));
Engine.setCurrentLayer('OPENING');
ents.push(rectClosed([500, oy + 200], [1500, oy + 1000])); // 风机底座
const frontProj = [[664, oy], [1336, oy + 672]];
ents.push(rectClosed(frontProj[0], frontProj[1]));
const leftProj = [[0, oy + 208], [672, oy + 880]];
ents.push(rectClosed(leftProj[0], leftProj[1]));
const rightProj = [[1328, oy + 208], [2000, oy + 880]];
ents.push(rectClosed(rightProj[0], rightProj[1]));
Engine.setCurrentLayer('HIDDEN');
const bottomHidden = [[664, oy + 672], [1336, oy + 1344]]; // 用户给值
ents.push(rectClosed(bottomHidden[0], bottomHidden[1]));
ents.push(...crossDiagonals(bottomHidden[0], bottomHidden[1]));
ents.push(...buildTopDims(oy));
return ents;
}
function buildTopDims(oy) {
Engine.setCurrentLayer('DIM');
const out = [];
out.push(hDim([0, oy + 1200], [2000, oy + 1200], oy + 1320)); // 宽 2000
out.push(hDim([0, oy], [2000, oy], oy - 120)); // 宽 2000 复用一条
out.push(vDim([0, oy], [0, oy + 1200], -120)); // 深 1200
const dimVent = new DiametricDimensionEnt(
new Point2D(700, oy + 600), new Point2D(1300, oy + 600));
dimVent.setDefaults();
dimVent.applyCurrentDocStyle();
out.push(dimVent);
out.push(hDim([664, oy], [1336, oy], oy - 240)); // 正面投影宽 672
out.push(vDim([0, oy + 208], [0, oy + 880], -240)); // 左 / 右投影深 672
out.push(vDim([2000, oy + 208], [2000, oy + 880], 2120));
return out;
}
4.5 视图标签(在三视图下方加 "主视图 / 左视图 / 俯视图")
function buildViewLabels() {
Engine.setCurrentLayer('TEXT');
const out = [];
const labels = [
{ text: '主视图 1:100', insertion: [600, -350], textAlignment: 7 },
{ text: '左视图 1:100', insertion: [4200, -350], textAlignment: 7 },
{ text: '俯视图 1:100', insertion: [600, 5400], textAlignment: 1 }
];
for (const { text, insertion,textAlignment } of labels) {
const t = new TextEnt();
t.insertionPoint = insertion;
t.text = text;
t.textAlignment = textAlignment;
t.height = 200;
t.styleName = 'simkai';
t.setDefaults();
out.push(t);
}
return out;
}
视图名称用单行
TextEnt(高 200 mm),放在每个视图下方留白处;中文字体simkai必须先在MainView({ fonts })注册(详 setup/fonts)。
4.6 技术说明(MText)+ 比例题块
function buildTitleNote() {
Engine.setCurrentLayer('TEXT');
const out = [];
const noteText =
'壁挂式通风过滤装置,设计风量 10000m³/h,过滤等级 G4。\n' +
'箱体 2000×1200×2500mm,1.5mm 镀锌钢板喷塑。\n' +
'前、左、右立面各 3 个滤芯口,底面 1 个进风口,配固定百叶窗。\n' +
'顶部 DN600 出风口,风机柔性软接。\n' +
'前方检修空间 ≥800mm,两侧及顶部空间 ≥500mm,墙体承重 ≥350kg。\n' +
'符合 GB/T 14295、GB 50243 标准。';
const mt = new MTextEnt();
mt.insertionPoint = [6200, 2400]; // ⬅ 由 5800 挪到 6200:再加 400 mm 安全间距
mt.textAttachment = 7; // ⬅ 7 = TopLeft(小键盘布局):插入点 = 文本框左上角,向右下生长
mt.text = noteText;
mt.height = 150; // 1:100 出图后纸面 1.5 mm 仍可读
mt.maxWidth = 3000; // MTextEnt 无 width 字段;maxWidth=文本框最大宽度(refWidth 等价别名)
mt.styleName = 'simkai';
mt.setDefaults();
out.push(mt);
const scaleBox = new PolylineEnt();
scaleBox.setPoints([[6200, 800], [9200, 800], [9200, 1100], [6200, 1100]]);
scaleBox.isClosed = true;
scaleBox.setDefaults();
out.push(scaleBox);
const scale = new TextEnt();
scale.insertionPoint = [6500, 880];
scale.text = '比例 1 : 100';
scale.height = 200; // 由 80 提到 200:明显成"题块"
scale.styleName = 'simkai';
scale.setDefaults();
out.push(scale);
return out;
}
关键:
MTextEnt.textAttachment默认就是7(MTextAttachmentEnum.TopLeft,插入点 = 文本框左上角,文字向右下生长)。vjcad 附着点按小键盘布局编号(7/8/9 = 上排,1/2/3 = 下排),不同于 DXF 的 1=TopLeft——写成1是 BottomLeft,文字会以插入点为左下角向上生长。这里仍显式赋值——一是代码可读(读者不用猜锚点),二是防御未来默认值变更。若误设成5(MidCenter)会以插入点为中心向四周溢出,盖到临近视图。详见 entity/text-mtext 第三节。
五、关键技术点
5.1 视图分块:绝对坐标 vs 偏移变换
用户给了所有视图的绝对坐标——本例直接用绝对坐标分块(每个 buildXxxView 内部所有点都是绝对值),最简单、便于调试 / 量距。
若同样的"通风装置"要画 1:5 缩小版,把所有坐标乘 0.2 即可;或先在原点构造,最后整组用 entity.move([0,0],[ox,oy]) 平移(详 edit/move-copy)。
5.2 数据驱动 vs 硬编码
3 个百叶用 frontOpens 数组 + for 循环替代手写 3 次:
const frontOpens = [
[[664, 192], [1336, 864]],
[[664, 914], [1336, 1586]],
[[664, 1636], [1336, 2308]]
];
for (const [p1, p2] of frontOpens) {
ents.push(rectClosed(p1, p2));
ents.push(...crossDiagonals(p1, p2));
}
好处:① 改间距只动数据,不动代码;② 与"5 个 / 7 个百叶"扩展只改数组;③ 代码更短易审。
5.3 标注分列(避免链尺寸堆成一堵墙)
主视图右侧 7 段竖向尺寸如果都堆同一 textPosX,渲染后会变成"一堵密不透风的红墙",看不清单条尺寸。本例改成3 列错开:
cInner cMid cOuter
y=2500 ─┬──────── ┐ ┬───── ┬─── ┐
│ 192 │ │ 2500 │3450│
y=2308 ─┴┐ │ │ │ │
│ 672 │ ←第3口 │ │ │
y=1636 ──┘ │ │ │ │
y=1586 ─┬─ │ │ │ │
│ 50 │ ←间距 │ │ │
y=1636 ─┴─ │ │ │ │
│ 672 │ ←第2口 │ │ │
y=914 ── │ │ │ │
...
| 列 | textPosX | 谁去 |
|---|---|---|
cInner=2150(内) |
4 段小缝(上下空 192 / 中间间距 50) | |
cMid=2350(中) |
3 段风口主尺寸 672 | |
cOuter=2600 / 2800(外) |
箱高 2500、总高 3450 |
step ≥ 字高 × 2 防文字重叠(详 autodraw/overview 第四节 4.5)。
5.4 隐藏 / 中实 / 中心 / 粗实 四线型
线型靠图层 + setDefaults() 自动应用:
| 线型 | 图层 | lineType 字段 |
颜色 |
|---|---|---|---|
| 粗实 | OUTLINE | CONTINUOUS |
7(白 / 黑) |
| 中实 | OPENING | CONTINUOUS |
3(绿) |
| 虚线 | HIDDEN | HIDDEN |
2(黄) |
| 点划 | CENTER | CENTER |
4(青) |
前提:项目模板里已加载这些线型;vjcad 默认带 CONTINUOUS / HIDDEN / CENTER,自定义线型见 property/color-linetype。
"粗实 / 中实"在 vjcad 里用图层 ACI 颜色 + ACI 线宽体现;想要严格的 lineWeight 区分(如打印时 0.7 / 0.35 mm),在创建后设图层字段:
const ly = Engine.createLayer('OUTLINE', { color: 7 }); ly.lineWeight = 70;——createLayer的 options 只认layerOn / color / lineType / isFrozen / isLocked,传lineWeight会被静默丢弃。
5.5 中文字体 MText / TextEnt
MTextEnt.styleName = 'simkai' 必须配合 MainView({ fonts: [{ name: 'simkai', path: '...' }] }) 在初始化时注册(详 setup/fonts、entity/text-mtext)。否则中文显示成方框 / 缺字。
字高与出图比例匹配(1:100 出图,几何字高建议 ≥ 100 mm,对应纸面 1 mm 起):
| 用途 | 实体 | 几何字高 | 1:100 纸面 |
|---|---|---|---|
| 标题 / 视图名 | TextEnt |
200 mm | 2 mm |
| 比例题块 | TextEnt |
200 mm | 2 mm |
| 部件标签(风机) | TextEnt |
150 mm | 1.5 mm |
| 多行技术说明 | MTextEnt |
150 mm | 1.5 mm |
| 标注文字 | dimstyle textHeight × scaleFactor |
2.5×50=125 mm | 见 8.1 |
MTextEnt.maxWidth = 3000 决定换行宽度(属性名是 maxWidth,不是 width;curWidth 为只读实际宽度);多行总高 ≈ height × 行数 × 1.2 ~ 1.5,6 行 × 150 ≈ 1170 mm。
textAttachment 建议显式设:默认虽已是 7(TopLeft),但多行说明文字块建议写死,避免复制来的代码段带其它锚点值,见红旗 #14:
mt.textAttachment = 7; // 小键盘布局:7=TopLeft / 8=TopCenter / 9=TopRight / 4=MidLeft / 5=MidCenter / 6=MidRight / 1=BottomLeft / 2=BottomCenter / 3=BottomRight
5.6 buildFan / buildViewLabels / setupDimStyle 三个"成品图"补丁
为把"几何图"升级到"可交付图",本例增加三个独立小函数(详第八节):
| 函数 | 解决 |
|---|---|
buildFan |
风机不再是空白矩形,加电机 / 4 辐条 / 4 角加强筋 / "风机"标签 |
buildViewLabels |
三个视图下方加"主视图 / 左视图 / 俯视图 1:100" TextEnt |
setupDimStyle |
设 Engine.currentDoc.DIMSCALE = 50,配合每个标注 applyCurrentDocStyle(),让 1:100 出图后字高 ≈ 2.5 mm/纸面 |
5.7 crossDiagonals 的 4 点顺序
每个百叶画两条交叉对角线 = LineEnt(p1, p2) + LineEnt([p1.x, p2.y], [p2.x, p1.y])——两条线在矩形对角点对调 y 即可。这种"两点矩形对角对调"是图层 OPENING 的常见模式。
六、标注布局规则
主视右侧的纵向链尺寸分三列而非堆一列,避免文字 / 尺寸线打架:
| 列 | textPosX | 用途 | 数量 |
|---|---|---|---|
| 内层(gap 列) | cInner = 2150 |
上下空 192 + 间距 50 | 4 |
| 中层(开口列) | cMid = 2350 |
3 个 672 风口 | 3 |
| 外层(总尺寸列) | cOuter = 2600 / 2800 |
箱体高 2500、总高 3450 | 2 |
横向 / 三视图整体规则:
| 视图 | 横向标注 textPosY | 纵向标注 textPosX | step |
|---|---|---|---|
| 主视 | 上方 y=3580、下方 y=-120 / -240 / -360 |
cInner / cMid / cOuter 三列 |
120 |
| 左视 | 下方 y=-120 / -240 |
右侧 x=ox+1400、x=ox+1600 |
120 |
| 俯视 | 上方 y=oy+1320、下方 y=oy-120 / oy-240 |
左侧 x=-120 / -240、右侧 x=2120 |
120 |
字高默认 50(
DIMSCALE = 50+ 每个标注applyCurrentDocStyle(),让 1:100 出图后纸面字高 ≈ 2.5 mm);step = 120 ≈ 字高 × 2.4防止文字与下一条尺寸线压叠。
七、红旗 / 反例
- 左视图忘了加
ox = 4000→ 整组左视图盖在主视图上,几何相互压死;先console.log几个左视图实体的bbox看看是否在X >= 4000。 - 百叶画在 OUTLINE 层 → 与箱体外框同色 / 同线型,看不出"开口"语义;必须
setCurrentLayer('OPENING')后再画百叶 + 对角线。 - HIDDEN 线显示成实线 → 模板没加载
HIDDEN线型;修:Engine.linetypeManager.registerLinetype(def)或loadFromLinDefinitions(lin文本)加载(小写 t,详 trouble/source-conflicts §7;无.add())(详 property/color-linetype),或先用Engine.regen(true)强刷一次。 MTextEnt没设styleName='simkai'→ 中文渲染成方框;先在MainView({ fonts: [...] })注册中文字体。textPosX / textPosY写错让标注盖在几何上 → 调试时把textPos加 100 mm 移开看清,再回填精确值。- 直径标注
DiametricDimensionEnt(中心, 圆周点)→ vjcad 的直径标注不是圆心+半径而是直径两端点;本例外圆 Φ700:new Point2D(650, 2500)/new Point2D(1350, 2500),差 700 = Φ700。 crossDiagonals写成LineEnt(p1, p2)+LineEnt(p1, p2):复制粘贴漏改 → 两条线重合,看不到 X 形;第二条要把 y 对调:[p1.x, p2.y]↔[p2.x, p1.y]。for (let i = 0; i < frontOpens.length; i++)内手动setCurrentLayer重复切层:性能略差且容易写错;进入循环前切一次层就好。- 底面进风口 Y 范围
[oy+672, oy+1344]超出箱体 Y 范围[oy, oy+1200]:用户原文如此,本例如实绘制;项目里真实交付前需要再核对一次。 - 链尺寸全堆同一
textPosX:渲染后视觉上挤成"一堵墙",文字相互压叠;本例用 3 列cInner / cMid / cOuter错开(详第六节)。 - MText
height = 60+ 1:100 出图:纸面字高 0.6 mm,几乎看不见;本例改用height = 150(纸面 1.5 mm);视图标签TextEnt.height = 200(纸面 2 mm)。 - 风机就一个空白矩形:浪费 950 mm 高的占位;至少加电机圆 + 4 条安装支架短斜线 + "风机"文字标签(详
buildFan函数)。 - 没视图名 / 没比例题块:图纸缺信息无法交付;本例增加
buildViewLabels三个 TextEnt,比例标用单独的PolylineEnt闭合矩形 + 高 200 的TextEnt。 - MText 误用居中锚点(
textAttachment = 5等) → 文字以插入点为中心向四周溢出,宽文本框会横向跨到临近视图上(默认7TopLeft 无此问题;误写 DXF 习惯的1则是 BottomLeft,文字向上长)。修法:mt.textAttachment = 7显式写死 + 插入点放在"左视图右边界 + 1000 mm"以右,双保险。
八、提升出图质量的若干技巧
这一节回答"为什么默认渲染看起来一般,怎么改"——也是把本示例从"几何对了"升级到"成品图能交付"的关键。
8.1 标注样式 dimstyle
vjcad 默认 DimensionStyle.textHeight = 2.5 是按 AutoCAD 习惯(纸面单位)。1:1 大几何 + 1:100 出图时纸面字高变成 0.025 mm,等于隐形:
function setupDimStyle() {
Engine.currentDoc.DIMSCALE = 50; // 文档标注比例
}
// 每个代码创建的标注:
dim.setDefaults();
dim.applyCurrentDocStyle(); // 克隆文档当前样式,并把 scaleFactor 覆盖为 DIMSCALE
// 或逐个设快捷属性:dim.scaleFactor = 50 / dim.textHeight / dim.arrowSize
⚠️ 只改
dimensionStyles.itemByName('Standard').scaleFactor无效:标注构造时_style = DefaultDimensionStyle.clone()(全局默认样式,不是文档样式),setDefaults()也不吸文档样式;applyCurrentDocStyle()会吸文档样式但随即把scaleFactor覆盖成doc.DIMSCALE。内置标注命令就是setDefaults(); applyCurrentDocStyle();两步。
DimensionStyle是驼峰字段名(scaleFactor / textHeight / arrowSize / extensionLineOffset等);dimscale / dimtxt这类小写名只出现在DbDimensionStyle序列化层,不能直接对样式对象赋值(详 entity/dimension-mleader 第四节"标注样式")。
8.2 视图名标签
每个视图下方放一行 TextEnt,高度 ≥ 字高 × 1.5,至少 200 mm:
const t = new TextEnt();
t.insertionPoint = [600, -350];
t.text = '主视图 1:100';
t.height = 200;
t.styleName = 'simkai';
t.setDefaults();
视图名下还可加图框线(PolylineEnt 闭合矩形)做"题块"。
8.3 风机 / 风口等"占位部件"加细节
不要画一个空白矩形当风机——观图者会怀疑是图层背景。至少加:
| 元素 | 实体 | 数量 |
|---|---|---|
| 电机外圆 | CircleEnt |
1 |
| 电机轴轮毂 | CircleEnt |
1 |
| 4 条电机支架(45° 辐条) | LineEnt |
4 |
| 4 个角加强筋(角内 80×80 短线) | LineEnt |
4 |
| "风机" 文字标签 | TextEnt(高 150) |
1 |
const motor = new CircleEnt([cx, cy], 200);
const hub = new CircleEnt([cx, cy], 60);
for (let i = 0; i < 4; i++) {
const a = Math.PI / 4 + i * Math.PI / 2;
out.push(new LineEnt(
[cx + 60 * Math.cos(a), cy + 60 * Math.sin(a)],
[cx + 200 * Math.cos(a), cy + 200 * Math.sin(a)]));
}
8.4 比例题块单独画 + MText 锚点
不要把"比例 1:100"塞在 MText 末行——观图者扫不到。给比例单独画一个题块:
const scaleBox = new PolylineEnt();
scaleBox.setPoints([[6200, 800], [9200, 800], [9200, 1100], [6200, 1100]]);
scaleBox.isClosed = true;
scaleBox.setDefaults();
const scale = new TextEnt();
scale.insertionPoint = [6500, 880];
scale.text = '比例 1 : 100';
scale.height = 200;
scale.setDefaults();
MText 一定要设 textAttachment:
const mt = new MTextEnt();
mt.insertionPoint = [6200, 2400];
mt.textAttachment = 7; // 小键盘布局:7=TopLeft / 8=TopCenter / 9=TopRight / 4~6=Mid* / 1=BottomLeft ... 3=BottomRight
mt.text = '...';
mt.maxWidth = 3000; // 无 width 字段;maxWidth=文本框最大宽度
mt.height = 150;
mt.setDefaults();
| 锚点值 | 含义 | 插入点 = | 文本生长方向 |
|---|---|---|---|
7 |
TopLeft(默认) | 文本框左上角 | 向右下 ✓ 推荐 |
8 |
TopCenter | 顶边中心 | 向左右 / 向下 |
9 |
TopRight | 文本框右上角 | 向左下 |
5 |
MidCenter | 文本框中心 | 向四周溢出 |
1 |
BottomLeft | 文本框左下角 | 向右上 |
vjcad 默认值已是 7(TopLeft):显式赋值是防御性写法;注意 vjcad 按小键盘布局编号(MTextAttachmentEnum,与 TextEnt.textAlignment 同一布局),不是 DXF 的 1=TopLeft;真正危险的是误用 5(MidCenter)等居中锚点——文字会以插入点为中心向四周溢出,把临近视图盖住。见红旗 #14(详 entity/text-mtext 第三节)。
8.5 隐藏 / 中心 / 中实四线型在小屏 / 缩略图里很接近
为防"颜色相近、线型微差"在缩略图下糊成一团,靠图层颜色拉开主次:
| 用途 | 推荐 ACI | 理由 |
|---|---|---|
| OUTLINE | 7(主题色) | 最显眼 |
| OPENING | 3(亮绿) | 中度 |
| HIDDEN | 2(黄) | 在深背景中清晰 |
| CENTER | 4(青) | 与黄不撞 |
| DIM | 1(红) | 与几何拉开 |
8.6 用 bbox 自检视图相互不压叠
import { mergeBoundingBoxes } from 'vjcad';
function bboxOf(ents) {
const boxes = ents.map(e => e.boundingBox()).filter(b => b && (b.maxX - b.minX) > 0 && (b.maxY - b.minY) > 0);
// 注意:boundingBox() 返回 BoundingBox 实例(pt1/pt2/width/height/center/minPoint/maxPoint),也有 minX..maxY getter
return mergeBoundingBoxes(boxes);
}
const bFront = bboxOf(buildFrontView());
const bLeft = bboxOf(buildLeftView());
console.assert(bFront.maxX < bLeft.minX, '主视、左视水平不分离');
8.7 关键尺寸 / 直径用 MLeaderEnt 引出
零散 Φ 标注混在 LinearDimension 阵列里很难一眼定位;改用 MLeaderEnt 把"DN15 / DN600"引到图外空白区:
const ml = new MLeaderEnt();
ml.contentType = MLeaderContentType.MText;
ml.textContent = 'DN600';
ml.textPosition = new Point2D(2000, 2700);
ml.textHeight = 120;
ml.addLeaderLine(new Point2D(1000, 2500));
ml.setDefaults();
详 entity/dimension-mleader 第三节。
九、扩展挑战(本例不实现,留给读者)
| 想增 | 提示 |
|---|---|
| 图框 + 标题栏 | 用 BlockDefinition 定义 A1 图框块,InsertEnt 引入(详 entity/block-insert) |
| 比例 1:100 真缩放 | 几何画 1:1,scale([0,0], 0.01) 缩到纸上,标注用 scaleFactor = 100 反算(详 edit/rotate-scale) |
| 视图标记 / 剖切线 | 在主视上加 A-A 剖切箭头(SolidEnt 三角箭头 + LineEnt 剖切线 + TextEnt "A-A") |
| 不同墙厚 hatch | 在隐藏 / OPENING 矩形内加 HatchEnt + ANSI31(详 entity/hatch-solid) |
| DWG 导出 | 见 file/dwg-import-export |
参考(官方文档 MCP)
- 通用骨架:autodraw/overview
- 实体:entity/polyline、entity/line-arc-circle、entity/text-mtext、entity/dimension-mleader
- 图层 / 属性:layer/create-switch、property/color-linetype、property/set-defaults
- 编辑:edit/move-copy、edit/rotate-scale
- 撤销:undo/group
- 视图:view/zoom
- 字体 / 初始化:setup/fonts、setup/install-and-mcp
- 文档:
vjcad/doc/entity/polyline-ent.md、vjcad/doc/entity/circle-ent.md、vjcad/doc/entity/mtext-ent.md、vjcad/doc/command/dimension-commands.md、vjcad/doc/property/index.md、vjcad/doc/layer/layers.md、vjcad/doc/guide/quick-start.md - 示例:
vjcad/example/entity/all/01entities.js、vjcad/example/entity/polyline/02rectangle.js、vjcad/example/entity/circle/01create.js、vjcad/example/entity/text/02multiline.js、vjcad/example/entity/text/04fonts.js、vjcad/example/entity/dimension/01linear.js、vjcad/example/entity/dimension/06diametric.js、vjcad/example/property/03linetype.js、vjcad/example/property/06setdefaults.js、vjcad/example/layer/01create.js、vjcad/example/undo/03group.js、vjcad/example/comprehensive/01section.js