二次开发实战

第15章 二次开发实战

15.1 开发环境准备

15.1.1 获取源代码

# 克隆仓库
git clone https://github.com/solvespace/solvespace
cd solvespace

# 初始化子模块
git submodule update --init --recursive

# 创建开发分支
git checkout -b feature/my-feature

15.1.2 开发环境配置

Linux开发环境

# 安装开发依赖
sudo apt install build-essential cmake git \
    libgtkmm-3.0-dev libpangomm-1.4-dev \
    libgl-dev libglu-dev libfreetype6-dev \
    libpng-dev libjson-c-dev libfontconfig1-dev \
    libcairo2-dev libspnav-dev

# 可选: Qt开发
sudo apt install qt6-base-dev

Windows开发环境

REM 安装Visual Studio 2019+
REM 安装CMake 3.16+
REM 使用vcpkg安装依赖

vcpkg install freetype libpng zlib cairo pango

macOS开发环境

# 使用Homebrew
brew install cmake freetype libpng json-c

15.1.3 构建项目

mkdir build && cd build

# Debug构建
cmake .. -DCMAKE_BUILD_TYPE=Debug

# 并行编译
make -j$(nproc)

# 运行测试
ctest

15.1.4 IDE配置

VS Code配置

// .vscode/settings.json
{
    "cmake.sourceDirectory": "${workspaceFolder}",
    "cmake.buildDirectory": "${workspaceFolder}/build",
    "cmake.configureSettings": {
        "CMAKE_BUILD_TYPE": "Debug"
    }
}

CLion配置

直接打开项目目录,CLion自动检测CMake项目。

15.2 添加新约束类型

15.2.1 需求分析

示例: 添加"等周长"约束,使两个封闭轮廓的周长相等。

15.2.2 定义约束类型

修改 sketch.h

// src/sketch.h
class ConstraintBase {
public:
    enum class Type : uint32_t {
        // ... 现有类型
        
        EQUAL_PERIMETER = 100050,  // 新增: 等周长
    };
    // ...
};

15.2.3 实现约束方程

修改 constraint.cpp

// src/constraint.cpp
void ConstraintBase::GenerateEquations(IdList<Equation> *l) {
    // ... 现有代码
    
    case Type::EQUAL_PERIMETER: {
        // 计算两个轮廓的周长表达式
        Expr *perimeterA = CalcPerimeterExpr(entityA);
        Expr *perimeterB = CalcPerimeterExpr(entityB);
        
        // 添加方程: perimeterA - perimeterB = 0
        AddEq(l, perimeterA->Minus(perimeterB), 0);
        break;
    }
}

// 辅助函数
Expr *ConstraintBase::CalcPerimeterExpr(hEntity he) {
    Entity *e = SK.GetEntity(he);
    
    // 遍历轮廓中的所有曲线
    // 计算总长度表达式
    Expr *total = Expr::From(0.0);
    
    // ... 实现细节
    
    return total;
}

15.2.4 添加UI入口

修改 graphicswin.cpp

// src/graphicswin.cpp
void GraphicsWindow::MenuConstrain(Command id) {
    // ... 现有代码
    
    case Command::CONSTRAIN_EQUAL_PERIMETER:
        if(gs.contours == 2 && gs.n == 2) {
            c.type = Constraint::Type::EQUAL_PERIMETER;
            c.entityA = gs.entity[0];
            c.entityB = gs.entity[1];
            AddConstraint(&c);
        } else {
            Error("请选择两个封闭轮廓");
        }
        break;
}

修改菜单定义

// src/ui.h
enum class Command : uint32_t {
    // ... 现有命令
    CONSTRAIN_EQUAL_PERIMETER,
};

// 菜单项
{ "&E Equal Perimeter", Command::CONSTRAIN_EQUAL_PERIMETER },

15.2.5 添加序列化

修改 file.cpp

// src/file.cpp
const char *Constraint::DescribeType(Type type) {
    switch(type) {
        // ... 现有类型
        case Type::EQUAL_PERIMETER: return "equal-perimeter";
    }
}

15.2.6 添加测试

// test/constraint/test_equal_perimeter.cpp
TEST_CASE("Equal perimeter constraint") {
    // 创建两个矩形
    // 添加等周长约束
    // 验证求解结果
}

15.3 添加新导出格式

15.3.1 需求分析

示例: 添加 IGES 格式导出支持。

15.3.2 实现导出函数

创建新文件 exportiges.cpp

// src/exportiges.cpp
#include "solvespace.h"

