第13章-C++插件开发
第十三章:C++插件开发
13.1 C++插件概述
13.1.1 插件系统介绍
QCAD的C++插件系统允许开发者:
- 扩展核心功能
- 集成外部库
- 实现高性能处理
- 添加新的文件格式支持
- 创建自定义实体类型
插件类型:
| 类型 | 说明 | 示例 |
|---|---|---|
| 文件格式插件 | 支持新的文件格式 | DWG导入/导出 |
| 实体插件 | 新的CAD实体类型 | 自定义图元 |
| 功能插件 | 扩展功能 | 特殊计算、外部集成 |
| 界面插件 | UI扩展 | 自定义面板、工具栏 |
13.1.2 插件接口
所有QCAD插件必须实现RPluginInterface:
class RPluginInterface {
public:
virtual ~RPluginInterface() {}
// 插件初始化
virtual bool init() = 0;
// 插件卸载
virtual void uninit(bool remove = false) = 0;
// 插件信息
virtual QString getTitle() const = 0;
virtual QString getVersion() const = 0;
virtual QString getDescription() const = 0;
virtual QString getAuthor() const = 0;
virtual QString getLicense() const = 0;
virtual QString getUrl() const = 0;
// 初始化脚本
virtual void initScriptExtensions(QScriptEngine& engine) = 0;
// 后初始化(在所有插件加载后调用)
virtual void postInit() {}
};
13.1.3 开发环境准备
必要工具:
- Qt 5.12+ 或 Qt 6.x
- C++编译器(MSVC/GCC/Clang)
- CMake 3.10+ 或 QMake
- QCAD源代码
开发环境设置(以Qt Creator为例):
- 安装Qt和Qt Creator
- 获取QCAD源代码
- 配置Qt版本与QCAD匹配
- 创建插件项目
13.2 创建基本插件
13.2.1 项目结构
MyPlugin/
├── CMakeLists.txt # CMake配置
├── MyPlugin.pro # QMake配置(可选)
├── RMyPlugin.cpp # 插件实现
├── RMyPlugin.h # 插件头文件
└── scripts/ # 关联的脚本文件
└── MyPlugin/
└── MyPlugin.js
13.2.2 插件头文件
// RMyPlugin.h
#ifndef RMYPLUGIN_H
#define RMYPLUGIN_H
#include <QObject>
#include <QtPlugin>
#include "RPluginInterface.h"
class RMyPlugin : public QObject, public RPluginInterface {
Q_OBJECT
Q_INTERFACES(RPluginInterface)
Q_PLUGIN_METADATA(IID "org.qcad.RPluginInterface")
public:
// 构造函数
RMyPlugin();
virtual ~RMyPlugin();
// RPluginInterface 实现
virtual bool init() override;
virtual void uninit(bool remove = false) override;
virtual QString getTitle() const override {
return "我的插件";
}
virtual QString getVersion() const override {
return "1.0.0";
}
virtual QString getDescription() const override {
return "QCAD示例插件";
}
virtual QString getAuthor() const override {
return "开发者";
}
virtual QString getLicense() const override {
return "GPLv3";
}
virtual QString getUrl() const override {
return "https://example.com";
}
virtual void initScriptExtensions(QScriptEngine& engine) override;
virtual void postInit() override;
private:
// 私有成员
};
#endif // RMYPLUGIN_H
13.2.3 插件实现
// RMyPlugin.cpp
#include "RMyPlugin.h"
#include "RMainWindow.h"
#include "RDocumentInterface.h"
#include "RPluginLoader.h"
RMyPlugin::RMyPlugin() {
// 构造函数
}
RMyPlugin::~RMyPlugin() {
// 析构函数
}
bool RMyPlugin::init() {
qDebug() << "MyPlugin: 初始化中...";
// 注册资源
// Q_INIT_RESOURCE(myplugin_resources);
// 初始化代码
qDebug() << "MyPlugin: 初始化完成";
return true;
}
void RMyPlugin::uninit(bool remove) {
qDebug() << "MyPlugin: 卸载中...";
// 清理代码
if (remove) {
// 完全移除时的清理
}
qDebug() << "MyPlugin: 卸载完成";
}
void RMyPlugin::initScriptExtensions(QScriptEngine& engine) {
// 注册脚本扩展
// 将C++类暴露给JavaScript
// 示例:注册全局函数
// QScriptValue func = engine.newFunction(myFunction);
// engine.globalObject().setProperty("myFunction", func);
}
void RMyPlugin::postInit() {
qDebug() << "MyPlugin: 后初始化...";
// 所有插件加载完成后执行的代码
}
13.2.4 CMake配置
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyPlugin)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
# 查找Qt
find_package(Qt5 COMPONENTS Core Widgets Script REQUIRED)
# QCAD头文件路径
set(QCAD_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../qcad/src")
include_directories(
${QCAD_INCLUDE_DIR}/core
${QCAD_INCLUDE_DIR}/entity
${QCAD_INCLUDE_DIR}/gui
${QCAD_INCLUDE_DIR}/operations
)
# 源文件
set(SOURCES
RMyPlugin.cpp
)
set(HEADERS
RMyPlugin.h
)
# 创建插件库
add_library(${PROJECT_NAME} SHARED ${SOURCES} ${HEADERS})
# 链接库
target_link_libraries(${PROJECT_NAME}
Qt5::Core
Qt5::Widgets
Qt5::Script
qcadcore
qcadentity
)
# 输出目录
set_target_properties(${PROJECT_NAME} PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins"
)
13.2.5 QMake配置
# MyPlugin.pro
TARGET = myplugin
TEMPLATE = lib
CONFIG += plugin
QT += core widgets script
# QCAD路径
QCAD_ROOT = $$PWD/../qcad
INCLUDEPATH += \
$$QCAD_ROOT/src/core \
$$QCAD_ROOT/src/entity \
$$QCAD_ROOT/src/gui \
$$QCAD_ROOT/src/operations
HEADERS += RMyPlugin.h
SOURCES += RMyPlugin.cpp
# 输出目录
DESTDIR = $$QCAD_ROOT/plugins
13.3 操作文档
13.3.1 访问文档
#include "RDocument.h"
#include "RDocumentInterface.h"
#include "RMainWindow.h"
void RMyPlugin::processDocument() {
// 获取主窗口
RMainWindow* mainWindow = RMainWindow::getMainWindow();
if (!mainWindow) return;
// 获取当前文档接口
RDocumentInterface* di = mainWindow->getDocumentInterface();
if (!di) return;
// 获取文档
RDocument& doc = di->getDocument();
// 文档操作...
}
13.3.2 遍历实体
void RMyPlugin::iterateEntities(RDocument& doc) {
// 获取所有实体ID
QSet<REntity::Id> entityIds = doc.queryAllEntities();
for (REntity::Id id : entityIds) {
QSharedPointer<REntity> entity = doc.queryEntityDirect(id);
if (entity.isNull()) continue;
// 检查实体类型
if (entity->getType() == RS::EntityLine) {
QSharedPointer<RLineEntity> line =
entity.dynamicCast<RLineEntity>();
if (!line.isNull()) {
RVector start = line->getStartPoint();
RVector end = line->getEndPoint();
double length = line->getLength();
qDebug() << "直线: (" << start.x << "," << start.y
<< ") -> (" << end.x << "," << end.y << ")";
qDebug() << "长度:" << length;
}
}
}
}
13.3.3 添加实体
void RMyPlugin::addEntities(RDocumentInterface* di) {
RDocument& doc = di->getDocument();
// 开始事务
RTransaction transaction(di->getStorage(), "添加实体", true);
// 创建直线
QSharedPointer<RLineEntity> line(new RLineEntity(
&doc,
RLineData(RVector(0, 0), RVector(100, 100))
));
// 设置属性
line->setColor(RColor(Qt::red));
line->setLayerId(doc.getCurrentLayerId());
// 添加到事务
transaction.addObject(line);
// 创建圆
QSharedPointer<RCircleEntity> circle(new RCircleEntity(
&doc,
RCircleData(RVector(50, 50), 30)
));
circle->setColor(RColor(Qt::blue));
transaction.addObject(circle);
// 提交事务
di->applyTransactionToDocument(transaction);
}
13.3.4 修改实体
void RMyPlugin::modifyEntity(RDocumentInterface* di, REntity::Id entityId) {
RDocument& doc = di->getDocument();
QSharedPointer<REntity> entity = doc.queryEntityDirect(entityId);
if (entity.isNull()) return;
// 克隆实体进行修改
QSharedPointer<REntity> modified =
QSharedPointer<REntity>(entity->clone());
// 修改属性
modified->setColor(RColor(Qt::green));
modified->setLineweight(RLineweight::Weight050);
// 几何变换
modified->move(RVector(10, 10));
modified->rotate(M_PI / 4, RVector(0, 0));
// 应用修改
RTransaction transaction(di->getStorage(), "修改实体", true);
transaction.addObject(modified, false); // false = 修改而非添加
di->applyTransactionToDocument(transaction);
}
13.4 创建自定义实体
13.4.1 实体基类
// RMyEntityData.h
#ifndef RMYENTITYDATA_H
#define RMYENTITYDATA_H
#include "REntityData.h"
#include "RVector.h"
class RMyEntityData : public REntityData {
public:
RMyEntityData();
RMyEntityData(const RVector& center, double radius, int segments);
virtual ~RMyEntityData();
// 实现基类方法
virtual RS::EntityType getType() const override {
return RS::EntityUnknown; // 或自定义类型
}
virtual RBox getBoundingBox(bool ignoreEmpty = false) const override;
virtual QList<RRefPoint> getReferencePoints(RS::ProjectionRenderingHint hint) const override;
virtual bool moveReferencePoint(const RVector& referencePoint,
const RVector& targetPoint) override;
virtual QList<QSharedPointer<RShape>> getShapes(
const RBox& queryBox = RDEFAULT_RBOX,
bool ignoreComplex = false,
bool segment = false) const override;
// 自定义属性
void setCenter(const RVector& c) { center = c; }
RVector getCenter() const { return center; }
void setRadius(double r) { radius = r; }
double getRadius() const { return radius; }
void setSegments(int s) { segments = s; }
int getSegments() const { return segments; }
private:
RVector center;
double radius;
int segments;
};
#endif
13.4.2 实体实现
// RMyEntityData.cpp
#include "RMyEntityData.h"
#include "RLine.h"
#include "RArc.h"
RMyEntityData::RMyEntityData()
: center(RVector(0, 0)), radius(10), segments(6) {
}
RMyEntityData::RMyEntityData(const RVector& center, double radius, int segments)
: center(center), radius(radius), segments(segments) {
}
RMyEntityData::~RMyEntityData() {
}
RBox RMyEntityData::getBoundingBox(bool ignoreEmpty) const {
return RBox(
center - RVector(radius, radius),
center + RVector(radius, radius)
);
}
QList<RRefPoint> RMyEntityData::getReferencePoints(RS::ProjectionRenderingHint hint) const {
QList<RRefPoint> ret;
// 中心点
ret.append(RRefPoint(center, RRefPoint::Center));
// 顶点
double angleStep = 2 * M_PI / segments;
for (int i = 0; i < segments; i++) {
double angle = i * angleStep;
RVector vertex(
center.x + radius * cos(angle),
center.y + radius * sin(angle)
);
ret.append(RRefPoint(vertex, RRefPoint::Secondary));
}
return ret;
}
bool RMyEntityData::moveReferencePoint(const RVector& referencePoint,
const RVector& targetPoint) {
if (referencePoint.equalsFuzzy(center)) {
center = targetPoint;
return true;
}
// 检查是否是顶点
double angleStep = 2 * M_PI / segments;
for (int i = 0; i < segments; i++) {
double angle = i * angleStep;
RVector vertex(
center.x + radius * cos(angle),
center.y + radius * sin(angle)
);
if (referencePoint.equalsFuzzy(vertex)) {
radius = center.getDistanceTo(targetPoint);
return true;
}
}
return false;
}
QList<QSharedPointer<RShape>> RMyEntityData::getShapes(
const RBox& queryBox, bool ignoreComplex, bool segment) const {
QList<QSharedPointer<RShape>> shapes;
// 生成正多边形的边
double angleStep = 2 * M_PI / segments;
for (int i = 0; i < segments; i++) {
double angle1 = i * angleStep;
double angle2 = (i + 1) * angleStep;
RVector p1(
center.x + radius * cos(angle1),
center.y + radius * sin(angle1)
);
RVector p2(
center.x + radius * cos(angle2),
center.y + radius * sin(angle2)
);
shapes.append(QSharedPointer<RShape>(new RLine(p1, p2)));
}
return shapes;
}
13.4.3 实体类
// RMyEntity.h
#ifndef RMYENTITY_H
#define RMYENTITY_H
#include "REntity.h"
#include "RMyEntityData.h"
class RMyEntity : public REntity {
public:
static Type getRtti() { return (Type)(RS::EntityUser + 1); }
RMyEntity(RDocument* document, const RMyEntityData& data);
virtual ~RMyEntity();
virtual RMyEntity* clone() const override;
virtual Type getType() const override { return getRtti(); }
virtual RMyEntityData& getData() override {
return data;
}
const RMyEntityData& getData() const {
return data;
}
protected:
virtual void print(QDebug dbg) const override;
private:
RMyEntityData data;
};
#endif
13.5 注册脚本扩展
13.5.1 暴露类到JavaScript
// REcmaMyPlugin.cpp
#include "REcmaMyPlugin.h"
#include <QScriptEngine>
#include <QScriptValue>
void REcmaMyPlugin::initScriptExtensions(QScriptEngine& engine) {
// 注册构造函数
QScriptValue ctor = engine.newFunction(createMyEntity);
ctor.setProperty("prototype", createMyEntityPrototype(engine));
engine.globalObject().setProperty("RMyEntity", ctor);
// 注册全局函数
engine.globalObject().setProperty("myPluginFunction",
engine.newFunction(myPluginFunction));
}
// 创建实体的脚本函数
QScriptValue REcmaMyPlugin::createMyEntity(QScriptContext* context,
QScriptEngine* engine) {
if (context->argumentCount() < 3) {
return context->throwError("需要参数: center, radius, segments");
}
// 解析参数
RVector center = REcmaHelper::toVector(context->argument(0));
double radius = context->argument(1).toNumber();
int segments = context->argument(2).toInt32();
// 创建实体
// ...
return QScriptValue();
}
// 自定义脚本函数
QScriptValue REcmaMyPlugin::myPluginFunction(QScriptContext* context,
QScriptEngine* engine) {
qDebug() << "myPluginFunction 被调用";
// 执行功能...
return QScriptValue("完成");
}
13.5.2 JavaScript使用示例
// 在QCAD脚本中使用
var entity = new RMyEntity(
new RVector(100, 100), // 中心
50, // 半径
6 // 边数
);
// 调用插件函数
var result = myPluginFunction();
print(result); // 输出: 完成
13.6 UI扩展
13.6.1 添加菜单项
void RMyPlugin::addMenuItems() {
RMainWindow* mainWindow = RMainWindow::getMainWindow();
if (!mainWindow) return;
// 获取菜单栏
QMenuBar* menuBar = mainWindow->menuBar();
// 创建新菜单
QMenu* myMenu = menuBar->addMenu(tr("我的插件"));
// 添加菜单项
QAction* action1 = myMenu->addAction(tr("功能1"));
connect(action1, &QAction::triggered, this, &RMyPlugin::onFunction1);
myMenu->addSeparator();
QAction* action2 = myMenu->addAction(tr("功能2"));
connect(action2, &QAction::triggered, this, &RMyPlugin::onFunction2);
}
void RMyPlugin::onFunction1() {
qDebug() << "功能1 执行";
}
void RMyPlugin::onFunction2() {
qDebug() << "功能2 执行";
}
13.6.2 添加工具栏
void RMyPlugin::addToolBar() {
RMainWindow* mainWindow = RMainWindow::getMainWindow();
if (!mainWindow) return;
// 创建工具栏
QToolBar* toolBar = mainWindow->addToolBar(tr("我的工具栏"));
toolBar->setObjectName("MyPluginToolBar");
// 添加按钮
QAction* action1 = toolBar->addAction(
QIcon(":/icons/my_icon1.png"),
tr("工具1")
);
connect(action1, &QAction::triggered, this, &RMyPlugin::onTool1);
QAction* action2 = toolBar->addAction(
QIcon(":/icons/my_icon2.png"),
tr("工具2")
);
connect(action2, &QAction::triggered, this, &RMyPlugin::onTool2);
}
13.6.3 添加停靠面板
void RMyPlugin::addDockWidget() {
RMainWindow* mainWindow = RMainWindow::getMainWindow();
if (!mainWindow) return;
// 创建停靠窗口
QDockWidget* dockWidget = new QDockWidget(tr("我的面板"), mainWindow);
dockWidget->setObjectName("MyPluginDock");
// 创建内容控件
QWidget* content = new QWidget();
QVBoxLayout* layout = new QVBoxLayout(content);
// 添加控件
QLabel* label = new QLabel(tr("自定义面板"));
layout->addWidget(label);
QPushButton* button = new QPushButton(tr("执行"));
connect(button, &QPushButton::clicked, this, &RMyPlugin::onExecute);
layout->addWidget(button);
layout->addStretch();
dockWidget->setWidget(content);
// 添加到主窗口
mainWindow->addDockWidget(Qt::RightDockWidgetArea, dockWidget);
}
13.7 编译与部署
13.7.1 Windows编译
:: 设置环境
set PATH=C:\Qt\5.15.2\msvc2019_64\bin;%PATH%
set QCAD_ROOT=C:\qcad
:: 创建构建目录
mkdir build
cd build
:: 使用CMake
cmake .. -G "Visual Studio 16 2019" ^
-DCMAKE_PREFIX_PATH="C:\Qt\5.15.2\msvc2019_64\lib\cmake"
cmake --build . --config Release
:: 或使用QMake
qmake ../MyPlugin.pro
nmake release
13.7.2 Linux编译
# 设置环境
export PATH="/opt/Qt/5.15.2/gcc_64/bin:$PATH"
export QCAD_ROOT=~/qcad
# 创建构建目录
mkdir build && cd build
# CMake
cmake .. -DCMAKE_PREFIX_PATH="/opt/Qt/5.15.2/gcc_64/lib/cmake"
make -j$(nproc)
# 或QMake
qmake ../MyPlugin.pro
make release
13.7.3 部署插件
# 复制插件到QCAD插件目录
cp libmyplugin.so $QCAD_ROOT/plugins/
# 或Windows
copy myplugin.dll %QCAD_ROOT%\plugins\
# 复制关联脚本
cp -r scripts/MyPlugin $QCAD_ROOT/scripts/
13.8 本章小结
本章介绍了QCAD C++插件开发:
-
插件基础:
- 插件接口
- 开发环境配置
-
创建插件:
- 项目结构
- 实现RPluginInterface
-
文档操作:
- 访问文档
- 遍历和操作实体
-
自定义实体:
- 实体数据类
- 实体类实现
-
脚本集成:
- 暴露类到JavaScript
- 脚本函数注册
-
UI扩展:
- 菜单和工具栏
- 停靠面板
-
编译部署:
- 跨平台编译
- 插件部署
C++插件开发适合需要高性能或深度集成的场景。
下一章预告:第十四章将介绍自定义工具开发实战。

浙公网安备 33010602011771号