我怀疑就是ai把AutoSolve关掉了导致的
这次能跑通,基本是几类问题一起被对上了,可以分开看:
-
草图里不该 EditRebuild3
在还在编辑草图时调用 EditRebuild3(),很容易让 SolidWorks 结束草图或把 ActiveSketch 清掉。后果是:拿不到草图特征名、拉伸选不上草图,甚至下一遍 InsertSketch(true) 也建不出活动草图。
去掉草图内的重建、只在退出草图后再重建,这些后续步骤才稳定。
-
启动下一张草图前不要乱 InsertSketch(false)
在并没有活动草图时也调 InsertSketch(false),部分版本会把内部状态弄乱,接着 InsertSketch(true) 后 ActiveSketch 一直为 null。
只有 ActiveSketch != null 时才先退出上一张草图,下一张才能正常开。
-
特征树里对不上「当前草图」
用 ReferenceEquals 比两个 COM 包装器会误判。改成 IUnknown 级比较 后,才能稳定拿到刚建好的草图特征名,拉伸才能用名字/选择集对准草图。
-
和你「手动动一下线就全好了」同机制的那一步
推理被 PushSketchInferOff 关掉了,SketchAddConstraints("sgCOINCIDENT") 在你环境里又常常加不上(所以你看到过 0/4),等于既没有吸附也没有可靠重合。
后面的 TrySketchSnapClosureLikeManualDrag 做了三件事:临时打开 AutoSolve、临时打开草图推理、再对一点 SetCoords 微扰又还原,等价于你在界面里拖动一下,迫使求解器跑一遍,微小间隙被吸附闭合,轮廓成了真正可用的封闭链,后面特征才顺。
- 其它
不画参考线:少一条构造线,少一批端点/选择上的干扰。
不用 dynamic:避免缺 Microsoft.CSharp 编不过,用反射调 SetCoords 功能不变。
所以「跑通」不是单点魔法,而是:草图状态不被重建/误退出搞坏 + 能指到正确草图 + 用微扰触发和你手动改线一样的求解 叠在一起的结果。
csharp
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;
namespace tools
{
/// <summary>
/// 选中面上最短两条直线边;在每条边上以边为「宽」边、
/// 垂直于边朝另一边的方向画 10×3 mm 矩形(CreateLine)。较高边一侧矩形朝「远离另一边」外扩(向上),较低边朝相反方向(向下)。
/// 矩形贴底边的对侧两角(外侧)做直线倒角(沿两邻边各截一段,非圆角),尺寸见 <see cref="ChamferAlongEdgeMm"/>。
/// 上侧与下侧矩形分两个独立草图绘制(同一面各 InsertSketch 一次)。
/// 每个草图绘制完成后在退出编辑前做凸台拉伸:成形到面(<see cref="swEndConditions_e.swEndCondUpToSurface"/>),
/// 在零件实体上找与草图所在面平行、且与草图面垂直距离最小(大于容差)的平面作为终止面;顶边为单段闭合轮廓以便拉伸(非薄壁)。
/// 草图坐标与 ModelToSketchTransform 一致时为米;<see cref="SketchManager.CreateLine"/> 前将端点换为毫米量化(见 <see cref="SketchCreateLineMmDecimalPlaces"/>),
/// 闭合轮廓采用链式端点且末段回到首点坐标,避免首尾缝隙。
/// </summary>
public static class face_shortest_edges_rectangles
{
const string LogPrefix = "[face_shortest_edge_rects]";
/// <summary>每条 CreateLine 成功后暂停(毫秒);0 表示不延时。</summary>
const int DelayMsAfterEachCreateLine = 0;
/// <summary>CreateLine 入参在毫米单位下保留的小数位数(过小易产生闭合缝隙)。</summary>
const int SketchCreateLineMmDecimalPlaces = 4;
/// <summary>绘制期间临时关闭,避免顶边等被错误推断连到 c0 等现有点(如 p2b→(-5,0) 而非 p2b→p3a)。</summary>
static readonly int[] SketchInferOffToggleIds =
{
(int)swUserPreferenceToggle_e.swSketchInference,
(int)swUserPreferenceToggle_e.swSketchInferFromModel,
(int)swUserPreferenceToggle_e.swSketchAutomaticRelations,
};
static void PushSketchInferOff(ISldWorks swApp, out bool[] prevBools, out bool[] applied)
{
int n = SketchInferOffToggleIds.Length;
prevBools = new bool[n];
applied = new bool[n];
if (swApp == null)
return;
for (int i = 0; i < n; i++)
{
try
{
int id = SketchInferOffToggleIds[i];
prevBools[i] = swApp.GetUserPreferenceToggle(id);
swApp.SetUserPreferenceToggle(id, false);
applied[i] = true;
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} PushSketchInferOff id={SketchInferOffToggleIds[i]}: {ex.Message}");
}
}
}
static void PopSketchInferOff(ISldWorks swApp, bool[] prevBools, bool[] applied)
{
if (swApp == null || applied == null || prevBools == null)
return;
for (int i = 0; i < applied.Length; i++)
{
if (!applied[i])
continue;
try
{
swApp.SetUserPreferenceToggle(SketchInferOffToggleIds[i], prevBools[i]);
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} PopSketchInferOff id={SketchInferOffToggleIds[i]}: {ex.Message}");
}
}
}
const double SegmentLengthMm = 10.0;
const double RectWidthMm = 10.0;
const double RectLengthMm = 3.0;
/// <summary>外侧两角直线倒角:沿每条邻边从角点向内的截距(mm)。过大会按边长自动缩小;≤0 则画直角矩形(4 段)。</summary>
const double ChamferAlongEdgeMm = 1.0;
static void Log(string message)
{
string line = $"{LogPrefix} {message}";
Console.WriteLine(line);
Debug.WriteLine(line);
}
public static void run(ISldWorks swApp, ModelDoc2 swModel)
{
Log("开始:最短两边→分两个草图:各 1 个 10×3mm 矩形,不画最短边参考线(Console→plugin_runtime_log.txt)");
if (swApp == null || swModel == null)
{
Log("中止:swApp 或 swModel 为空");
return;
}
if (swModel.GetType() != (int)swDocumentTypes_e.swDocPART)
{
Log("中止:当前不是零件文档");
swApp.SendMsgToUser("请在零件文档中使用本命令。");
return;
}
var selMgr = (SelectionMgr)swModel.SelectionManager;
if (selMgr == null)
{
Log("中止:SelectionMgr 为空");
swApp.SendMsgToUser("无法获取选择管理器。");
return;
}
Face2? targetFace = null;
int n = selMgr.GetSelectedObjectCount();
for (int i = 1; i <= n; i++)
{
int t = selMgr.GetSelectedObjectType3(i, -1);
if (t == (int)swSelectType_e.swSelFACES)
{
targetFace = (Face2)selMgr.GetSelectedObject6(i, -1);
break;
}
}
if (targetFace == null)
{
Log($"中止:选中项中无面(当前共 {n} 个选中对象)");
swApp.SendMsgToUser("请先选中一个面。");
return;
}
object[]? edgeObjs;
try
{
edgeObjs = (object[])targetFace.GetEdges();
}
catch (Exception ex)
{
Log($"GetEdges 异常: {ex.Message}");
swApp.SendMsgToUser($"无法读取面的边: {ex.Message}");
return;
}
if (edgeObjs == null || edgeObjs.Length < 2)
{
Log($"中止:面上边数不足");
swApp.SendMsgToUser("该面上的边少于 2 条,无法继续。");
return;
}
var edgesWithLen = new List<(Edge edge, double len)>();
foreach (object o in edgeObjs)
{
if (o is not Edge e)
continue;
double len = GetEdgeLengthMeters(e);
edgesWithLen.Add((e, len));
}
if (edgesWithLen.Count < 2)
{
Log("中止:未能得到至少 2 条有效边");
swApp.SendMsgToUser("未能解析该面上的边。");
return;
}
edgesWithLen.Sort((a, b) => a.len.CompareTo(b.len));
Edge edgeA = edgesWithLen[0].edge;
Edge edgeB = edgesWithLen[1].edge;
if (ReferenceEquals(edgeA, edgeB))
{
Log("中止:最短两条边引用相同");
swApp.SendMsgToUser("无法找到两条不同的最短边。");
return;
}
if (!TryGetEdgeVerticesModel(edgeA, out double[]? v0a, out double[]? v1a) ||
!TryGetEdgeVerticesModel(edgeB, out double[]? v0b, out double[]? v1b))
{
Log("中止:无法取得边的两端点(顶点)");
swApp.SendMsgToUser("无法取得最短两边的端点。");
return;
}
double segM = SegmentLengthMm / 1000.0;
if (!TryGetCenteredChordSegmentModel(v0a, v1a, segM, out double[]? segA0, out double[]? segA1) ||
!TryGetCenteredChordSegmentModel(v0b, v1b, segM, out double[]? segB0, out double[]? segB1))
{
Log("中止:线段端点计算失败(边可能退化)");
swApp.SendMsgToUser("无法计算边上的线段端点。");
return;
}
Log(
$"最短两边长度(m) L1={edgesWithLen[0].len:G9} L2={edgesWithLen[1].len:G9} | " +
$"参考线段≤{SegmentLengthMm}mm | " +
$"边A模型 {Vec3Fmt(segA0)} --- {Vec3Fmt(segA1)} | 边B {Vec3Fmt(segB0)} --- {Vec3Fmt(segB1)}");
Log("拉伸:成形到面,终止面=与草图面平行且法向距离最近之平面(非 LinkToThickness)。");
string modeUpper = "";
string modeLower = "";
bool drewUpper = TryDrawHalfSketch(
swApp, swModel, targetFace, segA0, segA1, segB0, segB1,
upperHalf: true, out modeUpper);
bool drewLower = TryDrawHalfSketch(
swApp, swModel, targetFace, segA0, segA1, segB0, segB1,
upperHalf: false, out modeLower);
if (drewUpper || drewLower)
{
Log(
$"完成:{(drewUpper ? $"上侧({modeUpper})" : "上侧失败")} | " +
$"{(drewLower ? $"下侧({modeLower})" : "下侧失败")}");
//swApp.SendMsgToUser(
// $"已在同一面上分两个草图绘制矩形(各含一条参考线段)。上侧:{(drewUpper ? modeUpper : "未完成")};下侧:{(drewLower ? modeLower : "未完成")}。[face_shortest_edge_rects]");
}
else
{
Log("两个草图均未成功绘制");
swApp.SendMsgToUser("绘制失败:两个草图均未完成,详见插件日志。[face_shortest_edge_rects]");
}
}
/// <summary>在同一面上打开新草图,只画上侧或下侧一个矩形(默认不画最短边构造参考线);绘制成功后尝试成形到「最近平行面」。</summary>
static bool TryDrawHalfSketch(
ISldWorks swApp,
ModelDoc2 swModel,
Face2 sketchHostFace,
double[] segA0,
double[] segA1,
double[] segB0,
double[] segB1,
bool upperHalf,
out string modeUsed)
{
modeUsed = "";
string tag = upperHalf ? "上侧矩形草图" : "下侧矩形草图";
// 仅在确有活动草图时退出;无草图时调用 InsertSketch(false) 部分 SolidWorks 版本会破坏后续 InsertSketch(true),导致 ActiveSketch 一直为空
try
{
if (swModel.SketchManager.ActiveSketch != null)
swModel.SketchManager.InsertSketch(false);
}
catch { /* ignore */ }
swModel.ClearSelection2(true);
if (!((Entity)sketchHostFace).Select4(false, null))
{
Log($"{tag}:Select4 面失败");
return false;
}
try
{
swModel.SketchManager.InsertSketch(true);
}
catch (Exception ex)
{
Log($"{tag}:InsertSketch 异常: {ex.Message}");
return false;
}
if (swModel.SketchManager.ActiveSketch == null)
{
Log($"{tag}:InsertSketch 后 ActiveSketch 为空");
return false;
}
var math = swApp.IGetMathUtility();
if (math == null)
{
Log($"{tag}:MathUtility 为空");
try { swModel.SketchManager.InsertSketch(false); } catch { /* ignore */ }
return false;
}
if (!TryComputeSketchRects(
math, swModel, segA0, segA1, segB0, segB1,
out double[] skA0, out double[] skA1, out double[] skB0, out double[] skB1,
out double[] upper0, out double[] upper1, out double[] lower0, out double[] lower1,
out double[] u0, out double[] u1, out double[] u2, out double[] u3,
out double[] l0, out double[] l1, out double[] l2, out double[] l3,
out bool aIsHigher))
{
Log($"{tag}:ModelToSketchTransform 或矩形几何失败");
try { swModel.SketchManager.InsertSketch(false); } catch { /* ignore */ }
return false;
}
Log(
$"{tag}:最短边A 草图 {Vec3Fmt(skA0)} → {Vec3Fmt(skA1)} | 边B {Vec3Fmt(skB0)} → {Vec3Fmt(skB1)} | " +
$"分层 上边={(aIsHigher ? "边A" : "边B")}");
double[] r0 = upperHalf ? upper0 : lower0;
double[] r1 = upperHalf ? upper1 : lower1;
double[] q0 = upperHalf ? u0 : l0;
double[] q1 = upperHalf ? u1 : l1;
double[] q2 = upperHalf ? u2 : l2;
double[] q3 = upperHalf ? u3 : l3;
bool ok = false;
string skFeatName = "";
try
{
if (TryDrawSingleSketchGeometry(swApp, swModel, r0, r1, q0, q1, q2, q3, drawRefSegment: false, out string mode))
{
modeUsed = mode;
ok = true;
// 在退出草图前先获取草图特征名
if (!TryGetActiveSketchFeatureName(swModel, out skFeatName))
{
Log($"{tag}:无法取得草图特征名,跳过拉伸");
// 即使获取失败也要退出草图
try
{
swModel.SketchManager.InsertSketch(false);
}
catch { }
}
else
{
Log($"{tag}:获取到草图特征名: {skFeatName}");
// 获取代表点(在退出草图前)
if (!TryGetFaceRepresentativePoint(sketchHostFace, out double[] pickOnHostFace))
{
Log($"{tag}:无法取草图所在面代表点,跳过拉伸");
try
{
swModel.SketchManager.InsertSketch(false);
}
catch { }
}
else
{
// 退出草图
try
{
swModel.SketchManager.InsertSketch(false);
}
catch (Exception ex)
{
Log($"{tag}:退出草图 InsertSketch(false): {ex.Message}");
}
TryEditRebuildAfterSketch(swModel, tag);
if (TryFeatureExtrudeUpToNearestParallelFace(
swModel, sketchHostFace, skFeatName, pickOnHostFace, tag))
modeUsed += ";已拉伸(成形到最近平行面)";
else
Log($"{tag}:成形到面拉伸失败");
}
}
}
else
Log($"{tag}:CreateLine 全部策略失败");
}
catch (Exception ex)
{
Log($"{tag}:CreateLine 异常: {ex.Message}");
}
finally
{
try
{
if (swModel.SketchManager.ActiveSketch != null)
swModel.SketchManager.InsertSketch(false);
}
catch (Exception ex)
{
Log($"{tag}:InsertSketch(false) 异常: {ex.Message}");
}
}
return ok;
}
const double ParallelFaceMinAbsDot = 0.985;
const double MinDistinctPlaneGapM = 1e-4;
const double MacroDraftRad = 0.01745329251994;
/// <summary>
/// 草图须已退出编辑。先成形到最近平行面(多组选择 × Dir × 选点 等尝试);失败则盲孔拉伸。
/// Interop 中 FeatureExtrusion2 第 3 个 bool 为 <c>Dir</c>(与宏里成功示例一致时常为 true),不是「先选草图/先选面」。
/// EnableContourSelection 在 FeatureExtrusion2 之后置 false,与宏一致。
/// </summary>
static bool TryFeatureExtrudeUpToNearestParallelFace(
ModelDoc2 swModel,
Face2 sketchHostFace,
string sketchFeatureName,
double[] pickOnHostFaceModel,
string logTag)
{
if (swModel == null || sketchHostFace == null || string.IsNullOrEmpty(sketchFeatureName) ||
pickOnHostFaceModel == null || pickOnHostFaceModel.Length < 3)
return false;
if (!TryGetFaceRepresentativePoint(sketchHostFace, out double[] p0) ||
!TryGetPlanarUnitNormal(sketchHostFace, out double[] n0))
{
Log($"{logTag}:草图所在面非法向或无法取点,无法成形到面");
return false;
}
if (!TryFindNearestParallelPlanarFace(
swModel, sketchHostFace, p0, n0,
out Face2? endFace,
out double gapMm))
{
Log($"{logTag}:未找到与草图面平行且间距>{MinDistinctPlaneGapM * 1000:G3}mm 的终止面");
return false;
}
double gapM = gapMm / 1000.0;
Log($"{logTag}:成形到面候选 间距≈{gapMm:G4}mm");
var selMgr = (SelectionMgr)swModel.SelectionManager;
var fm = (FeatureManager)swModel.FeatureManager;
// ------ 成形到面:草图→面 / 面→草图 × Dir × Sd × 选点(面上点/原点)
foreach (bool sketchFirst in new[] { true, false })
{
foreach (bool dir in new[] { true, false })
{
foreach (bool sd in new[] { true, false })
{
foreach (bool useZeroPick in new[] { false, true })
{
double px, py, pz;
if (useZeroPick)
{
px = py = pz = 0;
}
else
{
px = pickOnHostFaceModel[0];
py = pickOnHostFaceModel[1];
pz = pickOnHostFaceModel[2];
}
swModel.ClearSelection2(true);
bool picked;
if (sketchFirst)
{
picked = swModel.Extension.SelectByID2(
sketchFeatureName, "SKETCH", px, py, pz, false, 0, null, 0);
if (picked)
picked = ((Entity)endFace).Select4(true, null);
}
else
{
picked = ((Entity)endFace).Select4(false, null);
if (picked)
picked = swModel.Extension.SelectByID2(
sketchFeatureName, "SKETCH", px, py, pz, true, 0, null, 0);
}
if (!picked)
continue;
Feature? feat = fm.FeatureExtrusion2(
sd,
false,
dir,
(int)swEndConditions_e.swEndCondUpToSurface,
(int)swEndConditions_e.swEndCondBlind,
0.01,
0.01,
false,
false,
false,
false,
MacroDraftRad,
MacroDraftRad,
false,
false,
false,
false,
true,
true,
true,
0,
0.0,
false);
if (feat != null)
{
SetEnableContourSelectionSafe(selMgr, false);
Log(
$"{logTag}:成形到面成功(sketchFirst={sketchFirst} Dir={dir} Sd={sd} " +
$"选点={(useZeroPick ? "0,0,0" : "面上点")})");
return true;
}
}
}
}
}
Log($"{logTag}:成形到面全部组合失败,尝试盲孔拉伸(宏:T1=T2=0, D1≈min(0.05,间距), D2=0.01)");
SetEnableContourSelectionSafe(selMgr, false);
// ------ 盲孔回退:与宏一致 True,False,False,0,0, D1,D2, ..., 末三 true(VB 里 1,1,1)
swModel.ClearSelection2(true);
bool skOk = TrySelectSketchOnly(swModel, sketchFeatureName, pickOnHostFaceModel, logTag);
if (!skOk)
return false;
double blindD1 = Math.Min(0.05, Math.Max(gapM * 1.02, MinDistinctPlaneGapM * 2.0));
double blindD2 = 0.01;
Feature? blindFeat = null;
bool blindOkSd = false, blindOkDir = false;
foreach (bool dir in new[] { true, false })
{
foreach (bool sd in new[] { true, false })
{
blindFeat = fm.FeatureExtrusion2(
sd,
false,
dir,
(int)swEndConditions_e.swEndCondBlind,
(int)swEndConditions_e.swEndCondBlind,
blindD1,
blindD2,
false,
false,
false,
false,
MacroDraftRad,
MacroDraftRad,
false,
false,
false,
false,
true,
true,
true,
0,
0.0,
false);
if (blindFeat != null)
{
blindOkSd = sd;
blindOkDir = dir;
break;
}
}
if (blindFeat != null)
break;
}
SetEnableContourSelectionSafe(selMgr, false);
if (blindFeat == null)
{
Log($"{logTag}:盲孔 FeatureExtrusion2 仍返回 null(已尝试 Dir/Sd 组合)");
return false;
}
Log($"{logTag}:盲孔拉伸成功 D1={blindD1:G6}m D2={blindD2:G6}m Sd={blindOkSd} Dir={blindOkDir}");
return true;
}
/// <summary>退出草图后重建,便于轮廓参与后续 FeatureExtrusion2 选择。</summary>
static void TryEditRebuildAfterSketch(ModelDoc2 swModel, string logTag)
{
try
{
swModel.EditRebuild3();
}
catch (Exception ex)
{
Log($"{logTag}:EditRebuild3: {ex.Message}");
}
}
static void SetEnableContourSelectionSafe(SelectionMgr selMgr, bool value)
{
try
{
selMgr.EnableContourSelection = value;
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} EnableContourSelection={value}: {ex.Message}");
}
}
/// <summary>仅选中草图特征;先试面上坐标,再试 (0,0,0)(与示例宏一致)。</summary>
static bool TrySelectSketchOnly(
ModelDoc2 swModel,
string sketchFeatureName,
double[] pickOnHostFaceModel,
string logTag)
{
swModel.ClearSelection2(true);
double px = pickOnHostFaceModel[0], py = pickOnHostFaceModel[1], pz = pickOnHostFaceModel[2];
if (swModel.Extension.SelectByID2(sketchFeatureName, "SKETCH", px, py, pz, false, 0, null, 0))
return true;
if (swModel.Extension.SelectByID2(sketchFeatureName, "SKETCH", 0, 0, 0, false, 0, null, 0))
{
Log($"{logTag}:SelectByID2 草图改用(0,0,0)成功");
return true;
}
Log($"{logTag}:SelectByID2 草图「{sketchFeatureName}」在面上点与(0,0,0)均失败");
return false;
}
/// <summary>同一 COM 对象可能被包装为不同 RCW,ReferenceEquals 不可靠。</summary>
static bool ComObjectIdentityEquals(object? a, object? b)
{
if (a == null || b == null)
return a == null && b == null;
try
{
IntPtr pa = Marshal.GetIUnknownForObject(a);
try
{
IntPtr pb = Marshal.GetIUnknownForObject(b);
try
{
return pa == pb;
}
finally
{
Marshal.Release(pb);
}
}
finally
{
Marshal.Release(pa);
}
}
catch
{
return ReferenceEquals(a, b);
}
}
/// <summary>对两草图点添加「重合」关系(特征树/显示约束图标);已为同一 COM 点时跳过。</summary>
static bool TrySketchCoincident(ModelDoc2 swModel, SketchPoint? a, SketchPoint? b)
{
if (a == null || b == null)
return false;
if (ComObjectIdentityEquals(a, b))
return true;
try
{
swModel.ClearSelection2(true);
((Entity)a).Select4(false, null);
((Entity)b).Select4(true, null);
swModel.SketchAddConstraints("sgCOINCIDENT");
return true;
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TrySketchCoincident: {ex.Message}");
return false;
}
}
/// <summary>
/// 手动拖动一条线会触发草图求解与推理,邻近顶点常被吸附闭合。临时开启 AutoSolve、草图推理,
/// 并对轮廓上一点做 ISketchPoint.SetCoords 微扰再还原,用 API 模拟该效果。
/// </summary>
static void TrySketchSnapClosureLikeManualDrag(ISldWorks swApp, ModelDoc2 swModel, SketchPoint? anyContourPoint)
{
bool prevAutoSolve = false;
bool gotAutoSolve = false;
try
{
prevAutoSolve = swModel.SketchManager.AutoSolve;
gotAutoSolve = true;
}
catch { /* ignore */ }
try
{
try
{
swModel.SketchManager.AutoSolve = true;
}
catch { /* ignore */ }
foreach (int id in SketchInferOffToggleIds)
{
try
{
swApp.SetUserPreferenceToggle(id, true);
}
catch { /* ignore */ }
}
TryNudgeSketchPointCoords(anyContourPoint);
Log("已触发草图微扰+自动求解(等价于手动改线后间隙被吸附闭合)");
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TrySketchSnapClosureLikeManualDrag: {ex.Message}");
}
finally
{
if (gotAutoSolve)
{
try
{
swModel.SketchManager.AutoSolve = prevAutoSolve;
}
catch { /* ignore */ }
}
}
}
static void TryNudgeSketchPointCoords(SketchPoint? pt)
{
if (pt == null)
return;
try
{
Type t = pt.GetType();
PropertyInfo? px = t.GetProperty("X");
PropertyInfo? py = t.GetProperty("Y");
PropertyInfo? pz = t.GetProperty("Z");
MethodInfo? setCoords = t.GetMethod("SetCoords");
if (px == null || py == null || pz == null || setCoords == null)
return;
double x = Convert.ToDouble(px.GetValue(pt));
double y = Convert.ToDouble(py.GetValue(pt));
double z = Convert.ToDouble(pz.GetValue(pt));
const double eps = 1e-9;
setCoords.Invoke(pt, new object[] { x + eps, y, z });
setCoords.Invoke(pt, new object[] { x, y, z });
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TryNudgeSketchPointCoords: {ex.Message}");
}
}
/// <summary>Interop 未暴露时,用反射调用 COM Sketch.GetFeature() 取树中草图特征名。</summary>
static bool TryGetActiveSketchFeatureName(ModelDoc2 swModel, out string name)
{
name = "";
try
{
object? sk = swModel.SketchManager.ActiveSketch;
if (sk == null)
return false;
// 方法1: 直接尝试获取 Name 属性(SolidWorks 2018+可能支持)
Type skType = sk.GetType();
PropertyInfo? nameProp = skType.GetProperty("Name");
if (nameProp != null)
{
object? nameVal = nameProp.GetValue(sk);
if (nameVal is string n && !string.IsNullOrEmpty(n))
{
name = n;
Log($"[调试] 通过Sketch.Name获取: {name}");
return true;
}
}
// 方法2: 反射调用 GetFeature()
MethodInfo? mi = skType.GetMethod("GetFeature");
if (mi != null)
{
object? fob = mi.Invoke(sk, null);
if (fob is Feature feat)
{
name = feat.Name ?? "";
if (!string.IsNullOrEmpty(name))
{
Log($"[调试] 通过GetFeature()获取: {name}");
return true;
}
}
}
// 方法3: 遍历特征树找当前激活草图
Feature? currentFeat = swModel.FirstFeature() as Feature;
while (currentFeat != null)
{
if (currentFeat.GetTypeName2() == "ProfileFeature")
{
object? sketchObj = currentFeat.GetSpecificFeature2();
if (sketchObj != null && ComObjectIdentityEquals(sketchObj, sk))
{
name = currentFeat.Name ?? "";
Log($"[调试] 通过遍历特征树获取: {name}");
return !string.IsNullOrEmpty(name);
}
}
currentFeat = currentFeat.GetNextFeature() as Feature;
}
Log("[调试] 所有方法均未获取到草图特征名");
return false;
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TryGetActiveSketchFeatureName: {ex.Message}");
return false;
}
}
static bool TryGetFaceRepresentativePoint(Face2 face, out double[] p)
{
p = Array.Empty<double>();
try
{
var edges = (object[])face.GetEdges();
foreach (object o in edges)
{
if (o is not Edge ed)
continue;
var v = (Vertex)ed.GetStartVertex();
if (v == null)
continue;
var arr = (double[])v.GetPoint();
if (arr != null && arr.Length >= 3)
{
p = new[] { arr[0], arr[1], arr[2] };
return true;
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TryGetFaceRepresentativePoint: {ex.Message}");
}
return false;
}
static bool TryGetPlanarUnitNormal(Face2 face, out double[] n)
{
n = new[] { 0.0, 0.0, 1.0 };
try
{
var surf = face.IGetSurface();
if (surf == null || !surf.IsPlane())
return false;
var pp = (double[])surf.PlaneParams;
if (pp != null && pp.Length >= 3)
{
double L = Math.Sqrt(pp[0] * pp[0] + pp[1] * pp[1] + pp[2] * pp[2]);
if (L < 1e-15)
return false;
n = new[] { pp[0] / L, pp[1] / L, pp[2] / L };
return true;
}
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TryGetPlanarUnitNormal: {ex.Message}");
}
return false;
}
static bool TryFindNearestParallelPlanarFace(
ModelDoc2 swModel,
Face2 sketchFace,
double[] p0,
double[] n0,
out Face2? bestFace,
out double gapMm)
{
bestFace = null;
gapMm = double.PositiveInfinity;
if (swModel is not PartDoc partDoc)
return false;
object? bodiesOb;
try
{
bodiesOb = partDoc.GetBodies2((int)swBodyType_e.swSolidBody, false);
}
catch (Exception ex)
{
Log($"{LogPrefix} GetBodies2: {ex.Message}");
return false;
}
if (bodiesOb is not object[] bodies)
return false;
double bestGapM = double.PositiveInfinity;
foreach (object bo in bodies)
{
if (bo is not Body2 body)
continue;
object? facesOb;
try
{
facesOb = body.GetFaces();
}
catch
{
continue;
}
if (facesOb is not object[] faces)
continue;
foreach (object fo in faces)
{
if (fo is not Face2 f)
continue;
if (ReferenceEquals(f, sketchFace))
continue;
var surf = f.IGetSurface();
if (surf == null || !surf.IsPlane())
continue;
if (!TryGetPlanarUnitNormal(f, out double[] nf))
continue;
double align = Math.Abs(Dot3(nf, n0));
if (align < ParallelFaceMinAbsDot)
continue;
if (!TryGetFaceRepresentativePoint(f, out double[] pf))
continue;
double gapM = Math.Abs(Dot3(Sub3(pf, p0), n0));
if (gapM < MinDistinctPlaneGapM)
continue;
if (gapM < bestGapM - 1e-9)
{
bestGapM = gapM;
bestFace = f;
}
}
}
if (bestFace == null || double.IsPositiveInfinity(bestGapM))
return false;
gapMm = bestGapM * 1000.0;
return true;
}
/// <summary>当前激活草图下,将模型线段变为草图坐标并计算上下矩形四角。</summary>
static bool TryComputeSketchRects(
MathUtility math,
ModelDoc2 swModel,
double[] segA0,
double[] segA1,
double[] segB0,
double[] segB1,
out double[] skA0,
out double[] skA1,
out double[] skB0,
out double[] skB1,
out double[] upper0,
out double[] upper1,
out double[] lower0,
out double[] lower1,
out double[] u0,
out double[] u1,
out double[] u2,
out double[] u3,
out double[] l0,
out double[] l1,
out double[] l2,
out double[] l3,
out bool aIsHigher)
{
skA0 = skA1 = skB0 = skB1 = Array.Empty<double>();
upper0 = upper1 = lower0 = lower1 = Array.Empty<double>();
u0 = u1 = u2 = u3 = l0 = l1 = l2 = l3 = Array.Empty<double>();
aIsHigher = false;
TryModelPointToActiveSketch(math, swModel, segA0, out skA0);
TryModelPointToActiveSketch(math, swModel, segA1, out skA1);
TryModelPointToActiveSketch(math, swModel, segB0, out skB0);
TryModelPointToActiveSketch(math, swModel, segB1, out skB1);
if (skA0.Length < 3 || skA1.Length < 3 || skB0.Length < 3 || skB1.Length < 3)
return false;
double[] midA = Mid3(skA0, skA1);
double[] midB = Mid3(skB0, skB1);
aIsHigher = midA[1] > midB[1]
|| (Math.Abs(midA[1] - midB[1]) < 1e-12 && midA[0] >= midB[0]);
upper0 = aIsHigher ? skA0 : skB0;
upper1 = aIsHigher ? skA1 : skB1;
lower0 = aIsHigher ? skB0 : skA0;
lower1 = aIsHigher ? skB1 : skA1;
double[] midU = Mid3(upper0, upper1);
double[] midL = Mid3(lower0, lower1);
double[] nSep = Sub3(midU, midL);
double lenSep = Len3(nSep);
double[] nUp;
if (lenSep < 1e-12)
nUp = new double[] { 0, 1, 0 };
else
nUp = Scale3(nSep, 1.0 / lenSep);
double h = RectLengthMm / 1000.0;
double[] tU = Normalize3(Sub3(upper1, upper0));
if (Len3(tU) < 1e-12)
return false;
BuildInPlanePerpToward(nUp, tU, towardPositiveNUp: true, out double[] perpUp);
BuildInPlanePerpToward(nUp, tU, towardPositiveNUp: false, out double[] perpDn);
CornersRect(upper0, upper1, perpUp, h, out u0, out u1, out u2, out u3);
CornersRect(lower0, lower1, perpDn, h, out l0, out l1, out l2, out l3);
return true;
}
/// <summary>可选一条最短边构造参考线 + 矩形(底边 + 外侧两角直线倒角...);CreateLine 先试米,再试×1000。</summary>
static bool TryDrawSingleSketchGeometry(
ISldWorks swApp,
ModelDoc2 swModel,
double[] ref0,
double[] ref1,
double[] c0,
double[] c1,
double[] c2,
double[] c3,
bool drawRefSegment,
out string modeUsed)
{
modeUsed = "";
(bool meters, bool addDb)[] attempts =
{
(true, false),
(true, true),
(false, false),
(false, true),
};
foreach (var a in attempts)
{
if (TryDrawRectOnce(swApp, swModel, ref0, ref1, c0, c1, c2, c3, drawRefSegment, a.meters, a.addDb))
{
modeUsed =
$"CreateLine {(a.meters ? "米" : "×1000mm")} AddToDB={a.addDb}" +
(drawRefSegment ? " +参考线段" : "");
return true;
}
}
return false;
}
static string SketchPointMmFmt(double[] skM) =>
skM == null || skM.Length < 3
? "?"
: $"{skM[0] * 1000:G9},{skM[1] * 1000:G9},{skM[2] * 1000:G9}";
/// <summary>
/// 草图空间点(米)先换为毫米并按 <see cref="SketchCreateLineMmDecimalPlaces"/> 量化,再转为 <see cref="SketchManager.CreateLine"/> 所用单位:
/// <paramref name="apiCoordsInMeters"/> 为 true 时输出米,为 false 时输出毫米(与 sketchMeters=false 分支一致)。
/// </summary>
static double[] SketchPointForCreateLine(double[] sketchMeters, bool apiCoordsInMeters)
{
if (sketchMeters == null || sketchMeters.Length < 3)
return new double[] { 0, 0, 0 };
double MmQuantize(double coordMeters) =>
Math.Round(
coordMeters * 1000.0,
SketchCreateLineMmDecimalPlaces,
MidpointRounding.AwayFromZero);
double xMm = MmQuantize(sketchMeters[0]);
double yMm = MmQuantize(sketchMeters[1]);
double zMm = MmQuantize(sketchMeters[2]);
if (apiCoordsInMeters)
return new[] { xMm / 1000.0, yMm / 1000.0, zMm / 1000.0 };
return new[] { xMm, yMm, zMm };
}
static bool SketchCreateLineEndpoints(
ModelDoc2 swModel,
double[] ra,
double[] rb,
out SketchPoint? startPt,
out SketchPoint? endPt)
{
startPt = endPt = null;
try
{
swModel.ClearSelection2(true);
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} ClearSelection2: {ex.Message}");
}
object? o = swModel.SketchManager.CreateLine(ra[0], ra[1], ra[2], rb[0], rb[1], rb[2]);
if (o == null)
return false;
if (o is SketchSegment seg)
{
startPt = GetSegmentStartPoint(seg);
endPt = GetSegmentEndPoint(seg);
}
if (DelayMsAfterEachCreateLine > 0)
Thread.Sleep(DelayMsAfterEachCreateLine);
return true;
}
/// <summary>草图空间两点欧氏长度(mm);输入为草图米。</summary>
static double SketchSegLengthMm(double[] a, double[] b) =>
a == null || b == null || a.Length < 3 || b.Length < 3
? double.NaN
: Len3(Sub3(b, a)) * 1000.0;
/// <summary>与 <see cref="TryBuildRectSketchSegments"/> 输出顺序一致,便于对照日志。</summary>
static string RectContourSegLabel(int index, int segmentCount)
{
if (segmentCount == 4)
{
return index switch
{
0 => "矩形底边(c0---c1)",
1 => "矩形右侧(c1---c2)",
2 => "矩形顶边(c2---c3)",
3 => "矩形左侧(c3---c0)",
_ => $"矩形边#{index}",
};
}
if (segmentCount == 6)
{
return index switch
{
0 => "矩形底边(c0---c1)",
1 => "矩形右竖边至倒角(c1---p2a)",
2 => "外侧倒角斜线(c2 角 p2a---p2b)",
3 => "矩形顶边整段(p2b---p3a)",
4 => "外侧倒角斜线(c3 角 p3a---p3b)",
5 => "矩形左竖边至底(p3b---c0)",
_ => $"矩形边#{index}",
};
}
return $"矩形边#{index}";
}
static bool TryDrawRectOnce(
ISldWorks swApp,
ModelDoc2 swModel,
double[] ra0,
double[] ra1,
double[] c0,
double[] c1,
double[] c2,
double[] c3,
bool drawRefSegment,
bool sketchMeters,
bool addToDb)
{
PushSketchInferOff(swApp, out bool[] inferPrev, out bool[] inferApplied);
try
{
swModel.SketchManager.AddToDB = addToDb;
Log(
$"草图线段调试 CreateLine入参 sketchMeters={sketchMeters} AddToDB={addToDb} " +
$"(端点:毫米保留 {SketchCreateLineMmDecimalPlaces} 位;闭合轮廓链式端点+首尾重合;下列日志仍为理论值 mm)");
int segIdx = 0;
if (drawRefSegment)
{
double refLenMm = SketchSegLengthMm(ra0, ra1);
Log(
$" 线段#{segIdx} 参考线(最短边段) {SketchPointMmFmt(ra0)} --- {SketchPointMmFmt(ra1)} | " +
$"L={(double.IsNaN(refLenMm) ? "?" : $"{refLenMm:G9}")} mm");
segIdx++;
try
{
swModel.ClearSelection2(true);
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} ClearSelection2(ref): {ex.Message}");
}
double[] pa = SketchPointForCreateLine(ra0, sketchMeters);
double[] pb = SketchPointForCreateLine(ra1, sketchMeters);
object? refObj = swModel.SketchManager.CreateLine(pa[0], pa[1], pa[2], pb[0], pb[1], pb[2]);
if (refObj == null)
return false;
if (refObj is SketchSegment skRef)
{
try
{
skRef.ConstructionGeometry = true;
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} 参考线设构造几何: {ex.Message}");
}
}
if (DelayMsAfterEachCreateLine > 0)
Thread.Sleep(DelayMsAfterEachCreateLine);
}
double chamferM = ChamferAlongEdgeMm / 1000.0;
if (!TryBuildRectSketchSegments(c0, c1, c2, c3, chamferM, out var segs))
return false;
// 预计算所有量化后的端点,确保首尾完全重合
double[][] quantizedPoints = new double[segs.Count + 1][];
for (int i = 0; i < segs.Count; i++)
{
quantizedPoints[i] = SketchPointForCreateLine(segs[i].p0, sketchMeters);
}
// 最后一个点的终点强制等于第一个点的起点,确保闭合
quantizedPoints[segs.Count] = quantizedPoints[0];
var contourChain = new List<(SketchPoint? s, SketchPoint? e)>();
// 关键:使用原始量化点,不依赖前一段的返回值,避免SolidWorks内部微调导致的间隙
for (int i = 0; i < segs.Count; i++)
{
var (pa, pb) = segs[i];
double lenMm = SketchSegLengthMm(pa, pb);
Log(
$" 线段#{segIdx} {RectContourSegLabel(i, segs.Count)} " +
$"{SketchPointMmFmt(pa)} --- {SketchPointMmFmt(pb)} | " +
$"L={(double.IsNaN(lenMm) ? "?" : $"{lenMm:G9}")} mm");
segIdx++;
// 直接使用预计算的量化点,最后一段强制闭合到起点
double[] ra = quantizedPoints[i];
double[] rb = (i == segs.Count - 1) ? quantizedPoints[0] : quantizedPoints[i + 1];
if (!SketchCreateLineEndpoints(swModel, ra, rb, out SketchPoint? sp, out SketchPoint? ep))
return false;
contourChain.Add((sp, ep));
}
// 相邻线段端点逐对添加「重合」约束(树上可见);sgMERGEPOINTS 多为合并实体,不一定显示为约束
int nChain = contourChain.Count;
if (nChain >= 2)
{
int okCoin = 0;
for (int i = 0; i < nChain; i++)
{
SketchPoint? atCorner = contourChain[i].e;
SketchPoint? nextStart = contourChain[(i + 1) % nChain].s;
if (TrySketchCoincident(swModel, atCorner, nextStart))
okCoin++;
}
Log($"闭合轮廓重合(Coincident):{okCoin}/{nChain} 处成功(首尾闭合处含于最后一项)");
}
SketchPoint? snapHook =
contourChain.Count > 0 ? contourChain[0].s ?? contourChain[0].e : null;
TrySketchSnapClosureLikeManualDrag(swApp, swModel, snapHook);
// 勿在此处 EditRebuild3:草图编辑中重建常会退出草图或清空 ActiveSketch,
// 导致无法取得特征名、下一草图 InsertSketch 后 ActiveSketch 仍为空。重建见 TryEditRebuildAfterSketch(退出草图后)。
return true;
}
finally
{
PopSketchInferOff(swApp, inferPrev, inferApplied);
try { swModel.SketchManager.AddToDB = false; } catch { /* ignore */ }
}
}
/// <summary>
/// c0--c1 为贴参考边的底边;仅在远离底边的两角 c2、c3 做直线倒角。
/// 返回闭合轮廓的线段列表(顺序连接)。
/// </summary>
static bool TryBuildRectSketchSegments(
double[] c0,
double[] c1,
double[] c2,
double[] c3,
double chamferAlongEdgeM,
out List<(double[] p0, double[] p1)> segments)
{
segments = new List<(double[], double[])>();
if (chamferAlongEdgeM <= 1e-15)
{
segments.Add((c0, c1));
segments.Add((c1, c2));
segments.Add((c2, c3));
segments.Add((c3, c0));
return true;
}
double len12 = Len3(Sub3(c2, c1));
double len23 = Len3(Sub3(c3, c2));
double len30 = Len3(Sub3(c0, c3));
if (len12 < 1e-15 || len23 < 1e-15 || len30 < 1e-15)
return false;
double d2 = Math.Min(chamferAlongEdgeM, Math.Min(len12, len23) * 0.499);
double d3 = Math.Min(chamferAlongEdgeM, Math.Min(len23, len30) * 0.499);
const double eps = 1e-11;
if (d2 + d3 > len23 - eps)
{
double s = (len23 - eps) / (d2 + d3);
d2 *= s;
d3 *= s;
}
if (d2 < 1e-12 || d3 < 1e-12)
{
segments.Add((c0, c1));
segments.Add((c1, c2));
segments.Add((c2, c3));
segments.Add((c3, c0));
return true;
}
// c2:沿 c1→c2、c2→c3 各截 d2
double[] u21 = Normalize3(Sub3(c1, c2));
double[] u23 = Normalize3(Sub3(c3, c2));
double[] p2a = Add3(c2, Scale3(u21, d2));
double[] p2b = Add3(c2, Scale3(u23, d2));
// c3:沿 c2→c3、c3→c0 各截 d3
double[] u32 = Normalize3(Sub3(c2, c3));
double[] u30 = Normalize3(Sub3(c0, c3));
double[] p3a = Add3(c3, Scale3(u32, d3));
double[] p3b = Add3(c3, Scale3(u30, d3));
segments.Add((c0, c1));
segments.Add((c1, p2a));
segments.Add((p2a, p2b));
// 顶边必须单段闭合,否则凸台拉伸易被识别为薄壁;推断问题由 SketchInferOff 与构造参考线承担。
segments.Add((p2b, p3a));
segments.Add((p3a, p3b));
segments.Add((p3b, c0));
Log(
$"外侧直线倒角(草图米):沿边截距 C2={d2 * 1000:G4}mm C3={d3 * 1000:G4}mm | " +
$"顶边可用长={len23 * 1000:G4}mm");
return true;
}
static void CornersRect(double[] e0, double[] e1, double[] perp, double h, out double[] c0, out double[] c1, out double[] c2, out double[] c3)
{
double[] o = Scale3(perp, h);
c0 = e0;
c1 = e1;
c2 = Add3(e1, o);
c3 = Add3(e0, o);
}
/// <summary>在草图平面内、垂直于边切向 t,取指向 ±n_up 一侧的单位法向。</summary>
static void BuildInPlanePerpToward(double[] nUp, double[] t, bool towardPositiveNUp, out double[] perp)
{
double dt = Dot3(nUp, t);
double[] proj =
{
nUp[0] - dt * t[0],
nUp[1] - dt * t[1],
nUp[2] - dt * t[2],
};
double len = Len3(proj);
if (len < 1e-12)
proj = new double[] { -t[1], t[0], 0 };
len = Len3(proj);
if (len < 1e-12)
{
perp = new double[] { 0, 0, 1 };
len = 1;
}
perp = Scale3(proj, 1.0 / len);
double dn = Dot3(perp, nUp);
if (towardPositiveNUp && dn < 0 || !towardPositiveNUp && dn > 0)
perp = Scale3(perp, -1.0);
}
static double[] Mid3(double[] a, double[] b) =>
new[]
{
(a[0] + b[0]) * 0.5,
(a[1] + b[1]) * 0.5,
(a[2] + b[2]) * 0.5,
};
static double[] Sub3(double[] a, double[] b) =>
new[] { a[0] - b[0], a[1] - b[1], a[2] - b[2] };
static double[] Add3(double[] a, double[] b) =>
new[] { a[0] + b[0], a[1] + b[1], a[2] + b[2] };
static double[] Scale3(double[] a, double s) =>
new[] { a[0] * s, a[1] * s, a[2] * s };
static double Dot3(double[] a, double[] b) =>
a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
static double Len3(double[] v) =>
Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
static double[] Normalize3(double[] v)
{
double L = Len3(v);
if (L < 1e-15)
return new double[] { 1, 0, 0 };
return Scale3(v, 1.0 / L);
}
static string Vec3Fmt(double[] v) =>
v == null || v.Length < 3 ? "?" : $"({v[0]:G9},{v[1]:G9},{v[2]:G9})";
static bool TryGetCenteredChordSegmentModel(
double[] p0,
double[] p1,
double segmentLengthM,
out double[]? a,
out double[]? b)
{
a = b = null;
double dx = p1[0] - p0[0], dy = p1[1] - p0[1], dz = p1[2] - p0[2];
double L = Math.Sqrt(dx * dx + dy * dy + dz * dz);
if (L < 1e-15)
return false;
dx /= L;
dy /= L;
dz /= L;
double mx = (p0[0] + p1[0]) * 0.5;
double my = (p0[1] + p1[1]) * 0.5;
double mz = (p0[2] + p1[2]) * 0.5;
if (L <= segmentLengthM + 1e-12)
{
a = new[] { p0[0], p0[1], p0[2] };
b = new[] { p1[0], p1[1], p1[2] };
return true;
}
double h = segmentLengthM * 0.5;
a = new[] { mx - dx * h, my - dy * h, mz - dz * h };
b = new[] { mx + dx * h, my + dy * h, mz + dz * h };
return true;
}
static bool TryGetEdgeVerticesModel(Edge edge, out double[]? p0, out double[]? p1)
{
p0 = p1 = null;
try
{
var v1 = edge.GetStartVertex() as Vertex;
var v2 = edge.GetEndVertex() as Vertex;
if (v1 == null || v2 == null)
return false;
p0 = (double[])v1.GetPoint();
p1 = (double[])v2.GetPoint();
return p0 != null && p1 != null && p0.Length >= 3 && p1.Length >= 3;
}
catch (Exception ex)
{
Debug.WriteLine($"TryGetEdgeVerticesModel: {ex.Message}");
return false;
}
}
static double GetEdgeLengthMeters(Edge edge)
{
try
{
var v1 = edge.GetStartVertex() as Vertex;
var v2 = edge.GetEndVertex() as Vertex;
if (v1 != null && v2 != null)
{
var p1 = (double[])v1.GetPoint();
var p2 = (double[])v2.GetPoint();
double dx = p2[0] - p1[0], dy = p2[1] - p1[1], dz = p2[2] - p1[2];
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
var c = (Curve)edge.GetCurve();
if (c == null)
return double.MaxValue;
if (c.IsCircle())
{
c.GetEndParams(out double t0, out double t1, out _, out _);
var cp = (double[])c.CircleParams;
if (cp != null && cp.Length > 6)
return Math.Abs(t1 - t0) * cp[6];
}
c.GetEndParams(out double a0, out double a1, out _, out _);
if (TryCurveEvaluatePoint3D(c, a0, out var pt0) &&
TryCurveEvaluatePoint3D(c, a1, out var pt1))
{
double dx = pt1[0] - pt0[0], dy = pt1[1] - pt0[1], dz = pt1[2] - pt0[2];
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
}
catch (Exception ex)
{
Debug.WriteLine($"GetEdgeLengthMeters: {ex.Message}");
}
return double.MaxValue;
}
static bool TryModelPointToActiveSketch(
MathUtility math,
ModelDoc2 swModel,
double[] model3,
out double[] sketch3)
{
sketch3 = Array.Empty<double>();
try
{
var sk = (Sketch)swModel.SketchManager.ActiveSketch;
var mt = (MathTransform)sk.ModelToSketchTransform;
if (mt == null)
return false;
var mp = (MathPoint)math.CreatePoint(model3);
mp = (MathPoint)mp.MultiplyTransform(mt);
var arr = (double[])mp.ArrayData;
if (arr == null || arr.Length < 3)
return false;
sketch3 = new[] { arr[0], arr[1], arr[2] };
return true;
}
catch (Exception ex)
{
Debug.WriteLine($"TryModelPointToActiveSketch: {ex.Message}");
return false;
}
}
static bool TryCurveEvaluatePoint3D(Curve curve, double t, out double[]? pt)
{
pt = null;
try
{
object ev = curve.Evaluate(t);
if (ev is double[] da && da.Length >= 3)
{
pt = new[] { da[0], da[1], da[2] };
return true;
}
if (ev is object[] oa && oa.Length >= 3 &&
TryCoerceToDouble(oa[0], out var x) &&
TryCoerceToDouble(oa[1], out var y) &&
TryCoerceToDouble(oa[2], out var z))
{
pt = new[] { x, y, z };
return true;
}
}
catch (Exception ex)
{
Debug.WriteLine($"TryCurveEvaluatePoint3D: {ex.Message}");
}
return false;
}
static bool TryCoerceToDouble(object? o, out double v)
{
v = 0;
if (o == null) return false;
switch (o)
{
case double d:
v = d;
return true;
case float f:
v = f;
return true;
case int i:
v = i;
return true;
default:
return double.TryParse(o.ToString(), out v);
}
}
/// <summary>
/// 执行草图修复,自动闭合微小间隙、合并重叠线段等。
/// 策略:降低量化精度到4位小数(mm),减少浮点误差;退出草图前强制重建。
/// </summary>
static void TryRepairSketch(ISldWorks swApp, ModelDoc2 swModel, bool sketchMeters)
{
try
{
// 在退出草图前执行一次重建,让SolidWorks内部优化几何
swModel.EditRebuild3();
Log("草图绘制完成,已执行EditRebuild3优化几何");
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TryRepairSketch: {ex.Message}");
}
}
/// <summary>获取线段的起点</summary>
static SketchPoint? GetSegmentStartPoint(SketchSegment segment)
{
try
{
if (segment is SketchLine line)
return (SketchPoint?)line.GetStartPoint2();
else if (segment is SketchArc arc)
return (SketchPoint?)arc.GetStartPoint2();
}
catch { }
return null;
}
/// <summary>获取线段的终点</summary>
static SketchPoint? GetSegmentEndPoint(SketchSegment segment)
{
try
{
if (segment is SketchLine line)
return (SketchPoint?)line.GetEndPoint2();
else if (segment is SketchArc arc)
return (SketchPoint?)arc.GetEndPoint2();
}
catch { }
return null;
}
}
}