二次开发进阶与最佳实践

第十六章 二次开发进阶与最佳实践

16.1 架构设计原则

16.1.1 模块化设计

在进行LibreCAD二次开发时,应遵循模块化设计原则:

┌─────────────────────────────────────────────────────────┐
│                    应用层                                │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐             │
│  │ 自定义UI  │  │自定义命令│  │ 自定义   │              │
│  │   组件   │  │  Action  │  │  插件    │              │
│  └──────────┘  └──────────┘  └──────────┘             │
├─────────────────────────────────────────────────────────┤
│                    业务逻辑层                            │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐             │
│  │ 实体处理 │  │ 几何计算 │  │ 数据转换 │              │
│  └──────────┘  └──────────┘  └──────────┘             │
├─────────────────────────────────────────────────────────┤
│                    LibreCAD核心层                        │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐             │
│  │  Entity  │  │ Graphic  │  │   I/O    │              │
│  │  System  │  │   View   │  │  System  │              │
│  └──────────┘  └──────────┘  └──────────┘             │
└─────────────────────────────────────────────────────────┘

16.1.2 依赖注入

减少类之间的耦合:

// 不推荐 - 直接依赖
class MyProcessor {
    void process() {
        RS_Graphic* graphic = QC_ApplicationWindow::getInstance()
                                ->getDocument();
        // 处理图形...
    }
};

// 推荐 - 依赖注入
class MyProcessor {
public:
    MyProcessor(RS_EntityContainer* container)
        : container(container) {}
    
    void process() {
        // 使用注入的容器...
    }
    
private:
    RS_EntityContainer* container;
};

16.1.3 接口隔离

为不同功能定义清晰的接口:

// 几何计算接口
class IGeometryCalculator {
public:
    virtual ~IGeometryCalculator() = default;
    virtual double calculateArea(const RS_EntityContainer& entities) = 0;
    virtual double calculatePerimeter(const RS_EntityContainer& entities) = 0;
    virtual RS_Vector calculateCentroid(const RS_EntityContainer& entities) = 0;
};

// 实体创建接口
class IEntityFactory {
public:
    virtual ~IEntityFactory() = default;
    virtual RS_Entity* createEntity(const QVariantMap& params) = 0;
    virtual bool validateParams(const QVariantMap& params) = 0;
};

// 具体实现
class PolygonFactory : public IEntityFactory {
public:
    RS_Entity* createEntity(const QVariantMap& params) override;
    bool validateParams(const QVariantMap& params) override;
};

16.2 性能优化

16.2.1 大数据量处理

处理大量实体时的优化策略:

// 批量操作优化
void processEntities(RS_EntityContainer* container) {
    // 禁用重绘
    if (graphicView) {
        graphicView->setUpdatesEnabled(false);
    }
    
    // 开始单个撤销周期
    if (document) {
        document->startUndoCycle();
    }
    
    // 批量处理
    int count = container->count();
    for (int i = 0; i < count; i++) {
        RS_Entity* entity = container->entityAt(i);
        // 处理实体...
        
        // 每1000个实体进度更新
        if (i % 1000 == 0) {
            QCoreApplication::processEvents();
        }
    }
    
    // 结束撤销周期
    if (document) {
        document->endUndoCycle();
    }
    
    // 启用重绘
    if (graphicView) {
        graphicView->setUpdatesEnabled(true);
        graphicView->redraw();
    }
}

16.2.2 空间索引

使用空间索引加速查询:

#include <boost/geometry.hpp>
#include <boost/geometry/index/rtree.hpp>

namespace bg = boost::geometry;
namespace bgi = boost::geometry::index;

// R-tree索引
typedef bg::model::point<double, 2, bg::cs::cartesian> Point;
typedef bg::model::box<Point> Box;
typedef std::pair<Box, RS_Entity*> SpatialValue;
typedef bgi::rtree<SpatialValue, bgi::quadratic<16>> SpatialIndex;

