Files
SzmediTools/Szmedi.CADkits/TerrainTools.cs
2026-02-23 11:21:51 +08:00

376 lines
18 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<TerrainEntityInfo> terrainEntities = new List<TerrainEntityInfo>();
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<ObjectId, ObjectId> GetScoredValidPairs(List<TerrainEntityInfo> entities, List<TextWithPosition> texts, double maxDist)
{
var textCompetition = new Dictionary<ObjectId, double>();
// ... (原逻辑不变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<Tuple<ObjectId, ObjectId, double>>();
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<ObjectId, ObjectId, double>(ent.ObjectId, text.ObjectId, finalScore));
}
}
}
var sortedPairs = allPossiblePairs.OrderByDescending(p => p.Item3).ToList();
var validPairs = new Dictionary<ObjectId, ObjectId>();
var matchedEnts = new HashSet<ObjectId>();
var matchedTexts = new HashSet<ObjectId>();
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;
}
/// <summary>
/// 采集多段线方法 ---
/// </summary>
/// <param name="tr"></param>
/// <param name="btr"></param>
/// <param name="layerName"></param>
/// <returns></returns>
private List<TerrainEntityInfo> CollectPolylines(Transaction tr, BlockTableRecord btr, string layerName)
{
var list = new List<TerrainEntityInfo>();
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;
}
/// 采集点
/// </summary>
/// <param name="tr"></param>
/// <param name="btr"></param>
/// <param name="layerName"></param>
/// <returns></returns>
private List<TerrainEntityInfo> CollectPoints(Transaction tr, BlockTableRecord btr, string layerName)
{
var list = new List<TerrainEntityInfo>();
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<TerrainEntityInfo> CollectBlocks(Transaction tr, BlockTableRecord btr, string targetBlockName, string blockLayerName)
{
var list = new List<TerrainEntityInfo>();
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<TextWithPosition> CollectFilteredTexts(Transaction tr, BlockTableRecord btr, string textLayerName) { var list = new List<TextWithPosition>(); 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(); }
}
}
}