惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

TaoSecurity Blog
TaoSecurity Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
F
Fortinet All Blogs
Cisco Talos Blog
Cisco Talos Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Secure Thoughts
美团技术团队
雷峰网
雷峰网
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
Engineering at Meta
Engineering at Meta
人人都是产品经理
人人都是产品经理
月光博客
月光博客
T
Tor Project blog
P
Privacy & Cybersecurity Law Blog
Recorded Future
Recorded Future
I
Intezer
博客园 - 【当耐特】
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
GbyAI
GbyAI
罗磊的独立博客
V
V2EX
Google DeepMind News
Google DeepMind News
D
DataBreaches.Net
Last Week in AI
Last Week in AI
T
Tailwind CSS Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
A
About on SuperTechFans
Scott Helme
Scott Helme
Vercel News
Vercel News
Spread Privacy
Spread Privacy
T
Threat Research - Cisco Blogs
Recent Announcements
Recent Announcements
Hacker News: Ask HN
Hacker News: Ask HN
C
CERT Recently Published Vulnerability Notes
G
Google Developers Blog
B
Blog
博客园 - 叶小钗
WordPress大学
WordPress大学
博客园 - 聂微东
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Jina AI
Jina AI
IT之家
IT之家
C
Cybersecurity and Infrastructure Security Agency CISA
P
Palo Alto Networks Blog
小众软件
小众软件
博客园 - Franky
Microsoft Azure Blog
Microsoft Azure Blog
AWS News Blog
AWS News Blog

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
第八章:IFoxCAD开发实战案例教程
我才是银古 · 2026-06-20 · via 博客园 - 我才是银古

第八章:IFoxCAD开发实战案例教程

8.1 项目概述

本章将通过多个完整的实战案例,综合运用前面所学的IFoxCAD知识,展示如何开发实用的CAD插件功能。

8.2 案例一:图框生成器

8.2.1 需求分析

开发一个图框生成器,实现以下功能:

  1. 支持A4、A3、A2、A1、A0多种图幅
  2. 自动创建图框边界、标题栏
  3. 支持填写图纸信息
  4. 生成到指定位置

8.2.2 完整实现

public class DrawingFrameGenerator
{
    // 图幅尺寸定义
    private static readonly Dictionary<string, (double Width, double Height)> PaperSizes = new()
    {
        { "A4", (297, 210) },
        { "A3", (420, 297) },
        { "A2", (594, 420) },
        { "A1", (841, 594) },
        { "A0", (1189, 841) }
    };
    
    // 边框参数
    private const double OuterMargin = 5;
    private const double InnerMargin = 25;
    private const double TitleBarHeight = 40;
    private const double TitleBarWidth = 180;
    
    [CommandMethod("CreateFrame")]
    public void CreateDrawingFrame()
    {
        using var tr = new DBTrans();
        var ed = tr.Editor;
        
        // 选择图幅
        var options = new PromptKeywordOptions("\n选择图幅 [A4/A3/A2/A1/A0]:");
        options.Keywords.Add("A4");
        options.Keywords.Add("A3");
        options.Keywords.Add("A2");
        options.Keywords.Add("A1");
        options.Keywords.Add("A0");
        options.Keywords.Default = "A4";
        options.AllowNone = true;
        
        var kwResult = ed?.GetKeywords(options);
        if (kwResult?.Status != PromptStatus.OK && kwResult?.Status != PromptStatus.None)
            return;
        
        string paperSize = kwResult?.Status == PromptStatus.None ? "A4" : kwResult?.StringResult ?? "A4";
        
        // 获取插入点
        var ptResult = ed?.GetPoint("\n指定插入点:");
        if (ptResult?.Status != PromptStatus.OK) return;
        Point3d insertPoint = ptResult.Value;
        
        // 获取图纸信息
        var titleResult = ed?.GetString("\n输入图纸名称:");
        string title = titleResult?.Status == PromptStatus.OK ? titleResult.StringResult : "未命名";
        
        var numberResult = ed?.GetString("\n输入图号:");
        string number = numberResult?.Status == PromptStatus.OK ? numberResult.StringResult : "";
        
        // 创建图框
        CreateFrame(tr, insertPoint, paperSize, title, number);
        
        ed?.WriteMessage($"\n已创建 {paperSize} 图框");
    }
    
