Status: experimental, not yet released
Updates:
- 2022/11/29: covers 0.1.6 API
- 2022/10/31: published
- 2022/10/28: starting write up
Table of Contents
Introduction
As I was developing the 5-axis PAX printhead, and implement Inverse Kinematic (IK) for it in RepRapFirmware (RRF) for Duet3 board, the development speed was slow due the overhead to get to know the RRF written in C++, hence very verbose code, and the “recompiling, uploading, and reboot of the board” cycle to iterate the development – which turned out to be very slow and tedious.
My first remedy was to do a pre- or post-processor which takes tool coordinates and converts into motor positions applying IK, and Print3r supports it via declaring a post-processor like --post_something=something %i %o
and then use with --post=something
, but would only be a Print3r-centric solution.
So I thought, why not do a virtual serial device, and have a framework where the controller behaves like a physical one, but is pure software and thereby shorten development cycle to convert G-code coming out of a slicer or some software and then being processed and then sent to the 3D printer or robot – so the Virtual G-code Controller (vgcodectl
) was born.
It’s main feature is that it operates bidirectional:
- it takes input and optionally changes it, and outputs it to a file or a device
- it takes output from a device, and forwards it back to the virtual serial to so it can read back transparently
in essence it operates as in bidirectional intermediary looking like a serial port and thereby can be integrated into existing G-code-based machine park.
slicer/console/controller ⇆ physical device
↓
slicer/console/controller ⇆ vgcodectl ⇆ physical device
as a result the setup looks like a capability extended device:
Features
- simple to write G-code extensions with Python:
- execute macros and calculate additional values
- code execution from G-code
- synchronize G-code with external devices, e.g. webcam
- extend drivers/controller, like Inverse Kinematics (IK) which otherwise would run on the controller
- live reloading of controller code: change printer/robot behavior while it’s processing G-code
- fast iteration of code due scripting (no compiling, uploading or reboot)
- supported platforms: Linux
Limitations
- line-based processing: one line comes in, one line or multiple lines comes out (future version might lift this constraint)
Usage
% vgcodectl -h
VirtualGcodeController 0.1.4 USAGE: [<opts>]
options:
--help or -h this help
--version display version & exit
--verbose=<n> increase verbosity
-v or -vvv
--quiet or -q don't output to device or console anything unless an error
--output=<device/file> set output, e.g. /dev/ttyUSB0 or file or another device (default: /dev/stdout)
-o <device/file>
--controller=<ctl> set controller (default: conf['output'])
-c <ctl>
--keep-orig or -O enable to keep original g-code line as comment (default: off)
--keep-comment or -C enable to keep original comment (default: off)
hint: --keep-orig/-O includes comment as well
--serial-speed=<s> set serial speed [baudrate] (default: 115200)
--serial-timeout=<s> set serial timeout [s] (default: 0.1)
--check-code-timeout=<s> set code checking timeout [s] (default: 1)
--output-value-format=<fmt> set output value format (default: .4f)
examples:
vgcodectl --output=/dev/ttyUSB0 &
cat test01.gcode > pass0.pty
print3r --device=pass0.pty print tests/test01.gcode
print3r --device=pass0.pty print --scad 'cube(20)'
vgcodectl -o /dev/ttyACM0 -c gcode+ -Ov
Let’s try pass2
controller, which doubles X, Y and Z coordinates.
As first we start the controller:
% vgcodectl -c pass2 -O
== VirtualGcodeController 0.0.3 == https://github.com/Spiritdude/VirtualGcodeController
; VirtualGcodeController 0.0.3, created 2022-10-28T13:23:23.513285
; verbose = 0
; output = "/dev/stdout
; serial_speed = 115200
; controller = "pass2"
In another terminal we take a sample G-code and send it into pass20.pty
serial port (“pass2” plus the “0” indicates the first instance of pass2
controller):
% cat tests/test01.gcode > pass20.pty
and the vgcodectl
outputs to stdout
as it is the default:
G28 X0 Y0 ; G28 X Y
G92 ; G92 ; reset
G90 ; G90 ; abs coords
G0 X200 Y200 ; G0 X100 Y100
G1 X200 Y220 E1.2000 ; G1 X100 Y110 E1
M84 ; M84
As you notice, the new G-code is altered, e.g. X, Y and Z is doubled, the E is multiplied by 1.2 in this case, and the original G-code is appended as comment due the -O
switch used (--keep_orig
or -O
).
If we did call vgcodectl
with --output=/dev/ttyACM0
the end point would be a real 3D printer for example at /dev/ttyACM0
.
Print3r
% print3r --printer=ashtar-k-3 --device=pass20.pty print tests/test01.gcode
== Print3r 0.3.18 == https://github.com/Spiritdude/Print3r
print3r: conf: device pass20.pty, Ashtar K #3 E3 38x30x33, build/v 300x300x330mm, nozzle/d 0.4mm, layer/h 0.25mm, filament/d 1.75mm
print3r: authenticated "Ashtar K #3 E3 38x30x33" (8a09209a-1b93-11ed-861d-0242ac120002) at pass20.pty
print3r: print: 0h 00m elapsed, eta 0h 00m, 100.0% complete, z=0.00mm, layer #0, filament 0.00m
as indicated, the pass20.pty
operates bidirectional, reports back the proper UUID as requested by print3r
and then sends G-code forward and does proper synchronous messaging back and forth.
print3r
⇆ pass2*.pty
:vgcodectl
⇆ /dev/ttyACM0
Controllers
Following controllers are available:
pass
: it’s a simple pass-through changing no code, for debugging purposespass2
: double X, Y and Z, and multiply E by 1.2- don’t use with actual 3D printer, only for debugging purposes
scale
: scaling X, Y, Z and E, rather experimental / for debugging purposes- CLI argument: use
--scale=
<s> to set scale in , e.g.0.8
(default:1.0
)
- actually produces some working G-code
- CLI argument: use
gcode+
: writing low-level G-code and replaceE
automatically based on distance- use
G0 X100 Y100
and thenG1 Y110 E
and theG1
‘s extrusion will be calculated - actually produces some working G-code
- CLI arguments:
--layer-height
=<lh> [mm] (default:0.2
)--line-width
=<lw> [mm] (default:0.4
)--filament-diameter
=<d> [mm] (default:1.75
)
- use
delta
: implementing cartesian XYZ -> T1,T2,T2 motor angles, using Delta printer inverse kinematicspax
: implementing 5-axis inverse kinematics for PAX printhead5axis
: generalized 5-axis driver, define forward kinematics, automatic inverse kinematics calculated:open5x
: Open5xpax
: PAX Printhead, same aspax
controller, but defined within5axis
controller
macros
: implementing RepRapFirmware’sM98
functionality
Download
- [coming soon] Github: VirtualGcodeController (vgcodectl)
Writing A Controller
Note: vgcodectl
is currently under heavy development, API subject to change a lot – check back this page frequently.
This covers vgtcodectl
0.1.6 API:
controllers/
<controller>
/
:main.py
- must contain
def _map(self,c=None)
function, in there you can recalculate any existing G-code, thec
contains a dictionary with all the G-code values, the function returns- a dict of the remapped variables, e.g.
{ "G": 0, "X": 100.0 }
– note: order matters, make sure ‘G’ or ‘M’ key comes first for G or M commands - a string, e.g.
"G0 X100.0"
orG0 X100.0\nG1 X110 E1.2"
- a tuple or list of
- strings, e.g.
[ "G0 X100.0", "G0 X110.0" ]
, each element is a line - dictionaries, e.g.
[ { "G": 0, "X": 100 }, { "G": 0, "X": 110 } ]
, each element is a line
- strings, e.g.
- if
None
is returned, no change of G-code is made and the original data is passed on - if
""
(empty string) is returned, nothing is passed on (mute)
- a dict of the remapped variables, e.g.
- optionally
def __init__(self,conf=None)
function be composed, where persistent state can be stored, and referenced in_map()
then; theconf
is the configuration ofvgcodectl
:- command line arguments
vgcodectl --test="ABC" ...
becomesconf['test']
available within__init__()
, the controller profileprofile.json
is loaded and available asconf['profile']
conf
asself.conf
then_map()
has access to it as well withself.conf['test']
orself.conf['profile']['name']
- command line arguments
- must contain
profile.json
(optional):- basic JSON file with follow keys:
name
(string): full name of the controllerversion
(string): version of the controller, e.g. “0.1.0”- and any other key value default relevant for the controller
- basic JSON file with follow keys:
e.g. controllers/mine/main.py
:
def __init__(self,conf=None):
pass
def _map(self,c=None):
if 'X' in c:
c['X'] *= 2
return c
then the controller can be referenced as such:
% vgcodectl -c mine -v
== VirtualGcodeController 0.0.7 == https://github.com/Spiritdude/VirtualGcodeController
vgcodectl: 2022-10-31T06:48:14.026650: loading <mine> profile
vgcodectl: 2022-10-31T06:48:14.026705: loading <mine> code
vgcodectl: 2022-10-31T06:48:14.027444: created mine0.pty (/dev/ptmx,/dev/pts/7)
vgcodectl: 2022-10-31T06:48:14.027473: opening /dev/ptmx
; VirtualGcodeController 0.0.7, 2022-10-31T06:48:14.027622
; verbose = 1
; quiet = 0
; output = "/dev/stdout"
; keep_orig = 0
; keep_comment = 0
; serial_speed = 115200
; serial_timeout = 0.1
; check_code_timeout = 1
; output_value_format = ".4f"
; ignore_codes = ["M117"]
; controller = "mine"
Scale Controller
The scale
controller is just an experiment, to scale G-code with a factor:
% vgcodectl -c scale --scale=0.8 -o /dev/ttyACM0 &
% print3r --printer=ashtar-k-3 print --scad 'cube(20)' --fill-density=0 --device=scale0.pty
References
- G-code Reference (reprap.org)