> **本文回答**：`LineEnt` 直线和 `PolylineEnt` 多段线 —— 创建、bulge 弧段、双线 dline、闭合、编辑。

## 一、LineEnt（直线段）

### 创建

```js
const line = new vjmapext.LineEnt([x1, y1], [x2, y2]);
line.color = 0xff0000;
line.lineWidth = 2;
mapcadLayer.addEntity(line);
```

### 关键属性

| 属性 | 类型 |
|---|---|
| `startPoint` | `Point2D` |
| `endPoint`   | `Point2D` |
| `midPoint`   | 只读 getter（**没有 `length`/`angle` 属性**，要自己算：`startPoint.distanceTo(endPoint)` / `Math.atan2(dy,dx)`） |

### 修改

```js
line.startPoint = new Point2D(10, 10);
line.endPoint.x = 200;
line.changed();
```

### 场景

- 网格参考线 / 坐标轴
- 连接点（配合 `PointEnt` 显示节点）
- 配合标注、引线、扉页

## 二、PolylineEnt（多段线）

### 创建

```js
// 折线
const poly = new vjmapext.PolylineEnt([
  [0, 0], [100, 0], [100, 100], [200, 100]
]);

// 闭合多边形：最后一个参数 true
const rect = new vjmapext.PolylineEnt([
  [0, 0], [100, 0], [100, 50], [0, 50]
], true);

rect.fillColor = 0x4488ff;
rect.fillOpacity = 0.3;
mapcadLayer.addEntity(rect);
```

### 关键属性

| 属性 | 说明 |
|---|---|
| `bulgePoints` | `BulgePoint[]` = `{point: Point2D, bulge: number}`——**顶点和 bulge 都在这**，没有独立 `points`/`bulges` 数组 |
| `isClosed` | 是否闭合（字段名是 `isClosed` 不是 `closed`） |
| `vertices` | 只读 getter，返回 `Point2D[]`——数组是新的，但 Point2D 是实体内部引用（改坐标会影响实体，改完仍需 `changed()`） |
| `addVertex(point, bulge?)` | 追加顶点（内部已调 `changed()`） |
| `fillColor` / `fillOpacity` / `borderOpacity` | 填充（闭合时生效），`fillColor=-1` 时用 `color` 填充 |
| `lineWidth` | 默认线宽（继承 EntityBase） |

**没有 `widths` 属性**——1.0.1 不支持逐段线宽。

### bulge 弧段（PLINE 精髓）

bulge = tan(θ/4)，θ 为弧段包含角。

| bulge | 弧形 |
|---|---|
| 0 | 直线 |
| 1 | 半圆（θ = 180°） |
| 0.414 | 四分之一圆 |
| -1 | 反向半圆 |

```js
const arcPoly = new vjmapext.PolylineEnt([
  [0, 0], [100, 0], [100, 100], [0, 100]
], true);

// 第 1、3 段带弧：bulge 写在每个顶点的 bulgePoints[i].bulge 上
[0.5, 0, -0.5, 0].forEach((b, i) => { arcPoly.bulgePoints[i].bulge = b; });
arcPoly.changed();
```

### bulge 弧段顶点

```js
const pl = new vjmapext.PolylineEnt([[0, 0], [100, 50]], true);
pl.bulgePoints = [
  { point: new vjmapext.Point2D(0, 0), bulge: 0.5 },
  { point: new vjmapext.Point2D(100, 50), bulge: 0 }
];
pl.changed();
```

`BulgePoint = { point: Point2D, bulge: number }`；`bulge` 为弧凸度（0 = 直线段）。

## 三、双线（DLINE 命令）

双线没有独立实体类，由 `DLINE` 命令或工具函数生成一对平行 `LineEnt`：