void SolveSpaceUI::ExportIGES(const Platform::Path &path) {
    // 获取当前组的网格
    Group *g = SK.GetGroup(SS.GW.activeGroup);
    SShell *shell = &g->displayShell;
    
    // 打开文件
    FILE *f = OpenFile(path, "w");
    if(!f) {
        Error("无法创建文件");
        return;
    }
    
    // 写入IGES头部
    WriteIGESHeader(f);
    
    // 写入几何数据
    for(auto &surf : shell->surface) {
        WriteIGESSurface(f, &surf);
    }
    
    // 写入结束
    WriteIGESEnd(f);
    
    fclose(f);
}

void SolveSpaceUI::WriteIGESHeader(FILE *f) {
    // IGES文件头格式
    fprintf(f, "                                                        S      1\n");
    // ... 更多头部信息
}

void SolveSpaceUI::WriteIGESSurface(FILE *f, SSurface *s) {
    // 根据曲面类型输出IGES实体
    // ...
}

15.3.3 集成到UI

修改 export.cpp

// 添加菜单项
case Command::EXPORT_IGES:
    GetFilename("iges", ExportIGES);
    break;

修改 ui.h

enum class Command {
    // ...
    EXPORT_IGES,
};

15.3.4 添加文件过滤器

// 文件对话框过滤器
{ "IGES Files", { "iges", "igs" } },

15.4 嵌入求解器到其他应用

15.4.1 最小化集成示例

// my_cad_app.cpp
#include <slvs.h>
#include <vector>

class MyConstraintSolver {
    Slvs_System sys;
    std::vector<Slvs_Param> params;
    std::vector<Slvs_Entity> entities;
    std::vector<Slvs_Constraint> constraints;
    
public:
    MyConstraintSolver() {
        memset(&sys, 0, sizeof(sys));
    }
    
    ~MyConstraintSolver() {
        Clear();
    }
    
    void Clear() {
        params.clear();
        entities.clear();
        constraints.clear();
    }
    
    Slvs_hParam AddParam(double val) {
        Slvs_hParam h = params.size() + 1;
        params.push_back(Slvs_MakeParam(h, 1, val));
        return h;
    }
    
    Slvs_hEntity AddPoint2D(double x, double y, 
                           Slvs_hEntity workplane) {
        Slvs_hParam px = AddParam(x);
        Slvs_hParam py = AddParam(y);
        
        Slvs_hEntity h = entities.size() + 100;
        entities.push_back(
            Slvs_MakePoint2d(h, 1, workplane, px, py));
        return h;
    }
    
    void AddDistanceConstraint(Slvs_hEntity p1, 
                               Slvs_hEntity p2,
                               double dist,
                               Slvs_hEntity workplane) {
        Slvs_hConstraint h = constraints.size() + 1;
        constraints.push_back(
            Slvs_MakeConstraint(h, 1, SLVS_C_PT_PT_DISTANCE,
                               workplane, dist, p1, p2, 0, 0));
    }
    
    bool Solve() {
        // 设置系统
        sys.param = params.data();
        sys.params = params.size();
        sys.entity = entities.data();
        sys.entities = entities.size();
        sys.constraint = constraints.data();
        sys.constraints = constraints.size();
        
        // 分配失败约束数组
        std::vector<Slvs_hConstraint> failed(constraints.size());
        sys.failed = failed.data();
        sys.faileds = failed.size();
        sys.calculateFaileds = 1;
        
        // 求解
        Slvs_Solve(&sys, 1);
        
        return sys.result == SLVS_RESULT_OKAY;
    }
    
    double GetParamValue(Slvs_hParam h) {
        for(auto &p : params) {
            if(p.h == h) return p.val;
        }
        return 0;
    }
    
    int GetDOF() { return sys.dof; }
};

// 使用示例
int main() {
    MyConstraintSolver solver;
    
    // 创建基础工作平面
    // (简化: 使用预定义常量)
    Slvs_hEntity wp = SLVS_FREE_IN_3D; // 或创建工作平面
    
    // 添加几何
    auto p1 = solver.AddPoint2D(0, 0, wp);
    auto p2 = solver.AddPoint2D(100, 0, wp);
    
    // 添加约束
    solver.AddDistanceConstraint(p1, p2, 50, wp);
    
    // 求解
    if(solver.Solve()) {
        printf("求解成功, DOF=%d\n", solver.GetDOF());
    }
    
    return 0;
}

15.4.2 Python应用集成

# my_cad_app.py
from slvs import *

