using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Windows.Forms; using System.Windows.Forms.Integration; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.Colors; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Geometry; using Autodesk.AutoCAD.Runtime; using Autodesk.AutoCAD.Windows; using Szmedi.CADkits; using Application = Autodesk.AutoCAD.ApplicationServices.Application; public class TerrainTools { private static PaletteSet ps; public const string CheckLineLayer = "校对线"; [CommandMethod("MoveTerrainElevation")] public void MoveTerrainElevation() { Document doc = Application.DocumentManager.MdiActiveDocument; if (doc == null) return; Database db = doc.Database; Editor ed = doc.Editor; try { // --- Phase 1: User Input --- #region User Input Section string targetBlockName = null; // 仅当选中块时使用 string terrainLayerName; // 定义处理模式: 0-块, 1-点, 2-多段线 int processingMode = 0; string textLayerName; double maxDistance = 5.0; // 1. 选择样例地形点(块 或 点) using (Transaction tr = db.TransactionManager.StartTransaction()) { PromptEntityOptions peoEnt = new PromptEntityOptions("\n请选择一个样例地形点 (块/点/多段线(起终点重合)): "); peoEnt.SetRejectMessage("\n选择的对象无效。"); peoEnt.AddAllowedClass(typeof(BlockReference), true); peoEnt.AddAllowedClass(typeof(DBPoint), true); peoEnt.AddAllowedClass(typeof(Polyline), true); PromptEntityResult perEnt = ed.GetEntity(peoEnt); if (perEnt.Status != PromptStatus.OK) { ed.WriteMessage("\n操作已取消。"); return; } Entity sampleEnt = (Entity)tr.GetObject(perEnt.ObjectId, OpenMode.ForRead); terrainLayerName = sampleEnt.Layer; if (sampleEnt is BlockReference sampleBlock) { processingMode = 0; targetBlockName = sampleBlock.IsDynamicBlock ? ((BlockTableRecord)tr.GetObject(sampleBlock.DynamicBlockTableRecord, OpenMode.ForRead)).Name : sampleBlock.Name; ed.WriteMessage($"\n已选择模式: [处理块]。块名: '{targetBlockName}', 图层: '{terrainLayerName}'"); } else if (sampleEnt is DBPoint) { processingMode = 1; ed.WriteMessage($"\n已选择模式: [处理点实体]。所在图层: '{terrainLayerName}'"); } else if (sampleEnt is Polyline) { processingMode = 2; ed.WriteMessage("\n模式: [多段线]"); } tr.Commit(); } // 2. 选择样例文字 using (Transaction tr = db.TransactionManager.StartTransaction()) { PromptEntityOptions peoText = new PromptEntityOptions("\n请选择一个样例标高文字 (用于确定图层): "); peoText.SetRejectMessage("\n选择的不是 MText 或 DBText。"); peoText.AddAllowedClass(typeof(MText), true); peoText.AddAllowedClass(typeof(DBText), true); PromptEntityResult perText = ed.GetEntity(peoText); if (perText.Status != PromptStatus.OK) { ed.WriteMessage("\n操作已取消。"); return; } Entity sampleTextEnt = (Entity)tr.GetObject(perText.ObjectId, OpenMode.ForRead); textLayerName = sampleTextEnt.Layer; ed.WriteMessage($"\n已选择样例文字,所在图层: '{textLayerName}'"); tr.Commit(); } // 3. 输入距离 PromptDoubleOptions pdo = new PromptDoubleOptions($"\n请输入查找标高文字的最大距离(图形单位) <{maxDistance}>: "); pdo.AllowNegative = false; pdo.AllowZero = false; pdo.AllowNone = true; pdo.DefaultValue = maxDistance; PromptDoubleResult pdr = ed.GetDouble(pdo); if (pdr.Status != PromptStatus.OK && pdr.Status != PromptStatus.None) { ed.WriteMessage("\n操作已取消。"); return; } if (pdr.Status == PromptStatus.OK) maxDistance = pdr.Value; #endregion // --- Phase 2: Automatic Matching --- List terrainEntities = new List(); int updatedCount = 0; using (Transaction tr = db.TransactionManager.StartTransaction()) { BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite); // 根据模式采集 if (processingMode == 0) terrainEntities = CollectBlocks(tr, btr, targetBlockName, terrainLayerName); else if (processingMode == 1) terrainEntities = CollectPoints(tr, btr, terrainLayerName); else if (processingMode == 2) terrainEntities = CollectPolylines(tr, btr, terrainLayerName); var elevationTexts = CollectFilteredTexts(tr, btr, textLayerName); if (elevationTexts.Count == 0) { ed.WriteMessage($"\n警告: 在图层 '{textLayerName}' 上未找到任何有效的标高文字。"); tr.Commit(); return; } ed.WriteMessage($"\n正在使用贪婪匹配算法分析 {terrainEntities.Count} 个地形点..."); // 核心匹配算法不需要修改,因为它只依赖 ObjectId 和 Position,这在 TerrainEntityInfo 中都有 var validPairs = GetScoredValidPairs(terrainEntities, elevationTexts, maxDistance); ed.WriteMessage($"\n分析完成。找到 {validPairs.Count} 个最优匹配对。"); if (validPairs.Count > 0) { CreateLayerIfNotExists(db, tr, CheckLineLayer, 8); ProgressMeter pm = new ProgressMeter(); pm.SetLimit(validPairs.Count); pm.Start("正在更新已自动匹配的实体..."); var textDataMap = elevationTexts.ToDictionary(t => t.ObjectId, t => t); foreach (var pair in validPairs) { if (double.TryParse(textDataMap[pair.Value].TextContent, out double elevationZ)) { // 更新位置逻辑:需要判断是块还是点 Entity ent = (Entity)tr.GetObject(pair.Key, OpenMode.ForWrite); Point3d currentPos; if (ent is BlockReference blk) { blk.Position = new Point3d(blk.Position.X, blk.Position.Y, elevationZ); currentPos = blk.Position; } else if (ent is DBPoint pt) { pt.Position = new Point3d(pt.Position.X, pt.Position.Y, elevationZ); currentPos = pt.Position; } else if (ent is Polyline pl) { // 对于 2D 多段线,修改 Elevation 属性 pl.Elevation = elevationZ; Extents3d ext = pl.GeometricExtents; currentPos = ext.MinPoint + (ext.MaxPoint - ext.MinPoint) * 0.5; //currentPos = pl.GetPoint3dAt(0); // 取第一个点作为连线起点 } else { continue; } Line line = new Line(currentPos, textDataMap[pair.Value].Position) { Layer = CheckLineLayer, ColorIndex = 256 }; btr.AppendEntity(line); tr.AddNewlyCreatedDBObject(line, true); updatedCount++; } } pm.Stop(); } tr.Commit(); ed.WriteMessage($"\n\n--- 自动处理报告 ---\n成功自动更新 {updatedCount} 个实体。\n{terrainEntities.Count - updatedCount} 个实体需要审核。"); if (terrainEntities.Count > 0) { if (ps == null) { ps = new PaletteSet("审核并校对高程点", new Guid("C4E61D33-3162-4889-A263-6A1D2C52D26C")); } if (ps.Count > 0) { ps.Remove(0); } // UI 控件初始化 var reviewControl = new ReviewAndMatchWindow(terrainEntities, validPairs, elevationTexts, CheckLineLayer, ps); ElementHost host = new ElementHost { Dock = DockStyle.Fill, Child = reviewControl }; ps.Add("ReviewAndMatch", host); ps.Visible = true; } } } catch (System.Exception ex) { ed.WriteMessage($"\n发生严重错误: {ex.Message}\n{ex.StackTrace}"); } } // 匹配算法逻辑保持不变,只需签名中的 List 类型改为 TerrainEntityInfo private Dictionary GetScoredValidPairs(List entities, List texts, double maxDist) { var textCompetition = new Dictionary(); // ... (原逻辑不变,ent.Position 属性是一样的) ... foreach (var text in texts) { var distancesToEnts = entities .Select(b => new Point2d(b.Position.X, b.Position.Y).GetDistanceTo(new Point2d(text.Position.X, text.Position.Y))) .OrderBy(d => d) .ToList(); if (distancesToEnts.Count >= 2) { double d1 = distancesToEnts[0]; double d2 = distancesToEnts[1]; textCompetition[text.ObjectId] = (d2 > 0.001) ? (d1 / d2) : 1.0; } else { textCompetition[text.ObjectId] = 0.0; } } var allPossiblePairs = new List>(); foreach (var ent in entities) { foreach (var text in texts) { double dist = new Point2d(ent.Position.X, ent.Position.Y) .GetDistanceTo(new Point2d(text.Position.X, text.Position.Y)); if (dist <= maxDist) { double distanceScore = 1.0 / (dist + 0.001); double uniquenessScore = 1.0 - textCompetition[text.ObjectId]; double finalScore = 0.7 * distanceScore + 0.3 * uniquenessScore * distanceScore; allPossiblePairs.Add(new Tuple(ent.ObjectId, text.ObjectId, finalScore)); } } } var sortedPairs = allPossiblePairs.OrderByDescending(p => p.Item3).ToList(); var validPairs = new Dictionary(); var matchedEnts = new HashSet(); var matchedTexts = new HashSet(); foreach (var pair in sortedPairs) { if (!matchedEnts.Contains(pair.Item1) && !matchedTexts.Contains(pair.Item2)) { validPairs.Add(pair.Item1, pair.Item2); matchedEnts.Add(pair.Item1); matchedTexts.Add(pair.Item2); } } return validPairs; } /// /// 采集多段线方法 --- /// /// /// /// /// private List CollectPolylines(Transaction tr, BlockTableRecord btr, string layerName) { var list = new List(); foreach (ObjectId id in btr) { if (id.ObjectClass.IsDerivedFrom(RXObject.GetClass(typeof(Polyline)))) { var pl = (Polyline)tr.GetObject(id, OpenMode.ForRead); if (pl.Layer.Equals(layerName, StringComparison.OrdinalIgnoreCase)) { try { // 【核心修改】:对于圆弧/圆形多段线,取其几何中心作为参考点 // 这样即使多段线是由两个半圆组成的,中心点也会准确落在圆心 Extents3d ext = pl.GeometricExtents; Point3d center = ext.MinPoint + (ext.MaxPoint - ext.MinPoint) * 0.5; // 将计算出的“圆心”存入 TerrainEntityInfo list.Add(new TerrainEntityInfo(id, center)); } catch { // 如果某些特殊情况无法获取包围盒,则退而求其次取第一个点 list.Add(new TerrainEntityInfo(id, pl.GetPoint3dAt(0))); } } } } return list; } /// 采集点 /// /// /// /// /// private List CollectPoints(Transaction tr, BlockTableRecord btr, string layerName) { var list = new List(); foreach (ObjectId id in btr) { if (id.ObjectClass.IsDerivedFrom(RXObject.GetClass(typeof(DBPoint)))) { var pt = (DBPoint)tr.GetObject(id, OpenMode.ForRead); if (pt.Layer.Equals(layerName, StringComparison.OrdinalIgnoreCase)) { list.Add(new TerrainEntityInfo(id, pt.Position)); } } } return list; } // 修改:采集块实体 (返回类型变更为 TerrainEntityInfo) private List CollectBlocks(Transaction tr, BlockTableRecord btr, string targetBlockName, string blockLayerName) { var list = new List(); foreach (ObjectId id in btr) if (id.ObjectClass.IsDerivedFrom(RXObject.GetClass(typeof(BlockReference)))) { var br = (BlockReference)tr.GetObject(id, OpenMode.ForRead); string name = br.IsDynamicBlock ? ((BlockTableRecord)tr.GetObject(br.DynamicBlockTableRecord, OpenMode.ForRead)).Name : br.Name; if (br.Layer.Equals(blockLayerName, StringComparison.OrdinalIgnoreCase) && name.Equals(targetBlockName, StringComparison.OrdinalIgnoreCase)) list.Add(new TerrainEntityInfo(id, br.Position)); } return list; } private List CollectFilteredTexts(Transaction tr, BlockTableRecord btr, string textLayerName) { var list = new List(); Regex rgx = new Regex(@"[\u4e00-\u9fa5]"); foreach (ObjectId id in btr) { if (id.ObjectClass.IsDerivedFrom(RXObject.GetClass(typeof(DBText))) || id.ObjectClass.IsDerivedFrom(RXObject.GetClass(typeof(MText)))) { var ent = (Entity)tr.GetObject(id, OpenMode.ForRead); if (ent.Layer.Equals(textLayerName, StringComparison.OrdinalIgnoreCase)) { string txt = (ent is MText mt) ? mt.Contents : ((DBText)ent).TextString; if (!string.IsNullOrWhiteSpace(txt) && !rgx.IsMatch(txt) && txt.Contains(".") && double.TryParse(txt, out _)) { try { Extents3d ext = ent.GeometricExtents; Point3d center = ext.MinPoint + (ext.MaxPoint - ext.MinPoint) * 0.5; list.Add(new TextWithPosition(id, center, txt)); } catch { } } } } } return list; } private void CreateLayerIfNotExists(Database db, Transaction tr, string layerName, short colorIndex) { LayerTable lt = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead); if (!lt.Has(layerName)) { lt.UpgradeOpen(); var ltr = new LayerTableRecord { Name = layerName, Color = Color.FromColorIndex(ColorMethod.ByAci, colorIndex) }; lt.Add(ltr); tr.AddNewlyCreatedDBObject(ltr, true); } } // 清理功能保持不变 public static void CleanAllCheckLines() { // ... (与原代码一致,此处略) ... // 请确保保留原 CleanAllCheckLines 实现 Document doc = Application.DocumentManager.MdiActiveDocument; if (doc == null) return; Database db = doc.Database; Editor ed = doc.Editor; int cleanedCount = 0; using (doc.LockDocument()) using (Transaction tr = db.TransactionManager.StartTransaction()) { try { LayerTable lt = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead); if (!lt.Has(CheckLineLayer)) return; LayerTableRecord layer = (LayerTableRecord)tr.GetObject(lt[CheckLineLayer], OpenMode.ForRead); if (layer.IsLocked) { layer.UpgradeOpen(); layer.IsLocked = false; } BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForRead); foreach (ObjectId id in btr) { if (id.ObjectClass.DxfName.Equals("LINE", StringComparison.OrdinalIgnoreCase)) { Line line = tr.GetObject(id, OpenMode.ForRead) as Line; if (line != null && line.Layer.Equals(CheckLineLayer, StringComparison.OrdinalIgnoreCase)) { line.UpgradeOpen(); line.Erase(); cleanedCount++; } } } tr.Commit(); ed.WriteMessage($"\n已清理 {cleanedCount} 条校对线。"); ed.Regen(); } catch { tr.Abort(); } } } }