feat: 别名树子树别名导出/导入/复制/粘贴功能
This commit is contained in:
parent
b53471c022
commit
ef22e49491
@ -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>
|
||||
|
||||
@ -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 高亮
|
||||
// ============================================================
|
||||
|
||||
Loading…
Reference in New Issue
Block a user