class ParametricDrawing:
    def __init__(self):
        self.group = 1
        self.workplane = None
        self.elements = []
    
    def setup(self):
        clear_sketch()
        self.workplane = add_base_2d(self.group)
    
    def add_rectangle(self, x, y, width, height):
        wp = self.workplane
        g = self.group
        
        # 创建四个角点
        p1 = add_point_2d(g, x, y, wp)
        p2 = add_point_2d(g, x + width, y, wp)
        p3 = add_point_2d(g, x + width, y + height, wp)
        p4 = add_point_2d(g, x, y + height, wp)
        
        # 创建边
        lines = [
            add_line_2d(g, p1, p2, wp),
            add_line_2d(g, p2, p3, wp),
            add_line_2d(g, p3, p4, wp),
            add_line_2d(g, p4, p1, wp),
        ]
        
        # 添加约束
        horizontal(g, lines[0], wp)
        horizontal(g, lines[2], wp)
        vertical(g, lines[1], wp)
        vertical(g, lines[3], wp)
        
        distance(g, p1, p2, width, wp)
        distance(g, p2, p3, height, wp)
        
        self.elements.append({
            'type': 'rectangle',
            'points': [p1, p2, p3, p4],
            'lines': lines
        })
        
        return len(self.elements) - 1
    
    def solve_and_get_positions(self):
        result = solve_sketch(self.group, True)
        
        if result['result'] != ResultFlag.OKAY:
            raise Exception(f"求解失败: {result['result']}")
        
        positions = {}
        for i, elem in enumerate(self.elements):
            if elem['type'] == 'rectangle':
                positions[i] = []
                for p in elem['points']:
                    x = get_param_value(p['param'][0])
                    y = get_param_value(p['param'][1])
                    positions[i].append((x, y))
        
        return positions


# 使用
drawing = ParametricDrawing()
drawing.setup()
rect_id = drawing.add_rectangle(0, 0, 100, 60)
positions = drawing.solve_and_get_positions()
print(positions[rect_id])

15.5 创建自定义CAD应用

15.5.1 应用架构

my_cad_app/
├── src/
│   ├── main.cpp
│   ├── document.cpp      # 文档管理
│   ├── geometry.cpp      # 几何操作
│   ├── constraint.cpp    # 约束管理
│   ├── render.cpp        # 渲染
│   └── ui.cpp            # 用户界面
├── include/
│   └── ...
├── lib/
│   └── libslvs.so        # SolveSpace求解器库
└── CMakeLists.txt

15.5.2 CMake配置

cmake_minimum_required(VERSION 3.16)
project(my_cad_app)

set(CMAKE_CXX_STANDARD 17)

# 查找依赖
find_library(SLVS_LIB slvs REQUIRED)
find_package(OpenGL REQUIRED)
find_package(glfw3 REQUIRED)

# 源文件
add_executable(my_cad_app
    src/main.cpp
    src/document.cpp
    src/geometry.cpp
    src/constraint.cpp
    src/render.cpp
    src/ui.cpp
)

target_include_directories(my_cad_app PRIVATE
    include
    ${OPENGL_INCLUDE_DIR}
)

target_link_libraries(my_cad_app
    ${SLVS_LIB}
    OpenGL::GL
    glfw
)

15.5.3 文档类实现

// document.h
#pragma once
#include <slvs.h>
#include <vector>
#include <string>

class Document {
public:
    struct Point {
        int id;
        double x, y;
        Slvs_hParam params[2];
        Slvs_hEntity entity;
    };
    
    struct Line {
        int id;
        int startPoint, endPoint;
        Slvs_hEntity entity;
    };
    
    struct Constraint {
        int id;
        std::string type;
        std::vector<int> entities;
        double value;
        Slvs_hConstraint handle;
    };

private:
    Slvs_System sys;
    std::vector<Point> points;
    std::vector<Line> lines;
    std::vector<Constraint> constraints;
    Slvs_hEntity workplane;
    int nextId = 1;

public:
    Document();
    ~Document();
    
    void Clear();
    
    int AddPoint(double x, double y);
    int AddLine(int p1, int p2);
    int AddDistanceConstraint(int p1, int p2, double dist);
    int AddHorizontalConstraint(int lineId);
    
    bool Solve();
    
    Point* GetPoint(int id);
    Line* GetLine(int id);
    
    void ExportToSVG(const std::string &path);
};

15.5.4 主程序框架

// main.cpp
#include "document.h"
#include <GLFW/glfw3.h>

Document doc;

