using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;
using View = SolidWorks.Interop.sldworks.View;
namespace tools
{
public static partial class threadhol_dim
{
/// <summary>为 true 时优先从视图可见圆边定位攻牙孔(平板型式等)。</summary>
public static bool TapHoleSpecArcUseVisibleEdges = true;
/// <summary>攻牙孔规格圆弧:跳过已有同规格同心的视图草图圆弧(容差米)。</summary>
public static double TapHoleSpecArcSkipExistingCenterTolM = 0.0004;
/// <summary>每个攻牙孔绘制的圆弧张角(度),默认 270°。</summary>
public static double TapHoleSpecArcSpanDegrees = 270.0;
/// <summary>CreateArc 扫掠方向:1 或 -1,与 <see cref="TapHoleSpecArcSpanDegrees"/> 配合选取大弧。</summary>
public static short TapHoleSpecArcSweepDirection = 1;
static readonly Regex TapHoleNominalDiameterFromCallout = new(
@"M\s*([\d.]+)",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
/// <summary>
/// 工程图选中视图或零件:在攻牙孔位置于视图草图绘制规格直径圆弧(M4 → Ø4 mm 半圆等)。
/// </summary>
public static void InsertCosmeticThreadLinesFromDrawingSelection(ISldWorks swApp)
=> DrawTapHoleSpecDiameterArcsFromDrawingSelection(swApp);
/// <summary>在指定视图上为攻牙孔绘制规格直径草图圆弧。</summary>
public static void DrawTapHoleSpecDiameterArcsFromDrawingSelection(ISldWorks swApp)
{
try
{
var swModel = (ModelDoc2)swApp.ActiveDoc;
if (swModel == null)
{
Console.WriteLine("[tap_hole_arc] 没有活动文档");
return;
}
var swSelMgr = (SelectionMgr)swModel.SelectionManager;
if (swSelMgr.GetSelectedObjectCount() < 1)
{
Console.WriteLine("[tap_hole_arc] 请先选择工程图视图,或装配体视图中的零件/组件。");
return;
}
if (!TryResolveThreadHoleDrawingSelection(swModel, swSelMgr, out var view, out var partDoc, out var restrictComp, out var resolveMsg))
{
Console.WriteLine("[tap_hole_arc] " + resolveMsg);
return;
}
DrawTapHoleSpecDiameterArcsCore(swApp, swModel, view, partDoc, restrictComp);
}
catch (Exception ex)
{
Console.WriteLine($"[tap_hole_arc] 失败: {ex.Message}");
}
}
/// <summary>在指定工程图视图上为攻牙孔绘制规格直径草图圆(自动化出图等)。</summary>
public static void DrawTapHoleSpecDiameterArcsForDrawingView(ISldWorks swApp, View view, Component2? restrictComp = null)
{
if (view == null)
{
Console.WriteLine("[tap_hole_arc] 视图为空");
return;
}
var swModel = (ModelDoc2)swApp.ActiveDoc;
if (swModel == null)
{
Console.WriteLine("[tap_hole_arc] 没有活动文档");
return;
}
if (view.ReferencedDocument is not PartDoc partDoc)
{
Console.WriteLine("[tap_hole_arc] 视图关联文档不是零件。");
return;
}
DrawTapHoleSpecDiameterArcsCore(swApp, swModel, view, partDoc, restrictComp);
}
static void DrawTapHoleSpecDiameterArcsCore(
ISldWorks swApp,
ModelDoc2 drwModel,
View view,
PartDoc partDoc,
Component2? restrictComp)
{
bool restoreCmd = false;
try
{
swApp.CommandInProgress = true;
restoreCmd = true;
ExitDrawingSketchIfActive(drwModel);
var partModel = (ModelDoc2)partDoc;
var candidates = new List<Feature>();
CollectTargetFeaturesFromPart(partDoc, candidates);
if (candidates.Count == 0)
{
Console.WriteLine("[tap_hole_arc] 未找到螺纹/孔向导/装饰螺纹特征。");
return;
}
List<(Edge Edge, string? Callout)> dimTargets;
using (ActivateViewReferencedPartConfiguration(partModel, view, restrictComp))
{
if (ViewNeedsFlatPatternConfiguration(view))
{
TryEnsureFlatPatternFeaturesUnsuppressed(partModel);
try
{
partModel.EditRebuild3();
}
catch
{
// ignored
}
}
dimTargets = TapHoleSpecArcUseVisibleEdges
? BuildAllTapHoleArcDimTargets(swApp, view, partModel, candidates, restrictComp)
: BuildAllTapHoleCircleDimTargets(partModel, candidates);
}
var arcTargets = new List<(double[] CenterModel, double Nx, double Ny, double Nz, double DiameterMm, string Label)>();
foreach (var (edge, callout) in dimTargets)
{
if (!TryParseThreadNominalDiameterMm(callout, out double diameterMm))
continue;
if (!TryGetNearFullCircleCenterAndPlane(edge, out var center, out var nx, out var ny, out var nz))
continue;
string label = FormatTapHoleArcLabel(callout, diameterMm);
if (TryAddTapHoleArcTarget(arcTargets, center, nx, ny, nz, diameterMm, label))
Console.WriteLine($"[tap_hole_arc] 目标 {label} · 中心模型mm({center[0] * 1000:F2},{center[1] * 1000:F2},{center[2] * 1000:F2})");
}
if (arcTargets.Count == 0)
{
Console.WriteLine("[tap_hole_arc] 无攻牙孔可绘制(需特征名/标注含 M 规格,如 M4、M5x0.8)。");
return;
}
var specRadiiM = new List<double>();
foreach (var t in arcTargets)
{
double r = t.DiameterMm * 0.0005;
if (!specRadiiM.Exists(x => Math.Abs(x - r) < 1e-9))
specRadiiM.Add(r);
}
int existingArcCount = CountTapHoleSpecArcsInViewSketch(view, specRadiiM);
int expectedCount = arcTargets.Count;
Console.WriteLine(
$"[tap_hole_arc] 视图已有螺纹圆弧 {existingArcCount} 个,实际攻牙孔 {expectedCount} 个");
if (existingArcCount == expectedCount)
{
Console.WriteLine("[tap_hole_arc] 螺纹圆弧数量已满足,跳过绘制。");
return;
}
int created = DrawTapHoleSpecArcsInViewSketch(swApp, (DrawingDoc)drwModel, drwModel, view, arcTargets);
Console.WriteLine($"[tap_hole_arc] 视图「{view.Name}」完成:绘制 {created}/{arcTargets.Count} 个规格圆弧。");
}
finally
{
if (restoreCmd)
{
try
{
swApp.CommandInProgress = false;
}
catch
{
// ignored
}
}
}
}
static string FormatTapHoleArcLabel(string? callout, double diameterMm)
{
if (!string.IsNullOrWhiteSpace(callout))
{
string s = callout.Trim();
int dash = s.IndexOf('-');
if (dash > 0 && dash < s.Length - 1 && char.IsDigit(s[0]))
s = s.Substring(dash + 1).Trim();
if (TryParseThreadNominalDiameterMm(s, out _))
return s;
}
return $"M{diameterMm:0.##}";
}
/// <summary>每个攻牙孔一条目标:扫描全部可见圆边 + 平板展开实体补全(不按 M 规格去重)。</summary>
static List<(Edge ModelEdge, string? ThreadCalloutDisplay)> BuildAllTapHoleArcDimTargets(
ISldWorks swApp,
View view,
ModelDoc2 partModel,
List<Feature> candidates,
Component2? restrictComp)
{
var merged = BuildAllTapHoleArcDimTargetsFromVisibleEdges(view, partModel, candidates, restrictComp);
if (ViewNeedsFlatPatternConfiguration(view))
{
int before = merged.Count;
SupplementTapHoleArcTargetsFromFlatPatternBodies(partModel, candidates, merged);
if (merged.Count > before)
{
Console.WriteLine(
$"[tap_hole_arc] 展开实体按底孔半径补全 {merged.Count - before} 个孔(共 {merged.Count})");
}
}
if (merged.Count == 0)
{
Console.WriteLine("[tap_hole_arc] 可见圆边/展开实体均未匹配,回退特征面圆边...");
merged = BuildAllTapHoleCircleDimTargets(partModel, candidates);
}
return merged;
}
static List<(Edge ModelEdge, string? ThreadCalloutDisplay)> BuildAllTapHoleArcDimTargetsFromVisibleEdges(
View view,
ModelDoc2 partModel,
List<Feature> candidates,
Component2? restrictComp)
{
var merged = new List<(Edge ModelEdge, string? ThreadCalloutDisplay)>();
var seenEdges = new List<Edge>();
int scanned = 0;
int matched = 0;
foreach (Edge visEdge in EnumerateVisibleCircleEdges(view, restrictComp))
{
scanned++;
if (ContainsEdgeByReference(seenEdges, visEdge))
continue;
if (!TryResolveThreadHoleOwnerFromVisibleEdge(partModel, visEdge, candidates, out var owner))
continue;
string? callout = TryParseThreadCalloutFromHoleLikeFeature(owner);
if (string.IsNullOrWhiteSpace(callout))
{
try
{
callout = owner.Name;
}
catch
{
callout = null;
}
}
if (!TryParseThreadNominalDiameterMm(callout, out _))
continue;
matched++;
seenEdges.Add(visEdge);
merged.Add((visEdge, callout));
}
Console.WriteLine(
$"[tap_hole_arc] 可见圆边 {scanned} 条,攻牙匹配 {matched} 条(未按规格去重)");
return merged;
}
static void SupplementTapHoleArcTargetsFromFlatPatternBodies(
ModelDoc2 partModel,
List<Feature> candidates,
List<(Edge ModelEdge, string? ThreadCalloutDisplay)> sink)
{
var specs = CollectKnownThreadCallouts(candidates);
if (specs.Count == 0)
return;
var flatBodies = GetSheetMetalFlatPatternBodies(partModel);
if (flatBodies.Count == 0)
return;
foreach (Body2 body in flatBodies)
{
foreach (Edge edge in EnumerateBodyNearFullCircleEdges(body))
{
if (TapHoleArcTargetsContainEdge(sink, edge))
continue;
if (!TryGetCircleCenterRadiusM(edge, out _, out _, out _, out var edgeR))
continue;
foreach (string spec in specs)
{
if (!TryGetTapDrillRadiusMFromCallout(spec, out var expectedR))
continue;
double radTol = Math.Max(CircleRadiusAbsTolM, Math.Max(edgeR, expectedR) * CircleRadiusRelTol);
if (Math.Abs(edgeR - expectedR) > radTol)
continue;
sink.Add((edge, spec));
break;
}
}
}
}
static HashSet<string> CollectKnownThreadCallouts(List<Feature> candidates)
{
var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var feat in candidates)
{
string? callout = TryParseThreadCalloutFromHoleLikeFeature(feat);
if (!string.IsNullOrWhiteSpace(callout))
set.Add(callout);
}
return set;
}
static bool TapHoleArcTargetsContainEdge(
List<(Edge ModelEdge, string? ThreadCalloutDisplay)> sink,
Edge edge)
{
if (ContainsEdgeByReference(sink.ConvertAll(t => t.ModelEdge), edge))
return true;
if (!TryGetCircleCenterRadiusM(edge, out var cx, out var cy, out var cz, out var r))
return false;
const double centerTol = 0.0002;
foreach (var (existing, _) in sink)
{
if (!TryGetCircleCenterRadiusM(existing, out var ex, out var ey, out var ez, out var er))
continue;
double dx = cx - ex;
double dy = cy - ey;
double dz = cz - ez;
if (dx * dx + dy * dy + dz * dz <= centerTol * centerTol
&& Math.Abs(r - er) <= Math.Max(CircleRadiusAbsTolM, r * CircleRadiusRelTol))
return true;
}
return false;
}
/// <summary>每个螺纹/孔特征各取一条整圆边(不按 M 规格去重,供规格圆逐个绘制)。</summary>
static List<(Edge ModelEdge, string? ThreadCalloutDisplay)> BuildAllTapHoleCircleDimTargets(
ModelDoc2 partModel,
List<Feature> candidates)
{
bool anyHoleLike = false;
foreach (var f in candidates)
{
if (IsHoleLikeThreadSourceFeature(f))
{
anyHoleLike = true;
break;
}
}
var pairs = new List<(Edge e, string? c)>();
foreach (var feat in candidates)
{
if (anyHoleLike && IsCosmeticThreadType(feat))
continue;
var edges = new List<Edge>();
CollectThreadHoleCircleEdgesFromFeature(partModel, feat, edges);
string? callout = TryParseThreadCalloutFromHoleLikeFeature(feat);
if (string.IsNullOrWhiteSpace(callout))
{
try
{
callout = feat.Name;
}
catch
{
callout = null;
}
}
Edge? pick = null;
foreach (var e in edges)
{
if (IsNearFullCircleEdge(e, out _))
{
pick = e;
break;
}
}
if (pick == null)
continue;
pairs.Add((pick, callout));
}
var merged = new List<(Edge ModelEdge, string? ThreadCalloutDisplay)>();
foreach (var (e, c) in pairs)
{
int idx = -1;
for (int i = 0; i < merged.Count; i++)
{
if (ReferenceEquals(merged[i].ModelEdge, e))
{
idx = i;
break;
}
}
if (idx < 0)
merged.Add((e, c));
else if (c != null && merged[idx].ThreadCalloutDisplay == null)
merged[idx] = (e, c);
}
return merged;
}
/// <summary>从 <c>M4</c>、<c>4-M5</c>、<c>M8x1.25</c> 等解析公称直径(mm)。</summary>
static bool TryParseThreadNominalDiameterMm(string? callout, out double diameterMm)
{
diameterMm = 0;
if (string.IsNullOrWhiteSpace(callout))
return false;
string s = callout.Trim();
int dash = s.IndexOf('-');
if (dash > 0 && dash < s.Length - 1 && char.IsDigit(s[0]))
s = s.Substring(dash + 1).Trim();
var m = TapHoleNominalDiameterFromCallout.Match(s);
if (!m.Success)
return false;
return double.TryParse(
m.Groups[1].Value,
System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture,
out diameterMm) && diameterMm > 0;
}
static bool TryAddTapHoleArcTarget(
List<(double[] CenterModel, double Nx, double Ny, double Nz, double DiameterMm, string Label)> sink,
double[] center,
double nx,
double ny,
double nz,
double diameterMm,
string label)
{
const double centerTol = 0.00015;
foreach (var t in sink)
{
if (Math.Abs(t.DiameterMm - diameterMm) > 1e-6)
continue;
double dx = t.CenterModel[0] - center[0];
double dy = t.CenterModel[1] - center[1];
double dz = t.CenterModel[2] - center[2];
if (dx * dx + dy * dy + dz * dz <= centerTol * centerTol)
return false;
}
sink.Add((center, nx, ny, nz, diameterMm, label));
return true;
}
static bool TryGetNearFullCircleCenterAndPlane(
Edge edge,
out double[] center,
out double nx,
out double ny,
out double nz)
{
center = Array.Empty<double>();
nx = ny = nz = 0;
if (!IsNearFullCircleEdge(edge, out var curve) || curve == null)
return false;
double[]? cp = null;
try
{
cp = (double[])curve.CircleParams;
}
catch
{
return false;
}
if (cp == null || cp.Length < 7)
return false;
center = new[] { cp[0], cp[1], cp[2] };
nx = cp[3];
ny = cp[4];
nz = cp[5];
double len = Math.Sqrt(nx * nx + ny * ny + nz * nz);
if (len < 1e-12)
return false;
nx /= len;
ny /= len;
nz /= len;
return true;
}
static int DrawTapHoleSpecArcsInViewSketch(
ISldWorks swApp,
DrawingDoc drawingDoc,
ModelDoc2 drwModel,
View view,
List<(double[] CenterModel, double Nx, double Ny, double Nz, double DiameterMm, string Label)> targets)
{
ExitDrawingSketchIfActive(drwModel);
drwModel.ClearSelection2(true);
int created = 0;
try
{
TryActivateDrawingSheetAndView(drawingDoc, view);
if (view.GetSketch() == null)
{
Console.WriteLine("[tap_hole_arc] view.GetSketch() 为空,无法绘制。");
return 0;
}
drwModel.SketchManager.AddToDB = true;
drwModel.SketchManager.DisplayWhenAdded = true;
foreach (var (center, nx, ny, nz, diameterMm, label) in targets)
{
double radiusM = diameterMm * 0.0005;
if (!TryBuildTapHoleSpecArcSketchPoints(
swApp, view, drwModel, center, nx, ny, nz, radiusM,
out var skCenter, out var skStart, out var skEnd))
{
Console.WriteLine($"[tap_hole_arc] 投影失败,跳过 {label}");
continue;
}
if (ViewSketchHasSimilarSpecArc(view, skCenter, radiusM))
{
Console.WriteLine($"[tap_hole_arc] 已有相近圆弧,跳过 {label}");
continue;
}
try
{
// SW CreateArc:圆心、起点、终点(非三点定弧)
object? seg = drwModel.SketchManager.CreateArc(
skCenter[0], skCenter[1], skCenter[2],
skStart[0], skStart[1], skStart[2],
skEnd[0], skEnd[1], skEnd[2],
(short)TapHoleSpecArcSweepDirection);
if (seg != null)
{
created++;
Console.WriteLine(
$"[tap_hole_arc] 已绘制 {label} Ø{diameterMm:0.##} mm 圆弧({TapHoleSpecArcSpanDegrees:0.##}°)");
}
}
catch (Exception ex)
{
Console.WriteLine($"[tap_hole_arc] CreateArc 失败 {label}: {ex.Message}");
}
}
drwModel.SketchManager.AddToDB = false;
try
{
drwModel.SketchManager.InsertSketch(false);
}
catch
{
// ignored
}
}
finally
{
ExitDrawingSketchIfActive(drwModel);
}
return created;
}
/// <summary>在视图草图空间构造圆心/起/终点,保证半径等于规格半径。</summary>
static bool TryBuildTapHoleSpecArcSketchPoints(
ISldWorks swApp,
View view,
ModelDoc2 drwModel,
double[] centerModel,
double nx,
double ny,
double nz,
double radiusM,
out double[] skCenter,
out double[] skStart,
out double[] skEnd)
{
skCenter = skStart = skEnd = Array.Empty<double>();
if (!TryBuildPerpendicularUnit3(nx, ny, nz, out var ux, out var uy, out var uz))
return false;
double vx = ny * uz - nz * uy;
double vy = nz * ux - nx * uz;
double vz = nx * uy - ny * ux;
double vlen = Math.Sqrt(vx * vx + vy * vy + vz * vz);
if (vlen < 1e-12)
return false;
vx /= vlen;
vy /= vlen;
vz /= vlen;
if (!TryModelPointToViewSketchLocal(swApp, view, drwModel, centerModel, out skCenter))
return false;
// 在草图空间求平面基向量,避免分点投影后半径失真
if (!TryModelDirectionToViewSketchLocal(
swApp, view, drwModel, centerModel, ux, uy, uz, out var uSk))
return false;
if (!TryModelDirectionToViewSketchLocal(
swApp, view, drwModel, centerModel, vx, vy, vz, out var vSk))
return false;
if (!TryNormalize3(uSk, out uSk) || !TryNormalize3(vSk, out vSk))
return false;
double halfSpanRad = TapHoleSpecArcSpanDegrees * Math.PI / 360.0;
double cosNeg = Math.Cos(-halfSpanRad);
double sinNeg = Math.Sin(-halfSpanRad);
double cosPos = Math.Cos(halfSpanRad);
double sinPos = Math.Sin(halfSpanRad);
skStart = new[]
{
skCenter[0] + (uSk[0] * cosNeg + vSk[0] * sinNeg) * radiusM,
skCenter[1] + (uSk[1] * cosNeg + vSk[1] * sinNeg) * radiusM,
skCenter[2] + (uSk[2] * cosNeg + vSk[2] * sinNeg) * radiusM,
};
skEnd = new[]
{
skCenter[0] + (uSk[0] * cosPos + vSk[0] * sinPos) * radiusM,
skCenter[1] + (uSk[1] * cosPos + vSk[1] * sinPos) * radiusM,
skCenter[2] + (uSk[2] * cosPos + vSk[2] * sinPos) * radiusM,
};
return true;
}
static bool TryModelDirectionToViewSketchLocal(
ISldWorks swApp,
View view,
ModelDoc2 drwModel,
double[] originModel,
double dx,
double dy,
double dz,
out double[] dirSketch)
{
dirSketch = Array.Empty<double>();
const double scaleM = 0.005;
var offset = new[]
{
originModel[0] + dx * scaleM,
originModel[1] + dy * scaleM,
originModel[2] + dz * scaleM,
};
if (!TryModelPointToViewSketchLocal(swApp, view, drwModel, originModel, out var sk0))
return false;
if (!TryModelPointToViewSketchLocal(swApp, view, drwModel, offset, out var sk1))
return false;
dirSketch = new[]
{
sk1[0] - sk0[0],
sk1[1] - sk0[1],
sk1[2] - sk0[2],
};
return TryNormalize3(dirSketch, out dirSketch);
}
static bool TryNormalize3(double[] v, out double[] unit)
{
unit = v;
if (v == null || v.Length < 3)
return false;
double len = Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
if (len < 1e-15)
return false;
unit = new[] { v[0] / len, v[1] / len, v[2] / len };
return true;
}
static int CountTapHoleSpecArcsInViewSketch(View view, List<double> specRadiiM)
{
Sketch? sk = null;
try
{
sk = view.GetSketch() as Sketch;
}
catch
{
return 0;
}
if (sk == null || specRadiiM.Count == 0)
return 0;
object? segsObj = null;
try
{
segsObj = sk.GetSketchSegments();
}
catch
{
return 0;
}
if (segsObj is not object[] segs)
return 0;
int count = 0;
foreach (object? o in segs)
{
if (o is not SketchArc arc)
continue;
if (!TryGetSketchArcRadiusM(arc, out var r))
continue;
if (!TapHoleSpecRadiusMatchesAny(r, specRadiiM))
continue;
if (!IsPartialThreadSpecSketchArc(arc))
continue;
count++;
}
return count;
}
static bool TapHoleSpecRadiusMatchesAny(double radiusM, List<double> specRadiiM)
{
foreach (double spec in specRadiiM)
{
double rTol = Math.Max(TapHoleSpecArcSkipExistingCenterTolM * 0.5, spec * 0.05);
if (Math.Abs(radiusM - spec) <= rTol)
return true;
}
return false;
}
/// <summary>排除整圆(CreateCircle 或近 360° 圆弧),只计螺纹规格圆弧。</summary>
static bool IsPartialThreadSpecSketchArc(SketchArc arc)
{
if (!TryGetSketchArcSpanRadians(arc, out var span))
return true;
const double fullCircleRad = Math.PI * 2.0;
const double minSpan = 0.15;
const double fullTol = 0.25;
return span >= minSpan && span <= fullCircleRad - fullTol;
}
static bool TryGetSketchArcRadiusM(SketchArc arc, out double radiusM)
{
radiusM = 0;
try
{
radiusM = arc.GetRadius();
return radiusM > 1e-9;
}
catch
{
return false;
}
}
static bool TryGetSketchArcSpanRadians(SketchArc arc, out double span)
{
span = 0;
try
{
var c = (double[])arc.GetCenterPoint2();
var s = (double[])arc.GetStartPoint2();
var e = (double[])arc.GetEndPoint2();
if (c == null || s == null || e == null || c.Length < 3 || s.Length < 3 || e.Length < 3)
return false;
double r = arc.GetRadius();
if (r < 1e-9)
return false;
double chord = Math.Sqrt(
Math.Pow(e[0] - s[0], 2) + Math.Pow(e[1] - s[1], 2) + Math.Pow(e[2] - s[2], 2));
double ratio = Math.Min(1.0, Math.Max(-1.0, chord / (2.0 * r)));
span = 2.0 * Math.Asin(ratio);
return true;
}
catch
{
return false;
}
}
static bool ViewSketchHasSimilarSpecArc(View view, double[] skCenter, double radiusM)
{
Sketch? sk = null;
try
{
sk = view.GetSketch() as Sketch;
}
catch
{
return false;
}
if (sk == null)
return false;
object? segsObj = null;
try
{
segsObj = sk.GetSketchSegments();
}
catch
{
return false;
}
if (segsObj is not object[] segs)
return false;
double tol = TapHoleSpecArcSkipExistingCenterTolM;
double rTol = Math.Max(tol, radiusM * 0.05);
foreach (object? o in segs)
{
if (o is not SketchArc arc)
continue;
if (!TryGetSketchArcRadiusM(arc, out var r))
continue;
if (Math.Abs(r - radiusM) > rTol)
continue;
if (!IsPartialThreadSpecSketchArc(arc))
continue;
if (!TryGetSketchArcCenter(arc, out var cx, out var cy, out var cz))
continue;
double dx = cx - skCenter[0];
double dy = cy - skCenter[1];
double dz = cz - skCenter[2];
if (dx * dx + dy * dy + dz * dz <= tol * tol)
return true;
}
return false;
}
static bool TryGetSketchArcCenter(SketchArc arc, out double cx, out double cy, out double cz)
{
cx = cy = cz = 0;
try
{
var c = (double[])arc.GetCenterPoint2();
if (c == null || c.Length < 3)
return false;
cx = c[0];
cy = c[1];
cz = c[2];
return true;
}
catch
{
return false;
}
}
static bool TryBuildPerpendicularUnit3(double ax, double ay, double az, out double px, out double py, out double pz)
{
px = py = pz = 0;
double ux = 1, uy = 0, uz = 0;
if (Math.Abs(ax * ux + ay * uy + az * uz) > 0.9)
{
ux = 0;
uy = 1;
uz = 0;
}
px = ay * uz - az * uy;
py = az * ux - ax * uz;
pz = ax * uy - ay * ux;
double plen = Math.Sqrt(px * px + py * py + pz * pz);
if (plen < 1e-12)
return false;
px /= plen;
py /= plen;
pz /= plen;
return true;
}
static bool TryModelPointToViewSketchLocal(
ISldWorks swApp,
View view,
ModelDoc2 drwModel,
double[] model3,
out double[] sketch3)
{
sketch3 = Array.Empty<double>();
if (!TryModelPointToSheetXY(swApp, view, model3, out var sheetX, out var sheetY))
return false;
return TrySheetPointToViewSketchLocal(
swApp, view, drwModel, new[] { sheetX, sheetY, 0 }, out sketch3);
}
static bool TrySheetPointToViewSketchLocal(
ISldWorks swApp,
View view,
ModelDoc2 drwModel,
double[] sheet3,
out double[] sketch3)
{
sketch3 = Array.Empty<double>();
try
{
Sketch? sk = null;
try
{
if (drwModel.SketchManager.ActiveSketch != null)
sk = (Sketch)drwModel.SketchManager.ActiveSketch;
}
catch
{
// ignored
}
sk ??= view.GetSketch() as Sketch;
if (sk == null)
return false;
var math = swApp.IGetMathUtility();
if (math == null)
return false;
var mp = (MathPoint)math.CreatePoint(sheet3);
if (mp == null)
return false;
var sheetToSketch = (MathTransform)sk.ModelToSketchTransform;
if (sheetToSketch == null)
return false;
mp = (MathPoint)mp.MultiplyTransform(sheetToSketch);
var arr = (double[])mp.ArrayData;
if (arr == null || arr.Length < 3)
return false;
sketch3 = new[] { arr[0], arr[1], arr[2] };
return true;
}
catch
{
return false;
}
}
}
}