/******************************************************************** * Description: tp.c * Trajectory planner based on TC elements * * Derived from a work by Fred Proctor & Will Shackleford * * Author: * License: GPL Version 2 * System: Linux * * Copyright (c) 2004 All rights reserved. ********************************************************************/ #ifdef ULAPI #include #include #endif #include "rtapi.h" /* rtapi_print_msg */ #include "rtapi_string.h" /* NULL */ #include "posemath.h" #include "tc.h" #include "tp.h" #include "rtapi_math.h" #include "../motion/motion.h" #include "motion_debug.h" extern emcmot_status_t *emcmotStatus; extern emcmot_debug_t *emcmotDebug; int output_chan = 0; int tpCreate(TP_STRUCT * tp, int _queueSize, TC_STRUCT * tcSpace) { if (0 == tp) { return -1; } if (_queueSize <= 0) { tp->queueSize = TP_DEFAULT_QUEUE_SIZE; } else { tp->queueSize = _queueSize; } /* create the queue */ if (-1 == tcqCreate(&tp->queue, tp->queueSize, tcSpace)) { return -1; } /* init the rest of our data */ return tpInit(tp); } /* tpClear() is a "soft init" in the sense that the TP_STRUCT configuration parameters (cycleTime, vMax, and aMax) are left alone, but the queue is cleared, and the flags are set to an empty, ready queue. The currentPos is left alone, and goalPos is set to this position. This function is intended to put the motion queue in the state it would be if all queued motions finished at the current position. */ int tpClear(TP_STRUCT * tp) { tcqInit(&tp->queue); tp->goalPos.tran.x = tp->currentPos.tran.x; tp->goalPos.tran.y = tp->currentPos.tran.y; tp->goalPos.tran.z = tp->currentPos.tran.z; tp->goalPos.a = tp->currentPos.a; tp->goalPos.b = tp->currentPos.b; tp->goalPos.c = tp->currentPos.c; tp->nextId = 0; tp->execId = 0; tp->motionType = 0; tp->termCond = TC_TERM_COND_BLEND; tp->tolerance = 0.0; tp->done = 1; tp->depth = tp->activeDepth = 0; tp->aborting = 0; tp->pausing = 0; tp->vScale = emcmotStatus->net_feed_scale; tp->synchronized = 0; tp->uu_per_rev = 0.0; emcmotStatus->spindleSync = 0; return 0; } int tpInit(TP_STRUCT * tp) { tp->cycleTime = 0.0; tp->vLimit = 0.0; tp->vScale = 1.0; tp->aMax = 0.0; tp->vMax = 0.0; tp->ini_maxvel = 0.0; tp->wMax = 0.0; tp->wDotMax = 0.0; tp->currentPos.tran.x = 0.0; tp->currentPos.tran.y = 0.0; tp->currentPos.tran.z = 0.0; tp->currentPos.a = 0.0; tp->currentPos.b = 0.0; tp->currentPos.c = 0.0; return tpClear(tp); } int tpSetCycleTime(TP_STRUCT * tp, double secs) { if (0 == tp || secs <= 0.0) { return -1; } tp->cycleTime = secs; return 0; } // This is called before adding lines or circles, specifying // vMax (the velocity requested by the F word) and // ini_maxvel, the max velocity possible before meeting // a machine constraint caused by an AXIS's max velocity. // (the TP is allowed to go up to this high when feed // override >100% is requested) These settings apply to // subsequent moves until changed. int tpSetVmax(TP_STRUCT * tp, double vMax, double ini_maxvel) { if (0 == tp || vMax <= 0.0 || ini_maxvel <= 0.0) { return -1; } tp->vMax = vMax; tp->ini_maxvel = ini_maxvel; return 0; } // I think this is the [TRAJ] max velocity. This should // be the max velocity of the TOOL TIP, not necessarily // any particular axis. This applies to subsequent moves // until changed. int tpSetVlimit(TP_STRUCT * tp, double vLimit) { if (0 == tp || vLimit <= 0.0) { return -1; } tp->vLimit = vLimit; return 0; } // Set max accel int tpSetAmax(TP_STRUCT * tp, double aMax) { if (0 == tp || aMax <= 0.0) { return -1; } tp->aMax = aMax; return 0; } /* tpSetId() sets the id that will be used for the next appended motions. nextId is incremented so that the next time a motion is appended its id will be one more than the previous one, modulo a signed int. If you want your own ids for each motion, call this before each motion you append and stick what you want in here. */ int tpSetId(TP_STRUCT * tp, int id) { if (0 == tp) { return -1; } tp->nextId = id; return 0; } /* tpGetExecId() returns the id of the last motion that is currently executing. */ int tpGetExecId(TP_STRUCT * tp) { if (0 == tp) { return -1; } return tp->execId; } /* tpSetTermCond(tp, cond) sets the termination condition for all subsequent queued moves. If cond is TC_TERM_STOP, motion comes to a stop before a subsequent move begins. If cond is TC_TERM_BLEND, the following move is begun when the current move decelerates. */ int tpSetTermCond(TP_STRUCT * tp, int cond, double tolerance) { if (0 == tp) { return -1; } if (cond != TC_TERM_COND_STOP && cond != TC_TERM_COND_BLEND) { return -1; } tp->termCond = cond; tp->tolerance = tolerance; return 0; } // Used to tell the tp the initial position. It sets // the current position AND the goal position to be the same. // Used only at TP initialization and when switching modes. int tpSetPos(TP_STRUCT * tp, EmcPose pos) { if (0 == tp) { return -1; } tp->currentPos = pos; tp->goalPos = pos; return 0; } int tpAddRigidTap(TP_STRUCT *tp, EmcPose end, double vel, double ini_maxvel, double acc, unsigned char enables) { TC_STRUCT tc; PmLine line_xyz; PmPose start_xyz, end_xyz; PmCartesian abc, uvw; PmQuaternion identity_quat = { 1.0, 0.0, 0.0, 0.0 }; if (!tp) { rtapi_print_msg(RTAPI_MSG_ERR, "TP is null\n"); return -1; } if (tp->aborting) { rtapi_print_msg(RTAPI_MSG_ERR, "TP is aborting\n"); return -1; } start_xyz.tran = tp->goalPos.tran; end_xyz.tran = end.tran; start_xyz.rot = identity_quat; end_xyz.rot = identity_quat; // abc cannot move abc.x = tp->goalPos.a; abc.y = tp->goalPos.b; abc.z = tp->goalPos.c; uvw.x = tp->goalPos.u; uvw.y = tp->goalPos.v; uvw.z = tp->goalPos.w; pmLineInit(&line_xyz, start_xyz, end_xyz); tc.cycle_time = tp->cycleTime; tc.coords.rigidtap.reversal_target = line_xyz.tmag; // allow 10 turns of the spindle to stop - we don't want to just go on forever tc.target = line_xyz.tmag + 10. * tp->uu_per_rev; tc.progress = 0.0; tc.reqvel = vel; tc.maxaccel = acc; tc.feed_override = 0.0; tc.maxvel = ini_maxvel; tc.id = tp->nextId; tc.active = 0; tc.coords.rigidtap.xyz = line_xyz; tc.coords.rigidtap.abc = abc; tc.coords.rigidtap.uvw = uvw; tc.coords.rigidtap.state = TAPPING; tc.motion_type = TC_RIGIDTAP; tc.canon_motion_type = 0; tc.blend_with_next = 0; tc.tolerance = tp->tolerance; if(!tp->synchronized) { rtapi_print_msg(RTAPI_MSG_ERR, "Cannot add unsynchronized rigid tap move.\n"); return -1; } tc.synchronized = tp->synchronized; tc.uu_per_rev = tp->uu_per_rev; tc.velocity_mode = tp->velocity_mode; tc.enables = enables; if (tcqPut(&tp->queue, tc) == -1) { rtapi_print_msg(RTAPI_MSG_ERR, "tcqPut failed.\n"); return -1; } // do not change tp->goalPos here, // since this move will end just where it started tp->done = 0; tp->depth = tcqLen(&tp->queue); tp->nextId++; return 0; } // Add a straight line to the tc queue. This is a coordinated // move in any or all of the six axes. it goes from the end // of the previous move to the new end specified here at the // currently-active accel and vel settings from the tp struct. int tpAddLine(TP_STRUCT * tp, EmcPose end, int type, double vel, double ini_maxvel, double acc, unsigned char enables) { TC_STRUCT tc; PmLine line_xyz, line_uvw, line_abc; PmPose start_xyz, end_xyz; PmPose start_uvw, end_uvw; PmPose start_abc, end_abc; PmQuaternion identity_quat = { 1.0, 0.0, 0.0, 0.0 }; if (!tp) { rtapi_print_msg(RTAPI_MSG_ERR, "TP is null\n"); return -1; } if (tp->aborting) { rtapi_print_msg(RTAPI_MSG_ERR, "TP is aborting\n"); return -1; } start_xyz.tran = tp->goalPos.tran; end_xyz.tran = end.tran; start_uvw.tran.x = tp->goalPos.u; start_uvw.tran.y = tp->goalPos.v; start_uvw.tran.z = tp->goalPos.w; end_uvw.tran.x = end.u; end_uvw.tran.y = end.v; end_uvw.tran.z = end.w; start_abc.tran.x = tp->goalPos.a; start_abc.tran.y = tp->goalPos.b; start_abc.tran.z = tp->goalPos.c; end_abc.tran.x = end.a; end_abc.tran.y = end.b; end_abc.tran.z = end.c; start_xyz.rot = identity_quat; end_xyz.rot = identity_quat; start_uvw.rot = identity_quat; end_uvw.rot = identity_quat; start_abc.rot = identity_quat; end_abc.rot = identity_quat; pmLineInit(&line_xyz, start_xyz, end_xyz); pmLineInit(&line_uvw, start_uvw, end_uvw); pmLineInit(&line_abc, start_abc, end_abc); tc.cycle_time = tp->cycleTime; if (!line_xyz.tmag_zero) tc.target = line_xyz.tmag; else if (!line_uvw.tmag_zero) tc.target = line_uvw.tmag; else tc.target = line_abc.tmag; tc.progress = 0.0; tc.reqvel = vel; tc.maxaccel = acc; tc.feed_override = 0.0; tc.maxvel = ini_maxvel; tc.id = tp->nextId; tc.active = 0; tc.coords.line.xyz = line_xyz; tc.coords.line.uvw = line_uvw; tc.coords.line.abc = line_abc; tc.motion_type = TC_LINEAR; tc.canon_motion_type = type; tc.blend_with_next = tp->termCond == TC_TERM_COND_BLEND; tc.tolerance = tp->tolerance; tc.synchronized = tp->synchronized; tc.velocity_mode = tp->velocity_mode; tc.uu_per_rev = tp->uu_per_rev; tc.enables = enables; if (tcqPut(&tp->queue, tc) == -1) { rtapi_print_msg(RTAPI_MSG_ERR, "tcqPut failed.\n"); return -1; } tp->goalPos = end; // remember the end of this move, as it's // the start of the next one. tp->done = 0; tp->depth = tcqLen(&tp->queue); tp->nextId++; return 0; } // likewise, this adds a circular (circle, arc, helix) move from // the end of the last move to this new position. end is the // xyzabc of the destination, center/normal/turn specify the arc // in a way that makes sense to pmCircleInit (we don't care about // the details here.) Note that degenerate arcs/circles are not // allowed; we are guaranteed to have a move in xyz so target is // always the circle/arc/helical length. int tpAddCircle(TP_STRUCT * tp, EmcPose end, PmCartesian center, PmCartesian normal, int turn, int type, double vel, double ini_maxvel, double acc, unsigned char enables) { TC_STRUCT tc; PmCircle circle; PmLine line_uvw, line_abc; PmPose start_xyz, end_xyz; PmPose start_uvw, end_uvw; PmPose start_abc, end_abc; double helix_z_component; // z of the helix's cylindrical coord system double helix_length; PmQuaternion identity_quat = { 1.0, 0.0, 0.0, 0.0 }; if (!tp || tp->aborting) return -1; start_xyz.tran = tp->goalPos.tran; end_xyz.tran = end.tran; start_abc.tran.x = tp->goalPos.a; start_abc.tran.y = tp->goalPos.b; start_abc.tran.z = tp->goalPos.c; end_abc.tran.x = end.a; end_abc.tran.y = end.b; end_abc.tran.z = end.c; start_uvw.tran.x = tp->goalPos.u; start_uvw.tran.y = tp->goalPos.v; start_uvw.tran.z = tp->goalPos.w; end_uvw.tran.x = end.u; end_uvw.tran.y = end.v; end_uvw.tran.z = end.w; start_xyz.rot = identity_quat; end_xyz.rot = identity_quat; start_uvw.rot = identity_quat; end_uvw.rot = identity_quat; start_abc.rot = identity_quat; end_abc.rot = identity_quat; pmCircleInit(&circle, start_xyz, end_xyz, center, normal, turn); pmLineInit(&line_uvw, start_uvw, end_uvw); pmLineInit(&line_abc, start_abc, end_abc); // find helix length pmCartMag(circle.rHelix, &helix_z_component); helix_length = pmSqrt(pmSq(circle.angle * circle.radius) + pmSq(helix_z_component)); tc.cycle_time = tp->cycleTime; tc.target = helix_length; tc.progress = 0.0; tc.reqvel = vel; tc.maxaccel = acc; tc.feed_override = 0.0; tc.maxvel = ini_maxvel; tc.id = tp->nextId; tc.active = 0; tc.coords.circle.xyz = circle; tc.coords.circle.uvw = line_uvw; tc.coords.circle.abc = line_abc; tc.motion_type = TC_CIRCULAR; tc.canon_motion_type = type; tc.blend_with_next = tp->termCond == TC_TERM_COND_BLEND; tc.tolerance = tp->tolerance; tc.synchronized = tp->synchronized; tc.velocity_mode = tp->velocity_mode; tc.uu_per_rev = tp->uu_per_rev; tc.enables = enables; if (tcqPut(&tp->queue, tc) == -1) { return -1; } tp->goalPos = end; tp->done = 0; tp->depth = tcqLen(&tp->queue); tp->nextId++; return 0; } void tcRunCycle(TC_STRUCT *tc, double *v, int *on_final_decel) { double discr, maxnewvel, newvel, newaccel=0; if(!tc->blending) tc->vel_at_blend_start = tc->currentvel; discr = 0.5 * tc->cycle_time * tc->currentvel - (tc->target - tc->progress); if(discr > 0.0) { // should never happen: means we've overshot the target newvel = maxnewvel = 0.0; } else { discr = 0.25 * pmSq(tc->cycle_time) - 2.0 / tc->maxaccel * discr; newvel = maxnewvel = -0.5 * tc->maxaccel * tc->cycle_time + tc->maxaccel * pmSqrt(discr); } if(newvel <= 0.0) { // also should never happen - if we already finished this tc, it was // caught above newvel = newaccel = 0.0; tc->progress = tc->target; } else { // constrain velocity if(newvel > tc->reqvel * tc->feed_override) newvel = tc->reqvel * tc->feed_override; if(newvel > tc->maxvel) newvel = tc->maxvel; // get resulting acceleration newaccel = (newvel - tc->currentvel) / tc->cycle_time; // constrain acceleration and get resulting velocity if(newaccel > 0.0 && newaccel > tc->maxaccel) { newaccel = tc->maxaccel; newvel = tc->currentvel + newaccel * tc->cycle_time; } if(newaccel < 0.0 && newaccel < -tc->maxaccel) { newaccel = -tc->maxaccel; newvel = tc->currentvel + newaccel * tc->cycle_time; } // update position in this tc tc->progress += (newvel + tc->currentvel) * 0.5 * tc->cycle_time; } tc->currentvel = newvel; if(v) *v = newvel; if(on_final_decel) *on_final_decel = fabs(maxnewvel - newvel) < 0.001; } // This is the brains of the operation. It's called every TRAJ period // and is expected to set tp->currentPos to the new machine position. // Lots of other tp fields (depth, done, etc) have to be twiddled to // communicate the status; I think those are spelled out here correctly // and I can't clean it up without breaking the API that the TP presents // to motion. It's not THAT bad and in the interest of not touching // stuff outside this directory, I'm going to leave it for now. int tpRunCycle(TP_STRUCT * tp, long period) { // vel = (new position - old position) / cycle time // (two position points required) // // acc = (new vel - old vel) / cycle time // (three position points required) TC_STRUCT *tc, *nexttc; double primary_vel; int on_final_decel; EmcPose primary_before, primary_after; EmcPose secondary_before, secondary_after; EmcPose primary_displacement, secondary_displacement; static double spindleoffset; static int waiting = 0; double save_vel; emcmotStatus->tcqlen = tcqLen(&tp->queue); tc = tcqItem(&tp->queue, 0, period); if(!tc) { // this means the motion queue is empty. This can represent // the end of the program OR QUEUE STARVATION. In either case, // I want to stop. Some may not agree that's what it should do. tcqInit(&tp->queue); tp->goalPos = tp->currentPos; tp->done = 1; tp->depth = tp->activeDepth = 0; tp->aborting = 0; tp->execId = 0; tp->motionType = 0; tpResume(tp); // when not executing a move, use the current enable flags emcmotStatus->enables_queued = emcmotStatus->enables_new; return 0; } if (tc->target == tc->progress) { // if we're synced, and this move is ending, save the // spindle position so the next synced move can be in // the right place. if(tc->synchronized) spindleoffset += tc->target/tc->uu_per_rev; else spindleoffset = 0.0; // done with this move tcqRemove(&tp->queue, 1); // so get next move tc = tcqItem(&tp->queue, 0, period); if(!tc) return 0; } if (tc->motion_type == TC_RIGIDTAP) { switch (tc->coords.rigidtap.state) { case TAPPING: if (tc->progress >= tc->coords.rigidtap.reversal_target) { // command reversal emcmotStatus->spindle.speed *= -1; tc->coords.rigidtap.state = REVERSING; } break; case REVERSING: if (tc->reqvel == 0.0) { PmPose start, end; PmLine *aux = &tc->coords.rigidtap.aux_xyz; // we've stopped, so set a new target at the original position tc->coords.rigidtap.spindlerevs_at_reversal = emcmotStatus->spindleRevs; pmLinePoint(&tc->coords.rigidtap.xyz, tc->progress, &start); end = tc->coords.rigidtap.xyz.start; pmLineInit(aux, start, end); tc->coords.rigidtap.reversal_target = aux->tmag; tc->target = aux->tmag + 10. * tc->uu_per_rev; tc->progress = 0.0; tc->coords.rigidtap.state = RETRACTION; } break; case RETRACTION: if (tc->progress >= tc->coords.rigidtap.reversal_target) { emcmotStatus->spindle.speed *= -1; tc->coords.rigidtap.state = FINAL_REVERSAL; } break; case FINAL_REVERSAL: if (tc->reqvel == 0.0) { PmPose start, end; PmLine *aux = &tc->coords.rigidtap.aux_xyz; pmLinePoint(aux, tc->progress, &start); end = tc->coords.rigidtap.xyz.start; pmLineInit(aux, start, end); tc->target = aux->tmag; tc->progress = 0.0; tc->synchronized = 0; tc->reqvel = tc->maxvel; tc->coords.rigidtap.state = FINAL_PLACEMENT; } break; case FINAL_PLACEMENT: // this is a regular move now, it'll stop at target above. break; } } // this is no longer the segment we were waiting for if(waiting && waiting != tc->id) waiting = 0; if(waiting) { if(emcmotStatus->spindle_index_enable) { /* haven't passed index yet */ return 0; } else { /* passed index, start the move */ emcmotStatus->spindleSync = 1; waiting=0; } } if(!tc->synchronized) emcmotStatus->spindleSync = 0; // now we have the active tc. get the upcoming one, if there is one. // it's not an error if there isn't another one - we just don't // do blending. This happens in MDI for instance. if(!emcmotDebug->stepping && tc->blend_with_next) nexttc = tcqItem(&tp->queue, 1, period); else nexttc = NULL; if(!tc->synchronized && nexttc && nexttc->synchronized && !nexttc->velocity_mode) { // we'll have to wait for spindle sync; might as well // stop at the right place (don't blend) tc->blend_with_next = 0; nexttc = NULL; } if(tc->active == 0) { // this means this tc is being read for the first time. tc->currentvel = 0; tp->depth = tp->activeDepth = 1; tp->motionType = tc->canon_motion_type; tc->active = 1; tc->blending = 0; // clamp motion's velocity at TRAJ MAX_VELOCITY (tooltip maxvel) if(tc->maxvel > tp->vLimit) tc->maxvel = tp->vLimit; // honor accel constraint in case we happen to make an acute angle // with the next segment. if(tc->blend_with_next) tc->maxaccel /= 2.0; if(tc->synchronized) { if(!tc->velocity_mode && !emcmotStatus->spindleSync) { // if we aren't already synced, wait waiting = tc->id; // ask for an index reset emcmotStatus->spindle_index_enable = 1; spindleoffset = 0.0; // don't move: wait return 0; } } } if(nexttc && nexttc->active == 0) { // this means this tc is being read for the first time. nexttc->currentvel = 0; tp->depth = tp->activeDepth = 1; nexttc->active = 1; nexttc->blending = 0; // clamp motion's velocity at TRAJ MAX_VELOCITY (tooltip maxvel) if(nexttc->maxvel > tp->vLimit) nexttc->maxvel = tp->vLimit; // honor accel constraint if we happen to make an acute angle with the // above segment or the following one if(tc->blend_with_next || nexttc->blend_with_next) nexttc->maxaccel /= 2.0; } if(tp->aborting) { // an abort message has come if( (tc->currentvel == 0.0 && !nexttc) || (tc->currentvel == 0.0 && nexttc && nexttc->currentvel == 0.0) ) { tcqInit(&tp->queue); tp->goalPos = tp->currentPos; tp->done = 1; tp->depth = tp->activeDepth = 0; tp->aborting = 0; tp->execId = 0; tp->motionType = 0; tp->synchronized = 0; emcmotStatus->spindleSync = 0; tpResume(tp); return 0; } else { tc->reqvel = 0.0; if(nexttc) nexttc->reqvel = 0.0; } } if(tc->synchronized) { double revs; double pos_error; if(tc->velocity_mode) { pos_error = emcmotStatus->spindleSpeedIn * tc->uu_per_rev; if(nexttc) pos_error -= nexttc->progress; /* ?? */ if(!tp->aborting) tc->reqvel = pos_error; } else { if(tc->motion_type == TC_RIGIDTAP && (tc->coords.rigidtap.state == RETRACTION || tc->coords.rigidtap.state == FINAL_REVERSAL)) revs = tc->coords.rigidtap.spindlerevs_at_reversal - emcmotStatus->spindleRevs; else revs = emcmotStatus->spindleRevs; pos_error = (revs - spindleoffset) * tc->uu_per_rev - tc->progress; if(nexttc) pos_error -= nexttc->progress; tc->reqvel = pos_error/tc->cycle_time/2.0; } tc->feed_override = 1.0; if(tc->reqvel < 0.0) tc->reqvel = 0.0; if(nexttc) { if (nexttc->synchronized) { nexttc->reqvel = pos_error/nexttc->cycle_time; nexttc->feed_override = 1.0; if(nexttc->reqvel < 0.0) nexttc->reqvel = 0.0; } else { nexttc->feed_override = emcmotStatus->net_feed_scale; } } } else { tc->feed_override = emcmotStatus->net_feed_scale; if(nexttc) { nexttc->feed_override = emcmotStatus->net_feed_scale; } } /* handle pausing */ if(tp->pausing && !tc->synchronized) { tc->feed_override = 0.0; if(nexttc) { nexttc->feed_override = 0.0; } } // calculate the approximate peak velocity the nexttc will hit. // we know to start blending it in when the current tc goes below // this velocity... if(nexttc && nexttc->maxaccel) { tc->blend_vel = nexttc->maxaccel * pmSqrt(nexttc->target / nexttc->maxaccel); if(tc->blend_vel > nexttc->reqvel * nexttc->feed_override) { // segment has a cruise phase so let's blend over the // whole accel period if possible tc->blend_vel = nexttc->reqvel * nexttc->feed_override; } if(tc->maxaccel < nexttc->maxaccel) tc->blend_vel *= tc->maxaccel/nexttc->maxaccel; if(tc->tolerance) { /* see diagram blend.fig. T (blend tolerance) is given, theta * is calculated from dot(s1,s2) * * blend criteria: we are decelerating at the end of segment s1 * and we pass distance d from the end. * find the corresponding velocity v when passing d. * * in the drawing note d = 2T/cos(theta) * * when v1 is decelerating at a to stop, v = at, t = v/a * so required d = .5 a (v/a)^2 * * equate the two expressions for d and solve for v */ double tblend_vel; double dot; double theta; PmCartesian v1, v2; v1 = tcGetEndingUnitVector(tc); v2 = tcGetStartingUnitVector(nexttc); pmCartCartDot(v1, v2, &dot); theta = acos(-dot)/2.0; if(cos(theta) > 0.001) { tblend_vel = 2.0 * pmSqrt(tc->maxaccel * tc->tolerance / cos(theta)); if(tblend_vel < tc->blend_vel) tc->blend_vel = tblend_vel; } } } primary_before = tcGetPos(tc); tcRunCycle(tc, &primary_vel, &on_final_decel); primary_after = tcGetPos(tc); pmCartCartSub(primary_after.tran, primary_before.tran, &primary_displacement.tran); primary_displacement.a = primary_after.a - primary_before.a; primary_displacement.b = primary_after.b - primary_before.b; primary_displacement.c = primary_after.c - primary_before.c; primary_displacement.u = primary_after.u - primary_before.u; primary_displacement.v = primary_after.v - primary_before.v; primary_displacement.w = primary_after.w - primary_before.w; // blend criteria if((tc->blending && nexttc) || (nexttc && on_final_decel && primary_vel < tc->blend_vel)) { // make sure we continue to blend this segment even when its // accel reaches 0 (at the very end) tc->blending = 1; // hack to show blends in axis // tp->motionType = 0; if(tc->currentvel > nexttc->currentvel) { tp->motionType = tc->canon_motion_type; emcmotStatus->distance_to_go = tc->target - tc->progress; emcmotStatus->enables_queued = tc->enables; // report our line number to the guis tp->execId = tc->id; } else { tp->motionType = nexttc->canon_motion_type; emcmotStatus->distance_to_go = nexttc->target - nexttc->progress; emcmotStatus->enables_queued = nexttc->enables; // report our line number to the guis tp->execId = nexttc->id; } emcmotStatus->current_vel = tc->currentvel + nexttc->currentvel; secondary_before = tcGetPos(nexttc); save_vel = nexttc->reqvel; nexttc->reqvel = nexttc->feed_override > 0.0 ? ((tc->vel_at_blend_start - primary_vel) / nexttc->feed_override) : 0.0; tcRunCycle(nexttc, NULL, NULL); nexttc->reqvel = save_vel; secondary_after = tcGetPos(nexttc); pmCartCartSub(secondary_after.tran, secondary_before.tran, &secondary_displacement.tran); secondary_displacement.a = secondary_after.a - secondary_before.a; secondary_displacement.b = secondary_after.b - secondary_before.b; secondary_displacement.c = secondary_after.c - secondary_before.c; secondary_displacement.u = secondary_after.u - secondary_before.u; secondary_displacement.v = secondary_after.v - secondary_before.v; secondary_displacement.w = secondary_after.w - secondary_before.w; pmCartCartAdd(tp->currentPos.tran, primary_displacement.tran, &tp->currentPos.tran); pmCartCartAdd(tp->currentPos.tran, secondary_displacement.tran, &tp->currentPos.tran); tp->currentPos.a += primary_displacement.a + secondary_displacement.a; tp->currentPos.b += primary_displacement.b + secondary_displacement.b; tp->currentPos.c += primary_displacement.c + secondary_displacement.c; tp->currentPos.u += primary_displacement.u + secondary_displacement.u; tp->currentPos.v += primary_displacement.v + secondary_displacement.v; tp->currentPos.w += primary_displacement.w + secondary_displacement.w; } else { tp->motionType = tc->canon_motion_type; emcmotStatus->distance_to_go = tc->target - tc->progress; tp->currentPos = primary_after; emcmotStatus->current_vel = tc->currentvel; emcmotStatus->enables_queued = tc->enables; // report our line number to the guis tp->execId = tc->id; } return 0; } int tpSetSpindleSync(TP_STRUCT * tp, double sync, int mode) { if(sync) { tp->synchronized = 1; tp->uu_per_rev = sync; tp->velocity_mode = mode; } else tp->synchronized = 0; return 0; } int tpPause(TP_STRUCT * tp) { if (0 == tp) { return -1; } tp->pausing = 1; return 0; } int tpResume(TP_STRUCT * tp) { if (0 == tp) { return -1; } tp->pausing = 0; return 0; } int tpAbort(TP_STRUCT * tp) { if (0 == tp) { return -1; } if (!tp->aborting) { /* to abort, signal a pause and set our abort flag */ tpPause(tp); tp->aborting = 1; } return 0; } int tpGetMotionType(TP_STRUCT * tp) { return tp->motionType; } EmcPose tpGetPos(TP_STRUCT * tp) { EmcPose retval; if (0 == tp) { retval.tran.x = retval.tran.y = retval.tran.z = 0.0; retval.a = retval.b = retval.c = 0.0; retval.u = retval.v = retval.w = 0.0; return retval; } return tp->currentPos; } int tpIsDone(TP_STRUCT * tp) { if (0 == tp) { return 0; } return tp->done; } int tpQueueDepth(TP_STRUCT * tp) { if (0 == tp) { return 0; } return tp->depth; } int tpActiveDepth(TP_STRUCT * tp) { if (0 == tp) { return 0; } return tp->activeDepth; } int tpSetAout(TP_STRUCT *tp, unsigned char index, double start, double end) { return 0; } int tpSetDout(TP_STRUCT *tp, int index, unsigned char start, unsigned char end) { return 0; } // vim:sw=4:sts=4:et: