
本文目标重新书写状态机按一下按键完成整个车圆角过程改进机床移动路径减少走刀。参考博客机床车圆角的状态机示例-CSDN博客移动路径示意图(结果示意图)改进前 改进后原本的方案走空刀较多和同事交流后认为 圆弧正反交替走的方式能减少空刀提高车圆角的效率。相关代码DlgPruneArcSM.h//-------------------------------------------------------------------------- // 状态机相位 //-------------------------------------------------------------------------- enum PRUNE_SM_PHASE { PRUNE_SM_IDLE 0, // 空闲(未启动 / 已正常结束) PRUNE_SM_RUN, // 运行中 PRUNE_SM_STOP // 已被强行结束 }; //-------------------------------------------------------------------------- // 圆角修整状态机 //-------------------------------------------------------------------------- class CPruneArcSM { public: CPruneArcSM(); ~CPruneArcSM(); //---------------------------------------------------------------------- // 启动前配置(每切一次电极配置一次) // pEdm : EDM 单例 // pPara : 已经 ReadPrunePara() 过的加工参数 // piHomePos : 电极起始机械位置, 下标 0X,1Y,2Z,5U, 其余忽略 // iMaxPass : 本电极总共要切几刀, 1 // iSegDiv : 圆弧离散基准段数(每 90°), 24, 越大越圆滑 //---------------------------------------------------------------------- void Setup(EDM *pEdm, const MAC_PRUNE *pPara, const int *piHomePos, int iMaxPass, int iSegDiv 180); //---------------------------------------------------------------------- // 一个伺服周期调用一次 // bMoveOver : m_stEdmComm.enMoveRuleSpec RULE_MOVE_OVER // bRotateOk : 本周期 EdmRotate(true, iRSpeed, true) 的返回值 //---------------------------------------------------------------------- void Tick(BOOL bMoveOver, BOOL bRotateOk); // 强行结束(急停) void Abort(); //---------------------------------------------------------------------- // 状态查询(给界面显示用) //---------------------------------------------------------------------- int GetPhase() const { return m_iPhase; } int GetPassIndex() const { return m_iPass; } // 当前第几刀(1 起) int GetMaxPass() const { return m_iMaxPass; } BOOL IsRunning() const { return m_iPhase PRUNE_SM_RUN; } BOOL IsStopped() const { return m_iPhase PRUNE_SM_STOP; } //---------------------------------------------------------------------- // 纯计算: 把一条圆弧离散成增量点(不动机床) // iRadius : 半径 // iSegDiv : 基准段数 // vYPt/vZPt: 输出每一步的 Y / Z 增量 // bReverse : FALSE 正向, 从圆弧起点(0,-R)走到终点(R,0), 累加后 (R, R) // TRUE 反向, 从圆弧终点(R,0)走回起点(0,-R), 累加后 (-R, -R) // (同一条几何圆弧, 只是走刀方向相反) // 返回 : 半径, 参数非法返回 -1 //---------------------------------------------------------------------- static int BuildArcPoints(int iRadius, int iSegDiv, std::vectorint vYPt, std::vectorint vZPt, BOOL bReverse FALSE); static BOOL IsReversePass(int iPass); private: struct PRUNE_STEP { int iAxisCnt; int iLabel[MAC_LABEL_COUNT]; int iDist[MAC_LABEL_COUNT]; int iAbs; // 1 绝对 G90, 0 相对 G91 int iFreq; // 0 PRUNE_STEP() { Clear(); } void Clear() { iAxisCnt 0; iAbs 0; iFreq 0; for (int i 0; i MAC_LABEL_COUNT; i) { iLabel[i] 0; iDist[i] 0; } } void Add(int iLabel_, int iDist_) { if (iAxisCnt MAC_LABEL_COUNT) return; iLabel[iAxisCnt] iLabel_; iDist[iAxisCnt] iDist_; iAxisCnt; } BOOL IsEmpty() const { if (iAxisCnt 0) return TRUE; for (int i 0; i iAxisCnt; i) { if (iDist[i] ! 0) return FALSE; } return TRUE; } }; // 整个电极加工的大阶段 enum MacStep { MS_START 0, // 启动: 等 R 轴旋转就绪 MS_PASS, // 逐刀加工 MS_RETRACT_ALL, // 全部切完: 退到 iZSafeHigh MS_HOME, // 回电极原始位置 MS_FINISH // 收尾: 关高频 / 存参数 }; private: // 产生第 iPass 刀的过渡空程工步(退刀 横移 下刀) void BuildPassSteps(int iPass, std::vectorPRUNE_STEP vSteps); // 计算第 iPass 刀的几何 void GetPassGeometry(int iPass, int iR, int iEntryY, int iEntryZ) const; // 发送一个工步, 成功返回 TRUE BOOL SendStep(const PRUNE_STEP step); // 机械坐标 - 相对启动瞬间工作原点的增量 int RelZ(int iMacZ) const; int RelY(int iMacY) const; private: EDM *m_pEdm; MAC_PRUNE m_stPara; int m_iHome[MAC_LABEL_COUNT]; // 起始机械位置 int m_iWork[MAC_LABEL_COUNT]; // 启动瞬间工作坐标基准 int m_iMaxPass; // 总刀数 int m_iPass; // 当前刀号(1 起) int m_iSegDiv; // 圆弧离散基准段数 int m_iSpeedFreq; // 移动速度频率 MacStep m_eMacStep; int m_iPhase; // 本刀的过渡工步队列 std::vectorPRUNE_STEP m_vSteps; int m_iStepIdx; // 本刀圆弧离散数据 std::vectorint m_vArcY; std::vectorint m_vArcZ; int m_iArcIdx; BOOL m_bArcEntered; // 是否已经下刀到圆弧起点 };DlgPruneArcSM.cpp//-------------------------------------------------------------------------- // 构造 / 析构 //-------------------------------------------------------------------------- CPruneArcSM::CPruneArcSM() { m_pEdm NULL; memset(m_stPara, 0, sizeof(m_stPara)); memset(m_iHome, 0, sizeof(m_iHome)); memset(m_iWork, 0, sizeof(m_iWork)); m_iMaxPass 1; m_iPass 0; m_iSegDiv 180; m_iSpeedFreq 10000; m_eMacStep MS_START; m_iPhase PRUNE_SM_IDLE; m_iStepIdx 0; m_iArcIdx 0; m_bArcEntered FALSE; } CPruneArcSM::~CPruneArcSM() { } //-------------------------------------------------------------------------- // 配置 / 启动 //-------------------------------------------------------------------------- void CPruneArcSM::Setup(EDM* pEdm, const MAC_PRUNE* pPara, const int* piHomePos, int iMaxPass, int iSegDiv) { m_pEdm pEdm; if (pPara ! NULL) memcpy(m_stPara, pPara, sizeof(MAC_PRUNE)); if (piHomePos ! NULL) { for (int i 0; i MAC_LABEL_COUNT; i) m_iHome[i] piHomePos[i]; } // 启动瞬间工作坐标基准: 后面绝对定位(Z 安全高度 / 回原点)都用它换算 if (m_pEdm ! NULL) { for (int i 0; i MAC_LABEL_COUNT; i) m_iWork[i] m_pEdm-GetEdmAxisWorkPos(i); } // 移动速度频率, 与原版 MacPrune() 的用法一致 m_iSpeedFreq EDM_Cmd_Handle::GetSpeedFreq(m_stPara.iMvSpeed); m_iMaxPass (iMaxPass 1) ? 1 : iMaxPass; m_iSegDiv (iSegDiv 24) ? 24 : iSegDiv; // 每次 Setup() 都从第 1 刀重新开始 —— 第 1 刀假定电极停在刀路原点 (0,0), // 这是整个正反交替刀路成立的前提, 所以 SetFinish() 每次启动都要重调本函数。 m_iPass 0; m_eMacStep MS_START; m_iPhase PRUNE_SM_RUN; m_iStepIdx 0; m_iArcIdx 0; m_bArcEntered FALSE; m_vSteps.clear(); m_vArcY.clear(); m_vArcZ.clear(); } //-------------------------------------------------------------------------- // 急停 //-------------------------------------------------------------------------- void CPruneArcSM::Abort() { m_vSteps.clear(); m_vArcY.clear(); m_vArcZ.clear(); m_iStepIdx 0; m_iArcIdx 0; m_iPhase PRUNE_SM_STOP; } //-------------------------------------------------------------------------- // 机械坐标 - 相对启动瞬间工作原点的增量 //-------------------------------------------------------------------------- int CPruneArcSM::RelZ(int iMacZ) const { return iMacZ - m_iWork[2]; } int CPruneArcSM::RelY(int iMacY) const { return iMacY - m_iWork[1]; } //-------------------------------------------------------------------------- // 走刀方向策略 // 奇数刀正向: 从参考线(0,-kL)向外扫到上表面(kL,0) // 偶数刀反向: 从上表面(kL,0)扫回参考线(0,-kL) //-------------------------------------------------------------------------- BOOL CPruneArcSM::IsReversePass(int iPass) { return ((iPass % 2) 0) ? TRUE : FALSE; } //-------------------------------------------------------------------------- // 圆弧离散: 等弦长 端点误差补偿 // 段数按最大弓高误差自动确定, 半径再小也不会走成折线。 // 正向: 从(0,-R)到(R,0), 累加后严格等于 (R, R) // 反向: 从(R,0)到(0,-R), 累加后严格等于 (-R, -R) —— 就是正向序列倒过来取负 //-------------------------------------------------------------------------- int CPruneArcSM::BuildArcPoints(int iRadius, int iSegDiv, std::vectorint vYPt, std::vectorint vZPt, BOOL bReverse) { vYPt.clear(); vZPt.clear(); if (iRadius 0) return -1; if (iSegDiv 24) iSegDiv 24; // 弓高误差 err R*(1-cos(dθ/2)) ≈ R*dθ²/8, 取 err 0.05 单位 int iSeg (int)ceil((double)iRadius * DSH_PI / 2.0 / (8.0 * 0.05)); if (iSeg iSegDiv) iSeg iSegDiv; if (iSeg 4000) iSeg 4000; double dTh (DSH_PI / 2.0) / (double)iSeg; double dPrevY 0.0; double dPrevZ -(double)iRadius; int iSumY 0; int iSumZ 0; for (int i 1; i iSeg; i) { double th DSH_PI - dTh * (double)i; // 180° - 90° double dY (double)iRadius (double)iRadius * cos(th); double dZ -(double)iRadius (double)iRadius * sin(th); int iDy (int)floor(dY - dPrevY 0.5); int iDz (int)floor(dZ - dPrevZ 0.5); vYPt.push_back(iDy); vZPt.push_back(iDz); iSumY iDy; iSumZ iDz; dPrevY dY; dPrevZ dZ; } // 端点误差补偿: 保证正向 ΣdYR 且 ΣdZR, 否则多刀会累积偏差 if (!vYPt.empty()) { vYPt[vYPt.size() - 1] (iRadius - iSumY); vZPt[vZPt.size() - 1] (iRadius - iSumZ); } // 反向: 把正向序列整条倒过来再取负 从 (R,0) 走回 (0,-R), Σ (-R, -R) // 端点补偿在取负之后依然成立(总和跟着取负)。 if (bReverse) { int iCnt (int)vYPt.size(); std::vectorint vTmpY(iCnt), vTmpZ(iCnt); for (int i 0; i iCnt; i) { vTmpY[i] -vYPt[iCnt - 1 - i]; vTmpZ[i] -vZPt[iCnt - 1 - i]; } vYPt.swap(vTmpY); vZPt.swap(vTmpZ); } return iRadius; } //-------------------------------------------------------------------------- // 第 iPass 刀的几何(相对电极起始点) // 第 k 刀的圆弧起点 ((k-1)L, -kL), 终点 (kL, -(k-1)L) // 即: 起点在Y (k-1)L这条竖线上, 终点在Z -(k-1)L这条水平线上 // iEntryY/iEntryZ 保留给过渡逻辑参考(第 1 刀为 0) //-------------------------------------------------------------------------- void CPruneArcSM::GetPassGeometry(int iPass, int iR, int iEntryY, int iEntryZ) const { int iFeedUnit m_stPara.iFeedUnitLen; if (iFeedUnit 0) iFeedUnit 1; int k (iPass 1) ? 1 : iPass; iR k * iFeedUnit; iEntryY (k - 1) * iFeedUnit; iEntryZ -(k - 1) * iFeedUnit; } void CPruneArcSM::BuildPassSteps(int iPass, std::vectorPRUNE_STEP vSteps) { vSteps.clear(); int iFeedUnit m_stPara.iFeedUnitLen; if (iFeedUnit 0) iFeedUnit 1; int iR, iEntryY, iEntryZ; GetPassGeometry(iPass, iR, iEntryY, iEntryZ); (void)iR; (void)iEntryY; (void)iEntryZ; PRUNE_STEP st; st.iAbs 0; st.iFreq m_iSpeedFreq; if (iPass 1) { // 第 1 刀: 电极本来就在 (0,0), 直接下 L 到圆弧起点 (0,-L) st.Add(2, -iFeedUnit); // Z -L } else if (IsReversePass(iPass)) { // 偶数刀: 上一刀(正向)终点在 (kL,0), 本刀反向圆弧起点 ((k1)L,0) // 沿上表面右移一格 L st.Add(1, iFeedUnit); // Y L } else { // 奇数刀(k1): 上一刀(反向)终点在 (0,-kL), 本刀正向圆弧起点 (0,-(k1)L) // 沿参考线下行一格 L st.Add(2, -iFeedUnit); // Z -L } if (!st.IsEmpty()) vSteps.push_back(st); } //-------------------------------------------------------------------------- // 发送一个工步 //-------------------------------------------------------------------------- BOOL CPruneArcSM::SendStep(const PRUNE_STEP step) { if (m_pEdm NULL) return FALSE; if (step.IsEmpty()) return TRUE; // 空步直接跳过 DIGIT_CMD cmd; memset(cmd, 0, sizeof(DIGIT_CMD)); cmd.enAim step.iAbs ? AIM_G90 : AIM_G91; cmd.enOrbit ORBIT_G01; // 直线插补 cmd.iFreq (step.iFreq ! 0) ? step.iFreq : 10000; cmd.iAxisCnt 0; for (int i 0; i step.iAxisCnt cmd.iAxisCnt MAC_LABEL_COUNT; i) { if (step.iDist[i] 0) continue; // 该轴不动就不下发 cmd.stAxisDigit[cmd.iAxisCnt].iLabel step.iLabel[i]; cmd.stAxisDigit[cmd.iAxisCnt].iDistance step.iDist[i]; cmd.iAxisCnt; } if (cmd.iAxisCnt 0) return TRUE; return m_pEdm-EdmSendMovePara(cmd); } //-------------------------------------------------------------------------- // 状态机主循环 —— 每个伺服周期调用一次 //-------------------------------------------------------------------------- void CPruneArcSM::Tick(BOOL bMoveOver, BOOL bRotateOk) { if (m_pEdm NULL) return; if (m_iPhase PRUNE_SM_IDLE || m_iPhase PRUNE_SM_STOP) return; // 机床还没停稳, 本周期什么都不做 (与原版 MacPrune 的判断一致) if (!bMoveOver) return; switch (m_eMacStep) { //---------------------------------------------------------------------- // 启动: 打开高频后等 R 轴旋转就绪 //---------------------------------------------------------------------- case MS_START: { if (!bRotateOk) return; // 下个周期继续等 m_iPass 1; m_eMacStep MS_PASS; m_iStepIdx 0; m_vSteps.clear(); // 直接进入 MS_PASS 的排队逻辑 } // 注意: 这里故意不 break, 立刻开始排第 1 刀的工步 // fall through //---------------------------------------------------------------------- // 逐刀加工 //---------------------------------------------------------------------- case MS_PASS: { // (1) 本刀的过渡工步还没排完 - 一次发一条 if (m_iStepIdx (int)m_vSteps.size()) { const PRUNE_STEP step m_vSteps[m_iStepIdx]; if (SendStep(step)) m_iStepIdx; return; } // (2) 过渡工步走完, 开始切圆弧 if (!m_bArcEntered) { // 首次进入本刀圆弧: 生成离散点 int iR, iEntryY, iEntryZ; GetPassGeometry(m_iPass, iR, iEntryY, iEntryZ); (void)iEntryY; // 过渡工步里已经用过, 这里只取 R (void)iEntryZ; // 走刀方向: 奇数刀正向, 偶数刀反向(同一条圆弧换方向) BOOL bReverse IsReversePass(m_iPass); if (BuildArcPoints(iR, m_iSegDiv, m_vArcY, m_vArcZ, bReverse) 0) { m_iPhase PRUNE_SM_STOP; return; } m_iArcIdx 0; m_bArcEntered TRUE; } if (m_iArcIdx (int)m_vArcY.size()) { PRUNE_STEP stArc; stArc.iAbs 0; stArc.iFreq m_iSpeedFreq; stArc.Add(1, m_vArcY[m_iArcIdx]); // Y stArc.Add(2, m_vArcZ[m_iArcIdx]); // Z if (SendStep(stArc)) m_iArcIdx; return; } // (3) 本刀走完 - 进入下一刀 m_vArcY.clear(); m_vArcZ.clear(); m_iArcIdx 0; m_bArcEntered FALSE; m_iPass; if (m_iPass m_iMaxPass) { m_eMacStep MS_RETRACT_ALL; return; } // 排下一刀的过渡工步 BuildPassSteps(m_iPass, m_vSteps); m_iStepIdx 0; return; } //---------------------------------------------------------------------- // 全部切完: 抬到 Z 轴安全高度 //---------------------------------------------------------------------- case MS_RETRACT_ALL: { PRUNE_STEP stSafe; stSafe.iAbs 1; // 绝对坐标 stSafe.iFreq 10000; stSafe.Add(2, RelZ(m_stPara.iZSafeHigh)); // Z 到 iZSafeHigh if (SendStep(stSafe)) { m_eMacStep MS_HOME; } return; } //---------------------------------------------------------------------- // 回电极原始位置(与原版 m_iIndex9 的行为一致, Z 轴最后动) //---------------------------------------------------------------------- case MS_HOME: { PRUNE_STEP stHome; stHome.iAbs 1; stHome.iFreq 10000; for (int i 0; i MAC_LABEL_COUNT; i) { if (i 2) continue; stHome.Add(i, m_iHome[i]); } stHome.Add(2, m_iHome[2]); if (SendStep(stHome)) { m_eMacStep MS_FINISH; } return; } //---------------------------------------------------------------------- // 收尾 //---------------------------------------------------------------------- case MS_FINISH: { m_stPara.iZPos m_iHome[2]; m_pEdm-EdmPrune(false); // 关高频 m_pEdm-SavePrunePara(m_stPara); m_iPhase PRUNE_SM_IDLE; return; } default: m_iPhase PRUNE_SM_IDLE; return; } }