From 86ea0f287f25a976d1e9afce62e64e19a46b68d2 Mon Sep 17 00:00:00 2001 From: wepar1212 <276597402+wepar1212@users.noreply.github.com> Date: Tue, 11 Aug 2026 14:13:50 +0800 Subject: [PATCH] Update MiniSW modeling features and tests --- EDGE_TREATMENT_CAPABILITY_REPORT.md | 87 + PHASE1_M10_MANUAL_CHECKLIST.md | 77 + PHASE1_SUBAGENT_TASKS.md | 721 +++++++ README.md | 48 +- ROADMAP.md | 59 + SUBPROXY_TASKS.md | 152 -- TASK_0_REPORT.md | 88 - TASK_A_REPORT.md | 92 - TASK_B_REPORT.md | 125 -- TASK_C_REPORT.md | 155 -- .../appearance-textures/ash_bump.jpg | Bin 0 -> 66661 bytes .../appearance-textures/birch_bump.jpg | Bin 0 -> 61012 bytes .../en-US/SolidWorks DIN Materials.sldmat | Bin 0 -> 1027850 bytes .../materials/en-US/shader_mapping.dat | 399 ++++ .../en-US/solidworks materials.sldmat | Bin 0 -> 895550 bytes .../en-US/sustainability extras.sldmat | Bin 0 -> 12804 bytes .../zh-CN/SolidWorks DIN Materials.sldmat | Bin 0 -> 792114 bytes .../materials/zh-CN/shader_mapping.dat | 399 ++++ .../zh-CN/solidworks materials.sldmat | Bin 0 -> 789074 bytes .../zh-CN/sustainability extras.sldmat | Bin 0 -> 13470 bytes bearing_bracket.stl | Bin 0 -> 2171484 bytes build-bearing-bracket.mjs | 99 + css/style.css | 65 +- index.html | 52 +- js/agent.js | 1714 ++++++++++++++++- js/doc.js | 271 ++- js/features/assembly.js | 136 ++ js/features/chamfer.js | 189 ++ js/features/constraints.js | 163 ++ js/features/drawing.js | 83 + js/features/fillet.js | 163 ++ js/features/loft.js | 182 ++ js/features/reference.js | 162 ++ js/features/rib.js | 209 ++ js/features/sweep.js | 221 +++ js/features/termination.js | 170 ++ js/geometry.js | 273 ++- js/main.js | 765 +++++++- js/math.js | 16 + js/sketch.js | 68 +- js/sketchui.js | 63 +- js/tree.js | 18 +- js/viewport.js | 118 +- tests/loader.mjs | 16 +- tests/test.agent-crud.mjs | 255 +++ tests/test.assembly.mjs | 16 + tests/test.chamfer.mjs | 48 + tests/test.drawing.mjs | 13 + tests/test.fillet.mjs | 64 + tests/test.loft.mjs | 47 + tests/test.mjs | 229 ++- tests/test.phase2.mjs | 46 + tests/test.phase34.mjs | 39 + tests/test.reference.mjs | 57 + tests/test.rib.mjs | 45 + tests/test.sweep.mjs | 42 + tests/test.termination.mjs | 51 + "\345\220\257\345\212\250.bat" | 21 +- ...2\247_160x70_\317\20668_\317\20640H7.json" | 497 +++++ 59 files changed, 8262 insertions(+), 826 deletions(-) create mode 100644 EDGE_TREATMENT_CAPABILITY_REPORT.md create mode 100644 PHASE1_M10_MANUAL_CHECKLIST.md create mode 100644 PHASE1_SUBAGENT_TASKS.md delete mode 100644 SUBPROXY_TASKS.md delete mode 100644 TASK_0_REPORT.md delete mode 100644 TASK_A_REPORT.md delete mode 100644 TASK_B_REPORT.md delete mode 100644 TASK_C_REPORT.md create mode 100644 assets/solidworks/appearance-textures/ash_bump.jpg create mode 100644 assets/solidworks/appearance-textures/birch_bump.jpg create mode 100644 assets/solidworks/materials/en-US/SolidWorks DIN Materials.sldmat create mode 100644 assets/solidworks/materials/en-US/shader_mapping.dat create mode 100644 assets/solidworks/materials/en-US/solidworks materials.sldmat create mode 100644 assets/solidworks/materials/en-US/sustainability extras.sldmat create mode 100644 assets/solidworks/materials/zh-CN/SolidWorks DIN Materials.sldmat create mode 100644 assets/solidworks/materials/zh-CN/shader_mapping.dat create mode 100644 assets/solidworks/materials/zh-CN/solidworks materials.sldmat create mode 100644 assets/solidworks/materials/zh-CN/sustainability extras.sldmat create mode 100644 bearing_bracket.stl create mode 100644 build-bearing-bracket.mjs create mode 100644 js/features/assembly.js create mode 100644 js/features/chamfer.js create mode 100644 js/features/constraints.js create mode 100644 js/features/drawing.js create mode 100644 js/features/fillet.js create mode 100644 js/features/loft.js create mode 100644 js/features/reference.js create mode 100644 js/features/rib.js create mode 100644 js/features/sweep.js create mode 100644 js/features/termination.js create mode 100644 tests/test.agent-crud.mjs create mode 100644 tests/test.assembly.mjs create mode 100644 tests/test.chamfer.mjs create mode 100644 tests/test.drawing.mjs create mode 100644 tests/test.fillet.mjs create mode 100644 tests/test.loft.mjs create mode 100644 tests/test.phase2.mjs create mode 100644 tests/test.phase34.mjs create mode 100644 tests/test.reference.mjs create mode 100644 tests/test.rib.mjs create mode 100644 tests/test.sweep.mjs create mode 100644 tests/test.termination.mjs create mode 100644 "\350\275\264\346\211\277\346\224\257\345\272\247_160x70_\317\20668_\317\20640H7.json" diff --git a/EDGE_TREATMENT_CAPABILITY_REPORT.md b/EDGE_TREATMENT_CAPABILITY_REPORT.md new file mode 100644 index 0000000..8038dad --- /dev/null +++ b/EDGE_TREATMENT_CAPABILITY_REPORT.md @@ -0,0 +1,87 @@ +# M08/M09 F0:Manifold 内核能力探测报告 + +日期:2026-08-10 +范围:只读检查 `vendor/manifold/manifold.js`、`js/geometry.js` 与本地 Node/WASM 运行时;未修改生产源码,未启动浏览器、未下载依赖。 + +## 结论 + +**建议继续 F1(受限、纯网格的凸直棱倒角)进行原型验证;不建议直接进入 F2(实体圆角)的生产实现。** + +当前封装的 Manifold 能可靠执行网格构造、封闭性校验、布尔运算、切片和细分/平滑,但不提供可供 JavaScript 消费的 B-rep 拓扑面、拓扑棱边或原生 3D fillet/chamfer 算子。因此,真实倒角只能由 MiniSW 在 `MeshData` 层自行提取拓扑并重建网格;真实圆角还需要自行生成相切过渡面、处理边链与顶点角补片,风险显著更高。不得把 `smoothOut()` 当作实体圆角。 + +本地 vendor 文件不含可识别的 npm/package 版本元数据,因此本报告以实际导出和运行时行为为准,不猜测版本号。 + +## 基线与最小复现 + +- 执行:`node --loader ./tests/loader.mjs ./tests/test.mjs` +- 结果:64 通过、0 失败;几何内核初始化和既有实体布尔链路正常。 +- 运行时最小复现:`Manifold.cube([20,20,20], true)`,再调用 `getMesh()`、`decompose()`、`slice(0)`、`smoothOut()`、`refine(1)`,并在探测结束时手动释放全部创建的 WASM 对象。 +- 结果:立方体状态为 `NoError`,网格 8 顶点/12 三角形;`decompose()` 返回 1 个连通实体;`slice(0)` 返回 1 个二维多边形;`smoothOut()` 与 `refine(1)` 都返回 `NoError`。 + +## 已确认能力 + +| 能力 | 运行时接口/证据 | 对 M08/M09 的意义 | +|---|---|---| +| 网格读出 | `manifold.getMesh()` 返回 `vertProperties`、`triVerts`,并带可选 `faceID`、run/merge 数据 | 足以从 `MeshData` 合并共面三角形,自建拓扑面/边和稳定引用 | +| 网格构造与校验 | `new mod.Mesh({...})` + `new mod.Manifold(mesh)`;`status()` | 重建后的倒角/圆角网格可交由 Manifold 拒绝非流形结果 | +| 实体布尔 | `Manifold.union/difference/intersection`;现有 `geometry.js:booleanOp` 已统一封装 | 可用作工具体裁剪/并集的后备验证路线;不是原生边处理 | +| 组件分解 | `manifold.decompose()` | 仅按连通组件拆分,**不是**面/边分解,不能直接用于边选择 | +| 水平切片 | `manifold.slice(height)` 返回 `CrossSection`;`toPolygons()` 可读 | 可用于诊断或某些轴对齐特例;不能泛化为任意空间棱边圆角 | +| 细分/简化 | `refine`、`refineToLength`、`refineToTolerance`、`simplify` | 可改善离散网格质量,但不创建保持半径的圆角 | +| 平滑 | `smoothOut`、`smoothByNormals`、静态 `Manifold.smooth(mesh, sharpenedEdges)` | 是网格平滑/法线相关操作,非可控的几何圆角;不能用于 M09 验收 | +| 其他几何诊断 | `numVert/numEdge/numTri`、`volume`、`surfaceArea`、`genus`、`rayCast`、`boundingBox` | 可支持结果验证、性能统计及视口边拾取的射线辅助 | +| 二维偏移 | `CrossSection.offset(delta, joinType, ...)` | 只适用于 2D 截面;可服务某些专用工具体方案,不等于 3D 边偏移 | + +## 缺失能力与阻塞 + +1. **没有 B-rep 面/边枚举或邻接 API。** `numEdge()` 仅给数量;运行时没有 `faces()`、`edges()`、`faceID()` 等可遍历三维拓扑接口。`getMesh().faceID` 是可选来源/运行标识,不能作为完整稳定的几何边引用。 +2. **没有原生 3D chamfer/fillet/offset API。** 在 `manifold.js` 的导出和 Manifold 原型中未发现 `chamfer`、`fillet`、三维 `offset` 或曲面过渡算子。 +3. **`decompose()` 不是拓扑分面。** 它返回连通 Manifold 组件,无法定位“立方体的一条棱”。 +4. **`slice(height)` 仅限全局 Z 高度。** 它不能围绕任意选中边产生局部截面。 +5. **平滑不保特征语义。** `smoothOut()`/`smoothByNormals()` 无半径、目标边链、相切条件或顶点补片参数;用它冒充 R5 会改变不该处理的面且无法保证尺寸。 + +## 建议的 F1 可行路线:纯网格凸直棱倒角 + +前提:首版只接受封闭、方向一致、凸多面体上的直边;先做单条棱,再扩展相互不接触的多条平行棱。算法由 `js/features/edge-treatment.js` 独立实现,主代理随后负责注入 `geometry.js/doc.js/UI`。 + +1. 对 `MeshData` 以量化顶点坐标建立无向三角边表;合并共面的相邻三角形,得到逻辑平面与候选逻辑棱。 +2. 以相邻面法向、端点量化坐标、长度、二面角和凸性构造稳定边签名;重建时签名唯一匹配才解析,歧义或缺失必须明确报错。 +3. 对一条凸直棱,将其两侧面沿各自面内法向收缩,生成两条偏移边;以四边形(再三角化)替换原棱邻域,并处理端点局部拓扑。 +4. 严格检查倒角距离小于两侧可用面宽、未与其他待处理边冲突、输出无零面积三角形;构造后通过 `new mod.Manifold(new mod.Mesh(...)).status()` 做流形验收。 +5. 输出 `MeshData` 和边线段;WASM 对象只在主代理注入的上下文中创建和释放。 + +该路线可产生真实 STL 几何,但端点补片、多个相交选择边和凹边会迅速增加组合复杂度;首版不应承诺它们。 + +## F2 圆角评估 + +圆角在同一拓扑框架上还需:按半径生成圆弧截面、沿直棱扫出相切曲面、替换两侧面条带、在端点/边链转角处生成角补片,并保证共享顶点、面朝向和流形性。Manifold 可验证并布尔组合结果,却不替代这些构造步骤。 + +- **可验证的后备方案:** 仅对“贯穿整个实体、两端开放或端点可由明确平面封闭”的单条凸直棱,使用解析圆柱/四分之一圆柱工具体结合布尔差/并实现,并以截面、体积、流形状态验证。该方案仍需先可靠定位两相邻面与边局部坐标系。 +- **不可接受的方案:** 调用平滑 API、增加法线、仅在 Three.js 渲染高亮圆弧、或将草图圆角矩形改名。这些都不保证 STL 中存在真实 R 面。 +- **生产建议:** 在 F1 的拓扑提取、稳定引用、单边倒角、10 次重建稳定和独立网格流形测试完成前,暂停 F2。F2 首版范围应明确限制为立方体单条凸直棱 R 值,不承诺一般边链。 + +## 风险、复杂度与继续条件 + +| 项目 | 评估 | +|---|---| +| F1 单凸直棱倒角 | 中高复杂度;约 2–4 个实现/测试迭代,主要风险是端点补片和三角网格方向 | +| F1 多边选择 | 高复杂度;必须先检测相邻/相交选择边,否则偏移区域会重叠 | +| F2 单凸直棱圆角 | 高复杂度;需要圆柱曲面、两端补片、相切与半径上限校验 | +| F2 共线边链/一般边链 | 很高复杂度;需链识别、交界拼接和顶点角策略,当前不宜在同一轮承诺 | +| 性能 | 小型立方体可望低于 1 秒;必须设置三角数/分段数上限,避免无界细分 | + +继续 F1 的门槛:主代理认可“纯 MeshData 拓扑层”是 M08/M09 的正式基础,并允许先交付受限凸直棱功能。继续 F2 的门槛:F1 已通过闭合、体积、无退化三角形、稳定引用和 10 次重建测试;否则应以本报告记录技术阻塞,不做视觉伪实现。 + +## 对主代理的接入提示 + +F0 没有新增运行时代码或接口。若批准 F1,建议由子模块导出任务书中约定的 `extractTopology`、`makeStableEdgeRef`、`resolveEdgeRef`、`buildChamferMesh`;主代理再在 `geometry.js` 中以唯一已初始化的 `mod` 把 `MeshData` 转回 Manifold,并接入文档模型、拾取与序列化。 + +## 后续实施结果 + +本报告完成后,F1 的全部继续门槛已经通过,因此项目按报告建议继续了受限 F2: + +- M08 已实现凸多面体单条直棱的真实截切倒角、稳定引用和视口拾取。 +- M09 已实现凸多面体单条直棱的恒定半径多段圆柱过渡面;没有使用平滑 API。 +- 两者输出都使用共享索引的封闭 `MeshData`,并通过项目唯一 Manifold 内核的 `NoError`、正体积、无退化三角形和重复构建测试。 +- M08、M09 独立测试各 `5/5`,核心文档/AI/保存重建集成测试也已覆盖。 +- 安全范围仍严格限制为单条凸直棱;多边选择、边链、凹边和非凸实体按本报告风险结论明确拒绝。 diff --git a/PHASE1_M10_MANUAL_CHECKLIST.md b/PHASE1_M10_MANUAL_CHECKLIST.md new file mode 100644 index 0000000..833ab4f --- /dev/null +++ b/PHASE1_M10_MANUAL_CHECKLIST.md @@ -0,0 +1,77 @@ +# MiniSW 第一阶段 M10 手动验收清单 + +日期:2026-08-10 +状态:等待手动浏览器确认 +说明:M01~M09 的代码与自动化验收已完成;本文件只保留必须由人工观察的界面、交互和课程演练项目。全部勾选后,才在 `ROADMAP.md` 中把 M10 标为完成。 + +## 1. 启动与控制台 + +- [ ] 双击 `启动.bat`,浏览器能打开 `http://127.0.0.1:8898/index.html`。 +- [ ] 启动页正常消失,视口、Ribbon、特征树、属性面板和 AI 面板均可见。 +- [ ] 浏览器开发者工具控制台没有红色异常;允许出现浏览器自身的性能提示。 +- [ ] 前视、上视、右视和等轴测方向与界面名称一致。 + +## 2. AI 一句话轴承座 + +在 AI 面板输入: + +> 生成一个120×80×12的轴承座,主体凸台直径60高32,中心通孔直径30,两条厚4mm加强筋,实体倒角和R4实体圆角 + +验收: + +- [ ] AI 一次执行完成,不需要手工补特征。 +- [ ] 模型可见且没有错误提示,整体尺寸约为 `120×80×43 mm`。 +- [ ] 特征树依次包含:底板、实体倒角、实体圆角、主体凸台、左加强筋、右加强筋、中心通孔。 +- [ ] 参考几何中存在“筋定位基准面”。 +- [ ] 中心通孔使用“到下一面”而不是固定深度。 +- [ ] 模型上能观察到真实斜面倒角和多段圆弧过渡圆角。 + +## 3. 参数编辑与重建 + +- [ ] 右键左加强筋,进入特征参数编辑,把筋宽从 `4` 改为 `5`,确定后模型成功重建。 +- [ ] 编辑实体倒角距离,合法较小值能重建;过大值显示中文失败诊断而不是页面崩溃。 +- [ ] 编辑实体圆角半径或圆弧分段数,合法值能重建;过大半径显示中文失败诊断。 +- [ ] 编辑中心通孔,终止条件下拉框能看到固定深度、贯穿全部、对称、到下一面、到指定面和到实体。 +- [ ] 单击特征树中的新特征时,视口能显示对应高亮。 + +## 4. 实体棱边拾取 + +建议先新建一个简单长方体单独验证: + +- [ ] 点击“实体倒角”,再靠近长方体可见棱边单击,输入 `5` 后成功创建。 +- [ ] 点击“实体圆角”,选择另一条未处理凸直棱,输入 `5` 后成功创建。 +- [ ] 单击远离棱边的位置时,界面提示未拾取到棱边,不创建错误特征。 +- [ ] 对非凸复杂主体或不支持的多边选择,界面明确拒绝,不以光照平滑伪装成功。 + +## 5. 保存、撤销与导出 + +- [ ] 保存轴承座 JSON,刷新或关闭页面后重新打开,模型与特征树一致。 +- [ ] 修改一个新特征后执行撤销、重做,模型和参数能正确往返。 +- [ ] 导出 STL 成功,并能在外部查看器中打开;孔、筋、倒角和圆角均可见。 +- [ ] 打开一个第一阶段以前保存、没有 `references` 字段的旧工程,仍能正常重建。 + +## 6. 演练记录 + +- [ ] 完成一次从启动、AI 输入、参数编辑、保存到 STL 导出的连续课程作业演练。 +- [ ] 记录一段视频或至少四张截图:AI 输入、完整模型、特征树、STL 外部查看结果。 +- [ ] 在演练中没有持续卡顿、页面失去响应或连续重建后明显变慢。 + +## 7. 自动化证据(已完成) + +- 核心综合测试:`94/94` 通过。 +- 七个第一阶段独立模块测试:`93/93` 通过。 +- 总计:`187/187` 通过。 +- AI 轴承座 Node 环境完整重建:约 `20~25ms`,预算 `<1000ms`。 +- 轴承座连续重建 `10` 次体积一致。 +- 轴承座保存/打开体积一致,STL 三角数与网格一致。 +- 旧版无 `references` 字段工程可重建。 + +## 8. 最终签字 + +- [ ] 上述人工项目全部通过。 +- [ ] 将 `ROADMAP.md` 中 M10 改为 `[x]`。 +- [ ] 将路线图当前状态改为“第一阶段完成,下一步进入阶段二 M11”。 + +验收人:____________ +验收日期:____________ +备注:____________________________________________________________ diff --git a/PHASE1_SUBAGENT_TASKS.md b/PHASE1_SUBAGENT_TASKS.md new file mode 100644 index 0000000..4d21f6d --- /dev/null +++ b/PHASE1_SUBAGENT_TASKS.md @@ -0,0 +1,721 @@ +# MiniSW 第一阶段并行子代理任务书 + +版本:v1.0 +日期:2026-08-10 +适用范围:`ROADMAP.md` 的 M02~M10 +当前基线:M01 圆形截面基础扫描已完成,核心测试 51/51 通过 + +## 1. 目标 + +第一阶段的目标是补齐高级零件建模能力,使 MiniSW 能生成包含扫描、放样、筋、参考几何、复杂拉伸终止条件、实体倒角和实体圆角的机械零件,并最终通过“AI 一句话生成轴承座”的综合验收。 + +本任务书用于后续启动多个子代理并行开发。它只规定工作分工、接口、边界和验收方式,不代表现在已经启动子代理。 + +第一阶段包含: + +- M02:路径草图 + 任意封闭截面扫描 +- M03:两个平行等顶点截面的基础放样 +- M04:多截面、异顶点数放样 +- M05:筋特征 +- M06:参考几何 +- M07:`toNext`、`toFace`、`toBody` 终止条件 +- M08:实体棱边倒角 +- M09:实体棱边圆角 +- M10:阶段一综合验收 + +## 2. 协作模型 + +### 2.1 并发限制 + +当前包括主代理在内最多允许 4 个代理同时工作,因此每个批次最多同时运行 3 个子代理。 + +建议分三批执行: + +```text +批次 A(基础几何,可并行) + 子代理 A:扫描完整版 M02 + 子代理 B:放样 M03/M04 + 子代理 D:参考几何 M06 + ↓ 主代理集成与回归 + +批次 B(依赖基础能力,可并行) + 子代理 C:筋 M05 + 子代理 E:终止条件 M07 + 子代理 F:实体倒角/圆角 M08/M09 + ↓ 主代理集成与回归 + +批次 C(验收) + 子代理 G:第一阶段独立测试审查 + 主代理:M10 综合集成、浏览器验收、文档更新 +``` + +### 2.2 主代理是唯一集成者 + +主代理负责所有共享入口和最终决策。除非任务书后续被主代理显式修改,只有主代理可以修改下列共享文件: + +- `js/geometry.js` +- `js/doc.js` +- `js/agent.js` +- `js/main.js` +- `js/tree.js` +- `js/sketchui.js` +- `js/viewport.js` +- `js/math.js` +- `js/sketch.js` +- `index.html` +- `css/style.css` +- `tests/test.mjs` +- `tests/loader.mjs` +- `README.md` +- `ROADMAP.md` + +子代理负责在独立模块和独立测试文件中实现自己的能力,完成后把接口和交接说明提交给主代理,由主代理接入共享文件。 + +### 2.3 建议新增目录 + +为降低并行冲突,第一阶段的新实现优先放在: + +```text +js/features/ + sweep.js + loft.js + rib.js + reference.js + termination.js + edge-treatment.js + +tests/ + test.sweep.mjs + test.loft.mjs + test.rib.mjs + test.reference.mjs + test.termination.mjs + test.edge-treatment.mjs + test.phase1.mjs +``` + +目录和文件由对应子代理创建。其他子代理不得修改不属于自己的模块。 + +## 3. 全体子代理通用规则 + +### 3.1 开始工作前 + +每个子代理必须先完成以下只读检查: + +1. 阅读 `README.md`、`ROADMAP.md` 和本任务书。 +2. 查看 `git status --short`,识别用户和主代理已有改动。 +3. 阅读与自己任务直接相关的现有源码和测试。 +4. 运行当前核心测试,确认基线状态;如果基线已经失败,先报告主代理,不得擅自修复无关问题。 +5. 向主代理确认自己将创建或修改的文件列表,不得扩大范围。 + +### 3.2 文件边界 + +- 多个代理共享同一工作目录,文件修改会立即互相可见;严禁两个代理同时修改同一个文件。 +- 不得恢复或修改当前处于删除状态的 `SUBPROXY_TASKS.md`、`TASK_0_REPORT.md`、`TASK_A_REPORT.md`、`TASK_B_REPORT.md`、`TASK_C_REPORT.md`。 +- 不得修改 `E:\小灰\`、全局记忆或项目外部文件。 +- 不得使用 `git reset --hard`、`git checkout --`、清理工作区或其他会覆盖现有改动的命令。 +- 不得自行提交、推送或创建 PR,除非主人另行授权。 +- 不得重命名、移动或格式化与任务无关的文件。 +- 只使用 `apply_patch` 编辑文件,保留 UTF-8 中文内容。 + +### 3.3 架构约束 + +- 保持原生 JavaScript ESM,不引入打包器。 +- 不新增 CDN 或在线运行时依赖,应用必须继续彻底离线运行。 +- 优先复用仓库内 `vendor/three` 和 `vendor/manifold`。 +- 不得重新引入旧测试路径或外部源码副本;测试必须直接调用当前仓库源码。 +- 新模块使用命名导出,不使用隐式全局状态。 +- 所有长度单位统一为毫米,角度对外使用度数、内部可使用弧度但必须在接口说明中标明。 +- 世界坐标和草图坐标的转换只能走统一接口,不得在多个模块复制不同版本的坐标变换逻辑。 +- 错误必须提供可理解的中文信息,不能只返回 `undefined` 或底层 WASM 状态码。 + +### 3.4 几何内核生命周期 + +- 谁创建 manifold/CrossSection 等 WASM 对象,谁负责释放;所有权转移必须写在函数注释中。 +- 临时对象必须使用 `try/finally` 或等价结构释放。 +- 返回 manifold 的函数必须明确写明“返回值所有权交给调用方”。 +- 不得在失败路径遗留未释放对象。 +- 重复重建测试至少执行 10 次,并检查体积、包围盒和错误状态稳定。 + +### 3.5 测试要求 + +每个子代理必须提供独立测试文件,至少覆盖: + +- 正常路径 +- 参数边界 +- 退化输入 +- 非法引用 +- 保存/打开需要保留的纯数据 +- 重复执行稳定性 +- 中文错误信息 + +独立测试通过不等于任务完成。只有主代理完成共享入口接入并通过总回归后,里程碑才能在 `ROADMAP.md` 中勾选。 + +### 3.6 性能预算 + +- 普通拉伸、筋、参考几何解析:目标小于 200ms。 +- 扫描、放样、实体圆角/倒角:目标小于 1s。 +- 测试必须记录至少一个典型模型的运行时间。 +- 网格过密时应给出降采样建议,不得无上限增加路径和截面分段数。 + +### 3.7 子代理交付报告格式 + +每个子代理完成后必须向主代理报告: + +1. 完成的能力。 +2. 新增和修改的文件。 +3. 导出接口及参数语义。 +4. 对象所有权和释放规则。 +5. 测试命令、通过数和耗时。 +6. 已知限制。 +7. 需要主代理接入共享文件的具体位置。 +8. 是否存在与其他代理的接口冲突。 + +## 4. 公共数据与接口契约 + +子代理不得自行改变以下公共概念;如确需调整,先向主代理提交接口变更申请。 + +### 4.1 MeshData + +```js +{ + verts: Float32Array, // xyz 连续排列 + tris: Uint32Array, // 三角形顶点索引 +} +``` + +要求:三角面朝外、无退化三角形、封闭实体满足二维流形条件。 + +### 4.2 PathData + +```js +{ + points: [[x, y, z], ...], + closed: false, + sourceSketchId: number | null, +} +``` + +路径至少包含两个不同点。采样点去重容差统一为 `1e-6 mm`。 + +### 4.3 ProfileData + +```js +{ + points: [[u, v], ...], + closed: true, + plane: { + origin: [x, y, z], + xAxis: [x, y, z], + yAxis: [x, y, z], + normal: [x, y, z], + }, + sourceSketchId: number | null, +} +``` + +轮廓不得包含重复末点;闭合由 `closed` 表达。外环和孔环需要明确分组,禁止依赖不稳定的顺逆时针猜测。 + +### 4.4 ReferenceData + +参考几何使用稳定 ID,不直接保存运行时对象: + +```js +{ id, type: 'plane', name, origin, normal, xAxis, sourceRefs, params } +{ id, type: 'axis', name, origin, direction, sourceRefs, params } +{ id, type: 'point', name, position, sourceRefs, params } +{ id, type: 'csys', name, origin, xAxis, yAxis, zAxis, sourceRefs, params } +``` + +### 4.5 特征构建结果 + +纯网格算法优先返回 `MeshData`。需要 manifold 布尔运算的模块通过主代理提供的内核上下文工作,不得自行初始化第二套 WASM 模块。 + +接口成功时返回结果,失败时抛出 `Error`。不得混用“返回 null”和“抛异常”两种失败协议。 + +## 5. 子代理 A:扫描完整版(M02) + +### 5.1 目标 + +把当前“世界坐标离散路径 + 圆形截面”扫描扩展为“路径草图 + 任意封闭截面草图”,并支持可控的截面方向和扭转。 + +### 5.2 允许修改 + +- 新建并修改 `js/features/sweep.js` +- 新建并修改 `tests/test.sweep.mjs` +- 如需测试夹具,可新建 `tests/fixtures/sweep/` 下的 JSON 文件 + +### 5.3 禁止修改 + +- 所有第 2.2 节列出的共享文件 +- `js/features/loft.js` 及其他子代理文件 + +### 5.4 具体任务 + +1. 从路径草图的直线、圆弧和连续多段线生成有序三维采样点。 +2. 从封闭截面草图提取一个或多个轮廓环。 +3. 支持圆形、矩形和一般多边形截面。 +4. 使用平行传递标架,避免低曲率处的 Frenet 标架翻转。 +5. 支持 `frameMode: 'parallelTransport' | 'fixed'`。 +6. 支持 `twistDegrees`,沿路径累计旋转截面。 +7. 支持起点/终点封端。 +8. 检测零长度路径、路径断裂、开放截面、截面退化、急弯自交和截面尺寸过大。 +9. 保持当前 M01 圆形截面扫描结果兼容。 + +### 5.5 建议导出接口 + +```js +export function samplePathSketch(sketch, options = {}) +export function profileFromSketch(sketch, options = {}) +export function buildSweepMesh({ path, profile, frameMode, twistDegrees, capStart, capEnd, segments }) +``` + +### 5.6 最低测试集 + +- 圆形截面沿直线扫描 +- 矩形截面沿直线扫描 +- 圆形截面沿四分之一圆弧扫描 +- 矩形截面沿多段线路径扫描 +- 固定方向与平行传递结果不同但均有效 +- 90° 扭转后的终点截面方向正确 +- 开放截面被拒绝 +- 路径只有一个点被拒绝 +- 急弯导致自交时给出明确错误或降采样提示 +- 生成网格满足流形要求 + +### 5.7 验收标准 + +- 独立测试全部通过。 +- 直线、圆弧、多段线三类路径均可工作。 +- 圆形、矩形、一般封闭多边形三类截面均可工作。 +- 典型模型构建小于 1s。 +- 向主代理提供从现有 `Doc` 草图引用转换到公共 `PathData/ProfileData` 的接入说明。 + +## 6. 子代理 B:放样(M03/M04) + +### 6.1 目标 + +实现两个及多个封闭截面间的实体放样,解决顶点配对、异顶点数重采样和截面起点对齐问题。 + +### 6.2 允许修改 + +- 新建并修改 `js/features/loft.js` +- 新建并修改 `tests/test.loft.mjs` +- 如需测试夹具,可新建 `tests/fixtures/loft/` + +### 6.3 禁止修改 + +- 所有共享文件 +- 扫描、筋、参考几何等其他代理模块 + +### 6.4 具体任务 + +1. M03:两个平行、等顶点数封闭截面的侧壁连接和端面封闭。 +2. 自动统一截面环方向。 +3. 自动选择截面起始点,使相邻截面连线总长度尽量小。 +4. M04:支持三个及以上截面。 +5. 支持异顶点数截面的均匀弧长重采样。 +6. 支持矩形到圆形、圆形到多边形的过渡。 +7. 检测截面顺序错误、共面重复截面、面积趋近于零、自交和生成体翻转。 +8. 为后续切除放样保留同一网格接口,不在本任务中改文档布尔语义。 + +### 6.5 建议导出接口 + +```js +export function resampleClosedProfile(points, count) +export function alignProfileStart(reference, candidate) +export function buildLoftMesh({ profiles, resampleCount, capStart, capEnd, alignment }) +``` + +### 6.6 最低测试集 + +- 等顶点矩形到矩形 +- 等顶点圆到圆 +- 异顶点矩形到圆形 +- 三截面方—圆—方过渡 +- 截面输入顺序反向时自动修正 +- 两个截面重合时报错 +- 退化截面时报错 +- 网格封闭且体积大于零 +- 保存所需的截面引用参数可以 JSON 序列化 + +### 6.7 验收标准 + +- M03 与 M04 测试分别报告,不得用“基础版完成”代替完整版。 +- 至少通过矩形到圆形的异顶点放样。 +- 输出为稳定、封闭的 `MeshData`。 +- 典型三截面放样小于 1s。 + +## 7. 子代理 C:筋特征(M05) + +### 7.1 依赖 + +在批次 A 集成完成后启动。依赖稳定的草图平面转换、拉伸工具和布尔并集接口。 + +### 7.2 允许修改 + +- 新建并修改 `js/features/rib.js` +- 新建并修改 `tests/test.rib.mjs` + +### 7.3 禁止修改 + +- 所有共享文件 +- 扫描、放样和参考几何模块 + +### 7.4 具体任务 + +1. 接受一条或多条连续开放轮廓作为筋中心线。 +2. 支持厚度、单侧/双侧、翻转方向和拔模角参数。 +3. 将中心线扩展为薄壁封闭轮廓。 +4. 沿草图平面法向拉伸到目标实体范围。 +5. 与已有实体求交/并集,确保筋只保留与主体相连的部分。 +6. 检测筋不接触主体、厚度过大、轮廓自交、路径不连续和零长度线段。 + +### 7.5 建议导出接口 + +```js +export function offsetOpenPath(points, thickness, side) +export function buildRibProfile({ path, thickness, side, draftDegrees }) +export function buildRibTool(ctx, { sketch, thickness, side, flip, extent, body }) +``` + +其中 `ctx` 由主代理注入几何内核能力,子代理不得创建第二个 manifold 实例。 + +### 7.6 最低测试集 + +- 底板与侧板之间单条直筋 +- 双侧对称筋 +- 翻转方向 +- 折线筋 +- 筋不接触主体时报错 +- 厚度过大时报错 +- 重复重建体积稳定 + +### 7.7 验收标准 + +- “底板 + 侧板 + 厚 3mm 筋板”端到端几何有效。 +- 筋与主体形成一个连通实体。 +- 普通筋特征构建小于 200ms。 + +## 8. 子代理 D:参考几何(M06) + +### 8.1 目标 + +提供稳定、可序列化、可重建的基准面、基准轴、基准点和坐标系计算模块。 + +### 8.2 允许修改 + +- 新建并修改 `js/features/reference.js` +- 新建并修改 `tests/test.reference.mjs` + +### 8.3 禁止修改 + +- `doc.js`、`math.js`、UI 和其他共享文件;数据接入由主代理完成 + +### 8.4 具体任务 + +1. 偏移基准面:相对已有平面沿法向偏移指定距离。 +2. 角度基准面:绕指定轴旋转指定角度。 +3. 平行于指定面并经过指定点的基准面。 +4. 两点基准轴。 +5. 两平面交线基准轴。 +6. 显式坐标基准点和引用实体中心点。 +7. 用户坐标系:原点和正交三轴。 +8. 验证法向、方向向量归一化和右手坐标系。 +9. 检测平行平面无交线、重合点、零方向向量、循环引用和丢失引用。 + +### 8.5 建议导出接口 + +```js +export function makeOffsetPlane(basePlane, distance) +export function makeAngledPlane(basePlane, axis, degrees) +export function makePlaneThroughPoint(basePlane, point) +export function makeAxisFromPoints(a, b) +export function makeAxisFromPlanes(a, b) +export function makeCoordinateSystem(origin, xHint, yHint) +export function resolveReference(reference, lookup) +``` + +### 8.6 最低测试集 + +- 前视面偏移 10mm +- 绕基准轴旋转 30° +- 两垂直平面求交轴 +- 平行平面求交被拒绝 +- 两个相同点不能建立轴 +- 坐标系满足单位长度、互相垂直和右手性 +- JSON 往返后计算结果一致 +- 循环引用被检测 + +### 8.7 验收标准 + +- 所有参考几何均为纯数据,可 JSON 序列化。 +- 计算结果具有稳定 ID 引用,不依赖数组下标。 +- 主代理可据此在偏移基准面上创建草图并拉伸。 + +## 9. 子代理 E:拉伸终止条件(M07) + +### 9.1 依赖 + +在参考几何和稳定拓扑引用完成初步集成后启动。 + +### 9.2 允许修改 + +- 新建并修改 `js/features/termination.js` +- 新建并修改 `tests/test.termination.mjs` + +### 9.3 禁止修改 + +- 现有 `buildExtrude`、文档模型和 UI;接入由主代理完成 + +### 9.4 具体任务 + +1. 统一终止模式:`blind`、`throughAll`、`symmetric`、`toNext`、`toFace`、`toBody`。 +2. `toNext`:沿拉伸方向找到第一个有效实体交点。 +3. `toFace`:根据稳定面引用解析目标面,并计算有效深度。 +4. `toBody`:生成足够长工具体后与目标实体裁剪。 +5. 支持正向、反向和偏置距离。 +6. 检测目标位于反方向、目标不存在、没有交点、深度趋近于零和引用失效。 +7. 不允许用固定超大深度冒充 `toFace` 的最终结果;近似方案必须明确标注并有误差测试。 + +### 9.5 建议导出接口 + +```js +export function normalizeTermination(params) +export function resolveToNext({ origin, direction, body, tolerance }) +export function resolveToFace({ origin, direction, targetFace, offset, tolerance }) +export function buildToBodyTool(ctx, { profileTool, targetBody, direction }) +``` + +### 9.6 最低测试集 + +- 两层实体间 `toNext` +- 到平面 `toFace` +- 到倾斜面 `toFace` +- 到独立实体 `toBody` +- 翻转方向 +- 目标在反方向时报错 +- 丢失引用时报错 +- 重复重建结果稳定 + +### 9.7 验收标准 + +- 三种新增模式均有独立测试和文档参数示例。 +- 结果不能越过目标面或目标实体。 +- 普通终止条件解析小于 200ms。 + +## 10. 子代理 F:实体倒角与圆角(M08/M09) + +### 10.1 目标 + +建立实体拓扑边识别和稳定引用,在此基础上实现真正作用于三维棱边的倒角与圆角。草图圆角矩形不能作为本任务的替代品。 + +### 10.2 允许修改 + +- 新建并修改 `js/features/edge-treatment.js` +- 新建并修改 `tests/test.edge-treatment.mjs` +- 可新建 `tests/fixtures/edge-treatment/` + +### 10.3 禁止修改 + +- `geometry.js`、`doc.js`、`viewport.js`、`tree.js` 和 AI 文件 +- 不得把已有 `chamfer_rect`、`fillet_rect` 政名后宣称实体功能完成 + +### 10.4 执行顺序 + +该任务必须按三个检查点推进: + +1. **F0 内核能力探测**:确认当前 manifold 版本能提供哪些面、边、切片、分解、平滑或网格操作;形成能力报告。 +2. **F1 实体倒角**:先完成凸多面体的一条直棱边倒角,再扩展到多边选择。 +3. **F2 实体圆角**:先完成立方体一条直棱边的恒定半径圆角,再扩展到共线边链。 + +如果 F0 证明内核无法安全提供精确圆角所需能力,必须报告技术阻塞和可验证替代方案,不能提交会产生非流形网格的视觉伪实现。 + +### 10.5 具体任务 + +1. 从三角网格合并共面三角形,提取拓扑面和拓扑边。 +2. 为边生成稳定引用:相邻面特征、端点量化坐标和几何签名组合,禁止只保存三角形索引。 +3. 倒角支持距离参数和单边/多边选择。 +4. 圆角支持半径参数、圆弧分段数和相切边链。 +5. 检测距离/半径过大、边不存在、凹凸方向不支持、拓扑引用失效和处理后非流形。 +6. 输出选中边、高亮所需的线段数据,供主代理接入视口拾取。 + +### 10.6 建议导出接口 + +```js +export function extractTopology(mesh, tolerance) +export function makeStableEdgeRef(edge, topology) +export function resolveEdgeRef(ref, topology) +export function buildChamferMesh({ mesh, edgeRefs, distance, tolerance }) +export function buildFilletMesh({ mesh, edgeRefs, radius, segments, tolerance }) +``` + +### 10.7 最低测试集 + +- 立方体单边倒角 5mm +- 立方体四条平行边倒角 +- 倒角距离过大被拒绝 +- 立方体单边圆角 R5 +- 共线边链圆角 +- 圆角半径过大被拒绝 +- 保存/打开后稳定边引用仍能解析 +- 上游尺寸改变后引用能重定位或明确报失效 +- 输出网格封闭、体积合理、无退化三角形 +- 连续 10 次重建稳定 + +### 10.8 验收标准 + +- 放大观察可见真实三维斜面或圆弧过渡。 +- STL 输出包含处理后的真实几何,而非仅渲染效果。 +- 单边处理小于 1s。 +- M08 和 M09 分开报告测试结果。 + +## 11. 子代理 G:第一阶段独立测试审查 + +### 11.1 启动时机 + +仅在 A~F 的成果全部由主代理接入后启动。该代理不得参与功能实现,以保持审查独立性。 + +### 11.2 允许修改 + +- 新建并修改 `tests/test.phase1.mjs` +- 新建 `PHASE1_QA_REPORT.md` + +### 11.3 禁止修改 + +- 所有生产源码 +- 其他代理测试文件 +- README 和 ROADMAP + +### 11.4 审查内容 + +1. 检查 M01~M09 每项是否有正常、边界和错误测试。 +2. 检查 DSL、文档模型、几何构建和 UI 参数是否语义一致。 +3. 检查保存/打开后的全部新特征能否重建。 +4. 检查撤销/重做是否保留新参数。 +5. 连续重建组合模型,观察体积、包围盒和错误状态。 +6. 检查 WASM 临时对象释放路径。 +7. 测量扫描、放样、筋、终止条件、倒角、圆角耗时。 +8. 测试旧 51 条基线断言无回归。 +9. 为“轴承座”综合模型准备确定性数据夹具。 + +### 11.5 验收输出 + +QA 报告必须把问题分成: + +- 阻断 M10 +- 可在第一阶段收尾修复 +- 可延期到第二阶段 + +不得只写“全部正常”,每项结论必须附测试证据。 + +## 12. 主代理集成职责 + +主代理不把共享入口交给子代理并行修改。每一批完成后,主代理按以下顺序集成: + +1. 审查子代理文件边界和测试结果。 +2. 统一公共数据格式和错误协议。 +3. 接入 `geometry.js` 的内核包装和对象生命周期。 +4. 接入 `doc.js` 的特征类型、重建分支、稳定引用和序列化。 +5. 接入 `agent.js` 的 DSL、规则解析、校验与执行。 +6. 接入 `tree.js`、`main.js`、`viewport.js` 的名称、属性表单、拾取和高亮。 +7. 更新统一测试入口。 +8. 跑全部测试并修复集成问题。 +9. 只有总回归通过后才更新 README 和 ROADMAP 勾选状态。 + +主代理必须特别检查: + +- 子代理是否越界修改共享文件。 +- 是否出现同一概念的多个不兼容数据结构。 +- 是否初始化了多个 manifold WASM 实例。 +- 是否在失败路径泄漏对象。 +- 是否把近似功能错误描述为精确功能。 +- 是否破坏旧工程文件兼容性。 + +## 13. M10 综合验收 + +### 13.1 轴承座模型 + +最低包含: + +- 底板 +- 主体凸台 +- 中心通孔 +- 至少两条筋 +- 至少一个参考基准面 +- 至少一个非固定深度终止条件 +- 实体倒角 +- 实体圆角 +- 可选扫描或放样结构 + +### 13.2 AI 验收句 + +最终应能用一条中文描述生成完整模型。AI 输出允许分解为多个底层操作,但不得依赖手工补特征。 + +### 13.3 功能验收 + +- 特征树顺序正确,所有特征无错误。 +- 每个新特征可编辑参数并重建。 +- 撤销/重做有效。 +- 保存、关闭、重新打开后结果一致。 +- STL 导出有效。 +- 旧工程文件仍能打开。 + +### 13.4 质量验收 + +- 第一阶段模块测试全部通过。 +- 原有 51 条测试无回归。 +- 不存在已知 WASM 对象泄漏路径。 +- 单个普通特征满足性能预算。 +- 浏览器控制台无新增错误。 +- 完成一次浏览器课程作业演练并记录结果。 + +### 13.5 路线图完成条件 + +只有以上条件全部满足,才能在 `ROADMAP.md` 中将 M10 标记为完成,并宣布第一阶段结束。 + +## 14. 冲突与异常处理 + +### 14.1 文件冲突 + +发现其他代理正在修改自己的目标文件时: + +1. 立即停止写入。 +2. 保留当前分析结果,不覆盖对方改动。 +3. 向主代理报告冲突文件和双方任务。 +4. 由主代理重新划分边界后再继续。 + +### 14.2 接口冲突 + +需要改变公共数据结构或共享函数签名时,子代理只提交一份接口申请,包含: + +- 现有接口为什么不足 +- 建议的新签名 +- 调用方影响 +- 向后兼容方案 +- 所需测试 + +未经主代理批准,不得自行修改共享接口。 + +### 14.3 技术阻塞 + +同一阻塞连续验证三次仍无法解决时,提交最小复现、底层错误、尝试过的方案和建议替代路径。不得用空实现、静默跳过或仅视觉效果掩盖阻塞。 + +### 14.4 资源异常 + +发现测试、浏览器自动化或几何构建出现异常 token、内存或时间消耗时立即暂停,向主人说明规模、原因和更省资源的方案;未经确认不继续高消耗流程。 + +## 15. 第一阶段完成后的交付物 + +第一阶段结束时应具备: + +- M02~M09 的生产实现 +- 模块化测试文件和统一回归入口 +- 更新后的 DSL 说明 +- 更新后的文件格式版本和兼容逻辑 +- 第一阶段 QA 报告 +- 轴承座课程作业演练记录 +- README 功能说明 +- ROADMAP 完成标记和遗留问题清单 + +本任务书是第一阶段并行开发的边界依据。实际启动子代理时,主代理应从对应章节复制任务,并额外告知当时的最新 Git 状态和已完成依赖。 diff --git a/README.md b/README.md index 308a74c..751333b 100644 --- a/README.md +++ b/README.md @@ -6,24 +6,33 @@ ## 功能特性 -- **参数化建模**:草图(直线 / 矩形 / 圆 / 圆弧 / 中心线)→ 特征(拉伸 / 切除 / 旋转 / 圆柱 / 圆柱切除)→ 重建,特征树可回看编辑 -- **AI 智能体**:中文自然语言描述零件,自动解析为建模操作并执行;离线可用(内置规则解析器),也可配置 LLM 走深度推理 +- **参数化建模**:草图(直线 / 矩形 / 圆 / 圆弧 / 椭圆 / 长圆槽 / 样条 / 中心线)→ 特征(拉伸 / 切除 / 旋转 / 圆柱 / 扫描 / 放样 / 筋 / 实体倒角 / 实体圆角)→ 重建,特征树可回看编辑 +- **参数化草图**:水平、垂直、重合、相等、平行、垂直、相切、角度、半径等约束;命名尺寸/参数表达式;草图线性和圆周阵列 +- **装配体工作区**:组件树、零件插入、同心/重合/距离等基础配合、包围盒与实体布尔干涉检查、爆炸视图 +- **工程图工作区**:A3/A4 图框、第一角三视图、尺寸与基础公差、关联剖视图、标题栏,以及 SVG/PNG 导出 +- **AI 智能体**:中文自然语言描述零件,自动解析为建模操作并执行;支持手动切换“内置解析器 / LLM 智能体”,并支持新建、追加、查询、修改、删除;它还能读取实体尺寸、体积、网格统计、特征增量和错误状态进行几何自观察;遇到依赖顺序错误会按“建模→装配→工程图”自动重排,重建失败时会诊断并自动移除不可用操作后重试 +- **严格模式路由**:选择 LLM 后,查询、新建、追加、修改、删除、装配和工程图全部先由 LLM 理解;本地只校验并执行带稳定特征 ID 的 `query_document`、`update_feature`、`delete_feature` 等操作,不再提前用本地 CRUD 截获,也不静默降级 +- **LLM 质量优先流程**:设置中可选“快速模式/专家完整模式”;快速模式保留为用户手动选择的低延迟方案,专家模式不再为节省 token 跳过阶段,查询、新建、追加、修改、删除和建模都会依次经过顶级机械工程师预审、完整意图规范化与 DSL 规划。第一阶段机械工程师只输出完整的 Markdown 工程架构说明(工程目标、对象与场景、坐标基准、几何组成与特征顺序、尺寸与默认假设、几何约束、装配意图、工程图意图、风险项),不被 JSON 格式束缚;第二阶段再把这份 Markdown 转为规范化意图 JSON,第三阶段才生成 `{"ops":[...]}` DSL。每一层都允许模型充分内部推理,不向请求注入 `max_tokens` 输出上限;执行纠错与视觉复核仍分别严格限定为 JSON。生成并重建成功后,MiniSW 本地几何观察器会把模型位置、尺寸、网格、特征树和实体完整性反馈给机械制图专家,再进行一次生成后视觉复核;格式、计划或执行失败时会把真实错误回传模型继续纠错。界面会显示 API 调用次数、总耗时、当前阶段和视觉复核结论 +- **Agent 调试窗口**:AI 面板顶部可打开调试窗口,以 OpenAI/DeepSeek 兼容 SSE 流式方式实时查看模型增量输出,并查看每次请求的阶段、请求消息、原始响应、DSL 校验、文档预检、执行错误、视觉复核和纠错记录;支持日志内标准 `Ctrl+C`,以及 `Ctrl+Shift+C` 复制全部调试文本 - **高级宏指令**:倒角矩形、圆角矩形、多边形、抽壳、镜像、线性阵列、圆周阵列、凸台等一键生成 - **文档能力**:保存 / 打开(JSON)、导出 STL、撤销 / 重做 -- **SolidWorks 风格界面**:可拖拽调整的 Ribbon / 特征树 / 属性面板,可移动的 AI 浮动面板,四向视图切换 +- **参考几何**:偏移/角度/过点基准面、基准轴、基准点和用户坐标系,草图可挂载到自定义参考面 +- **拉伸终止条件**:固定深度、贯穿全部、对称、到下一面、到指定面和到实体;目标面引用随文档保存 +- **实体边处理**:视口直接拾取凸直棱;倒角用真实网格截切,圆角用多段圆柱过渡面,均通过 manifold 内核闭合验证 +- **SolidWorks 风格界面**:可拖拽调整的 Ribbon / 特征树 / 属性面板,可移动的 AI 浮动面板,四向视图切换;首次生成或打开实体时按包围盒自动取景,保持真实 mm 工程尺寸,不用“缩放模型”冒充适配视口 ## 快速开始 ### 方式一:双击启动脚本(推荐) -双击 `启动.bat`,脚本会自动启动本地服务器并打开浏览器,访问 `http://127.0.0.1:8898/index.html`。关闭黑色命令行窗口即停止服务器。 +双击当前项目目录中的 `启动.bat`,脚本会从脚本所在目录启动本地服务器并打开浏览器。默认端口为 `8898`;如果该端口已被旧服务器占用,会自动选择下一个空闲端口。关闭黑色命令行窗口即停止服务器。 > 注意:不要直接双击 `index.html` 用 `file://` 协议打开——ES Module 和 WASM 会被浏览器安全策略拦截,页面会卡在加载页。 ### 方式二:手动启动 ```bash -cd F:\Program Files\MiniSW +cd E:\MiniSW python -m http.server 8898 ``` @@ -45,6 +54,11 @@ python -m http.server 8898 | 输入 | 生成结果 | |---|---| | 做一个 100×100×20 的方块,中心开一个半径15的圆孔 | 底板 + 中心通孔 | +| 查询当前模型有哪些特征和尺寸 | 返回实体尺寸、草图和特征清单 | +| 把加强筋厚度改为6mm | 定位加强筋并全树重建 | +| 删除最后一个特征 | 删除末尾特征,并清理未使用草图 | +| 在当前零件上添加一个直径20mm的通孔 | 仅追加孔草图与切除,不重建底板 | +| 你能看到什么?请描述当前模型 | 返回实体状态、尺寸、体积、网格和特征观察结果 | | 空心圆柱,外径60,内径40,高50 | 空心圆柱 | | 做一个 200×120×10 的板,中间开一个 60×40 的矩形槽,四角打 4 个直径6的孔 | 槽板 + 四角孔 | | 做一个 100×80×20 的矩形板,四边倒角 5mm | 倒角板 | @@ -53,6 +67,13 @@ python -m http.server 8898 | 做一个直径120厚10的圆盘,打一个直径10的孔距离中心40,圆周阵列6个 | 圆周阵列孔 | | 做一个 100×80×10 的底板,顶面中心加一个直径30高20的圆柱凸台 | 底板 + 凸台 | | 做一个 100×60×10 的板,左侧凸台镜像到右侧 | 镜像特征 | +| 沿半径50的四分之一圆弧扫描直径8的圆形截面 | 90° 弯管实体 | +| 从40×30的矩形放样到直径20的圆形,高度50 | 矩形—圆形过渡体 | +| 创建距离前视面10mm的偏移基准面 | 偏移参考面 | +| 做一个100×60×10的底板,加一条厚3mm、筋高30mm的加强筋 | 底板 + 加强筋 | +| 做一个40×30×20的方块,给一条实体棱边倒角5mm | 单棱实体倒角 | +| 做一个40×30×20的方块,给一条实体棱边做R5圆角 | 多段实体圆角 | +| 生成一个120×80×12的轴承座,主体凸台直径60高32,中心通孔直径30,两条厚4mm加强筋,实体倒角和R4实体圆角 | 第一阶段综合轴承座 | ## 项目结构 @@ -63,6 +84,8 @@ MiniSW/ ├── css/ │ └── style.css # 全部样式(含可拖拽面板的 resizer) ├── js/ +│ ├── features/ # 扫描、放样、筋、参考几何、终止条件、倒角/圆角、装配、工程图 +│ ├── constraints.js # 轻量 2D 约束求解与参数表达式 │ ├── main.js # 应用主控:Ribbon、面板交互、事件绑定 │ ├── doc.js # 零件文档模型:草图/特征/重建/序列化/撤销 │ ├── geometry.js # manifold 几何内核封装:布尔/拉伸/旋转/圆柱 @@ -88,13 +111,13 @@ MiniSW/ node --loader ./tests/loader.mjs ./tests/test.mjs ``` -测试覆盖:WASM 加载、底板 + 四角孔手动建模、包围盒校验、STL 导出、旋转特征、AI 自然语言解析等。 +测试覆盖:WASM 加载、底板 + 四角孔手动建模、包围盒校验、STL 导出、旋转、扫描、放样、筋、参考几何、六种拉伸终止条件、实体倒角/圆角与 AI 一句话轴承座等。 ### 架构说明 - **几何内核**:manifold-3d WASM。`geometry.js` 统一封装实体构造(`buildExtrude` / `buildRevolve` / `buildCylinder`)与布尔运算(`booleanOp`),并负责 WASM 对象生命周期管理(`delete()`) - **文档模型**:`doc.js` 的 `Doc` 类持有草图与特征的有序列表,`rebuild()` 按顺序重建实体,支持增量网格(`deltaMeshes`,用于特征高亮) -- **AI 链路**:`agent.js` 先做中文规则匹配生成操作序列(ops),再经宏展开层(`expandOps`)扩展为底层操作,最终由 `executeOps` 写入文档并重建;LLM 模式则把 ops 交给模型规划 +- **AI 链路**:`agent.js` 先判断自然语言意图(新建 / 追加 / 查询 / 修改 / 删除),再生成操作序列或文档 CRUD 请求;`inspectDocument` 汇总实体状态、尺寸、体积、网格和特征增量作为自观察;几何请求经宏展开层(`expandOps`)交给 `executeOps`,重建出现特征错误时会回滚并自动修正一次;LLM 模式则把几何 ops 与观察上下文交给模型规划 - **坐标系统**:草图平面(前视 / 上视 / 右视)通过 `math.js` 的 `toWorld` 映射到世界系,拉伸方向由平面法向决定 ### 依赖本地化 @@ -104,14 +127,21 @@ node --loader ./tests/loader.mjs ./tests/test.mjs ## 已知限制 - 圆形分段数固定为 96(`js/sketch.js`),已满足绝大多数教学场景;极端大半径圆弧如需更光滑可调高该值(会相应增加面数) -- 目前仅支持单一实体(多体、装配、工程图在规划中,见 `ROADMAP.md`) -- 约束系统为数据结构层面,尚无数值求解器(尺寸标注暂不驱动几何) +- 装配体当前以当前文档实体作为组件来源,配合求解以轻量平移约束为主;跨文件零件库、完整旋转自由度和高级动画仍在后续路线图中 +- 扫描支持路径草图、单环任意封闭截面、固定/平行传递标架与扭转;带孔多环截面目前不支持自动封端 +- 放样支持平行单环多截面和异顶点数重采样;带孔截面及非平行截面尚未支持 +- `toBody` 的独立几何模块支持显式目标实体;当前单实体文档中的稳定目标名为 `current`,跨独立实体引用将在多实体文档阶段开放 +- 实体倒角/圆角当前只接受方向一致、封闭凸实体上的一条凸直棱;多边选择、边链、凹边和非凸体会明确报错。圆角是可导出 STL 的多段圆柱面近似,不是精确 NURBS/B-rep 圆角 +- 约束求解器为可解释的轻量 2D 迭代实现,支持常用教学约束与欠/过约束报告;尚不等同于工业级全局求解器 +- 工程图轮廓来自网格棱边投影,剖视采用三角形裁剪与剖面线;复杂曲面隐藏线和精确 B-rep 标注仍可继续增强 - LLM 模式需自行配置 API 地址与密钥(AI 面板 ⚙ 设置) ## 路线图 进阶规划(扫描 / 放样 / 筋 / 实体圆角、约束求解器与尺寸驱动、装配体、工程图)详见 [ROADMAP.md](ROADMAP.md)。 +第一阶段的最终浏览器与课程演练步骤见 [PHASE1_M10_MANUAL_CHECKLIST.md](PHASE1_M10_MANUAL_CHECKLIST.md)。 + ## 许可与声明 本项目为教学演示性质,仅用于学习与实验用途。SolidWorks 为 Dassault Systèmes 的注册商标,本项目与其无任何关联。 diff --git a/ROADMAP.md b/ROADMAP.md index 5e966be..428f78e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,6 +2,65 @@ 版本:v1.0 · 日期:2026-08-10 · 编写:总工程师(审阅视角) +## 当前实施进度(2026-08-10) + +- **M01~M09 已完成生产接入。** 扫描、放样、参考几何、筋、六种拉伸终止模式、真实网格实体倒角和多段圆柱面实体圆角均已接入文档、AI DSL、保存/打开和界面参数编辑;倒角/圆角支持视口拾取与稳定边引用。 +- 测试直接调用当前仓库的 `js/` 与 `vendor/`,不依赖旧外部源码副本;当前核心综合测试为 **94/94 通过**,七个第一阶段独立模块测试为 **93/93 通过**,合计 **187/187**。 +- M10 自动化部分已通过,最终浏览器演练由用户按 [`PHASE1_M10_MANUAL_CHECKLIST.md`](PHASE1_M10_MANUAL_CHECKLIST.md) 手动确认。实体倒角/圆角当前采用经过内核验证的保守范围:封闭凸实体的一条直棱;多边链、凹边和非凸体会明确拒绝,不以平滑伪装成功。 +- **阶段二至阶段四核心闭环已完成(2026-08-10)**:参数化草图、装配体、工程图均已接入文档模型、AI DSL、保存/打开和界面工作区;工程图支持 SVG/PNG 导出,装配体支持组件树、基础配合、干涉检查和爆炸视图。 +- 阶段二至四自动化验收:`test.phase2.mjs` 8/8、`test.assembly.mjs` 5/5、`test.drawing.mjs` 4/4、`test.phase34.mjs` 16/16;Agent 质量优先多轮编排(第一阶段 Markdown 工程架构 → 意图 JSON → DSL)、结构化 JSON、兼容接口降级、参数化直齿轮编译、模糊对象默认工程假设、视觉反馈复核、DeepSeek/OpenAI 兼容 SSE 流式调试、DSL 容错与增删改查回归 `test.agent-crud.mjs` 42/42、核心 `test.mjs` 94/94,合计 **169/169**(不含阶段一独立报告中的历史计数)。 + +### 阶段二至四交付边界 + +- 约束求解器是可解释的轻量 2D 迭代求解器,支持欠约束/完全约束/过约束报告;当前不承诺工业级全局最优求解。 +- 装配体支持当前文档实体插入、稳定组件引用、基础平移配合、包围盒预筛与可用时的实体布尔复核;高级旋转自由度和跨文件引用仍属于后续增强项。 +- 工程图使用网格棱边投影生成矢量视图,剖视使用三角形裁剪与剖面线;已满足课程作业交付所需的三视图、标注、标题栏和 SVG/PNG 输出,复杂曲面隐藏线算法仍可继续增强。 + +## 可执行里程碑拆分 + +执行规则:一次只推进一个里程碑。每个里程碑必须完成核心实现、文档模型接入、AI DSL/规则解析、保存/打开兼容、错误提示、自动化测试和本文档进度更新,才可以勾选完成。浏览器录像集中在每个阶段的综合验收任务中进行,避免重复消耗资源。 + +### 阶段一:高级零件建模 + +- [x] **M01 圆形截面基础扫描**:圆形截面沿直线、离散三维路径和四分之一圆弧扫描;支持封端、AI 指令和保存/打开。当前核心测试 51/51 通过。 +- [x] **M02 扫描完整版**:支持路径草图、任意封闭截面草图、截面与路径引用、方向控制和扭转控制。 +- [x] **M03 放样基础版**:支持两个平行、等顶点数的封闭截面放样,完成基本顶点配对和端面封闭。 +- [x] **M04 放样完整版**:支持三个及以上截面、异顶点数均匀重采样、截面起点对齐和自交诊断。 +- [x] **M05 筋特征**:支持开放轮廓、厚度、双侧/单侧方向、延伸到相邻实体并自动合并。 +- [x] **M06 参考几何**:增加偏移/角度基准面、两点/两平面基准轴、基准点与坐标系,并允许草图引用。 +- [x] **M07 拉伸终止条件**:为拉伸和切除增加 `toNext`、`toFace`、`toBody`,保留现有 `blind`、`throughAll` 和 `symmetric`。 +- [x] **M08 实体棱边倒角**:建立拓扑边引用与拾取,支持距离参数、特征重建、AI 指令和失败诊断;当前安全范围为凸实体单条直棱。 +- [x] **M09 实体棱边圆角**:支持半径参数、多段真实圆弧过渡、拓扑边引用、特征重建、AI 指令和失败诊断;当前安全范围为凸实体单条直棱。 +- [ ] **M10 阶段一综合验收**:用 AI 一句话生成轴承座(底板、凸台、通孔、筋、圆角),检查几何内存生命周期和性能预算,并录制课程作业演练。 + +### 阶段二:参数化草图 + +- [x] **M11 基础约束求解器**:实现固定、水平、垂直、重合和距离约束,建立残差方程、迭代求解与收敛报告。 +- [x] **M12 完整几何约束**:增加平行、垂直、相等、同心、相切、角度和半径约束,并检测欠约束、完全约束和过约束状态。 +- [x] **M13 新草图图元**:增加椭圆、长圆槽和样条曲线,支持绘制、编辑、离散化和轮廓检测。 +- [x] **M14 命名尺寸与参数表**:增加命名尺寸、表达式解析、单位校验和特征参数引用,修改参数后可全模型联动重建。 +- [x] **M15 草图阵列及阶段二验收**:实现草图线性/圆周阵列;完成“修改矩形尺寸驱动全部关联特征”的参数化闭环与自动化验收。 + +### 阶段三:装配体 + +- [x] **M16 多实体与零件引用基础**:扩展文档模型以安全表达多个实体、独立零件文档和稳定的面/边/轴引用。 +- [x] **M17 装配数据模型与装配树**:新增组件、变换和配合数据结构,支持零件插入、固定、隐藏、选择和装配树显示。 +- [x] **M18 配合约束求解**:实现重合、同心、距离、平行、垂直和角度配合,并保留合理自由度。 +- [x] **M19 干涉检查**:使用包围盒预筛与实体布尔交集检测干涉,显示干涉体积并高亮冲突区域。 +- [x] **M20 爆炸视图及阶段三验收**:实现组件爆炸方向、距离和动画;完成组件插入、配合、干涉与爆炸视图演练。 + +### 阶段四:工程图 + +- [x] **M21 图纸文档与图框**:建立工程图文档、视图和标注模型,提供 A3/A4 横竖图框、标题栏、比例与单位设置。 +- [x] **M22 第一角投影三视图**:从零件生成主视、俯视和左视轮廓,按第一角投影规则自动布局并支持比例调整。 +- [x] **M23 尺寸、公差与参数引用**:将模型命名尺寸映射到工程图,支持线性、角度、直径、半径尺寸及基础 ± 公差。 +- [x] **M24 剖视图**:实现裁剪平面、全剖视图、剖面轮廓和剖面线,保持与三维模型关联更新。 +- [x] **M25 工程图导出及阶段四验收**:支持 SVG/PNG 导出;完成任意零件一键生成 A4 三视图、尺寸和标题栏的课程交付演练。 + +### 最终收尾 + +- [ ] **M26 全项目交付验收**:按几何、约束、装配、工程图拆分测试套件;检查性能、WASM 内存、文件兼容、离线运行、错误恢复和文档完整性,完成代表性课程作业总演练。 + ## 1. 定位与目标 MiniSW 目前的定位是"能跑通基础草图和特征链路的原型",与 SolidWorks 的差距不在渲染层(Three.js 已足够),而在**建模能力覆盖**和**工程语义完整度**。 diff --git a/SUBPROXY_TASKS.md b/SUBPROXY_TASKS.md deleted file mode 100644 index e3447ae..0000000 --- a/SUBPROXY_TASKS.md +++ /dev/null @@ -1,152 +0,0 @@ -# MiniSW 并行子代理任务书 - -版本:v1.0 · 日期:2026-08-09 · 适用范围:MiniSW 项目全部子代理 - -## 1. 项目背景 - -MiniSW 是一款 AI 原生的 Web 参数化建模软件,参照 SolidWorks 的工作流(草图 → 特征 → 特征树 → 参数化重建)从零实现。核心差异点是"AI Agent 全自动建模":用户输入中文描述,系统解析为建模指令并自动完成建模。 - -技术栈:原生 JavaScript ESM,无打包器。Three.js r160(渲染)、manifold-3d 3.5.1(WebAssembly 布尔内核)、CSS2DRenderer(标注)、自定义中文规则解析器 + 可选 OpenAI 兼容 LLM 智能体。 - -运行方式:本地 HTTP 服务器打开 `index.html`(`python -m http.server 8899`,目录 `f:\Program Files\MiniSW`)。 - -## 2. 当前进度 - -已完成并通过 Node 冒烟测试(16/16 断言通过)的链路: - -- 几何内核:`math.js`(坐标系/平面映射)、`sketch.js`(图元/封闭环检测)、`geometry.js`(拉伸/旋转/圆柱/布尔/STL)、`doc.js`(文档模型/顺序重建) -- AI 解析器:`agent.js` 的中文规则解析路径(12 个 op 序列端到端建模成功) - -已编写但**未在真实浏览器验证**的模块:`main.js`(UI 主入口,约 1000+ 行)、`sketchui.js`(草图交互)、`viewport.js`(3D 视口)、`tree.js`(特征树)、`css/style.css`、`index.html`。 - -最高风险:`main.js` 从未在浏览器中实际运行,存在运行时错误、事件绑定失效、模块集成问题等未知风险。因此**浏览器联调(任务 0)是全部并行分支的前置依赖**。 - -## 3. 文件地图与归属 - -| 文件 | 归属 | 说明 | -|---|---|---| -| `js/math.js` | 任务 A | 坐标系、平面映射、几何工具(已稳定,可加固) | -| `js/sketch.js` | 任务 A | 图元、封闭环检测、轮廓校验(已稳定,可加固) | -| `js/geometry.js` | 任务 A | 特征构造、布尔运算、STL(已稳定,可加固) | -| `js/doc.js` | 任务 A | 文档模型、重建、序列化(已稳定,可加固) | -| `js/main.js` | 任务 B | UI 主入口,Ribbon/面板/状态机/AI 面板(最大风险文件) | -| `js/sketchui.js` | 任务 B | 草图交互工具(画线/矩形/圆/圆弧/标注/约束) | -| `js/viewport.js` | 任务 B | 3D 视口、导航、拾取、网格、高亮 | -| `js/tree.js` | 任务 B | 特征树 UI | -| `css/style.css` | 任务 B | SolidWorks 风格样式 | -| `index.html` | 任务 C | 页面骨架、AI 面板容器、依赖加载(import map) | -| `js/agent.js` | 任务 C | AI 解析器、DSL、LLM 接入 | -| `js/geometry.js` | 任务 0 | 联调发现的核心缺陷可修(需与 A 协调) | -| `js/main.js` | 任务 0 | 联调主战场(需与 B 协调) | -| 测试目录 `c:\Users\32919\.trae-cn\work\6a78857c0515f1e48ec3076f\` | 任务 0/A | Node 冒烟测试 `test.mjs`、`loader.mjs`、`swjs/`(工作区源码副本) | - -## 4. 通用协作规则 - -1. **文件互斥**:每个任务只能修改归属自己的文件。跨文件修改必须明确写入交接说明,交给主代理协调,禁止自行修改他人文件。 -2. **接口契约**:模块间通过命名导出通信(如 `geometry.js` 的 `buildExtrude/booleanOp/meshFromManifold`,`doc.js` 的 `Doc` 类)。改动签名必须同步更新所有调用方,并在报告中标明。 -3. **测试优先**:任何修改先跑测试(Node 冒烟测试或浏览器自动化),确认回归后再提交。 -4. **报告格式**:任务结束时报告必须包含:改动文件清单、新增/修复的功能点、测试结果(命令 + 断言数)、遗留问题、对下游任务的交接说明。 -5. **禁止事项**:不得重命名或移动他人文件;不得改变 `index.html` 的 import map 结构(任务 C 专属);不得删除 `swjs/` 副本的同步机制(改完工作区文件后同步 `Copy-Item` 到 `swjs/`)。 - -## 5. 任务 0:浏览器联调(前置,串行) - -**目标**:让 `main.js` 在真实浏览器中跑通,消除最高风险。 - -**职责**: - -1. 启动本地服务器,用浏览器自动化(或人工引导)加载 `index.html`,收集控制台错误。 -2. 逐项修复运行时错误:模块导入失败、事件绑定失效、初始化顺序错误、DOM 引用缺失。 -3. 验证最小闭环:新建草图 → 画矩形 → 拉伸 → 3D 显示 → 保存 → 加载 → STL 导出。 -4. 验证 AI 面板最小闭环:输入中文描述 → 生成建模指令 → 重建 → 视口刷新。 -5. 修复过程中发现的几何内核缺陷(`geometry.js`/`doc.js`)可以直接修,但需在报告中单独列出,供任务 A 复核。 - -**文件边界**:`js/main.js` 为主;`js/geometry.js`、`js/doc.js` 仅限修 bug;`index.html` 仅限修加载问题(不改 AI 面板结构);不得改动 `js/sketchui.js`、`js/viewport.js`、`js/tree.js` 的 API 签名。 - -**验收标准**: - -- 页面加载无控制台报错。 -- 上述两条最小闭环全部可手动操作完成。 -- 修复清单与交接说明写入报告。 - -## 6. 任务 A:特征引擎加固 - -**目标**:扩充特征引擎的健壮性与能力边界,把 Node 冒烟测试扩展到 25+ 断言。 - -**职责**: - -1. 扩充 `test.mjs`:拉伸"贯穿所有/对称"、旋转轴显式指定、多环轮廓(含嵌套孔)、圆柱原语、撤销重做栈(`Doc` 层)、STL 导出的二进制头与三角数校验。 -2. 修复测试暴露的缺陷:环方向、退化轮廓、极端尺寸、浮点边界。 -3. 加固 `buildRevolve` 的旋转轴校验与截面跨越检测。 -4. 为 `doc.js` 增加重建缓存的正确释放逻辑验证(manifold 对象的 delete 生命周期)。 -5. 保持 `geometry.js` 的导出签名不变。 - -**文件边界**:`js/math.js`、`js/sketch.js`、`js/geometry.js`、`js/doc.js`、测试目录(`test.mjs` 等)。不得改动 `js/main.js` 及任何 UI 文件。 - -**验收标准**: - -- Node 冒烟测试 25+ 断言全绿(`node --experimental-loader ./loader.mjs test.mjs`)。 -- 现有 16 条断言无回归。 -- 报告列出每个新增用例覆盖的场景与修复的缺陷。 - -## 7. 任务 B:UI 交互完善 - -**目标**:把 UI 从"能加载"提升到"可完整操作",对标 SolidWorks 的交互心智。 - -**职责**: - -1. 草图工具全流程:画线/矩形/圆/圆弧、标注编辑(双击)、裁剪、捕捉、约束的交互逻辑走查与修复。 -2. 特征工作流:属性面板(拉伸/切除/旋转表单)、特征树右键菜单(编辑草图/删除/编辑特征)、视图切换(前视/上视/右视/等轴测)。 -3. 3D 交互:拾取高亮、特征增量网格显示、相机操作(OrbitControls)的默认行为修正。 -4. 状态栏、Ribbon 分区、撤销/重做按钮的可用性打磨。 -5. 在浏览器自动化中走通 3 条完整用户路径并截图留证。 - -**文件边界**:`js/main.js`、`js/sketchui.js`、`js/viewport.js`、`js/tree.js`、`css/style.css`。不得改动 `js/geometry.js`、`js/doc.js` 的导出签名(如需新接口,向主代理申请,由任务 A 实现)。`index.html` 的 AI 面板区域不归本任务。 - -**验收标准**: - -- 3 条完整用户路径(草图→拉伸→保存;草图→切除→撤销→重做;视图切换+拾取高亮)在浏览器自动化中全部通过。 -- 无新增控制台错误。 -- 报告包含操作路径清单与截图。 - -## 8. 任务 C:AI 智能体扩展 - -**目标**:把 AI 从"基础指令解析"扩展到"覆盖常见建模意图的智能体"。 - -**职责**: - -1. 扩展 `agent.js` 的 DSL 与中文规则解析:倒角/圆角、阵列(线性/圆周)、镜像、抽壳、多草图组合等指令。 -2. 扩展 `executeOps` 到新指令的执行支持(与 `geometry.js` 对接的接口若缺,向主代理申请由任务 A 实现)。 -3. LLM 路径完善:OpenAI 兼容 API 接入、JSON 输出校验、失败重试与错误提示、多轮对话(基于当前模型状态的上下文)。 -4. AI 面板体验:指令历史、一键执行、执行结果反馈(成功/失败原因)。 -5. 离线可用性保障:无 LLM 时规则解析器仍能处理常见句式。 - -**文件边界**:`js/agent.js`、`index.html`(仅 AI 面板区域)、`js/main.js` 中 AI 面板相关的事件处理函数(如需)。不得改动 `js/sketch.js`、`js/geometry.js`、`js/doc.js`、`js/sketchui.js`、`js/viewport.js`、`js/tree.js`。 - -**验收标准**: - -- 10 条覆盖新指令的中文描述端到端建模成功(Node 或浏览器验证)。 -- 无效指令给出明确的中文错误信息,不崩溃。 -- 报告列出新增 DSL 指令、句式规则与测试用例。 - -## 9. 依赖关系与执行顺序 - -``` -任务 0(浏览器联调,串行前置) - │ - ├──→ 任务 A(特征引擎加固,可并行启动) - ├──→ 任务 B(UI 交互完善,依赖 0 的联调结果) - └──→ 任务 C(AI 智能体扩展,依赖 0 的最小闭环) -``` - -启动顺序建议:先完成任务 0,再并行启动 A、B、C。若任务 0 无法完整完成(环境限制),则 A 可立即并行(不依赖浏览器),B、C 的验收改为"代码走查 + 局部验证"。 - -任务间的接口申请统一走主代理:B 或 C 需要新几何接口时,提交接口规格(名称、参数、返回值、语义),主代理转交 A 实现后,B/C 再集成。 - -## 10. 验收总表 - -| 任务 | 核心验收 | 完成标志 | -|---|---|---| -| 0 浏览器联调 | 页面无报错,两条最小闭环可操作 | 修复清单 + 交接说明 | -| A 特征引擎 | Node 测试 25+ 断言全绿,16 条无回归 | 测试报告 + 缺陷清单 | -| B UI 交互 | 3 条完整用户路径浏览器自动化通过 | 操作路径 + 截图 | -| C AI 智能体 | 10 条新指令端到端建模成功 | DSL 指令表 + 测试用例 | diff --git a/TASK_0_REPORT.md b/TASK_0_REPORT.md deleted file mode 100644 index 8eccc4b..0000000 --- a/TASK_0_REPORT.md +++ /dev/null @@ -1,88 +0,0 @@ -# MiniSW 任务 0 报告:浏览器联调 - -版本:v1.0 · 日期:2026-08-09 · 执行者:主代理(任务 0) - -## 1. 目标 - -把任务 A/B/C 的成果在真实浏览器环境中跑通,验证两条完整闭环并修复联调中发现的问题: - -- 最小建模闭环:新建草图 → 画矩形 → 拉伸 → 3D 显示 → 保存 → 加载 → STL 导出 -- AI 面板闭环:自然语言 → 解析 → 特征生成 → 建模 → 导出 - -## 2. 测试环境 - -| 项 | 值 | -|---|---| -| 服务器 | `python -m http.server 8899`(目录 `f:\Program Files\MiniSW`) | -| 浏览器 | agent-browser(Chromium headless,CDP 驱动) | -| 页面 | http://localhost:8899 | -| 依赖加载 | three.js、OrbitControls、manifold.wasm 均从 CDN 正常加载(仅 favicon 404,无害) | - -## 3. 闭环验证结果 - -### 3.1 最小建模闭环 ✅ - -| 步骤 | 操作 | 结果 | -|---|---|---| -| 1 | 进入"前视草图",选矩形工具 | 草图模式激活,HUD 显示"草图1 · 前视" | -| 2 | 视口绘制矩形(pointerdown × 2) | HUD 更新"1 实体" | -| 3 | 拉伸凸台,深度 20,确定 | 特征树新增"拉伸",状态栏"1 草图 · 1 特征 · 尺寸 225.989×135.594×20 mm" | -| 4 | 3D 显示验证 | WebGL 持续绘制(1.5s 内 3318 次 drawElements),视口区域实测含实体着色像素 | -| 5 | 保存 | 捕获 JSON 完整:1 草图、1 特征(extrude depth=20)、矩形 226×135.6 | -| 6 | 加载 | 文件上传后重建正确,尺寸精确还原(100×60×25 mm) | -| 7 | STL 导出 | 12 三角形(长方体 6 面×2),文件长度 = 84+50×12 精确匹配 | - -### 3.2 AI 面板闭环 ✅ - -| 示例输入 | 结果 | -|---|---| -| 120×80×15 底板 + 四角直径8孔 + 中心半径12孔 | 3 草图 3 特征,尺寸 120×80×15 正确 | -| 空心圆柱,外径60,内径40,高50 | 圆柱 + 内孔,外径 60 → 半径 30 正确 | -| 200×120×10 板 + 60×40 矩形槽 + 四角直径6孔 | 3 草图 3 特征(主体/矩形槽/四角孔),尺寸正确 | -| 自定义输入"60×40×12 板 + 中心直径10圆孔" | 特征树正确新增板 + 孔 | -| 复合模型 STL 导出 | 1072 三角形,文件长度精确匹配 | - -### 3.3 视口交互 ✅ - -鼠标拖拽旋转验证:视口区域 30.3% 像素变化,OrbitControls 正常工作。 - -## 4. 联调发现并修复的问题 - -| # | 文件 | 问题 | 修复 | -|---|---|---|---| -| 1 | `js/main.js` | 隐藏的 file input 未挂载 DOM,自动化/外部代码无法触发"打开" | 挂载到 body 并保持 `display:none` | -| 2 | `js/agent.js` | 槽尺寸正则 `[槽坑凹][^\d]*(\d+)...` 只在"槽"字后找数字,"60×40 的矩形槽"解析失败,槽未生成 | 支持数字在槽字前或后(双分支正则) | -| 3 | `js/agent.js` | "中间开一个矩形槽"被中心孔正则 `/中心\|中间\|.../` 误触发,生成假中心孔 | 位置词后 12 字符内必须出现"孔"才触发 | -| 4 | `js/agent.js` | 四角孔正则 `四角各?一个?[^\d]*?(?:直径\|半径)?(\d+)` 匹配到数量"4 个"而非直径"6" | 优先匹配"直径N / 半径N"字样,数量词兜底 | -| 5 | `js/agent.js` | "外径60 / 内径40"未识别,空心圆柱半径回退默认 20(实际应为 30) | 新增 `outerDia` / `innerDia` 解析,圆柱分支优先使用 | -| 6 | `js/geometry.js` | STL 80 字节 header 全为 0 | 写入 "MiniSW binary STL" + ISO 时间戳标识 | - -## 5. 测试结果 - -Node 回归测试从 38 条扩充到 **43 条全部通过**,新增 5 条针对联调修复的断言: - -| 新增断言 | 覆盖 | -|---|---| -| 矩形槽尺寸 60×40 解析正确 | 槽正则修复 | -| 中间开槽不误判为中心孔 | 中心孔误触发修复 | -| 四角孔直径6 → 半径3 × 4 | 四角孔正则修复 | -| 外径60 → 半径30 | 外径解析 | -| 内径40 → 内孔半径20 | 内径解析 | - -## 6. 遗留问题 - -| 问题 | 严重性 | 说明 | -|---|---|---| -| 多次 AI 生成叠加建模,无"新文档"入口 | 低 | 连续 AI 指令在同一文档累积特征,尺寸显示为整体包围盒(联调中曾误判为解析错误,实际是叠加结果) | -| LLM 路径未联调 | 中 | LLM 配置(`runLLM`)依赖外部 API Key,本次联调仅验证内置解析器路径 | -| 视口拾取精度 | 低 | Raycaster 拾取在 headless 下未做像素级验证,依赖任务 B 的 DOM 断言结果 | - -## 7. 验收对照 - -| 验收标准 | 状态 | -|---|---| -| 最小闭环:草图 → 拉伸 → 3D → 保存 → 加载 → STL | ✅ 全链路通过 | -| AI 面板闭环:自然语言 → 建模 | ✅ 4 组输入通过 | -| 控制台无错误 | ✅ 仅 favicon 404(无害) | -| 修复问题回归 | ✅ 43 条断言全部通过 | -| 报告 | ✅ 本文档 + TASK_A_REPORT.md | diff --git a/TASK_A_REPORT.md b/TASK_A_REPORT.md deleted file mode 100644 index 7f1f690..0000000 --- a/TASK_A_REPORT.md +++ /dev/null @@ -1,92 +0,0 @@ -# MiniSW 任务 A 报告:特征引擎加固 - -版本:v1.0 · 日期:2026-08-09 · 执行者:子代理 A - -## 1. 目标 - -在任务 B 的 UI 基础上加固底层特征引擎,补齐旋转、切除、布尔运算的边界情况,建立文档层撤销/恢复能力,并把 Node 冒烟测试从 16 条断言扩充到 38 条,为核心几何链路提供回归保障。 - -## 2. 改动文件清单 - -| 文件 | 改动类型 | 说明 | -|---|---|---| -| `js/doc.js` | 增强 | 新增 `snapshot()` / `restore()` 文档层快照;`deleteSketch()` 增加"被特征引用则拒绝删除"保护 | -| `js/geometry.js` | 加固 | `buildRevolve` 新增旋转轴退化/跨轴/轴不存在三类校验;`buildExtrude` 新增非流形状态检查 | -| `tests/test.mjs` | 大幅扩充 | 断言从 16 条扩充到 38 条,覆盖旋转、切除、嵌套轮廓、布尔叠加、快照、错误路径、delete 生命周期 | - -## 3. 特征引擎加固点 - -### 3.1 旋转特征(buildRevolve) - -| 加固项 | 说明 | 失败表现 | -|---|---|---| -| 旋转轴退化 | 轴线实体长度 `< 1e-6` 时报错,避免 `atan2` 除零 | `旋转轴退化` | -| 截面跨轴校验 | 截面点集同时分布在 u>0 与 u<0 两侧时报错(manifold revolve 对跨轴截面行为未定义) | `旋转截面跨越了旋转轴,请把截面放在轴的一侧` | -| 显式轴存在性 | `axisEntityId` 指定的实体不存在时报错,而不是静默回退 | `指定的旋转轴实体不存在` | -| 轴类型约束 | 仅 `line` / `centerline` 可作为轴,其他类型忽略并回退默认轴 | — | - -### 3.2 拉伸特征(buildExtrude) - -在 `CrossSection.extrude()` 后检查 `tool.status()`,非 `NoError` 时释放资源并抛错,避免把非流形实体带入后续布尔运算。 - -### 3.3 文档层快照(Doc.snapshot / restore) - -- `snapshot()` 复用 `toJSON()`,几何缓存(`bodyManifold` / `bodyMesh` / `deltaMeshes` / `bbox`)不参与序列化 -- `restore(snap)` 从 JSON 重建文档结构并清空几何缓存,调用方随后执行 `rebuild()` 刷新几何 -- 该机制被任务 B 的撤销/重做栈复用,也供未来编辑特征参数后回滚使用 - -### 3.4 草图删除保护 - -`deleteSketch(id)` 增加引用检查:若有特征仍引用该草图,返回 `false` 拒绝删除,防止重建时出现"草图不存在"级联错误。 - -## 4. 测试结果 - -### 4.1 测试命令 - -```bash -cd c:\Users\32919\.trae-cn\work\6a78857c0515f1e48ec3076f -node --experimental-loader ./loader.mjs test.mjs -``` - -### 4.2 结果 - -**通过: 38,失败: 0 — 全部通过** - -### 4.3 测试覆盖清单 - -| # | 场景 | 断言要点 | -|---|---|---| -| 1 | 几何内核初始化 | manifold wasm 加载成功 | -| 2 | 手动建模:底板 + 四角孔 | 重建无错误、实体网格、包围盒 120×80×15、特征增量网格存在、STL 导出 | -| 3 | 旋转特征(中心线作轴) | 旋转重建无错误、生成实体、外径 80 | -| 4 | AI 解析器 → op 序列 → 执行 | 解析识别、校验通过、重建无错误、特征数 ≥3 | -| 5 | 空心圆柱 | 圆柱 + 内孔两个特征、重建无错误 | -| 6 | 对称拉伸 | z∈[-5,5] 对称 | -| 7 | 贯穿所有切除 | 体积 60000-π·25·10 误差 <1% | -| 8 | 显式旋转轴 | 外径 50 正确 | -| 9 | 嵌套矩形孔(多环轮廓) | 体积 72000(100×80×10 - 40×20×10) | -| 10 | 圆柱原语 | 包围盒 40×40×50 | -| 11 | 圆柱凸台叠加(顺序布尔) | 凸台顶部 z=15、合并体积 = 60000+π·225·5 | -| 12 | 文档层快照/撤销恢复 | 快照1 → 1 特征;快照2 → 2 特征,均重建无错误 | -| 13 | 旋转截面跨越旋转轴 | 报错信息包含"旋转轴" | -| 14 | 退化轮廓 | 单线段拉伸被拦截报错 | -| 15 | STL 二进制头部 | 三角数一致、文件长度 = 84+50×N | -| 16 | 重复重建稳定性(delete 生命周期) | 连续 3 次重建体积一致 | -| 17 | 显式旋转轴不存在 | 报错信息包含"轴" | - -## 5. 关键修复记录 - -| 问题 | 原因 | 修复 | -|---|---|---| -| revolve 跨轴截面产生垃圾几何 | manifold `revolve` 对跨轴截面行为未定义 | 重建前遍历截面点,u 轴两侧均有分布时抛错 | -| `axisEntityId` 指向不存在的实体时静默回退 | 未校验轴实体存在性 | 增加显式存在性检查并抛错 | -| 旋转轴退化为点导致 `atan2` 异常 | 轴实体两端点重合 | 长度 `< 1e-6` 时报"旋转轴退化" | -| `deleteSketch` 删除被引用的草图导致重建崩溃 | 无引用检查 | 返回 `false` 拒绝删除 | -| 拉伸非流形实体进入布尔运算 | 未检查 `tool.status()` | 非 `NoError` 时抛错并释放 | - -## 6. 对下游任务的交接说明 - -- **doc.js / geometry.js 接口未变**:新增方法均为增量(`snapshot` / `restore`),原有导出签名不变 -- **`deltaMeshes` 契约**:`rebuild()` 始终填充每个特征的增量网格,供任务 B 的高亮/拾取消费 -- **错误特征标记**:单个特征失败时仅标记 `f.error`,不中断整体重建,后续特征继续执行 -- **任务 0 依赖**:快照/恢复、delete 生命周期、错误路径等已在 Node 层验证,需在浏览器联调中复核实际 UI 触发路径(撤销/重做按钮、特征删除右键菜单) diff --git a/TASK_B_REPORT.md b/TASK_B_REPORT.md deleted file mode 100644 index 865dc4d..0000000 --- a/TASK_B_REPORT.md +++ /dev/null @@ -1,125 +0,0 @@ -# MiniSW 任务 B 报告:UI 交互完善 - -版本:v1.0 · 日期:2026-08-09 · 执行者:子代理 B - -## 1. 目标 - -把 UI 从"能加载"提升到"可完整操作",对标 SolidWorks 的交互心智。完善草图工具、特征工作流、3D 交互、状态栏/Ribbon 打磨,并在浏览器自动化中走通 3 条完整用户路径。 - -## 2. 改动文件清单 - -| 文件 | 改动类型 | 说明 | -|---|---|---| -| `js/main.js` | 修复+增强 | 运行时缺陷修复、撤销/重做按钮状态管理、特征工作流完善、草图预检查、AI 操作入栈 | -| `js/sketchui.js` | 修复 | 实体选择交互修复、约束应用逻辑修正 | -| `js/viewport.js` | 修复+增强 | toNDC 坐标转换修复(getBoundingClientRect 后备)、几何/材质内存释放、高亮渲染 | -| `js/tree.js` | 增强 | 特征右键菜单新增"编辑草图"、单击草图节点进入编辑 | -| `css/style.css` | 修复+增强 | CSS 选择器修正(.rib-tabs → .ribbon-tabs)、HUD 样式、工具按钮激活态 | -| `index.html` | 无改动 | — | - -## 3. 新增/修复的功能点 - -### 3.1 运行时缺陷修复 -- **main.js `rebuildAndRefresh()` 空指针**:`ui.sk` 可能为 null,增加 `ui.sk && doc.sketchById(ui.sk.id)` 双重检查 -- **main.js `updateStatus()` 未调用**:在 `rebuildAndRefresh()` 中补充调用 -- **CSS 选择器错误**:`.rib-tabs` 修正为 `.ribbon-tabs` -- **工具按钮激活态**:为 `tool:` 类型按钮补充 `data-tool` 属性,确保 `updateToolButtons()` 正确高亮当前工具 -- **特征按钮草图模式禁用**:草图模式下拉伸/切除/旋转按钮标记 `bad` 类 - -### 3.2 草图工具交互 -- **实体列表选择**:属性面板中实体列表项点击可选中对应实体,同步 `ui.sel.ents` 并刷新视口 -- **矩形/圆/圆弧绘制**:验证 PointerEvent 事件链(pointerdown → pointermove → pointerdown)正确触发绘制 -- **工具提示**:每个草图工具切换时显示操作提示(如"矩形:单击一角→拖到对角") - -### 3.3 特征工作流 -- **拉伸/切除/旋转表单**:属性面板显示参数表单(深度、双向、反向等),确定/取消按钮 -- **特征树右键菜单**:特征节点右键弹出"编辑草图/编辑特征参数/删除特征" -- **草图节点单击编辑**:特征树中单击草图节点直接进入草图编辑模式 -- **特征预检查**:创建特征前检查是否在草图模式、草图是否有实体 - -### 3.4 撤销/重做 -- **按钮状态管理**:`updateUndoRedo()` 根据栈深度启用/禁用按钮 -- **AI 操作入栈**:`sendAI()` 在执行 AI 指令前 `pushUndo()`,失败时从栈中弹出并恢复 -- **特征创建入栈**:新建草图、创建特征、裁剪、约束等操作前均 `pushUndo()` - -### 3.5 3D 视口 -- **toNDC 坐标转换修复**:`getBoundingClientRect()` 在部分浏览器环境下返回 0 宽高,增加 `clientWidth/clientHeight` + offset 链遍历后备 -- **内存释放**:`updateBody()` 和 `highlightFeature()` 中正确 dispose 旧 geometry/material,防止内存泄漏 -- **拾取高亮**:`pickFeature()` 使用 Raycaster 对 pickMeshes 求交,命中后 `highlightFeature()` 渲染半透明橙色高亮 - -### 3.6 状态栏与 HUD -- **HUD 显示**:草图模式显示草图名/平面/实体数/约束数/标注数;模型模式显示"3D 模型模式" -- **状态栏**:显示草图数/特征数/错误数/包围盒尺寸 -- **重建反馈**:重建后显示"重建完成"或错误信息 - -## 4. 测试结果 - -### 4.1 测试环境 -- 浏览器:集成浏览器自动化(Chromium 内核) -- 服务器:`python -m http.server 8899`(目录 `f:\Program Files\MiniSW`) -- 页面加载:无控制台错误 - -### 4.2 三条用户路径验证 - -#### 路径 1:草图 → 拉伸 → 保存 ✅ -| 步骤 | 操作 | 结果 | -|---|---|---| -| 1 | 点击"前视草图" | 草图1 创建,进入草图模式,HUD 显示"草图1 · 前视 0 实体" | -| 2 | 选择"矩形"工具,绘制矩形 | HUD 更新为"1 实体",属性面板显示矩形 #2 | -| 3 | 点击"拉伸凸台",深度 10mm,确定 | 特征树新增"拉伸",状态栏"1 草图 · 1 特征 · 尺寸 129.775×162.219×10 mm" | -| 4 | 点击"保存" | 无错误(JSON 序列化 + Blob 下载触发) | - -#### 路径 2:草图 → 切除 → 撤销 → 重做 ✅ -| 步骤 | 操作 | 结果 | -|---|---|---| -| 1 | 新建前视草图2,画圆 | HUD 显示"草图2 · 前视 1 实体" | -| 2 | 点击"拉伸切除",确定 | 特征树新增"切除",状态"2 草图 · 2 特征" | -| 3 | 点击"撤销" | 切除消失,状态"2 草图 · 1 特征",重做按钮启用 | -| 4 | 点击"重做" | 切除恢复,状态"2 草图 · 2 特征",重做按钮禁用 | - -#### 路径 3:视图切换 + 拾取高亮 ✅ -| 步骤 | 操作 | 结果 | -|---|---|---| -| 1 | 切换到"视图"选项卡 | 显示前视/上视/右视/等轴测按钮 | -| 2 | 依次点击 4 个视图按钮 | 全部无错误,相机视角切换 | -| 3 | 特征树点击"拉伸"节点 | 节点选中(sel 类),属性面板显示特征参数表单 | -| 4 | 3D 视口拾取 | 见遗留问题 | - -### 4.3 控制台状态 -全部测试过程中**无控制台错误**。 - -## 5. 遗留问题 - -### 5.1 3D 视口拾取(环境限制) -在集成浏览器自动化环境中,`getBoundingClientRect()` 偶发返回 0 宽高,已通过 offset 链后备修复坐标转换。但 WebGL 渲染缓冲在不可见标签页中可能不完整渲染,导致 Raycaster 求交结果不稳定。特征树拾取高亮路径已验证可用,3D 视口直接拾取需在可见浏览器窗口中进一步验证。 - -### 5.2 截图限制 -集成浏览器自动化在当前环境中无法截图(标签页不可见),3 条路径的验证结果以 DOM 状态断言替代截图。 - -### 5.3 草图工具交互细节 -- 矩形工具为"两次点击"模式(click-move-click),非拖拽模式,与 SolidWorks 的拖拽矩形有差异 -- 圆弧工具需要三次点击确定,无预览圆心标记 - -## 6. 对下游任务的交接说明 - -### 6.1 对任务 A(特征引擎) -- `doc.deltaMeshes` 在 `rebuildAndRefresh()` 中被 `vp.setDeltas()` 和 `vp.buildPickTargets()` 消费,请确保 `doc.rebuild()` 正确填充 `deltaMeshes`(每个特征的增量网格) -- `doc.toJSON()` / `doc.fromJSON()` 已被保存/加载功能调用,请确保序列化格式稳定 - -### 6.2 对任务 C(AI 智能体) -- AI 操作已接入撤销栈:`sendAI()` 中 `pushUndo()` → `executeOps()` → 失败时 `restore(snap)` -- `executeOps()` 异常时会从 undoStack 弹出快照并恢复,确保 AI 指令失败不影响模型状态 -- AI 面板的示例按钮(chip)data-ex 属性包含中文描述,可直接被 `parseNaturalLanguage()` 消费 - -### 6.3 对任务 0(浏览器联调) -- 页面加载无控制台错误 -- `toNDC()` 的 `getBoundingClientRect()` 后备逻辑需在真实浏览器中验证(当前环境的间歇性 0 值问题可能不存在于真实浏览器) -- 所有 Ribbon 按钮、草图工具、特征表单、特征树交互均已验证无运行时错误 - -## 7. 验收对照 - -| 验收标准 | 状态 | 说明 | -|---|---|---| -| 3 条完整用户路径浏览器自动化通过 | ✅ | 路径 1/2 全通过,路径 3 视图切换+特征树拾取通过 | -| 无新增控制台错误 | ✅ | 全程 0 控制台错误 | -| 报告包含操作路径清单与截图 | ⚠️ | 操作路径清单完整;截图因环境限制以 DOM 断言替代 | diff --git a/TASK_C_REPORT.md b/TASK_C_REPORT.md deleted file mode 100644 index ee85ca5..0000000 --- a/TASK_C_REPORT.md +++ /dev/null @@ -1,155 +0,0 @@ -# 任务 C 完成报告:AI 智能体扩展 - -版本:v1.0 · 日期:2026-08-10 · 任务:C(AI 智能体扩展) - -## 1. 改动文件清单 - -| 文件 | 改动类型 | 说明 | -|---|---|---| -| `js/agent.js` | 重大扩展 | 新增 7 种高级 DSL 指令、宏展开引擎、解析器增强、LLM 多轮上下文 | -| `index.html` | 修改 | AI 面板新增 10 个示例按钮、清空对话按钮 | -| `js/main.js` | 修改 | AI 面板事件处理增强:历史记录、执行反馈、LLM 上下文传递 | - -## 2. 新增 DSL 指令 - -### 2.1 高级 op(宏展开层) - -| 指令 | 参数 | 展开为 | 说明 | -|---|---|---|---| -| `chamfer_rect` | cx, cy, w, h, chamfer | 8 段直线轮廓 | 倒角矩形(四角 45° 斜角) | -| `fillet_rect` | cx, cy, w, h, fillet | 直线+圆弧点列轮廓 | 圆角矩形(四角圆弧) | -| `polygon` | points[[u,v],...] | 顺序连线 | 自定义多边形 | -| `shell` | w, h, d, wall, plane | 外凸台+内腔切除 | 抽壳盒体 | -| `mirror` | axis(x\|y) | 复制上一特征并镜像 | 镜像复制 | -| `pattern_linear` | count, dx, dy | 复制上一特征并平移 | 线性阵列 | -| `pattern_circular` | count, cx, cy | 复制上一特征并旋转 | 圆周阵列 | - -### 2.2 宏展开引擎(expandOps) - -- 位于 `validateOps` / `executeOps` 之前,将高级 op 展开为已有原语序列 -- **无需修改 geometry.js / doc.js 的接口契约** -- 幂等设计:已经是原语的 op 原样通过 -- 特征组查找(lastFeatureGroup):自动识别上一个特征及其所属草图和实体 - -### 2.3 解析器增强(parseNaturalLanguage) - -新增关键词识别: -- 倒角:`/倒角/` + 支持 "Nmm倒角" 和 "倒角N" 两种句式 -- 圆角:`/圆角|过渡圆/` + 支持 "半径N" 作为回退 -- 抽壳:`/抽壳|空心盒|中空盒/` -- 线性阵列:`/线性阵列|直线阵列|排成一行|排成一列/` -- 圆周阵列:`/圆周阵列|环形阵列|圆周均布|环形均布/` -- 镜像:`/镜像|对称复制/` -- 凸台:`/凸台|凸起|加.*圆柱|顶.*圆柱|加.*凸/` - -特征冲突防护: -- 四角孔仅在 `!hasFillet && !hasChamfer` 时生成(避免与圆角/倒角冲突) -- 中心孔仅在 `!hasBoss` 时生成(避免凸台直径被误用为孔径) -- 纯抽壳路径仅在无附加特征时触发(`!hasHole && !hasChamfer && !hasFillet && !hasMirror && !hasBoss`) - -### 2.4 LLM 智能体完善 - -- OpenAI 兼容 API 接入(`callLLMAPI`) -- JSON 输出校验(`extractOps` + `validateOps`) -- 失败重试机制(maxRetries=1,带修正提示) -- 多轮对话上下文(`modelStateSummary` + `aiHistory`,最近 6 条) -- 离线保障:无 LLM 配置时自动回退到内置解析器 - -## 3. 测试结果 - -### 3.1 测试命令 - -```bash -cd c:\Users\32919\.trae-cn\work\6a788fba0515f1e48ec30a23 -node test_agent.mjs -``` - -### 3.2 测试结果 - -**通过: 103,失败: 0 — 全部通过** - -### 3.3 测试覆盖 - -#### 10 条新指令端到端测试 - -| # | 测试名称 | 输入描述 | 原始指令 | 展开后 op | 特征数 | -|---|---|---|---|---|---| -| 1 | 倒角矩形拉伸 | 100×80×20 矩形板,四边倒角5mm | 3 | 10 | 1 | -| 2 | 圆角矩形拉伸 | 100×80×20 矩形,四角圆角半径8 | 3 | 27 | 1 | -| 3 | 抽壳盒体 | 100×80×40 抽壳盒子,壁厚3mm | 1 | 6 | 2 | -| 4 | 线性阵列孔 | 200×100×10 板,直径10通孔,线性阵列4个间距40 | 7 | 15 | 5 | -| 5 | 圆周阵列孔 | 直径120厚10圆盘,直径10孔距中心40,圆周阵列6个 | 5 | 19 | 7 | -| 6 | 圆柱凸台 | 100×80×10 底板,直径30高20圆柱凸台 | 4 | 4 | 2 | -| 7 | 倒角+中心孔 | 80×80×20 带5mm倒角方块,中心直径20通孔 | 6 | 13 | 2 | -| 8 | 圆角+抽壳 | 100×80×30 圆角盒子,半径10,抽壳壁厚2 | 6 | 30 | 2 | -| 9 | 线性阵列+镜像 | 150×100×10 板,直径8孔,线性阵列3个间距50,镜像 | 8 | 15 | 5 | -| 10 | 凸台+镜像 | 100×60×10 板,凸台直径20高15在x-30,镜像到右侧 | 5 | 5 | 3 | - -#### 回归测试(3 条原有指令) - -| 输入 | 特征数 | 结果 | -|---|---|---| -| 120×80×15 底板,四角直径8孔,中心半径12孔 | 3 | 通过 | -| 空心圆柱,外径60,内径40,高50 | 2 | 通过 | -| 200×120×10 板,60×40 矩形槽,四角4个直径6孔 | 3 | 通过 | - -#### 无效指令容错测试(4 条) - -| 输入 | 结果 | -|---|---| -| "asdfghjkl" | 正确返回 null | -| "" | 正确返回 null | -| "你好" | 正确返回 null | -| "做一个不知道什么东西的模型" | 正确返回 null | - -#### 高级 op 直接构造测试 - -| 测试 | 结果 | -|---|---| -| polygon 五边形展开为 5 段直线 | 通过 | -| mirror 无前置特征 → 校验拦截 | 通过 | -| pattern_circular 无前置特征 → 校验拦截 | 通过 | - -## 4. 关键修复记录 - -| 问题 | 原因 | 修复 | -|---|---|---| -| mirrorEntityOp 崩溃 | rect/circle op 使用 `cx`/`cy` 属性,但镜像函数错误访问 `op.c[neg]`(c 数组是 executeOps 后的内部格式) | 改为 `if (axis === 'x') op.cx = -op.cx; else op.cy = -op.cy;` | -| validateOps 对无前置特征的 mirror/pattern 抛异常 | `expandOps` 内部 throw 未被捕获 | 用 try-catch 包裹,返回错误字符串 | -| 圆角盒子 "半径10" 未被提取 | "半径10" 与 "圆角" 分离,正则无法匹配 | 添加 `?? radius` 作为回退 | -| 四角圆角误触发四角孔 | 四角孔条件未排除圆角/倒角场景 | 添加 `!hasFillet && !hasChamfer` 条件 | -| 抽壳盒子走了板路径 | 纯抽壳条件未排除附加特征 | 添加 `!hasMirror && !hasBoss` 等条件 | -| 凸台直径被误用于中心孔 | 中心孔条件未排除凸台场景 | 添加 `!hasBoss` 条件 | -| 倒角 "5mm倒角" 句式未匹配 | 正则只匹配 "倒角N",不匹配 "Nmm倒角" | 添加反向匹配 `(\\d+)\\s*(?:mm)?\\s*倒角` | - -## 5. index.html AI 面板更新 - -新增 10 个示例按钮(chip),覆盖所有新指令: -- 底板+孔、空心圆柱、槽+孔(原有) -- 倒角、圆角、抽壳、线性阵列、圆周阵列、凸台、镜像(新增) - -新增清空对话按钮(↻),重置 AI 历史记录。 - -## 6. main.js AI 面板更新 - -- `aiHistory` 数组:维护最近 12 条对话(6 轮),用于 LLM 多轮上下文 -- `modelStateSummary()`:生成当前模型状态摘要,传递给 LLM -- `expandOps` 集成:执行前展开高级 op,反馈中显示原始/展开后 op 数量 -- 执行结果反馈:成功/失败原因、特征列表、错误特征数 - -## 7. 遗留问题 - -| 问题 | 严重性 | 说明 | -|---|---|---| -| 矩形槽尺寸提取正则有 `[^\\d]` bug | 低 | 在 regex literal 中 `\\d` 匹配字面 "\d" 而非数字,但中文场景下不影响功能 | -| "外径"/"内径" 未作为直径/半径别名 | 低 | "空心圆柱,外径60,内径40" 中外径/内径未提取,使用默认值;特征数正确但尺寸不准 | -| 镜像只复制最后一个特征 | 设计限制 | 用户期望镜像整个特征组时需手动指定;当前设计为镜像最后单个特征 | -| LLM 路径未经浏览器实测 | 中 | LLM 集成代码已完成但未在真实浏览器环境中验证(依赖任务 0 联调) | - -## 8. 对下游任务的交接说明 - -- **agent.js 导出接口未变**:`parseNaturalLanguage`、`expandOps`、`validateOps`、`executeOps`、`runLLM`、`getLLMSettings`、`setLLMSettings`、`DSL_SPEC` 均保持原有签名 -- **新增导出**:`expandOps` 为新增导出函数,main.js 已集成调用 -- **doc.js / geometry.js 未修改**:所有新指令通过宏展开为已有原语,不涉及底层接口变更 -- **index.html import map 未改动**:仅修改 AI 面板 DOM 结构 -- **任务 0 依赖**:main.js 的 AI 面板事件处理需在浏览器联调中验证 diff --git a/assets/solidworks/appearance-textures/ash_bump.jpg b/assets/solidworks/appearance-textures/ash_bump.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2be4d53701c2a1f128dfd6241af55dff2ba023c5 GIT binary patch literal 66661 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 literal 0 HcmV?d00001 diff --git a/assets/solidworks/appearance-textures/birch_bump.jpg b/assets/solidworks/appearance-textures/birch_bump.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2f3b4832e3a4d6d3d0bd2b8033bc03ae5c444d47 GIT binary patch literal 61012 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�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

4GsNss|g!&sBTdwOofiGNH@2 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%0D1&(-@gIk}Y^ehmYc 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%0AA4j%7YucCycHV; zMru3lE$5Wbx?rfAidu`!yX@840MmpGocHHIueij|Gr|wz({SUz2&sWmT&GVJmDV~s^!ydO|k4)%-p>B@1V#B~l zZKu8EoD#Zxt9GwM^^h*Z9)CFY$b>E!>gISWHVll^cG_FcDWS`^YWGT159u=O@yBD2 zOz484ZjQHN!~9S#wVl>-9aBP=Z`E#;e~L<%VUIr@dt^cv40Ut76&nUdYCh@dObK1S z)w4u>AJS#mLFc*J^pg+kqKQe)Xni$Y#11+?X)?9B;*jfsxuy zd&@Z`boo~8UWw`rqVgf8Ez-6;PQl`g{`|2X!@gf1BB=6EYM42;x#($kp|x_ql= ziTXaI%dp4C7X#g6^GxW1p^n7k{`-Rs10%Ja_LkEglgqbi_exX`=`!r`&ts2F=z^hc zj<;gNz)0-iRvL;hCLpRJu;yShPpZ4iVXuJwVn2s zb4uv)t=hd3)kC@rdwhEAkqKQe)Xni$Y#11+?X|8wk-30*MM&GA-j7#OMTw6~m7LKh5mB&vsW z8TNR2rO-Y8{gDY>Fw~KF+<$+tVSXr=nooK<_Q-@T80t=WE9o-q;lIuG?u0HF>PS=% z|4l6zslDyH6S`oiBT?UnbQ$*e{7TK=M<#T^PzR%Wuwh`Nw$t8n`h83;->Tg!arNMH z8SeZ4o@u_Xw|hKK=;G(<{h6HH$_>AUflIwro>RiTY=6D~$>+(ihc{WBZl@lTYcbwV zRF4k;p)Gu!9$8p$Wa(UaZ zU(y~Vp5F@IL;bu{Q$p8ztG5%?0~h1xs&6+`jy*D=%eP8@_aI@9)UWF2owCQ|f-xkj z2OEYSsUL3Z$8p$Wa`{$azofS!VUN_W>*t-C61rfhBT+rrF!V_M>b8Cyhdm~jZx!}S zdMgt4Nd2aM-l-{}3x+xp)q@Q~kJPVk>&J1}V{-XcVZWreB4LlzFRnj}`FYQj&;>&s ziR!_Ip+{7s=e)6uEQRg z(B)gT8|AH}%kYNpAA4j%7YucCycHV;Mrv=nx13W#mv7bXm8kDSx{Uk9M1OGXkqKQe z)Xni$Y#2XRZKt(d$CS|JTeTbI_aR+|J#NPyna~A8-5hVlhJlgVetXL~C3N{#?Ouuc zKBUXIUrh8z#~zu`1w-8&Z^ef3bJccQ%XLf%UA|SjQGOrNW!Qs%@}Hh5p$mpO67~Ds z&w-KJe(z4`@~zs9@>bGi@N4wP#~zu`1w-8&UzZKz=c?_rmg|@jx_qm4qwFDFhCLpR zJu;yShPpZ4iVXuJwf**%b4uv)t=hd3_4`A*jQhnzzdZKHgf1BB=6EYMjGwEv(^{@$ zO6c;f+Kux2kS@a>UmSa6LKh5mbG#KB21aW8?Jeh&(B)gTdnM}okS@a>Umkm8LKh5m zbG#KB21aT-?Jeh&(B)gTdnKxebQ$(|b?lJ|T`<(m@m6dY7^&^Fx13W#mv7bXm8c%l zW!x_&`m1A)Oz484ZjQHN!}z&sJFVq9ri3ovs@*7me@K^MkFSqCGNB8Gx;fs84Fe;! z{q~k~O6c;f+PxC>eMpyKk8h4WGNB8Gx;fs84Fe;!o%WV-9aBP= zZ`E#;zdxkQu*Y}D9+}VuL){#2#fE{A+J1Y>IVE)YR_$Ji`aYz~u*dhu9+}VuL){#2 z#fE{A+D?1RIVE)YR_$Ji>LFc*J>DF9WI`7Vb#uHG8wN&dJMAs!l+fi{wR*dr6VV5pnpt=KSruG&s(xsEBJ%eQJb%HJQ-W!U4#V~-iRvL;hCN9I#9biq(J$6K*sV5GLw-f~U}T`<&&PHVll^eA3gg$K--BB&vsW8TNSZO3ghop$mq(Z|1GoFfdYk+r8zS61rfh zBT+r1%dp4KuhiTl6S`oigHhiH8wN&dJMAr}JtmiL)$Wz39@1sl;}^#sna~A8-5hVl zhJlgV+wLvrl+fi{wR=<==Fy%NewR_x?rfA3x>Km-ii$aBek9OmUBwzf}xH?^^h*Z9>2L#=pO$sfK2Fu zp^n7k{=ZAuFfdZ{Nl(WflMBX>s2*nYUuYz)0s^!ydoAQge??=z^gRMtvV_7#OMTw6~o0m|VV9yH}!mNS9%c-yM5oLKh5mbG#KB z21aUcySJQELYHsV?v(q-7=_s1TY&;>)?9B;*jfsxuyd&@Z`boo~8UWw`-iRvL;hCTjp?2!pwFx1WQR%{p;sqM74oKr%V zZ`JOVs2GZev0-4O_O^S=IVE(#P)DMA zNS9%cKVPZ2M<#T^PzR&F4>k;p)OOliPJ2u)->Tg!Q9Y!~u*YAHJu;yShPpZ4iVXuJ zwYS|{&MBeGw`%uFR1fJg?D5xQk4)%-p>B@1V#B~lZKu8EoD#Zxt9GwM^^h*Z9`|F9 zOz484ZjQHN!@x*wr@iHz61se=cCSSBkS@a>e>?Wbgf1BB=6EYM42;xv+FQ;kq06^w z_exX`=`!r`_hXMt=z^hcj<;gNz({SUz2%$|x?rdyQ9Y!~u*W~H6uQU1KQf^UhB^|D z`|l4n42;x#($lfWLH=B?N;Fj9Nlz2%$|x?rdyQ9Y!~ zu*W~I)Z8Nzx?re-QQrp}21aT-?JcK0CYNv3?v(q-7=lVgue=z^hcj<;gNz)0)0a`x?rfAK=-b%U*dwhPS<{p{Q1w$Q->cNJAk(y6> zI`)`cFowkSeek&q_x;Z^P0zP`JWuH2=j#2LoZQL{zlMQJy;Yu5!o6&Nz5mJQ$*_kv zS)FdD9+PV^-cD4H7uSaIbJccQ%XQcz6S}-@yHRYDF2hc@+o=bP=Po{JNK_9t42;w- zZtKT!*kf{e+pu5K9weUM3f@Egyi-#`*LthB6V(G3gS!Z$K--BB&r7+h90ROZtKT!*kf|}R$;%Sw<2MW)UWI3othH5V5lQeJ=iexNd4-z zejJBACYNs&_Dgyz681>_rheY3DWMC7Iug}`4MUIAuW#$eaoA&W`Bq`Sq_-krkJK-& zKa2T!&y>&wLmi3g!G@tnYIsk_9+S(rdY1UG{=BOD%D7*@M!#1-@02}YJa@qu64iqZ z-iTXaI%eY@m^hd`Yna~A8-5hVlhVgUNc3R7IObK1SRl8AsAJS#mgMad$ zo++UVhB^}U``pigk=lOmPU!Nj+Kuv7(q-^#^vA~@na~A8-5g(+4ddsk?X;Hbm=d~t zt9GO8Azg+&9*#XSp$mq(Io^s510%Kl_Lg%>=<==Fy%P2NL%NLn#YDe6_Q-@T80zMD zD>jUutG3fxu478*@~zs9^81i3!yaE8dt^cv40Ut76&nUdYWwXi=akUpTeW*7>idu` z!yaEAdt^cv40Ut76&nUdYCG*M=akUpTeW*7s)uwL_IP#dkqKQe)Xni$Y#11+?XF5jx%D1U!Qmtl{uk3BM> z3x>Km-ii$aBenhZmUBwz@~zsv67_vZmtl`@jy*D=3x>Km-ii$aBek9OmUBwz@~zsv z64gVx412sj_Q-@T80zMDD>e*_)OOli&MBeGw`%uFR1fJg?iUmN?XgEDbiq(J$6K*s z{9Lu2)^Z(FLYHsVZj`@2q|30!cgG%?&;>)?9B;*jfsxvNd&@Z`boo~8UWxiXq|30! z_s1TY&;>)?9B;*jfsxuyd&@Z`boo~8UWw`biq(Z;<5g>CAQbVz{SYhzw)%lE! z>gISWHVll^-ga*}r-Uxws@*G5--mP=_W0?sM<#T^P&db0v0-4Ow$t8nP6=JURl8TB zdPtXHkDncTWI`7Vb#uHG8|FXdQrl@Q*D)n@`Bv>l`QIz)GVJl*u}3C!!B98HTd`qa zq~?>J&XmyQTRltE_aR+|J$`;M&^?;m?)LKh5mbG#KB21aT-?Jeh&(B)gTdnKxebQ$*e)v-q=biq(J z$6K*sV5GLw-f~U}UA|SjSE71Imtl`zAA4j%7YucCycHV;Mru3lE$5Wb(q-7=_s1TY&;>)?9B;*jfsxuyd&@Z` zboo~8UWw`-iRvL;hCTjp?2!pwFx1WQ zR%{p;sqM74oKr%VZ`JOVs2LFc* zJ^pm;kqKQe)Xni$Y?%L)OKqpMT*s8q-iRvL;hCTjr z?2!pwFx1WQR%{p;slDyqa!v_dzE!(dqIyV|VUNEadt^cv40Ut76&nUdYCG*M=akUp zTeW*7s)uwL_P8H=WI`7Vb#uHG8wN&dJMAs!l+fi{wR|2p=_gf1BB=6EYM42;xv+FQ;kq06^w z_exX`=`!r`aO{x@T`<(m@m6dY7^&^Fx13W#mv7bXm8c%lW!U4>V~s^!ycam$MdnKxebQ$(|dF+u1T`<(m@m6dY7^%JO z-f~U}UA|SjSE71Imtl|pb?lJ|T`<(m@m6dY7^&^Fx13W#7YubIs)uwL_W1lt&Hug1 zgf1BBVAOxF*f9Somzqy{I`+tfE*R=g@!xhnm*Kwu-!skr?HK7- z;8Jgu=ag_S+h6a0@_91s;Z0Vj+o{LoT8y_7)#JsrVfg`1Jz{U8v z>e~&KV~WACTZR3S-im}hQopI6cWO%Lf}xH?^=Bbiq(ZqI$4l=#d)U)3L|o@~xgFKCC~l>b^4W z*RRp<)z3R+4;ar~Fos0+V8i&iYHz!i>##>Aboo~8MtLjgGQ8pY#~zu`1w-8&Z^edz zk=ontE$5Wbl;4MR8TR0x{HJG1=z^h+MEySZb6}*l-@6mKe5-b&yp?nr{2Kl7u}3C! z!B98H*JZ=_xoSJDF5jx%D0@hkVULGnk4)%-p>B@1V#B~lZNI(coD#Zxt9GwM z{r-?H<9;#GFONMkp$mq(Io^s5)?9B;*j zfsxvNd&@Z`boo~8UWxiXq|30!m&YEN&;>)?9B;*jfsxuyd&@Z`boo~8UWw`ua7-4p$mq(Io^s510%Ja_Lg%>=<==Fy%NjUutG3fxu478* z@~zs9^7n^y8TR<`*dr6VV5pnpt=KRyQrmBDIj4j!->Tg!QQwDj8TR<^u}3C!!B98H zTd`qaq_)%Ea!v_dzE!(dqIyV|VUHIV6W!xCmE0 zE4}-l-b%U*d;H|$%{?-q3x>Km-ii$aBel2PTh1w=%eQLxO4RouU4}h=dhC%2T`<(m z@m6dY7^&^Fx13W#mv7bXm8c%lW!U3q#~zu`1w-8&Z^edzk=jms%Q+=*@+PxChL%Ixm{PNf% z6S`oio8zt6FfdYk+r8zS61se=cCSSBkS@a>?;m?)LKh5mbG#KB21aT-?Jeh&(B)gT zdnKxebQ$*e)v-q=biq(J$6K*sV5GLw-f~U}UA|SjSE71Imtl`zAA4j%7YucCycHV; zMru3lE$5WbPS59|NDv!10yw`^mOboxnK;5>LFc*J$`$o<{p{Q1w-98^Hyva7^%JO z-f~U}T`<&Km-ii$aBek9OmUBwz@~zsv z64gVx414_H*dr6VV5pnpt=KRyQrl^7Ij4j!->Tg!Q9Y!~u*V;dJu;yShPpZ4iVXuJ zwVn2sb4uv)t=hd3)kC@rd;ICxBNMt{sGH-h*f20s+i7n(r-UvT>PS=%=`!r`(Un5? z`1eO9biq(Z;&K1|!G?j6nooK<_Ly8ShD7y{F2f#wzEX3KOz484?wfflHVll^-ga*} zr-UvT>PS=%=`!r`mn${*$b>E!>R{CO!G?j6+D?1RX^+X}TeW*7s)uwL_W0|uM<#T^ zP&db0v0-4O_O^S=IVE)YR_$Ji>LFc*J?_UIna~A8-5hVlhJlgVPJ7EaC3N{#?OuuM zAzg+&{&wt<30*MM&GA-j7#OMTw6~m7LYHsV?v(q-7=@5dgQ&;>)?9B;*jfsxuy zd&@Z`boo~8UWw`YE*L|idPtXHkAGgNxkn~+!BF?jycHV;Mrv=nx13W# z7YubIs)uwL_W0yV%{?-q3x+xv^?k5mV5GLw-g4Swa`{&6UWw`s^ z!yca=dt^cv40Ut76&nUdYCG*M=akUpTeW*7s)uwL_W11BBNMt{sGH-h*f20s+i7n( zr-Uxws@*G5J*3O9hyONLr~Gp_6S`oiBk{QZ+-1YSNNuOR<+R7-@~zsv64gVx412u1 znCKpRWI`7VbtI|>8wN&dZ@agg_LyA0Rl8TBdPtXH5B`m+xkn~+!B98HTlsHi!AR|G z-<{CqTeUlTNS9%c&yPJap$mq(Ird<~z({SUz2%$|x?rdyaeW_rF2mh_e5Uzt(c9n0 z^Mo#buHK)?$*tV*YZ$oHTje<=+{^aY`=5NC410K!)#-NXF}W7w?L_r>acvksS8b=Y zT!%d}q08I08^t#1GVFA_oqE7{?&6b%MD<|9z)1b#wtgIkJtmj84f`eSLE`zX;62pO zJ2fSAt+#qRQ9W=mey;j{b6S{n>^mh*u_DKDze%>j2OfDEhqI$4l=#l#2wtgIk zJtmiL74}PdD-!ld{kneMsVSighB^|}gAGHE)UR&q$8p$Wa`{$azofS!VUN^r>gS!B z61rfhBT+rrF!V_M`nG-?hdm~jZx!}SdMgt4Nd4mavzVXvObJ~u)RCwjY#4f^hWB*r zF}ZxJXNeE%&#StxjQjO#^n3O5PT2#-a~F&uQ9al&ey-ZvuH`!HkqKSCRl8B%O1cbh z`2Mj+CUn72H^*DCVPK^8wtLGtC3N{#?OuucKBUXIUrh7|#~zu`1w-8&Z^ef3bJccQ z%XLf%UA|SjQGOrNW!U3(?2!pwFx1WQR%{p;sqMG7oKr%VZ`JOVsP99%jQhnze{}4T z30*MM&GA-j7(Z8Sr?p(il+fi{wHxL4Azg+&_$UAAnG(8Ss3TFo&;1-2sqOdfgf8Ez z-6(G*T?W5Ke|+qb30*MM&GB{FFn+GuPHVZ2DWS`^YB$Or(q-7=;n*V+x?rfAB@1V#D~kYCEmvI;Mm!->TgxzYpm$ z?D561M<#T^P&db0v0-4Ow%^`zP6=JURl8TBz7Od#?D6HXM<#T^P&db0v0-4Ow$t8n zP6=JURl8TBdPtXHk5|VYna~A8-5hVlhJlgVPJ7EaC3N{#?OuuMAzjA(Vxqq~_Q-@T z80zMDD>jUutG3fxu478*@~zs9^7n^y8TR=4*dr6VV5pnpt=KRyQrmBDIj4j!->Tg! zQQwDj8TR<**dr6VV5pnpt=KRyQrl^7Ij4j!->Tg!Q9Y!~u*d6Tk4)%-p>B@1V#B~l zZKu8EoD#Zxt9GwM^^h*(elgMC9(!a$7YucCycHY9&sE!LE!Qz6boo~8M)~_gx(s`K zckGb~T`<(m@m6dY7^&^Ix13W#mv7bXm8kDSx(s`Kf9#P7T`<(m@m6g9f4Vaq>8h!On$>5o^?!TSPj}zTy~BZ#+D>cAeM;!^t=c^k)kC@rdwg;2 zkqKQe)Xi}#CL9>4?X;%cr-Uxws@*eDJ*3Nc8cg(mu01lL3x>KmZpDP-=c?^Ahx0lm zboo~8M)~_gx(s`KdF_!2T`<(maVsVq7^&^Irrf85F5jx%Gg04%bQ$*e>e?d{x?rfA z<5o;KFjCuTO}S4AUA|SjXQFyYmtl`LgNg3rH<$@sFw~KFuK#U`={PWOapbLEdE4XU z@~!l)f4Y@)8TR&wbiq(J$E}!fV5GLwnsT2Kx_qm4&qVc* zF2f$5Tzh0f7YucC+=>b3%W|phG>7v#C3N{#?MC^hsB{_j_~ErjCUn72H^;4*aA2h7 zlU|-vLYHs#DpB8ubQ$*e(O{ta*gO-uV5lSUy#D@R!hw<6PHW0-kCV%{YWGZ359u=O z@#AZcOz484ZjM_q;lN1kwrk3LO6c;f+C3B1L%IxmJgz-5p$mq(Ic~*-10%Ja)|C5{ z(B)gTdnT%fbQ$*e>9t2Dbiq(J$E}!fV5GLwnsT2Kx_qm4&qVc*F2f!_yY|S0E*R?O zxD^u)jMR2oQ|?njmv7bXnW!GpW!U5A*B+VB1w-8&w_?J9k=jmc%6&@c@~zrE6V*ey z410Wf?U4yxFx1U)D<+&T%cZu{9M0>M(B)gT8|9y((q-7=7uOz{&;>)?9JgY^fsvX| zdU;L>UB1<;M13F9W!U4FgMsd2^GxW1p^n7!`ul?k2S#c;ttq!XPA=c7-7`@=q|30! zudY2Zp$mq(Ic~*-10%KDt||8^q06^w_e@j|=`!r`bnTG|T`<(maVsVq7^&^Frrf85 zF5jx%Gf_RH%dp3KmZpDNHBek8@l>3y>s^!ydoA_Q-@T80zM@6%!7O)OK1^?o&dSZ`JOZs2B>_G2wh!F14NJa9*c`F5jx%DE}0dF2f$byY|S0E*R?OxD^u)jMRM6 z%X3QT@~vJa>idu`!ydmM40IoxXF?YYbtInG-ycjkFjCuTO}XuHa`{&6o{8!qU4}jW zaP5%^T`<(maVsVq7^&TMO}S4AUA|SjXQFyYmtl{$*B+VB1w-8&w_?J9k=jmc%6&@c z@~zrE6V*ey414_X+9MOXV5pnpR!lfBQrl@wxlajQzE!(tqIyV|VUIsudt^cv40Utd ziU|isYCEkd_bH*vw`%uHR1fJg?BT!7)hU0UXF?YYbtInG?{g*`7^&^Frrh>8xqPd3 z&qVc*F2f$54<@>gJu;yShB^|}g9!&lYPVfeZhM?uzE!(tqIyV|VGsXpu1?t_6S`oi zBT+s4x3gfRcH6H{=<==FjdCmLGVJl^!90H-na~A8-5g(+2?s`MJFO}ADWS`^YWGZx z?}N|fuGpZ8wT((q-7`@woMX@!G{F9TL@p2?s{%H;?(_xa@Iq zxoy~ovdt^eFZI4*mfT)tJcNCVkJRrU^T%=7&siR!_GLyy$(ULJd#T)x$-#82watJ+t_)A%*|qxyNL>;dDo3&tT)J(zI(T(#TI z;k@jT30=NbyHRc>U4|QeeC?45T`<(maVsVq7^&TMO}S4AUA|SjXQI9j=`x-M6aC4x zM<#T^P&db|m~i}DwVmd0UZ;dE->TgxzYpm$?D4qv$b>E!>gKo=6Ap~j_FGf#Q$m+- z)$W<7??bwbr@=&jdhL-3T`<(maVsVqKUZz1Ih@xiq06^wH_Gorx(s{pPyW;Al+Xo3 z9f|sV?&rWrZNINi=<==FjdCmLGWa$6vulq`=z^hcj<3sv7v#C3N{#?MB%{ zx(s`~z4pk2E*R?OxD^u)jMVm9Q|?njmv7bXnW*0%(q%jiCi?Shk4)%-p>B>_G2!^R zYCFy0yiN&SzE!(Xejn0h*yAtP9+}VuL){#=V#0xu+J0-weM;!^t=c^k^?gW}VUNFF zdt^cv40UtdiU|isYCEkd_bH*vw`%uHR1fJg?D6i}BNMt{sGH+fOgJ!7+i6X?PYGSV zRl8@RdPtY?G??hWU3+9g7YucC+=>au&sE!L4(D}B=<==Fjq>-0bQ$*e`?W_Vbiq(J z$E}!fV5GL+nsT2Kx_qm4&qRG6(q-7=AJ-n4&;>)?9JgY^fsxuyYs!5}=<==FJrmVK zx(s`~zxK$4E*R?OxD^u)jMR2oQ|?njmv7bXnW!GpWjqZg`k&Vxna~A8-5j@K!try} zcACR^of5iyt9GOO{UKe3J^pp=kqKQe)Xi}#CL9>4?YE}fr-Uxws@*eD--mP=_W1X; zM<#T^P&db|m~ddEw$qw&pAx!!t9H*s^^h*Z9$#F0WI`7Vb#vT`2?s`MJFO}ADWS`^ zYWGZ359uUA|SjQU3mrF2f#QUVCIh7YucC z+=>YYMr!-5DfcO%%eQLxOw{)wU4}iry7tI~E*R?OxD^u)jMR2oQ|?njmv7bXnW!Gp zW!U4*V50l@4Q4_Y40R-)>wjBfIt~n69C_)? z9JgY^fsxv6*OdE|(B)gTdnW4pkS@a>-@Nw7gf1BB=C~CT4vf@xT2t;*LYHsV?wP0_ z(q-7=Th|_$&;>)?9JgY^fsxuyYs!5}=<==FJrmVKx(s`KbnTG|T`<(maVsVq7^&^F zrrf85E*R=aR1fJg?D6f9Lih1^R3>!6P)Fi<{XS>HfsvX|dU@<|a=|zxs)uwL_V~_7 zoqJ?L7Yuc8=2lENFjBkinsT2Kx?rdyQ9Y!~u*Y{t>f9p}x?re-QQrp>4vf@xT2pR& zoLs(DyJw<$NS9%ckFPy4p$mq(Ic~*-10%KDt||8^q06^w_e@j|=`!r`y=#w5=z^hc zj$1L|z({SUHRV1fboo~8o{8!qU4}irf9;V8T`<(maVsVq7^&^Frrf85F5jx%Gf_RH z%dp1}u01lL3x>KmZpDNHBek8@l>3y>3y>1w$Q)>LFc*J$^h==N_5R1w$Q-`aYO&V5GLwnsVFY zKmZpDNHBemPEDfcO%%eQLxOjHl)GVJlwYmZFmf}w7X zTQT9lNNuMzTg+Q9Y!~ zu*c7@Ju;yShPpX!#e@SRwVl?K`;^e-TeW*8s)uwL_W1PLBNMt{sGH+fOgJ!7+i6X? zPYGQx)RCwj(q-7=7bAu4&siRbnA2NMpA)O^y*V~>*y#vxHXq|30!FGuR! zBNMt{sCzTFV#0xu+HKdA`;^cHLmi3gAzg+&el=3(9+}VuLmiCzKA3P|q_)$Va@*tN z@~zrE6V*ey40}9Xdt^cv40UtdiU|isYPVfe?o&dSZ`JOZs2cAeM;!^t=c^k)kC@rd;I3wBNMt{sGH+fOgJ!7+i6X?PYGSVRl8@RdPtXH zkKbNzdtge3x+xp&+G3GCL9>4`J|V}9w!%!L!x>}mtl|JkJPzG zCUn72_hxRzgaaeB+pa11DWMC7Iug}Gx(s{#VWiGIGNB8GIvDkRFyX*RZKpNmw#Uik zTeW*8s)uwL_IP{kkqKQe)Xi}#CL9>4-F8j6PYGSVRl8@RdPtXHk3U{}WI`7Vb#vT` z2?s`MJFO}ADWS`^YWGZ359u=O@uzE#Oz484ZjM_q;lN03r#0n1C3N{#?VgG1Azg+& z{I|I}<)2rX&;>&siRbm_6%!7O)OK1^ZhM?uzE!(tqIyV|VUN!T6Wzxina~A89f|6} zgaaeB+pa0MJx(s)s@*eDJ*3O9hyONLr|gjlT`<&lxs`Mo z_W1K)p1+Sw=z^hcj<3sv10%Ja)|C5{(B)gTdnU&B!RK<=_rI?+JwNR6I-!f7tJi08 zvy~Hm4Fi{Ysk~1K=d%6v`X`?!!yaxjJ3VeaPOi!LFi}0;j0wljRoiI}=Vgyf=yKb3 zqnIXLhMgXdTMrnoU3}6ZQ9YP&V5ENYm_Lro9w(REhJ8qTka)cnytno9PMs3E=39N3 zs2;dDey;lQK;^MVCUp5$>F*vS?2-Ci{k&85IJsaP64iqVhaRclKIV_(vd78gTZMf{ zw<2MW)bH!(ojN6S!B9t{dNAS8BlWw-{Bd0NIJtbQun*~0ByX_p#%O07~idu`<7qI_pIm!nLKh5mbKHsv$In&UX%6RgO6c;f z+Kux2kS@a>k86)i=z^hcj$1L|z({SsHRV1fboo~8o{9QCq|103O!TMM9+}VuL){#= zV#4ur)pnZ0d7Tove5-b&{63`1um}I-KYdOKT`<&Tgxw~{V{ zU!y;}_Q-@T80zNux=c8JuG&s>IImMemv7Z>ls%-&u*chLk4)%-p>B>_G2y^SZND|; zJ|%SdR_&gN`u!nY#?xS;Kfm_Kgf1BB=C~CTj-RWx(;Uw0l+fi{wHxL4Azg+&{&MY+ z30*MM&2cLx92lwXx2D{ugf8Ez-7`_&hjbbC`0KStCUn72H^;4*aA2gi)0%Rh61se= zcF#ohkS@a>@2)*Ep$mq(Ic~*-10%Ja)|C5{(B)gTdnT%fbQw>BiT>NQM<#T^P&db| zm~i}DwVmd0UZ;dE->Tgxe}71qVUNFGdt^cv40UtdiU|isYWuAz_bH*vw`%uH)b}A> zhCTjq?U4yxFx1U)D<&KmsqM6;+^2*t->Tg+Q9Y!~u*dsrk4)%-p>B>_G2y^SZKpNm zJ|%SdR_&gN>LFdm(_o_idF_!2T`<(maVsVqKUZz1Ih@xiq06^wH_G21(q-7=U)LU) z&;>)?9JgY^fsxvNYs!5}=<==FJrnhPNS9%ce_wlKLKh5mbKHsv2S#c;tts~@q06^w z_e@j|=`!r`#kEH!biq(J$E}!fV5GLwnsT2Kx_qm4&qVc*F5_u1(f_&j$b>E!>gKo= z6ONy&w$mKW>y*&tTeTbI?+@uR?D6HbM<#T^P&db|m~ddEw%?j^pAx!!t9H*seIL?g z*yF2fk4)%-p>B>_G2y^SZKpNmJ|%SdR_&gN>LFc*J>Co^x{u#rCUn72N8-8uwxlajQzE!(tqP`F5 zGVJlqYmZFmf}w7XTQT9lNNuMzTg+Q9Y!~u*XN&9+}VuL){#=V#0xu+D>cAeM;!^t=c^k)kC@rdwl!aBNMt{ zsGH+fOgR57m)cHqIImMemv7Z>l)ulV%dp3Hu01lL3x>KmZpDNHBQ>A&@|+U7e5+T9 z`aYz~u*Y`?1Kr2wna~A89f{}l=PnZtjMR2oQ*L{lT)tJiXQFyYmtl{OuRSuM3x>Km zZpDNHBemPEDfcO%%eQLxOjHl)GVJlaYmZFmf}w7XTQT9lNNuMzTg+Q9Y!~u*VOsJu;yShPpX!#e@SRwVl?K z`;^e-TeW*8s)uwL_W0!5BNMt{sGH+fOgJ!7+i6X?PYGSVRl8@RdPtXHj~`xpWI`7V zb#vT`3Fp7%Qrl?`=XFZx@~zs9@=sCeGVJlAYmZFmf}w7XTQT9lNX;j`Jg0;%-|AJO zz7Od#?D6BlK=-kECUn72N8)+?{lSC-Bek8@l-nLBmv7bXnW!GpW!U3!?U4yxFx1U) zD<&Kmsoi!>xlajQzE!(tqIyV|VUM3)dt^cv40UtdiU|isYCEkd_bH*vw`%uHR1fJg z?D4Z}k4)%-p>B>_G2y^SZKpNmJ|%SdR_&gN>LFc*J$`=ekqKQe)Xi}#CL9>4?X;%c zr-Uxws@*eDJ*3O9$EVjGna~A8-5j@K!hw<6PHW12O6c;f+C3B1L%Ixm{Nmap6S`oi zo8wkYIR7n|+D>yguTw&oZ`E#;e~L<%VUJ&4dt^cv40UtdiU|isYCh@ZIVE)YR<9EE zeMpyKk6#T2x{u8>p$mpO63^@J4<;NKsqM6;-1a!Re5-cPMD>s^!yZr99+}VuL){#= zV#0xu+HKdA`;^e-TeW*8s)uwL_W1R+M<#T^P&db|m~ddEw$qw&pAx!!t9H*s^^h*Z z9>2Nv$b>E!>gKo=6Ap~jc3M;JQ$m+-)$W<79@1slJxp_W1p^M<#T^P&db|m~ddE=96BYQ$m+-^(s-{ zhjbbC_`_hJ``A1ax?rdy@x1>2V8Vfs+D>cAZI6@7w`%uHR1fJg?D6*6BNMt{sGH+f zOgJ!7yX~5CpAx!!t9H*s^^h*Z9)G;{$b>E!>gKo=6Ap~jc3M;JQ$m+-)$W<79@1sl z<4@Nfna~A8-5j@K!hw<6PHW12O6c;f+C3B1L%Ixm_-}J{%HJQE&;>&siRbnEg9!&l zYCEkdw>?fS->Tg+Q9Y!~u*c_viSA>MOz484jzslf!hw<6ZP%3B9w(P?)$W<79@1sl z!+)ErQ})P&E*R=aR1g16Ef}fY_UjY6e5-b&+)BC(d;EDY&)-KTbiq(J$Jb@Tfsxuy zYs!5}=<==FJrm>m;Bz_b`~SSs^!%{L>x3?Tu3n$X%~nqMH4I$prSd){oXhst>z{m{ z412iA?DV+xIJqX{!$kFXGbS8AS8b;`oR>W^q04RCjbfT~8FqR+ZarYUcJWDvMD<|8 zfsy*nWBxcUdz@Tu8}=dXLE`mR@ZQ$XJ9SFvns4=CqI%%s__^xG1C_@fnb751rN4WS zut(~5_47{IcNCV zkJRrT^T%=7j2zlv_!c;f5bydt^cv40UtdiU|isYPVfe?o&dSZ`JOZ zsP99%jHkgwe{$`S30*MM&2cLx96wiWr#YP0DWS`^YB$R7L%IxmJgz-5p$mq(Ic~*- z10%Kl)|C5{(B)gTdnW4pkS^nCFwvi0dt^cv40UtdiV4ThRoiI}=XFZx@~zs9^81i3 z!yf#T|MWQ}biq(ZqJE$IIWSV&@9Pt~e5-b&+)BC(evSU@+9MOXV5pnp>oVc^xoSJj z;k-@>UA|SjQTC87!ya$1Ju;yShPpX!#e@SRwf)wV`;^e-TeW*8>i36q8Bc?W{`}e_ z6S`oio8wkYIDW3$PIEY~Q$m+-)ozsEhjbbC_{+6NCUn72H^;4*aA2gi-+^2*t->Tg+QQwDj8TRs^!yfOiJu;yShPpX!#e@SRwVl?K`;^e-TeW*8s)uwLPlJj6=e0*B zbiq(J$E}!f{9Lu2=5Su8gf8Ez-6(&5NS9%ce_eZILKh5mbKHsv2S#f9tts~@q06^w z_e|9HAzg+&{(bF{30*MM&2cLx92lwXw5Hsrgf8Ez-7`@=q|30!7uOz{&;>)?9JgY^ zfsxuyYs!5}=<==FJrmVKx{Rm6ME~d7BNMt{sGH+fOgMh7+D>yguTw&oZ`E#;zdxkQ zu*a9z9+}VuL){#=V#0xu+J0-weM;!^t=c^k^?gW}VUMq_Ju;yShPpX!#e@SRwVl?K z`;^e-TeW*8s)uwL_INXx=stdfna~A89f{}q-KmZpDNHBek8@ zl>3y>s^!yX@9dt^cv z40UtdiU|isYCEkd_bH*vw`%uHR1fJg?D6euk4)%-p>B>_G2y^SZKpNmJ|%R)P)DMA zNS9%c?~D|>kAE^`LKh5mB%arwS4=oCQu9eKk3CK<7>7jlkS@a>-yNxQk4)%-q3+Gx ziU|isYPVfe?o&b+40R-`hjbbC_;{qwJu;yShB_GaeK6s`NNuMz<+jJkTg+Q9Y!~u*VOsJu;yShPpX!#e@SRwVl?K`;^e-TeW*8s)uwL_W0!5BNMt{ zsGH+fOgJ!7+i6X?PYGSVRl8@RdPtXHj~`xpWI`7Vb#vT`2?s`MJFO}ADWMC7Iug}G zx(s{#Xr$16{QDylx?rdy@x1>2V8VfsnooLp>~V6zI3%ivbQ$*e@kpI}WI`7Vb#LZY zOgJ!7yX~5CpAx!Ys3TE5q|30!W2DYKGNB8GIvDkRFyX*RZKpNmw#UikTeW*8s)uwL z_W0?wM<#T^P&db|m~ddEcH1@OJ|%SdR_&gN>LFc*J$`oWkqKQe)Xi}#CL9>4?X;%c zr-Uxws@*eDJ*3O9$Iq`lGNB8Gx;bvegaaeBoz|55l+fi{wRpB&vsW8TRXb?%V~T`<(asPBUb2S#c;ttq!XPA=c7-7`@=q|30! zAFn+!p$mq(Ic~*-10%KDt||8^q06^w_e@j|=`!r`r)!T)=z^hcj$1L|z({SUHRV1f zboo~8o{8!qU4}jUx4Amy@AFLPf}xH?{r8Fq2S#c;ttq!XPA=c7-7`@=q|30!=Yxsv zV~E!>PS=%|4l6zsonPL z6S{n>cB9-%x(s{#c`(o4M<#T^P&dcdWx|1x+D>cAeM;!^t=c^k4-#q4zn8emqcl?2!pwzE%3W2MK$mepf&5ls!%^7>7jlV8WqC>bH;i zPkCV%{3j2_5MZzAb-;6(t`FWpHLKh5mB&r7!4n0!C zdwJ|}a`{%T5B z>9t2Dbiq(J$E}!f{9Lu2=5Su8gf8Ez-6+2g=`!rWKlx9eQ$iOEbtLNdxt{|gwf(+6 zq06^wH_EM~%i!1O&#pZ(p$mq(Ile9vj-RWx(;Uw0l+fi{wHsv*=`!r`_Sz#8x?rfA z<5o;KFjCuZO}S4AUA|SjXQF<8NSE<6nCQ>1Ju;yShPpX!#f0PMs_is~^ExGT`Bv>l z`F%*2VUNFDdt^cv40UtdiU|isYWuAz_bH*vw`%uH)b}A>hCTjz?U4yxFx1U)D<&Km zsqM6;+^2*t->Tg+Q9Y!~u*bV=k4)%-p>B>_G2y^SZKpNmJ|%SdR_&gN>LFdm(_o_i zcI}Y~T`<(maVsVqKUZz1Ih@xiq06^wH_G21(q-7=@7Erg&;>)?9JgY^fsxvNYs!5} z=<==FJrnhPNS9%ce_VTHLKh5mbKHsv2S#c;tts~@q06^w_e@j|=`!r`{@NoGx?rfA z<5o;KFjCuTO}S4AUA|SjXQFyYm+>^1=zm^&WI`7Vb#vT`3CGV>+i4ExbxP>+t=f(9 z_lI;D_W0MeM<#T^P&db|m~ddEw%?j^pAx!!t9H*seIL?g*yG>V9+}VuL){#=V#0xu z+D>cAeM;!^t=c^k)kC@rdwg;2kqKQe)Xi}#CL9>4?X;%cr-Uxws@*eDJ*3Nc8cg(m zu01lL3x>KmZpDP-=c?^Ahx0lmboo~8M)~_gx(s`KdF_!2T`<(maVsVq7^&^Irrf85 zF5jx%Gg04%bQ$*e>e?d{x?rfA<5o;KFjCuTO}S4AUA|SjXQFyYmtl`LgNg3rH<$@s zFw~KFuK#U`={PWOapbLEdE4XU@~!l)f4Y@)8TR zIh@xiq06^wH_AUnrOU9#kFPy4p$mq(Ic~*-10yw`^zxh%x_qlwiTXaI%dp2|FwlK$ zo(Wwr)RA~ze}6FHz({SUHRZO)$>m$MdnT%fbQ$*e>9t2Dbiq(J$E}!fV5D~2HRV1f zboo~8o{8!qU4}h=cI}Y~T`<(maVsVq7^&^Frrf85F5jx%Gf_RH%dp4KuRSuM3x>Km zZpDNHBek8@l>3y>B>_G2whwF14NJ za9*c`F5jx%DE}0dF2f$by7tI~E*R?OxD^u)jMRM6%X3QT@~vJa>idu`!yZqAf$n4T zOz484j>Pl&`-2GwMru2)DYrdNF5jx%Gf_RH%dp3KmZpDNHBemPEDfcO% z%eQLxOjHl)GVJl2YmZFmf}w7XTQT9lNNuMzTg+Q9Y!~u*YZD9+}VuL){#=V#0xu+D>cAeM;!^t=c^k)kC@r zd;IR&BNMt{sGH+fOgJ!7+i6X?PYGSVRl8@RdPtXHkKbQ=WI`7Vb#vT`3FoVFsqHj} z^ExGT`Bv>l`KPFK8TRM}XZ}los--mP=_INuO=sq^j zgf1BBNIb8s^!ybRS_Q-@T80zM@6%!7O)OK1^?o&dSZ`JOZs2DigY3 zs3TGTy<)Xbb)p$mpO64k?hQwv6FxBdErF5jx%D7TU>!ybPg%=7n=30*MM z&GB`aaA2gi)0%Rh61se=cF)B4KKNV?`~JVLG(A7;@j9W4pR3nra_fU0345e|GyW{*=Y38IT`<&^hgcw<*~=fp+Fg9*paRlDsR&dVN|(B)gT8|7BgWw_zT*B+VB1w-8&w_?J9k=kw7 zl>3y>+i4ExbxP>+t=f(9`;acf9*=8} zOz484ZjM_q;lN03zcuAPC3N{#?VgGHKBUWd8cg)3*B+VB1w-8&w_?KabJcd5!+D(& zx_qm4qx?Rk%diLkgI}XRyY|S0E*R?O z__|Cuey-Y1b2zV4LYHsVZj?Qw%dp4WYmZFmf}w7XTQT9lNNvA0E!>gKo=6ONy&w$mKW>y*&tTeTbI_aR+|J^pg-kqKQe)Xi}#CL9>4 z?YE}fr-Uxws@*eD--mP=_W0|yM<#T^P&db|m~ddEw$qw&pAx!!t9H*s^^h*Z9`CL_ zGNB8Gx;bvegaaeBoz|55l+fi{wRYYMr!-5DfcO%%eQLxOw{)wU4}jWaqW=_T`<(m zaVsVq7^&^Frrf85F5jx%Gf_RH%dp4$YmZFmf}w7XTQT9lNNuMzdF*j=!8jzUhjbbC_;{qwJu;yShPpR%D<&Kmsoi!>xlajQ zFw~K#9@1sl<9j1@?vV*yFx0`Q?}G^kMru2)DYrdNF5jx%Gf_RH%dp4yuRSuM3x>Km zZpDNHBemPEDfcO%%eQLxOjHl)GVJk#YmZFmf}w7XTQT9lNNuMzcA zeM;!^t=c^k)kC@rd;I9yBNMt{sGH+fOgJ!7+i6X?PYGQx)RCwj(q-7=$0LRA&siRbnA2NMpA)O^y*V~>*y#vxHXq|30!W2DYKGNB8Gx;JwxCL9>4-F8j6PYGQx z)RCwj(q-7=rz3UlkqKQe)WN9lg9!&lYCEkdw>?fS->Tg+Q9Y!~u*c7?Ju;yShPpX! z#e@SRwcD;K_bH*vw`%uHR1fJg?D6w!k4)%-p>B>_G2y^SZKpNmJ|%SdR_&gN>LFc* zJwCnm$b>E!>gKo=6Ap~jc3M;JQ$m+-)$W<79@1sl;}_Q+na~A8-5j@K!hw<6PHW12 zO6c;f+C3B1L%Ixm{PNl(6S`oio8wkYI51M%X-&CL30*MMk*FThW!U3aBZcnc-yfOK z1w$Q)=k@mo6Ap~jeA3HfkCO|=AyGY~%dp4ONS%9RLKh5mZ{}7^I51MX?V56*61rfh zBT+r1%dp3LFc*J$`fTkqKQe)Xi}# zCL9>4-F8j6PYGSVRl8@RdPtXHkKbNzq|Ixgf1BB=C~CT4vf@xT2t;* zLYHsV?wP0_(q-7=_tzeo&;>)?9JgY^fsxuyYs!5}=z^h+MD>s^!ybPaDRdwI{>X$b z80ttoufIQ-aA2h7lU^QsoLn#tiRvL;hCSYn)VW6{biq*fW^ToV10%KDt||8^p$mpO z64gVx414@>q|QAup$mpO81;QH;lN03r#0oa$I0bewRKmZpDNHBemPEDfcO%%eQLxOjHl)GVI~M&DANtk4)%-p^ij- zAO4$KFjCv;>l3od98$_c-QflIwq-lv3f+5UR{lh2c34>y^e9=9GR*JON{ zs2*>|gyZL`?KFq;vPUL#xox{qOp`9dPLIc}2aMM)KIxFC9!xkeQoni3AID{nlgn+x zKBPTJyxt1l+xmH@P6=J}tv*at4_q8SSN(XP^4KF2x_qnjcMlTwNd2yU-YI*WTrdua z>cNCVkJN7;^T%=7PS=%CLDUChWGN=h-(I3^%J7o_TuU#+>iR!_GKmZpDP-=c?^Ahx0lmboo~8M)`e6mtl{`wMQm&!B98Ht(b6Nq_*Fha-R~qe5-cP zM13F9WjqZg`qOKVOz484ZjM_q;rO{~JI&#|P6=JURl8AsAJS#mgMad$KBt5(80tvW z?{hx~Mr!+geL|OS)ozqqNteN|(Vty=WI`7Vb#r`OCLBLkZKpY$*D0aPw`w=a9@1sl zau&sE!L z4(D}B=<==Fjq>}DF2f#wx%S9}E*R?OxD^u)jMVm9Q|?njmv7bXnW*nWx(s{#_1Ys7 zx?rfA<5o;KFjCuTO}S4AUA|SjXQFyYmtl{0*B+VB1w-8&w_?J9k=jmc%6&@c@~zrE z6V*eyjHkgw|Lxi%6S`oio8wkYIDW3$PIEY~Q$m+-)ozr(KcvgB$KS6#GNB8Gx;bve zgaaeB{nnKGl+fi{wRE!>gKo=6Ap~jc3M;JQ$m+-)$W<79@1sl zKmZpDNHBek8@l>3y> zZw3?H$8Rtbx?rdy@m&Ag64P;D;Nr+zzw)-n$>m$=UH^0|=`!r`jlnzj$b>E!>gKo= z6Ap~jZo8)3r-Uxws@*eD--mP=_W0(tM<#T^P&db|m~ddEw$qw&pAx!!t9H*s^^h*Z z9^bn5$b>E!>gKo=6Ap~jc3M;JQ$m+-)$W<79@1sl}r-Uxws@*96 z+?6iF9v@$OWI`7Vb#vT`2?s`MKI!E-C3N{#uM+irNS9%c?+pgJkIgfo3x+xp&+G3& zCL9>4?X;%c_BgqGt9H*s^^h*Z9^b$A$b>E!>gKo=6Ap~jZo8)3r-Uxws@*eDJ*3O9 z#}BSOGNB8Gx;bvegaaeBoz|55l+fi{wR)?9JgY^fsxuyYs!5} z=<==FJrmVKx(s{#;@TqYYMru2)DfcO%%eQLxOjHl)GVJlIYmZFmf}w7XTQTAMuUu+7&EdRG30=NbyHWlr zDqV&>o~}JIp$mq(Ic~*-10yw`^zxh%x_qlwiTXaI%dp3E!>gKo=6Ap~j zc3M;JQ$m+-)$W<79@1sl<2TnHna~A8-5j@K!hw<6PHW12O6c;f+C3B1L%Ixm{Px-- z6S`oio8wkYI51M%X-&CL30=NbyJw<$NS9%c&#pZ(p$mq(Ic~*-10%Ja)|C5{(B)gT zdnT%fbQ$*e-L*$1biq(J$E}!fV5GLwnsT2Kx_qm4&qVc*F2f$bzxK$4E*R?OxD^u) zjMR2oQ|?njmv7bXnW!GpW!U2n*B+VB1w-8&w_?KiU%AwFn!|aW61se=cBB0JL%Ixm zyuJ3wgf1BB=C~CT4vf@%(#vy7=<=;zCF=W-F2f#w91L_Hn`c5740R-)*Z=*42?s`M zJFO|VJx(s)s@*eDJ*3O9$Dgh}GNB8Gx;bvegaaeB+pa11DWS`^YWGZ359u=O;lIt* zDZh_Q=z^h+M13DjI51M%X-&E9adP=q?VgG1Azg+&J|E2Uf3Gs33x>KmZpDNHBemPE zDfcO%%eQLxOjHl)GVI~M&DANtk4)%-p^ij-AO4$KFjCv;>l3Xgt0Lmi3g!GuGP)bAei$8p)?gSz0C3L}1N1}Q#;m{-X z`^WroT=qD*e59-YI*)cwsSZydt^eFZ`E#;TS=GUh96&hWI`7Vb#vT` z2?s`Mw_Q{2Q$m+-)$W<7??bwbr@=&ja_x}`T`<(maVsVqKUZz1Ih@xiq06^wH_Gor zx(s_fu01lL3x>KmZpDNHBenh3l>3y>WI`7Vb#vT`3CGV> z+i4ExbxP>+t=f(9`;acf9{iL4^f@JT!B9t{exLg}FjCv^>l3)?9JgY^fsxuyYs!5}=<==FJrmVK zx(s`~yY|S0E*R?OxD^u)jMR2oQ|?njmv7bXnW!GpWjqZg`ft}Bna~A8-5j@K!try} zcACR^of5iyt9GOO{UKe3J^p^}kqKQe)Xi}#CL9>4?YE}fr-Uxws@*eD--mP=_V~xO zM<#T^P&db|m~ddEw$qw&pAx!!t9H*s^^h*Z9`CO`GNB8Gx;bvegaaeBoz|55l+fi{ zwRYYMr!-5DfcO%%eQLxOw{)wU4}jWeeID6T`<(maVsVq7^&^Frrf85F5jx%Gf_RH z%dp26*B+VB1w-8&w_?J9k=jmc%6&@c@~zrE6V*eyjHkgw|L58x6S`oio8wkYIDW3$ zPIEY~Q$m+-)ozr(KcvgB$CuY0na~A8-5j@K!hw<6erw8oO6c;f+C3BXeMpyKkFTyh zGNB8Gx;bvegaaeBoz|55l+fi{wR&siRb#?mY9wM0~bf$ z`jxjmPA=a{@A{`(Nta=dZw%hKM<#T^P&db|m~ddEcH1@OJ|%SdR_&gN`aYz~u*WyA zJu;yShPpX!#e@SRwVl?K`;^e-TeW*8s)uwL_W0JdM<#T^P&db|m~ddEw$qw&pAx!! zt9H*s^^h*Z9v@wMWI`7Vb#vT`2?s`MJFO}ADWS`^YWGZ359u=O@$GAmOz484ZjM_q z;lN03r#0n1C3N{#?VgG1Azg+&zH{x730*MM&2cLx92lwXw5Hsrgf8Ez-7`@=q|30! zcdtD%p$mq(Ic~*-10%Ja)|C5{&;>&siRvL;hCMzWDRdwIq|Ssc80ttoufIQ-aA2h7 zlU^QsoLn#tiRvL;hCRMFQs*9-&;>)?o4FMe4vf@pyQbWygf1BBNK_B$GVJmFkvjLt zgf1BBVAS`)gaaeBoz|4w9w(P?)$W<79@1sl;|JFsna~A8-5j@K!hw<6ZP%3hl+fi{ zwRE!>PS4Vzdx98V5H`g zULJd#Trdua>LFc*J$^b;=N_5R1w-ANxfK%*jMQ$srrf85E*R=aR1fJg?D4aaI`_zg zE*R=y)c3)J10%Ja)|A^GCzo&4?wP0_(q-7==hq&Y&;>)?9JgY^fsxv6*OdE|(B)gT zdnT%fbQ$*e^x7j6x?rfA<5o;KFjCuTO}S4AUA|SjXQFyYmtl`zTzh0f7YucC+=>YY zMru2)DfcO%%eQLxOjHl)GVJloYmZFmf}w7XTQT9lNNuMzp+FL%Ixm{2J!5M<#T^Q1@nT#e@SRwcD;K_bH(ZhB^|}L%Ixm{D#!AM<#T^ zPzR&F4<;NKsqM6;-1a!Re5-cPMD>s^!ydoA_Q-@T80zM@6%!7O)NZ?`+^2*t->Tg+ zQ9Y!~u*YZD9+}VuL){#=V#0xu+D>cAeM;!^t=c^k)kC@rd;IR&BNMt{sGH+fOgJ!7 z+i6X?PYGSVRl8@RdPtXHkKbQ=WI`7Vb#vT`2?s`MJFO}ADWS`^YWGZ359u=O@rP@V zOz484ZjM_q;lN03r#0n1C3L}1N1}R2mtl{$BZcnc-yfOK1w$Q)=k@mo6Ap~jeA3Hf zkCO|=AyGY~%dp2EN9x=o6S`oido#CU!hw<6ZP%3hl+Xo39f|58U4}jWG*agtna~A8 z9gO-um~ddEw$qw&+vDW&t=c^k)kC@rd-!j2b;|!6Ap~jZo8)3_BgqG zt9H*s^^h*Z9-j~9`TNL(E*R?OxD^u)jMQ$srrf85F5jx%Gf_RH%dm(4Hdm+oJ~E*T zhB^}UefV!`!ANbVuTSXmt=f%pE9o-q@#n!je;=991w-8&UzZ67Mru2)DfcO%%eQLx zOpNb?&*jX0|F4&to*(vjozTV4)$4P(*~-cM>0sbemx_O!5{_m2>-A4QPli4ACUkn- zdYoL7@nNER@LBWJ=c;$78h7(d=yKa_o`UHMdhR~-<8kY;8-|NdIwYzGlX)tk=969? zzmH7la@$vl>OsQqBlTN7jlU^36>k@~GauW|qP>g4jRws{J-B4Llz@BR6Xof5iWsCQuhK4&t|=#l!J zKd*83IJtbQZJxrdNZ2Fw7yf+5P6=Hw)H|?yFqvocNd4ZQ*SLF}T)x#dPvKT1?2-D- z_&@8;pYPZyp$mq32X+r8^Nb#;_qH|e9w(P?wartw)utEY3FgorU3+9g7YucC{9Vgr zp88z%-nPcwBNMuOt8Jdb9-E%K5B>4AM<#T^P&daOOy;T2Rqt(U+&wa(%eUI*DeST7 zx%<$cTzh0f7YucC?7?K7`dsziw#MBf6S{n>ZJxp&o1VK5{kZnXgf1BB=GcSDJoUNi zy={%VM<#UnR@*#B>nn9Nh3tKQqzxO-$mmv6PrQ`lqEbN8X2 zu01lL3x>Km_FytkeXe?MTjTDL30=O`Hcw%XP0!tj{_NT#6S`oin_~|q^VH|6_qH|e z9+}YPTW#|c_Sp2?edxE>9+}VuL){#EFqx-5SG~8barelCF5haKr?AJS=k7y)e(jM7 zT`<(mu?LfR>T}h5+ZuO|Oz85hws{JBYGEaT3dT(3f?vV*y zzSTBQVUJDE-G~0`wMQm&!B98H9!%z`&sFbjYur6Dq06`0<|*v4>ACyR@2)*Ep$mq( zIrd;OPkpX>Z(HN;kqKSC)izIIk4?|rhyL5OM<#T^P&daOOy;T2Rqt(U+&wa(%eUI* zDeST7x%<$6zxK$4E*R?O*n`PD^||W3ZH>D}CUp5$+dPFmHa&MA`XARGna~A8-5h%` znWsKiy|=A#_sE1U-)ftuu*at7?nA%7_Q-@T80zNOgULMgx$3=bjk`xCboo}>JcT_r zJ$E1apVuCl&;>)?9D6XCr#@G`x2H6YMZC9$EN4*L;vgABNMt{sGDOCCiB$i zs`s`v?jD)YZJxp&o1VK5 z{l&FMCUn72H^&}K=BdwB?`><`Ju;!ox7y|@?6K*&`_TWn_Q-@T80zNOgULMgx$3=b zjk`xCboo}>JcT_rJ$E1a%WIEJ=z^hcjy;&nQ=hBe+t#>yWI~s3waru5W7Bi@p})HJ z$b>E!>gL#k$vpMB>b-4^yGJH;`BvLJg*`SscOUxA*z8IEHK7ZJdYV_F{&!I(^VH|6 z_qH|e9+}YPTW#|c_Sp2?edupodt^cv40Ut-KA6l?pR3;6*0_6QLYHr~%~RN8({uNs zzj^JE30*MM&9MiQdFpf3d)pd!k4)(Dt+sgzdu)2{KJ>S)Ju;yShPpZSU@}jAu6l1< zpR3;6*0_6Q zLYHr~%~RN8({uNszkBVG30*MM&9MiQdFpf3d)pd!k4)(Dt+sgzdu)2{KJ>@e9+}Vu zL){#EFqx-5SG~8barelCF5haKr?AJS=k7y)@7g01x?rfAV-F_t)aR=Awl(e^nb751 zZSxfN*!0|e=9WI`7Vb#v^&WS;t5_1?C|-6Ipae5-Aq!XBHRyASpR3;6*0_6QLYHr~%~RN8({uNsKe_hEgf1BB=GcSDJoUNiy={%VM<#UnR@*#< zJvKdeANq&a9+}VuL){#EFqx-5SG~8barelCF5haKr?AJS=k7!Q=-ML_x?rfAV-F_t z)aR=Awl(e^nb751ZSxfN*!0|e=pSEuWI`7Vb#v^&WS;t5_1?C|-6Ipae5-Aq!XBHR zyAS=i_Q-@T80zNOgULMgx$3=bjk`xCboo}>JcT_rJ$E1ar`H~t&;>)?9D6XCr#@G` zx2H6YMZC9$EN4*L;vjBBNMt{sGDOCCiB$is`s`v?jD)YZJxp&o1VK5{pqzwCUn72H^&}K=BdwB?`><` zJu;!ox7y|@?6K*&`_R9*_Q-@T80zNOgULMgx$3=bjk`xCboo}>JcT_rJ$E1am)9Pd z&;>)?9D6XCr#@G`x2H6YMZC9$EN4*L;vd9BNMt{sGDOCCiB$is`s`v?jD)Y zpR3;6*0_6QLYHr~%~RN8({uNse|_zd30*MM z&9MiQdFpf3d)pd!k4)(Dt+sgzdu)2{KJ;&{Ju;yShPpZSU@}jAu6l1<pR3;6*0_6QLYHr~%~RN8 z({uNse}CT}h5+ZuO|Oz85hws{JBYGEaT3dT(3f?vV*yzSTBQVUJDE-G~0uwMQm&!B98H9!%z`&sFbj zYur6Dq06`0<|*v4>ACyR{@YxgIwf?$P)DNv-_@ARQ=hBe+t#>yWI~s3wartw)u!j} zLw`Ql=l}P?Oz484ZjRpvlX>cM)qC3-caKcy@~yUc3VUpN?mo2tHdm)k30*MMk*M#( ze^ZP9fAgvLwmV|;lgqc-<|*82({uNs|9t&EGNB8Gx;bveWS;t5_1?C|{e5IYmv6Pr zQ_zF=;qc#||KpWr{^5VGUMIF|q_uMi2pk4JnTp&zBcKCxYcvGtC)6%!6UQoni3 zAIEh{Y*)Qi*oXA{;B);BQopUAcj}bbF5c?Hg#W!76VCk3A5*^Qw0^>E4?c^N&G~QW z<1v36ms?4fVW)Q^asK_0iR~JUtvAK3m~ddEe*2g|j_Z`zu6nDm4`~lR_x0}Z-q+7N zbxLg4IZ@vSE{>n8hW+x`BNN*duh1#CB4LlzU)0aPKCxYcvGtC)6%!6UQon!9AIEh{ zY*)Qi*oU+SpKFiQZ;t=}miX`J(KUX4NYwYighP+ieA3HfkE5&DuM+-wRr|_#s=rG< zy7qwafom`hiR!_G10%KDt|_-Yj;?rxNK_B$GTiXvYmZE9*I;bDA#TNl10%KDt||8^ zv0d>Bk*FThWjqZg`jcyqOl;RVQQrqHj-RWx(;Uvr9+}v#c!fw*4>yrvkH@t~CbnxZ zw%!o8V#0xu+HKdA`;^$OdaHKNMD>s^<7qI_pI&=pV!O_X`aW=R{9Lu2=5Sv2$i#NV zD@3AtxQPsV@K65J$3NjRv0a0)^^Ulep93Sc+wQ>rK8~(>t9GN@O1cbwjsEP~BNN+o zPSp2-i{t02-F6P=W%EpISG+icjL8TNR4?U9M?8jP(s#I2ZcV5D~2HRV1fwyWN% z-7`@=q|103O!Vj19+}v#bE3WvTpT}FZKpY$mpwAEUGWN$s2*-2!ybRR_Q=F`4aU|R z;#N#JFjBkinsT2K+f{GX?wP0_(q-7=uh$-#*sj6YdV}1G2?s`MJFO}ADY0Gg3X!ND z(q-7=-L*$1wreo9-VnE9!hw<6PHW12N^DoWLL{n(bQw>BiT>NQM<%xGoOm9*nn3n* z)pp|7>)3u}I51MX?V56*65ACo5{c>|U4}jWeeIEn?HY`&H^i-&aA2gi z)0%Rh65AE85Q*v`U4}irxc11zb`8eX8{$?>I51M%X-&CLiS3G4h(z^}F5_u1(f_&j z$i#M?6VHQJ6Uct9+D`m>9eeP(9JiXk7@u@VR1Y_iVUI7bJuBk*FThW!U2z*B+VJuEE%PL)?l92S#c; ztts~@v0d>Bk*FThW!U4J*B+VJuEE%PL)?l92S#c;tts~@v0d>Bk*FThW!U3e*B+VJ zuEE%PL)?l92S#c;tts~@v0d>Bk*FThW!U4RYmZE9*I;bDA#TNl10%Ja)|C5{*sgem zNK_B$GVJl~YmZE9*I;bDA#TNl10%Ja)|C5{*sgemNK_B$GVJl4YmZE9*I;bDA#TNl z10%Ja)|C5{*sgemNK_B$GVJl)YmZE9*I;bDA#TNl10%Ja)|C5{*sgemNK_B$GVJm3 zwMQnlYcRIn5VvB&fsxuyYs!5}Y*)NOB&vsW8TRYYMru2)DfcO{UGWN$s2YYMru2)DfcO{UGWN$s2YYMru2)DfcO{ zUGWN$s2YYMru2)DfcO{UGWN$s2LFc*J$`rXk%{dZ zjIB4st(b6Nq_)$Va-S006|WG9>LFc*J$`@fk%{dZjIB4st(b6Nq_)$Va-S006|WG9 z>LFc*J^pa*k%{dZjIB4st(b6Nq_)$Va-S006|WG9>LFc*J>Fh>WMaDpW9toZD<&Km zsqM6;+^58L#VbUjdPtXHk3U{}WMaDpW9toZtN)*GHw3ox$g-?o%y|Cs%y|9~FfcF> zFfcF>C{Uol3rB+n4GantC~$#+fq{U5fq}wdAYjn2p<%;@#Uxm;prK$vL&Lc%^DLcx zuTCl>oZ7l)?{DSKPbx|EBql5vsqM6<>{H^cD{dhY)kC@rd;E0lk%_miE90$x3-MG; zSTIuCX;0aw#9LR~LL{n(bQ$(|`>(m)M*O;3Cf>TPjJLWIudcEtdq zChXx!R;R~PkIA(d?nM964ism{i)!+sLwlP^GxVkclBcNCXkJLQrcG+Wcc~^Id>OsODsbAOUowCQ|f-xkj2NM=OQuCzS zWsk|_UEL+B2MK$mep8=!${v#o#*nBUOjz_t&694IJtmiTb(g3fBa2M<#T^P&da@ zF=6>!wVl>*ZBs&*chzo`Kdz+9u*c)EM<#T^P&da@F=4?-ZNEKbpAx#ft9GwMy$|U! zE>|Y{t7DH$=z^hcj;CV6^0{g|t>N0Hgf8!@-6-FObQ$*GTmI`cC3L}1N8;7~y^qg< zk=lOmPw4Wl+Kuv5(q-^{^z&nnOz484ZjQIhgynP9c3Q)=O$lAzRl8C4kS@a>FOEGj zp$mq(Ii89M3r1@D?J4_|(B)mVdnM}Uhjba2D--?l*dr6VV5pnpshF^QuG&s(xV9;w z%e!hf%J(5%hCTlM*dr6VV5pnpshF@}q_*FlvQG(J-c`F-qTYve8TR;(V~{CLQch&Bds2{CLQch&BdsP`dVhCO~g_Q-@T80zMD zDkdx#sqM6<>{CLQch&Bds2{tRbiq(J$5Sz3!ANbVJ!PK~y1c7)uSE5bF2f!VUp~D3 zv)fMk1~Z`xhPpYPiU|uwYCEmr+NOjq@2cG>PbFQ3Jw7=0$b>E!>gISVCM+1K?X;)t zQ$m+_)$Wz3_aR+|Jw814$b>E!>gISVCM+1K?X;)tQ$m+_)$Wz39@1slmT&GVJl`u}3C!!B98HQ!!z|NNuM*WuFqdysLJvMD>s^!yZqMJu;yS zhPpYPiU|uwYCG*I`;^e-UA229s)uwL_W11BBNMt{sGH-dn6O}^w$q-nPYGS#Rl8TB zdPtXHkI#=iGNB8Gx;dVT2@6JQJMAg^l+fi}wRY*yH}!mNS9%cXU86y&;>)?98blB1tYbc_LO}}=<=@Gy%NmT&GVJlEV~B?+V#0!v+D?1QJ|%Q{SM6Sj>LFc*J^uCB zBNMt{sGH-dn6O}^w$q-nPYGS#Rl8TBdPtXHkAFM%$b>E!>gISVCM+1K?X;)tQ$m+_ z)$Wz39@1sl#;{Bbiq(J$5Sz3!ANbVJ!PK~y1c7)uSE5bF2f#|V~{CLQch&Bds2B?+ zV#0!v+D?1QJ|%Q{SM6Sj>LFc*J-$8m$b>E!>gISVCM+1K?X;)tQ$m+_)$Wz39@1sl z-yM5oLKh5mb37Fj7L3$(+Eex^ zq075!_exX`=`!r`{jomT&GVJl<*dr6VV5pnpshF@}q_)$ZvQG(J-c`F-qIyV| zVUHh=Ju;yShPpYPiU|uwYCG*I`;^e-UA229s)uwL_W0@8BNMt{sGH-dn6O}^w$q-n zPYGS#Rl8TBdPtXH5C1h+r~C?5CUn72N8;7~dzMUCFjCuTPucA;xxA}(uSE5bF2f!# zkMAQBx?rfA2aTMF5B<#Z#|WC8E-p1J{)>v;-OrN@ou6{g^RWB z>T$`NPWuyfdt}1q*)I~;dr7(sdptg#dU$pcx_FX07}bLb%Y>RI-7b4%LYJq#OH>aM z_h*OqqCW4GZ!i`vvd84|uI>`mgM>X&zp2kV zWsk`PV@OmFCM_F#f%zJTIrx#pmkgcq%3=6KYSphHIM= zx@5N-Pmet^p$mq( zIi89M3r1=??J4_|(B)mVdnKxebQzZ`6aDPiBNMt{sGH-dn6P}V+D>b@wke^@yJ|Pe zA6L?4*yHioBNMt{sGH-dn6O}^w%?wzPYGS#Rl8TB-iLG|8eY*30*MM&GA%BSTIuCX;0awgf8!@ z-78T&q|30!FUKC4&;>)?98blB1tYbc_LO}}=<=@Gy%NB?+ zV#4ydYCEmr+NOjq@2cG>f1XR1VUPbh_Q-@T80zMDDkdx#sqMF?>{CLQch&BdsP`dV zhCTjv?2!pwFx1WQR7_YfQrl@y*{6gq@2cG^Q9Y!~u*d6Tk4)%-p>B?+V#0!v+D?1Q zJ|%Q{SM6Sj>LFdm<;q0={n#TDx?rfA)?98blB1tYcn_LO}}=<=@Gy%P04q|30!ug4yl&;>)?98blB1tYbc_LO}}=<=@G zy%NE!>gISVCM+1K?YF1wQ$m+_)$Wz3_aR+| zJ$^s-$b>E!>gISVCM+1K?X;)tQ$m+_)$Wz39@1slPbFQ3Jw7=0$b>E!>gISVCM+1K?X;)tQ$m+_)$Wz3_aR+|Jw814 z$b>E!>gISVCM+1K?X;)tQ$m+_)$Wz39@1slmT&GVJl` zu}3C!!B98HQ!!z|NNuM*WuFqdysLJvMD>s^!yZqMJu;yShPpYPiU|uwYCG*I`;^e- zUA229s)uwL_W11BBNMt{sGH-dn6O}^w$q-nPYGS#Rl8TBdPtXHkI#=iGNB8Gx;dVT z2@6JQJMAg^l+fi}wRY*yH}!mNS9%c zXU86y&;>)?98blB1tYbc_LO}}=<=@Gy%NmT&GVJlEV~B?+V#0!v+D?1QJ|%Q{SM6Sj>LFc*J^uCBBNMt{sGH-dn6O}^w$q-n zPYGS#Rl8TBdPtXHkAFM%$b>E!>gISVCM+1K?X;)tQ$m+_)$Wz39@1sl#;{Bbiq(J$5Sz3!ANbV zJ!PK~y1c7)uSE5bF2f#|V~LFc*J-$8m$b>E!>gISVCM+1KJ?);dPYGS#Rl8TBdPtXHkLSl8na~A8-5gKFgasqD zo%WP{O6c;g+PxChL%Ixme0S`T30*MM&GA%BSTIuCX;0awgf8!@-78T&q|30!_s1TY z&;>)?98blB1tYbc_LO}}=<=@Gy%N{CLQch&Bds2Tk4)%-p>B?+V#0!v+D?1QJ|%Q{SM6Sj>LFc*J^0sE z=w5yWD-*h4s3Y-e|9Z}Z1tYbc_LSWolgqnm_exX`=`!r`^4KF2x?rfAOtcE?C@UH=biEmWOsODseh@@J7tf_1!G854<;;nq~=Mt%N~=Ml_|NZ2FwoBF&{_Ly8ShD7yX!lFlNo^-qH zF}b{}yF~RMVUN@g*ZyuyK47J)ccSw<8o!9zdH8Fgf1BB=6EV5ET5~k(;BXAO6c;g+Kuvk zNS9#`zU9AOQ$iOEbtLM4uY3-S)b@LSLYH^dZj`5zE`#r*pC5ZyuyK47JR1fJg?D5O7M<#T^P&da@ zF=4?-ZKpkDpAx#ft9GwM^^h*(a%H0b^VlO3x?rfAE!>gISVCM=(;w$mD}ZA$3!uG)?A=ZAC|_V~xKM<#T^P&da@F=4?-ZNEKbpAx#f zt9GwMy$|U!?D6ZdM<#T^P&da@F=4?-ZKpkDpAx#ft9GwM^^h*Z9&e64GNB8Gx;dVT z2@6JQJMAg^l+fi}wRKmo{9+zMr!-*Df^Vr3x>Kmo{9+zMru3l zDf^VrKmo{9+zMru3lDf^Vr3x>Kmo{9+zMru3lDf^Vr z{CLQch&Bds2E!>gISVCM+1K?X;)tQ$m+_)$Wz39@1slY*yH}!m zNS9%c$77F7=z^hcj;CV6f|1%zd&)i~ba_|pUWw`Kmo{9+zMru3lDf^VryuyK47JR1fJg?D3alk4)%-p>B?+V#0!v z+D?1QJ|%Q{SM6Sj>LFc*J^p&^kqKQe)Xni!Ojs~d+i6ePr-Ux=s@*G5J*3O9$K}`~ z6S`oio8zgNuwbON)1IY*yH}!mNS9%cua7-4p$mq(Ii89M3r1=??J4_|(B)mV zdnKxebQ$*e=GY?>x?rfA3x>Kmo{9+z zMru3lDf^Vr(q-7=$77F7=z^hcj;CV6f|1%zd&)i~ba_|pUWw`RdrU6xs@*G5J*3O9$IIjU$b>E!>gISVCM+1KJ?);dPYGS#Rl8TBdPtXH5C1h+ zr+goo&;>&siFzOYOD!0w?ezYHF7K+{C{HC_hCO~hzK=}kf}w7Xx66bDBek9OlzmF* z@~+yw64&>^b6K4If81$$@_8>lxlicgb9E%H@52e7!oa28DtCI^C!EXn`}1ryic2gf5<>4o3B0 z!ZM-eNw>=$nb75F?-JF6#QoXfy{OMScNCXkJLQrcG+Wcc~^Id>OsODsUOC_x0L7Q zRJ!B?+V#0!v+J1Y=J|%Q{ zSM6SjdLPnd*yHK3M<#T^P&da@F=4?-ZKpkDpAx#ft9GwM^^h*(a%G~Q9eZR#7YucC zJQWj`&sE!L4c9g$ba_|pM)~7Px(s_f9(!a$7YucCJQWibjMVnqQ}!vL%e!j#O4R$1 zF5_}#qQ5%!$b>E!>gISVCM=(;w$mD}ZA$3!uG)?AeMpyK55DEUUQPbFOj-$y?`_Q-@T80zMDyG&R(q-7=mt&7i=z^hcj;CV6f|1%zd&)i~ba_|pUWw`< zUB>0gME~cpM<#T^P&da@F=6>!wVl>*ZBs&*chzo`KR=|)u*ZKLdt^cv40Urn6%!VW z)b`s`_9>yuyK47J)ccSw!ybP-_Q-@T80zMDDkdx#sqM6<>{CLQch&Bds2mT& zGA>sp`k%)hna~A8-5gKFgynP9c3Q)=O$lAzRl8CC{E#lg9={!XWI`7Vb#puw6Bdlr z_S;kTDWS`|YWGUi`;acf9={)ZWI`7Vb#puw6BdlrcG^?+DWS`|YWGT159u=O@$hB; z*=;9%gPG6;L){!t#e@YTwVl>*ZBs&*chzo`r;;wi9v>WgWI`7Vb#puw6BdlrcG^?+ zDWS`|YWGUi`;acf9v>ciWI`7Vb#puw6BdlrcG^?+DWS`|YWGT159u=O@zJqICUn72 zH^);kVZlgkr#)q#61u#rcCSSBkS@a>PmVn@p$mq(Ii89M3r1=??J4_|(B)mVdnKxe zbQ$*e_}C*8x?rfAKmo{9+zMru3l zDf^Vr3x>Kmo{9+zMru3lDf^Vrs^!yb>v9+}VuL){!t#e@YT zwVn2qeM;!^uG+m4)kC@rd;H6>M<#T^P&da@F=4?-ZKpkDpAx#ft9GwM^^h*Z9{+mm zkqKQe)Xni!Ojs~d+i6ePr-Ux=s@*G5J*3O9$G;tWWI`7Vb#puw6BdlrcG^?+DWS`| zYWGT159u=O@zt?MCUn72H^);kVZlgkr#)q#61u#rcCSSBkS@a>e?Ioegf1BB=6EV5 zEEuWnw5RM-LYH^d?v(q-7=FUKC4&;>)?98blB1tYbc_LO}}=<=@Gy%Ns^!yaEBdt^cv40Urn6%!VW)OOla_9>yuyK47JR1fJg?D5U9M<#T^P&da@ zF=4?-ZKpkDpAx#ft9GwM^^h*Z9^W2&WI`7Vb#puw6BdlrcG^?+DWS`|YWGT159u=O z@%-2$6S`oio8zgNuwbON)1IY*yH}!mNS9%c?~Xk(p$mq(Ii89M3r1=??J4_| z(B)mVdnKxebQ$*e{@5cEx?rfAKm zo{9+zMru3lDf^VrLna~A8-5hV12@6JQJMAg^l+fi}wR2aTMF5B<#Z#|WC8E-p1o_b{Bp#NxBSsJU*U!cy#A9=KROSIv`dmpw9}%e%TuR1XsNNc~HF-YI)bE*L|idN5(p zBQ;ODUG|t<-ql^AdXTV3>euyor|dDgU<`@s!GuMR)I8~S*<*5fS9gi(LBbxX-_+-w zvd83tF(j%76Ba#E^Q7BlkICg--6g6A345e|82{c)? z98blB1tYbc_LO}}=<=@Gy%NewR_x?rfA=VfkFOoz`$|Q$m+_)ozqMq|30!i(`*W=z^hcj;CV6f|1&Od&)i~ zba_|pUWxkoAzjAh%0$0B_Q-@T80zMDDkdzStG3e`u5C)_@~+yA@_qcR&I=>;>tm0} z1!G9m&krUn7^!*E?Xt(@f-xlOeMpyaxjq~H_hXMt=z^i{lX)s8ET60Pv}?GwDWS`| zYB$RFAzg+&{&DP)30*MM&GA%BSTIuCZ%^5$gf8!@-78V=L%Ixm{Cez>30*MM&GA%B zSTIuCX;0awgf8!@-78T&q|30!n`4hm=z^hcj;CV6f|1%zd&)i~ba_|pUWw`0g zME~>HBNMt{sGH-dn6P}V+D>b@wke^@yJ|PepC8g?*yFcjk4)%-p>B?+V#0!v+J1Y= zJ|%Q{SM6SjdLPnd*yHzOk4)%-p>B?+V#0!v+D?1QJ|%Q{SM6Sj>LFc*Js!U7-wW-e zZ!iJ4g*yDp^k4)%-p>B?+V#0!v+D?1QJ|%Q{ zSM6SjdLPnd*yF=vk4)%-p>B?+V#0!v+D?1QJ|%Q{SM6Sj>LFc*Jw7`2$b>E!>gISV zCM+1K?X;)tQ$m+_)$Wz39@1sl zA0K;ULKh5mb37Fj7L3$(+Eex^q075!_exX`=`!r`$+1T!biq(J$5Sz3!ANbVJ!PK~ zy1c7)uSE5bF2f$59(!a$7YucCJQWibjMR48Q}!vL%e!j#N>mT&GVJm6*dr6VV5pnp zshF@}q_)$ZvQG(J-c`F-qIyV|VUN#_Ju;yShPpYPiU|uwYCG*I`;^e-UA229s)uwL z_W1nRBNMt{sGH-dn6O}^w$q-nPYGS#Rl8TBdPtXHk1virGNB8Gx;dVT2@6JQJMAg^ zl+fi}wR)?98blB1tYbc_LO}}=<=@Gy%NB?+V#0!v+D?1QJ|%Q{SM6Sj>LFc*J-$2k z$b>E!>gISVCM+1K?X;)tQ$m+_)$Wz39@1sl(q-7=#j!^wbiq(J$5Sz3 z!ANbVJ!PK~y1c7)uSE5bF2f!_9(!a$7YucCJQWibjMR48Q}!vL%e!j#N>mT&GVJlw zu}3C!!B98HQ!!z|NNuM*WuFqdysLJvMD>s^!yf)^<4*bi2xmeU40R-4?cXOdVZlgk zr#)r2$K>*^+PxChL%Ixmyga^-Oz484ZjPs7!h(_7)9xwzl+fi}wRJ4g*yHEp`^bbY80zMDyG&RxQrl@y*{6gq z@2cG^aeW^=m&Mut=bfe}pZDUE`-Cn&S4ZOdKAi9=3|#81a;L|A!ntg}zrXcV(q+8u z^myu#iHCA6#=D6+6)x7gtH&j8I_*!`?U4zaXTL~X?fzZ*=;BH0U{nt# zEE8&;bi3@430S&5+@BrZi~77%zQIiBT6gtsqI%$B`CK(mx?T3jgf8#uE>S&5 z*dz5X^?9f4F}Yw2iR!_GMUT`x>2}#;a(P#GiRwYZ9;si~=bf_0E!>gISVCM+1K?YF1wQ$m+_ z)$Wz3_aR+|J)RzWWI`7Vb#puw6BdlrcG^?+DWS`|YWGT159u;4S0?(|u}3C!!B98H zQ!!!rT(zCnaBWjUmv_}}ls~Sd%dp4eu}3C!!B98HQ!!z|NNvA8WuFqdysLJvM7sp`m1A)Oz484ZjPs7!t%LlJFVf`ri3oB@1%Y@}~)plCLwM_|K-c`F%_V|097e?xT z9D7VI7(=4o2NM>I)I8~S*<*6S7!vjKL%Iy#$FIj8na~A8-6!)@Ojs~dd)hr^pAx#f zt9GwM^^h*Z9&e64GNB8Gx;dVT2@6JQJMAg^l+fi}wRKmo{9+zMr!-*Df^Vr3x>Kmo{9+zMru3lDf^Vr)?98blB z1tYbc)^Kf8LYH^dZj`5zF2f!l9D8I!7YucCJQWibjMR48Q}!vL%e!j#O4R$1F2f!l z9(!a$7YucCJQWibjMR48Q}!vL%e!j#N>mT&GVJluu}3C!!B98HQ!!z|NNuM*WuFqd zysLJvMD>s^!yZqLJu;yShPpYPiU|uwYCG*I`;^e-UA229s)uwL_W1bNBNMt{sGH-d zn6O}^w$q-nPYGS#Rl8TBdPtXHk57(0GNB8Gx;dVT2@6JQJMAg^l+fi}wRY*yH}!mNS9%cr^g)?98blB1tYbc_LO}} z=<=@Gy%NB?+V#0!v+D?1QJ|%Q{SM6Sj>LFc*J^pa)kqKQe)Xni!Ojs~d+i6eP zr-Ux=s@*G5J^otfg^~Jl>@m4u42f6u|CdA$Tr7F}d+zp_TrgnYE`Pr(o#&?h`q*P~ z!59+Ng9!^pYEQeT?Dm*k-c`F-qTYve8UDEX=GY?>x?rfAs^!yexqdt^cv40Urn6%!VW)OOla_9>yuyK47JR1fJg?D74v zM<#T^P&da@F=4?-ZKpkDpAx#ft9GwM^^h*Z9zPs=WI`7Vb#puw6BdlrcG^?+DWS`| zYWGT159u=O@#5Gc6S`oio8zgNuwbON)1IY*yH}!mNS9%cACEmUp$mq(Ii89M z3r1=??J4_|(B)mVdnKxebQ$*e>DVI^x?rfARdrU6xs@*G5J*3O9$IIjU$b>E!>gISVCM+1K zJ?);dPYGS#Rl8TBdPtXH5C67Ur+goo&;>&siFzOYO)wa#?ezYHF7K+{C{HC_hCO~h zzK=}kf}w7Xx66bDBek9OlzmF*@~+yw64&>^b6K4I!#hn+KJUdR_X%Bmu8zd@eK_G$ z7`W71G9Mf6A$HDjCT`tDqO5}SC32HblRV=+anV;&wi1( z-b>PD*yHi>)Wfrr(8ZI~!KfZgSSHjw>2}#86S_R@U7~uBxIa6*7xj6ke1naRU7~uBut(}&>hn(7V{*Y564iqViyoACn8lkfY*yHTD>x(s`KaO{x@T`<(m@l;G$FjCuTPuZu0F7K+{ zD^c%5x(s`Kc{CLQch&Bds2B?+V#0!v+D?1QJ|%Q{SM6Sj>LFc*Jw7@1$b>E!>gISVCM+1K?X;)tQ$m+_ z)$Wz39@1slmT&GVJliu}3C!!B98HQ!!z|NNuM*WuFqd zysLJvMD>s^!yeC$Ju;yShPpYPiU|uwYCG*I`;^e-UA229s>h|y3nTT{#~zal#*lcm z|6I$21tT?2x?T2|Trh@2^^h*Z_wmiKM<#T^Q1{6^6%!VW)Sh-v*{6gq@2cG^Q9Y!~ zu*bK@9+}VuL){!t#e@YTwVn2qeM;!^uG+m4)kC@rdptk($b>E!>gISVCM+1K?X;)t zQ$m+_)$Wz39@1sl(q-7=hhvXS=z^hcj;CV6f|1%zd&)i~ba_|pUWw`< zU4}hg9D8I!7YucCJQWibjMR48Q}!vL%e!j#N>mT&GVJl=u}3C!!B98HQ!!z|NNuM* zWuFqdysLJvMD>s^!yZ2!dt^cv40Urn6%!VW)OOla_9>yuyK47JR1fJg?BU;r>6HHu zOD1%|P)Fj`{`H&*3r1=??J2uGCYN{B?v(q-7=-*rjEYAMF?le95yceI`Cv@?-Iuh6S;e=0N;8Jgu zJ3a0b&Sm@k{jH~xF5_*d$5W3?Jd|rO-c8i0aIw~1JuZ3EX@A0Qk4)G+`$gh98iRwY({_OBx)aRY@4Q4{ux~q2+)dLsH z=c;+q?XpKEba_{IiRwYZ9;tt+&pTz0$pvFbR1YRBdZgw_x62-r%e%TuR1XsNNd3A# z@02|z7mOiMJ(#fQk(wvnE_+Na@9Hj5JxJIi^_%*@m5# ztGh(?AYqTx598O>^1Pf%7oV$} z(q&w(O!Tv3k4)%-p>B?+V#4ydYCEmr+NOjq@2cG>e_TnIVUNdSk4)%-p>B?+V#0!v z+J1Y=J|%Q{SM6SjdLPndT&_&?SH~Wi&;>)?98blB<#W|`TEn$X30>Y*yHUOm=`!rW zxBS;@O6Y>2j>N0|dmoc^FI8TR=7*dr6VV5pnpshF@}q_)$ZvQG(J-c`F-qIyV|VULF|`+r+@(l?k1T`<(m z@l;G$FjCuT4c9g$ba_|pMtLgfGVJleu}3C!!B98HQ!!z|NNuM*WuFqdysLJvM7s^!yX?Udt^cv40Urn6%!VW)OOla z_9>yuyK47JR1fJg?D6E-BNMt{sGH-dn6O}^w$q-nPYGS#Rl8TBdPtXHkB^T%GNB8G zx;dVT2@6JQJMAg^l+fi}wRY*yH}!m zNS9%cPmet^p$mq(Ii89M3r1=??J4_|(B)mVdnKxebQ$(|dhC%2T`<(m@l;G$FjCuT zPuZu0F7K+{D^We9%dp31#~zu`1w-8&PsM}E!>gISVCM+1K?X;)tQ$m+_)$Wz39+x^VjMQHrdrU4EL*mu`b1f4V zjMO~ocG+Wc!59+NL%Iy#$2Z3wna~A8-6!)@Ojs~dd)hr^pAx#ft9GwM^^h*Z9^W2& zWI`7Vb#puw6BdlrcG^?+DWS`|YWGT159u=O@%-2$6S`oio8zgNuwbON)1IY* zyH}!mNS9%c?~Xk(p$mq(Ii89M3r1=??J4_|(B)mVdnKxebQ$*e{@5cEx?rfAKmo{9+zMru3lDf^VrLna~A8-5hV1 z2@6JQJMAg^l+fi}wRcNC%Ld}zImpw9}%hTQ^st1Ytv%`B)pLfbPml4_qvttL90! z%O07~@m5#tGh(?AYqTxZ|d_-*<*6S7!uWk35y=7dD88&$K>*^?h@65 zggsI}j9*vF^KvR(e6DVer((h~q4u15Y_xy-qB%Ctgf1BBNK_C1W`)niA+Oyi zdt|~+-ql^AdPtXHkKc~(BNMt{sGH-dn6O}^_OyG-J|%Q{SM6Sj>LFc*J$^s-$b>E! z>gISVCM+1K?X;)tQ$m+_)$Wz39@1slwKsDag_;OFw~K#A6HCRFjCuT4cBIm z$>m+O8|A5_%dp1>#~zu`1w-8&PsM}mT&GVJluu}3C!!B98HQ!!z|NNuM*WuFqdysLJvMD>s^ z!yZqLJu;yShPpYPiU|uwYCG*I`;^e-UA229s)uwL_W1bNBNMt{sGH-dn6O}^w$q-n zPYGS#Rl8TBdPtXHk57(0GNB8Gx;dVT2@6JQJMAg^l+fi}wRY*yH}!mNS9%cr^g)?98blB1tYbc_LO}}=<=@Gy%NmT&GVJmE*dr6VV5pnpshF@}q_)$ZvQG(J-c`F-qIyV|VUO>QJu;yShPpYPiU|uw zYCG*I`;^e-UA229s)uwL_W1tTBNMt{sGH-dn6O}^w$q-nPYGS#Rl8TBdPtXHj~|Xb zGNB8Gx;dVT2@6JQJMAg^l+fi}wR)?98blB1tYbc_LO}}=<=@Gy%N*^+PxCh zL%Ixmyga^-Oz484ZjPs7!h(_7)9xwzl+fi}wRiu@_l4N7YubI>V5c? zd>EPbFQ3J$^pEk4)%-p>B@1%Y+3ZwVn2qeM;!^uG+m4*Z0A5S)Bd< z+-Z99c`rV>Pw3)vbtJCu!wH|lz@^?QcY53>oXht6`&&;XUB=r^kEb4)cqrFmyql;~ z;bN`3dR+3R)Bc3r9+|Lt_KU>zUXm`u9*>Ww9-f_qE}o zs2)sM^hnKNoXyr|dDgU<`@s!GuMR)I8~S*<*5fS9gi(LBbxXAI7iaY*yH}##hjbbCczW!S z30*MM&GA%BSTIuCX;0awgf8!@-78T&q|3NmndoQ79+}VuL){!t#f0T^)plCLwM_|K z-c`F%{v9+}VuL){!t#e@YTwf**#eM;!^uG+m4^**G_xLld&uZ}%3p$mq( zIi89M%jc@?w1#V&61u#rcB6bBzt(wSq<(YkF}Yw2iC6pgK1^6JQuCzSWsk`PV@TBd zkS^nLeKy*!_BY4ona~A89f|7USMq%>4tecH*&`En@~-X@)kC@rd;E5MADPevL){!t z#e@YTwWr-v_9>yuyK47JR1fJg?D6}tM<#T^P&da@F=4?-ZKpkDpAx#ft9GwM^^h*Z z9uHscU*|jJkE=}Rf}xH?{kUSnf|1%zYq&OhOfK)L-6&5bU4}hAIQGbdE*R?Ocq%3= z7^yw&p0ZB~UEWo@SEAmBbQ$*e@Yo|0x?rfAKmo{9+zMru3lDf^Vr2kqKQe)Xni! zOjs~d+i6ePr-Ux=s@*G5J*3O9$EU|0na~A8-5gKFgasqDo%WP{O6c;g+PxChL%Ixm zJU#Zvgf1BB=6EV5EEuWnw5RM-LYH^d?v(q-7=vty4;=z^hcj;CV6f|1%zd&)i~ zba_|pUWw`7^yw&p0ZB~UEWo@SE71Imtl|R#~zu`1w-8&PsM}(q-7=r(=&y=z^hcj;CV6f|1%zd&)i~ba_|pUWw`{CLQch&BdxV{gb%i`=G-D!IAc`rV>Pw3)vbtJCu!wH|lz@^?Q zcY53>oXht6`&&;XUB=r^kEb4)cqrFmyql;~;bN`3dR+3R)Bc3r9+|Lt_KU>zUXm`u z9*>Ww9-f_qE}os2)sM^hnKNoXyr|dDgU<`@s!GuMR)I8~S*<*5f zS9gi(LBbxXAI7iaY*yH}##hjbbCczW!S30*MM&GA%BSTIuCX;0awgf8!@-78T& zq|3NmndoQ79+}VuL){!t#f0T^)plCLwM_|K-c`F%{v9+}VuL){!t#e@YT zwf**#eM;!^uG+m4^**G_xLld&uZ}%3p$mq(Ii89M%jc@?w1#V&61u#rcB6bBzt(wS zq<(YkF}Yw2iC6pgK1^6JQuCzSWsk`PV@TBdkS^nLeKy*!_BY4ona~A89f|7USMq%> z4tecH*&`En@~-X@)kC@rd;E5MADPevL){!t#e@YTwWr-v_9>yuyK47JR1fJg?D6}t zM<#T^P&da@F=4?-ZKpkDpAx#ft9GwM^^h*Z9uHscU*|jJkE=}Rf}xH?{kUSnf|1%z zYq&OhOfK)L-6&5bU4}hAIQGbdE*R?Ocq%3=7^yw&p0ZB~UEWo@SEAmBbQ$*e@Yo|0 zx?rfAKmo{9+zMru3lDf^Vr2kqKQe)Xni!Ojs~d+i6ePr-Ux=s@*G5J*3O9$EU|0 zna~A8-5gKFgasqDo%WP{O6c;g+PxChL%IxmJU#Zvgf1BB=6EV5EEuWnw5RM-LYH^d z?v(q-7=vty4;=z^hcj;CV6f|1%zd&)i~ba_|pUWw`7^yw&p0ZB~ zUEWo@SE71Imtl|R#~zu`1w-8&PsM}(q-7=r(=&y=z^hc zj;CV6f|1%zd&)i~ba_|pUWw`{CLQch&BdxV{gb z%i`?+_fFH3&wKI7eL@$Xt0QrJA5Qob1}^nhxzpo5;as-g-`{#F=`!ASdOY>W#6!6j z^xxA~pMD-wHkJPX0^G?}ga={o9)q@F(9;tcK?Xt(@@~-X@ z)q{jRQopIsJ7tf_1!G854<;;nq~=Mt%N~=KBUXA z$J1kvOz484ZjPs7!h(_7PJ7BeC3JaL?OuuMAzjAh%0xdq_Q-@T80zMDDkdzStG3e` zu5C)_@~+yA^2e2Q8TNQQ_Q-@T80zMDDkdx#sqMF?>{CLQch&BdsP`dV#^uUHe|7AU z30*MM&GA%BSUy*6r!`#Ll+fi}wHxL8__fXpBlVkOkI4mNNW9v=_hG_$A~*wZAzw&x9@*>PS=%zmo5BamZ^o${v}plXrEOs2c)?98blB1tYbc_LO}}=<=@Gy%N{CLQch&Bds2B?+V#0!v+D?1QJ|%Q{SM6Sj>LFc*Jw845$b>E!>gISVCM+1K?X;)tQ$m+_)$Wz3 z9@1slpB;N-LKh5mb37Fj7L3$( z+Eex^q075!_exX`=`!r``LRbPbiq(J$5Sz3!ANbVJ!PK~y1c7)uSE5bF2f#Q9D8I! z7YucCJQWibjMR48Q}!vL%e!j#N>q=p>%1^he{<|HxnK;5SNqQ&Ojs~d^Q7BlkI4mN zNK_B$GJGH39(!a$7Yucu%u_L8!AR|C_mq7~=<=@Gy%N{CLQch&Bds2B?+V#0!v+D?1QJ|%Q{ zSM6Sj>LFc*JzgApWI`7Vb#puw6BdlrcG^?+DWS`|YWGT159u=O@#C>aCUn72H^);k zVZlgkr#)q#61u#rcCSSBkS@a>KOK8yLKh5mb37Fj7L3$(+Eex^q075!_exX`=`!r$ zSNl8VpXf883x+xpulBElOjs~d+i6eP?J>E$t9GwM^^h*Z9xspYBNMt{sGH-dn6O}^ z_OyG-J|%Q{SM6Sj>LFc*J^X5ar+goo&;>&siFzM?B_Bp=JH0=l%e!hf%2P>~VUM4W z?;{hsV5pnp?J{A(NNuM*WuFqdysLJv#PxmfToz~l|L-(C`MejO+$VJLxjGWp_u+(3 zVc=44l{-D|6V7G({r#<{k}l(Ir^i!|OgxlpG2Ttosc^B@T|F*&(`kRgZjVgZJo`oB zdM`=$lgqohOH>aM z_DKD@KJS!0CKrq$Q9YQj=#iQy-7b4fF7N6tQ9Vf5BlVm5yi@j=Trh@2^E!>gISVCM+1K?X;)tQ$m+_ z)$Wz39@1r8u1xf^V~Wn8XI^jF6ona~A8-5gKFgynP9c3Q)=O$lAzRl8BX zk6-J&FjBuc_Ly8ShQzD=dmkn&7^!*E?Xt(@f-xlOeMpyaxjq~1SNoe|^GxW1p^ilL z@GJQ~7l*udqwJ9hJ9$@kiRvL;hCO~ezK=}kf}w7Xr((i_k=oPlDf^VrwS&;>)?98blB1tYbc_LO}} z=<=@Gy%Ny1W8`pz2R|r~jtA zA|fIX5fKOk;uM8IJQ0CFAdZNLh=_=YK-@}2L?DpJNF)+>BaujCBoc}2GuB>%HP6Y~ zcU7@7bJm>S*mHlmFYt2Xt=KRyQrqclIk$u^-)h{I=pNE#*yFuxk4)%-p&pL6V#B~l zZKt#4+!DHct8rJNdq|gIkN2-VGNB8GdN|&S4Fe;!oz9kXOX%{g#$Ac-Azg+&KDhSC zgf1BB;dm=H42;xvI$O>yq06@#cO|-qbQ$*e*|kR|biq&$$6K*sV5GLw*>Y|PUB1=0 zE73iq%dp3X*B+VB1w%a?Z^edzk=jmY%ef_V`BvkuMECfl?+YXKr`H~v3&xUobN=@S z8wN&dKI!?`V{^e+65T_(48M=hu01lL3x;}b=B?N;Fj9Nlv*p|px_ql~SE74Jmtl|3 zuRSuM3x;|)-ii$aBek8*mUBz!@~y^QiS8j?hCRNx_Q-@T80z78D>e*_)OI>s&Ml$K zw;FdPx`%Wb_W1JJBNMt{sE6aN*f20s+v#jMw}dX=YTT9R9@1sl;}6#!na~A8JsfYv zhJlgVPG`%xC3N{#udh8ap$mq3INpj410%Ja&X#ja=<=<`U5V}?U4}ir zy7tI~E*R?Jcq=vxjMR2ITh1+^%eNYLCAx=n8TR=4+9MOXV5o=Vt=KRyQrqclIk$u^ z-)h{I=pNE#*u#I?KPmqgeI|6lP)Fj;`THOn21aT-oh_$5HkWTT?n-nI=`!r`&Gq}p zgf1BB;dm=H42;y?_G~$~gf8D|+?D7a(q-7gf7(AOzmH7lf}xH?e;@vnd>E+V8fVD^GVOg9+}YPZC@n12Z@(= zhxfXFJ}JM!Oz5h&`eCAb;9~q-HJ|i+?2!pwzSWCF_aI@9)Zg~cCuNV#1!GBc4>k-v zQu9gA#~z!@w|bH29wh9M`n&%5r0lV|U@VF5!G@tnYCh@t*kg0~Rxc9WgM>X&f8Rf! zlsz^Vj3v=M*f8`+%_lt{du%S>>P4b^kg!MUSL;8=$@_9EUHn`<9B;*jF`@RhwX9=H z=#o8dl&>pY#$*4zjUutF}`u>(~;ye5-Mz z{Od}(40}9Wdt^cv4E1ol6&nUdYWtln=a$grTaCLC{e4K6@%Y3!2|M{#FB08Dx(s{#`}%!kLKh76aJ&^8 z21aUcd$ycgLYHqf?n-nI=`!r`pKFgy=z^ghj<;gNz({SUv*p|px_ql~SE74Jmtl`r zzdC=PpOk-HWkMGWbtL+)D>e*_)OM<69roBDlu8wN&dJDn}(meA!}jk^-vL%IxmymRf530*MM!|_&Z7#OMTbheyZLYHqf z?n-nI=`!r`?zKlIbiq&$$6K*sV5GLw*>Y|PUB1=0E73iq%dp2!uRSuM3x;|)-ii$a zBek8*mUBz!@~y^QiS8j?hCSZ9_Q-@T80z78D>e*_)OI>s&Ml$Kw;FdPx`%Wb_IUr= zBNMt{sE6aN*f20s+v#jMw}dX=YTT9R9@1slyNS9%cpIv)oLKh76aJ&^821aT-oh|2<(B)f=yAs_)x(s`Kck%kca7 z?Ajv}x?rg1X5NYo10%J!JzLH#q06@#cO|-qbQ$*e{MsWEx?re>1;W-gf8D|+?D7a(q-7=t80%;=z^ghj<;gNz({SUv*p|p zx_ql~SE74Jmtl{uuRSuM3x;|)-ii$aBek8*mUBz!@~y^QiS8j?hCTeJ{gd*4(Pu&z z40R;loWBpUVPK@T)7f&`V{`dd-(0_sOz4849*(zS!@x-GZO@i-OX%{g z#$Ac-Azg+&{HOhs^83hyE*R=a^!MRE$%m2JPH#`>@~y^=@>bGi*yE4a?;{hsV5o=V z>#|{Bq_)%9a&8G-zSX!Z@$`N0xeRxI=Y^&xKcC?zFB7`>xjGV0--jE13Imt=s66TM zGT~mfe}4Yxt)$ENZ>NV_k4(I3S22E==v(1p>a8A1KI?RT!fB68*gX41;^|$IF2f!V z54RrPorEqvNga&t!Gz{U8v zYCh@t*dr6Ve5)6U?m@yHslV-?Ps$#f3&xV@9&8wTq~?>Jk3BY*Z}lS4JxJIi^>_XA zN!ep_!B`UAgAGHE)O^zOvB&1}tzIO$2MK$m{=R=cDSK=#7)zpiuwm$tnooK@_Sjs$ z)r&;;AYqTxuhxH#llSFTy7;+zINpj4V?yn1YgxyZ&?S4^C|_5)jK}_a$vf8`na~A8 zJsfYvhJlgVerLy zNSE>W#6*8|?U4yxFx12GR%{qQS8be*_ z)b=}D&Ml$Kw;FdP`umVB6S`oihvTi-Fn+GuPPMFKOX%{g#*Om(kS@a> z{E`36YzbX3)RA~|{=1K#10%Kl-k#9qTa6p#t^U#Xg^~K7*B+Y-#**kBY#11+`K0IL z_p!NPEQ$W>O1cc*3;q4IM<#T^P|wZ06&uFSReRf7*0CjY`Bvjb`F%*2@z_25-4~PE z61rfhBhlZ7zq0~HYWuxCq06@#H_BT{mtl{8U%!t`=z^ghj<3sxfsxuyXUn-Iboo}} zu0;QJC0&L+{&Ve-30*MM!|_&Z7#OMTbheyZLYHqf?n-nI=`!r`>R0oBTTaq%FcZ39 zsE6aN*f20s+o_gyYzbYy)wogKO1cbt{OH;v6S`oihvTi-FfdZv>1;W-gf8D|+?D9> zL%Ixm{P@}<6S`oihvTi-FfdZv>1;W-gf8D|+?D7a(q-7=C)XaC&;>(19B;*jfsxuy zXUn-Iboo}}u0;2cF2f%0Tzh0f7Yy}qycHV;Mru2qE$5cdyq06@#cO|-qbQ$*e;MyY-x?re>-?eNQ z7^(TB=VOn}1!GBc59u=eK0dwn$b>E!>baS>V#B~l?QPGNb4%#*t;Su6?jc=E!>fv}RHVll^b~;1;W-gf8D| z+?D7a(q-7=i))We=z^ghj<;gNz({SUv*p|px_ql~SE74Jmtl`DuRSuM3x;|)-ii$a zBek8*mUBz!@~y^QiS8j?hCTjp?U4yxFx12GR%{p;sqJ*OoLfSdZ#C{pbPwq=?D6{A zBNMt{sE6aN*f20s+v#jMw}dX=YTT9R9@1slyNS9%cudh8ap$mq3INpj410%Ja&X#ja=<=<`U5V}?U4}jU-7u5#|HG0AT`<&< zcys=K&W3@J+D>Q7X^+k2TaCLC-9x$zdwg^KJ~E*ThI%;OiVXuJwYNQ6&Ml$Kw;FdP zx`%Wb_V9PZOv>*g6S`oiBhlZ7ztaUqYCF9>q06@#H_BT{mtl`TUcZk_=z^ghj<3sx zfsxuyXUn-Iboo}}uEf*#!RIpE{r`BO>B-M$_{qzJE`F|##MAfThM&T~r9LW8db~`y zm+haQKYApBNH~yevx>3m!!+E$HT*| zhj%BTi%(JqqkFJnOsM&!=VOmd=<>EN65WHu%e%vS-9Mj{-(V(m)m!~A(LHc6ey*BN zdOr5Zgf8FeMWTCjUutF}`u>(~;ye5-Mz{63`1um^wS zKQmiG7YubI-kksLe*_)OI>s&Ml$Kw;FdP`umVB z!yZ4r_Q-@T80z78D>e*_)OI>s&Ml$Kw;FdPx`%Wb_V~%QM<#T^P!Gpjv0-4Ow$s^i zZV6q!)wnCsJ*3O9$2->^na~A8JsfYvhJlgVPG`%xC3N{#?_PUkLKh76 zaJ&^821aT-oh|2<(B)f=yAs_)x(s{#^x7j6x?re>E!>fv}RHVll^b~;(19B;*jfsxuyXUn-Iboo}}u0;2c zF2f!lU3+9g7Yy}qycHV;Mru2qE$5cde*_)OI>s&Ml$Kw;FdP zx`%Wb_W0u3BNMt{sE6aN*f20s+v#jMw}dX=YTT9R9@1slyNS9%cKU{lcLKh76aJ&^821aT-oh|2<(B)f=yAs_)x(s`~zV^t3 zE*R?Jcq=vxjMR2ITh1+^%eNYLCAx=n8TR<<+9MOXV5o=Vt=KRyQrqclIk$u^-)h{I z=pNE#*yHPKk4)%-p&pL6V#B~lZKt#4+!DHct8rJNdq|gI4}Ukzr2PM|WI`7VbtK-L zzn`;VV5GLw*>c)rbNN=|u0;2cF2f$*T)&S@=z^ghj<;gNz)016YgdE z=jV^!O1g~yc6zw=$i%C572}7Az7;N}-s+*`vrgwHoc73s&9h%5p57(tGVJm2aO>gS zN$BE})WPT;Y#0-2KI!?`BNMv3?TbYBAo23<@Lu=NC*?Po30?J8KTLEFT#TQq=98X} zJu;!ow|bH29wh9M`rH2br0lV|U@VF5!G@tnYCh@t*kg0~Rxc9WgM>X&f7d^slsz^V zj3v=M*f8`+%_lt{du%S>>P4b^kg!MU@B8PIvd89vu_U?&8-^aK`K0G#kIm&_YW?SGd0%d&i=V59yq06@#cO|-qbQzCN zO!PaC#fI^7)pn|79a}<|Z#8a|e_ctJVULGvk4)%-p&pL6V#B~lZNIbS z+!DHct8rJNzYpm$9-o-#kFPy4p$mq3INpj4#Rzx(((FjCv^?Fn7J)wogKO1cdG9R2yVM<#T^P!Gq~WyAQnYCF}kjxC|f zw;DIf9)Iuq!bts(YmdzZV@dS)!G?j6nooK@_Sjr7mPG&hkS@dT(19B;*jfsxuywX9=H=<=<`jq+B~W!U3K*B+VB1w%a?Z^edzk=jmY%ef_V z`BvkuM1LRBW!U4#*B+VB1w%a?Z^edzk=jmY%ef_V`BvkuME8&`!yZ4m_Q-@T80z78 zD>e*_)OI>s&Ml$Kw;FdPx`%Wb_IT&oBNMt{sE6aN*f20s+v#jMw}dX=YTT9R9@1sl zyNS9%cpI&=pLKh76aJ&^821aT-oh|2< z(B)f=yAs_)x(s`~ckPi0T`<(c@m6dY7^&@awwzl+mv1%hN^}qDGVJmGwMQm&!B7v! zTd`qaq_)%9a&8G-zSX!Z(LJQgu*V129+}VuLp>aC#fE{A+D>Q7xg~V@R^zTj_mD2b z9zVPG$b>E!>fv}RHVll^b~;1;W- zgf8D|+?D7a(q-7=qic^$=z^ghj<;gNz({SUv*p|px_ql~SE74Jmtl{eUwdRi7Yy}q zycHV;Mru2qE$5cdDlu8wN&dJDn}(meA!}jk^-vL%Ixme17eb z30*MM!|_&Z7#OMTbheyZLYHqf?n-nI=`!r`#kEH!biq&$$6K*sV5GLw*>Y|PUB1=0 zE73iq%dp3n*B+VB1w%a?Z^edzk=jmY%ef_V`BvkuME8&`!ybRQ_Q-@T80z78D>e*_ z)OI>s&Ml$Kw;FdPx`%Wb_IQ2mkqKQe)Wh*sY#11+?R2)BTSAv_HSS7u59u=O@zu3Q zCUn72564@vVPK@T)7f%v30=O`xGT{;q|30!*Vi7I&;>(19B;*jfsxuyXUn-Iboo}} zu0;2cF2f%FZm~)E-vBb93x+xpZ_a-&VZ*>kZKt#4w8!T1t;Su6?jc=aC#fE{A+S{Hj=a$grTaCLC-9x$zd-%J>Cgt~$30*MMk?8Nk-w6gIwVmFc(B)f= z8|AH}%dp2Euir-|biq&$$Jb@Uz({SUv*p|px_ql~SK{gW;By)7{(rsD^yKF={N!aq z7e7}=;_3Tv!%t!0QXiEkJzgf<%l6ODAH9`y8UO9{aO;tYSM4gs4-aBj5=pMKjKUd8sJs*2yLYHs#BGEla*dz6~{qsrLV{^e+65WFhLyy#a z((|#$=JKsxB)SI)d!+uZe?BRDY%Ul}qI`pgbPqNRJyP>Y&&M8{%eQ)w=pH2Ok^0s8&pGqH+)5WeR}aTqv0+T8y=^V)*b=&A zj~nIdN|*82e=m9G+9MOXV5o=Vt=KRyQrqurIk$u^-)h{I=(19B;*jfsxvNXUn-Iboo}}u0($y(q%k8G0`7idt^cv4E1ol6&uFS zRokhSb!-V;zSX!avpP4P83x+xpZ_a=B@pE9Lw%^+mx_ql~qr8=L8T>i= z^J|Yx=z^ghj<3sx@pIL7s%0HpLYHqfZj?Qw%dp4mYmZFmf}tLcw_?M(19B;*jfsxvNXUn-Iboo}}u0($y(q-7=pVuCl&;>(19B;*j zfsxuyXUn-Iboo}}u0;2cF2f$*UwdRi7Yy}qycHV;Mru2qE$5cdDlu8wN&dJDn}(meA!} zjk^;4eMpyKj~`!qWI`7V^>Dlu8wN&dJDn}(meA!}jk^-vL%Ixm{N&mr6S`oihvTi- zFfdZv>1;W-gf8D|+?D7a(q-7=ookOw=z^ghj<;gNz({SUv*p|px_ql~SE74Jmtl{0 zuRSuM3x;|)-ii$aBek8*mUBz!@~y^QiS8j?hCP0I?U4yxFx12GR%{p;sqJ*OoLfSd zZ#C{pbPwq=?D5{UM<#T^P!Gpjv0-4Ow$s^iZV6q!)wnCsJ*3O9$NSeFna~A8JsfYv zhJlgVPG`%xC3N{#A6$E6LKh76aJ&^821aT-oh|2<(B)f=yAs_)x(s{# z?Ajv}x?re>yNS9%c zUtW7;LKh76aJ&^821aT-oh|2<(B)f=yAs{ww|!q2segCvvAJL@i8tr}zsrV!k(y6> zKK9sLFqTC3kS@dT(19B;*jfsxuyXUn-Iboo}}u0;2c zF2f$5UVCIh7Yy}qycHV;Mru2qE$5cdUtW7;LKh76aJ&^821aT-oh|2<(B)f=yAs_) zx(s{#;o2h;x?re>aC#fE{A+D>Q7xg~V@R^zTj_mD2b9$#O3WI`7V z^>Dlu8wN&dJDn}(meA!}jk^-vL%Ixm_`8iK<^M-G6S`oiBk|_^_e3@fjMR2ITTXjy zF5hb0mFOPQW!U4J>-UifT`<(c@m6dY7^%JO*>Y|PUB1=0E73iq%dm&P+jvrbADPev zLmi3!KKz};FjCv;?Fn7J)wogKO1cbt{PFsIWI`7V^>BP$HVll^b~;(~;yWRDx=>q?jL*ncm1=h`C^x?re>FGNB8GdN|&S z4ddsk?NrM;wuCOaihGIbQ%0P`txg#Oz4849*(cehVgUNcB*9^TSAv_HExtWq|30!>uZlp z=z^ghj<;gNz({Ssv*p|px_ql~SE7G?NSE>W#6*8{?U4yxFx12GR%{qQS8bY|PUB1=0E73iq%XoZZ zqW^X6kqKQe)Wh*sY#2XRZKqn+u_bi*R^vwb>qEK>d;I&_BNMt{sE6aN*f20s+wW{S zw}dX=YTT9R??bu_d;I6xBNMt{sE6aN*f20s+v#jMw}dX=YTT9R9@1sl(~;ye5-Mzyp?nr_W04YM<#T^P!Gpjv0-4Ow$s^iZV6q! z)wnCs--mP=_W1F&M<#T^P!Gpjv0-4Ow$s^iZV6q!)wnCsJ*3O9$4{<3GNB8GdN|&S z4Fe;!oz9kXOX%{g#$Ac-Azg+&-nsV3gf1BB;dm=H42;xvI$O>yq06@#cO|-qbQ$(| z_u3;9x?re>aC z#fE{A+D>Q7xg~V@R^zTj_mD2b9>2Ku$b>E!>fv}RHVll^b~;Y|P zUB1=0E73iq%dp39+}VuLp>aC#fE{A+D>Q7xg~V@R^zTj_mD2b z9v@$OWI`7V^>Dlu8wN&dJDn}(meA!}jk^-vL%Ixm{Px--6S`oihvTi-FfdZv>1;W- zgf8D|+?D7a(q-7=ch?@7&;>(19B;*jfsxuyXUn-Iboo}}u0;2cF2f$bzxK$4E*R?J zcq=vxjMR2ITh1+^%eNYLCAx=n8TNR*_Q-@T80z78D>e*_)OI>s&Ml$Kw;FdPx`%Wb z_W0!5BNMt{sE6aN*f20s+v#jMw}dX=YTT9R9@1slyNS9%c&#pZ(p$mq3INpj410%Ja&X#ja=<=<`U5V}?U4}hAzxK$4E*R?J zcq=vxjMR2ITh1+^%eNYLCAx=n8TR<%+9MOXV5o=Vt=KRyQrqclIk$u^-)h{I=pNE# z*yGD{WI`7V^>Dlu8wN&dJDn}( zmeA!}jk^-vL%IxmyuS9xgf1BB;dm=H42;xvI$O>yq06@#cO|-qbQ$*e>e?d{x?re> z$L8{_#$Ac-Azg+&zPWxMna~A8JsfYv zhJlgV+nz1wmeA!}jk^-vL%Ixm_-}Jf%I_l+x?rdy(cg#vrWTCUc6xh4mv1$0l(&*D z!ybRUejl081w%a?UzZI7Bek8*mUBz!@~y^QiKp*_&t+Gc$si7+dn^l^j6Yk{I}D?tw$zawW}CEO!TdAG4)mtC7*RVKjE}T zCTyPlBJuPtNta=dhlg7a?@mG&pQH{(_h7@AQ1eO8#~zu``pgbPqNRJyP>Y&&M8{ z%eQ)w=pH2Ok@~y-`K0WzxnL}b?!ktkM`}Ll`PgG~`BpCy-GhWZQh(n+pOif|7mOv* zJ=iexNX;ibAA4*r-|9u8dyue4>R0RUTgv-#D_#6tJsfYvhB2Y`wzaHdOX!k4Zj`Sp zUB+Yoz2u#1k4)%-p&pL6V#B~lZNIbS+!DHct8rJNzYpm$?D77!M<#T^P!Gpjv0-4O zw$s^iZV6q!)wnCsJ*3Ncd}5+My7tI~E*R?Jcq=xHpR2Z0E$i44x_ql~qx|bix(s_f zTzh0f7Yy}qycHV;Mr!+=E$5cdbGi@aO2yuRSuM z3x;|)zAhWa&sE#0mUV0iUB1=0QTC87!yd1%Ju;yShI%;OiVXuJwf)YPb4%#*t;Su6 z{`DbU#^Vzc{mr#UCUn72564@vVfY|PUB1=0E73iq%XoZZqW^O3kqKQe)Wh*sY#2XRZKqn+ zu_bi*R^vwb>qEK>d;Imy zNSE>W#6aC#fE{A+D>Q7xg~V@R^zTj_mD2*@rjB4*R@9`biq&$$6K*s{9Lu2YFWpY z(B)f=8|AML=`!r`?`w}t=z^ghj<;gNz({Ssv*p|px_ql~SE9cU=`!r`pKFgy=z^gh zj<;gNz({SUv*p|px_ql~SE74Jmtl`rzncH;c9MRBna~A8JsfYvhJlgVPPMFKOX%{g z#*Ok;(q-7=N7o*i&;>(19B;*jfsxuyXUn-Iboo}}u0($y(q-7=$JZX2&;>(19B;*j zfsxuyXUn-Iboo}}u0;2cF2f!_x%S9}E*R?Jcq=vxjMR2ITh1+^%eNYLCAx=n8TNSR z+9MOXV5o=Vt=KRyQrqclIk$u^-)h{I=pNE#*yG)6k4)%-p&pL6V#B~lZKt#4+!DHc zt8rJNdq|gIkDp$9WI`7V^>Dlu8wN&dJDn}(meA!}jk^-vL%Ixmym#%930*MM!|_&Z z7#OMTbheyZLYHqf?n-nI=`!r`{Y|PUB1=0E73iq%dp1> z*B+VB1w%a?Z^edzk=jmY%ef_V`BvkuME8&`!yZ4o_Q-@T80z78D>e*_)OI>s&Ml$K zw;FdPx`%Wb_W1DHBNMt{sE6aN*f20s+v#jMw}dX=YTT9R9@1slyNS9%cpI>`qLKh76aJ&^821aT-oh|2<(B)f=yAs_)x(s{# z;@TqaC#fE{A+D>Q7xg~V@R^zTj_mD2b9>2c!$b>E!>fv}R zHVll^b~;|BNMt{sE6aN*f20s+v#jMw}dX=YTT9R9@1sl z1;W-gf8D|+?D7a(q-7=lWUJm=z^ghj<;gNz({SUv*p|p zx_ql~SE74Jmtl`juRSuM3x;|)-ii$aBek8*mUBz!@~y^QiS8j?hCM#J_Q-@T80z78 zD>e*_)OI>s&Ml$Kw;FdPx`%Wb_W1nTBNMt{sE6aN*f20s+v#jMw}dX=YTT9R9@1sl zyNS9%cFRwi^p$mq3INpj410%Ja&X#ja z=<=<`U5V}?U4}jWaP5%^T`<(c@m6dY7^&@awwzl+mv1%hN^}qDGVJmC+9MOXV5o=V zt=KRyQrqclIk$u^-)h{I=pNE#*yF2fk4)%-p&pL6V#B~lZKt#4+!DHct8rJNdq|gI zkFT#iGNB8GdN|&S4Fe;!oz9kXOX%{g#$Ac-Azg+&{I|I#BP$HVll^b~;vVp?X^%|UJo`oB>0Od8!yXS0w;tY|gf2cw z9gOb5hB2Y$lb(+~GNH@czDRTr5-;x#?{)utQhtM(&{c2s!$kMM#rU~uKI!?`BNMuO zs~3sxLBbxXzwMt-${w2w#**kBY#4f^=98X}JvNtb^&-(dNZ2Fwcm4B8*<*9TSQ6cX z4MUIAeA4r=$L8{_UL?8)345gezJER`du%QkOQL(QVd#;XPkKJ~*j&EVi$wPzVUN_W z*59|3_vKc)__=yG-ii%lLhWs9S;v;pC41Z`Ust+}$NqcCJJ%kW&;>(19B;*jfsxvN zXUn-Iboo}}u0($y(q-7={cDd*=z^ghj<;gNz({SUv*p|px_ql~SE74Jm+|<-M1OSc zkqKQe)Wh*sY#2XRZKqn+u_bi*R^vwb*Ohb`_ISAV$b>E!>fv}RHVll^_B&h7EuqV| z8h0i7`;ac<@rjB4_}U{Ax?re>}DF2f%Dk^jtW30*MM zk$7|dyN{m(BenhBp3vo6jT_~yq|4yX(Vt&?WI`7V^>BP$HjJOEwo@(Z*b=&Yt8t_3 zAzg+&USE4;LKh76aJ&^821aW8oh|2<(B)f=yAu8DL%NK|CnoxvYmZFmf}tLcw_?Ni zxoSJrvW_jG%eNXg%I`zE414_P+9MOXV5o=Vt=KRyQrqurIk$u^-)h{I=$OKFbiq&$ z$6K*sV5GL+*>Y|PUB1=0E79MFbQ$*e+qFj~biq&$$6K*sV5GLw*>Y|PUB1=0E73iq z%dp3H*B+VB1w%a?Z^edzk=jmY%ef_V`BvkuME8&`yNS9%c@2@>Fp$mq3INpj410%Ja&X#ja=<=<` zU5V}?UB=@R6aBAik4)%-p&pL6V#D~kYCF}kjxC|fw;DIfUmwzC*yG>V9+}VuLp>aC z#fE{A+J0xtxg~V@R^zTje;?9i*yBIf9+}VuLp>aC#fE{A+D>Q7xg~V@R^zTj_mD2b z9&;>(19B;*jfsxuyXUn-Iboo}}u0;2cF2f!_z4pk2E*R?Jcq=vx zjMR2ITh1+^%eNYLCAx=n8TNSZ+9MOXV5o=Vt=KRyQrqclIk$u^-)h{I=pNE#*yH_c zk4)%-p&pL6V#B~lZKt#4+!DHct8rJNdq|gIj}NXrGNB8GdN|&S4Fe;!oz9kXOX%{g z#$Ac-Azg+&es=AV30*MM!|_&Z7#OMTbheyZLYHqf?n-nI=`!r`;k8F5biq&$$6K*s zV5GLw*>Y|PUB1=0E73iq%dp2s*B+VB1w%a?Z^edzk=jmY%ef_V`BvkuME8&`!yZ4s z_Q-@T80z78D>e*_)OI>s&Ml$Kw;FdPx`%Wb_V~rMM<#T^P!Gpjv0-4Ow$s^iZV6q! z)wnCsJ*3O9$1krvGNB8GdN|&S4Fe;!oz9kXOX%{g#$Ac-Azg+&9e*_)OI>s&Ml$K zw;FdPx`%Wb_W1bPBNMt{sE6aN*f20s+v#jMw}dX=YTT9R9@1slzq|Ixgf1BB;dm=H42;xvI$O>yq06@#cO|-qbQ$*e z{k2CXbiq&$$6K*sV5GLw*>Y|PUB1=0E73iq%dp4ewMQm&!B7v!Td`qaq_)%9a&8G- zzSX!Z(LJQgu*WCY9+}VuLp>aC#fE{A+D>Q7xg~V@R^zTj_mD2b9-m%&WI`7V^>Dlu z8wN&dJDn}(meA!}jk^-vL%Ixme0J@T30*MM!|_&Z7#OMTbheyZLYHqf?n-nI=`!r` z`L#zTbiq&$$6K*sV5GLw*>Y|PUB1=0E73iq%dp26*B+VB1w%a?Z^edzk=jmY%ef_V z`BvkuME8&`!yaE=dt^cv4E1ol6&nUdYCD}R=a$grTaCLC-9x$zd;H(19B;*jfsxuyXUn-Iboo}}u0;2cF2f#QUwdRi7Yy}qycHV;Mru2qE$5cd zE!>fv}RHVll^-u7%cw}dX=YTT9R9@1sl!+)D=Qhpzq&;>&siT*zPH??4-w$s}a zx_ql~qr8=L8TR<&_4~+#E*R?J__}Nu7^&@awwzl+mv1%hN<4iZd@jS?|DP9{p8R}< zpS(=y;^*o}JbfQ-_$drr>Z9_c$IFC!+5Y+YqqmYSbGI zDEX|@`3a{zGGX)V7m25LNxBSsJUrZbcy|)I_#|~Ox(6G^gqlx!KK96jE^qrH(LG4K zygR(t{qsrr4Q4`Dz10sB-2)fn=c@Un=VOmd=<=;zB)SI)d!+uhe?BRDY%Ul}qI`pgbPqNRJyP>Y&&M8{%eQ)w=pH2Ok^1}o z`K0WzxnL}b?!ktkM`}Ll`PgG~`BpCy-GhWZQomY%-%{R}Tj}EG>fv}RHjD|ix2q;}aA8(X~e=biq&$$6K*s{9Lu2YFWpY(B)f= z8|7bD(q-7=;o2h;x?re>%Rk4)%-p&pL6 zV#D~kYCF}kjxC|fw;DIf??bu_d+eh!S(_IrCmmv1$0l(&*D zgFi=qe(jM7T`<(c@paiSey-Y1wX9=H=<=<`jk1Sy8TNR6?U4yxFx12GR%{p;sqJ^R zoLfSdZ#C{p^sf);G9I6p=x?q)GNB8GdN|&S4ddsk?NrM;wuCOyNS9%cZ?8Qvp$mq3INpj410%Ja&X#ja=<=<`U5V}?UB=@R6aANKk4)%-p&pL6 zV#D~kYCF}kjxC|fw;DIfUmwzC*yFF)9+}VuLp>aC#fE{A+J0xtxg~V@R^zTje;?9i z*yC^49+}VuLp>aC#fE{A+D>Q7xg~V@R^zTj_mD2b9^YMiWI`7V^>Dlu8wN&dJDn}( zmeA!}jk^-vL%NK|Cnoyu*B+VB1w%a?Z^ef3bJcdLWgS~Wmv1$0l)pZt%dp2ku01lL z3x;|)-ii$aBeng`mUBz!@~y^QiT*yM%dp2kuRSuM3x;|)-ii$aBek8*mUBz!@~y^Q ziS8j?hCRN&_Q-@T80z78D>e*_)OI>s&Ml$Kw;FdPx`%Wbk55eWzpgzpp$mq3INpj4 zfv}RHVll^ zcB*9^TSAv_HExu*k}ks@Kf3nFgf1BB;dm=H42;xvI$O>yq06@#cP0A!kS@a>Kfdyq06@#cO|-qbQ$*e$+br&biq&$$6K*sV5GLw*>Y|PUB1=0 zE73iq%dp2g*B+VB1w%a?Z^edzk=jmY%ef_V`BvkuME8&`!yfNmdt^cv4E1ol6&nUd zYCD}R=a$grTaCLC-9x$zd;Ij;BNMt{sE6aN*f20s+v#jMw}dX=YTT9R9@1slyNS9%c_pd!Np$mq3INpj410%Ja&X#ja=<=<` zU5V}?U4}hAxc10|E*R?Jcq=vxjMR2ITh1+^%eNYLCAx=n8TRaC#fE{A+D>Q7xg~V@R^zTj_mD2b9v@wM zWI`7V^>Dlu8wN&dJDn}(meA!}jk^-vL%Ixm{QTM@6S`oihvTi-FfdZv>1;W-gf8D| z+?D7a(q-7=7uOz{&;>(19B;*jfsxuyXUn-Iboo}}u0;2cF2f$by!Ob1E*R?Jcq=vx zjMR2ITh1+^%eNYLCAx=n8TNR%_Q-@T80z78D>e*_)OI>s&Ml$Kw;FdPx`%Wb_W1R+ zM<#T^P!Gpjv0-4Ow$s^iZV6q!)wnCsJ*3O9$8WAZGNB8GdN|&S4Fe;!oz9kXOX%{g z#$Ac-Azg+&{^!~w6S`oihvTi-FfdZv>1;W-gf8D|+?D7a(q-7=<7aC#fE{A+D>Q7xg~V@ zR^zTj_mD2b9*@@^na~A8JsfYvhJlgVPG`%xC3N{#pIm!nLKh76aJ&^8 z21aT-oh|2<(B)f=yAs_)x(s`KdhL-3T`<(c@m6dY7^&@awwzl+mv1%hN^}qDGVJl$ zwMQm&!B7v!Td`qaq_)%9a&8G-zSX!Z(LJQgu*c`u9+}VuLp>aC#fE{A+D>Q7xg~V@ zR^zTj_mD2b9$#F0WI`7V^>Dlu8wN&dJDn}(meA!}jk^-vL%Ixme0lAW30*MM!|_&Z z7#OMTbheyZLYHqf?n-nI=`!r`hii{a=z^ghj<;gNz({SUv*p|px_ql~SE74Jmtl|B z*B+VB1w%a?Z^edzk=jmY%ef_V`BvkuME8&`!yaE`|#h?f|1%zZ%^p*t;UV=R?=nI{<%CSc#bjeQt+=_%fQh(P!pOnov7mOv*J=iexNX;ibAA4*r-|9u8|GFY!kJR7y z&nIP%%>`pgbPqNRJyP>Y&&M8{%eQ)w=pH2Ok^0s8`+f4h+)5WeR}aTqv0+T8y=^V) z*b=&Aj~nIdN|*82|2O!ZYmZFmf}tLcw_?MW#6*95?U4yxFx12G zR%{qQS8bi=^J|Yx=z^ghj<3sx@pIL7s%0HpLYHqfZj?Qw%dp4mYmZFmf}tLcw_?MDlu8wN&d`<*T4meA!}jk^;4eMpyKk3U~~WI`7V^>Dlu8wN&dJDn}(meA!} zjk^-vL%Ixme0%MY30*MM!|_&Z7#OMTbheyZLYHqf?n-nI=`tRlnCQP;dt^cv4E1ol z6&uFSRokhSb!-V;zSX!<{`!zE!ybRV_Q-@T80z78D>e*_)b=}D&Ml$Kw;FdP`umVB z!ybRT_Q-@T80z78D>e*_)OI>s&Ml$Kw;FdPx`%Wb_W17FBNMt{sE6aN*f20s+v#jM zw}dX=YTT9R9@1qzJ~7dMzxK$4E*R?Jcq=xHpR2Z0E$i44x_ql~qx|(DU4}jWaqW=_ zT`<(c@m6dY7^&@dwwzl+mv1%hO7!<3U4}jWdF_!2T`<(c@m6dY7^&@awwzl+mv1%h zN^}qDGVJmFwMQm&!B7v!Td`qaq_)%9a&8G-zSX!Z(LJQgczj}_|8?z=30*MM!|_&Z z7(Z8Sr&`vrC3N{#<3{=GL%Ixm{QKG?6S`oihvTi-FfdZv?`%1@gf8D|+?D9>L%Ixm z{O8&u6S`oihvTi-FfdZv>1;W-gf8D|+?D7a(q-7=)vxA%yPc%pU?z0IP!Gpjv0-4O zwo@(Z*b=&Yt8t^em2?^Q_|dgTCUn72564@vVPK@T)7f%v30=O`xGT}$hjbbC`0=$z zCUn72564@vVPK@T)7f%v30=O`xGT{;q|30!Pp&;Op$mq3INpj410%Ja&X#ja=<=<` zU5V}?U4}j0x%S9}E*R?Jcq=vxjMR2ITh1+^%eNYLCAx=n8TNSh+9MOXV5o=Vt=KRy zQrqclIk$u^-)h{I=pNE#*yE?y9+}VuLp>aC#fE{A+D>Q7xg~V@R^zTj_mD2b9`9Xy zWI`7V^>Dlu8wN&dJDn}(meA!}jk^-vL%IxmynpSH30*MM!|_&Z7#OMTbheyZLYHqf z?n-nI=`!r`!L>&wbiq&$$6K*sV5GLw*>Y|PUB1=0E73iq%dp4Ku01lL3x;|)-ii$a zBek8*mUBz!@~y^QiS8j?hCM#K_Q-@T80z78D>e*_)OI>s&Ml$Kw;FdPx`%Wb_W0=9 zBNMt{sE6aN*f20s+v#jMw}dX=YTT9R9@1slzqt0ugf1BB;dm=H42;xvI$O>yq06@#cO|-qbQ$*e<+Vp9biq&$$6K*s zV5GLw*>Y|PUB1=0E73iq%dp48wMQm&!B7v!Td`qaq_)%9a&8G-zSX!Z(LJQgu*a{j zJu;yShI%;OiVXuJwVlqEb4%#*t;Su6?jc=A76W9LKh76aJ&^8 z21aT-oh|2<(B)f=yAs_)x(s{#_Sz#8x?re>e*_)OI>s&Ml$K zw;FdPx`%Wb_ISMZ$b>E!>fv}RHVll^b~;1;W-gf8D|+?D7a(q-7=(`%1R=z^ghj<;gNz({SUv*p|px_ql~SE74Jmtl|3 zu01lL3x;|)-ii$aBek8*mUBz!@~y^QiS8j?hCM#N_Q-@T80z78D>e*_)OI>s&Ml$K zw;FdPx`%Wb_W0u3BNMt{sE6aN*f20s+v#jMw}dX=YTT9R9@1slyNS9%cKU{lcLKh76aJ&^821aT-oh|2<(B)f=yAs_)x(s`~ zzV^t3E*R?Jcq=vxjMR2ITh1+^%eNYLCAx=n8TR<<+9MOXV5o=Vt=KRyQrqclIk$u^ z-)h{I=pNE#*yHPKk4)%-p&pL6V#B~lZKt#4+!DHct8rJNdq|gI5C1KwN%>E(GNB8G zIudWr@3ZvZaDtKAPH#`>@~y^=@>bGi*yEe)_mK%*Fx12Gb=fd5QrqclIk$u^-)h{I z=aih2upUWS)`~Ukwzk1mIdF{)D zE`F}&)6ej_ZukidT#UT)XP#Sv8#1!bpLyCtx(qu#JluL@LKh6Rg!5O48-5A{7bEZd znWsHASH6h;`97q}u*d6%TaQfWs<--KqQ4JZ42;x#((|#$=E@hDl-~!5mv@KvZU6l3 z30*MMk?0<57(ZA2`T><=k4)(DtHyOz4849*(zS!@x-GZO@i-OX%{g z#$Ad2KBUXA$NSeFna~A8JsfYvhJlgVPG`%xC3N{#7s3Xz;_sY+K zk=lN5Pw4Wk#*Ok;(q-`H=+Cb`GNB8GdN{r=8^+I7+o_gyYzbYy)wogikS@a>udh8a zp$mq3INpj410%Kl&X#ja=<=<`U5WnnAzjAf6BGT-wMQm&!B7v!Td`sMT(zBQS;v;p z(19B;*jfsxuyXUn-Iboo}}u0;2cF5~fu ziT>BMM<#T^P!Gpjv0?mNwVi5N$Cl9LTa6p#uMg=m?D6kwk4)%-p&pL6V#B~lZNIbS z+!DHct8rJNzYpm$?D3y#k4)%-p&pL6V#B~lZKt#4+!DHct8rJNdq|gIk5|8%|Lt~? zeuJ6N1w%a?Z^edzk=jnRtYb^)@~y^=@>bGi*yBgn9+}VuLp>aC#fE{A+D>Q7xg~V@ zR^zTje;?9i*yG379+}VuLp>aC#fE{A+D>Q7xg~V@R^zTj_mD2b9zVJE$b>E!>fv}R zHVll^b~;1;W-gf8D|+?D7a(q-7= z-D{6b=z^ghj<;gNz({SUv*p|px_ql~SE74Jmtl{eUVCIh7Yy}qycHV;Mru2qE$5cd z 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 literal 0 HcmV?d00001 diff --git a/build-bearing-bracket.mjs b/build-bearing-bracket.mjs new file mode 100644 index 0000000..394b5d7 --- /dev/null +++ b/build-bearing-bracket.mjs @@ -0,0 +1,99 @@ +import fs from 'node:fs/promises'; +import { initGeometry } from './js/geometry.js'; +import { Doc } from './js/doc.js'; + +await initGeometry(); + +const doc = new Doc(); +doc.name = '轴承支座_160x70_φ68_φ40H7'; +doc.units = 'mm'; + +// Named dimensions are kept in the document for editing and inspection. +for (const [name, expression] of [ + ['BaseLength', '160'], ['BaseWidth', '70'], ['BaseHeight', '14'], ['BaseCornerR', '12'], + ['MountHoleDia', '13'], ['MountHoleX', '60'], ['MountHoleY', '15'], + ['StandThicknessX', '26'], ['StandWidthY', '46'], ['StandHeight', '22'], + ['BossOuterDia', '68'], ['BossBoreDia', '40'], ['BossLengthX', '60'], ['BossCenterZ', '70'], + ['BoreChamfer', '2'], ['RibThickness', '8'], +]) doc.setDimension(name, expression); + +function sketch(plane, offset, name) { + return doc.addSketch({ plane, offset, name }); +} +function line(sk, p1, p2) { + doc.addEntity(sk.id, { type: 'line', p1, p2 }); +} +function arc(sk, c, r, a0, a1) { + doc.addEntity(sk.id, { type: 'arc', c, r, a0, a1 }); +} +function circle(sk, c, r) { + doc.addEntity(sk.id, { type: 'circle', c, r }); +} +function feature(type, name, params, sketchId = null) { + return doc.addFeature({ type, name, params, sketchId }); +} + +// 1. 160 x 70 x 14 base plate, true R12 corner arcs. +const base = sketch('front', 0, '底板外轮廓_R12'); +line(base, [-68, -35], [68, -35]); +arc(base, [68, -23], 12, -Math.PI / 2, 0); +line(base, [80, -23], [80, 23]); +arc(base, [68, 23], 12, 0, Math.PI / 2); +line(base, [68, 35], [-68, 35]); +arc(base, [-68, 23], 12, Math.PI / 2, Math.PI); +line(base, [-80, 23], [-80, -23]); +arc(base, [-68, -23], 12, Math.PI, 3 * Math.PI / 2); +feature('extrude', '底板_160x70x14', { depth: 14 }, base.id); + +// 2. Four Ø13 through holes at ±60, ±15. +const holes = sketch('front', 0, '四个安装孔_孔距120x30'); +for (const x of [-60, 60]) for (const y of [-15, 15]) circle(holes, [x, y], 6.5); +feature('cut', '四个φ13安装通孔_边距20', { throughAll: true, mode: 'throughAll' }, holes.id); + +// 3. Central upright plate, z=14 to z=36. +const stand = sketch('front', 14, '立板_26x46'); +doc.addEntity(stand.id, { type: 'rect', c: [0, 0], w: 26, h: 46 }); +feature('extrude', '立板_厚26_宽46_高22', { depth: 22 }, stand.id); + +// 4. Hollow boss along X: x=-30..30, center (y,z)=(0,70). +const boss = sketch('right', -30, '圆筒座_φ68_φ40'); +circle(boss, [70, 0], 34); +circle(boss, [70, 0], 20); +feature('extrude', '圆筒座_外φ68_内φ40_长60', { depth: 60 }, boss.id); + +// 5. C2 inner-bore chamfers. The extended cylinder primitive is used as a +// conical cutting tool, with the large radius at each outside end. +feature('cylcut', '内孔左端_C2', { r: 22, r2: 20, h: 2, axis: 'x', tx: -29, tz: 70 }); +feature('cylcut', '内孔右端_C2', { r: 20, r2: 22, h: 2, axis: 'x', tx: 29, tz: 70 }); + +// 6. Two triangular gussets, front and rear of the upright plate. +// The 8 mm extrusion is along Y; the apex intentionally penetrates the boss +// slightly so the Boolean union is robust while remaining tangent in design. +const ribFront = sketch('top', -27, '前加强筋_三角截面'); +line(ribFront, [-13, 14], [0, 14]); +line(ribFront, [0, 14], [13, 14]); +line(ribFront, [13, 14], [0, 66]); +line(ribFront, [0, 66], [-13, 14]); +feature('extrude', '前加强筋_厚8', { depth: 8 }, ribFront.id); + +const ribRear = sketch('top', 19, '后加强筋_三角截面'); +line(ribRear, [-13, 14], [0, 14]); +line(ribRear, [0, 14], [13, 14]); +line(ribRear, [13, 14], [0, 66]); +line(ribRear, [0, 66], [-13, 14]); +feature('extrude', '后加强筋_厚8', { depth: 8 }, ribRear.id); + +doc.rebuild(); +const errors = doc.features.filter(f => f.error).map(f => ({ name: f.name, error: f.error })); +if (errors.length) throw new Error(JSON.stringify(errors)); + +const bbox = doc.bbox; +const summary = { + bbox: { min: bbox.min, max: bbox.max }, + volume: doc.bodyManifold.volume(), + sketches: doc.sketches.length, + features: doc.features.length, + featureNames: doc.features.map(f => f.name), +}; +console.log(JSON.stringify(summary, null, 2)); +await fs.writeFile('./轴承支座_160x70_φ68_φ40H7.json', JSON.stringify(doc.toJSON(), null, 2), 'utf8'); diff --git a/css/style.css b/css/style.css index 1f68b95..20c72d4 100644 --- a/css/style.css +++ b/css/style.css @@ -4,16 +4,30 @@ --fg:#d7dce3; --fg-dim:#8b94a1; --fg-bright:#fff; --accent:#3d8bff; --accent2:#5ea3ff; --warn:#ffb454; --err:#ff6b6b; --ok:#5fd08a; --sketch:#ffd54a; --sel:#7fb8ff; - --ribbon-h:86px; --status-h:26px; + --chrome-h:30px; --ribbon-h:86px; --status-h:28px; } *{box-sizing:border-box; margin:0; padding:0} html,body{height:100%; overflow:hidden} body{background:var(--bg); color:var(--fg); font:13px/1.45 "Microsoft YaHei", "Segoe UI", sans-serif; user-select:none} #app{display:flex; flex-direction:column; height:100%} +/* ---------- application chrome ---------- */ +#app-chrome{height:var(--chrome-h); display:flex; align-items:center; gap:14px; padding:0 10px; background:#1a1e23; border-bottom:1px solid #11151a; color:var(--fg-dim); flex-shrink:0; font-size:11px} +.chrome-brand{display:flex; align-items:center; gap:6px; color:var(--fg); font-weight:700; letter-spacing:.6px; min-width:84px} +.chrome-mark{display:inline-grid; place-items:center; width:18px; height:18px; border-radius:4px; background:linear-gradient(135deg,#5ea3ff,#2f6fd6); color:#fff; font-size:11px; box-shadow:0 1px 4px rgba(0,0,0,.35)} +.chrome-context{display:flex; align-items:center; gap:8px; min-width:0} +.chrome-doc{color:var(--fg); max-width:260px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap} +.chrome-sep{color:var(--line2)} +.chrome-mode{color:var(--accent2)} +.chrome-state{padding:2px 7px; border:1px solid #2e5c40; border-radius:9px; color:#a5e8c0; background:#213a2c} +.chrome-state.err{border-color:#7a3a34; color:#ffb4ae; background:#3a2424} +.chrome-actions{display:flex; align-items:center; gap:6px; margin-left:auto; white-space:nowrap} +.chrome-status-dot{width:7px; height:7px; border-radius:50%; background:var(--ok); box-shadow:0 0 0 3px rgba(95,208,138,.12)} +.chrome-status-label{color:var(--fg-dim)} button{font-family:inherit; color:var(--fg); background:var(--panel2); border:1px solid var(--line); border-radius:5px; cursor:pointer} button:hover{background:var(--panel3); border-color:var(--line2)} button.active{background:#27456e; border-color:var(--accent)} button:disabled{opacity:.4; cursor:default} +button:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible{outline:2px solid var(--accent2); outline-offset:1px} input,textarea,select{font-family:inherit; color:var(--fg); background:#1a1e24; border:1px solid var(--line); border-radius:4px; padding:4px 6px; outline:none} input:focus,textarea:focus,select:focus{border-color:var(--accent)} input[type=number]{width:90px} @@ -47,8 +61,8 @@ input[type=number]{width:90px} /* ---------- workspace ---------- */ #workspace{flex:1; display:flex; min-height:0} -#tree-panel{width:210px; background:var(--panel); border-right:1px solid var(--line); display:flex; flex-direction:column; flex-shrink:0} -#props-panel{width:280px; background:var(--panel); border-left:1px solid var(--line); display:flex; flex-direction:column; flex-shrink:0; overflow-y:auto} +#tree-panel{width:230px; background:var(--panel); border-right:1px solid var(--line); display:flex; flex-direction:column; flex-shrink:0} +#props-panel{width:300px; background:var(--panel); border-left:1px solid var(--line); display:flex; flex-direction:column; flex-shrink:0; overflow-y:auto} /* ---------- resizer ---------- */ .resizer{position:relative; flex-shrink:0; z-index:15} @@ -56,8 +70,9 @@ input[type=number]{width:90px} .resizer-h:hover, .resizer-h.dragging{background:var(--accent)} .resizer-v{height:4px; cursor:row-resize; background:var(--line); transition:background .15s} .resizer-v:hover, .resizer-v.dragging{background:var(--accent)} -.panel-title{padding:8px 10px; font-size:12px; color:var(--fg-dim); border-bottom:1px solid var(--line); background:var(--panel2); flex-shrink:0} +.panel-title{display:flex; align-items:center; gap:0; min-height:36px; padding:8px 12px; font-size:12px; color:var(--fg); border-bottom:1px solid var(--line); background:linear-gradient(180deg,#303640,#2b3038); flex-shrink:0; font-weight:600} .panel-sub{color:var(--line2); margin-left:6px} +.panel-meta{margin-left:auto; color:var(--fg-dim); font-size:10px; font-weight:400; border:1px solid var(--line); border-radius:9px; padding:1px 6px; background:rgba(0,0,0,.14)} #tree{flex:1; overflow-y:auto; padding:6px 4px} #props{flex:1; padding:10px} @@ -76,8 +91,11 @@ input[type=number]{width:90px} .prop-sec{font-size:11px; color:var(--fg-dim); margin:12px 0 6px; text-transform:uppercase; letter-spacing:.5px} .prop-row{display:flex; align-items:center; gap:8px; margin:6px 0} .prop-row label{width:64px; color:var(--fg-dim); font-size:12px; flex-shrink:0} -.prop-row .val{flex:1} +.prop-row .val{flex:1; min-width:0; overflow-wrap:anywhere; word-break:break-word} .prop-note{font-size:11px; color:var(--fg-dim); line-height:1.5; margin:6px 0} +.prop-note{max-width:100%; overflow-wrap:anywhere; word-break:break-word; white-space:pre-wrap} +.prop-note.prop-error{color:#ffb4ae;border-left:2px solid var(--err);padding-left:8px} +.prop-note.prop-ok{color:#a5e8c0;border-left:2px solid var(--ok);padding-left:8px} .prop-btns{display:flex; gap:8px; margin-top:12px} .prop-btns button{flex:1; padding:6px} .btn-ok{background:linear-gradient(180deg,#3d8bff,#2f6fd6); border-color:#2459a8; color:#fff} @@ -95,8 +113,17 @@ input[type=number]{width:90px} /* ---------- viewport ---------- */ #viewport-wrap{flex:1; position:relative; min-width:0; background:#15181d} #viewport{position:absolute; inset:0} +#drawing-preview{position:absolute; inset:18px; z-index:8; display:flex; align-items:center; justify-content:center; background:#20252b; border:1px solid var(--line); border-radius:8px; overflow:auto; box-shadow:0 10px 28px rgba(0,0,0,.25)} +#drawing-preview svg{width:min(92%,900px); height:auto; max-height:94%; background:#fff; box-shadow:0 4px 18px rgba(0,0,0,.35)} +#viewport-wrap:after{content:""; position:absolute; inset:0; pointer-events:none; border:1px solid rgba(255,255,255,.025)} +#empty-state{position:absolute; z-index:5; left:50%; top:50%; transform:translate(-50%,-46%); width:min(390px,calc(100% - 44px)); padding:24px 26px 22px; text-align:center; border:1px solid var(--line); border-radius:12px; background:rgba(29,34,41,.9); box-shadow:0 16px 40px rgba(0,0,0,.28); backdrop-filter:blur(7px)} +.empty-glyph{width:44px; height:44px; margin:0 auto 10px; display:grid; place-items:center; color:var(--accent2); border:1px solid #3f6594; border-radius:12px; background:#1d2c47; font-size:27px; line-height:1} +.empty-title{font-size:16px; color:var(--fg-bright); font-weight:700} +.empty-copy{margin:7px auto 16px; max-width:300px; color:var(--fg-dim); font-size:12px; line-height:1.6} +.empty-actions{display:flex; justify-content:center; gap:8px} +.empty-actions button{padding:6px 12px; font-size:12px} #viewport-hud{position:absolute; left:10px; top:10px; pointer-events:none; color:var(--fg-dim); font-size:12px; display:flex; flex-direction:column; gap:4px} -#viewport-hud .hud-line{background:rgba(20,24,30,.7); padding:2px 8px; border-radius:4px; backdrop-filter:blur(4px)} +#viewport-hud .hud-line{background:rgba(20,24,30,.78); padding:4px 9px; border:1px solid rgba(69,78,91,.7); border-radius:5px; backdrop-filter:blur(4px)} #viewport-hud .hud-sub{font-size:11px; color:var(--fg-dim)} #viewport-hud .hud-snaptip{color:var(--warn)} @@ -112,20 +139,23 @@ input[type=number]{width:90px} .minisw-label.snapmark{pointer-events:none; color:#7fb8ff; font-size:13px; background:transparent; border:none} /* ---------- status bar ---------- */ -#statusbar{height:var(--status-h); background:var(--panel); border-top:1px solid var(--line); display:flex; align-items:center; gap:16px; padding:0 10px; font-size:12px; color:var(--fg-dim); flex-shrink:0} +#statusbar{height:var(--status-h); background:#20252b; border-top:1px solid var(--line); display:flex; align-items:center; gap:16px; padding:0 10px; font-size:11px; color:var(--fg-dim); flex-shrink:0} #statusbar .sb-item{white-space:nowrap} #statusbar .sb-tip{flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap} #statusbar .sb-snap{color:var(--warn)} +.sb-item strong{font-weight:600} /* ---------- AI panel ---------- */ -#ai-panel{position:absolute; right:12px; bottom:12px; width:400px; max-height:70%; background:var(--panel); border:1px solid var(--line2); border-radius:10px; box-shadow:0 8px 30px rgba(0,0,0,.5); display:flex; flex-direction:column; z-index:30; overflow:hidden} +#ai-panel{position:absolute; right:14px; bottom:14px; width:min(430px,calc(100% - 28px)); max-height:72%; background:var(--panel); border:1px solid var(--line2); border-radius:10px; box-shadow:0 8px 30px rgba(0,0,0,.5); display:flex; flex-direction:column; z-index:30; overflow:hidden} #ai-panel.hidden{display:none} .ai-header{display:flex; align-items:center; gap:8px; padding:8px 12px; background:var(--panel2); border-bottom:1px solid var(--line); cursor:move; user-select:none} .ai-title{font-weight:700; color:var(--accent2)} -.ai-mode-badge{font-size:11px; background:#1d2c47; color:#9ecbff; border:1px solid #23466e; padding:1px 8px; border-radius:10px} +.ai-mode-badge{font-size:11px; background:#1d2c47; color:#9ecbff; border:1px solid #23466e; padding:1px 8px; border-radius:10px; cursor:pointer} .ai-mode-badge.llm{background:#3a2c1d; color:#ffd9a0; border-color:#6e5523} +.ai-mode-badge:focus-visible{outline:2px solid var(--accent2); outline-offset:2px} .ai-close{margin-left:auto; background:none; border:none; font-size:16px; color:var(--fg-dim); padding:0 4px} .ai-close:hover{color:var(--fg)} +.ai-guide{padding:6px 12px; font-size:11px; color:var(--fg-dim); background:#20252c; border-bottom:1px solid var(--line); line-height:1.4} .ai-chat{flex:1; overflow-y:auto; padding:10px; display:flex; flex-direction:column; gap:8px; min-height:120px; max-height:280px} .ai-msg{max-width:88%; padding:7px 10px; border-radius:8px; font-size:12.5px; line-height:1.55; white-space:pre-wrap; word-break:break-word} .ai-msg.user{align-self:flex-end; background:#27456e; color:#eaf2ff} @@ -139,9 +169,11 @@ input[type=number]{width:90px} .chip:hover{color:var(--fg); border-color:var(--accent)} .ai-input-row{display:flex; gap:8px; padding:10px; border-top:1px solid var(--line)} .ai-input-row textarea{flex:1; resize:none; user-select:text} +#ai-input{min-height:48px; line-height:1.5} #ai-send{width:64px; background:linear-gradient(180deg,#3d8bff,#2f6fd6); border-color:#2459a8; color:#fff; font-weight:600} #ai-send:disabled{filter:grayscale(.7)} #ai-send .spin{display:inline-block; animation:spin 1s linear infinite} +.ai-input-hint{padding:0 12px 8px; color:var(--fg-dim); font-size:10px; text-align:right; border-top:0} @keyframes spin{to{transform:rotate(360deg)}} /* ---------- misc ---------- */ @@ -164,6 +196,21 @@ input[type=number]{width:90px} .splash-bar-fill{height:100%; width:0%; background:var(--accent); transition:width .3s} .hidden{display:none !important} +@media (max-width:900px){ + #app-chrome{gap:8px} + .chrome-actions{display:none} + #tree-panel{width:190px} + #props-panel{width:250px} + #ai-panel{width:min(390px,calc(100% - 20px)); right:10px; bottom:10px} +} +@media (max-width:680px){ + #app-chrome{padding:0 7px} + .chrome-context{font-size:10px} + .chrome-doc{max-width:150px} + #tree-panel{width:170px} + #props-panel{display:none} + #ai-panel{width:calc(100% - 16px); right:8px; bottom:8px; max-height:76%} +} ::-webkit-scrollbar{width:9px; height:9px} ::-webkit-scrollbar-thumb{background:var(--line2); border-radius:5px} ::-webkit-scrollbar-track{background:transparent} diff --git a/index.html b/index.html index 7f44a10..fa7b210 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ MiniSW · AI 参数化建模 - + + diff --git a/js/agent.js b/js/agent.js index d40a323..f24bed1 100644 --- a/js/agent.js +++ b/js/agent.js @@ -1,21 +1,29 @@ // agent.js —— AI 智能体建模:自然语言 → 建模操作序列 → 执行 -// 两条路径: -// 1) 内置规则解析器(离线可用,中文优先) -// 2) LLM 智能体(OpenAI 兼容接口,可配置,生成同样的 op 序列) +// 统一路径:本地精确闸门优先;任何模糊、缺参或复合请求交给一次紧凑的 LLM 规划, +// 最终统一通过同一套 DSL 校验、预检和执行。 // // 宏展开层(expandOps):高级指令(倒角/圆角/多边形/抽壳/镜像/阵列/凸台) // 在 validateOps / executeOps 之前被展开为已有原语操作序列, // 无需修改 geometry.js / doc.js 的接口契约。 +import { createAssembly, addComponent, addMate, checkAllInterferences, setExplode } from './features/assembly.js?v=20260810-assembly2'; +import { createDrawing, layoutThreeViews, addView, addDimension, addSectionView } from './features/drawing.js'; + +let agentConstraintSeq = 1000000; +const nextIdForAgent = () => agentConstraintSeq++; export const DSL_SPEC = ` 你是参数化三维建模智能体。根据用户的中文/英文需求,生成一段"建模操作序列"。 -只输出一个 JSON 数组,不要输出任何其他文字、注释或 Markdown。 +你可以在模型内部充分分析、反复校验和规划;这些分析不属于最终输出,不要写入 message content。 +最终只输出一个 {"ops":[...]} JSON 对象,不要输出任何其他文字、注释或 Markdown。 -坐标系:X 向右、Y 向上、Z 朝外。单位:毫米 mm。 -可用草图平面:front(前视,法向+Y)、top(上视,法向+Z)、right(右视,法向+X)。 +坐标系:X 向右、Y 向上、Z 朝外。单位:毫米 mm。模型参数必须保持真实工程尺寸;不要为了适配屏幕擅自按比例缩小或放大,视口会在生成后自动取景。 +可用草图平面:front(前视,法向+Z)、top(上视,法向+Y)、right(右视,法向+X)。 2D 坐标用 [u,v],v 为草图平面内"上"方向。 支持的 op(按顺序执行): +{"op":"query_document","answer":"根据当前模型状态回答用户的问题"} —— 查询当前文档;answer 必须是给用户的最终中文回答 +{"op":"delete_feature","featureId":3} —— 删除稳定 ID 对应的特征;用户只说“删除”时优先使用 selectedFeatureId,否则使用 lastFeatureId +{"op":"update_feature","featureId":3,"patch":{"depth":20}} —— 修改特征参数;patch 只包含需要改变的字段 {"op":"sketch","plane":"front|top|right","offset":0,"name":"草图1"} —— 开始一个新草图;后续 2D 实体属于它,直到下一个 sketch。offset 为沿法向偏移。 {"op":"rect","cx":0,"cy":0,"w":100,"h":80} —— 中心在(cx,cy)、宽 w、高 h 的矩形 @@ -23,10 +31,26 @@ export const DSL_SPEC = ` {"op":"circle","cx":0,"cy":0,"r":10} —— 圆 {"op":"arc","cx":0,"cy":0,"r":10,"a0":0,"a1":1.5708} —— 圆弧(a0/a1 为起止弧度) {"op":"centerline","p1":[u,v],"p2":[u,v]} —— 中心线(构造线,不参与实体,可作旋转轴) -{"op":"extrude","depth":20,"flip":false,"symmetric":false,"name":"拉伸1"} - —— 把当前草图拉伸成凸台(拉伸方向为平面法向;flip 反向) -{"op":"cut","depth":5,"throughAll":true,"name":"切除1"} - —— 用当前草图拉伸切除(throughAll=true 时完全贯穿) +{"op":"ellipse","cx":0,"cy":0,"rx":30,"ry":15,"rotation":0} —— 椭圆 +{"op":"slot","p1":[-20,0],"p2":[20,0],"width":10} —— 长圆槽 +{"op":"spline","points":[[u,v],...],"closed":false} —— 样条折线近似 +{"op":"constraint","type":"horizontal|vertical|coincident|parallel|perpendicular|equal|distance|length|radius|diameter|angle|fixed","refs":[],"value":0} —— 草图约束 +{"op":"set_dimension","name":"d1","expression":"120"} —— 命名尺寸 +{"op":"set_parameter","name":"pitch","value":2} —— 参数表变量 +{"op":"sketch_pattern_linear","entityIds":[1],"count":4,"dx":20,"dy":0} —— 草图线性阵列 +{"op":"sketch_pattern_circular","entityIds":[1],"count":6,"cx":0,"cy":0} —— 草图圆周阵列 +{"op":"assembly_create","name":"装配体"} —— 新建装配体 +{"op":"assembly_add_component","id":"base","name":"底座","source":"current"} —— 向装配体插入当前零件 +{"op":"mate","type":"coincident|concentric|distance|parallel|perpendicular|angle","refA":{},"refB":{},"value":0} —— 添加装配配合 +{"op":"interference_check"} —— 检查组件干涉 +{"op":"explode","progress":1,"vectors":{}} —— 设置爆炸视图 +{"op":"drawing_generate","format":"A4","orientation":"landscape","scale":1} —— 生成三视图工程图 +{"op":"drawing_dimension","value":0,"text":"尺寸"} —— 添加工程图尺寸 +{"op":"drawing_section","name":"A-A 剖视图"} —— 添加剖视图 +{"op":"extrude","mode":"blind|throughAll|symmetric|toNext|toFace|toBody","depth":20,"targetFaceRef":null,"targetBodyRef":null,"offset":0,"flip":false,"name":"拉伸1"} + —— 把当前草图拉伸成凸台;blind/symmetric 需要 depth,toFace 需要稳定面/基准面引用,toBody 当前目标写 current +{"op":"cut","mode":"throughAll","depth":5,"targetFaceRef":null,"targetBodyRef":null,"offset":0,"flip":false,"name":"切除1"} + —— 用当前草图拉伸切除;终止模式与 extrude 相同 {"op":"revolve","revolveDegrees":360,"flip":false,"axis":null,"name":"旋转1"} —— 把当前草图绕旋转轴旋转成凸台。旋转轴:默认草图 u=0 处的竖直轴; 若草图含 centerline 则自动以它为轴。 @@ -34,6 +58,18 @@ export const DSL_SPEC = ` —— 直接生成圆柱原语(轴线沿 Z,无需草图)。tx/ty/tz 为世界系偏移。 {"op":"cylcut","r":20,"h":80,"through":true,"tx":0,"ty":0,"tz":0,"name":"圆柱孔1"} —— 圆柱切除(through=true 贯穿;否则深度 h) +{"op":"sweep","path":[[0,0,0],[100,0,0]],"radius":5,"segments":32,"name":"扫描1"} + —— 用圆形截面沿三维路径扫描。path 至少两个不同的世界坐标点;radius 为截面半径 +{"op":"loft","profiles":[[[x,y,z],...],[[x,y,z],...]],"resampleCount":32,"name":"放样1"} + —— 两个或多个平行封闭截面之间放样;profiles 中每项是一个三维闭合点环 +{"op":"rib","thickness":3,"extent":30,"side":"both|left|right","draftDegrees":0,"flip":false,"name":"筋1"} + —— 把当前连续开放草图路径扩展为薄壁筋并与已有主体合并 +{"op":"chamfer_edge","edgeRef":{"type":"edge-query","point":[0,-40,0],"direction":[1,0,0]},"distance":5,"name":"实体倒角1"} + —— 对已有凸实体最接近定位点且方向匹配的一条直棱做真实倒角 +{"op":"fillet_edge","edgeRef":{"type":"edge-query","point":[0,-40,0],"direction":[1,0,0]},"radius":5,"segments":8,"name":"实体圆角1"} + —— 对已有凸实体的一条直棱做恒定半径、多段圆弧过渡的真实圆角 +{"op":"reference_plane","kind":"offset","source":"front","distance":10,"name":"偏移基准面1"} + —— 创建参考基准面。kind 可为 offset;source 为已有基准面 ID 高级 op(会被自动展开为上述原语序列): {"op":"chamfer_rect","cx":0,"cy":0,"w":100,"h":80,"chamfer":5} @@ -50,6 +86,8 @@ export const DSL_SPEC = ` —— 线性阵列上一个特征(count 含原始实例,dx/dy 为步距) {"op":"pattern_circular","count":6,"cx":0,"cy":0} —— 圆周阵列上一个特征(count 含原始实例,绕(cx,cy)均布) +{"op":"spur_gear","module":2,"teeth":20,"width":20,"pressureAngle":20,"boreDiameter":10,"name":"直齿轮"} + —— 参数化渐开线直齿轮;只提供齿轮参数,齿形坐标、单齿和圆周阵列由本地几何编译器生成 规则: - 特征按顺序执行:凸台=并集,切除=差集。 @@ -57,6 +95,8 @@ export const DSL_SPEC = ` - 圆孔半径 = 直径/2。 - 钻孔位置要相对底板边界留出合理边距(通常 10~20mm)。 - 空心圆柱:外层 cylinder 后再加一个稍大的 cylcut。 +- 直齿轮必须优先使用单个 spur_gear 高级 op,禁止手算渐开线坐标或逐个输出轮齿。 +- 用户只给出对象名或模糊用途(如“椅子”“桌子”“支架”)时,必须根据常见机械设计默认值继续建模,不要追问尺寸;把默认值写入特征 name 或参数,只有删除/修改目标不存在等无法推断的请求才追问。 - 简单模型应控制在 10 个 op 以内。 - 镜像/阵列操作前必须先有一个特征(凸台/切除/圆柱等)。 `; @@ -90,6 +130,199 @@ export function parseNaturalLanguage(text) { const hasCircularPattern = /圆周阵列|环形阵列|圆周均布|环形均布/.test(t); const hasMirror = /镜像|对称复制/.test(t); const hasBoss = /凸台|凸起|加.*圆柱|顶.*圆柱|加.*凸/.test(t); + const hasSweep = /扫描|扫掠/.test(t); + const hasLoft = /放样/.test(t); + const hasRib = /筋板|加强筋|肋板|\b筋\b/.test(t); + const hasEllipse = /椭圆|椭圆形/.test(t); + const hasSlot = /长圆槽|腰形槽|长槽/.test(t); + const hasEdgeChamfer = /实体.{0,8}倒角|棱边.{0,8}倒角|边缘.{0,8}倒角/.test(t); + const hasEdgeFillet = /实体.{0,8}圆角|棱边.{0,8}圆角|边缘.{0,8}圆角|R\s*\d+.{0,8}圆角/i.test(t); + + // 复合请求按依赖顺序拆解:零件 → 装配 → 工程图。 + // 不能让“工程图”分支提前吞掉前面的装配和建模意图。 + const wantsDrawing = /工程图|三视图|出图|图纸/.test(t); + const wantsAssembly = /装配体|装配/.test(t); + if (wantsDrawing && wantsAssembly) { + const drawingStart = t.search(/(?:最后|然后)[^。]*(?:工程图|三视图|图纸|出图)/); + const modelText = (drawingStart >= 0 ? t.slice(0, drawingStart) : t.replace(/[^。]*(?:工程图|三视图|图纸|出图)[^。]*$/g, '')).trim(); + const modelOps = modelText ? parseNaturalLanguage(modelText) : []; + const format = /A3/i.test(t) ? 'A3' : 'A4'; + const orientation = /竖|纵向|portrait/i.test(t) ? 'portrait' : 'landscape'; + const drawingOps = [{ op: 'drawing_generate', format, orientation, scale: 1, name: '自动工程图' }]; + if (/标注|尺寸|公差/.test(t)) drawingOps.push({ op: 'drawing_dimension', value: dims ? parseFloat(dims[1]) : 0, text: dims ? `${dims[1]} mm` : '尺寸' }); + if (/剖视|剖面/.test(t)) drawingOps.push({ op: 'drawing_section', name: 'A-A 剖视图' }); + return [...modelOps, ...drawingOps]; + } + + if (wantsDrawing) { + const format = /A3/i.test(t) ? 'A3' : 'A4'; + const orientation = /竖|纵向|portrait/i.test(t) ? 'portrait' : 'landscape'; + return [{ op: 'drawing_generate', format, orientation, scale: 1, name: '自动工程图' }]; + } + if (/装配体|装配/.test(t) && /新建|创建|建立|装配/.test(t)) { + // 装配描述常常同时包含“先做一个底座”。不能在这里提前返回, + // 否则 assembly_add_component 会在实体尚未重建时触发“没有当前零件实体”。 + // 先生成可用的底座,再由执行器在插入组件前确保 bodyMesh 已更新。 + const seed = []; + if (dims || hasPlate) { + const baseW = dims ? parseFloat(dims[1]) : 120; + const baseH = dims ? parseFloat(dims[2]) : 80; + const baseD = dims ? parseFloat(dims[3]) : 12; + seed.push( + { op: 'sketch', plane: 'front', name: '装配底座草图' }, + { op: 'rect', cx: 0, cy: 0, w: baseW, h: baseH }, + { op: 'extrude', mode: 'blind', depth: baseD, name: '装配底座' } + ); + const diaValues = [...t.matchAll(/直径\s*[::]?\s*(\d+(?:\.\d+)?)/g)].map(m => parseFloat(m[1])); + if (/凸台|凸起/.test(t)) { + const bossDia = diaValues[0] || 50; + const bossHeight = height || 20; + seed.push({ op: 'cylinder', r: bossDia / 2, h: bossHeight, tz: baseD - 1 + bossHeight / 2, name: '装配中心凸台' }); + } + if (/中心.*孔|孔.*中心|通孔/.test(t) && diaValues.length > 1) { + seed.push( + { op: 'sketch', plane: 'front', name: '装配中心孔草图' }, + { op: 'circle', cx: 0, cy: 0, r: diaValues[diaValues.length - 1] / 2 }, + { op: 'cut', mode: 'throughAll', name: '装配中心通孔' } + ); + } + } + const assemblyOps = [ + { op: 'assembly_create', name: '课程装配体' }, + { op: 'assembly_add_component', id: 'current', name: '当前零件', source: 'current', fixed: true } + ]; + if (/干涉|碰撞/.test(t)) assemblyOps.push({ op: 'interference_check' }); + if (/爆炸视图|爆炸/.test(t)) assemblyOps.push({ op: 'explode', progress: 1 }); + return [...seed, ...assemblyOps]; + } + + // ---- M10 轴承座:底板、两种实体边处理、凸台、通孔、参考面和双筋 ---- + if (/轴承座|轴承支座|bearing\s*housing/i.test(t)) { + const baseW = dims ? parseFloat(dims[1]) : 120; + const baseH = dims ? parseFloat(dims[2]) : 80; + const baseD = dims ? parseFloat(dims[3]) : 12; + const bossDia = matchD(t, '(?:主体凸台|凸台|轴承台).{0,12}?直径\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? 60; + const bossHeight = matchD(t, '(?:主体凸台|凸台|轴承台).{0,12}?(?:高度|高)\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? 32; + const boreDia = matchD(t, '(?:中心通孔|通孔|轴承孔).{0,12}?直径\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? 30; + const ribThickness = matchD(t, '(?:筋|筋板|加强筋).{0,10}?(?:厚度|厚)\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? 4; + const edgeSize = matchD(t, '(?:R|r|圆角|倒角)\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? 4; + const bossCenterZ = baseD - 1 + bossHeight / 2; + return [ + { op: 'sketch', plane: 'front', offset: 0, name: '轴承座底板草图' }, + { op: 'rect', cx: 0, cy: 0, w: baseW, h: baseH }, + { op: 'extrude', mode: 'blind', depth: baseD, name: '底板' }, + { op: 'chamfer_edge', edgeRef: { type: 'edge-query', point: [0, -baseH / 2, 0], direction: [1, 0, 0] }, distance: edgeSize, name: '底板实体倒角' }, + { op: 'fillet_edge', edgeRef: { type: 'edge-query', point: [0, baseH / 2, 0], direction: [1, 0, 0] }, radius: edgeSize, segments: 8, name: '底板实体圆角' }, + { op: 'cylinder', r: bossDia / 2, h: bossHeight, tz: bossCenterZ, name: '主体凸台' }, + { op: 'reference_plane', kind: 'offset', source: 'front', distance: baseD / 2, name: '筋定位基准面' }, + { op: 'sketch', plane: 'front', planeRefId: 'last', name: '左筋路径' }, + { op: 'line', p1: [-baseW * 0.38, 0], p2: [-bossDia * 0.38, 0] }, + { op: 'rib', thickness: ribThickness, extent: bossHeight * 0.85, side: 'both', name: '左加强筋' }, + { op: 'sketch', plane: 'front', planeRefId: 'last', name: '右筋路径' }, + { op: 'line', p1: [bossDia * 0.38, 0], p2: [baseW * 0.38, 0] }, + { op: 'rib', thickness: ribThickness, extent: bossHeight * 0.85, side: 'both', name: '右加强筋' }, + { op: 'sketch', plane: 'front', offset: 0, name: '中心通孔草图' }, + { op: 'circle', cx: 0, cy: 0, r: boreDia / 2 }, + { op: 'cut', mode: 'toNext', name: '中心通孔' }, + ]; + } + + // ---- 凸块单棱实体倒角/圆角(先建主体,再用稳定几何查询选边) ---- + if (hasEdgeChamfer || hasEdgeFillet) { + const w = dims ? parseFloat(dims[1]) : 40; + const h = dims ? parseFloat(dims[2]) : 40; + const d = dims ? parseFloat(dims[3]) : 40; + const value = hasEdgeFillet + ? (matchD(t, '(?:R|r|半径)\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? 5) + : (matchD(t, '倒角\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? 5); + return [ + { op: 'sketch', plane: 'front', offset: 0, name: '实体边处理草图' }, + { op: 'rect', cx: 0, cy: 0, w, h }, + { op: 'extrude', mode: 'blind', depth: d, name: '基础凸块' }, + hasEdgeFillet + ? { op: 'fillet_edge', edgeRef: { type: 'edge-query', point: [0, -h / 2, 0], direction: [1, 0, 0] }, radius: value, segments: 8, name: '实体圆角' } + : { op: 'chamfer_edge', edgeRef: { type: 'edge-query', point: [0, -h / 2, 0], direction: [1, 0, 0] }, distance: value, name: '实体倒角' }, + ]; + } + + // ---- 矩形到圆形的基础放样 ---- + if (hasLoft) { + const rectMatch = t.match(/(\d+(?:\.\d+)?)\s*[×xX*]\s*(\d+(?:\.\d+)?)\s*的?\s*矩形/); + const rw = rectMatch ? parseFloat(rectMatch[1]) : 40; + const rh = rectMatch ? parseFloat(rectMatch[2]) : 30; + const circleDia = matchD(t, '(?:直径|圆径)\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? 20; + const loftHeight = matchD(t, '(?:高度|高|间距|距离)\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? 40; + const rectProfile = [[-rw / 2, -rh / 2, 0], [rw / 2, -rh / 2, 0], [rw / 2, rh / 2, 0], [-rw / 2, rh / 2, 0]]; + const circleProfile = []; + for (let i = 0; i < 24; i++) { + const a = 2 * Math.PI * i / 24; + circleProfile.push([circleDia / 2 * Math.cos(a), circleDia / 2 * Math.sin(a), loftHeight]); + } + return [{ op: 'loft', profiles: [rectProfile, circleProfile], resampleCount: 32, name: '矩形到圆形放样' }]; + } + + // ---- 单独创建偏移基准面 ---- + if (/基准面/.test(t) && /偏移|距离/.test(t) && !hasPlate && !hasHole) { + const distance = matchD(t, '(?:偏移|距离)\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? 10; + const source = /上视/.test(t) ? 'top' : (/右视/.test(t) ? 'right' : 'front'); + return [{ op: 'reference_plane', kind: 'offset', source, distance, name: '偏移基准面' }]; + } + + // ---- 圆形截面扫描:直线路径 / 四分之一圆弧路径 ---- + if (hasSweep) { + const sectionDia = + matchD(t, '圆形截面.{0,12}?直径\\s*(\\d+(?:\\.\\d+)?)') ?? + matchD(t, '直径\\s*(\\d+(?:\\.\\d+)?).{0,12}?圆形截面') ?? + matchD(t, '管径\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? diameter; + const sectionRadius = + matchD(t, '圆形截面.{0,12}?半径\\s*(\\d+(?:\\.\\d+)?)') ?? + matchD(t, '截面半径\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? + (sectionDia ? sectionDia / 2 : 5); + if (/四分之一圆弧|90度圆弧|90°圆弧/.test(t)) { + const pathRadius = + matchD(t, '路径半径\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? + matchD(t, '半径\\s*(\\d+(?:\\.\\d+)?).{0,8}?(?:四分之一|90)') ?? 50; + const path = []; + const samples = 16; + for (let i = 0; i <= samples; i++) { + const a = Math.PI * 0.5 * i / samples; + path.push([pathRadius * Math.cos(a), pathRadius * Math.sin(a), 0]); + } + return [{ op: 'sweep', path, radius: sectionRadius, segments: 32, name: '圆弧扫描' }]; + } + const length = matchD(t, '长度?\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? 100; + return [{ op: 'sweep', path: [[0, 0, 0], [length, 0, 0]], radius: sectionRadius, segments: 32, name: '直线扫描' }]; + } + + // ---- 椭圆 / 长圆槽基础参数化草图 ---- + if (hasEllipse || hasSlot) { + const shapeDims = t.match(/(\d+(?:\.\d+)?)\s*[×xX*]\s*(\d+(?:\.\d+)?)/); + const a = shapeDims ? parseFloat(shapeDims[1]) : 60; + const b = shapeDims ? parseFloat(shapeDims[2]) : 30; + const h2 = height || (dims ? parseFloat(dims[3]) : null); + ops.push({ op: 'sketch', plane: 'front', name: hasEllipse ? '椭圆草图' : '长圆槽草图' }); + if (hasEllipse) ops.push({ op: 'ellipse', cx: 0, cy: 0, rx: a / 2, ry: b / 2 }); + else ops.push({ op: 'slot', p1: [-Math.max(1, a / 2 - b / 2), 0], p2: [Math.max(1, a / 2 - b / 2), 0], width: b }); + if (h2 > 0) ops.push({ op: 'extrude', depth: h2, name: hasEllipse ? '椭圆实体' : '长圆槽实体' }); + return ops; + } + + // ---- 底板上的直筋/加强筋 ---- + if (hasRib) { + const baseW = dims ? parseFloat(dims[1]) : (matchD(t, '底板.{0,10}?(?:宽|长)\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? 100); + const baseH = dims ? parseFloat(dims[2]) : 60; + const baseDepth = dims ? parseFloat(dims[3]) : (matchD(t, '(?:底板厚|厚度|厚)\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? 10); + const ribThickness = matchD(t, '(?:筋板|加强筋|筋).{0,8}?(?:厚度|厚)\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? matchD(t, '厚\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)\\s*(?:mm|毫米)?\\s*(?:筋|筋板)') ?? 3; + const ribHeight = matchD(t, '(?:筋高|筋板高|加强筋高)\\s*[::]?\\s*(\\d+(?:\\.\\d+)?)') ?? 30; + return [ + { op: 'sketch', plane: 'front', offset: 0, name: '底板草图' }, + { op: 'rect', cx: 0, cy: 0, w: baseW, h: baseH }, + { op: 'extrude', mode: 'blind', depth: baseDepth, name: '底板' }, + { op: 'sketch', plane: 'front', offset: baseDepth / 2, name: '筋路径' }, + { op: 'line', p1: [-baseW * 0.35, 0], p2: [baseW * 0.35, 0] }, + { op: 'rib', thickness: ribThickness, extent: ribHeight, side: 'both', name: '加强筋' }, + ]; + } // 提取所有"直径N"出现的位置,用于区分盘体直径和孔直径 const diaMatches = [...t.matchAll(/直径\s*(\d+(?:\.\d+)?)/g)].map(m => parseFloat(m[1])); @@ -305,6 +538,371 @@ function matchD(text, re) { return m ? parseFloat(m[1]) : null; } +// ============ 1.5 自然语言意图与文档 CRUD ============ +const FEATURE_TYPE_ALIASES = { + rib: ['筋', '加强筋', '肋板', '筋板'], + chamfer: ['倒角'], + fillet: ['圆角', '过渡圆'], + extrude: ['拉伸', '底板', '主体'], + cut: ['切除', '孔', '通孔', '槽'], + cylinder: ['凸台', '圆柱', '圆盘'], + cylcut: ['孔', '通孔'], + revolve: ['旋转'], + sweep: ['扫描', '扫掠'], + loft: ['放样'], +}; + +function compactText(s) { return normalize(String(s || '')).replace(/\s+/g, ''); } + +export function classifyIntent(text, doc = null) { + const t = compactText(text); + if (!t) return 'unknown'; + if (/^(查询|查看|显示|列出|告诉我|汇报|检查|你能看到|看到了什么|描述模型|观察|有哪些|多少|什么|当前状态|模型状态)|(?:查询|查看|显示|描述|观察).*(?:尺寸|参数|特征|草图|状态|信息|模型|实体)/.test(t)) return 'query'; + if (/^(删除|移除|去掉|取消|撤销).*(?:特征|草图|筋|倒角|圆角|孔|凸台)?$/.test(t) || /(?:删除|移除|去掉|取消).*(?:特征|草图|筋|倒角|圆角|孔|凸台)/.test(t)) return 'delete'; + if (/^(修改|更改|调整|把|将|设置|设定|重命名|改名)/.test(t) || /(?:改为|改成|设置为|调整为|重命名为|改名为)/.test(t)) return 'update'; + if (/^(做|生成|创建|新建)\s*(?:一个|一件|一只|个)?/.test(t)) return 'create'; + if (/^(在|向|给|为).*(?:添加|增加|加上|开一个|打一个|建立)/.test(t) || /(?:添加|增加|加上|开一个|打一个).*(?:孔|筋|凸台|倒角|圆角|特征)/.test(t)) return 'add'; + return doc?.features?.length ? 'add' : 'create'; +} + +function featureTypeLabel(type) { + return ({ extrude: '拉伸', cut: '切除', revolve: '旋转', cylinder: '圆柱/凸台', cylcut: '圆柱切除', rib: '加强筋', chamfer: '实体倒角', fillet: '实体圆角', sweep: '扫描', loft: '放样' })[type] || type; +} + +export function resolveFeatureTarget(doc, text) { + if (!doc?.features?.length) return null; + const t = compactText(text); + if (/最后|最近|上一个|末尾/.test(t)) return doc.features.at(-1); + const ranked = doc.features.map((f, index) => { + const name = compactText(f.name); + let score = name && t.includes(name) ? 100 + index : 0; + for (const alias of FEATURE_TYPE_ALIASES[f.type] || []) if (t.includes(compactText(alias))) score = Math.max(score, 20 + index); + return { f, score }; + }).filter(x => x.score > 0).sort((a, b) => b.score - a.score); + return ranked[0]?.f || null; +} + +function numberAfter(text, labels) { + const label = labels.join('|'); + const m = text.match(new RegExp(`(?:${label})\\s*(?:为|改为|改成|设为|设置为|调整为|到|[::=])?\\s*(-?\\d+(?:\\.\\d+)?)`)); + return m ? Number(m[1]) : null; +} + +function inferFeaturePatch(feature, text) { + const t = compactText(text); + const patch = {}; + if (feature.type === 'rib') { + const thickness = numberAfter(t, ['厚度', '厚', '筋宽']); + const extent = numberAfter(t, ['筋高', '高度', '延伸范围', '延伸']); + if (thickness != null) patch.thickness = thickness; + if (extent != null) patch.extent = extent; + if (/左侧/.test(t)) patch.side = 'left'; + if (/右侧/.test(t)) patch.side = 'right'; + } else if (feature.type === 'chamfer') { + const distance = numberAfter(t, ['倒角距离', '倒角', '距离']); + if (distance != null) patch.distance = distance; + } else if (feature.type === 'fillet') { + const radius = numberAfter(t, ['圆角半径', '半径', '圆角', 'R', 'r']); + if (radius != null) patch.radius = radius; + } else if (feature.type === 'extrude' || feature.type === 'cut') { + const depth = numberAfter(t, ['深度', '高度', '距离']); + if (depth != null) patch.depth = depth; + } else if (feature.type === 'cylinder' || feature.type === 'cylcut') { + const diameter = numberAfter(t, ['直径']); + const radius = numberAfter(t, ['半径']); + const height = numberAfter(t, ['高度', '高', '深度']); + if (radius != null) patch.r = radius; + else if (diameter != null) patch.r = diameter / 2; + if (height != null) patch.h = height; + } + const nameMatch = t.match(/(?:重命名为|改名为|命名为)([^,。,;;]+)/); + if (nameMatch && nameMatch[1]) patch.__name = nameMatch[1].trim(); + return patch; +} + +function buildAddOps(text, doc) { + const t = compactText(text); + const bb = doc?.bbox; + if (bb && /筋|肋板/.test(t)) { + const w = bb.max[0] - bb.min[0], h = bb.max[1] - bb.min[1]; + const thickness = numberAfter(t, ['厚度', '厚', '筋宽']) ?? 3; + const extent = numberAfter(t, ['筋高', '高度', '延伸范围', '延伸']) ?? Math.max(10, bb.max[2] - bb.min[2]); + const alongY = /宽度方向|纵向|沿Y|沿y/.test(t); + const path = alongY ? [[0, -h * 0.35], [0, h * 0.35]] : [[-w * 0.35, 0], [w * 0.35, 0]]; + return [ + { op: 'sketch', plane: 'front', offset: (bb.min[2] + bb.max[2]) / 2, name: '新增筋路径' }, + { op: 'line', p1: path[0], p2: path[1] }, + { op: 'rib', thickness, extent, side: /左侧/.test(t) ? 'left' : (/右侧/.test(t) ? 'right' : 'both'), name: '新增加强筋' }, + ]; + } + if (bb && /(?:孔|通孔|打孔|钻孔)/.test(t)) { + const diameter = numberAfter(t, ['直径']) ?? ((numberAfter(t, ['半径']) ?? 5) * 2); + return [ + { op: 'sketch', plane: 'front', offset: (bb.min[2] + bb.max[2]) / 2, name: '新增孔草图' }, + { op: 'circle', cx: 0, cy: 0, r: diameter / 2 }, + { op: 'cut', mode: 'throughAll', throughAll: true, name: '新增通孔' }, + ]; + } + if (bb && /凸台|圆柱/.test(t)) { + const diameter = numberAfter(t, ['直径']) ?? 30; + const height = numberAfter(t, ['高度', '高']) ?? 10; + return [{ op: 'cylinder', r: diameter / 2, h: height, tz: bb.max[2] + height / 2, name: '新增凸台' }]; + } + return parseNaturalLanguage(text); +} + +export function documentQuery(doc, text) { + if (!doc) return '当前没有打开的模型文档。'; + const t = compactText(text); + if (/你能看到|看到了什么|描述模型|观察|检查一下|几何状态|实体状态/.test(t)) return describeInspection(inspectDocument(doc)); + const bb = doc.bbox; + const dimensions = bb ? `${(bb.max[0] - bb.min[0]).toFixed(2)}×${(bb.max[1] - bb.min[1]).toFixed(2)}×${(bb.max[2] - bb.min[2]).toFixed(2)} mm` : '暂无实体'; + const target = resolveFeatureTarget(doc, text); + if (target && /参数|厚度|高度|半径|直径|尺寸|详细/.test(t)) { + return `${target.name}(${featureTypeLabel(target.type)})参数:${JSON.stringify(target.params)}`; + } + if (/尺寸|大小|外形/.test(t)) return `当前实体尺寸:${dimensions}。草图 ${doc.sketches.length} 张,特征 ${doc.features.length} 个。`; + if (/参考|基准面/.test(t)) return `参考几何:${doc.references.map(r => r.name).join('、') || '无'}。`; + return `当前模型:${dimensions};草图 ${doc.sketches.length} 张;特征 ${doc.features.length} 个:${doc.features.map(f => `${f.name}(${featureTypeLabel(f.type)}${f.error ? ',错误' : ''})`).join('、') || '无'}。`; +} + +function meshStats(mesh) { + if (!mesh || !Array.isArray(mesh.verts) || !Array.isArray(mesh.tris)) return null; + const b = { min: [Infinity, Infinity, Infinity], max: [-Infinity, -Infinity, -Infinity] }; + for (let i = 0; i + 2 < mesh.verts.length; i += 3) { + for (let k = 0; k < 3; k++) { + const v = Number(mesh.verts[i + k]); + if (!Number.isFinite(v)) continue; + b.min[k] = Math.min(b.min[k], v); + b.max[k] = Math.max(b.max[k], v); + } + } + const valid = b.min.every(Number.isFinite) && b.max.every(Number.isFinite); + return { vertices: mesh.verts.length / 3, triangles: mesh.tris.length / 3, bbox: valid ? b : null }; +} + +export function inspectDocument(doc) { + if (!doc) return { hasBody: false, status: 'NoDocument', errors: ['没有打开文档'] }; + const body = doc.bodyManifold; + let status = body ? 'Unknown' : 'Empty'; + let volume = null, surfaceArea = null; + try { status = body?.status?.() || status; } catch { status = 'Invalid'; } + try { volume = body?.volume?.(); } catch { volume = null; } + try { surfaceArea = body?.surfaceArea?.(); } catch { surfaceArea = null; } + const bbox = doc.bbox ? { min: doc.bbox.min.slice(), max: doc.bbox.max.slice() } : null; + const dimensions = bbox ? bbox.max.map((v, i) => v - bbox.min[i]) : null; + const features = (doc.features || []).map(f => ({ + id: f.id, + name: f.name, + type: f.type, + params: f.params, + error: f.error || null, + delta: meshStats(doc.deltaMeshes?.[f.id]), + })); + const errors = features.filter(f => f.error).map(f => `${f.name}: ${f.error}`); + if (status !== 'NoError' && status !== 'Empty') errors.push(`实体状态:${status}`); + if (body && !(Number(volume) > 0)) errors.push('实体体积不是正数'); + return { + hasBody: !!body, + status, + bbox, + dimensions, + volume: Number.isFinite(Number(volume)) ? Number(volume) : null, + surfaceArea: Number.isFinite(Number(surfaceArea)) ? Number(surfaceArea) : null, + mesh: meshStats(doc.bodyMesh), + sketches: (doc.sketches || []).map(s => ({ id: s.id, name: s.name, plane: typeof s.plane === 'string' ? s.plane : 'reference', entities: s.entities?.length || 0 })), + features, + errors, + }; +} + +export function describeInspection(snapshot) { + if (!snapshot?.hasBody) return `当前没有可观察的实体(状态:${snapshot?.status || 'Empty'})。${snapshot?.errors?.join(';') || ''}`; + const d = snapshot.dimensions?.map(v => Number(v).toFixed(2)).join('×') || '未知'; + const volume = snapshot.volume == null ? '未知' : Number(snapshot.volume).toFixed(2); + const mesh = snapshot.mesh ? `,网格 ${snapshot.mesh.vertices} 个顶点/${snapshot.mesh.triangles} 个三角形` : ''; + const featureText = snapshot.features.map(f => `${f.name}(${featureTypeLabel(f.type)}${f.error ? ',失败' : ''})`).join('、') || '无'; + const errorText = snapshot.errors.length ? ` 注意:${snapshot.errors.join(';')}` : ''; + return `我观察到一个状态为 ${snapshot.status} 的实体,尺寸约 ${d} mm,体积 ${volume} mm³${mesh};特征为:${featureText}。${errorText}`; +} + +/** + * 将本地几何观察结果整理成“视觉模块反馈”。这里不传图片, + * 而是把模型位置、尺寸、网格、特征和实体完整性反馈给机械制图专家。 + */ +export function buildVisualFeedback(snapshot) { + const source = snapshot || {}; + return { + observer: 'MiniSW local geometry observer', + hasBody: !!source.hasBody, + status: source.status || 'Empty', + placement: { + bbox: source.bbox || null, + dimensions: source.dimensions || null, + }, + massProperties: { + volume: source.volume ?? null, + surfaceArea: source.surfaceArea ?? null, + }, + mesh: source.mesh || null, + sketches: source.sketches || [], + featureTree: (source.features || []).map(feature => ({ + id: feature.id, + name: feature.name, + type: feature.type, + params: feature.params || {}, + error: feature.error || null, + delta: feature.delta || null, + })), + integrity: { + errors: source.errors || [], + readableSummary: describeInspection(source), + }, + }; +} + +/** + * 建模完成后的单次专家复核。复核模型不需要多模态输入, + * 只依据本地视觉/几何观察器生成的结构化反馈判断是否符合意图。 + */ +export async function reviewGeneratedModel(userText, cfg, context = {}, ops = [], snapshot = {}) { + const started = Date.now(); + context.onStage?.('模型已生成,正在让机械制图专家进行视觉复核…'); + const feedback = buildVisualFeedback(snapshot); + const messages = [ + { role: 'system', content: await readProjectSourceContext() }, + ...(context.modelState ? [{ role: 'user', content: context.modelState }] : []), + { role: 'user', content: JSON.stringify({ originalRequest: userText, generatedOps: ops, visualFeedback: feedback }) }, + ]; + let content; + try { + content = await callLLMAPI(cfg, messages); + context.onTrace?.({ stage: 'vision_review', ok: true, durationMs: Date.now() - started, request: messages, response: content }); + const result = extractJSONObject(content); + return { + pass: result.pass !== false, + severity: result.severity || (result.pass === false ? 'error' : 'info'), + summary: String(result.summary || (result.pass === false ? '专家发现需要修正的问题' : '专家复核通过')), + findings: Array.isArray(result.findings) ? result.findings.map(String) : [], + correction: String(result.correction || ''), + feedback, + calls: 1, + durationMs: Date.now() - started, + trace: [{ stage: 'vision_review', ok: true, durationMs: Date.now() - started }], + }; + } catch (error) { + // 视觉复核失败不覆盖已经成功生成的实体,只将失败显示给用户。 + context.onTrace?.({ stage: 'vision_review', ok: false, durationMs: Date.now() - started, request: messages, error: error.message || String(error) }); + return { + pass: true, + severity: 'warning', + summary: '视觉复核未能解析专家结果,模型本体仍已生成', + findings: [error.message || String(error)], + correction: '', + feedback, + reviewError: true, + calls: 1, + durationMs: Date.now() - started, + trace: [{ stage: 'vision_review', ok: false, durationMs: Date.now() - started, error: error.message || String(error) }], + }; + } +} + +export function interpretAgentRequest(text, doc = null) { + const intent = classifyIntent(text, doc); + if (intent === 'query') return { kind: 'query', intent, reply: documentQuery(doc, text) }; + if (intent === 'delete') { + const target = resolveFeatureTarget(doc, text); + return target ? { kind: 'delete', intent, featureId: target.id, featureName: target.name } : { kind: 'error', intent, message: '没有找到要删除的特征。请指定名称,例如“删除左加强筋”或“删除最后一个特征”。' }; + } + if (intent === 'update') { + const target = resolveFeatureTarget(doc, text); + if (!target) return { kind: 'error', intent, message: '没有找到要修改的特征。请指定特征名称或类型。' }; + const patch = inferFeaturePatch(target, text); + if (!Object.keys(patch).length) return { kind: 'error', intent, message: `已找到“${target.name}”,但没有识别到新的参数。可以说“把${target.name}厚度改为6mm”。` }; + return { kind: 'update', intent, featureId: target.id, featureName: target.name, patch }; + } + const ops = intent === 'add' && doc?.features?.length ? buildAddOps(text, doc) : parseNaturalLanguage(text); + if (ops) return { kind: 'ops', intent, ops, replaceExisting: intent === 'create' }; + return { kind: 'unknown', intent, message: '我暂时无法确定这是新建、追加、修改、删除还是查询。请补充对象和参数。' }; +} + +function hasConcreteEngineeringInput(text) { + const value = compactText(text); + if (!/(?:\d+(?:\.\d+)?|直径|半径|宽度|长度|厚度|高度|深度|距离|模数|齿数|齿宽|压力角|孔径|外径|内径)/.test(value)) return false; + // 这些词意味着对象或约束仍然需要模型判断,本地解析不能擅自替用户定方案。 + if (/随便|大概|差不多|适当|合理|默认|常见|类似|像|之类|先做个|看着办|椅子|桌子|凳子|支架|托架|齿轮|一个圆柱|一个底座/.test(value)) return false; + return true; +} + +/** + * 统一 Agent 的本地闸门:只有“动作、对象、参数、依赖”都能由本地规则完整确认时才返回, + * 其余一律返回 null,由统一 LLM Agent 在一次紧凑调用中理解和补全。 + */ +export function decideLocalRequest(text, doc = null) { + const request = interpretAgentRequest(text, doc); + if (request.kind === 'query') return request; + if (request.kind === 'delete') { + const value = compactText(text); + return /最后|最近|上一个|末尾|选中|当前特征|(?:删除|移除|去掉).+(?:特征|草图|筋|倒角|圆角|孔|凸台)/.test(value) ? request : null; + } + if (request.kind === 'update') return request; + if (request.kind !== 'ops' || !request.ops?.length) return null; + if (!hasConcreteEngineeringInput(text)) return null; + const schemaError = validateOps(request.ops); + return schemaError ? null : request; +} + +export function applyAgentMutation(doc, request) { + if (!doc || !request) throw new Error('缺少文档或智能体操作请求'); + if (request.kind === 'delete') { + const f = doc.featureById(request.featureId); + if (!f) throw new Error('目标特征已不存在'); + const sketchId = f.sketchId; + doc.deleteFeature(f.id); + if (sketchId) doc.deleteSketch(sketchId); + return `已删除特征“${f.name}”。`; + } + if (request.kind === 'update') { + const f = doc.featureById(request.featureId); + if (!f) throw new Error('目标特征已不存在'); + const patch = { ...request.patch }; + if (patch.__name) { f.name = patch.__name; delete patch.__name; } + doc.updateFeature(f.id, patch); + return `已修改特征“${f.name}”:${JSON.stringify(patch)}。`; + } + throw new Error('不是可变更的智能体请求'); +} + +export function repairOps(ops, failures = []) { + const failedNames = new Set(failures.map(f => compactText(f.name)).filter(Boolean)); + const failedTypes = new Set(failures.map(f => f.type).filter(Boolean)); + const removed = []; + const next = ops.filter(op => { + const name = compactText(op.name); + const hit = (name && failedNames.has(name)) || (!name && failedTypes.has(op.op)); + if (hit) removed.push(op.name || op.op); + return !hit; + }); + return { ops: next, removed }; +} + +// 复合请求的安全执行顺序:先产生实体,再引用实体做装配,最后生成工程图。 +// 保持同一阶段内的原始顺序,避免 LLM 输出顺序偶发颠倒导致前置对象缺失。 +function orderOpsForDependencies(ops) { + const rank = op => { + const name = String(op?.op || ''); + if (name === 'assembly_create') return 20; + if (name.startsWith('assembly_') || ['mate', 'interference_check', 'explode'].includes(name)) return 30; + if (name === 'drawing_generate') return 40; + if (name.startsWith('drawing_')) return 50; + return 10; + }; + return ops.map((op, index) => ({ op, index, rank: rank(op) })) + .sort((a, b) => a.rank - b.rank || a.index - b.index) + .map(item => item.op); +} + // ============ 2. 宏展开引擎 ============ /** * 将高级 op 展开为原语 op 序列。 @@ -335,6 +933,12 @@ export function expandOps(ops) { case 'pattern_circular': out.push(...expandPatternCircular(op, out)); break; + case 'spur_gear': + out.push(...expandSpurGear(op)); + break; + case 'bevel_gear': + out.push(...expandBevelGear(op)); + break; default: out.push(op); } @@ -342,6 +946,98 @@ export function expandOps(ops) { return out; } +// ---- 参数化渐开线直齿轮 → 齿根圆柱 + 单齿轮廓 + 圆周阵列 + 中心孔 ---- +function expandSpurGear(op) { + const moduleValue = Number(op.module); + const teeth = Math.round(Number(op.teeth)); + const width = Number(op.width); + const pressureAngle = Number(op.pressureAngle ?? 20); + const pitchRadius = moduleValue * teeth / 2; + const addendumRadius = pitchRadius + moduleValue; + const rootRadius = Math.max(moduleValue, pitchRadius - 1.25 * moduleValue); + const baseRadius = pitchRadius * Math.cos(pressureAngle * Math.PI / 180); + const boreDiameter = Number(op.boreDiameter ?? Math.max(2 * moduleValue, pitchRadius * 0.5)); + const boreRadius = Math.max(0, boreDiameter / 2); + const alpha = pressureAngle * Math.PI / 180; + const involute = angle => Math.tan(angle) - angle; + const halfPitchToothAngle = Math.PI / (2 * teeth); + const flankStartRadius = Math.max(baseRadius, rootRadius); + const flankAngleAt = radius => { + const ratio = Math.min(1, baseRadius / Math.max(baseRadius, radius)); + const localAlpha = Math.acos(ratio); + return Math.max(0.002, halfPitchToothAngle + involute(alpha) - involute(localAlpha)); + }; + const polar = (radius, angle) => [radius * Math.cos(angle), radius * Math.sin(angle)]; + const flankSamples = 7; + const tipSamples = 5; + const rootJoinRadius = Math.min(rootRadius - moduleValue * 0.05, Math.max(boreRadius + moduleValue * 0.2, rootRadius - moduleValue * 0.25)); + const startAngle = flankAngleAt(flankStartRadius); + const points = [polar(rootJoinRadius, -startAngle)]; + if (flankStartRadius > rootJoinRadius + 1e-6) points.push(polar(flankStartRadius, -startAngle)); + for (let i = 1; i <= flankSamples; i++) { + const radius = flankStartRadius + (addendumRadius - flankStartRadius) * i / flankSamples; + points.push(polar(radius, -flankAngleAt(radius))); + } + const tipAngle = flankAngleAt(addendumRadius); + for (let i = 1; i <= tipSamples; i++) { + points.push(polar(addendumRadius, -tipAngle + 2 * tipAngle * i / tipSamples)); + } + for (let i = flankSamples - 1; i >= 0; i--) { + const radius = flankStartRadius + (addendumRadius - flankStartRadius) * i / flankSamples; + points.push(polar(radius, flankAngleAt(radius))); + } + points.push(polar(rootJoinRadius, startAngle)); + + const name = op.name || '直齿轮'; + const result = [ + { op: 'cylinder', r: rootRadius, h: width, tz: width / 2, name: `${name}-齿根圆齿坯` }, + { op: 'sketch', plane: 'front', name: `${name}-渐开线单齿草图` }, + ...ptsToLines(points, `${name}-渐开线单齿草图`), + { op: 'extrude', depth: width, name: `${name}-渐开线单齿` }, + { op: 'pattern_circular', count: teeth, cx: 0, cy: 0 }, + ]; + if (boreRadius > 0) result.push({ op: 'cylcut', r: boreRadius, h: width + 2, through: true, tz: width / 2, name: `${name}-中心孔` }); + return expandOps(result); +} + +// ---- 鍥寸齿轮 → 两个渐缩齿形截面放样 + 中心孔 ---- +// 这是 CAD 预览级近似:保留锥形外观和齿数/模数关系,避免把锥齿轮错误降级为直齿轮。 +function expandBevelGear(op) { + const moduleValue = Number(op.module); + const teeth = Math.round(Number(op.teeth)); + const width = Number(op.width); + const pressureAngle = Number(op.pressureAngle ?? 20); + const pitchRadius = moduleValue * teeth / 2; + const addendumRadius = pitchRadius + moduleValue; + const rootRadius = Math.max(moduleValue, pitchRadius - 1.25 * moduleValue); + const boreDiameter = Number(op.boreDiameter ?? Math.max(2 * moduleValue, pitchRadius * 0.5)); + const boreRadius = Math.max(0, boreDiameter / 2); + const shaftAngle = Number(op.shaftAngle ?? 90); + const pitchConeAngle = Number(op.pitchConeAngle ?? shaftAngle / 2); + const coneDistance = Number(op.outerConeDistance ?? pitchRadius / Math.max(Math.sin(pitchConeAngle * Math.PI / 180), 1e-6)); + const smallScale = Math.max(0.35, Math.min(0.92, 1 - width / Math.max(coneDistance, width + 1))); + const step = 2 * Math.PI / teeth; + const offsets = [-0.5, -0.34, -0.2, 0.2, 0.34]; + const radii = [rootRadius, rootRadius, addendumRadius, addendumRadius, rootRadius]; + const outline = []; + for (let tooth = 0; tooth < teeth; tooth++) { + for (let i = 0; i < offsets.length; i++) { + const angle = tooth * step + offsets[i] * step; + outline.push([radii[i] * Math.cos(angle), radii[i] * Math.sin(angle)]); + } + } + const profile = (z, scale) => outline.map(([x, y]) => [x * scale, y * scale, z]); + const name = op.name || '\u9525\u9f7f\u8f6e'; + const result = [{ + op: 'loft', + profiles: [profile(0, 1), profile(width, smallScale)], + resampleCount: Math.max(32, teeth * offsets.length), + name: `${name}-\u9525\u9f7f\u8f6e\u9f7f\u4f53`, + }]; + if (boreRadius > 0) result.push({ op: 'cylcut', r: boreRadius, h: width + 2, through: true, tz: width / 2, name: `${name}-\u4e2d\u5fc3\u5b54` }); + return result; +} + // ---- 倒角矩形 → 8 段直线(八边形) ---- function expandChamferRect(op) { const cx = op.cx || 0, cy = op.cy || 0; @@ -573,91 +1269,740 @@ function duplicateGroupRotated(group, cx, cy, ang, suffix) { } // ============ 3. LLM 智能体 ============ +const LLM_DEFAULTS = { baseURL: '', apiKey: '', model: '' }; +const PROJECT_SOURCE_FILES = [ + './agent.js', './doc.js', './geometry.js', './sketch.js', './math.js', + './features/assembly.js', './features/drawing.js', './features/termination.js', +]; +let projectSourceContextPromise = null; + +function removeLegacyPromptText(source) { + return String(source).replace(/export const DSL_SPEC\s*=\s*`[\s\S]*?`;/, ''); +} + +async function readProjectSourceContext() { + if (typeof window === 'undefined' || typeof fetch !== 'function') { + return `MiniSW 本地项目文件:${PROJECT_SOURCE_FILES.join(', ')}。运行时由浏览器直接读取这些文件。`; + } + if (!projectSourceContextPromise) { + projectSourceContextPromise = Promise.all(PROJECT_SOURCE_FILES.map(async file => { + try { + const response = await fetch(new URL(file, import.meta.url)); + if (!response.ok) return `[${file}] 读取失败:HTTP ${response.status}`; + return `\n===== ${file} =====\n${removeLegacyPromptText(await response.text())}`; + } catch (error) { + return `[${file}] 读取失败:${error.message || String(error)}`; + } + })).then(parts => `MiniSW 本地项目文件源码:\n${parts.join('\n')}`); + } + return projectSourceContextPromise; +} + +const AGENT_PROJECT_FILES = [ + 'index.html', 'js/main.js', 'js/agent.js', 'js/doc.js', 'js/geometry.js', 'js/sketch.js', 'js/math.js', + 'js/features/assembly.js', 'js/features/drawing.js', 'js/features/termination.js', +]; + +export const AGENT_TOOL_DEFINITIONS = [ + { + type: 'function', + function: { + name: 'list_project_files', + description: '列出 MiniSW 当前项目中可读取的源码文件。', + parameters: { type: 'object', properties: {}, additionalProperties: false }, + }, + }, + { + type: 'function', + function: { + name: 'read_project_file', + description: '读取 MiniSW 项目目录内的源码文件。需要了解能力、参数或执行方式时自行调用。', + parameters: { + type: 'object', + properties: { path: { type: 'string', description: '相对于 MiniSW 项目目录的文件路径,例如 js/agent.js 或 js/doc.js' } }, + required: ['path'], + additionalProperties: false, + }, + }, + }, + { + type: 'function', + function: { + name: 'inspect_model', + description: '读取当前文档、特征树、尺寸、引用、装配和工程图状态。', + parameters: { type: 'object', properties: {}, additionalProperties: false }, + }, + }, + { + type: 'function', + function: { + name: 'execute_model_ops', + description: '在当前 MiniSW 文档中执行建模操作序列;返回真实执行结果或几何错误。', + parameters: { + type: 'object', + properties: { ops: { type: 'array', items: { type: 'object' }, description: '按依赖顺序排列的 MiniSW 操作对象数组' } }, + required: ['ops'], + additionalProperties: false, + }, + }, + }, + { + type: 'function', + function: { + name: 'reset_model', + description: '清空当前 MiniSW 文档并开始一个新的零件文档。', + parameters: { type: 'object', properties: {}, additionalProperties: false }, + }, + }, +]; + +export async function readProjectFile(path) { + const raw = String(path || '').replaceAll('\\', '/').replace(/^\.\//, ''); + if (!raw || raw.includes('..') || raw.startsWith('/') || /^[a-z]+:/i.test(raw)) throw new Error('只能读取 MiniSW 项目目录内的相对路径'); + if (typeof fetch !== 'function') throw new Error('当前运行环境没有本地文件读取接口'); + const response = await fetch(new URL(`../${raw}`, import.meta.url)); + if (!response.ok) throw new Error(`读取 ${raw} 失败:HTTP ${response.status}`); + return { path: raw, content: await response.text() }; +} + +export function listProjectFiles() { + return { root: 'MiniSW', files: AGENT_PROJECT_FILES.slice() }; +} + +function firstFinite(...values) { + for (const value of values) { + if (value === null || value === undefined || value === '') continue; + const number = Number(value); + if (Number.isFinite(number)) return number; + } + return null; +} + +function buildDefaultDesignGuidance(userText, normalizedIntent) { + const summary = `${userText}\n${JSON.stringify(normalizedIntent || {})}`; + if (/椅子|座椅|办公椅|餐椅|chair/i.test(summary)) return '这是一个模糊的椅子创建请求。请采用行业常用默认参数继续建模:座面500×500×450mm,座面厚度40mm,靠背宽500mm、高450mm、厚30mm,四条40×40mm椅腿;这些是 mm 制的真实工程尺寸,不要为了适配视口擅自缩放,也不要向用户追问。模型显示大小由软件视口自动适配。'; + if (/凳子|stool/i.test(summary)) return '这是一个模糊的凳子创建请求。请采用默认参数继续建模:座面400×400×450mm,厚度40mm,四条30×30mm椅腿;不要向用户追问。'; + if (/桌子|工作台|table|desk/i.test(summary)) return '这是一个模糊的桌子创建请求。请采用默认参数继续建模:桌面1200×600×30mm,高750mm,四条40×40mm桌腿;不要向用户追问。'; + if (Array.isArray(normalizedIntent?.ambiguities) && normalizedIntent.ambiguities.length) return '需求存在未指定项,但这是创建请求。请根据常见机械设计习惯补充合理默认值并继续建模,把假设写入名称或参数;不要因为缺少非关键尺寸而追问。'; + return ''; +} + +function compileKnownMechanicalIntent(userText, normalizedIntent, expertBrief) { + if (!normalizedIntent || !['create', 'add'].includes(String(normalizedIntent.intent || 'create'))) return null; + const summary = `${userText}\n${JSON.stringify(normalizedIntent)}\n${JSON.stringify(expertBrief)}`; + if (!/锥齿轮|直齿轮|直齿圆柱齿轮|bevel\s*gear|spur\s*gear/i.test(summary)) return null; + const dimensions = { ...(expertBrief?.dimensions || {}), ...(normalizedIntent?.dimensions || {}) }; + if (/\u9525\u9f7f\u8f6e|bevel\s*gear/i.test(summary)) { + const moduleValue = firstFinite(dimensions.module, dimensions.modulus, 2); + const teeth = Math.round(firstFinite(dimensions.teeth, dimensions.toothCount, dimensions.z, 20)); + const width = firstFinite(dimensions.width, dimensions.faceWidth, dimensions.thickness, 8); + const pressureAngle = firstFinite(dimensions.pressureAngle, 20); + const pitchDiameter = moduleValue * teeth; + const boreDiameter = firstFinite(dimensions.boreDiameter, dimensions.holeDiameter, Math.max(2 * moduleValue, pitchDiameter * 0.25)); + return [{ + op: 'bevel_gear', module: moduleValue, teeth, width, pressureAngle, + shaftAngle: firstFinite(dimensions.shaftAngle, dimensions.shaftAngleDeg, 90), + pitchConeAngle: firstFinite(dimensions.pitchConeAngle, dimensions.pitchConeAngleDeg, 45), + boreDiameter, name: normalizedIntent?.part?.name || '\u9525\u9f7f\u8f6e', + }]; + } + const moduleValue = firstFinite(dimensions.module, dimensions.modulus, 2); + const teeth = Math.round(firstFinite(dimensions.teeth, dimensions.toothCount, dimensions.z, 20)); + const width = firstFinite(dimensions.width, dimensions.faceWidth, dimensions.thickness, 20); + const pressureAngle = firstFinite(dimensions.pressureAngle, 20); + const pitchDiameter = moduleValue * teeth; + const boreDiameter = firstFinite(dimensions.boreDiameter, dimensions.holeDiameter, Math.max(2 * moduleValue, pitchDiameter * 0.25)); + return [{ op: 'spur_gear', module: moduleValue, teeth, width, pressureAngle, boreDiameter, name: normalizedIntent?.part?.name || '直齿轮' }]; +} + +// ============ 3.1 快速决策层 ============ +// 对象名很短、意图很明确时,不再把请求交给多轮 LLM:先用本地规则给出 +// 可执行的最小方案,并把默认工程假设记录到 agentMeta,保证判断果决且可追溯。 +function parseModelState(context) { + if (!context?.modelState) return null; + try { + const state = JSON.parse(context.modelState); + return state && typeof state === 'object' ? state : null; + } catch { return null; } +} + +function localStatePlan(userText, context) { + const state = parseModelState(context); + if (!state) return null; + const intent = classifyIntent(userText, { features: state.features || [] }); + const features = Array.isArray(state.features) ? state.features : []; + const selectedId = state.selectedFeatureId ?? state.lastFeatureId ?? features.at(-1)?.id; + const target = resolveFeatureTarget({ features }, userText) + || features.find(feature => String(feature.id) === String(selectedId)) + || (selectedId != null ? { id: selectedId, name: '当前特征', type: '' } : null); + let ops = null; + let answer = ''; + if (intent === 'delete' && target) { + ops = [{ op: 'delete_feature', featureId: target.id }]; + } else if (intent === 'update' && target) { + const patch = inferFeaturePatch(target, userText); + if (Object.keys(patch).length) ops = [{ op: 'update_feature', featureId: target.id, patch }]; + } else if (intent === 'query') { + const dimensions = Array.isArray(state.dimensions) ? state.dimensions.map(value => Number(value).toFixed(2)).join('×') : '未知'; + const featureText = features.map(feature => `${feature.name || '未命名特征'}(${featureTypeLabel(feature.type)})`).join('、') || '无'; + answer = `当前模型状态为 ${state.status || '未知'},尺寸约 ${dimensions} mm;特征为:${featureText}。`; + ops = [{ op: 'query_document', answer }]; + } + if (!ops) return null; + const requestType = intent === 'update' ? 'modify' : intent; + const meta = { + mode: 'local-decision', requestType, confidence: 'high', calls: 0, + trace: [{ stage: 'local_decision', ok: true, local: true, requestType }], + }; + context.onStage?.('已读取当前模型状态,直接执行确定性操作…'); + context.onTrace?.({ stage: 'local_decision', ok: true, local: true, request: userText, response: { ops } }); + return { ops, meta }; +} + +function inferSimpleObject(text) { + let value = compactText(text).replace(/[。.!!??]+$/, ''); + for (let i = 0; i < 3; i++) value = value.replace(/^(?:我想要|我需要|请给我|给我|帮我|请|来一个|来个|做|生成|创建|新建|建模)/, ''); + value = value.replace(/^(?:一个|一件|一只|一把|个)/, ''); + if (!value || /并|同时|以及|工程图|装配|添加|修改|删除|查询|出图/.test(value)) return null; + if (/锥齿轮|bevel\s*gear/i.test(value)) return { key: 'bevel_gear', label: '锥齿轮' }; + if (/直齿轮|直齿圆柱齿轮|spur\s*gear/i.test(value)) return { key: 'spur_gear', label: '直齿轮' }; + if (/椅子|座椅|办公椅|餐椅|chair/i.test(value)) return { key: 'chair', label: '椅子' }; + if (/凳子|stool/i.test(value)) return { key: 'stool', label: '凳子' }; + if (/桌子|工作台|table|desk/i.test(value)) return { key: 'table', label: '桌子' }; + if (/支架|托架|bracket|support/i.test(value)) return { key: 'bracket', label: '支架' }; + return null; +} + +function simpleGearIntent(text, object) { + const value = compactText(text); + return { + intent: 'create', + part: { name: object.label }, + dimensions: { + module: numberAfter(value, ['模数', 'module']) ?? undefined, + teeth: numberAfter(value, ['齿数', '齿', 'teeth']) ?? undefined, + width: numberAfter(value, ['齿宽', '宽度', '厚度', 'width']) ?? undefined, + boreDiameter: numberAfter(value, ['孔径', '孔直径', '内孔', 'bore']) ?? undefined, + pressureAngle: numberAfter(value, ['压力角', 'pressureAngle']) ?? undefined, + }, + assumptions: ['未指定参数采用 MiniSW 默认机械设计值'], + ambiguities: [], + }; +} + +function buildSimpleObjectOps(object, text) { + if (object.key === 'bevel_gear' || object.key === 'spur_gear') { + return compileKnownMechanicalIntent(text, simpleGearIntent(text, object), null); + } + if (object.key === 'chair') { + return [{ op: 'cylinder', r: 250, h: 40, tz: 450, name: '默认座面' }]; + } + if (object.key === 'stool') { + return [ + { op: 'cylinder', r: 200, h: 40, tz: 450, name: '默认凳面' }, + { op: 'cylinder', r: 18, h: 430, tz: 215, name: '默认凳柱' }, + ]; + } + if (object.key === 'table') { + return [ + { op: 'sketch', plane: 'top', name: '默认桌面草图' }, + { op: 'rect', cx: 0, cy: 0, w: 1200, h: 600 }, + { op: 'extrude', depth: 30, name: '默认桌面' }, + { op: 'cylinder', r: 20, h: 750, tx: -520, ty: -220, tz: -360, name: '默认桌腿1' }, + { op: 'cylinder', r: 20, h: 750, tx: 520, ty: -220, tz: -360, name: '默认桌腿2' }, + { op: 'cylinder', r: 20, h: 750, tx: -520, ty: 220, tz: -360, name: '默认桌腿3' }, + { op: 'cylinder', r: 20, h: 750, tx: 520, ty: 220, tz: -360, name: '默认桌腿4' }, + ]; + } + return [ + { op: 'sketch', plane: 'front', name: '默认支架底座草图' }, + { op: 'rect', cx: 0, cy: 0, w: 120, h: 80 }, + { op: 'extrude', depth: 12, name: '默认支架底座' }, + ]; +} + +/** + * 返回高确定性的本地计划;返回 null 表示仍需 LLM 处理。 + * 这里刻意只接管短对象请求,避免把“复杂复合请求”误判为单一默认模型。 + */ +export function decideAgentPlan(userText, context = {}) { + const request = decideLocalRequest(userText, context.document || null); + if (!request) return null; + return { + ops: request.ops, + meta: { mode: 'local-exact', requestType: request.intent, confidence: 'high', calls: 0, trace: [{ stage: 'local_exact', ok: true, local: true }] }, + }; +} + export function getLLMSettings() { try { const raw = localStorage.getItem('minisw_llm'); - return raw ? JSON.parse(raw) : { baseURL: '', apiKey: '', model: '' }; - } catch { return { baseURL: '', apiKey: '', model: '' }; } + const stored = raw ? JSON.parse(raw) : {}; + delete stored.speed; + return { ...LLM_DEFAULTS, ...stored }; + } catch { return { ...LLM_DEFAULTS }; } } -export function setLLMSettings(s) { localStorage.setItem('minisw_llm', JSON.stringify(s)); } +export function setLLMSettings(s) { + const next = { ...LLM_DEFAULTS, ...s }; + delete next.speed; + localStorage.setItem('minisw_llm', JSON.stringify(next)); +} + +/** + * 聊天型 Agent:LLM 自主读取源码、观察模型、调用建模工具,并在工具结果返回后继续工作。 + * 这里不预先拼装 DSL 提示,也不替 LLM 做意图分流。 + */ +export async function runAgentChat(userText, cfg, context = {}) { + const trace = []; + const messages = Array.isArray(context.history) ? context.history.slice() : []; + messages.push({ role: 'user', content: userText }); + const toolHandlers = { + list_project_files: () => listProjectFiles(), + read_project_file: args => readProjectFile(args?.path), + ...(context.toolHandlers || {}), + }; + let turn = 0; + while (true) { + const started = Date.now(); + const response = await callLLMAPI(cfg, messages, { + tools: AGENT_TOOL_DEFINITIONS, + returnResponse: true, + stream: false, + }); + trace.push({ stage: turn ? 'tool_followup' : 'agent_chat', ok: true, durationMs: Date.now() - started }); + context.onTrace?.({ stage: turn ? 'tool_followup' : 'agent_chat', ok: true, durationMs: Date.now() - started, request: messages, response }); + const toolCalls = Array.isArray(response.toolCalls) ? response.toolCalls : []; + if (!toolCalls.length) { + return { + reply: String(response.text || ''), + agentMeta: { mode: 'tool-chat', calls: trace.length, trace }, + }; + } + messages.push({ role: 'assistant', content: response.text || null, tool_calls: toolCalls }); + for (const call of toolCalls) { + const name = call?.function?.name || call?.name; + let args = {}; + try { args = typeof call?.function?.arguments === 'string' ? JSON.parse(call.function.arguments || '{}') : (call?.arguments || {}); } + catch (error) { args = { _parseError: error.message || String(error) }; } + let result; + try { + if (args._parseError) throw new Error('工具参数不是有效 JSON:' + args._parseError); + if (typeof toolHandlers[name] !== 'function') throw new Error(`不存在工具:${name}`); + result = await toolHandlers[name](args); + } catch (error) { + result = { ok: false, error: error.message || String(error) }; + } + const toolResult = typeof result === 'string' ? result : JSON.stringify(result); + context.onTrace?.({ stage: 'tool_call', ok: !result?.error, tool: name, arguments: args, result }); + messages.push({ role: 'tool', tool_call_id: call.id || `tool-${turn}`, name, content: toolResult }); + } + turn++; + } +} /** - * 调用 LLM 生成操作序列。 + * 兼容旧调用方:直接要求 LLM 返回可解析的操作序列。 * @param {string} userText 用户输入 * @param {object} cfg LLM 配置 {baseURL, apiKey, model} * @param {object} context 可选上下文 {modelState, history} */ export async function runLLM(userText, cfg, context = {}) { - const messages = [{ role: 'system', content: DSL_SPEC }]; - if (context.modelState) { - messages.push({ role: 'system', content: '当前模型状态:' + context.modelState + '。在已有模型基础上继续建模,不要重建已有特征。' }); - } - if (context.history && context.history.length) { - messages.push(...context.history.slice(-6)); - } + const trace = []; + const traceSnapshot = value => { + try { return structuredClone(value); } + catch { try { return JSON.parse(JSON.stringify(value)); } catch { return value; } } + }; + const invoke = async (stage, messages, options = {}) => { + const started = Date.now(); + const streamId = `${stage}-${started}-${trace.length}`; + try { + const content = await callLLMAPI(cfg, messages, { + ...options, + onDelta: delta => { + options.onDelta?.(delta); + context.onStream?.({ ...delta, stage, streamId }); + }, + }); + const durationMs = Date.now() - started; + trace.push({ stage, ok: true, durationMs }); + context.onTrace?.({ stage, ok: true, durationMs, request: traceSnapshot(messages), response: content }); + return content; + } catch (error) { + const durationMs = Date.now() - started; + trace.push({ stage, ok: false, durationMs, error: error.message || String(error) }); + context.onTrace?.({ stage, ok: false, durationMs, request: traceSnapshot(messages), error: error.message || String(error) }); + throw error; + } + }; + context.onStage?.('统一 Agent 正在理解并规划…'); + const projectFiles = await readProjectSourceContext(); + const messages = [{ role: 'system', content: projectFiles }]; + if (context.modelState) messages.push({ role: 'user', content: context.modelState }); + if (context.history?.length) messages.push(...context.history); messages.push({ role: 'user', content: userText }); - const maxRetries = 1; let lastError = null; - for (let attempt = 0; attempt <= maxRetries; attempt++) { + for (let attempt = 0; ; attempt++) { let content; try { - content = await callLLMAPI(cfg, messages); + content = await invoke(attempt === 0 ? 'unified_planning' : 'unified_planning_repair', messages); } catch (e) { - throw new Error('LLM 请求失败:' + (e.message || e) + '。请检查网络连接和 API 配置。'); + lastError = e.message || String(e); + context.onStage?.('统一 Agent 正在修正输出…'); + messages.push({ role: 'user', content: lastError }); + continue; } try { const ops = extractOps(content); const verr = validateOps(ops); if (verr) throw new Error('校验失败:' + verr); - return ops; + const semanticError = validatePlanCoverage(userText, ops, context); + if (semanticError) throw new Error('意图覆盖失败:' + semanticError); + return attachAgentMeta(ops, { mode: 'unified', requestType: inferAgentRequestType(userText), calls: trace.length, trace }); } catch (e) { lastError = e.message; - if (attempt === maxRetries) throw new Error('LLM 输出校验失败(已重试 1 次):' + lastError + '。可尝试用更标准的描述,或使用内置解析器。'); - messages.push({ role: 'assistant', content }); - messages.push({ role: 'user', content: `上一次输出有误:${lastError}。请修正并重新输出,只输出一个 JSON 数组。` }); + context.onStage?.('统一 Agent 正在纠正计划…'); + messages.push({ role: 'user', content: lastError }); } } } -async function callLLMAPI(cfg, messages) { +function inferAgentRequestType(text) { + const value = compactText(text); + if (/^(查询|查看|显示|列出|告诉我|汇报|你能看到|描述模型|有哪些|多少|当前状态|模型状态)/.test(value)) return 'query'; + if (/^(删除|移除|去掉|取消)/.test(value)) return 'delete'; + if (/^(修改|更改|调整|把|将|设置|设定|重命名|改名)/.test(value)) return 'modify'; + if (/装配体|装配/.test(value)) return 'assembly'; + if (/工程图|三视图|出图|图纸/.test(value)) return 'drawing'; + if (/^(在|向|给|为).*(添加|增加|加上|开一个|打一个)/.test(value)) return 'add'; + return 'create'; +} + +function attachAgentMeta(ops, meta) { + try { Object.defineProperty(ops, 'agentMeta', { value: meta, configurable: true, enumerable: false }); } + catch { ops.agentMeta = meta; } + return ops; +} + +function locallyRepairPlan(userText, failedOps, executionError) { + const error = String(executionError || ''); + if (/指定的旋转轴实体不存在/.test(error)) { + const next = JSON.parse(JSON.stringify(failedOps || [])); + let changed = false; + for (const op of next) { + if (op.op !== 'revolve' || typeof op.axis !== 'string') continue; + if (/^(?:y|vertical|default)$/i.test(op.axis)) { delete op.axis; changed = true; } + } + if (changed) return next; + } + return null; +} + +/** 软件执行失败后,把真实错误、失败计划和当前模型状态交回 LLM 修正。 */ +export async function repairLLMPlan(userText, cfg, context = {}, failedOps = [], executionError = '') { + const localRepair = locallyRepairPlan(userText, failedOps, executionError); + if (localRepair) { + const trace = { stage: 'local_plan_repair', ok: true, local: true, durationMs: 0 }; + context.onTrace?.({ ...trace, request: { userText, executionError, failedOps }, response: { ops: localRepair } }); + return attachAgentMeta(localRepair, { mode: 'local-repair', requestType: inferAgentRequestType(userText), calls: 0, trace: [trace] }); + } + context.onStage?.('软件执行失败,正在回传错误纠正…'); + const messages = [ + { role: 'system', content: await readProjectSourceContext() }, + ]; + if (context.modelState) messages.push({ role: 'user', content: context.modelState }); + messages.push({ role: 'user', content: JSON.stringify({ + originalRequest: userText, + executionError: String(executionError), + failedOps, + }) }); + let content; + const started = Date.now(); + try { + content = await callLLMAPI(cfg, messages); + context.onTrace?.({ stage: 'execution_repair', ok: true, durationMs: Date.now() - started, request: messages, response: content }); + } catch (error) { + context.onTrace?.({ stage: 'execution_repair', ok: false, durationMs: Date.now() - started, request: messages, error: error.message || String(error) }); + throw new Error('LLM 纠错请求失败:' + (error.message || error)); + } + const ops = extractOps(content); + const verr = validateOps(ops); + if (verr) throw new Error('LLM 纠错结果仍不合法:' + verr); + const semanticError = validatePlanCoverage(userText, ops, context); + if (semanticError) throw new Error('LLM 纠错结果仍未覆盖需求:' + semanticError); + return attachAgentMeta(ops, { mode: 'unified-repair', requestType: inferAgentRequestType(userText), calls: 1, trace: [{ stage: 'execution_repair', ok: true, durationMs: Date.now() - started }] }); +} + +function extractJSONObject(content) { + let source = String(content || '').replace(/^\uFEFF/, '').replace(/```(?:json)?/gi, '').replace(/```/g, '').trim(); + const start = source.indexOf('{'), end = source.lastIndexOf('}'); + if (start >= 0 && end > start) source = source.slice(start, end + 1); + let value; + try { value = JSON.parse(source); } + catch (firstError) { + const cleaned = sanitizeJsonLike(source); + try { value = JSON.parse(cleaned); } + catch { throw firstError; } + } + if (!value || Array.isArray(value) || typeof value !== 'object') throw new Error('意图结果不是 JSON 对象'); + return value; +} + +function validatePlanCoverage(userText, ops, context = {}) { + const text = normalize(userText); + const names = new Set(ops.map(op => String(op?.op || ''))); + const hasExistingBody = /"hasBody"\s*:\s*true/.test(String(context.modelState || '')); + const bodyOps = new Set(['extrude', 'revolve', 'cylinder', 'sweep', 'loft', 'shell', 'spur_gear', 'bevel_gear']); + const createsBody = ops.some(op => bodyOps.has(op.op)); + const isBevelGearRequest = /\u9525\u9f7f\u8f6e|bevel\s*gear/i.test(text); + if (isBevelGearRequest && !names.has('bevel_gear')) return '\u9525\u9f7f\u8f6e\u8bf7\u4f7f\u7528 bevel_gear\uff0c\u4e0d\u80fd\u964d\u7ea7\u4e3a spur_gear'; + if (isBevelGearRequest) return null; + const describesPart = /底座|底板|零件|实体|板|圆柱|凸台|轴|支撑|齿轮|轴承座/.test(text) && /做|创建|新建|生成|建模|加入|添加/.test(text); + const dependent = names.has('assembly_add_component') || names.has('drawing_generate'); + const primitiveOnly = [...names].every(name => ['sketch', 'rect', 'line', 'circle', 'arc', 'extrude', 'cut', 'cylinder', 'cylcut'].includes(name)); + const explicitPrimitive = /底座|底板|矩形板|圆柱|圆盘|通孔|孔|槽/.test(text); + const vagueNamedObject = !/\d/.test(text) && !explicitPrimitive && !/查询|查看|显示|修改|删除|调整/.test(text) && /做|创建|新建|生成|建模/.test(text) || (/^[\u4e00-\u9fff]{2,12}$/.test(text) && !explicitPrimitive); + if (vagueNamedObject && primitiveOnly && ops.length > 1) return '这是一个命名对象请求,当前计划只有通用几何,必须拆解并覆盖对象的主要功能组成,不能只生成普通底板或外壳'; + if (/齿轮/.test(text) && !names.has('spur_gear') && (!names.has('cylinder') || !names.has('pattern_circular') || !names.has('cylcut'))) return '直齿轮计划必须使用 spur_gear,或包含 cylinder 齿坯、单齿特征后的 pattern_circular 和中心孔 cylcut'; + if (/^(删除|移除|去掉|取消)/.test(text) && !names.has('delete_feature')) return '请求要求删除特征,但计划没有 delete_feature'; + if (/^(修改|更改|调整|把|将|设置|设定|重命名|改名)/.test(text) && !names.has('update_feature')) return '请求要求修改特征,但计划没有 update_feature'; + if (/^(查询|查看|显示|列出|告诉我|汇报|你能看到|描述模型|有哪些|多少|当前状态|模型状态)/.test(text) && !names.has('query_document')) return '请求是文档查询,但计划没有 query_document'; + if ((describesPart || dependent) && !hasExistingBody && !createsBody) return '请求需要零件实体,但计划没有任何创建实体的操作'; + if (/装配体|装配/.test(text) && !names.has('assembly_create') && !names.has('assembly_add_component')) return '请求包含装配,但计划没有装配操作'; + if (/工程图|三视图|出图|图纸/.test(text) && !names.has('drawing_generate')) return '请求包含工程图,但计划没有生成工程图'; + if (/干涉|碰撞/.test(text) && !names.has('interference_check')) return '请求要求干涉检查,但计划遗漏了该操作'; + if (/爆炸视图|爆炸/.test(text) && !names.has('explode')) return '请求要求爆炸视图,但计划遗漏了该操作'; + if (/剖视|剖面/.test(text) && !names.has('drawing_section')) return '请求要求剖视图,但计划遗漏了该操作'; + return null; +} + +async function callLLMAPI(cfg, messages, options = {}) { let base = (cfg.baseURL || 'https://api.openai.com/v1').trim().replace(/\/+$/, ''); const url = /\/chat\/completions$/.test(base) ? base : base + '/chat/completions'; - const resp = await fetch(url, { + let resp, data; + const requestBody = { + model: cfg.model, + messages, + }; + if (Array.isArray(options.tools) && options.tools.length) requestBody.tools = options.tools; + const streaming = options.stream !== false; + if (streaming) requestBody.stream = true; + const send = body => fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + cfg.apiKey }, - body: JSON.stringify({ model: cfg.model, messages, temperature: 0.2 }), + body: JSON.stringify(body), }); - const data = await resp.json().catch(() => null); + try { + resp = await send(requestBody); + if (resp.ok && isStreamingResponse(resp)) { + const streamed = await readStreamingResponse(resp, options.onDelta); + data = { choices: [{ message: { content: streamed.content || null, reasoning_content: streamed.reasoning || '', tool_calls: streamed.toolCalls || [] } }] }; + if (!streamed.content && streamed.reasoning) return streamed.reasoning; + } else data = await resp.json().catch(() => null); + let apiMessage = data?.error?.message || ''; + if (!resp.ok && streaming && /stream|unsupported|unknown parameter/i.test(apiMessage)) { + delete requestBody.stream; + resp = await send(requestBody); + data = await resp.json().catch(() => null); + } + } catch (error) { + throw error; + } if (!resp.ok) throw new Error((data && data.error && data.error.message) || ('HTTP ' + resp.status)); - const content = data && data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.content; - if (!content) throw new Error('模型未返回内容'); + const content = extractLLMText(data, { allowReasoning: true }); + const toolCalls = data?.choices?.[0]?.message?.tool_calls || []; + if (options.returnResponse) return { text: content, toolCalls, raw: data }; + if (!content) { + const finish = data?.choices?.[0]?.finish_reason; + throw new Error(finish === 'length' ? '模型输出达到长度上限,但没有给出最终内容' : '模型未返回最终内容'); + } return content; } +function isStreamingResponse(resp) { + const contentType = resp?.headers?.get?.('content-type') || ''; + return Boolean(resp?.ok && resp?.body && typeof resp.body.getReader === 'function' && /text\/event-stream/i.test(contentType)); +} + +/** 读取 OpenAI Chat Completions SSE;不改变最终内容,只把增量转发给调试窗口。 */ +async function readStreamingResponse(resp, onDelta) { + const reader = resp.body.getReader(); + const decoder = new TextDecoder(); + let buffer = '', content = '', reasoning = '', done = false, toolCalls = []; + const flatten = value => { + if (typeof value === 'string') return value; + if (Array.isArray(value)) return value.map(item => flatten(item?.text ?? item?.content ?? item)).join(''); + if (value && typeof value === 'object') return flatten(value.text ?? value.content ?? value.value); + return ''; + }; + const consume = frame => { + for (const line of String(frame).split(/\r?\n/)) { + if (!line.startsWith('data:')) continue; + const raw = line.slice(5).trim(); + if (!raw || raw === '[DONE]') { done = raw === '[DONE]' || done; continue; } + let packet; + try { packet = JSON.parse(raw); } catch { continue; } + const delta = packet?.choices?.[0]?.delta || packet?.choices?.[0]?.message || packet?.choices?.[0] || {}; + const nextContent = flatten(delta.content ?? packet?.choices?.[0]?.text); + const nextReasoning = flatten(delta.reasoning_content ?? delta.reasoning); + let hasToolDelta = false; + for (const part of (Array.isArray(delta.tool_calls) ? delta.tool_calls : [])) { + const index = Number.isInteger(part.index) ? part.index : toolCalls.length; + const call = toolCalls[index] || { id: '', type: 'function', function: { name: '', arguments: '' } }; + if (part.id) call.id = part.id; + if (part.type) call.type = part.type; + if (part.function?.name) call.function.name += part.function.name; + if (part.function?.arguments) call.function.arguments += part.function.arguments; + toolCalls[index] = call; + hasToolDelta = true; + } + if (!nextContent && !nextReasoning && !hasToolDelta) continue; + content += nextContent; + reasoning += nextReasoning; + onDelta?.({ delta: nextContent, reasoningDelta: nextReasoning, accumulated: content, reasoning, toolCalls, done: false }); + } + }; + while (!done) { + const { value, done: readerDone } = await reader.read(); + buffer += decoder.decode(value || new Uint8Array(), { stream: !readerDone }); + let boundary; + while ((boundary = buffer.search(/\r?\n\r?\n/)) >= 0) { + const frame = buffer.slice(0, boundary); + buffer = buffer.slice(boundary).replace(/^\r?\n\r?\n/, ''); + consume(frame); + } + if (readerDone) break; + } + if (buffer.trim()) consume(buffer); + onDelta?.({ delta: '', reasoningDelta: '', accumulated: content, reasoning, done: true }); + return { content, reasoning, toolCalls }; +} + +function extractLLMText(data, options = {}) { + const choice = data?.choices?.[0]; + const message = choice?.message; + const flatten = value => { + if (typeof value === 'string') return value.trim(); + if (Array.isArray(value)) return value.map(item => flatten(item?.text ?? item?.content ?? item)).filter(Boolean).join('\n').trim(); + if (value && typeof value === 'object') return flatten(value.text ?? value.content ?? value.value); + return ''; + }; + const direct = [ + message?.content, + choice?.text, + data?.output_text, + data?.response, + data?.result, + data?.output, + ].map(flatten).find(Boolean); + if (direct) return direct; + // 部分推理模型只返回 reasoning_content;只有其中已经包含 JSON 时才作为最终答案使用。 + if (options.allowReasoning === false) return ''; + const reasoning = flatten(message?.reasoning_content ?? message?.reasoning); + if (reasoning && ((reasoning.includes('{') && reasoning.includes('}')) || (reasoning.includes('[') && reasoning.includes(']')))) return reasoning; + return ''; +} + export function extractOps(content) { - let s = content; - const i = s.indexOf('['), j = s.lastIndexOf(']'); - if (i >= 0 && j > i) s = s.slice(i, j + 1); - const ops = JSON.parse(s); - if (!Array.isArray(ops)) throw new Error('模型输出不是数组'); - return ops; + const source = String(content || '').replace(/^\uFEFF/, '').replace(/```(?:json)?/gi, '').replace(/```/g, '').trim(); + const isOpsArray = value => Array.isArray(value) && value.length > 0 && value.every(item => item && typeof item === 'object' && !Array.isArray(item) && typeof item.op === 'string'); + let firstError = null; + for (const text of [source, sanitizeJsonLike(source)]) { + try { + const root = JSON.parse(text); + if (isOpsArray(root)) return root; + if (isOpsArray(root?.ops)) return root.ops; + } catch (error) { if (!firstError) firstError = error; } + } + for (let start = source.indexOf('['); start >= 0; start = source.indexOf('[', start + 1)) { + const candidate = extractBalancedArray(source, start); + if (!candidate) continue; + for (const text of [candidate, sanitizeJsonLike(candidate)]) { + try { + const ops = JSON.parse(text); + if (isOpsArray(ops)) return ops; + } catch (error) { if (!firstError) firstError = error; } + } + } + throw firstError || new Error('模型输出中没有完整的可执行 ops 数组'); +} + +function extractBalancedArray(source, start) { + let depth = 0, quoted = false, escaped = false; + for (let i = start; i < source.length; i++) { + const ch = source[i]; + if (quoted) { + if (escaped) escaped = false; + else if (ch === '\\') escaped = true; + else if (ch === '"') quoted = false; + continue; + } + if (ch === '"') { quoted = true; continue; } + if (ch === '[') depth++; + else if (ch === ']') { + depth--; + if (depth === 0) return source.slice(start, i + 1); + } + } + return null; +} + +// LLM 有时会把 JavaScript 对象字面量当作 JSON,统一清理常见非 JSON 值。 +function sanitizeJsonLike(source) { + return String(source) + .replace(/[“”]/g, '"').replace(/[‘’]/g, "'") + .replace(/\/\*[\s\S]*?\*\//g, '') + .replace(/^\s*\/\/.*$/gm, '') + .replace(/\bundefined\b/g, 'null') + .replace(/\bNaN\b/g, 'null') + .replace(/\b-Infinity\b|\bInfinity\b/g, 'null') + .replace(/([:,\[]\s*)-(?=\s*[,}\]])/g, '$1null') + // 未加引号的工程表达式(如 s.p/(2r_p))不能执行,先标记为空值交给 DSL 校验。 + .replace(/([:,\[]\s*)(?!["'{\[\]\-]|\d|true\b|false\b|null\b)([A-Za-z_$][^,}\]]*)(?=\s*[,}\]])/g, '$1null') + .replace(/([:,\[]\s*)(?=\d[^,}\]]*[/*+\-][^,}\]]*)([^,}\]]+)(?=\s*[,}\]])/g, '$1null') + .replace(/,\s*([}\]])/g, '$1') + .replace(/([{,]\s*)([A-Za-z_$][\w$-]*)\s*:/g, '$1"$2":'); } // ============ 4. 校验 + 执行 ============ const OP_SCHEMA = { + query_document: ['answer'], + delete_feature: ['featureId'], + update_feature: ['featureId', 'patch'], sketch: ['plane'], rect: ['cx', 'cy', 'w', 'h'], line: ['p1', 'p2'], circle: ['cx', 'cy', 'r'], arc: ['cx', 'cy', 'r', 'a0', 'a1'], centerline: ['p1', 'p2'], - extrude: ['depth'], + ellipse: ['cx', 'cy', 'rx', 'ry'], + slot: ['p1', 'p2', 'width'], + spline: ['points'], + constraint: ['type', 'refs'], + set_dimension: ['name', 'expression'], + set_parameter: ['name', 'value'], + sketch_pattern_linear: ['count'], + sketch_pattern_circular: ['count'], + assembly_create: [], + assembly_add_component: ['id'], + mate: ['type', 'refA', 'refB'], + interference_check: [], + explode: [], + drawing_generate: [], + drawing_dimension: ['value'], + drawing_section: [], + extrude: [], cut: [], revolve: [], cylinder: ['r', 'h'], cylcut: ['r', 'h'], + sweep: ['path'], + loft: [], + rib: ['thickness'], + chamfer_edge: ['edgeRef', 'distance'], + fillet_edge: ['edgeRef', 'radius'], + reference_plane: ['kind'], }; const HIGH_LEVEL_OPS = { @@ -668,10 +2013,14 @@ const HIGH_LEVEL_OPS = { mirror: [], pattern_linear: ['count'], pattern_circular: ['count'], + spur_gear: ['module', 'teeth', 'width'], + bevel_gear: ['module', 'teeth', 'width'], }; export function validateOps(ops) { if (!Array.isArray(ops) || ops.length === 0) return '操作序列为空'; + const namedDimensions = new Set(ops.filter(op => op?.op === 'set_dimension' && op.name).map(op => String(op.name))); + const isPositiveDimension = value => Number(value) > 0 || (typeof value === 'string' && namedDimensions.has(value.trim())); // 预校验(含高级 op) for (const op of ops) { if (!op || typeof op !== 'object') return '包含非法 op'; @@ -681,12 +2030,61 @@ export function validateOps(ops) { if (op[k] === undefined) return `op ${op.op} 缺少参数 ${k}`; } if (op.op === 'sketch' && !['front', 'top', 'right'].includes(op.plane)) return '草图平面必须是 front/top/right'; + if ((op.op === 'rect' && (!isPositiveDimension(op.w) || !isPositiveDimension(op.h))) || ((op.op === 'circle' || op.op === 'arc') && !isPositiveDimension(op.r))) return `${op.op} 的尺寸参数必须是正数或已定义的命名尺寸`; + if ((op.op === 'cylinder' || op.op === 'cylcut') && (!(Number(op.r) > 0) || !(Number(op.h) > 0))) return `${op.op} 的半径和高度必须是正数`; + if (op.op === 'ellipse' && (!(op.rx > 0) || !(op.ry > 0))) return '椭圆长短半轴必须大于 0'; + if (op.op === 'slot' && (!(op.width > 0) || !Array.isArray(op.p1) || !Array.isArray(op.p2))) return '长圆槽参数无效'; + if (op.op === 'spline' && (!Array.isArray(op.points) || op.points.length < 2)) return '样条至少需要两个控制点'; if ((op.op === 'chamfer_rect' || op.op === 'fillet_rect') && (op.w <= 0 || op.h <= 0)) return `${op.op} 的宽高必须大于 0`; + if (op.op === 'sweep') { + if (!Array.isArray(op.path) || op.path.length < 2) return '扫描路径至少需要两个点'; + if (!op.profile && !(op.radius > 0)) return '扫描需要有效的截面或正半径'; + if (op.path.some(p => !Array.isArray(p) || p.length < 3 || p.some(v => !Number.isFinite(Number(v))))) return '扫描路径点必须是三维数值坐标'; + } + if (op.op === 'loft') { + if (!Array.isArray(op.profiles) || op.profiles.length < 2) return '放样至少需要两个截面'; + } + if (op.op === 'extrude' || op.op === 'cut') { + const mode = op.mode || op.termination || (op.throughAll ? 'throughAll' : (op.symmetric ? 'symmetric' : 'blind')); + if (!['blind', 'throughAll', 'symmetric', 'toNext', 'toFace', 'toBody'].includes(mode)) return `不支持的拉伸终止模式:${mode}`; + if ((mode === 'blind' || mode === 'symmetric') && (!(Number(op.depth) > 0))) return `${op.op} 的深度必须大于 0`; + if (mode === 'toFace' && op.targetFaceRef == null) return `${op.op} 的到面终止缺少 targetFaceRef`; + if (mode === 'toBody' && op.targetBodyRef == null) return `${op.op} 的到实体终止缺少 targetBodyRef`; + } + if (op.op === 'rib') { + if (!(Number(op.thickness) > 0) || !(Number(op.extent ?? 10) > 0)) return '筋厚度和延伸范围必须大于 0'; + if (op.side && !['both', 'left', 'right', 'single'].includes(op.side)) return '筋侧向参数无效'; + } + if (op.op === 'chamfer_edge' && !(Number(op.distance) > 0)) return '实体倒角距离必须大于 0'; + if (op.op === 'fillet_edge') { + if (!(Number(op.radius) > 0)) return '实体圆角半径必须大于 0'; + if (!Number.isInteger(Number(op.segments ?? 8)) || Number(op.segments ?? 8) < 2 || Number(op.segments ?? 8) > 48) return '实体圆角分段必须为 2 到 48 的整数'; + } + if (op.op === 'reference_plane') { + if (op.kind !== 'offset') return '内置 DSL 当前仅支持 offset 参考基准面'; + if (!Number.isFinite(Number(op.distance))) return '参考基准面偏移距离必须是数值'; + } if (op.op === 'shell') { if (op.wall !== undefined && op.wall <= 0) return '抽壳壁厚必须大于 0'; if (op.wall !== undefined && op.wall >= Math.min(op.w, op.h) / 2) return '抽壳壁厚过大,内腔会消失'; } if ((op.op === 'pattern_linear' || op.op === 'pattern_circular') && op.count < 2) return '阵列数量至少为 2'; + if (op.op === 'bevel_gear') { + if (!(Number(op.module) > 0)) return '\u9525\u9f7f\u8f6e\u6a21\u6570\u5fc5\u987b\u5927\u4e8e 0'; + if (!Number.isInteger(Number(op.teeth)) || Number(op.teeth) < 6 || Number(op.teeth) > 200) return '\u9525\u9f7f\u8f6e\u9f7f\u6570\u5fc5\u987b\u662f 6\uFF5E200 \u7684\u6574\u6570'; + if (!(Number(op.width) > 0)) return '\u9525\u9f7f\u8f6e\u9f7f\u5bbd\u5fc5\u987b\u5927\u4e8e 0'; + if (op.pressureAngle !== undefined && (!(Number(op.pressureAngle) >= 10) || !(Number(op.pressureAngle) <= 35))) return '\u9525\u9f7f\u8f6e\u538b\u529b\u89d2\u5fc5\u987b\u5728 10\u00B0\uFF5E35\u00B0 \u4e4b\u95f4'; + const pitchDiameter = Number(op.module) * Number(op.teeth); + if (op.boreDiameter !== undefined && (!(Number(op.boreDiameter) >= 0) || !(Number(op.boreDiameter) < pitchDiameter - 3.5 * Number(op.module)))) return '\u9525\u9f7f\u8f6e\u4e2d\u5fc3\u5b54\u76f4\u5f84\u8fc7\u5927'; + } + if (op.op === 'spur_gear') { + if (!(Number(op.module) > 0)) return '直齿轮模数必须大于 0'; + if (!Number.isInteger(Number(op.teeth)) || Number(op.teeth) < 6 || Number(op.teeth) > 200) return '直齿轮齿数必须是 6~200 的整数'; + if (!(Number(op.width) > 0)) return '直齿轮齿宽必须大于 0'; + if (op.pressureAngle !== undefined && (!(Number(op.pressureAngle) >= 10) || !(Number(op.pressureAngle) <= 35))) return '直齿轮压力角必须在 10°~35° 之间'; + const pitchDiameter = Number(op.module) * Number(op.teeth); + if (op.boreDiameter !== undefined && (!(Number(op.boreDiameter) >= 0) || !(Number(op.boreDiameter) < pitchDiameter - 3.5 * Number(op.module)))) return '直齿轮中心孔直径过大,齿根处必须保留足够轮缘'; + } } // 展开后结构校验 let expanded; @@ -698,9 +2096,29 @@ export function validateOps(ops) { let hasSketch = false; for (const op of expanded) { if (op.op === 'sketch') hasSketch = true; - else if (['rect', 'line', 'circle', 'arc', 'centerline'].includes(op.op) && !hasSketch) return '2D 实体 op 之前必须有 sketch'; - else if ((op.op === 'extrude' || op.op === 'cut' || op.op === 'revolve') && !hasSketch) return `${op.op} 之前必须有 sketch`; + else if (['rect', 'line', 'circle', 'arc', 'centerline', 'ellipse', 'slot', 'spline', 'constraint', 'sketch_pattern_linear', 'sketch_pattern_circular'].includes(op.op) && !hasSketch) return '2D 实体 op 之前必须有 sketch'; + else if ((op.op === 'extrude' || op.op === 'cut' || op.op === 'revolve' || op.op === 'rib') && !hasSketch) return `${op.op} 之前必须有 sketch`; + } + return null; +} + +/** 在修改文档前检查稳定引用和前置对象,避免用异常作为正常控制流。 */ +export function validateOpsAgainstDocument(doc, ops) { + if (!doc) return '没有当前文档'; + const names = new Set(ops.map(op => op.op)); + if (names.has('query_document') && ops.length > 1) return '查询操作不能与修改文档的操作混在同一个计划中'; + for (const op of ops) { + if (op.op === 'delete_feature' || op.op === 'update_feature') { + if (!doc.features.some(feature => String(feature.id) === String(op.featureId))) return `稳定特征 ID 不存在:${op.featureId}`; + } } + const createsBody = ops.some(op => ['extrude', 'revolve', 'cylinder', 'sweep', 'loft', 'shell', 'spur_gear', 'bevel_gear'].includes(op.op)); + if (!doc.bodyMesh && !createsBody && names.has('assembly_add_component')) return '装配插入缺少前置零件实体'; + if (!doc.bodyMesh && !createsBody && names.has('drawing_generate')) return '工程图生成缺少前置零件实体'; + const createsAssembly = names.has('assembly_create') || !!doc.assembly; + if (!createsAssembly && [...names].some(name => ['assembly_add_component', 'mate', 'interference_check', 'explode'].includes(name))) return '装配操作缺少装配体'; + const createsDrawing = names.has('drawing_generate') || !!doc.drawings?.length; + if (!createsDrawing && (names.has('drawing_dimension') || names.has('drawing_section'))) return '工程图标注或剖视缺少工程图'; return null; } @@ -709,13 +2127,39 @@ export function validateOps(ops) { * 高级 op 会先被 expandOps 展开为原语。 */ export function executeOps(doc, ops) { - const expanded = expandOps(ops); + const expanded = expandOps(orderOpsForDependencies(ops)); let cur = null; - const created = { sketches: [], features: [] }; + const created = { sketches: [], features: [], references: [], deleted: [], updated: [], answers: [] }; + const ensureBodyMesh = (message) => { + if (!doc.bodyMesh && doc.features?.length) doc.rebuild(); + if (!doc.bodyMesh) throw new Error(message); + return doc.bodyMesh; + }; + let lastReference = null; for (const op of expanded) { switch (op.op) { + case 'query_document': + created.answers.push(String(op.answer)); + break; + case 'delete_feature': { const feature = doc.features.find(f => String(f.id) === String(op.featureId)); + if (!feature) throw new Error(`要删除的特征不存在:${op.featureId}`); + const sketchId = feature.sketchId; + doc.deleteFeature(feature.id); + if (sketchId) doc.deleteSketch(sketchId); + created.deleted.push({ id: feature.id, name: feature.name }); + break; + } + case 'update_feature': { const feature = doc.features.find(f => String(f.id) === String(op.featureId)); + if (!feature) throw new Error(`要修改的特征不存在:${op.featureId}`); + if (!op.patch || typeof op.patch !== 'object' || Array.isArray(op.patch)) throw new Error('修改特征需要 patch 对象'); + doc.updateFeature(feature.id, op.patch); + created.updated.push({ id: feature.id, name: feature.name }); + created.features.push(feature.id); + break; + } case 'sketch': { - cur = doc.addSketch({ plane: op.plane || 'front', offset: op.offset || 0, name: op.name }); + const planeRefId = op.planeRefId === 'last' ? (lastReference && lastReference.id) : op.planeRefId; + cur = doc.addSketch({ plane: op.plane || 'front', planeRefId: planeRefId || null, offset: op.offset || 0, name: op.name }); created.sketches.push(cur.id); break; } @@ -739,13 +2183,94 @@ export function executeOps(doc, ops) { if (!cur) throw new Error('缺少草图'); doc.addEntity(cur.id, { type: 'centerline', p1: op.p1, p2: op.p2, construction: true }); break; + case 'ellipse': + if (!cur) throw new Error('缺少草图'); + doc.addEntity(cur.id, { type: 'ellipse', c: [op.cx || 0, op.cy || 0], rx: Number(op.rx), ry: Number(op.ry), rotation: Number(op.rotation || 0) }); + break; + case 'slot': + if (!cur) throw new Error('缺少草图'); + doc.addEntity(cur.id, { type: 'slot', p1: op.p1.map(Number), p2: op.p2.map(Number), width: Number(op.width) }); + break; + case 'spline': + if (!cur) throw new Error('缺少草图'); + doc.addEntity(cur.id, { type: 'spline', points: op.points.map(p => p.map(Number)), closed: !!op.closed }); + break; + case 'constraint': + if (!cur) throw new Error('缺少草图'); + cur.constraints.push({ id: nextIdForAgent(), type: op.type, refs: op.refs || [], value: op.value ?? op.target, expression: op.expression }); + break; + case 'set_dimension': + doc.setDimension(op.name, op.expression ?? op.value, op.unit || 'mm'); + break; + case 'set_parameter': + doc.setParameter(op.name, op.value); + break; + case 'assembly_create': + doc.assembly = createAssembly(op.name || '未命名装配体'); + break; + case 'assembly_add_component': + if (!doc.assembly) doc.assembly = createAssembly('未命名装配体'); + { + const bodyMesh = ensureBodyMesh('装配插入前需要当前零件实体;请先描述一个底座或零件实体'); + const bodyWidth = Math.max(Number(doc.bbox?.max?.[0]) - Number(doc.bbox?.min?.[0]), 100); + const hasTransform = op.transform && Object.keys(op.transform).length > 0; + const transform = hasTransform ? op.transform : { position: [doc.assembly.components.length * (bodyWidth + 20), 0, 0] }; + addComponent(doc.assembly, { id: op.id, name: op.name, docId: op.source === 'current' ? doc.name : op.docId, mesh: bodyMesh, transform, fixed: !!op.fixed }); + } + break; + case 'mate': + if (!doc.assembly) throw new Error('当前没有装配体'); + addMate(doc.assembly, op); + break; + case 'interference_check': + if (!doc.assembly) throw new Error('当前没有装配体'); + doc.assembly.lastInterferences = checkAllInterferences(doc.assembly); + break; + case 'explode': + if (!doc.assembly) throw new Error('当前没有装配体'); + setExplode(doc.assembly, op); + break; + case 'drawing_generate': { + const bodyMesh = ensureBodyMesh('生成工程图前需要当前零件实体;请先描述一个零件'); + const drawing = createDrawing({ name: op.name || doc.name, format: op.format || 'A4', orientation: op.orientation || 'landscape', scale: Number(op.scale || 1), units: doc.units }); + for (const view of layoutThreeViews(bodyMesh, { scale: Number(op.scale || 1) })) addView(drawing, view); + doc.drawings.push(drawing); + break; + } + case 'drawing_dimension': { + const drawing = doc.drawings.at(-1); if (!drawing) throw new Error('当前没有工程图'); + addDimension(drawing, op); break; + } + case 'drawing_section': { + const drawing = doc.drawings.at(-1); if (!drawing) throw new Error('当前没有工程图'); + addSectionView(drawing, { ...op, mesh: doc.bodyMesh }); break; + } + case 'sketch_pattern_linear': + if (!cur) throw new Error('缺少草图'); + sketchPatternLinear(doc, cur, op); + break; + case 'sketch_pattern_circular': + if (!cur) throw new Error('缺少草图'); + sketchPatternCircular(doc, cur, op); + break; case 'extrude': case 'cut': case 'revolve': { if (!cur) throw new Error(`${op.op} 缺少草图`); const f = doc.addFeature({ type: op.op, sketchId: cur.id, name: op.name, - params: { depth: op.depth ?? 10, flip: !!op.flip, symmetric: !!op.symmetric, throughAll: !!op.throughAll, revolveDegrees: op.revolveDegrees ?? 360, axisEntityId: op.axis ?? null }, + params: { + depth: op.depth ?? 10, + mode: op.mode || op.termination, + targetFaceRef: op.targetFaceRef, + targetBodyRef: op.targetBodyRef, + offset: Number(op.offset || 0), + flip: !!op.flip, + symmetric: !!op.symmetric, + throughAll: !!op.throughAll, + revolveDegrees: op.revolveDegrees ?? 360, + axisEntityId: op.axis ?? null, + }, }); created.features.push(f.id); break; @@ -760,9 +2285,102 @@ export function executeOps(doc, ops) { created.features.push(f.id); break; } + case 'sweep': { + const f = doc.addFeature({ + type: 'sweep', + name: op.name, + params: { + path: op.path, radius: op.radius, profile: op.profile, + frameMode: op.frameMode || 'parallelTransport', twistDegrees: op.twistDegrees || 0, + capStart: op.capStart !== false, capEnd: op.capEnd !== false, segments: op.segments ?? 32, + }, + }); + created.features.push(f.id); + break; + } + case 'loft': { + const f = doc.addFeature({ + type: 'loft', name: op.name, + params: { profiles: op.profiles, resampleCount: op.resampleCount ?? 32, capStart: op.capStart !== false, capEnd: op.capEnd !== false, alignment: op.alignment || 'auto' }, + }); + created.features.push(f.id); + break; + } + case 'rib': { + if (!cur) throw new Error('rib 缺少开放路径草图'); + const f = doc.addFeature({ + type: 'rib', sketchId: cur.id, name: op.name, + params: { + thickness: Number(op.thickness), extent: Number(op.extent ?? 10), + side: op.side || 'both', draftDegrees: Number(op.draftDegrees || 0), flip: !!op.flip, + }, + }); + created.features.push(f.id); + break; + } + case 'chamfer_edge': { + const f = doc.addFeature({ type: 'chamfer', name: op.name, params: { edgeRefs: [op.edgeRef], distance: Number(op.distance) } }); + created.features.push(f.id); + break; + } + case 'fillet_edge': { + const f = doc.addFeature({ type: 'fillet', name: op.name, params: { edgeRefs: [op.edgeRef], radius: Number(op.radius), segments: Number(op.segments ?? 8) } }); + created.features.push(f.id); + break; + } + case 'reference_plane': { + lastReference = doc.addReference({ + type: 'plane', name: op.name, + sourceRefs: [op.source || 'front'], + params: { kind: op.kind, distance: Number(op.distance || 0) }, + }); + created.references.push(lastReference.id); + break; + } default: throw new Error('未知 op: ' + op.op); } } return created; } + +function cloneEntityTranslated(e, dx, dy) { + const out = JSON.parse(JSON.stringify(e)); + if (out.c) { out.c[0] += dx; out.c[1] += dy; } + if (out.p1) { out.p1[0] += dx; out.p1[1] += dy; } + if (out.p2) { out.p2[0] += dx; out.p2[1] += dy; } + if (out.points) out.points = out.points.map(p => [p[0] + dx, p[1] + dy]); + delete out.id; + return out; +} + +function cloneEntityRotated(e, cx, cy, angle) { + const out = JSON.parse(JSON.stringify(e)); + const rot = p => { const x = p[0] - cx, y = p[1] - cy, c = Math.cos(angle), s = Math.sin(angle); return [cx + x * c - y * s, cy + x * s + y * c]; }; + if (out.c) out.c = rot(out.c); + if (out.p1) out.p1 = rot(out.p1); + if (out.p2) out.p2 = rot(out.p2); + if (out.points) out.points = out.points.map(rot); + if (out.rotation != null) out.rotation += angle; + delete out.id; + return out; +} + +function selectedSketchEntities(sk, ids) { + const wanted = ids?.length ? new Set(ids.map(String)) : new Set([String(sk.entities.at(-1)?.id)]); + return sk.entities.filter(e => wanted.has(String(e.id))); +} + +function sketchPatternLinear(doc, sk, op) { + const count = Math.max(2, Math.min(50, Number(op.count) || 2)), dx = Number(op.dx || 0), dy = Number(op.dy || 0); + const base = selectedSketchEntities(sk, op.entityIds); + if (!base.length) throw new Error('草图阵列没有可复制的实体'); + for (let i = 1; i < count; i++) for (const e of base) doc.addEntity(sk.id, cloneEntityTranslated(e, dx * i, dy * i)); +} + +function sketchPatternCircular(doc, sk, op) { + const count = Math.max(2, Math.min(50, Number(op.count) || 2)), cx = Number(op.cx || 0), cy = Number(op.cy || 0); + const base = selectedSketchEntities(sk, op.entityIds); + if (!base.length) throw new Error('草图阵列没有可复制的实体'); + for (let i = 1; i < count; i++) for (const e of base) doc.addEntity(sk.id, cloneEntityRotated(e, cx, cy, Math.PI * 2 * i / count)); +} diff --git a/js/doc.js b/js/doc.js index ba1bdac..cb2f2c7 100644 --- a/js/doc.js +++ b/js/doc.js @@ -1,6 +1,13 @@ // doc.js —— 零件文档模型:草图/特征/重建/序列化 import { nextId, findLoops, validateProfile } from './sketch.js'; -import { meshFromManifold, booleanOp, buildExtrude, buildRevolve, buildCylinder, getMod } from './geometry.js'; +import { meshFromManifold, manifoldFromMeshData, booleanOp, buildExtrude, buildRevolve, buildCylinder, buildSweep, buildLoft, buildChamferEdge, buildFilletEdge, getMod } from './geometry.js'; +import { PLANES, toWorld } from './math.js'; +import { samplePathSketch, profileFromSketch } from './features/sweep.js'; +import { resolveReference } from './features/reference.js'; +import { normalizeTermination, resolveToNext, resolveToFace } from './features/termination.js'; +import { buildRibTool } from './features/rib.js'; +import { solveSketchConstraints, resolveDimensionTable, resolveParamExpressions } from './features/constraints.js'; +import { assemblyFromJSON } from './features/assembly.js?v=20260810-assembly2'; export class Doc { constructor() { @@ -8,11 +15,20 @@ export class Doc { } reset() { + // reset() 也用于“新建零件”场景;先释放上一次重建得到的 WASM 实体,避免 + // 连续通过 AI 创建多个零件时旧主体残留或被后续筋/倒角特征误引用。 + try { this.bodyManifold?.delete?.(); } catch { /* 已释放的 WASM 对象无需重复删除 */ } this.name = '未命名零件'; this.units = 'mm'; this.sketches = []; // {id,name,plane,offset,entities[],constraints[],dims[]} + this.references = []; // 用户参考几何(内置 front/top/right 不重复保存) + this.dimensions = []; // 命名尺寸:{name,expression,value,unit} + this.parameters = {}; // 可被尺寸/特征引用的参数 + this.assembly = null; // 装配体文档(零件文档保持 null) + this.drawings = []; // 工程图文档列表 this.features = []; // {id,name,type,sketchId,params} this.sketchSeq = 0; + this.referenceSeq = 0; this.featureSeq = 0; this.bodyManifold = null; // 最近一次重建结果(世界系) this.bodyMesh = null; // {verts,tris} @@ -21,11 +37,12 @@ export class Doc { } // ---------- 草图 ---------- - addSketch({ plane = 'front', offset = 0, name = null } = {}) { + addSketch({ plane = 'front', planeRefId = null, offset = 0, name = null } = {}) { const sk = { id: nextId(), name: name || `草图${++this.sketchSeq}`, plane, + planeRefId, offset: offset || 0, entities: [], constraints: [], @@ -37,6 +54,64 @@ export class Doc { sketchById(id) { return this.sketches.find(s => s.id === id); } + // ---------- 参考几何 ---------- + referenceById(id) { + const key = typeof id === 'string' && /^\d+$/.test(id) ? Number(id) : id; + const user = this.references.find(r => r.id === key); + if (user) return user; + const p = PLANES[key]; + if (!p) return null; + return { + id: key, + name: p.name + '基准面', + type: 'plane', + origin: [0, 0, 0], + normal: p.normal.slice(), + xAxis: p.uAxis.slice(), + yAxis: p.vAxis.slice(), + sourceRefs: [], + params: {}, + }; + } + + addReference(data = {}) { + if (!['plane', 'axis', 'point', 'csys'].includes(data.type)) throw new Error('未知的参考几何类型'); + const ref = JSON.parse(JSON.stringify(data)); + ref.id = ref.id ?? nextId(); + ref.name = ref.name || `参考几何${++this.referenceSeq}`; + ref.sourceRefs = Array.isArray(ref.sourceRefs) ? ref.sourceRefs : []; + ref.params = ref.params || {}; + this.references.push(ref); + return ref; + } + + updateReference(id, patch) { + const ref = this.references.find(r => r.id === id); + if (!ref) return null; + Object.assign(ref, JSON.parse(JSON.stringify(patch || {})), { id }); + return ref; + } + + deleteReference(id) { + const usedBySketch = this.sketches.some(s => s.planeRefId === id); + const usedByReference = this.references.some(r => r.id !== id && (r.sourceRefs || []).includes(id)); + if (usedBySketch || usedByReference) return false; + this.references = this.references.filter(r => r.id !== id); + return true; + } + + resolveReference(idOrRef) { + return resolveReference(idOrRef, id => this.referenceById(id)); + } + + resolveSketch(sketch) { + if (!sketch || !sketch.planeRefId) return sketch; + const plane = this.resolveReference(sketch.planeRefId); + if (!plane || plane.type !== 'plane') throw new Error('草图引用的基准面无效'); + const yAxis = plane.yAxis || cross3(plane.normal, plane.xAxis); + return { ...sketch, plane: { ...plane, yAxis }, offset: sketch.offset || 0 }; + } + addEntity(sketchId, ent) { const sk = this.sketchById(sketchId); ent.id = nextId(); @@ -76,9 +151,27 @@ export class Doc { updateFeature(id, params) { const f = this.features.find(x => x.id === id); if (!f) return; - f.params = JSON.parse(JSON.stringify(params)); + f.params = { ...f.params, ...JSON.parse(JSON.stringify(params)) }; } + setDimension(name, expression, unit = 'mm') { + if (!/^[A-Za-z_]\w*$/.test(String(name))) throw new Error('尺寸名称必须以字母或下划线开头'); + const d = this.dimensions.find(x => x.name === name); + if (d) Object.assign(d, { expression: String(expression), unit }); + else this.dimensions.push({ name: String(name), expression: String(expression), unit }); + return d || this.dimensions.at(-1); + } + + setParameter(name, value) { + if (!/^[A-Za-z_]\w*$/.test(String(name))) throw new Error('参数名称无效'); + const n = Number(value); + if (!Number.isFinite(n)) throw new Error('参数值必须是数字'); + this.parameters[String(name)] = n; + return n; + } + + dimensionContext() { return resolveDimensionTable(this.dimensions, this.parameters); } + deleteFeature(id) { this.features = this.features.filter(f => f.id !== id); } @@ -86,7 +179,12 @@ export class Doc { featureById(id) { return this.features.find(f => f.id === id); } deleteSketch(id) { - const used = this.features.some(f => f.sketchId === id); + const used = this.features.some(f => + f.sketchId === id || + f.params?.pathSketchId === id || + f.params?.profileSketchId === id || + (Array.isArray(f.params?.profileSketchIds) && f.params.profileSketchIds.includes(id)) + ); if (used) return false; this.sketches = this.sketches.filter(s => s.id !== id); return true; @@ -95,17 +193,31 @@ export class Doc { // ---------- 重建 ---------- rebuild() { const mod = getMod(); + if (!mod) throw new Error('几何内核尚未初始化'); + if (this.bodyManifold) { + this.bodyManifold.delete(); + this.bodyManifold = null; + } let body = null; + const dimensions = this.dimensionContext(); + for (const sk of this.sketches) { + const source = sk._paramEntities || (containsParamRef(sk.entities) ? (sk._paramEntities = JSON.parse(JSON.stringify(sk.entities))) : sk.entities); + sk.entities = resolveParamExpressions(JSON.parse(JSON.stringify(source)), { ...dimensions, dimensions, parameters: this.parameters }); + sk.solveReport = solveSketchConstraints(sk, { dimensions, parameters: this.parameters }); + } + for (const f of this.features) f.params = resolveParamExpressions(f.params, { ...dimensions, dimensions, parameters: this.parameters }); const deltas = {}; for (const f of this.features) { + let tool = null; try { - const sk = f.sketchId ? this.sketchById(f.sketchId) : null; + const storedSketch = f.sketchId ? this.sketchById(f.sketchId) : null; + const sk = storedSketch ? this.resolveSketch(storedSketch) : null; if (f.sketchId && !sk) throw new Error('草图不存在'); - let tool = null; + let replaceBody = false; switch (f.type) { case 'extrude': case 'cut': if (!sk) throw new Error('缺少草图'); - tool = buildExtrude(sk, f.params, body ? body.boundingBox() : null); + tool = buildExtrude(sk, resolveExtrudeParams(this, sk, resolveParamExpressions(f.params, { ...dimensions, dimensions, parameters: this.parameters }), body), body ? body.boundingBox() : null); break; case 'revolve': if (!sk) throw new Error('缺少草图'); @@ -117,21 +229,72 @@ export class Doc { case 'cylcut': tool = buildCylinder(f.params, body ? body.boundingBox() : null); break; + case 'sweep': + if (f.params.pathSketchId || f.params.profileSketchId) { + const pathSketch = this.sketchById(f.params.pathSketchId); + const profileSketch = this.sketchById(f.params.profileSketchId); + if (!pathSketch || !profileSketch) throw new Error('扫描引用的路径或截面草图不存在'); + const path = samplePathSketch(this.resolveSketch(pathSketch)); + const profile = profileFromSketch(this.resolveSketch(profileSketch)); + tool = buildSweep({ ...f.params, path, profile }); + } else { + tool = buildSweep(f.params); + } + break; + case 'loft': { + let profiles = f.params.profiles; + if (!profiles && Array.isArray(f.params.profileSketchIds)) { + profiles = f.params.profileSketchIds.map(id => { + const profileSketch = this.sketchById(id); + if (!profileSketch) throw new Error(`放样引用的截面草图不存在:${id}`); + const profile = profileFromSketch(this.resolveSketch(profileSketch)); + if (profile.rings && profile.rings.length > 1) throw new Error('放样暂不支持带孔的多环截面'); + return profile; + }); + } + tool = buildLoft(profiles, f.params); + break; + } + case 'rib': + if (!sk) throw new Error('筋特征缺少开放路径草图'); + if (!body) throw new Error('筋特征必须建立在已有主体上'); + tool = buildRibTool({ manifoldFromMeshData, mod }, { sketch: sk, ...f.params, body }); + break; + case 'chamfer': + if (!body) throw new Error('实体倒角必须建立在已有主体上'); + tool = buildChamferEdge(body, f.params); + replaceBody = true; + break; + case 'fillet': + if (!body) throw new Error('实体圆角必须建立在已有主体上'); + tool = buildFilletEdge(body, f.params); + replaceBody = true; + break; } const dm = meshFromManifold(tool); deltas[f.id] = dm; const subtract = f.type === 'cut' || f.type === 'cylcut'; let nb; - if (!body) { + if (replaceBody) { + body.delete(); + body = tool; + tool = null; + f.error = null; + continue; + } else if (!body) { nb = tool; // 首个特征直接成为实体 + tool = null; } else { nb = booleanOp(body, tool, subtract); body.delete(); tool.delete(); + tool = null; } body = nb; f.error = null; } catch (e) { + // 失败特征不能污染现有主体;已构造但未被接管的临时工具必须释放。 + if (tool && tool !== body) tool.delete?.(); f.error = (e && e.message) || String(e); } } @@ -148,10 +311,15 @@ export class Doc { /** 从快照恢复文档状态;调用方需随后 rebuild() 刷新几何 */ restore(snap) { + if (this.bodyManifold) this.bodyManifold.delete(); const d = Doc.fromJSON(snap); this.name = d.name; this.units = d.units; this.sketches = d.sketches; this.features = d.features; - this.sketchSeq = d.sketchSeq; this.featureSeq = d.featureSeq; + this.references = d.references; + this.dimensions = d.dimensions; this.parameters = d.parameters; + this.assembly = d.assembly; + this.drawings = d.drawings; + this.sketchSeq = d.sketchSeq; this.referenceSeq = d.referenceSeq; this.featureSeq = d.featureSeq; this.bodyManifold = null; this.bodyMesh = null; this.deltaMeshes = {}; this.bbox = null; return this; } @@ -159,9 +327,19 @@ export class Doc { // ---------- 序列化 ---------- toJSON() { return { - app: 'minisw', version: 1, units: this.units, + app: 'minisw', version: 2, units: this.units, name: this.name, - sketches: this.sketches, + sketches: this.sketches.map(sk => { + const copy = JSON.parse(JSON.stringify(sk)); + if (sk._paramEntities) copy.entities = sk._paramEntities; + delete copy._paramEntities; delete copy.solveReport; + return copy; + }), + references: this.references, + dimensions: this.dimensions, + parameters: this.parameters, + assembly: this.assembly, + drawings: this.drawings, features: this.features.map(f => ({ id: f.id, name: f.name, type: f.type, sketchId: f.sketchId, params: f.params })), }; } @@ -171,15 +349,22 @@ export class Doc { doc.name = data.name || '未命名零件'; doc.units = data.units || 'mm'; doc.sketches = (data.sketches || []).map(s => JSON.parse(JSON.stringify(s))); + doc.references = (data.references || []).map(r => JSON.parse(JSON.stringify(r))); + doc.dimensions = (data.dimensions || []).map(d => JSON.parse(JSON.stringify(d))); + doc.parameters = { ...(data.parameters || {}) }; + doc.assembly = data.assembly ? assemblyFromJSON(data.assembly) : null; + doc.drawings = (data.drawings || []).map(d => JSON.parse(JSON.stringify(d))); doc.features = (data.features || []).map(f => JSON.parse(JSON.stringify(f))); // 重建 id 计数器 const all = []; for (const s of doc.sketches) { all.push(s.id); for (const e of s.entities) all.push(e.id); for (const c of s.constraints) all.push(c.id); for (const d of s.dims) all.push(d.id); } + for (const r of doc.references) all.push(r.id); for (const f of doc.features) all.push(f.id); const max = all.reduce((m, v) => Math.max(m, v || 0), 0); for (let i = 0; i <= max; i++) nextId(); doc.sketchSeq = doc.sketches.filter(s => /^草图\d+$/.test(s.name)).length; - doc.featureSeq = doc.features.filter(f => /^(拉伸|切除|旋转)/.test(f.name)).length; + doc.referenceSeq = doc.references.length; + doc.featureSeq = doc.features.filter(f => /^(拉伸|切除|旋转|扫描|放样|筋|实体倒角|实体圆角)/.test(f.name)).length; return doc; } } @@ -191,6 +376,68 @@ function featureTypeName(type) { case 'revolve': return '旋转'; case 'cylinder': return '圆柱'; case 'cylcut': return '圆柱切除'; + case 'sweep': return '扫描'; + case 'loft': return '放样'; + case 'rib': return '筋'; + case 'chamfer': return '实体倒角'; + case 'fillet': return '实体圆角'; } return type; } + +function cross3(a, b) { + return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; +} + +function containsParamRef(value) { + if (typeof value === 'string') return /^[A-Za-z_]\w*$/.test(value); + if (Array.isArray(value)) return value.some(containsParamRef); + if (value && typeof value === 'object') return Object.values(value).some(containsParamRef); + return false; +} + +/** 把统一终止条件解析为现有拉伸内核可消费的精确深度。 */ +function resolveExtrudeParams(doc, sketch, params, body) { + const term = normalizeTermination(params || {}); + const out = { + ...params, + mode: term.mode, + flip: term.flip, + throughAll: term.mode === 'throughAll', + symmetric: term.mode === 'symmetric', + }; + if (term.mode === 'blind' || term.mode === 'symmetric') { + out.depth = term.depth; + return out; + } + if (term.mode === 'throughAll') return out; + + const plane = sketch.plane; + const direction = (typeof plane === 'object' ? plane.normal : PLANES[plane]?.normal)?.slice(); + if (!direction) throw new Error('拉伸草图平面缺少有效法向'); + const origin = toWorld(0, 0, 0, plane, sketch.offset || 0, 1); + + if (term.mode === 'toNext' || term.mode === 'toBody') { + if (!body) throw new Error(`${term.mode === 'toNext' ? '到下一面' : '到实体'}终止需要已有目标实体`); + // 当前文档是单实体顺序特征树;toBody 的稳定目标因此只能是当前主体。 + if (term.mode === 'toBody' && !['current', 'body', 'currentBody'].includes(term.targetBodyRef)) { + throw new Error('当前单实体文档仅支持以 current 作为到实体目标'); + } + const hit = resolveToNext({ origin, direction, body, flip: term.flip }); + out.depth = hit.depth + term.offset; + if (!(out.depth > 1e-6)) throw new Error('终止偏置后的拉伸深度必须大于零'); + return out; + } + + const targetFace = typeof term.targetFaceRef === 'object' + ? term.targetFaceRef + : doc.resolveReference(term.targetFaceRef); + out.depth = resolveToFace({ + origin, + direction, + targetFace, + offset: term.offset, + flip: term.flip, + }).depth; + return out; +} diff --git a/js/features/assembly.js b/js/features/assembly.js new file mode 100644 index 0000000..d244b26 --- /dev/null +++ b/js/features/assembly.js @@ -0,0 +1,136 @@ +// 装配体数据与轻量配合/干涉求解。组件仍引用独立零件文档,装配只保存变换和关系。 +import { getMod, manifoldFromMeshData } from '../geometry.js'; + +function identityTransform() { return { position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1] }; } +function clone(v) { return JSON.parse(JSON.stringify(v)); } +function add3(a, b) { return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; } +function sub3(a, b) { return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; } +function rotate(p, r) { + let [x, y, z] = p; const [rx, ry, rz] = r.map(Number); + let c = Math.cos(rx), s = Math.sin(rx); [y, z] = [y * c - z * s, y * s + z * c]; + c = Math.cos(ry); s = Math.sin(ry); [x, z] = [x * c + z * s, -x * s + z * c]; + c = Math.cos(rz); s = Math.sin(rz); [x, y] = [x * c - y * s, x * s + y * c]; + return [x, y, z]; +} + +export function createAssembly(name = '未命名装配体') { + return { version: 1, name, components: [], mates: [], explode: { active: false, progress: 0 } }; +} + +export function addComponent(assembly, { id, name, docId = null, mesh = null, transform = {}, fixed = false, visible = true } = {}) { + if (!assembly || !id) throw new Error('装配组件需要稳定 id'); + if (assembly.components.some(c => c.id === id)) throw new Error('组件已存在:' + id); + const component = { id: String(id), name: name || String(id), docId, mesh: mesh ? { verts: Float32Array.from(mesh.verts), tris: Uint32Array.from(mesh.tris) } : null, transform: { ...identityTransform(), ...clone(transform) }, fixed: !!fixed, visible: visible !== false }; + assembly.components.push(component); + return component; +} + +export function removeComponent(assembly, id) { + const before = assembly.components.length; + assembly.components = assembly.components.filter(c => c.id !== id); + assembly.mates = assembly.mates.filter(m => m.refA?.componentId !== id && m.refB?.componentId !== id); + return before !== assembly.components.length; +} + +export function updateComponentTransform(assembly, id, patch = {}) { + const c = assembly.components.find(x => x.id === id); + if (!c) throw new Error('组件不存在:' + id); + c.transform = { ...c.transform, ...clone(patch) }; + return c; +} + +export function transformedMesh(mesh, transform = identityTransform()) { + if (!mesh?.verts || !mesh?.tris) return null; + const position = transform.position || [0, 0, 0], rotation = transform.rotation || [0, 0, 0], scale = transform.scale || [1, 1, 1]; + const verts = new Float32Array(mesh.verts.length); + for (let i = 0; i < mesh.verts.length; i += 3) { + const p = rotate([mesh.verts[i] * scale[0], mesh.verts[i + 1] * scale[1], mesh.verts[i + 2] * scale[2]], rotation); + verts[i] = p[0] + position[0]; verts[i + 1] = p[1] + position[1]; verts[i + 2] = p[2] + position[2]; + } + return { verts, tris: mesh.tris.slice() }; +} + +export function boundsOfMesh(mesh) { + if (!mesh?.verts?.length) return null; + const min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity]; + for (let i = 0; i < mesh.verts.length; i += 3) for (let k = 0; k < 3; k++) { min[k] = Math.min(min[k], mesh.verts[i + k]); max[k] = Math.max(max[k], mesh.verts[i + k]); } + return { min, max }; +} + +function overlapBounds(a, b, tolerance = 1e-6) { + if (!a || !b) return null; + const min = [0, 1, 2].map(k => Math.max(a.min[k], b.min[k])), max = [0, 1, 2].map(k => Math.min(a.max[k], b.max[k])); + if (max.some((v, k) => v - min[k] <= tolerance)) return null; + return { min, max, volume: (max[0] - min[0]) * (max[1] - min[1]) * (max[2] - min[2]) }; +} + +export function checkInterference(a, b, { tolerance = 1e-6, precise = true } = {}) { + const ma = transformedMesh(a.mesh, a.transform), mb = transformedMesh(b.mesh, b.transform); + const overlap = overlapBounds(boundsOfMesh(ma), boundsOfMesh(mb), tolerance); + if (!overlap || !precise || !getMod()) return { interfering: !!overlap, volume: overlap?.volume || 0, bounds: overlap, componentA: a.id, componentB: b.id, precise: false }; + let aa = null, bb = null, intersection = null; + try { + aa = manifoldFromMeshData(ma, '装配组件A'); bb = manifoldFromMeshData(mb, '装配组件B'); + intersection = getMod().Manifold.intersection([aa, bb]); + const volume = intersection.volume(); + return { interfering: volume > tolerance, volume, bounds: overlap, componentA: a.id, componentB: b.id, precise: true }; + } catch { + return { interfering: !!overlap, volume: overlap.volume, bounds: overlap, componentA: a.id, componentB: b.id, precise: false }; + } finally { intersection?.delete?.(); aa?.delete?.(); bb?.delete?.(); } +} + +export function addMate(assembly, mate) { + const allowed = ['coincident', 'concentric', 'distance', 'parallel', 'perpendicular', 'angle']; + if (!allowed.includes(mate.type)) throw new Error('不支持的配合类型:' + mate.type); + if (!mate.refA?.componentId || !mate.refB?.componentId) throw new Error('配合需要两个组件引用'); + const out = { id: mate.id || 'mate-' + (assembly.mates.length + 1), type: mate.type, refA: clone(mate.refA), refB: clone(mate.refB), value: Number(mate.value || 0), suppressed: false }; + assembly.mates.push(out); return out; +} + +export function solveMates(assembly, { iterations = 4 } = {}) { + const reports = []; + for (let pass = 0; pass < iterations; pass++) for (const mate of assembly.mates) { + if (mate.suppressed) continue; + const a = assembly.components.find(c => c.id === mate.refA.componentId), b = assembly.components.find(c => c.id === mate.refB.componentId); + if (!a || !b) { reports.push({ id: mate.id, status: 'missing' }); continue; } + if (a.fixed && b.fixed) { reports.push({ id: mate.id, status: 'conflict' }); continue; } + const target = a.fixed ? b : a, anchor = a.fixed ? a : b; + const ar = mate.refA.position || [0, 0, 0], br = mate.refB.position || [0, 0, 0]; + const ap = add3(anchor.transform.position, a.fixed ? ar : br), tp = target.transform.position; + let desired = ap; + if (mate.type === 'distance') desired = add3(ap, [0, 0, mate.value]); + target.transform.position = sub3(desired, a.fixed ? br : ar); + reports.push({ id: mate.id, status: 'solved', residual: 0, component: target.id }); + } + return reports; +} + +export function setExplode(assembly, { progress = 1, vectors = {} } = {}) { + const p = Math.max(0, Math.min(1, Number(progress))); assembly.explode = { active: p > 0, progress: p }; + for (const c of assembly.components) { const v = vectors[c.id] || [0, 0, 0]; c.transform.position = add3(c.transform.position, v.map(n => Number(n) * p)); } + return assembly.explode; +} + +export function checkAllInterferences(assembly, options = {}) { + const out = []; + for (let i = 0; i < assembly.components.length; i++) for (let j = i + 1; j < assembly.components.length; j++) { + const result = checkInterference(assembly.components[i], assembly.components[j], options); if (result.interfering) out.push(result); + } + return out; +} + +export function assemblyToJSON(assembly) { return clone(assembly); } +function normalizeMesh(mesh) { + if (!mesh?.verts || !mesh?.tris) return null; + const values = value => Array.isArray(value) ? value : Object.values(value); + return { verts: Float32Array.from(values(mesh.verts)), tris: Uint32Array.from(values(mesh.tris)) }; +} + +export function assemblyFromJSON(data) { + const components = (data?.components || []).map(source => { + const component = clone(source); + component.mesh = normalizeMesh(source.mesh || component.mesh); + return component; + }); + return { ...createAssembly(data?.name), ...clone(data), components, mates: clone(data?.mates || []) }; +} diff --git a/js/features/chamfer.js b/js/features/chamfer.js new file mode 100644 index 0000000..e5284dc --- /dev/null +++ b/js/features/chamfer.js @@ -0,0 +1,189 @@ +// 受限的实体倒角:只处理方向一致的封闭凸 MeshData 上的一条直棱。 +// 不依赖 WASM;成功时返回新的 MeshData,所有数组所有权交给调用方。 + +const EPS = 1e-7; + +const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; +const add = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; +const mul = (a, s) => [a[0] * s, a[1] * s, a[2] * s]; +const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; +const length = a => Math.hypot(a[0], a[1], a[2]); +const norm = a => { const l = length(a); return l > EPS ? mul(a, 1 / l) : null; }; +const point = (verts, i) => [verts[i * 3], verts[i * 3 + 1], verts[i * 3 + 2]]; +const signedVolume = mesh => { let s = 0; for (let i = 0; i < mesh.tris.length; i += 3) { const a = point(mesh.verts, mesh.tris[i]), b = point(mesh.verts, mesh.tris[i + 1]), c = point(mesh.verts, mesh.tris[i + 2]); s += dot(a, cross(b, c)); } return s / 6; }; +const keyOf = (p, tol) => p.map(x => Math.round(x / tol)).join(','); +const sameNormal = (a, b, tol) => dot(a, b) > 1 - Math.max(tol * 10, 1e-6); + +function triangleNormal(mesh, triIndex) { + const a = point(mesh.verts, mesh.tris[triIndex]), b = point(mesh.verts, mesh.tris[triIndex + 1]), c = point(mesh.verts, mesh.tris[triIndex + 2]); + const n = norm(cross(sub(b, a), sub(c, a))); + if (!n) throw new Error('网格含退化三角形,不能创建倒角'); + return n; +} + +function assertMesh(mesh, tol) { + if (!mesh || !mesh.verts || !mesh.tris || mesh.verts.length < 12 || mesh.tris.length < 12 || mesh.verts.length % 3 || mesh.tris.length % 3) throw new Error('倒角需要封闭 MeshData 输入'); + if (!(tol > 0)) throw new Error('倒角容差必须为正数'); + const uses = new Map(); + for (let i = 0; i < mesh.tris.length; i += 3) { + const ids = [mesh.tris[i], mesh.tris[i + 1], mesh.tris[i + 2]]; + if (ids.some(id => !Number.isInteger(id) || id < 0 || id * 3 + 2 >= mesh.verts.length)) throw new Error('网格三角形索引无效'); + const ps = ids.map(id => point(mesh.verts, id)); + if (!norm(cross(sub(ps[1], ps[0]), sub(ps[2], ps[0])))) throw new Error('网格含退化三角形,不能创建倒角'); + for (let k = 0; k < 3; k++) { + const ka = keyOf(ps[k], tol), kb = keyOf(ps[(k + 1) % 3], tol); + const key = ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`; + const list = uses.get(key) || []; list.push(i); uses.set(key, list); + } + } + for (const [key, list] of uses) if (list.length !== 2) throw new Error(`网格不是封闭二流形,不能创建倒角(边 ${key} 邻接 ${list.length} 个面)`); + if (signedVolume(mesh) <= tol * tol * tol) throw new Error('网格面朝向不一致或体积退化,不能创建倒角'); +} + +/** + * 提取由量化几何端点和相邻三角形法向定义的棱边。 + * 仅暴露真实折棱;共面三角形的内部对角线会被过滤。 + */ +export function extractTopology(mesh, tolerance = 1e-6) { + assertMesh(mesh, tolerance); + const raw = new Map(); + for (let ti = 0; ti < mesh.tris.length; ti += 3) { + const n = triangleNormal(mesh, ti); + for (let k = 0; k < 3; k++) { + const ia = mesh.tris[ti + k], ib = mesh.tris[ti + (k + 1) % 3]; + const a = point(mesh.verts, ia), b = point(mesh.verts, ib); + const ka = keyOf(a, tolerance), kb = keyOf(b, tolerance); + const key = ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`; + const entries = raw.get(key) || []; + entries.push({ a, b, normal: n, triIndex: ti }); raw.set(key, entries); + } + } + const edges = []; + for (const [key, entries] of raw) { + if (entries.length !== 2) throw new Error('网格不是封闭二流形,无法提取棱边'); + if (sameNormal(entries[0].normal, entries[1].normal, tolerance)) continue; + const a = entries[0].a, b = entries[0].b, d = norm(sub(b, a)); + if (!d) continue; + edges.push({ key, a, b, direction: d, length: length(sub(b, a)), normals: [entries[0].normal, entries[1].normal], triangleIndices: [entries[0].triIndex, entries[1].triIndex] }); + } + return { mesh, tolerance, edges }; +} + +/** 用端点和两侧面法向生成可 JSON 序列化的稳定棱边引用。 */ +export function makeStableEdgeRef(edge, topology) { + if (!edge || !topology) throw new Error('缺少棱边或拓扑信息'); + const tol = topology.tolerance; + const endpoints = [edge.a, edge.b].map(p => p.slice()).sort((a, b) => keyOf(a, tol).localeCompare(keyOf(b, tol))); + const faceNormals = edge.normals.map(n => n.slice()).sort((a, b) => keyOf(a, tol).localeCompare(keyOf(b, tol))); + return { type: 'mesh-edge', version: 1, endpoints, faceNormals, length: edge.length, signature: `${keyOf(endpoints[0], tol)}|${keyOf(endpoints[1], tol)}|${faceNormals.map(n => keyOf(n, tol)).join('|')}` }; +} + +/** 重建后只能唯一匹配才解析,歧义或失效均明确报错。 */ +export function resolveEdgeRef(ref, topology) { + if (!ref || ref.type !== 'mesh-edge' || !Array.isArray(ref.endpoints) || !Array.isArray(ref.faceNormals)) throw new Error('棱边引用格式无效'); + const tol = topology.tolerance; + const matches = topology.edges.filter(edge => { + const e = makeStableEdgeRef(edge, topology); + if (typeof ref.signature === 'string') return e.signature === ref.signature; + return e.endpoints.every((p, i) => length(sub(p, ref.endpoints[i])) <= tol) && e.faceNormals.every((n, i) => sameNormal(n, ref.faceNormals[i], tol)); + }); + if (!matches.length) throw new Error('棱边引用已失效或目标边不存在'); + if (matches.length > 1) throw new Error('棱边引用不唯一,无法安全定位目标边'); + return matches[0]; +} + +function centroid(mesh) { const c = [0, 0, 0], n = mesh.verts.length / 3; for (let i = 0; i < n; i++) { c[0] += mesh.verts[i * 3]; c[1] += mesh.verts[i * 3 + 1]; c[2] += mesh.verts[i * 3 + 2]; } return mul(c, 1 / n); } +function projectionOnLine(p, a, d) { return add(a, mul(d, dot(sub(p, a), d))); } +function sideDirection(mesh, edge, normal) { + let best = null, bestDist = 0; + for (let i = 0; i < mesh.verts.length / 3; i++) { + const p = point(mesh.verts, i); + if (Math.abs(dot(normal, sub(p, edge.a))) > 1e-5) continue; + const v = sub(p, projectionOnLine(p, edge.a, edge.direction)), l = length(v); + if (l > bestDist) { best = v; bestDist = l; } + } + if (!best) throw new Error('无法确定棱边相邻面的可用宽度'); + return { direction: mul(best, 1 / bestDist), available: bestDist }; +} + +function unique(points, tol) { const out = []; const seen = new Set(); for (const p of points) { const k = keyOf(p, tol); if (!seen.has(k)) { seen.add(k); out.push(p); } } return out; } +function clipTriangle(tri, planePoint, planeNormal, retainedSign, tol, cuts) { + const value = p => retainedSign * dot(planeNormal, sub(p, planePoint)); + const out = []; + for (let i = 0; i < 3; i++) { + const a = tri[i], b = tri[(i + 1) % 3], va = value(a), vb = value(b); + const inA = va >= -tol, inB = vb >= -tol; + if (inA) out.push(a); + if (inA !== inB) { const t = va / (va - vb); const p = add(a, mul(sub(b, a), t)); out.push(p); cuts.push(p); } + } + return out; +} +function pushTriangle(out, a, b, c, tol) { if (length(cross(sub(b, a), sub(c, a))) > tol * tol) out.push(a, b, c); } +function capTriangles(cuts, planePoint, normal, desiredNormal, tol, out) { + const pts = unique(cuts, tol); + if (pts.length < 3) throw new Error('倒角截面退化,请减小倒角距离'); + // 不能假设第一个交点不同于 planePoint;直接从封口法向构造稳定平面基。 + const reference = Math.abs(desiredNormal[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0]; + const u = norm(cross(desiredNormal, reference)); + const v = u && norm(cross(desiredNormal, u)); + if (!u || !v) throw new Error('倒角截面退化,请减小倒角距离'); + const center = pts.reduce((s, p) => add(s, p), [0, 0, 0]).map(x => x / pts.length); + pts.sort((a, b) => Math.atan2(dot(sub(a, center), v), dot(sub(a, center), u)) - Math.atan2(dot(sub(b, center), v), dot(sub(b, center), u))); + const test = cross(sub(pts[1], pts[0]), sub(pts[2], pts[0])); + if (dot(test, desiredNormal) < 0) pts.reverse(); + // 中心扇形逐段复用裁剪边。原三角形对角线可能把封口边分成共线小段, + // 固定角点扇形会跳过这些零面积扇片并留下 T 型缝。 + for (let i = 0; i < pts.length; i++) pushTriangle(out, center, pts[i], pts[(i + 1) % pts.length], tol); +} + +/** + * 对一条凸直棱生成真实平面倒角。限制:整个输入必须为方向一致的封闭凸多面体; + * 一次只能处理一条棱,以避免端点处多倒角相交的组合拓扑问题。 + */ +export function buildChamferMesh({ mesh, edgeRefs, distance, tolerance = 1e-6 } = {}) { + const topology = extractTopology(mesh, tolerance); + if (!Array.isArray(edgeRefs) || edgeRefs.length !== 1) throw new Error('当前倒角仅支持选择一条凸直棱边'); + if (!(Number.isFinite(distance) && distance > tolerance)) throw new Error('倒角距离必须为大于容差的正数'); + const edge = edgeRefs[0].a ? edgeRefs[0] : resolveEdgeRef(edgeRefs[0], topology); + // 严格凸性验证:每个外向三角面外侧不得存在任何顶点。 + for (let ti = 0; ti < mesh.tris.length; ti += 3) { + const a = point(mesh.verts, mesh.tris[ti]), n = triangleNormal(mesh, ti); + for (let i = 0; i < mesh.verts.length / 3; i++) if (dot(n, sub(point(mesh.verts, i), a)) > tolerance * 8) throw new Error('当前倒角只支持凸实体;检测到凹边或非凸拓扑'); + } + const sa = sideDirection(mesh, edge, edge.normals[0]), sb = sideDirection(mesh, edge, edge.normals[1]); + if (distance >= Math.min(sa.available, sb.available) - tolerance) throw new Error('倒角距离过大,超过相邻面的可用宽度'); + const pa = add(edge.a, mul(sa.direction, distance)), pb = add(edge.a, mul(sb.direction, distance)); + let planeNormal = norm(cross(edge.direction, sub(pb, pa))); + if (!planeNormal) throw new Error('相邻面退化,无法生成倒角'); + const inside = centroid(mesh), retainedSign = dot(planeNormal, sub(inside, pa)) >= 0 ? 1 : -1; + const desiredCapNormal = mul(planeNormal, -retainedSign); + const triangles = [], cuts = []; + for (let ti = 0; ti < mesh.tris.length; ti += 3) { + const tri = [point(mesh.verts, mesh.tris[ti]), point(mesh.verts, mesh.tris[ti + 1]), point(mesh.verts, mesh.tris[ti + 2])]; + const poly = clipTriangle(tri, pa, planeNormal, retainedSign, tolerance, cuts); + // 也记录恰好落在裁剪面上的既有顶点,避免原三角形对角线把封口边拆分后出现 T 型缝。 + for (const p of poly) if (Math.abs(dot(planeNormal, sub(p, pa))) <= tolerance * 4) cuts.push(p); + for (let i = 1; i < poly.length - 1; i++) pushTriangle(triangles, poly[0], poly[i], poly[i + 1], tolerance); + } + capTriangles(cuts, pa, planeNormal, desiredCapNormal, tolerance, triangles); + if (!triangles.length) throw new Error('倒角结果为空,请检查目标边和距离'); + // 输出必须共享索引;逐三角形拆点即使坐标相同,也会被内核视为裂缝而拒绝。 + const vertices = [], indices = [], vertexByKey = new Map(); + const indexOf = p => { + const key = keyOf(p, tolerance); + const old = vertexByKey.get(key); + if (old !== undefined) return old; + const index = vertices.length / 3; + vertices.push(p[0], p[1], p[2]); vertexByKey.set(key, index); + return index; + }; + for (let i = 0; i < triangles.length; i += 3) { + const a = indexOf(triangles[i]), b = indexOf(triangles[i + 1]), c = indexOf(triangles[i + 2]); + if (a !== b && b !== c && c !== a) indices.push(a, b, c); + } + const verts = new Float32Array(vertices), tris = new Uint32Array(indices); + // 复用拓扑提取器验证每条几何边恰好被两个三角面使用。 + extractTopology({ verts, tris }, tolerance); + return { verts, tris, selectedEdges: [{ start: edge.a.slice(), end: edge.b.slice() }] }; +} diff --git a/js/features/constraints.js b/js/features/constraints.js new file mode 100644 index 0000000..9114b7e --- /dev/null +++ b/js/features/constraints.js @@ -0,0 +1,163 @@ +// 轻量 2D 参数化约束求解器。 +// 目标不是替代工业级 DCM,而是让常见课程草图在改尺寸后稳定重建。 + +const EPS = 1e-7; + +function num(v, fallback = 0) { + const n = Number(v); + return Number.isFinite(n) ? n : fallback; +} + +function entity(sk, id) { return sk.entities.find(e => String(e.id) === String(id)); } + +function point(sk, key) { + const m = String(key || '').match(/^(.+):(p0|p1|c)$/); + if (!m) return null; + const e = entity(sk, m[1]); + if (!e) return null; + if (m[2] === 'c') return e.c || null; + return m[2] === 'p0' ? (e.p1 || null) : (e.p2 || null); +} + +function lineLength(e) { return Math.hypot(e.p2[0] - e.p1[0], e.p2[1] - e.p1[1]); } +function normalize(x, y) { const l = Math.hypot(x, y) || 1; return [x / l, y / l]; } +function setLineLength(e, length, preserve = 'mid') { + const dx = e.p2[0] - e.p1[0], dy = e.p2[1] - e.p1[1]; + const d = normalize(dx, dy); + if (preserve === 'start') e.p2 = [e.p1[0] + d[0] * length, e.p1[1] + d[1] * length]; + else if (preserve === 'end') e.p1 = [e.p2[0] - d[0] * length, e.p2[1] - d[1] * length]; + else { + const mx = (e.p1[0] + e.p2[0]) / 2, my = (e.p1[1] + e.p2[1]) / 2; + e.p1 = [mx - d[0] * length / 2, my - d[1] * length / 2]; + e.p2 = [mx + d[0] * length / 2, my + d[1] * length / 2]; + } +} + +function targetOf(c, context) { + if (c.value != null) return evaluateExpression(c.value, context); + if (c.target != null) return evaluateExpression(c.target, context); + if (c.distance != null) return evaluateExpression(c.distance, context); + return null; +} + +export function evaluateExpression(value, context = {}) { + if (typeof value === 'number') return value; + if (value == null || value === '') return 0; + const text = String(value).trim().replace(/毫米|公厘|mm/gi, ''); + if (/^[A-Za-z_][\w.]*$/.test(text)) return num(context[text] ?? context.parameters?.[text] ?? context.dimensions?.[text], NaN); + if (!/^[0-9+\-*/(). _A-Za-z]+$/.test(text)) return NaN; + const names = { ...context.parameters, ...context.dimensions, ...context }; + const expr = text.replace(/[A-Za-z_]\w*/g, name => Number.isFinite(Number(names[name])) ? String(names[name]) : 'NaN'); + try { + // 表达式已通过白名单和变量替换;仅用于本地尺寸参数,不执行外部输入。 + const result = Function(`"use strict"; return (${expr})`)(); + return Number.isFinite(result) ? result : NaN; + } catch { return NaN; } +} + +export function resolveDimensionTable(dimensions = [], parameters = {}) { + const out = { ...parameters }; + const pending = [...dimensions]; + for (let pass = 0; pass < pending.length + 2; pass++) { + let changed = false; + for (const d of pending) { + const v = evaluateExpression(d.expression ?? d.value, { ...out, dimensions: out }); + if (Number.isFinite(v) && v > 0) { if (out[d.name] !== v) changed = true; out[d.name] = v; } + } + if (!changed) break; + } + return out; +} + +function residualFor(c, sk, ctx) { + const a = c.refs?.[0], b = c.refs?.[1]; + const ea = entity(sk, a), eb = entity(sk, b); + if (c.type === 'horizontal' && ea?.p1 && ea?.p2) return Math.abs(ea.p1[1] - ea.p2[1]); + if (c.type === 'vertical' && ea?.p1 && ea?.p2) return Math.abs(ea.p1[0] - ea.p2[0]); + if (c.type === 'coincident') { const p = point(sk, a), q = point(sk, b); return p && q ? Math.hypot(p[0] - q[0], p[1] - q[1]) : 1; } + if (c.type === 'equal' && ea && eb) { + const va = ea.r ?? (ea.p1 ? lineLength(ea) : 0), vb = eb.r ?? (eb.p1 ? lineLength(eb) : 0); return Math.abs(va - vb); + } + if (c.type === 'parallel' && ea?.p1 && ea?.p2 && eb?.p1 && eb?.p2) { + const da = normalize(ea.p2[0] - ea.p1[0], ea.p2[1] - ea.p1[1]); const db = normalize(eb.p2[0] - eb.p1[0], eb.p2[1] - eb.p1[1]); return Math.abs(da[0] * db[1] - da[1] * db[0]); + } + if (c.type === 'perpendicular' && ea?.p1 && ea?.p2 && eb?.p1 && eb?.p2) { + const da = normalize(ea.p2[0] - ea.p1[0], ea.p2[1] - ea.p1[1]); const db = normalize(eb.p2[0] - eb.p1[0], eb.p2[1] - eb.p1[1]); return Math.abs(da[0] * db[0] + da[1] * db[1]); + } + if (c.type === 'distance') { const p = point(sk, a), q = point(sk, b); const v = targetOf(c, ctx); return p && q && Number.isFinite(v) ? Math.abs(Math.hypot(q[0] - p[0], q[1] - p[1]) - v) : 1; } + if ((c.type === 'length' || c.type === 'distanceLength') && ea?.p1) { const v = targetOf(c, ctx); return Number.isFinite(v) ? Math.abs(lineLength(ea) - v) : 1; } + if (c.type === 'radius' || c.type === 'diameter') { const v = targetOf(c, ctx); const actual = c.type === 'diameter' ? (ea?.r || 0) * 2 : ea?.r || 0; return Number.isFinite(v) ? Math.abs(actual - v) : 1; } + if (c.type === 'tangent' && ea?.p1 && eb?.c && eb.r != null) { const dx = ea.p2[0] - ea.p1[0], dy = ea.p2[1] - ea.p1[1]; return Math.abs(Math.abs((eb.c[0] - ea.p1[0]) * dy - (eb.c[1] - ea.p1[1]) * dx) / (Math.hypot(dx, dy) || 1) - eb.r); } + if (c.type === 'angle' && ea?.p1 && ea?.p2) { const v = targetOf(c, ctx); return Number.isFinite(v) ? Math.abs(Math.atan2(ea.p2[1] - ea.p1[1], ea.p2[0] - ea.p1[0]) - v * Math.PI / 180) : 1; } + return 0; +} + +export function solveSketchConstraints(sketch, { dimensions = {}, parameters = {}, iterations = 60, tolerance = 1e-5 } = {}) { + const ctx = { ...parameters, ...dimensions, dimensions, parameters }; + const constraints = sketch.constraints || []; + const fixed = new Map(); + for (const c of constraints) { + if (c.type !== 'fixed') continue; + for (const ref of c.refs || []) { const p = point(sketch, ref); if (p) fixed.set(ref, p.slice()); } + } + let maxResidual = Infinity; + for (let pass = 0; pass < iterations; pass++) { + for (const c of constraints) { + const [a, b] = c.refs || []; + const ea = entity(sketch, a), eb = entity(sketch, b); + switch (c.type) { + case 'horizontal': if (ea?.p1 && ea?.p2) { const y = (ea.p1[1] + ea.p2[1]) / 2; ea.p1[1] = y; ea.p2[1] = y; } break; + case 'vertical': if (ea?.p1 && ea?.p2) { const x = (ea.p1[0] + ea.p2[0]) / 2; ea.p1[0] = x; ea.p2[0] = x; } break; + case 'coincident': { const p = point(sketch, a), q = point(sketch, b); if (p && q) { q[0] = p[0]; q[1] = p[1]; } break; } + case 'equal': if (ea && eb) { const va = ea.r ?? (ea.p1 ? lineLength(ea) : null); if (va != null) { if (eb.r != null) eb.r = va; else if (eb.p1) setLineLength(eb, va); } } break; + case 'parallel': if (ea?.p1 && ea?.p2 && eb?.p1 && eb?.p2) { const d = normalize(ea.p2[0] - ea.p1[0], ea.p2[1] - ea.p1[1]); const l = lineLength(eb); eb.p2 = [eb.p1[0] + d[0] * l, eb.p1[1] + d[1] * l]; } break; + case 'perpendicular': if (ea?.p1 && ea?.p2 && eb?.p1 && eb?.p2) { const d = normalize(ea.p2[0] - ea.p1[0], ea.p2[1] - ea.p1[1]); const l = lineLength(eb); eb.p2 = [eb.p1[0] - d[1] * l, eb.p1[1] + d[0] * l]; } break; + case 'distance': { const p = point(sketch, a), q = point(sketch, b), v = targetOf(c, ctx); if (p && q && Number.isFinite(v)) { const d = normalize(q[0] - p[0], q[1] - p[1]); q[0] = p[0] + d[0] * v; q[1] = p[1] + d[1] * v; } break; } + case 'length': case 'distanceLength': { const v = targetOf(c, ctx); if (ea?.p1 && Number.isFinite(v)) setLineLength(ea, v); break; } + case 'radius': case 'diameter': { const v = targetOf(c, ctx); if (ea && Number.isFinite(v)) ea.r = c.type === 'diameter' ? v / 2 : v; break; } + case 'tangent': if (ea?.p1 && eb?.c && eb.r != null) { const dx = ea.p2[0] - ea.p1[0], dy = ea.p2[1] - ea.p1[1], l = Math.hypot(dx, dy) || 1, n = [-dy / l, dx / l]; const side = ((eb.c[0] - ea.p1[0]) * n[0] + (eb.c[1] - ea.p1[1]) * n[1]) >= 0 ? 1 : -1; eb.c[0] = ea.p1[0] + n[0] * eb.r * side; eb.c[1] = ea.p1[1] + n[1] * eb.r * side; } break; + case 'angle': { const v = targetOf(c, ctx); if (ea?.p1 && ea?.p2 && Number.isFinite(v)) { const l = lineLength(ea), a0 = v * Math.PI / 180; ea.p2 = [ea.p1[0] + l * Math.cos(a0), ea.p1[1] + l * Math.sin(a0)]; } break; } + case 'fixed': break; + } + } + for (const [key, p] of fixed) { const q = point(sketch, key); if (q) { q[0] = p[0]; q[1] = p[1]; } } + maxResidual = constraints.reduce((m, c) => Math.max(m, residualFor(c, sketch, ctx)), 0); + if (maxResidual <= tolerance) break; + } + const conflicts = detectConflicts(constraints, ctx); + const status = conflicts.length ? 'over-constrained' : constraints.length === 0 ? 'under-constrained' : maxResidual <= tolerance ? 'solved' : 'under-constrained'; + return { status, residual: maxResidual, iterations, constrained: constraints.length, dof: estimateDof(sketch, constraints), overconstrained: conflicts.length > 0, conflicts }; +} + +function estimateDof(sketch, constraints) { + let vars = 0; + for (const e of sketch.entities || []) vars += e.type === 'circle' || e.type === 'arc' || e.type === 'ellipse' ? 3 : e.type === 'line' || e.type === 'centerline' ? 4 : 2; + return Math.max(0, vars - constraints.length); +} + +function detectConflicts(constraints, ctx) { + const seen = new Map(), out = []; + for (const c of constraints) { + if (!['length', 'distanceLength', 'radius', 'diameter', 'distance', 'angle'].includes(c.type)) continue; + const k = c.type + ':' + JSON.stringify(c.refs || []); + const v = targetOf(c, ctx); + if (!Number.isFinite(v)) continue; + if (seen.has(k) && Math.abs(seen.get(k) - v) > 1e-6) out.push({ refs: c.refs, type: c.type, values: [seen.get(k), v] }); + else seen.set(k, v); + } + return out; +} + +export function constraintSummary(sketch, options = {}) { + const report = solveSketchConstraints(sketch, options); + return { ...report, total: (sketch.constraints || []).length }; +} + +export function resolveParamExpressions(value, context) { + if (Array.isArray(value)) return value.map(v => resolveParamExpressions(v, context)); + if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, resolveParamExpressions(v, context)])); + if (typeof value === 'string' && /^[A-Za-z_]\w*$/.test(value)) { + const n = evaluateExpression(value, context); return Number.isFinite(n) ? n : value; + } + return value; +} diff --git a/js/features/drawing.js b/js/features/drawing.js new file mode 100644 index 0000000..7e24bec --- /dev/null +++ b/js/features/drawing.js @@ -0,0 +1,83 @@ +// 工程图基础模块:从三角网格生成可编辑的 SVG 三视图、尺寸和剖视数据。 + +const SHEETS = { A4: { width: 297, height: 210 }, A3: { width: 420, height: 297 } }; +function clone(v) { return JSON.parse(JSON.stringify(v)); } +function key(a, b) { return a < b ? a + ':' + b : b + ':' + a; } + +export function createDrawing({ name = '未命名工程图', format = 'A4', orientation = 'landscape', scale = 1, units = 'mm' } = {}) { + const base = SHEETS[format] || SHEETS.A4; + const sheet = orientation === 'portrait' ? { width: base.height, height: base.width } : { ...base }; + return { version: 1, name, format, orientation, scale, units, sheet, views: [], dimensions: [], notes: [], title: { partName: '', material: '', author: '', revision: 'A' } }; +} + +function vertices(mesh) { const out = []; for (let i = 0; i < mesh.verts.length; i += 3) out.push([mesh.verts[i], mesh.verts[i + 1], mesh.verts[i + 2]]); return out; } +function projectPoint(p, view) { if (view === 'top') return [p[0], p[2]]; if (view === 'left') return [-p[1], p[2]]; return [p[0], p[1]]; } + +export function projectMesh(mesh, view = 'front', { hidden = false } = {}) { + if (!mesh?.verts || !mesh?.tris) return { view, segments: [], bounds: null }; + const vs = vertices(mesh), segments = [], seen = new Set(), edgeSet = new Set(); + for (let i = 0; i < mesh.tris.length; i += 3) for (const pair of [[mesh.tris[i], mesh.tris[i + 1]], [mesh.tris[i + 1], mesh.tris[i + 2]], [mesh.tris[i + 2], mesh.tris[i]]]) edgeSet.add(key(pair[0], pair[1])); + for (const ek of edgeSet) { + const [a, b] = ek.split(':').map(Number), pa = projectPoint(vs[a], view), pb = projectPoint(vs[b], view); + const sig = [pa, pb].flat().map(n => Number(n).toFixed(5)).join(','), rev = [pb, pa].flat().map(n => Number(n).toFixed(5)).join(','); + if (seen.has(sig) || seen.has(rev)) continue; + seen.add(sig); seen.add(rev); segments.push({ a: pa, b: pb, hidden: !!hidden }); + } + const pts = segments.flatMap(s => [s.a, s.b]); + const bounds = pts.length ? { min: [Math.min(...pts.map(p => p[0])), Math.min(...pts.map(p => p[1]))], max: [Math.max(...pts.map(p => p[0])), Math.max(...pts.map(p => p[1]))] } : null; + return { view, segments, bounds }; +} + +export function layoutThreeViews(mesh, { scale = 1, origin = [148.5, 105] } = {}) { + return [ + { id: 'front', name: '主视图', view: 'front', x: origin[0] - 45, y: origin[1] + 35, scale, projection: projectMesh(mesh, 'front') }, + { id: 'top', name: '俯视图', view: 'top', x: origin[0] - 45, y: origin[1] - 35, scale, projection: projectMesh(mesh, 'top') }, + { id: 'left', name: '左视图', view: 'left', x: origin[0] + 55, y: origin[1] + 35, scale, projection: projectMesh(mesh, 'left') }, + ]; +} + +export function addView(drawing, view) { drawing.views = drawing.views.filter(v => v.id !== view.id); drawing.views.push(clone(view)); return view; } +export function addDimension(drawing, data) { const d = { id: data.id || 'dim-' + (drawing.dimensions.length + 1), kind: data.kind || 'linear', value: Number(data.value), text: data.text || String(data.value), a: data.a || [0, 0], b: data.b || [0, 0], tolerance: data.tolerance || '' }; drawing.dimensions.push(d); return d; } +export function addNote(drawing, text, position = [0, 0]) { const n = { id: 'note-' + (drawing.notes.length + 1), text: String(text), position: position.slice() }; drawing.notes.push(n); return n; } +function sectionProjection(mesh, plane) { + if (!mesh?.verts || !mesh?.tris) return { view: 'section', segments: [], bounds: null }; + const o = plane.origin || [0, 0, 0], n = plane.normal || [1, 0, 0], vs = vertices(mesh), segments = []; + const signed = p => (p[0] - o[0]) * n[0] + (p[1] - o[1]) * n[1] + (p[2] - o[2]) * n[2]; + const project = p => n[0] ? [p[1], p[2]] : n[1] ? [p[0], p[2]] : [p[0], p[1]]; + for (let i = 0; i < mesh.tris.length; i += 3) { + const tri = [vs[mesh.tris[i]], vs[mesh.tris[i + 1]], vs[mesh.tris[i + 2]]], hits = []; + for (let j = 0; j < 3; j++) { const a = tri[j], b = tri[(j + 1) % 3], sa = signed(a), sb = signed(b); if (Math.abs(sa) < 1e-6) hits.push(a); if (sa * sb < -1e-12) { const t = sa / (sa - sb); hits.push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t]); } } + if (hits.length >= 2) segments.push({ a: project(hits[0]), b: project(hits[1]), hidden: false }); + } + const pts = segments.flatMap(s => [s.a, s.b]); + return { view: 'section', segments, bounds: pts.length ? { min: [Math.min(...pts.map(p => p[0])), Math.min(...pts.map(p => p[1]))], max: [Math.max(...pts.map(p => p[0])), Math.max(...pts.map(p => p[1]))] } : null }; +} +export function addSectionView(drawing, { id = 'section-A', name = 'A-A 剖视图', plane = { origin: [0, 0, 0], normal: [1, 0, 0] }, hatch = true, mesh = null } = {}) { const view = { id, name, kind: 'section', plane: clone(plane), hatch: !!hatch, status: 'linked', projection: sectionProjection(mesh, plane) }; drawing.views.push(view); return view; } + +function esc(s) { return String(s).replace(/[&<>"']/g, function(c) { return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]; }); } +function mapPoint(view, p) { return [view.x + p[0] * view.scale, view.y - p[1] * view.scale]; } + +export function renderDrawingSVG(drawing) { + const width = drawing.sheet.width, height = drawing.sheet.height; + const lines = ['', '', '']; + for (const view of drawing.views || []) { + if (!view.projection || !view.projection.segments?.length) { + if (view.kind === 'section') lines.push('A-A 剖视图(关联视图)'); + continue; + } + lines.push('' + esc(view.name || view.id) + ''); + for (const s of view.projection.segments || []) { const a = mapPoint(view, s.a), b = mapPoint(view, s.b); lines.push(''); } + if (view.kind === 'section' && view.hatch && view.projection.bounds) { + const b = view.projection.bounds; + for (let x = b.min[0]; x < b.max[0] + (b.max[1] - b.min[1]); x += 4) { const a = mapPoint(view, [x, b.min[1]]), c = mapPoint(view, [x - (b.max[1] - b.min[1]), b.max[1]]); lines.push(''); } + } + } + for (const d of drawing.dimensions || []) { const x1 = d.a[0], y1 = d.a[1], x2 = d.b[0], y2 = d.b[1]; lines.push('' + esc(d.text + (d.tolerance ? ' ' + d.tolerance : '')) + ''); } + for (const n of drawing.notes || []) lines.push('' + esc(n.text) + ''); + const t = drawing.title || {}, tx = width - 95, ty = height - 35; + lines.push('零件:' + esc(t.partName || drawing.name) + '材料:' + esc(t.material || '—') + '比例:' + esc(String(drawing.scale)) + ' 单位:' + esc(drawing.units) + '版本:' + esc(t.revision || 'A') + ''); + lines.push(''); return lines.join(''); +} + +export function drawingToJSON(drawing) { return clone(drawing); } +export function drawingFromJSON(data) { return { ...createDrawing(data || {}), ...clone(data || {}), views: clone(data?.views || []), dimensions: clone(data?.dimensions || []), notes: clone(data?.notes || []) }; } diff --git a/js/features/fillet.js b/js/features/fillet.js new file mode 100644 index 0000000..afcf066 --- /dev/null +++ b/js/features/fillet.js @@ -0,0 +1,163 @@ +// 受限的实体圆角:只处理方向一致的闭合凸 MeshData 上的一条直棱。 +// 通过连续的切平面构造圆柱面分段逼近;成功时返回的新 MeshData 所有权交给调用方。 +import { extractTopology, resolveEdgeRef } from './chamfer.js'; + +const EPS = 1e-7; +const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; +const add = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; +const mul = (a, s) => [a[0] * s, a[1] * s, a[2] * s]; +const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; +const len = a => Math.hypot(a[0], a[1], a[2]); +const unit = a => { const l = len(a); return l > EPS ? mul(a, 1 / l) : null; }; +const at = (verts, i) => [verts[i * 3], verts[i * 3 + 1], verts[i * 3 + 2]]; +const key = (p, tol) => p.map(v => Math.round(v / tol)).join(','); + +function triNormal(mesh, offset) { + const a = at(mesh.verts, mesh.tris[offset]), b = at(mesh.verts, mesh.tris[offset + 1]), c = at(mesh.verts, mesh.tris[offset + 2]); + const n = unit(cross(sub(b, a), sub(c, a))); + if (!n) throw new Error('网格含退化三角形,无法创建圆角'); + return n; +} + +function assertConvex(mesh, tolerance) { + // extractTopology 同时检查闭合、朝向和正体积;这里补上严格凸性。 + extractTopology(mesh, tolerance); + const count = mesh.verts.length / 3; + for (let ti = 0; ti < mesh.tris.length; ti += 3) { + const a = at(mesh.verts, mesh.tris[ti]), n = triNormal(mesh, ti); + for (let i = 0; i < count; i++) { + if (dot(n, sub(at(mesh.verts, i), a)) > tolerance * 8) { + throw new Error('当前圆角只支持闭合凸实体;检测到凹边或非凸拓扑'); + } + } + } +} + +function availableFaceWidth(mesh, edge, normal) { + let available = 0; + for (let i = 0; i < mesh.verts.length / 3; i++) { + const p = at(mesh.verts, i); + if (Math.abs(dot(normal, sub(p, edge.a))) > 1e-5) continue; + const along = dot(sub(p, edge.a), edge.direction); + const off = sub(sub(p, edge.a), mul(edge.direction, along)); + available = Math.max(available, len(off)); + } + return available; +} + +function addTriangle(list, a, b, c, tolerance) { + if (len(cross(sub(b, a), sub(c, a))) > tolerance * tolerance) list.push(a, b, c); +} + +function dedupe(points, tolerance) { + const seen = new Map(); + for (const p of points) seen.set(key(p, tolerance), p); + return [...seen.values()]; +} + +function clipPolygon(points, planePoint, normal, tolerance, cutPoints) { + // 保留 dot(normal, p-planePoint) <= 0 一侧;normal 是新圆角面的外法向。 + const out = []; + const value = p => dot(normal, sub(p, planePoint)); + for (let i = 0; i < points.length; i++) { + const a = points[i], b = points[(i + 1) % points.length]; + const va = value(a), vb = value(b); + const inA = va <= tolerance, inB = vb <= tolerance; + if (inA) out.push(a); + if (inA !== inB) { + const t = va / (va - vb); + const p = add(a, mul(sub(b, a), t)); + out.push(p); cutPoints.push(p); + } + } + for (const p of out) if (Math.abs(value(p)) <= tolerance * 4) cutPoints.push(p); + return out; +} + +function capLoop(points, normal, tolerance, triangles) { + const loop = dedupe(points, tolerance); + if (loop.length < 3) throw new Error('圆角截面退化,请减小半径'); + const center = loop.reduce((s, p) => add(s, p), [0, 0, 0]).map(v => v / loop.length); + let u = unit(sub(loop[0], center)); + if (!u) u = unit(sub(loop[1], center)); + const v = u && unit(cross(normal, u)); + if (!u || !v) throw new Error('圆角截面退化,请减小半径'); + loop.sort((a, b) => Math.atan2(dot(sub(a, center), v), dot(sub(a, center), u)) - Math.atan2(dot(sub(b, center), v), dot(sub(b, center), u))); + // u × (normal × u) 指向 normal,故此绕序直接是外向。 + for (let i = 0; i < loop.length; i++) addTriangle(triangles, center, loop[i], loop[(i + 1) % loop.length], tolerance); +} + +function indexMesh(triangles, tolerance) { + const verts = [], tris = [], byKey = new Map(); + const indexOf = p => { + const k = key(p, tolerance); + const old = byKey.get(k); + if (old !== undefined) return old; + const i = verts.length / 3; + verts.push(p[0], p[1], p[2]); byKey.set(k, i); + return i; + }; + for (let i = 0; i < triangles.length; i += 3) { + const a = indexOf(triangles[i]), b = indexOf(triangles[i + 1]), c = indexOf(triangles[i + 2]); + if (a !== b && b !== c && c !== a) tris.push(a, b, c); + } + // Float32 落盘后,接近共线的小扇片可能在舍入时退化;在最终精度上再过滤一次。 + const finalVerts = new Float32Array(verts), finalTris = []; + for (let i = 0; i < tris.length; i += 3) { + const a = at(finalVerts, tris[i]), b = at(finalVerts, tris[i + 1]), c = at(finalVerts, tris[i + 2]); + if (len(cross(sub(b, a), sub(c, a))) > Math.max(tolerance * tolerance * 4, EPS * 2)) finalTris.push(tris[i], tris[i + 1], tris[i + 2]); + } + return { verts: finalVerts, tris: new Uint32Array(finalTris) }; +} + +function cutConvexMesh(mesh, planePoint, normal, tolerance) { + const triangles = [], cuts = []; + for (let ti = 0; ti < mesh.tris.length; ti += 3) { + const poly = clipPolygon([at(mesh.verts, mesh.tris[ti]), at(mesh.verts, mesh.tris[ti + 1]), at(mesh.verts, mesh.tris[ti + 2])], planePoint, normal, tolerance, cuts); + for (let i = 1; i < poly.length - 1; i++) addTriangle(triangles, poly[0], poly[i], poly[i + 1], tolerance); + } + if (cuts.length < 3) throw new Error('圆角半径或目标棱无效,未形成可封闭截面'); + capLoop(cuts, normal, tolerance, triangles); + return indexMesh(triangles, tolerance); +} + +function arcNormal(n1, n2, fraction) { + const cosine = Math.max(-1, Math.min(1, dot(n1, n2))); + const angle = Math.acos(cosine); + const sine = Math.sin(angle); + if (sine < 1e-6) throw new Error('相邻面夹角退化,无法创建圆角'); + return unit(add(mul(n1, Math.sin((1 - fraction) * angle) / sine), mul(n2, Math.sin(fraction * angle) / sine))); +} + +/** + * 创建一条凸直棱的恒定半径圆角。圆柱面由 segments 个相切平面构成, + * 因而导出的 STL 保留真实、多法向的 R 过渡面(不是光照平滑)。 + */ +export function buildFilletMesh({ mesh, edgeRefs, radius, segments = 8, tolerance = 1e-6 } = {}) { + if (!(Number.isFinite(tolerance) && tolerance > 0)) throw new Error('圆角容差必须为正数'); + if (!Array.isArray(edgeRefs) || edgeRefs.length !== 1) throw new Error('当前圆角仅支持选择一条凸直棱边'); + if (!(Number.isFinite(radius) && radius > tolerance)) throw new Error('圆角半径必须为大于容差的正数'); + if (!Number.isInteger(segments) || segments < 2 || segments > 48) throw new Error('圆角分段数必须是 2 到 48 的整数'); + + assertConvex(mesh, tolerance); + const topology = extractTopology(mesh, tolerance); + const edge = edgeRefs[0] && edgeRefs[0].a ? edgeRefs[0] : resolveEdgeRef(edgeRefs[0], topology); + const [n1, n2] = edge.normals; + const cosine = dot(n1, n2); + if (cosine <= -0.999 || cosine >= 0.999999) throw new Error('相邻面夹角退化,无法创建圆角'); + const width = Math.min(availableFaceWidth(mesh, edge, n1), availableFaceWidth(mesh, edge, n2)); + if (!(width > tolerance) || radius >= width - tolerance) throw new Error('圆角半径过大,超过相邻面的可用宽度'); + + // q 是从原棱到圆心线的位移。其到两张原面的有符号距离均为 -radius。 + const centerShift = mul(add(n1, n2), -radius / (1 + cosine)); + let out = mesh; + for (let i = 0; i < segments; i++) { + const normal = arcNormal(n1, n2, (i + 0.5) / segments); + const planePoint = add(add(edge.a, centerShift), mul(normal, radius)); + out = cutConvexMesh(out, planePoint, normal, tolerance); + } + // 每一步都保持闭合;末次再检查能在 MeshData 层提前给出明确诊断。 + extractTopology(out, tolerance); + return { ...out, selectedEdges: [{ start: edge.a.slice(), end: edge.b.slice() }], radius, segments }; +} diff --git a/js/features/loft.js b/js/features/loft.js new file mode 100644 index 0000000..c715e4d --- /dev/null +++ b/js/features/loft.js @@ -0,0 +1,182 @@ +/** + * 放样的纯 MeshData 构建器。输入中的数组均使用 mm;返回值所有权交给调用方。 + * 不创建 Manifold/WASM 对象,因此没有 delete 生命周期要求。 + */ + +const EPS = 1e-6; + +const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; +const add = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; +const scale = (a, n) => [a[0] * n, a[1] * n, a[2] * n]; +const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; +const len = a => Math.hypot(a[0], a[1], a[2]); +const norm = a => { const n = len(a); if (n < EPS) throw new Error('放样截面法向量退化'); return scale(a, 1 / n); }; +const centroid = pts => scale(pts.reduce((s, p) => add(s, p), [0, 0, 0]), 1 / pts.length); + +function newell(points) { + const n = [0, 0, 0]; + for (let i = 0; i < points.length; i++) { + const a = points[i], b = points[(i + 1) % points.length]; + n[0] += (a[1] - b[1]) * (a[2] + b[2]); + n[1] += (a[2] - b[2]) * (a[0] + b[0]); + n[2] += (a[0] - b[0]) * (a[1] + b[1]); + } + return n; +} + +function profilePoints(profile) { + const raw = Array.isArray(profile) ? profile : profile?.points; + if (!Array.isArray(raw) || raw.length < 3) throw new Error('放样截面至少需要三个顶点'); + const plane = !Array.isArray(profile) ? profile?.plane : null; + const out = raw.map((p) => { + if (!Array.isArray(p) || p.some(v => !Number.isFinite(Number(v)))) throw new Error('放样截面包含无效坐标'); + if (p.length >= 3) return [Number(p[0]), Number(p[1]), Number(p[2])]; + if (p.length === 2 && plane?.origin && plane?.xAxis && plane?.yAxis) { + return add(add(plane.origin, scale(plane.xAxis, Number(p[0]))), scale(plane.yAxis, Number(p[1]))); + } + throw new Error('二维截面必须提供 plane.origin、xAxis 和 yAxis'); + }); + if (len(sub(out[0], out[out.length - 1])) < EPS) out.pop(); + if (out.length < 3) throw new Error('放样截面退化,至少需要三个不同顶点'); + return out; +} + +function basis(points) { + let rawNormal = newell(points); + // 交叉的平面多边形可能 Newell 法向量相消;仍先建立投影基以给出“自交”诊断。 + if (len(rawNormal) < EPS) { + const origin0 = points[0]; + for (let i = 1; i < points.length - 1 && len(rawNormal) < EPS; i++) rawNormal = cross(sub(points[i], origin0), sub(points[i + 1], origin0)); + } + const n = norm(rawNormal); + const origin = points[0]; + let u = sub(points[1], origin); + u = norm(sub(u, scale(n, dot(u, n)))); + return { origin, u, v: cross(n, u), n }; +} + +function as2d(points, b) { return points.map(p => { const q = sub(p, b.origin); return [dot(q, b.u), dot(q, b.v)]; }); } +function area2d(points) { return points.reduce((s, p, i) => { const q = points[(i + 1) % points.length]; return s + p[0] * q[1] - q[0] * p[1]; }, 0) / 2; } +function orient(a, b, c) { return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); } +function onSegment(a, b, p) { return Math.min(a[0], b[0]) - EPS <= p[0] && p[0] <= Math.max(a[0], b[0]) + EPS && Math.min(a[1], b[1]) - EPS <= p[1] && p[1] <= Math.max(a[1], b[1]) + EPS; } +function intersects(a, b, c, d) { + const abC = orient(a, b, c), abD = orient(a, b, d), cdA = orient(c, d, a), cdB = orient(c, d, b); + if (Math.abs(abC) < EPS && onSegment(a, b, c)) return true; + if (Math.abs(abD) < EPS && onSegment(a, b, d)) return true; + if (Math.abs(cdA) < EPS && onSegment(c, d, a)) return true; + if (Math.abs(cdB) < EPS && onSegment(c, d, b)) return true; + return (abC > 0) !== (abD > 0) && (cdA > 0) !== (cdB > 0); +} +function validatePolygon(points) { + const b = basis(points), p = as2d(points, b); + for (const point of points) { + if (Math.abs(dot(sub(point, b.origin), b.n)) > 1e-5) throw new Error('放样截面必须共面'); + } + for (let i = 0; i < p.length; i++) for (let j = i + 1; j < p.length; j++) { + if (i === j || (i + 1) % p.length === j || (j + 1) % p.length === i) continue; + if (intersects(p[i], p[(i + 1) % p.length], p[j], p[(j + 1) % p.length])) throw new Error('放样截面存在自交'); + } + if (Math.abs(area2d(p)) < EPS) throw new Error('放样截面面积趋近于零'); + return b; +} + +/** 按闭合轮廓的均匀弧长重采样;保留输入点的三维坐标。 */ +export function resampleClosedProfile(points, count) { + if (!Number.isInteger(count) || count < 3) throw new Error('重采样顶点数至少为 3'); + const src = profilePoints(points); + validatePolygon(src); + const lengths = [0]; + for (let i = 0; i < src.length; i++) lengths.push(lengths[i] + len(sub(src[(i + 1) % src.length], src[i]))); + const total = lengths[lengths.length - 1]; + if (total < EPS) throw new Error('放样截面周长趋近于零'); + const out = []; + for (let k = 0; k < count; k++) { + const d = total * k / count; + let i = 0; while (i + 1 < lengths.length && lengths[i + 1] < d - EPS) i++; + const edge = lengths[i + 1] - lengths[i]; + const t = edge < EPS ? 0 : (d - lengths[i]) / edge; + const a = src[i], b = src[(i + 1) % src.length]; + out.push(add(a, scale(sub(b, a), t))); + } + return out; +} + +/** 将 candidate 循环移位到与 reference 的对应距离最小;返回新数组,不修改输入。 */ +export function alignProfileStart(reference, candidate) { + if (!Array.isArray(reference) || !Array.isArray(candidate) || reference.length !== candidate.length) throw new Error('起点对齐要求两个截面顶点数相同'); + let best = null, bestCost = Infinity; + for (const reverse of [false, true]) { + const c = reverse ? candidate.slice().reverse() : candidate; + for (let shift = 0; shift < c.length; shift++) { + let cost = 0; + for (let i = 0; i < c.length; i++) { const d = sub(reference[i], c[(i + shift) % c.length]); cost += dot(d, d); } + if (cost < bestCost) { bestCost = cost; best = c.map((_, i) => c[(i + shift) % c.length].slice()); } + } + } + return best; +} + +function pointInTriangle(p, a, b, c) { + const s1 = orient(a, b, p), s2 = orient(b, c, p), s3 = orient(c, a, p); + return s1 >= -EPS && s2 >= -EPS && s3 >= -EPS; +} +function triangulate(points) { + const p = as2d(points, basis(points)); + const ccw = area2d(p) > 0; + const idx = Array.from({ length: p.length }, (_, i) => i); + const tris = []; + while (idx.length > 3) { + let clipped = false; + for (let k = 0; k < idx.length; k++) { + const a = idx[(k - 1 + idx.length) % idx.length], b = idx[k], c = idx[(k + 1) % idx.length]; + if ((orient(p[a], p[b], p[c]) > EPS) !== ccw) continue; + if (idx.some(x => x !== a && x !== b && x !== c && pointInTriangle(p[x], p[a], p[b], p[c]))) continue; + tris.push(a, b, c); idx.splice(k, 1); clipped = true; break; + } + if (!clipped) throw new Error('放样截面无法三角剖分'); + } + tris.push(idx[0], idx[1], idx[2]); + return tris; +} + +/** + * 构建封闭放样 MeshData。profiles 可为 ProfileData(二维点 + plane)或三维点数组。 + * alignment 默认为 'auto';传 'none' 保留各截面的起点。capStart/capEnd 默认 true。 + */ +export function buildLoftMesh({ profiles, resampleCount, capStart = true, capEnd = true, alignment = 'auto' } = {}) { + if (!Array.isArray(profiles) || profiles.length < 2) throw new Error('放样至少需要两个截面'); + let rings = profiles.map(profilePoints); + const counts = rings.map(r => r.length); + const count = resampleCount == null ? Math.max(...counts) : Number(resampleCount); + if (!Number.isInteger(count) || count < 3 || count > 2048) throw new Error('放样重采样顶点数必须在 3 到 2048 之间'); + rings = rings.map(r => r.length === count ? r.map(p => p.slice()) : resampleClosedProfile(r, count)); + const bases = rings.map(validatePolygon); + const centers = rings.map(centroid); + const travel = sub(centers[1], centers[0]); + if (len(travel) < EPS) throw new Error('相邻放样截面共面或重复'); + const direction = norm(travel); + for (let i = 0; i < rings.length; i++) { + const b = bases[i]; + if (1 - Math.abs(dot(b.n, bases[0].n)) > 1e-5) throw new Error('放样截面必须彼此平行'); + if (Math.abs(dot(b.n, direction)) < 1e-4) throw new Error('放样截面必须彼此平行'); + if (i && dot(sub(centers[i], centers[i - 1]), direction) < EPS) throw new Error('放样截面顺序错误或存在共面重复截面'); + if (dot(b.n, direction) < 0) rings[i].reverse(); + if (i && alignment !== 'none') rings[i] = alignProfileStart(rings[i - 1], rings[i]); + } + // 避免截面之间的侧壁整体反向/折返。完全的曲面自交需要更重的三角形相交算法,先诊断常见翻转。 + for (let i = 0; i < rings.length - 1; i++) for (let j = 0; j < count; j++) { + const a = rings[i][j], b = rings[i][(j + 1) % count], c = rings[i + 1][j]; + if (len(cross(sub(b, a), sub(c, a))) < EPS) throw new Error('放样生成体存在退化或翻转侧壁'); + } + const verts = new Float32Array(rings.length * count * 3); + rings.forEach((ring, i) => ring.forEach((p, j) => { const k = (i * count + j) * 3; verts[k] = p[0]; verts[k + 1] = p[1]; verts[k + 2] = p[2]; })); + const tris = []; + for (let i = 0; i < rings.length - 1; i++) for (let j = 0; j < count; j++) { + const a = i * count + j, b = i * count + (j + 1) % count, c = (i + 1) * count + j, d = (i + 1) * count + (j + 1) % count; + tris.push(a, b, d, a, d, c); + } + if (capStart) { const cap = triangulate(rings[0]); for (let i = 0; i < cap.length; i += 3) tris.push(cap[i + 2], cap[i + 1], cap[i]); } + if (capEnd) { const cap = triangulate(rings[rings.length - 1]); const base = (rings.length - 1) * count; for (let i = 0; i < cap.length; i += 3) tris.push(base + cap[i], base + cap[i + 1], base + cap[i + 2]); } + return { verts, tris: new Uint32Array(tris) }; +} diff --git a/js/features/reference.js b/js/features/reference.js new file mode 100644 index 0000000..78c8128 --- /dev/null +++ b/js/features/reference.js @@ -0,0 +1,162 @@ +// M06 参考几何:只处理可 JSON 序列化的普通数据,不持有文档或 WASM 对象。 +const EPS = 1e-9; + +const isVec3 = v => Array.isArray(v) && v.length === 3 && v.every(Number.isFinite); +const copy3 = v => [v[0], v[1], v[2]]; +const add = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; +const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; +const mul = (v, n) => [v[0] * n, v[1] * n, v[2] * n]; +const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; +const length = v => Math.sqrt(dot(v, v)); + +function vector(value, label) { + if (!isVec3(value)) throw new Error(`${label}必须是三个有限数值组成的向量`); + return copy3(value); +} + +function unit(value, label) { + const v = vector(value, label), n = length(v); + if (n < EPS) throw new Error(`${label}不能是零向量`); + return mul(v, 1 / n); +} + +function pointOf(value, label = '点') { + if (isVec3(value)) return copy3(value); + if (value && isVec3(value.position)) return copy3(value.position); + if (value && isVec3(value.origin)) return copy3(value.origin); + throw new Error(`${label}缺少有效位置`); +} + +function sourceIds(...values) { + return values.map(v => v && v.id).filter(v => v !== undefined && v !== null); +} + +function planeBasis(plane) { + if (!plane || plane.type !== 'plane') throw new Error('需要有效的基准面'); + const normal = unit(plane.normal, '基准面法向'); + let xAxis = plane.xAxis ? vector(plane.xAxis, '基准面x轴') : [1, 0, 0]; + xAxis = sub(xAxis, mul(normal, dot(xAxis, normal))); + if (length(xAxis) < EPS) { + const fallback = Math.abs(normal[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0]; + xAxis = cross(fallback, normal); + } + return { origin: pointOf(plane, '基准面原点'), normal, xAxis: unit(xAxis, '基准面x轴') }; +} + +function axisData(axis) { + if (!axis || axis.type !== 'axis') throw new Error('需要有效的基准轴'); + return { origin: pointOf(axis, '基准轴原点'), direction: unit(axis.direction, '基准轴方向') }; +} + +function rotateAroundAxis(point, axis, radians) { + const p = sub(point, axis.origin), k = axis.direction; + const c = Math.cos(radians), s = Math.sin(radians); + return add(axis.origin, add(add(mul(p, c), mul(cross(k, p), s)), mul(k, dot(k, p) * (1 - c)))); +} + +function rotateVector(value, axis, radians) { + return sub(rotateAroundAxis(value, { origin: [0, 0, 0], direction: axis.direction }, radians), [0, 0, 0]); +} + +/** 沿基准面法向偏移,距离单位为 mm。 */ +export function makeOffsetPlane(basePlane, distance) { + const base = planeBasis(basePlane); + if (!Number.isFinite(distance)) throw new Error('偏移距离必须是有限数值'); + return { type: 'plane', origin: add(base.origin, mul(base.normal, distance)), normal: base.normal, xAxis: base.xAxis, + sourceRefs: sourceIds(basePlane), params: { kind: 'offset', distance } }; +} + +/** 绕给定轴旋转基准面,角度对外使用度。 */ +export function makeAngledPlane(basePlane, axis, degrees) { + const base = planeBasis(basePlane), a = axisData(axis); + if (!Number.isFinite(degrees)) throw new Error('角度必须是有限数值'); + const radians = degrees * Math.PI / 180; + return { type: 'plane', origin: rotateAroundAxis(base.origin, a, radians), normal: unit(rotateVector(base.normal, a, radians), '旋转后法向'), xAxis: unit(rotateVector(base.xAxis, a, radians), '旋转后x轴'), + sourceRefs: sourceIds(basePlane, axis), params: { kind: 'angled', degrees } }; +} + +/** 创建与基准面平行、并经过指定点的基准面。 */ +export function makePlaneThroughPoint(basePlane, point) { + const base = planeBasis(basePlane), origin = pointOf(point); + return { type: 'plane', origin, normal: base.normal, xAxis: base.xAxis, + sourceRefs: sourceIds(basePlane, point), params: { kind: 'throughPoint' } }; +} + +/** 用两点建立方向从 a 指向 b 的基准轴。 */ +export function makeAxisFromPoints(a, b) { + const origin = pointOf(a, '第一点'), end = pointOf(b, '第二点'); + const delta = sub(end, origin); + if (length(delta) < EPS) throw new Error('两点重合,不能建立基准轴'); + return { type: 'axis', origin, direction: unit(delta, '两点基准轴方向'), sourceRefs: sourceIds(a, b), params: { kind: 'twoPoints' } }; +} + +/** 用两个不平行基准面的交线建立基准轴。 */ +export function makeAxisFromPlanes(a, b) { + const pa = planeBasis(a), pb = planeBasis(b), direction = cross(pa.normal, pb.normal); + const d2 = dot(direction, direction); + if (d2 < EPS) throw new Error('两个平面平行或重合,不能建立交线基准轴'); + const ca = dot(pa.normal, pa.origin), cb = dot(pb.normal, pb.origin); + const origin = mul(cross(sub(mul(pb.normal, ca), mul(pa.normal, cb)), direction), 1 / d2); + return { type: 'axis', origin, direction: unit(direction, '平面交线方向'), sourceRefs: sourceIds(a, b), params: { kind: 'planeIntersection' } }; +} + +/** 由原点、x 方向提示和 y 方向提示创建正交右手坐标系。 */ +export function makeCoordinateSystem(origin, xHint, yHint) { + const o = pointOf(origin, '坐标系原点'), xAxis = unit(xHint, '坐标系x方向'); + const yProjected = sub(vector(yHint, '坐标系y方向'), mul(xAxis, dot(yHint, xAxis))); + if (length(yProjected) < EPS) throw new Error('坐标系x轴与y方向不能共线'); + const yAxis = unit(yProjected, '坐标系y轴'), zAxis = unit(cross(xAxis, yAxis), '坐标系z轴'); + return { type: 'csys', origin: o, xAxis, yAxis, zAxis, sourceRefs: sourceIds(origin), params: { kind: 'rightHanded' } }; +} + +function lookupById(lookup, id) { + if (typeof lookup === 'function') return lookup(id); + if (lookup instanceof Map) return lookup.get(id); + if (Array.isArray(lookup)) return lookup.find(item => item && item.id === id); + return lookup && lookup[id]; +} + +/** + * 解析可序列化参考对象。约定:params.kind 为 offset/angled/throughPoint/ + * twoPoints/planeIntersection/entityCenter/rightHanded;sourceRefs 按该 kind + * 的参数顺序保存 ID。lookup 可以是函数、Map、数组或 ID 键对象。 + */ +export function resolveReference(reference, lookup) { + const resolving = new Set(); + const resolve = value => { + const ref = typeof value === 'string' || typeof value === 'number' ? lookupById(lookup, value) : value; + if (!ref) throw new Error(`引用不存在:${String(value)}`); + const key = ref.id ?? ref; + if (resolving.has(key)) throw new Error(`检测到循环引用:${String(ref.id ?? '匿名参考')}`); + resolving.add(key); + try { + const params = ref.params || {}, kind = params.kind; + // 实体中心是一个文档实体引用,而非另一种 ReferenceData;不能把它当作 + // 参考几何递归解析,否则会错误要求实体也带 type/sourceRefs。 + const sources = kind === 'entityCenter' + ? (ref.sourceRefs || []).map(id => lookupById(lookup, id)) + : (ref.sourceRefs || []).map(id => resolve(id)); + let result; + if (kind === 'offset') result = makeOffsetPlane(sources[0], params.distance); + else if (kind === 'angled') result = makeAngledPlane(sources[0], sources[1], params.degrees); + else if (kind === 'throughPoint') result = makePlaneThroughPoint(sources[0], sources[1]); + else if (kind === 'twoPoints') result = makeAxisFromPoints(sources[0], sources[1]); + else if (kind === 'planeIntersection') result = makeAxisFromPlanes(sources[0], sources[1]); + else if (kind === 'entityCenter') { + const entity = sources[0]; + const position = entity && (entity.center || entity.position || (entity.bbox && entity.bbox.min && entity.bbox.max && mul(add(entity.bbox.min, entity.bbox.max), .5))); + result = { type: 'point', position: pointOf(position, '引用实体中心'), sourceRefs: sourceIds(entity), params: { kind } }; + } else if (kind === 'rightHanded') result = makeCoordinateSystem(sources[0] || ref.origin, params.xHint || ref.xAxis, params.yHint || ref.yAxis); + else if (ref.type === 'plane') { const p = planeBasis(ref); result = { type: 'plane', ...p, sourceRefs: ref.sourceRefs || [], params }; } + else if (ref.type === 'axis') { const a = axisData(ref); result = { type: 'axis', ...a, sourceRefs: ref.sourceRefs || [], params }; } + else if (ref.type === 'point') result = { type: 'point', position: pointOf(ref), sourceRefs: ref.sourceRefs || [], params }; + else if (ref.type === 'csys') result = makeCoordinateSystem(ref.origin, ref.xAxis, ref.yAxis); + else throw new Error('未知的参考几何类型'); + if (ref.id !== undefined) result.id = ref.id; + if (ref.name !== undefined) result.name = ref.name; + return result; + } finally { resolving.delete(key); } + }; + return resolve(reference); +} diff --git a/js/features/rib.js b/js/features/rib.js new file mode 100644 index 0000000..0467551 --- /dev/null +++ b/js/features/rib.js @@ -0,0 +1,209 @@ +// 筋特征:纯几何轮廓 + 由调用者注入的 Manifold 内核能力。 +// 本模块绝不初始化 WASM。buildRibTool 返回的 Manifold 所有权交给调用者; +// 用于接触检测的临时交集在本模块内释放。 +import { tessellate } from '../sketch.js'; +import { PLANES } from '../math.js'; + +const EPS = 1e-6; +const sub2 = (a, b) => [a[0] - b[0], a[1] - b[1]]; +const add2 = (a, b) => [a[0] + b[0], a[1] + b[1]]; +const mul2 = (a, s) => [a[0] * s, a[1] * s]; +const len2 = a => Math.hypot(a[0], a[1]); +const cross2 = (a, b) => a[0] * b[1] - a[1] * b[0]; +const dot2 = (a, b) => a[0] * b[0] + a[1] * b[1]; +const area2 = points => points.reduce((sum, p, i) => { const q = points[(i + 1) % points.length]; return sum + p[0] * q[1] - p[1] * q[0]; }, 0) / 2; +const unit2 = a => { const n = len2(a); if (n < EPS) throw new Error('筋路径存在零长度线段'); return mul2(a, 1 / n); }; +const vsub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; +const vadd = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; +const vmul = (a, s) => [a[0] * s, a[1] * s, a[2] * s]; +const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; +const length = a => Math.hypot(a[0], a[1], a[2]); +const unit = a => { const n = length(a); if (n < EPS) throw new Error('筋草图平面法向无效'); return vmul(a, 1 / n); }; + +function cleanPath(points, dimensions) { + if (!Array.isArray(points) || points.length < 2) throw new Error('筋路径至少需要两个点'); + const out = []; + for (const raw of points) { + if (!Array.isArray(raw) || raw.length < dimensions || raw.slice(0, dimensions).some(v => !Number.isFinite(Number(v)))) throw new Error('筋路径包含无效坐标'); + const p = raw.slice(0, dimensions).map(Number); + if (!out.length || len2(dimensions === 2 ? sub2(p, out.at(-1)) : [p[0] - out.at(-1)[0], p[1] - out.at(-1)[1]]) > EPS || (dimensions === 3 && length(vsub(p, out.at(-1))) > EPS)) out.push(p); + } + if (out.length < 2) throw new Error('筋路径退化为一点'); + if (dimensions === 2 && len2(sub2(out[0], out.at(-1))) < EPS) throw new Error('筋路径必须是开放轮廓'); + if (dimensions === 3 && length(vsub(out[0], out.at(-1))) < EPS) throw new Error('筋路径必须是开放轮廓'); + return out; +} + +function lineIntersection(a, da, b, db) { + const den = cross2(da, db); + if (Math.abs(den) < EPS) return null; + const t = cross2(sub2(b, a), db) / den; + return add2(a, mul2(da, t)); +} + +function sideOffsets(path, distance) { + const dirs = []; + for (let i = 0; i < path.length - 1; i++) dirs.push(unit2(sub2(path[i + 1], path[i]))); + const normals = dirs.map(d => [-d[1], d[0]]); + return path.map((p, i) => { + if (i === 0) return add2(p, mul2(normals[0], distance)); + if (i === path.length - 1) return add2(p, mul2(normals.at(-1), distance)); + const hit = lineIntersection(add2(p, mul2(normals[i - 1], distance)), dirs[i - 1], add2(p, mul2(normals[i], distance)), dirs[i]); + if (!hit || len2(sub2(hit, p)) > Math.max(Math.abs(distance) * 6, EPS)) throw new Error('筋轮廓在折角处退化或厚度过大'); + return hit; + }); +} + +function segmentsIntersect(a, b, c, d) { + const o1 = cross2(sub2(b, a), sub2(c, a)), o2 = cross2(sub2(b, a), sub2(d, a)); + const o3 = cross2(sub2(d, c), sub2(a, c)), o4 = cross2(sub2(d, c), sub2(b, c)); + return (o1 * o2 < -EPS && o3 * o4 < -EPS); +} + +function validateSimple(poly) { + for (let i = 0; i < poly.length; i++) { + const a = poly[i], b = poly[(i + 1) % poly.length]; + if (len2(sub2(a, b)) < EPS) throw new Error('筋轮廓退化'); + for (let j = i + 1; j < poly.length; j++) { + if (j === i || (j + 1) % poly.length === i || (i + 1) % poly.length === j) continue; + if (segmentsIntersect(a, b, poly[j], poly[(j + 1) % poly.length])) throw new Error('筋轮廓自交或厚度过大'); + } + } +} + +/** 对 CCW 简单多边形耳切;筋轮廓可能在折角内侧凹陷,不能使用扇形端盖。 */ +function triangulateCap(poly) { + const remaining = Array.from({ length: poly.length }, (_, i) => i), tris = []; + while (remaining.length > 3) { + let ear = -1; + for (let i = 0; i < remaining.length; i++) { + const a = remaining[(i - 1 + remaining.length) % remaining.length], b = remaining[i], c = remaining[(i + 1) % remaining.length]; + if (cross2(sub2(poly[b], poly[a]), sub2(poly[c], poly[b])) <= EPS) continue; + let contains = false; + for (const k of remaining) { + if (k === a || k === b || k === c) continue; + const ab = cross2(sub2(poly[b], poly[a]), sub2(poly[k], poly[a])); + const bc = cross2(sub2(poly[c], poly[b]), sub2(poly[k], poly[b])); + const ca = cross2(sub2(poly[a], poly[c]), sub2(poly[k], poly[c])); + if (ab >= -EPS && bc >= -EPS && ca >= -EPS) { contains = true; break; } + } + if (!contains) { tris.push(a, b, c); ear = i; break; } + } + if (ear < 0) throw new Error('筋轮廓无法三角化(可能自交或退化)'); + remaining.splice(ear, 1); + } + tris.push(remaining[0], remaining[1], remaining[2]); + return tris; +} + +/** + * 将连续开放二维路径偏置为封闭薄壁轮廓。side: both | left | right | single; + * single 等同 left。返回的数组不含重复终点,单位为 mm。 + */ +export function offsetOpenPath(points, thickness, side = 'both') { + const path = cleanPath(points, 2); + const t = Number(thickness); + if (!Number.isFinite(t) || t <= EPS) throw new Error('筋厚度必须大于 0'); + if (!['both', 'left', 'right', 'single'].includes(side)) throw new Error('筋侧向必须是 both、left、right 或 single'); + const leftDistance = side === 'both' ? t / 2 : (side === 'right' ? 0 : t); + const rightDistance = side === 'both' ? -t / 2 : (side === 'right' ? -t : 0); + const left = sideOffsets(path, leftDistance); + const right = sideOffsets(path, rightDistance); + let outline = left.concat(right.slice().reverse()); + validateSimple(outline); + // 后续三角化按 CCW 外环生成;统一方向也让 single/right 的输出稳定。 + if (area2(outline) < 0) outline = outline.reverse(); + return outline; +} + +/** 构建筋的二维闭合轮廓;draftDegrees 以度数输入,仅允许 -45..45 度。 */ +export function buildRibProfile({ path, thickness, side = 'both', draftDegrees = 0 } = {}) { + const draft = Number(draftDegrees); + if (!Number.isFinite(draft) || Math.abs(draft) >= 45) throw new Error('筋拔模角必须在 -45 到 45 度之间'); + return { points: offsetOpenPath(path, thickness, side), closed: true, draftDegrees: draft, side }; +} + +function orderedLocalPath(sketch) { + if (Array.isArray(sketch?.points)) return cleanPath(sketch.points, 2); + if (!sketch || !Array.isArray(sketch.entities)) throw new Error('筋草图或路径无效'); + const parts = sketch.entities.filter(e => !e.construction).map(tessellate); + if (!parts.length) throw new Error('筋草图中没有路径'); + const chain = parts.shift().map(p => p.slice()); + while (parts.length) { + const end = chain.at(-1); let found = -1, reverse = false; + for (let i = 0; i < parts.length; i++) { + if (len2(sub2(parts[i][0], end)) < EPS) { found = i; break; } + if (len2(sub2(parts[i].at(-1), end)) < EPS) { found = i; reverse = true; break; } + } + if (found < 0) throw new Error('筋路径不连续'); + const next = parts.splice(found, 1)[0]; if (reverse) next.reverse(); + chain.push(...next.slice(1)); + } + return cleanPath(chain, 2); +} + +function meshPrism(outline, origin, xAxis, yAxis, normal, extent, flip, draft) { + const n = outline.length, sign = flip ? -1 : 1; + const height = extent * sign; + const centroid = outline.reduce((s, p) => add2(s, p), [0, 0]).map(v => v / n); + const grow = Math.tan(draft * Math.PI / 180) * Math.abs(extent); + const top = outline.map(p => { + const r = sub2(p, centroid), l = len2(r); + return l < EPS ? p : add2(p, mul2(r, grow / l)); + }); + validateSimple(top); + const world = (p, h) => vadd(vadd(vadd(origin, vmul(xAxis, p[0])), vmul(yAxis, p[1])), vmul(normal, h)); + const verts = []; + for (const p of outline) verts.push(...world(p, 0)); + for (const p of top) verts.push(...world(p, height)); + const tris = []; + const bottomCap = triangulateCap(outline), topCap = triangulateCap(top); + for (let i = 0; i < bottomCap.length; i += 3) tris.push(bottomCap[i], bottomCap[i + 2], bottomCap[i + 1]); + for (let i = 0; i < topCap.length; i += 3) tris.push(n + topCap[i], n + topCap[i + 1], n + topCap[i + 2]); + for (let i = 0; i < n; i++) { const j = (i + 1) % n; tris.push(i, j, n + i, j, n + j, n + i); } + if (sign < 0) for (let i = 0; i < tris.length; i += 3) [tris[i + 1], tris[i + 2]] = [tris[i + 2], tris[i + 1]]; + return { verts: new Float32Array(verts), tris: new Uint32Array(tris) }; +} + +function bodyIntersects(ctx, body, tool) { + const intersect = ctx.intersect || (ctx.mod?.Manifold?.intersection ? ((a, b) => ctx.mod.Manifold.intersection([a, b])) : null); + if (!intersect) throw new Error('筋接触校验需要 ctx.intersect 或 ctx.mod.Manifold.intersection'); + const overlap = intersect(body, tool); + try { return overlap && Number(overlap.volume()) > EPS; } finally { overlap?.delete?.(); } +} + +/** + * 用主代理注入的 ctx.manifoldFromMeshData 创建筋工具,不创建第二套 WASM。 + * body 存在时会验证实体交叠;返回 tool 的所有权交给调用者,body 仍归调用者。 + */ +export function buildRibTool(ctx, { sketch, path, thickness, side = 'both', flip = false, extent, body, draftDegrees = 0 } = {}) { + if (!ctx || typeof ctx.manifoldFromMeshData !== 'function') throw new Error('筋工具需要主代理注入 ctx.manifoldFromMeshData'); + const source = path?.points || path || orderedLocalPath(sketch); + const local = cleanPath(source, Array.isArray(source[0]) && source[0].length >= 3 ? 3 : 2); + let local2, origin, xAxis, yAxis, normal; + if (local[0].length === 3) { + origin = local[0]; xAxis = unit(vsub(local[1], origin)); + normal = unit(ctx.planeNormal || sketch?.plane?.normal || [0, 0, 1]); + yAxis = unit(cross(normal, xAxis)); + local2 = local.map(p => [dot(vsub(p, origin), xAxis), dot(vsub(p, origin), yAxis)]); + } else { + const plane = typeof sketch?.plane === 'string' ? PLANES[sketch.plane] : sketch?.plane; + xAxis = unit(plane?.xAxis || plane?.uAxis || [1, 0, 0]); + normal = unit(plane?.normal || [0, 0, 1]); yAxis = unit(plane?.yAxis || plane?.vAxis || cross(normal, xAxis)); + const offset = Number(sketch?.offset || 0), sign = Number(sketch?.s ?? 1); + if (!Number.isFinite(offset) || !Number.isFinite(sign)) throw new Error('筋草图偏移无效'); + origin = vadd(plane?.origin ? plane.origin.slice() : [0, 0, 0], vmul(normal, offset * sign)); + local2 = local; + } + const h = Number(extent ?? 10); + if (!Number.isFinite(h) || h <= EPS) throw new Error('筋拉伸范围必须大于 0'); + const profile = buildRibProfile({ path: local2, thickness, side, draftDegrees }); + const mesh = meshPrism(profile.points, origin, xAxis, yAxis, normal, h, !!flip, profile.draftDegrees); + let tool; + try { + tool = ctx.manifoldFromMeshData(mesh, '筋'); + if (body && !bodyIntersects(ctx, body, tool)) throw new Error('筋不接触主体,无法形成连通实体'); + return tool; + } catch (error) { tool?.delete?.(); throw error; } +} diff --git a/js/features/sweep.js b/js/features/sweep.js new file mode 100644 index 0000000..1be04b8 --- /dev/null +++ b/js/features/sweep.js @@ -0,0 +1,221 @@ +// 通用扫描:纯 MeshData 实现,不持有 WASM 对象;返回值所有权归调用方。 +import { tessellate, findLoops } from '../sketch.js'; +import { toWorld } from '../math.js'; + +const EPS = 1e-6; +const vadd = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; +const vsub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; +const vmul = (a, s) => [a[0] * s, a[1] * s, a[2] * s]; +const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; +const length = a => Math.hypot(a[0], a[1], a[2]); +const unit = a => { const l = length(a); return l > EPS ? vmul(a, 1 / l) : null; }; +const dist = (a, b) => length(vsub(a, b)); +const finite2 = p => Array.isArray(p) && p.length >= 2 && p.every(Number.isFinite); +const finite3 = p => Array.isArray(p) && p.length >= 3 && p.slice(0, 3).every(Number.isFinite); +const area2 = ring => ring.reduce((s, p, i) => { const q = ring[(i + 1) % ring.length]; return s + p[0] * q[1] - p[1] * q[0]; }, 0) / 2; + +function sketchWorld(u, v, w, plane, offset, sign) { + if (!plane || typeof plane === 'string') return toWorld(u, v, w, plane || 'front', offset, sign); + const origin = plane.origin, xAxis = plane.xAxis; + const normal = unit(plane.normal || []); + const yAxis = unit(plane.yAxis || (normal && xAxis ? cross(normal, xAxis) : [])); + if (!finite3(origin) || !finite3(xAxis) || !normal || !yAxis) throw new Error('草图参考平面标架无效'); + const ww = Number(offset || 0) + Number(sign || 1) * w; + return [ + origin[0] + xAxis[0] * u + yAxis[0] * v + normal[0] * ww, + origin[1] + xAxis[1] * u + yAxis[1] * v + normal[1] * ww, + origin[2] + xAxis[2] * u + yAxis[2] * v + normal[2] * ww, + ]; +} + +function cleanPath(points) { + if (!Array.isArray(points)) throw new Error('扫描路径必须是三维点数组'); + const out = []; + for (const p of points) { + if (!finite3(p)) throw new Error('扫描路径必须由有限的三维坐标组成'); + const q = [Number(p[0]), Number(p[1]), Number(p[2])]; + if (!out.length || dist(q, out[out.length - 1]) > EPS) out.push(q); + } + if (out.length < 2) throw new Error('扫描路径至少需要两个不同的点'); + return out; +} + +function orderedPathParts(entities) { + const parts = entities.filter(e => !e.construction && ['line', 'arc'].includes(e.type)).map(tessellate); + if (!parts.length) throw new Error('路径草图中没有直线或圆弧'); + const chain = parts.shift().map(p => p.slice()); + while (parts.length) { + let found = -1, reverse = false; + let prepend = false; + for (let i = 0; i < parts.length && found < 0; i++) { + const part = parts[i], head = chain[0], tail = chain.at(-1); + if (Math.hypot(tail[0] - part[0][0], tail[1] - part[0][1]) <= EPS) found = i; + else if (Math.hypot(tail[0] - part.at(-1)[0], tail[1] - part.at(-1)[1]) <= EPS) { found = i; reverse = true; } + else if (Math.hypot(head[0] - part.at(-1)[0], head[1] - part.at(-1)[1]) <= EPS) { found = i; prepend = true; } + else if (Math.hypot(head[0] - part[0][0], head[1] - part[0][1]) <= EPS) { found = i; prepend = true; reverse = true; } + } + if (found < 0) throw new Error('路径草图存在断裂,线段和圆弧必须首尾相连'); + const part = parts.splice(found, 1)[0]; + if (reverse) part.reverse(); + if (prepend) chain.unshift(...part.slice(0, -1)); + else chain.push(...part.slice(1)); + } + return chain; +} + +/** 从草图的直线、圆弧生成 PathData。仅接受一条连续开放链。 */ +export function samplePathSketch(sketch, options = {}) { + if (!sketch || !Array.isArray(sketch.entities)) throw new Error('路径草图无效'); + const local = orderedPathParts(sketch.entities); + const plane = options.frame || options.plane || sketch.plane || 'front'; + const offset = Number(options.offset ?? sketch.offset ?? 0); + const sign = Number(options.sign ?? sketch.s ?? 1); + const points = cleanPath(local.map(p => sketchWorld(p[0], p[1], 0, plane, offset, sign))); + if (dist(points[0], points.at(-1)) <= EPS) throw new Error('扫描路径不能闭合'); + return { points, closed: false, sourceSketchId: sketch.id ?? null }; +} + +/** 从封闭草图提取 ProfileData;rings 保留外环和孔环的明确分组。 */ +export function profileFromSketch(sketch, options = {}) { + if (!sketch || !Array.isArray(sketch.entities)) throw new Error('截面草图无效'); + const loops = findLoops(sketch.entities); + if (loops.error || !loops.rings.length) throw new Error(loops.error || '截面草图未找到封闭轮廓'); + const rings = loops.rings.map(r => r.filter((p, i) => i === 0 || Math.hypot(p[0] - r[i - 1][0], p[1] - r[i - 1][1]) > EPS)); + for (const ring of rings) { + if (ring.length < 3 || Math.abs(area2(ring)) < EPS) throw new Error('扫描截面退化,面积必须大于 0'); + } + const plane = options.frame || options.plane || sketch.plane || 'front'; + const offset = Number(options.offset ?? sketch.offset ?? 0), s = Number(options.sign ?? sketch.s ?? 1); + const origin = sketchWorld(0, 0, 0, plane, offset, s); + const x = sketchWorld(1, 0, 0, plane, offset, s).map((value, i) => value - origin[i]); + const y = sketchWorld(0, 1, 0, plane, offset, s).map((value, i) => value - origin[i]); + const xAxis = unit(x), yAxis = unit(y); + return { points: rings[0], rings, closed: true, plane: { origin, xAxis, yAxis, normal: unit(cross(xAxis, yAxis)) }, sourceSketchId: sketch.id ?? null }; +} + +function normalFor(t) { + const candidates = [[0, 0, 1], [0, 1, 0], [1, 0, 0]]; + for (const c of candidates) { const n = unit(vsub(c, vmul(t, dot(c, t)))); if (n) return n; } + throw new Error('无法为扫描路径建立截面方向'); +} +function rotate(v, axis, angle) { + const c = Math.cos(angle), s = Math.sin(angle); + return vadd(vadd(vmul(v, c), vmul(cross(axis, v), s)), vmul(axis, dot(axis, v) * (1 - c))); +} +function pathTangents(path) { + return path.map((p, i) => unit(vsub(i === path.length - 1 ? p : path[i + 1], i === 0 ? p : path[i - 1]))); +} +function makeFrames(path, mode, twistDegrees) { + const tangents = pathTangents(path), frames = []; + let n = normalFor(tangents[0]); + for (let i = 0; i < path.length; i++) { + const t = tangents[i]; + if (i && mode === 'parallelTransport') { + const previous = tangents[i - 1], axis = cross(previous, t), al = length(axis); + if (al > EPS) n = rotate(n, vmul(axis, 1 / al), Math.atan2(al, dot(previous, t))); + n = unit(vsub(n, vmul(t, dot(n, t)))) || normalFor(t); + } else if (mode === 'fixed') n = normalFor(t); + const b = unit(cross(t, n)); + const fraction = path.length === 1 ? 0 : i / (path.length - 1); + const a = Number(twistDegrees || 0) * Math.PI / 180 * fraction; + frames.push({ t, n: rotate(n, t, a), b: rotate(b, t, a) }); + } + return frames; +} + +function validatePath(path, profileRadius) { + for (let i = 0; i < path.length - 1; i++) if (dist(path[i], path[i + 1]) <= EPS) throw new Error('扫描路径存在零长度线段'); + // Non-adjacent segments that meet are a clear self-intersection. Very short + // segments compared with the profile are rejected rather than emitting folded faces. + for (let i = 0; i < path.length - 1; i++) { + if (dist(path[i], path[i + 1]) < profileRadius * 0.2) throw new Error('截面尺寸相对路径过大;请降低采样密度或缩小截面'); + for (let j = i + 2; j < path.length - 1; j++) { + if (dist(path[i], path[j]) < profileRadius * 0.5 || dist(path[i + 1], path[j + 1]) < profileRadius * 0.5) throw new Error('扫描路径急弯或自交,建议降低截面尺寸或平滑路径'); + } + } +} + +function normaliseProfile(profile) { + if (!profile || profile.closed !== true) throw new Error('扫描截面必须是封闭轮廓'); + const source = Array.isArray(profile.rings) ? profile.rings : [profile.points]; + if (!source.length) throw new Error('扫描截面不能为空'); + const rings = source.map(r => { + if (!Array.isArray(r)) throw new Error('扫描截面点格式无效'); + if (!r.every(finite2)) throw new Error('扫描截面必须由有限二维坐标组成'); + const out = r.filter((p, i) => i === 0 || Math.hypot(p[0] - r[i - 1][0], p[1] - r[i - 1][1]) > EPS).map(p => [p[0], p[1]]); + if (out.length > 1 && Math.hypot(out[0][0] - out.at(-1)[0], out[0][1] - out.at(-1)[1]) <= EPS) out.pop(); + if (out.length < 3 || Math.abs(area2(out)) < EPS) throw new Error('扫描截面退化,至少需要三个不共线点'); + return out; + }); + return rings.map((ring, index) => { + const ccw = area2(ring) > 0; + // A positive outer ring and negative hole rings make lateral normals point outward. + return (index === 0 ? ccw : !ccw) ? ring : ring.slice().reverse(); + }); +} + +function pointInTriangle(p, a, b, c) { + const sign = (x, y, z) => (y[0] - x[0]) * (z[1] - x[1]) - (y[1] - x[1]) * (z[0] - x[0]); + const ab = sign(a, b, p), bc = sign(b, c, p), ca = sign(c, a, p); + return ab >= -EPS && bc >= -EPS && ca >= -EPS; +} + +/** Ear clipping for one simple, counter-clockwise polygon; returns local triangle indices. */ +function triangulateSimpleRing(ring) { + const remaining = Array.from({ length: ring.length }, (_, i) => i), out = []; + while (remaining.length > 3) { + let ear = -1; + for (let i = 0; i < remaining.length; i++) { + const a = remaining[(i - 1 + remaining.length) % remaining.length], b = remaining[i], c = remaining[(i + 1) % remaining.length]; + const turn = (ring[b][0] - ring[a][0]) * (ring[c][1] - ring[b][1]) - (ring[b][1] - ring[a][1]) * (ring[c][0] - ring[b][0]); + if (turn <= EPS) continue; + let contains = false; + for (const k of remaining) if (k !== a && k !== b && k !== c && pointInTriangle(ring[k], ring[a], ring[b], ring[c])) { contains = true; break; } + if (!contains) { ear = i; out.push(a, b, c); break; } + } + if (ear < 0) throw new Error('扫描截面不是可封端的简单多边形(可能自交)'); + remaining.splice(ear, 1); + } + out.push(remaining[0], remaining[1], remaining[2]); + return out; +} + +/** + * 构建封闭扫描 MeshData。path 可为 PathData 或点数组;profile 可为 ProfileData。 + * capStart/capEnd 默认 true;frameMode 为 parallelTransport(默认)或 fixed。 + */ +export function buildSweepMesh({ path, profile, frameMode = 'parallelTransport', twistDegrees = 0, capStart = true, capEnd = true } = {}) { + if (!['parallelTransport', 'fixed'].includes(frameMode)) throw new Error('frameMode 必须是 parallelTransport 或 fixed'); + if (!Number.isFinite(Number(twistDegrees))) throw new Error('twistDegrees 必须是有限角度'); + const points = cleanPath(Array.isArray(path) ? path : path && path.points); + const rings = normaliseProfile(profile); + const maxRadius = Math.max(...rings.flat().map(p => Math.hypot(p[0], p[1]))); + validatePath(points, maxRadius); + const frames = makeFrames(points, frameMode, Number(twistDegrees)); + const verts = [], tris = []; + for (const ring of rings) { for (let i = 0; i < points.length; i++) for (const p of ring) { + const f = frames[i], q = vadd(points[i], vadd(vmul(f.n, p[0]), vmul(f.b, p[1]))); verts.push(...q); + }} + let base = 0; + for (const ring of rings) { + const n = ring.length; + for (let i = 0; i < points.length - 1; i++) for (let j = 0; j < n; j++) { + const a = base + i * n + j, b = base + i * n + (j + 1) % n, c = base + (i + 1) * n + j, d = base + (i + 1) * n + (j + 1) % n; + tris.push(a, b, c, b, d, c); + } + base += n * points.length; + } + // Caps intentionally support one outer loop. Multi-loop profiles retain valid side walls; + // callers should leave caps off until the shared triangulation service handles holes. + if (rings.length > 1 && (capStart || capEnd)) throw new Error('多环截面的封端暂不支持;请关闭封端或拆分截面'); + if (rings.length === 1) { + const n = rings[0].length, end = (points.length - 1) * n; + const cap = triangulateSimpleRing(rings[0]); + for (let j = 0; j < cap.length; j += 3) { + if (capStart) tris.push(cap[j], cap[j + 2], cap[j + 1]); + if (capEnd) tris.push(end + cap[j], end + cap[j + 1], end + cap[j + 2]); + } + } + return { verts: new Float32Array(verts), tris: new Uint32Array(tris) }; +} diff --git a/js/features/termination.js b/js/features/termination.js new file mode 100644 index 0000000..577fa0a --- /dev/null +++ b/js/features/termination.js @@ -0,0 +1,170 @@ +// M07 拉伸终止条件:纯数据解析与射线/平面几何辅助。 +// 本模块不初始化几何内核;toBody 所需的构造和裁剪能力由调用方 ctx 注入。 + +const EPS = 1e-9; +const MODES = new Set(['blind', 'throughAll', 'symmetric', 'toNext', 'toFace', 'toBody']); + +const add = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; +const sub = (a, b) => [a[0] - b[0], a[0 + 1] - b[1], a[2] - b[2]]; +const scale = (v, n) => [v[0] * n, v[1] * n, v[2] * n]; +const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; +const magnitude = v => Math.sqrt(dot(v, v)); + +function point(value, label) { + if (!Array.isArray(value) || value.length !== 3 || !value.every(Number.isFinite)) throw new Error(`${label}必须是三个有限数值组成的坐标`); + return [...value]; +} + +function directionOf(value, flip = false) { + const d = point(value, '拉伸方向'), length = magnitude(d); + if (length < EPS) throw new Error('拉伸方向不能为零向量'); + return scale(d, (flip ? -1 : 1) / length); +} + +function meshOf(body) { + const mesh = body && typeof body.getMesh === 'function' ? body.getMesh() : (body && (body.mesh || body)); + const verts = mesh && (mesh.vertProperties || (typeof mesh.verts !== 'function' ? mesh.verts : null)); + const tris = mesh && (mesh.triVerts || (typeof mesh.tris !== 'function' ? mesh.tris : null)); + const stride = Number(mesh?.numProp || 3); + if (!verts || !tris || !Number.isInteger(stride) || stride < 3 || verts.length % stride || tris.length % 3) throw new Error('目标实体缺少有效网格数据'); + return { verts, tris, stride }; +} + +function vertex(verts, index, stride = 3) { + const i = index * stride; + return [verts[i], verts[i + 1], verts[i + 2]]; +} + +// Möller–Trumbore;返回沿已归一化射线的正距离,或 null。 +function rayTriangle(origin, direction, a, b, c, tolerance) { + const edge1 = sub(b, a), edge2 = sub(c, a), p = cross(direction, edge2); + const determinant = dot(edge1, p); + if (Math.abs(determinant) <= tolerance) return null; + const inv = 1 / determinant, tvec = sub(origin, a), u = dot(tvec, p) * inv; + if (u < -tolerance || u > 1 + tolerance) return null; + const q = cross(tvec, edge1), v = dot(direction, q) * inv; + if (v < -tolerance || u + v > 1 + tolerance) return null; + const distance = dot(edge2, q) * inv; + if (distance <= tolerance) return null; + return { distance, normal: cross(edge1, edge2) }; +} + +/** + * 规范化拉伸终止参数。成功时只返回可 JSON 序列化的纯数据;失败时抛出中文 Error。 + * depth 与 offset 的单位均为 mm;flip 会反转最终的拉伸方向。 + */ +export function normalizeTermination(params = {}) { + if (!params || typeof params !== 'object') throw new Error('终止条件必须是对象'); + // 兼容现有拉伸参数中的 throughAll/symmetric 布尔标记,统一输出 mode。 + const mode = params.mode || params.termination || (params.throughAll ? 'throughAll' : (params.symmetric ? 'symmetric' : 'blind')); + if (!MODES.has(mode)) throw new Error(`不支持的终止模式:${String(mode)}`); + const flip = Boolean(params.flip); + const offset = params.offset === undefined ? 0 : params.offset; + if (!Number.isFinite(offset)) throw new Error('终止偏置必须是有限数值'); + const result = { mode, flip, offset }; + if (mode === 'blind' || mode === 'symmetric') { + const depth = params.depth; + if (!Number.isFinite(depth) || depth <= 0) throw new Error(`${mode === 'blind' ? '固定深度' : '对称深度'}必须大于零`); + result.depth = depth; + } + if (mode === 'toFace') { + const targetFaceRef = params.targetFaceRef ?? params.targetFace ?? params.faceRef; + if (targetFaceRef === undefined || targetFaceRef === null) throw new Error('到面终止缺少目标面引用'); + result.targetFaceRef = targetFaceRef; + } + if (mode === 'toBody') { + const targetBodyRef = params.targetBodyRef ?? params.targetBody ?? params.bodyRef; + if (targetBodyRef === undefined || targetBodyRef === null) throw new Error('到实体终止缺少目标实体引用'); + result.targetBodyRef = targetBodyRef; + } + return result; +} + +/** 沿射线求目标网格的首个有效交点。body 可为 MeshData 或带 getMesh() 的内核实体。 */ +export function resolveToNext({ origin, direction, body, tolerance = 1e-6, flip = false }) { + const o = point(origin, '拉伸起点'), d = directionOf(direction, flip); + if (!Number.isFinite(tolerance) || tolerance <= 0) throw new Error('交点容差必须为正的有限数值'); + const { verts, tris, stride } = meshOf(body); + let hit = null; + for (let i = 0; i < tris.length; i += 3) { + const a = vertex(verts, tris[i], stride), b = vertex(verts, tris[i + 1], stride), c = vertex(verts, tris[i + 2], stride); + const candidate = rayTriangle(o, d, a, b, c, tolerance); + if (candidate && (!hit || candidate.distance < hit.depth)) hit = { depth: candidate.distance, point: add(o, scale(d, candidate.distance)), normal: candidate.normal, faceIndex: i / 3 }; + } + if (!hit) throw new Error('拉伸方向上没有与目标实体的有效交点'); + return { ...hit, direction: d }; +} + +function triangleOf(face) { + if (Array.isArray(face?.vertices) && face.vertices.length === 3) return face.vertices.map((v, i) => point(v, `目标三角面顶点${i + 1}`)); + if (Array.isArray(face?.points) && face.points.length === 3) return face.points.map((v, i) => point(v, `目标三角面顶点${i + 1}`)); + return null; +} + +/** + * 精确求到基准平面或单个三角面的深度。offset 沿拉伸方向附加,不能把结果推回起点或反向。 + * targetFace 为 {origin, normal} 平面,或 {vertices:[[x,y,z],...]} 三角面;不会用固定超大深度替代。 + */ +export function resolveToFace({ origin, direction, targetFace, offset = 0, tolerance = 1e-6, flip = false }) { + const o = point(origin, '拉伸起点'), d = directionOf(direction, flip); + if (!targetFace || typeof targetFace !== 'object') throw new Error('目标面引用无效或已丢失'); + if (!Number.isFinite(offset)) throw new Error('到面偏置必须是有限数值'); + if (!Number.isFinite(tolerance) || tolerance <= 0) throw new Error('交点容差必须为正的有限数值'); + const triangle = triangleOf(targetFace); + let planeOrigin, normal, faceIndex = targetFace.faceIndex; + if (triangle) { + planeOrigin = triangle[0]; normal = cross(sub(triangle[1], triangle[0]), sub(triangle[2], triangle[0])); + if (magnitude(normal) <= tolerance) throw new Error('目标三角面退化,无法作为终止面'); + } else { + planeOrigin = point(targetFace.origin, '目标面原点'); + normal = point(targetFace.normal, '目标面法向'); + if (magnitude(normal) <= tolerance) throw new Error('目标面法向不能为零向量'); + } + const denominator = dot(normal, d); + if (Math.abs(denominator) <= tolerance) throw new Error('拉伸方向与目标面平行,无法计算终止深度'); + const exactDepth = dot(normal, sub(planeOrigin, o)) / denominator; + if (exactDepth <= tolerance) throw new Error('目标面位于拉伸方向的反侧或与起点重合'); + const hitPoint = add(o, scale(d, exactDepth)); + if (triangle && !rayTriangle(o, d, triangle[0], triangle[1], triangle[2], tolerance)) throw new Error('射线未命中目标三角面'); + const depth = exactDepth + offset; + if (depth <= tolerance) throw new Error('偏置后的终止深度必须大于零'); + return { depth, exactDepth, offset, point: hitPoint, normal: scale(normal, 1 / magnitude(normal)), faceIndex, direction: d }; +} + +function bboxOf(ctx, body) { + const bbox = typeof ctx.getBoundingBox === 'function' ? ctx.getBoundingBox(body) : (body && typeof body.boundingBox === 'function' ? body.boundingBox() : body?.bbox); + if (!bbox || !Array.isArray(bbox.min) || !Array.isArray(bbox.max)) throw new Error('到实体终止需要目标实体的包围盒'); + return { min: point(bbox.min, '包围盒最小点'), max: point(bbox.max, '包围盒最大点') }; +} + +/** + * 用注入的 ctx 构造足够覆盖目标实体包围盒的工具,再裁剪到目标实体。 + * ctx 必须提供 buildExtrudeTool(profileTool,{direction,extent})(或 buildTool)和 intersect(tool,targetBody)(或 clipToBody)。 + * 返回工具实体所有权交给调用方;临时的未采用工具由此函数在可用时 delete()。 + */ +export function buildToBodyTool(ctx, { profileTool, targetBody, direction, flip = false }) { + if (!ctx || typeof ctx !== 'object') throw new Error('到实体终止缺少几何内核上下文'); + if (!profileTool || !targetBody) throw new Error('到实体终止缺少轮廓工具或目标实体'); + const d = directionOf(direction, flip), bbox = bboxOf(ctx, targetBody); + const corners = []; + for (const x of [bbox.min[0], bbox.max[0]]) for (const y of [bbox.min[1], bbox.max[1]]) for (const z of [bbox.min[2], bbox.max[2]]) corners.push([x, y, z]); + const origin = profileTool.origin ? point(profileTool.origin, '轮廓工具起点') : (typeof ctx.getToolOrigin === 'function' ? point(ctx.getToolOrigin(profileTool), '轮廓工具起点') : null); + if (!origin) throw new Error('到实体终止需要轮廓工具起点'); + const projections = corners.map(c => dot(sub(c, origin), d)); + const extent = Math.max(...projections); + if (extent <= EPS) throw new Error('目标实体位于拉伸方向的反侧'); + const padding = Math.max(1e-6, (Math.max(...projections) - Math.min(...projections)) * 1e-6); + const builder = ctx.buildExtrudeTool || ctx.buildTool; + const clipper = ctx.intersect || ctx.clipToBody; + if (typeof builder !== 'function' || typeof clipper !== 'function') throw new Error('到实体终止的几何内核上下文缺少构造或裁剪能力'); + const longTool = builder.call(ctx, profileTool, { direction: d, extent: extent + padding }); + if (!longTool) throw new Error('到实体终止未能构造拉伸工具'); + try { + const clipped = clipper.call(ctx, longTool, targetBody); + if (!clipped) throw new Error('拉伸工具与目标实体没有有效交集'); + return clipped; + } finally { + if (longTool && typeof longTool.delete === 'function') longTool.delete(); + } +} diff --git a/js/geometry.js b/js/geometry.js index 5958817..a17fd44 100644 --- a/js/geometry.js +++ b/js/geometry.js @@ -3,6 +3,10 @@ import * as THREE from 'three'; import ManifoldFactory from 'manifold-3d'; import { findLoops, validateProfile } from './sketch.js'; import { toWorld, rot2, dist, PLANES } from './math.js'; +import { buildSweepMesh } from './features/sweep.js'; +import { buildLoftMesh } from './features/loft.js'; +import { extractTopology, buildChamferMesh } from './features/chamfer.js'; +import { buildFilletMesh } from './features/fillet.js'; let mod = null; @@ -46,16 +50,33 @@ export function transformVerts(verts, planeKey, offset, dirSign) { const out = new Float32Array(verts.length); const n = verts.length / 3; const s = dirSign === undefined ? 1 : dirSign; - // 变换矩阵行列式:front=s, top=-s, right=-s(见推导) - const det = planeKey === 'front' ? s : -s; + // 内置平面保持原映射;参考平面使用显式正交标架。 + const isFrame = planeKey && typeof planeKey === 'object'; + const det = isFrame ? frameDeterminant(planeKey) * s : (planeKey === 'front' ? s : -s); for (let i = 0; i < n; i++) { const u = verts[i * 3], v = verts[i * 3 + 1], w = verts[i * 3 + 2]; - const p = toWorld(u, v, w, planeKey, offset, s); + const p = isFrame ? toFrameWorld(u, v, w, planeKey, offset, s) : toWorld(u, v, w, planeKey, offset, s); out[i * 3] = p[0]; out[i * 3 + 1] = p[1]; out[i * 3 + 2] = p[2]; } return { verts: out, flip: det < 0 }; } +function toFrameWorld(u, v, w, frame, offset = 0, s = 1) { + const o = frame.origin, x = frame.xAxis, y = frame.yAxis || vecNorm(vecCross(frame.normal, x)), n = frame.normal; + if (![o, x, y, n].every(a => Array.isArray(a) && a.length >= 3)) throw new Error('参考平面标架无效'); + const ww = Number(offset || 0) + s * w; + return [ + o[0] + x[0] * u + y[0] * v + n[0] * ww, + o[1] + x[1] * u + y[1] * v + n[1] * ww, + o[2] + x[2] * u + y[2] * v + n[2] * ww, + ]; +} + +function frameDeterminant(frame) { + const y = frame.yAxis || vecNorm(vecCross(frame.normal, frame.xAxis)); + return vecDot(vecCross(frame.xAxis, y), frame.normal); +} + /** 用变换后的顶点重建 manifold(拓扑不变;镜像变换时翻转三角面朝向) */ export function manifoldFromVerts(mesh, newVerts, flip) { let tris = mesh.triVerts; @@ -86,15 +107,20 @@ export function buildExtrude(sketch, params, bodyBBox) { // throughAll 时对称拉伸,确保贯穿 body 两侧 const sym = throughAll || !!params.symmetric; const cs = new mod.CrossSection(rings, 0); - const tool = cs.extrude(depth, 0, 0, [1, 1], sym); - cs.delete(); + let tool; + try { tool = cs.extrude(depth, 0, 0, [1, 1], sym); } + finally { cs.delete(); } if (tool.status() !== 'NoError') { tool.delete(); throw new Error('拉伸生成非流形实体'); } - const mesh = tool.getMesh(); - const tf = transformVerts(mesh.vertProperties, sketch.plane, sketch.offset, params.flip ? -1 : 1); - tool.delete(); + let mesh, tf; + try { + mesh = tool.getMesh(); + tf = transformVerts(mesh.vertProperties, sketch.plane, sketch.offset, params.flip ? -1 : 1); + } finally { + tool.delete(); + } const out = manifoldFromVerts(mesh, tf.verts, tf.flip); return out; } @@ -131,34 +157,247 @@ export function buildRevolve(sketch, params) { if (hasPos && hasNeg) throw new Error('旋转截面跨越了旋转轴,请把截面放在轴的一侧'); } let tool; + let cs = null; try { - const cs = new mod.CrossSection(rings, 0); + cs = new mod.CrossSection(rings, 0); tool = cs.revolve(96, params.revolveDegrees ?? 360); - cs.delete(); } catch (e) { throw new Error('旋转失败:' + (e.message || e)); + } finally { + cs?.delete?.(); + } + if (tool.status() !== 'NoError') { + tool.delete(); + throw new Error('旋转生成非流形实体'); } - if (tool.status() !== 'NoError') throw new Error('旋转生成非流形实体'); // 逆旋转回原始截面方向 - if (theta !== 0) tool = tool.rotate(0, 0, -theta * 180 / Math.PI); - if (pL[0] !== 0 || pL[1] !== 0) tool = tool.translate(pL[0], pL[1], 0); + if (theta !== 0) tool = transformOwned(tool, current => current.rotate(0, 0, -theta * 180 / Math.PI)); + if (pL[0] !== 0 || pL[1] !== 0) tool = transformOwned(tool, current => current.translate(pL[0], pL[1], 0)); // 转到世界系 - const mesh = tool.getMesh(); - const tf = transformVerts(mesh.vertProperties, sketch.plane, sketch.offset, params.flip ? -1 : 1); - tool.delete(); + let mesh, tf; + try { + mesh = tool.getMesh(); + tf = transformVerts(mesh.vertProperties, sketch.plane, sketch.offset, params.flip ? -1 : 1); + } finally { + tool.delete(); + } return manifoldFromVerts(mesh, tf.verts, tf.flip); } /** 圆柱原语(凸台/切除),世界系,轴线沿 Z */ export function buildCylinder(params, bodyBBox) { const r = params.r || 10; + const r2 = Number.isFinite(Number(params.r2)) ? Number(params.r2) : r; let h = params.h || 20; if (params.through) h = bigDepth(bodyBBox); let tool = mod.Manifold.cylinder(h, r, r, 96, true); // center=true → z 从 -h/2 到 h/2 - if (params.tx || params.ty || params.tz) tool = tool.translate(params.tx || 0, params.ty || 0, params.tz || 0); + if (r2 !== r) { + tool.delete(); + tool = mod.Manifold.cylinder(h, r, r2, 96, true); + } + if (params.axis === 'x') tool = transformOwned(tool, current => current.rotate(0, 90, 0)); + else if (params.axis === 'y') tool = transformOwned(tool, current => current.rotate(-90, 0, 0)); + if (params.tx || params.ty || params.tz) tool = transformOwned(tool, current => current.translate(params.tx || 0, params.ty || 0, params.tz || 0)); return tool; } +function transformOwned(previous, transform) { + try { + const next = transform(previous); + if (previous && previous !== next) previous.delete?.(); + return next; + } catch (error) { + previous?.delete?.(); + throw error; + } +} + +/** + * 圆形截面扫描(世界坐标系)。 + * params.path: [[x,y,z], ...] 路径采样点;params.radius: 截面半径。 + * 采用平行传递标架,避免弯曲路径上 Frenet 标架在低曲率处翻转。 + */ +export function buildSweep(params) { + if (params && params.profile) { + const mesh = buildSweepMesh({ + path: params.path, + profile: params.profile, + frameMode: params.frameMode || 'parallelTransport', + twistDegrees: params.twistDegrees || 0, + capStart: params.capStart !== false, + capEnd: params.capEnd !== false, + }); + return manifoldFromMeshData(mesh, '扫描'); + } + const radius = Number(params.radius); + if (!(radius > 0)) throw new Error('扫描截面半径必须大于 0'); + const raw = Array.isArray(params.path) ? params.path : []; + const path = []; + for (const p of raw) { + if (!Array.isArray(p) || p.length < 3 || p.some(v => !Number.isFinite(Number(v)))) { + throw new Error('扫描路径必须由三维坐标点组成'); + } + const q = [Number(p[0]), Number(p[1]), Number(p[2])]; + if (!path.length || vecLen(vecSub(q, path[path.length - 1])) > 1e-6) path.push(q); + } + if (path.length < 2) throw new Error('扫描路径至少需要两个不同的点'); + + const sides = Math.max(12, Math.min(96, Math.round(params.segments || 32))); + const frames = buildParallelFrames(path); + const verts = new Float32Array((path.length * sides + 2) * 3); + for (let i = 0; i < path.length; i++) { + const { n, b } = frames[i]; + for (let j = 0; j < sides; j++) { + const a = 2 * Math.PI * j / sides; + const c = Math.cos(a) * radius, s = Math.sin(a) * radius; + const k = (i * sides + j) * 3; + verts[k] = path[i][0] + n[0] * c + b[0] * s; + verts[k + 1] = path[i][1] + n[1] * c + b[1] * s; + verts[k + 2] = path[i][2] + n[2] * c + b[2] * s; + } + } + const startCenter = path.length * sides; + const endCenter = startCenter + 1; + for (let k = 0; k < 3; k++) { + verts[startCenter * 3 + k] = path[0][k]; + verts[endCenter * 3 + k] = path[path.length - 1][k]; + } + + const tris = []; + for (let i = 0; i < path.length - 1; i++) { + const a0 = i * sides, a1 = (i + 1) * sides; + for (let j = 0; j < sides; j++) { + const next = (j + 1) % sides; + tris.push(a0 + j, a0 + next, a1 + next, a0 + j, a1 + next, a1 + j); + } + } + for (let j = 0; j < sides; j++) { + const next = (j + 1) % sides; + tris.push(startCenter, next, j); + const last = (path.length - 1) * sides; + tris.push(endCenter, last + j, last + next); + } + + const out = manifoldFromVerts({ triVerts: new Uint32Array(tris) }, verts, false); + if (out.status() !== 'NoError') { + const status = out.status(); + out.delete(); + throw new Error('扫描生成非流形实体:' + status); + } + return out; +} + +/** 多截面放样;返回值所有权交给调用方。 */ +export function buildLoft(profiles, params = {}) { + const mesh = buildLoftMesh({ + profiles, + resampleCount: params.resampleCount, + capStart: params.capStart !== false, + capEnd: params.capEnd !== false, + alignment: params.alignment || 'auto', + }); + return manifoldFromMeshData(mesh, '放样'); +} + +/** 对现有完整实体应用受限凸直棱倒角,返回替换后的完整 Manifold。 */ +export function buildChamferEdge(body, params = {}) { + if (!body) throw new Error('实体倒角需要已有主体'); + const mesh = meshFromManifold(body); + const topology = extractTopology(mesh, params.tolerance || 1e-6); + const refs = (params.edgeRefs || []).map(ref => ref?.type === 'edge-query' ? resolveEdgeQuery(ref, topology) : ref); + const result = buildChamferMesh({ mesh, edgeRefs: refs, distance: Number(params.distance), tolerance: params.tolerance || 1e-6 }); + return manifoldFromMeshData(result, '实体倒角'); +} + +/** 对现有完整实体应用受限凸直棱恒定半径圆角,返回替换后的完整 Manifold。 */ +export function buildFilletEdge(body, params = {}) { + if (!body) throw new Error('实体圆角需要已有主体'); + const mesh = meshFromManifold(body); + const topology = extractTopology(mesh, params.tolerance || 1e-6); + const refs = (params.edgeRefs || []).map(ref => ref?.type === 'edge-query' ? resolveEdgeQuery(ref, topology) : ref); + const result = buildFilletMesh({ + mesh, + edgeRefs: refs, + radius: Number(params.radius), + segments: Number(params.segments ?? 8), + tolerance: params.tolerance || 1e-6, + }); + return manifoldFromMeshData(result, '实体圆角'); +} + +function resolveEdgeQuery(query, topology) { + if (!Array.isArray(query.point) || query.point.length !== 3) throw new Error('AI 棱边查询缺少三维定位点'); + const direction = Array.isArray(query.direction) ? vecNorm(query.direction.map(Number)) : null; + const candidates = topology.edges.map(edge => { + const ab = vecSub(edge.b, edge.a), ap = vecSub(query.point, edge.a); + const t = Math.max(0, Math.min(1, vecDot(ap, ab) / Math.max(vecDot(ab, ab), 1e-12))); + const closest = [edge.a[0] + ab[0] * t, edge.a[1] + ab[1] * t, edge.a[2] + ab[2] * t]; + const directionPenalty = direction ? 1000 * (1 - Math.abs(vecDot(direction, edge.direction))) : 0; + return { edge, score: vecLen(vecSub(query.point, closest)) + directionPenalty }; + }).sort((a, b) => a.score - b.score); + if (!candidates.length || !Number.isFinite(candidates[0].score)) throw new Error('未找到符合 AI 查询的实体棱边'); + if (candidates[0].score > Number(query.maxDistance ?? Infinity)) throw new Error('AI 棱边查询距离目标实体过远'); + if (candidates[1] && Math.abs(candidates[1].score - candidates[0].score) < topology.tolerance * 4) throw new Error('AI 棱边查询结果不唯一'); + return candidates[0].edge; +} + +/** MeshData → Manifold;返回值所有权交给调用方。 */ +export function manifoldFromMeshData(mesh, label = '网格') { + if (!mesh || !(mesh.verts instanceof Float32Array) || !(mesh.tris instanceof Uint32Array)) { + throw new Error(`${label}数据格式无效`); + } + const out = manifoldFromVerts({ triVerts: mesh.tris }, mesh.verts, false); + const status = out.status(); + if (status !== 'NoError') { + out.delete(); + throw new Error(`${label}生成非流形实体:${status}`); + } + return out; +} + +function buildParallelFrames(path) { + const tangents = path.map((p, i) => { + if (i === 0) return vecNorm(vecSub(path[1], p)); + if (i === path.length - 1) return vecNorm(vecSub(p, path[i - 1])); + return vecNorm(vecSub(path[i + 1], path[i - 1])); + }); + const frames = []; + const t0 = tangents[0]; + const ref = Math.abs(t0[2]) < 0.9 ? [0, 0, 1] : [0, 1, 0]; + let n = vecNorm(vecCross(ref, t0)); + let b = vecNorm(vecCross(t0, n)); + frames.push({ t: t0, n, b }); + for (let i = 1; i < path.length; i++) { + const t = tangents[i]; + const projected = vecSub(n, vecScale(t, vecDot(n, t))); + if (vecLen(projected) < 1e-7) { + const fallback = Math.abs(t[2]) < 0.9 ? [0, 0, 1] : [0, 1, 0]; + n = vecNorm(vecCross(fallback, t)); + } else { + n = vecNorm(projected); + } + b = vecNorm(vecCross(t, n)); + n = vecNorm(vecCross(b, t)); + frames.push({ t, n, b }); + } + return frames; +} + +const vecSub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; +const vecScale = (a, s) => [a[0] * s, a[1] * s, a[2] * s]; +const vecDot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +const vecCross = (a, b) => [ + a[1] * b[2] - a[2] * b[1], + a[2] * b[0] - a[0] * b[2], + a[0] * b[1] - a[1] * b[0], +]; +const vecLen = a => Math.hypot(a[0], a[1], a[2]); +const vecNorm = a => { + const l = vecLen(a); + if (l < 1e-9) throw new Error('扫描路径存在退化切线'); + return vecScale(a, 1 / l); +}; + // ---------------- 布尔 ---------------- /** body 与 tool 做并/差,返回新 manifold;旧对象由调用方管理 */ export function booleanOp(body, tool, subtract) { diff --git a/js/main.js b/js/main.js index 473d537..4d55b25 100644 --- a/js/main.js +++ b/js/main.js @@ -1,29 +1,35 @@ // main.js —— MiniSW 主入口:装配视口/文档/草图/特征树/AI 智能体 import * as THREE from 'three'; -import { Viewport } from './viewport.js'; -import { Doc } from './doc.js'; +import { Viewport } from './viewport.js?v=20260810-viewport25'; +import { Doc } from './doc.js?v=20260810-doc2'; import { SketchUI } from './sketchui.js'; import { TreeUI } from './tree.js'; import { initGeometry, toBinarySTL } from './geometry.js'; -import { parseNaturalLanguage, getLLMSettings, setLLMSettings, runLLM, validateOps, executeOps, expandOps } from './agent.js'; +import { getLLMSettings, setLLMSettings, runAgentChat, validateOps, validateOpsAgainstDocument, executeOps, inspectDocument, describeInspection } from './agent.js?v=20260810-agentflow37'; import { solveDim, dimValueOf } from './sketch.js'; import { fmt, PLANES } from './math.js'; +import { extractTopology, makeStableEdgeRef } from './features/chamfer.js'; +import { createAssembly, addComponent, addMate, solveMates, checkAllInterferences, setExplode, transformedMesh } from './features/assembly.js?v=20260810-assembly2'; +import { createDrawing, layoutThreeViews, addView, addDimension, addSectionView, renderDrawingSVG } from './features/drawing.js'; // ================= DOM ================= const $ = id => document.getElementById(id); const ribbon = $('ribbon'); const treeEl = $('tree'), propsEl = $('props'); -const viewportEl = $('viewport'), hudEl = $('viewport-hud'); +const viewportEl = $('viewport'), drawingPreview = $('drawing-preview'), hudEl = $('viewport-hud'), emptyState = $('empty-state'); const statusEl = $('statusbar'); const splash = $('splash'), splashMsg = $('splash-msg'), splashBar = $('splash-bar'); const aiPanel = $('ai-panel'), aiChat = $('ai-chat'), aiInput = $('ai-input'), aiSend = $('ai-send'); -const aiModeBadge = $('ai-mode-badge'), aiClose = $('ai-close'), aiSettings = $('ai-settings'), aiClear = $('ai-clear'); +const aiModeBadge = $('ai-mode-badge'), aiGuide = $('ai-guide'), aiClose = $('ai-close'), aiSettings = $('ai-settings'), aiClear = $('ai-clear'); // ================= 全局状态 ================= const doc = new Doc(); let vp = null, ui = null, tree = null; let mode = 'model'; // model | sketch +let workspace = 'part'; // part | assembly | drawing let selFeature = null; // 当前选中特征 id(模型模式) +let selComponent = null; // 当前选中的装配组件 id +let pendingEdgeFeature = null; // 等待用户在视口中拾取实体棱边 const undoStack = [], redoStack = []; const aiHistory = []; // AI 对话历史(用于 LLM 多轮上下文) @@ -49,6 +55,12 @@ const ICONS = { extrude: '', cut: '', revolve: '', + sweep: '', + loft: '', + rib: '', + reference: '', + chamfer: '', + fillet: '', rebuild: '', cursor: '', line: '', @@ -56,6 +68,9 @@ const ICONS = { rect: '', circle: '', arc: '', + ellipse: '', + slot: '', + spline: '', trim: '', dim: '', h: '', @@ -67,6 +82,7 @@ const ICONS = { viewTop: '', viewRight: '', iso: '', + fit: '', ai: '', }; @@ -89,6 +105,8 @@ const TABS = [ { key: 'feature', label: '特征' }, { key: 'sketch', label: '草图' }, { key: 'view', label: '视图' }, + { key: 'assembly', label: '装配' }, + { key: 'drawing', label: '工程图' }, { key: 'ai', label: 'AI 智能体' }, ]; const ribBody = el('div', 'rib-body'); @@ -119,6 +137,14 @@ function buildRibbon() { tbtn('上视草图', 'newsketch:top', 'plane', { title: '在上视基准面上新建草图' }), tbtn('右视草图', 'newsketch:right', 'plane', { title: '在右视基准面上新建草图' }), ])); + rbFeature.appendChild(group([ + tbtn('扫描', 'advanced:sweep', 'sweep', { title: '选择路径草图和封闭截面草图创建扫描' }), + tbtn('放样', 'advanced:loft', 'loft', { title: '选择两个或多个封闭截面草图创建放样' }), + tbtn('筋', 'advanced:rib', 'rib', { title: '选择开放路径草图,在已有主体上创建加强筋' }), + tbtn('实体倒角', 'edge:chamfer', 'chamfer', { title: '选择一条凸直棱并设置倒角距离' }), + tbtn('实体圆角', 'edge:fillet', 'fillet', { title: '选择一条凸直棱并设置圆角半径' }), + tbtn('偏移基准面', 'reference:offset', 'reference', { title: '基于内置基准面创建偏移参考面' }), + ])); rbFeature.appendChild(group([ tbtn('拉伸凸台', 'feat:extrude', 'extrude', { title: '把当前草图拉伸成实体(需要先进入草图)' }), tbtn('拉伸切除', 'feat:cut', 'cut', { title: '用当前草图从实体上切除(需要先进入草图)' }), @@ -136,6 +162,9 @@ function buildRibbon() { tbtn('矩形', 'tool:rect', 'rect'), tbtn('圆', 'tool:circle', 'circle'), tbtn('圆弧', 'tool:arc', 'arc'), + tbtn('椭圆', 'tool:ellipse', 'ellipse'), + tbtn('长圆槽', 'tool:slot', 'slot'), + tbtn('样条', 'tool:spline', 'spline'), ])); rbSketch.appendChild(group([ tbtn('裁剪', 'tool:trim', 'trim', { title: '点击删除实体' }), @@ -157,9 +186,30 @@ function buildRibbon() { tbtn('上视', 'view:top', 'viewTop'), tbtn('右视', 'view:right', 'viewRight'), tbtn('等轴测', 'view:iso', 'iso'), + tbtn('适配模型', 'view:fit', 'fit', { title: '按真实工程尺寸将模型完整放入视口' }), ])); ribBody.appendChild(rbView); + const rbAssembly = el('div', 'rib-tabbody', '

'); + rbAssembly.appendChild(group([ + tbtn('新建装配体', 'assembly:new', 'iso', { primary: true }), + tbtn('插入当前零件', 'assembly:add', 'extrude'), + tbtn('添加同心配合', 'assembly:concentric', 'co'), + tbtn('检查干涉', 'assembly:interference', 'cut'), + tbtn('爆炸视图', 'assembly:explode', 'viewRight'), + ])); + ribBody.appendChild(rbAssembly); + + const rbDrawing = el('div', 'rib-tabbody', '
工程图工作区:生成第一角三视图、尺寸和剖视,并导出 SVG
'); + rbDrawing.appendChild(group([ + tbtn('生成三视图', 'drawing:generate', 'viewFront', { primary: true }), + tbtn('添加尺寸', 'drawing:dimension', 'dim'), + tbtn('添加剖视', 'drawing:section', 'cut'), + tbtn('导出 SVG', 'drawing:export', 'rebuild'), + tbtn('导出 PNG', 'drawing:png', 'rebuild'), + ])); + ribBody.appendChild(rbDrawing); + // AI tab const rbAi = el('div', 'rib-tabbody', '
AI 智能体:用自然语言描述零件,自动完成全流程建模
'); rbAi.appendChild(group([tbtn('打开 AI 面板', 'ai-toggle', 'ai', { primary: true })])); @@ -184,13 +234,40 @@ function switchTab(key) { } const bodies = ribBody.querySelectorAll('.rib-tabbody'); bodies.forEach((b, i) => b.classList.toggle('hidden', TABS[i].key !== key)); + if (key === 'assembly') { + workspace = 'assembly'; + if (doc.assembly?.components?.length) { + vp.updateComponents(doc.assembly.components.map(c => ({ ...c, renderMesh: transformedMesh(c.mesh, c.transform) }))); + vp.fitToModel(); + } + } + else if (key === 'drawing') workspace = 'drawing'; + else if (key === 'feature' || key === 'sketch' || key === 'view' || key === 'ai') { workspace = 'part'; selComponent = null; } + if (typeof propsEl !== 'undefined' && (doc.assembly || doc.drawings.length)) renderProps(); + if (workspace === 'assembly' && doc.assembly) renderAssemblyTree(); + else if (workspace === 'part' && tree) tree.render({ selFeatureId: selFeature, onClickSketch: id => enterSketch(id) }); + updateDrawingPreview(); + if (typeof updateChrome === 'function') { updateChrome(); updateStatus(); } } function handleAct(act) { if (act.startsWith('newsketch:')) { newSketch(act.split(':')[1]); return; } if (act.startsWith('feat:')) { featureFlow(act.split(':')[1]); return; } + if (act.startsWith('advanced:')) { advancedFeatureFlow(act.split(':')[1]); return; } + if (act.startsWith('edge:')) { beginEdgeFeature(act.split(':')[1]); return; } + if (act.startsWith('reference:')) { referenceFlow(act.split(':')[1]); return; } + if (act.startsWith('assembly:')) { handleAssemblyAct(act.slice(9)); return; } + if (act.startsWith('drawing:')) { handleDrawingAct(act.slice(8)); return; } if (act.startsWith('tool:')) { setTool(act.split(':')[1]); return; } - if (act.startsWith('view:')) { vp.setView(act.split(':')[1]); return; } + if (act.startsWith('view:')) { + const view = act.split(':')[1]; + if (view === 'fit') { + if (!doc.bodyMesh) { toast('当前没有可适配的实体', 'err'); return; } + vp.fitToModel(); + setTip('已按真实工程尺寸适配模型'); + } else vp.setView(view); + return; + } switch (act) { case 'rebuild': rebuildAndRefresh(); toast('已重建模型', 'ok'); break; case 'finish-sketch': exitSketch(); break; @@ -198,6 +275,84 @@ function handleAct(act) { } } +function handleAssemblyAct(action) { + workspace = 'assembly'; + switchTab('assembly'); + if (action === 'new') { + if (!doc.bodyMesh) { toast('请先创建一个零件实体,再新建装配体', 'err'); return; } + doc.assembly = createAssembly('课程装配体'); + addComponent(doc.assembly, { id: 'component-1', name: doc.name || '零件1', docId: doc.name, mesh: doc.bodyMesh, fixed: true }); + toast('已新建装配体并插入固定零件', 'ok'); + } else if (action === 'add') { + if (!doc.assembly) { toast('请先新建装配体', 'err'); return; } + if (!doc.bodyMesh) { toast('当前没有可插入的实体', 'err'); return; } + const id = 'component-' + (doc.assembly.components.length + 1); + addComponent(doc.assembly, { id, name: (doc.name || '零件') + id.split('-').pop(), docId: doc.name, mesh: doc.bodyMesh, transform: { position: [40 * doc.assembly.components.length, 0, 0] } }); + toast('已插入当前零件:' + id, 'ok'); + } else if (action === 'concentric') { + if (!doc.assembly || doc.assembly.components.length < 2) { toast('至少需要两个组件才能添加配合', 'err'); return; } + const [a, b] = doc.assembly.components; + addMate(doc.assembly, { type: 'concentric', refA: { componentId: a.id }, refB: { componentId: b.id } }); + solveMates(doc.assembly); toast('已添加同心配合', 'ok'); + } else if (action === 'interference') { + if (!doc.assembly) { toast('请先新建装配体', 'err'); return; } + const hits = checkAllInterferences(doc.assembly); + doc.assembly.lastInterferences = hits; renderProps(); + toast((hits.length ? '检测到 ' + hits.length + ' 处干涉' : '未检测到组件干涉'), hits.length ? 'err' : 'ok'); + } else if (action === 'explode') { + if (!doc.assembly) { toast('请先新建装配体', 'err'); return; } + const vectors = Object.fromEntries(doc.assembly.components.slice(1).map((c, i) => [c.id, [30 * (i + 1), 0, 12 * (i + 1)]])); + setExplode(doc.assembly, { progress: doc.assembly.explode?.active ? 0 : 1, vectors }); + toast(doc.assembly.explode.active ? '已展开装配体' : '已恢复装配位置', 'ok'); + } + renderProps(); updateChrome(); rebuildAndRefresh(); +} + +function handleDrawingAct(action) { + workspace = 'drawing'; + switchTab('drawing'); + if (action === 'generate') { + if (!doc.bodyMesh) { toast('请先创建一个零件实体,再生成工程图', 'err'); return; } + const drawing = createDrawing({ name: doc.name, units: doc.units, format: 'A4', orientation: 'landscape', scale: 1 }); + for (const view of layoutThreeViews(doc.bodyMesh, { scale: 1 })) addView(drawing, view); + doc.drawings.push(drawing); toast('已生成 A4 第一角三视图', 'ok'); + } else if (action === 'dimension') { + const drawing = doc.drawings.at(-1); + if (!drawing) { toast('请先生成工程图', 'err'); return; } + const bb = doc.bbox; const width = bb ? bb.max[0] - bb.min[0] : 0; + addDimension(drawing, { value: width, text: fmt(width) + ' mm', a: [35, 170], b: [95, 170] }); + toast('已添加模型宽度尺寸', 'ok'); + } else if (action === 'section') { + const drawing = doc.drawings.at(-1); + if (!drawing) { toast('请先生成工程图', 'err'); return; } + addSectionView(drawing, { name: 'A-A 剖视图', mesh: doc.bodyMesh }); toast('已添加关联剖视视图', 'ok'); + } else if (action === 'export') { + const drawing = doc.drawings.at(-1); + if (!drawing) { toast('请先生成工程图', 'err'); return; } + download((drawing.name || doc.name || '工程图') + '.svg', renderDrawingSVG(drawing), 'image/svg+xml'); + toast('工程图 SVG 已导出', 'ok'); + } else if (action === 'png') { + const drawing = doc.drawings.at(-1); + if (!drawing) { toast('请先生成工程图', 'err'); return; } + exportDrawingPNG(drawing); + } + renderProps(); updateDrawingPreview(); updateChrome(); +} + +function exportDrawingPNG(drawing) { + const svg = renderDrawingSVG(drawing); + const blob = new Blob([svg], { type: 'image/svg+xml' }); + const url = URL.createObjectURL(blob), img = new Image(); + img.onload = () => { + const canvas = document.createElement('canvas'); canvas.width = 1600; canvas.height = Math.round(1600 * drawing.sheet.height / drawing.sheet.width); + const ctx = canvas.getContext('2d'); ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); + canvas.toBlob(png => { const purl = URL.createObjectURL(png); const a = document.createElement('a'); a.href = purl; a.download = (drawing.name || '工程图') + '.png'; a.click(); setTimeout(() => URL.revokeObjectURL(purl), 3000); toast('工程图 PNG 已导出', 'ok'); }, 'image/png'); + URL.revokeObjectURL(url); + }; + img.onerror = () => { URL.revokeObjectURL(url); toast('PNG 导出失败,请改用 SVG', 'err'); }; + img.src = url; +} + function setTool(t) { if (mode !== 'sketch') { toast('请先进入草图模式', 'err'); return; } ui.setTool(t); @@ -209,6 +364,9 @@ function setTool(t) { rect: '矩形:单击一角→拖到对角', circle: '圆:单击圆心→拖出半径', arc: '圆弧:依次点三点确定圆弧', + ellipse: '椭圆:单击中心→单击确定长短半轴', + slot: '长圆槽:依次点两端中心→点边界确定宽度', + spline: '样条:连续点击控制点,双击结束', trim: '裁剪:单击要删除的实体', dim: '标注:点实体(线/圆/弧)或两点;双击标注改数值', h: '水平约束:单击直线', @@ -239,8 +397,19 @@ function newSketch(plane) { toast(`已新建${PLANES[plane].name}草图:${sk.name}`, 'ok'); } +function newSketchReference(referenceId) { + const ref = doc.resolveReference(referenceId); + if (!ref || ref.type !== 'plane') { toast('只能在参考基准面上创建草图', 'err'); return; } + pushUndo(); + const sk = doc.addSketch({ plane: 'front', planeRefId: referenceId, name: `${ref.name || '参考面'}草图` }); + enterSketch(sk.id); + switchTab('sketch'); + toast(`已在${ref.name || '参考基准面'}上新建草图`, 'ok'); +} + function enterSketch(id) { - const sk = doc.sketchById(id); + const stored = doc.sketchById(id); + const sk = stored ? doc.resolveSketch(stored) : null; if (!sk) return; mode = 'sketch'; selFeature = null; @@ -251,7 +420,9 @@ function enterSketch(id) { updateToolButtons(); updateHUD(); updateStatus(); - setTip(`编辑草图:${sk.name}(${PLANES[sk.plane].name},偏移 ${fmt(sk.offset)})`); + updateChrome(); + const planeName = sk.planeRefId ? (doc.referenceById(sk.planeRefId)?.name || '参考基准面') : (PLANES[sk.plane]?.name || '自定义基准面'); + setTip(`编辑草图:${sk.name}(${planeName},偏移 ${fmt(sk.offset)})`); } function exitSketch() { @@ -265,6 +436,7 @@ function exitSketch() { updateToolButtons(); updateHUD(); updateStatus(); + updateChrome(); setTip('模型模式:旋转视图,单击实体可选中特征'); } @@ -278,23 +450,73 @@ function setSelFeature(id) { } // ================= 重建 & 刷新 ================= -function rebuildAndRefresh() { +function rebuildAndRefresh(options = {}) { + const hadBody = Boolean(doc.bodyMesh); const mesh = doc.rebuild(); - vp.updateBody(mesh); + if (workspace === 'assembly' && doc.assembly?.components?.length) { + vp.updateComponents(doc.assembly.components.map(c => ({ ...c, renderMesh: transformedMesh(c.mesh, c.transform) }))); + } else { + vp.updateBody(mesh); + } + // 首次生成或打开实体时自动取景;后续编辑保留用户当前缩放和观察角度。 + if ((options.fit || !hadBody) && mesh) vp.fitToModel(); + updateDrawingPreview(); vp.setDeltas(doc.deltaMeshes); vp.buildPickTargets(doc.deltaMeshes); if (ui.active && ui.sk && doc.sketchById(ui.sk.id)) ui.render(); else if (ui.active) ui.setSketch(null); - tree.render({ activeSketchId: mode === 'sketch' && ui.sk ? ui.sk.id : null, selFeatureId: selFeature, onClickSketch: id => enterSketch(id) }); + if (workspace === 'assembly' && doc.assembly) renderAssemblyTree(); + else tree.render({ activeSketchId: mode === 'sketch' && ui.sk ? ui.sk.id : null, selFeatureId: selFeature, onClickSketch: id => enterSketch(id) }); renderProps(); updateHUD(); updateStatus(); + updateChrome(); updateUndoRedo(); const nErr = doc.features.filter(f => f.error).length; if (nErr) { const f = doc.features.find(x => x.error); setTip(`重建完成,${nErr} 个特征失败:${f.error}`); } else if (mode === 'model') setTip('重建完成'); } +function updateDrawingPreview() { + const show = workspace === 'drawing' && doc.drawings.length > 0; + if (drawingPreview) { + drawingPreview.classList.toggle('hidden', !show); + viewportEl.style.visibility = show ? 'hidden' : ''; + if (show) drawingPreview.innerHTML = renderDrawingSVG(doc.drawings.at(-1)); + } +} + +function renderAssemblyTree() { + treeEl.innerHTML = ''; + treeEl.appendChild(el('div', 'tnode sel', '' + esc(doc.assembly.name) + '')); + const child = el('div', 'tchild'); + for (const c of doc.assembly.components) { + const row = el('div', 'tnode' + (c.id === selComponent || (!selComponent && c.fixed) ? ' sel' : ''), '' + esc(c.name) + '' + (c.fixed ? '固定' : '') + ''); + row.title = '单击选择组件,双击打开组件'; + row.addEventListener('click', () => selectAssemblyComponent(c.id)); + row.addEventListener('dblclick', () => openAssemblyComponent(c.id)); + child.appendChild(row); + } + const mates = el('div', 'tnode ghost', '配合(' + doc.assembly.mates.length + ')'); + child.appendChild(mates); treeEl.appendChild(child); +} + +function selectAssemblyComponent(id) { + selComponent = String(id); + renderAssemblyTree(); + renderProps(); +} + +function openAssemblyComponent(id) { + const component = doc.assembly?.components?.find(c => String(c.id) === String(id)); + if (!component) return; + workspace = 'part'; + switchTab('feature'); + selComponent = String(id); + vp.fitToModel(); + setTip(`已打开组件:${component.name}(当前装配组件引用同一零件文档)`); +} + function updateUndoRedo() { const btnU = $('btn-undo'), btnR = $('btn-redo'); if (!btnU) return; @@ -312,6 +534,29 @@ function updateHUD() { } } +function updateChrome() { + const nErr = doc.features.filter(f => f.error).length; + const docName = $('chrome-doc-name'); + const modeEl = $('chrome-mode'); + const stateEl = $('chrome-state'); + const dot = $('chrome-status-dot'); + if (docName) docName.textContent = doc.name || '未命名零件'; + if (modeEl) modeEl.textContent = workspace === 'assembly' ? '装配工作区' : workspace === 'drawing' ? '工程图工作区' : mode === 'sketch' ? '草图编辑' : '3D 模型'; + if (stateEl) { + stateEl.textContent = nErr ? `${nErr} 个错误` : (mode === 'sketch' ? '编辑中' : '就绪'); + stateEl.classList.toggle('err', Boolean(nErr)); + } + if (dot) dot.style.background = nErr ? 'var(--err)' : 'var(--ok)'; + const treeMeta = $('tree-meta'); + if (treeMeta) treeMeta.textContent = `草图 ${doc.sketches.length} · 特征 ${doc.features.length}`; + const propsMeta = $('props-meta'); + if (propsMeta) propsMeta.textContent = workspace === 'assembly' ? '装配' : workspace === 'drawing' ? '工程图' : mode === 'sketch' ? '草图' : (selFeature ? '特征' : '文档'); + if (emptyState) { + const empty = mode === 'model' && doc.features.length === 0 && doc.sketches.length === 0 && aiPanel?.classList.contains('hidden'); + emptyState.classList.toggle('hidden', !empty); + } +} + function updateStatus() { const nErr = doc.features.filter(f => f.error).length; let s = `${doc.sketches.length} 草图 · ${doc.features.length} 特征`; @@ -322,21 +567,56 @@ function updateStatus() { s += ` · 尺寸 ${fmt(d[0])}×${fmt(d[1])}×${fmt(d[2])} mm`; } $('sb-doc').innerHTML = s; + const sbMode = $('sb-mode'); + if (sbMode) sbMode.textContent = workspace === 'assembly' ? '装配' : workspace === 'drawing' ? '工程图' : mode === 'sketch' ? '草图编辑' : '3D 模型'; + const sbEngine = $('sb-engine'); + if (sbEngine) sbEngine.textContent = 'AI:统一 Agent'; } // ================= 属性面板 ================= function renderProps() { propsEl.innerHTML = ''; + if (workspace === 'assembly' && doc.assembly) { renderAssemblyProps(); return; } + if (workspace === 'drawing' && doc.drawings.length) { renderDrawingProps(); return; } if (mode === 'sketch' && ui.sk) { renderSketchProps(ui.sk); return; } if (selFeature) { const f = doc.featureById(selFeature); if (f) { renderFeatureForm(f, false); return; } } renderDefaultProps(); } +function renderAssemblyProps() { + const a = doc.assembly; + propsEl.appendChild(el('div', 'prop-sec', '当前装配体')); + propsEl.appendChild(el('div', 'prop-row', '' + esc(a.name) + '')); + propsEl.appendChild(el('div', 'prop-row', '' + a.components.length + '')); + propsEl.appendChild(el('div', 'prop-row', '' + a.mates.length + '')); + propsEl.appendChild(el('div', 'prop-sec', '装配树')); + for (const c of a.components) { + const row = el('div', 'ent-item' + (c.id === selComponent || (!selComponent && c.fixed) ? ' sel' : ''), '组件' + esc(c.name) + ''); + row.title = '单击选择组件,双击打开组件'; + row.addEventListener('click', () => selectAssemblyComponent(c.id)); + row.addEventListener('dblclick', () => openAssemblyComponent(c.id)); + propsEl.appendChild(row); + } + if (a.lastInterferences) propsEl.appendChild(el('div', 'prop-note ' + (a.lastInterferences.length ? 'prop-error' : 'prop-ok'), a.lastInterferences.length ? ('干涉:' + a.lastInterferences.length + ' 处') : '未检测到干涉')); + propsEl.appendChild(el('div', 'prop-note', '提示:可在上方“装配”选项卡插入零件、添加配合、检查干涉和切换爆炸视图。')); +} + +function renderDrawingProps() { + const d = doc.drawings.at(-1); + propsEl.appendChild(el('div', 'prop-sec', '当前工程图')); + propsEl.appendChild(el('div', 'prop-row', '' + d.format + ' ' + d.orientation + '')); + propsEl.appendChild(el('div', 'prop-row', '' + d.views.length + '')); + propsEl.appendChild(el('div', 'prop-row', '' + d.dimensions.length + '')); + propsEl.appendChild(el('div', 'prop-sec', '标题栏')); + propsEl.appendChild(el('div', 'prop-note', '零件:' + esc(d.title.partName || doc.name) + '\n单位:' + d.units + ' · 比例:' + d.scale)); + propsEl.appendChild(el('div', 'prop-note', '提示:工程图支持第一角三视图、基础尺寸、剖视和 SVG 导出。')); +} + function renderSketchProps(sk) { propsEl.appendChild(el('div', 'prop-sec', '当前草图')); const rows = [ ['名称', sk.name], - ['平面', PLANES[sk.plane].name], + ['平面', sk.planeRefId ? (doc.referenceById(sk.planeRefId)?.name || '参考基准面') : (PLANES[sk.plane]?.name || '自定义基准面')], ['偏移', fmt(sk.offset) + ' mm'], ['实体', sk.entities.length], ['约束', sk.constraints.length], @@ -368,7 +648,7 @@ function renderSketchProps(sk) { const FEAT_LABEL = { extrude: '拉伸凸台', cut: '拉伸切除', revolve: '旋转', - cylinder: '圆柱体', cylcut: '圆柱切除', + cylinder: '圆柱体', cylcut: '圆柱切除', sweep: '扫描', loft: '放样', rib: '筋', chamfer: '实体倒角', fillet: '实体圆角', }; function renderFeatureForm(f, isNew) { @@ -387,6 +667,19 @@ function renderFeatureForm(f, isNew) { propsEl.appendChild(r); fields.push([key, r.querySelector('input'), 'chk']); }; + const addText = (key, label, def = '', hint = '') => { + const r = el('div', 'prop-row', ``); + if (hint) r.title = hint; + propsEl.appendChild(r); + fields.push([key, r.querySelector('input'), 'txt']); + }; + const addSelect = (key, label, options, def) => { + const value = p[key] ?? def; + const html = options.map(([v, text]) => ``).join(''); + const r = el('div', 'prop-row', ``); + propsEl.appendChild(r); + fields.push([key, r.querySelector('select'), 'txt']); + }; const addName = () => { const r = el('div', 'prop-row', ``); propsEl.appendChild(r); @@ -395,14 +688,20 @@ function renderFeatureForm(f, isNew) { switch (f.type) { case 'extrude': + addSelect('mode', '终止条件', [['blind', '固定深度'], ['throughAll', '贯穿全部'], ['symmetric', '对称'], ['toNext', '到下一面'], ['toFace', '到指定面'], ['toBody', '到实体']], p.mode || (p.throughAll ? 'throughAll' : (p.symmetric ? 'symmetric' : 'blind'))); addNum('depth', '深度', 10, '拉伸距离 (mm)'); - addChk('symmetric', '双向拉伸'); + addText('targetFaceRef', '目标面引用', '', '到指定面时填写基准面 ID,例如 front 或参考几何数字 ID'); + addText('targetBodyRef', '目标实体', '', '当前单实体文档填写 current'); + addNum('offset', '终止偏置', 0, '沿拉伸方向的附加距离 (mm)'); addChk('flip', '反向'); addName(); break; case 'cut': + addSelect('mode', '终止条件', [['blind', '固定深度'], ['throughAll', '贯穿全部'], ['symmetric', '对称'], ['toNext', '到下一面'], ['toFace', '到指定面'], ['toBody', '到实体']], p.mode || (p.throughAll ? 'throughAll' : (p.symmetric ? 'symmetric' : 'blind'))); addNum('depth', '深度', 5, '切除深度 (mm)'); - addChk('throughAll', '完全贯穿'); + addText('targetFaceRef', '目标面引用', '', '到指定面时填写基准面 ID'); + addText('targetBodyRef', '目标实体', '', '当前单实体文档填写 current'); + addNum('offset', '终止偏置', 0, '沿拉伸方向的附加距离 (mm)'); addChk('flip', '反向'); addName(); break; @@ -424,6 +723,35 @@ function renderFeatureForm(f, isNew) { addChk('through', '完全贯穿'); addName(); break; + case 'sweep': + addNum('twistDegrees', '扭转角度', 0, '沿路径累计扭转(度)'); + addName(); + propsEl.appendChild(el('div', 'prop-note', `路径草图 #${p.pathSketchId ?? '直接路径'} · 截面草图 #${p.profileSketchId ?? '直接截面'} · 标架 ${p.frameMode || 'parallelTransport'}`)); + break; + case 'loft': + addNum('resampleCount', '重采样点数', 32, '每个截面统一后的顶点数'); + addName(); + propsEl.appendChild(el('div', 'prop-note', `截面草图:${(p.profileSketchIds || []).join('、') || '直接截面数据'}`)); + break; + case 'rib': + addNum('thickness', '筋宽', 3, '开放路径在草图平面内扩展的宽度 (mm)'); + addNum('extent', '延伸范围', 30, '沿草图法向的延伸距离 (mm)'); + addSelect('side', '路径侧向', [['both', '双侧'], ['left', '左侧'], ['right', '右侧']], 'both'); + addNum('draftDegrees', '拔模角', 0, '范围 -45° 到 45°'); + addChk('flip', '反向'); + addName(); + break; + case 'chamfer': + addNum('distance', '倒角距离', 5, '当前受限实现支持凸多面体的一条直棱'); + addName(); + propsEl.appendChild(el('div', 'prop-note', `稳定棱边引用:${p.edgeRefs?.length || 0} 条`)); + break; + case 'fillet': + addNum('radius', '圆角半径', 5, '当前受限实现支持凸多面体的一条直棱'); + addNum('segments', '圆弧分段', 8, '分段越多,圆弧过渡越平滑'); + addName(); + propsEl.appendChild(el('div', 'prop-note', `稳定棱边引用:${p.edgeRefs?.length || 0} 条`)); + break; default: addName(); } @@ -434,7 +762,7 @@ function renderFeatureForm(f, isNew) { const params = {}; for (const [k, inp, t] of fields) { if (k === '__name') continue; - params[k] = t === 'chk' ? !!inp.checked : (parseFloat(inp.value) || 0); + params[k] = t === 'chk' ? !!inp.checked : (t === 'num' ? (parseFloat(inp.value) || 0) : inp.value.trim()); } const nameV = (fields.find(x => x[0] === '__name') || [])[1]; pushUndo(); @@ -496,8 +824,8 @@ function featureFlow(type) { return; } const presets = { - extrude: { depth: 10, symmetric: false, flip: false }, - cut: { depth: 5, throughAll: false, flip: false }, + extrude: { mode: 'blind', depth: 10, offset: 0, flip: false }, + cut: { mode: 'blind', depth: 5, offset: 0, flip: false }, revolve: { revolveDegrees: 360, flip: false }, }; const names = { extrude: '拉伸', cut: '切除', revolve: '旋转' }; @@ -505,6 +833,72 @@ function featureFlow(type) { renderFeatureForm(dummy, true); } +function sketchChoices() { + return doc.sketches.map(s => `#${s.id} ${s.name}(${s.entities.length}实体)`).join('\n'); +} + +function advancedFeatureFlow(type) { + if (mode === 'sketch') exitSketch(); + if ((type === 'sweep' || type === 'loft') && doc.sketches.length < 2) { toast('扫描/放样至少需要两张草图', 'err'); return; } + if (type === 'rib' && (!doc.bodyManifold || doc.sketches.length < 1)) { toast('筋需要已有主体和一张开放路径草图', 'err'); return; } + const choices = sketchChoices(); + if (type === 'sweep') { + const pathRaw = window.prompt(`输入路径草图 ID:\n${choices}`, String(doc.sketches[0].id)); + if (pathRaw === null) return; + const profileRaw = window.prompt(`输入截面草图 ID:\n${choices}`, String(doc.sketches[1].id)); + if (profileRaw === null) return; + const pathSketchId = Number(pathRaw), profileSketchId = Number(profileRaw); + if (!doc.sketchById(pathSketchId) || !doc.sketchById(profileSketchId) || pathSketchId === profileSketchId) { toast('路径/截面草图 ID 无效', 'err'); return; } + pushUndo(); + const f = doc.addFeature({ type: 'sweep', name: '扫描', params: { pathSketchId, profileSketchId, frameMode: 'parallelTransport', twistDegrees: 0, capStart: true, capEnd: true } }); + selFeature = f.id; + } else if (type === 'loft') { + const raw = window.prompt(`按顺序输入至少两个截面草图 ID,用逗号分隔:\n${choices}`, doc.sketches.slice(0, 2).map(s => s.id).join(',')); + if (raw === null) return; + const profileSketchIds = raw.split(/[,,\s]+/).filter(Boolean).map(Number); + if (profileSketchIds.length < 2 || profileSketchIds.some(id => !doc.sketchById(id))) { toast('放样截面草图 ID 无效', 'err'); return; } + pushUndo(); + const f = doc.addFeature({ type: 'loft', name: '放样', params: { profileSketchIds, resampleCount: 32, capStart: true, capEnd: true, alignment: 'auto' } }); + selFeature = f.id; + } else if (type === 'rib') { + const raw = window.prompt(`输入开放路径草图 ID:\n${choices}`, String(doc.sketches.at(-1).id)); + if (raw === null) return; + const sketchId = Number(raw); + if (!doc.sketchById(sketchId)) { toast('筋路径草图 ID 无效', 'err'); return; } + const thickness = Number(window.prompt('输入筋宽(mm)', '3')); + const extent = Number(window.prompt('输入沿草图法向的延伸范围(mm)', '30')); + if (!(thickness > 0) || !(extent > 0)) { toast('筋宽和延伸范围必须大于 0', 'err'); return; } + pushUndo(); + const f = doc.addFeature({ type: 'rib', sketchId, name: '筋', params: { thickness, extent, side: 'both', draftDegrees: 0, flip: false } }); + selFeature = f.id; + } + rebuildAndRefresh(); + toast(`已创建${FEAT_LABEL[type]}`, 'ok'); +} + +function referenceFlow(kind) { + if (kind !== 'offset') return; + const source = window.prompt('输入基准面:front(前视)/ top(上视)/ right(右视)', 'front'); + if (source === null) return; + if (!PLANES[source]) { toast('基准面必须是 front、top 或 right', 'err'); return; } + const raw = window.prompt('输入偏移距离(mm)', '10'); + if (raw === null) return; + const distance = Number(raw); + if (!Number.isFinite(distance)) { toast('偏移距离无效', 'err'); return; } + pushUndo(); + const ref = doc.addReference({ type: 'plane', name: `偏移基准面${doc.references.length + 1}`, sourceRefs: [source], params: { kind: 'offset', distance } }); + tree.render({ activeSketchId: null, selFeatureId: selFeature, onClickSketch: id => enterSketch(id) }); + renderProps(); updateStatus(); + toast(`已创建 ${ref.name}`, 'ok'); +} + +function beginEdgeFeature(type) { + if (mode !== 'model' || !doc.bodyMesh) { toast('请先创建一个实体主体', 'err'); return; } + pendingEdgeFeature = type; + setTip(`请在模型的一条凸直棱附近单击,创建${FEAT_LABEL[type]}`); + toast(`已进入${FEAT_LABEL[type]}选边模式`); +} + // ================= 撤销 / 重做 ================= function pushUndo() { undoStack.push(doc.toJSON()); @@ -515,12 +909,19 @@ function pushUndo() { function restore(snap) { const d = Doc.fromJSON(snap); + try { doc.bodyManifold?.delete?.(); } catch { /* 已释放对象直接丢弃 */ } + doc.bodyManifold = null; + doc.bodyMesh = null; + doc.deltaMeshes = {}; + doc.bbox = null; doc.name = d.name; doc.units = d.units; - doc.sketches = d.sketches; doc.features = d.features; - doc.sketchSeq = d.sketchSeq; doc.featureSeq = d.featureSeq; + doc.sketches = d.sketches; doc.references = d.references; doc.features = d.features; + doc.dimensions = d.dimensions; doc.parameters = d.parameters; + doc.assembly = d.assembly; doc.drawings = d.drawings; + doc.sketchSeq = d.sketchSeq; doc.referenceSeq = d.referenceSeq; doc.featureSeq = d.featureSeq; // 若正在编辑的草图被删除 → 退出草图模式 if (mode === 'sketch' && !doc.sketchById(ui.sk.id)) exitSketch(); - else if (mode === 'sketch') { ui.setSketch(doc.sketchById(ui.sk.id)); vp.enterSketchMode(ui.sk); } + else if (mode === 'sketch') { const sk = doc.resolveSketch(doc.sketchById(ui.sk.id)); ui.setSketch(sk); vp.enterSketchMode(sk); } if (selFeature && !doc.featureById(selFeature)) selFeature = null; rebuildAndRefresh(); } @@ -570,11 +971,12 @@ const openInput = (() => { pushUndo(); const d = Doc.fromJSON(data); doc.name = d.name; doc.units = d.units; - doc.sketches = d.sketches; doc.features = d.features; - doc.sketchSeq = d.sketchSeq; doc.featureSeq = d.featureSeq; + doc.sketches = d.sketches; doc.references = d.references; doc.features = d.features; + doc.dimensions = d.dimensions; doc.parameters = d.parameters; doc.assembly = d.assembly; doc.drawings = d.drawings; + doc.sketchSeq = d.sketchSeq; doc.referenceSeq = d.referenceSeq; doc.featureSeq = d.featureSeq; if (mode === 'sketch') exitSketch(); selFeature = null; - rebuildAndRefresh(); + rebuildAndRefresh({ fit: true }); toast(`已打开工程:${doc.name}`, 'ok'); } catch (e) { toast('打开失败:' + (e.message || e), 'err'); @@ -609,16 +1011,23 @@ function editDim(dim) { } // ================= AI 智能体 ================= -function setAiMode(label, isLLM) { - aiModeBadge.textContent = label; - aiModeBadge.classList.toggle('llm', !!isLLM); +function setAiMode() { + aiModeBadge.textContent = 'Agent 聊天'; + aiModeBadge.classList.add('llm'); + aiModeBadge.title = 'Agent 聊天:LLM 自主读取项目、观察模型并调用建模工具'; + if (aiGuide) aiGuide.textContent = '聊天 Agent:直接描述目标。LLM 会自主读取项目文件、查看当前模型并调用建模工具。'; + if ($('sb-engine')) $('sb-engine').textContent = 'AI:聊天 Agent'; } function toggleAI() { aiPanel.classList.toggle('hidden'); - if (!aiPanel.classList.contains('hidden') && !aiChat.children.length) { - appendMsg('bot', '我是你的建模智能体。用一句话描述零件,例如:\n"做一个 120×80×15 的底板,四角各一个直径8的圆孔"\n支持指令:倒角、圆角、抽壳、线性/圆周阵列、镜像、凸台、多草图组合。\n内置解析器离线可用;点击 ⚙ 可接入任意 OpenAI 兼容大模型,支持更复杂的描述和多轮对话。'); - } + if (!aiPanel.classList.contains('hidden')) ensureAIWelcome(); + updateChrome(); +} + +function ensureAIWelcome() { + if (aiChat.children.length) return; + appendMsg('bot', '你好,直接告诉我你想做什么即可。我会自主读取 MiniSW 项目源码、查看当前模型,并调用建模工具完成任务。', 'ok'); } function appendMsg(role, text, cls = '') { @@ -634,61 +1043,185 @@ function appendMsg(role, text, cls = '') { } function modelStateSummary() { - const sks = doc.sketches.map(s => `${s.name}(${s.plane},${s.entities.length}实体)`); - const fes = doc.features.map(f => `${f.name}(${f.type}${f.error ? ',错误' : ''})`); - return `草图: ${sks.join(', ') || '无'}; 特征: ${fes.join(', ') || '无'}`; + const observed = inspectDocument(doc); + return JSON.stringify({ + name: doc.name, + hasBody: observed.hasBody, + status: observed.status, + dimensions: observed.dimensions, + selectedFeatureId: selFeature, + lastFeatureId: doc.features.at(-1)?.id ?? null, + references: doc.references.map(r => ({ id: r.id, name: r.name, type: r.type })), + sketches: doc.sketches.map(s => ({ id: s.id, name: s.name, plane: s.plane, entities: s.entities.length })), + features: doc.features.map(f => ({ id: f.id, name: f.name, type: f.type, error: f.error || null })), + assembly: doc.assembly ? { components: doc.assembly.components.map(c => ({ id: c.id, name: c.name })), mates: doc.assembly.mates.length } : null, + drawings: doc.drawings.map(d => ({ name: d.name, views: d.views.length })), + }); } function clearAI() { aiChat.innerHTML = ''; aiHistory.length = 0; - appendMsg('bot', '对话已清空。用一句话描述零件,例如:\n"做一个 120×80×15 的底板,四角各一个直径8的圆孔"\n新指令支持:倒角、圆角、抽壳、线性/圆周阵列、镜像、凸台。'); + appendMsg('bot', '对话已清空。'); + ensureAIWelcome(); } -async function sendAI() { +function promptStartsNewPart(text) { + return /^(?:做|生成|创建|新建)\s*(?:一个|一件|一只|个)?/.test(text.trim()); +} + +async function sendAILegacyPlan() { const text = aiInput.value.trim(); if (!text) { aiInput.focus(); return; } appendMsg('user', text); + debugRecord('请求开始', { text, mode: 'unified', document: modelStateSummary() }); aiInput.value = ''; aiSend.disabled = true; aiSend.innerHTML = ''; try { - let ops = null, usedLLM = false; - ops = parseNaturalLanguage(text); + let ops = null, usedLLM = false, llmElapsedMs = 0, llmCallCount = 0, llmCfg = null, llmCtx = {}; + const request = interpretAgentRequest(text, doc); + const localRequest = decideLocalRequest(text, doc); + debugRecord('统一 Agent 本地闸门', { parsed: request, accepted: localRequest }); + if (localRequest?.kind === 'query') { + appendMsg('bot', localRequest.reply, 'ok'); + aiHistory.push({ role: 'user', content: text }, { role: 'assistant', content: localRequest.reply }); + return; + } + if (localRequest?.kind === 'delete' || localRequest?.kind === 'update') { + const before = doc.toJSON(); + pushUndo(); + try { + const reply = applyAgentMutation(doc, localRequest); + rebuildAndRefresh(); + const errors = doc.features.filter(f => f.error); + if (errors.length) { + restore(before); + throw new Error(`修改后重建失败:${errors[0].error}`); + } + appendMsg('bot', `${reply}\n${describeInspection(inspectDocument(doc))}`, 'ok'); + aiHistory.push({ role: 'user', content: text }, { role: 'assistant', content: reply }); + return; + } catch (e) { + undoStack.pop(); + updateUndoRedo(); + throw e; + } + } + ops = localRequest?.kind === 'ops' ? localRequest.ops : null; if (!ops) { const cfg = getLLMSettings(); if (cfg.baseURL && cfg.apiKey && cfg.model) { usedLLM = true; const ctx = { modelState: modelStateSummary(), history: aiHistory.slice() }; - ops = await runLLM(text, cfg, ctx); - } - else throw new Error('内置解析器无法识别该描述。可尝试更标准的说法(如"板、孔、圆柱、倒角、圆角、抽壳、阵列、镜像、凸台"等关键词),或点击 ⚙ 配置 LLM 智能体。'); + ctx.onStage = label => { aiSend.textContent = label; debugRecord('Agent 阶段', label); }; + ctx.onTrace = event => debugRecord(`LLM 调用:${event.stage || 'unknown'}`, event); + llmCfg = cfg; + llmCtx = ctx; + const started = performance.now(); + aiSend.textContent = '统一 Agent 快速理解中…'; + try { ops = await runLLM(text, cfg, ctx); } + catch (llmError) { throw llmError; } + finally { llmElapsedMs = performance.now() - started; } + llmCallCount += Number(ops?.agentMeta?.calls || 0); + debugRecord('规划结果', { calls: ops?.agentMeta?.calls || 0, ops }); + } else throw new Error('当前请求需要统一 Agent 的 LLM 补全,但尚未完成配置。请点击 ⚙ 填写 Base URL、API Key 和模型名。'); } const verr = validateOps(ops); + debugRecord('DSL 结构校验', verr || '通过'); if (verr) throw new Error('操作序列校验失败:' + verr); + let preflightError = validateOpsAgainstDocument(doc, ops); + debugRecord('文档语义预检', preflightError || '通过'); + if (preflightError && usedLLM) { + const repairStarted = performance.now(); + aiSend.textContent = '执行前检查失败,正在让 LLM 修正…'; + ops = await repairLLMPlan(text, llmCfg, { ...llmCtx, modelState: modelStateSummary() }, ops, `执行前检查:${preflightError}`); + llmElapsedMs += performance.now() - repairStarted; + llmCallCount += Number(ops?.agentMeta?.calls || 1); + const repairedSchemaError = validateOps(ops); + preflightError = validateOpsAgainstDocument(doc, ops); + if (repairedSchemaError || preflightError) throw new Error('LLM 修正后的计划仍不可执行:' + (repairedSchemaError || preflightError)); + } else if (preflightError) throw new Error('操作计划不可执行:' + preflightError); + const queryOp = ops.find(op => op.op === 'query_document'); + if (queryOp) { + const answer = String(queryOp.answer || '当前模型没有可回答的信息。'); + appendMsg('bot', `${answer}\nLLM 共调用 ${llmCallCount} 次,处理耗时 ${(llmElapsedMs / 1000).toFixed(1)} 秒。`, 'ok'); + aiHistory.push({ role: 'user', content: text }, { role: 'assistant', content: answer }); + if (aiHistory.length > 12) aiHistory.splice(0, aiHistory.length - 12); + return; + } const expanded = expandOps(ops); pushUndo(); - try { executeOps(doc, ops); } - catch (e) { - const snap = undoStack.pop(); - updateUndoRedo(); - if (snap) restore(snap); - else rebuildAndRefresh(); - throw e; + // “做一个/生成一个”表示新零件。若当前已有模型,先清空旧特征, + // 避免新零件的筋、倒角等特征继承到旧主体而出现无主体错误。 + if (doc.features.length && promptStartsNewPart(text)) { + doc.reset(); + selFeature = null; } - rebuildAndRefresh(); - setAiMode(usedLLM ? 'LLM 智能体' : '内置解析器', usedLLM); - // 记录对话历史(用于 LLM 多轮) + const beforeExec = doc.toJSON(); + if (ops.some(o => String(o.op).startsWith('assembly'))) workspace = 'assembly'; + if (ops.some(o => String(o.op).startsWith('drawing'))) workspace = 'drawing'; + let runOps = ops; + let created = null; + const corrections = []; + for (let attempt = 0; attempt < 2; attempt++) { + try { created = executeOps(doc, runOps); } + catch (e) { + debugRecord('执行错误', { attempt: attempt + 1, message: e.message || String(e), ops: runOps }); + restore(beforeExec); + if (usedLLM && attempt === 0) { + const repairStarted = performance.now(); + aiSend.textContent = '根据错误纠正中…'; + runOps = await repairLLMPlan(text, llmCfg, { ...llmCtx, modelState: modelStateSummary() }, runOps, e.message || String(e)); + llmElapsedMs += performance.now() - repairStarted; + llmCallCount += Number(runOps?.agentMeta?.calls || 1); + corrections.push('LLM 根据执行错误重新规划'); + continue; + } + throw e; + } + rebuildAndRefresh(); + const createdIds = new Set(created.features || []); + const errors = doc.features.filter(f => createdIds.has(f.id) && f.error); + if (!errors.length || attempt === 1) break; + debugRecord('几何重建错误', { attempt: attempt + 1, errors: errors.map(f => ({ name: f.name, type: f.type, error: f.error })) }); + if (usedLLM) { + const errorText = errors.map(f => `${f.name}(${f.type}):${f.error}`).join(';'); + restore(beforeExec); + const repairStarted = performance.now(); + aiSend.textContent = '根据建模错误纠正中…'; + runOps = await repairLLMPlan(text, llmCfg, { ...llmCtx, modelState: modelStateSummary() }, runOps, errorText); + llmElapsedMs += performance.now() - repairStarted; + llmCallCount += Number(runOps?.agentMeta?.calls || 1); + corrections.push('LLM 根据几何内核错误重新规划'); + continue; + } + const repaired = repairOps(runOps, errors); + if (!repaired.removed.length || !repaired.ops.length) break; + corrections.push(...repaired.removed); + restore(beforeExec); + runOps = repaired.ops; + } + // 统一 Agent 默认不再追加生成后视觉复核;结构校验、文档预检和几何重建 + // 已经覆盖执行安全边界,避免每个普通请求再额外增加一次远程调用。 + if (created?.deleted?.some(item => String(item.id) === String(selFeature))) setSelFeature(null); + setAiMode(); + // 记录最近对话,用于统一 Agent 判断当前上下文。 aiHistory.push({ role: 'user', content: text }); const nSk = doc.sketches.length, nFe = doc.features.length; - const featList = expanded.filter(o => ['extrude', 'cut', 'revolve', 'cylinder', 'cylcut'].includes(o.op)) - .map(o => o.name || o.op).join('、'); + const featList = runOps.flatMap(o => expandOps([o])).filter(o => ['delete_feature', 'update_feature', 'extrude', 'cut', 'revolve', 'cylinder', 'cylcut', 'sweep', 'loft', 'rib', 'chamfer_edge', 'fillet_edge', 'assembly_create', 'drawing_generate'].includes(o.op)) + .map(o => o.op === 'delete_feature' ? `删除特征#${o.featureId}` : o.op === 'update_feature' ? `修改特征#${o.featureId}` : (o.name || o.op)).join('、'); aiHistory.push({ role: 'assistant', content: `已生成 ${expanded.length} 个操作,创建 ${nFe} 个特征:${featList}` }); if (aiHistory.length > 12) aiHistory.splice(0, aiHistory.length - 12); const errs = doc.features.filter(f => f.error); if (errs.length) appendMsg('bot', `模型已生成(${nFe} 个特征),但 ${errs.length} 个特征失败:${errs[0].error}`, 'err'); - else appendMsg('bot', `完成!共 ${expanded.length} 个操作(原始 ${ops.length} 条指令,含高级展开):草图 ${nSk} 张,特征 ${nFe} 个(${featList})。\n特征已加入左侧特征树,可继续手动编辑。`, 'ok'); + else { + const repairNote = corrections.length ? `\n我检测到 ${corrections.join('、')} 不可用,已自动移除后重建。` : ''; + const timing = usedLLM ? `\nLLM 共调用 ${llmCallCount} 次,总耗时 ${(llmElapsedMs / 1000).toFixed(1)} 秒。` : ''; + appendMsg('bot', `完成!共 ${runOps.length} 个操作(原始 ${ops.length} 条指令,含高级展开):草图 ${nSk} 张,特征 ${nFe} 个(${featList})。${repairNote}${timing}\n${describeInspection(inspectDocument(doc))}\n特征已加入左侧特征树,可继续手动编辑。`, 'ok'); + } } catch (e) { + debugRecord('Agent 失败', { message: e.message || String(e), stack: e.stack || null }); appendMsg('bot', '生成失败:' + (e.message || e), 'err'); } finally { aiSend.disabled = false; @@ -696,15 +1229,78 @@ async function sendAI() { } } +async function sendAI() { + const text = aiInput.value.trim(); + if (!text) { aiInput.focus(); return; } + appendMsg('user', text); + aiInput.value = ''; + aiSend.disabled = true; + aiSend.textContent = '处理中…'; + try { + const cfg = getLLMSettings(); + if (!cfg.baseURL || !cfg.apiKey || !cfg.model) throw new Error('请先在设置中填写 Base URL、API Key 和模型名。'); + const toolHandlers = { + inspect_model: async () => ({ inspection: inspectDocument(doc), state: JSON.parse(modelStateSummary()) }), + reset_model: async () => { + pushUndo(); + doc.reset(); + selFeature = null; + rebuildAndRefresh({ fit: true }); + return { ok: true, inspection: inspectDocument(doc) }; + }, + execute_model_ops: async ({ ops } = {}) => { + if (!Array.isArray(ops) || !ops.length) throw new Error('execute_model_ops 需要非空 ops 数组'); + const schemaError = validateOps(ops); + if (schemaError) throw new Error('操作校验失败:' + schemaError); + const preflightError = validateOpsAgainstDocument(doc, ops); + if (preflightError) throw new Error('当前文档无法执行:' + preflightError); + const before = doc.toJSON(); + const mutates = ops.some(op => op.op !== 'query_document'); + if (mutates) pushUndo(); + try { + const created = executeOps(doc, ops); + rebuildAndRefresh({ fit: true }); + const createdIds = new Set(created?.features || []); + const errors = doc.features.filter(feature => createdIds.has(feature.id) && feature.error); + if (errors.length) throw new Error(errors.map(feature => `${feature.name || feature.type}:${feature.error}`).join(';')); + if (ops.some(op => String(op.op).startsWith('assembly'))) workspace = 'assembly'; + if (ops.some(op => String(op.op).startsWith('drawing'))) workspace = 'drawing'; + return { ok: true, created, inspection: inspectDocument(doc) }; + } catch (error) { + restore(before); + if (mutates) { undoStack.pop(); updateUndoRedo(); } + throw error; + } + }, + }; + const ctx = { + history: aiHistory.slice(), + toolHandlers, + onStage: label => { aiSend.textContent = label; }, + }; + const result = await runAgentChat(text, cfg, ctx); + const reply = result.reply || '已完成工具操作。'; + appendMsg('bot', reply, 'ok'); + aiHistory.push({ role: 'user', content: text }, { role: 'assistant', content: reply }); + setAiMode(); + } catch (error) { + appendMsg('bot', '处理失败:' + (error.message || error), 'err'); + } finally { + aiSend.disabled = false; + aiSend.textContent = '发送'; + } +} + function openLLMSettings() { const cfg = getLLMSettings(); aiChat.innerHTML = ''; const box = el('div', 'ai-msg bot'); box.style.width = '100%'; - box.innerHTML = `
LLM 智能体配置(OpenAI 兼容接口)
+ box.innerHTML = `
Agent 的 LLM 配置(OpenAI 兼容接口)
+
聊天 Agent 会自主读取项目文件、查看模型并调用建模工具。
@@ -718,9 +1314,8 @@ function openLLMSettings() { }); const c = getLLMSettings(); const ok = !!(c.baseURL && c.apiKey && c.model); - setAiMode(ok ? 'LLM 智能体' : '内置解析器', ok); - toast(ok ? 'LLM 配置已保存' : '配置不完整,继续使用内置解析器'); - appendMsg('bot', ok ? 'LLM 智能体已就绪,可直接输入复杂描述。' : 'LLM 配置未完成,仍使用内置解析器(标准句式可用)。'); + toast(ok ? 'Agent 配置已保存' : '配置不完整,暂不能调用 LLM', ok ? 'ok' : 'err'); + appendMsg('bot', ok ? 'Agent 配置已保存。' : 'LLM 配置未完成。'); }); $('llm-cancel').addEventListener('click', () => { aiChat.innerHTML = ''; toggleAI(); }); } @@ -733,6 +1328,33 @@ viewportEl.addEventListener('pointerup', e => { const dx = e.clientX - downPos[0], dy = e.clientY - downPos[1]; downPos = null; if (Math.hypot(dx, dy) > 6 || mode !== 'model' || e.button !== 0) return; + if (pendingEdgeFeature) { + try { + const topology = extractTopology(doc.bodyMesh); + const edge = vp.pickBodyEdge(e.clientX, e.clientY, topology.edges); + if (!edge) { toast('没有拾取到棱边,请更靠近可见边线单击', 'err'); return; } + const type = pendingEdgeFeature; + const label = type === 'chamfer' ? '倒角距离' : '圆角半径'; + const rawValue = window.prompt(`输入${label}(mm)`, '5'); + if (rawValue === null) { pendingEdgeFeature = null; setTip(''); return; } + const value = Number(rawValue); + if (!(value > 0)) { toast(`${label}必须大于 0`, 'err'); return; } + pushUndo(); + const params = { edgeRefs: [makeStableEdgeRef(edge, topology)] }; + if (type === 'chamfer') params.distance = value; + else { params.radius = value; params.segments = 8; } + const feature = doc.addFeature({ type, params, name: FEAT_LABEL[type] }); + pendingEdgeFeature = null; + selFeature = feature.id; + rebuildAndRefresh(); + toast(`已创建${FEAT_LABEL[type]}`, feature.error ? 'err' : 'ok'); + setTip(''); + return; + } catch (error) { + toast(error.message || String(error), 'err'); + return; + } + } const fid = vp.pickFeature(e.clientX, e.clientY); setSelFeature(fid); }); @@ -743,6 +1365,8 @@ function buildStatusbar() { statusEl.appendChild(el('span', 'sb-item', 'MiniSW')); statusEl.appendChild(el('span', 'sb-item', '')); statusEl.appendChild(el('span', 'sb-item sb-tip', '')); + statusEl.appendChild(el('span', 'sb-item', '3D 模型')); + statusEl.appendChild(el('span', 'sb-item', 'AI:统一 Agent')); } async function boot() { @@ -779,22 +1403,40 @@ async function boot() { }, onEditFeature: id => { setSelFeature(id); switchTab('feature'); }, onSelectPlane: () => {}, + onNewSketchReference: id => newSketchReference(id), + onSelectReference: id => { + const ref = doc.resolveReference(id); + propsEl.innerHTML = ''; + propsEl.appendChild(el('div', 'prop-sec', ref.name || '参考几何')); + propsEl.appendChild(el('div', 'prop-note', `${ref.type} · #${ref.id}\n${JSON.stringify(ref.params || {})}`)); + }, + onDeleteReference: id => { + pushUndo(); + if (!doc.deleteReference(id)) { undoStack.pop(); updateUndoRedo(); toast('参考几何正在被草图或其他参考引用', 'err'); return; } + rebuildAndRefresh(); + toast('已删除参考几何'); + }, }); // AI 面板事件 aiSend.addEventListener('click', sendAI); aiInput.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendAI(); } }); - aiClose.addEventListener('click', () => aiPanel.classList.add('hidden')); + aiClose.addEventListener('click', () => { aiPanel.classList.add('hidden'); updateChrome(); }); aiSettings.addEventListener('click', openLLMSettings); aiClear.addEventListener('click', clearAI); + $('empty-sketch')?.addEventListener('click', () => newSketch('front')); + $('empty-ai')?.addEventListener('click', () => { + aiPanel.classList.remove('hidden'); + ensureAIWelcome(); + aiInput.focus(); + }); for (const chip of document.querySelectorAll('.chip')) { chip.addEventListener('click', () => { aiInput.value = chip.dataset.ex; sendAI(); }); } - const c0 = getLLMSettings(); - setAiMode((c0.baseURL && c0.apiKey && c0.model) ? 'LLM 智能体' : '内置解析器', !!(c0.baseURL && c0.apiKey && c0.model)); + setAiMode(); // 面板拖拽调整宽度 initPanelResizers(); @@ -815,6 +1457,7 @@ async function boot() { setTimeout(() => { splash.style.opacity = '0'; splash.style.transition = 'opacity .4s'; setTimeout(() => splash.remove(), 450); }, 200); rebuildAndRefresh(); + updateChrome(); updateToolButtons(); setTip('欢迎使用 MiniSW!在"特征"选项卡点击基准面新建草图,或用"AI 智能体"一句话建模。'); setAiModeBtnReady(); @@ -823,6 +1466,8 @@ async function boot() { function setAiModeBtnReady() { // AI 面板默认打开引导 aiPanel.classList.remove('hidden'); + ensureAIWelcome(); + updateChrome(); } boot(); diff --git a/js/math.js b/js/math.js index 1fd780c..2f0f201 100644 --- a/js/math.js +++ b/js/math.js @@ -90,6 +90,14 @@ export const PLANES = { /** 局部 (u,v,w) → 世界坐标;s 为 w 方向符号(±1) */ export function toWorld(u, v, w, planeKey, offset, s) { + if (planeKey && typeof planeKey === 'object') { + const o = planeKey.origin, x = planeKey.xAxis; + const n = planeKey.normal; + const y = planeKey.yAxis || [n[1] * x[2] - n[2] * x[1], n[2] * x[0] - n[0] * x[2], n[0] * x[1] - n[1] * x[0]]; + if (![o, x, y, n].every(a => Array.isArray(a) && a.length >= 3)) return [u, v, w]; + const ww = (offset || 0) + (s || 1) * w; + return [o[0] + x[0] * u + y[0] * v + n[0] * ww, o[1] + x[1] * u + y[1] * v + n[1] * ww, o[2] + x[2] * u + y[2] * v + n[2] * ww]; + } const ww = offset + (s || 1) * w; switch (planeKey) { case 'front': return [u, v, ww]; @@ -101,6 +109,14 @@ export function toWorld(u, v, w, planeKey, offset, s) { /** 世界坐标 → 局部 (u,v,w);s 与 toWorld 的 s 一致 */ export function fromWorld(x, y, z, planeKey, offset, s) { + if (planeKey && typeof planeKey === 'object') { + const o = planeKey.origin, ux = planeKey.xAxis, n = planeKey.normal; + const vy = planeKey.yAxis || [n[1] * ux[2] - n[2] * ux[1], n[2] * ux[0] - n[0] * ux[2], n[0] * ux[1] - n[1] * ux[0]]; + if (![o, ux, vy, n].every(a => Array.isArray(a) && a.length >= 3)) return [x, y, z]; + const q = [x - o[0], y - o[1], z - o[2]]; + const dot3 = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + return [dot3(q, ux), dot3(q, vy), (dot3(q, n) - (offset || 0)) / (s || 1)]; + } const inv = 1 / (s || 1); switch (planeKey) { case 'front': return [x, y, (z - offset) * inv]; diff --git a/js/sketch.js b/js/sketch.js index d56ed99..787e7e0 100644 --- a/js/sketch.js +++ b/js/sketch.js @@ -6,7 +6,7 @@ export const nextId = () => uid++; export const resetIds = () => { uid = 1; }; // ---------------- 实体 ---------------- -// type: line | rect | circle | arc | centerline +// type: line | rect | circle | arc | ellipse | slot | spline | centerline // 常用方法:tessellate(ent) → 折线点列;segmentsOf(ent) → 线段对列;boundsOf(ent) export function tessellate(ent) { @@ -26,6 +26,34 @@ export function tessellate(ent) { } return pts; } + case 'ellipse': { + const n = 96, pts = [], rot = ent.rotation || 0; + for (let i = 0; i < n; i++) { + const a = i / n * Math.PI * 2, x = ent.rx * Math.cos(a), y = ent.ry * Math.sin(a); + pts.push([ent.c[0] + x * Math.cos(rot) - y * Math.sin(rot), ent.c[1] + x * Math.sin(rot) + y * Math.cos(rot)]); + } + return pts; + } + case 'slot': { + const p1 = ent.p1, p2 = ent.p2, r = Math.max(1e-6, (ent.width || ent.w || 6) / 2); + const dx = p2[0] - p1[0], dy = p2[1] - p1[1], l = Math.hypot(dx, dy) || 1; + const nx = -dy / l, ny = dx / l, pts = [[p1[0] + nx * r, p1[1] + ny * r], [p2[0] + nx * r, p2[1] + ny * r]]; + for (let i = 1; i <= 16; i++) { const a = Math.atan2(ny, nx) - Math.PI * i / 16; pts.push([p2[0] + r * Math.cos(a), p2[1] + r * Math.sin(a)]); } + pts.push([p1[0] - nx * r, p1[1] - ny * r]); + for (let i = 1; i <= 16; i++) { const a = Math.atan2(-ny, -nx) - Math.PI * i / 16; pts.push([p1[0] + r * Math.cos(a), p1[1] + r * Math.sin(a)]); } + return pts; + } + case 'spline': { + const pts = ent.points || []; + if (pts.length < 2) return pts.map(p => p.slice()); + const out = []; + for (let i = 0; i < pts.length - 1; i++) { + const a = pts[Math.max(0, i - 1)], b = pts[i], c = pts[i + 1], d = pts[Math.min(pts.length - 1, i + 2)]; + for (let j = 0; j < 8; j++) { const t = j / 8, t2 = t * t, t3 = t2 * t; out.push([0.5 * ((2 * b[0]) + (-a[0] + c[0]) * t + (2 * a[0] - 5 * b[0] + 4 * c[0] - d[0]) * t2 + (-a[0] + 3 * b[0] - 3 * c[0] + d[0]) * t3), 0.5 * ((2 * b[1]) + (-a[1] + c[1]) * t + (2 * a[1] - 5 * b[1] + 4 * c[1] - d[1]) * t2 + (-a[1] + 3 * b[1] - 3 * c[1] + d[1]) * t3)]); } + } + out.push(pts[pts.length - 1].slice()); + return out; + } case 'arc': { const sweep = ent.a1 - ent.a0; const n = Math.max(4, Math.min(64, Math.ceil(Math.abs(sweep) / (Math.PI / 18)))); @@ -44,7 +72,7 @@ export function segmentsOf(ent) { const pts = tessellate(ent); const segs = []; for (let i = 0; i < pts.length - 1; i++) segs.push([pts[i], pts[i + 1]]); - if (ent.type === 'circle' || ent.type === 'rect') segs.push([pts[pts.length - 1], pts[0]]); // 闭合轮廓 + if (ent.type === 'circle' || ent.type === 'rect' || ent.type === 'ellipse' || ent.type === 'slot' || (ent.type === 'spline' && ent.closed)) segs.push([pts[pts.length - 1], pts[0]]); // 闭合轮廓 return segs; } @@ -62,6 +90,22 @@ export function distToEntity(ent, p) { } case 'circle': return Math.abs(dist(p, ent.c) - ent.r); + case 'ellipse': { + const rot = ent.rotation || 0, dx = p[0] - ent.c[0], dy = p[1] - ent.c[1]; + const x = dx * Math.cos(rot) + dy * Math.sin(rot), y = -dx * Math.sin(rot) + dy * Math.cos(rot); + const q = Math.hypot(x / Math.max(ent.rx, 1e-6), y / Math.max(ent.ry, 1e-6)); + return Math.abs(q - 1) * Math.min(ent.rx || 1, ent.ry || 1); + } + case 'slot': { + const r = Math.max(1e-6, (ent.width || ent.w || 6) / 2), dx = ent.p2[0] - ent.p1[0], dy = ent.p2[1] - ent.p1[1], l2 = dx * dx + dy * dy || 1; + const t = Math.max(0, Math.min(1, ((p[0] - ent.p1[0]) * dx + (p[1] - ent.p1[1]) * dy) / l2)); + return Math.abs(Math.hypot(p[0] - (ent.p1[0] + t * dx), p[1] - (ent.p1[1] + t * dy)) - r); + } + case 'spline': { + const pts = ent.points || []; let best = Infinity; + for (let i = 0; i < pts.length - 1; i++) best = Math.min(best, pointSegDist(p, pts[i], pts[i + 1])); + return best; + } case 'arc': { const d = dist(p, ent.c); const ang = Math.atan2(p[1] - ent.c[1], p[0] - ent.c[0]); @@ -95,11 +139,23 @@ export function snapPointsOf(ent) { case 'circle': pts.push({ pos: ent.c, kind: '圆心', key: ent.id + ':c', entId: ent.id }); break; + case 'ellipse': + pts.push({ pos: ent.c, kind: '中心', key: ent.id + ':c', entId: ent.id }); + break; + case 'slot': + pts.push({ pos: ent.p1, kind: '端点', key: ent.id + ':p0', entId: ent.id }, { pos: ent.p2, kind: '端点', key: ent.id + ':p1', entId: ent.id }); + break; + case 'spline': + (ent.points || []).forEach((p, i) => pts.push({ pos: p, kind: i === 0 || i === (ent.points.length - 1) ? '端点' : '控制点', key: ent.id + ':p' + i, entId: ent.id })); + break; case 'arc': pts.push({ pos: ent.c, kind: '圆心', key: ent.id + ':c', entId: ent.id }, { pos: [ent.c[0] + ent.r * Math.cos(ent.a0), ent.c[1] + ent.r * Math.sin(ent.a0)], kind: '端点', key: ent.id + ':p0', entId: ent.id }, { pos: [ent.c[0] + ent.r * Math.cos(ent.a1), ent.c[1] + ent.r * Math.sin(ent.a1)], kind: '端点', key: ent.id + ':p1', entId: ent.id }); break; + case 'ellipse': push(ent.c, 'c'); break; + case 'slot': push(ent.p1, 'p0'); push(ent.p2, 'p1'); break; + case 'spline': (ent.points || []).forEach((p, i) => push(p, 'p' + i)); break; } return pts; } @@ -159,7 +215,7 @@ export function applyConstraint(ent, cons) { } function resolvePointKey(ent, key) { - const m = String(key).match(/^(.+):(p0|p1|c)$/); + const m = String(key).match(/^(.+):(p\d+|c)$/); if (!m) return null; if (String(ent.id) !== m[1]) return null; switch (m[2]) { @@ -167,6 +223,8 @@ function resolvePointKey(ent, key) { case 'p1': return ent.p2 ?? null; case 'c': return ent.c ?? null; } + const i = Number(String(m[2]).slice(1)); + if (Number.isInteger(i) && Array.isArray(ent.points)) return ent.points[i] || null; return null; } @@ -194,7 +252,7 @@ export function dimValueOf(sk, dim) { } export function resolveDimPoint(sk, key) { - const m = key.match(/^(.+):(p0|p1|c)$/); + const m = key.match(/^(.+):(p\d+|c)$/); if (!m) return null; const e = sk.entities.find(x => x.id === m[1]); if (!e) return null; @@ -203,6 +261,8 @@ export function resolveDimPoint(sk, key) { case 'p1': return e.p2 ?? null; case 'c': return e.c ?? null; } + const i = Number(String(m[2]).slice(1)); + if (Number.isInteger(i) && Array.isArray(e.points)) return e.points[i] || null; return null; } diff --git a/js/sketchui.js b/js/sketchui.js index 0ab8cfe..1a73c76 100644 --- a/js/sketchui.js +++ b/js/sketchui.js @@ -118,7 +118,7 @@ export class SketchUI { const b = toWorld(t[i + 1][0], t[i + 1][1], 0, sk.plane, sk.offset, 1); arr.push(a[0], a[1], a[2], b[0], b[1], b[2]); } - if (e.type === 'circle') { + if (['circle', 'ellipse', 'slot'].includes(e.type) || (e.type === 'spline' && e.closed)) { const a = toWorld(t[0][0], t[0][1], 0, sk.plane, sk.offset, 1); const b = toWorld(t[t.length - 1][0], t[t.length - 1][1], 0, sk.plane, sk.offset, 1); arr.push(a[0], a[1], a[2], b[0], b[1], b[2]); @@ -183,6 +183,8 @@ export class SketchUI { if (d.type === 'arc' && d.c && d.r !== null && d.a0 !== null && d.a1 !== null) { segs.push(...arcSegs(d.c, d.r, d.a0, d.a1)); } + if (d.type === 'ellipse' && d.c && d.rx > 0 && d.ry > 0) segs.push(...ellipseSegs(d.c, d.rx, d.ry)); + if (d.type === 'spline' && d.points?.length > 1) for (let i = 0; i < d.points.length - 1; i++) segs.push([d.points[i], d.points[i + 1]]); const arr = []; for (const [a, b] of segs) { const wa = toWorld(a[0], a[1], 0, sk.plane, sk.offset, 1); @@ -218,6 +220,9 @@ export class SketchUI { case 'rect': this._rectTool(e, phase); break; case 'circle': this._circleTool(e, phase); break; case 'arc': this._arcTool(e, phase); break; + case 'ellipse': this._ellipseTool(e, phase); break; + case 'slot': this._slotTool(e, phase); break; + case 'spline': this._splineTool(e, phase); break; case 'trim': if (phase === 'down') this._trim(e); break; case 'dim': if (phase === 'down') this._dim(e); break; case 'h': case 'v': case 'eq': case 'co': if (phase === 'down') this._constrain(e); break; @@ -409,6 +414,46 @@ export class SketchUI { } } + _ellipseTool(e, phase) { + const uv = this.uv(e); if (!uv) return; + if (phase === 'down') { + if (!this.draft) this.draft = { type: 'ellipse', c: this.snap(uv).pos.slice(), rx: 0, ry: 0 }; + else { + const d = this.draft; d.rx = Math.abs(uv[0] - d.c[0]); d.ry = Math.abs(uv[1] - d.c[1]); + if (d.rx > 1e-4 && d.ry > 1e-4) { this.h.pushUndo(); this.doc.addEntity(this.sk.id, { type: 'ellipse', c: d.c.slice(), rx: d.rx, ry: d.ry, rotation: 0 }); } + this.draft = null; this.h.changed(); + } + this.render(); + } else if (phase === 'move' && this.draft) { this.draft.rx = Math.abs(uv[0] - this.draft.c[0]); this.draft.ry = Math.abs(uv[1] - this.draft.c[1]); this.render(); } + } + + _slotTool(e, phase) { + const uv = this.uv(e); if (!uv) return; + if (phase === 'down') { + if (!this.draft) this.draft = { type: 'slot', pts: [this.snap(uv).pos.slice()] }; + else if (this.draft.pts.length === 1) this.draft.pts.push(uv.slice()); + else { + const [a, b] = this.draft.pts, width = Math.max(0.1, 2 * distToLine(uv, a, b)); + this.h.pushUndo(); this.doc.addEntity(this.sk.id, { type: 'slot', p1: a, p2: b, width }); this.draft = null; this.h.changed(); + } + this.render(); + } else if (phase === 'move' && this.draft) { this.draft.previewPts = this.draft.pts.concat([uv]); this.render(); } + } + + _splineTool(e, phase) { + const uv = this.uv(e); if (!uv) return; + if (phase === 'down') { + if (!this.draft) this.draft = { type: 'spline', points: [this.snap(uv).pos.slice()] }; + else this.draft.points.push(uv.slice()); + this.render(); + } else if (phase === 'up' && this.draft && this.draft.points.length >= 3) { + const now = performance.now(); + if (now - this._lastClick < 300) { this.h.pushUndo(); this.doc.addEntity(this.sk.id, { type: 'spline', points: this.draft.points.map(p => p.slice()), closed: false }); this.draft = null; this.h.changed(); } + this._lastClick = now; + this.render(); + } + } + _trim(e) { const uv = this.uv(e); if (!uv) return; @@ -590,7 +635,7 @@ function segsOf(ent) { const t = tessellate(ent); const out = []; for (let i = 0; i < t.length - 1; i++) out.push([t[i], t[i + 1]]); - if (ent.type === 'circle') out.push([t[t.length - 1], t[0]]); + if (['circle', 'ellipse', 'slot'].includes(ent.type) || (ent.type === 'spline' && ent.closed)) out.push([t[t.length - 1], t[0]]); return out; } @@ -630,6 +675,18 @@ function arcSegs(c, r, a0, a1) { return segs; } +function ellipseSegs(c, rx, ry) { + const out = [], n = 32; let prev = [c[0] + rx, c[1]]; + for (let i = 1; i <= n; i++) { const a = i / n * Math.PI * 2, p = [c[0] + rx * Math.cos(a), c[1] + ry * Math.sin(a)]; out.push([prev, p]); prev = p; } + return out; +} + +function distToLine(p, a, b) { + const dx = b[0] - a[0], dy = b[1] - a[1], l2 = dx * dx + dy * dy || 1; + const t = Math.max(0, Math.min(1, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / l2)); + return Math.hypot(p[0] - (a[0] + t * dx), p[1] - (a[1] + t * dy)); +} + function circumCenter(a, b, c) { const d = 2 * (a[0] * (b[1] - c[1]) + b[0] * (c[1] - a[1]) + c[0] * (a[1] - b[1])); if (Math.abs(d) < 1e-9) return null; @@ -639,7 +696,7 @@ function circumCenter(a, b, c) { } function entName(e) { - return { line: '直线', rect: '矩形', circle: '圆', arc: '圆弧', centerline: '中心线' }[e.type] || e.type; + return { line: '直线', rect: '矩形', circle: '圆', arc: '圆弧', ellipse: '椭圆', slot: '长圆槽', spline: '样条', centerline: '中心线' }[e.type] || e.type; } function consName(t) { diff --git a/js/tree.js b/js/tree.js index a2a24c0..19f3426 100644 --- a/js/tree.js +++ b/js/tree.js @@ -34,6 +34,19 @@ export class TreeUI { planesBox.appendChild(pn); } this.el.appendChild(planesBox); + // 用户参考几何 + const refs = doc.references || []; + const refsBox = this._box(`参考几何 (${refs.length})`, true); + for (const ref of refs) { + const icon = { plane: '▱', axis: '╱', point: '•', csys: '⌖' }[ref.type] || '◇'; + refsBox.appendChild(this._node({ + icon, name: ref.name || `${ref.type} #${ref.id}`, cls: 'ref', + data: { menu: { type: 'reference', id: ref.id, refType: ref.type } }, + onClick: () => this.h.onSelectReference && this.h.onSelectReference(ref.id), + title: `${ref.type} · #${ref.id}`, + })); + } + this.el.appendChild(refsBox); // 草图 const skBox = this._box(`草图 (${doc.sketches.length})`, true); for (const sk of doc.sketches) { @@ -51,7 +64,7 @@ export class TreeUI { const feBox = this._box(`特征 (${doc.features.length})`, true); for (const f of doc.features) { const cls = 'feat' + (extra.selFeatureId === f.id ? ' sel' : '') + (f.error ? ' bad' : ''); - const icon = { extrude: '▣', cut: '▢', revolve: '◎', cylinder: '◉', cylcut: '◌' }[f.type] || '▣'; + const icon = { extrude: '▣', cut: '▢', revolve: '◎', cylinder: '◉', cylcut: '◌', sweep: '∿', loft: '⌑', rib: '◩', chamfer: '◪', fillet: '◜' }[f.type] || '▣'; const pn = this._node({ icon, name: f.name + (f.error ? ' ⚠' : ''), cls, data: { menu: { type: 'feature', id: f.id, sketchId: f.sketchId } }, @@ -110,6 +123,9 @@ export class TreeUI { if (menu.sketchId) add('编辑草图', () => this.h.onEditSketch(menu.sketchId)); add('编辑特征参数', () => this.h.onEditFeature(menu.id)); add('删除特征', () => this.h.onDeleteFeature(menu.id), true); + } else if (menu.type === 'reference') { + if (menu.refType === 'plane' && this.h.onNewSketchReference) add('在此参考面新建草图', () => this.h.onNewSketchReference(menu.id)); + if (this.h.onDeleteReference) add('删除参考几何', () => this.h.onDeleteReference(menu.id), true); } m.style.left = x + 'px'; m.style.top = y + 'px'; diff --git a/js/viewport.js b/js/viewport.js index 9c286d6..8296a62 100644 --- a/js/viewport.js +++ b/js/viewport.js @@ -55,6 +55,7 @@ export class Viewport { // 地面网格(3D 模式) this.groundGrid = new THREE.GridHelper(500, 50, 0x4a5568, 0x2f3640); this.groundGrid.position.y = 0; + this.groundGrid.userData.workspaceSize = 500; this.groundGrid.visible = true; this.scene.add(this.groundGrid); @@ -81,6 +82,30 @@ export class Viewport { this.css.setSize(w, h); } + /** + * 按真实工程尺寸把当前实体完整放入视口。 + * 这里只调整相机,不修改模型坐标、单位或文档参数。 + */ + fitToModel(padding = 1.35) { + const box = new THREE.Box3().setFromObject(this.bodyGroup); + if (box.isEmpty()) return null; + const center = box.getCenter(new THREE.Vector3()); + const size = box.getSize(new THREE.Vector3()); + const radius = Math.max(size.length() * 0.5, 1); + const direction = this.camera.position.clone().sub(this.controls.target); + if (direction.lengthSq() < 1e-8) direction.set(1, 0.8, 1); + direction.normalize(); + const halfFov = THREE.MathUtils.degToRad(this.camera.fov) * 0.5; + const distance = Math.max(80, radius * padding / Math.tan(halfFov)); + this.controls.target.copy(center); + this.camera.position.copy(center).addScaledVector(direction, distance); + this.camera.near = Math.max(0.01, distance / 1000); + this.camera.far = Math.max(8000, distance * 100); + this.camera.updateProjectionMatrix(); + this.controls.update(); + return { center: center.toArray(), dimensions: size.toArray(), distance }; + } + animate() { requestAnimationFrame(() => this.animate()); this.controls.update(); @@ -92,8 +117,8 @@ export class Viewport { setView(name, sketch) { const dist = this.camera.position.distanceTo(this.controls.target) || 200; const views = { - front: { pos: [0, dist, 0], up: [0, 0, 1] }, - top: { pos: [0, 0, dist], up: [0, 1, 0] }, + front: { pos: [0, 0, dist], up: [0, 1, 0] }, + top: { pos: [0, dist, 0], up: [0, 0, 1] }, right: { pos: [dist, 0, 0], up: [0, 1, 0] }, iso: { pos: [dist * 0.8, dist * 0.8, dist * 0.8], up: [0, 1, 0] }, }; @@ -104,11 +129,12 @@ export class Viewport { } lookAtSketch(sk) { - const nrm = PLANES[sk.plane].normal; + const def = planeDef(sk.plane); + const nrm = def.normal; const p = toWorld(0, 0, 0, sk.plane, sk.offset, 1); const dist = this.camera.position.distanceTo(this.controls.target) || 180; this.camera.position.set(p[0] + nrm[0] * dist, p[1] + nrm[1] * dist, p[2] + nrm[2] * dist); - this.camera.up.set(...PLANES[sk.plane].vAxis); + this.camera.up.set(...def.vAxis); this.controls.target.set(p[0], p[1], p[2]); this.controls.update(); } @@ -193,15 +219,15 @@ export class Viewport { geo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(c.flat()), 3)); const mat = new THREE.MeshBasicMaterial({ color: d.color, transparent: true, opacity: 0.06, side: THREE.DoubleSide, depthWrite: false }); const mesh = new THREE.Mesh(geo, mat); - if (d.key === 'front') { mesh.rotation.x = -Math.PI / 2; } // XZ 平面(法向 +Y) - else if (d.key === 'top') { } // XY 平面(法向 +Z) + if (d.key === 'front') { } // XY 平面(法向 +Z) + else if (d.key === 'top') { mesh.rotation.x = Math.PI / 2; } // XZ 平面(法向 +Y) else if (d.key === 'right') { mesh.rotation.y = Math.PI / 2; } // YZ 平面(法向 +X) this.planeGroup.add(mesh); // 标签 const el = document.createElement('div'); el.className = 'minisw-label plane'; el.textContent = PLANES[d.key].name; - const pos = d.key === 'front' ? [0, 0, 150] : d.key === 'top' ? [150, 150, 0] : [0, 150, 0]; + const pos = d.key === 'front' ? [150, 150, 0] : d.key === 'top' ? [150, 0, 150] : [0, 150, 150]; const obj = new CSS2DObject(el); obj.position.set(...pos); this.planeGroup.add(obj); @@ -210,6 +236,28 @@ export class Viewport { setPlanesVisible(v) { this.planeGroup.visible = v; } + /** 让参照网格和基准面覆盖当前实体,避免大尺寸零件看起来“跑出地图”。 */ + updateWorkspaceGuides() { + const box = new THREE.Box3().setFromObject(this.bodyGroup); + if (box.isEmpty()) return; + const center = box.getCenter(new THREE.Vector3()); + const size = box.getSize(new THREE.Vector3()); + const required = Math.max(500, Math.ceil(Math.max(size.x, size.y, size.z, 1) * 1.5 / 50) * 50); + const current = Number(this.groundGrid.userData.workspaceSize) || 500; + if (required !== current) { + this.scene.remove(this.groundGrid); + this.groundGrid.geometry?.dispose(); + if (Array.isArray(this.groundGrid.material)) this.groundGrid.material.forEach(m => m.dispose()); + else this.groundGrid.material?.dispose(); + this.groundGrid = new THREE.GridHelper(required, Math.min(2000, Math.max(50, Math.round(required / 10))), 0x4a5568, 0x2f3640); + this.groundGrid.userData.workspaceSize = required; + this.scene.add(this.groundGrid); + } + this.groundGrid.position.set(center.x, 0, center.z); + const guideScale = required / 500; + this.planeGroup.scale.setScalar(guideScale); + } + // ---------- 实体渲染 ---------- updateBody(meshData) { this._disposeGroup(this.bodyGroup); @@ -220,6 +268,23 @@ export class Viewport { const mat = new THREE.MeshStandardMaterial({ color: 0xb7c4d6, roughness: 0.6, metalness: 0.05, side: THREE.DoubleSide, flatShading: true }); const mesh = new THREE.Mesh(geo, mat); this.bodyGroup.add(mesh); + this.updateWorkspaceGuides(); + } + + updateComponents(components = []) { + this._disposeGroup(this.bodyGroup); + const palette = [0xb7c4d6, 0x86b8e8, 0xd7a96b, 0x8fd19e]; + for (let i = 0; i < components.length; i++) { + const c = components[i], md = c.renderMesh || c.mesh; + if (!c.visible || !md) continue; + const color = c.color ?? palette[i % palette.length]; + const geo = meshToGeo(md, color); + const mat = new THREE.MeshStandardMaterial({ color, roughness: 0.58, metalness: 0.08, side: THREE.DoubleSide, flatShading: true }); + const mesh = new THREE.Mesh(geo, mat); + mesh.userData.componentId = c.id; + this.bodyGroup.add(mesh); + } + this.updateWorkspaceGuides(); } _disposeGroup(group) { @@ -253,6 +318,31 @@ export class Viewport { return hits[0].object.userData.fid; } + /** 从当前可见实体表面附近拾取一条真实拓扑棱边。 */ + pickBodyEdge(x, y, edges, maxPixels = 18) { + const bodyMesh = this.bodyGroup.children.find(o => o.isMesh); + if (!bodyMesh || !Array.isArray(edges) || !edges.length) return null; + const raycaster = new THREE.Raycaster(); + raycaster.setFromCamera(this.toNDC(x, y), this.camera); + const hit = raycaster.intersectObject(bodyMesh, false)[0]; + if (!hit) return null; + const triangleIndex = Number.isInteger(hit.faceIndex) ? hit.faceIndex * 3 : null; + const visibleCandidates = triangleIndex == null ? edges : edges.filter(edge => edge.triangleIndices?.includes(triangleIndex)); + const candidates = visibleCandidates.length ? visibleCandidates : edges; + const rect = this.container.getBoundingClientRect(); + const project = p => { + const v = new THREE.Vector3(...p).project(this.camera); + return [rect.left + (v.x + 1) * rect.width / 2, rect.top + (1 - v.y) * rect.height / 2]; + }; + let best = null, bestDistance = Infinity; + for (const edge of candidates) { + const a = project(edge.a), b = project(edge.b); + const distance = pointSegmentDistance2D([x, y], a, b); + if (distance < bestDistance) { best = edge; bestDistance = distance; } + } + return bestDistance <= maxPixels ? best : null; + } + buildPickTargets(deltas) { this.clearPickTargets(); for (const [fid, md] of Object.entries(deltas || {})) { @@ -291,7 +381,7 @@ export class Viewport { screenToSketchUV(x, y, sk) { const ray = new THREE.Raycaster(); ray.setFromCamera(this.toNDC(x, y), this.camera); - const nrm = new THREE.Vector3(...PLANES[sk.plane].normal); + const nrm = new THREE.Vector3(...planeDef(sk.plane).normal); const origin = new THREE.Vector3(...toWorld(0, 0, 0, sk.plane, sk.offset, 1)); const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(nrm, origin); const hit = new THREE.Vector3(); @@ -330,6 +420,18 @@ export class Viewport { } } +function planeDef(plane) { + if (!plane || typeof plane === 'string') return PLANES[plane] || PLANES.front; + return { normal: plane.normal, uAxis: plane.xAxis, vAxis: plane.yAxis }; +} + +function pointSegmentDistance2D(p, a, b) { + const dx = b[0] - a[0], dy = b[1] - a[1]; + const l2 = dx * dx + dy * dy; + const t = l2 > 1e-12 ? Math.max(0, Math.min(1, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / l2)) : 0; + return Math.hypot(p[0] - (a[0] + dx * t), p[1] - (a[1] + dy * t)); +} + function meshToGeo(md, color) { const geo = new THREE.BufferGeometry(); geo.setAttribute('position', new THREE.BufferAttribute(md.verts.slice(), 3)); diff --git a/tests/loader.mjs b/tests/loader.mjs index 718ba96..2a80ace 100644 --- a/tests/loader.mjs +++ b/tests/loader.mjs @@ -1,14 +1,14 @@ -// loader.mjs —— 把浏览器裸导入映射到本地 node_modules(仅测试用) +// loader.mjs —— 把浏览器裸导入映射到仓库内 vendor 依赖(仅测试用) import { pathToFileURL } from 'node:url'; -import { createRequire } from 'node:module'; -const require = createRequire(import.meta.url); +import { dirname, resolve as resolvePath } from 'node:path'; +import { fileURLToPath } from 'node:url'; -const NM = 'c:/Users/32919/.trae-cn/work/6a78857c0515f1e48ec3076f/node_modules'; +const ROOT = resolvePath(dirname(fileURLToPath(import.meta.url)), '..'); const map = { - 'three': pathToFileURL(NM + '/three/build/three.module.js').href, - 'three/addons/controls/OrbitControls.js': pathToFileURL(require.resolve('three/examples/jsm/controls/OrbitControls.js')).href, - 'three/addons/renderers/CSS2DRenderer.js': pathToFileURL(require.resolve('three/examples/jsm/renderers/CSS2DRenderer.js')).href, - 'manifold-3d': pathToFileURL(NM + '/manifold-3d/manifold.js').href, + 'three': pathToFileURL(resolvePath(ROOT, 'vendor/three/three.module.js')).href, + 'three/addons/controls/OrbitControls.js': pathToFileURL(resolvePath(ROOT, 'vendor/three/examples/jsm/controls/OrbitControls.js')).href, + 'three/addons/renderers/CSS2DRenderer.js': pathToFileURL(resolvePath(ROOT, 'vendor/three/examples/jsm/renderers/CSS2DRenderer.js')).href, + 'manifold-3d': pathToFileURL(resolvePath(ROOT, 'vendor/manifold/manifold.js')).href, }; export async function resolve(specifier, context, next) { diff --git a/tests/test.agent-crud.mjs b/tests/test.agent-crud.mjs new file mode 100644 index 0000000..214f004 --- /dev/null +++ b/tests/test.agent-crud.mjs @@ -0,0 +1,255 @@ +import { initGeometry } from '../js/geometry.js'; +import { Doc } from '../js/doc.js'; +import { + parseNaturalLanguage, + executeOps, + expandOps, + classifyIntent, + interpretAgentRequest, + applyAgentMutation, + repairOps, + validateOps, + validateOpsAgainstDocument, + inspectDocument, + describeInspection, + buildVisualFeedback, + reviewGeneratedModel, + decideLocalRequest, + runLLM, + runAgentChat, + repairLLMPlan, +} from '../js/agent.js'; + +await initGeometry(() => {}); +let pass = 0; +const ok = (value, message) => { + if (!value) throw new Error(message); + pass++; +}; + +const doc = new Doc(); +executeOps(doc, parseNaturalLanguage('做一个120×80×12的底板,加一条加强筋,厚度8mm,筋高50mm')); +doc.rebuild(); +ok(doc.features.length === 2 && !doc.features.some(f => f.error), '基线实体建立'); + +const observation = inspectDocument(doc); +ok(observation.hasBody && observation.status === 'NoError' && observation.dimensions.every((v, i) => Math.abs(v - [120, 80, 56][i]) < 1e-6), '几何自观察读取尺寸与实体状态'); +ok(describeInspection(observation).includes('体积') && describeInspection(observation).includes('加强筋'), '几何自观察生成可读描述'); + +const query = interpretAgentRequest('查询当前模型有哪些特征和尺寸', doc); +ok(query.kind === 'query' && query.reply.includes('120.00×80.00×56.00'), '查询意图返回尺寸与特征'); +const visionQuery = interpretAgentRequest('你能看到什么?请描述当前模型', doc); +ok(visionQuery.kind === 'query' && visionQuery.reply.includes('我观察到') && visionQuery.reply.includes('体积'), '自然语言自观察查询'); +const localExact = decideLocalRequest('做一个120×80×12的底板', doc); +ok(localExact?.kind === 'ops' && localExact.ops.some(op => op.op === 'extrude'), '完整尺寸请求由本地精确闸门直接执行'); +ok(!decideLocalRequest('椅子', doc) && !decideLocalRequest('做一个锥齿轮', doc), '对象或关键参数模糊时本地拒绝擅自补全并转交 LLM'); + +const update = interpretAgentRequest('把加强筋厚度改为6mm,筋高改为40mm', doc); +ok(update.kind === 'update' && update.patch.thickness === 6 && update.patch.extent === 40, '修改意图解析参数'); +applyAgentMutation(doc, update); +doc.rebuild(); +ok(doc.features.at(-1).params.thickness === 6 && doc.features.at(-1).params.extent === 40 && !doc.features.some(f => f.error), '修改后全树重建'); + +const add = interpretAgentRequest('在当前零件上添加一个直径20mm的通孔', doc); +ok(add.kind === 'ops' && add.intent === 'add' && add.ops.at(-1).op === 'cut' && !validateOps(add.ops), '追加通孔生成局部操作'); + +const del = interpretAgentRequest('删除最后一个特征', doc); +ok(del.kind === 'delete' && del.featureId === doc.features.at(-1).id, '删除意图定位最后特征'); +applyAgentMutation(doc, del); +doc.rebuild(); +ok(!doc.features.some(f => f.error), '删除后全树重建'); + +const repaired = repairOps( + [{ op: 'sketch', plane: 'front', name: '草图' }, { op: 'extrude', depth: 10, name: '主体' }, { op: 'fillet_edge', radius: 5, name: '失败圆角' }], + [{ type: 'fillet', name: '失败圆角', error: '仅支持凸实体' }], +); +ok(repaired.removed.length === 1 && repaired.ops.length === 2, '错误操作自动修正计划'); + +const llmCrudDoc = new Doc(); +executeOps(llmCrudDoc, [{ op: 'cylinder', r: 12, h: 20, name: '主体' }, { op: 'cylinder', r: 6, h: 8, tz: 14, name: '凸台' }]); +llmCrudDoc.rebuild(); +const llmTargetId = llmCrudDoc.features.at(-1).id; +ok(!validateOps([{ op: 'update_feature', featureId: llmTargetId, patch: { h: 12 } }]) && !validateOps([{ op: 'delete_feature', featureId: llmTargetId }]), 'LLM 修改/删除 DSL 可校验'); +executeOps(llmCrudDoc, [{ op: 'update_feature', featureId: llmTargetId, patch: { h: 12 } }]); +ok(llmCrudDoc.featureById(llmTargetId).params.h === 12, 'LLM 修改操作作用于稳定特征 ID'); +executeOps(llmCrudDoc, [{ op: 'delete_feature', featureId: llmTargetId }]); +ok(!llmCrudDoc.featureById(llmTargetId) && llmCrudDoc.features.length === 1, 'LLM 删除操作作用于稳定特征 ID'); +const queryResult = executeOps(llmCrudDoc, [{ op: 'query_document', answer: '当前有一个主体特征。' }]); +ok(queryResult.answers[0].includes('一个主体'), 'LLM 查询操作返回最终回答'); +ok(validateOpsAgainstDocument(llmCrudDoc, [{ op: 'delete_feature', featureId: 999999 }]).includes('不存在'), '执行前检查拦截失效稳定特征 ID'); +ok(validateOpsAgainstDocument(new Doc(), [{ op: 'assembly_add_component', id: 'part' }]).includes('前置零件实体'), '执行前检查拦截缺少实体的装配计划'); + +const originalFetch = globalThis.fetch; +let capturedRequest = null; +globalThis.fetch = async (_url, init) => { + capturedRequest = JSON.parse(init.body); + return { ok: true, json: async () => ({ choices: [{ message: { content: '[{"op":"cylinder","r":10,"h":20}]' } }] }) }; +}; +try { + const fastOps = await runLLM('做一个圆柱', { baseURL: 'https://example.invalid/v1', apiKey: 'test', model: 'fast-model', speed: 'fast', timeoutMs: 5000 }, { + modelState: '{"hasBody":true}', + history: Array.from({ length: 8 }, (_, i) => ({ role: i % 2 ? 'assistant' : 'user', content: String(i) })), + }); + ok(fastOps.length === 1 && fastOps[0].op === 'cylinder', '快速 LLM 输出可直接校验'); + ok(capturedRequest.temperature === undefined && capturedRequest.max_tokens === undefined && capturedRequest.thinking === undefined, 'LLM 请求不注入额外输出和推理约束'); + ok(capturedRequest.response_format === undefined, 'LLM 请求不强制响应格式'); + ok(capturedRequest.messages.length === 11, '统一 Agent 不截断历史上下文'); + + let toolRound = 0; + globalThis.fetch = async (_url, init) => { + const body = JSON.parse(init.body); + toolRound++; + if (toolRound === 1) return { ok: true, json: async () => ({ choices: [{ message: { content: null, tool_calls: [{ id: 'call-inspect', type: 'function', function: { name: 'inspect_model', arguments: '{}' } }] } }] }) }; + return { ok: true, json: async () => ({ choices: [{ message: { content: '已读取模型并完成判断。' } }] }) }; + }; + const chatResult = await runAgentChat('查看当前模型', { baseURL: 'https://example.invalid/v1', apiKey: 'test', model: 'tool-model' }, { + toolHandlers: { inspect_model: async () => ({ hasBody: true, features: [] }) }, + }); + ok(chatResult.reply.includes('已读取模型') && toolRound === 2, '聊天 Agent 能自主调用工具并继续对话'); + + let jsonFallbackCalls = 0; + globalThis.fetch = async () => { + jsonFallbackCalls++; + if (jsonFallbackCalls === 1) return { ok: false, status: 400, json: async () => ({ error: { message: 'response_format is not supported' } }) }; + return { ok: true, json: async () => ({ choices: [{ message: { content: '[{"op":"cylinder","r":8,"h":12}]' } }] }) }; + }; + const fallbackOps = await runLLM('做一个小圆柱', { baseURL: 'https://example.invalid/v1', apiKey: 'test', model: 'compatible-model', speed: 'fast', timeoutMs: 5000 }); + ok(jsonFallbackCalls === 2 && fallbackOps[0].op === 'cylinder', '不支持结构化输出的兼容接口会自动降级且仍能完成计划'); + + globalThis.fetch = async () => ({ ok: true, json: async () => ({ choices: [{ message: { content: `[{"op":"delete_feature","featureId":${llmCrudDoc.features[0].id}}]` } }] }) }); + const deleteOps = await runLLM('删除', { baseURL: 'https://example.invalid/v1', apiKey: 'test', model: 'fast-model', speed: 'fast', timeoutMs: 5000 }, { modelState: JSON.stringify({ hasBody: true, selectedFeatureId: llmCrudDoc.features[0].id, lastFeatureId: llmCrudDoc.features[0].id }) }); + ok(deleteOps[0].op === 'delete_feature' && deleteOps[0].featureId === llmCrudDoc.features[0].id, 'LLM 模式理解裸“删除”并使用当前稳定 ID'); + + let expertDeleteCalls = 0; + globalThis.fetch = async () => { + expertDeleteCalls++; + const content = `{"ops":[{"op":"delete_feature","featureId":${llmCrudDoc.features[0].id}}]}`; + return { ok: true, json: async () => ({ choices: [{ message: { content } }] }) }; + }; + const expertDeleteOps = await runLLM('删除', { baseURL: 'https://example.invalid/v1', apiKey: 'test', model: 'precise-model', speed: 'precise', timeoutMs: 5000 }, { modelState: JSON.stringify({ hasBody: true, selectedFeatureId: llmCrudDoc.features[0].id, lastFeatureId: llmCrudDoc.features[0].id }) }); + ok(expertDeleteCalls === 1 && expertDeleteOps.agentMeta.calls === 1 && expertDeleteOps.agentMeta.mode === 'unified', '模糊删除请求交给统一 Agent 一次性结合模型状态判断'); + + let preciseCalls = 0; + globalThis.fetch = async () => { + preciseCalls++; + const content = '{"ops":[{"op":"sketch","plane":"front"},{"op":"rect","cx":0,"cy":0,"w":80,"h":50},{"op":"extrude","depth":10},{"op":"drawing_generate","format":"A4"}]}'; + return { ok: true, json: async () => ({ choices: [{ message: { content } }] }) }; + }; + const preciseOps = await runLLM('新建一个80×50×10的底座并生成A4工程图', { baseURL: 'https://example.invalid/v1', apiKey: 'test', model: 'precise-model', speed: 'precise', timeoutMs: 5000 }); + ok(preciseCalls === 1, '统一 Agent 一次调用完成复杂建模与工程图意图'); + ok(preciseOps.some(op => op.op === 'extrude') && preciseOps.some(op => op.op === 'drawing_generate'), '统一规划覆盖建模与工程图意图'); + + let malformedCalls = 0; + globalThis.fetch = async () => { + malformedCalls++; + const responses = [ + '{"ops":[{"op":"sketch","plane":"front"},{"op":"chamfer_rect","cx":0,"cy":0,"w":100,"h":80,"chamfer":5},{"op":"extrude","depth":20}]}', + '{"ops":[{"op":"sketch","plane":"front"},{"op":"chamfer_rect","cx":0,"cy":0,"w":100,"h":80,"chamfer":5},{"op":"extrude","depth":20}]}', + ]; + return { ok: true, json: async () => ({ choices: [{ message: { content: responses[malformedCalls - 1] } }] }) }; + }; + const malformedRecovered = await runLLM('做一个100×80×20的矩形板,四边倒角5mm', { baseURL: 'https://example.invalid/v1', apiKey: 'test', model: 'precise-model', speed: 'precise', timeoutMs: 5000 }); + ok(malformedCalls === 1, '统一 Agent 直接输出可执行 DSL,无需额外意图阶段'); + ok(malformedRecovered.some(op => op.op === 'extrude'), '修复意图后继续生成可执行 DSL'); + + let emptyContentCalls = 0; + globalThis.fetch = async () => { + emptyContentCalls++; + if (emptyContentCalls === 1) return { ok: true, json: async () => ({ choices: [{ finish_reason: 'stop', message: { content: null, reasoning_content: '正在分析尺寸与空心结构' } }] }) }; + const content = '{"ops":[{"op":"cylinder","r":30,"h":50},{"op":"cylcut","r":20,"h":52}]}' + return { ok: true, json: async () => ({ choices: [{ message: { content } }] }) }; + }; + const emptyRecovered = await runLLM('空心圆柱,外径60,内径40,高50', { baseURL: 'https://example.invalid/v1', apiKey: 'test', model: 'reasoning-model', speed: 'precise', timeoutMs: 5000 }); + ok(emptyContentCalls === 2, '统一 Agent 空内容只进行一次紧凑重试'); + ok(emptyRecovered.some(op => op.op === 'cylcut'), '空内容恢复后继续完成空心圆柱计划'); + + const gearRequests = []; + globalThis.fetch = async (_url, init) => { + gearRequests.push(JSON.parse(init.body)); + return { ok: true, json: async () => ({ choices: [{ message: { content: '{"ops":[{"op":"spur_gear","module":2,"teeth":20,"width":20,"pressureAngle":20,"boreDiameter":10,"name":"直齿轮"}]}' } }] }) }; + }; + const gearOps = await runLLM('直齿轮', { baseURL: 'https://example.invalid/v1', apiKey: 'test', model: 'reasoning-model', speed: 'precise', timeoutMs: 5000 }); + ok(gearRequests.length === 1 && gearOps.agentMeta.calls === 1 && gearOps.agentMeta.mode === 'unified', '模糊齿轮请求交给统一 Agent 一次性补全并规划'); + ok(gearOps.length === 1 && gearOps[0].op === 'spur_gear' && gearOps[0].teeth === 20 && gearOps[0].boreDiameter === 10, '直齿轮使用单个参数化高级指令,并为未指定孔径采用合理默认值'); + const gearDoc = new Doc(); + executeOps(gearDoc, gearOps); + gearDoc.rebuild(); + ok(!gearDoc.features.some(feature => feature.error) && Math.abs(gearDoc.bbox.max[0] - gearDoc.bbox.min[0] - 44) < 0.1 && Math.abs(gearDoc.bbox.max[2] - gearDoc.bbox.min[2] - 20) < 0.1, '参数化编译器生成外径44、齿宽20的有效渐开线直齿轮'); + + const bevelOps = [{ op: 'bevel_gear', module: 2, teeth: 20, width: 8, pressureAngle: 20, shaftAngle: 90, pitchConeAngle: 45, boreDiameter: 10, name: '锥齿轮' }]; + ok(!validateOps(bevelOps) && expandOps(bevelOps)[0].op === 'loft', '锥齿轮使用独立 bevel_gear,并展开为锥形放样'); + const bevelDoc = new Doc(); + executeOps(bevelDoc, bevelOps); + bevelDoc.rebuild(); + ok(!bevelDoc.features.some(feature => feature.error) && Math.abs(bevelDoc.bbox.max[2] - bevelDoc.bbox.min[2] - 8) < 0.1, '锥齿轮放样实体可重建且保持齿宽'); + let chairCalls = 0; + globalThis.fetch = async () => { + chairCalls++; + const content = '{"ops":[{"op":"cylinder","r":250,"h":40,"tz":450,"name":"默认座面"}]}'; + return { ok: true, json: async () => ({ choices: [{ finish_reason: content ? 'stop' : 'length', message: { content } }] }) }; + }; + const chairOps = await runLLM('椅子', { baseURL: 'https://example.invalid/v1', apiKey: 'test', model: 'reasoning-model', speed: 'precise', timeoutMs: 5000 }); + ok(chairCalls === 1 && chairOps.agentMeta.calls === 1 && chairOps[0].op === 'cylinder', '模糊对象交给统一 Agent 补充默认工程假设后完成 DSL'); + + globalThis.fetch = async () => ({ ok: true, json: async () => ({ choices: [{ message: { content: '[{"op":"cylinder","r":24,"h":10,"axis":undefined},{"op":"pattern_circular","count":20,"cx":NaN,"cy":0,}]' } }] }) }); + const jsLikeOps = await runLLM('做一个圆柱', { baseURL: 'https://example.invalid/v1', apiKey: 'test', model: 'reasoning-model', speed: 'fast', timeoutMs: 5000 }); + ok(jsLikeOps[0].axis === null && jsLikeOps[1].cx === null, 'DSL 自动修复 undefined、NaN 和尾逗号等非 JSON 值'); + + let expressionCalls = 0; + globalThis.fetch = async () => { + expressionCalls++; + const content = expressionCalls === 1 + ? '[{"op":"cylinder","r":s.p/(2r_p),"h":10}]' + : '[{"op":"cylinder","r":24,"h":10}]'; + return { ok: true, json: async () => ({ choices: [{ message: { content } }] }) }; + }; + const expressionOps = await runLLM('做一个圆柱', { baseURL: 'https://example.invalid/v1', apiKey: 'test', model: 'reasoning-model', speed: 'fast', timeoutMs: 5000 }); + ok(expressionCalls === 2 && expressionOps[0].r === 24, '未加引号的工程表达式会被拦截并触发 DSL 重规划'); + + let loneMinusCalls = 0; + globalThis.fetch = async () => { + loneMinusCalls++; + const content = loneMinusCalls === 1 + ? '[{"op":"cylinder","r":-,"h":10}]' + : '[{"op":"cylinder","r":24,"h":10}]'; + return { ok: true, json: async () => ({ choices: [{ message: { content } }] }) }; + }; + const loneMinusOps = await runLLM('做一个圆柱', { baseURL: 'https://example.invalid/v1', apiKey: 'test', model: 'reasoning-model', speed: 'fast', timeoutMs: 5000 }); + ok(loneMinusCalls === 2 && loneMinusOps[0].r === 24, '孤立负号会被拦截并触发 DSL 重规划'); + + const streamEvents = []; + globalThis.fetch = async () => { + const frames = [ + { choices: [{ delta: { content: '{"ops":[' } }] }, + { choices: [{ delta: { content: '{"op":"cylinder","r":24,"h":10}' } }] }, + { choices: [{ delta: { content: ']}' } }] }, + ]; + const encoder = new TextEncoder(); + const body = new ReadableStream({ + start(controller) { + for (const frame of frames) controller.enqueue(encoder.encode(`data: ${JSON.stringify(frame)}\n\n`)); + controller.enqueue(encoder.encode('data: [DONE]\n\n')); + controller.close(); + }, + }); + return new Response(body, { status: 200, headers: { 'content-type': 'text/event-stream' } }); + }; + const streamedOps = await runLLM('做一个圆柱', { baseURL: 'https://api.deepseek.com/v1', apiKey: 'test', model: 'deepseek-chat', speed: 'fast', timeoutMs: 5000 }, { onStream: event => streamEvents.push(event) }); + ok(streamedOps[0].op === 'cylinder' && streamEvents.some(event => !event.done && event.accumulated.includes('cylinder')) && streamEvents.at(-1).done, 'DeepSeek SSE 流式输出实时转发调试窗口并保持最终 DSL 解析'); + + globalThis.fetch = async () => ({ ok: true, json: async () => ({ choices: [{ message: { content: '[{"op":"sketch","plane":"front"},{"op":"rect","cx":0,"cy":0,"w":80,"h":50},{"op":"extrude","depth":10},{"op":"drawing_generate","format":"A4"}]' } }] }) }); + const visualFeedback = buildVisualFeedback(observation); + ok(visualFeedback.observer.includes('local geometry') && visualFeedback.placement.dimensions[2] === 56, '视觉模块生成模型位置与尺寸反馈'); + globalThis.fetch = async () => ({ ok: true, json: async () => ({ choices: [{ message: { content: '{"pass":true,"severity":"info","summary":"几何结构与用户意图一致","findings":[],"correction":""}' } }] }) }); + const reviewed = await reviewGeneratedModel('做一个底板并加加强筋', { baseURL: 'https://example.invalid/v1', apiKey: 'test', model: 'review-model', speed: 'precise', timeoutMs: 5000 }, {}, [{ op: 'extrude' }, { op: 'rib' }], observation); + ok(reviewed.pass && reviewed.calls === 1 && reviewed.feedback.featureTree.length === 2, '生成后由机械制图专家完成一次视觉复核'); + + globalThis.fetch = async () => ({ ok: true, json: async () => ({ choices: [{ message: { content: '[{"op":"sketch","plane":"front"},{"op":"rect","cx":0,"cy":0,"w":80,"h":50},{"op":"extrude","depth":10},{"op":"drawing_generate","format":"A4"}]' } }] }) }); + const correctedOps = await repairLLMPlan('新建一个底座并生成工程图', { baseURL: 'https://example.invalid/v1', apiKey: 'test', model: 'repair-model', speed: 'fast', timeoutMs: 5000 }, {}, [{ op: 'drawing_generate' }], '生成工程图前需要当前零件实体'); + ok(correctedOps.some(op => op.op === 'extrude'), '执行错误会回传 LLM 并补全前置实体'); + ok(correctedOps.agentMeta.calls === 1 && correctedOps.agentMeta.trace[0].stage === 'execution_repair', 'LLM 纠错计划记录调用轨迹'); +} finally { + globalThis.fetch = originalFetch; +} + +console.log(`agent CRUD/vision ${pass}/${pass} passed`); diff --git a/tests/test.assembly.mjs b/tests/test.assembly.mjs new file mode 100644 index 0000000..2538a44 --- /dev/null +++ b/tests/test.assembly.mjs @@ -0,0 +1,16 @@ +import assert from 'node:assert/strict'; +import { createAssembly, addComponent, addMate, solveMates, checkInterference, setExplode, assemblyFromJSON } from '../js/features/assembly.js'; + +const cube = (x = 10) => ({ verts: new Float32Array([-x,-x,-x, x,-x,-x, x,x,-x, -x,x,-x, -x,-x,x, x,-x,x, x,x,x, -x,x,x]), tris: new Uint32Array([0,1,2,0,2,3,4,6,5,4,7,6,0,4,5,0,5,1,1,5,6,1,6,2,2,6,7,2,7,3,4,0,3,4,3,7]) }); +const a = createAssembly('滑块机构'); +const base = addComponent(a, { id: 'base', name: '底座', mesh: cube(), fixed: true }); +const slide = addComponent(a, { id: 'slide', name: '滑块', mesh: cube(), transform: { position: [30, 0, 0] } }); +addMate(a, { type: 'coincident', refA: { componentId: 'base', position: [0, 0, 0] }, refB: { componentId: 'slide', position: [0, 0, 0] } }); +const report = solveMates(a); +assert.equal(report.at(-1).status, 'solved'); +assert.deepEqual(slide.transform.position, [0, 0, 0]); +assert.equal(checkInterference(base, slide).interfering, true); +setExplode(a, { progress: 1, vectors: { slide: [0, 0, 25] } }); +assert.equal(slide.transform.position[2], 25); +assert.equal(assemblyFromJSON(JSON.parse(JSON.stringify(a))).components.length, 2); +console.log('assembly 5/5 passed'); diff --git a/tests/test.chamfer.mjs b/tests/test.chamfer.mjs new file mode 100644 index 0000000..d9f8a92 --- /dev/null +++ b/tests/test.chamfer.mjs @@ -0,0 +1,48 @@ +import assert from 'node:assert/strict'; +import { extractTopology, makeStableEdgeRef, resolveEdgeRef, buildChamferMesh } from '../js/features/chamfer.js'; +import { initGeometry, manifoldFromMeshData } from '../js/geometry.js'; + +await initGeometry(); + +function cube(size = 20) { + const v = [-10,-10,-10, 10,-10,-10, 10,10,-10, -10,10,-10, -10,-10,10, 10,-10,10, 10,10,10, -10,10,10]; + // 朝外三角面。 + const t = [0,2,1, 0,3,2, 4,5,6, 4,6,7, 0,1,5, 0,5,4, 1,2,6, 1,6,5, 2,3,7, 2,7,6, 3,0,4, 3,4,7]; + return { verts: new Float32Array(v.map(x => x * size / 20)), tris: new Uint32Array(t) }; +} +function closed(mesh, tol = 1e-6) { extractTopology(mesh, tol); return true; } +function hasNoDegenerate(mesh, tol = 1e-7) { for (let i = 0; i < mesh.tris.length; i += 3) { const p = j => [mesh.verts[mesh.tris[i+j]*3], mesh.verts[mesh.tris[i+j]*3+1], mesh.verts[mesh.tris[i+j]*3+2]]; const a=p(0),b=p(1),c=p(2); const area2=Math.hypot((b[1]-a[1])*(c[2]-a[2])-(b[2]-a[2])*(c[1]-a[1]),(b[2]-a[2])*(c[0]-a[0])-(b[0]-a[0])*(c[2]-a[2]),(b[0]-a[0])*(c[1]-a[1])-(b[1]-a[1])*(c[0]-a[0])); assert.ok(area2 > tol * tol); } } +function pickEdge(mesh) { const topo = extractTopology(mesh); const edge = topo.edges.find(e => e.a[0] === -10 && e.b[0] === 10 && e.a[1] === -10 && e.b[1] === -10); assert.ok(edge, '应找到立方体底前棱'); return { topo, edge, ref: makeStableEdgeRef(edge, topo) }; } + +let passed = 0; +function test(name, fn) { try { fn(); passed++; console.log(`✓ ${name}`); } catch (err) { console.error(`✗ ${name}: ${err.message}`); throw err; } } + +test('立方体单边 5mm 倒角生成真实封闭网格', () => { + const m = cube(), { ref } = pickEdge(m); + const out = buildChamferMesh({ mesh: m, edgeRefs: [ref], distance: 5 }); + assert.ok(out.tris.length > m.tris.length, '倒角应增加真实几何面'); + assert.equal(out.selectedEdges.length, 1); closed(out); hasNoDegenerate(out); + const solid = manifoldFromMeshData(out, '倒角测试网格'); + assert.equal(solid.status(), 'NoError', '内核应接受焊接后的封闭流形'); + assert.ok(solid.volume() > 0 && solid.volume() < 8000, '倒角后体积应为合理正值'); + solid.delete(); +}); +test('稳定边引用在等价重建中唯一解析', () => { const m = cube(), { topo, ref } = pickEdge(m); assert.equal(resolveEdgeRef(JSON.parse(JSON.stringify(ref)), topo).key.length > 0, true); }); +test('距离过大、引用失效和多边选择给出中文诊断', () => { + const m = cube(), { ref } = pickEdge(m); + assert.throws(() => buildChamferMesh({ mesh:m, edgeRefs:[ref], distance:20 }), /倒角距离过大/); + const bad = { ...ref, signature: 'missing' }; assert.throws(() => buildChamferMesh({ mesh:m, edgeRefs:[bad], distance:2 }), /引用已失效|目标边不存在/); + assert.throws(() => buildChamferMesh({ mesh:m, edgeRefs:[ref, ref], distance:2 }), /一条凸直棱/); +}); +test('非封闭与凹/非凸输入被拒绝', () => { + const m = cube(); const open = { verts:m.verts, tris:m.tris.slice(0, -3) }; assert.throws(() => extractTopology(open), /封闭二流形/); + const concave = cube(); concave.verts[18] = 0; concave.verts[19] = 0; concave.verts[20] = 0; + const concaveEdge = extractTopology(concave).edges.find(e => e.a[0] === -10 && e.b[0] === 10 && e.a[1] === -10 && e.b[1] === -10); + assert.throws(() => buildChamferMesh({ mesh:concave, edgeRefs:[concaveEdge], distance:2 }), /凸实体|非凸/); +}); +test('连续 100 次重建稳定且小于一秒', () => { + const m=cube(), { ref }=pickEdge(m), start=performance.now(); let count=0; + for(let i=0;i<100;i++) { const out=buildChamferMesh({mesh:m,edgeRefs:[ref],distance:5}); count += out.tris.length; } + assert.ok(count > 0); assert.ok(performance.now()-start < 1000, '100 次小立方体倒角应小于 1 秒'); +}); +console.log(`倒角独立测试通过 ${passed}/5`); diff --git a/tests/test.drawing.mjs b/tests/test.drawing.mjs new file mode 100644 index 0000000..edd090c --- /dev/null +++ b/tests/test.drawing.mjs @@ -0,0 +1,13 @@ +import assert from 'node:assert/strict'; +import { createDrawing, layoutThreeViews, addView, addDimension, addSectionView, renderDrawingSVG, drawingFromJSON } from '../js/features/drawing.js'; + +const mesh = { verts: new Float32Array([-10,-5,0,10,-5,0,10,5,0,-10,5,0,-10,-5,20,10,-5,20,10,5,20,-10,5,20]), tris: new Uint32Array([0,1,2,0,2,3,4,6,5,4,7,6,0,4,5,0,5,1,1,5,6,1,6,2,2,6,7,2,7,3,4,0,3,4,3,7]) }; +const d = createDrawing({ name: '测试零件' }); +for (const v of layoutThreeViews(mesh, { scale: 2 })) addView(d, v); +addDimension(d, { value: 20, text: '20 ±0.1', a: [20, 60], b: [60, 60], tolerance: '' }); +addSectionView(d, { mesh }); +assert.ok(d.views.at(-1).projection?.segments?.length > 0, '剖视图应包含剖面轮廓'); +const svg = renderDrawingSVG(d); +assert.ok(svg.startsWith(' e.a[0] === -10 && e.b[0] === 10 && e.a[1] === -10 && e.b[1] === -10); + assert.ok(edge, '应找到立方体底前棱'); + return { topo, edge, ref: makeStableEdgeRef(edge, topo) }; +} +function noDegenerate(mesh) { + for (let i = 0; i < mesh.tris.length; i += 3) { + const p = j => { const k = mesh.tris[i + j] * 3; return [mesh.verts[k], mesh.verts[k + 1], mesh.verts[k + 2]]; }; + const a=p(0), b=p(1), c=p(2), u=[b[0]-a[0],b[1]-a[1],b[2]-a[2]], v=[c[0]-a[0],c[1]-a[1],c[2]-a[2]]; + assert.ok(Math.hypot(u[1]*v[2]-u[2]*v[1],u[2]*v[0]-u[0]*v[2],u[0]*v[1]-u[1]*v[0]) > 1e-7); + } +} +function faceNormalsAlongFillet(mesh) { + const seen = new Set(); + for (let i = 0; i < mesh.tris.length; i += 3) { + const ids=[mesh.tris[i],mesh.tris[i+1],mesh.tris[i+2]], p=ids.map(id=>[mesh.verts[id*3],mesh.verts[id*3+1],mesh.verts[id*3+2]]); + const u=[p[1][0]-p[0][0],p[1][1]-p[0][1],p[1][2]-p[0][2]],v=[p[2][0]-p[0][0],p[2][1]-p[0][1],p[2][2]-p[0][2]]; + const n=[u[1]*v[2]-u[2]*v[1],u[2]*v[0]-u[0]*v[2],u[0]*v[1]-u[1]*v[0]], l=Math.hypot(...n); + if (l && Math.abs(n[1]) > 0.02 && Math.abs(n[2]) > 0.02 && Math.abs(n[0]) < 1e-4) seen.add(`${Math.round(n[1]/l*1000)},${Math.round(n[2]/l*1000)}`); + } + return seen.size; +} +let passed=0; +function test(name, fn) { try { fn(); passed++; console.log(`✓ ${name}`); } catch (e) { console.error(`✗ ${name}: ${e.message}`); throw e; } } + +test('单凸直棱生成闭合、正体积的多段真实圆角', () => { + const mesh=cube(), {ref}=pick(mesh), out=buildFilletMesh({mesh,edgeRefs:[ref],radius:4,segments:8}); + assert.ok(out.tris.length > mesh.tris.length); assert.equal(out.segments, 8); extractTopology(out); noDegenerate(out); + assert.ok(faceNormalsAlongFillet(out) >= 8, '圆角带应至少包含每段不同的过渡法向'); + const solid=manifoldFromMeshData(out, '圆角测试网格'); + assert.equal(solid.status(), 'NoError'); assert.ok(solid.volume()>0 && solid.volume()<8000); solid.delete(); +}); +test('稳定边引用可序列化后唯一解析', () => { const mesh=cube(), {topo,ref}=pick(mesh); assert.equal(resolveEdgeRef(JSON.parse(JSON.stringify(ref)),topo).key.length>0,true); }); +test('过大半径、失效引用和多边选择被拒绝', () => { + const mesh=cube(), {ref}=pick(mesh); + assert.throws(()=>buildFilletMesh({mesh,edgeRefs:[ref],radius:20}),/圆角半径过大/); + assert.throws(()=>buildFilletMesh({mesh,edgeRefs:[{...ref,signature:'missing'}],radius:2}),/引用已失效|目标边不存在/); + assert.throws(()=>buildFilletMesh({mesh,edgeRefs:[ref,ref],radius:2}),/一条凸直棱/); +}); +test('凹体和退化分段被拒绝', () => { + const concave=cube(); concave.verts[18]=0; concave.verts[19]=0; concave.verts[20]=0; + const edge=extractTopology(concave).edges.find(e=>e.a[0]===-10&&e.b[0]===10&&e.a[1]===-10&&e.b[1]===-10); + assert.throws(()=>buildFilletMesh({mesh:concave,edgeRefs:[edge],radius:2}),/凸实体|凹边|非凸/); + const mesh=cube(), {ref}=pick(mesh); assert.throws(()=>buildFilletMesh({mesh,edgeRefs:[ref],radius:2,segments:1}),/分段数/); +}); +test('重复构建性能稳定', () => { + const mesh=cube(), {ref}=pick(mesh), start=performance.now(); let faces=0; + for(let i=0;i<20;i++) faces+=buildFilletMesh({mesh,edgeRefs:[ref],radius:4,segments:8}).tris.length; + assert.ok(faces>0); assert.ok(performance.now()-start<1000, '20 次小立方体圆角应小于 1 秒'); +}); +console.log(`圆角独立测试通过 ${passed}/5`); diff --git a/tests/test.loft.mjs b/tests/test.loft.mjs new file mode 100644 index 0000000..d84b9a8 --- /dev/null +++ b/tests/test.loft.mjs @@ -0,0 +1,47 @@ +import { alignProfileStart, buildLoftMesh, resampleClosedProfile } from '../js/features/loft.js'; + +let pass = 0, fail = 0; +const ok = (v, msg) => { if (v) { pass++; console.log(' OK ' + msg); } else { fail++; console.error(' FAIL ' + msg); } }; +const mustThrow = (fn, text) => { try { fn(); ok(false, text); } catch (e) { ok(e.message.includes('放样'), text + ':' + e.message); } }; +const square = (z, size = 10) => [[-size, -size, z], [size, -size, z], [size, size, z], [-size, size, z]]; +const circle = (z, r, n = 16) => Array.from({ length: n }, (_, i) => { const a = 2 * Math.PI * i / n; return [r * Math.cos(a), r * Math.sin(a), z]; }); +const signedVolume = m => { let v = 0; for (let i = 0; i < m.tris.length; i += 3) { const ids = [m.tris[i], m.tris[i + 1], m.tris[i + 2]]; const a = ids.map(q => m.verts[q * 3]); const b = ids.map(q => m.verts[q * 3 + 1]); const c = ids.map(q => m.verts[q * 3 + 2]); v += a[0] * (b[1] * c[2] - b[2] * c[1]) + a[1] * (b[2] * c[0] - b[0] * c[2]) + a[2] * (b[0] * c[1] - b[1] * c[0]); } return v / 6; }; +const closed = m => { const edges = new Map(); for (let i = 0; i < m.tris.length; i += 3) for (const [a, b] of [[m.tris[i], m.tris[i + 1]], [m.tris[i + 1], m.tris[i + 2]], [m.tris[i + 2], m.tris[i]]]) { const k = a < b ? a + ':' + b : b + ':' + a; edges.set(k, (edges.get(k) || 0) + 1); } return [...edges.values()].every(n => n === 2); }; + +console.log('== M03:等顶点双截面 =='); +const m03 = buildLoftMesh({ profiles: [square(0), square(20, 6)] }); +ok(m03.verts.length === 24 && m03.tris.length === 36, '矩形到矩形生成侧壁与两端封闭'); +ok(closed(m03) && signedVolume(m03) > 0, '输出闭合、朝外且体积为正'); +const reversed = buildLoftMesh({ profiles: [square(0), square(20).reverse()] }); +ok(closed(reversed) && signedVolume(reversed) > 0, '反向输入自动统一环方向'); +const aligned = alignProfileStart(square(0), [...square(20).slice(2), ...square(20).slice(0, 2)]); +ok(aligned[0][0] === -10 && aligned[0][1] === -10, '自动选择截面起点对齐'); + +console.log('== M04:多截面与异顶点数 =='); +const t0 = performance.now(); +const m04 = buildLoftMesh({ profiles: [square(0), circle(10, 8, 12), square(20, 5)], resampleCount: 24 }); +const elapsed = performance.now() - t0; +ok(m04.verts.length === 3 * 24 * 3 && closed(m04), '方—圆—方多截面弧长重采样放样'); +ok(signedVolume(m04) > 0, '多截面放样体积为正'); +ok(elapsed < 1000, `典型三截面构建 ${elapsed.toFixed(2)}ms(预算 <1s)`); +const json = JSON.parse(JSON.stringify({ profiles: [square(0), circle(20, 8, 8)], resampleCount: 16, capStart: true, capEnd: true })); +ok(closed(buildLoftMesh(json)), '截面引用参数可 JSON 序列化后重建'); +const sample = resampleClosedProfile(square(0), 12); +ok(sample.length === 12 && sample.every(p => p.length === 3), '闭合轮廓均匀弧长重采样'); +const planeProfile = { points: [[-5, -5], [5, -5], [5, 5], [-5, 5]], plane: { origin: [0, 0, 30], xAxis: [1, 0, 0], yAxis: [0, 1, 0] } }; +ok(closed(buildLoftMesh({ profiles: [square(20, 5), planeProfile] })), 'ProfileData 二维点和坐标平面可直接输入'); +let stable = true, expectedTriCount = 0; +for (let i = 0; i < 10; i++) { const mesh = buildLoftMesh({ profiles: [square(0), circle(10, 8, 12), square(20, 5)], resampleCount: 24 }); if (i && mesh.tris.length !== expectedTriCount) stable = false; expectedTriCount = mesh.tris.length; } +ok(stable, '连续 10 次重建网格规模稳定'); + +console.log('== 诊断 =='); +mustThrow(() => buildLoftMesh({ profiles: [square(0), square(0)] }), '共面重复截面被拒绝'); +mustThrow(() => buildLoftMesh({ profiles: [square(0), [[-10, -10, 10], [10, 10, 10], [-10, 10, 10], [10, -10, 10]]] }), '自交截面被拒绝'); +mustThrow(() => buildLoftMesh({ profiles: [square(0), [[0, 0, 10], [1, 0, 10], [2, 0, 10]]] }), '退化截面被拒绝'); +mustThrow(() => buildLoftMesh({ profiles: [square(0), [[-10, -10, 10], [10, -10, 10], [10, 10, 13], [-10, 10, 10]]] }), '非共面截面被拒绝'); +mustThrow(() => buildLoftMesh({ profiles: [square(0), [[-10, -10, 10], [10, -10, 10], [10, 10, 20], [-10, 10, 20]]] }), '倾斜截面被拒绝'); +mustThrow(() => buildLoftMesh({ profiles: [square(0), square(20), square(10)] }), '截面顺序翻转被拒绝'); +mustThrow(() => buildLoftMesh({ profiles: [square(0), square(20)], resampleCount: 2 }), '非法重采样数被拒绝'); + +console.log(`\\n结果:${pass} 通过 / ${fail} 失败`); +process.exit(fail ? 1 : 0); diff --git a/tests/test.mjs b/tests/test.mjs index 6235206..3017b71 100644 --- a/tests/test.mjs +++ b/tests/test.mjs @@ -1,6 +1,6 @@ // MiniSW 核心链路 Node 冒烟测试(真实调用 MiniSW 源码模块) -const base = 'file:///c:/Users/32919/.trae-cn/work/6a78857c0515f1e48ec3076f/swjs/'; -const { initGeometry, buildExtrude, buildRevolve, booleanOp, meshFromManifold, toBinarySTL, getMod } = await import(base + 'geometry.js'); +const base = new URL('../js/', import.meta.url).href; +const { initGeometry, buildExtrude, buildRevolve, buildSweep, booleanOp, meshFromManifold, toBinarySTL, getMod } = await import(base + 'geometry.js'); const { Doc } = await import(base + 'doc.js'); const { parseNaturalLanguage, validateOps, executeOps } = await import(base + 'agent.js'); const { findLoops } = await import(base + 'sketch.js'); @@ -220,5 +220,230 @@ d17.addFeature({ type: 'revolve', sketchId: sk17.id, params: { axisEntityId: 999 d17.rebuild(); ok(!!d17.features[0].error && d17.features[0].error.includes('轴'), `轴不存在被拦截(${d17.features[0].error})`); +console.log('== 18. 圆形截面扫描:直线 + 四分之一圆弧 =='); +const straightSweep = buildSweep({ path: [[0, 0, 0], [80, 0, 0]], radius: 5, segments: 32 }); +const straightBB = straightSweep.boundingBox(); +ok(straightSweep.status() === 'NoError', '直线扫描生成有效实体'); +ok(Math.abs(straightBB.max[0] - straightBB.min[0] - 80) < 0.1 && Math.abs(straightBB.max[1] - straightBB.min[1] - 10) < 0.2, + '直线扫描包围盒 80×10'); +const arcPath = []; +for (let i = 0; i <= 16; i++) { + const a = Math.PI * 0.5 * i / 16; + arcPath.push([50 * Math.cos(a), 50 * Math.sin(a), 0]); +} +const arcSweep = buildSweep({ path: arcPath, radius: 4, segments: 32 }); +ok(arcSweep.status() === 'NoError' && arcSweep.volume() > 0, '四分之一圆弧扫描生成有效实体'); +straightSweep.delete(); +arcSweep.delete(); + +const sweepOps = parseNaturalLanguage('沿半径50的四分之一圆弧扫描直径8的圆形截面'); +ok(sweepOps.length === 1 && sweepOps[0].op === 'sweep' && Math.abs(sweepOps[0].radius - 4) < 1e-6, 'AI 识别圆弧扫描与截面直径'); +ok(!validateOps(sweepOps), '扫描 op 校验通过'); +const d18 = new Doc(); +executeOps(d18, sweepOps); +d18.rebuild(); +ok(!d18.features.some(f => f.error) && !!d18.bodyMesh, 'AI 扫描端到端重建成功'); +const d18Reloaded = Doc.fromJSON(d18.toJSON()); +d18Reloaded.rebuild(); +ok(!d18Reloaded.features.some(f => f.error) && d18Reloaded.features[0].params.path.length === 17, '扫描特征保存/打开后可重建'); +ok(!!validateOps([{ op: 'sweep', path: [[0, 0, 0]], radius: 4 }]), '退化扫描路径在执行前被拦截'); + +console.log('== 19. 第一阶段批次 A 集成:草图扫描 / 放样 / 参考几何 =='); +const d19ref = new Doc(); +const offsetPlane = d19ref.addReference({ + type: 'plane', name: '偏移面10', sourceRefs: ['front'], params: { kind: 'offset', distance: 10 }, +}); +const sk19ref = d19ref.addSketch({ planeRefId: offsetPlane.id, name: '偏移面草图' }); +d19ref.addEntity(sk19ref.id, { type: 'rect', c: [0, 0], w: 20, h: 10 }); +d19ref.addFeature({ type: 'extrude', sketchId: sk19ref.id, params: { depth: 5 } }); +d19ref.rebuild(); +ok(!d19ref.features.some(f => f.error), '偏移基准面上的草图可拉伸'); +ok(Math.abs(d19ref.bbox.min[2] - 10) < 1e-4 && Math.abs(d19ref.bbox.max[2] - 15) < 1e-4, '偏移面拉伸位于 z=10..15'); +const d19refReloaded = Doc.fromJSON(d19ref.toJSON()); +d19refReloaded.rebuild(); +ok(d19refReloaded.references.length === 1 && !d19refReloaded.features.some(f => f.error), '参考几何保存/打开后可重建'); +const d19angled = new Doc(); +const axis19 = d19angled.addReference({ type: 'axis', name: 'Y轴', origin: [0, 0, 0], direction: [0, 1, 0], sourceRefs: [], params: {} }); +const angledPlane19 = d19angled.addReference({ type: 'plane', name: '旋转面90', sourceRefs: ['front', axis19.id], params: { kind: 'angled', degrees: 90 } }); +const angledSketch19 = d19angled.addSketch({ planeRefId: angledPlane19.id }); +d19angled.addEntity(angledSketch19.id, { type: 'rect', c: [0, 0], w: 20, h: 10 }); +d19angled.addFeature({ type: 'extrude', sketchId: angledSketch19.id, params: { depth: 5 } }); +d19angled.rebuild(); +ok(!d19angled.features.some(f => f.error), '角度基准面上的草图可拉伸'); +const angledSize19 = [0, 1, 2].map(i => d19angled.bbox.max[i] - d19angled.bbox.min[i]); +ok(Math.abs(angledSize19[0] - 5) < 0.1 && Math.abs(angledSize19[1] - 10) < 0.1 && Math.abs(angledSize19[2] - 20) < 0.1, '角度面拉伸方向与局部坐标正确'); + +const d19sweep = new Doc(); +const path19 = d19sweep.addSketch({ plane: 'front', name: '扫描路径' }); +d19sweep.addEntity(path19.id, { type: 'line', p1: [0, 0], p2: [60, 0] }); +const profile19 = d19sweep.addSketch({ plane: 'right', name: '扫描截面' }); +d19sweep.addEntity(profile19.id, { type: 'rect', c: [0, 0], w: 8, h: 6 }); +d19sweep.addFeature({ type: 'sweep', params: { pathSketchId: path19.id, profileSketchId: profile19.id, frameMode: 'parallelTransport', twistDegrees: 0 } }); +d19sweep.rebuild(); +ok(!d19sweep.features.some(f => f.error) && !!d19sweep.bodyMesh, '路径草图 + 矩形截面草图扫描成功'); +ok(Math.abs(d19sweep.bbox.max[0] - d19sweep.bbox.min[0] - 60) < 0.1, '草图扫描路径长度为 60mm'); + +const d19loft = new Doc(); +const lp1 = d19loft.addSketch({ plane: 'front', offset: 0, name: '放样截面1' }); +d19loft.addEntity(lp1.id, { type: 'rect', c: [0, 0], w: 30, h: 20 }); +const lp2 = d19loft.addSketch({ plane: 'front', offset: 30, name: '放样截面2' }); +d19loft.addEntity(lp2.id, { type: 'circle', c: [0, 0], r: 6 }); +d19loft.addFeature({ type: 'loft', params: { profileSketchIds: [lp1.id, lp2.id], resampleCount: 32 } }); +d19loft.rebuild(); +ok(!d19loft.features.some(f => f.error) && !!d19loft.bodyMesh, '矩形草图到圆形草图放样成功'); +ok(Math.abs(d19loft.bbox.max[2] - d19loft.bbox.min[2] - 30) < 0.1, '放样跨越两个相距 30mm 的截面'); + +const loftOps19 = parseNaturalLanguage('从40×30的矩形放样到直径20的圆形,高度50'); +ok(loftOps19.length === 1 && loftOps19[0].op === 'loft' && !validateOps(loftOps19), 'AI 识别矩形到圆形放样'); +const d19aiLoft = new Doc(); +executeOps(d19aiLoft, loftOps19); +d19aiLoft.rebuild(); +ok(!d19aiLoft.features.some(f => f.error) && Math.abs(d19aiLoft.bbox.max[2] - 50) < 0.1, 'AI 放样端到端重建成功'); +const refOps19 = parseNaturalLanguage('创建距离前视面10mm的偏移基准面'); +ok(refOps19.length === 1 && refOps19[0].op === 'reference_plane' && !validateOps(refOps19), 'AI 识别偏移基准面'); +const d19aiRef = new Doc(); +const createdRef19 = executeOps(d19aiRef, refOps19); +ok(createdRef19.references.length === 1 && d19aiRef.resolveReference(createdRef19.references[0]).origin[2] === 10, 'AI 创建的参考基准面可解析'); + +console.log('== 20. 第一阶段批次 B 集成:筋 / 拉伸终止条件 =='); +const d20term = new Doc(); +const base20 = d20term.addSketch({ plane: 'front' }); +d20term.addEntity(base20.id, { type: 'rect', c: [0, 0], w: 80, h: 50 }); +d20term.addFeature({ type: 'extrude', sketchId: base20.id, params: { depth: 12 } }); +const cut20 = d20term.addSketch({ plane: 'front' }); +d20term.addEntity(cut20.id, { type: 'circle', c: [0, 0], r: 5 }); +d20term.addFeature({ type: 'cut', sketchId: cut20.id, params: { mode: 'toNext' } }); +d20term.rebuild(); +ok(!d20term.features.some(f => f.error), '到下一面切除在当前主体上精确求深度'); +const expected20 = 80 * 50 * 12 - Math.PI * 25 * 12; +ok(Math.abs(d20term.bodyManifold.volume() - expected20) / expected20 < 0.01, '到下一面切除贯穿首个相交区间'); + +const d20face = new Doc(); +const face20 = d20face.addReference({ type: 'plane', name: '终止面25', sourceRefs: ['front'], params: { kind: 'offset', distance: 25 } }); +const faceSketch20 = d20face.addSketch({ plane: 'front' }); +d20face.addEntity(faceSketch20.id, { type: 'rect', c: [0, 0], w: 30, h: 20 }); +d20face.addFeature({ type: 'extrude', sketchId: faceSketch20.id, params: { mode: 'toFace', targetFaceRef: face20.id } }); +d20face.rebuild(); +ok(!d20face.features.some(f => f.error) && Math.abs(d20face.bbox.max[2] - 25) < 1e-4, '到指定参考面拉伸精确终止于 z=25'); +const faceReload20 = Doc.fromJSON(d20face.toJSON()); +faceReload20.rebuild(); +ok(!faceReload20.features.some(f => f.error) && faceReload20.features[0].params.targetFaceRef === face20.id, '到面稳定引用保存/打开后可重建'); + +const d20body = new Doc(); +const bodyBase20 = d20body.addSketch({ plane: 'front' }); +d20body.addEntity(bodyBase20.id, { type: 'rect', c: [0, 0], w: 60, h: 40 }); +d20body.addFeature({ type: 'extrude', sketchId: bodyBase20.id, params: { depth: 10 } }); +const bodyCut20 = d20body.addSketch({ plane: 'front' }); +d20body.addEntity(bodyCut20.id, { type: 'circle', c: [10, 0], r: 3 }); +d20body.addFeature({ type: 'cut', sketchId: bodyCut20.id, params: { mode: 'toBody', targetBodyRef: 'current' } }); +d20body.rebuild(); +ok(!d20body.features.some(f => f.error), '单实体文档的到实体终止使用 current 稳定目标'); + +const d20rib = new Doc(); +const ribBase20 = d20rib.addSketch({ plane: 'front' }); +d20rib.addEntity(ribBase20.id, { type: 'rect', c: [0, 0], w: 90, h: 50 }); +d20rib.addFeature({ type: 'extrude', sketchId: ribBase20.id, params: { depth: 10 } }); +const ribWall20 = d20rib.addSketch({ plane: 'right', name: '侧板草图' }); +d20rib.addEntity(ribWall20.id, { type: 'rect', c: [20, 0], w: 40, h: 24 }); +d20rib.addFeature({ type: 'extrude', sketchId: ribWall20.id, params: { depth: 4, symmetric: true } }); +const ribPath20 = d20rib.addSketch({ plane: 'front', offset: 5, name: '筋路径' }); +d20rib.addEntity(ribPath20.id, { type: 'line', p1: [-30, 0], p2: [30, 0] }); +d20rib.addFeature({ type: 'rib', sketchId: ribPath20.id, params: { thickness: 3, extent: 25, side: 'both' } }); +d20rib.rebuild(); +ok(!d20rib.features.some(f => f.error) && Math.abs(d20rib.bbox.max[2] - 40) < 0.1, '底板、侧板与开放路径筋合并为单一主体'); +const ribReload20 = Doc.fromJSON(d20rib.toJSON()); +ribReload20.rebuild(); +ok(!ribReload20.features.some(f => f.error) && ribReload20.features.at(-1).params.thickness === 3, '筋特征保存/打开后可重建'); + +const ribOps20 = parseNaturalLanguage('做一个120×80×12的底板,加一条加强筋,厚度8mm,筋高50mm'); +const ribOp20 = ribOps20.find(o => o.op === 'rib'); +ok(ribOp20 && ribOp20.thickness === 8 && ribOp20.extent === 50 && !validateOps(ribOps20), 'AI 精确识别加强筋厚度 8mm 和筋高 50mm'); +const ribAi20 = new Doc(); +executeOps(ribAi20, ribOps20); +ribAi20.rebuild(); +ok(!ribAi20.features.some(f => f.error) && ribAi20.features.at(-1).type === 'rib' && Math.abs(ribAi20.bbox.max[2] - 56) < 0.1, 'AI 加强筋端到端重建成功且整体高度为 56mm'); + +console.log('== 21. 第一阶段批次 B 集成:实体倒角 / 实体圆角 =='); +function edgeBoxDoc(type) { + const d = new Doc(); + const s = d.addSketch({ plane: 'front' }); + d.addEntity(s.id, { type: 'rect', c: [0, 0], w: 40, h: 30 }); + d.addFeature({ type: 'extrude', sketchId: s.id, params: { depth: 20 } }); + d.addFeature({ + type, + params: { + edgeRefs: [{ type: 'edge-query', point: [0, -15, 0], direction: [1, 0, 0] }], + distance: 5, radius: 5, segments: 8, + }, + }); + d.rebuild(); + return d; +} +const chamfer21 = edgeBoxDoc('chamfer'); +ok(!chamfer21.features.some(f => f.error) && chamfer21.bodyManifold.status() === 'NoError', '立方体单条凸直棱实体倒角通过内核验证'); +ok(Math.abs(chamfer21.bodyManifold.volume() - 23500) < 1, `实体倒角移除真实三角棱柱体积(实测 ${chamfer21.bodyManifold.volume().toFixed(2)})`); +const chamferReload21 = Doc.fromJSON(chamfer21.toJSON()); +chamferReload21.rebuild(); +ok(!chamferReload21.features.some(f => f.error), '实体倒角稳定边查询保存/打开后可重建'); + +const fillet21 = edgeBoxDoc('fillet'); +const filletVolume21 = fillet21.bodyManifold?.volume() || 0; +ok(!fillet21.features.some(f => f.error) && fillet21.bodyManifold.status() === 'NoError', '立方体单条凸直棱恒定半径圆角通过内核验证'); +ok(filletVolume21 > 23500 && filletVolume21 < 24000, `实体圆角体积位于倒角与原方块之间(实测 ${filletVolume21.toFixed(2)})`); +const filletReload21 = Doc.fromJSON(fillet21.toJSON()); +filletReload21.rebuild(); +ok(!filletReload21.features.some(f => f.error) && filletReload21.features.at(-1).params.segments === 8, '实体圆角保存/打开后可重建'); + +const chamferOps21 = parseNaturalLanguage('做一个40×30×20的方块,给一条实体棱边倒角5mm'); +ok(chamferOps21.some(o => o.op === 'chamfer_edge') && !validateOps(chamferOps21), 'AI 识别实体棱边倒角'); +const chamferAi21 = new Doc(); executeOps(chamferAi21, chamferOps21); chamferAi21.rebuild(); +ok(!chamferAi21.features.some(f => f.error) && chamferAi21.features.at(-1).type === 'chamfer', 'AI 实体倒角端到端重建成功'); +const filletOps21 = parseNaturalLanguage('做一个40×30×20的方块,给一条实体棱边做R5圆角'); +ok(filletOps21.some(o => o.op === 'fillet_edge') && !validateOps(filletOps21), 'AI 识别实体棱边 R5 圆角'); +const filletAi21 = new Doc(); executeOps(filletAi21, filletOps21); filletAi21.rebuild(); +ok(!filletAi21.features.some(f => f.error) && filletAi21.features.at(-1).type === 'fillet', 'AI 实体圆角端到端重建成功'); + +console.log('== 22. M10 第一阶段综合验收:AI 一句话轴承座 =='); +const bearingText22 = '生成一个120×80×12的轴承座,主体凸台直径60高32,中心通孔直径30,两条厚4mm加强筋,实体倒角和R4实体圆角'; +const bearingOps22 = parseNaturalLanguage(bearingText22); +ok(Array.isArray(bearingOps22) && !validateOps(bearingOps22), '轴承座中文验收句生成合法操作序列'); +ok(bearingOps22.filter(o => o.op === 'rib').length >= 2 && bearingOps22.some(o => o.op === 'chamfer_edge') && bearingOps22.some(o => o.op === 'fillet_edge'), '操作序列包含双筋、实体倒角和实体圆角'); +ok(bearingOps22.some(o => o.op === 'reference_plane') && bearingOps22.some(o => o.op === 'cut' && o.mode === 'toNext'), '操作序列包含参考基准面和非固定深度终止条件'); +const bearing22 = new Doc(); +executeOps(bearing22, bearingOps22); +const bearingStart22 = performance.now(); +bearing22.rebuild(); +const bearingElapsed22 = performance.now() - bearingStart22; +ok(!bearing22.features.some(f => f.error) && bearing22.bodyManifold?.status() === 'NoError', 'AI 轴承座全部特征按顺序重建且实体有效'); +ok(bearing22.features.filter(f => f.type === 'rib').length === 2 && bearing22.references.length === 1, '轴承座特征树含两条筋和一个参考面'); +ok(bearingElapsed22 < 1000, `完整轴承座单次重建 ${bearingElapsed22.toFixed(1)}ms(预算 <1000ms)`); +const bearingVolume22 = bearing22.bodyManifold.volume(); +const bearingSTL22 = toBinarySTL(bearing22.bodyMesh); +ok(bearingSTL22.length > 84 && new DataView(bearingSTL22.buffer).getUint32(80, true) === bearing22.bodyMesh.tris.length / 3, '轴承座 STL 导出三角数与网格一致'); + +const bearingReload22 = Doc.fromJSON(bearing22.toJSON()); +bearingReload22.rebuild(); +ok(!bearingReload22.features.some(f => f.error) && Math.abs(bearingReload22.bodyManifold.volume() - bearingVolume22) < 1e-3, '轴承座保存/重新打开后体积一致'); +const ribFeature22 = bearingReload22.features.find(f => f.type === 'rib'); +bearingReload22.updateFeature(ribFeature22.id, { thickness: 5 }); +bearingReload22.rebuild(); +ok(!bearingReload22.features.some(f => f.error) && bearingReload22.featureById(ribFeature22.id).params.thickness === 5, '第一阶段新特征参数可编辑并全树重建'); + +let stableBearing22 = true, lastBearing22 = 0; +const repeatStart22 = performance.now(); +for (let i = 0; i < 10; i++) { + bearing22.rebuild(); + const volume = bearing22.bodyManifold.volume(); + if (i && Math.abs(volume - lastBearing22) > 1e-3) stableBearing22 = false; + lastBearing22 = volume; +} +ok(stableBearing22 && performance.now() - repeatStart22 < 3000, '轴承座连续 10 次重建稳定且无可见 WASM 生命周期退化'); +const legacy22 = bearing22.toJSON(); +legacy22.version = 1; delete legacy22.references; +legacy22.features = legacy22.features.filter(f => !['chamfer', 'fillet', 'rib'].includes(f.type)); +legacy22.sketches = legacy22.sketches.filter(s => !s.planeRefId); +const legacyDoc22 = Doc.fromJSON(legacy22); legacyDoc22.rebuild(); +ok(Array.isArray(legacyDoc22.references) && !legacyDoc22.features.some(f => f.error), '旧版无 references 字段的工程仍可打开重建'); + console.log(`\n结果:${pass} 通过 / ${fail} 失败`); process.exit(fail ? 1 : 0); diff --git a/tests/test.phase2.mjs b/tests/test.phase2.mjs new file mode 100644 index 0000000..fbdcb57 --- /dev/null +++ b/tests/test.phase2.mjs @@ -0,0 +1,46 @@ +import assert from 'node:assert/strict'; +import { initGeometry } from '../js/geometry.js'; +import { Doc } from '../js/doc.js'; +import { parseNaturalLanguage, validateOps, executeOps } from '../js/agent.js'; + +const ok = (value, message) => assert.ok(value, message); +await initGeometry(); + +const d = new Doc(); +const ops = [ + { op: 'sketch', plane: 'front', name: '参数草图' }, + { op: 'rect', cx: 0, cy: 0, w: 'W', h: 40 }, + { op: 'set_dimension', name: 'W', expression: '120' }, + { op: 'extrude', depth: 12, name: '参数主体' }, +]; +ok(!validateOps(ops), '命名尺寸序列校验通过'); +executeOps(d, ops); +d.rebuild(); +ok(Math.abs(d.bbox.max[0] - d.bbox.min[0] - 120) < 1e-6, '命名尺寸驱动矩形宽度'); +d.setDimension('W', '150'); +d.rebuild(); +ok(Math.abs(d.bbox.max[0] - d.bbox.min[0] - 150) < 1e-6, '修改命名尺寸后主体联动'); + +const ellipseDoc = new Doc(); +executeOps(ellipseDoc, parseNaturalLanguage('做一个 80×40 的椭圆,高 10')); +ellipseDoc.rebuild(); +ok(ellipseDoc.bodyMesh && ellipseDoc.bbox.max[2] - ellipseDoc.bbox.min[2] > 9.9, '椭圆实体可重建'); + +const slotDoc = new Doc(); +executeOps(slotDoc, parseNaturalLanguage('做一个 80×20 的长圆槽,高 8')); +slotDoc.rebuild(); +ok(slotDoc.bodyMesh && slotDoc.bbox.max[0] - slotDoc.bbox.min[0] > 75, '长圆槽实体可重建'); + +const arrayDoc = new Doc(); +executeOps(arrayDoc, [ + { op: 'sketch', plane: 'front' }, + { op: 'circle', cx: 30, cy: 0, r: 4 }, + { op: 'sketch_pattern_circular', count: 6, cx: 0, cy: 0 }, +]); +ok(arrayDoc.sketches[0].entities.length === 6, '草图圆周阵列生成六个实体'); + +const round = Doc.fromJSON(JSON.parse(JSON.stringify(d.toJSON()))); +round.rebuild(); +ok(Math.abs(round.bbox.max[0] - round.bbox.min[0] - 150) < 1e-6, '命名尺寸保存/打开后保持联动'); + +console.log('phase2 8/8 passed'); diff --git a/tests/test.phase34.mjs b/tests/test.phase34.mjs new file mode 100644 index 0000000..40f9ccc --- /dev/null +++ b/tests/test.phase34.mjs @@ -0,0 +1,39 @@ +import assert from 'node:assert/strict'; +import { initGeometry } from '../js/geometry.js'; +import { Doc } from '../js/doc.js'; +import { parseNaturalLanguage, executeOps, validateOps } from '../js/agent.js'; + +await initGeometry(); +const d = new Doc(); +executeOps(d, [ + { op: 'sketch', plane: 'front' }, { op: 'rect', cx: 0, cy: 0, w: 80, h: 50 }, { op: 'extrude', depth: 10, name: '底座' }, +]); +d.rebuild(); +const drawingOps = parseNaturalLanguage('给当前零件生成一张 A4 横向三视图工程图'); +assert.equal(validateOps(drawingOps), null); +executeOps(d, drawingOps); +assert.equal(d.drawings.length, 1); +assert.equal(d.drawings[0].views.length, 3); + +executeOps(d, [{ op: 'assembly_create', name: '底座装配' }, { op: 'assembly_add_component', id: 'base', source: 'current', fixed: true }, { op: 'assembly_add_component', id: 'slider', source: 'current' }, { op: 'interference_check' }]); +assert.equal(d.assembly.components.length, 2); +assert.ok(d.assembly.components[1].transform.position[0] > 0); +assert.ok(Array.isArray(d.assembly.lastInterferences)); +const round = Doc.fromJSON(JSON.parse(JSON.stringify(d.toJSON()))); +assert.equal(round.drawings.length, 1); +assert.equal(round.assembly.components.length, 2); + +// 自然语言同时描述“先建零件、再装配”时,内置代理应自动重建前置实体, +// 不再把 assembly_add_component 提前执行到空 bodyMesh 上。 +const natural = new Doc(); +const naturalText = '新建一个装配体。先做一个120×80×12mm的底座,顶面中心加一个直径50高20mm的凸台并开直径24mm中心通孔;检查装配体干涉,生成爆炸视图。最后为底座生成A4横向三视图,标注120,并添加A-A剖视图。'; +const naturalOps = parseNaturalLanguage(naturalText); +assert.equal(validateOps(naturalOps), null); +executeOps(natural, naturalOps); +assert.ok(natural.bodyMesh && natural.bodyMesh.verts.length > 0); +assert.equal(natural.assembly?.components.length, 1); +assert.equal(natural.features.filter(f => !f.error).length, 3); +assert.equal(natural.drawings.length, 1); +assert.ok(Array.isArray(natural.assembly.lastInterferences)); +assert.equal(natural.assembly.explode.active, true); +console.log('phase3/4 16/16 passed'); diff --git a/tests/test.reference.mjs b/tests/test.reference.mjs new file mode 100644 index 0000000..f571a2e --- /dev/null +++ b/tests/test.reference.mjs @@ -0,0 +1,57 @@ +import { + makeOffsetPlane, makeAngledPlane, makePlaneThroughPoint, makeAxisFromPoints, + makeAxisFromPlanes, makeCoordinateSystem, resolveReference, +} from '../js/features/reference.js'; + +let pass = 0, fail = 0; +const ok = (condition, message) => { if (condition) { pass++; console.log(` ✓ ${message}`); } else { fail++; console.error(` ✗ ${message}`); } }; +const throws = (fn, text) => { try { fn(); ok(false, text); } catch (error) { ok(error.message.includes(''), text); } }; +const near = (a, b, tolerance = 1e-8) => Math.abs(a - b) < tolerance; +const vecNear = (a, b) => a.every((v, i) => near(v, b[i])); + +console.log('== M06 参考几何 =='); +const front = { id: 'front', type: 'plane', origin: [0, 0, 0], normal: [0, 0, 1], xAxis: [1, 0, 0], sourceRefs: [], params: {} }; +const vertical = { id: 'vertical', type: 'plane', origin: [0, 0, 0], normal: [1, 0, 0], xAxis: [0, 1, 0], sourceRefs: [], params: {} }; +const offset = makeOffsetPlane(front, 10); +ok(vecNear(offset.origin, [0, 0, 10]) && vecNear(offset.normal, [0, 0, 1]), '前视面偏移 10mm'); +const axisZ = { id: 'zAxis', type: 'axis', origin: [0, 0, 0], direction: [0, 0, 5], sourceRefs: [], params: {} }; +const angled = makeAngledPlane(front, axisZ, 30); +ok(near(angled.normal[0], 0) && near(angled.normal[1], 0) && near(angled.xAxis[0], Math.cos(Math.PI / 6)), '绕基准轴旋转 30°'); +const through = makePlaneThroughPoint(front, { id: 'p10', type: 'point', position: [2, 3, 10] }); +ok(vecNear(through.origin, [2, 3, 10]) && vecNear(through.normal, front.normal), '过点平行基准面'); +const pointAxis = makeAxisFromPoints([1, 2, 3], [1, 2, 8]); +ok(vecNear(pointAxis.direction, [0, 0, 1]), '两点基准轴'); +const intersection = makeAxisFromPlanes(front, vertical); +ok(vecNear(intersection.direction, [0, 1, 0]) || vecNear(intersection.direction, [0, -1, 0]), '两个垂直平面交线基准轴'); +throws(() => makeAxisFromPlanes(front, makeOffsetPlane(front, 5)), '平行平面交线被拒绝'); +throws(() => makeAxisFromPoints([0, 0, 0], [0, 0, 0]), '重合点建轴被拒绝'); +const csys = makeCoordinateSystem([1, 2, 3], [2, 0, 0], [1, 4, 0]); +ok(near(csys.xAxis.reduce((s, v, i) => s + v * csys.yAxis[i], 0), 0) && near(csys.zAxis[2], 1), '坐标系正交且为右手系'); +throws(() => makeCoordinateSystem([0, 0, 0], [1, 0, 0], [2, 0, 0]), '共线坐标系提示被拒绝'); + +const refs = { + front, + zAxis: axisZ, + p10: { id: 'p10', type: 'point', position: [0, 0, 12], sourceRefs: [], params: {} }, + offsetRef: { id: 'offsetRef', type: 'plane', sourceRefs: ['front'], params: { kind: 'offset', distance: 10 } }, + angledRef: { id: 'angledRef', type: 'plane', sourceRefs: ['front', 'zAxis'], params: { kind: 'angled', degrees: 30 } }, + throughRef: { id: 'throughRef', type: 'plane', sourceRefs: ['front', 'p10'], params: { kind: 'throughPoint' } }, + centerEntity: { id: 'centerEntity', bbox: { min: [0, 2, 4], max: [4, 6, 8] } }, + centerRef: { id: 'centerRef', type: 'point', sourceRefs: ['centerEntity'], params: { kind: 'entityCenter' } }, +}; +ok(vecNear(resolveReference('offsetRef', refs).origin, [0, 0, 10]), '稳定 ID 偏移引用可解析'); +ok(near(resolveReference('angledRef', refs).xAxis[1], .5), '稳定 ID 角度引用可解析'); +ok(vecNear(resolveReference('throughRef', refs).origin, [0, 0, 12]), '稳定 ID 过点引用可解析'); +ok(vecNear(resolveReference('centerRef', refs).position, [2, 4, 6]), '引用实体中心点'); +const roundTrip = JSON.parse(JSON.stringify(refs)); +ok(vecNear(resolveReference('offsetRef', roundTrip).origin, [0, 0, 10]), 'JSON 往返后结果一致'); +throws(() => resolveReference({ id: 'missing', type: 'plane', sourceRefs: ['absent'], params: { kind: 'offset', distance: 1 } }, refs), '丢失引用被拒绝'); +const cycle = { a: { id: 'a', type: 'plane', sourceRefs: ['b'], params: { kind: 'offset', distance: 1 } }, b: { id: 'b', type: 'plane', sourceRefs: ['a'], params: { kind: 'offset', distance: 1 } } }; +throws(() => resolveReference('a', cycle), '循环引用被检测'); + +const started = performance.now(); +for (let i = 0; i < 1000; i++) resolveReference('angledRef', refs); +const elapsed = performance.now() - started; +ok(elapsed < 200, `1000 次引用解析 ${elapsed.toFixed(2)}ms`); +console.log(`结果:${pass} 通过 / ${fail} 失败`); +process.exit(fail ? 1 : 0); diff --git a/tests/test.rib.mjs b/tests/test.rib.mjs new file mode 100644 index 0000000..8e788c8 --- /dev/null +++ b/tests/test.rib.mjs @@ -0,0 +1,45 @@ +import { initGeometry, getMod, manifoldFromMeshData, buildCylinder } from '../js/geometry.js'; +import { offsetOpenPath, buildRibProfile, buildRibTool } from '../js/features/rib.js'; + +let pass = 0, fail = 0; +const ok = (v, label) => { if (v) { pass++; console.log(' ✓ ' + label); } else { fail++; console.error(' ✗ ' + label); } }; +const throws = (fn, word) => { try { fn(); return false; } catch (e) { return e.message.includes(word); } }; +const area = p => Math.abs(p.reduce((s, a, i) => { const b = p[(i + 1) % p.length]; return s + a[0] * b[1] - a[1] * b[0]; }, 0) / 2); + +await initGeometry(); +const ctx = { manifoldFromMeshData, mod: getMod() }; +console.log('== M05 筋特征 =='); +const line = [[0, 0], [40, 0]]; +const both = offsetOpenPath(line, 3, 'both'); +ok(both.length === 4 && Math.abs(area(both) - 120) < 1e-6, '双侧对称偏置生成 3mm 薄壁轮廓'); +const single = offsetOpenPath(line, 3, 'left'); +ok(Math.min(...single.map(p => p[1])) === 0 && Math.max(...single.map(p => p[1])) === 3, '单侧偏置保留中心线一侧'); +const folded = buildRibProfile({ path: [[0, 0], [20, 0], [20, 20]], thickness: 3, side: 'both', draftDegrees: 5 }); +ok(folded.closed && folded.points.length === 6, '折线路径生成封闭筋轮廓并保存拔模参数'); +ok(throws(() => buildRibProfile({ path: line, thickness: 3, draftDegrees: 45 }), '拔模角'), '不合理拔模角被拒绝'); +ok(throws(() => offsetOpenPath([[0, 0], [0, 0]], 3), '退化'), '退化路径被拒绝'); +ok(throws(() => buildRibTool(null, { path: line, thickness: 3 }), '注入'), '未注入内核上下文被拒绝'); +ok(throws(() => buildRibTool(ctx, { path: [[0, 0, 0], [10, 0, 0], [0, 0, 0]], thickness: 3 }), '开放轮廓'), '闭合三维路径被拒绝'); +ok(throws(() => offsetOpenPath([[0, 0], [1, 0], [1, .01]], 20), '厚度过大'), '厚度过大的折角被拒绝'); +ok(throws(() => offsetOpenPath([[0, 0], [5, 0], [10, 0]], 3), '退化'), '共线折点导致的退化被拒绝'); + +const base = buildCylinder({ r: 30, h: 4, z: 0 }); +const rib = buildRibTool(ctx, { path: [[-20, 0, 1], [20, 0, 1]], thickness: 3, extent: 20, body: base, side: 'both' }); +ok(rib.volume() > 0 && ctx.mod.Manifold.intersection([base, rib]).volume() > 0, '底板与直筋形成可连通实体'); +const concaveRib = buildRibTool(ctx, { path: [[0, 0, 0], [20, 0, 0], [20, 20, 0]], thickness: 4, extent: 10, side: 'both' }); +ok(concaveRib.status() === 'NoError' && concaveRib.volume() > 0, '凹形折线路径的耳切端盖生成有效 Manifold'); +const standardSketchRib = buildRibTool(ctx, { sketch: { plane: 'front', offset: 7, points: [[0, 0], [10, 0]] }, thickness: 2, extent: 3 }); +ok(Math.abs(standardSketchRib.boundingBox().min[2] - 7) < 1e-5, '内置草图平面 offset 正确映射到世界坐标'); +const customSketchRib = buildRibTool(ctx, { sketch: { plane: { origin: [10, 0, 0], normal: [1, 0, 0], xAxis: [0, 1, 0], yAxis: [0, 0, 1] }, offset: 4, points: [[0, 0], [10, 0]] }, thickness: 2, extent: 3 }); +ok(Math.abs(customSketchRib.boundingBox().min[0] - 14) < 1e-5, '自定义草图平面 offset 正确映射到世界坐标'); +const flip = buildRibTool(ctx, { path: [[-20, 0, 2], [20, 0, 2]], thickness: 3, extent: 8, flip: true }); +ok(flip.boundingBox().min[2] < 2 && flip.boundingBox().max[2] <= 2.001, 'flip 反转筋的拉伸方向'); +ok(throws(() => buildRibTool(ctx, { path: [[100, 0, 2], [120, 0, 2]], thickness: 3, extent: 20, body: base }), '不接触主体'), '不接触主体时给出中文诊断'); +let stable = true, first = 0; const started = performance.now(); +for (let i = 0; i < 10; i++) { const m = buildRibTool(ctx, { path: [[-20, 0, 1], [20, 0, 1]], thickness: 3, extent: 20 }); if (i) stable &&= Math.abs(m.volume() - first) < 1e-5; else first = m.volume(); m.delete(); } +const elapsed = performance.now() - started; +ok(stable, '连续 10 次构建体积稳定'); +ok(elapsed < 200, `典型筋 10 次构建 ${elapsed.toFixed(1)}ms`); +rib.delete(); concaveRib.delete(); standardSketchRib.delete(); customSketchRib.delete(); flip.delete(); base.delete(); +console.log(`结果:${pass} 通过 / ${fail} 失败`); +process.exit(fail ? 1 : 0); diff --git a/tests/test.sweep.mjs b/tests/test.sweep.mjs new file mode 100644 index 0000000..a7d6054 --- /dev/null +++ b/tests/test.sweep.mjs @@ -0,0 +1,42 @@ +import { samplePathSketch, profileFromSketch, buildSweepMesh } from '../js/features/sweep.js'; + +let pass = 0, fail = 0; +const ok = (condition, label) => { if (condition) { pass++; console.log(' ✓ ' + label); } else { fail++; console.log(' ✗ ' + label); } }; +const throws = (fn, text) => { try { fn(); return false; } catch (e) { return e.message.includes(text); } }; +const box = mesh => { const a = [Infinity, Infinity, Infinity], b = [-Infinity, -Infinity, -Infinity]; for (let i = 0; i < mesh.verts.length; i += 3) for (let j = 0; j < 3; j++) { a[j] = Math.min(a[j], mesh.verts[i + j]); b[j] = Math.max(b[j], mesh.verts[i + j]); } return { a, b }; }; +const closed = mesh => { const edges = new Map(); for (let i = 0; i < mesh.tris.length; i += 3) for (const [a, b] of [[mesh.tris[i], mesh.tris[i + 1]], [mesh.tris[i + 1], mesh.tris[i + 2]], [mesh.tris[i + 2], mesh.tris[i]]]) { const key = a < b ? `${a}:${b}` : `${b}:${a}`; edges.set(key, (edges.get(key) || 0) + 1); } return [...edges.values()].every(n => n === 2); }; +const volume = mesh => { let v = 0; for (let i = 0; i < mesh.tris.length; i += 3) { const a = mesh.tris[i] * 3, b = mesh.tris[i + 1] * 3, c = mesh.tris[i + 2] * 3; v += mesh.verts[a] * (mesh.verts[b + 1] * mesh.verts[c + 2] - mesh.verts[b + 2] * mesh.verts[c + 1]) - mesh.verts[a + 1] * (mesh.verts[b] * mesh.verts[c + 2] - mesh.verts[b + 2] * mesh.verts[c]) + mesh.verts[a + 2] * (mesh.verts[b] * mesh.verts[c + 1] - mesh.verts[b + 1] * mesh.verts[c]); } return Math.abs(v / 6); }; +const circle = (r, n = 24) => ({ points: Array.from({ length: n }, (_, i) => [r * Math.cos(i * Math.PI * 2 / n), r * Math.sin(i * Math.PI * 2 / n)]), closed: true }); + +console.log('== M02 通用扫描 =='); +const line = { points: [[0, 0, 0], [80, 0, 0]], closed: false }; +const round = buildSweepMesh({ path: line, profile: circle(5) }); +const bb = box(round); ok(round.tris.length > 0 && Math.abs(bb.b[0] - bb.a[0] - 80) < .01, '圆截面沿直线扫描'); +const rect = { points: [[-4, -3], [4, -3], [4, 3], [-4, 3]], closed: true }; +const rectMesh = buildSweepMesh({ path: line, profile: rect }); ok(rectMesh.tris.length === 36, '矩形截面沿直线扫描并封端'); +const concave = { points: [[0, 0], [4, 0], [4, 1], [1, 1], [1, 4], [0, 4]], closed: true }; +const concaveMesh = buildSweepMesh({ path: { points: [[0, 0, 0], [10, 0, 0]] }, profile: concave }); +ok(closed(concaveMesh) && Math.abs(volume(concaveMesh) - 70) < 1e-4, '凹 L 形截面耳切封端、边闭合且体积正确'); +const arc = { points: Array.from({ length: 17 }, (_, i) => { const a = i * Math.PI / 32; return [50 * Math.cos(a), 50 * Math.sin(a), 0]; }), closed: false }; +ok(buildSweepMesh({ path: arc, profile: circle(3) }).verts.length > 0, '圆截面沿圆弧扫描'); +const polyPath = { points: [[0, 0, 0], [30, 0, 0], [30, 30, 0]], closed: false }; +ok(buildSweepMesh({ path: polyPath, profile: rect }).verts.length > 0, '矩形截面沿多段线扫描'); +const framePath = { points: [[0, 0, 0], [30, 0, 0], [30, 0, 30]], closed: false }; +const fixed = buildSweepMesh({ path: framePath, profile: rect, frameMode: 'fixed' }); +const transported = buildSweepMesh({ path: framePath, profile: rect, frameMode: 'parallelTransport' }); +ok(fixed.verts.some((v, i) => Math.abs(v - transported.verts[i]) > 1e-4), 'fixed 与 parallelTransport 结果不同'); +const twist = buildSweepMesh({ path: line, profile: rect, twistDegrees: 90 }); +ok(Math.abs(twist.verts[13] - rectMesh.verts[13]) > .5, '90° 扭转改变终点截面'); +const pathSketch = { id: 1, plane: 'front', offset: 0, entities: [{ type: 'line', p1: [0, 0], p2: [10, 0] }, { type: 'arc', c: [10, 10], r: 10, a0: -Math.PI / 2, a1: 0 }] }; +ok(samplePathSketch(pathSketch).points.length > 2, '路径草图直线与圆弧采样'); +const unorderedPath = samplePathSketch({ plane: 'front', entities: [{ type: 'line', p1: [10, 0], p2: [20, 0] }, { type: 'line', p1: [0, 0], p2: [10, 0] }, { type: 'line', p1: [20, 0], p2: [20, 10] }] }); +ok(unorderedPath.points[0][0] === 0 && unorderedPath.points.at(-1)[0] === 20 && unorderedPath.points.at(-1)[1] === 10, '乱序连续路径支持链首和链尾拼接'); +const profileSketch = { id: 2, plane: 'front', offset: 0, entities: [{ type: 'rect', c: [0, 0], w: 8, h: 6 }] }; +ok(profileFromSketch(profileSketch).rings.length === 1, '封闭截面草图提取'); +ok(throws(() => buildSweepMesh({ path: { points: [[0, 0, 0]] }, profile: rect }), '至少需要两个'), '单点路径拒绝'); +ok(throws(() => buildSweepMesh({ path: line, profile: { points: [[0, 0], [1, 0]], closed: false } }), '封闭'), '开放截面拒绝'); +ok(throws(() => samplePathSketch({ entities: [{ type: 'line', p1: [0, 0], p2: [1, 0] }, { type: 'line', p1: [3, 0], p2: [4, 0] }] }), '断裂'), '断裂路径拒绝'); +ok(throws(() => buildSweepMesh({ path: { points: [[0, 0, 0], [.1, 0, 0]] }, profile: circle(5) }), '截面尺寸'), '过大截面拒绝'); +let stable = true; const start = performance.now(); for (let i = 0; i < 10; i++) { const m = buildSweepMesh({ path: arc, profile: circle(3) }); stable &&= m.verts.length === 1224 && m.tris.length === 2436; } const elapsed = performance.now() - start; +ok(stable, '重复 10 次构建稳定'); console.log(` 典型圆弧扫描 10 次:${elapsed.toFixed(1)} ms`); +console.log(`结果:${pass} 通过 / ${fail} 失败`); process.exit(fail ? 1 : 0); diff --git a/tests/test.termination.mjs b/tests/test.termination.mjs new file mode 100644 index 0000000..6ff8c0d --- /dev/null +++ b/tests/test.termination.mjs @@ -0,0 +1,51 @@ +import { normalizeTermination, resolveToNext, resolveToFace, buildToBodyTool } from '../js/features/termination.js'; + +let pass = 0, fail = 0; +const ok = (value, message) => { if (value) { pass++; console.log(` ✓ ${message}`); } else { fail++; console.error(` ✗ ${message}`); } }; +const throws = (fn, text) => { try { fn(); ok(false, text); } catch (error) { ok(/[\u4e00-\u9fff]/.test(error.message), text); } }; +const near = (a, b, eps = 1e-7) => Math.abs(a - b) < eps; + +console.log('== M07 拉伸终止条件 =='); +const layers = { + verts: new Float32Array([0,0,5, 10,0,5, 0,10,5, 0,0,10, 10,0,10, 0,10,10]), + tris: new Uint32Array([0,1,2, 3,4,5]), +}; +const next = resolveToNext({ origin: [1, 1, 0], direction: [0, 0, 2], body: layers }); +ok(near(next.depth, 5) && next.faceIndex === 0, 'toNext 沿射线命中首个实体交点'); +const reverse = resolveToNext({ origin: [1, 1, 12], direction: [0, 0, 1], flip: true, body: layers }); +ok(near(reverse.depth, 2), 'flip 后沿反向射线命中首个交点'); +throws(() => resolveToNext({ origin: [20, 20, 0], direction: [0, 0, 1], body: layers }), '无交点给出中文错误'); +throws(() => resolveToNext({ origin: [0, 0, 0], direction: [0, 0, 1] }), '缺失目标实体给出中文错误'); + +const plane = { origin: [0, 0, 8], normal: [0, 0, 2], id: 'top' }; +const face = resolveToFace({ origin: [2, 3, 0], direction: [0, 0, 1], targetFace: plane, offset: 1.5 }); +ok(near(face.exactDepth, 8) && near(face.depth, 9.5), 'toFace 精确计算平面深度和偏置'); +const tilted = { vertices: [[0, 0, 5], [10, 0, 5], [0, 10, 15]] }; +const sloped = resolveToFace({ origin: [2, 2, 0], direction: [0, 0, 1], targetFace: tilted }); +ok(near(sloped.depth, 7), 'toFace 精确命中倾斜三角面'); +throws(() => resolveToFace({ origin: [0, 0, 10], direction: [0, 0, 1], targetFace: plane }), '反方向目标面被拒绝'); +throws(() => resolveToFace({ origin: [20, 20, 0], direction: [0, 0, 1], targetFace: tilted }), '三角面外命中被拒绝'); +throws(() => resolveToFace({ origin: [0, 0, 0], direction: [0, 0, 1], targetFace: null }), '丢失面引用被拒绝'); + +const modes = ['blind', 'throughAll', 'symmetric', 'toNext', 'toFace', 'toBody']; +ok(modes.every(mode => normalizeTermination({ mode, depth: 3, targetFaceRef: 'f1', targetBodyRef: 'b1' }).mode === mode), '六种终止模式可统一解析'); +ok(normalizeTermination({ throughAll: true }).mode === 'throughAll' && normalizeTermination({ symmetric: true, depth: 3 }).mode === 'symmetric', '兼容现有贯穿与对称布尔参数'); +throws(() => normalizeTermination({ mode: 'toFace' }), '缺失到面引用被拒绝'); +throws(() => normalizeTermination({ mode: 'blind', depth: 0 }), '零深度被拒绝'); + +let built = 0, deleted = 0; +const tool = buildToBodyTool({ + getBoundingBox: () => ({ min: [0, 0, 5], max: [10, 10, 15] }), + buildExtrudeTool: (_profile, options) => { built = options.extent; return { delete: () => { deleted++; } }; }, + intersect: (_tool, body) => ({ body, clipped: true }), +}, { profileTool: { origin: [1, 1, 0] }, targetBody: { id: 'body' }, direction: [0, 0, 1] }); +ok(tool.clipped && built > 15 && deleted === 1, 'toBody 使用注入上下文构造并裁剪工具'); +throws(() => buildToBodyTool({}, { profileTool: { origin: [0, 0, 0] }, targetBody: {}, direction: [0, 0, 1] }), '缺失到实体上下文给出中文错误'); + +const started = performance.now(); +let stable = true; +for (let i = 0; i < 1000; i++) stable &&= near(resolveToFace({ origin: [2, 3, 0], direction: [0, 0, 1], targetFace: plane }).depth, 8); +const elapsed = performance.now() - started; +ok(stable && elapsed < 200, `重复解析稳定且 1000 次耗时 ${elapsed.toFixed(2)}ms`); +console.log(`结果:${pass} 通过 / ${fail} 失败`); +process.exit(fail ? 1 : 0); diff --git "a/\345\220\257\345\212\250.bat" "b/\345\220\257\345\212\250.bat" index 6f877ca..b27ff77 100644 --- "a/\345\220\257\345\212\250.bat" +++ "b/\345\220\257\345\212\250.bat" @@ -1,6 +1,6 @@ @echo off title MiniSW Launcher -cd /d "F:\Program Files\MiniSW" +cd /d "%~dp0" echo ======================================== echo MiniSW Launcher @@ -20,23 +20,18 @@ if not exist "%PY%" ( echo Using Python: %PY% echo. -:: If server already running, just open browser -powershell -Command "if(Get-NetTCPConnection -LocalPort 8898 -State Listen -EA SilentlyContinue){exit 0}else{exit 1}" -if %errorlevel%==0 ( - echo Server already running on port 8898. - echo Opening browser... - start "" "http://127.0.0.1:8898/index.html" - timeout /t 2 >nul - exit /b 0 -) +:: Pick the first free port. This avoids accidentally reopening an older MiniSW +:: server that may already occupy 8898 from another directory. +for /f %%P in ('powershell -NoProfile -Command "$p=8898; while($true){try{$l=[Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback,$p); $l.Start(); $l.Stop(); $p; break}catch{$p++}}"') do set "PORT=%%P" echo Opening browser... -start "" "http://127.0.0.1:8898/index.html" +start "" "http://127.0.0.1:%PORT%/index.html" -echo Starting server on port 8898... +echo Serving project: %CD% +echo Starting server on port %PORT%... echo Close this window to stop the server. echo. -"%PY%" -m http.server 8898 +"%PY%" -m http.server %PORT% echo. echo Server stopped. Press any key to close. diff --git "a/\350\275\264\346\211\277\346\224\257\345\272\247_160x70_\317\20668_\317\20640H7.json" "b/\350\275\264\346\211\277\346\224\257\345\272\247_160x70_\317\20668_\317\20640H7.json" new file mode 100644 index 0000000..289f5f3 --- /dev/null +++ "b/\350\275\264\346\211\277\346\224\257\345\272\247_160x70_\317\20668_\317\20640H7.json" @@ -0,0 +1,497 @@ +{ + "app": "minisw", + "version": 2, + "units": "mm", + "name": "轴承支座_160x70_φ68_φ40H7", + "sketches": [ + { + "id": 1, + "name": "底板外轮廓_R12", + "plane": "front", + "planeRefId": null, + "offset": 0, + "entities": [ + { + "type": "line", + "p1": [ + -68, + -35 + ], + "p2": [ + 68, + -35 + ], + "id": 2 + }, + { + "type": "arc", + "c": [ + 68, + -23 + ], + "r": 12, + "a0": -1.5707963267948966, + "a1": 0, + "id": 3 + }, + { + "type": "line", + "p1": [ + 80, + -23 + ], + "p2": [ + 80, + 23 + ], + "id": 4 + }, + { + "type": "arc", + "c": [ + 68, + 23 + ], + "r": 12, + "a0": 0, + "a1": 1.5707963267948966, + "id": 5 + }, + { + "type": "line", + "p1": [ + 68, + 35 + ], + "p2": [ + -68, + 35 + ], + "id": 6 + }, + { + "type": "arc", + "c": [ + -68, + 23 + ], + "r": 12, + "a0": 1.5707963267948966, + "a1": 3.141592653589793, + "id": 7 + }, + { + "type": "line", + "p1": [ + -80, + 23 + ], + "p2": [ + -80, + -23 + ], + "id": 8 + }, + { + "type": "arc", + "c": [ + -68, + -23 + ], + "r": 12, + "a0": 3.141592653589793, + "a1": 4.71238898038469, + "id": 9 + } + ], + "constraints": [], + "dims": [] + }, + { + "id": 11, + "name": "四个安装孔_孔距120x30", + "plane": "front", + "planeRefId": null, + "offset": 0, + "entities": [ + { + "type": "circle", + "c": [ + -60, + -15 + ], + "r": 6.5, + "id": 12 + }, + { + "type": "circle", + "c": [ + -60, + 15 + ], + "r": 6.5, + "id": 13 + }, + { + "type": "circle", + "c": [ + 60, + -15 + ], + "r": 6.5, + "id": 14 + }, + { + "type": "circle", + "c": [ + 60, + 15 + ], + "r": 6.5, + "id": 15 + } + ], + "constraints": [], + "dims": [] + }, + { + "id": 17, + "name": "立板_26x46", + "plane": "front", + "planeRefId": null, + "offset": 14, + "entities": [ + { + "type": "rect", + "c": [ + 0, + 0 + ], + "w": 26, + "h": 46, + "id": 18 + } + ], + "constraints": [], + "dims": [] + }, + { + "id": 20, + "name": "圆筒座_φ68_φ40", + "plane": "right", + "planeRefId": null, + "offset": -30, + "entities": [ + { + "type": "circle", + "c": [ + 70, + 0 + ], + "r": 34, + "id": 21 + }, + { + "type": "circle", + "c": [ + 70, + 0 + ], + "r": 20, + "id": 22 + } + ], + "constraints": [], + "dims": [] + }, + { + "id": 26, + "name": "前加强筋_三角截面", + "plane": "top", + "planeRefId": null, + "offset": -27, + "entities": [ + { + "type": "line", + "p1": [ + -13, + 14 + ], + "p2": [ + 0, + 14 + ], + "id": 27 + }, + { + "type": "line", + "p1": [ + 0, + 14 + ], + "p2": [ + 13, + 14 + ], + "id": 28 + }, + { + "type": "line", + "p1": [ + 13, + 14 + ], + "p2": [ + 0, + 66 + ], + "id": 29 + }, + { + "type": "line", + "p1": [ + 0, + 66 + ], + "p2": [ + -13, + 14 + ], + "id": 30 + } + ], + "constraints": [], + "dims": [] + }, + { + "id": 32, + "name": "后加强筋_三角截面", + "plane": "top", + "planeRefId": null, + "offset": 19, + "entities": [ + { + "type": "line", + "p1": [ + -13, + 14 + ], + "p2": [ + 0, + 14 + ], + "id": 33 + }, + { + "type": "line", + "p1": [ + 0, + 14 + ], + "p2": [ + 13, + 14 + ], + "id": 34 + }, + { + "type": "line", + "p1": [ + 13, + 14 + ], + "p2": [ + 0, + 66 + ], + "id": 35 + }, + { + "type": "line", + "p1": [ + 0, + 66 + ], + "p2": [ + -13, + 14 + ], + "id": 36 + } + ], + "constraints": [], + "dims": [] + } + ], + "references": [], + "dimensions": [ + { + "name": "BaseLength", + "expression": "160", + "unit": "mm" + }, + { + "name": "BaseWidth", + "expression": "70", + "unit": "mm" + }, + { + "name": "BaseHeight", + "expression": "14", + "unit": "mm" + }, + { + "name": "BaseCornerR", + "expression": "12", + "unit": "mm" + }, + { + "name": "MountHoleDia", + "expression": "13", + "unit": "mm" + }, + { + "name": "MountHoleX", + "expression": "60", + "unit": "mm" + }, + { + "name": "MountHoleY", + "expression": "15", + "unit": "mm" + }, + { + "name": "StandThicknessX", + "expression": "26", + "unit": "mm" + }, + { + "name": "StandWidthY", + "expression": "46", + "unit": "mm" + }, + { + "name": "StandHeight", + "expression": "22", + "unit": "mm" + }, + { + "name": "BossOuterDia", + "expression": "68", + "unit": "mm" + }, + { + "name": "BossBoreDia", + "expression": "40", + "unit": "mm" + }, + { + "name": "BossLengthX", + "expression": "60", + "unit": "mm" + }, + { + "name": "BossCenterZ", + "expression": "70", + "unit": "mm" + }, + { + "name": "BoreChamfer", + "expression": "2", + "unit": "mm" + }, + { + "name": "RibThickness", + "expression": "8", + "unit": "mm" + } + ], + "parameters": {}, + "assembly": null, + "drawings": [], + "features": [ + { + "id": 10, + "name": "底板_160x70x14", + "type": "extrude", + "sketchId": 1, + "params": { + "depth": 14 + } + }, + { + "id": 16, + "name": "四个φ13安装通孔_边距20", + "type": "cut", + "sketchId": 11, + "params": { + "throughAll": true, + "mode": "throughAll" + } + }, + { + "id": 19, + "name": "立板_厚26_宽46_高22", + "type": "extrude", + "sketchId": 17, + "params": { + "depth": 22 + } + }, + { + "id": 23, + "name": "圆筒座_外φ68_内φ40_长60", + "type": "extrude", + "sketchId": 20, + "params": { + "depth": 60 + } + }, + { + "id": 24, + "name": "内孔左端_C2", + "type": "cylcut", + "sketchId": null, + "params": { + "r": 22, + "r2": 20, + "h": 2, + "axis": "x", + "tx": -29, + "tz": 70 + } + }, + { + "id": 25, + "name": "内孔右端_C2", + "type": "cylcut", + "sketchId": null, + "params": { + "r": 20, + "r2": 22, + "h": 2, + "axis": "x", + "tx": 29, + "tz": 70 + } + }, + { + "id": 31, + "name": "前加强筋_厚8", + "type": "extrude", + "sketchId": 26, + "params": { + "depth": 8 + } + }, + { + "id": 37, + "name": "后加强筋_厚8", + "type": "extrude", + "sketchId": 32, + "params": { + "depth": 8 + } + } + ] +} \ No newline at end of file

bf_)XuebM0+Pk5W2%Ny^f_1yrb8< z@S0ISzbER7w{D6pbgFrCX5rpwJD3>r?Zg1eb5cOu?gjr|)-W?qJegspb{=Y1$K-OY z9UdgZM{sKA)XqI*O*QP)&Z*r?&88)v;3|D|YUk9>sa>QTPVGD+t|`ul-;DRo&PVMj zw5IGm=^1g`RA={9Ud<5?H)VN{2`6S8#U8=3I19w0T+S8=x(hA)Sy?vQj(UC%S^W3A z@iLq38yB_fnwNLFsGY@kn^bB?96*a_hj&=aAUj%b$JQz1Y_sOhvO$&AQ6Ds#OWu8L zd0ulrM2~J~K9Kll5^I$?_1p*3zQ>4vcx65hl*9ZiX_e}-%2qSO<_foJzFGH_9gfnU z*HHJAB`4r?M7Kjxx2TqJn9U`6w-RPLhPQBf=k(6$ozuH|%i^MP-?pjYurpk9SI&W( z=T>RA!@6dxU03dMY$cCxH>r6t+*8&)W%CjsE3wV}j?NN7-6>NOzO}a;uDNr~-9^*8 z)rp{YLS4CX!lmsuC~)ZGPl{wGRJYMF+I5-b5$oQh-1;)VPaqJf4&jgNoGxK9>uhxmdA2mGbsn! zdr$gOa!Qe}hW{+Vcboi5jE(4z?`l4c&&-Z7V`Xy!s7+zx|Ir_Mwln=U#jX81wAXCO zT(WteJyIXc(P`g@XL;gPvro&c_dF=1U+X(DrQYe?`tbZX^ckoDIPZ9UZ^OB;pQNg_ z2%0I+&C`#+ODfuw`NsWiAxow|vKU=+jaH(wKaY|}R9l`vYg=5o=gK`-?$KvqZ@O~N zm3zAy$uR2Kmr{|@)`uE39eFdG_>*ba?yH5J2IXFvxsBq_<5KQ*J&WmKe`FPzWaXZ* zzg5+8{B2FS7unx(ha4BxU({1S8_ABB`jJU8W~9w?)0$27Ms^J6wyp8f@kgt#O*8mG zt;U}0HEbI44CL8kXkD?hXdCvlKVrKj8w#5a`{=cn62Hx2#2Hg_?pJR&*Dv*)ww}|L z>^awy`z^Gp{>Y$Fm-W`UpL0Lwe)Oj9vFjeY?y=h|puO)j0wosynXFppey=n?@`J|R zSohBToclTVJJpk5GkDC%xyP<^KT}*8aU)a1qI9Tu8nyGz@sK}qzmY+8RmZn8YC4PQ zA%Dw(r*WN=tNKHcXR5#CvulyBId9k&Gt$iVTVG2HL%o_GkV5zj+q3ns*3XtZ^&@{z z@`ttZhHn&y{}HSLwGrLg_~gK#%WddM@+*N3*c5 zxurM{Vo}~w%t!a|bq`;7Xup%Uf6C9p$isY-laKKojYgSCN;(@`7@&VGWBEG-okI`w5G+gC+$J;v)ZXZY0l$IoxYME%F|C&c%&-_`R!)0dX}qjBcxIFns{ppo~N(j}rTbw6KM{JG-K6@Qnf_^bEg zP1!wA{MBXeb;Y04KUe&@;?EU-YvK&<=j(pHj9Q34jkj~;@=pJp{xP5OC)EmzN$c_b z+|QR1aK&Fz{0(QAfzH&8ENl*7JYK#;G%B<3rn8tHFW;3w|G3V}Wq*|5&S0j7c_7Qr z9iyKfX0tzxu53o9ZoB$On zIONYFrsNqx=P^oo$b1#qN!Xqk>83HP`WrJ3#eEnrd(3$wq}_RAdtP8P5JW*F8og=W)slXr|Mb?~qYm`L6T7b7U`vtNddgRM7P(+3n?>qd^4N~94@?)Q99_8X-&yk z;#olQj6kjtWYpEH_&F9&ISpDzBD$QYttpTCJOgmKe9EPRuG2FB*V&TTAbGv2tAQ4~ zFw#6?lNsadsYiNPd=;Vz?WiA$e^t98X@pMo%Ux|3_i%ZX(4v=bswdmi@!#q^Xn`%K zfJNED?jR>DHWGhkC7J4XM(w!m;ekc~WQB^c+L9``xpWf=O;=vFyv(AXBt>9fElimM z*f&b4?n>h4 zzYFvN`j1F~Hbom-qC`n{oKrZqn>rt^TRTPa!ayNQvaLpzRY{KR7rmHs3Fi{ec-GYN z{@$c)QK85ILgLpZxw*OaT5CRQ%rX00!#5LDs(4^AmO%kRfo~@Gb2=6|3pmr3;!Ny6 z;PehFOS8!$_KR^;iFU^VZJp?*F@q^)l*WTByyL^G|mjp3~uX;#rEf8UVR4FSA}<^ z9-F_5VsJ3FvHDY#;VWtW|38DT)9pv`+cMDAZN}aoE3lDm~t-`hrg{S?E^>qay5C;mjnxAM!27NY;`rU(7|nJ|KM*#i04tl zLFo$q)~!)x3Ne-Qdn(4*-xbPBUo{Tz(R+Ck7e;oW>eG=89SFDzk zhRLLESx8-*S6*2yx1zJ+qDUPYxYb1eAa;Kp+!K}ids-Fwiz*D>5UHao`ghvmZ+hOO zZvIFeo+nnz)z!11r8KR}^ZdxJWMwQgaBW(pn7>&q*B__Uk56@eYqeb0(X(3aT~hm* z)>U;n#A;1e(5;h+Udn2@eiy^KKFn%4tL4zZJWp$v^wHI`FqmkW*LC&dasJJQ)=@2^ zybs;Iz~a!lv7)(fjhkOuhreGnYjty~tW{o>m2#VDm8z+fyNoKi<*ZeBqe)#qsjFwL zS|?}vwjA^gy>^qWmFe47>lPoDt7@IxEA_=Fbx(CUQoiSPUEZY2wfBmUeJMduhg=uC zK-s%3MK4;BJw3wzqOvhrKu71Gly}eGkGcgYg~>5u9U}61pGwWMr$^uDSyaW*XY$BB zsTZBAIyzgIY1T0@eQUZ_O;ReEzC~7if81gEb~>J@eBHK`5GhQ8s%Q#~8H&VN4n zjTMrdt9m}I9KUBl;JUPOmr=9U1P&J{6mR6CPR>+4pH>c~To8Qw%2_FgpIWXm+KkPM z8lzV|r*+h-gDbN-v{6l(XI58NOBHQm(d*7yDOdmP&FakRTz$gTC+sUX|Gsio%2oNq zBu-*hS8zIRnbmzftJ|DfqZUVeHmt6m)!Ka~EC#C!U%4$sd@#v*W_4TIKk{1f{X0)y zE893HM7 zmh;4kY1ocvX;1IRGevwvP86}vPkJ;eKH-V|Oura)X$p6@I0D2-QTvH^KVL>EZU&K| zRgc=@(`Kcs%dC!7C$l=Ux@BQ?^ovd1T$$C8Mc>@Qtgf!EDl6;OHJX*{cCHShzs!ut zS+Hhx<^1QD8ZpUsKG4Y9q=nZtrdMY@F>`vWHU`aMf1nkZPFrprZnI96eWzc#&GszK+PY zS`{y8iZh|eeJyUeVuZ6`->G*h&Z~RspQ4(`v{|rL)>&B>6^+ps zRT5%YDwY zXX(A*!pz~fb&s0t#EtL{UIV2)(B)O~(jtB&`h1mU_`!v5w(e#7zK53P8@)627+E1G zD{Jua$~u}LELk(NJoK)uK17w?wbhs~y)(TF2avJOp+`EiqG!OZfV2C*={$oil@)M* zN#{FTn%uj$#5s8WA?v7>RS%q#2P!}QMdj`eYrH@DTAo~C)wq1tBARv)YH zq;EV?i=!W91${^J7Pg3-EqmD2bMj2@9;Kc!T!LPzAKOhzbo4OvSamwp)Hro#0|u*d zUM$Cl))1LurSH-MZ#7p*5RVdWTwsm;$ZcMq+p0saJcH2;4N} zZe6cMZ5OYC+-;?vuu1Kr%7m%iJZbKj|DX_LrbJ%U`Luuh=EeAjVupD)F+#ikoh{(N z%(-Gkr%)mWk3yXov$kqGfrvqmcowqsRQHEzRhqpI1xD642HOfvVzj~{CO+TyGl}b_ zE?3>5ZoRcuM47~GsrR;y?z?oxWzRyQsqQwhsv~9{9mLxtZZwsGvQQ7v-$%A?k#%bw zok?6~j8#qvO^nqn<}J)O$_%k9u80V`&JLUJ9K7?kG@0lam?6X7```6MOcYXDsN-0n zE@-HAbS80abK0g(I9lnwq>I>1v*|Vs&-qDQtRIy%JlyVY)vo30TCSPJnZ$iOi5n;H zvGO_CfBwxzN4GZhUMvoYTa%(}z8&gTHPcvY<#3_q{pzn|LI7H`c|) z5vayKvBGQ`SNHIYmi3ir0_m$s)3;At?LFO)3Lu#{$>Ip zew>twg`$F<1&^rL>H+8ne%9Zq)D+FnXC1fyv+wkN(?{xQ_Vg){f3%}*QIoS4()_G- zebyuj9Te;b(TKj|TW1xl5tX?_^Rvh4vwwLS#}O!Bx{m$g&^R(i=Uo@KDqa}6ICP6y z+~%cOFy62ZDBQBoI;`w*YGs0-~X5v~%kEP6U>koS?hQQ~aP zEv6@T<3xCZ+$~E*SL^Alr?Z}}U(FP8VO=j)_h*BxABeo8jf#4_tj5`AE^1G3EUc%q zo-XTXsCCWSN$7P72Rk%ZIQsGzSU4R7q^Leqi49bi@JG zOaHGto9tF%pX#M+FBXT~jm5M=vpKImbI`QaGjcaB)rdeZfc9gW{8oJG(9`7_5nH;? zYx#Yf^e#p~)4MnV^HJlK{9?w+thx#Rx3U8rZ1<0Y+tTWo*O}KX3$NSMoUuvc%K5J& zY2un&nAh2jE_{7l7oy0TN5x^KT|`oovJcJc3hLM$uh)5|^IwBGT^syc9O!wcN+^g@ zKQW#r|Mi~E==|sWSI*fuZ?7(|t48SgURR_Z-_~w)%i=~Cbu;=~gY#cMwyNv&q%6+A z+3>o0_G|z8#D}dq+|n8aHCm`h__D_Z4_K~=HK~dkt+czkDbH53yT{5$LTMK{ zpYzP?a$Sh6_tcHL5D>c?Q}H_3TtV!h`Mi2~_RX8bF0_Xxb|!YNA3;=dnEH;WUg1iv zZ`5x_W!dP(_mWEDK)w1k_3rfR*R^7Z&&09rztE#(av#Z&`_EdVrh4>udX{I%Mr75x z$3lwdjCQF1P86 z=ZDy>E0(-*rsxfWzEq#2G)~#K7cI=AwVtM`Q zo+h%sTG=tR0HL{8j|0s-$awv|Q69{Hmg+T-ieu(obgcw%!?z^s{=e zvR5GY$SjAW8AiSkE6lo@2{)+y0$G$>z13=t-Ysp*4J5I*EUNvtuw7-hTv+i zt}ii9`LI<=m+zU@HT&?;HZ7Nvo%MB@O0wcMtNIew*A+*ZODYMy!B zre+N|*_Cj+sZmC>#VG^vd-RhtkzSl zl#H9U8l@{z5@dp!^)TID0T=CEXI57p!_DgAXe@?z-BrnjjZ9Wp9k;SZE^=YFrp$${ z^E&8XFv_O3F6zWy9(H+4s9io2mCF1Q|KP0u40W5ebr;F&ZfMT&VgTtY zQ@>|PCH+#5zR>fhT06O^{MPG(H|a7@nXp^x6DRSyamyCwb>?;Eb>?+lniTUo^Saj> z1+A^SIk2|Q+B*8ls*@C4%C#oikBfO-m(uZ$+R?7!JiKwJD^tx$y(+aP#_x%2{-wL- z5%99C@%wS`t#ZNozJECj48OQ?_E)o|kzZNeldip33|>bj?4~aBUTb1i?n}8!Y^HTl zCw5KwuGLtF69yW`SmbW`Smb-Lq%s**eeGEbxU^0nGx<0-GelEN~vRdl3P4 zjcP%r?21<M~4#;)?5uZ&Ri*eF#OtTk@!2;LB0@oC=2K$=V zJX@4)&APL}0ymR4-bnvPQxU;({aM~2*R$xZK^ywM%hU0(C#Hh^9M5^T&bysO-tE<9 zcjPs+)vGA8yEUzUG?jj4cV>5HcV>58b8hSStmCtekGfN4cOO>0F*wORWnc9vckU%M z6SN?5SY9Swz-v89<;Ny5i!rMlugC4Kek}IF6)pOIg~w{=WC@{>IneL5C-?0m-9FTB z5L%3(v=*=Q__O4aJ+FDUTbd1X>2D2YcOT2{?2$*mk}sF(k#|E8-~6*XRCra7yboh{ zC^Hmi25YQ152Rsq)hjye|QinQ>%Q45{R z{B9(6H_|rdci*L)hfw7*?%vjXbVu{i6J5ePtZpB)A`@4kh(xGOysfw)%!#pn#j+O3dqc~iGQA+t)0HnZT3HUR1~K7RN@`oK1R_dpRysPD+f9Z#jcs}sD4V+w{Bo_S4%cjJ;7-lo3KGHdX{ zRRE1yKgWBaRUmS1$=^Ll@hZ8HMMfx(zJ7lm#4}_`vaS?y`lpKBAFJ)0ug)vmITuW-`a(S3XE1@*WsqGiU{R-<-Rqrke34QmzswJ~rcZQH^cy=7&5_yc{Wu{JY4 z#>+?QRimGUKho>|SATHPvNCu$m^5o)CRwGBnQro)^riH9^t3eBp!{y+h%>`SAVgPBaF@D~swmX!;o$ntV41FcfsGPbqtEj#6)5 zd}6!z=1c8aRO>1Byx4>5ns2mGw72>sjus@LIy=0pxgfI{6e;8?K#uoxK|fK9hs9%= zE)R9o7(EZu42LbBr?HVaV<;C+^l33(^4MzHuFbjJjIa8OxHg7sW5k(5|M612SgsCG zoJWsSkH)N!RhRII+R^N=IQ#DERj8cMpY5f`;|$CmTl9>)k~NxmU4H(B9y?5bu`U(w zNxoHVaZmdcGuF^l6`$FDcC*^_MxPbE4?X39{*GJwDQomzrGC<0d)DZ&min{)I6s>d zhSgynZ9C_Jy4}&Ui{Q1FX=-R9C#fQ5lV3YJ^V7=v(7hLnkzc!^+3S|RGc9a>vuokM zZBBa+G@FI+jrBKOb}dT3?6(J_i5W5R3c7@Lg&x_x9G@z8ab;U(56{oZ#U3jz2`x!I zs>_6GTSuA*@8cDE#AVZKT}i$pIPz!BD1#_M6LAKPh?-hf5jStE1#IgcGgLTh zu*%?#@YYwF5k@;9;(Vn(=2+1p^Ua&*_-#z=`ib3GbM~g@F01n3br+je%eyfziT{5a z{9X6fboo)e=-29BZ>U#h<>;?+!kzvM9G#JrN8;_@sV&`Cs|r@cs#bQ{gf#qS2t9wO zN8#y5Dawe}hhGBZDU*%1D#upkS(RsEXJS`gQ;$e$N26KU1CZ5VJL1_9&snwPj9Hb} zq%&6K&7Uf7Q}bPC)#lM{cB(vTG!&d{@)4g6vD;9bG2LD)4zVN8DzZlBnb|RWYG&2K z>~O<_*{yxBCXe?BX1A8uTwRezWO~`mZeytxc^AX%Xfb~rSaH|P&~rtLZgtgFcf}j4 zH|*wjYwGL9>hYUfe5m5i{I0v^an?QUi$-$WS+%hqgSy0uyI&7}rFlnjxh3ZzEACj) zd!Y95M(mxVfs*~Wq(zF1@lbm`(JUz|8=vTPnbki@9RDD#4x)FbtrgE+)3YPJk~v+s z62km0I3_wQj_X_fcUG;lYMoUZtB2-ys50kWac6#4%r9n{RlB9AVmfjbqspxK-L!kL z82pZ`T5>{yPt7a8WB+nRi?uVw+*3WWITf+n&@3%fc29MSSJCq}8C~e@tgDM7ko}>4 zSzR3&KIIHptLQ#I(=d@cY6`cw6pr<*CSxm9bo66wTbROiw+VaOIqx;+oI39nDt6M6 z4;S<6BT<_^^%*+XEM{SC%?&@z1u1)c=wJGZKYtpDP8w!WUU^*7K7!;1W zvZ(07w{3nY96Gj8(QUlXTYapV9ywi~rFqC7(k!Ig*N@(+;BouPt=iWkygnSdIlr4^ zE>v|UbMr*zTvIl(0mIw!d|cApuA9+ZZS@@ERvVSe+>VhAN1Gw*6Xtg2cII|v z8fR{2zdHNX*{?2+Ks7Q759X#HXK;Pjl>I^L=KUUJD1O89=cQIT3g>L1)2c@v&LUM( z!M%P;bz43p4<}Ui#hHlp8Din9TvIk5pZ+6aZuha=&NV5jD#hF*R@crZxjWNJyV+Iq z><{(ax*|HBn_q6Xsfe@A?Qj?ib{ADC$by~DwXV$X^1Fntj{juBb~)FDcVzx;=-+Ue zEIV=G>iSqcUsN7o?X>WNE>ja+mBN4axo+VVmL1zp6w2=y$lJ9hs`GHOyQ|6W?kQ6!|Fug_U-L86sa!obZSm8Jxr0licC)_t!Pe7$t zd`2AgIu)tDH=g&bY^>>cK?xp-t&y=kN$QkwvZ%-L|5wu`!ea7upXpHfCE zIU!VH4L*efRIYm$$7A;Sx70(RW=8SJ&r+MjyFT`Tl`q=O`K#KVPY>s67p``Z^*yUo z)ua4I=hi#5OvaIdU*?LK*VRA83NCIt*K}r4m*MT;&pQ8hb#}u%d4@BNGqTD2zLkU2 z7m9Trv^>XqM15L%MAjIZuP>M(T*!LAOod$SBIX>dwdMM>>*`+_C!_yo#fGuP^=aeG zah@*oqPE#|o-Oz1=`tfZRt>9|gPf?H#oUk?*~R4Oc74Ldxa1Xk{4mW<@FTfX`cD1P zX+4X%I;)+O!`vP9*1i3z@B4B4>`dLu&D4cGp;MwIb=Rq7D=N*$R$bL|5&tpxN~6&vg7ru|Ewh6AN;Q#-&LD+HQJ~J zY9GDD)$|3QYol)kpTpnR)hw2s&*ilt|L~Bp1O`Yn|eCuCH4S?tIwZXr& zYW2L6q69DMZ-XdL-=vu#>s)LI&dNF~>-zFhm&;wx%DQ4?H@VBl!sL!4(4A|{vvo38)mYzZ zJYQA#7x}PMuBei`uQXOoqIdt8LRGhsR{qTIiu_b(#tya0{whTmxF#MBey{c2p@@IZ z9Y>cz?s51h;;vX6Jv4%lI9fiQvelLqIs2jRZXSqhcjcd^8-5Vtjo0WF3)XQp*O#Sim=unxx7~1T}?l| zth(dc$~$MqX7cOISZBuO@$Qxs0_Qs#sC@CA3h$A&_hQoKtvp{fO=u3DG+i_gC$ zr-eS{S%QptVycN$VmM`6#)7^weHL{S@UM)U2aYBtLDhR%Cs)p zVUJatc~{=iDl@+$Z<(yvN$d{g9k1)ohuuu=KOg1U_}W={XXRbAER#D{if@V}{Z<}t zcg3Sj?mpJjuCInIEAiwEnB0Z7YFg!8J#%t8r^B2Vwy)8Y+t)R-3zyQTx<1i-3)i5s z+u@OBVMpgU8}6RHU+zztKf-lmP2s853Q+RCNwY|NDq|f1kD~{=7nwyScenNHnB2jo z`r>$#JCnQDDMN|%M|v2OJ9>52V{fb1eX3rT)!WmQnHOi$FNU7@i%Pi2ZWMwj(>P`oXTYo>Unc&2zL_Q}RK#e?>m z;t{>oGf_usA7vLV>+hJMnBpCY)LNPanp7j_Qi_OekXds{YN+cV%KZyZqVpL^R4Qnct$@1Hu$<|7=a@{@F@ARX90j z6P3H6mNJe0j@NYS@1mAdb=+Y@ zdZnXqoL1>MVs#pw>#vsm6w&&X{S*raGt{hY^p2{wt}5%Qvi80+yURJR`FeVG$Lc_n z%oXg8=i9PtojY4sVSSxOJ|fj-?E)+CLU}St`Omx~G>2Iun6-yws4@e=FW|LQEI;WM zZUk?19qVf0{aH8;5Eb%kWR>A-jj)mF`b@7xdB`sXiz~b^AL^NS-APhG##WzdzCdkd zvpXu$hlWPMSe~2)m=YLsVRyUv@`DlVEpiibtno zcf6*H-O<)KR)6?MIX6b6HDAnNAMucC+Y<8x@H<} zG4NaIS2)5%%hg$4t`Z#F5$eY8u$Qv;^6Fvvv7EaNHD%U#nC3QY`8;J+69>e6$Na9$ ztE{~%AB`Y0=6B|I=6AtP`aQ7Ql`!Ua?Z+jvomH)*u|+rEyqg?9ti8Kh{EocH{2Rx0 z|F3NAU7l5ctoH6I@H=>0=67(pX|#8|rkmeIFEU$xXYaeL!^rhiU3E699J*?Sz3)EG z`;K{Hll*0JhbKbwSyMYxJ5xKnr(Ch3Eq-#viinvCHtLEM_Pw+3T~Jw5Z*Uea*inH@i?KtO)suig2zMhiY@!h(8@3Pv^?5?Pp z8pk60dhe%|dvcSeuQl*kDuR6}1)4cjW_RSWyW;HhZOIj9b0%%RhRdx=Pfs>J8L@4e zy3FoyyxWk&fcc#r@9cQztXXHx)?+7U&DM{IRd-h1S#@XC9Z?*Ldps?0dEApTHO`k* zWr1GefPtgMUaG@_(mdC3F~8%8z1ERI?Vf82w5^iS_bv**o1UD-x1N>ij)*9;Vy?Ti ze~ZKKs9Q1Lj(7084Yl=8mEVIqAN+1K_)52?QQu8sc=6q5%kWMWLmw#j<#V0WkxRSG zj(2z=QB#RpX5i6m zAHUP*QVE~q z>2qiqLz}P9G%x%&GY8DqR^lzE5-(~dp-R805$Q&{IDa-cZ=3VsD2)Y$hlqZ7zFaZkNhwG9iC&lU ziJz$NZ2yF7di_YR>YB+%JYkYo{ypt~_ewKBlRT3=lRT5WvTs@aXuvQJQ^ZKcfqm7> z*w^*D7Io(Gki5_q6cnw0ZXPSpwK zR^$2V{0}7#-qWj6)HO-o`Z-8mIQ+%l5ZlH)ar3P9Zk4fIbPbCt$J@|%j(O#_=9ov? z7mm<&+M~zsnGp5i3MTYU{R93t&@7I1wN?gmSm+mXXb2WPOZE$gCP4^((xZ@*pi`lV z!vj3?FDvlO?6x%y(dr7G%6Q(zfnFE@7}iE2Cb^L7M%(NzYQ5q+AK+ zM8v=fyo+OYZ53rRvqOQm*{abqw_8qbw<4A?PHqx6b&J`Jeec5IkCm5Z#S(Kn`u&sI zd@i#dbG!c7q)s)fbJ`=#ln*pU!aZYsvfz>Z?^~yh)%PvlW4XJSo>sMlT}Xg z5`Hs8r~6R1S%Dw|rpJaJztaCl=~dzHpR0{V&Dc;@{ULo028~j`S z_w!C&By0X$AUl*^vr@E_sRaft&GmX~5yW=-g&WYiS`#F7P&QWBQve4P! zDzc*aNa!@W^A;ZrzMokCwLkZ!xhE@X)po#ZKQ8$fvI8DxL2+4>3|c5s{*B|he{7tU z?hZOs-jA-mSS;P$dKyRPweBq1Q*;86K^y#T`V894@E)Y^*i_zaq{pix&_y%6vhr@t zx;xF=3dP^EG+q;h$8SLXDr)5Z4BMD%p#-DS9*Qq#%f1y+`TUHV+}5o!U(WhU=0#Q2 zX4ZX19FHs~zNX3a@|@{p!S`<(6@z_HlF$!g*Kk zgEl*_ghYFb%)))?WJ9Hm;uZc>s9|#jQyeJpo+vVAw~{Btqx7yOcUIozh{CliLVLsc z9JPK;?p(VfC>85IFV)+^r&wVsa=agF%x8whc+dFH^E;X;utm5q-PNm}=|YZDv@mGT zrq8uMmL?ySDcF{kTSxPFDR zXD^oFwQ1`z!(&d5vssh!K^coV{(bfJUx?;xX#24!4=bc=`rAwdFmy=aTwwJbP6O@U zXY8YXGq>Jei(ZeOI1au*!j|=t$>bxgE_adv?CncjSnz=tvamy9ZPG-QCk`P%}r4 zY+ZLZK6^YL>DlDYmUVY%AnbVe@yffl`W3EHVcp%9dIj1+e|F>z#T(@exyY}ab)e|+4ZHOsq19xkyFt0>`Q99qBrcdqlsYIIQS z-)$)zP&ts%#2MfJu6De$<6SlX*t$C$lnw{KPiH5}%JA^uJgTaGoH2_Iwf(qcuIP-} zVvOb1qGZexkL2GtuKULs>+bf|lTgk1zBmx8Vq_U|%&1pzqI>ABkIwpV$@G9NfkchpL=A`zlQ zRKt23bA{M~bNzf=vaC3EzARCC;cB)B+zu^ot{j`M%iNBcQgaKd?Uvi?&fG3IiMd_7 zD#P5a%E8R-oG_*CZg{6ve_p;+t(%34+#VomF3Df<<6>>n((?%JAAKpBbJR zp0#(@-Z49B%d9fPy9|ccry1<}WkRuGzx^1 zH^ZYMLUtVWYicW%2FXS=RXxENQv!X?giWt}H$ zK4(5>f4f|**m<(Ms>OpV*}i-ZqXsmzpz3+JDuq$MTKbM&v#ZxrjhcGW?=)88cN2A| zbFFD=*@TZ$)GKN85u^g=bn3Pr29J9`^>K==&sUSeD!W3vdAT(yMvC!hWAm%B!~3wT zvin+b$XD9y^Dw#xt&DCjlYEeT#CH`9Y%4Q@XyDVq zoxvZ~0j-VpW>f7&-E<<|(=^L&J|AOrey`c9!6KUAd(Ja$+SL9PIh{e9)~BLP z_Y@7~YNt&-*o{kIP1b`|qqK2byFzvIjKj}+~Pwx)T-(p zp+Tcp8;eJ+ivz8T1Z@p|)H7e{@vlUMzEtfB9{syMby^NJO62&qc+z9hBkBY(AB60% zeik*zxOFi2%@B_JFwr4a*@(H&k}{?SkBWMN1-*%Sra@|~8q2D&k!zxACYA}KIo_`a zztZ_rTrMi3(;Tl%IjeK}*}Jlt<9(u-YvujlC!IX{L}SZtBL8@e9IIz2Jl}2(oYh(H z9X>1qjtKYO1Rtz>`Yl-fVcg$QpY&3{NU=`bq-V@>vUfk%DCsvf8|jMRV=o>kQ|JfH zT>2C{=6J=5`kq{qsofZ>@3AJwN|~!GST**A&Nck=%V*(q>a`yS-ztjeI|s|rD}4e! z15{;XExN5Q*sSeGS1(|W7yaOM^|4)4l4BI)tXO5Qa?4wd=c{uZR@#XcZ|JYg@xDpX z;&iI9K8}aVf?qgOQ}**TvDsJpR>%5ou_9AfL1x`hxpkptD7UVn&ZRk?IUYHwM~O+$ z-&;?1B7!hI;cWTHZSZf6 z=+9G2ZEu0sE61))&FJa?;fxsP_chJx11$Tok1dXf4 z91*veK)p&W*Ge)IC=)0XD0;>6#d(W)hM5UewK_l$sE=2YZJmvHCQz)Ky*!)slW!A8 z1OmrOI~hRFlg?v$YVcT$msvzJKgdcl6DSiXGT6wuAkJp3v`LVr&1hT`huUm;eI`($ zBx}ofw34h_U5KWfbt*EKBbPALXhkOC3q60Tp7N$vTCOWr=RyWzUjL5U*Wc$y=DbKE z(>-m)3dak*f@tXI96a_zc^%oNz8~t1E9NYq+c= zE4L|gyn^FxX>`MpC6r`nCRP-WOyfLuPp^sX>Cfufpy<Ai%WLLYrxZcTo(FOwWy86&dwCnb$Wdp&8zCGd$P# zG{bX!&phkKr{%duscv^V^1==@p0N$%A7fr<^!lUaUs{cKTbcFv=P(mxJ)(ba?daxA zP&>Ls^yy5{990d~(>W8gI2x!_UEi}@-;)ZCzkDX>T5@16??=~Yw>X)gxK8se=hM$E z=-o*Bho2knbLXRXdB#$wcThmS^=#=KpN^w=5ncWz`OVDp}$2egSO6G&Rou1&Ri}Z0j?wC zw5pb~uFei^c4#{s{9YtJ>I0Dx>HJvN^>kfNs_O;AuKTl1TK}0-V6T zwLe>Mxi$5i!R10#_w7Vo-D6R_Q`MjAQ`T)L-k&|2v$C$+pUrG8GKlLwhUcqQTJ{Od z>dU6$(yFRZbBjpRcfY2ES9n%C_rd+1_Sb8YM` zXTDNlq3wBV=B$~EnasLIKt{so7hDOon(;hJ+0T=DgQ5>$6|Y{4^}6N|Z5~i>lSb<_ z?`XLE&Gej)M&3WZitGrIg(&bX$!=zRItO{x;W==MN;6Lm@ynj$gna#O+ z0x{TuMq_K{>eXK7MKiaVR8D4d-)lB?SB^YCP4I`k3^uo_*;i(B%x52{X2ztP*D{;q zH88lQZZ2x%SU2}eIoFxZna#l=gN5ZPmawhxyT`HdkbDI-eCcAG5ixnxfXtF_PrVW%|+mGWo3J1Mi4a zqdB2&0WMdp4~M4su(T45Q%LuA)mckkkwz&HULnNxkF_?!su^eBPx>5cB*6BntKHL7 zS%ng8Df60l{UKEqyrZ*vRktRwLbe_aV(YaQQn;n(r_u1WX{74Tb$t73jaBLw9Ve!N zLm1kDOe%|-vyhRxUmbh2j81veFhg_z$I?k<&p%XF!@*w%|In|DT61hqJUK8H`M7N@ zmnfs!Qs-IS2Nyim9CvTAuWbxJgy9GhK}WdaV43R4G8mSFK8LT`MST zvz?SF^m(V+n?lJK`~215dpQ!_)vR<|Ep}a7(UXmJ*-&q`EB?o0tGe|xwOq1kxAfem z`ekaTKGh%2cWCr}D}~_Q$}c-?eKhY^MCh>|`BEyE=;NQNKPKn6=#Mx)Jd)#0>%I8x z;Ly!1Th9GRIZ{jwEH^Er-(P0V}R2x9z}i{_AaNB!s>ozq|H ze|-*G%>Pf+KklX;KdN(<*~-nqEHg1f(b!$vAV1$#)c`Fw^Tzh-q&(C1TN4kIzvI=_ zc4b{h*Efw;{Xiq~4}%|dGvu~SF}2}DM-v@ZbK{|+cOhy zXE$TBo4KiSV0M#hOk|>m!YN2XLHm|1Gc!1d*^SvvL84xZl|^NZB6DsvvAjYZ_)wgo zo+($bzmd2nce()PdOd57JZgQA@KMqAYYQ=)=Zc81| zt_gUY6h`ofLgx|LX;BpuwGlNCQPu>#KrJxTt57tQt0VGD?WM=F1}7r9P~k*vjK6A) zt*kJh7JB@`)K;WUC#`_$J7~hT^|||LMt-VwnDg-w*R!1WdX|g8e@1GxPo>CN)f{z2 z?f04VJG;{5tfx6E4qDH2Z%= z?Ti%1tf&|HRM%*{U_bv9)0w2(iLHbC{8O)Q*X=|l$4S(uPI$66b(sb=sZS;v#9_Ij z;TP&Ri`#2jX?Up%t$i7@tnh$^!jSpV#d%(68we2F9_RwMF`IE-)uCDf@%6mqRc(mE ztf?=3stan!8@fExBkO9f+q%A?bDjLwv2LxW3)_wL$ev!cZ8npB&&Y5)QZ*H{5cCaJ z=5Q2>_#5>SESx^kF51yXilbu}ZRWX54Z05!U z4)E2)Y%VGXM70&x9hv#Q)_nH0`f|QgHJfFt7Kz5U@Bi-0rOY92HJgd^e0Itl902l} zH%%Ly4VhtHJ>6a`2ANq=gge%F7Ib1>$qaeERDJnW(acKXGT%u>ff8bKBAwwC!DjMn zhI$qKTV0nE{q7_}QzkK)&eW+*lhD`|2QLrQI+w4@xvp92=)GG`;R0ffj&roGvcC?F zI?PQr(p+y((K-wWcdzY4fVR|UK21D`*mdV@tF^Q}aho+da6NOND)Y%>T0uI@f%-gr z%z>7b135#hP2+X7IZ%0B-WghxsK>H#Ajm*J8M8*`V>LRgW>$S|`j7Z*IM9~%Au5n$ z8q~EHi^G8+!I7~v&m1Ufr`0*oBgH+RY40a#b(%Ejkz#-?{k7$4-8ehn&4i-<%%60e zLTmI=w{oNqM~C<_{I&7CKA&#pmt{id+gImbIsfWta8n+V*X1b8<=PoZ4#%I1@lM_q zw}I@S+8HNygF7;RBmIZhTV^|EJ5(7o+j)`d<2=zhnyWBA zDQTRZC`-5MjZ;2iW;@HucI=$I*lcG~ROkGwWn(+ezbce`!Bj5ZeFwIa&%C(qqAGFz z&5rF%w-<}WcIMkP8I8||=BwenL*0Vor0~IkViMIMOO$o(tn;Bd60f6mRaHJW^f-^2 z=qwu1DR@-4{IITnoN~FS`SwbBF*3XO);v!YqeK1wzvm=Xraz`XmqCB1k43H)u7Eol zH5oTYx{VhP^|H`x?&z|n7LDR(LywR24EbO?raw9=R`!_wknKx#WYeEwZg2OU_S#QF zRnz=Rd0n&@D|;4?{*?1Ntn8VjV_7cxv#I&iq$6Tw&&Miz$l+N_N4lUdzXboeG4)<7 z2LHi*2ba#MemSq&9-^4g_7Llks}=q`TNCLI+Yd{xK+n@8KcVI_`}s*n_NuU-SIH%L zIQWlpm#KY@e{%RRgojV$F8@EZl!E@?=7IMzxlQG5M~aMB25f7+k3D_n&C_Q`q=)vo zsSAW>L#mz?@gMwqsFwazZJ$S1b#Gn&nXkl*b}i8$>wQdotoLD5F!9N(C$4X)=XkCa zkHQh#e#=qkyi9zo_gOUJQ_bGU5odqAIXh3Eh)CLwaVQ-w*3)NGbA|kki|hXHzFfqI znUsl79wBC>_rZ@A)>^*wx5C*de$axHGdtOzuKidH<`cd?Xkz%Dm&<%Of;;jX!#fhS z#WVfkBuSkzbQndQGUg`T+g0Z&<$5nsyA-X^_j*6P-x<;0rFb8$Q1J=TyM_j+a675K zGj(`ZQacTgp*yK>n4~$1tvI?>g%NFjI*n1?H7Ms(l^uqfPD0u1d8}3b10B=P^_Opb zk@kFOufH8U(02Fqzxf>R zv8rcL5i$E&>)2+bZ8!>yn9X-#52IhFFTX#ytF2KT!KBEUW{rpOHZ)3cd{K6?@)La{ zpS(KwpTXDqzoKvagL?6A_3Q&3({(-iy*?GUu)F$RajUz^A5|s!ogT?o>^t4MuYH05 zAM3gpaTpVNgeF^gYeU$>~t@)vzBzX%LJKXyO0U8*ATJLGSi_b`8R z-ZRvjPj&7c41Uu6=(Y36TOQZZ^>SvI2AT%a1{akEhJu-MiZhoUt(>>m72CvY_`E!j zUYaw$|6Ng4an8Th|JOPTIRm4AiQfRX#ch#D_V!6_OCBl4!zVg_>Ge+aXukEY6h*LV zb8YZ%z3=nRy&?K2)6TZ~f48*78-2#9-v5REm=7`@mJ#SF|)n zsdqZo5&s?NccZpStj%^;t&Z2;)4ZUs_SZfAX3qB1v><{>ChlxFe2Lw9O(yJs)2zli=JsnHMv=#$}7oDM!wbp`Y~}zXLSmP`9{G zObmt3dbsD>j@W_sioNAE{Ek7=;Q+ipF-39CoG9|2p3FeihhnPcZ<*0m`5WHdO}fT5 z1%F#@*~0vdyvK-nqdgZ)*8I)<4T2B*8YMPAiN_bzp&*B49$#NejQPx4^}CrKRM!&o zDrM|LnaLCiWqRGrnls;Ug}tooE{@JTyKk|VF@~DI{Yv9}aWQ{;cNT{bn^1|3xfFjf zW(8lnme#=7;-2Cc=8D;+!u+k=mdxMG-*S7nrD)=OM+35ds2TNZ%@`pBKkM&QM4I!i zS_N@cE6+UItm4rP?RGYQUP&*BE-m_kN+VWZCbWF#+F5ZD_Uie$R z>SWikUo8H%mRiF+6S&C0yrbFkXbJ+yTsv4>lflJzoh^f_E8kW$mcC5Kcw6V=kg$eZ7lD@NF9z}sOZ*{ zV(wch*6!-wKaw`?vHqi^yQ6BBi6fF-GzHT>~0$M9MO1urzX1#7H)Qz>jY%INvNXEN4}=h z&9SnOHCV?|Ud1OBJdwN!NL}dbcs%kILVK6D4=oqWll2GG@nt1gJKu}E9!EN=_WMYn#M>$G) zvZ%SI*J7Ienf(+`$jEoYXj*HT%ow4r>~O?C;~*ADY4PrT>V}iv3ON=4bX7XB$pZt)=`F1Vjnc&s9{xftfGMf=qij(Ya1d#U-1^JP&~#admV zo@1_V6`u2DyH#O@y3;B=tMG^z=`*VOpStqKD!fVD$tt{p$p()wiThX*SFR%*Jnoku zapN=YS`fjmy;v*~x0(9Ic~#*-;?~q}<6*Zx6^Uajez~RwFDiG;mczM*M0Ojuhh6xl zhYOrN?1D1d!!CQ* zbkg6`|&ZAi|PoMRoT@!tIk=i)+~wh(mAWnSmnh?3j5WZbjin!0XQEz2~YE zt~!xjo~Bb#%}dpZbupMWMpveIWq8hc&Dl}Td3DY!etOj!7tVRj zxvu%TobyVCe_O5B%;l&VK@XE(o3BmfmXpd+A7oQhZhdf5qZ{)VYwL!lasyMj=M@fF zr;8882FjVcRWH3ZAGLX-azss0N3DBrPgIllRO_K{_5XFM{uisAtWl78xu1MtsQq;^ zcv9PvN2maNn$}dGh^Vkida6hBt$!uwcvfAltTU54QCvmM+bRz*lY5l>I;jObnMj(+ zxf(>y?Oj*zK8eT9D&LhTqp|o^PuGLUzKBJAl=JETTo0m;x~wQ#IKLKZUKDbSfs++-=yfxL z%h&V8;_hnyh~YAor`pTQqqqI;hspcwSpSEE z|LPCj6Oko^8_n%dm1EIopsw9{|JePrPukJ8c28rBi>jz6ikRbfa76>^7T7(_?rEk; z`>7TqY8}%g)1>wzV4CzmGm<`cFJ>mLEn2Vb@ypVrI#252IJ2ZlIN?vnv=)md&AY-2 zMMEgOMq0t1mM5_-t>|TnLr_Pso)s(LJZ_$JtT@lEX^a%*HHE@gj%b%zH!XS2)P(3n?a!>s`a{^b$bcM_|trYY9Nja-zwZ2Jn%^i*PQ8E2h(scYoj?-VJ z*{6NK?E6*Cg^qaEzF+qJq9sIYaK>O&A$We@ue#sXbSkMI2j6NSM!-U9Bbxq zsF;tagZV?XS}qPM=1KGA#!qZXm4wWbrq?HpS~CX2!{u^$(w1iUFH?Kl7Lzzi>+ZzR z@U8l}lD(|nX>Q9bm(}@t%N=UA!gfJIj#8UuyI1M4AQwCG!h&nS&56x}YmiBqKOyKy zj?G!=e|}D{tjF&eNxh0%rYL9>!iArGfM4yN?G3u(({wxLT=^PG8IFo*np#11GW_!N7C)${nuy=6XP&9+zk*8FBa>o^8WaIMZ>!8XU!-c3dZ^o4FMKT8IFz30P;H79hfWFg6MZ1`_cufMhkJB6x_8~pNtDIjV43lm^tEdj>-3@?EjAh3cxug7u+>@RA#PUsg6=b6ag<~0 zautb;z4^^Vc=j_BdnZ{Xx=v|AJK#^XIWWI zc2(76-zt~x3#nTlbWpV~)GO^J1`RPGORSx~t?FIy1%SZy`-H8x5ruO87M>Px7DXdyIDcLWX+gaDZb+fTq61uT+XUep`doLD=(2dUg zKj)XwQ6)6Acx#GSr_JqTTUvRO&8?-!tC@Q5G@cYXpgxscS+PXqQ`UE8%^QlNqVA@> z!X~k~$b>-Kf%ct*s+j3DS+1qIytOp1zFuy9irmiC7N%z&x12mK`}U9R z+pqDPsF*P%=Y*r;H;;=Mb6MwQ9(VP9`x)y{f%fx5JNKi(F6Z3d9Q;k(C~7-~W5;bp z2u~FqlS^=#qUP|rd7NgVhk7-Y-D8%&Bd-Z!gE;PY^{QvO*lTT9N8;jmTv;<`#oVNG zeAyIpHJ(g26}f}w@l~yILuYAim<7^s+-QC z1Y*^qxrOOmU#nQ$OxCQNTeosf(WRAh-LqHIIovIClyvp!9Eze?aqiB$wQkP3xoU-u zCS$v$xX5gdezWRmzar039Fn?v)SwbyiB0~f*LEuh&F0MJ@~CrF@>p@p`Q_OhnUB-$ z#bU8JdYpMx%~5GItLA7U)2im!mhaWfVhuIjWwE((KC8(b`2ZhA=A6;$3J}Xwt#nIc zQIopm3J{^1Ya@5&a?8!-oY5L}l$_Dp?vG~GT-6Ki>T@|}mCk7W^`Ncx=ZTbGyHbEr zy~vjIOoZ`#kuHk@OxcT?=IJ*GMVFi*-IJPs3&uVcl&Y4yoB11Y|*_oa5woc zKhiec>=FkGewV10NDg__OKE=g=2?zo|7SX*^l3weykb~+&G4*UHECVciI^>| zWA>C?=gPbe8f+z9Kd*Cj2v>)oO6PZ)F$WubT>shD#&9YWr!O`?(%J89dRW zd(!Tort0kR2zrnhou8z9NNWETxBit}&RrkEestYr#yPLn)mc|})pT|Bx(vUJAKmn> zc#A{tsJ#HAoL_zi*$sZTQekmhQ}8=pS!RC2Gu?8N--RdNZ24W8*}0zUB!!)cor#@^ z-RFv#cGX8y&$TmieOxW;Sola>J`KS*D-%rd)wchSH@p+sXA@h*? z(6*sJ9439L^>);j`B{Ib@@;JD{Z`KuLf!hpJ35lx{Q9b7!mcKz-FeQy*(mN9<(4ZO zc2)WJ#Ch{vMFQO&TFl^f>lFsKF$K3<*DI^sF7$OxZWlfT=61(YH14EzRkX;5gXd}` zLvesD*9nOfB-LI)gg_)mQ$jKS&ljn%w6M2Y*TZKUxe6mup}T$f{&gpM zCSJ)rfp5yJGAe@K)-!oLbB=6#zLHgTW_DKDkuB<~uT7qB_qtate8%o|R6%ZXD~?O1 zInI%V85fsZViAkN?DB7%ue(;ntrS@L9I zaOBFu8~>1cjorkUcG4U^>Z}tnpzV)r+48HH`*(4>8|mFHkJ}Ml5Z$0U+kKx!i>k)C zv&Vx!>8KE|?WSAL6#EfX@@O0}*2&2*CjJ}h_F;+@chhT*^hhwm=PB}huKVPB@D9Ut z4OM)q*Y4?c!@=}?ke2wz!QTgu&tk&qX(nxUH^s2zO7K1U*Y?ZLQEV2N|dFh(10uMs62`@`av1 zRlj~ynwaZ@H|cUMjdqcXSdh+wSYFrsrA;aG_H5*XN>u68-NAi%kK}zAYFvWb?xis$ zkErFxs0Yb?*5MeN57Uf^EuZT(;Sd#hGAAOXj7{-ax4)5jVf7cWr=GKNU5Pe0hpS!e ztB-BxVy<@4eq1uUF)u7wVQ{lM8nvzQ>plYI_?0;hP7%y&h>x3XZ8ta*$?5!8s z(qt3it+$m}A*=WNetnd1OV7gd)#K%k-4{=x(0rFLW{6>NmG_4|Ykk7`!r=udt zOj&dD9Qx2u@qDAtB&I&q`;;|35Z8H8iC=A9*$!zx5@{%0)lBu2?si)_f}bqGg~B_*DBE)i5R#JGPue>|)J%w#3fv zd7-_72AR=C?wOT!pPwA6@8hoXzDgKmReI6s;R8AoTRi|TwZ5QUE6PO zO&)xS>&)xS>u`HTMP*H$d7U+N2cks5>*hsM2T3Vk7N6IRQ;aw7#18&O z7VKuCaMKXG&56V=yaG+^@DR^rCyqw;1$UhbMrwQ4;WzIr*ms(_%>%I;weYyI!j6jL zCUz!vII}UAv3FfFFW$t?#Lf!4;eg!dH%YnpR{xy^>nvDj!Jg=xzzy{Gx{mrxCU#M^ z$_hIx>#6H8qnSEnTd)72*ITW8-o z6FU<-6T7ZB09DJ+8ZQf-k*m|XI&Hm8te#Z?Xro5KuI{KEb=O*Wr0kqMQ*4 zTLPVDv;9$Vpqo*ZN!bso%Gmd*kM$W*O_91(QSGXnlhyp}tr{ttZa;n;d^@xg zvB|Y2#>u6-%9W2>{rT#w2~Vp{QPfrG z1R!=~_~fdzlXZ4%9n_B60>iU2DL#=1mt1V#dz{V%lRJ|;lRH{x5<+Nf=`4%dlgHWwF@yXqX&*|E-!b#`p-8(eY1*|E-! zb#`n)t>+~>c3Q>X;*h(IGom%GZg!)je=NJ%!Qm!zyESby4Y%941a3!+eIUB|x&HF4 zFVgq2r=30RSWkJTHcf2~bRVexa!uocd7sa-ue@TBGoR}#7xuKfs-AX?Qj@41wW)Ij zi+t@G6w6tIVkC#d&B13Pxi{0Ovy%K_AE~#AzQKAs>+OimP3=tWthck?&U!oR?G7{^ z?8%e)D9sU0lB1t%P>`MA4B4C^>lze!y)$M{S^ZU=Mf^qQ&S5%>P*9@P9S-hG9m1JK z#bMNeyE@lSbdQ>pM|uobuEV4)YpNVk_-<$hALMGDxn0hX9i2mOx2_0f8g93F3EXZ~ z$2jx4GPy(HYI0|Cx1*M7a`(HzZ#7SF_1Jr=tJq|2Xz6nG*iET~#~s^@)Z3#_MnB(t z7n8f?BzM-^S#QUTF+8NBLTh^_iK|gC$EMm485!2wS#M{(o%MFEMqzRnN<)*oa+Y}b zCYN(mf0&-K}i(FZLo#}IB*?Jmw7xi;9Ek)yWs_ZJ9&RBh< zHUo3Gdj5CTF_pDVXc)>l;RTOzCE0EL7NI4}jEYB|YL=XpW@PY_qe%Ttp4rodHJ<rw&0W9dj_O4RiP!Dw zm)}<1GiYAsrqfeB+RrzGKdSA7<5eiU?kaz|(0UEeZdV^Q;d`MYj)Oh9b-`9m_^t-w z+mhP1zw+!2^{Q6q*$wZ3&TK}cQK~HenOa58t7X;9gwKTUS84~v#e~m<&v~`Zt94$j z^J<+}`#7mej-?}sTu7o;tMkh0JQKcR?u08zW=_>v#H{jaFJGAlA>9$VL5BtNf2KcH fz;YgN)VpA-mwNtGXUmBqjv#b}L)gjS{|x>=q>@7W literal 0 HcmV?d00001 diff --git a/assets/solidworks/materials/en-US/shader_mapping.dat b/assets/solidworks/materials/en-US/shader_mapping.dat new file mode 100644 index 0000000..31b6b99 --- /dev/null +++ b/assets/solidworks/materials/en-US/shader_mapping.dat @@ -0,0 +1,399 @@ +leather, Leather (PW) +sponge, Sponge (PW) +grass, Grass (PW) +sand, Sand (PW) +skin, Skin (PW) +heavyclouds, Heavy Clouds (PW) +lightclouds, Light Clouds (PW) +clearsky, Clear Sky (PW) +waterstill, Water Still (PW) +orientedstrandboard, Oriented Strand Board (OSB) (PW) +hardwoodfloor, Hardwood Floor (PW) +hardwoodfloor2, Hardwood Floor 2 (PW) +hardwoodfloor3, Hardwood Floor 3 (PW) +laminatefloor, Laminate Floor (PW) +laminatefloor2, Laminate Floor 2 (PW) +laminatefloor3, Laminate Floor 3 (PW) +polishedash, Polished Ash (PW) +satinfinishash, Satin Finish Ash (PW) +unfinishedash, Unfinished Ash (PW) +polishedbeech, Polished Beech (PW) +satinfinishbeech, Satin Finish Beech (PW) +unfinishedbeech, Unfinished Beech (PW) +polishedbirch, Polished Birch (PW) +satinfinishbirch, Satin Finish Birch (PW) +unfinishedbirch, Unfinished Birch (PW) +polishedcherry, Polished Cherry (PW) +satinfinishcherry, Satin Finish Cherry (PW) +unfinishedcherry, Unfinished Cherry (PW) +polishedmahogany, Polished Mahongany (PW) +satinfinishmahogany, Satin Finish Mahogany (PW) +unfinishedmahogany, Unfinished Mahogany (PW) +polishedmaple, Polished Maple (PW) +satinfinishmaple, Satin Finish Maple (PW) +unfinishedmaple, Unfinished Maple (PW) +polishedoak, Polished Oak (PW) +satinfinishoak, Satin Finish Oak (PW) +unfinishedoak, Unfinished Oak (PW) +polishedpine, Polished Pine (PW) +satinfinishpine, Satin Finish Pine (PW) +unfinishedpine, Unfinished Pine (PW) +polishedrosewood, Polished Rosewood (PW) +satinfinishrosewood, Satin Finish Rosewood (PW) +unfinishedrosewood, Unfinished Rosewood (PW) +polishedsatinwood, Polished Satinwood (PW) +satinfinishsatinwood, Satin Finish Satinwood (PW) +unfinishedsatinwood, Unfinished Satinwood (PW) +polishedspruce, Polished Spruce (PW) +satinfinishspruce, Satin Finish Spruce (PW) +unfinishedspruce, Unfinished Spruce (PW) +polishedteak, Polished Teak (PW) +satinfinishteak, Satin Finish Teak (PW) +unfinishedteak, Unfinished Teak (PW) +polishedwalnut, Polished Walnut (PW) +satinfinishwalnut, Satin Finish Walnut (PW) +unfinishedwalnut, Unfinished Walnut (PW) +redceramictile, Red Ceramic Tile (PW) +floortile7, Floor Tile 7 (PW) +floortile6, Floor Tile 6 (PW) +floortile5, Floor Tile 5 (PW) +floortile4, Floor Tile 4 (PW) +floortile3, Floor Tile 3 (PW) +floortile2, Floor Tile 2 (PW) +floortile1, Floor Tile 1 (PW) +cobblestone4, Cobblestone 4 (PW) +cobblestone3, Cobblestone 3 (PW) +cobblestone2, Cobblestone 2 (PW) +cobblestone1, Cobblestone 1 (PW) +cobblestone, Cobblestone (PW) +limestone, Limestone (PW) +sandstone, Sandstone (PW) +slate, Slate (PW) +granite, Granite (PW) +polishedpinkmarble, Polished Pink Marble (PW) +pinkmarble, Pink Marble (PW) +polishedgreenmarble, Polished Green Marble (PW) +greenmarble, Green Marble (PW) +polisheddarkmarble, Polished Dark Marble (PW) +darkmarble, Dark Marble (PW) +polishedbeigemarble, Polished Beige Marble (PW) +beigemarble, Beige Marble (PW) +polishedbluemarble, Polished Blue Marble (PW) +bluemarble, Blue Marble (PW) +flemishbrick, Flemish Brick (PW) +firebrick, Fire Brick (PW) +weatheredbrick, Weathered Brick (PW) +oldenglishbrick2, Old English Brick 2 (PW) +oldenglishbrick, Old English Brick (PW) +bonechina, Bone China (PW) +ceramic, Cermaic (PW) +stoneware, Stoneware (PW) +earthenware, Earthenware (PW) +porcelain, Porcelain (PW) +pavingasphalt, Paving Asphalt (PW) +pavingwetconcrete, Paving Wet Concrete (PW) +pavingdarkconcrete, Paving Dark Concrete (PW) +pavinglightconcrete, Paving Light Concrete (PW) +pavingredconcrete, Paving Red Concrete (PW) +pavingstone, Paving Stone (PW) +clothwhitecotton, Cloth White Cotton (PW) +clothbeigecotton, Cloth Beige Cotton (PW) +clothbluecotton, Cloth Blue Cotton (PW) +clothburgundycotton, Cloth Burgundy Cotton (PW) +clothgreycotton, Cloth Grey Cotton (PW) +clothcanvas, Cloth Canvas (PW) +clothburlap, Cloth Burplap (PW) +carpetcolor5, Carpet Color 5 (PW) +carpetcolor4, Carpet Color 4 (PW) +carpetcolor3, Carpet Color 3 (PW) +carpetcolor2, Carpet Color 2 (PW) +carpetcolor1, Carpet Color 1 (PW) +sandblastedglass, Sandblasted Glass (PW) +frostedglass, Frosted Glass (PW) +reflectivegreenglass, Reflective Green Glass (PW) +reflectiveblueglas, Reflective Blue Glass (PW) +reflectiveclearglass, Reflective Clear Glass (PW) +mirror, Mirror (PW) +brownglass, Brown Glass (PW) +greenglass, Green Glass (PW) +blueglass, Blue Glass (PW) +clearglass, Clear Glass (PW) +texturedrubber, Textured Rubber (PW) +perforatedrubber, Perforated Rubber (PW) +tiretreadrubber, Tire Tread Rubber (PW) +glossyrubber, Glossy Rubber (PW) +matterubber, Matte Rubber (PW) +darkpowdercoat, Dark Powdercoat (PW) +aluminumpowdercoat, Aluminum Powdercoat (PW) +blackspraypaint, Black Spray Paint (PW) +redspraypaint, Red Spray Paint (PW) +candyappleredcarpainthq, Candy Apple Red Car Paint HQ (PW) +steelgreycarpainthq, Steel Grey Car Paint HQ (PW) +sienacarpainthq, Siena Car Paint HQ (PW) +glossredcarpainthq, Gloss Red Car Paint HQ (PW) +glossbluecarpainthq, Gloss Blue Car Paint HQ (PW) +metallicgoldcarpainthq, Metallic Gold Car Paint HQ (PW) +metallicwarmgreycarpainthq, Metallic Warm Grey Car Paint HQ (PW) +metalliccoolgreycarpainthq, Metallic Cool Grey Car Paint HQ (PW) +whitecarpaint, White Car Paint (PW) +blackcarpaint, Black Car Paint (PW) +beigesatinfinishplastic, beige Satin Finish Plastic (PW) +yellowsatinfinishplastic, Yellow Satin Finish Plastic (PW) +greensatinfinishplastic, Green Satin Finish Plastic (PW) +redsatinfinishplastic, Red Satin Finish Plastic (PW) +bluesatinfinishplastic, Blue Satin Finish Plastic (PW) +blacksatinfinishplastic, Black Satin Finish Plastic (PW) +darkgreysatinfinishplastic, Dark Grey Satin Finish Plastic (PW) +lightgreysatinfinishplastic, Light Grey Satin Finish Plastic (PW) +creamsatinfinishplastic, Cream Satin Finish Plastic (PW) +whitesatinfinishplastic, White Satin Finish Plastic (PW) +beigelowglossplastic, beige Low Gloss Plastic (PW) +yellowlowglossplastic, Yellow Low Gloss Plastic (PW) +greenlowglossplastic, Green Low Gloss Plastic (PW) +redlowglossplastic, Red Low Gloss Plastic (PW) +bluelowglossplastic, Blue Low Gloss Plastic (PW) +blacklowglossplastic, Black Low Gloss Plastic (PW) +darkgreylowglossplastic, Dark Grey Low Gloss Plastic (PW) +lightgreylowglossplastic, Light Grey Low Gloss Plastic (PW) +creamlowglossplastic, Cream Low Gloss Plastic (PW) +whitelowglossplastic, White Low Gloss Plastic (PW) +beigemediumglossplastic, Beige Medium Gloss Plastic (PW) +yellowmediumglossplastic, Yellow Medium Gloss Plastic (PW) +greenmediumglossplastic, Green Medium Gloss Plastic (PW) +redmediumglossplastic, Red Medium Gloss Plastic (PW) +bluemediumglossplastic, Blue Medium Gloss Plastic (PW) +blackmediumglossplastic, Black Medium Gloss Plastic (PW) +darkgreymediumglossplastic, Dark Grey Medium Gloss Plastic (PW) +lightgreymediumglossplastic, Light Grey Medium Gloss Plastic (PW) +creammediumglossplastic, Cream Medium Gloss Plastic (PW) +whitehighglossplastic, White High Gloss Plastic (PW) +beigehighglossplastic, beige High Gloss Plastic (PW) +yellowhighglossplastic, Yellow High Gloss Plastic (PW) +greenhighglossplastic, Green High Gloss Plastic (PW) +redhighglossplastic, Red High Gloss Plastic (PW) +bluehighglossplastic, Blue High Gloss Plastic (PW) +blackhighglossplastic, Black High Gloss Plastic (PW) +darkgreyhighglossplastic, Dark Grey High Gloss Plastic (PW) +lightgreyhighglossplastic, Light Grey High Gloss Plastic (PW) +creamhighglossplastic, Cream High Gloss Plastic (PW) +whitehighglossplastic, White High Gloss Plastic (PW) +translucentplastic, Translucent Plastic (PW) +frostedplastic, Frosted Plastic (PW) +polypropyleneplastic, Polypropylene Plastic (PW) +polycarbonateplastic, Polycarbonate Plastic (PW) +largesparkerosionplasticblue, Large Spark Erosion Plasitc Blue (PW) +sparkerosionplasticblue, Spark Erosion Plasitc Blue (PW) +blueknurledplastic, Blue Knurled Plastic (PW) +bluedimpledplastic, Blue Dimpled Plastic (PW) +bluetreadplateplastic, Blue Treadplate Plastic (PW) +carbonfiberaramidfabric, Carbon Fiber Aramid Fabric (PW) +carbonfiberdesignfabric, Carbon Fiber Design Fabric (PW) +carbonfiberdyneemaplain, Carbon Fiber Dyneema Plain (PW) +carbonfiberinlayunidirectional, Carbon Fiber Inlay Unidirectional (PW) +carbonfiberepoxy, Carbon Fiber Epoxy (PW) +circularmeshplastic, Circular Mesh Plastic (PW) +diamondmeshplastic, Diamond Mesh Plastic (PW) +polishedsteel, Polished Steel (PW) +brushedsteel, Brushed Steel (PW) +satinfinishstainlesssteel, Satin Finish Stainless Steel (PW) +mattesteel, Matte Steel (PW) +sandblastedsteel, Sandblasted Steel (PW) +burnishedsteel, Burnished Steel (PW) +caststainlesssteel, Cast Stainless Steel (PW) +castcarbonsteel, Cast Carbon Steel (PW) +plaingalvanized, Plain Galvanized (PW) +chainlinksteel, Chain Link Steel (PW) +machinedsteel, Machined Steel (PW) +stainlesssteeltreadplate, Stainless Steel Treadplate (PW) +stainlesssteelknurled, Stainless Steel Knurled (PW) +wroughtstainlesssteel, Wrought Stainless Steel (PW) +chromiumplate, Chromium Plate (PW) +brushedchromium, Brushed Chromium (PW) +satinfinishchrome, Satin Finish Chrome (PW) +mattechrome, Matte Chrome (PW) +sandblastedchrome, Sandblasted Chrome (PW) +burnishedchrome, Burnished Chrome (PW) +chromiumplatecast, Chromium Plate Cast (PW) +polishedaluminum, Polished Aluminum (PW) +brushedaluminum, Brushed Aluminum (PW) +satinfinishaluminum, Satin Finish Aluminum (PW) +mattealuminum, Matte Aluminum (PW) +sandblastedaluminum, Sandblasted Aluminum (PW) +burnishedaluminum, Burnished Aluminum (PW) +castaluminum, Cast Aluminum (PW) +blueanodizedaluminum, Blue Anodized Aluminum (PW) +aluminumtreadplate, Aluminum Treadplate (PW) +polishedbronze, Polished Bronze (PW) +brushedbronze, Brushed Bronze (PW) +satinfinishbronze, Satin Finish Bronze (PW) +mattebronze, Matte Bronze (PW) +sandblastedbronze, Sandblasted Bronze (PW) +burnishedbronze, Burnished Bronze (PW) +castbronze, Cast Bronze (PW) +manganesebronze, Manganese Bronze (PW) +polishedbrass, Polished Brass (PW) +brushedbrass, Brushed Brass (PW) +satinfinishbrass, Satin Finish Brass (PW) +mattebrass, Matte Brass (PW) +sandblastedbrass, Sandblasted Brass (PW) +burnishedbrass, Burnished Brass (PW) +castbrass, Cast Brass (PW) +polishedcopper, Polished Copper (PW) +brushedcopper, Brushed Copper (PW) +satinfinishcopper, Satin Finish Copper (PW) +mattecopper, Matte Copper (PW) +sandblastedcopper, Sandblasted Copper (PW) +burnishedcopper, Burnished Copper (PW) +castcopper, Cast Copper (PW) +wroughtcopper, Wrought Copper (PW) +polishednickel, Polished Nickel (PW) +brushednickel, Brushed Nickel (PW) +satinfinishnickel, Satin Finish Nickel (PW) +mattenickel, Matte Nickel (PW) +sandblastednickel, Sandblasted Nickel (PW) +burnishednickel, Burnished Nickel (PW) +castnickel, Cast Nickel (PW) +polishedzinc, Polished Zinc (PW) +brushedzinc, Brushed Zinc (PW) +satinfinishzinc, Satin Finish Zinc (PW) +sandblastedzinc, Sandblasted Zinc (PW) +burnishedzinc, Burnished Zinc (PW) +castzinc, Cast Zinc (PW) +brushedmagnesium, Brushed Magnesium (PW) +mattemagnesium, Matte Magnesium (PW) +satinfinishmagnesium, Satin Finish Magnesium (PW) +sandblastedmagnesium, Sandblasted Magnesium (PW) +burnishedmagnesium, Burnished Magnesium (PW) +castmagnesium, Cast Magnesium (PW) +matteiron, Matte Iron (PW) +sandblastediron, Sandblasted Iron (PW) +castiron, Cast Iron (PW) +wroughtiron, Wrought Iron (PW) +brushedtitanium, Brushed Titanium (PW) +mattetitanium, Matte Titanium (PW) +satinfinishtitanium, Satin Finish Titanium (PW) +sandblastedtitanium, Sandblasted Titanium (PW) +burnishedtitanium, Burnished Titanium (PW) +casttitanium, Cast Titanium (PW) +brushedtungsten, Brushed Tungsten (PW) +matttetungsten, Mattte Tungsten (PW) +satinfinishtungsten, Satin Finish Tungsten (PW) +sandblastedtungsten, Sandblasted Tungsten (PW) +burnishedtungsten, Burnished Tungsten (PW) +casttungsten, Cast Tungsten (PW) +polishedgold, Polished Gold (PW) +mattegold, Matte Gold (PW) +satinfinishgold, Satin Finish Gold (PW) +polishedsilver, Polished Silver (PW) +mattesilver, Matte Silver (PW) +satinfinishsilver, Satin Finish Silver (PW) +polishedplatinum, Polished Platinum (PW) +matteplatinum, Matte Platinum (PW) +satinfinishplatinum, Satin Finish Platinum (PW) +mattelead, Matte Lead (PW) +sandblastedlead, Sandblasted Lead (PW) +burnishedlead, Burnished Lead (PW) +castlead, Cast Lead (PW) +brushedgalvanized, Brushed Galvanized (PW) +mattegalvanized, Matte Galvanized (PW) +coarsegalvanized, Coarse Galvanized (PW) +castgalvanized, Cast Galvanized (PW) +roughgalvanized, Rough Galvanized (PW) +shinygalvanized, Shiny Galvanized (PW) +SpecCobblestone, Cobble Stone Specular (BGE) +SpecFloorTile, Floor Tile Specular (BGE) +TransFloorTile, Floor Tile Transparent (BGE) +Aluminum1345Alloy, Aluminum 1345 Alloy +Aluminum1350Alloy, Aluminum 1350 Alloy +Aluminum2014Alloy, Aluminum 2014 Alloy +Aluminum2018Alloy, Aluminum 2018 Alloy +Aluminum2024Alloy, Aluminum 2024 Alloy +Aluminum3003Alloy, Aluminum 3003 Alloy +Aluminum6061Alloy, Aluminum 6061 Alloy +Aluminum7079Alloy, Aluminum 7079 Alloy +AluminumBrushed, Aluminum Brushed +AluminumCircularBrushed, Aluminum Circular Brushed +AluminumSatin, Aluminum Satin +BrassPolished, Brass Polished +BronzeAluminum, Bronze Aluminum +BronzeCommercial, Bronze Commercial +BronzeManganese, Bronze Manganese +BronzeTinBearing, Bronze Tin Bearing +CarbonFibre, Carbon Fiber +ceramicporcelain, Ceramic Porcelain +ceramicporcelain2, Ceramic Porcelain2 +ChromePolished, Chrome Polished +Cobalt, Cobalt +CopperPolished, Copper Polished +CopperWrought, Copper Wrought +Glass, Glass +GlassFibre, Glass Fiber +GoldDull, Gold +IronCast, Iron Cast +IronDuctile, Iron Ductile +IronMalleableCast, Iron Malleable Cast +LeadPure, Lead Pure +Molybdenum, Molybdenum +AlloyMagnesium, Magnesium Alloy +AlloyMonel400, Monel 400 +NickelBrushed, Nickel Brushed +NickelPolished, Nickel Polished +PlasticAcrylic, Plastic Acrylic +PlasticDimpled, Plastic Dimpled +PlasticMT11010, Plastic MT11010 +PlasticMT11020, Plastic MT11020 +PlasticMT11030, Plastic MT11030 +PlasticMT11040, Plastic MT11040 +PlasticMT11050, Plastic MT11050 +PlasticMT11060, Plastic MT11060 +PlasticMT11070, Plastic MT11070 +PlasticMT11080, Plastic MT11080 +PlasticMT11090, Plastic MT11090 +PlasticMT11100, Plastic MT11100 +PlasticMT11110, Plastic MT11110 +PlasticMT11120, Plastic MT11120 +PlasticMT11130, Plastic MT11130 +PlasticMT11140, Plastic MT11140 +PlasticMT11150, Plastic MT11150 +PlasticMT11155, Plastic MT11155 +PlasticMT11200, Plastic MT11200 +PlasticMT11205, Plastic MT11205 +PlasticMT11215, Plastic MT11215 +PlasticMT11230, Plastic MT11230 +PlasticMT11235, Plastic MT11235 +PlasticMT11240, Plastic MT11240 +PlasticMT11245, Plastic MT11245 +PlasticMT11250, Plastic MT11250 +PlasticNylon, Plastic Nylon +PlasticShiny, Plastic Shiny +PlasticPerspex, Plastic Perspex +PlasticPTFE, Plastic PTFE +PlasticPolycarbonate, Plastic Polycarbonate +PlasticPolypropylene, Plastic Polypropylene +PlasticPolystyrene, Plastic Polystyrene +PlasticPVC, Plastic PVC +RubberSoft, Rubber Soft +Silicon, Silicon +SiliconDioxide, Silicon Dioxide +silverpure, Silver Pure +StainlessSteel, Steel Stainless +SteelAISI304, Steel AISI 304 +SteelAISI1020, Steel AISI 1020 +SteelCastCarbon, Steel Cast Carbon +SteelCastStainless, Steel Cast Stainless +SteelGalvanised, Steel Galvanised +SteelPlainCarbon, Steel Plain Carbon +SteelSatin, Steel Satin +SteelWroughtStainless, Steel Wrought Stainless +Titanium, Titanium +Tungsten, Tungsten +Vanadium, Vanadium +water, Water +WoodBeech, Wood Beech +WoodMahogany, Wood Mahogany +WoodMaple, Wood Maple +WoodOak, Wood Oak +WoodPine, Wood Pine +WoodRedCedar, Wood Red Cedar +WoodTeak, Wood Teak +Zirconium, Zirconium \ No newline at end of file diff --git a/assets/solidworks/materials/en-US/solidworks materials.sldmat b/assets/solidworks/materials/en-US/solidworks materials.sldmat new file mode 100644 index 0000000000000000000000000000000000000000..e492c7be3e7a9151ace569129aec8435473132f0 GIT binary patch literal 895550 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 jqn|T|IE@UX23;2Y)G~-Upb`hvs$tpqrxr+)R#U literal 0 HcmV?d00001 diff --git a/assets/solidworks/materials/en-US/sustainability extras.sldmat b/assets/solidworks/materials/en-US/sustainability extras.sldmat new file mode 100644 index 0000000000000000000000000000000000000000..45a3ad02d255945eed3dd0dcab523c1fe684024c GIT binary patch literal 12804 zcmeHOT~8WO5S>?>^gj@v+N7YOHO5dIKU&j7TOv00!53IQ+!lebAn@ngo-@O`cNdD9 zwzMHP8xYug=i|)Gxp!yRAKy#zR>pEGZOO@l0f-QZS-Th$<`eUTIX6~QpUrN=c29GB8#Y>gt|3DMjXq~E zADLOSD*2T)5s&8h9=u>I2AWeGn^ew3=f>YR9++3WuehhpW6oHm{%T$caY{Uh?(Lwr zZOv`wWfLzqOLFk4iX5;?J_0VG)|0V74mp{v-!wAW`p(K@-3MB+j8R}miDtnA~@<;=AUPH@bodw7!0_$}|D(g=GHgG-8`cqD6 zFKcB@Fk>JS)!%^)v^j(v@-b~9JH!=8ubS|ujbAx()&kZjSt0Q$jX+K73tOz<2D4sHTc_?mx?E`Y+;n4V+hiI z<}{}zC+k!w2{Y!#iH(V+_88{5Bi<5bb#wr z_H5YG3Y4>BS?tv;?tZX4%KI68?7xpOzYhuQYqNWQgmdiA**y#26Y)I}-xJ|gs_#c9 zd9X@7k^f^aUREyW9c;Vvz}~*g#jlj9yWn+R9rGTP@09pGy6=?Oy+zN??zP$~vGeW{ z_UMnEjqeqRFYJcJH>6AN$_AKKtX>2ER7=wPBUl20r08|0&5o_l)1;Rt9@Y OF#ZpUdH-#3AU^@t-@9V~ literal 0 HcmV?d00001 diff --git a/assets/solidworks/materials/zh-CN/SolidWorks DIN Materials.sldmat b/assets/solidworks/materials/zh-CN/SolidWorks DIN Materials.sldmat new file mode 100644 index 0000000000000000000000000000000000000000..d222ec63883d96b7a179fa8b1c4cc897e70c4bab GIT binary patch literal 792114 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∋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 I+l`$5KjpG3Hvj+t literal 0 HcmV?d00001 diff --git a/assets/solidworks/materials/zh-CN/shader_mapping.dat b/assets/solidworks/materials/zh-CN/shader_mapping.dat new file mode 100644 index 0000000..93cad00 --- /dev/null +++ b/assets/solidworks/materials/zh-CN/shader_mapping.dat @@ -0,0 +1,399 @@ +leather, Ƥ(PW) +sponge, (PW) +grass, (PW) +sand, ɳ (PW) +skin, Ƥ (PW) +heavyclouds, (PW) +lightclouds, (PW) +clearsky, (PW) +waterstill, ˮ (PW) +orientedstrandboard, Ť (OSB) (PW) +hardwoodfloor, Ӳľذ (PW) +hardwoodfloor2, Ӳľذ 2 (PW) +hardwoodfloor3, Ӳľذ 3 (PW) +laminatefloor, ״ذ (PW) +laminatefloor2, ״ذ 2 (PW) +laminatefloor3, ״ذ 3 (PW) +polishedash, ׹ľ (PW) +satinfinishash, ׹ľ (PW) +unfinishedash, ľ (PW) +polishedbeech, ׹ɽë (PW) +satinfinishbeech, ׹ɽë (PW) +unfinishedbeech, ɽë (PW) +polishedbirch, ׹ľ (PW) +satinfinishbirch, ׹ľ (PW) +unfinishedbirch, ľ (PW) +polishedcherry, ׹ӣľ (PW) +satinfinishcherry, ׹ӣľ (PW) +unfinishedcherry, ӣľ (PW) +polishedmahogany, ׹ľ (PW) +satinfinishmahogany, ׹ľ (PW) +unfinishedmahogany, ƺľ (PW) +polishedmaple, ׹ľ (PW) +satinfinishmaple, ׹ľ (PW) +unfinishedmaple, ľ (PW) +polishedoak, ׹ľ (PW) +satinfinishoak, ׹ľ (PW) +unfinishedoak, ľ (PW) +polishedpine, ׹ľ (PW) +satinfinishpine, ׹ľ (PW) +unfinishedpine, ľ (PW) +polishedrosewood, ׹ľ (PW) +satinfinishrosewood, ׹ľ (PW) +unfinishedrosewood, ľ (PW) +polishedsatinwood, ׹ľ (PW) +satinfinishsatinwood, ׹ľ (PW) +unfinishedsatinwood, ľ (PW) +polishedspruce, ׹ɼ (PW) +satinfinishspruce, ׹ɼ (PW) +unfinishedspruce, ɼ (PW) +polishedteak, ׹ľ (PW) +satinfinishteak, ׹ľ (PW) +unfinishedteak, ľ (PW) +polishedwalnut, ׹ľ (PW) +satinfinishwalnut, ׹ľ (PW) +unfinishedwalnut, ƺľ (PW) +redceramictile, ש (PW) +floortile7, ذש 7 (PW) +floortile6, ذש 6 (PW) +floortile5, ذש 5 (PW) +floortile4, ذש 4 (PW) +floortile3, ذש 3 (PW) +floortile2, ذש 2 (PW) +floortile1, ذש 1 (PW) +cobblestone4, ʯ 4 (PW) +cobblestone3, ʯ 3 (PW) +cobblestone2, ʯ 2 (PW) +cobblestone1, ʯ 1 (PW) +cobblestone, ʯ (PW) +limestone, ʯʯ (PW) +sandstone, ɰ (PW) +slate, ʯ (PW) +granite, (PW) +polishedpinkmarble, ׹ۺʯ (PW) +pinkmarble, ۺʯ (PW) +polishedgreenmarble, ׹ɫʯ (PW) +greenmarble, ɫʯ (PW) +polisheddarkmarble, ׹ɫʯ (PW) +darkmarble, ɫʯ (PW) +polishedbeigemarble, ׹ɫʯ (PW) +beigemarble, ɫʯ (PW) +polishedbluemarble, ׹ɫʯ (PW) +bluemarble, ɫʯ (PW) +flemishbrick, äӲש (PW) +firebrick, ͻש (PW) +weatheredbrick, 绯ʯ (PW) +oldenglishbrick2, ʽӢӲש 2 (PW) +oldenglishbrick, ʽӢӲש (PW) +bonechina, ʴ (PW) +ceramic, մ (PW) +stoneware, մ (PW) +earthenware, (PW) +porcelain, (PW) +pavingasphalt, · (PW) +pavingwetconcrete, ·ʪ (PW) +pavingdarkconcrete, ·ڻ (PW) +pavinglightconcrete, · (PW) +pavingredconcrete, · (PW) +pavingstone, ·ʯ (PW) +clothwhitecotton, ޲ (PW) +clothbeigecotton, ɫ޲ (PW) +clothbluecotton, ɫ޲ (PW) +clothburgundycotton, Ѿɫ޲ (PW) +clothgreycotton, ɫ޲ (PW) +clothcanvas, (PW) +clothburlap, 鲼 (PW) +carpetcolor5, ̺ɫ 5 (PW) +carpetcolor4, ̺ɫ 4 (PW) +carpetcolor3, ̺ɫ 3 (PW) +carpetcolor2, ̺ɫ 2 (PW) +carpetcolor1, ̺ɫ 1 (PW) +sandblastedglass, ɳ (PW) +frostedglass, ë (PW) +reflectivegreenglass, ̲ (PW) +reflectiveblueglas, (PW) +reflectiveclearglass, (PW) +mirror, (PW) +brownglass, ز (PW) +greenglass, ̲ (PW) +blueglass, (PW) +clearglass, ͸ (PW) +texturedrubber, ṹ (PW) +perforatedrubber, (PW) +tiretreadrubber, ̥ (PW) +glossyrubber, (PW) +matterubber, ޹ (PW) +darkpowdercoat, ɫ۲(PW) +aluminumpowdercoat, ۲ (PW) +blackspraypaint, ɫ (PW) +redspraypaint, ɫ (PW) +candyappleredcarpainthq, ʺƻɫ (PW) +steelgreycarpainthq, ʸֻɫ (PW) +sienacarpainthq, ɫ (PW) +glossredcarpainthq, ʹɫ (PW) +glossbluecarpainthq, ʹɫ (PW) +metallicgoldcarpainthq, ʽɫ (PW) +metallicwarmgreycarpainthq, ʽȻɫ (PW) +metalliccoolgreycarpainthq, ʽɫ (PW) +whitecarpaint, ɫ (PW) +blackcarpaint, ɫ (PW) +beigesatinfinishplastic, ɫ׹ (PW) +yellowsatinfinishplastic, ɫ׹ (PW) +greensatinfinishplastic, ɫ׹ (PW) +redsatinfinishplastic, ɫ׹ (PW) +bluesatinfinishplastic, ɫ׹ (PW) +blacksatinfinishplastic, ɫ׹ (PW) +darkgreysatinfinishplastic, ɫ׹ (PW) +lightgreysatinfinishplastic, ɫ׹ (PW) +creamsatinfinishplastic, ɫ׹ (PW) +whitesatinfinishplastic, ɫ׹ (PW) +beigelowglossplastic, ɫ͹ (PW) +yellowlowglossplastic, ɫ͹ (PW) +greenlowglossplastic, ɫ͹ (PW) +redlowglossplastic, ɫ͹ (PW) +bluelowglossplastic, ɫ͹ (PW) +blacklowglossplastic, ɫ͹ (PW) +darkgreylowglossplastic, ɫ͹ (PW) +lightgreylowglossplastic, ɫ͹ (PW) +creamlowglossplastic, ɫ͹ (PW) +whitelowglossplastic, ɫ͹ (PW) +beigemediumglossplastic, ɫй (PW) +yellowmediumglossplastic, ɫй (PW) +greenmediumglossplastic, ɫй (PW) +redmediumglossplastic, ɫй (PW) +bluemediumglossplastic, ɫй (PW) +blackmediumglossplastic, ɫй (PW) +darkgreymediumglossplastic, ɫй (PW) +lightgreymediumglossplastic, ɫй (PW) +creammediumglossplastic, ɫй (PW) +whitemediumglossplastic, ɫй (PW) +beigehighglossplastic, ɫ߹ (PW) +yellowhighglossplastic, ɫ߹ (PW) +greenhighglossplastic, ɫ߹ (PW) +redhighglossplastic, ɫ߹ (PW) +bluehighglossplastic, ɫ߹ (PW) +blackhighglossplastic, ɫ߹ (PW) +darkgreyhighglossplastic, ɫ߹ (PW) +lightgreyhighglossplastic, ɫ߹ (PW) +creamhighglossplastic, ɫ߹ (PW) +whitehighglossplastic, ɫ߹ (PW) +translucentplastic, ͸ (PW) +frostedplastic, ë (PW) +polypropyleneplastic, ۱ϩ (PW) +polycarbonateplastic, ̼ (PW) +largesparkerosionplasticblue, ɫʴ (PW) +sparkerosionplasticblue, ɫʴ (PW) +blueknurledplastic, (PW) +bluedimpledplastic, (PW) +bluetreadplateplastic, ̤ (PW) +carbonfiberaramidfabric, ̼ά᰷֯ (PW) +carbonfiberdesignfabric, ̼άװ֯ (PW) +carbonfiberdyneemaplain, ̼άͨ (PW) +carbonfiberinlayunidirectional, ̼ά (PW) +carbonfiberepoxy, ̼ά˹ (PW) +circularmeshplastic, Բ (PW) +diamondmeshplastic, (PW) +polishedsteel, ׹ (PW) +brushedsteel, Ϳˢ (PW) +satinfinishstainlesssteel, ׹ⲻ (PW) +mattesteel, ޹ (PW) +sandblastedsteel, ɳ (PW) +burnishedsteel, ĥ (PW) +caststainlesssteel, 첻 (PW) +castcarbonsteel, ̼ (PW) +plaingalvanized, ͨƸ (PW) +chainlinksteel, (PW) +machinedsteel, ӹ (PW) +stainlesssteeltreadplate, ̤ (PW) +stainlesssteelknurled, ֹ (PW) +wroughtstainlesssteel, Ʋ (PW) +chromiumplate, Ƹ (PW) +brushedchromium, Ϳˢ (PW) +satinfinishchrome, ׹ (PW) +mattechrome, ޹ (PW) +sandblastedchrome, ɳ (PW) +burnishedchrome, ĥ (PW) +chromiumplatecast, Ƹģ (PW) +polishedaluminum, ׹ (PW) +brushedaluminum, Ϳˢ (PW) +satinfinishaluminum, ׹ (PW) +mattealuminum, ޹ (PW) +sandblastedaluminum, ɳ (PW) +burnishedaluminum, ĥ (PW) +castaluminum, (PW) +blueanodizedaluminum, ɫ (PW) +aluminumtreadplate, ̤ (PW) +polishedbronze, ׹ͭ (PW) +brushedbronze, Ϳˢͭ (PW) +satinfinishbronze, ׹ͭ (PW) +mattebronze, ޹ͭ (PW) +sandblastedbronze, ɳͭ (PW) +burnishedbronze, ĥͭ (PW) +castbronze, ͭ (PW) +manganesebronze, ͭ (PW) +polishedbrass, ׹ͭ (PW) +brushedbrass, Ϳˢͭ (PW) +satinfinishbrass, ׹ͭ (PW) +mattebrass, ޹ͭ (PW) +sandblastedbrass, ɳͭ (PW) +burnishedbrass, ĥͭ (PW) +castbrass, ͭ (PW) +polishedcopper, ׹ͭ (PW) +brushedcopper, Ϳˢͭ (PW) +satinfinishcopper, ׹ͭ (PW) +mattecopper, ޹ͭ (PW) +sandblastedcopper, ɳͭ (PW) +burnishedcopper, ĥͭ (PW) +castcopper, ͭ (PW) +wroughtcopper, ƺͭ (PW) +polishednickel, ׹ (PW) +brushednickel, Ϳˢ (PW) +satinfinishnickel, ׹ (PW) +mattenickel, ޹ (PW) +sandblastednickel, ɳ (PW) +burnishednickel, ĥ (PW) +castnickel, (PW) +polishedzinc, ׹п (PW) +brushedzinc, Ϳˢп (PW) +satinfinishzinc, ׹п (PW) +sandblastedzinc, ɳп (PW) +burnishedzinc, ĥп (PW) +castzinc, п (PW) +brushedmagnesium, Ϳˢþ (PW) +mattemagnesium, ޹þ (PW) +satinfinishmagnesium, ׹þ (PW) +sandblastedmagnesium, ɳþ (PW) +burnishedmagnesium, ĥþ (PW) +castmagnesium, þ (PW) +matteiron, ޹ (PW) +sandblastediron, ɳ (PW) +castiron, (PW) +wroughtiron, (PW) +brushedtitanium, Ϳˢ (PW) +mattetitanium, ޹ (PW) +satinfinishtitanium, ׹ (PW) +sandblastedtitanium, ɳ (PW) +burnishedtitanium, ĥ (PW) +casttitanium, (PW) +brushedtungsten, Ϳˢ (PW) +matttetungsten, ޹ (PW) +satinfinishtungsten, ׹ (PW) +sandblastedtungsten, ɳ (PW) +burnishedtungsten, ĥ (PW) +casttungsten, (PW) +polishedgold, ׹ (PW) +mattegold, ޹ (PW) +satinfinishgold, ׹ (PW) +polishedsilver, ׹ (PW) +mattesilver, ޹ (PW) +satinfinishsilver, ׹ (PW) +polishedplatinum, ׹׽ (PW) +matteplatinum, ޹׽ (PW) +satinfinishplatinum, ׹׽ (PW) +mattelead, ޹Ǧ (PW) +sandblastedlead, ɳǦ (PW) +burnishedlead, ĥǦ (PW) +castlead, Ǧ (PW) +brushedgalvanized, Ϳˢ (PW) +mattegalvanized, ޹ (PW) +coarsegalvanized, (PW) +castgalvanized, (PW) +roughgalvanized, ֲڵ (PW) +shinygalvanized, (PW) +SpecCobblestone, ʯ (BGE) +SpecFloorTile, ذש (BGE) +TransFloorTile, ͸ذש (BGE) +Aluminum1345Alloy, 1345 Ͻ +Aluminum1350Alloy, 1350 Ͻ +Aluminum2014Alloy, 2014 Ͻ +Aluminum2018Alloy, 2018 Ͻ +Aluminum2024Alloy, 2024 Ͻ +Aluminum3003Alloy, 3003 Ͻ +Aluminum6061Alloy, 6061 Ͻ +Aluminum7079Alloy, 7079 Ͻ +AluminumBrushed, Ϳˢ +AluminumCircularBrushed, ԲͿˢ +AluminumSatin, +BrassPolished, ׹ͭ +BronzeAluminum, ͭ +BronzeCommercial, ҵͭ +BronzeManganese, ͭ +BronzeTinBearing, ͭ +CarbonFibre, ̼ά +ceramicporcelain, մɴ +ceramicporcelain2, մɴ2 +ChromePolished, ׹ +Cobalt, +CopperPolished, ׹ͭ +CopperWrought, ƺͭ +Glass, +GlassFibre, ά +GoldDull, +IronCast, +IronDuctile, +IronMalleableCast, ɶ +LeadPure, Ǧ +Molybdenum, +AlloyMagnesium, þϽ +AlloyMonel400, Monel 400 +NickelBrushed, Ϳˢ +NickelPolished, ׹ +PlasticAcrylic, ϩ +PlasticDimpled, +PlasticMT11010, MT11010 +PlasticMT11020, MT11020 +PlasticMT11030, MT11030 +PlasticMT11040, MT11040 +PlasticMT11050, MT11050 +PlasticMT11060, MT11060 +PlasticMT11070, MT11070 +PlasticMT11080, MT11080 +PlasticMT11090, MT11090 +PlasticMT11100, MT11100 +PlasticMT11110, MT11110 +PlasticMT11120, MT11120 +PlasticMT11130, MT11130 +PlasticMT11140, MT11140 +PlasticMT11150, MT11150 +PlasticMT11155, MT11155 +PlasticMT11200, MT11200 +PlasticMT11205, MT11205 +PlasticMT11215, MT11215 +PlasticMT11230, MT11230 +PlasticMT11235, MT11235 +PlasticMT11240, MT11240 +PlasticMT11245, MT11245 +PlasticMT11250, MT11250 +PlasticNylon, +PlasticShiny, +PlasticPerspex, л +PlasticPTFE, PTFE +PlasticPolycarbonate, ̼֬ +PlasticPolypropylene, ۱ϩ +PlasticPolystyrene, ۱ϩ +PlasticPVC, ϩ +RubberSoft, +Silicon, +SiliconDioxide, +silverpure, +StainlessSteel, +SteelAISI304, AISI 304 +SteelAISI1020, AISI 1020 +SteelCastCarbon, ̼ +SteelCastStainless, 첻 +SteelGalvanised, Ƹ +SteelPlainCarbon, ̼ͨ +SteelSatin, и +SteelWroughtStainless, 첻 +Titanium, +Tungsten, +Vanadium, +water, ˮ +WoodBeech, ɽëľ +WoodMahogany, ľ +WoodMaple, ľ +WoodOak, ľ +WoodPine, ľ +WoodRedCedar, ѩľ +WoodTeak, ľ +Zirconium, \ No newline at end of file diff --git a/assets/solidworks/materials/zh-CN/solidworks materials.sldmat b/assets/solidworks/materials/zh-CN/solidworks materials.sldmat new file mode 100644 index 0000000000000000000000000000000000000000..300d263e3ab31f6c943d629d3f308301302ea721 GIT binary patch literal 789074 zcmeFaU5s7VmFIWTNG5_IC1WHol6lFM@*yf7Qv57d$)%a0NXpjOrl_itCASDtWU*K* zvPf35iW1rO$nJ>;Zh%m`1(e`u2u#OLp!g{PX7Yg`5AFuWerUk0c0&(-XaW7wz&wn4 z8cyIg+D4T1JF7O&$317CUFYn3>)fh^3sx1Y?%n(BefHV=zy9mL*81fCn@#>rx{T(A^t33w+)DesBGrn*Gua*G@0Y{?b-ov|qgLFG+ig>GfpyuDbVjbNSyS z|D}xmMDn@hwDf!~SxDz=O@6AI{R8rT=BRuZ^!==))pQQl<(@K++=Ff_pt?=^A70lw z>(lA#J|fqsD|aC8os}!OBlm{SuvS@hTvzs6o`>xH`k6Hu3-@XxU30}J=U)1G_ar?X z@Y%x;pStaTmbIl%Z2f0vWwmAFSK`Qk>hAY9zp*?>_Q`smPo7FwiGJ42_q4o|d&j=>#=~b#XO`bz zlwD%2@cXmN?=3y?_V?y~>%vbK)|PHAK7HkHU-`W=e{yD!?qUOZx|Y%>X*D@3_m`1z zhuuEHwXiA<$=9c({qNoX?$^Hc#^TbCe)kXN-&^{dGqYEJQNqlI6fWe zbaFi1mstT*GT!6qnN~l>6S7yIZJ6Va*H$hKF8u22YfIJRbYo=A`NnxlcHJ}bocQkN zzPCSk=f|(Te}1^9<1(6K@{fH%Uq>4H`(op`e*N0F*N1yLChcauG~Kbq;&_g7_|obvYm zg>#QBKbRXH>tPw|;dCu>1w4_=?IHO-oxXg1&m~{<_PKiZGxLA(m9ww0>+!@LOi>)) zKPR8l-#nWSxO5p4<6!=XJPnAlK?p!FqblIw9QEwZv>ovx{|od(=vi1 z>AjBGgIh_QgPc-~U^?O$+z5_K+gv>eBJ3`M#p`#gVRXm)*92v59~aIF61*yVHajg= z{&3s6x2%apmVYN>Ta(XiY*}dMQVT%QEcPGPJ$pC5=Ux1Nko)}s`SfA=TX~BX8Im)Xv@ST$?2V(D^~ zYQq-Kk`pOzdqLI&v$!w${-&Ot*z?QMzn*1!E--Sq2(Y;RzRqUU`^1^X?B9~Pt-qJ> z7;DmUejc8e5%YgI1!ZLhf|G8fcX?4-vB|x9eYp5 zKo3*$w8gdw4oZvK!<6hJFiF^!aqok(F6jZ=BYqFgx@0$Go>;Rnt$b%)Zr^g!^RbC- z$*fDR9{y%Z?m^7w4R%8BGba;v9~OSt995y)Y`-evTpe$Jt-tjYg=1L^QqK`q0Jf9b zf*0j~H&e?7-%^Nu*tR{;yIGKvWkGUUaS9$wZLhvrkSFA6X7?B*f3*3xul%#OA6fWm z>NiunGxJ!&Nt@-kGnS)c^YfV5XB~946=C^YU%r02`6x4c)b-`JdBQByANUWC3-ByzP`5G*qU38)`P%P-8sJ5TU%uYrq6Sz9UvFJd z1G+5#sn`}Z9FwOMZrOL`?N9?Ad0(qtQ3JlkkW*Y&?sk~zYSeH@##3Bh)uS`iQ14|i z)R4yv?5N{G4fs*;RO55&KWcyj2Q~bkFMW8)@3U6VyJ3f&zz)T|S9iv6Phkus;dbNd zeOUMX_=qBWjA8hqFIs63M)=s;>l}1rld=|oIaXYA_N-I5#fx`PXe|f+C<<1j~ z6^$Mq9c@hQrt?6eFUEg&JblIUmTP>F`s3+gCVCG}eTuJ>vjA^@Oa(9&oq9M&r7^~W zao%{8L_E*<UB$DQewS-|qW-nRzOHKD!3z!BEuY=Uj>5M}L1;z7ji6{6{`wWF_@=EX(iL zB}%0DPJWX`N1scXe) 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�X+U=gbfg9t-%npZ8~Gt=|k7P_N=mVCG{ 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%0DL1yrRW{jTgP@k;XKtPx_iMC(CJDJi2J z6yYOIPZW+di1wq7g!zUqpibr{C1qJ(7ZYdgqifNS$UB&pu|l2MPe&Ol$}zdzgvQCZ z6>F5#!F|wIO3KWjqN&;u3bz%}WW;E2#?FYDgOrqsvMb&!E$gr!vd!!=(+&rvqYG-y z?FE#v{;X~!9Lmfx_FL8~C1p4bKV=VvsF68VIi4UKrZ2Mh=_rF)(G#E%Rx>MzSw?Fx z%k)bg13y#JMIUP>RXsuE8gv1I5wFqL(w`~iT38HBj%8rP$eQ!Fuv$npoK6|JhaQxW zChgLcUC^p!yl7P93dv5%Y3XBTm{Brb$rg%V_hND z%r%txN@<%VTdZcR1(cC3{*qMFA1EX3Xti`>W)9KJ%r_Vj>?3_4Zu5V*05*c(VU$OW zACdHOP3SmI%YY*wL=cnWK*$Ai#c3Hr6-WuZL16oU*4sn!m>XJB=8BbpW_K$@ zT1mJzj*e`%Fe-jznGS0n*_}jwT*kQ$9SZ8HQ$eJP^@szZ3ka5Q|_Z#gZ|C4pb zOvZ6fnMN6o zPgV2OPf0NzW{V!hQBsr(YYm+$_ZW%R8oD-)61-yGH4{}z%#aWqIyjCJbXZTU&;Bt9 z^I}hk1;kRK6=}eJN=Tm5e=#v~q?}%31<^Zol$by1iI|lOB~T5mA?N*+B;UL*kEN|9 zmlr}iszT_e1bs9FyUIXctM>k7`^i23)lo9*4?YpZ>#+s!EnDr;WkXuVM~_xFz5ES)uJLz2)+*K8K%Z8oH(`R{|L z?|Al8&%Jk(`Ft^x(HCz0*WsbE#QALM9y>+gNu&9+m+DpP#Z2#yS6*5A{)K+3P{SY0 zCAT^3gu1}hAfna{MS@-0<;jxNv7RA^pT)zcS<0TtKPAo~u-I?PbUMP-gGn~s_{ zWP)}(Y4Te{P`p#cSgW_}!{*n8I=sN|4l_{S;52!M8Dw>*{T2*tcZgk+g-T&nwg1S; zSqSY(wyFEgXEPIVv#$L`Zd8FrArDeGOfjKxo%G>d?Dx}I5u{55#8_*@GAx!8KK?dC|?9T{hRpGRQe8}U(g zLCLNwl2Wf9&<*F!?_IYU8QzbN^K*$)YhMAg-Rw#rGr**PM{6*FXOw+XHLi$c7M48*Xb>yD>mU1L9 zt&=CCvPZRi>bq)Ik=EE8u4*#jGZizm4wsN5RU*f6M%7txpS&WilY7S2(|#=9>7jDW z{iY_`)b+_)MP}J+p;Xq-9w&{*KX0 zw5<9)`bG95+)b#>D)g@36H<39Ao|tJ)9$5LscJVik~m zG;Y{_gb-^?FVL=72~a^^gV-E%uB-~#%ezEPDa8-^6n z7c?PjJslqyIelP8Bx~XW`j-Ad6F&<-j@i)uzqAqwMP?nG1()4ipog#`%n2(4{l}`o z&M4lHiVtvGc$%3g$^*Kbcc8}(IOYL~5ywDl>H2_ogYU{rl>5Mcm?7w@j$2+Gyhz&z zjTJi}EaT<^ddMi63yUGSmB)(zkdhCq2S&!)EAs)_;9ac@tcm&wN=T`pkatGKN)%qq z`5*~_8?u~eYa)2~tSR{beOhEj;C8+)v`$z9NFx}D^#iYj{L{^a{B6m+`omcDHy#mO zfil(vdZp|CXZ^RElri;*>u=Bg<};UXZm}_ z#_PIjM!bxftP+m>7iY6vmGlkocU2y%CRZ1YM@<=!J(g=-vK(JtWm2uw?5aH6zLndz z;!3O9-G$rpehegkZ=V!h!{1V3(*{S7ZWj5m4spzw5|4O8DEKSB8qYS*Is3i zqT(K)YvNYmC-6l3*72BV6T~G9Eo?^2g)!hgv~L}JiI+}0AWRS^$PZL1Zy~1dv<|*h z6b4j9>+l7H3-*oK_fG5ha*ER8XQ_202k8W_w&#pawGrl}$F6nqGuX7a$h-6b^9lD| z>mYsMYtUp|-{Cv$3*o~V#==Z!UgKJqPr{0Ivx>E69IW9OnSnFRDRC=!Exs$3q&Q>D zTBUX25=Cp&I)0e`=2aYLhz{bZNq50_v?O!EsAA-uzAM_sPpvET41OeDJic{I{)6_> zG4KS={Y zMm zFJ!H;($J~t#*IEfjaxS2v3@z3R`yu>|W>G)1BAEp=KDY;o;qTBjIBN6>Kgv<5@zfeKclau3 z3BKS7Qp8N5uko3fN6jLVgieX^A!!rI#K*%%L5;;5G!|1IW39ZZ0$xn)p zg&{X^2H62MOXteQfLxlgHiLRjED-I1wnmz1nLf+=iiruRt_Y2xs245sm%IuzLfbxr zpwO+%9+hqM9i0TnW8!Lx+94ZgaEich7+{LyeYyc6idI`w~T$jlF_PKmi(o07KNTR00+7}^qNV=s>yDbhyJji7TD#XyVC|-CU~Z|$Ikqu?qtJiP|lkrDxMEE z%_jgUx=w`OXA^ISEHt(qEP=fT_b87DCRBb14+(sTPX|Wg-MGvPh#!mrUIv+i4?#+x zMQ}!*Tf@@;pEdJ%5Q>&}K{QRVOvP}Ew1`XKkurV|cN{JGogj8_1EVa`0+S4=(^ zN<>GPA=MH0Q$i+)|Hfr7Rs~JcULv4`1!S&>_w-X@{)+L*wi=eGD=nctiMq=260H$U>@M@< z^lbB(>iqEO$i2&wIxEDSphd|NDjNtI!P1FqyhcLOW=FAdAS`T;v^77`wM2TDH8co& zOqfkoYB7gtGo?Zn$R+ZEWkHK*1*__eabszS8Nmr5NyYQiWfhcnXI-;8B%Qp1BV?^* z>xfRdII=#$&y-mfW*Ta&8|H>WRKxl%6=f9!Yx8%MSc)ZMRHh4$SJs2kpp;=GTWmFv-p5rDD#7rf`lmNq{<3< zM$eJzbo^kA(kePsYo^E#Y`W;e570l>P<9?UN!brtMFOE%Zo3p&XoS96eGDJ6%8EHKP-5D9eZ5N85sH`N?`gV(52DZXf}g z^Zr>ukD#xZ9p*&{Q635$N!t(jK~Jz8Wo}5HA&<-${7{C0b%nl3*$>vFe8&D&OFyJ( z$tc4QtP%2w#YtP6q8Z12@v5I6(jZ7GdW^LNcjR5C;|HTtg$y##-w#%kEUWs#Dn?#X za{~+k8e~P(^@0_`%qhDC4iW-l#iZkg!=^~7bT+FO1cJ@Sen@uV7*-DMu&OjaVf`32 zhTW_TzT`*|JK-fox8NJAS#u`~#QJkzF%cDJK_0qL5N$#WZALr~=}?prtrFucK?%Kx z3~08|R*sTRM?*_M83cr`XzMRYwm>jilk(CWE%+(S%jjaF56}U}Yi*$ks$&9M2vfkt zxDlZ-_+l0Jk4SM#`GIkCkT7ITn5ch5^nmzeTr>*aK?f}@wAoKdzKe88+?beq`mMef zDIpCY7y80H#?jGuoS#vogM@>R6mJ!xkCAVrK;Nv~eoCw)dAl(y32cRaV76spiU9#@;L$P$QZmd(RJ$6sM7h;8T zXa;m0xV4#c!&*C@-;DZaPRAOYT390Gl%j zbs*iXOY3$Ly-(7)nXr79Qg`c;%g}c>HL`}g8y;@Q8y>)2>|Agj3%`AOAjO?aSX^*r zPBVmlJL{>eKY5hV-DSA{s=N6(-0sPf?atv@HMG^$KX&zxiW3qYBhu8J!&AMjS@qK0 zZA;T!|L9_{{_4Cvb~{)6ZNkb|I13zNe`QH;kLpXJnTjANhD1c2G0LMS-bD6;cxp4& z38@z({sJZ1A|FgOf#RsDgCmX>M?pCXdP@{c^$K7oXc52gSIdTsf&LO{agkESz*yiP zv8A~F6FDb#OcV<3PU}Qo-R_{62pDln;_%eW(f_=C)jKMZ+`j*cNRc5TH>KE<>P0l) zr?Gry%R)~?f_K_v@2J5(e`)Ds|VWgd0q}r$FtY%S+Yo8vndYR|E zebA=nCB_2Kf94YDWJdG$MUQ!piB!`*T8DTz8X<3=j3b<5RB`-4e$d6NS9B@!p!_rY z%axPSUSFRi4I2p`7E8!mF7AUF&(3XSBFI6pTCs_Iu{#tUgwE5c50Y4B9jS_$6Koo` zUfMy`q{@Nil%>0oXY^P5`G*#mQ`tmt46j`M8&v^L9z-jqa$2+W!aHOUeaXyKDS?Bq zm+`rC)oUqF$(&Rvfx4JO{ueh>Lbc2UXrZ4HymcA5_LRUXtXS+8{HvQM<@`YMG4mu! zXnww!i|Uw^Nfq*l#gD8a*?DDw%ku;cIE(Izu|$lCUJGFh@l`1aB}j|J%@h0*E@+~} z{2_U<^Mo|uo4_GxRmUWZAPI~c6DUC|mwxwC!UhX*#?BLUDDoBSB|@^mcw?|R5`m^c z2MC=oXF?7_akLMDmM;*;JGL?xR4mkp*c~WE{T1KUQa|ieu>mJVDSrnus_8 zbg-=KGKHRWp(HV7Wv%Q6Bh;FLI%u3V4OUZy3>Fc&Ko-+dmlPu1ifL$9k?Lz5b=DsJ zp@(Uy3wg5our`dHG0KL5e~}6P$I5~CDQl}`jj=8GC}rFffd#x4A%%BzMS2~%4 zlj69>4Q;GBe8>I}-9sY5=V%7_kditp3r#~Gpd^WR_z#&!<1r(W!nD-U?TSLdbw^!u z%-j>ZVpcSE^nALp3u{Qr@>A5&$gD7|G#UpxD(--WQ&LBlFr$1q>io^jGaEuf$Qw3@ zHJ6gQMy9IboI3M?heYi@PoV9<4E#(su2 zO3Ji{{?fV=WvIYwG%$0{ig2hX?YQBm;sW_vS5!)UK+hq8l6J+^QsD|&HBcYAL_Q39 zAN!)*5%Ug)W%jW#@I;nPO|%ZD(CgjWils4gJv7)O0m9 zu41_yJDBd88f+?lqCbCiJJhv+K@{P@_Ji3#z^c~ZHTVRC4sH-q1f$19nBf~<510pR z3{76aQDP#BL*SL*5$IVkobrkCi(+|xz_RUH%lFsnh{SzGB-#si6PzG#(_RwMpu+&n=^ zSz|`UTI;7I&kQfb7j+&qPf!xiikQn9)U#mSO)d(pT zL3?$Sq%X9l<0#Qr<^uiJPl;8;Izwh+ax0998B*?$S5=8%ok=q?Q*mR0cjz|7Tly(E zub5uMQGzeZ($q;g8baT3Vy?#JCfdbAV>m*~LS^W8Rkoq4&@6B$uFtFx@H%>(KBN8NH&`*QW!54+rcGLg zFYtqv6W1~r0$oB+X&ITLedMow%c^NZg5d}NE-e&GA?;!{y=T`rYW<<;B_Pr9SeeB>~H{0 zF1;%*DQCnp{Fmob@`CoD4oAdsUNe3W2ii$r%nDYC!ME=0GK3YKj5#{1N=sNA`|qDey}dl zB>ZH(N{Z37NNU=CfW;LhV7#CsAv^k^Rg6|cZ=kWT;=(#9wFYv}TnHmz-H{P=s8%fW z;Sx{L4`f=hnlkf`Wn$XclRG>T)5T2>cQ5^ z(xT0fVV&Q}GxQY6N!bsyH+0Yx^5&6N{!%~alOzHumBg}w;7huG&<8k-T*#h4lfUHC zFi-GIRu)@>gzyd;BxOIqJzA^kfowfom*0l`fJm_`tSLz}mN@0AW!y-oc!J(!toTUU z?MM$Kx%ii?YUndl%wjrzzzuX0dXpJKK6wu<0zZTpvGHhiRT42v@Fykq(JTn-&^Gkp zg|I17!T(q-(jLNQl9!ZPU8@$#l2qnI8UqYQKhPHZmvzfJ;R)5!gv#l#2%6_ppYcYU z3s(CvpVfXW_s&@r$wGEN)UwrsTq@-D_jsQxBlRsyhnA4dVqL(X%AA+JzQsOMJ;zOm zcE{GQKhGT?e$MUQM-u_r<~>xCMb^-5oPLQkz)HBIqJ zJk{b}!BykZiuHkK7JFyyztrQ*Gs@VHF>DpsYT*=lMlCqG^CG$ShJJLH~XDU*)bT1d*blv3dY@vl>yThLzM;vO#D&*EO`CGS9&f9Cwo?7T#%X&qvqVu`<&GQeNq@J@c zPQqHup2AvHpPIAQSQ#&5`bI0bxy?D_Oa0A#!Vqd5>R~6>G|yDsJd|~pbV2jYd5>Ae z-9hWgmfr8Zac9O6RxdSm;zk|M){PVMA3V{|uloKDum5vN+21>EeLS0Kqt$EC1ft!m$h9zFT-rQN6NN0rM8_`RwL z>S&Jw+h{!1>Z`IXaki+6JLL6rxTC`z^LB=ywD^YwckDNxYhUjX(?#;^s@PfUYBF&L zdYh=1`$8A`aYz5tO}GPpxY%wJ?kMAvCT&;5X*vEI4tHEOpXtzUU9GAWh@89& z@5Xtf%+{)XoweT^_D#!&mfl+Y^w++9(aS z^G1Ec`mn`TClQDHdUx7i-@ftYOJ9C*Vexxk{np%Di$9s&bN(auxc|@eHcpX2oaudZ ziS~H)!^J~oHv3-%6Cle_I~=erBPiMA1oShe>*V|#Tj*=X^L z+~=jsJ#&rEf)B5w7Rl&xd&G3dI zy{l$v6pzMM^53!_jl}`?kv-tQFIdaOhu1UP`lKPihfObC@J8&KM+1+lUSxT^gJRpi z_ex!Cn=A&s9XFaF#(3HId?AZI5px_RmH;i}v~nCF$#6J=_sDNsrkc`jTY%1peIqUyH!xoQQjAFmNuG65S!vjddc^i`=^rOH8RSI>up!&?{utAlQ z&mQ~nfmT5*K6s+Se_wyn;e#p{#)A`7{UwB5C*{(y9Zu+Q0+JwUXpa+=`LAD3(}5GH zt)SAD`i4ow3GXl0;{TQu(uJHs`X%T1Fd1 zz~hD@Q6bUGBMG0kc+bUK=V0A!aRHv>85b{DH$-s0ciKLCuf3WzOh8=+l}O|f z_WE9nDV?xZp102~*>gwiIhXGvQme{-*Am%{awdctDDoE6QeLw-hN>uuZ4oPY%qV^C zR(sTx138{G>wYpH7#)ovXEDN-B-`cV5md+$dc|MSf=Z~k!ct|hIu zSOjt~CnfLWv8f*y<`$L9{jP(>I1Y-;o(}j71HbQM~6RB;E(h*0+P?>n19~u z@nkUAlf`Za>i;G$f1%87e>H^A()jZilZ8LRutgbBP%EKbtfNE}U=C1Bjy5hB?zxuL zcdTWI#O1xm8wGJx)1wd9avlC~{~KMd#8L2BeLn_y=geb=)6`KM%J9IV>62z8lMaEn zk&g#`s6ugn7yYo+%$dDp#mRto6^hN3U+tqG)TvE(^g}iJ>}H%q^znaYb!GkNLp%P) zk3J@$dg6@vd+X-EFJ?8E)IMG|tA5(z7y0{nO~qk*>Y7DHsAQxTgGj`9E&hg49!NPtIgH z{&M!y)nZnkFIS+l7gkt@9m{K3b^t70S8w`^J^OboDwFSzt!nB}nokGQWVdWQy%WLo zP9&h`_W2IO*QYbiy4}C+_zK;pEq4=2_ge1@HrjZa$A_SKH33cbH;0dsXJ>l(j^}o~ zJunn!nS~TI*)1MAo2*VrQS4avT0QZh7JA|R)puX}?)9I&yt>$ryuvhtD2I<3?U0IU zxI{6x@l>5f4xQgEUwiMScF(tyVD`+1(sG*K?W1o?aoCyEuKl*uhMmC}YWsqD`TYy; zueMzeI)mW2okKG0%*~vB=ZH=vXva-=`aw8l)?yl}$IU|@cDy&{?tC@Hc*7|(-Y>tH ziY}*2gpN5saUy1J+uN2HK0je|%h_(BV`V?^|8wR3-=?aDoc_JG%eU=n5^dEA>4Y9X z-mTJRhjAQjS5-9_YgcJ+kEZQ>o8f8ih^DRmU2S-Ne>82k6K?o%KM+Y#uV*4$I2SJ^xD#nl{HRhYC~J6#K-A1@aZ_It%X3cJZc96s=Q?$Ec%90)W7imG(6$*j71goh zrt+%|?G&=c*|TkEKhV1?is$>y@8RCeP}=|Y#lL*(RJ(S#c%SX2e$;kS&kaHIcV7PJ z(HK1Zh}Bo`%SL%1c$~{;|M}Vf{Wm#ruH87FuyJzp^L~pLoi^H`{Jra?-YmFu*D20M zOhNmhA!xszn0C0Qch^h5l$dt7uXoo=fBIBfp6?%m=Wiq)_aj5lzIZrx+=?bXI)t~0 zgui>_og?uv%tsBuhp(&GlTi&vfk<_#v4&$2vTr34=L>VFl)HJ4>4=m@nLv-Ugks0%65Gi-kz8H5VOu*ABMN*z}%G=cV=$>8G-)kWjzsnC<9Xu`f={y=b}hyRBZ4vqLx&WySt_B&%fBIpu$__{(32 zjR4WRaQ|cY-n||bpW0^`g#Gq4OvUD3URYiH8(;mwTud}4ryCwgzk2w_v6OVlV$7Mu zhI^=@zPgANYn#I;j@D&iZQyb{ii%r{cm-js@hq%J*=0^hudwQ9Jhe62URzrxD!nNe=ktIy8r+H literal 0 HcmV?d00001 diff --git a/assets/solidworks/materials/zh-CN/sustainability extras.sldmat b/assets/solidworks/materials/zh-CN/sustainability extras.sldmat new file mode 100644 index 0000000000000000000000000000000000000000..75bc47133a06712577193972d495ce13dab00158 GIT binary patch literal 13470 zcmeHOL2nyX5S{}d711l#T0OzRPU19G6_ub?0fcD5ty?8kqezb9Y_>QXE4$7*LTbtl z2_bqz`2+lh-g+uOfIHV-IUs@5L(BKg^Jd?!Et6n1%3);Lb#`ZGzWLtF_ujsB-@Lvq zpUOaXq$?HKmyYa7ETQa5B%jD-nUk8#;@=9+MAF9bO`MG+(6jgDo_sD?S_D>Z7A}5x!qWoF%y^@5^;Wqh*B8MSO4JJL86WRg72;Pj)`DC7x~ACep)oGkB%? zs=?1L;)RHt;@h5liz78fy928j=QhX0VOHfxM9v%-s}D~RBE|5u4TMA*BikKBYUA84 z{@Rr-*jIjxlkjF&nA_5pl2Qd(AUBLEEt}VRkdR#U~)l;sH=*T4vw7{$dgVLh=< zSaYc&Bh%B3^LU?(KU&?6+iP0m^T6op^8zZgt)tH?SdXkMqRNl5Co*f=_mBn0BGHH9=-;m6kV6ca-&PMJ*`<+v1|CtaYNK?SySHwd{Rd` zz`2GflVjH4qYehio2!y?4&^M{&@f)FOJbYj1Fhl7}vIjqu0hba*SrR!+q;zc-z9O4|~^r~X)66a^gg(Ky!BZzN)mciXNh~ z>zrP8!8%tD(woxTlU=)GnC{8$)ib_h;2qrLC%c)_Kl$!{)0CdS6fmVXr8lKFrJqje&w5S& zHui?zZNB)S5gc_+vp@7QSr4B*J`8@}xBVfTOYAv=J!i1z4AcD{l)nYde{uWve=Bsd NnS@r8{~yqj{0l`QXf^-< literal 0 HcmV?d00001 diff --git a/bearing_bracket.stl b/bearing_bracket.stl new file mode 100644 index 0000000000000000000000000000000000000000..ad71fc1e61b5a3482b0f04422aa60994de656042 GIT binary patch literal 2171484 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