class EntitySpatialIndex {
public:
    void buildIndex(RS_EntityContainer* container) {
        index.clear();
        
        for (RS_Entity* entity : *container) {
            RS_Vector min = entity->getMin();
            RS_Vector max = entity->getMax();
            
            Box bounds(
                Point(min.x, min.y),
                Point(max.x, max.y)
            );
            
            index.insert(std::make_pair(bounds, entity));
        }
    }
    
    QList<RS_Entity*> queryRange(const RS_Vector& min, 
                                 const RS_Vector& max) {
        QList<RS_Entity*> result;
        Box queryBox(
            Point(min.x, min.y),
            Point(max.x, max.y)
        );
        
        std::vector<SpatialValue> found;
        index.query(bgi::intersects(queryBox), 
                   std::back_inserter(found));
        
        for (const auto& pair : found) {
            result.append(pair.second);
        }
        
        return result;
    }
    
private:
    SpatialIndex index;
};

16.2.3 延迟计算

避免不必要的计算:

class LazyBoundingBox {
public:
    LazyBoundingBox(RS_EntityContainer* container)
        : container(container), valid(false) {}
    
    const RS_Vector& getMin() {
        ensureValid();
        return min;
    }
    
    const RS_Vector& getMax() {
        ensureValid();
        return max;
    }
    
    void invalidate() {
        valid = false;
    }
    
private:
    void ensureValid() {
        if (!valid) {
            container->calculateBorders();
            min = container->getMin();
            max = container->getMax();
            valid = true;
        }
    }
    
    RS_EntityContainer* container;
    RS_Vector min, max;
    bool valid;
};

16.2.4 内存管理

正确管理内存避免泄漏:

// 使用智能指针
#include <memory>

class EntityManager {
public:
    void addEntity(std::unique_ptr<RS_Entity> entity) {
        entities.push_back(std::move(entity));
    }
    
    RS_Entity* getEntity(size_t index) {
        return entities.at(index).get();
    }
    
private:
    std::vector<std::unique_ptr<RS_Entity>> entities;
};

// 或使用Qt的内存管理
class QtEntityManager : public QObject {
public:
    void addEntity(RS_Entity* entity) {
        entity->setParent(this);  // Qt自动管理内存
        entities.append(entity);
    }
    
private:
    QList<RS_Entity*> entities;
};

16.3 几何算法实现

16.3.1 多边形布尔运算

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/polygon.hpp>

namespace bg = boost::geometry;
typedef bg::model::d2::point_xy<double> BoostPoint;
typedef bg::model::polygon<BoostPoint> BoostPolygon;

class PolygonBoolean {
public:
    // 并集
    static RS_Polyline* unionPolygons(RS_Polyline* p1, RS_Polyline* p2) {
        BoostPolygon bp1 = toBoostPolygon(p1);
        BoostPolygon bp2 = toBoostPolygon(p2);
        
        std::vector<BoostPolygon> output;
        bg::union_(bp1, bp2, output);
        
        if (!output.empty()) {
            return fromBoostPolygon(output[0]);
        }
        return nullptr;
    }
    
    // 交集
    static RS_Polyline* intersectPolygons(RS_Polyline* p1, RS_Polyline* p2) {
        BoostPolygon bp1 = toBoostPolygon(p1);
        BoostPolygon bp2 = toBoostPolygon(p2);
        
        std::vector<BoostPolygon> output;
        bg::intersection(bp1, bp2, output);
        
        if (!output.empty()) {
            return fromBoostPolygon(output[0]);
        }
        return nullptr;
    }
    
    // 差集
    static RS_Polyline* differencePolygons(RS_Polyline* p1, RS_Polyline* p2) {
        BoostPolygon bp1 = toBoostPolygon(p1);
        BoostPolygon bp2 = toBoostPolygon(p2);
        
        std::vector<BoostPolygon> output;
        bg::difference(bp1, bp2, output);
        
        if (!output.empty()) {
            return fromBoostPolygon(output[0]);
        }
        return nullptr;
    }
    
private:
    static BoostPolygon toBoostPolygon(RS_Polyline* polyline);
    static RS_Polyline* fromBoostPolygon(const BoostPolygon& polygon);
};

16.3.2 凸包计算

