Skip to content

Commit 4e5ceea

Browse files
committed
Collinear segment consolidation for CAM toolpaths
When a new LINE segment is collinear with the previous LINE in the queue (< 1 degree angle, same feed rate, same motion type), extend the previous segment instead of creating a new segment boundary. This reduces segment count, eliminates split cycles at collinear junctions, and produces smoother velocity profiles for CAM-generated toolpaths that approximate curves as chains of tiny line segments. Guarded out in G61.1 (exact stop) mode where every junction must decelerate to zero.
1 parent c50bcd5 commit 4e5ceea

1 file changed

Lines changed: 103 additions & 1 deletion

File tree

src/emc/motion_planning/motion_planning_9d_userspace.cc

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ extern "C" {
2626
#include "tc.h"
2727
#include "tc_types.h"
2828
#include "emcpos.h"
29+
#include "emcpose.h" // pmCartesianToEmcPose
2930
#include "posemath.h"
3031
#include "blendmath.h" // pmCircleEffectiveMinRadius, findIntersectionAngle
3132
#include "atomic_9d.h" // atomicStoreDouble
@@ -1035,14 +1036,70 @@ static void tpComputeKinkVelocity_9D(TP_STRUCT *tp, TC_QUEUE_STRUCT *queue,
10351036
TC_PLAN_UNTOUCHED, __ATOMIC_RELEASE);
10361037
}
10371038

1039+
/**
1040+
* @brief Check if two LINE segments can be consolidated (merged).
1041+
*
1042+
* Two adjacent LINE segments are consolidatable when they have:
1043+
* - Same motion type (both TC_LINEAR)
1044+
* - Same canon motion type (don't merge G0 with G1)
1045+
* - Compatible feed rate (same F word)
1046+
* - Collinear direction in all active 9D subspaces (< 1° angle)
1047+
*
1048+
* @return 1 if segments can be merged, 0 otherwise
1049+
*/
1050+
static int canConsolidateLines(TC_STRUCT const *prev_tc, TC_STRUCT const *tc)
1051+
{
1052+
if (prev_tc->motion_type != TC_LINEAR || tc->motion_type != TC_LINEAR)
1053+
return 0;
1054+
1055+
if (prev_tc->canon_motion_type != tc->canon_motion_type)
1056+
return 0;
1057+
1058+
if (fabs(prev_tc->reqvel - tc->reqvel) > 1e-6)
1059+
return 0;
1060+
1061+
// cos(1°) ≈ 0.99985 — matches BLEND9_MIN_THETA threshold
1062+
double const cos_thresh = 0.99985;
1063+
double dot;
1064+
1065+
// XYZ subspace
1066+
if (!prev_tc->coords.line.xyz.tmag_zero && !tc->coords.line.xyz.tmag_zero) {
1067+
pmCartCartDot(&prev_tc->coords.line.xyz.uVec,
1068+
&tc->coords.line.xyz.uVec, &dot);
1069+
if (dot < cos_thresh) return 0;
1070+
} else if (prev_tc->coords.line.xyz.tmag_zero != tc->coords.line.xyz.tmag_zero) {
1071+
return 0;
1072+
}
1073+
1074+
// ABC subspace
1075+
if (!prev_tc->coords.line.abc.tmag_zero && !tc->coords.line.abc.tmag_zero) {
1076+
pmCartCartDot(&prev_tc->coords.line.abc.uVec,
1077+
&tc->coords.line.abc.uVec, &dot);
1078+
if (dot < cos_thresh) return 0;
1079+
} else if (prev_tc->coords.line.abc.tmag_zero != tc->coords.line.abc.tmag_zero) {
1080+
return 0;
1081+
}
1082+
1083+
// UVW subspace
1084+
if (!prev_tc->coords.line.uvw.tmag_zero && !tc->coords.line.uvw.tmag_zero) {
1085+
pmCartCartDot(&prev_tc->coords.line.uvw.uVec,
1086+
&tc->coords.line.uvw.uVec, &dot);
1087+
if (dot < cos_thresh) return 0;
1088+
} else if (prev_tc->coords.line.uvw.tmag_zero != tc->coords.line.uvw.tmag_zero) {
1089+
return 0;
1090+
}
1091+
1092+
return 1;
1093+
}
1094+
10381095
/**
10391096
* @brief Add linear move to shared memory queue (userspace planning)
10401097
*
10411098
* - Initialize TC_STRUCT
10421099
* - Set geometry
1100+
* - Consolidate with previous segment if collinear
10431101
* - Write to queue
10441102
* - Trigger optimization
1045-
* - NO blending (requires blend geometry implementation)
10461103
*/
10471104
extern "C" int tpAddLine_9D(
10481105
TP_STRUCT * const tp,
@@ -1098,6 +1155,51 @@ extern "C" int tpAddLine_9D(
10981155
return 0;
10991156
}
11001157

1158+
// Collinear segment consolidation: if the new LINE goes the same direction
1159+
// as the previous LINE, extend the previous segment instead of creating a
1160+
// new segment boundary. Eliminates split cycles, blends, and velocity
1161+
// sawtooth in CAM-generated toolpaths with many tiny collinear segments.
1162+
if (tp->termCond != TC_TERM_COND_STOP) { // G61.1 must stop at every junction
1163+
TC_STRUCT *prev_tc = tcqLast_user(queue);
1164+
if (prev_tc && canConsolidateLines(prev_tc, &tc)) {
1165+
// Extend prev_tc geometry: keep current start, use new end
1166+
pmCartLineInit(&prev_tc->coords.line.xyz,
1167+
&prev_tc->coords.line.xyz.start,
1168+
&tc.coords.line.xyz.end);
1169+
pmCartLineInit(&prev_tc->coords.line.abc,
1170+
&prev_tc->coords.line.abc.start,
1171+
&tc.coords.line.abc.end);
1172+
pmCartLineInit(&prev_tc->coords.line.uvw,
1173+
&prev_tc->coords.line.uvw.start,
1174+
&tc.coords.line.uvw.end);
1175+
1176+
// Update lengths
1177+
prev_tc->target = pmLine9Target(&prev_tc->coords.line);
1178+
prev_tc->nominal_length = prev_tc->target;
1179+
1180+
// Recompute joint-space segment for extended geometry
1181+
if (motion_planning::userspace_kins_is_enabled()) {
1182+
EmcPose start_pose;
1183+
pmCartesianToEmcPose(&prev_tc->coords.line.xyz.start,
1184+
&prev_tc->coords.line.abc.start,
1185+
&prev_tc->coords.line.uvw.start,
1186+
&start_pose);
1187+
if (motion_planning::userspace_kins_compute_joint_segment(
1188+
&start_pose, &end_pose, prev_tc) != 0) {
1189+
prev_tc->joint_space.valid = 0;
1190+
}
1191+
}
1192+
1193+
// Mark for re-optimization (geometry changed)
1194+
__atomic_store_n((int*)&prev_tc->shared_9d.optimization_state,
1195+
TC_PLAN_UNTOUCHED, __ATOMIC_RELEASE);
1196+
1197+
tp->goalPos = end_pose;
1198+
triggerAdaptiveOptimization_9D(tp);
1199+
return 0; // No new segment needed
1200+
}
1201+
}
1202+
11011203
// Set termination condition from G-code mode (G61/G61.1/G64):
11021204
// G61.1 (STOP): decelerate to zero at every junction
11031205
// G61 (EXACT): exact path with kink velocity

0 commit comments

Comments
 (0)