mirror of
https://git.mirrors.martin98.com/https://github.com/slic3r/Slic3r.git
synced 2025-08-14 03:55:54 +08:00
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
This commit is contained in:
parent
4f2250dc70
commit
202a90ff90
@ -151,7 +151,7 @@ sub export {
|
||||
# set extruder(s) temperature before and after start G-code
|
||||
$self->_print_first_layer_temperature(0)
|
||||
if $self->config->start_gcode !~ /M(?:109|104)/i;
|
||||
printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->start_gcode);
|
||||
printf $fh "%s\n", Slic3r::ConditionalGCode::apply_math($gcodegen->placeholder_parser->process($self->config->start_gcode));
|
||||
foreach my $start_gcode (@{ $self->config->start_filament_gcode }) { # process filament gcode in order
|
||||
printf $fh "%s\n", $gcodegen->placeholder_parser->process($start_gcode);
|
||||
}
|
||||
@ -256,7 +256,7 @@ sub export {
|
||||
&& $self->config->between_objects_gcode !~ /M(?:190|140)/i;
|
||||
$self->_print_first_layer_temperature(0)
|
||||
if $self->config->between_objects_gcode !~ /M(?:109|104)/i;
|
||||
printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->between_objects_gcode);
|
||||
printf $fh "%s\n", Slic3r::ConditionalGCode::apply_math($gcodegen->placeholder_parser->process($self->config->between_objects_gcode));
|
||||
}
|
||||
$self->process_layer($layer, [$copy]);
|
||||
}
|
||||
@ -294,9 +294,9 @@ sub export {
|
||||
print $fh $gcodegen->retract; # TODO: process this retract through PressureRegulator in order to discharge fully
|
||||
print $fh $gcodegen->writer->set_fan(0);
|
||||
foreach my $end_gcode (@{ $self->config->end_filament_gcode }) { # Process filament-specific gcode in extruder order.
|
||||
printf $fh "%s\n", $gcodegen->placeholder_parser->process($end_gcode);
|
||||
printf $fh "%s\n", Slic3r::ConditionalGCode::apply_math($gcodegen->placeholder_parser->process($end_gcode));
|
||||
}
|
||||
printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->end_gcode);
|
||||
printf $fh "%s\n", Slic3r::ConditionalGCode::apply_math($gcodegen->placeholder_parser->process($self->config->end_gcode));
|
||||
print $fh $gcodegen->writer->update_progress($gcodegen->layer_count, $gcodegen->layer_count, 1); # 100%
|
||||
print $fh $gcodegen->writer->postamble;
|
||||
|
||||
@ -444,14 +444,14 @@ sub process_layer {
|
||||
my $pp = $self->_gcodegen->placeholder_parser->clone;
|
||||
$pp->set('layer_num' => $self->_gcodegen->layer_index + 1);
|
||||
$pp->set('layer_z' => $layer->print_z);
|
||||
$gcode .= $pp->process($self->print->config->before_layer_gcode) . "\n";
|
||||
$gcode .= Slic3r::ConditionalGCode::apply_math($pp->process($self->print->config->before_layer_gcode) . "\n");
|
||||
}
|
||||
$gcode .= $self->_gcodegen->change_layer($layer->as_layer); # this will increase $self->_gcodegen->layer_index
|
||||
if ($self->print->config->layer_gcode) {
|
||||
my $pp = $self->_gcodegen->placeholder_parser->clone;
|
||||
$pp->set('layer_num' => $self->_gcodegen->layer_index);
|
||||
$pp->set('layer_z' => $layer->print_z);
|
||||
$gcode .= $pp->process($self->print->config->layer_gcode) . "\n";
|
||||
$gcode .= Slic3r::ConditionalGCode::apply_math($pp->process($self->print->config->layer_gcode) . "\n");
|
||||
}
|
||||
|
||||
# extrude skirt along raft layers and normal object layers
|
||||
|
@ -19,7 +19,7 @@ $ENV{LD_RUN_PATH} //= "";
|
||||
# HAS_BOOL : stops Perl/lib/CORE/handy.h from doing "# define bool char" for MSVC
|
||||
# NOGDI : prevents inclusion of wingdi.h which defines functions Polygon() and Polyline() in global namespace
|
||||
# BOOST_ASIO_DISABLE_KQUEUE : prevents a Boost ASIO bug on OS X: https://svn.boost.org/trac/boost/ticket/5339
|
||||
my @cflags = qw(-D_GLIBCXX_USE_C99 -DHAS_BOOL -DNOGDI -DSLIC3RXS -DBOOST_ASIO_DISABLE_KQUEUE);
|
||||
my @cflags = qw(-D_GLIBCXX_USE_C99 -DHAS_BOOL -DNOGDI -DSLIC3RXS -DBOOST_ASIO_DISABLE_KQUEUE -Dexprtk_disable_rtl_io_file -Dexprtk_disable_return_statement -Dexprtk_disable_rtl_vecops -Dexprtk_disable_string_capabilities -Dexprtk_disable_enhanced_features);
|
||||
if ($cpp_guess->is_gcc) {
|
||||
# GCC is pedantic with c++11 std, so undefine strict ansi to get M_PI back
|
||||
push @cflags, qw(-U__STRICT_ANSI__);
|
||||
@ -211,7 +211,7 @@ if ($ENV{SLIC3R_DEBUG}) {
|
||||
push @cflags, $cpp_guess->is_msvc ? '-Gd' : '-g';
|
||||
} else {
|
||||
# Disable asserts in the release builds.
|
||||
push @cflags, '-DNDEBUG';
|
||||
push @cflags, '-DNDEBUG', '-O';
|
||||
}
|
||||
if ($cpp_guess->is_gcc) {
|
||||
# our templated XS bindings cause undefined-var-template warnings
|
||||
|
38195
xs/src/exprtk/exprtk.hpp
Normal file
38195
xs/src/exprtk/exprtk.hpp
Normal file
File diff suppressed because it is too large
Load Diff
132
xs/src/libslic3r/ConditionalGcode.cpp
Normal file
132
xs/src/libslic3r/ConditionalGcode.cpp
Normal file
@ -0,0 +1,132 @@
|
||||
#include <string>
|
||||
|
||||
#include <sstream>
|
||||
#include <exprtk/exprtk.hpp>
|
||||
#include "ConditionalGcode.hpp"
|
||||
namespace Slic3r {
|
||||
|
||||
// trim from start (in place)
|
||||
static inline void ltrim(std::string &s) {
|
||||
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
|
||||
return !std::isspace(ch);
|
||||
}));
|
||||
}
|
||||
|
||||
// trim from end (in place)
|
||||
static inline void rtrim(std::string &s) {
|
||||
s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
|
||||
return !std::isspace(ch);
|
||||
}).base(), s.end());
|
||||
}
|
||||
|
||||
// trim from both ends (in place)
|
||||
static inline void trim(std::string &s) {
|
||||
ltrim(s);
|
||||
rtrim(s);
|
||||
}
|
||||
/// Start of recursive function to parse gcode file.
|
||||
std::string apply_math(const std::string& input) {
|
||||
return expression(input);
|
||||
}
|
||||
|
||||
/// Evaluate expressions with exprtk
|
||||
/// Everything must resolve to a number.
|
||||
std::string evaluate(const std::string& expression_string) {
|
||||
std::stringstream result;
|
||||
|
||||
#if SLIC3R_DEBUG
|
||||
std::cerr << __FILE__ << ":" << __LINE__ << " "<< "Evaluating expression: " << expression_string << std::endl;
|
||||
#endif
|
||||
double num_result = double(0);
|
||||
if ( exprtk::compute(expression_string, num_result)) {
|
||||
result << num_result;
|
||||
} else {
|
||||
#if SLIC3R_DEBUG
|
||||
std::cerr << __FILE__ << ":" << __LINE__ << " "<< "Failed to parse: " << expression_string.c_str() << std::endl;
|
||||
#endif
|
||||
}
|
||||
std::string output = result.str();
|
||||
trim(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/// Parse an expression and return a string. We assume that PlaceholderParser has expanded all variables.
|
||||
std::string expression(const std::string& input, const int depth) {
|
||||
// check for subexpressions first.
|
||||
std::string buffer(input);
|
||||
std::stringstream tmp;
|
||||
|
||||
bool is_conditional = false;
|
||||
|
||||
auto open_bracket = std::count(buffer.begin(), buffer.end(), '{');
|
||||
auto close_bracket = std::count(buffer.begin(), buffer.end(), '}');
|
||||
if (open_bracket != close_bracket) return buffer;
|
||||
|
||||
auto i = 0;
|
||||
#ifdef SLIC3R_DEBUG
|
||||
std::cerr << __FILE__ << ":" << __LINE__ << " " << "depth " << depth << " input str: " << input << std::endl;
|
||||
#endif
|
||||
if (open_bracket == 0 && depth > 0) { // no subexpressions, resolve operators.
|
||||
|
||||
return evaluate(buffer);
|
||||
}
|
||||
while (open_bracket > 0) {
|
||||
// a subexpression has been found, find the end of it.
|
||||
// find the last open bracket, then the first open bracket after it.
|
||||
size_t pos_if = buffer.rfind("{if");
|
||||
size_t pos = buffer.rfind("{");
|
||||
size_t shift_if = ( pos_if >= pos && pos_if < buffer.size() ? 3 : 1 );
|
||||
is_conditional = (shift_if == 3); // conditional statement
|
||||
pos_if = (pos_if > buffer.size() ? pos : pos_if);
|
||||
|
||||
pos = (pos_if > pos ? pos_if : pos);
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
std::cerr << __FILE__ << ":" << __LINE__ << " "<< "depth " << depth << " loop " << i << " pos: " << pos << std::endl;
|
||||
#endif
|
||||
|
||||
// find the first bracket after the position
|
||||
size_t end_pos = buffer.find("}", pos);
|
||||
|
||||
if (end_pos > buffer.size()) return buffer; // error!
|
||||
if (pos > 0)
|
||||
tmp << buffer.substr(0, pos);
|
||||
|
||||
std::string retval = expression(buffer.substr(pos+shift_if, ( end_pos - ( pos+shift_if))), depth+1);
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
if (is_conditional) {
|
||||
std::cerr << __FILE__ << ":" << __LINE__ << " "<< "depth " << depth << " loop " << i << " return: '" << retval << "'" << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (is_conditional && retval == "0") {
|
||||
is_conditional = false;
|
||||
end_pos = buffer.find('\n', pos); // drop everything to the next line
|
||||
} else if (!is_conditional) {
|
||||
tmp << retval;
|
||||
} // in either case, don't print the output from {if}
|
||||
|
||||
if (end_pos < buffer.size()-1) // special case, last } is final char
|
||||
tmp << buffer.substr(end_pos+1, buffer.size() - (end_pos));
|
||||
buffer = tmp.str();
|
||||
|
||||
// flush the internal string.
|
||||
tmp.str(std::string());
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
std::cerr << __FILE__ << ":" << __LINE__ << " "<< "depth: " << depth <<" Result from loop " << i << ": " << buffer << std::endl;
|
||||
#endif
|
||||
|
||||
open_bracket = std::count(buffer.begin(), buffer.end(), '{');
|
||||
close_bracket = std::count(buffer.begin(), buffer.end(), '}');
|
||||
i++;
|
||||
}
|
||||
// {if that resolves to false/0 signifies dropping everything up to the next newline from the input buffer.
|
||||
// Also remove the result of the {if itself.
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
}
|
33
xs/src/libslic3r/ConditionalGcode.hpp
Normal file
33
xs/src/libslic3r/ConditionalGcode.hpp
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* https://github.com/alexrj/Slic3r/wiki/Conditional-Gcode-Syntax-Spec
|
||||
*
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
// Valid start tokens
|
||||
// {, {if
|
||||
//
|
||||
// Valid end tokens
|
||||
// }
|
||||
//
|
||||
// Special case:
|
||||
//
|
||||
// {if is special, it indicates that the rest of the line is dropped (ignored) if
|
||||
// it evaluates to False/0.
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/// Recursive expression parser. Offloads mathematics to exprtk.
|
||||
/// Precondition: All strings inside {} are able to be understood by exprtk (and thus parsed to a number).
|
||||
/// Starts from the end of the string and works from the inside out.
|
||||
/// Any statements that resolve to {if0} will remove everything on the same line.
|
||||
std::string expression(const std::string& input, const int depth = 0);
|
||||
|
||||
/// External access function to begin replac
|
||||
std::string apply_math(const std::string& input);
|
||||
|
||||
}
|
46
xs/t/24_gcodemath.t
Normal file
46
xs/t/24_gcodemath.t
Normal file
@ -0,0 +1,46 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Slic3r::XS;
|
||||
use Test::More tests => 6;
|
||||
{
|
||||
{
|
||||
my $test_string = "{if{3 == 4}} string";
|
||||
|
||||
my $result = Slic3r::ConditionalGCode::apply_math($test_string);
|
||||
is $result, "", 'If statement with nested bracket removes on false resolution.';
|
||||
}
|
||||
{
|
||||
my $test_string = "{if{3 == 4}} string\notherstring";
|
||||
|
||||
my $result = Slic3r::ConditionalGCode::apply_math($test_string);
|
||||
is $result, "otherstring", 'if false only removes up to newline.';
|
||||
}
|
||||
|
||||
{
|
||||
my $test_string = "{if{3 == 3}} string";
|
||||
|
||||
my $result = Slic3r::ConditionalGCode::apply_math($test_string);
|
||||
is $result, " string", 'If statement with nested bracket removes itself only on resulting true, does not strip text outside of brackets.';
|
||||
}
|
||||
{
|
||||
my $test_string = "{if{3 == 3}}string";
|
||||
|
||||
my $result = Slic3r::ConditionalGCode::apply_math($test_string);
|
||||
is $result, "string", 'If statement with nested bracket removes itself only on resulting true.';
|
||||
}
|
||||
{
|
||||
my $test_string = "M104 S{4*5}; Sets temp to {4*5}";
|
||||
|
||||
my $result = Slic3r::ConditionalGCode::apply_math($test_string);
|
||||
is $result, "M104 S20; Sets temp to 20", 'Bracket replacement works with math ops';
|
||||
}
|
||||
{
|
||||
my $test_string = "M104 S{a}; Sets temp to {4*5}";
|
||||
|
||||
my $result = Slic3r::ConditionalGCode::apply_math($test_string);
|
||||
is $result, "M104 S; Sets temp to 20", 'Blank string emittal on failure to parse';
|
||||
}
|
||||
}
|
18
xs/xsp/ConditionalGcode.xsp
Normal file
18
xs/xsp/ConditionalGcode.xsp
Normal file
@ -0,0 +1,18 @@
|
||||
%module{Slic3r::XS};
|
||||
|
||||
%{
|
||||
#include <xsinit.h>
|
||||
#include "libslic3r/ConditionalGcode.hpp"
|
||||
%}
|
||||
|
||||
|
||||
%package{Slic3r::ConditionalGCode};
|
||||
|
||||
%{
|
||||
PROTOTYPES: DISABLE
|
||||
std::string apply_math(std::string input)
|
||||
CODE:
|
||||
RETVAL = Slic3r::apply_math(input);
|
||||
OUTPUT:
|
||||
RETVAL
|
||||
%}
|
Loading…
x
Reference in New Issue
Block a user