From af4b1538ab3f0e5b3fbcba22f288e0ac293d7563 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Thu, 22 Jan 2026 20:28:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=B7=AF=E5=BE=84=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=B7=A5=E5=85=B7=E7=B1=BB=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=88=AA=E5=9B=BE=E7=94=9F=E6=88=90=E5=92=8C=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E5=8A=9F=E8=83=BD=EF=BC=9B=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=A2=B0=E6=92=9E=E6=8A=A5=E5=91=8A=E5=92=8C=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E5=AF=B9=E8=AF=9D=E6=A1=86=EF=BC=8C=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=88=AA=E5=9B=BE=E9=A2=84=E8=A7=88=E5=92=8C=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E6=88=AA=E5=9B=BE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NavisworksTransportPlugin.csproj | 1 + doc/requirement/todo_features.md | 8 + .../GenerateCollisionReportCommand.cs | 34 ++++ .../ViewModels/CollisionReportViewModel.cs | 141 +++++++++++++- src/UI/WPF/Views/CollisionReportDialog.xaml | 31 +++ .../Views/GenerateNavigationMapDialog.xaml | 4 +- .../Views/GenerateNavigationMapDialog.xaml.cs | 2 +- src/Utils/PathHelper.cs | 179 ++++++++++++++++++ 8 files changed, 394 insertions(+), 6 deletions(-) create mode 100644 src/Utils/PathHelper.cs diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj index aa9703b..63de6d3 100644 --- a/NavisworksTransportPlugin.csproj +++ b/NavisworksTransportPlugin.csproj @@ -283,6 +283,7 @@ + diff --git a/doc/requirement/todo_features.md b/doc/requirement/todo_features.md index 9b7750a..f6fc845 100644 --- a/doc/requirement/todo_features.md +++ b/doc/requirement/todo_features.md @@ -2,6 +2,14 @@ ## 功能点 +### [2026/1/18] + +1. [x] (功能)实现吊装路径,支持3步吊运过程 +2. [x] (功能)给移动到起点的虚拟车辆或物体增加调整角度能力 +3. [x] (优化)在自动路径规划的几何体获取过程中,过滤隐藏项,提高性能 +4. [x] (功能)在碰撞检测报告中,增加碰撞结果截图 +5. [x] (功能)实现多条路径碰撞检测的批处理 + ### [2026/1/13] 1. [x] (优化)检查API调用的线程安全问题,提高插件的稳定性 diff --git a/src/Commands/GenerateCollisionReportCommand.cs b/src/Commands/GenerateCollisionReportCommand.cs index 226c7d8..8b42bf1 100644 --- a/src/Commands/GenerateCollisionReportCommand.cs +++ b/src/Commands/GenerateCollisionReportCommand.cs @@ -86,6 +86,12 @@ namespace NavisworksTransport.Commands public int FrameRate { get; set; } public double Duration { get; set; } public double DetectionGap { get; set; } + + // 新增:截图信息 + public string ScreenshotPath { get; set; } + public string ScreenshotFormat { get; set; } + public int ScreenshotWidth { get; set; } + public int ScreenshotHeight { get; set; } } /// @@ -228,6 +234,34 @@ namespace NavisworksTransport.Commands } } + UpdateProgress(95, "生成碰撞场景截图..."); + + // 自动生成默认截图(1920x1080, JPG格式) + try + { + string screenshotPath = PathHelper.GenerateSceneScreenshot( + result.PathName ?? "collision", + 1920, + 1080, + System.Drawing.Imaging.ImageFormat.Jpeg, + "collision" + ); + + if (screenshotPath != null) + { + result.ScreenshotPath = screenshotPath; + result.ScreenshotFormat = "JPG"; + result.ScreenshotWidth = 1920; + result.ScreenshotHeight = 1080; + LogManager.Info($"碰撞场景截图已自动生成: {screenshotPath}"); + } + } + catch (Exception ex) + { + LogManager.Error($"自动生成碰撞场景截图失败: {ex.Message}"); + // 截图失败不影响报告生成 + } + UpdateProgress(100, "报告生成完成"); // 显示报告(UI线程) diff --git a/src/UI/WPF/ViewModels/CollisionReportViewModel.cs b/src/UI/WPF/ViewModels/CollisionReportViewModel.cs index b848749..6fce5f7 100644 --- a/src/UI/WPF/ViewModels/CollisionReportViewModel.cs +++ b/src/UI/WPF/ViewModels/CollisionReportViewModel.cs @@ -9,6 +9,8 @@ using System.IO; using System.Text; using Microsoft.Win32; using System.Threading.Tasks; +using System.Windows.Media.Imaging; +using NavisworksTransport.UI.WPF.Views; namespace NavisworksTransport.UI.WPF.ViewModels { @@ -80,6 +82,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels private int _frameRate; private double _duration; private double _detectionGap; + private CollisionReportResult _currentReport; #endregion @@ -239,6 +242,46 @@ namespace NavisworksTransport.UI.WPF.ViewModels set => SetProperty(ref _detectionGap, value); } + /// + /// 当前报告结果 + /// + public CollisionReportResult CurrentReport + { + get => _currentReport; + set => SetProperty(ref _currentReport, value); + } + + /// + /// 截图预览图像源 + /// + public BitmapImage ScreenshotPreviewSource + { + get + { + if (string.IsNullOrEmpty(CurrentReport?.ScreenshotPath) || + !File.Exists(CurrentReport.ScreenshotPath)) + { + return null; + } + + try + { + var bitmap = new BitmapImage(); + bitmap.BeginInit(); + bitmap.CacheOption = BitmapCacheOption.OnLoad; + bitmap.UriSource = new Uri(CurrentReport.ScreenshotPath); + bitmap.EndInit(); + bitmap.Freeze(); // 防止跨线程访问问题 + return bitmap; + } + catch (Exception ex) + { + LogManager.Error($"加载截图预览失败: {ex.Message}"); + return null; + } + } + } + #endregion #region 命令属性 @@ -253,6 +296,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels /// public ICommand CloseCommand { get; } + /// + /// 重新截图命令 + /// + public ICommand RetakeScreenshotCommand { get; } /// /// 高亮碰撞对象命令 @@ -275,6 +322,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels ExportReportCommand = new RelayCommand(async () => await ExportReportAsync(), () => !IsGenerating && HasCollisions); CloseCommand = new RelayCommand(CloseWindow); HighlightCollisionCommand = new RelayCommand(HighlightCollision, item => item?.CollisionData != null); + RetakeScreenshotCommand = new RelayCommand(ExecuteRetakeScreenshot, CanExecuteRetakeScreenshot); // 初始化状态 ReportStatus = CollisionReportStatus.Generating; @@ -299,6 +347,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels return; } + // 保存当前报告结果 + CurrentReport = reportResult; + try { IsGenerating = true; @@ -572,7 +623,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels { await Task.Run(() => { - var content = GenerateExportContent(Path.GetExtension(saveFileDialog.FileName).ToLower()); + var content = GenerateExportContent(Path.GetExtension(saveFileDialog.FileName).ToLower(), saveFileDialog.FileName); File.WriteAllText(saveFileDialog.FileName, content, Encoding.UTF8); }); @@ -590,11 +641,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels /// /// 生成导出内容 /// - private string GenerateExportContent(string fileExtension) + private string GenerateExportContent(string fileExtension, string filePath = null) { if (fileExtension == ".html") { - return GenerateHtmlReport(); + return GenerateHtmlReport(filePath); } else { @@ -703,7 +754,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels /// /// 生成HTML报告 /// - private string GenerateHtmlReport() + private string GenerateHtmlReport(string htmlFilePath = null) { var html = new StringBuilder(); var now = DateTime.Now; @@ -731,6 +782,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels html.AppendLine(".collision-status { display: inline-block; padding: 3px 8px; border-radius: 12px; font-size: 11px; color: white; margin-bottom: 8px; }"); html.AppendLine(".status-new { background-color: #ff6b6b; } .status-active { background-color: #ffa500; }"); html.AppendLine(".status-reviewed { background-color: #87ceeb; } .status-approved { background-color: #98fb98; } .status-resolved { background-color: #90ee90; }"); + html.AppendLine(".screenshot-section { margin: 20px 0; padding: 15px; background-color: #f5f5f5; border-radius: 5px; }"); + html.AppendLine(".screenshot-container { text-align: center; }"); + html.AppendLine(".screenshot-image { max-width: 100%; height: auto; border: 1px solid #ddd; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }"); + html.AppendLine(".screenshot-info { margin-top: 10px; font-size: 12px; color: #666; }"); html.AppendLine(""); html.AppendLine(""); @@ -776,6 +831,25 @@ namespace NavisworksTransport.UI.WPF.ViewModels html.AppendLine(""); html.AppendLine(""); + // 碰撞场景截图 + if (!string.IsNullOrEmpty(CurrentReport?.ScreenshotPath) && File.Exists(CurrentReport.ScreenshotPath)) + { + html.AppendLine("碰撞场景截图"); + html.AppendLine(""); + + // 使用 PathHelper 计算相对路径 + string relativePath = PathHelper.GetRelativePath(htmlFilePath, CurrentReport.ScreenshotPath); + + html.AppendLine(""); + html.AppendLine($""); + html.AppendLine(""); + html.AppendLine($"分辨率: {CurrentReport.ScreenshotWidth} x {CurrentReport.ScreenshotHeight}"); + html.AppendLine($"格式: {CurrentReport.ScreenshotFormat}"); + html.AppendLine(""); + html.AppendLine(""); + html.AppendLine(""); + } + // 运动构件信息 if (!string.IsNullOrEmpty(MovingObjectInfo)) { @@ -903,6 +977,65 @@ namespace NavisworksTransport.UI.WPF.ViewModels LogManager.Info("关闭碰撞报告窗口"); } + /// + /// 执行重新截图 + /// + private void ExecuteRetakeScreenshot() + { + try + { + // 复用现有的截图配置对话框 + var dialog = new GenerateNavigationMapDialog(); + dialog.Title = "碰撞场景截图配置"; + + // 尝试设置 Owner(如果主窗口已显示) + var mainWindow = System.Windows.Application.Current.MainWindow; + if (mainWindow != null && mainWindow.IsLoaded) + { + dialog.Owner = mainWindow; + } + + if (dialog.ShowDialog() == true) + { + // 调用 PathHelper 生成截图 + string screenshotPath = PathHelper.GenerateSceneScreenshot( + CurrentReport.PathName ?? "collision", + dialog.ImageWidth, + dialog.ImageHeight, + dialog.ImageFormat, + "collision" + ); + + if (screenshotPath != null) + { + // 更新报告数据 + CurrentReport.ScreenshotPath = screenshotPath; + CurrentReport.ScreenshotFormat = PathHelper.ImageFormatToExtension(dialog.ImageFormat).ToUpper(); + CurrentReport.ScreenshotWidth = dialog.ImageWidth; + CurrentReport.ScreenshotHeight = dialog.ImageHeight; + + // 通知UI更新 + OnPropertyChanged(nameof(CurrentReport)); + OnPropertyChanged(nameof(ScreenshotPreviewSource)); + + LogManager.Info("碰撞场景截图已更新"); + } + } + } + catch (Exception ex) + { + LogManager.Error($"重新截图失败: {ex.Message}"); + System.Windows.MessageBox.Show($"重新截图失败: {ex.Message}", "错误", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); + } } + + /// + /// 检查是否可以执行重新截图 + /// + private bool CanExecuteRetakeScreenshot() + { + return CurrentReport != null; + } + #endregion } } \ No newline at end of file diff --git a/src/UI/WPF/Views/CollisionReportDialog.xaml b/src/UI/WPF/Views/CollisionReportDialog.xaml index 30220d7..51cddc6 100644 --- a/src/UI/WPF/Views/CollisionReportDialog.xaml +++ b/src/UI/WPF/Views/CollisionReportDialog.xaml @@ -269,6 +269,32 @@ NavisworksTransport 碰撞检测报告对话框 - 采用与主界面一致的Nav + + + + + + + + + + + + + + + + + diff --git a/src/UI/WPF/Views/GenerateNavigationMapDialog.xaml b/src/UI/WPF/Views/GenerateNavigationMapDialog.xaml index 57766c0..492a87c 100644 --- a/src/UI/WPF/Views/GenerateNavigationMapDialog.xaml +++ b/src/UI/WPF/Views/GenerateNavigationMapDialog.xaml @@ -20,7 +20,8 @@ NavisworksTransport 生成导航地图对话框 - 采用与主界面一致的Nav Width="460" ResizeMode="NoResize" WindowStartupLocation="CenterOwner" - ShowInTaskbar="False"> + ShowInTaskbar="True" + Topmost="True"> @@ -85,6 +86,7 @@ NavisworksTransport 生成导航地图对话框 - 采用与主界面一致的Nav + diff --git a/src/UI/WPF/Views/GenerateNavigationMapDialog.xaml.cs b/src/UI/WPF/Views/GenerateNavigationMapDialog.xaml.cs index 96262fd..bc84b21 100644 --- a/src/UI/WPF/Views/GenerateNavigationMapDialog.xaml.cs +++ b/src/UI/WPF/Views/GenerateNavigationMapDialog.xaml.cs @@ -60,7 +60,7 @@ namespace NavisworksTransport.UI.WPF.Views // 设置默认选择 FormatComboBox.SelectedIndex = 1; // PNG - PresetSizeComboBox.SelectedIndex = 1; // 1920x1080 + PresetSizeComboBox.SelectedIndex = 2; // 1920x1080 (因为添加了4K选项,索引变为2) WidthTextBox.Text = ImageWidth.ToString(); HeightTextBox.Text = ImageHeight.ToString(); diff --git a/src/Utils/PathHelper.cs b/src/Utils/PathHelper.cs new file mode 100644 index 0000000..f16f3cd --- /dev/null +++ b/src/Utils/PathHelper.cs @@ -0,0 +1,179 @@ +using System; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using Autodesk.Navisworks.Api; + +namespace NavisworksTransport.Utils +{ + /// + /// 路径处理工具类 - 提供文件路径相关的通用方法 + /// + public static class PathHelper + { + /// + /// 获取插件目录路径 + /// + public static string GetPluginDirectory() + { + return Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), + "Autodesk", + "Navisworks Manage 2026", + "plugins", + "NavisworksTransportPlugin" + ); + } + + /// + /// 获取截图目录路径 + /// + public static string GetScreenshotDirectory() + { + return Path.Combine(GetPluginDirectory(), "screenshots"); + } + + /// + /// 确保目录存在,不存在则创建 + /// + public static void EnsureDirectoryExists(string directory) + { + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + } + + /// + /// 清理文件名中的非法字符 + /// + /// 原始文件名 + /// 清理后的文件名 + public static string SanitizeFileName(string fileName) + { + if (string.IsNullOrEmpty(fileName)) + { + return "unnamed"; + } + + var invalidChars = Path.GetInvalidFileNameChars(); + var cleanedFileName = new string(fileName.Where(c => !invalidChars.Contains(c)).ToArray()); + + // 如果清理后为空,返回默认名称 + return string.IsNullOrWhiteSpace(cleanedFileName) ? "unnamed" : cleanedFileName; + } + + /// + /// 生成带时间戳的文件名 + /// + /// 文件名前缀 + /// 文件扩展名(不含点号) + /// 带时间戳的文件名 + public static string GenerateTimestampedFileName(string prefix, string extension) + { + string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); + return $"{prefix}_{timestamp}.{extension}"; + } + + /// + /// 计算从HTML文件到目标文件的相对路径 + /// + /// HTML文件路径 + /// 目标文件路径 + /// 相对路径(使用正斜杠) + public static string GetRelativePath(string fromPath, string toPath) + { + try + { + var fromUri = new Uri(fromPath); + var toUri = new Uri(toPath); + var relativeUri = fromUri.MakeRelativeUri(toUri); + return Uri.UnescapeDataString(relativeUri.ToString()).Replace('\\', '/'); + } + catch (Exception ex) + { + LogManager.Error($"计算相对路径失败: {ex.Message}"); + return toPath; // 失败时返回绝对路径 + } + } + + /// + /// 将ImageFormat转换为文件扩展名 + /// + public static string ImageFormatToExtension(ImageFormat format) + { + if (format.Equals(ImageFormat.Jpeg)) + return "jpg"; + if (format.Equals(ImageFormat.Png)) + return "png"; + if (format.Equals(ImageFormat.Bmp)) + return "bmp"; + return "png"; // 默认 + } + + /// + /// 将文件扩展名转换为ImageFormat + /// + public static ImageFormat ExtensionToImageFormat(string extension) + { + if (string.IsNullOrEmpty(extension)) + return ImageFormat.Png; + + extension = extension.ToLowerInvariant(); + if (extension == ".jpg" || extension == ".jpeg") + return ImageFormat.Jpeg; + if (extension == ".png") + return ImageFormat.Png; + if (extension == ".bmp") + return ImageFormat.Bmp; + return ImageFormat.Png; // 默认 + } + + /// + /// 生成场景截图(通用方法) + /// + /// 场景名称 + /// 图像宽度 + /// 图像高度 + /// 图像格式 + /// 文件名前缀 + /// 截图文件路径,失败返回null + public static string GenerateSceneScreenshot(string sceneName, int width, int height, ImageFormat format, string prefix = "scene") + { + try + { + LogManager.Info($"开始生成场景截图: {prefix}_{sceneName}, 尺寸={width}x{height}, 格式={ImageFormatToExtension(format)}"); + + // 1. 确保截图目录存在 + string screenshotDir = GetScreenshotDirectory(); + EnsureDirectoryExists(screenshotDir); + + // 2. 生成文件名 + string extension = ImageFormatToExtension(format); + string sanitizedName = SanitizeFileName(sceneName); + string filename = GenerateTimestampedFileName($"{prefix}_{sanitizedName}", extension); + string screenshotPath = Path.Combine(screenshotDir, filename); + + // 3. 调用截图生成器(复用 NavigationMapGenerator) + var generator = new Core.NavigationMapGenerator(); + var bitmap = generator.GenerateNavigationMapImage( + ImageGenerationStyle.ScenePlusOverlay, + width, + height, + false + ); + + // 4. 保存截图 + generator.SaveNavigationMapImage(bitmap, screenshotPath, format); + + LogManager.Info($"场景截图已生成: {screenshotPath}"); + return screenshotPath; + } + catch (Exception ex) + { + LogManager.Error($"生成场景截图失败: {ex.Message}", ex); + return null; + } + } + } +} \ No newline at end of file
分辨率: {CurrentReport.ScreenshotWidth} x {CurrentReport.ScreenshotHeight}
格式: {CurrentReport.ScreenshotFormat}