一个 AutoCAD .NET 插件:把磁盘上的 DWG 目录变成可视化图库——左侧分类树、右侧缩略图墙、底部插入参数,双击或拖拽即可插入当前图形。同时支持中望 CAD、浩辰 CAD、BricsCAD。
当前版本:1.51.13。 覆盖 AutoCAD 2010 ~ 2027(一套源码,6 个目标框架),外加中望、浩辰、BricsCAD。
许可证:MIT。 作者:悟空。
AutoCAD、中望、浩辰、BricsCAD 的托管程序集受厂商版权约束,不包含在本仓库中。请在装有对应 CAD 的机器上编译,或自行从厂商 NuGet 取得参考程序集后再指定 CadRefDir。
| 版本组 | 状态 | 说明 |
|---|---|---|
| CAD2019-2024 | ✅ 已编译并验证 | 用本机 AutoCAD 2020 的 acdbmgd/acmgd/accoremgd 编译,net47,x64,0 错误 0 警告。产物 dist\CAD2019-2024\BlockLibraryManager.dll,已可用于 AutoCAD 2019–2024 |
| CAD2025-2027 | 需在装有 AutoCAD 2025+ 的机器上用其自带 DLL 重新编译(本机无 2025,暂用 2020 引用验证过语法) | |
| CAD2010-2011 / 2012 / 2013-2014 / 2015-2018 | ⏳ 待编译 | 每个 AutoCAD 大版本的 acdbmgd.dll 版本号不同,必须在装有对应版本的机器上编译,无法交叉引用 |
为什么不能一次编译全部 6 个版本?
acdbmgd.dll是强命名程序集,AutoCAD 2010 / 2015 / 2020 / 2025 各自的版本号不同。 用 2020 的 DLL 编出来的程序集,加载到 AutoCAD 2015 会报"找不到程序集"; 反过来,用低版本的 DLL 去编译高版本目标,MSBuild 会因为引用程序集框架版本过低而直接丢弃引用。 这正是build\下要拆 6 个工程的原因——源码只有一份,引用各取所需。
| 功能 | 说明 |
|---|---|
| 分类树 | 按磁盘目录自动生成,显示每类的图块数量,支持多图库根目录(本机 + 服务器共享) |
| 缩略图 | 读取 DWG 自带预览图,两级缓存(内存 + %LOCALAPPDATA% PNG),空闲时分批生成,上千张图也不卡界面 |
| 三种视图 | 大图标 / 列表 / 详细信息 |
| 搜索 | 按名称过滤,可选是否包含子目录 |
| 最近使用 | 自动记录最近插入的图块 |
| 插入参数 | X/Y/Z 比例(可锁定等比)、旋转角度、插入后是否炸开、是否更新已有块定义 |
| 连续插入 | 一次选择,连续指定多个插入点,Esc 结束 |
| 拖拽入库 | 从资源管理器把 DWG 拖进列表即可复制到当前分类 |
| 拖拽出库 | 把列表里的图块拖到 CAD 图形窗口 / 资源管理器 |
| 展开内部块 | 可选:把每个 DWG 内部的块定义也单独列出来 |
| 同名处理 | 块名自动清洗(空格与非法字符转 _);同名时按"更新已有定义"策略处理 |
┌──────────────────────────────────────────────┐
│ 图库管理器 _ □ X │ 标题栏(自绘,可拖动)
├──────────────────────────────────────────────┤
│ [添加][刷新][上级][视图▾]│[设置]│[搜索][清除]│关于│ 工具栏
├────────────┬─────────────────────────────────┤
│ 图库 (128) │ □ □ □ □ □ │
│ ├ 机械(42)│ □ □ □ □ □ │ 左:分类树
│ ├ 建筑(56)│ □ □ □ □ □ │ 右:缩略图列表
│ └ 电气(30)│ □ □ □ □ □ │
├────────────┴─────────────────────────────────┤
│ 比例:[1][1][1] ☑等比 旋转°:[0] │
│ ☐炸开 ☑更新已有定义 [ 插入 ] │ 插入参数
├──────────────────────────────────────────────┤
│ 共 42 个图块 │ 状态栏
└──────────────────────────────────────────────┘
窗口模式(v1.1 起):不再使用 AutoCAD 的 PaletteSet(它的停靠标题栏是一条无法隐藏的
黑色竖条,官方 API PaletteSetTitleBarLocation 只有 Left/Right,没有 None),
改为无边框自绘窗口,我们自己的标题栏就是唯一标题栏:
- 按住标题栏拖动窗口位置;窗口八边可拉伸缩放
- 关闭 AutoCAD 主窗口时窗口跟随最小化(通过
ShowModelessDialog归属主窗口) - 位置/尺寸自动记忆到 BlockLibrary.ini,下次打开原位还原;显示器变更时自动拉回屏幕内
- 想改回停靠面板模式,把
src/UI/LibraryPalette.cs换回旧版宿主即可(git 历史)
| 按钮 | 行为 |
|---|---|
_ 最小化 |
最小化到任务栏(WindowState.Minimized) |
□ 最大化 / ▣ 还原 |
最大化占满工作区 ↔ 还原(图标随状态切换) |
X 关闭 |
隐藏窗口(BLOCKLIB 可再次打开) |
双击标题栏 = 最大化 / 还原。按钮图标全部用 GDI 绘制,不使用特殊字符,避免个别机器上字体缺字。
点「添加」打开系统文件树浏览对话框,选中一个文件夹确定后,它会成为分类树的一级节点, 它下面的子文件夹自动成为下级分类。
┌──────────────────────────────────────────┐
│ 添加图库目录 _ □ X │
├──────────────────────────────────────────┤
│ 浏览并选择要加入图库的文件夹: │
│ ┌──────────────────────────────────────┐ │
│ │ ▾ 桌面 │ │
│ │ ▾ 计算机 │ │
│ │ ├ 本地磁盘 (C:) │ │
│ │ ├ 数据盘 (D:) │ │
│ │ └ 光盘驱动器 (E:) │ │
│ │ 我的文档 │ │
│ └──────────────────────────────────────┘ │
│ 位置:[D:\我的图库 ][转到][上级]│
│ [新建文件夹] ☐显示隐藏文件夹 [确定] [取消] │
└──────────────────────────────────────────┘
实现见 src/UI/FolderPickerForm.cs。几个刻意的取舍:
- 不用系统
FolderBrowserDialog:net35(AutoCAD 2010/2011)上它是老式控件,不能拉伸、 没有地址栏、不能粘贴路径;自己写反而全版本一致且能扩展。 - 惰性加载:只有节点真正展开时才枚举子目录,打开对话框不会卡。
- 图标取真的:走
shell32!SHGetFileInfo拿 Explorer 的原生图标(驱动器图标各不相同), 取不到时退回自绘色块,绝不抛异常。整个方法包在 try/catch 里。 - 地址栏可直接粘贴路径(含
\\服务器\共享这类 UNC),回车即定位。 - 双击-TreeView 节点导航;支持就地新建文件夹、重命名(节点 LabelEdit)。
- 全程 try/catch:遍历无权访问的目录只是不展开,不会崩。
选择时的三种冲突处理(LibraryControl.Events.cs 的 OnAddRootClick):
| 情况 | 处理 |
|---|---|
| 与已有图库目录相同 | 提示已存在,自动选中该节点 |
| 落在已有图库目录之内 | 提示已在扫描范围内,不必单独添加 |
| 包含了已有图库目录 | 弹窗询问是否移除旧目录(避免重复统计) |
另外选择驱动器根目录(如 D:\)会二次确认——那会递归扫描整盘。
工具栏最右侧的"关于"按钮弹出版权/版本信息。所有内容集中在
src/UI/AboutForm.cs 顶部的 AboutInfo 静态类里(产品名、版本号、构建日期、
作者、单位、联系方式、功能简介),改常量即可,留空的行不显示。
对话框还会动态显示当前运行插件的 AutoCAD 版本号。
RolledUp / AutoRollUp 并非每个 AutoCAD 版本都有,代码用反射探测,
依次尝试 RolledUp → AutoRollUp → AutoHide,都不支持时退化为隐藏面板,不会抛异常。
cad-block-manager/
├── README.md
├── BlockLibraryManager.sln # VS 解决方案,含 6 个版本工程
├── build.cmd # 构建单个版本
├── build-all.cmd # 构建全部版本
├── Common.props 位于 src/
├── src/ # ★ 唯一的一份源码,所有版本共享
│ ├── Common.props # 共享的 MSBuild 配置(引用、编译项、输出路径)
│ ├── Plugin.cs # 插件入口(ExtensionApplication)
│ ├── Commands.cs # 命令 BLOCKLIB / BLOCKLIBH / BLOCKLIBR / BLOCKLIBS / BLOCKLIBIN
│ ├── Model.cs # BlockItem / CategoryNode / GalleryView
│ ├── Config.cs # 配置读写(DLL 同目录 BlockLibrary.ini)
│ ├── Helpers\MiniJson.cs # 零依赖 JSON(net35 没有 System.Text.Json)
│ ├── Services\
│ │ ├── LibraryIndex.cs # 扫描磁盘,构建分类树
│ │ ├── ThumbnailService.cs # 缩略图队列 + 缓存
│ │ ├── BlockInsertService.cs # 块定义导入 + Jig 拖拽插入 + 手动炸开
│ │ ├── CadHost.cs # ★ 国产 CAD 兼容层(反射调用不确定成员,缺失自动降级)
│ │ └── InsertBridge.cs # 面板 → 命令 的请求桥
│ └── UI\
│ ├── LibraryControl.cs # 主界面(布局)
│ ├── LibraryControl.Logic.cs # 主界面(数据)
│ ├── LibraryControl.Events.cs # 主界面(事件)
│ ├── LibraryControl.Draw.cs # ★ 列表自绘 + 可见区间优先(只为可见项取缩略图)
│ ├── LibraryPalette.cs # PaletteSet 宿主
│ └── SettingsForm.cs # 设置对话框
├── build\ # 版本工程,每个只有十几行
│ ├── CAD2010-2011\ (net35, AutoCAD 2011)
│ ├── CAD2012\ (net40, AutoCAD 2012)
│ ├── CAD2013-2014\ (net40, accore, AutoCAD 2014)
│ ├── CAD2015-2018\ (net45, accore, AutoCAD 2018)
│ ├── CAD2019-2024\ (net47, accore, AutoCAD 2024)
│ ├── CAD2025-2027\ (net8.0-windows, AutoCAD 2026)
│ ├── ZWCAD\ (net47, 中望 CAD —— + Common.CadPlatform.props)
│ ├── GstarCAD\ (net48, 浩辰 CAD —— + Common.CadPlatform.props)
│ └── BricsCAD\ (net8.0-windows, BricsCAD —— + Common.CadPlatform.props)
├── tools\
│ └── gen-platform-src.ps1 # ★ 源码命名空间转换(AutoCAD → 中望 / 浩辰 / BricsCAD)
├── bundle\ # 自动加载插件包(见第 6 节)
└── dist\ # 构建产物(不入库,编译后生成)
| 目录 | AutoCAD | .NET | 条件编译符号 | accoremgd |
|---|---|---|---|---|
CAD2010-2011 |
2010 / 2011 | net35 | ACAD_2012_OR_OLDER |
否 |
CAD2012 |
2012 | net40 | ACAD_2012_OR_OLDER |
否 |
CAD2013-2014 |
2013 / 2014 | net40 | ACAD_2013_OR_NEWER |
是 |
CAD2015-2018 |
2015 / 2016 / 2017 / 2018 | net45 | ACAD_2013_OR_NEWER;ACAD_2015_OR_NEWER |
是 |
CAD2019-2024 |
2019 ~ 2024 | net47 | 同上 | 是 |
CAD2025-2027 |
2025 / 2026 / 2027 | net8.0-windows | 同上 + ACAD2025_OR_NEWER;NETCORE |
是 |
accoremgd.dll 从 AutoCAD 2013 起才存在(ApplicationServices / EditorInput / Windows 都搬进去了),2012 及更早只需要 acdbmgd.dll + acmgd.dll。
| 目录 | 平台 | 命名空间 | 托管程序集 | .NET | 条件符号 |
|---|---|---|---|---|---|
ZWCAD |
中望 CAD 2024~2026 | 单根 ZwSoft.ZwCAD.* |
ZwDatabaseMgd.dll + ZwManaged.dll |
net47 | BLM_ZWCAD |
GstarCAD |
浩辰 CAD 2024/2025 | 单根 Gssoft.Gscad.* |
GcCoreMgd.dll + GcDbMgd.dll + GcMgd.dll |
net48 | BLM_GSTARCAD |
BricsCAD |
BricsCAD V26 / V27 | 双根(见下) | BrxMgd.dll + TD_Mgd.dll |
net8.0-windows | BLM_BRICSCAD |
原理:这三家的 .NET API 都是 ObjectARX 的整体移植,类型名与成员签名基本一致,只有命名空间不同。 所以做法不是复制四份源码,而是:
- 日常只维护
src/一份源码(写的是 AutoCAD 的命名空间); - 编译时由
tools\gen-platform-src.ps1自动替换命名空间,生成到build\<平台>\gen\里 再编译(源码没变就跳过,不拖慢编译); - 少数各家不一定实现的成员(
Application.ShowModelessDialog/Document.CommandEnded/Document.UnknownCommand),由src\Services\CadHost.cs用反射 + IL 适配器调用: 有这个成员就走原生路径,没有就自动降级,因此不会在编译期报错。
BricsCAD 的托管 API 分装在两个程序集里,命名空间也被拆成两个根:
| AutoCAD | BricsCAD | 来自 |
|---|---|---|
Autodesk.AutoCAD.ApplicationServices |
Bricscad.ApplicationServices |
BrxMgd.dll |
Autodesk.AutoCAD.EditorInput |
Bricscad.EditorInput |
BrxMgd.dll |
Autodesk.AutoCAD.Windows |
Bricscad.Windows |
BrxMgd.dll |
Autodesk.AutoCAD.Runtime |
Teigha.Runtime |
TD_Mgd.dll |
Autodesk.AutoCAD.DatabaseServices |
Teigha.DatabaseServices |
TD_Mgd.dll |
Autodesk.AutoCAD.Geometry |
Teigha.Geometry |
TD_Mgd.dll |
Autodesk.AutoCAD.Colors |
Teigha.Colors |
TD_Mgd.dll |
Teigha.* 来自 ODA 的 DWG 内核(TD_Mgd.dll),Bricscad.* 是 Bricsys 自己在
BrxMgd.dll 里做的 CAD 应用层。映射表写在 tools\gen-platform-src.ps1 的
$NsMapBricsCAD 里,没列到的走命名空间根兜底。
版本提醒:BricsCAD V26 起宿主切换到 .NET 8,V25 及更早是 .NET Framework 4.8。 本工程默认编 net8.0-windows(对应 V26/V27);要给 V25 及更早编译,见第 5 节。
| 受影响的功能 | AutoCAD | 其它 CAD(有该成员时) | 其它 CAD(无该成员时) |
|---|---|---|---|
| 面板跟随 CAD 主窗口最小化 | ShowModelessDialog |
自动反射调用,效果一致 | 直接 Form.Show(),面板不跟着最小化(功能不受影响) |
| 拖拽插入后自动还原面板 | CommandEnded 事件 |
精确时机还原 | 兜底定时器 12 秒还原 |
| 设置里自定义启动命令 | UnknownCommand 事件 |
与 AutoCAD 一致 | 该功能不可用,默认命令 BLOCKLIB 照常可用 |
已用官方程序集逐个核对过 BricsCAD V27:
Application.ShowModelessDialog(Form)/ShowModalDialog(Form)/Idle/Document.CommandEnded/CommandCancelled/UnknownCommand/UnknownCommandEventArgs.GlobalCommandName全部存在, 所以 BricsCAD 上这三项功能都是完整行为,不会降级。
- Visual Studio 2022(推荐,社区版即可)+ ".NET 桌面开发"工作负载
- 需要勾选 ".NET Framework 4.7 SDK / 目标包"(用于 net35~net47)
- 需要 .NET 8 SDK(用于 AutoCAD 2025+)
- 或 .NET SDK 8.0+ 命令行编译(已通过
Microsoft.NETFramework.ReferenceAssemblies支持老框架,无需装 VS) - 目标机器装有对应版本的 AutoCAD(只为拿
acdbmgd.dll等引用,不装也能编译——把 DLL 拷到一个目录里指过去即可)
:: 构建一个版本
build.cmd CAD2019-2024 "C:\Program Files\Autodesk\AutoCAD 2024"
:: 构建全部(自动探测常见安装路径,找不到就跳过)
build-all.cmd等价的原始命令:
dotnet build build\CAD2019-2024\BlockLibraryManager.csproj -c Release ^
-p:AcadRefDir="C:\Program Files\Autodesk\AutoCAD 2024"AcadRefDir 是唯一的必填参数,指向包含 acdbmgd.dll 的目录。
每个 csproj 里都写了一个默认值(例如 CAD2019-2024 默认 C:\Program Files\Autodesk\AutoCAD 2024),
路径一致的话可以省略。
:: 中望 CAD(脚本会先自动探测常见安装路径,也可手工指定)
build-zwcad.cmd "C:\Program Files\ZWSOFT\ZWCAD 2021"
:: 浩辰 CAD
build-gstarcad.cmd "C:\Program Files\Gstarsoft\GstarCAD 2024"
:: BricsCAD(官方 API 为 NuGet:BricsCAD.27.NET / BricsCAD.26.NET,本仓库不附带)
:: 把包里的 ref\net8.0 解到 tools\bcapi\v27\ref\net8.0 后可直接:
build-bricscad.cmd
build-bricscad.cmd V26
:: V25 及更早是 .NET Framework 4.8,需指向本机安装目录
build-bricscad.cmd V25 "C:\Program Files\Bricsys\BricsCAD V25"
:: 或一次性构建全部 9 个平台(没有装对应 CAD、也没有参考程序集的会自动跳过)
build-all.cmd手工打命令的话,这几个平台对应的属性是 CadRefDir(不是 AcadRefDir):
dotnet build build\ZWCAD\BlockLibraryManager.csproj -c Release -p:CadRefDir="...中望安装目录"
dotnet build build\GstarCAD\BlockLibraryManager.csproj -c Release -p:CadRefDir="...浩辰安装目录"
:: BricsCAD:把官方 NuGet 的 ref\net8.0 解到 tools\bcapi\v27\ref\net8.0,或直接指向安装目录
dotnet build build\BricsCAD\BlockLibraryManager.csproj -c Release ^
-p:CadRefDir="tools\bcapi\v27\ref\net8.0"
dotnet build build\BricsCAD\BlockLibraryManager.csproj -c Release ^
-p:TargetFramework=net48 -p:CadRefDir="C:\Program Files\Bricsys\BricsCAD V25"个别机器上中望把 acmgd 的内容也单列了一个 DLL,追加引用:
dotnet build build\ZWCAD\BlockLibraryManager.csproj -c Release ^
-p:CadRefDir="..." -p:ExtraCadRefItems=ZwManagedUx不要把厂商 DLL 放进仓库。 中望、浩辰、BricsCAD、Autodesk 的托管程序集都受厂商版权约束,本仓库不附带。 找不到这些程序集时,构建会在编译前直接失败,避免生成无法 NETLOAD 的无效 DLL。
打开 BlockLibraryManager.sln,在"解决方案配置"里选择 Release | Any CPU,然后:
- 只生成某一个项目 → 右键该项目 → 生成
- 全部生成 → 生成 → 生成解决方案
如果 VS 报"找不到 acdbmgd.dll",在 Common.props 顶部或命令行里指定 AcadRefDir。
dist\CAD2019-2024\BlockLibraryManager.dll ← AutoCAD 2019~2024(本机真编译,实测可加载)
dist\ZWCAD\BlockLibraryManager.dll ← 中望 CAD(官方 NuGet 程序集真编译)
dist\GstarCAD\BlockLibraryManager.dll ← 浩辰 CAD(官方 NuGet 程序集真编译)
dist\BricsCAD\BlockLibraryManager.dll ← BricsCAD V26 / V27(官方 NuGet 程序集真编译)
每个版本只能用自己那份 DLL,不要交叉复制。
> dotnet build build\CAD2019-2024\BlockLibraryManager.csproj -c Release ^
-p:AcadRefDir="C:\Program Files\Autodesk\AutoCAD 2020"
BlockLibraryManager -> ...\dist\CAD2019-2024\BlockLibraryManager.dll
已成功生成。
0 个警告
0 个错误
> dotnet build build\BricsCAD\BlockLibraryManager.csproj -c Release
gen-platform-src: [BricsCAD] 已生成 30 个文件 -> ...\build\BricsCAD\gen
BlockLibraryManager -> ...\dist\BricsCAD\BlockLibraryManager.dll
已成功生成。
0 个警告
0 个错误
(换用 BricsCAD V26 的参考程序集再编一次:同样 0 警告 0 错误,
说明这份 DLL 在 V26 / V27 上通用)
编译过程中真实暴露并修复的 6 个问题(都是跨版本容易踩的坑):
LibraryPalette.cs:Application在Autodesk.AutoCAD.ApplicationServices与System.Windows.Forms之间二义性(CS0104)→ 改为写全名LibraryIndex.cs:string filterHit = false;类型写错(CS0029)→ 改boolBlockInsertService.cs:ObjectIdPair不存在 → 正确类型名是IdPair(CS0246) 4~6. 连带的两处 CS0029 / CS0023 均由第 2 条引起
另有 2 个"字段未使用"警告,已删除 _searchMode / _menuRecent 两个残留字段。
- 启动 AutoCAD,打开任意图形
- 命令行输入
NETLOAD - 选择
bundle\BlockLibraryManager.bundle\Contents\CAD2019-2024\BlockLibraryManager.dll(自己编译后则用dist\<版本组>\里对应的 DLL) - 输入
BLOCKLIB打开面板
仓库里的 bundle\BlockLibraryManager.bundle 带有 AutoCAD 2019–2024 的 DLL:
BlockLibraryManager.bundle\
├── PackageContents.xml ← 注册 AutoCAD 2019-2024
├── PackageContents-full-template.xml ← 6 版本完整模板(其他版本参考)
└── Contents\CAD2019-2024\BlockLibraryManager.dll
复制到当前用户的自动加载目录:
xcopy /E /I bundle\BlockLibraryManager.bundle ^
"%APPDATA%\Autodesk\ApplicationPlugins\BlockLibraryManager.bundle"
复制完成后重启 AutoCAD,输入 BLOCKLIB 打开面板。之后更新 DLL 可以运行 update.cmd(需先关闭 AutoCAD,且上面的目录已经存在)。
装到所有用户(需管理员):C:\Program Files\Autodesk\ApplicationPlugins\
卸载:删掉 %APPDATA%\Autodesk\ApplicationPlugins\BlockLibraryManager.bundle 文件夹。
AutoCAD 2010 / 2011 不支持 ApplicationPlugins,请在 acaddoc.lsp 里加:
(command "NETLOAD" "D:\\YourPath\\dist\\CAD2010-2011\\BlockLibraryManager.dll")| 命令 | 作用 |
|---|---|
BLOCKLIB |
显示 / 隐藏图库面板(开关) |
BLOCKLIBH |
隐藏面板 |
BLOCKLIBR |
重新扫描图库 |
BLOCKLIBS |
打开设置 |
BLOCKLIBIN |
内部命令:执行插入(面板自动调用,一般不手工输入) |
想改成中文命令名,把 src\Commands.cs 里的 [CommandMethod("BLOCKLIB")] 改成
[CommandMethod("图库")] 即可,AutoCAD 支持 Unicode 命令名。
- 插入:双击列表项,或选中后点"插入",然后在图形窗口点一下位置
- 连续插入:右键 → "插入多个(连续)",逐个点位置,Esc 结束
- 入库:从资源管理器把 DWG 拖到列表区域,会复制到当前选中的分类目录
- 添加图库目录:工具栏"添加"→ 文件树浏览选文件夹(详见上文)
- 改图库位置:工具栏"设置" → 增删图库根目录
- 快速入库:资源管理器里把 DWG 拖到右侧列表;或先把 DWG 放进图库目录再点"刷新"
这部分是给后续维护者看的,也是这套代码能在 2010~2027 全版本跑起来的原因。
src\**\*.cs 被 6 个 csproj 通过 EnableDefaultCompileItems=false + 通配符共享。
版本差异全部收敛到两处:
- csproj 里的
DefineConstants(条件编译符号) src\Common.props里的 DLL 引用(是否引用accoremgd)
| 不用的 API | 原因 | 替代方案 |
|---|---|---|
Editor.Command |
2015 才引入 | 全部用对象数据库 API |
Entity.Explode() |
各版本签名变过 | ExplodeManually():克隆块定义内实体 + Matrix3d 变换 |
UnitsConverter |
签名/行为不一致 | 需要时自行按单位换算 |
PaletteSet(name, Guid) |
老版本没有该重载 | #if ACAD_2013_OR_NEWER 分流 |
System.Text.Json |
net35 没有 | 自研 MiniJson(只处理 string/int/bool/数组) |
| Newtonsoft.Json | 需额外部署 DLL | 同上 |
XmlSerializer |
要求类型 public,易踩坑 | 同上 |
AutoCAD 的 Database 只能在主线程操作,丢到 ThreadPool 会崩。
所以采用队列 + Application.Idle 的模式:每次空闲事件最多处理 6 张、
总耗时预算 40ms(到点就收工,剩下的等下一次 Idle),界面始终可响应;
结果先写磁盘 PNG 缓存,第二次打开同一图库基本秒开。
说明:本插件是 WinForms(不是 WPF),所以用户提的
BitmapImage.DecodePixelWidth没有直接对应的 API,这里用的是 GDI+ 的等价做法,效果一致。
一、UI 虚拟化 —— 只为屏幕上可见的项加载图片
列表原来走的是 ListView + ImageList:每加一项就把一整张缩略图塞进 ImageList,
打开一个 2000 个图块的分类就要同步做出 2000 张图,这是刷新列表最慢的一步;
而且 ImageList 没有"原地替换"的方法,缩略图陆续到位时只能 RemoveByKey + Add,
一删一加会让后面所有项的索引整体错位。
现在改成 OwnerDraw 自绘(见 src\UI\LibraryControl.Draw.cs):
| 原来 | 现在 | |
|---|---|---|
| ImageList | 每个 item 一张图(几千张) | 只留 1 张透明占位图,纯粹用来撑出图标尺寸 |
| 取图时机 | 建列表时全部取 | 绘制事件触发时才取,屏幕外的项永远取不到 |
| 动态块角标 | 每个动态块复制一张 Bitmap 把角标烤进去 | 绘制期直接叠画上去,零拷贝 |
| 缩略图到位 | RemoveByKey+Add+每条 BeginInvoke(索引会错位) |
只打脏标记,一帧统一 Invalidate 一次 |
WinForms 的 ListView 只为可见项发 DrawItem / DrawSubItem 事件,
所以"进入一个 2000 项的分类"从"先做 2000 张图"变成"先做屏幕上那几十张"。
二、数据虚拟化 —— 后台队列跟着视口走
ThumbnailService.Prioritize() 把当前可见区间(用 LVM_GETTOPINDEX /
LVM_GETCOUNTPERPAGE 算出)的条目提到队列最前面。用户滚到哪儿,哪一段就优先出图。
只有可见区间真的变了才重排队列(用指纹比对),不会每帧重排。
三、解码时降采样
- 加载磁盘缓存:GDI+ 没有解码参数,只能在
Image.FromStream之后立刻按 目标尺寸重采样、然后马上释放大图与流 —— 留在缓存里的是小图而不是原始大图。 顺带修掉一个隐患:原来Image.FromStream依赖的MemoryStream一直挂着不释放, 每张缩略图都额外占着一份完整文件字节。 - 生成新缩略图:DWG 内嵌的预览位图动辄 512×512 甚至 1024×768,而后面两步 (裁右缘黑带、泛洪换底色)都是逐像素处理,是整个生成过程最贵的一段。 现在先等比缩到 2 倍缩略图尺寸再处理,像素规模降到十几分之一,出图尺寸不变、 肉眼无差别。
四、流畅性
- 列表打开双缓冲(
DoubleBuffered是protected,用反射设),自绘不再闪; DisplayBlocks从逐条Items.Add改成一次性Items.AddRange, 少发几千次原生通知;- 每张缩略图到位不再各发一个
BeginInvoke,改成脏标记 + 每帧一次Invalidate。
面板是非模态的,在按钮 Click 里直接 Editor.Drag 经常报 eNotApplicable,
或者鼠标输入被面板吃掉。这里用 Autodesk 官方推荐的模式:
面板按钮 → InsertBridge.Request(...) → doc.SendStringToExecute("BLOCKLIBIN\n")
→ 命令 BLOCKLIBIN(命令上下文) → Editor.Drag(jig) → 完成插入
块名会先清洗(空格与 < > / \ " : ; ? * | , = \ '转_`,最长 250 字符)。
插入时若目标图中已有同名定义:
- 勾选"更新已有定义"(默认)→ 先尝试删除旧定义再重新导入,拿到图库最新版本; 删不掉(说明正被引用)就复用旧定义
- 不勾选 → 直接复用已有定义
- "炸开"是手动实现,含属性定义(Attribute)的块不会转成属性值
- 缩略图用的是 DWG 自带预览图;如果原图保存时没生成预览图,会显示占位图
- 跨版本扫描网络盘较慢时,建议关闭设置里的"展开 DWG 内部块"
- 单位换算没有自动处理(图块本身的单位与目标图不一致时,比例仍按你填的数值)
| 内容 | 路径 |
|---|---|
| 配置 | DLL 同目录 BlockLibrary.ini(DLL 目录不可写时回退到 %APPDATA%\BlockLibraryManager\) |
| 缩略图缓存 | %LOCALAPPDATA%\BlockLibraryManager\Thumbnails\*.png |
| 默认图库目录 | 我的文档\AutoCAD BlockLibrary(首次运行自动创建) |
删掉缓存目录即可强制重新生成缩略图。
| 现象 | 排查 |
|---|---|
NETLOAD 报"无法加载程序集" |
确认用的是对应版本的 DLL;AutoCAD 2025+ 必须用 CAD2025-2027 那份 |
| 编译报"找不到 acdbmgd.dll" | 用 -p:AcadRefDir="..." 指向正确目录,或确认装了对应 AutoCAD |
| 编译报"未能找到类型或命名空间名 Autodesk" | AcadRefDir 指错了版本。例如用 AutoCAD 2020 的 DLL 编译 net45 目标,MSBuild 会因引用程序集框架版本过高而静默丢弃引用。必须用与目标版本组匹配的 AutoCAD 目录 |
dist\CAD2025-2027 为空或产物不可用 |
本机没有 AutoCAD 2025+。需在装有 2025+ 的机器上用其自带 DLL 编译 |
| 编译报"缺少 .NET Framework 3.5/4.0 引用程序集" | 首次编译需要联网还原 Microsoft.NETFramework.ReferenceAssemblies;离线环境请在能上网的机器上先 dotnet restore |
| 面板打开但列表是空的 | 设置 → 检查图库根目录是否存在且有 .dwg 文件 |
| 缩略图一直是占位图 | 原 DWG 保存时未生成预览图;或 %LOCALAPPDATA% 无写权限 |
| 点击插入没反应 | 确认当前有打开的图形;命令行输入 BLOCKLIBIN 看是否有报错 |
本项目以 MIT License 开源。
AutoCAD、中望 CAD、浩辰 CAD、BricsCAD 及其托管 API 的名称与程序集分别属于 Autodesk、中望软件、浩辰软件、Bricsys,不在本许可证的授权范围内。