Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c697794
Basically working gcode variable replacement
wawanbreton Jan 14, 2026
e2e690a
Clean and simplify code
wawanbreton Jan 14, 2026
3c3b947
Split code to files
wawanbreton Jan 14, 2026
3eef8fd
Use initial extruder by default for start gcode
wawanbreton Jan 14, 2026
0d18c03
Allow using variables to resolve extruder number
wawanbreton Jan 14, 2026
40c4eec
Apply GCode resolving on end GCode too
wawanbreton Jan 14, 2026
0b991bf
Use single regex with named capture groups
wawanbreton Jan 14, 2026
ff59ea4
Handle conditional start/end gcode
wawanbreton Jan 14, 2026
3c74844
Fix global settings not being retrieved from extruder settings
wawanbreton Jan 14, 2026
c83d7f1
Add unit tests (mostly ported from origin Python code)
wawanbreton Jan 15, 2026
a82cb17
Add template resolving to extruder start/end gcode
wawanbreton Jan 15, 2026
fed95ba
Add previous and next extruder number variables in extruder switch gcode
wawanbreton Jan 15, 2026
e3ae8e7
Add code documentation
wawanbreton Jan 16, 2026
1858524
Properly handle post slice variables
wawanbreton Jan 21, 2026
7d72041
Fix string expressions generating errors
wawanbreton Jan 21, 2026
02375ca
Use LocalEnvironment instead of extra settings
wawanbreton Jan 21, 2026
4cd5086
Merge branch 'main' into CURA-12556_formula-parser-in-CuraEngine
wawanbreton Jan 21, 2026
d9f67ad
Fix wrong extruder being used
wawanbreton Jan 21, 2026
4498ad3
Restore simplified unit test
wawanbreton Jan 21, 2026
2ac8ad3
Fix Emscripten build
wawanbreton Jan 22, 2026
ec4ce82
Fix Mac build
wawanbreton Jan 22, 2026
b0f1083
Use release conan package of the formulae engine
wawanbreton Jan 22, 2026
803e5af
Merge branch 'main' into CURA-12556_formula-parser-in-CuraEngine
rburema Feb 12, 2026
61d105c
Always provide next_extruder_nr and previous_extruder_nr
wawanbreton Feb 12, 2026
d80f17c
Use the extruder stack to get the expression's extruder number
wawanbreton Feb 12, 2026
276a43a
Use integer values for extra extruder numbers
wawanbreton Feb 12, 2026
c6b4680
Merge remote-tracking branch 'origin/main' into CURA-12556_formula-pa…
wawanbreton Mar 20, 2026
e3e62a2
Remove char*-based GCode writing methods
wawanbreton Mar 20, 2026
684bfb5
Add missing variable resolvement for extruder final end gcode
wawanbreton Mar 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ set(engine_SRCS # Except main.cpp.
src/FffProcessor.cpp
src/gcodeExport.cpp
src/GCodePathConfig.cpp
src/GcodeTemplateResolver.cpp
src/infill.cpp
src/InterlockingGenerator.cpp
src/InsetOrderOptimizer.cpp
Expand Down Expand Up @@ -142,6 +143,7 @@ set(engine_SRCS # Except main.cpp.
src/settings/FlowTempGraph.cpp
src/settings/MeshPathConfigs.cpp
src/settings/PathConfigStorage.cpp
src/settings/SettingContainersEnvironmentAdapter.cpp
src/settings/Settings.cpp
src/settings/ZSeamConfig.cpp

Expand Down Expand Up @@ -251,6 +253,8 @@ find_package(fmt REQUIRED)
find_package(range-v3 REQUIRED)
find_package(scripta REQUIRED)
find_package(PNG REQUIRED)
find_package(cura-formulae-engine REQUIRED)

if (NOT EMSCRIPTEN)
find_package(TBB REQUIRED)
endif ()
Expand All @@ -275,6 +279,7 @@ target_link_libraries(_CuraEngine
boost::boost
scripta::scripta
PNG::PNG
cura-formulae-engine::cura-formulae-engine
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:onetbb::onetbb>
$<$<TARGET_EXISTS:semver::semver>:semver::semver>
$<$<TARGET_EXISTS:curaengine_grpc_definitions::curaengine_grpc_definitions>:curaengine_grpc_definitions::curaengine_grpc_definitions>
Expand Down
2 changes: 2 additions & 0 deletions conandata.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
version: "5.14.0-alpha.0"
commit: "unknown"
requirements:
- "cura-formulae-engine/1.0.0"
- "scripta/[>=1.1.0]@ultimaker/testing"
- "libpng/1.6.48"
- "onetbb/2022.2.0"
- "zeus_expected/[>=1.1.1]"
requirements_arcus:
- "arcus/5.11.1"
requirements_plugins:
Expand Down
31 changes: 31 additions & 0 deletions include/GcodeTemplateResolver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) 2026 UltiMaker
// CuraEngine is released under the terms of the AGPLv3 or higher

#ifndef GCODETEMPLATERESOLVER_H
#define GCODETEMPLATERESOLVER_H

#include <cura-formulae-engine/eval.h>
#include <optional>
#include <string>
#include <unordered_map>

namespace cura::GcodeTemplateResolver
{

/*!
* Resolve a raw GCode template that can contains conditional code and complex formulas
* @param input The raw GCode template text
* @param context_extruder_nr The default contextual extruder number, which should be the current extruder number when dealing when an extruder start/end GCode, and nullopt when
* dealing with the global machine start/end gcode
* @param extra_settings Extra settings to be used locally for the resolving, even though they don't exist as actual settings
* @return The fully resolved GCode template, or the raw template if an error occurred. Also, if the given input is empty, the result will also be fully empty. However, if it
* has some content, the function ensures that the result contains and end-of-line at the end so that it can be inserted directly in a GCode without having to insert one.
*/
std::string resolveGCodeTemplate(
const std::string& input,
const std::optional<int> context_extruder_nr = std::nullopt,
const std::unordered_map<std::string, CuraFormulaeEngine::eval::Value>& extra_settings = {});

} // namespace cura::GcodeTemplateResolver

#endif
19 changes: 14 additions & 5 deletions include/gcodeExport.h
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,9 @@ class GCodeExport : public NoCopy
*/
void writeLayerCountComment(const size_t layer_count);

void writeLine(const char* line);
void writeLine(const std::string& line);

void writeRaw(const std::string& gcode);

/*!
* Reset the current_e_value to prevent too high E values.
Expand Down Expand Up @@ -608,7 +610,7 @@ class GCodeExport : public NoCopy
*/
void switchExtruder(size_t new_extruder, const RetractionConfig& retraction_config_old_extruder, coord_t perform_z_hop = 0);

void writeCode(const char* str);
void writeCode(const std::string& str);

/*!
* Write code while temporarily ensuring absolute extrusion mode.
Expand All @@ -619,7 +621,7 @@ class GCodeExport : public NoCopy
*
* \param str The code string to write
*/
void writeCodeWithAbsoluteExtrusion(const char* str);
void writeCodeWithAbsoluteExtrusion(const std::string& str);

void resetExtruderToPrimed(const size_t extruder, const double initial_retraction);

Expand Down Expand Up @@ -709,9 +711,16 @@ class GCodeExport : public NoCopy
/*!
* Finish the gcode: turn fans off, write end gcode and flush all gcode left in the buffer.
*
* \param endCode The end gcode to be appended at the very end.
* \param end_code The end gcode to be appended at the very end.
*/
void finalize(const std::string& end_code);

/*!
* Finish the extruder gcode: write extrude rend gcode.
*
* \param extruder_end_code The end gcode to be appended at the end.
*/
void finalize(const char* endCode);
void finalizeExtruder(const std::string& extruder_end_code);

/*!
* Get amount of material extruded since last wipe script was inserted.
Expand Down
38 changes: 38 additions & 0 deletions include/settings/SettingContainersEnvironmentAdapter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) 2026 UltiMaker
// CuraEngine is released under the terms of the AGPLv3 or higher

#ifndef SETTINGS_SETTINGCONTAINERENVIRONMENTADAPTER_H
#define SETTINGS_SETTINGCONTAINERENVIRONMENTADAPTER_H

#include <cura-formulae-engine/ast/ast.h>

namespace cura
{

class Settings;

/*!
* Adapter class used to expose the engine settings stack to the formulae engine resolving system
*/
class SettingContainersEnvironmentAdapter : public CuraFormulaeEngine::env::Environment
{
public:
/*!
* Base constructor
* @param settings The settings to be used as the base contextual stack
*/
explicit SettingContainersEnvironmentAdapter(const Settings& settings);

[[nodiscard]] std::optional<CuraFormulaeEngine::eval::Value> get(const std::string& setting_id) const override;

[[nodiscard]] bool has(const std::string& key) const override;

[[nodiscard]] std::unordered_map<std::string, CuraFormulaeEngine::eval::Value> getAll() const override;

private:
const Settings& settings_;
};

} // namespace cura

#endif
3 changes: 2 additions & 1 deletion include/settings/Settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,11 @@ class Settings
* If this returns ``false``, that means that the setting would be obtained
* via some inheritance.
* \param key The setting to check.
* \param parent_lookup Indicates whether the setting should also be looked up in the parent settings, as the get() would
* \return Whether that setting is contained in this particular Settings
* instance (``true``) or would be obtained via inheritance (``false``).
*/
bool has(const std::string& key) const;
bool has(const std::string& key, const bool parent_lookup = false) const;

/*
* Change the parent settings object.
Expand Down
13 changes: 9 additions & 4 deletions src/FffGcodeWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "Application.h"
#include "ExtruderTrain.h"
#include "FffProcessor.h"
#include "GcodeTemplateResolver.h"
#include "InsetOrderOptimizer.h"
#include "LayerPlan.h"
#include "PrimeTower/PrimeTower.h"
Expand Down Expand Up @@ -4349,7 +4350,7 @@ void FffGcodeWriter::finalize()

if (! extruder_end_code.empty())
{
gcode.writeCodeWithAbsoluteExtrusion(extruder_end_code.c_str());
gcode.finalizeExtruder(extruder_end_code);
}

if (mesh_group_settings.get<bool>("machine_heated_bed"))
Expand Down Expand Up @@ -4392,13 +4393,17 @@ void FffGcodeWriter::finalize()
gcode.writeJerk(mesh_group_settings.get<Velocity>("machine_max_jerk_xy"));
}

const auto end_gcode = mesh_group_settings.get<std::string>("machine_end_gcode");
// Replace the setting tokens in start and end g-code.
// Use values from the first used extruder by default so we get the expected temperatures
auto machine_end_gcode = mesh_group_settings.get<std::string>("machine_end_gcode");
auto initial_extruder_nr = Application::getInstance().current_slice_->scene.settings.get<int>("initial_extruder_nr");
machine_end_gcode = GcodeTemplateResolver::resolveGCodeTemplate(machine_end_gcode, initial_extruder_nr);

if (end_gcode.length() > 0 && mesh_group_settings.get<bool>("relative_extrusion"))
if (! machine_end_gcode.empty() && mesh_group_settings.get<bool>("relative_extrusion"))
{
gcode.writeExtrusionMode(false); // ensure absolute extrusion mode is set before the end gcode
}
gcode.finalize(end_gcode.c_str());
gcode.finalize(machine_end_gcode);

// set extrusion mode back to "normal"
gcode.resetExtrusionMode();
Expand Down
Loading
Loading