Skip to content

Commit 43db874

Browse files
committed
Blending refinements
1 parent 956b4d6 commit 43db874

5 files changed

Lines changed: 236 additions & 115 deletions

File tree

src/emc/motion_planning/blend_sizing.cc

Lines changed: 120 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -535,9 +535,14 @@ double findMaxBlendRegion9(TC_STRUCT const * const prev_tc,
535535
return 0.0;
536536
}
537537

538-
/* Per-segment usable length */
539-
double l_prev = fmin(prev_tc->target, prev_tc->nominal_length / 2.0);
540-
double l_tc = fmin(tc->target, tc->nominal_length / 2.0);
538+
/* Per-segment usable length.
539+
* Each blend can claim up to BLEND9_MAX_SEGMENT_USE of the original
540+
* segment, so two blends on the same segment leave a guaranteed
541+
* remnant (≥10% at default 0.45). Unit-free, works in mm or inches. */
542+
double l_prev = fmin(prev_tc->target,
543+
prev_tc->nominal_length * BLEND9_MAX_SEGMENT_USE);
544+
double l_tc = fmin(tc->target,
545+
tc->nominal_length * BLEND9_MAX_SEGMENT_USE);
541546

542547
/* For circular arcs, limit to 60 degrees */
543548
if (prev_tc->motion_type == TC_CIRCULAR) {
@@ -706,6 +711,99 @@ int findBlendParameters9(BlendBoundary9 const * const boundary,
706711
return TP_ERR_OK;
707712
}
708713

714+
/**
715+
* Golden section search for optimal alpha (control point distance).
716+
*
717+
* bezier9AccLimit(alpha) = min(v_curvature, v_dkds) is unimodal in alpha:
718+
* - Small alpha → steep curvature ramp at endpoints → high dκ/ds → low v_dkds
719+
* - Large alpha → sharp peak curvature at midpoint → high κ_max → low v_curvature
720+
* The maximum is where the two limits cross.
721+
*
722+
* Returns the best velocity limit, stores the winning Bezier9 and alpha.
723+
*/
724+
static double findOptimalAlpha9(double Rb,
725+
BlendBoundary9 const * const boundary,
726+
double a_max,
727+
double j_max,
728+
double v_goal,
729+
Bezier9 * const best_bezier,
730+
double * const best_alpha)
731+
{
732+
double a_lo = Rb * BLEND9_ALPHA_MIN_RATIO;
733+
double a_hi = Rb * BLEND9_ALPHA_MAX_RATIO;
734+
735+
double gr = BEZIER9_GOLDEN_RATIO;
736+
double a1 = a_hi - gr * (a_hi - a_lo);
737+
double a2 = a_lo + gr * (a_hi - a_lo);
738+
739+
/* Evaluate at initial probe points */
740+
Bezier9 trial1, trial2;
741+
double v1 = 0.0, v2 = 0.0;
742+
743+
if (bezier9Init(&trial1, &boundary->P_start, &boundary->P_end,
744+
&boundary->u_start_xyz, &boundary->u_end_xyz,
745+
&boundary->u_start_abc, &boundary->u_end_abc,
746+
&boundary->u_start_uvw, &boundary->u_end_uvw,
747+
a1) == TP_ERR_OK) {
748+
v1 = bezier9AccLimit(&trial1, v_goal, a_max, j_max);
749+
}
750+
751+
if (bezier9Init(&trial2, &boundary->P_start, &boundary->P_end,
752+
&boundary->u_start_xyz, &boundary->u_end_xyz,
753+
&boundary->u_start_abc, &boundary->u_end_abc,
754+
&boundary->u_start_uvw, &boundary->u_end_uvw,
755+
a2) == TP_ERR_OK) {
756+
v2 = bezier9AccLimit(&trial2, v_goal, a_max, j_max);
757+
}
758+
759+
for (int i = 0; i < BLEND9_ALPHA_SEARCH_ITERS; i++) {
760+
if (v1 < v2) {
761+
/* Maximum is in [a1, a_hi] */
762+
a_lo = a1;
763+
a1 = a2;
764+
v1 = v2;
765+
trial1 = trial2;
766+
a2 = a_lo + gr * (a_hi - a_lo);
767+
if (bezier9Init(&trial2, &boundary->P_start, &boundary->P_end,
768+
&boundary->u_start_xyz, &boundary->u_end_xyz,
769+
&boundary->u_start_abc, &boundary->u_end_abc,
770+
&boundary->u_start_uvw, &boundary->u_end_uvw,
771+
a2) == TP_ERR_OK) {
772+
v2 = bezier9AccLimit(&trial2, v_goal, a_max, j_max);
773+
} else {
774+
v2 = 0.0;
775+
}
776+
} else {
777+
/* Maximum is in [a_lo, a2] */
778+
a_hi = a2;
779+
a2 = a1;
780+
v2 = v1;
781+
trial2 = trial1;
782+
a1 = a_hi - gr * (a_hi - a_lo);
783+
if (bezier9Init(&trial1, &boundary->P_start, &boundary->P_end,
784+
&boundary->u_start_xyz, &boundary->u_end_xyz,
785+
&boundary->u_start_abc, &boundary->u_end_abc,
786+
&boundary->u_start_uvw, &boundary->u_end_uvw,
787+
a1) == TP_ERR_OK) {
788+
v1 = bezier9AccLimit(&trial1, v_goal, a_max, j_max);
789+
} else {
790+
v1 = 0.0;
791+
}
792+
}
793+
}
794+
795+
/* Pick the better of the two final probes */
796+
if (v1 >= v2) {
797+
*best_bezier = trial1;
798+
*best_alpha = a1;
799+
return v1;
800+
} else {
801+
*best_bezier = trial2;
802+
*best_alpha = a2;
803+
return v2;
804+
}
805+
}
806+
709807
/**
710808
* Binary search optimizer for blend region size.
711809
*
@@ -722,6 +820,7 @@ int optimizeBlendSize9(TC_STRUCT const * const prev_tc,
722820
double max_feed_scale,
723821
AxisBounds9 const * const vel_bounds,
724822
AxisBounds9 const * const acc_bounds,
823+
double j_max,
725824
BlendSolution9 * const result)
726825
{
727826
if (!prev_tc || !tc || !result) {
@@ -757,6 +856,10 @@ int optimizeBlendSize9(TC_STRUCT const * const prev_tc,
757856
return res;
758857
}
759858

859+
/* Kinematic limits (constant across iterations) */
860+
double a_max_blend = fmin(prev_tc->maxaccel, tc->maxaccel);
861+
double j_max_blend = j_max;
862+
760863
/* Binary search between epsilon and Rb_max */
761864
double Rb_lo = TP_POS_EPSILON;
762865
double Rb_hi = Rb_max;
@@ -780,33 +883,20 @@ int optimizeBlendSize9(TC_STRUCT const * const prev_tc,
780883
continue;
781884
}
782885

783-
/* Construct trial Bezier9 */
784-
double alpha = Rb * BLEND9_ALPHA_FACTOR;
886+
/* Find optimal alpha for this Rb via golden section search */
785887
Bezier9 trial;
786-
res = bezier9Init(&trial,
787-
&boundary.P_start,
788-
&boundary.P_end,
789-
&boundary.u_start_xyz,
790-
&boundary.u_end_xyz,
791-
&boundary.u_start_abc,
792-
&boundary.u_end_abc,
793-
&boundary.u_start_uvw,
794-
&boundary.u_end_uvw,
795-
alpha);
796-
if (res != TP_ERR_OK) {
888+
double alpha;
889+
double v_limit = findOptimalAlpha9(Rb, &boundary, a_max_blend,
890+
j_max_blend, params.v_goal,
891+
&trial, &alpha);
892+
if (v_limit <= 0.0) {
797893
Rb_hi = Rb;
798894
continue;
799895
}
800896

801897
/* Check deviation against tolerance */
802898
double deviation = bezier9Deviation(&trial, &boundary.intersection_point);
803899

804-
/* Check velocity limit from curvature and curvature rate */
805-
double a_max_blend = fmin(prev_tc->maxaccel, tc->maxaccel);
806-
double j_max_blend = fmin(prev_tc->maxjerk, tc->maxjerk);
807-
double v_limit = bezier9AccLimit(&trial, params.v_goal, a_max_blend,
808-
j_max_blend);
809-
810900
tp_debug_print(" iter=%d Rb=%g alpha=%g dev=%g tol=%g v_limit=%g v_goal=%g len=%g\n",
811901
iter, Rb, alpha, deviation, tolerance, v_limit, params.v_goal,
812902
bezier9Length(&trial));
@@ -858,25 +948,14 @@ int optimizeBlendSize9(TC_STRUCT const * const prev_tc,
858948
vel_bounds, acc_bounds, &params);
859949
}
860950
if (res == TP_ERR_OK) {
861-
double alpha = Rb * BLEND9_ALPHA_FACTOR;
862951
Bezier9 trial;
863-
res = bezier9Init(&trial,
864-
&boundary.P_start,
865-
&boundary.P_end,
866-
&boundary.u_start_xyz,
867-
&boundary.u_end_xyz,
868-
&boundary.u_start_abc,
869-
&boundary.u_end_abc,
870-
&boundary.u_start_uvw,
871-
&boundary.u_end_uvw,
872-
alpha);
873-
if (res == TP_ERR_OK) {
952+
double alpha;
953+
double v_limit = findOptimalAlpha9(Rb, &boundary, a_max_blend,
954+
j_max_blend, params.v_goal,
955+
&trial, &alpha);
956+
if (v_limit > 0.0) {
874957
double deviation = bezier9Deviation(&trial, &boundary.intersection_point);
875958
if (deviation <= tolerance) {
876-
double a_max_blend = fmin(prev_tc->maxaccel, tc->maxaccel);
877-
double j_max_blend = fmin(prev_tc->maxjerk, tc->maxjerk);
878-
double v_limit = bezier9AccLimit(&trial, params.v_goal,
879-
a_max_blend, j_max_blend);
880959
result->Rb = Rb;
881960
result->bezier = trial;
882961
result->boundary = boundary;
@@ -1043,7 +1122,9 @@ int trimSegment9(TC_STRUCT * const tc,
10431122
}
10441123

10451124
tc->target = new_target;
1046-
tc->nominal_length = new_target;
1125+
/* Keep nominal_length as original untrimmed length so that
1126+
* findMaxBlendRegion9's "half the original segment" guard
1127+
* gives equal Rb budget to blends on both sides. */
10471128
break;
10481129
}
10491130

@@ -1075,7 +1156,6 @@ int trimSegment9(TC_STRUCT * const tc,
10751156

10761157
/* Recompute target from new geometry */
10771158
tc->target = pmCircle9Target(&tc->coords.circle);
1078-
tc->nominal_length = tc->target;
10791159
break;
10801160
}
10811161

@@ -1129,7 +1209,6 @@ int trimSegment9(TC_STRUCT * const tc,
11291209

11301210
/* Update arc target */
11311211
tc->target = new_angle * arc->radius + arc->line_length * ratio;
1132-
tc->nominal_length = tc->target;
11331212
break;
11341213
}
11351214

src/emc/motion_planning/blend_sizing.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,19 @@ extern "C" {
2424

2525
/* Blend sizing constants */
2626
#define BLEND9_TOLERANCE_RATIO 0.5
27-
#define BLEND9_ALPHA_FACTOR 0.30
27+
#define BLEND9_ALPHA_MIN_RATIO 0.10
28+
#define BLEND9_ALPHA_MAX_RATIO 0.49
29+
#define BLEND9_ALPHA_SEARCH_ITERS 8
2830
#define BLEND9_MAX_ITERATIONS 12
2931
#define BLEND9_VEL_REL_TOL 0.01
3032
#define BLEND9_VEL_ABS_TOL 0.1
3133
#define BLEND9_CIRC_MAX_ANGLE (PM_PI / 3.0)
3234
#define BLEND9_MIN_THETA (PM_PI / 180.0)
35+
#define BLEND9_MAX_SEGMENT_USE 0.45 /* Max fraction of original segment
36+
* each blend may claim (unit-free).
37+
* Two blends leave ≥10% remnant,
38+
* preventing micro-segment jerk
39+
* spikes at segment junctions. */
3340

3441
/**
3542
* Per-axis velocity and acceleration bounds in 9D.
@@ -145,13 +152,17 @@ int findBlendParameters9(BlendBoundary9 const * const boundary,
145152
* Finds optimal Rb that satisfies both G64 P tolerance and velocity
146153
* constraints. Per-axis bounds limit blend velocity and acceleration
147154
* to stay within each axis's individual limits.
155+
*
156+
* @param j_max Jerk limit for curvature-rate optimization (per-joint minimum).
157+
* Pass 0 to skip curvature-rate limit (not recommended).
148158
*/
149159
int optimizeBlendSize9(TC_STRUCT const * const prev_tc,
150160
TC_STRUCT const * const tc,
151161
double tolerance,
152162
double max_feed_scale,
153163
AxisBounds9 const * const vel_bounds,
154164
AxisBounds9 const * const acc_bounds,
165+
double j_max,
155166
BlendSolution9 * const result);
156167

157168
/**

src/emc/motion_planning/motion_planning_9d.cc

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4171,23 +4171,6 @@ int computeRuckigProfiles_9D(TP_STRUCT *tp, TC_QUEUE_STRUCT *queue, int optimiza
41714171
// because no predecessor within the window sets prev_exit_vel.
41724172
{
41734173
TC_STRUCT *pre_window = tcqBack_user(queue, -optimization_depth);
4174-
{
4175-
static int seed_dbg = 0;
4176-
if (seed_dbg < 10) {
4177-
seed_dbg++;
4178-
rtapi_print_msg(RTAPI_MSG_ERR,
4179-
"SEED_DBG: pre_window=%p depth=%d term=%d pvalid=%d pfeed=%.3f "
4180-
"pv_exit=%.3f fv=%.3f type=%d id=%d\n",
4181-
pre_window, optimization_depth,
4182-
pre_window ? pre_window->term_cond : -1,
4183-
pre_window ? pre_window->shared_9d.profile.valid : 0,
4184-
pre_window ? pre_window->shared_9d.profile.computed_feed_scale : 0.0,
4185-
pre_window ? profileExitVelUnscaled(&pre_window->shared_9d.profile) : -1.0,
4186-
pre_window ? atomicLoadDouble(&pre_window->shared_9d.final_vel) : -1.0,
4187-
pre_window ? pre_window->motion_type : -1,
4188-
pre_window ? pre_window->id : -1);
4189-
}
4190-
}
41914174
if (pre_window && pre_window->term_cond == TC_TERM_COND_TANGENT
41924175
&& pre_window->shared_9d.profile.valid
41934176
&& pre_window->shared_9d.profile.computed_feed_scale > 0.001) {
@@ -4466,26 +4449,7 @@ int computeRuckigProfiles_9D(TP_STRUCT *tp, TC_QUEUE_STRUCT *queue, int optimiza
44664449
prev_exit_feed_scale = feed_scale;
44674450
prev_exit_vel_known = true;
44684451

4469-
// Debug: log profile params for blend segments and neighbors
4470-
{
4471-
static int blend_fwd_dbg = 0;
4472-
// Check if this or the next segment is a bezier
4473-
TC_STRUCT *next_tc = (k > 0) ? tcqBack_user(queue, -(k-1)) : NULL;
4474-
bool is_blend_neighbor = (next_tc && next_tc->motion_type == TC_BEZIER);
4475-
if ((tc->motion_type == TC_BEZIER || is_blend_neighbor) && blend_fwd_dbg < 50) {
4476-
blend_fwd_dbg++;
4477-
rtapi_print_msg(RTAPI_MSG_ERR,
4478-
"BLEND_FWD[id=%d type=%d]: v_in=%.3f v_out=%.3f "
4479-
"sc_in=%.3f sc_out=%.3f first=%d maxv=%.3f "
4480-
"target=%.4f pv0=%.3f pvf=%.3f prev_exit=%.3f\n",
4481-
tc->id, tc->motion_type, v_entry, v_exit,
4482-
scaled_v_entry, scaled_v_exit, is_first_profile,
4483-
max_vel, tc->target,
4484-
tc->shared_9d.profile.v[0],
4485-
tc->shared_9d.profile.v[RUCKIG_PROFILE_PHASES],
4486-
prev_exit_vel);
4487-
}
4488-
}
4452+
44894453

44904454
// One-step backtrack: fix backward reachability gap
44914455
// Skip active segment — rewriting triggers STOPWATCH_RESET

0 commit comments

Comments
 (0)