Add linear assembly reference path and rod alignment

This commit is contained in:
tian 2026-03-19 22:00:04 +08:00
parent 0c83c73917
commit a55ef42f71
10 changed files with 628 additions and 153 deletions

View File

@ -470,13 +470,6 @@
</Page>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WinFX\3.0\Microsoft.WinFX.targets" Condition="Exists('$(MSBuildExtensionsPath)\Microsoft\WinFX\3.0\Microsoft.WinFX.targets')" />
<ItemGroup>
<!-- Localization Files -->
<None Include="resources\TransportPlugin.name.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>TransportPlugin.name.txt</Link>
</None>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- Import NETStandard.Library targets to support .NET Standard 2.0 libraries -->
<Import Project="packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets" Condition="Exists('packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets')" />
@ -486,6 +479,11 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>resources\default_config.toml</Link>
</None>
<!-- SQLite native x64 runtime dependency -->
<None Include="packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.118.0\build\net46\x64\SQLite.Interop.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>SQLite.Interop.dll</Link>
</None>
<!-- Plugin Name File (in resources folder) -->
<None Include="resources\TransportPlugin.name.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@ -496,6 +494,11 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>resources\unit_cube.nwc</Link>
</None>
<!-- Unit Cylinder NWC Model File -->
<None Include="resources\unit_cylinder.nwc">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>resources\unit_cylinder.nwc</Link>
</None>
</ItemGroup>
</Project>

View File

