New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Conditional Gcode Syntax for Custom Gcode #3390

Closed
lordofhyphens opened this Issue Jun 30, 2016 · 35 comments

Comments

@lordofhyphens
Member

lordofhyphens commented Jun 30, 2016

So with a simple post-processing script like the one below, I'm looking at proposing a simple grammar for some conditional statements in gcode. What I was thinking about was coming up with a simple grammar with references that can be added by the post-processor. It'd be based around parsing the numerical result and parsing everything around the statement and variable/value and running the comparison in perl. If the condition is false, the line is removed. Starting with a semicolon means that if it breaks the gcode itself isn't run.

No consideration for else is currently proposed, and also planning to limit to single-variable, although I think the grammar could be extended to arbitrary numbers of evaluations.

Just in case, I want to avoid just calling eval() and do some basic sensitization to avoid someone doing weird things to your system through this mechanism.

[variable] is some slic3r variable and the value will be filled in. We'll use ; to delineate the blocks.

;_if [variable] == value; gcode

Example, set the speed factor to 50% if the current_extruder is 2 (for toolchange or layer change or even start/stop gcode).

;_if [current_extruder] == 2; M220 S50

if [current_extruder] is 2, then the processor would see:
;_if 2 == 2; M220 S50
and print:M220 S50 in the resulting gcode.

I believe the leading _ in the keyword is important to make it less likely that an actual comment doesn't trigger the logic; additionally the leading ; (indicates the start of a comment for most, if not all, gcode parsers ) means that the default state of execution is no execution.

@jaredabrandt001

This comment has been minimized.

jaredabrandt001 commented Jun 30, 2016

Conditional statements in gcode would be wonderful! I think Cura supports IF statements, where when true, the line is included and if false the line is excluded. I use Slic3r because it processes thin walls better. I'd suspect this simple conditional argument could take care of 99% of the circumstances printer would encounter. It would also greatly simplify wipe and prime code, and could easily be used to trigger certain operations (such as bed temp adjustments) at certain layers. Something like: IF ([layernum] == 25) M190 S45;
It's my experience that bed heat always needs to be turned down after the first few layers; it makes the print less likely to pull off the bed and it saves a lot of electricity.

Is there any way to get slic3r to process these without the post-processing step? It would be ideal if this were built-in, but I understand I might be asking too much.

Overall though, thanks for looking into this. I think it will make Slic3r better!

@lordofhyphens

This comment has been minimized.

Member

lordofhyphens commented Jun 30, 2016

@lordofhyphens

This comment has been minimized.

Member

lordofhyphens commented Jul 1, 2016

@alranel

This comment has been minimized.

Member

alranel commented Jul 1, 2016

I remember discussing this in another issue here in GitHub. I was considering a syntax like this:

{if layer_z > 2}M104 S210
{if layer_num % 5}G28 X
@lordofhyphens

This comment has been minimized.

Member

lordofhyphens commented Jul 1, 2016

What appealed to me with the leading semicolon is that if it goes unprocessed you see the results in the output gcode and the gcode is not executed.

@alranel

This comment has been minimized.

Member

alranel commented Jul 1, 2016

I appreciate the thing, but I'm not happy to have eval in the Slic3r codebase. It's a huge security hole. I could basically execute code on your system by giving you a config.ini file.

@lordofhyphens

This comment has been minimized.

Member

lordofhyphens commented Jul 1, 2016

@alexrj agreed re: security hole; I just am inexperienced at writing safe parsers in Perl. I will not merge the open PR until you're at least satisfied with it or I can reliably show that there will not be a security problem with the final project.

Some have asked about this going into the main executable; do you have any preferences w/r/t that?

@lordofhyphens lordofhyphens added this to the 1.4. milestone Jul 1, 2016

@lordofhyphens lordofhyphens self-assigned this Jul 1, 2016

@alranel

This comment has been minimized.

Member

alranel commented Jul 1, 2016

I still prefer the {} syntax as I don't see why would it go unparsed. For being embedded in Slic3r the parser should be a real parser, and it should be implemented in C++. It should also allow this:

{if layer_num % 5}{if layer_z > 10}G28 X

For avoding eval in the post-processing script you can define a set of valid constructs (var + operator + value) and implement them as strict regexes.

@lordofhyphens

This comment has been minimized.

Member

lordofhyphens commented Jul 1, 2016

Well, it was more important when it was running as a post-processing script, as a parser failure would mean that it doesn't do anything (at least in my limited experience with them). If we pack it into the C++ side your syntax makes more sense.

If we're going the C++ route, I'd like to come up with a few variants on the syntax so we can do some more arbitrary math operations, mostly along the lines of returning the value of, say {nozzle_diameter - layer_z}.

@bubnikv

This comment has been minimized.

Contributor

bubnikv commented Jul 8, 2016

For writing C++ parsers, the usual pair of tools: Lex & Yacc (flex and bison in the GNU world) are suitable. I used these tools to write a domain specific interpreted language.

@platsch

This comment has been minimized.

Member

platsch commented Nov 14, 2016

