问题解构
用户需要在C#中实现合并所有首尾相连的多段线。核心需求包括:
- 识别连接性:判断多段线是否首尾相连(端点重合)
- 合并算法:将相连的多段线合并为单一多段线
- CAD环境:通常在AutoCAD等CAD二次开发环境中操作
方案推演
基于CAD二次开发经验,合并首尾相连多段线的标准流程如下:
| 步骤 | 关键操作 | 技术要点 |
|---|---|---|
| 1. 收集多段线 | 获取所有待处理的多段线对象 | 通过选择集或遍历图元 |
| 2. 建立连接图 | 构建端点索引关系 | 使用字典存储端点坐标与多段线映射 |
| 3. 合并处理 | 递归合并相连多段线 | 处理顶点顺序和方向 |
| 4. 生成结果 | 创建新多段线并清理原对象 | 注意内存管理和事务提交 |
具体实现
1. 核心数据结构
csharp
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System.Collections.Generic;
using System.Linq;
public class PolylineMerger
{
// 端点容差(用于判断是否重合)
private const double Tolerance = 0.0001;
// 端点信息结构
private class EndpointInfo
{
public Point3d Point { get; set; }
public Polyline Polyline { get; set; }
public bool IsStartPoint { get; set; }
}
}
2. 连接性检测方法
csharp
private bool ArePointsEqual(Point3d p1, Point3d p2)
{
return p1.DistanceTo(p2) < Tolerance;
}
private bool ArePolylinesConnected(Polyline pl1, Polyline pl2)
{
Point3d start1 = pl1.StartPoint;
Point3d end1 = pl1.EndPoint;
Point3d start2 = pl2.StartPoint;
Point3d end2 = pl2.EndPoint;
// 判断四种可能的连接情况
return ArePointsEqual(end1, start2) ||
ArePointsEqual(end1, end2) ||
ArePointsEqual(start1, start2) ||
ArePointsEqual(start1, end2);
}
3. 多段线合并算法
csharp
public List<Polyline> MergeConnectedPolylines(List<Polyline> polylines)
{
if (polylines == null || polylines.Count < 2)
return polylines;
// 构建端点索引
Dictionary<Point3d, List<EndpointInfo>> endpointMap = new Dictionary<Point3d, List<EndpointInfo>>();
foreach (var pl in polylines)
{
AddEndpointToMap(pl.StartPoint, pl, true, endpointMap);
AddEndpointToMap(pl.EndPoint, pl, false, endpointMap);
}
// 合并处理
List<Polyline> result = new List<Polyline>();
HashSet<Polyline> processed = new HashSet<Polyline>();
foreach (var pl in polylines)
{
if (processed.Contains(pl)) continue;
Polyline merged = MergeFromPolyline(pl, endpointMap, processed);
if (merged != null)
{
result.Add(merged);
}
}
return result;
}
private void AddEndpointToMap(Point3d point, Polyline pl, bool isStart,
Dictionary<Point3d, List<EndpointInfo>> map)
{
// 查找相近的端点
Point3d? existingKey = null;
foreach (var key in map.Keys)
{
if (ArePointsEqual(key, point))
{
existingKey = key;
break;
}
}
Point3d actualPoint = existingKey ?? point;
if (!map.ContainsKey(actualPoint))
{
map[actualPoint] = new List<EndpointInfo>();
}
map[actualPoint].Add(new EndpointInfo
{
Point = actualPoint,
Polyline = pl,
IsStartPoint = isStart
});
}
4. 递归合并实现
csharp
private Polyline MergeFromPolyline(Polyline startPl,
Dictionary<Point3d, List<EndpointInfo>> endpointMap,
HashSet<Polyline> processed)
{
List<Point3d> mergedPoints = new List<Point3d>();
List<double> mergedBulges = new List<double>();
// 收集起点多段线的顶点
CollectVertices(startPl, mergedPoints, mergedBulges);
processed.Add(startPl);
// 递归查找并合并相连多段线
bool foundMore = true;
while (foundMore)
{
foundMore = false;
Point3d currentEnd = mergedPoints.Last();
if (endpointMap.ContainsKey(currentEnd))
{
foreach (var endpointInfo in endpointMap[currentEnd])
{
if (processed.Contains(endpointInfo.Polyline)) continue;
// 检查连接方向
if (ShouldConnect(currentEnd, endpointInfo))
{
AppendPolylineVertices(endpointInfo.Polyline, endpointInfo.IsStartPoint,
mergedPoints, mergedBulges);
processed.Add(endpointInfo.Polyline);
foundMore = true;
break;
}
}
}
}
// 创建新的合并后多段线
return CreateMergedPolyline(mergedPoints, mergedBulges);
}
private void CollectVertices(Polyline pl, List<Point3d> points, List<double> bulges)
{
for (int i = 0; i < pl.NumberOfVertices; i++)
{
points.Add(pl.GetPoint3dAt(i));
if (i < pl.NumberOfVertices - 1)
{
bulges.Add(pl.GetBulgeAt(i));
}
}
}
5. 完整调用示例
csharp
[CommandMethod("MergePolylines")]
public void MergePolylinesCommand()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
try
{
// 1. 选择多段线
PromptSelectionResult selResult = ed.GetSelection();
if (selResult.Status != PromptStatus.OK) return;
// 2. 收集多段线对象
List<Polyline> polylines = new List<Polyline>();
foreach (SelectedObject selObj in selResult.Value)
{
Entity ent = tr.GetObject(selObj.ObjectId, OpenMode.ForRead) as Entity;
if (ent is Polyline pl)
{
polylines.Add(pl);
}
}
// 3. 执行合并
PolylineMerger merger = new PolylineMerger();
List<Polyline> mergedPolylines = merger.MergeConnectedPolylines(polylines);
// 4. 创建新实体并删除原实体
BlockTableRecord btr = (BlockTableRecord)tr.GetObject(
db.CurrentSpaceId, OpenMode.ForWrite);
foreach (var mergedPl in mergedPolylines)
{
btr.AppendEntity(mergedPl);
tr.AddNewlyCreatedDBObject(mergedPl, true);
}
// 可选:删除原多段线
foreach (var pl in polylines)
{
pl.UpgradeOpen();
pl.Erase();
}
tr.Commit();
ed.WriteMessage($"
合并完成:{polylines.Count}条多段线合并为{mergedPolylines.Count}条");
}
catch (System.Exception ex)
{
ed.WriteMessage($"
错误:{ex.Message}");
}
}
}
关键注意事项
| 注意事项 | 解决方案 | 参考依据 |
|---|---|---|
| 端点容差处理 | 使用容差值比较坐标,避免浮点精度问题 | |
| 顶点顺序保持 | 根据连接方向调整顶点添加顺序 | |
| 凸度值传递 | 合并时保留圆弧段的凸度值 | |
| 性能优化 | 使用字典建立端点索引,避免O(n²)复杂度 | - |
| Z坐标处理 | 忽略Z坐标或保持一致性 | |
| 闭合多段线 | 特殊处理闭合多段线的连接逻辑 | - |
应用场景示例
- CAD图纸清理:将破碎的线段合并为完整轮廓
- 激光切割路径优化:减少路径中断点,提高加工效率
- 工程图处理:简化复杂的多段线结构,便于后续操作
- 数据交换准备:合并多段线以符合其他软件格式要求
该实现通过建立端点索引图高效识别连接关系,递归合并确保处理任意长度的连接链,容差比较解决坐标精度问题,完整支持直线和圆弧段的合并需求。