diff --git a/doc/design/2026/转DelmiaXML的步骤.md b/doc/design/2026/转DelmiaXML的步骤.md new file mode 100644 index 0000000..44b5102 --- /dev/null +++ b/doc/design/2026/转DelmiaXML的步骤.md @@ -0,0 +1,650 @@ +# 转 DELMIA Apriso XML 的步骤 + +直接生成符合 DELMIA Apriso 要求的 TransportPathOrders XML,跳过 XSLT 转换步骤。 +简化方案:直接生成目标XML +目标Schema(直接使用) +创建文件 TransportPathOrder.xsd(这是唯一需要的Schema文件): +xml +复制 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +您的程序直接生成XML的代码 +C# 示例(如果您用C#) +csharp +复制 +using System; +using System.Collections.Generic; +using System.Xml.Linq; +using System.IO; + +public class TransportPathGenerator +{ + public class PathPoint + { + public string Id { get; set; } + public string Name { get; set; } + public string Type { get; set; } // StartPoint, WayPoint, EndPoint + public int Index { get; set; } + public double X { get; set; } + public double Y { get; set; } + public double Z { get; set; } + } + + public class PathEdge + { + public string Type { get; set; } // straight, arc + public double PhysicalLength { get; set; } + public TrajectoryInfo Trajectory { get; set; } + } + + public class TrajectoryInfo + { + public Point3D StartTangent { get; set; } + public Point3D EndTangent { get; set; } + public Point3D ArcCenter { get; set; } + public double Radius { get; set; } + public double DeflectionAngle { get; set; } + } + + public class Point3D + { + public double X { get; set; } + public double Y { get; set; } + public double Z { get; set; } + } + + public void GenerateXml( + string orderId, + string orderName, + List points, + List edges, + string outputPath) + { + XNamespace ns = "http://www.dassaultsystemes.com/delmia/apriso"; + + // 构建Nodes + var nodesElement = new XElement(ns + "Nodes"); + foreach (var point in points) + { + nodesElement.Add(new XElement(ns + "Node", + new XElement(ns + "NodeId", point.Id), + new XElement(ns + "SequenceId", point.Index), + new XElement(ns + "NodeName", point.Name), + new XElement(ns + "NodeType", point.Type), + new XElement(ns + "Position", + new XElement(ns + "X", point.X), + new XElement(ns + "Y", point.Y), + new XElement(ns + "Z", point.Z) + ), + new XElement(ns + "Released", "true") + )); + } + + // 构建Edges(需要关联起点和终点ID) + var edgesElement = new XElement(ns + "Edges"); + for (int i = 0; i < edges.Count; i++) + { + var edge = edges[i]; + var startNode = points[i]; + var endNode = points[i + 1]; + + var edgeElement = new XElement(ns + "Edge", + new XElement(ns + "EdgeId", $"edge_{i}"), + new XElement(ns + "EdgeType", edge.Type), + new XElement(ns + "SequenceId", i), + new XElement(ns + "StartNodeId", startNode.Id), + new XElement(ns + "EndNodeId", endNode.Id), + new XElement(ns + "PhysicalLength", edge.PhysicalLength) + ); + + // 如果是弧线,添加轨迹信息 + if (edge.Type == "arc" && edge.Trajectory != null) + { + edgeElement.Add(new XElement(ns + "Trajectory", + new XElement(ns + "StartTangent", + new XElement(ns + "X", edge.Trajectory.StartTangent.X), + new XElement(ns + "Y", edge.Trajectory.StartTangent.Y), + new XElement(ns + "Z", edge.Trajectory.StartTangent.Z) + ), + new XElement(ns + "EndTangent", + new XElement(ns + "X", edge.Trajectory.EndTangent.X), + new XElement(ns + "Y", edge.Trajectory.EndTangent.Y), + new XElement(ns + "Z", edge.Trajectory.EndTangent.Z) + ), + new XElement(ns + "ArcCenter", + new XElement(ns + "X", edge.Trajectory.ArcCenter.X), + new XElement(ns + "Y", edge.Trajectory.ArcCenter.Y), + new XElement(ns + "Z", edge.Trajectory.ArcCenter.Z) + ), + new XElement(ns + "Radius", edge.Trajectory.Radius), + new XElement(ns + "DeflectionAngle", edge.Trajectory.DeflectionAngle) + )); + } + + edgesElement.Add(edgeElement); + } + + // 构建完整文档 + var document = new XDocument( + new XDeclaration("1.0", "utf-8", null), + new XElement(ns + "TransportPathOrders", + new XElement(ns + "PathOrder", + new XElement(ns + "OrderId", orderId), + new XElement(ns + "OrderName", orderName), + new XElement(ns + "Description", "NavisworksTransport生成的路径"), + new XElement(ns + "PathType", "Ground"), + new XElement(ns + "TotalLength", CalculateTotalLength(edges)), + new XElement(ns + "Timestamp", DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ")), + new XElement(ns + "Generator", "NavisworksTransport"), + new XElement(ns + "Version", "1.0"), + nodesElement, + edgesElement, + new XElement(ns + "ObjectLimits", + new XElement(ns + "MaxLength", "0"), + new XElement(ns + "MaxWidth", "0"), + new XElement(ns + "MaxHeight", "0"), + new XElement(ns + "SafetyMargin", "0") + ), + new XElement(ns + "CoordinateSystem", "Global") + ) + ) + ); + + document.Save(outputPath); + } + + private double CalculateTotalLength(List edges) + { + double total = 0; + foreach (var edge in edges) + total += edge.PhysicalLength; + return total; + } +} + +// 使用示例 +class Program +{ + static void Main() + { + var generator = new TransportPathGenerator(); + + var points = new List + { + new AGVPathGenerator.PathPoint + { + Id = "3ada559e-36b9-4537-b341-cfff765c8d43", + Name = "起点", + Type = "StartPoint", + Index = 0, + X = -152.614, + Y = 0.517, + Z = 14.833 + }, + new AGVPathGenerator.PathPoint + { + Id = "042229ba-0ce2-4f6c-af8f-57f840300e82", + Name = "路径点1", + Type = "WayPoint", + Index = 1, + X = -131.345, + Y = -14.532, + Z = 14.75 + }, + new AGVPathGenerator.PathPoint + { + Id = "69fa7321-3dc5-4ec5-9c1f-74e2a6d787a5", + Name = "终点", + Type = "EndPoint", + Index = 2, + X = -55.369, + Y = -0.504, + Z = 14.833 + } + }; + + var edges = new List + { + new AGVPathGenerator.PathEdge + { + Type = "straight", + PhysicalLength = 22.595 + }, + new AGVPathGenerator.PathEdge + { + Type = "arc", + PhysicalLength = 6.548, + Trajectory = new AGVPathGenerator.TrajectoryInfo + { + StartTangent = new AGVPathGenerator.Point3D + { X = -134.17, Y = -12.533, Z = 14.761 }, + EndTangent = new AGVPathGenerator.Point3D + { X = -127.943, Y = -13.903, Z = 14.754 }, + ArcCenter = new AGVPathGenerator.Point3D + { X = -129.432, Y = -5.837, Z = 14.799 }, + Radius = 8.202, + DeflectionAngle = 0.798 + } + }, + new AGVPathGenerator.PathEdge + { + Type = "straight", + PhysicalLength = 73.8 + } + }; + + generator.GenerateXml( + "17838e6e-1112-4e62-a3ab-d7a3354d39d3", + "人工_0219_203325", + points, + edges, + "output.xml" + ); + } +} +Python 示例(如果您用Python) +Python +复制 +import xml.etree.ElementTree as ET +from datetime import datetime +from dataclasses import dataclass +from typing import List, Optional + +@dataclass +class Point3D: + x: float + y: float + z: float + +@dataclass +class TrajectoryInfo: + start_tangent: Point3D + end_tangent: Point3D + arc_center: Point3D + radius: float + deflection_angle: float + +@dataclass +class PathPoint: + id: str + name: str + type: str # StartPoint, WayPoint, EndPoint + index: int + x: float + y: float + z: float + +@dataclass +class PathEdge: + edge_type: str # straight, arc + physical_length: float + trajectory: Optional[TrajectoryInfo] = None + +class TransportPathGenerator: + def __init__(self): + self.ns = "http://www.dassaultsystemes.com/delmia/apriso" + ET.register_namespace('', self.ns) + + def generate_xml(self, + order_id: str, + order_name: str, + points: List[PathPoint], + edges: List[PathEdge], + output_path: str): + + # 创建根元素 + root = ET.Element(f"{{{self.ns}}}TransportPathOrders") + + # 创建PathOrder + path_order = ET.SubElement(root, f"{{{self.ns}}}PathOrder") + + # 基础信息 + ET.SubElement(path_order, f"{{{self.ns}}}OrderId").text = order_id + ET.SubElement(path_order, f"{{{self.ns}}}OrderName").text = order_name + ET.SubElement(path_order, f"{{{self.ns}}}Description").text = "NavisworksTransport生成的路径" + ET.SubElement(path_order, f"{{{self.ns}}}PathType").text = "Ground" + ET.SubElement(path_order, f"{{{self.ns}}}TotalLength").text = str(sum(e.physical_length for e in edges)) + ET.SubElement(path_order, f"{{{self.ns}}}Timestamp").text = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") + ET.SubElement(path_order, f"{{{self.ns}}}Generator").text = "NavisworksTransport" + ET.SubElement(path_order, f"{{{self.ns}}}Version").text = "1.0" + + # 创建Nodes + nodes = ET.SubElement(path_order, f"{{{self.ns}}}Nodes") + for point in points: + node = ET.SubElement(nodes, f"{{{self.ns}}}Node") + ET.SubElement(node, f"{{{self.ns}}}NodeId").text = point.id + ET.SubElement(node, f"{{{self.ns}}}SequenceId").text = str(point.index) + ET.SubElement(node, f"{{{self.ns}}}NodeName").text = point.name + ET.SubElement(node, f"{{{self.ns}}}NodeType").text = point.type + + position = ET.SubElement(node, f"{{{self.ns}}}Position") + ET.SubElement(position, f"{{{self.ns}}}X").text = str(point.x) + ET.SubElement(position, f"{{{self.ns}}}Y").text = str(point.y) + ET.SubElement(position, f"{{{self.ns}}}Z").text = str(point.z) + + ET.SubElement(node, f"{{{self.ns}}}Released").text = "true" + + # 创建Edges + edges_elem = ET.SubElement(path_order, f"{{{self.ns}}}Edges") + for i, edge in enumerate(edges): + edge_elem = ET.SubElement(edges_elem, f"{{{self.ns}}}Edge") + ET.SubElement(edge_elem, f"{{{self.ns}}}EdgeId").text = f"edge_{i}" + ET.SubElement(edge_elem, f"{{{self.ns}}}EdgeType").text = edge.edge_type + ET.SubElement(edge_elem, f"{{{self.ns}}}SequenceId").text = str(i) + ET.SubElement(edge_elem, f"{{{self.ns}}}StartNodeId").text = points[i].id + ET.SubElement(edge_elem, f"{{{self.ns}}}EndNodeId").text = points[i+1].id + ET.SubElement(edge_elem, f"{{{self.ns}}}PhysicalLength").text = str(edge.physical_length) + + # 如果是弧线,添加轨迹 + if edge.edge_type == "arc" and edge.trajectory: + traj = ET.SubElement(edge_elem, f"{{{self.ns}}}Trajectory") + + start_tan = ET.SubElement(traj, f"{{{self.ns}}}StartTangent") + ET.SubElement(start_tan, f"{{{self.ns}}}X").text = str(edge.trajectory.start_tangent.x) + ET.SubElement(start_tan, f"{{{self.ns}}}Y").text = str(edge.trajectory.start_tangent.y) + ET.SubElement(start_tan, f"{{{self.ns}}}Z").text = str(edge.trajectory.start_tangent.z) + + end_tan = ET.SubElement(traj, f"{{{self.ns}}}EndTangent") + ET.SubElement(end_tan, f"{{{self.ns}}}X").text = str(edge.trajectory.end_tangent.x) + ET.SubElement(end_tan, f"{{{self.ns}}}Y").text = str(edge.trajectory.end_tangent.y) + ET.SubElement(end_tan, f"{{{self.ns}}}Z").text = str(edge.trajectory.end_tangent.z) + + arc_center = ET.SubElement(traj, f"{{{self.ns}}}ArcCenter") + ET.SubElement(arc_center, f"{{{self.ns}}}X").text = str(edge.trajectory.arc_center.x) + ET.SubElement(arc_center, f"{{{self.ns}}}Y").text = str(edge.trajectory.arc_center.y) + ET.SubElement(arc_center, f"{{{self.ns}}}Z").text = str(edge.trajectory.arc_center.z) + + ET.SubElement(traj, f"{{{self.ns}}}Radius").text = str(edge.trajectory.radius) + ET.SubElement(traj, f"{{{self.ns}}}DeflectionAngle").text = str(edge.trajectory.deflection_angle) + + # 对象限制 + limits = ET.SubElement(path_order, f"{{{self.ns}}}ObjectLimits") + ET.SubElement(limits, f"{{{self.ns}}}MaxLength").text = "0" + ET.SubElement(limits, f"{{{self.ns}}}MaxWidth").text = "0" + ET.SubElement(limits, f"{{{self.ns}}}MaxHeight").text = "0" + ET.SubElement(limits, f"{{{self.ns}}}SafetyMargin").text = "0" + + ET.SubElement(path_order, f"{{{self.ns}}}CoordinateSystem").text = "Global" + + # 保存文件 + tree = ET.ElementTree(root) + tree.write(output_path, encoding='utf-8', xml_declaration=True) + print(f"XML已生成: {output_path}") + +# 使用示例 +if __name__ == "__main__": + generator = TransportPathGenerator() + + points = [ + PathPoint("3ada559e-36b9-4537-b341-cfff765c8d43", "起点", "StartPoint", 0, -152.614, 0.517, 14.833), + PathPoint("042229ba-0ce2-4f6c-af8f-57f840300e82", "路径点1", "WayPoint", 1, -131.345, -14.532, 14.75), + PathPoint("69fa7321-3dc5-4ec5-9c1f-74e2a6d787a5", "终点", "EndPoint", 2, -55.369, -0.504, 14.833) + ] + + edges = [ + PathEdge("straight", 22.595), + PathEdge("arc", 6.548, TrajectoryInfo( + Point3D(-134.17, -12.533, 14.761), + Point3D(-127.943, -13.903, 14.754), + Point3D(-129.432, -5.837, 14.799), + 8.202, 0.798 + )), + PathEdge("straight", 73.8) + ] + + generator.generate_xml( + "17838e6e-1112-4e62-a3ab-d7a3354d39d3", + "人工_0219_203325", + points, + edges, + "transport_path.xml" + ) +生成的 XML 示例 +xml +复制 + + + + 17838e6e-1112-4e62-a3ab-d7a3354d39d3 + 人工_0219_203325 + NavisworksTransport生成的路径 + Ground + 102.943 + 2026-03-09T16:30:45Z + NavisworksTransport + 1.0 + + + 3ada559e-36b9-4537-b341-cfff765c8d43 + 0 + 起点 + StartPoint + + -152.614 + 0.517 + 14.833 + + true + + + 042229ba-0ce2-4f6c-af8f-57f840300e82 + 1 + 路径点1 + WayPoint + + -131.345 + -14.532 + 14.75 + + true + + + 69fa7321-3dc5-4ec5-9c1f-74e2a6d787a5 + 2 + 终点 + EndPoint + + -55.369 + -0.504 + 14.833 + + true + + + + + edge_0 + straight + 0 + 3ada559e-36b9-4537-b341-cfff765c8d43 + 042229ba-0ce2-4f6c-af8f-57f840300e82 + 22.595 + + + edge_1 + arc + 1 + 042229ba-0ce2-4f6c-af8f-57f840300e82 + 69fa7321-3dc5-4ec5-9c1f-74e2a6d787a5 + 6.548 + + + -134.17 + -12.533 + 14.761 + + + -127.943 + -13.903 + 14.754 + + + -129.432 + -5.837 + 14.799 + + 8.202 + 0.798 + + + + edge_2 + straight + 2 + 69fa7321-3dc5-4ec5-9c1f-74e2a6d787a5 + 终点_D + 73.8 + + + + 0 + 0 + 0 + 0 + + Global + + +DELMIA Apriso 配置(简化版) +既然您直接生成目标XML,配置也简化了: +1. 部署Schema +将 TransportPathOrder.xsd 复制到: +plain +复制 +SchemaRepository/XMLSchemas/FlexNet/TransportPathOrder.xsd +2. Routing Configuration(仅需路由,无需XSLT) +xml +复制 + + + +注意:不需要 Mapping Configuration,因为您直接生成了 Apriso 内部格式! +3. 创建Job Action处理程序 +在DELMIA Apriso中创建 ProcessTransportPathOrder 来处理导入的路径数据。 +关键优势 +✅ 无需 XSLT 转换:直接生成目标格式,性能更好 +✅ 配置更简单:只需配置路由,无需映射 +✅ 数据完整性:保留所有原始信息(轨迹、切线等) +✅ 支持吊装路径:扩展了 LiftPoint/DescendPoint/HoverPoint 等节点类型 +✅ 符合 Schema:遵循 TransportPathOrders.xsd 规范 + +您需要: +1. 把上面的 C# 或 Python 代码集成到您的程序中(或直接使用插件的导出功能) +2. 部署 XSD 文件到 Apriso +3. 配置简单的路由规则 +4. 开发 Job Action 处理程序 + +> 注:插件已内置 ExportToDelmiaAprisoXml() 方法,在导出对话框中选择 `.delmiaxml` 格式即可直接生成符合该 Schema 的 XML 文件。 \ No newline at end of file diff --git a/doc/requirement/todo_features.md b/doc/requirement/todo_features.md index 8f9e920..9eb638c 100644 --- a/doc/requirement/todo_features.md +++ b/doc/requirement/todo_features.md @@ -2,6 +2,12 @@ ## 功能点 +### [2026/3/9] + +1. [x] (功能)支持记录并查看路径文件操作的历史记录 +2. [x] (优化)物流自定义类别配置,简化并合并到系统配置中 +3. [x] (优化)给版本号增加时间戳(增加Build号) + ### [2026/2/24] 1. [x] (优化)吊装路径支持多个水平层 diff --git a/src/Commands/ExportPathCommand.cs b/src/Commands/ExportPathCommand.cs index 12faa60..4ec48aa 100644 --- a/src/Commands/ExportPathCommand.cs +++ b/src/Commands/ExportPathCommand.cs @@ -154,6 +154,9 @@ namespace NavisworksTransport.Commands case ExportFormat.Csv: expectedExtensions = new[] { ".csv" }; break; + case ExportFormat.DelmiaApriso: + expectedExtensions = new[] { ".aprxml" }; + break; default: expectedExtensions = new[] { ".xml" }; break; @@ -440,9 +443,14 @@ namespace NavisworksTransport.Commands { success = _pathDataManager.ExportToCsv(pathsToExport, _parameters.OutputFilePath, _parameters.ExportSettings); } + else if (_parameters.ExportFormat == ExportFormat.DelmiaApriso) + { + // DELMIA Apriso格式(TransportPathOrders Schema) + success = _pathDataManager.ExportToDelmiaAprisoXml(pathsToExport, _parameters.OutputFilePath, _parameters.ExportSettings); + } else { - // DELMIA格式 + // 旧版DELMIA格式 success = _pathDataManager.ExportDelmiaFormat(pathsToExport, _parameters.OutputFilePath, _parameters.ExportSettings); } diff --git a/src/Core/PathDataManager.cs b/src/Core/PathDataManager.cs index a26d1ce..c797993 100644 --- a/src/Core/PathDataManager.cs +++ b/src/Core/PathDataManager.cs @@ -766,6 +766,378 @@ namespace NavisworksTransport } } + /// + /// 导出路径到DELMIA Apriso XML格式(符合TransportPathOrders Schema) + /// + /// 路径集合 + /// 文件路径 + /// 导出设置 + /// 是否成功 + public bool ExportToDelmiaAprisoXml(List routes, string filePath, ExportSettings exportSettings = null) + { + if (routes == null || routes.Count == 0) + { + throw new ArgumentException("路径集合不能为空"); + } + + try + { + LogManager.Info($"[路径文件操作] 开始导出 {routes.Count} 条路径到DELMIA Apriso XML文件: {filePath}"); + + // 确保导出目录存在 + var directory = Path.GetDirectoryName(filePath); + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + var xmlDoc = new XmlDocument(); + var declaration = xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", null); + xmlDoc.AppendChild(declaration); + + // DELMIA Apriso命名空间 + const string ns = "http://www.dassaultsystemes.com/delmia/apriso"; + const string nsPrefix = "apr"; + + // 创建根元素 TransportPathOrders + var rootElement = xmlDoc.CreateElement(nsPrefix, "TransportPathOrders", ns); + xmlDoc.AppendChild(rootElement); + + // 为每个路径创建PathOrder + foreach (var route in routes.Where(r => r.IsValid())) + { + var pathOrderElement = CreateDelmiaAprisoPathOrderElement(xmlDoc, route, exportSettings, ns, nsPrefix); + rootElement.AppendChild(pathOrderElement); + } + + // 保存文件 + using (var writer = new XmlTextWriter(filePath, Encoding.UTF8)) + { + writer.Formatting = Formatting.Indented; + writer.Indentation = 2; + xmlDoc.WriteTo(writer); + } + + LogManager.Info($"[路径文件操作] 成功导出 {routes.Count} 条路径到DELMIA Apriso XML文件: {filePath}"); + return true; + } + catch (Exception ex) + { + LogManager.Error($"[路径文件操作] 导出DELMIA Apriso XML文件失败: {ex.Message}", ex); + return false; + } + } + + /// + /// 创建DELMIA Apriso格式的PathOrder元素 + /// + private XmlElement CreateDelmiaAprisoPathOrderElement(XmlDocument xmlDoc, PathRoute route, ExportSettings settings, string ns, string nsPrefix) + { + var pathOrderElement = xmlDoc.CreateElement(nsPrefix, "PathOrder", ns); + + // 基础信息 + var orderIdElement = xmlDoc.CreateElement(nsPrefix, "OrderId", ns); + orderIdElement.InnerText = route.Id ?? Guid.NewGuid().ToString(); + pathOrderElement.AppendChild(orderIdElement); + + var orderNameElement = xmlDoc.CreateElement(nsPrefix, "OrderName", ns); + orderNameElement.InnerText = route.Name ?? "未命名路径"; + pathOrderElement.AppendChild(orderNameElement); + + var descriptionElement = xmlDoc.CreateElement(nsPrefix, "Description", ns); + descriptionElement.InnerText = route.Description ?? $"NavisworksTransport生成的路径: {route.Name}"; + pathOrderElement.AppendChild(descriptionElement); + + var pathTypeElement = xmlDoc.CreateElement(nsPrefix, "PathType", ns); + pathTypeElement.InnerText = MapToDelmiaPathType(route.PathType); + pathOrderElement.AppendChild(pathTypeElement); + + var totalLengthElement = xmlDoc.CreateElement(nsPrefix, "TotalLength", ns); + totalLengthElement.InnerText = route.TotalLength.ToString("F3", CultureInfo.InvariantCulture); + pathOrderElement.AppendChild(totalLengthElement); + + var timestampElement = xmlDoc.CreateElement(nsPrefix, "Timestamp", ns); + timestampElement.InnerText = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"); + pathOrderElement.AppendChild(timestampElement); + + var generatorElement = xmlDoc.CreateElement(nsPrefix, "Generator", ns); + generatorElement.InnerText = "NavisworksTransport"; + pathOrderElement.AppendChild(generatorElement); + + var versionElement = xmlDoc.CreateElement(nsPrefix, "Version", ns); + versionElement.InnerText = "1.0"; + pathOrderElement.AppendChild(versionElement); + + // 创建Nodes + var nodesElement = xmlDoc.CreateElement(nsPrefix, "Nodes", ns); + pathOrderElement.AppendChild(nodesElement); + + var sortedPoints = route.GetSortedPoints(); + for (int i = 0; i < sortedPoints.Count; i++) + { + var point = sortedPoints[i]; + var nodeElement = xmlDoc.CreateElement(nsPrefix, "Node", ns); + + // NodeId + var nodeIdElement = xmlDoc.CreateElement(nsPrefix, "NodeId", ns); + nodeIdElement.InnerText = point.Id ?? $"node_{i}"; + nodeElement.AppendChild(nodeIdElement); + + // SequenceId + var sequenceIdElement = xmlDoc.CreateElement(nsPrefix, "SequenceId", ns); + sequenceIdElement.InnerText = i.ToString(); + nodeElement.AppendChild(sequenceIdElement); + + // NodeName + var nodeNameElement = xmlDoc.CreateElement(nsPrefix, "NodeName", ns); + nodeNameElement.InnerText = point.Name ?? $"点{i}"; + nodeElement.AppendChild(nodeNameElement); + + // NodeType (映射到DELMIA枚举,支持吊装路径特殊类型) + var nodeTypeElement = xmlDoc.CreateElement(nsPrefix, "NodeType", ns); + string nodeType = MapToDelmiaNodeType(point.Type, route.PathType, i, sortedPoints.Count); + nodeTypeElement.InnerText = nodeType; + nodeElement.AppendChild(nodeTypeElement); + + // 吊装路径特殊属性:LiftHeight (仅对LiftPoint和HoverPoint) + if ((nodeType == "LiftPoint" || nodeType == "HoverPoint") && route.LiftHeight > 0) + { + var liftHeightElement = xmlDoc.CreateElement(nsPrefix, "LiftHeight", ns); + liftHeightElement.InnerText = route.LiftHeight.ToString("F3", CultureInfo.InvariantCulture); + nodeElement.AppendChild(liftHeightElement); + } + + // Position + var positionElement = xmlDoc.CreateElement(nsPrefix, "Position", ns); + var xElement = xmlDoc.CreateElement(nsPrefix, "X", ns); + xElement.InnerText = point.Position.X.ToString("F3", CultureInfo.InvariantCulture); + positionElement.AppendChild(xElement); + var yElement = xmlDoc.CreateElement(nsPrefix, "Y", ns); + yElement.InnerText = point.Position.Y.ToString("F3", CultureInfo.InvariantCulture); + positionElement.AppendChild(yElement); + var zElement = xmlDoc.CreateElement(nsPrefix, "Z", ns); + zElement.InnerText = point.Position.Z.ToString("F3", CultureInfo.InvariantCulture); + positionElement.AppendChild(zElement); + nodeElement.AppendChild(positionElement); + + // Released + var releasedElement = xmlDoc.CreateElement(nsPrefix, "Released", ns); + releasedElement.InnerText = "true"; + nodeElement.AppendChild(releasedElement); + + nodesElement.AppendChild(nodeElement); + } + + // 创建Edges + var edgesElement = xmlDoc.CreateElement(nsPrefix, "Edges", ns); + pathOrderElement.AppendChild(edgesElement); + + if (route.Edges != null && route.Edges.Count > 0) + { + for (int i = 0; i < route.Edges.Count && i < sortedPoints.Count - 1; i++) + { + var edge = route.Edges[i]; + var startNode = sortedPoints[i]; + var endNode = sortedPoints[i + 1]; + + var edgeElement = xmlDoc.CreateElement(nsPrefix, "Edge", ns); + + // EdgeId + var edgeIdElement = xmlDoc.CreateElement(nsPrefix, "EdgeId", ns); + edgeIdElement.InnerText = $"edge_{i}"; + edgeElement.AppendChild(edgeIdElement); + + // EdgeType (支持吊装路径特殊类型) + var edgeTypeElement = xmlDoc.CreateElement(nsPrefix, "EdgeType", ns); + string edgeType = MapToDelmiaEdgeType(edge.SegmentType, route.PathType); + edgeTypeElement.InnerText = edgeType; + edgeElement.AppendChild(edgeTypeElement); + + // SequenceId + var edgeSeqIdElement = xmlDoc.CreateElement(nsPrefix, "SequenceId", ns); + edgeSeqIdElement.InnerText = i.ToString(); + edgeElement.AppendChild(edgeSeqIdElement); + + // StartNodeId + var startNodeIdElement = xmlDoc.CreateElement(nsPrefix, "StartNodeId", ns); + startNodeIdElement.InnerText = startNode.Id ?? $"node_{i}"; + edgeElement.AppendChild(startNodeIdElement); + + // EndNodeId + var endNodeIdElement = xmlDoc.CreateElement(nsPrefix, "EndNodeId", ns); + endNodeIdElement.InnerText = endNode.Id ?? $"node_{i + 1}"; + edgeElement.AppendChild(endNodeIdElement); + + // PhysicalLength + var physicalLengthElement = xmlDoc.CreateElement(nsPrefix, "PhysicalLength", ns); + physicalLengthElement.InnerText = edge.PhysicalLength.ToString("F3", CultureInfo.InvariantCulture); + edgeElement.AppendChild(physicalLengthElement); + + // Trajectory (如果是弧线) + if (edge.SegmentType == PathSegmentType.Arc && edge.Trajectory != null) + { + var trajectoryElement = xmlDoc.CreateElement(nsPrefix, "Trajectory", ns); + + // StartTangent + var startTangentElement = xmlDoc.CreateElement(nsPrefix, "StartTangent", ns); + var stXElement = xmlDoc.CreateElement(nsPrefix, "X", ns); + stXElement.InnerText = edge.Trajectory.Ts.X.ToString("F3", CultureInfo.InvariantCulture); + startTangentElement.AppendChild(stXElement); + var stYElement = xmlDoc.CreateElement(nsPrefix, "Y", ns); + stYElement.InnerText = edge.Trajectory.Ts.Y.ToString("F3", CultureInfo.InvariantCulture); + startTangentElement.AppendChild(stYElement); + var stZElement = xmlDoc.CreateElement(nsPrefix, "Z", ns); + stZElement.InnerText = edge.Trajectory.Ts.Z.ToString("F3", CultureInfo.InvariantCulture); + startTangentElement.AppendChild(stZElement); + trajectoryElement.AppendChild(startTangentElement); + + // EndTangent + var endTangentElement = xmlDoc.CreateElement(nsPrefix, "EndTangent", ns); + var etXElement = xmlDoc.CreateElement(nsPrefix, "X", ns); + etXElement.InnerText = edge.Trajectory.Te.X.ToString("F3", CultureInfo.InvariantCulture); + endTangentElement.AppendChild(etXElement); + var etYElement = xmlDoc.CreateElement(nsPrefix, "Y", ns); + etYElement.InnerText = edge.Trajectory.Te.Y.ToString("F3", CultureInfo.InvariantCulture); + endTangentElement.AppendChild(etYElement); + var etZElement = xmlDoc.CreateElement(nsPrefix, "Z", ns); + etZElement.InnerText = edge.Trajectory.Te.Z.ToString("F3", CultureInfo.InvariantCulture); + endTangentElement.AppendChild(etZElement); + trajectoryElement.AppendChild(endTangentElement); + + // ArcCenter + var arcCenterElement = xmlDoc.CreateElement(nsPrefix, "ArcCenter", ns); + var acXElement = xmlDoc.CreateElement(nsPrefix, "X", ns); + acXElement.InnerText = edge.Trajectory.ArcCenter.X.ToString("F3", CultureInfo.InvariantCulture); + arcCenterElement.AppendChild(acXElement); + var acYElement = xmlDoc.CreateElement(nsPrefix, "Y", ns); + acYElement.InnerText = edge.Trajectory.ArcCenter.Y.ToString("F3", CultureInfo.InvariantCulture); + arcCenterElement.AppendChild(acYElement); + var acZElement = xmlDoc.CreateElement(nsPrefix, "Z", ns); + acZElement.InnerText = edge.Trajectory.ArcCenter.Z.ToString("F3", CultureInfo.InvariantCulture); + arcCenterElement.AppendChild(acZElement); + trajectoryElement.AppendChild(arcCenterElement); + + // Radius + var radiusElement = xmlDoc.CreateElement(nsPrefix, "Radius", ns); + radiusElement.InnerText = edge.Trajectory.ActualRadius.ToString("F3", CultureInfo.InvariantCulture); + trajectoryElement.AppendChild(radiusElement); + + // DeflectionAngle + var deflectionAngleElement = xmlDoc.CreateElement(nsPrefix, "DeflectionAngle", ns); + deflectionAngleElement.InnerText = edge.Trajectory.DeflectionAngle.ToString("F3", CultureInfo.InvariantCulture); + trajectoryElement.AppendChild(deflectionAngleElement); + + edgeElement.AppendChild(trajectoryElement); + } + + edgesElement.AppendChild(edgeElement); + } + } + + // ObjectLimits (使用 -1 表示无限制) + var objectLimitsElement = xmlDoc.CreateElement(nsPrefix, "ObjectLimits", ns); + + var maxLengthElement = xmlDoc.CreateElement(nsPrefix, "MaxLength", ns); + maxLengthElement.InnerText = (route.MaxObjectLength > 0 ? route.MaxObjectLength : -1).ToString("F3", CultureInfo.InvariantCulture); + objectLimitsElement.AppendChild(maxLengthElement); + + var maxWidthElement = xmlDoc.CreateElement(nsPrefix, "MaxWidth", ns); + maxWidthElement.InnerText = (route.MaxObjectWidth > 0 ? route.MaxObjectWidth : -1).ToString("F3", CultureInfo.InvariantCulture); + objectLimitsElement.AppendChild(maxWidthElement); + + var maxHeightElement = xmlDoc.CreateElement(nsPrefix, "MaxHeight", ns); + maxHeightElement.InnerText = (route.MaxObjectHeight > 0 ? route.MaxObjectHeight : -1).ToString("F3", CultureInfo.InvariantCulture); + objectLimitsElement.AppendChild(maxHeightElement); + + var safetyMarginElement = xmlDoc.CreateElement(nsPrefix, "SafetyMargin", ns); + safetyMarginElement.InnerText = (route.SafetyMargin >= 0 ? route.SafetyMargin : 0.5).ToString("F3", CultureInfo.InvariantCulture); + objectLimitsElement.AppendChild(safetyMarginElement); + + // MaxWeight (新增,默认 -1 表示无限制) + var maxWeightElement = xmlDoc.CreateElement(nsPrefix, "MaxWeight", ns); + maxWeightElement.InnerText = "-1"; + objectLimitsElement.AppendChild(maxWeightElement); + + pathOrderElement.AppendChild(objectLimitsElement); + + // CoordinateSystem + var coordinateSystemElement = xmlDoc.CreateElement(nsPrefix, "CoordinateSystem", ns); + coordinateSystemElement.InnerText = settings?.CoordinateSystem ?? "Global"; + pathOrderElement.AppendChild(coordinateSystemElement); + + return pathOrderElement; + } + + /// + /// 将PathPointType映射到DELMIA NodeType枚举 + /// 支持吊装路径特殊节点类型:LiftPoint, DescendPoint, HoverPoint + /// + private string MapToDelmiaNodeType(PathPointType type, PathType pathType = PathType.Ground, int pointIndex = 0, int totalPoints = 0) + { + // 基础类型映射 + switch (type) + { + case PathPointType.StartPoint: + return "StartPoint"; + case PathPointType.EndPoint: + return "EndPoint"; + case PathPointType.WayPoint: + default: + // 对于吊装路径,根据点位置判断特殊类型 + if (pathType == PathType.Hoisting && totalPoints > 2) + { + // 吊装路径的第二点通常是提升点 + if (pointIndex == 1) + return "LiftPoint"; + // 吊装路径的倒数第二点通常是下降点 + if (pointIndex == totalPoints - 2) + return "DescendPoint"; + // 中间空中点 + if (pointIndex > 1 && pointIndex < totalPoints - 2) + return "HoverPoint"; + } + return "WayPoint"; + } + } + + /// + /// 将PathType映射到DELMIA PathTypeEnum + /// + private string MapToDelmiaPathType(PathType pathType) + { + switch (pathType) + { + case PathType.Ground: + return "Ground"; + case PathType.Rail: + case PathType.Hoisting: + return "Aerial"; + default: + return "Mixed"; + } + } + + /// + /// 将PathSegmentType映射到DELMIA EdgeTypeEnum + /// 支持吊装路径特殊边类型:vertical, suspension, hoisting + /// + private string MapToDelmiaEdgeType(PathSegmentType segmentType, PathType pathType = PathType.Ground) + { + switch (segmentType) + { + case PathSegmentType.Arc: + return "arc"; + case PathSegmentType.Straight: + default: + // 对于吊装路径,根据上下文判断是否为垂直运动 + if (pathType == PathType.Hoisting) + return "hoisting"; + if (pathType == PathType.Rail) + return "suspension"; + return "straight"; + } + } + /// /// 获取默认导出路径 /// @@ -1279,7 +1651,8 @@ namespace NavisworksTransport Json, Csv, Delmia, - Navisworks + Navisworks, + DelmiaApriso } /// diff --git a/src/PathPlanning/FlexNet.BusinessFacade.Logistics.TransportPath.xsd b/src/PathPlanning/FlexNet.BusinessFacade.Logistics.TransportPath.xsd new file mode 100644 index 0000000..ecd8e68 --- /dev/null +++ b/src/PathPlanning/FlexNet.BusinessFacade.Logistics.TransportPath.xsd @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/UI/WPF/ViewModels/PathEditingViewModel.cs b/src/UI/WPF/ViewModels/PathEditingViewModel.cs index 58d036c..84139bf 100644 --- a/src/UI/WPF/ViewModels/PathEditingViewModel.cs +++ b/src/UI/WPF/ViewModels/PathEditingViewModel.cs @@ -2977,7 +2977,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels var saveFileDialog = new SaveFileDialog { Title = "导出全部路径", - Filter = "XML文件 (*.xml)|*.xml|JSON文件 (*.json)|*.json|CSV文件 (*.csv)|*.csv|所有文件 (*.*)|*.*", + Filter = "XML文件 (*.xml)|*.xml|JSON文件 (*.json)|*.json|CSV文件 (*.csv)|*.csv|DELMIA Apriso XML (*.aprxml)|*.aprxml|所有文件 (*.*)|*.*", FilterIndex = 1, FileName = $"路径导出_{DateTime.Now:yyyyMMdd_HHmmss}", DefaultExt = "xml", @@ -2987,19 +2987,21 @@ namespace NavisworksTransport.UI.WPF.ViewModels if (saveFileDialog.ShowDialog() == true) { var filePath = saveFileDialog.FileName; - var extension = Path.GetExtension(filePath).ToLower(); - - // 确定导出格式 + + // 确定导出格式(通过FilterIndex判断,因为DELMIA Apriso和普通XML都是.xml扩展名) ExportFormat exportFormat; - switch (extension) + switch (saveFileDialog.FilterIndex) { - case ".json": + case 2: // JSON文件 exportFormat = ExportFormat.Json; break; - case ".csv": + case 3: // CSV文件 exportFormat = ExportFormat.Csv; break; - default: + case 4: // DELMIA Apriso XML + exportFormat = ExportFormat.DelmiaApriso; + break; + default: // XML文件或其他 exportFormat = ExportFormat.Xml; break; } @@ -3065,7 +3067,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels var saveFileDialog = new SaveFileDialog { Title = $"导出选中路径: {SelectedPathRoute?.Name}", - Filter = "XML文件 (*.xml)|*.xml|JSON文件 (*.json)|*.json|CSV文件 (*.csv)|*.csv|所有文件 (*.*)|*.*", + Filter = "XML文件 (*.xml)|*.xml|JSON文件 (*.json)|*.json|CSV文件 (*.csv)|*.csv|DELMIA Apriso XML (*.aprxml)|*.aprxml|所有文件 (*.*)|*.*", FilterIndex = 1, FileName = SelectedPathRoute?.Name?.Replace(" ", "_") ?? "路径", DefaultExt = "xml", @@ -3075,19 +3077,21 @@ namespace NavisworksTransport.UI.WPF.ViewModels if (saveFileDialog.ShowDialog() == true) { var filePath = saveFileDialog.FileName; - var extension = Path.GetExtension(filePath).ToLower(); - - // 确定导出格式 + + // 确定导出格式(通过FilterIndex判断,因为DELMIA Apriso和普通XML都是.xml扩展名) ExportFormat exportFormat; - switch (extension) + switch (saveFileDialog.FilterIndex) { - case ".json": + case 2: // JSON文件 exportFormat = ExportFormat.Json; break; - case ".csv": + case 3: // CSV文件 exportFormat = ExportFormat.Csv; break; - default: + case 4: // DELMIA Apriso XML + exportFormat = ExportFormat.DelmiaApriso; + break; + default: // XML文件或其他 exportFormat = ExportFormat.Xml; break; }