431 lines
15 KiB
C#
431 lines
15 KiB
C#
using System;
|
||
using System.ComponentModel;
|
||
using System.Runtime.CompilerServices;
|
||
using System.Windows;
|
||
using System.Windows.Controls;
|
||
using System.Windows.Data;
|
||
using NavisworksTransport.UI.WPF.Converters;
|
||
using NavisworksTransport.Utils;
|
||
using NavisworksTransport.Utils.CoordinateSystem;
|
||
|
||
using System.Numerics;
|
||
|
||
namespace NavisworksTransport.UI.WPF.Views
|
||
{
|
||
public partial class EditRotationWindow : Window, INotifyPropertyChanged
|
||
{
|
||
private enum RotationAxisTarget
|
||
{
|
||
X,
|
||
Y,
|
||
Z
|
||
}
|
||
|
||
private double _rotationXDegrees;
|
||
private double _rotationYDegrees;
|
||
private double _rotationZDegrees;
|
||
private double _groundPathLiftHeightInMeters;
|
||
private RotationAxisTarget _activeAxisTarget = RotationAxisTarget.Z;
|
||
private ObjectStartPlacementRequest _adjustmentRequest;
|
||
private readonly ObjectPassageProjectionOptimizationRequest _autoAdjustmentRequest;
|
||
private readonly Func<ObjectPassageProjectionOptimizationResult> _autoAdjustmentProvider;
|
||
private readonly Func<Vector3, ObjectPassageProjectionOptimizationResult> _alignToGroundProvider;
|
||
private readonly bool _isGroundLiftEnabled;
|
||
private double _vehicleHeightInMeters = 0.15;
|
||
private bool _requireWidthLarger = false;
|
||
private bool _isAlignToGroundResult;
|
||
private bool _alignToGroundRequested = false;
|
||
|
||
/// <summary>
|
||
/// 门型偏好:true=宽>高,false=高>宽(默认)
|
||
/// </summary>
|
||
public bool RequireWidthLarger
|
||
{
|
||
get => _requireWidthLarger;
|
||
set => _requireWidthLarger = value;
|
||
}
|
||
|
||
public (double sx, double sy, double sz)? AutoAdjustAabbSizes { get; set; }
|
||
|
||
public bool AlignToGroundRequested => _alignToGroundRequested;
|
||
public bool IsAlignToGroundResult { get => _isAlignToGroundResult; set => _isAlignToGroundResult = value; }
|
||
|
||
public double VehicleHeightInMeters
|
||
{
|
||
get => _vehicleHeightInMeters;
|
||
set => _vehicleHeightInMeters = value;
|
||
}
|
||
|
||
public double RotationXDegrees
|
||
{
|
||
get => _rotationXDegrees;
|
||
set
|
||
{
|
||
if (_rotationXDegrees != value)
|
||
{
|
||
_rotationXDegrees = value;
|
||
OnPropertyChanged();
|
||
OnPropertyChanged(nameof(RotationCorrection));
|
||
}
|
||
}
|
||
}
|
||
|
||
public double RotationYDegrees
|
||
{
|
||
get => _rotationYDegrees;
|
||
set
|
||
{
|
||
if (_rotationYDegrees != value)
|
||
{
|
||
_rotationYDegrees = value;
|
||
OnPropertyChanged();
|
||
OnPropertyChanged(nameof(RotationCorrection));
|
||
}
|
||
}
|
||
}
|
||
|
||
public double RotationZDegrees
|
||
{
|
||
get => _rotationZDegrees;
|
||
set
|
||
{
|
||
if (_rotationZDegrees != value)
|
||
{
|
||
_rotationZDegrees = value;
|
||
OnPropertyChanged();
|
||
OnPropertyChanged(nameof(RotationCorrection));
|
||
}
|
||
}
|
||
}
|
||
|
||
public LocalEulerRotationCorrection RotationCorrection =>
|
||
new LocalEulerRotationCorrection(RotationXDegrees, RotationYDegrees, RotationZDegrees);
|
||
|
||
public double GroundPathLiftHeightInMeters
|
||
{
|
||
get => _groundPathLiftHeightInMeters;
|
||
set
|
||
{
|
||
if (Math.Abs(_groundPathLiftHeightInMeters - value) > 1e-9)
|
||
{
|
||
_groundPathLiftHeightInMeters = value;
|
||
OnPropertyChanged();
|
||
}
|
||
}
|
||
}
|
||
|
||
public bool IsGroundLiftEnabled => _isGroundLiftEnabled;
|
||
|
||
public string GroundLiftHint => _isGroundLiftEnabled
|
||
? "仅地面路径生效,正值向上偏移,负值向下偏移。"
|
||
: "上下偏移当前仅对地面路径生效。";
|
||
|
||
public ObjectStartPlacementRequest AdjustmentRequest => _adjustmentRequest;
|
||
|
||
public EditRotationWindow(
|
||
LocalEulerRotationCorrection currentRotation,
|
||
double currentGroundLiftHeightInMeters,
|
||
bool isGroundLiftEnabled,
|
||
ObjectPassageProjectionOptimizationRequest autoAdjustmentRequest = null,
|
||
Func<ObjectPassageProjectionOptimizationResult> autoAdjustmentProvider = null,
|
||
Func<Vector3, ObjectPassageProjectionOptimizationResult> alignToGroundProvider = null)
|
||
{
|
||
try
|
||
{
|
||
InitializeComponent();
|
||
RotationXDegrees = currentRotation.XDegrees;
|
||
RotationYDegrees = currentRotation.YDegrees;
|
||
RotationZDegrees = currentRotation.ZDegrees;
|
||
_isGroundLiftEnabled = isGroundLiftEnabled;
|
||
_autoAdjustmentRequest = autoAdjustmentRequest;
|
||
_autoAdjustmentProvider = autoAdjustmentProvider;
|
||
_alignToGroundProvider = alignToGroundProvider;
|
||
// 默认上下偏移 = 物流车高度(若未设置过)
|
||
GroundPathLiftHeightInMeters = Math.Abs(currentGroundLiftHeightInMeters) < 1e-9
|
||
? VehicleHeightInMeters
|
||
: currentGroundLiftHeightInMeters;
|
||
_adjustmentRequest = ObjectStartPlacementRequest.CreateRotationCorrection(currentRotation, currentGroundLiftHeightInMeters);
|
||
DataContext = this;
|
||
Loaded += (sender, args) => ZAxisTextBox.Focus();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"角度修正对话框初始化失败: {ex.Message}", ex);
|
||
MessageBox.Show($"角度修正对话框初始化失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
private void OnYAxisRotateClick(object sender, RoutedEventArgs e)
|
||
{
|
||
double newValue = RotationYDegrees + 90.0;
|
||
RotationYDegrees = NormalizeDisplayDegrees(newValue);
|
||
YAxisTextBox.Focus();
|
||
YAxisTextBox.SelectAll();
|
||
LogManager.Debug($"[Y轴旋转] 增加 90°: {newValue:F3} → {RotationYDegrees:F3}");
|
||
}
|
||
|
||
private void OnAutoAdjustClick(object sender, RoutedEventArgs e)
|
||
{
|
||
if (_autoAdjustmentProvider == null && _autoAdjustmentRequest == null)
|
||
{
|
||
MessageBox.Show("当前路径或物体尺寸不足,无法自动调整。", "自动调整", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||
return;
|
||
}
|
||
|
||
// 同步门型偏好和车辆高度到请求
|
||
if (_autoAdjustmentRequest != null)
|
||
{
|
||
_autoAdjustmentRequest.RequireWidthLarger = _requireWidthLarger;
|
||
_autoAdjustmentRequest.VehicleHeightModelUnits =
|
||
UnitsConverter.ConvertFromMeters(_vehicleHeightInMeters);
|
||
}
|
||
|
||
var oldCursor = System.Windows.Input.Mouse.OverrideCursor;
|
||
try
|
||
{
|
||
System.Windows.Input.Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait;
|
||
|
||
ObjectPassageProjectionOptimizationResult result = _autoAdjustmentProvider != null
|
||
? _autoAdjustmentProvider()
|
||
: ObjectPassageProjectionOptimizer.Optimize(_autoAdjustmentRequest);
|
||
if (!result.Success)
|
||
{
|
||
MessageBox.Show(result.ErrorMessage ?? "自动调整失败。", "自动调整", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||
return;
|
||
}
|
||
|
||
RotationXDegrees = NormalizeDisplayDegrees(result.Correction.XDegrees);
|
||
RotationYDegrees = NormalizeDisplayDegrees(result.Correction.YDegrees);
|
||
RotationZDegrees = NormalizeDisplayDegrees(result.Correction.ZDegrees);
|
||
if (result.ComputedLiftOffsetModelUnits.HasValue)
|
||
{
|
||
GroundPathLiftHeightInMeters = Math.Round(UnitsConverter.ConvertToMeters(result.ComputedLiftOffsetModelUnits.Value), 3);
|
||
}
|
||
if (result.AabbSx.HasValue)
|
||
AutoAdjustAabbSizes = (result.AabbSx.Value, result.AabbSy.Value, result.AabbSz.Value);
|
||
LogManager.Debug(
|
||
$"[自动调整] 已写入角度: {RotationCorrection}, " +
|
||
$"截面宽度={result.Score.WidthAcrossPath:F3}, 高度={result.Score.HeightAlongHostUp:F3}, 面积={result.Score.Area:F3}");
|
||
}
|
||
finally
|
||
{
|
||
System.Windows.Input.Mouse.OverrideCursor = oldCursor;
|
||
}
|
||
}
|
||
|
||
private void OnAlignToGroundClick(object sender, RoutedEventArgs e)
|
||
{
|
||
LogManager.Info($"[贴合地面] 按钮被点击, provider={_alignToGroundProvider != null}");
|
||
if (_alignToGroundProvider == null)
|
||
{
|
||
MessageBox.Show("当前无法使用贴合地面功能。", "贴合地面", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||
return;
|
||
}
|
||
|
||
_alignToGroundRequested = true;
|
||
TryCommitInputValues();
|
||
_adjustmentRequest = ObjectStartPlacementRequest.CreateTranslationOnly(GroundPathLiftHeightInMeters);
|
||
DialogResult = false;
|
||
Close();
|
||
}
|
||
|
||
private void OnQuickAngleClick(object sender, RoutedEventArgs e)
|
||
{
|
||
if (sender is Button button)
|
||
{
|
||
LogManager.Debug($"[快捷角度] 点击按钮: {button.Content}, Tag类型: {button.Tag?.GetType()}, Tag值: {button.Tag}");
|
||
|
||
if (button.Tag != null)
|
||
{
|
||
double? parsedAngle = null;
|
||
if (button.Tag is int intTag)
|
||
{
|
||
parsedAngle = intTag;
|
||
}
|
||
else if (button.Tag is string stringTag && double.TryParse(stringTag, out double angle))
|
||
{
|
||
parsedAngle = angle;
|
||
}
|
||
else if (button.Tag is double doubleTag)
|
||
{
|
||
parsedAngle = doubleTag;
|
||
}
|
||
|
||
if (parsedAngle.HasValue)
|
||
{
|
||
ApplyQuickAngle(parsedAngle.Value);
|
||
LogManager.Debug($"[快捷角度] 设置 {_activeAxisTarget} 轴角度为: {parsedAngle.Value}°");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void OnAxisTextBoxGotFocus(object sender, RoutedEventArgs e)
|
||
{
|
||
if (ReferenceEquals(sender, XAxisTextBox))
|
||
{
|
||
_activeAxisTarget = RotationAxisTarget.X;
|
||
return;
|
||
}
|
||
|
||
if (ReferenceEquals(sender, YAxisTextBox))
|
||
{
|
||
_activeAxisTarget = RotationAxisTarget.Y;
|
||
return;
|
||
}
|
||
|
||
_activeAxisTarget = RotationAxisTarget.Z;
|
||
}
|
||
|
||
private void OnResetClick(object sender, RoutedEventArgs e)
|
||
{
|
||
RotationXDegrees = 0.0;
|
||
RotationYDegrees = 0.0;
|
||
RotationZDegrees = 0.0;
|
||
}
|
||
|
||
private void OnConfirmClick(object sender, RoutedEventArgs e)
|
||
{
|
||
if (!TryCommitInputValues())
|
||
{
|
||
return;
|
||
}
|
||
|
||
_adjustmentRequest = ObjectStartPlacementRequest.CreateRotationCorrection(RotationCorrection, GroundPathLiftHeightInMeters);
|
||
DialogResult = true;
|
||
Close();
|
||
}
|
||
|
||
private void OnTranslateClick(object sender, RoutedEventArgs e)
|
||
{
|
||
if (!TryCommitInputValues())
|
||
{
|
||
return;
|
||
}
|
||
|
||
_adjustmentRequest = ObjectStartPlacementRequest.CreateTranslationOnly(GroundPathLiftHeightInMeters);
|
||
DialogResult = true;
|
||
Close();
|
||
}
|
||
|
||
private void OnCancelClick(object sender, RoutedEventArgs e)
|
||
{
|
||
DialogResult = false;
|
||
Close();
|
||
}
|
||
|
||
public event PropertyChangedEventHandler PropertyChanged;
|
||
|
||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||
{
|
||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||
}
|
||
|
||
private void ApplyQuickAngle(double angle)
|
||
{
|
||
switch (_activeAxisTarget)
|
||
{
|
||
case RotationAxisTarget.X:
|
||
RotationXDegrees = angle;
|
||
break;
|
||
case RotationAxisTarget.Y:
|
||
RotationYDegrees = angle;
|
||
break;
|
||
case RotationAxisTarget.Z:
|
||
default:
|
||
RotationZDegrees = angle;
|
||
break;
|
||
}
|
||
}
|
||
|
||
private bool TryCommitInputValues()
|
||
{
|
||
if (!TryUpdateNumberTextBox(XAxisTextBox, "请输入有效的 X 轴角度。"))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (!TryUpdateNumberTextBox(YAxisTextBox, "请输入有效的 Y 轴角度。"))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (!TryUpdateNumberTextBox(ZAxisTextBox, "请输入有效的 Z 轴角度。"))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (!TryUpdateNumberTextBox(GroundLiftTextBox, "请输入有效的上下偏移值。"))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (!TryUpdateNumberTextBox(VehicleHeightTextBox, "请输入有效的物流车高度。"))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (VehicleHeightInMeters < 0.0)
|
||
{
|
||
ShowInputError("物流车高度必须大于或等于 0。", VehicleHeightTextBox);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
private static bool TryUpdateNumberTextBox(TextBox textBox, string message)
|
||
{
|
||
BindingExpression binding = textBox.GetBindingExpression(TextBox.TextProperty);
|
||
|
||
if (EditableNumberConverter.IsIntermediateText(textBox.Text))
|
||
{
|
||
ShowInputError(message, textBox);
|
||
return false;
|
||
}
|
||
|
||
if (!EditableNumberConverter.TryParseNumber(textBox.Text.Trim(), System.Globalization.CultureInfo.CurrentCulture, typeof(double), out _))
|
||
{
|
||
ShowInputError(message, textBox);
|
||
return false;
|
||
}
|
||
|
||
try
|
||
{
|
||
binding?.UpdateSource();
|
||
}
|
||
catch (Exception)
|
||
{
|
||
ShowInputError(message, textBox);
|
||
return false;
|
||
}
|
||
|
||
if (Validation.GetHasError(textBox))
|
||
{
|
||
ShowInputError(message, textBox);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
private static void ShowInputError(string message, TextBox textBox)
|
||
{
|
||
MessageBox.Show(message, "输入错误", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||
textBox.Focus();
|
||
textBox.SelectAll();
|
||
}
|
||
|
||
private static double NormalizeDisplayDegrees(double degrees)
|
||
{
|
||
double normalized = degrees % 360.0;
|
||
if (normalized < 0.0)
|
||
{
|
||
normalized += 360.0;
|
||
}
|
||
|
||
return Math.Abs(normalized) < 1e-9 ? 0.0 : normalized;
|
||
}
|
||
}
|
||
}
|