NavisworksTransport/src/Core/AssemblyReferencePathManager.cs

346 lines
13 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;
using NavisworksTransport.Utils;
using NavisworksTransport.Utils.CoordinateSystem;
namespace NavisworksTransport.Core
{
/// <summary>
/// 直线装配参考路径管理器。
/// 使用单位立方体资源生成一根可点击的临时参考杆,供用户在 3D 视图中选择起点。
/// 参考杆本质上表达的是一段 start->end 参考轴线,而不是终点处的独立构件。
/// </summary>
public class AssemblyReferencePathManager
{
private const double ReferenceRodBaseSizeInMeters = 0.01;
private static readonly ModelAxisConvention ReferenceRodAxisConvention = ModelAxisConvention.CreateReferenceRodAssetConvention();
private static AssemblyReferencePathManager _instance;
private static readonly object _lock = new object();
private Model _referenceRodModel;
private ModelItem _referenceRodModelItem;
private bool _isUpdatingReferenceRod;
private bool _isReferenceRodVisible;
private Point3D _referenceLineStart = Point3D.Origin;
private Point3D _referenceLineEnd = Point3D.Origin;
private double _referenceRodDiameterInMeters;
private string _referenceResourceName;
public static AssemblyReferencePathManager Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new AssemblyReferencePathManager();
}
}
}
return _instance;
}
}
public ModelItem CurrentReferenceRod => _referenceRodModelItem;
public bool IsReferenceRodVisible => _isReferenceRodVisible;
public bool IsUpdatingReferenceRod => _isUpdatingReferenceRod;
public bool HasReferenceLine => VectorLengthSquared(new Vector3D(
_referenceLineEnd.X - _referenceLineStart.X,
_referenceLineEnd.Y - _referenceLineStart.Y,
_referenceLineEnd.Z - _referenceLineStart.Z)) > 1e-9;
public Point3D ReferenceLineStart => _referenceLineStart;
public Point3D ReferenceLineEnd => _referenceLineEnd;
public string ReferenceResourceName => _referenceResourceName;
private AssemblyReferencePathManager()
{
_isReferenceRodVisible = false;
}
/// <summary>
/// 创建或更新参考杆。
/// 先记录参考线主数据,再基于参考线更新可点击实体外壳。
/// </summary>
public ModelItem CreateOrUpdateReferenceRod(Point3D startPoint, Point3D endPoint, double diameterInMeters)
{
var doc = Application.ActiveDocument;
if (doc == null || doc.IsClear)
{
throw new InvalidOperationException("当前没有可用文档,无法创建装配参考杆");
}
double referenceLength = GeometryHelper.CalculatePointDistance(startPoint, endPoint);
if (referenceLength <= 1e-6)
{
throw new InvalidOperationException("参考线长度为 0无法创建装配参考杆");
}
_isUpdatingReferenceRod = true;
try
{
_referenceLineStart = startPoint;
_referenceLineEnd = endPoint;
_referenceRodDiameterInMeters = diameterInMeters;
EnsureReferenceRodModelLoaded();
ScaleReferenceRod(referenceLength, diameterInMeters);
PositionReferenceRod();
ShowReferenceRod();
double referenceLengthInMeters = UnitsConverter.ConvertToMeters(referenceLength);
LogManager.Info($"[装配参考杆] 已创建/更新,长度={referenceLengthInMeters:F3}m直径={diameterInMeters:F3}m");
return _referenceRodModelItem;
}
finally
{
_isUpdatingReferenceRod = false;
}
}
public void ShowReferenceRod()
{
if (_referenceRodModelItem == null)
{
return;
}
var items = new ModelItemCollection { _referenceRodModel.RootItem };
Application.ActiveDocument.Models.SetHidden(items, false);
_isReferenceRodVisible = true;
}
public void HideReferenceRod()
{
if (_referenceRodModelItem == null)
{
return;
}
var items = new ModelItemCollection { _referenceRodModel.RootItem };
Application.ActiveDocument.Models.SetHidden(items, true);
_isReferenceRodVisible = false;
}
/// <summary>
/// 将任意点击点投影到当前参考线。
/// </summary>
public Point3D ProjectPointToReferenceLine(Point3D point)
{
if (!HasReferenceLine)
{
throw new InvalidOperationException("当前没有可用的装配参考线");
}
return ProjectPointToSegment(point, _referenceLineStart, _referenceLineEnd);
}
public void Cleanup()
{
try
{
if (_referenceRodModel != null)
{
var doc = Application.ActiveDocument;
if (doc != null && !doc.IsClear)
{
int index = doc.Models.IndexOf(_referenceRodModel);
if (index >= 0)
{
doc.TryRemoveFile(index);
}
}
}
}
catch (Exception ex)
{
LogManager.Error($"清理装配参考杆失败: {ex.Message}");
}
finally
{
_referenceRodModel = null;
_referenceRodModelItem = null;
_isReferenceRodVisible = false;
_referenceLineStart = Point3D.Origin;
_referenceLineEnd = Point3D.Origin;
_referenceRodDiameterInMeters = 0.0;
_referenceResourceName = null;
}
}
private void EnsureReferenceRodModelLoaded()
{
if (_referenceRodModel != null &&
Application.ActiveDocument.Models.IndexOf(_referenceRodModel) >= 0 &&
_referenceRodModelItem != null)
{
return;
}
string resourcePath = GetReferenceRodResourcePath(out string resourceName);
if (string.IsNullOrEmpty(resourcePath) || !File.Exists(resourcePath))
{
throw new FileNotFoundException($"找不到参考杆资源文件: {resourcePath}");
}
var doc = Application.ActiveDocument;
int modelCountBefore = doc.Models.Count;
if (!doc.TryAppendFile(resourcePath))
{
throw new InvalidOperationException($"无法追加文件: {resourcePath}");
}
if (doc.Models.Count <= modelCountBefore)
{
throw new InvalidOperationException("追加参考杆资源后模型数量未增加");
}
_referenceRodModel = doc.Models.Last();
_referenceRodModelItem = _referenceRodModel.RootItem;
_isReferenceRodVisible = true;
_referenceResourceName = resourceName;
LogManager.Info($"[装配参考杆] 已加载资源: {resourceName}");
}
private void ScaleReferenceRod(double length, double diameterInMeters)
{
if (_referenceRodModel == null)
{
return;
}
var doc = Application.ActiveDocument;
double lengthInMeters = UnitsConverter.ConvertToMeters(length);
var currentTransform = _referenceRodModel.Transform;
var components = currentTransform.Factor();
components.Scale = ReferenceRodAxisConvention.CreateScaleVector(
lengthInMeters / ReferenceRodBaseSizeInMeters,
diameterInMeters / ReferenceRodBaseSizeInMeters,
diameterInMeters / ReferenceRodBaseSizeInMeters);
Transform3D scaledTransform = components.Combine();
doc.Models.SetModelUnitsAndTransform(
_referenceRodModel,
_referenceRodModel.Units,
scaledTransform,
false);
LogManager.Info(
$"[装配参考杆] 缩放完成,目标长度={lengthInMeters:F3}m目标直径={diameterInMeters:F3}m模型单位={_referenceRodModel.Units}");
}
private void PositionReferenceRod()
{
if (_referenceRodModelItem == null || _referenceRodModel == null)
{
return;
}
Point3D targetCenter = new Point3D(
(_referenceLineStart.X + _referenceLineEnd.X) / 2.0,
(_referenceLineStart.Y + _referenceLineEnd.Y) / 2.0,
(_referenceLineStart.Z + _referenceLineEnd.Z) / 2.0);
Rotation3D rotation = CreateLineRotation(_referenceLineStart, _referenceLineEnd);
ModelItemTransformHelper.MoveItemToCenterAndRotation(
_referenceRodModelItem,
targetCenter,
rotation);
LogManager.Info(
$"[装配参考杆] 定位完成,中心=({targetCenter.X:F3}, {targetCenter.Y:F3}, {targetCenter.Z:F3}),起点=({_referenceLineStart.X:F3}, {_referenceLineStart.Y:F3}, {_referenceLineStart.Z:F3}),终点=({_referenceLineEnd.X:F3}, {_referenceLineEnd.Y:F3}, {_referenceLineEnd.Z:F3})");
}
private static Rotation3D CreateLineRotation(Point3D startPoint, Point3D endPoint)
{
var tangent = Normalize(new Vector3D(
endPoint.X - startPoint.X,
endPoint.Y - startPoint.Y,
endPoint.Z - startPoint.Z));
// 参考杆资源约定:几何中心在原点,长度轴沿本地 ForwardAxis。
// Navisworks API 文档中 Rotation3D(vector1, vector2) 的语义
// 是“将 vector1 旋转到与 vector2 同方向”,这里直接将资源本地 forward 轴对齐参考线方向。
return new Rotation3D(
new UnitVector3D(ReferenceRodAxisConvention.ForwardVector),
new UnitVector3D(tangent));
}
private static Point3D ProjectPointToSegment(Point3D point, Point3D segmentStart, Point3D segmentEnd)
{
double segmentX = segmentEnd.X - segmentStart.X;
double segmentY = segmentEnd.Y - segmentStart.Y;
double segmentZ = segmentEnd.Z - segmentStart.Z;
double lengthSquared = segmentX * segmentX + segmentY * segmentY + segmentZ * segmentZ;
if (lengthSquared < 1e-9)
{
return segmentStart;
}
double pointX = point.X - segmentStart.X;
double pointY = point.Y - segmentStart.Y;
double pointZ = point.Z - segmentStart.Z;
double projection = (pointX * segmentX + pointY * segmentY + pointZ * segmentZ) / lengthSquared;
projection = Math.Max(0.0, Math.Min(1.0, projection));
return new Point3D(
segmentStart.X + projection * segmentX,
segmentStart.Y + projection * segmentY,
segmentStart.Z + projection * segmentZ);
}
private static Vector3D Normalize(Vector3D vector)
{
double lengthSquared = VectorLengthSquared(vector);
if (lengthSquared < 1e-9)
{
return new Vector3D(1, 0, 0);
}
double length = Math.Sqrt(lengthSquared);
return new Vector3D(vector.X / length, vector.Y / length, vector.Z / length);
}
private static double VectorLengthSquared(Vector3D vector)
{
return vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z;
}
private static string GetReferenceRodResourcePath(out string resourceName)
{
string pluginDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string[] preferredPaths = new[]
{
Path.Combine(pluginDir, "resources", "unit_cylinder.nwc"),
Path.Combine(pluginDir, "..", "..", "resources", "unit_cylinder.nwc"),
@"c:\Users\Tellme\apps\NavisworksTransport\resources\unit_cylinder.nwc",
Path.Combine(pluginDir, "resources", "unit_cube.nwc"),
Path.Combine(pluginDir, "..", "..", "resources", "unit_cube.nwc"),
@"c:\Users\Tellme\apps\NavisworksTransport\resources\unit_cube.nwc"
};
foreach (string path in preferredPaths)
{
if (File.Exists(path))
{
resourceName = Path.GetFileName(path);
return path;
}
}
resourceName = "unit_cylinder.nwc";
return preferredPaths[0];
}
}
}