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

推荐订阅源

Jina AI
Jina AI
V
Vulnerabilities – Threatpost
Security Latest
Security Latest
AI
AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
量子位
H
Help Net Security
Attack and Defense Labs
Attack and Defense Labs
The GitHub Blog
The GitHub Blog
L
LINUX DO - 最新话题
A
Arctic Wolf
博客园_首页
S
Securelist
S
Secure Thoughts
Google DeepMind News
Google DeepMind News
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
Tailwind CSS Blog
Apple Machine Learning Research
Apple Machine Learning Research
酷 壳 – CoolShell
酷 壳 – CoolShell
Stack Overflow Blog
Stack Overflow Blog
N
Netflix TechBlog - Medium
Cyberwarzone
Cyberwarzone
小众软件
小众软件
T
Threatpost
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Blog — PlanetScale
Blog — PlanetScale
N
News and Events Feed by Topic
NISL@THU
NISL@THU
Forbes - Security
Forbes - Security
博客园 - 聂微东
F
Fortinet All Blogs
Simon Willison's Weblog
Simon Willison's Weblog
H
Heimdal Security Blog
罗磊的独立博客
S
Security @ Cisco Blogs
B
Blog
T
Troy Hunt's Blog
Engineering at Meta
Engineering at Meta
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
The Hacker News
The Hacker News
The Last Watchdog
The Last Watchdog
Hacker News - Newest:
Hacker News - Newest: "LLM"
I
Intezer
T
Threat Research - Cisco Blogs
C
Cybersecurity and Infrastructure Security Agency CISA
The Cloudflare Blog
S
Schneier on Security
月光博客
月光博客
L
LINUX DO - 热门话题
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org

博客园 - 我才是银古

第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
插件开发基础
我才是银古 · 2026-06-19 · via 博客园 - 我才是银古

第四章:插件开发基础

4.1 LightCAD插件体系

4.1.1 插件概述

LightCAD的插件系统允许开发者扩展平台功能,插件可以:

  • 添加新的元素类型
  • 注册新的命令
  • 扩展用户界面
  • 自定义绘图和编辑行为
  • 集成外部系统

4.1.2 插件加载机制

LightCAD启动
      │
      ▼
扫描插件目录
      │
      ▼
加载程序集(DLL)
      │
      ▼
反射查找ILcPlugin实现
      │
      ▼
创建插件实例
      │
      ▼
依次调用生命周期方法

4.1.3 插件接口定义

public interface ILcPlugin
{
    /// <summary>
    /// 插件加载时调用
    /// </summary>
    void Loaded();
    
    /// <summary>
    /// UI初始化时调用
    /// </summary>
    void InitUI();
    
    /// <summary>
    /// 完成所有初始化后调用
    /// </summary>
    void Completed();
    
    /// <summary>
    /// 文档运行时初始化时调用
    /// </summary>
    void OnInitializeDocRt(DocumentRuntime docRt);
    
    /// <summary>
    /// 文档运行时销毁时调用
    /// </summary>
    void OnDisposeDocRt(DocumentRuntime docRt);
}

4.2 创建基础插件

4.2.1 最小插件示例

using LightCAD.Core;
using LightCAD.Runtime;

namespace MyFirstPlugin
{
    public class MyFirstPlugin : ILcPlugin
    {
        public void Loaded()
        {
            Console.WriteLine("MyFirstPlugin: Loaded");
        }
        
        public void InitUI()
        {
            Console.WriteLine("MyFirstPlugin: InitUI");
        }
        
        public void Completed()
        {
            Console.WriteLine("MyFirstPlugin: Completed");
        }
        
        public void OnInitializeDocRt(DocumentRuntime docRt)
        {
            Console.WriteLine($"MyFirstPlugin: Document initialized - {docRt.Document.Name}");
        }
        
        public void OnDisposeDocRt(DocumentRuntime docRt)
        {
            Console.WriteLine($"MyFirstPlugin: Document disposed - {docRt.Document.Name}");
        }
    }
}