    private void CreateFrame(DBTrans tr, Point3d insertPoint, string paperSize, 
        string title, string number)
    {
        var (width, height) = PaperSizes[paperSize];
        
        // 确保图框图层存在
        if (!tr.LayerTable.Has("图框"))
        {
            tr.LayerTable.Add("图框", layer =>
            {
                layer.Color = Color.FromColorIndex(ColorMethod.ByAci, 7);
            });
        }
        
        // 创建外边框
        var outerFrame = new Polyline();
        outerFrame.AddVertexAt(0, new Point2d(insertPoint.X, insertPoint.Y), 0, 0, 0);
        outerFrame.AddVertexAt(1, new Point2d(insertPoint.X + width, insertPoint.Y), 0, 0, 0);
        outerFrame.AddVertexAt(2, new Point2d(insertPoint.X + width, insertPoint.Y + height), 0, 0, 0);
        outerFrame.AddVertexAt(3, new Point2d(insertPoint.X, insertPoint.Y + height), 0, 0, 0);
        outerFrame.Closed = true;
        outerFrame.Layer = "图框";
        outerFrame.LineWeight = LineWeight.LineWeight070;
        tr.CurrentSpace.AddEntity(outerFrame);
        
        // 创建内边框
        double innerLeft = insertPoint.X + InnerMargin;
        double innerBottom = insertPoint.Y + OuterMargin;
        double innerRight = insertPoint.X + width - OuterMargin;
        double innerTop = insertPoint.Y + height - OuterMargin;
        
        var innerFrame = new Polyline();
        innerFrame.AddVertexAt(0, new Point2d(innerLeft, innerBottom), 0, 0, 0);
        innerFrame.AddVertexAt(1, new Point2d(innerRight, innerBottom), 0, 0, 0);
        innerFrame.AddVertexAt(2, new Point2d(innerRight, innerTop), 0, 0, 0);
        innerFrame.AddVertexAt(3, new Point2d(innerLeft, innerTop), 0, 0, 0);
        innerFrame.Closed = true;
        innerFrame.Layer = "图框";
        innerFrame.LineWeight = LineWeight.LineWeight030;
        tr.CurrentSpace.AddEntity(innerFrame);
        
        // 创建标题栏
        CreateTitleBar(tr, insertPoint, width, height, title, number);
    }
    
    private void CreateTitleBar(DBTrans tr, Point3d insertPoint, double width, double height,
        string title, string number)
    {
        double titleBarLeft = insertPoint.X + width - OuterMargin - TitleBarWidth;
        double titleBarBottom = insertPoint.Y + OuterMargin;
        double titleBarRight = insertPoint.X + width - OuterMargin;
        double titleBarTop = insertPoint.Y + OuterMargin + TitleBarHeight;
        
        // 标题栏边框
        var titleBarFrame = new Polyline();
        titleBarFrame.AddVertexAt(0, new Point2d(titleBarLeft, titleBarBottom), 0, 0, 0);
        titleBarFrame.AddVertexAt(1, new Point2d(titleBarRight, titleBarBottom), 0, 0, 0);
        titleBarFrame.AddVertexAt(2, new Point2d(titleBarRight, titleBarTop), 0, 0, 0);
        titleBarFrame.AddVertexAt(3, new Point2d(titleBarLeft, titleBarTop), 0, 0, 0);
        titleBarFrame.Closed = true;
        titleBarFrame.Layer = "图框";
        tr.CurrentSpace.AddEntity(titleBarFrame);
        
        // 分隔线
        double midHeight = titleBarBottom + TitleBarHeight / 2;
        var divLine = new Line(
            new Point3d(titleBarLeft, midHeight, 0),
            new Point3d(titleBarRight, midHeight, 0));
        divLine.Layer = "图框";
        tr.CurrentSpace.AddEntity(divLine);
        
        // 垂直分隔
        double col1 = titleBarLeft + 60;
        var vLine = new Line(
            new Point3d(col1, titleBarBottom, 0),
            new Point3d(col1, titleBarTop, 0));
        vLine.Layer = "图框";
        tr.CurrentSpace.AddEntity(vLine);
        
        // 添加文字
        // 图名标签
        var labelTitle = new DBText();
        labelTitle.Position = new Point3d(titleBarLeft + 5, midHeight + 5, 0);
        labelTitle.TextString = "图名";
        labelTitle.Height = 3;
        labelTitle.Layer = "图框";
        tr.CurrentSpace.AddEntity(labelTitle);
        
        // 图名内容
        var contentTitle = new DBText();
        contentTitle.Position = new Point3d(col1 + 5, midHeight + 10, 0);
        contentTitle.TextString = title;
        contentTitle.Height = 5;
        contentTitle.Layer = "图框";
        tr.CurrentSpace.AddEntity(contentTitle);
        
        // 图号标签
        var labelNumber = new DBText();
        labelNumber.Position = new Point3d(titleBarLeft + 5, titleBarBottom + 5, 0);
        labelNumber.TextString = "图号";
        labelNumber.Height = 3;
        labelNumber.Layer = "图框";
        tr.CurrentSpace.AddEntity(labelNumber);
        
        // 图号内容
        var contentNumber = new DBText();
        contentNumber.Position = new Point3d(col1 + 5, titleBarBottom + 10, 0);
        contentNumber.TextString = number;
        contentNumber.Height = 5;
        contentNumber.Layer = "图框";
        tr.CurrentSpace.AddEntity(contentNumber);
    }
}

