Add rail mount placement controls and persistence

This commit is contained in:
tian 2026-03-26 01:01:01 +08:00
parent 921dc07856
commit 3a5693a453
20 changed files with 1002 additions and 775 deletions

View File

@ -58,6 +58,7 @@
<Compile Include="UnitTests\CoordinateSystem\CanonicalRailPoseBuilderTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\CanonicalPlanarPoseBuilderTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\ObjectSpaceOrientationHelperTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\ObjectStartPlacementRequestTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\CanonicalRailOffsetResolverTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\CanonicalTrackedPositionResolverTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\FragmentRepresentativePoseHelperTests.cs" />

View File

@ -336,6 +336,7 @@
<Compile Include="src\Utils\CoordinateSystem\CanonicalBounds3.cs" />
<Compile Include="src\Utils\CoordinateSystem\LocalAxisPoseBuilder.cs" />
<Compile Include="src\Utils\CoordinateSystem\LocalEulerRotationCorrection.cs" />
<Compile Include="src\Utils\CoordinateSystem\ObjectStartPlacementRequest.cs" />
<Compile Include="src\Utils\CoordinateSystem\CanonicalPlanarPoseBuilder.cs" />
<Compile Include="src\Utils\CoordinateSystem\CanonicalRailOffsetResolver.cs" />
<Compile Include="src\Utils\CoordinateSystem\CanonicalRailPoseBuilder.cs" />

View File

