diff --git a/BoreholeExtract/App.config b/BoreholeExtract/App.config
new file mode 100644
index 0000000..ebe5140
--- /dev/null
+++ b/BoreholeExtract/App.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BoreholeExtract/App.xaml b/BoreholeExtract/App.xaml
new file mode 100644
index 0000000..cb141ef
--- /dev/null
+++ b/BoreholeExtract/App.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+ pack://application:,,,/Wpf.Ui;component/Fonts/#Segoe Fluent Icons
+
+
+
diff --git a/BoreholeExtract/App.xaml.cs b/BoreholeExtract/App.xaml.cs
new file mode 100644
index 0000000..254f8a8
--- /dev/null
+++ b/BoreholeExtract/App.xaml.cs
@@ -0,0 +1,11 @@
+using System.Windows;
+
+namespace BoreholeExtract
+{
+ ///
+ /// App.xaml 的交互逻辑
+ ///
+ public partial class App : Application
+ {
+ }
+}
diff --git a/BoreholeExtract/BoreholeExtract.csproj b/BoreholeExtract/BoreholeExtract.csproj
new file mode 100644
index 0000000..8385694
--- /dev/null
+++ b/BoreholeExtract/BoreholeExtract.csproj
@@ -0,0 +1,40 @@
+
+
+ net472
+ WinExe
+ false
+ true
+ preview
+ true
+ Debug;DefaultBuild;Release
+ False
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Settings.settings
+
+
+
+
+ PreserveNewest
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
\ No newline at end of file
diff --git a/BoreholeExtract/BoreholeLog.cs b/BoreholeExtract/BoreholeLog.cs
new file mode 100644
index 0000000..561c0ee
--- /dev/null
+++ b/BoreholeExtract/BoreholeLog.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace BoreholeExtract
+{
+ public class BoreholeLog
+ {
+ public HeaderInfo Header { get; set; }
+
+ public List Layers { get; set; }
+
+ public double HoleDepth => Header.HoleElevation - Layers.Last().BottomElevation;
+ }
+}
\ No newline at end of file
diff --git a/BoreholeExtract/CategoryConfig.cs b/BoreholeExtract/CategoryConfig.cs
new file mode 100644
index 0000000..013c5e1
--- /dev/null
+++ b/BoreholeExtract/CategoryConfig.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+
+namespace BoreholeExtract
+{
+ public class CategoryConfig
+ {
+ [JsonConverter(typeof(StringEnumConverter))]
+ public Identify Id { get; set; } // 关键:用枚举做唯一键
+ public string Title { get; set; }
+ public List Items { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/BoreholeExtract/CategoryWrapper.cs b/BoreholeExtract/CategoryWrapper.cs
new file mode 100644
index 0000000..624b26e
--- /dev/null
+++ b/BoreholeExtract/CategoryWrapper.cs
@@ -0,0 +1,58 @@
+using System.Collections.ObjectModel;
+
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+
+namespace BoreholeExtract
+{
+ public partial class CategoryWrapper : ObservableObject
+ {
+ public Identify Id { get; set; }
+
+ public string Title { get; set; }
+
+ public ObservableCollection Items { get; set; }
+
+ [ObservableProperty]
+ public partial string InputText { get; set; }
+
+ public CategoryWrapper(Identify id, string title, ObservableCollection items)
+ {
+ Id = id;
+ Title = title;
+ Items = items;
+ }
+ [RelayCommand]
+ private void Remove(string param)
+ {
+ if (param is string item) Items.Remove(item);
+ }
+
+ [RelayCommand]
+ private void Add()
+ {
+ if (!string.IsNullOrWhiteSpace(InputText))
+ {
+ var val = InputText.Trim();
+ if (!Items.Contains(val)) Items.Add(val);
+ InputText = ""; // 清空输入框
+ }
+ }
+ }
+
+ public enum Identify
+ {
+ Company = 0,
+ Project = 1,
+ Mileage = 2,
+ DesignElevation = 3,
+ DrillCode = 4,
+ DrillCategory = 5,
+ HoleDiameter = 6,
+ HoleElevation = 7,
+ StartDate = 8,
+ EndDate = 9,
+ InitialWater = 10,
+ StableWater = 11,
+ }
+}
diff --git a/BoreholeExtract/ConfigService.cs b/BoreholeExtract/ConfigService.cs
new file mode 100644
index 0000000..4c15541
--- /dev/null
+++ b/BoreholeExtract/ConfigService.cs
@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using System.IO;
+
+using Newtonsoft.Json;
+
+namespace BoreholeExtract
+{
+ public class ConfigService
+ {
+ private const string FileName = "config.json";
+
+ // 加载配置
+ public List LoadConfig()
+ {
+ if (!File.Exists(FileName)) return new List();
+
+ try
+ {
+ var json = File.ReadAllText(FileName);
+
+ // 使用 Newtonsoft 反序列化
+ // 即使 JSON 格式稍微有点错(比如多了个逗号),Newtonsoft 通常也能兼容
+ var result = JsonConvert.DeserializeObject>(json);
+
+ return result ?? new List();
+ }
+ catch
+ {
+ // 文件损坏或格式错误,返回空列表,稍后逻辑会填充默认值
+ return new List();
+ }
+ }
+
+ // 保存配置
+ public void SaveConfig(IEnumerable configs)
+ {
+ // 设置格式化选项:缩进排版,方便人眼阅读
+ var json = JsonConvert.SerializeObject(configs, Formatting.Indented);
+
+ File.WriteAllText(FileName, json);
+ }
+ }
+}
diff --git a/BoreholeExtract/HeaderInfo.cs b/BoreholeExtract/HeaderInfo.cs
new file mode 100644
index 0000000..f2afb68
--- /dev/null
+++ b/BoreholeExtract/HeaderInfo.cs
@@ -0,0 +1,33 @@
+namespace BoreholeExtract
+{
+ public class HeaderInfo
+ {
+ public string SurveyUnit { get; set; }
+
+ public string ProjectName { get; set; }
+
+ public string Mileage { get; set; }
+
+ public double DesignElevation { get; set; }
+
+ public string HoleNumber { get; set; }
+
+ public string HoleType { get; set; }
+
+ public double CoordinateX { get; set; }
+
+ public double CoordinateY { get; set; }
+
+ public double HoleElevation { get; set; }
+
+ public string Diameter { get; set; }
+
+ public string StartDate { get; set; }
+
+ public string EndDate { get; set; }
+
+ public double StableWaterLevel { get; set; }
+
+ public double InitialWaterLevel { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/BoreholeExtract/IdentityHelper.cs b/BoreholeExtract/IdentityHelper.cs
new file mode 100644
index 0000000..ef4128b
--- /dev/null
+++ b/BoreholeExtract/IdentityHelper.cs
@@ -0,0 +1,25 @@
+namespace BoreholeExtract
+{
+ public static class IdentityHelper
+ {
+ public static string GetDefaultTitle(Identify id)
+ {
+ return id switch
+ {
+ Identify.Company => "🏢 单位",
+ Identify.Project => "📁 项目名",
+ Identify.Mileage => "🛣️ 里程",
+ Identify.DesignElevation => "📐 设计标高",
+ Identify.DrillCode => "🔢 钻孔编号",
+ Identify.DrillCategory => "🏷️ 钻孔类型",
+ Identify.HoleDiameter => "📏 孔口直径",
+ Identify.HoleElevation => "⛰️ 孔口高程",
+ Identify.StartDate => "📅 开工日期",
+ Identify.EndDate => "🏁 竣工日期",
+ Identify.InitialWater => "💧 初见水位",
+ Identify.StableWater => "🌊 稳定水位",
+ _ => id.ToString()
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoreholeExtract/KeywordMatcher.cs b/BoreholeExtract/KeywordMatcher.cs
new file mode 100644
index 0000000..48d2df7
--- /dev/null
+++ b/BoreholeExtract/KeywordMatcher.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace BoreholeExtract
+{
+ public class KeywordMatcher
+ {
+ // 缓存字典:Key是枚举,Value是用户配置的关键词列表
+ private readonly Dictionary> _keywordMap;
+
+ // 构造函数:传入 ViewModel 中的数据
+ public KeywordMatcher(IEnumerable dashboardItems)
+ {
+ _keywordMap = new Dictionary>();
+
+ foreach (var item in dashboardItems)
+ {
+ // 过滤掉空字符串,防止 txt.Contains("") 永远为 true
+ var validKeywords = item.Items
+ .Where(k => !string.IsNullOrWhiteSpace(k))
+ .ToList();
+
+ if (validKeywords.Count > 0)
+ {
+ _keywordMap[item.Id] = validKeywords;
+ }
+ }
+ }
+
+ ///
+ /// 判断文本是否包含该 ID 下的任意一个关键词
+ ///
+ public bool IsMatch(Identify id, string text)
+ {
+ if (string.IsNullOrEmpty(text)) return false;
+
+ // 如果配置里没有这个 ID,直接返回 false
+ if (!_keywordMap.TryGetValue(id, out var keywords)) return false;
+
+ // 核心逻辑:只要包含任意一个关键词,就返回 true
+ // StringComparison.OrdinalIgnoreCase 可以忽略大小写(可选)
+ return keywords.Any(k => text.Contains(k, StringComparison.OrdinalIgnoreCase));
+ }
+ }
+}
diff --git a/BoreholeExtract/LayerData.cs b/BoreholeExtract/LayerData.cs
new file mode 100644
index 0000000..358b7af
--- /dev/null
+++ b/BoreholeExtract/LayerData.cs
@@ -0,0 +1,15 @@
+namespace BoreholeExtract
+{
+ public class LayerData
+ {
+ public string LayerNo { get; set; }
+
+ public double BottomElevation { get; set; }
+
+ public double BottomDepth { get; set; }
+
+ public double Thickness { get; set; }
+
+ public string Description { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/BoreholeExtract/MainViewModel.cs b/BoreholeExtract/MainViewModel.cs
new file mode 100644
index 0000000..af03f4d
--- /dev/null
+++ b/BoreholeExtract/MainViewModel.cs
@@ -0,0 +1,562 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Windows;
+
+using ACadSharp.Entities;
+
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+
+using CSMath;
+
+using Microsoft.Win32;
+
+namespace BoreholeExtract
+{
+ public partial class MainViewModel : ObservableObject
+ {
+ // ================= 参数配置 =================
+ private const string FrameLayerName = "0";
+ private readonly string OutputDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "提取钻孔数据");
+ // 行对齐容差 (用于地层数字提取,必须严格)
+ private const double RowAlignmentTolerance = 4.0;
+
+ // 表头提取容差 (普通短标签)
+ private const double HeaderStrictDx = 30.0;
+ private const double HeaderStrictDy = 5.0;
+
+ private readonly ConfigService _configService = new ConfigService();
+ public ObservableCollection DashboardItems { get; } = new();
+ [ObservableProperty]
+ public partial string Message { get; set; }
+ public MainViewModel()
+ {
+ LoadData();
+ }
+ private void LoadData()
+ {
+ // 1. 读取配置文件 (List)
+ var savedConfigs = _configService.LoadConfig();
+
+ DashboardItems.Clear();
+
+ // 2. 遍历枚举的所有值:确保界面上包含枚举中定义的每一项
+ foreach (Identify id in Enum.GetValues(typeof(Identify)))
+ {
+ // 尝试从保存的配置中找到对应的项
+ var config = savedConfigs.FirstOrDefault(c => c.Id == id);
+
+ ObservableCollection items;
+ string title;
+
+ if (config != null)
+ {
+ // 如果找到了保存的配置,用保存的数据
+ items = new ObservableCollection(config.Items);
+ title = config.Title; // 使用保存的标题(如果允许改名)或重置为默认
+ }
+ else
+ {
+ // 如果没找到(比如第一次运行,或者新加了枚举项),使用默认值
+ items = new ObservableCollection();
+ title = IdentityHelper.GetDefaultTitle(id);
+ }
+
+ // 创建包装器并添加到界面列表
+ DashboardItems.Add(new CategoryWrapper(id, title, items));
+ }
+ }
+ [RelayCommand]
+ public void SaveConfig()
+ {
+ var configs = DashboardItems.Select(w => new CategoryConfig
+ {
+ Id = w.Id, // 保存枚举 ID
+ Title = w.Title, // 保存标题
+ Items = w.Items.ToList()
+ }).ToList();
+
+ _configService.SaveConfig(configs);
+ }
+
+ [RelayCommand]
+ private async Task Extract()
+ {
+ OpenFileDialog dialog = new OpenFileDialog
+ {
+ Filter = "dwg文件(*.dwg)|*.dwg",
+ Multiselect = true,
+ };
+ if (dialog.ShowDialog() == true)
+ {
+ Action logAction = (msg) =>
+ {
+ // ⚠️ 关键:强制切回 UI 线程更新集合
+ System.Windows.Application.Current.Dispatcher.Invoke(() =>
+ {
+ Logs.Add(msg);
+ });
+ };
+ if (!Directory.Exists(OutputDir))
+ {
+ Directory.CreateDirectory(OutputDir);
+ }
+
+ foreach (var path in dialog.FileNames)
+ {
+ Logs.Add($"{DateTime.Now:HH:mm:ss} > 正在处理 {path}");
+ await Task.Run(() => ExportBoreholeHybrid(path, logAction));
+ }
+ var result = MessageBox.Show("是否打开输出目录", "提示", MessageBoxButton.YesNo, MessageBoxImage.Question);
+ if (result == MessageBoxResult.Yes)
+ {
+ Process.Start(OutputDir);
+ }
+ }
+ }
+ private class TextEntity
+ {
+ public string Text { get; set; }
+ public XYZ Position { get; set; }
+ public double X => Position.X;
+ public double Y => Position.Y;
+ public ulong Id { get; set; }
+ }
+
+ private class FrameInfo
+ {
+ public BoundingBox Bounds { get; set; }
+ public string HoleNumber { get; set; }
+ }
+ public ObservableCollection Logs { get; } = new ObservableCollection();
+ ACadSharp.IO.DwgReader reader;
+ private async void ExportBoreholeHybrid(string dwgPath, Action logCallback)
+ {
+ reader = new(dwgPath);
+ var cadDocument = reader.Read();
+
+ List allTexts = new List();
+ //List rawFrames = new List();
+ var entities = cadDocument.Entities
+ .Where(
+ entity => (entity is ACadSharp.Entities.MText ||
+ entity is ACadSharp.Entities.TextEntity))
+ .ToList();
+ foreach (var entity in entities)
+ {
+ if (entity is MText mText)
+ {
+ allTexts.Add(new TextEntity { Text = mText.Value.Trim(), Position = mText.InsertPoint, Id = mText.Handle });
+ }
+ if (entity is ACadSharp.Entities.TextEntity dbText)
+ {
+ allTexts.Add(new TextEntity { Text = dbText.Value.Trim(), Position = dbText.InsertPoint, Id = dbText.Handle });
+ }
+ }
+ var rawFrames = cadDocument.Entities
+ .Where(
+ entity => entity is LwPolyline polyline && polyline.ConstantWidth == 1.0 && polyline.Layer.Name == FrameLayerName).Select(e => e.GetBoundingBox())
+ .ToList();
+
+ // 2. 过滤嵌套图框
+ List uniqueFrames = FilterNestedFrames(rawFrames);
+ if (uniqueFrames.Count == 0)
+ {
+ logCallback?.Invoke("\n未找到图框。");
+ return;
+ }
+
+ logCallback?.Invoke($"\n识别到 {uniqueFrames.Count} 个图框,正在进行孔号分组...");
+
+ // 3. 预扫描孔号并分组
+ var framesWithHole = PreScanHoleNumbers(uniqueFrames, allTexts);
+ var groupedFrames = framesWithHole
+ .Where(f => !string.IsNullOrEmpty(f.HoleNumber))
+ .GroupBy(f => f.HoleNumber)
+ .ToList();
+
+ List finalLogs = new List();
+
+ // 4. 按孔号处理
+ foreach (var group in groupedFrames)
+ {
+ string currentHoleNo = group.Key;
+
+ // A. 确定主表 (Master Frame)
+ // 通常取列表中的第一个,或者Y坐标最高的那个(假设是第一页)
+ // 我们这里简单取 List 的第一个,因为 PreScan 顺序通常对应选择顺序
+ var masterFrameInfo = group.First();
+ BoundingBox masterBounds = masterFrameInfo.Bounds;
+
+ // B. 获取主表的文字 (用于提取 Header)
+ var masterTexts = allTexts
+ .Where(t => t.X >= masterBounds.Min.X && t.X <= masterBounds.Max.X &&
+ t.Y >= masterBounds.Min.Y && t.Y <= masterBounds.Max.Y)
+ .ToList();
+
+ // C. 获取该孔所有表的文字 (用于提取 Layers)
+ var allPageTexts = new List();
+ foreach (var f in group)
+ {
+ var pageTexts = allTexts
+ .Where(t => t.X >= f.Bounds.Min.X && t.X <= f.Bounds.Max.X &&
+ t.Y >= f.Bounds.Min.Y && t.Y <= f.Bounds.Max.Y)
+ .ToList();
+ allPageTexts.AddRange(pageTexts);
+ }
+
+ try
+ {
+ BoreholeLog log = new BoreholeLog();
+
+ // Step 1: 仅从主表提取 Header (避免空白表头干扰)
+ log.Header = ExtractHeaderOnly(masterTexts);
+
+ // 确保孔号存在
+ if (string.IsNullOrEmpty(log.Header.HoleNumber)) log.Header.HoleNumber = currentHoleNo;
+
+ // Step 2: 从所有合并的文字中提取 Layers (解决跨页描述)
+ log.Layers = ExtractLayersOnly(allPageTexts);
+
+ // 排序
+ log.Layers = log.Layers.OrderBy(l => l.BottomDepth).ToList();
+
+ finalLogs.Add(log);
+ }
+ catch (Exception ex)
+ {
+ logCallback?.Invoke($"\n解析孔 {currentHoleNo} 失败: {ex.Message}");
+ }
+ }
+
+ // 输出
+ string fileName = Path.GetFileNameWithoutExtension(dwgPath);
+ string path = Path.Combine(OutputDir, $"{fileName}_钻孔数据.csv");
+ //string json = JsonConvert.SerializeObject(finalLogs, Formatting.Indented);
+ //File.WriteAllText(path, json);
+
+ ExportToCsvManual(finalLogs, path);
+ logCallback?.Invoke($"\n处理完成!共 {finalLogs.Count} 个钻孔。文件: {path}");
+ }
+ // ----------------------------------------------------------------
+ // 模块 1: Header 提取 (仅针对单张表)
+ // ----------------------------------------------------------------
+ private HeaderInfo ExtractHeaderOnly(List texts)
+ {
+ HeaderInfo h = new HeaderInfo();
+ var matcher = new KeywordMatcher(DashboardItems);
+ foreach (var t in texts)
+ {
+ string txt = t.Text.Replace(" ", "");
+
+ // 🏢 勘察单位
+ if (matcher.IsMatch(Identify.Company, txt))
+ h.SurveyUnit = GetValueStrict(texts, t);
+
+ // 📁 工程名称
+ if (matcher.IsMatch(Identify.Project, txt))
+ h.ProjectName = GetValueStrict(texts, t);
+
+ // 🛣️ 里程
+ if (matcher.IsMatch(Identify.Mileage, txt))
+ h.Mileage = GetValueStrict(texts, t);
+
+ // 📐 设计底板标高
+ if (matcher.IsMatch(Identify.DesignElevation, txt))
+ h.DesignElevation = ExtractDouble(GetValueStrict(texts, t));
+
+ // 🔢 钻孔编号
+ if (matcher.IsMatch(Identify.DrillCode, txt))
+ h.HoleNumber = GetValueStrict(texts, t);
+
+ // 🏷️ 钻孔类别
+ if (matcher.IsMatch(Identify.DrillCategory, txt))
+ h.HoleType = GetValueStrict(texts, t);
+
+ // ⛰️ 孔口标高
+ if (matcher.IsMatch(Identify.HoleElevation, txt))
+ h.HoleElevation = ExtractDouble(GetValueStrict(texts, t));
+
+ // 📏 孔口直径
+ if (matcher.IsMatch(Identify.HoleDiameter, txt))
+ h.Diameter = GetValueStrict(texts, t);
+
+ // 📅 开工日期
+ if (matcher.IsMatch(Identify.StartDate, txt))
+ h.StartDate = GetValueStrict(texts, t);
+
+ // 🏁 竣工日期
+ if (matcher.IsMatch(Identify.EndDate, txt))
+ h.EndDate = GetValueStrict(texts, t);
+
+ // 🌊 稳定水位
+ if (matcher.IsMatch(Identify.StableWater, txt))
+ h.StableWaterLevel = ExtractDouble(GetValueStrict(texts, t));
+
+ // 💧 初见水位
+ if (matcher.IsMatch(Identify.InitialWater, txt))
+ h.InitialWaterLevel = ExtractDouble(GetValueStrict(texts, t));
+
+ if (txt.Contains("X=")) h.CoordinateX = ExtractDouble(txt);
+ if (txt.Contains("Y=")) h.CoordinateY = ExtractDouble(txt);
+ }
+ return h;
+ }
+
+ // ----------------------------------------------------------------
+ // 模块 2: Layers 提取 (针对合并后的全量文字)
+ // ----------------------------------------------------------------
+ private static List ExtractLayersOnly(List texts)
+ {
+ List layers = new List();
+
+ // 1. 找到所有层号 (Anchors) - 包含所有页面的
+ var layerAnchors = texts
+ .Where(t => Regex.IsMatch(t.Text, @"^<[\d-]+>$"))
+ .OrderByDescending(t => t.Y) // 注意:跨页合并后,Y坐标是绝对坐标,排序可能不完全代表深度顺序,但没关系
+ .ToList();
+
+ if (layerAnchors.Count == 0)
+ {
+ // Fallback: 纯数字编号
+ var numericAnchors = texts.Where(t => Regex.IsMatch(t.Text, @"^\d+(-\d+)?$")).ToList();
+ if (numericAnchors.Count > 0)
+ {
+ double minX = numericAnchors.Min(t => t.X);
+ layerAnchors = numericAnchors
+ .Where(t => t.X < minX + 200.0)
+ .OrderByDescending(t => t.Y)
+ .ToList();
+ }
+ }
+
+ foreach (var anchor in layerAnchors)
+ {
+ LayerData layer = new LayerData { LayerNo = anchor.Text };
+
+ // A. 提取数值 (标高/深度/厚度)
+ // 必须严格限制在 anchor 的同一行 (Strict Y)
+ var lineNumbers = texts
+ .Where(t => t.Id != anchor.Id)
+ .Where(t => t.X > anchor.X)
+ .Where(t => Math.Abs(t.Y - anchor.Y) < RowAlignmentTolerance) // 严格同行
+ .Where(t => IsPureNumber(t.Text))
+ .OrderBy(t => t.X)
+ .Take(3)
+ .ToList();
+
+ if (lineNumbers.Count > 0) layer.BottomElevation = ExtractDouble(lineNumbers[0].Text);
+ if (lineNumbers.Count > 1) layer.BottomDepth = ExtractDouble(lineNumbers[1].Text);
+ if (lineNumbers.Count > 2) layer.Thickness = ExtractDouble(lineNumbers[2].Text);
+
+ // B. 提取描述
+ // 全局搜索以 anchor.Text 开头的文字
+ string searchPrefix = anchor.Text.Trim();
+
+ var descEntity = texts
+ .Where(t => t.Id != anchor.Id)
+ .Where(t => t.Text.Trim().StartsWith(searchPrefix)) // 核心:前缀匹配
+ .OrderByDescending(t => t.Text.Length) // 取最长的,最稳
+ .FirstOrDefault();
+
+ if (descEntity != null)
+ {
+ layer.Description = descEntity.Text;
+ }
+
+ layers.Add(layer);
+ }
+
+ return layers;
+ }
+ public void ExportToCsvManual(List data, string filePath)
+ {
+ var sb = new StringBuilder();
+
+ // 1. 写表头
+ sb.AppendLine("BoreName,Easting,Northing,Elevation,HoleDepth,TopDepth,BottomDepth,Identifier");
+
+ // 2. 写数据
+ foreach (var hole in data)
+ {
+ foreach (var item in hole.Layers)
+ {
+ var line = string.Join(
+ ",",
+ Escape(hole.Header.HoleNumber),
+ hole.Header.CoordinateY, // 数字不需要转义
+ hole.Header.CoordinateX,
+ hole.Header.HoleElevation,
+ hole.HoleDepth,
+ Math.Round(hole.Header.HoleElevation - item.BottomElevation - item.Thickness, MidpointRounding.AwayFromZero),
+ hole.Header.HoleElevation - item.BottomElevation,
+ ExtractAndJoinFast(item.Description));
+ sb.AppendLine(line);
+ }
+
+ }
+
+ // 3. 保存文件 (同样需要 UTF8 BOM)
+ File.WriteAllText(filePath, sb.ToString(), new UTF8Encoding(true));
+ }
+ public string ExtractAndJoin(string input)
+ {
+ // 正则解释:
+ // < : 匹配左尖括号
+ // ([^>]+) : 第1组,匹配任意非右尖括号的字符(即 3-6)
+ // > : 匹配右尖括号
+ // ([^::]+) : 第2组,匹配任意非冒号的字符(即 粉质黏土)
+ // [::] : 匹配中文或英文冒号
+ string pattern = @"<([^>]+)>([^::]+)[::]";
+
+ Match match = Regex.Match(input, pattern);
+
+ if (match.Success)
+ {
+ // match.Groups[1] 是尖括号里的内容 (3-6)
+ // match.Groups[2] 是冒号前的内容 (粉质黏土)
+ return $"{match.Groups[1].Value}-{match.Groups[2].Value}";
+ }
+
+ return string.Empty; // 或者返回原字符串,视需求而定
+ }
+ public string ExtractAndJoinFast(string input)
+ {
+ if (string.IsNullOrEmpty(input)) return "";
+
+ // 1. 找关键符号的位置
+ int leftBracket = input.IndexOf('<');
+ int rightBracket = input.IndexOf('>');
+ int colon = input.IndexOf(':'); // 注意这里是中文冒号
+ if (colon == -1) colon = input.IndexOf(':'); // 兼容英文冒号
+
+ // 2. 确保符号都存在且顺序正确 (< 在 > 前,> 在 : 前)
+ if (leftBracket != -1 && rightBracket > leftBracket && colon > rightBracket)
+ {
+ // 提取 3-6
+ string part1 = input.Substring(leftBracket + 1, rightBracket - leftBracket - 1);
+
+ // 提取 粉质黏土
+ string part2 = input.Substring(rightBracket + 1, colon - rightBracket - 1);
+
+ return $"{part1}-{part2}";
+ }
+
+ return "";
+ }
+ // 核心转义函数:防止格式错乱
+ private string Escape(string content)
+ {
+ if (string.IsNullOrEmpty(content)) return "";
+
+ bool needsQuotes = false;
+
+ // 检查是否包含特殊字符
+ if (content.Contains(",") || content.Contains("\"") || content.Contains("\r") || content.Contains("\n"))
+ {
+ needsQuotes = true;
+ }
+
+ // 将内部的一个双引号变成两个双引号
+ content = content.Replace("\"", "\"\"");
+
+ // 如果包含特殊字符,首尾加引号
+ if (needsQuotes)
+ {
+ content = $"\"{content}\"";
+ }
+
+ return content;
+ }
+ // ----------------------------------------------------------------
+ // 辅助功能
+ // ----------------------------------------------------------------
+
+ private List PreScanHoleNumbers(List frames, List allTexts)
+ {
+ var result = new List();
+ foreach (var frame in frames)
+ {
+ var textsInFrame = allTexts
+ .Where(t => frame.IsIn(t.Position))
+ .ToList();
+
+ if (textsInFrame.Count < 5) continue;
+
+ // 尝试找孔号
+ string holeNo = "";
+ var labelNode = textsInFrame.FirstOrDefault(t => t.Text.Replace(" ", "").Contains("钻孔编号"));
+ if (labelNode != null)
+ {
+ holeNo = GetValueStrict(textsInFrame, labelNode);
+ }
+
+ // 必须找到孔号才加入分组,否则可能是图例或其他表格
+ if (!string.IsNullOrEmpty(holeNo))
+ {
+ result.Add(new FrameInfo { Bounds = frame, HoleNumber = holeNo });
+ }
+ }
+ return result;
+ }
+
+ private List FilterNestedFrames(List frames)
+ {
+ if (frames == null || frames.Count == 0) return new List();
+ frames.Sort((a, b) =>
+ {
+ double areaA = (a.Max.X - a.Min.X) * (a.Max.Y - a.Min.Y);
+ double areaB = (b.Max.X - b.Min.X) * (b.Max.Y - b.Min.Y);
+ return areaB.CompareTo(areaA);
+ });
+ List validFrames = new List();
+ foreach (var candidate in frames)
+ {
+ bool isNested = false;
+ foreach (var existing in validFrames)
+ {
+ if (existing.IsIn(candidate))
+ {
+ isNested = true; break;
+ }
+ //if (candidate.Min.X >= existing.Min.X &&
+ // candidate.Max.X <= existing.Max.X &&
+ // candidate.Min.Y >= existing.Min.Y &&
+ // candidate.Max.Y <= existing.Max.Y)
+ //{
+ // isNested = true; break;
+ //}
+ }
+ if (!isNested) validFrames.Add(candidate);
+ }
+ return validFrames;
+ }
+
+ private static string GetValueStrict(List allTexts, TextEntity label)
+ {
+ var target = allTexts
+ .Where(t => t != label)
+ .Where(t => t.X > label.X && t.X < label.X + HeaderStrictDx)
+ .Where(t => Math.Abs(t.Y - label.Y) < HeaderStrictDy)
+ .OrderBy(t => t.X)
+ .FirstOrDefault();
+ return target?.Text ?? "";
+ }
+
+ private static bool IsPureNumber(string text) => Regex.IsMatch(text.Trim(), @"^-?\d+(\.\d+)?$");
+
+ private static double ExtractDouble(string text)
+ {
+ if (string.IsNullOrEmpty(text)) return 0;
+ var match = Regex.Match(text, @"-?\d+(\.\d+)?");
+ return match.Success ? double.Parse(match.Value) : 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/BoreholeExtract/MainWindow.xaml b/BoreholeExtract/MainWindow.xaml
new file mode 100644
index 0000000..ba08cf2
--- /dev/null
+++ b/BoreholeExtract/MainWindow.xaml
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BoreholeExtract/MainWindow.xaml.cs b/BoreholeExtract/MainWindow.xaml.cs
new file mode 100644
index 0000000..c17ae35
--- /dev/null
+++ b/BoreholeExtract/MainWindow.xaml.cs
@@ -0,0 +1,22 @@
+namespace BoreholeExtract
+{
+ ///
+ /// MainWindow.xaml 的交互逻辑
+ ///
+ public partial class MainWindow
+ {
+ public MainWindow()
+ {
+ this.DataContext = new MainViewModel();
+ InitializeComponent();
+ //Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this);
+ }
+ private void ScrollToBottom()
+ {
+ if (LogBox.Items.Count > 0)
+ {
+ LogBox.ScrollIntoView(LogBox.Items[LogBox.Items.Count - 1]);
+ }
+ }
+ }
+}
diff --git a/BoreholeExtract/Properties/AssemblyInfo.cs b/BoreholeExtract/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..1d33707
--- /dev/null
+++ b/BoreholeExtract/Properties/AssemblyInfo.cs
@@ -0,0 +1,52 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("BoreholeExtract")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BoreholeExtract")]
+[assembly: AssemblyCopyright("Copyright © 2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+//若要开始生成可本地化的应用程序,请设置
+//.csproj 文件中的 CultureYouAreCodingWith
+//在 中。例如,如果你使用的是美国英语。
+//使用的是美国英语,请将 设置为 en-US。 然后取消
+//对以下 NeutralResourceLanguage 特性的注释。 更新
+//以下行中的“en-US”以匹配项目文件中的 UICulture 设置。
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //主题特定资源词典所处位置
+ //(未在页面中找到资源时使用,
+ //或应用程序资源字典中找到时使用)
+ ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
+ //(未在页面中找到资源时使用,
+ //、应用程序或任何主题专用资源字典中找到时使用)
+)]
+
+
+// 程序集的版本信息由下列四个值组成:
+//
+// 主版本
+// 次版本
+// 生成号
+// 修订号
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/BoreholeExtract/Properties/Resources.Designer.cs b/BoreholeExtract/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..60d00f3
--- /dev/null
+++ b/BoreholeExtract/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+//
+// 此代码由工具生成。
+// 运行时版本: 4.0.30319.42000
+//
+// 对此文件的更改可能导致不正确的行为,如果
+// 重新生成代码,则所做更改将丢失。
+//
+//------------------------------------------------------------------------------
+
+namespace BoreholeExtract.Properties
+{
+
+
+ ///
+ /// 强类型资源类,用于查找本地化字符串等。
+ ///
+ // 此类是由 StronglyTypedResourceBuilder
+ // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
+ // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
+ // (以 /str 作为命令选项),或重新生成 VS 项目。
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ ///
+ /// 返回此类使用的缓存 ResourceManager 实例。
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BoreholeExtract.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// 重写当前线程的 CurrentUICulture 属性,对
+ /// 使用此强类型资源类的所有资源查找执行重写。
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/BoreholeExtract/Properties/Resources.resx b/BoreholeExtract/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/BoreholeExtract/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/BoreholeExtract/Properties/Settings.Designer.cs b/BoreholeExtract/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..34e31ce
--- /dev/null
+++ b/BoreholeExtract/Properties/Settings.Designer.cs
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+//
+// 此代码由工具生成。
+// 运行时版本:4.0.30319.42000
+//
+// 对此文件的更改可能会导致不正确的行为,并且如果
+// 重新生成代码,这些更改将会丢失。
+//
+//------------------------------------------------------------------------------
+
+namespace BoreholeExtract.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/BoreholeExtract/Properties/Settings.settings b/BoreholeExtract/Properties/Settings.settings
new file mode 100644
index 0000000..8e615f2
--- /dev/null
+++ b/BoreholeExtract/Properties/Settings.settings
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/BoreholeExtract/config.json b/BoreholeExtract/config.json
new file mode 100644
index 0000000..9a435c3
--- /dev/null
+++ b/BoreholeExtract/config.json
@@ -0,0 +1,86 @@
+[
+ {
+ "Id": "Company",
+ "Title": "🏢 勘察单位",
+ "Items": [
+ "勘察单位"
+ ]
+ },
+ {
+ "Id": "Project",
+ "Title": "📁 项目名称",
+ "Items": [
+ "项目名称"
+ ]
+ },
+ {
+ "Id": "Mileage",
+ "Title": "🛣️ 里程",
+ "Items": [
+ "里程"
+ ]
+ },
+ {
+ "Id": "DesignElevation",
+ "Title": "📐 设计标高",
+ "Items": [
+ "设计结构底板标高"
+ ]
+ },
+ {
+ "Id": "DrillCode",
+ "Title": "🔢 钻孔编号",
+ "Items": [
+ "钻孔编号"
+ ]
+ },
+ {
+ "Id": "DrillCategory",
+ "Title": "🏷️ 钻孔类型",
+ "Items": [
+ "钻孔类别"
+ ]
+ },
+ {
+ "Id": "HoleDiameter",
+ "Title": "📏 孔口直径",
+ "Items": [
+ "孔口直径"
+ ]
+ },
+ {
+ "Id": "HoleElevation",
+ "Title": "⛰️ 孔底高程",
+ "Items": [
+ "孔口标高"
+ ]
+ },
+ {
+ "Id": "StartDate",
+ "Title": "📅 开孔日期",
+ "Items": [
+ "开工日期"
+ ]
+ },
+ {
+ "Id": "EndDate",
+ "Title": "🏁 终孔日期",
+ "Items": [
+ "竣工日期"
+ ]
+ },
+ {
+ "Id": "InitialWater",
+ "Title": "💧 初见水位",
+ "Items": [
+ "初见水位"
+ ]
+ },
+ {
+ "Id": "StableWater",
+ "Title": "🌊 稳定水位",
+ "Items": [
+ "稳定水位"
+ ]
+ }
+]
\ No newline at end of file
diff --git a/Setup/SzmediTools2.2.3.82.exe b/Setup/SzmediTools2.2.3.82.exe
new file mode 100644
index 0000000..d937289
Binary files /dev/null and b/Setup/SzmediTools2.2.3.82.exe differ
diff --git a/Szmedi.AIScriptRunner/Services/AuthenticationService.cs b/Szmedi.AIScriptRunner/Services/AuthenticationService.cs
index 28db4ca..a2319a3 100644
--- a/Szmedi.AIScriptRunner/Services/AuthenticationService.cs
+++ b/Szmedi.AIScriptRunner/Services/AuthenticationService.cs
@@ -51,7 +51,7 @@ namespace Szmedi.AIScriptRunner.Services
{
// 2. 用 BouncyCastle 加密
var encrypted = RSAHelper.EncryptByJavaKey(pubKeyVO.Data.PublicKey, $"{password}");
-
+
var jsonData = $"{{\"accountName\":\"{username}\",\"password\":\"{encrypted}\",\"uuid\":\"{pubKeyVO.Data.UUID}\",\"appVersion\":\"newversion\"}}";
// 3. 发送登录请求
var respones = GetStreamReader(Settings.Default.LoginUrl, jsonData);
diff --git a/Szmedi.AIScriptRunner/Web/RSAHelper.cs b/Szmedi.AIScriptRunner/Web/RSAHelper.cs
index 3b13c1b..cfeb642 100644
--- a/Szmedi.AIScriptRunner/Web/RSAHelper.cs
+++ b/Szmedi.AIScriptRunner/Web/RSAHelper.cs
@@ -1,10 +1,5 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Security.Cryptography;
+using System.Security.Cryptography;
using System.Text;
-using System.Threading.Tasks;
namespace Szmedi.AIScriptRunner.Web
{
@@ -228,6 +223,12 @@ namespace Szmedi.AIScriptRunner.Web
//cspParams.KeyContainerName = CONTAINER_NAME;
//RSACryptoServiceProvider rsa1 = new RSACryptoServiceProvider(cspParams);
//rsa1.FromXmlString("2rRVVVFJRbH/wAPDtnwZwu+nxU+AZ6uXxh/sW+AMCBogg7vndZsnRiHoLttYYPqOyOhfgaBOQogrIfrKL4lipK4m52SBzw/FfcM9DsKs/rYR83tBLiIAfgdnVjF27tZID+HJMFTiI30mALjr7+tfp+2lIACXA1RIKTk7S9pDmX8=AQAB92jJJyzFBSx6gL4Y1YpALmc5CNjoE/wETjqb3ci2v0+3rZWvJKmKy1ZEdlXpyuvXVksJ6cMdUpNAkMknUk9pTQ==
4kxkABZOXyDLryYGCGY0b8N0FIdu5BTCFDYEdcatxl/f7ZGDS1NgHJpUWxkVXFfHy2Y/GuDOIbpcwlsO739H+w==
5bNFvrdUHF+VRN45VFjNCcgQLeSkY5mBrdfASoNFGA29LM5iE5nNIMfxPCS7sQiRnq6Af6YFHVtVgJchiMvtqQ==j+ng1qVY5epnXlWiFIla45C7K6sNfIMvAcdwgq39KWEjeWPGyYqWXtpOtzh2eylf6Bx4GVHKBW0NPJTIJMsfLQ==8uu0dfPVDqB2qFM1Vdi8hl+2uZtN7gjT2co1cEWy29HVYBZD0k9KKCf2PbkeuSfpgFpE70wW5Hrp8V7l/SwSOw==MM/c18zroJ2Iqi9s5/asvUBF3pjO3NSEbFjFpP/NT6WdKimvECWPz2xT6NlV0Vc6tQaAAmtn7Bt+HPhfVdrA4/ysYVe3/6TWkPjW+bvAhMWu/ZqISx11/jPYSGD9g3ZXgUiqcQM8UbOjlswoq4fpheEXTB0xdVutDLpO3qgHN6k=");
+
+ //获取公钥:服务器返回一个字符串(pubKey),这是一个 Base64 格式的公钥数据。
+ //解析公钥:将 Base64 还原为字节数组,并解析成 RSA 公钥对象(对应 C# 的 DecodeX509PublicKey)。
+ //准备数据:将密码字符串转为字节数组(UTF - 8)。
+ //RSA 加密:使用解析出来的公钥对密码字节进行加密。
+ //再次 Base64:将加密后的乱码字节数组,转回 Base64 字符串,以便通过 HTTP 发送给服务器。
byte[] bytes = Convert.FromBase64String(pubKey);
RSACryptoServiceProvider rsa1 = DecodeX509PublicKey(bytes);
diff --git a/Szmedi.CADkits/BlockRefWithPosition.cs b/Szmedi.CADkits/BlockRefWithPosition.cs
new file mode 100644
index 0000000..88c6c74
--- /dev/null
+++ b/Szmedi.CADkits/BlockRefWithPosition.cs
@@ -0,0 +1,17 @@
+using Autodesk.AutoCAD.DatabaseServices;
+using Autodesk.AutoCAD.Geometry;
+
+namespace Szmedi.CADkits;
+
+public readonly struct BlockRefWithPosition
+{
+ public ObjectId ObjectId { get; }
+
+ public Point3d Position { get; }
+
+ public BlockRefWithPosition(ObjectId id, Point3d pos)
+ {
+ ObjectId = id;
+ Position = pos;
+ }
+}
diff --git a/Szmedi.CADkits/BlockReviewItem.cs b/Szmedi.CADkits/BlockReviewItem.cs
new file mode 100644
index 0000000..0868fe5
--- /dev/null
+++ b/Szmedi.CADkits/BlockReviewItem.cs
@@ -0,0 +1,37 @@
+using Autodesk.AutoCAD.DatabaseServices;
+
+using System.ComponentModel;
+
+// 用于WPF数据绑定的数据模型
+public class BlockReviewItem : INotifyPropertyChanged
+{
+ private string _handle;
+ private string _position;
+ private string _status; // 新增状态属性
+
+ public ObjectId BlockId { get; set; }
+
+ public string Handle
+ {
+ get => _handle;
+ set { _handle = value; OnPropertyChanged(nameof(Handle)); }
+ }
+
+ public string Position
+ {
+ get => _position;
+ set { _position = value; OnPropertyChanged(nameof(Position)); }
+ }
+
+ public string Status
+ {
+ get => _status;
+ set { _status = value; OnPropertyChanged(nameof(Status)); }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ protected void OnPropertyChanged(string name)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+ }
+}
\ No newline at end of file
diff --git a/Szmedi.CADkits/Properties/AssemblyInfo.cs b/Szmedi.CADkits/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..bc6dc3b
--- /dev/null
+++ b/Szmedi.CADkits/Properties/AssemblyInfo.cs
@@ -0,0 +1,33 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("Szmedi.CADkits")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Szmedi.CADkits")]
+[assembly: AssemblyCopyright("Copyright © 2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("a8343f78-b04d-4c75-bde3-8133314b5597")]
+
+// 程序集的版本信息由下列四个值组成:
+//
+// 主版本
+// 次版本
+// 生成号
+// 修订号
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Szmedi.CADkits/ReviewAndMatchWindow.xaml b/Szmedi.CADkits/ReviewAndMatchWindow.xaml
new file mode 100644
index 0000000..6685377
--- /dev/null
+++ b/Szmedi.CADkits/ReviewAndMatchWindow.xaml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Szmedi.CADkits/ReviewAndMatchWindow.xaml.cs b/Szmedi.CADkits/ReviewAndMatchWindow.xaml.cs
new file mode 100644
index 0000000..28537c7
--- /dev/null
+++ b/Szmedi.CADkits/ReviewAndMatchWindow.xaml.cs
@@ -0,0 +1,287 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+
+using Autodesk.AutoCAD.ApplicationServices;
+using Autodesk.AutoCAD.DatabaseServices;
+using Autodesk.AutoCAD.EditorInput;
+using Autodesk.AutoCAD.Geometry;
+using Autodesk.AutoCAD.Windows;
+
+using Application = Autodesk.AutoCAD.ApplicationServices.Application;
+
+
+namespace Szmedi.CADkits
+{
+ public partial class ReviewAndMatchWindow : UserControl
+ {
+ public ObservableCollection ReviewItems { get; set; }
+ private readonly string _checkLineLayer;
+ private bool _isListeningToSelection = true;
+ private readonly PaletteSet _parentPalette;
+ private readonly Dictionary _textDataMap;
+ public ReviewAndMatchWindow(
+ List allBlocks,
+ Dictionary autoPairs,
+ List allTexts,
+ string checkLineLayer,
+ PaletteSet parentPalette)
+ {
+ InitializeComponent();
+ _checkLineLayer = checkLineLayer;
+ _parentPalette = parentPalette;
+ _textDataMap = allTexts.ToDictionary(t => t.ObjectId, t => t);
+
+ ReviewItems = new ObservableCollection();
+ ReviewListView.ItemsSource = ReviewItems;
+
+ PopulateList(allBlocks, autoPairs);
+ }
+ private void PopulateList(List allBlocks, Dictionary autoPairs)
+ {
+ foreach (var blockData in allBlocks)
+ {
+ var item = new BlockReviewItem
+ {
+ BlockId = blockData.ObjectId,
+ Handle = blockData.ObjectId.Handle.Value.ToString(),
+ Position = $"{blockData.Position.X:F2}, {blockData.Position.Y:F2}"
+ };
+
+ if (autoPairs.TryGetValue(blockData.ObjectId, out ObjectId textId))
+ {
+ // 自动匹配成功
+ item.Status = _textDataMap[textId].TextContent;
+ }
+ else
+ {
+ // 未匹配
+ item.Status = "--- 未匹配 ---";
+ }
+ ReviewItems.Add(item);
+ }
+ }
+
+ //private void LocateButton_Click(object sender, RoutedEventArgs e)
+ //{
+ // if (UnmatchedBlocksListView.SelectedItem is UnmatchedBlockItem selected)
+ // {
+ // ZoomToEntity(selected.BlockId);
+ // }
+ // else
+ // {
+ // MessageBox.Show("请先在列表中选择一个块。", "提示");
+ // }
+ //}
+ private void MatchButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (!(ReviewListView.SelectedItem is BlockReviewItem selected))
+ {
+ MessageBox.Show("请先在列表中选择一个要匹配的块。", "提示");
+ return;
+ }
+
+ Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
+ Editor ed = doc.Editor;
+
+ // 通过控制父面板来隐藏
+ _parentPalette.Visible = false;
+
+ try
+ {
+ PromptEntityOptions peo = new PromptEntityOptions("\n请点选此块对应的标高文字: ");
+ peo.SetRejectMessage("\n选择的不是文字对象。");
+ peo.AddAllowedClass(typeof(DBText), true);
+ peo.AddAllowedClass(typeof(MText), true);
+
+ PromptEntityResult per = ed.GetEntity(peo);
+
+ if (per.Status == PromptStatus.OK)
+ {
+ UpdateBlockWithSelectedText(selected, per.ObjectId);
+ }
+ }
+ catch (System.Exception ex)
+ {
+ MessageBox.Show($"发生错误: {ex.Message}");
+ }
+ finally
+ {
+ // 操作结束后,重新显示父面板
+ _parentPalette.Visible = true;
+ }
+ }
+
+ private void UpdateBlockWithSelectedText(BlockReviewItem itemToUpdate, ObjectId textId)
+ {
+ Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
+ Database db = doc.Database;
+ using (DocumentLock docLock = doc.LockDocument())
+ {
+ using (Transaction tr = db.TransactionManager.StartTransaction())
+ {
+ // 1. 获取文字内容和中心点
+ Entity textEnt = (Entity)tr.GetObject(textId, OpenMode.ForRead);
+ string textContent = "";
+ if (textEnt is MText mtext) textContent = mtext.Contents;
+ else if (textEnt is DBText dbtext) textContent = dbtext.TextString;
+
+ // 验证文字内容
+ if (!double.TryParse(textContent, NumberStyles.Float, CultureInfo.InvariantCulture, out double elevationZ))
+ {
+ MessageBox.Show($"文字内容 '{textContent}' 不是有效的数字。", "匹配失败");
+ tr.Abort();
+ return;
+ }
+
+ // 2. 更新块
+ BlockReference blockRef = (BlockReference)tr.GetObject(itemToUpdate.BlockId, OpenMode.ForWrite);
+ blockRef.Position = new Point3d(blockRef.Position.X, blockRef.Position.Y, elevationZ);
+
+ // 3. 创建校对线
+ BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
+ Extents3d textExtents = textEnt.GeometricExtents;
+ Point3d textCenter = textExtents.MinPoint + (textExtents.MaxPoint - textExtents.MinPoint) * 0.5;
+
+ Line checkLine = new Line(blockRef.Position, textCenter);
+ checkLine.Layer = _checkLineLayer;
+ checkLine.ColorIndex = 256; // ByLayer
+ btr.AppendEntity(checkLine);
+ tr.AddNewlyCreatedDBObject(checkLine, true);
+ itemToUpdate.Status = $"手动: {elevationZ}";
+
+ tr.Commit();
+ //Autodesk.AutoCAD.ApplicationServices.Application.ShowAlertDialog($"块 {itemToUpdate.Handle} 已手动更新为 {elevationZ}。");
+ }
+ }
+ }
+
+ private void CloseButton_Click(object sender, RoutedEventArgs e)
+ {
+ _parentPalette.Visible = false;
+ }
+
+ // 辅助方法:缩放到实体
+ private void ZoomToEntity(ObjectId entId)
+ {
+ Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
+ if (doc == null) return;
+ Editor ed = doc.Editor;
+ Database db = doc.Database;
+
+ using (Transaction tr = db.TransactionManager.StartTransaction())
+ {
+ if (entId.IsErased) return;
+ Entity ent = tr.GetObject(entId, OpenMode.ForRead) as Entity;
+ if (ent != null)
+ {
+ // 创建一个基于实体包围盒的视图
+ Extents3d extents = ent.GeometricExtents;
+ Matrix3d ucs = ed.CurrentUserCoordinateSystem;
+ double viewSize = Math.Max(extents.MaxPoint.X - extents.MinPoint.X, extents.MaxPoint.Y - extents.MinPoint.Y) * 5.0;
+ // 扩大一点包围盒,使其不至于占满全屏
+ extents.TransformBy(ucs.Inverse());
+ ViewTableRecord view = new ViewTableRecord
+ {
+ CenterPoint = new Point2d(
+ (extents.MinPoint.X + extents.MaxPoint.X) / 2.0,
+ (extents.MinPoint.Y + extents.MaxPoint.Y) / 2.0),
+ Height = viewSize,
+ Width = viewSize
+ };
+ ed.SetCurrentView(view);
+ ed.SetImpliedSelection(new ObjectId[] { entId });
+ }
+ tr.Commit();
+ }
+ }
+
+ private void ListView_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
+ {
+ if (ReviewListView.SelectedItem is BlockReviewItem selected)
+ {
+ ZoomToEntity(selected.BlockId);
+ }
+ }
+
+
+ private void SelectAndScrollToItem(ObjectId blockId)
+ {
+ var itemToSelect = ReviewItems.FirstOrDefault(item => item.BlockId == blockId);
+ if (itemToSelect != null)
+ {
+ ReviewListView.SelectedItem = itemToSelect;
+ ReviewListView.ScrollIntoView(itemToSelect);
+ }
+ }
+
+
+
+ private void CleanLinesButton_Click(object sender, RoutedEventArgs e)
+ {
+ MessageBoxResult result = MessageBox.Show(
+ "确定要删除所有由本工具创建的校对线吗?\n此操作不可撤销。",
+ "确认清理",
+ MessageBoxButton.YesNo,
+ MessageBoxImage.Warning);
+
+ if (result == MessageBoxResult.Yes)
+ {
+ // 调用主类中的公共静态方法来执行清理
+ TerrainTools.CleanAllCheckLines();
+ }
+ }
+
+ private void LocateInListButton_Click(object sender, RoutedEventArgs e)
+ {
+ Document doc = Application.DocumentManager.MdiActiveDocument;
+ Editor ed = doc.Editor;
+ ObjectId targetId = ObjectId.Null;
+
+ _isListeningToSelection = false; // 暂时关闭自动监听,避免冲突
+
+ try
+ {
+ // 1. 检查是否已经有选中的对象
+ PromptSelectionResult psr = ed.GetSelection();
+ if (psr.Status == PromptStatus.OK && psr.Value.Count > 0)
+ {
+ // 如果有,直接使用第一个
+ targetId = psr.Value.GetObjectIds()[0];
+ }
+ else
+ {
+ // 2. 如果没有选中,则提示用户去选择
+ _parentPalette.Visible = false; // 隐藏面板方便用户选择
+
+ PromptEntityOptions peo = new PromptEntityOptions("\n请在图中选择一个地形点块进行定位: ");
+ peo.SetRejectMessage("\n选择的不是块参照。");
+ peo.AddAllowedClass(typeof(BlockReference), true);
+
+ PromptEntityResult per = ed.GetEntity(peo);
+ if (per.Status == PromptStatus.OK)
+ {
+ targetId = per.ObjectId;
+ }
+ }
+
+ // 3. 如果成功获取到ID,则在列表中定位
+ if (!targetId.IsNull)
+ {
+ SelectAndScrollToItem(targetId);
+ }
+ }
+ finally
+ {
+ // 4. 无论如何都恢复UI状态
+ _isListeningToSelection = true; // 重新开启监听
+ _parentPalette.Visible = true;
+ this.Focus();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Szmedi.CADkits/SpatialExtractCommand.cs b/Szmedi.CADkits/SpatialExtractCommand.cs
new file mode 100644
index 0000000..59d941a
--- /dev/null
+++ b/Szmedi.CADkits/SpatialExtractCommand.cs
@@ -0,0 +1,338 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+using Autodesk.AutoCAD.ApplicationServices;
+using Autodesk.AutoCAD.DatabaseServices;
+using Autodesk.AutoCAD.EditorInput;
+using Autodesk.AutoCAD.Geometry;
+using Autodesk.AutoCAD.Runtime;
+
+using Newtonsoft.Json;
+
+namespace BoreholeTool
+{
+ public class BoreholeHybridCommand
+ {
+ // ================= 参数配置 =================
+ private const string FrameLayerName = "0";
+
+ // 行对齐容差 (用于地层数字提取,必须严格)
+ private const double RowAlignmentTolerance = 4.0;
+
+ // 表头提取容差 (普通短标签)
+ private const double HeaderStrictDx = 50.0;
+ private const double HeaderStrictDy = 5.0;
+
+ private class TextEntity
+ {
+ public string Text { get; set; }
+ public Point3d Position { get; set; }
+ public double X => Position.X;
+ public double Y => Position.Y;
+ public ObjectId Id { get; set; }
+ }
+
+ private class FrameInfo
+ {
+ public Extents3d Bounds { get; set; }
+ public string HoleNumber { get; set; }
+ }
+
+ [CommandMethod("ExportBoreholeHybrid")]
+ public void ExportBoreholeHybrid()
+ {
+ Document doc = Application.DocumentManager.MdiActiveDocument;
+ Editor ed = doc.Editor;
+
+ PromptSelectionOptions opts = new PromptSelectionOptions();
+ opts.MessageForAdding = "\n请框选所有表格区域: ";
+ SelectionSet ss = ed.GetSelection(opts).Value;
+ if (ss == null || ss.Count == 0) return;
+
+ List allTexts = new List();
+ List rawFrames = new List();
+
+ // 1. 读取数据
+ using (Transaction tr = doc.TransactionManager.StartTransaction())
+ {
+ foreach (SelectedObject sobj in ss)
+ {
+ Entity ent = tr.GetObject(sobj.ObjectId, OpenMode.ForRead) as Entity;
+
+ if ((ent is Polyline || ent is Polyline2d) && ent.Layer == FrameLayerName)
+ {
+ if (ent.Bounds.HasValue) rawFrames.Add(ent.Bounds.Value);
+ }
+ else if (ent is DBText dbText)
+ {
+ allTexts.Add(new TextEntity { Text = dbText.TextString.Trim(), Position = dbText.Position, Id = dbText.ObjectId });
+ }
+ else if (ent is MText mText)
+ {
+ allTexts.Add(new TextEntity { Text = mText.Text.Trim(), Position = mText.Location, Id = mText.ObjectId });
+ }
+ }
+ tr.Commit();
+ }
+
+ // 2. 过滤嵌套图框
+ List uniqueFrames = FilterNestedFrames(rawFrames);
+ if (uniqueFrames.Count == 0) { ed.WriteMessage("\n未找到图框。"); return; }
+
+ ed.WriteMessage($"\n识别到 {uniqueFrames.Count} 个图框,正在进行孔号分组...");
+
+ // 3. 预扫描孔号并分组
+ var framesWithHole = PreScanHoleNumbers(uniqueFrames, allTexts);
+ var groupedFrames = framesWithHole
+ .Where(f => !string.IsNullOrEmpty(f.HoleNumber))
+ .GroupBy(f => f.HoleNumber)
+ .ToList();
+
+ List finalLogs = new List();
+
+ // 4. 按孔号处理
+ foreach (var group in groupedFrames)
+ {
+ string currentHoleNo = group.Key;
+
+ // A. 确定主表 (Master Frame)
+ // 通常取列表中的第一个,或者Y坐标最高的那个(假设是第一页)
+ // 我们这里简单取 List 的第一个,因为 PreScan 顺序通常对应选择顺序
+ var masterFrameInfo = group.First();
+ Extents3d masterBounds = masterFrameInfo.Bounds;
+
+ // B. 获取主表的文字 (用于提取 Header)
+ var masterTexts = allTexts
+ .Where(t => t.X >= masterBounds.MinPoint.X && t.X <= masterBounds.MaxPoint.X &&
+ t.Y >= masterBounds.MinPoint.Y && t.Y <= masterBounds.MaxPoint.Y)
+ .ToList();
+
+ // C. 获取该孔所有表的文字 (用于提取 Layers)
+ var allPageTexts = new List();
+ foreach (var f in group)
+ {
+ var pageTexts = allTexts
+ .Where(t => t.X >= f.Bounds.MinPoint.X && t.X <= f.Bounds.MaxPoint.X &&
+ t.Y >= f.Bounds.MinPoint.Y && t.Y <= f.Bounds.MaxPoint.Y)
+ .ToList();
+ allPageTexts.AddRange(pageTexts);
+ }
+
+ try
+ {
+ BoreholeLog log = new BoreholeLog();
+
+ // Step 1: 仅从主表提取 Header (避免空白表头干扰)
+ log.Header = ExtractHeaderOnly(masterTexts);
+
+ // 确保孔号存在
+ if (string.IsNullOrEmpty(log.Header.HoleNumber)) log.Header.HoleNumber = currentHoleNo;
+
+ // Step 2: 从所有合并的文字中提取 Layers (解决跨页描述)
+ log.Layers = ExtractLayersOnly(allPageTexts);
+
+ // 排序
+ log.Layers = log.Layers.OrderBy(l => l.BottomDepth).ToList();
+
+ finalLogs.Add(log);
+ }
+ catch (Autodesk.AutoCAD.Runtime.Exception ex)
+ {
+ ed.WriteMessage($"\n解析孔 {currentHoleNo} 失败: {ex.Message}");
+ }
+ }
+
+ // 输出
+ string json = JsonConvert.SerializeObject(finalLogs, Formatting.Indented);
+ string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "BoreholeHybrid.json");
+ File.WriteAllText(path, json);
+ ed.WriteMessage($"\n处理完成!共 {finalLogs.Count} 个钻孔。文件: {path}");
+ }
+
+ // ----------------------------------------------------------------
+ // 模块 1: Header 提取 (仅针对单张表)
+ // ----------------------------------------------------------------
+ private HeaderInfo ExtractHeaderOnly(List texts)
+ {
+ HeaderInfo h = new HeaderInfo();
+ foreach (var t in texts)
+ {
+ string txt = t.Text.Replace(" ", "");
+
+ if (txt.Contains("勘察单位")) h.SurveyUnit = GetValueStrict(texts, t);
+ if (txt.Contains("工程名称")) h.ProjectName = GetValueStrict(texts, t);
+ if (txt.Contains("里程")) h.Mileage = GetValueStrict(texts, t);
+ if (txt.Contains("设计结构底板标高")) h.DesignElevation = GetValueStrict(texts, t);
+
+ if (txt.Contains("钻孔编号")) h.HoleNumber = GetValueStrict(texts, t);
+ if (txt.Contains("钻孔类别")) h.HoleType = GetValueStrict(texts, t);
+
+ if (txt.Contains("X=")) h.CoordinateX = ExtractDouble(txt);
+ if (txt.Contains("Y=")) h.CoordinateY = ExtractDouble(txt);
+
+ if (txt.Contains("孔口标高")) h.HoleElevation = GetValueStrict(texts, t);
+ if (txt.Contains("孔口直径")) h.Diameter = GetValueStrict(texts, t);
+ if (txt.Contains("开工日期")) h.StartDate = GetValueStrict(texts, t);
+ if (txt.Contains("竣工日期")) h.EndDate = GetValueStrict(texts, t);
+ if (txt.Contains("稳定水位")) h.StableWaterLevel = GetValueStrict(texts, t);
+ if (txt.Contains("初见水位")) h.InitialWaterLevel = GetValueStrict(texts, t);
+ }
+ return h;
+ }
+
+ // ----------------------------------------------------------------
+ // 模块 2: Layers 提取 (针对合并后的全量文字)
+ // ----------------------------------------------------------------
+ private List ExtractLayersOnly(List texts)
+ {
+ List layers = new List();
+
+ // 1. 找到所有层号 (Anchors) - 包含所有页面的
+ var layerAnchors = texts
+ .Where(t => Regex.IsMatch(t.Text, @"^<[\d-]+>$"))
+ .OrderByDescending(t => t.Y) // 注意:跨页合并后,Y坐标是绝对坐标,排序可能不完全代表深度顺序,但没关系
+ .ToList();
+
+ if (layerAnchors.Count == 0)
+ {
+ // Fallback: 纯数字编号
+ var numericAnchors = texts.Where(t => Regex.IsMatch(t.Text, @"^\d+(-\d+)?$")).ToList();
+ if (numericAnchors.Count > 0)
+ {
+ double minX = numericAnchors.Min(t => t.X);
+ layerAnchors = numericAnchors
+ .Where(t => t.X < minX + 200.0)
+ .OrderByDescending(t => t.Y)
+ .ToList();
+ }
+ }
+
+ foreach (var anchor in layerAnchors)
+ {
+ LayerData layer = new LayerData { LayerNo = anchor.Text };
+
+ // A. 提取数值 (标高/深度/厚度)
+ // 必须严格限制在 anchor 的同一行 (Strict Y)
+ var lineNumbers = texts
+ .Where(t => t.Id != anchor.Id)
+ .Where(t => t.X > anchor.X)
+ .Where(t => Math.Abs(t.Y - anchor.Y) < RowAlignmentTolerance) // 严格同行
+ .Where(t => IsPureNumber(t.Text))
+ .OrderBy(t => t.X)
+ .Take(3)
+ .ToList();
+
+ if (lineNumbers.Count > 0) layer.BottomElevation = ExtractDouble(lineNumbers[0].Text);
+ if (lineNumbers.Count > 1) layer.BottomDepth = ExtractDouble(lineNumbers[1].Text);
+ if (lineNumbers.Count > 2) layer.Thickness = ExtractDouble(lineNumbers[2].Text);
+
+ // B. 提取描述
+ // 全局搜索以 anchor.Text 开头的文字
+ string searchPrefix = anchor.Text.Trim();
+
+ var descEntity = texts
+ .Where(t => t.Id != anchor.Id)
+ .Where(t => t.Text.Trim().StartsWith(searchPrefix)) // 核心:前缀匹配
+ .OrderByDescending(t => t.Text.Length) // 取最长的,最稳
+ .FirstOrDefault();
+
+ if (descEntity != null)
+ {
+ layer.Description = descEntity.Text;
+ }
+
+ layers.Add(layer);
+ }
+
+ return layers;
+ }
+
+ // ----------------------------------------------------------------
+ // 辅助功能
+ // ----------------------------------------------------------------
+
+ private List PreScanHoleNumbers(List frames, List allTexts)
+ {
+ var result = new List();
+ foreach (var frame in frames)
+ {
+ var textsInFrame = allTexts
+ .Where(t => t.X >= frame.MinPoint.X && t.X <= frame.MaxPoint.X &&
+ t.Y >= frame.MinPoint.Y && t.Y <= frame.MaxPoint.Y)
+ .ToList();
+
+ if (textsInFrame.Count < 5) continue;
+
+ // 尝试找孔号
+ string holeNo = "";
+ var labelNode = textsInFrame.FirstOrDefault(t => t.Text.Replace(" ", "").Contains("钻孔编号"));
+ if (labelNode != null)
+ {
+ holeNo = GetValueStrict(textsInFrame, labelNode);
+ }
+
+ // 必须找到孔号才加入分组,否则可能是图例或其他表格
+ if (!string.IsNullOrEmpty(holeNo))
+ {
+ result.Add(new FrameInfo { Bounds = frame, HoleNumber = holeNo });
+ }
+ }
+ return result;
+ }
+
+ private List FilterNestedFrames(List frames)
+ {
+ if (frames == null || frames.Count == 0) return new List();
+ frames.Sort((a, b) =>
+ {
+ double areaA = (a.MaxPoint.X - a.MinPoint.X) * (a.MaxPoint.Y - a.MinPoint.Y);
+ double areaB = (b.MaxPoint.X - b.MinPoint.X) * (b.MaxPoint.Y - b.MinPoint.Y);
+ return areaB.CompareTo(areaA);
+ });
+ List validFrames = new List();
+ foreach (var candidate in frames)
+ {
+ bool isNested = false;
+ foreach (var existing in validFrames)
+ {
+ if (candidate.MinPoint.X >= existing.MinPoint.X &&
+ candidate.MaxPoint.X <= existing.MaxPoint.X &&
+ candidate.MinPoint.Y >= existing.MinPoint.Y &&
+ candidate.MaxPoint.Y <= existing.MaxPoint.Y)
+ {
+ isNested = true; break;
+ }
+ }
+ if (!isNested) validFrames.Add(candidate);
+ }
+ return validFrames;
+ }
+
+ private string GetValueStrict(List allTexts, TextEntity label)
+ {
+ var target = allTexts
+ .Where(t => t != label)
+ .Where(t => t.X > label.X && t.X < label.X + HeaderStrictDx)
+ .Where(t => Math.Abs(t.Y - label.Y) < HeaderStrictDy)
+ .OrderBy(t => t.X)
+ .FirstOrDefault();
+ return target?.Text ?? "";
+ }
+
+ private bool IsPureNumber(string text) => Regex.IsMatch(text.Trim(), @"^-?\d+(\.\d+)?$");
+
+ private double ExtractDouble(string text)
+ {
+ if (string.IsNullOrEmpty(text)) return 0;
+ var match = Regex.Match(text, @"-?\d+(\.\d+)?");
+ return match.Success ? double.Parse(match.Value) : 0;
+ }
+
+ public class BoreholeLog { public HeaderInfo Header { get; set; } = new HeaderInfo(); public List Layers { get; set; } = new List(); }
+ public class HeaderInfo { public string SurveyUnit { get; set; } public string ProjectName { get; set; } public string Mileage { get; set; } public string DesignElevation { get; set; } public string HoleNumber { get; set; } public string HoleType { get; set; } public double CoordinateX { get; set; } public double CoordinateY { get; set; } public string HoleElevation { get; set; } public string Diameter { get; set; } public string StartDate { get; set; } public string EndDate { get; set; } public string StableWaterLevel { get; set; } public string InitialWaterLevel { get; set; } }
+ public class LayerData { public string LayerNo { get; set; } public double BottomElevation { get; set; } public double BottomDepth { get; set; } public double Thickness { get; set; } public string Description { get; set; } }
+ }
+}
\ No newline at end of file
diff --git a/Szmedi.CADkits/Szmedi.CADkits.csproj b/Szmedi.CADkits/Szmedi.CADkits.csproj
new file mode 100644
index 0000000..4236e57
--- /dev/null
+++ b/Szmedi.CADkits/Szmedi.CADkits.csproj
@@ -0,0 +1,23 @@
+
+
+ net472
+ Library
+ false
+ true
+ 10.0
+ True
+ Debug;DefaultBuild;Release
+ False
+
+
+
+
+
+
+
+
+ 1.0.0
+
+
+
+
\ No newline at end of file
diff --git a/Szmedi.CADkits/TerrainTools.cs b/Szmedi.CADkits/TerrainTools.cs
new file mode 100644
index 0000000..7c67b8b
--- /dev/null
+++ b/Szmedi.CADkits/TerrainTools.cs
@@ -0,0 +1,278 @@
+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; string blockLayerName; string textLayerName; double maxDistance = 5.0;
+ using (Transaction tr = db.TransactionManager.StartTransaction()) { PromptEntityOptions peoBlock = new PromptEntityOptions("\n请选择一个样例地形点块 (用于确定块名和图层): "); peoBlock.SetRejectMessage("\n选择的不是块参照。"); peoBlock.AddAllowedClass(typeof(BlockReference), true); PromptEntityResult perBlock = ed.GetEntity(peoBlock); if (perBlock.Status != PromptStatus.OK) { ed.WriteMessage("\n操作已取消。"); return; } BlockReference sampleBlockRef = (BlockReference)tr.GetObject(perBlock.ObjectId, OpenMode.ForRead); targetBlockName = sampleBlockRef.IsDynamicBlock ? ((BlockTableRecord)tr.GetObject(sampleBlockRef.DynamicBlockTableRecord, OpenMode.ForRead)).Name : sampleBlockRef.Name; blockLayerName = sampleBlockRef.Layer; ed.WriteMessage($"\n已选择样例块: '{targetBlockName}', 所在图层: '{blockLayerName}'"); tr.Commit(); }
+ 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(); }
+ 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; ed.WriteMessage($"\n最大匹配距离设置为: {maxDistance}");
+ #endregion
+
+ // --- Phase 2: Automatic Matching ---
+ List terrainBlocks;
+ List elevationTexts;
+ Dictionary validPairs;
+ int updatedCount = 0;
+
+ using (Transaction tr = db.TransactionManager.StartTransaction())
+ {
+ BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
+ terrainBlocks = CollectBlocks(tr, btr, targetBlockName, blockLayerName);
+ if (terrainBlocks.Count == 0) { ed.WriteMessage($"\n错误: 在图层 '{blockLayerName}' 上未找到名为 '{targetBlockName}' 的块。"); return; }
+ elevationTexts = CollectFilteredTexts(tr, btr, textLayerName);
+ if (elevationTexts.Count == 0) { ed.WriteMessage($"\n警告: 在图层 '{textLayerName}' 上未找到任何有效的标高文字。"); tr.Commit(); return; }
+
+ ed.WriteMessage($"\n正在使用贪婪匹配算法分析 {terrainBlocks.Count} 个块...");
+ validPairs = GetScoredValidPairs(terrainBlocks, 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))
+ {
+ BlockReference blockRef = (BlockReference)tr.GetObject(pair.Key, OpenMode.ForWrite);
+ blockRef.Position = new Point3d(blockRef.Position.X, blockRef.Position.Y, elevationZ);
+ Line line = new Line(blockRef.Position, textDataMap[pair.Value].Position);
+ line.Layer = CheckLineLayer;
+ line.ColorIndex = 256;
+ btr.AppendEntity(line);
+ tr.AddNewlyCreatedDBObject(line, true);
+ updatedCount++;
+ }
+ }
+ pm.Stop();
+ }
+ tr.Commit();
+ }
+
+ // --- Phase 3: Reporting and Launching Review Palette ---
+ ed.WriteMessage($"\n\n--- 自动处理报告 ---\n成功自动更新 {updatedCount} 个块。\n{terrainBlocks.Count - updatedCount} 个块需要审核。");
+
+ if (terrainBlocks.Count > 0)
+ {
+ if (ps == null) { ps = new PaletteSet("审核并校对高程点", new Guid("C4E61D33-3162-4889-A263-6A1D2C52D26C")); }
+ if (ps.Count > 0) { ps.Remove(0); }
+
+ var reviewControl = new ReviewAndMatchWindow(terrainBlocks, 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}"); }
+ }
+ private Dictionary GetScoredValidPairs(List blocks, List texts, double maxDist)
+ {
+ // 1. 预先计算每个文字的竞争情况
+ var textCompetition = new Dictionary();
+ foreach (var text in texts)
+ {
+ var distancesToBlocks = blocks
+ .Select(b => new Point2d(b.Position.X, b.Position.Y).GetDistanceTo(new Point2d(text.Position.X, text.Position.Y)))
+ .OrderBy(d => d)
+ .ToList();
+
+ if (distancesToBlocks.Count >= 2)
+ {
+ double d1 = distancesToBlocks[0]; // 最近距离
+ double d2 = distancesToBlocks[1]; // 次近距离
+ // 竞争度比率:d1/d2越小,说明越没有争议。范围 [0, 1]
+ textCompetition[text.ObjectId] = (d2 > 0.001) ? (d1 / d2) : 1.0;
+ }
+ else
+ {
+ textCompetition[text.ObjectId] = 0.0; // 只有一个块,没有竞争
+ }
+ }
+
+ // 2. 计算所有可能配对的综合得分
+ var allPossiblePairs = new List>(); // BlockId, TextId, Score
+ foreach (var block in blocks)
+ {
+ foreach (var text in texts)
+ {
+ double dist = new Point2d(block.Position.X, block.Position.Y)
+ .GetDistanceTo(new Point2d(text.Position.X, text.Position.Y));
+
+ if (dist <= maxDist)
+ {
+ // 距离分:距离越小,分数越高。用 1/dist 标准化
+ double distanceScore = 1.0 / (dist + 0.001); // 加一个小数防止除以0
+
+ // 唯一性分:竞争度越小(比率越小),分数越高
+ double uniquenessScore = 1.0 - textCompetition[text.ObjectId]; // 范围 [0, 1]
+
+ // **综合得分 = 70% 距离分 + 30% 唯一性分**
+ double finalScore = 0.7 * distanceScore + 0.3 * uniquenessScore * distanceScore; // 让唯一性也受距离影响
+
+ allPossiblePairs.Add(new Tuple(block.ObjectId, text.ObjectId, finalScore));
+ }
+ }
+ }
+
+ // 3. 按综合得分从高到低排序
+ var sortedPairs = allPossiblePairs.OrderByDescending(p => p.Item3).ToList();
+
+ // 4. 贪婪匹配最高分的
+ var validPairs = new Dictionary();
+ var matchedBlocks = new HashSet();
+ var matchedTexts = new HashSet();
+
+ foreach (var pair in sortedPairs)
+ {
+ ObjectId blockId = pair.Item1;
+ ObjectId textId = pair.Item2;
+
+ if (!matchedBlocks.Contains(blockId) && !matchedTexts.Contains(textId))
+ {
+ validPairs.Add(blockId, textId);
+ matchedBlocks.Add(blockId);
+ matchedTexts.Add(textId);
+ }
+ }
+ return validPairs;
+ }
+ public static void 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))
+ {
+ ed.WriteMessage($"\n图层 '{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 (System.Exception ex)
+ {
+ ed.WriteMessage($"\n清理时发生错误: {ex.Message}");
+ tr.Abort();
+ }
+ }
+ }
+ ///
+ /// Implements a greedy algorithm to find the best one-to-one block/text pairings.
+ ///
+ private Dictionary GetGreedyValidPairs(List blocks, List texts, double maxDist)
+ {
+ // 1. Create a list of all possible pairs within the max distance
+ var allPossiblePairs = new List>();
+ foreach (var block in blocks)
+ {
+ foreach (var text in texts)
+ {
+ double dist = new Point2d(block.Position.X, block.Position.Y)
+ .GetDistanceTo(new Point2d(text.Position.X, text.Position.Y));
+ if (dist <= maxDist)
+ {
+ allPossiblePairs.Add(new Tuple(block.ObjectId, text.ObjectId, dist));
+ }
+ }
+ }
+
+ // 2. Sort all pairs by distance, ascending
+ var sortedPairs = allPossiblePairs.OrderBy(p => p.Item3).ToList();
+
+ // 3. Iterate and match, ensuring one-to-one mapping
+ var validPairs = new Dictionary();
+ var matchedBlocks = new HashSet();
+ var matchedTexts = new HashSet();
+
+ foreach (var pair in sortedPairs)
+ {
+ ObjectId blockId = pair.Item1;
+ ObjectId textId = pair.Item2;
+
+ // If neither the block nor the text has been matched yet, it's a valid pair
+ if (!matchedBlocks.Contains(blockId) && !matchedTexts.Contains(textId))
+ {
+ validPairs.Add(blockId, textId);
+ matchedBlocks.Add(blockId);
+ matchedTexts.Add(textId);
+ }
+ }
+
+ return validPairs;
+ }
+
+
+ #region Other Helper Methods (Unchanged)
+ 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 BlockRefWithPosition(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); } }
+ #endregion
+}
diff --git a/Szmedi.CADkits/TextWithPosition.cs b/Szmedi.CADkits/TextWithPosition.cs
new file mode 100644
index 0000000..9c55969
--- /dev/null
+++ b/Szmedi.CADkits/TextWithPosition.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Linq;
+
+using Autodesk.AutoCAD.DatabaseServices;
+using Autodesk.AutoCAD.Geometry;
+namespace Szmedi.CADkits;
+
+public readonly struct TextWithPosition
+{
+ public ObjectId ObjectId { get; }
+
+ public Point3d Position { get; }
+
+ public string TextContent { get; }
+
+ public TextWithPosition(ObjectId id, Point3d pos, string content)
+ {
+ ObjectId = id;
+ Position = pos;
+ TextContent = content;
+ }
+}
\ No newline at end of file
diff --git a/Szmedi.RvKits.sln b/Szmedi.RvKits.sln
index 4160c48..db70863 100644
--- a/Szmedi.RvKits.sln
+++ b/Szmedi.RvKits.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.1.32328.378
+# Visual Studio Version 18
+VisualStudioVersion = 18.0.11201.2
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Szmedi.RvKits", "Szmedi.RvKits\Szmedi.RvKits.csproj", "{1C5AFB22-E95E-4EF0-B425-8896BA0D0C4B}"
EndProject
@@ -26,6 +26,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RevitAddins", "RevitAddins\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvdLiteSnoop", "MvdLiteSnoop\MvdLiteSnoop.csproj", "{5845A999-9AEC-4787-9EBC-B1D176E37D6C}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Szmedi.CADkits", "Szmedi.CADkits\Szmedi.CADkits.csproj", "{A8343F78-B04D-4C75-BDE3-8133314B5597}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebUITest", "WebUITest\WebUITest.csproj", "{976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BoreholeExtract", "BoreholeExtract\BoreholeExtract.csproj", "{BBB28329-D187-48B6-BA11-C2DFBA96FDD6}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -183,6 +189,60 @@ Global
{5845A999-9AEC-4787-9EBC-B1D176E37D6C}.Release|x64.Build.0 = Release|Any CPU
{5845A999-9AEC-4787-9EBC-B1D176E37D6C}.Release|x86.ActiveCfg = Release|Any CPU
{5845A999-9AEC-4787-9EBC-B1D176E37D6C}.Release|x86.Build.0 = Release|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.Debug|x64.Build.0 = Debug|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.Debug|x86.Build.0 = Debug|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.DefaultBuild|Any CPU.ActiveCfg = Release|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.DefaultBuild|Any CPU.Build.0 = Release|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.DefaultBuild|x64.ActiveCfg = Release|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.DefaultBuild|x64.Build.0 = Release|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.DefaultBuild|x86.ActiveCfg = Release|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.DefaultBuild|x86.Build.0 = Release|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.Release|x64.ActiveCfg = Release|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.Release|x64.Build.0 = Release|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.Release|x86.ActiveCfg = Release|Any CPU
+ {A8343F78-B04D-4C75-BDE3-8133314B5597}.Release|x86.Build.0 = Release|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.Debug|x64.Build.0 = Debug|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.Debug|x86.Build.0 = Debug|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.DefaultBuild|Any CPU.ActiveCfg = Release|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.DefaultBuild|Any CPU.Build.0 = Release|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.DefaultBuild|x64.ActiveCfg = Release|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.DefaultBuild|x64.Build.0 = Release|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.DefaultBuild|x86.ActiveCfg = Release|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.DefaultBuild|x86.Build.0 = Release|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.Release|x64.ActiveCfg = Release|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.Release|x64.Build.0 = Release|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.Release|x86.ActiveCfg = Release|Any CPU
+ {976F4EE6-7EFC-AFBF-BC2D-C15F273A38C1}.Release|x86.Build.0 = Release|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.Debug|x64.Build.0 = Debug|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.Debug|x86.Build.0 = Debug|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.DefaultBuild|Any CPU.ActiveCfg = Release|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.DefaultBuild|Any CPU.Build.0 = Release|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.DefaultBuild|x64.ActiveCfg = Release|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.DefaultBuild|x64.Build.0 = Release|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.DefaultBuild|x86.ActiveCfg = Release|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.DefaultBuild|x86.Build.0 = Release|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.Release|x64.ActiveCfg = Release|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.Release|x64.Build.0 = Release|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.Release|x86.ActiveCfg = Release|Any CPU
+ {BBB28329-D187-48B6-BA11-C2DFBA96FDD6}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Szmedi.RvKits/Civil/CutGeologyByLoopCmd.cs b/Szmedi.RvKits/Civil/CutGeologyByLoopCmd.cs
index 6d844aa..e8a1081 100644
--- a/Szmedi.RvKits/Civil/CutGeologyByLoopCmd.cs
+++ b/Szmedi.RvKits/Civil/CutGeologyByLoopCmd.cs
@@ -14,7 +14,6 @@ namespace Szmedi.RvKits.Civil
/// Revit执行命令
///
[Transaction(TransactionMode.Manual)]
- [Regeneration(RegenerationOption.Manual)]
public class CutGeologyByLoopCmd : ExternalCommand
{
public override void Execute()
@@ -22,7 +21,7 @@ namespace Szmedi.RvKits.Civil
try
{
var curveElements = UiDocument.Selection
- .PickElementsByRectangle(new GenericFilter(), "请框选模型线或符号线")
+ .PickElementsByRectangle(new GenericFilter(), "请框选闭合的模型线或符号线")
.Cast();
var curvesSelected = curveElements.Select(x => x.GeometryCurve).ToList();
if (curvesSelected.Count < 3)
diff --git a/Szmedi.RvKits/Converters/NullOrEmptyToBooleanConverter.cs b/Szmedi.RvKits/Converters/NullOrEmptyToBooleanConverter.cs
new file mode 100644
index 0000000..5fffc8f
--- /dev/null
+++ b/Szmedi.RvKits/Converters/NullOrEmptyToBooleanConverter.cs
@@ -0,0 +1,17 @@
+using System.Globalization;
+using System.Windows.Data;
+
+namespace Szmedi.RvKits.Converters
+{
+ public class NullOrEmptyToBooleanConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var str = value as string;
+ return !string.IsNullOrEmpty(str); // 非空返回 true
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ => throw new NotImplementedException();
+ }
+}
diff --git a/Szmedi.RvKits/Display/EleManageEventHandler.cs b/Szmedi.RvKits/Display/EleManageEventHandler.cs
index 36437c0..e8afb84 100644
--- a/Szmedi.RvKits/Display/EleManageEventHandler.cs
+++ b/Szmedi.RvKits/Display/EleManageEventHandler.cs
@@ -1,4 +1,6 @@
-using Autodesk.Revit.DB;
+using System.Linq;
+
+using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
@@ -193,29 +195,72 @@ namespace Szmedi.RvKits.DisplayTools
{
try
{
+ //if (doc.IsFamilyDocument)
+ //{
+ // var col = new FilteredElementCollector(doc).WhereElementIsNotElementType();
+ // var views = new FilteredElementCollector(doc).OfClass(typeof(View)).WhereElementIsNotElementType().Cast().ToList();
+ // Transaction transaction = new Transaction(doc, "显示所有隐藏元素");
+ // transaction.Start();
+ // foreach (var v in views)
+ // {
+ // foreach (Element item in col)
+ // {
+ // if (item.IsHidden(v))
+ // {
+ // v.UnhideElements([item.Id]);
+ // }
+ // }
+ // }
+ // transaction.Commit();
+ //}
+ //else
+ //{
+ // ElementClassFilter hostFilter = new(typeof(HostObject));
+ // //逻辑过滤器
+ // LogicalOrFilter orFilter = new(instanceFilter, hostFilter);
+ // //收集器应用过滤器(未应用过滤器,收集器会报错)
+ // collection.WherePasses(orFilter);
+ //}
+ //foreach (Element item in collection)
+ //{
+ // if (item.IsHidden(doc.ActiveView))
+ // {
+ // doc.ActiveView.UnhideElements([item.Id]);
+ // }
+ //}
if (doc.IsFamilyDocument)
{
- ElementClassFilter formFilter = new(typeof(GenericForm));
- ElementClassFilter combineFilter = new(typeof(GeomCombination));
-
- LogicalOrFilter logicalOrFilter = new([instanceFilter, formFilter, combineFilter]);
- collection.WherePasses(logicalOrFilter);
+ var col = new FilteredElementCollector(doc).WhereElementIsNotElementType();
+ var views = new FilteredElementCollector(doc).OfClass(typeof(View)).WhereElementIsNotElementType().Cast().ToList();
+ Transaction transaction = new Transaction(doc, $"显示隐藏");
+ transaction.Start();
+ foreach (var v in views)
+ {
+ foreach (Element item in col)
+ {
+ if (item.IsHidden(v))
+ {
+ v.UnhideElements([item.Id]);
+ }
+ }
+ }
+ transaction.Commit();
}
else
{
- ElementClassFilter hostFilter = new(typeof(HostObject));
- //逻辑过滤器
- LogicalOrFilter orFilter = new(instanceFilter, hostFilter);
- //收集器应用过滤器(未应用过滤器,收集器会报错)
- collection.WherePasses(orFilter);
- }
- foreach (Element item in collection)
- {
- if (item.IsHidden(doc.ActiveView))
+ var col = new FilteredElementCollector(doc).WhereElementIsNotElementType();
+ Transaction transaction = new Transaction(doc, $"显示隐藏");
+ transaction.Start();
+ foreach (Element item in col)
{
- doc.ActiveView.UnhideElements([item.Id]);
+ if (item.IsHidden(doc.ActiveView))
+ {
+ doc.ActiveView.UnhideElements([item.Id]);
+ }
}
+ transaction.Commit();
}
+ return;
}
catch (System.Exception)
{
diff --git a/Szmedi.RvKits/Display/ElementsControlCmd.cs b/Szmedi.RvKits/Display/ElementsControlCmd.cs
index 48b3906..4014aa4 100644
--- a/Szmedi.RvKits/Display/ElementsControlCmd.cs
+++ b/Szmedi.RvKits/Display/ElementsControlCmd.cs
@@ -6,14 +6,10 @@ using Autodesk.Revit.UI;
using Nice3point.Revit.Toolkit.External;
-using System;
-
namespace Szmedi.RvKits.DisplayTools
{
[Transaction(TransactionMode.Manual)]
-
- [Journaling(JournalingMode.NoCommandData)]
//打开ElementsControlDock窗口
public class ElementsControlCmd : ExternalCommand
{
diff --git a/Szmedi.RvKits/FamilyTools/ComponentInfoCmd.cs b/Szmedi.RvKits/FamilyTools/ComponentInfoCmd.cs
index 603791b..c6aec31 100644
--- a/Szmedi.RvKits/FamilyTools/ComponentInfoCmd.cs
+++ b/Szmedi.RvKits/FamilyTools/ComponentInfoCmd.cs
@@ -1,4 +1,8 @@
+using System.Data;
+using System.Linq;
+using System.Windows;
+
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
@@ -9,14 +13,6 @@ using Newtonsoft.Json;
using Nice3point.Revit.Toolkit.External;
-using System;
-
-using System.Data;
-
-using System.Linq;
-
-using System.Windows;
-
using Szmedi.RvKits.ExportFamilyInfo;
namespace Szmedi.RvKits.FamilyTools
@@ -499,126 +495,5 @@ namespace Szmedi.RvKits.FamilyTools
ExportOption(saveDirChoosed, filesFullPathChoosed, famdoc, views, forward);
//ExportOption(ps, saveDirChoosed, filesFullPathChoosed, famdoc, views);
}
- //private void TableToExcel(string fullName, DataTable dt, string DirChoosed)
- //{
- // IWorkbook workbook;
- // Configuration config = new Configuration();
- // //��ȡ�ļ�����������·������չ����
- // string safefilename = Path.GetFileNameWithoutExtension(fullName);
- // //��������չ��������·��
- // string ExcelWithoutEx = DirChoosed + "\\" + safefilename + "\\" + safefilename + "-" + "������";
- // //���ļ���������Ŀ¼���ļ�����Ŀ¼��
- // string fileDir = DirChoosed + "\\" + safefilename + "\\";
- // //����Ŀ¼
- // DirectoryInfo fileDirInfo = new DirectoryInfo(fileDir);
- // if (!fileDirInfo.Exists)
- // {
- // fileDirInfo.Create();
- // }
- // string filename = null;
- // string ver = config.GetConfig(Selection.ExcelVersion);
-
- // //if (ver == "Excel ������(*.xlsx)")
- // //{
-
- // // workbook = new XSSFWorkbook();
- // // filename = ExcelWithoutEx + ".xlsx";
-
- // //}
- // if (ver == "Excel 97-2003 ������(*.xls)")
- // {
- // workbook = new HSSFWorkbook();
- // filename = ExcelWithoutEx + ".xls";
- // }
- // else
- // {
- // workbook = null;
- // }
- // //using (FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
- // //{
- // // //�Զ����ݶ�ȡ���ı��汾����2007����2003�湤��book
- // // workbook = WorkbookFactory.Create(fileName);
- // // fileStream.Close();
- // //}
- // if (workbook == null)
- // {
- // return;
- // }
- // ISheet sheet = string.IsNullOrEmpty(dt.TableName) ? workbook.CreateSheet("������") : workbook.CreateSheet(dt.TableName);
-
- // ICellStyle style = workbook.CreateCellStyle();
- // //style.BorderRight = NPOI.SS.UserModel.BorderStyle.Thin;
- // //style.BorderLeft = NPOI.SS.UserModel.BorderStyle.Thin;
- // //style.BorderTop = NPOI.SS.UserModel.BorderStyle.Thin;
- // //style.BorderBottom = NPOI.SS.UserModel.BorderStyle.Thin;
- // IFont font = workbook.CreateFont();
- // font.FontHeightInPoints = 14;
- // //font.Boldweight = (short)NPOI.SS.UserModel.FontBoldWeight.Bold;
- // font.FontName = "����";
- // style.SetFont(font);//HEAD ��ʽ
-
- // //��ͷ
- // IRow row = sheet.CreateRow(0);
- // for (int i = 0; i < dt.Columns.Count; i++)
- // {
- // //ICellStyle ics = workbook.CreateCellStyle();
- // //ics.FillPattern = NPOI.SS.UserModel.FillPattern.SolidForeground;
- // //cell.CellStyle = ics;
- // ICell cell = row.CreateCell(i);
-
- // cell.SetCellValue(dt.Columns[i].ColumnName);
- // }
-
- // //����
- // for (int i = 0; i < dt.Rows.Count; i++)
- // {
- // IRow row1 = sheet.CreateRow(i + 1);
- // for (int j = 0; j < dt.Columns.Count; j++)
- // {
-
- // ICell cell = row1.CreateCell(j);
- // if (dt.Rows[i][0].ToString() == "")
- // {
- // cell.CellStyle = style;
- // //IFont f = workbook.CreateFont();
- // //f.FontHeightInPoints = 14;
- // //cell.CellStyle.SetFont(f);
- // }
- // cell.SetCellValue(dt.Rows[i][j].ToString());
- // }
- // }
-
- // //תΪ�ֽ�����
- // MemoryStream stream = new MemoryStream();
- // workbook.Write(stream);
- // var buf = stream.ToArray();
- // FileInfo file = new FileInfo(filename);
- // //����ΪExcel�ļ�
- // using (FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write))
- // {
-
- // fs.Write(buf, 0, buf.Length);
- // fs.Flush();
- // }
- //}
-
- //foreach (Autodesk.Revit.DB.View view in views)
- //{
- // //if (view.IsTemplate) continue;
- // if (view.ViewType == ViewType.ThreeD)
- // {
- // Transaction trans = new Transaction(famdoc, "Export Image");
- // trans.Start();
- // if (concol!=null)
- // {
- // famdoc.Delete(concol.ToElementIds());
- // }
- // var graphicDisplayOptions = view.get_Parameter(BuiltInParameter.MODEL_GRAPHICS_STYLE);
- // graphicDisplayOptions.Set(6);
- // trans.Commit();
- // ImageExportList.Add(view.Id);
- // }
- //}
- //string directroy = Path.GetDirectoryName(path) +"\\"+;
}
}
diff --git a/Szmedi.RvKits/FamilyTools/PreviewFam.xaml b/Szmedi.RvKits/FamilyTools/PreviewFam.xaml
index 9efadc2..de44e59 100644
--- a/Szmedi.RvKits/FamilyTools/PreviewFam.xaml
+++ b/Szmedi.RvKits/FamilyTools/PreviewFam.xaml
@@ -64,7 +64,5 @@
Click="LoadFam_Click"
Content="载入" />
-
-
\ No newline at end of file
diff --git a/Szmedi.RvKits/FamilyTools/RenameFamilyViewModel.cs b/Szmedi.RvKits/FamilyTools/RenameFamilyViewModel.cs
index f7e7c47..689f2e6 100644
--- a/Szmedi.RvKits/FamilyTools/RenameFamilyViewModel.cs
+++ b/Szmedi.RvKits/FamilyTools/RenameFamilyViewModel.cs
@@ -39,7 +39,7 @@ public partial class RenameFamilyViewModel : ObservableObject
private bool isRunning;
[ObservableProperty]
- private Dictionary> collection;
+ public partial Dictionary> Collection { get; set; }
[ObservableProperty]
private int categoryCount;
@@ -86,7 +86,7 @@ public partial class RenameFamilyViewModel : ObservableObject
{
get
{
- var selected = RenameItems.Select(item => item.IsSelected).Distinct().ToList();
+ var selected = RenameItems?.Select(item => item.IsSelected).Distinct().ToList();
if (selected == null)
{
return false;
diff --git a/Szmedi.RvKits/FamilyTools/RenameFamilyWin.xaml b/Szmedi.RvKits/FamilyTools/RenameFamilyWin.xaml
index a6d74e0..e26cc9d 100644
--- a/Szmedi.RvKits/FamilyTools/RenameFamilyWin.xaml
+++ b/Szmedi.RvKits/FamilyTools/RenameFamilyWin.xaml
@@ -76,7 +76,10 @@
-
+
@@ -110,7 +113,10 @@
-
+
@@ -122,9 +128,18 @@
-
-
-
+
+
+
-
+
@@ -197,7 +212,10 @@
-->
-
+
@@ -210,7 +228,10 @@
-
+
@@ -219,7 +240,10 @@
-
+
diff --git a/Szmedi.RvKits/InfoManager/AFCA/AFCAMetroCmd.cs b/Szmedi.RvKits/InfoManager/AFCA/AFCAMetroCmd.cs
index 6a72a04..85d8491 100644
--- a/Szmedi.RvKits/InfoManager/AFCA/AFCAMetroCmd.cs
+++ b/Szmedi.RvKits/InfoManager/AFCA/AFCAMetroCmd.cs
@@ -17,7 +17,6 @@ namespace Szmedi.RvKits.InfoManager.AFCA
/// 报批报建
///
[Transaction(TransactionMode.Manual)]
- [Regeneration(RegenerationOption.Manual)]
public class AFCAMetroCmd : ExternalCommand
{
private static readonly string DbString = $"Data Source={GlobalVariables.DirAssembly}\\SZBIM.db";
diff --git a/Szmedi.RvKits/InfoManager/EAMTools/EAMCodeCheckCmd.cs b/Szmedi.RvKits/InfoManager/EAMTools/EAMCodeCheckCmd.cs
index 9ad415f..7b406e7 100644
--- a/Szmedi.RvKits/InfoManager/EAMTools/EAMCodeCheckCmd.cs
+++ b/Szmedi.RvKits/InfoManager/EAMTools/EAMCodeCheckCmd.cs
@@ -13,7 +13,6 @@ namespace Szmedi.RvKits.InfoManager.EAMTools
/// Revit执行命令
///
[Transaction(TransactionMode.Manual)]
- [Regeneration(RegenerationOption.Manual)]
public class EAMCodeCheckCmd : ExternalCommand
{
public override void Execute()
diff --git a/Szmedi.RvKits/InfoManager/EAMTools/EAMCodeCheckViewModel.cs b/Szmedi.RvKits/InfoManager/EAMTools/EAMCodeCheckViewModel.cs
index bdc84a5..fb6bf3d 100644
--- a/Szmedi.RvKits/InfoManager/EAMTools/EAMCodeCheckViewModel.cs
+++ b/Szmedi.RvKits/InfoManager/EAMTools/EAMCodeCheckViewModel.cs
@@ -1,6 +1,4 @@
-
-using System;
-using System.Linq;
+using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Windows;
@@ -14,23 +12,16 @@ using Nice3point.Revit.Toolkit.External.Handlers;
using OfficeOpenXml;
-using Szmedi.RvKits.InfoManager.EAMTools;
-
-namespace Szmedi.RvKits.InfoManager
+namespace Szmedi.RvKits.InfoManager.EAMTools
{
public partial class EAMCodeCheckViewModel : ObservableObject
{
private readonly ActionEventHandler handler;
- [ObservableProperty]
- private List currentFacilities;
- [ObservableProperty]
- private List instances;
- [ObservableProperty]
- private string selectedStation;
- [ObservableProperty]
- private List facilitySystems;
- [ObservableProperty]
- private string selectedSystem;
+ [ObservableProperty] private List currentFacilities;
+ [ObservableProperty] private List instances;
+ [ObservableProperty] private string selectedStation;
+ [ObservableProperty] private List facilitySystems;
+ [ObservableProperty] private string selectedSystem;
public EAMCodeCheckViewModel(Document doc)
{
@@ -44,12 +35,10 @@ namespace Szmedi.RvKits.InfoManager
try
{
var list = doc.OfClass()
- .Where(
- e => e.Category.AllowsBoundParameters &&
- e.Category.CategoryType == CategoryType.Model &&
- !Enum.GetName(typeof(BuiltInCategory), e.Category.Id.IntegerValue)!.Contains("Fitting") &&
- e.Category.CategoryType == CategoryType.Model &&
- e.Category.Id.IntegerValue != -2008013) //风道末端
+ .Where(e => e.Category.AllowsBoundParameters &&
+ !Enum.GetName(typeof(BuiltInCategory), e.Category.Id.IntegerValue)!.Contains("Fitting") &&
+ e.Category.CategoryType == CategoryType.Model &&
+ e.Category.Id.IntegerValue != -2008013) //风道末端
.Cast()
.OrderBy(ins => ins.Name)
.Select(ins => new InstanceFacility(ins));
@@ -57,7 +46,7 @@ namespace Szmedi.RvKits.InfoManager
Facilities = package.ToList(1, configuration => configuration.SkipCastingErrors());
var group = Facilities.GroupBy(fa => fa.StationName).OrderBy(g => g.Key);
Stations = [.. group.Select(g => g.Key)];
- Regex regex = new Regex(@"-(\D+站|所|段|场)-");
+ var regex = new Regex(@"-(\D+站|所|段|场)-");
var result = regex.Matches(doc.PathName).Cast().Select(m => m.Groups[1].Value).FirstOrDefault();
if (result != null && Stations.Contains(result))
{
@@ -66,13 +55,12 @@ namespace Szmedi.RvKits.InfoManager
}
catch (EPPlus.Core.Extensions.Exceptions.ExcelValidationException)
{
- System.Windows.MessageBox.Show("列名不存在或不匹配,请检查表头是否存在换行。");
+ MessageBox.Show("列名不存在或不匹配,请检查表头是否存在换行。");
}
finally
{
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomainOnAssemblyResolve;
}
-
}
[RelayCommand]
@@ -82,6 +70,7 @@ namespace Szmedi.RvKits.InfoManager
{
return;
}
+
foreach (var EAMData in CurrentFacilities)
{
//var b = Instances.Any(ins => ins.Number.Contains(EAMData.EAMCode));
@@ -95,9 +84,11 @@ namespace Szmedi.RvKits.InfoManager
}
}
}
- CurrentFacilities = [.. CurrentFacilities.OrderBy(fa => fa.IsMapped)];
- Instances = [.. Instances.OrderBy(ins => ins.IsMapped)];
+
+ CurrentFacilities = [.. Enumerable.OrderBy(CurrentFacilities, fa => fa.IsMapped)];
+ Instances = [.. Enumerable.OrderBy(Instances, ins => ins.IsMapped)];
}
+
[RelayCommand]
private void ShowFacility(object obj)
{
@@ -109,7 +100,7 @@ namespace Szmedi.RvKits.InfoManager
handler.Raise(uiapp =>
{
var uidoc = uiapp.ActiveUIDocument;
- Document doc = uidoc.Document;
+ var doc = uidoc.Document;
if (!model.Instance.IsValidObject)
{
@@ -140,15 +131,19 @@ namespace Szmedi.RvKits.InfoManager
uidoc.Selection.SetElementIds([model.Instance.Id]);
});
}
+
private static bool IsVisible(ElementId viewId, Element elem)
{
- if (FilteredElementCollector.IsViewValidForElementIteration(elem.Document, viewId)) // 某类视图不能使用 FilteredElementCollector
+ if (FilteredElementCollector.IsViewValidForElementIteration(elem.Document,
+ viewId)) // 某类视图不能使用 FilteredElementCollector
{
var fec = new FilteredElementCollector(elem.Document, viewId).ToElementIds();
return fec.Any(id => id == elem.Id);
}
+
return false;
}
+
private Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args)
{
if (!args.Name.Contains("ComponentModel.Annotations"))
@@ -156,7 +151,7 @@ namespace Szmedi.RvKits.InfoManager
return null;
}
- string assemblyFile = Path.Combine(GlobalVariables.DirAssembly, "System.ComponentModel.Annotations.dll");
+ var assemblyFile = Path.Combine(GlobalVariables.DirAssembly, "System.ComponentModel.Annotations.dll");
return File.Exists(assemblyFile) ? Assembly.LoadFrom(assemblyFile) : null;
}
@@ -165,12 +160,13 @@ namespace Szmedi.RvKits.InfoManager
if (!string.IsNullOrEmpty(newValue))
{
CurrentFacilities = Facilities.Where(fa => fa.StationName == newValue).ToList();
- var group = CurrentFacilities.GroupBy(fa => fa.System);
+ var group = Enumerable.GroupBy(CurrentFacilities, fa => fa.System);
FacilitySystems = [.. group.Select(g => g.Key)];
FacilitySystems.Insert(0, "<请选择系统>");
}
}
+
partial void OnSelectedSystemChanged(string oldValue, string newValue)
{
if (newValue == "<请选择系统>")
@@ -178,15 +174,15 @@ namespace Szmedi.RvKits.InfoManager
CurrentFacilities = Facilities.Where(fa => fa.System == newValue).ToList();
return;
}
+
if (!string.IsNullOrEmpty(newValue))
{
- CurrentFacilities = CurrentFacilities.Where(fa => fa.System == newValue).ToList();
+ CurrentFacilities = Enumerable.Where(CurrentFacilities, fa => fa.System == newValue).ToList();
}
}
public List Facilities { get; }
public List Stations { get; }
-
}
public class EAMData : ObservableObject
@@ -195,6 +191,7 @@ namespace Szmedi.RvKits.InfoManager
[ExcelTableColumn("是否匹配", true)]
public bool? IsMapped { get => isMapped; set => SetProperty(ref isMapped, value); }
+
///
/// 线路名称
///
diff --git a/Szmedi.RvKits/InfoManager/EAMTools/EAMCodingViewModel.cs b/Szmedi.RvKits/InfoManager/EAMTools/EAMCodingViewModel.cs
index 0c6ca18..af34722 100644
--- a/Szmedi.RvKits/InfoManager/EAMTools/EAMCodingViewModel.cs
+++ b/Szmedi.RvKits/InfoManager/EAMTools/EAMCodingViewModel.cs
@@ -397,7 +397,6 @@ public partial class EAMCodingViewModel : ObservableObject
.OfClass(typeof(FamilyInstance))
.Where(i =>
i.Category.AllowsBoundParameters
- && i.Category.CategoryType == CategoryType.Model
&& !Enum.GetName(typeof(BuiltInCategory), i.Category.Id.IntegerValue)!.Contains("Fitting")
&& i.Category.CategoryType == CategoryType.Model
//风道末端
diff --git a/Szmedi.RvKits/InfoManager/EAMTools/EAMCodingWin.xaml b/Szmedi.RvKits/InfoManager/EAMTools/EAMCodingWin.xaml
index 68b9fae..fcbe898 100644
--- a/Szmedi.RvKits/InfoManager/EAMTools/EAMCodingWin.xaml
+++ b/Szmedi.RvKits/InfoManager/EAMTools/EAMCodingWin.xaml
@@ -12,7 +12,6 @@
Width="1280"
Height="700"
d:DataContext="{d:DesignInstance Type=local1:EAMCodingViewModel}"
-
mc:Ignorable="d">
@@ -42,7 +41,10 @@
-
+
@@ -75,7 +77,10 @@
Width="2"
HorizontalAlignment="Stretch" />
-
+
-
+
@@ -102,7 +110,10 @@
-
+
@@ -138,21 +149,45 @@
Foreground="{DynamicResource MaterialDesign.Brush.Foreground}"
IsReadOnly="True"
Text="{Binding ExcelPath, Mode=OneWay}" />
-
-
+
-
+
-
-
-
-
+
+
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Szmedi.RvKits/MEPTools/FacilityInfoProcessView.xaml.cs b/Szmedi.RvKits/MEPTools/FacilityInfoProcessView.xaml.cs
new file mode 100644
index 0000000..e93f549
--- /dev/null
+++ b/Szmedi.RvKits/MEPTools/FacilityInfoProcessView.xaml.cs
@@ -0,0 +1,13 @@
+namespace Szmedi.RvKits.MEPTools
+{
+ ///
+ /// InfoMapperView.xaml 的交互逻辑
+ ///
+ public partial class FacilityInfoProcessView
+ {
+ public FacilityInfoProcessView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Szmedi.RvKits/MEPTools/FlipWorkplaneCmd.cs b/Szmedi.RvKits/MEPTools/FlipWorkplaneCmd.cs
index 303a5e7..af2a95b 100644
--- a/Szmedi.RvKits/MEPTools/FlipWorkplaneCmd.cs
+++ b/Szmedi.RvKits/MEPTools/FlipWorkplaneCmd.cs
@@ -11,7 +11,6 @@ using System.Linq;
namespace Szmedi.RvKits.MEPTools
{
[Transaction(TransactionMode.Manual)]
- [Regeneration(RegenerationOption.Manual)]
public class FlipWorkplaneCmd : ExternalCommand
{
public override void Execute()
diff --git a/Szmedi.RvKits/MEPTools/MEPCurveSignCmd.cs b/Szmedi.RvKits/MEPTools/MEPCurveSignCmd.cs
index cc5b73a..bf23ee9 100644
--- a/Szmedi.RvKits/MEPTools/MEPCurveSignCmd.cs
+++ b/Szmedi.RvKits/MEPTools/MEPCurveSignCmd.cs
@@ -14,7 +14,6 @@ namespace Szmedi.RvKits.MEPTools
/// Revit执行命令
///
[Transaction(TransactionMode.Manual)]
- [Regeneration(RegenerationOption.Manual)]
public class MEPCurveSignCmd : ExternalCommand
{
public override void Execute()
diff --git a/Szmedi.RvKits/MEPTools/RotateInstanceCmd.cs b/Szmedi.RvKits/MEPTools/RotateInstanceCmd.cs
index a6d71f9..d9568a7 100644
--- a/Szmedi.RvKits/MEPTools/RotateInstanceCmd.cs
+++ b/Szmedi.RvKits/MEPTools/RotateInstanceCmd.cs
@@ -10,7 +10,6 @@ using System.Linq;
namespace Szmedi.RvKits.MEPTools
{
[Transaction(TransactionMode.Manual)]
- [Regeneration(RegenerationOption.Manual)]
class RotateInstanceCmd : ExternalCommand
{
public override void Execute()
diff --git a/Szmedi.RvKits/ModelManager/ComparatorProjectViewModel.cs b/Szmedi.RvKits/ModelManager/ComparatorProjectViewModel.cs
new file mode 100644
index 0000000..e86d2f8
--- /dev/null
+++ b/Szmedi.RvKits/ModelManager/ComparatorProjectViewModel.cs
@@ -0,0 +1,148 @@
+using System.Collections;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows;
+
+using Autodesk.Revit.DB;
+
+using EPPlus.Core.Extensions;
+using EPPlus.Core.Extensions.Attributes;
+
+using Microsoft.Win32;
+
+using OfficeOpenXml;
+
+using Szmedi.RvKits.MEPTools;
+
+namespace Szmedi.RvKits.ModelManager
+{
+ public partial class ProjectComparatorViewModel : ObservableObject
+ {
+ public ProjectComparatorViewModel()
+ {
+ OldDocuments = [.. GlobalVariables.UIApplication.Application.Documents.OfType().Where(d => !d.IsFamilyDocument)];
+ DesignParameters = new ObservableCollection
+ {
+ new ("长度"),
+ new ("宽度"),
+ new ("高度"),
+ new ("抗渗等级"),
+ new ("结构材质"),
+ new ("混凝土标号"),
+ new ("基础编号"),
+ };
+ QuantityParameters = new ObservableCollection
+ {
+ new ("体积"),
+ new ("面积"),
+ };
+ }
+ [RelayCommand]
+ private void AddCustomDesignParameter()
+ {
+ if (!string.IsNullOrWhiteSpace(CustomDesignParamName))
+ {
+ // 防止重复添加
+ if (!DesignParameters.Any(p => p.Name.Equals(CustomDesignParamName)))
+ {
+ DesignParameters.Add(new ParameterItem(CustomDesignParamName));
+ }
+ CustomDesignParamName = string.Empty; // 清空输入框
+ }
+ }
+ [RelayCommand]
+ private void RemoveCustomDesignParameter(IList list)
+ {
+ var designParams = list.Cast
-
-
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Szmedi.RvKits/ModelManager/ProjectComparatorView.xaml.cs b/Szmedi.RvKits/ModelManager/ProjectComparatorView.xaml.cs
new file mode 100644
index 0000000..053a17a
--- /dev/null
+++ b/Szmedi.RvKits/ModelManager/ProjectComparatorView.xaml.cs
@@ -0,0 +1,13 @@
+namespace Szmedi.RvKits.ModelManager
+{
+ ///
+ /// ComparatorProjectView.xaml 的交互逻辑
+ ///
+ public partial class ProjectComparatorView
+ {
+ public ProjectComparatorView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Szmedi.RvKits/ModelManager/ProjectComparerTest.cs b/Szmedi.RvKits/ModelManager/ProjectComparerTest.cs
new file mode 100644
index 0000000..6ffe9c0
--- /dev/null
+++ b/Szmedi.RvKits/ModelManager/ProjectComparerTest.cs
@@ -0,0 +1,507 @@
+using System.Linq;
+using System.Text;
+
+using Autodesk.Revit.DB;
+
+using EPPlus.Core.Extensions.Attributes;
+
+namespace Szmedi.RvKits.ModelManager
+{
+ // ==========================================
+ // 1. 数据模型定义 (Models)
+ // ==========================================
+
+ public enum DiffType { Added, Deleted, Modified }
+
+ public class LocationDetail
+ {
+ public string OldValue { get; set; } // 修改前的坐标 (新增时为null)
+ public string NewValue { get; set; } // 修改后的坐标 / 新增时的当前坐标
+ }
+
+ public class ParamDetail
+ {
+ public string ParamName { get; set; }
+ public string OldValue { get; set; }
+ public string NewValue { get; set; }
+
+ public ParamDetail(string name, string oldVal, string newVal)
+ {
+ ParamName = name;
+ OldValue = oldVal;
+ NewValue = newVal;
+ }
+ }
+ ///
+ /// 构件状态快照 (对应 Excel 表格中的半边数据)
+ ///
+ public class ComponentState
+ {
+ public string ElementId { get; set; } = "无";
+ public string TypeName { get; set; } = "无";
+ public string DesignProps { get; set; } = "无"; // 设计属性 (多行文本)
+ public string QuantityProps { get; set; } = "无"; // 出量属性 (体积/面积)
+ public string Coordinates { get; set; } = "无"; // 坐标 (X/Y/Z)
+
+ public bool IsEmpty => ElementId == "无";
+ }
+
+ ///
+ /// 报表行数据
+ ///
+ public class ReportRow
+ {
+ public int Index { get; set; }
+ public string ChangeType { get; set; } // 新增/删除/修改
+ public ComponentState After { get; set; } = new ComponentState(); // 变更后
+ public ComponentState Before { get; set; } = new ComponentState(); // 变更前
+ }
+ public class ElementDiffInfo
+ {
+ [ExcelTableColumn("图元ID")]
+ public ElementId ElementId { get; set; }
+ public string UniqueId { get; set; }
+ public string CategoryName { get; set; }
+ [ExcelTableColumn("类型")]
+ public string Name { get; set; } // 族名称或类型名称
+ [ExcelTableColumn("修改类型")]
+ public DiffType Status { get; set; }
+
+ // 位置变更详情 (新增或修改)
+ [ExcelTableColumn("坐标")]
+ public LocationDetail LocationChange { get; set; }
+
+ [ExcelTableColumn("设计属性")]
+ // 参数变更详情列表
+ public List ParamChanges { get; set; } = new List();
+ [ExcelTableColumn("出量属性")]
+ public string CalculatedParameter { get; set; }
+ public bool HasChanges => LocationChange != null || ParamChanges.Count > 0;
+ }
+
+ // ==========================================
+ // 2. 核心对比逻辑 (ProjectComparer)
+ // ==========================================
+
+ public class ProjectComparerTest
+ {
+ private List _targetParams;
+ private const double Tolerance = 0.0001; // 几何容差 (英尺)
+
+ public ProjectComparerTest(List targetParams)
+ {
+ _targetParams = targetParams;
+ }
+
+ public List CompareDocuments(Document oldDoc, Document newDoc)
+ {
+ var results = new List();
+
+ // 1. 获取构件字典,使用 UniqueId 进行匹配
+ var oldMap = GetElementMap(oldDoc);
+ var newMap = GetElementMap(newDoc);
+
+ // 2. 遍历新文档 -> 检查 [新增] 和 [修改]
+ foreach (var kvp in newMap)
+ {
+ string uid = kvp.Key;
+ Element newElem = kvp.Value;
+
+ if (!oldMap.ContainsKey(uid))
+ {
+ // ================= [新增] =================
+ var diffInfo = new ElementDiffInfo
+ {
+ ElementId = newElem.Id,
+ UniqueId = uid,
+ CategoryName = newElem.Category?.Name,
+ Name = newElem.Name,
+ Status = DiffType.Added
+ };
+
+ // 获取坐标:优先定位点,其次包围盒底部中心
+ string locStr = GetSmartCoordinate(newElem);
+ if (!string.IsNullOrEmpty(locStr))
+ {
+ // 新增时,将坐标存入 NewValue
+ diffInfo.LocationChange = new LocationDetail
+ {
+ OldValue = null,
+ NewValue = locStr
+ };
+ }
+
+ results.Add(diffInfo);
+ }
+ else
+ {
+ // ================= [修改] =================
+ Element oldElem = oldMap[uid];
+
+ // A. 检查位置 (仅检查 LocationPoint 类型的变化)
+ LocationDetail locDiff = CheckPointLocationChange(oldElem, newElem);
+
+ // B. 检查参数 (包含 Name 和 用户自定义参数)
+ List paramDiffs = CheckParamChanges(oldElem, newElem);
+
+ // 只有当 位置变了 或 参数变了 时才记录
+ if (locDiff != null || paramDiffs.Count > 0)
+ {
+ results.Add(new ElementDiffInfo
+ {
+ ElementId = newElem.Id,
+ UniqueId = uid,
+ CategoryName = newElem.Category?.Name,
+ Name = newElem.Name,
+ Status = DiffType.Modified,
+ LocationChange = locDiff, // 包含 OldValue 和 NewValue
+ ParamChanges = paramDiffs
+ });
+ }
+ }
+ }
+
+ // 3. 遍历旧文档 -> 检查 [删除]
+ foreach (var kvp in oldMap)
+ {
+ if (!newMap.ContainsKey(kvp.Key))
+ {
+ // ================= [删除] =================
+ results.Add(new ElementDiffInfo
+ {
+ ElementId = kvp.Value.Id,
+ UniqueId = kvp.Key,
+ CategoryName = kvp.Value.Category?.Name,
+ Name = kvp.Value.Name,
+ Status = DiffType.Deleted
+ });
+ }
+ }
+
+ return results;
+ }
+
+ // --------------------------------------------------------
+ // 辅助方法区域
+ // --------------------------------------------------------
+
+ ///
+ /// 智能获取构件坐标 (用于新增构件的定位描述)
+ ///
+ private string GetSmartCoordinate(Element elem)
+ {
+ XYZ p = null;
+
+ // 策略1: 如果是基于点的构件 (柱、门、窗、家具等)
+ if (elem.Location is LocationPoint lp)
+ {
+ p = lp.Point;
+ }
+ // 策略2: 如果是基于线或轮廓的构件 (墙、梁、楼板),取包围盒底部中心
+ else
+ {
+ BoundingBoxXYZ box = elem.get_BoundingBox(null);
+ if (box != null)
+ {
+ double cx = (box.Min.X + box.Max.X) / 2.0;
+ double cy = (box.Min.Y + box.Max.Y) / 2.0;
+ double cz = box.Min.Z; // 底部 Z
+ p = new XYZ(cx, cy, cz);
+ }
+ }
+
+ return p != null ? FormatPoint(p) : "";
+ }
+
+ ///
+ /// 检查位置变化 (严格模式:仅检查 Point 类型)
+ ///
+ private LocationDetail CheckPointLocationChange(Element oldElem, Element newElem)
+ {
+ // 只有当两个版本都是 LocationPoint 时才对比位置
+ // 避免对比墙体等 LocationCurve 导致的复杂性
+ if (oldElem.Location is LocationPoint lpOld && newElem.Location is LocationPoint lpNew)
+ {
+ // 1. 比较坐标
+ bool posChanged = !lpOld.Point.IsAlmostEqualTo(lpNew.Point, Tolerance);
+
+ // 2. 比较旋转 (如果不关心旋转可注释掉)
+ bool rotChanged = Math.Abs(lpOld.Rotation - lpNew.Rotation) > Tolerance;
+
+ if (posChanged || rotChanged)
+ {
+ return new LocationDetail
+ {
+ OldValue = FormatPoint(lpOld.Point),
+ NewValue = FormatPoint(lpNew.Point)
+ };
+ }
+ }
+ return null; // 无变化或不支持的定位类型
+ }
+
+ ///
+ /// 检查参数变化 (包含构件名和自定义参数)
+ ///
+ private List CheckParamChanges(Element oldElem, Element newElem)
+ {
+ var list = new List();
+
+ // 1. 检查构件名 (Name)
+ if (oldElem.Name != newElem.Name)
+ {
+ list.Add(new ParamDetail("构件名", oldElem.Name, newElem.Name));
+ }
+
+ // 2. 检查自定义参数
+ foreach (var paramName in _targetParams)
+ {
+ string vOld = GetParamValueString(oldElem, paramName);
+ string vNew = GetParamValueString(newElem, paramName);
+
+ if (vOld != vNew)
+ {
+ list.Add(new ParamDetail(paramName, vOld, vNew));
+ }
+ }
+
+ return list;
+ }
+
+ ///
+ /// 获取参数值的安全字符串形式
+ ///
+ private string GetParamValueString(Element elem, string paramName)
+ {
+ Parameter p = elem.LookupParameter(paramName);
+ if (p == null) return ""; // 或者返回 ""
+
+ // 优先返回用户可见的格式化字符串 (如 "1200 mm")
+ if (p.StorageType == StorageType.String)
+ return p.AsString() ?? "";
+
+ string val = p.AsValueString();
+ if (!string.IsNullOrEmpty(val)) return val;
+
+ // 如果 AsValueString 为空,读取底层数据
+ switch (p.StorageType)
+ {
+ case StorageType.Integer: return p.AsInteger().ToString();
+ case StorageType.Double: return p.AsDouble().ToString("F2");
+ case StorageType.ElementId: return p.AsElementId().ToString();
+ default: return "";
+ }
+ }
+
+ ///
+ /// 获取需要对比的构件集合
+ ///
+ private Dictionary GetElementMap(Document doc)
+ {
+ // 过滤规则:非类型、非视图相关、有材质(通常代表实体)
+ return new FilteredElementCollector(doc)
+ .WhereElementIsNotElementType()
+ .WhereElementIsViewIndependent()
+ .Where(e => e.Category != null && e.Category.HasMaterialQuantities)
+ .ToDictionary(e => e.UniqueId);
+ }
+
+ ///
+ /// 统一坐标格式化
+ ///
+ private string FormatPoint(XYZ p)
+ {
+ // 内部单位是英尺,如需毫米显示请在此处转换
+ // 例如: double x = p.X * 304.8;
+ return $"({p.X:F1}, {p.Y:F1}, {p.Z:F1})";
+ }
+ }
+
+ public class ProjectComparer
+ {
+ public ProjectComparer(List designParams, List quantityParams)
+ {
+ this.designParams = designParams;
+ this.quantityParams = quantityParams;
+ }
+
+ //设计参数
+ private readonly List designParams;
+
+
+ // 配置:需要在“出量属性”栏显示的参数
+ private readonly List quantityParams;
+
+ public List CompareDocuments(Document oldDoc, Document newDoc)
+ {
+ var rows = new List();
+ var oldMap = GetElementMap(oldDoc);
+ var newMap = GetElementMap(newDoc);
+
+ int indexCounter = 1;
+
+ // --- A. 遍历新文档 (查找 [新增] 和 [修改]) ---
+ foreach (var kvp in newMap)
+ {
+ string uid = kvp.Key;
+ Element newElem = kvp.Value;
+
+ if (!oldMap.ContainsKey(uid))
+ {
+ // === 新增构件 ===
+ rows.Add(new ReportRow
+ {
+ Index = indexCounter++,
+ ChangeType = "新增构件",
+ After = ExtractState(newElem),
+ Before = new ComponentState { ElementId = "无" } // 空状态
+ });
+ }
+ else
+ {
+ // === 修改构件 ===
+ Element oldElem = oldMap[uid];
+
+ // 判断是否有实质性修改 (参数或位置)
+ if (IsElementModified(oldElem, newElem))
+ {
+ rows.Add(new ReportRow
+ {
+ Index = indexCounter++,
+ ChangeType = "修改构件",
+ After = ExtractState(newElem),
+ Before = ExtractState(oldElem)
+ });
+ }
+ }
+ }
+
+ // --- B. 遍历旧文档 (查找 [删除]) ---
+ foreach (var kvp in oldMap)
+ {
+ if (!newMap.ContainsKey(kvp.Key))
+ {
+ // === 删除构件 ===
+ rows.Add(new ReportRow
+ {
+ Index = indexCounter++,
+ ChangeType = "删除构件",
+ After = new ComponentState { ElementId = "无" }, // 空状态
+ Before = ExtractState(kvp.Value)
+ });
+ }
+ }
+
+ return rows;
+ }
+
+ // --- 提取构件信息为状态对象 ---
+ private ComponentState ExtractState(Element e)
+ {
+ var state = new ComponentState
+ {
+ ElementId = e.Id.ToString(),
+ TypeName = e.Name // 这里的Name通常是族类型名称
+ };
+
+ // 1. 提取设计属性 (拼接成换行字符串)
+ var sbDesign = new StringBuilder();
+ foreach (var paramName in designParams)
+ {
+ string val = GetParamValue(e, paramName);
+ if (!string.IsNullOrEmpty(val))
+ {
+ // Excel 单元格内换行使用 Environment.NewLine
+ sbDesign.AppendLine($"{paramName}: {val}");
+ }
+ }
+ state.DesignProps = sbDesign.Length > 0 ? sbDesign.ToString().TrimEnd() : "";
+
+ // 2. 提取出量属性
+ var sbQty = new StringBuilder();
+ foreach (var paramName in quantityParams)
+ {
+ string val = GetParamValue(e, paramName);
+ // 排除 0.00 的情况
+ if (!string.IsNullOrEmpty(val) && val != "0.00" && val != "0")
+ {
+ sbQty.AppendLine($"{paramName}: {val}");
+ }
+ }
+ state.QuantityProps = sbQty.Length > 0 ? sbQty.ToString().TrimEnd() : "";
+
+ // 3. 提取坐标 (竖排显示)
+ state.Coordinates = GetFormattedCoordinates(e);
+
+ return state;
+ }
+
+ // --- 辅助:坐标格式化 (核心逻辑:有定位点取点,没点取包围盒) ---
+ private string GetFormattedCoordinates(Element e)
+ {
+ XYZ p = null;
+
+ // 策略1: 基于点 (柱、门、窗、家具)
+ if (e.Location is LocationPoint lp)
+ {
+ p = lp.Point;
+ }
+ // 策略2: 基于线或形状 (墙、梁、板) -> 取包围盒底部中心
+ else
+ {
+ var box = e.get_BoundingBox(null);
+ if (box != null)
+ {
+ double cx = (box.Min.X + box.Max.X) / 2.0;
+ double cy = (box.Min.Y + box.Max.Y) / 2.0;
+ double cz = box.Min.Z;
+ p = new XYZ(cx, cy, cz);
+ }
+ }
+
+ if (p == null) return "无";
+
+ // 格式化为 Excel 竖排效果,并转换单位 (英尺 -> 毫米)
+ double toMm = 304.8;
+ return $"X: {(p.X * toMm):F2}\nY: {(p.Y * toMm):F2}\nZ: {(p.Z * toMm):F2}";
+ }
+
+ // --- 辅助:判断是否修改 ---
+ private bool IsElementModified(Element oldE, Element newE)
+ {
+ // 1. 名字变了
+ if (oldE.Name != newE.Name) return true;
+
+ // 2. 坐标变了
+ if (GetFormattedCoordinates(oldE) != GetFormattedCoordinates(newE)) return true;
+
+ // 3. 关键参数变了 (可选:遍历 _designParams 检查)
+ // 这里简单示例:如果提取出的设计属性字符串不一致,就算修改
+ // 实际为了性能,建议在这里写单独的参数对比循环
+ var s1 = ExtractState(oldE).DesignProps;
+ var s2 = ExtractState(newE).DesignProps;
+ if (s1 != s2) return true;
+
+ return false;
+ }
+
+ // --- 辅助:获取参数值 ---
+ private string GetParamValue(Element e, string name)
+ {
+ Parameter p = e.LookupParameter(name);
+ if (p == null) return "";
+ if (p.StorageType == StorageType.String) return p.AsString();
+ return p.AsValueString(); // AsValueString 会带单位
+ }
+
+ // --- 辅助:获取元素字典 ---
+ private Dictionary GetElementMap(Document doc)
+ {
+ return new FilteredElementCollector(doc)
+ .WhereElementIsNotElementType()
+ .WhereElementIsViewIndependent()
+ .Where(e => e.Category != null && e.Category.HasMaterialQuantities) // 简单的实体过滤
+ .ToDictionary(e => e.UniqueId);
+ }
+ }
+}
diff --git a/Szmedi.RvKits/ModelManager/ReportGenerator.cs b/Szmedi.RvKits/ModelManager/ReportGenerator.cs
new file mode 100644
index 0000000..5013f0e
--- /dev/null
+++ b/Szmedi.RvKits/ModelManager/ReportGenerator.cs
@@ -0,0 +1,238 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using EPPlus.Core.Extensions;
+
+using OfficeOpenXml;
+using OfficeOpenXml.Style;
+
+namespace Szmedi.RvKits.ModelManager
+{
+ public static class ReportGenerator
+ {
+ public static string Generate(List results)
+ {
+ if (results == null || results.Count == 0) return "未发现构件差异。";
+
+ StringBuilder sb = new StringBuilder();
+ sb.AppendLine("=== Revit 版本对比报告 ===");
+
+ // 1. 新增
+ var added = results.Where(r => r.Status == DiffType.Added).ToList();
+ if (added.Any())
+ {
+ sb.AppendLine($"\n【新增构件】: {added.Count}");
+ foreach (var item in added)
+ {
+ sb.Append($" [+] ID:{item.ElementId} | {item.CategoryName}: {item.Name}");
+
+ // 如果 LocationChange 不为空,说明计算出了坐标
+ if (item.LocationChange != null && !string.IsNullOrEmpty(item.LocationChange.NewValue))
+ {
+ sb.Append($" @ {item.LocationChange.NewValue}");
+ }
+ sb.AppendLine();
+ }
+ }
+
+ // 2. 修改
+ var modified = results.Where(r => r.Status == DiffType.Modified).ToList();
+ if (modified.Any())
+ {
+ sb.AppendLine($"\n【修改构件】: {modified.Count}");
+ foreach (var item in modified)
+ {
+ sb.AppendLine($" [*] ID:{item.ElementId} | {item.CategoryName}: {item.Name}");
+
+ // 显示位置移动
+ if (item.LocationChange != null)
+ {
+ sb.AppendLine($" [位置移动]");
+ sb.AppendLine($" 原: {item.LocationChange.OldValue}");
+ sb.AppendLine($" 新: {item.LocationChange.NewValue}");
+ }
+
+ // 显示参数变化
+ foreach (var p in item.ParamChanges)
+ {
+ sb.AppendLine($" > {p.ParamName}: \"{p.OldValue}\" -> \"{p.NewValue}\"");
+ }
+ sb.AppendLine();
+ }
+ }
+
+ // 3. 删除
+ var deleted = results.Where(r => r.Status == DiffType.Deleted).ToList();
+ if (deleted.Any())
+ {
+ sb.AppendLine($"\n【删除构件】: {deleted.Count}");
+ foreach (var item in deleted)
+ {
+ sb.AppendLine($" [-] ID:{item.ElementId} | {item.CategoryName}: {item.Name}");
+ }
+ }
+
+ return sb.ToString();
+ }
+
+
+ public static void ToExcel(string excelPath, List infos)
+ {
+ FileInfo fi = new(excelPath);
+ ExcelPackage package = infos.ToWorksheet("构件数量变动对比表")
+ .WithConfiguration(cfg => cfg.WithColumnConfiguration(c => c.AutoFit()))
+ .ToExcelPackage();
+
+ package.SaveAs(fi);
+ }
+
+ public static void Export(List data, string filePath)
+ {
+ var file = new FileInfo(filePath);
+ if (file.Exists)
+ {
+ try { file.Delete(); }
+ catch { throw new Exception("无法覆盖文件,请确保Excel已关闭。"); }
+ }
+
+ using (var package = new ExcelPackage(file))
+ {
+ var ws = package.Workbook.Worksheets.Add("构件对比表");
+
+ // --- 1. 设置表头 (模仿图片样式) ---
+
+ // 设置列宽
+ ws.Column(1).Width = 6; // 序号
+ ws.Column(2).Width = 10; // 变更类型
+
+ // 变更后 (3-7)
+ ws.Column(3).Width = 10; // ID
+ ws.Column(4).Width = 15; // 类型
+ ws.Column(5).Width = 25; // 设计属性
+ ws.Column(6).Width = 12; // 出量
+ ws.Column(7).Width = 12; // 坐标
+
+ // 变更前 (8-12)
+ ws.Column(8).Width = 10;
+ ws.Column(9).Width = 15;
+ ws.Column(10).Width = 25;
+ ws.Column(11).Width = 12;
+ ws.Column(12).Width = 12;
+
+ // 第一行:合并的大标题
+ ws.Cells[1, 1].Value = "序号"; ws.Cells[1, 1, 2, 1].Merge = true;
+ ws.Cells[1, 2].Value = "变更\n类型"; ws.Cells[1, 2, 2, 2].Merge = true;
+
+ ws.Cells[1, 3].Value = "变更后构件"; ws.Cells[1, 3, 1, 7].Merge = true;
+ ws.Cells[1, 8].Value = "变更前构件"; ws.Cells[1, 8, 1, 12].Merge = true;
+
+ // 第二行:子标题
+ string[] headers = { "图元ID", "类型", "设计属性", "出量属性", "坐标" };
+ for (int i = 0; i < 5; i++)
+ {
+ ws.Cells[2, 3 + i].Value = headers[i];
+ ws.Cells[2, 8 + i].Value = headers[i];
+ }
+
+ // 表头样式
+ using (var range = ws.Cells[1, 1, 2, 12])
+ {
+ range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
+ range.Style.VerticalAlignment = ExcelVerticalAlignment.Center;
+ range.Style.Font.Bold = true;
+ range.Style.Fill.PatternType = ExcelFillStyle.Solid;
+ range.Style.Fill.BackgroundColor.SetColor(Color.LightGray);
+ range.Style.Border.Top.Style = ExcelBorderStyle.Thin;
+ range.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
+ range.Style.Border.Left.Style = ExcelBorderStyle.Thin;
+ range.Style.Border.Right.Style = ExcelBorderStyle.Thin;
+ range.Style.WrapText = true; // 允许表头换行
+ }
+
+ // --- 2. 填充数据 ---
+ int rowIdx = 3;
+
+ foreach (var item in data)
+ {
+ // 序号 & 类型
+ ws.Cells[rowIdx, 1].Value = item.Index;
+ ws.Cells[rowIdx, 2].Value = item.ChangeType;
+
+ // 设置变更类型颜色
+ var typeCell = ws.Cells[rowIdx, 2];
+ if (item.ChangeType.Contains("新增")) typeCell.Style.Font.Color.SetColor(Color.Green);
+ else if (item.ChangeType.Contains("删除")) typeCell.Style.Font.Color.SetColor(Color.Red);
+ else typeCell.Style.Font.Color.SetColor(Color.Blue);
+
+ // --- 变更后数据 (After) ---
+ if (item.After.IsEmpty)
+ {
+ // 如果为空,合并5列显示“无”
+ ws.Cells[rowIdx, 3].Value = "无";
+ ws.Cells[rowIdx, 3, rowIdx, 7].Merge = true;
+ }
+ else
+ {
+ FillData(ws, rowIdx, 3, item.After);
+ }
+
+ // --- 变更前数据 (Before) ---
+ if (item.Before.IsEmpty)
+ {
+ // 如果为空,合并5列显示“无”
+ ws.Cells[rowIdx, 8].Value = "无";
+ ws.Cells[rowIdx, 8, rowIdx, 12].Merge = true;
+ }
+ else
+ {
+ FillData(ws, rowIdx, 8, item.Before);
+ }
+
+ rowIdx++;
+ }
+
+ // --- 3. 全局样式调整 ---
+ if (data.Count > 0)
+ {
+ var dataRange = ws.Cells[3, 1, rowIdx - 1, 12];
+
+ // 边框
+ dataRange.Style.Border.Top.Style = ExcelBorderStyle.Thin;
+ dataRange.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
+ dataRange.Style.Border.Left.Style = ExcelBorderStyle.Thin;
+ dataRange.Style.Border.Right.Style = ExcelBorderStyle.Thin;
+
+ // 对齐
+ dataRange.Style.VerticalAlignment = ExcelVerticalAlignment.Center;
+ dataRange.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
+
+ // 特殊列左对齐 (属性列)
+ ws.Cells[3, 5, rowIdx - 1, 5].Style.HorizontalAlignment = ExcelHorizontalAlignment.Left; // After Props
+ ws.Cells[3, 10, rowIdx - 1, 10].Style.HorizontalAlignment = ExcelHorizontalAlignment.Left; // Before Props
+ ws.Cells[3, 7, rowIdx - 1, 7].Style.HorizontalAlignment = ExcelHorizontalAlignment.Left; // After Coords
+ ws.Cells[3, 12, rowIdx - 1, 12].Style.HorizontalAlignment = ExcelHorizontalAlignment.Left; // Before Coords
+
+ // 开启自动换行 (关键,否则 \n 不生效)
+ dataRange.Style.WrapText = true;
+ }
+
+ package.Save();
+ }
+ }
+
+ private static void FillData(ExcelWorksheet ws, int row, int col, ComponentState s)
+ {
+ ws.Cells[row, col].Value = s.ElementId;
+ ws.Cells[row, col + 1].Value = s.TypeName;
+ ws.Cells[row, col + 2].Value = s.DesignProps;
+ ws.Cells[row, col + 3].Value = s.QuantityProps;
+ ws.Cells[row, col + 4].Value = s.Coordinates;
+ }
+
+ }
+}
diff --git a/Szmedi.RvKits/Modeling/RelativeMoveCmd.cs b/Szmedi.RvKits/Modeling/RelativeMoveCmd.cs
index 208dd97..3eda3aa 100644
--- a/Szmedi.RvKits/Modeling/RelativeMoveCmd.cs
+++ b/Szmedi.RvKits/Modeling/RelativeMoveCmd.cs
@@ -11,7 +11,6 @@ namespace Szmedi.RvKits.Modeling
/// Revit执行命令
///
[Transaction(TransactionMode.Manual)]
- [Regeneration(RegenerationOption.Manual)]
public class RelativeMoveCmd : ExternalCommand
{
public override void Execute()
diff --git a/Szmedi.RvKits/Modeling/RoomNameAssignCmd.cs b/Szmedi.RvKits/Modeling/RoomNameAssignCmd.cs
index dbae65f..88e5ead 100644
--- a/Szmedi.RvKits/Modeling/RoomNameAssignCmd.cs
+++ b/Szmedi.RvKits/Modeling/RoomNameAssignCmd.cs
@@ -19,7 +19,6 @@ using Szmedi.RvKits.Assists;
namespace Szmedi.RvKits.Modeling
{
[Transaction(TransactionMode.Manual)]
- [Regeneration(RegenerationOption.Manual)]
public class RoomNameAssignCmd : ExternalCommand
{
public override void Execute()
diff --git a/Szmedi.RvKits/Modeling/TrackCreatorViewModel.cs b/Szmedi.RvKits/Modeling/TrackCreatorViewModel.cs
index 946500f..5778cfc 100644
--- a/Szmedi.RvKits/Modeling/TrackCreatorViewModel.cs
+++ b/Szmedi.RvKits/Modeling/TrackCreatorViewModel.cs
@@ -1,6 +1,4 @@
-
-using System;
-using System.ComponentModel;
+using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Windows;
@@ -13,9 +11,7 @@ using Autodesk.Revit.UI.Selection;
using Nice3point.Revit.Toolkit.External.Handlers;
using Szmedi.RvKits.Assists;
-using Szmedi.RvKits.Attributes;
using Szmedi.RvKits.Properties;
-using Szmedi.Toolkit.Assists;
namespace Szmedi.RvKits.Modeling
{
diff --git a/Szmedi.RvKits/Properties/Resources.Designer.cs b/Szmedi.RvKits/Properties/Resources.Designer.cs
index 17825d0..575a16b 100644
--- a/Szmedi.RvKits/Properties/Resources.Designer.cs
+++ b/Szmedi.RvKits/Properties/Resources.Designer.cs
@@ -19,7 +19,7 @@ namespace Szmedi.RvKits.Properties {
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
@@ -280,6 +280,26 @@ namespace Szmedi.RvKits.Properties {
}
}
+ ///
+ /// 查找 System.Drawing.Bitmap 类型的本地化资源。
+ ///
+ internal static System.Drawing.Bitmap compare_project_16px {
+ get {
+ object obj = ResourceManager.GetObject("compare_project_16px", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// 查找 System.Drawing.Bitmap 类型的本地化资源。
+ ///
+ internal static System.Drawing.Bitmap compare_project_32px {
+ get {
+ object obj = ResourceManager.GetObject("compare_project_32px", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
///
/// 查找 System.Drawing.Bitmap 类型的本地化资源。
///
@@ -490,6 +510,26 @@ namespace Szmedi.RvKits.Properties {
}
}
+ ///
+ /// 查找 System.Drawing.Bitmap 类型的本地化资源。
+ ///
+ internal static System.Drawing.Bitmap Facility_Info_Process_16px {
+ get {
+ object obj = ResourceManager.GetObject("Facility_Info_Process_16px", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// 查找 System.Drawing.Bitmap 类型的本地化资源。
+ ///
+ internal static System.Drawing.Bitmap Facility_Info_Process_32px {
+ get {
+ object obj = ResourceManager.GetObject("Facility_Info_Process_32px", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
///
/// 查找 System.Drawing.Bitmap 类型的本地化资源。
///
diff --git a/Szmedi.RvKits/Properties/Resources.resx b/Szmedi.RvKits/Properties/Resources.resx
index 4486296..baad00c 100644
--- a/Szmedi.RvKits/Properties/Resources.resx
+++ b/Szmedi.RvKits/Properties/Resources.resx
@@ -487,4 +487,16 @@
..\Resources\CodeList_32px.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+ ..\Resources\Facility_Info_Process_32px.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\Facility_Info_Process_16px.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\compare_project_16px.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\compare_project_32px.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
\ No newline at end of file
diff --git a/Szmedi.RvKits/Properties/Settings.Designer.cs b/Szmedi.RvKits/Properties/Settings.Designer.cs
index 3180e98..e3edb75 100644
--- a/Szmedi.RvKits/Properties/Settings.Designer.cs
+++ b/Szmedi.RvKits/Properties/Settings.Designer.cs
@@ -251,16 +251,9 @@ namespace Szmedi.RvKits.Properties {
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Configuration.DefaultSettingValueAttribute(@"
-
- 新增地质剖切工具
- 管线线上标记功能
- 移动模型功能优化
- 界面控件压缩
- 增加根据EAM编码表填写编码工具,在参数管理下拉菜单
- 增加部分功能的弹窗提示,避免误操作
- 修复轨道创建下拉框无法搜索并选择的问题
-")]
+ [global::System.Configuration.DefaultSettingValueAttribute("\r\n\r\n rvt文档比较工具\r\n")]
public global::System.Collections.Specialized.StringCollection UpdateNotes {
get {
return ((global::System.Collections.Specialized.StringCollection)(this["UpdateNotes"]));
diff --git a/Szmedi.RvKits/Properties/Settings.settings b/Szmedi.RvKits/Properties/Settings.settings
index 332f460..9b5c4ec 100644
--- a/Szmedi.RvKits/Properties/Settings.settings
+++ b/Szmedi.RvKits/Properties/Settings.settings
@@ -99,13 +99,7 @@
<?xml version="1.0" encoding="utf-16"?>
<ArrayOfString xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
- <string>新增地质剖切工具</string>
- <string>管线线上标记功能</string>
- <string>移动模型功能优化</string>
- <string>界面控件压缩</string>
- <string>增加根据EAM编码表填写编码工具,在参数管理下拉菜单</string>
- <string>增加部分功能的弹窗提示,避免误操作</string>
- <string>修复轨道创建下拉框无法搜索并选择的问题</string>
+ <string>rvt文档比较工具</string>
</ArrayOfString>
diff --git a/Szmedi.RvKits/Resources/Facility_Info_Process_16px.png b/Szmedi.RvKits/Resources/Facility_Info_Process_16px.png
new file mode 100644
index 0000000..4ed86c9
Binary files /dev/null and b/Szmedi.RvKits/Resources/Facility_Info_Process_16px.png differ
diff --git a/Szmedi.RvKits/Resources/Facility_Info_Process_32px.png b/Szmedi.RvKits/Resources/Facility_Info_Process_32px.png
new file mode 100644
index 0000000..c0fc637
Binary files /dev/null and b/Szmedi.RvKits/Resources/Facility_Info_Process_32px.png differ
diff --git a/Szmedi.RvKits/Resources/compare_project_16px.png b/Szmedi.RvKits/Resources/compare_project_16px.png
new file mode 100644
index 0000000..9fef380
Binary files /dev/null and b/Szmedi.RvKits/Resources/compare_project_16px.png differ
diff --git a/Szmedi.RvKits/Resources/compare_project_32px.png b/Szmedi.RvKits/Resources/compare_project_32px.png
new file mode 100644
index 0000000..1e7db53
Binary files /dev/null and b/Szmedi.RvKits/Resources/compare_project_32px.png differ
diff --git a/Szmedi.RvKits/RvScript/ChatDialogueViewModel.cs b/Szmedi.RvKits/RvScript/ChatDialogueViewModel.cs
index dc219e2..24e3091 100644
--- a/Szmedi.RvKits/RvScript/ChatDialogueViewModel.cs
+++ b/Szmedi.RvKits/RvScript/ChatDialogueViewModel.cs
@@ -142,7 +142,7 @@ public partial class ChatDialogueViewModel : ObservableObject
{
if (ChatHistory.Count == 0)
{
- CurrentRequest += Message.Human($"你是一个专业的Revit二次开发工程师,不会随意捏造事实,能客观地回答用户的问题,对于不确定或难以理解的问题,需要用户补充说明的,需要主动提出。你需要在{GlobalVariables.UIApplication.Application.VersionName}二次开发中,使用已定义的uidoc、doc两个变量,构造一个正确执行的C#代码块,保证使用的RevitAPI的所有方法都能在{GlobalVariables.UIApplication.Application.VersionName}的API文档中找到,在代码中添加关键的注释,不需要方法签名和using命名空间,但使用类名时需要完整的命名空间。需求是:{UserInput}");
+ CurrentRequest += Message.Human($"你是专业的Revit二次开发工程师,不随意捏造事实,能客观地回答用户的问题,对于不确定或难以理解的问题,需要用户补充说明的,需要主动提出。你需要在{GlobalVariables.UIApplication.Application.VersionName}二次开发中,使用已定义的uidoc、doc两个变量,构造一个可以正确执行的C#代码块,保证使用的RevitAPI的所有方法都能在{GlobalVariables.UIApplication.Application.VersionName}版本的API文档中找到,在代码中添加关键的注释,不需要Excute方法签名和using命名空间,使用类声明对象时,需要完整的命名空间。需求是:{UserInput}");
ChatHistory.Add(CurrentRequest);
}
else
diff --git a/Szmedi.RvKits/RvScript/ScriptRunnerCmd.cs b/Szmedi.RvKits/RvScript/ScriptRunnerCmd.cs
index dcc5f00..68913fc 100644
--- a/Szmedi.RvKits/RvScript/ScriptRunnerCmd.cs
+++ b/Szmedi.RvKits/RvScript/ScriptRunnerCmd.cs
@@ -21,7 +21,6 @@ using Szmedi.RvKits.Assists;
namespace Szmedi.RvKits.RvScript
{
[Transaction(TransactionMode.Manual)]
- [Regeneration(RegenerationOption.Manual)]
public class ScriptRunnerCmd : IExternalCommand
{
//public override void Execute()
diff --git a/Szmedi.RvKits/Szmedi.RvKits.csproj b/Szmedi.RvKits/Szmedi.RvKits.csproj
index 7cbc40e..0ffba3e 100644
--- a/Szmedi.RvKits/Szmedi.RvKits.csproj
+++ b/Szmedi.RvKits/Szmedi.RvKits.csproj
@@ -16,7 +16,7 @@
Szmedi.RvKits
$(AssemblyName)
Szmedi
- 2.2.3.75
+ 2.2.3.82
$(AssemblyVersion)
$(AssemblyVersion)
@@ -116,16 +116,17 @@
-
+
+
-
+
@@ -202,6 +203,36 @@
ResXFileCodeGenerator
Resources.Designer.cs
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
PreserveNewest
diff --git a/Szmedi.RvKits/Szmedi.RvKits.csproj.DotSettings b/Szmedi.RvKits/Szmedi.RvKits.csproj.DotSettings
deleted file mode 100644
index cb39080..0000000
--- a/Szmedi.RvKits/Szmedi.RvKits.csproj.DotSettings
+++ /dev/null
@@ -1,2 +0,0 @@
-
- False
\ No newline at end of file
diff --git a/Szmedi.RvKits/UIApp/UIRibbon.cs b/Szmedi.RvKits/UIApp/UIRibbon.cs
index 8f6e4dc..4df7259 100644
--- a/Szmedi.RvKits/UIApp/UIRibbon.cs
+++ b/Szmedi.RvKits/UIApp/UIRibbon.cs
@@ -214,7 +214,7 @@ public class UIRibbon : ExternalApplication
{
LargeImage = Resources.MoveModel_32px.ToBitmapSource(),
ToolTip = "在平、立面、剖面视图下移动全部模型",
- LongDescription= "部分图元无法移动或移动会错位,需手动处理,如:基于面放置的族实例等,如果发生严重错误,如部分草图楼梯移动发生严重错误,可以尝试剪切粘贴后再使用此命令",
+ LongDescription = "部分图元无法移动或移动会错位,需手动处理,如:基于面放置的族实例等,如果发生严重错误,如部分草图楼梯移动发生严重错误,可以尝试剪切粘贴后再使用此命令",
AvailabilityClassName = typeof(EnableOnPlanOrSection).FullName
};
PushButtonData getCoordinate =
@@ -351,6 +351,14 @@ public class UIRibbon : ExternalApplication
LargeImage = Resources.WriteFacilityCode_32px.ToBitmapSource(),
ToolTip = "设备编号提取、写入",
};
+ PushButtonData writeFacilityInfo =
+ new(nameof(FacilityInfoProcessCmd), "设备信息", AddInPath, typeof(FacilityInfoProcessCmd).FullName)
+ {
+ //Image = ConvertFromBitmap(Resources.cmd_16px),
+ LargeImage = Resources.Facility_Info_Process_32px.ToBitmapSource(),
+ ToolTip = "给设备构件批量添加信息",
+ LongDescription = "Excel需要包含设备、设备参数、部署范围三列,设备参数列的每个单元格中每行一个参数,参数表达例如“品牌:日立”"
+ };
PushButtonData afcaMetroManager =
new(nameof(AFCAMetroCmd), "轨交交付", AddInPath, typeof(AFCAMetroCmd).FullName)
{
@@ -358,7 +366,8 @@ public class UIRibbon : ExternalApplication
ToolTip = "按轨道交通交付标准要求,添加、填写、修改属性",
AvailabilityClassName = typeof(OnActiveProjectDocument).FullName
};
-
+
+
standardPanel.AddSplitButton(new("土建工具", "土建工具"), geologyByLoop, resolveLevelPbd, levelSeparatePbd, splittingPbd, slopeFloorCreator, resolveConnection, addViewFilters);
standardPanel.AddSplitButton(new("房间功能", "房间功能"), room3D, roomNaming, roomNameAssign);
standardPanel.AddSplitButton(new("基点功能", "基点功能"), resetCoordinate, getCoordinate, moveModel);
@@ -370,10 +379,10 @@ public class UIRibbon : ExternalApplication
//standardPanel.AddItem(dwgBlockModelCreator);
//standardPanel.AddItem(trackCreator);
standardPanel.AddSplitButton(new("清理工具", "清理工具"), pureModel, setDisplay);
- standardPanel.AddSplitButton(new("参数管理", "参数管理"), parameterManager, eamManager, eamCodeManager, mappingManager, writeFacilityCode);
+ standardPanel.AddSplitButton(new("参数管理", "参数管理"), parameterManager, afcaMetroManager, eamManager, eamCodeManager, mappingManager, writeFacilityCode, writeFacilityInfo);
standardPanel.AddSplitButton(new("参数修改", "参数修改"), parameterWriter, relacePropValue, removeProps);
//standardPanel.AddSplitButton(new("交付管理", "交付管理"), afcaMetroManager, afcaArchiManager);
- standardPanel.AddItem(afcaMetroManager);
+ //standardPanel.AddItem(afcaMetroManager);
}
private void ComponentManager_UIElementActivated(object sender, UIElementActivatedEventArgs e)
@@ -584,7 +593,14 @@ public class UIRibbon : ExternalApplication
DisplayApp(displayPanel);
var generalPanel = application.CreateRibbonPanel(TabName, "其他");
-
+ PushButtonData compareProject =
+ new(nameof(CompareProjectDiffCmd), "文档比较", AddInPath, typeof(CompareProjectDiffCmd).FullName)
+ {
+ Image = Resources.compare_project_16px.ToBitmapSource(),
+ LargeImage = Resources.compare_project_32px.ToBitmapSource(),
+ ToolTip = "对比两个文档之间的构件差异",
+ AvailabilityClassName = typeof(OnActiveProjectDocument).FullName
+ };
PushButtonData elementsControlPbData =
new(nameof(ElementsControlCmd), "对象控制", AddInPath, typeof(ElementsControlCmd).FullName)
{
@@ -627,6 +643,7 @@ public class UIRibbon : ExternalApplication
//ElementsControlPBData.LargeImage = ConvertFromBitmap(Resources.Control_Panel_32px);
//ZoomRBGroup = CreateViewToggleButton(generalPanel);
CreateToggleButton(tab, "其他");
+ generalPanel.AddItem(compareProject);
generalPanel.AddSplitButton(new("显示控制", "显示控制"), categoryControlPbData, elementsControlPbData);
//generalPanel.AddItem(categoryControlPbData);
generalPanel.AddItem(autoSave);
@@ -1377,10 +1394,17 @@ public class UIRibbon : ExternalApplication
//if (FAIRTab.Panels.Count == 0) FAIRTab.IsVisible = false;
}
}
-
+ private static void CreateLogger()
+ {
+ AppDomain.CurrentDomain.UnhandledException += (_, args) =>
+ {
+ var e = (Exception)args.ExceptionObject;
+ LogAssists.WriteLog(e.Message);
+ };
+ }
public override void OnStartup()
{
-
+ CreateLogger();
UpdateCheck();
zoomElementHandler = new();
saveHandler = new();
@@ -1425,6 +1449,11 @@ public class UIRibbon : ExternalApplication
// Assembly.LoadFrom(services);
//}
}
+ public override void OnShutdown()
+ {
+ Application.ControlledApplication.ApplicationInitialized -= ControlledApplication_ApplicationInitialized;
+ GlobalVariables.Timer.Stop();
+ }
///
/// 上传使用数据
///
diff --git a/Szmedi.RvKits/WPFUI.xaml b/Szmedi.RvKits/WPFUI.xaml
index 1ff2138..5cc7dca 100644
--- a/Szmedi.RvKits/WPFUI.xaml
+++ b/Szmedi.RvKits/WPFUI.xaml
@@ -155,9 +155,6 @@
-
diff --git a/Szmedi.RvKits/app.config b/Szmedi.RvKits/app.config
index 7078b9a..28c27af 100644
--- a/Szmedi.RvKits/app.config
+++ b/Szmedi.RvKits/app.config
@@ -115,13 +115,7 @@
- 新增地质剖切工具
- 管线线上标记功能
- 移动模型功能优化
- 界面控件压缩
- 增加根据EAM编码表填写编码工具,在参数管理下拉菜单
- 增加部分功能的弹窗提示,避免误操作
- 修复轨道创建下拉框无法搜索并选择的问题
+ rvt文档比较工具
diff --git a/Szmedi.Rvkits.iss b/Szmedi.Rvkits.iss
index 0c253a4..d88a4c5 100644
--- a/Szmedi.Rvkits.iss
+++ b/Szmedi.Rvkits.iss
@@ -4,7 +4,7 @@
; 定义应用程序的名称
#define MyAppName "SzmediTools"
; 定义应用程序的版本号
-#define MyAppVersion "2.2.3.75"
+#define MyAppVersion "2.2.3.82"
; 定义应用程序的发布者
#define MyAppPublisher "SZMEDI"
; 定义应用程序的网址
diff --git a/Szmedi.Test/TestCmd.cs b/Szmedi.Test/TestCmd.cs
index d4ff052..6e6773d 100644
--- a/Szmedi.Test/TestCmd.cs
+++ b/Szmedi.Test/TestCmd.cs
@@ -1,34 +1,23 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Security.Cryptography;
+using System.Linq;
using System.Text;
using System.Windows;
-using System.Windows.Controls;
-using System.Xml.Linq;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
-using Autodesk.Revit.DB.Mechanical;
-using Autodesk.Revit.DB.Plumbing;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
-using CommunityToolkit.Mvvm.DependencyInjection;
+using EPPlus.Core.Extensions;
using Nice3point.Revit.Toolkit.External;
using Nice3point.Revit.Toolkit.Utils;
using OfficeOpenXml;
-using OfficeOpenXml.FormulaParsing.Excel.Functions.Math;
using RevitAddins;
using Szmedi.RvKits.Mvvm.Attributes;
-using Wpf.Ui;
-
namespace Szmedi.Test
{
[Transaction(TransactionMode.Manual)]
@@ -138,8 +127,116 @@ namespace Szmedi.Test
// return hasConnectToSystem;
//}
+
+ public List GetUsedFamilies(Document doc)
+ {
+ // 获取所有实例元素(这里不需要由ToElements()转化全部对象,节省内存,仅遍历)
+ var instances = doc.OfClass();
+
+ // 3. 收集所有被使用的“类型ID” (使用HashSet自动去重)
+ HashSet usedTypeIds = new HashSet();
+
+ foreach (Element elem in instances)
+ {
+ ElementId typeId = elem.GetTypeId();
+ if (typeId != ElementId.InvalidElementId)
+ {
+ usedTypeIds.Add(typeId);
+ }
+ }
+
+ // 4. 从类型反向获取族 (使用HashSet再次去重族ID,防止多个类型属于同一个族)
+ HashSet usedFamilyIds = new HashSet();
+ List resultFamilies = new List();
+
+ foreach (ElementId typeId in usedTypeIds)
+ {
+ // 获取类型元素 (FamilySymbol)
+ Element typeElem = doc.GetElement(typeId);
+ FamilySymbol symbol = typeElem as FamilySymbol;
+
+ if (symbol != null)
+ {
+ Family family = symbol.Family;
+
+ // 确保族存在且未被添加过
+ if (family != null && !usedFamilyIds.Contains(family.Id))
+ {
+ usedFamilyIds.Add(family.Id);
+ resultFamilies.Add(family);
+ }
+ }
+ }
+
+ return resultFamilies;
+ }
+ public static void ToExcel(string excelPath, List infos)
+ {
+ FileInfo fi = new(excelPath);
+ ExcelPackage package = infos.ToWorksheet("构件数量变动对比表")
+ .WithConfiguration(cfg => cfg.WithColumnConfiguration(c => c.AutoFit()))
+ .ToExcelPackage();
+
+ package.SaveAs(fi);
+ }
public override void Execute()
{
+ //var parameter = Document.FamilyManager.GetParameters().FirstOrDefault(p => p.Definition.Name == "额定电流");
+ //Document.Invoke(
+ // ts =>
+ // {
+ // Document.FamilyManager.ReplaceParameter(parameter, "测试", BuiltInParameterGroup.PG_TEXT, true);
+ // });
+ var doc = Document;
+ if (doc.IsFamilyDocument)
+ {
+ return;
+ }
+ var familyCollector = new FilteredElementCollector(doc)
+ .OfClass(typeof(Family))
+ .Cast()
+ .ToList();
+ MessageBox.Show(familyCollector.Count().ToString());
+
+ var fams = GetUsedFamilies(doc)
+ .Cast()
+ .Where(
+ fam => fam.FamilyCategory.AllowsBoundParameters &&
+ fam.IsEditable &&
+ fam.FamilyCategory.CategoryType == CategoryType.Model &&
+ !Enum.GetName(typeof(BuiltInCategory), fam.FamilyCategoryId.IntegerValue)!.Contains(
+ "Fitting") &&
+ fam.FamilyCategoryId.IntegerValue != -2008013).ToList();//风道末端
+ MessageBox.Show(fams.Count().ToString());
+ return;
+
+ var elements = new FilteredElementCollector(Document).WhereElementIsNotElementType()
+ .ToElements()
+ .ToList();
+ StringBuilder sb = new StringBuilder();
+ var elementIds = new List();
+ foreach (var item in elements)
+ {
+ var level = Document.GetElement(item.GetLevelId())?.Name;
+ var param = item.get_Parameter(BuiltInParameter.DOOR_NUMBER)?.AsString();
+ if (param != null && level != null && param.Contains("B2") && level.Contains("站厅"))
+ {
+ sb.AppendLine($"{item.Id}");
+ elementIds.Add(item.Id);
+
+ }
+ if (param != null && level != null && param.Contains("B1") && level.Contains("站台"))
+ {
+ sb.AppendLine($"{item.Id}");
+ elementIds.Add(item.Id);
+
+ }
+ }
+ if (sb.Length > 0)
+ {
+ UiDocument.Selection.SetElementIds(elementIds);
+ }
+ return;
var walls = new FilteredElementCollector(Document).WhereElementIsNotElementType()
.OfClass(typeof(Wall))
.ToElements()
diff --git a/Szmedi.Test/TestExcel.cs b/Szmedi.Test/TestExcel.cs
new file mode 100644
index 0000000..4138744
--- /dev/null
+++ b/Szmedi.Test/TestExcel.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using EPPlus.Core.Extensions.Attributes;
+
+namespace Szmedi.Test
+{
+ public class TestExcel
+ {
+ [ExcelTableColumn("变更前")]
+ public Test Old { get; set; }
+ [ExcelTableColumn("变更后")]
+ public Test New { get; set; }
+ }
+ public class Test
+ {
+ public Test(string iD, string name, string description)
+ {
+ ID = iD;
+ Name = name;
+ Description = description;
+ }
+
+ [ExcelTableColumn("ID")]
+ public string ID { get; set; }
+ [ExcelTableColumn("名称")]
+ public string Name { get; set; }
+ [ExcelTableColumn("描述")]
+ public string Description { get; set; }
+ }
+}
diff --git a/Szmedi.Toolkit.Revit/Assists/LogAssists.cs b/Szmedi.Toolkit.Revit/Assists/LogAssists.cs
index 9e326a3..857929f 100644
--- a/Szmedi.Toolkit.Revit/Assists/LogAssists.cs
+++ b/Szmedi.Toolkit.Revit/Assists/LogAssists.cs
@@ -1,69 +1,9 @@
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Text;
+using System.Text;
namespace Szmedi.Toolkit.Assists
{
public static class LogAssists
{
-
- private static string GetCurrentMethodFullName()
- {
- string str1;
- try
- {
- var num = 2;
- StackTrace stackTrace = new();
- var length = stackTrace.GetFrames().Length;
- StackFrame frame;
- string str;
- bool flag;
- do
- {
- var num1 = num;
- num = num1 + 1;
- frame = stackTrace.GetFrame(num1);
- str = frame.GetMethod().DeclaringType.ToString();
- flag = str.EndsWith("Exception") && num < length;
- } while (flag);
-
- var name = frame.GetMethod().Name;
- str1 = string.Concat(str, ".", name);
- }
- catch
- {
- str1 = null;
- }
-
- return str1;
- }
-
- public static void ToLog(this string strLog, string logFolder = default)
- {
- if (logFolder == default)
- {
- var assemblyPath = typeof(LogAssists).Assembly.Location;
- var directory = Path.GetDirectoryName(assemblyPath);
- logFolder = Path.Combine(directory, "Logs");
- }
-
- if (!Directory.Exists(logFolder))
- {
- Directory.CreateDirectory(logFolder);
- }
- //logFolder = Directory.GetCurrentDirectory();返回是Revit目录
- var logFile = Path.Combine(logFolder, DateTime.Now.ToString("yyyy-MM-dd") + ".log");
-
- var fs = File.Exists(logFile)
- ? new FileStream(logFile, FileMode.Append, FileAccess.Write)
- : new FileStream(logFile, FileMode.Create, FileAccess.Write);
-
- using StreamWriter sw = new(fs);
- sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss-ffff : ") + strLog);
- sw.Close();
- fs.Close();
- }
public static void WriteLog(string strLog, string logFolder = default)
{
try
@@ -116,7 +56,7 @@ namespace Szmedi.Toolkit.Assists
string filePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) +
$"\\{fileName}.csv";
File.WriteAllText(filePath, message, Encoding.UTF8);
- //System.Diagnostics.Process.Start(filePath);
+ System.Diagnostics.Process.Start(filePath);
}
}
}
diff --git a/Szmedi.Toolkit.Revit/Assists/WinDialogAssists.cs b/Szmedi.Toolkit.Revit/Assists/WinDialogAssists.cs
index d21b482..5134b20 100644
--- a/Szmedi.Toolkit.Revit/Assists/WinDialogAssists.cs
+++ b/Szmedi.Toolkit.Revit/Assists/WinDialogAssists.cs
@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
+using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
@@ -219,5 +216,21 @@ namespace Szmedi.Toolkit.Assists
};
window.Show();
}
+ public static void ShowDialog(params object[] viewModelParams)
+ where TWindow : Window, new()
+ where TViewModel : class
+ {
+ var window = new TWindow();
+ if (viewModelParams.Length == 0)
+ {
+ window.DataContext = Activator.CreateInstance(typeof(TViewModel));
+ }
+ else
+ {
+ window.DataContext = Activator.CreateInstance(typeof(TViewModel), viewModelParams);
+ }
+ _ = new WindowInteropHelper(window) { Owner = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle };
+ window.ShowDialog();
+ }
}
}
\ No newline at end of file
diff --git a/WPFUI.Test/App.xaml b/WPFUI.Test/App.xaml
index bb80d05..65d425c 100644
--- a/WPFUI.Test/App.xaml
+++ b/WPFUI.Test/App.xaml
@@ -8,7 +8,10 @@
-
+
diff --git a/WPFUI.Test/MainWindow.xaml b/WPFUI.Test/MainWindow.xaml
new file mode 100644
index 0000000..fa6b41b
--- /dev/null
+++ b/WPFUI.Test/MainWindow.xaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
diff --git a/WPFUI.Test/MainWindow.xaml.cs b/WPFUI.Test/MainWindow.xaml.cs
new file mode 100644
index 0000000..dd03084
--- /dev/null
+++ b/WPFUI.Test/MainWindow.xaml.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+using WPFUI.Test.Web;
+
+namespace WPFUI.Test
+{
+ ///
+ /// MainWindow.xaml 的交互逻辑
+ ///
+ public partial class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ InitializeComponent();
+ }
+
+ private async void Button_Click(object sender, RoutedEventArgs e)
+ {
+ AuthenticationService service = new AuthenticationService();
+ await service.LoginAsync(UserNameBox.Text, PasswordBox.Password);
+ }
+ }
+}
diff --git a/WebUITest/App.xaml b/WebUITest/App.xaml
new file mode 100644
index 0000000..ff56ea0
--- /dev/null
+++ b/WebUITest/App.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/WebUITest/App.xaml.cs b/WebUITest/App.xaml.cs
new file mode 100644
index 0000000..d94725d
--- /dev/null
+++ b/WebUITest/App.xaml.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace WebUITest
+{
+ ///
+ /// App.xaml 的交互逻辑
+ ///
+ public partial class App : Application
+ {
+ }
+}
diff --git a/WebUITest/Command.cs b/WebUITest/Command.cs
new file mode 100644
index 0000000..99ccd17
--- /dev/null
+++ b/WebUITest/Command.cs
@@ -0,0 +1,28 @@
+using Autodesk.Revit.UI;
+
+using System;
+
+namespace WebUITest
+{
+ [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
+ public class Command : IExternalCommand
+ {
+ public Result Execute(ExternalCommandData commandData, ref string message, Autodesk.Revit.DB.ElementSet elements)
+ {
+ try
+ {
+ var uiApp = commandData.Application;
+ var uiDoc = uiApp.ActiveUIDocument;
+ var wnd = new WebUIWindow(uiDoc);
+ wnd.Owner = System.Windows.Application.Current?.MainWindow;
+ wnd.ShowDialog();
+ return Result.Succeeded;
+ }
+ catch (Exception ex)
+ {
+ message = ex.Message;
+ return Result.Failed;
+ }
+ }
+ }
+}
diff --git a/WebUITest/Html/index.html b/WebUITest/Html/index.html
new file mode 100644
index 0000000..d26fc87
--- /dev/null
+++ b/WebUITest/Html/index.html
@@ -0,0 +1,44 @@
+
+
+
+
+ AntD + React
+
+
+
+
+
+
+
+
+
+
diff --git a/WebUITest/MainWindow.xaml b/WebUITest/MainWindow.xaml
new file mode 100644
index 0000000..3696882
--- /dev/null
+++ b/WebUITest/MainWindow.xaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebUITest/MainWindow.xaml.cs b/WebUITest/MainWindow.xaml.cs
new file mode 100644
index 0000000..00e5067
--- /dev/null
+++ b/WebUITest/MainWindow.xaml.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+namespace WebUITest
+{
+ ///
+ /// MainWindow.xaml 的交互逻辑
+ ///
+ public partial class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/WebUITest/Properties/AssemblyInfo.cs b/WebUITest/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..3abb6eb
--- /dev/null
+++ b/WebUITest/Properties/AssemblyInfo.cs
@@ -0,0 +1,33 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("WebUITest")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("WebUITest")]
+[assembly: AssemblyCopyright("Copyright © 2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("be44de16-ea42-424a-b16f-07c46e157618")]
+
+// 程序集的版本信息由下列四个值组成:
+//
+// 主版本
+// 次版本
+// 生成号
+// 修订号
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/WebUITest/WebUITest.csproj b/WebUITest/WebUITest.csproj
new file mode 100644
index 0000000..94e2f0f
--- /dev/null
+++ b/WebUITest/WebUITest.csproj
@@ -0,0 +1,34 @@
+
+
+ net472
+ WinExe
+ x64
+
+ false
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+ Code
+
+
+
\ No newline at end of file
diff --git a/WebUITest/WebUIWindow.xaml b/WebUITest/WebUIWindow.xaml
new file mode 100644
index 0000000..e5a993c
--- /dev/null
+++ b/WebUITest/WebUIWindow.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/WebUITest/WebUIWindow.xaml.cs b/WebUITest/WebUIWindow.xaml.cs
new file mode 100644
index 0000000..23d4b25
--- /dev/null
+++ b/WebUITest/WebUIWindow.xaml.cs
@@ -0,0 +1,142 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Windows;
+using System.Windows.Shell;
+
+using Autodesk.Revit.DB;
+using Autodesk.Revit.UI;
+
+using Microsoft.Web.WebView2.Core;
+
+namespace WebUITest
+{
+ public partial class WebUIWindow : Window
+ {
+ private UIDocument _uiDoc;
+
+ public WebUIWindow(UIDocument uiDoc)
+ {
+ InitializeComponent();
+ _uiDoc = uiDoc;
+ InitializeWebView();
+ }
+
+ private async void InitializeWebView()
+ {
+ string userDataFolder = Path.Combine(Path.GetTempPath(), "WebUI_Revit_UserData");
+ var env = await CoreWebView2Environment.CreateAsync(null, userDataFolder);
+ await WebView.EnsureCoreWebView2Async(env);
+ WebView.CoreWebView2.WebMessageReceived += WebMessageReceived;
+
+ // 获取嵌入的 HTML 内容
+ string htmlContent = GetEmbeddedResource("WebUI_WallBatchTag.Html.index.html");
+ if (string.IsNullOrEmpty(htmlContent))
+ {
+ MessageBox.Show("HTML 内容加载失败!", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ return;
+ }
+ // 加载嵌入的 CSS 和 JS 文件
+ //var cssContent = GetEmbeddedResource("WebUI_WallBatchTag.Resources.antd.min.css");
+ //var jsContent = GetEmbeddedResource("WebUI_WallBatchTag.Resources.antd.min.js");
+
+ //// 将 HTML 写入临时文件并加载
+ //string tempPath = Path.Combine(Path.GetTempPath(), "index.html");
+ //File.WriteAllText(tempPath, htmlContent);
+ //WebView.Source = new Uri(tempPath);
+
+
+ // 在 HTML 内容中注入 CSS 和 JS
+ //htmlContent = InjectCssAndJs(htmlContent, cssContent, jsContent);
+
+ // 尝试加载 HTML
+ try
+ {
+ WebView.NavigateToString(htmlContent);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"HTML 加载失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+ //private string InjectCssAndJs(string htmlContent, string cssContent, string jsContent)
+ //{
+ // // 在 HTML 内容中找到 标签,插入 CSS
+ // var cssInjected = $"";
+ // var jsInjected = $"";
+
+ // // 确保注入 CSS
+ // int headEndIndex = htmlContent.IndexOf("");
+ // if (headEndIndex >= 0)
+ // {
+ // htmlContent = htmlContent.Insert(headEndIndex, cssInjected);
+ // }
+
+ // // 确保注入 JS,通常放在
或
+ var jsTag = $"";
+ htmlContent = htmlContent.Replace("", $"{jsTag} 前
+ // int bodyEndIndex = htmlContent.IndexOf("");
+ // if (bodyEndIndex >= 0)
+ // {
+ // htmlContent = htmlContent.Insert(bodyEndIndex, jsInjected);
+ // }
+
+ // return htmlContent;
+ //}
+
+ private string InjectCssAndJs(string htmlContent, string cssContent, string jsContent)
+ {
+ // 注入 CSS 到
+ var cssTag = $"";
+ htmlContent = htmlContent.Replace("", $"{cssTag}");
+
+ // 注入 JS 到
");
+
+ return htmlContent;
+ }
+ private string GetEmbeddedResource(string resourceName)
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ using (Stream stream = assembly.GetManifestResourceStream(resourceName))
+ using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
+ {
+ return reader.ReadToEnd();
+ }
+ }
+
+ private void WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
+ {
+ string json = e.WebMessageAsJson;
+ dynamic data = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
+ string newTag = data.newTag;
+ ApplyTagToWalls(newTag);
+ }
+
+ private void ApplyTagToWalls(string tagValue)
+ {
+ // 执行墙标记批量修改操作
+ Document doc = _uiDoc.Document;
+ var walls = new FilteredElementCollector(doc)
+ .OfCategory(BuiltInCategory.OST_Walls)
+ .WhereElementIsNotElementType()
+ .Cast()
+ .ToList();
+
+ using (Transaction tx = new Transaction(doc, "批量修改墙标记"))
+ {
+ tx.Start();
+ foreach (var wall in walls)
+ {
+ Parameter p = wall.get_Parameter(BuiltInParameter.ALL_MODEL_MARK);
+ if (p != null && !p.IsReadOnly)
+ p.Set(tagValue);
+ }
+ tx.Commit();
+ }
+
+ MessageBox.Show($"共修改 {walls.Count} 面墙的标记为 '{tagValue}'。", "完成", MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+ }
+}
diff --git a/WebUITest/background.avif b/WebUITest/background.avif
new file mode 100644
index 0000000..0328316
Binary files /dev/null and b/WebUITest/background.avif differ