Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/src/config/ini-config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,14 @@ The maximum number of `USER_M_PATH` directories is defined at compile time (typ:
Preserve case in O-word names within comments if set, enables reading of mixed-case HAL items in structured comments like `(debug, #<_hal[MixedCaseItem])`.
* `OWORD_WARNONLY = 0` (Default: 0) +
Warn rather than error in case of errors in O-word subroutines.
* `GCODE_HOMING = 0` (bool, Default: 0) +
When set, a plain `G28` (no axis words) references the machine before its
return move: it runs the homing cycle on all joints when the machine is not
already fully homed, then performs the normal `G28` return. An already-homed
machine skips the homing step, so `G28` keeps its stock behavior. The option
affects only the bare `G28`; `G28 axes`, `G28.1`, `G30`, `G30.1`, `G28.2`
and `G28.3` are unchanged. With the default of 0, behavior is identical to
stock LinuxCNC. See <<gcode:g28-g28.1,G28>> and <<gcode:g28.2-g28.3,G28.2/G28.3>>.
* `DISABLE_G92_PERSISTENCE = 0` (bool, Default: 0)
Allow to clear the G92 offset automatically when config start-up.
* `DISABLE_AUTO_G54 = 0` (bool, Default: 0) +
Expand Down
59 changes: 59 additions & 0 deletions docs/src/gcode/g-code.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ as the 'L number', and so on for any other letter.
|<<gcode:g17-g19.1,G17 - G19.1>> |Plane Select
|<<gcode:g20-g21,G20 G21>> |Set Units of Measure
|<<gcode:g28-g28.1,G28 - G28.1>> |Go to Predefined Position
|<<gcode:g28.2-g28.3,G28.2 G28.3>> |Home / Unhome from G-code
|<<gcode:g30-g30.1,G30 - G30.1>> |Go to Predefined Position
|<<gcode:g33,G33>> |Spindle Synchronized Motion
|<<gcode:g33.1,G33.1>> |Rigid Tapping
Expand Down Expand Up @@ -997,6 +998,64 @@ It is an error if :

* Cutter Compensation is turned on

[NOTE]
When `<<sub:ini:sec:rs274ngc,[RS274NGC]GCODE_HOMING>>` is set to 1, a plain
`G28` (no axis words) first references the machine -- it runs the homing
cycle on all joints when the machine is not already fully homed -- and then
performs the normal return move. On an already-homed machine the homing step
is skipped, so `G28` behaves exactly as described above. The flag has no
effect on `G28 axes`, `G28.1`, `G30`, `G30.1`, `G28.2` or `G28.3`, and it is
off by default (stock behavior). See also `G28.2` below.

[[gcode:g28.2-g28.3]]
== G28.2, G28.3 Home, Unhome from G-code(((G28.2 Home from G-code)))(((G28.3 Unhome from G-code)))

These non-modal codes let a program or MDI line reference the machine
instead of requiring the operator to use the GUI's *Home All* button. They
follow the same modal-group-0 pattern as `G28.1`/`G30.1` and take no axis
words.

* 'G28.2' - runs the homing cycle on all joints, in `HOME_SEQUENCE` order
(the same operation as the GUI *Home All*).
* 'G28.3' - unhomes all joints.
* 'G28.2 Pn' - runs the homing cycle on joint 'n' only, where 'n' is the
0-based joint number matching its `[JOINT_n]` INI section (the same
numbering used by `G28.2`'s `HOME_SEQUENCE`, and by `G28.6`/joint jogging).
Other joints are left as they are.
* 'G28.3 Pn' - unhomes joint 'n' only, leaving other joints as they are.

.G28.2/G28.3 Example Lines
[source,ngc]
----
G28.2 (home all joints, in HOME_SEQUENCE order)
G28.2 P1 (home joint 1 only)
G28.3 P1 (unhome joint 1 only)
G28.3 (unhome all joints)
----

A queued `G28.2`/`G28.3` dips motion into free mode for the duration of the
homing cycle and restores whatever mode (manual/MDI/auto) was active once it
finishes, so the mode dip is invisible at the task level. Motion still
enforces its own safety: the home is honored only when the machine is idle
(in position with no queued motion) or in joint mode, and a home is refused
mid-motion. Homing inhibits and per-joint limit handling are unchanged.

[IMPORTANT]
When `[TRAJ]NO_FORCE_HOMING` is not set (the default), unhoming a joint that
leaves the machine not fully homed blocks any further `AUTO` or `MDI`
command until the machine is fully re-homed, exactly as if the machine had
never been homed in the first place -- closing the gap where `G28.3` could
otherwise be used to bypass the homed-before-running requirement.

[NOTE]
`G28.2` and `G28.3` are LinuxCNC extensions; there is no standard Fanuc
equivalent. They are available regardless of the `GCODE_HOMING` INI setting.

It is an error if :

* Cutter Compensation is turned on
* 'Pn' names a joint number that does not exist on the machine

[[gcode:g30-g30.1]]
== G30, G30.1 Go/Set Predefined Position(((G30 Go/Set Predefined Position)))

Expand Down
9 changes: 6 additions & 3 deletions src/emc/motion/command.c
Original file line number Diff line number Diff line change
Expand Up @@ -1416,9 +1416,12 @@ void emcmotCommandHandler_locked(void *arg, long servo_period)
rtapi_print_msg(RTAPI_MSG_DBG, "JOINT_HOME");
rtapi_print_msg(RTAPI_MSG_DBG, " %d", joint_num);

if (emcmotStatus->motion_state != EMCMOT_MOTION_FREE) {
/* can't home unless in free mode */
reportError(_("must be in joint mode to home"));
/* Normally homing requires free (joint) mode. Allow it also when
* motion is otherwise IDLE (in position, nothing queued) so a
* G-code-triggered home (G28.2) works from MDI / a program. */
if (emcmotStatus->motion_state != EMCMOT_MOTION_FREE
&& !(GET_MOTION_INPOS_FLAG() && emcmotStatus->depth == 0)) {
reportError(_("must be in joint mode (or idle) to home"));
return;
}
if (*(emcmot_hal_data->homing_inhibit)) {
Expand Down
15 changes: 15 additions & 0 deletions src/emc/nml_intf/canon.hh
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,21 @@ extern void SET_G92_OFFSET(double x, double y, double z,

extern void SET_XY_ROTATION(double t);

/* G28.2 / G28.3: trigger the machine homing cycle / unhome from G-code
* (bare form = all joints). Maps to EMC_JOINT_HOME/UNHOME(-1). */
extern void HOME_CYCLE(void);
extern void UNHOME_AXES(void);
/* G28.2 Pn / G28.3 Pn: home/unhome a single joint by its 0-based joint
* number (matching [JOINT_n] INI section numbering). Maps to
* EMC_JOINT_HOME/UNHOME(joint). */
extern void HOME_CYCLE_JOINT(int joint);
extern void UNHOME_JOINT(int joint);
/* GCODE_HOMING (plain G28 with [RS274NGC]GCODE_HOMING=1): reference the
* machine before the G28 return move, but only when it is not already fully
* homed (a homed machine sees a pure legacy G28). Emits EMC_JOINT_HOME with
* the EMC_HOME_ALL_IF_UNHOMED sentinel; task skips it when all_homed(). */
extern void HOME_CYCLE_IF_UNHOMED(void);

/* Offset the origin to the point with absolute coordinates x, y, z,
a, b, c, u, v, and w. Values of x, y, z, a, b, c, u, v, and w are real
numbers. The units are whatever length units are being used at the time
Expand Down
3 changes: 2 additions & 1 deletion src/emc/nml_intf/emc.hh
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ enum class EMC_TASK_EXEC {
WAITING_FOR_MOTION_AND_IO = 7,
WAITING_FOR_DELAY = 8,
WAITING_FOR_SYSTEM_CMD = 9,
WAITING_FOR_SPINDLE_ORIENTED = 10
WAITING_FOR_SPINDLE_ORIENTED = 10,
WAITING_FOR_HOMING = 11
};

// types for EMC_TASK interpState
Expand Down
7 changes: 7 additions & 0 deletions src/emc/nml_intf/emc_nml.hh
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,13 @@ class EMC_JOINT_HALT:public EMC_JOINT_CMD_MSG {
void update(CMS * cms);
};

// GCODE_HOMING ([RS274NGC]GCODE_HOMING=1): a plain G28 emits EMC_JOINT_HOME
// carrying this sentinel in 'joint' to mean "home all joints, but only if the
// machine is not already fully homed". Task resolves it via all_homed(): a
// fully-homed machine sees a pure legacy G28 return (no homing). Distinct from
// joint = -1 (unconditional home-all) and joint = -2 (UNHOME volatile).
#define EMC_HOME_ALL_IF_UNHOMED (-3)

class EMC_JOINT_HOME:public EMC_JOINT_CMD_MSG {
public:
EMC_JOINT_HOME()
Expand Down
6 changes: 6 additions & 0 deletions src/emc/rs274ngc/gcodemodule.cc
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,12 @@ void SELECT_PLANE(CANON_PLANE pl) {
Py_XDECREF(result);
}

void HOME_CYCLE(void) {}
void UNHOME_AXES(void) {}
void HOME_CYCLE_JOINT(int) {}
void UNHOME_JOINT(int) {}
void HOME_CYCLE_IF_UNHOMED(void) {}

void SET_TRAVERSE_RATE(double rate) {
maybe_new_line();
if(interp_error) return;
Expand Down
2 changes: 1 addition & 1 deletion src/emc/rs274ngc/interp_array.cc
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ const int Interp::gees[] = {
/* 220 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
/* 240 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
/* 260 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
/* 280 */ 0, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
/* 280 */ 0, 0, 0, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 282=G28.2 283=G28.3
/* 300 */ 0, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
/* 320 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 1,-1,-1,-1,-1,-1,-1,-1,-1,
/* 340 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
Expand Down
3 changes: 3 additions & 0 deletions src/emc/rs274ngc/interp_check.cc
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ int Interp::check_g_codes(block_pointer block, //!< pointer to a block to be c
} else if (mode1 == G_5_2){
} else if (mode1 == G_6_2){
} else if (mode0 == G_28_1 || mode0 == G_30_1) {
} else if (mode0 == G_28_2 || mode0 == G_28_3) { // G-code homing
} else if (mode0 == G_52) {
} else if (mode0 == G_53) {
CHKS(((block->motion_to_be != G_0) && (block->motion_to_be != G_1)),
Expand Down Expand Up @@ -326,12 +327,14 @@ int Interp::check_other_codes(block_pointer block) //!< pointer to a block
(motion != G_6) && (motion != G_6_2) &&
(motion != G_2) && (motion != G_3) &&
(motion != G_74) && (motion != G_84) &&
(block->g_modes[GM_MODAL_0] != G_28_2) && (block->g_modes[GM_MODAL_0] != G_28_3) &&
(block->m_modes[9] != 50) && (block->m_modes[9] != 51) && (block->m_modes[9] != 52) &&
(block->m_modes[9] != 53) && (block->m_modes[5] != 62) && (block->m_modes[5] != 63) &&
(block->m_modes[5] != 64) && (block->m_modes[5] != 65) && (block->m_modes[5] != 66) &&
(block->m_modes[7] != 19) && (block->user_m != 1) &&
(block->o_type != M_98)),
_("P word with no G2 G3 G4 G10 G64 G5 G5.2 G6, G6.2, G76 G82 G86 G88 G89"
" G28.2 G28.3"
" or M50 M51 M52 M53 M62 M63 M64 M65 M66 M98 "
"or user M code to use it"));
int p_value = round_to_int(block->p_number);
Expand Down
70 changes: 70 additions & 0 deletions src/emc/rs274ngc/interp_convert.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3111,6 +3111,64 @@ Called by: convert_modal_0.

*/

/*! convert_home_cycle

Handles G28.2 (run the homing cycle on all joints) and G28.3 (unhome all
joints) from a G-code line, so machines can reference / clear references
from MDI or a program instead of only from the GUI. Bare form (no P word)
acts on all joints, in HOME_SEQUENCE order.

An optional Pn word homes/unhomes a single joint by its 0-based joint number
(matching [JOINT_n] INI section numbering, e.g. P1 -> JOINT_1). This is the
primitive Sigma1912 asked for in the PR #4172 discussion for re-homing a
joint that is switched between rotary-axis and spindle use mid-program
(https://github.com/LinuxCNC/linuxcnc/pull/4172) -- it reuses the existing
EMC_JOINT_HOME/UNHOME 'joint' field, so it needs no NML change and works
identically on any kinematics (per grandixximo's review comment on that PR).
Axis-letter forms (G28.2 X) are deliberately NOT supported: resolving an
axis letter to a joint needs the kinematics coordinate map and isn't
trivial even on trivkins (duplicate letters on gantries) -- andypugh's
review also objected that homing is a joint concept, not an axis one.

On a synchronized (negative HOME_SEQUENCE) joint pair, Pn on either joint
homes both (motion's existing gantry-homing behavior); on a positive shared
sequence Pn homes only the named joint -- use the bare form to home both.

Motion still enforces its own safety (idle / not on limits) and does the
final range check of the joint number against the machine's actual joint
count.
*/
int Interp::convert_home_cycle(int move,
block_pointer block,
setup_pointer settings)
{
CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF),
"Cannot home (G28.2/G28.3) with cutter radius compensation on");

int joint = -1;
if (block->p_flag) {
CHKS(((block->p_number < 0.0) ||
(block->p_number != round_to_int(block->p_number))),
"P value for G28.2/G28.3 must be a non-negative whole joint number");
joint = round_to_int(block->p_number);
}

if (move == G_28_2) {
if (joint < 0) {
HOME_CYCLE();
} else {
HOME_CYCLE_JOINT(joint);
}
} else {
if (joint < 0) {
UNHOME_AXES();
} else {
UNHOME_JOINT(joint);
}
}
return INTERP_OK;
}

int Interp::convert_home(int move, //!< G-code, must be G_28 or G_30
block_pointer block, //!< pointer to a block of RS274 instructions
setup_pointer settings) //!< pointer to machine settings
Expand Down Expand Up @@ -3143,6 +3201,16 @@ int Interp::convert_home(int move, //!< G-code, must be G_28 or G_30
CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF),
NCE_CANNOT_USE_G28_OR_G30_WITH_CUTTER_RADIUS_COMP);

/* GCODE_HOMING ([RS274NGC]GCODE_HOMING=1): a plain G28 references the
* machine (runs the homing cycle on all joints) BEFORE the waypoint +
* return moves below, but only when it is not already fully homed - task
* drops the home when all_homed(), so a homed machine sees a pure legacy
* G28. The home is emitted first so it completes (motion stays busy, the
* return waits) while the joint positions are still unknown. Flag-gated and
* G28-only; G30/G28.2/G28.3 are unchanged. */
if (FEATURE(GCODE_HOMING) && move == G_28)
HOME_CYCLE_IF_UNHOMED();

// waypoint is in currently active coordinate system

// move indexers first, one at a time
Expand Down Expand Up @@ -4290,6 +4358,8 @@ int Interp::convert_modal_0(int code, //!< G-code, must be from group 0
CHP(convert_home(code, block, settings));
} else if ((code == G_28_1) || (code == G_30_1)) {
CHP(convert_savehome(code, block, settings));
} else if ((code == G_28_2) || (code == G_28_3)) {
CHP(convert_home_cycle(code, block, settings));
} else if ((code == G_52) || (code == G_92)) {
CHP(convert_axis_offsets(code, block, settings));
} else if ((code == G_5_3)||(code == G_6_3)) { // jjf
Expand Down
7 changes: 7 additions & 0 deletions src/emc/rs274ngc/interp_internal.hh
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ enum GCodes
G_21 = 210,
G_28 = 280,
G_28_1 = 281,
G_28_2 = 282, /* G-code homing cycle (home worded/all joints) */
G_28_3 = 283, /* G-code unhome */
G_30 = 300,
G_30_1 = 301,
G_33 = 330,
Expand Down Expand Up @@ -844,6 +846,11 @@ struct setup
// do not lowercase named params inside comments - for #<_hal[PinName]>
#define FEATURE_NO_DOWNCASE_OWORD 0x00000010
#define FEATURE_OWORD_WARNONLY 0x00000020
// [RS274NGC]GCODE_HOMING=1: a plain G28 references the machine (runs the
// homing cycle) before its return move when the machine is not already
// fully homed; a fully-homed machine sees a pure legacy G28. G28.2/G28.3
// are flag-independent.
#define FEATURE_GCODE_HOMING 0x00000040

boost::python::object *pythis; // boost::cref to 'this'
const char *on_abort_command;
Expand Down
2 changes: 2 additions & 0 deletions src/emc/rs274ngc/rs274ngc_interp.hh
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ public:
setup_pointer settings);
int convert_savehome(int move, block_pointer block,
setup_pointer settings);
int convert_home_cycle(int move, block_pointer block, // G28.2/G28.3
setup_pointer settings);
int convert_length_units(int g_code, setup_pointer settings);
int convert_m(block_pointer block, setup_pointer settings);
int convert_modal_0(int code, block_pointer block,
Expand Down
2 changes: 2 additions & 0 deletions src/emc/rs274ngc/rs274ngc_pre.cc
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,8 @@ int Interp::init()
_setup.feature_set |= FEATURE_NO_DOWNCASE_OWORD;
if (inifile.findBoolV("OWORD_WARNONLY", "RS274NGC", false))
_setup.feature_set |= FEATURE_OWORD_WARNONLY;
if (inifile.findBoolV("GCODE_HOMING", "RS274NGC", false))
_setup.feature_set |= FEATURE_GCODE_HOMING;

if (auto inival = inifile.findInt("LOCKING_INDEXER_JOINT", "AXIS_A")) {
_setup.a_indexer_jnum = *inival;
Expand Down
6 changes: 6 additions & 0 deletions src/emc/sai/saicanon.cc
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ void SET_XY_ROTATION(double t) {
ECHO_WITH_ARGS("%.4f", t);
}

void HOME_CYCLE(void) { ECHO_WITH_ARGS(""); }
void UNHOME_AXES(void) { ECHO_WITH_ARGS(""); }
void HOME_CYCLE_JOINT(int joint) { ECHO_WITH_ARGS("%d", joint); }
void UNHOME_JOINT(int joint) { ECHO_WITH_ARGS("%d", joint); }
void HOME_CYCLE_IF_UNHOMED(void) { ECHO_WITH_ARGS(""); }

void SET_G5X_OFFSET(int index,
double x, double y, double z,
double a, double b, double c,
Expand Down
53 changes: 53 additions & 0 deletions src/emc/task/emccanon.cc
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,59 @@ void SET_XY_ROTATION(double t) {
canon.xy_rotation = t;
}


void HOME_CYCLE(void)
{
// STRAIGHT_FEED/STRAIGHT_TRAVERSE buffer points into chained_points for
// arc-blend lookahead and only append to interp_list on flush (see
// see_segment()/flush_segments()). Without flushing here first, any
// motion queued just before this G28.2 would get silently reordered to
// execute AFTER the home instead of before it.
flush_segments();
auto msg = std::make_unique<EMC_JOINT_HOME>();
msg->joint = -1; // -1 = all joints (HOME_SEQUENCE order)
interp_list.append(std::move(msg));
}

void UNHOME_AXES(void)
{
flush_segments();
auto msg = std::make_unique<EMC_JOINT_UNHOME>();
msg->joint = -1;
interp_list.append(std::move(msg));
}

/* G28.2 Pn / G28.3 Pn -- home/unhome a single joint. joint is the interp's
* already-validated (non-negative) P value; motion does the final
* range check against the machine's actual joint count. */
void HOME_CYCLE_JOINT(int joint)
{
flush_segments(); // see HOME_CYCLE
auto msg = std::make_unique<EMC_JOINT_HOME>();
msg->joint = joint;
interp_list.append(std::move(msg));
}

void UNHOME_JOINT(int joint)
{
flush_segments(); // see HOME_CYCLE
auto msg = std::make_unique<EMC_JOINT_UNHOME>();
msg->joint = joint;
interp_list.append(std::move(msg));
}

/* GCODE_HOMING plain G28: home all joints, but only if the machine is not
* already fully homed. The sentinel joint value defers the all-homed test to
* task (execution time), so a homed machine drops the home and runs a pure
* legacy G28 return. */
void HOME_CYCLE_IF_UNHOMED(void)
{
flush_segments(); // see HOME_CYCLE
auto msg = std::make_unique<EMC_JOINT_HOME>();
msg->joint = EMC_HOME_ALL_IF_UNHOMED;
interp_list.append(std::move(msg));
}

void SET_G5X_OFFSET(int index,
double x, double y, double z,
double a, double b, double c,
Expand Down
Loading
Loading