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
{
///
/// 直线装配参考路径管理器。
/// 使用单位立方体资源生成一根可点击的临时参考杆,供用户在 3D 视图中选择起点。
/// 参考杆本质上表达的是一段 start->end 参考轴线,而不是终点处的独立构件。
///
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;
}
///
/// 创建或更新参考杆。
/// 先记录参考线主数据,再基于参考线更新可点击实体外壳。
///
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;
}
///
/// 将任意点击点投影到当前参考线。
///
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];
}
}
}