Program emitters¶
tesseract_robotics.emitters turns a planned or hand-authored
CompositeInstruction into controller-native robot programs.
Architecture¶
flowchart LR
CI[CompositeInstruction] -->|lower| IR[ProgramIR<br/>typed events]
IR -->|drive| BE{{ProgramBackend}}
BE --> RB[RapidBackend]
BE --> KB[KrlBackend]
RB --> RW[rapid_writer DSL]
KB --> KW[krl_writer DSL]
RW --> OUT[EmittedProgram]
KW --> OUT
One walk produces the brand-neutral ProgramIR; drive() dispatches its
events to a ProgramBackend, and each backend renders through its brand's
hand-authoring DSL (rapid_writer, krl_writer, …). The DSL is the same layer
you would write by hand to author a program directly — the backend just drives
it from the IR.
core/— the brand-independent spine: the single lowering walk (lower), the typed event IR (JointMove,CartesianMove,Dwell,WaitDigital,SetDigital,SetAnalog,ToolChange,Note),EmitIdentity(content-addressed reproducibility), theProgramBackendprotocol withdrive(),EmittedProgram, and the shared DSL skeleton (core.dsl:Writer,Command,Block).rapid/,krl/— per-brand backends. Each owns a pure target formatter, a validatedProfile, a hand-authoring DSL (rapid_writer,krl_writer) that specializescore.dsl, and a backend that drives that DSL from the IR. ABB RAPID is itself a backend over this core — its golden tests prove the extraction is behaviour-preserving.
The DSL skeleton (core.dsl)¶
Every brand's hand-authoring DSL is the same shape, captured once:
Writer— an indented code buffer, one singleton per subclass; a brand sets the tab width and leading-newline policy (RAPID: 4 spaces + leading newline; KRL: 2 spaces, none).Command— the base every statement/scope class inherits; a brand binds itsWritersoMoveL(...)/Ptp(...)write themselves on construction.Block— a<opening> … <closing>indentedwith-scope (RAPIDModule/Proc, KRLDef).
One walk, many backends: lower() runs once and the resulting ProgramIR can
drive any number of brand backends.
Example — KUKA KRL¶
import numpy as np
from tesseract_robotics.emitters.krl import KrlProfile, emit_krl
from tesseract_robotics.planning import CartesianTarget, JointTarget, MotionProgram, Pose
joint_names = [f"joint_{i}" for i in range(1, 7)]
program = MotionProgram("manipulator", tcp_frame="tool0", profile="weld")
program.move_to(JointTarget(np.deg2rad([0, -20, 30, 0, 50, 0]), profile="approach"))
program.linear_to(
CartesianTarget(Pose.from_xyz_rpy([0.6, -0.1, 0.8], [np.pi, 0, 0]), profile="weld")
)
program.set_joint_names(joint_names)
composite = program.to_composite_instruction(joint_names=joint_names, tcp_frame="tool0")
profiles = {"approach": KrlProfile.build(), "weld": KrlProfile.build(cp_speed_mms=100.0)}
result = emit_krl(composite, profiles, program_name="WELD_01")
result.write_to("out") # writes out/WELD_01.src
print(result.text)
DEF WELD_01()
; generated by tesseract_robotics.emitters <version>
; ir-digest: sha256:…
; profiles-digest: sha256:…
; DO NOT EDIT — regenerate from the planning pipeline
BAS(#VEL_PTP, 100)
PTP {A1 0.00000,A2 -20.00000,A3 30.00000,A4 0.00000,A5 50.00000,A6 0.00000}
$VEL.CP = 0.10000
LIN {X 600.000,Y -100.000,Z 800.000,A 0.000,B 0.000,C 180.000}
END
Determinism¶
The same program and profiles produce byte-identical output. Every file carries
ir-digest / profiles-digest SHA-256 headers and there are no timestamps in
the body, so goldens diff meaningfully and artifacts are reproducible by
construction.
Conventions¶
- The IR is strictly SI (metres, radians, seconds); unit conversion happens only
inside brand backends, through the named functions in
core.units. - KUKA
ABCangles are the intrinsic ZYX decomposition (Aabout Z,Babout Y,Cabout X), computed directly from the rotation matrix. - Quaternions follow the project-wide scalar-last
[qx, qy, qz, qw]convention at the Python surface; backends reorder as their dialect requires.
Supported brands¶
| brand | entry point | output |
|---|---|---|
| ABB | emitters.rapid.emit_rapid |
RAPID .mod |
| KUKA KRC4 | emitters.krl.emit_krl |
KRL .src |
Fanuc LS, Yaskawa Motoman JBI, and Universal Robots URScript follow in subsequent backends over the same core.