void render() {
    glClear(GL_COLOR_BUFFER_BIT);
    
    // 绘制线条
    glBegin(GL_LINES);
    for(auto &line : doc.GetLines()) {
        auto* p1 = doc.GetPoint(line.startPoint);
        auto* p2 = doc.GetPoint(line.endPoint);
        glVertex2d(p1->x, p1->y);
        glVertex2d(p2->x, p2->y);
    }
    glEnd();
    
    // 绘制点
    glPointSize(5);
    glBegin(GL_POINTS);
    for(auto &p : doc.GetPoints()) {
        glVertex2d(p.x, p.y);
    }
    glEnd();
}

void onMouseClick(GLFWwindow *w, int button, int action, int mods) {
    if(button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) {
        double x, y;
        glfwGetCursorPos(w, &x, &y);
        
        // 转换坐标
        // 添加点或选择元素
    }
}

int main() {
    // 初始化GLFW
    glfwInit();
    GLFWwindow *window = glfwCreateWindow(800, 600, "My CAD", NULL, NULL);
    glfwMakeContextCurrent(window);
    
    // 设置回调
    glfwSetMouseButtonCallback(window, onMouseClick);
    
    // 主循环
    while(!glfwWindowShouldClose(window)) {
        render();
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    
    glfwTerminate();
    return 0;
}

15.6 贡献代码到上游

15.6.1 贡献流程

# 1. Fork仓库
# 在GitHub上fork solvespace/solvespace

# 2. 克隆你的fork
git clone https://github.com/YOUR_USERNAME/solvespace

# 3. 添加上游远程
git remote add upstream https://github.com/solvespace/solvespace

# 4. 创建功能分支
git checkout -b feature/my-feature

# 5. 开发和测试
# ... 编写代码
# ... 运行测试

# 6. 提交
git add .
git commit -m "Add feature: description"

# 7. 推送
git push origin feature/my-feature

# 8. 创建Pull Request
# 在GitHub上创建PR

15.6.2 代码规范

命名约定

// 类名: PascalCase
class MyNewClass { };

// 函数名: PascalCase
void MyFunction();

// 变量名: camelCase
int myVariable;

// 常量: UPPER_CASE
const int MAX_VALUE = 100;

// 成员变量: 无前缀
class Example {
    int memberVar;  // 不是 m_memberVar
};

代码风格

// 大括号风格
if(condition) {
    // code
} else {
    // code
}

// 缩进: 4空格
void Function() {
    if(x) {
        // code
    }
}

// 指针声明
char *str;  // 星号靠近变量名

15.6.3 测试要求

// 添加单元测试
TEST_CASE("Description of test") {
    // Setup
    // ...
    
    // Action
    // ...
    
    // Assert
    CHECK(result == expected);
}

15.6.4 文档要求

// 添加代码注释
/**
 * @brief 计算两点之间的距离
 * @param p1 第一个点
 * @param p2 第二个点
 * @return 距离值
 */
double Distance(Point p1, Point p2);

15.7 调试技巧

15.7.1 使用日志

// SolveSpace内置日志
dbp("Debug message: %d", value);

15.7.2 可视化调试

// 在屏幕上绘制调试信息
void DebugDraw() {
    // 绘制辅助线
    SS.GW.canvas->DrawLine(p1, p2, hStroke);
    
    // 绘制文本
    SS.GW.canvas->DrawText("Debug info", pos);
}

15.7.3 求解器调试

// 输出求解过程
System::Solve() {
    // ...
    
    // 添加调试输出
    dbp("Iteration %d: residual = %g", iter, residual);
    
    // 输出雅可比矩阵
    for(int i = 0; i < mat.m; i++) {
        for(int j = 0; j < mat.n; j++) {
            dbp("J[%d][%d] = %g", i, j, mat.A.coeff(i,j));
        }
    }
}

15.8 总结

本章介绍了SolveSpace的二次开发实战:

  1. 开发环境: 源码获取、环境配置、构建
  2. 添加约束: 类型定义、方程实现、UI集成
  3. 添加导出: 格式实现、菜单集成
  4. 库集成: C/C++和Python集成示例
  5. 自定义应用: 架构设计、核心组件
  6. 上游贡献: 流程、规范、测试
  7. 调试技巧: 日志、可视化、求解器调试

通过本教程的学习,您应该能够:

  • 理解SolveSpace的核心架构
  • 使用约束求解器库开发自己的应用
  • 为SolveSpace贡献新功能
  • 创建基于SolveSpace的自定义CAD工具

导航


附录: 资源链接


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