插件系统与开发实战
第十五章 插件系统与开发实战
15.1 插件系统概述
15.1.1 插件架构
LibreCAD采用Qt插件机制,允许动态加载扩展功能:
┌─────────────────────────────────────────────┐
│ LibreCAD 主程序 │
├─────────────────────────────────────────────┤
│ 插件接口层 │
│ ┌──────────────────────────────────────┐ │
│ │ QC_PluginInterface │ │
│ │ Document_Interface │ │
│ └──────────────────────────────────────┘ │
├─────────────────────────────────────────────┤
│ 插件加载器 │
└─────────────────────────────────────────────┘
↑ ↑ ↑
┌─────┴─┐ ┌────┴────┐ ┌─┴────┐
│Plugin1│ │ Plugin2 │ │ ... │
└───────┘ └─────────┘ └──────┘
15.1.2 官方插件列表
LibreCAD自带的官方插件:
| 插件 | 功能 |
|---|---|
| align | 对象对齐 |
| asciifile | ASCII文件导入 |
| divide | 等分线段 |
| gear | 齿轮绘制 |
| list | 列表显示 |
| picfile | 图片文件处理 |
| plotequation | 方程曲线绘制 |
| pointstocsv | 点导出CSV |
| sameprop | 相同属性选择 |
| sample | 示例插件 |
15.1.3 插件目录
插件文件位置:
- Windows:
%APPDATA%\LibreCAD\plugins\或安装目录 - Linux:
~/.local/share/librecad/plugins/或/usr/share/librecad/plugins/ - macOS:
~/Library/Application Support/LibreCAD/plugins/
15.2 插件接口详解
15.2.1 QC_PluginInterface
所有插件必须实现的接口:
// qc_plugininterface.h
#ifndef QC_PLUGININTERFACE_H
#define QC_PLUGININTERFACE_H
#include <QtPlugin>
#include <QString>
#include <QStringList>
#include "document_interface.h"
#define QC_PLUGININTERFACE_IID "org.librecad.PluginInterface"
class QC_PluginInterface {
public:
virtual ~QC_PluginInterface() = default;
// 插件名称
virtual QString name() const = 0;
// 插件功能列表
virtual PluginCapabilities getCapabilities() const = 0;
// 执行命令
virtual void execComm(Document_Interface* doc,
QWidget* parent,
QString cmd) = 0;
};
Q_DECLARE_INTERFACE(QC_PluginInterface, QC_PLUGININTERFACE_IID)
#endif
15.2.2 PluginCapabilities
插件能力描述:
// 能力标志
enum PluginCapabilities {
PluginNothing = 0, // 无特殊能力
PluginDrawing = 1 << 0, // 绘图功能
PluginFileIO = 1 << 1, // 文件I/O
PluginModification = 1 << 2, // 修改功能
PluginInformation = 1 << 3 // 信息功能
};
// 菜单入口
struct PluginMenuEntry {
QString menuName; // 菜单名称
QString command; // 命令标识
};
// 完整能力结构
struct PluginCapabilities {
PluginCapabilities flags;
QStringList commands; // 命令列表
QList<PluginMenuEntry> menuEntries; // 菜单项
};
15.2.3 Document_Interface
与图形文档交互的接口:
// document_interface.h (部分)
class Document_Interface {
public:
virtual ~Document_Interface() = default;
// ===== 实体创建 =====
// 添加点
virtual void addPoint(QPointF* start) = 0;
// 添加直线
virtual void addLine(QPointF* start, QPointF* end) = 0;
// 添加圆
virtual void addCircle(QPointF* center, qreal radius) = 0;
// 添加圆弧
virtual void addArc(QPointF* center, qreal radius,
qreal startAngle, qreal endAngle) = 0;
// 添加椭圆
virtual void addEllipse(QPointF* center, QPointF* majorP,
qreal ratio, qreal angle1, qreal angle2) = 0;
// 添加文字
virtual void addText(QString text, QString style,
QPointF* start, qreal height,
qreal angle, DPI::HAlign ha, DPI::VAlign va) = 0;
// 添加折线
virtual void addPolyline(QList<Plug_VertexData>& data,
bool closed = false) = 0;
// 添加样条曲线
virtual void addSpline(QList<QPointF>& points, bool closed = false) = 0;
// ===== 属性设置 =====
// 设置当前图层
virtual void setLayer(QString name) = 0;
// 获取当前图层
virtual QString getCurrentLayer() = 0;
// 获取图层列表
virtual QStringList getLayers() = 0;
// 添加图层
virtual void addLayer(QString name) = 0;
// 设置颜色
virtual void setColor(int r, int g, int b) = 0;
// 设置线宽
virtual void setWidth(DPI::LineWidth width) = 0;
// 设置线型
virtual void setLineType(DPI::LineType type) = 0;
// ===== 文档操作 =====
// 获取文件名
virtual QString getFilename() = 0;
// 获取所选实体
virtual QList<Plug_Entity*> getSelectedEntities() = 0;
// 获取所有实体
virtual QList<Plug_Entity*> getAllEntities() = 0;
// 更新视图
virtual void updateView() = 0;
// 获取点(用户输入)
virtual QPointF getPoint(QString message) = 0;
// 获取实数(用户输入)
virtual qreal getReal(QString message, qreal default_value = 0.0) = 0;
// 获取整数
virtual int getInt(QString message, int default_value = 0) = 0;
// 获取字符串
virtual QString getString(QString message, QString default_value = "") = 0;
// ===== 撤销支持 =====
virtual void startUndoCycle() = 0;
virtual void endUndoCycle() = 0;
};
15.2.4 Plug_Entity
实体操作接口:
class Plug_Entity {
public:
virtual ~Plug_Entity() = default;
// 获取实体类型
virtual DPI::EntityType getType() const = 0;
// 获取数据
virtual QHash<int, QVariant> getData() const = 0;
// 更新数据
virtual void updateData(QHash<int, QVariant>& data) = 0;
// 获取点列表
virtual void getPoints(QList<QPointF>& points) const = 0;
// 移动
virtual void move(QPointF offset) = 0;
// 旋转
virtual void rotate(QPointF center, qreal angle) = 0;
// 缩放
virtual void scale(QPointF center, qreal factor) = 0;
};
15.3 创建插件项目
15.3.1 项目结构
myplugin/
├── CMakeLists.txt # CMake配置
├── myplugin.pro # qmake配置(可选)
├── myplugin.h # 插件头文件
├── myplugin.cpp # 插件实现
├── myplugin.json # 插件元数据
└── ts/ # 翻译文件
├── myplugin_zh_CN.ts
└── myplugin_en.ts
15.3.2 CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(myplugin)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)
# 查找Qt
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
# 包含目录
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${LIBRECAD_INCLUDE_DIR}/plugins
)
# 创建插件库
add_library(myplugin SHARED
myplugin.cpp
myplugin.h
)
# 链接库
target_link_libraries(myplugin
Qt6::Core
Qt6::Gui
Qt6::Widgets
)
# 设置输出目录
set_target_properties(myplugin PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins
)
15.3.3 qmake配置(可选)
# myplugin.pro
include(../../common.pri)
TEMPLATE = lib
CONFIG += plugin
TARGET = myplugin
HEADERS = myplugin.h
SOURCES = myplugin.cpp
INCLUDEPATH += ../../librecad/src/plugins
DESTDIR = ../../unix/resources/plugins
15.3.4 插件元数据
// myplugin.json
{
"name": "My Custom Plugin",
"version": "1.0.0",
"author": "Your Name",
"description": "A custom plugin for LibreCAD",
"license": "GPL-2.0"
}
15.4 插件开发实战
15.4.1 简单插件示例
创建一个绘制正多边形的插件:
// polygon_plugin.h
#ifndef POLYGON_PLUGIN_H
#define POLYGON_PLUGIN_H
#include <QObject>
#include <QtPlugin>
#include "qc_plugininterface.h"
class PolygonPlugin : public QObject, public QC_PluginInterface {
Q_OBJECT
Q_PLUGIN_METADATA(IID QC_PLUGININTERFACE_IID FILE "polygon_plugin.json")
Q_INTERFACES(QC_PluginInterface)
public:
explicit PolygonPlugin(QObject* parent = nullptr);
virtual ~PolygonPlugin() = default;
// 实现接口
QString name() const override;
PluginCapabilities getCapabilities() const override;
void execComm(Document_Interface* doc,
QWidget* parent,
QString cmd) override;
private:
void drawPolygon(Document_Interface* doc, QWidget* parent);
};
#endif
// polygon_plugin.cpp
#include "polygon_plugin.h"
#include <QInputDialog>
#include <QMessageBox>
#include <cmath>
PolygonPlugin::PolygonPlugin(QObject* parent)
: QObject(parent) {
}
QString PolygonPlugin::name() const {
return "Polygon Plugin";
}
PluginCapabilities PolygonPlugin::getCapabilities() const {
PluginCapabilities caps;
caps.flags = PluginDrawing;
caps.commands << "polygon" << "pl";
PluginMenuEntry entry;
entry.menuName = tr("绘制正多边形");
entry.command = "polygon";
caps.menuEntries << entry;
return caps;
}
void PolygonPlugin::execComm(Document_Interface* doc,
QWidget* parent,
QString cmd) {
Q_UNUSED(cmd)
drawPolygon(doc, parent);
}
void PolygonPlugin::drawPolygon(Document_Interface* doc,
QWidget* parent) {
// 获取边数
bool ok;
int sides = QInputDialog::getInt(
parent,
tr("正多边形"),
tr("输入边数:"),
6, // 默认值
3, // 最小值
100, // 最大值
1, // 步长
&ok
);
if (!ok) return;
// 获取中心点
QPointF center = doc->getPoint(tr("指定中心点:"));
// 获取半径
qreal radius = doc->getReal(tr("指定外接圆半径:"), 50.0);
if (radius <= 0) {
QMessageBox::warning(parent, tr("错误"), tr("半径必须大于0"));
return;
}
// 开始撤销周期
doc->startUndoCycle();
// 计算顶点并创建折线
QList<Plug_VertexData> vertices;
for (int i = 0; i < sides; i++) {
double angle = 2.0 * M_PI * i / sides - M_PI / 2;
QPointF vertex(
center.x() + radius * cos(angle),
center.y() + radius * sin(angle)
);
Plug_VertexData vd;
vd.point = vertex;
vd.bulge = 0.0;
vertices.append(vd);
}
// 添加闭合折线
doc->addPolyline(vertices, true);
// 结束撤销周期
doc->endUndoCycle();
// 更新视图
doc->updateView();
}
15.4.2 齿轮插件分析
分析官方gear插件的实现:
// gear/gear.cpp (简化版)
#include "gear.h"
#include <QDialog>
#include <QFormLayout>
#include <QDoubleSpinBox>
#include <QPushButton>
void GearPlugin::execComm(Document_Interface* doc,
QWidget* parent,
QString cmd) {
Q_UNUSED(cmd)
// 创建参数对话框
QDialog dialog(parent);
dialog.setWindowTitle(tr("齿轮参数"));
QFormLayout* layout = new QFormLayout(&dialog);
QDoubleSpinBox* moduleBox = new QDoubleSpinBox;
moduleBox->setRange(0.1, 100.0);
moduleBox->setValue(2.0);
layout->addRow(tr("模数:"), moduleBox);
QSpinBox* teethBox = new QSpinBox;
teethBox->setRange(6, 200);
teethBox->setValue(20);
layout->addRow(tr("齿数:"), teethBox);
QDoubleSpinBox* pressureBox = new QDoubleSpinBox;
pressureBox->setRange(14.5, 25.0);
pressureBox->setValue(20.0);
layout->addRow(tr("压力角:"), pressureBox);
QPushButton* okButton = new QPushButton(tr("确定"));
connect(okButton, &QPushButton::clicked, &dialog, &QDialog::accept);
layout->addRow(okButton);
if (dialog.exec() != QDialog::Accepted) return;
// 获取参数
double module = moduleBox->value();
int teeth = teethBox->value();
double pressure = pressureBox->value();
// 获取中心点
QPointF center = doc->getPoint(tr("指定齿轮中心:"));
// 绘制齿轮
drawGear(doc, center, module, teeth, pressure);
}
void GearPlugin::drawGear(Document_Interface* doc,
QPointF center,
double module,
int teeth,
double pressureAngle) {
doc->startUndoCycle();
// 计算齿轮参数
double pitchRadius = module * teeth / 2.0;
double addendum = module;
double dedendum = 1.25 * module;
double outerRadius = pitchRadius + addendum;
double rootRadius = pitchRadius - dedendum;
double baseRadius = pitchRadius * cos(pressureAngle * M_PI / 180.0);
// 生成齿形
QList<Plug_VertexData> profile;
for (int i = 0; i < teeth; i++) {
double toothAngle = 2.0 * M_PI * i / teeth;
// 每个齿的轮廓点
// ... 渐开线计算 ...
// 添加到轮廓
}
// 添加折线
doc->addPolyline(profile, true);
// 添加节圆(参考)
doc->setLineType(DPI::DashLine);
doc->addCircle(¢er, pitchRadius);
doc->setLineType(DPI::SolidLine);
doc->endUndoCycle();
doc->updateView();
}
15.4.3 文件导入插件
创建从CSV导入点的插件:
// csv_import.cpp
#include "csv_import.h"
#include <QFileDialog>
#include <QFile>
#include <QTextStream>
#include <QMessageBox>
void CSVImportPlugin::execComm(Document_Interface* doc,
QWidget* parent,
QString cmd) {
// 选择文件
QString filename = QFileDialog::getOpenFileName(
parent,
tr("选择CSV文件"),
QString(),
tr("CSV文件 (*.csv);;所有文件 (*.*)")
);
if (filename.isEmpty()) return;
QFile file(filename);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox::critical(parent, tr("错误"),
tr("无法打开文件"));
return;
}
doc->startUndoCycle();
QTextStream in(&file);
int lineNumber = 0;
int pointCount = 0;
while (!in.atEnd()) {
QString line = in.readLine();
lineNumber++;
// 跳过空行和标题行
if (line.isEmpty() || lineNumber == 1) continue;
// 解析CSV行
QStringList parts = line.split(',');
if (parts.size() < 2) continue;
bool okX, okY;
double x = parts[0].trimmed().toDouble(&okX);
double y = parts[1].trimmed().toDouble(&okY);
if (okX && okY) {
QPointF point(x, y);
doc->addPoint(&point);
pointCount++;
}
}
file.close();
doc->endUndoCycle();
doc->updateView();
QMessageBox::information(parent, tr("导入完成"),
tr("成功导入 %1 个点").arg(pointCount));
}
15.5 插件UI开发
15.5.1 使用Qt Designer
创建可视化的参数对话框:
// 使用.ui文件
#include "ui_myplugindialog.h"
class MyPluginDialog : public QDialog {
Q_OBJECT
public:
explicit MyPluginDialog(QWidget* parent = nullptr)
: QDialog(parent)
, ui(new Ui::MyPluginDialog) {
ui->setupUi(this);
// 连接信号槽
connect(ui->okButton, &QPushButton::clicked,
this, &QDialog::accept);
connect(ui->cancelButton, &QPushButton::clicked,
this, &QDialog::reject);
}
~MyPluginDialog() {
delete ui;
}
// 获取参数
double getRadius() const {
return ui->radiusSpinBox->value();
}
int getSides() const {
return ui->sidesSpinBox->value();
}
private:
Ui::MyPluginDialog* ui;
};
15.5.2 纯代码UI
QDialog* createParameterDialog(QWidget* parent) {
QDialog* dialog = new QDialog(parent);
dialog->setWindowTitle(tr("参数设置"));
dialog->setMinimumWidth(300);
QVBoxLayout* mainLayout = new QVBoxLayout(dialog);
// 参数组
QGroupBox* paramGroup = new QGroupBox(tr("参数"));
QFormLayout* formLayout = new QFormLayout(paramGroup);
QDoubleSpinBox* radiusBox = new QDoubleSpinBox;
radiusBox->setRange(0.1, 10000.0);
radiusBox->setValue(50.0);
radiusBox->setDecimals(2);
formLayout->addRow(tr("半径:"), radiusBox);
QSpinBox* sidesBox = new QSpinBox;
sidesBox->setRange(3, 100);
sidesBox->setValue(6);
formLayout->addRow(tr("边数:"), sidesBox);
mainLayout->addWidget(paramGroup);
// 按钮
QHBoxLayout* buttonLayout = new QHBoxLayout;
QPushButton* okButton = new QPushButton(tr("确定"));
QPushButton* cancelButton = new QPushButton(tr("取消"));
buttonLayout->addStretch();
buttonLayout->addWidget(okButton);
buttonLayout->addWidget(cancelButton);
mainLayout->addLayout(buttonLayout);
QObject::connect(okButton, &QPushButton::clicked,
dialog, &QDialog::accept);
QObject::connect(cancelButton, &QPushButton::clicked,
dialog, &QDialog::reject);
return dialog;
}
15.6 插件调试
15.6.1 调试输出
#include <QDebug>
void MyPlugin::execComm(Document_Interface* doc,
QWidget* parent,
QString cmd) {
qDebug() << "Plugin executed with command:" << cmd;
// 调试实体数据
QList<Plug_Entity*> entities = doc->getSelectedEntities();
qDebug() << "Selected entities:" << entities.size();
for (Plug_Entity* entity : entities) {
qDebug() << "Entity type:" << entity->getType();
QHash<int, QVariant> data = entity->getData();
qDebug() << "Entity data:" << data;
}
}
15.6.2 在Qt Creator中调试
- 设置LibreCAD作为启动程序
- 设置插件输出目录
- 添加断点
- 启动调试
15.6.3 日志文件
void writeLog(const QString& message) {
QFile file(QDir::homePath() + "/myplugin.log");
if (file.open(QIODevice::Append | QIODevice::Text)) {
QTextStream out(&file);
out << QDateTime::currentDateTime().toString()
<< " - " << message << "\n";
file.close();
}
}
15.7 插件国际化
15.7.1 使用tr()
QString PolygonPlugin::name() const {
return tr("正多边形插件");
}
void PolygonPlugin::drawPolygon(Document_Interface* doc,
QWidget* parent) {
QMessageBox::information(parent,
tr("提示"),
tr("请指定多边形的中心点"));
}
15.7.2 翻译文件
创建翻译文件:
# 提取需要翻译的字符串
lupdate myplugin.cpp -ts ts/myplugin_zh_CN.ts
# 使用Qt Linguist编辑翻译
# 编译翻译文件
lrelease ts/myplugin_zh_CN.ts -qm ts/myplugin_zh_CN.qm
15.7.3 加载翻译
class MyPlugin : public QObject, public QC_PluginInterface {
public:
MyPlugin() {
// 加载翻译文件
QTranslator* translator = new QTranslator(this);
QString locale = QLocale::system().name();
if (translator->load("myplugin_" + locale, ":/translations")) {
QCoreApplication::installTranslator(translator);
}
}
};
15.8 发布插件
15.8.1 打包清单
myplugin-1.0.0/
├── myplugin.dll (Windows) 或
│ libmyplugin.so (Linux) 或
│ libmyplugin.dylib (macOS)
├── translations/
│ ├── myplugin_zh_CN.qm
│ └── myplugin_en.qm
├── README.md
├── LICENSE
└── CHANGELOG.md
15.8.2 安装说明
# MyPlugin 安装说明
## Windows
1. 将 myplugin.dll 复制到 LibreCAD 安装目录下的 plugins 文件夹
2. 将翻译文件复制到 translations 文件夹
3. 重启 LibreCAD
## Linux
1. 将 libmyplugin.so 复制到 ~/.local/share/librecad/plugins/
2. 重启 LibreCAD
## macOS
1. 将 libmyplugin.dylib 复制到 ~/Library/Application Support/LibreCAD/plugins/
2. 重启 LibreCAD
15.8.3 版本兼容性
// 检查接口版本
#define PLUGIN_INTERFACE_VERSION 1
class MyPlugin : public QC_PluginInterface {
public:
int getInterfaceVersion() const {
return PLUGIN_INTERFACE_VERSION;
}
};
15.9 本章小结
本章介绍了LibreCAD的插件系统:
- 插件架构:Qt插件机制、官方插件
- 插件接口:QC_PluginInterface、Document_Interface
- 创建项目:项目结构、CMake/qmake配置
- 开发实战:正多边形、齿轮、CSV导入
- UI开发:Qt Designer、纯代码UI
- 调试技巧:调试输出、Qt Creator调试
- 国际化:tr()、翻译文件
- 发布:打包、安装说明
插件开发是扩展LibreCAD功能的有效方式,可以根据需要添加自定义功能。
上一章:Action系统与命令开发 | 下一章:二次开发进阶与最佳实践

浙公网安备 33010602011771号