☰
LibreCAD 图层树视图(Layer Tree View)详解:基于命名约定的层次化图层管理
2026/10/15 1:52:41 网站建设 项目流程
  • 桌面应用
  • 图形学

【免费下载链接】LibreCAD

LibreCAD is a cross-platform 2D CAD program. It can read DXF/DWG, and write DXF/DWG/PDF/SVG files. It supports point/line/circle/ellipse/parabola/hyperbola/spline primitives. The GUI is highly customizable, and has dozens of translations.

项目地址:https://gitcode.com/gh_mirrors/li/LibreCAD
点击查看免费下载

导读

LibreCAD 是一款跨平台的开源 2D CAD 程序(见 README.md),其原生 DXF 格式并不支持图层的层级结构。本文围绕 ChangeLogs/LAYER_TREE_VIEW.md 介绍的Layer Tree View(图层树视图)控件,讲解如何在纯命名约定的基础上为图层建立树状层级、借助图层类型前缀/后缀实现标注、信息、替代位置等辅助图层的自动归类,以及如何利用正则过滤、拖放重组、分组批量操作来提升大型工程图的图层管理效率。读完本文,你将掌握图层树的两种显示模式、五类预定义图层类型、完整工具栏动作与右键菜单操作,以及该控件全部可配置项在源码中的实现与存储位置。


一、问题背景:DXF 图层为何需要"树"

在大型工程图中,图层数量可能非常多,且同一图层常常被复用于单一零件/视图;标注、零件的替代位置、注释与草图等附加信息也往往需要独立的图层。常见做法是:

  • 用统一前缀为属于同一零件/视图的图层命名;
  • 依靠命名中的公共逻辑前缀进行"逻辑分组"。

但人工命名只能让"分组的味道"更接近,依然存在局限:前缀一旦拼写不一致,分组就会失效。更根本的问题是,DXF 格式本身不支持图层层级,LibreCAD 原生图层结构同样没有层级概念(见原文档 The Problem 一节)。

二、解决方案:命名约定驱动 + 纯 UI 实现

Layer Tree View 的核心理念可以概括为两点:

  1. 层级完全由命名约定推导:假设图层名中存在某个统一的"分隔子串"(默认是-),该子串将图层名切分成若干层级片段,第 1 个片段是顶层、最后一个片段是实际图层名,中间片段自动成为"虚拟图层"(组节点)。
  2. 纯 UI 实现,不影响底层存储:控件不改变RS_Layer内部结构与 DXF 存储格式,原始平面图层列表(RS_LayersList)不受影响,两个控件可以并行使用(见原文档 Implementation notes 一节)。

从源码看,这一设计得到了印证:LC_LayerTreeItem中的m_name保存的是"解析后的原始名称片段",m_displayName是 UI 展示名,m_fullName是完整路径;而LC_LayerTreeItem对RS_Layer仅持有一个引用(m_layer),本身并不创建新的图层对象,见 lc_layertreeitem.h。RS_Layer类中的m_layerType字段也明确注释为"transient field, should be updated by LayerTreeWidget"(瞬态字段,应由图层树控件更新),见 rs_layer.h。

源码中的层级构建流程

在 lc_layertreemodel.cpp 的rebuildModel()中,层级还原逻辑非常清晰:

// split by parts using defined separator of layer levels QStringList nameParts = layerFullName.split(m_options->layerLevelSeparator, Qt::KeepEmptyParts); ... for (int indent = 0; indent < partsCount-1; ++indent){ const QString& subName = nameParts.at(indent); virtualRoot = virtualRoot -> getOrCreateVirtualChild(subName); ... } if (partsCount > 0){ layerName = nameParts.at(partsCount-1); }

即:先用layerLevelSeparator拆分全名,除最后一段外的每一段通过getOrCreateVirtualChild()逐级挂到虚拟节点下,最后一段作为实际图层的显示名。

三、功能总览

根据原文档 Features 一节,控件提供的能力可分为以下几类(各项功能均可在 lc_layertreewidget.cpp 中找到对应实现槽函数):

3.1 常规功能

  • 两种显示模式:层级树状(tree)与平面列表(flat list)。LC_LayerTreeWidget中的m_flatListMode控制该模式,toggleFlatView()槽函数负责切换,见 lc_layertreewidget.h;
  • 基于正则表达式的过滤:可"过滤图层"或"高亮匹配图层的图层";
  • 拖放(Drag&Drop)重组:通过拖动节点调整层级结构;
  • 图层层级的分组操作:对虚拟图层(组节点)的点击会递归作用于其全部后代图层。

3.2 预定义的图层类型

原文档定义了五类图层,源码中由LC_LayerTreeItem::LayerType枚举实现(见 lc_layertreeitem.h):

类型英文枚举值含义默认命名后缀
普通图层NORMAL常规图层,存放实体无
标注图层DIMENSIONAL为对应普通图层存放尺寸标注+
信息图层INFORMATIONAL存放注释与草图_meta
替代位置图层ALTERNATE_POSITION同一零件在不同位置的表达_pos
虚拟图层VIRTUAL其他类型图层的"容器",类似文件夹由分隔符切分出的中间片段

注意:RS_Layer::LayerType枚举同样包含这五类(见 rs_layer.h),LC_LayerTreeItem与RS_Layer共用同一套类型体系,类型标记会在rebuildModel()中通过layer->setLayerType(type)写回图层对象。

类型识别完全依赖"名称是否以指定后缀结尾",这在rebuildModel()中有明确实现:

if (layerName.endsWith(m_options->alternatePositionLayerNameSuffix)){ type = RS_Layer::LayerType::ALTERNATE_POSITION; ... } else if (layerName.endsWith(m_options->informationalLayerNameSuffix)){ type = RS_Layer::LayerType::INFORMATIONAL; ... } else if (layerName.endsWith(m_options->dimensionalLayerNameSuffix)){ type = RS_Layer::LayerType::DIMENSIONAL; ... }

这里有个重要细节:同名辅助图层会被自动挂到其主图层(primary layer)之下。例如Doors+会被识别为Doors的标注子图层;若Doors尚不存在,则它会以Doors为主节点挂载(findPrimaryLayerChild查找同父节点下已存在的同名主图层,见 lc_layertreemodel.cpp)。因此主图层必须排在辅助图层之前——源码注释明确指出依赖"incoming list 已正确排序",即主图层因排序在先已被插入树中。

3.3 工具栏动作

initButtonsBar()(见 lc_layertreewidget.cpp)构建了完整按钮条,与文档列举一一对应:

  • 显示所有图层(Show all Layers)
  • 隐藏除当前图层外的所有图层(Hide all Layers except current)
  • 显示/隐藏次级图层(Show/Hide Secondary Layers,按钮可勾选,默认可见)
  • 仅显示活动图层(Show Active Layer Only)
  • 全部展开(Expand All,树模式)
  • 全部折叠(Collapse All,树模式)
  • 折叠次级图层(Collapse Secondary,树模式)
  • 全部锁定 / 全部解锁(Lock/Unlock All Layers)
  • 添加图层(Add a layer)
  • 添加标注图层(Add Dimensions Layer)
  • 移除图层(Remove layer)
  • 编辑/重命名图层(Modify layer attributes / rename)
  • 平面列表模式切换(Flat List Mode)

此外,updateToolBarButtons()会根据当前状态(如是否树模式)动态启用/禁用部分按钮。

3.4 过滤框(Filtering Box)

过滤框由过滤输入框(QLineEdit)、Highlight Mode勾选框与设置按钮组成,见initFilterAndSettingsSection()(lc_layertreewidget.cpp):

  • 高亮模式(Highlight Mode):默认勾选。匹配正则的图层项在列表中高亮显示;
  • 过滤模式:取消勾选后,仅显示匹配正则的图层项,其余全部隐藏。

两种模式由m_layerTreeModel->setFilteringRegexp(mask, highlightMode)驱动(见 lc_layertreemodel.h),模型内部以QRegularExpression m_filteringRegexp保存表达式,并在rebuildModel()中对每个图层名执行hasMatch()匹配。

