唯杰地图 logo唯杰地图
VJMAP
EN

valve

vjcad/references/autodraw/examples/valve.md 16.5 KB423 行
原文

本文回答:DN15 内螺纹黄铜截止阀机加工图 = 主视剖视 + 侧视外形 双视图。把 autodraw/overview 五步骨架用在机械图上,重点示范:① 剖切区 HatchEnt 用 polyline bulgePoints.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 防止文字与尺寸线重叠。

七、红旗 / 反例

  1. Hatch 边界用了"外轮廓 PolylineEnt"自身 bulgePoints,没 clone():阀体被错误打剖;源 polyline 与 hatch 边界共享后修改 polyline 直接影响 hatch。
  2. 螺纹画在 OUTLINE 层:与阀体外轮廓粘连,分不清;必须 setCurrentLayer('THREAD')。
  3. 侧视图忘了偏移 / 写在原点:直接盖住主视图。先确认 ox 大于主视图右边界(本例主视右边 32,故 ox=200 远超)。
  4. bulge 符号按"上半/下半"分正负:buildBodyOuter 是逆时针轮廓,上半写成 -b 会让上边的弧向 X 轴中心凹,上下不对称、像被捏了一下的"沙漏"——逆时针轮廓要外凸就全用 +b(凸度 > 0 = 向行进方向右侧鼓出 = 逆时针轮廓的外侧)。
  5. Φ 标注用 LinearDimensionEnt:得到的是数值(弦长),不是 Φ 前缀。直径必须 DiametricDimensionEnt(p1, p2),p1/p2 是直径两端点。
  6. 手轮辐条 i 从 0 开始 + 间隔 π/2:得到 4 条十字辐条,OK;若 N 改 6 要把 (i * Math.PI) / 2 改成 (2 * Math.PI * i) / N。
  7. 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)