8.3 案例二:图元统计工具

8.3.1 需求分析

开发一个图元统计工具,实现以下功能:

  1. 统计当前图纸中各类型图元的数量
  2. 按图层分类统计
  3. 统计曲线的总长度
  4. 统计闭合区域的面积
  5. 输出详细的统计报告

8.3.2 完整实现

public class EntityStatisticsTool
{
    [CommandMethod("EntityStats")]
    public void CollectStatistics()
    {
        using var tr = new DBTrans();
        var ed = tr.Editor;
        
        // 收集统计数据
        var stats = new StatisticsData();
        
        // 遍历模型空间
        foreach (ObjectId id in tr.ModelSpace)
        {
            var ent = tr.GetObject<Entity>(id);
            if (ent == null) continue;
            
            // 按类型统计
            string typeName = ent.GetType().Name;
            if (!stats.TypeCounts.ContainsKey(typeName))
                stats.TypeCounts[typeName] = 0;
            stats.TypeCounts[typeName]++;
            
            // 按图层统计
            string layer = ent.Layer;
            if (!stats.LayerCounts.ContainsKey(layer))
                stats.LayerCounts[layer] = 0;
            stats.LayerCounts[layer]++;
            
            // 统计曲线长度
            if (ent is Curve curve)
            {
                try
                {
                    stats.TotalLength += curve.GetLength();
                }
                catch { }
            }
            
            // 统计闭合区域面积
            if (ent is Polyline pline && pline.Closed)
            {
                stats.ClosedAreaCount++;
                stats.TotalArea += Math.Abs(pline.Area);
            }
            else if (ent is Circle circle)
            {
                stats.ClosedAreaCount++;
                stats.TotalArea += Math.PI * circle.Radius * circle.Radius;
            }
            
            stats.TotalCount++;
        }
        
        // 输出报告
        OutputReport(ed, stats);
    }
    
    private void OutputReport(Editor? ed, StatisticsData stats)
    {
        ed?.WriteMessage("\n");
        ed?.WriteMessage("\n╔══════════════════════════════════════════╗");
        ed?.WriteMessage("\n║           图元统计报告                   ║");
        ed?.WriteMessage("\n╠══════════════════════════════════════════╣");
        
        ed?.WriteMessage($"\n║ 图元总数:{stats.TotalCount,-30}║");
        ed?.WriteMessage($"\n║ 曲线总长度:{stats.TotalLength,-27:F2}║");
        ed?.WriteMessage($"\n║ 闭合区域数:{stats.ClosedAreaCount,-27}║");
        ed?.WriteMessage($"\n║ 总面积:{stats.TotalArea,-31:F2}║");
        
        ed?.WriteMessage("\n╠══════════════════════════════════════════╣");
        ed?.WriteMessage("\n║ 按类型统计                               ║");
        ed?.WriteMessage("\n╠──────────────────────────────────────────╣");
        
        foreach (var kvp in stats.TypeCounts.OrderByDescending(x => x.Value))
        {
            ed?.WriteMessage($"\n║ {kvp.Key,-20}{kvp.Value,20} ║");
        }
        
        ed?.WriteMessage("\n╠══════════════════════════════════════════╣");
        ed?.WriteMessage("\n║ 按图层统计                               ║");
        ed?.WriteMessage("\n╠──────────────────────────────────────────╣");
        
        foreach (var kvp in stats.LayerCounts.OrderByDescending(x => x.Value))
        {
            ed?.WriteMessage($"\n║ {kvp.Key,-20}{kvp.Value,20} ║");
        }
        
        ed?.WriteMessage("\n╚══════════════════════════════════════════╝");
    }
    