I think this is an important feature. I personally prefer an implementation in C++ because I expect this to be a frequently used feature.
However, I just needed a quick solution and modified @lordofhyphens pp-script to a set of predefined operators: https://github.com/platsch/Slic3r/blob/electronics/utils/post-processing/conditional.pl.
I'm aware that this is probably not an optimal solution, but at least avoids the use of eval.

@bubnikv

This comment has been minimized.

Contributor

bubnikv commented Nov 14, 2016

I think something like this
https://github.com/codeplea/tinyexpr
or this
http://partow.net/programming/exprtk/index.html
or this
https://fastmathparser.codeplex.com/
could be integrated for expression evaluation. Just take everything in the curly braces and throw it to the library.

Vojtech

@bubnikv

This comment has been minimized.

Contributor

bubnikv commented Nov 14, 2016

or maybe the boost::spirit library
http://www.boost.org/doc/libs/1_62_0/libs/spirit/doc/html/
boost::spirit provides some exmaples of calculators.

@ArashPartow

This comment has been minimized.

ArashPartow commented Nov 25, 2016

@bubnikv

This comment has been minimized.

Contributor

bubnikv commented Nov 25, 2016

@bubnikv

This comment has been minimized.

Contributor

bubnikv commented Nov 25, 2016

@ArashPartow I see you made yourself an advertisement :-) I have to look into your library then.

@alranel

This comment has been minimized.

Member

alranel commented Nov 25, 2016

Let's draft a syntax spec.

Conditional expressions

  • Conditional expressions must follow one of these syntaxes: {if VARIABLE OPERATOR VARIABLE} or {if VARIABLE OPERATOR CONSTANT}.
  • If a conditional expression evaluates to false, all the characters until the end of the line are removed.
  • If a conditional expression fails to parse, it's silently left untouched.
  • In order to avoid a complicated block syntax, a condition can be applied to multiple lines just by repeating it for each line:
    {if layer_num == 10}M104 S210
    {if layer_num == 10}M600
    
  • An AND combination can be performed by chaining multiple conditions:
    {if layer_num == 10}{if temperature_1 != 210}M104 S210
    
  • An OR combination can be performed by repeating the line:
    {if layer_num == 10}M104 S210
    {if z > 2.0}M104 S210
    

