增加导出剖面盒相交元素的包围盒信息的功能(用于三维场景重建)

This commit is contained in:
tian 2026-02-12 20:30:00 +08:00
parent 64c6079011
commit 579434fbff
4 changed files with 425 additions and 0 deletions

View File

@ -131,6 +131,8 @@
<Compile Include="src\Core\DocumentStateManager.cs" />
<!-- Core - Virtual Vehicle Management -->
<Compile Include="src\Core\VirtualVehicleManager.cs" />
<!-- Core - Section Box Export -->
<Compile Include="src\Core\SectionBoxExporter.cs" />
<!-- Core - Configuration Management -->
<Compile Include="src\Core\Config\SystemConfig.cs" />
<Compile Include="src\Core\Config\ConfigManager.cs" />

View File

@ -0,0 +1,354 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using Autodesk.Navisworks.Api;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace NavisworksTransport.Core
{
/// <summary>
/// 剖面盒导出器 - 导出剖面盒内的对象包围盒信息到JSON
/// </summary>
public class SectionBoxExporter
{
/// <summary>
/// 导出剖面盒信息到JSON文件
/// </summary>
/// <param name="document">Navisworks文档</param>
/// <returns>导出的文件路径失败返回null</returns>
public string ExportToJson(Document document)
{
if (document == null)
throw new ArgumentNullException(nameof(document));
// 1. 获取当前视点的裁剪平面
var viewpoint = document.CurrentViewpoint.Value;
var clipPlanes = viewpoint.ClipPlanes;
if (clipPlanes == null)
{
MessageBox.Show(
"当前没有活动的剖面盒!\n请先使用 Navisworks 的剖面工具创建剖面盒。",
"提示",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
return null;
}
// 检查是否启用
if (!clipPlanes.Enabled)
{
MessageBox.Show(
"剖面盒未启用!\n请先在菜单【视点】-【启用剖分】,然后在菜单【剖分工具】的【模式】中选择【长方体】。\n【移动】、【缩放】或【旋转】剖面盒到适当的位置或者用【适应选择】调整",
"提示",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
return null;
}
// 检查是否为长方体模式Box排除平面裁剪模式Clip
if (clipPlanes.Mode != ClipPlaneSetMode.Box)
{
MessageBox.Show(
"当前是平面裁剪模式,请切换为剖面盒模式!\n在 Navisworks 剖面工具中选择'Box'模式。",
"提示",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
return null;
}
// 2. 获取剖面盒的包围盒
BoundingBox3D sectionBoxBounds;
if (!GetSectionBoxBounds(clipPlanes, out sectionBoxBounds))
{
MessageBox.Show(
"无法获取剖面盒边界!\n请确保剖面盒已正确设置。",
"错误",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
return null;
}
// 3. 选择保存路径
string filePath = ShowSaveFileDialog();
if (string.IsNullOrEmpty(filePath))
return null;
try
{
// 4. 获取剖面盒内的对象
var objectsInSectionBox = GetObjectsInSectionBox(document, sectionBoxBounds);
// 5. 构建导出数据
var exportData = BuildExportData(sectionBoxBounds, objectsInSectionBox, document);
// 6. 序列化为JSON并保存
string json = JsonConvert.SerializeObject(exportData, Formatting.Indented);
File.WriteAllText(filePath, json);
LogManager.Info($"剖面盒导出完成: {objectsInSectionBox.Count} 个对象 -> {filePath}");
return filePath;
}
catch (Exception ex)
{
LogManager.Error($"导出剖面盒失败: {ex.Message}", ex);
MessageBox.Show(
$"导出失败:{ex.Message}",
"错误",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
return null;
}
}
/// <summary>
/// 获取剖面盒的包围盒
/// </summary>
private bool GetSectionBoxBounds(ClipPlaneSet clipPlanes, out BoundingBox3D result)
{
result = default(BoundingBox3D);
try
{
// 直接获取 Box 属性(长方体模式)
var box = clipPlanes.Box;
if (box == null)
{
LogManager.Warning("ClipPlanes.Box 为 null");
return false;
}
// 转换为 BoundingBox3D
result = new BoundingBox3D(
new Point3D(box.Min.X, box.Min.Y, box.Min.Z),
new Point3D(box.Max.X, box.Max.Y, box.Max.Z)
);
return true;
}
catch (Exception ex)
{
LogManager.Warning($"获取剖面盒边界失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 获取剖面盒内的所有对象
/// </summary>
private List<ModelItem> GetObjectsInSectionBox(Document document, BoundingBox3D sectionBox)
{
var result = new List<ModelItem>();
// 手动遍历所有模型
foreach (var model in document.Models)
{
var rootItem = model.RootItem;
if (rootItem != null)
{
TraverseModelItem(rootItem, sectionBox, result);
}
}
return result;
}
/// <summary>
/// 递归遍历模型项 - 剪枝策略:遇到几何体就停止向下遍历
/// </summary>
private void TraverseModelItem(ModelItem item, BoundingBox3D sectionBox, List<ModelItem> result)
{
if (item == null) return;
// 如果节点隐藏,跳过
if (item.IsHidden)
return;
// 如果有几何体信息,检查并添加,然后停止遍历该分支(剪枝)
if (item.HasGeometry)
{
var bbox = item.BoundingBox();
// 检查是否有体积三个方向尺寸都大于0
double sizeX = bbox.Max.X - bbox.Min.X;
double sizeY = bbox.Max.Y - bbox.Min.Y;
double sizeZ = bbox.Max.Z - bbox.Min.Z;
bool hasVolume = sizeX > 0.0001 && sizeY > 0.0001 && sizeZ > 0.0001;
// 有体积且与剖面盒相交才添加
if (hasVolume && BoundingBoxesIntersect(bbox, sectionBox))
{
result.Add(item);
}
// 找到几何体,停止向下遍历(无论是否相交)
return;
}
// 没有几何体,继续遍历子节点
foreach (var child in item.Children)
{
TraverseModelItem(child, sectionBox, result);
}
}
/// <summary>
/// 检查两个包围盒是否相交
/// </summary>
private bool BoundingBoxesIntersect(BoundingBox3D a, BoundingBox3D b)
{
return (a.Min.X <= b.Max.X && a.Max.X >= b.Min.X) &&
(a.Min.Y <= b.Max.Y && a.Max.Y >= b.Min.Y) &&
(a.Min.Z <= b.Max.Z && a.Max.Z >= b.Min.Z);
}
/// <summary>
/// 构建导出数据
/// </summary>
private JObject BuildExportData(BoundingBox3D sectionBox, List<ModelItem> objects, Document document)
{
var root = new JObject();
// 1. 导出剖面盒信息
var sectionBoxInfo = new JObject
{
["min"] = new JArray { sectionBox.Min.X, sectionBox.Min.Y, sectionBox.Min.Z },
["max"] = new JArray { sectionBox.Max.X, sectionBox.Max.Y, sectionBox.Max.Z },
["center"] = new JArray
{
(sectionBox.Min.X + sectionBox.Max.X) / 2,
(sectionBox.Min.Y + sectionBox.Max.Y) / 2,
(sectionBox.Min.Z + sectionBox.Max.Z) / 2
},
["size"] = new JArray
{
sectionBox.Max.X - sectionBox.Min.X,
sectionBox.Max.Y - sectionBox.Min.Y,
sectionBox.Max.Z - sectionBox.Min.Z
}
};
root["sectionBox"] = sectionBoxInfo;
// 2. 导出文档信息
var docInfo = new JObject
{
["fileName"] = document.FileName ?? "Unknown",
["units"] = document.Units.ToString(),
["exportTime"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
["objectCount"] = objects.Count
};
root["documentInfo"] = docInfo;
// 3. 导出对象列表
var objectsArray = new JArray();
foreach (var item in objects)
{
var bbox = item.BoundingBox();
var objInfo = new JObject
{
["name"] = GetFullPathName(item),
["boundingBox"] = new JObject
{
["min"] = new JArray { bbox.Min.X, bbox.Min.Y, bbox.Min.Z },
["max"] = new JArray { bbox.Max.X, bbox.Max.Y, bbox.Max.Z },
["center"] = new JArray
{
(bbox.Min.X + bbox.Max.X) / 2,
(bbox.Min.Y + bbox.Max.Y) / 2,
(bbox.Min.Z + bbox.Max.Z) / 2
},
["size"] = new JArray
{
bbox.Max.X - bbox.Min.X,
bbox.Max.Y - bbox.Min.Y,
bbox.Max.Z - bbox.Min.Z
}
}
};
// 添加Transform信息仅非单位变换
try
{
var components = item.Transform.Factor();
var rotation = components.Rotation.ToAxisAndAngle();
bool isIdentity = Math.Abs(rotation.Angle) < 0.0001 &&
Math.Abs(components.Scale.X - 1) < 0.0001 &&
Math.Abs(components.Scale.Y - 1) < 0.0001 &&
Math.Abs(components.Scale.Z - 1) < 0.0001 &&
Math.Abs(components.Translation.X) < 0.0001 &&
Math.Abs(components.Translation.Y) < 0.0001 &&
Math.Abs(components.Translation.Z) < 0.0001;
if (!isIdentity)
{
objInfo["transform"] = new JObject
{
["translation"] = new JArray { components.Translation.X, components.Translation.Y, components.Translation.Z },
["rotation"] = new JObject
{
["axis"] = new JArray { rotation.Axis.X, rotation.Axis.Y, rotation.Axis.Z },
["angle"] = rotation.Angle
},
["scale"] = new JArray { components.Scale.X, components.Scale.Y, components.Scale.Z }
};
}
}
catch (Exception ex)
{
LogManager.Debug($"获取Transform信息失败: {ex.Message}");
}
objectsArray.Add(objInfo);
}
root["objects"] = objectsArray;
return root;
}
/// <summary>
/// 获取对象的全路径名称
/// </summary>
private string GetFullPathName(ModelItem item)
{
if (item == null) return "";
var pathParts = new List<string>();
// 从当前节点向上遍历到根节点
var current = item;
while (current != null)
{
string name = current.DisplayName;
if (!string.IsNullOrEmpty(name))
{
pathParts.Insert(0, name);
}
current = current.Parent;
}
// 使用 "/" 连接路径
return string.Join("/", pathParts);
}
/// <summary>
/// 显示保存文件对话框
/// </summary>
private string ShowSaveFileDialog()
{
using (var dialog = new SaveFileDialog())
{
dialog.Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*";
dialog.DefaultExt = "json";
dialog.FileName = $"SectionBox_Export_{DateTime.Now:yyyyMMdd_HHmmss}.json";
dialog.Title = "导出剖面盒信息";
if (dialog.ShowDialog() == DialogResult.OK)
{
return dialog.FileName;
}
}
return null;
}
}
}

View File

@ -171,6 +171,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ICommand TestVoxelPathFindingCommand { get; private set; }
public ICommand ReadTransformTestCommand { get; private set; }
public ICommand CoordinateSystemExplorerCommand { get; private set; }
public ICommand ExportSectionBoxCommand { get; private set; }
// 坐标系设置
public ObservableCollection<string> CoordinateSystemOptions { get; private set; }
@ -308,6 +309,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
TestVoxelPathFindingCommand = new RelayCommand(() => ExecuteTestVoxelPathFinding());
ReadTransformTestCommand = new RelayCommand(() => ExecuteReadTransformTest());
CoordinateSystemExplorerCommand = new RelayCommand(() => ExecuteCoordinateSystemExplorer());
ExportSectionBoxCommand = new RelayCommand(() => ExecuteExportSectionBox());
// 初始化坐标系选项
CoordinateSystemOptions = new ObservableCollection<string> { "AutoDetect", "ZUp", "YUp" };
@ -1764,6 +1766,68 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}, "坐标系探索");
}
/// <summary>
/// 执行导出剖面盒功能
/// </summary>
private void ExecuteExportSectionBox()
{
SafeExecute(() =>
{
try
{
UpdateMainStatus("正在导出剖面盒信息...");
LogManager.Info("开始导出剖面盒信息");
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
if (doc == null || doc.IsClear)
{
System.Windows.MessageBox.Show(
"没有活动的文档!请先打开一个模型。",
"错误",
System.Windows.MessageBoxButton.OK,
System.Windows.MessageBoxImage.Error);
UpdateMainStatus("导出剖面盒失败:无活动文档");
return;
}
// 使用导出功能类
var exporter = new NavisworksTransport.Core.SectionBoxExporter();
string filePath = exporter.ExportToJson(doc);
if (!string.IsNullOrEmpty(filePath))
{
var result = System.Windows.MessageBox.Show(
$"剖面盒信息已导出到:\n{filePath}\n\n是否打开文件所在目录",
"导出成功",
System.Windows.MessageBoxButton.YesNo,
System.Windows.MessageBoxImage.Information);
if (result == System.Windows.MessageBoxResult.Yes)
{
System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{filePath}\"");
}
UpdateMainStatus($"剖面盒信息已导出: {filePath}");
LogManager.Info($"剖面盒信息导出成功: {filePath}");
}
else
{
UpdateMainStatus("导出剖面盒被取消或失败");
}
}
catch (Exception ex)
{
LogManager.Error($"导出剖面盒异常: {ex.Message}", ex);
System.Windows.MessageBox.Show(
$"导出剖面盒出现异常:\n{ex.Message}",
"错误",
System.Windows.MessageBoxButton.OK,
System.Windows.MessageBoxImage.Error);
UpdateMainStatus($"导出剖面盒异常: {ex.Message}");
}
}, "导出剖面盒");
}
#endregion
#region

View File

@ -236,6 +236,11 @@ NavisworksTransport 系统管理页签视图 - 采用与其他页签一致的Nav
<Button Content="检查更新"
Command="{Binding CheckUpdateCommand}"
Style="{StaticResource SecondaryButtonStyle}"/>
<Button Content="导出剖面盒"
Command="{Binding ExportSectionBoxCommand}"
Style="{StaticResource ActionButtonStyle}"
ToolTip="导出当前剖面盒内的对象包围盒信息到JSON文件"/>
</StackPanel>
</StackPanel>
</Border>