4.2.2 项目配置

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  
  <ItemGroup>
    <Reference Include="LightCAD.Core">
      <HintPath>..\Libs\LightCAD.Core.dll</HintPath>
    </Reference>
    <Reference Include="LightCAD.Runtime">
      <HintPath>..\Libs\LightCAD.Runtime.dll</HintPath>
    </Reference>
  </ItemGroup>
</Project>

4.3 UI集成

4.3.1 TabItem结构

LightCAD使用Ribbon风格的选项卡界面,主要结构如下:

TabItem (选项卡)
├── Name           # 内部名称
├── Text           # 显示文本
├── ShortcutKey    # 快捷键
└── ButtonGroups   # 按钮组列表
    └── TabButtonGroup
        └── Buttons  # 按钮列表
            └── TabButton
                ├── Name        # 命令名称
                ├── Text        # 显示文本
                ├── Icon        # 图标
                ├── IsCommand   # 是否为命令
                ├── Width       # 宽度
                └── DropDowns   # 下拉菜单

4.3.2 创建选项卡

public static TabItem MyTabItem = new TabItem
{
    Name = "MyTab",
    Text = "我的工具",
    ShortcutKey = "ALT-M",
    ButtonGroups = new List<TabButtonGroup>
    {
        new TabButtonGroup
        {
            Buttons = new List<TabButton>
            {
                new TabButton
                {
                    Name = "MyCommand1",
                    Text = "功能一",
                    Icon = LoadIcon("icon1.png"),
                    IsCommand = true,
                },
                new TabButton
                {
                    Name = "MyCommand2",
                    Text = "功能二",
                    Icon = LoadIcon("icon2.png"),
                    IsCommand = true,
                    Width = 80,
                }
            }
        }
    }
};

// 在Completed()中注册
public void Completed()
{
    AppRuntime.UISystem.AddInitTabItems([MyTabItem]);
}

4.3.3 带下拉菜单的按钮

new TabButton
{
    Name = "DrawTools",
    Text = "绘图工具",
    Icon = Properties.Resources.DrawIcon,
    IsCommand = true,
    DropDowns = new List<TabButton>
    {
        new TabButton
        {
            Name = "DrawLine",
            Text = "画线",
            Icon = Properties.Resources.LineIcon,
            IsCommand = true,
        },
        new TabButton
        {
            Name = "DrawCircle",
            Text = "画圆",
            Icon = Properties.Resources.CircleIcon,
            IsCommand = true,
        },
        new TabButton
        {
            Name = "DrawRect",
            Text = "画矩形",
            Icon = Properties.Resources.RectIcon,
            IsCommand = true,
        },
    }
}

4.3.4 图标资源管理

使用Resources.resx管理图标:

  1. 在项目中创建Properties/Resources.resx
  2. 添加图像资源
  3. 在代码中引用:
// 自动生成的访问器
Icon = Properties.Resources.MyIcon

或者从文件加载:

private static System.Drawing.Image LoadIcon(string path)
{
    var assembly = Assembly.GetExecutingAssembly();
    using var stream = assembly.GetManifestResourceStream($"MyPlugin.Resources.{path}");
    return System.Drawing.Image.FromStream(stream);
}

4.4 命令开发

4.4.1 命令类定义

using LightCAD.Runtime;

namespace MyPlugin
{
    [CommandClass]
    public class MyCommands
    {
        // 命令方法将在这里定义
    }
}

4.4.2 基本命令

[CommandMethod(Name = "Hello", ShortCuts = "HI")]
public CommandResult HelloWorld(IDocumentEditor docEditor, string[] args)
{
    // 获取命令控制器
    var cmdCtrl = docEditor.GetCommandController();
    
    // 输出消息
    cmdCtrl.WriteInfo("Hello, LightCAD World!");
    
    return CommandResult.Succ();
}

4.4.3 带参数的命令

[CommandMethod(Name = "SetColor", ShortCuts = "SC")]
public CommandResult SetColor(IDocumentEditor docEditor, string[] args)
{
    if (args.Length < 1)
    {
        docEditor.GetCommandController().WriteError("请指定颜色参数");
        return CommandResult.Fail("缺少参数");
    }
    
    var colorName = args[0];
    // 处理颜色设置...
    
    return CommandResult.Succ();
}

