# 建模与反馈小闭环

目标不是把一串命令发送给 SOLIDWORKS 就结束，而是完成：

**定位目标 → 执行动作 → 重建 → 读取状态/几何量 → 获取图像 → 根据结果判断下一步。**

## 完整可运行示例

[打开 closed-loop.aardio](closed-loop.html)。它只创建新零件，不使用已有活动模型；默认输出到当前用户的本地应用数据目录下，每次运行使用新目录。

也可以指定模板与输出目录调用：

```aardio
var result = loadcodex("~/docs/library/com/sldWorks/closed-loop.aardio",{
    version=2025;
    template="%ProgramData%\SOLIDWORKS\SOLIDWORKS 2025\templates\gb_part.prtdot";
    outputDir=io.fullpath("/output/sw-loop");
});
print(result);
```

示例模板路径适用于本次验证的中文 SOLIDWORKS 2025 安装。其他安装请显式提供自己的空白零件模板。指定固定输出目录时，后续运行可能覆盖该目录中的示例文件。

示例执行过程：

1. 只连接运行中的 2025 实例。
2. 从显式模板创建空白零件。
3. 按 `GetTypeName2()=="RefPlane"` 取得第一个基准面，不依赖中文/英文名称。
4. 创建 100 × 60 mm 矩形草图，拉伸 20 mm。
5. 重建并确认体积为 **0.00012 m³**（120000 mm³）。
6. 导出 `before.png`。
7. 将自己命名的特征 `AA_Block` 的深度尺寸从 20 改为 40 mm。
8. 再次重建，确认体积为 **0.00024 m³**、实体数为 1，并检查有限特征扫描结果。
9. 以唯一文件名保存 `block-时间标记.SLDPRT`，导出 `block.step`、`after.png`、`feedback.json`。原生文件的基名也保持唯一，避免先前示例尚未关闭时的同名冲突。
10. 保留已保存模型供查看。失败会停止，可能留下用于诊断的未保存测试文档；不会自动回滚。

示例的修改后模型视图（通过本库 `CaptureView` 导出）

## 第二个示例：带孔零件（推荐的助手函数写法）

[打开 plate-with-hole.aardio](plate-with-hole.html)。它演示本库封装的建模助手，在 SOLIDWORKS 2025 实测通过：

1. `NewSketch(doc,plane,draw,"AA_Profile")`：选平面、进入草图、执行 `draw` 回调、退出草图，再用 `ISldWorks::IsSame` 找回对应的草图特征并命名。回调出错或返回 `false` 时也会退出草图。
2. 在回调内为矩形加两个尺寸并让角点与原点重合，`GetSketchInfo` 报告 `fullyDefined=true`。
3. `Extrude(doc,"AA_Profile",{depth=0.020;name="AA_Block"})` 创建凸台。
4. `GetFaces(doc)` 按 `normal=[0,0,1]` 找到顶面，用其 `point` 作为 `NewSketch` 的目标画圆。
5. `Extrude(doc,"AA_HoleProfile",{cut=true;endCondition="throughAll";name="AA_Hole"})` 切出通孔，体积等于长方体减圆柱。
6. `GetMaterials()` 读取本地化材料名后 `SetMaterial`，质量 = 体积 × 密度。
7. `SetCustomProperty`、`GetDimensions("AA_Block")`、`SetDimension("D1@AA_Block",0.030)`，再用体积验证。
8. `CaptureView(path,doc,960,720,"isometric")` 出图并保存。

```aardio
var result = loadcodex("~/docs/library/com/sldWorks/plate-with-hole.aardio",{
    version=2025;
    template="%ProgramData%\SOLIDWORKS\SOLIDWORKS 2025\templates\gb_part.prtdot";
    outputDir=io.fullpath("/output/sw-plate");
    // material="1060 合金"; // 省略时选择名称以 1060 开头的材料
});
print(result.volumes,result.change);
```

### 草图尺寸与关系

`AddDimension2` 在 **“输入尺寸值”选项（swInputDimValOnCreate，编号 10）为 true** 时会弹出对话框并阻塞自动化。示例在添加尺寸前临时关闭并在之后恢复该选项：