@ -91,6 +91,27 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
AssertVector(trackedCenter, 0.0, 0.0, 2.0);
}
[TestMethod]
public void RailNormalOffset_ShouldAddExtraDisplacementAlongRailNormal()
{
PathRoute route = new PathRoute
{
PathType = PathType.Rail,
RailMountMode = RailMountMode.OverRail,
RailPathDefinitionMode = RailPathDefinitionMode.RailCenterLine,
RailNormalOffset = 1.5
};
RailLocalFrame frame = new RailLocalFrame(Vector3.UnitX, Vector3.UnitY, Vector3.UnitZ);
Vector3 trackedCenter = CanonicalRailOffsetResolver.ResolveTrackedCenter(
route,
Vector3.Zero,
frame,
4.0);
AssertVector(trackedCenter, 0.0, 0.0, 3.5);
}
private static void AssertVector(Vector3 actual, double x, double y, double z)
{
Assert.AreEqual(x, actual.X, 1e-6);

View File

@ -0,0 +1,31 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NavisworksTransport.Utils.CoordinateSystem;
namespace NavisworksTransport.UnitTests.CoordinateSystem
{
[TestClass]
public class ObjectStartPlacementRequestTests
{
[TestMethod]
public void TranslationOnly_ShouldPreserveInitialPoseAndClearRotationCorrection()
{
var request = ObjectStartPlacementRequest.TranslationOnly;
Assert.AreEqual(ObjectStartPlacementMode.PreserveInitialPose, request.PlacementMode);
Assert.IsTrue(request.PreserveInitialPose);
Assert.AreEqual(LocalEulerRotationCorrection.Zero, request.RotationCorrection);
}
[TestMethod]
public void RotationCorrectionRequest_ShouldKeepCorrectionAndAlignToPathPose()
{
var correction = new LocalEulerRotationCorrection(15.0, 30.0, 45.0);
var request = ObjectStartPlacementRequest.CreateRotationCorrection(correction);
Assert.AreEqual(ObjectStartPlacementMode.AlignToPathPose, request.PlacementMode);
Assert.IsFalse(request.PreserveInitialPose);
Assert.AreEqual(correction, request.RotationCorrection);
}
}
}

View File

@ -38,6 +38,7 @@ namespace NavisworksTransport.UnitTests.Core
""pathType"": ""Rail"",
""railMountMode"": ""OverRail"",
""railPathDefinitionMode"": ""RailCenterLine"",
""railNormalOffset"": 0.25,
""railPreferredNormal"": { ""x"": -0.2, ""y"": 0.5, ""z"": 0.8 },
""totalLength"": 10.0,
""objectLimits"": { ""maxLength"": 0, ""maxWidth"": 0, ""maxHeight"": 0, ""safetyMargin"": 0 },
@ -57,6 +58,7 @@ namespace NavisworksTransport.UnitTests.Core
var importedRoutes = manager.ImportFromJson(filePath);
Assert.AreEqual(1, importedRoutes.Count);
Assert.IsNotNull(importedRoutes[0].RailPreferredNormal);
Assert.AreEqual(0.25, importedRoutes[0].RailNormalOffset, 1e-6);
}
finally
{
@ -78,7 +80,7 @@ namespace NavisworksTransport.UnitTests.Core
<PathPlanningData xmlns=""http://www.3ds.com/delmia/pathplanning"" version=""1.0"" generator=""test"" timestamp=""2026-03-25T00:00:00"">
<ProjectInfo name=""test"" description=""test"" units=""meters"" coordinateSystem=""Global"" />
<Routes>
<Route id=""route-1"" name=""rail-test"" description="""" pathType=""Rail"" railMountMode=""OverRail"" railPathDefinitionMode=""RailCenterLine"" railPreferredNormalX=""0.0"" railPreferredNormalY=""1.0"" railPreferredNormalZ=""-0.5"" totalLength=""10.0"" maxObjectLength=""0.0"" maxObjectWidth=""0.0"" maxObjectHeight=""0.0"" safetyMargin=""0.0"" gridSize=""1.0"" liftHeight=""0.0"" created=""2026-03-25T00:00:00"">
<Route id=""route-1"" name=""rail-test"" description="""" pathType=""Rail"" railMountMode=""OverRail"" railPathDefinitionMode=""RailCenterLine"" railNormalOffset=""0.25"" railPreferredNormalX=""0.0"" railPreferredNormalY=""1.0"" railPreferredNormalZ=""-0.5"" totalLength=""10.0"" maxObjectLength=""0.0"" maxObjectWidth=""0.0"" maxObjectHeight=""0.0"" safetyMargin=""0.0"" gridSize=""1.0"" liftHeight=""0.0"" created=""2026-03-25T00:00:00"">
<Points>
<Point id=""p1"" name=""start"" type=""StartPoint"" index=""0"" x=""0"" y=""0"" z=""0"" created=""2026-03-25T00:00:00"" />
<Point id=""p2"" name=""end"" type=""EndPoint"" index=""1"" x=""10"" y=""0"" z=""0"" created=""2026-03-25T00:00:00"" />
@ -91,6 +93,7 @@ namespace NavisworksTransport.UnitTests.Core
var importedRoutes = manager.ImportFromXml(filePath);
Assert.AreEqual(1, importedRoutes.Count);
Assert.IsNotNull(importedRoutes[0].RailPreferredNormal);
Assert.AreEqual(0.25, importedRoutes[0].RailNormalOffset, 1e-6);
}
finally
{

File diff suppressed because it is too large Load Diff

View File

@ -9,13 +9,27 @@
- `YUp` 模型下,终端安装仿真、`Rail` 姿态、真实物体与虚拟物体通行空间、起点与动画主链路已基本跑通。
- 地面路径在 `YUp` 模型下:
- 虚拟物体起点姿态、动画姿态、转弯姿态已恢复正常。
- 当前已禁止地面/吊装路径偷偷退回旧 `yaw` 链路。
- 真实物体地面路径当前已恢复到可用状态:
- 起点落位补偿已接入;
- 逐帧补偿会随当前目标姿态一起旋转,不再使用固定世界补偿矢量;
- 动画结束后不再因恢复链或重复落最后一帧而再次跳动。
- `Ground + 真实物体` 的逐帧姿态约束已经明确:
- 动画播放阶段只允许绕宿主 `up` 轴转动;
- 不再在播放阶段每帧重建完整三维姿态。
- `Ground/Hoisting + 真实物体` 的前进轴语义当前已固定:
- 对象级前进轴统一按 `PositiveX` 解释;
- 不再因为路径初始方向更偏 `Z` 就把对象前进轴自动切换成 `PositiveZ`
- 当前已禁止地面/吊装路径偷偷退回旧 `yaw` 链路。
- 碰撞检测/恢复主链路已稳定:
- `ClashDetective` 三维恢复不能再先 `ResetPermanentTransform`
- 碰撞恢复、自动报告、自动截图已重新对齐到动画主链路。
- 虚拟物体资源问题已确认并修复:
- 旧 `unit_cube.nwc` 局部几何中心不在原点,会导致虚拟物体中心偏差。
- 新 `unit_cube.nwc` 已替换为几何中心在原点的版本。
- 旋转适配入口当前稳定分流:
- 虚拟物体继续走 `HostCoordinateAdapter``Legacy` 入口;
- 真实物体走 `Direct` 入口;
- 两者不能再强行共用同一条旋转转换链。
## 2. 当前坐标系架构
@ -63,6 +77,10 @@
- 当前规则:
- 先解释真实物体参考姿态,再做路径对齐
- 不能跳过这一步,直接拿 fragment 世界轴去猜业务姿态
- 当前补充规则:
- `Ground/Hoisting` 的真实物体“对象前进轴”与 fragment 参考姿态解释是两层语义;
- fragment 负责解释真实参考姿态;
- 对象前进轴当前在 `Ground/Hoisting` 上固定按 `PositiveX` 语义使用,不再做“按路径方向选最近轴”的自动切换。
### 4.1 Rotation / 矩阵语义
@ -102,6 +120,15 @@
- 目前稳定可用的是:`Y` 轴转动。
- `X / Z` 轴在起点静态预览或直线段里可能看起来合理,但路径一旦拐弯,逐帧按宿主世界轴重算会让“俯仰/侧倾”语义发生耦合,表现成不符合现场直觉的侧旋。
- 因此当前阶段,对地面路径应按“只稳定支持 `Y` 轴转动”使用;`X / Z` 轴播放链问题留待后续专项修复。
- 当前新增稳定规则2026-03-25
- `Ground + 真实物体` 的播放阶段不能再每帧应用完整三维姿态;
- 必须以起点基姿态为基线,只叠加宿主 `up` 轴上的单轴旋转。
- 当前新增稳定规则2026-03-25
- `Ground + 真实物体` 的起点目标点语义仍然是路径跟踪点,不允许修改路径点本身;
- 如真实物体起点落位存在固定偏差,应把补偿施加到物体位置,而不是回写路径点或路径跟踪点语义。
- 当前新增稳定规则2026-03-25
- `Ground + 真实物体` 的补偿不是固定世界矢量;
- 起点测得的偏差必须随当前目标姿态一起旋转后,再参与逐帧位置应用。
- `YUp` 吊装路径创建主链路已补齐到宿主坐标适配架构:
- 提升、水平移动、下降、终点落地都不能再把世界 `Z` 硬编码成“向上”。
- 终点必须使用用户最后一次点击的地面点,不能回填起点地面高程。
@ -223,10 +250,35 @@
- 保存/恢复姿态
- 关键碰撞恢复
- 虚拟物体应用后中心/偏差
- 真实物体地面路径:
- `[路径起点诊断]`
- `[路径起点补偿]`
- `[Ground路径补偿]`
- 已降级或删除:
- 大量重复逐帧宿主姿态轴日志
- 虚拟物体 `Transform` 即时读回日志(容易误导)
## 6. 当前 Navisworks 变换 API 结论
- `ModelItem.Transform`
- 只表示原始设计文件变换;
- 不反映 `OverridePermanentTransform` 后的当前显示姿态。
- `ModelGeometry.ActiveTransform`
- 是当前几何实际生效的变换;
- 当前项目里,凡是要读“当前姿态/当前位置”,优先使用这一层。
- `ModelGeometry`
- 关键四层语义:
- `OriginalTransform`
- `PermanentOverrideTransform`
- `PermanentTransform`
- `ActiveTransform`
- `OverridePermanentTransform(...)`
- 官方语义是增量变换;
- 不是“直接设成最终世界姿态”。
- `ResetPermanentTransform(...)`
- 清掉的是永久增量层;
- 不会修改原始设计文件变换。
## 6. 当前还值得继续观察的点
- 空轨路径里仍有一个旧 warning

View File

@ -205,6 +205,9 @@ namespace NavisworksTransport.Core.Animation
private Rotation3D _savedObjectRotation = Rotation3D.Identity;
private bool _savedObjectHasCustomRotation = false;
private bool _hasSavedObjectState = false;
private ObjectStartPlacementMode _objectStartPlacementMode = ObjectStartPlacementMode.AlignToPathPose;
private Rotation3D _railPreservedPoseRotation = Rotation3D.Identity;
private bool _hasRailPreservedPoseRotation = false;
private bool IsVirtualObjectMode => _animatedObjectMode == AnimatedObjectMode.VirtualObject;
private bool IsRealObjectMode => _animatedObjectMode == AnimatedObjectMode.RealObject;
@ -637,6 +640,8 @@ namespace NavisworksTransport.Core.Animation
/// <returns>是否成功移动</returns>
public bool MoveObjectToPathStart(ModelItem animatedObject = null, List<Point3D> pathPoints = null)
{
_objectStartPlacementMode = ObjectStartPlacementMode.AlignToPathPose;
// 前置检查:必须有路径
if (_route == null)
{
@ -859,6 +864,97 @@ namespace NavisworksTransport.Core.Animation
}
}
public bool MoveObjectToPathStartPreservingInitialPose(ModelItem animatedObject = null, List<Point3D> pathPoints = null)
{
_objectStartPlacementMode = ObjectStartPlacementMode.PreserveInitialPose;
if (_route == null)
{
LogManager.Error("[平移到起点] 路径为空,无法移动物体到路径起点");
return false;
}
try
{
if (CurrentControlledObject != null || IsVirtualObjectMode)
{
RestoreObjectToCADPosition();
LogManager.Info($"[平移到起点] 已恢复物体到原始状态, _currentYaw={_currentYaw * 180 / Math.PI:F2}°");
}
if (animatedObject != null)
{
_animatedObject = animatedObject;
bool isVirtualObject =
VirtualObjectManager.Instance.IsVirtualObjectActive &&
ReferenceEquals(animatedObject, VirtualObjectManager.Instance.CurrentVirtualObject);
_animatedObjectMode = isVirtualObject
? AnimatedObjectMode.VirtualObject
: AnimatedObjectMode.RealObject;
ResetRealObjectReferenceRotation();
_originalTransform = animatedObject.Transform;
_originalCenter = animatedObject.BoundingBox().Center;
_trackedPosition = GetTrackedObjectPosition(animatedObject);
if (isVirtualObject)
{
_currentYaw = ModelItemTransformHelper.GetYawFromTransform(_originalTransform);
_trackedRotation = _originalTransform.Factor().Rotation;
_hasTrackedRotation = true;
}
else
{
SyncTrackedRotationToObjectReference(animatedObject, isVirtualObject: false);
}
}
if (pathPoints != null)
{
_pathPoints = pathPoints;
}
if (_pathPoints == null || _pathPoints.Count < 2)
{
LogManager.Warning("[平移到起点] 没有可用的路径点");
return false;
}
Point3D pathStartPoint = _pathPoints[0];
Point3D startPosition = pathStartPoint;
if (_route.PathType == PathType.Hoisting)
{
startPosition = ResolveGroundTrackedCenter(startPosition, GetAnimatedObjectGroundContactHeight());
}
else if (_route.PathType == PathType.Rail)
{
Point3D previousPoint = _pathPoints[0];
Point3D nextPoint = _pathPoints.Count > 1 ? _pathPoints[1] : _pathPoints[0];
double objectHeight = GetAnimatedObjectRailNormalExtent(previousPoint, _pathPoints[0], nextPoint);
startPosition = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(_route, startPosition, previousPoint, nextPoint, objectHeight);
}
else
{
startPosition = ResolveGroundTrackedCenter(startPosition, GetAnimatedObjectGroundContactHeight());
}
UpdateObjectPosition(startPosition);
_hasGroundRealObjectBasePose = false;
_groundRealObjectStartCompensation = new Vector3D(0, 0, 0);
_hasGroundRealObjectStartCompensation = false;
var startAppliedPoint = GetTrackedObjectPosition(CurrentControlledObject ?? _animatedObject);
LogManager.Info(
$"[平移到起点] 已保持初始位姿移动到起点: 路径point0=({pathStartPoint.X:F3},{pathStartPoint.Y:F3},{pathStartPoint.Z:F3}), " +
$"目标trackedPoint=({startPosition.X:F3},{startPosition.Y:F3},{startPosition.Z:F3}), " +
$"实际trackedPoint=({startAppliedPoint.X:F3},{startAppliedPoint.Y:F3},{startAppliedPoint.Z:F3}), 路径类型={_route.PathType.GetDisplayName()}");
return true;
}
catch (Exception ex)
{
LogManager.Error($"保持初始位姿平移到路径起点失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 预计算所有动画帧和碰撞信息
/// </summary>
@ -872,7 +968,20 @@ namespace NavisworksTransport.Core.Animation
if (_animatedObject != null && _route != null && _route.Points != null && _route.Points.Count > 0)
{
var pathPoints = _route.Points.Select(p => p.Position).ToList();
MoveObjectToPathStart(_animatedObject, pathPoints);
MoveObjectToPathStartUsingCurrentPlacementMode(_animatedObject, pathPoints);
if (_route.PathType == PathType.Rail &&
_objectStartPlacementMode == ObjectStartPlacementMode.PreserveInitialPose &&
_hasTrackedRotation)
{
_railPreservedPoseRotation = _trackedRotation;
_hasRailPreservedPoseRotation = true;
LogManager.Info("[预计算] Rail平移模式已锁定起点姿态为整段动画旋转基线");
}
else
{
_railPreservedPoseRotation = Rotation3D.Identity;
_hasRailPreservedPoseRotation = false;
}
LogManager.Info("[预计算] 物体已移动到路径起点");
}
@ -1169,11 +1278,18 @@ namespace NavisworksTransport.Core.Animation
};
if (_route.PathType == PathType.Rail &&
TryCreateRailPathRotation(
previousFramePoint,
framePosition,
nextFramePoint,
out var railRotation))
_objectStartPlacementMode == ObjectStartPlacementMode.PreserveInitialPose &&
_hasRailPreservedPoseRotation)
{
frame.Rotation = _railPreservedPoseRotation;
frame.HasCustomRotation = true;
}
else if (_route.PathType == PathType.Rail &&
TryCreateRailPathRotation(
previousFramePoint,
framePosition,
nextFramePoint,
out var railRotation))
{
frame.Rotation = railRotation;
frame.HasCustomRotation = true;
@ -1622,7 +1738,7 @@ namespace NavisworksTransport.Core.Animation
if (needsReset)
{
LogManager.Info($"[动画开始] 物体不在起点,重置到起点: {mismatchSummary}");
MoveObjectToPathStart();
MoveObjectToPathStartUsingCurrentPlacementMode();
}
}
@ -4623,6 +4739,8 @@ namespace NavisworksTransport.Core.Animation
/// </summary>
public void SetObjectRotationCorrection(LocalEulerRotationCorrection rotationCorrection)
{
_objectStartPlacementMode = ObjectStartPlacementMode.AlignToPathPose;
_hasRailPreservedPoseRotation = false;
_objectRotationCorrection = rotationCorrection;
// 如果动画已创建,更新物体到起点的朝向
@ -4658,6 +4776,23 @@ namespace NavisworksTransport.Core.Animation
LogManager.Debug($"[角度修正] 直接设置角度修正值: {_objectRotationCorrection}(不触发旋转)");
}
public void SetObjectStartPlacementMode(ObjectStartPlacementMode placementMode)
{
_objectStartPlacementMode = placementMode;
if (placementMode != ObjectStartPlacementMode.PreserveInitialPose)
{
_hasRailPreservedPoseRotation = false;
}
LogManager.Debug($"[起点摆放] 当前模式已设置为: {_objectStartPlacementMode}");
}
private bool MoveObjectToPathStartUsingCurrentPlacementMode(ModelItem animatedObject = null, List<Point3D> pathPoints = null)
{
return _objectStartPlacementMode == ObjectStartPlacementMode.PreserveInitialPose
? MoveObjectToPathStartPreservingInitialPose(animatedObject, pathPoints)
: MoveObjectToPathStart(animatedObject, pathPoints);
}
/// <summary>
/// 保留给旧单轴调用方:按当前宿主 up 轴映射成三轴角度修正。
/// </summary>

View File

@ -50,6 +50,7 @@ namespace NavisworksTransport
public string pathType { get; set; }
public string railMountMode { get; set; }
public string railPathDefinitionMode { get; set; }
public double railNormalOffset { get; set; }
public JsonVector3 railPreferredNormal { get; set; }
public double totalLength { get; set; }
public JsonObjectLimits objectLimits { get; set; }
@ -287,6 +288,7 @@ namespace NavisworksTransport
pathType = route.PathType.ToString(),
railMountMode = route.RailMountMode.ToString(),
railPathDefinitionMode = route.RailPathDefinitionMode.ToString(),
railNormalOffset = Math.Round(route.RailNormalOffset, exportSettings?.Precision ?? 3),
railPreferredNormal = route.RailPreferredNormal != null ? new
{
x = Math.Round(route.RailPreferredNormal.X, exportSettings?.Precision ?? 3),
@ -542,6 +544,8 @@ namespace NavisworksTransport
route.RailPathDefinitionMode = railPathDefinitionMode;
}
route.RailNormalOffset = jsonRoute.railNormalOffset;
if (jsonRoute.railPreferredNormal != null)
{
route.RailPreferredNormal = new Point3D(
@ -1409,6 +1413,7 @@ namespace NavisworksTransport
routeElement.SetAttribute("pathType", route.PathType.ToString());
routeElement.SetAttribute("railMountMode", route.RailMountMode.ToString());
routeElement.SetAttribute("railPathDefinitionMode", route.RailPathDefinitionMode.ToString());
routeElement.SetAttribute("railNormalOffset", route.RailNormalOffset.ToString("F3"));
if (route.RailPreferredNormal != null)
{
routeElement.SetAttribute("railPreferredNormalX", route.RailPreferredNormal.X.ToString("F3"));
@ -1557,6 +1562,11 @@ namespace NavisworksTransport
route.RailPathDefinitionMode = railPathDefinitionMode;
}
if (double.TryParse(routeNode.Attributes?["railNormalOffset"]?.Value, out var railNormalOffset))
{
route.RailNormalOffset = railNormalOffset;
}
if (double.TryParse(routeNode.Attributes?["railPreferredNormalX"]?.Value, out var railPreferredNormalX) &&
double.TryParse(routeNode.Attributes?["railPreferredNormalY"]?.Value, out var railPreferredNormalY) &&
double.TryParse(routeNode.Attributes?["railPreferredNormalZ"]?.Value, out var railPreferredNormalZ))

View File

@ -101,6 +101,7 @@ namespace NavisworksTransport
LiftHeight REAL,
RailMountMode INTEGER,
RailPathDefinitionMode INTEGER,
RailNormalOffset REAL,
RailPreferredNormalX REAL,
RailPreferredNormalY REAL,
RailPreferredNormalZ REAL,
@ -466,8 +467,8 @@ namespace NavisworksTransport
// 路径长度由 PathRoute.TotalLength 计算属性实时从 Edges/Points 计算
var sql = @"
INSERT OR REPLACE INTO PathRoutes
(Id, Name, EstimatedTime, TurnRadius, IsCurved, MaxObjectLength, MaxObjectWidth, MaxObjectHeight, SafetyMargin, GridSize, PathType, LiftHeight, RailMountMode, RailPathDefinitionMode, RailPreferredNormalX, RailPreferredNormalY, RailPreferredNormalZ, CreatedTime, LastModified)
VALUES (@id, @name, @time, @turnRadius, @isCurved, @maxLength, @maxWidth, @maxHeight, @safetyMargin, @gridSize, @pathType, @liftHeightMeters, @railMountMode, @railPathDefinitionMode, @railPreferredNormalX, @railPreferredNormalY, @railPreferredNormalZ, @created, @modified)
(Id, Name, EstimatedTime, TurnRadius, IsCurved, MaxObjectLength, MaxObjectWidth, MaxObjectHeight, SafetyMargin, GridSize, PathType, LiftHeight, RailMountMode, RailPathDefinitionMode, RailNormalOffset, RailPreferredNormalX, RailPreferredNormalY, RailPreferredNormalZ, CreatedTime, LastModified)
VALUES (@id, @name, @time, @turnRadius, @isCurved, @maxLength, @maxWidth, @maxHeight, @safetyMargin, @gridSize, @pathType, @liftHeightMeters, @railMountMode, @railPathDefinitionMode, @railNormalOffset, @railPreferredNormalX, @railPreferredNormalY, @railPreferredNormalZ, @created, @modified)
";
using (var cmd = new SQLiteCommand(sql, _connection))
@ -486,6 +487,7 @@ namespace NavisworksTransport
cmd.Parameters.AddWithValue("@liftHeightMeters", route.LiftHeight);
cmd.Parameters.AddWithValue("@railMountMode", (int)route.RailMountMode);
cmd.Parameters.AddWithValue("@railPathDefinitionMode", (int)route.RailPathDefinitionMode);
cmd.Parameters.AddWithValue("@railNormalOffset", route.RailNormalOffset);
cmd.Parameters.AddWithValue("@railPreferredNormalX", route.RailPreferredNormal != null ? (object)route.RailPreferredNormal.X : DBNull.Value);
cmd.Parameters.AddWithValue("@railPreferredNormalY", route.RailPreferredNormal != null ? (object)route.RailPreferredNormal.Y : DBNull.Value);
cmd.Parameters.AddWithValue("@railPreferredNormalZ", route.RailPreferredNormal != null ? (object)route.RailPreferredNormal.Z : DBNull.Value);
@ -1546,6 +1548,7 @@ namespace NavisworksTransport
LiftHeight = Convert.ToDouble(reader["LiftHeight"]),
RailMountMode = (RailMountMode)Convert.ToInt32(reader["RailMountMode"]),
RailPathDefinitionMode = (RailPathDefinitionMode)Convert.ToInt32(reader["RailPathDefinitionMode"]),
RailNormalOffset = reader["RailNormalOffset"] != DBNull.Value ? Convert.ToDouble(reader["RailNormalOffset"]) : 0.0,
CreatedTime = Convert.ToDateTime(reader["CreatedTime"]),
LastModified = Convert.ToDateTime(reader["LastModified"])
};
@ -2737,8 +2740,8 @@ namespace NavisworksTransport
cmd.CommandText = @"
SELECT Id, Name, CreatedTime, LastModified,
TurnRadius, IsCurved, MaxObjectLength, MaxObjectWidth, MaxObjectHeight,
SafetyMargin, GridSize, PathType, LiftHeight, RailMountMode, RailPathDefinitionMode,
RailPreferredNormalX, RailPreferredNormalY, RailPreferredNormalZ
SafetyMargin, GridSize, PathType, LiftHeight, RailMountMode, RailPathDefinitionMode,
RailNormalOffset, RailPreferredNormalX, RailPreferredNormalY, RailPreferredNormalZ
FROM PathRoutes
WHERE Id = @Id";
@ -2764,7 +2767,8 @@ namespace NavisworksTransport
PathType = (PathType)Convert.ToInt32(reader["PathType"]),
LiftHeight = Convert.ToDouble(reader["LiftHeight"]),
RailMountMode = (RailMountMode)Convert.ToInt32(reader["RailMountMode"]),
RailPathDefinitionMode = (RailPathDefinitionMode)Convert.ToInt32(reader["RailPathDefinitionMode"])
RailPathDefinitionMode = (RailPathDefinitionMode)Convert.ToInt32(reader["RailPathDefinitionMode"]),
RailNormalOffset = reader["RailNormalOffset"] != DBNull.Value ? Convert.ToDouble(reader["RailNormalOffset"]) : 0.0
};
if (reader["RailPreferredNormalX"] != DBNull.Value &&
@ -2812,7 +2816,8 @@ namespace NavisworksTransport
{
["railmountmode"] = "ALTER TABLE PathRoutes ADD COLUMN RailMountMode INTEGER DEFAULT 0",
["railpathdefinitionmode"] = "ALTER TABLE PathRoutes ADD COLUMN RailPathDefinitionMode INTEGER DEFAULT 0",
["railpreferrednormalx"] = "ALTER TABLE PathRoutes ADD COLUMN RailPreferredNormalX REAL",
["railnormaloffset"] = "ALTER TABLE PathRoutes ADD COLUMN RailNormalOffset REAL DEFAULT 0",
["railpreferrednormalx"] = "ALTER TABLE PathRoutes ADD COLUMN RailPreferredNormalX REAL",
["railpreferrednormaly"] = "ALTER TABLE PathRoutes ADD COLUMN RailPreferredNormalY REAL",
["railpreferrednormalz"] = "ALTER TABLE PathRoutes ADD COLUMN RailPreferredNormalZ REAL"
};

View File

@ -1271,6 +1271,7 @@ namespace NavisworksTransport
route.LiftHeight = loadedRoute.LiftHeight;
route.RailMountMode = loadedRoute.RailMountMode;
route.RailPathDefinitionMode = loadedRoute.RailPathDefinitionMode;
route.RailNormalOffset = loadedRoute.RailNormalOffset;
route.RailPreferredNormal = loadedRoute.RailPreferredNormal != null
? new Point3D(
loadedRoute.RailPreferredNormal.X,

View File

@ -743,6 +743,12 @@ namespace NavisworksTransport
/// </summary>
public Point3D RailPreferredNormal { get; set; }
/// <summary>
/// Rail 安装法向偏移(模型单位)。
/// 沿 Rail 最终法向对安装参考点施加额外偏移,用于快速微调路径。
/// </summary>
public double RailNormalOffset { get; set; }
// 数据库分析相关属性
/// <summary>
/// 碰撞数量(从数据库加载)
@ -784,6 +790,7 @@ namespace NavisworksTransport
RailMountMode = RailMountMode.UnderRail;
RailPathDefinitionMode = RailPathDefinitionMode.LegacySuspensionPoint;
RailPreferredNormal = null;
RailNormalOffset = 0.0;
}
/// <summary>
@ -807,6 +814,7 @@ namespace NavisworksTransport
RailMountMode = RailMountMode.UnderRail;
RailPathDefinitionMode = RailPathDefinitionMode.LegacySuspensionPoint;
RailPreferredNormal = null;
RailNormalOffset = 0.0;
}
/// <summary>
@ -1042,6 +1050,7 @@ namespace NavisworksTransport
SafetyMargin = SafetyMargin,
RailMountMode = RailMountMode,
RailPathDefinitionMode = RailPathDefinitionMode,
RailNormalOffset = RailNormalOffset,
RailPreferredNormal = RailPreferredNormal != null
? new Point3D(RailPreferredNormal.X, RailPreferredNormal.Y, RailPreferredNormal.Z)
: null

View File

@ -2196,7 +2196,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels
var dialog = new Views.EditRotationWindow(_objectRotationCorrection);
if (dialog.ShowDialog() == true)
{
ObjectRotationCorrection = dialog.RotationCorrection;
var placementRequest = dialog.AdjustmentRequest;
if (placementRequest.PreserveInitialPose)
{
ApplyTranslationOnlyObjectPlacement();
return;
}
_pathAnimationManager?.SetObjectStartPlacementMode(ObjectStartPlacementMode.AlignToPathPose);
ObjectRotationCorrection = placementRequest.RotationCorrection;
LogManager.Info($"物体角度修正已更新: {_objectRotationCorrection}");
UpdateMainStatus($"物体角度修正: {_objectRotationCorrection}");
@ -2214,6 +2222,40 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
}
private void ApplyTranslationOnlyObjectPlacement()
{
try
{
if (_pathAnimationManager == null || !HasSelectedAnimatedObject)
{
return;
}
_objectRotationCorrection = LocalEulerRotationCorrection.Zero;
_pathAnimationManager.SetObjectRotationCorrectionDirect(LocalEulerRotationCorrection.Zero);
_pathAnimationManager.SetObjectStartPlacementMode(ObjectStartPlacementMode.PreserveInitialPose);
OnPropertyChanged(nameof(ObjectRotationCorrection));
if (_pathAnimationManager.MoveObjectToPathStartPreservingInitialPose())
{
LogManager.Info("[角度修正] 已按平移模式将物体移动到路径起点,保持初始位姿");
UpdateMainStatus("物体已平移到路径起点并保持初始位姿");
}
else
{
LogManager.Warning("[角度修正] 平移模式未能将物体移动到路径起点");
UpdateMainStatus("平移模式执行失败");
}
UpdatePassageSpaceVisualization();
}
catch (Exception ex)
{
LogManager.Error($"平移到路径起点失败: {ex.Message}");
UpdateMainStatus($"平移到路径起点失败: {ex.Message}");
}
}
/// <summary>
/// 更新物体旋转(应用角度修正)
/// </summary>

View File

@ -125,6 +125,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private const double DefaultAssemblyReferenceRodLengthInMeters = 20.0;
private static readonly double DefaultAssemblyReferenceRodDiameterInMeters = CalculateDefaultAssemblyReferenceRodDiameterInMeters();
private const double DefaultAssemblyAnchorVerticalOffsetInMeters = 0.0;
private const double RailNormalOffsetNudgeStepInMeters = 0.1;
private const double DefaultAssemblySphereCenterX = 0.0;
private const double DefaultAssemblySphereCenterY = 0.0;
private const double DefaultAssemblySphereCenterZ = 0.0;
@ -267,6 +268,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
OnPropertyChanged(nameof(CanUsePathLines));
OnPropertyChanged(nameof(IsRailRouteSelected));
OnPropertyChanged(nameof(SelectedRailMountMode));
OnPropertyChanged(nameof(SelectedRailNormalOffsetInMeters));
if (!CanUsePathLines && ShowPathLines)
{
// 吊装和空轨路径不能使用路径线,自动关闭
@ -348,6 +350,38 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
}
public double SelectedRailNormalOffsetInMeters
{
get
{
var coreRoute = GetSelectedCoreRoute();
if (coreRoute == null || coreRoute.PathType != PathType.Rail)
{
return 0.0;
}
return UnitsConverter.ConvertToMeters(coreRoute.RailNormalOffset);
}
set
{
var coreRoute = GetSelectedCoreRoute();
if (coreRoute == null || coreRoute.PathType != PathType.Rail)
{
return;
}
double offsetInModelUnits = UnitsConverter.ConvertFromMeters(value);
if (Math.Abs(coreRoute.RailNormalOffset - offsetInModelUnits) < 1e-6)
{
return;
}
coreRoute.RailNormalOffset = offsetInModelUnits;
ApplyRailRouteConfigurationChange(coreRoute, $"安装法向偏移已更新为 {value:F3} 米");
OnPropertyChanged(nameof(SelectedRailNormalOffsetInMeters));
}
}
public string AssemblyTerminalObjectName
{
get => _assemblyTerminalObjectName;
@ -992,6 +1026,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ICommand SelectAssemblyInstallationPointCommand { get; private set; }
public ICommand ClearAssemblyReferenceRodCommand { get; private set; }
public ICommand AnalyzeAssemblyTerminalFaceCommand { get; private set; }
public ICommand DecreaseSelectedRailNormalOffsetCommand { get; private set; }
public ICommand IncreaseSelectedRailNormalOffsetCommand { get; private set; }
// 多层吊装命令
public ICommand SelectMultiLevelStartPointCommand { get; private set; }
@ -1196,6 +1232,19 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Info($"[Rail构型] {coreRoute.Name}: {logMessage}");
}
private void AdjustSelectedRailNormalOffset(double deltaInMeters)
{
var coreRoute = GetSelectedCoreRoute();
if (coreRoute == null || coreRoute.PathType != PathType.Rail)
{
return;
}
double currentOffsetInMeters = UnitsConverter.ConvertToMeters(coreRoute.RailNormalOffset);
double nextOffsetInMeters = Math.Round(currentOffsetInMeters + deltaInMeters, 3);
SelectedRailNormalOffsetInMeters = nextOffsetInMeters;
}
/// <summary>
/// 从配置文件加载参数
/// </summary>
@ -1336,6 +1385,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
SelectAssemblyInstallationPointCommand = new RelayCommand(async () => await ExecuteSelectAssemblyInstallationPointAsync(), () => CanSelectAssemblyInstallationPoint);
ClearAssemblyReferenceRodCommand = new RelayCommand(() => ExecuteClearAssemblyReferenceRod());
AnalyzeAssemblyTerminalFaceCommand = new RelayCommand(async () => await ExecuteAnalyzeAssemblyTerminalFaceAsync(), () => CanAnalyzeAssemblyTerminalFace);
DecreaseSelectedRailNormalOffsetCommand = new RelayCommand(() => AdjustSelectedRailNormalOffset(-RailNormalOffsetNudgeStepInMeters));
IncreaseSelectedRailNormalOffsetCommand = new RelayCommand(() => AdjustSelectedRailNormalOffset(RailNormalOffsetNudgeStepInMeters));
}
#endregion
@ -1704,7 +1755,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
Description = $"直线装配路径 - {AssemblyTerminalObjectName}",
PathType = PathType.Rail,
RailMountMode = AssemblyMountMode,
RailPathDefinitionMode = RailPathDefinitionMode.RailCenterLine
RailPathDefinitionMode = RailPathDefinitionMode.RailCenterLine,
RailNormalOffset = UnitsConverter.ConvertFromMeters(AssemblyAnchorVerticalOffsetInMeters)
};
if (_hasAssemblyInstallationReference)

