# 进阶实体

标注、填充、块插入等高级实体类型。

## HatchEnt - 填充

```javascript
const { HatchEnt, PolylineEnt, Edge, Edges, EdgeType, Engine } = vjcad;

// 1. 创建边界（闭合多段线）
const boundary = new PolylineEnt();
boundary.setPoints([
    [0, 0],
    [100, 0],
    [100, 60],
    [0, 60]
]);
boundary.isClosed = true;
boundary.setDefaults();

// 2. 创建填充
const hatch = new HatchEnt();
hatch.patternName = "ANSI31";  // 图案名称
hatch.patternScale = 1.0;      // 图案比例
hatch.patternAngle = 0;        // 图案角度

// 3. 设置边界
const edge = new Edge();
edge.edgeType = EdgeType.Polyline;
edge.bulgePoints = boundary.bulgePoints.clone();

const edges = new Edges();
edges.add(edge);
hatch.setLoops(edges);

hatch.setDefaults();
hatch.color = 4;
Engine.addEntities([boundary, hatch]);
```

**常用填充图案**：
| 图案名 | 说明 |
|--------|------|
| `SOLID` | 实心填充 |
| `ANSI31` | 45° 单斜线（铁/砖石）|
| `ANSI32` | 45° 双线组（钢）|
| `ANSI33` | 45° 实线+虚线交替（青铜/黄铜）|
| `ANSI34` | 45° 四线组（塑料/橡胶）|
| `ANSI37` | 45°/135° 交叉斜线 |

**属性**：
- `patternName` - 图案名称
- `patternScale` - 图案比例
- `patternAngle` - 图案角度
- `color` - 填充颜色

---

## 标注实体

### LinearDimensionEnt - 线性标注

```javascript
const { LinearDimensionEnt, Point2D, Engine } = vjcad;

const dim = new LinearDimensionEnt(
    new Point2D(0, 0),     // startPoint: 标注起点
    new Point2D(100, 0),   // endPoint: 标注终点
    new Point2D(50, 20),   // thirdPoint: 标注线位置
    0                      // dimAngle: 0=水平, PI/2=垂直
);
dim.setDefaults();
dim.textHeight = 5;
dim.arrowSize = 3;
dim.textOverride = "L=<>";  // <> 会被替换为测量值
dim.color = 3;
Engine.addEntities(dim);
```

### AlignedDimensionEnt - 对齐标注

```javascript
const { AlignedDimensionEnt, Point2D, Engine } = vjcad;

const dim = new AlignedDimensionEnt(
    new Point2D(0, 0),     // startPoint
    new Point2D(100, 50),  // endPoint
    new Point2D(50, 40)    // thirdPoint
);
dim.setDefaults();
Engine.addEntities(dim);
```

### RadialDimensionEnt - 半径标注

```javascript
const { RadialDimensionEnt, Point2D, CircleEnt, Engine } = vjcad;

// 先创建圆
const circle = new CircleEnt([50, 50], 30);
circle.setDefaults();
Engine.addEntities(circle);

// 创建半径标注
const dim = new RadialDimensionEnt(
    new Point2D(50, 50),   // center: 圆心
    new Point2D(80, 50),   // chordPoint: 圆周点
    10                     // leaderLength: 引线延伸长度
);
dim.setDefaults();
Engine.addEntities(dim);
```

### DiametricDimensionEnt - 直径标注

```javascript
const { DiametricDimensionEnt, Point2D, Engine } = vjcad;

const dim = new DiametricDimensionEnt(
    new Point2D(20, 50),   // chordPoint1: 直径端点1
    new Point2D(80, 50),   // chordPoint2: 直径端点2
    10                     // leaderLength
);
dim.setDefaults();
Engine.addEntities(dim);
```

### AngleDimensionEnt - 角度标注

```javascript
const { AngleDimensionEnt, Point2D, Engine } = vjcad;

const dim = new AngleDimensionEnt(
    new Point2D(0, 0),     // center: 角度顶点
    new Point2D(100, 0),   // xLine1Point: 第一条边上的点
    new Point2D(50, 80),   // xLine2Point: 第二条边上的点
    new Point2D(60, 40)    // arcPoint: 标注弧位置
);
dim.setDefaults();
Engine.addEntities(dim);
```