    private class StatisticsData
    {
        public int TotalCount { get; set; }
        public double TotalLength { get; set; }
        public int ClosedAreaCount { get; set; }
        public double TotalArea { get; set; }
        public Dictionary<string, int> TypeCounts { get; } = new();
        public Dictionary<string, int> LayerCounts { get; } = new();
    }
}

8.4 案例三:批量图层管理

8.4.1 需求分析

开发一个批量图层管理工具,实现以下功能:

  1. 批量创建图层
  2. 批量修改图层属性
  3. 合并图层
  4. 清理未使用图层
  5. 导出/导入图层配置

8.4.2 完整实现

public class LayerManagementTool
{
    // 预定义的图层配置
    private static readonly LayerConfig[] StandardLayers = new[]
    {
        new LayerConfig("墙体", 1, "Continuous", LineWeight.LineWeight030),
        new LayerConfig("门窗", 2, "Continuous", LineWeight.LineWeight018),
        new LayerConfig("家具", 3, "Continuous", LineWeight.LineWeight013),
        new LayerConfig("标注", 4, "Continuous", LineWeight.LineWeight009),
        new LayerConfig("文字", 5, "Continuous", LineWeight.LineWeight009),
        new LayerConfig("辅助线", 8, "DASHED", LineWeight.LineWeight000),
        new LayerConfig("中心线", 1, "CENTER", LineWeight.LineWeight000),
        new LayerConfig("设备", 6, "Continuous", LineWeight.LineWeight018)
    };
    
    [CommandMethod("CreateStdLayers")]
    public void CreateStandardLayers()
    {
        using var tr = new DBTrans();
        var ed = tr.Editor;
        
        int created = 0;
        foreach (var config in StandardLayers)
        {
            if (!tr.LayerTable.Has(config.Name))
            {
                // 确保线型存在
                EnsureLinetype(tr, config.Linetype);
                
                tr.LayerTable.Add(config.Name, layer =>
                {
                    layer.Color = Color.FromColorIndex(ColorMethod.ByAci, config.Color);
                    layer.LinetypeObjectId = tr.LinetypeTable[config.Linetype];
                    layer.LineWeight = config.LineWeight;
                });
                created++;
            }
        }
        
        ed?.WriteMessage($"\n创建了 {created} 个标准图层");
    }
    
    [CommandMethod("MergeLayers")]
    public void MergeLayers()
    {
        using var tr = new DBTrans();
        var ed = tr.Editor;
        
        // 获取源图层
        var sourceResult = ed?.GetString("\n输入源图层名:");
        if (sourceResult?.Status != PromptStatus.OK) return;
        string sourceLayer = sourceResult.StringResult;
        
        if (!tr.LayerTable.Has(sourceLayer))
        {
            ed?.WriteMessage($"\n图层 {sourceLayer} 不存在!");
            return;
        }
        
        // 获取目标图层
        var targetResult = ed?.GetString("\n输入目标图层名:");
        if (targetResult?.Status != PromptStatus.OK) return;
        string targetLayer = targetResult.StringResult;
        
        // 确保目标图层存在
        if (!tr.LayerTable.Has(targetLayer))
        {
            tr.LayerTable.Add(targetLayer);
        }
        
        // 移动图元
        var filter = OpFilter.Build(e => e.Dxf(8) == sourceLayer);
        var result = ed?.SelectAll(filter);
        
        int count = 0;
        if (result?.Status == PromptStatus.OK)
        {
            foreach (SelectedObject obj in result.Value)
            {
                if (obj == null) continue;
                
                var ent = tr.GetObject<Entity>(obj.ObjectId, OpenMode.ForWrite);
                if (ent != null)
                {
                    ent.Layer = targetLayer;
                    count++;
                }
            }
        }
        
        // 删除源图层
        tr.LayerTable.Remove(sourceLayer);
        
        ed?.WriteMessage($"\n已将 {count} 个图元从 {sourceLayer} 移动到 {targetLayer}");
        ed?.WriteMessage($"\n图层 {sourceLayer} 已删除");
    }
    
