第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为例):

  1. 安装Qt和Qt Creator
  2. 获取QCAD源代码
  3. 配置Qt版本与QCAD匹配
  4. 创建插件项目

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++插件开发:

  1. 插件基础

    • 插件接口
    • 开发环境配置
  2. 创建插件

    • 项目结构
    • 实现RPluginInterface
  3. 文档操作

    • 访问文档
    • 遍历和操作实体
  4. 自定义实体

    • 实体数据类
    • 实体类实现
  5. 脚本集成

    • 暴露类到JavaScript
    • 脚本函数注册
  6. UI扩展

    • 菜单和工具栏
    • 停靠面板
  7. 编译部署

    • 跨平台编译
    • 插件部署

C++插件开发适合需要高性能或深度集成的场景。


下一章预告:第十四章将介绍自定义工具开发实战。

posted @ 2026-01-11 01:40  我才是银古  阅读(3)  评论(0)    收藏  举报