class ConvexHull {
public:
    static RS_Polyline* compute(const QList<RS_Vector>& points) {
        if (points.size() < 3) return nullptr;
        
        // 使用Graham扫描算法
        QList<RS_Vector> sorted = points;
        
        // 找到最下最左点
        auto lowest = std::min_element(sorted.begin(), sorted.end(),
            [](const RS_Vector& a, const RS_Vector& b) {
                return a.y < b.y || (a.y == b.y && a.x < b.x);
            });
        
        RS_Vector pivot = *lowest;
        sorted.removeOne(pivot);
        
        // 按极角排序
        std::sort(sorted.begin(), sorted.end(),
            [&pivot](const RS_Vector& a, const RS_Vector& b) {
                double angleA = pivot.angleTo(a);
                double angleB = pivot.angleTo(b);
                return angleA < angleB;
            });
        
        // 构建凸包
        QList<RS_Vector> hull;
        hull.append(pivot);
        
        for (const RS_Vector& p : sorted) {
            while (hull.size() > 1 && 
                   crossProduct(hull[hull.size()-2], 
                               hull[hull.size()-1], p) <= 0) {
                hull.removeLast();
            }
            hull.append(p);
        }
        
        // 创建折线
        RS_Polyline* result = new RS_Polyline(nullptr);
        for (const RS_Vector& v : hull) {
            result->addVertex(v);
        }
        result->setClosed(true);
        
        return result;
    }
    
private:
    static double crossProduct(const RS_Vector& o, 
                              const RS_Vector& a, 
                              const RS_Vector& b) {
        return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
    }
};

16.3.3 线段相交检测

class IntersectionDetector {
public:
    struct IntersectionResult {
        bool intersects;
        RS_Vector point;
    };
    
    static IntersectionResult lineLineIntersection(
        const RS_Vector& p1, const RS_Vector& p2,
        const RS_Vector& p3, const RS_Vector& p4) {
        
        IntersectionResult result;
        result.intersects = false;
        
        double d = (p1.x - p2.x) * (p3.y - p4.y) - 
                   (p1.y - p2.y) * (p3.x - p4.x);
        
        if (fabs(d) < RS_TOLERANCE) {
            return result;  // 平行或重合
        }
        
        double t = ((p1.x - p3.x) * (p3.y - p4.y) - 
                   (p1.y - p3.y) * (p3.x - p4.x)) / d;
        double u = -((p1.x - p2.x) * (p1.y - p3.y) - 
                    (p1.y - p2.y) * (p1.x - p3.x)) / d;
        
        if (t >= 0 && t <= 1 && u >= 0 && u <= 1) {
            result.intersects = true;
            result.point = RS_Vector(
                p1.x + t * (p2.x - p1.x),
                p1.y + t * (p2.y - p1.y)
            );
        }
        
        return result;
    }
    
    static IntersectionResult lineCircleIntersection(
        const RS_Vector& p1, const RS_Vector& p2,
        const RS_Vector& center, double radius);
    
    static IntersectionResult circleCircleIntersection(
        const RS_Vector& c1, double r1,
        const RS_Vector& c2, double r2);
};

16.4 自定义实体开发

16.4.1 创建自定义实体类

// 自定义表格实体
class RS_Table : public RS_EntityContainer {
public:
    struct TableData {
        RS_Vector position;
        int rows;
        int cols;
        double rowHeight;
        double colWidth;
        QList<QStringList> cellData;
    };
    
    RS_Table(RS_EntityContainer* parent, const TableData& d);
    
    RS2::EntityType rtti() const override {
        return static_cast<RS2::EntityType>(1001);  // 自定义类型ID
    }
    
    RS_Entity* clone() const override {
        return new RS_Table(nullptr, data);
    }
    
    // 设置单元格内容
    void setCellText(int row, int col, const QString& text);
    QString getCellText(int row, int col) const;
    
    // 更新表格几何
    void update();
    
    void draw(RS_Painter* painter, RS_GraphicView* view,
              double& patternOffset) override;
    
private:
    TableData data;
    
    void createTableGeometry();
};

// 实现
RS_Table::RS_Table(RS_EntityContainer* parent, const TableData& d)
    : RS_EntityContainer(parent), data(d) {
    createTableGeometry();
}

