gcode-rs icon indicating copy to clipboard operation
gcode-rs copied to clipboard

Consider Gcode transformation support?

Open gagath opened this issue 6 years ago • 6 comments

Hello,

In this implementation I manage to operate very basic gcode transformation such as translation. I am thinking of implementing more transformations like linear transformations, which would lead to features such as rotate on Z axis and mirror X/Y.

Do you think this create can integrate such features? Since it is aimed at embedded devices such as Cortex-M microcontrollers, it may increase crate size a little. But if the linker is smart enough maybe it can reduce final size by removing unused functions.

What do you think? This crate could contain various Gcode-processing functions, added to actually implemented parsing.

gagath avatar Nov 16 '18 09:11 gagath

I think it's a great idea! The approach I'd probably take is to add various wrapper types which let you build up the full translation operation. Kinda like how you can use map() and filter() with iterators.

The only difficult part would be adding new comments, seeing as we currently use borrowed strings and both the new comments and the original ones would need the same lifetime. We can probably work around that by using Cow<str> though.


What transformations and operations would you like to support?

Michael-F-Bryan avatar Nov 16 '18 10:11 Michael-F-Bryan

Yes, I was also thinking of an API that could look like this:

gcode.transform()
    .rotate_clockwise(90.0)
    .mirror_x()
    .translate_x(10)
    .linear_transfrom(0, 1, -1, 0)
    .apply()

The order of the operations is important. For example, the rotate_clockwise and mirror_x functions can lead to a single linear transformation since both operations are linear transformations. However, translate is not linear, so we should apply it before grouping other linear transformations.

The call to linear_transform(0, 1, -1, 0) is the same as calling rotate_conterclockwise(90). For example, mirror_x() would be linear_transform(0, 1, 1, 0).

gagath avatar Nov 16 '18 17:11 gagath

Hmm... That's a lot more high-level than I was intending. I was thinking something like map_g(01, 'X', |x| X+50.0) to map the X argument of G01. So instead of working at a semantic level ("rotate about the X-axis by 45 degrees") we work at the syntactic level ("multiply the XYZ arguments of G00, G01, G02 by this matrix"). You can still compose high-level transformations out of these low level primitives, but it means the transformations in this crate aren't making any assumptions about the gcodes you provide.

The annoying bit is that gcode is only semi-defined, with different manufacturers having their own slightly different (and sometimes conflicting) interpretations. So it's not like there's a definitive, all-encompassing spec we can code to.

Michael-F-Bryan avatar Nov 17 '18 01:11 Michael-F-Bryan

Hey @MicroJoe, how does this look to you? I'm planning to allow syntax-level transformations instead of the more high-level stuff, but I've introduced a Predicate<T> trait and implemented it for loads of things (matching on a list of major numbers, anything with a particular argument, etc) to hopefully make it easier to compose more powerful transformations.

use gcode::{Argument, GcodeTransforms, Span};

let f = Argument {
    letter: 'F',
    value: 10_000.0,
    span: Span::placeholder(),
};

// append the `f` argument to all gcodes with a major number of 0 or 1.
let gcodes = gcode::parse("G90 G00 X5.0 Y-2.2 G01 X10.0 Z-2.0")
    .map_gcode([0, 1], |gcode| gcode.with_argument(f));

for gcode in gcodes {
    let major = gcode.major_number();
    if major == 0 || major == 1 {
        assert_eq!(gcode.value_for('F'), Some(10_000.0));
    }
}

I'll also add a BlockTransforms extension trait so you can do things to entire Blocks (e.g. append a comment after a particular gcode). That way you can process an entire gcode file and save it as text which should retain comments and resemble the original code, which sounds like your use case.

Michael-F-Bryan avatar Nov 18 '18 08:11 Michael-F-Bryan

Hello,

Thanks a lot for designing this. My usecase for the moment would be to map to XY to do basic transforms, so with this API it would look like this:

let x_offset = 42.0;
let gcodes = gcode::parse("G90 G00 X5.0 Y-2.2 G01 X10.0 Z-2.0")
let transformed_gcode = gcodes.map_gcode(
    [0, 1],
    |gcode| gcode.with_argument(
        Argument::new('X', gcode.value('X').unwrap() + x_offset));

Is that correct? (quick though so in reality something better that unwrap is used)

gagath avatar Nov 20 '18 09:11 gagath

Is that correct?

I believe so, although you'd probably use something like .unwrap_or(0.0).

If we want to round-trip gcode and have it (roughly) resemble the original source code, the Display impl will probably need some more work. We retain the original Span information as well as comments, so it should be possible to get ordering and whitespace right.

Michael-F-Bryan avatar Nov 23 '18 10:11 Michael-F-Bryan