Add hoisting layer height editing

This commit is contained in:
tian 2026-04-02 22:37:16 +08:00
parent 9be5d250e8
commit 7058e5fd23
3 changed files with 324 additions and 5 deletions

View File

@ -2,6 +2,7 @@ using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Autodesk.Navisworks.Api;
using System.Collections.Generic;
namespace NavisworksTransport.UI.WPF.Models
{
@ -53,4 +54,47 @@ namespace NavisworksTransport.UI.WPF.Models
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// 吊装路径层高编辑项
/// </summary>
public class HoistingLayerEditItem : INotifyPropertyChanged
{
private int _displayIndex;
private double _heightInMeters;
private string _pointSummary;
public int DisplayIndex
{
get => _displayIndex;
set { _displayIndex = value; OnPropertyChanged(nameof(DisplayIndex)); }
}
/// <summary>
/// 相对起吊点的绝对高度(米)
/// </summary>
public double HeightInMeters
{
get => _heightInMeters;
set { _heightInMeters = value; OnPropertyChanged(nameof(HeightInMeters)); }
}
public string PointSummary
{
get => _pointSummary;
set { _pointSummary = value; OnPropertyChanged(nameof(PointSummary)); }
}
/// <summary>
/// 当前层关联的路径点 Id 列表
/// </summary>
public List<string> PointIds { get; } = new List<string>();
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
@ -332,6 +332,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 多层吊装模式
private bool _isMultiLevelHoistingMode = false;
private ObservableCollection<HoistingLevelItem> _multiLevelItems = new ObservableCollection<HoistingLevelItem>();
private ObservableCollection<HoistingLayerEditItem> _hoistingLayerEditItems = new ObservableCollection<HoistingLayerEditItem>();
private Point3D _multiLevelStartPoint;
private Point3D _multiLevelEndPoint;
private bool _hasMultiLevelStartPoint = false;
@ -339,6 +340,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private string _multiLevelStartPointText = "未选择";
private string _multiLevelEndPointText = "未选择";
private string _multiLevelStatsText = "层级数: 0 | 预估路径点数: 0";
private string _hoistingLayerEditHintText = "选择一条吊装路径后,可在这里按层编辑相对起点的绝对高度。";
private bool _isSelectingMultiLevelStartPoint = false;
private bool _isSelectingMultiLevelEndPoint = false;
private HoistingLevelItem _currentSelectingLevelTarget;
@ -431,9 +433,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 🔥 路径类型改变时,检查是否可以使用路径线
OnPropertyChanged(nameof(CanUsePathLines));
OnPropertyChanged(nameof(IsRailRouteSelected));
OnPropertyChanged(nameof(IsHoistingRouteSelected));
OnPropertyChanged(nameof(SelectedRailMountMode));
OnPropertyChanged(nameof(SelectedRailNormalOffsetInMeters));
OnPropertyChanged(nameof(CanRepositionRailStartPoint));
OnPropertyChanged(nameof(CanApplyHoistingLayerHeights));
NotifyRailAssemblyCommandStateChanged();
if (!CanUsePathLines && ShowPathLines)
{
@ -442,6 +446,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Info("[路径可视化] 切换到吊装/空轨路径,自动关闭路径线");
}
RefreshHoistingLayerEditItemsFromSelectedRoute();
// 实现路径选择时的可视化切换
UpdatePathVisualization();
}
@ -493,6 +499,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
public bool IsRailRouteSelected => SelectedPathRoute?.PathType == PathType.Rail;
public bool IsHoistingRouteSelected => SelectedPathRoute?.PathType == PathType.Hoisting;
public ObservableCollection<RailConfigOption<RailMountMode>> RailMountModeOptions
{
@ -906,6 +913,18 @@ namespace NavisworksTransport.UI.WPF.ViewModels
set => SetProperty(ref _multiLevelItems, value);
}
public ObservableCollection<HoistingLayerEditItem> HoistingLayerEditItems
{
get => _hoistingLayerEditItems;
set => SetProperty(ref _hoistingLayerEditItems, value);
}
public string HoistingLayerEditHintText
{
get => _hoistingLayerEditHintText;
set => SetProperty(ref _hoistingLayerEditHintText, value);
}
public string MultiLevelStartPointText
{
get => _multiLevelStartPointText;
@ -925,6 +944,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
public bool CanCreateMultiLevelPath => _hasMultiLevelStartPoint && _hasMultiLevelEndPoint && _multiLevelItems.Count > 0;
public bool CanApplyHoistingLayerHeights => IsHoistingRouteSelected &&
SelectedPathRoute != null &&
HoistingLayerEditItems.Count > 0;
#endregion
@ -1326,6 +1348,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ICommand PickMultiLevelTargetCommand { get; private set; }
public ICommand CreateMultiLevelPathCommand { get; private set; }
public ICommand CancelMultiLevelHoistingCommand { get; private set; }
public ICommand ApplyHoistingLayerHeightsCommand { get; private set; }
#endregion
@ -1977,6 +2000,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
AnalyzeAssemblyTerminalFaceForEditCommand = new RelayCommand(async () => await ExecuteAnalyzeAssemblyTerminalFaceAsync(RailAssemblyWorkflowMode.EditSelectedRail), () => CanAnalyzeAssemblyTerminalFaceForEdit);
DecreaseSelectedRailNormalOffsetCommand = new RelayCommand(() => AdjustSelectedRailNormalOffset(-RailNormalOffsetNudgeStepInMeters));
IncreaseSelectedRailNormalOffsetCommand = new RelayCommand(() => AdjustSelectedRailNormalOffset(RailNormalOffsetNudgeStepInMeters));
ApplyHoistingLayerHeightsCommand = new RelayCommand(async () => await ExecuteApplyHoistingLayerHeightsAsync(), () => CanApplyHoistingLayerHeights);
}
#endregion
@ -2010,22 +2034,58 @@ namespace NavisworksTransport.UI.WPF.ViewModels
HasAssemblyTerminalObject = true;
AssemblyStartPointText = "未选择";
AssemblyTerminalObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(selectedItem);
bool isEditMode = IsRailAssemblyEditMode(mode);
ResetAssemblyEndFaceAnalysisState();
ResetAssemblyInstallationReferenceState();
if (!isEditMode)
{
ResetAssemblyInstallationReferenceState();
}
RefreshAssemblyTerminalObjectInfo();
ClearAssemblyAnchorMarker();
ClearAssemblyEndFaceAnalysisVisuals();
ClearAssemblyInstallationReferenceVisuals();
if (!isEditMode)
{
ClearAssemblyInstallationReferenceVisuals();
}
NotifyRailAssemblyCommandStateChanged();
// 新建路径时隐藏其他路径的辅助线
ClearAssemblyReferenceLineVisuals();
if (isEditMode)
{
// 编辑态重选箱体后,只恢复可由当前 Rail 路径和终点箱体稳定重建的可视化。
// 安装参考面依赖安装拾取点、安装面 span 等会话态数据,这些数据当前未持久化到 PathRoute
// 因此这里不尝试恢复安装参考面,避免后续误以为“少了一次刷新调用”是 bug。
RefreshSelectedPathVisualizationForAssemblyEdit();
RefreshAssemblyReferenceRodIfNeeded();
}
else
{
// 新建路径时隐藏其他路径的辅助线
ClearAssemblyReferenceLineVisuals();
}
UpdateMainStatus($"已捕获终点箱体: {AssemblyTerminalObjectName}");
LogManager.Info($"[直线装配] 已捕获终点箱体: {AssemblyTerminalObjectName}");
}, "捕获终点箱体");
}
private void RefreshSelectedPathVisualizationForAssemblyEdit()
{
if (_selectedPathRoute == null || _selectedPathRoute.Points.Count == 0 || _pathPlanningManager == null)
{
return;
}
var coreRoute = _pathPlanningManager.Routes?.FirstOrDefault(route => route.Id == _selectedPathRoute.Id)
?? _pathPlanningManager.Routes?.FirstOrDefault(route => route.Name == _selectedPathRoute.Name);
if (coreRoute == null)
{
return;
}
_pathPlanningManager.DrawRouteVisualization(coreRoute, isAutoPath: false);
}
private void ClearNonGridPathVisualizations(string context)
{
if (PathPointRenderPlugin.Instance == null)
@ -4572,6 +4632,163 @@ namespace NavisworksTransport.UI.WPF.ViewModels
return true;
}
private void RefreshHoistingLayerEditItemsFromSelectedRoute()
{
HoistingLayerEditItems.Clear();
var coreRoute = GetSelectedCoreRoute();
if (coreRoute == null || coreRoute.PathType != PathType.Hoisting || coreRoute.Points == null || coreRoute.Points.Count < 2)
{
HoistingLayerEditHintText = "选择一条吊装路径后,可在这里按层编辑相对起点的绝对高度。";
OnPropertyChanged(nameof(CanApplyHoistingLayerHeights));
return;
}
HostCoordinateAdapter hostAdapter = CoordinateSystemManager.Instance.CreateHostAdapter();
PathPoint hostStartPoint = coreRoute.Points.FirstOrDefault(point => point.Type == PathPointType.StartPoint) ?? coreRoute.Points.First();
double hostStartElevation = HoistingCoordinateHelper.GetElevation(hostStartPoint.Position, hostAdapter);
const double relativeHeightToleranceInMeters = 0.001;
var layerItems = new List<HoistingLayerEditItem>();
foreach (var point in coreRoute.Points)
{
if (point == null || point.Type == PathPointType.StartPoint || point.Type == PathPointType.EndPoint)
{
continue;
}
double hostPointElevation = HoistingCoordinateHelper.GetElevation(point.Position, hostAdapter);
double relativeHeightInMeters = UnitsConverter.ConvertToMeters(hostPointElevation - hostStartElevation);
if (relativeHeightInMeters <= relativeHeightToleranceInMeters)
{
continue;
}
HoistingLayerEditItem existingLayer = layerItems.FirstOrDefault(item =>
Math.Abs(item.HeightInMeters - relativeHeightInMeters) <= relativeHeightToleranceInMeters);
if (existingLayer == null)
{
existingLayer = new HoistingLayerEditItem
{
HeightInMeters = Math.Round(relativeHeightInMeters, 3)
};
layerItems.Add(existingLayer);
}
if (!string.IsNullOrWhiteSpace(point.Id) && !existingLayer.PointIds.Contains(point.Id))
{
existingLayer.PointIds.Add(point.Id);
}
}
List<HoistingLayerEditItem> orderedLayers = layerItems
.OrderByDescending(item => item.HeightInMeters)
.ToList();
for (int i = 0; i < orderedLayers.Count; i++)
{
orderedLayers[i].DisplayIndex = i + 1;
orderedLayers[i].PointSummary = $"关联点 {orderedLayers[i].PointIds.Count} 个";
HoistingLayerEditItems.Add(orderedLayers[i]);
}
HoistingLayerEditHintText = HoistingLayerEditItems.Count > 0
? "层高按高到低显示;每层高度相对起吊点,且必须严格低于上一层、高于下一层。"
: "当前吊装路径未解析出可编辑的空中层。";
OnPropertyChanged(nameof(CanApplyHoistingLayerHeights));
}
private async Task ExecuteApplyHoistingLayerHeightsAsync()
{
await SafeExecuteAsync(() =>
{
var coreRoute = GetSelectedCoreRoute();
if (coreRoute == null || coreRoute.PathType != PathType.Hoisting)
{
return;
}
if (HoistingLayerEditItems.Count == 0)
{
UpdateMainStatus("当前吊装路径没有可编辑的空中层。");
return;
}
for (int i = 0; i < HoistingLayerEditItems.Count; i++)
{
HoistingLayerEditItem currentLayer = HoistingLayerEditItems[i];
if (currentLayer.HeightInMeters <= 0)
{
HandleInvalidHoistingLayerHeightInput($"第{currentLayer.DisplayIndex}层高度必须大于0。");
return;
}
if (i > 0 && currentLayer.HeightInMeters >= HoistingLayerEditItems[i - 1].HeightInMeters)
{
HandleInvalidHoistingLayerHeightInput($"第{currentLayer.DisplayIndex}层高度必须严格低于上一层。");
return;
}
if (i < HoistingLayerEditItems.Count - 1 && currentLayer.HeightInMeters <= HoistingLayerEditItems[i + 1].HeightInMeters)
{
HandleInvalidHoistingLayerHeightInput($"第{currentLayer.DisplayIndex}层高度必须严格高于下一层。");
return;
}
}
HostCoordinateAdapter hostAdapter = CoordinateSystemManager.Instance.CreateHostAdapter();
PathPoint hostStartPoint = coreRoute.Points.FirstOrDefault(point => point.Type == PathPointType.StartPoint) ?? coreRoute.Points.First();
double hostStartElevation = HoistingCoordinateHelper.GetElevation(hostStartPoint.Position, hostAdapter);
foreach (HoistingLayerEditItem layerItem in HoistingLayerEditItems)
{
double hostTargetElevation = hostStartElevation + UnitsConverter.ConvertFromMeters(layerItem.HeightInMeters);
foreach (string pointId in layerItem.PointIds)
{
PathPoint targetPoint = coreRoute.Points.FirstOrDefault(point => string.Equals(point.Id, pointId, StringComparison.Ordinal));
if (targetPoint != null)
{
targetPoint.Position = HoistingCoordinateHelper.SetElevation(targetPoint.Position, hostTargetElevation, hostAdapter);
}
}
}
PathPoint firstAerialPoint = coreRoute.Points.FirstOrDefault(point =>
point != null &&
point.Type != PathPointType.StartPoint &&
point.Type != PathPointType.EndPoint &&
HoistingCoordinateHelper.GetElevation(point.Position, hostAdapter) > hostStartElevation + UnitsConverter.ConvertFromMeters(0.001));
coreRoute.LiftHeight = firstAerialPoint != null
? HoistingCoordinateHelper.GetElevation(firstAerialPoint.Position, hostAdapter) - hostStartElevation
: 0.0;
coreRoute.LastModified = DateTime.Now;
coreRoute.RecalculateRoute("更新吊装层高");
SyncObjectParametersToRenderPlugin();
_pathPlanningManager.DrawRouteVisualization(coreRoute, isAutoPath: false);
_pathPlanningManager.SavePathToDatabase(coreRoute);
_pathPlanningManager.RaiseVisualizationStateChanged();
if (SelectedPathRoute != null && SelectedPathRoute.Id == coreRoute.Id)
{
SyncPathViewModelFromCoreRoute(SelectedPathRoute, coreRoute, preserveSelection: true);
}
RefreshHoistingLayerEditItemsFromSelectedRoute();
UpdateMainStatus($"已应用 {HoistingLayerEditItems.Count} 个吊装层高。");
LogManager.Info($"[吊装层高] {coreRoute.Name}: 已应用 {HoistingLayerEditItems.Count} 个层高编辑项");
}, "应用吊装层高");
}
private void HandleInvalidHoistingLayerHeightInput(string message)
{
const string validationMessage = "每层高度必须低于上一层、高于下一层,也不能相等。";
LogManager.Warning($"[吊装层高] 用户输入无效: {message}");
RefreshHoistingLayerEditItemsFromSelectedRoute();
UpdateMainStatus(validationMessage);
MessageBox.Show(validationMessage, "层高输入无效", MessageBoxButton.OK, MessageBoxImage.Warning);
}
/// <summary>
/// 更新吊装路径的关联点UI层
/// </summary>
@ -6579,6 +6796,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Info($"已更新路径点列表,当前点数: {pathViewModel.Points.Count}");
if (SelectedPathRoute == pathViewModel)
{
RefreshHoistingLayerEditItemsFromSelectedRoute();
}
// 更新真实路径可视化
UpdatePathVisualization();
}