    [CommandMethod("CleanUnusedLayers")]
    public void CleanUnusedLayers()
    {
        using var tr = new DBTrans();
        var ed = tr.Editor;
        
        // 生成使用数据
        tr.LayerTable.CurrentSymbolTable.GenerateUsageData();
        
        // 查找未使用的图层
        var unusedLayers = tr.LayerTable.GetRecords()
            .Where(l => !l.IsUsed &&
                       !SymbolUtilityServices.IsLayerZeroName(l.Name) &&
                       !SymbolUtilityServices.IsLayerDefpointsName(l.Name))
            .Select(l => l.Name)
            .ToList();
        
        if (unusedLayers.Count == 0)
        {
            ed?.WriteMessage("\n没有找到未使用的图层");
            return;
        }
        
        ed?.WriteMessage($"\n找到 {unusedLayers.Count} 个未使用的图层:");
        foreach (var name in unusedLayers)
        {
            ed?.WriteMessage($"\n  - {name}");
        }
        
        var confirmResult = ed?.GetString("\n是否删除这些图层?[是(Y)/否(N)] <N>:");
        if (confirmResult?.StringResult.ToUpper() != "Y") return;
        
        // 删除图层
        foreach (var name in unusedLayers)
        {
            tr.LayerTable.Remove(name);
        }
        
        ed?.WriteMessage($"\n已删除 {unusedLayers.Count} 个图层");
    }
    
    [CommandMethod("ExportLayerConfig")]
    public void ExportLayerConfiguration()
    {
        using var tr = new DBTrans();
        var ed = tr.Editor;
        
        // 收集图层配置
        var configs = tr.LayerTable.GetRecords()
            .Select(l => new
            {
                Name = l.Name,
                Color = l.Color.ColorIndex,
                Linetype = l.LinetypeObjectId.IsNull ? "Continuous" : 
                    tr.LinetypeTable.GetRecord(l.LinetypeObjectId)?.Name ?? "Continuous",
                LineWeight = l.LineWeight.ToString(),
                IsOff = l.IsOff,
                IsFrozen = l.IsFrozen
            })
            .ToList();
        
        // 导出为JSON
        string json = System.Text.Json.JsonSerializer.Serialize(configs, 
            new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
        
        // 获取保存路径
        var dialog = new System.Windows.Forms.SaveFileDialog
        {
            Filter = "JSON文件|*.json",
            DefaultExt = "json",
            FileName = "layer_config.json"
        };
        
        if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
        {
            System.IO.File.WriteAllText(dialog.FileName, json);
            ed?.WriteMessage($"\n图层配置已导出到:{dialog.FileName}");
        }
    }
    
    private void EnsureLinetype(DBTrans tr, string linetypeName)
    {
        if (!tr.LinetypeTable.Has(linetypeName))
        {
            try
            {
                tr.Database.LoadLineTypeFile(linetypeName, "acad.lin");
            }
            catch { }
        }
    }
    
    private record LayerConfig(string Name, short Color, string Linetype, LineWeight LineWeight);
}

8.5 案例四:设备标注工具

8.5.1 需求分析

开发一个设备标注工具,实现以下功能:

  1. 创建设备符号块
  2. 插入设备并填写信息
  3. 生成设备表
  4. 更新设备编号

8.5.2 完整实现

public class EquipmentLabelTool
{
    private const string BlockName = "设备标注";
    private const string AppName = "EquipmentData";
    