View File

@ -1,7 +1,7 @@
<Window x:Class="NavisworksTransport.UI.WPF.Views.EditRotationWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="调整物体角度" Height="360" Width="430"
Title="调整物体角度" Height="360" Width="400"
WindowStartupLocation="CenterScreen"
ResizeMode="NoResize"
ShowInTaskbar="False"
@ -105,21 +105,21 @@
Style="{StaticResource SecondaryButtonStyle}"
Width="70"
Height="28"
Margin="0,0,8,0"/>
Margin="0,0,10,0"/>
<Button Content="90°"
Click="OnQuickAngleClick"
Tag="90"
Style="{StaticResource SecondaryButtonStyle}"
Width="70"
Height="28"
Margin="0,0,8,0"/>
Margin="0,0,10,0"/>
<Button Content="180°"
Click="OnQuickAngleClick"
Tag="180"
Style="{StaticResource SecondaryButtonStyle}"
Width="70"
Height="28"
Margin="0,0,8,0"/>
Margin="0,0,10,0"/>
<Button Content="270°"
Click="OnQuickAngleClick"
Tag="270"
@ -133,22 +133,28 @@
<!-- 按钮栏 -->
<Border Grid.Row="2" Background="#FFF8FBFF" BorderBrush="#FFD4E7FF" BorderThickness="0,1,0,0" Padding="20,12">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="恢复初始值"
<Button Content="初始值"
Click="OnResetClick"
Style="{StaticResource SecondaryButtonStyle}"
Width="90"
Width="70"
Height="32"
Margin="0,0,10,0"/>
<Button Content="平移"
Click="OnTranslateClick"
Style="{StaticResource SecondaryButtonStyle}"
Width="70"
Height="32"
Margin="0,0,10,0"/>
<Button Content="确认"
Click="OnConfirmClick"
Style="{StaticResource ActionButtonStyle}"
Width="90"
Width="70"
Height="32"
Margin="0,0,10,0"/>
<Button Content="取消"
Click="OnCancelClick"
Style="{StaticResource SecondaryButtonStyle}"
Width="90"
Width="70"
Height="32"/>
</StackPanel>
</Border>

