# 填充图案

填充实体（HatchEnt）的完整使用指南，包含内置图案、自定义图案、边界创建。

## 概览

WebCAD 内置 80+ 种填充图案，涵盖 ANSI、ISO、JIS 标准以及各种材质图案。

---

## 基本用法

### 创建填充

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

// 1. 创建闭合边界
const boundary = new PolylineEnt();
boundary.addVertex([0, 0]);
boundary.addVertex([100, 0]);
boundary.addVertex([100, 60]);
boundary.addVertex([0, 60]);
boundary.isClosed = true;
boundary.setDefaults();

// 2. 创建填充实体
const hatch = new HatchEnt();
hatch.patternName = "SOLID";  // 图案名称
hatch.patternScale = 1;       // 图案比例

// 3. 从多段线创建边界
const edge = new Edge();
edge.edgeType = EdgeType.Polyline;
edge.bulgePoints = boundary.bulgePoints.clone();

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

// 4. 设置边界
hatch.setLoops(edges);
hatch.setDefaults();
hatch.color = 3;  // 绿色

// 5. 添加到画布
Engine.addEntities([boundary, hatch]);
Engine.zoomExtents();
```

### 填充属性

| 属性 | 说明 |
|------|------|
| `patternName` | 图案名称（如 "SOLID", "ANSI31"）|
| `patternScale` | 图案比例（默认 1）|
| `patternAngle` | 图案角度（**弧度**，与其他实体 rotate 一致；`Math.PI/4` = 45°）|
| `color` | 填充颜色 |

---

## 内置图案

### 获取所有图案名称

```javascript
const patternManager = Engine.patternManager;
const allPatternNames = patternManager.getAllPatternNames();
console.log(`共有 ${allPatternNames.length} 个填充图案`);
```

### 实心填充

| 图案名 | 说明 |
|--------|------|
| `SOLID` | 实心填充 |

### ANSI 图案（美国标准）

| 图案名 | 说明 |
|--------|------|
| `ANSI31` | 铁、砖和石 (45° 斜线) |
| `ANSI32` | 钢 |
| `ANSI33` | 青铜、黄铜和紫铜 |
| `ANSI34` | 塑料和橡胶 |
| `ANSI35` | 耐火砖和耐火材质 |
| `ANSI36` | 大理石、板岩和玻璃 |
| `ANSI37` | 铅、锌、镁和绝缘体 (交叉斜线) |
| `ANSI38` | 铝 |

### AR 图案（建筑）

| 图案名 | 说明 |
|--------|------|
| `AR-B816` | 8x16 块砖顺砌 |
| `AR-B816C` | 8x16 块砖顺砌（带灰泥接缝）|
| `AR-B88` | 8x8 块砖顺砌 |
| `AR-BRELM` | 标准砖块英式堆砌 |
| `AR-BRSTD` | 标准砖块顺砌 |
| `AR-CONC` | 随机点和石头图案（混凝土）|
| `AR-HBONE` | 人字形图案 |
| `AR-PARQ1` | 镶木地板图案 |
| `AR-RROOF` | 屋顶木瓦图案 |
| `AR-RSHKE` | 屋顶实木瓦图案 |
| `AR-SAND` | 随机点图案（沙地）|

### ISO 图案（国际标准）

| 图案名 | 说明 |
|--------|------|
| `ACAD_ISO02W100` | 划线 |
| `ACAD_ISO03W100` | 划、空格线 |
| `ACAD_ISO04W100` | 长划、点线 |
| `ACAD_ISO05W100` | 长划、双点线 |
| `ACAD_ISO06W100` | 长划、三点线 |
| `ACAD_ISO07W100` | 点线 |
| `ACAD_ISO08W100` | 长划、短划线 |
| `ACAD_ISO09W100` | 长划、双短划线 |
| `ACAD_ISO10W100` | 划、点线 |
| `ACAD_ISO11W100` | 双划、点线 |
| `ACAD_ISO12W100` | 划、双点线 |
| `ACAD_ISO13W100` | 双划、双点线 |
| `ACAD_ISO14W100` | 划、三点线 |
| `ACAD_ISO15W100` | 双划、三点线 |

### JIS 图案

| 图案名 | 说明 |
|--------|------|
| `JIS_LC_8` | LC 图案 (@8) |
| `JIS_LC_8A` | LC 图案 (@8 变体) |
| `JIS_LC_20` | LC 图案 (@20) |
| `JIS_LC_20A` | LC 图案 (@20 变体) |
| `JIS_RC_10` | RC 图案 (@10) |
| `JIS_RC_15` | RC 图案 (@15) |
| `JIS_RC_18` | RC 图案 (@18) |
| `JIS_RC_30` | RC 图案 (@30) |
| `JIS_STN_1E` | 石材图案 (@1) |
| `JIS_STN_2.5` | 石材图案 (@2.5) |
| `JIS_WOOD` | 木材图案 |

### 材质图案

| 图案名 | 说明 |
|--------|------|
| `ANGLE` | 角钢 |
| `BOX` | 方钢 |
| `BRASS` | 黄铜 |
| `BRICK` | 砖 |
| `BRSTONE` | 砖和石 |
| `CLAY` | 粘土 |
| `CORK` | 软木 |
| `CROSS` | 十字形 |
| `DASH` | 划线 |
| `DOLMIT` | 地壳岩层 |
| `DOTS` | 点 |
| `EARTH` | 地面 |
| `ESCHER` | Escher 图案 |
| `FLEX` | 软性材质 |
| `GRASS` | 草地 |
| `GRATE` | 格栅 |
| `GRAVEL` | 沙砾 |
| `HEX` | 六边形 |
| `HONEY` | 蜂巢 |
| `HOUND` | 犬牙交错 |
| `INSUL` | 绝缘材质 |
| `LINE` | 平行水平线 |
| `MUDST` | 泥沙 |
| `NET` | 水平/垂直栅格 |
| `NET3` | 网状 0-60-120 |
| `PLAST` | 塑料 |
| `PLASTI` | 塑料（变体）|
| `SACNCR` | 混凝土 |
| `SQUARE` | 小方块 |
| `STARS` | 六芒星 |
| `STEEL` | 钢 |
| `SWAMP` | 沼泽 |
| `TRANS` | 热传递材质 |
| `TRIANG` | 等边三角形 |
| `ZIGZAG` | 锯齿 |
| `GOST_GLASS` | 玻璃 (GOST) |
| `GOST_WOOD` | 木材 (GOST) |
| `GOST_GROUND` | 地面 (GOST) |

---

## 边界类型

### 多段线边界

```javascript
const boundary = new PolylineEnt();
boundary.setPoints([[0, 0], [60, 0], [60, 50], [30, 70], [0, 50]]);
boundary.isClosed = true;
boundary.setDefaults();

const edges = new Edges();
const edge = new Edge();
edge.edgeType = EdgeType.Polyline;
edge.bulgePoints = boundary.bulgePoints.clone();
edges.add(edge);

hatch.setLoops(edges);
```

### 圆形边界

```javascript
const { CircleEnt } = vjcad;

const circle = new CircleEnt([100, 50], 30);
circle.setDefaults();

const edges = new Edges();
const edge = new Edge();
edge.edgeType = EdgeType.Circle;
edge.center = circle.center.clone();
edge.radius = circle.radius;
edges.add(edge);

hatch.setLoops(edges);
```

### 带孔填充（外边界 + 内边界）

```javascript
// 外边界（矩形）
const outerRect = new PolylineEnt();
outerRect.setPoints([[0, 0], [100, 0], [100, 80], [0, 80]]);
outerRect.isClosed = true;
outerRect.setDefaults();

// 内边界（圆形孔）
const innerCircle = new CircleEnt([50, 40], 20);
innerCircle.setDefaults();

// 创建 Edges（外边界 + 内边界）
const allEdges = new Edges();

// 外边界
const outerEdge = new Edge();
outerEdge.edgeType = EdgeType.Polyline;
outerEdge.bulgePoints = outerRect.bulgePoints.clone();
allEdges.add(outerEdge);

// 内边界（孔）
const innerEdge = new Edge();
innerEdge.edgeType = EdgeType.Circle;
innerEdge.center = innerCircle.center.clone();
innerEdge.radius = innerCircle.radius;
allEdges.add(innerEdge);

// 设置边界
hatch.setLoops(allEdges);
```

---

## 图案比例

### 自动计算比例

```javascript
const { calculateHatchPatternScale } = vjcad;

// 计算边界大小
const bbox = boundary.boundingBox();
const boundsSize = Math.max(bbox.maxX - bbox.minX, bbox.maxY - bbox.minY);

// 获取图案定义
const patternDef = Engine.patternManager.getPattern(patternName);

// 自动计算合适的比例
const autoScale = calculateHatchPatternScale(boundsSize, patternName, patternDef);

hatch.patternScale = autoScale;
```

### 手动设置比例

```javascript
hatch.patternScale = 2;  // 放大 2 倍
hatch.patternScale = 0.5; // 缩小一半
```

---

## 自定义图案

### PAT 格式说明

```
*图案名称,图案描述
角度,X原点,Y原点,deltaX,deltaY[,线型定义...]
```

- **角度**: 线条绘制方向（度），0=水平向右，90=垂直向上
- **X原点/Y原点**: 第一条线的起点坐标
- **deltaX**: 沿线条方向的偏移量
- **deltaY**: 垂直于线条方向的偏移量（行间距）
- **线型定义**: 可选的虚线模式（正数=实线，负数=空隙，0=点）

### 注册自定义图案

最简单：直接把 PAT 文本交给内置解析器（可含多个 `*图案`，返回成功加载的数量）：

```javascript
const count = Engine.patternManager.loadFromPATDefinitions(`*MY_PATTERN,我的自定义图案
45,0,0,0,5
135,0,0,0,5`, "custom");
```

也可以自己解析后 `registerPattern`：

```javascript
// PAT 格式字符串
const patString = `*MY_PATTERN,我的自定义图案
45,0,0,0,5
135,0,0,0,5`;

// 解析 PAT 格式
function parsePatternString(patString) {
    const lines = patString.trim().split('\n');
    const headerMatch = lines[0].match(/^\*([^,]+),(.*)$/);
    
    const name = headerMatch[1].trim();
    const description = headerMatch[2].trim();
    
    const patternLines = [];
    for (let i = 1; i < lines.length; i++) {
        const line = lines[i].trim();
        if (!line || line.startsWith(';')) continue;
        
        const parts = line.split(',').map(p => parseFloat(p.trim()));
        patternLines.push({
            angle: parts[0],
            xOrigin: parts[1],
            yOrigin: parts[2],
            deltaX: parts[3],
            deltaY: parts[4],
            dashes: parts.slice(5)
        });
    }
    
    return { name, description, lines: patternLines };
}

// 注册图案
const patternDef = parsePatternString(patString);
Engine.patternManager.registerPattern(patternDef, "custom");
```

---

## PatternManager API

### 获取管理器

```javascript
const patternManager = Engine.patternManager;
```

### 常用方法

```javascript
// 获取所有图案名称
const allNames = patternManager.getAllPatternNames();
const categoryNames = patternManager.getAllPatternNames("builtin");

// 获取图案定义
const patternDef = patternManager.getPattern("ANSI31");
console.log(patternDef.name);
console.log(patternDef.description);
console.log(patternDef.lines);  // 线定义数组

// 检查图案是否存在
const exists = patternManager.hasPattern("MY_PATTERN");

// 注册自定义图案
patternManager.registerPattern(definition, "custom");

// 获取分类
const categories = patternManager.getCategories();

// 获取统计信息
const stats = patternManager.getStatistics();
```

---

## 完整示例：图案表

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

const patternManager = Engine.patternManager;
const allPatternNames = patternManager.getAllPatternNames();

const COLS = 6;
const CELL_WIDTH = 80;
const CELL_HEIGHT = 60;
const PADDING = 10;

function createEdgesFromPolyline(polyline) {
    const edges = new Edges();
    const edge = new Edge();
    edge.edgeType = EdgeType.Polyline;
    edge.bulgePoints = polyline.bulgePoints.clone();
    edges.add(edge);
    return edges;
}

const allEntities = [];

allPatternNames.forEach((patternName, i) => {
    const col = i % COLS;
    const row = Math.floor(i / COLS);
    const x = col * (CELL_WIDTH + PADDING);
    const y = -row * (CELL_HEIGHT + PADDING + 20);
    
    // 边界
    const boundary = new PolylineEnt();
    boundary.addVertex([x, y]);
    boundary.addVertex([x + CELL_WIDTH, y]);
    boundary.addVertex([x + CELL_WIDTH, y + CELL_HEIGHT]);
    boundary.addVertex([x, y + CELL_HEIGHT]);
    boundary.isClosed = true;
    boundary.setDefaults();
    allEntities.push(boundary);
    
    // 填充
    const hatch = new HatchEnt();
    hatch.patternName = patternName;
    hatch.patternScale = patternName === "SOLID" ? 1 : 5;
    hatch.setLoops(createEdgesFromPolyline(boundary));
    hatch.setDefaults();
    hatch.color = patternName === "SOLID" ? 3 : 2;
    allEntities.push(hatch);
    
    // 标签
    const label = new TextEnt();
    label.insertionPoint = [x, y - 8];
    label.text = patternName;
    label.height = 5;
    label.setDefaults();
    allEntities.push(label);
});

Engine.addEntities(allEntities);
Engine.zoomExtents();
```

---

## 命令方式

```javascript
// HATCH 命令：选择边界并填充
await Engine.editor.executerWithOp('HATCH');

// EHATCH 命令：编辑现有填充（注册名是 EHATCH，不是 HATCHEDIT）
await Engine.editor.executerWithOp('EHATCH');
```