```js
mapcadLayer.executeCommand('DLINE');        // 交互式绘制（内部会问宽度）

// 程序化：中心线 + 宽度 → 两侧端点
// 方式一：自己算两侧端点，返回 { startPt1, startPt2, endPt1, endPt2 }
const pts = vjmapext.calculateDoubleLinePoints(
  new vjmapext.Point2D(0, 0), new vjmapext.Point2D(500, 0), 20
);
mapcadLayer.addEntity(new vjmapext.LineEnt(pts.startPt1, pts.endPt1));
mapcadLayer.addEntity(new vjmapext.LineEnt(pts.startPt2, pts.endPt2));

// 方式二：createDoubleLineSegment(startPt, endPt, width, prevLine1?, prevLine2?, miterLimit?)
// 返回 { line1, line2, bevelLine? }——传入前一段时拐角在 miterLimit 内用尖角，否则给斜角补边
const seg = vjmapext.createDoubleLineSegment(
  new vjmapext.Point2D(0, 0), new vjmapext.Point2D(500, 0), 20
);
mapcadLayer.addEntity(seg.line1);
mapcadLayer.addEntity(seg.line2);
if (seg.bevelLine) mapcadLayer.addEntity(seg.bevelLine);
// 端头封口：createDoubleButtCap / createDoubleSquareCap / createDoubleRoundCap；闭合用 closeDoubleLineSegment
```

## 四、生成常用形状

### 圆角矩形

```js
function roundRect(x, y, w, h, r) {
  const pts = [
    [x + r, y], [x + w - r, y],
    [x + w, y + r], [x + w, y + h - r],
    [x + w - r, y + h], [x + r, y + h],
    [x, y + h - r], [x, y + r]
  ];
  const poly = new vjmapext.PolylineEnt(pts, true);
  // 每条短边（角）加 0.414 的 bulge（bulge 在 bulgePoints[i].bulge 上）
  const bs = [0, 0.414, 0, 0.414, 0, 0.414, 0, 0.414];
  poly.bulgePoints.forEach((bp, i) => { if (bs[i] != null) bp.bulge = bs[i]; });
  return poly;
}
```

### 正多边形

```js
function regularPolygon(cx, cy, radius, sides) {
  const pts = [];
  for (let i = 0; i < sides; i++) {
    const a = (i / sides) * Math.PI * 2 - Math.PI / 2;
    pts.push([cx + radius * Math.cos(a), cy + radius * Math.sin(a)]);
  }
  return new vjmapext.PolylineEnt(pts, true);
}
```

## 五、查询几何

```js
poly.boundingBox();       // [minX, minY, maxX, maxY]——BBox 是 4 元素数组，不是 {min,max}

// 总长 / 面积 / 取点等 SDK 未直接提供，用 vertices 自算：
const vs = poly.vertices;
let len = 0;
for (let i = 1; i < vs.length; i++) len += vs[i].distanceTo(vs[i - 1]);
```

## 六、交互编辑

默认 `editable = true`，选中后显示夹点（`getGripPoints()` / `gripEdit()`）：

- **LineEnt**：`start`/`end` 端点夹点拖动改端点；`mid` 中点夹点拖动平移整条线
- **PolylineEnt**：顶点夹点（key 为索引 `0..n-1`）拖动改该顶点；`mid_i` 段中点夹点拖动**平移该段**（相邻两顶点一起动）

注意：夹点**没有**"添加顶点"和"调 bulge"的操作——增顶点用 `addVertex()`（或属性面板 vertex-list 的插入），调 bulge 直接改 `bulgePoints[i].bulge`。

禁用：

```js
line.editable = false;
```

详见 [drawing/grip](../drawing/grip.md)。

## 七、性能

| 场景 | 推荐 |
|---|---|
| < 1000 段的独立线段 | `LineEnt` 数组 |
| 连续折线（1 条 N 段） | `PolylineEnt` |
| 几十万级等值线 / 道路网 | 转 GeoJSON 走 [primitive/line](../../../vjmap/references/primitive/line.md) |
| 需要弧段 | `PolylineEnt` + `bulgePoints` |

## 八、常见坑

1. **PLINE 弧段 bulge 数量不对** → `bulgePoints` 长度 = 顶点数；第 i 项的 `bulge` 作用于"顶点 i → 顶点 i+1"段（闭合时末顶点段回首顶点）。
2. **把 `bulgePoints` 改成新数组/改了成员但没 `changed()`** → 改引用和改值都要 `changed()`（`addVertex()` 内部已调，除外）。
3. **以为不设 fillColor 就不填充** → 默认 `-1` 的语义是"跟随 `color` 填充"（默认 `fillOpacity=0.2`），不是不填充；想要别的填充色须显式设 `fillColor`。
4. **LineEnt 当多段线用** → 直线只有 2 端点；多点必须 `PolylineEnt`。
5. **期望 bulge 是弧度 / 角度** → 它是 tan(θ/4)，不是角。