View File

@ -20,6 +20,7 @@ namespace NavisworksTransport.UI.WPF.Views
private double _rotationYDegrees;
private double _rotationZDegrees;
private RotationAxisTarget _activeAxisTarget = RotationAxisTarget.Z;
private ObjectStartPlacementRequest _adjustmentRequest;
public double RotationXDegrees
{
@ -66,6 +67,8 @@ namespace NavisworksTransport.UI.WPF.Views
public LocalEulerRotationCorrection RotationCorrection =>
new LocalEulerRotationCorrection(RotationXDegrees, RotationYDegrees, RotationZDegrees);
public ObjectStartPlacementRequest AdjustmentRequest => _adjustmentRequest;
public EditRotationWindow(LocalEulerRotationCorrection currentRotation)
{
try
@ -74,6 +77,7 @@ namespace NavisworksTransport.UI.WPF.Views
RotationXDegrees = currentRotation.XDegrees;
RotationYDegrees = currentRotation.YDegrees;
RotationZDegrees = currentRotation.ZDegrees;
_adjustmentRequest = ObjectStartPlacementRequest.CreateRotationCorrection(currentRotation);
DataContext = this;
Loaded += (sender, args) => ZAxisTextBox.Focus();
}
@ -141,6 +145,14 @@ namespace NavisworksTransport.UI.WPF.Views
private void OnConfirmClick(object sender, RoutedEventArgs e)
{
_adjustmentRequest = ObjectStartPlacementRequest.CreateRotationCorrection(RotationCorrection);
DialogResult = true;
Close();
}
private void OnTranslateClick(object sender, RoutedEventArgs e)
{
_adjustmentRequest = ObjectStartPlacementRequest.TranslationOnly;
DialogResult = true;
Close();
}

View File

@ -267,116 +267,6 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
</StackPanel>
</Border>
<Border BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Margin="0,10,0,0" Padding="10">
<StackPanel>
<Label Content="终端安装仿真" Style="{StaticResource SectionHeaderStyle}"/>
<Grid Margin="0,5,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="安装方式:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<ComboBox Grid.Column="1"
Margin="6,0,8,0"
ItemsSource="{Binding RailMountModeOptions}"
SelectedValue="{Binding AssemblyMountMode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Value"
DisplayMemberPath="DisplayName"
ToolTip="选择直线装配路径使用轨上安装还是轨下安装"/>
<Label Grid.Column="2"
Content="垂直偏移:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<StackPanel Grid.Column="3"
Orientation="Horizontal"
VerticalAlignment="Center"
Margin="6,0,0,0">
<TextBox Text="{Binding AssemblyAnchorVerticalOffsetInMeters, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Content="米"
Style="{StaticResource UnitLabelStyle}"/>
</StackPanel>
</Grid>
<Grid Margin="0,6,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="球心:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<StackPanel Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center"
Margin="6,0,8,0">
<TextBox Text="{Binding AssemblySphereCenterX, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Content="X" Style="{StaticResource UnitLabelStyle}"/>
<TextBox Text="{Binding AssemblySphereCenterY, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Content="Y" Style="{StaticResource UnitLabelStyle}"/>
<TextBox Text="{Binding AssemblySphereCenterZ, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Content="Z" Style="{StaticResource UnitLabelStyle}"/>
</StackPanel>
<Label Grid.Column="2"
Content="终点箱体:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<TextBox Grid.Column="3"
Margin="6,0,0,0"
Text="{Binding AssemblyTerminalObjectName}"
Style="{StaticResource ReadOnlyTextBoxStyle}"/>
</Grid>
<Grid Margin="0,6,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="装配起点:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<TextBox Grid.Column="1"
Margin="6,0,0,0"
Text="{Binding AssemblyStartPointText}"
Style="{StaticResource ReadOnlyTextBoxStyle}"/>
</Grid>
<WrapPanel Margin="0,8,0,0">
<Button Content="捕获箱体"
Command="{Binding CaptureAssemblyTerminalObjectCommand}"
Style="{StaticResource SecondaryButtonStyle}"/>
<Button Content="找端面"
Command="{Binding AnalyzeAssemblyTerminalFaceCommand}"
Style="{StaticResource SecondaryButtonStyle}"
IsEnabled="{Binding CanAnalyzeAssemblyTerminalFace}"/>
<Button Content="{Binding AssemblyInstallationPointButtonText}"
Command="{Binding SelectAssemblyInstallationPointCommand}"
Style="{StaticResource SecondaryButtonStyle}"
IsEnabled="{Binding CanSelectAssemblyInstallationPoint}"/>
<Button Content="生成辅助线"
Command="{Binding GenerateAssemblyReferenceRodCommand}"
Style="{StaticResource ActionButtonStyle}"
IsEnabled="{Binding CanGenerateAssemblyReferenceRod}"/>
<Button Content="取起点"
Command="{Binding SelectAssemblyStartPointCommand}"
Style="{StaticResource ActionButtonStyle}"
IsEnabled="{Binding CanSelectAssemblyStartPoint}"/>
<Button Content="隐藏辅助线"
Command="{Binding ClearAssemblyReferenceRodCommand}"
Style="{StaticResource SecondaryButtonStyle}"/>
</WrapPanel>
</StackPanel>
</Border>
<!-- 区域3: 路径编辑 -->
<Border BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Margin="0,10,0,0" Padding="10">
<StackPanel>
@ -480,8 +370,224 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
</StackPanel>
</Border>
<!-- 区域4: 路径文件管理 -->
<!-- 区域4: Rail 创建与参数 -->
<Border BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Margin="0,10,0,0" Padding="10">
<StackPanel>
<Label Content="终端安装 / Rail" Style="{StaticResource SectionHeaderStyle}"/>
<TextBlock Text="创建直线装配 Rail 路径,并在选中 Rail 路径后调整其安装方式。"
Style="{StaticResource StatusTextStyle}"
TextWrapping="Wrap"
Margin="0,2,0,0"/>
<Expander Header="创建 Rail 路径" IsExpanded="False" Margin="0,8,0,0">
<StackPanel Margin="10,6,0,0">
<Grid Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="安装方式:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<ComboBox Grid.Column="1"
Margin="6,0,10,0"
ItemsSource="{Binding RailMountModeOptions}"
SelectedValue="{Binding AssemblyMountMode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Value"
DisplayMemberPath="DisplayName"
ToolTip="选择直线装配路径使用轨上安装还是轨下安装"/>
<Label Grid.Column="2"
Content="安装法向偏移:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<StackPanel Grid.Column="3"
Orientation="Horizontal"
Margin="6,0,0,0"
VerticalAlignment="Center">
<TextBox Width="72"
Text="{Binding AssemblyAnchorVerticalOffsetInMeters, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Content="米" Style="{StaticResource UnitLabelStyle}"/>
</StackPanel>
</Grid>
<Grid Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="终点箱体:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<TextBox Grid.Column="1"
Margin="6,0,10,0"
Text="{Binding AssemblyTerminalObjectName}"
Style="{StaticResource ReadOnlyTextBoxStyle}"/>
<Label Grid.Column="2"
Content="装配起点:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<TextBox Grid.Column="3"
Margin="6,0,0,0"
Text="{Binding AssemblyStartPointText}"
Style="{StaticResource ReadOnlyTextBoxStyle}"/>
</Grid>
<Expander Header="更多创建参数" IsExpanded="False">
<StackPanel Margin="10,6,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="球心:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<StackPanel Grid.Column="1"
Orientation="Horizontal"
Margin="6,0,0,0"
VerticalAlignment="Center">
<TextBox Width="56"
Text="{Binding AssemblySphereCenterX, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Content="X" Style="{StaticResource UnitLabelStyle}"/>
<TextBox Width="56"
Text="{Binding AssemblySphereCenterY, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Content="Y" Style="{StaticResource UnitLabelStyle}"/>
<TextBox Width="56"
Text="{Binding AssemblySphereCenterZ, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Label Content="Z" Style="{StaticResource UnitLabelStyle}"/>
</StackPanel>
</Grid>
</StackPanel>
</Expander>
<WrapPanel Margin="0,8,0,0">
<Button Content="捕获箱体"
Command="{Binding CaptureAssemblyTerminalObjectCommand}"
Style="{StaticResource SecondaryButtonStyle}"/>
<Button Content="找端面"
Command="{Binding AnalyzeAssemblyTerminalFaceCommand}"
Style="{StaticResource SecondaryButtonStyle}"
IsEnabled="{Binding CanAnalyzeAssemblyTerminalFace}"/>
<Button Content="{Binding AssemblyInstallationPointButtonText}"
Command="{Binding SelectAssemblyInstallationPointCommand}"
Style="{StaticResource SecondaryButtonStyle}"
IsEnabled="{Binding CanSelectAssemblyInstallationPoint}"/>
<Button Content="生成辅助线"
Command="{Binding GenerateAssemblyReferenceRodCommand}"
Style="{StaticResource ActionButtonStyle}"
IsEnabled="{Binding CanGenerateAssemblyReferenceRod}"/>
<Button Content="取起点"
Command="{Binding SelectAssemblyStartPointCommand}"
Style="{StaticResource ActionButtonStyle}"
IsEnabled="{Binding CanSelectAssemblyStartPoint}"/>
<Button Content="隐藏辅助线"
Command="{Binding ClearAssemblyReferenceRodCommand}"
Style="{StaticResource SecondaryButtonStyle}"/>
</WrapPanel>
</StackPanel>
</Expander>
<Expander Header="Rail 参数" IsExpanded="True" Margin="0,8,0,0">
<StackPanel Margin="10,6,0,0">
<StackPanel Visibility="{Binding IsRailRouteSelected, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter=Inverse}">
<TextBlock Text="选择一条 Rail 路径后,可在这里修改安装方式。"
Style="{StaticResource StatusTextStyle}"
Foreground="#FF666666"/>
</StackPanel>
<StackPanel Visibility="{Binding IsRailRouteSelected, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Column="0"
Grid.Row="0"
Content="当前路径:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<TextBox Grid.Column="1"
Grid.Row="0"
Margin="6,0,10,0"
Text="{Binding SelectedPathRoute.Name}"
Style="{StaticResource ReadOnlyTextBoxStyle}"/>
<Label Grid.Column="2"
Grid.Row="0"
Content="安装方式:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<ComboBox Grid.Column="3"
Grid.Row="0"
Margin="6,0,0,0"
ItemsSource="{Binding RailMountModeOptions}"
SelectedValue="{Binding SelectedRailMountMode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Value"
DisplayMemberPath="DisplayName"/>
<Label Grid.Column="0"
Grid.Row="1"
Margin="0,6,0,0"
Content="安装法向偏移:"
Style="{StaticResource ParameterLabelStyle}"
VerticalAlignment="Center"/>
<StackPanel Grid.Column="1"
Grid.Row="1"
Grid.ColumnSpan="3"
Orientation="Horizontal"
Margin="6,6,0,0"
VerticalAlignment="Center">
<Button Content="-"
Width="24"
Height="24"
Padding="0"
Command="{Binding DecreaseSelectedRailNormalOffsetCommand}"
Style="{StaticResource SecondaryButtonStyle}"/>
<TextBox Width="84"
Margin="6,0"
HorizontalContentAlignment="Center"
Text="{Binding SelectedRailNormalOffsetInMeters, Mode=TwoWay, UpdateSourceTrigger=LostFocus, StringFormat=0.0###}"
Style="{StaticResource ParameterInputStyle}"/>
<Button Content="+"
Width="24"
Height="24"
Padding="0"
Command="{Binding IncreaseSelectedRailNormalOffsetCommand}"
Style="{StaticResource SecondaryButtonStyle}"/>
<Label Content="米"
Style="{StaticResource UnitLabelStyle}"
Margin="6,0,0,0"/>
<TextBlock Text="用于快速沿当前 Rail 法向微调整条路径"
Style="{StaticResource StatusTextStyle}"
Foreground="#FF666666"
VerticalAlignment="Center"
Margin="10,0,0,0"/>
</StackPanel>
</Grid>
</StackPanel>
</StackPanel>
</Expander>
</StackPanel>
</Border>
<!-- 区域5: 路径文件管理 -->
<Border BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Margin="0,10,0,0" Padding="10">
<StackPanel>
<Label Content="路径文件管理" Style="{StaticResource SectionHeaderStyle}"/>
@ -508,7 +614,7 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
</StackPanel>
</Border>
<!-- 区域5: 可视化设置 -->
<!-- 区域6: 可视化设置 -->
<Border BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Margin="0,10,0,0" Padding="10">
<StackPanel>
<Label Content="可视化设置" Style="{StaticResource SectionHeaderStyle}"/>

View File

@ -15,9 +15,10 @@ namespace NavisworksTransport.Utils.CoordinateSystem
// 路径点本身已经是安装参考点(终点锚点)语义。
// 动画跟踪中心只需要相对安装参考点沿轨道法向偏移半个高度,
// 不能叠加任何“参考线 -> 锚点”旧语义偏移,否则会把终点/逐帧参考点重复平移。
return PathRoute.IsTopPayloadAnchorForMountMode(route.RailMountMode)
double halfHeightOffset = PathRoute.IsTopPayloadAnchorForMountMode(route.RailMountMode)
? -objectHeight / 2.0
: objectHeight / 2.0;
return halfHeightOffset + route.RailNormalOffset;
}
public static Vector3 ResolveTrackedCenter(

View File

@ -0,0 +1,59 @@
using System;
namespace NavisworksTransport.Utils.CoordinateSystem
{
public enum ObjectStartPlacementMode
{
AlignToPathPose = 0,
PreserveInitialPose = 1
}
public struct ObjectStartPlacementRequest : IEquatable<ObjectStartPlacementRequest>
{
public static readonly ObjectStartPlacementRequest TranslationOnly =
new ObjectStartPlacementRequest(LocalEulerRotationCorrection.Zero, ObjectStartPlacementMode.PreserveInitialPose);
public ObjectStartPlacementRequest(
LocalEulerRotationCorrection rotationCorrection,
ObjectStartPlacementMode placementMode)
{
RotationCorrection = rotationCorrection;
PlacementMode = placementMode;
}
public LocalEulerRotationCorrection RotationCorrection { get; }
public ObjectStartPlacementMode PlacementMode { get; }
public bool PreserveInitialPose => PlacementMode == ObjectStartPlacementMode.PreserveInitialPose;
public static ObjectStartPlacementRequest CreateRotationCorrection(LocalEulerRotationCorrection rotationCorrection)
{
return new ObjectStartPlacementRequest(rotationCorrection, ObjectStartPlacementMode.AlignToPathPose);
}
public bool Equals(ObjectStartPlacementRequest other)
{
return RotationCorrection.Equals(other.RotationCorrection) &&
PlacementMode == other.PlacementMode;
}
public override bool Equals(object obj)
{
return obj is ObjectStartPlacementRequest other && Equals(other);
}
public override int GetHashCode()
{
unchecked
{
return (RotationCorrection.GetHashCode() * 397) ^ (int)PlacementMode;
}
}
public override string ToString()
{
return $"PlacementMode={PlacementMode}, Rotation={RotationCorrection}";
}
}
}

View File

@ -528,16 +528,18 @@ namespace NavisworksTransport.Utils
private static double GetBottomOffsetMagnitude(PathRoute route, double objectHeight)
{
return PathRoute.IsTopPayloadAnchorForMountMode(route.RailMountMode)
double baseOffset = PathRoute.IsTopPayloadAnchorForMountMode(route.RailMountMode)
? -objectHeight
: 0.0;
return baseOffset + route.RailNormalOffset;
}
private static double GetObjectSpaceCenterOffsetMagnitude(PathRoute route, double objectSpaceHeight)
{
return PathRoute.IsTopPayloadAnchorForMountMode(route.RailMountMode)
double baseOffset = PathRoute.IsTopPayloadAnchorForMountMode(route.RailMountMode)
? -objectSpaceHeight / 2.0
: objectSpaceHeight / 2.0;
return baseOffset + route.RailNormalOffset;
}
private static Vector3D ResolveRailNormal(Point3D previousPoint, Point3D currentPoint, Point3D nextPoint)