4.4.4 异步命令

[CommandMethod(Name = "LongTask", ShortCuts = "LT")]
public CommandResult LongTask(IDocumentEditor docEditor, string[] args)
{
    Task.Run(async () =>
    {
        var cmdCtrl = docEditor.GetCommandController();
        cmdCtrl.WriteInfo("开始长时间任务...");
        
        await Task.Delay(3000); // 模拟耗时操作
        
        cmdCtrl.WriteInfo("任务完成!");
    });
    
    return CommandResult.Succ();
}

4.4.5 CommandResult详解

// 成功结果
CommandResult.Succ()

// 失败结果
CommandResult.Fail("错误信息")

// 带数据的结果
new CommandResult
{
    Status = CommandStatus.Success,
    Data = someData
}

4.5 文档操作

4.5.1 获取当前文档

[CommandMethod(Name = "DocInfo")]
public CommandResult GetDocInfo(IDocumentEditor docEditor, string[] args)
{
    var docRt = docEditor.GetDocumentRuntime();
    var doc = docRt.Document;
    
    var cmdCtrl = docEditor.GetCommandController();
    cmdCtrl.WriteInfo($"文档名称: {doc.Name}");
    cmdCtrl.WriteInfo($"元素数量: {doc.Elements.Count}");
    cmdCtrl.WriteInfo($"图层数量: {doc.Layers.Count}");
    
    return CommandResult.Succ();
}

4.5.2 遍历元素

[CommandMethod(Name = "ListElements")]
public CommandResult ListElements(IDocumentEditor docEditor, string[] args)
{
    var doc = docEditor.GetDocumentRuntime().Document;
    var cmdCtrl = docEditor.GetCommandController();
    
    foreach (var element in doc.Elements)
    {
        cmdCtrl.WriteInfo($"元素: {element.Type.Name} - {element.Id}");
    }
    
    return CommandResult.Succ();
}

4.5.3 图层操作

// 获取或创建图层
private LcLayer GetOrCreateLayer(LcDocument doc, string layerName, int color)
{
    var layer = doc.Layers.FirstOrDefault(l => l.Name == layerName);
    if (layer == null)
    {
        layer = doc.CreateObject<LcLayer>();
        layer.Name = layerName;
        layer.Color = color;
        layer.SetLineType(new LcLineType("ByLayer"));
        layer.Transparency = 0;
        doc.Layers.Add(layer);
    }
    return layer;
}

// 使用示例
var layer = GetOrCreateLayer(doc, "MyLayer", 0xFF0000); // 红色图层
element.Layer = layer.Name;

4.6 用户输入

4.6.1 PointInputer - 点输入

[CommandMethod(Name = "GetPoint")]
public async CommandResult GetPoint(IDocumentEditor docEditor, string[] args)
{
    var pointInputer = new PointInputer(docEditor);
    
    // 获取单个点
    var result = await pointInputer.Execute("请指定点:");
    
    if (result.Status == InputStatus.OK)
    {
        var point = result.Point;
        docEditor.GetCommandController().WriteInfo($"点坐标: ({point.X}, {point.Y})");
    }
    else if (result.Status == InputStatus.Cancel)
    {
        docEditor.GetCommandController().WriteInfo("操作已取消");
    }
    
    return CommandResult.Succ();
}

4.6.2 获取多个点

[CommandMethod(Name = "GetPoints")]
public async CommandResult GetPoints(IDocumentEditor docEditor, string[] args)
{
    var pointInputer = new PointInputer(docEditor);
    var points = new List<Vector2>();
    var cmdCtrl = docEditor.GetCommandController();
    
    cmdCtrl.WriteInfo("指定第一个点:");
    var result = await pointInputer.Execute();
    
    while (result.Status == InputStatus.OK)
    {
        points.Add(result.Point);
        cmdCtrl.WriteInfo($"已添加点 {points.Count}: ({result.Point.X}, {result.Point.Y})");
        
        result = await pointInputer.Execute("指定下一点或按ESC完成:");
    }
    
    cmdCtrl.WriteInfo($"共获取 {points.Count} 个点");
    return CommandResult.Succ();
}

