Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 224 additions & 0 deletions src/SpatialViewer.App/CadCompatibilityReportBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
using System.Collections.ObjectModel;
using System.Globalization;
using System.Text.Json;
using SpatialViewer.Formats.Cad;
using SpatialViewer.Formats.Cad.ACadSharp;

namespace SpatialViewer.Product;

internal static class CadCompatibilityReportBuilder
{
public const int SchemaVersion = 1;

private static readonly string[] SafeDocumentMetadataKeys =
[
"Reader",
"ReaderVersion",
"SourceFormat",
"CadVersion",
"Units",
"CustomClassCount",
"CustomEntityCount",
"CustomProxyGraphicEntityCount",
"XiangyuanDetected",
"XiangyuanClassCount",
"XiangyuanEntityCount",
"RawProxyCommandCaptureSupported",
"RawProxyCommandCaptureFailed",
"RawProxyCommandCapturedEntityCount",
"RawProxyCommandMalformedEntityCount",
"RawProxyUnknownCommandEntityCount",
"RawProxyUnknownCommandCount",
"RawProxyUnknownTypeIds"
];

private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
WriteIndented = true
};

public static string Build(CadDocument document)
{
ArgumentNullException.ThrowIfNull(document);

var customEntities = EnumerateEntities(document)
.OfType<CadCustomEntity>()
.ToArray();

var groups = customEntities
.GroupBy(EntityGroupKey.From)
.OrderBy(group => group.Key.Vendor, StringComparer.Ordinal)
.ThenBy(group => group.Key.ApplicationName, StringComparer.OrdinalIgnoreCase)
.ThenBy(group => group.Key.CppClassName, StringComparer.OrdinalIgnoreCase)
.ThenBy(group => group.Key.DxfName, StringComparer.OrdinalIgnoreCase)
.ThenBy(group => group.Key.SourceEntityType, StringComparer.OrdinalIgnoreCase)
.Select(group => BuildGroup(group.Key, group.ToArray()))
.ToArray();

var report = new CadCompatibilityReport(
SchemaVersion,
DateTimeOffset.UtcNow,
AppVersionProvider.Version,
typeof(CadDocument).Assembly.GetName().Version?.ToString() ?? "unknown",
typeof(ACadSharpCadImporter).Assembly.GetName().Version?.ToString() ?? "unknown",
document.SourceFormat,
document.Version,
document.Units.ToString(),
document.CustomClasses.Count,
customEntities.Length,
FilterMetadata(document.Metadata, SafeDocumentMetadataKeys),
groups);

return JsonSerializer.Serialize(report, JsonOptions);
}

private static CadCompatibilityCustomGroup BuildGroup(
EntityGroupKey key,
IReadOnlyList<CadCustomEntity> entities)

Check failure on line 77 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 77 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 77 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 77 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)
{
var primitiveKinds = entities
.SelectMany(entity => entity.ProxyGraphicKinds)
.Where(value => !string.IsNullOrWhiteSpace(value))
.Distinct(StringComparer.Ordinal)
.OrderBy(value => value, StringComparer.Ordinal)
.ToArray();

var commandSignatures = DistinctMetadata(entities, "RawProxyCommandTypeSignature");
var unknownTypeIds = entities
.SelectMany(entity => ParseIntegerList(Metadata(entity, "RawProxyUnknownTypeIds")))
.Distinct()
.OrderBy(value => value)
.ToArray();

return new(
key.DxfName,
key.CppClassName,
key.ApplicationName,
key.SourceEntityType,
key.Vendor,
key.Representation,
entities.Count,
primitiveKinds,
SumMetadata(entities, "ProxyGraphicCount"),
SumMetadata(entities, "ProxyGraphicTranslatedCount"),
SumMetadata(entities, "ProxyGraphicUnsupportedCount"),
SumMetadata(entities, "RawProxyCommandDeclaredCount"),
SumMetadata(entities, "RawProxyCommandScannedCount"),
SumMetadata(entities, "RawProxyCommandKnownCount"),
SumMetadata(entities, "RawProxyCommandUnknownCount"),
entities.Count(entity => MetadataBoolean(entity, "RawProxyCommandMalformed")),
entities.Count(entity => MetadataBoolean(entity, "RawProxyCommandTruncated")),
unknownTypeIds,
commandSignatures);
}

private static IEnumerable<CadEntity> EnumerateEntities(CadDocument document)
{
foreach (var entity in document.ModelSpace) yield return entity;
foreach (var block in document.Blocks)
foreach (var entity in block.Entities)
yield return entity;
foreach (var layout in document.Layouts.Where(layout => layout.IsPaperSpace))
foreach (var entity in layout.Entities)
yield return entity;
}