void RS_Table::createTableGeometry() {
    clear();
    
    // 创建表格线
    for (int i = 0; i <= data.rows; i++) {
        double y = data.position.y - i * data.rowHeight;
        RS_Line* line = new RS_Line(this, RS_LineData(
            RS_Vector(data.position.x, y),
            RS_Vector(data.position.x + data.cols * data.colWidth, y)
        ));
        addEntity(line);
    }
    
    for (int j = 0; j <= data.cols; j++) {
        double x = data.position.x + j * data.colWidth;
        RS_Line* line = new RS_Line(this, RS_LineData(
            RS_Vector(x, data.position.y),
            RS_Vector(x, data.position.y - data.rows * data.rowHeight)
        ));
        addEntity(line);
    }
    
    // 创建单元格文字
    for (int i = 0; i < data.rows && i < data.cellData.size(); i++) {
        const QStringList& rowData = data.cellData[i];
        for (int j = 0; j < data.cols && j < rowData.size(); j++) {
            RS_Vector textPos(
                data.position.x + j * data.colWidth + 2,
                data.position.y - i * data.rowHeight - data.rowHeight / 2
            );
            RS_Text* text = new RS_Text(this, RS_TextData(
                textPos, textPos, 2.5, 1.0, RS2::HAlignLeft,
                RS2::VAlignMiddle, RS2::AtLeast, RS2::Exact, 0.0,
                rowData[j], "Standard", 0.0
            ));
            addEntity(text);
        }
    }
}

16.4.2 自定义实体序列化

class RS_Table : public RS_EntityContainer {
public:
    // DXF导出
    void writeToFile(RS_FilterDXFRW& filter) {
        // 写入自定义数据
        filter.writeGroupCode(0, "TABLE");  // 自定义类型标识
        filter.writeGroupCode(100, "AcDbEntity");
        
        // 位置
        filter.writeGroupCode(10, data.position.x);
        filter.writeGroupCode(20, data.position.y);
        
        // 尺寸
        filter.writeGroupCode(90, data.rows);
        filter.writeGroupCode(91, data.cols);
        filter.writeGroupCode(40, data.rowHeight);
        filter.writeGroupCode(41, data.colWidth);
        
        // 单元格数据
        for (int i = 0; i < data.rows; i++) {
            for (int j = 0; j < data.cols; j++) {
                filter.writeGroupCode(1, getCellText(i, j));
            }
        }
    }
    
    // DXF导入
    static RS_Table* readFromFile(const QHash<int, QVariant>& dxfData) {
        TableData d;
        d.position = RS_Vector(
            dxfData.value(10).toDouble(),
            dxfData.value(20).toDouble()
        );
        d.rows = dxfData.value(90).toInt();
        d.cols = dxfData.value(91).toInt();
        d.rowHeight = dxfData.value(40).toDouble();
        d.colWidth = dxfData.value(41).toDouble();
        
        // 读取单元格数据...
        
        return new RS_Table(nullptr, d);
    }
};

16.5 与外部系统集成

16.5.1 数据库集成

#include <QSqlDatabase>
#include <QSqlQuery>

class DatabaseExporter {
public:
    bool exportToDatabase(RS_Graphic* graphic, 
                         const QString& connectionName) {
        QSqlDatabase db = QSqlDatabase::database(connectionName);
        if (!db.isOpen()) return false;
        
        QSqlQuery query(db);
        
        // 创建表
        query.exec(R"(
            CREATE TABLE IF NOT EXISTS entities (
                id INTEGER PRIMARY KEY,
                type TEXT,
                layer TEXT,
                geometry TEXT,
                properties TEXT
            )
        )");
        
        // 导出实体
        db.transaction();
        
        for (RS_Entity* entity : *graphic) {
            QString geometry = entityToGeoJSON(entity);
            QString properties = entityPropertiesToJSON(entity);
            
            query.prepare(R"(
                INSERT INTO entities (type, layer, geometry, properties)
                VALUES (?, ?, ?, ?)
            )");
            query.addBindValue(entity->typeName());
            query.addBindValue(entity->getLayer()->getName());
            query.addBindValue(geometry);
            query.addBindValue(properties);
            query.exec();
        }
        
        return db.commit();
    }
    
private:
    QString entityToGeoJSON(RS_Entity* entity);
    QString entityPropertiesToJSON(RS_Entity* entity);
};