### ArcDimensionEnt - 弧长标注

```javascript
const { ArcDimensionEnt, Point2D, Engine } = vjcad;

const dim = new ArcDimensionEnt(
    new Point2D(50, 50),   // center
    new Point2D(80, 50),   // xLine1Point: 圆弧起点
    new Point2D(50, 80),   // xLine2Point: 圆弧终点
    new Point2D(70, 70)    // arcPoint: 标注位置
);
dim.setDefaults();
Engine.addEntities(dim);
```

### OrdinateDimensionEnt - 坐标标注

```javascript
const { OrdinateDimensionEnt, Point2D, Engine } = vjcad;

const dim = new OrdinateDimensionEnt(
    new Point2D(0, 0),     // origin: 坐标原点（首参，不能省）
    new Point2D(100, 50),  // definingPoint: 被标注点
    new Point2D(120, 70),  // leaderEndPoint: 引线终点
    true                   // displayModeOrUseXAxis: true=X坐标, false=Y坐标
);
dim.setDefaults();
Engine.addEntities(dim);
```

**标注通用属性**：
| 属性 | 说明 |
|------|------|
| `textHeight` | 文字高度 |
| `arrowSize` | 箭头大小 |
| `textOverride` | 文字覆盖（`<>` 被替换为测量值）|
| `scaleFactor` | 缩放因子 |
| `decimalPlaces` | 小数位数 |

**标注方法**：
- `getMeasurement()` - 获取测量值
- `getFormattedMeasurement()` - 获取格式化后的文本

---

## 关联标注

标注可以关联到源实体，源实体移动时标注自动更新。

```javascript
const { LinearDimensionEnt, LineEnt, Point2D, Engine } = vjcad;

// 创建直线
const line = new LineEnt([0, 0], [100, 0]);
line.setDefaults();
Engine.addEntities(line);

// 创建标注
const dim = new LinearDimensionEnt(
    new Point2D(0, 0),
    new Point2D(100, 0),
    new Point2D(50, 20),
    0
);
dim.setDefaults();
Engine.addEntities(dim);

// 建立关联（必须在 addEntities 之后）
dim.setSourceEntities(
    line.id, -1,    // 起点关联实体 + 段索引：LINE 忽略段索引（填 -1 即可）；PLINE 须填 ≥0 的直线段索引（-1/弧段取不到点，标注不跟随）
    line.id, -1,    // 终点关联实体
    'start',        // 起点端点类型
    'end'           // 终点端点类型
);

// 检查关联状态
console.log("isAssociative:", dim.isAssociative);
```

**端点类型**：
- `'start'` - 起点
- `'end'` - 终点
- `'center'` - 类型里有但**未实现**（Linear/Aligned 关联落到默认分支取起点）
- `'midpoint'` - 中点

**精确定位**：
```javascript
// 使用 parameterT（0~1 表示在线段上的比例位置）
dim.setSourceEntities(
    line.id, -1,
    line.id, -1,
    undefined,       // 不使用 pointType
    undefined,
    0.25,            // startParameterT: 1/4 处
    0.75             // endParameterT: 3/4 处
);
```

---

## InsertEnt - 块插入

### 创建块定义

```javascript
const { LineEnt, CircleEnt, Engine } = vjcad;

const doc = Engine.currentDoc;
const blockName = "MySymbol";

// 检查是否已存在
let blockDef = doc.blocks.itemByName(blockName);
if (!blockDef) {
    // 创建块定义
    blockDef = doc.blocks.add(blockName);
    blockDef.basePoint = [0, 0];  // 基点
    
    // 添加块内实体
    const line1 = new LineEnt([-10, 0], [10, 0]);
    const line2 = new LineEnt([0, -10], [0, 10]);
    const circle = new CircleEnt([0, 0], 5);
    
    line1.setDefaults();
    line2.setDefaults();
    circle.setDefaults();
    
    blockDef.addEntity(line1);
    blockDef.addEntity(line2);
    blockDef.addEntity(circle);
}
```

### 插入块引用

