346 lines
13 KiB
C#
346 lines
13 KiB
C#
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];
|
||
}
|
||
}
|
||
}
|