Improve clipboard copy utilities and dialogs
This commit is contained in:
parent
f4735b164e
commit
6214cd4397
@ -367,6 +367,7 @@
|
||||
<Compile Include="src\Utils\NwdExportHelper.cs" />
|
||||
<Compile Include="src\Utils\ModelItemTransformHelper.cs" />
|
||||
<Compile Include="src\Utils\CachedTriangle3D.cs" />
|
||||
<Compile Include="src\Utils\ClipboardHelper.cs" />
|
||||
<Compile Include="src\Utils\PathHelper.cs" />
|
||||
<Compile Include="src\Utils\RailPathPoseHelper.cs" />
|
||||
<Compile Include="src\Utils\CollisionSceneHelper.cs" />
|
||||
|
||||
@ -631,6 +631,106 @@ Transform3D original = geometry != null ? geometry.OriginalTransform : item.Tran
|
||||
- 如果业务尺寸是后续叠加出来的,单纯 reset 会把业务尺寸也一起清掉
|
||||
- 因此虚拟物体常常需要 reset 后再重放业务尺寸
|
||||
|
||||
### 11.5.1 真实物体 Rail 起点旋转的两个关键经验
|
||||
|
||||
这是本项目在真实物体沿 `Rail` 路径“移动到起点”与“角度调整”排查中确认的两条硬结论。
|
||||
|
||||
#### A. 正确理解 Navisworks 的“增量旋转”
|
||||
|
||||
`document.Models.OverridePermanentTransform(items, transform, false)` 施加的是**增量层**,不是“把对象直接设成最终姿态”。
|
||||
|
||||
对真实物体要区分 3 个量:
|
||||
|
||||
1. `OriginalTransform`
|
||||
- 原始设计文件里的姿态
|
||||
2. `PermanentOverrideTransform`
|
||||
- 我们写进去的增量/覆盖姿态
|
||||
3. `PermanentTransform / ActiveTransform`
|
||||
- Navisworks 最终真正显示出来的姿态
|
||||
|
||||
项目实测中,真实物体常见关系应按下面理解:
|
||||
|
||||
```text
|
||||
最终显示姿态 = OriginalTransform × PermanentOverrideTransform
|
||||
```
|
||||
|
||||
因此:
|
||||
|
||||
- 如果你手上拿到的是“最终想看到的目标姿态”
|
||||
- 不能直接把它当 `PermanentOverrideTransform` 写进去
|
||||
- 必须先换算出真正应该写入的 override 姿态
|
||||
|
||||
否则就会出现:
|
||||
|
||||
- 目标姿态日志看起来是对的
|
||||
- `OverridePermanentTransform` 也写进去了
|
||||
- 但最终 `ActiveTransform` 仍然被原始模型姿态再转一次
|
||||
|
||||
这类问题在真实物体上非常常见,尤其是 Revit 导入件。
|
||||
|
||||
#### B. 正确区分“宿主坐标系语义”和“物体自身坐标系语义”
|
||||
|
||||
UI 里的角度调整,用户理解的一定是**宿主坐标系**:
|
||||
|
||||
- `X/Y/Z` 表示宿主世界 `X/Y/Z`
|
||||
|
||||
但 Navisworks 对真实物体施加旋转时,底层消费的仍然是**物体自身轴语义**。
|
||||
|
||||
因此正确链路必须是:
|
||||
|
||||
```text
|
||||
宿主世界轴角度输入
|
||||
-> 映射到当前真实物体的本地轴
|
||||
-> 再生成本地 correction quaternion
|
||||
-> 最后交给 OverridePermanentTransform 链
|
||||
```
|
||||
|
||||
不能直接把“宿主世界 X/Y/Z”角度传给一个只接受物体本地轴语义的方法。
|
||||
|
||||
#### C. 哪种映射是对的
|
||||
|
||||
真实物体这里至少有两套看起来很像、但不能混用的映射:
|
||||
|
||||
1. **宿主世界轴 -> raw 本地轴**
|
||||
- 用于 UI 角度调整
|
||||
- 例如:宿主世界 `X` 对应物体本地 `+Y`
|
||||
2. **宿主语义轴 -> raw 本地轴**
|
||||
- 用于路径姿态解释
|
||||
- 例如:`Rail` 的 `forward/up` 选择
|
||||
|
||||
这两套映射在 `up` 轴上可能碰巧一致,但在水平轴上经常不同。
|
||||
|
||||
项目这次问题的根因之一,就是把:
|
||||
|
||||
- “路径姿态用的宿主语义映射”
|
||||
|
||||
误当成了:
|
||||
|
||||
- “角度调整用的宿主世界轴映射”
|
||||
|
||||
结果表现为:
|
||||
|
||||
- `Y` 调整看起来正确
|
||||
- `X/Z` 调整方向却反了
|
||||
|
||||
#### D. 项目中的推荐实现
|
||||
|
||||
对真实物体:
|
||||
|
||||
1. 先从 fragment representative frame 读取 `rawAxisX / rawAxisY / rawAxisZ`
|
||||
2. 单独解析:
|
||||
- 宿主世界 `X/Y/Z` 分别对应哪根 raw 本地轴
|
||||
- 宿主语义 `forward/up` 对应哪根 raw 本地轴
|
||||
3. UI 角度调整只使用“宿主世界轴映射”
|
||||
4. 路径姿态求解只使用“宿主语义映射”
|
||||
5. 不要让这两套映射复用同一组字段名后再靠上下文猜
|
||||
|
||||
一句话记忆:
|
||||
|
||||
- **角度调整看宿主世界轴**
|
||||
- **路径姿态看业务语义轴**
|
||||
- **最终落到 Navisworks 时,始终要转回物体自身轴**
|
||||
|
||||
### 11.6 常见误区
|
||||
|
||||
#### 11.6.1 误区:`ModelItem.Transform` 代表当前姿态
|
||||
|
||||
@ -27,15 +27,12 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
/// </summary>
|
||||
private void CopyButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
if (ClipboardHelper.TrySetText(ResultTextBox.Text, "捕获真实物体位姿"))
|
||||
{
|
||||
Clipboard.SetText(ResultTextBox.Text);
|
||||
MessageBox.Show("内容已复制到剪贴板!", "复制成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
MessageBox.Show($"复制失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
MessageBox.Show("复制到剪贴板失败,请稍后重试。", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -111,6 +111,17 @@
|
||||
<!-- 按钮栏 -->
|
||||
<Border Grid.Row="2" Background="#FFF8FBFF" BorderBrush="#FFD4E7FF" BorderThickness="0,1,0,0" Padding="20,12">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button x:Name="CopyButton"
|
||||
Click="OnCopyClick"
|
||||
Style="{StaticResource IconButtonStyle}"
|
||||
Width="32"
|
||||
Height="32"
|
||||
Margin="0,0,10,0"
|
||||
ToolTip="复制坐标 (格式: X,Y,Z)">
|
||||
<Path Data="{StaticResource CopyIconGeometry}"
|
||||
Fill="{StaticResource NavisworksPrimaryBrush}"
|
||||
Width="16" Height="16" Stretch="Uniform"/>
|
||||
</Button>
|
||||
<Button x:Name="PasteButton"
|
||||
Click="OnPasteClick"
|
||||
Style="{StaticResource IconButtonStyle}"
|
||||
|
||||
@ -46,6 +46,16 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复制坐标
|
||||
/// </summary>
|
||||
private void OnCopyClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
string coordinate = string.Format("{0:0.000}, {1:0.000}, {2:0.000}", X, Y, Z);
|
||||
bool success = ClipboardHelper.TrySetText(coordinate, "编辑路径点坐标");
|
||||
ShowButtonFeedback(CopyButton, success);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 粘贴坐标
|
||||
/// </summary>
|
||||
|
||||
@ -192,16 +192,9 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
/// </summary>
|
||||
private void OnCopyCenterClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
string coordinate = string.Format("{0:0.000}, {1:0.000}, {2:0.000}", CenterX, CenterY, CenterZ);
|
||||
System.Windows.Clipboard.SetText(coordinate);
|
||||
ShowButtonFeedback(CopyCenterButton);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Debug($"[坐标窗口] 复制到剪贴板失败: {ex.Message}");
|
||||
}
|
||||
string coordinate = string.Format("{0:0.000}, {1:0.000}, {2:0.000}", CenterX, CenterY, CenterZ);
|
||||
bool success = ClipboardHelper.TrySetText(coordinate, "元素包围盒信息");
|
||||
ShowButtonFeedback(CopyCenterButton, success);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -209,16 +202,9 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
/// </summary>
|
||||
private void OnCopyTopCenterClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
string coordinate = string.Format("{0:0.000}, {1:0.000}, {2:0.000}", TopCenterX, TopCenterY, TopCenterZ);
|
||||
System.Windows.Clipboard.SetText(coordinate);
|
||||
ShowButtonFeedback(CopyTopButton);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Debug($"[坐标窗口] 复制到剪贴板失败: {ex.Message}");
|
||||
}
|
||||
string coordinate = string.Format("{0:0.000}, {1:0.000}, {2:0.000}", TopCenterX, TopCenterY, TopCenterZ);
|
||||
bool success = ClipboardHelper.TrySetText(coordinate, "元素包围盒信息");
|
||||
ShowButtonFeedback(CopyTopButton, success);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -226,33 +212,32 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
/// </summary>
|
||||
private void OnCopyBottomCenterClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
string coordinate = string.Format("{0:0.000}, {1:0.000}, {2:0.000}", BottomCenterX, BottomCenterY, BottomCenterZ);
|
||||
System.Windows.Clipboard.SetText(coordinate);
|
||||
ShowButtonFeedback(CopyBottomButton);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Debug($"[坐标窗口] 复制到剪贴板失败: {ex.Message}");
|
||||
}
|
||||
string coordinate = string.Format("{0:0.000}, {1:0.000}, {2:0.000}", BottomCenterX, BottomCenterY, BottomCenterZ);
|
||||
bool success = ClipboardHelper.TrySetText(coordinate, "元素包围盒信息");
|
||||
ShowButtonFeedback(CopyBottomButton, success);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示按钮点击反馈(绿色背景闪烁)
|
||||
/// 显示按钮点击反馈
|
||||
/// </summary>
|
||||
private void ShowButtonFeedback(Button button)
|
||||
private void ShowButtonFeedback(Button button, bool success)
|
||||
{
|
||||
if (button == null) return;
|
||||
|
||||
var originalBackground = button.Background;
|
||||
var originalBorderBrush = button.BorderBrush;
|
||||
|
||||
// 变为绿色表示成功
|
||||
button.Background = new SolidColorBrush(System.Windows.Media.Color.FromRgb(76, 175, 80));
|
||||
button.BorderBrush = new SolidColorBrush(System.Windows.Media.Color.FromRgb(56, 142, 60));
|
||||
if (success)
|
||||
{
|
||||
button.Background = new SolidColorBrush(System.Windows.Media.Color.FromRgb(76, 175, 80));
|
||||
button.BorderBrush = new SolidColorBrush(System.Windows.Media.Color.FromRgb(56, 142, 60));
|
||||
}
|
||||
else
|
||||
{
|
||||
button.Background = new SolidColorBrush(System.Windows.Media.Color.FromRgb(244, 67, 54));
|
||||
button.BorderBrush = new SolidColorBrush(System.Windows.Media.Color.FromRgb(211, 47, 47));
|
||||
}
|
||||
|
||||
// 800毫秒后恢复
|
||||
var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(800) };
|
||||
timer.Tick += (s, e) =>
|
||||
{
|
||||
|
||||
68
src/Utils/ClipboardHelper.cs
Normal file
68
src/Utils/ClipboardHelper.cs
Normal file
@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
|
||||
namespace NavisworksTransport
|
||||
{
|
||||
/// <summary>
|
||||
/// 剪贴板工具方法
|
||||
/// </summary>
|
||||
public static class ClipboardHelper
|
||||
{
|
||||
private const int MaxAttempts = 6;
|
||||
private const int RetryDelayMilliseconds = 80;
|
||||
|
||||
/// <summary>
|
||||
/// 尝试写入文本到剪贴板
|
||||
/// </summary>
|
||||
public static bool TrySetText(string text, string context = null)
|
||||
{
|
||||
string clipboardText = text ?? string.Empty;
|
||||
Exception lastException = null;
|
||||
|
||||
for (int attempt = 0; attempt < MaxAttempts; attempt++)
|
||||
{
|
||||
if (TrySetTextOnStaThread(clipboardText, out lastException))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (attempt < MaxAttempts - 1)
|
||||
{
|
||||
Thread.Sleep(RetryDelayMilliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
string prefix = string.IsNullOrWhiteSpace(context) ? "[剪贴板]" : $"[{context}]";
|
||||
LogManager.Debug($"{prefix} 写入剪贴板失败: {lastException?.Message ?? "未知错误"}");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TrySetTextOnStaThread(string text, out Exception exception)
|
||||
{
|
||||
bool success = false;
|
||||
Exception capturedException = null;
|
||||
|
||||
Thread thread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetDataObject(text, true);
|
||||
success = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
capturedException = ex;
|
||||
}
|
||||
});
|
||||
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.IsBackground = true;
|
||||
thread.Start();
|
||||
thread.Join();
|
||||
|
||||
exception = capturedException;
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user