```javascript
const { InsertEnt, Engine } = vjcad;

const insert = new InsertEnt();
insert.blockId = blockDef.blockId;     // 块ID（必须）
insert.insertionPoint = [50, 50];      // 插入点
insert.scaleFactor = 1.5;              // 缩放因子
insert.rotation = Math.PI / 4;         // 旋转角度（弧度）
insert.setDefaults();
insert.color = 5;
Engine.addEntities(insert);
```

### 炸开块

```javascript
// getNestEnts(forceUpdate) 的参数是"强制重算缓存"，不是"炸开"；返回的是 insert 内部缓存数组
// （已按插入点/比例/旋转变换到世界坐标）——加入图纸前先 clone，别把缓存对象本身加进去
const explodedEnts = insert.getNestEnts(true).map(ent => ent.clone());
Engine.addEntities(explodedEnts);
Engine.eraseEntities(insert);
// 与 EXPLODE 命令的差别：命令还会把 0 图层 / ByBlock(color 0) 的子实体解析为继承插入块的图层/颜色，手写时需自行处理
```

**属性**：
- `blockId` - 块ID（必须，从 blockDef.blockId 获取）
- `insertionPoint` - 插入点
- `scaleFactor` - 缩放因子
- `rotation` - 旋转角度（弧度）

**方法**：
- `getNestEnts(forceUpdate = false)` - 获取（变换到世界坐标的）嵌套实体缓存；true 强制重算，返回内部数组，炸开时需 clone

---

## 符号管理

### 从本地存储加载符号

```javascript
const { 
    getLocalStorageService, 
    parseSymbolVcadData, 
    mergeSymbolToDocument,
    Point2D, Engine 
} = vjcad;

const localService = getLocalStorageService();

// 获取分类和符号列表
const categories = await localService.getLocalSymbolCategories();
const symbols = await localService.getLocalSymbolList(categories[0].id);
const symbol = await localService.getLocalSymbol(symbols[0].id);

// 解析符号数据
const symbolDoc = await parseSymbolVcadData(symbol.vcadData);

// 合并到文档（getLocalSymbol 返回 {record, vcadData}，basePoint 在 record 上）
const addedEntities = mergeSymbolToDocument(
    symbolDoc,
    new Point2D(100, 100),   // insertPoint
    symbol.record.basePoint,  // basePoint: [x, y]
    1.5,                      // scale
    45,                       // rotation (度)
    false                     // clearSourceInfo
);

Engine.zoomExtents();
```

### 交互式插入符号

```javascript
const { insertSymbolInteractive } = vjcad;

const entities = await insertSymbolInteractive(
    symbolData,
    basePoint,
    {
        allowScale: true,      // 允许指定缩放
        allowRotation: true,   // 允许指定旋转
        clearSourceInfo: false
    }
);
```

### 创建本地符号

```javascript
const { 
    getLocalStorageService,
    buildSymbolVcadData,
    generateThumbnailFromEntities
} = vjcad;

const localService = getLocalStorageService();

// 创建分类
const category = await localService.createLocalSymbolCategory("我的符号");

// 创建符号内容
const entities = [line1, circle1];

// 生成符号数据
const vcadData = await buildSymbolVcadData(entities, false);
const thumbnail = await generateThumbnailFromEntities(entities, 128, 128);

// 保存符号
const symbol = await localService.saveLocalSymbol({
    categoryId: category.id,
    name: "十字符号",
    basePoint: [0, 0],
    thumbnail: thumbnail,
    vcadData: vcadData
});
```

---

## SplineEnt 进阶 - 拟合点与参数化

### 拟合点 vs 控制点

```javascript
const { SplineEnt, Engine } = vjcad;

const points = [[0, 0], [50, 80], [100, 20], [150, 90], [200, 40]];

// 拟合点：曲线精确经过每个点
const fit = new SplineEnt();
fit.setFitPoints(points);
fit.setDefaults();
fit.color = 1;

// 控制点：点"拉动"曲线但不经过
const ctrl = new SplineEnt();
ctrl.setControlPoints(points);
ctrl.setDefaults();
ctrl.color = 3;

Engine.addEntities([fit, ctrl]);  // 第二参是 options 不是第二个实体，必须传数组
```