View File

@ -371,6 +371,59 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
</GridView>
</ListView.View>
</ListView>
<Expander Header="吊装参数" IsExpanded="True" Margin="0,8,0,0">
<StackPanel Margin="10,6,0,0">
<StackPanel Visibility="{Binding IsHoistingRouteSelected, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter=Inverse}">
<TextBlock Text="选择一条吊装路径后,可在这里编辑各空中层的绝对高度。"
Style="{StaticResource StatusTextStyle}"
Foreground="#FF666666"/>
</StackPanel>
<StackPanel Visibility="{Binding IsHoistingRouteSelected, Converter={StaticResource BoolToVisibilityConverter}}">
<TextBlock Text="{Binding HoistingLayerEditHintText}"
Style="{StaticResource StatusTextStyle}"
TextWrapping="Wrap"
Foreground="#FF666666"/>
<ListView ItemsSource="{Binding HoistingLayerEditItems}"
Margin="0,6,0,0"
Height="120"
BorderBrush="#FFCCCCCC"
BorderThickness="1">
<ListView.View>
<GridView>
<GridViewColumn Header="层级" Width="48">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayIndex}"
HorizontalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="高度(米)" Width="88">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding HeightInMeters, Mode=TwoWay, UpdateSourceTrigger=LostFocus, StringFormat=0.0###}"
HorizontalContentAlignment="Center"
Style="{StaticResource ParameterInputStyle}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="关联点" DisplayMemberBinding="{Binding PointSummary}" Width="92"/>
</GridView>
</ListView.View>
</ListView>
<WrapPanel Margin="0,8,0,0">
<Button Content="应用层高"
Command="{Binding ApplyHoistingLayerHeightsCommand}"
IsEnabled="{Binding CanApplyHoistingLayerHeights}"
Style="{StaticResource ActionButtonStyle}"/>
</WrapPanel>
</StackPanel>
</StackPanel>
</Expander>
</StackPanel>
</Border>