
展平



csharp
using System;
using System.Collections.Generic;
using System.Text;
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;
using View = SolidWorks.Interop.sldworks.View;
namespace tools
{
/// <summary>
/// 选中工程图视图:检查每条折弯线两个端点是否被零件可见直线几何横穿。
/// 折弯线从零件平板型式 FlatPattern→ProfileFeature 草图读取,按 <see cref="swBendLineDirection_e"/> 过滤边框线。
/// 任一端点未被穿过则弹窗提示。
/// </summary>
public static partial class selected_view_bend_line_endpoint_check
{
/// <summary>点到可见线段的最大垂距(米)。</summary>
const double PointOnSegmentLineDistTolM = 0.0001;
/// <summary>允许点略微超出可见线段端点的延伸量(米)。</summary>
const double PointOnSegmentExtendTolM = 0.0002;
/// <summary>图纸 2D 坐标下的容差(米)。</summary>
const double Sheet2DLineDistTolM = 0.0002;
/// <summary>与折弯线方向余弦低于此值才视为「横穿」可见边(排除与折弯线平行的边)。</summary>
const double CrossingLineMaxAlignAbs = 0.25;
sealed class ModelSegment3D
{
public int Index;
public double LengthM;
/// <summary>展开模型坐标(日志/弹窗)。</summary>
public double[] P0 = Array.Empty<double>();
public double[] P1 = Array.Empty<double>();
public double SheetX0;
public double SheetY0;
public double SheetX1;
public double SheetY1;
public bool HasSheet2D;
}
sealed class SheetSegment2D
{
public double X0;
public double Y0;
public double X1;
public double Y1;
}
sealed class EndpointViolation
{
public int BendLineIndex;
public double BendLineLengthMm;
public bool IsStart;
public double[] ModelPoint = Array.Empty<double>();
public double SheetX = double.NaN;
public double SheetY = double.NaN;
}
public static void run(SldWorks swApp, ModelDoc2 swModel)
{
try
{
if (swModel == null)
{
Console.WriteLine("[bend_line_endpoint] 错误:没有活动文档。");
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($"[bend_line_endpoint] 失败:{ex.Message}");
swApp?.SendMsgToUser($"折弯线端点检查失败:{ex.Message}");
}
}
/// <summary>对指定视图执行折弯线端点检查(自动化出图可调用)。通过返回 true,未通过返回 false。</summary>
/// <param name="appendAbortHint">为 true 时弹窗附加「出图已终止」说明(new_drw 跳过保存用,不关闭工程图)。</param>
public static bool runForView(
SldWorks swApp,
ModelDoc2 swModel,
View view,
bool appendAbortHint = false)
{
if (swApp == null || swModel == null || view == null)
{
Console.WriteLine("[bend_line_endpoint] 参数无效,检查未通过。");
return false;
}
if (!TryResolvePartModelForView(view, out var partModel, out var scopeComponent, out var resolveFail))
{
Console.WriteLine($"[bend_line_endpoint] 无法解析视图零件:{resolveFail}");
NotifyFail(swApp, $"无法解析视图关联零件:{resolveFail}", appendAbortHint);
return false;
}
List<ModelSegment3D> bendLines;
List<SheetSegment2D> visibleLinesSheet2D;
List<ModelSegment3D> visibleLines3D;
using (ActivateViewReferencedPartConfiguration(partModel, view, scopeComponent))
{
bendLines = CollectBendLineSegmentsFromFlatPattern(swApp, partModel, view);
if (bendLines.Count == 0)
{
NotifyFail(
swApp,
"零件平板型式特征中未读取到折弯线:请确认零件含 FlatPattern 且视图引用展开配置。",
appendAbortHint);
return false;
}
visibleLinesSheet2D = CollectVisibleStraightSegmentsSheet2D(swApp, view);
visibleLines3D = CollectVisibleStraightSegmentsModel3D(view);
}
Console.WriteLine(
$"[bend_line_endpoint] 视图「{view.Name}」折弯线 {bendLines.Count} 条,"
+ $"可见直线 {visibleLines3D.Count} 条(图纸 2D {visibleLinesSheet2D.Count} 条)");
Console.WriteLine(
"[bend_line_endpoint] 可见线图纸 = GetPoint() → ModelToViewTransform(与 benddim 一致)");
Console.WriteLine(
"[bend_line_endpoint] 折弯线 = FlatPattern ProfileFeature 草图 + GetBendLineDirection(Up/Down) → 模型3D → 图纸");
Console.WriteLine(
"[bend_line_endpoint] 横穿检查:优先模型3D,回退图纸2D;可视化用图纸2D");
LogModelSegmentsBBox("[bend_line_endpoint] 可见线 bbox(模型3D)", visibleLines3D);
LogModelSegmentsBBox("[bend_line_endpoint] 折弯线 bbox(模型3D)", bendLines);
LogBendLineInventory(swApp, view, bendLines);
var violations = new List<EndpointViolation>();
for (int i = 0; i < bendLines.Count; i++)
{
var seg = bendLines[i];
CheckBendEndpoint(seg, true, visibleLinesSheet2D, visibleLines3D, violations);
CheckBendEndpoint(seg, false, visibleLinesSheet2D, visibleLines3D, violations);
}
SaveDebugVisualization(bendLines, visibleLinesSheet2D, violations);
if (visibleLines3D.Count == 0 && visibleLinesSheet2D.Count == 0)
{
NotifyFail(swApp, "视图中未找到零件可见直线几何,无法检查折弯线端点。", appendAbortHint);
return false;
}
if (violations.Count == 0)
{
Console.WriteLine(
$"[bend_line_endpoint] 检查通过:{bendLines.Count} 条折弯线共 {bendLines.Count * 2} 个端点均在可见几何线上。");
return true;
}
LogViolations(violations);
NotifyFail(swApp, BuildViolationMessage(violations), appendAbortHint);
return false;
}
static void NotifyFail(SldWorks swApp, string message, bool appendAbortHint)
{
if (appendAbortHint)
{
message += "\n\n出图已终止,后续保存已跳过(工程图保持打开)。";
}
Console.WriteLine($"[bend_line_endpoint] 检查未通过:{message.Replace("\n", " | ")}");
swApp.SendMsgToUser(message);
}
static void CheckBendEndpoint(
ModelSegment3D seg,
bool isStart,
List<SheetSegment2D> visibleLinesSheet2D,
List<ModelSegment3D> visibleLines3D,
List<EndpointViolation> violations)
{
var modelPt = isStart ? seg.P0 : seg.P1;
if (IsPointOnCrossingVisibleLineModel3D(modelPt, seg, visibleLines3D)
|| IsPointOnCrossingVisibleLineSheet2D(isStart, seg, visibleLinesSheet2D))
{
return;
}
double sheetX = double.NaN;
double sheetY = double.NaN;
if (seg.HasSheet2D)
{
sheetX = isStart ? seg.SheetX0 : seg.SheetX1;
sheetY = isStart ? seg.SheetY0 : seg.SheetY1;
}
violations.Add(new EndpointViolation
{
BendLineIndex = seg.Index,
BendLineLengthMm = seg.LengthM * 1000.0,
IsStart = isStart,
ModelPoint = isStart ? seg.P0 : seg.P1,
SheetX = sheetX,
SheetY = sheetY,
});
}
/// <summary>模型 3D 空间横穿判定(与可见边 GetPoint 同空间,benddim 折弯模型链)。</summary>
static bool IsPointOnCrossingVisibleLineModel3D(
double[] point,
ModelSegment3D bendSeg,
List<ModelSegment3D> visibleLines3D)
{
var bendDir3 = Normalize3D(Sub3(bendSeg.P1, bendSeg.P0));
if (bendDir3 == null)
{
return false;
}
foreach (var line in visibleLines3D)
{
var edgeDir3 = Normalize3D(Sub3(line.P1, line.P0));
if (edgeDir3 == null)
{
continue;
}
double align = Math.Abs(
bendDir3[0] * edgeDir3[0] + bendDir3[1] * edgeDir3[1] + bendDir3[2] * edgeDir3[2]);
if (align > CrossingLineMaxAlignAbs)
{
continue;
}
if (IsPointOnSegment3D(
point, line.P0, line.P1,
PointOnSegmentLineDistTolM, PointOnSegmentExtendTolM))
{
return true;
}
}
return false;
}
/// <summary>图纸 2D 空间横穿判定(benddim TryModelPointToSheetXY)。</summary>
static bool IsPointOnCrossingVisibleLineSheet2D(
bool isStart,
ModelSegment3D bendSeg,
List<SheetSegment2D> visibleLinesSheet2D)
{
if (!bendSeg.HasSheet2D)
{
return false;
}
var bendDir2 = Normalize2D(
bendSeg.SheetX1 - bendSeg.SheetX0,
bendSeg.SheetY1 - bendSeg.SheetY0);
if (bendDir2 == null)
{
return false;
}
double px = isStart ? bendSeg.SheetX0 : bendSeg.SheetX1;
double py = isStart ? bendSeg.SheetY0 : bendSeg.SheetY1;
foreach (var line in visibleLinesSheet2D)
{
var edgeDir2 = Normalize2D(line.X1 - line.X0, line.Y1 - line.Y0);
if (edgeDir2 == null)
{
continue;
}
if (Math.Abs(bendDir2[0] * edgeDir2[0] + bendDir2[1] * edgeDir2[1]) > CrossingLineMaxAlignAbs)
{
continue;
}
if (IsPointOnSegment2D(
px, py,
line.X0, line.Y0, line.X1, line.Y1,
Sheet2DLineDistTolM, PointOnSegmentExtendTolM))
{
return true;
}
}
return false;
}
static void LogModelSegmentsBBox(string tag, List<ModelSegment3D> segments)
{
if (segments.Count == 0)
{
Console.WriteLine($"{tag}: (空)");
return;
}
double minX = double.PositiveInfinity;
double minY = double.PositiveInfinity;
double minZ = double.PositiveInfinity;
double maxX = double.NegativeInfinity;
double maxY = double.NegativeInfinity;
double maxZ = double.NegativeInfinity;
foreach (var seg in segments)
{
UpdateModelPointBounds(seg.P0, ref minX, ref minY, ref minZ, ref maxX, ref maxY, ref maxZ);
UpdateModelPointBounds(seg.P1, ref minX, ref minY, ref minZ, ref maxX, ref maxY, ref maxZ);
}
Console.WriteLine(
$"{tag}: X[{minX * 1000:F2},{maxX * 1000:F2}] mm "
+ $"Y[{minY * 1000:F2},{maxY * 1000:F2}] mm "
+ $"Z[{minZ * 1000:F2},{maxZ * 1000:F2}] mm,{segments.Count} 条");
}
static void UpdateModelPointBounds(
double[] p,
ref double minX,
ref double minY,
ref double minZ,
ref double maxX,
ref double maxY,
ref double maxZ)
{
if (p == null || p.Length < 3)
{
return;
}
minX = Math.Min(minX, p[0]);
minY = Math.Min(minY, p[1]);
minZ = Math.Min(minZ, p[2]);
maxX = Math.Max(maxX, p[0]);
maxY = Math.Max(maxY, p[1]);
maxZ = Math.Max(maxZ, p[2]);
}
static void LogBendLineInventory(ISldWorks swApp, View view, List<ModelSegment3D> bendLines)
{
foreach (var seg in bendLines)
{
string sheetPart = "";
if (seg.HasSheet2D)
{
double sheetLenMm = Distance2D(seg.SheetX0, seg.SheetY0, seg.SheetX1, seg.SheetY1) * 1000.0;
sheetPart =
$",图纸 {FormatPointSheetMm(seg.SheetX0, seg.SheetY0)}→{FormatPointSheetMm(seg.SheetX1, seg.SheetY1)} L={sheetLenMm:F2} mm";
}
else if (TryModelPointToSheetXY(swApp, view, seg.P0, out var x0, out var y0)
&& TryModelPointToSheetXY(swApp, view, seg.P1, out var x1, out var y1))
{
double sheetLenMm = Distance2D(x0, y0, x1, y1) * 1000.0;
sheetPart =
$",图纸 {FormatPointSheetMm(x0, y0)}→{FormatPointSheetMm(x1, y1)} L={sheetLenMm:F2} mm";
}
Console.WriteLine(
$"[bend_line_endpoint] 折弯线#{seg.Index} 模型 "
+ $"{FormatPointMm(seg.P0)}→{FormatPointMm(seg.P1)} L={seg.LengthM * 1000:F2} mm{sheetPart}");
}
}
static void LogViolations(List<EndpointViolation> violations)
{
foreach (var v in violations)
{
string endLabel = v.IsStart ? "起点" : "终点";
string sheetPart = !double.IsNaN(v.SheetX)
? $",图纸({v.SheetX * 1000:F2},{v.SheetY * 1000:F2}) mm"
: "";
Console.WriteLine(
$"[bend_line_endpoint] 折弯线#{v.BendLineIndex} L={v.BendLineLengthMm:F2} mm {endLabel} "
+ $"模型 {FormatPointMm(v.ModelPoint)}{sheetPart} 未被横穿可见几何线穿过");
}
}
static string BuildViolationMessage(List<EndpointViolation> violations)
{
var sb = new StringBuilder();
sb.AppendLine("以下折弯线端点未被横穿可见几何线穿过(折弯线可能短了一截或视图缺边):");
foreach (var v in violations)
{
string endLabel = v.IsStart ? "起点" : "终点";
sb.AppendLine(
$" · 折弯线#{v.BendLineIndex} L={v.BendLineLengthMm:F2} mm {endLabel} {FormatPointMm(v.ModelPoint)}");
}
return sb.ToString().TrimEnd();
}
/// <summary>从零件平板型式特征(FlatPattern → UiBend 草图线段)采集折弯线,变换到模型 3D。</summary>
static List<ModelSegment3D> CollectBendLineSegmentsFromFlatPattern(
ISldWorks swApp,
ModelDoc2 partModel,
View view)
{
var segments = new List<ModelSegment3D>();
var flatPatterns = FindQualifiedFlatPatternFeatures(partModel);
if (flatPatterns.Count == 0)
{
Console.WriteLine("[bend_line_endpoint] 零件未找到平板型式 FlatPattern 特征");
return segments;
}
foreach (var flatFeat in flatPatterns)
{
string featName = "";
try
{
featName = flatFeat.Name ?? "?";
}
catch
{
featName = "?";
}
CollectBendLinesFromFlatPatternFeature(swApp, flatFeat, view, segments);
}
ReindexBendSegments(segments);
return segments;
}
static void ReindexBendSegments(List<ModelSegment3D> segments)
{
for (int i = 0; i < segments.Count; i++)
{
segments[i].Index = i + 1;
}
}
static List<Feature> FindQualifiedFlatPatternFeatures(ModelDoc2 partModel)
{
var list = new List<Feature>();
if (partModel.GetType() != (int)swDocumentTypes_e.swDocPART)
{
return list;
}
var partDoc = (PartDoc)partModel;
object[]? bodies;
try
{
bodies = (object[]?)partDoc.GetBodies2((int)swBodyType_e.swSolidBody, false);
}
catch
{
bodies = null;
}
if (bodies != null)
{
foreach (object bodyObj in bodies)
{
if (bodyObj is not Body2 body)
{
continue;
}
object[]? features;
try
{
features = (object[]?)body.GetFeatures();
}
catch
{
continue;
}
if (!BodyFeatureTreeHasSheetMetal(features))
{
continue;
}
if (features == null || features.Length == 0)
{
continue;
}
if (features[features.Length - 1] is not Feature lastFeat)
{
continue;
}
if (!string.Equals(lastFeat.GetTypeName2(), "FlatPattern", StringComparison.OrdinalIgnoreCase))
{
continue;
}
list.Add(lastFeat);
}
}
if (list.Count > 0)
{
return list;
}
Feature? top = (Feature?)partModel.FirstFeature();
while (top != null)
{
if (string.Equals(top.GetTypeName2(), "FlatPattern", StringComparison.OrdinalIgnoreCase))
{
list.Add(top);
}
top = (Feature?)top.GetNextFeature();
}
return list;
}
static bool BodyFeatureTreeHasSheetMetal(object[]? features)
{
if (features == null)
{
return false;
}
foreach (object featureObj in features)
{
if (featureObj is Feature feature
&& string.Equals(feature.GetTypeName2(), "SheetMetal", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
static void CollectBendLinesFromFlatPatternFeature(
ISldWorks swApp,
Feature flatPattern,
View view,
List<ModelSegment3D> sink)
{
string featName = "";
try
{
featName = flatPattern.Name ?? "?";
}
catch
{
featName = "?";
}
Sketch? sketch = FindFlatPatternProfileSketch(flatPattern);
if (sketch == null)
{
Console.WriteLine(
$"[bend_line_endpoint] 平板特征「{featName}」未找到 ProfileFeature 折弯草图");
return;
}
int before = sink.Count;
int totalLines = 0;
int bendLines = 0;
AddSketchBendLineSegmentsToModel(swApp, sketch, view, sink, ref totalLines, ref bendLines);
Console.WriteLine(
$"[bend_line_endpoint] 平板特征「{featName}」草图直线 {totalLines} 条,"
+ $"GetBendLineDirection 折弯线 {bendLines} 条,采纳 {sink.Count - before} 条");
}
/// <summary>FlatPattern 下存放展开几何的 ProfileFeature 草图(含折弯线与边框线,靠方向枚举区分)。</summary>
static Sketch? FindFlatPatternProfileSketch(Feature flatPattern)
{
Feature? sub = (Feature?)flatPattern.GetFirstSubFeature();
while (sub != null)
{
if (string.Equals(sub.GetTypeName2(), "ProfileFeature", StringComparison.OrdinalIgnoreCase)
&& sub.GetSpecificFeature2() is Sketch sketch)
{
return sketch;
}
sub = (Feature?)sub.GetNextSubFeature();
}
return null;
}
static void AddSketchBendLineSegmentsToModel(
ISldWorks swApp,
Sketch sketch,
View view,
List<ModelSegment3D> sink,
ref int totalLineCount,
ref int bendLineCount)
{
object? raw;
try
{
raw = sketch.GetSketchSegments();
}
catch (Exception ex)
{
Console.WriteLine($"[bend_line_endpoint] GetSketchSegments 异常:{ex.Message}");
return;
}
if (raw is not object[] segs)
{
return;
}
foreach (object obj in segs)
{
if (obj is not SketchLine line)
{
continue;
}
totalLineCount++;
if (!TryIsSketchBendLineSegment(line, out _))
{
continue;
}
bendLineCount++;
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 (!TryPartSketchLocalToModelPoint(swApp, sketch, sk0, out var m0)
|| !TryPartSketchLocalToModelPoint(swApp, sketch, sk1, out var m1)
|| m0 == null || m1 == null)
{
continue;
}
if (Distance3D(m0, m1) < 1e-6)
{
continue;
}
double sx0 = 0;
double sy0 = 0;
double sx1 = 0;
double sy1 = 0;
bool hasSheet = TryModelPointToSheetXY(swApp, view, m0, out sx0, out sy0)
&& TryModelPointToSheetXY(swApp, view, m1, out sx1, out sy1);
sink.Add(new ModelSegment3D
{
P0 = m0,
P1 = m1,
LengthM = Distance3D(m0, m1),
HasSheet2D = hasSheet,
SheetX0 = sx0,
SheetY0 = sy0,
SheetX1 = sx1,
SheetY1 = sy1,
});
}
catch (Exception ex)
{
Console.WriteLine($"[bend_line_endpoint] 平板草图折弯线段读取失败:{ex.Message}");
}
}
}
/// <summary>
/// 草图线段是否为折弯线(<see cref="SketchLine.GetBendLineDirection"/> → <see cref="swBendLineDirection_e"/>;
/// 边框轮廓线为 swNotBendLine)。
/// </summary>
static bool TryIsSketchBendLineSegment(SketchLine line, out int bendDirection)
{
bendDirection = (int)swBendLineDirection_e.swNotBendLine;
try
{
bendDirection = line.GetBendLineDirection();
}
catch
{
return false;
}
return bendDirection == (int)swBendLineDirection_e.swUpDirection
|| bendDirection == (int)swBendLineDirection_e.swDownDirection;
}
/// <summary>零件草图局部坐标 → 模型 3D(仅 ModelToSketchTransform 逆变换,不经视图草图)。</summary>
static bool TryPartSketchLocalToModelPoint(
ISldWorks swApp,
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 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 List<ModelSegment3D> CollectVisibleStraightSegmentsModel3D(View view)
{
var segments = new List<ModelSegment3D>();
object[]? comps;
try
{
comps = (object[])view.GetVisibleComponents();
}
catch
{
return segments;
}
if (comps == null)
{
return segments;
}
foreach (Component2 comp in comps)
{
if (comp == null)
{
continue;
}
object[]? visibleEdges;
try
{
visibleEdges = (object[])view.GetVisibleEntities(
comp, (int)swViewEntityType_e.swViewEntityType_Edge);
}
catch
{
continue;
}
if (visibleEdges == null)
{
continue;
}
foreach (object obj in visibleEdges)
{
if (obj is not Edge edge)
{
continue;
}
TryAddVisibleStraightSegment(edge, segments);
}
}
return segments;
}
static List<SheetSegment2D> CollectVisibleStraightSegmentsSheet2D(ISldWorks swApp, View view)
{
var segments = new List<SheetSegment2D>();
foreach (var seg in CollectVisibleStraightSegmentsModel3D(view))
{
if (!TryModelPointToSheetXY(swApp, view, seg.P0, out var x0, out var y0)
|| !TryModelPointToSheetXY(swApp, view, seg.P1, out var x1, out var y1))
{
continue;
}
if (Distance2D(x0, y0, x1, y1) < 1e-6)
{
continue;
}
segments.Add(new SheetSegment2D { X0 = x0, Y0 = y0, X1 = x1, Y1 = y1 });
}
return segments;
}
static void TryAddVisibleStraightSegment(Edge edge, List<ModelSegment3D> sink)
{
try
{
var curve = (Curve)edge.GetCurve();
if (curve == null || !curve.IsLine())
{
return;
}
var sv = (Vertex)edge.GetStartVertex();
var ev = (Vertex)edge.GetEndVertex();
if (sv == null || ev == null)
{
return;
}
var v0 = (double[])sv.GetPoint();
var v1 = (double[])ev.GetPoint();
if (v0 == null || v1 == null || v0.Length < 3 || v1.Length < 3)
{
return;
}
if (Distance3D(v0, v1) < 1e-6)
{
return;
}
sink.Add(new ModelSegment3D
{
P0 = new[] { v0[0], v0[1], v0[2] },
P1 = new[] { v1[0], v1[1], v1[2] },
});
}
catch
{
// skip
}
}
static bool IsPointOnSegment3D(
double[] point,
double[] a,
double[] b,
double lineDistTol,
double extendTol)
{
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(point, a) <= lineDistTol;
}
double len = Math.Sqrt(lenSq);
double t = ((point[0] - a[0]) * dx + (point[1] - a[1]) * dy + (point[2] - a[2]) * dz) / lenSq;
double tMin = -extendTol / len;
double tMax = 1.0 + extendTol / len;
if (t < tMin || t > tMax)
{
return false;
}
double dist = PointToLineDistance3D(point, a, b);
return dist <= lineDistTol;
}
static bool IsPointOnSegment2D(
double px,
double py,
double ax,
double ay,
double bx,
double by,
double lineDistTol,
double extendTol)
{
double dx = bx - ax;
double dy = by - ay;
double lenSq = dx * dx + dy * dy;
if (lenSq < 1e-18)
{
return Distance2D(px, py, ax, ay) <= lineDistTol;
}
double len = Math.Sqrt(lenSq);
double t = ((px - ax) * dx + (py - ay) * dy) / lenSq;
double tMin = -extendTol / len;
double tMax = 1.0 + extendTol / len;
if (t < tMin || t > tMax)
{
return false;
}
double vx = px - ax;
double vy = py - ay;
double cross = Math.Abs(vx * dy - vy * dx) / len;
return cross <= lineDistTol;
}
static bool TryResolvePartModelForView(
View view,
out ModelDoc2 partModel,
out Component2? scopeComponent,
out string failReason)
{
partModel = null!;
scopeComponent = null;
failReason = "";
ModelDoc2? refModel = null;
try
{
refModel = view.ReferencedDocument as ModelDoc2;
}
catch
{
refModel = null;
}
if (refModel == null)
{
failReason = "无法获取视图关联文档";
return false;
}
int docType;
try
{
docType = refModel.GetType();
}
catch
{
failReason = "无法识别视图关联文档类型";
return false;
}
if (docType == (int)swDocumentTypes_e.swDocPART)
{
partModel = refModel;
return true;
}
if (docType == (int)swDocumentTypes_e.swDocASSEMBLY)
{
return TryResolveSheetMetalPartFromView(view, out partModel, out scopeComponent, out failReason);
}
failReason = $"视图关联文档类型({docType})不支持";
return false;
}
static bool TryResolveSheetMetalPartFromView(
View view,
out ModelDoc2 partModel,
out Component2? scopeComponent,
out string failReason)
{
partModel = null!;
scopeComponent = null;
failReason = "";
object[]? comps;
try
{
comps = (object[])view.GetVisibleComponents();
}
catch
{
comps = null;
}
if (comps == null || comps.Length == 0)
{
failReason = "装配视图中无可见组件";
return false;
}
ModelDoc2? bestDoc = null;
Component2? bestComp = null;
foreach (Component2 comp in comps)
{
if (comp == null)
{
continue;
}
ModelDoc2? compDoc;
try
{
compDoc = comp.GetModelDoc2() as ModelDoc2;
}
catch
{
continue;
}
if (compDoc == null || compDoc.GetType() != (int)swDocumentTypes_e.swDocPART)
{
continue;
}
bestDoc = compDoc;
bestComp = comp;
break;
}
if (bestDoc == null)
{
failReason = "装配视图中未找到零件组件";
return false;
}
partModel = bestDoc;
scopeComponent = bestComp;
return true;
}
static IDisposable ActivateViewReferencedPartConfiguration(
ModelDoc2 partModel,
View view,
Component2? scopeComponent)
=> new ViewReferencedConfigurationScope(partModel, view, scopeComponent);
sealed class ViewReferencedConfigurationScope : IDisposable
{
readonly ModelDoc2 _model;
readonly string _restoreName;
readonly bool _switched;
bool _disposed;
public ViewReferencedConfigurationScope(ModelDoc2 partModel, View view, Component2? scopeComponent)
{
_model = partModel;
_restoreName = "";
_switched = false;
try
{
_restoreName = partModel.ConfigurationManager?.ActiveConfiguration?.Name ?? "";
}
catch
{
// ignored
}
string refCfg = "";
try
{
refCfg = (scopeComponent?.ReferencedConfiguration ?? view.ReferencedConfiguration ?? "").Trim();
}
catch
{
refCfg = "";
}
if (string.IsNullOrEmpty(refCfg))
{
Console.WriteLine("[bend_line_endpoint] 视图未指定引用配置,使用零件当前活动配置");
return;
}
if (string.Equals(_restoreName, refCfg, StringComparison.OrdinalIgnoreCase))
{
return;
}
try
{
Console.WriteLine(
$"[bend_line_endpoint] 切换零件到视图引用配置「{refCfg}」(原「{_restoreName}」)");
partModel.ShowConfiguration2(refCfg);
_switched = true;
try
{
partModel.EditRebuild3();
}
catch
{
// ignored
}
}
catch (Exception ex)
{
Console.WriteLine($"[bend_line_endpoint] 切换配置失败: {ex.Message}");
}
}
public void Dispose()
{
if (_disposed || !_switched || string.IsNullOrEmpty(_restoreName))
{
return;
}
_disposed = true;
try
{
_model.ShowConfiguration2(_restoreName);
_model.EditRebuild3();
}
catch
{
// ignored
}
}
}
static bool TryModelPointToSheetXY(ISldWorks swApp, View view, double[] model3, out double sheetX, out double sheetY)
{
sheetX = sheetY = 0;
try
{
var math = swApp.IGetMathUtility();
if (math == null)
{
return false;
}
var mp = (MathPoint)math.CreatePoint(model3);
if (mp == null)
{
return false;
}
var modelToSheet = (MathTransform)view.ModelToViewTransform;
if (modelToSheet == null)
{
return false;
}
mp = (MathPoint)mp.MultiplyTransform(modelToSheet);
var arr = (double[])mp.ArrayData;
if (arr == null || arr.Length < 2)
{
return false;
}
sheetX = arr[0];
sheetY = arr[1];
return true;
}
catch
{
return false;
}
}
static double PointToLineDistance3D(double[] p, double[] lineA, double[] lineB)
{
double dx = lineB[0] - lineA[0];
double dy = lineB[1] - lineA[1];
double dz = lineB[2] - lineA[2];
double lenSq = dx * dx + dy * dy + dz * dz;
if (lenSq < 1e-18)
{
return Distance3D(p, lineA);
}
double t = ((p[0] - lineA[0]) * dx + (p[1] - lineA[1]) * dy + (p[2] - lineA[2]) * dz) / lenSq;
var cx = lineA[0] + t * dx;
var cy = lineA[1] + t * dy;
var cz = lineA[2] + t * dz;
return Distance3D(p, new[] { cx, cy, cz });
}
static bool PointsEqual3D(double[] a, double[] b, double tol)
=> Math.Abs(a[0] - b[0]) <= tol
&& Math.Abs(a[1] - b[1]) <= tol
&& Math.Abs(a[2] - b[2]) <= tol;
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 double Distance2D(double x0, double y0, double x1, double y1)
{
double dx = x1 - x0;
double dy = y1 - y0;
return Math.Sqrt(dx * dx + dy * dy);
}
static string FormatPointMm(double[] p)
=> $"({p[0] * 1000:F2},{p[1] * 1000:F2},{p[2] * 1000:F2}) mm";
static string FormatPointSheetMm(double x, double y)
=> $"({x * 1000:F2},{y * 1000:F2})";
static double[]? Sub3(double[] a, double[] b)
=> new[] { a[0] - b[0], a[1] - b[1], a[2] - b[2] };
static double[]? Normalize3D(double[] v)
{
double len = Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
if (len < 1e-12)
{
return null;
}
return new[] { v[0] / len, v[1] / len, v[2] / len };
}
static double[]? Normalize2D(double dx, double dy)
{
double len = Math.Sqrt(dx * dx + dy * dy);
if (len < 1e-12)
{
return null;
}
return new[] { dx / len, dy / len };
}
}
}
csharp
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
namespace tools
{
public static partial class selected_view_bend_line_endpoint_check
{
const int VisualPaddingPx = 32;
const int VisualMinCanvasW = 1200;
const int VisualMinCanvasH = 900;
sealed class LineSeg2D
{
public double X0;
public double Y0;
public double X1;
public double Y1;
}
static void SaveDebugVisualization(
List<ModelSegment3D> bendLines,
List<SheetSegment2D> visibleLinesSheet2D,
List<EndpointViolation> violations)
{
try
{
var visibleSheet2D = ConvertSheetSegmentsToLineSeg2D(visibleLinesSheet2D);
var bendSheet2D = ConvertBendSegmentsToSheet2D(bendLines);
if (bendSheet2D.Count == 0 && visibleSheet2D.Count == 0)
{
Console.WriteLine("[bend_line_endpoint] 可视化跳过:未收集到可绘制的线段");
return;
}
LogSegmentBounds("[bend_line_endpoint] 可见线 bbox(图纸)", visibleSheet2D);
LogSegmentBounds("[bend_line_endpoint] 折弯线 bbox(图纸)", bendSheet2D);
BuildFitLayout(
visibleSheet2D,
bendSheet2D,
out double minX,
out double minY,
out double maxX,
out double maxY,
out int canvasW,
out int canvasH);
var dir = GetDrwSourceDirectory();
Directory.CreateDirectory(dir);
string pngPath = Path.Combine(dir, "bend_line_endpoint_debug.png");
string svgPath = Path.Combine(dir, "bend_line_endpoint_debug.svg");
string svg = BuildLinesOnlySvg(
visibleSheet2D, bendSheet2D, minX, minY, maxX, maxY, canvasW, canvasH);
File.WriteAllText(svgPath, svg, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
using (var bmp = RenderLinesOnlyBitmap(
visibleSheet2D, bendSheet2D, minX, minY, maxX, maxY, canvasW, canvasH))
{
bmp.Save(pngPath, ImageFormat.Png);
}
Console.WriteLine(
$"[bend_line_endpoint] 可视化:可见线 {visibleSheet2D.Count} 条,折弯线 {bendSheet2D.Count} 条(图纸 2D)");
Console.WriteLine($"[bend_line_endpoint] 可视化 PNG: {pngPath}");
Console.WriteLine($"[bend_line_endpoint] 可视化 SVG: {svgPath}");
}
catch (Exception ex)
{
Console.WriteLine($"[bend_line_endpoint] 保存可视化失败: {ex.Message}");
}
}
static List<LineSeg2D> ConvertSheetSegmentsToLineSeg2D(List<SheetSegment2D> sheetSegments)
{
var list = new List<LineSeg2D>();
foreach (var seg in sheetSegments)
{
list.Add(new LineSeg2D { X0 = seg.X0, Y0 = seg.Y0, X1 = seg.X1, Y1 = seg.Y1 });
}
return list;
}
static List<LineSeg2D> ConvertBendSegmentsToSheet2D(List<ModelSegment3D> bendLines)
{
var list = new List<LineSeg2D>();
foreach (var seg in bendLines)
{
if (!seg.HasSheet2D)
{
continue;
}
if (Distance2D(seg.SheetX0, seg.SheetY0, seg.SheetX1, seg.SheetY1) < 1e-9)
{
continue;
}
list.Add(new LineSeg2D
{
X0 = seg.SheetX0,
Y0 = seg.SheetY0,
X1 = seg.SheetX1,
Y1 = seg.SheetY1,
});
}
return list;
}
static void LogSegmentBounds(string tag, List<LineSeg2D> segments)
{
if (segments.Count == 0)
{
Console.WriteLine($"{tag}: (空)");
return;
}
double minX = double.PositiveInfinity;
double minY = double.PositiveInfinity;
double maxX = double.NegativeInfinity;
double maxY = double.NegativeInfinity;
foreach (var seg in segments)
{
minX = Math.Min(minX, Math.Min(seg.X0, seg.X1));
minY = Math.Min(minY, Math.Min(seg.Y0, seg.Y1));
maxX = Math.Max(maxX, Math.Max(seg.X0, seg.X1));
maxY = Math.Max(maxY, Math.Max(seg.Y0, seg.Y1));
}
Console.WriteLine(
$"{tag}: X[{minX * 1000:F2},{maxX * 1000:F2}] mm "
+ $"Y[{minY * 1000:F2},{maxY * 1000:F2}] mm,{segments.Count} 条");
}
static void BuildFitLayout(
List<LineSeg2D> visibleLines,
List<LineSeg2D> bendLines,
out double minX,
out double minY,
out double maxX,
out double maxY,
out int canvasW,
out int canvasH)
{
minX = double.PositiveInfinity;
minY = double.PositiveInfinity;
maxX = double.NegativeInfinity;
maxY = double.NegativeInfinity;
foreach (var seg in visibleLines)
{
minX = Math.Min(minX, Math.Min(seg.X0, seg.X1));
minY = Math.Min(minY, Math.Min(seg.Y0, seg.Y1));
maxX = Math.Max(maxX, Math.Max(seg.X0, seg.X1));
maxY = Math.Max(maxY, Math.Max(seg.Y0, seg.Y1));
}
foreach (var seg in bendLines)
{
minX = Math.Min(minX, Math.Min(seg.X0, seg.X1));
minY = Math.Min(minY, Math.Min(seg.Y0, seg.Y1));
maxX = Math.Max(maxX, Math.Max(seg.X0, seg.X1));
maxY = Math.Max(maxY, Math.Max(seg.Y0, seg.Y1));
}
if (double.IsInfinity(minX))
{
minX = minY = 0;
maxX = maxY = 1;
}
double spanX = Math.Max(maxX - minX, 1e-9);
double spanY = Math.Max(maxY - minY, 1e-9);
double padX = spanX * 0.06;
double padY = spanY * 0.06;
minX -= padX;
minY -= padY;
maxX += padX;
maxY += padY;
spanX = maxX - minX;
spanY = maxY - minY;
double aspect = spanX / spanY;
if (aspect > (double)VisualMinCanvasW / VisualMinCanvasH)
{
canvasW = VisualMinCanvasW;
canvasH = Math.Max(VisualMinCanvasH, (int)Math.Round(VisualMinCanvasW / aspect));
}
else
{
canvasH = VisualMinCanvasH;
canvasW = Math.Max(VisualMinCanvasW, (int)Math.Round(VisualMinCanvasH * aspect));
}
}
static PointF DataToCanvas(
double x,
double y,
double minX,
double minY,
double maxX,
double maxY,
int canvasW,
int canvasH)
{
double spanX = Math.Max(maxX - minX, 1e-9);
double spanY = Math.Max(maxY - minY, 1e-9);
double drawW = canvasW - VisualPaddingPx * 2.0;
double drawH = canvasH - VisualPaddingPx * 2.0;
float px = VisualPaddingPx + (float)((x - minX) / spanX * drawW);
float py = VisualPaddingPx + (float)((maxY - y) / spanY * drawH);
return new PointF(px, py);
}
static Bitmap RenderLinesOnlyBitmap(
List<LineSeg2D> visibleLines,
List<LineSeg2D> bendLines,
double minX,
double minY,
double maxX,
double maxY,
int canvasW,
int canvasH)
{
var bmp = new Bitmap(canvasW, canvasH, PixelFormat.Format32bppArgb);
using (var g = Graphics.FromImage(bmp))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.Clear(Color.White);
using (var visiblePen = new Pen(Color.FromArgb(51, 65, 85), 1f))
using (var bendPen = new Pen(Color.FromArgb(220, 38, 38), 2.5f))
{
foreach (var line in visibleLines)
{
var a = DataToCanvas(line.X0, line.Y0, minX, minY, maxX, maxY, canvasW, canvasH);
var b = DataToCanvas(line.X1, line.Y1, minX, minY, maxX, maxY, canvasW, canvasH);
g.DrawLine(visiblePen, a, b);
}
foreach (var line in bendLines)
{
var a = DataToCanvas(line.X0, line.Y0, minX, minY, maxX, maxY, canvasW, canvasH);
var b = DataToCanvas(line.X1, line.Y1, minX, minY, maxX, maxY, canvasW, canvasH);
g.DrawLine(bendPen, a, b);
}
}
}
return bmp;
}
static string BuildLinesOnlySvg(
List<LineSeg2D> visibleLines,
List<LineSeg2D> bendLines,
double minX,
double minY,
double maxX,
double maxY,
int canvasW,
int canvasH)
{
var sb = new StringBuilder();
sb.AppendLine(
$"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{canvasW}\" height=\"{canvasH}\" viewBox=\"0 0 {canvasW} {canvasH}\">");
sb.AppendLine("<rect width=\"100%\" height=\"100%\" fill=\"#ffffff\"/>");
foreach (var line in visibleLines)
{
var a = DataToCanvas(line.X0, line.Y0, minX, minY, maxX, maxY, canvasW, canvasH);
var b = DataToCanvas(line.X1, line.Y1, minX, minY, maxX, maxY, canvasW, canvasH);
sb.AppendLine(FormattableString.Invariant(
$"<line x1=\"{a.X:F2}\" y1=\"{a.Y:F2}\" x2=\"{b.X:F2}\" y2=\"{b.Y:F2}\" stroke=\"#334155\" stroke-width=\"1\"/>"));
}
foreach (var line in bendLines)
{
var a = DataToCanvas(line.X0, line.Y0, minX, minY, maxX, maxY, canvasW, canvasH);
var b = DataToCanvas(line.X1, line.Y1, minX, minY, maxX, maxY, canvasW, canvasH);
sb.AppendLine(FormattableString.Invariant(
$"<line x1=\"{a.X:F2}\" y1=\"{a.Y:F2}\" x2=\"{b.X:F2}\" y2=\"{b.Y:F2}\" stroke=\"#dc2626\" stroke-width=\"2.5\"/>"));
}
sb.AppendLine("</svg>");
return sb.ToString();
}
static string GetDrwSourceDirectory()
{
try
{
var loc = typeof(selected_view_bend_line_endpoint_check).Assembly.Location;
if (!string.IsNullOrEmpty(loc))
{
var asmDir = Path.GetDirectoryName(loc);
if (!string.IsNullOrEmpty(asmDir))
{
var fromSwPlugin = Path.GetFullPath(Path.Combine(
asmDir, "..", "..", "..", "..", "share", "src", "drw"));
if (Directory.Exists(fromSwPlugin))
{
return fromSwPlugin;
}
var fromShare = Path.GetFullPath(Path.Combine(
asmDir, "..", "..", "..", "src", "drw"));
if (Directory.Exists(fromShare))
{
return fromShare;
}
}
}
}
catch
{
// ignored
}
return Path.Combine(AppContext.BaseDirectory, "bend_line_endpoint_debug");
}
}
}