### 参数化方式

| 方式 | 说明 | 适用场景 |
|------|------|----------|
| `'chord'` | 弦长参数化（默认） | 通用场景 |
| `'sqrtChord'` | 弦长平方根（向心） | 急转弯、密集点 |
| `'uniform'` | 均匀参数化 | 等距分布点 |

```javascript
// 向心参数化
spline.setFitPoints(points, { parameterization: 'sqrtChord' });
```

### 端点切线控制

```javascript
spline.setFitPoints(points, {
    startTangent: [1, 0],   // 起点水平向右
    endTangent: [0, 1]      // 终点竖直向上
});
```

不指定切线时使用自然端条件（端点二阶导数为零）。

### 拟合点 API 速查

| 方法 | 说明 |
|------|------|
| `setFitPoints(pts, opts?)` | 设置拟合点，自动计算控制点和节点向量 |
| `addFitPoint(pt, opts?)` | 追加一个拟合点并重新计算 ⚠️ 内部是"读当前 fitPoints→push→setFitPoints"，而 setFitPoints 的插值求解器在**拟合点 <4 个时静默 return**——从空样条逐点 `addFitPoint` 永远不积累（每次求解失败、读回仍为空）。要逐点加需先 `setFitPoints`/`addFitPoints` 铺够 ≥4 个点 |
| `addFitPoints(pts, opts?)` | 批量追加拟合点（一次给 ≥4 个即可通过求解器） |
| `getFitPoint(i)` | 获取第 i 个拟合点 `[x, y]` |
| `getFitPoints()` | 获取所有拟合点 `[x, y][]` |

### 控制点 API 速查

| 方法 | 说明 |
|------|------|
| `setControlPoints(pts)` | 设置控制点，支持 `[x,y]` 或 `[[x,y], weight]` |
| `addControlPoint(pt, w?)` | 追加一个控制点（链式调用） |
| `addControlPoints(pts)` | 批量追加控制点 |
| `getControlPoint(i)` | 获取第 i 个控制点 `[x, y]` |
| `getControlPoints()` | 获取所有控制点 `[x, y][]` |
| `getControlPointsWithWeight()` | 获取含权重的控制点 |

### 底层插值算法

```javascript
const { fitPointsToControlPoints } = vjcad;

const result = fitPointsToControlPoints(
    [[0,0], [50,100], [100,50], [150,100], [200,0]],
    { parameterization: 'chord' }
);
// result.controlPoints — 计算出的控制点
// result.knots — 对应的节点向量
```

---

## MLeaderEnt - 多重引线

```javascript
const { MLeaderEnt, Point2D, Engine } = vjcad;

const mleader = new MLeaderEnt();
mleader.setDefaults();
mleader.textContent = "标注内容";  // 注意是 textContent 不是 text
// 文字位置用 textPosition 设置（默认 (0,0)），且要在 addLeaderLine 之前设——基线朝向据此计算
mleader.textPosition = new Point2D(100, 50);
mleader.textHeight = 5;
mleader.arrowSize = 3;
// 引线顶点用 addLeaderLine + addLastVertex 构建（无 leaderPoints 数组属性）
const lineIdx = mleader.addLeaderLine(new Point2D(0, 0)); // 箭头位置
mleader.addLastVertex(lineIdx, new Point2D(50, 50));      // 中间拐点（addLastVertex 只加顶点，不决定文字位置）
Engine.addEntities(mleader);
```

---

## GroupEnt - 组

```javascript
const { GroupEnt, LineEnt, CircleEnt, Engine } = vjcad;

// 创建实体
const line = new LineEnt([0, 0], [100, 0]);
const circle = new CircleEnt([50, 50], 20);
line.setDefaults();
circle.setDefaults();

// 创建组
const group = new GroupEnt("MyGroup", [line, circle]);
group.setDefaults();
Engine.addEntities(group);

// 访问组内实体（items 是数组属性）
group.items.forEach((item, index) => {
    console.log(`${index}: ${item.type}`);
});

// 炸开组
const explodedEntities = group.explode();
explodedEntities.forEach(ent => {
    Engine.addEntities(ent);
});
Engine.eraseEntities(group);
```