```aardio
import com.sldWorks;
var sw,err = com.sldWorks(false);
if(!sw){ print(err); return; }
var doc,findError = sw.FindDoc();
if(!doc){ print(findError); return; }

var profile,sketchError = sw.NewSketch(doc,"前视基准面",function(sketchManager,doc){
    var segments = sketchManager.CreateCornerRectangle(0,0,0,0.05,0.03,0);
    var previous = sw.GetUserPreferenceToggle(10/*_swInputDimValOnCreate*/);
    sw.SetUserPreferenceToggle(10,false);
    doc.ClearSelection2(true);
    segments[1].Select4(false,com.nothing());              // 草图线段用 IEntity::Select4
    var display = doc.AddDimension2(0.025,-0.01,0);         // 文本位置，米
    display.GetDimension2(0).SetSystemValue3(0.05,1,com.nothing());
    sw.SetUserPreferenceToggle(10,previous);
    doc.ClearSelection2(true);
    segments[1].GetStartPoint2().Select4(false,com.nothing());
    sw.SelectByPoint(doc,"EXTSKETCHPOINT",0,0,0,true);     // 原点按类型+坐标选择，不依赖名称
    doc.SketchAddConstraints("sgCOINCIDENT");
},"AA_Demo");
if(!profile){ print(sketchError); return; }
print(sw.GetSketchInfo(doc,profile));
```

上例用本地化名称 `"前视基准面"` 只为演示 `NewSketch` 接受特征名；跨语言脚本应像完整示例那样按 `GetTypeName2()=="RefPlane"` 取基准面对象。`SketchAddConstraints` 的关系名见官方 `sgCOINCIDENT`、`sgHORIZONTAL`、`sgVERTICAL`、`sgFIXED` 等。

### Extrude 选项

| 选项 | 含义 |
| --- | --- |
| `depth` | 深度，米；`blind`/`midPlane` 必填正数 |
| `cut` | `true` 调用 `FeatureCut4` 切除，否则 `FeatureExtrusion3` 凸台 |
| `endCondition` | `blind`（默认）、`throughAll`、`throughAllBoth`、`midPlane`、`upToNext` 或 `swEndConditions_e` 数值 |
| `reverse` | 反向（原生 `Dir`） |
| `flipSide` | 仅切除：切除轮廓外侧 |
| `draft` / `draftInward` | 拔模角（弧度）与方向 |
| `merge` | 凸台是否合并实体，默认 `true` |
| `name` | 创建后重命名特征 |

返回 `特征,错误码,isWarning`。特征范围固定为“自动选择所有实体”，凸台/切除默认方向沿草图法向的原生规则不变；需要更复杂的终止条件（到面、到顶点、偏移）时直接调用原生接口。

## 单位与选择是关键前置条件

建模 API 的长度使用米，角度使用弧度，不跟随界面显示的 mm 自动转换：

```aardio
var width = 100 / 1000;       // 100 mm → 0.1 m
var angle = 30 * math.pi / 180; // 30° → 弧度
```

每个阶段显式处理选择：选择基准面后进入草图；退出草图后清空旧选择，再选择草图特征进行拉伸。**选择函数返回 false 是操作失败，不是可以忽略的通知。**

`InsertSketch(true)` 是进入/退出的切换，不是“确保进入”的幂等命令。中断后先读取 `GetDocInfo().editingSketch`，不要盲目再次调用。`NewSketch` 在文档已处于草图编辑状态时直接拒绝执行。

选择面时优先用坐标而不是名称：`GetFaces(doc)` 返回每个面的 `normal`、`area`、`box` 以及面上实际点 `point`，`SelectByPoint(doc,"FACE",x,y,z)` 或 `NewSketch(doc,{x=,y=,z=},...)` 直接使用该点。实测坐标选择与草图进入都不要求文档处于活动状态。

## ISketch 与 IFeature 不要混用

`SketchManager.ActiveSketch` 是草图接口，不提供 `GetFeature()`。`NewSketch` 在退出草图后用 `sw.IsSame(feature.GetSpecificFeature2(),sketch)==1` 找回草图特征；第一个示例则简单地取最后一个 `ProfileFeature`。