4.6.3 CmdTextInputer - 文本输入

[CommandMethod(Name = "GetText")]
public async CommandResult GetText(IDocumentEditor docEditor, string[] args)
{
    var textInputer = new CmdTextInputer(docEditor);
    
    var result = await textInputer.Execute("请输入文本:");
    
    if (!string.IsNullOrEmpty(result))
    {
        docEditor.GetCommandController().WriteInfo($"输入的文本: {result}");
    }
    
    return CommandResult.Succ();
}

4.6.4 ElementSetInputer - 元素选择

[CommandMethod(Name = "SelectElements")]
public async CommandResult SelectElements(IDocumentEditor docEditor, string[] args)
{
    var elementInputer = new ElementSetInputer(docEditor);
    
    var result = await elementInputer.Execute("请选择元素:");
    
    if (result?.ValueX != null)
    {
        var elements = result.ValueX as List<LcElement>;
        var cmdCtrl = docEditor.GetCommandController();
        cmdCtrl.WriteInfo($"选择了 {elements.Count} 个元素");
        
        foreach (var ele in elements)
        {
            cmdCtrl.WriteInfo($"  - {ele.Type.DispalyName}");
        }
    }
    
    return CommandResult.Succ();
}

4.7 元素创建

4.7.1 创建基本元素

// 创建直线
var line = doc.CreateObject<LcLine>();
line.Start = new Vector2(0, 0);
line.End = new Vector2(100, 100);
line.Layer = "0";
vportRt.ActiveElementSet.InsertElement(line);

// 创建多段线
var polyline = doc.CreateObject<LcPolyLine>();
polyline.AddVertex(new Vector2(0, 0));
polyline.AddVertex(new Vector2(100, 0));
polyline.AddVertex(new Vector2(100, 100));
polyline.IsClosed = true;
vportRt.ActiveElementSet.InsertElement(polyline);

// 创建圆
var circle = doc.CreateObject<LcCircle>();
circle.Center = new Vector2(50, 50);
circle.Radius = 25;
vportRt.ActiveElementSet.InsertElement(circle);

4.7.2 创建自定义元素

[CommandMethod(Name = "CreateLawn")]
public CommandResult CreateLawn(IDocumentEditor docEditor, string[] args)
{
    var docRt = docEditor.GetDocumentRuntime();
    var doc = docRt.Document;
    var vportRt = docRt.ActiveViewportRuntime;
    
    // 获取组件定义
    var lawnDef = docRt.GetUseComDef("场布施工设计.绿色文明", "草坪", null) as QdLawnDef;
    
    // 创建草坪元素
    var lawn = new QdLawn(lawnDef);
    lawn.Initilize(doc);
    
    // 设置轮廓
    var poly = new Polyline2d();
    poly.AddCurve(new Line2d(new Vector2(0, 0), new Vector2(100, 0)));
    poly.AddCurve(new Line2d(new Vector2(100, 0), new Vector2(100, 100)));
    poly.AddCurve(new Line2d(new Vector2(100, 100), new Vector2(0, 100)));
    poly.AddCurve(new Line2d(new Vector2(0, 100), new Vector2(0, 0)));
    lawn.Outline = poly;
    
    // 设置其他属性
    lawn.ResetBoundingBox();
    lawn.Layer = "Layout_Lawn";
    lawn.Bottom = 0;
    
    // 插入到文档
    vportRt.ActiveElementSet.InsertElement(lawn);
    
    return CommandResult.Succ();
}

4.8 事件处理

4.8.1 文档事件

public void OnInitializeDocRt(DocumentRuntime docRt)
{
    var doc = docRt.Document;
    
    // 订阅元素添加事件
    doc.ElementAdded += (sender, e) =>
    {
        Console.WriteLine($"元素已添加: {e.Element.Type.Name}");
    };
    
    // 订阅元素删除事件
    doc.ElementRemoved += (sender, e) =>
    {
        Console.WriteLine($"元素已删除: {e.Element.Type.Name}");
    };
    
    // 订阅元素修改事件
    doc.ElementChanged += (sender, e) =>
    {
        Console.WriteLine($"元素已修改: {e.Element.Type.Name}");
    };
}

