mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-14 04:27:36 +00:00
fix: load default config on initial load
This commit is contained in:
@@ -7,12 +7,6 @@
|
|||||||
# author = "OrcaSlicer"
|
# author = "OrcaSlicer"
|
||||||
# version = "0.01"
|
# version = "0.01"
|
||||||
# type = "slicing-pipeline"
|
# type = "slicing-pipeline"
|
||||||
#
|
|
||||||
# [tool.orcaslicer.plugin.settings]
|
|
||||||
# thickness_mm = "0.3"
|
|
||||||
# point_distance_mm = "0.8"
|
|
||||||
# fuzz_holes = "1"
|
|
||||||
# skip_first_layer = "1"
|
|
||||||
# ///
|
# ///
|
||||||
"""Fuzzy Slices -- the fuzzy-skin effect applied at slice time.
|
"""Fuzzy Slices -- the fuzzy-skin effect applied at slice time.
|
||||||
|
|
||||||
@@ -44,6 +38,7 @@ Polygon.as_array()/set_points numpy path would be the faster route.
|
|||||||
"""
|
"""
|
||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
|
import json
|
||||||
|
|
||||||
import orca
|
import orca
|
||||||
|
|
||||||
@@ -55,9 +50,9 @@ _DEFAULTS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _params(ctx):
|
def _params(self):
|
||||||
try:
|
try:
|
||||||
src = dict(ctx.params)
|
src = json.loads(self.get_config())
|
||||||
except (AttributeError, TypeError):
|
except (AttributeError, TypeError):
|
||||||
src = {}
|
src = {}
|
||||||
out = {}
|
out = {}
|
||||||
@@ -111,11 +106,14 @@ class FuzzySlices(orca.slicing.SlicingPipelineCapabilityBase):
|
|||||||
def get_name(self):
|
def get_name(self):
|
||||||
return "Fuzzy Slices"
|
return "Fuzzy Slices"
|
||||||
|
|
||||||
|
def get_default_config(self):
|
||||||
|
return _DEFAULTS
|
||||||
|
|
||||||
def execute(self, ctx):
|
def execute(self, ctx):
|
||||||
if ctx.step != orca.slicing.Step.posSlice or ctx.object is None:
|
if ctx.step != orca.slicing.Step.posSlice or ctx.object is None:
|
||||||
return orca.ExecutionResult.success()
|
return orca.ExecutionResult.success()
|
||||||
|
|
||||||
p = _params(ctx)
|
p = _params(self)
|
||||||
if p["thickness_mm"] <= 0.0 or p["point_distance_mm"] <= 0.0:
|
if p["thickness_mm"] <= 0.0 or p["point_distance_mm"] <= 0.0:
|
||||||
return orca.ExecutionResult.success("Fuzzy Slices: zero thickness/point distance, nothing to do")
|
return orca.ExecutionResult.success("Fuzzy Slices: zero thickness/point distance, nothing to do")
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,6 @@
|
|||||||
# author = "OrcaSlicer"
|
# author = "OrcaSlicer"
|
||||||
# version = "0.01"
|
# version = "0.01"
|
||||||
# type = "slicing-pipeline"
|
# type = "slicing-pipeline"
|
||||||
#
|
|
||||||
# [tool.orcaslicer.plugin.settings]
|
|
||||||
# stamp_text = "processed by the OrcaSlicer G-code Stamp plugin"
|
|
||||||
# ///
|
# ///
|
||||||
"""G-code Stamp -- the post-processing half of the slicing-pipeline plugin.
|
"""G-code Stamp -- the post-processing half of the slicing-pipeline plugin.
|
||||||
|
|
||||||
@@ -19,7 +16,7 @@ exported G-code file -- NOT from Print::process(). So unlike the geometry steps
|
|||||||
(posSlice, posPerimeters, ...) there is no live slicing graph here: ctx.print and
|
(posSlice, posPerimeters, ...) there is no live slicing graph here: ctx.print and
|
||||||
ctx.object are None. Instead the context carries ctx.gcode_path (the working G-code
|
ctx.object are None. Instead the context carries ctx.gcode_path (the working G-code
|
||||||
file on disk, edited IN PLACE), ctx.host ("File", "OctoPrint", ...) and
|
file on disk, edited IN PLACE), ctx.host ("File", "OctoPrint", ...) and
|
||||||
ctx.output_name (the final file name). ctx.params and ctx.config_value() still work.
|
ctx.output_name (the final file name). self.get_config() still works.
|
||||||
|
|
||||||
This sample inserts a single comment line near the top of the file. Because the same
|
This sample inserts a single comment line near the top of the file. Because the same
|
||||||
capability class can also implement the geometry steps, one plugin can transform slices
|
capability class can also implement the geometry steps, one plugin can transform slices
|
||||||
@@ -30,22 +27,28 @@ separate working copy), and its output is not reflected in the G-code preview --
|
|||||||
viewer maps the pre-post-process file.
|
viewer maps the pre-post-process file.
|
||||||
"""
|
"""
|
||||||
import orca
|
import orca
|
||||||
|
import json
|
||||||
|
|
||||||
_DEFAULT_STAMP = "processed by the OrcaSlicer G-code Stamp plugin"
|
_DEFAULTS = {
|
||||||
|
"stamp_text": "processed by the OrcaSlicer G-code Stamp plugin",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _stamp_text(ctx):
|
def _stamp_text(self):
|
||||||
try:
|
try:
|
||||||
text = dict(ctx.params).get("stamp_text", _DEFAULT_STAMP)
|
text = json.loads(self.get_config())["stamp_text"]
|
||||||
except (AttributeError, TypeError):
|
except (AttributeError, TypeError, ValueError, KeyError):
|
||||||
text = _DEFAULT_STAMP
|
text = _DEFAULTS["stamp_text"]
|
||||||
return str(text).replace("\n", " ").strip() or _DEFAULT_STAMP
|
return str(text).replace("\n", " ").strip() or _DEFAULTS["stamp_text"]
|
||||||
|
|
||||||
|
|
||||||
class GCodeStamp(orca.slicing.SlicingPipelineCapabilityBase):
|
class GCodeStamp(orca.slicing.SlicingPipelineCapabilityBase):
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return "G-code Stamp"
|
return "G-code Stamp"
|
||||||
|
|
||||||
|
def get_default_config(self):
|
||||||
|
return _DEFAULTS
|
||||||
|
|
||||||
def execute(self, ctx):
|
def execute(self, ctx):
|
||||||
# Only act at the post-process seam; at every geometry step this is a no-op.
|
# Only act at the post-process seam; at every geometry step this is a no-op.
|
||||||
if ctx.step != orca.slicing.Step.psGCodePostProcess:
|
if ctx.step != orca.slicing.Step.psGCodePostProcess:
|
||||||
@@ -53,7 +56,7 @@ class GCodeStamp(orca.slicing.SlicingPipelineCapabilityBase):
|
|||||||
if not ctx.gcode_path:
|
if not ctx.gcode_path:
|
||||||
return orca.ExecutionResult.success("G-code Stamp: no gcode_path, nothing to do")
|
return orca.ExecutionResult.success("G-code Stamp: no gcode_path, nothing to do")
|
||||||
|
|
||||||
comment = "; " + _stamp_text(ctx) + " (host=" + (ctx.host or "?") + ")\n"
|
comment = "; " + _stamp_text(self) + " (host=" + (ctx.host or "?") + ")\n"
|
||||||
|
|
||||||
# Edit the exported G-code in place: keep the original first line first (some flavors
|
# Edit the exported G-code in place: keep the original first line first (some flavors
|
||||||
# expect a specific leading line), then insert the stamp right after it.
|
# expect a specific leading line), then insert the stamp right after it.
|
||||||
|
|||||||
@@ -25,21 +25,39 @@ A surface may split into several islands or vanish when shrunk; both are handled
|
|||||||
|
|
||||||
No numpy required: the whole edit is expressed with the host geometry classes.
|
No numpy required: the whole edit is expressed with the host geometry classes.
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
import orca
|
import orca
|
||||||
|
|
||||||
INSET_MM = 1.0
|
_DEFAULTS = {
|
||||||
|
"inset_mm": 1.0, # inward offset applied to every slice
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _inset_mm(self):
|
||||||
|
try:
|
||||||
|
return float(json.loads(self.get_config())["inset_mm"])
|
||||||
|
except (AttributeError, TypeError, ValueError, KeyError):
|
||||||
|
return _DEFAULTS["inset_mm"]
|
||||||
|
|
||||||
|
|
||||||
class InsetEverySlice(orca.slicing.SlicingPipelineCapabilityBase):
|
class InsetEverySlice(orca.slicing.SlicingPipelineCapabilityBase):
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return "Inset Every Slice"
|
return "Inset Every Slice"
|
||||||
|
|
||||||
|
def get_default_config(self):
|
||||||
|
return _DEFAULTS
|
||||||
|
|
||||||
def execute(self, ctx):
|
def execute(self, ctx):
|
||||||
if ctx.step != orca.slicing.Step.posSlice or ctx.object is None:
|
if ctx.step != orca.slicing.Step.posSlice or ctx.object is None:
|
||||||
return orca.ExecutionResult.success()
|
return orca.ExecutionResult.success()
|
||||||
|
|
||||||
|
inset_mm = _inset_mm(self)
|
||||||
|
if inset_mm <= 0.0:
|
||||||
|
return orca.ExecutionResult.success("Inset: zero inset, nothing to do")
|
||||||
|
|
||||||
# Millimeters -> scaled integer units via the *live* scale (never hardcode 1e6).
|
# Millimeters -> scaled integer units via the *live* scale (never hardcode 1e6).
|
||||||
inset_scaled = int(round(INSET_MM / orca.slicing.unscale(1)))
|
inset_scaled = int(round(inset_mm / orca.slicing.unscale(1)))
|
||||||
|
|
||||||
regions_touched = 0
|
regions_touched = 0
|
||||||
for layer in ctx.object.layers():
|
for layer in ctx.object.layers():
|
||||||
|
|||||||
@@ -7,13 +7,6 @@
|
|||||||
# author = "OrcaSlicer"
|
# author = "OrcaSlicer"
|
||||||
# version = "0.02"
|
# version = "0.02"
|
||||||
# type = "slicing-pipeline"
|
# type = "slicing-pipeline"
|
||||||
#
|
|
||||||
# [tool.orcaslicer.plugin.settings]
|
|
||||||
# twist_deg_per_mm = "1.0"
|
|
||||||
# taper_per_mm = "0.0"
|
|
||||||
# wobble_ampl_mm = "0.0"
|
|
||||||
# wobble_period_mm = "20.0"
|
|
||||||
# min_scale = "0.05"
|
|
||||||
# ///
|
# ///
|
||||||
"""Twistify -- twist/taper/wobble any model at slice time.
|
"""Twistify -- twist/taper/wobble any model at slice time.
|
||||||
|
|
||||||
@@ -36,7 +29,7 @@ settings table above). The first object layer is untouched (z_rel = 0), so bed
|
|||||||
adhesion is unaffected.
|
adhesion is unaffected.
|
||||||
"""
|
"""
|
||||||
import math
|
import math
|
||||||
|
import json
|
||||||
import orca
|
import orca
|
||||||
|
|
||||||
_DEFAULTS = {
|
_DEFAULTS = {
|
||||||
@@ -48,9 +41,9 @@ _DEFAULTS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _params(ctx):
|
def _params(self):
|
||||||
try:
|
try:
|
||||||
src = dict(ctx.params)
|
src = json.loads(self.get_config())
|
||||||
except (AttributeError, TypeError):
|
except (AttributeError, TypeError):
|
||||||
src = {}
|
src = {}
|
||||||
out = {}
|
out = {}
|
||||||
@@ -80,11 +73,14 @@ class Twistify(orca.slicing.SlicingPipelineCapabilityBase):
|
|||||||
def get_name(self):
|
def get_name(self):
|
||||||
return "Twistify"
|
return "Twistify"
|
||||||
|
|
||||||
|
def get_default_config(self):
|
||||||
|
return _DEFAULTS
|
||||||
|
|
||||||
def execute(self, ctx):
|
def execute(self, ctx):
|
||||||
if ctx.step != orca.slicing.Step.posSlice or ctx.object is None:
|
if ctx.step != orca.slicing.Step.posSlice or ctx.object is None:
|
||||||
return orca.ExecutionResult.success()
|
return orca.ExecutionResult.success()
|
||||||
|
|
||||||
p = _params(ctx)
|
p = _params(self)
|
||||||
if _is_identity(p):
|
if _is_identity(p):
|
||||||
return orca.ExecutionResult.success("Twistify: identity parameters, nothing to do")
|
return orca.ExecutionResult.success("Twistify: identity parameters, nothing to do")
|
||||||
|
|
||||||
|
|||||||
@@ -273,7 +273,6 @@ static void run_post_process_plugins(const ConfigOptionStrings& capabilities,
|
|||||||
// Hand the plugin its own [tool.orcaslicer.plugin.settings] as ctx.params (same plugin_key the
|
// Hand the plugin its own [tool.orcaslicer.plugin.settings] as ctx.params (same plugin_key the
|
||||||
// capability was resolved by), mirroring the in-pipeline dispatcher in GUI_App.cpp.
|
// capability was resolved by), mirroring the in-pipeline dispatcher in GUI_App.cpp.
|
||||||
const std::string plugin_key = ref.uuid.empty() ? ref.name : ref.uuid;
|
const std::string plugin_key = ref.uuid.empty() ? ref.name : ref.uuid;
|
||||||
ctx.params = PluginManager::instance().get_plugin_settings(plugin_key);
|
|
||||||
|
|
||||||
ExecutionResult exec_result;
|
ExecutionResult exec_result;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ struct PluginDescriptor
|
|||||||
std::string entry_path; // Full path to the installed plugin entry file
|
std::string entry_path; // Full path to the installed plugin entry file
|
||||||
std::string entry_package; // Import package/module used for package-based loading
|
std::string entry_package; // Import package/module used for package-based loading
|
||||||
std::vector<std::string> dependencies; // Python dependency requirements declared by plugin package metadata
|
std::vector<std::string> dependencies; // Python dependency requirements declared by plugin package metadata
|
||||||
std::map<std::string, std::string> settings; // [tool.orcaslicer.plugin.settings] table -> per-plugin params (ctx.params)
|
|
||||||
std::vector<PluginChangelog> changelog; // Cloud release changelog, sorted newest-first when available.
|
std::vector<PluginChangelog> changelog; // Cloud release changelog, sorted newest-first when available.
|
||||||
|
|
||||||
std::string error; // Blocking error message. Non-empty means the plugin is in an error state.
|
std::string error; // Blocking error message. Non-empty means the plugin is in an error state.
|
||||||
|
|||||||
@@ -70,9 +70,6 @@ void install_slicing_pipeline_hook()
|
|||||||
const std::string plugin_key = ref.uuid.empty() ? ref.name : ref.uuid;
|
const std::string plugin_key = ref.uuid.empty() ? ref.name : ref.uuid;
|
||||||
ExecutionResult r;
|
ExecutionResult r;
|
||||||
try {
|
try {
|
||||||
// Read manager state before acquiring the GIL so this path does not take
|
|
||||||
// m_mutex in the opposite order to plugin teardown.
|
|
||||||
const auto plugin_settings = PluginManager::instance().get_plugin_settings(plugin_key);
|
|
||||||
// GIL is acquired per capability (not once for the whole dispatch) so it
|
// GIL is acquired per capability (not once for the whole dispatch) so it
|
||||||
// is released between capabilities.
|
// is released between capabilities.
|
||||||
PythonGILState gil;
|
PythonGILState gil;
|
||||||
@@ -87,9 +84,6 @@ void install_slicing_pipeline_hook()
|
|||||||
ctx.step = step;
|
ctx.step = step;
|
||||||
ctx.print = &print;
|
ctx.print = &print;
|
||||||
ctx.object = object;
|
ctx.object = object;
|
||||||
// hand the plugin its own [tool.orcaslicer.plugin.settings] as ctx.params
|
|
||||||
// (same plugin_key the capability was resolved by, so it always matches).
|
|
||||||
ctx.params = plugin_settings;
|
|
||||||
r = cap->execute(ctx);
|
r = cap->execute(ctx);
|
||||||
} catch (const CanceledException&) {
|
} catch (const CanceledException&) {
|
||||||
throw; // cancellation must reach process(), never become a slicing error
|
throw; // cancellation must reach process(), never become a slicing error
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <slic3r/plugin/PluginConfig.hpp>
|
||||||
#include <slic3r/plugin/PluginLoader.hpp>
|
#include <slic3r/plugin/PluginLoader.hpp>
|
||||||
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||||
#include <slic3r/plugin/pluginTypes/script/ScriptPluginCapability.hpp>
|
#include <slic3r/plugin/pluginTypes/script/ScriptPluginCapability.hpp>
|
||||||
@@ -555,17 +556,6 @@ std::shared_ptr<PluginCapabilityInterface> PluginManager::get_plugin_capability(
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<std::string, std::string> PluginManager::get_plugin_settings(const std::string& plugin_key) const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(m_mutex);
|
|
||||||
|
|
||||||
const Plugin* plugin = find_plugin_locked(plugin_key);
|
|
||||||
if (plugin == nullptr || !plugin->is_loaded())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
return plugin->descriptor.settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Lifecycle ───────────────────────────────────────────────────────────────────────────────
|
// ── Lifecycle ───────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
bool PluginManager::is_plugin_loaded(const std::string& plugin_key) const
|
bool PluginManager::is_plugin_loaded(const std::string& plugin_key) const
|
||||||
@@ -858,6 +848,16 @@ void PluginManager::load_plugin_impl(const std::string& plugin_key, bool skip_de
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto& cap : plugin.capabilities) {
|
||||||
|
auto config = cap->get_default_config();
|
||||||
|
if (config.empty())
|
||||||
|
continue;
|
||||||
|
if (m_config.has_config(plugin_key, cap->name()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
m_config.save_config(plugin_key, cap->name(), plugin.descriptor.installed_version, config);
|
||||||
|
}
|
||||||
|
|
||||||
bool committed = false;
|
bool committed = false;
|
||||||
bool cancelled = false;
|
bool cancelled = false;
|
||||||
std::string registry_error;
|
std::string registry_error;
|
||||||
|
|||||||
@@ -179,8 +179,6 @@ public:
|
|||||||
|
|
||||||
void set_capability_enabled(const std::string& plugin_key, const std::string& capability_name, bool enabled);
|
void set_capability_enabled(const std::string& plugin_key, const std::string& capability_name, bool enabled);
|
||||||
|
|
||||||
// The plugin's [tool.orcaslicer.plugin.settings] table (empty if the plugin is unknown). This will be replaced once the config is merged in.
|
|
||||||
std::map<std::string, std::string> get_plugin_settings(const std::string& plugin_key) const;
|
|
||||||
// Sets the cloud user whose _subscribed/{user_id} directory is scanned and installed into.
|
// Sets the cloud user whose _subscribed/{user_id} directory is scanned and installed into.
|
||||||
void set_cloud_user(const std::string& user_id);
|
void set_cloud_user(const std::string& user_id);
|
||||||
|
|
||||||
|
|||||||
@@ -187,7 +187,6 @@ bool parse_pep723_toml(const std::string& toml_content,
|
|||||||
std::string& out_description,
|
std::string& out_description,
|
||||||
std::string& out_author,
|
std::string& out_author,
|
||||||
std::string& out_version,
|
std::string& out_version,
|
||||||
std::map<std::string, std::string>& out_settings,
|
|
||||||
std::string& error)
|
std::string& error)
|
||||||
{
|
{
|
||||||
out_deps.clear();
|
out_deps.clear();
|
||||||
@@ -196,7 +195,6 @@ bool parse_pep723_toml(const std::string& toml_content,
|
|||||||
out_description.clear();
|
out_description.clear();
|
||||||
out_author.clear();
|
out_author.clear();
|
||||||
out_version.clear();
|
out_version.clear();
|
||||||
out_settings.clear();
|
|
||||||
|
|
||||||
TomlSection section = TomlSection::Root;
|
TomlSection section = TomlSection::Root;
|
||||||
|
|
||||||
@@ -274,10 +272,6 @@ bool parse_pep723_toml(const std::string& toml_content,
|
|||||||
else if (key == "description") out_description = unquote_toml_string(val);
|
else if (key == "description") out_description = unquote_toml_string(val);
|
||||||
else if (key == "author") out_author = unquote_toml_string(val);
|
else if (key == "author") out_author = unquote_toml_string(val);
|
||||||
else if (key == "version") out_version = unquote_toml_string(val);
|
else if (key == "version") out_version = unquote_toml_string(val);
|
||||||
} else if (section == TomlSection::OrcaPluginSettings) {
|
|
||||||
// collect every key as a string; the plugin parses (int/float/...) what it needs.
|
|
||||||
if (!key.empty())
|
|
||||||
out_settings[key] = unquote_toml_string(val);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -675,7 +669,6 @@ bool read_python_plugin_metadata(const boost::filesystem::path& py_path, PluginD
|
|||||||
pep_desc,
|
pep_desc,
|
||||||
pep_author,
|
pep_author,
|
||||||
pep_version,
|
pep_version,
|
||||||
descriptor.settings,
|
|
||||||
pep723_error)) {
|
pep723_error)) {
|
||||||
error = "Failed to parse PEP 723 metadata: " + pep723_error;
|
error = "Failed to parse PEP 723 metadata: " + pep723_error;
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -48,9 +48,6 @@ void SlicingPipelinePluginCapability::RegisterBindings(py::module_& module, py::
|
|||||||
py::class_<SlicingPipelineContext>(slicing, "SlicingPipelineContext")
|
py::class_<SlicingPipelineContext>(slicing, "SlicingPipelineContext")
|
||||||
.def_readonly("orca_version", &SlicingPipelineContext::orca_version)
|
.def_readonly("orca_version", &SlicingPipelineContext::orca_version)
|
||||||
.def_readonly("step", &SlicingPipelineContext::step)
|
.def_readonly("step", &SlicingPipelineContext::step)
|
||||||
.def_readonly("params", &SlicingPipelineContext::params,
|
|
||||||
"read-only dict of this plugin's [tool.orcaslicer.plugin.settings] values "
|
|
||||||
"(string->string). Parse the values you need, e.g. float(ctx.params['rate']).")
|
|
||||||
.def_readonly("gcode_path", &SlicingPipelineContext::gcode_path,
|
.def_readonly("gcode_path", &SlicingPipelineContext::gcode_path,
|
||||||
"Path to the working G-code file, set ONLY at Step.psGCodePostProcess. Edit it in "
|
"Path to the working G-code file, set ONLY at Step.psGCodePostProcess. Edit it in "
|
||||||
"place; empty at every other step.")
|
"place; empty at every other step.")
|
||||||
|
|||||||
@@ -18,10 +18,6 @@ struct SlicingPipelineContext {
|
|||||||
SlicingPipelineStepPlugin step { SlicingPipelineStepPlugin::posSlice };
|
SlicingPipelineStepPlugin step { SlicingPipelineStepPlugin::posSlice };
|
||||||
Print* print { nullptr }; // present for in-pipeline steps; null at psGCodePostProcess
|
Print* print { nullptr }; // present for in-pipeline steps; null at psGCodePostProcess
|
||||||
const PrintObject* object { nullptr }; // null for print-wide steps and psGCodePostProcess
|
const PrintObject* object { nullptr }; // null for print-wide steps and psGCodePostProcess
|
||||||
// read-only per-plugin settings, populated by the dispatcher from the
|
|
||||||
// plugin's [tool.orcaslicer.plugin.settings] PEP-723 table. Exposed as
|
|
||||||
// ctx.params (dict of string->string).
|
|
||||||
std::map<std::string, std::string> params;
|
|
||||||
// Populated ONLY at Step.psGCodePostProcess (the GUI G-code export/post-process seam,
|
// Populated ONLY at Step.psGCodePostProcess (the GUI G-code export/post-process seam,
|
||||||
// PostProcessor.cpp). gcode_path is the working G-code file on disk that the plugin edits
|
// PostProcessor.cpp). gcode_path is the working G-code file on disk that the plugin edits
|
||||||
// in place; host is the target ("File", "OctoPrint", ...); output_name mirrors
|
// in place; host is the target ("File", "OctoPrint", ...); output_name mirrors
|
||||||
|
|||||||
@@ -121,37 +121,3 @@ TEST_CASE("install-state sidecar is the source of truth for a cloud plugin's ins
|
|||||||
read_install_state(plugin_dir, scanned);
|
read_install_state(plugin_dir, scanned);
|
||||||
CHECK(scanned.installed_version == "1.2.0");
|
CHECK(scanned.installed_version == "1.2.0");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("install_plugin parses [tool.orcaslicer.plugin.settings] into descriptor.settings", "[PluginInstall]")
|
|
||||||
{
|
|
||||||
ScopedDataDir data_dir_guard("plugin-settings");
|
|
||||||
|
|
||||||
// A PEP-723 header with a per-plugin settings sub-table. Values stay strings; the plugin
|
|
||||||
// parses what it needs (ctx.params). This is the source Twistify reads its knobs from.
|
|
||||||
const std::string contents =
|
|
||||||
"# /// script\n"
|
|
||||||
"# requires-python = \">=3.12\"\n"
|
|
||||||
"#\n"
|
|
||||||
"# [tool.orcaslicer.plugin]\n"
|
|
||||||
"# name = \"Settings Plugin\"\n"
|
|
||||||
"# type = \"slicing-pipeline\"\n"
|
|
||||||
"#\n"
|
|
||||||
"# [tool.orcaslicer.plugin.settings]\n"
|
|
||||||
"# twist_deg_per_mm = \"1.5\"\n"
|
|
||||||
"# taper_per_mm = \"-0.004\"\n"
|
|
||||||
"# ///\n"
|
|
||||||
"print('ok')\n";
|
|
||||||
const fs::path py = write_py_file(data_dir_guard.dir / "src", "settings.py", contents);
|
|
||||||
|
|
||||||
PluginDescriptor descriptor;
|
|
||||||
std::string error;
|
|
||||||
const bool installed = plugin_loader::install_plugin(py, /*cloud_user_id=*/"", descriptor, error);
|
|
||||||
|
|
||||||
REQUIRE(installed);
|
|
||||||
CHECK(error.empty());
|
|
||||||
REQUIRE(descriptor.settings.count("twist_deg_per_mm") == 1);
|
|
||||||
CHECK(descriptor.settings.at("twist_deg_per_mm") == "1.5");
|
|
||||||
CHECK(descriptor.settings.at("taper_per_mm") == "-0.004");
|
|
||||||
// Identity keys are NOT captured as settings (they belong to [tool.orcaslicer.plugin]).
|
|
||||||
CHECK(descriptor.settings.count("name") == 0);
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user