    [CommandMethod("CreateEquipBlock")]
    public void CreateEquipmentBlock()
    {
        using var tr = new DBTrans();
        
        if (tr.BlockTable.Has(BlockName))
        {
            tr.Editor?.WriteMessage("\n设备标注块已存在");
            return;
        }
        
        tr.BlockTable.Add(BlockName, btr =>
        {
            // 创建符号
            var circle = new Circle(Point3d.Origin, Vector3d.ZAxis, 10);
            btr.AppendEntity(circle);
            tr.Transaction.AddNewlyCreatedDBObject(circle, true);
            
            var line1 = new Line(new Point3d(-7, -7, 0), new Point3d(7, 7, 0));
            btr.AppendEntity(line1);
            tr.Transaction.AddNewlyCreatedDBObject(line1, true);
            
            var line2 = new Line(new Point3d(-7, 7, 0), new Point3d(7, -7, 0));
            btr.AppendEntity(line2);
            tr.Transaction.AddNewlyCreatedDBObject(line2, true);
            
            // 创建属性
            var attName = new AttributeDefinition();
            attName.Position = new Point3d(0, 15, 0);
            attName.Tag = "NAME";
            attName.Prompt = "设备名称";
            attName.TextString = "";
            attName.Height = 5;
            attName.HorizontalMode = TextHorizontalMode.TextCenter;
            attName.AlignmentPoint = new Point3d(0, 15, 0);
            btr.AppendEntity(attName);
            tr.Transaction.AddNewlyCreatedDBObject(attName, true);
            
            var attCode = new AttributeDefinition();
            attCode.Position = new Point3d(0, 0, 0);
            attCode.Tag = "CODE";
            attCode.Prompt = "设备编号";
            attCode.TextString = "";
            attCode.Height = 4;
            attCode.HorizontalMode = TextHorizontalMode.TextCenter;
            attCode.VerticalMode = TextVerticalMode.TextVerticalMid;
            attCode.AlignmentPoint = new Point3d(0, 0, 0);
            btr.AppendEntity(attCode);
            tr.Transaction.AddNewlyCreatedDBObject(attCode, true);
        });
        
        // 注册应用程序
        if (!tr.RegAppTable.Has(AppName))
        {
            tr.RegAppTable.Add(AppName);
        }
        
        tr.Editor?.WriteMessage("\n设备标注块创建完成");
    }
    
    [CommandMethod("InsertEquip")]
    public void InsertEquipment()
    {
        using var tr = new DBTrans();
        var ed = tr.Editor;
        
        // 确保块存在
        if (!tr.BlockTable.Has(BlockName))
        {
            ed?.WriteMessage("\n请先运行 CreateEquipBlock 命令创建块定义");
            return;
        }
        
        // 获取插入点
        var ptResult = ed?.GetPoint("\n指定设备位置:");
        if (ptResult?.Status != PromptStatus.OK) return;
        
        // 获取设备信息
        var nameResult = ed?.GetString("\n输入设备名称:");
        string name = nameResult?.Status == PromptStatus.OK ? nameResult.StringResult : "";
        
        var codeResult = ed?.GetString("\n输入设备编号:");
        string code = codeResult?.Status == PromptStatus.OK ? codeResult.StringResult : "";
        
        // 创建块参照
        ObjectId blockId = tr.BlockTable[BlockName];
        var blockRef = new BlockReference(ptResult.Value, blockId);
        ObjectId blockRefId = tr.CurrentSpace.AddEntity(blockRef);
        
        // 添加属性
        var btr = tr.BlockTable.GetRecord(blockId);
        if (btr != null)
        {
            foreach (ObjectId id in btr)
            {
                var attDef = tr.GetObject<AttributeDefinition>(id);
                if (attDef != null && !attDef.Constant)
                {
                    var attRef = new AttributeReference();
                    attRef.SetAttributeFromBlock(attDef, blockRef.BlockTransform);
                    
                    if (attDef.Tag == "NAME")
                        attRef.TextString = name;
                    else if (attDef.Tag == "CODE")
                        attRef.TextString = code;
                    
                    using (blockRef.ForWrite())
                    {
                        blockRef.AttributeCollection.AppendAttribute(attRef);
                    }
                    tr.Transaction.AddNewlyCreatedDBObject(attRef, true);
                }
            }
        }
        
        // 添加扩展数据
        var xdata = new XDataList(AppName);
        xdata.Add(DxfCode.ExtendedDataAsciiString, "EquipmentType");
        xdata.Add(DxfCode.ExtendedDataAsciiString, name);
        xdata.Add(DxfCode.ExtendedDataAsciiString, "EquipmentCode");
        xdata.Add(DxfCode.ExtendedDataAsciiString, code);
        
        using (blockRef.ForWrite())
        {
            blockRef.XData = xdata.ToResultBuffer();
        }
        
        ed?.WriteMessage($"\n已插入设备:{name} ({code})");
    }
    
