Merge branch 'main' into feature/texture_displacement

This commit is contained in:
SoftFever
2026-07-21 19:50:35 +08:00
631 changed files with 2326755 additions and 1243600 deletions
+4
View File
@@ -202,6 +202,10 @@ void AppConfig::set_defaults()
if (get("seq_top_layer_only").empty())
set("seq_top_layer_only", "1");
// ORCA: darken layers below the current one while scrubbing the preview (ported from preFlight)
if (get("preview_dim_previous_layers").empty())
set_bool("preview_dim_previous_layers", false);
if (get("filaments_area_preferred_count").empty())
set("filaments_area_preferred_count", "10");
+1 -1
View File
@@ -125,7 +125,7 @@ public:
template<class It, class = IteratorOnly<It> > BoundingBox3Base(It from, It to)
{
if (from == to)
throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor");
throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor.");
auto it = from;
this->min = it->template cast<typename PointType::Scalar>();
+43 -129
View File
@@ -34,14 +34,14 @@ static void append_and_translate(ExPolygons &dst, const ExPolygons &src, const P
}
// BBS: generate brim area by objs
static void append_and_translate(ExPolygons& dst, const ExPolygons& src,
const PrintInstance& instance, const Print& print, std::map<ObjectID, ExPolygons>& brimAreaMap) {
const PrintInstance& instance, size_t instance_idx, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap) {
ExPolygons srcShifted = src;
Point instance_shift = instance.shift_without_plate_offset();
for (size_t src_idx = 0; src_idx < srcShifted.size(); ++src_idx)
srcShifted[src_idx].translate(instance_shift);
srcShifted = diff_ex(srcShifted, dst);
//expolygons_append(dst, temp2);
expolygons_append(brimAreaMap[instance.print_object->id()], std::move(srcShifted));
expolygons_append(brimAreaMap[{ instance.print_object->id(), instance_idx }], std::move(srcShifted));
}
static void append_and_translate(Polygons &dst, const Polygons &src, const PrintInstance &instance) {
@@ -420,8 +420,7 @@ static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWi
//BBS: create all brims
static ExPolygons outer_inner_brim_area(const Print& print,
const float no_brim_offset, std::map<ObjectID, ExPolygons>& brimAreaMap,
std::map<ObjectID, ExPolygons>& supportBrimAreaMap,
const float no_brim_offset, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap,
std::vector<std::pair<ObjectID, unsigned int>>& objPrintVec,
std::vector<unsigned int>& printExtruders)
{
@@ -470,7 +469,6 @@ static ExPolygons outer_inner_brim_area(const Print& print,
ExPolygons brim_area_object;
ExPolygons no_brim_area_object;
ExPolygons brim_area_support;
ExPolygons no_brim_area_support;
Polygons holes_object;
Polygons holes_support;
@@ -571,16 +569,18 @@ static ExPolygons outer_inner_brim_area(const Print& print,
append(no_brim_area_object, objectIsland);
brimToWrite.at(object->id()).obj = false;
for (const PrintInstance& instance : object->instances()) {
for (size_t instance_idx = 0; instance_idx < object->instances().size(); ++instance_idx) {
const PrintInstance& instance = object->instances()[instance_idx];
if (!brim_area_object.empty())
append_and_translate(brim_area, brim_area_object, instance, print, brimAreaMap);
append_and_translate(brim_area, brim_area_object, instance, instance_idx, brimAreaMap);
append_and_translate(no_brim_area, no_brim_area_object, instance);
append_and_translate(holes, holes_object, instance);
append_and_translate(objectIslands, objectIsland, instance);
}
if (brimAreaMap.find(object->id()) != brimAreaMap.end())
expolygons_append(brim_area, brimAreaMap[object->id()]);
for (const auto& [key, areas] : brimAreaMap)
if (key.object_id == object->id())
expolygons_append(brim_area, areas);
}
support_material_extruder = object->config().support_filament;
if (support_material_extruder == 0 && object->has_support_material()) {
@@ -592,32 +592,12 @@ static ExPolygons outer_inner_brim_area(const Print& print,
if (support_material_extruder == extruderNo && brimToWrite.at(object->id()).sup) {
if (!object->support_layers().empty() && object->support_layers().front()->support_type==stInnerNormal) {
for (const Polygon& support_contour : object->support_layers().front()->support_fills.polygons_covered_by_spacing()) {
// Brim will not be generated for supports
/*
if (has_outer_brim) {
append(brim_area_support, diff_ex(offset_ex(support_contour, brim_width + brim_offset, jtRound, SCALED_RESOLUTION), offset_ex(support_contour, brim_offset)));
}
if (has_inner_brim || has_outer_brim)
append(no_brim_area_support, offset_ex(support_contour, 0));
*/
no_brim_area_support.emplace_back(support_contour);
}
}
// BBS
if (!object->support_layers().empty() && object->support_layers().front()->support_type == stInnerTree) {
for (const ExPolygon &ex_poly : object->support_layers().front()->lslices) {
// BBS: additional brim width will be added if adhesion area is too small without brim
float brim_width_mod = ex_poly.area() / ex_poly.contour.length() < scaled_half_min_adh_length
&& brim_width < scaled_flow_width ? brim_width + scaled_additional_brim_width : brim_width;
brim_width_mod = floor(brim_width_mod / scaled_flow_width / 2) * scaled_flow_width * 2;
// Brim will not be generated for supports
/*
if (has_outer_brim) {
append(brim_area_support, diff_ex(offset_ex(ex_poly.contour, brim_width_mod + brim_offset, jtRound, SCALED_RESOLUTION), offset_ex(ex_poly.contour, brim_offset)));
}
if (has_inner_brim)
append(brim_area_support, diff_ex(offset_ex(ex_poly.holes, -brim_offset), offset_ex(ex_poly.holes, -brim_width - brim_offset)));
*/
if (!has_outer_brim)
append(no_brim_area_support, diff_ex(offset(ex_poly.contour, no_brim_offset), ex_poly.holes));
if (!has_inner_brim && !has_outer_brim)
@@ -630,13 +610,9 @@ static ExPolygons outer_inner_brim_area(const Print& print,
}
brimToWrite.at(object->id()).sup = false;
for (const PrintInstance& instance : object->instances()) {
if (!brim_area_support.empty())
append_and_translate(brim_area, brim_area_support, instance, print, supportBrimAreaMap);
append_and_translate(no_brim_area, no_brim_area_support, instance);
append_and_translate(holes, holes_support, instance);
}
if (supportBrimAreaMap.find(object->id()) != supportBrimAreaMap.end())
expolygons_append(brim_area, supportBrimAreaMap[object->id()]);
}
}
}
@@ -676,28 +652,27 @@ static ExPolygons outer_inner_brim_area(const Print& print,
}
if (brimAreaMap.find(object->id()) != brimAreaMap.end()) {
brimAreaMap[object->id()] = diff_ex(brimAreaMap[object->id()], extruder_no_brim_area);
}
for (auto& [key, areas] : brimAreaMap)
if (key.object_id == object->id())
areas = diff_ex(areas, extruder_no_brim_area);
if (supportBrimAreaMap.find(object->id()) != supportBrimAreaMap.end())
supportBrimAreaMap[object->id()] = diff_ex(supportBrimAreaMap[object->id()], extruder_no_brim_area);
}
brim_area.clear();
for (const PrintObject* object : print.objects()) {
// BBS: brim should be contacted to at least one object's island or brim area
if (brimAreaMap.find(object->id()) != brimAreaMap.end()) {
for (auto map_it = brimAreaMap.begin(); map_it != brimAreaMap.end(); ++map_it) {
if (map_it->first.object_id != object->id())
continue;
// find other objects' brim area
ExPolygons otherExPolys;
for (const PrintObject* otherObject : print.objects()) {
if ((otherObject->id() != object->id()) && (brimAreaMap.find(otherObject->id()) != brimAreaMap.end())) {
expolygons_append(otherExPolys, brimAreaMap[otherObject->id()]);
}
}
for (const auto& [other_key, other_areas] : brimAreaMap)
if (other_key != map_it->first)
expolygons_append(otherExPolys, other_areas);
auto tempArea = brimAreaMap[object->id()];
brimAreaMap[object->id()].clear();
auto tempArea = map_it->second;
map_it->second.clear();
for (int ia = 0; ia != tempArea.size(); ++ia) {
// find this object's other brim area
@@ -709,9 +684,9 @@ static ExPolygons outer_inner_brim_area(const Print& print,
if (!intersection_ex(offsetedTa, objectIslands).empty() ||
!intersection_ex(offsetedTa, otherExPoly).empty() ||
!intersection_ex(offsetedTa, otherExPolys).empty())
brimAreaMap[object->id()].push_back(tempArea[ia]);
map_it->second.push_back(tempArea[ia]);
}
expolygons_append(brim_area, brimAreaMap[object->id()]);
expolygons_append(brim_area, map_it->second);
}
}
return brim_area;
@@ -890,19 +865,15 @@ ExtrusionEntityCollection makeBrimInfillFromPlateCoordinates(const ExPolygons& s
//BBS: an overload of the orignal brim generator that generates the brim by obj and by extruders
void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_area,
std::map<ObjectID, ExtrusionEntityCollection>& brimMap,
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
std::map<ObjectInstanceID, ExtrusionEntityCollection>& brimMapByInstance,
std::vector<std::pair<ObjectID, unsigned int>> &objPrintVec,
std::vector<unsigned int>& printExtruders,
std::map<ObjectID, ExPolygons>* objectBrimAreasOut,
std::map<ObjectID, ExPolygons>* supportBrimAreasOut)
std::map<ObjectInstanceID, ExPolygons>* objectBrimAreasByInstanceOut)
{
std::map<ObjectID, double> brim_width_map;
std::map<ObjectID, ExPolygons> brimAreaMap;
std::map<ObjectID, ExPolygons> supportBrimAreaMap;
std::map<ObjectInstanceID, ExPolygons> brimAreaMap;
Flow flow = print.brim_flow();
const auto scaled_resolution = scaled<double>(print.config().resolution.value);
ExPolygons islands_area_ex = outer_inner_brim_area(print,
float(flow.scaled_spacing()), brimAreaMap, supportBrimAreaMap, objPrintVec, printExtruders);
float(flow.scaled_spacing()), brimAreaMap, objPrintVec, printExtruders);
// BBS: Find boundingbox of the first layer
for (const ObjectID printObjID : print.print_object_ids()) {
@@ -924,14 +895,10 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
ex_poly_translated.translate(instance.shift_without_plate_offset());
bbx.merge(get_extents(ex_poly_translated));
}
if (supportBrimAreaMap.find(printObjID) != supportBrimAreaMap.end()) {
for (const ExPolygon& ex_poly : supportBrimAreaMap.at(printObjID))
bbx.merge(get_extents(ex_poly.contour));
}
if (brimAreaMap.find(printObjID) != brimAreaMap.end()) {
for (const ExPolygon& ex_poly : brimAreaMap.at(printObjID))
bbx.merge(get_extents(ex_poly.contour));
}
for (const auto& [key, areas] : brimAreaMap)
if (key.object_id == printObjID)
for (const ExPolygon& ex_poly : areas)
bbx.merge(get_extents(ex_poly.contour));
object->firstLayerObjectBrimBoundingBox = bbx;
}
@@ -944,77 +911,24 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
islands_area[iia].translate(plate_shift);
// Orca: keep translated brim footprints for skirt grouping.
auto translate_area_map = [plate_shift](const std::map<ObjectID, ExPolygons>& src) {
std::map<ObjectID, ExPolygons> dst = src;
auto translate_area_map = [plate_shift](const auto& src) {
auto dst = src;
for (auto& [_, areas] : dst)
for (ExPolygon& area : areas)
area.translate(plate_shift);
return dst;
};
if (objectBrimAreasOut != nullptr)
*objectBrimAreasOut = translate_area_map(brimAreaMap);
if (supportBrimAreasOut != nullptr)
*supportBrimAreasOut = translate_area_map(supportBrimAreaMap);
if (objectBrimAreasByInstanceOut != nullptr)
*objectBrimAreasByInstanceOut = translate_area_map(brimAreaMap);
const bool has_per_object_skirt_or_shield = print.config().skirt_type == stPerObject &&
(print.has_skirt() || print.has_infinite_skirt());
const bool combine_brims = print.config().combine_brims.value &&
!has_per_object_skirt_or_shield &&
print.config().print_sequence != PrintSequence::ByObject;
if (!combine_brims) {
// Orca: Generate brims separately when brims cannot be combined.
for (auto iter = brimAreaMap.begin(); iter != brimAreaMap.end(); ++iter) {
if (!iter->second.empty()) {
brimMap.insert(std::make_pair(iter->first, makeBrimInfill(iter->second, print, islands_area)));
};
}
for (auto iter = supportBrimAreaMap.begin(); iter != supportBrimAreaMap.end(); ++iter) {
if (!iter->second.empty()) {
supportBrimMap.insert(std::make_pair(iter->first, makeBrimInfill(iter->second, print, islands_area)));
};
}
} else {
// Orca: Unified brim mode.
ExPolygons all_brims_merged;
std::vector<ObjectID> brim_object_ids;
// Add all object brims
for (auto& [obj_id, brims] : brimAreaMap) {
if (!brims.empty()) {
expolygons_append(all_brims_merged, brims);
brim_object_ids.push_back(obj_id);
}
}
if (!all_brims_merged.empty()) {
// Merge all brims into a single continuous area
all_brims_merged = union_ex(all_brims_merged);
// Apply a tiny morphological cleanup to reduce boolean-union micro-artifacts.
const float brim_cleanup_delta = std::max(float(scaled_resolution), float(SCALED_EPSILON));
all_brims_merged = offset2_ex(all_brims_merged, brim_cleanup_delta, -brim_cleanup_delta, jtRound, scaled_resolution);
// Generate infill once for the merged brim area.
ExtrusionEntityCollection merged_brim = makeBrimInfill(all_brims_merged, print, islands_area);
// In unified mode, assign the merged brim to a deterministic carrier object.
// Pick the first object in print order that actually contributed brim area.
ObjectID carrier_id;
bool carrier_found = false;
for (const auto& [obj_id, _extruder] : objPrintVec) {
if (std::find(brim_object_ids.begin(), brim_object_ids.end(), obj_id) != brim_object_ids.end()) {
carrier_id = obj_id;
carrier_found = true;
break;
}
}
if (!carrier_found)
carrier_id = brim_object_ids.front();
brimMap[carrier_id] = std::move(merged_brim);
}
// Orca: Generate brims per object instance. If Combine brims is enabled,
// Print::_make_skirt() will join the touching ones.
for (auto iter = brimAreaMap.begin(); iter != brimAreaMap.end(); ++iter) {
if (!iter->second.empty()) {
ExtrusionEntityCollection brim = makeBrimInfill(iter->second, print, islands_area);
brimMap[iter->first.object_id].append(brim.entities);
brimMapByInstance.emplace(iter->first, std::move(brim));
};
}
}
} // namespace Slic3r
+3 -4
View File
@@ -2,6 +2,7 @@
#define slic3r_Brim_hpp_
#include "ExPolygon.hpp"
#include "ObjectID.hpp"
#include "Point.hpp"
#include<map>
@@ -12,17 +13,15 @@ namespace Slic3r {
class Print;
class ExtrusionEntityCollection;
class PrintTryCancel;
class ObjectID;
// Produce brim lines around those objects, that have the brim enabled.
// Collect islands_area to be merged into the final 1st layer convex hull.
void make_brim(const Print& print, PrintTryCancel try_cancel,
Polygons& islands_area, std::map<ObjectID, ExtrusionEntityCollection>& brimMap,
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
std::map<ObjectInstanceID, ExtrusionEntityCollection>& brimMapByInstance,
std::vector<std::pair<ObjectID, unsigned int>>& objPrintVec,
std::vector<unsigned int>& printExtruders,
std::map<ObjectID, ExPolygons>* objectBrimAreasOut = nullptr,
std::map<ObjectID, ExPolygons>* supportBrimAreasOut = nullptr);
std::map<ObjectInstanceID, ExPolygons>* objectBrimAreasByInstanceOut = nullptr);
ExtrusionEntityCollection makeBrimInfill(const ExPolygons& singleBrimArea, const Print& print, const Polygons& islands_area);
ExtrusionEntityCollection makeBrimInfillFromPlateCoordinates(const ExPolygons& singleBrimArea, const Print& print, const Polygons& islands_area);
-2
View File
@@ -225,8 +225,6 @@ set(lisbslic3r_sources
GCode.hpp
GCode/PchipInterpolatorHelper.cpp
GCode/PchipInterpolatorHelper.hpp
GCode/PostProcessor.cpp
GCode/PostProcessor.hpp
GCode/PressureEqualizer.cpp
GCode/PressureEqualizer.hpp
GCode/PrintExtents.cpp
+104 -3
View File
@@ -4,6 +4,7 @@
#include "LocalesUtils.hpp"
#include "Preset.hpp"
#include <algorithm>
#include <assert.h>
#include <fstream>
#include <iostream>
@@ -36,6 +37,8 @@ using namespace nlohmann;
namespace Slic3r {
std::function<std::string(std::string, std::string)> ConfigBase::resolve_capability_fn = nullptr;
//BBS: add json support
//static const std::string CONFIG_VERSION_KEY = "version";
//static const std::string CONFIG_NAME_KEY = "name";
@@ -84,12 +87,12 @@ std::string escape_strings_cstyle(const std::vector<std::string> &strs)
// Separate the strings.
(*outptr ++) = ';';
const std::string &str = strs[j];
// Is the string simple or complex? Complex string contains spaces, tabs, new lines and other
// escapable characters. Empty string shall be quoted as well, if it is the only string in strs.
// Is the string simple or complex? Complex string contains spaces, tabs, semicolons, new lines
// and other escapable characters. Empty string shall be quoted as well, if it is the only string in strs.
bool should_quote = strs.size() == 1 && str.empty();
for (size_t i = 0; i < str.size(); ++ i) {
char c = str[i];
if (c == ' ' || c == '\t' || c == '\\' || c == '"' || c == '\r' || c == '\n') {
if (c == ' ' || c == '\t' || c == ';' || c == '\\' || c == '"' || c == '\r' || c == '\n') {
should_quote = true;
break;
}
@@ -1486,6 +1489,29 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, Fo
return std::move(substitutions_ctxt.substitutions);
}
std::optional<PluginCapabilityRef> parse_capability_ref(const std::string& value)
{
// Capability references are stored as "<plugin_name>;<cloud_uuid>;<capability_name>".
// The cloud UUID is empty for local plugins (two consecutive semicolons).
if (value.empty())
return std::nullopt;
const size_t first = value.find(';');
if (first == std::string::npos)
return std::nullopt;
const size_t second = value.find(';', first + 1);
if (second == std::string::npos)
return std::nullopt;
std::string name = value.substr(0, first);
std::string uuid = value.substr(first + 1, second - first - 1);
std::string capability_name = value.substr(second + 1);
if (name.empty() || capability_name.empty())
return std::nullopt;
return PluginCapabilityRef{ std::move(name), std::move(capability_name), std::move(uuid) };
}
//BBS: add json support
void ConfigBase::save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const
{
@@ -1522,6 +1548,18 @@ void ConfigBase::save_to_json(const std::string &file, const std::string &name,
}
}
// Serialize the top-level "plugins" manifest: the individual plugin-backed options keep bare
// capability names; the full "name;uuid;capability" references are derived here (same helper as
// update_plugin_manifest). Only with a resolver (GUI); without one (CLI/headless) leave whatever
// the "plugins" option already serialized above, so a round-trip never drops the manifest.
if (resolve_capability_fn) {
std::vector<std::string> unique_refs = this->collect_plugin_manifest();
if (unique_refs.empty())
j.erase("plugins");
else
j["plugins"] = unique_refs;
}
boost::nowide::ofstream c;
c.open(file, std::ios::out | std::ios::trunc);
c << j.dump(1, '\t') << std::endl;
@@ -1553,6 +1591,69 @@ void ConfigBase::null_nullables()
}
}
void ConfigBase::save_plugin_collection(const std::string& opt_key, const ConfigOption* opt, std::vector<std::string>& plugin_refs) const {
// Full plugin capability references ("name;uuid;capability") can only be derived through the
// resolver registered by the GUI once plugins are loaded. In non-GUI/headless contexts (e.g.
// the CLI) it stays null, so skip silently rather than calling an empty std::function.
if (!resolve_capability_fn)
return;
// A plugin-backed option declares its capability type via ConfigOptionDef::plugin_type (the same
// metadata PluginResolver::find_option_for_capability scans). Deriving off the def rather than a
// per-key branch keeps this generic across every plugin-backed option.
const ConfigDef* def = this->def();
const ConfigOptionDef* opt_def = def ? def->get(opt_key) : nullptr;
if (opt_def == nullptr || !opt_def->is_plugin_backed())
return;
const std::string& type = opt_def->plugin_type;
// Resolve a single bare capability value into its full reference and append it, skipping unset
// values, capabilities that could not be resolved (resolver returns ""), and duplicates already
// collected (preserving insertion order).
const auto append_ref = [&plugin_refs, &type](const std::string& capability_value) {
if (capability_value.empty())
return;
std::string ref = resolve_capability_fn(capability_value, type);
if (!ref.empty() && std::find(plugin_refs.begin(), plugin_refs.end(), ref) == plugin_refs.end())
plugin_refs.emplace_back(std::move(ref));
};
// Scalar options carry a single capability name; vector options carry a list. Same scalar/vector
// dispatch as PluginResolver::find_option_for_capability.
if (const auto* string_option = dynamic_cast<const ConfigOptionString*>(opt))
append_ref(string_option->value);
else if (const auto* vector_option = dynamic_cast<const ConfigOptionVectorBase*>(opt))
for (const std::string& val : vector_option->vserialize())
append_ref(val);
}
std::vector<std::string> ConfigBase::collect_plugin_manifest() const
{
std::vector<std::string> refs;
if (!resolve_capability_fn)
return refs;
// Each plugin-backed option (ConfigOptionDef::is_plugin_backed) contributes its resolved
// reference(s) via save_plugin_collection, which appends in order and skips duplicates, so no
// second de-duplication pass is needed here.
for (const std::string& opt_key : this->keys())
if (const ConfigOption* opt = this->option(opt_key))
this->save_plugin_collection(opt_key, opt, refs);
return refs;
}
void ConfigBase::update_plugin_manifest()
{
// Writes the derived manifest back into this config's "plugins" option (save_to_json writes the
// same manifest into a JSON document instead), so an in-memory backend config carries a resolved
// manifest even when the source preset was never serialized (picked-but-unsaved). Without a
// resolver (CLI/headless) leave whatever manifest was loaded from disk untouched.
if (!resolve_capability_fn)
return;
if (auto* manifest = this->option<ConfigOptionStrings>("plugins", true))
manifest->values = this->collect_plugin_manifest();
}
DynamicConfig::DynamicConfig(const ConfigBase& rhs, const t_config_option_keys& keys)
{
for (const t_config_option_key& opt_key : keys)
+36 -1
View File
@@ -9,6 +9,7 @@
#include <cstdlib>
#include <functional>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
@@ -2254,6 +2255,9 @@ public:
legend,
// Vector value, but edited as a single string.
one_string,
plugin_picker,
// Raw JSON string value, edited through a dialog behind a button rather than in the row.
plugin_config,
};
// Identifier of this option. It is stored here so that it is accessible through the by_serialization_key_ordinal map.
@@ -2470,6 +2474,13 @@ public:
// "serialized" - vector valued option is entered in a single edit field. Values are separated by a semicolon.
// "show_value" - even if enum_values / enum_labels are set, still display the value, not the enum label.
std::string gui_flags;
// Capability type of a plugin-backed option, e.g. "slicing-pipeline" / "printer-connection"
// (empty for ordinary options). GUIType::plugin_picker filters the plugin list by it, and it
// resolves the option's "plugins" manifest reference; see is_plugin_backed().
std::string plugin_type;
// Whether this option holds plugin capability name(s) that feed the "plugins" manifest -- true
// iff it declares a plugin_type. Setting plugin_type is the only step needed to add one.
bool is_plugin_backed() const { return !plugin_type.empty(); }
// Label of the GUI input field.
// In case the GUI input fields are grouped in some views, the label defines a short label of a grouped value,
// while full_label contains a label of a stand-alone field.
@@ -2783,14 +2794,29 @@ public:
//BBS: add json support
void save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const;
// Rebuild the in-memory "plugins" manifest (the "name;uuid;capability" references the plugin
// dispatchers consume) from the plugin-backed options via the registered resolver. save_to_json()
// derives the same manifest, but only when a preset is written to disk; a config assembled in
// memory for the backend (PresetBundle::full_config -> Print::apply) must refresh it here or a
// picked-but-unsaved plugin never resolves at slice/export time. No-op without a resolver.
void update_plugin_manifest();
// Set all the nullable values to nils.
void null_nullables();
static size_t load_from_gcode_string_legacy(ConfigBase& config, const char* str, ConfigSubstitutionContext& substitutions);
static void set_resolve_capability_fn(std::function<std::string(std::string, std::string)> fn) { resolve_capability_fn = fn; }
private:
// Set a configuration value from a string.
bool set_deserialize_raw(const t_config_option_key& opt_key_src, const std::string& value, ConfigSubstitutionContext& substitutions, bool append);
void save_plugin_collection(const std::string& opt_key, const ConfigOption* opt, std::vector<std::string>& plugin_refs) const;
// Collect the de-duplicated "name;uuid;capability" plugin references derived from this config's
// plugin-backed options via the resolver. Shared by save_to_json (serializes them into the JSON
// manifest) and update_plugin_manifest (writes them back into the "plugins" option). Order is
// preserved and empties are dropped; returns empty without a resolver (CLI/headless).
std::vector<std::string> collect_plugin_manifest() const;
static std::function<std::string(std::string, std::string)> resolve_capability_fn;
};
// Configuration store with dynamic number of configuration values.
@@ -3027,6 +3053,15 @@ protected:
void set_defaults();
};
struct PluginCapabilityRef
{
std::string name;
std::string capability_name;
std::string uuid;
};
std::optional<PluginCapabilityRef> parse_capability_ref(const std::string& value);
}
#endif
+3 -3
View File
@@ -61,7 +61,7 @@ static inline FlowRole opt_key_to_flow_role(const std::string &opt_key)
static inline void throw_on_missing_variable(const std::string &opt_key, const char *dependent_opt_key)
{
throw FlowErrorMissingVariable((boost::format(L("Failed to calculate line width of %1%. Cannot get value of \u201c%2%\u201d ")) % opt_key % dependent_opt_key).str());
throw FlowErrorMissingVariable((boost::format("Failed to calculate line width of %1%. Cannot get value of \u201c%2%\u201d.") % opt_key % dependent_opt_key).str());
}
// Used to provide hints to the user on default extrusion width values, and to provide reasonable values to the PlaceholderParser.
@@ -129,7 +129,7 @@ double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionResol
Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height)
{
if (height <= 0)
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()");
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width().");
float w;
if (!width.percent && width.value <= 0.) {
@@ -157,7 +157,7 @@ Flow Flow::with_spacing(float new_spacing) const
assert(m_width >= m_height);
out.m_width += new_spacing - m_spacing;
if (out.m_width < out.m_height)
throw Slic3r::InvalidArgument(L("Invalid spacing supplied to Flow::with_spacing(), check your layer height and extrusion width"));
throw Slic3r::InvalidArgument("Invalid spacing supplied to Flow::with_spacing(), check your layer height and extrusion width.");
}
out.m_spacing = new_spacing;
return out;
+56 -54
View File
@@ -1845,7 +1845,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
std::string WipeTowerIntegration::finalize(GCode &gcodegen)
{
std::string gcode;
if (gcodegen.wipe_tower_type() == WipeTowerType::Type2) {
if (gcodegen.wipe_tower_type() == WipeTowerType::Type2 && !m_final_purge.gcode.empty()) {
if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON)
gcode += gcodegen.change_layer(m_final_purge.print_z);
gcode += append_tcr2(gcodegen, m_final_purge, -1);
@@ -3246,7 +3246,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
BoundingBoxf bbox;
auto pts = std::make_unique<ConfigOptionPoints>();
if (print.calib_mode() == CalibMode::Calib_PA_Line || print.calib_mode() == CalibMode::Calib_PA_Pattern) {
if (print.calib_mode() == CalibMode::Calib_PA_Pattern) {
//PA_Pattern can have any size or arrangement - not dependent on 3mf model size
bbox = bbox_bed;
bbox.offset(-25.0);
// add 4 corner points of bbox into pts
@@ -3256,6 +3257,22 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
pts->values.emplace_back(bbox.max.x(), bbox.max.y());
pts->values.emplace_back(bbox.min.x(), bbox.max.y());
} else if (print.calib_mode() == CalibMode::Calib_PA_Line) {
// Derive X bounds from the actual calibration geometry.
CalibPressureAdvanceLine temp_pa_line_forsize(this);
BoundingBoxf pattern_extents = temp_pa_line_forsize.print_extents(bbox_bed);
bbox = bbox_bed;
bbox.offset(-25.0);
bbox.min.x() = std::max(pattern_extents.min.x(), bbox.min.x());
bbox.max.x() = std::min(pattern_extents.max.x(), bbox.max.x());
pts->values.reserve(4);
pts->values.emplace_back(bbox.min.x(), bbox.min.y());
pts->values.emplace_back(bbox.max.x(), bbox.min.y());
pts->values.emplace_back(bbox.max.x(), bbox.max.y());
pts->values.emplace_back(bbox.min.x(), bbox.max.y());
} else {
// Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line.
// It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
@@ -3591,19 +3608,19 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
}
this->m_objsWithBrim.clear();
this->m_objSupportsWithBrim.clear();
m_brim_done = false;
// BBS: set that indicates objs with brim
for (auto iter = print.m_brimMap.begin(); iter != print.m_brimMap.end(); ++iter) {
if (!iter->second.empty())
this->m_objsWithBrim.insert(iter->first);
// Orca: Track brims by instance. When a combined brim is printed, all of
// its instances are marked done together.
for (const Print::SkirtBrimGroup& group : print.skirt_brim_groups()) {
for (const Print::SkirtBrimGroup::Brim& brim : group.brims) {
if (brim.brim.empty())
continue;
for (const ObjectInstanceID& instance : brim.instances)
this->m_objsWithBrim.insert(instance);
}
}
for (auto iter = print.m_supportBrimMap.begin(); iter != print.m_supportBrimMap.end(); ++iter) {
if (!iter->second.empty())
this->m_objSupportsWithBrim.insert(iter->first);
}
if (this->m_objsWithBrim.empty() && this->m_objSupportsWithBrim.empty()) m_brim_done = true;
if (this->m_objsWithBrim.empty()) m_brim_done = true;
// SoftFever: calib
if (print.calib_params().mode == CalibMode::Calib_PA_Line) {
@@ -4900,25 +4917,30 @@ std::string GCode::generate_skirt(const Print &print,
return gcode;
}
static size_t find_skirt_brim_group_idx(const Print& print, ObjectID object_id)
static size_t find_skirt_brim_group_idx(const Print& print, ObjectID object_id, size_t instance_id)
{
const std::vector<Print::SkirtBrimGroup>& groups = print.skirt_brim_groups();
for (size_t idx = 0; idx < groups.size(); ++idx)
if (std::find(groups[idx].object_ids.begin(), groups[idx].object_ids.end(), object_id) != groups[idx].object_ids.end())
for (size_t idx = 0; idx < groups.size(); ++idx) {
const std::vector<ObjectInstanceID>& instances = groups[idx].instances;
if (std::any_of(instances.begin(), instances.end(), [object_id, instance_id](const ObjectInstanceID& instance) {
return instance.object_id == object_id && instance.instance_id == instance_id;
}))
return idx;
}
return size_t(-1);
}
std::string GCode::generate_object_skirt_group(const Print &print,
const PrintObject &object,
size_t instance_id,
const LayerTools &layer_tools,
const Layer& layer,
unsigned int extruder_id)
{
if (print.config().skirt_type != stPerObject || print.skirt_brim_groups().empty())
if (print.config().skirt_type != stPerObject || !layer_tools.has_skirt || print.skirt_brim_groups().empty())
return {};
const size_t group_idx = find_skirt_brim_group_idx(print, object.id());
const size_t group_idx = find_skirt_brim_group_idx(print, object.id(), instance_id);
if (group_idx == size_t(-1) || print.skirt_brim_groups()[group_idx].skirt.empty())
return {};
@@ -4930,15 +4952,15 @@ std::string GCode::generate_object_skirt_group(const Print &print,
object_skirt_tools, layer, extruder_id, m_skirt_group_done[group_idx]);
}
std::string GCode::generate_object_brim(const Print &print, const PrintObject &object, bool first_layer)
std::string GCode::generate_object_brim(const Print &print, const PrintObject &object, size_t instance_id, bool first_layer)
{
if (!first_layer)
return {};
auto emit_brim = [this](const ExtrusionEntityCollection& brim, const std::vector<ObjectID>& object_ids) {
auto emit_brim = [this](const ExtrusionEntityCollection& brim, const std::vector<ObjectInstanceID>& instances) {
std::string gcode;
const bool already_emitted = std::none_of(object_ids.begin(), object_ids.end(), [this](ObjectID object_id) {
return m_objsWithBrim.find(object_id) != m_objsWithBrim.end();
const bool already_emitted = std::none_of(instances.begin(), instances.end(), [this](const ObjectInstanceID& instance) {
return m_objsWithBrim.find(instance) != m_objsWithBrim.end();
});
if (already_emitted || brim.empty())
return gcode;
@@ -4950,32 +4972,22 @@ std::string GCode::generate_object_brim(const Print &print, const PrintObject &o
gcode += this->extrude_entity(*ee, "brim", NOZZLE_CONFIG(support_speed));
m_avoid_crossing_perimeters.use_external_mp(false);
m_avoid_crossing_perimeters.disable_once();
for (ObjectID object_id : object_ids)
m_objsWithBrim.erase(object_id);
for (const ObjectInstanceID& instance : instances)
m_objsWithBrim.erase(instance);
return gcode;
};
const bool has_per_object_skirt_or_shield = print.config().skirt_type == stPerObject &&
(print.has_skirt() || print.has_infinite_skirt());
if (print.config().combine_brims && !has_per_object_skirt_or_shield &&
print.config().print_sequence != PrintSequence::ByObject && print.m_brimMap.size() == 1) {
const auto brim_it = print.m_brimMap.begin();
return emit_brim(brim_it->second, { brim_it->first });
}
const size_t group_idx = find_skirt_brim_group_idx(print, object.id());
const ObjectInstanceID object_instance_id{ object.id(), instance_id };
const size_t group_idx = find_skirt_brim_group_idx(print, object.id(), instance_id);
if (group_idx != size_t(-1)) {
std::string gcode;
for (const Print::SkirtBrimGroup::Brim& brim : print.skirt_brim_groups()[group_idx].brims)
if (std::find(brim.object_ids.begin(), brim.object_ids.end(), object.id()) != brim.object_ids.end())
gcode += emit_brim(brim.brim, brim.object_ids);
if (std::find(brim.instances.begin(), brim.instances.end(), object_instance_id) != brim.instances.end())
gcode += emit_brim(brim.brim, brim.instances);
return gcode;
}
const auto brim_it = print.m_brimMap.find(object.id());
if (brim_it == print.m_brimMap.end())
return {};
return emit_brim(brim_it->second, { object.id() });
return {};
}
// Bedslinger model. The heavier the bed load, the lower the achievable Y acceleration for a given
@@ -6096,8 +6108,8 @@ LayerResult GCode::process_layer(
const auto& inst = instance_to_print.print_object.instances()[instance_to_print.instance_id];
const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id];
if (print_wipe_extrusions == (is_anything_overridden ? 1 : 0)) {
gcode += generate_object_skirt_group(print, instance_to_print.print_object, layer_tools, layer, extruder_id);
gcode += generate_object_brim(print, instance_to_print.print_object, first_layer);
gcode += generate_object_skirt_group(print, instance_to_print.print_object, instance_to_print.instance_id, layer_tools, layer, extruder_id);
gcode += generate_object_brim(print, instance_to_print.print_object, instance_to_print.instance_id, first_layer);
}
// To control print speed of the 1st object layer printed over raft interface.
@@ -6152,18 +6164,6 @@ LayerResult GCode::process_layer(
m_layer = layers[instance_to_print.layer_id].support_layer;
m_object_layer_over_raft = false;
//BBS: print supports' brims first
if (this->m_objSupportsWithBrim.find(instance_to_print.print_object.id()) != this->m_objSupportsWithBrim.end() && !print_wipe_extrusions) {
this->set_origin(0., 0.);
m_avoid_crossing_perimeters.use_external_mp();
for (const ExtrusionEntity* ee : print.m_supportBrimMap.at(instance_to_print.print_object.id()).entities) {
gcode += this->extrude_entity(*ee, "brim", NOZZLE_CONFIG(support_speed));
}
m_avoid_crossing_perimeters.use_external_mp(false);
// Allow a straight travel move to the first object point.
m_avoid_crossing_perimeters.disable_once();
this->m_objSupportsWithBrim.erase(instance_to_print.print_object.id());
}
// When starting a new object, use the external motion planner for the first travel move.
const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift;
std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset);
@@ -7549,7 +7549,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// If adaptive PA is enabled, by default evaluate PA on all extrusion moves
bool is_pa_calib = m_curr_print->calib_mode() == CalibMode::Calib_PA_Line ||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern ||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower;
m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower;
bool evaluate_adaptive_pa = false;
bool role_change = (m_last_extrusion_role != path.role());
if (!is_pa_calib && FILAMENT_CONFIG(adaptive_pressure_advance) && FILAMENT_CONFIG(enable_pressure_advance)) {
@@ -9104,7 +9104,7 @@ std::string GCode::set_object_info(Print *print) {
std::ostringstream gcode;
size_t object_id = 0;
// Orca: check if we are in pa calib mode
if (print->calib_mode() == CalibMode::Calib_PA_Line || print->calib_mode() == CalibMode::Calib_PA_Pattern) {
if (print->calib_mode() == CalibMode::Calib_PA_Pattern) {
BoundingBoxf bbox_bed(print->config().printable_area.values);
bbox_bed.offset(-25.0);
Polygon polygon_bed;
@@ -9115,6 +9115,8 @@ std::string GCode::set_object_info(Print *print) {
gcode << "EXCLUDE_OBJECT_DEFINE NAME="
<< "Orca-PA-Calibration-Test"
<< " CENTER=" << 0 << "," << 0 << " POLYGON=" << polygon_to_string(polygon_bed, print, true) << "\n";
} else if (print->calib_mode() == CalibMode::Calib_PA_Line) {
// PA_Line has only one object, no EXCLUDE_OBJECT_DEFINE needed
} else {
size_t unique_id = 0;
for (PrintObject* object : print->objects()) {
+3 -2
View File
@@ -359,11 +359,13 @@ private:
std::vector<coordf_t> &skirt_done);
std::string generate_object_skirt_group(const Print &print,
const PrintObject &object,
size_t instance_id,
const LayerTools &layer_tools,
const Layer& layer,
unsigned int extruder_id);
std::string generate_object_brim(const Print &print,
const PrintObject &object,
size_t instance_id,
bool first_layer);
LayerResult process_layer(
@@ -533,8 +535,7 @@ private:
// BBS
LiftType to_lift_type(ZHopType z_hop_types);
std::set<ObjectID> m_objsWithBrim; // indicates the objs with brim
std::set<ObjectID> m_objSupportsWithBrim; // indicates the objs' supports with brim
std::set<ObjectInstanceID> m_objsWithBrim; // indicates the object instances with brim
// Cache for custom seam enforcers/blockers for each layer.
SeamPlacer m_seam_placer;
-379
View File
@@ -1,379 +0,0 @@
#include "PostProcessor.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/format.hpp"
#include "libslic3r/I18N.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <boost/format.hpp>
#include <boost/filesystem.hpp>
#include <boost/nowide/cstdlib.hpp>
#include <boost/nowide/convert.hpp>
#include <boost/nowide/fstream.hpp>
// BBS
#include <iostream>
#include <fstream>
#ifdef WIN32
// The standard Windows includes.
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <shellapi.h>
// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
// This routine appends the given argument to a command line such that CommandLineToArgvW will return the argument string unchanged.
// Arguments in a command line should be separated by spaces; this function does not add these spaces.
// Argument - Supplies the argument to encode.
// CommandLine - Supplies the command line to which we append the encoded argument string.
static void quote_argv_winapi(const std::wstring &argument, std::wstring &commmand_line_out)
{
// Don't quote unless we actually need to do so --- hopefully avoid problems if programs won't parse quotes properly.
if (argument.empty() == false && argument.find_first_of(L" \t\n\v\"") == argument.npos)
commmand_line_out.append(argument);
else {
commmand_line_out.push_back(L'"');
for (auto it = argument.begin(); ; ++ it) {
unsigned number_backslashes = 0;
while (it != argument.end() && *it == L'\\') {
++ it;
++ number_backslashes;
}
if (it == argument.end()) {
// Escape all backslashes, but let the terminating double quotation mark we add below be interpreted as a metacharacter.
commmand_line_out.append(number_backslashes * 2, L'\\');
break;
} else if (*it == L'"') {
// Escape all backslashes and the following double quotation mark.
commmand_line_out.append(number_backslashes * 2 + 1, L'\\');
commmand_line_out.push_back(*it);
} else {
// Backslashes aren't special here.
commmand_line_out.append(number_backslashes, L'\\');
commmand_line_out.push_back(*it);
}
}
commmand_line_out.push_back(L'"');
}
}
static DWORD execute_process_winapi(const std::wstring &command_line)
{
// Extract the current environment to be passed to the child process.
std::wstring envstr;
{
wchar_t *env = GetEnvironmentStrings();
assert(env != nullptr);
const wchar_t* var = env;
size_t totallen = 0;
size_t len;
while ((len = wcslen(var)) > 0) {
totallen += len + 1;
var += len + 1;
}
envstr = std::wstring(env, totallen);
FreeEnvironmentStrings(env);
}
STARTUPINFOW startup_info;
memset(&startup_info, 0, sizeof(startup_info));
startup_info.cb = sizeof(STARTUPINFO);
#if 0
startup_info.dwFlags = STARTF_USESHOWWINDOW;
startup_info.wShowWindow = SW_HIDE;
#endif
PROCESS_INFORMATION process_info;
if (! ::CreateProcessW(
nullptr /* lpApplicationName */, (LPWSTR)command_line.c_str(), nullptr /* lpProcessAttributes */, nullptr /* lpThreadAttributes */, false /* bInheritHandles */,
CREATE_UNICODE_ENVIRONMENT /* | CREATE_NEW_CONSOLE */ /* dwCreationFlags */, (LPVOID)envstr.c_str(), nullptr /* lpCurrentDirectory */, &startup_info, &process_info))
throw Slic3r::RuntimeError(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError())));
::WaitForSingleObject(process_info.hProcess, INFINITE);
ULONG rc = 0;
::GetExitCodeProcess(process_info.hProcess, &rc);
::CloseHandle(process_info.hThread);
::CloseHandle(process_info.hProcess);
return rc;
}
// Run the script. If it is a perl script, run it through the bundled perl interpreter.
// If it is a batch file, run it through the cmd.exe.
// Otherwise run it directly.
static int run_script(const std::string &script, const std::string &gcode, std::string &/*std_err*/)
{
// Unpack the argument list provided by the user.
int nArgs;
LPWSTR *szArglist = CommandLineToArgvW(boost::nowide::widen(script).c_str(), &nArgs);
if (szArglist == nullptr || nArgs <= 0) {
// CommandLineToArgvW failed. Maybe the command line escapment is invalid?
throw Slic3r::RuntimeError(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path.");
}
std::wstring command_line;
std::wstring command = szArglist[0];
if (! boost::filesystem::exists(boost::filesystem::path(command)))
throw Slic3r::RuntimeError(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command));
if (boost::iends_with(command, L".pl")) {
// This is a perl script. Run it through the perl interpreter.
// The current process may be slic3r.exe or slic3r-console.exe.
// Find the path of the process:
wchar_t wpath_exe[_MAX_PATH + 1];
::GetModuleFileNameW(nullptr, wpath_exe, _MAX_PATH);
boost::filesystem::path path_exe(wpath_exe);
boost::filesystem::path path_perl = path_exe.parent_path() / "perl" / "perl.exe";
if (! boost::filesystem::exists(path_perl)) {
LocalFree(szArglist);
throw Slic3r::RuntimeError(std::string("Perl interpreter ") + path_perl.string() + " does not exist.");
}
// Replace it with the current perl interpreter.
quote_argv_winapi(boost::nowide::widen(path_perl.string()), command_line);
command_line += L" ";
} else if (boost::iends_with(command, ".bat")) {
// Run a batch file through the command line interpreter.
command_line = L"cmd.exe /C ";
}
for (int i = 0; i < nArgs; ++ i) {
quote_argv_winapi(szArglist[i], command_line);
command_line += L" ";
}
LocalFree(szArglist);
quote_argv_winapi(boost::nowide::widen(gcode), command_line);
return (int)execute_process_winapi(command_line);
}
#else
// POSIX
#include <cstdlib> // getenv()
#include <sstream>
#include <boost/process.hpp>
namespace process = boost::process;
static int run_script(const std::string &script, const std::string &gcode, std::string &std_err)
{
// Try to obtain user's default shell
const char *shell = ::getenv("SHELL");
if (shell == nullptr) { shell = "/bin/sh"; }
// Quote and escape the gcode path argument
std::string command { script };
command.append(" '");
for (char c : gcode) {
if (c == '\'') { command.append("'\\''"); }
else { command.push_back(c); }
}
command.push_back('\'');
BOOST_LOG_TRIVIAL(debug) << boost::format("Executing script, shell: %1%, command: %2%") % shell % command;
process::ipstream istd_err;
process::child child(shell, "-c", command, process::std_err > istd_err);
std_err.clear();
std::string line;
while (child.running() && std::getline(istd_err, line)) {
std_err.append(line);
std_err.push_back('\n');
}
child.wait();
return child.exit_code();
}
#endif
namespace Slic3r {
//! macro used to mark string used at localization,
//! return same string
#define L(s) (s)
#define _(s) Slic3r::I18N::translate(s)
// BBS
void gcode_add_line_number(const std::string& path, const DynamicPrintConfig& config)
{
const ConfigOptionBool* opt = config.opt<ConfigOptionBool>("gcode_add_line_number");
if (!opt->getBool())
return;
auto gcode_file = boost::filesystem::path(path);
if (!boost::filesystem::exists(gcode_file))
return;
std::fstream fs;
std::string new_gcode;
fs.open(gcode_file.c_str(), std::fstream::in | std::fstream::out);
size_t line_number = 1;
std::string gcode_line;
while (std::getline(fs, gcode_line)) {
char num_str[128];
memset(num_str, 0, sizeof(num_str));
snprintf(num_str, sizeof(num_str), "%zd", line_number);
new_gcode += std::string("N") + num_str + " " + gcode_line + "\n";
line_number++;
}
fs.clear();
fs.seekp(0, std::ios_base::beg);
fs.write(new_gcode.c_str(), new_gcode.length());
fs.close();
}
// Run post processing script / scripts if defined.
// Returns true if a post-processing script was executed.
// Returns false if no post-processing script was defined.
// Throws an exception on error.
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
// For a "File" target, a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
// In that case the caller is responsible to delete the temp file created.
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
// The post-processing script may change the output_name.
bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config)
{
const auto *post_process = config.opt<ConfigOptionStrings>("post_process");
if (// likely running in SLA mode
post_process == nullptr ||
// no post-processing script
post_process->values.empty())
return false;
std::string path;
if (make_copy) {
// Don't run the post-processing script on the input file, it will be memory mapped by the G-code viewer.
// Make a copy.
path = src_path + ".pp";
// First delete an old file if it exists.
try {
if (boost::filesystem::exists(path))
boost::filesystem::remove(path);
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting an old temporary file %1% before running a post-processing script: %2%", path, err.what());
}
// Second make a copy.
std::string error_message;
if (copy_file(src_path, path, error_message, false) != SUCCESS)
throw Slic3r::RuntimeError(Slic3r::format("Failed making a temporary copy of G-code file %1% before running a post-processing script: %2%", src_path, error_message));
} else {
// Don't make a copy of the G-code before running the post-processing script.
path = src_path;
}
auto delete_copy = [&path, &src_path, make_copy]() {
if (make_copy)
try {
if (boost::filesystem::exists(path))
boost::filesystem::remove(path);
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a temporary copy %1% of a G-code file %2% : %3%", path, src_path, err.what());
}
};
auto gcode_file = boost::filesystem::path(path);
if (! boost::filesystem::exists(gcode_file))
throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file"));
// Store print configuration into environment variables.
config.setenv_();
// Let the post-processing script know the target host ("File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...)
boost::nowide::setenv("SLIC3R_PP_HOST", host.c_str(), 1);
// Let the post-processing script know the final file name. For "File" host, it is a full path of the target file name and its location, for example pointing to an SD card.
// For "PrusaLink" or "OctoPrint", it is a file name optionally with a directory on the target host.
boost::nowide::setenv("SLIC3R_PP_OUTPUT_NAME", output_name.c_str(), 1);
// Path to an optional file that the post-processing script may create and populate it with a single line containing the output_name replacement.
std::string path_output_name = path + ".output_name";
auto remove_output_name_file = [&path_output_name, &src_path]() {
try {
if (boost::filesystem::exists(path_output_name))
boost::filesystem::remove(path_output_name);
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a file %1% carrying the final name / path of a G-code file %2%: %3%", path_output_name, src_path, err.what());
}
};
// Remove possible stalled path_output_name of the previous run.
remove_output_name_file();
try {
for (const std::string &scripts : post_process->values) {
std::vector<std::string> lines;
boost::split(lines, scripts, boost::is_any_of("\r\n"));
for (std::string script : lines) {
// Ignore empty post processing script lines.
boost::trim(script);
if (script.empty())
continue;
BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
std::string std_err;
const int result = run_script(script, gcode_file.string(), std_err);
if (result != 0) {
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
BOOST_LOG_TRIVIAL(error) << msg;
delete_copy();
throw Slic3r::RuntimeError(msg);
}
if (! boost::filesystem::exists(gcode_file)) {
const std::string msg = (boost::format(_(L(
"Post-processing script %1% failed.\n\n"
"The post-processing script is expected to change the G-code file %2% in place, but the G-code file was deleted and likely saved under a new name.\n"
"Please adjust the post-processing script to change the G-code in place and consult the manual on how to optionally rename the post-processed G-code file.\n")))
% script % path).str();
BOOST_LOG_TRIVIAL(error) << msg;
throw Slic3r::RuntimeError(msg);
}
}
}
if (boost::filesystem::exists(path_output_name)) {
try {
// Read a single line from path_output_name, which should contain the new output name of the post-processed G-code.
boost::nowide::fstream f;
f.open(path_output_name, std::ios::in);
std::string new_output_name;
std::getline(f, new_output_name);
f.close();
if (host == "File") {
namespace fs = boost::filesystem;
fs::path op(new_output_name);
if (op.is_relative() && op.has_filename() && op.parent_path().empty()) {
// Is this just a filename? Make it an absolute path.
auto outpath = fs::path(output_name).parent_path();
outpath /= op.string();
new_output_name = outpath.string();
}
else {
if (! op.is_absolute() || ! op.has_filename())
throw Slic3r::RuntimeError("Unable to parse desired new path from output name file");
}
if (! fs::exists(fs::path(new_output_name).parent_path()))
throw Slic3r::RuntimeError(Slic3r::format("Output directory does not exist: %1%",
fs::path(new_output_name).parent_path().string()));
}
BOOST_LOG_TRIVIAL(trace) << "Post-processing script changed the file name from " << output_name << " to " << new_output_name;
output_name = new_output_name;
} catch (const std::exception &err) {
throw Slic3r::RuntimeError(Slic3r::format("run_post_process_scripts: Failed reading a file %1% "
"carrying the final name / path of a G-code file: %2%",
path_output_name, err.what()));
}
remove_output_name_file();
}
} catch (...) {
remove_output_name_file();
delete_copy();
throw;
}
src_path = std::move(path);
return true;
}
} // namespace Slic3r
-34
View File
@@ -1,34 +0,0 @@
#ifndef slic3r_GCode_PostProcessor_hpp_
#define slic3r_GCode_PostProcessor_hpp_
#include <string>
#include "../libslic3r.h"
#include "../PrintConfig.hpp"
namespace Slic3r {
// Run post processing script / scripts if defined.
// Returns true if a post-processing script was executed.
// Returns false if no post-processing script was defined.
// Throws an exception on error.
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
// If make_copy, then a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
// In that case the caller is responsible to delete the temp file created.
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
// The post-processing script may change the output_name.
extern bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config);
inline bool run_post_process_scripts(std::string &src_path, const DynamicPrintConfig &config)
{
std::string src_path_name = src_path;
return run_post_process_scripts(src_path, false, "File", src_path_name, config);
}
// BBS
extern void gcode_add_line_number(const std::string &path, const DynamicPrintConfig &config);
} // namespace Slic3r
#endif /* slic3r_GCode_PostProcessor_hpp_ */
+13
View File
@@ -3,6 +3,7 @@
#include <cereal/access.hpp>
#include <cereal/types/base_class.hpp>
#include <cstddef>
namespace Slic3r {
@@ -41,6 +42,18 @@ private:
template<class Archive> void serialize(Archive &ar) { ar(id); }
};
struct ObjectInstanceID {
ObjectID object_id;
size_t instance_id { size_t(-1) };
bool operator==(const ObjectInstanceID& rhs) const { return object_id == rhs.object_id && instance_id == rhs.instance_id; }
bool operator!=(const ObjectInstanceID& rhs) const { return !(*this == rhs); }
bool operator<(const ObjectInstanceID& rhs) const
{
return object_id < rhs.object_id || (object_id == rhs.object_id && instance_id < rhs.instance_id);
}
};
// Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID
// to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject).
// Also base for Print, PrintObject, SLAPrint, SLAPrintObject to provide a unique ID for matching Model / ModelObject
+26
View File
@@ -105,6 +105,32 @@ PlatformFlavor platform_flavor()
return s_platform_flavor;
}
std::string platform_os_type()
{
#if defined(_WIN32)
return "win";
#elif defined(__APPLE__)
return "macos";
#elif defined(__linux__) || defined(__LINUX__)
return "linux";
#else
return "unknown";
#endif
}
std::string platform_architecture()
{
#if defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64)
return "arm64";
#elif defined(__x86_64__) || defined(__x86_64) || defined(__amd64__) || defined(__amd64) || defined(_M_X64) || defined(_M_AMD64)
return "x86_64";
#elif defined(__i386__) || defined(__i386) || defined(i386) || defined(_M_IX86)
return "i386";
#else
return "unknown";
#endif
}
std::string platform_to_string(Platform platform)
+2
View File
@@ -36,6 +36,8 @@ void detect_platform();
Platform platform();
PlatformFlavor platform_flavor();
std::string platform_os_type();
std::string platform_architecture();
std::string platform_to_string(Platform platform);
std::string platform_flavor_to_string(PlatformFlavor pf);
+19 -3
View File
@@ -1201,6 +1201,9 @@ static std::vector<std::string> s_Preset_print_options{
"min_feature_size",
"min_bead_width",
"post_process",
"slicing_pipeline_plugin",
"plugins",
"plugin_config_overrides",
"process_change_extrusion_role_gcode",
"min_length_factor",
"wall_maximum_resolution",
@@ -1375,6 +1378,7 @@ static std::vector<std::string> s_Preset_filament_options {/*"filament_colour",
"filament_preheat_temperature_delta", "filament_retract_length_nc",
"filament_change_length_nc", "filament_prime_volume_nc",
"long_retractions_when_ec", "retraction_distances_when_ec",
"plugin_config_overrides",
//ams chamber
"filament_dev_ams_drying_ams_limitations", "filament_dev_ams_drying_temperature", "filament_dev_ams_drying_time", "filament_dev_ams_drying_heat_distortion_temperature",
"filament_dev_chamber_drying_bed_temperature", "filament_dev_chamber_drying_time",
@@ -1425,7 +1429,8 @@ static std::vector<std::string> s_Preset_printer_options {
"machine_hotend_change_time", "machine_prepare_compensation_time",
// Fast-purge printer flag + device/firmware-facing per-variant extruder-change
// deretraction speed (unconsumed by the slicer; carried by H2D/A2L/X2D/P2S machine profiles).
"support_fast_purge_mode", "deretract_speed_extruder_change"
"support_fast_purge_mode", "deretract_speed_extruder_change",
"plugin_config_overrides"
};
static std::vector<std::string> s_Preset_sla_print_options {
@@ -3453,7 +3458,18 @@ void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys&
for (int i = 0; i < int(opt_cur->values.size()); i++)
{
int init_id = i <= opt_init_max_id ? i : 0;
const bool is_new_index = i > opt_init_max_id;
int init_id = is_new_index ? 0 : i;
if (is_new_index) {
// Orca: intentional divergence from upstream. Any new vector index (at or
// beyond the reference vector's length) is flagged dirty unconditionally --
// independent of its value and nil-state -- so preset dirty-detection notices
// per-extruder/filament entries added by growth (e.g. extruder count). This
// applies to every vector option type routed through deep_diff().
// Covered by tests/libslic3r/test_preset_diff.cpp.
vec.emplace_back(opt_key + "#" + std::to_string(i));
continue;
}
if (opt_cur->values[i] != opt_init->values[init_id]) {
if (opt_cur->nullable()) {
if (opt_cur->is_nil(i)) {
@@ -3488,7 +3504,7 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi
if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt)
{
//BBS: add bed_exclude_area
if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnails" || opt_key == "wrapping_exclude_area") {
if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnails" || opt_key == "wrapping_exclude_area" || opt_key == "slicing_pipeline_plugin") {
// Scalar variable, or a vector variable, which is independent from number of extruders,
// thus the vector is presented to the user as a single input.
diff.emplace_back(opt_key);
+175 -90
View File
@@ -51,6 +51,8 @@ using namespace nlohmann;
namespace Slic3r {
Print::SlicingPipelineHookFn Print::s_slicing_pipeline_hook_fn = nullptr;
template class PrintState<PrintStep, psCount>;
template class PrintState<PrintObjectStep, posCount>;
@@ -124,6 +126,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"printing_by_object_gcode",
"filament_end_gcode",
"post_process",
// "plugins" is the derived manifest backing the plugin-picker options; on its own it only
// affects G-code export. The specific option (e.g. slicing_pipeline_plugin) drives any re-slice.
"plugins",
"extruder_clearance_height_to_rod",
"extruder_clearance_height_to_lid",
"extruder_clearance_radius",
@@ -279,7 +284,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "wipe_tower_rotation_angle") {
steps.emplace_back(psSkirtBrim);
} else if (
opt_key == "initial_layer_print_height"
opt_key == "slicing_pipeline_plugin"
|| opt_key == "initial_layer_print_height"
|| opt_key == "nozzle_diameter"
|| opt_key == "filament_shrink"
|| opt_key == "filament_shrinkage_compensation_z"
@@ -2207,6 +2213,11 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
if (time_cost_with_cache)
*time_cost_with_cache = 0;
{
const auto* sp = this->config().option<ConfigOptionStrings>("slicing_pipeline_plugin");
m_pipeline_plugin_active = s_slicing_pipeline_hook_fn && sp && !sp->values.empty();
}
name_tbb_thread_pool_threads_set_locale();
//compute the PrintObject with the same geometries
@@ -2316,20 +2327,47 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": total object counts %1% in current print, need to slice %2%")%m_objects.size()%need_slicing_objects.size();
BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info();
if (!use_cache) {
// Fire the SlicingPipeline hook for `obj` iff it just (re)computed `pstep` this pass.
auto hook_after = [this](PrintObject* obj, bool was_done, PrintObjectStep pstep, SlicingPipelineStepPlugin sstep) {
if (m_pipeline_plugin_active && !was_done && obj->is_step_done(pstep))
run_pipeline_hook(sstep, obj);
};
// SlicingPipeline: dedicated slice loop so the Slice boundary is hookable before perimeters.
for (PrintObject *obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
obj->make_perimeters();
}
else {
if (obj->set_started(posSlice))
obj->set_done(posSlice);
if (obj->set_started(posPerimeters))
obj->set_done(posPerimeters);
const bool was_done = obj->is_step_done(posSlice);
obj->slice();
hook_after(obj, was_done, posSlice, SlicingPipelineStepPlugin::posSlice);
// re-snapshot each layer's raw_slices AFTER the Slice hook ran, so the
// plugin's mutation becomes the untyped baseline. Without this, a later
// perimeter-only re-run (make_perimeters -> restore_untyped_slices) reverts
// slices to the PRE-hook geometry while posSlice stays cached (the hook does
// not re-fire), silently un-applying the mutation; raw_slices consumers
// (sharp-tail support, ToolOrdering) also read this backup directly. Gated on
// an active plugin AND a genuine (re)slice, so the inactive path is untouched
// and re-backing-up an unmutated layer is a harmless identical copy.
if (m_pipeline_plugin_active && !was_done && obj->is_step_done(posSlice))
for (Layer *layer : obj->layers())
layer->backup_untyped_slices();
} else {
if (obj->set_started(posSlice)) obj->set_done(posSlice); // shared/duplicate — no hook
}
}
for (PrintObject *obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
const bool was_done = obj->is_step_done(posPerimeters);
obj->make_perimeters(); // slice() inside is a no-op: posSlice already DONE
hook_after(obj, was_done, posPerimeters, SlicingPipelineStepPlugin::posPerimeters);
} else {
if (obj->set_started(posPerimeters)) obj->set_done(posPerimeters);
}
}
for (PrintObject *obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
const bool was_done = obj->is_step_done(posEstimateCurledExtrusions);
obj->estimate_curled_extrusions();
hook_after(obj, was_done, posEstimateCurledExtrusions, SlicingPipelineStepPlugin::posEstimateCurledExtrusions);
}
else {
if (obj->set_started(posEstimateCurledExtrusions))
@@ -2338,7 +2376,17 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
}
for (PrintObject *obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
// split prepare_infill (fill-surface prep) from infill (make_fills) so a
// plugin can mutate fill surfaces at the PrepareInfill seam and have make_fills
// consume them (unlike the Infill seam, which fires after the fills are already
// built). infill() re-invokes prepare_infill() as a no-op once posPrepareInfill
// is DONE, so this is a mechanical split mirroring the slice/perimeters loop.
const bool prepare_was_done = obj->is_step_done(posPrepareInfill);
obj->prepare_infill();
hook_after(obj, prepare_was_done, posPrepareInfill, SlicingPipelineStepPlugin::posPrepareInfill);
const bool was_done = obj->is_step_done(posInfill);
obj->infill();
hook_after(obj, was_done, posInfill, SlicingPipelineStepPlugin::posInfill);
}
else {
if (obj->set_started(posPrepareInfill))
@@ -2349,7 +2397,9 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
}
for (PrintObject *obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
const bool was_done = obj->is_step_done(posIroning);
obj->ironing();
hook_after(obj, was_done, posIroning, SlicingPipelineStepPlugin::posIroning);
}
else {
if (obj->set_started(posIroning))
@@ -2361,13 +2411,22 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
for (PrintObject *obj : m_objects) {
bool need_contouring = need_slicing_objects.count(obj) != 0 && obj->need_z_contouring();
if (need_contouring) {
const bool was_done = obj->is_step_done(posContouring);
obj->contour_z();
hook_after(obj, was_done, posContouring, SlicingPipelineStepPlugin::posContouring);
} else {
if (obj->set_started(posContouring))
obj->set_done(posContouring);
}
}
// SlicingPipeline: support runs in the parallel block below; the hook must fire in a
// sequential loop afterward. Snapshot per-object done-state just before the parallel_for.
std::vector<char> sup_was_done(m_objects.size(), 1);
if (m_pipeline_plugin_active)
for (size_t i = 0; i < m_objects.size(); ++i)
sup_was_done[i] = m_objects[i]->is_step_done(posSupportMaterial) ? 1 : 0;
tbb::parallel_for(tbb::blocked_range<int>(0, int(m_objects.size())),
[this, need_slicing_objects](const tbb::blocked_range<int>& range) {
for (int i = range.begin(); i < range.end(); i++) {
@@ -2383,9 +2442,17 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
}
);
if (m_pipeline_plugin_active)
for (size_t i = 0; i < m_objects.size(); ++i)
if (need_slicing_objects.count(m_objects[i]) != 0 && !sup_was_done[i]
&& m_objects[i]->is_step_done(posSupportMaterial))
run_pipeline_hook(SlicingPipelineStepPlugin::posSupportMaterial, m_objects[i]);
for (PrintObject* obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
const bool was_done = obj->is_step_done(posDetectOverhangsForLift);
obj->detect_overhangs_for_lift();
hook_after(obj, was_done, posDetectOverhangsForLift, SlicingPipelineStepPlugin::posDetectOverhangsForLift);
}
else {
if (obj->set_started(posDetectOverhangsForLift))
@@ -2462,6 +2529,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
}
this->set_done(psWipeTower);
if (m_pipeline_plugin_active) run_pipeline_hook(SlicingPipelineStepPlugin::psWipeTower, nullptr);
}
if (this->has_wipe_tower()) {
@@ -2478,8 +2546,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
m_skirt_brim_groups.clear();
m_has_shared_per_object_skirt = false;
m_skirt_convex_hull.clear();
m_objectBrimAreas.clear();
m_supportBrimAreas.clear();
m_objectBrimAreasByInstance.clear();
m_first_layer_convex_hull.points.clear();
for (PrintObject *object : m_objects) object->m_skirt.clear();
@@ -2635,14 +2702,16 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
if (!existObject && objectExtruderMap.find(print_object_ID) != objectExtruderMap.end())
objPrintVec.push_back(std::make_pair(print_object_ID, objectExtruderMap.at(print_object_ID)));
}
// BBS: m_brimMap and m_supportBrimMap are used instead of m_brim to generate brim of objs and supports seperately
// Orca: Build both the old object-keyed brim map and the per-instance
// maps used by skirt/brim groups.
m_brimMap.clear();
m_supportBrimMap.clear();
m_brimMapByInstance.clear();
m_first_layer_convex_hull.points.clear();
if (this->has_brim()) {
Polygons islands_area;
make_brim(*this, this->make_try_cancel(), islands_area, m_brimMap,
m_supportBrimMap, objPrintVec, printExtruders, &m_objectBrimAreas, &m_supportBrimAreas);
m_brimMapByInstance, objPrintVec, printExtruders,
&m_objectBrimAreasByInstance);
for (Polygon& poly_ex : islands_area)
poly_ex.douglas_peucker(SCALED_RESOLUTION);
for (Polygon &poly : union_(this->first_layer_islands(), islands_area))
@@ -2664,6 +2733,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
this->finalize_first_layer_convex_hull();
this->set_done(psSkirtBrim);
if (m_pipeline_plugin_active) run_pipeline_hook(SlicingPipelineStepPlugin::psSkirtBrim, nullptr);
if (time_cost_with_cache) {
end_time = (long long)Slic3r::Utils::get_current_time_utc();
@@ -2674,7 +2744,13 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
for (PrintObject *obj : m_objects) {
if (((!use_cache)&&(need_slicing_objects.count(obj) != 0))
|| (use_cache &&(re_slicing_objects.count(obj) != 0))){
const bool was_done = obj->is_step_done(posSimplifyPath);
obj->simplify_extrusion_path();
// Unlike every other seam (all inside the `if (!use_cache)` block above), this loop is
// shared with the use_cache path (re_slicing_objects), so `!use_cache` must be checked
// explicitly here to keep hooks from ever firing on cache-loaded (plugin-final) objects.
if (!use_cache && m_pipeline_plugin_active && !was_done && obj->is_step_done(posSimplifyPath))
run_pipeline_hook(SlicingPipelineStepPlugin::posSimplifyPath, obj);
}
else {
if (obj->set_started(posSimplifyPath))
@@ -2784,7 +2860,8 @@ void Print::_make_skirt()
Polygon hull;
};
// Orca: build one local occupied hull per object from object and support geometry up to skirt height.
// Orca: Build one local occupied hull per object from object/support
// geometry up to skirt height. Instances translate this hull later.
std::vector<ObjectSkirtHull> object_convex_hulls;
for (PrintObject *object : m_objects) {
Points object_points;
@@ -2894,49 +2971,49 @@ void Print::_make_skirt()
object_hull.object->m_skirt.clear();
if (m_config.skirt_type == stCombined || m_config.skirt_type == stPerObject) {
struct SkirtGroupItem {
struct SkirtBrimGroupItem {
Points occupied_points;
ObjectID object_id;
size_t instance_id;
bool emits_skirt;
};
// Orca: group items represent occupied first-layer areas. Object items emit skirts;
// obstacle-only items, such as wipe tower, only force nearby object groups to merge.
std::vector<SkirtGroupItem> group_items;
// Orca: Each object instance can emit skirt/brim. Wipe tower is only an
// obstacle here; it may merge nearby items, but does not emit anything.
std::vector<SkirtBrimGroupItem> group_items;
const coord_t grouping_offset = scale_(m_config.skirt_distance.value + m_config.skirt_loops.value * spacing);
for (const ObjectSkirtHull& object_hull : object_convex_hulls) {
PrintObject* object = object_hull.object;
Points occupied_points;
for (const PrintInstance &instance : object->instances()) {
std::vector<size_t> object_item_indices;
object_item_indices.reserve(object->instances().size());
for (size_t instance_idx = 0; instance_idx < object->instances().size(); ++instance_idx) {
const PrintInstance &instance = object->instances()[instance_idx];
Points copy_points = object_hull.hull.points;
for (Point &pt : copy_points)
pt += instance.shift;
append(occupied_points, copy_points);
if (copy_points.size() < 3)
continue;
object_item_indices.push_back(group_items.size());
group_items.push_back({ std::move(copy_points), object->id(), instance_idx, true });
}
auto append_brim_points = [&occupied_points](const ExPolygons& areas) {
for (const ExPolygon& area : areas)
append(occupied_points, area.contour.points);
};
if (auto it = m_objectBrimAreas.find(object->id()); it != m_objectBrimAreas.end())
append_brim_points(it->second);
if (auto it = m_supportBrimAreas.find(object->id()); it != m_supportBrimAreas.end())
append_brim_points(it->second);
if (occupied_points.size() < 3)
continue;
// Orca: include the object's brim/support-brim footprint before checking skirt collisions.
group_items.push_back({ std::move(occupied_points), object->id(), true });
for (size_t item_idx : object_item_indices) {
const ObjectInstanceID key{ object->id(), group_items[item_idx].instance_id };
if (auto instance_brim_it = m_objectBrimAreasByInstance.find(key); instance_brim_it != m_objectBrimAreasByInstance.end())
for (const ExPolygon& area : instance_brim_it->second)
append(group_items[item_idx].occupied_points, area.contour.points);
}
}
// Orca: the wipe tower contributes occupied area, but does not emit a skirt by itself.
Points wipe_tower_points = this->first_layer_wipe_tower_corners();
if (wipe_tower_points.size() >= 3)
group_items.push_back({ std::move(wipe_tower_points), ObjectID(), false });
group_items.push_back({ std::move(wipe_tower_points), ObjectID(), size_t(-1), false });
std::vector<size_t> parent(group_items.size());
std::iota(parent.begin(), parent.end(), 0);
// Orca: union-find keeps collision merging local without repeatedly rebuilding item lists.
// Orca: Use union-find so touching items can be merged while scanning.
auto find_parent = [&parent](size_t idx) {
while (parent[idx] != idx) {
parent[idx] = parent[parent[idx]];
@@ -2951,24 +3028,24 @@ void Print::_make_skirt()
parent[b] = a;
};
// Orca: combined skirt is the same grouping model with all items forced into one group.
// Orca: Combined skirt starts with all items in the same group.
if (m_config.skirt_type == stCombined && !group_items.empty())
for (size_t i = 1; i < group_items.size(); ++i)
unite(0, i);
auto build_grouped_points = [&]() {
struct GroupData {
Points points;
std::vector<ObjectID> object_ids;
bool emits_skirt = false;
auto build_skirt_brim_groups = [&]() {
struct SkirtBrimGroupData {
Points points;
std::vector<ObjectInstanceID> instances;
bool emits_skirt = false;
};
std::map<size_t, GroupData> grouped;
std::map<size_t, SkirtBrimGroupData> grouped;
for (size_t i = 0; i < group_items.size(); ++i) {
GroupData& group = grouped[find_parent(i)];
SkirtBrimGroupData& group = grouped[find_parent(i)];
append(group.points, group_items[i].occupied_points);
if (group_items[i].object_id.valid())
group.object_ids.push_back(group_items[i].object_id);
group.instances.push_back({ group_items[i].object_id, group_items[i].instance_id });
group.emits_skirt = group.emits_skirt || group_items[i].emits_skirt;
}
return grouped;
@@ -2977,16 +3054,18 @@ void Print::_make_skirt()
bool groups_changed = m_config.skirt_type == stPerObject;
while (groups_changed) {
groups_changed = false;
auto grouped_points = build_grouped_points();
auto grouped_points = build_skirt_brim_groups();
std::vector<std::pair<size_t, Polygon>> group_envelopes;
for (const auto& [root, group] : grouped_points) {
if (group.points.size() < 3)
continue;
// Orca: emitting groups are expanded to their final skirt reach; obstacle groups are not.
// Orca: Only skirt-emitting groups are expanded by skirt distance;
// obstacle-only groups stay at their occupied outline.
Polygon envelope = Geometry::convex_hull(group.points);
if (group.emits_skirt) {
// Orca: merge groups when a skirt envelope intersects another group or obstacle.
// Orca: If the expanded skirt outline touches another group
// or obstacle, merge them and run the pass again.
Polygons envelopes = offset(envelope, grouping_offset, ClipperLib::jtRound, float(scale_(0.1)));
if (envelopes.empty())
continue;
@@ -3007,28 +3086,23 @@ void Print::_make_skirt()
}
}
auto make_group_brims = [this](const std::vector<ObjectID>& group_object_ids) {
auto make_brims_for_skirt_brim_group = [this](const std::vector<ObjectInstanceID>& group_instances) {
std::vector<SkirtBrimGroup::Brim> brims;
std::vector<ObjectID> brim_object_ids;
for (ObjectID object_id : group_object_ids) {
const auto brim_it = m_brimMap.find(object_id);
if (brim_it != m_brimMap.end() && !brim_it->second.empty())
brim_object_ids.push_back(object_id);
std::vector<ObjectInstanceID> brim_instances;
for (const ObjectInstanceID& instance : group_instances) {
const auto brim_it = m_brimMapByInstance.find(instance);
if (brim_it != m_brimMapByInstance.end() && !brim_it->second.empty())
brim_instances.push_back(instance);
}
const bool global_combined_brim = m_config.combine_brims && m_config.skirt_type != stPerObject && m_brimMap.size() == 1;
auto brim_owner_ids = [&group_object_ids, global_combined_brim](const std::vector<ObjectID>& object_ids) {
return global_combined_brim ? group_object_ids : object_ids;
};
const bool combine_group_brims = m_config.combine_brims && brim_object_ids.size() > 1;
const bool combine_group_brims = m_config.combine_brims && brim_instances.size() > 1;
if (!combine_group_brims) {
for (ObjectID object_id : brim_object_ids)
brims.push_back({ m_brimMap.at(object_id), brim_owner_ids({ object_id }) });
for (const ObjectInstanceID& instance : brim_instances)
brims.push_back({ m_brimMapByInstance.at(instance), { instance } });
return brims;
}
std::vector<size_t> brim_parent(brim_object_ids.size());
std::vector<size_t> brim_parent(brim_instances.size());
std::iota(brim_parent.begin(), brim_parent.end(), 0);
auto find_brim_parent = [&brim_parent](size_t idx) {
while (brim_parent[idx] != idx) {
@@ -3045,61 +3119,64 @@ void Print::_make_skirt()
};
const coord_t brim_contact_distance = coord_t(brim_flow().scaled_spacing() * 2.);
for (size_t i = 0; i < brim_object_ids.size(); ++i) {
const auto area_i = m_objectBrimAreas.find(brim_object_ids[i]);
if (area_i == m_objectBrimAreas.end())
for (size_t i = 0; i < brim_instances.size(); ++i) {
const auto area_i = m_objectBrimAreasByInstance.find(brim_instances[i]);
if (area_i == m_objectBrimAreasByInstance.end())
continue;
for (size_t j = i + 1; j < brim_object_ids.size(); ++j) {
const auto area_j = m_objectBrimAreas.find(brim_object_ids[j]);
if (area_j != m_objectBrimAreas.end() &&
for (size_t j = i + 1; j < brim_instances.size(); ++j) {
const auto area_j = m_objectBrimAreasByInstance.find(brim_instances[j]);
if (area_j != m_objectBrimAreasByInstance.end() &&
!intersection_ex(offset_ex(area_i->second, brim_contact_distance, jtRound, SCALED_RESOLUTION), area_j->second).empty())
unite_brims(i, j);
}
}
std::map<size_t, std::vector<ObjectID>> combined_brim_ids;
for (size_t i = 0; i < brim_object_ids.size(); ++i)
combined_brim_ids[find_brim_parent(i)].push_back(brim_object_ids[i]);
std::map<size_t, std::vector<ObjectInstanceID>> combined_brim_ids;
for (size_t i = 0; i < brim_instances.size(); ++i)
combined_brim_ids[find_brim_parent(i)].push_back(brim_instances[i]);
for (const auto& [_, object_ids] : combined_brim_ids) {
if (object_ids.size() == 1) {
brims.push_back({ m_brimMap.at(object_ids.front()), brim_owner_ids(object_ids) });
for (const auto& [_, instances] : combined_brim_ids) {
if (instances.size() == 1) {
const ObjectInstanceID& instance = instances.front();
brims.push_back({ m_brimMapByInstance.at(instance), { instance } });
continue;
}
ExPolygons combined_area;
for (ObjectID object_id : object_ids)
expolygons_append(combined_area, m_objectBrimAreas.at(object_id));
for (const ObjectInstanceID& instance : instances)
expolygons_append(combined_area, m_objectBrimAreasByInstance.at(instance));
combined_area = union_ex(combined_area);
const float scaled_resolution = float(scaled(m_config.resolution.value));
const float brim_cleanup_delta = std::max(scaled_resolution, float(SCALED_EPSILON));
combined_area = offset2_ex(combined_area, brim_cleanup_delta, -brim_cleanup_delta, jtRound, scaled_resolution);
Polygons islands_area;
brims.push_back({ makeBrimInfillFromPlateCoordinates(combined_area, *this, islands_area), object_ids });
brims.push_back({ makeBrimInfillFromPlateCoordinates(combined_area, *this, islands_area), instances });
}
return brims;
};
auto grouped_points = build_grouped_points();
auto grouped_points = build_skirt_brim_groups();
for (auto& [_, group] : grouped_points) {
if (!group.emits_skirt || group.points.size() < 3)
continue;
if (generate_skirt && m_config.skirt_type == stPerObject && group.object_ids.size() > 1)
if (generate_skirt && m_config.skirt_type == stPerObject && group.instances.size() > 1)
m_has_shared_per_object_skirt = true;
// Orca: after merging, use the occupied outline directly; do not add skirt distance twice.
// Orca: Group points already include the occupied outline, so don't
// add skirt distance here again.
ExtrusionEntityCollection group_skirt;
if (generate_skirt)
append_skirt_loops_for_hull(Geometry::convex_hull(group.points), group_skirt, true);
std::vector<SkirtBrimGroup::Brim> group_brims = make_group_brims(group.object_ids);
std::vector<SkirtBrimGroup::Brim> group_brims = make_brims_for_skirt_brim_group(group.instances);
if (!group_skirt.empty()) {
group_skirt.reverse();
// Orca: keep m_skirt as a flattened compatibility mirror for preview/extents.
// Orca: Keep m_skirt filled for code that still reads a flat
// skirt collection.
m_skirt.append(group_skirt.entities);
}
if (!group_skirt.empty() || !group_brims.empty())
m_skirt_brim_groups.push_back({ std::move(group_skirt), std::move(group.object_ids), std::move(group_brims) });
m_skirt_brim_groups.push_back({ std::move(group_skirt), std::move(group.instances), std::move(group_brims) });
}
}
}
@@ -4049,6 +4126,7 @@ void Print::_make_wipe_tower()
// Unload the current filament over the purge tower.
coordf_t layer_height = m_objects.front()->config().layer_height.value;
bool generate_final_purge = true;
if (m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions > 0) {
// The wipe tower goes up to the last layer of the print.
if (wipe_tower.layer_finished()) {
@@ -4060,11 +4138,14 @@ void Print::_make_wipe_tower()
// There is yet enough space at this layer of the wipe tower for the final purge.
}
} else {
// The wipe tower does not reach the last print layer, perform the pruge at the last print layer.
// The wipe tower does not reach the last print layer.
// Skip final purge to avoid generating purge lines in mid-air.
assert(m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions == 0);
wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z), float(layer_height), 0, false, true);
generate_final_purge = false;
}
m_wipe_tower_data.final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>(wipe_tower.tool_change((unsigned int) (-1)));
m_wipe_tower_data.final_purge = generate_final_purge
? Slic3r::make_unique<WipeTower::ToolChangeResult>(wipe_tower.tool_change((unsigned int)(-1)))
: Slic3r::make_unique<WipeTower::ToolChangeResult>();
m_wipe_tower_data.used_filament = wipe_tower.get_used_filament();
m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges();
@@ -4160,6 +4241,7 @@ void Print::_make_wipe_tower()
// Unload the current filament over the purge tower.
coordf_t layer_height = m_objects.front()->config().layer_height.value;
bool generate_final_purge = true;
if (m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions > 0) {
// The wipe tower goes up to the last layer of the print.
if (wipe_tower.layer_finished()) {
@@ -4171,11 +4253,14 @@ void Print::_make_wipe_tower()
// There is yet enough space at this layer of the wipe tower for the final purge.
}
} else {
// The wipe tower does not reach the last print layer, perform the pruge at the last print layer.
// The wipe tower does not reach the last print layer.
// Skip final purge to avoid generating purge lines in mid-air.
assert(m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions == 0);
wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z), float(layer_height), 0, false, true);
generate_final_purge = false;
}
m_wipe_tower_data.final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>(wipe_tower.tool_change((unsigned int) (-1)));
m_wipe_tower_data.final_purge = generate_final_purge
? Slic3r::make_unique<WipeTower::ToolChangeResult>(wipe_tower.tool_change((unsigned int)(-1)))
: Slic3r::make_unique<WipeTower::ToolChangeResult>();
m_wipe_tower_data.used_filament = wipe_tower.get_used_filament();
m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges();
+29 -8
View File
@@ -17,6 +17,7 @@
#include "GCode/ThumbnailData.hpp"
#include "GCode/GCodeProcessor.hpp"
#include "MultiMaterialSegmentation.hpp"
#include "ObjectID.hpp"
#include "libslic3r.h"
#include <Eigen/Geometry>
@@ -101,6 +102,14 @@ enum PrintObjectStep {
posCount,
};
enum class SlicingPipelineStepPlugin {
posSlice, posPerimeters, posEstimateCurledExtrusions, posPrepareInfill, posInfill, posIroning, posContouring,
posSupportMaterial, posDetectOverhangsForLift, posSimplifyPath, psWipeTower, psSkirtBrim,
// Fires from the GUI G-code export/post-process seam (PostProcessor.cpp), NOT from Print::process().
// At this step the plugin edits the exported G-code file in place; see SlicingPipelinePluginCapability for the full contract.
psGCodePostProcess
};
// A PrintRegion object represents a group of volumes to print
// sharing the same config (including the same assigned extruder(s))
class PrintRegion
@@ -894,6 +903,11 @@ private: // Prevents erroneous use by other classes.
typedef std::pair<PrintObject *, bool> PrintObjectInfo;
public:
using SlicingPipelineHookFn = std::function<void(Print&, const PrintObject*, SlicingPipelineStepPlugin)>;
// Cross-layer injection (mirrors ConfigBase::set_resolve_capability_fn): the GUI/plugin
// layer registers a dispatcher; libslic3r stays free of any plugin/Python dependency.
static void set_slicing_pipeline_hook_fn(SlicingPipelineHookFn fn) { s_slicing_pipeline_hook_fn = std::move(fn); }
Print() = default;
virtual ~Print() { this->clear(); }
@@ -961,7 +975,7 @@ public:
[object_id](const PrintObject *obj) { return obj->id() == object_id; });
return (it == m_objects.end()) ? nullptr : *it;
}
//BBS: Function to get m_brimMap;
// Orca: Old callers still expect object-keyed brim paths.
std::map<ObjectID, ExtrusionEntityCollection>&
get_brimMap() { return m_brimMap; }
@@ -976,11 +990,11 @@ public:
struct SkirtBrimGroup {
struct Brim {
ExtrusionEntityCollection brim;
std::vector<ObjectID> object_ids;
std::vector<ObjectInstanceID> instances;
};
ExtrusionEntityCollection skirt;
std::vector<ObjectID> object_ids;
std::vector<ObjectInstanceID> instances;
// Brims stay separate unless Combine brims merges colliding brims inside this group.
std::vector<Brim> brims;
};
@@ -1263,6 +1277,13 @@ private:
// Islands of objects and their supports extruded at the 1st layer.
Polygons first_layer_islands() const;
static SlicingPipelineHookFn s_slicing_pipeline_hook_fn;
bool m_pipeline_plugin_active { false };
void run_pipeline_hook(SlicingPipelineStepPlugin step, const PrintObject* object) {
if (m_pipeline_plugin_active && s_slicing_pipeline_hook_fn)
s_slicing_pipeline_hook_fn(*this, object, step);
}
PrintConfig m_config;
PrintObjectConfig m_default_object_config;
PrintRegionConfig m_default_region_config;
@@ -1276,12 +1297,12 @@ private:
ExtrusionEntityCollection m_skirt;
std::vector<SkirtBrimGroup> m_skirt_brim_groups;
bool m_has_shared_per_object_skirt { false };
// BBS: collecting extrusion paths to build brim by objs
// Orca: Object-keyed brim paths kept for existing code.
std::map<ObjectID, ExtrusionEntityCollection> m_brimMap;
std::map<ObjectID, ExtrusionEntityCollection> m_supportBrimMap;
// Orca: cached occupied brim footprints used when grouping per-object skirts.
std::map<ObjectID, ExPolygons> m_objectBrimAreas;
std::map<ObjectID, ExPolygons> m_supportBrimAreas;
// Orca: Actual brim paths keyed by object instance.
std::map<ObjectInstanceID, ExtrusionEntityCollection> m_brimMapByInstance;
// Orca: Translated brim areas keyed by instance, used to find touching brims.
std::map<ObjectInstanceID, ExPolygons> m_objectBrimAreasByInstance;
// Convex hull of the 1st layer extrusions.
// It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
// It does NOT encompass user extrusions generated by custom G-code,
+37 -7
View File
@@ -980,6 +980,9 @@ void PrintConfigDef::init_common_params()
def->tooltip = L("Select the network agent implementation for printer communication.");
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
// Plugin-backed (see ConfigOptionDef::is_plugin_backed), but edited through the Choice widget above
// rather than a plugin_picker field.
def->plugin_type = "printer-connection";
def->set_default_value(new ConfigOptionString(""));
def = this->add("print_host", coString);
@@ -1077,6 +1080,17 @@ void PrintConfigDef::init_common_params()
def = this->add("preset_name", coString);
def->set_default_value(new ConfigOptionString());
}
def = this->add("plugin_config_overrides", coString);
def->label = L("Capabilities");
def->tooltip = L("Configuration for the plugin capabilities this preset uses, overriding the global "
"Capabilities configuration. Stored as a raw JSON array and edited through the dialog "
"behind the button, never typed in directly.");
// Never shown as a text field: GUIType::plugin_config renders a button that opens PluginsConfigDialog.
def->gui_type = ConfigOptionDef::GUIType::plugin_config;
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionString(""));
}
void PrintConfigDef::init_fff_params()
@@ -2273,8 +2287,8 @@ void PrintConfigDef::init_fff_params()
def->label = L("Top surface expansion");
def->category = L("Strength");
def->tooltip = L("Expands the top surfaces by this distance to connect distinct top surfaces and fill gaps.\n"
"Useful for cases where the top surface is interrupted by a raised feature, such as text on a plane."
"Expanding it removes the holes beneath these features and creates a continuous path with a better finish for printing on top."
"Useful for cases where the top surface is interrupted by a raised feature, such as text on a plane. "
"Expanding it removes the holes beneath these features and creates a continuous path with a better finish for printing on top. "
"The expansion is applied to the original top surface, before any other processing such as bridging or overhang detection.");
def->sidetext = L("mm");
def->min = 0;
@@ -2849,7 +2863,7 @@ void PrintConfigDef::init_fff_params()
def->nullable = true;
def->min = 0;
def->max = max_temp;
def->sidetext = L(u8"℃" /* °C */); // degrees Celsius, CIS languages need translation
def->sidetext = L(u8"\u2103" /* °C */); // degrees Celsius, CIS languages need translation
def->set_default_value(new ConfigOptionIntsNullable{0});
def = this->add("filament_flush_volumetric_speed", coFloats);
@@ -5488,7 +5502,23 @@ void PrintConfigDef::init_fff_params()
def->height = 5;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString());
def = this->add("plugins", coStrings);
def->label = L("Plugins Used");
def->tooltip = L("Plugin capabilities referenced by this preset, stored as name;uuid;capability.");
def->mode = comDevelop;
def->set_default_value(new ConfigOptionStrings());
def = this->add("slicing_pipeline_plugin", coStrings);
def->label = L("Slicing Pipeline Plugin");
def->tooltip = L("Python plugin(s) invoked at each slicing pipeline step to read and modify intermediate slicing data, "
"including a final G-code post-processing step. Research/experimental.");
def->gui_type = ConfigOptionDef::GUIType::plugin_picker;
def->plugin_type = "slicing-pipeline";
def->full_width = true;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionStrings());
def = this->add("printer_model", coString);
def->label = L("Printer type");
def->tooltip = L("Type of the printer.");
@@ -7930,7 +7960,7 @@ void PrintConfigDef::init_fff_params()
def->label = L("Extruder change");
def->tooltip = L("To prevent oozing, the nozzle temperature will be cooled during ramming. Therefore, the ramming time must be greater than the cooldown time. 0 means disabled.");
def->mode = comAdvanced;
def->sidetext = "°C";
def->sidetext = L(u8"\u2103" /* °C */); // degrees Celsius, CIS languages need translation
def->min = 0;
def->nullable = true;
def->set_default_value(new ConfigOptionIntsNullable{0});
@@ -7960,7 +7990,7 @@ void PrintConfigDef::init_fff_params()
def->tooltip = L(
"To prevent oozing, the nozzle temperature will be cooled during ramming. Note: only a cooldown command and fan activation are triggered, reaching the target temperature is not guaranteed. 0 means disabled.");
def->mode = comAdvanced;
def->sidetext = "°C";
def->sidetext = L(u8"\u2103" /* °C */); // degrees Celsius, CIS languages need translation
def->min = 0;
def->nullable = true;
def->set_default_value(new ConfigOptionIntsNullable{0});
@@ -8011,7 +8041,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("filament_preheat_temperature_delta", coFloats);
def->label = L("Preheat temperature delta");
def->tooltip = L("Temperature delta applied during pre-heating before tool change.");
def->sidetext = "°C";
def->sidetext = L(u8"\u2103" /* °C */); // degrees Celsius, CIS languages need translation
def->mode = comDevelop;
def->nullable = true;
def->set_default_value(new ConfigOptionFloatsNullable{0});
+1
View File
@@ -1779,6 +1779,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionBool, ooze_prevention))
((ConfigOptionString, filename_format))
((ConfigOptionStrings, post_process))
((ConfigOptionStrings, slicing_pipeline_plugin))
((ConfigOptionString, printer_model))
((ConfigOptionFloat, resolution))
((ConfigOptionFloats, retraction_minimum_travel))
+1 -4
View File
@@ -376,10 +376,7 @@ SupportGeneratorLayersPtr generate_raft_base(
Polygons trimming;
// BBS: if first layer of support is intersected with object island, it must have the same function as brim unless in nobrim mode.
// brim_object_gap is changed to 0 by default, it's no longer appropriate to use it to determine the gap of first layer support.
//if (object.has_brim())
// trimming = offset(object.layers().front()->lslices, (float)scale_(object.config().brim_object_gap.value), SUPPORT_SURFACES_OFFSET_PARAMETERS);
//else
trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy_first_layer), SUPPORT_SURFACES_OFFSET_PARAMETERS);
trimming = offset(object.layers().front()->lslices, (float) scale_(support_params.gap_xy_first_layer), SUPPORT_SURFACES_OFFSET_PARAMETERS);
if (inflate_factor_1st_layer > SCALED_EPSILON) {
// Inflate in multiple steps to avoid leaking of the support 1st layer through object walls.
auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / support_params.first_layer_flow.scaled_width())));
+46 -4
View File
@@ -469,6 +469,31 @@ std::string CalibPressureAdvanceLine::generate_test(double start_pa /*= 0*/, dou
return print_pa_lines(startx, starty, start_pa, step_pa, count);
}
BoundingBoxf CalibPressureAdvanceLine::print_extents(const BoundingBoxf &bed_ext) const
{
BoundingBoxf adjusted_bed = bed_ext;
if (is_delta()) {
CalibPressureAdvanceLine::delta_scale_bed_ext(adjusted_bed);
}
double bed_width = adjusted_bed.size().x();
// m_length_long adjusts for narrow beds – exactly as in generate_test()
double line_long = 40.0 + std::min(bed_width - 120.0, 0.0);
double total_line_len = m_length_short * 2 + line_long;
double start_x = adjusted_bed.min.x() + (bed_width - 2 * m_length_short - line_long - 20.0) / 2.0;
double box_width = m_draw_numbers ? (number_spacing() * 8) : 0.0; // 3.0 * 8 = 24 mm
BoundingBoxf extent;
extent.min.x() = start_x;
extent.max.x() = start_x + total_line_len + m_line_width + box_width;
// Y bounds are the full bed (the caller will inset them by -25)
extent.min.y() = adjusted_bed.min.y();
extent.max.y() = adjusted_bed.max.y();
return extent;
}
bool CalibPressureAdvanceLine::is_delta() const { return mp_gcodegen->config().printable_area.values.size() > 4; }
std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double start_y, double start_pa, double step_pa, int num)
@@ -491,9 +516,10 @@ std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double star
const double slow = CalibPressureAdvance::speed_adjust(m_slow_speed);
std::stringstream gcode;
gcode << mp_gcodegen->writer().travel_to_z(m_height_layer + z_offset);
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) << (m_height_layer + z_offset) << "\n";
double y_pos = start_y;
// prime line
// Purge/first perimeter - acts as an anchor to the rest of the model
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Custom\n";
gcode << writer.set_pressure_advance(0.0);
auto prime_x = start_x;
gcode << move_to(Vec2d(prime_x, y_pos + (num) * m_space_y), writer);
@@ -503,10 +529,13 @@ std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double star
for (int i = 0; i < num; ++i) {
gcode << writer.set_pressure_advance(start_pa + i * step_pa);
gcode << move_to(Vec2d(start_x, y_pos + i * m_space_y), writer);
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Custom\n";
gcode << writer.set_speed(slow);
gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short, y_pos + i * m_space_y), e_per_mm * m_length_short);
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
gcode << writer.set_speed(fast);
gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long, y_pos + i * m_space_y), e_per_mm * m_length_long);
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Custom\n";
gcode << writer.set_speed(slow);
gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long + m_length_short, y_pos + i * m_space_y),
e_per_mm * m_length_short);
@@ -530,9 +559,15 @@ std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double star
const auto box_start_x = start_x + m_length_short + m_length_long + m_length_short + m_line_width;
DrawBoxOptArgs default_box_opt_args(2, m_height_layer, m_line_width, fast);
//Draw box
default_box_opt_args.is_filled = true;
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Bottom surface\n";
gcode << draw_box(writer, box_start_x, start_y - m_space_y,
number_spacing() * 8, (num + 1) * m_space_y, default_box_opt_args);
//Ensure numbers are shown on the next layer in gcode processor, as in reality
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change) << "\n";
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) << (m_height_layer*2 + z_offset) << "\n";
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Top surface\n";
gcode << writer.travel_to_z(m_height_layer*2 + z_offset);
for (int i = 0; i < num; i += 2) {
gcode << draw_number(box_start_x + 3 + m_line_width, y_pos + i * m_space_y + m_space_y / 2, start_pa + i * step_pa, m_draw_digit_mode,
@@ -595,12 +630,16 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
speed_adjust(speed_first_layer()));
// create anchoring frame
//pattern uses outer wall speed/width
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
gcode << draw_box(m_writer, m_starting_point.x(), m_starting_point.y(), print_size_x(), frame_size_y(), default_box_opt_args);
// create tab for numbers
DrawBoxOptArgs draw_box_opt_args = default_box_opt_args;
draw_box_opt_args.is_filled = true;
draw_box_opt_args.num_perimeters = wall_count();
//draw box as bottom surface, so numbers are clearly visible on top
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Bottom surface\n";
gcode << draw_box(m_writer, m_starting_point.x(), m_starting_point.y() + frame_size_y() + line_spacing_first_layer(),
print_size_x(),
max_numbering_height() + line_spacing_first_layer() + m_glyph_padding_vertical * 2, draw_box_opt_args);
@@ -611,7 +650,9 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
const double zhop_config_value = m_config.option<ConfigOptionFloats>("z_hop")->get_at(0);
const auto accel = accel_perimeter();
// draw pressure advance pattern
// Draw pressure advance pattern
// pattern uses outer wall speed, label it as such
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
for (int i = 0; i < m_num_layers; ++i) {
const double layer_height = height_first_layer() + height_z_offset() + (i * height_layer());
const double zhop_height = layer_height + zhop_config_value;
@@ -643,6 +684,7 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
m_config.option<ConfigOptionFloats>("filament_flow_ratio")->get_at(0));
// glyph on every other line
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
for (int j = 0; j < num_patterns; j += 2) {
gcode << draw_number(glyph_start_x(j), m_starting_point.y() + frame_size_y() + m_glyph_padding_vertical + line_width(),
m_params.start + (j * m_params.step), m_draw_digit_mode, line_width(), number_e_per_mm,
@@ -692,7 +734,7 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
for (int j = 0; j < num_patterns; ++j) {
// increment pressure advance
gcode << m_writer.set_pressure_advance(m_params.start + (j * m_params.step));
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
for (int k = 0; k < wall_count(); ++k) {
to_x += std::cos(to_radians(m_corner_angle) / 2) * side_length;
to_y += std::sin(to_radians(m_corner_angle) / 2) * side_length;
+3 -1
View File
@@ -254,6 +254,8 @@ class CalibPressureAdvanceLine : public CalibPressureAdvance
public:
CalibPressureAdvanceLine(GCode* gcodegen);
~CalibPressureAdvanceLine(){};
// Return the X‑bounds of the pattern on the given bed.
BoundingBoxf print_extents(const BoundingBoxf &bed_ext) const;
std::string generate_test(double start_pa = 0, double step_pa = 0.002, int count = 50);
@@ -379,4 +381,4 @@ private:
const double m_glyph_padding_vertical{1};
};
} // namespace Slic3r
} // namespace Slic3r