valve
本文回答:DN15 内螺纹黄铜截止阀机加工图 = 主视剖视 + 侧视外形 双视图。把 autodraw/overview 五步骨架用在机械图上,重点示范:① 剖切区
HatchEnt用 polylinebulgePoints.clone()喂Edge;② 螺纹示意LineEnt循环;③ 中心线用lineType='CENTER';④ 视图间距通过绝对坐标分块(不是变换平移)。
一、需求拆解
生成一个 DN15 内螺纹黄铜截止阀阀体机加工图纸,并合理布局各视图(主视、侧视、剖视),标注关键尺寸。
DN15 = 公称通径 15 mm,内螺纹 G1/2"。简化后的关键尺寸:
| 参数 | 值 |
|---|---|
| 法兰对法兰长度 L | 65 |
| 阀体最大直径 D | 50 |
| 螺纹端长 Lt | 12 |
| 阀腔内径(DN) | 15 |
| 阀杆 + 手轮高 | 80(其中手轮 R = 25) |
| 自然语言要素 | 图层 | 实体 | 数量 |
|---|---|---|---|
| 阀体外形(剖面侧轮廓) | OUTLINE |
PolylineEnt(带 bulge) |
1 |
| 阀腔内壁(内孔) | OUTLINE |
PolylineEnt(带 bulge) |
1 |
| 阀体壁 Hatch | HATCH |
HatchEnt × 2(上 / 下壁) |
2 |
| 内螺纹示意 | THREAD |
LineEnt × 20(4 段 × 5 线) |
20 |
| 阀座 / 阀瓣 | OUTLINE |
PolylineEnt × 2 |
2 |
| 阀杆 | OUTLINE |
PolylineEnt(细矩形) |
1 |
| 阀盖(bonnet) | OUTLINE |
PolylineEnt 矩形 |
1 |
| 手轮 | OUTLINE |
CircleEnt × 2 + LineEnt × 4 |
6 |
| 中心线(水平) | CENTER |
LineEnt |
1 |
| 中心线(垂直) | CENTER |
LineEnt |
1 |
| 侧视图 外圆 | OUTLINE |
CircleEnt |
1 |
| 侧视图 内孔(DN15) | OUTLINE |
CircleEnt |
1 |
| 侧视图 中心十字线 | CENTER |
LineEnt × 2 |
2 |
| 标注 | DIM |
LinearDimensionEnt × 5 + DiametricDimensionEnt × 2 + MLeaderEnt(DN15) |
8 |
总实体 ≈ 50。
二、视图布局
主视-剖视 侧视-外形
[x: -45..45] [x: 175..225]
[y: -30..105] [y: -30..30]
原点(0,0) 原点(200,0)
视图间距 = 200 - 45 = 155 ≥ 阀体直径 50 + 100,符合 GB 视图布置规范。
三、生成步骤
flowchart LR
L[ensureLayers]
L --> M[buildMainView<br/>外形+内孔+Hatch+螺纹+阀杆+手轮+中心线]
M --> S[buildSideView<br/>外圆+内孔+十字中心线]
S --> D[buildDims<br/>5 Linear+2 Diametric+1 MLeader]
D --> C[commit<br/>addEntities+regen+zoomExtents]
四、完整可运行代码
import {
Engine, PolylineEnt, BulgePoint, Point2D, CircleEnt, LineEnt,
HatchEnt, Edge, Edges, EdgeType,
LinearDimensionEnt, DiametricDimensionEnt, MLeaderEnt, MLeaderContentType,
writeMessage
} from 'vjcad';
await cadView.onLoad();
drawValve();
function drawValve() {
Engine.startUndoRecord('AUTO_DRAW_VALVE_DN15');
try {
ensureValveLayers();
const ents = [];
ents.push(...buildMainView());
ents.push(...buildSideView({ ox: 200, oy: 0 }));
ents.push(...buildDims());
Engine.addEntities(ents);
writeMessage(`截止阀生成完成,共 ${ents.length} 个实体`);
} finally {
Engine.endUndoRecord();
}
Engine.regen();
Engine.zoomExtents();
}
function ensureValveLayers() {
const defs = [
{ name: 'OUTLINE', color: 7, lineType: 'CONTINUOUS' },
{ name: 'HATCH', color: 8, lineType: 'CONTINUOUS' },
{ name: 'THREAD', color: 6, lineType: 'CONTINUOUS' },
{ name: 'CENTER', color: 4, lineType: 'CENTER' },
{ name: 'DIM', color: 3, lineType: 'CONTINUOUS' }
];
for (const d of defs) {
if (!Engine.currentDoc.layers.itemByName(d.name)) {
Engine.createLayer(d.name, d);
}
}
}
4.1 主视图(剖视)
function buildMainView() {
const out = [];
Engine.setCurrentLayer('OUTLINE');
Engine.CECOLOR = 256;
Engine.CELTYPE = 'ByLayer';
const bodyOuter = buildBodyOuter();
const bodyInner = buildBodyInner();
out.push(bodyOuter, bodyInner);
out.push(...buildBonnetAndStem());
out.push(...buildHandwheel({ cx: 0, cy: 90, r: 25 }));
out.push(...buildSeatAndDisc());
Engine.setCurrentLayer('CENTER');
const cx = new LineEnt([-50, 0], [50, 0]); cx.setDefaults();
const cy = new LineEnt([0, -30], [0, 120]); cy.setDefaults();
out.push(cx, cy);
Engine.setCurrentLayer('THREAD');
out.push(...buildThreads({ x0: -32, x1: -20, yTop: 8, yBot: -8 }));
out.push(...buildThreads({ x0: 20, x1: 32, yTop: 8, yBot: -8 }));
Engine.setCurrentLayer('HATCH');
out.push(...buildBodyHatches(bodyOuter, bodyInner));
return out;
}
function buildBodyOuter() {
const b = Math.tan(Math.PI / 8); // 90° 圆角
const pl = new PolylineEnt();
pl.bulgePoints.add(new BulgePoint(new Point2D(-32, -11), 0));
pl.bulgePoints.add(new BulgePoint(new Point2D(-20, -11), 0));
pl.bulgePoints.add(new BulgePoint(new Point2D(-15, -25), b));
pl.bulgePoints.add(new BulgePoint(new Point2D( 15, -25), 0));
pl.bulgePoints.add(new BulgePoint(new Point2D( 20, -11), b));
pl.bulgePoints.add(new BulgePoint(new Point2D( 32, -11), 0));
pl.bulgePoints.add(new BulgePoint(new Point2D( 32, 11), 0));
pl.bulgePoints.add(new BulgePoint(new Point2D( 20, 11), 0));
pl.bulgePoints.add(new BulgePoint(new Point2D( 15, 25), b)); // 逆时针轮廓:外凸统一 +b
pl.bulgePoints.add(new BulgePoint(new Point2D( -15, 25), 0));
pl.bulgePoints.add(new BulgePoint(new Point2D(-20, 11), b));
pl.bulgePoints.add(new BulgePoint(new Point2D(-32, 11), 0));
pl.isClosed = true;
pl.setDefaults();
return pl;
}
function buildBodyInner() {
const pl = new PolylineEnt();
pl.setPoints([
[-32, -7.5], [-12, -7.5], [-7, -16], [7, -16], [12, -7.5], [32, -7.5],
[ 32, 7.5], [ 12, 7.5], [ 7, 16], [-7, 16], [-12, 7.5], [-32, 7.5]
]);
pl.isClosed = true;
pl.setDefaults();
return pl;
}
function buildBonnetAndStem() {
const out = [];
const bonnet = new PolylineEnt();
bonnet.setPoints([[-10, 25], [10, 25], [10, 40], [-10, 40]]);
bonnet.isClosed = true;
bonnet.setDefaults();
const stem = new PolylineEnt();
stem.setPoints([[-2, 18], [2, 18], [2, 65], [-2, 65]]);
stem.isClosed = true;
stem.setDefaults();
out.push(bonnet, stem);
return out;
}
function buildHandwheel({ cx, cy, r }) {
const out = [];
const rim = new CircleEnt([cx, cy], r);
const hub = new CircleEnt([cx, cy], 5);
rim.setDefaults(); hub.setDefaults();
out.push(rim, hub);
for (let i = 0; i < 4; i++) {
const a = (i * Math.PI) / 2;
const ln = new LineEnt(
[cx + 5 * Math.cos(a), cy + 5 * Math.sin(a)],
[cx + r * Math.cos(a), cy + r * Math.sin(a)]
);
ln.setDefaults();
out.push(ln);
}
return out;
}
function buildSeatAndDisc() {
const out = [];
const seat = new PolylineEnt();
seat.setPoints([[-7, -1], [7, -1], [7, 1], [-7, 1]]);
seat.isClosed = true;
seat.setDefaults();
const disc = new PolylineEnt();
disc.setPoints([[-5, 1], [5, 1], [3, 8], [-3, 8]]);
disc.isClosed = true;
disc.setDefaults();
out.push(seat, disc);
return out;
}
function buildThreads({ x0, x1, yTop, yBot }) {
const out = [];
const N = 5;
const dx = (x1 - x0) / N;
for (let i = 0; i <= N; i++) {
const x = x0 + i * dx;
const top = new LineEnt([x, yTop], [x + dx * 0.5, yTop - 1.5]);
const bot = new LineEnt([x, yBot], [x + dx * 0.5, yBot + 1.5]);
top.setDefaults(); bot.setDefaults();
out.push(top, bot);
}
return out;
}
function buildBodyHatches(outer, inner) {
const out = [];
const wallTop = new PolylineEnt();
wallTop.setPoints([[-32, 7.5], [32, 7.5], [32, 11], [-32, 11]]);
wallTop.isClosed = true;
wallTop.setDefaults();
const wallBot = new PolylineEnt();
wallBot.setPoints([[-32, -11], [32, -11], [32, -7.5], [-32, -7.5]]);
wallBot.isClosed = true;
wallBot.setDefaults();
for (const wall of [wallTop, wallBot]) {
const h = new HatchEnt();
h.patternName = 'ANSI31';
const e = new Edge();
e.edgeType = EdgeType.Polyline;
e.bulgePoints = wall.bulgePoints.clone();
const edges = new Edges();
edges.add(e);
h.setLoops(edges);
h.setDefaults();
out.push(wall, h);
}
return out;
}
4.2 侧视图
function buildSideView({ ox, oy }) {
const out = [];
Engine.setCurrentLayer('OUTLINE');
const outer = new CircleEnt([ox, oy], 25); // 阀体外径
const bore = new CircleEnt([ox, oy], 7.5); // DN15 内孔
outer.setDefaults(); bore.setDefaults();
out.push(outer, bore);
Engine.setCurrentLayer('CENTER');
const hLine = new LineEnt([ox - 32, oy], [ox + 32, oy]);
const vLine = new LineEnt([ox, oy - 32], [ox, oy + 32]);
hLine.setDefaults(); vLine.setDefaults();
out.push(hLine, vLine);
return out;
}
4.3 标注
function buildDims() {
Engine.setCurrentLayer('DIM');
const out = [];
const dimL = new LinearDimensionEnt(
new Point2D(-32, -25), new Point2D(32, -25), new Point2D(0, -45), 0);
dimL.setDefaults();
out.push(dimL);
const dimD = new LinearDimensionEnt(
new Point2D(0, -25), new Point2D(0, 25), new Point2D(-45, 0), Math.PI / 2);
dimD.setDefaults();
out.push(dimD);
const dimH = new LinearDimensionEnt(
new Point2D(0, 0), new Point2D(0, 115), new Point2D(60, 60), Math.PI / 2);
dimH.setDefaults();
out.push(dimH);
const dimT = new LinearDimensionEnt(
new Point2D(-32, 11), new Point2D(-20, 11), new Point2D(-26, 30), 0);
dimT.setDefaults();
out.push(dimT);
const dimW = new DiametricDimensionEnt(
new Point2D(-25, 90), new Point2D(25, 90));
dimW.setDefaults();
out.push(dimW);
const dimSideOuter = new DiametricDimensionEnt(
new Point2D(200 - 25, 0), new Point2D(200 + 25, 0));
dimSideOuter.setDefaults();
out.push(dimSideOuter);
const ml = new MLeaderEnt();
ml.contentType = MLeaderContentType.MText;
ml.textContent = 'G1/2" 内螺纹(DN15)';
ml.textPosition = new Point2D(-70, 25);
ml.textHeight = 4;
ml.addLeaderLine(new Point2D(-26, 8));
ml.setDefaults();
out.push(ml);
return out;
}
五、关键技术点
5.1 剖切区 Hatch:单回路 vs 多回路
vjcad 已验证示例(entity/hatch-solid)只展示单回路:1 个 Edge 进 1 个 Edges。本例阀体壁本应是"外轮廓 + 内孔"双回路;为避开未验证特性,拆成上 / 下两块矩形墙各自单回路——视觉效果几乎一致,且 100% 复用已知 API。
const e = new Edge();
e.edgeType = EdgeType.Polyline;
e.bulgePoints = wall.bulgePoints.clone(); // ⚠️ 必须 clone
5.2 螺纹示意:循环 + 短斜线
DN15 内螺纹用"齿尖" 5~7 段平行短斜线表示。每条斜线长度 ≈ 螺距/2,斜率 ≈ 螺纹半角。关键是单独建 THREAD 图层 / 颜色 6,否则与外轮廓粘成一团。
for (let i = 0; i <= N; i++) {
const x = x0 + i * dx;
out.push(new LineEnt([x, yTop], [x + dx * 0.5, yTop - 1.5]));
out.push(new LineEnt([x, yBot], [x + dx * 0.5, yBot + 1.5]));
}
5.3 中心线:图层 lineType='CENTER'
Engine.setCurrentLayer('CENTER');
const cx = new LineEnt([-50, 0], [50, 0]);
cx.setDefaults(); // ⬅ 自动吸 'CENTER' 层 → 应用 CENTER 线型
若图纸里
CENTER线型显示成实线,是因为该.dwg模板没加载 CENTER 线型;解决方案见 property/color-linetype。
5.4 视图间偏移:绝对坐标 vs 变换平移
| 方式 | 适用 |
|---|---|
| 绝对坐标分块(本例采用) | 视图坐标已知;最简,调试时便于直接量 |
entity.move([0,0],[dx,dy]) 批平移 |
视图先在原点构造,最后整组挪到目标位置;适合复用同样的"构造函数" |
本例侧视图的 buildSideView({ ox, oy }) 形参化使两种风格都能切换:传 { ox: 200, oy: 0 } 是绝对坐标;想改用变换平移就把 ox/oy = 0 然后对返回的实体数组循环 e.move([0,0],[200,0])。
5.5 弧 / 圆角的 bulge 符号
bulge 正负 = 弧的旋转方向(>0 逆时针,<0 顺时针,详见 entity/polyline 第二节),换成几何直觉:凸度 > 0 时弧向行进方向右侧鼓出(ArcEnt.initForPlineArc / HatchEnt 离散化同此约定)。外轮廓 buildBodyOuter 按逆时针遍历(下边左→右、上边右→左),行进方向右侧恰好是轮廓外侧,所以要整体外凸就各段统一用 +b;上半用 -b 会让弧向内凹。顺时针遍历的轮廓则相反,统一用 -b。
六、标注布局规则
| 标注 | 位置 | step |
|---|---|---|
| 总长 65 | 阀体下方 y=-45 | 主基线 |
| 阀体高 50 | 阀体左侧 x=-45 | 主基线 |
| 总高 115 | 整图右侧 x=60 | 与阀体高错开 ≥ 30 mm |
| 螺纹长 12 | 阀体上方 y=30,靠左 | 局部尺寸单独偏移 |
| 手轮 Φ50 | 手轮平面线 | 直径标注 |
| 侧视外径 Φ50 | 侧视图中线 | 直径标注 |
| DN15 引线 | x=-70 | MLeader 单独引出 |
字高默认 5;多条平行标注
step ≥ 字高 × 2 = 10防止文字与尺寸线重叠。
七、红旗 / 反例
- Hatch 边界用了"外轮廓 PolylineEnt"自身
bulgePoints,没clone():阀体被错误打剖;源 polyline 与 hatch 边界共享后修改 polyline 直接影响 hatch。 - 螺纹画在 OUTLINE 层:与阀体外轮廓粘连,分不清;必须
setCurrentLayer('THREAD')。 - 侧视图忘了偏移 / 写在原点:直接盖住主视图。先确认
ox大于主视图右边界(本例主视右边 32,故 ox=200 远超)。 - bulge 符号按"上半/下半"分正负:
buildBodyOuter是逆时针轮廓,上半写成-b会让上边的弧向 X 轴中心凹,上下不对称、像被捏了一下的"沙漏"——逆时针轮廓要外凸就全用+b(凸度 > 0 = 向行进方向右侧鼓出 = 逆时针轮廓的外侧)。 - Φ 标注用
LinearDimensionEnt:得到的是数值(弦长),不是 Φ 前缀。直径必须DiametricDimensionEnt(p1, p2),p1/p2 是直径两端点。 - 手轮辐条 i 从 0 开始 + 间隔 π/2:得到 4 条十字辐条,OK;若 N 改 6 要把
(i * Math.PI) / 2改成(2 * Math.PI * i) / N。 MLeaderEnt漏addLeaderLine:只有文字飘在画面上,没箭头指到螺纹。
八、扩展挑战(本例不实现,留给读者)
| 想增 | 提示 |
|---|---|
| 表面粗糙度符号 ▽ | 用 BlockDefinition 定义符号块,InsertEnt 复用(entity/block-insert) |
| 几何公差框 ⌖ | 块 + 块属性(attribute),vjcad/example/entity/block/04attribute.js |
| 俯视图 | 与侧视图同模板;按法兰孔做圆形阵列(详 autodraw/overview 第五节) |
| 比例缩放 1:2 / 1:5 | 整体 scale([0,0], 0.5) 后再标注;标注样式 scaleFactor 对应放大 |
参考(官方文档 MCP)
- 通用骨架:autodraw/overview
- 实体:entity/polyline、entity/line-arc-circle、entity/hatch-solid、entity/dimension-mleader、entity/block-insert
- 图层 / 属性:layer/create-switch、property/color-linetype、property/set-defaults
- 编辑:edit/move-copy
- 撤销:undo/group
- 视图:view/zoom
- 文档:
vjcad/doc/entity/polyline-ent.md、vjcad/doc/entity/circle-ent.md、vjcad/doc/entity/hatch-ent.md、vjcad/doc/command/dimension-commands.md、vjcad/doc/entity/index.md、vjcad/doc/property/index.md、vjcad/doc/layer/layers.md - 示例:
vjcad/example/entity/all/01entities.js、vjcad/example/entity/polyline/02rectangle.js、vjcad/example/entity/hatch/01create.js、vjcad/example/entity/hatch/03boundary.js、vjcad/example/entity/dimension/01linear.js、vjcad/example/entity/dimension/06diametric.js、vjcad/example/entity/mleader/01create.js、vjcad/example/property/03linetype.js、vjcad/example/layer/01create.js、vjcad/example/comprehensive/01section.js、vjcad/example/undo/03group.js