using System;
using System.Collections.Generic;
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;
using View = SolidWorks.Interop.sldworks.View;
namespace tools
{
/// <summary>
/// 选中工程图视图:在模型 3D 坐标中检查圆孔/圆弧圆心到折弯中心线距离是否 > 4×板厚 + 圆半径。
/// 折弯中心线取折弯前(折叠配置)OneBend 圆柱轴线,不用平板型式草图坐标。
/// <see cref="View.GetBendLines"/> 仅作对照日志。
/// </summary>
public static class selected_view_arc_bend_clearance
{
const double MinClearanceThicknessFactor = 4.0;
const double MinCircleEdgeParamSpanRad = 0.08;
const double BendLineCoincidentTolM = 0.00005;
const double CircleDedupeCenterTolM = 0.00035;
const double CircleDedupeRadiusRelTol = 0.004;
const double NearFullCircleParamSpanRad = 5.5;
const int MaxSubFeatureWalkSteps = 100_000;
sealed class ModelSegment3D
{
public double[] P0 = Array.Empty<double>();
public double[] P1 = Array.Empty<double>();
}
sealed class BendRefInfo
{
public string FullName = string.Empty;
public double[] AxisCenter = Array.Empty<double>();
public double[] AxisDir = Array.Empty<double>();
public double[] CenterLineP0 = Array.Empty<double>();
public double[] CenterLineP1 = Array.Empty<double>();
public double RadiusM;
public bool HasAxis;
public bool HasCenterLineSegment;
}
sealed class CircleArcItem
{
public double RadiusM;
public double ParamSpanRad;
public string Kind = string.Empty;
public double[] CenterModel = Array.Empty<double>();
public double[] AxisModel = Array.Empty<double>();
}
public static void run(SldWorks swApp, ModelDoc2 swModel)
{
try
{
if (swModel == null)
{
Console.WriteLine("[arc_bend_clearance] 错误:没有活动文档。");
return;
}
if (swModel.GetType() != (int)swDocumentTypes_e.swDocDRAWING)
{
swApp.SendMsgToUser("当前文档不是工程图。");
return;
}
var selMgr = (SelectionMgr)swModel.SelectionManager;
if (selMgr == null || selMgr.GetSelectedObjectType3(1, -1) != (int)swSelectType_e.swSelDRAWINGVIEWS)
{
swApp.SendMsgToUser("请先选中一个工程图视图(建议平板型式/展开视图)。");
return;
}
var view = selMgr.GetSelectedObject6(1, -1) as View;
if (view == null)
{
swApp.SendMsgToUser("无法获取选中视图。");
return;
}
runForView(swApp, swModel, view);
}
catch (Exception ex)
{
Console.WriteLine($"[arc_bend_clearance] 失败:{ex.Message}");
swApp?.SendMsgToUser($"圆弧折弯距检查失败:{ex.Message}");
}
}
public static void runForView(SldWorks swApp, ModelDoc2 swModel, View view)
{
if (view == null)
{
return;
}
var partModel = view.ReferencedDocument as ModelDoc2;
if (partModel == null || partModel.GetType() != (int)swDocumentTypes_e.swDocPART)
{
swApp.SendMsgToUser("视图未关联零件,无法读取板厚。");
return;
}
double thicknessMm = get_thickness.run(partModel);
if (thicknessMm <= 1e-6)
{
swApp.SendMsgToUser("无法读取板厚,请先确认零件钣金厚度或自定义属性。");
return;
}
Console.WriteLine(
$"[arc_bend_clearance] 视图「{view.Name}」板厚={thicknessMm:F2} mm,"
+ $"圆心距折弯要求 > 4×板厚+圆半径(4×板厚={thicknessMm * MinClearanceThicknessFactor:F2} mm)");
using (ActivateViewReferencedPartConfiguration(partModel, view))
{
var partDoc = (PartDoc)partModel;
string foldedCfg = TryResolveFoldedConfigurationName(partModel);
List<BendRefInfo> bendRefs;
using (new PartConfigurationScope(partModel, foldedCfg))
{
bendRefs = CollectNamedBendRefsFromPart(partModel, partDoc);
}
var viewBendLines = CollectBendLineSegmentsModel3D(swApp, view);
if (bendRefs.Count == 0 && viewBendLines.Count == 0)
{
swApp.SendMsgToUser(
"未读取到折弯:请确认视图引用钣金零件,且零件特征树含 OneBend,或工程图已显示折弯线。");
return;
}
LogNamedBendRefs(bendRefs);
if (viewBendLines.Count > 0)
{
Console.WriteLine(
$"[arc_bend_clearance] 工程图 GetBendLines 对照 {viewBendLines.Count} 条(模型 3D,不参与主判定)");
for (int i = 0; i < viewBendLines.Count; i++)
{
var seg = viewBendLines[i];
Console.WriteLine(
$"[arc_bend_clearance] 对照折弯线#{i + 1} "
+ FormatPointMm(seg.P0) + " → " + FormatPointMm(seg.P1));
Console.WriteLine(
$"[arc_bend_clearance] 参数式 {FormatSegmentParametricMm(seg.P0, seg.P1)}");
}
}
var distanceRefs = BuildDistanceRefs(bendRefs, viewBendLines);
var arcs = CollectCircleArcItems(swApp, view, distanceRefs);
if (arcs.Count == 0)
{
swApp.SendMsgToUser("视图中未找到圆弧或圆线段。");
return;
}
Console.WriteLine($"[arc_bend_clearance] 圆弧/圆线段 {arcs.Count} 处");
var violations = new List<string>();
foreach (var arc in arcs)
{
double centerDistM = MinDistancePointToBendRefs(
arc.CenterModel, distanceRefs, out string nearestBendName);
double holeRadiusMm = arc.RadiusM * 1000.0;
double requiredCenterDistMm =
thicknessMm * MinClearanceThicknessFactor + holeRadiusMm;
double requiredCenterDistM = requiredCenterDistMm / 1000.0;
double centerDistMm = centerDistM * 1000.0;
string bendHint = string.IsNullOrEmpty(nearestBendName)
? ""
: $"(最近折弯「{nearestBendName}」)";
Console.WriteLine($"[arc_bend_clearance] {arc.Kind} R={holeRadiusMm:F2} mm");
Console.WriteLine(
$"[arc_bend_clearance] 圆心 3D C={FormatPointMm(arc.CenterModel)}");
Console.WriteLine(
$"[arc_bend_clearance] 圆轴线 {FormatAxisParametricMm(arc.CenterModel, arc.AxisModel)}");
Console.WriteLine(
$"[arc_bend_clearance] 圆心距折弯 {centerDistMm:F2} mm{bendHint},"
+ $"要求 > {requiredCenterDistMm:F2} mm"
+ $"(4×{thicknessMm:F2}+{holeRadiusMm:F2})");
if (centerDistM <= requiredCenterDistM + 1e-9)
{
violations.Add(
$" · {arc.Kind} R={holeRadiusMm:F2} mm,距「{nearestBendName}」{centerDistMm:F2} mm"
+ $" ≤ {requiredCenterDistMm:F2} mm(4×板厚+圆R),会拉孔");
}
}
if (violations.Count > 0)
{
string preview = string.Join(
"\n",
violations.Count <= 8 ? violations : violations.GetRange(0, 8));
if (violations.Count > 8)
{
preview += $"\n... 另有 {violations.Count - 8} 处";
}
string msg =
$"板厚 {thicknessMm:F2} mm:以下圆心到折弯距离未大于 4×板厚+圆半径,会拉孔:\n"
+ preview;
Console.WriteLine(msg);
swApp.SendMsgToUser(msg);
}
else
{
swApp.SendMsgToUser(
$"检查通过:{arcs.Count} 处圆孔/圆弧圆心到最近折弯均 > 4×板厚+圆半径。");
}
}
}
/// <summary>与 benddim 一致:草图局部 → 逆 ModelToSketch → 逆 ModelToView → 模型坐标。</summary>
static List<ModelSegment3D> CollectBendLineSegmentsModel3D(SldWorks swApp, View view)
{
var segments = new List<ModelSegment3D>();
object? raw;
try
{
raw = view.GetBendLines();
}
catch (Exception ex)
{
Console.WriteLine($"[arc_bend_clearance] GetBendLines 异常:{ex.Message}");
return segments;
}
if (raw is not object[] arr || arr.Length == 0)
{
return segments;
}
Sketch? viewSketch = null;
try
{
viewSketch = (Sketch)view.GetSketch();
}
catch
{
viewSketch = null;
}
if (viewSketch == null)
{
Console.WriteLine("[arc_bend_clearance] view.GetSketch() 为空,无法将折弯线变换到模型坐标");
return segments;
}
foreach (object item in arr)
{
if (item is not SketchSegment seg || seg is not SketchLine line)
{
continue;
}
try
{
var sp0 = line.GetStartPoint2() as SketchPoint;
var sp1 = line.GetEndPoint2() as SketchPoint;
if (sp0 == null || sp1 == null)
{
continue;
}
var sk0 = new[] { sp0.X, sp0.Y, sp0.Z };
var sk1 = new[] { sp1.X, sp1.Y, sp1.Z };
if (!TrySketchLocalToModelPoint(swApp, view, viewSketch, sk0, out var m0)
|| !TrySketchLocalToModelPoint(swApp, view, viewSketch, sk1, out var m1)
|| m0 == null || m1 == null)
{
continue;
}
if (Distance3D(m0, m1) < 1e-6)
{
continue;
}
segments.Add(new ModelSegment3D { P0 = m0, P1 = m1 });
}
catch (Exception ex)
{
Console.WriteLine($"[arc_bend_clearance] 折弯线段读取失败:{ex.Message}");
}
}
return segments;
}
static List<CircleArcItem> CollectCircleArcItems(
SldWorks swApp,
View view,
List<BendRefInfo> distanceRefs)
{
var items = new List<CircleArcItem>();
var seen = new HashSet<string>(StringComparer.Ordinal);
object[]? comps = null;
try
{
comps = (object[])view.GetVisibleComponents();
}
catch
{
comps = null;
}
if (comps == null || comps.Length == 0)
{
return items;
}
foreach (Component2 comp in comps)
{
if (comp == null)
{
continue;
}
object[]? edges = null;
try
{
edges = (object[])view.GetVisibleEntities(
comp, (int)swViewEntityType_e.swViewEntityType_Edge);
}
catch
{
edges = null;
}
if (edges == null)
{
continue;
}
foreach (object edgeObj in edges)
{
if (edgeObj is not Edge edge)
{
continue;
}
if (!TryParseCircleEdge(edge, out var cp, out var span))
{
continue;
}
double radiusM = cp[6];
var centerModel = new[] { cp[0], cp[1], cp[2] };
var axisModel = new[] { cp[3], cp[4], cp[5] };
string key = BuildCircleDedupeKey(cp[0], cp[1], cp[2], radiusM, span);
if (!seen.Add(key))
{
continue;
}
if (IsLikelyBendReliefArc(span, centerModel, distanceRefs))
{
Console.WriteLine(
$"[arc_bend_clearance] 跳过折弯圆角弧 R={radiusM * 1000:F2} mm span={span:F2} rad");
continue;
}
string kind = span >= NearFullCircleParamSpanRad ? "圆孔" : "圆弧";
items.Add(new CircleArcItem
{
RadiusM = radiusM,
ParamSpanRad = span,
Kind = kind,
CenterModel = centerModel,
AxisModel = axisModel,
});
}
}
return items;
}
static bool TryParseCircleEdge(Edge edge, out double[] cp, out double span)
{
cp = Array.Empty<double>();
span = 0;
try
{
var curve = (Curve)edge.GetCurve();
if (curve == null || !curve.IsCircle())
{
return false;
}
cp = (double[])curve.CircleParams;
if (cp == null || cp.Length < 7 || cp[6] <= 1e-9)
{
return false;
}
curve.GetEndParams(out var t0, out var t1, out _, out _);
span = Math.Abs(t1 - t0);
return span >= MinCircleEdgeParamSpanRad;
}
catch
{
return false;
}
}
static string BuildCircleDedupeKey(double cx, double cy, double cz, double r, double span)
{
double cTol = CircleDedupeCenterTolM;
double rTol = Math.Max(1e-6, r * CircleDedupeRadiusRelTol);
long qx = (long)Math.Round(cx / cTol);
long qy = (long)Math.Round(cy / cTol);
long qz = (long)Math.Round(cz / cTol);
long qr = (long)Math.Round(r / rTol);
long qSpan = (long)Math.Round(span * 100.0);
return $"{qx}:{qy}:{qz}:{qr}:{qSpan}";
}
static bool IsLikelyBendReliefArc(double spanRad, double[] centerModel, List<BendRefInfo> distanceRefs)
{
if (spanRad >= NearFullCircleParamSpanRad)
{
return false;
}
double d = MinDistancePointToBendRefs(centerModel, distanceRefs, out _);
return d <= BendLineCoincidentTolM;
}
static List<BendRefInfo> BuildDistanceRefs(List<BendRefInfo> bendRefs, List<ModelSegment3D> viewBendLines)
{
if (bendRefs.Count > 0)
{
return bendRefs;
}
var fallback = new List<BendRefInfo>();
for (int i = 0; i < viewBendLines.Count; i++)
{
var seg = viewBendLines[i];
fallback.Add(new BendRefInfo
{
FullName = $"工程图折弯线#{i + 1}",
HasCenterLineSegment = true,
CenterLineP0 = seg.P0,
CenterLineP1 = seg.P1,
});
}
return fallback;
}
static double MinDistancePointToBendRefs(
double[] point,
List<BendRefInfo> bendRefs,
out string nearestBendName)
{
nearestBendName = string.Empty;
double best = double.MaxValue;
foreach (var br in bendRefs)
{
double d = MinDistancePointToSingleBendRef(point, br);
if (d < best)
{
best = d;
nearestBendName = br.FullName;
}
}
return best;
}
static double MinDistancePointToSingleBendRef(double[] point, BendRefInfo br)
{
if (br.HasAxis)
{
return PointToLineDistance3D(point, br.AxisCenter, br.AxisDir);
}
if (br.HasCenterLineSegment)
{
return PointToSegmentDistance3D(point, br.CenterLineP0, br.CenterLineP1);
}
return double.MaxValue;
}
static double MinDistancePointToSegments3D(
double[] point,
List<ModelSegment3D> segments,
out int bestSegmentIndex)
{
bestSegmentIndex = -1;
double best = double.MaxValue;
for (int i = 0; i < segments.Count; i++)
{
var seg = segments[i];
double d = PointToSegmentDistance3D(point, seg.P0, seg.P1);
if (d < best)
{
best = d;
bestSegmentIndex = i;
}
}
return best;
}
static double PointToLineDistance3D(double[] p, double[] linePoint, double[] lineDir)
{
double dx = lineDir[0];
double dy = lineDir[1];
double dz = lineDir[2];
double lenSq = dx * dx + dy * dy + dz * dz;
if (lenSq < 1e-18)
{
return Distance3D(p, linePoint);
}
double vx = p[0] - linePoint[0];
double vy = p[1] - linePoint[1];
double vz = p[2] - linePoint[2];
double t = (vx * dx + vy * dy + vz * dz) / lenSq;
var q = new[]
{
linePoint[0] + t * dx,
linePoint[1] + t * dy,
linePoint[2] + t * dz,
};
return Distance3D(p, q);
}
static bool TrySketchLocalToModelPoint(
ISldWorks swApp,
View view,
Sketch sketch,
double[] sketchLocal3,
out double[]? model3)
{
model3 = null;
try
{
var math = swApp.IGetMathUtility();
if (math == null)
{
return false;
}
var mp = (MathPoint)math.CreatePoint(sketchLocal3);
if (mp == null)
{
return false;
}
var skInv = (MathTransform)sketch.ModelToSketchTransform.Inverse();
mp = (MathPoint)mp.MultiplyTransform(skInv);
var viewInv = (MathTransform)view.ModelToViewTransform.Inverse();
mp = (MathPoint)mp.MultiplyTransform(viewInv);
var arr = (double[])mp.ArrayData;
if (arr == null || arr.Length < 3)
{
return false;
}
model3 = new[] { arr[0], arr[1], arr[2] };
return true;
}
catch
{
return false;
}
}
static double PointToSegmentDistance3D(double[] p, double[] a, double[] b)
{
double dx = b[0] - a[0];
double dy = b[1] - a[1];
double dz = b[2] - a[2];
double lenSq = dx * dx + dy * dy + dz * dz;
if (lenSq < 1e-18)
{
return Distance3D(p, a);
}
double t = ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy + (p[2] - a[2]) * dz) / lenSq;
if (t < 0.0)
{
t = 0.0;
}
else if (t > 1.0)
{
t = 1.0;
}
var q = new[]
{
a[0] + t * dx,
a[1] + t * dy,
a[2] + t * dz,
};
return Distance3D(p, q);
}
static double Distance3D(double[] a, double[] b)
{
double dx = a[0] - b[0];
double dy = a[1] - b[1];
double dz = a[2] - b[2];
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
static void LogNamedBendRefs(List<BendRefInfo> bendRefs)
{
if (bendRefs.Count == 0)
{
Console.WriteLine("[arc_bend_clearance] 零件特征树未读到 OneBend 折弯(将回退工程图折弯线)");
return;
}
Console.WriteLine($"[arc_bend_clearance] 零件 OneBend 折弯 {bendRefs.Count} 处(折弯前 3D 中心线)");
foreach (var br in bendRefs)
{
if (br.HasAxis)
{
Console.WriteLine(
$"[arc_bend_clearance] 折弯「{br.FullName}」圆柱 R={br.RadiusM * 1000:F2} mm "
+ $"轴心 {FormatPointMm(br.AxisCenter)}");
Console.WriteLine(
$"[arc_bend_clearance] 轴线 {FormatAxisParametricMm(br.AxisCenter, br.AxisDir)}");
if (br.HasCenterLineSegment)
{
Console.WriteLine(
$"[arc_bend_clearance] 中心线段 "
+ FormatPointMm(br.CenterLineP0) + " → " + FormatPointMm(br.CenterLineP1));
Console.WriteLine(
$"[arc_bend_clearance] 参数式 "
+ FormatSegmentParametricMm(br.CenterLineP0, br.CenterLineP1));
}
}
else
{
Console.WriteLine($"[arc_bend_clearance] 折弯「{br.FullName}」未读到折弯前圆柱轴线");
}
}
}
static string TryResolveFoldedConfigurationName(ModelDoc2 partModel)
{
string activeName = string.Empty;
try
{
activeName = partModel.ConfigurationManager?.ActiveConfiguration?.Name ?? string.Empty;
}
catch
{
activeName = string.Empty;
}
activeName = activeName.Trim();
if (string.IsNullOrEmpty(activeName))
{
return string.Empty;
}
try
{
var cfg = partModel.ConfigurationManager?.ActiveConfiguration as Configuration;
Configuration? parent = cfg?.GetParent() as Configuration;
string parentName = parent?.Name?.Trim() ?? string.Empty;
if (!string.IsNullOrEmpty(parentName)
&& !string.Equals(parentName, activeName, StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine(
$"[arc_bend_clearance] 折弯中心线读取配置「{parentName}」(视图引用「{activeName}」)");
return parentName;
}
}
catch (Exception ex)
{
Console.WriteLine($"[arc_bend_clearance] 解析折叠配置失败:{ex.Message},使用当前配置");
}
Console.WriteLine($"[arc_bend_clearance] 折弯中心线读取配置「{activeName}」");
return activeName;
}
static List<BendRefInfo> CollectNamedBendRefsFromPart(ModelDoc2 partModel, PartDoc partDoc)
{
var list = new List<BendRefInfo>();
var seenFeatIds = new HashSet<int>();
try
{
foreach (Body2 body in (object[])partDoc.GetBodies2((int)swBodyType_e.swSolidBody, false))
{
if (body == null)
{
continue;
}
object[]? features = (object[]?)body.GetFeatures();
if (features == null)
{
continue;
}
foreach (object featureObject in features)
{
if (featureObject is not Feature parentFeat)
{
continue;
}
string parentName = parentFeat.Name ?? string.Empty;
var subFeat = (Feature?)parentFeat.GetFirstSubFeature();
while (subFeat != null)
{
if (IsLinearSheetMetalBendSubFeature(subFeat))
{
string fullName = string.IsNullOrEmpty(parentName)
? subFeat.Name ?? "?"
: $"{parentName}+{subFeat.Name}";
TryAddBendRef(subFeat, fullName, list, seenFeatIds);
}
subFeat = (Feature?)subFeat.GetNextSubFeature();
}
}
}
int steps = 0;
Feature? top = (Feature?)partModel.FirstFeature();
while (top != null && steps < MaxSubFeatureWalkSteps)
{
steps++;
CollectOneBendsInPartSubtree(top, string.Empty, list, seenFeatIds, ref steps);
top = (Feature?)top.GetNextFeature();
}
}
catch (Exception ex)
{
Console.WriteLine($"[arc_bend_clearance] 读取零件折弯失败:{ex.Message}");
}
return list;
}
static void CollectOneBendsInPartSubtree(
Feature node,
string parentPath,
List<BendRefInfo> sink,
HashSet<int> seenFeatIds,
ref int steps)
{
if (steps >= MaxSubFeatureWalkSteps)
{
return;
}
steps++;
string nodeName = node.Name ?? string.Empty;
string path = string.IsNullOrEmpty(parentPath) ? nodeName : $"{parentPath}+{nodeName}";
if (IsLinearSheetMetalBendSubFeature(node))
{
TryAddBendRef(node, path, sink, seenFeatIds);
}
Feature? sub = (Feature?)node.GetFirstSubFeature();
while (sub != null && steps < MaxSubFeatureWalkSteps)
{
if (IsLinearSheetMetalBendSubFeature(sub))
{
string bendPath = string.IsNullOrEmpty(path)
? sub.Name ?? "?"
: $"{path}+{sub.Name}";
TryAddBendRef(sub, bendPath, sink, seenFeatIds);
}
string type2 = string.Empty;
try
{
type2 = sub.GetTypeName2() ?? string.Empty;
}
catch
{
type2 = string.Empty;
}
if (!string.Equals(type2, "FlatPattern", StringComparison.Ordinal))
{
CollectOneBendsInPartSubtree(sub, path, sink, seenFeatIds, ref steps);
}
sub = (Feature?)sub.GetNextSubFeature();
}
}
static void TryAddBendRef(
Feature bendFeat,
string fullName,
List<BendRefInfo> sink,
HashSet<int> seenFeatIds)
{
int featId;
try
{
featId = bendFeat.GetID();
}
catch
{
featId = (fullName ?? string.Empty).GetHashCode();
}
if (!seenFeatIds.Add(featId))
{
return;
}
var info = new BendRefInfo { FullName = fullName ?? "?" };
if (!TryReadMainBendCylinderAxis(bendFeat, out var center, out var axis, out var radius, out Face? mainCylFace))
{
return;
}
info.HasAxis = true;
info.AxisCenter = center;
info.AxisDir = axis;
info.RadiusM = radius;
if (mainCylFace != null
&& TryReadBendCenterLineSegment(mainCylFace, center, axis, out var p0, out var p1))
{
info.HasCenterLineSegment = true;
info.CenterLineP0 = p0;
info.CenterLineP1 = p1;
}
sink.Add(info);
}
static bool TryReadMainBendCylinderAxis(
Feature bendFeat,
out double[] center,
out double[] axis,
out double radius,
out Face? mainCylinderFace)
{
center = Array.Empty<double>();
axis = Array.Empty<double>();
radius = 0;
mainCylinderFace = null;
double maxArea = 0;
foreach (Face face in EnumerateFeatureFaces(bendFeat))
{
if (!TryGetCylinderData(face, out var ctr, out var ax, out var r))
{
continue;
}
double area;
try
{
area = face.GetArea();
}
catch
{
area = 0;
}
if (area > maxArea)
{
maxArea = area;
center = ctr;
axis = ax;
radius = r;
mainCylinderFace = face;
}
}
return maxArea > 0;
}
static bool TryReadBendCenterLineSegment(
Face cylinderFace,
double[] axisCenter,
double[] axisDir,
out double[] p0,
out double[] p1)
{
p0 = axisCenter;
p1 = axisCenter;
double tMin = double.MaxValue;
double tMax = double.MinValue;
bool hasSample = false;
object[]? edges = null;
try
{
edges = (object[]?)cylinderFace.GetEdges();
}
catch
{
edges = null;
}
if (edges == null)
{
return false;
}
foreach (object edgeObj in edges)
{
if (edgeObj is not Edge edge)
{
continue;
}
if (!TrySampleEdgePoints(edge, out var samples))
{
continue;
}
foreach (double[] pt in samples)
{
double t = ProjectPointOnAxis(axisCenter, axisDir, pt);
tMin = Math.Min(tMin, t);
tMax = Math.Max(tMax, t);
hasSample = true;
}
}
if (!hasSample || tMax + 1e-9 < tMin)
{
return false;
}
p0 = PointOnAxis(axisCenter, axisDir, tMin);
p1 = PointOnAxis(axisCenter, axisDir, tMax);
return Distance3D(p0, p1) > 1e-6;
}
static bool TrySampleEdgePoints(Edge edge, out List<double[]> samples)
{
samples = new List<double[]>();
try
{
var v0 = (edge.GetStartVertex() as Vertex)?.GetPoint() as double[];
var v1 = (edge.GetEndVertex() as Vertex)?.GetPoint() as double[];
if (v0 != null && v0.Length >= 3)
{
samples.Add(new[] { v0[0], v0[1], v0[2] });
}
if (v1 != null && v1.Length >= 3)
{
samples.Add(new[] { v1[0], v1[1], v1[2] });
}
var curve = (Curve?)edge.GetCurve();
if (curve != null)
{
curve.GetEndParams(out double t0, out double t1, out _, out _);
for (int i = 1; i < 4; i++)
{
double t = t0 + (t1 - t0) * i / 4.0;
var raw = (double[]?)curve.Evaluate(t);
if (raw != null && raw.Length >= 3)
{
samples.Add(new[] { raw[0], raw[1], raw[2] });
}
}
}
}
catch
{
samples.Clear();
}
return samples.Count > 0;
}
static double ProjectPointOnAxis(double[] axisCenter, double[] axisDir, double[] point)
{
double dx = axisDir[0];
double dy = axisDir[1];
double dz = axisDir[2];
double lenSq = dx * dx + dy * dy + dz * dz;
if (lenSq < 1e-18)
{
return 0;
}
double vx = point[0] - axisCenter[0];
double vy = point[1] - axisCenter[1];
double vz = point[2] - axisCenter[2];
return (vx * dx + vy * dy + vz * dz) / lenSq;
}
static double[] PointOnAxis(double[] axisCenter, double[] axisDir, double t)
=> new[]
{
axisCenter[0] + t * axisDir[0],
axisCenter[1] + t * axisDir[1],
axisCenter[2] + t * axisDir[2],
};
static bool IsLinearSheetMetalBendSubFeature(Feature subFeat)
{
try
{
if (string.Equals(subFeat.GetTypeName(), "OneBend", StringComparison.Ordinal)
|| string.Equals(subFeat.GetTypeName2() ?? "", "OneBend", StringComparison.Ordinal))
{
return true;
}
return subFeat.GetDefinition() is OneBendFeatureData;
}
catch
{
return false;
}
}
static IEnumerable<Face> EnumerateFeatureFaces(Feature feat)
{
if (feat == null)
{
yield break;
}
object? raw;
try
{
raw = feat.GetFaces();
}
catch
{
yield break;
}
if (raw == null)
{
yield break;
}
if (raw is object[] arr)
{
foreach (object o in arr)
{
if (o is Face f)
{
yield return f;
}
}
yield break;
}
if (raw is Face one)
{
yield return one;
}
}
static bool TryGetCylinderData(Face face, out double[] center, out double[] axis, out double radius)
{
center = Array.Empty<double>();
axis = Array.Empty<double>();
radius = 0;
if (face == null)
{
return false;
}
try
{
var s = (Surface)face.GetSurface();
if (s == null || !s.IsCylinder())
{
return false;
}
var p = (double[])s.CylinderParams;
if (p == null || p.Length < 7)
{
return false;
}
center = new[] { p[0], p[1], p[2] };
axis = new[] { p[3], p[4], p[5] };
radius = p[6];
return true;
}
catch
{
return false;
}
}
static string FormatPointMm(double[] p)
=> $"({p[0] * 1000:F3},{p[1] * 1000:F3},{p[2] * 1000:F3}) mm";
static string FormatAxisParametricMm(double[] center, double[] axis)
=> $"P(t)=({center[0] * 1000:F3},{center[1] * 1000:F3},{center[2] * 1000:F3}) mm"
+ $"+t·({axis[0]:G6},{axis[1]:G6},{axis[2]:G6})";
static string FormatSegmentParametricMm(double[] p0, double[] p1)
{
var d = new[]
{
(p1[0] - p0[0]) * 1000.0,
(p1[1] - p0[1]) * 1000.0,
(p1[2] - p0[2]) * 1000.0,
};
return $"P(u)={FormatPointMm(p0)}+u·({d[0]:F3},{d[1]:F3},{d[2]:F3}) mm,u∈[0,1]";
}
static IDisposable ActivateViewReferencedPartConfiguration(ModelDoc2 partModel, View view)
=> new ViewReferencedConfigurationScope(partModel, view);
sealed class PartConfigurationScope : IDisposable
{
readonly ModelDoc2 _model;
readonly string _restoreName;
readonly bool _switched;
bool _disposed;
public PartConfigurationScope(ModelDoc2 partModel, string targetConfigName)
{
_model = partModel;
_restoreName = string.Empty;
_switched = false;
string target = (targetConfigName ?? string.Empty).Trim();
if (string.IsNullOrEmpty(target))
{
return;
}
try
{
_restoreName = partModel.ConfigurationManager?.ActiveConfiguration?.Name ?? string.Empty;
}
catch
{
_restoreName = string.Empty;
}
if (string.Equals(_restoreName, target, StringComparison.OrdinalIgnoreCase))
{
return;
}
try
{
partModel.ShowConfiguration2(target);
_switched = true;
try
{
partModel.EditRebuild3();
}
catch
{
// ignored
}
}
catch (Exception ex)
{
Console.WriteLine($"[arc_bend_clearance] 切换折叠配置失败:{ex.Message}");
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
if (!_switched || string.IsNullOrEmpty(_restoreName))
{
return;
}
try
{
_model.ShowConfiguration2(_restoreName);
}
catch
{
// ignored
}
}
}
sealed class ViewReferencedConfigurationScope : IDisposable
{
readonly ModelDoc2 _model;
readonly string _restoreName;
readonly bool _switched;
bool _disposed;
public ViewReferencedConfigurationScope(ModelDoc2 partModel, View view)
{
_model = partModel;
_restoreName = "";
_switched = false;
try
{
_restoreName = partModel.ConfigurationManager?.ActiveConfiguration?.Name ?? "";
}
catch
{
// ignored
}
string refCfg = "";
try
{
refCfg = (view.ReferencedConfiguration ?? "").Trim();
}
catch
{
refCfg = "";
}
if (string.IsNullOrEmpty(refCfg))
{
Console.WriteLine("[arc_bend_clearance] 视图未指定引用配置,使用零件当前活动配置");
return;
}
if (string.Equals(_restoreName, refCfg, StringComparison.OrdinalIgnoreCase))
{
return;
}
try
{
Console.WriteLine(
$"[arc_bend_clearance] 切换零件到视图引用配置「{refCfg}」(原「{_restoreName}」)");
partModel.ShowConfiguration2(refCfg);
_switched = true;
try
{
partModel.EditRebuild3();
}
catch
{
// ignored
}
}
catch (Exception ex)
{
Console.WriteLine($"[arc_bend_clearance] 切换配置失败:{ex.Message}");
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
if (!_switched || string.IsNullOrEmpty(_restoreName))
{
return;
}
try
{
_model.ShowConfiguration2(_restoreName);
}
catch
{
// ignored
}
}
}
}
}