二次开发实战
第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的二次开发实战:
- 开发环境: 源码获取、环境配置、构建
- 添加约束: 类型定义、方程实现、UI集成
- 添加导出: 格式实现、菜单集成
- 库集成: C/C++和Python集成示例
- 自定义应用: 架构设计、核心组件
- 上游贡献: 流程、规范、测试
- 调试技巧: 日志、可视化、求解器调试
通过本教程的学习,您应该能够:
- 理解SolveSpace的核心架构
- 使用约束求解器库开发自己的应用
- 为SolveSpace贡献新功能
- 创建基于SolveSpace的自定义CAD工具
导航
- 上一章: 第14章 - 源码架构分析
- 返回教程首页
附录: 资源链接
- 官方网站: https://solvespace.com/
- GitHub仓库: https://github.com/solvespace/solvespace
- 参考手册: https://solvespace.com/ref.pl
- 论坛: https://solvespace.com/forum.pl
- IRC: #solvespace @ libera.chat

浙公网安备 33010602011771号