一个简单的凸轮设计程序(演示 EasyX 中的 GUI)
easyx吧
全部回复
仅看楼主
level 5
邹汇东 楼主
rt,这是一个凸轮设计的简单程序,供大家参考。[呵呵]
2026年05月10日 14点05分 1
level 5
邹汇东 楼主
2026年05月10日 14点05分 2
level 5
邹汇东 楼主
代码很短,只有不到 500 行,不过其中多窗口和 GUI 的实现需要借助 HiEasyX 库。
2026年05月10日 14点05分 3
level 5
邹汇东 楼主
防止链接被吞,HiEasyX 的地址我就不放了,在 CodeBus 可以搜到。下面贴代码:
2026年05月10日 14点05分 4
level 5
邹汇东 楼主
/////////////////////////////////////////////////////
//
// 解析法设计凸轮大作业
// Cam Design by Analytical Method
//
// 作者:huidong
// 日期:2026-05-10
// 编译环境:Visual Studio 2022 | Windows 11
// 依赖库:
// HiEasyX 1.0.0
// HiEasyX (Traditional) Ver 0.5.2 (dev)
//
// * 注意:
// 在 HiEasyX (Traditional) 上使用新版 HiEasyX,
// 需要将 impl 的源代码中所有
#include <graphics.h> 改为 #
include 。
//
#include
#include
#include
#define PI 3.14
15926535897
9323846f
////////////// 类型定义 //////////////
struct PointF
{
float x;
float y;
};
////////////// 全局变量 //////////////
float g_r0 = 30; ///< 基圆半径
float g_e = 10; ///< 正偏距
float g_r_r = 10; ///< 滚子半径
float g_h = 26.f; ///< 总行程
bool b_show_motion_curve = false; ///< 是否显示运动规律图像
bool b_show_roller = true; ///< 是否显示滚子
bool b_show_envelope_circles = true; ///< 是否显示包络圆
////////////// 函数实现(算法) //////////////
/**
* @Brief 获取极坐标 (r, theta) 对应的笛卡尔坐标
* @param r 半径
* @param theta 角度(逆时针为正)
* @Return笛卡尔坐标
*/
PointF GetCirclePoint(float r, float theta)
{
PointF pt;
pt.x = r * cosf(theta);
pt.y = r * sinf(theta);
return pt;
}
/**
* @brief 获取凸轮运动角为δ时顶杆到基圆的距离
* @param delta 凸轮运动角(弧度)
* @return 顶杆到基圆的距离
*/
float GetDisplacementAt(float delta)
{
while (delta < 0.f)
{
delta += 2 * PI;
}
while (delta > 2 * PI)
{
delta -= 2 * PI;
}
// #########
# 此处描述凸轮运动规律 #
#########
// delta = 0 ~ 2 * PI
// 匀速上升
if (0.f <= delta && delta <= 150.f * PI / 180.f)
{
return delta / (150.f * PI / 180.f) * g_h;
}
// 远休止
if (150.f * PI / 180.f < delta && delta <= 180.f * PI / 180.f)
{
return g_h;
}
// 等加速等减速回程
float A = g_h / 2 * 9 / (PI * PI); // 二次项系数
if (180.f * PI / 180.f < delta && delta <= 240.f * PI / 180.f)
{
return -A * (delta - PI) * (delta - PI) + g_h;
}
if (240.f * PI / 180.f < delta && delta <= 300.f * PI / 180.f)
{
return A * powf(delta - 300.f * PI / 180.f, 2);
}
// 近休止
if (300.f * PI / 180.f < delta)
{
return 0.f;
}
return 0.f; // 异常
}
/**
* @brief 获取凸轮运动角为δ时凸轮迹线上点的位置
* @param delta 凸轮运动角(弧度)
* @param offset 已经旋转的角度偏移(弧度),默认为 0,表示不偏移
* @return 凸轮迹线上点的位置
* @note
* 1. 凸轮逆时针旋转,角速度记为ω
* 2. 该函数返回坐标时,视凸轮为不动,顶杆绕凸轮顺时针旋转
*/
PointF CamPointAt(float delta, float offset)
{
// 点 A 位于偏心圆上
PointF ptA = GetCirclePoint(g_e, -delta); // 视凸轮静止,故角度反转
// 由 OA 线段推导 B 点位置(B 点位于基圆上)
float k_OA = tanf(-delta); // OA 线段斜率
float eqA = g_r0 * k_OA; // eq 开头表示方程系数
float eqB = g_r0;
float eqC = ptA.y * k_OA + ptA.x;
float eqDelta = sqrtf(eqA * eqA + eqB * eqB - eqC * eqC); // 判别式
// 测试两个解的有效性,sign = 1 或 -1,分别对应两个解
auto funcTrySolution = [=](int sign, PointF& ptB) -> bool {
float t = (eqA + sign * eqDelta) / (eqB + eqC);
// TODO:理论上换元为 t 之后 theta = 180°是否为原方程的解需要特判,但在程序中不特判也能运行
float theta = 2 * atanf(t);
ptB = GetCirclePoint(g_r0, theta);
float z = ptA.x * ptB.y - ptA.y * ptB.x; // 叉乘得到 z 分量
if (z > 0) // 顶杆绕凸轮顺时针旋转,故向量积为正
{
return true;
}
else
{
return false;
}
};
// 点 B 位于基圆上,且 OA 线段与偏心圆相切
PointF ptB;
bool bSucceed = funcTrySolution(1, ptB) || funcTrySolution(-1, ptB);
if (!bSucceed) // 异常:无解
{
// TODO
__debugbreak();
}
// 点 C 位于凸轮迹线上,代表顶杆位置
PointF ptC;
float displacement = GetDisplacementAt(delta + offset) - g_r_r;
ptC.x = ptB.x + displacement * sinf(delta);
ptC.y = ptB.y + displacement * cosf(delta);
return ptC;
}
2026年05月10日 14点05分 5
level 5
邹汇东 楼主
////////////// 函数实现(可视化) //////////////
/**
* @Brief 绘制运动规律图像
*/
void DrawMotionCurve()
{
setaspectratio(1, -1);
setorigin(320, 240);
float visual_ratio_x = 30.f;
float visual_ratio_y = 4.f;
for (float theta = 0; theta <= 2 * PI; theta += 0.01f)
{
float s = GetDisplacementAt(theta);
putpixel((int)(visual_ratio_x * theta), (int)(visual_ratio_y * s), WHITE);
}
}
/**
* @brief 绘制凸轮
*/
void DrawCam(float offset)
{
setaspectratio(1, -1);
setorigin(320, 240);
// 手动放大比 setaspectratio 的效果好
float visual_ratio = 3.f;
// 推杆轴与基圆的交点 y 坐标
float fBaseY = sqrtf(g_r0 * g_r0 - g_e * g_e);
// 中心点
setlinecolor(WHITE);
setfillcolor(WHITE);
fillcircle(0, 0, 1);
// 基圆(由于最终画的是包络线,所以基圆半径要减掉滚子半径)
setlinecolor(GREEN);
circle(0, 0, (int)((g_r0 - g_r_r) * visual_ratio));
// 偏心圆
setlinecolor(BLUE);
circle(0, 0, (int)(g_e * visual_ratio));
// 包络线(近似的)
for (float theta = 0; theta <= 2 * PI; theta += 0.01f)
{
PointF pt = CamPointAt(theta, offset);
putpixel((int)(visual_ratio * pt.x), (int)(visual_ratio * pt.y), WHITE);
}
// 运动规律图像
if (b_show_motion_curve)
{
// 确定图幅(x 方向)
int nCurveW = 60; // 图幅宽度(会乘以 visual_ratio)
float recal_offset = offset;
while (recal_offset < 0.f)
{
recal_offset += 2 * PI;
}
while (recal_offset > 2 * PI)
{
recal_offset -= 2 * PI;
}
// 1. 绘制坐标轴主体 (黄色)
setlinecolor(YELLOW);
int ax_x0 = (int)(visual_ratio * (-recal_offset / (2 * PI) * nCurveW + g_e));
int ax_y0 = (int)(visual_ratio * fBaseY);
int ax_x_end = ax_x0 + (int)(visual_ratio * (nCurveW + g_e + 5)); // 稍微增长一点画箭头
int ax_y_end = (int)(visual_ratio * (fBaseY + g_h + 5));
// x 轴
line(ax_x0, ax_y0, ax_x_end, ax_y0);
// x 轴箭头
line(ax_x_end, ax_y0, ax_x_end - 5, ax_y0 + 3);
line(ax_x_end, ax_y0, ax_x_end - 5, ax_y0 - 3);
// y 轴
line(ax_x0, ax_y0, ax_x0, ax_y_end);
// y 轴箭头
line(ax_x0, ax_y_end, ax_x0 - 3, ax_y_end - 5);
line(ax_x0, ax_y_end, ax_x0 + 3, ax_y_end - 5);
// 绘制文字 (需要反转 aspect ratio 否则文字是倒下的)
setaspectratio(1, 1);
settextcolor(YELLOW);
setbkmode(TRANSPARENT);
// 注意:在 1, 1 坐标系下,原本 y 轴正方向的值要取反才能对齐
outtextxy(ax_x_end + 5, -ax_y0 - 8, L"delta");
outtextxy(ax_x0 - 20, -ax_y_end - 15, L"s");
// 恢复原本的 y 轴向上坐标系
setaspectratio(1, -1);
// 绘制图像
for (float theta = 0; theta <= 2 * PI; theta += 0.01f)
{
float s = GetDisplacementAt(theta);
putpixel((int)(visual_ratio * ((theta - recal_offset) / (2 * PI) * nCurveW + g_e)),
(int)(visual_ratio * (fBaseY + s)), WHITE);
}
}
// 滚子
if (b_show_roller)
{
float posY_r = fBaseY + GetDisplacementAt(offset);
setlinecolor(GRAY);
rectangle((int)((g_e - g_r_r / 6) * visual_ratio), (int)(posY_r * visual_ratio),
(int)((g_e + g_r_r / 6) * visual_ratio), (int)((posY_r + 3 * g_r_r) * visual_ratio));
circle((int)(g_e * visual_ratio), (int)(posY_r * visual_ratio), (int)(g_r_r * visual_ratio));
}
///// DEBUG /////
// 显示包络
if (b_show_envelope_circles)
{
float r_r_save = g_r_r;
g_r_r = 0;
for (float theta = 0; theta <= 2 * PI; theta += 0.3f)
{
PointF pt = CamPointAt(theta, offset);
putpixel((int)(visual_ratio * pt.x), (int)(visual_ratio * pt.y), WHITE);
setlinecolor(RED);
circle((int)(visual_ratio * pt.x), (int)(visual_ratio * pt.y), (int)(visual_ratio * r_r_save));
}
g_r_r = r_r_save;
}
setaspectratio(1, 1);
setorigin(0, 0);
}
2026年05月10日 14点05分 6
吧务
level 11
大神一如既往的厉害[真棒]
2026年05月18日 11点05分 8
谢谢吧主支持!我会继续努力滴[太开心] 加油~~
2026年06月10日 09点06分
1