对已有复杂模型，不能把“最后一个草图”普遍当成目标。应记录自己创建的特征名称，或用原生 API 提供的更稳定引用方式；重建或关闭重开之后重新定位对象。

## 拉伸参数对照

完整示例调用 `FeatureExtrusion3`，其 23 个参数按以下顺序分组：

| 参数组 | 含义 / 示例选择 |
| --- | --- |
| `Sd,Flip,Dir` | 单向、不翻转、不反向 |
| `T1,T2` | 两个方向的终止条件；示例为给定深度 0 |
| `D1,D2` | 两个方向的深度，米 |
| `Dchk1,Dchk2,Ddir1,Ddir2,Dang1,Dang2` | 拔模开关、方向和角度；示例关闭 |
| `OffsetReverse1,OffsetReverse2,TranslateSurface1,TranslateSurface2` | 参考面偏移相关；示例关闭 |
| `Merge,UseFeatScope,UseAutoSelect` | 合并与特征影响范围 |
| `T0,StartOffset,FlipStartOffset` | 起始条件；示例从草图平面开始 |

不要把全部参数的长串布尔值当成适用于任意拉伸的模板。更改终止条件、参考面、特征范围或多实体行为前，应查对应版本文档并增加验证。

## 修改后的独立验证

`SetDimension(doc,"D1@AA_Block",0.03)` 写入、重建并读回，返回 `{before,after,status,statusName,rebuilt,scope}`；状态码非 0（例如从动尺寸 3、无效值 2）返回 `null,说明,状态码`。不知道尺寸名时先用 `GetDimensions(doc,"AA_Block")` 列出。修改后仍应读取独立的几何量，而不是仅确认赋值语句执行完毕：

```aardio
import com.sldWorks;
var sw,err = com.sldWorks(false);
if(!sw){ print(err); return; }
var doc,findError = sw.FindDoc();
if(!doc){ print(findError); return; }

// 本段仅用于已经运行完整示例创建的模型。
var depth = doc.Parameter("D1@AA_Block");
if(!depth){ print("没有目标尺寸，不修改其他模型"); return; }
var value = depth.SystemValue;
print("当前深度 mm：",value*1000);
var mass,massError = sw.GetMassProperties(doc);
if(!mass){ print(massError); return; }
print("当前体积 mm³：",mass.volume*1000000000);
```

体积、包围盒与截图分别回答不同问题。简单长方体可通过公式验证体积；复杂模型应选择与任务对应的不变量，例如实体数、孔径、材料去除量、组件数、配置或选定面的几何。近似包围盒不是精密测量工具。

## 中断和失败时

- 先保留原生返回码、目标文档路径和当前编辑状态。
- 检查特征创建是否返回对象，是否处于草图/特征编辑模式，选择是否正确。
- 不要因为上一次调用超时就直接重试创建特征：上一次可能已经生效。
- 本库不实现自动撤销事务。重要模型应先复制或另存，错误后重新读取模型再决定恢复方式。
- 对话框和命令管理器不包含在模型视图 PNG 中。需要诊断 UI 时应另外检查主窗口，而不是误判模型截图。

## 官方接口

- [FeatureExtrusion3](https://help.solidworks.com/2025/english/api/sldworksapi/SolidWorks.Interop.sldworks~SolidWorks.Interop.sldworks.IFeatureManager~FeatureExtrusion3.html)
- [FeatureCut4](https://help.solidworks.com/2025/english/api/sldworksapi/SolidWorks.Interop.sldworks~SolidWorks.Interop.sldworks.IFeatureManager~FeatureCut4.html)
- [AddDimension2](https://help.solidworks.com/2025/english/api/sldworksapi/SolidWorks.Interop.sldworks~SolidWorks.Interop.sldworks.IModelDoc2~AddDimension2.html)
- [ISketch 成员](https://help.solidworks.com/2025/english/api/sldworksapi/SolidWorks.Interop.sldworks~SolidWorks.Interop.sldworks.ISketch_members.html)

[模型反馈与图像](feedback.html) · [返回首页](index.html)
