---
title: Tutorial 36 Build a Mini G-code Engine
description: Simulating the printer in software.
---
Tutorial 36 Build a Mini G-code Engine
1. Lesson Header
- Lesson Number: 36
- Level: Advanced
- Title: Build a Mini G-code Engine
- Estimated Duration: 60 Minutes
- Prerequisites: Lesson 29 (Parsing), Lesson 30 (Time)
- What You Will Build: A "Print Time Estimator" and "Collision Detector".
2. Concept Introduction
The Virtual Twin.
To predict how long a print will take or if it will crash, we need a Virtual Printer.
This engine reads G-code and updates internal state variables just like the real firmware (Marlin/Klipper).
It calculates:
- Distance traveled.
- Time taken (based on Feedrate and Acceleration).
- Filament used.
- Bounding Box.
3. Machine State Explanation
Kinematics.
- Distance: d = sqrt(dx^2 + dy^2 + dz^2).
- Time: t = d / F (Simple).
- Time (Advanced): t = t_accel + t_cruise + t_decel.
State Variables:
- current_pos (X, Y, Z, E)
- feedrate (F)
- acceleration (M204)
- jerk (M205)
- mode (G90/G91)
- extrusion_mode (M82/M83)
4. Command Breakdown
- Trapezoidal Motion Profile: Calculate ramp up/down.
- State Machine Pattern: Update state line-by-line.
5. Minimal Working Example
The Estimator.
Line 1: G1 X100 F6000 (1 second).
Line 2: G1 X200 F3000 (2 seconds).
Total: 3 seconds.
6. Visual Representation
Interactive preview is available in the interactive reader.
7. Build Exercise
Task: Write a Python Class GcodeEngine.
Methods:
- process_line(line): Update state.
- get_stats(): Return total time and filament.
Features:
- Handle G0, G1.
- Handle G90, G91.
- Handle F (Feedrate).
- Ignore comments.
Algorithm:
1. Parse line.
2. If move:
- Calculate distance to target.
- Calculate time d / (F/60).
- Add to total_time.
- Update current_pos.
3. If F changes, update current_feedrate.
8. Deep Insight Section
Acceleration Physics.
Simple d/F is wrong for short moves.
If d is small, the printer never reaches F.v_max = sqrt(2 * a * d).
If v_max < F, use v_max as the speed.
This makes estimation accurate for detailed models (e.g., Voronoi patterns) where acceleration limits dominate.
9. Common Failure Modes
- Units: Feedrate is usually mm/min. Physics formulas use mm/s. Divide by 60!
- Relative E: If you assume absolute E but the file is relative (M83), your filament usage calculation will be massive.
10. Real-World Application
OctoPrint / Mainsail.
These interfaces run a G-code engine in JavaScript/Python to show you "Time Remaining".
Slicers run a very accurate one to tell you "1 hour 23 minutes".
Firmware runs a real-time one to drive the motors.
11. Final Clean Version
The Engine:
import math
class GcodeEngine:
def __init__(self):
self.x = 0
self.y = 0
self.z = 0
self.e = 0
self.f = 1000 # mm/min
self.total_time = 0 # seconds
self.total_filament = 0 # mm
self.relative_mode = False
self.relative_e = False
def process_file(self, filename):
with open(filename, 'r') as f:
for line in f:
self.process_line(line)
def process_line(self, line):
line = line.strip().upper().split(';')[0] # Remove comments
if not line: return
parts = line.split()
cmd = parts[0]
if cmd == "G90": self.relative_mode = False
if cmd == "G91": self.relative_mode = True
if cmd == "M82": self.relative_e = False
if cmd == "M83": self.relative_e = True
if cmd in ["G0", "G1"]:
# Parse args
new_x, new_y, new_z, new_e = self.x, self.y, self.z, self.e
new_f = self.f
# (Simplified parsing logic - assumes X,Y,Z,E,F order or key-value)
# In reality, need a robust parser
for part in parts[1:]:
val = float(part[1:])
if part.startswith("X"): new_x = (self.x + val) if self.relative_mode else val
if part.startswith("Y"): new_y = (self.y + val) if self.relative_mode else val
if part.startswith("Z"): new_z = (self.z + val) if self.relative_mode else val
if part.startswith("E"):
if self.relative_e:
self.total_filament += val
new_e = self.e + val
else:
diff = val - self.e
if diff > 0: self.total_filament += diff
new_e = val
if part.startswith("F"): new_f = val
# Calculate Distance (XYZ only)
dist = math.sqrt((new_x-self.x)**2 + (new_y-self.y)**2 + (new_z-self.z)**2)
# Calculate Time (Simple)
if dist > 0:
time = dist / (new_f / 60.0) # mm / (mm/s)
self.total_time += time
# Update State
self.x, self.y, self.z, self.e, self.f = new_x, new_y, new_z, new_e, new_f
def report(self):
print(f"Total Time: {self.total_time/60:.2f} minutes")
print(f"Total Filament: {self.total_filament/1000:.2f} meters")
# Usage
engine = GcodeEngine()
engine.process_file("test.gcode")
engine.report()
12. Stretch Challenge
Challenge: Implement Bounding Box Tracking.
Track min_x, max_x, min_y, max_y, max_z.
At the end, report the print volume.
This is useful to check if a print fits on your bed (e.g., "Error: Max X 250 > Bed 220").