Value expressions

  • Any expression enclosed in two curly brackets, but not starting with {if is evaluated as an arithmetic expression: {foo - bar}.
  • If evaluation fails, the expression is silently left untouched.
  • If any float variables are used, return value will have decimals. If string variables are mixed with numeric variables, they are parsed as floats if they have a dot, or ints otherwise.
  • Value expressions can be used in conditional expressions by nesting them: {if {foo - bar} > 10}.

What do you think? Would this cover all the possible needs? We should try to list all the possible usage cases before implementing in order to make sure it's correctly designed. @lordofhyphens, @platsch, what did you use the script for?

  • Homing every 'n' layers.
  • Inserting a pause or a filament change or other commands every 'n' layers or at given Z intervals (taking a picture, moving a servo etc.).
  • ...?

I can't think about examples of using math. Do we actually need it? It's much more work than just plain conditionals.

(FYI, LinuxCNC supports another syntax but it looks like there's no true standard.)

@bubnikv

This comment has been minimized.

Contributor

bubnikv commented Nov 25, 2016

By the way, one can use the perl Safe module to sandbox an eval
http://perldoc.perl.org/Safe.html

Also looking at the very cool library by @ArashPartow , it does much more than just expression evaluation. It contains quite a powerfull conditional processor and it could do expressions over strings. Only it seems to me that the library in current form cannot return strings as a result of an expression, only numeric values.

@platsch

This comment has been minimized.

Member

platsch commented Nov 25, 2016

@alexrj the OR combination works for your example, but if both conditions are true, the gcode would be inserted twice. That is no problem for a temperature command, but could be e.g. for priming / retraction.

As for the use cases: I'm running a multi-extruder setup with very different materials (normal plastic and conductive ink from a syringe extruder). I need to clean the ink-extruder after each toolchange by swiping it with a sponge, but I can not execute the same toolchange commands for the plastic extruder since the hot nozzle would burn my cleaning area.

Currently, I see only one reason for math expressions: solving the extruder numbering issue #3574...

@lordofhyphens

This comment has been minimized.

Member

lordofhyphens commented Nov 25, 2016

@3daybreaker

This comment has been minimized.

3daybreaker commented May 30, 2017

With respect to you development guys, I would like to refer to a very simple use case, that I need myself very bad:

If layer = n, move extruder to x=zero and pause the printer, keep all temps as they are.

This would allow me to predefine all points where a color change would be necessary. It would skipp the need to stay awake and guard the printer not to miss the exact layer.

I think that conditional Gcode could solve this, but a separate {'Change Filament at 'n' } function could do the job too :-)

Thanks
Thomas

@lordofhyphens

This comment has been minimized.

Member

lordofhyphens commented May 31, 2017

@alexrj the OR part of the spec has a hidden surprise in it -- the code will be executed multiple times if multiple conditions match, which can be dangerous depending on the code.

I think it would be safer (in terms of user expectations) to borrow | and & and/or keywords and and or and permit use of () to permit generally arbitrary Boolean expressions. Three days after the feature goes live someone will want to use "A and (B or not C)" logic and run into a wall.

I'd also like add a not (or !) keyword {if not ... } to signify general negation of the following predicate.

I am fine with leaving an else structure out of the clause if we have a general negation.

@lordofhyphens

This comment has been minimized.

Member

lordofhyphens commented May 31, 2017

I've adapted the spec by @alexrj and added my comments:

https://github.com/alexrj/Slic3r/wiki/Conditional-Gcode-Syntax-Spec

We can add comments at the bottom there or just revise the page.

@lordofhyphens

This comment has been minimized.

Member

lordofhyphens commented Jun 1, 2017

https://github.com/taocpp/PEGTL looks promising. Header-only. Longer compile times for the grammar, but looks pretty simple.

@lordofhyphens

This comment has been minimized.

Member

lordofhyphens commented Jun 1, 2017

I did some experimenting around with the calculator sample and I think I have something workable w/r/t parsing.

lordofhyphens added a commit that referenced this issue Oct 21, 2017

Math evaluation in gcode (#4157)
* Prototype gcode infix math, very basic.

* Adding exprtk math parser library, header only.
@ArashPartow
https://github.com/ArashPartow/exprtk@4e1315a87dcc99a1ccad21fae1def0c2d4913c0f

* Now evaluating strings with exprtk, only support no variables in input
strings.

* Moved executable code to cpp file, stubbed out xsp and let the testing begin...

* Added conditional gcode parser into export path, added tests.

* Added one more test to ensure that {if0} only removes up to newlines.

* Test failure to parse

* Add some compiler flags to compile out stuff from exprtk

* Fix debug messages to be more specific, don't use deleted stringstream = method.

* Trade expression speed for apparently around 50MB of object size.

* Removed an extra trim that was breaking existing tests.

* fix test

Fixes #3390
@VanessaE

This comment has been minimized.

Collaborator

VanessaE commented Jan 26, 2018

So, since @alexrj has not updated the Slic3r manual..... how much of this is actually implemented, as of commit 6dd4009?

I tried a simple expression in my filament g-code: {if [solid_infill_speed] < 241}M900 K35

...but Slic3r just evaluates the [solid_infill_speed] placeholder, and then writes the resulting expression into the g-code, instead of just the conditional part. Literally, {if 240 < 241}M900 K35 appears in the file.

@bubnikv

This comment has been minimized.

Contributor

bubnikv commented Jan 26, 2018

@lordofhyphens lordofhyphens reopened this Jan 26, 2018

@lordofhyphens

This comment has been minimized.

Member

lordofhyphens commented Feb 20, 2018

@VanessaE I missed one of the application points, it should work now.

@BETLOG

This comment has been minimized.

BETLOG commented Mar 9, 2018

layer_height's uppercase equivalent is absent from env | grep ^SLIC3R

...min_layer_height and max_layer_height are present however.

I hope this is the right place to comment, I see references to other places that don't exist or don't seem to be commentable.

@curieos

This comment has been minimized.

Contributor

curieos commented Mar 12, 2018

Has this feature been implemented? It looks like (from the more recent references and merges linked on the history) math expressions are evaluated, but conditional expression are not? I'm interested in this for SoC this year. I'm also on IRC if someone wants to discuss there.

@3daybreaker

This comment has been minimized.

3daybreaker commented Mar 12, 2018

Which version should I download to play with/ test/use conditional expressions ?
I am on 1.3.0 dev and the manual does not reference any conditional code as I see it.

rgrd Thomas

@lordofhyphens

This comment has been minimized.

Member

lordofhyphens commented Apr 13, 2018

@3daybreaker https://github.com/slic3r/Slic3r-Manual/blob/master/src/advanced/conditional-gcode.md is the manual entry for it, mostly copied from the wiki.

@curieos yes, it has been implemented by me and separately by @bubnikv for the prusa3d fork. His implementation is likely better than mine.

@bubnikv I'm going to close this issue here and open a separate issue to port the prusa3d version over.

@zbrozek

This comment has been minimized.

zbrozek commented Jun 6, 2018

I almost managed to use this to make a smooth-varying nozzle temperature test tower. But I needed a way to cast to integers. Could we add a way to turn floats into int?

@bubnikv

This comment has been minimized.

Contributor

bubnikv commented Jun 6, 2018

As @lordofhyphens considers to port the Prusa3D expression evaluator to upstream, I thing my response fits.

If you try the Prusa3D fork, the ints are converted to floats when used in a float context. For example, adding a float to int results to a float.

https://github.com/prusa3d/Slic3r/wiki/Slic3r-Prusa-Edition-Macro-Language

@zbrozek

This comment has been minimized.

zbrozek commented Jun 9, 2018

I just tried it anyway, despite not seeming to match the format, it looks like my printer does consume floats in the nozzle temperature field. So my gcode worked despite looking like it shouldn't.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment