c# solidworks 生成方管工艺插槽 createline 拉伸 约束
·

cursor真香
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>
/// 选中面上最短两条直线边(须为几何直线且长度不小于 <see cref="MinStraightEdgeLengthMm"/> mm);在每条边上以边为「宽」边、
/// 垂直于边朝另一边的方向画 10×3 mm 矩形(CreateLine)。较高边一侧矩形朝「远离另一边」外扩(向上),较低边朝相反方向(向下)。
/// 矩形贴底边的对侧两角(外侧)做直线倒角(沿两邻边各截一段,非圆角),尺寸见 <see cref="ChamferAlongEdgeMm"/>。
/// 上侧与下侧矩形分两个独立草图绘制(同一面各 InsertSketch 一次)。
/// 每个草图绘制完成后在退出编辑前做凸台拉伸:成形到面(<see cref="swEndConditions_e.swEndCondUpToSurface"/>),
/// 在零件实体上找与草图所在面平行、且与草图面垂直距离最小(大于容差)的平面作为终止面;顶边为单段闭合轮廓以便拉伸(非薄壁)。
/// 草图坐标与 ModelToSketchTransform 一致时为米;<see cref="SketchManager.CreateLine"/> 前将端点换为毫米量化(见 <see cref="SketchCreateLineMmDecimalPlaces"/>),
/// 闭合轮廓采用链式端点且末段回到首点坐标,避免首尾缝隙。
/// 矩形底边约束到对应最短模型边(共线 + 端点落在边上 + 相对边端点距离 + 底边长度 10mm + 首条竖边垂直),
/// 方管伸长/改尺寸后草图与凸台随边移动,避免「白拉伸」。
/// </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}");
}
}
}
/// <summary>关闭「输入尺寸值」弹窗,批量 AddDimension2 后改 SystemValue 无需每次手动确认(与 benddim 一致)。</summary>
static void PushInputDimValOff(ISldWorks swApp, out bool prev, out bool applied)
{
prev = false;
applied = false;
if (swApp == null)
return;
try
{
prev = swApp.GetUserPreferenceToggle((int)swUserPreferenceToggle_e.swInputDimValOnCreate);
swApp.SetUserPreferenceToggle((int)swUserPreferenceToggle_e.swInputDimValOnCreate, false);
applied = true;
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} PushInputDimValOff: {ex.Message}");
}
}
static void PopInputDimValOff(ISldWorks swApp, bool prev, bool applied)
{
if (!applied || swApp == null)
return;
try
{
swApp.SetUserPreferenceToggle((int)swUserPreferenceToggle_e.swInputDimValOnCreate, prev);
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} PopInputDimValOff: {ex.Message}");
}
}
const double SegmentLengthMm = 10.0;
/// <summary>参与「最短两边」筛选:仅直线边,且几何长度不小于该值(mm)。</summary>
const double MinStraightEdgeLengthMm = 20.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;
}
RunOnPartFace(swApp, swModel, targetFace);
}
/// <summary>
/// 装配体中选中零件上的面时调用:解析 <see cref="Component2"/> 与 <see cref="Component2.GetCorresponding"/> 得到零件文档中的面,
/// 激活零件窗口后与当前活动文档指针对齐,再执行与 <see cref="run"/> 相同流程;草图选面在 <see cref="TryDrawHalfSketch"/> 内可射线重选。
/// </summary>
public static void run_from_assembly(ISldWorks swApp, ModelDoc2 asmDoc)
{
Log("开始(装配体):最短两边矩形 — 将选中面映射到零件文档后执行");
if (swApp == null || asmDoc == null)
{
Log("中止:swApp 或 asmDoc 为空");
return;
}
if (asmDoc.GetType() != (int)swDocumentTypes_e.swDocASSEMBLY)
{
Log("中止:当前不是装配体文档");
swApp.SendMsgToUser("请在装配体文档中使用本命令。");
return;
}
var selMgr = (SelectionMgr)asmDoc.SelectionManager;
if (selMgr == null)
{
Log("中止:SelectionMgr 为空");
swApp.SendMsgToUser("无法获取选择管理器。");
return;
}
Face2? asmFace = null;
Component2? comp = null;
int n = selMgr.GetSelectedObjectCount2(-1);
for (int i = 1; i <= n; i++)
{
int t = selMgr.GetSelectedObjectType3(i, -1);
if (t == (int)swSelectType_e.swSelFACES)
{
asmFace = selMgr.GetSelectedObject6(i, -1) as Face2;
comp = (Component2)selMgr.GetSelectedObjectsComponent3(i, -1);
break;
}
}
if (asmFace == null)
{
Log($"中止:选中项中无面(当前共 {n} 个选中对象)");
swApp.SendMsgToUser("请在装配体中选中零件上的一个面。");
return;
}
if (comp == null)
{
Log("中止:无组件上下文");
swApp.SendMsgToUser("无法取得该面所属的零件实例,请直接选中零件上的面。");
return;
}
ModelDoc2? partDoc = TryEnsurePartDocFromComponent(swApp, comp);
if (partDoc == null || partDoc.GetType() != (int)swDocumentTypes_e.swDocPART)
{
Log("中止:无法打开零件文档(组件可能未解析)");
swApp.SendMsgToUser("无法打开该面所属的零件文档,请确认零件已加载。");
return;
}
Face2? partFace = null;
try
{
object? corrObj = comp.GetCorresponding((Entity)asmFace);
partFace = corrObj as Face2;
}
catch (Exception ex)
{
Log($"GetCorresponding 异常: {ex.Message}");
}
if (partFace == null)
{
Log("中止:GetCorresponding 未返回 Face2");
swApp.SendMsgToUser("无法将装配体中的面映射到零件文档(GetCorresponding)。");
return;
}
int activateErr = 0;
string partTitle = partDoc.GetTitle() ?? "";
if (!string.IsNullOrEmpty(partTitle))
{
swApp.ActivateDoc3(
partTitle,
true,
(int)swRebuildOnActivation_e.swDontRebuildActiveDoc,
ref activateErr);
}
partDoc = ResolveSketchTargetDoc(swApp, partDoc);
RunOnPartFace(swApp, partDoc, partFace);
}
static ModelDoc2? TryEnsurePartDocFromComponent(ISldWorks swApp, Component2 comp)
{
if (comp == null || swApp == null)
return null;
ModelDoc2? partDoc = comp.GetModelDoc2() as ModelDoc2;
if (partDoc == null)
{
string path = comp.GetPathName() ?? "";
if (!string.IsNullOrEmpty(path))
{
try
{
int errors = 0;
int warnings = 0;
partDoc = swApp.OpenDoc6(
path,
(int)swDocumentTypes_e.swDocPART,
(int)swOpenDocOptions_e.swOpenDocOptions_Silent,
"",
ref errors,
ref warnings) as ModelDoc2;
}
catch (Exception ex)
{
Log($"OpenDoc6 零件失败: {ex.Message}");
}
}
}
if (partDoc == null)
return null;
try
{
partDoc.Visible = true;
}
catch (Exception ex)
{
Log($"设置零件 Visible: {ex.Message}");
}
return partDoc;
}
/// <summary>在零件文档坐标系下对给定面执行最短边矩形草图与拉伸(与 <see cref="run"/> 核心逻辑相同)。</summary>
public static void RunOnPartFace(ISldWorks swApp, ModelDoc2 partModel, Face2 targetFace)
{
if (swApp == null || partModel == null || targetFace == null)
{
Log("RunOnPartFace:参数为空");
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;
}
double minLenM = MinStraightEdgeLengthMm / 1000.0;
var edgesWithLen = new List<(Edge edge, double len)>();
foreach (object o in edgeObjs)
{
if (o is not Edge e)
continue;
Curve? crv = null;
try
{
crv = e.GetCurve() as Curve;
}
catch
{
continue;
}
if (crv == null || !crv.IsLine())
continue;
double len = GetEdgeLengthMeters(e);
if (len + 1e-12 < minLenM)
continue;
edgesWithLen.Add((e, len));
}
if (edgesWithLen.Count < 2)
{
Log($"中止:直线边且长度≥{MinStraightEdgeLengthMm}mm 的边不足 2 条");
swApp.SendMsgToUser(
$"该面上符合条件的直线边少于 2 条(须为直线且长度不小于 {MinStraightEdgeLengthMm} mm)。");
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, partModel, targetFace, edgeA, edgeB, segA0, segA1, segB0, segB1,
upperHalf: true, out modeUpper);
bool drewLower = TryDrawHalfSketch(
swApp, partModel, targetFace, edgeA, edgeB, 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>
/// 装配体中 ActivateDoc3 后,传入的 <see cref="ModelDoc2"/> 与当前活动文档可能不是同一 COM 包装;
/// 草图 API 必须以 <see cref="ISldWorks.ActiveDoc"/> 为准,否则 <c>InsertSketch(true)</c> 后 <c>ActiveSketch</c> 仍为空。
/// </summary>
static ModelDoc2 ResolveSketchTargetDoc(ISldWorks swApp, ModelDoc2 requested)
{
if (requested == null || swApp == null)
return requested;
ModelDoc2? active = swApp.ActiveDoc as ModelDoc2;
if (active == null)
return requested;
string rqPath = requested.GetPathName()?.Trim() ?? "";
string acPath = active.GetPathName()?.Trim() ?? "";
if (!string.IsNullOrEmpty(rqPath) && !string.IsNullOrEmpty(acPath) &&
string.Equals(rqPath, acPath, StringComparison.OrdinalIgnoreCase))
return active;
string rqTitle = requested.GetTitle()?.Trim() ?? "";
string acTitle = active.GetTitle()?.Trim() ?? "";
if (!string.IsNullOrEmpty(rqTitle) && !string.IsNullOrEmpty(acTitle) &&
string.Equals(rqTitle, acTitle, StringComparison.OrdinalIgnoreCase))
return active;
return requested;
}
/// <summary>为新建草图选中承载面:先试 <see cref="Entity.Select4"/>,失败则用射线(装配体映射面激活零件后更可靠)。</summary>
static bool TrySelectSketchHostFace(ModelDoc2 doc, Face2 face, string tag)
{
doc.ClearSelection2(true);
try
{
if (((Entity)face).Select4(false, null))
return true;
}
catch (Exception ex)
{
Log($"{tag}:Select4 异常: {ex.Message}");
}
return TrySelectSketchHostFaceByRay(doc, face, tag);
}
static bool TrySelectSketchHostFaceByRay(ModelDoc2 doc, Face2 face, string tag)
{
if (!TryGetFaceRepresentativePoint(face, out double[] p) || p.Length < 3)
{
Log($"{tag}:射线选面失败(无面上点)");
return false;
}
if (!TryGetPlanarUnitNormal(face, out double[] n) || n.Length < 3)
{
Log($"{tag}:射线选面失败(非平面或无平面法向)");
return false;
}
const double eps = 2e-4;
double ox = p[0] + n[0] * eps;
double oy = p[1] + n[1] * eps;
double oz = p[2] + n[2] * eps;
try
{
bool ok = doc.Extension.SelectByRay(ox, oy, oz, -n[0], -n[1], -n[2], 0.0001, 1, false, 0, 0);
if (!ok)
Log($"{tag}:SelectByRay 返回 false(面上点 {Vec3Fmt(p)})");
return ok;
}
catch (Exception ex)
{
Log($"{tag}:SelectByRay 异常: {ex.Message}");
return false;
}
}
/// <summary>在同一面上打开新草图,只画上侧或下侧一个矩形(默认不画最短边构造参考线);绘制成功后尝试成形到「最近平行面」。</summary>
static bool TryDrawHalfSketch(
ISldWorks swApp,
ModelDoc2 swModel,
Face2 sketchHostFace,
Edge edgeA,
Edge edgeB,
double[] segA0,
double[] segA1,
double[] segB0,
double[] segB1,
bool upperHalf,
out string modeUsed)
{
modeUsed = "";
string tag = upperHalf ? "上侧矩形草图" : "下侧矩形草图";
ModelDoc2 doc = ResolveSketchTargetDoc(swApp, swModel);
if (!ReferenceEquals(doc, swModel))
Log($"{tag}:使用 ActiveDoc 作为草图文档(与传入 ModelDoc 对齐)");
// 仅在确有活动草图时退出;无草图时调用 InsertSketch(false) 部分 SolidWorks 版本会破坏后续 InsertSketch(true),导致 ActiveSketch 一直为空
try
{
if (doc.SketchManager.ActiveSketch != null)
doc.SketchManager.InsertSketch(false);
}
catch { /* ignore */ }
bool sketchEntered = false;
for (int attempt = 0; attempt < 2 && !sketchEntered; attempt++)
{
bool selOk = attempt == 0
? TrySelectSketchHostFace(doc, sketchHostFace, tag)
: TrySelectSketchHostFaceByRay(doc, sketchHostFace, tag);
if (!selOk)
{
Log($"{tag}:选面失败(尝试 {attempt + 1}/2)");
if (attempt == 1)
return false;
continue;
}
try
{
doc.SketchManager.InsertSketch(true);
}
catch (Exception ex)
{
Log($"{tag}:InsertSketch 异常: {ex.Message}");
return false;
}
if (doc.SketchManager.ActiveSketch != null)
{
sketchEntered = true;
break;
}
Log($"{tag}:InsertSketch 后 ActiveSketch 为空(尝试 {attempt + 1}/2),将改用射线重选面");
try
{
if (doc.SketchManager.ActiveSketch != null)
doc.SketchManager.InsertSketch(false);
}
catch { /* ignore */ }
}
if (!sketchEntered || doc.SketchManager.ActiveSketch == null)
{
Log($"{tag}:无法进入草图(含射线重选后仍失败)");
return false;
}
var math = swApp.IGetMathUtility();
if (math == null)
{
Log($"{tag}:MathUtility 为空");
try { doc.SketchManager.InsertSketch(false); } catch { /* ignore */ }
return false;
}
if (!TryComputeSketchRects(
math, doc, 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 { doc.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;
Edge anchorEdge = upperHalf
? (aIsHigher ? edgeA : edgeB)
: (aIsHigher ? edgeB : edgeA);
double[] anchorModelSeg0 = upperHalf
? (aIsHigher ? segA0 : segB0)
: (aIsHigher ? segB0 : segA0);
bool ok = false;
string skFeatName = "";
try
{
if (TryDrawSingleSketchGeometry(
swApp, doc, sketchHostFace, r0, r1, q0, q1, q2, q3,
anchorEdge, anchorModelSeg0,
drawRefSegment: false, out string mode))
{
modeUsed = mode;
ok = true;
// 在退出草图前先获取草图特征名
if (!TryGetActiveSketchFeatureName(doc, out skFeatName))
{
Log($"{tag}:无法取得草图特征名,跳过拉伸");
// 即使获取失败也要退出草图
try
{
doc.SketchManager.InsertSketch(false);
}
catch { }
}
else
{
Log($"{tag}:获取到草图特征名: {skFeatName}");
// 获取代表点(在退出草图前)
if (!TryGetFaceRepresentativePoint(sketchHostFace, out double[] pickOnHostFace))
{
Log($"{tag}:无法取草图所在面代表点,跳过拉伸");
try
{
doc.SketchManager.InsertSketch(false);
}
catch { }
}
else
{
// 退出草图
try
{
doc.SketchManager.InsertSketch(false);
}
catch (Exception ex)
{
Log($"{tag}:退出草图 InsertSketch(false): {ex.Message}");
}
TryEditRebuildAfterSketch(doc, tag);
if (TryFeatureExtrudeUpToNearestParallelFace(
doc, sketchHostFace, skFeatName, pickOnHostFace, tag))
modeUsed += ";已拉伸(成形到最近平行面)";
else
Log($"{tag}:成形到面拉伸失败");
}
}
}
else
Log($"{tag}:CreateLine 全部策略失败");
}
catch (Exception ex)
{
Log($"{tag}:CreateLine 异常: {ex.Message}");
}
finally
{
try
{
if (doc.SketchManager.ActiveSketch != null)
doc.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;
return TrySketchAddConstraint(swModel, "sgCOINCIDENT", a, b);
}
/// <summary>COM 对象未必实现 <see cref="Entity"/>,用反射调用 Select4(与宏里直接选 SKETCHSEGMENT 等价)。</summary>
static bool TryComSelect4(object? comObj, bool append)
{
if (comObj == null)
return false;
try
{
if (comObj is Entity ent)
return ent.Select4(append, null);
}
catch { /* fall through */ }
try
{
object? result = comObj.GetType().InvokeMember(
"Select4",
BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance,
null,
comObj,
new object[] { append, null });
return result is bool b && b;
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TryComSelect4: {ex.Message}");
return false;
}
}
static string? TryGetComString(object comObj, string memberName)
{
try
{
if (memberName == "GetName")
{
object? v = comObj.GetType().InvokeMember(
"GetName",
BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance,
null,
comObj,
Array.Empty<object>());
return v as string;
}
object? p = comObj.GetType().InvokeMember(
memberName,
BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance,
null,
comObj,
null);
return p as string;
}
catch
{
return null;
}
}
static bool TrySelectSketchSegment(ModelDoc2 swModel, SketchSegment seg, bool append)
{
if (TryComSelect4(seg, append))
return true;
string? name = TryGetComString(seg, "GetName");
if (string.IsNullOrEmpty(name))
return false;
double x = 0, y = 0, z = 0;
SketchPoint? sp = GetSegmentStartPoint(seg);
if (sp != null)
{
x = sp.X;
y = sp.Y;
z = sp.Z;
}
try
{
return swModel.Extension.SelectByID2(name, "SKETCHSEGMENT", x, y, z, append, 0, null, 0);
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TrySelectSketchSegment SelectByID2: {ex.Message}");
return false;
}
}
/// <summary>草图编辑中选模型边:与宏一致,草图线 SelectByID2 + 边 SelectByRay。</summary>
static bool TrySelectModelEdgeByRay(
ModelDoc2 swModel,
Edge edge,
Face2? hostFace,
bool append)
{
if (!TryGetEdgeVerticesModel(edge, out double[]? v0, out double[]? v1) ||
v0 == null || v1 == null)
return false;
double[] mid = Mid3(v0, v1);
double[] axis = Normalize3(Sub3(v1, v0));
double[] ray = { 1, 0, 0 };
if (hostFace != null && TryGetPlanarUnitNormal(hostFace, out double[] n) && n.Length >= 3)
ray = n;
else if (Math.Abs(axis[0]) < 0.9)
ray = Normalize3(Cross3(axis, new double[] { 1, 0, 0 }));
else
ray = Normalize3(Cross3(axis, new double[] { 0, 1, 0 }));
const double eps = 2e-4;
double ox = mid[0] + ray[0] * eps;
double oy = mid[1] + ray[1] * eps;
double oz = mid[2] + ray[2] * eps;
try
{
return swModel.Extension.SelectByRay(
ox, oy, oz, -ray[0], -ray[1], -ray[2], 0.0001, 1, append, 0, 0);
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TrySelectModelEdgeByRay: {ex.Message}");
return false;
}
}
static double[] Cross3(double[] a, double[] b) =>
new[]
{
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
};
static bool TrySketchAddConstraint(ModelDoc2 swModel, string constraintId, params object?[] items)
{
if (items == null || items.Length == 0)
return false;
try
{
swModel.ClearSelection2(true);
for (int i = 0; i < items.Length; i++)
{
if (items[i] == null)
return false;
bool append = i > 0;
bool picked = items[i] switch
{
SketchSegment seg => TrySelectSketchSegment(swModel, seg, append),
Edge edge => TrySelectModelEdgeByRay(swModel, edge, null, append),
_ => TryComSelect4(items[i], append),
};
if (!picked)
return false;
}
swModel.SketchAddConstraints(constraintId);
return true;
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TrySketchAddConstraint {constraintId}: {ex.Message}");
return false;
}
}
static bool TrySketchColinearSegmentToEdge(
ModelDoc2 swModel,
Face2? hostFace,
SketchSegment sketchSeg,
Edge modelEdge)
{
try
{
swModel.ClearSelection2(true);
if (!TrySelectSketchSegment(swModel, sketchSeg, false))
return false;
if (!TrySelectModelEdgeByRay(swModel, modelEdge, hostFace, append: true))
return false;
swModel.SketchAddConstraints("sgCOLINEAR");
return true;
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TrySketchColinearSegmentToEdge: {ex.Message}");
return false;
}
}
/// <summary>
/// 将矩形底边钉在对应最短模型边上,并标 10×3 mm,使方管改长后槽位随边移动、凸台仍有效。
/// </summary>
static void TrySketchConstrainRectToHostEdge(
ISldWorks swApp,
ModelDoc2 swModel,
Face2? sketchHostFace,
Edge anchorEdge,
double[] anchorSeg0Model,
IReadOnlyList<(SketchSegment? seg, SketchPoint? s, SketchPoint? e)> contourChain,
SketchSegment? bottomSeg,
SketchPoint? ptC0,
SketchPoint? ptC1,
SketchSegment? firstVerticalSeg)
{
if (anchorEdge == null || bottomSeg == null || ptC0 == null || ptC1 == null)
return;
int contourSegmentCount = contourChain.Count;
int topIdx = GetRectTopSegmentIndex(contourSegmentCount);
SketchSegment? topSeg =
topIdx >= 0 && topIdx < contourChain.Count ? contourChain[topIdx].seg : null;
PushInputDimValOff(swApp, out bool prevInputDim, out bool inputDimOff);
bool prevAutoSolve = false;
bool gotAutoSolve = false;
try
{
prevAutoSolve = swModel.SketchManager.AutoSolve;
gotAutoSolve = true;
swModel.SketchManager.AutoSolve = true;
}
catch { /* ignore */ }
int ok = 0;
int tried = 0;
tried++;
if (TrySketchColinearSegmentToEdge(swModel, sketchHostFace, bottomSeg, anchorEdge))
ok++;
tried++;
if (TrySketchOnEdge(swModel, sketchHostFace, ptC0, anchorEdge))
ok++;
else
{
tried++;
if (TrySketchOnEdge(swModel, sketchHostFace, ptC0, anchorEdge, usePierce: true))
ok++;
}
tried++;
if (TrySketchOnEdge(swModel, sketchHostFace, ptC1, anchorEdge))
ok++;
else
{
tried++;
if (TrySketchOnEdge(swModel, sketchHostFace, ptC1, anchorEdge, usePierce: true))
ok++;
}
if (firstVerticalSeg != null)
{
tried++;
if (TrySketchAddConstraint(swModel, "sgPERPENDICULAR", bottomSeg, firstVerticalSeg))
ok++;
}
if (TryGetEdgeVerticesModel(anchorEdge, out double[]? ev0, out double[]? ev1) &&
ev0 != null && ev1 != null &&
anchorSeg0Model != null && anchorSeg0Model.Length >= 3)
{
Vertex? vStart = PickEdgeVertexCloserToModelPoint(anchorEdge, ev0, ev1, anchorSeg0Model);
if (vStart != null)
{
var edgeStartVtx = anchorEdge.GetStartVertex() as Vertex;
bool fromStart = ComObjectIdentityEquals(vStart, edgeStartVtx);
double distFromStartM = DistAlongEdgeFromVertexM(
fromStart ? ev0 : ev1,
fromStart ? ev1 : ev0,
anchorSeg0Model);
tried++;
if (TrySketchSetDistanceBetweenPointAndVertex(swModel, ptC0, vStart, distFromStartM))
ok++;
}
}
tried++;
if (TrySketchSetSegmentLength(swModel, bottomSeg, SegmentLengthMm / 1000.0))
ok++;
if (topSeg != null)
{
tried++;
if (TrySketchAddConstraint(swModel, "sgPARALLEL", bottomSeg, topSeg))
ok++;
tried++;
if (TrySketchSetDistanceBetweenTwoSegments(
swModel, bottomSeg, topSeg, RectLengthMm / 1000.0))
ok++;
}
else
{
Log($"矩形尺寸:无顶边线段(轮廓 {contourSegmentCount} 段),跳过底—顶 3mm 线间距");
}
int okContour = TrySketchConstrainFullContour(swModel, contourChain);
ok += okContour;
Log(
$"矩形钉边约束:成功 {ok}/{tried}+轮廓{okContour} " +
$"(共线+边上点+竖直+距端点+底边10mm+底顶3mm+其余线段尺寸/相等)");
if (gotAutoSolve)
{
try
{
swModel.SketchManager.AutoSolve = prevAutoSolve;
}
catch { /* ignore */ }
}
PopInputDimValOff(swApp, prevInputDim, inputDimOff);
}
/// <summary>倒角/直角轮廓其余线段用相等+垂直+长度尺寸一次标全,避免欠约束蓝线。</summary>
static int TrySketchConstrainFullContour(
ModelDoc2 swModel,
IReadOnlyList<(SketchSegment? seg, SketchPoint? s, SketchPoint? e)> chain)
{
if (chain.Count == 6)
return TrySketchConstrainChamferContourSix(swModel, chain);
if (chain.Count == 4)
return TrySketchConstrainRectContourFour(swModel, chain);
return 0;
}
static int TrySketchConstrainChamferContourSix(
ModelDoc2 swModel,
IReadOnlyList<(SketchSegment? seg, SketchPoint? s, SketchPoint? e)> chain)
{
SketchSegment? bottom = chain[0].seg;
SketchSegment? rightVert = chain[1].seg;
SketchSegment? chamferA = chain[2].seg;
SketchSegment? top = chain[3].seg;
SketchSegment? chamferB = chain[4].seg;
SketchSegment? leftVert = chain[5].seg;
if (bottom == null || rightVert == null || chamferA == null || top == null ||
chamferB == null || leftVert == null)
return 0;
int ok = 0;
if (TrySketchAddConstraint(swModel, "sgPERPENDICULAR", bottom, rightVert))
ok++;
if (TrySketchAddConstraint(swModel, "sgPERPENDICULAR", bottom, leftVert))
ok++;
if (TrySketchAddConstraint(swModel, "sgPARALLEL", rightVert, leftVert))
ok++;
if (TrySketchAddConstraint(swModel, "sgPARALLEL", top, bottom))
ok++;
// 两外侧倒角斜边为镜面对称(走向相反),不可 sgPARALLEL,否则会扭坏轮廓
double vertLegM = (RectLengthMm - ChamferAlongEdgeMm) / 1000.0;
double chamferLenM = Math.Sqrt(2.0) * ChamferAlongEdgeMm / 1000.0;
double topLenM = (SegmentLengthMm - 2.0 * ChamferAlongEdgeMm) / 1000.0;
double bottomLenM = SegmentLengthMm / 1000.0;
// 六条线段全部标长度(底边若已标过会多一次,SolidWorks 通常可共存或忽略)
if (TrySketchSetSegmentLength(swModel, bottom, bottomLenM))
ok++;
if (TrySketchSetSegmentLength(swModel, rightVert, vertLegM))
ok++;
if (TrySketchSetSegmentLength(swModel, chamferA, chamferLenM))
ok++;
if (TrySketchSetSegmentLength(swModel, top, topLenM))
ok++;
if (TrySketchSetSegmentLength(swModel, chamferB, chamferLenM))
ok++;
if (TrySketchSetSegmentLength(swModel, leftVert, vertLegM))
ok++;
// 45° 倒角:四角夹角(先试 135° 外角,失败再试 45°)
if (TrySketchSetChamferCornerAngle(swModel, rightVert, chamferA))
ok++;
if (TrySketchSetChamferCornerAngle(swModel, chamferA, top))
ok++;
if (TrySketchSetChamferCornerAngle(swModel, top, chamferB))
ok++;
if (TrySketchSetChamferCornerAngle(swModel, chamferB, leftVert))
ok++;
return ok;
}
static bool TrySketchSetChamferCornerAngle(
ModelDoc2 swModel,
SketchSegment segA,
SketchSegment segB)
{
const double rad135 = 135.0 * Math.PI / 180.0;
const double rad45 = 45.0 * Math.PI / 180.0;
if (TrySketchSetAngleBetweenSegments(swModel, segA, segB, rad135))
return true;
return TrySketchSetAngleBetweenSegments(swModel, segA, segB, rad45);
}
static int TrySketchConstrainRectContourFour(
ModelDoc2 swModel,
IReadOnlyList<(SketchSegment? seg, SketchPoint? s, SketchPoint? e)> chain)
{
SketchSegment? bottom = chain[0].seg;
SketchSegment? right = chain[1].seg;
SketchSegment? top = chain[2].seg;
SketchSegment? left = chain[3].seg;
if (bottom == null || right == null || top == null || left == null)
return 0;
int ok = 0;
if (TrySketchAddConstraint(swModel, "sgPERPENDICULAR", bottom, right))
ok++;
if (TrySketchAddConstraint(swModel, "sgPERPENDICULAR", bottom, left))
ok++;
if (TrySketchAddConstraint(swModel, "sgPARALLEL", top, bottom))
ok++;
if (TrySketchAddConstraint(swModel, "sgPARALLEL", right, left))
ok++;
double heightM = RectLengthMm / 1000.0;
double widthM = SegmentLengthMm / 1000.0;
if (TrySketchSetSegmentLength(swModel, bottom, widthM))
ok++;
if (TrySketchSetSegmentLength(swModel, right, heightM))
ok++;
if (TrySketchSetSegmentLength(swModel, top, widthM))
ok++;
if (TrySketchSetSegmentLength(swModel, left, heightM))
ok++;
if (TrySketchSetDistanceBetweenTwoSegments(swModel, bottom, top, heightM))
ok++;
return ok;
}
static bool TrySketchOnEdge(
ModelDoc2 swModel,
Face2? hostFace,
SketchPoint pt,
Edge edge,
bool usePierce = false)
{
string id = usePierce ? "sgPIERCE" : "sgONEDGE";
try
{
swModel.ClearSelection2(true);
if (!TryComSelect4(pt, false))
return false;
if (!TrySelectModelEdgeByRay(swModel, edge, hostFace, append: true))
return false;
swModel.SketchAddConstraints(id);
return true;
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TrySketchOnEdge {id}: {ex.Message}");
return false;
}
}
static Vertex? PickEdgeVertexCloserToModelPoint(Edge edge, double[] v0, double[] v1, double[] modelPt)
{
try
{
var vs = edge.GetStartVertex() as Vertex;
var ve = edge.GetEndVertex() as Vertex;
if (vs == null || ve == null)
return null;
double d0 = Len3(Sub3(modelPt, v0));
double d1 = Len3(Sub3(modelPt, v1));
return d0 <= d1 ? vs : ve;
}
catch
{
return null;
}
}
static double DistAlongEdgeFromVertexM(double[] edgeStart, double[] edgeEnd, double[] pt)
{
double[] axis = Normalize3(Sub3(edgeEnd, edgeStart));
return Math.Max(0, Dot3(Sub3(pt, edgeStart), axis));
}
static bool TrySketchSetDistanceBetweenPointAndVertex(
ModelDoc2 swModel,
SketchPoint pt,
Vertex vertex,
double distM)
{
try
{
swModel.ClearSelection2(true);
if (!TryComSelect4(pt, false))
return false;
if (!TryComSelect4(vertex, true))
return false;
return TryApplyNewDimensionValue(swModel, distM);
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TrySketchSetDistancePointVertex: {ex.Message}");
return false;
}
}
static bool TrySketchSetDistanceBetweenPoints(ModelDoc2 swModel, SketchPoint a, SketchPoint b, double distM)
{
try
{
swModel.ClearSelection2(true);
if (!TryComSelect4(a, false))
return false;
if (!TryComSelect4(b, true))
return false;
TryGetSketchPointsPlacement(a, b, out double px, out double py, out double pz);
return TryApplyNewDimensionValue(swModel, distM, px, py, pz);
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TrySketchSetDistancePoints: {ex.Message}");
return false;
}
}
/// <summary>与宏一致:先选两条 SKETCHSEGMENT,再 AddDimension2 得线间距(如底边—顶边 3mm)。</summary>
static bool TrySketchSetDistanceBetweenTwoSegments(
ModelDoc2 swModel,
SketchSegment segA,
SketchSegment segB,
double distM)
{
try
{
swModel.ClearSelection2(true);
if (!TrySelectSketchSegment(swModel, segA, false))
return false;
if (!TrySelectSketchSegment(swModel, segB, true))
return false;
TryGetSketchSegmentsPlacement(segA, segB, out double px, out double py, out double pz);
return TryApplyNewDimensionValue(swModel, distM, px, py, pz);
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TrySketchSetDistanceBetweenTwoSegments: {ex.Message}");
return false;
}
}
/// <summary>两草图线夹角(弧度),用于 45° 倒角处 135° 外角。</summary>
static bool TrySketchSetAngleBetweenSegments(
ModelDoc2 swModel,
SketchSegment segA,
SketchSegment segB,
double angleRad)
{
try
{
swModel.ClearSelection2(true);
if (!TrySelectSketchSegment(swModel, segA, false))
return false;
if (!TrySelectSketchSegment(swModel, segB, true))
return false;
TryGetSketchSegmentsPlacement(segA, segB, out double px, out double py, out double pz);
return TryApplyNewDimensionValue(swModel, angleRad, px, py, pz);
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TrySketchSetAngleBetweenSegments: {ex.Message}");
return false;
}
}
/// <summary>单条草图线长度尺寸(底边 10mm)。</summary>
static bool TrySketchSetSegmentLength(ModelDoc2 swModel, SketchSegment seg, double lengthM)
{
try
{
swModel.ClearSelection2(true);
if (!TrySelectSketchSegment(swModel, seg, false))
return false;
TryGetSketchSegmentMidpoint(seg, out double px, out double py, out double pz);
return TryApplyNewDimensionValue(swModel, lengthM, px, py, pz);
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TrySketchSetSegmentLength: {ex.Message}");
return false;
}
}
static void TryGetSketchSegmentMidpoint(SketchSegment seg, out double x, out double y, out double z)
{
x = y = z = 0;
SketchPoint? s = GetSegmentStartPoint(seg);
SketchPoint? e = GetSegmentEndPoint(seg);
if (s == null || e == null)
return;
x = (s.X + e.X) * 0.5;
y = (s.Y + e.Y) * 0.5;
z = (s.Z + e.Z) * 0.5;
}
static void TryGetSketchPointsPlacement(SketchPoint a, SketchPoint b, out double x, out double y, out double z)
{
x = (a.X + b.X) * 0.5;
y = (a.Y + b.Y) * 0.5;
z = (a.Z + b.Z) * 0.5;
}
static void TryGetSketchSegmentsPlacement(
SketchSegment segA,
SketchSegment segB,
out double x,
out double y,
out double z)
{
TryGetSketchSegmentMidpoint(segA, out double ax, out double ay, out double az);
TryGetSketchSegmentMidpoint(segB, out double bx, out double by, out double bz);
x = (ax + bx) * 0.5;
y = (ay + by) * 0.5;
z = (az + bz) * 0.5;
}
/// <summary>倒角轮廓 6 段时顶边为 #3,直角矩形 4 段时顶边为 #2。</summary>
static int GetRectTopSegmentIndex(int segmentCount) =>
segmentCount switch
{
6 => 3,
4 => 2,
_ => -1,
};
static bool TryApplyNewDimensionValue(
ModelDoc2 swModel,
double valueM,
double? placeX = null,
double? placeY = null,
double? placeZ = null)
{
try
{
double px = placeX ?? 0, py = placeY ?? 0, pz = placeZ ?? 0;
if (!placeX.HasValue)
{
var sp = (SketchPoint?)((SelectionMgr)swModel.SelectionManager).GetSelectedObject6(1, -1);
if (sp != null)
{
px = sp.X;
py = sp.Y;
pz = sp.Z;
}
}
object? dimOb = swModel.AddDimension2(px, py, pz);
if (dimOb is not DisplayDimension disp)
return false;
var dim = (Dimension)disp.GetDimension();
dim.SystemValue = valueM;
return true;
}
catch (Exception ex)
{
Debug.WriteLine($"{LogPrefix} TryApplyNewDimensionValue: {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,
Face2? sketchHostFace,
double[] ref0,
double[] ref1,
double[] c0,
double[] c1,
double[] c2,
double[] c3,
Edge? anchorEdge,
double[] anchorModelSeg0,
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, sketchHostFace, ref0, ref1, c0, c1, c2, c3,
anchorEdge, anchorModelSeg0,
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 SketchSegment? segment,
out SketchPoint? startPt,
out SketchPoint? endPt)
{
segment = null;
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)
{
segment = 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,
Face2? sketchHostFace,
double[] ra0,
double[] ra1,
double[] c0,
double[] c1,
double[] c2,
double[] c3,
Edge? anchorEdge,
double[] anchorModelSeg0,
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<(SketchSegment? seg, 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 SketchSegment? lineSeg, out SketchPoint? sp, out SketchPoint? ep))
return false;
contourChain.Add((lineSeg, 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} 处成功(首尾闭合处含于最后一项)");
}
if (anchorEdge != null && contourChain.Count > 0)
{
SketchSegment? bottomSeg = contourChain[0].seg;
SketchPoint? ptC0 = contourChain[0].s;
SketchPoint? ptC1 = contourChain[0].e;
SketchSegment? vertSeg = contourChain.Count > 1 ? contourChain[1].seg : null;
try
{
TrySketchConstrainRectToHostEdge(
swApp,
swModel,
sketchHostFace,
anchorEdge,
anchorModelSeg0 ?? Array.Empty<double>(),
contourChain,
bottomSeg,
ptC0,
ptC1,
vertSeg);
}
catch (Exception ex)
{
Log($"矩形钉边约束异常(草图线段已生成): {ex.Message}");
}
}
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;
}
}
}
更多推荐



所有评论(0)