NavisworksTransport/src/Core/VirtualVehicleManager.cs

438 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.Core
{
/// <summary>
/// 虚拟物体管理器 - 负责创建和管理虚拟物体几何体
/// 通过追加预制的单位立方体NWC文件并缩放来创建指定尺寸的虚拟物体
/// </summary>
public class VirtualObjectManager
{
private static VirtualObjectManager _instance;
private static readonly object _lock = new object();
/// <summary>
/// 单例实例
/// </summary>
public static VirtualObjectManager Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new VirtualObjectManager();
}
}
}
return _instance;
}
}
private ModelItem _virtualObjectModelItem;
private Model _virtualObjectModel;
private bool _isVirtualObjectActive;
// 🔥 新增:标记是否正在更新虚拟物体,用于避免触发缓存重建
private bool _isUpdatingVirtualObject = false;
/// <summary>
/// 是否正在更新虚拟物体(用于外部检测是否应该跳过缓存重建)
/// </summary>
public bool IsUpdatingVirtualObject => _isUpdatingVirtualObject;
// 🔥 记住虚拟物体的尺寸变换参数(避免动态计算)
private double _currentLengthMeters = 0;
private double _currentWidthMeters = 0;
private double _currentHeightMeters = 0;
/// <summary>
/// 当前虚拟物体ModelItem
/// </summary>
public ModelItem CurrentVirtualObject => _virtualObjectModelItem;
/// <summary>
/// 虚拟物体是否激活
/// </summary>
public bool IsVirtualObjectActive => _isVirtualObjectActive;
private VirtualObjectManager()
{
_isVirtualObjectActive = false;
}
/// <summary>
/// 显示虚拟物体
/// </summary>
/// <param name="lengthMeters">长度(米)</param>
/// <param name="widthMeters">宽度(米)</param>
/// <param name="heightMeters">高度(米)</param>
public void ShowVirtualObject(double lengthMeters, double widthMeters, double heightMeters)
{
// 🔥 设置标志,防止文档变化事件触发缓存重建
_isUpdatingVirtualObject = true;
try
{
var doc = Application.ActiveDocument;
// 检查虚拟物体模型是否已存在
if (_virtualObjectModel != null)
{
int currentIndex = doc.Models.IndexOf(_virtualObjectModel);
if (currentIndex >= 0)
{
// 模型存在,显示它并更新尺寸
LogManager.Info($"虚拟物体模型已存在(索引: {currentIndex}),显示并更新尺寸");
var modelItems = new ModelItemCollection { _virtualObjectModelItem };
doc.Models.SetHidden(modelItems, false);
// 获取实际的几何体项
_virtualObjectModelItem = _virtualObjectModel.RootItem;
var geometryItem = FindFirstGeometryItem(_virtualObjectModelItem);
if (geometryItem != null && !geometryItem.Equals(_virtualObjectModelItem))
{
_virtualObjectModelItem = geometryItem;
}
// 清除覆盖变换(之前动画移动和旋转的累积)
modelItems.Clear();
modelItems.Add(_virtualObjectModelItem);
doc.Models.ResetPermanentTransform(modelItems);
// 重置变换并缩放到目标尺寸
ResetVirtualObjectTransform();
ScaleVirtualObject(lengthMeters, widthMeters, heightMeters);
_isVirtualObjectActive = true;
LogManager.Info($"虚拟物体更新成功");
return;
}
else
{
LogManager.Warning($"已保存的虚拟物体模型不在文档中,需要重新创建");
}
}
// 模型不存在,创建新的
CreateVirtualObjectInternal(doc, lengthMeters, widthMeters, heightMeters);
}
catch (Exception ex)
{
LogManager.Error($"显示虚拟物体失败: {ex.Message}");
}
finally
{
// 🔥 清除标志
_isUpdatingVirtualObject = false;
}
}
/// <summary>
/// 隐藏虚拟物体
/// </summary>
public void HideVirtualObject()
{
try
{
if (_virtualObjectModelItem != null)
{
var doc = Application.ActiveDocument;
var modelItems = new ModelItemCollection { _virtualObjectModelItem };
doc.Models.SetHidden(modelItems, true);
LogManager.Info($"已隐藏虚拟物体模型");
}
// 隐藏时设置为非激活状态,但保留模型引用
_isVirtualObjectActive = false;
}
catch (Exception ex)
{
LogManager.Error($"隐藏虚拟物体失败: {ex.Message}");
_isVirtualObjectActive = false;
}
}
/// <summary>
/// 创建虚拟物体(内部方法)
/// </summary>
private void CreateVirtualObjectInternal(Document doc, double lengthMeters, double widthMeters, double heightMeters)
{
LogManager.Info($"=== 创建虚拟物体 ===");
LogManager.Info($"目标尺寸: {lengthMeters:F2}m × {widthMeters:F2}m × {heightMeters:F2}m");
// 加载新的虚拟物体模型
LoadNewVirtualObjectModel(doc);
// 获取实际的几何体项
_virtualObjectModelItem = _virtualObjectModel.RootItem;
var geometryItem = FindFirstGeometryItem(_virtualObjectModelItem);
if (geometryItem != null && !geometryItem.Equals(_virtualObjectModelItem))
{
_virtualObjectModelItem = geometryItem;
}
// 重置变换并缩放到目标尺寸
ResetVirtualObjectTransform();
ScaleVirtualObject(lengthMeters, widthMeters, heightMeters);
// 设置状态
_isVirtualObjectActive = true;
LogManager.Info($"虚拟物体创建成功");
}
/// <summary>
/// 加载新的虚拟物体模型
/// </summary>
private void LoadNewVirtualObjectModel(Document doc)
{
LogManager.Info($"需要加载新的虚拟物体模型");
// 1. 获取单位立方体文件路径
var unitCubePath = GetUnitCubeFilePath();
if (string.IsNullOrEmpty(unitCubePath) || !File.Exists(unitCubePath))
{
throw new FileNotFoundException($"找不到单位立方体文件: {unitCubePath}");
}
LogManager.Info($"单位立方体文件: {unitCubePath}");
// 2. 记录追加前的模型数量
int modelCountBefore = doc.Models.Count;
LogManager.Info($"追加前模型数量: {modelCountBefore}");
// 3. 追加单位立方体文件
if (!doc.TryAppendFile(unitCubePath))
{
throw new InvalidOperationException($"无法追加文件: {unitCubePath}");
}
// 4. 验证并获取新追加的模型
int modelCountAfter = doc.Models.Count;
LogManager.Info($"追加后模型数量: {modelCountAfter}");
if (modelCountAfter <= modelCountBefore)
{
throw new InvalidOperationException("追加文件后模型数量未增加");
}
// 保存模型引用(最后一个模型就是刚追加的)
_virtualObjectModel = doc.Models.Last();
LogManager.Info($"虚拟物体模型: {_virtualObjectModel.FileName}");
LogManager.Info($"虚拟物体根项: {_virtualObjectModel.RootItem.DisplayName}");
}
/// <summary>
/// <summary>
/// 重置虚拟物体变换为单位矩阵(公开方法,供批处理使用)
/// </summary>
public void ResetVirtualObjectTransform()
{
if (_virtualObjectModel == null) return;
var doc = Application.ActiveDocument;
// 创建单位变换矩阵
Transform3D identityTransform = new Transform3D();
Transform3DComponents identityComponents = identityTransform.Factor();
// 应用单位变换
Units currentUnits = _virtualObjectModel.Units;
doc.Models.SetModelUnitsAndTransform(_virtualObjectModel, currentUnits, identityTransform, false);
LogManager.Info("已重置虚拟物体变换");
}
/// <summary>
/// 缩放虚拟物体到目标尺寸
/// 使用DocumentModels.SetModelUnitsAndTransform方法进行模型级缩放
/// </summary>
private void ScaleVirtualObject(double lengthMeters, double widthMeters, double heightMeters)
{
if (_virtualObjectModel == null || _virtualObjectModelItem == null) return;
var doc = Application.ActiveDocument;
// 🔥 记住尺寸参数(避免动态计算)
_currentLengthMeters = lengthMeters;
_currentWidthMeters = widthMeters;
_currentHeightMeters = heightMeters;
// 获取单位转换因子(米到模型单位)
double metersToUnits = Utils.UnitsConverter.GetMetersToUnitsConversionFactor(doc.Units);
// 🔥 对于虚拟物体,直接应用缩放比例(不动态读取当前尺寸)
// 原因:虚拟物体可能被旋转,导致包围盒尺寸不准确
// 单位立方体是 0.01m × 0.01m × 0.01m
// X方向 = 物体长度前进方向Y方向 = 物体宽度侧面Z方向 = 物体高度
double baseSizeMeters = 0.01;
double scaleX = lengthMeters / baseSizeMeters; // X方向 = 物体长度(前进方向)
double scaleY = widthMeters / baseSizeMeters; // Y方向 = 物体宽度(侧面)
double scaleZ = heightMeters / baseSizeMeters; // Z方向 = 物体高度
LogManager.Info($"目标尺寸: 长度={lengthMeters:F2}m, 宽度={widthMeters:F2}m, 高度={heightMeters:F2}m");
LogManager.Info($"缩放比例: X(长度)={scaleX:F2}, Y(宽度)={scaleY:F2}, Z(高度)={scaleZ:F2}");
// 使用Transform3DComponents进行缩放
// 获取当前模型的变换并分解
Transform3D currentTransform = _virtualObjectModel.Transform;
Transform3DComponents transformComponents = currentTransform.Factor();
// 获取当前值
Vector3D currentTranslation = transformComponents.Translation;
Rotation3D currentRotation = transformComponents.Rotation;
Vector3D currentScale = transformComponents.Scale;
LogManager.Info($"当前变换 - 平移: ({currentTranslation.X:F2}, {currentTranslation.Y:F2}, {currentTranslation.Z:F2})");
LogManager.Info($"当前变换 - 缩放: ({currentScale.X:F2}, {currentScale.Y:F2}, {currentScale.Z:F2})");
// 🔥 直接应用新的缩放值(不乘以当前缩放,避免累积)
transformComponents.Scale = new Vector3D(scaleX, scaleY, scaleZ);
// 组合成新的变换
Transform3D newTransform = transformComponents.Combine();
// 获取当前模型单位
Units currentUnits = _virtualObjectModel.Units;
// 应用新的变换
doc.Models.SetModelUnitsAndTransform(_virtualObjectModel, currentUnits, newTransform, false);
// 验证缩放结果
var newBoundingBox = _virtualObjectModelItem.BoundingBox();
double newLength = newBoundingBox.Max.X - newBoundingBox.Min.X;
double newWidth = newBoundingBox.Max.Y - newBoundingBox.Min.Y;
double newHeight = newBoundingBox.Max.Z - newBoundingBox.Min.Z;
double newLengthMeters = newLength / metersToUnits;
double newWidthMeters = newWidth / metersToUnits;
double newHeightMeters = newHeight / metersToUnits;
LogManager.Info($"缩放后尺寸: {newLengthMeters:F2}m × {newWidthMeters:F2}m × {newHeightMeters:F2}m");
}
/// <summary>
/// 移除虚拟物体(使用 TryRemoveFile 真正删除模型)
/// </summary>
public void RemoveVirtualObject()
{
try
{
var doc = Application.ActiveDocument;
if (_virtualObjectModel != null)
{
// 动态查找模型索引
int currentIndex = doc.Models.IndexOf(_virtualObjectModel);
if (currentIndex >= 0)
{
// 使用 TryRemoveFile 真正删除模型
bool removed = doc.TryRemoveFile(currentIndex);
if (removed)
{
LogManager.Info($"已删除虚拟物体模型(索引: {currentIndex}");
}
else
{
LogManager.Warning($"删除虚拟物体模型失败(索引: {currentIndex}");
}
}
else
{
LogManager.Warning($"虚拟物体模型不在文档中,无法删除");
}
}
// 清理引用
_virtualObjectModel = null;
_virtualObjectModelItem = null;
_isVirtualObjectActive = false;
}
catch (Exception ex)
{
LogManager.Error($"删除虚拟物体失败: {ex.Message}");
_virtualObjectModel = null;
_virtualObjectModelItem = null;
_isVirtualObjectActive = false;
}
}
/// <summary>
/// 获取单位立方体文件路径
/// </summary>
private string GetUnitCubeFilePath()
{
// 方法1从插件目录获取
var assemblyLocation = Assembly.GetExecutingAssembly().Location;
var pluginDir = Path.GetDirectoryName(assemblyLocation);
// 尝试多个可能的位置
var possiblePaths = new[]
{
Path.Combine(pluginDir, "resources", "unit_cube.nwc"),
Path.Combine(pluginDir, "unit_cube.nwc"),
Path.Combine(pluginDir, "..", "resources", "unit_cube.nwc"),
// 开发时的位置
@"c:\Users\Tellme\apps\NavisworksTransport\resources\unit_cube.nwc"
};
foreach (var path in possiblePaths)
{
var fullPath = Path.GetFullPath(path);
if (File.Exists(fullPath))
{
LogManager.Info($"找到单位立方体文件: {fullPath}");
return fullPath;
}
}
LogManager.Warning($"在以下位置未找到单位立方体文件:");
foreach (var path in possiblePaths)
{
LogManager.Warning($" - {Path.GetFullPath(path)}");
}
return null;
}
/// <summary>
/// 查找第一个包含几何体的ModelItem
/// </summary>
private ModelItem FindFirstGeometryItem(ModelItem root)
{
if (root.HasGeometry)
return root;
foreach (var child in root.Children)
{
var result = FindFirstGeometryItem(child);
if (result != null)
return result;
}
return null;
}
/// <summary>
/// 清理资源
/// </summary>
public void Cleanup()
{
RemoveVirtualObject();
}
}
}