@ -1,6 +1,7 @@
@echo off
setlocal
:: 如果 Navisworks 正在运行,关闭它以释放 DLL 锁定
:: Stop Navisworks to release locked plugin DLLs.
taskkill /F /IM Roamer.exe 2>nul
if %errorlevel% == 0 (
echo Navisworks process terminated.
@ -8,15 +9,17 @@ if %errorlevel% == 0 (
)
set "TARGET_DIR=%PROGRAMDATA%\Autodesk\Navisworks Manage 2026\plugins\TransportPlugin"
set "BUILD_DIR=bin\x64\Release"
if not exist "%TARGET_DIR%" mkdir "%TARGET_DIR%"
copy "bin\x64\Release\TransportPlugin.dll" "%TARGET_DIR%\" >nul
copy "%BUILD_DIR%\*.dll" "%TARGET_DIR%\" >nul
echo Plugin DLLs deployed.
:: 复制resources文件夹包含chart.js等本地资源
if exist "bin\x64\Release\resources" (
:: Copy bundled resources.
if exist "%BUILD_DIR%\resources" (
if not exist "%TARGET_DIR%\resources" mkdir "%TARGET_DIR%\resources"
copy "bin\x64\Release\resources\*" "%TARGET_DIR%\resources\" >nul
xcopy "%BUILD_DIR%\resources\*" "%TARGET_DIR%\resources\" /E /I /Y /Q >nul
echo Resources folder deployed.
)

View File

@ -703,6 +703,131 @@ var transform = components.Combine();
// 结果:物体"公转"到错误位置
```
#### 11.7.2.1 Rotation3D 构造函数的真实语义2026-03-19 新增,重要)
这次在“直线装配参考杆”功能中,验证了一个很容易误用的点:
- `Rotation3D(UnitVector3D, UnitVector3D, UnitVector3D)` **不是**“传入本地 X/Y/Z 三个轴来直接构造三维姿态”
- 之前如果把它当成“三轴姿态构造器”使用,会得到完全错误的旋转结果
根据 Navisworks .NET API HTML 文档:
- `Rotation3D(UnitVector3D vector1, UnitVector3D vector2)`
- 文档摘要:`Creates rotation that rotates vector1 to same direction as vector2`
- 正确语义:**将向量1旋转到与向量2同方向**
- `Rotation3D(UnitVector3D axis, double angle)`
- 文档摘要:`Creates rotation about given axis by angle in radians`
- 正确语义:**绕指定轴按弧度旋转**
- `Rotation3D(UnitVector3D axis, UnitVector3D vector1, UnitVector3D vector2)`
- 文档摘要:`Creates rotation about axis by angle between v1 and v2 projected onto plane normal to axis.`
- 正确语义:**绕 axis 旋转,旋转角由 vector1 和 vector2 在垂直于 axis 的平面上的投影夹角决定**
**结论:**
- 3 参数版本是“轴 + 两个参考向量”的构造,不是“三个基向量/三个坐标轴”
- 如果需求是“让物体本地某个轴对齐任意空间方向”,优先使用 **2 参数版本**
#### 11.7.2.2 三维参考杆/任意向量对齐的正确用法
适用场景:
- 参考杆、箭头、圆柱体等“有明确本地长度轴”的资源
- 需要将资源的本地某个轴,对齐到一个任意空间向量
例如:
- 资源中心在原点
- 长轴沿本地 `+X`
- 目标方向是参考线方向 `referenceDirection`
那么旋转应直接写成:
```csharp
var rotation = new Rotation3D(
new UnitVector3D(1, 0, 0),
new UnitVector3D(referenceDirection));
```
其含义非常直接:
- 把资源的本地 `+X`
- 旋转到 `referenceDirection`
这正是“直线装配参考杆”最终验证通过的做法。
**不要写成下面这样:**
```csharp
// ❌ 错误:把三个向量误当成 X/Y/Z 三个姿态轴
var rotation = new Rotation3D(
new UnitVector3D(tangent),
new UnitVector3D(lateral),
new UnitVector3D(normal));
```
这段代码的真实语义并不是“建立一个由 tangent/lateral/normal 组成的姿态”,
而是“绕 tangent 这个轴,根据 lateral 和 normal 的投影夹角去旋转”,
所以结果通常会完全错误。
#### 11.7.2.3 三维旋转与平移补偿必须分开理解
即使 `Rotation3D(vector1, vector2)` 构造正确,仍然不能忽略 Navisworks 的旋转中心限制:
- `OverridePermanentTransform()` 仍然是**增量变换**
- 旋转仍然是**绕世界原点**
- 因此三维姿态正确后,位置仍需要像 yaw 场景一样做补偿
对于“资源中心在原点、目标中心为 `targetCenter`”的对象,推荐模式是:
```csharp
// 1. 从资源原始状态出发
doc.Models.ResetPermanentTransform(modelItems);
// 2. 计算旋转:本地 X -> 目标方向
var rotation = new Rotation3D(
new UnitVector3D(1, 0, 0),
new UnitVector3D(referenceDirection));
// 3. 计算原始中心绕世界原点旋转后的位置
var originalCenter = item.BoundingBox().Center;
var rotationTransform = new Transform3D(rotation);
var linear = rotationTransform.Linear;
var rotatedCenter = new Point3D(
linear.Get(0, 0) * originalCenter.X + linear.Get(0, 1) * originalCenter.Y + linear.Get(0, 2) * originalCenter.Z,
linear.Get(1, 0) * originalCenter.X + linear.Get(1, 1) * originalCenter.Y + linear.Get(1, 2) * originalCenter.Z,
linear.Get(2, 0) * originalCenter.X + linear.Get(2, 1) * originalCenter.Y + linear.Get(2, 2) * originalCenter.Z);
// 4. 用补偿平移把旋转后的中心送到目标中心
var translation = new Vector3D(
targetCenter.X - rotatedCenter.X,
targetCenter.Y - rotatedCenter.Y,
targetCenter.Z - rotatedCenter.Z);
// 5. 组合并应用增量变换
var transform = new Transform3D(rotation, translation);
doc.Models.OverridePermanentTransform(modelItems, transform, false);
```
**关键原则:**
- 先把“旋转方向”算对
- 再按“绕原点旋转后的实际位置”计算平移补偿
- 不要把“姿态构造错误”和“旋转中心补偿缺失”混成一个问题
#### 11.7.2.4 这类问题的调试建议
当三维物体“位置大概对,但方向明显错”时,优先检查:
1. 是否误用了 `Rotation3D(UnitVector3D, UnitVector3D, UnitVector3D)`
2. 资源文件的本地长度轴是否真的与你的代码约定一致
3. 是否把 `ModelItem.Transform` 错当成 override 后的真实世界姿态
推荐观察项:
- 参考线/目标向量本身是否正确
- 资源本地长度轴约定(例如是否沿本地 `+X`
- 当前实际显示位置,优先看 `BoundingBox()`,不要只看 `ModelItem.Transform`
#### 11.7.3 正确实现"绕物体中心旋转"
**解决方案:手动计算旋转导致的位置偏移并补偿**

View File

@ -2,6 +2,10 @@
## 功能点
### [2026/3/18]
1. [x](功能)增加轨上路径(角度可倾斜)
### [2026/3/11]
1. [x] 功能给“导出剖面盒”增加导出nwd文件的选项

BIN
resources/unit_cylinder.nwc Normal file

Binary file not shown.

View File

@ -9,8 +9,8 @@ namespace NavisworksTransport.Core
{
/// <summary>
/// 直线装配参考路径管理器。
/// 优先使用单位圆柱体资源生成一根可点击的临时参考杆,供用户在 3D 视图中选择起点。
/// 如果圆柱资源缺失,则退回到单位立方体资源
/// 使用单位立方体资源生成一根可点击的临时参考杆,供用户在 3D 视图中选择起点。
/// 参考杆本质上表达的是一段 start->end 参考轴线,而不是终点处的独立构件
/// </summary>
public class AssemblyReferencePathManager
{
@ -27,7 +27,6 @@ namespace NavisworksTransport.Core
private Point3D _referenceLineEnd = Point3D.Origin;
private double _referenceRodDiameterInMeters;
private string _referenceResourceName;
public static AssemblyReferencePathManager Instance
{
get
@ -50,7 +49,10 @@ namespace NavisworksTransport.Core
public ModelItem CurrentReferenceRod => _referenceRodModelItem;
public bool IsReferenceRodVisible => _isReferenceRodVisible;
public bool IsUpdatingReferenceRod => _isUpdatingReferenceRod;
public bool HasReferenceLine => _referenceRodModelItem != null && _isReferenceRodVisible;
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;
@ -62,6 +64,7 @@ namespace NavisworksTransport.Core
/// <summary>
/// 创建或更新参考杆。
/// 先记录参考线主数据,再基于参考线更新可点击实体外壳。
/// </summary>
public ModelItem CreateOrUpdateReferenceRod(Point3D startPoint, Point3D endPoint, double diameterInMeters)
{
@ -80,19 +83,19 @@ namespace NavisworksTransport.Core
_isUpdatingReferenceRod = true;
try
{
EnsureReferenceRodModelLoaded();
double lengthInMeters = UnitsConverter.ConvertToMeters(referenceLength);
ScaleReferenceRod(lengthInMeters, diameterInMeters);
PositionReferenceRod(startPoint, endPoint);
ShowReferenceRod();
_referenceLineStart = startPoint;
_referenceLineEnd = endPoint;
_referenceRodDiameterInMeters = diameterInMeters;
LogManager.Info($"[装配参考杆] 已创建/更新,长度={lengthInMeters:F3}m直径={diameterInMeters:F3}m");
EnsureReferenceRodModelLoaded();
ScaleReferenceRod(referenceLength, diameterInMeters);
PositionReferenceRod();
ShowReferenceRod();
double referenceLengthInMeters = UnitsConverter.ConvertToMeters(referenceLength);
LogManager.Info($"[装配参考杆] 已创建/更新,长度={referenceLengthInMeters:F3}m直径={diameterInMeters:F3}m");
return _referenceRodModelItem;
}
finally
@ -108,7 +111,7 @@ namespace NavisworksTransport.Core
return;
}
var items = new ModelItemCollection { _referenceRodModelItem };
var items = new ModelItemCollection { _referenceRodModel.RootItem };
Application.ActiveDocument.Models.SetHidden(items, false);
_isReferenceRodVisible = true;
}
@ -120,7 +123,7 @@ namespace NavisworksTransport.Core
return;
}
var items = new ModelItemCollection { _referenceRodModelItem };
var items = new ModelItemCollection { _referenceRodModel.RootItem };
Application.ActiveDocument.Models.SetHidden(items, true);
_isReferenceRodVisible = false;
}
@ -199,19 +202,22 @@ namespace NavisworksTransport.Core
}
_referenceRodModel = doc.Models.Last();
_referenceRodModelItem = FindFirstGeometryItem(_referenceRodModel.RootItem);
_referenceRodModelItem = _referenceRodModel.RootItem;
_isReferenceRodVisible = true;
_referenceResourceName = resourceName;
LogManager.Info($"[装配参考杆] 已加载资源: {resourceName}");
}
private void ScaleReferenceRod(double lengthInMeters, double diameterInMeters)
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 = new Vector3D(
@ -220,54 +226,36 @@ namespace NavisworksTransport.Core
diameterInMeters / ReferenceRodBaseSizeInMeters);
Transform3D scaledTransform = components.Combine();
Application.ActiveDocument.Models.SetModelUnitsAndTransform(
doc.Models.SetModelUnitsAndTransform(
_referenceRodModel,
_referenceRodModel.Units,
scaledTransform,
false);
LogManager.Info(
$"[装配参考杆] 缩放完成,目标长度={lengthInMeters:F3}m目标直径={diameterInMeters:F3}m模型单位={_referenceRodModel.Units}");
}
private void PositionReferenceRod(Point3D startPoint, Point3D endPoint)
private void PositionReferenceRod()
{
if (_referenceRodModelItem == null)
if (_referenceRodModelItem == null || _referenceRodModel == null)
{
return;
}
var doc = Application.ActiveDocument;
var modelItems = new ModelItemCollection { _referenceRodModelItem };
var currentScale = _referenceRodModelItem.Transform.Factor().Scale;
Point3D targetCenter = new Point3D(
(_referenceLineStart.X + _referenceLineEnd.X) / 2.0,
(_referenceLineStart.Y + _referenceLineEnd.Y) / 2.0,
(_referenceLineStart.Z + _referenceLineEnd.Z) / 2.0);
doc.Models.ResetPermanentTransform(modelItems);
Rotation3D rotation = CreateLineRotation(_referenceLineStart, _referenceLineEnd);
ModelItemTransformHelper.MoveItemToCenterAndRotation(
_referenceRodModelItem,
targetCenter,
rotation);
var originalBounds = _referenceRodModelItem.BoundingBox();
var originalCenter = originalBounds.Center;
var targetCenter = new Point3D(
(startPoint.X + endPoint.X) / 2.0,
(startPoint.Y + endPoint.Y) / 2.0,
(startPoint.Z + endPoint.Z) / 2.0);
Rotation3D rotation = CreateLineRotation(startPoint, endPoint);
var identity = Transform3D.CreateTranslation(new Vector3D(0, 0, 0));
var components = identity.Factor();
components.Scale = currentScale;
components.Rotation = rotation;
var preTransform = components.Combine();
var linear = preTransform.Linear;
var transformedCenter = new Point3D(
linear.Get(0, 0) * originalCenter.X + linear.Get(0, 1) * originalCenter.Y + linear.Get(0, 2) * originalCenter.Z,
linear.Get(1, 0) * originalCenter.X + linear.Get(1, 1) * originalCenter.Y + linear.Get(1, 2) * originalCenter.Z,
linear.Get(2, 0) * originalCenter.X + linear.Get(2, 1) * originalCenter.Y + linear.Get(2, 2) * originalCenter.Z);
components.Translation = new Vector3D(
targetCenter.X - transformedCenter.X,
targetCenter.Y - transformedCenter.Y,
targetCenter.Z - transformedCenter.Z);
Transform3D transform = components.Combine();
doc.Models.OverridePermanentTransform(modelItems, transform, false);
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)
@ -277,28 +265,12 @@ namespace NavisworksTransport.Core
endPoint.Y - startPoint.Y,
endPoint.Z - startPoint.Z));
var worldUp = new Vector3D(0, 0, 1);
var projectedNormal = new Vector3D(
worldUp.X - Dot(worldUp, tangent) * tangent.X,
worldUp.Y - Dot(worldUp, tangent) * tangent.Y,
worldUp.Z - Dot(worldUp, tangent) * tangent.Z);
if (VectorLengthSquared(projectedNormal) < 1e-9)
{
var fallbackUp = new Vector3D(0, 1, 0);
projectedNormal = new Vector3D(
fallbackUp.X - Dot(fallbackUp, tangent) * tangent.X,
fallbackUp.Y - Dot(fallbackUp, tangent) * tangent.Y,
fallbackUp.Z - Dot(fallbackUp, tangent) * tangent.Z);
}
var normal = Normalize(projectedNormal);
var lateral = Normalize(Cross(normal, tangent));
// 参考杆资源约定:几何中心在原点,长度轴沿局部 +X。
// Navisworks API 文档中 Rotation3D(vector1, vector2) 的语义
// 是“将 vector1 旋转到与 vector2 同方向”,这里直接将本地 X 轴对齐参考线方向。
return new Rotation3D(
new UnitVector3D(tangent),
new UnitVector3D(lateral),
new UnitVector3D(normal));
new UnitVector3D(1, 0, 0),
new UnitVector3D(tangent));
}
private static Point3D ProjectPointToSegment(Point3D point, Point3D segmentStart, Point3D segmentEnd)
@ -325,19 +297,6 @@ namespace NavisworksTransport.Core
segmentStart.Z + projection * segmentZ);
}
private static Vector3D Cross(Vector3D a, Vector3D b)
{
return new Vector3D(
a.Y * b.Z - a.Z * b.Y,
a.Z * b.X - a.X * b.Z,
a.X * b.Y - a.Y * b.X);
}
private static double Dot(Vector3D a, Vector3D b)
{
return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
}
private static Vector3D Normalize(Vector3D vector)
{
double lengthSquared = VectorLengthSquared(vector);
@ -355,30 +314,6 @@ namespace NavisworksTransport.Core
return vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z;
}
private static ModelItem FindFirstGeometryItem(ModelItem item)
{
if (item == null)
{
return null;
}
if (item.HasGeometry)
{
return item;
}
foreach (var child in item.Children)
{
var geometryItem = FindFirstGeometryItem(child);
if (geometryItem != null)
{
return geometryItem;
}
}
return item;
}
private static string GetReferenceRodResourcePath(out string resourceName)
{
string pluginDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

View File

@ -439,7 +439,6 @@ namespace NavisworksTransport
var edge = BuildStraightEdge(sortedPoints[0], sortedPoints[1], samplingStep);
route.Edges.Add(edge);
route.IsCurved = true;
RecalculateRouteLength(route);
return;
}
@ -493,7 +492,6 @@ namespace NavisworksTransport
route.Edges.Add(lastEdge);
route.IsCurved = true;
RecalculateRouteLength(route);
}
/// <summary>

View File

@ -127,6 +127,11 @@ namespace NavisworksTransport
/// </summary>
RailBaseline,
/// <summary>
/// 直线装配基准向量样式(绿色)
/// </summary>
AssemblyGuideLine,
/// <summary>
/// 吊装路径样式(紫色)
/// </summary>
@ -1126,6 +1131,14 @@ namespace NavisworksTransport
/// <param name="railModelId">空轨模型ID</param>
/// <param name="baselinePoints">基准路径点列表</param>
public void RenderRailBaseline(string railModelId, List<Point3D> baselinePoints)
{
RenderRailBaseline(railModelId, baselinePoints, RenderStyleName.RailBaseline);
}
/// <summary>
/// 按指定样式渲染基准路径。
/// </summary>
public void RenderRailBaseline(string railModelId, List<Point3D> baselinePoints, RenderStyleName renderStyleName)
{
if (string.IsNullOrEmpty(railModelId) || baselinePoints == null || baselinePoints.Count < 2)
{
@ -1146,7 +1159,7 @@ namespace NavisworksTransport
LastUpdated = DateTime.Now
};
var renderStyle = GetRenderStyle(RenderStyleName.RailBaseline);
var renderStyle = GetRenderStyle(renderStyleName);
// 创建连线标记
for (int i = 0; i < baselinePoints.Count - 1; i++)
@ -2328,6 +2341,9 @@ namespace NavisworksTransport
case RenderStyleName.RailBaseline:
return new RenderStyle(Color.FromByteRGB(255, 138, 128), 0.7); // 浅红色30%透明
case RenderStyleName.AssemblyGuideLine:
return new RenderStyle(Color.FromByteRGB(76, 175, 80), 0.85); // Material Green装配基准向量
case RenderStyleName.HoistingLine:
return new RenderStyle(Color.FromByteRGB(156, 39, 176), 0.8); // Material Purple吊装路径20%透明

View File

@ -121,7 +121,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private ObservableCollection<RailConfigOption<RailMountMode>> _railMountModeOptions;
private ObservableCollection<RailConfigOption<RailPayloadAnchorMode>> _railPayloadAnchorModeOptions;
private const double DefaultAssemblyReferenceRodLengthInMeters = 20.0;
private const double DefaultAssemblyReferenceRodDiameterInMeters = 0.2;
private static readonly double DefaultAssemblyReferenceRodDiameterInMeters = CalculateDefaultAssemblyReferenceRodDiameterInMeters();
private string _assemblyTerminalObjectName = "未选择";
private string _assemblyTerminalObjectInfo = "请选择终点处已安装箱体";
private double _assemblyReferenceRodLengthInMeters = DefaultAssemblyReferenceRodLengthInMeters;
@ -134,6 +134,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private ModelItem _assemblyTerminalObject;
private Point3D _assemblyStartPoint;
private const string AssemblyAnchorMarkerPathId = "assembly_anchor_marker";
private const string AssemblyCenterGuideLinePathId = "assembly_center_guide_line";
private const string AssemblyReferenceLinePathId = "assembly_reference_line";
// 自动路径起点和终点的路径对象引用用于正确的ID管理
private PathRoute _autoPathStartPointRoute = null;
@ -1320,27 +1322,29 @@ namespace NavisworksTransport.UI.WPF.ViewModels
throw new InvalidOperationException("终点箱体未设置或已失效,请重新捕获终点箱体");
}
var forward = ModelItemTransformHelper.GetForwardDirectionFromTransform(_assemblyTerminalObject.Transform);
var endPoint = GetAssemblyTerminalAnchorPoint();
double rodLength = UnitsConverter.ConvertFromMeters(AssemblyReferenceRodLengthInMeters);
var startPoint = new Point3D(
endPoint.X - forward.X * rodLength,
endPoint.Y - forward.Y * rodLength,
endPoint.Z - forward.Z * rodLength);
AssemblyReferenceLine referenceLine = BuildAssemblyReferenceLine();
AssemblyReferencePathManager.Instance.CreateOrUpdateReferenceRod(
startPoint,
endPoint,
referenceLine.StartPoint,
referenceLine.EndPoint,
AssemblyReferenceRodDiameterInMeters);
_assemblyStartPoint = Point3D.Origin;
AssemblyStartPointText = "未选择";
RefreshAssemblyTerminalObjectInfo(startPoint, endPoint);
RefreshAssemblyTerminalObjectInfo(referenceLine.StartPoint, referenceLine.EndPoint);
RenderAssemblyAnchorMarker();
RenderAssemblyCenterGuideLine();
RenderAssemblyReferenceLine(referenceLine);
FocusOnAssemblyReferenceArea(referenceLine.StartPoint, referenceLine.EndPoint);
UpdateMainStatus("已生成直线装配参考杆,请在三维视图中确认位置");
UpdateMainStatus("已生成直线装配参考杆,请在三维视图中确认终点锚点、方向、外端位置和杆体贴合情况");
OnPropertyChanged(nameof(CanSelectAssemblyStartPoint));
LogManager.Info($"[直线装配] 已生成参考杆: {AssemblyTerminalObjectName}");
LogManager.Info(
$"[直线装配] 已生成参考杆: {AssemblyTerminalObjectName}, " +
$"终点锚点=({referenceLine.EndPoint.X:F2}, {referenceLine.EndPoint.Y:F2}, {referenceLine.EndPoint.Z:F2}), " +
$"参考线外端=({referenceLine.StartPoint.X:F2}, {referenceLine.StartPoint.Y:F2}, {referenceLine.StartPoint.Z:F2}), " +
$"方向=({referenceLine.Direction.X:F3}, {referenceLine.Direction.Y:F3}, {referenceLine.Direction.Z:F3}), " +
$"资源={AssemblyReferencePathManager.Instance.ReferenceResourceName}");
}, "生成装配参考杆");
}
@ -1391,14 +1395,16 @@ namespace NavisworksTransport.UI.WPF.ViewModels
CleanupAssemblyReferenceSelection();
AssemblyReferencePathManager.Instance.HideReferenceRod();
ClearAssemblyAnchorMarker();
ClearAssemblyCenterGuideLine();
ClearAssemblyReferenceLine();
AssemblyStartPointText = "未选择";
OnPropertyChanged(nameof(CanSelectAssemblyStartPoint));
UpdateMainStatus("已隐藏直线装配参考");
LogManager.Info("[直线装配] 已隐藏参考");
UpdateMainStatus("已隐藏直线装配参考线");
LogManager.Info("[直线装配] 已隐藏参考线");
}
catch (Exception ex)
{
LogManager.Error($"[直线装配] 隐藏参考失败: {ex.Message}");
LogManager.Error($"[直线装配] 隐藏参考线失败: {ex.Message}");
}
}
@ -1494,6 +1500,39 @@ namespace NavisworksTransport.UI.WPF.ViewModels
center.Z + up.Z * halfHeight * direction);
}
private AssemblyReferenceLine BuildAssemblyReferenceLine()
{
if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject))
{
throw new InvalidOperationException("终点箱体未设置或已失效,无法生成装配参考线");
}
BoundingBox3D bounds = _assemblyTerminalObject.BoundingBox();
Point3D centerPoint = bounds.Center;
Point3D endPoint = GetAssemblyTerminalAnchorPoint();
Vector3D direction = new Vector3D(centerPoint.X, centerPoint.Y, centerPoint.Z);
double directionLengthSquared = direction.X * direction.X + direction.Y * direction.Y + direction.Z * direction.Z;
if (directionLengthSquared < 1e-9)
{
throw new InvalidOperationException("箱体中心与世界原点重合,无法生成装配参考线方向");
}
direction = direction.Normalize();
double rodLength = UnitsConverter.ConvertFromMeters(AssemblyReferenceRodLengthInMeters);
Point3D startPoint = new Point3D(
endPoint.X + direction.X * rodLength,
endPoint.Y + direction.Y * rodLength,
endPoint.Z + direction.Z * rodLength);
LogManager.Info(
$"[直线装配] 参考线已计算,终点锚点=({endPoint.X:F2}, {endPoint.Y:F2}, {endPoint.Z:F2}), " +
$"箱体中心=({centerPoint.X:F2}, {centerPoint.Y:F2}, {centerPoint.Z:F2}), " +
$"参考线外端=({startPoint.X:F2}, {startPoint.Y:F2}, {startPoint.Z:F2}), " +
$"原点到箱体中心方向=({direction.X:F3}, {direction.Y:F3}, {direction.Z:F3})");
return new AssemblyReferenceLine(startPoint, endPoint, direction);
}
private void RefreshAssemblyTerminalObjectInfo(Point3D referenceStartPoint = null, Point3D referenceEndPoint = null)
{
if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject))
@ -1551,21 +1590,17 @@ namespace NavisworksTransport.UI.WPF.ViewModels
try
{
Point3D endPoint = GetAssemblyTerminalAnchorPoint();
Vector3D forward = ModelItemTransformHelper.GetForwardDirectionFromTransform(_assemblyTerminalObject.Transform);
double rodLength = UnitsConverter.ConvertFromMeters(AssemblyReferenceRodLengthInMeters);
Point3D startPoint = new Point3D(
endPoint.X - forward.X * rodLength,
endPoint.Y - forward.Y * rodLength,
endPoint.Z - forward.Z * rodLength);
AssemblyReferenceLine referenceLine = BuildAssemblyReferenceLine();
AssemblyReferencePathManager.Instance.CreateOrUpdateReferenceRod(
startPoint,
endPoint,
referenceLine.StartPoint,
referenceLine.EndPoint,
AssemblyReferenceRodDiameterInMeters);
RefreshAssemblyTerminalObjectInfo(startPoint, endPoint);
RefreshAssemblyTerminalObjectInfo(referenceLine.StartPoint, referenceLine.EndPoint);
RenderAssemblyAnchorMarker();
RenderAssemblyCenterGuideLine();
RenderAssemblyReferenceLine(referenceLine);
OnPropertyChanged(nameof(CanSelectAssemblyStartPoint));
}
catch (Exception ex)
@ -1574,6 +1609,82 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
}
private sealed class AssemblyReferenceLine
{
public AssemblyReferenceLine(Point3D startPoint, Point3D endPoint, Vector3D direction)
{
StartPoint = startPoint;
EndPoint = endPoint;
Direction = direction;
}
public Point3D StartPoint { get; }
public Point3D EndPoint { get; }
public Vector3D Direction { get; }
}
private static double CalculateDefaultAssemblyReferenceRodDiameterInMeters()
{
double standardRadiusInMeters = ConfigManager.Instance.Current.PathEditing.CellSizeMeters;
standardRadiusInMeters = Math.Max(0.1, Math.Min(0.5, standardRadiusInMeters));
return standardRadiusInMeters * 0.8;
}
private void FocusOnAssemblyReferenceArea(Point3D startPoint, Point3D endPoint)
{
try
{
double minX = Math.Min(startPoint.X, endPoint.X);
double minY = Math.Min(startPoint.Y, endPoint.Y);
double minZ = Math.Min(startPoint.Z, endPoint.Z);
double maxX = Math.Max(startPoint.X, endPoint.X);
double maxY = Math.Max(startPoint.Y, endPoint.Y);
double maxZ = Math.Max(startPoint.Z, endPoint.Z);
if (_assemblyTerminalObject != null && ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject))
{
var terminalBounds = _assemblyTerminalObject.BoundingBox();
minX = Math.Min(minX, terminalBounds.Min.X);
minY = Math.Min(minY, terminalBounds.Min.Y);
minZ = Math.Min(minZ, terminalBounds.Min.Z);
maxX = Math.Max(maxX, terminalBounds.Max.X);
maxY = Math.Max(maxY, terminalBounds.Max.Y);
maxZ = Math.Max(maxZ, terminalBounds.Max.Z);
}
Point3D center = new Point3D(
(minX + maxX) / 2.0,
(minY + maxY) / 2.0,
(minZ + maxZ) / 2.0);
double targetSize = Math.Max(
maxX - minX,
Math.Max(maxY - minY, maxZ - minZ));
targetSize = Math.Max(targetSize, UnitsConverter.ConvertFromMeters(1.0));
ViewpointHelper.FocusOnPosition(center, targetSize, 45.0, 0.35);
var selection = new List<ModelItem>();
if (_assemblyTerminalObject != null && ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject))
{
selection.Add(_assemblyTerminalObject);
}
var referenceRod = AssemblyReferencePathManager.Instance.CurrentReferenceRod;
if (referenceRod != null)
{
selection.Add(referenceRod);
}
NavisworksSelectionHelper.SetModelSelection(selection);
}
catch (Exception ex)
{
LogManager.Warning($"[直线装配] 聚焦参考杆区域失败: {ex.Message}");
}
}
private void RenderAssemblyAnchorMarker()
{
if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject))
@ -1588,6 +1699,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
Point3D anchorPoint = GetAssemblyTerminalAnchorPoint();
LogManager.Info(
$"[直线装配] 已渲染终点锚点标记: ({anchorPoint.X:F2}, {anchorPoint.Y:F2}, {anchorPoint.Z:F2})");
var markerRoute = new PathRoute("装配对接点")
{
Id = AssemblyAnchorMarkerPathId,
@ -1597,6 +1710,47 @@ namespace NavisworksTransport.UI.WPF.ViewModels
renderPlugin.RenderPointOnly(markerRoute);
}
private void RenderAssemblyReferenceLine(AssemblyReferenceLine referenceLine)
{
var renderPlugin = PathPointRenderPlugin.Instance;
if (renderPlugin == null || referenceLine == null)
{
return;
}
renderPlugin.RenderRailBaseline(
AssemblyReferenceLinePathId,
new List<Point3D> { referenceLine.EndPoint, referenceLine.StartPoint });
LogManager.Info(
$"[直线装配] 已渲染参考线: 终点锚点=({referenceLine.EndPoint.X:F2}, {referenceLine.EndPoint.Y:F2}, {referenceLine.EndPoint.Z:F2}), " +
$"参考线外端=({referenceLine.StartPoint.X:F2}, {referenceLine.StartPoint.Y:F2}, {referenceLine.StartPoint.Z:F2})");
}
private void RenderAssemblyCenterGuideLine()
{
if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject))
{
return;
}
var renderPlugin = PathPointRenderPlugin.Instance;
if (renderPlugin == null)
{
return;
}
Point3D centerPoint = _assemblyTerminalObject.BoundingBox().Center;
renderPlugin.RenderRailBaseline(
AssemblyCenterGuideLinePathId,
new List<Point3D> { Point3D.Origin, centerPoint },
RenderStyleName.AssemblyGuideLine);
LogManager.Info(
$"[直线装配] 已渲染原点到箱体中心基准线: 原点=(0.00, 0.00, 0.00), " +
$"箱体中心=({centerPoint.X:F2}, {centerPoint.Y:F2}, {centerPoint.Z:F2})");
}
private void ClearAssemblyAnchorMarker()
{
var renderPlugin = PathPointRenderPlugin.Instance;
@ -1608,6 +1762,28 @@ namespace NavisworksTransport.UI.WPF.ViewModels
renderPlugin.RemovePath(AssemblyAnchorMarkerPathId);
}
private void ClearAssemblyReferenceLine()
{
var renderPlugin = PathPointRenderPlugin.Instance;
if (renderPlugin == null)
{
return;
}
renderPlugin.ClearRailBaseline(AssemblyReferenceLinePathId);
}
private void ClearAssemblyCenterGuideLine()
{
var renderPlugin = PathPointRenderPlugin.Instance;
if (renderPlugin == null)
{
return;
}
renderPlugin.ClearRailBaseline(AssemblyCenterGuideLinePathId);
}
private void CleanupAssemblyReferenceSelection()
{
try
@ -5071,6 +5247,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Warning($"清理直线装配对接点标记时发生异常: {ex.Message}");
}
try
{
ClearAssemblyReferenceLine();
}
catch (Exception ex)
{
LogManager.Warning($"清理直线装配参考线时发生异常: {ex.Message}");
}
// 确保停止任何活动的点击工具
try
{

View File

@ -152,6 +152,50 @@ namespace NavisworksTransport.Utils
}
}
/// <summary>
/// 根据箱体局部尺寸和世界原点方向,推导“背离球心”的真实长轴方向。
/// 先选局部最长轴,再用箱体中心相对世界原点的方向决定正负。
/// </summary>
public static Vector3D GetLongestAxisDirectionAwayFromOrigin(BoundingBox3D bounds, Transform3D transform)
{
Vector3D localSize = EstimateLocalBoxSize(bounds, transform);
int longestAxisIndex = 0;
double longestAxisLength = localSize.X;
if (localSize.Y > longestAxisLength)
{
longestAxisIndex = 1;
longestAxisLength = localSize.Y;
}
if (localSize.Z > longestAxisLength)
{
longestAxisIndex = 2;
}
Vector3D axisDirection = GetAxisDirectionFromTransform(
transform,
longestAxisIndex,
longestAxisIndex == 0 ? new Vector3D(1, 0, 0) :
longestAxisIndex == 1 ? new Vector3D(0, 1, 0) :
new Vector3D(0, 0, 1));
Vector3D awayFromOrigin = new Vector3D(bounds.Center.X, bounds.Center.Y, bounds.Center.Z);
double awayLengthSquared = awayFromOrigin.X * awayFromOrigin.X + awayFromOrigin.Y * awayFromOrigin.Y + awayFromOrigin.Z * awayFromOrigin.Z;
if (awayLengthSquared < 1e-9)
{
return axisDirection;
}
double dot = axisDirection.X * awayFromOrigin.X + axisDirection.Y * awayFromOrigin.Y + axisDirection.Z * awayFromOrigin.Z;
if (dot < 0.0)
{
return new Vector3D(-axisDirection.X, -axisDirection.Y, -axisDirection.Z);
}
return axisDirection;
}
private static Vector3D GetAxisDirectionFromTransform(Transform3D transform, int axisIndex, Vector3D fallback)
{
if (transform == null)
@ -317,6 +361,168 @@ namespace NavisworksTransport.Utils
ApplyAbsoluteTransform(item, targetPosition, targetRotation, preserveCurrentScale: true);
}
/// <summary>
/// 将物体移动到指定中心点和完整三维朝向,同时保留当前缩放。
/// 适用于参考杆、辅助几何等以几何中心作为定位基准的场景。
/// </summary>
public static void MoveItemToCenterAndRotationWithCurrentScale(ModelItem item, Point3D targetCenter, Rotation3D targetRotation)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
var doc = Application.ActiveDocument;
var modelItems = new ModelItemCollection { item };
var currentComponents = item.Transform.Factor();
Vector3D currentScale = currentComponents.Scale;
doc.Models.ResetPermanentTransform(modelItems);
var originalBounds = item.BoundingBox();
Point3D originalCenter = originalBounds.Center;
var rotationTransform = new Transform3D(targetRotation);
var linear = rotationTransform.Linear;
var rotatedCenter = new Point3D(
linear.Get(0, 0) * originalCenter.X + linear.Get(0, 1) * originalCenter.Y + linear.Get(0, 2) * originalCenter.Z,
linear.Get(1, 0) * originalCenter.X + linear.Get(1, 1) * originalCenter.Y + linear.Get(1, 2) * originalCenter.Z,
linear.Get(2, 0) * originalCenter.X + linear.Get(2, 1) * originalCenter.Y + linear.Get(2, 2) * originalCenter.Z);
var identity = Transform3D.CreateTranslation(new Vector3D(0, 0, 0));
var components = identity.Factor();
components.Scale = currentScale;
components.Rotation = targetRotation;
components.Translation = new Vector3D(
targetCenter.X - rotatedCenter.X,
targetCenter.Y - rotatedCenter.Y,
targetCenter.Z - rotatedCenter.Z);
doc.Models.OverridePermanentTransform(modelItems, components.Combine(), false);
}
/// <summary>
/// 将物体移动到指定中心点和完整三维朝向。
/// 仅应用位置和旋转,不保留当前缩放。
/// 适用于缩放已经在 Model 层完成的参考杆等场景。
/// </summary>
public static void MoveItemToCenterAndRotation(ModelItem item, Point3D targetCenter, Rotation3D targetRotation)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
var doc = Application.ActiveDocument;
var modelItems = new ModelItemCollection { item };
doc.Models.ResetPermanentTransform(modelItems);
var originalBounds = item.BoundingBox();
Point3D originalCenter = originalBounds.Center;
var rotationTransform = new Transform3D(targetRotation);
var linear = rotationTransform.Linear;
var rotatedCenter = new Point3D(
linear.Get(0, 0) * originalCenter.X + linear.Get(0, 1) * originalCenter.Y + linear.Get(0, 2) * originalCenter.Z,
linear.Get(1, 0) * originalCenter.X + linear.Get(1, 1) * originalCenter.Y + linear.Get(1, 2) * originalCenter.Z,
linear.Get(2, 0) * originalCenter.X + linear.Get(2, 1) * originalCenter.Y + linear.Get(2, 2) * originalCenter.Z);
var translation = new Vector3D(
targetCenter.X - rotatedCenter.X,
targetCenter.Y - rotatedCenter.Y,
targetCenter.Z - rotatedCenter.Z);
doc.Models.OverridePermanentTransform(modelItems, new Transform3D(targetRotation, translation), false);
}
/// <summary>
/// 将物体的局部正 X 端面中心对齐到目标点,并保留当前缩放。
/// 适用于单位立方体/单位圆柱体沿 +X 定义长度方向的参考杆资源。
/// </summary>
public static void MoveItemPositiveXEndToPointAndRotationWithCurrentScale(ModelItem item, Point3D targetPoint, Rotation3D targetRotation)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
var doc = Application.ActiveDocument;
var modelItems = new ModelItemCollection { item };
var currentComponents = item.Transform.Factor();
Vector3D currentScale = currentComponents.Scale;
doc.Models.ResetPermanentTransform(modelItems);
var originalBounds = item.BoundingBox();
Point3D originalPositiveXEndCenter = new Point3D(
originalBounds.Max.X,
originalBounds.Center.Y,
originalBounds.Center.Z);
var rotationTransform = new Transform3D(targetRotation);
var linear = rotationTransform.Linear;
var rotatedPositiveXEndCenter = new Point3D(
linear.Get(0, 0) * originalPositiveXEndCenter.X + linear.Get(0, 1) * originalPositiveXEndCenter.Y + linear.Get(0, 2) * originalPositiveXEndCenter.Z,
linear.Get(1, 0) * originalPositiveXEndCenter.X + linear.Get(1, 1) * originalPositiveXEndCenter.Y + linear.Get(1, 2) * originalPositiveXEndCenter.Z,
linear.Get(2, 0) * originalPositiveXEndCenter.X + linear.Get(2, 1) * originalPositiveXEndCenter.Y + linear.Get(2, 2) * originalPositiveXEndCenter.Z);
var identity = Transform3D.CreateTranslation(new Vector3D(0, 0, 0));
var components = identity.Factor();
components.Scale = currentScale;
components.Rotation = targetRotation;
components.Translation = new Vector3D(
targetPoint.X - rotatedPositiveXEndCenter.X,
targetPoint.Y - rotatedPositiveXEndCenter.Y,
targetPoint.Z - rotatedPositiveXEndCenter.Z);
doc.Models.OverridePermanentTransform(modelItems, components.Combine(), false);
}
/// <summary>
/// 将物体的局部正 X 端面中心对齐到目标点。
/// 仅应用位置和旋转,不保留当前缩放。
/// 适用于缩放已经在 Model 层完成、Item 层只负责定位的场景。
/// </summary>
public static void MoveItemPositiveXEndToPointAndRotation(ModelItem item, Point3D targetPoint, Rotation3D targetRotation)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
var doc = Application.ActiveDocument;
var modelItems = new ModelItemCollection { item };
doc.Models.ResetPermanentTransform(modelItems);
var originalBounds = item.BoundingBox();
Point3D originalPositiveXEndCenter = new Point3D(
originalBounds.Max.X,
originalBounds.Center.Y,
originalBounds.Center.Z);
var rotationTransform = new Transform3D(targetRotation);
var linear = rotationTransform.Linear;
var rotatedPositiveXEndCenter = new Point3D(
linear.Get(0, 0) * originalPositiveXEndCenter.X + linear.Get(0, 1) * originalPositiveXEndCenter.Y + linear.Get(0, 2) * originalPositiveXEndCenter.Z,
linear.Get(1, 0) * originalPositiveXEndCenter.X + linear.Get(1, 1) * originalPositiveXEndCenter.Y + linear.Get(1, 2) * originalPositiveXEndCenter.Z,
linear.Get(2, 0) * originalPositiveXEndCenter.X + linear.Get(2, 1) * originalPositiveXEndCenter.Y + linear.Get(2, 2) * originalPositiveXEndCenter.Z);
var translation = new Vector3D(
targetPoint.X - rotatedPositiveXEndCenter.X,
targetPoint.Y - rotatedPositiveXEndCenter.Y,
targetPoint.Z - rotatedPositiveXEndCenter.Z);
doc.Models.OverridePermanentTransform(modelItems, new Transform3D(targetRotation, translation), false);
}
private static void ApplyAbsoluteTransform(ModelItem item, Point3D targetPosition, Rotation3D targetRotation, bool preserveCurrentScale)
{
var doc = Application.ActiveDocument;