# Parametric Bracket Demo：四孔安装板

这个案例用同一个 `Sketch` 建立矩形板、两条构造轴和四个安装孔，再依次修改板宽、孔半径和水平边距。孔位由几何关系联合求解，编辑函数只替换驱动约束，不直接设置目标孔坐标。

![同一比例下的安装板修改前后求解结果](images/bracket-before-after.svg)

本例选择毫米作为长度单位；求解库本身使用调用者的统一长度单位。它验证二维草图几何与编辑流程，未计算材料、载荷或加工工艺。

## 运行与查看结果

需要项目要求的 MoonBit 工具链（`moonc >= 0.10.15`）。仅运行示例不需要 Python 或 Node.js：

```sh
moon run examples/parametric_bracket
```

程序按顺序输出六条 JSON Lines：`initial`、`widened`、`larger_holes`、`edited`、`conflict`、`recovered`。任何预期几何或失败恢复检查不符都会以非零状态退出。

如果已安装 Node.js 18+，可运行独立检查及图形生成脚本：

```sh
node scripts/bracket_showcase.cjs --check
node scripts/bracket_showcase.cjs --check --target wasm
node scripts/bracket_showcase.cjs --write
```

默认使用 JS 后端；`--target` 也接受 `wasm`、`wasm-gc`。`--check` 运行实际求解器、校验六个阶段，并确认已提交的 SVG 与本次求解图形一致；`--write` 在检查通过后重新生成 SVG。原始坐标、参数、约束编号和完整残差保存于 `_build/showcase/bracket-<target>.json`。

SVG 使用返回坐标与半径绘制，前后采用同一比例。绘制时将坐标保留三位小数，独立几何检查在舍入前执行。图中的文字说明采用该示例的参数值；几何轮廓与圆来自求解结果。

## 参数与设计关系

| 参数 | 初始值 | 最终值 | 含义 |
| --- | --- | --- | --- |
| `width` | 120 | 160 | 板宽 |
| `height` | 80 | 80 | 板高 |
| `hole_radius` | 5 | 8 | 四孔的共同半径 |
| `horizontal_margin` | 20 | 30 | 左右孔心到相应竖边的距离 |
| `vertical_margin` | 15 | 15 | 上下孔心到相应横边的距离 |

边距指**孔心到板边的距离**，不是孔壁净距。初始水平 / 垂直孔壁净距分别为 15 / 10，最终分别为 22 / 7。

建模代码位于 [examples/parametric_bracket/main.mbt](../examples/parametric_bracket/main.mbt)，使用核心库的公开接口：

| 设计关系 | 使用的约束 | 方程数 |
| --- | --- | --- |
| 左下角定位在原点 | `Fix` | 2 |
| 两条横边水平、两条竖边垂直 | `Horizontal`、`Vertical` | 4 |
| 板宽与板高 | `DistanceX`、`DistanceY` | 2 |
| 构造轴端点位于四条边的中点 | 四个 `Midpoint` | 8 |
| 左下孔心到左边、底边的距离 | 两个带符号 `DistanceToLine` | 2 |
| 左下孔关于竖轴得到右下孔；下排关于横轴得到上排 | 三个 `Mirror` | 6 |
| 四孔等半径，由一个尺寸驱动 | 一个 `Radius`、三个 `EqualRadius` | 4 |
| 合计 | 20 条启用关系 | 28 |

两条轴由板边中点决定，板宽或板高变化时会随板联动。左边的有向线从左上指向左下，底边从左下指向右下，所以朝板内的孔心距离为正。三条独立镜像关系已经确定四孔，无需再增加一条冗余镜像关系。

变量为 12 个点的 24 个坐标，加四个半径，共 28 个。另保存一条默认禁用的冲突约束，共 21 个约束句柄。正常解的 `local_dof=0`，当前雅可比满秩；这是一阶局部诊断。

## 尺寸修改后的实际结果

孔顺序为 A 左下、B 右下、C 左上、D 右上。下表为用于独立核对的解析坐标；实际输出在容差内与其一致。

| 阶段 | 孔 A | 孔 B | 孔 C | 孔 D | 半径 |
| --- | --- | --- | --- | --- | --- |
| 初始 120×80 | (20,15) | (100,15) | (20,65) | (100,65) | 5 |
| 仅将板宽改为 160 | (20,15) | (140,15) | (20,65) | (140,65) | 5 |
| 再将孔半径改为 8 | (20,15) | (140,15) | (20,65) | (140,65) | 8 |
| 再将水平边距改为 30 | (30,15) | (130,15) | (30,65) | (130,65) | 8 |

宽度修改后右侧两孔随板移动；水平边距修改后左右两列都移动；上下孔仍关于横轴对称。五个驱动尺寸和冲突关系的句柄编号在六个阶段保持不变。

示例先调用 `Bracket::edit()`，使用 `set_constraint()` 替换宽、高、半径和两个边距尺寸，再调用 `solve_and_apply()`。几何解析公式只存在于 `Bracket::verify()` 和独立检查脚本中，用来核对结果，不参与初值更新或求解后的坐标赋值。

## 冲突编辑与恢复

最终板宽为 160，再启用第二条宽度为 170 的关系。此时共 21 条启用关系、29 个方程。求解结果未满足全部残差，本次测试返回 `Stalled`；代码也接受未达标的 `IterationLimit`。

`solve_and_apply()` 不应用该候选结果。示例与独立脚本分别比较失败前后的全部 28 个几何数值，确认坐标与半径逐项完全相同。关闭冲突关系后再次求解，恢复 `Satisfied`，保留最终安装板。

每条 JSON 记录中，`geometry` 来自草图当前保存的几何，`report` 属于本次求解尝试。失败时二者分别对应“上一成功几何”和“未应用的候选诊断”，应结合 `applied=false` 阅读。未满足关系列表不是最小冲突集，一般的未收敛也不证明无解。

## 验证与交付范围

- MoonBit 示例对四个角点、四个构造轴端点、四个孔心和四个半径做解析核对。
- Node 脚本从真实命令行输出做第二次独立核对，检查状态、参数、句柄、逐约束残差、失败保留和恢复，并验证 SVG 可再生成。
- [额外测试](../examples/parametric_bracket/bracket_wbtest.mbt)从 105×65、R4、边距 17/12 改到 150×90、R6、边距 26/19，另覆盖板高和垂直边距变化，以及冲突恢复。
- CI 对四后端运行单元测试，并在 wasm 与 JS 上运行 Showcase 检查。源码包检查要求案例、说明、SVG 和生成脚本都进入分发包，并在解包后的源码上重新运行案例与图形检查。

本例的输入检查要求参数有限且为正，孔处于板内且互不相交；这属于这个安装板应用的参数范围检查，没有为核心库新增不等式或碰撞约束。它交付一个可运行、可检查、可视化的参数化零件案例，未新增交互式 CAD 编辑器、DXF/STEP 接口或 Mooncakes 发布。


## 核心库 SVG 输出（源码开发版）

六条 JSON 事件现增加 `svg` 字段，由 `Sketch::to_svg()` 直接生成。运行 `python scripts/export_svg.py --write` 可保存独立的 `before.svg`、`after.svg` 和 `conflict.svg`，三张图使用同一视野与比例；冲突阶段只画保留几何。原有 Node 对比图和校验入口继续保留。[SVG 接口及文件说明](SVG_EXPORT.md)列出具体行为；Mooncakes 0.2.0 起包含这个接口。