    [CommandMethod("GenerateEquipTable")]
    public void GenerateEquipmentTable()
    {
        using var tr = new DBTrans();
        var ed = tr.Editor;
        
        // 选择所有设备
        var filter = OpFilter.Build(e => 
            e.Dxf(0) == "INSERT" & e.Dxf(2) == BlockName);
        var result = ed?.SelectAll(filter);
        
        if (result?.Status != PromptStatus.OK || result.Value.Count == 0)
        {
            ed?.WriteMessage("\n未找到设备标注");
            return;
        }
        
        // 收集设备信息
        var equipments = new List<(string Name, string Code, Point3d Position)>();
        
        foreach (SelectedObject obj in result.Value)
        {
            if (obj == null) continue;
            
            var blockRef = tr.GetObject<BlockReference>(obj.ObjectId);
            if (blockRef == null) continue;
            
            string name = "", code = "";
            foreach (ObjectId attId in blockRef.AttributeCollection)
            {
                var attRef = tr.GetObject<AttributeReference>(attId);
                if (attRef != null)
                {
                    if (attRef.Tag == "NAME") name = attRef.TextString;
                    else if (attRef.Tag == "CODE") code = attRef.TextString;
                }
            }
            
            equipments.Add((name, code, blockRef.Position));
        }
        
        // 获取表格位置
        var tableResult = ed?.GetPoint("\n指定设备表位置:");
        if (tableResult?.Status != PromptStatus.OK) return;
        
        // 创建表格
        CreateEquipmentTable(tr, tableResult.Value, equipments);
        
        ed?.WriteMessage($"\n已生成包含 {equipments.Count} 个设备的设备表");
    }
    
    private void CreateEquipmentTable(DBTrans tr, Point3d position,
        List<(string Name, string Code, Point3d Position)> equipments)
    {
        double rowHeight = 8;
        double colWidth1 = 30;  // 序号列
        double colWidth2 = 60;  // 名称列
        double colWidth3 = 40;  // 编号列
        double totalWidth = colWidth1 + colWidth2 + colWidth3;
        
        // 表头
        double y = position.Y;
        
        // 标题
        var title = new DBText();
        title.Position = new Point3d(position.X + totalWidth / 2, y + 5, 0);
        title.TextString = "设备表";
        title.Height = 6;
        title.HorizontalMode = TextHorizontalMode.TextCenter;
        title.AlignmentPoint = new Point3d(position.X + totalWidth / 2, y + 5, 0);
        tr.CurrentSpace.AddEntity(title);
        
        y -= rowHeight;
        
        // 表头行
        DrawTableRow(tr, position.X, y, 
            new[] { ("序号", colWidth1), ("设备名称", colWidth2), ("设备编号", colWidth3) },
            rowHeight, true);
        
        // 数据行
        int index = 1;
        foreach (var equip in equipments.OrderBy(e => e.Code))
        {
            y -= rowHeight;
            DrawTableRow(tr, position.X, y,
                new[] { (index.ToString(), colWidth1), (equip.Name, colWidth2), (equip.Code, colWidth3) },
                rowHeight, false);
            index++;
        }
    }
    
    private void DrawTableRow(DBTrans tr, double x, double y,
        (string Text, double Width)[] cells, double height, bool isHeader)
    {
        double currentX = x;
        
        foreach (var (text, width) in cells)
        {
            // 绘制单元格边框
            var rect = new Polyline();
            rect.AddVertexAt(0, new Point2d(currentX, y), 0, 0, 0);
            rect.AddVertexAt(1, new Point2d(currentX + width, y), 0, 0, 0);
            rect.AddVertexAt(2, new Point2d(currentX + width, y + height), 0, 0, 0);
            rect.AddVertexAt(3, new Point2d(currentX, y + height), 0, 0, 0);
            rect.Closed = true;
            tr.CurrentSpace.AddEntity(rect);
            
            // 添加文字
            var cellText = new DBText();
            cellText.Position = new Point3d(currentX + width / 2, y + height / 2, 0);
            cellText.TextString = text;
            cellText.Height = isHeader ? 4 : 3;
            cellText.HorizontalMode = TextHorizontalMode.TextCenter;
            cellText.VerticalMode = TextVerticalMode.TextVerticalMid;
            cellText.AlignmentPoint = new Point3d(currentX + width / 2, y + height / 2, 0);
            tr.CurrentSpace.AddEntity(cellText);
            
            currentX += width;
        }
    }
}

8.6 案例五:批量文件处理

8.6.1 需求分析

开发一个批量文件处理工具,实现以下功能:

