多段线实体 (PolylineEnt)
大约 12 分钟
多段线实体 (PolylineEnt)
PolylineEnt 表示 CAD 中的多段线实体,由一系列连接的线段和弧段组成,支持闭合和凸度值。
在线示例
| 示例 | 描述 | 链接 |
|---|---|---|
| 创建多段线 | 简化接口用法 | 在线演示{target="_blank"} |
| 绘制矩形 | 使用简化接口创建闭合多段线 | 在线演示{target="_blank"} |
| 带圆弧多段线 | 使用简化接口设置凸度参数 | 在线演示{target="_blank"} |
| 多段线属性 | 展示多段线的各种属性和计算值 | 在线演示{target="_blank"} |
概述
多段线是 CAD 中最灵活的绘图元素之一,可以组合直线段和弧段形成复杂的轮廓。每个顶点可以有一个凸度值(bulge)来定义到下一顶点的弧段。
3D 坐标支持
多段线顶点的 Z 坐标存储在 BulgePoint.point2d.z 属性中。通过 point2d.z 访问和修改每个顶点的高程值。
凸度 (Bulge) 概念
凸度是多段线中表示弧段的关键参数:
bulge = tan(内角 / 4)| bulge 值 | 含义 |
|---|---|
| 0 | 直线段 |
| 1 | 逆时针半圆 |
| -1 | 顺时针半圆 |
| > 0 | 逆时针弧段 |
| < 0 | 顺时针弧段 |
构造函数
import { PolylineEnt, BulgePoints, BulgePoint } from 'vjcad';
// 方式一:使用 setPoints 批量设置(推荐)
const pline1 = new PolylineEnt();
pline1.setPoints([
[0, 0],
[100, 0],
[100, 50],
[50, 80],
[0, 50]
]);
// 方式二:链式调用 addPoint
const pline2 = new PolylineEnt();
pline2.addPoint([0, 0])
.addPoint([100, 0])
.addPoint([100, 50], -0.5); // 第二参数是凸度
// 方式三:带凸度的批量设置
const pline3 = new PolylineEnt();
pline3.setPoints([
[0, 0],
[100, 0],
[[100, 50], -0.5], // 格式: [[x, y], bulge]
[50, 80],
[0, 50]
]);
// 设置闭合
pline3.isClosed = true;简化写法
setPoints() 和 addPoint() 方法都支持 [x, y] 数组形式。带凸度时使用 [[x, y], bulge] 格式。
简化接口(推荐)
为了简化多段线的创建和操作,提供了更简洁的数组接口。
设置和添加点
import { PolylineEnt, Engine } from 'vjcad';
const pline = new PolylineEnt();
// 方式1: 批量设置点(推荐)
// 格式: [x, y] 表示坐标,[[x, y], bulge] 表示带凸度的点
pline.setPoints([
[0, 0], // 直线段
[100, 0], // 直线段
[[100, 50], -0.5], // 带凸度(顺时针弧)
[50, 80],
[0, 50]
]);
// 方式2: 链式添加单个点
pline.addPoint([0, 0])
.addPoint([100, 0])
.addPoint([100, 50], -0.5) // 第二参数是凸度
.addPoint([50, 80]);
// 方式3: 批量追加点
pline.addPoints([[200, 0], [200, 50], [[250, 80], 0.5]]);
pline.setDefaults();
Engine.addEntities(pline);获取点
const pline: PolylineEnt = /* ... */;
// 获取单点坐标
const [x, y] = pline.getPoint(0);
// 获取单点坐标和凸度
const [[px, py], bulge] = pline.getPointWithBulge(2);
// 获取所有点坐标(不含凸度)
const coords = pline.getPoints(); // [[0,0], [100,0], [100,50], ...]
// 获取所有点(含凸度,与 setPoints 格式一致)
const full = pline.getPointsWithBulge(); // [[0,0], [100,0], [[100,50], -0.5], ...]
// 复制到另一个多段线
const pline2 = new PolylineEnt();
pline2.setPoints(pline.getPointsWithBulge());简化接口 API 参考
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
addPoint(coord, bulge?) | [x, y], number | this | 添加单个点,支持链式调用 |
addPoints(points) | 见下方格式 | this | 批量追加点 |
setPoints(points) | 见下方格式 | this | 设置所有点(替换现有) |
getPoint(index) | number | [x, y] | 获取指定索引的点坐标 |
getPoints() | - | [x, y][] | 获取所有点坐标 |
getPointWithBulge(index) | number | [[x, y], bulge] | 获取点坐标和凸度 |
getPointsWithBulge() | - | 见下方格式 | 获取所有点(含凸度) |
点数组格式:
- 无凸度:
[x, y] - 有凸度:
[[x, y], bulge] - 混合:
[[0, 0], [100, 0], [[100, 50], -0.5], [50, 80]]
推荐使用简化接口
简化接口比传统的 BulgePoint + Point2D 方式更简洁,代码更易读。getPointsWithBulge() 的返回格式与 setPoints() 完全兼容,方便复制和序列化操作。
属性
基本属性
| 属性 | 类型 | 读写 | 说明 |
|---|---|---|---|
bulgePoints | BulgePoints | 读写 | 顶点集合(包含坐标和凸度) |
isClosed | boolean | 读写 | 是否闭合 |
globalWidth | number | 读写 | 全局线宽 |
elevation | number | 读写 | 整体高程(2D) |
计算属性(只读)
| 属性 | 类型 | 说明 |
|---|---|---|
length | number | 多段线总长度 |
area | number | 闭合多段线的面积 |
isCCW | boolean | 是否逆时针方向(闭合多段线) |
import { PolylineEnt } from 'vjcad';
// 使用简化接口创建矩形多段线
const pline = new PolylineEnt();
pline.setPoints([
[0, 0],
[100, 0],
[100, 100],
[0, 100]
]);
pline.isClosed = true;
console.log('顶点数:', pline.bulgePoints.length); // 4
console.log('是否闭合:', pline.isClosed); // true
console.log('长度:', pline.length); // 400
console.log('面积:', pline.area); // 10000
console.log('是否逆时针:', pline.isCCW); // true 或 false全局线宽 (globalWidth)
globalWidth 属性用于设置多段线的统一宽度。当设置大于 0 的值时,多段线会渲染为带有宽度的填充图形。
import { Engine, PolylineEnt } from 'vjcad';
// 创建带线宽的多段线
const pline = new PolylineEnt();
pline.setPoints([
[0, 0],
[100, 0],
[100, 100]
]);
pline.setDefaults();
// 设置全局线宽
pline.globalWidth = 10; // 宽度为 10 单位
Engine.addEntities(pline);
// 创建不同线宽的多段线对比
function drawPolylineWithWidths() {
const widths = [0, 5, 10, 20]; // 不同线宽
for (let i = 0; i < widths.length; i++) {
const pl = new PolylineEnt();
pl.setPoints([
[0, i * 50],
[200, i * 50]
]);
pl.setDefaults();
pl.globalWidth = widths[i];
Engine.addEntities(pl);
}
}
drawPolylineWithWidths();线宽注意事项
globalWidth = 0时,多段线渲染为无宽度的线条- 线宽会影响多段线的视觉效果和边界框计算
- 闭合多段线设置线宽后,会形成中空的封闭图形
BulgePoint 和 BulgePoints
BulgePoint - 带凸度的顶点
BulgePoint 类表示多段线中的一个顶点,包含坐标点和凸度值。
构造函数:
constructor(pointCoordinate: Point2D = new Point2D(), bulgeValue: number = 0)属性:
| 属性 | 类型 | 说明 |
|---|---|---|
point2d | Point2D | 顶点坐标 |
bulge | number | 凸度值 |
方法:
| 方法 | 返回类型 | 说明 |
|---|---|---|
clone() | BulgePoint | 克隆当前顶点 |
toDb() | object | 转换为数据库格式 |
fromDb(dbData) | this | 从数据库格式加载 |
import { BulgePoint, Point2D } from 'vjcad';
// 直线段顶点(bulge = 0)
const pt1 = new BulgePoint(new Point2D(0, 0), 0);
// 带凸度的顶点
const pt2 = new BulgePoint(new Point2D(100, 0), 0.5);
// 访问属性 - 通过 point2d 访问坐标
console.log('坐标:', pt2.point2d.x, pt2.point2d.y);
console.log('凸度:', pt2.bulge);
// 修改属性
pt2.bulge = 1; // 改为半圆
pt2.point2d.x = 150; // 修改 x 坐标
// 克隆
const pt3 = pt2.clone();BulgePoints - 顶点集合
BulgePoints 类用于管理多段线中的凸度点集合。
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
items | BulgePoint[] | 顶点数组 |
length | number | 只读,顶点数量 |
hasElevations | boolean | 只读,是否有高程数据(检查 point2d.z) |
方法:
| 方法 | 参数 | 说明 |
|---|---|---|
add(bulgePoint) | BulgePoint | 添加顶点(z 值在 point2d.z 中) |
removeLast() | - | 移除最后一个顶点 |
clone() | - | 克隆整个集合(包含 z 值) |
toDb() | - | 转换为数据库格式 |
fromDb(dbData) | object | 从数据库格式加载 |
import { BulgePoints, BulgePoint, Point2D } from 'vjcad';
const points = new BulgePoints();
// 添加顶点 - 使用 add() 方法
points.add(new BulgePoint(new Point2D(0, 0), 0));
points.add(new BulgePoint(new Point2D(100, 0), 0.5));
points.add(new BulgePoint(new Point2D(100, 100), 0));
// 添加带 Z 坐标的顶点
const pt3d = new BulgePoint(new Point2D(200, 100), 0);
pt3d.point2d.z = 50; // 设置 Z 坐标
points.add(pt3d);
// 访问顶点
console.log('顶点数:', points.length);
console.log('是否有高程:', points.hasElevations); // true
// 克隆(包含 z 值)
const pointsCopy = points.clone();
// 遍历 - 通过 point2d 访问坐标和 z 值
for (const pt of points.items) {
const z = pt.point2d.z !== undefined ? pt.point2d.z : 0;
console.log(`(${pt.point2d.x}, ${pt.point2d.y}, ${z}), bulge=${pt.bulge}`);
}
// 移除最后一个顶点
points.removeLast();方法
获取子实体
getSubEnts() - 获取线段和弧段实体
import { PolylineEnt, BulgePoints, BulgePoint, Point2D, LineEnt, ArcEnt } from 'vjcad';
const points = new BulgePoints();
points.add(new BulgePoint(new Point2D(0, 0), 0));
points.add(new BulgePoint(new Point2D(100, 0), 0.5)); // 弧段
points.add(new BulgePoint(new Point2D(100, 100), 0));
const pline = new PolylineEnt(points);
// 获取子实体(LineEnt 或 ArcEnt)
const subEnts = pline.getSubEnts();
for (const ent of subEnts) {
if (ent instanceof LineEnt) {
console.log('直线:', ent.startPoint, '->', ent.endPoint);
} else if (ent instanceof ArcEnt) {
console.log('圆弧:', ent.center, 'r=', ent.radius);
}
}getSubGeometries() - 获取几何对象(轻量级)
import { PolylineEnt } from 'vjcad';
const pline: PolylineEnt = /* ... */;
// 获取轻量级几何对象(比 getSubEnts 更高效)
const geometries = pline.getSubGeometries();
for (const geom of geometries) {
console.log('类型:', geom.type); // 'LINE' 或 'ARC'
}顶点操作
import { PolylineEnt, BulgePoints, BulgePoint, Point2D } from 'vjcad';
const pline = new PolylineEnt();
// 添加顶点 - 使用 add() 方法
pline.bulgePoints.add(new BulgePoint(new Point2D(0, 0), 0));
pline.bulgePoints.add(new BulgePoint(new Point2D(100, 0), 0));
pline.bulgePoints.add(new BulgePoint(new Point2D(100, 100), 0));
// 在指定位置插入顶点(直接操作 items 数组)
pline.bulgePoints.items.splice(1, 0, new BulgePoint(new Point2D(50, 50), 0));
// 删除顶点
pline.bulgePoints.items.splice(1, 1);
// 修改顶点坐标 - 通过 point2d 属性访问
pline.bulgePoints.items[0].point2d.x = 10;
pline.bulgePoints.items[0].point2d.y = 10;
// 修改凸度
pline.bulgePoints.items[1].bulge = 0.5;
// 移除最后一个顶点
pline.bulgePoints.removeLast();
// 通知修改
pline.setModified();几何变换
move() - 移动
import { PolylineEnt, Point2D } from 'vjcad';
const pline: PolylineEnt = /* ... */;
// 简化写法(推荐)
pline.move([0, 0], [50, 50]);rotate() - 旋转
import { PolylineEnt, Point2D } from 'vjcad';
const pline: PolylineEnt = /* ... */;
// 简化写法(推荐)
pline.rotate([0, 0], Math.PI / 4); // 绕原点旋转 45°scale() - 缩放
import { PolylineEnt, Point2D } from 'vjcad';
const pline: PolylineEnt = /* ... */;
// 简化写法(推荐)
pline.scale([0, 0], 2); // 以原点为中心放大 2 倍mirror() - 镜像
import { PolylineEnt, Point2D } from 'vjcad';
const pline: PolylineEnt = /* ... */;
// 简化写法(推荐)
pline.mirror([0, 0], [100, 0]); // 沿 X 轴镜像闭合操作
import { PolylineEnt, BulgePoints, BulgePoint, Point2D } from 'vjcad';
const points = new BulgePoints();
points.add(new BulgePoint(new Point2D(0, 0), 0));
points.add(new BulgePoint(new Point2D(100, 0), 0));
points.add(new BulgePoint(new Point2D(100, 100), 0));
points.add(new BulgePoint(new Point2D(0, 100), 0));
const pline = new PolylineEnt(points);
// 设置闭合
pline.isClosed = true;
// 闭合后,最后一个顶点会自动连接到第一个顶点
console.log('是否闭合:', pline.isClosed);
console.log('长度:', pline.length); // 包含闭合边的长度
console.log('面积:', pline.area); // 可以计算面积序列化
import { PolylineEnt, BulgePoints, BulgePoint, Point2D } from 'vjcad';
const points = new BulgePoints();
points.add(new BulgePoint(new Point2D(0, 0), 0));
points.add(new BulgePoint(new Point2D(100, 0), 0.5));
points.add(new BulgePoint(new Point2D(100, 100), 0));
const pline = new PolylineEnt(points);
pline.isClosed = true;
pline.setDefaults();
// 导出
const dbData = pline.toDb();
console.log(dbData);
// 导入
const newPline = new PolylineEnt();
newPline.fromDb(dbData);完整示例
绘制矩形
使用简化接口(推荐):
import { Engine, PolylineEnt } from 'vjcad';
function drawRectangle(x: number, y: number, width: number, height: number) {
const pline = new PolylineEnt();
pline.setPoints([
[x, y],
[x + width, y],
[x + width, y + height],
[x, y + height]
]);
pline.isClosed = true;
pline.setDefaults();
Engine.addEntities(pline);
return pline;
}
drawRectangle(0, 0, 100, 50);绘制圆角矩形
import { Engine, PolylineEnt } from 'vjcad';
function drawRoundedRect(
x: number, y: number,
width: number, height: number,
radius: number
) {
const r = Math.min(radius, width / 2, height / 2);
// 计算圆角的凸度(90°弧的凸度 ≈ 0.414)
const bulge = Math.tan(Math.PI / 8); // tan(22.5°)
const pline = new PolylineEnt();
pline.setPoints([
[x + r, y], // 右下角前
[[x + width - r, y], bulge], // 右下角(圆角开始)
[x + width, y + r], // 右下角后
[[x + width, y + height - r], bulge], // 右上角(圆角开始)
[x + width - r, y + height], // 右上角后
[[x + r, y + height], bulge], // 左上角(圆角开始)
[x, y + height - r], // 左上角后
[[x, y + r], bulge] // 左下角(圆角开始)
]);
pline.isClosed = true;
pline.setDefaults();
Engine.addEntities(pline);
return pline;
}
drawRoundedRect(0, 0, 200, 100, 20);绘制带弧段的多段线
import { Engine, PolylineEnt } from 'vjcad';
function drawPathWithArcs() {
const pline = new PolylineEnt();
// 使用简化接口:[[x, y], bulge] 表示带凸度的点
pline.setPoints([
[0, 0], // 起点,直线段
[[50, 0], 0.5], // 弧段起点,凸度0.5(逆时针)
[100, 50], // 弧段终点
[[100, 100], -0.5], // 弧段起点,凸度-0.5(顺时针)
[50, 150], // 弧段终点
[0, 150] // 终点
]);
pline.setDefaults();
Engine.addEntities(pline);
}
drawPathWithArcs();绘制正多边形
import { Engine, PolylineEnt } from 'vjcad';
function drawPolygon(centerX: number, centerY: number, radius: number, sides: number) {
const angleStep = (2 * Math.PI) / sides;
const points: [number, number][] = [];
for (let i = 0; i < sides; i++) {
const angle = i * angleStep - Math.PI / 2; // 从顶部开始
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
points.push([x, y]);
}
const pline = new PolylineEnt();
pline.setPoints(points);
pline.isClosed = true;
pline.setDefaults();
Engine.addEntities(pline);
return pline;
}
// 绘制正六边形
drawPolygon(100, 100, 50, 6);从实体创建多段线
import { Engine, PolylineEnt, BulgePoint, LineEnt, ArcEnt, Point2D } from 'vjcad';
// 从一系列直线和圆弧创建多段线
function entitiesToPolyline(entities: (LineEnt | ArcEnt)[]): PolylineEnt | null {
if (entities.length === 0) return null;
const pline = new PolylineEnt();
for (let i = 0; i < entities.length; i++) {
const ent = entities[i];
if (ent instanceof LineEnt) {
if (i === 0) {
pline.bulgePoints.add(new BulgePoint(
new Point2D(ent.startPoint.x, ent.startPoint.y), 0
));
}
pline.bulgePoints.add(new BulgePoint(
new Point2D(ent.endPoint.x, ent.endPoint.y), 0
));
} else if (ent instanceof ArcEnt) {
if (i === 0) {
pline.bulgePoints.add(new BulgePoint(
new Point2D(ent.startPoint.x, ent.startPoint.y),
ent.bulge
));
} else {
// 设置前一个顶点的凸度
const prevPt = pline.bulgePoints.items[pline.bulgePoints.items.length - 1];
prevPt.bulge = ent.bulge;
}
pline.bulgePoints.add(new BulgePoint(
new Point2D(ent.endPoint.x, ent.endPoint.y), 0
));
}
}
pline.setDefaults();
return pline;
}交互式绘制多段线
import {
Engine,
PolylineEnt,
BulgePoints,
BulgePoint,
Point2D,
PointInputOptions,
InputStatusEnum
} from 'vjcad';
async function drawPolylineCommand() {
const points = new BulgePoints();
let currentBulge = 0;
// 获取第一个点
const opt1 = new PointInputOptions("指定起点:");
const result1 = await Engine.getPoint(opt1);
if (result1.status !== InputStatusEnum.OK) return;
let lastPoint = result1.value as Point2D;
points.add(new BulgePoint(new Point2D(lastPoint.x, lastPoint.y), 0));
// 循环获取后续点
while (true) {
const opt = new PointInputOptions("指定下一点或 [弧段(A)/直线(L)/闭合(C)/放弃(U)]:");
opt.basePoint = lastPoint;
opt.useBasePoint = true;
opt.keywords = ["A", "L", "C", "U"];
const result = await Engine.getPoint(opt);
if (result.status === InputStatusEnum.CANCEL) {
break;
}
if (result.status === InputStatusEnum.KEYWORD) {
const keyword = result.stringResult?.toUpperCase();
if (keyword === "A") {
currentBulge = 0.5; // 设置为弧段模式
Engine.writeMessage("切换到弧段模式");
continue;
} else if (keyword === "L") {
currentBulge = 0; // 设置为直线模式
Engine.writeMessage("切换到直线模式");
continue;
} else if (keyword === "C") {
// 闭合多段线
if (points.items.length >= 3) {
const pline = new PolylineEnt(points);
pline.isClosed = true;
pline.setDefaults();
Engine.addEntities(pline);
Engine.writeMessage("多段线已闭合");
}
return;
} else if (keyword === "U" && points.items.length > 1) {
// 放弃上一个点
points.removeLast();
const lastPt = points.items[points.items.length - 1];
lastPoint = new Point2D(lastPt.point2d.x, lastPt.point2d.y);
continue;
}
}
if (result.status === InputStatusEnum.OK) {
const pt = result.value as Point2D;
// 设置前一个顶点的凸度
points.items[points.items.length - 1].bulge = currentBulge;
// 添加新顶点
points.add(new BulgePoint(new Point2D(pt.x, pt.y), 0));
lastPoint = pt;
}
}
// 创建多段线
if (points.items.length >= 2) {
const pline = new PolylineEnt(points);
pline.setDefaults();
Engine.addEntities(pline);
Engine.writeMessage(`多段线已创建,共 ${points.items.length} 个顶点`);
}
}常见问题
Q: 如何判断点是否在闭合多段线内部?
import { PolylineEnt, Point2D } from 'vjcad';
function isPointInPolyline(pline: PolylineEnt, point: Point2D): boolean {
if (!pline.isClosed) return false;
// 使用射线法判断
const vertices = pline.bulgePoints.items;
let inside = false;
for (let i = 0, j = vertices.length - 1; i < vertices.length; j = i++) {
const xi = vertices[i].point2d.x, yi = vertices[i].point2d.y;
const xj = vertices[j].point2d.x, yj = vertices[j].point2d.y;
if (((yi > point.y) !== (yj > point.y)) &&
(point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi)) {
inside = !inside;
}
}
return inside;
}Q: 如何获取多段线上指定距离处的点?
import { PolylineEnt, Point2D } from 'vjcad';
function getPointAtDistance(pline: PolylineEnt, distance: number): Point2D | null {
const subEnts = pline.getSubEnts();
let accumulatedDist = 0;
for (const ent of subEnts) {
const segmentLength = ent.Length || ent.length;
if (accumulatedDist + segmentLength >= distance) {
// 点在这个段上
const localDist = distance - accumulatedDist;
const ratio = localDist / segmentLength;
// 根据实体类型计算点
// ... 实现细节
}
accumulatedDist += segmentLength;
}
return null;
}Q: 如何简化多段线(减少顶点)?
import { PolylineEnt, BulgePoints, BulgePoint, Point2D } from 'vjcad';
// Douglas-Peucker 算法简化
function simplifyPolyline(pline: PolylineEnt, tolerance: number): PolylineEnt {
const points = pline.bulgePoints.items;
if (points.length <= 2) return pline.clone();
// 实现 Douglas-Peucker 算法
// ...
const simplified = new PolylineEnt();
// 添加简化后的点
return simplified;
}下一步
- 椭圆实体 (EllipseEnt) - 椭圆实体详解
- 样条曲线 (SplineEnt) - 样条曲线详解
- 填充实体 (HatchEnt) - 填充实体详解