Skip to content

Command step: inject custom G-Code and/or run OS commands in a workflow (with optional feedback/loops) #449

Description

@knipknap

Feature request: Command step (G-Code and OS commands) — evaluated

Submitted by AVRASM2. This request was evaluated against the current codebase; findings and a proposed phasing are included below.

The problem

Rayforge's workflow is built from geometry/material operations (contour, engrave, CNC toolpaths). There is no way to:

  • inject user-defined G-Code at an arbitrary point in a job in a well-defined order;
  • run an external program as part of a job (robot part loader, relay/pump over a serial port, sound player, post-processing script);
  • react to the result of such a program (exit code / output) to branch or repeat.

Today users work around this by exporting the G-Code, hand-editing or post-processing it externally, and re-importing — repeating the whole cycle on any change.

Requested solution

A new "Command" step type that can be placed anywhere in a layer's workflow, before, between, or after operations, multiple times. It should support:

  1. Send G-Code — a multi-line, templated block emitted at the step's position.
  2. Execute an OS shell command — run a host program and optionally wait for it.
  3. (stretch) React to feedback — read the called program's exit code / stdout and branch, loop, GOTO a marker, or use counters/variables (e.g. repeat a layer/job until all pieces are loaded).

Example from the requester:

Layer 1
  Command: G-code  G0 X0 Y0 Z0        # pre-position (multiple lines)
  Command: Run program: Robot.sh Move 1   # load first piece
  Command: Run program: echo start > /dev/ttyACM3  # fan & pumps on
  Contour
  Frame
  Engrave
Layer 2 ... Layer 3 ...
  Command: Run program: Robot.sh NextPiece ; IF return = 1 THEN Restart Job/Layer
  Command: Run program: echo stop > /dev/ttyACM3
  Command: Run program: spd-say 'I'm done!'   # operator alert

What already exists (important — this narrows the request)

  • Machine "Hooks & Macros" are already implemented (machine/models/macro.py, pipeline/encoder/rust_helpers.py, UI under Machine Settings → Hooks & Macros). G-Code macros can be injected automatically at Layer Start/End and Workpiece Start/End. Macros support @include of other macros and templating (machine.*, layer.*, workpiece.*, job.*, wcs_offset[*]).
  • Job Start/End G-Code is handled via the dialect preamble/postscript (legacy JOB_START/JOB_END hooks are auto-migrated to a custom dialect).
  • Manual macro execution from the Macros menu exists (MachineCmd.execute_macro_by_uid), and the encoder can already emit arbitrary raw lines via macros.
  • Addon hooks (core/hooks.py) are pluggy addon lifecycle hooks, not user event hooks. They are not what the requester saw in the UI. However, addons can already connect to the job-lifecycle signals (machine_cmd.job_started, machine.job_finished) to run code in rayforge_init/main_window_ready.

So "send G-Code" is largely covered today — but only at fixed lifecycle points, not as an ordered, per-step command, and never as an OS command.

Evaluation / feasibility

1. G-Code command as an ordered workflow step — moderate, safe, recommended first.
Steps are ordered in a Workflow and layers execute in document order, so a command step slots naturally into sequencing. Two obstacles:

  • The pipeline builds compute nodes per (workpiece × step), and Pipeline._can_generate_job requires every job layer to have at least one visible step and at least one workpiece. A geometry-less command step does not fit this model and needs explicit core support (a non-geometry stage / handling at intent-build and aggregate time).
  • raygeo's CommandType has no raw/custom command; the encoder emits only known op types. Raw lines can be passed through today via the macro/dialect mechanism, so the encoder side is mostly reusable, but the step→ops→encode path needs a defined representation.
    The G-Code half is host-interpretable and low-risk (still able to crash a machine, see Safety), so it is the best first increment.

2. OS shell command step — technically easy, security-sensitive.
Actually executing a program is just subprocess on the host, but it must not be a quiet default. It also does not belong in the G-Code stream; it runs around/alongside the job, so the pipeline model above does not directly apply — a cleaner first form is an addon reacting to job signals, before any core step exists.

3. Feedback / loops / GOTO / variables — large; a separate feature, not a step.
Rayforge compiles the document ahead of time into a linear G-Code stream and sends it to firmware. Most GRBL-style controllers have no variables, labels, or GOTO, so conditionals and loops cannot be expressed in the emitted job. Reacting to a program's result and repeating requires host-side job control (pause, re-run a layer/job, re-generate, branch) and a scripting model — a distinct design effort. spd-say/relay/sound are just the OS-command half, not control flow.

4. "Enhance the hooks to run OS commands" — mostly an addon, not core.
Because addons can already subscribe to job lifecycle signals, a "play a sound / trigger a relay at job end" addon can be written today with no core change. That is a good way to prove the OS-command path and its permissions UX before adding a user-facing shell step.

Proposed phasing

  1. Core: "G-Code Command" step — a geometry-less step that injects a templated multi-line G-Code block at its workflow position. Reuse the existing macro expansion and encoder path. No OS execution. This delivers the pre-position / retract-Z / de-focus / rotary / pause use cases safely.
  2. Addon: job lifecycle OS commands — run a program on job start/finish/layer events via the existing signals, with an explicit enable switch. Proves the shell path and safety model.
  3. Core: opt-in "Shell Command" step — if still wanted after (2), add a step with an allow-list/explicit confirmation, clearly marked as machine-specific and non-portable.
  4. Separate feature: host-side conditional/loop job control — branching/repeat based on program feedback, GOTO markers, counters. Needs its own design; not required for 1–3.

Safety and portability

  • Arbitrary code execution: a .ryp project or a shared machine profile that embeds shell commands is a code-execution vector. Commands must require explicit opt-in, never auto-run from an untrusted project, and should be surfaced/confirmed before execution. Consider an allow-list and storing shell commands in machine config rather than the document.
  • Machine safety: arbitrary G-Code (Z moves, G53, de-focus, rotary) can crash hardware. Command steps should be visually and textually flagged, and ideally gated by machine capability.
  • Portability: the requester already noted that projects with command steps are machine-specific. Mark such steps/layers, and warn when opening a project that references programs or coordinates not present locally.

Additional context

The requester compared this to MeerK40t, which assigns steps to drawing colors in a single layer and allows commands in any order and number between operations.

could also have a step called "Command" and it would allow to A) send G-Code and B) execute OS Shellcommands, then it gets really powerful with maybe not much effort i hope! especially if it could also react on the feedback of a called program to make a Loop or other actions?
that would instantly beat MeerK40t by lightyears and maybe other tools too!
no clue if that could be an Addon or if that Command-Step needs to be baked into RF it self

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions