> **本文回答**：实体变换 —— `move` / `rotate` / `scale` / `mirror` + 组合用法。

## 一、四个方法（参数都是点对象，不是散坐标）

| 方法 | 签名 | 说明 |
|---|---|---|
| `move(from, to)` | `move(Point2DLike, Point2DLike)` | 位移 = `to − from` |
| `rotate(center, angle)` | `rotate(Point2DLike, number)` | 绕点旋转，**弧度**，逆时针正 |
| `scale(center, factor)` | `scale(Point2DLike, number)` | 以点为中心缩放 |
| `mirror(p1, p2)` | `mirror(Point2DLike, Point2DLike)` | 沿 p1→p2 直线镜像 |

`Point2DLike = Point2D | [number, number]`——传 `[x,y]` 数组即可。

**这四个方法内部已自动调 `changed()`**（标记脏 + 通知 store），不需要手动再调。手动 `changed()` 只在**直接改属性**时需要。

```js
line.move([0, 0], [200, 100]);   // 平移向量 (200,100)；内部自动 changed()
```

## 二、平移

```js
const line = new LineEnt([0, 0], [100, 0]);
mapcadLayer.addEntity(line);

line.move([0, 0], [200, 100]);   // → (200,100) → (300,100)
```

批量：

```js
const dx = 200, dy = 100, origin = [0, 0];
selected.forEach(e => e.move(origin, [origin[0] + dx, origin[1] + dy]));
// 或更直观：e.move([0,0],[dx,dy])
```

## 三、旋转（弧度！）

```js
entity.rotate([cx, cy], angleRad);   // 弧度，逆时针为正
```

- **单位是弧度**（内部 `Math.cos(angle)`）——传 90 会转出 90 弧度而非 90°
- `Math.PI / 2` = 90°；`deg * Math.PI / 180` 换算

```js
const rect = new PolylineEnt([[-50,-50],[50,-50],[50,50],[-50,50]], true);
mapcadLayer.addEntity(rect);

rect.rotate([0, 0], Math.PI / 4);    // 绕原点逆时针 45°
```

绕自身中心：

```js
const bb = rect.boundingBox();       // [x1, y1, x2, y2]
const c = [(bb[0] + bb[2]) / 2, (bb[1] + bb[3]) / 2];
rect.rotate(c, 30 * Math.PI / 180);
```

## 四、缩放

```js
entity.scale([cx, cy], factor);
```

- `factor > 1` 放大；`< 1` 缩小；**负值会把坐标镜像到中心另一侧**（`scaleFrom` 不取 abs；仅 radius/height 等标量内部取 `Math.abs`）
- 同时改变相对缩放中心的距离

```js
circle.scale([0, 0], 2);     // 半径 ×2，圆心坐标也 ×2
```

只改大小不改位置：

```js
const bb = circle.boundingBox();
circle.scale([(bb[0]+bb[2])/2, (bb[1]+bb[3])/2], 1.5);
```

## 五、镜像

```js
entity.mirror([p1x, p1y], [p2x, p2y]);
```

沿两点的直线翻转。

| 轴 | 参数 |
|---|---|
| X 轴（上下翻） | `mirror([0,0], [100,0])` |
| Y 轴（左右翻） | `mirror([0,0], [0,100])` |
| 45° 对角线 | `mirror([0,0], [100,100])` |

```js
const tri = new PolylineEnt([[0,0],[100,0],[50,80]], true);
mapcadLayer.addEntity(tri);

const mirrored = tri.clone();
mirrored.mirror([0, 0], [0, 100]);   // 沿 Y 轴镜像
mirrored.color = 0x4488ff;
mirrored.changed();                  // clone 后改属性，手动 changed
mapcadLayer.addEntity(mirrored);
```

## 六、组合变换

不满足交换律，顺序重要：

```js
rect.rotate([0, 0], Math.PI / 6);
rect.move([0, 0], [200, 150]);
rect.scale([200, 150], 1.5);
```

## 七、克隆 + 变换 模式

保留原始实体：

```js
const base = new CircleEnt([0, 0], 100);
mapcadLayer.addEntity(base);

for (let i = 1; i <= 4; i++) {
  const copy = base.clone();
  copy.move([0, 0], [200 * i, 0]);
  copy.color = 0xff0000 + i * 0x001000;
  copy.changed();
  mapcadLayer.addEntity(copy);
}
```

## 八、环形阵列

```js
const base = new LineEnt([50, 0], [150, 0]);
base.color = 0x00ffff;

const N = 12;
for (let i = 0; i < N; i++) {
  const c = base.clone();
  c.rotate([0, 0], (2 * Math.PI / N) * i);
  mapcadLayer.addEntity(c);
}
```

## 九、矩阵阵列

```js
function arrayPattern(src, rows, cols, dx, dy) {
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      if (r === 0 && c === 0) { mapcadLayer.addEntity(src); continue; }
      const cpy = src.clone();
      cpy.move([0, 0], [c * dx, r * dy]);
      mapcadLayer.addEntity(cpy);
    }
  }
}
```

## 十、角度单位速查（容易混）

| API | 单位 |
|---|---|
| `entity.rotate(center, angle)` | **弧度** |
| `ArcEnt` 构造的 startAngle/endAngle | **弧度** |
| `EllipseEnt` 的 startAngle/endAngle/rotation | **弧度** |
| `BlockRefEnt.rotation` | **弧度** |
| `TextEnt.rotation` / `MTextEnt.rotation` 属性 | **度**（仅这两个例外；`rotate()` 内部会 `×180/π` 累加进去） |

## 十一、变换撤销

⚠️ **`changed()` 不会把变换记入撤销栈**——它只标脏（`isDirty=true`）并通知 store 重渲染（`entityChanged`）。`um.clear()` 后裸调 `entity.move(...)`，`undoManager.canUndo` 仍为 `false`。要撤销须**改前先手动快照**：

```js
mapcadLayer.undoManager.recordModify(entity);  // 先快照
entity.move([0,0],[100,0]);                    // 再改
mapcadLayer.undoManager.undo();                // 撤销（快照还原，实体被替换为新对象——旧引用失效）
```

多个变换当一次记录（注意 `recordModify` 也要在事务内逐个调用）：

```js
mapcadLayer.undoManager.beginTransaction();
selected.forEach(e => { mapcadLayer.undoManager.recordModify(e); e.rotate([cx, cy], Math.PI / 2); });
mapcadLayer.undoManager.endTransaction();
```

## 十二、常见坑

1. **rotate 传了角度数** → 90° 写成 90 会转 90 弧度（≈5156°）；记住 `deg * Math.PI/180`。
2. **move 传 `(dx, dy)`** → 签名是 `move(from, to)` 两个点，位移 = `to − from`；平移向量写法 `move([0,0],[dx,dy])`。
3. **scale 中心选错** → 实体跑到屏幕外；选自身 boundingBox 中心。
4. **mirror 两点太近** → 轴线方向不稳定；拉开点距。
5. **在 store 迭代中 move 选中项** → 先 `.slice()` 一份数组再遍历。

## 相关

- 交互式编辑命令 → [drawing/builtin-edit](../drawing/builtin-edit.md)（无独立 `ROTATE` 命令，但 `SCALE` 命令带 `[旋转(R)]` 分支可交互旋转；程序化旋转用实体方法）
- Undo / Redo → [drawing/undo-redo](../drawing/undo-redo.md)
