519 lines
30 KiB
C
519 lines
30 KiB
C
#include "sim_linuxcnc_tp.h"
|
|
|
|
#include "motion.h"
|
|
#include "tp.h"
|
|
#include "emcpose.h"
|
|
#include "motion_types.h"
|
|
|
|
#include <math.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
extern int tcGetPosReal(TC_STRUCT const *tc, int of_point, EmcPose *pos);
|
|
|
|
struct sim_linuxcnc_tp {
|
|
TP_STRUCT tp;
|
|
emcmot_status_t status;
|
|
emcmot_config_t config;
|
|
double axis_velocity[9];
|
|
double axis_acceleration[9];
|
|
double axis_min[9];
|
|
double axis_max[9];
|
|
double velocity_scale;
|
|
double jog_scale;
|
|
double scale_minimum;
|
|
uint32_t tick;
|
|
uint32_t last_active_id;
|
|
int just_completed;
|
|
int initialized;
|
|
int step_id;
|
|
};
|
|
|
|
static sim_linuxcnc_tp *active_planner;
|
|
|
|
/* Same-build checkpoint: TP/TC contain the integration and blend state which
|
|
cannot be reconstructed by re-enqueuing the remaining geometric path.
|
|
The queue pointer is removed on export and rebound on restore. All other
|
|
members of these source structs are values, not process addresses. */
|
|
typedef struct {
|
|
uint32_t magic, planner_size, tc_size, capacity;
|
|
sim_linuxcnc_tp planner;
|
|
TC_STRUCT queue[TP_DEFAULT_QUEUE_SIZE];
|
|
} tp_checkpoint;
|
|
|
|
uint32_t sim_linuxcnc_tp_checkpoint_size(void) { return (uint32_t)sizeof(tp_checkpoint); }
|
|
|
|
int32_t sim_linuxcnc_tp_checkpoint_export(const sim_linuxcnc_tp *planner, uint8_t *output, uint32_t capacity) {
|
|
if (planner == NULL || !planner->initialized || output == NULL || capacity != sizeof(tp_checkpoint) || planner->tp.queue.size != TP_DEFAULT_QUEUE_SIZE) return -1228;
|
|
tp_checkpoint *saved = (tp_checkpoint *)output;
|
|
memset(saved, 0, sizeof(*saved));
|
|
saved->magic = UINT32_C(0x54504331);
|
|
saved->planner_size = sizeof(sim_linuxcnc_tp); saved->tc_size = sizeof(TC_STRUCT); saved->capacity = TP_DEFAULT_QUEUE_SIZE;
|
|
saved->planner = *planner;
|
|
saved->planner.tp.queue.queue = NULL;
|
|
memcpy(saved->queue, planner->tp.queue.queue, sizeof(saved->queue));
|
|
return 0;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_checkpoint_restore(sim_linuxcnc_tp *planner, const uint8_t *input, uint32_t length) {
|
|
if (planner == NULL || !planner->initialized || input == NULL || length != sizeof(tp_checkpoint)) return -1228;
|
|
const tp_checkpoint *saved = (const tp_checkpoint *)input;
|
|
const TC_QUEUE_STRUCT *queue = &saved->planner.tp.queue;
|
|
if (saved->magic != UINT32_C(0x54504331) || saved->planner_size != sizeof(sim_linuxcnc_tp) || saved->tc_size != sizeof(TC_STRUCT) || saved->capacity != TP_DEFAULT_QUEUE_SIZE || saved->planner.initialized != 1 || queue->queue != NULL || queue->size != TP_DEFAULT_QUEUE_SIZE || queue->start < 0 || queue->start >= queue->size || queue->end < 0 || queue->end >= queue->size || queue->_len < 0 || queue->_rlen < 0 || queue->_len > queue->size || queue->_rlen > queue->size - queue->_len) return -1228;
|
|
if (!isfinite(saved->planner.tp.cycleTime) || saved->planner.tp.cycleTime != planner->tp.cycleTime || memcmp(&saved->planner.config, &planner->config, sizeof(planner->config)) != 0 || memcmp(saved->planner.axis_min, planner->axis_min, sizeof(planner->axis_min)) != 0 || memcmp(saved->planner.axis_max, planner->axis_max, sizeof(planner->axis_max)) != 0 || memcmp(saved->planner.axis_velocity, planner->axis_velocity, sizeof(planner->axis_velocity)) != 0 || memcmp(saved->planner.axis_acceleration, planner->axis_acceleration, sizeof(planner->axis_acceleration)) != 0) return -1229;
|
|
TC_STRUCT *storage = planner->tp.queue.queue;
|
|
*planner = saved->planner;
|
|
planner->tp.queue.queue = storage;
|
|
memcpy(storage, saved->queue, sizeof(saved->queue));
|
|
active_planner = planner;
|
|
tpMotData(&planner->status, &planner->config);
|
|
return 0;
|
|
}
|
|
|
|
static void noop_dio(int index, char value) { (void)index; (void)value; }
|
|
static void noop_aio(int index, double value) { (void)index; (void)value; }
|
|
static void noop_rotary_unlock(int index, int value) { (void)index; (void)value; }
|
|
static int noop_rotary_locked(int index) { (void)index; return 0; }
|
|
static double axis_velocity(int axis) { return active_planner && axis >= 0 && axis < 9 ? active_planner->axis_velocity[axis] : 0.0; }
|
|
static double axis_acceleration(int axis) { return active_planner && axis >= 0 && axis < 9 ? active_planner->axis_acceleration[axis] : 0.0; }
|
|
|
|
static double pose_delta(const EmcPose *start, const EmcPose *end, int axis) {
|
|
const double values_start[9] = {start->tran.x, start->tran.y, start->tran.z, start->a, start->b, start->c, start->u, start->v, start->w};
|
|
const double values_end[9] = {end->tran.x, end->tran.y, end->tran.z, end->a, end->b, end->c, end->u, end->v, end->w};
|
|
return values_end[axis] - values_start[axis];
|
|
}
|
|
|
|
static void xyzac_line_limits(const sim_linuxcnc_tp *planner, const EmcPose *start, const EmcPose *end, double *velocity, double *acceleration) {
|
|
double linear_length = 0.0, angular_length = 0.0, max_velocity_time = 0.0, max_acceleration_time = 0.0;
|
|
int has_linear = 0, has_angular = 0;
|
|
for (int axis = 0; axis < 9; ++axis) {
|
|
const double delta = fabs(pose_delta(start, end, axis));
|
|
const double velocity_limit = planner->axis_velocity[axis];
|
|
const double acceleration_limit = planner->axis_acceleration[axis];
|
|
if (delta <= 1e-12) continue;
|
|
if (axis < 3 || axis >= 6) { has_linear = 1; linear_length += delta * delta; }
|
|
else { has_angular = 1; angular_length += delta * delta; }
|
|
if (velocity_limit > 0.0) max_velocity_time = fmax(max_velocity_time, delta / velocity_limit);
|
|
if (acceleration_limit > 0.0) max_acceleration_time = fmax(max_acceleration_time, delta / acceleration_limit);
|
|
}
|
|
const double distance = has_linear ? sqrt(linear_length) : has_angular ? sqrt(angular_length) : 0.0;
|
|
if (!(distance > 0.0)) return;
|
|
if (max_velocity_time > 0.0) *velocity = fmin(*velocity, distance / max_velocity_time);
|
|
if (max_acceleration_time > 0.0) *acceleration = fmin(*acceleration, distance / max_acceleration_time);
|
|
}
|
|
|
|
static void xyzac_circle_limits(const sim_linuxcnc_tp *planner, const EmcPose *start, const EmcPose *end, const PmCartesian *center, const PmCartesian *normal, int turn, double *velocity, double *initial_velocity, double *acceleration) {
|
|
const double sx = start->tran.x - center->x, sy = start->tran.y - center->y, sz = start->tran.z - center->z;
|
|
const double ex = end->tran.x - center->x, ey = end->tran.y - center->y, ez = end->tran.z - center->z;
|
|
// ARC_FEED measures radius in the active plane. For the XYZAC case the
|
|
// normal is +Z, so the axial helix delta must not become a radial spiral
|
|
// term (the previous 3-D radius caused a small but accumulating TP error).
|
|
const double normal_abs_x = fabs(normal->x), normal_abs_y = fabs(normal->y), normal_abs_z = fabs(normal->z);
|
|
const double start_radius = normal_abs_z >= fmax(normal_abs_x, normal_abs_y) ? hypot(sx, sy) : normal_abs_y >= normal_abs_x ? hypot(sx, sz) : hypot(sy, sz);
|
|
const double end_radius = normal_abs_z >= fmax(normal_abs_x, normal_abs_y) ? hypot(ex, ey) : normal_abs_y >= normal_abs_x ? hypot(ex, ez) : hypot(ey, ez);
|
|
const double dot = sx * ex + sy * ey + sz * ez;
|
|
const double cross_x = sy * ez - sz * ey, cross_y = sz * ex - sx * ez, cross_z = sx * ey - sy * ex;
|
|
double angle = fabs(atan2(normal->x * cross_x + normal->y * cross_y + normal->z * cross_z, dot));
|
|
if (angle <= 1e-12) angle = 2.0 * M_PI;
|
|
angle += 2.0 * M_PI * (fabs((double)turn) - 1.0);
|
|
const double spiral = end_radius - start_radius;
|
|
const double circular_length = fmin(start_radius, end_radius) * fabs(angle);
|
|
const double spiral_length = hypot(circular_length, spiral);
|
|
const double effective_radius = sqrt((spiral / fmax(fabs(angle), 1e-12)) * (spiral / fmax(fabs(angle), 1e-12)) + fmin(start_radius, end_radius) * fmin(start_radius, end_radius));
|
|
const double planar_velocity = fmin(sqrt(planner->axis_acceleration[0] * sqrt(3.0) * 0.5 * effective_radius), fmin(planner->axis_velocity[0], fmin(planner->axis_velocity[1], planner->axis_velocity[2])));
|
|
double total_xyz_length = hypot(spiral_length, normal->x * (end->tran.x - start->tran.x) + normal->y * (end->tran.y - start->tran.y) + normal->z * (end->tran.z - start->tran.z));
|
|
double time_motion_velocity = 0.0, time_motion_acceleration = 0.0;
|
|
for (int axis = 0; axis < 9; ++axis) {
|
|
const double delta = fabs(pose_delta(start, end, axis));
|
|
if (planner->axis_velocity[axis] > 0.0) time_motion_velocity = fmax(time_motion_velocity, delta / planner->axis_velocity[axis]);
|
|
if (planner->axis_acceleration[axis] > 0.0) time_motion_acceleration = fmax(time_motion_acceleration, delta / planner->axis_acceleration[axis]);
|
|
}
|
|
const double time_spiral_velocity = planar_velocity > 0.0 ? spiral_length / planar_velocity : INFINITY;
|
|
const double time_spiral_acceleration = planner->axis_acceleration[0] > 0.0 ? spiral_length / planner->axis_acceleration[0] : INFINITY;
|
|
const double max_velocity = total_xyz_length / fmax(time_motion_velocity, time_spiral_velocity);
|
|
const double max_acceleration = total_xyz_length / fmax(time_motion_acceleration, time_spiral_acceleration);
|
|
*velocity = fmin(*velocity, max_velocity);
|
|
*initial_velocity = max_velocity;
|
|
*acceleration = fmin(*acceleration, max_acceleration);
|
|
}
|
|
|
|
static EmcPose to_emc_pose(const sim_posemath_pose *value) {
|
|
EmcPose result = {0};
|
|
if (value != NULL) {
|
|
result.tran.x = value->tran.x; result.tran.y = value->tran.y; result.tran.z = value->tran.z;
|
|
result.a = value->a; result.b = value->b; result.c = value->c;
|
|
result.u = value->u; result.v = value->v; result.w = value->w;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static PmCartesian to_pm_cart(const sim_posemath_cartesian *value) {
|
|
PmCartesian result = {0, 0, 0};
|
|
if (value != NULL) { result.x = value->x; result.y = value->y; result.z = value->z; }
|
|
return result;
|
|
}
|
|
|
|
static sim_posemath_pose from_emc_pose(const EmcPose *value) {
|
|
sim_posemath_pose result = {{0, 0, 0}, 0, 0, 0, 0, 0, 0};
|
|
if (value != NULL) {
|
|
result.tran.x = value->tran.x; result.tran.y = value->tran.y; result.tran.z = value->tran.z;
|
|
result.a = value->a; result.b = value->b; result.c = value->c;
|
|
result.u = value->u; result.v = value->v; result.w = value->w;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static uint64_t hash_bytes(const unsigned char *bytes, size_t length) {
|
|
uint64_t hash = UINT64_C(1469598103934665603);
|
|
for (size_t index = 0; index < length; ++index) { hash ^= bytes[index]; hash *= UINT64_C(1099511628211); }
|
|
return hash;
|
|
}
|
|
|
|
uint32_t sim_linuxcnc_tp_size(void) { return (uint32_t)sizeof(sim_linuxcnc_tp); }
|
|
uint32_t sim_linuxcnc_tp_queue_capacity(const sim_linuxcnc_tp *planner) { return planner == NULL || !planner->initialized ? 0u : (uint32_t)TP_DEFAULT_QUEUE_SIZE; }
|
|
|
|
int32_t sim_linuxcnc_tp_init(sim_linuxcnc_tp *planner, double cycle_time, double velocity_limit, double acceleration_limit) {
|
|
if (planner == NULL || !isfinite(cycle_time) || cycle_time <= 0.0 || cycle_time > 60.0 || !isfinite(velocity_limit) || velocity_limit <= 0.0 || !isfinite(acceleration_limit) || acceleration_limit <= 0.0) return -1200;
|
|
memset(planner, 0, sizeof(*planner));
|
|
planner->config.numJoints = 5; planner->config.numExtraJoints = 0; planner->config.numSpindles = 1;
|
|
planner->config.numDIO = 0; planner->config.numAIO = 0; planner->config.numMiscError = 0;
|
|
planner->config.trajCycleTime = cycle_time; planner->config.servoCycleTime = cycle_time;
|
|
planner->config.limitVel = velocity_limit; planner->config.maxFeedScale = 2.0;
|
|
planner->config.arcBlendEnable = 1; planner->config.arcBlendFallbackEnable = 0;
|
|
planner->config.arcBlendOptDepth = 50; planner->config.arcBlendGapCycles = 4;
|
|
planner->config.arcBlendRampFreq = 100.0; planner->config.arcBlendTangentKinkRatio = 0.1;
|
|
// The caller configures each axis from INI before motion is queued.
|
|
// Standalone planners use their supplied trajectory limits.
|
|
for (int axis = 0; axis < 9; ++axis) {
|
|
planner->axis_velocity[axis] = velocity_limit;
|
|
planner->axis_acceleration[axis] = acceleration_limit;
|
|
planner->axis_min[axis] = -INFINITY;
|
|
planner->axis_max[axis] = INFINITY;
|
|
}
|
|
planner->velocity_scale = 1.0;
|
|
planner->jog_scale = 1.0;
|
|
planner->scale_minimum = 0.0;
|
|
planner->status.feed_scale = 1.0; planner->status.rapid_scale = 1.0; planner->status.net_feed_scale = 1.0;
|
|
planner->status.enables_new = FS_ENABLED | FH_ENABLED; planner->status.enables_queued = planner->status.enables_new;
|
|
planner->status.motionFlag = EMCMOT_MOTION_ENABLE_BIT | EMCMOT_MOTION_COORD_BIT | EMCMOT_MOTION_INPOS_BIT;
|
|
active_planner = planner;
|
|
tpMotData(&planner->status, &planner->config);
|
|
tpMotFunctions(noop_dio, noop_aio, noop_rotary_unlock, noop_rotary_locked, axis_velocity, axis_acceleration);
|
|
if (tpCreate(&planner->tp, TP_DEFAULT_QUEUE_SIZE, 0) != TP_ERR_OK) return -1201;
|
|
if (tpSetCycleTime(&planner->tp, cycle_time) != TP_ERR_OK || tpSetVlimit(&planner->tp, velocity_limit) != TP_ERR_OK || tpSetAmax(&planner->tp, acceleration_limit) != TP_ERR_OK) return -1202;
|
|
planner->initialized = 1;
|
|
return 0;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_reset(sim_linuxcnc_tp *planner) {
|
|
if (planner == NULL || !planner->initialized) return -1203;
|
|
active_planner = planner;
|
|
planner->last_active_id = 0;
|
|
planner->just_completed = 0;
|
|
return tpClear(&planner->tp) == TP_ERR_OK ? 0 : -1204;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_set_profile(sim_linuxcnc_tp *planner, int profile, double tolerance) {
|
|
if (planner == NULL || !planner->initialized || profile < 0 || profile > 2 || !isfinite(tolerance) || tolerance < 0.0) return -1205;
|
|
active_planner = planner;
|
|
return tpSetTermCond(&planner->tp, profile, tolerance) == TP_ERR_OK ? 0 : -1206;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_set_feed_override(sim_linuxcnc_tp *planner, double scale) {
|
|
if (planner == NULL || !planner->initialized || !isfinite(scale) || scale < 0.0 || scale > planner->config.maxFeedScale) return -1207;
|
|
planner->status.net_feed_scale = scale; planner->status.feed_scale = scale; return 0;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_set_velocity_scale(sim_linuxcnc_tp *planner, double scale) {
|
|
if (planner == NULL || !planner->initialized || !isfinite(scale) || scale <= 0.0 || scale < planner->scale_minimum || scale > 1.0) return -1211;
|
|
active_planner = planner;
|
|
if (tpSetVlimit(&planner->tp, planner->config.limitVel * scale) != TP_ERR_OK) return -1212;
|
|
planner->velocity_scale = scale;
|
|
return 0;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_set_jog_scale(sim_linuxcnc_tp *planner, double scale) {
|
|
if (planner == NULL || !planner->initialized || !isfinite(scale) || scale <= 0.0 || scale < planner->scale_minimum || scale > 1.0) return -1213;
|
|
planner->jog_scale = scale;
|
|
return 0;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_set_axis_limit(sim_linuxcnc_tp *planner, int axis, double minimum, double maximum) {
|
|
if (planner == NULL || !planner->initialized || axis < 0 || axis >= 9 || !isfinite(minimum) || !isfinite(maximum) || minimum >= maximum) return -1214;
|
|
planner->axis_min[axis] = minimum;
|
|
planner->axis_max[axis] = maximum;
|
|
return 0;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_set_axis_dynamics(sim_linuxcnc_tp *planner, int axis, double velocity, double acceleration) {
|
|
if (planner == NULL || !planner->initialized || axis < 0 || axis >= 9 || !isfinite(velocity) || velocity <= 0.0 || !isfinite(acceleration) || acceleration <= 0.0) return -1214;
|
|
planner->axis_velocity[axis] = velocity;
|
|
planner->axis_acceleration[axis] = acceleration;
|
|
return 0;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_set_feed_limit(sim_linuxcnc_tp *planner, double maximum) {
|
|
if (planner == NULL || !planner->initialized || !isfinite(maximum) || maximum < 1.0) return -1207;
|
|
planner->config.maxFeedScale = maximum;
|
|
return 0;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_set_scale_minimum(sim_linuxcnc_tp *planner, double minimum) {
|
|
if (planner == NULL || !planner->initialized || !isfinite(minimum) || minimum <= 0.0 || minimum > 1.0) return -1211;
|
|
planner->scale_minimum = minimum;
|
|
return 0;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_set_blend(sim_linuxcnc_tp *planner, int enable, int fallback, int depth, int gap, double ramp, double kink) {
|
|
if (planner == NULL || !planner->initialized || (enable != 0 && enable != 1) || (fallback != 0 && fallback != 1) || depth < 1 || gap < 0 || !isfinite(ramp) || ramp <= 0.0 || !isfinite(kink) || kink <= 0.0) return -1205;
|
|
planner->config.arcBlendEnable = enable;
|
|
planner->config.arcBlendFallbackEnable = fallback;
|
|
planner->config.arcBlendOptDepth = depth;
|
|
planner->config.arcBlendGapCycles = gap;
|
|
planner->config.arcBlendRampFreq = ramp;
|
|
planner->config.arcBlendTangentKinkRatio = kink;
|
|
return 0;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_jog(sim_linuxcnc_tp *planner, int axis, int direction, double increment, uint32_t tag) {
|
|
EmcPose end;
|
|
double *value;
|
|
double velocity;
|
|
struct state_tag_t state_tag = {0};
|
|
if (planner == NULL || !planner->initialized || axis < 0 || axis >= 9 || (direction != -1 && direction != 1) || !isfinite(increment) || increment < 0.0) return -1215;
|
|
active_planner = planner;
|
|
end = planner->tp.goalPos;
|
|
value = axis == 0 ? &end.tran.x : axis == 1 ? &end.tran.y : axis == 2 ? &end.tran.z : axis == 3 ? &end.a : axis == 4 ? &end.b : axis == 5 ? &end.c : axis == 6 ? &end.u : axis == 7 ? &end.v : &end.w;
|
|
// command.c::EMCMOT_JOG_CONT targets the directional jog limit until abort.
|
|
// Zero is the continuous-mode encoding; positive increments retain their ABI.
|
|
if (increment == 0.0) {
|
|
double target = direction > 0 ? planner->axis_max[axis] : planner->axis_min[axis];
|
|
if ((target - *value) * direction <= 0.0) return -1217;
|
|
*value = target;
|
|
} else {
|
|
*value += (double)direction * increment;
|
|
}
|
|
if (!isfinite(*value) || *value < planner->axis_min[axis] || *value > planner->axis_max[axis]) return -1217;
|
|
velocity = planner->axis_velocity[axis] * planner->jog_scale;
|
|
state_tag.fields_float[GM_FIELD_FLOAT_LINE_NUMBER] = (float)tag;
|
|
if (tpSetId(&planner->tp, (int)tag) != TP_ERR_OK) return -1218;
|
|
return tpAddLine(&planner->tp, end, TC_LINEAR, velocity, velocity, planner->axis_acceleration[axis], planner->status.enables_new, 0, -1, state_tag) == TP_ERR_OK ? 0 : -1219;
|
|
}
|
|
|
|
double sim_linuxcnc_tp_velocity_scale(const sim_linuxcnc_tp *planner) {
|
|
return planner == NULL || !planner->initialized ? 0.0 : planner->velocity_scale;
|
|
}
|
|
|
|
double sim_linuxcnc_tp_velocity_limit(const sim_linuxcnc_tp *planner) {
|
|
return planner == NULL || !planner->initialized ? 0.0 : planner->tp.vLimit;
|
|
}
|
|
|
|
double sim_linuxcnc_tp_velocity_mm_min(const sim_linuxcnc_tp *planner) {
|
|
return planner == NULL || !planner->initialized ? 0.0 : planner->status.current_vel * 60.0;
|
|
}
|
|
|
|
double sim_linuxcnc_tp_jog_scale(const sim_linuxcnc_tp *planner) {
|
|
return planner == NULL || !planner->initialized ? 0.0 : planner->jog_scale;
|
|
}
|
|
|
|
double sim_linuxcnc_tp_jog_velocity(const sim_linuxcnc_tp *planner, int axis) {
|
|
return planner == NULL || !planner->initialized || axis < 0 || axis >= 9 ? 0.0 : planner->axis_velocity[axis] * planner->jog_scale;
|
|
}
|
|
|
|
double sim_linuxcnc_tp_feed_override(const sim_linuxcnc_tp *planner) {
|
|
return planner == NULL || !planner->initialized ? 0.0 : planner->status.net_feed_scale;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_set_dynamics(sim_linuxcnc_tp *planner, double acceleration_limit) {
|
|
if (planner == NULL || !planner->initialized || !isfinite(acceleration_limit) || acceleration_limit <= 0.0) return -1217;
|
|
active_planner = planner;
|
|
for (int axis = 0; axis < 9; ++axis) planner->axis_acceleration[axis] = acceleration_limit;
|
|
return tpSetAmax(&planner->tp, acceleration_limit) == TP_ERR_OK ? 0 : -1218;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_set_paused(sim_linuxcnc_tp *planner, int paused) {
|
|
if (planner == NULL || !planner->initialized || (paused != 0 && paused != 1)) return -1219;
|
|
active_planner = planner;
|
|
planner->status.stepping = 0;
|
|
if (paused) return tpPause(&planner->tp) == TP_ERR_OK ? 0 : -1220;
|
|
return tpResume(&planner->tp) == TP_ERR_OK ? 0 : -1221;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_step_line(sim_linuxcnc_tp *planner) {
|
|
if (planner == NULL || !planner->initialized) return -1219;
|
|
TC_STRUCT *active = tcqItem(&planner->tp.queue, 0);
|
|
if (active == NULL) return -1219;
|
|
active_planner = planner;
|
|
planner->step_id = active->id;
|
|
planner->status.stepping = 1;
|
|
return tpResume(&planner->tp) == TP_ERR_OK ? 0 : -1221;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_is_stepping(const sim_linuxcnc_tp *planner) {
|
|
return planner != NULL && planner->initialized && planner->status.stepping;
|
|
}
|
|
|
|
double sim_linuxcnc_tp_axis_limit(const sim_linuxcnc_tp *planner, int axis, int maximum) {
|
|
if (planner == NULL || !planner->initialized || axis < 0 || axis > 8) return NAN;
|
|
return maximum ? planner->axis_max[axis] : planner->axis_min[axis];
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_abort(sim_linuxcnc_tp *planner) {
|
|
if (planner == NULL || !planner->initialized) return -1222;
|
|
active_planner = planner;
|
|
planner->last_active_id = 0;
|
|
planner->just_completed = 0;
|
|
planner->status.stepping = 0;
|
|
if (tpAbort(&planner->tp) != TP_ERR_OK) return -1223;
|
|
// A browser PROGRAM_ABORT has no background emcmot servo thread after
|
|
// the command response. Complete the source TP abort transition here so
|
|
// the authoritative snapshot cannot retain an active queue indefinitely.
|
|
return tpClear(&planner->tp) == TP_ERR_OK ? 0 : -1223;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_set_position(sim_linuxcnc_tp *planner, const sim_posemath_pose *position) {
|
|
if (planner == NULL || !planner->initialized || position == NULL) return -1208;
|
|
active_planner = planner;
|
|
EmcPose pose = to_emc_pose(position);
|
|
return tpSetPos(&planner->tp, &pose) == TP_ERR_OK ? 0 : -1209;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_set_goal_position(sim_linuxcnc_tp *planner, const sim_posemath_pose *position) {
|
|
if (planner == NULL || !planner->initialized || position == NULL) return -1228;
|
|
planner->tp.goalPos = to_emc_pose(position);
|
|
return 0;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_add_line(sim_linuxcnc_tp *planner, const sim_posemath_pose *end, double velocity, double acceleration, uint32_t tag, int canon_motion_type) {
|
|
if (planner == NULL || !planner->initialized || end == NULL || !isfinite(velocity) || velocity < 0.0 || !isfinite(acceleration) || acceleration < 0.0) return -1210;
|
|
active_planner = planner;
|
|
struct state_tag_t state_tag = {0}; state_tag.fields_float[GM_FIELD_FLOAT_LINE_NUMBER] = (float)tag;
|
|
EmcPose pose = to_emc_pose(end), start = planner->tp.goalPos;
|
|
xyzac_line_limits(planner, &start, &pose, &velocity, &acceleration);
|
|
if (tpSetId(&planner->tp, (int)tag) != TP_ERR_OK) return -1211;
|
|
return tpAddLine(&planner->tp, pose, canon_motion_type, velocity, velocity, acceleration, planner->status.enables_new, 0, -1, state_tag) == TP_ERR_OK ? 0 : -1211;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_add_circle(sim_linuxcnc_tp *planner, const sim_posemath_pose *end, const sim_posemath_cartesian *center, const sim_posemath_cartesian *normal, int turn, double velocity, double acceleration, uint32_t tag, int canon_motion_type) {
|
|
if (planner == NULL || !planner->initialized || end == NULL || center == NULL || normal == NULL || !isfinite(velocity) || velocity < 0.0 || !isfinite(acceleration) || acceleration < 0.0) return -1212;
|
|
active_planner = planner;
|
|
struct state_tag_t state_tag = {0}; state_tag.fields_float[GM_FIELD_FLOAT_LINE_NUMBER] = (float)tag;
|
|
EmcPose pose = to_emc_pose(end), start = planner->tp.goalPos; PmCartesian pm_center = to_pm_cart(center); PmCartesian pm_normal = to_pm_cart(normal);
|
|
double initial_velocity = velocity;
|
|
xyzac_circle_limits(planner, &start, &pose, &pm_center, &pm_normal, turn, &velocity, &initial_velocity, &acceleration);
|
|
if (tpSetId(&planner->tp, (int)tag) != TP_ERR_OK) return -1212;
|
|
// emccanon computes ini_maxvel independently of the programmed feed for
|
|
// ARC_FEED. For this XYZAC case the planar XYZ limit (20 mm/s) is the
|
|
// governing bound; passing the feed here loses the native corner ramp.
|
|
return tpAddCircle(&planner->tp, pose, pm_center, pm_normal, turn, canon_motion_type, velocity, initial_velocity, acceleration, planner->status.enables_new, 0, state_tag) == TP_ERR_OK ? 0 : -1213;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_step(sim_linuxcnc_tp *planner) {
|
|
if (planner == NULL || !planner->initialized) return -1214;
|
|
active_planner = planner;
|
|
planner->just_completed = 0;
|
|
const int prior_id = tpGetExecId(&planner->tp);
|
|
const int prior_depth = tcqLen(&planner->tp.queue);
|
|
const int was_stepping = planner->status.stepping;
|
|
const int result = tpRunCycle(&planner->tp, (long)llround(planner->tp.cycleTime * 1e9));
|
|
// motion/control.c stops a step when the execution ID changes. Compare
|
|
// the next queued ID too, before the following TC can start moving.
|
|
TC_STRUCT *next = tcqItem(&planner->tp.queue, 0);
|
|
if (planner->status.stepping && (next == NULL || next->id != planner->step_id)) {
|
|
tpPause(&planner->tp);
|
|
planner->status.stepping = 0;
|
|
}
|
|
planner->tick += 1u;
|
|
if (!was_stepping && prior_id > 0 && prior_depth > 0 && tcqLen(&planner->tp.queue) == 0) {
|
|
planner->last_active_id = (uint32_t)prior_id;
|
|
planner->just_completed = 1;
|
|
}
|
|
return result >= TP_ERR_OK && result <= TP_ERR_WAITING ? 0 : result;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_idle(sim_linuxcnc_tp *planner) {
|
|
if (planner == NULL || !planner->initialized) return -1227;
|
|
planner->just_completed = 0;
|
|
planner->tick += 1u;
|
|
return 0;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_read_state(const sim_linuxcnc_tp *planner, sim_tp_state *state) {
|
|
if (planner == NULL || !planner->initialized || state == NULL) return -1215;
|
|
EmcPose position = {0};
|
|
if (tpGetPos(&planner->tp, &position) != TP_ERR_OK) return -1216;
|
|
memset(state, 0, sizeof(*state)); state->position = from_emc_pose(&position); state->velocity = planner->status.current_vel;
|
|
TC_STRUCT *active = tcqItem(&planner->tp.queue, 0);
|
|
if (active != NULL && active->target > 0.0) state->progress = active->progress / active->target;
|
|
// tpQueueDepth() exposes TP's legacy cached depth, which is only updated
|
|
// when appending a TC. Native emcmot reports the live tcq length after a
|
|
// segment pop, so use the upstream queue directly for oracle parity.
|
|
state->tick = planner->tick;
|
|
const uint32_t live_depth = (uint32_t)tcqLen(&planner->tp.queue);
|
|
state->queue_depth = live_depth > 0u ? live_depth : planner->just_completed ? 1u : 0u;
|
|
{
|
|
const int exec_id = tpGetExecId((TP_STRUCT *)&planner->tp);
|
|
state->active_id = state->queue_depth > 0 && exec_id > 0 ? (uint32_t)exec_id : planner->just_completed ? planner->last_active_id : 0u;
|
|
}
|
|
state->motion_type = tpGetMotionType((TP_STRUCT *)&planner->tp);
|
|
state->status = tpIsDone((TP_STRUCT *)&planner->tp) ? 0 : 1; return 0;
|
|
}
|
|
|
|
uint32_t sim_linuxcnc_tp_queue_depth(const sim_linuxcnc_tp *planner) {
|
|
return planner == NULL || !planner->initialized ? 0u : (uint32_t)tcqLen(&planner->tp.queue);
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_queue_segment(const sim_linuxcnc_tp *planner, uint32_t index, sim_linuxcnc_tp_segment *segment) {
|
|
TC_STRUCT *tc;
|
|
EmcPose start = {0}, end = {0};
|
|
if (planner == NULL || !planner->initialized || segment == NULL) return -1224;
|
|
tc = tcqItem(&planner->tp.queue, (int)index);
|
|
if (tc == NULL || tcGetPosReal(tc, TC_GET_STARTPOINT, &start) != TP_ERR_OK || tcGetPosReal(tc, TC_GET_ENDPOINT, &end) != TP_ERR_OK) return -1225;
|
|
memset(segment, 0, sizeof(*segment));
|
|
segment->segment.start = from_emc_pose(&start);
|
|
segment->segment.end = from_emc_pose(&end);
|
|
segment->segment.velocity = tc->reqvel;
|
|
segment->segment.acceleration = tc->maxaccel;
|
|
segment->segment.motion_type = tc->motion_type;
|
|
if (tc->motion_type == TC_CIRCULAR) {
|
|
const double turns = fabs(tc->coords.circle.xyz.angle) / (2.0 * M_PI);
|
|
segment->segment.turn = (tc->coords.circle.xyz.angle < 0.0 ? -1 : 1) * (turns >= 1.0 ? (int)llround(turns) : 1);
|
|
}
|
|
if (tc->motion_type == TC_CIRCULAR) {
|
|
segment->segment.center.x = tc->coords.circle.xyz.center.x;
|
|
segment->segment.center.y = tc->coords.circle.xyz.center.y;
|
|
segment->segment.center.z = tc->coords.circle.xyz.center.z;
|
|
segment->segment.normal.x = tc->coords.circle.xyz.normal.x;
|
|
segment->segment.normal.y = tc->coords.circle.xyz.normal.y;
|
|
segment->segment.normal.z = tc->coords.circle.xyz.normal.z;
|
|
} else {
|
|
segment->segment.normal.z = 1.0;
|
|
}
|
|
segment->id = tc->id;
|
|
segment->term_cond = tc->term_cond;
|
|
segment->progress = tc->progress;
|
|
segment->target = tc->target;
|
|
return 0;
|
|
}
|
|
|
|
int32_t sim_linuxcnc_tp_restore_progress(sim_linuxcnc_tp *planner, const sim_posemath_pose *position, double velocity, uint32_t tick) {
|
|
if (planner == NULL || !planner->initialized || position == NULL || !isfinite(velocity) || velocity < 0.0) return -1226;
|
|
active_planner = planner;
|
|
planner->tp.currentPos = to_emc_pose(position);
|
|
planner->status.current_vel = velocity;
|
|
planner->tick = tick;
|
|
return 0;
|
|
}
|
|
|
|
uint64_t sim_linuxcnc_tp_state_hash(const sim_linuxcnc_tp *planner) {
|
|
sim_tp_state state; if (sim_linuxcnc_tp_read_state(planner, &state) != 0) return 0; return hash_bytes((const unsigned char *)&state, sizeof(state));
|
|
}
|
|
|
|
uint32_t sim_linuxcnc_tp_canonical(const sim_linuxcnc_tp *planner, char *output, uint32_t capacity) {
|
|
sim_tp_state state; int written;
|
|
if (sim_linuxcnc_tp_read_state(planner, &state) != 0) return 0;
|
|
written = snprintf(output, capacity, "{\"active_id\":%u,\"motion_type\":%d,\"position\":{\"a\":%.17g,\"c\":%.17g,\"x\":%.17g,\"y\":%.17g,\"z\":%.17g},\"queue_depth\":%u,\"schema\":\"linuxcnc-source-tp.v1\",\"tick\":%u,\"velocity\":%.17g}", state.active_id, state.motion_type, state.position.a, state.position.c, state.position.tran.x, state.position.tran.y, state.position.tran.z, state.queue_depth, state.tick, state.velocity);
|
|
return written < 0 ? 0 : (uint32_t)written + 1u;
|
|
}
|