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]; } } }