refactor(core): унификация COM-хелперов и классификаторов топологии
Вынесены общие ComHelper (Release + Safe-геттеры) и GeomClassifiers (FaceType/EdgeType), устранены дубли в 5+ сервисах. Освобождение doc3d RCW в GetTopPart, void-возвраты вместо фиктивных bool, HashSet для OpKinds, DocumentTypeName в паспорте модели, FileExtension без аллокаций. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using Kompas.Mcp.Core.Documents;
|
using Kompas.Mcp.Core.Documents;
|
||||||
|
using Kompas.Mcp.Core.Interop;
|
||||||
using Kompas.Mcp.Core.Threading;
|
using Kompas.Mcp.Core.Threading;
|
||||||
using Kompas6Constants;
|
using Kompas6Constants;
|
||||||
using KompasAPI7;
|
using KompasAPI7;
|
||||||
@@ -39,7 +39,7 @@ public sealed class ConversionService
|
|||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
=> _dispatcher.InvokeAsync(() => ImportCore(path, docType, createComponentFiles), ct);
|
=> _dispatcher.InvokeAsync(() => ImportCore(path, docType, createComponentFiles), ct);
|
||||||
|
|
||||||
private bool ImportCore(string path, KompasDocumentType docType, bool createComponentFiles)
|
private void ImportCore(string path, KompasDocumentType docType, bool createComponentFiles)
|
||||||
{
|
{
|
||||||
if (!File.Exists(path))
|
if (!File.Exists(path))
|
||||||
throw new FileNotFoundException($"STEP-файл не найден: {path}", path);
|
throw new FileNotFoundException($"STEP-файл не найден: {path}", path);
|
||||||
@@ -63,19 +63,22 @@ public sealed class ConversionService
|
|||||||
prm.Format = (ksKOMPASConverterEnum)StepFormats.ImportConverterCode;
|
prm.Format = (ksKOMPASConverterEnum)StepFormats.ImportConverterCode;
|
||||||
if (createComponentFiles)
|
if (createComponentFiles)
|
||||||
{
|
{
|
||||||
|
// NeedCreateComponentsFiles может отсутствовать в Home-редакции КОМПАС —
|
||||||
|
// ловим COM-исключения; не критично, если свойство недоступно.
|
||||||
try { prm.NeedCreateComponentsFiles = true; }
|
try { prm.NeedCreateComponentsFiles = true; }
|
||||||
catch { /* свойство может быть недоступно — не критично */ }
|
catch (System.Runtime.InteropServices.COMException) { }
|
||||||
|
catch (System.Runtime.InteropServices.InvalidComObjectException) { }
|
||||||
|
catch (NotImplementedException) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!doc3d1.ConvertFromAdditionFormat(path, (AdditionConvertParameters)prm))
|
if (!doc3d1.ConvertFromAdditionFormat(path, (AdditionConvertParameters)prm))
|
||||||
throw new InvalidOperationException("ConvertFromAdditionFormat вернул FALSE (импорт STEP не удался).");
|
throw new InvalidOperationException("ConvertFromAdditionFormat вернул FALSE (импорт STEP не удался).");
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
ReleaseCom(prm);
|
ComHelper.Release(prm);
|
||||||
ReleaseCom(doc);
|
ComHelper.Release(doc);
|
||||||
ReleaseCom(conv);
|
ComHelper.Release(conv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +86,7 @@ public sealed class ConversionService
|
|||||||
public Task ExportStepAsync(string path, StepFormat format = StepFormat.Auto, CancellationToken ct = default)
|
public Task ExportStepAsync(string path, StepFormat format = StepFormat.Auto, CancellationToken ct = default)
|
||||||
=> _dispatcher.InvokeAsync(() => ExportCore(path, format), ct);
|
=> _dispatcher.InvokeAsync(() => ExportCore(path, format), ct);
|
||||||
|
|
||||||
private bool ExportCore(string path, StepFormat format)
|
private void ExportCore(string path, StepFormat format)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(path))
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
throw new ArgumentException("Путь экспорта не задан.", nameof(path));
|
throw new ArgumentException("Путь экспорта не задан.", nameof(path));
|
||||||
@@ -109,13 +112,12 @@ public sealed class ConversionService
|
|||||||
|
|
||||||
if (!doc3d1.ConvertToAdditionFormat(path, (AdditionConvertParameters)prm))
|
if (!doc3d1.ConvertToAdditionFormat(path, (AdditionConvertParameters)prm))
|
||||||
throw new InvalidOperationException("ConvertToAdditionFormat вернул FALSE (экспорт STEP не удался).");
|
throw new InvalidOperationException("ConvertToAdditionFormat вернул FALSE (экспорт STEP не удался).");
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
ReleaseCom(prm);
|
ComHelper.Release(prm);
|
||||||
ReleaseCom(active);
|
ComHelper.Release(active);
|
||||||
ReleaseCom(conv);
|
ComHelper.Release(conv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,13 +133,4 @@ public sealed class ConversionService
|
|||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
$"get_Converter({formatCode}) вернул null — STEP-конвертер недоступен в этой редакции КОМПАС.");
|
$"get_Converter({formatCode}) вернул null — STEP-конвертер недоступен в этой редакции КОМПАС.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReleaseCom(object? comObject)
|
|
||||||
{
|
|
||||||
if (comObject is not null && Marshal.IsComObject(comObject))
|
|
||||||
{
|
|
||||||
try { Marshal.ReleaseComObject(comObject); }
|
|
||||||
catch { /* освобождение не критично */ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
using Kompas.Mcp.Core.Interop;
|
||||||
using Kompas.Mcp.Core.Threading;
|
using Kompas.Mcp.Core.Threading;
|
||||||
using Kompas6Constants;
|
using Kompas6Constants;
|
||||||
using KompasAPI7;
|
using KompasAPI7;
|
||||||
@@ -26,7 +26,7 @@ public sealed class DocumentService
|
|||||||
var docs = _session.Application.Documents;
|
var docs = _session.Application.Documents;
|
||||||
var doc = (IKompasDocument)docs.Add(DocumentTypes.ToEnum(type), visible);
|
var doc = (IKompasDocument)docs.Add(DocumentTypes.ToEnum(type), visible);
|
||||||
try { return ToInfo(doc); }
|
try { return ToInfo(doc); }
|
||||||
finally { ReleaseCom(doc); ReleaseCom(docs); }
|
finally { ComHelper.Release(doc); ComHelper.Release(docs); }
|
||||||
}, ct);
|
}, ct);
|
||||||
|
|
||||||
/// <summary>Открыть документ с диска.</summary>
|
/// <summary>Открыть документ с диска.</summary>
|
||||||
@@ -38,7 +38,7 @@ public sealed class DocumentService
|
|||||||
if (doc is null)
|
if (doc is null)
|
||||||
throw new InvalidOperationException($"Не удалось открыть документ: {path}");
|
throw new InvalidOperationException($"Не удалось открыть документ: {path}");
|
||||||
try { return ToInfo(doc); }
|
try { return ToInfo(doc); }
|
||||||
finally { ReleaseCom(doc); ReleaseCom(docs); }
|
finally { ComHelper.Release(doc); ComHelper.Release(docs); }
|
||||||
}, ct);
|
}, ct);
|
||||||
|
|
||||||
/// <summary>Сводка по активному документу или <c>null</c>, если документов нет.</summary>
|
/// <summary>Сводка по активному документу или <c>null</c>, если документов нет.</summary>
|
||||||
@@ -48,7 +48,7 @@ public sealed class DocumentService
|
|||||||
var doc = _session.Application.ActiveDocument;
|
var doc = _session.Application.ActiveDocument;
|
||||||
if (doc is null) return null;
|
if (doc is null) return null;
|
||||||
try { return ToInfo(doc); }
|
try { return ToInfo(doc); }
|
||||||
finally { ReleaseCom(doc); }
|
finally { ComHelper.Release(doc); }
|
||||||
}, ct);
|
}, ct);
|
||||||
|
|
||||||
/// <summary>Сохранить активный документ (для уже сохранённого — без диалога).</summary>
|
/// <summary>Сохранить активный документ (для уже сохранённого — без диалога).</summary>
|
||||||
@@ -72,7 +72,7 @@ public sealed class DocumentService
|
|||||||
var doc = _session.Application.ActiveDocument
|
var doc = _session.Application.ActiveDocument
|
||||||
?? throw new InvalidOperationException("Нет активного документа.");
|
?? throw new InvalidOperationException("Нет активного документа.");
|
||||||
try { return func(doc); }
|
try { return func(doc); }
|
||||||
finally { ReleaseCom(doc); }
|
finally { ComHelper.Release(doc); }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DocumentInfo ToInfo(IKompasDocument doc) => new()
|
private static DocumentInfo ToInfo(IKompasDocument doc) => new()
|
||||||
@@ -81,13 +81,4 @@ public sealed class DocumentService
|
|||||||
PathName = doc.PathName ?? string.Empty,
|
PathName = doc.PathName ?? string.Empty,
|
||||||
Type = DocumentTypes.FromEnum(doc.DocumentType),
|
Type = DocumentTypes.FromEnum(doc.DocumentType),
|
||||||
};
|
};
|
||||||
|
|
||||||
private static void ReleaseCom(object? comObject)
|
|
||||||
{
|
|
||||||
if (comObject is not null && Marshal.IsComObject(comObject))
|
|
||||||
{
|
|
||||||
try { Marshal.ReleaseComObject(comObject); }
|
|
||||||
catch { /* освобождение не критично */ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public sealed class FaceEditService
|
|||||||
public Task MoveFaceAsync(double x, double y, double z, double distance, CancellationToken ct = default)
|
public Task MoveFaceAsync(double x, double y, double z, double distance, CancellationToken ct = default)
|
||||||
=> _dispatcher.InvokeAsync(() => MoveFaceCore(x, y, z, distance), ct);
|
=> _dispatcher.InvokeAsync(() => MoveFaceCore(x, y, z, distance), ct);
|
||||||
|
|
||||||
private bool MoveFaceCore(double x, double y, double z, double distance)
|
private void MoveFaceCore(double x, double y, double z, double distance)
|
||||||
{
|
{
|
||||||
if (distance == 0) throw new ArgumentException("Смещение не может быть нулевым.", nameof(distance));
|
if (distance == 0) throw new ArgumentException("Смещение не может быть нулевым.", nameof(distance));
|
||||||
|
|
||||||
@@ -61,7 +61,6 @@ public sealed class FaceEditService
|
|||||||
|
|
||||||
// Перестраиваем, чтобы состояние ошибки операции стало актуальным (проверяется через validate_part).
|
// Перестраиваем, чтобы состояние ошибки операции стало актуальным (проверяется через validate_part).
|
||||||
doc3d.RebuildDocument();
|
doc3d.RebuildDocument();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IFace? FindFaceAtPoint(IPart7 part, double x, double y, double z)
|
private static IFace? FindFaceAtPoint(IPart7 part, double x, double y, double z)
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Kompas.Mcp.Core.Interop;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Вспомогательные методы для работы с COM RCW: безопасное освобождение
|
||||||
|
/// и safe-геттеры для свойств, которые могут бросить исключение.
|
||||||
|
/// <para>
|
||||||
|
/// Метод <see cref="Release"/> идентично реализован в каждом сервисе — вынесен сюда.
|
||||||
|
/// Safe-геттеры заменяют разрозненные <c>SafeStr</c>/<c>SafeGet</c>/<c>SafeBool</c>/<c>SafeNum</c>.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
internal static class ComHelper
|
||||||
|
{
|
||||||
|
/// <summary>Освободить COM RCW: null-гард + IsComObject-проверка + try/catch.</summary>
|
||||||
|
internal static void Release(object? com)
|
||||||
|
{
|
||||||
|
if (com is not null && Marshal.IsComObject(com))
|
||||||
|
try { Marshal.ReleaseComObject(com); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Безопасно получить строку: возвращает <c>""</c> при любом исключении.</summary>
|
||||||
|
internal static string SafeStr(Func<string?> f) { try { return f() ?? ""; } catch { return ""; } }
|
||||||
|
|
||||||
|
/// <summary>Безопасно получить число: возвращает <c>0</c> при любом исключении.</summary>
|
||||||
|
internal static double SafeNum(Func<double> f) { try { return f(); } catch { return 0; } }
|
||||||
|
|
||||||
|
/// <summary>Безопасно получить флаг: возвращает <c>false</c> при любом исключении.</summary>
|
||||||
|
internal static bool SafeBool(Func<bool> f) { try { return f(); } catch { return false; } }
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ public sealed class KompasSession : IDisposable
|
|||||||
private KompasConnectionInfo ConnectCore(bool visible)
|
private KompasConnectionInfo ConnectCore(bool visible)
|
||||||
{
|
{
|
||||||
if (_app is not null)
|
if (_app is not null)
|
||||||
return LastInfo ??= BuildInfo(LastProgId);
|
return LastInfo ??= BuildInfo(_lastProgId);
|
||||||
|
|
||||||
// 1) attach к уже запущенному экземпляру
|
// 1) attach к уже запущенному экземпляру
|
||||||
foreach (var id in ProgIds)
|
foreach (var id in ProgIds)
|
||||||
@@ -73,7 +73,7 @@ public sealed class KompasSession : IDisposable
|
|||||||
if (NativeMethods.GetActiveObject(id) is KompasObject ko)
|
if (NativeMethods.GetActiveObject(id) is KompasObject ko)
|
||||||
{
|
{
|
||||||
_kompas = ko;
|
_kompas = ko;
|
||||||
LastProgId = id;
|
_lastProgId = id;
|
||||||
_launchedByUs = false;
|
_launchedByUs = false;
|
||||||
_log.LogInformation("Присоединились к запущенному КОМПАС ({ProgId})", id);
|
_log.LogInformation("Присоединились к запущенному КОМПАС ({ProgId})", id);
|
||||||
break;
|
break;
|
||||||
@@ -92,7 +92,7 @@ public sealed class KompasSession : IDisposable
|
|||||||
if (Activator.CreateInstance(type) is KompasObject ko)
|
if (Activator.CreateInstance(type) is KompasObject ko)
|
||||||
{
|
{
|
||||||
_kompas = ko;
|
_kompas = ko;
|
||||||
LastProgId = id;
|
_lastProgId = id;
|
||||||
_launchedByUs = true;
|
_launchedByUs = true;
|
||||||
_log.LogInformation("Запустили новый КОМПАС ({ProgId})", id);
|
_log.LogInformation("Запустили новый КОМПАС ({ProgId})", id);
|
||||||
break;
|
break;
|
||||||
@@ -114,10 +114,10 @@ public sealed class KompasSession : IDisposable
|
|||||||
|
|
||||||
_app.Visible = visible;
|
_app.Visible = visible;
|
||||||
|
|
||||||
return LastInfo = BuildInfo(LastProgId);
|
return LastInfo = BuildInfo(_lastProgId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string LastProgId { get; set; } = string.Empty;
|
private string _lastProgId = string.Empty;
|
||||||
|
|
||||||
private KompasConnectionInfo BuildInfo(string progId)
|
private KompasConnectionInfo BuildInfo(string progId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
using Kompas.Mcp.Core.Interop;
|
||||||
using Kompas.Mcp.Core.Threading;
|
using Kompas.Mcp.Core.Threading;
|
||||||
using Kompas6API5;
|
using Kompas6API5;
|
||||||
using Kompas6Constants3D;
|
using Kompas6Constants3D;
|
||||||
@@ -432,14 +432,7 @@ public sealed class PartModeler : IDisposable
|
|||||||
_nextId = 1;
|
_nextId = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReleaseCom(object? comObject)
|
private static void ReleaseCom(object? comObject) => ComHelper.Release(comObject);
|
||||||
{
|
|
||||||
if (comObject is not null && Marshal.IsComObject(comObject))
|
|
||||||
{
|
|
||||||
try { Marshal.ReleaseComObject(comObject); }
|
|
||||||
catch { /* освобождение не критично */ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using Kompas6API5;
|
||||||
|
|
||||||
|
namespace Kompas.Mcp.Core.Query;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Классификаторы топологических объектов КОМПАС: строковый тип грани и ребра.
|
||||||
|
/// <para>
|
||||||
|
/// Каждый вызов COM-метода обёрнут в отдельный <c>try/catch</c> — на экзотической геометрии
|
||||||
|
/// (NURBS, импортированные STEP-тела) методы класса <c>ksFaceDefinition</c>/<c>ksEdgeDefinition</c>
|
||||||
|
/// могут бросить <c>COMException</c>, что не должно прерывать bulk-перебор. Ранее этот код
|
||||||
|
/// был продублирован в <c>QueryService</c> (без защиты) и <c>ModelInspectionService</c> (с защитой).
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
internal static class GeomClassifiers
|
||||||
|
{
|
||||||
|
/// <summary>Строковый тип поверхности: plane/cylinder/cone/sphere/torus/nurbs/other.</summary>
|
||||||
|
internal static string FaceType(ksFaceDefinition d)
|
||||||
|
{
|
||||||
|
try { if (d.IsPlanar()) return "plane"; } catch { }
|
||||||
|
try { if (d.IsCylinder()) return "cylinder"; } catch { }
|
||||||
|
try { if (d.IsCone()) return "cone"; } catch { }
|
||||||
|
try { if (d.IsSphere()) return "sphere"; } catch { }
|
||||||
|
try { if (d.IsTorus()) return "torus"; } catch { }
|
||||||
|
try { if (d.IsNurbsSurface()) return "nurbs"; } catch { }
|
||||||
|
return "other";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Строковый тип кривой: line/circle/arc/ellipse_arc/ellipse/nurbs/other.</summary>
|
||||||
|
internal static string EdgeType(ksEdgeDefinition d)
|
||||||
|
{
|
||||||
|
try { if (d.IsLineSeg()) return "line"; } catch { }
|
||||||
|
try { if (d.IsCircle()) return "circle"; } catch { }
|
||||||
|
try { if (d.IsArc()) return "arc"; } catch { }
|
||||||
|
try { if (d.IsEllipseArc()) return "ellipse_arc"; } catch { }
|
||||||
|
try { if (d.IsEllipse()) return "ellipse"; } catch { }
|
||||||
|
try { if (d.IsNurbs()) return "nurbs"; } catch { }
|
||||||
|
return "other";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,8 +23,9 @@ public static class InspectionText
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Формообразующие операции, наличие которых означает параметрическую историю.
|
// Формообразующие операции, наличие которых означает параметрическую историю.
|
||||||
private static readonly string[] OpKinds =
|
// HashSet<string> даёт O(1) Contains вместо O(n) по массиву при bulk-проверке граней.
|
||||||
{ "sketch", "extrusion", "cut_extrusion", "revolve", "cut_revolve", "fillet", "chamfer" };
|
private static readonly HashSet<string> OpKinds =
|
||||||
|
new(StringComparer.Ordinal) { "sketch", "extrusion", "cut_extrusion", "revolve", "cut_revolve", "fillet", "chamfer" };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Модель — «голый» импорт без истории построения (типично для STEP): в дереве только начало
|
/// Модель — «голый» импорт без истории построения (типично для STEP): в дереве только начало
|
||||||
@@ -39,18 +40,21 @@ public static class InspectionText
|
|||||||
public static string GroupTypes(IReadOnlyList<string> types)
|
public static string GroupTypes(IReadOnlyList<string> types)
|
||||||
{
|
{
|
||||||
if (types.Count == 0) return "—";
|
if (types.Count == 0) return "—";
|
||||||
|
// Count материализуем один раз: IGrouping<K,V> — IEnumerable без кеша,
|
||||||
|
// двойной вызов g.Count() итерирует группу дважды.
|
||||||
return string.Join(", ", types
|
return string.Join(", ", types
|
||||||
.GroupBy(t => t)
|
.GroupBy(t => t)
|
||||||
.OrderByDescending(g => g.Count())
|
.Select(g => (Key: g.Key, Count: g.Count()))
|
||||||
.ThenBy(g => g.Key, StringComparer.Ordinal)
|
.OrderByDescending(x => x.Count)
|
||||||
.Select(g => $"{g.Count()} {g.Key}"));
|
.ThenBy(x => x.Key, StringComparer.Ordinal)
|
||||||
|
.Select(x => $"{x.Count} {x.Key}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Отрендерить «паспорт» модели в читаемый текст.</summary>
|
/// <summary>Отрендерить «паспорт» модели в читаемый текст.</summary>
|
||||||
public static string Render(ModelDescription d)
|
public static string Render(ModelDescription d)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine($"Документ: «{d.Name}» (деталь)");
|
sb.AppendLine($"Документ: «{d.Name}» ({d.DocumentTypeName})");
|
||||||
|
|
||||||
if (d.IsEmpty)
|
if (d.IsEmpty)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,4 +38,10 @@ public sealed record ModelDescription
|
|||||||
|
|
||||||
/// <summary>Переменные модели.</summary>
|
/// <summary>Переменные модели.</summary>
|
||||||
public required IReadOnlyList<VariableInfo> Variables { get; init; }
|
public required IReadOnlyList<VariableInfo> Variables { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Локализованное название типа документа для вывода: «деталь», «сборка», «чертёж» и т.д.
|
||||||
|
/// По умолчанию «деталь».
|
||||||
|
/// </summary>
|
||||||
|
public string DocumentTypeName { get; init; } = "деталь";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
using Kompas.Mcp.Core.Documents;
|
||||||
|
using Kompas.Mcp.Core.Interop;
|
||||||
using Kompas.Mcp.Core.Threading;
|
using Kompas.Mcp.Core.Threading;
|
||||||
using Kompas6API5;
|
using Kompas6API5;
|
||||||
using Kompas6Constants3D;
|
using Kompas6Constants3D;
|
||||||
@@ -34,7 +36,7 @@ public sealed class ModelInspectionService
|
|||||||
var part = GetTopPart();
|
var part = GetTopPart();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var name = ActiveDocName();
|
var (name, typeName) = ActiveDocInfo();
|
||||||
|
|
||||||
bool hasGab = part.GetGabarit(false, false,
|
bool hasGab = part.GetGabarit(false, false,
|
||||||
out double x1, out double y1, out double z1, out double x2, out double y2, out double z2);
|
out double x1, out double y1, out double z1, out double x2, out double y2, out double z2);
|
||||||
@@ -48,6 +50,7 @@ public sealed class ModelInspectionService
|
|||||||
Bodies = Array.Empty<BodyInfo>(), FaceTypes = Array.Empty<string>(),
|
Bodies = Array.Empty<BodyInfo>(), FaceTypes = Array.Empty<string>(),
|
||||||
EdgeTypes = Array.Empty<string>(), Features = Array.Empty<FeatureInfo>(),
|
EdgeTypes = Array.Empty<string>(), Features = Array.Empty<FeatureInfo>(),
|
||||||
Imported = false, Variables = Array.Empty<VariableInfo>(),
|
Imported = false, Variables = Array.Empty<VariableInfo>(),
|
||||||
|
DocumentTypeName = typeName,
|
||||||
};
|
};
|
||||||
|
|
||||||
var bodies = ReadBodies(part);
|
var bodies = ReadBodies(part);
|
||||||
@@ -67,9 +70,10 @@ public sealed class ModelInspectionService
|
|||||||
Features = features,
|
Features = features,
|
||||||
Imported = InspectionText.IsImported(features, bodies.Count),
|
Imported = InspectionText.IsImported(features, bodies.Count),
|
||||||
Variables = variables,
|
Variables = variables,
|
||||||
|
DocumentTypeName = typeName,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
finally { Release(part); }
|
finally { ComHelper.Release(part); }
|
||||||
}, ct);
|
}, ct);
|
||||||
|
|
||||||
/// <summary>Перечислить узлы дерева построения по порядку.</summary>
|
/// <summary>Перечислить узлы дерева построения по порядку.</summary>
|
||||||
@@ -78,7 +82,7 @@ public sealed class ModelInspectionService
|
|||||||
{
|
{
|
||||||
var part = GetTopPart();
|
var part = GetTopPart();
|
||||||
try { return ReadFeatures(part); }
|
try { return ReadFeatures(part); }
|
||||||
finally { Release(part); }
|
finally { ComHelper.Release(part); }
|
||||||
}, ct);
|
}, ct);
|
||||||
|
|
||||||
/// <summary>Перечислить тела детали.</summary>
|
/// <summary>Перечислить тела детали.</summary>
|
||||||
@@ -87,7 +91,7 @@ public sealed class ModelInspectionService
|
|||||||
{
|
{
|
||||||
var part = GetTopPart();
|
var part = GetTopPart();
|
||||||
try { return ReadBodies(part); }
|
try { return ReadBodies(part); }
|
||||||
finally { Release(part); }
|
finally { ComHelper.Release(part); }
|
||||||
}, ct);
|
}, ct);
|
||||||
|
|
||||||
/// <summary>Перечислить переменные модели.</summary>
|
/// <summary>Перечислить переменные модели.</summary>
|
||||||
@@ -96,7 +100,7 @@ public sealed class ModelInspectionService
|
|||||||
{
|
{
|
||||||
var part = GetTopPart();
|
var part = GetTopPart();
|
||||||
try { return ReadVariables(part); }
|
try { return ReadVariables(part); }
|
||||||
finally { Release(part); }
|
finally { ComHelper.Release(part); }
|
||||||
}, ct);
|
}, ct);
|
||||||
|
|
||||||
/// <summary>Детали грани по индексу из list_faces: тип, площадь, нормаль, радиус, число рёбер.</summary>
|
/// <summary>Детали грани по индексу из list_faces: тип, площадь, нормаль, радиус, число рёбер.</summary>
|
||||||
@@ -117,14 +121,14 @@ public sealed class ModelInspectionService
|
|||||||
return new FaceDetail
|
return new FaceDetail
|
||||||
{
|
{
|
||||||
Index = index,
|
Index = index,
|
||||||
Type = FaceType(def),
|
Type = GeomClassifiers.FaceType(def),
|
||||||
Area = area,
|
Area = area,
|
||||||
Normal = FaceNormal(def),
|
Normal = FaceNormal(def),
|
||||||
Radius = radius,
|
Radius = radius,
|
||||||
EdgeCount = edgeCount,
|
EdgeCount = edgeCount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
finally { Release(part); }
|
finally { ComHelper.Release(part); }
|
||||||
}, ct);
|
}, ct);
|
||||||
|
|
||||||
/// <summary>Детали ребра по индексу из list_edges: тип, длина, смежные грани, концевые вершины.</summary>
|
/// <summary>Детали ребра по индексу из list_edges: тип, длина, смежные грани, концевые вершины.</summary>
|
||||||
@@ -139,13 +143,13 @@ public sealed class ModelInspectionService
|
|||||||
|
|
||||||
double len = 0; try { len = def.GetLength(MixMm); } catch { }
|
double len = 0; try { len = def.GetLength(MixMm); } catch { }
|
||||||
string? f1 = null, f2 = null;
|
string? f1 = null, f2 = null;
|
||||||
try { if (def.GetAdjacentFace(true) is ksFaceDefinition a) f1 = FaceType(a); } catch { }
|
try { if (def.GetAdjacentFace(true) is ksFaceDefinition a) f1 = GeomClassifiers.FaceType(a); } catch { }
|
||||||
try { if (def.GetAdjacentFace(false) is ksFaceDefinition b) f2 = FaceType(b); } catch { }
|
try { if (def.GetAdjacentFace(false) is ksFaceDefinition b) f2 = GeomClassifiers.FaceType(b); } catch { }
|
||||||
|
|
||||||
return new EdgeDetail
|
return new EdgeDetail
|
||||||
{
|
{
|
||||||
Index = index,
|
Index = index,
|
||||||
Type = EdgeType(def),
|
Type = GeomClassifiers.EdgeType(def),
|
||||||
Length = len,
|
Length = len,
|
||||||
AdjacentFace1 = f1,
|
AdjacentFace1 = f1,
|
||||||
AdjacentFace2 = f2,
|
AdjacentFace2 = f2,
|
||||||
@@ -153,7 +157,7 @@ public sealed class ModelInspectionService
|
|||||||
End = Vertex(def, false),
|
End = Vertex(def, false),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
finally { Release(part); }
|
finally { ComHelper.Release(part); }
|
||||||
}, ct);
|
}, ct);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -186,7 +190,7 @@ public sealed class ModelInspectionService
|
|||||||
AngleValid = angleValid,
|
AngleValid = angleValid,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
finally { Release(part); }
|
finally { ComHelper.Release(part); }
|
||||||
}, ct);
|
}, ct);
|
||||||
|
|
||||||
// ── чтение данных (на STA-потоке) ─────────────────────────────────────
|
// ── чтение данных (на STA-потоке) ─────────────────────────────────────
|
||||||
@@ -264,11 +268,11 @@ public sealed class ModelInspectionService
|
|||||||
if (vc.GetByIndex(i) is not ksVariable v) continue;
|
if (vc.GetByIndex(i) is not ksVariable v) continue;
|
||||||
list.Add(new VariableInfo
|
list.Add(new VariableInfo
|
||||||
{
|
{
|
||||||
Name = SafeStr(() => v.name),
|
Name = ComHelper.SafeStr(() => v.name),
|
||||||
Expression = SafeStr(() => v.Expression),
|
Expression = ComHelper.SafeStr(() => v.Expression),
|
||||||
Value = SafeNum(() => v.value),
|
Value = ComHelper.SafeNum(() => v.value),
|
||||||
External = SafeBool(() => v.external),
|
External = ComHelper.SafeBool(() => v.external),
|
||||||
Information = SafeBool(() => v.Information),
|
Information = ComHelper.SafeBool(() => v.Information),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
@@ -280,10 +284,12 @@ public sealed class ModelInspectionService
|
|||||||
var edgeTypes = new List<string>();
|
var edgeTypes = new List<string>();
|
||||||
if (Faces(part) is { } faces)
|
if (Faces(part) is { } faces)
|
||||||
for (int i = 0; i < faces.GetCount(); i++)
|
for (int i = 0; i < faces.GetCount(); i++)
|
||||||
if (faces.GetByIndex(i) is ksEntity e && e.GetDefinition() is ksFaceDefinition d) faceTypes.Add(FaceType(d));
|
if (faces.GetByIndex(i) is ksEntity e && e.GetDefinition() is ksFaceDefinition d)
|
||||||
|
faceTypes.Add(GeomClassifiers.FaceType(d));
|
||||||
if (Edges(part) is { } edges)
|
if (Edges(part) is { } edges)
|
||||||
for (int i = 0; i < edges.GetCount(); i++)
|
for (int i = 0; i < edges.GetCount(); i++)
|
||||||
if (edges.GetByIndex(i) is ksEntity e && e.GetDefinition() is ksEdgeDefinition d) edgeTypes.Add(EdgeType(d));
|
if (edges.GetByIndex(i) is ksEntity e && e.GetDefinition() is ksEdgeDefinition d)
|
||||||
|
edgeTypes.Add(GeomClassifiers.EdgeType(d));
|
||||||
return (faceTypes, edgeTypes);
|
return (faceTypes, edgeTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,16 +355,43 @@ public sealed class ModelInspectionService
|
|||||||
{
|
{
|
||||||
var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D
|
var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D
|
||||||
?? throw new InvalidOperationException("Нет активного 3D-документа.");
|
?? throw new InvalidOperationException("Нет активного 3D-документа.");
|
||||||
return doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart
|
// Освобождаем RCW документа сразу после получения ksPart — part имеет собственный ref.
|
||||||
?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart).");
|
try
|
||||||
|
{
|
||||||
|
return doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart
|
||||||
|
?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart).");
|
||||||
|
}
|
||||||
|
finally { ComHelper.Release(doc3d); }
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ActiveDocName()
|
/// <summary>Имя и локализованный тип активного документа. Отдельный API7-вызов от GetTopPart.</summary>
|
||||||
|
private (string name, string typeName) ActiveDocInfo()
|
||||||
{
|
{
|
||||||
var doc = _session.Application.ActiveDocument;
|
var doc = _session.Application.ActiveDocument;
|
||||||
try { return doc?.Name ?? ""; }
|
try
|
||||||
catch { return ""; }
|
{
|
||||||
finally { Release(doc); }
|
string name = ComHelper.SafeStr(() => doc?.Name);
|
||||||
|
string typeName = "деталь";
|
||||||
|
if (doc is not null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
typeName = DocumentTypes.FromEnum(doc.DocumentType) switch
|
||||||
|
{
|
||||||
|
KompasDocumentType.Assembly => "сборка",
|
||||||
|
KompasDocumentType.Drawing => "чертёж",
|
||||||
|
KompasDocumentType.Fragment => "фрагмент",
|
||||||
|
KompasDocumentType.Specification => "спецификация",
|
||||||
|
KompasDocumentType.Text => "текст",
|
||||||
|
_ => "деталь",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
return (name, typeName);
|
||||||
|
}
|
||||||
|
catch { return ("", "деталь"); }
|
||||||
|
finally { ComHelper.Release(doc); }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ksEntityCollection? Faces(ksPart part) => part.EntityCollection((short)Obj3dType.o3d_face) as ksEntityCollection;
|
private static ksEntityCollection? Faces(ksPart part) => part.EntityCollection((short)Obj3dType.o3d_face) as ksEntityCollection;
|
||||||
@@ -382,44 +415,11 @@ public sealed class ModelInspectionService
|
|||||||
?? throw new InvalidOperationException($"Ребро[{index}] не приводится к ksEdgeDefinition.");
|
?? throw new InvalidOperationException($"Ребро[{index}] не приводится к ksEdgeDefinition.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FaceType(ksFaceDefinition d)
|
|
||||||
{
|
|
||||||
try { if (d.IsPlanar()) return "plane"; } catch { }
|
|
||||||
try { if (d.IsCylinder()) return "cylinder"; } catch { }
|
|
||||||
try { if (d.IsCone()) return "cone"; } catch { }
|
|
||||||
try { if (d.IsSphere()) return "sphere"; } catch { }
|
|
||||||
try { if (d.IsTorus()) return "torus"; } catch { }
|
|
||||||
try { if (d.IsNurbsSurface()) return "nurbs"; } catch { }
|
|
||||||
return "other";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string EdgeType(ksEdgeDefinition d)
|
|
||||||
{
|
|
||||||
try { if (d.IsLineSeg()) return "line"; } catch { }
|
|
||||||
try { if (d.IsCircle()) return "circle"; } catch { }
|
|
||||||
try { if (d.IsArc()) return "arc"; } catch { }
|
|
||||||
try { if (d.IsEllipseArc()) return "ellipse_arc"; } catch { }
|
|
||||||
try { if (d.IsEllipse()) return "ellipse"; } catch { }
|
|
||||||
try { if (d.IsNurbs()) return "nurbs"; } catch { }
|
|
||||||
return "other";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundingBox Box(double x1, double y1, double z1, double x2, double y2, double z2) => new()
|
private static BoundingBox Box(double x1, double y1, double z1, double x2, double y2, double z2) => new()
|
||||||
{
|
{
|
||||||
MinX = Math.Min(x1, x2), MinY = Math.Min(y1, y2), MinZ = Math.Min(z1, z2),
|
MinX = Math.Min(x1, x2), MinY = Math.Min(y1, y2), MinZ = Math.Min(z1, z2),
|
||||||
MaxX = Math.Max(x1, x2), MaxY = Math.Max(y1, y2), MaxZ = Math.Max(z1, z2),
|
MaxX = Math.Max(x1, x2), MaxY = Math.Max(y1, y2), MaxZ = Math.Max(z1, z2),
|
||||||
};
|
};
|
||||||
|
|
||||||
private static string SafeStr(Func<string?> g) { try { return g() ?? ""; } catch { return ""; } }
|
|
||||||
private static double SafeNum(Func<double> g) { try { return g(); } catch { return 0; } }
|
|
||||||
private static bool SafeBool(Func<bool> g) { try { return g(); } catch { return false; } }
|
|
||||||
private static string Inv(FormattableString fs) => FormattableString.Invariant(fs);
|
private static string Inv(FormattableString fs) => FormattableString.Invariant(fs);
|
||||||
|
|
||||||
private static void Release(object? com)
|
|
||||||
{
|
|
||||||
if (com is not null && Marshal.IsComObject(com))
|
|
||||||
{
|
|
||||||
try { Marshal.ReleaseComObject(com); } catch { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
using Kompas.Mcp.Core.Interop;
|
||||||
using Kompas.Mcp.Core.Threading;
|
using Kompas.Mcp.Core.Threading;
|
||||||
using Kompas6API5;
|
using Kompas6API5;
|
||||||
using Kompas6Constants3D;
|
using Kompas6Constants3D;
|
||||||
@@ -14,6 +15,9 @@ public sealed class QueryService
|
|||||||
// Иначе КОМПАС вернёт значения в единицах по умолчанию (см/г).
|
// Иначе КОМПАС вернёт значения в единицах по умолчанию (см/г).
|
||||||
private const int MixMmKg = 0x1 | 0x10;
|
private const int MixMmKg = 0x1 | 0x10;
|
||||||
|
|
||||||
|
// ST_MIX_MM (0x1) — площади граней / длины рёбер в мм.
|
||||||
|
private const uint MixMm = 0x1;
|
||||||
|
|
||||||
private readonly KompasSession _session;
|
private readonly KompasSession _session;
|
||||||
private readonly KompasDispatcher _dispatcher;
|
private readonly KompasDispatcher _dispatcher;
|
||||||
|
|
||||||
@@ -33,11 +37,7 @@ public sealed class QueryService
|
|||||||
public Task<PartInfo> GetPartInfoAsync(CancellationToken ct = default)
|
public Task<PartInfo> GetPartInfoAsync(CancellationToken ct = default)
|
||||||
=> _dispatcher.InvokeAsync(() =>
|
=> _dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D
|
var part = GetTopPart();
|
||||||
?? throw new InvalidOperationException("Нет активного 3D-документа.");
|
|
||||||
var part = doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart
|
|
||||||
?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart).");
|
|
||||||
|
|
||||||
ksMassInertiaParam? mass = null;
|
ksMassInertiaParam? mass = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -56,8 +56,8 @@ public sealed class QueryService
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (mass is not null && Marshal.IsComObject(mass)) Marshal.ReleaseComObject(mass);
|
ComHelper.Release(mass);
|
||||||
if (Marshal.IsComObject(part)) Marshal.ReleaseComObject(part);
|
ComHelper.Release(part);
|
||||||
}
|
}
|
||||||
}, ct);
|
}, ct);
|
||||||
|
|
||||||
@@ -83,13 +83,22 @@ public sealed class QueryService
|
|||||||
bool gab = false;
|
bool gab = false;
|
||||||
try { gab = p.GetGabarit(false, false, out x1, out y1, out z1, out x2, out y2, out z2); }
|
try { gab = p.GetGabarit(false, false, out x1, out y1, out z1, out x2, out y2, out z2); }
|
||||||
catch { /* габарит может быть недоступен у отдельных компонентов */ }
|
catch { /* габарит может быть недоступен у отдельных компонентов */ }
|
||||||
|
|
||||||
|
// Inline safe-геттеры: избегаем лямбда-аллокаций в горячем цикле по компонентам.
|
||||||
|
string name = "", marking = "", fileName = "";
|
||||||
|
bool isDetail = false;
|
||||||
|
try { name = p.Name ?? ""; } catch { }
|
||||||
|
try { marking = p.Marking ?? ""; } catch { }
|
||||||
|
try { fileName = p.FileName ?? ""; } catch { }
|
||||||
|
try { isDetail = p.Detail; } catch { }
|
||||||
|
|
||||||
list.Add(new ComponentInfo
|
list.Add(new ComponentInfo
|
||||||
{
|
{
|
||||||
Index = i,
|
Index = i,
|
||||||
Name = SafeGet(() => p.Name),
|
Name = name,
|
||||||
Marking = SafeGet(() => p.Marking),
|
Marking = marking,
|
||||||
FileName = SafeGet(() => p.FileName),
|
FileName = fileName,
|
||||||
IsDetail = SafeBool(() => p.Detail),
|
IsDetail = isDetail,
|
||||||
SizeX = gab ? Math.Abs(x2 - x1) : 0,
|
SizeX = gab ? Math.Abs(x2 - x1) : 0,
|
||||||
SizeY = gab ? Math.Abs(y2 - y1) : 0,
|
SizeY = gab ? Math.Abs(y2 - y1) : 0,
|
||||||
SizeZ = gab ? Math.Abs(z2 - z1) : 0,
|
SizeZ = gab ? Math.Abs(z2 - z1) : 0,
|
||||||
@@ -98,27 +107,11 @@ public sealed class QueryService
|
|||||||
return list;
|
return list;
|
||||||
}, ct);
|
}, ct);
|
||||||
|
|
||||||
private static string SafeGet(Func<string?> get)
|
|
||||||
{
|
|
||||||
try { return get() ?? string.Empty; } catch { return string.Empty; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool SafeBool(Func<bool> get)
|
|
||||||
{
|
|
||||||
try { return get(); } catch { return false; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ST_MIX_MM (0x1) — площади граней в мм².
|
|
||||||
private const uint MixMm = 0x1;
|
|
||||||
|
|
||||||
/// <summary>Перечислить грани активной детали: индекс, тип поверхности и площадь (мм²).</summary>
|
/// <summary>Перечислить грани активной детали: индекс, тип поверхности и площадь (мм²).</summary>
|
||||||
public Task<IReadOnlyList<FaceInfo>> ListFacesAsync(CancellationToken ct = default)
|
public Task<IReadOnlyList<FaceInfo>> ListFacesAsync(CancellationToken ct = default)
|
||||||
=> _dispatcher.InvokeAsync<IReadOnlyList<FaceInfo>>(() =>
|
=> _dispatcher.InvokeAsync<IReadOnlyList<FaceInfo>>(() =>
|
||||||
{
|
{
|
||||||
var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D
|
var part = GetTopPart();
|
||||||
?? throw new InvalidOperationException("Нет активного 3D-документа.");
|
|
||||||
var part = doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart
|
|
||||||
?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart).");
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var faces = part.EntityCollection((short)Obj3dType.o3d_face) as ksEntityCollection
|
var faces = part.EntityCollection((short)Obj3dType.o3d_face) as ksEntityCollection
|
||||||
@@ -133,37 +126,20 @@ public sealed class QueryService
|
|||||||
list.Add(new FaceInfo
|
list.Add(new FaceInfo
|
||||||
{
|
{
|
||||||
Index = i,
|
Index = i,
|
||||||
Type = FaceType(def),
|
Type = GeomClassifiers.FaceType(def),
|
||||||
Area = def.GetArea(MixMm),
|
Area = def.GetArea(MixMm),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
finally
|
finally { ComHelper.Release(part); }
|
||||||
{
|
|
||||||
if (Marshal.IsComObject(part)) Marshal.ReleaseComObject(part);
|
|
||||||
}
|
|
||||||
}, ct);
|
}, ct);
|
||||||
|
|
||||||
private static string FaceType(ksFaceDefinition def)
|
|
||||||
{
|
|
||||||
if (def.IsPlanar()) return "plane";
|
|
||||||
if (def.IsCylinder()) return "cylinder";
|
|
||||||
if (def.IsCone()) return "cone";
|
|
||||||
if (def.IsSphere()) return "sphere";
|
|
||||||
if (def.IsTorus()) return "torus";
|
|
||||||
if (def.IsNurbsSurface()) return "nurbs";
|
|
||||||
return "other";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Перечислить рёбра активной детали: индекс, тип кривой и длину (мм).</summary>
|
/// <summary>Перечислить рёбра активной детали: индекс, тип кривой и длину (мм).</summary>
|
||||||
public Task<IReadOnlyList<EdgeInfo>> ListEdgesAsync(CancellationToken ct = default)
|
public Task<IReadOnlyList<EdgeInfo>> ListEdgesAsync(CancellationToken ct = default)
|
||||||
=> _dispatcher.InvokeAsync<IReadOnlyList<EdgeInfo>>(() =>
|
=> _dispatcher.InvokeAsync<IReadOnlyList<EdgeInfo>>(() =>
|
||||||
{
|
{
|
||||||
var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D
|
var part = GetTopPart();
|
||||||
?? throw new InvalidOperationException("Нет активного 3D-документа.");
|
|
||||||
var part = doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart
|
|
||||||
?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart).");
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var edges = part.EntityCollection((short)Obj3dType.o3d_edge) as ksEntityCollection
|
var edges = part.EntityCollection((short)Obj3dType.o3d_edge) as ksEntityCollection
|
||||||
@@ -178,37 +154,20 @@ public sealed class QueryService
|
|||||||
list.Add(new EdgeInfo
|
list.Add(new EdgeInfo
|
||||||
{
|
{
|
||||||
Index = i,
|
Index = i,
|
||||||
Type = EdgeType(def),
|
Type = GeomClassifiers.EdgeType(def),
|
||||||
Length = def.GetLength(MixMm),
|
Length = def.GetLength(MixMm),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
finally
|
finally { ComHelper.Release(part); }
|
||||||
{
|
|
||||||
if (Marshal.IsComObject(part)) Marshal.ReleaseComObject(part);
|
|
||||||
}
|
|
||||||
}, ct);
|
}, ct);
|
||||||
|
|
||||||
private static string EdgeType(ksEdgeDefinition def)
|
|
||||||
{
|
|
||||||
if (def.IsLineSeg()) return "line";
|
|
||||||
if (def.IsCircle()) return "circle";
|
|
||||||
if (def.IsArc()) return "arc";
|
|
||||||
if (def.IsEllipseArc()) return "ellipse_arc";
|
|
||||||
if (def.IsEllipse()) return "ellipse";
|
|
||||||
if (def.IsNurbs()) return "nurbs";
|
|
||||||
return "other";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Габаритный параллелепипед активной детали (по телам), мм.</summary>
|
/// <summary>Габаритный параллелепипед активной детали (по телам), мм.</summary>
|
||||||
public Task<BoundingBox> GetBoundingBoxAsync(CancellationToken ct = default)
|
public Task<BoundingBox> GetBoundingBoxAsync(CancellationToken ct = default)
|
||||||
=> _dispatcher.InvokeAsync(() =>
|
=> _dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D
|
var part = GetTopPart();
|
||||||
?? throw new InvalidOperationException("Нет активного 3D-документа.");
|
|
||||||
var part = doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart
|
|
||||||
?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart).");
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// full=false → только тела (без вспомогательной геометрии); customizable игнорируется.
|
// full=false → только тела (без вспомогательной геометрии); customizable игнорируется.
|
||||||
@@ -223,9 +182,24 @@ public sealed class QueryService
|
|||||||
MaxX = Math.Max(x1, x2), MaxY = Math.Max(y1, y2), MaxZ = Math.Max(z1, z2),
|
MaxX = Math.Max(x1, x2), MaxY = Math.Max(y1, y2), MaxZ = Math.Max(z1, z2),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
finally
|
finally { ComHelper.Release(part); }
|
||||||
{
|
|
||||||
if (Marshal.IsComObject(part)) Marshal.ReleaseComObject(part);
|
|
||||||
}
|
|
||||||
}, ct);
|
}, ct);
|
||||||
|
|
||||||
|
// ── низкоуровневые помощники ──────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить вершинный компонент активной детали. Освобождает RCW doc3d сразу после
|
||||||
|
/// GetPart — ksPart имеет собственный COM-счётчик ссылок.
|
||||||
|
/// </summary>
|
||||||
|
private ksPart GetTopPart()
|
||||||
|
{
|
||||||
|
var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D
|
||||||
|
?? throw new InvalidOperationException("Нет активного 3D-документа.");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart
|
||||||
|
?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart).");
|
||||||
|
}
|
||||||
|
finally { ComHelper.Release(doc3d); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,18 @@ public static class RasterImageFormats
|
|||||||
_ => "application/octet-stream",
|
_ => "application/octet-stream",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>Расширение файла без точки — постоянная строка без аллокации (в отличие от ToString().ToLower()).</summary>
|
||||||
|
public static string FileExtension(this RasterImageFormat format) => format switch
|
||||||
|
{
|
||||||
|
RasterImageFormat.Bmp => "bmp",
|
||||||
|
RasterImageFormat.Gif => "gif",
|
||||||
|
RasterImageFormat.Jpg => "jpg",
|
||||||
|
RasterImageFormat.Png => "png",
|
||||||
|
RasterImageFormat.Tif => "tif",
|
||||||
|
RasterImageFormat.Tga => "tga",
|
||||||
|
_ => format.ToString().ToLowerInvariant(),
|
||||||
|
};
|
||||||
|
|
||||||
public static RasterImageFormat Parse(string value) => value.Trim().ToLowerInvariant() switch
|
public static RasterImageFormat Parse(string value) => value.Trim().ToLowerInvariant() switch
|
||||||
{
|
{
|
||||||
"png" => RasterImageFormat.Png,
|
"png" => RasterImageFormat.Png,
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public sealed class SnapshotService
|
|||||||
param.returnResultAsArrayBytes = false;
|
param.returnResultAsArrayBytes = false;
|
||||||
|
|
||||||
var outFile = saveToFile ?? Path.Combine(
|
var outFile = saveToFile ?? Path.Combine(
|
||||||
Path.GetTempPath(), $"kompas-snap-{Guid.NewGuid():N}.{format.ToString().ToLowerInvariant()}");
|
Path.GetTempPath(), $"kompas-snap-{Guid.NewGuid():N}.{format.FileExtension()}");
|
||||||
var deleteAfter = saveToFile is null;
|
var deleteAfter = saveToFile is null;
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|||||||
Reference in New Issue
Block a user