5499 lines
229 KiB
C#
5499 lines
229 KiB
C#
using ClosedXML.Excel;
|
||
using SolidWorks.Interop.sldworks;
|
||
using SolidWorks.Interop.swconst;
|
||
using System.Runtime.InteropServices;
|
||
using System.Text.Json;
|
||
using System.Text.Json.Serialization;
|
||
using System.Text.RegularExpressions;
|
||
|
||
namespace PartFeatureAudit;
|
||
|
||
internal class Program
|
||
{
|
||
const int Part = 1, Silent = 1, ReadOnly = 2;
|
||
static readonly List<Row> Rows = new();
|
||
static readonly List<SkillFlowStep> Skills = new();
|
||
static readonly HashSet<IntPtr> SeenSketch = new();
|
||
static readonly HashSet<string> EmittedRefPlaneCreation = new(StringComparer.OrdinalIgnoreCase);
|
||
static readonly Dictionary<string, int> FeatureStepByName = new(StringComparer.OrdinalIgnoreCase);
|
||
static readonly Dictionary<IntPtr, string> SketchPlanNameById = new();
|
||
static readonly Dictionary<IntPtr, HashSet<IntPtr>> SketchSelectedContourSegmentIdsBySketchId = new();
|
||
static int SketchPlanCounter;
|
||
static SldWorks CurrentSwApp;
|
||
|
||
class Row
|
||
{
|
||
public string PartName = "", Feature = "", Type = "", Category = "", Sketch = "", SketchPlane = "", Dims = "", Rels = "", Note = "";
|
||
public int Step, Depth;
|
||
}
|
||
|
||
class SkillFlowStep
|
||
{
|
||
[JsonPropertyName("step")]
|
||
public int Step { get; set; }
|
||
[JsonPropertyName("skill")]
|
||
public string Skill { get; set; } = "";
|
||
[JsonPropertyName("args")]
|
||
public Dictionary<string, object> Args { get; set; } = new();
|
||
[JsonPropertyName("source_feature")]
|
||
public string SourceFeature { get; set; } = "";
|
||
[JsonPropertyName("note")]
|
||
public string Note { get; set; } = "";
|
||
}
|
||
|
||
class SkillFlowValidationResult
|
||
{
|
||
public bool Ok { get; set; } = true;
|
||
public int TotalSteps { get; set; }
|
||
public List<string> SkillsUsed { get; set; } = new();
|
||
public List<string> Errors { get; set; } = new();
|
||
public List<string> Warnings { get; set; } = new();
|
||
}
|
||
|
||
class SketchReferenceDebugInfo
|
||
{
|
||
public string DirectStatus = "";
|
||
public string EditSketchStatus = "";
|
||
public string RollbackStatus = "";
|
||
public string RefetchedSketchStatus = "";
|
||
public string FinalEntityType = "";
|
||
}
|
||
|
||
class SketchReferenceResult
|
||
{
|
||
public string Description = "";
|
||
public string EntityType = "";
|
||
public int EntityTypeCode;
|
||
public string Kind = "unresolved";
|
||
public string ReferenceName = "";
|
||
public string PersistReferenceHex = "";
|
||
public int FaceIndex;
|
||
public int HostFeatureStep;
|
||
public string HostFeatureName = "";
|
||
public string SurfaceType = "";
|
||
public double AreaMm2;
|
||
public double[] CenterMm = Array.Empty<double>();
|
||
public double[] BoxMm = Array.Empty<double>();
|
||
public double[] Normal = Array.Empty<double>();
|
||
|
||
public string PlaneName => !string.IsNullOrWhiteSpace(ReferenceName) ? ReferenceName : Description;
|
||
|
||
public Dictionary<string, object> ToSkillArgs()
|
||
{
|
||
var args = new Dictionary<string, object>
|
||
{
|
||
["reference_kind"] = Kind,
|
||
["reference"] = Description
|
||
};
|
||
if (FaceIndex > 0)
|
||
{
|
||
args["face_index"] = FaceIndex;
|
||
args["face_index_scope"] = "SWagent CommonExecutor.ListFaces order";
|
||
args["selection_strategy"] = "create_face_sketch_by_index";
|
||
}
|
||
if (!string.IsNullOrWhiteSpace(HostFeatureName)) args["host_feature"] = HostFeatureName;
|
||
if (HostFeatureStep > 0) args["host_step"] = HostFeatureStep;
|
||
if (!string.IsNullOrWhiteSpace(SurfaceType)) args["surface_type"] = SurfaceType;
|
||
if (AreaMm2 > 0) args["area_mm2"] = AreaMm2;
|
||
if (CenterMm.Length > 0) args["center_mm"] = CenterMm;
|
||
if (BoxMm.Length > 0) args["box_mm"] = BoxMm;
|
||
if (Normal.Length > 0) args["normal"] = Normal;
|
||
if (Normal.Length >= 3)
|
||
{
|
||
args["normal_x"] = Normal[0];
|
||
args["normal_y"] = Normal[1];
|
||
args["normal_z"] = Normal[2];
|
||
}
|
||
if (!string.IsNullOrWhiteSpace(ReferenceName)) args["reference_name"] = ReferenceName;
|
||
if (Kind == "datum_plane" && !string.IsNullOrWhiteSpace(PlaneName)) args["plane_name"] = PlaneName;
|
||
if (!string.IsNullOrWhiteSpace(PersistReferenceHex)) args["persist_ref_hex"] = PersistReferenceHex;
|
||
if (EntityTypeCode != 0) args["entity_type_code"] = EntityTypeCode;
|
||
return args;
|
||
}
|
||
}
|
||
|
||
class RefPlaneBuildInfo
|
||
{
|
||
public bool CanCreate;
|
||
public string BuildKind = "";
|
||
public string PlaneName = "";
|
||
public string BaseReferenceKind = "";
|
||
public string BaseReferenceName = "";
|
||
public int BaseFaceIndex;
|
||
public double OffsetMm;
|
||
public double OffsetAbsMm;
|
||
public bool ReverseDirection;
|
||
public bool ReversedReferenceDirection;
|
||
public double AngleDeg;
|
||
public string AxisName = "";
|
||
public List<Dictionary<string, object>> EdgeSignatures = new();
|
||
public Dictionary<string, object> FaceSignature = new();
|
||
public string Note = "";
|
||
}
|
||
|
||
[DllImport("ole32.dll", CharSet = CharSet.Unicode)] static extern int CLSIDFromProgID(string progId, out Guid clsid);
|
||
[DllImport("oleaut32.dll", PreserveSig = false)] [return: MarshalAs(UnmanagedType.IUnknown)] static extern object GetActiveObject(ref Guid clsid, IntPtr reserved);
|
||
|
||
[STAThread]
|
||
static void Main(string[] args)
|
||
{
|
||
try
|
||
{
|
||
string path = FirstPositionalArg(args, @"D:\Desktop\减速器\三维");
|
||
string outputDir = ReadOption(args, "--output-dir");
|
||
bool useActiveDocument = HasFlag(args, "--active");
|
||
Console.WriteLine("正在连接 SolidWorks...");
|
||
var sw = Connect() ?? throw new Exception("无法连接 SolidWorks");
|
||
|
||
if (useActiveDocument)
|
||
{
|
||
AuditActivePart(sw);
|
||
Save(string.IsNullOrWhiteSpace(outputDir) ? System.Environment.CurrentDirectory : outputDir);
|
||
}
|
||
else if (Directory.Exists(path))
|
||
{
|
||
foreach (var p in Directory.EnumerateFiles(path, "*.sldprt", SearchOption.AllDirectories)) AuditPartFile(sw, p);
|
||
Save(string.IsNullOrWhiteSpace(outputDir) ? path : outputDir);
|
||
}
|
||
else
|
||
{
|
||
if (Path.GetExtension(path).ToLowerInvariant() != ".sldprt")
|
||
throw new Exception("该程序只支持 .SLDPRT 零件文件或包含零件的目录");
|
||
AuditPartFile(sw, path);
|
||
Save(string.IsNullOrWhiteSpace(outputDir) ? Path.GetDirectoryName(path) ?? System.Environment.CurrentDirectory : outputDir);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine("程序出错:" + ex.Message);
|
||
Console.WriteLine(ex);
|
||
}
|
||
|
||
Console.WriteLine("按回车退出...");
|
||
if (args.Length == 0)
|
||
Console.ReadLine();
|
||
}
|
||
|
||
static string FirstPositionalArg(string[] args, string fallback)
|
||
{
|
||
for (int i = 0; i < args.Length; i++)
|
||
{
|
||
if (string.Equals(args[i], "--output-dir", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
i++;
|
||
continue;
|
||
}
|
||
|
||
if (string.Equals(args[i], "--active", StringComparison.OrdinalIgnoreCase))
|
||
continue;
|
||
|
||
if (!args[i].StartsWith("--", StringComparison.OrdinalIgnoreCase))
|
||
return args[i];
|
||
}
|
||
|
||
return fallback;
|
||
}
|
||
|
||
static string ReadOption(string[] args, string name)
|
||
{
|
||
for (int i = 0; i < args.Length - 1; i++)
|
||
if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase))
|
||
return args[i + 1];
|
||
|
||
return "";
|
||
}
|
||
|
||
static bool HasFlag(string[] args, string name)
|
||
{
|
||
return args.Any(arg => string.Equals(arg, name, StringComparison.OrdinalIgnoreCase));
|
||
}
|
||
|
||
static SldWorks Connect()
|
||
{
|
||
try
|
||
{
|
||
int hr = CLSIDFromProgID("SldWorks.Application", out var clsid);
|
||
if (hr < 0) Marshal.ThrowExceptionForHR(hr);
|
||
return (SldWorks)GetActiveObject(ref clsid, IntPtr.Zero);
|
||
}
|
||
catch
|
||
{
|
||
try
|
||
{
|
||
var t = Type.GetTypeFromProgID("SldWorks.Application");
|
||
if (t == null) return null;
|
||
var sw = (SldWorks)Activator.CreateInstance(t);
|
||
if (sw != null) sw.Visible = true;
|
||
return sw;
|
||
}
|
||
catch { return null; }
|
||
}
|
||
}
|
||
|
||
static void AuditPartFile(SldWorks sw, string path)
|
||
{
|
||
CurrentSwApp = sw;
|
||
int e = 0, w = 0;
|
||
var doc = sw.OpenDoc6(Path.GetFullPath(path), Part, Silent | ReadOnly, "", ref e, ref w) as ModelDoc2;
|
||
if (doc == null) throw new Exception($"OpenDoc6 失败 errors={e}, warnings={w}, path={path}");
|
||
string title = Safe(() => doc.GetTitle(), Path.GetFileName(path));
|
||
try
|
||
{
|
||
Console.WriteLine("正在读取零件:" + title);
|
||
AuditPart(doc);
|
||
}
|
||
finally
|
||
{
|
||
try
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(title))
|
||
sw.CloseDoc(title);
|
||
}
|
||
catch
|
||
{
|
||
}
|
||
}
|
||
}
|
||
|
||
static void AuditActivePart(SldWorks sw)
|
||
{
|
||
CurrentSwApp = sw;
|
||
var doc = sw.ActiveDoc as ModelDoc2;
|
||
if (doc == null)
|
||
throw new Exception("No active SolidWorks document found.");
|
||
|
||
int docType = SafeInt(() => doc.GetType());
|
||
if (docType != (int)swDocumentTypes_e.swDocPART)
|
||
throw new Exception($"Active SolidWorks document is not a part. documentType={docType}");
|
||
|
||
string title = Safe(() => doc.GetTitle(), "active_part");
|
||
Console.WriteLine("Reading active SolidWorks part: " + title);
|
||
AuditPart(doc);
|
||
}
|
||
|
||
static void AuditPart(ModelDoc2 doc)
|
||
{
|
||
SeenSketch.Clear();
|
||
EmittedRefPlaneCreation.Clear();
|
||
FeatureStepByName.Clear();
|
||
SketchPlanNameById.Clear();
|
||
SketchSelectedContourSegmentIdsBySketchId.Clear();
|
||
SketchPlanCounter = 0;
|
||
string part = Safe(() => doc.GetTitle());
|
||
AddSkill("create_new_part", new(), part, "新建零件");
|
||
int step = 0;
|
||
var pendingTopLevelSketches = new List<Feature>();
|
||
Feature lastHolePlacementSketchFeature = null;
|
||
foreach (var it in Features(doc).Take(2000))
|
||
{
|
||
string name = Safe(() => it.f.Name), type = Safe(() => it.f.GetTypeName2());
|
||
if (ShouldSkipDefaultFeature(name, type)) continue;
|
||
if (IsSketch(type))
|
||
{
|
||
pendingTopLevelSketches.Add(it.f);
|
||
continue;
|
||
}
|
||
if (!string.IsNullOrWhiteSpace(name) && !FeatureStepByName.ContainsKey(name))
|
||
FeatureStepByName[name] = step + 1;
|
||
bool isRevolveBoss = IsRevolveBossFeature(name, type, it.f);
|
||
bool isRevolveCut = IsRevolveCutFeature(name, type, it.f);
|
||
bool isHelix = IsHelixFeature(name, type);
|
||
bool isSweep = IsSweepFeature(name, type);
|
||
bool isHoleWizard = IsHoleWizardFeature(name, type);
|
||
bool isGenericExtrude = !isHelix && !isSweep && !isRevolveBoss && !isRevolveCut &&
|
||
(IsExtrude(name, type, false) || IsExtrude(name, type, true));
|
||
Feature holePlacementSketchFeature = null;
|
||
if (isHoleWizard && pendingTopLevelSketches.Count > 0)
|
||
{
|
||
Feature placementSketchFeature = pendingTopLevelSketches[^1];
|
||
holePlacementSketchFeature = placementSketchFeature;
|
||
lastHolePlacementSketchFeature = placementSketchFeature;
|
||
pendingTopLevelSketches.Clear();
|
||
}
|
||
var row = new Row { PartName = part, Step = ++step, Depth = it.depth, Feature = name, Type = type, Category = Cat(name, type) };
|
||
bool keepsActiveSketch = isRevolveBoss || isRevolveCut;
|
||
if (IsCustomRefPlane(name, type)) AddRefPlaneCreationSkills(doc, it.f, name);
|
||
if (!isHoleWizard && isGenericExtrude)
|
||
RegisterSelectedContourSegmentFilter(doc, it.f);
|
||
if (!isHoleWizard) ReadSubSketches(doc, it.f, row, emitExitSketch: !keepsActiveSketch);
|
||
if (isGenericExtrude)
|
||
{
|
||
bool isCut = IsExtrude(name, type, true);
|
||
double depthMm = Depth(it.f);
|
||
bool reverseDir = ExtrudeReverseDir(it.f, out string directionSource);
|
||
int endCondition = ExtrudeEndCondition(it.f, true);
|
||
bool throughAll = IsThroughAllEndCondition(endCondition);
|
||
var selectedContours = ExtrudeSelectedContourSignatures(doc, it.f);
|
||
row.Dims = AppendText(row.Dims, depthMm > 0 ? $"拉伸深度={R(depthMm)}mm" : "拉伸深度=未读取到");
|
||
AddSkill(isCut ? "extrude_cut_mm" : "extrude_boss_mm", new() { ["depth_mm"] = depthMm, ["reverse_dir"] = reverseDir, ["direction_source"] = directionSource }, name, isCut ? "切除拉伸" : "凸台拉伸");
|
||
if (Skills.Count > 0)
|
||
AddExtrudeDirectionModelArgs(it.f, reverseDir, Skills[^1].Args);
|
||
if (Skills.Count > 0 && isCut)
|
||
AddExtrudeStartArgs(it.f, Skills[^1].Args);
|
||
if (Skills.Count > 0)
|
||
Skills[^1].Args.Remove("reverse_dir");
|
||
if (Skills.Count > 0)
|
||
{
|
||
if (endCondition >= 0)
|
||
{
|
||
Skills[^1].Args["end_condition_code"] = endCondition;
|
||
Skills[^1].Args["end_condition"] = EndConditionName(endCondition);
|
||
}
|
||
if (throughAll) Skills[^1].Args["through_all"] = true;
|
||
}
|
||
if (selectedContours.Count > 0 && Skills.Count > 0)
|
||
{
|
||
Skills[^1].Args["selected_contours"] = selectedContours;
|
||
Skills[^1].Args["selected_contour_count"] = selectedContours.Count;
|
||
}
|
||
}
|
||
else if (isRevolveBoss)
|
||
{
|
||
if (TryExtractRevolveBoss(doc, it.f, out var revolveArgs, out string revolveNote))
|
||
{
|
||
row.Dims = AppendText(row.Dims, revolveNote);
|
||
AddSkill("create_revolve_boss_mm", revolveArgs, name, "旋转凸台");
|
||
}
|
||
else
|
||
{
|
||
row.Note = AppendText(row.Note, revolveNote);
|
||
}
|
||
}
|
||
else if (isRevolveCut)
|
||
{
|
||
if (TryExtractRevolveCut(doc, it.f, out var revolveArgs, out string revolveNote))
|
||
{
|
||
row.Dims = AppendText(row.Dims, revolveNote);
|
||
AddSkill("create_revolve_cut_mm", revolveArgs, name, "旋转切除");
|
||
}
|
||
else
|
||
{
|
||
row.Note = AppendText(row.Note, revolveNote);
|
||
}
|
||
}
|
||
else if (isHelix)
|
||
{
|
||
if (TryExtractHelix(it.f, name, type, out var helixArgs, out string helixNote))
|
||
{
|
||
row.Note = AppendText(row.Note, helixNote);
|
||
AddSkill("create_helix_mm", helixArgs, name, "helix");
|
||
}
|
||
else
|
||
{
|
||
row.Note = AppendText(row.Note, helixNote);
|
||
}
|
||
}
|
||
else if (IsFilletFeature(name, type))
|
||
{
|
||
if (TryExtractSimpleFillet(doc, it.f, out var filletArgs, out string filletNote))
|
||
{
|
||
row.Note = AppendText(row.Note, filletNote);
|
||
AddSkill("select_edges_by_signature", new() { ["edge_signatures"] = filletArgs["edge_signatures"], ["source_feature"] = name }, name, "圆角边选择");
|
||
AddSkill("fillet_edges_radius_mm", new() { ["radius_mm"] = filletArgs["radius_mm"] }, name, "圆角");
|
||
}
|
||
else
|
||
{
|
||
row.Note = AppendText(row.Note, filletNote);
|
||
}
|
||
}
|
||
else if (IsChamferFeature(name, type))
|
||
{
|
||
if (TryExtractChamfer(doc, it.f, out var chamferArgs, out string chamferNote))
|
||
{
|
||
row.Note = AppendText(row.Note, chamferNote);
|
||
AddSkill("select_edges_by_signature", new() { ["edge_signatures"] = chamferArgs["edge_signatures"], ["source_feature"] = name }, name, "倒角边选择");
|
||
AddSkill("chamfer_edges_angle_distance_mm", new()
|
||
{
|
||
["distance_mm"] = chamferArgs["distance_mm"],
|
||
["angle_deg"] = chamferArgs["angle_deg"],
|
||
["other_distance_mm"] = chamferArgs["other_distance_mm"],
|
||
["chamfer_type"] = chamferArgs["chamfer_type"],
|
||
["equal_distance"] = chamferArgs["equal_distance"]
|
||
}, name, "倒角");
|
||
}
|
||
else
|
||
{
|
||
row.Note = AppendText(row.Note, chamferNote);
|
||
}
|
||
}
|
||
else if (TryExtractCircularPattern(doc, it.f, name, type, out var circularPatternArgs, out string circularPatternNote))
|
||
{
|
||
row.Note = AppendText(row.Note, circularPatternNote);
|
||
AddSkill("circular_pattern_feature", circularPatternArgs, name, "圆周阵列");
|
||
}
|
||
else if (TryExtractLinearPattern(doc, it.f, name, type, out var linearPatternArgs, out string linearPatternNote))
|
||
{
|
||
row.Note = AppendText(row.Note, linearPatternNote);
|
||
AddSkill("linear_pattern_feature_mm", linearPatternArgs, name, "线性阵列");
|
||
}
|
||
else if (TryExtractDraft(doc, it.f, name, type, out var draftArgs, out string draftNote))
|
||
{
|
||
row.Note = AppendText(row.Note, draftNote);
|
||
AddSkill("draft_faces_neutral_plane_mm", draftArgs, name, "拔模");
|
||
}
|
||
else if (TryExtractRib(it.f, name, type, out var ribArgs, out string ribNote))
|
||
{
|
||
row.Note = AppendText(row.Note, ribNote);
|
||
AddSkill("create_rib_mm", ribArgs, name, "筋");
|
||
}
|
||
else if (TryExtractReferenceAxis(doc, it.f, name, type, out var axisArgs, out string axisNote))
|
||
{
|
||
row.Note = AppendText(row.Note, axisNote);
|
||
AddSkill("create_reference_axis", axisArgs, name, "基准轴");
|
||
}
|
||
else if (TryExtractShell(doc, it.f, name, type, out var shellArgs, out string shellNote))
|
||
{
|
||
row.Note = AppendText(row.Note, shellNote);
|
||
AddSkill("shell_remove_face_at_point_mm", shellArgs, name, "抽壳");
|
||
}
|
||
else if (TryExtractHoleWizard(doc, it.f, name, type, out var holeArgs, out string holeNote))
|
||
{
|
||
if (!HasHoleWizardTargetFace(holeArgs))
|
||
{
|
||
Feature matchedPlacementSketch = null;
|
||
if (holePlacementSketchFeature != null &&
|
||
HoleWizardPointsMatchSketch(holePlacementSketchFeature, holeArgs))
|
||
matchedPlacementSketch = holePlacementSketchFeature;
|
||
else if (lastHolePlacementSketchFeature != null &&
|
||
HoleWizardPointsMatchSketch(lastHolePlacementSketchFeature, holeArgs))
|
||
matchedPlacementSketch = lastHolePlacementSketchFeature;
|
||
|
||
if (matchedPlacementSketch != null)
|
||
AddHoleWizardTargetFaceFromPlacementSketch(doc, matchedPlacementSketch, holeArgs);
|
||
}
|
||
row.Note = AppendText(row.Note, holeNote);
|
||
AddSkill("hole_wizard_threaded_mm", holeArgs, name, "异形孔向导");
|
||
}
|
||
else if (TryExtractMirror(doc, it.f, name, type, out var mirrorArgs, out string mirrorNote))
|
||
{
|
||
row.Note = AppendText(row.Note, mirrorNote);
|
||
AddSkill("mirror_feature_about_plane", mirrorArgs, name, "镜像");
|
||
}
|
||
else if (TryExtractWrap(doc, it.f, name, type, out var wrapArgs, out string wrapNote))
|
||
{
|
||
row.Note = AppendText(row.Note, wrapNote);
|
||
AddSkill("wrap_sketch_on_faces_mm", wrapArgs, name, "包覆");
|
||
}
|
||
else if (TryExtractSweep(doc, it.f, name, type, out string sweepSkill, out var sweepArgs, out string sweepNote))
|
||
{
|
||
row.Note = AppendText(row.Note, sweepNote);
|
||
AddSkill(sweepSkill, sweepArgs, name, sweepSkill.Contains("cut", StringComparison.OrdinalIgnoreCase) ? "扫描切除" : "扫描凸台");
|
||
}
|
||
else if (TryExtractLoftOrBoundary(it.f, name, type, out string blendSkill, out var blendArgs, out string blendNote))
|
||
{
|
||
row.Note = AppendText(row.Note, blendNote);
|
||
AddSkill(blendSkill, blendArgs, name, blendSkill.Contains("cut", StringComparison.OrdinalIgnoreCase) ? "放样/边界切除" : "放样/边界凸台");
|
||
}
|
||
Rows.Add(row);
|
||
}
|
||
Console.WriteLine($" -> 已读取 {step} 个特征");
|
||
}
|
||
|
||
static IEnumerable<(Feature f, int depth)> Features(ModelDoc2 doc)
|
||
{
|
||
var seen = new HashSet<IntPtr>();
|
||
Feature f = doc.FirstFeature() as Feature;
|
||
for (int guard = 0; f != null && guard < 2000; guard++)
|
||
{
|
||
var id = Id(f);
|
||
if (id != IntPtr.Zero && !seen.Add(id)) break;
|
||
yield return (f, 0);
|
||
f = f.GetNextFeature() as Feature;
|
||
}
|
||
}
|
||
|
||
static void ReadSubSketches(ModelDoc2 doc, Feature feat, Row row, bool emitExitSketch = true)
|
||
{
|
||
Feature sub = feat.GetFirstSubFeature() as Feature;
|
||
for (int i = 0; sub != null && i < 200; i++)
|
||
{
|
||
if (IsSketch(Safe(() => sub.GetTypeName2()))) ReadSketch(doc, sub, row, emitExitSketch);
|
||
sub = sub.GetNextSubFeature() as Feature;
|
||
}
|
||
}
|
||
|
||
static void ReadSketch(ModelDoc2 doc, Feature feat, Row row, bool emitExitSketch = true)
|
||
{
|
||
var id = Id(feat); if (id != IntPtr.Zero && !SeenSketch.Add(id)) return;
|
||
if (feat.GetSpecificFeature2() is not Sketch sk) { row.Note += "无法转 ISketch;"; return; }
|
||
string sourceName = Safe(() => feat.Name, row.Feature);
|
||
string name = CanonicalSketchPlanName(feat, sourceName);
|
||
var debug = new SketchReferenceDebugInfo();
|
||
var sketchReference = ResolveSketchPlane(doc, feat, sk, debug);
|
||
NormalizeGenericDatumPlaneReference(sk, sketchReference);
|
||
row.SketchPlane = sketchReference.Description;
|
||
row.Note = AppendText(row.Note, BuildSketchReferenceDebugNote(debug));
|
||
EnsureRefPlaneCreationSkills(doc, sketchReference, name);
|
||
AddSketchStartSkill(sketchReference, name);
|
||
row.Sketch = Segments(doc, sk, name, row.Type);
|
||
row.Dims = Dimensions(sk, feat);
|
||
row.Rels = Relations(sk);
|
||
if (emitExitSketch)
|
||
AddSkill("exit_sketch", new(), name, "退出草图");
|
||
}
|
||
|
||
static string CanonicalSketchPlanName(Feature feat, string sourceName)
|
||
{
|
||
IntPtr id = Id(feat);
|
||
if (id != IntPtr.Zero && SketchPlanNameById.TryGetValue(id, out string existing))
|
||
return existing;
|
||
|
||
string canonical = "草图" + (++SketchPlanCounter).ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||
if (id != IntPtr.Zero)
|
||
SketchPlanNameById[id] = canonical;
|
||
return canonical;
|
||
}
|
||
|
||
static SketchReferenceResult ResolveSketchPlane(ModelDoc2 doc, Feature feat, Sketch sk, SketchReferenceDebugInfo debug)
|
||
{
|
||
if (TryDescribeSketchReference(doc, sk, out var direct))
|
||
{
|
||
debug.DirectStatus = "直接获取成功";
|
||
debug.FinalEntityType = direct.EntityType;
|
||
return direct;
|
||
}
|
||
|
||
debug.DirectStatus = string.IsNullOrWhiteSpace(direct.EntityType) ? "直接获取失败:null" : $"直接获取失败:{direct.EntityType}";
|
||
|
||
if (TryDescribeSketchReferenceByEditSketch(doc, feat, out var editSketch, debug))
|
||
return editSketch;
|
||
|
||
return new SketchReferenceResult { Description = "未识别(引用实体可能已被消耗)" };
|
||
}
|
||
|
||
static bool TryDescribeSketchReference(ModelDoc2 doc, Sketch sk, out SketchReferenceResult result)
|
||
{
|
||
result = new SketchReferenceResult();
|
||
if (sk == null)
|
||
{
|
||
result.EntityType = "null";
|
||
return false;
|
||
}
|
||
|
||
int referenceType = 0;
|
||
object entity = SafeObj(() => sk.GetReferenceEntity(ref referenceType));
|
||
result.EntityTypeCode = referenceType;
|
||
result.EntityType = entity?.GetType().FullName ?? "null";
|
||
return TryDescribeReferenceEntity(doc, entity, result);
|
||
}
|
||
|
||
static bool TryDescribeSketchReferenceByEditSketch(ModelDoc2 doc, Feature feat, out SketchReferenceResult result, SketchReferenceDebugInfo debug)
|
||
{
|
||
result = new SketchReferenceResult();
|
||
if (doc == null || feat == null) return false;
|
||
|
||
string featureName = Safe(() => feat.Name);
|
||
bool enteredEditSketch = false;
|
||
try
|
||
{
|
||
SafeAction(() => doc.ClearSelection2(true));
|
||
bool selected = SafeBool(() => feat.Select2(false, -1));
|
||
if (!selected)
|
||
{
|
||
debug.EditSketchStatus = "编辑草图前选择草图失败";
|
||
return false;
|
||
}
|
||
|
||
enteredEditSketch = TryInvokeCom(doc, "EditSketch", Array.Empty<object>());
|
||
debug.EditSketchStatus = enteredEditSketch ? "EditSketch调用成功" : "EditSketch调用失败";
|
||
if (!enteredEditSketch) return false;
|
||
|
||
if (TryGetActiveSketch(doc, out var activeSketch) && TryDescribeSketchReference(doc, activeSketch, out result))
|
||
{
|
||
result.Description += "(编辑草图恢复)";
|
||
debug.FinalEntityType = result.EntityType;
|
||
return true;
|
||
}
|
||
|
||
if (TryGetFreshSketch(doc, featureName, out var refreshedSketch))
|
||
{
|
||
debug.RefetchedSketchStatus = "编辑草图后重新获取草图成功";
|
||
if (TryDescribeSketchReference(doc, refreshedSketch, out result))
|
||
{
|
||
result.Description += "(编辑草图恢复)";
|
||
debug.FinalEntityType = result.EntityType;
|
||
return true;
|
||
}
|
||
|
||
debug.FinalEntityType = result.EntityType;
|
||
return false;
|
||
}
|
||
|
||
debug.RefetchedSketchStatus = "编辑草图后重新获取草图失败";
|
||
return false;
|
||
}
|
||
finally
|
||
{
|
||
if (enteredEditSketch) TryExitSketch(doc);
|
||
SafeAction(() => doc.ClearSelection2(true));
|
||
}
|
||
}
|
||
|
||
static string BuildSketchReferenceDebugNote(SketchReferenceDebugInfo debug)
|
||
{
|
||
var parts = new List<string>();
|
||
if (!string.IsNullOrWhiteSpace(debug.DirectStatus)) parts.Add(debug.DirectStatus);
|
||
if (!string.IsNullOrWhiteSpace(debug.EditSketchStatus)) parts.Add(debug.EditSketchStatus);
|
||
if (!string.IsNullOrWhiteSpace(debug.RollbackStatus)) parts.Add(debug.RollbackStatus);
|
||
if (!string.IsNullOrWhiteSpace(debug.RefetchedSketchStatus)) parts.Add(debug.RefetchedSketchStatus);
|
||
if (!string.IsNullOrWhiteSpace(debug.FinalEntityType)) parts.Add("引用类型=" + debug.FinalEntityType);
|
||
return parts.Count == 0 ? "" : "草图基准调试=" + string.Join(";", parts);
|
||
}
|
||
|
||
static bool TryGetActiveSketch(ModelDoc2 doc, out Sketch sketch)
|
||
{
|
||
sketch = null;
|
||
var sketchManager = SafeObj(() => doc.SketchManager);
|
||
if (sketchManager == null) return false;
|
||
|
||
sketch = SafeObj(() => GetComProperty(sketchManager, "ActiveSketch")) as Sketch;
|
||
return sketch != null;
|
||
}
|
||
|
||
static void TryExitSketch(ModelDoc2 doc)
|
||
{
|
||
var sketchManager = SafeObj(() => doc.SketchManager);
|
||
if (!TryInvokeCom(sketchManager, "InsertSketch", true))
|
||
TryInvokeCom(doc, "EditSketch", Array.Empty<object>());
|
||
}
|
||
|
||
static bool TryGetFreshSketch(ModelDoc2 doc, string featureName, out Sketch sketch)
|
||
{
|
||
sketch = null;
|
||
if (doc == null || string.IsNullOrWhiteSpace(featureName)) return false;
|
||
|
||
var feature = FindFeatureByName(doc, featureName);
|
||
if (feature == null) return false;
|
||
sketch = SafeObj(() => feature.GetSpecificFeature2()) as Sketch;
|
||
return sketch != null;
|
||
}
|
||
|
||
static Feature FindFeatureByName(ModelDoc2 doc, string featureName)
|
||
{
|
||
foreach (var it in Features(doc).Take(4000))
|
||
{
|
||
if (string.Equals(Safe(() => it.f.Name), featureName, StringComparison.OrdinalIgnoreCase))
|
||
return it.f;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
static bool TryDescribeReferenceEntity(ModelDoc2 doc, object entity, SketchReferenceResult result)
|
||
{
|
||
result.Description = "";
|
||
if (entity == null) return false;
|
||
|
||
if (entity is Feature feature)
|
||
{
|
||
string featureType = Safe(() => feature.GetTypeName2(), "未知类型");
|
||
result.ReferenceName = Safe(() => feature.Name, "特征");
|
||
if (string.Equals(featureType, "RefPlane", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
result.Kind = "datum_plane";
|
||
result.Description = string.IsNullOrWhiteSpace(result.ReferenceName)
|
||
? "基准面 / RefPlane"
|
||
: result.ReferenceName + " / RefPlane";
|
||
return true;
|
||
}
|
||
|
||
if (string.Equals(featureType, "RefAxis", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
result.Kind = "datum_axis";
|
||
result.Description = string.IsNullOrWhiteSpace(result.ReferenceName)
|
||
? "基准轴 / RefAxis"
|
||
: result.ReferenceName + " / RefAxis";
|
||
return true;
|
||
}
|
||
|
||
result.Kind = "feature";
|
||
result.Description = result.ReferenceName + " / " + featureType;
|
||
return true;
|
||
}
|
||
|
||
if (entity is RefPlane plane)
|
||
{
|
||
string name = SafeObj(() => InvokeCom(plane, "Name"))?.ToString() ?? "";
|
||
if (IsGenericRefPlaneName(name))
|
||
name = FindRefPlaneFeatureName(doc, plane);
|
||
result.Kind = "datum_plane";
|
||
result.ReferenceName = string.IsNullOrWhiteSpace(name) ? "基准面" : name;
|
||
result.Description = result.ReferenceName + " / RefPlane";
|
||
return true;
|
||
}
|
||
|
||
if (entity is Face2 face)
|
||
{
|
||
result.Kind = "face";
|
||
FillFaceSelector(doc, face, result);
|
||
result.Description = DescribeFace(face, result);
|
||
result.PersistReferenceHex = TryGetPersistReferenceHex(doc, face);
|
||
return !string.IsNullOrWhiteSpace(result.Description);
|
||
}
|
||
|
||
result.Kind = "unknown_com_entity";
|
||
result.Description = entity.GetType().Name;
|
||
return true;
|
||
}
|
||
|
||
static bool IsGenericRefPlaneName(string name)
|
||
{
|
||
string n = (name ?? "").Trim();
|
||
return string.IsNullOrWhiteSpace(n)
|
||
|| string.Equals(n, "基准面", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(n, "基准面 / RefPlane", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(n, "Plane", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(n, "RefPlane", StringComparison.OrdinalIgnoreCase)
|
||
|| (n.Contains("RefPlane", StringComparison.OrdinalIgnoreCase)
|
||
&& !n.Any(char.IsDigit)
|
||
&& !n.Contains("front", StringComparison.OrdinalIgnoreCase)
|
||
&& !n.Contains("top", StringComparison.OrdinalIgnoreCase)
|
||
&& !n.Contains("right", StringComparison.OrdinalIgnoreCase)
|
||
&& !n.Contains("前")
|
||
&& !n.Contains("上")
|
||
&& !n.Contains("右"));
|
||
}
|
||
|
||
static void NormalizeGenericDatumPlaneReference(Sketch sketch, SketchReferenceResult reference)
|
||
{
|
||
if (sketch == null || reference == null) return;
|
||
if (!string.Equals(reference.Kind, "datum_plane", StringComparison.OrdinalIgnoreCase)) return;
|
||
if (!IsGenericRefPlaneName(reference.ReferenceName) && !IsGenericRefPlaneName(reference.Description)) return;
|
||
|
||
reference.Kind = "unresolved";
|
||
reference.ReferenceName = "";
|
||
reference.Description = "datum plane reference name unavailable from SolidWorks API";
|
||
}
|
||
|
||
static bool TryInferDefaultPlaneFromSketchTransform(Sketch sketch, out string planeKey)
|
||
{
|
||
planeKey = "";
|
||
if (!TrySketchLocalMmToModelMm(sketch, 0.0, 0.0, 0.0, out var origin) ||
|
||
!TrySketchLocalMmToModelMm(sketch, 0.0, 0.0, 1.0, out var zPoint))
|
||
return false;
|
||
|
||
double nx = zPoint[0] - origin[0];
|
||
double ny = zPoint[1] - origin[1];
|
||
double nz = zPoint[2] - origin[2];
|
||
var normal = Normalize(nx, ny, nz);
|
||
if (normal.Length < 3) return false;
|
||
|
||
double ax = Math.Abs(normal[0]);
|
||
double ay = Math.Abs(normal[1]);
|
||
double az = Math.Abs(normal[2]);
|
||
const double originToleranceMm = 0.05;
|
||
const double axisTolerance = 0.95;
|
||
|
||
if (ax >= ay && ax >= az && ax >= axisTolerance && Math.Abs(origin[0]) <= originToleranceMm)
|
||
{
|
||
planeKey = "right";
|
||
return true;
|
||
}
|
||
if (ay >= ax && ay >= az && ay >= axisTolerance && Math.Abs(origin[1]) <= originToleranceMm)
|
||
{
|
||
planeKey = "top";
|
||
return true;
|
||
}
|
||
if (az >= ax && az >= ay && az >= axisTolerance && Math.Abs(origin[2]) <= originToleranceMm)
|
||
{
|
||
planeKey = "front";
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
static string FindRefPlaneFeatureName(ModelDoc2 doc, RefPlane plane)
|
||
{
|
||
if (doc == null || plane == null) return "";
|
||
IntPtr targetId = Id(plane);
|
||
if (targetId == IntPtr.Zero) return "";
|
||
|
||
foreach (var it in Features(doc).Take(5000))
|
||
{
|
||
if (!string.Equals(Safe(() => it.f.GetTypeName2()), "RefPlane", StringComparison.OrdinalIgnoreCase))
|
||
continue;
|
||
|
||
object specific = SafeObj(() => it.f.GetSpecificFeature2());
|
||
if (specific == null) continue;
|
||
if (Id(specific) == targetId)
|
||
return Safe(() => it.f.Name, "");
|
||
}
|
||
|
||
return "";
|
||
}
|
||
|
||
static string DescribeFace(Face2 face, SketchReferenceResult result = null)
|
||
{
|
||
string host = result?.HostFeatureName ?? "";
|
||
try
|
||
{
|
||
if (string.IsNullOrWhiteSpace(host) && face.GetFeature() is Feature f) host = Safe(() => f.Name);
|
||
}
|
||
catch { }
|
||
|
||
string indexText = result != null && result.FaceIndex > 0 ? $"#{result.FaceIndex} " : "";
|
||
if (result != null && !string.IsNullOrWhiteSpace(result.SurfaceType))
|
||
{
|
||
string label = result.SurfaceType switch
|
||
{
|
||
"plane" => "平面面",
|
||
"cylinder" => "圆柱面",
|
||
"cone" => "圆锥面",
|
||
"sphere" => "球面",
|
||
_ => "面引用"
|
||
};
|
||
return string.IsNullOrWhiteSpace(host) ? $"{indexText}{label}" : $"{indexText}{label} / 宿主特征={host}";
|
||
}
|
||
|
||
try
|
||
{
|
||
if (face.GetSurface() is Surface s)
|
||
{
|
||
if (SafeBool(() => s.IsPlane())) return string.IsNullOrWhiteSpace(host) ? "平面面" : $"平面面 / 宿主特征={host}";
|
||
if (SafeBool(() => s.IsCylinder())) return string.IsNullOrWhiteSpace(host) ? "圆柱面" : $"圆柱面 / 宿主特征={host}";
|
||
if (SafeBool(() => s.IsCone())) return string.IsNullOrWhiteSpace(host) ? "圆锥面" : $"圆锥面 / 宿主特征={host}";
|
||
if (SafeBool(() => s.IsSphere())) return string.IsNullOrWhiteSpace(host) ? "球面" : $"球面 / 宿主特征={host}";
|
||
}
|
||
}
|
||
catch { }
|
||
|
||
return string.IsNullOrWhiteSpace(host) ? "面引用" : $"面引用 / 宿主特征={host}";
|
||
}
|
||
|
||
static void FillFaceSelector(ModelDoc2 doc, Face2 face, SketchReferenceResult result)
|
||
{
|
||
if (face == null || result == null) return;
|
||
|
||
try
|
||
{
|
||
if (face.GetFeature() is Feature host)
|
||
{
|
||
result.HostFeatureName = Safe(() => host.Name);
|
||
if (!string.IsNullOrWhiteSpace(result.HostFeatureName) &&
|
||
FeatureStepByName.TryGetValue(result.HostFeatureName, out int hostStep))
|
||
result.HostFeatureStep = hostStep;
|
||
}
|
||
}
|
||
catch { }
|
||
|
||
result.FaceIndex = FindFaceIndexLikeExecutor(doc, face);
|
||
result.AreaMm2 = R(SafeDouble(() => face.GetArea()) * 1_000_000.0);
|
||
result.BoxMm = TryGetFaceBoxMm(face);
|
||
if (result.BoxMm.Length >= 6)
|
||
{
|
||
result.CenterMm = new[]
|
||
{
|
||
R((result.BoxMm[0] + result.BoxMm[3]) / 2.0),
|
||
R((result.BoxMm[1] + result.BoxMm[4]) / 2.0),
|
||
R((result.BoxMm[2] + result.BoxMm[5]) / 2.0)
|
||
};
|
||
}
|
||
|
||
try
|
||
{
|
||
if (face.GetSurface() is Surface surface)
|
||
{
|
||
result.SurfaceType = SurfaceKind(surface);
|
||
if (SafeBool(() => surface.IsPlane()))
|
||
result.Normal = TryGetPlaneNormal(face, surface);
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
static int FindFaceIndexLikeExecutor(ModelDoc2 doc, Face2 target)
|
||
{
|
||
if (doc == null || target == null) return 0;
|
||
var targetId = Id(target);
|
||
if (targetId == IntPtr.Zero) return 0;
|
||
|
||
try
|
||
{
|
||
if (doc is not PartDoc part) return 0;
|
||
object bodiesObj = part.GetBodies2((int)swBodyType_e.swAllBodies, true);
|
||
int faceIndex = 0;
|
||
foreach (object bodyObj in ToObjectArray(bodiesObj))
|
||
{
|
||
object facesObj = InvokeCom(bodyObj, "GetFaces");
|
||
foreach (object faceObj in ToObjectArray(facesObj))
|
||
{
|
||
if (faceObj is not Face2 candidate) continue;
|
||
faceIndex++;
|
||
if (Id(candidate) == targetId) return faceIndex;
|
||
}
|
||
}
|
||
}
|
||
catch { }
|
||
|
||
return 0;
|
||
}
|
||
|
||
static string SurfaceKind(Surface surface)
|
||
{
|
||
if (surface == null) return "";
|
||
try
|
||
{
|
||
if (surface.IsPlane()) return "plane";
|
||
if (surface.IsCylinder()) return "cylinder";
|
||
if (surface.IsCone()) return "cone";
|
||
if (surface.IsSphere()) return "sphere";
|
||
}
|
||
catch { }
|
||
return "unknown";
|
||
}
|
||
|
||
static double[] TryGetFaceBoxMm(Face2 face)
|
||
{
|
||
try
|
||
{
|
||
var values = ToDoubleArray(face.GetBox());
|
||
if (values.Length >= 6)
|
||
return values.Take(6).Select(v => R(v * 1000.0)).ToArray();
|
||
}
|
||
catch { }
|
||
return Array.Empty<double>();
|
||
}
|
||
|
||
static double[] TryGetPlaneNormal(Face2 face, Surface surface)
|
||
{
|
||
try
|
||
{
|
||
var values = ToDoubleArray(GetComProperty(surface, "PlaneParams"));
|
||
if (values.Length == 0)
|
||
values = ToDoubleArray(InvokeCom(surface, "PlaneParams"));
|
||
if (values.Length >= 6)
|
||
{
|
||
var first = Normalize(values[0], values[1], values[2]);
|
||
if (first.Length >= 3) return ApplyFaceSense(face, first);
|
||
|
||
var second = Normalize(values[3], values[4], values[5]);
|
||
if (second.Length >= 3) return ApplyFaceSense(face, second);
|
||
}
|
||
}
|
||
catch { }
|
||
return Array.Empty<double>();
|
||
}
|
||
|
||
static double[] FaceNormal(Face2 face)
|
||
{
|
||
if (face == null) return Array.Empty<double>();
|
||
try
|
||
{
|
||
if (face.GetSurface() is Surface surface && SafeBool(() => surface.IsPlane()))
|
||
return TryGetPlaneNormal(face, surface);
|
||
}
|
||
catch { }
|
||
return Array.Empty<double>();
|
||
}
|
||
|
||
static double[] ApplyFaceSense(Face2 face, double[] normal)
|
||
{
|
||
if (normal.Length < 3) return normal;
|
||
try
|
||
{
|
||
if (face.FaceInSurfaceSense())
|
||
return new[] { R(-normal[0]), R(-normal[1]), R(-normal[2]) };
|
||
}
|
||
catch { }
|
||
return normal;
|
||
}
|
||
|
||
static void EnrichExtrudeDirectionHints()
|
||
{
|
||
for (int i = 0; i < Skills.Count; i++)
|
||
{
|
||
var step = Skills[i];
|
||
if (!string.Equals(step.Skill, "extrude_boss_mm", StringComparison.OrdinalIgnoreCase))
|
||
continue;
|
||
if (!TryGetDouble(step.Args, "depth_mm", out double depthMm) || depthMm <= 0)
|
||
continue;
|
||
|
||
var start = FindNearestFaceSketchCenter(i, -1);
|
||
var end = FindNearestFaceSketchCenter(i, 1);
|
||
if (start == null || end == null)
|
||
continue;
|
||
|
||
double dx = end[0] - start[0];
|
||
double dy = end[1] - start[1];
|
||
double dz = end[2] - start[2];
|
||
double length = Math.Sqrt(dx * dx + dy * dy + dz * dz);
|
||
if (length <= 1e-6)
|
||
continue;
|
||
|
||
double tolerance = Math.Max(1.0, depthMm * 0.05);
|
||
if (Math.Abs(length - depthMm) > tolerance)
|
||
continue;
|
||
|
||
step.Args["direction_x"] = R(dx / length);
|
||
step.Args["direction_y"] = R(dy / length);
|
||
step.Args["direction_z"] = R(dz / length);
|
||
step.Args["expected_end_x_mm"] = R(end[0]);
|
||
step.Args["expected_end_y_mm"] = R(end[1]);
|
||
step.Args["expected_end_z_mm"] = R(end[2]);
|
||
step.Args["direction_hint_source"] = "next_face_sketch_center";
|
||
}
|
||
}
|
||
|
||
static double[] FindNearestFaceSketchCenter(int startIndex, int direction)
|
||
{
|
||
for (int i = startIndex + direction; i >= 0 && i < Skills.Count; i += direction)
|
||
{
|
||
var step = Skills[i];
|
||
if (string.Equals(step.Skill, "extrude_boss_mm", StringComparison.OrdinalIgnoreCase) ||
|
||
string.Equals(step.Skill, "extrude_cut_mm", StringComparison.OrdinalIgnoreCase))
|
||
return null;
|
||
|
||
if (!string.Equals(step.Skill, "create_face_sketch_by_signature", StringComparison.OrdinalIgnoreCase))
|
||
continue;
|
||
|
||
if (TryGetDouble(step.Args, "center_x_mm", out double x) &&
|
||
TryGetDouble(step.Args, "center_y_mm", out double y) &&
|
||
TryGetDouble(step.Args, "center_z_mm", out double z))
|
||
return new[] { x, y, z };
|
||
|
||
if (step.Args.TryGetValue("center_mm", out object value))
|
||
{
|
||
var values = FlattenDoubles(value).Take(3).ToArray();
|
||
if (values.Length >= 3)
|
||
return values;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
static bool TryGetDouble(Dictionary<string, object> args, string key, out double value)
|
||
{
|
||
value = 0;
|
||
if (args == null || !args.TryGetValue(key, out object raw) || raw == null)
|
||
return false;
|
||
try
|
||
{
|
||
if (raw is double d) { value = d; return true; }
|
||
if (raw is float f) { value = f; return true; }
|
||
if (raw is int i) { value = i; return true; }
|
||
if (raw is long l) { value = l; return true; }
|
||
if (raw is decimal m) { value = (double)m; return true; }
|
||
return double.TryParse(raw.ToString(), out value);
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static void NormalizeAutoNumberedFeatureNames()
|
||
{
|
||
var counters = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||
var renameMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||
|
||
foreach (var step in Skills)
|
||
{
|
||
if (!IsFeatureCreatingSkill(step.Skill))
|
||
continue;
|
||
if (!TrySplitAutoNumberedName(step.SourceFeature, out string prefix, out int sourceNumber))
|
||
continue;
|
||
|
||
int expectedNumber = counters.TryGetValue(prefix, out int current) ? current + 1 : 1;
|
||
counters[prefix] = expectedNumber;
|
||
if (sourceNumber != expectedNumber)
|
||
renameMap[step.SourceFeature] = prefix + expectedNumber.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||
}
|
||
|
||
if (renameMap.Count == 0)
|
||
return;
|
||
|
||
foreach (var step in Skills)
|
||
{
|
||
step.SourceFeature = RewriteFeatureNameText(step.SourceFeature, renameMap);
|
||
step.Note = RewriteFeatureNameText(step.Note, renameMap);
|
||
|
||
var keys = step.Args.Keys.ToList();
|
||
foreach (string key in keys)
|
||
step.Args[key] = RewriteFeatureNameValue(step.Args[key], renameMap);
|
||
}
|
||
}
|
||
|
||
static bool IsFeatureCreatingSkill(string skill)
|
||
{
|
||
return string.Equals(skill, "extrude_boss_mm", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(skill, "extrude_cut_mm", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(skill, "create_revolve_boss_mm", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(skill, "create_revolve_cut_mm", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(skill, "fillet_edges_radius_mm", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(skill, "chamfer_edges_angle_distance_mm", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(skill, "linear_pattern_feature_mm", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(skill, "mirror_feature_about_plane", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(skill, "draft_faces_neutral_plane_mm", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(skill, "create_rib_mm", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(skill, "shell_remove_face_at_point_mm", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(skill, "hole_wizard_threaded_mm", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(skill, "wrap_sketch_on_faces_mm", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(skill, "swept_cut_circular_profile_mm", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(skill, "swept_boss_circular_profile_mm", StringComparison.OrdinalIgnoreCase);
|
||
}
|
||
|
||
static bool TrySplitAutoNumberedName(string name, out string prefix, out int number)
|
||
{
|
||
prefix = "";
|
||
number = 0;
|
||
if (string.IsNullOrWhiteSpace(name)) return false;
|
||
|
||
var match = Regex.Match(name.Trim(), @"^(?<prefix>.*?)(?<number>\d+)$");
|
||
if (!match.Success) return false;
|
||
|
||
prefix = match.Groups["prefix"].Value;
|
||
return !string.IsNullOrWhiteSpace(prefix)
|
||
&& int.TryParse(match.Groups["number"].Value, out number)
|
||
&& number > 0;
|
||
}
|
||
|
||
static object RewriteFeatureNameValue(object value, Dictionary<string, string> renameMap)
|
||
{
|
||
if (value == null) return value;
|
||
if (value is string text) return RewriteFeatureNameText(text, renameMap);
|
||
if (value is string[] strings) return strings.Select(item => RewriteFeatureNameText(item, renameMap)).ToArray();
|
||
if (value is List<string> stringList) return stringList.Select(item => RewriteFeatureNameText(item, renameMap)).ToList();
|
||
if (value is Dictionary<string, object> dict)
|
||
{
|
||
var keys = dict.Keys.ToList();
|
||
foreach (string key in keys)
|
||
dict[key] = RewriteFeatureNameValue(dict[key], renameMap);
|
||
return dict;
|
||
}
|
||
if (value is object[] objects)
|
||
return objects.Select(item => RewriteFeatureNameValue(item, renameMap)).ToArray();
|
||
return value;
|
||
}
|
||
|
||
static string RewriteFeatureNameText(string text, Dictionary<string, string> renameMap)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(text) || renameMap.Count == 0)
|
||
return text;
|
||
|
||
string pattern = string.Join("|", renameMap.Keys
|
||
.OrderByDescending(key => key.Length)
|
||
.Select(Regex.Escape));
|
||
return Regex.Replace(text, $"({pattern})(?!\\d)", match =>
|
||
renameMap.TryGetValue(match.Value, out string mapped) ? mapped : match.Value);
|
||
}
|
||
|
||
static double[] Normalize(double x, double y, double z)
|
||
{
|
||
double len = Math.Sqrt(x * x + y * y + z * z);
|
||
if (len < 0.5 || len > 1.5) return Array.Empty<double>();
|
||
return new[] { R(x / len), R(y / len), R(z / len) };
|
||
}
|
||
|
||
static string TryGetPersistReferenceHex(ModelDoc2 doc, object entity)
|
||
{
|
||
try
|
||
{
|
||
object raw = doc?.Extension?.GetPersistReference3(entity);
|
||
if (raw is byte[] bytes) return Convert.ToHexString(bytes);
|
||
if (raw is Array array)
|
||
{
|
||
var bytesFromObjects = new List<byte>();
|
||
foreach (object value in array)
|
||
{
|
||
if (value == null) continue;
|
||
bytesFromObjects.Add(Convert.ToByte(value));
|
||
}
|
||
if (bytesFromObjects.Count > 0) return Convert.ToHexString(bytesFromObjects.ToArray());
|
||
}
|
||
}
|
||
catch { }
|
||
|
||
return "";
|
||
}
|
||
|
||
static string Segments(ModelDoc2 doc, Sketch sk, string name, string type)
|
||
{
|
||
int line = 0, arc = 0, spline = 0, point = 0, other = 0;
|
||
var details = new List<string>();
|
||
var canonicalPoints = BuildSketchPointCanonicalMap(sk);
|
||
HashSet<IntPtr> selectedContourSegmentIds = null;
|
||
var sketchId = Id(sk);
|
||
if (sketchId != IntPtr.Zero)
|
||
SketchSelectedContourSegmentIdsBySketchId.TryGetValue(sketchId, out selectedContourSegmentIds);
|
||
var segmentPointIds = new HashSet<IntPtr>();
|
||
var segmentPointKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
foreach (object o in ToObjectArray(SafeObj(() => sk.GetSketchSegments())))
|
||
{
|
||
if (o is not SketchSegment s) continue;
|
||
var segmentId = Id(s);
|
||
if (selectedContourSegmentIds != null && (segmentId == IntPtr.Zero || !selectedContourSegmentIds.Contains(segmentId)))
|
||
continue;
|
||
int t = SafeInt(() => s.GetType());
|
||
RegisterSegmentPoints(s, canonicalPoints, segmentPointIds, segmentPointKeys);
|
||
if (t == 0 && Line(doc, sk, s, name, type, canonicalPoints, out string lineText)) { line++; details.Add(lineText); }
|
||
else if (t == 1 && Arc(doc, sk, s, name, type, canonicalPoints, out string arcText)) { arc++; details.Add(arcText); }
|
||
else if (s is SketchSpline && Spline(doc, sk, s, name, type, out string splineText)) { spline++; details.Add(splineText); }
|
||
else other++;
|
||
}
|
||
var freePointKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
foreach (double[] pointLocal in UserSketchPointsLocalMm(sk))
|
||
{
|
||
if (pointLocal.Length < 3) continue;
|
||
double px = pointLocal[0], py = pointLocal[1], pz = pointLocal[2];
|
||
if (segmentPointKeys.Contains(SketchPointCoordKey(px, py, pz))) continue;
|
||
if (!freePointKeys.Add(SketchPointCoordKey(px, py, pz))) continue;
|
||
if (SketchPointLocal(doc, sk, px, py, pz, name, type, out string pointText))
|
||
{
|
||
point++;
|
||
details.Add(pointText);
|
||
}
|
||
}
|
||
string summary = $"lines={line}, arcs={arc}, splines={spline}, points={point}, other={other}";
|
||
return details.Count == 0 ? summary : summary + ";" + string.Join(";", details.Take(80));
|
||
}
|
||
|
||
static void RegisterSegmentPoints(SketchSegment segment, Dictionary<IntPtr, double[]> canonicalPoints, HashSet<IntPtr> ids, HashSet<string> coordKeys = null)
|
||
{
|
||
if (segment == null || ids == null) return;
|
||
void Add(SketchPoint p)
|
||
{
|
||
if (p == null) return;
|
||
IntPtr id = Id(p);
|
||
if (id != IntPtr.Zero) ids.Add(id);
|
||
if (coordKeys != null && Pt3(p, canonicalPoints, out var x, out var y, out var z))
|
||
coordKeys.Add(SketchPointCoordKey(x, y, z));
|
||
}
|
||
|
||
try
|
||
{
|
||
if (segment is SketchLine line)
|
||
{
|
||
Add(line.GetStartPoint2() as SketchPoint);
|
||
Add(line.GetEndPoint2() as SketchPoint);
|
||
}
|
||
else if (segment is SketchArc arc)
|
||
{
|
||
Add(arc.GetCenterPoint2() as SketchPoint);
|
||
Add(arc.GetStartPoint2() as SketchPoint);
|
||
Add(arc.GetEndPoint2() as SketchPoint);
|
||
}
|
||
else if (segment is SketchSpline spline)
|
||
{
|
||
foreach (object pointObj in ToObjectArray(SafeObj(() => spline.GetPoints2())))
|
||
Add(pointObj as SketchPoint);
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
static bool IsSegmentOwnedSketchPoint(SketchPoint point, double x, double y, double z, HashSet<IntPtr> ids, HashSet<string> coordKeys)
|
||
{
|
||
IntPtr pointId = Id(point);
|
||
if (pointId != IntPtr.Zero && ids != null && ids.Contains(pointId)) return true;
|
||
return coordKeys != null && coordKeys.Contains(SketchPointCoordKey(x, y, z));
|
||
}
|
||
|
||
static List<double[]> UserSketchPointsLocalMm(Sketch sketch)
|
||
{
|
||
var result = new List<double[]>();
|
||
if (sketch == null) return result;
|
||
|
||
// SolidWorks ISketch.GetUserPoints2 returns only user-created sketch points.
|
||
// Its record layout is 8 doubles per point; the last three values are point location.
|
||
double[] raw = ToDoubleArray(SafeObj(() => sketch.GetUserPoints2()));
|
||
if (raw.Length == 0)
|
||
raw = ToDoubleArray(InvokeCom(sketch, "GetUserPoints2"));
|
||
if (raw.Length == 0)
|
||
return result;
|
||
|
||
int count = SafeInt(() => Convert.ToInt32(InvokeCom(sketch, "GetUserPointsCount")), 0);
|
||
int stride = 8;
|
||
if (count > 0 && raw.Length >= count * stride)
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
int offset = i * stride;
|
||
result.Add(new[] { raw[offset + 5] * 1000.0, raw[offset + 6] * 1000.0, raw[offset + 7] * 1000.0 });
|
||
}
|
||
return result;
|
||
}
|
||
|
||
if (raw.Length % stride == 0)
|
||
{
|
||
for (int offset = 0; offset + 7 < raw.Length; offset += stride)
|
||
result.Add(new[] { raw[offset + 5] * 1000.0, raw[offset + 6] * 1000.0, raw[offset + 7] * 1000.0 });
|
||
}
|
||
else if (raw.Length % 3 == 0)
|
||
{
|
||
for (int offset = 0; offset + 2 < raw.Length; offset += 3)
|
||
result.Add(new[] { raw[offset] * 1000.0, raw[offset + 1] * 1000.0, raw[offset + 2] * 1000.0 });
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
static string SketchPointCoordKey(double x, double y, double z)
|
||
{
|
||
const double tolMm = 0.001;
|
||
return $"{QuantizeMm(x, tolMm)}|{QuantizeMm(y, tolMm)}|{QuantizeMm(z, tolMm)}";
|
||
}
|
||
|
||
static long QuantizeMm(double value, double tolerance)
|
||
{
|
||
return (long)Math.Round(value / tolerance, MidpointRounding.AwayFromZero);
|
||
}
|
||
|
||
static bool SketchPoint(ModelDoc2 doc, Sketch sk, SketchPoint p, string name, string type, Dictionary<IntPtr, double[]> canonicalPoints, out string info)
|
||
{
|
||
info = "";
|
||
if (!Pt3(p, canonicalPoints, out var x, out var y, out var z)) return false;
|
||
return SketchPointLocal(doc, sk, x, y, z, name, type, out info);
|
||
}
|
||
|
||
static bool SketchPointLocal(ModelDoc2 doc, Sketch sk, double x, double y, double z, string name, string type, out string info)
|
||
{
|
||
info = "";
|
||
if (!TrySketchLocalMmToModelMm(sk, x, y, z, out var pointModel)) return false;
|
||
AddSkill("draw_point_mm", new()
|
||
{
|
||
["coordinate_system"] = "model",
|
||
["point_model_mm"] = pointModel
|
||
}, name, type);
|
||
info = $"point: model=({pointModel[0]},{pointModel[1]},{pointModel[2]})mm";
|
||
return true;
|
||
}
|
||
|
||
static bool Line(ModelDoc2 doc, Sketch sk, SketchSegment s, string name, string type, Dictionary<IntPtr, double[]> canonicalPoints, out string info)
|
||
{
|
||
info = "";
|
||
if (s is not SketchLine l) return false;
|
||
var rawStartPoint = l.GetStartPoint2() as SketchPoint;
|
||
var rawEndPoint = l.GetEndPoint2() as SketchPoint;
|
||
if (!Pt3(rawStartPoint, canonicalPoints, out var x1, out var y1, out var z1) || !Pt3(rawEndPoint, canonicalPoints, out var x2, out var y2, out var z2)) return false;
|
||
if (!TrySketchLocalMmToModelMm(sk, x1, y1, z1, out var startModel) ||
|
||
!TrySketchLocalMmToModelMm(sk, x2, y2, z2, out var endModel))
|
||
return false;
|
||
bool isConstruction = IsConstructionGeometry(s);
|
||
AddSkill(isConstruction ? "draw_center_line_mm" : "draw_line_mm", new()
|
||
{
|
||
["coordinate_system"] = "model",
|
||
["start_model_mm"] = startModel,
|
||
["end_model_mm"] = endModel
|
||
}, name, type);
|
||
string lineKind = isConstruction ? "centerline" : "line";
|
||
info = $"{lineKind}: model_start=({startModel[0]},{startModel[1]},{startModel[2]})mm, model_end=({endModel[0]},{endModel[1]},{endModel[2]})mm";
|
||
return true;
|
||
}
|
||
|
||
static bool Arc(ModelDoc2 doc, Sketch sk, SketchSegment s, string name, string type, Dictionary<IntPtr, double[]> canonicalPoints, out string info)
|
||
{
|
||
info = "";
|
||
if (IsConstructionGeometry(s)) return false;
|
||
if (s is not SketchArc a) return false;
|
||
if (!Pt3(a.GetCenterPoint2() as SketchPoint, canonicalPoints, out var cx, out var cy, out var cz)) return false;
|
||
double r = a.GetRadius() * 1000;
|
||
double sx = 0, sy = 0, sz = 0, ex = 0, ey = 0, ez = 0;
|
||
bool ok = Pt3(a.GetStartPoint2() as SketchPoint, canonicalPoints, out sx, out sy, out sz) && Pt3(a.GetEndPoint2() as SketchPoint, canonicalPoints, out ex, out ey, out ez);
|
||
if (!TrySketchLocalMmToModelMm(sk, cx, cy, cz, out var centerModel)) return false;
|
||
if (!ok || Math.Abs(sx - ex) + Math.Abs(sy - ey) < .001)
|
||
{
|
||
AddSkill("draw_circle_diameter_mm", new()
|
||
{
|
||
["coordinate_system"] = "model",
|
||
["center_model_mm"] = centerModel,
|
||
["diameter_mm"] = R(2 * r)
|
||
}, name, type);
|
||
info = $"圆: center=({R(cx)},{R(cy)})mm, diameter={R(2 * r)}mm";
|
||
}
|
||
else
|
||
{
|
||
int direction = TryGetArcDirection(a);
|
||
if (!TrySketchLocalMmToModelMm(sk, sx, sy, sz, out var startModel) ||
|
||
!TrySketchLocalMmToModelMm(sk, ex, ey, ez, out var endModel) ||
|
||
!TryArcMidLocalMm(cx, cy, cz, sx, sy, ex, ey, direction, out var midLocal) ||
|
||
!TrySketchLocalMmToModelMm(sk, midLocal[0], midLocal[1], midLocal[2], out var midModel))
|
||
return false;
|
||
AddSkill("draw_arc_center_start_end_mm", new()
|
||
{
|
||
["coordinate_system"] = "model",
|
||
["center_model_mm"] = centerModel,
|
||
["start_model_mm"] = startModel,
|
||
["end_model_mm"] = endModel,
|
||
["mid_model_mm"] = midModel,
|
||
["direction_source"] = "model_midpoint"
|
||
}, name, type);
|
||
info = $"圆弧: center=({R(cx)},{R(cy)})mm, start=({R(sx)},{R(sy)})mm, end=({R(ex)},{R(ey)})mm, radius={R(r)}mm";
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static bool Spline(ModelDoc2 doc, Sketch sk, SketchSegment s, string name, string type, out string info)
|
||
{
|
||
info = "";
|
||
if (IsConstructionGeometry(s)) return false;
|
||
if (s is not SketchSpline spline) return false;
|
||
|
||
var pointsModel = new List<List<double>>();
|
||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
foreach (object pointObj in ToObjectArray(SafeObj(() => spline.GetPoints2())))
|
||
{
|
||
if (pointObj is not SketchPoint point) continue;
|
||
if (!Pt3(point, out double x, out double y, out double z)) continue;
|
||
string key = SketchPointCoordKey(x, y, z);
|
||
if (!seen.Add(key)) continue;
|
||
if (TrySketchLocalMmToModelMm(sk, x, y, z, out var modelPoint))
|
||
pointsModel.Add(modelPoint);
|
||
}
|
||
|
||
if (pointsModel.Count < 2)
|
||
return false;
|
||
|
||
AddSkill("draw_spline_points_mm", new()
|
||
{
|
||
["coordinate_system"] = "model",
|
||
["points_model_mm"] = pointsModel,
|
||
["simulate_natural_ends"] = true
|
||
}, name, type);
|
||
|
||
info = $"spline: points={pointsModel.Count}";
|
||
return true;
|
||
}
|
||
|
||
static int TryGetArcDirection(SketchArc arc)
|
||
{
|
||
try
|
||
{
|
||
int direction = arc.GetRotationDir();
|
||
if (direction < 0) return -1;
|
||
if (direction > 0) return 1;
|
||
}
|
||
catch { }
|
||
return 1;
|
||
}
|
||
|
||
static bool TryArcMidLocalMm(double cx, double cy, double cz, double sx, double sy, double ex, double ey, int direction, out double[] mid)
|
||
{
|
||
mid = Array.Empty<double>();
|
||
double r = Math.Sqrt((sx - cx) * (sx - cx) + (sy - cy) * (sy - cy));
|
||
if (r <= 1e-9) return false;
|
||
double start = Math.Atan2(sy - cy, sx - cx);
|
||
double end = Math.Atan2(ey - cy, ex - cx);
|
||
if (direction < 0)
|
||
{
|
||
while (end >= start) end -= Math.PI * 2.0;
|
||
}
|
||
else
|
||
{
|
||
while (end <= start) end += Math.PI * 2.0;
|
||
}
|
||
double angle = (start + end) / 2.0;
|
||
mid = new[] { R(cx + Math.Cos(angle) * r), R(cy + Math.Sin(angle) * r), R(cz) };
|
||
return true;
|
||
}
|
||
|
||
static bool TrySketchLocalMmToModelMm(Sketch sk, double xMm, double yMm, double zMm, out List<double> modelMm)
|
||
{
|
||
modelMm = new List<double>();
|
||
try
|
||
{
|
||
if (sk == null || CurrentSwApp == null) return false;
|
||
MathTransform modelToSketch = sk.ModelToSketchTransform;
|
||
MathTransform sketchToModel = modelToSketch?.Inverse() as MathTransform;
|
||
MathUtility mathUtility = CurrentSwApp.GetMathUtility() as MathUtility;
|
||
MathPoint point = mathUtility?.CreatePoint(new double[] { xMm / 1000.0, yMm / 1000.0, zMm / 1000.0 }) as MathPoint;
|
||
MathPoint transformed = point?.MultiplyTransform(sketchToModel) as MathPoint;
|
||
var data = ToDoubleArray(transformed?.ArrayData);
|
||
if (data.Length < 3) return false;
|
||
modelMm = new List<double> { R(data[0] * 1000.0), R(data[1] * 1000.0), R(data[2] * 1000.0) };
|
||
return true;
|
||
}
|
||
catch
|
||
{
|
||
modelMm = new List<double>();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static void AddExtrudeDirectionModelArgs(Feature feature, bool reverseDir, Dictionary<string, object> args)
|
||
{
|
||
try
|
||
{
|
||
Sketch sketch = FirstSketchFromFeature(feature);
|
||
if (sketch == null) return;
|
||
if (!TrySketchLocalMmToModelMm(sketch, 0.0, 0.0, 0.0, out var origin) ||
|
||
!TrySketchLocalMmToModelMm(sketch, 0.0, 0.0, 1.0, out var zPoint))
|
||
return;
|
||
|
||
var normal = Normalize(zPoint[0] - origin[0], zPoint[1] - origin[1], zPoint[2] - origin[2]);
|
||
if (normal.Length < 3) return;
|
||
var direction = reverseDir
|
||
? new[] { R(-normal[0]), R(-normal[1]), R(-normal[2]) }
|
||
: new[] { normal[0], normal[1], normal[2] };
|
||
|
||
args["sketch_normal_model"] = normal.ToList();
|
||
args["extrude_direction_model"] = direction.ToList();
|
||
args["direction_coordinate_system"] = "model";
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
static Sketch FirstSketchFromFeature(Feature feature)
|
||
{
|
||
if (feature == null) return null;
|
||
Feature sub = feature.GetFirstSubFeature() as Feature;
|
||
for (int i = 0; sub != null && i < 200; i++)
|
||
{
|
||
if (IsSketch(Safe(() => sub.GetTypeName2())) && sub.GetSpecificFeature2() is Sketch sk)
|
||
return sk;
|
||
sub = sub.GetNextSubFeature() as Feature;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
static bool IsConstructionGeometry(SketchSegment segment)
|
||
{
|
||
if (segment == null) return false;
|
||
try { return segment.ConstructionGeometry; }
|
||
catch
|
||
{
|
||
object value = GetComProperty(segment, "ConstructionGeometry");
|
||
return value is bool b && b;
|
||
}
|
||
}
|
||
|
||
static bool TryExtractSimpleFillet(ModelDoc2 doc, Feature feature, out Dictionary<string, object> args, out string note)
|
||
{
|
||
args = new Dictionary<string, object>();
|
||
note = "";
|
||
try
|
||
{
|
||
if (feature.GetDefinition() is not ISimpleFilletFeatureData2 data)
|
||
return false;
|
||
|
||
bool accessed = SafeBool(() => data.AccessSelections(doc, null));
|
||
try
|
||
{
|
||
double radiusMm = R(data.DefaultRadius * 1000.0);
|
||
var edgeSignatures = EdgeSignatures(data.Edges);
|
||
if (radiusMm <= 0 || edgeSignatures.Count == 0)
|
||
return false;
|
||
|
||
args["radius_mm"] = radiusMm;
|
||
args["edge_signatures"] = edgeSignatures;
|
||
args["fillet_type"] = data.Type;
|
||
args["edge_count"] = edgeSignatures.Count;
|
||
note = $"圆角半径={radiusMm}mm, edges={edgeSignatures.Count}, source=ISimpleFilletFeatureData2";
|
||
return true;
|
||
}
|
||
finally
|
||
{
|
||
if (accessed)
|
||
{
|
||
try { data.ReleaseSelectionAccess(); } catch { }
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
note = $"圆角提取失败: {ex.Message}";
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static bool TryExtractChamfer(ModelDoc2 doc, Feature feature, out Dictionary<string, object> args, out string note)
|
||
{
|
||
args = new Dictionary<string, object>();
|
||
note = "";
|
||
try
|
||
{
|
||
object data = SafeObj(() => feature.GetDefinition());
|
||
if (data == null)
|
||
{
|
||
note = "倒角提取失败: GetDefinition 返回空";
|
||
return false;
|
||
}
|
||
|
||
IChamferFeatureData2 typedData = data as IChamferFeatureData2;
|
||
bool accessed = typedData != null
|
||
? SafeBool(() => typedData.AccessSelections(doc, null))
|
||
: SafeBool(() => Convert.ToBoolean(InvokeCom(data, "AccessSelections", doc, null)));
|
||
try
|
||
{
|
||
bool typedCast = typedData != null;
|
||
double distanceMm = typedCast ? R(typedData.GetEdgeChamferDistance(1) * 1000.0) : R(ReadChamferDistance(data, 1) * 1000.0);
|
||
if (distanceMm <= 0) distanceMm = R(ReadChamferDistance(data, 0) * 1000.0);
|
||
double otherDistanceMm = typedCast ? R(typedData.GetEdgeChamferDistance(2) * 1000.0) : R(ReadChamferDistance(data, 2) * 1000.0);
|
||
if (otherDistanceMm <= 0) otherDistanceMm = R(ReadChamferDistance(data, 1) * 1000.0);
|
||
|
||
double angleRad = typedCast ? typedData.EdgeChamferAngle : ReadDoubleMember(data, "EdgeChamferAngle");
|
||
double angleDeg = R(angleRad * 180.0 / Math.PI);
|
||
if (angleDeg <= 0) angleDeg = 45.0;
|
||
|
||
int chamferType = typedCast ? typedData.Type : RInt(ReadDoubleMember(data, "Type"));
|
||
bool equalDistance = typedCast ? typedData.EqualDistance : ReadBoolMember(data, "EqualDistance");
|
||
bool tangentPropagation = typedCast ? typedData.TangentPropagation : ReadBoolMember(data, "TangentPropagation");
|
||
int apiEdgeCount = typedCast ? SafeInt(() => typedData.GetEdgeCount()) : 0;
|
||
object edgesObj = typedCast ? typedData.Edges : GetComProperty(data, "Edges");
|
||
string edgesObjType = edgesObj?.GetType().FullName ?? "null";
|
||
if (edgesObj == null) edgesObj = InvokeCom(data, "IGetEdges");
|
||
var edgeSignatures = EdgeSignatures(edgesObj);
|
||
if (edgeSignatures.Count == 0 && typedCast && apiEdgeCount > 0)
|
||
{
|
||
object iEdgesObj = SafeObj(() => typedData.IGetEdges(apiEdgeCount));
|
||
edgesObjType += $"; IGetEdges={iEdgesObj?.GetType().FullName ?? "null"}";
|
||
edgeSignatures = EdgeSignatures(iEdgesObj);
|
||
}
|
||
if (edgeSignatures.Count == 0)
|
||
edgeSignatures = EdgeSignaturesFromSelection(doc);
|
||
int faceCount = ToObjectArray(GetComProperty(data, "Faces")).OfType<Face2>().Count();
|
||
int loopCount = ToObjectArray(GetComProperty(data, "Loops")).Length;
|
||
|
||
if (distanceMm <= 0)
|
||
distanceMm = FirstFeatureLengthDimensionMm(feature);
|
||
if (otherDistanceMm <= 0)
|
||
otherDistanceMm = distanceMm;
|
||
|
||
if (distanceMm <= 0 || edgeSignatures.Count == 0)
|
||
{
|
||
note = $"倒角提取失败: distance={distanceMm}mm, edges={edgeSignatures.Count}, apiEdgeCount={apiEdgeCount}, edgesObj={edgesObjType}, faces={faceCount}, loops={loopCount}, typed={typedCast}, data={data.GetType().FullName}";
|
||
return false;
|
||
}
|
||
|
||
args["distance_mm"] = distanceMm;
|
||
args["angle_deg"] = angleDeg;
|
||
args["other_distance_mm"] = otherDistanceMm;
|
||
args["chamfer_type"] = chamferType;
|
||
args["equal_distance"] = equalDistance;
|
||
args["tangent_propagation"] = tangentPropagation;
|
||
args["edge_signatures"] = edgeSignatures;
|
||
args["edge_count"] = edgeSignatures.Count;
|
||
note = $"倒角距离={distanceMm}mm, other={otherDistanceMm}mm, angle={angleDeg}deg, type={chamferType}, edges={edgeSignatures.Count}, source=IChamferFeatureData2";
|
||
return true;
|
||
}
|
||
finally
|
||
{
|
||
if (accessed)
|
||
{
|
||
try
|
||
{
|
||
if (typedData != null) typedData.ReleaseSelectionAccess();
|
||
else InvokeCom(data, "ReleaseSelectionAccess");
|
||
}
|
||
catch { }
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
note = $"倒角提取失败: {ex.Message}";
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static double ReadChamferDistance(object data, int side)
|
||
{
|
||
try
|
||
{
|
||
object value = InvokeCom(data, "GetEdgeChamferDistance", side);
|
||
if (value != null && double.TryParse(value.ToString(), out double parsed)) return parsed;
|
||
}
|
||
catch { }
|
||
return 0.0;
|
||
}
|
||
|
||
static double ReadDoubleMember(object target, string name)
|
||
{
|
||
try
|
||
{
|
||
object raw = GetComProperty(target, name);
|
||
if (raw != null && double.TryParse(raw.ToString(), out double value)) return value;
|
||
}
|
||
catch { }
|
||
try
|
||
{
|
||
object raw = InvokeCom(target, name);
|
||
if (raw != null && double.TryParse(raw.ToString(), out double value)) return value;
|
||
}
|
||
catch { }
|
||
return 0.0;
|
||
}
|
||
|
||
static bool ReadBoolMember(object target, string name)
|
||
{
|
||
try
|
||
{
|
||
object raw = GetComProperty(target, name);
|
||
if (TryConvertBool(raw, out bool value)) return value;
|
||
}
|
||
catch { }
|
||
try
|
||
{
|
||
object raw = InvokeCom(target, name);
|
||
if (TryConvertBool(raw, out bool value)) return value;
|
||
}
|
||
catch { }
|
||
return false;
|
||
}
|
||
|
||
static int RInt(double value) => (int)Math.Round(value);
|
||
|
||
static double FirstFeatureLengthDimensionMm(Feature feature)
|
||
{
|
||
try
|
||
{
|
||
for (var dd = feature.GetFirstDisplayDimension() as DisplayDimension; dd != null; dd = feature.GetNextDisplayDimension(dd) as DisplayDimension)
|
||
{
|
||
if (dd.GetDimension() is Dimension dim)
|
||
{
|
||
double valueMm = R(dim.SystemValue * 1000.0);
|
||
if (valueMm > 0 && valueMm < 100000) return valueMm;
|
||
}
|
||
}
|
||
}
|
||
catch { }
|
||
return 0.0;
|
||
}
|
||
|
||
static List<Dictionary<string, object>> EdgeSignatures(object edgesObj)
|
||
{
|
||
var result = new List<Dictionary<string, object>>();
|
||
foreach (object item in FlattenComObjects(edgesObj))
|
||
{
|
||
if (TryAsEdge(item, out Edge edge))
|
||
{
|
||
var signature = EdgeSignature(edge);
|
||
if (signature.Count > 0)
|
||
result.Add(signature);
|
||
}
|
||
}
|
||
return ExpandSymmetricArcEdgeSignatures(result);
|
||
}
|
||
|
||
static List<Dictionary<string, object>> ExpandSymmetricArcEdgeSignatures(List<Dictionary<string, object>> signatures)
|
||
{
|
||
var expanded = signatures.Select(CloneSignature).ToList();
|
||
var circles = signatures
|
||
.Where(item => string.Equals(GetString(item, "curve_type"), "circle", StringComparison.OrdinalIgnoreCase))
|
||
.ToList();
|
||
var lines = signatures
|
||
.Where(item => string.Equals(GetString(item, "curve_type"), "line", StringComparison.OrdinalIgnoreCase))
|
||
.ToList();
|
||
|
||
foreach (var circle in circles)
|
||
{
|
||
var center = GetDoubleArray(circle, "center_mm");
|
||
if (center.Length < 3)
|
||
continue;
|
||
|
||
double radius = GetDouble(circle, "radius_mm");
|
||
var axis = GetDoubleArray(circle, "axis");
|
||
foreach (var line in lines)
|
||
{
|
||
var start = GetDoubleArray(line, "start_mm");
|
||
var end = GetDoubleArray(line, "end_mm");
|
||
if (start.Length < 3 || end.Length < 3)
|
||
continue;
|
||
|
||
int spanAxis = DominantAxis(start, end);
|
||
if (spanAxis < 0)
|
||
continue;
|
||
|
||
double a = start[spanAxis];
|
||
double b = end[spanAxis];
|
||
if (!NearlyEqual(center[spanAxis], a, 0.05) && !NearlyEqual(center[spanAxis], b, 0.05))
|
||
continue;
|
||
|
||
if (!CircleCompatibleWithLineLayer(center, radius, start, end, spanAxis))
|
||
continue;
|
||
|
||
var inferredCenter = center.ToArray();
|
||
inferredCenter[spanAxis] = NearlyEqual(center[spanAxis], a, 0.05) ? b : a;
|
||
if (HasCircleSignatureNear(expanded, inferredCenter, radius, axis))
|
||
continue;
|
||
|
||
var inferred = CloneSignature(circle);
|
||
inferred["center_mm"] = inferredCenter.Select(R).ToArray();
|
||
inferred.Remove("start_mm");
|
||
inferred.Remove("end_mm");
|
||
inferred["inferred_symmetric_counterpart"] = true;
|
||
inferred["inferred_from_center_mm"] = center;
|
||
inferred["inferred_span_axis"] = spanAxis;
|
||
expanded.Add(inferred);
|
||
}
|
||
}
|
||
|
||
return expanded;
|
||
}
|
||
|
||
static Dictionary<string, object> CloneSignature(Dictionary<string, object> source)
|
||
{
|
||
var clone = new Dictionary<string, object>();
|
||
foreach (var kv in source)
|
||
{
|
||
if (kv.Value is double[] doubles)
|
||
clone[kv.Key] = doubles.ToArray();
|
||
else if (kv.Value is Array array)
|
||
clone[kv.Key] = array.Cast<object>().ToArray();
|
||
else
|
||
clone[kv.Key] = kv.Value;
|
||
}
|
||
return clone;
|
||
}
|
||
|
||
static string GetString(Dictionary<string, object> dict, string key)
|
||
{
|
||
return dict.TryGetValue(key, out var value) ? value?.ToString() ?? "" : "";
|
||
}
|
||
|
||
static double GetDouble(Dictionary<string, object> dict, string key)
|
||
{
|
||
if (!dict.TryGetValue(key, out var value) || value == null)
|
||
return 0.0;
|
||
try { return Convert.ToDouble(value); }
|
||
catch { return 0.0; }
|
||
}
|
||
|
||
static double[] GetDoubleArray(Dictionary<string, object> dict, string key)
|
||
{
|
||
if (!dict.TryGetValue(key, out var value) || value == null)
|
||
return Array.Empty<double>();
|
||
return ToDoubleArray(value);
|
||
}
|
||
|
||
static int DominantAxis(double[] start, double[] end)
|
||
{
|
||
double best = 0.0;
|
||
int axis = -1;
|
||
for (int i = 0; i < 3; i++)
|
||
{
|
||
double delta = Math.Abs(end[i] - start[i]);
|
||
if (delta > best)
|
||
{
|
||
best = delta;
|
||
axis = i;
|
||
}
|
||
}
|
||
return best > 0.05 ? axis : -1;
|
||
}
|
||
|
||
static bool CircleCompatibleWithLineLayer(double[] center, double radiusMm, double[] start, double[] end, int spanAxis)
|
||
{
|
||
double tolerance = Math.Max(0.1, Math.Abs(radiusMm) + 0.1);
|
||
for (int i = 0; i < 3; i++)
|
||
{
|
||
if (i == spanAxis)
|
||
continue;
|
||
double min = Math.Min(start[i], end[i]) - tolerance;
|
||
double max = Math.Max(start[i], end[i]) + tolerance;
|
||
if (center[i] < min || center[i] > max)
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static bool HasCircleSignatureNear(List<Dictionary<string, object>> signatures, double[] center, double radiusMm, double[] axis)
|
||
{
|
||
foreach (var item in signatures)
|
||
{
|
||
if (!string.Equals(GetString(item, "curve_type"), "circle", StringComparison.OrdinalIgnoreCase))
|
||
continue;
|
||
var existingCenter = GetDoubleArray(item, "center_mm");
|
||
if (existingCenter.Length < 3 || Distance(existingCenter, center) > 0.1)
|
||
continue;
|
||
double existingRadius = GetDouble(item, "radius_mm");
|
||
if (Math.Abs(existingRadius - radiusMm) > 0.1)
|
||
continue;
|
||
var existingAxis = GetDoubleArray(item, "axis");
|
||
if (axis.Length >= 3 && existingAxis.Length >= 3 && AxisMismatch(axis, existingAxis))
|
||
continue;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static bool AxisMismatch(double[] a, double[] b)
|
||
{
|
||
double dot = Math.Abs(a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
|
||
double la = Math.Sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]);
|
||
double lb = Math.Sqrt(b[0] * b[0] + b[1] * b[1] + b[2] * b[2]);
|
||
if (la <= 1e-12 || lb <= 1e-12)
|
||
return false;
|
||
return dot / (la * lb) < 0.99;
|
||
}
|
||
|
||
static bool NearlyEqual(double a, double b, double tolerance)
|
||
{
|
||
return Math.Abs(a - b) <= tolerance;
|
||
}
|
||
|
||
static IEnumerable<object> FlattenComObjects(object value)
|
||
{
|
||
if (value == null) yield break;
|
||
|
||
if (value is DispatchWrapper wrapper)
|
||
{
|
||
foreach (object nested in FlattenComObjects(wrapper.WrappedObject))
|
||
yield return nested;
|
||
yield break;
|
||
}
|
||
|
||
if (value is Array array)
|
||
{
|
||
foreach (object item in array)
|
||
{
|
||
foreach (object nested in FlattenComObjects(item))
|
||
yield return nested;
|
||
}
|
||
yield break;
|
||
}
|
||
|
||
yield return value;
|
||
}
|
||
|
||
static bool TryAsEdge(object value, out Edge edge)
|
||
{
|
||
edge = null;
|
||
if (value == null) return false;
|
||
if (value is DispatchWrapper wrapper)
|
||
value = wrapper.WrappedObject;
|
||
if (value is Edge typed)
|
||
{
|
||
edge = typed;
|
||
return true;
|
||
}
|
||
try
|
||
{
|
||
edge = value as Edge;
|
||
return edge != null;
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static bool TryAsFace(object value, out Face2 face)
|
||
{
|
||
face = null;
|
||
if (value == null) return false;
|
||
if (value is DispatchWrapper wrapper)
|
||
value = wrapper.WrappedObject;
|
||
if (value is Face2 typed)
|
||
{
|
||
face = typed;
|
||
return true;
|
||
}
|
||
try
|
||
{
|
||
face = value as Face2;
|
||
return face != null;
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static List<Dictionary<string, object>> EdgeSignaturesFromSelection(ModelDoc2 doc)
|
||
{
|
||
var result = new List<Dictionary<string, object>>();
|
||
try
|
||
{
|
||
if (doc?.SelectionManager is not SelectionMgr selMgr)
|
||
return result;
|
||
|
||
int count = selMgr.GetSelectedObjectCount2(-1);
|
||
for (int i = 1; i <= count; i++)
|
||
{
|
||
object selected = selMgr.GetSelectedObject6(i, -1);
|
||
if (selected is Edge edge)
|
||
{
|
||
var signature = EdgeSignature(edge);
|
||
if (signature.Count > 0)
|
||
result.Add(signature);
|
||
}
|
||
}
|
||
}
|
||
catch { }
|
||
return result;
|
||
}
|
||
|
||
static Dictionary<string, object> EdgeSignature(Edge edge)
|
||
{
|
||
var result = new Dictionary<string, object>();
|
||
try
|
||
{
|
||
Vertex startVertex = SafeObj(() => edge.GetStartVertex()) as Vertex;
|
||
Vertex endVertex = SafeObj(() => edge.GetEndVertex()) as Vertex;
|
||
double[] start = VertexPointMm(startVertex);
|
||
double[] end = VertexPointMm(endVertex);
|
||
if (start.Length >= 3) result["start_mm"] = start;
|
||
if (end.Length >= 3) result["end_mm"] = end;
|
||
|
||
Curve curve = SafeObj(() => edge.GetCurve()) as Curve;
|
||
string curveType = "other";
|
||
if (curve != null)
|
||
{
|
||
if (SafeBool(() => curve.IsCircle()))
|
||
{
|
||
curveType = "circle";
|
||
var circle = ToDoubleArray(GetComProperty(curve, "CircleParams"));
|
||
if (circle.Length == 0) circle = ToDoubleArray(InvokeCom(curve, "CircleParams"));
|
||
if (circle.Length >= 7)
|
||
{
|
||
result["center_mm"] = new[] { R(circle[0] * 1000.0), R(circle[1] * 1000.0), R(circle[2] * 1000.0) };
|
||
result["axis"] = new[] { R(circle[3]), R(circle[4]), R(circle[5]) };
|
||
result["radius_mm"] = R(circle[6] * 1000.0);
|
||
}
|
||
}
|
||
else if (SafeBool(() => curve.IsLine()))
|
||
{
|
||
curveType = "line";
|
||
var line = ToDoubleArray(GetComProperty(curve, "LineParams"));
|
||
if (line.Length == 0) line = ToDoubleArray(InvokeCom(curve, "LineParams"));
|
||
if (line.Length >= 6)
|
||
{
|
||
result["line_point_mm"] = new[] { R(line[0] * 1000.0), R(line[1] * 1000.0), R(line[2] * 1000.0) };
|
||
result["line_direction"] = new[] { R(line[3]), R(line[4]), R(line[5]) };
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!result.ContainsKey("center_mm") && start.Length >= 3 && end.Length >= 3)
|
||
{
|
||
result["center_mm"] = new[]
|
||
{
|
||
R((start[0] + end[0]) / 2.0),
|
||
R((start[1] + end[1]) / 2.0),
|
||
R((start[2] + end[2]) / 2.0)
|
||
};
|
||
result["length_mm"] = R(Distance(start, end));
|
||
}
|
||
|
||
result["curve_type"] = curveType;
|
||
}
|
||
catch { }
|
||
return result;
|
||
}
|
||
|
||
static double[] VertexPointMm(Vertex vertex)
|
||
{
|
||
if (vertex == null) return Array.Empty<double>();
|
||
try
|
||
{
|
||
var values = ToDoubleArray(vertex.GetPoint());
|
||
if (values.Length >= 3)
|
||
return new[] { R(values[0] * 1000.0), R(values[1] * 1000.0), R(values[2] * 1000.0) };
|
||
}
|
||
catch { }
|
||
return Array.Empty<double>();
|
||
}
|
||
|
||
static double Distance(double[] a, double[] b)
|
||
{
|
||
if (a.Length < 3 || b.Length < 3) return 0.0;
|
||
double dx = a[0] - b[0], dy = a[1] - b[1], dz = a[2] - b[2];
|
||
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
|
||
}
|
||
|
||
static string Dimensions(Sketch sk, Feature feat)
|
||
{
|
||
var list = new List<string>();
|
||
try { foreach (object o in InvokeArray(sk, "GetDimensions")) AddDim(o, list); } catch { }
|
||
try { for (var dd = feat.GetFirstDisplayDimension() as DisplayDimension; dd != null; dd = feat.GetNextDisplayDimension(dd) as DisplayDimension) AddDim(dd, list); } catch { }
|
||
return string.Join(";", list.Distinct().Take(80));
|
||
}
|
||
|
||
static void AddDim(object o, List<string> list)
|
||
{
|
||
Dimension d = o as Dimension;
|
||
if (d == null && o is DisplayDimension dd) d = dd.GetDimension() as Dimension;
|
||
if (d != null) list.Add($"{Safe(() => d.GetNameForSelection())}={R(d.SystemValue * 1000)}mm");
|
||
}
|
||
|
||
static string Relations(Sketch sk)
|
||
{
|
||
var d = new Dictionary<int, int>();
|
||
try { foreach (object o in SketchRelations(sk)) if (o is SketchRelation r) Inc(d, SafeInt(() => r.GetRelationType())); } catch { }
|
||
return string.Join(",", d.Select(kv => $"约束{kv.Key}:{kv.Value}"));
|
||
}
|
||
|
||
static double Depth(Feature f)
|
||
{
|
||
try
|
||
{
|
||
var data = f.GetDefinition() as ExtrudeFeatureData2;
|
||
if (data == null) return 0;
|
||
return R(data.GetDepth(true) * 1000.0);
|
||
}
|
||
catch { return 0; }
|
||
}
|
||
|
||
static int ExtrudeEndCondition(Feature f, bool firstDirection)
|
||
{
|
||
try
|
||
{
|
||
var data = f.GetDefinition() as ExtrudeFeatureData2;
|
||
if (data == null) return -1;
|
||
object raw = InvokeCom(data, "GetEndCondition", firstDirection);
|
||
if (raw != null) return Convert.ToInt32(raw);
|
||
}
|
||
catch { }
|
||
return -1;
|
||
}
|
||
|
||
static bool IsThroughAllEndCondition(int endCondition)
|
||
=> endCondition == (int)swEndConditions_e.swEndCondThroughAll;
|
||
|
||
static string EndConditionName(int endCondition)
|
||
{
|
||
if (endCondition == (int)swEndConditions_e.swEndCondBlind) return "blind";
|
||
if (endCondition == (int)swEndConditions_e.swEndCondThroughAll) return "through_all";
|
||
if (endCondition == (int)swEndConditions_e.swEndCondThroughNext) return "through_next";
|
||
if (endCondition == (int)swEndConditions_e.swEndCondUpToVertex) return "up_to_vertex";
|
||
if (endCondition == (int)swEndConditions_e.swEndCondUpToSurface) return "up_to_surface";
|
||
if (endCondition == (int)swEndConditions_e.swEndCondOffsetFromSurface) return "offset_from_surface";
|
||
if (endCondition == (int)swEndConditions_e.swEndCondMidPlane) return "mid_plane";
|
||
return endCondition.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||
}
|
||
|
||
static void AddExtrudeStartArgs(Feature feature, Dictionary<string, object> args)
|
||
{
|
||
try
|
||
{
|
||
if (feature.GetDefinition() is not ExtrudeFeatureData2 data)
|
||
return;
|
||
|
||
int fromType = SafeValue(() => data.FromType, (int)swStartConditions_e.swStartSketchPlane);
|
||
if (fromType == (int)swStartConditions_e.swStartSketchPlane)
|
||
return;
|
||
|
||
args["start_condition_code"] = fromType;
|
||
args["start_condition"] = StartConditionName(fromType);
|
||
args["start_offset_mm"] = R(SafeValue(() => data.FromOffsetDistance, 0.0) * 1000.0);
|
||
args["flip_start_offset"] = SafeValue(() => data.FromOffsetReverse, false);
|
||
|
||
object fromEntity = null;
|
||
int fromEntityType = 0;
|
||
try { data.GetFromEntity(out fromEntity, out fromEntityType); } catch { }
|
||
string fromName = ReferenceName(fromEntity);
|
||
if (!string.IsNullOrWhiteSpace(fromName))
|
||
args["start_reference_name"] = fromName;
|
||
if (fromEntityType != 0)
|
||
args["start_entity_type_code"] = fromEntityType;
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
static string StartConditionName(int startCondition)
|
||
{
|
||
if (startCondition == (int)swStartConditions_e.swStartSketchPlane) return "sketch_plane";
|
||
if (startCondition == (int)swStartConditions_e.swStartSurface) return "surface_face_or_plane";
|
||
if (startCondition == (int)swStartConditions_e.swStartVertex) return "vertex";
|
||
if (startCondition == (int)swStartConditions_e.swStartOffset) return "offset";
|
||
return startCondition.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||
}
|
||
|
||
static List<string> ExtrudeSelectedContourSignatures(ModelDoc2 doc, Feature feature)
|
||
{
|
||
var result = new List<string>();
|
||
try
|
||
{
|
||
if (feature.GetDefinition() is not ExtrudeFeatureData2 data)
|
||
return result;
|
||
|
||
bool accessed = SafeBool(() => data.AccessSelections(doc, null));
|
||
try
|
||
{
|
||
int count = SafeInt(() => data.GetContoursCount(), 0);
|
||
if (count <= 0) return result;
|
||
|
||
var contours = ToObjectArray(SafeObj(() => data.Contours));
|
||
if (contours.Length == 0)
|
||
contours = ToObjectArray(SafeObj(() => data.IGetContours(count)));
|
||
|
||
foreach (object contourObj in contours)
|
||
{
|
||
if (contourObj is not SketchContour contour)
|
||
continue;
|
||
string signature = SketchContourSignature(contour);
|
||
if (!string.IsNullOrWhiteSpace(signature) && !result.Contains(signature, StringComparer.OrdinalIgnoreCase))
|
||
result.Add(signature);
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
if (accessed) SafeAction(() => data.ReleaseSelectionAccess());
|
||
}
|
||
}
|
||
catch { }
|
||
return result;
|
||
}
|
||
|
||
static void RegisterSelectedContourSegmentFilter(ModelDoc2 doc, Feature feature)
|
||
{
|
||
try
|
||
{
|
||
if (doc == null || feature == null)
|
||
return;
|
||
if (feature.GetDefinition() is not ExtrudeFeatureData2 data)
|
||
return;
|
||
|
||
bool accessed = SafeBool(() => data.AccessSelections(doc, null));
|
||
try
|
||
{
|
||
int count = SafeInt(() => data.GetContoursCount(), 0);
|
||
if (count <= 0)
|
||
return;
|
||
|
||
var contours = ToObjectArray(SafeObj(() => data.Contours));
|
||
if (contours.Length == 0)
|
||
contours = ToObjectArray(SafeObj(() => data.IGetContours(count)));
|
||
if (contours.Length == 0)
|
||
return;
|
||
|
||
foreach (object contourObj in contours)
|
||
{
|
||
if (contourObj is not SketchContour contour)
|
||
continue;
|
||
foreach (object segmentObj in ToObjectArray(SafeObj(() => contour.GetSketchSegments())))
|
||
{
|
||
if (segmentObj is not SketchSegment segment)
|
||
continue;
|
||
var segmentId = Id(segment);
|
||
if (segmentId == IntPtr.Zero)
|
||
continue;
|
||
Sketch sketch = SafeObj(() => segment.GetSketch()) as Sketch;
|
||
var sketchId = Id(sketch);
|
||
if (sketchId == IntPtr.Zero)
|
||
continue;
|
||
if (!SketchSelectedContourSegmentIdsBySketchId.TryGetValue(sketchId, out var ids))
|
||
{
|
||
ids = new HashSet<IntPtr>();
|
||
SketchSelectedContourSegmentIdsBySketchId[sketchId] = ids;
|
||
}
|
||
ids.Add(segmentId);
|
||
}
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
if (accessed) SafeAction(() => data.ReleaseSelectionAccess());
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
static string SketchContourSignature(SketchContour contour)
|
||
{
|
||
var tokens = new List<string>();
|
||
foreach (object segObj in ToObjectArray(SafeObj(() => contour.GetSketchSegments())))
|
||
{
|
||
if (segObj is SketchLine line)
|
||
{
|
||
if (!Pt(line.GetStartPoint2() as SketchPoint, out double x1, out double y1)
|
||
|| !Pt(line.GetEndPoint2() as SketchPoint, out double x2, out double y2))
|
||
continue;
|
||
string p1 = SigPoint(x1, y1);
|
||
string p2 = SigPoint(x2, y2);
|
||
if (string.CompareOrdinal(p1, p2) > 0) (p1, p2) = (p2, p1);
|
||
tokens.Add($"L:{p1}:{p2}");
|
||
}
|
||
else if (segObj is SketchArc arc)
|
||
{
|
||
if (!Pt(arc.GetCenterPoint2() as SketchPoint, out double cx, out double cy)
|
||
|| !Pt(arc.GetStartPoint2() as SketchPoint, out double sx, out double sy)
|
||
|| !Pt(arc.GetEndPoint2() as SketchPoint, out double ex, out double ey))
|
||
continue;
|
||
string p1 = SigPoint(sx, sy);
|
||
string p2 = SigPoint(ex, ey);
|
||
if (string.CompareOrdinal(p1, p2) > 0) (p1, p2) = (p2, p1);
|
||
tokens.Add($"A:{SigPoint(cx, cy)}:{p1}:{p2}");
|
||
}
|
||
}
|
||
|
||
tokens.Sort(StringComparer.Ordinal);
|
||
return string.Join("|", tokens);
|
||
}
|
||
|
||
static string SigPoint(double xMm, double yMm)
|
||
=> $"{SigNumber(xMm)},{SigNumber(yMm)}";
|
||
|
||
static string SigNumber(double value)
|
||
=> Math.Round(value, 3).ToString("0.###", System.Globalization.CultureInfo.InvariantCulture);
|
||
|
||
static bool IsRevolveBossFeature(string n, string t, Feature feature)
|
||
{
|
||
string s = (n + " " + t).ToLowerInvariant();
|
||
if (s.Contains("cut") || s.Contains("切除") || s.Contains("revcut")) return false;
|
||
if (s.Contains("revol") || s.Contains("revolution") || s.Contains("旋转") || s.Contains("旋轉"))
|
||
{
|
||
try
|
||
{
|
||
if (feature.GetDefinition() is IRevolveFeatureData2 data)
|
||
return data.IsBossFeature();
|
||
}
|
||
catch { }
|
||
return true;
|
||
}
|
||
|
||
try
|
||
{
|
||
if (feature.GetDefinition() is IRevolveFeatureData2 data)
|
||
return data.IsBossFeature();
|
||
}
|
||
catch { }
|
||
return false;
|
||
}
|
||
|
||
static bool TryExtractRevolveBoss(ModelDoc2 doc, Feature feature, out Dictionary<string, object> args, out string note)
|
||
{
|
||
args = new Dictionary<string, object>();
|
||
note = "";
|
||
try
|
||
{
|
||
if (feature.GetDefinition() is not IRevolveFeatureData2 data)
|
||
{
|
||
note = "RevolveFeatureData2 not available";
|
||
return false;
|
||
}
|
||
|
||
bool accessed = SafeBool(() => data.AccessSelections(doc, null));
|
||
try
|
||
{
|
||
double angleRad = data.GetRevolutionAngle(true);
|
||
if (Math.Abs(angleRad) < 1e-9)
|
||
angleRad = data.GetRevolutionAngle(false);
|
||
double angleDeg = R(angleRad * 180.0 / Math.PI);
|
||
if (angleDeg <= 0.0) angleDeg = 360.0;
|
||
|
||
bool reverseDir = false;
|
||
bool merge = true;
|
||
try { reverseDir = data.ReverseDirection; } catch { }
|
||
try { merge = data.Merge; } catch { }
|
||
|
||
args["angle_deg"] = angleDeg;
|
||
args["merge"] = merge;
|
||
args["reverse_dir"] = reverseDir;
|
||
AddRevolveAxisArgs(data, args);
|
||
var selectedContours = RevolveSelectedContourSignatures(data);
|
||
if (selectedContours.Count > 0)
|
||
{
|
||
args["selected_contours"] = selectedContours;
|
||
args["selected_contour_count"] = selectedContours.Count;
|
||
}
|
||
note = $"revolve angle={angleDeg}deg, reverse_dir={reverseDir}, merge={merge}";
|
||
return true;
|
||
}
|
||
finally
|
||
{
|
||
if (accessed) SafeAction(() => data.ReleaseSelectionAccess());
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
note = $"revolve extract failed: {ex.Message}";
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static bool IsRevolveCutFeature(string n, string t, Feature feature)
|
||
{
|
||
string s = (n + " " + t).ToLowerInvariant();
|
||
if (s.Contains("revcut") || s.Contains("revolvecut") || s.Contains("cut-revolve") || s.Contains("cut revolve") || s.Contains("旋转切除"))
|
||
return true;
|
||
if ((s.Contains("revol") || s.Contains("revolution") || s.Contains("旋转") || s.Contains("鏃嬭浆") || s.Contains("鏃嬭綁")) &&
|
||
(s.Contains("cut") || s.Contains("切除") || s.Contains("鍒囬櫎")))
|
||
return true;
|
||
|
||
try
|
||
{
|
||
if (feature.GetDefinition() is IRevolveFeatureData2 data)
|
||
return !data.IsBossFeature();
|
||
}
|
||
catch { }
|
||
return false;
|
||
}
|
||
|
||
static bool TryExtractRevolveCut(ModelDoc2 doc, Feature feature, out Dictionary<string, object> args, out string note)
|
||
{
|
||
args = new Dictionary<string, object>();
|
||
note = "";
|
||
try
|
||
{
|
||
if (feature.GetDefinition() is not IRevolveFeatureData2 data)
|
||
{
|
||
note = "RevolveFeatureData2 not available";
|
||
return false;
|
||
}
|
||
|
||
bool accessed = SafeBool(() => data.AccessSelections(doc, null));
|
||
try
|
||
{
|
||
double angleRad = data.GetRevolutionAngle(true);
|
||
if (Math.Abs(angleRad) < 1e-9)
|
||
angleRad = data.GetRevolutionAngle(false);
|
||
double angleDeg = R(angleRad * 180.0 / Math.PI);
|
||
if (angleDeg <= 0.0) angleDeg = 360.0;
|
||
|
||
bool reverseDir = false;
|
||
try { reverseDir = data.ReverseDirection; } catch { }
|
||
|
||
args["angle_deg"] = angleDeg;
|
||
args["reverse_dir"] = reverseDir;
|
||
AddRevolveAxisArgs(data, args);
|
||
var selectedContours = RevolveSelectedContourSignatures(data);
|
||
if (selectedContours.Count > 0)
|
||
{
|
||
args["selected_contours"] = selectedContours;
|
||
args["selected_contour_count"] = selectedContours.Count;
|
||
}
|
||
note = $"revolve cut angle={angleDeg}deg, reverse_dir={reverseDir}";
|
||
return true;
|
||
}
|
||
finally
|
||
{
|
||
if (accessed) SafeAction(() => data.ReleaseSelectionAccess());
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
note = $"revolve cut extract failed: {ex.Message}";
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static bool TryExtractLinearPattern(ModelDoc2 doc, Feature feature, string name, string type, out Dictionary<string, object> args, out string note)
|
||
{
|
||
args = new Dictionary<string, object>();
|
||
note = "";
|
||
if (!IsLinearPatternFeature(name, type)) return false;
|
||
|
||
object data = SafeObj(() => feature.GetDefinition());
|
||
if (data == null)
|
||
{
|
||
note = "linear pattern definition not available";
|
||
return false;
|
||
}
|
||
|
||
try { TryInvokeCom(data, "AccessSelections", doc, null); } catch { }
|
||
try
|
||
{
|
||
int count1 = FirstPositiveInt(data, new[] { "D1TotalInstances", "D1InstanceCount", "D1Count", "TotalInstances", "Instances" }, 2);
|
||
double spacing1Mm = FirstPositiveLengthMm(data, new[] { "D1Spacing", "Spacing", "Direction1Spacing" }, 0.0);
|
||
int count2 = FirstPositiveInt(data, new[] { "D2TotalInstances", "D2InstanceCount", "D2Count" }, 1);
|
||
double spacing2Mm = FirstPositiveLengthMm(data, new[] { "D2Spacing", "Direction2Spacing" }, 0.0);
|
||
bool reverseDir1 = FirstBool(data, new[] { "D1ReverseDirection", "ReverseDirection1", "Dir1ReverseDirection" }, false);
|
||
bool reverseDir2 = FirstBool(data, new[] { "D2ReverseDirection", "ReverseDirection2", "Dir2ReverseDirection" }, false);
|
||
bool geometryPattern = FirstBool(data, new[] { "GeometryPattern", "GeometryPatternOption" }, true);
|
||
|
||
if (spacing1Mm <= 0.0)
|
||
{
|
||
note = "linear pattern spacing1 not available";
|
||
return false;
|
||
}
|
||
|
||
if (!TryReadEdgePointMm(data, new[] { "D1Reference", "D1Direction", "Direction1", "Dir1", "Direction1Reference" }, out var dir1PointMm))
|
||
{
|
||
note = "linear pattern direction1 reference edge not available";
|
||
return false;
|
||
}
|
||
|
||
args["count1"] = count1;
|
||
args["spacing1_mm"] = spacing1Mm;
|
||
args["dir1_edge_x_mm"] = dir1PointMm[0];
|
||
args["dir1_edge_y_mm"] = dir1PointMm[1];
|
||
args["dir1_edge_z_mm"] = dir1PointMm[2];
|
||
args["reverse_dir1"] = reverseDir1;
|
||
args["geometry_pattern"] = geometryPattern;
|
||
args["count2"] = count2;
|
||
args["spacing2_mm"] = spacing2Mm;
|
||
args["reverse_dir2"] = reverseDir2;
|
||
|
||
string seedFeatureName = FirstFeatureName(data, new[] { "PatternFeatureArray", "FeatureArray", "SeedFeatures", "FeaturesToPattern" });
|
||
if (!string.IsNullOrWhiteSpace(seedFeatureName))
|
||
args["feature_name"] = seedFeatureName;
|
||
|
||
if (count2 > 1 && TryReadEdgePointMm(data, new[] { "D2Reference", "D2Direction", "Direction2", "Dir2", "Direction2Reference" }, out var dir2PointMm))
|
||
{
|
||
args["dir2_edge_x_mm"] = dir2PointMm[0];
|
||
args["dir2_edge_y_mm"] = dir2PointMm[1];
|
||
args["dir2_edge_z_mm"] = dir2PointMm[2];
|
||
}
|
||
|
||
note = $"linear pattern count1={count1}, spacing1={spacing1Mm}mm, count2={count2}";
|
||
return true;
|
||
}
|
||
finally
|
||
{
|
||
TryInvokeCom(data, "ReleaseSelectionAccess");
|
||
}
|
||
}
|
||
|
||
static bool TryExtractCircularPattern(ModelDoc2 doc, Feature feature, string name, string type, out Dictionary<string, object> args, out string note)
|
||
{
|
||
args = new Dictionary<string, object>();
|
||
note = "";
|
||
if (!IsCircularPatternFeature(name, type)) return false;
|
||
|
||
object data = SafeObj(() => feature.GetDefinition());
|
||
if (data is not ICircularPatternFeatureData circular)
|
||
{
|
||
note = "circular pattern definition not available";
|
||
return false;
|
||
}
|
||
|
||
bool accessed = SafeBool(() => circular.AccessSelections(doc, null));
|
||
try
|
||
{
|
||
int count = SafeValue(() => circular.TotalInstances, 0);
|
||
double spacingRad = SafeValue(() => circular.Spacing, 0.0);
|
||
if (count < 2)
|
||
{
|
||
note = $"circular pattern count invalid: {count}";
|
||
return false;
|
||
}
|
||
|
||
if (Math.Abs(spacingRad) <= 1e-9)
|
||
{
|
||
note = "circular pattern spacing angle not available";
|
||
return false;
|
||
}
|
||
|
||
object axisObject = SafeObj(() => circular.Axis);
|
||
string axisName = ReferenceName(axisObject);
|
||
if (string.IsNullOrWhiteSpace(axisName))
|
||
axisName = FirstReferenceName(data, new[] { "Axis", "PatternAxis" });
|
||
var axisReference = BuildCircularPatternAxisReference(doc, axisObject, axisName);
|
||
|
||
string seedFeatureName = FirstFeatureNameFromObject(SafeObj(() => circular.PatternFeatureArray));
|
||
if (string.IsNullOrWhiteSpace(seedFeatureName))
|
||
seedFeatureName = FirstFeatureName(data, new[] { "PatternFeatureArray", "FeatureArray", "SeedFeatures", "FeaturesToPattern" });
|
||
|
||
args["count"] = count;
|
||
args["angle_deg"] = R(spacingRad * 180.0 / Math.PI);
|
||
if (!string.IsNullOrWhiteSpace(axisName))
|
||
args["axis_name"] = axisName;
|
||
if (axisReference.Count > 0)
|
||
args["axis_reference"] = axisReference;
|
||
args["reverse_direction"] = SafeValue(() => circular.ReverseDirection, false);
|
||
args["equal_spacing"] = SafeValue(() => circular.EqualSpacing, false);
|
||
args["geometry_pattern"] = SafeValue(() => circular.GeometryPattern, true);
|
||
args["vary_sketch"] = SafeValue(() => circular.VarySketch, false);
|
||
if (!string.IsNullOrWhiteSpace(seedFeatureName))
|
||
args["source_feature_name"] = seedFeatureName;
|
||
|
||
note = $"circular pattern count={count}, angle={args["angle_deg"]}deg, axis={axisName}, axis_ref={GetString(axisReference, "reference_type")}, seed={seedFeatureName}";
|
||
return true;
|
||
}
|
||
finally
|
||
{
|
||
if (accessed) SafeAction(() => circular.ReleaseSelectionAccess());
|
||
}
|
||
}
|
||
|
||
static Dictionary<string, object> BuildCircularPatternAxisReference(ModelDoc2 doc, object axisObject, string axisName)
|
||
{
|
||
var result = new Dictionary<string, object>();
|
||
try
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(axisName))
|
||
result["name"] = axisName;
|
||
|
||
if (axisObject is Edge edge)
|
||
{
|
||
var signature = EdgeSignature(edge);
|
||
string curveType = GetString(signature, "curve_type");
|
||
result["reference_type"] = string.Equals(curveType, "circle", StringComparison.OrdinalIgnoreCase)
|
||
? "circular_edge"
|
||
: string.Equals(curveType, "line", StringComparison.OrdinalIgnoreCase)
|
||
? "linear_edge"
|
||
: "edge";
|
||
result["edge_signature"] = signature;
|
||
return result;
|
||
}
|
||
|
||
if (axisObject is Face2 face)
|
||
{
|
||
var reference = new SketchReferenceResult();
|
||
if (TryDescribeReferenceEntity(doc, face, reference) &&
|
||
string.Equals(reference.Kind, "face", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
var signature = BuildFaceSignatureArgs(reference);
|
||
string surfaceType = GetString(signature, "surface_type");
|
||
result["reference_type"] = string.Equals(surfaceType, "cylinder", StringComparison.OrdinalIgnoreCase)
|
||
? "cylindrical_face"
|
||
: "face";
|
||
result["face_signature"] = signature;
|
||
return result;
|
||
}
|
||
}
|
||
|
||
if (axisObject is Feature feature)
|
||
{
|
||
string typeName = SafeValue(() => feature.GetTypeName2(), "");
|
||
string name = SafeValue(() => feature.Name, axisName);
|
||
if (!string.IsNullOrWhiteSpace(name))
|
||
result["name"] = name;
|
||
result["reference_type"] = string.Equals(typeName, "RefAxis", StringComparison.OrdinalIgnoreCase)
|
||
? "reference_axis"
|
||
: "feature";
|
||
result["feature_type"] = typeName;
|
||
return result;
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(axisName))
|
||
result["reference_type"] = "reference_axis";
|
||
}
|
||
catch { }
|
||
|
||
return result;
|
||
}
|
||
|
||
static void AddRevolveAxisArgs(IRevolveFeatureData2 data, Dictionary<string, object> args)
|
||
{
|
||
try
|
||
{
|
||
object axis = SafeObj(() => data.Axis);
|
||
if (axis is not SketchLine line)
|
||
return;
|
||
|
||
var sketch = SafeObj(() => InvokeCom(line, "GetSketch")) as Sketch;
|
||
if (sketch == null)
|
||
return;
|
||
|
||
var sp = line.GetStartPoint2() as SketchPoint;
|
||
var ep = line.GetEndPoint2() as SketchPoint;
|
||
if (!Pt3(sp, out double sx, out double sy, out double sz) ||
|
||
!Pt3(ep, out double ex, out double ey, out double ez))
|
||
return;
|
||
|
||
if (!TrySketchLocalMmToModelMm(sketch, sx, sy, sz, out var startModel) ||
|
||
!TrySketchLocalMmToModelMm(sketch, ex, ey, ez, out var endModel))
|
||
return;
|
||
|
||
args["axis_start_model_mm"] = startModel;
|
||
args["axis_end_model_mm"] = endModel;
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
static List<string> RevolveSelectedContourSignatures(IRevolveFeatureData2 data)
|
||
{
|
||
var result = new List<string>();
|
||
try
|
||
{
|
||
int count = SafeInt(() => data.GetContoursCount(), 0);
|
||
if (count <= 0)
|
||
return result;
|
||
|
||
var contours = ToObjectArray(SafeObj(() => data.Contours));
|
||
if (contours.Length == 0)
|
||
contours = ToObjectArray(SafeObj(() => data.IGetContours(count)));
|
||
|
||
foreach (object contourObj in contours)
|
||
{
|
||
if (contourObj is not SketchContour contour)
|
||
continue;
|
||
string signature = SketchContourSignature(contour);
|
||
if (!string.IsNullOrWhiteSpace(signature) && !result.Contains(signature, StringComparer.OrdinalIgnoreCase))
|
||
result.Add(signature);
|
||
}
|
||
}
|
||
catch { }
|
||
|
||
return result;
|
||
}
|
||
|
||
static bool TryExtractDraft(ModelDoc2 doc, Feature feature, string name, string type, out Dictionary<string, object> args, out string note)
|
||
{
|
||
args = new Dictionary<string, object>();
|
||
note = "";
|
||
if (!IsDraftFeature(name, type)) return false;
|
||
|
||
object data = SafeObj(() => feature.GetDefinition());
|
||
if (data == null)
|
||
{
|
||
note = "draft definition not available";
|
||
return false;
|
||
}
|
||
|
||
try { TryInvokeCom(data, "AccessSelections", doc, null); } catch { }
|
||
try
|
||
{
|
||
double angleDeg = FirstPositiveAngleDeg(data, new[] { "DraftAngle", "Angle", "Angle1" }, 0.0);
|
||
int neutralFaceIndex = FirstFaceIndex(doc, data, new[] { "NeutralPlane", "NeutralFace", "NeutralFaces", "PullDirectionFace" });
|
||
var draftFaceIndices = FaceIndices(doc, data, new[] { "FacesToDraft", "DraftFaces", "Faces", "FaceArray" })
|
||
.Where(i => i != neutralFaceIndex)
|
||
.Distinct()
|
||
.ToList();
|
||
|
||
if (angleDeg <= 0.0 || neutralFaceIndex <= 0 || draftFaceIndices.Count == 0)
|
||
{
|
||
note = $"draft incomplete: angle={angleDeg}, neutral={neutralFaceIndex}, faces={draftFaceIndices.Count}";
|
||
return false;
|
||
}
|
||
|
||
args["angle_deg"] = angleDeg;
|
||
args["neutral_face_index"] = neutralFaceIndex;
|
||
args["draft_face_indices"] = draftFaceIndices;
|
||
args["flip_dir"] = FirstBool(data, new[] { "FlipDir", "ReverseDirection", "Direction" }, false);
|
||
args["edge_draft"] = FirstBool(data, new[] { "EdgeDraft", "IsEdgeDraft" }, false);
|
||
args["propagation_type"] = FirstPositiveInt(data, new[] { "PropagationType" }, 0);
|
||
args["is_step_draft"] = FirstBool(data, new[] { "StepDraft", "IsStepDraft" }, false);
|
||
args["is_body_draft"] = FirstBool(data, new[] { "BodyDraft", "IsBodyDraft" }, false);
|
||
note = $"draft angle={angleDeg}deg, neutral_face={neutralFaceIndex}, draft_faces={draftFaceIndices.Count}";
|
||
return true;
|
||
}
|
||
finally
|
||
{
|
||
TryInvokeCom(data, "ReleaseSelectionAccess");
|
||
}
|
||
}
|
||
|
||
static bool TryExtractRib(Feature feature, string name, string type, out Dictionary<string, object> args, out string note)
|
||
{
|
||
args = new Dictionary<string, object>();
|
||
note = "";
|
||
if (!IsRibFeature(name, type)) return false;
|
||
|
||
object data = SafeObj(() => feature.GetDefinition());
|
||
double thicknessMm = data != null
|
||
? FirstPositiveLengthMm(data, new[] { "Thickness", "RibThickness", "WallThickness" }, 0.0)
|
||
: 0.0;
|
||
if (thicknessMm <= 0.0)
|
||
thicknessMm = FirstFeatureLengthDimensionMm(feature);
|
||
if (thicknessMm <= 0.0)
|
||
{
|
||
note = "rib thickness not available";
|
||
return false;
|
||
}
|
||
|
||
args["thickness_mm"] = thicknessMm;
|
||
if (data is IRibFeatureData2 rib2)
|
||
{
|
||
int extrusionDirection = SafeValue(() => rib2.ExtrusionDirection, (int)swRibExtrusionDirection_e.swRibNormalToSketch);
|
||
int ribType = SafeValue(() => rib2.Type, (int)swRibType_e.swRibLinear);
|
||
args["is_two_sided"] = SafeBool(() => rib2.IsTwoSided);
|
||
args["reverse_thickness_dir"] = SafeBool(() => rib2.ReverseThicknessDir);
|
||
args["reverse_material_dir"] = SafeBool(() => rib2.FlipSide);
|
||
args["is_norm_to_sketch"] = extrusionDirection == (int)swRibExtrusionDirection_e.swRibNormalToSketch;
|
||
args["is_drafted"] = SafeBool(() => rib2.EnableDraft);
|
||
args["draft_outward"] = SafeBool(() => rib2.DraftOutward);
|
||
args["draft_angle_deg"] = R(ToAngleDeg(SafeValue(() => rib2.DraftAngle, 0.0)));
|
||
args["is_drafted_from_wall"] = SafeBool(() => rib2.DraftFromWall);
|
||
args["reference_edge_index"] = SafeValue(() => rib2.RefSketchIndex, 0);
|
||
args["rib_type_code"] = ribType;
|
||
args["rib_type"] = ribType == (int)swRibType_e.swRibNatural ? "natural" : "linear";
|
||
args["extrusion_direction_code"] = extrusionDirection;
|
||
args["extrusion_direction"] = extrusionDirection == (int)swRibExtrusionDirection_e.swRibNormalToSketch ? "normal_to_sketch" : "parallel_to_sketch";
|
||
}
|
||
else if (data is IRibFeatureData rib1)
|
||
{
|
||
args["is_two_sided"] = SafeBool(() => rib1.IsTwoSided);
|
||
args["reverse_thickness_dir"] = SafeBool(() => rib1.ReverseThicknessDir);
|
||
args["reverse_material_dir"] = SafeBool(() => rib1.FlipSide);
|
||
args["is_norm_to_sketch"] = true;
|
||
args["is_drafted"] = SafeBool(() => rib1.EnableDraft);
|
||
args["draft_outward"] = SafeBool(() => rib1.DraftOutward);
|
||
args["draft_angle_deg"] = R(ToAngleDeg(SafeValue(() => rib1.DraftAngle, 0.0)));
|
||
args["is_drafted_from_wall"] = false;
|
||
args["reference_edge_index"] = SafeValue(() => rib1.RefSketchIndex, 0);
|
||
}
|
||
else if (data != null)
|
||
{
|
||
args["is_two_sided"] = FirstBool(data, new[] { "IsTwoSided", "BothSides", "TwoSided" }, false);
|
||
args["reverse_thickness_dir"] = FirstBool(data, new[] { "ReverseThicknessDir", "ReverseThicknessDirection" }, false);
|
||
args["reverse_material_dir"] = FirstBool(data, new[] { "FlipSide", "ReverseMaterialDir", "ReverseMaterialDirection", "FlipMaterialSide" }, false);
|
||
int extrusionDirection = FirstPositiveInt(data, new[] { "ExtrusionDirection" }, (int)swRibExtrusionDirection_e.swRibNormalToSketch);
|
||
args["is_norm_to_sketch"] = extrusionDirection == (int)swRibExtrusionDirection_e.swRibNormalToSketch;
|
||
args["is_drafted"] = FirstBool(data, new[] { "EnableDraft", "IsDrafted", "Drafted" }, false);
|
||
args["draft_outward"] = FirstBool(data, new[] { "DraftOutward", "OutwardDraft" }, false);
|
||
args["draft_angle_deg"] = FirstPositiveAngleDeg(data, new[] { "DraftAngle", "DraftAngle1" }, 0.0);
|
||
args["is_drafted_from_wall"] = FirstBool(data, new[] { "DraftFromWall", "IsDraftedFromWall", "DraftedFromWall" }, false);
|
||
args["reference_edge_index"] = FirstPositiveInt(data, new[] { "RefSketchIndex", "ReferenceEdgeIndex" }, 0);
|
||
}
|
||
note = $"rib thickness={thicknessMm}mm";
|
||
return true;
|
||
}
|
||
|
||
static bool TryExtractReferenceAxis(ModelDoc2 doc, Feature feature, string name, string type, out Dictionary<string, object> args, out string note)
|
||
{
|
||
args = new Dictionary<string, object>();
|
||
note = "";
|
||
if (!IsReferenceAxisFeature(name, type)) return false;
|
||
|
||
var edgeSignatures = new List<Dictionary<string, object>>();
|
||
var datumPlaneNames = new List<string>();
|
||
Dictionary<string, object> faceSignature = new();
|
||
|
||
object data = SafeObj(() => feature.GetDefinition());
|
||
bool accessed = false;
|
||
string dataType = data?.GetType().FullName ?? "";
|
||
try
|
||
{
|
||
if (data is IRefAxisFeatureData axisData)
|
||
{
|
||
accessed = SafeBool(() => axisData.AccessSelections(doc, null));
|
||
object selectionTypes = null;
|
||
foreach (object selected in ToObjectArray(SafeObj(() => axisData.GetSelections(out selectionTypes))))
|
||
AddReferenceAxisSelectionEntity(doc, selected, edgeSignatures, datumPlaneNames, ref faceSignature);
|
||
}
|
||
else if (data != null)
|
||
{
|
||
accessed = SafeBool(() => ((dynamic)data).AccessSelections(doc, null));
|
||
}
|
||
|
||
if (doc?.SelectionManager is SelectionMgr selMgr)
|
||
{
|
||
int count = selMgr.GetSelectedObjectCount2(-1);
|
||
for (int i = 1; i <= count; i++)
|
||
AddReferenceAxisSelectionEntity(doc, selMgr.GetSelectedObject6(i, -1), edgeSignatures, datumPlaneNames, ref faceSignature);
|
||
}
|
||
|
||
if (edgeSignatures.Count == 0 && datumPlaneNames.Count == 0 && faceSignature.Count == 0 && data != null)
|
||
{
|
||
foreach (object selected in ToObjectArray(SafeObj(() => InvokeCom(data, "GetSelections"))))
|
||
AddReferenceAxisSelectionEntity(doc, selected, edgeSignatures, datumPlaneNames, ref faceSignature);
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
if (data != null && accessed)
|
||
TryInvokeCom(data, "ReleaseSelectionAccess");
|
||
}
|
||
|
||
args["axis_name"] = name;
|
||
args["selection_accessed"] = accessed;
|
||
if (!string.IsNullOrWhiteSpace(dataType))
|
||
args["axis_feature_data_type"] = dataType;
|
||
if (edgeSignatures.Count > 0)
|
||
args["edge_signatures"] = edgeSignatures;
|
||
if (faceSignature.Count > 0)
|
||
args["face_signature"] = faceSignature;
|
||
if (datumPlaneNames.Count > 0)
|
||
args["datum_plane_names"] = datumPlaneNames.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
|
||
|
||
note = $"reference axis: edges={edgeSignatures.Count}, face={(faceSignature.Count > 0 ? 1 : 0)}, planes={datumPlaneNames.Count}, accessed={accessed}, data={dataType}";
|
||
return true;
|
||
}
|
||
|
||
static void AddReferenceAxisSelectionEntity(
|
||
ModelDoc2 doc,
|
||
object selected,
|
||
List<Dictionary<string, object>> edgeSignatures,
|
||
List<string> datumPlaneNames,
|
||
ref Dictionary<string, object> faceSignature)
|
||
{
|
||
if (selected == null) return;
|
||
|
||
if (selected is Edge edge)
|
||
{
|
||
var signature = EdgeSignature(edge);
|
||
if (signature.Count > 0)
|
||
edgeSignatures.Add(signature);
|
||
return;
|
||
}
|
||
|
||
var reference = new SketchReferenceResult();
|
||
if (!TryDescribeReferenceEntity(doc, selected, reference))
|
||
return;
|
||
|
||
if (string.Equals(reference.Kind, "datum_plane", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
string planeName = reference.PlaneName;
|
||
if (!string.IsNullOrWhiteSpace(planeName))
|
||
datumPlaneNames.Add(planeName);
|
||
return;
|
||
}
|
||
|
||
if (string.Equals(reference.Kind, "face", StringComparison.OrdinalIgnoreCase) && faceSignature.Count == 0)
|
||
faceSignature = ReferenceFaceSignature(reference);
|
||
}
|
||
|
||
static Dictionary<string, object> ReferenceFaceSignature(SketchReferenceResult reference)
|
||
{
|
||
var result = new Dictionary<string, object>();
|
||
if (reference == null) return result;
|
||
if (!string.IsNullOrWhiteSpace(reference.SurfaceType)) result["surface_type"] = reference.SurfaceType;
|
||
if (reference.AreaMm2 > 0) result["area_mm2"] = reference.AreaMm2;
|
||
if (reference.CenterMm != null && reference.CenterMm.Length >= 3)
|
||
{
|
||
result["center_mm"] = reference.CenterMm;
|
||
result["center_x_mm"] = reference.CenterMm[0];
|
||
result["center_y_mm"] = reference.CenterMm[1];
|
||
result["center_z_mm"] = reference.CenterMm[2];
|
||
}
|
||
if (reference.BoxMm != null && reference.BoxMm.Length >= 6) result["box_mm"] = reference.BoxMm;
|
||
if (reference.Normal != null && reference.Normal.Length >= 3)
|
||
{
|
||
result["normal"] = reference.Normal;
|
||
result["normal_x"] = reference.Normal[0];
|
||
result["normal_y"] = reference.Normal[1];
|
||
result["normal_z"] = reference.Normal[2];
|
||
}
|
||
if (reference.FaceIndex > 0) result["fallback_face_index"] = reference.FaceIndex;
|
||
if (!string.IsNullOrWhiteSpace(reference.HostFeatureName)) result["host_feature"] = reference.HostFeatureName;
|
||
return result;
|
||
}
|
||
|
||
static bool TryExtractShell(ModelDoc2 doc, Feature feature, string name, string type, out Dictionary<string, object> args, out string note)
|
||
{
|
||
args = new Dictionary<string, object>();
|
||
note = "";
|
||
if (!IsShellFeature(name, type)) return false;
|
||
|
||
if (feature.GetDefinition() is not IShellFeatureData data)
|
||
{
|
||
note = "shell definition not available";
|
||
return false;
|
||
}
|
||
|
||
try { data.AccessSelections(doc, null); } catch { }
|
||
try
|
||
{
|
||
double thicknessMm = R(ToLengthMm(data.Thickness));
|
||
if (thicknessMm <= 0.0)
|
||
{
|
||
note = "shell thickness not available";
|
||
return false;
|
||
}
|
||
|
||
var removedFaces = new List<Face2>();
|
||
try
|
||
{
|
||
object removedObj = data.FacesRemoved ?? data.IGetFacesRemoved(data.FacesRemovedCount);
|
||
foreach (object item in FlattenComObjects(removedObj))
|
||
{
|
||
if (item is Face2 face)
|
||
removedFaces.Add(face);
|
||
}
|
||
}
|
||
catch { }
|
||
|
||
if (removedFaces.Count == 0)
|
||
{
|
||
note = "shell remove face not available";
|
||
return false;
|
||
}
|
||
|
||
double[] point = FaceCenterMm(removedFaces[0]);
|
||
if (point.Length < 3)
|
||
{
|
||
note = "shell remove face point not available";
|
||
return false;
|
||
}
|
||
|
||
args["thickness_mm"] = thicknessMm;
|
||
args["x_mm"] = point[0];
|
||
args["y_mm"] = point[1];
|
||
args["z_mm"] = point[2];
|
||
args["outward"] = FirstBool(data, new[] { "Direction", "Outward", "ShellOutward", "ReverseDirection" }, false);
|
||
var removeFaceReference = new SketchReferenceResult();
|
||
if (TryDescribeReferenceEntity(doc, removedFaces[0], removeFaceReference) &&
|
||
string.Equals(removeFaceReference.Kind, "face", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
args["remove_face_signature"] = BuildFaceSignatureArgs(removeFaceReference);
|
||
if (removeFaceReference.FaceIndex > 0)
|
||
args["remove_face_index"] = removeFaceReference.FaceIndex;
|
||
if (removeFaceReference.AreaMm2 > 0)
|
||
args["remove_face_area_mm2"] = removeFaceReference.AreaMm2;
|
||
if (removeFaceReference.CenterMm.Length >= 3)
|
||
args["remove_face_center_mm"] = removeFaceReference.CenterMm;
|
||
if (removeFaceReference.BoxMm.Length >= 6)
|
||
args["remove_face_box_mm"] = removeFaceReference.BoxMm;
|
||
if (removeFaceReference.Normal.Length >= 3)
|
||
args["remove_face_normal"] = removeFaceReference.Normal;
|
||
if (!string.IsNullOrWhiteSpace(removeFaceReference.SurfaceType))
|
||
args["remove_face_surface_type"] = removeFaceReference.SurfaceType;
|
||
if (!string.IsNullOrWhiteSpace(removeFaceReference.HostFeatureName))
|
||
args["remove_face_host_feature"] = removeFaceReference.HostFeatureName;
|
||
}
|
||
note = $"shell thickness={thicknessMm}mm, remove_face_point=({point[0]},{point[1]},{point[2]})";
|
||
return true;
|
||
}
|
||
finally
|
||
{
|
||
try { data.ReleaseSelectionAccess(); } catch { }
|
||
}
|
||
}
|
||
|
||
static bool TryExtractHoleWizard(ModelDoc2 doc, Feature feature, string name, string type, out Dictionary<string, object> args, out string note)
|
||
{
|
||
args = new Dictionary<string, object>();
|
||
note = "";
|
||
if (!IsHoleWizardFeature(name, type)) return false;
|
||
|
||
object data = SafeObj(() => feature.GetDefinition());
|
||
bool accessed = false;
|
||
if (data != null)
|
||
accessed = SafeBool(() => Convert.ToBoolean(InvokeCom(data, "AccessSelections", doc, null)));
|
||
try
|
||
{
|
||
double diameterMm = data != null
|
||
? FirstPositiveLengthMm(data, new[] { "HoleDiameter", "Diameter", "FastenerDiameter" }, 0.0)
|
||
: 0.0;
|
||
double depthMm = data != null
|
||
? FirstPositiveLengthMm(data, new[] { "HoleDepth", "Depth", "EndConditionDepth" }, 0.0)
|
||
: 0.0;
|
||
bool throughAll = data != null && FirstBool(data, new[] { "ThroughAll", "Through", "IsThrough" }, false);
|
||
|
||
var dims = FeatureDimensionValuesMm(feature).Where(v => v > 0.0).OrderBy(v => v).ToList();
|
||
if (diameterMm <= 0.0 && dims.Count > 0) diameterMm = dims[0];
|
||
if (depthMm <= 0.0 && dims.Count > 1) depthMm = dims[^1];
|
||
if (diameterMm <= 0.0)
|
||
{
|
||
note = "hole diameter not available";
|
||
return false;
|
||
}
|
||
if (!throughAll && depthMm <= 0.0)
|
||
depthMm = diameterMm * 2.0;
|
||
|
||
args["diameter_mm"] = diameterMm;
|
||
args["depth_mm"] = depthMm;
|
||
var positionPoints = HoleWizardPositionPointsModelMm(data, feature);
|
||
if (positionPoints.Count > 0)
|
||
{
|
||
args["position_points_model_mm"] = positionPoints;
|
||
args["position_point_source"] = "wizard_hole_feature_data_get_sketch_points";
|
||
}
|
||
else
|
||
{
|
||
args["position_points_model_mm"] = new List<List<double>>();
|
||
args["position_point_missing"] = "IWizardHoleFeatureData2.GetSketchPoints returned no usable sketch points";
|
||
}
|
||
args["through_all"] = throughAll;
|
||
args["reverse_dir"] = data != null && FirstBool(data, new[] { "ReverseDirection", "FlipDirection" }, false);
|
||
|
||
AddHoleWizardFeatureDataArgs(doc, data, feature, args);
|
||
AddHoleWizardTargetFaceFromSelectionArgs(doc, args);
|
||
note = $"hole wizard diameter={diameterMm}mm, depth={depthMm}mm, through_all={throughAll}, points={positionPoints.Count}";
|
||
return true;
|
||
}
|
||
finally
|
||
{
|
||
if (accessed)
|
||
TryInvokeCom(data, "ReleaseSelectionAccess");
|
||
}
|
||
}
|
||
|
||
static void AddHoleWizardTargetFaceFromSelectionArgs(ModelDoc2 doc, Dictionary<string, object> args)
|
||
{
|
||
try
|
||
{
|
||
if (doc?.SelectionManager is not SelectionMgr selMgr)
|
||
return;
|
||
|
||
int count = selMgr.GetSelectedObjectCount2(-1);
|
||
Face2 selectedFace = null;
|
||
for (int i = 1; i <= count; i++)
|
||
{
|
||
object selected = selMgr.GetSelectedObject6(i, -1);
|
||
if (selected is Face2 face)
|
||
{
|
||
selectedFace = face;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (selectedFace == null)
|
||
return;
|
||
|
||
var reference = new SketchReferenceResult();
|
||
if (!TryDescribeReferenceEntity(doc, selectedFace, reference) ||
|
||
!string.Equals(reference.Kind, "face", StringComparison.OrdinalIgnoreCase))
|
||
return;
|
||
|
||
ApplyHoleWizardTargetFaceReferenceArgs(args, reference, "hole_wizard_selection", "");
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
static void ApplyHoleWizardTargetFaceReferenceArgs(Dictionary<string, object> args, SketchReferenceResult reference, string source, string note)
|
||
{
|
||
if (args == null || reference == null)
|
||
return;
|
||
|
||
args["target_face_source"] = source;
|
||
args["target_face_role"] = "hole_start_face";
|
||
args["placement_reference_kind"] = reference.Kind;
|
||
args["placement_reference"] = reference.Description;
|
||
if (!string.IsNullOrWhiteSpace(reference.HostFeatureName))
|
||
args["target_host_feature"] = reference.HostFeatureName;
|
||
if (reference.HostFeatureStep > 0)
|
||
args["target_host_step"] = reference.HostFeatureStep;
|
||
if (reference.FaceIndex > 0)
|
||
{
|
||
args["target_face_indices"] = new List<int> { reference.FaceIndex };
|
||
args["target_face_index"] = reference.FaceIndex;
|
||
}
|
||
if (!string.IsNullOrWhiteSpace(reference.SurfaceType))
|
||
args["target_face_surface_type"] = reference.SurfaceType;
|
||
if (reference.AreaMm2 > 0)
|
||
args["target_face_area_mm2"] = reference.AreaMm2;
|
||
if (reference.CenterMm.Length >= 3)
|
||
{
|
||
args["target_face_center_mm"] = reference.CenterMm;
|
||
args["target_face_x_mm"] = reference.CenterMm[0];
|
||
args["target_face_y_mm"] = reference.CenterMm[1];
|
||
args["target_face_z_mm"] = reference.CenterMm[2];
|
||
}
|
||
if (reference.BoxMm.Length >= 6)
|
||
args["target_face_box_mm"] = reference.BoxMm;
|
||
if (reference.Normal.Length >= 3)
|
||
args["target_face_normal"] = reference.Normal;
|
||
if (!string.IsNullOrWhiteSpace(reference.PersistReferenceHex))
|
||
args["target_face_persist_ref_hex"] = reference.PersistReferenceHex;
|
||
|
||
var signature = BuildFaceSignatureArgs(reference);
|
||
if (signature.Count > 0)
|
||
args["target_face_signature"] = signature;
|
||
if (!string.IsNullOrWhiteSpace(note))
|
||
args["target_face_selection_note"] = note;
|
||
}
|
||
|
||
static bool TryReadArgDouble(Dictionary<string, object> args, string key, out double value)
|
||
{
|
||
value = 0.0;
|
||
if (args == null || !args.TryGetValue(key, out var obj) || obj == null) return false;
|
||
try
|
||
{
|
||
value = Convert.ToDouble(obj, System.Globalization.CultureInfo.InvariantCulture);
|
||
return true;
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static bool TryReadArgDoubleList(Dictionary<string, object> args, string key, out List<double> values)
|
||
{
|
||
values = new List<double>();
|
||
if (args == null || !args.TryGetValue(key, out var obj) || obj == null) return false;
|
||
if (obj is IEnumerable<double> doubles)
|
||
{
|
||
values = doubles.ToList();
|
||
return values.Count > 0;
|
||
}
|
||
if (obj is IEnumerable<object> objects)
|
||
{
|
||
foreach (var item in objects)
|
||
{
|
||
try { values.Add(Convert.ToDouble(item, System.Globalization.CultureInfo.InvariantCulture)); }
|
||
catch { return false; }
|
||
}
|
||
return values.Count > 0;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static bool TryReadPointListArg(Dictionary<string, object> args, string key, out List<List<double>> points)
|
||
{
|
||
points = new List<List<double>>();
|
||
if (args == null || !args.TryGetValue(key, out var obj) || obj == null) return false;
|
||
if (obj is IEnumerable<List<double>> listPoints)
|
||
{
|
||
points = listPoints.Select(p => p.ToList()).ToList();
|
||
return points.Count > 0;
|
||
}
|
||
if (obj is IEnumerable<object> objectPoints)
|
||
{
|
||
foreach (var pointObj in objectPoints)
|
||
{
|
||
if (pointObj is IEnumerable<double> doubles)
|
||
points.Add(doubles.ToList());
|
||
else if (pointObj is IEnumerable<object> objects)
|
||
points.Add(objects.Select(v => Convert.ToDouble(v, System.Globalization.CultureInfo.InvariantCulture)).ToList());
|
||
}
|
||
return points.Count > 0;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static bool HasHoleWizardTargetFace(Dictionary<string, object> args)
|
||
{
|
||
return args != null &&
|
||
args.TryGetValue("target_face_role", out var roleObj) &&
|
||
string.Equals(roleObj?.ToString(), "hole_start_face", StringComparison.OrdinalIgnoreCase);
|
||
}
|
||
|
||
static bool HoleWizardPointsMatchSketch(Feature sketchFeature, Dictionary<string, object> args)
|
||
{
|
||
if (sketchFeature == null || args == null)
|
||
return false;
|
||
if (!TryReadPointListArg(args, "position_points_model_mm", out var holePoints) || holePoints.Count == 0)
|
||
return false;
|
||
|
||
var sketchPoints = SketchFreePointModelMmFromFeature(sketchFeature);
|
||
if (sketchPoints.Count == 0)
|
||
return false;
|
||
|
||
foreach (var holePoint in holePoints)
|
||
{
|
||
if (holePoint == null || holePoint.Count < 3)
|
||
return false;
|
||
bool matched = sketchPoints.Any(sketchPoint =>
|
||
sketchPoint.Count >= 3 &&
|
||
Distance(holePoint.Take(3).ToArray(), sketchPoint.Take(3).ToArray()) <= 0.25);
|
||
if (!matched)
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static void AddHoleWizardTargetFaceFromPlacementSketch(ModelDoc2 doc, Feature sketchFeature, Dictionary<string, object> args)
|
||
{
|
||
try
|
||
{
|
||
if (sketchFeature == null || sketchFeature.GetSpecificFeature2() is not Sketch sketch)
|
||
return;
|
||
|
||
var debug = new SketchReferenceDebugInfo();
|
||
var sketchReference = ResolveSketchPlane(doc, sketchFeature, sketch, debug);
|
||
NormalizeGenericDatumPlaneReference(sketch, sketchReference);
|
||
if (!string.Equals(sketchReference.Kind, "face", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
args["target_face_missing"] = "matched placement sketch did not resolve to a model face";
|
||
args["target_face_missing_reference_kind"] = sketchReference.Kind;
|
||
args["target_face_missing_reference"] = sketchReference.Description;
|
||
return;
|
||
}
|
||
|
||
string sketchName = Safe(() => sketchFeature.Name, "");
|
||
ApplyHoleWizardTargetFaceReferenceArgs(
|
||
args,
|
||
sketchReference,
|
||
"placement_sketch_matched_by_wizard_points",
|
||
string.IsNullOrWhiteSpace(sketchName) ? "" : $"placement_sketch={sketchName}");
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
static List<List<double>> SketchFreePointModelMmFromFeature(Feature sketchFeature)
|
||
{
|
||
var points = new List<List<double>>();
|
||
if (sketchFeature == null || sketchFeature.GetSpecificFeature2() is not Sketch sketch)
|
||
return points;
|
||
|
||
var canonicalPoints = BuildSketchPointCanonicalMap(sketch);
|
||
var segmentPointIds = new HashSet<IntPtr>();
|
||
var segmentPointKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
foreach (object o in ToObjectArray(SafeObj(() => sketch.GetSketchSegments())))
|
||
{
|
||
if (o is SketchSegment segment)
|
||
RegisterSegmentPoints(segment, canonicalPoints, segmentPointIds, segmentPointKeys);
|
||
}
|
||
|
||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
foreach (double[] pointLocal in UserSketchPointsLocalMm(sketch))
|
||
{
|
||
if (pointLocal.Length < 3) continue;
|
||
double x = pointLocal[0], y = pointLocal[1], z = pointLocal[2];
|
||
if (segmentPointKeys.Contains(SketchPointCoordKey(x, y, z))) continue;
|
||
if (!TrySketchLocalMmToModelMm(sketch, x, y, z, out var modelPoint)) continue;
|
||
string key = string.Join(",", modelPoint.Select(v => R(v).ToString(System.Globalization.CultureInfo.InvariantCulture)));
|
||
if (seen.Add(key))
|
||
points.Add(modelPoint);
|
||
}
|
||
|
||
return points;
|
||
}
|
||
|
||
static Feature FindFeatureOwningSketch(Feature feature, Sketch sketch)
|
||
{
|
||
if (feature == null || sketch == null) return null;
|
||
var targetId = Id(sketch);
|
||
Feature sub = feature.GetFirstSubFeature() as Feature;
|
||
for (int i = 0; sub != null && i < 300; i++)
|
||
{
|
||
try
|
||
{
|
||
if (sub.GetSpecificFeature2() is Sketch candidate && Id(candidate) == targetId)
|
||
return sub;
|
||
}
|
||
catch { }
|
||
sub = sub.GetNextSubFeature() as Feature;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
static void AddHoleWizardFeatureDataArgs(ModelDoc2 doc, object data, Feature feature, Dictionary<string, object> args)
|
||
{
|
||
if (data == null) return;
|
||
|
||
args["hole_wizard_api"] = "HoleWizard5";
|
||
if (TryReadIntMember(data, "Type", out int holeType))
|
||
{
|
||
args["hole_type"] = holeType;
|
||
args["hole_type_name"] = EnumName(typeof(swWzdHoleTypes_e), holeType);
|
||
}
|
||
|
||
int genericHoleType = InferWizardGenericHoleType(holeType);
|
||
if (genericHoleType != int.MinValue)
|
||
{
|
||
args["generic_hole_type"] = genericHoleType;
|
||
args["generic_hole_type_name"] = EnumName(typeof(swWzdGeneralHoleTypes_e), genericHoleType);
|
||
args["generic_hole_type_source"] = "derived_from_iwizardholefeaturedata2_type";
|
||
}
|
||
|
||
AddIntArgAllowZero(args, "standard_index", data, new[] { "Standard2", "Standard", "StandardIndex", "HoleStandard" }, typeof(swWzdHoleStandards_e), "standard_name");
|
||
AddIntArgAllowZero(args, "fastener_type_index", data, new[] { "FastenerType2", "FastenerType", "TypeIndex" }, typeof(swWzdHoleStandardFastenerTypes_e), "fastener_type_name");
|
||
AddStringArg(args, "size", data, new[] { "FastenerSize", "Size", "SSize", "HoleSize" });
|
||
AddStringArg(args, "thread_class", data, new[] { "ThreadClass", "Class" });
|
||
AddStringArg(args, "thread_callout", data, new[] { "ThreadCallout", "Callout" });
|
||
AddIntArgAllowZero(args, "end_condition_code", data, new[] { "EndCondition" }, typeof(swEndConditions_e), "end_condition_name");
|
||
AddIntArgAllowZero(args, "thread_end_condition_code", data, new[] { "ThreadEndCondition", "ThreadEndType" }, null, "");
|
||
AddIntArgAllowZero(args, "cosmetic_thread_type", data, new[] { "CosmeticThreadType", "ThreadType" }, null, "");
|
||
AddIntArgAllowZero(args, "tap_type", data, new[] { "TapType" }, null, "");
|
||
AddIntArgAllowZero(args, "hole_fit", data, new[] { "HoleFit" }, null, "");
|
||
AddIntArgAllowZero(args, "head_clearance_type", data, new[] { "HeadClearanceType" }, null, "");
|
||
AddLengthArg(args, "hole_diameter_mm", data, new[] { "HoleDiameter", "Diameter" });
|
||
AddLengthArg(args, "hole_depth_mm", data, new[] { "HoleDepth", "Depth" });
|
||
AddLengthArg(args, "length_mm", data, new[] { "Length" });
|
||
AddLengthArg(args, "tap_drill_diameter_mm", data, new[] { "TapDrillDiameter" });
|
||
AddLengthArg(args, "tap_drill_depth_mm", data, new[] { "TapDrillDepth" });
|
||
AddLengthArg(args, "thread_diameter_mm", data, new[] { "ThreadDiameter", "TapDrillDiameter" });
|
||
AddLengthArg(args, "thread_depth_mm", data, new[] { "ThreadDepth", "TapDepth" });
|
||
AddLengthArg(args, "major_diameter_mm", data, new[] { "MajorDiameter" });
|
||
AddLengthArg(args, "minor_diameter_mm", data, new[] { "MinorDiameter" });
|
||
AddLengthArg(args, "thru_hole_diameter_mm", data, new[] { "ThruHoleDiameter" });
|
||
AddLengthArg(args, "thru_hole_depth_mm", data, new[] { "ThruHoleDepth" });
|
||
AddLengthArg(args, "thru_tap_drill_diameter_mm", data, new[] { "ThruTapDrillDiameter" });
|
||
AddLengthArg(args, "thru_tap_drill_depth_mm", data, new[] { "ThruTapDrillDepth" });
|
||
AddLengthArg(args, "counter_bore_diameter_mm", data, new[] { "CounterBoreDiameter" });
|
||
AddLengthArg(args, "counter_bore_depth_mm", data, new[] { "CounterBoreDepth" });
|
||
AddLengthArg(args, "counter_sink_diameter_mm", data, new[] { "CounterSinkDiameter" });
|
||
AddLengthArg(args, "near_counter_sink_diameter_mm", data, new[] { "NearCounterSinkDiameter" });
|
||
AddLengthArg(args, "mid_counter_sink_diameter_mm", data, new[] { "MidCounterSinkDiameter" });
|
||
AddLengthArg(args, "far_counter_sink_diameter_mm", data, new[] { "FarCounterSinkDiameter" });
|
||
AddLengthArg(args, "counter_drill_diameter_mm", data, new[] { "CounterDrillDiameter" });
|
||
AddLengthArg(args, "counter_drill_depth_mm", data, new[] { "CounterDrillDepth" });
|
||
AddLengthArg(args, "head_clearance_mm", data, new[] { "HeadClearance" });
|
||
AddLengthArg(args, "offset_distance_mm", data, new[] { "OffsetDistance" });
|
||
AddLengthArg(args, "drill_angle_deg", data, new[] { "DrillAngle", "TipAngle" }, isAngle: true);
|
||
AddLengthArg(args, "thread_angle_deg", data, new[] { "ThreadAngle" }, isAngle: true);
|
||
AddLengthArg(args, "counter_sink_angle_deg", data, new[] { "CounterSinkAngle" }, isAngle: true);
|
||
AddLengthArg(args, "near_counter_sink_angle_deg", data, new[] { "NearCounterSinkAngle" }, isAngle: true);
|
||
AddLengthArg(args, "mid_counter_sink_angle_deg", data, new[] { "MidCounterSinkAngle" }, isAngle: true);
|
||
AddLengthArg(args, "far_counter_sink_angle_deg", data, new[] { "FarCounterSinkAngle" }, isAngle: true);
|
||
AddLengthArg(args, "counter_drill_angle_deg", data, new[] { "CounterDrillAngle" }, isAngle: true);
|
||
args["feature_scope"] = FirstBool(data, new[] { "FeatureScope" }, true);
|
||
args["auto_select"] = FirstBool(data, new[] { "AutoSelect" }, true);
|
||
args["assembly_feature_scope"] = FirstBool(data, new[] { "AssemblyFeatureScope" }, false);
|
||
args["auto_select_components"] = FirstBool(data, new[] { "AutoSelectComponents" }, false);
|
||
args["propagate_feature_to_parts"] = FirstBool(data, new[] { "PropagateFeatureToParts" }, false);
|
||
|
||
var targetFaceIndices = FaceIndices(doc, data, new[] { "Face", "Faces", "FaceArray", "TargetFace", "TargetFaces", "PlacementFace", "PlacementFaces" }).Distinct().ToList();
|
||
if (targetFaceIndices.Count > 0)
|
||
args["target_face_indices"] = targetFaceIndices;
|
||
|
||
Face2 face = FirstFace(data, new[] { "Face", "Faces", "FaceArray", "TargetFace", "TargetFaces", "PlacementFace", "PlacementFaces" });
|
||
if (face != null)
|
||
{
|
||
var reference = new SketchReferenceResult();
|
||
if (TryDescribeReferenceEntity(doc, face, reference) &&
|
||
string.Equals(reference.Kind, "face", StringComparison.OrdinalIgnoreCase))
|
||
ApplyHoleWizardTargetFaceReferenceArgs(args, reference, "wizard_hole_feature_data_face", "");
|
||
|
||
args["target_face_area_mm2"] = R(SafeDouble(() => face.GetArea()) * 1_000_000.0);
|
||
var center = FaceCenterMm(face);
|
||
if (center.Length >= 3)
|
||
args["target_face_center_mm"] = center;
|
||
var box = TryGetFaceBoxMm(face);
|
||
if (box.Length >= 6)
|
||
args["target_face_box_mm"] = box;
|
||
var normal = FaceNormal(face);
|
||
if (normal.Length >= 3)
|
||
args["target_face_normal"] = normal;
|
||
}
|
||
}
|
||
|
||
static void AddIntArg(Dictionary<string, object> args, string key, object data, IEnumerable<string> names)
|
||
{
|
||
int value = FirstPositiveInt(data, names, int.MinValue);
|
||
if (value != int.MinValue) args[key] = value;
|
||
}
|
||
|
||
static void AddIntArgAllowZero(Dictionary<string, object> args, string key, object data, IEnumerable<string> names, Type enumType = null, string enumKey = "")
|
||
{
|
||
int value = FirstReadableInt(data, names, int.MinValue);
|
||
if (value == int.MinValue) return;
|
||
args[key] = value;
|
||
if (enumType != null && !string.IsNullOrWhiteSpace(enumKey))
|
||
args[enumKey] = EnumName(enumType, value);
|
||
}
|
||
|
||
static int FirstReadableInt(object data, IEnumerable<string> names, int fallback)
|
||
{
|
||
foreach (string name in names)
|
||
{
|
||
if (TryReadIntMember(data, name, out int value))
|
||
return value;
|
||
}
|
||
return fallback;
|
||
}
|
||
|
||
static bool TryReadIntMember(object data, string name, out int value)
|
||
{
|
||
value = 0;
|
||
object raw = SafeObj(() => GetComProperty(data, name)) ?? InvokeCom(data, name);
|
||
if (raw == null) return false;
|
||
try
|
||
{
|
||
value = Convert.ToInt32(raw);
|
||
return true;
|
||
}
|
||
catch
|
||
{
|
||
return int.TryParse(raw.ToString(), out value);
|
||
}
|
||
}
|
||
|
||
static int InferWizardGenericHoleType(int holeType)
|
||
{
|
||
if (holeType >= (int)swWzdHoleTypes_e.swPipeTapBlind && holeType <= (int)swWzdHoleTypes_e.swPipeTapThruCounterSinkTopBottom)
|
||
return (int)swWzdGeneralHoleTypes_e.swWzdPipeTap;
|
||
if ((holeType >= (int)swWzdHoleTypes_e.swTapBlind && holeType <= (int)swWzdHoleTypes_e.swTapThruCounterSinkTopBottom)
|
||
|| (holeType >= (int)swWzdHoleTypes_e.swTapBlindCosmeticThread && holeType <= (int)swWzdHoleTypes_e.swTapBlindRemoveThread))
|
||
return (int)swWzdGeneralHoleTypes_e.swWzdTap;
|
||
if (holeType >= (int)swWzdHoleTypes_e.swCounterBoreSlotBlind)
|
||
return (int)swWzdGeneralHoleTypes_e.swWzdHoleSlot;
|
||
if (holeType >= (int)swWzdHoleTypes_e.swCounterBoreBlind && holeType <= (int)swWzdHoleTypes_e.swCounterBoreThruCounterSinkTopMiddleBottom)
|
||
return (int)swWzdGeneralHoleTypes_e.swWzdCounterBore;
|
||
if (holeType == (int)swWzdHoleTypes_e.swCounterSunk
|
||
|| holeType == (int)swWzdHoleTypes_e.swCounterSinkBlind
|
||
|| holeType == (int)swWzdHoleTypes_e.swCounterSinkThru
|
||
|| holeType == (int)swWzdHoleTypes_e.swCounterSinkBlindWithoutHeadClearance
|
||
|| holeType == (int)swWzdHoleTypes_e.swCounterSinkThruWithoutHeadClearance
|
||
|| holeType == (int)swWzdHoleTypes_e.swCounterSinkThruCounterSinkBottomWithoutHeadClearance)
|
||
return (int)swWzdGeneralHoleTypes_e.swWzdCounterSink;
|
||
if (holeType != 0)
|
||
return (int)swWzdGeneralHoleTypes_e.swWzdHole;
|
||
return int.MinValue;
|
||
}
|
||
|
||
static string EnumName(Type enumType, int value)
|
||
{
|
||
try
|
||
{
|
||
string name = Enum.GetName(enumType, value);
|
||
return string.IsNullOrWhiteSpace(name) ? value.ToString(System.Globalization.CultureInfo.InvariantCulture) : name;
|
||
}
|
||
catch
|
||
{
|
||
return value.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||
}
|
||
}
|
||
|
||
static void AddStringArg(Dictionary<string, object> args, string key, object data, IEnumerable<string> names)
|
||
{
|
||
foreach (string name in names)
|
||
{
|
||
object value = SafeObj(() => GetComProperty(data, name)) ?? InvokeCom(data, name);
|
||
string text = value?.ToString() ?? "";
|
||
if (!string.IsNullOrWhiteSpace(text))
|
||
{
|
||
args[key] = text;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
static void AddLengthArg(Dictionary<string, object> args, string key, object data, IEnumerable<string> names, bool isAngle = false)
|
||
{
|
||
foreach (string name in names)
|
||
{
|
||
double value = ReadDoubleMember(data, name);
|
||
if (Math.Abs(value) <= 1e-9) continue;
|
||
args[key] = isAngle ? R(ToAngleDeg(value)) : R(ToLengthMm(value));
|
||
return;
|
||
}
|
||
}
|
||
|
||
static List<List<double>> HoleWizardPositionPointsModelMm(object data, Feature feature)
|
||
{
|
||
var points = new List<List<double>>();
|
||
if (data is not IWizardHoleFeatureData2 wizardData)
|
||
return points;
|
||
|
||
Sketch sketch = FirstSketchFromFeature(feature);
|
||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
|
||
void AddPoint(SketchPoint point)
|
||
{
|
||
if (point == null) return;
|
||
double x = point.X * 1000.0;
|
||
double y = point.Y * 1000.0;
|
||
double z = point.Z * 1000.0;
|
||
|
||
List<double> modelPoint;
|
||
if (sketch != null && TrySketchLocalMmToModelMm(sketch, x, y, z, out var transformedPoint))
|
||
modelPoint = transformedPoint;
|
||
else
|
||
modelPoint = new List<double> { R(x), R(y), R(z) };
|
||
|
||
string key = string.Join(",", modelPoint.Select(v => R(v).ToString(System.Globalization.CultureInfo.InvariantCulture)));
|
||
if (seen.Add(key))
|
||
points.Add(modelPoint);
|
||
}
|
||
|
||
foreach (object pointObj in ToObjectArray(SafeObj(() => wizardData.GetSketchPoints())))
|
||
{
|
||
if (pointObj is SketchPoint point)
|
||
AddPoint(point);
|
||
}
|
||
|
||
if (points.Count > 0)
|
||
return points;
|
||
|
||
int count = SafeInt(() => wizardData.GetSketchPointCount(), 0);
|
||
if (count <= 0)
|
||
return points;
|
||
|
||
object rawPoints = SafeObj(() => InvokeCom(data, "IGetSketchPoints", count)) ??
|
||
SafeObj(() => wizardData.IGetSketchPoints(count));
|
||
foreach (object pointObj in ToObjectArray(rawPoints))
|
||
{
|
||
if (pointObj is SketchPoint point)
|
||
AddPoint(point);
|
||
}
|
||
|
||
return points;
|
||
}
|
||
|
||
static List<List<double>> DominantCoplanarPoints(List<List<double>> candidates)
|
||
{
|
||
if (candidates == null || candidates.Count == 0) return new List<List<double>>();
|
||
var best = new List<List<double>>();
|
||
const double bucketMm = 0.01;
|
||
for (int axis = 0; axis < 3; axis++)
|
||
{
|
||
var groups = new Dictionary<long, List<List<double>>>();
|
||
foreach (var point in candidates)
|
||
{
|
||
if (point == null || point.Count < 3) continue;
|
||
long bucket = (long)Math.Round(point[axis] / bucketMm);
|
||
if (!groups.TryGetValue(bucket, out var group))
|
||
{
|
||
group = new List<List<double>>();
|
||
groups[bucket] = group;
|
||
}
|
||
group.Add(point);
|
||
}
|
||
|
||
foreach (var group in groups.Values)
|
||
{
|
||
if (group.Count > best.Count)
|
||
best = group;
|
||
}
|
||
}
|
||
|
||
var result = new List<List<double>>();
|
||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
foreach (var point in best)
|
||
{
|
||
string key = string.Join(",", point.Select(v => R(v).ToString(System.Globalization.CultureInfo.InvariantCulture)));
|
||
if (seen.Add(key))
|
||
result.Add(point);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
static bool TryExtractMirror(ModelDoc2 doc, Feature feature, string name, string type, out Dictionary<string, object> args, out string note)
|
||
{
|
||
args = new Dictionary<string, object>();
|
||
note = "";
|
||
if (!IsMirrorFeature(name, type)) return false;
|
||
|
||
object data = SafeObj(() => feature.GetDefinition());
|
||
bool accessed = false;
|
||
if (data != null)
|
||
{
|
||
IMirrorPatternFeatureData typedData = data as IMirrorPatternFeatureData;
|
||
if (typedData != null)
|
||
accessed = SafeBool(() => typedData.AccessSelections(doc, null));
|
||
else
|
||
accessed = SafeBool(() => Convert.ToBoolean(InvokeCom(data, "AccessSelections", doc, null)));
|
||
|
||
var seedFeatures = typedData != null
|
||
? FeatureNamesFromObject(typedData.PatternFeatureArray)
|
||
: new List<string>();
|
||
if (seedFeatures.Count == 0)
|
||
seedFeatures = FeatureNames(data, new[] { "PatternFeatureArray", "FeatureArray", "SeedFeatures", "FeaturesToMirror" });
|
||
if (seedFeatures.Count > 0)
|
||
{
|
||
args["feature_name"] = seedFeatures[0];
|
||
args["feature_names"] = seedFeatures;
|
||
args["feature_count"] = seedFeatures.Count;
|
||
}
|
||
|
||
string planeName = typedData != null
|
||
? ReferenceName(typedData.Plane)
|
||
: "";
|
||
if (string.IsNullOrWhiteSpace(planeName))
|
||
planeName = FirstReferenceName(data, new[] { "Plane", "MirrorFaceOrPlane", "MirrorPlane", "MirrorReference" });
|
||
if (!string.IsNullOrWhiteSpace(planeName))
|
||
args["mirror_plane_name"] = planeName;
|
||
|
||
args["geometry_pattern"] = FirstBool(data, new[] { "GeometryPattern", "GeometryPatternOption" }, true);
|
||
args["pattern_seed_only"] = FirstBool(data, new[] { "PatternSeedOnly" }, false);
|
||
args["propagate_visual_property"] = FirstBool(data, new[] { "PropagateVisualProperty" }, true);
|
||
|
||
if (accessed)
|
||
{
|
||
try
|
||
{
|
||
if (typedData != null) typedData.ReleaseSelectionAccess();
|
||
else InvokeCom(data, "ReleaseSelectionAccess");
|
||
}
|
||
catch { }
|
||
}
|
||
}
|
||
|
||
if (!args.ContainsKey("mirror_plane_name"))
|
||
{
|
||
args["mirror_plane_missing"] = true;
|
||
note = $"mirror features={args.GetValueOrDefault("feature_count", 0)}, first={args.GetValueOrDefault("feature_name", "")}, plane=unresolved";
|
||
}
|
||
else
|
||
{
|
||
note = $"mirror features={args.GetValueOrDefault("feature_count", 0)}, first={args.GetValueOrDefault("feature_name", "")}, plane={args["mirror_plane_name"]}";
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static bool TryExtractWrap(ModelDoc2 doc, Feature feature, string name, string type, out Dictionary<string, object> args, out string note)
|
||
{
|
||
args = new Dictionary<string, object>();
|
||
note = "";
|
||
if (!IsWrapFeature(name, type)) return false;
|
||
|
||
object data = SafeObj(() => feature.GetDefinition());
|
||
if (data == null)
|
||
{
|
||
note = "wrap definition not available";
|
||
return false;
|
||
}
|
||
|
||
try { TryInvokeCom(data, "AccessSelections", doc, null); } catch { }
|
||
try
|
||
{
|
||
double thicknessMm = FirstPositiveLengthMm(data, new[] { "Thickness", "Depth", "WrapThickness" }, 0.0);
|
||
var faceIndices = FaceIndices(doc, data, new[] { "Faces", "TargetFaces", "WrapFaces", "FaceArray" }).Distinct().ToList();
|
||
string sketchName = FirstReferenceName(data, new[] { "Sketch", "ProfileSketch", "SourceSketch" });
|
||
|
||
args["thickness_mm"] = thicknessMm;
|
||
if (faceIndices.Count > 0) args["target_face_indices"] = faceIndices;
|
||
if (!string.IsNullOrWhiteSpace(sketchName)) args["sketch_name"] = sketchName;
|
||
args["wrap_type"] = FirstPositiveInt(data, new[] { "WrapType", "Type" }, 0);
|
||
args["reverse_direction"] = FirstBool(data, new[] { "ReverseDirection", "FlipDirection" }, false);
|
||
args["method"] = FirstPositiveInt(data, new[] { "Method" }, 0);
|
||
args["spline_surface_quality"] = FirstPositiveInt(data, new[] { "SplineSurfaceQuality", "SurfaceQuality" }, 1);
|
||
|
||
note = $"wrap thickness={thicknessMm}mm, target_faces={faceIndices.Count}, sketch={sketchName}";
|
||
return true;
|
||
}
|
||
finally
|
||
{
|
||
TryInvokeCom(data, "ReleaseSelectionAccess");
|
||
}
|
||
}
|
||
|
||
static bool TryExtractHelix(Feature feature, string name, string type, out Dictionary<string, object> args, out string note)
|
||
{
|
||
args = new Dictionary<string, object>();
|
||
note = "";
|
||
if (!IsHelixFeature(name, type)) return false;
|
||
|
||
object data = SafeObj(() => feature.GetDefinition());
|
||
if (data is not IHelixFeatureData helix)
|
||
{
|
||
note = "helix definition not available";
|
||
return false;
|
||
}
|
||
|
||
args["feature_name"] = name;
|
||
args["defined_by"] = helix.DefinedBy;
|
||
args["height_mm"] = R(helix.Height * 1000.0);
|
||
args["pitch_mm"] = R(helix.Pitch * 1000.0);
|
||
args["revolution"] = R(helix.Revolution);
|
||
args["starting_angle_deg"] = R(helix.StartingAngle * 180.0 / Math.PI);
|
||
args["reverse_direction"] = helix.ReverseDirection;
|
||
args["clockwise"] = helix.Clockwise;
|
||
args["taper"] = helix.Taper;
|
||
args["taper_angle_deg"] = R(helix.TaperAngle * 180.0 / Math.PI);
|
||
args["taper_outward"] = helix.TaperOutward;
|
||
args["variable_pitch"] = helix.VariablePitch;
|
||
|
||
note = $"helix sketch=last, defined_by={helix.DefinedBy}, height={args["height_mm"]}mm, pitch={args["pitch_mm"]}mm, revolution={args["revolution"]}";
|
||
return true;
|
||
}
|
||
|
||
static bool TryExtractSweep(ModelDoc2 doc, Feature feature, string name, string type, out string skill, out Dictionary<string, object> args, out string note)
|
||
{
|
||
skill = "";
|
||
args = new Dictionary<string, object>();
|
||
note = "";
|
||
if (!IsSweepFeature(name, type)) return false;
|
||
|
||
bool isCut = IsCutLike(name, type);
|
||
object data = SafeObj(() => feature.GetDefinition());
|
||
if (data is ISweepFeatureData sweepData)
|
||
{
|
||
bool accessed = SafeBool(() => sweepData.AccessSelections(doc, null));
|
||
try
|
||
{
|
||
object profileObj = SafeObj(() => sweepData.Profile);
|
||
string profileName = CanonicalReplayReferenceName(profileObj);
|
||
string profileNameSource = ReferenceName(profileObj);
|
||
string subSketchProfileName = FirstCanonicalSubSketchName(feature);
|
||
if (!string.IsNullOrWhiteSpace(subSketchProfileName) &&
|
||
!string.Equals(profileName, subSketchProfileName, StringComparison.OrdinalIgnoreCase))
|
||
profileName = subSketchProfileName;
|
||
string pathName = ReferenceName(SafeObj(() => sweepData.Path));
|
||
bool circularProfile = SafeBool(() => sweepData.CircularProfile);
|
||
int direction = SafeInt(() => sweepData.Direction, 0);
|
||
|
||
if (!circularProfile && isCut && !string.IsNullOrWhiteSpace(profileName) && !string.IsNullOrWhiteSpace(pathName))
|
||
{
|
||
skill = "swept_cut_profile_path";
|
||
args["profile_name"] = profileName;
|
||
args["path_name"] = pathName;
|
||
args["direction"] = direction;
|
||
args["circular_profile"] = false;
|
||
if (!string.IsNullOrWhiteSpace(profileNameSource) &&
|
||
!string.Equals(profileName, profileNameSource, StringComparison.OrdinalIgnoreCase))
|
||
args["source_profile_name"] = profileNameSource;
|
||
note = $"sweep cut profile={profileName}, path={pathName}, direction={direction}";
|
||
return true;
|
||
}
|
||
|
||
double circularDiameterMm = R(sweepData.CircularProfileDiameter * 1000.0);
|
||
if (circularProfile && circularDiameterMm > 0.0)
|
||
{
|
||
skill = isCut ? "swept_cut_circular_profile_mm" : "swept_boss_circular_profile_mm";
|
||
args["diameter_mm"] = circularDiameterMm;
|
||
if (!string.IsNullOrWhiteSpace(pathName)) args["path_sketch_name"] = pathName;
|
||
args["direction"] = direction;
|
||
if (!isCut) args["merge"] = true;
|
||
note = $"sweep circular diameter={circularDiameterMm}mm, path={pathName}, direction={direction}";
|
||
return true;
|
||
}
|
||
|
||
note = $"sweep profile/path unresolved: circular={circularProfile}, profile={profileName}, path={pathName}";
|
||
return false;
|
||
}
|
||
finally
|
||
{
|
||
if (accessed) SafeAction(() => sweepData.ReleaseSelectionAccess());
|
||
}
|
||
}
|
||
|
||
double diameterMm = FirstFeatureLengthDimensionMm(feature);
|
||
if (diameterMm <= 0.0)
|
||
{
|
||
note = "sweep definition/profile diameter not available";
|
||
return false;
|
||
}
|
||
var sketchNames = SketchFeatureNames(feature);
|
||
string pathSketchName = sketchNames.Count > 0 ? sketchNames[^1] : "";
|
||
skill = isCut ? "swept_cut_circular_profile_mm" : "swept_boss_circular_profile_mm";
|
||
args["diameter_mm"] = diameterMm;
|
||
if (!string.IsNullOrWhiteSpace(pathSketchName)) args["path_sketch_name"] = pathSketchName;
|
||
args["direction"] = 0;
|
||
if (!isCut) args["merge"] = true;
|
||
note = $"sweep circular diameter={diameterMm}mm, path_sketch={pathSketchName}";
|
||
return true;
|
||
}
|
||
|
||
static bool TryExtractLoftOrBoundary(Feature feature, string name, string type, out string skill, out Dictionary<string, object> args, out string note)
|
||
{
|
||
skill = "";
|
||
args = new Dictionary<string, object>();
|
||
note = "";
|
||
bool isBoundary = IsBoundaryFeature(name, type);
|
||
bool isLoft = IsLoftFeature(name, type);
|
||
if (!isBoundary && !isLoft) return false;
|
||
|
||
bool isCut = IsCutLike(name, type);
|
||
var sketchNames = SketchFeatureNames(feature).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
|
||
if (sketchNames.Count >= 2)
|
||
args["profile_sketch_names"] = sketchNames;
|
||
args["closed"] = false;
|
||
if (!isCut) args["merge"] = true;
|
||
|
||
if (isBoundary)
|
||
skill = isCut ? "boundary_cut_from_profiles" : "boundary_boss_from_profiles";
|
||
else
|
||
skill = isCut ? "loft_cut_from_profiles" : "loft_boss_from_profiles";
|
||
|
||
note = $"{(isBoundary ? "boundary" : "loft")} profiles={sketchNames.Count}";
|
||
return true;
|
||
}
|
||
|
||
static int FirstPositiveInt(object data, IEnumerable<string> names, int fallback)
|
||
{
|
||
foreach (string name in names)
|
||
{
|
||
double value = ReadDoubleMember(data, name);
|
||
if (Math.Abs(value) > 1e-9)
|
||
return RInt(value);
|
||
}
|
||
return fallback;
|
||
}
|
||
|
||
static double FirstPositiveLengthMm(object data, IEnumerable<string> names, double fallback)
|
||
{
|
||
foreach (string name in names)
|
||
{
|
||
double value = ReadDoubleMember(data, name);
|
||
if (Math.Abs(value) > 1e-9)
|
||
return R(ToLengthMm(value));
|
||
}
|
||
return fallback;
|
||
}
|
||
|
||
static double FirstPositiveAngleDeg(object data, IEnumerable<string> names, double fallback)
|
||
{
|
||
foreach (string name in names)
|
||
{
|
||
double value = ReadDoubleMember(data, name);
|
||
if (Math.Abs(value) > 1e-9)
|
||
return R(ToAngleDeg(value));
|
||
}
|
||
return fallback;
|
||
}
|
||
|
||
static bool FirstBool(object data, IEnumerable<string> names, bool fallback)
|
||
{
|
||
foreach (string name in names)
|
||
{
|
||
if (TryReadBoolMember(data, name, out bool value))
|
||
return value;
|
||
}
|
||
return fallback;
|
||
}
|
||
|
||
static double ToLengthMm(double value)
|
||
{
|
||
// SolidWorks feature data lengths are normally meters. Some dimension fallbacks
|
||
// already arrive in mm, so avoid multiplying obvious mm-scale values.
|
||
return Math.Abs(value) < 10.0 ? value * 1000.0 : value;
|
||
}
|
||
|
||
static double ToAngleDeg(double value)
|
||
{
|
||
return Math.Abs(value) <= Math.PI * 2.0 + 1e-6 ? value * 180.0 / Math.PI : value;
|
||
}
|
||
|
||
static bool TryReadEdgePointMm(object data, IEnumerable<string> names, out double[] point)
|
||
{
|
||
point = Array.Empty<double>();
|
||
foreach (string name in names)
|
||
{
|
||
object value = SafeObj(() => GetComProperty(data, name));
|
||
foreach (object item in FlattenComObjects(value))
|
||
{
|
||
if (TryAsEdge(item, out Edge edge))
|
||
{
|
||
point = EdgePointMm(edge);
|
||
if (point.Length >= 3)
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static double[] EdgePointMm(Edge edge)
|
||
{
|
||
var signature = EdgeSignature(edge);
|
||
if (signature.TryGetValue("center_mm", out object centerObj))
|
||
{
|
||
double[] center = ToDoubleArray(centerObj);
|
||
if (center.Length >= 3) return new[] { R(center[0]), R(center[1]), R(center[2]) };
|
||
}
|
||
if (signature.TryGetValue("start_mm", out object startObj))
|
||
{
|
||
double[] start = ToDoubleArray(startObj);
|
||
if (start.Length >= 3) return new[] { R(start[0]), R(start[1]), R(start[2]) };
|
||
}
|
||
return Array.Empty<double>();
|
||
}
|
||
|
||
static string FirstFeatureName(object data, IEnumerable<string> names)
|
||
{
|
||
foreach (string name in names)
|
||
{
|
||
object value = SafeObj(() => GetComProperty(data, name));
|
||
string featureName = FirstFeatureNameFromObject(value);
|
||
if (!string.IsNullOrWhiteSpace(featureName))
|
||
return featureName;
|
||
}
|
||
return "";
|
||
}
|
||
|
||
static List<string> FeatureNames(object data, IEnumerable<string> names)
|
||
{
|
||
var result = new List<string>();
|
||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
foreach (string name in names)
|
||
{
|
||
object value = SafeObj(() => GetComProperty(data, name));
|
||
foreach (string featureName in FeatureNamesFromObject(value))
|
||
{
|
||
if (seen.Add(featureName))
|
||
result.Add(featureName);
|
||
}
|
||
if (result.Count > 0)
|
||
break;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
static string FirstFeatureNameFromObject(object value)
|
||
{
|
||
foreach (string featureName in FeatureNamesFromObject(value))
|
||
return featureName;
|
||
return "";
|
||
}
|
||
|
||
static List<string> FeatureNamesFromObject(object value)
|
||
{
|
||
var result = new List<string>();
|
||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
foreach (object item in FlattenComObjects(value))
|
||
{
|
||
if (item is Feature feature)
|
||
{
|
||
string featureName = Safe(() => feature.Name);
|
||
if (!string.IsNullOrWhiteSpace(featureName) && seen.Add(featureName))
|
||
result.Add(featureName);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
static string FirstReferenceName(object data, IEnumerable<string> names)
|
||
{
|
||
foreach (string name in names)
|
||
{
|
||
object value = SafeObj(() => GetComProperty(data, name));
|
||
foreach (object item in FlattenComObjects(value))
|
||
{
|
||
string referenceName = ReferenceName(item);
|
||
if (!string.IsNullOrWhiteSpace(referenceName))
|
||
return referenceName;
|
||
}
|
||
}
|
||
return "";
|
||
}
|
||
|
||
static string ReferenceName(object item)
|
||
{
|
||
if (item == null) return "";
|
||
if (item is Feature feature) return Safe(() => feature.Name);
|
||
if (item is RefPlane)
|
||
{
|
||
object refPlaneFeature = InvokeCom(item, "GetFeature");
|
||
if (refPlaneFeature is Feature planeFeature) return Safe(() => planeFeature.Name);
|
||
}
|
||
try
|
||
{
|
||
object ownerFeature = InvokeCom(item, "GetFeature");
|
||
if (ownerFeature is Feature featureOwner) return Safe(() => featureOwner.Name);
|
||
}
|
||
catch { }
|
||
return "";
|
||
}
|
||
|
||
static string CanonicalReplayReferenceName(object item)
|
||
{
|
||
if (item is Feature feature)
|
||
{
|
||
string type = Safe(() => feature.GetTypeName2());
|
||
string name = Safe(() => feature.Name);
|
||
if (IsSketch(type))
|
||
return CanonicalSketchPlanName(feature, name);
|
||
return name;
|
||
}
|
||
|
||
try
|
||
{
|
||
object ownerFeature = InvokeCom(item, "GetFeature");
|
||
if (ownerFeature is Feature featureOwner)
|
||
{
|
||
string type = Safe(() => featureOwner.GetTypeName2());
|
||
string name = Safe(() => featureOwner.Name);
|
||
if (IsSketch(type))
|
||
return CanonicalSketchPlanName(featureOwner, name);
|
||
return name;
|
||
}
|
||
}
|
||
catch { }
|
||
|
||
return ReferenceName(item);
|
||
}
|
||
|
||
static string FirstCanonicalSubSketchName(Feature feature)
|
||
{
|
||
Feature sub = SafeObj(() => feature.GetFirstSubFeature()) as Feature;
|
||
for (int i = 0; sub != null && i < 200; i++)
|
||
{
|
||
string type = Safe(() => sub.GetTypeName2());
|
||
if (IsSketch(type))
|
||
return CanonicalSketchPlanName(sub, Safe(() => sub.Name));
|
||
sub = SafeObj(() => sub.GetNextSubFeature()) as Feature;
|
||
}
|
||
return "";
|
||
}
|
||
|
||
static int FirstFaceIndex(ModelDoc2 doc, object data, IEnumerable<string> names)
|
||
{
|
||
foreach (int index in FaceIndices(doc, data, names))
|
||
return index;
|
||
return 0;
|
||
}
|
||
|
||
static List<int> FaceIndices(ModelDoc2 doc, object data, IEnumerable<string> names)
|
||
{
|
||
var result = new List<int>();
|
||
foreach (string name in names)
|
||
{
|
||
object value = SafeObj(() => GetComProperty(data, name));
|
||
foreach (object item in FlattenComObjects(value))
|
||
{
|
||
if (item is Face2 face)
|
||
{
|
||
int index = FindFaceIndexLikeExecutor(doc, face);
|
||
if (index > 0) result.Add(index);
|
||
}
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
static Face2 FirstFace(object data, IEnumerable<string> names)
|
||
{
|
||
foreach (string name in names)
|
||
{
|
||
object value = SafeObj(() => GetComProperty(data, name));
|
||
foreach (object item in FlattenComObjects(value))
|
||
{
|
||
if (item is Face2 face) return face;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
static double[] FaceCenterMm(Face2 face)
|
||
{
|
||
double[] box = TryGetFaceBoxMm(face);
|
||
if (box.Length >= 6)
|
||
{
|
||
return new[]
|
||
{
|
||
R((box[0] + box[3]) / 2.0),
|
||
R((box[1] + box[4]) / 2.0),
|
||
R((box[2] + box[5]) / 2.0)
|
||
};
|
||
}
|
||
return Array.Empty<double>();
|
||
}
|
||
|
||
static List<double> FeatureDimensionValuesMm(Feature feature)
|
||
{
|
||
var values = new List<double>();
|
||
try
|
||
{
|
||
object dimObj = feature.GetFirstDisplayDimension();
|
||
while (dimObj != null)
|
||
{
|
||
object dimensionObj = InvokeCom(dimObj, "GetDimension");
|
||
if (dimensionObj is Dimension dimension)
|
||
{
|
||
double value = SafeDouble(() => dimension.SystemValue);
|
||
if (Math.Abs(value) > 1e-9)
|
||
values.Add(R(value * 1000.0));
|
||
}
|
||
dimObj = InvokeCom(dimObj, "GetNext");
|
||
}
|
||
}
|
||
catch { }
|
||
return values;
|
||
}
|
||
|
||
static double[] FirstSketchPointMm(Feature feature)
|
||
{
|
||
foreach (Sketch sketch in SketchesFromFeature(feature))
|
||
{
|
||
foreach (object pointObj in InvokeArray(sketch, "GetSketchPoints2").Concat(InvokeArray(sketch, "GetSketchPoints")))
|
||
{
|
||
if (pointObj is SketchPoint point && Pt(point, out double x, out double y))
|
||
return new[] { R(x), R(y) };
|
||
}
|
||
}
|
||
return Array.Empty<double>();
|
||
}
|
||
|
||
static List<string> SketchFeatureNames(Feature feature)
|
||
{
|
||
var names = new List<string>();
|
||
Feature sub = SafeObj(() => feature.GetFirstSubFeature()) as Feature;
|
||
int guard = 0;
|
||
while (sub != null && guard++ < 200)
|
||
{
|
||
string type = Safe(() => sub.GetTypeName2());
|
||
if (IsSketch(type))
|
||
{
|
||
string sketchName = Safe(() => sub.Name);
|
||
if (!string.IsNullOrWhiteSpace(sketchName))
|
||
names.Add(sketchName);
|
||
}
|
||
sub = SafeObj(() => sub.GetNextSubFeature()) as Feature;
|
||
}
|
||
return names;
|
||
}
|
||
|
||
static IEnumerable<Sketch> SketchesFromFeature(Feature feature)
|
||
{
|
||
Feature sub = SafeObj(() => feature.GetFirstSubFeature()) as Feature;
|
||
int guard = 0;
|
||
while (sub != null && guard++ < 200)
|
||
{
|
||
string type = Safe(() => sub.GetTypeName2());
|
||
if (IsSketch(type))
|
||
{
|
||
object sketchObj = SafeObj(() => sub.GetSpecificFeature2());
|
||
if (sketchObj is Sketch sketch)
|
||
yield return sketch;
|
||
}
|
||
sub = SafeObj(() => sub.GetNextSubFeature()) as Feature;
|
||
}
|
||
}
|
||
|
||
static bool ExtrudeReverseDir(Feature f, out string source)
|
||
{
|
||
source = "default_false";
|
||
try
|
||
{
|
||
object data = SafeObj(() => f.GetDefinition());
|
||
if (data == null) return false;
|
||
|
||
foreach (string name in new[]
|
||
{
|
||
"ReverseDirection",
|
||
"ReverseDirection1",
|
||
"Direction",
|
||
"Direction1",
|
||
"Dir",
|
||
"Ddir1"
|
||
})
|
||
{
|
||
if (TryReadBoolMember(data, name, out bool value))
|
||
{
|
||
source = name;
|
||
return value;
|
||
}
|
||
}
|
||
|
||
foreach (string name in new[]
|
||
{
|
||
"GetReverseDirection",
|
||
"GetDirection"
|
||
})
|
||
{
|
||
object value = InvokeCom(data, name);
|
||
if (TryConvertBool(value, out bool parsed))
|
||
{
|
||
source = name;
|
||
return parsed;
|
||
}
|
||
}
|
||
}
|
||
catch { }
|
||
|
||
return false;
|
||
}
|
||
|
||
static bool TryReadBoolMember(object target, string name, out bool value)
|
||
{
|
||
value = false;
|
||
object raw = GetComProperty(target, name);
|
||
if (TryConvertBool(raw, out value)) return true;
|
||
|
||
raw = InvokeCom(target, name);
|
||
return TryConvertBool(raw, out value);
|
||
}
|
||
|
||
static bool TryConvertBool(object raw, out bool value)
|
||
{
|
||
value = false;
|
||
if (raw == null) return false;
|
||
if (raw is bool b) { value = b; return true; }
|
||
if (raw is int i) { value = i != 0; return true; }
|
||
if (raw is short s) { value = s != 0; return true; }
|
||
if (raw is double d) { value = Math.Abs(d) > 1e-9; return true; }
|
||
if (bool.TryParse(raw.ToString(), out bool parsedBool)) { value = parsedBool; return true; }
|
||
if (double.TryParse(raw.ToString(), out double parsedDouble)) { value = Math.Abs(parsedDouble) > 1e-9; return true; }
|
||
return false;
|
||
}
|
||
|
||
static void Save(string dir)
|
||
{
|
||
Directory.CreateDirectory(dir);
|
||
string fileBase = BuildOutputFileBaseName();
|
||
string xlsx = Path.Combine(dir, $"{fileBase}_建模过程.xlsx");
|
||
string json = Path.Combine(dir, $"{fileBase}_skill_flow.json");
|
||
using var wb = new XLWorkbook(); var ws = wb.Worksheets.Add("建模过程");
|
||
string[] h = { "零件", "步骤", "层级", "特征", "类型", "分类", "草图图元", "草图基准", "尺寸", "约束", "备注" };
|
||
for (int i = 0; i < h.Length; i++) ws.Cell(1, i + 1).Value = h[i];
|
||
int r = 2; foreach (var a in Rows) { ws.Cell(r, 1).Value = a.PartName; ws.Cell(r, 2).Value = a.Step; ws.Cell(r, 3).Value = a.Depth; ws.Cell(r, 4).Value = a.Feature; ws.Cell(r, 5).Value = a.Type; ws.Cell(r, 6).Value = a.Category; ws.Cell(r, 7).Value = a.Sketch; ws.Cell(r, 8).Value = a.SketchPlane; ws.Cell(r, 9).Value = a.Dims; ws.Cell(r, 10).Value = a.Rels; ws.Cell(r, 11).Value = a.Note; r++; }
|
||
ws.Columns().AdjustToContents(8, 80); wb.SaveAs(xlsx);
|
||
EnrichExtrudeDirectionHints();
|
||
NormalizeAutoNumberedFeatureNames();
|
||
var jsonOptions = new JsonSerializerOptions { WriteIndented = true };
|
||
var validation = ValidateSkillFlow();
|
||
string swagentJson = Path.Combine(dir, $"{fileBase}_modeling_plan.json");
|
||
string validationJson = Path.Combine(dir, $"{fileBase}_skill_flow_validation.json");
|
||
File.WriteAllText(json, JsonSerializer.Serialize(Skills, jsonOptions));
|
||
File.WriteAllText(swagentJson, JsonSerializer.Serialize(BuildSwagentModelingPlan(fileBase), jsonOptions));
|
||
File.WriteAllText(validationJson, JsonSerializer.Serialize(validation, jsonOptions));
|
||
Console.WriteLine("SWagent ModelingPlan JSON: " + swagentJson);
|
||
Console.WriteLine("Skill Flow validation JSON: " + validationJson);
|
||
Console.WriteLine(validation.Ok
|
||
? "Skill Flow validation passed."
|
||
: $"Skill Flow validation failed with {validation.Errors.Count} error(s).");
|
||
Console.WriteLine("完成!Excel 已导出:" + xlsx); Console.WriteLine("完成!Skill Flow JSON 已导出:" + json);
|
||
}
|
||
|
||
static string BuildOutputFileBaseName()
|
||
{
|
||
var partNames = Rows.Select(r => r.PartName)
|
||
.Where(p => !string.IsNullOrWhiteSpace(p))
|
||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||
.ToList();
|
||
|
||
string name = partNames.Count == 1 ? partNames[0] : "multi_part";
|
||
name = Path.GetFileNameWithoutExtension(name);
|
||
if (string.IsNullOrWhiteSpace(name))
|
||
name = "part";
|
||
|
||
foreach (char c in Path.GetInvalidFileNameChars())
|
||
name = name.Replace(c, '_');
|
||
|
||
return name.Trim();
|
||
}
|
||
|
||
static void AddSketchStartSkill(SketchReferenceResult reference, string source)
|
||
{
|
||
string description = reference?.Description ?? "";
|
||
if (TryGetDefaultPlaneSketchSkill(description, out string defaultPlaneSkill))
|
||
{
|
||
AddSkill(defaultPlaneSkill, new(), source, $"草图基准={description}");
|
||
return;
|
||
}
|
||
|
||
if (reference == null || reference.Kind == "unresolved")
|
||
{
|
||
AddSkill("list_faces", new() { ["unresolved_sketch_reference"] = description }, source, "草图基准未识别,列出面供人工校验");
|
||
return;
|
||
}
|
||
|
||
if (reference.Kind == "face" && reference.FaceIndex > 0)
|
||
{
|
||
if (CanUseFaceSignature(reference))
|
||
{
|
||
var args = reference.ToSkillArgs();
|
||
args["center_x_mm"] = R(reference.CenterMm[0]);
|
||
args["center_y_mm"] = R(reference.CenterMm[1]);
|
||
args["center_z_mm"] = R(reference.CenterMm[2]);
|
||
args["fallback_face_index"] = reference.FaceIndex;
|
||
AddSkill("create_face_sketch_by_signature", args, source, $"草图基准={description}");
|
||
return;
|
||
}
|
||
|
||
AddSkill("create_face_sketch_by_index", reference.ToSkillArgs(), source, $"草图基准={description}");
|
||
return;
|
||
}
|
||
|
||
if (reference.Kind == "datum_plane")
|
||
{
|
||
AddSkill("create_ref_plane_sketch_by_name", reference.ToSkillArgs(), source, $"草图基准={description}");
|
||
return;
|
||
}
|
||
|
||
if (reference.Kind != "face")
|
||
{
|
||
AddSkill("list_faces", reference.ToSkillArgs(), source, $"草图基准尚无可执行选择 skill={description}");
|
||
return;
|
||
}
|
||
|
||
if (reference.Kind == "face")
|
||
{
|
||
AddSkill("list_faces", reference.ToSkillArgs(), source, $"实体面未获得 SWagent face_index={description}");
|
||
return;
|
||
}
|
||
|
||
if (reference == null || reference.Kind == "unresolved")
|
||
{
|
||
AddSkill("create_sketch_requires_manual_reference", new() { ["reason"] = description }, source, "草图基准未识别,未生成自动选面目标");
|
||
return;
|
||
}
|
||
|
||
AddSkill("create_sketch_on_reference", reference.ToSkillArgs(), source, $"草图基准={description}");
|
||
}
|
||
|
||
static bool CanUseFaceSignature(SketchReferenceResult reference)
|
||
{
|
||
return reference != null
|
||
&& string.Equals(reference.Kind, "face", StringComparison.OrdinalIgnoreCase)
|
||
&& string.Equals(reference.SurfaceType, "plane", StringComparison.OrdinalIgnoreCase)
|
||
&& reference.CenterMm != null
|
||
&& reference.CenterMm.Length >= 3
|
||
&& reference.AreaMm2 > 0;
|
||
}
|
||
|
||
static bool TryInferPlanarFacePosition(SketchReferenceResult reference, out string position)
|
||
{
|
||
position = "";
|
||
if (reference == null) return false;
|
||
if (!string.Equals(reference.Kind, "face", StringComparison.OrdinalIgnoreCase)) return false;
|
||
if (!string.Equals(reference.SurfaceType, "plane", StringComparison.OrdinalIgnoreCase)) return false;
|
||
|
||
double[] center = reference.CenterMm ?? Array.Empty<double>();
|
||
double[] box = reference.BoxMm ?? Array.Empty<double>();
|
||
if (center.Length < 3) return false;
|
||
|
||
int axis = -1;
|
||
if (box.Length >= 6)
|
||
{
|
||
double dx = Math.Abs(box[3] - box[0]);
|
||
double dy = Math.Abs(box[4] - box[1]);
|
||
double dz = Math.Abs(box[5] - box[2]);
|
||
double min = Math.Min(dx, Math.Min(dy, dz));
|
||
if (min <= 1e-6)
|
||
{
|
||
if (Math.Abs(dx - min) <= 1e-6) axis = 0;
|
||
else if (Math.Abs(dy - min) <= 1e-6) axis = 1;
|
||
else axis = 2;
|
||
}
|
||
}
|
||
|
||
if (axis < 0 && reference.Normal.Length >= 3)
|
||
{
|
||
double ax = Math.Abs(reference.Normal[0]);
|
||
double ay = Math.Abs(reference.Normal[1]);
|
||
double az = Math.Abs(reference.Normal[2]);
|
||
if (ax >= ay && ax >= az) axis = 0;
|
||
else if (ay >= ax && ay >= az) axis = 1;
|
||
else axis = 2;
|
||
}
|
||
|
||
if (axis < 0) return false;
|
||
|
||
double value = center[axis];
|
||
position = axis switch
|
||
{
|
||
0 => value >= 0 ? "right" : "left",
|
||
1 => value >= 0 ? "front" : "back",
|
||
_ => value >= 0 ? "top" : "bottom"
|
||
};
|
||
return true;
|
||
}
|
||
|
||
static void EnsureRefPlaneCreationSkills(ModelDoc2 doc, SketchReferenceResult reference, string source)
|
||
{
|
||
if (doc == null || reference == null) return;
|
||
if (!string.Equals(reference.Kind, "datum_plane", StringComparison.OrdinalIgnoreCase)) return;
|
||
|
||
string planeName = (reference.ReferenceName ?? "").Trim();
|
||
if (string.IsNullOrWhiteSpace(planeName) || IsDefaultPlaneName(planeName)) return;
|
||
if (EmittedRefPlaneCreation.Contains(planeName)) return;
|
||
|
||
var planeFeature = FindFeatureByName(doc, planeName);
|
||
if (planeFeature == null || !string.Equals(Safe(() => planeFeature.GetTypeName2()), "RefPlane", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
EmittedRefPlaneCreation.Add(planeName);
|
||
AddSkill("list_ref_planes", new() { ["reference_name"] = planeName, ["extract_note"] = "按名称未找到对应 RefPlane 特征,无法自动提取创建参数" }, source, "草图引用了自建基准面,但未定位到对应基准面特征");
|
||
return;
|
||
}
|
||
|
||
AddRefPlaneCreationSkills(doc, planeFeature, planeName);
|
||
}
|
||
|
||
static bool TryGetDefaultPlaneSketchSkill(string description, out string skill)
|
||
{
|
||
skill = "";
|
||
string text = (description ?? "").ToLowerInvariant();
|
||
if (text.Contains("前视")) { skill = "create_front_plane_sketch"; return true; }
|
||
if (text.Contains("上视")) { skill = "create_top_plane_sketch"; return true; }
|
||
if (text.Contains("右视")) { skill = "create_right_plane_sketch"; return true; }
|
||
if (text.Contains("前视") || text.Contains("front")) { skill = "create_front_plane_sketch"; return true; }
|
||
if (text.Contains("上视") || text.Contains("top")) { skill = "create_top_plane_sketch"; return true; }
|
||
if (text.Contains("右视") || text.Contains("right")) { skill = "create_right_plane_sketch"; return true; }
|
||
return false;
|
||
}
|
||
|
||
static bool IsCustomRefPlane(string name, string type)
|
||
{
|
||
if (!string.Equals(type, "RefPlane", StringComparison.OrdinalIgnoreCase)) return false;
|
||
return !IsDefaultPlaneName(name);
|
||
}
|
||
|
||
static bool IsDefaultPlaneName(string name)
|
||
{
|
||
string n = (name ?? "").Trim();
|
||
return string.Equals(n, "前视基准面", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(n, "上视基准面", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(n, "右视基准面", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(n, "Front Plane", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(n, "Top Plane", StringComparison.OrdinalIgnoreCase)
|
||
|| string.Equals(n, "Right Plane", StringComparison.OrdinalIgnoreCase);
|
||
}
|
||
|
||
static void AddRefPlaneCreationSkills(ModelDoc2 doc, Feature feature, string planeName)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(planeName)) return;
|
||
if (!EmittedRefPlaneCreation.Add(planeName)) return;
|
||
|
||
var info = TryReadRefPlane(doc, feature, planeName);
|
||
if (!info.CanCreate)
|
||
{
|
||
AddSkill("list_ref_planes", new() { ["reference_name"] = planeName, ["extract_note"] = info.Note }, planeName, "自建基准面创建参数未能自动提取,列出基准面供校验");
|
||
return;
|
||
}
|
||
|
||
if (string.Equals(info.BuildKind, "angle_edge_face", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
AddSkill("create_angle_plane_by_edge_and_face", new()
|
||
{
|
||
["edge_signatures"] = info.EdgeSignatures,
|
||
["face_index"] = info.BaseFaceIndex,
|
||
["face_signature"] = info.FaceSignature,
|
||
["angle_deg"] = info.AngleDeg,
|
||
["plane_name"] = planeName
|
||
}, planeName, "创建角度自建基准面");
|
||
return;
|
||
}
|
||
|
||
if (string.Equals(info.BuildKind, "angle_axis_face", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
AddSkill("create_angle_plane_by_axis_and_face", new()
|
||
{
|
||
["axis_name"] = info.AxisName,
|
||
["face_index"] = info.BaseFaceIndex,
|
||
["face_signature"] = info.FaceSignature,
|
||
["angle_deg"] = info.AngleDeg,
|
||
["plane_name"] = planeName
|
||
}, planeName, "创建基准轴+面角度基准面");
|
||
return;
|
||
}
|
||
|
||
if (info.BaseReferenceKind == "default_plane" && TryGetDefaultPlaneSelector(info.BaseReferenceName, out string plane))
|
||
{
|
||
AddSkill("select_default_plane", new() { ["plane"] = plane }, planeName, $"创建自建基准面前选择基准={info.BaseReferenceName}");
|
||
}
|
||
else if (info.BaseReferenceKind == "face" && info.BaseFaceIndex > 0)
|
||
{
|
||
AddSkill("select_face_by_index", new() { ["face_index"] = info.BaseFaceIndex }, planeName, $"创建自建基准面前选择实体面={info.BaseReferenceName}");
|
||
}
|
||
else if (info.BaseReferenceKind == "datum_plane" && !string.IsNullOrWhiteSpace(info.BaseReferenceName))
|
||
{
|
||
AddSkill("select_reference_plane_by_name", new() { ["plane_name"] = info.BaseReferenceName }, planeName, $"创建自建基准面前选择自建基准面={info.BaseReferenceName}");
|
||
}
|
||
else
|
||
{
|
||
AddSkill("list_faces", new() { ["reference_name"] = planeName, ["base_reference"] = info.BaseReferenceName, ["extract_note"] = info.Note }, planeName, "自建基准面基准引用未能映射为 SWagent 选择 skill");
|
||
return;
|
||
}
|
||
|
||
AddSkill("create_offset_plane_mm", new()
|
||
{
|
||
["offset_mm"] = info.OffsetMm,
|
||
["offset_abs_mm"] = info.OffsetAbsMm,
|
||
["reverse_direction"] = info.ReverseDirection,
|
||
["reversed_reference_direction"] = info.ReversedReferenceDirection,
|
||
["plane_name"] = planeName
|
||
}, planeName, "创建偏移自建基准面");
|
||
}
|
||
|
||
static bool TryGetDefaultPlaneSelector(string description, out string plane)
|
||
{
|
||
plane = "";
|
||
string text = (description ?? "").ToLowerInvariant();
|
||
if (text.Contains("前视") || text.Contains("front")) { plane = "front"; return true; }
|
||
if (text.Contains("上视") || text.Contains("top")) { plane = "top"; return true; }
|
||
if (text.Contains("右视") || text.Contains("right")) { plane = "right"; return true; }
|
||
return false;
|
||
}
|
||
|
||
static RefPlaneBuildInfo TryReadRefPlane(ModelDoc2 doc, Feature feature, string planeName)
|
||
{
|
||
var info = new RefPlaneBuildInfo { PlaneName = planeName };
|
||
try { doc?.EditRebuild3(); } catch { }
|
||
object data = SafeObj(() => feature.GetDefinition());
|
||
if (data == null)
|
||
{
|
||
info.Note = "RefPlane.GetDefinition 返回空";
|
||
return info;
|
||
}
|
||
|
||
try { doc?.ClearSelection2(true); } catch { }
|
||
try { feature?.Select2(false, 0); } catch { }
|
||
AccessRefPlaneSelections(data, doc);
|
||
try
|
||
{
|
||
var angleInfo = TryReadAngleRefPlane(doc, data, planeName);
|
||
if (angleInfo.CanCreate)
|
||
return angleInfo;
|
||
string angleNote = angleInfo.Note;
|
||
|
||
int refPlaneType = ReadRefPlaneType(data);
|
||
double offsetMeters = 0.0;
|
||
object baseRef = null;
|
||
bool hasDistanceConstraint = refPlaneType == (int)swRefPlaneType_e.swRefPlaneDistance;
|
||
int distanceReferenceIndex = -1;
|
||
bool reverseDirection = false;
|
||
bool reversedReferenceDirection = false;
|
||
if (data is IRefPlaneFeatureData typedRefPlane)
|
||
{
|
||
reverseDirection = SafeBool(() => typedRefPlane.ReverseDirection);
|
||
}
|
||
else
|
||
{
|
||
reverseDirection = FirstBool(data, new[] { "ReverseDirection" }, false);
|
||
reversedReferenceDirection = FirstBool(data, new[] { "ReversedReferenceDirection" }, false);
|
||
}
|
||
|
||
for (int i = 0; i < 3; i++)
|
||
{
|
||
object referenceCandidate = FirstComReference(ReadRefPlaneReference(data, i));
|
||
int constraint = ReadRefPlaneConstraint(data, i);
|
||
double value = ReadRefPlaneAngleOrDistance(data, i);
|
||
|
||
bool isDistanceReference =
|
||
(constraint & (int)swRefPlaneReferenceConstraints_e.swRefPlaneReferenceConstraint_Distance) != 0 ||
|
||
refPlaneType == (int)swRefPlaneType_e.swRefPlaneDistance;
|
||
|
||
if (referenceCandidate != null && isDistanceReference)
|
||
{
|
||
baseRef = referenceCandidate;
|
||
distanceReferenceIndex = i;
|
||
hasDistanceConstraint = true;
|
||
if (Math.Abs(value) > 1e-9)
|
||
offsetMeters = value;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (data is IRefPlaneFeatureData typedRefPlaneWithIndex && distanceReferenceIndex >= 0)
|
||
reversedReferenceDirection = SafeBool(() => typedRefPlaneWithIndex.ReversedReferenceDirection[distanceReferenceIndex]);
|
||
|
||
if (hasDistanceConstraint && Math.Abs(offsetMeters) <= 1e-9)
|
||
{
|
||
offsetMeters = FirstMeaningfulDouble(
|
||
GetComProperty(data, "Distance"),
|
||
InvokeCom(data, "get_Distance"));
|
||
}
|
||
|
||
var reference = new SketchReferenceResult();
|
||
if (baseRef != null && TryDescribeReferenceEntity(doc, baseRef, reference))
|
||
{
|
||
string referencePlaneName = string.IsNullOrWhiteSpace(reference.ReferenceName) ? reference.Description : reference.ReferenceName;
|
||
if (reference.Kind == "datum_plane" && TryGetDefaultPlaneSelector(referencePlaneName, out _))
|
||
{
|
||
info.BaseReferenceKind = "default_plane";
|
||
info.BaseReferenceName = referencePlaneName;
|
||
}
|
||
else if (reference.Kind == "datum_plane")
|
||
{
|
||
info.BaseReferenceKind = "datum_plane";
|
||
info.BaseReferenceName = referencePlaneName;
|
||
}
|
||
else if (reference.Kind == "face" && reference.FaceIndex > 0)
|
||
{
|
||
info.BaseReferenceKind = "face";
|
||
info.BaseReferenceName = reference.Description;
|
||
info.BaseFaceIndex = reference.FaceIndex;
|
||
}
|
||
}
|
||
|
||
double offsetAbsMm = R(Math.Abs(offsetMeters) * 1000.0);
|
||
bool negativeOffset = reverseDirection || reversedReferenceDirection || offsetMeters < -1e-9;
|
||
info.OffsetAbsMm = offsetAbsMm;
|
||
info.ReverseDirection = reverseDirection;
|
||
info.ReversedReferenceDirection = reversedReferenceDirection;
|
||
info.OffsetMm = negativeOffset ? -offsetAbsMm : offsetAbsMm;
|
||
info.CanCreate = hasDistanceConstraint && !string.IsNullOrWhiteSpace(info.BaseReferenceKind);
|
||
if (info.CanCreate) info.BuildKind = "offset";
|
||
if (!info.CanCreate)
|
||
info.Note = $"angle plane not recognized: {angleNote}; offset plane not recognized: base={reference.Kind}/{reference.Description}, offset_mm={info.OffsetMm}";
|
||
info.Note = info.CanCreate
|
||
? "已识别单引用偏移基准面"
|
||
: $"angle plane not recognized: {angleNote}; offset plane not recognized: base={reference.Kind}/{reference.Description}, offset_mm={info.OffsetMm}";
|
||
return info;
|
||
}
|
||
finally
|
||
{
|
||
ReleaseRefPlaneSelections(data);
|
||
}
|
||
}
|
||
|
||
static bool AccessRefPlaneSelections(object data, ModelDoc2 doc)
|
||
{
|
||
if (data is IRefPlaneFeatureData typed)
|
||
return SafeBool(() => typed.AccessSelections(doc, null));
|
||
return TryInvokeCom(data, "AccessSelections", doc, null);
|
||
}
|
||
|
||
static void ReleaseRefPlaneSelections(object data)
|
||
{
|
||
if (data is IRefPlaneFeatureData typed)
|
||
{
|
||
try { typed.ReleaseSelectionAccess(); } catch { }
|
||
return;
|
||
}
|
||
TryInvokeCom(data, "ReleaseSelectionAccess");
|
||
}
|
||
|
||
static object FirstComReference(params object[] candidates)
|
||
{
|
||
foreach (object candidate in candidates)
|
||
{
|
||
foreach (object item in FlattenObjects(candidate))
|
||
{
|
||
if (item == null) continue;
|
||
if (TryAsEdge(item, out Edge edge)) return edge;
|
||
if (TryAsFace(item, out Face2 face)) return face;
|
||
if (item is Feature || item is RefPlane) return item;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
static int ReadRefPlaneType(object data)
|
||
{
|
||
if (data is IRefPlaneFeatureData typed)
|
||
return SafeInt(() => typed.Type, 0);
|
||
return FirstMeaningfulInt(GetComProperty(data, "Type"), InvokeCom(data, "get_Type"));
|
||
}
|
||
|
||
static object ReadRefPlaneReference(object data, int index)
|
||
{
|
||
if (data is IRefPlaneFeatureData typed)
|
||
return SafeObj(() => typed.Reference[index]);
|
||
return FirstComReference(GetComProperty(data, "Reference", index), InvokeCom(data, "get_Reference", index));
|
||
}
|
||
|
||
static int ReadRefPlaneConstraint(object data, int index)
|
||
{
|
||
if (data is IRefPlaneFeatureData typed)
|
||
return SafeInt(() => typed.Constraint[index], 0);
|
||
return FirstMeaningfulInt(GetComProperty(data, "Constraint", index), InvokeCom(data, "get_Constraint", index));
|
||
}
|
||
|
||
static double ReadRefPlaneAngleOrDistance(object data, int index)
|
||
{
|
||
if (data is IRefPlaneFeatureData typed)
|
||
return SafeDouble(() => typed.AngleOrDistance[index], 0.0);
|
||
return FirstMeaningfulDouble(GetComProperty(data, "AngleOrDistance", index), InvokeCom(data, "get_AngleOrDistance", index));
|
||
}
|
||
|
||
static IEnumerable<object> ReadRefPlaneSelectionObjects(object data)
|
||
{
|
||
if (data is IRefPlaneFeatureData typed)
|
||
{
|
||
foreach (object item in FlattenComObjects(SafeObj(() => typed.Selections)))
|
||
yield return item;
|
||
|
||
yield break;
|
||
}
|
||
|
||
foreach (object item in FlattenComObjects(GetComProperty(data, "Selections")))
|
||
yield return item;
|
||
}
|
||
|
||
static RefPlaneBuildInfo TryReadAngleRefPlane(ModelDoc2 doc, object data, string planeName)
|
||
{
|
||
var info = new RefPlaneBuildInfo { PlaneName = planeName };
|
||
var edgeSignatures = new List<Dictionary<string, object>>();
|
||
var faceReference = new SketchReferenceResult();
|
||
string axisName = "";
|
||
double angleRad = 0.0;
|
||
var debug = new List<string>();
|
||
|
||
for (int i = 0; i < 3; i++)
|
||
{
|
||
object rawReference = ReadRefPlaneReference(data, i);
|
||
object reference = FirstComReference(rawReference);
|
||
int constraint = ReadRefPlaneConstraint(data, i);
|
||
double value = ReadRefPlaneAngleOrDistance(data, i);
|
||
|
||
if (edgeSignatures.Count == 0)
|
||
{
|
||
var candidateEdgeSignatures = EdgeSignatures(new object[] { rawReference });
|
||
debug.Add($"raw[{i}]={rawReference?.GetType().Name ?? "null"}, asEdge={TryAsEdge(rawReference, out _)}, edgeSigCount={candidateEdgeSignatures.Count}");
|
||
if (candidateEdgeSignatures.Count > 0)
|
||
edgeSignatures = candidateEdgeSignatures;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(axisName))
|
||
{
|
||
foreach (object axisCandidate in FlattenComObjects(new object[] { rawReference, reference }))
|
||
{
|
||
var candidateAxisReference = new SketchReferenceResult();
|
||
if (axisCandidate != null &&
|
||
TryDescribeReferenceEntity(doc, axisCandidate, candidateAxisReference) &&
|
||
string.Equals(candidateAxisReference.Kind, "datum_axis", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
axisName = candidateAxisReference.ReferenceName;
|
||
break;
|
||
}
|
||
}
|
||
if (!string.IsNullOrWhiteSpace(axisName))
|
||
debug.Add($"axisProbe[{i}]={axisName}");
|
||
}
|
||
|
||
if (faceReference.FaceIndex <= 0)
|
||
{
|
||
var candidateFaceReference = new SketchReferenceResult();
|
||
foreach (object faceCandidate in FlattenComObjects(new object[] { rawReference, reference }))
|
||
{
|
||
if (faceCandidate != null &&
|
||
TryDescribeReferenceEntity(doc, faceCandidate, candidateFaceReference) &&
|
||
candidateFaceReference.FaceIndex > 0)
|
||
{
|
||
faceReference = candidateFaceReference;
|
||
break;
|
||
}
|
||
}
|
||
debug.Add($"faceProbe[{i}]={candidateFaceReference.Kind}/{candidateFaceReference.FaceIndex}");
|
||
}
|
||
|
||
if (Math.Abs(value) > 1e-9 &&
|
||
((constraint & (int)swRefPlaneReferenceConstraints_e.swRefPlaneReferenceConstraint_Angle) != 0 || angleRad == 0.0))
|
||
angleRad = value;
|
||
|
||
debug.Add($"i={i}, constraint={constraint}, value={value}, ref={reference?.GetType().Name ?? "null"}");
|
||
}
|
||
|
||
if (edgeSignatures.Count == 0 || faceReference.FaceIndex <= 0)
|
||
{
|
||
foreach (object selectedReference in ReadRefPlaneSelectionObjects(data))
|
||
{
|
||
if (edgeSignatures.Count == 0)
|
||
{
|
||
var candidateEdgeSignatures = EdgeSignatures(new object[] { selectedReference });
|
||
debug.Add($"selection={selectedReference?.GetType().Name ?? "null"}, asEdge={TryAsEdge(selectedReference, out _)}, edgeSigCount={candidateEdgeSignatures.Count}");
|
||
if (candidateEdgeSignatures.Count > 0)
|
||
edgeSignatures = candidateEdgeSignatures;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(axisName))
|
||
{
|
||
foreach (object axisCandidate in FlattenComObjects(selectedReference))
|
||
{
|
||
var candidateAxisReference = new SketchReferenceResult();
|
||
if (axisCandidate != null &&
|
||
TryDescribeReferenceEntity(doc, axisCandidate, candidateAxisReference) &&
|
||
string.Equals(candidateAxisReference.Kind, "datum_axis", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
axisName = candidateAxisReference.ReferenceName;
|
||
break;
|
||
}
|
||
}
|
||
if (!string.IsNullOrWhiteSpace(axisName))
|
||
debug.Add($"selectionAxis={axisName}");
|
||
}
|
||
|
||
if (faceReference.FaceIndex <= 0)
|
||
{
|
||
var candidateFaceReference = new SketchReferenceResult();
|
||
foreach (object faceCandidate in FlattenComObjects(selectedReference))
|
||
{
|
||
if (faceCandidate != null &&
|
||
TryDescribeReferenceEntity(doc, faceCandidate, candidateFaceReference) &&
|
||
candidateFaceReference.FaceIndex > 0)
|
||
{
|
||
faceReference = candidateFaceReference;
|
||
break;
|
||
}
|
||
}
|
||
debug.Add($"selectionFace={candidateFaceReference.Kind}/{candidateFaceReference.FaceIndex}");
|
||
}
|
||
}
|
||
}
|
||
|
||
if (Math.Abs(angleRad) <= 1e-9)
|
||
{
|
||
angleRad = FirstMeaningfulDouble(
|
||
GetComProperty(data, "Angle"),
|
||
InvokeCom(data, "GetAngle"),
|
||
GetComProperty(data, "AngleOrDistance"));
|
||
}
|
||
|
||
bool hasAxisReference = edgeSignatures.Count > 0 || !string.IsNullOrWhiteSpace(axisName);
|
||
if (!hasAxisReference || faceReference.FaceIndex <= 0)
|
||
{
|
||
info.Note = "未识别为角度基准面: " + string.Join("; ", debug);
|
||
return info;
|
||
}
|
||
|
||
double angleDeg = R(angleRad * 180.0 / Math.PI);
|
||
while (angleDeg < 0) angleDeg += 360.0;
|
||
while (angleDeg >= 360.0) angleDeg -= 360.0;
|
||
|
||
info.CanCreate = true;
|
||
info.BuildKind = !string.IsNullOrWhiteSpace(axisName) ? "angle_axis_face" : "angle_edge_face";
|
||
info.BaseReferenceKind = "face";
|
||
info.BaseReferenceName = faceReference.Description;
|
||
info.BaseFaceIndex = faceReference.FaceIndex;
|
||
info.AngleDeg = R(angleDeg);
|
||
info.EdgeSignatures = edgeSignatures;
|
||
info.AxisName = axisName;
|
||
info.FaceSignature = BuildFaceSignatureArgs(faceReference);
|
||
info.Note = $"已识别边线+面角度基准面: face_index={info.BaseFaceIndex}, angle_deg={info.AngleDeg}";
|
||
return info;
|
||
}
|
||
|
||
static Dictionary<string, object> BuildFaceSignatureArgs(SketchReferenceResult reference)
|
||
{
|
||
var result = new Dictionary<string, object>();
|
||
if (reference == null) return result;
|
||
if (!string.IsNullOrWhiteSpace(reference.SurfaceType)) result["surface_type"] = reference.SurfaceType;
|
||
if (reference.AreaMm2 > 0) result["area_mm2"] = reference.AreaMm2;
|
||
if (reference.CenterMm != null && reference.CenterMm.Length >= 3)
|
||
{
|
||
result["center_mm"] = reference.CenterMm;
|
||
result["center_x_mm"] = R(reference.CenterMm[0]);
|
||
result["center_y_mm"] = R(reference.CenterMm[1]);
|
||
result["center_z_mm"] = R(reference.CenterMm[2]);
|
||
}
|
||
if (reference.BoxMm != null && reference.BoxMm.Length >= 6)
|
||
result["box_mm"] = reference.BoxMm;
|
||
if (reference.Normal != null && reference.Normal.Length >= 3)
|
||
{
|
||
result["normal"] = reference.Normal;
|
||
result["normal_x"] = reference.Normal[0];
|
||
result["normal_y"] = reference.Normal[1];
|
||
result["normal_z"] = reference.Normal[2];
|
||
}
|
||
return result;
|
||
}
|
||
|
||
static IEnumerable<object> FlattenObjects(object value)
|
||
{
|
||
if (value == null) yield break;
|
||
if (value is string) yield break;
|
||
if (value is Array array)
|
||
{
|
||
foreach (object item in array)
|
||
{
|
||
foreach (object nested in FlattenObjects(item)) yield return nested;
|
||
}
|
||
yield break;
|
||
}
|
||
yield return value;
|
||
}
|
||
|
||
static double FirstMeaningfulDouble(params object[] candidates)
|
||
{
|
||
foreach (object candidate in candidates)
|
||
{
|
||
foreach (double value in FlattenDoubles(candidate))
|
||
{
|
||
if (Math.Abs(value) > 1e-9) return value;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int FirstMeaningfulInt(params object[] candidates)
|
||
{
|
||
foreach (object candidate in candidates)
|
||
{
|
||
foreach (double value in FlattenDoubles(candidate))
|
||
{
|
||
int result = Convert.ToInt32(value);
|
||
if (result != 0) return result;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static IEnumerable<double> FlattenDoubles(object value)
|
||
{
|
||
if (value == null) yield break;
|
||
if (value is double d) { yield return d; yield break; }
|
||
if (value is float f) { yield return f; yield break; }
|
||
if (value is int i) { yield return i; yield break; }
|
||
if (value is Array array)
|
||
{
|
||
foreach (object item in array)
|
||
{
|
||
foreach (double nested in FlattenDoubles(item)) yield return nested;
|
||
}
|
||
}
|
||
}
|
||
|
||
static object BuildSwagentModelingPlan(string fileBase)
|
||
{
|
||
string workflowId = "part-feature-audit-" + fileBase;
|
||
var steps = Skills.Select(item => new
|
||
{
|
||
id = $"step-{item.Step:000}",
|
||
targetAgent = "solidworks",
|
||
skillName = item.Skill,
|
||
name = string.IsNullOrWhiteSpace(item.SourceFeature) ? item.Skill : item.SourceFeature,
|
||
description = item.Note ?? "",
|
||
arguments = item.Args ?? new Dictionary<string, object>(),
|
||
dependencies = item.Step > 1 ? new[] { $"step-{item.Step - 1:000}" } : Array.Empty<string>(),
|
||
acceptance = new Dictionary<string, object>()
|
||
}).ToList();
|
||
|
||
return new
|
||
{
|
||
workflowId,
|
||
sourceAgent = "part_feature_audit",
|
||
approvedPlanText = "Generated from extracted SolidWorks feature skill flow.",
|
||
modelingPlan = new
|
||
{
|
||
planId = workflowId + "-plan",
|
||
targetAgent = "solidworks",
|
||
units = "mm",
|
||
summary = $"Rebuild part from extracted SolidWorks skill flow. steps={steps.Count}",
|
||
steps
|
||
},
|
||
simulationPlan = new Dictionary<string, object>(),
|
||
exportOptions = new
|
||
{
|
||
outputDirectory = "",
|
||
fileBaseName = "",
|
||
formats = Array.Empty<string>()
|
||
},
|
||
handoffOptions = new
|
||
{
|
||
autoSubmitToSimulation = false,
|
||
simulationAgentBaseUrl = "http://127.0.0.1:6066"
|
||
}
|
||
};
|
||
}
|
||
|
||
static SkillFlowValidationResult ValidateSkillFlow()
|
||
{
|
||
var result = new SkillFlowValidationResult
|
||
{
|
||
TotalSteps = Skills.Count,
|
||
SkillsUsed = Skills.Select(s => s.Skill)
|
||
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||
.OrderBy(s => s)
|
||
.ToList()
|
||
};
|
||
|
||
var supportedSkills = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||
{
|
||
"create_new_part",
|
||
"create_front_plane_sketch",
|
||
"create_top_plane_sketch",
|
||
"create_right_plane_sketch",
|
||
"enter_sketch",
|
||
"exit_sketch",
|
||
"draw_line_mm",
|
||
"draw_center_line_mm",
|
||
"draw_point_mm",
|
||
"draw_spline_points_mm",
|
||
"draw_circle_diameter_mm",
|
||
"draw_arc_center_start_end_mm",
|
||
"draw_3point_arc_mm",
|
||
"extrude_boss_mm",
|
||
"extrude_cut_mm",
|
||
"create_revolve_boss_mm",
|
||
"create_revolve_cut_mm",
|
||
"create_helix_mm",
|
||
"list_faces",
|
||
"create_face_sketch_by_index",
|
||
"create_face_sketch_by_signature",
|
||
"create_planar_face_sketch_by_position",
|
||
"select_face_by_index",
|
||
"list_ref_planes",
|
||
"select_default_plane",
|
||
"select_reference_plane_by_name",
|
||
"create_ref_plane_sketch_by_name",
|
||
"create_reference_axis",
|
||
"create_offset_plane_mm",
|
||
"create_angle_plane_by_edge_and_face",
|
||
"create_angle_plane_by_axis_and_face",
|
||
"select_edges_by_signature",
|
||
"fillet_edges_radius_mm",
|
||
"chamfer_edges_angle_distance_mm",
|
||
"linear_pattern_feature_mm",
|
||
"circular_pattern_feature",
|
||
"draft_faces_neutral_plane_mm",
|
||
"create_rib_mm",
|
||
"shell_remove_face_at_point_mm",
|
||
"hole_wizard_threaded_mm",
|
||
"mirror_feature_about_plane",
|
||
"wrap_sketch_on_faces_mm",
|
||
"swept_cut_circular_profile_mm",
|
||
"swept_cut_profile_path",
|
||
"swept_boss_circular_profile_mm",
|
||
"loft_boss_from_profiles",
|
||
"loft_cut_from_profiles",
|
||
"boundary_boss_from_profiles",
|
||
"boundary_cut_from_profiles"
|
||
};
|
||
|
||
var requiredArgs = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
|
||
{
|
||
["draw_line_mm"] = new[] { "start_model_mm", "end_model_mm" },
|
||
["draw_center_line_mm"] = new[] { "start_model_mm", "end_model_mm" },
|
||
["draw_point_mm"] = new[] { "point_model_mm" },
|
||
["draw_spline_points_mm"] = new[] { "points_model_mm" },
|
||
["draw_circle_diameter_mm"] = new[] { "diameter_mm", "center_model_mm" },
|
||
["draw_arc_center_start_end_mm"] = new[] { "center_model_mm", "start_model_mm", "end_model_mm", "mid_model_mm" },
|
||
["draw_3point_arc_mm"] = new[] { "start_x_mm", "start_y_mm", "point_x_mm", "point_y_mm", "end_x_mm", "end_y_mm" },
|
||
["extrude_boss_mm"] = new[] { "depth_mm", "extrude_direction_model" },
|
||
["extrude_cut_mm"] = new[] { "depth_mm", "extrude_direction_model" },
|
||
["create_revolve_cut_mm"] = new[] { "angle_deg" },
|
||
["create_helix_mm"] = new[] { "defined_by" },
|
||
["create_face_sketch_by_index"] = new[] { "face_index" },
|
||
["create_face_sketch_by_signature"] = new[] { "center_x_mm", "center_y_mm", "center_z_mm", "area_mm2" },
|
||
["select_face_by_index"] = new[] { "face_index" },
|
||
["select_default_plane"] = new[] { "plane" },
|
||
["select_reference_plane_by_name"] = new[] { "plane_name" },
|
||
["create_ref_plane_sketch_by_name"] = new[] { "plane_name" },
|
||
["create_offset_plane_mm"] = new[] { "offset_mm" },
|
||
["create_angle_plane_by_edge_and_face"] = new[] { "edge_signatures", "face_index", "angle_deg" },
|
||
["select_edges_by_signature"] = new[] { "edge_signatures" },
|
||
["fillet_edges_radius_mm"] = new[] { "radius_mm" },
|
||
["chamfer_edges_angle_distance_mm"] = new[] { "distance_mm", "angle_deg" },
|
||
["linear_pattern_feature_mm"] = new[] { "count1", "spacing1_mm", "dir1_edge_x_mm", "dir1_edge_y_mm", "dir1_edge_z_mm" },
|
||
["circular_pattern_feature"] = new[] { "count", "angle_deg" },
|
||
["draft_faces_neutral_plane_mm"] = new[] { "angle_deg", "neutral_face_index", "draft_face_indices" },
|
||
["create_rib_mm"] = new[] { "thickness_mm" },
|
||
["shell_remove_face_at_point_mm"] = new[] { "thickness_mm", "x_mm", "y_mm", "z_mm" },
|
||
["hole_wizard_threaded_mm"] = new[] { "diameter_mm", "depth_mm", "position_points_model_mm" },
|
||
["swept_cut_circular_profile_mm"] = new[] { "diameter_mm" },
|
||
["swept_cut_profile_path"] = new[] { "profile_name", "path_name" },
|
||
["swept_boss_circular_profile_mm"] = new[] { "diameter_mm" }
|
||
};
|
||
|
||
var reviewOnlySkills = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||
{
|
||
"list_faces",
|
||
"list_ref_planes"
|
||
};
|
||
|
||
for (int i = 0; i < Skills.Count; i++)
|
||
{
|
||
var step = Skills[i];
|
||
string label = $"step-{step.Step:000}";
|
||
|
||
if (step.Step != i + 1)
|
||
result.Errors.Add($"{label}: step number is not sequential; expected {i + 1}.");
|
||
|
||
if (string.IsNullOrWhiteSpace(step.Skill))
|
||
{
|
||
result.Errors.Add($"{label}: missing skill.");
|
||
continue;
|
||
}
|
||
|
||
if (!supportedSkills.Contains(step.Skill))
|
||
{
|
||
result.Errors.Add($"{label}: skill '{step.Skill}' is not registered in the current SWagent skill registry.");
|
||
continue;
|
||
}
|
||
|
||
var args = step.Args ?? new Dictionary<string, object>();
|
||
if (requiredArgs.TryGetValue(step.Skill, out var required))
|
||
{
|
||
foreach (string key in required)
|
||
{
|
||
if (!args.TryGetValue(key, out var value) || IsBlankValue(value))
|
||
result.Errors.Add($"{label}: skill '{step.Skill}' is missing required argument '{key}'.");
|
||
}
|
||
}
|
||
|
||
if (reviewOnlySkills.Contains(step.Skill))
|
||
result.Warnings.Add($"{label}: skill '{step.Skill}' is an inspection/manual-review step; the flow may need a resolved selection before fully automatic replay.");
|
||
|
||
if ((string.Equals(step.Skill, "extrude_boss_mm", StringComparison.OrdinalIgnoreCase) ||
|
||
string.Equals(step.Skill, "extrude_cut_mm", StringComparison.OrdinalIgnoreCase)) &&
|
||
args.TryGetValue("depth_mm", out var depthValue) &&
|
||
TryToDouble(depthValue, out double depthMm) &&
|
||
Math.Abs(depthMm) <= 1e-9 &&
|
||
!HasTrueArg(args, "through_all") &&
|
||
!HasNonBlindEndCondition(args))
|
||
{
|
||
result.Warnings.Add($"{label}: '{step.Skill}' has depth_mm=0; SWagent execution will probably fail or create no geometry.");
|
||
}
|
||
}
|
||
|
||
result.Ok = result.Errors.Count == 0;
|
||
return result;
|
||
}
|
||
|
||
static bool IsBlankValue(object value) => value == null || value is string s && string.IsNullOrWhiteSpace(s);
|
||
|
||
static bool HasTrueArg(Dictionary<string, object> args, string key)
|
||
{
|
||
if (!args.TryGetValue(key, out var value) || value == null) return false;
|
||
if (value is bool b) return b;
|
||
return bool.TryParse(value.ToString(), out bool parsed) && parsed;
|
||
}
|
||
|
||
static bool HasNonBlindEndCondition(Dictionary<string, object> args)
|
||
{
|
||
if (args == null || !args.TryGetValue("end_condition_code", out var value))
|
||
return false;
|
||
return TryToDouble(value, out double code) &&
|
||
Math.Abs(code - (int)swEndConditions_e.swEndCondBlind) > 1e-9;
|
||
}
|
||
|
||
static bool TryToDouble(object value, out double number)
|
||
{
|
||
number = 0;
|
||
if (value == null) return false;
|
||
if (value is double d) { number = d; return true; }
|
||
if (value is float f) { number = f; return true; }
|
||
if (value is int i) { number = i; return true; }
|
||
return double.TryParse(value.ToString(), out number);
|
||
}
|
||
|
||
static void AddSkill(string skill, Dictionary<string, object> args, string source, string note) => Skills.Add(new SkillFlowStep { Step = Skills.Count + 1, Skill = skill, Args = args ?? new Dictionary<string, object>(), SourceFeature = source, Note = note });
|
||
static bool ShouldSkipDefaultFeature(string name, string type)
|
||
{
|
||
string n = (name ?? "").Trim();
|
||
string t = (type ?? "").Trim().ToLowerInvariant();
|
||
if (string.IsNullOrWhiteSpace(n) && string.IsNullOrWhiteSpace(t)) return true;
|
||
|
||
return n switch
|
||
{
|
||
"Favorites" => true,
|
||
"History" => true,
|
||
"Selection Sets" => true,
|
||
"传感器" => true,
|
||
"设计活页夹" => true,
|
||
"注解" => true,
|
||
"标注" => true,
|
||
"光源、相机与布景" => true,
|
||
"实体" => true,
|
||
"曲面实体" => true,
|
||
"备注" => true,
|
||
"方程式" => true,
|
||
"材料 <未指定>" => true,
|
||
"前视基准面" => true,
|
||
"上视基准面" => true,
|
||
"右视基准面" => true,
|
||
"原点" => true,
|
||
_ => t.Contains("historyfolder")
|
||
|| t.Contains("selectionsetfolder")
|
||
|| t.Contains("sensorfolder")
|
||
|| t.Contains("annotation")
|
||
|| t.Contains("commentsfolder")
|
||
|| t.Contains("equation")
|
||
|| t.Contains("refplane") && (n.Contains("前视") || n.Contains("上视") || n.Contains("右视"))
|
||
|| t.Contains("originprofilefeature")
|
||
};
|
||
}
|
||
|
||
static string Cat(string n, string t) { string s = (n + " " + t).ToLowerInvariant(); if (IsSketch(t)) return "草图"; if (IsHelixFeature(n, t)) return "螺旋线/涡状线"; if (IsSweepFeature(n, t)) return "扫描"; if (IsCutLike(n, t) && (s.Contains("revol") || s.Contains("revolution") || s.Contains("旋转"))) return "旋转切除"; if (s.Contains("revol") || s.Contains("revolution") || s.Contains("旋转")) return "旋转凸台"; if (IsExtrude(n, t, true)) return "切除-拉伸"; if (IsExtrude(n, t, false)) return "凸台-拉伸"; if (s.Contains("fillet") || s.Contains("圆角")) return "圆角"; if (s.Contains("chamfer") || s.Contains("倒角")) return "倒角"; if (IsCircularPatternFeature(n, t)) return "圆周阵列"; if (IsLinearPatternFeature(n, t)) return "线性阵列"; if (IsDraftFeature(n, t)) return "拔模"; if (IsRibFeature(n, t)) return "筋"; if (IsShellFeature(n, t)) return "抽壳"; if (IsHoleWizardFeature(n, t)) return "异形孔向导"; if (IsMirrorFeature(n, t)) return "镜像"; if (IsWrapFeature(n, t)) return "包覆"; if (IsBoundaryFeature(n, t)) return "边界"; if (IsLoftFeature(n, t)) return "放样"; return "其他"; }
|
||
static bool IsSketch(string t) { string s = (t ?? "").ToLowerInvariant(); return s.Contains("profilefeature") || s.Contains("sketch"); }
|
||
static bool IsExtrude(string n, string t, bool cut) { string s = (n + " " + t).ToLowerInvariant(); return cut ? s.Contains("cut") || s.Contains("切除") : (s.Contains("extrude") || s.Contains("boss") || s.Contains("拉伸")) && !s.Contains("cut") && !s.Contains("切除"); }
|
||
static bool IsFilletFeature(string n, string t) { string s = (n + " " + t).ToLowerInvariant(); return s.Contains("fillet") || s.Contains("圆角") || s.Contains("鍦嗚"); }
|
||
static bool IsChamferFeature(string n, string t) { string s = (n + " " + t).ToLowerInvariant(); return s.Contains("chamfer") || s.Contains("倒角") || s.Contains("鍊掕"); }
|
||
static bool IsCutLike(string n, string t) { string s = (n + " " + t).ToLowerInvariant(); return s.Contains("cut") || s.Contains("切除") || s.Contains("鍒囬櫎"); }
|
||
static bool IsLinearPatternFeature(string n, string t) { string s = (n + " " + t).ToLowerInvariant(); return s.Contains("lpattern") || s.Contains("linearpattern") || s.Contains("linear pattern") || s.Contains("线性阵列") || s.Contains("線性陣列"); }
|
||
static bool IsCircularPatternFeature(string n, string t) { string s = (n + " " + t).ToLowerInvariant(); return s.Contains("cirpattern") || s.Contains("circularpattern") || s.Contains("circular pattern") || s.Contains("圆周阵列") || s.Contains("圓周陣列") || s.Contains("阵列(圆周)") || s.Contains("陣列(圓周)"); }
|
||
static bool IsDraftFeature(string n, string t) { string s = (n + " " + t).ToLowerInvariant(); return s.Contains("draft") || s.Contains("拔模"); }
|
||
static bool IsRibFeature(string n, string t) { string s = (n + " " + t).ToLowerInvariant(); return s.Contains("rib") || s.Contains("筋"); }
|
||
static bool IsReferenceAxisFeature(string n, string t)
|
||
{
|
||
string type = (t ?? "").Trim();
|
||
if (string.Equals(type, "RefAxis", StringComparison.OrdinalIgnoreCase))
|
||
return true;
|
||
string s = (n + " " + t).ToLowerInvariant();
|
||
return s.Contains("refaxis") || s.Contains("reference axis") || s.Contains("基准轴");
|
||
}
|
||
static bool IsShellFeature(string n, string t) { string s = (n + " " + t).ToLowerInvariant(); return s.Contains("shell") || s.Contains("抽壳") || s.Contains("抽殼"); }
|
||
static bool IsHoleWizardFeature(string n, string t) { string s = (n + " " + t).ToLowerInvariant(); return s.Contains("holewzd") || s.Contains("hole wizard") || s.Contains("wizardhole") || s.Contains("异形孔") || s.Contains("異型孔"); }
|
||
static bool IsMirrorFeature(string n, string t) { string s = (n + " " + t).ToLowerInvariant(); return s.Contains("mirror") || s.Contains("镜像") || s.Contains("鏡像"); }
|
||
static bool IsWrapFeature(string n, string t) { string s = (n + " " + t).ToLowerInvariant(); return s.Contains("wrap") || s.Contains("包覆"); }
|
||
static bool IsHelixFeature(string n, string t) { string s = (n + " " + t).ToLowerInvariant(); return s.Contains("helix") || s.Contains("spiral") || s.Contains("螺旋") || s.Contains("涡状") || s.Contains("渦狀"); }
|
||
static bool IsSweepFeature(string n, string t) { string s = (n + " " + t).ToLowerInvariant(); return s.Contains("sweep") || s.Contains("swept") || s.Contains("扫描") || s.Contains("掃描"); }
|
||
static bool IsLoftFeature(string n, string t) { string s = (n + " " + t).ToLowerInvariant(); return s.Contains("loft") || s.Contains("blend") || s.Contains("放样") || s.Contains("放樣"); }
|
||
static bool IsBoundaryFeature(string n, string t) { string s = (n + " " + t).ToLowerInvariant(); return s.Contains("boundary") || s.Contains("netblend") || s.Contains("边界") || s.Contains("邊界"); }
|
||
static object SafeObj(Func<object> f) { try { return f(); } catch { return null; } }
|
||
static bool SafeBool(Func<bool> f, bool d = false) { try { return f(); } catch { return d; } }
|
||
static T SafeValue<T>(Func<T> f, T d = default) { try { return f(); } catch { return d; } }
|
||
static void SafeAction(Action a) { try { a(); } catch { } }
|
||
class SketchPointSample
|
||
{
|
||
public double X, Y, Z;
|
||
public int Priority, Order;
|
||
}
|
||
|
||
static Dictionary<IntPtr, double[]> BuildSketchPointCanonicalMap(Sketch sk)
|
||
{
|
||
var samples = new Dictionary<IntPtr, List<SketchPointSample>>();
|
||
var parent = new Dictionary<IntPtr, IntPtr>();
|
||
int order = 0;
|
||
|
||
IntPtr Find(IntPtr id)
|
||
{
|
||
if (id == IntPtr.Zero) return IntPtr.Zero;
|
||
if (!parent.TryGetValue(id, out var p))
|
||
{
|
||
parent[id] = id;
|
||
return id;
|
||
}
|
||
if (p == id) return id;
|
||
var root = Find(p);
|
||
parent[id] = root;
|
||
return root;
|
||
}
|
||
|
||
void Union(IntPtr a, IntPtr b)
|
||
{
|
||
var ra = Find(a);
|
||
var rb = Find(b);
|
||
if (ra == IntPtr.Zero || rb == IntPtr.Zero || ra == rb) return;
|
||
parent[rb] = ra;
|
||
}
|
||
|
||
IntPtr Register(SketchPoint point, int priority)
|
||
{
|
||
if (point == null) return IntPtr.Zero;
|
||
var id = Id(point);
|
||
if (id == IntPtr.Zero) return IntPtr.Zero;
|
||
if (!parent.ContainsKey(id)) parent[id] = id;
|
||
try
|
||
{
|
||
if (!samples.TryGetValue(id, out var list))
|
||
{
|
||
list = new List<SketchPointSample>();
|
||
samples[id] = list;
|
||
}
|
||
list.Add(new SketchPointSample
|
||
{
|
||
X = point.X * 1000.0,
|
||
Y = point.Y * 1000.0,
|
||
Z = point.Z * 1000.0,
|
||
Priority = priority,
|
||
Order = order++
|
||
});
|
||
}
|
||
catch { }
|
||
return id;
|
||
}
|
||
|
||
foreach (object o in ToObjectArray(SafeObj(() => sk.GetSketchSegments())))
|
||
{
|
||
if (o is SketchLine line)
|
||
{
|
||
Register(line.GetStartPoint2() as SketchPoint, 2);
|
||
Register(line.GetEndPoint2() as SketchPoint, 2);
|
||
}
|
||
else if (o is SketchArc arc)
|
||
{
|
||
Register(arc.GetCenterPoint2() as SketchPoint, 1);
|
||
Register(arc.GetStartPoint2() as SketchPoint, 3);
|
||
Register(arc.GetEndPoint2() as SketchPoint, 3);
|
||
}
|
||
}
|
||
|
||
foreach (object pointObj in InvokeArray(sk, "GetSketchPoints2").Concat(InvokeArray(sk, "GetSketchPoints")))
|
||
{
|
||
if (pointObj is SketchPoint point) Register(point, 0);
|
||
}
|
||
|
||
foreach (object relObj in SketchRelations(sk))
|
||
{
|
||
if (relObj is not SketchRelation relation) continue;
|
||
int relationType = SafeInt(() => relation.GetRelationType());
|
||
if (relationType != (int)swConstraintType_e.swConstraintType_COINCIDENT
|
||
&& relationType != (int)swConstraintType_e.swConstraintType_MERGEPOINTS)
|
||
continue;
|
||
|
||
var ids = new List<IntPtr>();
|
||
object[] entities = ToObjectArray(SafeObj(() => relation.GetEntities()));
|
||
if (entities.Length == 0)
|
||
{
|
||
int count = SafeInt(() => relation.GetEntitiesCount(), 0);
|
||
if (count > 0) entities = ToObjectArray(SafeObj(() => relation.IGetEntities(count)));
|
||
}
|
||
|
||
foreach (object entity in entities)
|
||
{
|
||
if (entity is SketchPoint point)
|
||
{
|
||
var id = Register(point, 0);
|
||
if (id != IntPtr.Zero) ids.Add(id);
|
||
}
|
||
}
|
||
|
||
for (int i = 1; i < ids.Count; i++)
|
||
Union(ids[0], ids[i]);
|
||
}
|
||
|
||
var groups = new Dictionary<IntPtr, List<SketchPointSample>>();
|
||
foreach (var kv in samples)
|
||
{
|
||
var root = Find(kv.Key);
|
||
if (!groups.TryGetValue(root, out var list))
|
||
{
|
||
list = new List<SketchPointSample>();
|
||
groups[root] = list;
|
||
}
|
||
list.AddRange(kv.Value);
|
||
}
|
||
|
||
var result = new Dictionary<IntPtr, double[]>();
|
||
foreach (var group in groups)
|
||
{
|
||
var chosen = group.Value
|
||
.OrderByDescending(p => p.Priority)
|
||
.ThenBy(p => p.Order)
|
||
.FirstOrDefault();
|
||
if (chosen == null) continue;
|
||
var canonical = new[] { chosen.X, chosen.Y, chosen.Z };
|
||
foreach (var id in samples.Keys.Where(id => Find(id) == group.Key))
|
||
result[id] = canonical;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
static object[] SketchRelations(Sketch sk)
|
||
{
|
||
var list = new List<object>();
|
||
var seen = new HashSet<IntPtr>();
|
||
|
||
void Add(object value)
|
||
{
|
||
foreach (object item in ToObjectArray(value))
|
||
{
|
||
if (item == null) continue;
|
||
var id = Id(item);
|
||
if (id == IntPtr.Zero || seen.Add(id))
|
||
list.Add(item);
|
||
}
|
||
}
|
||
|
||
Add(InvokeCom(sk, "GetRelations"));
|
||
|
||
object manager = GetComProperty(sk, "RelationManager");
|
||
if (manager != null)
|
||
{
|
||
Add(InvokeCom(manager, "GetRelations"));
|
||
int count = SafeInt(() => Convert.ToInt32(InvokeCom(manager, "GetRelationsCount")), 0);
|
||
if (count > 0) Add(InvokeCom(manager, "IGetRelations", count));
|
||
}
|
||
|
||
return list.ToArray();
|
||
}
|
||
|
||
static bool Pt(SketchPoint p, out double x, out double y) { x = y = 0; if (p == null) return false; x = p.X * 1000; y = p.Y * 1000; return true; }
|
||
static bool Pt3(SketchPoint p, out double x, out double y, out double z) { x = y = z = 0; if (p == null) return false; x = p.X * 1000; y = p.Y * 1000; z = p.Z * 1000; return true; }
|
||
static bool Pt(SketchPoint p, Dictionary<IntPtr, double[]> canonical, out double x, out double y)
|
||
{
|
||
x = y = 0;
|
||
if (p == null) return false;
|
||
var id = Id(p);
|
||
if (id != IntPtr.Zero && canonical != null && canonical.TryGetValue(id, out var values) && values.Length >= 2)
|
||
{
|
||
x = values[0];
|
||
y = values[1];
|
||
return true;
|
||
}
|
||
return Pt(p, out x, out y);
|
||
}
|
||
|
||
static bool Pt3(SketchPoint p, Dictionary<IntPtr, double[]> canonical, out double x, out double y, out double z)
|
||
{
|
||
x = y = z = 0;
|
||
if (p == null) return false;
|
||
var id = Id(p);
|
||
if (id != IntPtr.Zero && canonical != null && canonical.TryGetValue(id, out var values) && values.Length >= 3)
|
||
{
|
||
x = values[0];
|
||
y = values[1];
|
||
z = values[2];
|
||
return true;
|
||
}
|
||
return Pt3(p, out x, out y, out z);
|
||
}
|
||
|
||
static IntPtr Id(object o) { if (!Marshal.IsComObject(o)) return IntPtr.Zero; IntPtr p = IntPtr.Zero; try { p = Marshal.GetIUnknownForObject(o); return p; } catch { return IntPtr.Zero; } finally { if (p != IntPtr.Zero) Marshal.Release(p); } }
|
||
static object[] InvokeArray(object target, string name)
|
||
{
|
||
try { return target.GetType().InvokeMember(name, System.Reflection.BindingFlags.InvokeMethod, null, target, Array.Empty<object>()) as object[] ?? Array.Empty<object>(); }
|
||
catch { return Array.Empty<object>(); }
|
||
}
|
||
static object[] ToObjectArray(object value)
|
||
{
|
||
if (value == null) return Array.Empty<object>();
|
||
if (value is DispatchWrapper wrapper) return ToObjectArray(wrapper.WrappedObject);
|
||
if (value is object[] objects) return objects;
|
||
if (value is Array array)
|
||
{
|
||
var list = new List<object>();
|
||
foreach (object item in array)
|
||
{
|
||
if (item is DispatchWrapper nestedWrapper)
|
||
list.Add(nestedWrapper.WrappedObject);
|
||
else
|
||
list.Add(item);
|
||
}
|
||
return list.ToArray();
|
||
}
|
||
return new[] { value };
|
||
}
|
||
static double[] ToDoubleArray(object value)
|
||
{
|
||
if (value == null) return Array.Empty<double>();
|
||
if (value is double[] doubles) return doubles;
|
||
if (value is Array array)
|
||
{
|
||
var list = new List<double>();
|
||
foreach (object item in array)
|
||
{
|
||
if (item == null) continue;
|
||
list.Add(Convert.ToDouble(item));
|
||
}
|
||
return list.ToArray();
|
||
}
|
||
return Array.Empty<double>();
|
||
}
|
||
static object InvokeCom(object target, string name, params object[] args)
|
||
{
|
||
try { return target?.GetType().InvokeMember(name, System.Reflection.BindingFlags.InvokeMethod, null, target, args ?? Array.Empty<object>()); }
|
||
catch { return null; }
|
||
}
|
||
static object GetComProperty(object target, string name)
|
||
{
|
||
try { return target?.GetType().InvokeMember(name, System.Reflection.BindingFlags.GetProperty, null, target, Array.Empty<object>()); }
|
||
catch { return null; }
|
||
}
|
||
static object GetComProperty(object target, string name, params object[] args)
|
||
{
|
||
try { return target?.GetType().InvokeMember(name, System.Reflection.BindingFlags.GetProperty, null, target, args ?? Array.Empty<object>()); }
|
||
catch { return null; }
|
||
}
|
||
static bool TryInvokeCom(object target, string name, params object[] args)
|
||
{
|
||
try
|
||
{
|
||
target?.GetType().InvokeMember(name, System.Reflection.BindingFlags.InvokeMethod, null, target, args ?? Array.Empty<object>());
|
||
return target != null;
|
||
}
|
||
catch { return false; }
|
||
}
|
||
static string Safe(Func<string> f, string d = "") { try { return f() ?? d; } catch { return d; } }
|
||
static int SafeInt(Func<int> f, int d = -1) { try { return f(); } catch { return d; } }
|
||
static double SafeDouble(Func<double> f, double d = 0) { try { return f(); } catch { return d; } }
|
||
static double R(double v) => Math.Round(v, 6);
|
||
static string AppendText(string a, string b) => string.IsNullOrWhiteSpace(a) ? b : a + ";" + b;
|
||
static void Inc(Dictionary<int, int> d, int k) => d[k] = d.TryGetValue(k, out var v) ? v + 1 : 1;
|
||
}
|