4.8.2 选择事件

public void OnInitializeDocRt(DocumentRuntime docRt)
{
    // 订阅选择变化事件
    docRt.SelectionChanged += (sender, e) =>
    {
        var selectedElements = docRt.GetSelectedElements();
        Console.WriteLine($"选择了 {selectedElements.Count} 个元素");
    };
}

4.9 配置持久化

4.9.1 使用Settings

// 创建Settings类
public class MyPluginSettings
{
    private static readonly string SettingsPath = 
        Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
                     "LightCAD", "MyPlugin", "settings.json");
    
    public string DefaultLayer { get; set; } = "0";
    public int DefaultColor { get; set; } = 0xFFFFFF;
    public double DefaultLineWidth { get; set; } = 1.0;
    
    public static MyPluginSettings Load()
    {
        if (File.Exists(SettingsPath))
        {
            var json = File.ReadAllText(SettingsPath);
            return JsonConvert.DeserializeObject<MyPluginSettings>(json);
        }
        return new MyPluginSettings();
    }
    
    public void Save()
    {
        var dir = Path.GetDirectoryName(SettingsPath);
        if (!Directory.Exists(dir))
            Directory.CreateDirectory(dir);
        
        var json = JsonConvert.SerializeObject(this, Formatting.Indented);
        File.WriteAllText(SettingsPath, json);
    }
}

4.9.2 使用示例

public class MyPlugin : ILcPlugin
{
    private static MyPluginSettings Settings;
    
    public void Loaded()
    {
        Settings = MyPluginSettings.Load();
    }
    
    public void OnDisposeDocRt(DocumentRuntime docRt)
    {
        Settings.Save();
    }
}

4.10 错误处理

4.10.1 命令异常处理

[CommandMethod(Name = "SafeCommand")]
public CommandResult SafeCommand(IDocumentEditor docEditor, string[] args)
{
    try
    {
        // 可能抛出异常的代码
        DoSomething();
        return CommandResult.Succ();
    }
    catch (ArgumentException ex)
    {
        docEditor.GetCommandController().WriteError($"参数错误: {ex.Message}");
        return CommandResult.Fail(ex.Message);
    }
    catch (Exception ex)
    {
        docEditor.GetCommandController().WriteError($"发生错误: {ex.Message}");
        // 记录详细日志
        LogError(ex);
        return CommandResult.Fail(ex.Message);
    }
}

4.10.2 输入验证

[CommandMethod(Name = "ValidatedInput")]
public async CommandResult ValidatedInput(IDocumentEditor docEditor, string[] args)
{
    var cmdCtrl = docEditor.GetCommandController();
    var pointInputer = new PointInputer(docEditor);
    
    // 获取第一个点
    var result1 = await pointInputer.Execute("指定第一个点:");
    if (result1.Status != InputStatus.OK)
    {
        cmdCtrl.WriteInfo("操作已取消");
        return CommandResult.Succ();
    }
    
    // 获取第二个点,并验证不能与第一个点相同
    Vector2 point2;
    while (true)
    {
        var result2 = await pointInputer.Execute("指定第二个点:");
        if (result2.Status != InputStatus.OK)
        {
            cmdCtrl.WriteInfo("操作已取消");
            return CommandResult.Succ();
        }
        
        point2 = result2.Point;
        if (point2.DistanceTo(result1.Point) > 0.001)
            break;
        
        cmdCtrl.WriteError("第二个点不能与第一个点重合,请重新输入");
    }
    
    // 继续处理...
    return CommandResult.Succ();
}

4.11 完整插件示例

using System;
using System.Collections.Generic;
using LightCAD.Core;
using LightCAD.Runtime;
using LightCAD.Drawing;
using LightCAD.MathLib;

