#include "rtapi.h"
#include "posemath.h"
#include "tc.h"
#include "tp.h"
#include "emcpose.h"
#include "rtapi_math.h"
#include "mot_priv.h"
#include "motion_debug.h"
#include "motion_types.h"
#include "spherical_arc.h"
#include "blendmath.h"
#define EMC_TRAJ_TERM_COND_STOP 0
#define EMC_TRAJ_TERM_COND_EXACT 1
#define EMC_TRAJ_TERM_COND_BLEND 2
#include "tp_debug.h"
#undef TP_SHOW_BLENDS
#define TP_OPTIMIZATION_LAZY
extern emcmot_status_t *emcmotStatus;
extern emcmot_debug_t *emcmotDebug;
extern emcmot_config_t *emcmotConfig;
STATIC int tpComputeBlendVelocity(
TC_STRUCT const *tc,
TC_STRUCT const *nexttc,
double v_target_this,
double v_target_next,
double *v_blend_this,
double *v_blend_next,
double *v_blend_net);
STATIC double estimateParabolicBlendPerformance(
TP_STRUCT const *tp,
TC_STRUCT const *tc,
TC_STRUCT const *nexttc);
STATIC int tpCheckEndCondition(TP_STRUCT const * const tp, TC_STRUCT * const tc, TC_STRUCT const * const nexttc);
STATIC int tpUpdateCycle(TP_STRUCT * const tp,
TC_STRUCT * const tc, TC_STRUCT const * const nexttc);
STATIC int tpRunOptimization(TP_STRUCT * const tp);
STATIC inline int tpAddSegmentToQueue(TP_STRUCT * const tp, TC_STRUCT * const tc, int inc_id);
STATIC inline double tpGetMaxTargetVel(TP_STRUCT const * const tp, TC_STRUCT const * const tc);
STATIC int tcRotaryMotionCheck(TC_STRUCT const * const tc) {
switch (tc->motion_type) {
case TC_RIGIDTAP:
return false;
case TC_LINEAR:
if (tc->coords.line.abc.tmag_zero && tc->coords.line.uvw.tmag_zero) {
return false;
} else {
return true;
}
case TC_CIRCULAR:
if (tc->coords.circle.abc.tmag_zero && tc->coords.circle.uvw.tmag_zero) {
return false;
} else {
return true;
}
case TC_SPHERICAL:
return true;
default:
tp_debug_print("Unknown motion type!\n");
return false;
}
}
STATIC double tpGetTangentKinkRatio(void) {
const double max_ratio = 0.7071;
const double min_ratio = 0.001;
return fmax(fmin(emcmotConfig->arcBlendTangentKinkRatio,max_ratio),min_ratio);
}
STATIC int tpGetMachineAccelBounds(PmCartesian * const acc_bound) {
if (!acc_bound) {
return TP_ERR_FAIL;
}
acc_bound->x = emcmotDebug->axes[0].acc_limit; acc_bound->y = emcmotDebug->axes[1].acc_limit; acc_bound->z = emcmotDebug->axes[2].acc_limit; return TP_ERR_OK;
}
STATIC int tpGetMachineVelBounds(PmCartesian * const vel_bound) {
if (!vel_bound) {
return TP_ERR_FAIL;
}
vel_bound->x = emcmotDebug->axes[0].vel_limit; vel_bound->y = emcmotDebug->axes[1].vel_limit; vel_bound->z = emcmotDebug->axes[2].vel_limit; return TP_ERR_OK;
}
STATIC int tpGetMachineActiveLimit(double * const act_limit, PmCartesian const * const bounds) {
if (!act_limit) {
return TP_ERR_FAIL;
}
*act_limit = fmax(fmax(bounds->x,bounds->y),bounds->z);
if (bounds->x > 0) {
*act_limit = fmin(*act_limit, bounds->x);
}
if (bounds->y > 0) {
*act_limit = fmin(*act_limit, bounds->y);
}
if (bounds->z > 0) {
*act_limit = fmin(*act_limit, bounds->z);
}
tp_debug_print(" arc blending a_max=%f\n", *act_limit);
return TP_ERR_OK;
}
STATIC double tpGetFeedScale(TP_STRUCT const * const tp,
TC_STRUCT const * const tc) {
if (!tc) {
return 0.0;
}
bool pausing = tp->pausing && (tc->synchronized == TC_SYNC_NONE || tc->synchronized == TC_SYNC_VELOCITY);
bool aborting = tp->aborting;
if (pausing) {
tc_debug_print("pausing\n");
return 0.0;
} else if (aborting) {
tc_debug_print("aborting\n");
return 0.0;
} else if (tc->synchronized == TC_SYNC_POSITION ) {
return 1.0;
} else if (tc->is_blending) {
return fmin(emcmotStatus->net_feed_scale, 1.0);
} else {
return emcmotStatus->net_feed_scale;
}
}
STATIC inline double tpGetRealTargetVel(TP_STRUCT const * const tp,
TC_STRUCT const * const tc) {
if (!tc) {
return 0.0;
}
double v_target = tc->synchronized ? tc->target_vel : tc->reqvel;
return fmin(v_target * tpGetFeedScale(tp,tc), tpGetMaxTargetVel(tp, tc));
}
STATIC inline double getMaxFeedScale(TC_STRUCT const * tc)
{
if (tc && tc->synchronized == TC_SYNC_POSITION ) {
return 1.0;
} else {
return emcmotConfig->maxFeedScale;
}
}
STATIC inline double tpGetMaxTargetVel(TP_STRUCT const * const tp, TC_STRUCT const * const tc)
{
double max_scale = emcmotConfig->maxFeedScale;
if (tc->is_blending) {
max_scale = fmin(max_scale, 1.0);
}
double v_max_target = tc->target_vel * max_scale;
if (!tcPureRotaryCheck(tc) && (tc->synchronized != TC_SYNC_POSITION)){
v_max_target = fmin(v_max_target,tp->vLimit);
}
return fmin(v_max_target, tc->maxvel);
}
STATIC inline double tpGetRealFinalVel(TP_STRUCT const * const tp,
TC_STRUCT const * const tc, TC_STRUCT const * const nexttc) {
if (emcmotDebug->stepping || tc->term_cond != TC_TERM_COND_TANGENT || tp->reverse_run) {
return 0.0;
}
double v_target_this = tpGetRealTargetVel(tp, tc);
double v_target_next = 0.0;
if (nexttc) {
v_target_next = tpGetRealTargetVel(tp, nexttc);
}
tc_debug_print("v_target_next = %f\n",v_target_next);
double v_target = fmin(v_target_this, v_target_next);
return fmin(tc->finalvel, v_target);
}
STATIC inline double tpGetSignedSpindlePosition(spindle_status_t *status) {
int spindle_dir;
double spindle_pos;
spindle_dir = status->direction;
spindle_pos = status->spindleRevs;
if (spindle_dir < 0.0) {
spindle_pos*=-1.0;
}
return spindle_pos;
}
int tpCreate(TP_STRUCT * const tp, int _queueSize, TC_STRUCT * const tcSpace)
{
if (0 == tp) {
return TP_ERR_FAIL;
}
if (_queueSize <= 0) {
tp->queueSize = TP_DEFAULT_QUEUE_SIZE;
} else {
tp->queueSize = _queueSize;
}
if (-1 == tcqCreate(&tp->queue, tp->queueSize, tcSpace)) {
return TP_ERR_FAIL;
}
return tpInit(tp);
}
int tpClearDIOs(TP_STRUCT * const tp) {
int i;
tp->syncdio.anychanged = 0;
tp->syncdio.dio_mask = 0;
tp->syncdio.aio_mask = 0;
for (i = 0; i < emcmotConfig->numDIO; i++) {
tp->syncdio.dios[i] = 0;
}
for (i = 0; i < emcmotConfig->numAIO; i++) {
tp->syncdio.aios[i] = 0;
}
return TP_ERR_OK;
}
int tpClear(TP_STRUCT * const tp)
{
tcqInit(&tp->queue);
tp->queueSize = 0;
tp->goalPos = tp->currentPos;
tp->nextId = 0;
tp->execId = 0;
tp->motionType = 0;
tp->termCond = TC_TERM_COND_PARABOLIC;
tp->tolerance = 0.0;
tp->done = 1;
tp->depth = tp->activeDepth = 0;
tp->aborting = 0;
tp->pausing = 0;
tp->reverse_run = 0;
tp->synchronized = 0;
tp->uu_per_rev = 0.0;
emcmotStatus->current_vel = 0.0;
emcmotStatus->requested_vel = 0.0;
emcmotStatus->distance_to_go = 0.0;
ZERO_EMC_POSE(emcmotStatus->dtg);
SET_MOTION_INPOS_FLAG(1);
return tpClearDIOs(tp);
}
int tpInit(TP_STRUCT * const tp)
{
tp->cycleTime = 0.0;
tp->vLimit = 0.0;
tp->ini_maxvel = 0.0;
tp->aLimit = 0.0;
PmCartesian acc_bound;
tpGetMachineAccelBounds(&acc_bound);
tpGetMachineActiveLimit(&tp->aMax, &acc_bound);
tp->wMax = 0.0;
tp->wDotMax = 0.0;
tp->spindle.offset = 0.0;
tp->spindle.revs = 0.0;
tp->spindle.waiting_for_index = MOTION_INVALID_ID;
tp->spindle.waiting_for_atspeed = MOTION_INVALID_ID;
tp->reverse_run = TC_DIR_FORWARD;
ZERO_EMC_POSE(tp->currentPos);
PmCartesian vel_bound;
tpGetMachineVelBounds(&vel_bound);
tpGetMachineActiveLimit(&tp->vMax, &vel_bound);
return tpClear(tp);
}
int tpSetCycleTime(TP_STRUCT * const tp, double secs)
{
if (0 == tp || secs <= 0.0) {
return TP_ERR_FAIL;
}
tp->cycleTime = secs;
return TP_ERR_OK;
}
int tpSetVmax(TP_STRUCT * const tp, double vMax, double ini_maxvel)
{
if (0 == tp || vMax <= 0.0 || ini_maxvel <= 0.0) {
return TP_ERR_FAIL;
}
tp->vMax = vMax;
tp->ini_maxvel = ini_maxvel;
return TP_ERR_OK;
}
int tpSetVlimit(TP_STRUCT * const tp, double vLimit)
{
if (!tp) return TP_ERR_FAIL;
if (vLimit < 0.)
tp->vLimit = 0.;
else
tp->vLimit = vLimit;
return TP_ERR_OK;
}
int tpSetAmax(TP_STRUCT * const tp, double aMax)
{
if (0 == tp || aMax <= 0.0) {
return TP_ERR_FAIL;
}
tp->aMax = aMax;
return TP_ERR_OK;
}
int tpSetId(TP_STRUCT * const tp, int id)
{
if (!MOTION_ID_VALID(id)) {
rtapi_print_msg(RTAPI_MSG_ERR, "tpSetId: invalid motion id %d\n", id);
return TP_ERR_FAIL;
}
if (0 == tp) {
return TP_ERR_FAIL;
}
tp->nextId = id;
return TP_ERR_OK;
}
int tpGetExecId(TP_STRUCT * const tp)
{
if (0 == tp) {
return TP_ERR_FAIL;
}
return tp->execId;
}
int tpSetTermCond(TP_STRUCT * const tp, int cond, double tolerance)
{
if (!tp) {
return TP_ERR_FAIL;
}
switch (cond) {
case TC_TERM_COND_PARABOLIC:
case TC_TERM_COND_TANGENT:
case TC_TERM_COND_EXACT:
case TC_TERM_COND_STOP:
tp->termCond = cond;
tp->tolerance = tolerance;
break;
default:
return -1;
}
return TP_ERR_OK;
}
int tpSetPos(TP_STRUCT * const tp, EmcPose const * const pos)
{
if (0 == tp) {
return TP_ERR_FAIL;
}
int res_invalid = tpSetCurrentPos(tp, pos);
if (res_invalid) {
return TP_ERR_FAIL;
}
tp->goalPos = *pos;
return TP_ERR_OK;
}
int tpSetCurrentPos(TP_STRUCT * const tp, EmcPose const * const pos)
{
if (0 == tp) {
return TP_ERR_FAIL;
}
if (emcPoseValid(pos)) {
tp->currentPos = *pos;
return TP_ERR_OK;
} else {
rtapi_print_msg(RTAPI_MSG_ERR, "Tried to set invalid pose in tpSetCurrentPos on id %d!"
"pos is %.12g, %.12g, %.12g\n",
tp->execId,
pos->tran.x,
pos->tran.y,
pos->tran.z);
return TP_ERR_INVALID;
}
}
int tpAddCurrentPos(TP_STRUCT * const tp, EmcPose const * const disp)
{
if (!tp || !disp) {
return TP_ERR_MISSING_INPUT;
}
if (emcPoseValid(disp)) {
emcPoseSelfAdd(&tp->currentPos, disp);
return TP_ERR_OK;
} else {
rtapi_print_msg(RTAPI_MSG_ERR, "Tried to set invalid pose in tpAddCurrentPos on id %d!"
"disp is %.12g, %.12g, %.12g\n",
tp->execId,
disp->tran.x,
disp->tran.y,
disp->tran.z);
return TP_ERR_INVALID;
}
}
int tpErrorCheck(TP_STRUCT const * const tp) {
if (!tp) {
rtapi_print_msg(RTAPI_MSG_ERR, "TP is null\n");
return TP_ERR_FAIL;
}
if (tp->aborting) {
rtapi_print_msg(RTAPI_MSG_ERR, "TP is aborting\n");
return TP_ERR_FAIL;
}
return TP_ERR_OK;
}
STATIC double tpCalculateTriangleVel(TC_STRUCT const *tc) {
double acc_scaled = tcGetTangentialMaxAccel(tc);
double length = tc->target;
if (!tc->finalized) {
length /= 2.0;
}
return findVPeak(acc_scaled, length);
}
STATIC double tpCalculateOptimizationInitialVel(TP_STRUCT const * const tp, TC_STRUCT * const tc)
{
double acc_scaled = tcGetTangentialMaxAccel(tc);
double triangle_vel = findVPeak(acc_scaled, tc->target);
double max_vel = tpGetMaxTargetVel(tp, tc);
tp_debug_json_start(tpCalculateOptimizationInitialVel);
tp_debug_json_double(triangle_vel);
tp_debug_json_end();
return fmin(triangle_vel, max_vel);
}
STATIC int tpInitBlendArcFromPrev(TP_STRUCT const * const tp,
TC_STRUCT const * const prev_tc,
TC_STRUCT* const blend_tc,
double vel,
double ini_maxvel,
double acc) {
#ifdef TP_SHOW_BLENDS
int canon_motion_type = EMC_MOTION_TYPE_ARC;
#else
int canon_motion_type = prev_tc->canon_motion_type;
#endif
tcInit(blend_tc,
TC_SPHERICAL,
canon_motion_type,
tp->cycleTime,
prev_tc->enables,
false);
tcSetupState(blend_tc, tp);
tcSetupMotion(blend_tc,
vel,
ini_maxvel,
acc);
blend_tc->syncdio = prev_tc->syncdio;
double length;
arcLength(&blend_tc->coords.arc.xyz, &length);
tp_info_print("blend tc length = %f\n",length);
blend_tc->target = length;
blend_tc->nominal_length = length;
tcSetTermCond(blend_tc, NULL, TC_TERM_COND_TANGENT);
tcFinalizeLength(blend_tc);
return TP_ERR_OK;
}
STATIC int tcSetLineXYZ(TC_STRUCT * const tc, PmCartLine const * const line)
{
if (!line || tc->motion_type != TC_LINEAR) {
return TP_ERR_FAIL;
}
if (!tc->coords.line.abc.tmag_zero || !tc->coords.line.uvw.tmag_zero) {
rtapi_print_msg(RTAPI_MSG_ERR, "SetLineXYZ does not supportABC or UVW motion\n");
return TP_ERR_FAIL;
}
tc->coords.line.xyz = *line;
tc->target = line->tmag;
return TP_ERR_OK;
}
static inline int find_max_element(double arr[], int sz)
{
if (sz < 1) {
return -1;
}
int max_idx = 0;
int idx;
for (idx = 0; idx < sz; ++idx) {
if (arr[idx] > arr[max_idx]) {
max_idx = idx;
}
}
return max_idx;
}
STATIC tc_blend_type_t tpChooseBestBlend(TP_STRUCT const * const tp,
TC_STRUCT * const prev_tc,
TC_STRUCT * const tc,
TC_STRUCT * const blend_tc)
{
if (!tc || !prev_tc) {
return NO_BLEND;
}
switch (prev_tc->term_cond)
{
case TC_TERM_COND_EXACT:
case TC_TERM_COND_STOP:
return NO_BLEND;
}
double perf_parabolic = estimateParabolicBlendPerformance(tp, prev_tc, tc) / 2.0;
double perf_tangent = prev_tc->kink_vel;
double perf_arc_blend = blend_tc ? blend_tc->maxvel : 0.0;
tp_debug_print("Blend performance: parabolic %f, tangent %f, arc_blend %f, ",
perf_parabolic,
perf_tangent,
perf_arc_blend);
double perf[3] = {perf_parabolic, perf_tangent, perf_arc_blend};
tc_blend_type_t best_blend = find_max_element(perf, 3);
switch (best_blend) {
case PARABOLIC_BLEND: tp_debug_print("using parabolic blend\n");
tcRemoveKinkProperties(prev_tc, tc);
tcSetTermCond(prev_tc, tc, TC_TERM_COND_PARABOLIC);
break;
case TANGENT_SEGMENTS_BLEND: tp_debug_print("using approximate tangent blend\n");
tcSetTermCond(prev_tc, tc, TC_TERM_COND_TANGENT);
break;
case ARC_BLEND: tp_debug_print("using blend arc\n");
tcRemoveKinkProperties(prev_tc, tc);
break;
case NO_BLEND:
break;
}
return best_blend;
}
STATIC tp_err_t tpCreateLineArcBlend(TP_STRUCT * const tp, TC_STRUCT * const prev_tc, TC_STRUCT * const tc, TC_STRUCT * const blend_tc)
{
tp_debug_print("-- Starting LineArc blend arc --\n");
PmCartesian acc_bound, vel_bound;
tpGetMachineAccelBounds(&acc_bound);
tpGetMachineVelBounds(&vel_bound);
BlendGeom3 geom;
BlendParameters param;
BlendPoints3 points_approx;
BlendPoints3 points_exact;
int res_init = blendInit3FromLineArc(&geom, ¶m,
prev_tc,
tc,
&acc_bound,
&vel_bound,
emcmotConfig->maxFeedScale);
if (res_init != TP_ERR_OK) {
tp_debug_print("blend init failed with code %d, aborting blend arc\n",
res_init);
return res_init;
}
int coplanar = pmUnitCartsColinear(&geom.binormal,
&tc->coords.circle.xyz.normal);
if (!coplanar) {
tp_debug_print("aborting arc, not coplanar\n");
return TP_ERR_FAIL;
}
int res_param = blendComputeParameters(¶m);
int res_points = blendFindPoints3(&points_approx, &geom, ¶m);
int res_post = blendLineArcPostProcess(&points_exact,
&points_approx,
¶m,
&geom, &prev_tc->coords.line.xyz,
&tc->coords.circle.xyz);
if (res_init || res_param || res_points || res_post) {
tp_debug_print("Got %d, %d, %d, %d for init, param, points, post, aborting arc\n",
res_init,
res_param,
res_points,
res_post);
return TP_ERR_FAIL;
}
if (points_exact.trim2 > param.phi2_max) {
tp_debug_print("trim2 %f > phi2_max %f, aborting arc...\n",
points_exact.trim2,
param.phi2_max);
return TP_ERR_FAIL;
}
blendCheckConsume(¶m, &points_exact, prev_tc, emcmotConfig->arcBlendGapCycles);
PmCartLine line1_temp = prev_tc->coords.line.xyz;
PmCircle circ2_temp = tc->coords.circle.xyz;
double new_len1 = line1_temp.tmag - points_exact.trim1;
int res_stretch1 = pmCartLineStretch(&line1_temp,
new_len1,
false);
double phi2_new = tc->coords.circle.xyz.angle - points_exact.trim2;
tp_debug_print("phi2_new = %f\n",phi2_new);
int res_stretch2 = pmCircleStretch(&circ2_temp,
phi2_new,
true);
if (res_stretch1 || res_stretch2) {
tp_debug_print("segment resize failed, aborting arc\n");
return TP_ERR_FAIL;
}
pmCartLinePoint(&line1_temp,
line1_temp.tmag,
&points_exact.arc_start);
pmCirclePoint(&circ2_temp,
0.0,
&points_exact.arc_end);
blendPoints3Print(&points_exact);
int res_arc = arcFromBlendPoints3(&blend_tc->coords.arc.xyz,
&points_exact,
&geom,
¶m);
if (res_arc < 0) {
tp_debug_print("arc creation failed, aborting arc\n");
return TP_ERR_FAIL;
}
blend_tc->coords.arc.abc = prev_tc->coords.line.abc.end;
blend_tc->coords.arc.uvw = prev_tc->coords.line.uvw.end;
tpInitBlendArcFromPrev(tp, prev_tc, blend_tc, param.v_req,
param.v_plan, param.a_max);
int res_tangent = checkTangentAngle(&circ2_temp,
&blend_tc->coords.arc.xyz,
&geom,
¶m,
tp->cycleTime,
true);
if (res_tangent < 0) {
tp_debug_print("failed tangent check, aborting arc...\n");
return TP_ERR_FAIL;
}
if (tpChooseBestBlend(tp, prev_tc, tc, blend_tc) != ARC_BLEND) {
return TP_ERR_NO_ACTION;
}
tp_debug_print("Passed all tests, updating segments\n");
if (param.consume) {
int res_pop = tcqPopBack(&tp->queue);
if (res_pop) {
tp_debug_print("failed to pop segment, aborting arc\n");
return TP_ERR_FAIL;
}
} else {
tcSetLineXYZ(prev_tc, &line1_temp);
blend_tc->atspeed=0;
}
tcSetCircleXYZ(tc, &circ2_temp);
tcSetTermCond(prev_tc, tc, TC_TERM_COND_TANGENT);
return TP_ERR_OK;
}
STATIC tp_err_t tpCreateArcLineBlend(TP_STRUCT * const tp, TC_STRUCT * const prev_tc, TC_STRUCT * const tc, TC_STRUCT * const blend_tc)
{
tp_debug_print("-- Starting ArcLine blend arc --\n");
PmCartesian acc_bound, vel_bound;
tpGetMachineAccelBounds(&acc_bound);
tpGetMachineVelBounds(&vel_bound);
BlendGeom3 geom;
BlendParameters param;
BlendPoints3 points_approx;
BlendPoints3 points_exact;
param.consume = 0;
int res_init = blendInit3FromArcLine(&geom, ¶m,
prev_tc,
tc,
&acc_bound,
&vel_bound,
emcmotConfig->maxFeedScale);
if (res_init != TP_ERR_OK) {
tp_debug_print("blend init failed with code %d, aborting blend arc\n",
res_init);
return res_init;
}
int coplanar = pmUnitCartsColinear(&geom.binormal,
&prev_tc->coords.circle.xyz.normal);
if (!coplanar) {
tp_debug_print("aborting arc, not coplanar\n");
return TP_ERR_FAIL;
}
int res_param = blendComputeParameters(¶m);
int res_points = blendFindPoints3(&points_approx, &geom, ¶m);
int res_post = blendArcLinePostProcess(&points_exact,
&points_approx,
¶m,
&geom, &prev_tc->coords.circle.xyz,
&tc->coords.line.xyz);
if (res_init || res_param || res_points || res_post) {
tp_debug_print("Got %d, %d, %d, %d for init, param, points, post\n",
res_init,
res_param,
res_points,
res_post);
return TP_ERR_FAIL;
}
blendCheckConsume(¶m, &points_exact, prev_tc, emcmotConfig->arcBlendGapCycles);
PmCircle circ1_temp = prev_tc->coords.circle.xyz;
PmCartLine line2_temp = tc->coords.line.xyz;
double phi1_new = circ1_temp.angle - points_exact.trim1;
if (points_exact.trim1 > param.phi1_max) {
tp_debug_print("trim1 %f > phi1_max %f, aborting arc...\n",
points_exact.trim1,
param.phi1_max);
return TP_ERR_FAIL;
}
int res_stretch1 = pmCircleStretch(&circ1_temp,
phi1_new,
false);
if (res_stretch1 != TP_ERR_OK) {
return TP_ERR_FAIL;
}
double new_len2 = tc->target - points_exact.trim2;
int res_stretch2 = pmCartLineStretch(&line2_temp,
new_len2,
true);
if (res_stretch1 || res_stretch2) {
tp_debug_print("segment resize failed, aborting arc\n");
return TP_ERR_FAIL;
}
pmCirclePoint(&circ1_temp,
circ1_temp.angle,
&points_exact.arc_start);
pmCartLinePoint(&line2_temp,
0.0,
&points_exact.arc_end);
blendPoints3Print(&points_exact);
int res_arc = arcFromBlendPoints3(&blend_tc->coords.arc.xyz, &points_exact, &geom, ¶m);
if (res_arc < 0) {
return TP_ERR_FAIL;
}
blend_tc->coords.arc.abc = tc->coords.line.abc.start;
blend_tc->coords.arc.uvw = tc->coords.line.uvw.start;
tpInitBlendArcFromPrev(tp, prev_tc, blend_tc, param.v_req,
param.v_plan, param.a_max);
int res_tangent = checkTangentAngle(&circ1_temp, &blend_tc->coords.arc.xyz, &geom, ¶m, tp->cycleTime, false);
if (res_tangent) {
tp_debug_print("failed tangent check, aborting arc...\n");
return TP_ERR_FAIL;
}
if (tpChooseBestBlend(tp, prev_tc, tc, blend_tc) != ARC_BLEND) {
return TP_ERR_NO_ACTION;
}
tp_debug_print("Passed all tests, updating segments\n");
tcSetCircleXYZ(prev_tc, &circ1_temp);
tcSetLineXYZ(tc, &line2_temp);
tc->blend_prev = 0;
blend_tc->atspeed=0;
tcSetTermCond(prev_tc, tc, TC_TERM_COND_TANGENT);
return TP_ERR_OK;
}
STATIC tp_err_t tpCreateArcArcBlend(TP_STRUCT * const tp, TC_STRUCT * const prev_tc, TC_STRUCT * const tc, TC_STRUCT * const blend_tc)
{
tp_debug_print("-- Starting ArcArc blend arc --\n");
int colinear = pmUnitCartsColinear(&prev_tc->coords.circle.xyz.normal,
&tc->coords.circle.xyz.normal);
if (!colinear) {
tp_debug_print("arc abort: not coplanar\n");
return TP_ERR_FAIL;
}
PmCartesian acc_bound, vel_bound;
tpGetMachineAccelBounds(&acc_bound);
tpGetMachineVelBounds(&vel_bound);
BlendGeom3 geom;
BlendParameters param;
BlendPoints3 points_approx;
BlendPoints3 points_exact;
int res_init = blendInit3FromArcArc(&geom, ¶m,
prev_tc,
tc,
&acc_bound,
&vel_bound,
emcmotConfig->maxFeedScale);
if (res_init != TP_ERR_OK) {
tp_debug_print("blend init failed with code %d, aborting blend arc\n",
res_init);
return res_init;
}
int coplanar1 = pmUnitCartsColinear(&geom.binormal,
&prev_tc->coords.circle.xyz.normal);
if (!coplanar1) {
tp_debug_print("aborting blend arc, arc id %d is not coplanar with binormal\n", prev_tc->id);
return TP_ERR_FAIL;
}
int coplanar2 = pmUnitCartsColinear(&geom.binormal,
&tc->coords.circle.xyz.normal);
if (!coplanar2) {
tp_debug_print("aborting blend arc, arc id %d is not coplanar with binormal\n", tc->id);
return TP_ERR_FAIL;
}
int res_param = blendComputeParameters(¶m);
int res_points = blendFindPoints3(&points_approx, &geom, ¶m);
int res_post = blendArcArcPostProcess(&points_exact,
&points_approx,
¶m,
&geom, &prev_tc->coords.circle.xyz,
&tc->coords.circle.xyz);
if (res_init || res_param || res_points || res_post) {
tp_debug_print("Got %d, %d, %d, %d for init, param, points, post\n",
res_init,
res_param,
res_points,
res_post);
return TP_ERR_FAIL;
}
blendCheckConsume(¶m, &points_exact, prev_tc, emcmotConfig->arcBlendGapCycles);
double phi1_new = prev_tc->coords.circle.xyz.angle - points_exact.trim1;
double phi2_new = tc->coords.circle.xyz.angle - points_exact.trim2;
tp_debug_print("phi1_new = %f, trim1 = %f\n", phi1_new, points_exact.trim1);
tp_debug_print("phi2_new = %f, trim2 = %f\n", phi2_new, points_exact.trim2);
if (points_exact.trim1 > param.phi1_max) {
tp_debug_print("trim1 %f > phi1_max %f, aborting arc...\n",
points_exact.trim1,
param.phi1_max);
return TP_ERR_FAIL;
}
if (points_exact.trim2 > param.phi2_max) {
tp_debug_print("trim2 %f > phi2_max %f, aborting arc...\n",
points_exact.trim2,
param.phi2_max);
return TP_ERR_FAIL;
}
PmCircle circ1_temp = prev_tc->coords.circle.xyz;
PmCircle circ2_temp = tc->coords.circle.xyz;
int res_stretch1 = pmCircleStretch(&circ1_temp,
phi1_new,
false);
if (res_stretch1 != TP_ERR_OK) {
return TP_ERR_FAIL;
}
int res_stretch2 = pmCircleStretch(&circ2_temp,
phi2_new,
true);
if (res_stretch1 || res_stretch2) {
tp_debug_print("segment resize failed, aborting arc\n");
return TP_ERR_FAIL;
}
pmCirclePoint(&circ1_temp,
circ1_temp.angle,
&points_exact.arc_start);
pmCirclePoint(&circ2_temp,
0.0,
&points_exact.arc_end);
tp_debug_print("Modified arc points\n");
blendPoints3Print(&points_exact);
int res_arc = arcFromBlendPoints3(&blend_tc->coords.arc.xyz, &points_exact, &geom, ¶m);
if (res_arc < 0) {
return TP_ERR_FAIL;
}
blend_tc->coords.arc.abc = prev_tc->coords.circle.abc.end;
blend_tc->coords.arc.uvw = prev_tc->coords.circle.uvw.end;
tpInitBlendArcFromPrev(tp, prev_tc, blend_tc, param.v_req,
param.v_plan, param.a_max);
int res_tangent1 = checkTangentAngle(&circ1_temp, &blend_tc->coords.arc.xyz, &geom, ¶m, tp->cycleTime, false);
int res_tangent2 = checkTangentAngle(&circ2_temp, &blend_tc->coords.arc.xyz, &geom, ¶m, tp->cycleTime, true);
if (res_tangent1 || res_tangent2) {
tp_debug_print("failed tangent check, aborting arc...\n");
return TP_ERR_FAIL;
}
if (tpChooseBestBlend(tp, prev_tc, tc, blend_tc) != ARC_BLEND) {
return TP_ERR_NO_ACTION;
}
tp_debug_print("Passed all tests, updating segments\n");
tcSetCircleXYZ(prev_tc, &circ1_temp);
tcSetCircleXYZ(tc, &circ2_temp);
tc->blend_prev = 0;
blend_tc->atspeed=0;
tcSetTermCond(prev_tc, tc, TC_TERM_COND_TANGENT);
return TP_ERR_OK;
}
STATIC tp_err_t tpCreateLineLineBlend(TP_STRUCT * const tp, TC_STRUCT * const prev_tc,
TC_STRUCT * const tc, TC_STRUCT * const blend_tc)
{
tp_debug_print("-- Starting LineLine blend arc --\n");
PmCartesian acc_bound, vel_bound;
tpGetMachineAccelBounds(&acc_bound);
tpGetMachineVelBounds(&vel_bound);
BlendGeom3 geom;
BlendParameters param;
BlendPoints3 points;
int res_init = blendInit3FromLineLine(&geom, ¶m,
prev_tc,
tc,
&acc_bound,
&vel_bound,
emcmotConfig->maxFeedScale);
if (res_init != TP_ERR_OK) {
tp_debug_print("blend init failed with code %d, aborting blend arc\n",
res_init);
return res_init;
}
int res_blend = blendComputeParameters(¶m);
if (res_blend != TP_ERR_OK) {
return res_blend;
}
blendFindPoints3(&points, &geom, ¶m);
blendCheckConsume(¶m, &points, prev_tc, emcmotConfig->arcBlendGapCycles);
int res_arc = arcFromBlendPoints3(&blend_tc->coords.arc.xyz, &points, &geom, ¶m);
if (res_arc < 0) {
return TP_ERR_FAIL;
}
blend_tc->coords.arc.abc = prev_tc->coords.line.abc.end;
blend_tc->coords.arc.uvw = prev_tc->coords.line.uvw.end;
tpInitBlendArcFromPrev(tp, prev_tc, blend_tc, param.v_req,
param.v_plan, param.a_max);
tp_debug_print("blend_tc target_vel = %g\n", blend_tc->target_vel);
if (tpChooseBestBlend(tp, prev_tc, tc, blend_tc) != ARC_BLEND) {
return TP_ERR_NO_ACTION;
}
int retval = TP_ERR_FAIL;
if (param.consume) {
retval = tcqPopBack(&tp->queue);
if (retval) {
rtapi_print_msg(RTAPI_MSG_ERR, "PopBack failed\n");
return TP_ERR_FAIL;
}
retval = tcConnectBlendArc(NULL, tc, &points.arc_start, &points.arc_end);
} else {
tp_debug_print("keeping previous line\n");
retval = tcConnectBlendArc(prev_tc, tc, &points.arc_start, &points.arc_end);
blend_tc->atspeed=0;
}
return retval;
}
STATIC inline int tpAddSegmentToQueue(TP_STRUCT * const tp, TC_STRUCT * const tc, int inc_id) {
tc->id = tp->nextId;
if (tcqPut(&tp->queue, tc) == -1) {
rtapi_print_msg(RTAPI_MSG_ERR, "tcqPut failed.\n");
return TP_ERR_FAIL;
}
if (inc_id) {
tp->nextId++;
}
if (tc->motion_type != TC_RIGIDTAP) {
tcGetEndpoint(tc, &tp->goalPos);
}
tp->done = 0;
tp->depth = tcqLen(&tp->queue);
tp_debug_print("Adding TC id %d of type %d, total length %0.08f\n",tc->id,tc->motion_type,tc->target);
return TP_ERR_OK;
}
STATIC int tpCheckCanonType(TC_STRUCT * prev_tc, TC_STRUCT * tc)
{
if (!tc || !prev_tc) {
return TP_ERR_FAIL;
}
if ((prev_tc->canon_motion_type == EMC_MOTION_TYPE_TRAVERSE) ^
(tc->canon_motion_type == EMC_MOTION_TYPE_TRAVERSE)) {
tp_debug_print("Can't blend between rapid and feed move, aborting arc\n");
tcSetTermCond(prev_tc, tc, TC_TERM_COND_STOP);
}
return TP_ERR_OK;
}
STATIC int tpSetupSyncedIO(TP_STRUCT * const tp, TC_STRUCT * const tc) {
if (tp->syncdio.anychanged != 0) {
tc->syncdio = tp->syncdio; tpClearDIOs(tp); return TP_ERR_OK;
} else {
tc->syncdio.anychanged = 0;
return TP_ERR_NO_ACTION;
}
}
int tpAddRigidTap(TP_STRUCT * const tp, EmcPose end, double vel, double ini_maxvel,
double acc, unsigned char enables, double scale) {
if (tpErrorCheck(tp)) {
return TP_ERR_FAIL;
}
tp_info_print("== AddRigidTap ==\n");
if(!tp->synchronized) {
rtapi_print_msg(RTAPI_MSG_ERR, "Cannot add unsynchronized rigid tap move.\n");
return TP_ERR_FAIL;
}
TC_STRUCT tc = {0};
tcInit(&tc,
TC_RIGIDTAP,
0,
tp->cycleTime,
enables,
1);
tpSetupSyncedIO(tp, &tc);
tcSetupState(&tc, tp);
tcSetupMotion(&tc,
vel,
ini_maxvel,
acc);
pmRigidTapInit(&tc.coords.rigidtap,
&tp->goalPos,
&end, scale);
tc.target = pmRigidTapTarget(&tc.coords.rigidtap, tp->uu_per_rev);
tcSetTermCond(&tc, NULL, TC_TERM_COND_STOP);
TC_STRUCT *prev_tc;
prev_tc = tcqLast(&tp->queue);
tcFinalizeLength(prev_tc);
tcFlagEarlyStop(prev_tc, &tc);
int retval = tpAddSegmentToQueue(tp, &tc, true);
tpRunOptimization(tp);
return retval;
}
STATIC blend_type_t tpCheckBlendArcType(
TC_STRUCT const * const prev_tc,
TC_STRUCT const * const tc) {
if (!prev_tc || !tc) {
tp_debug_print("prev_tc or tc doesn't exist\n");
return BLEND_NONE;
}
if (prev_tc->term_cond != TC_TERM_COND_PARABOLIC) {
tp_debug_print("Wrong term cond = %u\n", prev_tc->term_cond);
return BLEND_NONE;
}
if (tcRotaryMotionCheck(tc) || tcRotaryMotionCheck(prev_tc)) {
tp_debug_print("One of the segments has rotary motion, aborting blend arc\n");
return BLEND_NONE;
}
if (tc->finalized || prev_tc->finalized) {
tp_debug_print("Can't create blend when segment lengths are finalized\n");
return BLEND_NONE;
}
tp_debug_print("Motion types: prev_tc = %u, tc = %u\n",
prev_tc->motion_type,tc->motion_type);
if ((prev_tc->motion_type == TC_LINEAR) && (tc->motion_type == TC_LINEAR)) {
return BLEND_LINE_LINE;
} else if (prev_tc->motion_type == TC_LINEAR && tc->motion_type == TC_CIRCULAR) {
return BLEND_LINE_ARC;
} else if (prev_tc->motion_type == TC_CIRCULAR && tc->motion_type == TC_LINEAR) {
return BLEND_ARC_LINE;
} else if (prev_tc->motion_type == TC_CIRCULAR && tc->motion_type == TC_CIRCULAR) {
return BLEND_ARC_ARC;
} else {
return BLEND_NONE;
}
}
STATIC int tpComputeOptimalVelocity(TP_STRUCT const * const tp, TC_STRUCT * const tc, TC_STRUCT * const prev1_tc) {
double acc_this = tcGetTangentialMaxAccel(tc);
double vs_back = pmSqrt(pmSq(tc->finalvel) + 2.0 * acc_this * tc->target);
double vf_limit_this = tc->maxvel;
double vf_limit_prev = prev1_tc->maxvel;
if (prev1_tc->kink_vel >=0 && prev1_tc->term_cond == TC_TERM_COND_TANGENT) {
vf_limit_prev = fmin(vf_limit_prev, prev1_tc->kink_vel);
}
double vf_limit = fmin(vf_limit_this, vf_limit_prev);
if (vs_back >= vf_limit ) {
vs_back = vf_limit;
prev1_tc->optimization_state = TC_OPTIM_AT_MAX;
tp_debug_print("found peak due to v_limit %f\n", vf_limit);
}
prev1_tc->finalvel = vs_back;
double sample_maxvel = tc->target / (tp->cycleTime * TP_MIN_SEGMENT_CYCLES);
tc->maxvel = fmin(tc->maxvel, sample_maxvel);
tp_info_print(" prev1_tc-> fv = %f, tc->fv = %f\n",
prev1_tc->finalvel, tc->finalvel);
return TP_ERR_OK;
}
STATIC int tpRunOptimization(TP_STRUCT * const tp) {
TC_STRUCT *tc;
TC_STRUCT *prev1_tc;
int ind, x;
int len = tcqLen(&tp->queue);
int hit_peaks = 0;
bool hit_non_tangent = false;
for (x = 1; x < emcmotConfig->arcBlendOptDepth + 2; ++x) {
tp_info_print("==== Optimization step %d ====\n",x);
ind = len-x;
tc = tcqItem(&tp->queue, ind);
prev1_tc = tcqItem(&tp->queue, ind-1);
if ( !prev1_tc || !tc) {
tp_debug_print(" Reached end of queue in optimization\n");
return TP_ERR_OK;
}
if (prev1_tc->term_cond != TC_TERM_COND_TANGENT) {
if (hit_non_tangent) {
tp_debug_print("Found 2nd non-tangent segment, stopping optimization\n");
return TP_ERR_OK;
} else {
tp_debug_print("Found first non-tangent segment, contining\n");
hit_non_tangent = true;
continue;
}
}
double progress_ratio = prev1_tc->progress / prev1_tc->target;
double cutoff_ratio = BLEND_DIST_FRACTION / 2.0;
if (progress_ratio >= cutoff_ratio) {
tp_debug_print("segment %d has moved past %f percent progress, cannot blend safely!\n",
ind-1, cutoff_ratio * 100.0);
return TP_ERR_OK;
}
if (prev1_tc->splitting || prev1_tc->blending_next) {
tp_debug_print("segment %d is already blending, cannot optimize safely!\n",
ind-1);
return TP_ERR_OK;
}
tp_info_print(" current term = %u, type = %u, id = %u, accel_mode = %d\n",
tc->term_cond, tc->motion_type, tc->id, tc->accel_mode);
tp_info_print(" prev term = %u, type = %u, id = %u, accel_mode = %d\n",
prev1_tc->term_cond, prev1_tc->motion_type, prev1_tc->id, prev1_tc->accel_mode);
if (tc->atspeed) {
tp_debug_print("Found atspeed at id %d\n",tc->id);
tc->finalvel = 0.0;
}
if (!tc->finalized) {
tp_debug_print("Segment %d, type %d not finalized, continuing\n",tc->id,tc->motion_type);
prev1_tc->finalvel = fmin(prev1_tc->maxvel, tpCalculateOptimizationInitialVel(tp,tc));
if (prev1_tc->kink_vel >=0 && prev1_tc->term_cond == TC_TERM_COND_TANGENT) {
prev1_tc->finalvel = fmin(prev1_tc->finalvel, prev1_tc->kink_vel);
}
tc->finalvel = 0.0;
} else {
tpComputeOptimalVelocity(tp, tc, prev1_tc);
}
tc->active_depth = x - 2 - hit_peaks;
#ifdef TP_OPTIMIZATION_LAZY
if (tc->optimization_state == TC_OPTIM_AT_MAX) {
hit_peaks++;
}
if (hit_peaks > TP_OPTIMIZATION_CUTOFF) {
return TP_ERR_OK;
}
#endif
}
tp_debug_print("Reached optimization depth limit\n");
return TP_ERR_OK;
}
STATIC int tpSetupTangent(TP_STRUCT const * const tp,
TC_STRUCT * const prev_tc, TC_STRUCT * const tc) {
if (!tc || !prev_tc) {
tp_debug_print("missing tc or prev tc in tangent check\n");
return TP_ERR_FAIL;
}
if (tcRotaryMotionCheck(tc) || tcRotaryMotionCheck(prev_tc)) {
tp_debug_print("found rotary axis motion\n");
return TP_ERR_FAIL;
}
if (emcmotConfig->arcBlendOptDepth < 2) {
tp_debug_print("Optimization depth %d too low for tangent optimization\n",
emcmotConfig->arcBlendOptDepth);
return TP_ERR_FAIL;
}
if (prev_tc->term_cond == TC_TERM_COND_STOP) {
tp_debug_print("Found exact stop condition\n");
return TP_ERR_FAIL;
}
PmCartesian prev_tan, this_tan;
int res_endtan = tcGetEndTangentUnitVector(prev_tc, &prev_tan);
int res_starttan = tcGetStartTangentUnitVector(tc, &this_tan);
if (res_endtan || res_starttan) {
tp_debug_print("Got %d and %d from tangent vector calc\n",
res_endtan, res_starttan);
}
tp_debug_print("prev tangent vector: %f %f %f\n", prev_tan.x, prev_tan.y, prev_tan.z);
tp_debug_print("this tangent vector: %f %f %f\n", this_tan.x, this_tan.y, this_tan.z);
const double SHARP_CORNER_DEG = 2.0;
const double SHARP_CORNER_EPSILON = pmSq(PM_PI * ( SHARP_CORNER_DEG / 180.0));
if (pmCartCartAntiParallel(&prev_tan, &this_tan, SHARP_CORNER_EPSILON))
{
tp_debug_print("Found sharp corner\n");
tcSetTermCond(prev_tc, tc, TC_TERM_COND_STOP);
return TP_ERR_FAIL;
}
double v_max1 = tcGetMaxTargetVel(prev_tc, getMaxFeedScale(prev_tc));
double v_max2 = tcGetMaxTargetVel(tc, getMaxFeedScale(tc));
double v_max = fmin(v_max1, v_max2);
tp_debug_print("tangent v_max = %f\n",v_max);
double a_inst = v_max / tp->cycleTime + tc->maxaccel;
PmCartesian acc1, acc2, acc_diff;
pmCartScalMult(&prev_tan, a_inst, &acc1);
pmCartScalMult(&this_tan, a_inst, &acc2);
pmCartCartSub(&acc2,&acc1,&acc_diff);
PmCartesian acc_bound;
tpGetMachineAccelBounds(&acc_bound);
PmCartesian acc_scale;
findAccelScale(&acc_diff,&acc_bound,&acc_scale);
tp_debug_print("acc_diff: %f %f %f\n",
acc_diff.x,
acc_diff.y,
acc_diff.z);
tp_debug_print("acc_scale: %f %f %f\n",
acc_scale.x,
acc_scale.y,
acc_scale.z);
double acc_scale_max = pmCartAbsMax(&acc_scale);
if (prev_tc->motion_type == TC_CIRCULAR || tc->motion_type == TC_CIRCULAR) {
acc_scale_max /= BLEND_ACC_RATIO_TANGENTIAL;
}
const double kink_ratio = tpGetTangentKinkRatio();
if (acc_scale_max < kink_ratio) {
tp_debug_print(" Kink acceleration within %g, using tangent blend\n", kink_ratio);
tcSetTermCond(prev_tc, tc, TC_TERM_COND_TANGENT);
tcSetKinkProperties(prev_tc, tc, v_max, acc_scale_max);
return TP_ERR_OK;
} else {
tcSetKinkProperties(prev_tc, tc, v_max * kink_ratio / acc_scale_max, kink_ratio);
tp_debug_print("Kink acceleration scale %f above %f, kink vel = %f, blend arc may be faster\n",
acc_scale_max,
kink_ratio,
prev_tc->kink_vel);
return TP_ERR_NO_ACTION;
}
}
static bool tpCreateBlendIfPossible(
TP_STRUCT *tp,
TC_STRUCT *prev_tc,
TC_STRUCT *tc,
TC_STRUCT *blend_tc)
{
tp_err_t res_create = TP_ERR_FAIL;
blend_type_t blend_requested = tpCheckBlendArcType(prev_tc, tc);
switch (blend_requested) {
case BLEND_LINE_LINE:
res_create = tpCreateLineLineBlend(tp, prev_tc, tc, blend_tc);
break;
case BLEND_LINE_ARC:
res_create = tpCreateLineArcBlend(tp, prev_tc, tc, blend_tc);
break;
case BLEND_ARC_LINE:
res_create = tpCreateArcLineBlend(tp, prev_tc, tc, blend_tc);
break;
case BLEND_ARC_ARC:
res_create = tpCreateArcArcBlend(tp, prev_tc, tc, blend_tc);
break;
case BLEND_NONE:
default:
tp_debug_print("intersection type not recognized, aborting arc\n");
res_create = TP_ERR_FAIL;
break;
}
return res_create == TP_ERR_OK;
}
STATIC tc_blend_type_t tpHandleBlendArc(TP_STRUCT * const tp, TC_STRUCT * const tc) {
tp_debug_print("*****************************************\n** Handle Blend Arc **\n");
TC_STRUCT *prev_tc;
prev_tc = tcqLast(&tp->queue);
if ( !prev_tc) {
tp_debug_print(" queue empty\n");
return NO_BLEND;
}
if (prev_tc->progress > prev_tc->target / 2.0) {
tp_debug_print(" prev_tc progress (%f) is too large, aborting blend arc\n", prev_tc->progress);
return NO_BLEND;
}
int res_tan = tpSetupTangent(tp, prev_tc, tc);
switch (res_tan) {
case TP_ERR_FAIL:
tp_debug_print(" tpSetupTangent failed, aborting blend arc\n");
case TP_ERR_OK:
return res_tan;
case TP_ERR_NO_ACTION:
default:
break;
}
TC_STRUCT blend_tc = {0};
tc_blend_type_t blend_used = NO_BLEND;
bool arc_blend_ok = tpCreateBlendIfPossible(tp, prev_tc, tc, &blend_tc);
if (arc_blend_ok) {
blend_used = ARC_BLEND;
tpAddSegmentToQueue(tp, &blend_tc, false);
} else {
blend_used = tpChooseBestBlend(tp, prev_tc, tc, NULL) ;
}
return blend_used;
}
int tpAddLine(TP_STRUCT * const tp, EmcPose end, int canon_motion_type, double vel, double
ini_maxvel, double acc, unsigned char enables, char atspeed, int indexer_jnum) {
if (tpErrorCheck(tp) < 0) {
return TP_ERR_FAIL;
}
tp_info_print("== AddLine ==\n");
TC_STRUCT tc = {0};
tcInit(&tc,
TC_LINEAR,
canon_motion_type,
tp->cycleTime,
enables,
atspeed);
tpSetupSyncedIO(tp, &tc);
tcSetupState(&tc, tp);
tcSetupMotion(&tc,
vel,
ini_maxvel,
acc);
pmLine9Init(&tc.coords.line,
&tp->goalPos,
&end);
tc.target = pmLine9Target(&tc.coords.line);
if (tc.target < TP_POS_EPSILON) {
rtapi_print_msg(RTAPI_MSG_DBG,"failed to create line id %d, zero-length segment\n",tp->nextId);
return TP_ERR_ZERO_LENGTH;
}
tc.nominal_length = tc.target;
tcClampVelocityByLength(&tc);
tc.indexer_jnum = indexer_jnum;
TC_STRUCT *prev_tc;
prev_tc = tcqLast(&tp->queue);
tpCheckCanonType(prev_tc, &tc);
if (emcmotConfig->arcBlendEnable){
tpHandleBlendArc(tp, &tc);
}
tcFinalizeLength(prev_tc);
tcFlagEarlyStop(prev_tc, &tc);
int retval = tpAddSegmentToQueue(tp, &tc, true);
tpRunOptimization(tp);
return retval;
}
int tpAddCircle(TP_STRUCT * const tp,
EmcPose end,
PmCartesian center,
PmCartesian normal,
int turn,
int canon_motion_type,
double vel,
double ini_maxvel,
double acc,
unsigned char enables,
char atspeed)
{
if (tpErrorCheck(tp)<0) {
return TP_ERR_FAIL;
}
tp_info_print("== AddCircle ==\n");
tp_debug_print("ini_maxvel = %f\n",ini_maxvel);
TC_STRUCT tc = {0};
tcInit(&tc,
TC_CIRCULAR,
canon_motion_type,
tp->cycleTime,
enables,
atspeed);
tpSetupSyncedIO(tp, &tc);
tcSetupState(&tc, tp);
int res_init = pmCircle9Init(&tc.coords.circle,
&tp->goalPos,
&end,
¢er,
&normal,
turn);
if (res_init) return res_init;
tc.target = pmCircle9Target(&tc.coords.circle);
if (tc.target < TP_POS_EPSILON) {
return TP_ERR_ZERO_LENGTH;
}
tp_debug_print("tc.target = %f\n",tc.target);
tc.nominal_length = tc.target;
tcSetupMotion(&tc,
vel,
ini_maxvel,
acc);
tcClampVelocityByLength(&tc);
TC_STRUCT *prev_tc;
prev_tc = tcqLast(&tp->queue);
tpCheckCanonType(prev_tc, &tc);
if (emcmotConfig->arcBlendEnable){
tpHandleBlendArc(tp, &tc);
findSpiralArcLengthFit(&tc.coords.circle.xyz, &tc.coords.circle.fit);
}
tcFinalizeLength(prev_tc);
tcFlagEarlyStop(prev_tc, &tc);
int retval = tpAddSegmentToQueue(tp, &tc, true);
tpRunOptimization(tp);
return retval;
}
STATIC int tpComputeBlendVelocity(
TC_STRUCT const *tc,
TC_STRUCT const *nexttc,
double target_vel_this,
double target_vel_next,
double *v_blend_this,
double *v_blend_next,
double *v_blend_net)
{
if (!nexttc || !tc || !v_blend_this || !v_blend_next ) {
return TP_ERR_FAIL;
}
double acc_this = tcGetTangentialMaxAccel(tc);
double acc_next = tcGetTangentialMaxAccel(nexttc);
double v_reachable_this = fmin(tpCalculateTriangleVel(tc), target_vel_this);
double v_reachable_next = fmin(tpCalculateTriangleVel(nexttc), target_vel_next);
double t_max_this = tc->target / v_reachable_this;
double t_max_next = nexttc->target / v_reachable_next;
double t_max_reachable = fmin(t_max_this, t_max_next);
double t_min_blend_this = v_reachable_this / acc_this;
double t_min_blend_next = v_reachable_next / acc_next;
double t_max_blend = fmax(t_min_blend_this, t_min_blend_next);
double t_blend = fmin(t_max_reachable, t_max_blend);
*v_blend_this = fmin(v_reachable_this, t_blend * acc_this);
*v_blend_next = fmin(v_reachable_next, t_blend * acc_next);
double theta;
PmCartesian v1, v2;
tcGetEndAccelUnitVector(tc, &v1);
tcGetStartAccelUnitVector(nexttc, &v2);
findIntersectionAngle(&v1, &v2, &theta);
double cos_theta = cos(theta);
if (tc->tolerance > 0) {
double tblend_vel;
const double min_cos_theta = cos(PM_PI / 2.0 - TP_MIN_ARC_ANGLE);
if (cos_theta > min_cos_theta) {
tblend_vel = 2.0 * pmSqrt(acc_this * tc->tolerance / cos_theta);
*v_blend_this = fmin(*v_blend_this, tblend_vel);
*v_blend_next = fmin(*v_blend_next, tblend_vel);
}
}
if (v_blend_net) {
*v_blend_net = sin(theta) * (*v_blend_this + *v_blend_next) / 2.0;
}
return TP_ERR_OK;
}
STATIC double estimateParabolicBlendPerformance(
TP_STRUCT const *tp,
TC_STRUCT const *tc,
TC_STRUCT const *nexttc)
{
double v_this = 0.0, v_next = 0.0;
double target_vel_this = tpGetMaxTargetVel(tp, tc);
double target_vel_next = tpGetMaxTargetVel(tp, nexttc);
double v_net = 0.0;
tpComputeBlendVelocity(tc, nexttc, target_vel_this, target_vel_next, &v_this, &v_next, &v_net);
return v_net;
}
STATIC int tcUpdateDistFromAccel(TC_STRUCT * const tc, double acc, double vel_desired, int reverse_run)
{
double v_next = tc->currentvel + acc * tc->cycle_time;
if (v_next < 0.0) {
v_next = 0.0;
if (tcGetDistanceToGo(tc,reverse_run) < (tc->currentvel * tc->cycle_time)) {
tc->progress = tcGetTarget(tc,reverse_run);
}
} else {
double displacement = (v_next + tc->currentvel) * 0.5 * tc->cycle_time;
double disp_sign = reverse_run ? -1 : 1;
tc->progress += (disp_sign * displacement);
tc->progress = bisaturate(tc->progress, tcGetTarget(tc, TC_DIR_FORWARD), tcGetTarget(tc, TC_DIR_REVERSE));
}
tc->currentvel = v_next;
tc->on_final_decel = (fabs(vel_desired - tc->currentvel) < TP_VEL_EPSILON) && (acc < 0.0);
return TP_ERR_OK;
}
STATIC void tpDebugCycleInfo(TP_STRUCT const * const tp, TC_STRUCT const * const tc, TC_STRUCT const * const nexttc, double acc) {
#ifdef TC_DEBUG
double tc_target_vel = tpGetRealTargetVel(tp, tc);
double tc_finalvel = tpGetRealFinalVel(tp, tc, nexttc);
tc_debug_print("tc state: vr = %f, vf = %f, maxvel = %f\n",
tc_target_vel, tc_finalvel, tc->maxvel);
tc_debug_print(" currentvel = %f, fs = %f, tc = %f, term = %d\n",
tc->currentvel, tpGetFeedScale(tp,tc), tc->cycle_time, tc->term_cond);
tc_debug_print(" acc = %f, T = %f, DTG = %.12g\n", acc,
tcGetTarget(tc,tp->reverse_run), tcGetDistanceToGo(tc,tp->reverse_run));
tc_debug_print(" reverse_run = %d\n", tp->reverse_run);
tc_debug_print(" motion type %d\n", tc->motion_type);
if (tc->on_final_decel) {
rtapi_print(" on final decel\n");
}
#endif
}
void tpCalculateTrapezoidalAccel(TP_STRUCT const * const tp, TC_STRUCT * const tc, TC_STRUCT const * const nexttc,
double * const acc, double * const vel_desired)
{
tc_debug_print("using trapezoidal acceleration\n");
double tc_target_vel = tpGetRealTargetVel(tp, tc);
double tc_finalvel = tpGetRealFinalVel(tp, tc, nexttc);
#ifdef TP_PEDANTIC
if (tc_finalvel > 0.0 && tc->term_cond != TC_TERM_COND_TANGENT) {
rtapi_print_msg(RTAPI_MSG_ERR, "Final velocity of %f with non-tangent segment!\n",tc_finalvel);
tc_finalvel = 0.0;
}
#endif
double dx = tcGetDistanceToGo(tc, tp->reverse_run);
double maxaccel = tcGetTangentialMaxAccel(tc);
double discr_term1 = pmSq(tc_finalvel);
double discr_term2 = maxaccel * (2.0 * dx - tc->currentvel * tc->cycle_time);
double tmp_adt = maxaccel * tc->cycle_time * 0.5;
double discr_term3 = pmSq(tmp_adt);
double discr = discr_term1 + discr_term2 + discr_term3;
#ifdef TP_PEDANTIC
if (discr < 0.0) {
rtapi_print_msg(RTAPI_MSG_ERR,
"discriminant %f < 0 in velocity calculation!\n", discr);
}
#endif
double maxnewvel = -tmp_adt;
if (discr > discr_term3) {
maxnewvel += pmSqrt(discr);
}
double newvel = saturate(maxnewvel, tc_target_vel);
double dt = fmax(tc->cycle_time, TP_TIME_EPSILON);
double maxnewaccel = (newvel - tc->currentvel) / dt;
*acc = saturate(maxnewaccel, maxaccel);
*vel_desired = maxnewvel;
}
STATIC int tpCalculateRampAccel(TP_STRUCT const * const tp,
TC_STRUCT * const tc,
TC_STRUCT const * const nexttc,
double * const acc,
double * const vel_desired)
{
tc_debug_print("using ramped acceleration\n");
double dx = tcGetDistanceToGo(tc, tp->reverse_run);
if (!tc->blending_next) {
tc->vel_at_blend_start = tc->currentvel;
}
double vel_final = tpGetRealFinalVel(tp, tc, nexttc);
if (vel_final < TP_VEL_EPSILON) {
tp_debug_print(" vel_final %f too low for velocity ramping\n", vel_final);
return TP_ERR_FAIL;
}
double vel_avg = (tc->currentvel + vel_final) / 2.0;
double dt = 1e-16;
if (vel_avg > TP_VEL_EPSILON) {
dt = fmax( dx / vel_avg, 1e-16);
}
double dv = vel_final - tc->currentvel;
double acc_final = dv / dt;
double acc_max = tcGetTangentialMaxAccel(tc);
*acc = saturate(acc_final, acc_max);
*vel_desired = vel_final;
return TP_ERR_OK;
}
void tpToggleDIOs(TC_STRUCT * const tc) {
int i=0;
if (tc->syncdio.anychanged != 0) { for (i=0; i < emcmotConfig->numDIO; i++) {
if (!(tc->syncdio.dio_mask & (1 << i))) continue;
if (tc->syncdio.dios[i] > 0) emcmotDioWrite(i, 1); if (tc->syncdio.dios[i] < 0) emcmotDioWrite(i, 0); }
for (i=0; i < emcmotConfig->numAIO; i++) {
if (!(tc->syncdio.aio_mask & (1 << i))) continue;
emcmotAioWrite(i, tc->syncdio.aios[i]); }
tc->syncdio.anychanged = 0; }
}
STATIC void tpUpdateRigidTapState(TP_STRUCT const * const tp,
TC_STRUCT * const tc) {
static double old_spindlepos;
double new_spindlepos = emcmotStatus->spindle_status[tp->spindle.spindle_num].spindleRevs;
if (emcmotStatus->spindle_status[tp->spindle.spindle_num].direction < 0)
new_spindlepos = -new_spindlepos;
switch (tc->coords.rigidtap.state) {
case TAPPING:
tc_debug_print("TAPPING\n");
if (tc->progress >= tc->coords.rigidtap.reversal_target) {
emcmotStatus->spindle_status[tp->spindle.spindle_num].speed *= -1.0 * tc->coords.rigidtap.reversal_scale;
tc->coords.rigidtap.state = REVERSING;
}
break;
case REVERSING:
tc_debug_print("REVERSING\n");
if (new_spindlepos < old_spindlepos) {
PmCartesian start, end;
PmCartLine *aux = &tc->coords.rigidtap.aux_xyz;
tc->coords.rigidtap.spindlerevs_at_reversal = new_spindlepos + tp->spindle.offset;
pmCartLinePoint(&tc->coords.rigidtap.xyz, tc->progress, &start);
end = tc->coords.rigidtap.xyz.start;
pmCartLineInit(aux, &start, &end);
rtapi_print_msg(RTAPI_MSG_DBG, "old target = %f", tc->target);
tc->coords.rigidtap.reversal_target = aux->tmag;
tc->target = aux->tmag + 10. * tc->uu_per_rev;
tc->progress = 0.0;
rtapi_print_msg(RTAPI_MSG_DBG, "new target = %f", tc->target);
tc->coords.rigidtap.state = RETRACTION;
}
old_spindlepos = new_spindlepos;
tc_debug_print("Spindlepos = %f\n", new_spindlepos);
break;
case RETRACTION:
tc_debug_print("RETRACTION\n");
if (tc->progress >= tc->coords.rigidtap.reversal_target) {
emcmotStatus->spindle_status[tp->spindle.spindle_num].speed *= -1 / tc->coords.rigidtap.reversal_scale;
tc->coords.rigidtap.state = FINAL_REVERSAL;
}
break;
case FINAL_REVERSAL:
tc_debug_print("FINAL_REVERSAL\n");
if (new_spindlepos > old_spindlepos) {
PmCartesian start, end;
PmCartLine *aux = &tc->coords.rigidtap.aux_xyz;
pmCartLinePoint(aux, tc->progress, &start);
end = tc->coords.rigidtap.xyz.start;
pmCartLineInit(aux, &start, &end);
tc->target = aux->tmag;
tc->progress = 0.0;
tc->synchronized = 0;
tc->target_vel = tc->maxvel;
tc->coords.rigidtap.state = FINAL_PLACEMENT;
}
old_spindlepos = new_spindlepos;
break;
case FINAL_PLACEMENT:
tc_debug_print("FINAL_PLACEMENT\n");
break;
}
}
STATIC int tpUpdateMovementStatus(TP_STRUCT * const tp, TC_STRUCT const * const tc ) {
if (!tp) {
return TP_ERR_FAIL;
}
if (!tc) {
emcmotStatus->distance_to_go = 0;
emcmotStatus->enables_queued = emcmotStatus->enables_new;
emcmotStatus->requested_vel = 0;
emcmotStatus->current_vel = 0;
emcPoseZero(&emcmotStatus->dtg);
tp->motionType = 0;
tp->activeDepth = 0;
return TP_ERR_STOPPED;
}
EmcPose tc_pos;
tcGetEndpoint(tc, &tc_pos);
tc_debug_print("tc id = %u canon_type = %u mot type = %u\n",
tc->id, tc->canon_motion_type, tc->motion_type);
tp->motionType = tc->canon_motion_type;
tp->activeDepth = tc->active_depth;
emcmotStatus->distance_to_go = tc->target - tc->progress;
emcmotStatus->enables_queued = tc->enables;
tp->execId = tc->id;
emcmotStatus->requested_vel = tc->reqvel;
emcmotStatus->current_vel = tc->currentvel;
emcPoseSub(&tc_pos, &tp->currentPos, &emcmotStatus->dtg);
return TP_ERR_OK;
}
STATIC void tpUpdateBlend(TP_STRUCT * const tp, TC_STRUCT * const tc,
TC_STRUCT * const nexttc) {
if (!nexttc) {
return;
}
double save_vel = nexttc->target_vel;
if (tpGetFeedScale(tp, nexttc) > TP_VEL_EPSILON) {
double dv = tc->vel_at_blend_start - tc->currentvel;
double vel_start = fmax(tc->vel_at_blend_start, TP_VEL_EPSILON);
double blend_progress = fmax(fmin(dv / vel_start, 1.0), 0.0);
double blend_scale = tc->vel_at_blend_start / tc->blend_vel;
nexttc->target_vel = blend_progress * nexttc->blend_vel * blend_scale;
nexttc->is_blending = true;
} else {
nexttc->target_vel = 0.0;
}
tpUpdateCycle(tp, nexttc, NULL);
nexttc->target_vel = save_vel;
}
STATIC void tpHandleEmptyQueue(TP_STRUCT * const tp)
{
tcqInit(&tp->queue);
tp->goalPos = tp->currentPos;
tp->done = 1;
tp->depth = tp->activeDepth = 0;
tp->aborting = 0;
tp->execId = 0;
tp->motionType = 0;
tpUpdateMovementStatus(tp, NULL);
tpResume(tp);
}
STATIC void tpSetRotaryUnlock(int axis, int unlock) {
emcmotSetRotaryUnlock(axis, unlock);
}
STATIC int tpGetRotaryIsUnlocked(int axis) {
return emcmotGetRotaryIsUnlocked(axis);
}
STATIC int tpCompleteSegment(TP_STRUCT * const tp,
TC_STRUCT * const tc) {
if (tp->spindle.waiting_for_atspeed == tc->id) {
return TP_ERR_FAIL;
}
if(tc->synchronized != TC_SYNC_NONE) {
tp->spindle.offset += tc->target / tc->uu_per_rev;
} else {
tp->spindle.offset = 0.0;
}
if(tc->indexer_jnum != -1) {
tpSetRotaryUnlock(tc->indexer_jnum, 0);
if(tpGetRotaryIsUnlocked(tc->indexer_jnum)) {
return TP_ERR_FAIL;
}
}
tc->active = 0;
tc->remove = 0;
tc->is_blending = 0;
tc->splitting = 0;
tc->cycle_time = tp->cycleTime;
tc->currentvel = 0.0;
tc->term_vel = 0.0;
if (tp->reverse_run) {
tcqBackStep(&tp->queue);
tp_debug_print("Finished reverse run of tc id %d\n", tc->id);
} else {
int res_pop = tcqPop(&tp->queue);
if (res_pop) rtapi_print_msg(RTAPI_MSG_ERR,"Got error %d from tcqPop!\n", res_pop);
tp_debug_print("Finished tc id %d\n", tc->id);
}
return TP_ERR_OK;
}
STATIC tp_err_t tpHandleAbort(TP_STRUCT * const tp, TC_STRUCT * const tc,
TC_STRUCT * const nexttc) {
if(!tp->aborting) {
return TP_ERR_NO_ACTION;
}
if( MOTION_ID_VALID(tp->spindle.waiting_for_index) ||
MOTION_ID_VALID(tp->spindle.waiting_for_atspeed) ||
(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;
tp->reverse_run = 0;
tp->spindle.waiting_for_index = MOTION_INVALID_ID;
tp->spindle.waiting_for_atspeed = MOTION_INVALID_ID;
tpResume(tp);
return TP_ERR_STOPPED;
} return TP_ERR_SLOWING;
}
STATIC tp_err_t tpCheckAtSpeed(TP_STRUCT * const tp, TC_STRUCT * const tc)
{
int s;
if (MOTION_ID_VALID(tp->spindle.waiting_for_index) && tp->spindle.waiting_for_index != tc->id)
{
rtapi_print_msg(RTAPI_MSG_ERR,
"Was waiting for index on motion id %d, but reached id %d\n",
tp->spindle.waiting_for_index, tc->id);
tp->spindle.waiting_for_index = MOTION_INVALID_ID;
}
if (MOTION_ID_VALID(tp->spindle.waiting_for_atspeed) && tp->spindle.waiting_for_atspeed != tc->id)
{
rtapi_print_msg(RTAPI_MSG_ERR,
"Was waiting for atspeed on motion id %d, but reached id %d\n",
tp->spindle.waiting_for_atspeed, tc->id);
tp->spindle.waiting_for_atspeed = MOTION_INVALID_ID;
}
if (MOTION_ID_VALID(tp->spindle.waiting_for_atspeed)) {
for (s = 0; s < emcmotConfig->numSpindles; s++){
if(!emcmotStatus->spindle_status[s].at_speed) {
return TP_ERR_WAITING;
}
}
tp->spindle.waiting_for_atspeed = MOTION_INVALID_ID;
}
if (MOTION_ID_VALID(tp->spindle.waiting_for_index)) {
if (emcmotStatus->spindle_status[tp->spindle.spindle_num].spindle_index_enable) {
return TP_ERR_WAITING;
} else {
rtapi_print_msg(RTAPI_MSG_DBG, "Index seen on spindle %d\n", tp->spindle.spindle_num);
emcmotStatus->spindleSync = 1;
tp->spindle.waiting_for_index = MOTION_INVALID_ID;
tc->sync_accel = 1;
tp->spindle.revs = 0;
}
}
return TP_ERR_OK;
}
STATIC tp_err_t tpActivateSegment(TP_STRUCT * const tp, TC_STRUCT * const tc) {
if (!tc || tc->active) {
return TP_ERR_OK;
}
if (!tp) {
return TP_ERR_MISSING_INPUT;
}
if (tp->reverse_run && (tc->motion_type == TC_RIGIDTAP || tc->synchronized != TC_SYNC_NONE)) {
return TP_ERR_REVERSE_EMPTY;
}
double cutoff_time = 1.0 / (fmax(emcmotConfig->arcBlendRampFreq, TP_TIME_EPSILON));
double length = tcGetDistanceToGo(tc, tp->reverse_run);
double segment_time = 2.0 * length / (tc->currentvel + fmin(tc->finalvel,tpGetRealTargetVel(tp,tc)));
if (segment_time < cutoff_time &&
tc->canon_motion_type != EMC_MOTION_TYPE_TRAVERSE &&
tc->term_cond == TC_TERM_COND_TANGENT &&
tc->motion_type != TC_RIGIDTAP &&
length != 0)
{
tp_debug_print("segment_time = %f, cutoff_time = %f, ramping\n",
segment_time, cutoff_time);
tc->accel_mode = TC_ACCEL_RAMP;
}
int needs_atspeed = tc->atspeed ||
(tc->synchronized == TC_SYNC_POSITION && !(emcmotStatus->spindleSync));
if (needs_atspeed){
int s;
for (s = 0; s < emcmotConfig->numSpindles; s++){
if (!emcmotStatus->spindle_status[s].at_speed) {
tp->spindle.waiting_for_atspeed = tc->id;
return TP_ERR_WAITING;
}
}
}
if (tc->indexer_jnum != -1) {
tpSetRotaryUnlock(tc->indexer_jnum, 1);
if (!tpGetRotaryIsUnlocked(tc->indexer_jnum))
return TP_ERR_WAITING;
}
tp_debug_print("Activate tc id = %d target_vel = %f req_vel = %f final_vel = %f length = %f\n",
tc->id,
tc->target_vel,
tc->reqvel,
tc->finalvel,
tc->target);
tc->active = 1;
tp->motionType = tc->canon_motion_type;
tc->blending_next = 0;
tc->on_final_decel = 0;
if (TC_SYNC_POSITION == tc->synchronized && !(emcmotStatus->spindleSync)) {
tp_debug_print("Setting up position sync\n");
tp->spindle.waiting_for_index = tc->id;
emcmotStatus->spindle_status[tp->spindle.spindle_num].spindle_index_enable = 1;
tp->spindle.offset = 0.0;
rtapi_print_msg(RTAPI_MSG_DBG, "Waiting on sync. spindle_num %d..\n", tp->spindle.spindle_num);
return TP_ERR_WAITING;
}
return TP_ERR_OK;
}
STATIC void tpSyncVelocityMode(TP_STRUCT * const tp, TC_STRUCT * const tc, TC_STRUCT * const nexttc) {
double speed = emcmotStatus->spindle_status[tp->spindle.spindle_num].spindleSpeedIn;
double pos_error = fabs(speed) * tc->uu_per_rev;
if(nexttc) {
pos_error -= nexttc->progress;
}
tc->target_vel = pos_error;
if (nexttc && nexttc->synchronized) {
nexttc->target_vel = tc->target_vel;
}
}
STATIC void tpSyncPositionMode(TP_STRUCT * const tp, TC_STRUCT * const tc,
TC_STRUCT * const nexttc ) {
double spindle_pos = tpGetSignedSpindlePosition(&emcmotStatus->spindle_status[tp->spindle.spindle_num]);
tp_debug_print("Spindle at %f\n",spindle_pos);
double spindle_vel, target_vel;
double oldrevs = tp->spindle.revs;
if ((tc->motion_type == TC_RIGIDTAP) && (tc->coords.rigidtap.state == RETRACTION ||
tc->coords.rigidtap.state == FINAL_REVERSAL)) {
tp->spindle.revs = tc->coords.rigidtap.spindlerevs_at_reversal -
spindle_pos;
} else {
tp->spindle.revs = spindle_pos;
}
double pos_desired = (tp->spindle.revs - tp->spindle.offset) * tc->uu_per_rev;
double pos_error = pos_desired - tc->progress;
if(nexttc) {
pos_error -= nexttc->progress;
}
if(tc->sync_accel) {
double dt = fmax(tp->cycleTime, TP_TIME_EPSILON);
spindle_vel = tp->spindle.revs / ( dt * tc->sync_accel++);
target_vel = spindle_vel * tc->uu_per_rev;
if(tc->currentvel >= target_vel) {
tc_debug_print("Hit accel target in pos sync\n");
tp->spindle.offset = tp->spindle.revs - tc->progress / tc->uu_per_rev;
tc->sync_accel = 0;
tc->target_vel = target_vel;
} else {
tc_debug_print("accelerating in pos_sync\n");
tc->target_vel = tc->maxvel;
}
} else {
tc_debug_print("tracking in pos_sync\n");
double errorvel;
spindle_vel = (tp->spindle.revs - oldrevs) / tp->cycleTime;
target_vel = spindle_vel * tc->uu_per_rev;
errorvel = pmSqrt(fabs(pos_error) * tcGetTangentialMaxAccel(tc));
if(pos_error<0) {
errorvel *= -1.0;
}
tc->target_vel = target_vel + errorvel;
}
if (tc->target_vel < 0.0) {
tc->target_vel = 0.0;
}
if (nexttc && nexttc->synchronized) {
nexttc->target_vel = tc->target_vel;
}
}
STATIC int tpDoParabolicBlending(TP_STRUCT * const tp, TC_STRUCT * const tc,
TC_STRUCT * const nexttc) {
tc_debug_print("in DoParabolicBlend\n");
tpUpdateBlend(tp,tc,nexttc);
if(tc->currentvel > nexttc->currentvel) {
tpUpdateMovementStatus(tp, tc);
} else {
tpToggleDIOs(nexttc);
tpUpdateMovementStatus(tp, nexttc);
}
#ifdef TP_SHOW_BLENDS
tp->motionType = 0;
#endif
emcmotStatus->current_vel = tc->currentvel + nexttc->currentvel;
return TP_ERR_OK;
}
STATIC int tpUpdateCycle(TP_STRUCT * const tp,
TC_STRUCT * const tc, TC_STRUCT const * const nexttc) {
EmcPose before;
tcGetPos(tc, &before);
if (!tc->blending_next) {
tc->vel_at_blend_start = tc->currentvel;
}
int res_accel = 1;
double acc=0, vel_desired=0;
if (tc->accel_mode && tc->term_cond == TC_TERM_COND_TANGENT) {
res_accel = tpCalculateRampAccel(tp, tc, nexttc, &acc, &vel_desired);
}
if (res_accel != TP_ERR_OK) {
tpCalculateTrapezoidalAccel(tp, tc, nexttc, &acc, &vel_desired);
}
tcUpdateDistFromAccel(tc, acc, vel_desired, tp->reverse_run);
tpDebugCycleInfo(tp, tc, nexttc, acc);
tpCheckEndCondition(tp, tc, nexttc);
EmcPose displacement;
tcGetPos(tc, &displacement);
emcPoseSelfSub(&displacement, &before);
int res_set = tpAddCurrentPos(tp, &displacement);
#ifdef TC_DEBUG
double mag;
emcPoseMagnitude(&displacement, &mag);
tc_debug_print("cycle movement = %f\n", mag);
#endif
return res_set;
}
STATIC int tpUpdateInitialStatus(TP_STRUCT const * const tp) {
emcmotStatus->tcqlen = tcqLen(&tp->queue);
emcmotStatus->requested_vel = 0.0;
emcmotStatus->current_vel = 0.0;
return TP_ERR_OK;
}
STATIC inline int tcSetSplitCycle(TC_STRUCT * const tc, double split_time,
double v_f)
{
tp_debug_print("split time for id %d is %.16g\n", tc->id, split_time);
if (tc->splitting != 0 && split_time > 0.0) {
rtapi_print_msg(RTAPI_MSG_ERR,"already splitting on id %d with cycle time %.16g, dx = %.16g, split time %.12g\n",
tc->id,
tc->cycle_time,
tc->target-tc->progress,
split_time);
return TP_ERR_FAIL;
}
tc->splitting = 1;
tc->cycle_time = split_time;
tc->term_vel = v_f;
return 0;
}
STATIC int tpCheckEndCondition(TP_STRUCT const * const tp, TC_STRUCT * const tc, TC_STRUCT const * const nexttc) {
tc->cycle_time = tp->cycleTime;
double dx = tcGetDistanceToGo(tc, tp->reverse_run);
tc_debug_print("tpCheckEndCondition: dx = %e\n",dx);
if (dx <= TP_POS_EPSILON) {
tp_debug_print("close to target, dx = %.12f\n",dx);
tc->progress = tcGetTarget(tc, tp->reverse_run);
if (!tp->reverse_run) {
tcSetSplitCycle(tc, 0.0, tc->currentvel);
}
if (tc->term_cond == TC_TERM_COND_STOP || tc->term_cond == TC_TERM_COND_EXACT || tp->reverse_run) {
tc->remove = 1;
}
return TP_ERR_OK;
} else if (tp->reverse_run) {
return TP_ERR_NO_ACTION;
} else if (tc->term_cond == TC_TERM_COND_STOP || tc->term_cond == TC_TERM_COND_EXACT) {
return TP_ERR_NO_ACTION;
}
double v_f = tpGetRealFinalVel(tp, tc, nexttc);
double v_avg = (tc->currentvel + v_f) / 2.0;
double dt = TP_TIME_EPSILON / 2.0;
if (v_avg > TP_VEL_EPSILON) {
dt = fmax(dt, dx / v_avg);
} else {
if ( dx > (v_avg * tp->cycleTime) && dx > TP_POS_EPSILON) {
tc_debug_print(" below velocity threshold, assuming far from end\n");
return TP_ERR_NO_ACTION;
}
}
double dv = v_f - tc->currentvel;
double a_f = dv / dt;
double a_max = tcGetTangentialMaxAccel(tc);
double a = a_f;
int recalc = sat_inplace(&a, a_max);
if (recalc) {
tc_debug_print(" recalculating with a_f = %f, a = %f\n", a_f, a);
double disc = pmSq(tc->currentvel / a) + 2.0 / a * dx;
if (disc < 0) {
tc_debug_print(" dx = %f, too large, not at end yet\n",dx);
return TP_ERR_NO_ACTION;
}
if (disc < TP_TIME_EPSILON * TP_TIME_EPSILON) {
tc_debug_print("disc too small, skipping sqrt\n");
dt = -tc->currentvel / a;
} else if (a > 0) {
tc_debug_print("using positive sqrt\n");
dt = -tc->currentvel / a + pmSqrt(disc);
} else {
tc_debug_print("using negative sqrt\n");
dt = -tc->currentvel / a - pmSqrt(disc);
}
tc_debug_print(" revised dt = %f\n", dt);
v_f = tc->currentvel + dt * a;
}
if (dt < TP_TIME_EPSILON) {
tc_debug_print("revised dt small, finishing tc\n");
tc->progress = tcGetTarget(tc, tp->reverse_run);
tcSetSplitCycle(tc, 0.0, v_f);
} else if (dt < tp->cycleTime ) {
tc_debug_print(" corrected v_f = %f, a = %f\n", v_f, a);
tcSetSplitCycle(tc, dt, v_f);
} else {
tc_debug_print(" dt = %f, not at end yet\n",dt);
return TP_ERR_NO_ACTION;
}
return TP_ERR_OK;
}
STATIC int tpHandleSplitCycle(TP_STRUCT * const tp, TC_STRUCT * const tc,
TC_STRUCT * const nexttc)
{
if (tc->remove) {
return TP_ERR_NO_ACTION;
}
EmcPose before;
tcGetPos(tc, &before);
tp_debug_print("tc id %d splitting\n",tc->id);
tc->progress = tcGetTarget(tc,tp->reverse_run);
EmcPose displacement;
tcGetPos(tc, &displacement);
emcPoseSelfSub(&displacement, &before);
tpAddCurrentPos(tp, &displacement);
#ifdef TC_DEBUG
double mag;
emcPoseMagnitude(&displacement, &mag);
tc_debug_print("cycle movement = %f\n",mag);
#endif
tc->remove = 1;
if (!nexttc) {
tp_debug_print("no nexttc in split cycle\n");
return TP_ERR_OK;
}
switch (tc->term_cond) {
case TC_TERM_COND_TANGENT:
nexttc->cycle_time = tp->cycleTime - tc->cycle_time;
nexttc->currentvel = tc->term_vel;
tp_debug_print("Doing tangent split\n");
break;
case TC_TERM_COND_PARABOLIC:
break;
case TC_TERM_COND_STOP:
break;
case TC_TERM_COND_EXACT:
break;
default:
rtapi_print_msg(RTAPI_MSG_ERR,"unknown term cond %d in segment %d\n",
tc->term_cond,
tc->id);
}
int queue_dir_step = tp->reverse_run ? -1 : 1;
TC_STRUCT *next2tc = tcqItem(&tp->queue, queue_dir_step*2);
tpUpdateCycle(tp, nexttc, next2tc);
if (tc->cycle_time > nexttc->cycle_time && tc->term_cond == TC_TERM_COND_TANGENT) {
tpToggleDIOs(tc);
tpUpdateMovementStatus(tp, tc);
} else {
tpToggleDIOs(nexttc);
tpUpdateMovementStatus(tp, nexttc);
}
return TP_ERR_OK;
}
STATIC int tpHandleRegularCycle(TP_STRUCT * const tp,
TC_STRUCT * const tc,
TC_STRUCT * const nexttc)
{
if (tc->remove) {
return TP_ERR_NO_ACTION;
}
tc_debug_print("Normal cycle\n");
tc->cycle_time = tp->cycleTime;
tpUpdateCycle(tp, tc, nexttc);
double v_this = 0.0, v_next = 0.0;
double target_vel_this = tpGetRealTargetVel(tp, tc);
double target_vel_next = tpGetRealTargetVel(tp, nexttc);
tpComputeBlendVelocity(tc, nexttc, target_vel_this, target_vel_next, &v_this, &v_next, NULL);
tc->blend_vel = v_this;
if (nexttc) {
nexttc->blend_vel = v_next;
}
if (nexttc && tcIsBlending(tc)) {
tpDoParabolicBlending(tp, tc, nexttc);
} else {
tpToggleDIOs(tc);
tpUpdateMovementStatus(tp, tc);
}
return TP_ERR_OK;
}
int tpRunCycle(TP_STRUCT * const tp, long period)
{
TC_STRUCT *tc;
TC_STRUCT *nexttc;
int queue_dir_step = tp->reverse_run ? -1 : 1;
tc = tcqItem(&tp->queue, 0);
nexttc = tcqItem(&tp->queue, queue_dir_step * 1);
tpUpdateInitialStatus(tp);
#ifdef TC_DEBUG
static double time_elapsed = 0;
time_elapsed+=tp->cycleTime;
#endif
if(!tc) {
tpHandleEmptyQueue(tp);
return TP_ERR_WAITING;
}
tc_debug_print("-------------------\n");
if (tpHandleAbort(tp, tc, nexttc) == TP_ERR_STOPPED) {
return TP_ERR_STOPPED;
}
if (tpCheckAtSpeed(tp, tc) != TP_ERR_OK){
return TP_ERR_WAITING;
}
int res_activate = tpActivateSegment(tp, tc);
if (res_activate != TP_ERR_OK ) {
return res_activate;
}
if (tc->motion_type == TC_RIGIDTAP) {
tpUpdateRigidTapState(tp, tc);
}
switch (tc->synchronized) {
case TC_SYNC_NONE:
emcmotStatus->spindleSync = 0;
break;
case TC_SYNC_VELOCITY:
tp_debug_print("sync velocity\n");
tpSyncVelocityMode(tp, tc, nexttc);
break;
case TC_SYNC_POSITION:
tp_debug_print("sync position\n");
tpSyncPositionMode(tp, tc, nexttc);
break;
default:
tp_debug_print("unrecognized spindle sync state!\n");
break;
}
#ifdef TC_DEBUG
EmcPose pos_before = tp->currentPos;
#endif
tcClearFlags(tc);
tcClearFlags(nexttc);
if (tc->splitting) {
tpHandleSplitCycle(tp, tc, nexttc);
} else {
tpHandleRegularCycle(tp, tc, nexttc);
}
#ifdef TC_DEBUG
double mag;
EmcPose disp;
emcPoseSub(&tp->currentPos, &pos_before, &disp);
emcPoseMagnitude(&disp, &mag);
tc_debug_print("time: %.12e total movement = %.12e vel = %.12e\n",
time_elapsed,
mag, emcmotStatus->current_vel);
tc_debug_print("tp_displacement = %.12e %.12e %.12e time = %.12e\n",
disp.tran.x,
disp.tran.y,
disp.tran.z,
time_elapsed);
#endif
if (tc->remove) {
tpCompleteSegment(tp, tc);
}
return TP_ERR_OK;
}
int tpSetSpindleSync(TP_STRUCT * const tp, int spindle, double sync, int mode) {
if(sync) {
if (mode) {
tp->synchronized = TC_SYNC_VELOCITY;
} else {
tp->synchronized = TC_SYNC_POSITION;
}
tp->uu_per_rev = sync;
tp->spindle.spindle_num = spindle;
} else
tp->synchronized = 0;
return TP_ERR_OK;
}
int tpPause(TP_STRUCT * const tp)
{
if (0 == tp) {
return TP_ERR_FAIL;
}
tp->pausing = 1;
return TP_ERR_OK;
}
int tpResume(TP_STRUCT * const tp)
{
if (0 == tp) {
return TP_ERR_FAIL;
}
tp->pausing = 0;
return TP_ERR_OK;
}
int tpAbort(TP_STRUCT * const tp)
{
if (0 == tp) {
return TP_ERR_FAIL;
}
if (!tp->aborting) {
tpPause(tp);
tp->aborting = 1;
}
return tpClearDIOs(tp); }
int tpGetMotionType(TP_STRUCT * const tp)
{
return tp->motionType;
}
int tpGetPos(TP_STRUCT const * const tp, EmcPose * const pos)
{
if (0 == tp) {
ZERO_EMC_POSE((*pos));
return TP_ERR_FAIL;
} else {
*pos = tp->currentPos;
}
return TP_ERR_OK;
}
int tpIsDone(TP_STRUCT * const tp)
{
if (0 == tp) {
return TP_ERR_OK;
}
return tp->done;
}
int tpQueueDepth(TP_STRUCT * const tp)
{
if (0 == tp) {
return TP_ERR_OK;
}
return tp->depth;
}
int tpActiveDepth(TP_STRUCT * const tp)
{
if (0 == tp) {
return TP_ERR_OK;
}
return tp->activeDepth;
}
int tpSetAout(TP_STRUCT * const tp, unsigned char index, double start, double end) {
if (0 == tp) {
return TP_ERR_FAIL;
}
tp->syncdio.anychanged = 1; tp->syncdio.aio_mask |= (1 << index);
tp->syncdio.aios[index] = start;
return TP_ERR_OK;
}
int tpSetDout(TP_STRUCT * const tp, int index, unsigned char start, unsigned char end) {
if (0 == tp) {
return TP_ERR_FAIL;
}
tp->syncdio.anychanged = 1; tp->syncdio.dio_mask |= (1 << index);
if (start > 0)
tp->syncdio.dios[index] = 1; else
tp->syncdio.dios[index] = -1;
return TP_ERR_OK;
}
int tpSetRunDir(TP_STRUCT * const tp, tc_direction_t dir)
{
if (tpIsMoving(tp)) {
return TP_ERR_FAIL;
}
switch (dir) {
case TC_DIR_FORWARD:
case TC_DIR_REVERSE:
tp->reverse_run = dir;
return TP_ERR_OK;
default:
rtapi_print_msg(RTAPI_MSG_ERR,"Invalid direction flag in SetRunDir");
return TP_ERR_FAIL;
}
}
int tpIsMoving(TP_STRUCT const * const tp)
{
if (emcmotStatus->current_vel >= TP_VEL_EPSILON ) {
tp_debug_print("TP moving, current_vel = %.16g\n", emcmotStatus->current_vel);
return true;
} else if (tp->spindle.waiting_for_index != MOTION_INVALID_ID || tp->spindle.waiting_for_atspeed != MOTION_INVALID_ID) {
tp_debug_print("TP moving, waiting for index or atspeed\n");
return true;
}
return false;
}