3.5 图层树视图(Layers Tree View)

  • 展示基于命名约定的逻辑结构;
  • 显示图层类型图标(普通、虚拟、标注、信息、替代位置各有独立图标,见 lc_layertreemodel.h);
  • 提供图层标志列:冻结(可见性)、锁定、打印、构造,模型列定义见 lc_layertreemodel.h;
  • 标志列支持 GROUP 操作:点击虚拟图层(组)某一列的标志,会递归应用到其后代图层。其实现位于slotTreeClicked()(lc_layertreewidget.cpp):虚拟图层通过layerItem->collectLayers(...)收集后代并统一调用manageLayersVisibilityFlag/manageLayersLockFlag/manageLayersPrintFlag/manageLayersConstructionFlag;
  • 拖放重组层级(LC_LayerTreeView重写dragEnterEvent/dropEvent,模型提供performReStructure(),见 lc_layertreeview.h);
  • 双击虚拟图层:展开其后代子项;
  • 双击实际图层:弹出图层编辑对话框;
  • 项目感知的右键上下文菜单(见下文);
  • 可自定义外观(颜色、显示项);
  • 悬停显示完整图层名作为工具提示(showToolTips选项控制);
  • 可按层级深度对图层名进行可选缩进(showIndentedName选项控制)。

3.6 图层操作(Operations With Layers)

原文档列出的全部操作在源码中均有对应实现:

操作说明主要源码位置
图层创建支持按图层类型使用默认画笔invokeLayerAddDialog/getDefaultPen()
图层编辑名称、画笔、类型、构造标志等LC_LayerDialogEx(lc_layerdialog_ex.h)
层级感知重命名主图层重命名时可按选项联动重命名后代(次级)图层renamePrimaryLayer()(lc_layertreemodel.cpp)
单个图层删除删除指定图层doRemoveLayers
层级感知删除删除"图层及其子图层"或"仅子图层"doRemoveLayersFromSource(source, removeChildrenOnly)(lc_layertreewidget.cpp)
添加指定类型的子图层为普通图层添加标注/信息/替代位置子层addDimensionalLayerForSelectedItem等
改变图层类型普通 ↔ 标注/信息/替代位置互转convertLayerTypeToXxx
冻结除选中图层外的所有图层Freeze OthershideOtherThanSelectedLayers
复制图层(层级感知)复制层级结构并拷贝属性,不复制实体doCreateLayersCopy(..., false)
复制图层(层级感知)复制结构+属性,并同时复制源图层上的实体doCreateLayersCopy(..., true)
移除空图层删除不含实体的图层removeEmptyLayers

其中"复制"与"复制含内容"的区别值得展开:doCreateLayersCopy()先通过m_layerTreeModel->createLayersCopy()生成结构副本,再在LC_DocumentModificationBatch事务内逐个copyLayerAttributes()复制属性;若duplicateEntities为真,则遍历文档实体,将源图层上的实体clone()后设置到副本图层(duplicateLayerEntities(),见 lc_layertreewidget.cpp)。属性复制覆盖构造/转换/打印标志、画笔的颜色/宽度/线型/透明度/屏幕宽度/虚线偏移等,并同步冻结状态。

3.7 实体操作(Operations With Entities)

  • 选择图层实体(层级感知):选中图层(含子层)上的实体;
  • 移动当前选中实体到图层(moveSelectionToLayer);
  • 复制当前选中实体到图层(duplicateSelectionToLayer)。

两者最终都进入doMoveSelectionToLayer(layerItem, duplicate, resolvePens)(lc_layertreewidget.cpp):通过undoableModify事务克隆实体并设置目标图层,移动模式额外从文档移除原实体;resolvePens=true时还会把By Layer/By Block的画笔解析为具体值,保证实体迁移到目标图层后外观不变。

3.8 右键上下文菜单

onCustomContextMenu()(lc_layertreewidget.cpp)根据点击对象动态生成菜单:

  • 虚拟图层:添加子图层、重命名、移除(子树)、复制结构(子树)、复制内容(子树)、选择实体(子树);
  • 实际普通图层:编辑图层属性、移除图层、添加标注/信息/替代位置子图层(仅在该类型子层尚不存在时出现)、移除子图层、类型转换(无子层时)、选择图层实体、创建图层副本、复制含内容图层、移动/复制当前选中实体到该图层;
  • 空白区域:添加图层;
  • 全局动作(对所有场景):冻结其他图层、解冻所有图层、隐藏除当前外图层、锁定/解锁所有图层、启用/禁用所有图层打印、导出单个图层/图层子树/可见图层、查找并移除空图层。

四、命名约定与配置项(Customization Options)

这是控件最核心的可配置部分。所有选项由LC_LayerTreeModelOptions结构体承载(见 lc_layertreemodel_options.h),并通过load()/save()持久化到 LibreCAD 设置文件的Widget.LayerTree组(见 lc_layertreemodel_options.cpp)。

4.1 命名约定(Naming Convention)

配置项(源码成员)持久化键名默认值作用
layerLevelSeparatornamingLayerSeparator-图层层级的分隔子串,用于切分图层名各级
informationalLayerNameSuffixnamingInfoSuffix_meta标识信息图层(注释/草图)的后缀
dimensionalLayerNameSuffixnamingDimSuffix+标识标注图层的后缀
alternatePositionLayerNameSuffixnamingAltSuffix_pos标识替代位置图层的后缀
copiedNamePathPrefixnamingCopyPrefix(Copy自动生成副本名前缀(防止名称冲突)
copiedNamePathSuffixnamingCopySuffix)自动生成副本名后缀(与序号组合)

举个直观例子:图层名Walls-Outer-North会被解析为层级Walls→Outer→North;Walls-Inner_meta则成为Walls-Inner的信息子图层;Walls-Outer-South_pos是Walls-Outer-South的替代位置子图层。这一推导逻辑与 tests/lc_layertreemodel_tests.cpp 中的测试数据完全一致(测试用Doors、Doors+、Walls-Inner、Walls-Inner_meta、Walls-Outer-North、Walls-Outer-South_pos构建了三层深度:组Walls与Walls-Outer,以及Doors、Walls-Inner下的次级图层)。

4.2 显示选项(Display Settings)

配置项持久化键名默认值作用
showIndentedNameshowIndentedNametrue图层名按层级缩进
identSizeindentSize4缩进宽度(像素级间距)
showToolTipsshowToolTipstrue悬停时显示完整图层名提示
renameSecondaryLayersOnPrimaryRenamerenameSecondaryOnPrimarytrue主图层重命名时联动重命名次级图层
hideLayerTypeIconshideLayerTypeIconsfalse是否隐藏图层类型图标
dragDropEnableddragDropEnabledtrue是否允许拖放重组
showGridshowGridtrue树视图中是否显示网格线

4.3 颜色(Colors)

配置项持久化键名默认值(浅色)作用
matchedItemColor(跟随loadDefaults)blue正则匹配项的高亮颜色
virtualLayerBgColorvirtualLayerBgColorrgb(245,245,245)虚拟图层背景色
selectedItemBgColorselectedItemBgColorrgb(245,245,245)选中项背景色
activeLayerBgColoractiveLayerBgColorwhite活动图层背景色

注意:loadDefaults(bool isDark)会根据当前配色方案是否为深色自动切换默认值——深色方案下虚拟/选中/活动图层的背景取自系统QPalette(AlternateBase/Highlight/Mid),见 lc_layertreemodel_options.cpp。

4.4 每种图层类型的默认画笔(Default Pens)

配置项持久化键名默认值说明
defaultPenNormalNormalLayer黑色、Width00、实线普通图层创建对话框中的默认画笔
defaultPenDimensionalDimensionalLayer蓝色、Width02、实线标注图层
defaultPenInformationalInfoLayer品红、Width02、实线信息图层
defaultPenAlternatePositionAltPosLayer青色、Width00、实线替代位置图层

getDefaultPen(layerType)按类型返回对应画笔,图层创建对话框会将该画笔预置为初始值(见 lc_layertreemodel_options.cpp)。画笔通过RS_Settings::writePen/readPen持久化,可在"图层树设置"对话框中修改。

4.5 设置对话框

上述绝大多数配置均可通过过滤框右侧的设置按钮(invokeSettingsDialog)打开设置对话框进行可视化调整,其 UI 定义位于 lc_layertreeoptionsdialog.ui。保存时会调用LC_LayerTreeModelOptions::save(),写入上述持久化键。

五、控件的注册与使用方式

图层树控件以停靠窗口(QDockWidget)形式集成在主程序中。LC_WidgetFactory::createLayerTreeWidget()(见 lc_widgetfactory.cpp)创建名为"Layers Tree"(layer_tree_dockwidget)的停靠窗口,并实例化LC_LayerTreeWidget:

QDockWidget* dock = createDockWidget(tr("Layers Tree"), "layer_tree_dockwidget", tr("Layers Tree")); const auto widget = new LC_LayerTreeWidget(actionHandler, dock, "Layer Tree"); dock->setWidget(widget);

因此使用方式非常直接:在 LibreCAD 主界面的 View/窗口菜单中找到"Layers Tree"停靠面板打开即可,它可与传统的"Layers"列表面板(QG_LayerWidget,见同文件 lc_widgetfactory.cpp)并行使用,二者数据源一致。

LC_LayerTreeWidget实现了RS_LayerListListener接口,监听图层列表的增删改、激活与冻结/锁定/打印/构造标志变化(见 lc_layertreewidget.h),保证树视图与文档状态实时同步。

六、实现说明(Implementation Notes)与架构评价

原文档对实现思路给出了坦诚的说明:

  • 纯 UI 实现:不触及图层的内部结构或底层存储格式,原始平面图层列表不受影响,两个控件可并行;
  • 最小化重构:尽量少改动既有代码,避免引入回归;
  • 零新增依赖:仅使用纯 C++/Qt 代码实现;
  • 隔离性:实现高度自包含,但也因此在架构层面略显"游离",文档指出未来可能重构的候选对象包括:动作类(actions)、实体操作代码、以及原始图层列表(RS_LayersList)。

从源码观察可以补充两点佐证:

  1. RS_Layer新增的m_layerType是瞬态字段,仅由 LayerTreeWidget 更新,不会写入 DXF,这正是"不影响底层存储格式"的直接体现(见 rs_layer.h);
  2. 图层树模型的内存管理已纳入测试覆盖:tests/lc_layertreemodel_tests.cpp通过堆探针验证模型与控件销毁后不留内存泄漏,并复现了文档描述的树构建行为(6 个图层 + 2 个虚拟组 = 8 行)与 100 次循环构建后堆不再增长的结论(见 lc_layertreemodel_tests.cpp)。

七、本地化(Localization)

控件中所有可见字符串均通过tr()包裹,具备完整的本地化能力;实际翻译值由各语言翻译文件(.ts)提供。例如过滤框提示"Filter"、按钮提示"Show all layers"、"Add Dimensions Layer"等均可在 librecad/ts 目录下的语言文件中找到对应条目。

八、最佳实践小结

结合原文档与源码实现,使用图层树控件时建议遵循以下约定:

  1. 统一层级分隔符:全图使用同一个分隔子串(默认-)组织层级,如Chassis-Frame-Bracket;
  2. 主图层先创建:由于次级图层会自动挂到同名主图层之下,应确保主图层(如Doors)先于其辅助层(如Doors+)存在;
  3. 按后缀自动分类:尺寸标注统一使用+后缀、注释统一使用_meta、替代位置统一使用_pos,即可自动归入对应子图层并享受分组标志操作;
  4. 善用正则过滤:高亮模式下用正则快速定位,过滤模式下只保留目标图层,适合超多图层的图纸;
  5. 充分利用分组操作:对虚拟图层(组)执行冻结/锁定/打印切换、移除、复制等操作,可一次性作用于整棵子树;
  6. 用 Copy 与 Duplicate 区分需求:仅要结构拷贝用"Create Layer Copy",连实体一起复制用"Duplicate Layer With Content";
  7. 命名与层级调整:通过拖放重组层级、批量重命名次级图层(renameSecondaryLayersOnPrimaryRename)可随时修正命名约定,无需手工逐层修改。

结语

Layer Tree View 以极低侵入的方式解决了 DXF 无图层层级这一历史约束:全部依赖命名约定,纯 UI 层实现,不修改底层存储,同时把图层管理从"手工前缀分组"升级为"可折叠树 + 类型子层 + 正则过滤 + 分组批量操作"的完整工作流。无论你是处理数百图层的复杂装配图,还是希望统一管理尺寸与注释,都可以直接在主界面打开 "Layers Tree" 面板投入使用。

  • 桌面应用
  • 图形学

【免费下载链接】LibreCAD

LibreCAD is a cross-platform 2D CAD program. It can read DXF/DWG, and write DXF/DWG/PDF/SVG files. It supports point/line/circle/ellipse/parabola/hyperbola/spline primitives. The GUI is highly customizable, and has dozens of translations.

项目地址:https://gitcode.com/gh_mirrors/li/LibreCAD
点击查看免费下载
上一篇:ROS-LLM 开源项目教程
下一篇:DeskGap安全最佳实践:保护你的桌面应用免受威胁

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询