private static IReadOnlyDictionary<string, string> FilterMetadata(
IReadOnlyDictionary<string, string> source,
IEnumerable<string> allowList)
{
var safe = new SortedDictionary<string, string>(StringComparer.Ordinal);
foreach (var key in allowList)
if (source.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value))
safe[key] = value;
return new ReadOnlyDictionary<string, string>(safe);
}

private static string[] DistinctMetadata(
IEnumerable<CadCustomEntity> entities,

Check failure on line 138 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 138 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 138 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 138 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)
string key)
=> entities
.Select(entity => Metadata(entity, key))
.Where(value => !string.IsNullOrWhiteSpace(value))
.Distinct(StringComparer.Ordinal)
.OrderBy(value => value, StringComparer.Ordinal)
.ToArray();

private static string Metadata(CadCustomEntity entity, string key)

Check failure on line 147 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 147 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 147 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 147 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)
=> entity.Metadata.TryGetValue(key, out var value) ? value : string.Empty;

private static bool MetadataBoolean(CadCustomEntity entity, string key)

Check failure on line 150 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 150 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 150 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 150 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)
=> bool.TryParse(Metadata(entity, key), out var value) && value;

private static long SumMetadata(
IEnumerable<CadCustomEntity> entities,

Check failure on line 154 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 154 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)
string key)
{
long sum = 0;
foreach (var entity in entities)
if (long.TryParse(Metadata(entity, key), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) && value > 0)
sum = checked(sum + value);
return sum;
}