namespace MyCompletePlugin
{
    /// <summary>
    /// 完整的插件示例
    /// </summary>
    public class MyCompletePlugin : ILcPlugin
    {
        public static TabItem MyTabItem = new TabItem
        {
            Name = "MyTools",
            Text = "我的工具",
            ShortcutKey = "ALT-Y",
            ButtonGroups = new List<TabButtonGroup>
            {
                new TabButtonGroup
                {
                    Buttons = new List<TabButton>
                    {
                        new TabButton
                        {
                            Name = "DrawRectangle",
                            Text = "绘制矩形",
                            IsCommand = true,
                        },
                        new TabButton
                        {
                            Name = "ShowInfo",
                            Text = "显示信息",
                            IsCommand = true,
                        }
                    }
                }
            }
        };
        
        public void Loaded()
        {
            Console.WriteLine("MyCompletePlugin loaded");
        }
        
        public void InitUI()
        {
            // UI初始化
        }
        
        public void Completed()
        {
            AppRuntime.UISystem.AddInitTabItems([MyTabItem]);
        }
        
        public void OnInitializeDocRt(DocumentRuntime docRt)
        {
            Console.WriteLine($"Document initialized: {docRt.Document.Name}");
        }
        
        public void OnDisposeDocRt(DocumentRuntime docRt)
        {
            Console.WriteLine($"Document disposed: {docRt.Document.Name}");
        }
    }
    
    [CommandClass]
    public class MyCommands
    {
        [CommandMethod(Name = "DrawRectangle", ShortCuts = "DR")]
        public async CommandResult DrawRectangle(IDocumentEditor docEditor, string[] args)
        {
            var cmdCtrl = docEditor.GetCommandController();
            var pointInputer = new PointInputer(docEditor);
            
            // 获取第一个角点
            cmdCtrl.WriteInfo("指定矩形第一个角点:");
            var result1 = await pointInputer.Execute();
            if (result1.Status != InputStatus.OK)
                return CommandResult.Succ();
            
            // 获取对角点
            cmdCtrl.WriteInfo("指定矩形对角点:");
            var result2 = await pointInputer.Execute();
            if (result2.Status != InputStatus.OK)
                return CommandResult.Succ();
            
            // 创建矩形
            var docRt = docEditor.GetDocumentRuntime();
            var doc = docRt.Document;
            var vportRt = docRt.ActiveViewportRuntime;
            
            var p1 = result1.Point;
            var p2 = result2.Point;
            
            var polyline = doc.CreateObject<LcPolyLine>();
            polyline.AddVertex(new Vector2(p1.X, p1.Y));
            polyline.AddVertex(new Vector2(p2.X, p1.Y));
            polyline.AddVertex(new Vector2(p2.X, p2.Y));
            polyline.AddVertex(new Vector2(p1.X, p2.Y));
            polyline.IsClosed = true;
            
            vportRt.ActiveElementSet.InsertElement(polyline);
            
            cmdCtrl.WriteInfo("矩形创建成功");
            return CommandResult.Succ();
        }
        
        [CommandMethod(Name = "ShowInfo", ShortCuts = "SI")]
        public CommandResult ShowInfo(IDocumentEditor docEditor, string[] args)
        {
            var docRt = docEditor.GetDocumentRuntime();
            var doc = docRt.Document;
            var cmdCtrl = docEditor.GetCommandController();
            
            cmdCtrl.WriteInfo("=== 文档信息 ===");
            cmdCtrl.WriteInfo($"文档名称: {doc.Name}");
            cmdCtrl.WriteInfo($"元素总数: {doc.Elements.Count}");
            cmdCtrl.WriteInfo($"图层数量: {doc.Layers.Count}");
            cmdCtrl.WriteInfo("================");
            
            return CommandResult.Succ();
        }
    }
}

4.12 本章小结

本章介绍了LightCAD插件开发的基础知识:

  1. 插件体系:ILcPlugin接口和生命周期
  2. UI集成:TabItem、TabButton的创建和配置
  3. 命令开发:CommandClass和CommandMethod的使用
  4. 文档操作:获取文档、遍历元素、图层管理
  5. 用户输入:点输入、文本输入、元素选择
  6. 元素创建:基本元素和自定义元素的创建
  7. 事件处理:文档事件和选择事件
  8. 配置持久化:设置的保存和加载
  9. 错误处理:异常处理和输入验证

掌握这些基础知识后,下一章我们将深入学习元素类型系统的详细实现。