> **本文回答**：`SplineEnt` 样条曲线 —— 拟合点 / 控制点、阶数、闭合、用法。

## 一、构造（位置参数）

```js
// new SplineEnt(fitPts?, degree?) —— 拟合点是第一参
const spline = new vjmapext.SplineEnt(
  [[0,0], [50,100], [150,80], [200,0], [250,100]],
  3                      // degree，默认 3（拟合点模式下不影响形状，见第四节）
);
spline.color = 0xff4488;
spline.lineWidth = 2;
mapcadLayer.addEntity(spline);
```

## 二、属性（d.ts 真实成员）

| 属性 | 类型 | 说明 |
|---|---|---|
| `fitPoints` | `Point2D[]` | 拟合点（曲线穿过；按 Catmull-Rom 插值） |
| `controlPoints` | `Point2D[]` | B 样条控制点（多数不在曲线上；无权重/节点向量） |
| `degree` | `number` | 阶数，默认 3——**只在控制点模式生效**，拟合点模式忽略 |
| `isClosed` | `boolean` | 是否闭合（注意：不叫 `closed`） |
| `fillColor` / `fillOpacity` / `borderOpacity` | number | 闭合时填充 |
| `isFitMode` | getter | `fitPoints.length >= 2` 即为真——**fitPoints 优先，controlPoints 有值也会被忽略**（渲染/夹点/导出都走 activePoints） |

控制点模式：构造后改 `spline.controlPoints = [...]`（d.ts 无 knots/weights 暴露）。要走控制点模式须保证 `fitPoints.length < 2`。

> ⚠️ `controlPoints` 必须赋 **`Point2D` 实例数组**，不能赋裸 `[x,y]`——`clone()`/`move()`/`rotate()`/`scale()`/`mirror()` 对 `controlPoints` 无条件 `.map(p => p.clone())`，裸数组会抛 `p.clone is not a function`（`addEntity` 的 undo 快照路径必触发）。而构造器 `new SplineEnt(fitPts)` 接受裸数组（内部 `Ut()` 转 Point2D）。赋值写法：`s.controlPoints = pts.map(p => new Point2D(p[0], p[1]))`。

## 三、闭合样条

```js
const loop = new vjmapext.SplineEnt([[0,0],[100,80],[200,0],[150,-80],[50,-80]]);
loop.isClosed = true;
loop.fillColor = 0x4488ff;
mapcadLayer.addEntity(loop);
```

## 四、阶数（degree）何时生效

拟合点模式（构造传点，`fitPoints.length >= 2`）按 **Catmull-Rom** 插值渲染，**`degree` 不参与**——`new SplineEnt(pts, 5)` 与 `new SplineEnt(pts)` 画出来一样。`degree` 只在控制点模式（`fitPoints.length < 2`）下作 B 样条阶数（开口时自动夹到 `控制点数 - 1`）：

```js
const sp = new vjmapext.SplineEnt();          // 不传拟合点 → 控制点模式
sp.controlPoints = [[0,0],[50,200],[150,200],[200,0]].map(p => new vjmapext.Point2D(p[0], p[1]));
sp.degree = 2;                                // 此时 degree 才生效
mapcadLayer.addEntity(sp);
```

## 五、替代方案：贝塞尔

SplineEnt 不是严格 NURBS（拟合模式是 Catmull-Rom，控制点模式是无权重 B 样条）；若只要光滑曲线，也可用辅助函数生成 PolylineEnt：

```js
// polylineToBezierCurve 返回 BezierCurve 对象（控制点结构），
// 要再经 bezierCurveToPolyline 采样成点数组才能喂 PolylineEnt：
const curve = vjmap.polylineToBezierCurve([[0,0],[100,50],[200,0],[150,-50]]);
const smooth = new vjmapext.PolylineEnt(
  vjmap.bezierCurveToPolyline(curve, 0.5)   // precision?: number 采样精度
);
```

见 [primitive/circle-arc](../../../vjmap/references/primitive/circle-arc.md)。

## 六、与 Polyline 的差别

| 特性 | SplineEnt | PolylineEnt |
|---|---|---|
| 光滑 | 拟合模式 Catmull-Rom（C1 连续） | 端点有折角 |
| bulge 弧 | 不需要 | 通过 `bulgePoints` 近似 |
| 交互夹点 | 拟合点 / 控制点 | 顶点 |
| 导出 DWG | 保留 SPLINE 类型 | 转 PLINE |

## 七、编辑

选中后夹点出现在拟合点（或控制点），拖动修改局部形状：

```js
spline.fitPoints[1] = vjmapext.Point2D.fromArray([70, 150]);
spline.changed();
```

## 八、常见坑

1. **构造是位置参数**：`new SplineEnt(fitPts, degree)`——options 对象不会生效（同 TextEnt）。
2. **闭合属性是 `isClosed`**，不是 `closed`。
3. **以为改 degree 能调平滑度**：拟合点模式下 degree 被忽略（Catmull-Rom 固定）；只有控制点模式才用，且开口样条会自动夹到 `控制点数 - 1`。
4. **精确通过某点**：必须用 `fitPoints`；`controlPoints` 只决定形状。
5. **大量 Spline 很慢**：内部采样重建 Polyline；量大时直接生成 Polyline。