  1. 批量打开DWG文件
  2. 执行统一操作(如修改图层、清理等)
  3. 保存并生成处理报告

8.6.2 完整实现

public class BatchProcessor
{
    [CommandMethod("BatchProcess", CommandFlags.Session)]
    public void BatchProcessFiles()
    {
        // 选择文件夹
        var folderDialog = new System.Windows.Forms.FolderBrowserDialog
        {
            Description = "选择要处理的DWG文件所在文件夹"
        };
        
        if (folderDialog.ShowDialog() != System.Windows.Forms.DialogResult.OK)
            return;
        
        string folder = folderDialog.SelectedPath;
        string[] files = System.IO.Directory.GetFiles(folder, "*.dwg");
        
        if (files.Length == 0)
        {
            System.Windows.Forms.MessageBox.Show("未找到DWG文件!");
            return;
        }
        
        // 处理报告
        var report = new System.Text.StringBuilder();
        report.AppendLine("批量处理报告");
        report.AppendLine($"处理时间:{DateTime.Now}");
        report.AppendLine($"处理文件夹:{folder}");
        report.AppendLine($"文件数量:{files.Length}");
        report.AppendLine(new string('-', 50));
        
        int success = 0, failed = 0;
        
        foreach (string file in files)
        {
            string fileName = System.IO.Path.GetFileName(file);
            
            try
            {
                ProcessSingleFile(file);
                report.AppendLine($"[成功] {fileName}");
                success++;
            }
            catch (Exception ex)
            {
                report.AppendLine($"[失败] {fileName}: {ex.Message}");
                failed++;
            }
        }
        
        report.AppendLine(new string('-', 50));
        report.AppendLine($"处理完成:成功 {success},失败 {failed}");
        
        // 保存报告
        string reportPath = System.IO.Path.Combine(folder, $"处理报告_{DateTime.Now:yyyyMMdd_HHmmss}.txt");
        System.IO.File.WriteAllText(reportPath, report.ToString());
        
        System.Windows.Forms.MessageBox.Show(
            $"处理完成!\n成功:{success}\n失败:{failed}\n报告已保存到:{reportPath}");
    }
    
    private void ProcessSingleFile(string filePath)
    {
        using var tr = new DBTrans(filePath);
        
        tr.Task(() =>
        {
            // 操作1:统一图层颜色
            tr.LayerTable.ForEach(layer =>
            {
                if (layer.Name.StartsWith("Temp"))
                {
                    using (layer.ForWrite())
                    {
                        layer.Color = Color.FromColorIndex(ColorMethod.ByAci, 8);
                    }
                }
            });
            
            // 操作2:清理未使用的图层
            tr.LayerTable.CurrentSymbolTable.GenerateUsageData();
            var unusedLayers = tr.LayerTable.GetRecords()
                .Where(l => !l.IsUsed && 
                           !SymbolUtilityServices.IsLayerZeroName(l.Name) &&
                           !SymbolUtilityServices.IsLayerDefpointsName(l.Name))
                .Select(l => l.Name)
                .ToList();
            
            foreach (var name in unusedLayers)
            {
                tr.LayerTable.Remove(name);
            }
            
            // 操作3:更新文档属性
            var summaryInfo = tr.Database.SummaryInfo;
            var builder = new DatabaseSummaryInfoBuilder(summaryInfo);
            builder.Comments = $"批量处理于 {DateTime.Now}";
            tr.Database.SummaryInfo = builder.ToDatabaseSummaryInfo();
        });
        
        // 保存文件
        tr.Database.SaveAs(filePath, DwgVersion.Current);
    }
}

8.7 本章小结

本章通过五个完整的实战案例,综合运用了IFoxCAD的各项技术:

  1. 图框生成器:展示了如何创建复杂的图形结构,包括多段线、文字和图层管理
  2. 图元统计工具:展示了如何遍历和分析图元,收集统计信息
  3. 批量图层管理:展示了图层的创建、合并、清理和配置导出
  4. 设备标注工具:展示了块的创建、属性操作、扩展数据和表格生成
  5. 批量文件处理:展示了如何使用Session命令标记进行文件批量处理

通过这些案例的学习,读者应该能够:

  • 理解如何将IFoxCAD的各项功能组合使用
  • 掌握开发完整CAD插件功能的方法
  • 学会处理实际开发中的常见需求

希望这些案例能够帮助读者在实际项目中更好地应用IFoxCAD进行CAD二次开发。