Skip to content

Commit 36de389

Browse files
tzuohannclaude
andcommitted
task: run from line -- nothing a skipped line produces leaves the queue (#246)
A wait (M66, G38, M6 epilog, M61, user M-codes) inside a subroutine or remap on a skipped line sent the queued commands to motion before the skip discarded them, so skipped code half-executed. While skipping, discard at every call level and let a wait neither send nor wait. The skip ends before the first main-program line at or after the start line (this also fixes run from line 1). #<_task_skipping> lets remaps skip their own work. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 3a7faff commit 36de389

16 files changed

Lines changed: 380 additions & 54 deletions

File tree

‎src/emc/rs274ngc/interp_namedparams.cc‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ namespace bp = boost::python;
5555

5656
using namespace linuxcnc;
5757

58+
// 1 while task skips lines before a run-from-line start line (issue #246).
59+
// Only milltask sets it; the preview and standalone rs274 leave it 0.
60+
int _task_skipping = 0;
61+
5862
enum predefined_named_parameters {
5963
NP_LINE,
6064
NP_MOTION_MODE,
@@ -114,6 +118,7 @@ enum predefined_named_parameters {
114118
NP_SELECTED_TOOL,
115119
NP_VALUE_RETURNED,
116120
NP_TASK,
121+
NP_TASK_SKIPPING,
117122
};
118123

119124
/****************************************************************************/
@@ -859,6 +864,11 @@ int Interp::lookup_named_param(const char *nameBuf,
859864
*value = _task;
860865
break;
861866

867+
case NP_TASK_SKIPPING:
868+
// 1 while task skips lines before a run-from-line start line (issue #246)
869+
*value = _task_skipping;
870+
break;
871+
862872
default:
863873
ERS(_("BUG: lookup_named_param(%s): unhandled index=%fn"),
864874
nameBuf,index);
@@ -1031,6 +1041,7 @@ int Interp::init_named_parameters()
10311041

10321042
// predicate: 1 in milltask instance, 0 in UI - control preview behaviour
10331043
init_readonly_param("_task", NP_TASK, PA_USE_LOOKUP);
1044+
init_readonly_param("_task_skipping", NP_TASK_SKIPPING, PA_USE_LOOKUP);
10341045

10351046
// debugging aids
10361047
init_readonly_param("_call_level", NP_CALL_LEVEL, PA_USE_LOOKUP);

‎src/emc/task/emctaskmain.cc‎

Lines changed: 76 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,43 @@ extern void emcTaskQueueTaskPlanSynchCmd();
474474

475475
static EMC_TASK_INTERP interpResumeState = EMC_TASK_INTERP::IDLE;
476476
static int programStartLine = 0; // which line to run program from
477+
// RUN FROM LINE (issue #246): 1 from an AUTO_RUN with a start line until the
478+
// skip ends. While it is set, nothing a skipped line produces -- at any call
479+
// level -- may leave interp_list: a queue-busting wait inside a remap or
480+
// subroutine used to send the queued commands to the machine before the skip
481+
// discarded them, so skipped code half-executed. Read by the interpreter as
482+
// #<_task_skipping> so a remap can skip its own side effects.
483+
extern int _task_skipping; // defined in the interpreter library
484+
485+
static void skip_end(void)
486+
{
487+
if (!_task_skipping) return;
488+
_task_skipping = 0;
489+
programStartLine = 0;
490+
}
491+
492+
static void skip_reset(void)
493+
{
494+
_task_skipping = 0;
495+
}
496+
497+
// Discard what a skipped line queued: report operator messages (as the skip
498+
// always did), then clear.
499+
static void skip_discard(void)
500+
{
501+
while (interp_list.len() > 0) {
502+
auto cmd = interp_list.get();
503+
if (!cmd) break;
504+
switch (cmd->_type) {
505+
case EMC_OPERATOR_ERROR_TYPE:
506+
emcOperatorError("%s", static_cast<EMC_OPERATOR_ERROR *>(cmd.get())->error);
507+
break;
508+
default:
509+
break;
510+
}
511+
}
512+
interp_list.clear();
513+
}
477514
// how long the interp list can be
478515

479516
int stepping = 0;
@@ -495,48 +532,13 @@ static NML_INTERP_LIST mdi_input_queue;
495532
#define MAX_MDI_QUEUE 10
496533
static int max_mdi_queued_commands = MAX_MDI_QUEUE;
497534

498-
/*
499-
checkInterpList(NML_INTERP_LIST *il, EMC_STAT *stat) takes a pointer
500-
to an interpreter list and a pointer to the EMC status, pops each NML
501-
message off the list, and checks it against limits, resource availability,
502-
etc. in the status.
503-
504-
It returns 0 if all messages check out, -1 if any of them fail. If one
505-
fails, the rest of the list is not checked.
506-
*/
507-
static int checkInterpList(NML_INTERP_LIST * il, EMC_STAT * /*stat*/)
508-
{
509-
while (il->len() > 0) {
510-
auto cmd = il->get();
511-
512-
switch (cmd->_type) {
513-
514-
case EMC_OPERATOR_ERROR_TYPE: {
515-
auto error_msg = static_cast<EMC_OPERATOR_ERROR*>(cmd.get());
516-
emcOperatorError("%s", error_msg->error);
517-
break;
518-
}
519-
520-
//FIXME: there was limit checking tests below, see if they were needed
521-
case EMC_TRAJ_LINEAR_MOVE_TYPE:
522-
break;
523-
524-
case EMC_TRAJ_CIRCULAR_MOVE_TYPE:
525-
break;
526-
527-
default:
528-
break;
529-
}
530-
}
531-
532-
return 0;
533-
}
534535
extern int emcTaskMopup();
535536

536537
void readahead_reading(void)
537538
{
538539
int readRetval;
539540
int execRetval;
541+
int skipYield = 0; // a wait was met while skipping: stop reading this cycle
540542

541543
if (interp_list.len() <= emc_task_interp_max_len) {
542544
int count = 0;
@@ -582,6 +584,25 @@ void readahead_reading(void)
582584

583585
emcTaskPlanCommand((char *) &emcStatus->task.
584586
command);
587+
// RUN FROM LINE: the skip ends before the first
588+
// main-program line at or after the start line runs --
589+
// also when the line before it never ran at level 0
590+
// (start line 1, a %-headed file).
591+
if (_task_skipping && programStartLine > 0
592+
&& emcTaskPlanLevel() == 0
593+
&& emcStatus->task.readLine >= programStartLine) {
594+
CANON_UPDATE_END_POINT(emcStatus->motion.traj.actualPosition.tran.x,
595+
emcStatus->motion.traj.actualPosition.tran.y,
596+
emcStatus->motion.traj.actualPosition.tran.z,
597+
emcStatus->motion.traj.actualPosition.a,
598+
emcStatus->motion.traj.actualPosition.b,
599+
emcStatus->motion.traj.actualPosition.c,
600+
emcStatus->motion.traj.actualPosition.u,
601+
emcStatus->motion.traj.actualPosition.v,
602+
emcStatus->motion.traj.actualPosition.w);
603+
emcTaskPlanSynch();
604+
skip_end();
605+
}
585606
// and execute it
586607
execRetval = emcTaskPlanExecute(NULL);
587608
// line number may need update after
@@ -598,6 +619,12 @@ void readahead_reading(void)
598619
|| execRetval == INTERP_EXIT ) {
599620
emcStatus->task.interpState =
600621
EMC_TASK_INTERP::WAITING;
622+
} else if (execRetval == INTERP_EXECUTE_FINISH && _task_skipping) {
623+
// RUN FROM LINE: a wait inside skipped code must not send
624+
// the skipped commands to the machine. Discard below, do
625+
// not wait, re-read nothing; one wait per task cycle.
626+
emcStatus->task.input_timeout = 0;
627+
skipYield = 1;
601628
} else if (execRetval == INTERP_EXECUTE_FINISH) {
602629
// INTERP_EXECUTE_FINISH signifies
603630
// that no more reading should be done until
@@ -621,24 +648,13 @@ void readahead_reading(void)
621648
// throw the results away if we're supposed to
622649
// read
623650
// through it
624-
if ( programStartLine != 0 &&
625-
emcTaskPlanLevel() == 0 &&
626-
( programStartLine < 0 ||
627-
emcTaskPlanLine() <= programStartLine )) {
628-
// we're stepping over lines, so check them
629-
// for
630-
// limits, etc. and clear then out
631-
if (0 != checkInterpList(&interp_list,
632-
emcStatus)) {
633-
// problem with actions, so do same as we
634-
// did
635-
// for a bad read from emcTaskPlanRead()
636-
// above
637-
emcStatus->task.interpState =
638-
EMC_TASK_INTERP::WAITING;
639-
}
640-
// and clear it regardless
641-
interp_list.clear();
651+
if (_task_skipping) {
652+
// RUN FROM LINE: every block executed while skipping,
653+
// at any call level, is discarded before anything can
654+
// send it (formerly: only main-program lines, after the
655+
// whole line -- remap/subroutine bodies leaked out at
656+
// their waits). Operator messages are still shown.
657+
skip_discard();
642658
}
643659

644660
if (emcStatus->task.readLine < programStartLine &&
@@ -663,10 +679,12 @@ void readahead_reading(void)
663679
// reset programStartLine so we don't fall into our stepping routines
664680
// if we happen to execute lines before the current point later (due to subroutines).
665681
programStartLine = 0;
682+
skip_end();
666683
}
667684
}
668685

669686
if (count++ < emc_task_interp_max_len
687+
&& !skipYield
670688
&& emcStatus->task.interpState == EMC_TASK_INTERP::READING
671689
&& interp_list.len() <= emc_task_interp_max_len * 2/3) {
672690
goto interpret_again;
@@ -777,6 +795,7 @@ void readahead_waiting(void)
777795
emcStatus->task.interpState = EMC_TASK_INTERP::IDLE;
778796
}
779797
emcStatus->task.readLine = 0;
798+
skip_reset(); // run over: never carry "skipping" into MDI or the next run
780799
} else {
781800
// still executing
782801
}
@@ -2188,6 +2207,7 @@ static int emcTaskIssueCommand(NMLmsg * cmd)
21882207

21892208
case EMC_TASK_ABORT_TYPE:
21902209
// abort everything
2210+
skip_reset();
21912211
emcTaskAbort();
21922212
// KLUDGE call motion abort before state restore to make absolutely sure no
21932213
// stray restore commands make it down to motion
@@ -2440,6 +2460,8 @@ static int emcTaskIssueCommand(NMLmsg * cmd)
24402460
}
24412461
run_msg = reinterpret_cast<EMC_TASK_PLAN_RUN *>(cmd);
24422462
programStartLine = run_msg->line;
2463+
skip_reset();
2464+
_task_skipping = (programStartLine != 0);
24432465
emcStatus->task.interpState = EMC_TASK_INTERP::READING;
24442466
emcStatus->task.task_paused = 0;
24452467
retval = 0;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/sh
2+
# Run from line 1: nothing is skipped, the M442 call after it runs in full.
3+
cd "$(dirname "$1")" || exit 1
4+
fail=0
5+
grep -q "z=1," out.motion-logger || { echo "FAIL: G0 Z1 did not run"; fail=1; }
6+
grep -q "SET_LINE x=7," out.motion-logger || { echo "FAIL: X7 of the called subroutine did not run"; fail=1; }
7+
grep -q "y=7," out.motion-logger || { echo "FAIL: Y7 of the called subroutine did not run"; fail=1; }
8+
exit $fail
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
loadusr -W motion-logger out.motion-logger
2+
setp iocontrol.0.emc-enable-in 1
3+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
o<m442> sub
2+
(a skipped call: one move before a wait, one after)
3+
G0 X7
4+
M66 P0 L0
5+
G0 Y7
6+
o<m442> endsub
7+
m2
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env python3
2+
# Run-from-line with a remapped code whose subroutine waits (M66): issue #246.
3+
import linuxcnc, sys, time
4+
c = linuxcnc.command(); s = linuxcnc.stat(); e = linuxcnc.error_channel()
5+
c.state(linuxcnc.STATE_ESTOP_RESET)
6+
c.state(linuxcnc.STATE_ON)
7+
c.mode(linuxcnc.MODE_AUTO)
8+
c.program_open('test.ngc')
9+
c.auto(linuxcnc.AUTO_RUN, 1)
10+
c.wait_complete()
11+
start = time.time()
12+
while time.time() - start < 10:
13+
s.poll()
14+
if s.interp_state == linuxcnc.INTERP_IDLE and s.exec_state == linuxcnc.EXEC_DONE:
15+
break
16+
time.sleep(0.1)
17+
sys.exit(0)
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
[EMC]
2+
VERSION = 1.1
3+
DEBUG = 0xffffffff
4+
RCS_DEBUG = 0x05400f01
5+
6+
[DISPLAY]
7+
DISPLAY = ./test-ui.py
8+
9+
[TASK]
10+
TASK = milltask
11+
CYCLE_TIME = 0.001
12+
13+
[RS274NGC]
14+
SUBROUTINE_PATH = ./subs
15+
LOG_LEVEL = 999
16+
REMAP=M442 modalgroup=5 ngc=m442
17+
18+
[EMCMOT]
19+
#EMCMOT = motmod
20+
COMM_TIMEOUT = 4.0
21+
BASE_PERIOD = 0
22+
SERVO_PERIOD = 1000000
23+
24+
[EMCIO]
25+
TOOL_TABLE = simpockets.tbl
26+
TOOL_CHANGE_QUILL_UP = 1
27+
RANDOM_TOOLCHANGER = 0
28+
29+
[HAL]
30+
HALFILE = mock-motion.hal
31+
#POSTGUI_HALFILE = postgui.hal
32+
33+
[TRAJ]
34+
NO_FORCE_HOMING = 1
35+
AXES = 3
36+
COORDINATES = X Y Z
37+
HOME = 0 0 0
38+
LINEAR_UNITS = inch
39+
ANGULAR_UNITS = degree
40+
DEFAULT_LINEAR_VELOCITY = 120
41+
MAX_LINEAR_VELOCITY = 400
42+
43+
[KINS]
44+
KINEMATICS = trivkins
45+
JOINTS = 3
46+
47+
[AXIS_X]
48+
MIN_LIMIT = -40.0
49+
MAX_LIMIT = 40.0
50+
MAX_VELOCITY = 400
51+
MAX_ACCELERATION = 1000.0
52+
53+
[JOINT_0]
54+
TYPE = LINEAR
55+
HOME = 0.000
56+
MAX_VELOCITY = 400
57+
MAX_ACCELERATION = 1000.0
58+
BACKLASH = 0.000
59+
INPUT_SCALE = 4000
60+
OUTPUT_SCALE = 1.000
61+
MIN_LIMIT = -40.0
62+
MAX_LIMIT = 40.0
63+
FERROR = 0.050
64+
MIN_FERROR = 0.010
65+
66+
[AXIS_Y]
67+
MIN_LIMIT = -40.0
68+
MAX_LIMIT = 40.0
69+
MAX_VELOCITY = 400
70+
MAX_ACCELERATION = 1000.0
71+
72+
[JOINT_1]
73+
TYPE = LINEAR
74+
HOME = 0.000
75+
MAX_VELOCITY = 400
76+
MAX_ACCELERATION = 1000.0
77+
BACKLASH = 0.000
78+
INPUT_SCALE = 4000
79+
OUTPUT_SCALE = 1.000
80+
MIN_LIMIT = -40.0
81+
MAX_LIMIT = 40.0
82+
FERROR = 0.050
83+
MIN_FERROR = 0.010
84+
85+
[AXIS_Z]
86+
MIN_LIMIT = -40
87+
MAX_LIMIT = 40
88+
MAX_VELOCITY = 400
89+
MAX_ACCELERATION = 1000.0
90+
91+
[JOINT_2]
92+
TYPE = LINEAR
93+
HOME = 0.0
94+
MAX_VELOCITY = 400
95+
MAX_ACCELERATION = 1000.0
96+
BACKLASH = 0.000
97+
INPUT_SCALE = 4000
98+
OUTPUT_SCALE = 1.000
99+
MIN_LIMIT = -40
100+
MAX_LIMIT = 40
101+
FERROR = 0.050
102+
MIN_FERROR = 0.010
103+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
G0 Z1
2+
M442 (called after the start line: must run in full)
3+
M2
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash -e
2+
3+
rm -f out.motion-logger
4+
5+
linuxcnc -r test.ini

0 commit comments

Comments
 (0)