16.5.2 REST API集成

#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QJsonDocument>

class CADServiceClient : public QObject {
    Q_OBJECT
    
public:
    void uploadDrawing(RS_Graphic* graphic) {
        QJsonObject json;
        json["name"] = graphic->getFilename();
        json["entities"] = entitiesToJson(graphic);
        
        QNetworkRequest request(QUrl("https://api.example.com/drawings"));
        request.setHeader(QNetworkRequest::ContentTypeHeader, 
                         "application/json");
        
        QNetworkReply* reply = manager->post(request, 
            QJsonDocument(json).toJson());
        
        connect(reply, &QNetworkReply::finished, 
                this, &CADServiceClient::onUploadFinished);
    }
    
signals:
    void uploadCompleted(bool success, QString message);
    
private slots:
    void onUploadFinished() {
        QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
        bool success = reply->error() == QNetworkReply::NoError;
        QString message = success ? "上传成功" : reply->errorString();
        emit uploadCompleted(success, message);
        reply->deleteLater();
    }
    
private:
    QNetworkAccessManager* manager = new QNetworkAccessManager(this);
    
    QJsonArray entitiesToJson(RS_EntityContainer* container);
};

16.5.3 脚本引擎集成

#include <QJSEngine>

class ScriptEngine {
public:
    ScriptEngine(RS_Graphic* graphic, RS_GraphicView* view)
        : graphic(graphic), view(view) {
        setupEngine();
    }
    
    QJSValue runScript(const QString& script) {
        return engine.evaluate(script);
    }
    
private:
    void setupEngine() {
        // 注册CAD对象
        QJSValue cadObj = engine.newObject();
        
        // 添加方法
        cadObj.setProperty("addLine", engine.newFunction(jsAddLine));
        cadObj.setProperty("addCircle", engine.newFunction(jsAddCircle));
        cadObj.setProperty("getEntities", engine.newFunction(jsGetEntities));
        
        engine.globalObject().setProperty("CAD", cadObj);
    }
    
    static QJSValue jsAddLine(QJSContext* ctx, QJSEngine* engine) {
        // 实现添加线的JavaScript接口
        if (ctx->argumentCount() < 4) {
            return engine->undefinedValue();
        }
        
        double x1 = ctx->argument(0).toNumber();
        double y1 = ctx->argument(1).toNumber();
        double x2 = ctx->argument(2).toNumber();
        double y2 = ctx->argument(3).toNumber();
        
        // 创建线...
        
        return engine->trueValue();
    }
    
    QJSEngine engine;
    RS_Graphic* graphic;
    RS_GraphicView* view;
};

// 使用示例
/*
JavaScript:
CAD.addLine(0, 0, 100, 100);
CAD.addCircle(50, 50, 25);
*/

16.6 测试最佳实践

16.6.1 单元测试

#include <QtTest>

class TestGeometry : public QObject {
    Q_OBJECT
    
private slots:
    void testLineLength() {
        RS_LineData data(RS_Vector(0, 0), RS_Vector(3, 4));
        RS_Line line(nullptr, data);
        
        QCOMPARE(line.getLength(), 5.0);
    }
    
    void testCircleArea() {
        RS_CircleData data(RS_Vector(0, 0), 10.0);
        RS_Circle circle(nullptr, data);
        
        QVERIFY(qAbs(circle.getArea() - M_PI * 100) < 0.001);
    }
    
    void testLineIntersection() {
        RS_Vector p1(0, 0), p2(10, 10);
        RS_Vector p3(0, 10), p4(10, 0);
        
        auto result = IntersectionDetector::lineLineIntersection(
            p1, p2, p3, p4);
        
        QVERIFY(result.intersects);
        QCOMPARE(result.point.x, 5.0);
        QCOMPARE(result.point.y, 5.0);
    }
    
