feat: 别名树子树别名导出/导入/复制/粘贴功能

This commit is contained in:
tian 2026-07-07 20:00:08 +08:00
parent b53471c022
commit ef22e49491
2 changed files with 275 additions and 0 deletions

View File

@ -224,6 +224,24 @@
</MenuItem>
<Separator/>
<MenuItem Header="复制原名" Click="OnCtxCopyNameClick"/>
<Separator/>
<MenuItem Header="导出子树别名..." Click="OnCtxExportSubtreeClick">
<MenuItem.Icon>
<Path Data="{StaticResource ExportIconGeometry}"
Fill="#FF666666" Width="14" Height="14" Stretch="Uniform"
Margin="0,0,4,0"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="导入子树别名..." Click="OnCtxImportSubtreeClick">
<MenuItem.Icon>
<Path Data="{StaticResource ImportIconGeometry}"
Fill="#FF666666" Width="14" Height="14" Stretch="Uniform"
Margin="0,0,4,0"/>
</MenuItem.Icon>
</MenuItem>
<Separator/>
<MenuItem Header="复制别名树" Click="OnCtxCopyAliasTreeClick"/>
<MenuItem Header="粘贴别名树" Click="OnCtxPasteAliasTreeClick"/>
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>

View File

@ -25,6 +25,7 @@ namespace NavisworksTransport.UI.WPF.Views
// ============================================================
private AliasDataStore _dataStore;
private string _copiedAliasTreeJson;
/// <summary>当控件检测到模型就绪但数据存储未连接时触发</summary>
internal event Action OnDatabaseNeeded;
@ -1191,6 +1192,262 @@ namespace NavisworksTransport.UI.WPF.Views
catch (Exception ex) { LogManager.Debug($"[别名树] Clipboard异常: {ex.Message}"); }
}
// ============================================================
// 子树别名导出/导入 / 复制/粘贴
// ============================================================
/// <summary>
/// 解析带 #N 后缀的段名 ("Room101#1" → "Room101", 1)
/// </summary>
private static void ParseSuffixedSegment(string segment, out string baseName, out int index)
{
int hashIdx = segment.LastIndexOf('#');
if (hashIdx > 0 && int.TryParse(segment.Substring(hashIdx + 1), out index))
{
baseName = segment.Substring(0, hashIdx);
}
else
{
baseName = segment;
index = 0;
}
}
/// <summary>
/// 收集源节点子树下所有别名,返回相对路径→别名 字典(不含源节点自身)
/// </summary>
private Dictionary<string, string> CollectSubtreeAliases(AliasNodeIdentity sourceIdentity)
{
var result = new Dictionary<string, string>();
var allAliases = _dataStore.LoadAll();
if (allAliases == null || allAliases.Count == 0) return result;
string prefix = sourceIdentity.DisplayPath + "/";
foreach (var kvp in allAliases)
{
if (!AliasNodeIdentity.TryParseKey(kvp.Key, out var id)) continue;
if (id.DisplayPath.StartsWith(prefix, StringComparison.Ordinal))
{
string relativePath = id.DisplayPath.Substring(prefix.Length);
result[relativePath] = kvp.Value;
}
}
return result;
}
/// <summary>
/// 将相对路径→别名 字典应用到目标节点子树。
/// 逐段 Children 寻径,任一缺失则返回错误信息取消全部。
/// </summary>
private string TryApplySubtreeAliases(AliasNodeViewModel targetNode, Dictionary<string, string> relativeAliases)
{
if (relativeAliases == null || relativeAliases.Count == 0) return "没有可导入的别名数据";
EnsureFullNodeKey(targetNode);
if (!AliasNodeIdentity.TryParseKey(targetNode.NodeKey, out var targetIdentity))
return "无法解析目标节点标识";
var targetItem = FindModelItemByNodeKey(targetNode.NodeKey);
if (targetItem == null) return "无法定位目标模型节点";
var batch = new Dictionary<string, string>();
foreach (var kvp in relativeAliases)
{
Autodesk.Navisworks.Api.ModelItem current = targetItem;
string[] segments = kvp.Key.Split('/');
bool found = true;
foreach (var segment in segments)
{
ParseSuffixedSegment(segment, out var baseName, out var index);
int matchCount = 0;
Autodesk.Navisworks.Api.ModelItem matched = null;
foreach (var child in current.Children)
{
if (string.Equals(child.DisplayName, baseName, StringComparison.Ordinal))
{
if (matchCount == index) { matched = child; break; }
matchCount++;
}
}
if (matched == null) { found = false; break; }
current = matched;
}
if (!found) return $"目标子树缺少路径:{kvp.Key},已取消";
var descIdentity = AliasNodeIdentity.FromModelItem(current);
batch[descIdentity.ToKey()] = kvp.Value;
}
if (batch.Count > 0)
{
_dataStore.ImportBatch(batch);
RefreshAliasesFromStore();
SetStatus($"已导入 {batch.Count} 条别名到目标子树");
}
return null;
}
private void OnCtxExportSubtreeClick(object sender, RoutedEventArgs e)
{
var node = GetSelectedNode();
if (node == null || _dataStore == null) return;
EnsureFullNodeKey(node);
if (!AliasNodeIdentity.TryParseKey(node.NodeKey, out var identity))
{ SetStatus("无法解析节点标识"); return; }
var entries = CollectSubtreeAliases(identity);
if (entries.Count == 0) { SetStatus("该节点下没有别名数据"); return; }
var defaultName = string.Join("_", identity.DisplayPath.Split(System.IO.Path.GetInvalidFileNameChars(), StringSplitOptions.RemoveEmptyEntries)) + ".alias-subtree.json";
var dlg = new Microsoft.Win32.SaveFileDialog
{
Filter = "JSON 文件|*.alias-subtree.json",
Title = "导出子树别名",
FileName = defaultName
};
if (dlg.ShowDialog() != true) return;
try
{
var data = new
{
version = 1,
type = "alias_subtree",
exportedAt = DateTime.Now.ToString("s"),
sourceDisplayPath = identity.DisplayPath,
aliases = entries
};
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
File.WriteAllText(dlg.FileName, serializer.Serialize(data), System.Text.Encoding.UTF8);
SetStatus($"已导出 {entries.Count} 条子树别名");
}
catch (Exception ex)
{
LogManager.Error($"[别名树] 子树导出失败: {ex.Message}");
SetStatus("导出失败,见日志");
}
}
private void OnCtxImportSubtreeClick(object sender, RoutedEventArgs e)
{
var node = GetSelectedNode();
if (node == null || _dataStore == null) return;
var dlg = new Microsoft.Win32.OpenFileDialog
{
Filter = "JSON 文件|*.alias-subtree.json;*.json",
Title = "导入子树别名"
};
if (dlg.ShowDialog() != true) return;
try
{
var json = File.ReadAllText(dlg.FileName, System.Text.Encoding.UTF8);
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var data = serializer.Deserialize<Dictionary<string, object>>(json);
if (!(data.TryGetValue("type", out var typeObj) && (typeObj as string) == "alias_subtree"))
{ SetStatus("无效的子树别名文件"); return; }
if (!(data.TryGetValue("aliases", out var aliasesObj) && aliasesObj is Dictionary<string, object> rawAliases))
{ SetStatus("别名数据格式无效"); return; }
var entries = new Dictionary<string, string>();
foreach (var kvp in rawAliases)
{
var alias = kvp.Value as string;
if (!string.IsNullOrEmpty(kvp.Key) && !string.IsNullOrEmpty(alias))
entries[kvp.Key] = alias;
}
if (entries.Count == 0) { SetStatus("文件不包含有效别名"); return; }
var error = TryApplySubtreeAliases(node, entries);
if (error != null) SetStatus(error);
}
catch (Exception ex)
{
LogManager.Error($"[别名树] 子树导入失败: {ex.Message}");
SetStatus("导入失败,见日志");
}
}
private void OnCtxCopyAliasTreeClick(object sender, RoutedEventArgs e)
{
var node = GetSelectedNode();
if (node == null || _dataStore == null) return;
EnsureFullNodeKey(node);
if (!AliasNodeIdentity.TryParseKey(node.NodeKey, out var identity))
{ SetStatus("无法解析节点标识"); return; }
var entries = CollectSubtreeAliases(identity);
if (entries.Count == 0) { SetStatus("该节点下没有别名数据"); return; }
var data = new
{
version = 1,
type = "alias_subtree",
exportedAt = DateTime.Now.ToString("s"),
sourceDisplayPath = identity.DisplayPath,
aliases = entries
};
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
_copiedAliasTreeJson = serializer.Serialize(data);
SetStatus($"已复制 {entries.Count} 条别名树");
}
private void OnCtxPasteAliasTreeClick(object sender, RoutedEventArgs e)
{
var node = GetSelectedNode();
if (node == null || _dataStore == null) return;
if (string.IsNullOrEmpty(_copiedAliasTreeJson))
{ SetStatus("没有已复制的别名树数据"); return; }
try
{
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var data = serializer.Deserialize<Dictionary<string, object>>(_copiedAliasTreeJson);
if (!(data.TryGetValue("type", out var typeObj) && (typeObj as string) == "alias_subtree"))
{ SetStatus("内存数据无效"); return; }
if (!(data.TryGetValue("aliases", out var aliasesObj) && aliasesObj is Dictionary<string, object> rawAliases))
{ SetStatus("别名数据格式无效"); return; }
var entries = new Dictionary<string, string>();
foreach (var kvp in rawAliases)
{
var alias = kvp.Value as string;
if (!string.IsNullOrEmpty(kvp.Key) && !string.IsNullOrEmpty(alias))
entries[kvp.Key] = alias;
}
if (entries.Count == 0) { SetStatus("别名数据为空"); return; }
var error = TryApplySubtreeAliases(node, entries);
if (error != null) SetStatus(error);
}
catch (Exception ex)
{
LogManager.Debug($"[别名树] 粘贴别名树异常: {ex.Message}");
SetStatus("别名树数据格式无效");
}
}
// ============================================================
// 3D 高亮
// ============================================================