private static IEnumerable<int> ParseIntegerList(string raw)
{
if (string.IsNullOrWhiteSpace(raw)) yield break;
foreach (var token in raw.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
if (int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
yield return value;
}

private sealed record EntityGroupKey(
string DxfName,
string CppClassName,
string ApplicationName,
string SourceEntityType,
string Vendor,
string Representation)
{
public static EntityGroupKey From(CadCustomEntity entity)

Check failure on line 180 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 180 in src/SpatialViewer.App/CadCompatibilityReportBuilder.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The type or namespace name 'CadCustomEntity' could not be found (are you missing a using directive or an assembly reference?)
=> new(
entity.ClassDefinition?.DxfName ?? string.Empty,
entity.ClassDefinition?.CppClassName ?? string.Empty,
entity.ClassDefinition?.ApplicationName ?? string.Empty,
entity.SourceEntityType,
entity.Vendor.ToString(),
entity.Representation.ToString());
}
}

internal sealed record CadCompatibilityReport(
int SchemaVersion,
DateTimeOffset GeneratedUtc,
string AppVersion,
string CadCoreAssemblyVersion,
string CadAdapterAssemblyVersion,
string SourceFormat,
string CadVersion,
string Units,
int CustomClassCount,
int CustomEntityCount,
IReadOnlyDictionary<string, string> AggregateMetadata,
IReadOnlyList<CadCompatibilityCustomGroup> CustomGroups);

internal sealed record CadCompatibilityCustomGroup(
string DxfName,
string CppClassName,
string ApplicationName,
string SourceEntityType,
string Vendor,
string Representation,
int EntityCount,
IReadOnlyList<string> ProxyGraphicKinds,
long ProxyGraphicCommandCount,
long ProxyGraphicTranslatedCount,
long ProxyGraphicUnsupportedCount,
long RawCommandDeclaredCount,
long RawCommandScannedCount,
long RawCommandKnownCount,
long RawCommandUnknownCount,
int RawCommandMalformedEntityCount,
int RawCommandTruncatedEntityCount,
IReadOnlyList<int> RawUnknownTypeIds,
IReadOnlyList<string> RawCommandTypeSignatures);
8 changes: 8 additions & 0 deletions src/SpatialViewer.App/Strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,12 @@
<data name="Update_Download" xml:space="preserve"><value>Download update</value></data>
<data name="Update_Retry" xml:space="preserve"><value>Retry</value></data>
<data name="Update_WaitingRestart" xml:space="preserve"><value>Waiting for restart</value></data>
<data name="CadCompatibilityReportButton.Content" xml:space="preserve"><value>Export CAD compatibility report</value></data>
<data name="Cad_CompatibilityReport_Unavailable" xml:space="preserve"><value>Report is not available yet</value></data>
<data name="Cad_CompatibilityReport_UnavailableMessage" xml:space="preserve"><value>The current drawing has not finished loading.</value></data>
<data name="Cad_CompatibilityReport_Saved" xml:space="preserve"><value>CAD compatibility report exported</value></data>
<data name="Cad_CompatibilityReport_SavedMessage" xml:space="preserve"><value>Report saved to: {0}\nThe file path was copied to the clipboard.</value></data>
<data name="Cad_CompatibilityReport_PathCopied" xml:space="preserve"><value>Compatibility report path copied</value></data>
<data name="Cad_CompatibilityReport_Failed" xml:space="preserve"><value>CAD compatibility report export failed</value></data>
<data name="Cad_CompatibilityReport_FailedMessage" xml:space="preserve"><value>The diagnostic report could not be written. Check local file permissions and try again.</value></data>
</root>
8 changes: 8 additions & 0 deletions src/SpatialViewer.App/Strings/ja-JP/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,12 @@
<data name="Update_Download" xml:space="preserve"><value>更新をダウンロード</value></data>
<data name="Update_Retry" xml:space="preserve"><value>再試行</value></data>
<data name="Update_WaitingRestart" xml:space="preserve"><value>再起動待ち</value></data>
<data name="CadCompatibilityReportButton.Content" xml:space="preserve"><value>CAD 互換性レポートを出力</value></data>
<data name="Cad_CompatibilityReport_Unavailable" xml:space="preserve"><value>レポートを出力できません</value></data>
<data name="Cad_CompatibilityReport_UnavailableMessage" xml:space="preserve"><value>現在の図面はまだ読み込みを完了していません。</value></data>
<data name="Cad_CompatibilityReport_Saved" xml:space="preserve"><value>CAD 互換性レポートを出力しました</value></data>
<data name="Cad_CompatibilityReport_SavedMessage" xml:space="preserve"><value>レポートを保存しました:{0}\nファイルパスをクリップボードにコピーしました。</value></data>
<data name="Cad_CompatibilityReport_PathCopied" xml:space="preserve"><value>互換性レポートのパスをコピーしました</value></data>
<data name="Cad_CompatibilityReport_Failed" xml:space="preserve"><value>CAD 互換性レポートの出力に失敗しました</value></data>
<data name="Cad_CompatibilityReport_FailedMessage" xml:space="preserve"><value>診断レポートを書き込めません。ローカルのファイル権限を確認して再試行してください。</value></data>
</root>
8 changes: 8 additions & 0 deletions src/SpatialViewer.App/Strings/zh-CN/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,12 @@
<data name="Update_Download" xml:space="preserve"><value>下载更新</value></data>
<data name="Update_Retry" xml:space="preserve"><value>重试</value></data>
<data name="Update_WaitingRestart" xml:space="preserve"><value>等待重启</value></data>
<data name="CadCompatibilityReportButton.Content" xml:space="preserve"><value>导出 CAD 兼容性报告</value></data>
<data name="Cad_CompatibilityReport_Unavailable" xml:space="preserve"><value>暂时无法导出报告</value></data>
<data name="Cad_CompatibilityReport_UnavailableMessage" xml:space="preserve"><value>当前图纸尚未完成读取。</value></data>
<data name="Cad_CompatibilityReport_Saved" xml:space="preserve"><value>CAD 兼容性报告已导出</value></data>
<data name="Cad_CompatibilityReport_SavedMessage" xml:space="preserve"><value>报告已保存到:{0}\n文件路径已复制到剪贴板。</value></data>
<data name="Cad_CompatibilityReport_PathCopied" xml:space="preserve"><value>兼容性报告路径已复制</value></data>
<data name="Cad_CompatibilityReport_Failed" xml:space="preserve"><value>CAD 兼容性报告导出失败</value></data>
<data name="Cad_CompatibilityReport_FailedMessage" xml:space="preserve"><value>无法写入诊断报告,请检查本机文件权限后重试。</value></data>
</root>
8 changes: 7 additions & 1 deletion src/SpatialViewer.App/Views/CadViewerView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@
<SplitView.Content>
<SplitView x:Name="RightPaneHost" DisplayMode="Inline" PanePlacement="Right" IsPaneOpen="True" OpenPaneLength="300" CompactPaneLength="0">
<SplitView.Pane>
<Border x:Name="RightPanel" Background="{ThemeResource BgPanelBrush}" BorderBrush="{ThemeResource BorderBrush}" BorderThickness="1,0,0,0" Padding="12"><StackPanel Spacing="10"><TextBlock x:Uid="CadPropertiesPanelTitle" Text="属性" Style="{StaticResource BodyText}" FontWeight="SemiBold" /><TextBlock x:Name="PropertiesEmpty" x:Uid="CadPropertiesEmpty" Text="选择一个对象以查看真实 CAD 属性。" Style="{StaticResource BodyText}" Foreground="{ThemeResource TextSecondaryBrush}" TextWrapping="Wrap" /><ListView x:Name="PropertiesList"><ListView.ItemTemplate><DataTemplate><StackPanel><TextBlock Text="{Binding Label}" Style="{StaticResource MetadataText}" /><TextBlock Text="{Binding Value}" TextWrapping="Wrap" /></StackPanel></DataTemplate></ListView.ItemTemplate></ListView><InfoBar x:Name="DiagnosticsBar" x:Uid="CadDiagnosticsBar" IsOpen="False" Severity="Warning" Title="图纸包含暂不支持的对象" IsClosable="True" /></StackPanel></Border>
<Border x:Name="RightPanel" Background="{ThemeResource BgPanelBrush}" BorderBrush="{ThemeResource BorderBrush}" BorderThickness="1,0,0,0" Padding="12"><StackPanel Spacing="10"><TextBlock x:Uid="CadPropertiesPanelTitle" Text="属性" Style="{StaticResource BodyText}" FontWeight="SemiBold" /><TextBlock x:Name="PropertiesEmpty" x:Uid="CadPropertiesEmpty" Text="选择一个对象以查看真实 CAD 属性。" Style="{StaticResource BodyText}" Foreground="{ThemeResource TextSecondaryBrush}" TextWrapping="Wrap" /><ListView x:Name="PropertiesList"><ListView.ItemTemplate><DataTemplate><StackPanel><TextBlock Text="{Binding Label}" Style="{StaticResource MetadataText}" /><TextBlock Text="{Binding Value}" TextWrapping="Wrap" /></StackPanel></DataTemplate></ListView.ItemTemplate></ListView><InfoBar x:Name="DiagnosticsBar" x:Uid="CadDiagnosticsBar" IsOpen="False" Severity="Warning" Title="图纸包含暂不支持的对象" IsClosable="True" />
<Button x:Name="CompatibilityReportButton"
x:Uid="CadCompatibilityReportButton"
Content="导出 CAD 兼容性报告"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Click="ExportCompatibilityReport_Click" /></StackPanel></Border>
</SplitView.Pane>
<SplitView.Content><controls:CadViewportControl x:Name="Viewport" /></SplitView.Content>
</SplitView>
Expand Down
53 changes: 53 additions & 0 deletions src/SpatialViewer.App/Views/CadViewerView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
using SpatialViewer.Formats.Cad.ACadSharp;
using SpatialViewer.Presentation;
using System.Globalization;
using System.Text;
using System.Text.Json;
using Windows.ApplicationModel.DataTransfer;

namespace SpatialViewer.Product.Views;

Expand Down Expand Up @@ -166,6 +169,56 @@ private void Viewport_SelectionChanged(object? sender, SceneItem? item)

private void Layer_Click(object sender, RoutedEventArgs e) => Viewport.Draw();
private void LayerList_SelectionChanged(object sender, SelectionChangedEventArgs e) { }
private void ExportCompatibilityReport_Click(object sender, RoutedEventArgs e)
{
if (_session.State != DocumentSessionState.Ready || _session.Document is not CadDocument document)
{
DiagnosticsBar.Severity = InfoBarSeverity.Warning;
DiagnosticsBar.Title = T("Cad_CompatibilityReport_Unavailable");
DiagnosticsBar.Message = T("Cad_CompatibilityReport_UnavailableMessage");
DiagnosticsBar.IsOpen = true;
return;
}

try
{
var json = CadCompatibilityReportBuilder.Build(document);
var desktop = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
var root = string.IsNullOrWhiteSpace(desktop)
? Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"SpatialViewer")
: desktop;
var directory = Path.Combine(root, "SpatialViewer Diagnostics");
Directory.CreateDirectory(directory);

var timestamp = DateTimeOffset.Now.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture);
var path = Path.Combine(directory, $"SpatialViewer-CAD-compatibility-{timestamp}.json");
File.WriteAllText(path, json, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));

var clipboard = new DataPackage();
clipboard.SetText(path);
Clipboard.SetContent(clipboard);
Clipboard.Flush();

DiagnosticsBar.Severity = InfoBarSeverity.Success;
DiagnosticsBar.Title = T("Cad_CompatibilityReport_Saved");
DiagnosticsBar.Message = string.Format(
CultureInfo.CurrentCulture,
T("Cad_CompatibilityReport_SavedMessage"),
path);
DiagnosticsBar.IsOpen = true;
ObjectText.Text = T("Cad_CompatibilityReport_PathCopied");
}
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or JsonException)
{
DiagnosticsBar.Severity = InfoBarSeverity.Error;
DiagnosticsBar.Title = T("Cad_CompatibilityReport_Failed");
DiagnosticsBar.Message = T("Cad_CompatibilityReport_FailedMessage");
DiagnosticsBar.IsOpen = true;
}
}

private void Fit_Click(object sender, RoutedEventArgs e) => Viewport.Fit();
private void SelectTool_Click(object sender, RoutedEventArgs e) => SetMode(ViewerMode.Select);
private void PanTool_Click(object sender, RoutedEventArgs e) => SetMode(ViewerMode.Pan);
Expand Down
Loading