    void testPolygonArea() {
        RS_Polyline poly(nullptr);
        poly.addVertex(RS_Vector(0, 0));
        poly.addVertex(RS_Vector(10, 0));
        poly.addVertex(RS_Vector(10, 10));
        poly.addVertex(RS_Vector(0, 10));
        poly.setClosed(true);
        
        QCOMPARE(poly.getArea(), 100.0);
    }
};

QTEST_MAIN(TestGeometry)

16.6.2 集成测试

class TestDocumentOperations : public QObject {
    Q_OBJECT
    
private slots:
    void initTestCase() {
        // 创建测试文档
        document = new RS_Graphic();
    }
    
    void cleanupTestCase() {
        delete document;
    }
    
    void testSaveAndLoad() {
        // 添加实体
        RS_Line* line = new RS_Line(document,
            RS_LineData(RS_Vector(0, 0), RS_Vector(100, 100)));
        document->addEntity(line);
        
        // 保存
        QString tempFile = QDir::temp().filePath("test.dxf");
        QVERIFY(document->save(tempFile));
        
        // 重新加载
        RS_Graphic* loaded = new RS_Graphic();
        QVERIFY(loaded->open(tempFile));
        
        // 验证
        QCOMPARE(loaded->count(), 1);
        
        delete loaded;
        QFile::remove(tempFile);
    }
    
    void testUndoRedo() {
        int initialCount = document->count();
        
        document->startUndoCycle();
        RS_Circle* circle = new RS_Circle(document,
            RS_CircleData(RS_Vector(50, 50), 25));
        document->addEntity(circle);
        document->addUndoable(circle);
        document->endUndoCycle();
        
        QCOMPARE(document->count(), initialCount + 1);
        
        // 撤销
        document->undo();
        QCOMPARE(document->count(), initialCount);
        
        // 重做
        document->redo();
        QCOMPARE(document->count(), initialCount + 1);
    }
    
private:
    RS_Graphic* document;
};

16.7 文档与发布

16.7.1 代码文档

使用Doxygen格式的注释:

/**
 * @class MyCustomAction
 * @brief 自定义绘图动作
 * 
 * 该类实现了一个自定义的绘图动作,用于绘制特殊形状。
 * 
 * @section usage 使用方法
 * @code
 * MyCustomAction* action = new MyCustomAction(container, graphicView);
 * graphicView->setCurrentAction(action);
 * @endcode
 * 
 * @author Your Name
 * @date 2024-01-15
 * @version 1.0
 */
class MyCustomAction : public RS_ActionInterface {
public:
    /**
     * @brief 构造函数
     * @param container 实体容器引用
     * @param graphicView 图形视图引用
     */
    MyCustomAction(RS_EntityContainer& container,
                   RS_GraphicView& graphicView);
    
    /**
     * @brief 执行绘制操作
     * @return 成功返回true,失败返回false
     * @throws std::runtime_error 如果参数无效
     */
    bool execute();
};

16.7.2 用户文档

# MyPlugin 用户手册

## 功能介绍
MyPlugin 提供了以下功能:
- 功能A:描述...
- 功能B:描述...

## 安装步骤
1. 下载插件文件
2. 复制到LibreCAD插件目录
3. 重启LibreCAD

## 使用方法
### 功能A
1. 选择菜单 "插件" > "MyPlugin" > "功能A"
2. 按照提示操作...

### 命令行使用

命令: myplugin_functiona
参数:


## 常见问题
Q: 插件无法加载?
A: 请检查Qt版本是否兼容...

16.8 本章小结

本章介绍了LibreCAD二次开发的进阶内容:

  1. 架构设计:模块化、依赖注入、接口隔离
  2. 性能优化:批量处理、空间索引、延迟计算、内存管理
  3. 几何算法:布尔运算、凸包、相交检测
  4. 自定义实体:创建自定义实体类、序列化
  5. 外部集成:数据库、REST API、脚本引擎
  6. 测试实践:单元测试、集成测试
  7. 文档发布:代码文档、用户文档

遵循这些最佳实践可以开发出高质量、可维护的LibreCAD扩展。


上一章:插件系统与开发实战


附录:参考资源

官方资源

开发资源

社区资源


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