I’m becoming quite unimpressed with the quality of the software available for the hardware part of 3D printers. A quick look at SkyNet3D v1.1 makes me feel like it’s been hacked together. I am wondering if anyone has started working on a Rust based version of a G-code interpreter. I’ve seen several posts that Rust can run quite well on Arm processors and I’m looking for a project to possibly start on.

I don’t have a ton of Rust experience or 3D printing experience, but I have a solid knowledge of what it would take to put something like this together. I really need to focus my skill on something specific that doesn’t exist yet.

I don’t believe anyone has published anything for working with gcode, searching “gcode” and “cnc” on crates.io and google don’t show any useful results.

I’ve considered doing something like this a couple times now. Having a #[no_std] library for parsing and interpreting a stream of gcode would be really useful for people wanting to do embedded systems and cnc-like applications.

What kind of stuff would you expect from a hypothetical library for interpreting gcode?

I also did a quick search and everything I find is C/C++ code written in a very non modular method. It seems like each variation of printer (Cartesian, polar, Delta) have just been #ifdef’d in where necessary. The code does work, but it’s spaghetti.

My idea is simple, design a library that is well structured and performance oriented. I’d love to support

Raspberry Pi / Compute module

ARM Cortex series

as targets with hardware simple at first with lots of testing to ensure accuracy.

Stuff I think would do well in a library like this is

Legible / fail-safe G-Code parsing

Open/Closed loop motion control via traits (possibly) so different types of devices can be well supported/maintainable

Configurable acceleration/deceleration curves

Auto-leveling bed as default

Well defined modules i.e.- MotionControl, LCD, GCode etc. other projects code is mostly everywhere as far as I can tell.

Anyway I’m sure a #[no_std] library is necessary here to support as many platforms as possible.

It sounds like what you’re asking for goes much further than a simple gcode interpreter. It’s fairly easy to create a “parser” which effectively takes in an iterator of characters and then transforms that into an iterator who’s item is Result<GCode, Error>. Gcode itself is a very easy language to parse and I could probably knock up something like that in 100-200 lines of code.

What you are asking for extends much further though, and would probably end up being a fairly in-depth project.

The top-level design is fairly straightforward though. You’d set up the 3D printer library to have some sort of Controller struct which takes in a stream of characters (the gcode instructions), and your printer’s implementation of some Printer trait. The controller then reads the instructions and uses your Printer implementation as a driver to execute the instructions.

Overall what you are talking about is definitely doable. With Rust it’s easy to design things in a way that is modular and extensible, and which use proper dependency injection so you don’t need to resort to a bunch of #ifdef statements to swap between printer implementations.

I’d be really keen to work on something like this if you want to take it further. Overall it would benefit the Rust embedded community massively, plus it sounds like a genuinely interesting project

+1 for separate crates where doable. A crate for parsing g-code would be useful for a desktop qaqc tool. Also, If the cnc control code only used a type that deserialized into a g-code enum, then users could swap out for one of the binary formats that never got much traction.

A streaming gcode parser which can would be work regardless of if the gcodes are read from some file on a SD card, or if it were receiving via a serial line (maybe it reads from a buffered reader or whatever?)

A platform-agnostic “controller” which can take in a stream of instructions and execute them using some platform-specific Driver trait

At least one Driver implementation so we can test this out on actual hardware

Rust looks to be a really nice language for this! Having some generic Driver trait means you’d easily be able to test out and simulate a gcode program without needing to do any difficult hacking or code copying. You just write a Driver implementation which records what it does for playback later. Plus you get modularity and crates.io out of the box.

@japaric may have some interesting ideas for this. He’s currently doing a lot to help further the Rust embedded ecosystem and has a heap of experience with microcontrollers.

Very nice. I will start on a simply math crate for doing the motion
control. And try to come up with some simple test files and a list of
G-Codes we should support. I think the Controller should perform the
interpolation and create a buffer of positions and then the Driver can
perform any coordinate translations for the different types of machines.

Man I am not used to writing Rust with no_std! I wrote an entire vector math file and then remembered to add #![no_std] and realized there is no operator overloading! I guess my hint should have been ‘use std::…’

What is the suggested method of overcoming this limitation? Just write all the code with no operator overloading?

Yeah, I considered it. But the problem with a lot of the libraries out there is it takes longer to learn how to use them than to write a simple recursive descent parser. Plus they often give really cryptic error messages during development due to the use of generics or unreadable macros (I’m looking at you nom).

So yeah, for a super simple line-base language like gcode I thought I’d roll my own recursive descent parser. I’ve written my fair share of interpreters and compilers in the past, so I’m hoping it shouldn’t be overly difficult.

I agreed that G-code is quite easy to parse. I had hoped to include almost no external libraries just to decrease any external dependencies. Not using the standard library should enable a fairly easy transition to running on embedded platforms. Smoothieboard is around the level of minimum hardware I’d like to support. 8 bit AVRs running at 16 MHz isn’t enough, although it may be possible once these are supported by Rust. I’d like to think an STM32F0 running at 48 MHz should be enough to run just g-code parsing and motion control of a 4 axis machine. I do want to design my own board simply because I want to include an option for closed loop motion control. Of course first step is to get g-code parsing and a stepper moving

to run tests with initially. These are low cost, Arm based systems, with at least 2 having Rust support already. I’ll also have an ANET A8 3D printer soon and it has an Atmega1284 processor, far to slow in my opinion.

Interesting to note that the only the RaspberryPi has floating point hardware support. I will have to get a Teensy 3.5 & 3.6 to try as well and note the difference.

@iAlwaysLoseThis, how strict do you think the gcode parser should be? I can make the parser quite strict so it’ll only accept valid gcode commands. Or I could make it a bit more relaxed so if you were to pass in R30.0 to a command which doesn’t accept an R command, it’ll just be ignored.

At the moment I’m thinking I’ll ignore invalid stuff, but log it at the info level.

I should hope that unless your writing g-code by hand there shouldn’t be any syntax errors. Mostly just trying to move the tool pass the end stops. My opinion is relaxed and just log errors as we go. Don’t forget to include E as an axis token (Extruder), I’m not sure how multi-head extrudors work yet. Also I love your power implementation.

Haha oh, you’re talking about this? Yeah I’m not proud of it, but it’s about the only thing I could think of to get a floating point number from the fractional and integer parts and doesn’t rely on std or nightly-only stuff. That naive implementation shouldn’t be overly expensive though because most numbers used in CNC/3D printing would only go out to a couple decimal places.

I’m having a couple troubles representing a command without any dynamic memory allocation though. Turns out it’s not really possible to have a list of arguments of unknown length with no_std (a la Vec), so I’m thinking I’ll pull in arrayvec and settle for a reasonably sized buffer.

If you’ve got any decently sized gcode examples feel free to send them to me via pull request/issue. I’ve got a couple in the tests/data/ directory which I’m using for integration tests to ensure the parser and lexer can handle non-trivial programs.